mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-10-02 18:48:11 +08:00
Freehand tool: Allow extension of existing paths (#1594)
* feat: extend freehand path * fix: rm logs * fix: rename variable
This commit is contained in:
@@ -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)));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,3 +5,4 @@ pub mod resize;
|
|||||||
pub mod shape_editor;
|
pub mod shape_editor;
|
||||||
pub mod snapping;
|
pub mod snapping;
|
||||||
pub mod transformation_cage;
|
pub mod transformation_cage;
|
||||||
|
pub mod utility_funcitons;
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -1,8 +1,11 @@
|
|||||||
use super::tool_prelude::*;
|
use super::tool_prelude::*;
|
||||||
use crate::messages::portfolio::document::node_graph::VectorDataModification;
|
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::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||||
use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType};
|
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::graph_modification_utils;
|
||||||
|
use crate::messages::tool::common_functionality::utility_funcitons::should_extend;
|
||||||
|
|
||||||
use graph_craft::document::NodeId;
|
use graph_craft::document::NodeId;
|
||||||
use graphene_core::uuid::generate_uuid;
|
use graphene_core::uuid::generate_uuid;
|
||||||
@@ -42,6 +45,8 @@ impl Default for FreehandOptions {
|
|||||||
pub enum FreehandToolMessage {
|
pub enum FreehandToolMessage {
|
||||||
// Standard messages
|
// Standard messages
|
||||||
#[remain::unsorted]
|
#[remain::unsorted]
|
||||||
|
Overlays(OverlayContext),
|
||||||
|
#[remain::unsorted]
|
||||||
Abort,
|
Abort,
|
||||||
#[remain::unsorted]
|
#[remain::unsorted]
|
||||||
WorkingColorChanged,
|
WorkingColorChanged,
|
||||||
@@ -169,6 +174,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for Freehan
|
|||||||
impl ToolTransition for FreehandTool {
|
impl ToolTransition for FreehandTool {
|
||||||
fn event_to_message_map(&self) -> EventToMessageMap {
|
fn event_to_message_map(&self) -> EventToMessageMap {
|
||||||
EventToMessageMap {
|
EventToMessageMap {
|
||||||
|
overlay_provider: Some(|overlay_context: OverlayContext| FreehandToolMessage::Overlays(overlay_context).into()),
|
||||||
tool_abort: Some(FreehandToolMessage::Abort.into()),
|
tool_abort: Some(FreehandToolMessage::Abort.into()),
|
||||||
working_color_changed: Some(FreehandToolMessage::WorkingColorChanged.into()),
|
working_color_changed: Some(FreehandToolMessage::WorkingColorChanged.into()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
@@ -178,6 +184,7 @@ impl ToolTransition for FreehandTool {
|
|||||||
|
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
struct FreehandToolData {
|
struct FreehandToolData {
|
||||||
|
extend_from_start: bool,
|
||||||
last_point: DVec2,
|
last_point: DVec2,
|
||||||
dragged: bool,
|
dragged: bool,
|
||||||
weight: f64,
|
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 {
|
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 {
|
let ToolActionHandlerData {
|
||||||
document, global_tool_data, input, ..
|
document,
|
||||||
|
global_tool_data,
|
||||||
|
input,
|
||||||
|
shape_editor,
|
||||||
|
..
|
||||||
} = tool_action_data;
|
} = tool_action_data;
|
||||||
|
|
||||||
let ToolMessage::Freehand(event) = event else {
|
let ToolMessage::Freehand(event) = event else {
|
||||||
return self;
|
return self;
|
||||||
};
|
};
|
||||||
match (self, event) {
|
match (self, event) {
|
||||||
|
(_, FreehandToolMessage::Overlays(mut overlay_context)) => {
|
||||||
|
path_endpoint_overlays(document, shape_editor, &mut overlay_context);
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
(FreehandToolFsmState::Ready, FreehandToolMessage::DragStart) => {
|
(FreehandToolFsmState::Ready, FreehandToolMessage::DragStart) => {
|
||||||
responses.add(DocumentMessage::StartTransaction);
|
responses.add(DocumentMessage::StartTransaction);
|
||||||
responses.add(DocumentMessage::DeselectAllLayers);
|
|
||||||
|
|
||||||
let parent = document.new_layer_parent();
|
let parent = document.new_layer_parent();
|
||||||
let transform = document.metadata().transform_to_viewport(parent);
|
let transform = document.metadata().transform_to_viewport(parent);
|
||||||
let pos = transform.inverse().transform_point2(input.mouse.position);
|
let pos = transform.inverse().transform_point2(input.mouse.position);
|
||||||
|
|
||||||
tool_data.dragged = false;
|
tool_data.dragged = false;
|
||||||
|
tool_data.extend_from_start = false;
|
||||||
tool_data.last_point = pos;
|
tool_data.last_point = pos;
|
||||||
|
|
||||||
tool_data.weight = tool_options.line_weight;
|
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);
|
let layer = graph_modification_utils::new_vector_layer(vec![subpath], NodeId(generate_uuid()), parent, responses);
|
||||||
tool_data.layer = Some(layer);
|
tool_data.layer = Some(layer);
|
||||||
|
|
||||||
responses.add(GraphOperationMessage::FillSet {
|
responses.add(GraphOperationMessage::FillSet {
|
||||||
layer,
|
layer,
|
||||||
fill: if let Some(color) = tool_options.fill.active_color() { Fill::Solid(color) } else { Fill::None },
|
fill: if let Some(color) = tool_options.fill.active_color() { Fill::Solid(color) } else { Fill::None },
|
||||||
});
|
});
|
||||||
|
|
||||||
responses.add(GraphOperationMessage::StrokeSet {
|
responses.add(GraphOperationMessage::StrokeSet {
|
||||||
layer,
|
layer,
|
||||||
stroke: Stroke::new(tool_options.stroke.active_color(), tool_data.weight),
|
stroke: Stroke::new(tool_options.stroke.active_color(), tool_data.weight),
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
FreehandToolFsmState::Drawing
|
FreehandToolFsmState::Drawing
|
||||||
}
|
}
|
||||||
@@ -234,7 +265,11 @@ impl Fsm for FreehandToolFsmState {
|
|||||||
|
|
||||||
if tool_data.last_point != pos {
|
if tool_data.last_point != pos {
|
||||||
let manipulator_group = ManipulatorGroup::new_anchor(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 });
|
responses.add(GraphOperationMessage::Vector { layer, modification });
|
||||||
tool_data.dragged = true;
|
tool_data.dragged = true;
|
||||||
tool_data.last_point = pos;
|
tool_data.last_point = pos;
|
||||||
|
|||||||
@@ -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;
|
||||||
use crate::messages::tool::common_functionality::graph_modification_utils::get_subpaths;
|
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::snapping::{SnapCandidatePoint, SnapConstraint, SnapData, SnapManager};
|
||||||
|
use crate::messages::tool::common_functionality::utility_funcitons::should_extend;
|
||||||
|
|
||||||
use graph_craft::document::NodeId;
|
use graph_craft::document::NodeId;
|
||||||
use graphene_core::uuid::{generate_uuid, ManipulatorGroupId};
|
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()
|
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
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user