diff --git a/crates/ui-egui/src/sketch_palette.rs b/crates/ui-egui/src/sketch_palette.rs index 3cd0389e..e978d64f 100644 --- a/crates/ui-egui/src/sketch_palette.rs +++ b/crates/ui-egui/src/sketch_palette.rs @@ -128,6 +128,31 @@ fn selected_curves(app: &SolveApp) -> Vec { app.session.selection.iter().filter_map(|s| if let Sel::SketchCurve { id } = s { Some(id.clone()) } else { None }).collect() } +/// The line types among the selected curves of the active sketch, indexed like the palette's +/// buttons (0 normal, 1 construction, 2 centerline); a mixed selection marks each of its types. +fn selected_linetypes(app: &SolveApp) -> [bool; 3] { + let mut types = [false; 3]; + let curves = selected_curves(app); + if curves.is_empty() { + return types; + } + let st = app.session.world_state(); + let Some(ss) = app.session.active_sketch.and_then(|s| st.sketch(s)) else { return types }; + for c in curves.iter().filter_map(|id| ss.sketch.curve_index(id).and_then(|i| ss.sketch.curves.get(i))) { + let k = if c.centerline { + 2 + } else if c.construction { + 1 + } else { + 0 + }; + if let Some(t) = types.get_mut(k) { + *t = true; + } + } + types +} + /// Set the line type of the selected curves: 0 normal, 1 construction, 2 centerline (lines). pub fn set_linetype(app: &mut SolveApp, kind: u8) { let curves = selected_curves(app); @@ -205,6 +230,7 @@ pub fn show(app: &mut SolveApp, ctx: &egui::Context) { let mut look = false; let mut finish = false; let has_curves = !selected_curves(app).is_empty(); + let selected_types = selected_linetypes(app); let response = crate::frame::docked( app.tr("ui.heading_sketch_palette"), egui::Id::new("sc_sketch_palette"), @@ -241,10 +267,11 @@ pub fn show(app: &mut SolveApp, ctx: &egui::Context) { ui.vertical(|ui| { ui.label(app.tr("ui.linetype")); ui.horizontal(|ui| { - // With a selection: convert it; without: the mode new curves are drawn in. + // With a selection: its line type, and a click converts it; without: + // the mode new curves are drawn in. let current = mode(app); for (i, (label, tip)) in [("—", "Normal"), ("- -", "Construction"), ("-·-", "Centerline")].iter().enumerate() { - let on = !has_curves && current == i as u8; + let on = if has_curves { selected_types.get(i).copied().unwrap_or(false) } else { current == i as u8 }; let tr_tip = app.tr_label(tip); let tip = if has_curves { crate::i18n::format("sketch.make_selection", &[("linetype", tr_tip)]) @@ -352,6 +379,38 @@ mod tests { } } + #[test] + fn linetype_buttons_follow_the_selection_and_fall_back_to_the_mode() { + let mut h = crate::scenario::Harness::new(); + h.app.run("sketch.create", json!({"plane":"XY"})).unwrap(); + let curve = |r: Value| r["curves"][0].as_str().unwrap().to_string(); + let normal = curve(h.app.run("sketch.line", json!({"points":[[0,0],[20,0]],"infer":false,"connect":false})).unwrap()); + let cons = curve(h.app.run("sketch.line", json!({"points":[[0,10],[20,10]],"infer":false,"connect":false})).unwrap()); + let center = curve(h.app.run("sketch.line", json!({"points":[[0,20],[20,20]],"infer":false,"connect":false})).unwrap()); + h.app.run("sketch.construction", json!({"curves":[cons],"value":true})).unwrap(); + h.app.run("sketch.centerline", json!({"curves":[center],"value":true})).unwrap(); + // Which of Normal, Construction and Centerline the palette shows pressed. + let pressed = |h: &mut crate::scenario::Harness, ids: &[&String]| { + h.app.session.selection = ids.iter().map(|id| Sel::SketchCurve { id: (*id).clone() }).collect(); + h.settle(); + let tree = h.output.accesskit_update.as_ref().unwrap(); + ["—", "- -", "-·-"] + .map(|label| tree.nodes.iter().any(|(_, n)| n.label() == Some(label) && n.toggled() == Some(egui::accesskit::Toggled::True))) + }; + assert_eq!(pressed(&mut h, &[&normal]), [true, false, false]); + assert_eq!(pressed(&mut h, &[&cons]), [false, true, false]); + assert_eq!(pressed(&mut h, &[¢er]), [false, false, true]); + // A mixed selection shows each of its line types. + assert_eq!(pressed(&mut h, &[&normal, ¢er]), [true, false, true]); + // Nothing selected: the drawing mode. + assert_eq!(pressed(&mut h, &[]), [true, false, false]); + set_mode(&mut h.app, 1); + assert_eq!(pressed(&mut h, &[]), [false, true, false]); + // A selection overrides the mode until it is cleared. + assert_eq!(pressed(&mut h, &[&normal]), [true, false, false]); + assert_eq!(pressed(&mut h, &[]), [false, true, false]); + } + #[test] fn dof_readout() { assert_eq!(dof_text(0, true), "Fully constrained");