Promote vector meshes from experimental by removing it from preferences

This commit is contained in:
Keavon Chambers 2025-12-21 15:30:04 -08:00
parent e73e524f3d
commit 123108c813
12 changed files with 71 additions and 133 deletions

View File

@ -256,29 +256,6 @@ impl PreferencesDialogMessageHandler {
.widget_instance(), .widget_instance(),
]; ];
let checkbox_id = CheckboxId::new();
let vector_mesh_description = "
Allow the Pen tool to produce branching geometry, where more than two segments may be connected to one anchor point.\n\
\n\
Currently, vector meshes do not properly render strokes (branching joins) and fills (multiple regions).
"
.trim();
let vector_meshes = vec![
Separator::new(SeparatorType::Unrelated).widget_instance(),
Separator::new(SeparatorType::Unrelated).widget_instance(),
CheckboxInput::new(preferences.vector_meshes)
.tooltip_label("Vector Meshes")
.tooltip_description(vector_mesh_description)
.on_update(|checkbox_input: &CheckboxInput| PreferencesMessage::VectorMeshes { enabled: checkbox_input.checked }.into())
.for_label(checkbox_id)
.widget_instance(),
TextLabel::new("Vector Meshes")
.tooltip_label("Vector Meshes")
.tooltip_description(vector_mesh_description)
.for_checkbox(checkbox_id)
.widget_instance(),
];
let checkbox_id = CheckboxId::new(); let checkbox_id = CheckboxId::new();
let brush_tool_description = " let brush_tool_description = "
Enable the Brush tool to support basic raster-based layer painting.\n\ Enable the Brush tool to support basic raster-based layer painting.\n\
@ -304,7 +281,7 @@ impl PreferencesDialogMessageHandler {
.widget_instance(), .widget_instance(),
]; ];
rows.extend_from_slice(&[header, node_graph_wires_label, graph_wire_style, use_vello, vector_meshes, brush_tool]); rows.extend_from_slice(&[header, node_graph_wires_label, graph_wire_style, use_vello, brush_tool]);
} }
Layout(rows.into_iter().map(|r| LayoutGroup::Row { widgets: r }).collect()) Layout(rows.into_iter().map(|r| LayoutGroup::Row { widgets: r }).collect())

View File

