mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-29 00:08:11 +08:00
Migrate vector data and tools to use nodes (#1065)
* Add rendering to vector nodes * Add line, shape, rectange and freehand tool * Fix transforms, strokes and fills * Migrate spline tool * Remove blank lines * Fix test * Fix fill in properties * Select layers when filling * Properties panel transform around pivot * Fix select tool outlines * Select tool modifies node graph pivot * Add the pivot assist to the properties * Improve setting non existant fill UX * Cleanup hash function * Path and pen tools * Bug fixes * Disable boolean ops * Fix default handle smoothing on ellipses * Fix test and warnings --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
co-authored by
Keavon Chambers
parent
5759600f95
commit
ac49519fa9
@@ -58,8 +58,8 @@ impl ToolMetadata for ArtboardTool {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for ArtboardTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: ToolActionHandlerData<'a>) {
|
||||
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for ArtboardTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
|
||||
self.fsm_state.process_event(message, &mut self.data, tool_data, &(), responses, false);
|
||||
}
|
||||
|
||||
@@ -112,7 +112,7 @@ impl Fsm for ArtboardToolFsmState {
|
||||
self,
|
||||
event: ToolMessage,
|
||||
tool_data: &mut Self::ToolData,
|
||||
(document, _document_id, _global_tool_data, input, render_data): ToolActionHandlerData,
|
||||
ToolActionHandlerData { document, input, render_data, .. }: &mut ToolActionHandlerData,
|
||||
_tool_options: &Self::ToolOptions,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) -> Self {
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
use crate::messages::frontend::utility_types::MouseCursorIcon;
|
||||
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion};
|
||||
use crate::messages::layout::utility_types::layout_widget::PropertyHolder;
|
||||
use crate::messages::portfolio::document::node_graph::VectorDataModification;
|
||||
use crate::messages::prelude::*;
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||
use crate::messages::tool::common_functionality::resize::Resize;
|
||||
use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType};
|
||||
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
|
||||
|
||||
use document_legacy::layers::style;
|
||||
use document_legacy::Operation;
|
||||
use graphene_core::vector::style::Fill;
|
||||
|
||||
use glam::DAffine2;
|
||||
use glam::DVec2;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Default)]
|
||||
@@ -49,8 +50,8 @@ impl ToolMetadata for EllipseTool {
|
||||
|
||||
impl PropertyHolder for EllipseTool {}
|
||||
|
||||
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for EllipseTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: ToolActionHandlerData<'a>) {
|
||||
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for EllipseTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
|
||||
self.fsm_state.process_event(message, &mut self.data, tool_data, &(), responses, true);
|
||||
}
|
||||
|
||||
@@ -100,7 +101,13 @@ impl Fsm for EllipseToolFsmState {
|
||||
self,
|
||||
event: ToolMessage,
|
||||
tool_data: &mut Self::ToolData,
|
||||
(document, _document_id, global_tool_data, input, render_data): ToolActionHandlerData,
|
||||
ToolActionHandlerData {
|
||||
document,
|
||||
global_tool_data,
|
||||
input,
|
||||
render_data,
|
||||
..
|
||||
}: &mut ToolActionHandlerData,
|
||||
_tool_options: &Self::ToolOptions,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) -> Self {
|
||||
@@ -114,18 +121,32 @@ impl Fsm for EllipseToolFsmState {
|
||||
(Ready, DragStart) => {
|
||||
shape_data.start(responses, document, input, render_data);
|
||||
responses.push_back(DocumentMessage::StartTransaction.into());
|
||||
shape_data.path = Some(document.get_path_for_new_layer());
|
||||
responses.push_back(DocumentMessage::DeselectAllLayers.into());
|
||||
|
||||
responses.push_back(
|
||||
Operation::AddEllipse {
|
||||
path: shape_data.path.clone().unwrap(),
|
||||
insert_index: -1,
|
||||
transform: DAffine2::ZERO.to_cols_array(),
|
||||
style: style::PathStyle::new(None, style::Fill::solid(global_tool_data.primary_color)),
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
// Create a new layer path for this shape
|
||||
let layer_path = document.get_path_for_new_layer();
|
||||
shape_data.path = Some(layer_path.clone());
|
||||
|
||||
// Create a new ellipse vector shape
|
||||
let subpath = bezier_rs::Subpath::new_ellipse(DVec2::ZERO, DVec2::ONE);
|
||||
let manipulator_groups = subpath.manipulator_groups().to_vec();
|
||||
graph_modification_utils::new_vector_layer(vec![subpath], layer_path.clone(), responses);
|
||||
|
||||
// Set the four manipulator groups to have their handle angles mirrored by default
|
||||
for manipulator_group in manipulator_groups {
|
||||
responses.add(GraphOperationMessage::Vector {
|
||||
layer: layer_path.clone(),
|
||||
modification: VectorDataModification::SetManipulatorHandleMirroring {
|
||||
id: manipulator_group.id,
|
||||
mirror_angle: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Set the fill color to the primary working color
|
||||
responses.add(GraphOperationMessage::FillSet {
|
||||
layer: layer_path,
|
||||
fill: Fill::solid(global_tool_data.primary_color),
|
||||
});
|
||||
|
||||
Drawing
|
||||
}
|
||||
|
||||
@@ -43,8 +43,8 @@ impl ToolMetadata for EyedropperTool {
|
||||
|
||||
impl PropertyHolder for EyedropperTool {}
|
||||
|
||||
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for EyedropperTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: ToolActionHandlerData<'a>) {
|
||||
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for EyedropperTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
|
||||
self.fsm_state.process_event(message, &mut self.data, tool_data, &(), responses, true);
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ impl Fsm for EyedropperToolFsmState {
|
||||
self,
|
||||
event: ToolMessage,
|
||||
_tool_data: &mut Self::ToolData,
|
||||
(_document, _document_id, global_tool_data, input, _render_data): ToolActionHandlerData,
|
||||
ToolActionHandlerData { global_tool_data, input, .. }: &mut ToolActionHandlerData,
|
||||
_tool_options: &Self::ToolOptions,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) -> Self {
|
||||
|
||||
@@ -8,7 +8,6 @@ use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
|
||||
|
||||
use document_legacy::intersection::Quad;
|
||||
use document_legacy::layers::style::Fill;
|
||||
use document_legacy::Operation;
|
||||
|
||||
use glam::DVec2;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -46,8 +45,8 @@ impl ToolMetadata for FillTool {
|
||||
|
||||
impl PropertyHolder for FillTool {}
|
||||
|
||||
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for FillTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: ToolActionHandlerData<'a>) {
|
||||
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for FillTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
|
||||
self.fsm_state.process_event(message, &mut self.data, tool_data, &(), responses, true);
|
||||
}
|
||||
|
||||
@@ -84,7 +83,13 @@ impl Fsm for FillToolFsmState {
|
||||
self,
|
||||
event: ToolMessage,
|
||||
_tool_data: &mut Self::ToolData,
|
||||
(document, _document_id, global_tool_data, input, render_data): ToolActionHandlerData,
|
||||
ToolActionHandlerData {
|
||||
document,
|
||||
global_tool_data,
|
||||
input,
|
||||
render_data,
|
||||
..
|
||||
}: &mut ToolActionHandlerData,
|
||||
_tool_options: &Self::ToolOptions,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) -> Self {
|
||||
@@ -106,9 +111,12 @@ impl Fsm for FillToolFsmState {
|
||||
};
|
||||
let fill = Fill::Solid(color);
|
||||
|
||||
responses.push_back(DocumentMessage::StartTransaction.into());
|
||||
responses.push_back(Operation::SetLayerFill { path: path.to_vec(), fill }.into());
|
||||
responses.push_back(DocumentMessage::CommitTransaction.into());
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
responses.add(DocumentMessage::SetSelectedLayers {
|
||||
replacement_selected_layers: vec![path.to_vec()],
|
||||
});
|
||||
responses.add(GraphOperationMessage::FillSet { layer: path.to_vec(), fill });
|
||||
responses.add(DocumentMessage::CommitTransaction);
|
||||
}
|
||||
|
||||
Ready
|
||||
|
||||
@@ -3,14 +3,15 @@ use crate::messages::input_mapper::utility_types::input_keyboard::MouseMotion;
|
||||
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, WidgetLayout};
|
||||
use crate::messages::layout::utility_types::widgets::input_widgets::NumberInput;
|
||||
use crate::messages::prelude::*;
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||
use crate::messages::tool::utility_types::{DocumentToolData, EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType};
|
||||
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
|
||||
|
||||
use document_legacy::layers::style;
|
||||
use document_legacy::LayerId;
|
||||
use document_legacy::Operation;
|
||||
use graphene_core::vector::style::Stroke;
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
use glam::DVec2;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Default)]
|
||||
@@ -82,8 +83,8 @@ impl PropertyHolder for FreehandTool {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for FreehandTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: ToolActionHandlerData<'a>) {
|
||||
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for FreehandTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
|
||||
if let ToolMessage::Freehand(FreehandToolMessage::UpdateOptions(action)) = message {
|
||||
match action {
|
||||
FreehandToolMessageOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight,
|
||||
@@ -137,7 +138,9 @@ impl Fsm for FreehandToolFsmState {
|
||||
self,
|
||||
event: ToolMessage,
|
||||
tool_data: &mut Self::ToolData,
|
||||
(document, _document_id, global_tool_data, input, _render_data): ToolActionHandlerData,
|
||||
ToolActionHandlerData {
|
||||
document, global_tool_data, input, ..
|
||||
}: &mut ToolActionHandlerData,
|
||||
tool_options: &Self::ToolOptions,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) -> Self {
|
||||
@@ -159,7 +162,7 @@ impl Fsm for FreehandToolFsmState {
|
||||
|
||||
tool_data.weight = tool_options.line_weight;
|
||||
|
||||
responses.push_back(add_polyline(tool_data, global_tool_data));
|
||||
add_polyline(tool_data, global_tool_data, responses);
|
||||
|
||||
Drawing
|
||||
}
|
||||
@@ -170,15 +173,14 @@ impl Fsm for FreehandToolFsmState {
|
||||
tool_data.points.push(pos);
|
||||
}
|
||||
|
||||
responses.push_back(remove_preview(tool_data));
|
||||
responses.push_back(add_polyline(tool_data, global_tool_data));
|
||||
add_polyline(tool_data, global_tool_data, responses);
|
||||
|
||||
Drawing
|
||||
}
|
||||
(Drawing, DragStop) | (Drawing, Abort) => {
|
||||
if tool_data.points.len() >= 2 {
|
||||
responses.push_back(remove_preview(tool_data));
|
||||
responses.push_back(add_polyline(tool_data, global_tool_data));
|
||||
add_polyline(tool_data, global_tool_data, responses);
|
||||
responses.push_back(DocumentMessage::CommitTransaction.into());
|
||||
} else {
|
||||
responses.push_back(DocumentMessage::AbortTransaction.into());
|
||||
@@ -214,15 +216,13 @@ fn remove_preview(data: &FreehandToolData) -> Message {
|
||||
Operation::DeleteLayer { path: data.path.clone().unwrap() }.into()
|
||||
}
|
||||
|
||||
fn add_polyline(data: &FreehandToolData, tool_data: &DocumentToolData) -> Message {
|
||||
let points: Vec<(f64, f64)> = data.points.iter().map(|p| (p.x, p.y)).collect();
|
||||
fn add_polyline(data: &FreehandToolData, tool_data: &DocumentToolData, responses: &mut VecDeque<Message>) {
|
||||
let layer_path = data.path.clone().unwrap();
|
||||
let subpath = bezier_rs::Subpath::from_anchors(data.points.iter().copied(), false);
|
||||
graph_modification_utils::new_vector_layer(vec![subpath], layer_path.clone(), responses);
|
||||
|
||||
Operation::AddPolyline {
|
||||
path: data.path.clone().unwrap(),
|
||||
insert_index: -1,
|
||||
transform: DAffine2::IDENTITY.to_cols_array(),
|
||||
points,
|
||||
style: style::PathStyle::new(Some(style::Stroke::new(tool_data.primary_color, data.weight)), style::Fill::None),
|
||||
}
|
||||
.into()
|
||||
responses.add(GraphOperationMessage::StrokeSet {
|
||||
layer: layer_path,
|
||||
stroke: Stroke::new(tool_data.primary_color, data.weight),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -75,8 +75,8 @@ impl ToolMetadata for GradientTool {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for GradientTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: ToolActionHandlerData<'a>) {
|
||||
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for GradientTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
|
||||
if let ToolMessage::Gradient(GradientToolMessage::UpdateOptions(action)) = message {
|
||||
match action {
|
||||
GradientOptionsUpdate::Type(gradient_type) => {
|
||||
@@ -353,8 +353,8 @@ impl SelectedGradient {
|
||||
pub fn render_gradient(&mut self, responses: &mut VecDeque<Message>) {
|
||||
self.gradient.transform = self.transform;
|
||||
let fill = Fill::Gradient(self.gradient.clone());
|
||||
let path = self.path.clone();
|
||||
responses.push_back(Operation::SetLayerFill { path, fill }.into());
|
||||
let layer = self.path.clone();
|
||||
responses.add(GraphOperationMessage::FillSet { layer, fill });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -396,7 +396,13 @@ impl Fsm for GradientToolFsmState {
|
||||
self,
|
||||
event: ToolMessage,
|
||||
tool_data: &mut Self::ToolData,
|
||||
(document, _document_id, global_tool_data, input, render_data): ToolActionHandlerData,
|
||||
ToolActionHandlerData {
|
||||
document,
|
||||
global_tool_data,
|
||||
input,
|
||||
render_data,
|
||||
..
|
||||
}: &mut ToolActionHandlerData,
|
||||
tool_options: &Self::ToolOptions,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) -> Self {
|
||||
@@ -450,8 +456,8 @@ impl Fsm for GradientToolFsmState {
|
||||
// The gradient has only one point and so should become a fill
|
||||
if selected_gradient.gradient.positions.len() == 1 {
|
||||
let fill = Fill::Solid(selected_gradient.gradient.positions[0].1.unwrap_or(Color::BLACK));
|
||||
let path = selected_gradient.path.clone();
|
||||
responses.push_back(Operation::SetLayerFill { path, fill }.into());
|
||||
let layer = selected_gradient.path.clone();
|
||||
responses.add(GraphOperationMessage::FillSet { layer, fill });
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
@@ -37,8 +37,8 @@ pub enum ImaginateToolMessage {
|
||||
|
||||
impl PropertyHolder for ImaginateTool {}
|
||||
|
||||
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for ImaginateTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: ToolActionHandlerData<'a>) {
|
||||
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for ImaginateTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
|
||||
self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &(), responses, true);
|
||||
}
|
||||
|
||||
@@ -100,7 +100,7 @@ impl Fsm for ImaginateToolFsmState {
|
||||
self,
|
||||
event: ToolMessage,
|
||||
tool_data: &mut Self::ToolData,
|
||||
(document, _document_id, _global_tool_data, input, render_data): ToolActionHandlerData,
|
||||
ToolActionHandlerData { document, input, render_data, .. }: &mut ToolActionHandlerData,
|
||||
_tool_options: &Self::ToolOptions,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) -> Self {
|
||||
|
||||
@@ -5,15 +5,15 @@ use crate::messages::input_mapper::utility_types::input_mouse::ViewportPosition;
|
||||
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, WidgetLayout};
|
||||
use crate::messages::layout::utility_types::widgets::input_widgets::NumberInput;
|
||||
use crate::messages::prelude::*;
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||
use crate::messages::tool::common_functionality::snapping::SnapManager;
|
||||
use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType};
|
||||
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
|
||||
|
||||
use document_legacy::layers::style;
|
||||
use document_legacy::LayerId;
|
||||
use document_legacy::Operation;
|
||||
use graphene_core::vector::style::Stroke;
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
use glam::DVec2;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Default)]
|
||||
@@ -82,8 +82,8 @@ impl PropertyHolder for LineTool {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for LineTool {
|
||||
fn process_message(&mut self, message: ToolMessage, messages: &mut VecDeque<Message>, tool_data: ToolActionHandlerData<'a>) {
|
||||
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for LineTool {
|
||||
fn process_message(&mut self, message: ToolMessage, messages: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
|
||||
if let ToolMessage::Line(LineToolMessage::UpdateOptions(action)) = message {
|
||||
match action {
|
||||
LineOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight,
|
||||
@@ -137,7 +137,13 @@ impl Fsm for LineToolFsmState {
|
||||
self,
|
||||
event: ToolMessage,
|
||||
tool_data: &mut Self::ToolData,
|
||||
(document, _document_id, global_tool_data, input, render_data): ToolActionHandlerData,
|
||||
ToolActionHandlerData {
|
||||
document,
|
||||
global_tool_data,
|
||||
input,
|
||||
render_data,
|
||||
..
|
||||
}: &mut ToolActionHandlerData,
|
||||
tool_options: &Self::ToolOptions,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) -> Self {
|
||||
@@ -151,22 +157,19 @@ impl Fsm for LineToolFsmState {
|
||||
tool_data.snap_manager.add_all_document_handles(document, input, &[], &[], &[]);
|
||||
tool_data.drag_start = tool_data.snap_manager.snap_position(responses, document, input.mouse.position);
|
||||
|
||||
let subpath = bezier_rs::Subpath::new_line(DVec2::ZERO, DVec2::X);
|
||||
|
||||
responses.push_back(DocumentMessage::StartTransaction.into());
|
||||
tool_data.path = Some(document.get_path_for_new_layer());
|
||||
responses.push_back(DocumentMessage::DeselectAllLayers.into());
|
||||
let layer_path = document.get_path_for_new_layer();
|
||||
tool_data.path = Some(layer_path.clone());
|
||||
graph_modification_utils::new_vector_layer(vec![subpath], layer_path.clone(), responses);
|
||||
responses.add(GraphOperationMessage::StrokeSet {
|
||||
layer: layer_path.clone(),
|
||||
stroke: Stroke::new(global_tool_data.primary_color, tool_options.line_weight),
|
||||
});
|
||||
|
||||
tool_data.weight = tool_options.line_weight;
|
||||
|
||||
responses.push_back(
|
||||
Operation::AddLine {
|
||||
path: tool_data.path.clone().unwrap(),
|
||||
insert_index: -1,
|
||||
transform: DAffine2::ZERO.to_cols_array(),
|
||||
style: style::PathStyle::new(Some(style::Stroke::new(global_tool_data.primary_color, tool_data.weight)), style::Fill::None),
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
|
||||
Drawing
|
||||
}
|
||||
(Drawing, Redraw { center, snap_angle, lock_angle }) => {
|
||||
@@ -249,9 +252,10 @@ fn generate_transform(tool_data: &mut LineToolData, lock_angle: bool, snap_angle
|
||||
line_length *= 2.;
|
||||
}
|
||||
|
||||
Operation::SetLayerTransformInViewport {
|
||||
path: tool_data.path.clone().unwrap(),
|
||||
transform: glam::DAffine2::from_scale_angle_translation(DVec2::new(line_length, 1.), angle, start).to_cols_array(),
|
||||
GraphOperationMessage::TransformSet {
|
||||
layer: tool_data.path.clone().unwrap(),
|
||||
transform: glam::DAffine2::from_scale_angle_translation(DVec2::new(line_length, 1.), angle, start),
|
||||
transform_in: TransformIn::Viewport,
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
||||
@@ -50,8 +50,8 @@ impl ToolMetadata for NavigateTool {
|
||||
|
||||
impl PropertyHolder for NavigateTool {}
|
||||
|
||||
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for NavigateTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: ToolActionHandlerData<'a>) {
|
||||
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for NavigateTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
|
||||
self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &(), responses, true);
|
||||
}
|
||||
|
||||
@@ -105,7 +105,7 @@ impl Fsm for NavigateToolFsmState {
|
||||
self,
|
||||
message: ToolMessage,
|
||||
tool_data: &mut Self::ToolData,
|
||||
(_document, _document_id, _global_tool_data, input, _render_data): ToolActionHandlerData,
|
||||
ToolActionHandlerData { input, .. }: &mut ToolActionHandlerData,
|
||||
_tool_options: &Self::ToolOptions,
|
||||
messages: &mut VecDeque<Message>,
|
||||
) -> Self {
|
||||
|
||||
@@ -37,8 +37,8 @@ pub enum NodeGraphFrameToolMessage {
|
||||
|
||||
impl PropertyHolder for NodeGraphFrameTool {}
|
||||
|
||||
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for NodeGraphFrameTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: ToolActionHandlerData<'a>) {
|
||||
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for NodeGraphFrameTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
|
||||
self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &(), responses, true);
|
||||
}
|
||||
|
||||
@@ -100,7 +100,7 @@ impl Fsm for NodeGraphToolFsmState {
|
||||
self,
|
||||
event: ToolMessage,
|
||||
tool_data: &mut Self::ToolData,
|
||||
(document, _document_id, _global_tool_data, input, render_data): ToolActionHandlerData,
|
||||
ToolActionHandlerData { document, input, render_data, .. }: &mut ToolActionHandlerData,
|
||||
_tool_options: &Self::ToolOptions,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) -> Self {
|
||||
|
||||
@@ -4,13 +4,14 @@ use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMot
|
||||
use crate::messages::layout::utility_types::layout_widget::PropertyHolder;
|
||||
use crate::messages::prelude::*;
|
||||
use crate::messages::tool::common_functionality::overlay_renderer::OverlayRenderer;
|
||||
use crate::messages::tool::common_functionality::shape_editor::{ManipulatorPointInfo, ShapeEditor};
|
||||
use crate::messages::tool::common_functionality::shape_editor::{ManipulatorPointInfo, ShapeState};
|
||||
use crate::messages::tool::common_functionality::snapping::SnapManager;
|
||||
use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, HintData, HintGroup, HintInfo, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType};
|
||||
|
||||
use document_legacy::intersection::Quad;
|
||||
use document_legacy::LayerId;
|
||||
use graphene_std::vector::consts::ManipulatorType;
|
||||
use graphene_core::uuid::ManipulatorGroupId;
|
||||
use graphene_core::vector::{ManipulatorPointId, SelectedType};
|
||||
|
||||
use glam::DVec2;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -62,8 +63,8 @@ impl ToolMetadata for PathTool {
|
||||
|
||||
impl PropertyHolder for PathTool {}
|
||||
|
||||
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for PathTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: ToolActionHandlerData<'a>) {
|
||||
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for PathTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
|
||||
self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &(), responses, true);
|
||||
}
|
||||
|
||||
@@ -106,14 +107,28 @@ enum PathToolFsmState {
|
||||
|
||||
#[derive(Default)]
|
||||
struct PathToolData {
|
||||
shape_editor: ShapeEditor,
|
||||
overlay_renderer: OverlayRenderer,
|
||||
snap_manager: SnapManager,
|
||||
|
||||
drag_start_pos: DVec2,
|
||||
previous_mouse_position: DVec2,
|
||||
alt_debounce: bool,
|
||||
opposing_handle_lengths: Option<HashMap<Vec<LayerId>, HashMap<u64, f64>>>,
|
||||
opposing_handle_lengths: Option<HashMap<Vec<LayerId>, HashMap<ManipulatorGroupId, f64>>>,
|
||||
}
|
||||
|
||||
impl PathToolData {
|
||||
fn refresh_overlays(&mut self, document: &DocumentMessageHandler, shape_editor: &mut ShapeState, shape_overlay: &mut OverlayRenderer, responses: &mut VecDeque<Message>) {
|
||||
// Set the previously selected layers to invisible
|
||||
for layer_path in document.all_layers() {
|
||||
shape_overlay.layer_overlay_visibility(&document.document_legacy, layer_path.to_vec(), false, responses);
|
||||
}
|
||||
|
||||
// Render the new overlays
|
||||
for layer_path in shape_editor.selected_shape_state.keys() {
|
||||
shape_overlay.render_subpath_overlays(&shape_editor.selected_shape_state, &document.document_legacy, layer_path.to_vec(), responses);
|
||||
}
|
||||
|
||||
self.opposing_handle_lengths = None;
|
||||
}
|
||||
}
|
||||
|
||||
impl Fsm for PathToolFsmState {
|
||||
@@ -124,27 +139,25 @@ impl Fsm for PathToolFsmState {
|
||||
self,
|
||||
event: ToolMessage,
|
||||
tool_data: &mut Self::ToolData,
|
||||
(document, _document_id, _global_tool_data, input, render_data): ToolActionHandlerData,
|
||||
ToolActionHandlerData {
|
||||
document,
|
||||
input,
|
||||
render_data,
|
||||
shape_editor,
|
||||
shape_overlay,
|
||||
..
|
||||
}: &mut ToolActionHandlerData,
|
||||
_tool_options: &Self::ToolOptions,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) -> Self {
|
||||
if let ToolMessage::Path(event) = event {
|
||||
match (self, event) {
|
||||
(_, PathToolMessage::SelectionChanged) => {
|
||||
// Set the previously selected layers to invisible
|
||||
for layer_path in document.all_layers() {
|
||||
tool_data.overlay_renderer.layer_overlay_visibility(&document.document_legacy, layer_path.to_vec(), false, responses);
|
||||
}
|
||||
|
||||
// Set the newly targeted layers to visible
|
||||
let layer_paths = document.selected_visible_layers().map(|layer_path| layer_path.to_vec()).collect();
|
||||
tool_data.shape_editor.set_selected_layers(layer_paths);
|
||||
// Render the new overlays
|
||||
for layer_path in tool_data.shape_editor.selected_layers() {
|
||||
tool_data.overlay_renderer.render_subpath_overlays(&document.document_legacy, layer_path.to_vec(), responses);
|
||||
}
|
||||
shape_editor.set_selected_layers(layer_paths);
|
||||
|
||||
tool_data.opposing_handle_lengths = None;
|
||||
tool_data.refresh_overlays(document, shape_editor, shape_overlay, responses);
|
||||
// This can happen in any state (which is why we return self)
|
||||
self
|
||||
}
|
||||
@@ -152,7 +165,7 @@ impl Fsm for PathToolFsmState {
|
||||
// When the document has moved / needs to be redraw, re-render the overlays
|
||||
// TODO the overlay system should probably receive this message instead of the tool
|
||||
for layer_path in document.selected_visible_layers() {
|
||||
tool_data.overlay_renderer.render_subpath_overlays(&document.document_legacy, layer_path.to_vec(), responses);
|
||||
shape_overlay.render_subpath_overlays(&shape_editor.selected_shape_state, &document.document_legacy, layer_path.to_vec(), responses);
|
||||
}
|
||||
|
||||
self
|
||||
@@ -162,39 +175,39 @@ impl Fsm for PathToolFsmState {
|
||||
let shift_pressed = input.keyboard.get(add_to_selection as usize);
|
||||
|
||||
tool_data.opposing_handle_lengths = None;
|
||||
let selected_layers = shape_editor.selected_layers().cloned().collect();
|
||||
|
||||
// Select the first point within the threshold (in pixels)
|
||||
if let Some(mut selected_points) = tool_data
|
||||
.shape_editor
|
||||
.select_point(&document.document_legacy, input.mouse.position, SELECTION_THRESHOLD, shift_pressed, responses)
|
||||
{
|
||||
if let Some(mut selected_points) = shape_editor.select_point(&document.document_legacy, input.mouse.position, SELECTION_THRESHOLD, shift_pressed) {
|
||||
responses.push_back(DocumentMessage::StartTransaction.into());
|
||||
|
||||
let ignore_document = tool_data.shape_editor.selected_layers().clone();
|
||||
tool_data
|
||||
.snap_manager
|
||||
.start_snap(document, input, document.bounding_boxes(Some(&ignore_document), None, render_data), true, true);
|
||||
.start_snap(document, input, document.bounding_boxes(Some(&selected_layers), None, render_data), true, true);
|
||||
|
||||
// Do not snap against handles when anchor is selected
|
||||
let mut extension = Vec::new();
|
||||
let mut additional_selected_points = Vec::new();
|
||||
for point in selected_points.points.iter() {
|
||||
if point.manipulator_type == ManipulatorType::Anchor {
|
||||
extension.push(ManipulatorPointInfo {
|
||||
manipulator_type: ManipulatorType::InHandle,
|
||||
..*point
|
||||
if point.point_id.manipulator_type == SelectedType::Anchor {
|
||||
additional_selected_points.push(ManipulatorPointInfo {
|
||||
shape_layer_path: point.shape_layer_path,
|
||||
point_id: ManipulatorPointId::new(point.point_id.group, SelectedType::InHandle),
|
||||
});
|
||||
extension.push(ManipulatorPointInfo {
|
||||
manipulator_type: ManipulatorType::OutHandle,
|
||||
..*point
|
||||
additional_selected_points.push(ManipulatorPointInfo {
|
||||
shape_layer_path: point.shape_layer_path,
|
||||
point_id: ManipulatorPointId::new(point.point_id.group, SelectedType::OutHandle),
|
||||
});
|
||||
}
|
||||
}
|
||||
selected_points.points.extend(extension);
|
||||
selected_points.points.extend(additional_selected_points);
|
||||
|
||||
let include_handles = tool_data.shape_editor.selected_layers_ref();
|
||||
let include_handles: Vec<_> = selected_layers.iter().map(|x| x.as_slice()).collect();
|
||||
tool_data.snap_manager.add_all_document_handles(document, input, &include_handles, &[], &selected_points.points);
|
||||
tool_data.drag_start_pos = input.mouse.position;
|
||||
tool_data.previous_mouse_position = input.mouse.position - selected_points.offset;
|
||||
|
||||
tool_data.refresh_overlays(document, shape_editor, shape_overlay, responses);
|
||||
|
||||
PathToolFsmState::Dragging
|
||||
}
|
||||
// We didn't find a point nearby, so consider selecting the nearest shape instead
|
||||
@@ -219,7 +232,7 @@ impl Fsm for PathToolFsmState {
|
||||
tool_data.drag_start_pos = input.mouse.position;
|
||||
tool_data.previous_mouse_position = input.mouse.position;
|
||||
// Selects all the anchor points when clicking in a filled area of shape. If two shapes intersect we pick the topmost layer.
|
||||
tool_data.shape_editor.select_all_anchors(responses, top_most_intersection);
|
||||
shape_editor.select_all_anchors(&document.document_legacy, &top_most_intersection);
|
||||
return PathToolFsmState::Dragging;
|
||||
}
|
||||
} else {
|
||||
@@ -241,51 +254,47 @@ impl Fsm for PathToolFsmState {
|
||||
) => {
|
||||
// Determine when alt state changes
|
||||
let alt_pressed = input.keyboard.get(alt_mirror_angle as usize);
|
||||
if alt_pressed != tool_data.alt_debounce {
|
||||
tool_data.alt_debounce = alt_pressed;
|
||||
// Only on alt down
|
||||
if alt_pressed {
|
||||
tool_data.opposing_handle_lengths = None;
|
||||
tool_data.shape_editor.toggle_handle_mirroring_on_selected(true, responses);
|
||||
}
|
||||
|
||||
// Only on alt down
|
||||
if alt_pressed && !tool_data.alt_debounce {
|
||||
tool_data.opposing_handle_lengths = None;
|
||||
shape_editor.toggle_handle_mirroring_on_selected(responses);
|
||||
}
|
||||
tool_data.alt_debounce = alt_pressed;
|
||||
|
||||
// Determine when shift state changes
|
||||
let shift_pressed = input.keyboard.get(shift_mirror_distance as usize);
|
||||
|
||||
if shift_pressed {
|
||||
if tool_data.opposing_handle_lengths.is_none() {
|
||||
tool_data.opposing_handle_lengths = Some(tool_data.shape_editor.opposing_handle_lengths(&document.document_legacy));
|
||||
tool_data.opposing_handle_lengths = Some(shape_editor.opposing_handle_lengths(&document.document_legacy));
|
||||
}
|
||||
} else {
|
||||
if let Some(opposing_handle_lengths) = &tool_data.opposing_handle_lengths {
|
||||
tool_data.shape_editor.reset_opposing_handle_lengths(&document.document_legacy, opposing_handle_lengths, responses);
|
||||
shape_editor.reset_opposing_handle_lengths(&document.document_legacy, opposing_handle_lengths, responses);
|
||||
tool_data.opposing_handle_lengths = None;
|
||||
}
|
||||
}
|
||||
|
||||
// Move the selected points by the mouse position
|
||||
let snapped_position = tool_data.snap_manager.snap_position(responses, document, input.mouse.position);
|
||||
tool_data
|
||||
.shape_editor
|
||||
.move_selected_points(snapped_position - tool_data.previous_mouse_position, shift_pressed, responses);
|
||||
shape_editor.move_selected_points(&document.document_legacy, snapped_position - tool_data.previous_mouse_position, shift_pressed, responses);
|
||||
tool_data.previous_mouse_position = snapped_position;
|
||||
PathToolFsmState::Dragging
|
||||
}
|
||||
// Mouse up
|
||||
(_, PathToolMessage::DragStop { shift_mirror_distance }) => {
|
||||
let selected_points = tool_data.shape_editor.selected_points(&document.document_legacy);
|
||||
let nearest_point = tool_data.shape_editor.find_nearest_point(&document.document_legacy, input.mouse.position, SELECTION_THRESHOLD);
|
||||
let nearest_point = shape_editor
|
||||
.find_nearest_point_indices(&document.document_legacy, input.mouse.position, SELECTION_THRESHOLD)
|
||||
.map(|(_, nearest_point)| nearest_point)
|
||||
.clone();
|
||||
let shift_pressed = input.keyboard.get(shift_mirror_distance as usize);
|
||||
|
||||
if tool_data.drag_start_pos.distance(input.mouse.position) <= DRAG_THRESHOLD && !shift_pressed {
|
||||
for point in selected_points {
|
||||
if nearest_point == Some(point) {
|
||||
responses.push_back(DocumentMessage::DeselectAllManipulatorPoints.into());
|
||||
tool_data
|
||||
.shape_editor
|
||||
.select_point(&document.document_legacy, input.mouse.position, SELECTION_THRESHOLD, false, responses);
|
||||
}
|
||||
let clicked_selected = shape_editor.selected_points().any(|&point| nearest_point == Some(point));
|
||||
if clicked_selected {
|
||||
shape_editor.deselect_all();
|
||||
shape_editor.select_point(&document.document_legacy, input.mouse.position, SELECTION_THRESHOLD, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -296,18 +305,18 @@ impl Fsm for PathToolFsmState {
|
||||
(_, PathToolMessage::Delete) => {
|
||||
// Delete the selected points and clean up overlays
|
||||
responses.push_back(DocumentMessage::StartTransaction.into());
|
||||
tool_data.shape_editor.delete_selected_points(responses);
|
||||
shape_editor.delete_selected_points(responses);
|
||||
responses.push_back(PathToolMessage::SelectionChanged.into());
|
||||
for layer_path in document.all_layers() {
|
||||
tool_data.overlay_renderer.clear_subpath_overlays(&document.document_legacy, layer_path.to_vec(), responses);
|
||||
shape_overlay.clear_subpath_overlays(&document.document_legacy, layer_path.to_vec(), responses);
|
||||
}
|
||||
PathToolFsmState::Ready
|
||||
}
|
||||
(_, PathToolMessage::InsertPoint) => {
|
||||
// First we try and flip the sharpness (if they have clicked on an anchor)
|
||||
if !tool_data.shape_editor.flip_sharp(&document.document_legacy, input.mouse.position, SELECTION_TOLERANCE, responses) {
|
||||
if !shape_editor.flip_sharp(&document.document_legacy, input.mouse.position, SELECTION_TOLERANCE, responses) {
|
||||
// If not, then we try and split the path that may have been clicked upon
|
||||
tool_data.shape_editor.split(&document.document_legacy, input.mouse.position, SELECTION_TOLERANCE, responses);
|
||||
shape_editor.split(&document.document_legacy, input.mouse.position, SELECTION_TOLERANCE, responses);
|
||||
}
|
||||
|
||||
self
|
||||
@@ -315,7 +324,7 @@ impl Fsm for PathToolFsmState {
|
||||
(_, PathToolMessage::Abort) => {
|
||||
// TODO Tell overlay manager to remove the overlays
|
||||
for layer_path in document.all_layers() {
|
||||
tool_data.overlay_renderer.clear_subpath_overlays(&document.document_legacy, layer_path.to_vec(), responses);
|
||||
shape_overlay.clear_subpath_overlays(&document.document_legacy, layer_path.to_vec(), responses);
|
||||
}
|
||||
PathToolFsmState::Ready
|
||||
}
|
||||
|
||||
@@ -3,17 +3,20 @@ use crate::messages::frontend::utility_types::MouseCursorIcon;
|
||||
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion};
|
||||
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, WidgetLayout};
|
||||
use crate::messages::layout::utility_types::widgets::input_widgets::NumberInput;
|
||||
use crate::messages::portfolio::document::node_graph::VectorDataModification;
|
||||
use crate::messages::prelude::*;
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||
use crate::messages::tool::common_functionality::overlay_renderer::OverlayRenderer;
|
||||
|
||||
use crate::messages::tool::common_functionality::snapping::SnapManager;
|
||||
use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType};
|
||||
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
|
||||
|
||||
use document_legacy::layers::style;
|
||||
use document_legacy::LayerId;
|
||||
use document_legacy::Operation;
|
||||
use graphene_std::vector::consts::ManipulatorType;
|
||||
use graphene_std::vector::manipulator_group::ManipulatorGroup;
|
||||
use graphene_core::uuid::ManipulatorGroupId;
|
||||
use graphene_core::vector::style::Stroke;
|
||||
use graphene_core::vector::{ManipulatorPointId, SelectedType};
|
||||
use graphene_core::Color;
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -98,8 +101,8 @@ impl PropertyHolder for PenTool {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for PenTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: ToolActionHandlerData<'a>) {
|
||||
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for PenTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
|
||||
if let ToolMessage::Pen(PenToolMessage::UpdateOptions(action)) = message {
|
||||
match action {
|
||||
PenOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight,
|
||||
@@ -139,18 +142,342 @@ impl ToolTransition for PenTool {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ModifierState {
|
||||
snap_angle: bool,
|
||||
lock_angle: bool,
|
||||
break_handle: bool,
|
||||
}
|
||||
#[derive(Clone, Debug, Default)]
|
||||
struct PenToolData {
|
||||
weight: f64,
|
||||
path: Option<Vec<LayerId>>,
|
||||
overlay_renderer: OverlayRenderer,
|
||||
subpath_index: usize,
|
||||
snap_manager: SnapManager,
|
||||
should_mirror: bool,
|
||||
// Indicates that curve extension is occurring from the first point, rather than (more commonly) the last point
|
||||
from_start: bool,
|
||||
angle: f64,
|
||||
}
|
||||
impl PenToolData {
|
||||
fn extend_subpath(&mut self, layer: &[LayerId], subpath_index: usize, from_start: bool, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
|
||||
self.path = Some(layer.to_vec());
|
||||
self.from_start = from_start;
|
||||
self.subpath_index = subpath_index;
|
||||
|
||||
// Stop the handles on the first point from mirroring
|
||||
let Some(vector_data) = document.document_legacy.layer(layer).ok().and_then(|layer| layer.as_vector_data()) else { return };
|
||||
let manipulator_groups = vector_data.subpaths[subpath_index].manipulator_groups();
|
||||
let Some(last_handle) = (if from_start { manipulator_groups.first() } else { manipulator_groups.last() }) else { return };
|
||||
|
||||
responses.add(GraphOperationMessage::Vector {
|
||||
layer: layer.to_vec(),
|
||||
modification: VectorDataModification::SetManipulatorHandleMirroring {
|
||||
id: last_handle.id,
|
||||
mirror_angle: false,
|
||||
},
|
||||
});
|
||||
}
|
||||
fn create_new_path(&mut self, document: &DocumentMessageHandler, line_weight: f64, color: Color, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) {
|
||||
// Deselect layers because we are now creating a new layer
|
||||
responses.add(DocumentMessage::DeselectAllLayers);
|
||||
|
||||
let layer_path = document.get_path_for_new_layer();
|
||||
|
||||
// Get the position and set properties
|
||||
let transform = document.document_legacy.multiply_transforms(&layer_path[..layer_path.len() - 1]).unwrap_or_default();
|
||||
let snapped_position = self.snap_manager.snap_position(responses, document, input.mouse.position);
|
||||
let start_position = transform.inverse().transform_point2(snapped_position);
|
||||
self.weight = line_weight;
|
||||
|
||||
// Create the initial shape with a `bez_path` (only contains a moveto initially)
|
||||
let subpath = bezier_rs::Subpath::new(vec![bezier_rs::ManipulatorGroup::new(start_position, Some(start_position), Some(start_position))], false);
|
||||
graph_modification_utils::new_vector_layer(vec![subpath], layer_path.clone(), responses);
|
||||
responses.add(GraphOperationMessage::StrokeSet {
|
||||
layer: layer_path.clone(),
|
||||
stroke: Stroke::new(color, line_weight),
|
||||
});
|
||||
|
||||
self.path = Some(layer_path);
|
||||
self.from_start = false;
|
||||
self.subpath_index = 0;
|
||||
}
|
||||
/// If you place the anchor on top of the previous anchor then you break the mirror
|
||||
///
|
||||
/// TODO: tooltip / user documentation?
|
||||
fn check_break(&mut self, document: &DocumentMessageHandler, transform: DAffine2, shape_overlay: &mut OverlayRenderer, responses: &mut VecDeque<Message>) -> Option<()> {
|
||||
// Get subpath
|
||||
let layer_path = self.path.as_ref()?;
|
||||
let vector_data = document.document_legacy.layer(layer_path).ok().and_then(|layer| layer.as_vector_data())?;
|
||||
let subpath = &vector_data.subpaths[self.subpath_index];
|
||||
|
||||
// Get the last manipulator group and the one previous to that
|
||||
let mut manipulator_groups = subpath.manipulator_groups().iter();
|
||||
let last_manipulator_group = if self.from_start { manipulator_groups.next()? } else { manipulator_groups.next_back()? };
|
||||
let previous_manipulator_group = if self.from_start { manipulator_groups.next()? } else { manipulator_groups.next_back()? };
|
||||
|
||||
// Get correct handle types
|
||||
let outwards_handle = if self.from_start { SelectedType::InHandle } else { SelectedType::OutHandle };
|
||||
|
||||
// Get manipulator points
|
||||
let last_anchor = last_manipulator_group.anchor;
|
||||
let previous_anchor = previous_manipulator_group.anchor;
|
||||
|
||||
// Break the control
|
||||
let on_top = transform.transform_point2(last_anchor).distance_squared(transform.transform_point2(previous_anchor)) < crate::consts::SNAP_POINT_TOLERANCE.powi(2);
|
||||
if !on_top {
|
||||
return None;
|
||||
}
|
||||
// Remove the point that has just been placed
|
||||
responses.add(GraphOperationMessage::Vector {
|
||||
layer: layer_path.to_vec(),
|
||||
modification: VectorDataModification::RemoveManipulatorGroup { id: last_manipulator_group.id },
|
||||
});
|
||||
|
||||
// Move the in handle of the previous anchor to on top of the previous position
|
||||
let point = ManipulatorPointId::new(previous_manipulator_group.id, outwards_handle);
|
||||
responses.add(GraphOperationMessage::Vector {
|
||||
layer: layer_path.to_vec(),
|
||||
modification: VectorDataModification::SetManipulatorPosition { point, position: previous_anchor },
|
||||
});
|
||||
|
||||
// Stop the handles on the last point from mirroring
|
||||
let id = previous_manipulator_group.id;
|
||||
responses.add(GraphOperationMessage::Vector {
|
||||
layer: layer_path.to_vec(),
|
||||
modification: VectorDataModification::SetManipulatorHandleMirroring { id, mirror_angle: false },
|
||||
});
|
||||
|
||||
// The overlay system cannot detect deleted points so we must just delete all the overlays
|
||||
for layer_path in document.all_layers() {
|
||||
shape_overlay.clear_subpath_overlays(&document.document_legacy, layer_path.to_vec(), responses);
|
||||
}
|
||||
|
||||
self.should_mirror = false;
|
||||
None
|
||||
}
|
||||
|
||||
fn finish_placing_handle(&mut self, document: &DocumentMessageHandler, transform: DAffine2, shape_overlay: &mut OverlayRenderer, responses: &mut VecDeque<Message>) -> Option<PenToolFsmState> {
|
||||
// Get subpath
|
||||
let layer_path = self.path.as_ref()?;
|
||||
let vector_data = document.document_legacy.layer(layer_path).ok().and_then(|layer| layer.as_vector_data())?;
|
||||
let subpath = &vector_data.subpaths[self.subpath_index];
|
||||
|
||||
// Get the last manipulator group and the one previous to that
|
||||
let mut manipulator_groups = subpath.manipulator_groups().iter();
|
||||
let last_manipulator_group = if self.from_start { manipulator_groups.next()? } else { manipulator_groups.next_back()? };
|
||||
let previous_manipulator_group = if self.from_start { manipulator_groups.next() } else { manipulator_groups.next_back() };
|
||||
|
||||
// Get the first manipulator group
|
||||
let first_manipulator_group = if self.from_start {
|
||||
subpath.manipulator_groups().last()?
|
||||
} else {
|
||||
subpath.manipulator_groups().first()?
|
||||
};
|
||||
|
||||
// Get correct handle types
|
||||
let inwards_handle = if self.from_start { SelectedType::OutHandle } else { SelectedType::InHandle };
|
||||
let outwards_handle = if self.from_start { SelectedType::InHandle } else { SelectedType::OutHandle };
|
||||
|
||||
// Get manipulator points
|
||||
let last_anchor = last_manipulator_group.anchor;
|
||||
let first_anchor = first_manipulator_group.anchor;
|
||||
let last_in = inwards_handle.get_position(last_manipulator_group)?;
|
||||
|
||||
let transformed_distance_between_squared = transform.transform_point2(last_anchor).distance_squared(transform.transform_point2(first_anchor));
|
||||
let snap_point_tolerance_squared = crate::consts::SNAP_POINT_TOLERANCE.powi(2);
|
||||
let should_close_path = transformed_distance_between_squared < snap_point_tolerance_squared && previous_manipulator_group.is_some();
|
||||
if should_close_path {
|
||||
// Move the in handle of the first point to where the user has placed it
|
||||
let point = ManipulatorPointId::new(first_manipulator_group.id, inwards_handle);
|
||||
responses.add(GraphOperationMessage::Vector {
|
||||
layer: layer_path.to_vec(),
|
||||
modification: VectorDataModification::SetManipulatorPosition { point, position: last_in },
|
||||
});
|
||||
|
||||
// Stop the handles on the first point from mirroring
|
||||
let id = first_manipulator_group.id;
|
||||
responses.add(GraphOperationMessage::Vector {
|
||||
layer: layer_path.to_vec(),
|
||||
modification: VectorDataModification::SetManipulatorHandleMirroring { id, mirror_angle: false },
|
||||
});
|
||||
|
||||
// Remove the point that has just been placed
|
||||
responses.add(GraphOperationMessage::Vector {
|
||||
layer: layer_path.to_vec(),
|
||||
modification: VectorDataModification::RemoveManipulatorGroup { id: last_manipulator_group.id },
|
||||
});
|
||||
|
||||
// Push a close path node
|
||||
responses.add(GraphOperationMessage::Vector {
|
||||
layer: layer_path.to_vec(),
|
||||
modification: VectorDataModification::SetClosed { index: 0, closed: true },
|
||||
});
|
||||
|
||||
responses.push_back(DocumentMessage::CommitTransaction.into());
|
||||
|
||||
// Clean up overlays
|
||||
for layer_path in document.all_layers() {
|
||||
shape_overlay.clear_subpath_overlays(&document.document_legacy, layer_path.to_vec(), responses);
|
||||
}
|
||||
|
||||
// Clean up tool data
|
||||
self.path = None;
|
||||
self.snap_manager.cleanup(responses);
|
||||
|
||||
// Return to ready state
|
||||
return Some(PenToolFsmState::Ready);
|
||||
}
|
||||
// Add a new manipulator for the next anchor that we will place
|
||||
if let Some(out_handle) = outwards_handle.get_position(last_manipulator_group) {
|
||||
responses.push_back(add_manipulator_group(&self.path, self.from_start, bezier_rs::ManipulatorGroup::new_anchor(out_handle)));
|
||||
}
|
||||
|
||||
Some(PenToolFsmState::PlacingAnchor)
|
||||
}
|
||||
|
||||
fn drag_handle(&mut self, document: &DocumentMessageHandler, transform: DAffine2, mouse: DVec2, modifiers: ModifierState, responses: &mut VecDeque<Message>) -> Option<PenToolFsmState> {
|
||||
// Get subpath
|
||||
let layer_path = self.path.as_ref()?;
|
||||
let vector_data = document.document_legacy.layer(layer_path).ok().and_then(|layer| layer.as_vector_data())?;
|
||||
let subpath = &vector_data.subpaths[self.subpath_index];
|
||||
|
||||
// Get the last manipulator group
|
||||
let manipulator_groups = subpath.manipulator_groups();
|
||||
let last_manipulator_group = if self.from_start { manipulator_groups.first()? } else { manipulator_groups.last()? };
|
||||
|
||||
// Get correct handle types
|
||||
let inwards_handle = if self.from_start { SelectedType::OutHandle } else { SelectedType::InHandle };
|
||||
let outwards_handle = if self.from_start { SelectedType::InHandle } else { SelectedType::OutHandle };
|
||||
|
||||
// Get manipulator points
|
||||
let last_anchor = last_manipulator_group.anchor;
|
||||
|
||||
let mouse = self.snap_manager.snap_position(responses, document, mouse);
|
||||
let pos = transform.inverse().transform_point2(mouse);
|
||||
|
||||
let pos = compute_snapped_angle(&mut self.angle, modifiers.lock_angle, modifiers.snap_angle, pos, last_anchor);
|
||||
|
||||
// Update points on current segment (to show preview of new handle)
|
||||
let point = ManipulatorPointId::new(last_manipulator_group.id, outwards_handle);
|
||||
responses.add(GraphOperationMessage::Vector {
|
||||
layer: layer_path.to_vec(),
|
||||
modification: VectorDataModification::SetManipulatorPosition { point, position: pos },
|
||||
});
|
||||
|
||||
let should_mirror = !modifiers.break_handle && self.should_mirror;
|
||||
// Mirror handle of last segment
|
||||
if should_mirror {
|
||||
// Could also be written as `last_anchor.position * 2 - pos` but this way avoids overflow/underflow better
|
||||
let pos = last_anchor - (pos - last_anchor);
|
||||
let point = ManipulatorPointId::new(last_manipulator_group.id, inwards_handle);
|
||||
responses.add(GraphOperationMessage::Vector {
|
||||
layer: layer_path.to_vec(),
|
||||
modification: VectorDataModification::SetManipulatorPosition { point, position: pos },
|
||||
});
|
||||
}
|
||||
|
||||
// Update the mirror status of the currently modifying point
|
||||
let id = last_manipulator_group.id;
|
||||
responses.add(GraphOperationMessage::Vector {
|
||||
layer: layer_path.to_vec(),
|
||||
modification: VectorDataModification::SetManipulatorHandleMirroring { id, mirror_angle: should_mirror },
|
||||
});
|
||||
|
||||
Some(PenToolFsmState::DraggingHandle)
|
||||
}
|
||||
|
||||
fn place_anchor(&mut self, document: &DocumentMessageHandler, transform: DAffine2, mouse: DVec2, modifiers: ModifierState, responses: &mut VecDeque<Message>) -> Option<PenToolFsmState> {
|
||||
// Get subpath
|
||||
let layer_path = self.path.as_ref()?;
|
||||
let vector_data = document.document_legacy.layer(layer_path).ok().and_then(|layer| layer.as_vector_data())?;
|
||||
let subpath = &vector_data.subpaths[self.subpath_index];
|
||||
|
||||
// Get the last manipulator group and the one previous to that
|
||||
let mut manipulator_groups = subpath.manipulator_groups().iter();
|
||||
let last_manipulator_group = if self.from_start { manipulator_groups.next()? } else { manipulator_groups.next_back()? };
|
||||
let previous_manipulator_group = if self.from_start { manipulator_groups.next() } else { manipulator_groups.next_back() };
|
||||
|
||||
// Get the first manipulator group
|
||||
let manipulator_groups = subpath.manipulator_groups();
|
||||
let first_manipulator_group = if self.from_start { manipulator_groups.last()? } else { manipulator_groups.first()? };
|
||||
|
||||
// Get manipulator points
|
||||
let first_anchor = first_manipulator_group.anchor;
|
||||
|
||||
let mouse = self.snap_manager.snap_position(responses, document, mouse);
|
||||
let mut pos = transform.inverse().transform_point2(mouse);
|
||||
|
||||
// Snap to the first point (to show close path)
|
||||
let show_close_path = mouse.distance_squared(transform.transform_point2(first_anchor)) < crate::consts::SNAP_POINT_TOLERANCE.powi(2);
|
||||
if show_close_path {
|
||||
pos = first_anchor;
|
||||
}
|
||||
|
||||
if let Some(relative_previous_anchor) = previous_manipulator_group.map(|group| group.anchor) {
|
||||
// Snap to the previously placed point (to show break control)
|
||||
if mouse.distance_squared(transform.transform_point2(relative_previous_anchor)) < crate::consts::SNAP_POINT_TOLERANCE.powi(2) {
|
||||
pos = relative_previous_anchor;
|
||||
} else {
|
||||
pos = compute_snapped_angle(&mut self.angle, modifiers.lock_angle, modifiers.snap_angle, pos, relative_previous_anchor);
|
||||
}
|
||||
}
|
||||
|
||||
for manipulator_type in [SelectedType::Anchor, SelectedType::InHandle, SelectedType::OutHandle] {
|
||||
let point = ManipulatorPointId::new(last_manipulator_group.id, manipulator_type);
|
||||
responses.add(GraphOperationMessage::Vector {
|
||||
layer: layer_path.to_vec(),
|
||||
modification: VectorDataModification::SetManipulatorPosition { point, position: pos },
|
||||
});
|
||||
}
|
||||
Some(PenToolFsmState::PlacingAnchor)
|
||||
}
|
||||
|
||||
fn finish_transaction(&mut self, fsm: PenToolFsmState, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) -> Option<DocumentMessage> {
|
||||
// Get subpath
|
||||
let layer_path = self.path.as_ref()?;
|
||||
let vector_data = document.document_legacy.layer(layer_path).ok().and_then(|layer| layer.as_vector_data())?;
|
||||
let subpath = &vector_data.subpaths[self.subpath_index];
|
||||
|
||||
// Abort if only one manipulator group has been placed
|
||||
if fsm == PenToolFsmState::PlacingAnchor && subpath.len() < 3 {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Get the last manipulator group and the one previous to that
|
||||
let mut manipulator_groups = subpath.manipulator_groups().iter();
|
||||
let mut last_manipulator_group = if self.from_start { manipulator_groups.next()? } else { manipulator_groups.next_back()? };
|
||||
let previous_manipulator_group = if self.from_start { manipulator_groups.next() } else { manipulator_groups.next_back() };
|
||||
|
||||
// Get correct handle types
|
||||
let outwards_handle = if self.from_start { SelectedType::InHandle } else { SelectedType::OutHandle };
|
||||
|
||||
// If placing anchor we should abort if there are less than three manipulators (as the last one gets deleted)
|
||||
let Some(previous_manipulator_group) = previous_manipulator_group else {
|
||||
return Some(DocumentMessage::AbortTransaction);
|
||||
};
|
||||
|
||||
// Clean up if there are two or more manipulators
|
||||
// Remove the unplaced anchor if in anchor placing mode
|
||||
if fsm == PenToolFsmState::PlacingAnchor {
|
||||
let layer_path = layer_path.clone();
|
||||
responses.add(GraphOperationMessage::Vector {
|
||||
layer: layer_path.to_vec(),
|
||||
modification: VectorDataModification::RemoveManipulatorGroup { id: last_manipulator_group.id },
|
||||
});
|
||||
last_manipulator_group = previous_manipulator_group;
|
||||
}
|
||||
|
||||
// Remove the out handle
|
||||
let point = ManipulatorPointId::new(last_manipulator_group.id, outwards_handle);
|
||||
let position = last_manipulator_group.anchor;
|
||||
responses.add(GraphOperationMessage::Vector {
|
||||
layer: layer_path.to_vec(),
|
||||
modification: VectorDataModification::SetManipulatorPosition { point, position },
|
||||
});
|
||||
|
||||
return Some(DocumentMessage::CommitTransaction);
|
||||
}
|
||||
}
|
||||
|
||||
impl Fsm for PenToolFsmState {
|
||||
type ToolData = PenToolData;
|
||||
@@ -160,7 +487,15 @@ impl Fsm for PenToolFsmState {
|
||||
self,
|
||||
event: ToolMessage,
|
||||
tool_data: &mut Self::ToolData,
|
||||
(document, _document_id, global_tool_data, input, render_data): ToolActionHandlerData,
|
||||
ToolActionHandlerData {
|
||||
document,
|
||||
global_tool_data,
|
||||
input,
|
||||
render_data,
|
||||
shape_editor,
|
||||
shape_overlay,
|
||||
..
|
||||
}: &mut ToolActionHandlerData,
|
||||
tool_options: &Self::ToolOptions,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) -> Self {
|
||||
@@ -172,19 +507,19 @@ impl Fsm for PenToolFsmState {
|
||||
// When the document has moved / needs to be redraw, re-render the overlays
|
||||
// TODO the overlay system should probably receive this message instead of the tool
|
||||
for layer_path in document.selected_visible_layers() {
|
||||
tool_data.overlay_renderer.render_subpath_overlays(&document.document_legacy, layer_path.to_vec(), responses);
|
||||
shape_overlay.render_subpath_overlays(&shape_editor.selected_shape_state, &document.document_legacy, layer_path.to_vec(), responses);
|
||||
}
|
||||
self
|
||||
}
|
||||
(_, PenToolMessage::SelectionChanged) => {
|
||||
// Set the previously selected layers to invisible
|
||||
for layer_path in document.all_layers() {
|
||||
tool_data.overlay_renderer.layer_overlay_visibility(&document.document_legacy, layer_path.to_vec(), false, responses);
|
||||
shape_overlay.layer_overlay_visibility(&document.document_legacy, layer_path.to_vec(), false, responses);
|
||||
}
|
||||
|
||||
// Redraw the overlays of the newly selected layers
|
||||
for layer_path in document.selected_visible_layers() {
|
||||
tool_data.overlay_renderer.render_subpath_overlays(&document.document_legacy, layer_path.to_vec(), responses);
|
||||
shape_overlay.render_subpath_overlays(&shape_editor.selected_shape_state, &document.document_legacy, layer_path.to_vec(), responses);
|
||||
}
|
||||
self
|
||||
}
|
||||
@@ -199,383 +534,49 @@ impl Fsm for PenToolFsmState {
|
||||
tool_data.should_mirror = false;
|
||||
|
||||
// Perform extension of an existing path
|
||||
if let Some((layer, from_start)) = should_extend(document, input.mouse.position, crate::consts::SNAP_POINT_TOLERANCE) {
|
||||
tool_data.path = Some(layer.to_vec());
|
||||
tool_data.from_start = from_start;
|
||||
|
||||
// Stop the handles on the first point from mirroring
|
||||
let mut stop_mirror = || {
|
||||
let subpath = document.document_legacy.layer(layer).ok().and_then(|layer| layer.as_subpath())?;
|
||||
let mut manipulator_groups = subpath.manipulator_groups().enumerate();
|
||||
let (&id, _) = if from_start { manipulator_groups.next()? } else { manipulator_groups.next_back()? };
|
||||
|
||||
let op = Operation::SetManipulatorHandleMirroring {
|
||||
layer_path: layer.to_vec(),
|
||||
id,
|
||||
mirror_angle: false,
|
||||
};
|
||||
responses.push_back(op.into());
|
||||
Some(())
|
||||
};
|
||||
stop_mirror();
|
||||
|
||||
return PenToolFsmState::DraggingHandle;
|
||||
}
|
||||
|
||||
// Deselect layers because we are now creating a new layer
|
||||
responses.push_back(DocumentMessage::DeselectAllLayers.into());
|
||||
|
||||
// Create a new layer
|
||||
tool_data.path = Some(document.get_path_for_new_layer());
|
||||
tool_data.from_start = false;
|
||||
|
||||
// Get the position and set properties
|
||||
let transform = tool_data
|
||||
.path
|
||||
.as_ref()
|
||||
.and_then(|path| document.document_legacy.multiply_transforms(&path[..path.len() - 1]).ok())
|
||||
.unwrap_or_default();
|
||||
let snapped_position = tool_data.snap_manager.snap_position(responses, document, input.mouse.position);
|
||||
let start_position = transform.inverse().transform_point2(snapped_position);
|
||||
tool_data.weight = tool_options.line_weight;
|
||||
|
||||
// Create the initial shape with a `bez_path` (only contains a moveto initially)
|
||||
if let Some(layer_path) = &tool_data.path {
|
||||
responses.push_back(
|
||||
Operation::AddShape {
|
||||
path: layer_path.clone(),
|
||||
transform: DAffine2::IDENTITY.to_cols_array(),
|
||||
insert_index: -1,
|
||||
subpath: Default::default(),
|
||||
style: style::PathStyle::new(Some(style::Stroke::new(global_tool_data.primary_color, tool_data.weight)), style::Fill::None),
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
responses.push_back(add_manipulator_group(
|
||||
&tool_data.path,
|
||||
tool_data.from_start,
|
||||
ManipulatorGroup::new_with_handles(start_position, Some(start_position), Some(start_position)),
|
||||
));
|
||||
if let Some((layer, subpath_index, from_start)) = should_extend(document, input.mouse.position, crate::consts::SNAP_POINT_TOLERANCE) {
|
||||
tool_data.extend_subpath(layer, subpath_index, from_start, document, responses);
|
||||
} else {
|
||||
tool_data.create_new_path(document, tool_options.line_weight, global_tool_data.primary_color, input, responses);
|
||||
}
|
||||
|
||||
// Enter the dragging handle state while the mouse is held down, allowing the user to move the mouse and position the handle
|
||||
PenToolFsmState::DraggingHandle
|
||||
}
|
||||
(PenToolFsmState::PlacingAnchor, PenToolMessage::DragStart) => {
|
||||
// If you place the anchor on top of the previous anchor then you break the mirror
|
||||
let mut check_break = || {
|
||||
// Get subpath
|
||||
let layer_path = tool_data.path.as_ref()?;
|
||||
let subpath = document.document_legacy.layer(layer_path).ok().and_then(|layer| layer.as_subpath())?;
|
||||
|
||||
// Get the last manipulator group and the one previous to that
|
||||
let mut manipulator_groups = subpath.manipulator_groups().enumerate();
|
||||
let (&last_id, last_manipulator_group) = if tool_data.from_start { manipulator_groups.next()? } else { manipulator_groups.next_back()? };
|
||||
let previous = if tool_data.from_start { manipulator_groups.next() } else { manipulator_groups.next_back() };
|
||||
|
||||
// Get correct handle types
|
||||
let outwards_handle = if tool_data.from_start { ManipulatorType::InHandle } else { ManipulatorType::OutHandle };
|
||||
|
||||
// Get manipulator points
|
||||
let last_anchor = last_manipulator_group.points[ManipulatorType::Anchor].as_ref()?;
|
||||
|
||||
if let Some((previous_id, previous_anchor)) = previous
|
||||
.as_ref()
|
||||
.and_then(|(&id, manipulator_group)| manipulator_group.points[ManipulatorType::Anchor].as_ref().map(|x| (id, x)))
|
||||
{
|
||||
// Break the control
|
||||
if transform.transform_point2(last_anchor.position).distance_squared(transform.transform_point2(previous_anchor.position)) < crate::consts::SNAP_POINT_TOLERANCE.powi(2) {
|
||||
// Remove the point that has just been placed
|
||||
let op = Operation::RemoveManipulatorGroup {
|
||||
layer_path: layer_path.clone(),
|
||||
id: last_id,
|
||||
};
|
||||
responses.push_back(op.into());
|
||||
|
||||
// Move the in handle of the previous anchor to on top of the previous position
|
||||
let op = Operation::MoveManipulatorPoint {
|
||||
layer_path: layer_path.clone(),
|
||||
id: previous_id,
|
||||
manipulator_type: outwards_handle,
|
||||
position: previous_anchor.position.into(),
|
||||
};
|
||||
responses.push_back(op.into());
|
||||
|
||||
// Stop the handles on the last point from mirroring
|
||||
let op = Operation::SetManipulatorHandleMirroring {
|
||||
layer_path: layer_path.clone(),
|
||||
id: previous_id,
|
||||
mirror_angle: false,
|
||||
};
|
||||
responses.push_back(op.into());
|
||||
|
||||
// The overlay system cannot detect deleted points so we must just delete all the overlays
|
||||
for layer_path in document.all_layers() {
|
||||
tool_data.overlay_renderer.clear_subpath_overlays(&document.document_legacy, layer_path.to_vec(), responses);
|
||||
}
|
||||
|
||||
tool_data.should_mirror = false;
|
||||
}
|
||||
}
|
||||
None
|
||||
};
|
||||
check_break().unwrap_or(PenToolFsmState::DraggingHandle)
|
||||
tool_data.check_break(document, transform, shape_overlay, responses);
|
||||
PenToolFsmState::DraggingHandle
|
||||
}
|
||||
(PenToolFsmState::DraggingHandle, PenToolMessage::DragStop) => {
|
||||
let mut process = || {
|
||||
// Get subpath
|
||||
let layer_path = tool_data.path.as_ref()?;
|
||||
let subpath = document.document_legacy.layer(layer_path).ok().and_then(|layer| layer.as_subpath())?;
|
||||
|
||||
// Get the last manipulator group and the one previous to that
|
||||
let mut manipulator_groups = subpath.manipulator_groups().enumerate();
|
||||
let (&last_id, last_manipulator_group) = if tool_data.from_start { manipulator_groups.next()? } else { manipulator_groups.next_back()? };
|
||||
let previous = if tool_data.from_start { manipulator_groups.next() } else { manipulator_groups.next_back() };
|
||||
|
||||
// Get the first manipulator group
|
||||
let mut manipulator_groups = subpath.manipulator_groups().enumerate();
|
||||
let (&first_id, first_manipulator_group) = if tool_data.from_start { manipulator_groups.next_back()? } else { manipulator_groups.next()? };
|
||||
|
||||
// Get correct handle types
|
||||
let inwards_handle = if tool_data.from_start { ManipulatorType::OutHandle } else { ManipulatorType::InHandle };
|
||||
let outwards_handle = if tool_data.from_start { ManipulatorType::InHandle } else { ManipulatorType::OutHandle };
|
||||
|
||||
// Get manipulator points
|
||||
let last_anchor = last_manipulator_group.points[ManipulatorType::Anchor].as_ref()?;
|
||||
let first_anchor = first_manipulator_group.points[ManipulatorType::Anchor].as_ref()?;
|
||||
let last_in = last_manipulator_group.points[inwards_handle].as_ref()?;
|
||||
|
||||
// Close path
|
||||
let transformed_distance_between_squared = transform.transform_point2(last_anchor.position).distance_squared(transform.transform_point2(first_anchor.position));
|
||||
let snap_point_tolerance_squared = crate::consts::SNAP_POINT_TOLERANCE.powi(2);
|
||||
if transformed_distance_between_squared < snap_point_tolerance_squared && previous.is_some() {
|
||||
// Move the in handle of the first point to where the user has placed it
|
||||
let op = Operation::MoveManipulatorPoint {
|
||||
layer_path: layer_path.clone(),
|
||||
id: first_id,
|
||||
manipulator_type: inwards_handle,
|
||||
position: last_in.position.into(),
|
||||
};
|
||||
responses.push_back(op.into());
|
||||
|
||||
// Stop the handles on the first point from mirroring
|
||||
let op = Operation::SetManipulatorHandleMirroring {
|
||||
layer_path: layer_path.clone(),
|
||||
id: first_id,
|
||||
mirror_angle: false,
|
||||
};
|
||||
responses.push_back(op.into());
|
||||
|
||||
// Remove the point that has just been placed
|
||||
let op = Operation::RemoveManipulatorGroup {
|
||||
layer_path: layer_path.clone(),
|
||||
id: last_id,
|
||||
};
|
||||
responses.push_back(op.into());
|
||||
|
||||
// Push a close path node
|
||||
responses.push_back(add_manipulator_group(&tool_data.path, false, ManipulatorGroup::closed()));
|
||||
|
||||
responses.push_back(DocumentMessage::CommitTransaction.into());
|
||||
|
||||
// Clean up overlays
|
||||
for layer_path in document.all_layers() {
|
||||
tool_data.overlay_renderer.clear_subpath_overlays(&document.document_legacy, layer_path.to_vec(), responses);
|
||||
}
|
||||
|
||||
// Clean up tool data
|
||||
tool_data.path = None;
|
||||
tool_data.snap_manager.cleanup(responses);
|
||||
|
||||
// Return the new tool state, wrapped in `Some()` because this closure returns an Option used by the `?` operation various times above
|
||||
return Some(PenToolFsmState::Ready);
|
||||
}
|
||||
// Add a new manipulator for the next anchor that we will place
|
||||
if let Some(out_handle) = &last_manipulator_group.points[outwards_handle] {
|
||||
responses.push_back(add_manipulator_group(&tool_data.path, tool_data.from_start, ManipulatorGroup::new_with_anchor(out_handle.position)));
|
||||
}
|
||||
|
||||
// Returning `None` means the `unwrap_or` clause below returns the state `PlacingAnchor`
|
||||
None
|
||||
};
|
||||
tool_data.should_mirror = true;
|
||||
process().unwrap_or(PenToolFsmState::PlacingAnchor)
|
||||
tool_data.finish_placing_handle(document, transform, shape_overlay, responses).unwrap_or(PenToolFsmState::PlacingAnchor)
|
||||
}
|
||||
(PenToolFsmState::DraggingHandle, PenToolMessage::PointerMove { snap_angle, break_handle, lock_angle }) => {
|
||||
let mut process = || {
|
||||
// Get subpath
|
||||
let layer_path = tool_data.path.as_ref()?;
|
||||
let subpath = document.document_legacy.layer(layer_path).ok().and_then(|layer| layer.as_subpath())?;
|
||||
|
||||
// Get the last manipulator group
|
||||
let mut manipulator_groups = subpath.manipulator_groups().enumerate();
|
||||
let (&last_id, last_manipulator_group) = if tool_data.from_start { manipulator_groups.next()? } else { manipulator_groups.next_back()? };
|
||||
|
||||
// Get correct handle types
|
||||
let inwards_handle = if tool_data.from_start { ManipulatorType::OutHandle } else { ManipulatorType::InHandle };
|
||||
let outwards_handle = if tool_data.from_start { ManipulatorType::InHandle } else { ManipulatorType::OutHandle };
|
||||
|
||||
// Get manipulator points
|
||||
let last_anchor = last_manipulator_group.points[ManipulatorType::Anchor].as_ref()?;
|
||||
|
||||
let mouse = tool_data.snap_manager.snap_position(responses, document, input.mouse.position);
|
||||
let pos = transform.inverse().transform_point2(mouse);
|
||||
|
||||
let snap_angle = input.keyboard.get(snap_angle as usize);
|
||||
let lock_angle = input.keyboard.get(lock_angle as usize);
|
||||
let pos = compute_snapped_angle(&mut tool_data.angle, lock_angle, snap_angle, pos, last_anchor.position);
|
||||
|
||||
// Update points on current segment (to show preview of new handle)
|
||||
let msg = Operation::MoveManipulatorPoint {
|
||||
layer_path: layer_path.clone(),
|
||||
id: last_id,
|
||||
manipulator_type: outwards_handle,
|
||||
position: pos.into(),
|
||||
};
|
||||
responses.push_back(msg.into());
|
||||
|
||||
let should_mirror = !input.keyboard.get(break_handle as usize) && tool_data.should_mirror;
|
||||
// Mirror handle of last segment
|
||||
if should_mirror {
|
||||
// Could also be written as `last_anchor.position * 2 - pos` but this way avoids overflow/underflow better
|
||||
let pos = last_anchor.position - (pos - last_anchor.position);
|
||||
|
||||
let msg = Operation::MoveManipulatorPoint {
|
||||
layer_path: layer_path.clone(),
|
||||
id: last_id,
|
||||
manipulator_type: inwards_handle,
|
||||
position: pos.into(),
|
||||
};
|
||||
responses.push_back(msg.into());
|
||||
}
|
||||
|
||||
// Update the mirror status of the currently modifying point
|
||||
let op = Operation::SetManipulatorHandleMirroring {
|
||||
layer_path: layer_path.clone(),
|
||||
id: last_id,
|
||||
mirror_angle: should_mirror,
|
||||
};
|
||||
responses.push_back(op.into());
|
||||
|
||||
Some(())
|
||||
let modifiers = ModifierState {
|
||||
snap_angle: input.keyboard.key(snap_angle),
|
||||
lock_angle: input.keyboard.key(lock_angle),
|
||||
break_handle: input.keyboard.key(break_handle),
|
||||
};
|
||||
if process().is_none() {
|
||||
PenToolFsmState::Ready
|
||||
} else {
|
||||
self
|
||||
}
|
||||
tool_data.drag_handle(document, transform, input.mouse.position, modifiers, responses).unwrap_or(PenToolFsmState::Ready)
|
||||
}
|
||||
(PenToolFsmState::PlacingAnchor, PenToolMessage::PointerMove { snap_angle, lock_angle, .. }) => {
|
||||
let mut process = || {
|
||||
// Get subpath
|
||||
let data = tool_data.clone();
|
||||
let layer_path = data.path.as_ref()?;
|
||||
let subpath = document.document_legacy.layer(layer_path).ok().and_then(|layer| layer.as_subpath())?;
|
||||
|
||||
// Get the last manipulator group and the one previous to that
|
||||
let mut manipulator_groups = subpath.manipulator_groups().enumerate();
|
||||
let (&last_id, _last_manipulator_group) = if tool_data.from_start { manipulator_groups.next()? } else { manipulator_groups.next_back()? };
|
||||
let previous = if tool_data.from_start { manipulator_groups.next() } else { manipulator_groups.next_back() };
|
||||
|
||||
// Get the first manipulator group
|
||||
let mut manipulator_groups = subpath.manipulator_groups().enumerate();
|
||||
let (_first_id, first_manipulator_group) = if tool_data.from_start { manipulator_groups.next_back()? } else { manipulator_groups.next()? };
|
||||
|
||||
// Get manipulator points
|
||||
let first_anchor = first_manipulator_group.points[ManipulatorType::Anchor].as_ref()?;
|
||||
|
||||
let mouse = tool_data.snap_manager.snap_position(responses, document, input.mouse.position);
|
||||
let mut pos = transform.inverse().transform_point2(mouse);
|
||||
|
||||
// Snap to the first point (to show close path)
|
||||
if mouse.distance_squared(transform.transform_point2(first_anchor.position)) < crate::consts::SNAP_POINT_TOLERANCE.powi(2) {
|
||||
pos = first_anchor.position;
|
||||
}
|
||||
|
||||
if let Some(relative) = previous.as_ref().and_then(|(_, manipulator_group)| manipulator_group.points[ManipulatorType::Anchor].as_ref()) {
|
||||
// Snap to the previously placed point (to show break control)
|
||||
if mouse.distance_squared(transform.transform_point2(relative.position)) < crate::consts::SNAP_POINT_TOLERANCE.powi(2) {
|
||||
pos = relative.position;
|
||||
} else {
|
||||
let snap_angle = input.keyboard.get(snap_angle as usize);
|
||||
let lock_angle = input.keyboard.get(lock_angle as usize);
|
||||
pos = compute_snapped_angle(&mut tool_data.angle, lock_angle, snap_angle, pos, relative.position);
|
||||
}
|
||||
}
|
||||
|
||||
for manipulator_type in [ManipulatorType::Anchor, ManipulatorType::InHandle, ManipulatorType::OutHandle] {
|
||||
let msg = Operation::MoveManipulatorPoint {
|
||||
layer_path: layer_path.clone(),
|
||||
id: last_id,
|
||||
manipulator_type,
|
||||
position: pos.into(),
|
||||
};
|
||||
responses.push_back(msg.into());
|
||||
}
|
||||
|
||||
Some(())
|
||||
(PenToolFsmState::PlacingAnchor, PenToolMessage::PointerMove { snap_angle, break_handle, lock_angle }) => {
|
||||
let modifiers = ModifierState {
|
||||
snap_angle: input.keyboard.key(snap_angle),
|
||||
lock_angle: input.keyboard.key(lock_angle),
|
||||
break_handle: input.keyboard.key(break_handle),
|
||||
};
|
||||
if process().is_none() {
|
||||
PenToolFsmState::Ready
|
||||
} else {
|
||||
self
|
||||
}
|
||||
tool_data
|
||||
.place_anchor(document, transform, input.mouse.position, modifiers, responses)
|
||||
.unwrap_or(PenToolFsmState::Ready)
|
||||
}
|
||||
(PenToolFsmState::DraggingHandle | PenToolFsmState::PlacingAnchor, PenToolMessage::Abort | PenToolMessage::Confirm) => {
|
||||
// Abort or commit the transaction to the undo history
|
||||
let mut commit = || {
|
||||
// Get subpath
|
||||
let layer_path = tool_data.path.as_ref()?;
|
||||
let subpath = document.document_legacy.layer(layer_path).ok().and_then(|layer| layer.as_subpath())?;
|
||||
|
||||
// If placing anchor we should abort if there are less than three manipulators (as the last one gets deleted)
|
||||
if self == PenToolFsmState::PlacingAnchor && subpath.manipulator_groups().len() < 3 {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Get the last manipulator group and the one previous to that
|
||||
let mut manipulator_groups = subpath.manipulator_groups().enumerate();
|
||||
let (&(mut last_id), mut last_manipulator_group) = if tool_data.from_start { manipulator_groups.next()? } else { manipulator_groups.next_back()? };
|
||||
let previous = if tool_data.from_start { manipulator_groups.next() } else { manipulator_groups.next_back() };
|
||||
|
||||
// Get correct handle types
|
||||
let outwards_handle = if tool_data.from_start { ManipulatorType::InHandle } else { ManipulatorType::OutHandle };
|
||||
|
||||
// Clean up if there are two or more manipulators
|
||||
if let Some((&previous_id, previous_manipulator_group)) = previous {
|
||||
// Remove the unplaced anchor if in anchor placing mode
|
||||
if self == PenToolFsmState::PlacingAnchor {
|
||||
let layer_path = layer_path.clone();
|
||||
let op = Operation::RemoveManipulatorGroup { layer_path, id: last_id };
|
||||
responses.push_back(op.into());
|
||||
last_id = previous_id;
|
||||
last_manipulator_group = previous_manipulator_group;
|
||||
}
|
||||
|
||||
// Remove the out handle
|
||||
let op = Operation::MoveManipulatorPoint {
|
||||
layer_path: layer_path.clone(),
|
||||
id: last_id,
|
||||
manipulator_type: outwards_handle,
|
||||
position: last_manipulator_group.points[ManipulatorType::Anchor].as_ref()?.position.into(),
|
||||
};
|
||||
responses.push_back(op.into());
|
||||
|
||||
responses.push_back(DocumentMessage::CommitTransaction.into());
|
||||
|
||||
return Some(());
|
||||
}
|
||||
|
||||
// Abort if only one manipulator group has been placed
|
||||
None
|
||||
};
|
||||
if commit().is_none() {
|
||||
responses.push_back(DocumentMessage::AbortTransaction.into());
|
||||
}
|
||||
let message = tool_data.finish_transaction(self, document, responses).unwrap_or(DocumentMessage::AbortTransaction);
|
||||
responses.add(message);
|
||||
|
||||
// Clean up overlays
|
||||
for layer_path in document.all_layers() {
|
||||
tool_data.overlay_renderer.clear_subpath_overlays(&document.document_legacy, layer_path.to_vec(), responses);
|
||||
shape_overlay.clear_subpath_overlays(&document.document_legacy, layer_path.to_vec(), responses);
|
||||
}
|
||||
tool_data.path = None;
|
||||
tool_data.snap_manager.cleanup(responses);
|
||||
@@ -585,7 +586,7 @@ impl Fsm for PenToolFsmState {
|
||||
(_, PenToolMessage::Abort) => {
|
||||
// Clean up overlays
|
||||
for layer_path in document.all_layers() {
|
||||
tool_data.overlay_renderer.clear_subpath_overlays(&document.document_legacy, layer_path.to_vec(), responses);
|
||||
shape_overlay.clear_subpath_overlays(&document.document_legacy, layer_path.to_vec(), responses);
|
||||
}
|
||||
self
|
||||
}
|
||||
@@ -640,51 +641,44 @@ fn compute_snapped_angle(cached_angle: &mut f64, lock_angle: bool, snap_angle: b
|
||||
}
|
||||
}
|
||||
|
||||
/// Pushes a [ManipulatorGroup] to the current layer via an [Operation].
|
||||
fn add_manipulator_group(layer_path: &Option<Vec<LayerId>>, from_start: bool, manipulator_group: ManipulatorGroup) -> Message {
|
||||
match (layer_path, from_start) {
|
||||
(Some(layer_path), true) => Operation::PushFrontManipulatorGroup {
|
||||
layer_path: layer_path.clone(),
|
||||
manipulator_group,
|
||||
}
|
||||
.into(),
|
||||
(Some(layer_path), false) => Operation::PushManipulatorGroup {
|
||||
layer_path: layer_path.clone(),
|
||||
manipulator_group,
|
||||
}
|
||||
.into(),
|
||||
(None, _) => Message::NoOp,
|
||||
}
|
||||
/// Pushes a [ManipulatorGroup] to the current layer via a [GraphOperationMessage].
|
||||
fn add_manipulator_group(layer_path: &Option<Vec<LayerId>>, from_start: bool, manipulator_group: bezier_rs::ManipulatorGroup<ManipulatorGroupId>) -> Message {
|
||||
let Some(layer) = layer_path.clone() else {
|
||||
return Message::NoOp;
|
||||
};
|
||||
let modification = if from_start {
|
||||
VectorDataModification::AddStartManipulatorGroup { subpath_index: 0, manipulator_group }
|
||||
} else {
|
||||
VectorDataModification::AddEndManipulatorGroup { subpath_index: 0, manipulator_group }
|
||||
};
|
||||
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<(&[LayerId], bool)> {
|
||||
fn should_extend(document: &DocumentMessageHandler, pos: DVec2, tolerance: f64) -> Option<(&[LayerId], usize, bool)> {
|
||||
let mut best = None;
|
||||
let mut best_distance_squared = tolerance * tolerance;
|
||||
|
||||
for layer_path in document.selected_layers() {
|
||||
(|| {
|
||||
let viewspace = document.document_legacy.generate_transform_relative_to_viewport(layer_path).ok()?;
|
||||
let Ok(viewspace) = document.document_legacy.generate_transform_relative_to_viewport(layer_path) else { continue };
|
||||
|
||||
let subpath = document.document_legacy.layer(layer_path).ok().and_then(|layer| layer.as_subpath())?;
|
||||
let (_first_id, first) = subpath.manipulator_groups().enumerate().next()?;
|
||||
let (_last_id, last) = subpath.manipulator_groups().enumerate().next_back()?;
|
||||
|
||||
if !last.is_close() {
|
||||
for (manipulator_group, from_start) in [(first, true), (last, false)] {
|
||||
if let Some(point) = &manipulator_group.points[ManipulatorType::Anchor] {
|
||||
let distance_squared = viewspace.transform_point2(point.position).distance_squared(pos);
|
||||
|
||||
if distance_squared < best_distance_squared {
|
||||
best = Some((layer_path, from_start));
|
||||
best_distance_squared = distance_squared;
|
||||
}
|
||||
}
|
||||
}
|
||||
let Some(vector_data) = document.document_legacy.layer(layer_path).ok().and_then(|layer| layer.as_vector_data()) else { continue };
|
||||
for (subpath_index, subpath) in vector_data.subpaths.iter().enumerate() {
|
||||
if subpath.closed() {
|
||||
continue;
|
||||
}
|
||||
|
||||
None::<()>
|
||||
})();
|
||||
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_path, subpath_index, from_start));
|
||||
best_distance_squared = distance_squared;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
best
|
||||
|
||||
@@ -2,14 +2,13 @@ use crate::messages::frontend::utility_types::MouseCursorIcon;
|
||||
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion};
|
||||
use crate::messages::layout::utility_types::layout_widget::PropertyHolder;
|
||||
use crate::messages::prelude::*;
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||
use crate::messages::tool::common_functionality::resize::Resize;
|
||||
use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType};
|
||||
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
|
||||
|
||||
use document_legacy::layers::style;
|
||||
use document_legacy::Operation;
|
||||
|
||||
use glam::DAffine2;
|
||||
use glam::DVec2;
|
||||
use graphene_core::vector::style::Fill;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Default)]
|
||||
@@ -37,8 +36,8 @@ pub enum RectangleToolMessage {
|
||||
|
||||
impl PropertyHolder for RectangleTool {}
|
||||
|
||||
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for RectangleTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: ToolActionHandlerData<'a>) {
|
||||
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for RectangleTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
|
||||
self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &(), responses, true);
|
||||
}
|
||||
|
||||
@@ -100,7 +99,13 @@ impl Fsm for RectangleToolFsmState {
|
||||
self,
|
||||
event: ToolMessage,
|
||||
tool_data: &mut Self::ToolData,
|
||||
(document, _document_id, global_tool_data, input, render_data): ToolActionHandlerData,
|
||||
ToolActionHandlerData {
|
||||
document,
|
||||
global_tool_data,
|
||||
input,
|
||||
render_data,
|
||||
..
|
||||
}: &mut ToolActionHandlerData,
|
||||
_tool_options: &Self::ToolOptions,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) -> Self {
|
||||
@@ -113,19 +118,17 @@ impl Fsm for RectangleToolFsmState {
|
||||
match (self, event) {
|
||||
(Ready, DragStart) => {
|
||||
shape_data.start(responses, document, input, render_data);
|
||||
responses.push_back(DocumentMessage::StartTransaction.into());
|
||||
shape_data.path = Some(document.get_path_for_new_layer());
|
||||
responses.push_back(DocumentMessage::DeselectAllLayers.into());
|
||||
|
||||
responses.push_back(
|
||||
Operation::AddRect {
|
||||
path: shape_data.path.clone().unwrap(),
|
||||
insert_index: -1,
|
||||
transform: DAffine2::ZERO.to_cols_array(),
|
||||
style: style::PathStyle::new(None, style::Fill::solid(global_tool_data.primary_color)),
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
let subpath = bezier_rs::Subpath::new_rect(DVec2::ZERO, DVec2::ONE);
|
||||
|
||||
let layer_path = document.get_path_for_new_layer();
|
||||
responses.push_back(DocumentMessage::StartTransaction.into());
|
||||
shape_data.path = Some(layer_path.clone());
|
||||
graph_modification_utils::new_vector_layer(vec![subpath], layer_path.clone(), responses);
|
||||
responses.add(GraphOperationMessage::FillSet {
|
||||
layer: layer_path,
|
||||
fill: Fill::solid(global_tool_data.primary_color),
|
||||
});
|
||||
|
||||
Drawing
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ use crate::messages::tool::common_functionality::transformation_cage::*;
|
||||
use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType};
|
||||
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
|
||||
|
||||
use document_legacy::boolean_ops::BooleanOperation;
|
||||
use document_legacy::document::Document;
|
||||
use document_legacy::intersection::Quad;
|
||||
use document_legacy::layers::layer_info::{Layer, LayerDataType};
|
||||
@@ -219,24 +218,24 @@ impl PropertyHolder for SelectTool {
|
||||
})),
|
||||
Separator::new(SeparatorDirection::Horizontal, SeparatorType::Section).widget_holder(),
|
||||
IconButton::new("BooleanUnion", 24)
|
||||
.tooltip("Boolean Union")
|
||||
.on_update(|_| DocumentMessage::BooleanOperation(BooleanOperation::Union).into())
|
||||
.tooltip("Boolean Union (coming soon)")
|
||||
.on_update(|_| DialogMessage::RequestComingSoonDialog { issue: Some(1091) }.into())
|
||||
.widget_holder(),
|
||||
IconButton::new("BooleanSubtractFront", 24)
|
||||
.tooltip("Boolean Subtract Front")
|
||||
.on_update(|_| DocumentMessage::BooleanOperation(BooleanOperation::SubtractFront).into())
|
||||
.tooltip("Boolean Subtract Front (coming soon)")
|
||||
.on_update(|_| DialogMessage::RequestComingSoonDialog { issue: Some(1091) }.into())
|
||||
.widget_holder(),
|
||||
IconButton::new("BooleanSubtractBack", 24)
|
||||
.tooltip("Boolean Subtract Back")
|
||||
.on_update(|_| DocumentMessage::BooleanOperation(BooleanOperation::SubtractBack).into())
|
||||
.tooltip("Boolean Subtract Back (coming soon)")
|
||||
.on_update(|_| DialogMessage::RequestComingSoonDialog { issue: Some(1091) }.into())
|
||||
.widget_holder(),
|
||||
IconButton::new("BooleanIntersect", 24)
|
||||
.tooltip("Boolean Intersect")
|
||||
.on_update(|_| DocumentMessage::BooleanOperation(BooleanOperation::Intersection).into())
|
||||
.tooltip("Boolean Intersect (coming soon)")
|
||||
.on_update(|_| DialogMessage::RequestComingSoonDialog { issue: Some(1091) }.into())
|
||||
.widget_holder(),
|
||||
IconButton::new("BooleanDifference", 24)
|
||||
.tooltip("Boolean Difference")
|
||||
.on_update(|_| DocumentMessage::BooleanOperation(BooleanOperation::Difference).into())
|
||||
.tooltip("Boolean Difference (coming soon)")
|
||||
.on_update(|_| DialogMessage::RequestComingSoonDialog { issue: Some(1091) }.into())
|
||||
.widget_holder(),
|
||||
WidgetHolder::related_separator(),
|
||||
PopoverButton::new("Boolean", "Coming soon").widget_holder(),
|
||||
@@ -245,8 +244,8 @@ impl PropertyHolder for SelectTool {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for SelectTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: ToolActionHandlerData<'a>) {
|
||||
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for SelectTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
|
||||
if let ToolMessage::Select(SelectToolMessage::SelectOptions(SelectOptionsUpdate::NestedSelectionBehavior(nested_selection_behavior))) = message {
|
||||
self.tool_data.nested_selection_behavior = nested_selection_behavior;
|
||||
responses.push_back(ToolMessage::UpdateHints.into());
|
||||
@@ -345,9 +344,10 @@ impl SelectToolData {
|
||||
for layer_path in Document::shallowest_unique_layers(self.layers_dragging.iter_mut()) {
|
||||
// Moves the original back to its starting position.
|
||||
responses.push_front(
|
||||
Operation::TransformLayerInViewport {
|
||||
path: layer_path.clone(),
|
||||
transform: DAffine2::from_translation(self.drag_start - self.drag_current).to_cols_array(),
|
||||
GraphOperationMessage::TransformChange {
|
||||
layer: layer_path.clone(),
|
||||
transform: DAffine2::from_translation(self.drag_start - self.drag_current),
|
||||
transform_in: TransformIn::Viewport,
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
@@ -401,9 +401,10 @@ impl SelectToolData {
|
||||
// Move the original to under the mouse
|
||||
for layer_path in Document::shallowest_unique_layers(originals.iter()) {
|
||||
responses.push_front(
|
||||
Operation::TransformLayerInViewport {
|
||||
path: layer_path.clone(),
|
||||
transform: DAffine2::from_translation(self.drag_current - self.drag_start).to_cols_array(),
|
||||
GraphOperationMessage::TransformChange {
|
||||
layer: layer_path.clone(),
|
||||
transform: DAffine2::from_translation(self.drag_current - self.drag_start),
|
||||
transform_in: TransformIn::Viewport,
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
@@ -429,7 +430,7 @@ impl Fsm for SelectToolFsmState {
|
||||
self,
|
||||
event: ToolMessage,
|
||||
tool_data: &mut Self::ToolData,
|
||||
(document, _document_id, _global_tool_data, input, render_data): ToolActionHandlerData,
|
||||
ToolActionHandlerData { document, input, render_data, .. }: &mut ToolActionHandlerData,
|
||||
_tool_options: &Self::ToolOptions,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) -> Self {
|
||||
@@ -619,9 +620,10 @@ impl Fsm for SelectToolFsmState {
|
||||
// TODO: Cache the result of `shallowest_unique_layers` to avoid this heavy computation every frame of movement, see https://github.com/GraphiteEditor/Graphite/pull/481
|
||||
for path in Document::shallowest_unique_layers(tool_data.layers_dragging.iter()) {
|
||||
responses.push_front(
|
||||
Operation::TransformLayerInViewport {
|
||||
path: path.to_vec(),
|
||||
transform: DAffine2::from_translation(mouse_delta + closest_move).to_cols_array(),
|
||||
GraphOperationMessage::TransformChange {
|
||||
layer: path.to_vec(),
|
||||
transform: DAffine2::from_translation(mouse_delta + closest_move),
|
||||
transform_in: TransformIn::Viewport,
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
|
||||
@@ -3,14 +3,13 @@ use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMot
|
||||
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, WidgetLayout};
|
||||
use crate::messages::layout::utility_types::widgets::input_widgets::NumberInput;
|
||||
use crate::messages::prelude::*;
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||
use crate::messages::tool::common_functionality::resize::Resize;
|
||||
use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType};
|
||||
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
|
||||
|
||||
use document_legacy::layers::style;
|
||||
use document_legacy::Operation;
|
||||
|
||||
use glam::DAffine2;
|
||||
use glam::DVec2;
|
||||
use graphene_core::vector::style::Fill;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Default)]
|
||||
@@ -80,8 +79,8 @@ impl PropertyHolder for ShapeTool {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for ShapeTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: ToolActionHandlerData<'a>) {
|
||||
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for ShapeTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
|
||||
if let ToolMessage::Shape(ShapeToolMessage::UpdateOptions(action)) = message {
|
||||
match action {
|
||||
ShapeOptionsUpdate::Vertices(vertices) => self.options.vertices = vertices,
|
||||
@@ -139,7 +138,13 @@ impl Fsm for ShapeToolFsmState {
|
||||
self,
|
||||
event: ToolMessage,
|
||||
tool_data: &mut Self::ToolData,
|
||||
(document, _document_id, global_tool_data, input, render_data): ToolActionHandlerData,
|
||||
ToolActionHandlerData {
|
||||
document,
|
||||
global_tool_data,
|
||||
input,
|
||||
render_data,
|
||||
..
|
||||
}: &mut ToolActionHandlerData,
|
||||
tool_options: &Self::ToolOptions,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) -> Self {
|
||||
@@ -153,20 +158,16 @@ impl Fsm for ShapeToolFsmState {
|
||||
(Ready, DragStart) => {
|
||||
shape_data.start(responses, document, input, render_data);
|
||||
responses.push_back(DocumentMessage::StartTransaction.into());
|
||||
shape_data.path = Some(document.get_path_for_new_layer());
|
||||
responses.push_back(DocumentMessage::DeselectAllLayers.into());
|
||||
let layer_path = document.get_path_for_new_layer();
|
||||
shape_data.path = Some(layer_path.clone());
|
||||
tool_data.sides = tool_options.vertices;
|
||||
|
||||
responses.push_back(
|
||||
Operation::AddNgon {
|
||||
path: shape_data.path.clone().unwrap(),
|
||||
insert_index: -1,
|
||||
transform: DAffine2::ZERO.to_cols_array(),
|
||||
sides: tool_data.sides,
|
||||
style: style::PathStyle::new(None, style::Fill::solid(global_tool_data.primary_color)),
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
let subpath = bezier_rs::Subpath::new_regular_polygon(DVec2::ZERO, tool_data.sides as u64, 1.);
|
||||
graph_modification_utils::new_vector_layer(vec![subpath], layer_path.clone(), responses);
|
||||
responses.add(GraphOperationMessage::FillSet {
|
||||
layer: layer_path,
|
||||
fill: Fill::solid(global_tool_data.primary_color),
|
||||
});
|
||||
|
||||
Drawing
|
||||
}
|
||||
|
||||
@@ -4,15 +4,15 @@ use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMot
|
||||
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, WidgetLayout};
|
||||
use crate::messages::layout::utility_types::widgets::input_widgets::NumberInput;
|
||||
use crate::messages::prelude::*;
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||
use crate::messages::tool::common_functionality::snapping::SnapManager;
|
||||
use crate::messages::tool::utility_types::{DocumentToolData, EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType};
|
||||
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
|
||||
|
||||
use document_legacy::layers::style;
|
||||
use document_legacy::LayerId;
|
||||
use document_legacy::Operation;
|
||||
use document_legacy::{LayerId, Operation};
|
||||
use graphene_core::vector::style::Stroke;
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
use glam::DVec2;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Default)]
|
||||
@@ -86,8 +86,8 @@ impl PropertyHolder for SplineTool {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for SplineTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: ToolActionHandlerData<'a>) {
|
||||
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for SplineTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
|
||||
if let ToolMessage::Spline(SplineToolMessage::UpdateOptions(action)) = message {
|
||||
match action {
|
||||
SplineOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight,
|
||||
@@ -146,7 +146,13 @@ impl Fsm for SplineToolFsmState {
|
||||
self,
|
||||
event: ToolMessage,
|
||||
tool_data: &mut Self::ToolData,
|
||||
(document, _document_id, global_tool_data, input, render_data): ToolActionHandlerData,
|
||||
ToolActionHandlerData {
|
||||
document,
|
||||
global_tool_data,
|
||||
input,
|
||||
render_data,
|
||||
..
|
||||
}: &mut ToolActionHandlerData,
|
||||
tool_options: &Self::ToolOptions,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) -> Self {
|
||||
@@ -173,7 +179,7 @@ impl Fsm for SplineToolFsmState {
|
||||
|
||||
tool_data.weight = tool_options.line_weight;
|
||||
|
||||
responses.push_back(add_spline(tool_data, global_tool_data, true));
|
||||
add_spline(tool_data, global_tool_data, true, responses);
|
||||
|
||||
Drawing
|
||||
}
|
||||
@@ -189,7 +195,7 @@ impl Fsm for SplineToolFsmState {
|
||||
}
|
||||
|
||||
responses.push_back(remove_preview(tool_data));
|
||||
responses.push_back(add_spline(tool_data, global_tool_data, true));
|
||||
add_spline(tool_data, global_tool_data, true, responses);
|
||||
|
||||
Drawing
|
||||
}
|
||||
@@ -199,14 +205,14 @@ impl Fsm for SplineToolFsmState {
|
||||
tool_data.next_point = pos;
|
||||
|
||||
responses.push_back(remove_preview(tool_data));
|
||||
responses.push_back(add_spline(tool_data, global_tool_data, true));
|
||||
add_spline(tool_data, global_tool_data, true, responses);
|
||||
|
||||
Drawing
|
||||
}
|
||||
(Drawing, Confirm) | (Drawing, Abort) => {
|
||||
if tool_data.points.len() >= 2 {
|
||||
responses.push_back(remove_preview(tool_data));
|
||||
responses.push_back(add_spline(tool_data, global_tool_data, false));
|
||||
add_spline(tool_data, global_tool_data, false, responses);
|
||||
responses.push_back(DocumentMessage::CommitTransaction.into());
|
||||
} else {
|
||||
responses.push_back(DocumentMessage::AbortTransaction.into());
|
||||
@@ -249,18 +255,18 @@ fn remove_preview(tool_data: &SplineToolData) -> Message {
|
||||
.into()
|
||||
}
|
||||
|
||||
fn add_spline(tool_data: &SplineToolData, global_tool_data: &DocumentToolData, show_preview: bool) -> Message {
|
||||
let mut points: Vec<(f64, f64)> = tool_data.points.iter().map(|p| (p.x, p.y)).collect();
|
||||
fn add_spline(tool_data: &SplineToolData, global_tool_data: &DocumentToolData, show_preview: bool, responses: &mut VecDeque<Message>) {
|
||||
let mut points = tool_data.points.clone();
|
||||
if show_preview {
|
||||
points.push((tool_data.next_point.x, tool_data.next_point.y))
|
||||
points.push(tool_data.next_point)
|
||||
}
|
||||
|
||||
Operation::AddSpline {
|
||||
path: tool_data.path.clone().unwrap(),
|
||||
insert_index: -1,
|
||||
transform: DAffine2::IDENTITY.to_cols_array(),
|
||||
points,
|
||||
style: style::PathStyle::new(Some(style::Stroke::new(global_tool_data.primary_color, tool_data.weight)), style::Fill::None),
|
||||
}
|
||||
.into()
|
||||
let subpath = bezier_rs::Subpath::new_cubic_spline(points);
|
||||
|
||||
let layer_path = tool_data.path.clone().unwrap();
|
||||
graph_modification_utils::new_vector_layer(vec![subpath], layer_path.clone(), responses);
|
||||
responses.add(GraphOperationMessage::StrokeSet {
|
||||
layer: layer_path.clone(),
|
||||
stroke: Stroke::new(global_tool_data.primary_color, tool_data.weight),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -127,8 +127,8 @@ impl PropertyHolder for TextTool {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for TextTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: ToolActionHandlerData<'a>) {
|
||||
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for TextTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
|
||||
if let ToolMessage::Text(TextToolMessage::UpdateOptions(action)) = message {
|
||||
match action {
|
||||
TextOptionsUpdate::Font { family, style } => {
|
||||
@@ -266,7 +266,13 @@ impl Fsm for TextToolFsmState {
|
||||
self,
|
||||
event: ToolMessage,
|
||||
tool_data: &mut Self::ToolData,
|
||||
(document, _document_id, global_tool_data, input, render_data): ToolActionHandlerData,
|
||||
ToolActionHandlerData {
|
||||
document,
|
||||
global_tool_data,
|
||||
input,
|
||||
render_data,
|
||||
..
|
||||
}: &mut ToolActionHandlerData,
|
||||
tool_options: &Self::ToolOptions,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) -> Self {
|
||||
@@ -300,7 +306,7 @@ impl Fsm for TextToolFsmState {
|
||||
else if state == TextToolFsmState::Ready {
|
||||
responses.push_back(DocumentMessage::StartTransaction.into());
|
||||
|
||||
let transform = DAffine2::from_translation(input.mouse.position).to_cols_array();
|
||||
let transform = DAffine2::from_translation(input.mouse.position);
|
||||
let font_size = tool_options.font_size;
|
||||
let font_name = tool_options.font_name.clone();
|
||||
let font_style = tool_options.font_style.clone();
|
||||
@@ -320,9 +326,10 @@ impl Fsm for TextToolFsmState {
|
||||
.into(),
|
||||
);
|
||||
responses.push_back(
|
||||
Operation::SetLayerTransformInViewport {
|
||||
path: tool_data.layer_path.clone(),
|
||||
GraphOperationMessage::TransformSet {
|
||||
layer: tool_data.layer_path.clone(),
|
||||
transform,
|
||||
transform_in: TransformIn::Viewport,
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user