From a747308dfcfb7e129690f4d9a0f94ae8fe29aacf Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 11 Nov 2026 09:18:56 +0000 Subject: [PATCH] =?UTF-8?q?Unfold=20Recent=20changes=20inside=20the=20"?= =?UTF-8?q?=E2=80=A6"=20menu?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Recent changes was a side menu that opened on hover. At the window's right edge, where the "…" menu is, it opened over the menu itself, so moving the pointer down past it to Export or Preferences covered them for a while. It now unfolds in place on a click, newest first: - At most about ten changes tall (and 40% of the screen), and the rest scroll, so with the full hundred the menu still fits. - A fixed width, so a long name is cut with "…" (whole on hover) instead of widening the menu. - Folded and at the top each time the menu opens. The "…" menu now closes only on a click outside or an item, as the strip and bus menus do, so the unfolding click doesn't close it. Its items close it themselves; the list's items name the menu (close_kind), since a plain close() closed the fold around them. Tried under Xvfb with 60 changes: unfold, scroll, a long name cut and shown on hover, reopening folded at the top, and picking the third change undid three and closed the menu. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01PWaMWDrXpfpALWtEnsyueZ --- crates/gui/src/app/mod.rs | 6 +++ crates/gui/src/app/top_bar.rs | 72 ++++++++++++++++++++++++++--------- docs/USER_GUIDE.md | 5 ++- 3 files changed, 64 insertions(+), 19 deletions(-) diff --git a/crates/gui/src/app/mod.rs b/crates/gui/src/app/mod.rs index edfb557..a6a6dca 100644 --- a/crates/gui/src/app/mod.rs +++ b/crates/gui/src/app/mod.rs @@ -225,6 +225,10 @@ pub struct App { clear_seen_asked: bool, /// What can be undone and redone, as of this frame. history: HistoryInfo, + /// The pass in which the "…" menu was last drawn, to tell when it + /// has just opened, and how often it has opened. + main_menu_pass: u64, + main_menu_opens: u64, /// Strips and buses that have clipped since their CLIP light was last /// cleared. clips: HashSet, @@ -311,6 +315,8 @@ impl App { seen_filter: String::new(), clear_seen_asked: false, history: HistoryInfo::default(), + main_menu_pass: 0, + main_menu_opens: 0, reductions: HashMap::new(), desktop_font: None, spectrum_watch: Vec::new(), diff --git a/crates/gui/src/app/top_bar.rs b/crates/gui/src/app/top_bar.rs index abe7cf5..390b87f 100644 --- a/crates/gui/src/app/top_bar.rs +++ b/crates/gui/src/app/top_bar.rs @@ -45,7 +45,9 @@ impl App { } // Right to left, so the first here is the rightmost. ui.with_layout(Layout::right_to_left(Align::Center), |ui| { - ui.menu_button("…", |ui| self.main_menu(ui)); + // Clicking "Recent changes" unfolds it: only a + // click outside, or an item, closes this menu. + widgets::field_menu(ui, "…", |ui| self.main_menu(ui)); if ui .add_enabled(connected, egui::Button::new("Hotkeys")) .on_hover_text( @@ -110,22 +112,57 @@ impl App { self.step_history(HistoryKey::Redo); ui.close(); } - ui.add_enabled_ui(!self.history.undo.is_empty(), |ui| { - ui.menu_button("Recent changes", |ui| { - ui.label( - RichText::new("Click one to undo back to before it") - .size(11.0) - .color(theme::p().text_dim), - ); - let entries = self.history.undo.clone(); - for (k, e) in entries.iter().enumerate() { - let r = ui.add(egui::Button::new(&e.label).shortcut_text(ago(e.at_ms))); - if r.clicked() { - self.undo_steps(k + 1); - ui.close(); - } - } - }); + // The list unfolds in place. As a side menu it opened on hover, and + // at the window's right edge it covered the items below it while + // the pointer passed on its way down. A long list scrolls, about + // ten changes tall, so the menu never grows past that. + // Each time the menu opens, the list starts folded and at its top, + // the newest change. + let pass = ui.ctx().cumulative_pass_nr(); + let just_opened = pass > self.main_menu_pass + 1; + self.main_menu_pass = pass; + if just_opened { + self.main_menu_opens += 1; + } + let opens = self.main_menu_opens; + let any = !self.history.undo.is_empty(); + ui.add_enabled_ui(any, |ui| { + egui::CollapsingHeader::new("Recent changes") + .id_salt("recent_changes") + .open((!any || just_opened).then_some(false)) + .show(ui, |ui| { + ui.label( + RichText::new("Click one to undo back to before it") + .size(11.0) + .color(theme::p().text_dim), + ); + let row = ui.spacing().interact_size.y + ui.spacing().item_spacing.y; + let max_h = (10.0 * row).min(ui.content_rect().height() * 0.4); + let entries = self.history.undo.clone(); + // A width of its own: sized by the text, a long name + // would widen the whole menu. Long names are cut with + // "…" and shown whole on hover. + let list_w = 240.0; + // A new list each opening, so it starts at the top. + egui::ScrollArea::vertical() + .id_salt(("recent_changes_list", opens)) + .max_height(max_h) + .show(ui, |ui| { + ui.set_width(list_w); + ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Truncate); + for (k, e) in entries.iter().enumerate() { + let button = egui::Button::new(&e.label) + .shortcut_text(ago(e.at_ms)) + .min_size(vec2(list_w, 0.0)); + let r = ui.add(button).on_hover_text(&e.label); + if r.clicked() { + self.undo_steps(k + 1); + // The menu, not the fold this sits in. + ui.close_kind(egui::UiKind::Menu); + } + } + }); + }); }); ui.separator(); if ui @@ -158,6 +195,7 @@ impl App { } if ui.button("Quit window").clicked() { ui.ctx().send_viewport_cmd(egui::ViewportCommand::Close); + ui.close(); } } diff --git a/docs/USER_GUIDE.md b/docs/USER_GUIDE.md index 30a0611..38b9f7e 100644 --- a/docs/USER_GUIDE.md +++ b/docs/USER_GUIDE.md @@ -907,8 +907,9 @@ out](API.md#exported-files). **Ctrl+Z** takes back the last change, and **Ctrl+Shift+Z** or **Ctrl+Y** brings it back, in the mixer and in settings windows alike. One fader drag is one step, however long it was. The **…** menu at the top names what the -next undo and redo are, and **Recent changes** lists the last few, so you -can jump back to before any of them. After removing a strip or bus, a +next undo and redo are. Click **Recent changes** there to unfold the last +changes, newest first (the list scrolls when it is long), and click one +to jump back to before it. After removing a strip or bus, a message at the bottom of the window offers to undo it. The history covers changes made from anywhere, including `weirctl` and