mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-07-07 15:55:00 +00:00
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:
parent
26d66298cf
commit
93880abc4c
16 changed files with 102 additions and 78 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -2397,6 +2397,7 @@ dependencies = [
|
|||
"serde_json",
|
||||
"specta",
|
||||
"spirv-std",
|
||||
"tinyvec",
|
||||
"tokio",
|
||||
"usvg",
|
||||
"vello",
|
||||
|
|
|
@ -228,6 +228,7 @@ impl Dispatcher {
|
|||
input: &self.message_handlers.input_preprocessor_message_handler,
|
||||
persistent_data: &self.message_handlers.portfolio_message_handler.persistent_data,
|
||||
node_graph: &self.message_handlers.portfolio_message_handler.executor,
|
||||
preferences: &self.message_handlers.preferences_message_handler,
|
||||
};
|
||||
|
||||
self.message_handlers.tool_message_handler.process_message(message, &mut queue, data);
|
||||
|
|
|
@ -47,7 +47,7 @@ impl PreferencesDialogMessageHandler {
|
|||
TextLabel::new("Zoom with Scroll").table_align(true).tooltip(zoom_with_scroll_tooltip).widget_holder(),
|
||||
];
|
||||
let vello_tooltip = "Use the experimental Vello renderer (your browser must support WebGPU)";
|
||||
let renderer_section = vec![TextLabel::new("Renderer").italic(true).widget_holder()];
|
||||
let renderer_section = vec![TextLabel::new("Experimental").italic(true).widget_holder()];
|
||||
let use_vello = vec![
|
||||
CheckboxInput::new(preferences.use_vello && preferences.supports_wgpu())
|
||||
.tooltip(vello_tooltip)
|
||||
|
@ -55,7 +55,7 @@ impl PreferencesDialogMessageHandler {
|
|||
.on_update(|checkbox_input: &CheckboxInput| PreferencesMessage::UseVello { use_vello: checkbox_input.checked }.into())
|
||||
.widget_holder(),
|
||||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||
TextLabel::new("Vello (Experimental)")
|
||||
TextLabel::new("Vello Renderer")
|
||||
.table_align(true)
|
||||
.tooltip(vello_tooltip)
|
||||
.disabled(!preferences.supports_wgpu())
|
||||
|
@ -85,11 +85,22 @@ impl PreferencesDialogMessageHandler {
|
|||
// .widget_holder(),
|
||||
// ];
|
||||
|
||||
let vector_mesh_tooltip = "Allow tools to produce vector meshes, where more than two segments can connect to an anchor point.\n\nCurrently this does not properly handle line joins and fills.";
|
||||
let vector_meshes = vec![
|
||||
CheckboxInput::new(preferences.vector_meshes)
|
||||
.tooltip(vector_mesh_tooltip)
|
||||
.on_update(|checkbox_input: &CheckboxInput| PreferencesMessage::VectorMeshes { enabled: checkbox_input.checked }.into())
|
||||
.widget_holder(),
|
||||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||
TextLabel::new("Vector Meshes").table_align(true).tooltip(vector_mesh_tooltip).widget_holder(),
|
||||
];
|
||||
|
||||
Layout::WidgetLayout(WidgetLayout::new(vec![
|
||||
LayoutGroup::Row { widgets: input_section },
|
||||
LayoutGroup::Row { widgets: zoom_with_scroll },
|
||||
LayoutGroup::Row { widgets: renderer_section },
|
||||
LayoutGroup::Row { widgets: use_vello },
|
||||
LayoutGroup::Row { widgets: vector_meshes },
|
||||
// LayoutGroup::Row { widgets: imaginate_server_hostname },
|
||||
// LayoutGroup::Row { widgets: imaginate_refresh_frequency },
|
||||
]))
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use super::utility_types::OverlayContext;
|
||||
use crate::consts::HIDE_HANDLE_DISTANCE;
|
||||
use crate::messages::tool::common_functionality::shape_editor::{SelectedLayerState, ShapeState};
|
||||
use crate::messages::tool::tool_messages::tool_prelude::DocumentMessageHandler;
|
||||
use crate::messages::tool::tool_messages::tool_prelude::{DocumentMessageHandler, PreferencesMessageHandler};
|
||||
|
||||
use graphene_core::vector::ManipulatorPointId;
|
||||
|
||||
|
@ -62,7 +62,7 @@ pub fn path_overlays(document: &DocumentMessageHandler, shape_editor: &mut Shape
|
|||
}
|
||||
}
|
||||
|
||||
pub fn path_endpoint_overlays(document: &DocumentMessageHandler, shape_editor: &mut ShapeState, overlay_context: &mut OverlayContext) {
|
||||
pub fn path_endpoint_overlays(document: &DocumentMessageHandler, shape_editor: &mut ShapeState, overlay_context: &mut OverlayContext, preferences: &PreferencesMessageHandler) {
|
||||
for layer in document.network_interface.selected_nodes(&[]).unwrap().selected_layers(document.metadata()) {
|
||||
let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else {
|
||||
continue;
|
||||
|
@ -72,7 +72,7 @@ pub fn path_endpoint_overlays(document: &DocumentMessageHandler, shape_editor: &
|
|||
let selected = shape_editor.selected_shape_state.get(&layer);
|
||||
let is_selected = |selected: Option<&SelectedLayerState>, point: ManipulatorPointId| selected.is_some_and(|selected| selected.is_selected(point));
|
||||
|
||||
for point in vector_data.single_connected_points() {
|
||||
for point in vector_data.extendable_points(preferences.vector_meshes) {
|
||||
let Some(position) = vector_data.point_domain.position_from_id(point) else { continue };
|
||||
let position = transform.transform_point2(position);
|
||||
overlay_context.manipulator_anchor(position, is_selected(selected, ManipulatorPointId::Anchor(point)), None);
|
||||
|
|
|
@ -9,5 +9,6 @@ pub enum PreferencesMessage {
|
|||
ImaginateRefreshFrequency { seconds: f64 },
|
||||
UseVello { use_vello: bool },
|
||||
ImaginateServerHostname { hostname: String },
|
||||
VectorMeshes { enabled: bool },
|
||||
ModifyLayout { zoom_with_scroll: bool },
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ pub struct PreferencesMessageHandler {
|
|||
pub imaginate_refresh_frequency: f64,
|
||||
pub zoom_with_scroll: bool,
|
||||
pub use_vello: bool,
|
||||
pub vector_meshes: bool,
|
||||
}
|
||||
|
||||
impl PreferencesMessageHandler {
|
||||
|
@ -34,6 +35,7 @@ impl Default for PreferencesMessageHandler {
|
|||
imaginate_refresh_frequency: 1.,
|
||||
zoom_with_scroll: matches!(MappingVariant::default(), MappingVariant::ZoomWithScroll),
|
||||
use_vello,
|
||||
vector_meshes: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -88,6 +90,9 @@ impl MessageHandler<PreferencesMessage, ()> for PreferencesMessageHandler {
|
|||
responses.add(PortfolioMessage::ImaginateCheckServerStatus);
|
||||
responses.add(PortfolioMessage::EditorPreferences);
|
||||
}
|
||||
PreferencesMessage::VectorMeshes { enabled } => {
|
||||
self.vector_meshes = enabled;
|
||||
}
|
||||
PreferencesMessage::ModifyLayout { zoom_with_scroll } => {
|
||||
self.zoom_with_scroll = zoom_with_scroll;
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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 {}
|
||||
|
|
|
@ -428,7 +428,7 @@ impl<PointId: crate::Identifier> Subpath<PointId> {
|
|||
_ => 4.,
|
||||
};
|
||||
// TODO: Besides returning None using the `?` operator, is there a more appropriate way to handle a `None` result from `get_segment`?
|
||||
let in_segment = self.get_segment(self.len_segments() - 1)?;
|
||||
let in_segment = self.get_segment(self.len_segments().checked_sub(1)?)?;
|
||||
let out_segment = other.get_segment(0)?;
|
||||
|
||||
let in_tangent = in_segment.tangent(TValue::Parametric(1.));
|
||||
|
@ -483,13 +483,13 @@ impl<PointId: crate::Identifier> Subpath<PointId> {
|
|||
let center_to_right = right - center;
|
||||
let center_to_left = left - center;
|
||||
|
||||
let in_segment = self.get_segment(self.len_segments() - 1).unwrap();
|
||||
let in_tangent = in_segment.tangent(TValue::Parametric(1.));
|
||||
let in_segment = self.len_segments().checked_sub(1).and_then(|segment| self.get_segment(segment));
|
||||
let in_tangent = in_segment.map(|in_segment| in_segment.tangent(TValue::Parametric(1.)));
|
||||
|
||||
let mut angle = center_to_right.angle_to(center_to_left) / 2.;
|
||||
let mut arc_point = center + DMat2::from_angle(angle).mul_vec2(center_to_right);
|
||||
|
||||
if (arc_point - left).angle_to(in_tangent).abs() > PI / 2. {
|
||||
if in_tangent.map(|in_tangent| (arc_point - left).angle_to(in_tangent).abs()).unwrap_or_default() > PI / 2. {
|
||||
angle = angle - PI * (if angle < 0. { -1. } else { 1. });
|
||||
arc_point = center + DMat2::from_angle(angle).mul_vec2(center_to_right);
|
||||
}
|
||||
|
|
|
@ -55,6 +55,7 @@ glam = { workspace = true, default-features = false, features = [
|
|||
|
||||
# Required dependencies
|
||||
half = { version = "2.4.1", default-features = false, features = ["bytemuck"] }
|
||||
tinyvec = { version = "1" }
|
||||
|
||||
# Optional workspace dependencies
|
||||
dyn-any = { workspace = true, optional = true }
|
||||
|
|
|
@ -243,14 +243,12 @@ impl VectorData {
|
|||
self.point_domain.resolve_id(point).map_or(0, |point| self.segment_domain.connected_count(point))
|
||||
}
|
||||
|
||||
/// Points connected to a single segment
|
||||
pub fn single_connected_points(&self) -> impl Iterator<Item = PointId> + '_ {
|
||||
self.point_domain
|
||||
.ids()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(index, _)| self.segment_domain.connected_count(*index) == 1)
|
||||
.map(|(_, &id)| id)
|
||||
/// Points that can be extended from.
|
||||
///
|
||||
/// This is usually only points with exactly one connection unless vector meshes are enabled.
|
||||
pub fn extendable_points(&self, vector_meshes: bool) -> impl Iterator<Item = PointId> + '_ {
|
||||
let point_ids = self.point_domain.ids().iter().enumerate();
|
||||
point_ids.filter(move |(index, _)| vector_meshes || 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.
|
||||
|
|
|
@ -654,7 +654,7 @@ impl super::VectorData {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
|
||||
struct StrokePathIterPointSegmentMetadata {
|
||||
segment_index: usize,
|
||||
start_from_end: bool,
|
||||
|
@ -675,28 +675,24 @@ impl StrokePathIterPointSegmentMetadata {
|
|||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
struct StrokePathIterPointMetadata([Option<StrokePathIterPointSegmentMetadata>; 2]);
|
||||
struct StrokePathIterPointMetadata(tinyvec::TinyVec<[StrokePathIterPointSegmentMetadata; 2]>);
|
||||
|
||||
impl StrokePathIterPointMetadata {
|
||||
fn set(&mut self, value: StrokePathIterPointSegmentMetadata) {
|
||||
if self.0[0].is_none() {
|
||||
self.0[0] = Some(value)
|
||||
} else if self.0[1].is_none() {
|
||||
self.0[1] = Some(value);
|
||||
} else {
|
||||
panic!("Mesh networks are not supported");
|
||||
}
|
||||
self.0.insert(0, value);
|
||||
}
|
||||
#[must_use]
|
||||
fn connected(&self) -> usize {
|
||||
self.0.iter().filter(|val| val.is_some()).count()
|
||||
self.0.len()
|
||||
}
|
||||
#[must_use]
|
||||
fn take_first(&mut self) -> Option<StrokePathIterPointSegmentMetadata> {
|
||||
self.0[0].take().or_else(|| self.0[1].take())
|
||||
self.0.pop()
|
||||
}
|
||||
fn take_eq(&mut self, target: StrokePathIterPointSegmentMetadata) -> bool {
|
||||
self.0[0].take_if(|&mut value| value == target).or_else(|| self.0[1].take_if(|&mut value| value == target)).is_some()
|
||||
let has_taken = self.0.contains(&target);
|
||||
self.0.retain(|value| *value != target);
|
||||
has_taken
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue