From 5cb6ba8403a0d1598c59c83e9fd5c83a227a29ee Mon Sep 17 00:00:00 2001 From: Luis <43929895+Luis-0508@users.noreply.github.com> Date: Mon, 2 Nov 2026 21:03:34 +0100 Subject: [PATCH] Comments: the pane keeps its width and resizes again (fixes #1431) A comment card's reply row reserved a fixed 70 px for its Resolve and delete buttons, which need more. The row overflowed the card, the pane grew by the overflow every frame until it stuck at its widest, and dragging its edge no longer changed it. The buttons are now laid out first, from the right, and the reply field takes the width they leave. --- crates/ui-egui/src/panels/comments.rs | 47 +++++++++++++++------------ crates/ui-egui/src/tests.rs | 44 +++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 21 deletions(-) diff --git a/crates/ui-egui/src/panels/comments.rs b/crates/ui-egui/src/panels/comments.rs index edf0e8b8..5acb7140 100644 --- a/crates/ui-egui/src/panels/comments.rs +++ b/crates/ui-egui/src/panels/comments.rs @@ -130,29 +130,34 @@ fn comment_card(app: &mut SlideApp, ui: &mut Ui, slide: usize, index: usize, c: for r in &c.replies { ui.label(egui::RichText::new(format!("↳ {}: {}", r.author, r.text)).color(t.text_dim)); } + // The buttons are laid out first, from the right, so the reply field takes exactly the + // width they leave. A fixed allowance overflowed the card and widened the pane every + // frame until it stuck at its widest (#1431). ui.horizontal(|ui| { - let id = ui.scope_id().with(("reply", slide, index)); - let mut txt: String = ui.data_mut(|d| d.get_temp(id).unwrap_or_default()); - let r = ui.add(egui::TextEdit::singleline(&mut txt).hint_text("Reply…").desired_width(ui.available_width() - 70.0)); - if r.lost_focus() && ui.input(|i| i.key_pressed(egui::Key::Enter)) && !txt.trim().is_empty() { - focus_comment(app, slide, index); - run(app, "comment.reply", json!({"index": index, "text": txt})); - txt.clear(); - } - ui.data_mut(|d| d.insert_temp(id, txt)); - if ui.small_button(if c.resolved { "Reopen" } else { "Resolve" }).clicked() { - focus_comment(app, slide, index); - run(app, "comment.resolve", json!({"index": index, "resolved": !c.resolved})); - } - if ui.small_button("🗑").clicked() { - let _ = app.run("slide.go", json!({"index": slide})); - let _ = app.run("comment.delete", json!({"index": index})); - match app.ui.comment_focus { - Some((fs, fi)) if fs == slide && fi == index => app.ui.comment_focus = None, - Some((fs, fi)) if fs == slide && fi > index => app.ui.comment_focus = Some((fs, fi - 1)), - _ => {} + ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { + if ui.small_button("🗑").clicked() { + let _ = app.run("slide.go", json!({"index": slide})); + let _ = app.run("comment.delete", json!({"index": index})); + match app.ui.comment_focus { + Some((fs, fi)) if fs == slide && fi == index => app.ui.comment_focus = None, + Some((fs, fi)) if fs == slide && fi > index => app.ui.comment_focus = Some((fs, fi - 1)), + _ => {} + } } - } + if ui.small_button(if c.resolved { "Reopen" } else { "Resolve" }).clicked() { + focus_comment(app, slide, index); + run(app, "comment.resolve", json!({"index": index, "resolved": !c.resolved})); + } + let id = ui.scope_id().with(("reply", slide, index)); + let mut txt: String = ui.data_mut(|d| d.get_temp(id).unwrap_or_default()); + let r = ui.add(egui::TextEdit::singleline(&mut txt).hint_text("Reply…").desired_width(ui.available_width())); + if r.lost_focus() && ui.input(|i| i.key_pressed(egui::Key::Enter)) && !txt.trim().is_empty() { + focus_comment(app, slide, index); + run(app, "comment.reply", json!({"index": index, "text": txt})); + txt.clear(); + } + ui.data_mut(|d| d.insert_temp(id, txt)); + }); }); }); if focused { diff --git a/crates/ui-egui/src/tests.rs b/crates/ui-egui/src/tests.rs index 29fa755f..5221544a 100644 --- a/crates/ui-egui/src/tests.rs +++ b/crates/ui-egui/src/tests.rs @@ -2361,3 +2361,47 @@ fn overdue_visible_logic_does_not_reanchor_a_later_hidden_show() { let _ = ctx.run_logic(&raw_at(12.0, true, false), |ctx| app.tick_hidden_show(ctx, visible_anchor + std::time::Duration::from_secs(3))); assert_eq!(app.show.as_ref().unwrap().state.slide, 1, "time from the full visible frame is preserved through the logic-only pass"); } + +/// #1431: a comment card's reply row was wider than the pane, so the Comments pane grew every +/// frame until it stuck at its widest and dragging its edge did nothing. +#[test] +fn comments_pane_keeps_its_width_and_resizes() { + let mut h = egui_kittest::Harness::builder().with_size(egui::vec2(1200.0, 800.0)).build_ui_state( + |ui, app: &mut SlideApp| { + app.logic(ui.ctx()); + app.ui(ui); + }, + SlideApp::new(Session::with_new(), Services::default()), + ); + h.run_steps(3); + h.state_mut().run("comment.add", json!({"text": "Check the figures on this slide"})).unwrap(); + h.state_mut().run("comment.add", json!({"text": "Resolved one"})).unwrap(); + h.state_mut().run("comment.resolve", json!({"index": 1, "resolved": true})).unwrap(); + h.state_mut().run("view.pane", json!({"pane": "comments"})).unwrap(); + h.run_steps(30); + let width = h.state().ui.pane_width; + assert!(width < 320.0, "the pane keeps its default width, not {width}"); + // The cards stay compact: the second one starts near the top, not below a stretched first. + let second = h.get_by_label("Resolved one").rect(); + assert!(second.top() < 450.0, "the second comment sits near the top, at {second:?}"); + + // Drag the pane's left edge: right narrows it, left widens it. + let drag = |h: &mut egui_kittest::Harness<'_, SlideApp>, dx: f32| { + let start = egui::pos2(1200.0 - h.state().ui.pane_width - 1.0, 500.0); + let end = start + egui::vec2(dx, 0.0); + h.event(egui::Event::PointerMoved(start)); + h.run_steps(1); + h.event(egui::Event::PointerButton { pos: start, button: egui::PointerButton::Primary, pressed: true, modifiers: egui::Modifiers::NONE }); + for i in 1..=6 { + h.event(egui::Event::PointerMoved(start + (end - start) * (i as f32 / 6.0))); + h.run_steps(1); + } + h.event(egui::Event::PointerButton { pos: end, button: egui::PointerButton::Primary, pressed: false, modifiers: egui::Modifiers::NONE }); + h.run_steps(10); + h.state().ui.pane_width + }; + let narrow = drag(&mut h, 50.0); + assert!(narrow < width - 40.0, "dragging right narrows the pane: {width} -> {narrow}"); + let wide = drag(&mut h, -150.0); + assert!(wide > narrow + 140.0, "dragging left widens the pane: {narrow} -> {wide}"); +}