diff --git a/crates/ui-egui/src/dialogs/tests_screen_assets.rs b/crates/ui-egui/src/dialogs/tests_screen_assets.rs index 9274b5224..60d47b39a 100644 --- a/crates/ui-egui/src/dialogs/tests_screen_assets.rs +++ b/crates/ui-egui/src/dialogs/tests_screen_assets.rs @@ -136,7 +136,7 @@ fn the_menus_reach_asset_export() { assert_eq!(find("assets.add", "As Single Asset"), Some(vec!["Object".to_string(), "Collect for Export".to_string()])); assert_eq!(find("assets.add", "As Multiple Assets"), Some(vec!["Object".to_string(), "Collect for Export".to_string()])); let window = entries.iter().find(|e| e.label == "Asset Export").unwrap(); - assert_eq!(window.params, json!({"panel": crate::panels::asset_export::ID})); + assert_eq!(window.params, json!({"panel": crate::panels::asset_export::ID, "toggle": true})); app.run("window.panel", window.params.clone()).unwrap(); assert_eq!(app.ui.open_panel.as_deref(), Some(crate::panels::asset_export::ID)); app.run("assets.add", json!({"multiple": false})).unwrap(); diff --git a/crates/ui-egui/src/menus.rs b/crates/ui-egui/src/menus.rs index ae1a6668c..2ba3ea82f 100644 --- a/crates/ui-egui/src/menus.rs +++ b/crates/ui-egui/src/menus.rs @@ -325,7 +325,12 @@ pub const UI_COMMANDS: &[(&str, &str, &str, &str)] = &[ "Shift+Tab", "{} show/hide the dock and the floating panels (hiding them closes a panel popped out of the icon column), leaving the toolbar and the Control bar as they are; returns whether the panels show", ), - ("window.panel", "Show Panel", "", "{panel: id} e.g. layers, swatches, stroke (case-insensitive; display labels like \"Layers\" work too)"), + ( + "window.panel", + "Show Panel", + "", + "{panel: id, toggle?: bool} e.g. layers, swatches, stroke (case-insensitive; display labels like \"Layers\" work too); toggle (the Window menu) hides a panel that shows, and shows it again where it was", + ), ( "window.panel.layout", "Change Panel Layout", @@ -979,6 +984,16 @@ pub fn check_params(session: &vectorcraft_engine::Session, id: &str, params: &Va } pub fn run_ui_command(app: &mut VectorcraftApp, id: &str, p: &Value) -> Option> { + // The Window menu's panel items toggle like its other checked items (#2361); the panel's + // other ways in (shortcuts, Show Properties Panel, a tool's double-click) only show it. + if id == "window.panel" && p.get("toggle").and_then(Value::as_bool) == Some(true) && checked(app, id, p) == Some(true) { + let panel = p.get("panel").and_then(Value::as_str).and_then(normalize_panel).unwrap_or_default(); + if app.ui.open_panel.as_deref() == Some(panel) { + app.ui.open_panel = None; + return Some(Ok(json!({"open": null}))); + } + return Some(app.run("window.panel.close", json!({"panel": panel}))); + } if let Some(result) = crate::panel_docking::command(app, id, p) { return Some(result); } @@ -2157,7 +2172,7 @@ pub fn menu_tree() -> Vec<(&'static str, Vec)> { } pub fn menu_tree_named(english_names: bool) -> Vec<(&'static str, Vec)> { - let panel = |label: &'static str, id: &'static str| cp(label, "window.panel", json!({ "panel": id })); + let panel = |label: &'static str, id: &'static str| cp(label, "window.panel", json!({ "panel": id, "toggle": true })); vec![ ( // The application menu: About and Discord only. The in-window bar never shows it (File diff --git a/crates/ui-egui/src/panel_docking_tests.rs b/crates/ui-egui/src/panel_docking_tests.rs index 911f5d3dd..c66623e75 100644 --- a/crates/ui-egui/src/panel_docking_tests.rs +++ b/crates/ui-egui/src/panel_docking_tests.rs @@ -574,3 +574,43 @@ fn native_set_drop_targets_respect_the_visible_layer_above_an_ordinary_float() { "visible native group owns the drop: {target:?}" ); } + +/// Window › Properties hides the panel while it shows, and the next click shows it again where it +/// was (#2361): in the dock as the app starts, floating, and once the layout has been changed. +/// The panel's other ways in (a tool's double-click, Show Properties Panel) only show it. +#[test] +fn the_window_menu_item_hides_a_shown_panel_and_shows_it_again() { + let item = crate::menus::menu_entries(&app()) + .into_iter() + .find(|e| e.path == ["Window"] && e.command.as_deref() == Some("window.panel") && e.params["panel"] == "properties") + .expect("Window › Properties"); + let params = item.params; + let shown = |app: &VectorcraftApp| crate::menus::checked(app, "window.panel", ¶ms) == Some(true); + let in_layout = |app: &VectorcraftApp| app.ui.docking.as_ref().is_some_and(|layout| layout.contains(&"properties".into())); + + let mut app = app(); + assert!(app.ui.docking.is_none() && shown(&app), "the default dock shows Properties"); + let start = legacy(&app); + app.run("window.panel", params.clone()).unwrap(); + assert!(!shown(&app) && !in_layout(&app), "the first click hides it"); + app.run("window.panel", params.clone()).unwrap(); + assert!(shown(&app)); + assert_eq!(app.ui.docking.as_ref(), Some(&start), "the second shows it where it was"); + + app.run("window.panel.move", json!({"panel":"properties", "anchor":"layers", "zone":"top"})).unwrap(); + let moved = app.ui.docking.clone(); + app.run("window.panel", params.clone()).unwrap(); + assert!(!shown(&app) && !in_layout(&app)); + app.run("window.panel", params.clone()).unwrap(); + assert!(shown(&app) && app.ui.docking == moved); + + app.run("window.panel.float", json!({"panel":"properties", "x":50, "y":70, "width":330, "height":410})).unwrap(); + let floating = app.ui.docking.clone(); + app.run("window.panel", params.clone()).unwrap(); + assert!(!shown(&app) && !in_layout(&app), "a floating Properties hides too"); + app.run("window.panel", params.clone()).unwrap(); + assert!(shown(&app) && app.ui.docking == floating); + + app.run("window.panel", json!({"panel":"properties"})).unwrap(); + assert!(shown(&app), "without the menu's toggle the command only shows the panel"); +}