Freehand tool: Allow extension of existing paths (#1594)

* feat: extend freehand path

* fix: rm logs

* fix: rename variable
This commit is contained in:
zhiyuan 2024-02-05 05:36:25 +08:00 committed by GitHub
parent 05b4582cd7
commit a4a2680ac4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 110 additions and 44 deletions

View File

@ -52,3 +52,28 @@ 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) {
for layer in document.selected_nodes.selected_layers(document.metadata()) {
let Some(subpaths) = get_subpaths(layer, &document.network) else { continue };
let transform = document.metadata().transform_to_viewport(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_selected(point));
let mut manipulator_groups = get_manipulator_groups(subpaths);
if let Some(first_manipulator) = manipulator_groups.next() {
let anchor = first_manipulator.anchor;
let anchor_position = transform.transform_point2(anchor);
overlay_context.square(anchor_position, is_selected(selected, ManipulatorPointId::new(first_manipulator.id, SelectedType::Anchor)));
};
if let Some(last_manipulator) = manipulator_groups.last() {
let anchor = last_manipulator.anchor;
let anchor_position = transform.transform_point2(anchor);
overlay_context.square(anchor_position, is_selected(selected, ManipulatorPointId::new(last_manipulator.id, SelectedType::Anchor)));
};
}
}

View File

@ -5,3 +5,4 @@ pub mod resize;
pub mod shape_editor;
pub mod snapping;
pub mod transformation_cage;
pub mod utility_funcitons;

View File

@ -0,0 +1,34 @@
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::graph_modification_utils::get_subpaths;
use glam::DVec2;
/// Determines if a path should be extended. Returns the path and if it is extending from the start, if applicable.
pub fn should_extend(document: &DocumentMessageHandler, pos: DVec2, tolerance: f64) -> Option<(LayerNodeIdentifier, usize, bool)> {
let mut best = None;
let mut best_distance_squared = tolerance * tolerance;
for layer in document.selected_nodes.selected_layers(document.metadata()) {
let viewspace = document.metadata().transform_to_viewport(layer);
let subpaths = get_subpaths(layer, &document.network)?;
for (subpath_index, subpath) in subpaths.iter().enumerate() {
if subpath.closed() {
continue;
}
for (manipulator_group, from_start) in [(subpath.manipulator_groups().first(), true), (subpath.manipulator_groups().last(), false)] {
let Some(manipulator_group) = manipulator_group else { break };
let distance_squared = viewspace.transform_point2(manipulator_group.anchor).distance_squared(pos);
if distance_squared < best_distance_squared {
best = Some((layer, subpath_index, from_start));
best_distance_squared = distance_squared;
}
}
}
}
best
}

View File

