diff --git a/crates/ui-egui/src/chrome/control_bar.rs b/crates/ui-egui/src/chrome/control_bar.rs index a40d88dfe..1df1f08ab 100644 --- a/crates/ui-egui/src/chrome/control_bar.rs +++ b/crates/ui-egui/src/chrome/control_bar.rs @@ -142,9 +142,9 @@ const GLOBAL_EDIT_OPTIONS_WIDTH: f32 = 24.0; /// page places the options in the Control panel as well as in Properties /// (). /// It opens the Properties panel's Global Edit Options, which set the same session options, and is -/// off while Global Edit is on, as that panel's dropdown is. It sits after Recolor Artwork, ahead of -/// the X/Y/W/H fields that the bar leaves out when it runs short of room, and is left out only when -/// no room is left for it. +/// off and closed while Global Edit is on, as that panel's dropdown is. It sits after Recolor +/// Artwork, ahead of the X/Y/W/H fields that the bar leaves out when it runs short of room, and is +/// left out only when no room is left for it. fn global_edit_options(app: &mut VectorcraftApp, ui: &mut Ui) { if !crate::menus::enabled(app, "select.globalEdit") || ui.available_width() < GLOBAL_EDIT_OPTIONS_WIDTH { return; @@ -152,6 +152,7 @@ fn global_edit_options(app: &mut VectorcraftApp, ui: &mut Ui) { let on = app.session.active().is_some_and(|d| d.global_edit.is_some()); let options = widgets::icon_button_enabled(ui, "chevron-down", tl!("Global Edit Options"), false, !on, GLOBAL_EDIT_OPTIONS_WIDTH); egui::Popup::menu(&options) + .open_memory(crate::panels::properties::global_edit_options_open(&options, on)) .close_behavior(egui::PopupCloseBehavior::CloseOnClickOutside) .show(|ui| crate::panels::properties::global_edit_options(app, ui)); } diff --git a/crates/ui-egui/src/chrome/tests.rs b/crates/ui-egui/src/chrome/tests.rs index 00c56ff67..5c49b153d 100644 --- a/crates/ui-egui/src/chrome/tests.rs +++ b/crates/ui-egui/src/chrome/tests.rs @@ -1807,7 +1807,7 @@ fn the_control_bar_shows_the_distribute_buttons_for_two_or_more_objects() { /// The Control bar shows the Global Edit Options dropdown while Start Global Edit can run. Its /// options are the session's, which the Properties panel's dropdown also sets: Match Size from /// the Control bar narrows the Results and the objects Start Global Edit takes. The dropdown is off -/// while Global Edit is on, and absent when the selection cannot start it. +/// and closed while Global Edit is on, and absent when the selection cannot start it. #[test] fn the_control_bar_shows_the_global_edit_options() { crate::i18n::set_current(crate::i18n::Lang::EN); @@ -1860,9 +1860,14 @@ fn the_control_bar_shows_the_global_edit_options() { let nodes = frame(&mut app, vec![]); let button = find(&nodes, "Global Edit Options").unwrap_or_else(|| panic!("Global Edit on: the dropdown still shows")); assert!(button.is_disabled(), "the options are off while Global Edit is on"); + let results = |nodes: &Nodes| nodes.iter().any(|(_, n)| n.label().or(n.value()).is_some_and(|t| t.starts_with("Result"))); + assert!(!results(&nodes), "Global Edit starting closes the open options"); + assert!(!results(&frame(&mut app, vec![]))); + app.run("select.globalEdit", json!({})).unwrap(); + frame(&mut app, vec![]); + assert!(!results(&frame(&mut app, vec![])), "the options stay closed once Global Edit stops"); // At any width the button either fits inside the bar or is left out, and a wider bar never // leaves it out once a narrower one showed it. - app.run("select.globalEdit", json!({})).unwrap(); let mut seen = false; for width in (600..=2400).step_by(10) { let screen = egui::Rect::from_min_size(egui::Pos2::ZERO, egui::vec2(width as f32, 300.0)); diff --git a/crates/ui-egui/src/panels/properties.rs b/crates/ui-egui/src/panels/properties.rs index f236c6a73..b775dc05a 100644 --- a/crates/ui-egui/src/panels/properties.rs +++ b/crates/ui-egui/src/panels/properties.rs @@ -240,10 +240,20 @@ fn global_edit_row(app: &mut VectorcraftApp, ui: &mut Ui) { crate::menus::invoke(app, "select.globalEdit", json!({})); } let options = widgets::icon_button_enabled(ui, "chevron-down", tl!("Global Edit Options"), false, !on, 20.0); - egui::Popup::menu(&options).close_behavior(egui::PopupCloseBehavior::CloseOnClickOutside).show(|ui| global_edit_options(app, ui)); + egui::Popup::menu(&options) + .open_memory(global_edit_options_open(&options, on)) + .close_behavior(egui::PopupCloseBehavior::CloseOnClickOutside) + .show(|ui| global_edit_options(app, ui)); }); } +/// The open state for a Global Edit Options dropdown's popup: a click on the button toggles the +/// popup, and the popup is closed while Global Edit is on, including when Global Edit starts with +/// the popup open. +pub(crate) fn global_edit_options_open(options: &egui::Response, on: bool) -> Option { + if on { Some(egui::SetOpenCommand::Bool(false)) } else { options.clicked().then_some(egui::SetOpenCommand::Toggle) } +} + /// The Global Edit Options: what else similar objects must share (Match), where they may lie (On /// Artboards) and how many objects that takes now. The Control bar's Global Edit Options dropdown /// shows them too. diff --git a/crates/ui-egui/src/panels/properties/tests.rs b/crates/ui-egui/src/panels/properties/tests.rs index f13e98384..1a38e4f70 100644 --- a/crates/ui-egui/src/panels/properties/tests.rs +++ b/crates/ui-egui/src/panels/properties/tests.rs @@ -135,6 +135,40 @@ fn start_global_edit_quick_action_and_escape() { assert!(!tall(&mut app, vec![]).iter().any(|(t, _)| t == "Start Global Edit"), "type has no Global Edit"); } +/// The Quick Actions' Global Edit Options close when Global Edit starts while they are open, and +/// stay closed once it stops. +#[test] +fn global_edit_starting_closes_the_quick_action_options() { + crate::i18n::set_current(crate::i18n::Lang::EN); + let mut app = VectorcraftApp::new(Session::new(), Default::default()); + app.run("file.new", json!({"width": 400, "height": 300})).unwrap(); + let a = app.run("shape.rectangle", json!({"x": 20, "y": 20, "width": 40, "height": 40})).unwrap()["id"].clone(); + app.run("shape.rectangle", json!({"x": 200, "y": 20, "width": 40, "height": 40})).unwrap(); + app.run("select.set", json!({"ids": [a]})).unwrap(); + let ctx = egui::Context::default(); + ctx.enable_accesskit(); + let frame = |app: &mut VectorcraftApp, events: Vec| { + let raw = egui::RawInput { screen_rect: Some(Rect::from_min_size(Pos2::ZERO, vec2(300.0, 700.0))), events, ..Default::default() }; + let mut out = ctx.run_ui(raw, |ui| global_edit_row(app, ui)); + out.textures_delta.clear(); + out.platform_output.accesskit_update.map(|u| u.nodes).unwrap_or_default() + }; + type Nodes = Vec<(egui::accesskit::NodeId, egui::accesskit::Node)>; + let results = |nodes: &Nodes| nodes.iter().any(|(_, n)| n.label().or(n.value()).is_some_and(|t| t.starts_with("Result"))); + let nodes = frame(&mut app, vec![]); + let b = nodes.iter().find(|(_, n)| n.label() == Some("Global Edit Options")).and_then(|(_, n)| n.bounds()).unwrap_or_else(|| panic!("{nodes:?}")); + let at = Pos2::new(((b.x0 + b.x1) / 2.0) as f32, ((b.y0 + b.y1) / 2.0) as f32); + let press = |pressed| Event::PointerButton { pos: at, button: PointerButton::Primary, pressed, modifiers: Default::default() }; + frame(&mut app, vec![Event::PointerMoved(at), press(true)]); + frame(&mut app, vec![press(false)]); + assert!(results(&frame(&mut app, vec![])), "the options open"); + app.run("select.globalEdit", json!({})).unwrap(); + assert!(!results(&frame(&mut app, vec![])), "Global Edit starting closes the open options"); + app.run("select.globalEdit", json!({})).unwrap(); + frame(&mut app, vec![]); + assert!(!results(&frame(&mut app, vec![])), "the options stay closed once Global Edit stops"); +} + /// The options' Results count is found once per document revision, options and selected /// object, not every frame; another selected object or other options find it again. #[test]