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.
This commit is contained in:
saimskywalker
2026-10-31 02:10:55 +08:00
parent e97961ba66
commit f4e34bac71
3 changed files with 74 additions and 2 deletions
+55
View File
@@ -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<f64> {
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]);
}
+2
View File
@@ -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;
+17 -2
View File
@@ -187,6 +187,21 @@ fn brush_row(app: &mut FigApp, ui: &mut Ui, ids: &[NodeId], brush: Option<Option
});
}
/// Dash pattern `dashes` with its first dash (`i` 0) or first gap (`i` 1) set to `v`, keeping the
/// rest of the pattern: the panel shows only the first pair, and editing it must not collapse an
/// imported `[4, 2, 1, 2]` to two values (#1681). A single value is the dash and the gap alike.
fn set_dash(dashes: &[f64], i: usize, v: f64) -> Vec<f64> {
let mut d = dashes.to_vec();
if d.len() < 2 {
let first = d.first().copied().unwrap_or(4.0);
d = vec![first, first];
}
if let Some(x) = d.get_mut(i) {
*x = v.max(0.0);
}
d
}
/// Advanced stroke settings: dash pattern, width profile, join and the end points of open paths.
pub(super) fn stroke_settings(app: &mut FigApp, ui: &mut Ui, doc: &Document, sel: &[NodeId]) {
let ids = sel.to_vec();
@@ -214,9 +229,9 @@ pub(super) fn stroke_settings(app: &mut FigApp, ui: &mut Ui, doc: &Document, sel
widgets::sublabels(ui, tr("Dash"), Some(tr("Gap")));
row(ui, 32.0, |ui| {
let e = widgets::number_field(ui, Id::unique("stroke_dash"), Lead::None, Some(dash), "", half, 1.0);
app.field(&ids, "dashes", e, |v| json!([v.max(0.0), gap]));
app.field(&ids, "dashes", e, |v| json!(set_dash(&d, 0, v)));
let e = widgets::number_field(ui, Id::unique("stroke_gap"), Lead::None, Some(gap), "", half, 1.0);
app.field(&ids, "dashes", e, |v| json!([dash, v.max(0.0)]));
app.field(&ids, "dashes", e, |v| json!(set_dash(&d, 1, v)));
});
}
width_profile_row(app, ui, &ids, st.as_ref().map(|s| s.profile().cloned()));