forked from 1b-agent/opencadstudio-upstream
Add per-constraint delete; fix named-parameter reference bugs
Constraint delete: a Properties-panel row delete button, viewport
click-to-select on a constraint glyph pill with Delete-key support, and a
right-click "Delete Constraint" menu item — all routed through one
undoable Scene::selected_constraint-aware path.
Fixes two named-parameter bugs found while testing that feature:
- DIMCONSTRAINT: a parameter named like a mode-switch keyword ("r", "d",
"a", "x", "y") could never be referenced, since the keyword check ran
before the parameter-name check.
- PARAMETERS modal Apply rebuilt Scene::named_parameters but never
refreshed the Properties panel, leaving renamed/deleted rows stale
until an unrelated refresh (e.g. a selection change) happened to occur.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
1 parent
753df67ee4
commit
59ae38ae1a
14 files changed
+370
-77
No files matched your search
+55
-6
@@ -94,6 +94,34 @@ fn constraint_glyph_offsets(glyphs: &[(Point, [f32; 2], String, bool)]) -> Vec<f
|
||||
offsets
|
||||
}
|
||||
|
||||
/// Hit-tests screen point `p` against the same glyph-pill layout `draw`
|
||||
/// renders — reusing `constraint_glyph_offsets`/`constraint_glyph_box` so
|
||||
/// the clickable area can never drift from what's actually drawn, including
|
||||
/// the tangential fan-out applied when several glyphs share one anchor.
|
||||
/// Returns the index into `glyphs` of the topmost (last-drawn) match.
|
||||
/// `Scene::constraint_glyph_hit` (`src/scene/sketch_constraints.rs`) is the
|
||||
/// only caller — it maps the index back to a `ConstraintId`, since this
|
||||
/// plain-data layout math has no reason to know about that scene-layer type.
|
||||
pub(crate) fn constraint_glyph_hit_test(
|
||||
glyphs: &[(Point, [f32; 2], String, bool)],
|
||||
p: Point,
|
||||
) -> Option<usize> {
|
||||
let offsets = constraint_glyph_offsets(glyphs);
|
||||
glyphs
|
||||
.iter()
|
||||
.zip(offsets)
|
||||
.enumerate()
|
||||
.rev()
|
||||
.find_map(|(index, ((anchor, outward, label, _), tangent_offset))| {
|
||||
let (top_left, size) = constraint_glyph_box(*anchor, *outward, label, tangent_offset);
|
||||
let within = p.x >= top_left.x
|
||||
&& p.x <= top_left.x + size.width
|
||||
&& p.y >= top_left.y
|
||||
&& p.y <= top_left.y + size.height;
|
||||
within.then_some(index)
|
||||
})
|
||||
}
|
||||
|
||||
/// Convert CURSORSIZE to a screen-space arm length while keeping the original
|
||||
/// 60 px cursor at the default value and the full-viewport result at 100.
|
||||
pub(crate) fn crosshair_arm_px(bounds: iced::Rectangle, value: i32) -> f32 {
|
||||
@@ -723,7 +751,7 @@ pub fn selection_overlay<'a>(
|
||||
crosshair_bg: [f32; 4],
|
||||
crosshair: CrosshairOptions,
|
||||
selection_visual: SelectionVisualOptions,
|
||||
constraint_glyphs: Vec<(Point, [f32; 2], String, bool)>,
|
||||
constraint_glyphs: Vec<(Point, [f32; 2], String, bool, bool)>,
|
||||
) -> Element<'a, Message> {
|
||||
canvas(SelectionCanvas {
|
||||
selection,
|
||||
@@ -806,8 +834,10 @@ struct SelectionCanvas {
|
||||
crosshair_bg: [f32; 4],
|
||||
crosshair: CrosshairOptions,
|
||||
selection_visual: SelectionVisualOptions,
|
||||
/// Constraint glyph anchor, outward screen direction, label, and conflict state.
|
||||
constraint_glyphs: Vec<(Point, [f32; 2], String, bool)>,
|
||||
/// Constraint glyph anchor, outward screen direction, label, conflict
|
||||
/// state, and whether the pill itself is the current click-to-select
|
||||
/// target (`Scene::selected_constraint`) — for the current scope.
|
||||
constraint_glyphs: Vec<(Point, [f32; 2], String, bool, bool)>,
|
||||
}
|
||||
|
||||
fn draw_grip_marker(
|
||||
@@ -1703,9 +1733,21 @@ impl canvas::Program<Message> for SelectionCanvas {
|
||||
frame.stroke(&b1, stroke.clone());
|
||||
frame.stroke(&b2, stroke);
|
||||
}
|
||||
// Constraint glyphs are visual-only and have no hit testing.
|
||||
// Click-to-select hit testing against these pills lives in
|
||||
// `Scene::constraint_glyph_hit`, sharing this same size formula so
|
||||
// the clickable area never drifts from what's drawn here.
|
||||
if !self.constraint_glyphs.is_empty() {
|
||||
let offsets = constraint_glyph_offsets(&self.constraint_glyphs);
|
||||
// `constraint_glyph_offsets` only needs the anchor/outward/label/
|
||||
// conflict quadruple it was written against; project away the
|
||||
// trailing `is_selected` flag rather than widen its signature.
|
||||
let glyphs_for_offsets: Vec<(Point, [f32; 2], String, bool)> = self
|
||||
.constraint_glyphs
|
||||
.iter()
|
||||
.map(|(anchor, outward, label, is_conflicting, _)| {
|
||||
(*anchor, *outward, label.clone(), *is_conflicting)
|
||||
})
|
||||
.collect();
|
||||
let offsets = constraint_glyph_offsets(&glyphs_for_offsets);
|
||||
let normal_bg = theme.palette().primary.base.color;
|
||||
let normal_fg = theme.palette().primary.base.text;
|
||||
// A redundant or conflicting constraint gets the danger palette
|
||||
@@ -1714,7 +1756,8 @@ impl canvas::Program<Message> for SelectionCanvas {
|
||||
// would show, surfaced right on the geometry.
|
||||
let conflict_bg = theme.palette().danger.base.color;
|
||||
let conflict_fg = theme.palette().danger.base.text;
|
||||
for ((anchor, outward, label, is_conflicting), tangent_offset) in
|
||||
let selected_ring = theme.palette().primary.strong.color;
|
||||
for ((anchor, outward, label, is_conflicting, is_selected), tangent_offset) in
|
||||
self.constraint_glyphs.iter().zip(offsets)
|
||||
{
|
||||
if !anchor.x.is_finite() || !anchor.y.is_finite() {
|
||||
@@ -1729,6 +1772,12 @@ impl canvas::Program<Message> for SelectionCanvas {
|
||||
(size.height * 0.5).into(),
|
||||
);
|
||||
frame.fill(&pill, bg);
|
||||
if *is_selected {
|
||||
frame.stroke(
|
||||
&pill,
|
||||
canvas::Stroke::default().with_color(selected_ring).with_width(2.0),
|
||||
);
|
||||
}
|
||||
frame.fill_text(canvas::Text {
|
||||
content: label.clone(),
|
||||
position: Point::new(
|
||||
|
||||
+18
-5
@@ -865,8 +865,8 @@ impl PropertiesPanel {
|
||||
self.render_hatch_pattern_row(label, current)
|
||||
}
|
||||
PropValue::AttrText { tag, value } => self.render_attr_row(tag, value),
|
||||
PropValue::EntityLink { handles, conflicting } => {
|
||||
render_entity_link_row(label, handles.clone(), *conflicting)
|
||||
PropValue::EntityLink { id, handles, conflicting } => {
|
||||
render_entity_link_row(label, *id, handles.clone(), *conflicting)
|
||||
}
|
||||
PropValue::ParamRow { index, name, formula, resolved } => {
|
||||
self.render_param_row(*index, name, formula, resolved)
|
||||
@@ -1933,8 +1933,15 @@ fn render_ro_with_tooltip_row<'a>(
|
||||
/// field, so this doesn't use the usual `prop_row_widget` label|value split.
|
||||
/// Clicking it selects every entity in `handles`; a conflicting/redundant
|
||||
/// constraint (mirrors the viewport glyph pill's own color cue) tints red.
|
||||
fn render_entity_link_row<'a>(label: &'a str, handles: Vec<Handle>, conflicting: bool) -> Element<'a, Message> {
|
||||
let btn = button(text(label).size(FONT_SZ).width(Length::Fill))
|
||||
/// A trailing `✕` deletes the constraint outright (undoable), matching
|
||||
/// `PropParamDelete`'s own row-delete button in the Parameters section.
|
||||
fn render_entity_link_row<'a>(
|
||||
label: &'a str,
|
||||
id: crate::scene::sketch_constraints::ConstraintId,
|
||||
handles: Vec<Handle>,
|
||||
conflicting: bool,
|
||||
) -> Element<'a, Message> {
|
||||
let link_btn = button(text(label).size(FONT_SZ).width(Length::Fill))
|
||||
.on_press(Message::PropConstraintLinkClick(handles))
|
||||
.style(move |theme: &Theme, status| {
|
||||
let palette = theme.palette();
|
||||
@@ -1955,7 +1962,13 @@ fn render_entity_link_row<'a>(label: &'a str, handles: Vec<Handle>, conflicting:
|
||||
})
|
||||
.padding([3, 8])
|
||||
.width(Length::Fill);
|
||||
container(btn).width(Length::Fill).into()
|
||||
let delete_btn = button(text("\u{2715}").size(FONT_SZ))
|
||||
.on_press(Message::PropConstraintDelete(id))
|
||||
.style(button::text)
|
||||
.padding([2, 6]);
|
||||
container(row![link_btn, delete_btn].spacing(2).align_y(iced::Center))
|
||||
.width(Length::Fill)
|
||||
.into()
|
||||
}
|
||||
|
||||
// ── Parameters section: "+ Add parameter" row ──────────────────────────────
|
||||
|
||||
Reference in new issue
Block a user