@ -1,8 +1,11 @@
use super::tool_prelude::*;
use crate::messages::portfolio::document::node_graph::VectorDataModification;
use crate::messages::portfolio::document::overlays::utility_functions::path_endpoint_overlays;
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType};
use crate::messages::tool::common_functionality::graph_modification_utils;
use crate::messages::tool::common_functionality::utility_funcitons::should_extend;
use graph_craft::document::NodeId;
use graphene_core::uuid::generate_uuid;
@ -42,6 +45,8 @@ impl Default for FreehandOptions {
pub enum FreehandToolMessage {
// Standard messages
#[remain::unsorted]
Overlays(OverlayContext),
#[remain::unsorted]
Abort,
#[remain::unsorted]
WorkingColorChanged,
@ -169,6 +174,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for Freehan
impl ToolTransition for FreehandTool {
fn event_to_message_map(&self) -> EventToMessageMap {
EventToMessageMap {
overlay_provider: Some(|overlay_context: OverlayContext| FreehandToolMessage::Overlays(overlay_context).into()),
tool_abort: Some(FreehandToolMessage::Abort.into()),
working_color_changed: Some(FreehandToolMessage::WorkingColorChanged.into()),
..Default::default()
@ -178,6 +184,7 @@ impl ToolTransition for FreehandTool {
#[derive(Clone, Debug, Default)]
struct FreehandToolData {
extend_from_start: bool,
last_point: DVec2,
dragged: bool,
weight: f64,
@ -190,40 +197,64 @@ impl Fsm for FreehandToolFsmState {
fn transition(self, event: ToolMessage, tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionHandlerData, tool_options: &Self::ToolOptions, responses: &mut VecDeque<Message>) -> Self {
let ToolActionHandlerData {
document, global_tool_data, input, ..
document,
global_tool_data,
input,
shape_editor,
..
} = 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);
self
}
(FreehandToolFsmState::Ready, FreehandToolMessage::DragStart) => {
responses.add(DocumentMessage::StartTransaction);
responses.add(DocumentMessage::DeselectAllLayers);
let parent = document.new_layer_parent();
let transform = document.metadata().transform_to_viewport(parent);
let pos = transform.inverse().transform_point2(input.mouse.position);
tool_data.dragged = false;
tool_data.extend_from_start = false;
tool_data.last_point = pos;
tool_data.weight = tool_options.line_weight;
let subpath = bezier_rs::Subpath::from_anchors([pos], false);
if let Some((layer, subpath_index, from_start)) = should_extend(document, input.mouse.position, crate::consts::SNAP_POINT_TOLERANCE) {
let manipulator_group = ManipulatorGroup::new_anchor(pos);
let modification = if from_start {
tool_data.extend_from_start = true;
VectorDataModification::AddStartManipulatorGroup { subpath_index, manipulator_group }
} else {
VectorDataModification::AddEndManipulatorGroup { subpath_index, manipulator_group }
};
responses.add(GraphOperationMessage::Vector { layer, modification });
tool_data.dragged = true;
tool_data.last_point = pos;
tool_data.layer = Some(layer);
} else {
responses.add(DocumentMessage::DeselectAllLayers);
let subpath = bezier_rs::Subpath::from_anchors([pos], false);
let layer = graph_modification_utils::new_vector_layer(vec![subpath], NodeId(generate_uuid()), parent, responses);
tool_data.layer = Some(layer);
let layer = graph_modification_utils::new_vector_layer(vec![subpath], NodeId(generate_uuid()), parent, responses);
tool_data.layer = Some(layer);
responses.add(GraphOperationMessage::FillSet {
layer,
fill: if let Some(color) = tool_options.fill.active_color() { Fill::Solid(color) } else { Fill::None },
});
responses.add(GraphOperationMessage::FillSet {
layer,
fill: if let Some(color) = tool_options.fill.active_color() { Fill::Solid(color) } else { Fill::None },
});
responses.add(GraphOperationMessage::StrokeSet {
layer,
stroke: Stroke::new(tool_options.stroke.active_color(), tool_data.weight),
});
responses.add(GraphOperationMessage::StrokeSet {
layer,
stroke: Stroke::new(tool_options.stroke.active_color(), tool_data.weight),
});
}
FreehandToolFsmState::Drawing
}
@ -234,7 +265,11 @@ impl Fsm for FreehandToolFsmState {
if tool_data.last_point != pos {
let manipulator_group = ManipulatorGroup::new_anchor(pos);
let modification = VectorDataModification::AddEndManipulatorGroup { subpath_index: 0, manipulator_group };
let modification = if tool_data.extend_from_start {
VectorDataModification::AddStartManipulatorGroup { subpath_index: 0, manipulator_group }
} else {
VectorDataModification::AddEndManipulatorGroup { subpath_index: 0, manipulator_group }
};
responses.add(GraphOperationMessage::Vector { layer, modification });
tool_data.dragged = true;
tool_data.last_point = pos;

View File

@ -8,6 +8,7 @@ use crate::messages::tool::common_functionality::color_selector::{ToolColorOptio
use crate::messages::tool::common_functionality::graph_modification_utils;
use crate::messages::tool::common_functionality::graph_modification_utils::get_subpaths;
use crate::messages::tool::common_functionality::snapping::{SnapCandidatePoint, SnapConstraint, SnapData, SnapManager};
use crate::messages::tool::common_functionality::utility_funcitons::should_extend;
use graph_craft::document::NodeId;
use graphene_core::uuid::{generate_uuid, ManipulatorGroupId};
@ -767,33 +768,3 @@ fn add_manipulator_group(layer: Option<LayerNodeIdentifier>, from_start: bool, m
};
GraphOperationMessage::Vector { layer, modification }.into()
}
/// Determines if a path should be extended. Returns the path and if it is extending from the start, if applicable.
fn should_extend(document: &DocumentMessageHandler, pos: DVec2, tolerance: f64) -> Option<(LayerNodeIdentifier, usize, bool)> {
let mut best = None;
let mut best_distance_squared = tolerance * tolerance;
for layer in document.selected_nodes.selected_layers(document.metadata()) {
let viewspace = document.metadata().transform_to_viewport(layer);
let subpaths = get_subpaths(layer, &document.network)?;
for (subpath_index, subpath) in subpaths.iter().enumerate() {
if subpath.closed() {
continue;
}
for (manipulator_group, from_start) in [(subpath.manipulator_groups().first(), true), (subpath.manipulator_groups().last(), false)] {
let Some(manipulator_group) = manipulator_group else { break };
let distance_squared = viewspace.transform_point2(manipulator_group.anchor).distance_squared(pos);
if distance_squared < best_distance_squared {
best = Some((layer, subpath_index, from_start));
best_distance_squared = distance_squared;
}
}
}
}
best
}