Freehand tool: Allow extension of existing paths (#1594)
* feat: extend freehand path * fix: rm logs * fix: rename variable
This commit is contained in:
parent
05b4582cd7
commit
a4a2680ac4
|
|
@ -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 snapping;
|
||||
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 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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue