Window menu: choosing a checked panel closes it, as in After Effects (fixes #1630)

This commit is contained in:
Marco Arnulfo
2026-10-16 10:25:08 +02:00
parent 08627d4afa
commit e228f712a9
3 changed files with 52 additions and 1 deletions
+14 -1
View File
@@ -1521,6 +1521,19 @@ pub(crate) fn panel_shown(app: &EffectcraftApp, p: PanelKind) -> bool {
}
}
/// The command a menu entry runs when chosen from a menu: Window ▸ <panel> closes a panel whose
/// entry is checked, as in After Effects (#1630). Shortcuts and agents calling `window.panel`
/// still only show the panel.
pub(crate) fn menu_choice(app: &EffectcraftApp, id: String, params: Value) -> (String, Value) {
if id == "window.panel"
&& let Some(p) = params.get("panel").and_then(Value::as_str).and_then(PanelKind::from_name)
&& panel_shown(app, p)
{
return ("window.closePanel".into(), params);
}
(id, params)
}
/// Check-mark state of frontend toggles (engine state is answered by the engine).
pub(crate) fn entry_checked(app: &EffectcraftApp, e: &MenuEntry) -> Option<bool> {
let v = &app.ui.viewer;
@@ -1990,7 +2003,7 @@ pub fn menu_bar(app: &mut EffectcraftApp, ui: &mut egui::Ui) {
});
});
crate::menu_keys::handle(&ctx, &nav);
if let Some((id, params)) = clicked
if let Some((id, params)) = clicked.map(|(id, params)| menu_choice(app, id, params))
&& let Err(e) = invoke(app, &ctx, &id, if params.is_null() { json!({}) } else { params })
{
app.ui.status = e;
+18
View File
@@ -376,6 +376,7 @@ pub fn activate(app: &mut EffectcraftApp, ctx: &egui::Context, menu: &NativeMenu
}
}
let params = if params.is_null() { json!({}) } else { params };
let (command, params) = crate::menus::menu_choice(app, command, params);
let r = crate::menus::invoke(app, ctx, &command, params);
if let Err(e) = &r {
app.ui.status = e.clone();
@@ -612,6 +613,23 @@ mod tests {
assert!(spec2.items().iter().any(|i| i.command == "file.openRecent" && i.label == "Recent Test.ecproj"));
}
/// #1630: a checked Window menu entry closes its panel; an unchecked one opens it.
#[test]
fn window_menu_entry_toggles_its_panel() {
use crate::dock::PanelKind;
let mut app = app();
let ctx = egui::Context::default();
let choose = |app: &mut EffectcraftApp| {
let spec = build(app);
let id = spec.items().into_iter().find(|i| i.command == "window.panel" && i.params["panel"] == "align").unwrap().id.clone();
activate(app, &ctx, &spec, &id).unwrap();
};
choose(&mut app);
assert!(app.ui.dock.contains(PanelKind::Align));
choose(&mut app);
assert!(!app.ui.dock.contains(PanelKind::Align));
}
#[test]
fn activation_dispatches_the_menu_command() {
let mut app = app();
+20
View File
@@ -67,6 +67,26 @@ fn window_menu_opens_the_new_panels() {
}
}
/// #1630: choosing a checked panel in the Window menu closes it, as in After Effects (it only
/// brought the panel to the front, so the check mark never went away); choosing it again opens it.
#[test]
fn window_menu_closes_a_checked_panel() {
let mut h = harness();
let shown = |h: &Harness<'_, EffectcraftApp>| {
h.state().ui.dock.contains(PanelKind::Align) || h.state().ui.floating.iter().any(|f| f.panels.contains(&PanelKind::Align))
};
open(&mut h, "align");
assert!(shown(&h));
click(&mut h, "menu.Window");
let at = h.query_by_label("✔ Align").expect("Window ▸ Align, checked").rect().center();
click_at(&mut h, at);
assert!(!shown(&h), "Align closed");
click(&mut h, "menu.Window");
let at = h.query_by_label(" Align").expect("Window ▸ Align, unchecked").rect().center();
click_at(&mut h, at);
assert!(shown(&h), "Align open again");
}
#[test]
fn scopes_switch_kind() {
let mut h = harness();