Complete automatic constraint behavior

This commit is contained in:
ramox81 committed 2026-09-15 11:47:28 +03:00
1 parent d738ebfcc6
commit 66be530655
25 files changed
+1494 -102

No files matched your search

+26 -1
View File
@@ -94,6 +94,27 @@ fn constraint_glyph_offsets(glyphs: &[(Point, [f32; 2], String, bool)]) -> Vec<f
offsets
}
pub(crate) fn constraint_glyph_hit(
glyphs: &[(Point, [f32; 2], String, bool)],
cursor: 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);
(cursor.x >= top_left.x
&& cursor.x <= top_left.x + size.width
&& cursor.y >= top_left.y
&& cursor.y <= top_left.y + size.height)
.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 {
@@ -989,6 +1010,11 @@ impl canvas::Program<Message> for SelectionCanvas {
}
}
}
if let Some(pos) = cursor.position_in(bounds) {
if constraint_glyph_hit(&self.constraint_glyphs, pos).is_some() {
return mouse::Interaction::Pointer;
}
}
// The resize cursor over a divider is supplied by the input pane_grid
// layered above this overlay; `draw` only suppresses the CAD crosshair
// there (see `divider_under`).
@@ -1703,7 +1729,6 @@ 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.
if !self.constraint_glyphs.is_empty() {
let offsets = constraint_glyph_offsets(&self.constraint_glyphs);
let normal_bg = theme.palette().primary.base.color;
+143
View File
@@ -0,0 +1,143 @@
use crate::app::settings::AutoConstrainSettings;
use crate::app::Message;
use iced::widget::{button, checkbox, column, container, row, scrollable, text, text_input, Space};
use iced::{Element, Fill};
pub fn view_window<'a>(
settings: &'a AutoConstrainSettings,
selected_row: usize,
distance_input: &'a str,
angle_input: &'a str,
sizing: crate::ui::modal::ModalSizing,
) -> Element<'a, Message> {
let heading = row![
text(crate::t!("Priority")).width(70),
text(crate::t!("Constraint Type")).width(Fill),
text(crate::t!("Apply")).width(60),
]
.spacing(8);
let mut list = column![heading].spacing(3);
for (index, kind) in settings.priority.iter().copied().enumerate() {
let enabled = settings.enabled.contains(&kind);
let label = row![
text(format!("{}", index + 1)).width(70),
text(crate::t!(kind.label())).width(Fill),
checkbox(enabled)
.on_toggle(move |_| Message::AutoConstrainToggleKind(kind))
.width(60),
]
.spacing(8)
.align_y(iced::Center);
list = list.push(
button(label)
.on_press(Message::AutoConstrainSelectRow(index))
.style(if selected_row == index {
button::primary
} else {
button::text
})
.width(Fill)
.padding([5, 8]),
);
}
let reorder = column![
button(text(crate::t!("Move Up")))
.on_press_maybe((selected_row > 0).then_some(Message::AutoConstrainMoveUp))
.width(Fill),
button(text(crate::t!("Move Down")))
.on_press_maybe(
(selected_row + 1 < settings.priority.len())
.then_some(Message::AutoConstrainMoveDown)
)
.width(Fill),
Space::new().height(8),
button(text(crate::t!("Select All")))
.on_press(Message::AutoConstrainSelectAll)
.width(Fill),
button(text(crate::t!("Clear All")))
.on_press(Message::AutoConstrainClearAll)
.width(Fill),
button(text(crate::t!("Reset")))
.on_press(Message::AutoConstrainReset)
.width(Fill),
]
.spacing(6)
.width(125);
let types = row![
container(scrollable(list).height(260))
.padding(6)
.width(Fill),
reorder
]
.spacing(12);
let conditions = column![
checkbox(settings.tangent_must_share_point)
.label(crate::t!("Tangent objects must share an intersection point"))
.on_toggle(|_| Message::AutoConstrainToggleTangentPoint),
checkbox(settings.perpendicular_must_intersect)
.label(crate::t!("Perpendicular objects must share an intersection point"))
.on_toggle(|_| Message::AutoConstrainTogglePerpendicularIntersection),
]
.spacing(8);
let tolerances = column![
text(crate::t!("Tolerances")).size(14),
row![
text(crate::t!("Distance")).width(130),
text_input("0.05", distance_input)
.on_input(Message::AutoConstrainDistanceChanged)
.width(150),
]
.align_y(iced::Center),
row![
text(crate::t!("Angle")).width(130),
text_input("1", angle_input)
.on_input(Message::AutoConstrainAngleChanged)
.width(150),
text("°"),
]
.align_y(iced::Center),
]
.spacing(7);
let actions = row![
Space::new().width(Fill),
button(text(crate::t!("OK")))
.on_press(Message::AutoConstrainOk)
.style(button::primary)
.padding([6, 18]),
button(text(crate::t!("Apply")))
.on_press(Message::AutoConstrainApply)
.style(button::secondary)
.padding([6, 18]),
button(text(crate::t!("Cancel")))
.on_press(Message::AutoConstrainCancel)
.style(button::secondary)
.padding([6, 18]),
]
.spacing(8)
.align_y(iced::Center);
container(
column![
text(crate::t!("Auto Constrain")).size(16),
types,
conditions,
tolerances,
Space::new().height(Fill),
actions,
]
.spacing(14)
.width(sizing.width)
.height(sizing.height),
)
.padding([14, 16])
.width(sizing.width)
.height(sizing.height)
.style(container::rounded_box)
.into()
}
+1
View File
@@ -5,6 +5,7 @@ pub mod layer_state_manager;
pub mod drawing_units;
pub mod geometric_tolerance;
pub mod drafting_settings;
pub mod auto_constrain_settings;
pub mod layer_translator;
pub mod plot;
pub mod print_all;