mirror of
https://github.com/storytold/deckcraft.git
synced 2026-11-09 20:04:46 +00:00
Animations: the Add Animation menu scrolls when it is taller than the window (fixes #1370)
Every effect in one column is taller than a 1440p screen, and the popup did not scroll, so the last entries could not be reached. Cap the menu at the window height minus the title bar and ribbon, and scroll inside it. Co-Authored-By: Grok 4.7 <noreply@x.ai> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
27009cd941
commit
62ea604394
2 files changed
+86
-16
No files matched your search
@@ -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;
|
||||
|
||||
Reference in new issue
Block a user