Browse / github.com/storytold/deckcraft / patch
Animations: the Add Animation menu scrolls when it is taller than the window (fixes #1370)
bug: "Add Animation" menu is very tall and does not scroll when pressing "Add Animation" in the ribbon, the menu that pops out is taller than my 1440p screen and does not scroll so the bottom entries are not reachable. <img width="260" height="1396" alt="Image" src="https://github.com/user-attachments/assets/dd6c489f-ec40-482d-9da6-81f95d6a9db4" /> i think it would make sense to have a 3-column menu for Entrance, Emphasis and Exit, which is more structured and makes better use of screen space. perhaps it should be submenus on smaller screens/windows. in any case, it should definitely scroll if it doesn't fit on screen. i'm using the Flatpak version
- Status
- open
- Score
- 0.00
- Adopters
- 0 other operators
- Author
- SusieeTheLinuxUser (imported from pull request #1438, open)
- Base
27009cd941b22cb0788cd5040f9fad91472230c7- Size
- +86 −16 in 2 files
- Review
- pending
- Shared
- 2026-10-28
- Id
626d5387b559ff175dceec2f
How it went
No other operator has reported using it yet.
Diff
raw · crates/ui-egui/src/add_animation_menu_tests.rs crates/ui-egui/src/ribbon.rs
new file mode 100644@@ -0,0 +1,61 @@+//! Add Animation (#1370): the menu scrolls, so the last effect can be reached and added.++use deckcraft_engine::Session;+use egui_kittest::Harness;+use egui_kittest::kittest::Queryable;+use serde_json::json;++use crate::{Services, SlideApp};++fn harness(size: egui::Vec2) -> Harness<'static, SlideApp> {+ let mut session = Session::with_new();+ let id = session.execute("shape.insert", &json!({"preset": "rect", "rect": [80.0, 80.0, 200.0, 120.0]})).unwrap()["id"].as_u64().unwrap();+ session.execute("edit.select", &json!({"ids": [id]})).unwrap();+ let mut app = SlideApp::new(session, Services::default());+ app.ui.tab = "Animations".into();+ let mut h = Harness::builder().with_size(size).with_step_dt(1.0 / 60.0).build_ui_state(+ |ui, app: &mut SlideApp| {+ app.logic(ui.ctx());+ app.ui(ui);+ },+ app,+ );+ h.run_steps(3);+ h+}++fn open_add_animation(h: &mut Harness<'static, SlideApp>, window: egui::Vec2) {+ // The Animations ribbon is wider than a short window. The edge chevron is what a person uses+ // to reach Add Animation; scroll-into-view on the button does not move that bar.+ for _ in 0..12 {+ let rect = h.get_by_label("Add Animation").rect();+ if rect.min.x >= 8.0 && rect.max.x <= window.x - 8.0 {+ break;+ }+ let Some(cue) = h.query_by_label("Scroll ribbon right") else { break };+ cue.click();+ h.run_steps(2);+ }+ h.get_by_label("Add Animation").click();+ h.run_steps(3);+}++fn added_effect(h: &Harness<'_, SlideApp>) -> Option<(String, deckcraft_model::anim::AnimClass)> {+ h.state().session.active().and_then(|d| d.current_slide()).and_then(|s| s.animations.first()).map(|a| (a.effect.clone(), a.class))+}++#[test]+fn the_last_effect_is_reachable_by_scrolling() {+ let size = egui::vec2(800.0, 700.0);+ let mut h = harness(size);+ open_add_animation(&mut h, size);+ let before = h.get_by_label("Neutron — Motion").rect();+ assert!(before.min.y > 700.0, "the last effect starts below the window: {before:?}");+ h.get_by_label("Neutron — Motion").scroll_to_me();+ h.run_steps(4);+ let row = h.get_by_label("Neutron — Motion").rect();+ assert!(row.min.y >= 0.0 && row.max.y <= 700.0, "a scrolled effect stays inside the window: {row:?}");+ h.get_by_label("Neutron — Motion").click();+ h.run_steps(3);+ assert_eq!(added_effect(&h), Some(("neutron".into(), deckcraft_model::anim::AnimClass::Path)));+}@@ -2091,24 +2091,29 @@ fn animations(app: &mut SlideApp, ui: &mut Ui) { }); group(ui, |ui| { let r = big_button(ui, Icon::AddAnimation, "Add\nAnimation", has_sel);+ // Every effect in one column is taller than a 1440p screen, so cap the height and scroll (#1370).+ // The menu opens under the title bar and the ribbon; keep that chrome out of the cap.+ let max_h = (r.ctx.viewport_rect().height() - 160.0).max(120.0); egui::Popup::menu(&r).width(260.0).show(|ui| {- for a in deckcraft_model::anim::ANIMATIONS.iter().filter(|a| a.2 != AnimClass::Media) {- if ui- .button(format!(- "{} — {}",- a.1,- match a.2 {- AnimClass::Entrance => "Entrance",- AnimClass::Emphasis => "Emphasis",- AnimClass::Exit => "Exit",- _ => "Motion",- }- ))- .clicked()- {- run(app, "animation.add", json!({"effect": a.0, "class": a.2.xml()}));+ egui::ScrollArea::vertical().id_salt("add_animation").max_height(max_h).show(ui, |ui| {+ for a in deckcraft_model::anim::ANIMATIONS.iter().filter(|a| a.2 != AnimClass::Media) {+ if ui+ .button(format!(+ "{} — {}",+ a.1,+ match a.2 {+ AnimClass::Entrance => "Entrance",+ AnimClass::Emphasis => "Emphasis",+ AnimClass::Exit => "Exit",+ _ => "Motion",+ }+ ))+ .clicked()+ {+ run(app, "animation.add", json!({"effect": a.0, "class": a.2.xml()}));+ } }- }+ }); }); big(app, ui, Icon::AnimationPane, "Animation\nPane", "view.pane", json!({"pane": "animation"})); let r = big_button(ui, Icon::Trigger, "Trigger", cur.is_some());@@ -3633,6 +3638,10 @@ mod master_layout_tests; #[path = "icons_popup_tests.rs"] mod icons_popup_tests; +#[cfg(test)]+#[path = "add_animation_menu_tests.rs"]+mod add_animation_menu_tests;+ #[cfg(test)] #[path = "align_tests.rs"] mod align_tests;