Disable the Path tool's "Colinear Handles" checkbox when no interior anchors are selected (#2339)

* disable "Colinear Handles" checkbox when only endpoint or its handle is selected

* small refactor

* ignore endpoints when calculating multiple selected points colinearity

---------

Co-authored-by: indierusty <priyaayadav@gmail.com>
Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
Priyanshu 2025-03-08 11:20:52 +05:30 committed by GitHub
parent 381dcbf17f
commit 0a91dd2141
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 28 additions and 5 deletions

View File

@ -575,7 +575,7 @@ impl ShapeState {
Some(()) Some(())
} }
/// Iterates over the selected manipulator groups, returning whether their handles have mixed, colinear, or free angles. /// Iterates over the selected manipulator groups exluding endpoints, returning whether their handles have mixed, colinear, or free angles.
/// If there are no points selected this function returns mixed. /// If there are no points selected this function returns mixed.
pub fn selected_manipulator_angles(&self, network_interface: &NodeNetworkInterface) -> ManipulatorAngle { pub fn selected_manipulator_angles(&self, network_interface: &NodeNetworkInterface) -> ManipulatorAngle {
// This iterator contains a bool indicating whether or not selected points' manipulator groups have colinear handles. // This iterator contains a bool indicating whether or not selected points' manipulator groups have colinear handles.
@ -583,7 +583,13 @@ impl ShapeState {
.selected_shape_state .selected_shape_state
.iter() .iter()
.map(|(&layer, selection_state)| (network_interface.compute_modified_vector(layer), selection_state)) .map(|(&layer, selection_state)| (network_interface.compute_modified_vector(layer), selection_state))
.flat_map(|(data, selection_state)| selection_state.selected_points.iter().map(move |&point| data.as_ref().is_some_and(|data| data.colinear(point)))); .flat_map(|(data, selection_state)| {
selection_state.selected_points.iter().filter_map(move |&point| {
let Some(data) = &data else { return None };
let Some(_) = point.get_handle_pair(&data) else { return None }; // ignores the endpoints.
Some(data.colinear(point))
})
});
let Some(first_is_colinear) = points_colinear_status.next() else { return ManipulatorAngle::Mixed }; let Some(first_is_colinear) = points_colinear_status.next() else { return ManipulatorAngle::Mixed };
if points_colinear_status.any(|point| first_is_colinear != point) { if points_colinear_status.any(|point| first_is_colinear != point) {

View File

@ -175,7 +175,7 @@ impl LayoutHolder for PathTool {
// TODO: Remove `unwrap_or_default` once checkboxes are capable of displaying a mixed state // TODO: Remove `unwrap_or_default` once checkboxes are capable of displaying a mixed state
.unwrap_or_default(); .unwrap_or_default();
let colinear_handle_checkbox = CheckboxInput::new(colinear_handles_state) let colinear_handle_checkbox = CheckboxInput::new(colinear_handles_state)
.disabled(self.tool_data.selection_status.is_none()) .disabled(!self.tool_data.can_toggle_colinearity)
.on_update(|&CheckboxInput { checked, .. }| { .on_update(|&CheckboxInput { checked, .. }| {
if checked { if checked {
PathToolMessage::ManipulatorMakeHandlesColinear.into() PathToolMessage::ManipulatorMakeHandlesColinear.into()
@ -186,7 +186,7 @@ impl LayoutHolder for PathTool {
.tooltip(colinear_handles_tooltip) .tooltip(colinear_handles_tooltip)
.widget_holder(); .widget_holder();
let colinear_handles_label = TextLabel::new("Colinear Handles") let colinear_handles_label = TextLabel::new("Colinear Handles")
.disabled(self.tool_data.selection_status.is_none()) .disabled(!self.tool_data.can_toggle_colinearity)
.tooltip(colinear_handles_tooltip) .tooltip(colinear_handles_tooltip)
.widget_holder(); .widget_holder();
@ -359,7 +359,10 @@ struct PathToolData {
opposing_handle_lengths: Option<OpposingHandleLengths>, opposing_handle_lengths: Option<OpposingHandleLengths>,
/// Describes information about the selected point(s), if any, across one or multiple shapes and manipulator point types (anchor or handle). /// Describes information about the selected point(s), if any, across one or multiple shapes and manipulator point types (anchor or handle).
/// The available information varies depending on whether `None`, `One`, or `Multiple` points are currently selected. /// The available information varies depending on whether `None`, `One`, or `Multiple` points are currently selected.
/// NOTE: It must be updated using `update_selection_status` to ensure `can_toggle_colinearity` stays synchronized with the current selection.
selection_status: SelectionStatus, selection_status: SelectionStatus,
/// `true` if we can change the current selection to colinear or not.
can_toggle_colinearity: bool,
segment: Option<ClosestSegment>, segment: Option<ClosestSegment>,
snap_cache: SnapCache, snap_cache: SnapCache,
double_click_handled: bool, double_click_handled: bool,
@ -415,6 +418,20 @@ impl PathToolData {
} }
} }
fn update_selection_status(&mut self, shape_editor: &mut ShapeState, document: &DocumentMessageHandler) {
let selection_status = get_selection_status(&document.network_interface, shape_editor);
self.can_toggle_colinearity = match &selection_status {
SelectionStatus::None => false,
SelectionStatus::One(single_selected_point) => {
let vector_data = document.network_interface.compute_modified_vector(single_selected_point.layer).unwrap();
single_selected_point.id.get_handle_pair(&vector_data).is_some()
}
SelectionStatus::Multiple(_) => true,
};
self.selection_status = selection_status;
}
fn start_insertion(&mut self, responses: &mut VecDeque<Message>, segment: ClosestSegment) -> PathToolFsmState { fn start_insertion(&mut self, responses: &mut VecDeque<Message>, segment: ClosestSegment) -> PathToolFsmState {
if self.segment.is_some() { if self.segment.is_some() {
warn!("Segment was `Some(..)` before `start_insertion`") warn!("Segment was `Some(..)` before `start_insertion`")
@ -1302,7 +1319,7 @@ impl Fsm for PathToolFsmState {
point_select_state: shape_editor.get_dragging_state(&document.network_interface), point_select_state: shape_editor.get_dragging_state(&document.network_interface),
colinear, colinear,
}; };
tool_data.selection_status = get_selection_status(&document.network_interface, shape_editor); tool_data.update_selection_status(shape_editor, document);
self self
} }
(_, PathToolMessage::ManipulatorMakeHandlesColinear) => { (_, PathToolMessage::ManipulatorMakeHandlesColinear) => {