From d5bbf730460a65679d667519024c41ffbef965f6 Mon Sep 17 00:00:00 2001 From: Altynzhan Karybai Date: Sat, 24 Oct 2026 01:51:07 +0500 Subject: [PATCH 1/4] Fix animation Browse Presets command (#2172) --- crates/ui-egui/src/menus.rs | 8 +++++++- crates/ui-egui/tests/it/ui_menus.rs | 16 ++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/crates/ui-egui/src/menus.rs b/crates/ui-egui/src/menus.rs index 92893dcb6..c9e527ae3 100644 --- a/crates/ui-egui/src/menus.rs +++ b/crates/ui-egui/src/menus.rs @@ -1317,7 +1317,13 @@ pub fn frontend(app: &mut EffectcraftApp, ctx: &egui::Context, id: &str, p: Valu app.ui.home_templates = true; Value::Null } - "effect.manage" | "anim.browsePresets" => { + "effect.manage" => { + app.show_panel(PanelKind::EffectsPresets); + Value::Null + } + "anim.browsePresets" => { + app.ui.effects_view.presets = true; + app.ui.effects_open.insert("presets".into()); app.show_panel(PanelKind::EffectsPresets); Value::Null } diff --git a/crates/ui-egui/tests/it/ui_menus.rs b/crates/ui-egui/tests/it/ui_menus.rs index ca4c8cabc..b06dfaa22 100644 --- a/crates/ui-egui/tests/it/ui_menus.rs +++ b/crates/ui-egui/tests/it/ui_menus.rs @@ -46,6 +46,22 @@ fn shown(h: &Harness<'_, EffectcraftApp>, label: &str) -> bool { h.query_by_label_contains(label).is_some() } +#[test] +fn browse_presets_opens_the_animation_presets_list() { + use effectcraft_ui_egui::dock::PanelKind; + + let mut app = EffectcraftApp::new(Session::default()); + app.ui.effects_view.presets = false; + app.ui.effects_open.clear(); + + let result = effectcraft_ui_egui::menus::invoke(&mut app, &egui::Context::default(), "anim.browsePresets", json!({})); + + assert!(result.is_ok()); + assert_eq!(app.ui.focused, PanelKind::EffectsPresets); + assert!(app.ui.effects_view.presets); + assert!(app.ui.effects_open.contains("presets")); +} + /// Space (or any shortcut) while a menu is open goes to the menu, not the app: it started the /// preview behind the open menu. #[test] From 7d7382c14e82dffa44556dd95f77758663752e1f Mon Sep 17 00:00:00 2001 From: Altynzhan Karybai Date: Sat, 24 Oct 2026 02:36:31 +0500 Subject: [PATCH 2/4] Make Browse Presets navigate to visible section (#2172) --- crates/ui-egui/src/menus.rs | 2 ++ crates/ui-egui/tests/it/ui_menus.rs | 31 ++++++++++++++++++++--------- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/crates/ui-egui/src/menus.rs b/crates/ui-egui/src/menus.rs index c9e527ae3..284f16b23 100644 --- a/crates/ui-egui/src/menus.rs +++ b/crates/ui-egui/src/menus.rs @@ -1322,8 +1322,10 @@ pub fn frontend(app: &mut EffectcraftApp, ctx: &egui::Context, id: &str, p: Valu Value::Null } "anim.browsePresets" => { + app.ui.effects_search.clear(); app.ui.effects_view.presets = true; app.ui.effects_open.insert("presets".into()); + let _ = ctx.data_mut(|data| data.remove_temp::<(f32, f32)>(egui::Id::new("fx-scroll"))); app.show_panel(PanelKind::EffectsPresets); Value::Null } diff --git a/crates/ui-egui/tests/it/ui_menus.rs b/crates/ui-egui/tests/it/ui_menus.rs index b06dfaa22..14e94a400 100644 --- a/crates/ui-egui/tests/it/ui_menus.rs +++ b/crates/ui-egui/tests/it/ui_menus.rs @@ -47,19 +47,32 @@ fn shown(h: &Harness<'_, EffectcraftApp>, label: &str) -> bool { } #[test] -fn browse_presets_opens_the_animation_presets_list() { +fn browse_presets_clears_search_and_scrolls_to_the_visible_presets_list() { use effectcraft_ui_egui::dock::PanelKind; - let mut app = EffectcraftApp::new(Session::default()); - app.ui.effects_view.presets = false; - app.ui.effects_open.clear(); + let mut h = harness(); + let ctx = h.ctx.clone(); - let result = effectcraft_ui_egui::menus::invoke(&mut app, &egui::Context::default(), "anim.browsePresets", json!({})); + let managed = effectcraft_ui_egui::menus::invoke(h.state_mut(), &ctx, "effect.manage", json!({})); + assert!(managed.is_ok(), "{managed:?}"); + assert_eq!(h.state().ui.focused, PanelKind::EffectsPresets); - assert!(result.is_ok()); - assert_eq!(app.ui.focused, PanelKind::EffectsPresets); - assert!(app.ui.effects_view.presets); - assert!(app.ui.effects_open.contains("presets")); + h.state_mut().ui.effects_search = "no matching animation preset".into(); + let scroll_id = egui::Id::new("fx-scroll"); + h.ctx.data_mut(|data| data.insert_temp(scroll_id, (480.0_f32, 960.0_f32))); + assert_eq!(h.ctx.data(|data| data.get_temp::<(f32, f32)>(scroll_id)), Some((480.0, 960.0))); + + let result = effectcraft_ui_egui::menus::invoke(h.state_mut(), &ctx, "anim.browsePresets", json!({})); + + assert!(result.is_ok(), "{result:?}"); + assert_eq!(h.state().ui.focused, PanelKind::EffectsPresets); + assert!(h.state().ui.effects_search.is_empty()); + assert!(h.state().ui.effects_view.presets); + assert!(h.state().ui.effects_open.contains("presets")); + assert!(h.ctx.data(|data| data.get_temp::<(f32, f32)>(scroll_id)).is_none()); + + h.run_steps(3); + assert!(h.state().auto.find("effects.category.presets").is_some(), "Animation Presets must be visible after Browse Presets"); } /// Space (or any shortcut) while a menu is open goes to the menu, not the app: it started the From 60011b72f330ae6c378116b56fdd2b9f6c6bf1b8 Mon Sep 17 00:00:00 2001 From: Altynzhan Karybai Date: Wed, 28 Oct 2026 21:02:17 +0500 Subject: [PATCH 3/4] Keep Browse Presets section visible (#2172) --- crates/ui-egui/src/menus/frontend.rs | 10 ++++++- crates/ui-egui/src/panels/effects_presets.rs | 31 ++++++++++++++++++-- crates/ui-egui/tests/it/ui_menus.rs | 24 +++++++++++---- 3 files changed, 56 insertions(+), 9 deletions(-) diff --git a/crates/ui-egui/src/menus/frontend.rs b/crates/ui-egui/src/menus/frontend.rs index f536086d9..2000cb31c 100644 --- a/crates/ui-egui/src/menus/frontend.rs +++ b/crates/ui-egui/src/menus/frontend.rs @@ -479,7 +479,15 @@ pub fn frontend(app: &mut EffectcraftApp, ctx: &egui::Context, id: &str, p: Valu app.ui.home_templates = true; Value::Null } - "effect.manage" | "anim.browsePresets" => { + "effect.manage" => { + app.show_panel(PanelKind::EffectsPresets); + Value::Null + } + "anim.browsePresets" => { + app.ui.effects_search.clear(); + app.ui.effects_view.presets = true; + app.ui.effects_open.insert("presets".into()); + ctx.data_mut(|data| data.insert_temp(egui::Id::unique("fx-reveal-presets"), true)); app.show_panel(PanelKind::EffectsPresets); Value::Null } diff --git a/crates/ui-egui/src/panels/effects_presets.rs b/crates/ui-egui/src/panels/effects_presets.rs index 380d1d869..d7cb7de43 100644 --- a/crates/ui-egui/src/panels/effects_presets.rs +++ b/crates/ui-egui/src/panels/effects_presets.rs @@ -157,6 +157,24 @@ fn folder_row( open } +/// Number of visible rows before Animation Presets in the current panel state. +fn rows_before_presets(favorites: usize, recent: usize, searching: bool, favorites_open: bool, recent_open: bool) -> usize { + let mut rows = 0usize; + if favorites > 0 { + rows = rows.saturating_add(1); + if searching || favorites_open { + rows = rows.saturating_add(favorites); + } + } + if !searching && recent > 0 { + rows = rows.saturating_add(1); + if recent_open { + rows = rows.saturating_add(recent); + } + } + rows +} + /// An effect row (fx icon, highlighted name, favourite star, 32 bpc badge). #[allow(clippy::too_many_arguments)] fn effect_row( @@ -364,7 +382,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { let view = app.ui.effects_view.clone(); let list = Rect::from_min_max(pos2(rect.min.x, sr.max.y + 6.0), rect.max); let lp = p.with_clip_rect(list); - let scroll = widgets::PanelScroll::begin(ui, egui::Id::unique("fx-scroll"), list); + let mut scroll = widgets::PanelScroll::begin(ui, egui::Id::unique("fx-scroll"), list); let query = q.trim().to_lowercase(); let searching = !query.is_empty(); let mut y = list.min.y - scroll.offset; @@ -378,6 +396,16 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { // Favorites and Recently Used. let favs: Vec<_> = app.session.prefs.effects_favorites.iter().filter_map(|id| lookup(id)).filter(|e| keep(e)).collect(); + let recent: Vec<_> = app.ui.effects_recent.iter().filter_map(|id| lookup(id)).filter(|e| keep(e)).collect(); + let reveal_presets = ctx.data_mut(|data| data.remove_temp::(egui::Id::unique("fx-reveal-presets"))).unwrap_or(false); + if reveal_presets { + // These rows come before Animation Presets. Scroll past expanded lists so the section is + // visible even when Favorites or Recently Used is long. + let preceding_rows = + rows_before_presets(favs.len(), recent.len(), searching, app.ui.effects_open.contains("favorites"), app.ui.effects_open.contains("recent")); + scroll.offset = ROW_H * preceding_rows as f32; + y = list.min.y - scroll.offset; + } if !favs.is_empty() && folder_row(app, ui, &lp, list, &mut y, "favorites", "Favorites", 0.0, searching, Some(Icon::Star)) { let mut favs = favs; favs.sort_by_key(|e| e.name); @@ -385,7 +413,6 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { effect_row(app, ui, &lp, list, &mut y, e, 0.0, &query, "effects.favorites", &mut apply); } } - let recent: Vec<_> = app.ui.effects_recent.iter().filter_map(|id| lookup(id)).filter(|e| keep(e)).collect(); if !recent.is_empty() && !searching && folder_row(app, ui, &lp, list, &mut y, "recent", "Recently Used", 0.0, false, None) { for e in recent { effect_row(app, ui, &lp, list, &mut y, e, 0.0, &query, "effects.recent", &mut apply); diff --git a/crates/ui-egui/tests/it/ui_menus.rs b/crates/ui-egui/tests/it/ui_menus.rs index be598b7c3..88612fe0b 100644 --- a/crates/ui-egui/tests/it/ui_menus.rs +++ b/crates/ui-egui/tests/it/ui_menus.rs @@ -71,15 +71,25 @@ fn browse_presets_clears_search_and_scrolls_to_the_visible_presets_list() { let mut h = harness(); let ctx = h.ctx.clone(); + let favorites: Vec = effectcraft_engine::effects::all().iter().map(|effect| effect.id.to_string()).collect(); + assert!(!favorites.is_empty(), "the test needs registered effects"); + let recent: Vec = favorites.iter().take(8).cloned().collect(); + + h.state_mut().session.prefs.effects_favorites = favorites; + h.state_mut().ui.effects_recent = recent; + h.state_mut().ui.effects_open.extend(["favorites".into(), "recent".into()]); + h.state_mut().ui.effects_search = "no matching animation preset".into(); + h.state_mut().ui.effects_view.presets = true; let managed = effectcraft_ui_egui::menus::invoke(h.state_mut(), &ctx, "effect.manage", json!({})); assert!(managed.is_ok(), "{managed:?}"); assert_eq!(h.state().ui.focused, PanelKind::EffectsPresets); + assert_eq!(h.state().ui.effects_search, "no matching animation preset", "Manage Effects keeps its existing search behavior"); + assert!(h.state().ui.effects_view.presets, "Manage Effects keeps the existing presets setting"); - h.state_mut().ui.effects_search = "no matching animation preset".into(); - let scroll_id = egui::Id::new("fx-scroll"); - h.ctx.data_mut(|data| data.insert_temp(scroll_id, (480.0_f32, 960.0_f32))); - assert_eq!(h.ctx.data(|data| data.get_temp::<(f32, f32)>(scroll_id)), Some((480.0, 960.0))); + h.run_steps(3); + assert!(h.state().auto.find("effects.category.presets").is_none(), "the unmatched search hides Animation Presets"); + h.state_mut().ui.effects_view.presets = false; let result = effectcraft_ui_egui::menus::invoke(h.state_mut(), &ctx, "anim.browsePresets", json!({})); @@ -88,10 +98,12 @@ fn browse_presets_clears_search_and_scrolls_to_the_visible_presets_list() { assert!(h.state().ui.effects_search.is_empty()); assert!(h.state().ui.effects_view.presets); assert!(h.state().ui.effects_open.contains("presets")); - assert!(h.ctx.data(|data| data.get_temp::<(f32, f32)>(scroll_id)).is_none()); h.run_steps(3); - assert!(h.state().auto.find("effects.category.presets").is_some(), "Animation Presets must be visible after Browse Presets"); + assert!( + h.state().auto.find("effects.category.presets").is_some(), + "Animation Presets must be visible after Browse Presets, even below expanded Favorites and Recently Used" + ); } /// Space (or any shortcut) while a menu is open goes to the menu, not the app: it started the From 102b20207d598fd8b20ddd3ea5878b518ccd2e6e Mon Sep 17 00:00:00 2001 From: Altynzhan Karybai Date: Thu, 29 Oct 2026 19:43:50 +0500 Subject: [PATCH 4/4] Refactor animation presets reveal ID --- crates/ui-egui/src/menus/frontend.rs | 2 +- crates/ui-egui/src/panels/effects_presets.rs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/ui-egui/src/menus/frontend.rs b/crates/ui-egui/src/menus/frontend.rs index 2000cb31c..180c623b0 100644 --- a/crates/ui-egui/src/menus/frontend.rs +++ b/crates/ui-egui/src/menus/frontend.rs @@ -487,7 +487,7 @@ pub fn frontend(app: &mut EffectcraftApp, ctx: &egui::Context, id: &str, p: Valu app.ui.effects_search.clear(); app.ui.effects_view.presets = true; app.ui.effects_open.insert("presets".into()); - ctx.data_mut(|data| data.insert_temp(egui::Id::unique("fx-reveal-presets"), true)); + ctx.data_mut(|data| data.insert_temp(egui::Id::unique(crate::panels::effects_presets::REVEAL_PRESETS_ID), true)); app.show_panel(PanelKind::EffectsPresets); Value::Null } diff --git a/crates/ui-egui/src/panels/effects_presets.rs b/crates/ui-egui/src/panels/effects_presets.rs index d7cb7de43..44c77ec22 100644 --- a/crates/ui-egui/src/panels/effects_presets.rs +++ b/crates/ui-egui/src/panels/effects_presets.rs @@ -17,6 +17,7 @@ use crate::{EffectcraftApp, widgets}; const ROW_H: f32 = 21.0; const RECENT_MAX: usize = 8; +pub(crate) const REVEAL_PRESETS_ID: &str = "fx-reveal-presets"; /// A user animation preset file. #[derive(Clone, Debug, PartialEq)] @@ -397,7 +398,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { // Favorites and Recently Used. let favs: Vec<_> = app.session.prefs.effects_favorites.iter().filter_map(|id| lookup(id)).filter(|e| keep(e)).collect(); let recent: Vec<_> = app.ui.effects_recent.iter().filter_map(|id| lookup(id)).filter(|e| keep(e)).collect(); - let reveal_presets = ctx.data_mut(|data| data.remove_temp::(egui::Id::unique("fx-reveal-presets"))).unwrap_or(false); + let reveal_presets = ctx.data_mut(|data| data.remove_temp::(egui::Id::unique(REVEAL_PRESETS_ID))).unwrap_or(false); if reveal_presets { // These rows come before Animation Presets. Scroll past expanded lists so the section is // visible even when Favorites or Recently Used is long.