diff --git a/crates/ui-egui/src/panels/actions.rs b/crates/ui-egui/src/panels/actions.rs index 543da8ac..11aa0996 100644 --- a/crates/ui-egui/src/panels/actions.rs +++ b/crates/ui-egui/src/panels/actions.rs @@ -5,6 +5,7 @@ use serde::{Deserialize, Serialize}; use serde_json::{Value, json}; +use super::{pstate, set_pstate}; use crate::theme::Tokens; use crate::widgets::{self, dim_label}; use crate::{VectorcraftApp, icons}; @@ -52,8 +53,10 @@ pub fn play(app: &mut VectorcraftApp, set: usize, idx: usize) -> Result = ui.data(|d| d.get_temp(selected_id)); + // The selected action as (set, action). Read as the type it is stored as: egui keeps a + // value per type, so reading the bare pair never found it and no action stayed selected (#1629). + const SELECTED: &str = "actions-selected"; + let mut selected: Option<(usize, usize)> = pstate(ui.ctx(), SELECTED); let mut play_req = None; egui::ScrollArea::vertical().max_height(260.0).show(ui, |ui| { for (si, set) in app.ui.action_sets.iter().enumerate() { @@ -89,7 +92,7 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut egui::Ui) { } } }); - ui.data_mut(|d| d.insert_temp(selected_id, selected)); + set_pstate(ui.ctx(), SELECTED, selected); widgets::divider(ui); // Bottom bar: stop, record, play, new, delete. ui.horizontal(|ui| { @@ -127,7 +130,7 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut egui::Ui) { && ai < s.actions.len() { s.actions.remove(ai); - ui.data_mut(|d| d.insert_temp::>(selected_id, None)); + set_pstate::>(ui.ctx(), SELECTED, None); } if recording { ui.label(egui::RichText::new(tl!("● Recording")).color(egui::Color32::from_rgb(0xe0, 0x30, 0x30))); @@ -160,6 +163,36 @@ mod tests { } } + /// #1629: a clicked action stays selected, so Play Current Selection plays it and Delete + /// Selection deletes it. + #[test] + fn a_clicked_action_stays_selected_for_play_and_delete() { + use super::super::tests_appearance::{click, frame_events, text_rect}; + let mut app = VectorcraftApp::new(Session::new(), Default::default()); + app.run("file.new", json!({"width": 200, "height": 100})).unwrap(); + let id = app.run("shape.rectangle", json!({"x": 10, "y": 10, "width": 20, "height": 20})).unwrap()["id"].as_u64().unwrap(); + let id = vectorcraft_doc::NodeId(id); + let ctx = egui::Context::default(); + let texts = frame_events(&ctx, &mut app, vec![], show); + click(&ctx, &mut app, text_rect(&texts, "Reflect Horizontal").center(), show); + frame_events(&ctx, &mut app, vec![], show); + assert_eq!(pstate::>(&ctx, "actions-selected"), Some((0, 5)), "selected on the frames after the click"); + // The bottom bar's 24-point buttons, left to right: stop, record, play, delete. + let mut bar = crate::toolbar::tests::widget_rects(&ctx, egui::vec2(24.0, 24.0)); + bar.sort_by(|a, b| a.left().total_cmp(&b.left())); + let [_, _, play, delete] = bar[..] else { panic!("the bottom bar: {bar:?}") }; + click(&ctx, &mut app, text_rect(&texts, "Duplicate and Offset 10 pt").center(), show); + click(&ctx, &mut app, play.center(), show); + assert_eq!(app.session.active().unwrap().selection.len(), 1); + assert_ne!(app.session.active().unwrap().selection.objects, [id], "played: the copy is selected"); + let names = |app: &VectorcraftApp| app.ui.action_sets[0].actions.iter().map(|a| a.name.clone()).collect::>(); + let before = names(&app); + click(&ctx, &mut app, delete.center(), show); + assert_eq!(names(&app), before[1..], "the selected action is deleted"); + click(&ctx, &mut app, delete.center(), show); + assert_eq!(names(&app), before[1..], "nothing selected after a delete: nothing more goes"); + } + /// Actions recorded with Align to Artboard and Paste in Place, and the default Center on /// Artboard, play on the artboard active when they play, not on the one active when they were /// recorded.