@ -3,7 +3,7 @@ use crate::consts::HIDE_HANDLE_DISTANCE;
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface; use crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface;
use crate::messages::tool::common_functionality::shape_editor::{SelectedLayerState, ShapeState}; use crate::messages::tool::common_functionality::shape_editor::{SelectedLayerState, ShapeState};
use crate::messages::tool::tool_messages::tool_prelude::{DocumentMessageHandler, PreferencesMessageHandler}; use crate::messages::tool::tool_messages::tool_prelude::DocumentMessageHandler;
use glam::{DAffine2, DVec2}; use glam::{DAffine2, DVec2};
use graphene_std::subpath::{Bezier, BezierHandles}; use graphene_std::subpath::{Bezier, BezierHandles};
use graphene_std::text::{Font, FontCache, TextAlign, TextContext, TypesettingConfig}; use graphene_std::text::{Font, FontCache, TextAlign, TextContext, TypesettingConfig};
@ -200,7 +200,7 @@ pub fn path_overlays(document: &DocumentMessageHandler, draw_handles: DrawHandle
} }
} }
pub fn path_endpoint_overlays(document: &DocumentMessageHandler, shape_editor: &mut ShapeState, overlay_context: &mut OverlayContext, preferences: &PreferencesMessageHandler) { pub fn path_endpoint_overlays(document: &DocumentMessageHandler, shape_editor: &mut ShapeState, overlay_context: &mut OverlayContext) {
if !overlay_context.visibility_settings.anchors() { if !overlay_context.visibility_settings.anchors() {
return; return;
} }
@ -213,7 +213,7 @@ pub fn path_endpoint_overlays(document: &DocumentMessageHandler, shape_editor: &
let selected = shape_editor.selected_shape_state.get(&layer); let selected = shape_editor.selected_shape_state.get(&layer);
let is_selected = |selected: Option<&SelectedLayerState>, point: ManipulatorPointId| selected.is_some_and(|selected| selected.is_point_selected(point)); let is_selected = |selected: Option<&SelectedLayerState>, point: ManipulatorPointId| selected.is_some_and(|selected| selected.is_point_selected(point));
for point in vector.extendable_points(preferences.vector_meshes) { for point in vector.extendable_points() {
let Some(position) = vector.point_domain.position_from_id(point) else { continue }; let Some(position) = vector.point_domain.position_from_id(point) else { continue };
let position = transform.transform_point2(position); let position = transform.transform_point2(position);
overlay_context.manipulator_anchor(position, is_selected(selected, ManipulatorPointId::Anchor(point)), None); overlay_context.manipulator_anchor(position, is_selected(selected, ManipulatorPointId::Anchor(point)), None);

View File

@ -12,7 +12,6 @@ pub enum PreferencesMessage {
// Per-preference messages // Per-preference messages
UseVello { use_vello: bool }, UseVello { use_vello: bool },
SelectionMode { selection_mode: SelectionMode }, SelectionMode { selection_mode: SelectionMode },
VectorMeshes { enabled: bool },
BrushTool { enabled: bool }, BrushTool { enabled: bool },
ModifyLayout { zoom_with_scroll: bool }, ModifyLayout { zoom_with_scroll: bool },
GraphWireStyle { style: GraphWireStyle }, GraphWireStyle { style: GraphWireStyle },

View File

@ -17,7 +17,6 @@ pub struct PreferencesMessageHandler {
pub selection_mode: SelectionMode, pub selection_mode: SelectionMode,
pub zoom_with_scroll: bool, pub zoom_with_scroll: bool,
pub use_vello: bool, pub use_vello: bool,
pub vector_meshes: bool,
pub brush_tool: bool, pub brush_tool: bool,
pub graph_wire_style: GraphWireStyle, pub graph_wire_style: GraphWireStyle,
pub viewport_zoom_wheel_rate: f64, pub viewport_zoom_wheel_rate: f64,
@ -46,7 +45,6 @@ impl Default for PreferencesMessageHandler {
selection_mode: SelectionMode::Touched, selection_mode: SelectionMode::Touched,
zoom_with_scroll: matches!(MappingVariant::default(), MappingVariant::ZoomWithScroll), zoom_with_scroll: matches!(MappingVariant::default(), MappingVariant::ZoomWithScroll),
use_vello: EditorPreferences::default().use_vello, use_vello: EditorPreferences::default().use_vello,
vector_meshes: false,
brush_tool: false, brush_tool: false,
graph_wire_style: GraphWireStyle::default(), graph_wire_style: GraphWireStyle::default(),
viewport_zoom_wheel_rate: VIEWPORT_ZOOM_WHEEL_RATE, viewport_zoom_wheel_rate: VIEWPORT_ZOOM_WHEEL_RATE,
@ -87,9 +85,6 @@ impl MessageHandler<PreferencesMessage, PreferencesMessageContext<'_>> for Prefe
responses.add(PortfolioMessage::UpdateVelloPreference); responses.add(PortfolioMessage::UpdateVelloPreference);
responses.add(PortfolioMessage::EditorPreferences); responses.add(PortfolioMessage::EditorPreferences);
} }
PreferencesMessage::VectorMeshes { enabled } => {
self.vector_meshes = enabled;
}
PreferencesMessage::BrushTool { enabled } => { PreferencesMessage::BrushTool { enabled } => {
self.brush_tool = enabled; self.brush_tool = enabled;

View File

@ -421,7 +421,7 @@ impl ShapeState {
(point.as_handle().is_some() && self.ignore_handles) || (point.as_anchor().is_some() && self.ignore_anchors) (point.as_handle().is_some() && self.ignore_handles) || (point.as_anchor().is_some() && self.ignore_anchors)
} }
pub fn close_selected_path(&self, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>, vector_meshes: bool) { pub fn close_selected_path(&self, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
// First collect all selected anchor points across all layers // First collect all selected anchor points across all layers
let all_selected_points: Vec<(LayerNodeIdentifier, PointId)> = self let all_selected_points: Vec<(LayerNodeIdentifier, PointId)> = self
.selected_shape_state .selected_shape_state
@ -446,14 +446,6 @@ impl ShapeState {
let (layer1, start_point) = all_selected_points[0]; let (layer1, start_point) = all_selected_points[0];
let (layer2, end_point) = all_selected_points[1]; let (layer2, end_point) = all_selected_points[1];
let Some(vector1) = document.network_interface.compute_modified_vector(layer1) else { return };
let Some(vector2) = document.network_interface.compute_modified_vector(layer2) else { return };
// If vector meshes is not selected then only for endpoints, otherwise normally applicable
if !vector_meshes && (vector1.all_connected(start_point).count() != 1 || vector2.all_connected(end_point).count() != 1) {
return;
}
if layer1 == layer2 { if layer1 == layer2 {
if start_point == end_point { if start_point == end_point {
return; return;

View File

@ -22,14 +22,8 @@ use graphene_std::vector::{HandleExt, PointId, SegmentId, Vector, VectorModifica
use kurbo::{CubicBez, DEFAULT_ACCURACY, Line, ParamCurve, PathSeg, Point, QuadBez, Shape}; use kurbo::{CubicBez, DEFAULT_ACCURACY, Line, ParamCurve, PathSeg, Point, QuadBez, Shape};
/// Determines if a path should be extended. Goal in viewport space. Returns the path and if it is extending from the start, if applicable. /// Determines if a path should be extended. Goal in viewport space. Returns the path and if it is extending from the start, if applicable.
pub fn should_extend( pub fn should_extend(document: &DocumentMessageHandler, goal: DVec2, tolerance: f64, layers: impl Iterator<Item = LayerNodeIdentifier>) -> Option<(LayerNodeIdentifier, PointId, DVec2)> {
document: &DocumentMessageHandler, closest_point(document, goal, tolerance, layers, |_| false)
goal: DVec2,
tolerance: f64,
layers: impl Iterator<Item = LayerNodeIdentifier>,
preferences: &PreferencesMessageHandler,
) -> Option<(LayerNodeIdentifier, PointId, DVec2)> {
closest_point(document, goal, tolerance, layers, |_| false, preferences)
} }
/// Determine the closest point to the goal point under max_distance. /// Determine the closest point to the goal point under max_distance.
@ -40,7 +34,6 @@ pub fn closest_point<T>(
max_distance: f64, max_distance: f64,
layers: impl Iterator<Item = LayerNodeIdentifier>, layers: impl Iterator<Item = LayerNodeIdentifier>,
exclude: T, exclude: T,
preferences: &PreferencesMessageHandler,
) -> Option<(LayerNodeIdentifier, PointId, DVec2)> ) -> Option<(LayerNodeIdentifier, PointId, DVec2)>
where where
T: Fn(PointId) -> bool, T: Fn(PointId) -> bool,
@ -50,7 +43,7 @@ where
for layer in layers { for layer in layers {
let viewspace = document.metadata().transform_to_viewport(layer); let viewspace = document.metadata().transform_to_viewport(layer);
let Some(vector) = document.network_interface.compute_modified_vector(layer) else { continue }; let Some(vector) = document.network_interface.compute_modified_vector(layer) else { continue };
for id in vector.extendable_points(preferences.vector_meshes) { for id in vector.extendable_points() {
if exclude(id) { if exclude(id) {
continue; continue;
} }

View File

@ -236,7 +236,6 @@ impl Fsm for FreehandToolFsmState {
global_tool_data, global_tool_data,
input, input,
shape_editor, shape_editor,
preferences,
viewport, viewport,
.. ..
} = tool_action_data; } = tool_action_data;
@ -244,7 +243,7 @@ impl Fsm for FreehandToolFsmState {
let ToolMessage::Freehand(event) = event else { return self }; let ToolMessage::Freehand(event) = event else { return self };
match (self, event) { match (self, event) {
(_, FreehandToolMessage::Overlays { context: mut overlay_context }) => { (_, FreehandToolMessage::Overlays { context: mut overlay_context }) => {
path_endpoint_overlays(document, shape_editor, &mut overlay_context, tool_action_data.preferences); path_endpoint_overlays(document, shape_editor, &mut overlay_context);
self self
} }
@ -258,7 +257,7 @@ impl Fsm for FreehandToolFsmState {
// Extend an endpoint of the selected path // Extend an endpoint of the selected path
let selected_nodes = document.network_interface.selected_nodes(); let selected_nodes = document.network_interface.selected_nodes();
let tolerance = crate::consts::SNAP_POINT_TOLERANCE; let tolerance = crate::consts::SNAP_POINT_TOLERANCE;
if let Some((layer, point, position)) = should_extend(document, input.mouse.position, tolerance, selected_nodes.selected_layers(document.metadata()), preferences) { if let Some((layer, point, position)) = should_extend(document, input.mouse.position, tolerance, selected_nodes.selected_layers(document.metadata())) {
tool_data.layer = Some(layer); tool_data.layer = Some(layer);
tool_data.end_point = Some((position, point)); tool_data.end_point = Some((position, point));
@ -519,7 +518,7 @@ mod test_freehand {
initial_segment_count initial_segment_count
); );
let extendable_points = initial_vector.extendable_points(false).collect::<Vec<_>>(); let extendable_points = initial_vector.extendable_points().collect::<Vec<_>>();
assert!(!extendable_points.is_empty(), "No extendable points found in the path"); assert!(!extendable_points.is_empty(), "No extendable points found in the path");
let endpoint_id = extendable_points[0]; let endpoint_id = extendable_points[0];

View File

@ -823,7 +823,7 @@ impl PathToolData {
.filter(|handle| handle.length(&vector) < 1e-6) .filter(|handle| handle.length(&vector) < 1e-6)
.map(|handle| handle.to_manipulator_point()) .map(|handle| handle.to_manipulator_point())
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let endpoint = vector.extendable_points(false).any(|anchor| point == anchor); let endpoint = vector.extendable_points_no_vector_meshes().any(|anchor| point == anchor);
if drag_zero_handle && (handles.len() == 1 && !endpoint) { if drag_zero_handle && (handles.len() == 1 && !endpoint) {
shape_editor.deselect_all_points(); shape_editor.deselect_all_points();
@ -2662,7 +2662,7 @@ impl Fsm for PathToolFsmState {
} }
(_, PathToolMessage::ClosePath) => { (_, PathToolMessage::ClosePath) => {
responses.add(DocumentMessage::AddTransaction); responses.add(DocumentMessage::AddTransaction);
shape_editor.close_selected_path(document, responses, tool_action_data.preferences.vector_meshes); shape_editor.close_selected_path(document, responses);
responses.add(DocumentMessage::EndTransaction); responses.add(DocumentMessage::EndTransaction);
responses.add(OverlaysMessage::Draw); responses.add(OverlaysMessage::Draw);

View File

@ -589,15 +589,7 @@ impl PenToolData {
} }
/// If the user places the anchor on top of the previous anchor, it becomes sharp and the outgoing handle may be dragged. /// If the user places the anchor on top of the previous anchor, it becomes sharp and the outgoing handle may be dragged.
fn bend_from_previous_point( fn bend_from_previous_point(&mut self, snap_data: SnapData, transform: DAffine2, layer: LayerNodeIdentifier, shape_editor: &mut ShapeState, responses: &mut VecDeque<Message>) {
&mut self,
snap_data: SnapData,
transform: DAffine2,
layer: LayerNodeIdentifier,
preferences: &PreferencesMessageHandler,
shape_editor: &mut ShapeState,
responses: &mut VecDeque<Message>,
) {
self.g1_continuous = true; self.g1_continuous = true;
let document = snap_data.document; let document = snap_data.document;
self.next_handle_start = self.next_point; self.next_handle_start = self.next_point;
@ -614,7 +606,7 @@ impl PenToolData {
self.handle_end = None; self.handle_end = None;
self.handle_mode = HandleMode::Free; self.handle_mode = HandleMode::Free;
self.store_clicked_endpoint(document, &transform, snap_data.input, snap_data.viewport, preferences); self.store_clicked_endpoint(document, &transform, snap_data.input, snap_data.viewport);
if self.modifiers.lock_angle { if self.modifiers.lock_angle {
self.set_lock_angle(&vector, id, self.prior_segment); self.set_lock_angle(&vector, id, self.prior_segment);
@ -631,8 +623,8 @@ impl PenToolData {
} }
// Closing path // Closing path
let closing_path_on_point = self.close_path_on_point(snap_data, &vector, document, preferences, id, &transform); let closing_path_on_point = self.close_path_on_point(snap_data, &vector, document, id, &transform);
if !closing_path_on_point && preferences.vector_meshes { if !closing_path_on_point {
// Attempt to find nearest segment and close path on segment by creating an anchor point on it // Attempt to find nearest segment and close path on segment by creating an anchor point on it
let tolerance = crate::consts::SNAP_POINT_TOLERANCE; let tolerance = crate::consts::SNAP_POINT_TOLERANCE;
if let Some(closest_segment) = shape_editor.upper_closest_segment(&document.network_interface, transform.transform_point2(self.next_point), tolerance) { if let Some(closest_segment) = shape_editor.upper_closest_segment(&document.network_interface, transform.transform_point2(self.next_point), tolerance) {
@ -659,8 +651,8 @@ impl PenToolData {
} }
} }
fn close_path_on_point(&mut self, snap_data: SnapData, vector: &Vector, document: &DocumentMessageHandler, preferences: &PreferencesMessageHandler, id: PointId, transform: &DAffine2) -> bool { fn close_path_on_point(&mut self, snap_data: SnapData, vector: &Vector, document: &DocumentMessageHandler, id: PointId, transform: &DAffine2) -> bool {
for id in vector.extendable_points(preferences.vector_meshes).filter(|&point| point != id) { for id in vector.extendable_points().filter(|&point| point != id) {
let Some(pos) = vector.point_domain.position_from_id(id) else { continue }; let Some(pos) = vector.point_domain.position_from_id(id) else { continue };
let transformed_distance_between_squared = transform.transform_point2(pos).distance_squared(transform.transform_point2(self.next_point)); let transformed_distance_between_squared = transform.transform_point2(pos).distance_squared(transform.transform_point2(self.next_point));
let snap_point_tolerance_squared = crate::consts::SNAP_POINT_TOLERANCE.powi(2); let snap_point_tolerance_squared = crate::consts::SNAP_POINT_TOLERANCE.powi(2);
@ -670,7 +662,7 @@ impl PenToolData {
self.handle_end_offset = None; self.handle_end_offset = None;
self.path_closed = true; self.path_closed = true;
self.next_handle_start = self.next_point; self.next_handle_start = self.next_point;
self.store_clicked_endpoint(document, transform, snap_data.input, snap_data.viewport, preferences); self.store_clicked_endpoint(document, transform, snap_data.input, snap_data.viewport);
self.handle_mode = HandleMode::Free; self.handle_mode = HandleMode::Free;
if let (true, Some(prior_endpoint)) = (self.modifiers.lock_angle, self.prior_segment_endpoint) { if let (true, Some(prior_endpoint)) = (self.modifiers.lock_angle, self.prior_segment_endpoint) {
self.set_lock_angle(vector, prior_endpoint, self.prior_segment); self.set_lock_angle(vector, prior_endpoint, self.prior_segment);
@ -682,7 +674,7 @@ impl PenToolData {
false false
} }
fn finish_placing_handle(&mut self, snap_data: SnapData, transform: DAffine2, preferences: &PreferencesMessageHandler, responses: &mut VecDeque<Message>) -> Option<PenToolFsmState> { fn finish_placing_handle(&mut self, snap_data: SnapData, transform: DAffine2, responses: &mut VecDeque<Message>) -> Option<PenToolFsmState> {
let document = snap_data.document; let document = snap_data.document;
let next_handle_start = self.next_handle_start; let next_handle_start = self.next_handle_start;
let handle_start = self.latest_point()?.handle_start; let handle_start = self.latest_point()?.handle_start;
@ -693,12 +685,12 @@ impl PenToolData {
let Some(handle_end) = self.handle_end else { let Some(handle_end) = self.handle_end else {
responses.add(DocumentMessage::EndTransaction); responses.add(DocumentMessage::EndTransaction);
self.handle_end = Some(next_handle_start); self.handle_end = Some(next_handle_start);
self.place_anchor(snap_data, transform, mouse, preferences, responses); self.place_anchor(snap_data, transform, mouse, responses);
self.latest_point_mut()?.handle_start = next_handle_start; self.latest_point_mut()?.handle_start = next_handle_start;
return None; return None;
}; };
let next_point = self.next_point; let next_point = self.next_point;
self.place_anchor(snap_data, transform, mouse, preferences, responses); self.place_anchor(snap_data, transform, mouse, responses);
let handles = [handle_start - self.latest_point()?.pos, handle_end - next_point].map(Some); let handles = [handle_start - self.latest_point()?.pos, handle_end - next_point].map(Some);
// Get close path // Get close path
@ -709,7 +701,7 @@ impl PenToolData {
let vector = document.network_interface.compute_modified_vector(layer)?; let vector = document.network_interface.compute_modified_vector(layer)?;
let start = self.latest_point()?.id; let start = self.latest_point()?.id;
let transform = document.metadata().document_to_viewport * transform; let transform = document.metadata().document_to_viewport * transform;
for id in vector.extendable_points(preferences.vector_meshes).filter(|&point| point != start) { for id in vector.extendable_points().filter(|&point| point != start) {
let Some(pos) = vector.point_domain.position_from_id(id) else { continue }; let Some(pos) = vector.point_domain.position_from_id(id) else { continue };
let transformed_distance_between_squared = transform.transform_point2(pos).distance_squared(transform.transform_point2(next_point)); let transformed_distance_between_squared = transform.transform_point2(pos).distance_squared(transform.transform_point2(next_point));
let snap_point_tolerance_squared = crate::consts::SNAP_POINT_TOLERANCE.powi(2); let snap_point_tolerance_squared = crate::consts::SNAP_POINT_TOLERANCE.powi(2);
@ -1123,7 +1115,7 @@ impl PenToolData {
self.update_target_handle_pos(opposite_handle, self.next_point, responses, new_handle_position, layer); self.update_target_handle_pos(opposite_handle, self.next_point, responses, new_handle_position, layer);
} }
fn place_anchor(&mut self, snap_data: SnapData, transform: DAffine2, mouse: DVec2, preferences: &PreferencesMessageHandler, responses: &mut VecDeque<Message>) -> Option<PenToolFsmState> { fn place_anchor(&mut self, snap_data: SnapData, transform: DAffine2, mouse: DVec2, responses: &mut VecDeque<Message>) -> Option<PenToolFsmState> {
let document = snap_data.document; let document = snap_data.document;
let relative = if self.path_closed { None } else { self.latest_point().map(|point| point.pos) }; let relative = if self.path_closed { None } else { self.latest_point().map(|point| point.pos) };
@ -1134,7 +1126,7 @@ impl PenToolData {
let layer = selected_layers.next().filter(|_| selected_layers.next().is_none()).or(self.current_layer)?; let layer = selected_layers.next().filter(|_| selected_layers.next().is_none()).or(self.current_layer)?;
let vector = document.network_interface.compute_modified_vector(layer)?; let vector = document.network_interface.compute_modified_vector(layer)?;
let transform = document.metadata().document_to_viewport * transform; let transform = document.metadata().document_to_viewport * transform;
for point in vector.extendable_points(preferences.vector_meshes) { for point in vector.extendable_points() {
let Some(pos) = vector.point_domain.position_from_id(point) else { continue }; let Some(pos) = vector.point_domain.position_from_id(point) else { continue };
let transformed_distance_between_squared = transform.transform_point2(pos).distance_squared(transform.transform_point2(self.next_point)); let transformed_distance_between_squared = transform.transform_point2(pos).distance_squared(transform.transform_point2(self.next_point));
let snap_point_tolerance_squared = crate::consts::SNAP_POINT_TOLERANCE.powi(2); let snap_point_tolerance_squared = crate::consts::SNAP_POINT_TOLERANCE.powi(2);
@ -1230,7 +1222,6 @@ impl PenToolData {
responses: &mut VecDeque<Message>, responses: &mut VecDeque<Message>,
tool_options: &PenOptions, tool_options: &PenOptions,
append: bool, append: bool,
preferences: &PreferencesMessageHandler,
shape_editor: &mut ShapeState, shape_editor: &mut ShapeState,
) { ) {
let point = SnapCandidatePoint::handle(document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position)); let point = SnapCandidatePoint::handle(document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position));
@ -1242,14 +1233,12 @@ impl PenToolData {
self.handle_end = None; self.handle_end = None;
let tolerance = crate::consts::SNAP_POINT_TOLERANCE; let tolerance = crate::consts::SNAP_POINT_TOLERANCE;
let extension_choice = should_extend(document, viewport_vec, tolerance, selected_nodes.selected_layers(document.metadata()), preferences); let extension_choice = should_extend(document, viewport_vec, tolerance, selected_nodes.selected_layers(document.metadata()));
if let Some((layer, point, position)) = extension_choice { if let Some((layer, point, position)) = extension_choice {
self.current_layer = Some(layer); self.current_layer = Some(layer);
self.extend_existing_path(document, layer, point, position); self.extend_existing_path(document, layer, point, position);
return; return;
} else if preferences.vector_meshes } else if let Some(closest_segment) = shape_editor.upper_closest_segment(&document.network_interface, viewport_vec, tolerance) {
&& let Some(closest_segment) = shape_editor.upper_closest_segment(&document.network_interface, viewport_vec, tolerance)
{
let (point, segments) = closest_segment.adjusted_insert(responses); let (point, segments) = closest_segment.adjusted_insert(responses);
let layer = closest_segment.layer(); let layer = closest_segment.layer();
let position = closest_segment.closest_point_document(); let position = closest_segment.closest_point_document();
@ -1264,7 +1253,7 @@ impl PenToolData {
} }
if append { if append {
if let Some((layer, point, _)) = closest_point(document, viewport_vec, tolerance, document.metadata().all_layers(), |_| false, preferences) { if let Some((layer, point, _)) = closest_point(document, viewport_vec, tolerance, document.metadata().all_layers(), |_| false) {
let vector = document.network_interface.compute_modified_vector(layer).unwrap(); let vector = document.network_interface.compute_modified_vector(layer).unwrap();
let segment = vector.all_connected(point).collect::<Vec<_>>().first().map(|s| s.segment); let segment = vector.all_connected(point).collect::<Vec<_>>().first().map(|s| s.segment);
@ -1282,7 +1271,7 @@ impl PenToolData {
} }
} }
if let Some((layer, point, _position)) = closest_point(document, viewport_vec, tolerance, document.metadata().all_layers(), |_| false, preferences) { if let Some((layer, point, _position)) = closest_point(document, viewport_vec, tolerance, document.metadata().all_layers(), |_| false) {
let vector = document.network_interface.compute_modified_vector(layer).unwrap(); let vector = document.network_interface.compute_modified_vector(layer).unwrap();
let segment = vector.all_connected(point).collect::<Vec<_>>().first().map(|s| s.segment); let segment = vector.all_connected(point).collect::<Vec<_>>().first().map(|s| s.segment);
self.handle_mode = HandleMode::Free; self.handle_mode = HandleMode::Free;
@ -1366,14 +1355,7 @@ impl PenToolData {
} }
// Stores the segment and point ID of the clicked endpoint // Stores the segment and point ID of the clicked endpoint
fn store_clicked_endpoint( fn store_clicked_endpoint(&mut self, document: &DocumentMessageHandler, transform: &DAffine2, input: &InputPreprocessorMessageHandler, viewport: &ViewportMessageHandler) {
&mut self,
document: &DocumentMessageHandler,
transform: &DAffine2,
input: &InputPreprocessorMessageHandler,
viewport: &ViewportMessageHandler,
preferences: &PreferencesMessageHandler,
) {
let mut manipulators = HashMap::with_hasher(NoHashBuilder); let mut manipulators = HashMap::with_hasher(NoHashBuilder);
let mut unselected = Vec::new(); let mut unselected = Vec::new();
let mut layer_manipulators = HashSet::with_hasher(NoHashBuilder); let mut layer_manipulators = HashSet::with_hasher(NoHashBuilder);
@ -1389,7 +1371,7 @@ impl PenToolData {
self.prior_segment_layer = None; self.prior_segment_layer = None;
self.prior_segments = None; self.prior_segments = None;
if let Some((layer, point, _position)) = closest_point(document, viewport, tolerance, document.metadata().all_layers(), |_| false, preferences) { if let Some((layer, point, _position)) = closest_point(document, viewport, tolerance, document.metadata().all_layers(), |_| false) {
self.prior_segment_endpoint = Some(point); self.prior_segment_endpoint = Some(point);
self.prior_segment_layer = Some(layer); self.prior_segment_layer = Some(layer);
let vector = document.network_interface.compute_modified_vector(layer).unwrap(); let vector = document.network_interface.compute_modified_vector(layer).unwrap();
@ -1468,7 +1450,6 @@ impl Fsm for PenToolFsmState {
global_tool_data, global_tool_data,
input, input,
shape_editor, shape_editor,
preferences,
viewport, viewport,
.. ..
} = tool_action_data; } = tool_action_data;
@ -1630,11 +1611,8 @@ impl Fsm for PenToolFsmState {
let snapped = tool_data.snap_manager.free_snap(&SnapData::new(document, input, viewport), &point, SnapTypeConfiguration::default()); let snapped = tool_data.snap_manager.free_snap(&SnapData::new(document, input, viewport), &point, SnapTypeConfiguration::default());
let viewport_vec = document.metadata().document_to_viewport.transform_point2(snapped.snapped_point_document); let viewport_vec = document.metadata().document_to_viewport.transform_point2(snapped.snapped_point_document);
let close_to_point = closest_point(document, viewport_vec, tolerance, document.metadata().all_layers(), |_| false, preferences).is_some(); let close_to_point = closest_point(document, viewport_vec, tolerance, document.metadata().all_layers(), |_| false).is_some();
if preferences.vector_meshes if !close_to_point && let Some(closest_segment) = shape_editor.upper_closest_segment(&document.network_interface, viewport_vec, tolerance) {
&& !close_to_point
&& let Some(closest_segment) = shape_editor.upper_closest_segment(&document.network_interface, viewport_vec, tolerance)
{
let pos = closest_segment.closest_point_to_viewport(); let pos = closest_segment.closest_point_to_viewport();
let perp = closest_segment.calculate_perp(document); let perp = closest_segment.calculate_perp(document);
overlay_context.manipulator_anchor(pos, true, None); overlay_context.manipulator_anchor(pos, true, None);
@ -1689,10 +1667,8 @@ impl Fsm for PenToolFsmState {
} }
// If a vector mesh then there can be more than one prior segments // If a vector mesh then there can be more than one prior segments
else if let Some(segments) = tool_data.prior_segments.clone() { else if let Some(segments) = tool_data.prior_segments.clone() {
if preferences.vector_meshes { let selected_anchors_data = HashMap::from([(layer, segments)]);
let selected_anchors_data = HashMap::from([(layer, segments)]); path_overlays(document, DrawHandles::SelectedAnchors(selected_anchors_data), shape_editor, &mut overlay_context);
path_overlays(document, DrawHandles::SelectedAnchors(selected_anchors_data), shape_editor, &mut overlay_context);
}
} else { } else {
path_overlays(document, DrawHandles::None, shape_editor, &mut overlay_context); path_overlays(document, DrawHandles::None, shape_editor, &mut overlay_context);
}; };
@ -1755,12 +1731,12 @@ impl Fsm for PenToolFsmState {
overlay_context.manipulator_anchor(next_anchor, false, None); overlay_context.manipulator_anchor(next_anchor, false, None);
} }
if self == PenToolFsmState::PlacingAnchor && preferences.vector_meshes { if self == PenToolFsmState::PlacingAnchor {
let tolerance = crate::consts::SNAP_POINT_TOLERANCE; let tolerance = crate::consts::SNAP_POINT_TOLERANCE;
let point = SnapCandidatePoint::handle(document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position)); let point = SnapCandidatePoint::handle(document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position));
let snapped = tool_data.snap_manager.free_snap(&SnapData::new(document, input, viewport), &point, SnapTypeConfiguration::default()); let snapped = tool_data.snap_manager.free_snap(&SnapData::new(document, input, viewport), &point, SnapTypeConfiguration::default());
let viewport = document.metadata().document_to_viewport.transform_point2(snapped.snapped_point_document); let viewport = document.metadata().document_to_viewport.transform_point2(snapped.snapped_point_document);
let close_to_point = closest_point(document, viewport, tolerance, document.metadata().all_layers(), |_| false, preferences).is_some(); let close_to_point = closest_point(document, viewport, tolerance, document.metadata().all_layers(), |_| false).is_some();
if !close_to_point && let Some(closest_segment) = shape_editor.upper_closest_segment(&document.network_interface, viewport, tolerance) { if !close_to_point && let Some(closest_segment) = shape_editor.upper_closest_segment(&document.network_interface, viewport, tolerance) {
let pos = closest_segment.closest_point_to_viewport(); let pos = closest_segment.closest_point_to_viewport();
let perp = closest_segment.calculate_perp(document); let perp = closest_segment.calculate_perp(document);
@ -1779,7 +1755,7 @@ impl Fsm for PenToolFsmState {
if let Some(layer) = layer if let Some(layer) = layer
&& let Some(mut vector) = document.network_interface.compute_modified_vector(layer) && let Some(mut vector) = document.network_interface.compute_modified_vector(layer)
{ {
let closest_point = vector.extendable_points(preferences.vector_meshes).filter(|&id| id != start).find(|&id| { let closest_point = vector.extendable_points().filter(|&id| id != start).find(|&id| {
vector.point_domain.position_from_id(id).is_some_and(|pos| { vector.point_domain.position_from_id(id).is_some_and(|pos| {
let dist_sq = transform.transform_point2(pos).distance_squared(transform.transform_point2(next_point)); let dist_sq = transform.transform_point2(pos).distance_squared(transform.transform_point2(next_point));
dist_sq < crate::consts::SNAP_POINT_TOLERANCE.powi(2) dist_sq < crate::consts::SNAP_POINT_TOLERANCE.powi(2)
@ -1835,8 +1811,8 @@ impl Fsm for PenToolFsmState {
// Get the closest point and the segment it is on // Get the closest point and the segment it is on
let append = input.keyboard.key(append_to_selected); let append = input.keyboard.key(append_to_selected);
tool_data.store_clicked_endpoint(document, &transform, input, viewport, preferences); tool_data.store_clicked_endpoint(document, &transform, input, viewport);
tool_data.create_initial_point(document, input, viewport, responses, tool_options, append, preferences, shape_editor); tool_data.create_initial_point(document, input, viewport, responses, tool_options, append, shape_editor);
// Enter the dragging handle state while the mouse is held down, allowing the user to move the mouse and position the handle // Enter the dragging handle state while the mouse is held down, allowing the user to move the mouse and position the handle
PenToolFsmState::DraggingHandle(tool_data.handle_mode) PenToolFsmState::DraggingHandle(tool_data.handle_mode)
@ -1860,8 +1836,8 @@ impl Fsm for PenToolFsmState {
if let Some(layer) = layer { if let Some(layer) = layer {
tool_data.buffering_merged_vector = false; tool_data.buffering_merged_vector = false;
tool_data.handle_mode = HandleMode::ColinearLocked; tool_data.handle_mode = HandleMode::ColinearLocked;
tool_data.bend_from_previous_point(SnapData::new(document, input, viewport), transform, layer, preferences, shape_editor, responses); tool_data.bend_from_previous_point(SnapData::new(document, input, viewport), transform, layer, shape_editor, responses);
tool_data.place_anchor(SnapData::new(document, input, viewport), transform, input.mouse.position, preferences, responses); tool_data.place_anchor(SnapData::new(document, input, viewport), transform, input.mouse.position, responses);
} }
tool_data.buffering_merged_vector = false; tool_data.buffering_merged_vector = false;
PenToolFsmState::DraggingHandle(tool_data.handle_mode) PenToolFsmState::DraggingHandle(tool_data.handle_mode)
@ -1876,7 +1852,7 @@ impl Fsm for PenToolFsmState {
let layers = LayerNodeIdentifier::ROOT_PARENT let layers = LayerNodeIdentifier::ROOT_PARENT
.descendants(document.metadata()) .descendants(document.metadata())
.filter(|layer| !document.network_interface.is_artboard(&layer.to_node(), &[])); .filter(|layer| !document.network_interface.is_artboard(&layer.to_node(), &[]));
if let Some((other_layer, _, _)) = should_extend(document, viewport_vec, crate::consts::SNAP_POINT_TOLERANCE, layers, preferences) { if let Some((other_layer, _, _)) = should_extend(document, viewport_vec, crate::consts::SNAP_POINT_TOLERANCE, layers) {
let selected_nodes = document.network_interface.selected_nodes(); let selected_nodes = document.network_interface.selected_nodes();
let mut selected_layers = selected_nodes.selected_layers(document.metadata()); let mut selected_layers = selected_nodes.selected_layers(document.metadata());
if let Some(current_layer) = selected_layers if let Some(current_layer) = selected_layers
@ -1906,7 +1882,7 @@ impl Fsm for PenToolFsmState {
(PenToolFsmState::DraggingHandle(_), PenToolMessage::DragStop) => { (PenToolFsmState::DraggingHandle(_), PenToolMessage::DragStop) => {
tool_data.cleanup_target_selections(shape_editor, layer, document, responses); tool_data.cleanup_target_selections(shape_editor, layer, document, responses);
tool_data tool_data
.finish_placing_handle(SnapData::new(document, input, viewport), transform, preferences, responses) .finish_placing_handle(SnapData::new(document, input, viewport), transform, responses)
.unwrap_or(PenToolFsmState::PlacingAnchor) .unwrap_or(PenToolFsmState::PlacingAnchor)
} }
( (
@ -2025,7 +2001,7 @@ impl Fsm for PenToolFsmState {
move_anchor_with_handles: input.keyboard.key(move_anchor_with_handles), move_anchor_with_handles: input.keyboard.key(move_anchor_with_handles),
}; };
let state = tool_data let state = tool_data
.place_anchor(SnapData::new(document, input, viewport), transform, input.mouse.position, preferences, responses) .place_anchor(SnapData::new(document, input, viewport), transform, input.mouse.position, responses)
.unwrap_or(PenToolFsmState::Ready); .unwrap_or(PenToolFsmState::Ready);
// Auto-panning // Auto-panning
@ -2132,7 +2108,7 @@ impl Fsm for PenToolFsmState {
// Confirm to end path // Confirm to end path
if let Some((vector, layer)) = layer.and_then(|layer| document.network_interface.compute_modified_vector(layer)).zip(layer) { if let Some((vector, layer)) = layer.and_then(|layer| document.network_interface.compute_modified_vector(layer)).zip(layer) {
let single_point_in_layer = vector.point_domain.ids().len() == 1; let single_point_in_layer = vector.point_domain.ids().len() == 1;
tool_data.finish_placing_handle(SnapData::new(document, input, viewport), transform, preferences, responses); tool_data.finish_placing_handle(SnapData::new(document, input, viewport), transform, responses);
let latest_points = tool_data.latest_points.len() == 1; let latest_points = tool_data.latest_points.len() == 1;
if latest_points && single_point_in_layer { if latest_points && single_point_in_layer {
@ -2179,7 +2155,7 @@ impl Fsm for PenToolFsmState {
PenToolFsmState::Ready PenToolFsmState::Ready
} else { } else {
tool_data tool_data
.place_anchor(SnapData::new(document, input, viewport), transform, input.mouse.position, preferences, responses) .place_anchor(SnapData::new(document, input, viewport), transform, input.mouse.position, responses)
.unwrap_or(PenToolFsmState::Ready) .unwrap_or(PenToolFsmState::Ready)
} }
} }
@ -2210,7 +2186,7 @@ impl Fsm for PenToolFsmState {
if tool_data.point_index > 0 { if tool_data.point_index > 0 {
tool_data.point_index -= 1; tool_data.point_index -= 1;
tool_data tool_data
.place_anchor(SnapData::new(document, input, viewport), transform, input.mouse.position, preferences, responses) .place_anchor(SnapData::new(document, input, viewport), transform, input.mouse.position, responses)
.unwrap_or(PenToolFsmState::PlacingAnchor) .unwrap_or(PenToolFsmState::PlacingAnchor)
} else { } else {
responses.add(PenToolMessage::Abort); responses.add(PenToolMessage::Abort);
@ -2219,7 +2195,7 @@ impl Fsm for PenToolFsmState {
} }
(_, PenToolMessage::Redo) => { (_, PenToolMessage::Redo) => {
tool_data.point_index = (tool_data.point_index + 1).min(tool_data.latest_points.len().saturating_sub(1)); tool_data.point_index = (tool_data.point_index + 1).min(tool_data.latest_points.len().saturating_sub(1));
tool_data.place_anchor(SnapData::new(document, input, viewport), transform, input.mouse.position, preferences, responses); tool_data.place_anchor(SnapData::new(document, input, viewport), transform, input.mouse.position, responses);
match tool_data.point_index { match tool_data.point_index {
0 => PenToolFsmState::Ready, 0 => PenToolFsmState::Ready,
_ => PenToolFsmState::PlacingAnchor, _ => PenToolFsmState::PlacingAnchor,

View File

@ -556,7 +556,6 @@ impl Fsm for ShapeToolFsmState {
document, document,
global_tool_data, global_tool_data,
input, input,
preferences,
shape_editor, shape_editor,
viewport, viewport,
.. ..
@ -762,7 +761,6 @@ impl Fsm for ShapeToolFsmState {
SNAP_POINT_TOLERANCE, SNAP_POINT_TOLERANCE,
document.network_interface.selected_nodes().selected_visible_and_unlocked_layers(&document.network_interface), document.network_interface.selected_nodes().selected_visible_and_unlocked_layers(&document.network_interface),
|_| false, |_| false,
preferences,
) && clicked_on_line_endpoints(layer, document, input, tool_data) ) && clicked_on_line_endpoints(layer, document, input, tool_data)
&& !input.keyboard.key(Key::Control) && !input.keyboard.key(Key::Control)
{ {

View File

@ -294,7 +294,6 @@ impl Fsm for SplineToolFsmState {
global_tool_data, global_tool_data,
input, input,
shape_editor, shape_editor,
preferences,
viewport, viewport,
.. ..
} = tool_action_data; } = tool_action_data;
@ -303,7 +302,7 @@ impl Fsm for SplineToolFsmState {
match (self, event) { match (self, event) {
(_, SplineToolMessage::CanvasTransformed) => self, (_, SplineToolMessage::CanvasTransformed) => self,
(_, SplineToolMessage::Overlays { context: mut overlay_context }) => { (_, SplineToolMessage::Overlays { context: mut overlay_context }) => {
path_endpoint_overlays(document, shape_editor, &mut overlay_context, preferences); path_endpoint_overlays(document, shape_editor, &mut overlay_context);
tool_data.snap_manager.draw_overlays(SnapData::new(document, input, viewport), &mut overlay_context); tool_data.snap_manager.draw_overlays(SnapData::new(document, input, viewport), &mut overlay_context);
self self
} }
@ -351,7 +350,7 @@ impl Fsm for SplineToolFsmState {
.filter(|layer| !document.network_interface.is_artboard(&layer.to_node(), &[])); .filter(|layer| !document.network_interface.is_artboard(&layer.to_node(), &[]));
// Extend an endpoint of the selected path // Extend an endpoint of the selected path
if let Some((layer, point, position)) = should_extend(document, viewport_vec, SNAP_POINT_TOLERANCE, layers, preferences) { if let Some((layer, point, position)) = should_extend(document, viewport_vec, SNAP_POINT_TOLERANCE, layers) {
if find_spline(document, layer).is_some() { if find_spline(document, layer).is_some() {
// If the point is the part of Spline then we extend it. // If the point is the part of Spline then we extend it.
tool_data.current_layer = Some(layer); tool_data.current_layer = Some(layer);
@ -417,7 +416,7 @@ impl Fsm for SplineToolFsmState {
extend_spline(tool_data, false, responses); extend_spline(tool_data, false, responses);
tool_data.preview_point = preview_point; tool_data.preview_point = preview_point;
if try_merging_lastest_endpoint(document, tool_data, preferences).is_some() { if try_merging_lastest_endpoint(document, tool_data).is_some() {
responses.add(SplineToolMessage::Confirm); responses.add(SplineToolMessage::Confirm);
} }
} }
@ -427,7 +426,7 @@ impl Fsm for SplineToolFsmState {
(SplineToolFsmState::Drawing, SplineToolMessage::PointerMove) => { (SplineToolFsmState::Drawing, SplineToolMessage::PointerMove) => {
let Some(layer) = tool_data.current_layer else { return SplineToolFsmState::Ready }; let Some(layer) = tool_data.current_layer else { return SplineToolFsmState::Ready };
let ignore = |cp: PointId| tool_data.preview_point.is_some_and(|pp| pp == cp) || tool_data.points.last().is_some_and(|(ep, _)| *ep == cp); let ignore = |cp: PointId| tool_data.preview_point.is_some_and(|pp| pp == cp) || tool_data.points.last().is_some_and(|(ep, _)| *ep == cp);
let join_point = closest_point(document, input.mouse.position, PATH_JOIN_THRESHOLD, vec![layer].into_iter(), ignore, preferences); let join_point = closest_point(document, input.mouse.position, PATH_JOIN_THRESHOLD, vec![layer].into_iter(), ignore);
// Endpoints snapping // Endpoints snapping
if let Some((_, _, point)) = join_point { if let Some((_, _, point)) = join_point {
@ -511,7 +510,7 @@ impl Fsm for SplineToolFsmState {
} }
} }
fn try_merging_lastest_endpoint(document: &DocumentMessageHandler, tool_data: &mut SplineToolData, preferences: &PreferencesMessageHandler) -> Option<()> { fn try_merging_lastest_endpoint(document: &DocumentMessageHandler, tool_data: &mut SplineToolData) -> Option<()> {
if tool_data.points.len() < 2 { if tool_data.points.len() < 2 {
return None; return None;
}; };
@ -526,7 +525,7 @@ fn try_merging_lastest_endpoint(document: &DocumentMessageHandler, tool_data: &m
let exclude = |p: PointId| preview_point.is_some_and(|pp| pp == p) || *last_endpoint == p; let exclude = |p: PointId| preview_point.is_some_and(|pp| pp == p) || *last_endpoint == p;
let position = document.metadata().transform_to_viewport(current_layer).transform_point2(*last_endpoint_position); let position = document.metadata().transform_to_viewport(current_layer).transform_point2(*last_endpoint_position);
let (layer, endpoint, _) = closest_point(document, position, PATH_JOIN_THRESHOLD, layers, exclude, preferences)?; let (layer, endpoint, _) = closest_point(document, position, PATH_JOIN_THRESHOLD, layers, exclude)?;
tool_data.merge_layers.insert(layer); tool_data.merge_layers.insert(layer);
tool_data.merge_endpoints.push((EndpointPosition::End, endpoint)); tool_data.merge_endpoints.push((EndpointPosition::End, endpoint));
@ -647,7 +646,7 @@ mod test_spline_tool {
let layer_to_viewport = document.metadata().transform_to_viewport(spline_layer); let layer_to_viewport = document.metadata().transform_to_viewport(spline_layer);
let endpoints: Vec<(PointId, DVec2)> = first_vector let endpoints: Vec<(PointId, DVec2)> = first_vector
.extendable_points(false) .extendable_points_no_vector_meshes()
.filter_map(|point_id| first_vector.point_domain.position_from_id(point_id).map(|pos| (point_id, layer_to_viewport.transform_point2(pos)))) .filter_map(|point_id| first_vector.point_domain.position_from_id(point_id).map(|pos| (point_id, layer_to_viewport.transform_point2(pos))))
.collect(); .collect();

View File

@ -366,10 +366,20 @@ impl<Upstream> Vector<Upstream> {
/// Points that can be extended from. /// Points that can be extended from.
/// ///
/// This is usually only points with exactly one connection unless vector meshes are enabled. /// This may be points with more than one connection because of vector meshes.
pub fn extendable_points(&self, vector_meshes: bool) -> impl Iterator<Item = PointId> + '_ { pub fn extendable_points(&self) -> impl Iterator<Item = PointId> + '_ {
let point_ids = self.point_domain.ids().iter().enumerate(); self.point_domain.ids().iter().copied()
point_ids.filter(move |(index, _)| vector_meshes || self.segment_domain.connected_count(*index) == 1).map(|(_, &id)| id) }
// TODO: Avoid needing this special function that's used in only one place. See: <https://github.com/GraphiteEditor/Graphite/commit/6e7f218068a55cc22659ee2cf4f0f2cf26d37774#r173283173>
/// Points that can be extended from.
///
/// This includes only points with exactly one connection because vector meshes are ignored.
pub fn extendable_points_no_vector_meshes(&self) -> impl Iterator<Item = PointId> + '_ {
self.extendable_points()
.enumerate()
.filter(|&(index, _)| self.segment_domain.connected_count(index) == 1)
.map(|(_, id)| id)
} }
/// Computes if all the connected handles are colinear for an anchor, or if that handle is colinear for a handle. /// Computes if all the connected handles are colinear for an anchor, or if that handle is colinear for a handle.