Merge pull request #1438

Animations: the Add Animation menu scrolls when it is taller than the window (fixes #1370)
This commit is contained in:
Brandon Thomas committed 2026-10-30 16:05:27 -04:00
commit cee1d015ec
3 files changed
+85 -16

No files matched your search

+3
View File
@@ -429,6 +429,9 @@ mod master_layout_tests;
#[cfg(test)]
mod icons_popup_tests;
#[cfg(test)]
mod add_animation_menu_tests;
#[cfg(test)]
mod align_tests;
@@ -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)));
}
+21 -16
View File
@@ -103,24 +103,29 @@ pub(super) 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());