Experimental vector meshes (#2223)

* Experimental vector meshes

* Clarify limitations in label and tooltip

* Restore old traversal direction

* Fix Bezier-rs crashes

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
James Lindsay
2025-01-25 17:13:35 -08:00
committed by GitHub
co-authored by Keavon Chambers
parent 26d66298cf
commit 93880abc4c
16 changed files with 102 additions and 78 deletions
@@ -6,8 +6,14 @@ use graphene_std::vector::PointId;
use glam::DVec2;
/// 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(document: &DocumentMessageHandler, goal: DVec2, tolerance: f64, layers: impl Iterator<Item = LayerNodeIdentifier>) -> Option<(LayerNodeIdentifier, PointId, DVec2)> {
closest_point(document, goal, tolerance, layers, |_| false)
pub fn should_extend(
document: &DocumentMessageHandler,
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.
@@ -18,6 +24,7 @@ pub fn closest_point<T>(
max_distance: f64,
layers: impl Iterator<Item = LayerNodeIdentifier>,
exclude: T,
preferences: &PreferencesMessageHandler,
) -> Option<(LayerNodeIdentifier, PointId, DVec2)>
where
T: Fn(PointId) -> bool,
@@ -29,7 +36,7 @@ where
let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else {
continue;
};
for id in vector_data.single_connected_points() {
for id in vector_data.extendable_points(preferences.vector_meshes) {
if exclude(id) {
continue;
}
@@ -18,6 +18,7 @@ pub struct ToolMessageData<'a> {
pub input: &'a InputPreprocessorMessageHandler,
pub persistent_data: &'a PersistentData,
pub node_graph: &'a NodeGraphExecutor,
pub preferences: &'a PreferencesMessageHandler,
}
#[derive(Debug, Default)]
@@ -36,6 +37,7 @@ impl MessageHandler<ToolMessage, ToolMessageData<'_>> for ToolMessageHandler {
input,
persistent_data,
node_graph,
preferences,
} = data;
let font_cache = &persistent_data.font_cache;
@@ -86,6 +88,7 @@ impl MessageHandler<ToolMessage, ToolMessageData<'_>> for ToolMessageHandler {
font_cache,
shape_editor: &mut self.shape_editor,
node_graph,
preferences,
};
if let Some(tool_abort_message) = tool.event_to_message_map().tool_abort {
@@ -180,6 +183,7 @@ impl MessageHandler<ToolMessage, ToolMessageData<'_>> for ToolMessageHandler {
font_cache,
shape_editor: &mut self.shape_editor,
node_graph,
preferences,
};
// Set initial hints and cursor
@@ -269,6 +273,7 @@ impl MessageHandler<ToolMessage, ToolMessageData<'_>> for ToolMessageHandler {
font_cache,
shape_editor: &mut self.shape_editor,
node_graph,
preferences,
};
if matches!(tool_message, ToolMessage::UpdateHints) {
if self.transform_layer_handler.is_transforming() {
@@ -191,13 +191,14 @@ impl Fsm for FreehandToolFsmState {
global_tool_data,
input,
shape_editor,
preferences,
..
} = tool_action_data;
let ToolMessage::Freehand(event) = event else { return self };
match (self, event) {
(_, FreehandToolMessage::Overlays(mut overlay_context)) => {
path_endpoint_overlays(document, shape_editor, &mut overlay_context);
path_endpoint_overlays(document, shape_editor, &mut overlay_context, tool_action_data.preferences);
self
}
@@ -210,7 +211,8 @@ impl Fsm for FreehandToolFsmState {
// Extend an endpoint of the selected path
let selected_nodes = document.network_interface.selected_nodes(&[]).unwrap();
if let Some((layer, point, position)) = should_extend(document, input.mouse.position, crate::consts::SNAP_POINT_TOLERANCE, selected_nodes.selected_layers(document.metadata())) {
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) {
tool_data.layer = Some(layer);
tool_data.end_point = Some((position, point));
@@ -275,19 +275,19 @@ impl PenToolData {
}
}
fn finish_placing_handle(&mut self, snap_data: SnapData, transform: DAffine2, responses: &mut VecDeque<Message>) -> Option<PenToolFsmState> {
fn finish_placing_handle(&mut self, snap_data: SnapData, transform: DAffine2, preferences: &PreferencesMessageHandler, responses: &mut VecDeque<Message>) -> Option<PenToolFsmState> {
let document = snap_data.document;
let next_handle_start = self.next_handle_start;
let handle_start = self.latest_point()?.handle_start;
let mouse = snap_data.input.mouse.position;
let Some(handle_end) = self.handle_end else {
self.handle_end = Some(next_handle_start);
self.place_anchor(snap_data, transform, mouse, responses);
self.place_anchor(snap_data, transform, mouse, preferences, responses);
self.latest_point_mut()?.handle_start = next_handle_start;
return None;
};
let next_point = self.next_point;
self.place_anchor(snap_data, transform, mouse, responses);
self.place_anchor(snap_data, transform, mouse, preferences, responses);
let handles = [handle_start - self.latest_point()?.pos, handle_end - next_point].map(Some);
// Get close path
@@ -298,7 +298,7 @@ impl PenToolData {
let vector_data = document.network_interface.compute_modified_vector(layer)?;
let start = self.latest_point()?.id;
let transform = document.metadata().document_to_viewport * transform;
for id in vector_data.single_connected_points().filter(|&point| point != start) {
for id in vector_data.extendable_points(preferences.vector_meshes).filter(|&point| point != start) {
let Some(pos) = vector_data.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 snap_point_tolerance_squared = crate::consts::SNAP_POINT_TOLERANCE.powi(2);
@@ -359,7 +359,7 @@ impl PenToolData {
Some(PenToolFsmState::DraggingHandle)
}
fn place_anchor(&mut self, snap_data: SnapData, transform: DAffine2, mouse: DVec2, responses: &mut VecDeque<Message>) -> Option<PenToolFsmState> {
fn place_anchor(&mut self, snap_data: SnapData, transform: DAffine2, mouse: DVec2, preferences: &PreferencesMessageHandler, responses: &mut VecDeque<Message>) -> Option<PenToolFsmState> {
let document = snap_data.document;
let relative = self.latest_point().map(|point| point.pos);
@@ -370,7 +370,7 @@ impl PenToolData {
let layer = selected_layers.next().filter(|_| selected_layers.next().is_none())?;
let vector_data = document.network_interface.compute_modified_vector(layer)?;
let transform = document.metadata().document_to_viewport * transform;
for point in vector_data.single_connected_points() {
for point in vector_data.extendable_points(preferences.vector_meshes) {
let Some(pos) = vector_data.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 snap_point_tolerance_squared = crate::consts::SNAP_POINT_TOLERANCE.powi(2);
@@ -456,7 +456,15 @@ impl PenToolData {
transform.inverse().transform_point2(document_pos)
}
fn create_initial_point(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>, tool_options: &PenOptions, append: bool) {
fn create_initial_point(
&mut self,
document: &DocumentMessageHandler,
input: &InputPreprocessorMessageHandler,
responses: &mut VecDeque<Message>,
tool_options: &PenOptions,
append: bool,
preferences: &PreferencesMessageHandler,
) {
let point = SnapCandidatePoint::handle(document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position));
let snapped = self.snap_manager.free_snap(&SnapData::new(document, input), &point, SnapTypeConfiguration::default());
let viewport = document.metadata().document_to_viewport.transform_point2(snapped.snapped_point_document);
@@ -464,7 +472,8 @@ impl PenToolData {
let selected_nodes = document.network_interface.selected_nodes(&[]).unwrap();
self.handle_end = None;
if let Some((layer, point, position)) = should_extend(document, viewport, crate::consts::SNAP_POINT_TOLERANCE, selected_nodes.selected_layers(document.metadata())) {
let tolerance = crate::consts::SNAP_POINT_TOLERANCE;
if let Some((layer, point, position)) = should_extend(document, viewport, tolerance, selected_nodes.selected_layers(document.metadata()), preferences) {
// Perform extension of an existing path
self.add_point(LastPoint {
id: point,
@@ -533,6 +542,7 @@ impl Fsm for PenToolFsmState {
global_tool_data,
input,
shape_editor,
preferences,
..
} = tool_action_data;
@@ -638,7 +648,7 @@ impl Fsm for PenToolFsmState {
(PenToolFsmState::Ready, PenToolMessage::DragStart { append_to_selected }) => {
responses.add(DocumentMessage::StartTransaction);
tool_data.create_initial_point(document, input, responses, tool_options, input.keyboard.key(append_to_selected));
tool_data.create_initial_point(document, input, responses, tool_options, input.keyboard.key(append_to_selected), preferences);
// Enter the dragging handle state while the mouse is held down, allowing the user to move the mouse and position the handle
PenToolFsmState::DraggingHandle
@@ -660,7 +670,7 @@ impl Fsm for PenToolFsmState {
if tool_data.buffering_merged_vector {
tool_data.buffering_merged_vector = false;
tool_data.bend_from_previous_point(SnapData::new(document, input), transform);
tool_data.place_anchor(SnapData::new(document, input), transform, input.mouse.position, responses);
tool_data.place_anchor(SnapData::new(document, input), transform, input.mouse.position, preferences, responses);
tool_data.buffering_merged_vector = false;
PenToolFsmState::DraggingHandle
} else {
@@ -673,7 +683,7 @@ impl Fsm for PenToolFsmState {
let layers = LayerNodeIdentifier::ROOT_PARENT
.descendants(document.metadata())
.filter(|layer| !document.network_interface.is_artboard(&layer.to_node(), &[]));
if let Some((other_layer, _, _)) = should_extend(document, viewport, crate::consts::SNAP_POINT_TOLERANCE, layers) {
if let Some((other_layer, _, _)) = should_extend(document, viewport, crate::consts::SNAP_POINT_TOLERANCE, layers, preferences) {
let selected_nodes = document.network_interface.selected_nodes(&[]).unwrap();
let mut selected_layers = selected_nodes.selected_layers(document.metadata());
if let Some(current_layer) = selected_layers.next().filter(|current_layer| selected_layers.next().is_none() && *current_layer != other_layer) {
@@ -696,7 +706,7 @@ impl Fsm for PenToolFsmState {
self
}
(PenToolFsmState::DraggingHandle, PenToolMessage::DragStop) => tool_data
.finish_placing_handle(SnapData::new(document, input), transform, responses)
.finish_placing_handle(SnapData::new(document, input), transform, preferences, responses)
.unwrap_or(PenToolFsmState::PlacingAnchor),
(PenToolFsmState::DraggingHandle, PenToolMessage::PointerMove { snap_angle, break_handle, lock_angle }) => {
tool_data.modifiers = ModifierState {
@@ -724,7 +734,7 @@ impl Fsm for PenToolFsmState {
break_handle: input.keyboard.key(break_handle),
};
let state = tool_data
.place_anchor(SnapData::new(document, input), transform, input.mouse.position, responses)
.place_anchor(SnapData::new(document, input), transform, input.mouse.position, preferences, responses)
.unwrap_or(PenToolFsmState::Ready);
// Auto-panning
@@ -783,7 +793,7 @@ impl Fsm for PenToolFsmState {
if tool_data.point_index > 0 {
tool_data.point_index -= 1;
tool_data
.place_anchor(SnapData::new(document, input), transform, input.mouse.position, responses)
.place_anchor(SnapData::new(document, input), transform, input.mouse.position, preferences, responses)
.unwrap_or(PenToolFsmState::PlacingAnchor)
} else {
responses.add(PenToolMessage::Abort);
@@ -792,7 +802,7 @@ impl Fsm for PenToolFsmState {
}
(_, PenToolMessage::Redo) => {
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), transform, input.mouse.position, responses);
tool_data.place_anchor(SnapData::new(document, input), transform, input.mouse.position, preferences, responses);
match tool_data.point_index {
0 => PenToolFsmState::Ready,
_ => PenToolFsmState::PlacingAnchor,
@@ -227,6 +227,7 @@ impl Fsm for SplineToolFsmState {
global_tool_data,
input,
shape_editor,
preferences,
..
} = tool_action_data;
@@ -234,7 +235,7 @@ impl Fsm for SplineToolFsmState {
match (self, event) {
(_, SplineToolMessage::CanvasTransformed) => self,
(_, SplineToolMessage::Overlays(mut overlay_context)) => {
path_endpoint_overlays(document, shape_editor, &mut overlay_context);
path_endpoint_overlays(document, shape_editor, &mut overlay_context, preferences);
tool_data.snap_manager.draw_overlays(SnapData::new(document, input), &mut overlay_context);
self
}
@@ -250,7 +251,7 @@ impl Fsm for SplineToolFsmState {
// Extend an endpoint of the selected path
let selected_nodes = document.network_interface.selected_nodes(&[]).unwrap();
if let Some((layer, point, position)) = should_extend(document, viewport, SNAP_POINT_TOLERANCE, selected_nodes.selected_layers(document.metadata())) {
if let Some((layer, point, position)) = should_extend(document, viewport, SNAP_POINT_TOLERANCE, selected_nodes.selected_layers(document.metadata()), preferences) {
tool_data.layer = Some(layer);
tool_data.points.push((point, position));
tool_data.next_point = position;
@@ -304,7 +305,7 @@ impl Fsm for SplineToolFsmState {
if tool_data.layer.is_none() {
return SplineToolFsmState::Ready;
};
if join_path(document, input.mouse.position, tool_data, responses) {
if join_path(document, input.mouse.position, tool_data, preferences, responses) {
responses.add(DocumentMessage::EndTransaction);
return SplineToolFsmState::Ready;
}
@@ -318,7 +319,7 @@ impl Fsm for SplineToolFsmState {
(SplineToolFsmState::Drawing, SplineToolMessage::PointerMove) => {
let Some(layer) = tool_data.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 join_point = closest_point(document, input.mouse.position, PATH_JOIN_THRESHOLD, vec![layer].into_iter(), ignore);
let join_point = closest_point(document, input.mouse.position, PATH_JOIN_THRESHOLD, vec![layer].into_iter(), ignore, preferences);
// Endpoints snapping
if let Some((_, _, point)) = join_point {
@@ -402,7 +403,7 @@ impl Fsm for SplineToolFsmState {
}
/// Return `true` only if new segment is inserted to connect two end points in the selected layer otherwise `false`.
fn join_path(document: &DocumentMessageHandler, mouse_pos: DVec2, tool_data: &mut SplineToolData, responses: &mut VecDeque<Message>) -> bool {
fn join_path(document: &DocumentMessageHandler, mouse_pos: DVec2, tool_data: &mut SplineToolData, preferences: &PreferencesMessageHandler, responses: &mut VecDeque<Message>) -> bool {
let Some(&(endpoint, _)) = tool_data.points.last() else { return false };
let preview_point = tool_data.preview_point;
@@ -410,9 +411,14 @@ fn join_path(document: &DocumentMessageHandler, mouse_pos: DVec2, tool_data: &mu
let selected_layers = selected_nodes.selected_layers(document.metadata());
// Get the closest point to mouse position which is not preview_point or end_point.
let closest_point = closest_point(document, mouse_pos, PATH_JOIN_THRESHOLD, selected_layers, |cp| {
preview_point.is_some_and(|pp| pp == cp) || cp == endpoint
});
let closest_point = closest_point(
document,
mouse_pos,
PATH_JOIN_THRESHOLD,
selected_layers,
|cp| preview_point.is_some_and(|pp| pp == cp) || cp == endpoint,
preferences,
);
let Some((layer, join_point, _)) = closest_point else { return false };
// Last end point inserted was the preview point and segment therefore we delete it before joining the end_point & join_point.
+1 -21
View File
@@ -26,27 +26,7 @@ pub struct ToolActionHandlerData<'a> {
pub font_cache: &'a FontCache,
pub shape_editor: &'a mut ShapeState,
pub node_graph: &'a NodeGraphExecutor,
}
impl<'a> ToolActionHandlerData<'a> {
pub fn new(
document: &'a mut DocumentMessageHandler,
document_id: DocumentId,
global_tool_data: &'a DocumentToolData,
input: &'a InputPreprocessorMessageHandler,
font_cache: &'a FontCache,
shape_editor: &'a mut ShapeState,
node_graph: &'a NodeGraphExecutor,
) -> Self {
Self {
document,
document_id,
global_tool_data,
input,
font_cache,
shape_editor,
node_graph,
}
}
pub preferences: &'a PreferencesMessageHandler,
}
pub trait ToolCommon: for<'a, 'b> MessageHandler<ToolMessage, &'b mut ToolActionHandlerData<'a>> + LayoutHolder + ToolTransition + ToolMetadata {}