From f4e34bac7164c5089f533a858fe422661d88f23b Mon Sep 17 00:00:00 2001 From: saimskywalker <15120878+saimskywalker@users.noreply.github.com> Date: Sat, 31 Oct 2026 02:10:55 +0800 Subject: [PATCH] M4: editing a stroke's Dash or Gap keeps the rest of its dash pattern (#1681) The stroke settings show the first dash and gap only, and editing either wrote back [dash, gap], so an imported pattern like [4, 2, 1, 2] collapsed on the first edit. Each field now changes its own value and keeps the rest of the pattern. --- crates/ui-egui/src/dash_pattern_tests.rs | 55 +++++++++++++++++++++++ crates/ui-egui/src/lib.rs | 2 + crates/ui-egui/src/panels/right/stroke.rs | 19 +++++++- 3 files changed, 74 insertions(+), 2 deletions(-) create mode 100644 crates/ui-egui/src/dash_pattern_tests.rs diff --git a/crates/ui-egui/src/dash_pattern_tests.rs b/crates/ui-egui/src/dash_pattern_tests.rs new file mode 100644 index 00000000..09a2b175 --- /dev/null +++ b/crates/ui-egui/src/dash_pattern_tests.rs @@ -0,0 +1,55 @@ +//! The stroke settings' Dash and Gap fields edit the first pair of a dash pattern and keep the +//! rest of it (#1681): an imported `[4, 2, 1, 2]` used to collapse to `[dash, gap]` on the first edit. + +use egui::{Event, Id, Key, Modifiers}; +use figcraft_doc::NodeId; +use figcraft_engine::Session; +use serde_json::json; + +use crate::layers_drag_tests::Ui; + +/// A selected rectangle with a stroke dashed `dashes`, its advanced stroke settings shown. +fn scene(dashes: serde_json::Value) -> (Ui, NodeId) { + let mut s = Session::new(); + let r = NodeId(s.execute("shape.rectangle", &json!({"x": 0, "y": 0, "width": 200, "height": 120})).unwrap()["id"].as_u64().unwrap()); + s.execute("node.set", &json!({"ids": [r], "props": {"strokes": ["#000000"], "strokeWeight": 2, "dashes": dashes}})).unwrap(); + s.execute("select.set", &json!({"ids": [r]})).unwrap(); + let mut u = Ui::new(s); + u.ctx.data_mut(|d| d.insert_temp(Id::unique("stroke_adv"), true)); + u.frame(vec![]); + (u, r) +} + +fn dashes(u: &Ui, r: NodeId) -> Vec { + u.app.session.doc().unwrap().node(r).unwrap().stroke.dashes.clone() +} + +/// Type `text` into the number field `id` and press Enter. +fn type_into(u: &mut Ui, id: &str, text: &str) { + let at = u.ctx.read_response(Id::unique(id).with("field")).unwrap_or_else(|| panic!("{id} shows")).rect.center(); + u.button(at, true); + u.button(at, false); + u.frame(vec![]); + u.frame(vec![Event::Text(text.into())]); + u.frame(vec![ + Event::Key { key: Key::Enter, physical_key: None, pressed: true, repeat: false, modifiers: Modifiers::NONE }, + Event::Key { key: Key::Enter, physical_key: None, pressed: false, repeat: false, modifiers: Modifiers::NONE }, + ]); + u.frame(vec![]); +} + +#[test] +fn editing_the_dash_or_gap_keeps_the_rest_of_the_pattern() { + let (mut u, r) = scene(json!([4, 2, 1, 2])); + type_into(&mut u, "stroke_dash", "6"); + assert_eq!(dashes(&u, r), [6.0, 2.0, 1.0, 2.0]); + type_into(&mut u, "stroke_gap", "3"); + assert_eq!(dashes(&u, r), [6.0, 3.0, 1.0, 2.0]); +} + +#[test] +fn a_single_value_pattern_is_the_dash_and_the_gap() { + let (mut u, r) = scene(json!([5])); + type_into(&mut u, "stroke_gap", "2"); + assert_eq!(dashes(&u, r), [5.0, 2.0]); +} diff --git a/crates/ui-egui/src/lib.rs b/crates/ui-egui/src/lib.rs index 3464de0b..3019c146 100644 --- a/crates/ui-egui/src/lib.rs +++ b/crates/ui-egui/src/lib.rs @@ -90,6 +90,8 @@ mod crop_image_tests; #[cfg(test)] mod cursors_tests; #[cfg(test)] +mod dash_pattern_tests; +#[cfg(test)] mod design_paste_tests; #[cfg(test)] mod dev_mode_tests; diff --git a/crates/ui-egui/src/panels/right/stroke.rs b/crates/ui-egui/src/panels/right/stroke.rs index ef7a3648..c790878f 100644 --- a/crates/ui-egui/src/panels/right/stroke.rs +++ b/crates/ui-egui/src/panels/right/stroke.rs @@ -187,6 +187,21 @@ fn brush_row(app: &mut FigApp, ui: &mut Ui, ids: &[NodeId], brush: Option