mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-10-01 06:48:11 +08:00
Initial work migrating vector layers to document graph
* Fix pen tool (except overlays) * Thumbnail of only the layer and not the composite * Fix occasional transform breakages * Constrain size of thumbnail * Insert new layers at the top * Broken layer tree * Fix crash when drawing * Reduce calls to send graph * Reduce calls to updating properties * Store cached transforms upon the document * Fix missing node UI updates * Fix fill tool and clean up imports and indentation * Error on overide existing layer * Fix pen tool (partially) * Fix some lints
This commit is contained in:
committed by
Keavon Chambers
parent
fc6cee372a
commit
4cd72edb64
@@ -1,20 +1,15 @@
|
||||
use super::tool_prelude::*;
|
||||
use crate::application::generate_uuid;
|
||||
use crate::consts::SELECTION_TOLERANCE;
|
||||
use crate::messages::frontend::utility_types::MouseCursorIcon;
|
||||
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion};
|
||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||
use crate::messages::portfolio::document::utility_types::misc::TargetDocument;
|
||||
use crate::messages::prelude::*;
|
||||
use crate::messages::tool::common_functionality::snapping::SnapManager;
|
||||
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::intersection::Quad;
|
||||
use document_legacy::LayerId;
|
||||
|
||||
use glam::{DVec2, IVec2, Vec2Swizzles};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use document_legacy::layers::RenderData;
|
||||
use glam::{IVec2, Vec2Swizzles};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ArtboardTool {
|
||||
@@ -108,306 +103,317 @@ struct ArtboardToolData {
|
||||
drag_current: DVec2,
|
||||
}
|
||||
|
||||
impl ArtboardToolData {
|
||||
fn refresh_overlays(&mut self, document: &DocumentMessageHandler, render_data: &RenderData, responses: &mut VecDeque<Message>) {
|
||||
let current_artboard = self.selected_artboard.and_then(|path| document.artboard_bounding_box_and_transform(&[path], render_data));
|
||||
match (current_artboard, self.bounding_box_overlays.take()) {
|
||||
(None, Some(bounding_box_overlays)) => bounding_box_overlays.delete(responses),
|
||||
(Some((bounds, transform)), paths) => {
|
||||
let mut bounding_box_overlays = paths.unwrap_or_else(|| BoundingBoxOverlays::new(responses));
|
||||
|
||||
bounding_box_overlays.bounds = bounds;
|
||||
bounding_box_overlays.transform = transform;
|
||||
|
||||
bounding_box_overlays.transform(responses);
|
||||
|
||||
self.bounding_box_overlays = Some(bounding_box_overlays);
|
||||
|
||||
responses.add(OverlaysMessage::Rerender);
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
|
||||
fn check_dragging_bounds(&mut self, cursor: DVec2) -> Option<(bool, bool, bool, bool)> {
|
||||
let bounding_box = self.bounding_box_overlays.as_mut()?;
|
||||
let edges = bounding_box.check_selected_edges(cursor)?;
|
||||
let (top, bottom, left, right) = edges;
|
||||
let selected_edges = SelectedEdges::new(top, bottom, left, right, bounding_box.bounds);
|
||||
bounding_box.opposite_pivot = selected_edges.calculate_pivot();
|
||||
|
||||
Some(edges)
|
||||
}
|
||||
|
||||
fn start_resizing(&mut self, selected_edges: (bool, bool, bool, bool), document: &DocumentMessageHandler, render_data: &RenderData, input: &InputPreprocessorMessageHandler) {
|
||||
let snap_x = selected_edges.2 || selected_edges.3;
|
||||
let snap_y = selected_edges.0 || selected_edges.1;
|
||||
|
||||
let artboard = self.selected_artboard.unwrap();
|
||||
self.snap_manager
|
||||
.start_snap(document, input, document.bounding_boxes(None, Some(artboard), render_data), snap_x, snap_y);
|
||||
self.snap_manager.add_all_document_handles(document, input, &[], &[], &[]);
|
||||
|
||||
if let Some(bounds) = &mut self.bounding_box_overlays {
|
||||
let pivot = document.artboard_message_handler.artboards_document.pivot(&[artboard], render_data).unwrap_or_default();
|
||||
let root = document.document_legacy.metadata.document_to_viewport;
|
||||
let pivot = root.inverse().transform_point2(pivot);
|
||||
bounds.center_of_transformation = pivot;
|
||||
}
|
||||
}
|
||||
|
||||
fn select_artboard(&mut self, document: &DocumentMessageHandler, render_data: &RenderData, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) -> bool {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
let tolerance = DVec2::splat(SELECTION_TOLERANCE);
|
||||
let quad = Quad::from_box([input.mouse.position - tolerance, input.mouse.position + tolerance]);
|
||||
let intersection = document.artboard_message_handler.artboards_document.intersects_quad_root(quad, render_data);
|
||||
|
||||
responses.add(BroadcastEvent::DocumentIsDirty);
|
||||
if let Some(intersection) = intersection.last() {
|
||||
self.selected_artboard = Some(intersection[0]);
|
||||
|
||||
self.snap_manager
|
||||
.start_snap(document, input, document.bounding_boxes(None, Some(intersection[0]), render_data), true, true);
|
||||
self.snap_manager.add_all_document_handles(document, input, &[], &[], &[]);
|
||||
|
||||
true
|
||||
} else {
|
||||
self.selected_artboard = None;
|
||||
|
||||
responses.add(PropertiesPanelMessage::ClearSelection);
|
||||
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn resize_artboard(&mut self, responses: &mut VecDeque<Message>, document: &DocumentMessageHandler, mouse_position: DVec2, from_center: bool, constrain_square: bool) {
|
||||
let Some(bounds) = &self.bounding_box_overlays else {
|
||||
return;
|
||||
};
|
||||
let Some(movement) = &bounds.selected_edges else {
|
||||
return;
|
||||
};
|
||||
|
||||
let snapped_mouse_position: DVec2 = self.snap_manager.snap_position(responses, document, mouse_position);
|
||||
|
||||
let (position, size) = movement.new_size(snapped_mouse_position, bounds.transform, from_center, bounds.center_of_transformation, constrain_square);
|
||||
responses.add(ArtboardMessage::ResizeArtboard {
|
||||
artboard: self.selected_artboard.unwrap(),
|
||||
position: position.round().into(),
|
||||
size: size.round().into(),
|
||||
});
|
||||
responses.add(GraphOperationMessage::ResizeArtboard {
|
||||
id: self.selected_artboard.unwrap(),
|
||||
location: position.round().as_ivec2(),
|
||||
dimensions: size.round().as_ivec2(),
|
||||
});
|
||||
|
||||
responses.add(BroadcastEvent::DocumentIsDirty);
|
||||
}
|
||||
}
|
||||
|
||||
impl Fsm for ArtboardToolFsmState {
|
||||
type ToolData = ArtboardToolData;
|
||||
type ToolOptions = ();
|
||||
|
||||
fn transition(
|
||||
self,
|
||||
event: ToolMessage,
|
||||
tool_data: &mut Self::ToolData,
|
||||
ToolActionHandlerData { document, input, render_data, .. }: &mut ToolActionHandlerData,
|
||||
_tool_options: &Self::ToolOptions,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) -> Self {
|
||||
if let ToolMessage::Artboard(event) = event {
|
||||
match (self, event) {
|
||||
(state, ArtboardToolMessage::DocumentIsDirty) if state != ArtboardToolFsmState::Drawing => {
|
||||
let current_artboard = tool_data.selected_artboard.and_then(|path| document.artboard_bounding_box_and_transform(&[path], render_data));
|
||||
match (current_artboard, tool_data.bounding_box_overlays.take()) {
|
||||
(None, Some(bounding_box_overlays)) => bounding_box_overlays.delete(responses),
|
||||
(Some((bounds, transform)), paths) => {
|
||||
let mut bounding_box_overlays = paths.unwrap_or_else(|| BoundingBoxOverlays::new(responses));
|
||||
fn transition(self, event: ToolMessage, tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionHandlerData, _tool_options: &(), responses: &mut VecDeque<Message>) -> Self {
|
||||
let ToolActionHandlerData { document, input, render_data, .. } = tool_action_data;
|
||||
|
||||
bounding_box_overlays.bounds = bounds;
|
||||
bounding_box_overlays.transform = transform;
|
||||
let ToolMessage::Artboard(event) = event else {
|
||||
return self;
|
||||
};
|
||||
|
||||
bounding_box_overlays.transform(responses);
|
||||
match (self, event) {
|
||||
(state, ArtboardToolMessage::DocumentIsDirty) if state != ArtboardToolFsmState::Drawing => {
|
||||
tool_data.refresh_overlays(document, render_data, responses);
|
||||
|
||||
tool_data.bounding_box_overlays = Some(bounding_box_overlays);
|
||||
self
|
||||
}
|
||||
(ArtboardToolFsmState::Ready, ArtboardToolMessage::PointerDown) => {
|
||||
tool_data.drag_start = input.mouse.position;
|
||||
tool_data.drag_current = input.mouse.position;
|
||||
|
||||
responses.add(OverlaysMessage::Rerender);
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
self
|
||||
}
|
||||
(ArtboardToolFsmState::Ready, ArtboardToolMessage::PointerDown) => {
|
||||
tool_data.drag_start = input.mouse.position;
|
||||
tool_data.drag_current = input.mouse.position;
|
||||
if let Some(selected_edges) = tool_data.check_dragging_bounds(input.mouse.position) {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
tool_data.start_resizing(selected_edges, document, render_data, input);
|
||||
|
||||
let dragging_bounds = if let Some(bounding_box) = &mut tool_data.bounding_box_overlays {
|
||||
let edges = bounding_box.check_selected_edges(input.mouse.position);
|
||||
|
||||
bounding_box.selected_edges = edges.map(|(top, bottom, left, right)| {
|
||||
let edges = SelectedEdges::new(top, bottom, left, right, bounding_box.bounds);
|
||||
bounding_box.opposite_pivot = edges.calculate_pivot();
|
||||
|
||||
edges
|
||||
});
|
||||
|
||||
edges
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if let Some(selected_edges) = dragging_bounds {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
|
||||
let snap_x = selected_edges.2 || selected_edges.3;
|
||||
let snap_y = selected_edges.0 || selected_edges.1;
|
||||
|
||||
let artboard = tool_data.selected_artboard.unwrap();
|
||||
tool_data
|
||||
.snap_manager
|
||||
.start_snap(document, input, document.bounding_boxes(None, Some(artboard), render_data), snap_x, snap_y);
|
||||
tool_data.snap_manager.add_all_document_handles(document, input, &[], &[], &[]);
|
||||
|
||||
if let Some(bounds) = &mut tool_data.bounding_box_overlays {
|
||||
let pivot = document.artboard_message_handler.artboards_document.pivot(&[artboard], render_data).unwrap_or_default();
|
||||
let root = document.document_legacy.root.transform;
|
||||
let pivot = root.inverse().transform_point2(pivot);
|
||||
bounds.center_of_transformation = pivot;
|
||||
}
|
||||
|
||||
ArtboardToolFsmState::ResizingBounds
|
||||
} else {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
let tolerance = DVec2::splat(SELECTION_TOLERANCE);
|
||||
let quad = Quad::from_box([input.mouse.position - tolerance, input.mouse.position + tolerance]);
|
||||
let intersection = document.artboard_message_handler.artboards_document.intersects_quad_root(quad, render_data);
|
||||
|
||||
responses.add(BroadcastEvent::DocumentIsDirty);
|
||||
if let Some(intersection) = intersection.last() {
|
||||
tool_data.selected_artboard = Some(intersection[0]);
|
||||
|
||||
tool_data
|
||||
.snap_manager
|
||||
.start_snap(document, input, document.bounding_boxes(None, Some(intersection[0]), render_data), true, true);
|
||||
tool_data.snap_manager.add_all_document_handles(document, input, &[], &[], &[]);
|
||||
|
||||
ArtboardToolFsmState::Dragging
|
||||
} else {
|
||||
tool_data.selected_artboard = None;
|
||||
|
||||
responses.add(PropertiesPanelMessage::ClearSelection);
|
||||
|
||||
ArtboardToolFsmState::Drawing
|
||||
}
|
||||
}
|
||||
}
|
||||
(ArtboardToolFsmState::ResizingBounds, ArtboardToolMessage::PointerMove { constrain_axis_or_aspect, center }) => {
|
||||
if let Some(bounds) = &tool_data.bounding_box_overlays {
|
||||
if let Some(movement) = &bounds.selected_edges {
|
||||
let from_center = input.keyboard.get(center as usize);
|
||||
let constrain_square = input.keyboard.get(constrain_axis_or_aspect as usize);
|
||||
|
||||
let mouse_position = input.mouse.position;
|
||||
let snapped_mouse_position = tool_data.snap_manager.snap_position(responses, document, mouse_position);
|
||||
|
||||
let (position, size) = movement.new_size(snapped_mouse_position, bounds.transform, from_center, bounds.center_of_transformation, constrain_square);
|
||||
responses.add(ArtboardMessage::ResizeArtboard {
|
||||
artboard: tool_data.selected_artboard.unwrap(),
|
||||
position: position.round().into(),
|
||||
size: size.round().into(),
|
||||
});
|
||||
responses.add(GraphOperationMessage::ResizeArtboard {
|
||||
id: tool_data.selected_artboard.unwrap(),
|
||||
location: position.round().as_ivec2(),
|
||||
dimensions: size.round().as_ivec2(),
|
||||
});
|
||||
|
||||
responses.add(BroadcastEvent::DocumentIsDirty);
|
||||
}
|
||||
}
|
||||
ArtboardToolFsmState::ResizingBounds
|
||||
}
|
||||
(ArtboardToolFsmState::Dragging, ArtboardToolMessage::PointerMove { constrain_axis_or_aspect, .. }) => {
|
||||
if let Some(bounds) = &tool_data.bounding_box_overlays {
|
||||
let axis_align = input.keyboard.get(constrain_axis_or_aspect as usize);
|
||||
|
||||
let mouse_position = axis_align_drag(axis_align, input.mouse.position, tool_data.drag_start);
|
||||
let mouse_delta = mouse_position - tool_data.drag_current;
|
||||
|
||||
let snap = bounds.evaluate_transform_handle_positions().into_iter().collect();
|
||||
let closest_move = tool_data.snap_manager.snap_layers(responses, document, snap, mouse_delta);
|
||||
|
||||
let size = bounds.bounds[1] - bounds.bounds[0];
|
||||
|
||||
let position = bounds.bounds[0] + bounds.transform.inverse().transform_vector2(mouse_position - tool_data.drag_current + closest_move);
|
||||
|
||||
responses.add(ArtboardMessage::ResizeArtboard {
|
||||
artboard: tool_data.selected_artboard.unwrap(),
|
||||
position: position.round().into(),
|
||||
size: size.round().into(),
|
||||
});
|
||||
responses.add(GraphOperationMessage::ResizeArtboard {
|
||||
id: tool_data.selected_artboard.unwrap(),
|
||||
location: position.round().as_ivec2(),
|
||||
dimensions: size.round().as_ivec2(),
|
||||
});
|
||||
|
||||
responses.add(BroadcastEvent::DocumentIsDirty);
|
||||
|
||||
tool_data.drag_current = mouse_position + closest_move;
|
||||
}
|
||||
} else if tool_data.select_artboard(document, render_data, input, responses) {
|
||||
ArtboardToolFsmState::Dragging
|
||||
}
|
||||
(ArtboardToolFsmState::Drawing, ArtboardToolMessage::PointerMove { constrain_axis_or_aspect, center }) => {
|
||||
let mouse_position = input.mouse.position;
|
||||
let snapped_mouse_position = tool_data.snap_manager.snap_position(responses, document, mouse_position);
|
||||
|
||||
let root_transform = document.document_legacy.root.transform.inverse();
|
||||
|
||||
let mut start = tool_data.drag_start;
|
||||
let mut size = snapped_mouse_position - start;
|
||||
// Constrain axis
|
||||
if input.keyboard.get(constrain_axis_or_aspect as usize) {
|
||||
size = size.abs().max(size.abs().yx()) * size.signum();
|
||||
}
|
||||
// From center
|
||||
if input.keyboard.get(center as usize) {
|
||||
start -= size;
|
||||
size *= 2.;
|
||||
}
|
||||
|
||||
let start = root_transform.transform_point2(start);
|
||||
let size = root_transform.transform_vector2(size);
|
||||
|
||||
if let Some(artboard) = tool_data.selected_artboard {
|
||||
responses.add(ArtboardMessage::ResizeArtboard {
|
||||
artboard,
|
||||
position: start.round().into(),
|
||||
size: size.round().into(),
|
||||
});
|
||||
responses.add(GraphOperationMessage::ResizeArtboard {
|
||||
id: tool_data.selected_artboard.unwrap(),
|
||||
location: start.round().as_ivec2(),
|
||||
dimensions: size.round().as_ivec2(),
|
||||
});
|
||||
} else {
|
||||
let id = generate_uuid();
|
||||
tool_data.selected_artboard = Some(id);
|
||||
|
||||
tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(None, Some(id), render_data), true, true);
|
||||
tool_data.snap_manager.add_all_document_handles(document, input, &[], &[], &[]);
|
||||
|
||||
responses.add(ArtboardMessage::AddArtboard {
|
||||
id: Some(id),
|
||||
position: start.round().into(),
|
||||
size: (1., 1.),
|
||||
});
|
||||
responses.add(GraphOperationMessage::NewArtboard {
|
||||
id,
|
||||
artboard: graphene_core::Artboard {
|
||||
graphic_group: graphene_core::GraphicGroup::EMPTY,
|
||||
location: start.round().as_ivec2(),
|
||||
dimensions: IVec2::splat(1),
|
||||
background: graphene_core::Color::WHITE,
|
||||
clip: false,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// Have to put message here instead of when Artboard is created
|
||||
// This might result in a few more calls but it is not reliant on the order of messages
|
||||
|
||||
responses.add(BroadcastEvent::DocumentIsDirty);
|
||||
|
||||
} else {
|
||||
ArtboardToolFsmState::Drawing
|
||||
}
|
||||
(ArtboardToolFsmState::Ready, ArtboardToolMessage::PointerMove { .. }) => {
|
||||
let cursor = tool_data.bounding_box_overlays.as_ref().map_or(MouseCursorIcon::Default, |bounds| bounds.get_cursor(input, false));
|
||||
}
|
||||
(ArtboardToolFsmState::ResizingBounds, ArtboardToolMessage::PointerMove { constrain_axis_or_aspect, center }) => {
|
||||
let from_center = input.keyboard.get(center as usize);
|
||||
let constrain_square = input.keyboard.get(constrain_axis_or_aspect as usize);
|
||||
let mouse_position = input.mouse.position;
|
||||
tool_data.resize_artboard(responses, document, mouse_position, from_center, constrain_square);
|
||||
|
||||
if tool_data.cursor != cursor {
|
||||
tool_data.cursor = cursor;
|
||||
responses.add(FrontendMessage::UpdateMouseCursor { cursor });
|
||||
}
|
||||
ArtboardToolFsmState::ResizingBounds
|
||||
}
|
||||
(ArtboardToolFsmState::Dragging, ArtboardToolMessage::PointerMove { constrain_axis_or_aspect, .. }) => {
|
||||
if let Some(bounds) = &tool_data.bounding_box_overlays {
|
||||
let axis_align = input.keyboard.get(constrain_axis_or_aspect as usize);
|
||||
|
||||
ArtboardToolFsmState::Ready
|
||||
}
|
||||
(ArtboardToolFsmState::ResizingBounds, ArtboardToolMessage::PointerUp) => {
|
||||
tool_data.snap_manager.cleanup(responses);
|
||||
let mouse_position = axis_align_drag(axis_align, input.mouse.position, tool_data.drag_start);
|
||||
let mouse_delta = mouse_position - tool_data.drag_current;
|
||||
|
||||
if let Some(bounds) = &mut tool_data.bounding_box_overlays {
|
||||
bounds.original_transforms.clear();
|
||||
}
|
||||
let snap = bounds.evaluate_transform_handle_positions().into_iter().collect();
|
||||
let closest_move = tool_data.snap_manager.snap_layers(responses, document, snap, mouse_delta);
|
||||
|
||||
ArtboardToolFsmState::Ready
|
||||
}
|
||||
(ArtboardToolFsmState::Drawing, ArtboardToolMessage::PointerUp) => {
|
||||
tool_data.snap_manager.cleanup(responses);
|
||||
let size = bounds.bounds[1] - bounds.bounds[0];
|
||||
|
||||
if let Some(bounds) = &mut tool_data.bounding_box_overlays {
|
||||
bounds.original_transforms.clear();
|
||||
}
|
||||
let position = bounds.bounds[0] + bounds.transform.inverse().transform_vector2(mouse_position - tool_data.drag_current + closest_move);
|
||||
|
||||
responses.add(ArtboardMessage::ResizeArtboard {
|
||||
artboard: tool_data.selected_artboard.unwrap(),
|
||||
position: position.round().into(),
|
||||
size: size.round().into(),
|
||||
});
|
||||
responses.add(GraphOperationMessage::ResizeArtboard {
|
||||
id: tool_data.selected_artboard.unwrap(),
|
||||
location: position.round().as_ivec2(),
|
||||
dimensions: size.round().as_ivec2(),
|
||||
});
|
||||
|
||||
responses.add(BroadcastEvent::DocumentIsDirty);
|
||||
|
||||
ArtboardToolFsmState::Ready
|
||||
tool_data.drag_current = mouse_position + closest_move;
|
||||
}
|
||||
(ArtboardToolFsmState::Dragging, ArtboardToolMessage::PointerUp) => {
|
||||
tool_data.snap_manager.cleanup(responses);
|
||||
|
||||
if let Some(bounds) = &mut tool_data.bounding_box_overlays {
|
||||
bounds.original_transforms.clear();
|
||||
}
|
||||
|
||||
ArtboardToolFsmState::Ready
|
||||
}
|
||||
(_, ArtboardToolMessage::DeleteSelected) => {
|
||||
if let Some(artboard) = tool_data.selected_artboard.take() {
|
||||
responses.add(ArtboardMessage::DeleteArtboard { artboard });
|
||||
responses.add(GraphOperationMessage::DeleteArtboard { id: artboard });
|
||||
|
||||
responses.add(BroadcastEvent::DocumentIsDirty);
|
||||
}
|
||||
ArtboardToolFsmState::Ready
|
||||
}
|
||||
(_, ArtboardToolMessage::NudgeSelected { delta_x, delta_y }) => {
|
||||
if let Some(bounds) = &mut tool_data.bounding_box_overlays {
|
||||
responses.add(ArtboardMessage::ResizeArtboard {
|
||||
artboard: tool_data.selected_artboard.unwrap(),
|
||||
position: (bounds.bounds[0].x + delta_x, bounds.bounds[0].y + delta_y),
|
||||
size: (bounds.bounds[1] - bounds.bounds[0]).round().into(),
|
||||
});
|
||||
responses.add(GraphOperationMessage::ResizeArtboard {
|
||||
id: tool_data.selected_artboard.unwrap(),
|
||||
location: DVec2::new(bounds.bounds[0].x + delta_x, bounds.bounds[0].y + delta_y).round().as_ivec2(),
|
||||
dimensions: (bounds.bounds[1] - bounds.bounds[0]).round().as_ivec2(),
|
||||
});
|
||||
}
|
||||
|
||||
ArtboardToolFsmState::Ready
|
||||
}
|
||||
(_, ArtboardToolMessage::Abort) => {
|
||||
if let Some(bounding_box_overlays) = tool_data.bounding_box_overlays.take() {
|
||||
bounding_box_overlays.delete(responses);
|
||||
}
|
||||
|
||||
// Register properties when switching back to other tools
|
||||
responses.add(PropertiesPanelMessage::SetActiveLayers {
|
||||
paths: document.selected_layers().map(|path| path.to_vec()).collect(),
|
||||
document: TargetDocument::Artwork,
|
||||
});
|
||||
|
||||
tool_data.snap_manager.cleanup(responses);
|
||||
ArtboardToolFsmState::Ready
|
||||
}
|
||||
_ => self,
|
||||
ArtboardToolFsmState::Dragging
|
||||
}
|
||||
} else {
|
||||
self
|
||||
(ArtboardToolFsmState::Drawing, ArtboardToolMessage::PointerMove { constrain_axis_or_aspect, center }) => {
|
||||
let mouse_position = input.mouse.position;
|
||||
let snapped_mouse_position = tool_data.snap_manager.snap_position(responses, document, mouse_position);
|
||||
|
||||
let root_transform = document.document_legacy.metadata.document_to_viewport.inverse();
|
||||
|
||||
let mut start = tool_data.drag_start;
|
||||
let mut size = snapped_mouse_position - start;
|
||||
// Constrain axis
|
||||
if input.keyboard.get(constrain_axis_or_aspect as usize) {
|
||||
size = size.abs().max(size.abs().yx()) * size.signum();
|
||||
}
|
||||
// From center
|
||||
if input.keyboard.get(center as usize) {
|
||||
start -= size;
|
||||
size *= 2.;
|
||||
}
|
||||
|
||||
let start = root_transform.transform_point2(start);
|
||||
let size = root_transform.transform_vector2(size);
|
||||
|
||||
if let Some(artboard) = tool_data.selected_artboard {
|
||||
responses.add(ArtboardMessage::ResizeArtboard {
|
||||
artboard,
|
||||
position: start.round().into(),
|
||||
size: size.round().into(),
|
||||
});
|
||||
responses.add(GraphOperationMessage::ResizeArtboard {
|
||||
id: tool_data.selected_artboard.unwrap(),
|
||||
location: start.round().as_ivec2(),
|
||||
dimensions: size.round().as_ivec2(),
|
||||
});
|
||||
} else {
|
||||
let id = generate_uuid();
|
||||
tool_data.selected_artboard = Some(id);
|
||||
|
||||
tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(None, Some(id), render_data), true, true);
|
||||
tool_data.snap_manager.add_all_document_handles(document, input, &[], &[], &[]);
|
||||
|
||||
responses.add(ArtboardMessage::AddArtboard {
|
||||
id: Some(id),
|
||||
position: start.round().into(),
|
||||
size: (1., 1.),
|
||||
});
|
||||
responses.add(GraphOperationMessage::NewArtboard {
|
||||
id,
|
||||
artboard: graphene_core::Artboard {
|
||||
graphic_group: graphene_core::GraphicGroup::EMPTY,
|
||||
location: start.round().as_ivec2(),
|
||||
dimensions: IVec2::splat(1),
|
||||
background: graphene_core::Color::WHITE,
|
||||
clip: false,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// Have to put message here instead of when Artboard is created
|
||||
// This might result in a few more calls but it is not reliant on the order of messages
|
||||
|
||||
responses.add(BroadcastEvent::DocumentIsDirty);
|
||||
|
||||
ArtboardToolFsmState::Drawing
|
||||
}
|
||||
(ArtboardToolFsmState::Ready, ArtboardToolMessage::PointerMove { .. }) => {
|
||||
let cursor = tool_data.bounding_box_overlays.as_ref().map_or(MouseCursorIcon::Default, |bounds| bounds.get_cursor(input, false));
|
||||
|
||||
if tool_data.cursor != cursor {
|
||||
tool_data.cursor = cursor;
|
||||
responses.add(FrontendMessage::UpdateMouseCursor { cursor });
|
||||
}
|
||||
|
||||
ArtboardToolFsmState::Ready
|
||||
}
|
||||
(ArtboardToolFsmState::ResizingBounds, ArtboardToolMessage::PointerUp) => {
|
||||
tool_data.snap_manager.cleanup(responses);
|
||||
|
||||
if let Some(bounds) = &mut tool_data.bounding_box_overlays {
|
||||
bounds.original_transforms.clear();
|
||||
}
|
||||
|
||||
ArtboardToolFsmState::Ready
|
||||
}
|
||||
(ArtboardToolFsmState::Drawing, ArtboardToolMessage::PointerUp) => {
|
||||
tool_data.snap_manager.cleanup(responses);
|
||||
|
||||
if let Some(bounds) = &mut tool_data.bounding_box_overlays {
|
||||
bounds.original_transforms.clear();
|
||||
}
|
||||
|
||||
responses.add(BroadcastEvent::DocumentIsDirty);
|
||||
|
||||
ArtboardToolFsmState::Ready
|
||||
}
|
||||
(ArtboardToolFsmState::Dragging, ArtboardToolMessage::PointerUp) => {
|
||||
tool_data.snap_manager.cleanup(responses);
|
||||
|
||||
if let Some(bounds) = &mut tool_data.bounding_box_overlays {
|
||||
bounds.original_transforms.clear();
|
||||
}
|
||||
|
||||
ArtboardToolFsmState::Ready
|
||||
}
|
||||
(_, ArtboardToolMessage::DeleteSelected) => {
|
||||
if let Some(artboard) = tool_data.selected_artboard.take() {
|
||||
responses.add(ArtboardMessage::DeleteArtboard { artboard });
|
||||
responses.add(GraphOperationMessage::DeleteLayer { id: artboard });
|
||||
|
||||
responses.add(BroadcastEvent::DocumentIsDirty);
|
||||
}
|
||||
ArtboardToolFsmState::Ready
|
||||
}
|
||||
(_, ArtboardToolMessage::NudgeSelected { delta_x, delta_y }) => {
|
||||
if let Some(bounds) = &mut tool_data.bounding_box_overlays {
|
||||
responses.add(ArtboardMessage::ResizeArtboard {
|
||||
artboard: tool_data.selected_artboard.unwrap(),
|
||||
position: (bounds.bounds[0].x + delta_x, bounds.bounds[0].y + delta_y),
|
||||
size: (bounds.bounds[1] - bounds.bounds[0]).round().into(),
|
||||
});
|
||||
responses.add(GraphOperationMessage::ResizeArtboard {
|
||||
id: tool_data.selected_artboard.unwrap(),
|
||||
location: DVec2::new(bounds.bounds[0].x + delta_x, bounds.bounds[0].y + delta_y).round().as_ivec2(),
|
||||
dimensions: (bounds.bounds[1] - bounds.bounds[0]).round().as_ivec2(),
|
||||
});
|
||||
}
|
||||
|
||||
ArtboardToolFsmState::Ready
|
||||
}
|
||||
(_, ArtboardToolMessage::Abort) => {
|
||||
if let Some(bounding_box_overlays) = tool_data.bounding_box_overlays.take() {
|
||||
bounding_box_overlays.delete(responses);
|
||||
}
|
||||
|
||||
// Register properties when switching back to other tools
|
||||
responses.add(PropertiesPanelMessage::SetActiveLayers {
|
||||
paths: document.selected_layers().map(|path| path.to_vec()).collect(),
|
||||
document: TargetDocument::Artwork,
|
||||
});
|
||||
|
||||
tool_data.snap_manager.cleanup(responses);
|
||||
ArtboardToolFsmState::Ready
|
||||
}
|
||||
_ => self,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
use crate::messages::frontend::utility_types::MouseCursorIcon;
|
||||
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion};
|
||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||
use super::tool_prelude::*;
|
||||
use crate::messages::portfolio::document::node_graph::transform_utils::get_current_transform;
|
||||
use crate::messages::prelude::*;
|
||||
use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType};
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||
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::layer_layer::CachedOutputData;
|
||||
use document_legacy::LayerId;
|
||||
@@ -16,9 +11,6 @@ use graphene_core::raster::{BlendMode, ImageFrame};
|
||||
use graphene_core::vector::brush_stroke::{BrushInputSample, BrushStroke, BrushStyle};
|
||||
use graphene_core::Color;
|
||||
|
||||
use glam::DAffine2;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
const EXPOSED_BLEND_MODES: &[&[BlendMode]] = {
|
||||
use BlendMode::*;
|
||||
&[
|
||||
@@ -227,43 +219,41 @@ impl LayoutHolder for BrushTool {
|
||||
|
||||
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for BrushTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
|
||||
if let ToolMessage::Brush(BrushToolMessage::UpdateOptions(action)) = message {
|
||||
match action {
|
||||
BrushToolMessageOptionsUpdate::BlendMode(blend_mode) => self.options.blend_mode = blend_mode,
|
||||
BrushToolMessageOptionsUpdate::ChangeDiameter(change) => {
|
||||
let needs_rounding = ((self.options.diameter + change.abs() / 2.) % change.abs() - change.abs() / 2.).abs() > 0.5;
|
||||
if needs_rounding && change > 0. {
|
||||
self.options.diameter = (self.options.diameter / change.abs()).ceil() * change.abs();
|
||||
} else if needs_rounding && change < 0. {
|
||||
self.options.diameter = (self.options.diameter / change.abs()).floor() * change.abs();
|
||||
} else {
|
||||
self.options.diameter = (self.options.diameter / change.abs()).round() * change.abs() + change;
|
||||
}
|
||||
self.options.diameter = self.options.diameter.max(1.);
|
||||
self.send_layout(responses, LayoutTarget::ToolOptions);
|
||||
}
|
||||
BrushToolMessageOptionsUpdate::Diameter(diameter) => self.options.diameter = diameter,
|
||||
BrushToolMessageOptionsUpdate::DrawMode(draw_mode) => self.options.draw_mode = draw_mode,
|
||||
BrushToolMessageOptionsUpdate::Hardness(hardness) => self.options.hardness = hardness,
|
||||
BrushToolMessageOptionsUpdate::Flow(flow) => self.options.flow = flow,
|
||||
BrushToolMessageOptionsUpdate::Spacing(spacing) => self.options.spacing = spacing,
|
||||
BrushToolMessageOptionsUpdate::Color(color) => {
|
||||
self.options.color.custom_color = color;
|
||||
self.options.color.color_type = ToolColorType::Custom;
|
||||
}
|
||||
BrushToolMessageOptionsUpdate::ColorType(color_type) => self.options.color.color_type = color_type,
|
||||
BrushToolMessageOptionsUpdate::WorkingColors(primary, secondary) => {
|
||||
self.options.color.primary_working_color = primary;
|
||||
self.options.color.secondary_working_color = secondary;
|
||||
}
|
||||
}
|
||||
|
||||
self.send_layout(responses, LayoutTarget::ToolOptions);
|
||||
|
||||
let ToolMessage::Brush(BrushToolMessage::UpdateOptions(action)) = message else{
|
||||
self.fsm_state.process_event(message, &mut self.data, tool_data, &self.options, responses, true);
|
||||
return;
|
||||
};
|
||||
match action {
|
||||
BrushToolMessageOptionsUpdate::BlendMode(blend_mode) => self.options.blend_mode = blend_mode,
|
||||
BrushToolMessageOptionsUpdate::ChangeDiameter(change) => {
|
||||
let needs_rounding = ((self.options.diameter + change.abs() / 2.) % change.abs() - change.abs() / 2.).abs() > 0.5;
|
||||
if needs_rounding && change > 0. {
|
||||
self.options.diameter = (self.options.diameter / change.abs()).ceil() * change.abs();
|
||||
} else if needs_rounding && change < 0. {
|
||||
self.options.diameter = (self.options.diameter / change.abs()).floor() * change.abs();
|
||||
} else {
|
||||
self.options.diameter = (self.options.diameter / change.abs()).round() * change.abs() + change;
|
||||
}
|
||||
self.options.diameter = self.options.diameter.max(1.);
|
||||
self.send_layout(responses, LayoutTarget::ToolOptions);
|
||||
}
|
||||
BrushToolMessageOptionsUpdate::Diameter(diameter) => self.options.diameter = diameter,
|
||||
BrushToolMessageOptionsUpdate::DrawMode(draw_mode) => self.options.draw_mode = draw_mode,
|
||||
BrushToolMessageOptionsUpdate::Hardness(hardness) => self.options.hardness = hardness,
|
||||
BrushToolMessageOptionsUpdate::Flow(flow) => self.options.flow = flow,
|
||||
BrushToolMessageOptionsUpdate::Spacing(spacing) => self.options.spacing = spacing,
|
||||
BrushToolMessageOptionsUpdate::Color(color) => {
|
||||
self.options.color.custom_color = color;
|
||||
self.options.color.color_type = ToolColorType::Custom;
|
||||
}
|
||||
BrushToolMessageOptionsUpdate::ColorType(color_type) => self.options.color.color_type = color_type,
|
||||
BrushToolMessageOptionsUpdate::WorkingColors(primary, secondary) => {
|
||||
self.options.color.primary_working_color = primary;
|
||||
self.options.color.secondary_working_color = secondary;
|
||||
}
|
||||
}
|
||||
|
||||
self.fsm_state.process_event(message, &mut self.data, tool_data, &self.options, responses, true);
|
||||
self.send_layout(responses, LayoutTarget::ToolOptions);
|
||||
}
|
||||
|
||||
fn actions(&self) -> ActionList {
|
||||
@@ -346,94 +336,88 @@ impl Fsm for BrushToolFsmState {
|
||||
type ToolData = BrushToolData;
|
||||
type ToolOptions = BrushOptions;
|
||||
|
||||
fn transition(
|
||||
self,
|
||||
event: ToolMessage,
|
||||
tool_data: &mut Self::ToolData,
|
||||
ToolActionHandlerData {
|
||||
fn transition(self, event: ToolMessage, tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionHandlerData, tool_options: &Self::ToolOptions, responses: &mut VecDeque<Message>) -> Self {
|
||||
let ToolActionHandlerData {
|
||||
document, global_tool_data, input, ..
|
||||
}: &mut ToolActionHandlerData,
|
||||
tool_options: &Self::ToolOptions,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) -> Self {
|
||||
let document_position = (document.document_legacy.root.transform).inverse().transform_point2(input.mouse.position);
|
||||
} = tool_action_data;
|
||||
|
||||
let document_position = document.document_legacy.metadata.document_to_viewport.inverse().transform_point2(input.mouse.position);
|
||||
let layer_position = tool_data.transform.inverse().transform_point2(document_position);
|
||||
|
||||
if let ToolMessage::Brush(event) = event {
|
||||
match (self, event) {
|
||||
(BrushToolFsmState::Ready, BrushToolMessage::DragStart) => {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
let layer_path = tool_data.load_existing_strokes(document);
|
||||
let new_layer = layer_path.is_none();
|
||||
if new_layer {
|
||||
responses.add(DocumentMessage::DeselectAllLayers);
|
||||
tool_data.layer_path = document.get_path_for_new_layer();
|
||||
}
|
||||
let layer_position = tool_data.transform.inverse().transform_point2(document_position);
|
||||
// TODO: Also scale it based on the input image ('Background' parameter).
|
||||
// TODO: Resizing the input image results in a different brush size from the chosen diameter.
|
||||
let layer_scale = 0.0001_f64 // Safety against division by zero
|
||||
.max((tool_data.transform.matrix2 * glam::DVec2::X).length())
|
||||
.max((tool_data.transform.matrix2 * glam::DVec2::Y).length());
|
||||
|
||||
// Start a new stroke with a single sample
|
||||
let blend_mode = match tool_options.draw_mode {
|
||||
DrawMode::Draw => tool_options.blend_mode,
|
||||
DrawMode::Erase => BlendMode::Erase,
|
||||
DrawMode::Restore => BlendMode::Restore,
|
||||
};
|
||||
tool_data.strokes.push(BrushStroke {
|
||||
trace: vec![BrushInputSample { position: layer_position }],
|
||||
style: BrushStyle {
|
||||
color: tool_options.color.active_color().unwrap_or_default(),
|
||||
diameter: tool_options.diameter / layer_scale,
|
||||
hardness: tool_options.hardness,
|
||||
flow: tool_options.flow,
|
||||
spacing: tool_options.spacing,
|
||||
blend_mode,
|
||||
},
|
||||
});
|
||||
|
||||
if new_layer {
|
||||
add_brush_render(tool_options, tool_data, responses);
|
||||
}
|
||||
tool_data.update_strokes(responses);
|
||||
|
||||
BrushToolFsmState::Drawing
|
||||
let ToolMessage::Brush(event) = event else {
|
||||
return self;
|
||||
};
|
||||
match (self, event) {
|
||||
(BrushToolFsmState::Ready, BrushToolMessage::DragStart) => {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
let layer_path = tool_data.load_existing_strokes(document);
|
||||
let new_layer = layer_path.is_none();
|
||||
if new_layer {
|
||||
responses.add(DocumentMessage::DeselectAllLayers);
|
||||
tool_data.layer_path = document.get_path_for_new_layer();
|
||||
}
|
||||
let layer_position = tool_data.transform.inverse().transform_point2(document_position);
|
||||
// TODO: Also scale it based on the input image ('Background' parameter).
|
||||
// TODO: Resizing the input image results in a different brush size from the chosen diameter.
|
||||
let layer_scale = 0.0001_f64 // Safety against division by zero
|
||||
.max((tool_data.transform.matrix2 * glam::DVec2::X).length())
|
||||
.max((tool_data.transform.matrix2 * glam::DVec2::Y).length());
|
||||
|
||||
(BrushToolFsmState::Drawing, BrushToolMessage::PointerMove) => {
|
||||
if let Some(stroke) = tool_data.strokes.last_mut() {
|
||||
stroke.trace.push(BrushInputSample { position: layer_position })
|
||||
}
|
||||
tool_data.update_strokes(responses);
|
||||
// Start a new stroke with a single sample
|
||||
let blend_mode = match tool_options.draw_mode {
|
||||
DrawMode::Draw => tool_options.blend_mode,
|
||||
DrawMode::Erase => BlendMode::Erase,
|
||||
DrawMode::Restore => BlendMode::Restore,
|
||||
};
|
||||
tool_data.strokes.push(BrushStroke {
|
||||
trace: vec![BrushInputSample { position: layer_position }],
|
||||
style: BrushStyle {
|
||||
color: tool_options.color.active_color().unwrap_or_default(),
|
||||
diameter: tool_options.diameter / layer_scale,
|
||||
hardness: tool_options.hardness,
|
||||
flow: tool_options.flow,
|
||||
spacing: tool_options.spacing,
|
||||
blend_mode,
|
||||
},
|
||||
});
|
||||
|
||||
BrushToolFsmState::Drawing
|
||||
if new_layer {
|
||||
add_brush_render(tool_options, tool_data, responses);
|
||||
}
|
||||
tool_data.update_strokes(responses);
|
||||
|
||||
(BrushToolFsmState::Drawing, BrushToolMessage::DragStop) | (BrushToolFsmState::Drawing, BrushToolMessage::Abort) => {
|
||||
if !tool_data.strokes.is_empty() {
|
||||
responses.add(DocumentMessage::CommitTransaction);
|
||||
} else {
|
||||
responses.add(DocumentMessage::AbortTransaction);
|
||||
}
|
||||
|
||||
tool_data.strokes.clear();
|
||||
|
||||
BrushToolFsmState::Ready
|
||||
}
|
||||
|
||||
(_, BrushToolMessage::WorkingColorChanged) => {
|
||||
responses.add(BrushToolMessage::UpdateOptions(BrushToolMessageOptionsUpdate::WorkingColors(
|
||||
Some(global_tool_data.primary_color),
|
||||
Some(global_tool_data.secondary_color),
|
||||
)));
|
||||
self
|
||||
}
|
||||
_ => self,
|
||||
BrushToolFsmState::Drawing
|
||||
}
|
||||
} else {
|
||||
self
|
||||
|
||||
(BrushToolFsmState::Drawing, BrushToolMessage::PointerMove) => {
|
||||
if let Some(stroke) = tool_data.strokes.last_mut() {
|
||||
stroke.trace.push(BrushInputSample { position: layer_position })
|
||||
}
|
||||
tool_data.update_strokes(responses);
|
||||
|
||||
BrushToolFsmState::Drawing
|
||||
}
|
||||
|
||||
(BrushToolFsmState::Drawing, BrushToolMessage::DragStop) | (BrushToolFsmState::Drawing, BrushToolMessage::Abort) => {
|
||||
if !tool_data.strokes.is_empty() {
|
||||
responses.add(DocumentMessage::CommitTransaction);
|
||||
} else {
|
||||
responses.add(DocumentMessage::AbortTransaction);
|
||||
}
|
||||
|
||||
tool_data.strokes.clear();
|
||||
|
||||
BrushToolFsmState::Ready
|
||||
}
|
||||
|
||||
(_, BrushToolMessage::WorkingColorChanged) => {
|
||||
responses.add(BrushToolMessage::UpdateOptions(BrushToolMessageOptionsUpdate::WorkingColors(
|
||||
Some(global_tool_data.primary_color),
|
||||
Some(global_tool_data.secondary_color),
|
||||
)));
|
||||
self
|
||||
}
|
||||
_ => self,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,19 +1,11 @@
|
||||
use crate::messages::frontend::utility_types::MouseCursorIcon;
|
||||
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion};
|
||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||
use crate::messages::prelude::*;
|
||||
use super::tool_prelude::*;
|
||||
use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType};
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||
use crate::messages::tool::common_functionality::resize::Resize;
|
||||
use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType};
|
||||
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
|
||||
|
||||
use graphene_core::vector::style::{Fill, Stroke};
|
||||
use graphene_core::Color;
|
||||
|
||||
use glam::DVec2;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct EllipseTool {
|
||||
fsm_state: EllipseToolFsmState,
|
||||
@@ -120,33 +112,31 @@ impl LayoutHolder for EllipseTool {
|
||||
|
||||
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>) {
|
||||
if let ToolMessage::Ellipse(EllipseToolMessage::UpdateOptions(action)) = message {
|
||||
match action {
|
||||
EllipseOptionsUpdate::FillColor(color) => {
|
||||
self.options.fill.custom_color = color;
|
||||
self.options.fill.color_type = ToolColorType::Custom;
|
||||
}
|
||||
EllipseOptionsUpdate::FillColorType(color_type) => self.options.fill.color_type = color_type,
|
||||
EllipseOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight,
|
||||
EllipseOptionsUpdate::StrokeColor(color) => {
|
||||
self.options.stroke.custom_color = color;
|
||||
self.options.stroke.color_type = ToolColorType::Custom;
|
||||
}
|
||||
EllipseOptionsUpdate::StrokeColorType(color_type) => self.options.stroke.color_type = color_type,
|
||||
EllipseOptionsUpdate::WorkingColors(primary, secondary) => {
|
||||
self.options.stroke.primary_working_color = primary;
|
||||
self.options.stroke.secondary_working_color = secondary;
|
||||
self.options.fill.primary_working_color = primary;
|
||||
self.options.fill.secondary_working_color = secondary;
|
||||
}
|
||||
}
|
||||
|
||||
self.send_layout(responses, LayoutTarget::ToolOptions);
|
||||
|
||||
let ToolMessage::Ellipse(EllipseToolMessage::UpdateOptions(action)) = message else{
|
||||
self.fsm_state.process_event(message, &mut self.data, tool_data, &self.options, responses, true);
|
||||
return;
|
||||
};
|
||||
match action {
|
||||
EllipseOptionsUpdate::FillColor(color) => {
|
||||
self.options.fill.custom_color = color;
|
||||
self.options.fill.color_type = ToolColorType::Custom;
|
||||
}
|
||||
EllipseOptionsUpdate::FillColorType(color_type) => self.options.fill.color_type = color_type,
|
||||
EllipseOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight,
|
||||
EllipseOptionsUpdate::StrokeColor(color) => {
|
||||
self.options.stroke.custom_color = color;
|
||||
self.options.stroke.color_type = ToolColorType::Custom;
|
||||
}
|
||||
EllipseOptionsUpdate::StrokeColorType(color_type) => self.options.stroke.color_type = color_type,
|
||||
EllipseOptionsUpdate::WorkingColors(primary, secondary) => {
|
||||
self.options.stroke.primary_working_color = primary;
|
||||
self.options.stroke.secondary_working_color = secondary;
|
||||
self.options.fill.primary_working_color = primary;
|
||||
self.options.fill.secondary_working_color = secondary;
|
||||
}
|
||||
}
|
||||
|
||||
self.fsm_state.process_event(message, &mut self.data, tool_data, &self.options, responses, true);
|
||||
self.send_layout(responses, LayoutTarget::ToolOptions);
|
||||
}
|
||||
|
||||
fn actions(&self) -> ActionList {
|
||||
@@ -192,88 +182,79 @@ impl Fsm for EllipseToolFsmState {
|
||||
type ToolData = EllipseToolData;
|
||||
type ToolOptions = EllipseToolOptions;
|
||||
|
||||
fn transition(
|
||||
self,
|
||||
event: ToolMessage,
|
||||
tool_data: &mut Self::ToolData,
|
||||
ToolActionHandlerData {
|
||||
fn transition(self, event: ToolMessage, tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionHandlerData, tool_options: &Self::ToolOptions, responses: &mut VecDeque<Message>) -> Self {
|
||||
let ToolActionHandlerData {
|
||||
document,
|
||||
global_tool_data,
|
||||
input,
|
||||
render_data,
|
||||
..
|
||||
}: &mut ToolActionHandlerData,
|
||||
tool_options: &Self::ToolOptions,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) -> Self {
|
||||
use EllipseToolFsmState::*;
|
||||
use EllipseToolMessage::*;
|
||||
} = tool_action_data;
|
||||
|
||||
let shape_data = &mut tool_data.data;
|
||||
|
||||
if let ToolMessage::Ellipse(event) = event {
|
||||
match (self, event) {
|
||||
(Drawing, CanvasTransformed) => {
|
||||
tool_data.data.recalculate_snaps(document, input, render_data);
|
||||
self
|
||||
}
|
||||
(Ready, DragStart) => {
|
||||
shape_data.start(responses, document, input, render_data);
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
|
||||
// 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);
|
||||
graph_modification_utils::set_manipulator_mirror_angle(&manipulator_groups, &layer_path, true, responses);
|
||||
|
||||
let fill_color = tool_options.fill.active_color();
|
||||
responses.add(GraphOperationMessage::FillSet {
|
||||
layer: layer_path.clone(),
|
||||
fill: if let Some(color) = fill_color { Fill::Solid(color) } else { Fill::None },
|
||||
});
|
||||
|
||||
responses.add(GraphOperationMessage::StrokeSet {
|
||||
layer: layer_path,
|
||||
stroke: Stroke::new(tool_options.stroke.active_color(), tool_options.line_weight),
|
||||
});
|
||||
|
||||
Drawing
|
||||
}
|
||||
(state, Resize { center, lock_ratio }) => {
|
||||
if let Some(message) = shape_data.calculate_transform(responses, document, input, center, lock_ratio, false) {
|
||||
responses.add(message);
|
||||
}
|
||||
|
||||
state
|
||||
}
|
||||
(Drawing, DragStop) => {
|
||||
input.mouse.finish_transaction(shape_data.viewport_drag_start(document), responses);
|
||||
shape_data.cleanup(responses);
|
||||
|
||||
Ready
|
||||
}
|
||||
(Drawing, Abort) => {
|
||||
responses.add(DocumentMessage::AbortTransaction);
|
||||
shape_data.cleanup(responses);
|
||||
|
||||
Ready
|
||||
}
|
||||
(_, WorkingColorChanged) => {
|
||||
responses.add(EllipseToolMessage::UpdateOptions(EllipseOptionsUpdate::WorkingColors(
|
||||
Some(global_tool_data.primary_color),
|
||||
Some(global_tool_data.secondary_color),
|
||||
)));
|
||||
self
|
||||
}
|
||||
_ => self,
|
||||
let ToolMessage::Ellipse(event) = event else {
|
||||
return self;
|
||||
};
|
||||
match (self, event) {
|
||||
(EllipseToolFsmState::Drawing, EllipseToolMessage::CanvasTransformed) => {
|
||||
tool_data.data.recalculate_snaps(document, input, render_data);
|
||||
self
|
||||
}
|
||||
} else {
|
||||
self
|
||||
(EllipseToolFsmState::Ready, EllipseToolMessage::DragStart) => {
|
||||
shape_data.start(responses, document, input, render_data);
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
|
||||
// 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);
|
||||
graph_modification_utils::set_manipulator_mirror_angle(&manipulator_groups, &layer_path, true, responses);
|
||||
|
||||
let fill_color = tool_options.fill.active_color();
|
||||
responses.add(GraphOperationMessage::FillSet {
|
||||
layer: layer_path.clone(),
|
||||
fill: if let Some(color) = fill_color { Fill::Solid(color) } else { Fill::None },
|
||||
});
|
||||
|
||||
responses.add(GraphOperationMessage::StrokeSet {
|
||||
layer: layer_path,
|
||||
stroke: Stroke::new(tool_options.stroke.active_color(), tool_options.line_weight),
|
||||
});
|
||||
|
||||
EllipseToolFsmState::Drawing
|
||||
}
|
||||
(state, EllipseToolMessage::Resize { center, lock_ratio }) => {
|
||||
if let Some(message) = shape_data.calculate_transform(responses, document, input, center, lock_ratio, false) {
|
||||
responses.add(message);
|
||||
}
|
||||
|
||||
state
|
||||
}
|
||||
(EllipseToolFsmState::Drawing, EllipseToolMessage::DragStop) => {
|
||||
input.mouse.finish_transaction(shape_data.viewport_drag_start(document), responses);
|
||||
shape_data.cleanup(responses);
|
||||
|
||||
EllipseToolFsmState::Ready
|
||||
}
|
||||
(EllipseToolFsmState::Drawing, EllipseToolMessage::Abort) => {
|
||||
responses.add(DocumentMessage::AbortTransaction);
|
||||
shape_data.cleanup(responses);
|
||||
|
||||
EllipseToolFsmState::Ready
|
||||
}
|
||||
(_, EllipseToolMessage::WorkingColorChanged) => {
|
||||
responses.add(EllipseToolMessage::UpdateOptions(EllipseOptionsUpdate::WorkingColors(
|
||||
Some(global_tool_data.primary_color),
|
||||
Some(global_tool_data.secondary_color),
|
||||
)));
|
||||
self
|
||||
}
|
||||
_ => self,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
use crate::messages::frontend::utility_types::MouseCursorIcon;
|
||||
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion};
|
||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||
use crate::messages::prelude::*;
|
||||
use crate::messages::tool::utility_types::{DocumentToolData, EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType};
|
||||
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use super::tool_prelude::*;
|
||||
use crate::messages::tool::utility_types::DocumentToolData;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct EyedropperTool {
|
||||
@@ -87,58 +81,49 @@ impl Fsm for EyedropperToolFsmState {
|
||||
type ToolData = EyedropperToolData;
|
||||
type ToolOptions = ();
|
||||
|
||||
fn transition(
|
||||
self,
|
||||
event: ToolMessage,
|
||||
_tool_data: &mut Self::ToolData,
|
||||
ToolActionHandlerData { global_tool_data, input, .. }: &mut ToolActionHandlerData,
|
||||
_tool_options: &Self::ToolOptions,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) -> Self {
|
||||
use EyedropperToolFsmState::*;
|
||||
use EyedropperToolMessage::*;
|
||||
fn transition(self, event: ToolMessage, _tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionHandlerData, _tool_options: &(), responses: &mut VecDeque<Message>) -> Self {
|
||||
let ToolActionHandlerData { global_tool_data, input, .. } = tool_action_data;
|
||||
|
||||
if let ToolMessage::Eyedropper(event) = event {
|
||||
match (self, event) {
|
||||
// Ready -> Sampling
|
||||
(Ready, mouse_down) | (Ready, mouse_down) if mouse_down == LeftPointerDown || mouse_down == RightPointerDown => {
|
||||
update_cursor_preview(responses, input, global_tool_data, None);
|
||||
let ToolMessage::Eyedropper(event) = event else{
|
||||
return self;
|
||||
};
|
||||
match (self, event) {
|
||||
// Ready -> Sampling
|
||||
(EyedropperToolFsmState::Ready, mouse_down) if matches!(mouse_down, EyedropperToolMessage::LeftPointerDown | EyedropperToolMessage::RightPointerDown) => {
|
||||
update_cursor_preview(responses, input, global_tool_data, None);
|
||||
|
||||
if mouse_down == LeftPointerDown {
|
||||
SamplingPrimary
|
||||
} else {
|
||||
SamplingSecondary
|
||||
}
|
||||
if mouse_down == EyedropperToolMessage::LeftPointerDown {
|
||||
EyedropperToolFsmState::SamplingPrimary
|
||||
} else {
|
||||
EyedropperToolFsmState::SamplingSecondary
|
||||
}
|
||||
// Sampling -> Sampling
|
||||
(SamplingPrimary, PointerMove) | (SamplingSecondary, PointerMove) => {
|
||||
if input.viewport_bounds.in_bounds(input.mouse.position) {
|
||||
update_cursor_preview(responses, input, global_tool_data, None);
|
||||
} else {
|
||||
disable_cursor_preview(responses);
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
// Sampling -> Ready
|
||||
(SamplingPrimary, mouse_up) | (SamplingSecondary, mouse_up) if mouse_up == LeftPointerUp || mouse_up == RightPointerUp => {
|
||||
let set_color_choice = if self == SamplingPrimary { "Primary".to_string() } else { "Secondary".to_string() };
|
||||
update_cursor_preview(responses, input, global_tool_data, Some(set_color_choice));
|
||||
disable_cursor_preview(responses);
|
||||
|
||||
Ready
|
||||
}
|
||||
// Any -> Ready
|
||||
(_, Abort) => {
|
||||
disable_cursor_preview(responses);
|
||||
|
||||
Ready
|
||||
}
|
||||
// Ready -> Ready
|
||||
_ => self,
|
||||
}
|
||||
} else {
|
||||
self
|
||||
// Sampling -> Sampling
|
||||
(EyedropperToolFsmState::SamplingPrimary | EyedropperToolFsmState::SamplingSecondary, EyedropperToolMessage::PointerMove) => {
|
||||
if input.viewport_bounds.in_bounds(input.mouse.position) {
|
||||
update_cursor_preview(responses, input, global_tool_data, None);
|
||||
} else {
|
||||
disable_cursor_preview(responses);
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
// Sampling -> Ready
|
||||
(EyedropperToolFsmState::SamplingPrimary, EyedropperToolMessage::LeftPointerUp) | (EyedropperToolFsmState::SamplingSecondary, EyedropperToolMessage::RightPointerUp) => {
|
||||
let set_color_choice = if self == EyedropperToolFsmState::SamplingPrimary { "Primary" } else { "Secondary" }.to_string();
|
||||
update_cursor_preview(responses, input, global_tool_data, Some(set_color_choice));
|
||||
disable_cursor_preview(responses);
|
||||
|
||||
EyedropperToolFsmState::Ready
|
||||
}
|
||||
// Any -> Ready
|
||||
(_, EyedropperToolMessage::Abort) => {
|
||||
disable_cursor_preview(responses);
|
||||
|
||||
EyedropperToolFsmState::Ready
|
||||
}
|
||||
// Ready -> Ready
|
||||
_ => self,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,32 +1,15 @@
|
||||
use crate::consts::SELECTION_TOLERANCE;
|
||||
use crate::messages::frontend::utility_types::MouseCursorIcon;
|
||||
use crate::messages::input_mapper::utility_types::input_keyboard::MouseMotion;
|
||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||
use crate::messages::prelude::*;
|
||||
use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType};
|
||||
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
|
||||
|
||||
use document_legacy::intersection::Quad;
|
||||
use document_legacy::layers::layer_layer::CachedOutputData;
|
||||
use super::tool_prelude::*;
|
||||
use document_legacy::layers::style::Fill;
|
||||
|
||||
use glam::DVec2;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct FillTool {
|
||||
fsm_state: FillToolFsmState,
|
||||
data: FillToolData,
|
||||
}
|
||||
|
||||
#[remain::sorted]
|
||||
#[impl_message(Message, ToolMessage, Fill)]
|
||||
#[derive(PartialEq, Eq, Clone, Debug, Hash, Serialize, Deserialize, specta::Type)]
|
||||
pub enum FillToolMessage {
|
||||
// Standard messages
|
||||
#[remain::unsorted]
|
||||
Abort,
|
||||
|
||||
// Tool-specific messages
|
||||
LeftPointerDown,
|
||||
RightPointerDown,
|
||||
@@ -52,7 +35,7 @@ impl LayoutHolder for FillTool {
|
||||
|
||||
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);
|
||||
self.fsm_state.process_event(message, &mut (), tool_data, &(), responses, true);
|
||||
}
|
||||
|
||||
advertise_actions!(FillToolMessageDiscriminant;
|
||||
@@ -63,10 +46,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for FillToo
|
||||
|
||||
impl ToolTransition for FillTool {
|
||||
fn event_to_message_map(&self) -> EventToMessageMap {
|
||||
EventToMessageMap {
|
||||
tool_abort: Some(FillToolMessage::Abort.into()),
|
||||
..Default::default()
|
||||
}
|
||||
EventToMessageMap::default()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,71 +56,37 @@ enum FillToolFsmState {
|
||||
Ready,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
struct FillToolData {}
|
||||
|
||||
impl Fsm for FillToolFsmState {
|
||||
type ToolData = FillToolData;
|
||||
type ToolData = ();
|
||||
type ToolOptions = ();
|
||||
|
||||
fn transition(
|
||||
self,
|
||||
event: ToolMessage,
|
||||
_tool_data: &mut Self::ToolData,
|
||||
ToolActionHandlerData {
|
||||
document,
|
||||
global_tool_data,
|
||||
input,
|
||||
render_data,
|
||||
..
|
||||
}: &mut ToolActionHandlerData,
|
||||
_tool_options: &Self::ToolOptions,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) -> Self {
|
||||
use FillToolFsmState::*;
|
||||
use FillToolMessage::*;
|
||||
fn transition(self, event: ToolMessage, _tool_data: &mut Self::ToolData, handler_data: &mut ToolActionHandlerData, _tool_options: &Self::ToolOptions, responses: &mut VecDeque<Message>) -> Self {
|
||||
let ToolActionHandlerData {
|
||||
document, global_tool_data, input, ..
|
||||
} = handler_data;
|
||||
|
||||
if let ToolMessage::Fill(event) = event {
|
||||
match (self, event) {
|
||||
(Ready, lmb_or_rmb) if lmb_or_rmb == LeftPointerDown || lmb_or_rmb == RightPointerDown => {
|
||||
let mouse_pos = input.mouse.position;
|
||||
let tolerance = DVec2::splat(SELECTION_TOLERANCE);
|
||||
let quad = Quad::from_box([mouse_pos - tolerance, mouse_pos + tolerance]);
|
||||
let ToolMessage::Fill(event) = event else {
|
||||
return self;
|
||||
};
|
||||
let Some((layer_identifier, _)) = document.document_legacy.metadata.click(input.mouse.position) else {
|
||||
return self;
|
||||
};
|
||||
let layer = layer_identifier.to_path();
|
||||
|
||||
if let Some(path) = document.document_legacy.intersects_quad_root(quad, render_data).last() {
|
||||
let is_bitmap = document
|
||||
.document_legacy
|
||||
.layer(path)
|
||||
.ok()
|
||||
.and_then(|layer| layer.as_layer().ok())
|
||||
.map_or(false, |layer| matches!(layer.cached_output_data, CachedOutputData::BlobURL(_) | CachedOutputData::SurfaceId(_)));
|
||||
let color = match event {
|
||||
FillToolMessage::LeftPointerDown => global_tool_data.primary_color,
|
||||
FillToolMessage::RightPointerDown => global_tool_data.secondary_color,
|
||||
};
|
||||
let fill = Fill::Solid(color);
|
||||
|
||||
if is_bitmap {
|
||||
return self;
|
||||
}
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
responses.add(DocumentMessage::SetSelectedLayers {
|
||||
replacement_selected_layers: vec![layer.clone()],
|
||||
});
|
||||
responses.add(GraphOperationMessage::FillSet { layer, fill });
|
||||
responses.add(DocumentMessage::CommitTransaction);
|
||||
|
||||
let color = match lmb_or_rmb {
|
||||
LeftPointerDown => global_tool_data.primary_color,
|
||||
RightPointerDown => global_tool_data.secondary_color,
|
||||
Abort => unreachable!(),
|
||||
};
|
||||
let fill = Fill::Solid(color);
|
||||
|
||||
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
|
||||
}
|
||||
_ => self,
|
||||
}
|
||||
} else {
|
||||
self
|
||||
}
|
||||
FillToolFsmState::Ready
|
||||
}
|
||||
|
||||
fn update_hints(&self, responses: &mut VecDeque<Message>) {
|
||||
|
||||
@@ -1,18 +1,10 @@
|
||||
use crate::messages::frontend::utility_types::MouseCursorIcon;
|
||||
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion};
|
||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||
use super::tool_prelude::*;
|
||||
use crate::messages::portfolio::document::node_graph;
|
||||
use crate::messages::prelude::*;
|
||||
use crate::messages::tool::common_functionality::path_outline::PathOutline;
|
||||
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::Operation;
|
||||
|
||||
use glam::DAffine2;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct FrameTool {
|
||||
fsm_state: NodeGraphToolFsmState,
|
||||
@@ -107,79 +99,70 @@ impl Fsm for NodeGraphToolFsmState {
|
||||
type ToolData = NodeGraphToolData;
|
||||
type ToolOptions = ();
|
||||
|
||||
fn transition(
|
||||
self,
|
||||
event: ToolMessage,
|
||||
tool_data: &mut Self::ToolData,
|
||||
ToolActionHandlerData { document, input, render_data, .. }: &mut ToolActionHandlerData,
|
||||
_tool_options: &Self::ToolOptions,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) -> Self {
|
||||
use FrameToolMessage::*;
|
||||
use NodeGraphToolFsmState::*;
|
||||
fn transition(self, event: ToolMessage, tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionHandlerData, _tool_options: &(), responses: &mut VecDeque<Message>) -> Self {
|
||||
let ToolActionHandlerData { document, input, render_data, .. } = tool_action_data;
|
||||
|
||||
let shape_data = &mut tool_data.data;
|
||||
|
||||
if let ToolMessage::Frame(event) = event {
|
||||
match (self, event) {
|
||||
(_, DocumentIsDirty | SelectionChanged) => {
|
||||
tool_data.path_outlines.clear_selected(responses);
|
||||
tool_data.path_outlines.update_selected(document.selected_visible_layers(), document, responses, render_data);
|
||||
let ToolMessage::Frame(event) = event else {
|
||||
return self;
|
||||
};
|
||||
match (self, event) {
|
||||
(_, FrameToolMessage::DocumentIsDirty | FrameToolMessage::SelectionChanged) => {
|
||||
tool_data.path_outlines.clear_selected(responses);
|
||||
tool_data.path_outlines.update_selected(document.selected_visible_layers(), document, responses, render_data);
|
||||
|
||||
self
|
||||
}
|
||||
(Ready, DragStart) => {
|
||||
tool_data.path_outlines.clear_selected(responses);
|
||||
|
||||
shape_data.start(responses, document, input, render_data);
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
shape_data.path = Some(document.get_path_for_new_layer());
|
||||
responses.add(DocumentMessage::DeselectAllLayers);
|
||||
|
||||
let network = node_graph::new_image_network(8, 0);
|
||||
|
||||
responses.add(Operation::AddFrame {
|
||||
path: shape_data.path.clone().unwrap(),
|
||||
insert_index: -1,
|
||||
transform: DAffine2::ZERO.to_cols_array(),
|
||||
network,
|
||||
});
|
||||
|
||||
Drawing
|
||||
}
|
||||
(state, Resize { center, lock_ratio }) => {
|
||||
let message = shape_data.calculate_transform(responses, document, input, center, lock_ratio, true);
|
||||
responses.try_add(message);
|
||||
|
||||
state
|
||||
}
|
||||
(Drawing, DragStop) => {
|
||||
if let Some(layer_path) = &shape_data.path {
|
||||
responses.add(DocumentMessage::InputFrameRasterizeRegionBelowLayer { layer_path: layer_path.to_vec() });
|
||||
}
|
||||
|
||||
input.mouse.finish_transaction(shape_data.viewport_drag_start(document), responses);
|
||||
shape_data.cleanup(responses);
|
||||
|
||||
Ready
|
||||
}
|
||||
(Drawing, Abort) => {
|
||||
responses.add(DocumentMessage::AbortTransaction);
|
||||
|
||||
shape_data.cleanup(responses);
|
||||
tool_data.path_outlines.clear_selected(responses);
|
||||
|
||||
Ready
|
||||
}
|
||||
(_, Abort) => {
|
||||
tool_data.path_outlines.clear_selected(responses);
|
||||
|
||||
Ready
|
||||
}
|
||||
_ => self,
|
||||
self
|
||||
}
|
||||
} else {
|
||||
self
|
||||
(NodeGraphToolFsmState::Ready, FrameToolMessage::DragStart) => {
|
||||
tool_data.path_outlines.clear_selected(responses);
|
||||
|
||||
shape_data.start(responses, document, input, render_data);
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
shape_data.path = Some(document.get_path_for_new_layer());
|
||||
responses.add(DocumentMessage::DeselectAllLayers);
|
||||
|
||||
let network = node_graph::new_image_network(8, 0);
|
||||
|
||||
responses.add(Operation::AddFrame {
|
||||
path: shape_data.path.clone().unwrap(),
|
||||
insert_index: -1,
|
||||
transform: DAffine2::ZERO.to_cols_array(),
|
||||
network,
|
||||
});
|
||||
|
||||
NodeGraphToolFsmState::Drawing
|
||||
}
|
||||
(state, FrameToolMessage::Resize { center, lock_ratio }) => {
|
||||
let message = shape_data.calculate_transform(responses, document, input, center, lock_ratio, true);
|
||||
responses.try_add(message);
|
||||
|
||||
state
|
||||
}
|
||||
(NodeGraphToolFsmState::Drawing, FrameToolMessage::DragStop) => {
|
||||
if let Some(layer_path) = &shape_data.path {
|
||||
responses.add(DocumentMessage::InputFrameRasterizeRegionBelowLayer { layer_path: layer_path.to_vec() });
|
||||
}
|
||||
|
||||
input.mouse.finish_transaction(shape_data.viewport_drag_start(document), responses);
|
||||
shape_data.cleanup(responses);
|
||||
|
||||
NodeGraphToolFsmState::Ready
|
||||
}
|
||||
(NodeGraphToolFsmState::Drawing, FrameToolMessage::Abort) => {
|
||||
responses.add(DocumentMessage::AbortTransaction);
|
||||
|
||||
shape_data.cleanup(responses);
|
||||
tool_data.path_outlines.clear_selected(responses);
|
||||
|
||||
NodeGraphToolFsmState::Ready
|
||||
}
|
||||
(_, FrameToolMessage::Abort) => {
|
||||
tool_data.path_outlines.clear_selected(responses);
|
||||
|
||||
NodeGraphToolFsmState::Ready
|
||||
}
|
||||
_ => self,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,8 @@
|
||||
use crate::messages::frontend::utility_types::MouseCursorIcon;
|
||||
use crate::messages::input_mapper::utility_types::input_keyboard::MouseMotion;
|
||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||
use crate::messages::prelude::*;
|
||||
use super::tool_prelude::*;
|
||||
use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType};
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||
use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType};
|
||||
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
|
||||
|
||||
use document_legacy::LayerId;
|
||||
use document_legacy::Operation;
|
||||
use graphene_core::vector::style::{Fill, Stroke};
|
||||
use graphene_core::Color;
|
||||
|
||||
@@ -123,33 +117,31 @@ impl LayoutHolder for FreehandTool {
|
||||
|
||||
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 {
|
||||
FreehandOptionsUpdate::FillColor(color) => {
|
||||
self.options.fill.custom_color = color;
|
||||
self.options.fill.color_type = ToolColorType::Custom;
|
||||
}
|
||||
FreehandOptionsUpdate::FillColorType(color_type) => self.options.fill.color_type = color_type,
|
||||
FreehandOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight,
|
||||
FreehandOptionsUpdate::StrokeColor(color) => {
|
||||
self.options.stroke.custom_color = color;
|
||||
self.options.stroke.color_type = ToolColorType::Custom;
|
||||
}
|
||||
FreehandOptionsUpdate::StrokeColorType(color_type) => self.options.stroke.color_type = color_type,
|
||||
FreehandOptionsUpdate::WorkingColors(primary, secondary) => {
|
||||
self.options.stroke.primary_working_color = primary;
|
||||
self.options.stroke.secondary_working_color = secondary;
|
||||
self.options.fill.primary_working_color = primary;
|
||||
self.options.fill.secondary_working_color = secondary;
|
||||
}
|
||||
}
|
||||
|
||||
self.send_layout(responses, LayoutTarget::ToolOptions);
|
||||
|
||||
let ToolMessage::Freehand(FreehandToolMessage::UpdateOptions(action)) = message else{
|
||||
self.fsm_state.process_event(message, &mut self.data, tool_data, &self.options, responses, true);
|
||||
return;
|
||||
};
|
||||
match action {
|
||||
FreehandOptionsUpdate::FillColor(color) => {
|
||||
self.options.fill.custom_color = color;
|
||||
self.options.fill.color_type = ToolColorType::Custom;
|
||||
}
|
||||
FreehandOptionsUpdate::FillColorType(color_type) => self.options.fill.color_type = color_type,
|
||||
FreehandOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight,
|
||||
FreehandOptionsUpdate::StrokeColor(color) => {
|
||||
self.options.stroke.custom_color = color;
|
||||
self.options.stroke.color_type = ToolColorType::Custom;
|
||||
}
|
||||
FreehandOptionsUpdate::StrokeColorType(color_type) => self.options.stroke.color_type = color_type,
|
||||
FreehandOptionsUpdate::WorkingColors(primary, secondary) => {
|
||||
self.options.stroke.primary_working_color = primary;
|
||||
self.options.stroke.secondary_working_color = secondary;
|
||||
self.options.fill.primary_working_color = primary;
|
||||
self.options.fill.secondary_working_color = secondary;
|
||||
}
|
||||
}
|
||||
|
||||
self.fsm_state.process_event(message, &mut self.data, tool_data, &self.options, responses, true);
|
||||
self.send_layout(responses, LayoutTarget::ToolOptions);
|
||||
}
|
||||
|
||||
fn actions(&self) -> ActionList {
|
||||
@@ -191,74 +183,65 @@ impl Fsm for FreehandToolFsmState {
|
||||
type ToolData = FreehandToolData;
|
||||
type ToolOptions = FreehandOptions;
|
||||
|
||||
fn transition(
|
||||
self,
|
||||
event: ToolMessage,
|
||||
tool_data: &mut Self::ToolData,
|
||||
ToolActionHandlerData {
|
||||
fn transition(self, event: ToolMessage, tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionHandlerData, tool_options: &Self::ToolOptions, responses: &mut VecDeque<Message>) -> Self {
|
||||
let ToolActionHandlerData {
|
||||
document, global_tool_data, input, ..
|
||||
}: &mut ToolActionHandlerData,
|
||||
tool_options: &Self::ToolOptions,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) -> Self {
|
||||
use FreehandToolFsmState::*;
|
||||
use FreehandToolMessage::*;
|
||||
} = tool_action_data;
|
||||
|
||||
let transform = document.document_legacy.root.transform;
|
||||
let transform = document.document_legacy.metadata.document_to_viewport;
|
||||
|
||||
if let ToolMessage::Freehand(event) = event {
|
||||
match (self, event) {
|
||||
(Ready, DragStart) => {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
responses.add(DocumentMessage::DeselectAllLayers);
|
||||
tool_data.path = Some(document.get_path_for_new_layer());
|
||||
let ToolMessage::Freehand(event) = event else {
|
||||
return self;
|
||||
};
|
||||
match (self, event) {
|
||||
(FreehandToolFsmState::Ready, FreehandToolMessage::DragStart) => {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
responses.add(DocumentMessage::DeselectAllLayers);
|
||||
tool_data.path = Some(document.get_path_for_new_layer());
|
||||
|
||||
let pos = transform.inverse().transform_point2(input.mouse.position);
|
||||
let pos = transform.inverse().transform_point2(input.mouse.position);
|
||||
|
||||
tool_data.points.push(pos);
|
||||
tool_data.points.push(pos);
|
||||
|
||||
tool_data.weight = tool_options.line_weight;
|
||||
tool_data.weight = tool_options.line_weight;
|
||||
|
||||
add_polyline(tool_data, tool_options.stroke.active_color(), tool_options.fill.active_color(), responses);
|
||||
add_polyline(tool_data, tool_options.stroke.active_color(), tool_options.fill.active_color(), responses);
|
||||
|
||||
Drawing
|
||||
}
|
||||
(Drawing, PointerMove) => {
|
||||
let pos = transform.inverse().transform_point2(input.mouse.position);
|
||||
|
||||
if tool_data.points.last() != Some(&pos) {
|
||||
tool_data.points.push(pos);
|
||||
}
|
||||
|
||||
add_polyline(tool_data, tool_options.stroke.active_color(), tool_options.fill.active_color(), responses);
|
||||
|
||||
Drawing
|
||||
}
|
||||
(Drawing, DragStop) | (Drawing, Abort) => {
|
||||
if tool_data.points.len() >= 2 {
|
||||
responses.add(remove_preview(tool_data));
|
||||
add_polyline(tool_data, tool_options.stroke.active_color(), tool_options.fill.active_color(), responses);
|
||||
responses.add(DocumentMessage::CommitTransaction);
|
||||
} else {
|
||||
responses.add(DocumentMessage::AbortTransaction);
|
||||
}
|
||||
|
||||
tool_data.path = None;
|
||||
tool_data.points.clear();
|
||||
|
||||
Ready
|
||||
}
|
||||
(_, FreehandToolMessage::WorkingColorChanged) => {
|
||||
responses.add(FreehandToolMessage::UpdateOptions(FreehandOptionsUpdate::WorkingColors(
|
||||
Some(global_tool_data.primary_color),
|
||||
Some(global_tool_data.secondary_color),
|
||||
)));
|
||||
self
|
||||
}
|
||||
_ => self,
|
||||
FreehandToolFsmState::Drawing
|
||||
}
|
||||
} else {
|
||||
self
|
||||
(FreehandToolFsmState::Drawing, FreehandToolMessage::PointerMove) => {
|
||||
let pos = transform.inverse().transform_point2(input.mouse.position);
|
||||
|
||||
if tool_data.points.last() != Some(&pos) {
|
||||
tool_data.points.push(pos);
|
||||
}
|
||||
|
||||
add_polyline(tool_data, tool_options.stroke.active_color(), tool_options.fill.active_color(), responses);
|
||||
|
||||
FreehandToolFsmState::Drawing
|
||||
}
|
||||
(FreehandToolFsmState::Drawing, FreehandToolMessage::DragStop | FreehandToolMessage::Abort) => {
|
||||
if tool_data.points.len() >= 2 {
|
||||
responses.add(remove_preview(tool_data));
|
||||
add_polyline(tool_data, tool_options.stroke.active_color(), tool_options.fill.active_color(), responses);
|
||||
responses.add(DocumentMessage::CommitTransaction);
|
||||
} else {
|
||||
responses.add(DocumentMessage::AbortTransaction);
|
||||
}
|
||||
|
||||
tool_data.path = None;
|
||||
tool_data.points.clear();
|
||||
|
||||
FreehandToolFsmState::Ready
|
||||
}
|
||||
(_, FreehandToolMessage::WorkingColorChanged) => {
|
||||
responses.add(FreehandToolMessage::UpdateOptions(FreehandOptionsUpdate::WorkingColors(
|
||||
Some(global_tool_data.primary_color),
|
||||
Some(global_tool_data.secondary_color),
|
||||
)));
|
||||
self
|
||||
}
|
||||
_ => self,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -277,7 +260,7 @@ impl Fsm for FreehandToolFsmState {
|
||||
}
|
||||
|
||||
fn remove_preview(data: &FreehandToolData) -> Message {
|
||||
Operation::DeleteLayer { path: data.path.clone().unwrap() }.into()
|
||||
GraphOperationMessage::DeleteLayer { id: data.path.clone().unwrap()[0] }.into()
|
||||
}
|
||||
|
||||
fn add_polyline(data: &FreehandToolData, stroke_color: Option<Color>, fill_color: Option<Color>, responses: &mut VecDeque<Message>) {
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
use super::tool_prelude::*;
|
||||
use crate::application::generate_uuid;
|
||||
use crate::consts::{COLOR_ACCENT, LINE_ROTATE_SNAP_ANGLE, MANIPULATOR_GROUP_MARKER_SIZE, SELECTION_THRESHOLD, SELECTION_TOLERANCE};
|
||||
use crate::messages::frontend::utility_types::MouseCursorIcon;
|
||||
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion};
|
||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||
use crate::messages::prelude::*;
|
||||
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::intersection::Quad;
|
||||
use document_legacy::layers::layer_info::Layer;
|
||||
@@ -16,9 +11,6 @@ use document_legacy::LayerId;
|
||||
use document_legacy::Operation;
|
||||
use graphene_core::raster::color::Color;
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct GradientTool {
|
||||
fsm_state: GradientToolFsmState,
|
||||
@@ -26,16 +18,11 @@ pub struct GradientTool {
|
||||
options: GradientOptions,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct GradientOptions {
|
||||
gradient_type: GradientType,
|
||||
}
|
||||
|
||||
impl Default for GradientOptions {
|
||||
fn default() -> Self {
|
||||
Self { gradient_type: GradientType::Linear }
|
||||
}
|
||||
}
|
||||
|
||||
#[remain::sorted]
|
||||
#[impl_message(Message, ToolMessage, Gradient)]
|
||||
#[derive(PartialEq, Eq, Clone, Debug, Hash, Serialize, Deserialize, specta::Type)]
|
||||
@@ -77,20 +64,19 @@ impl ToolMetadata for GradientTool {
|
||||
|
||||
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) => {
|
||||
self.options.gradient_type = gradient_type;
|
||||
if let Some(selected_gradient) = &mut self.data.selected_gradient {
|
||||
selected_gradient.gradient.gradient_type = gradient_type;
|
||||
selected_gradient.render_gradient(responses);
|
||||
}
|
||||
let ToolMessage::Gradient(GradientToolMessage::UpdateOptions(action)) = message else {
|
||||
self.fsm_state.process_event(message, &mut self.data, tool_data, &self.options, responses, false);
|
||||
return;
|
||||
};
|
||||
match action {
|
||||
GradientOptionsUpdate::Type(gradient_type) => {
|
||||
self.options.gradient_type = gradient_type;
|
||||
if let Some(selected_gradient) = &mut self.data.selected_gradient {
|
||||
selected_gradient.gradient.gradient_type = gradient_type;
|
||||
selected_gradient.render_gradient(responses);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
self.fsm_state.process_event(message, &mut self.data, tool_data, &self.options, responses, false);
|
||||
}
|
||||
|
||||
advertise_actions!(GradientToolMessageDiscriminant;
|
||||
@@ -394,253 +380,248 @@ impl Fsm for GradientToolFsmState {
|
||||
type ToolData = GradientToolData;
|
||||
type ToolOptions = GradientOptions;
|
||||
|
||||
fn transition(
|
||||
self,
|
||||
event: ToolMessage,
|
||||
tool_data: &mut Self::ToolData,
|
||||
ToolActionHandlerData {
|
||||
fn transition(self, event: ToolMessage, tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionHandlerData, tool_options: &Self::ToolOptions, responses: &mut VecDeque<Message>) -> Self {
|
||||
let ToolActionHandlerData {
|
||||
document,
|
||||
global_tool_data,
|
||||
input,
|
||||
render_data,
|
||||
..
|
||||
}: &mut ToolActionHandlerData,
|
||||
tool_options: &Self::ToolOptions,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) -> Self {
|
||||
if let ToolMessage::Gradient(event) = event {
|
||||
match (self, event) {
|
||||
(_, GradientToolMessage::DocumentIsDirty) => {
|
||||
while let Some(overlay) = tool_data.gradient_overlays.pop() {
|
||||
overlay.delete_overlays(responses);
|
||||
}
|
||||
} = tool_action_data;
|
||||
|
||||
if self != GradientToolFsmState::Drawing {
|
||||
SelectedGradient::update(&mut tool_data.selected_gradient, document, render_data, responses);
|
||||
}
|
||||
let ToolMessage::Gradient(event) = event else {
|
||||
return self;
|
||||
};
|
||||
|
||||
for path in document.selected_visible_layers() {
|
||||
let layer = document.document_legacy.layer(path).unwrap();
|
||||
|
||||
if let Ok(Fill::Gradient(gradient)) = layer.style().map(|style| style.fill()) {
|
||||
let dragging = tool_data
|
||||
.selected_gradient
|
||||
.as_ref()
|
||||
.and_then(|selected| if selected.path == path { Some(selected.dragging) } else { None });
|
||||
tool_data
|
||||
.gradient_overlays
|
||||
.push(GradientOverlay::new(gradient, dragging, path, layer, document, responses, render_data))
|
||||
}
|
||||
}
|
||||
|
||||
self
|
||||
match (self, event) {
|
||||
(_, GradientToolMessage::DocumentIsDirty) => {
|
||||
while let Some(overlay) = tool_data.gradient_overlays.pop() {
|
||||
overlay.delete_overlays(responses);
|
||||
}
|
||||
(GradientToolFsmState::Ready, GradientToolMessage::DeleteStop) => {
|
||||
let Some(selected_gradient) = &mut tool_data.selected_gradient else {
|
||||
return self;
|
||||
};
|
||||
|
||||
// Skip if invalid gradient
|
||||
if selected_gradient.gradient.positions.len() < 2 {
|
||||
return self;
|
||||
}
|
||||
|
||||
// Remove the selected point
|
||||
match selected_gradient.dragging {
|
||||
GradientDragTarget::Start => selected_gradient.gradient.positions.remove(0),
|
||||
GradientDragTarget::End => selected_gradient.gradient.positions.pop().unwrap(),
|
||||
GradientDragTarget::Step(index) => selected_gradient.gradient.positions.remove(index),
|
||||
};
|
||||
|
||||
// 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 layer = selected_gradient.path.clone();
|
||||
responses.add(GraphOperationMessage::FillSet { layer, fill });
|
||||
return self;
|
||||
}
|
||||
|
||||
// Find the minimum and maximum positions
|
||||
let min_position = selected_gradient.gradient.positions.iter().map(|(pos, _)| *pos).reduce(f64::min).expect("No min");
|
||||
let max_position = selected_gradient.gradient.positions.iter().map(|(pos, _)| *pos).reduce(f64::max).expect("No max");
|
||||
|
||||
// Recompute the start and end posiiton of the gradient (in viewport transform)
|
||||
let transform = selected_gradient.transform;
|
||||
let (start, end) = (transform.transform_point2(selected_gradient.gradient.start), transform.transform_point2(selected_gradient.gradient.end));
|
||||
let (new_start, new_end) = (start.lerp(end, min_position), start.lerp(end, max_position));
|
||||
selected_gradient.gradient.start = transform.inverse().transform_point2(new_start);
|
||||
selected_gradient.gradient.end = transform.inverse().transform_point2(new_end);
|
||||
|
||||
// Remap the positions
|
||||
for (position, _) in selected_gradient.gradient.positions.iter_mut() {
|
||||
*position = (*position - min_position) / (max_position - min_position);
|
||||
}
|
||||
|
||||
// Render the new gradient
|
||||
selected_gradient.render_gradient(responses);
|
||||
|
||||
self
|
||||
if self != GradientToolFsmState::Drawing {
|
||||
SelectedGradient::update(&mut tool_data.selected_gradient, document, render_data, responses);
|
||||
}
|
||||
(_, GradientToolMessage::InsertStop) => {
|
||||
for overlay in &tool_data.gradient_overlays {
|
||||
let mouse = input.mouse.position;
|
||||
let (start, end) = (overlay.evaluate_gradient_start(), overlay.evaluate_gradient_end());
|
||||
|
||||
// Compute the distance from the mouse to the gradient line in viewport space
|
||||
let distance = (end - start).angle_between(mouse - start).sin() * (mouse - start).length();
|
||||
for path in document.selected_visible_layers() {
|
||||
let layer = document.document_legacy.layer(path).unwrap();
|
||||
|
||||
// If click is on the line then insert point
|
||||
if distance < SELECTION_THRESHOLD {
|
||||
let mut gradient = overlay.gradient.clone();
|
||||
|
||||
// Try and insert the new stop
|
||||
if let Some(index) = gradient.insert_stop(mouse, overlay.transform) {
|
||||
document.backup_nonmut(responses);
|
||||
|
||||
let layer = document.document_legacy.layer(&overlay.path);
|
||||
if let Ok(layer) = layer {
|
||||
let mut selected_gradient = SelectedGradient::new(gradient, &overlay.path, layer, document, render_data);
|
||||
|
||||
// Select the new point
|
||||
selected_gradient.dragging = GradientDragTarget::Step(index);
|
||||
|
||||
// Update the layer fill
|
||||
selected_gradient.render_gradient(responses);
|
||||
|
||||
tool_data.selected_gradient = Some(selected_gradient);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
if let Ok(Fill::Gradient(gradient)) = layer.style().map(|style| style.fill()) {
|
||||
let dragging = tool_data
|
||||
.selected_gradient
|
||||
.as_ref()
|
||||
.and_then(|selected| if selected.path == path { Some(selected.dragging) } else { None });
|
||||
tool_data
|
||||
.gradient_overlays
|
||||
.push(GradientOverlay::new(gradient, dragging, path, layer, document, responses, render_data))
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
(GradientToolFsmState::Ready, GradientToolMessage::PointerDown) => {
|
||||
responses.add(BroadcastEvent::DocumentIsDirty);
|
||||
|
||||
self
|
||||
}
|
||||
(GradientToolFsmState::Ready, GradientToolMessage::DeleteStop) => {
|
||||
let Some(selected_gradient) = &mut tool_data.selected_gradient else {
|
||||
return self;
|
||||
};
|
||||
|
||||
// Skip if invalid gradient
|
||||
if selected_gradient.gradient.positions.len() < 2 {
|
||||
return self;
|
||||
}
|
||||
|
||||
// Remove the selected point
|
||||
match selected_gradient.dragging {
|
||||
GradientDragTarget::Start => selected_gradient.gradient.positions.remove(0),
|
||||
GradientDragTarget::End => selected_gradient.gradient.positions.pop().unwrap(),
|
||||
GradientDragTarget::Step(index) => selected_gradient.gradient.positions.remove(index),
|
||||
};
|
||||
|
||||
// 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 layer = selected_gradient.path.clone();
|
||||
responses.add(GraphOperationMessage::FillSet { layer, fill });
|
||||
return self;
|
||||
}
|
||||
|
||||
// Find the minimum and maximum positions
|
||||
let min_position = selected_gradient.gradient.positions.iter().map(|(pos, _)| *pos).reduce(f64::min).expect("No min");
|
||||
let max_position = selected_gradient.gradient.positions.iter().map(|(pos, _)| *pos).reduce(f64::max).expect("No max");
|
||||
|
||||
// Recompute the start and end posiiton of the gradient (in viewport transform)
|
||||
let transform = selected_gradient.transform;
|
||||
let (start, end) = (transform.transform_point2(selected_gradient.gradient.start), transform.transform_point2(selected_gradient.gradient.end));
|
||||
let (new_start, new_end) = (start.lerp(end, min_position), start.lerp(end, max_position));
|
||||
selected_gradient.gradient.start = transform.inverse().transform_point2(new_start);
|
||||
selected_gradient.gradient.end = transform.inverse().transform_point2(new_end);
|
||||
|
||||
// Remap the positions
|
||||
for (position, _) in selected_gradient.gradient.positions.iter_mut() {
|
||||
*position = (*position - min_position) / (max_position - min_position);
|
||||
}
|
||||
|
||||
// Render the new gradient
|
||||
selected_gradient.render_gradient(responses);
|
||||
|
||||
self
|
||||
}
|
||||
(_, GradientToolMessage::InsertStop) => {
|
||||
for overlay in &tool_data.gradient_overlays {
|
||||
let mouse = input.mouse.position;
|
||||
tool_data.drag_start = mouse;
|
||||
let tolerance = MANIPULATOR_GROUP_MARKER_SIZE.powi(2);
|
||||
let (start, end) = (overlay.evaluate_gradient_start(), overlay.evaluate_gradient_end());
|
||||
|
||||
let mut dragging = false;
|
||||
for overlay in &tool_data.gradient_overlays {
|
||||
// Check for dragging step
|
||||
for (index, (pos, _)) in overlay.gradient.positions.iter().enumerate() {
|
||||
let pos = overlay.transform.transform_point2(overlay.gradient.start.lerp(overlay.gradient.end, *pos));
|
||||
if pos.distance_squared(mouse) < tolerance {
|
||||
dragging = true;
|
||||
tool_data.selected_gradient = Some(SelectedGradient {
|
||||
path: overlay.path.clone(),
|
||||
transform: overlay.transform,
|
||||
gradient: overlay.gradient.clone(),
|
||||
dragging: GradientDragTarget::Step(index),
|
||||
})
|
||||
}
|
||||
}
|
||||
// Compute the distance from the mouse to the gradient line in viewport space
|
||||
let distance = (end - start).angle_between(mouse - start).sin() * (mouse - start).length();
|
||||
|
||||
// Check dragging start or end handle
|
||||
for (pos, dragging_target) in [
|
||||
(overlay.evaluate_gradient_start(), GradientDragTarget::Start),
|
||||
(overlay.evaluate_gradient_end(), GradientDragTarget::End),
|
||||
] {
|
||||
if pos.distance_squared(mouse) < tolerance {
|
||||
dragging = true;
|
||||
start_snap(&mut tool_data.snap_manager, document, input, render_data);
|
||||
tool_data.selected_gradient = Some(SelectedGradient {
|
||||
path: overlay.path.clone(),
|
||||
transform: overlay.transform,
|
||||
gradient: overlay.gradient.clone(),
|
||||
dragging: dragging_target,
|
||||
})
|
||||
// If click is on the line then insert point
|
||||
if distance < SELECTION_THRESHOLD {
|
||||
let mut gradient = overlay.gradient.clone();
|
||||
|
||||
// Try and insert the new stop
|
||||
if let Some(index) = gradient.insert_stop(mouse, overlay.transform) {
|
||||
document.backup_nonmut(responses);
|
||||
|
||||
let layer = document.document_legacy.layer(&overlay.path);
|
||||
if let Ok(layer) = layer {
|
||||
let mut selected_gradient = SelectedGradient::new(gradient, &overlay.path, layer, document, render_data);
|
||||
|
||||
// Select the new point
|
||||
selected_gradient.dragging = GradientDragTarget::Step(index);
|
||||
|
||||
// Update the layer fill
|
||||
selected_gradient.render_gradient(responses);
|
||||
|
||||
tool_data.selected_gradient = Some(selected_gradient);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
if dragging {
|
||||
document.backup_nonmut(responses);
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
(GradientToolFsmState::Ready, GradientToolMessage::PointerDown) => {
|
||||
responses.add(BroadcastEvent::DocumentIsDirty);
|
||||
|
||||
let mouse = input.mouse.position;
|
||||
tool_data.drag_start = mouse;
|
||||
let tolerance = MANIPULATOR_GROUP_MARKER_SIZE.powi(2);
|
||||
|
||||
let mut dragging = false;
|
||||
for overlay in &tool_data.gradient_overlays {
|
||||
// Check for dragging step
|
||||
for (index, (pos, _)) in overlay.gradient.positions.iter().enumerate() {
|
||||
let pos = overlay.transform.transform_point2(overlay.gradient.start.lerp(overlay.gradient.end, *pos));
|
||||
if pos.distance_squared(mouse) < tolerance {
|
||||
dragging = true;
|
||||
tool_data.selected_gradient = Some(SelectedGradient {
|
||||
path: overlay.path.clone(),
|
||||
transform: overlay.transform,
|
||||
gradient: overlay.gradient.clone(),
|
||||
dragging: GradientDragTarget::Step(index),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Check dragging start or end handle
|
||||
for (pos, dragging_target) in [
|
||||
(overlay.evaluate_gradient_start(), GradientDragTarget::Start),
|
||||
(overlay.evaluate_gradient_end(), GradientDragTarget::End),
|
||||
] {
|
||||
if pos.distance_squared(mouse) < tolerance {
|
||||
dragging = true;
|
||||
start_snap(&mut tool_data.snap_manager, document, input, render_data);
|
||||
tool_data.selected_gradient = Some(SelectedGradient {
|
||||
path: overlay.path.clone(),
|
||||
transform: overlay.transform,
|
||||
gradient: overlay.gradient.clone(),
|
||||
dragging: dragging_target,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
if dragging {
|
||||
document.backup_nonmut(responses);
|
||||
GradientToolFsmState::Drawing
|
||||
} else {
|
||||
let tolerance = DVec2::splat(SELECTION_TOLERANCE);
|
||||
let quad = Quad::from_box([input.mouse.position - tolerance, input.mouse.position + tolerance]);
|
||||
let intersection = document.document_legacy.intersects_quad_root(quad, render_data).pop();
|
||||
|
||||
// the intersection is the layer where the gradient is being applied
|
||||
if let Some(intersection) = intersection {
|
||||
let is_bitmap = document
|
||||
.document_legacy
|
||||
.layer(&intersection)
|
||||
.ok()
|
||||
.and_then(|layer| layer.as_layer().ok())
|
||||
.map_or(false, |layer| matches!(layer.cached_output_data, CachedOutputData::BlobURL(_) | CachedOutputData::SurfaceId(_)));
|
||||
if is_bitmap {
|
||||
return self;
|
||||
}
|
||||
|
||||
if !document.selected_layers_contains(&intersection) {
|
||||
let replacement_selected_layers = vec![intersection.clone()];
|
||||
|
||||
responses.add(DocumentMessage::SetSelectedLayers { replacement_selected_layers });
|
||||
}
|
||||
|
||||
let layer = document.document_legacy.layer(&intersection).unwrap();
|
||||
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
|
||||
// Use the already existing gradient if it exists
|
||||
let gradient = if let Some(gradient) = layer.style().ok().map(|style| style.fill()).and_then(|fill| fill.as_gradient()) {
|
||||
gradient.clone()
|
||||
} else {
|
||||
// Generate a new gradient
|
||||
Gradient::new(
|
||||
DVec2::ZERO,
|
||||
global_tool_data.secondary_color,
|
||||
DVec2::ONE,
|
||||
global_tool_data.primary_color,
|
||||
DAffine2::IDENTITY,
|
||||
generate_uuid(),
|
||||
tool_options.gradient_type,
|
||||
)
|
||||
};
|
||||
let selected_gradient = SelectedGradient::new(gradient, &intersection, layer, document, render_data).with_gradient_start(input.mouse.position);
|
||||
|
||||
tool_data.selected_gradient = Some(selected_gradient);
|
||||
|
||||
start_snap(&mut tool_data.snap_manager, document, input, render_data);
|
||||
|
||||
GradientToolFsmState::Drawing
|
||||
} else {
|
||||
let tolerance = DVec2::splat(SELECTION_TOLERANCE);
|
||||
let quad = Quad::from_box([input.mouse.position - tolerance, input.mouse.position + tolerance]);
|
||||
let intersection = document.document_legacy.intersects_quad_root(quad, render_data).pop();
|
||||
|
||||
// the intersection is the layer where the gradient is being applied
|
||||
if let Some(intersection) = intersection {
|
||||
let is_bitmap = document
|
||||
.document_legacy
|
||||
.layer(&intersection)
|
||||
.ok()
|
||||
.and_then(|layer| layer.as_layer().ok())
|
||||
.map_or(false, |layer| matches!(layer.cached_output_data, CachedOutputData::BlobURL(_) | CachedOutputData::SurfaceId(_)));
|
||||
if is_bitmap {
|
||||
return self;
|
||||
}
|
||||
|
||||
if !document.selected_layers_contains(&intersection) {
|
||||
let replacement_selected_layers = vec![intersection.clone()];
|
||||
|
||||
responses.add(DocumentMessage::SetSelectedLayers { replacement_selected_layers });
|
||||
}
|
||||
|
||||
let layer = document.document_legacy.layer(&intersection).unwrap();
|
||||
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
|
||||
// Use the already existing gradient if it exists
|
||||
let gradient = if let Some(gradient) = layer.style().ok().map(|style| style.fill()).and_then(|fill| fill.as_gradient()) {
|
||||
gradient.clone()
|
||||
} else {
|
||||
// Generate a new gradient
|
||||
Gradient::new(
|
||||
DVec2::ZERO,
|
||||
global_tool_data.secondary_color,
|
||||
DVec2::ONE,
|
||||
global_tool_data.primary_color,
|
||||
DAffine2::IDENTITY,
|
||||
generate_uuid(),
|
||||
tool_options.gradient_type,
|
||||
)
|
||||
};
|
||||
let selected_gradient = SelectedGradient::new(gradient, &intersection, layer, document, render_data).with_gradient_start(input.mouse.position);
|
||||
|
||||
tool_data.selected_gradient = Some(selected_gradient);
|
||||
|
||||
start_snap(&mut tool_data.snap_manager, document, input, render_data);
|
||||
|
||||
GradientToolFsmState::Drawing
|
||||
} else {
|
||||
GradientToolFsmState::Ready
|
||||
}
|
||||
GradientToolFsmState::Ready
|
||||
}
|
||||
}
|
||||
(GradientToolFsmState::Drawing, GradientToolMessage::PointerMove { constrain_axis }) => {
|
||||
if let Some(selected_gradient) = &mut tool_data.selected_gradient {
|
||||
let mouse = tool_data.snap_manager.snap_position(responses, document, input.mouse.position);
|
||||
selected_gradient.update_gradient(mouse, responses, input.keyboard.get(constrain_axis as usize), selected_gradient.gradient.gradient_type);
|
||||
}
|
||||
GradientToolFsmState::Drawing
|
||||
}
|
||||
|
||||
(GradientToolFsmState::Drawing, GradientToolMessage::PointerUp) => {
|
||||
input.mouse.finish_transaction(tool_data.drag_start, responses);
|
||||
tool_data.snap_manager.cleanup(responses);
|
||||
|
||||
GradientToolFsmState::Ready
|
||||
}
|
||||
|
||||
(_, GradientToolMessage::Abort) => {
|
||||
tool_data.snap_manager.cleanup(responses);
|
||||
|
||||
while let Some(overlay) = tool_data.gradient_overlays.pop() {
|
||||
overlay.delete_overlays(responses);
|
||||
}
|
||||
GradientToolFsmState::Ready
|
||||
}
|
||||
_ => self,
|
||||
}
|
||||
} else {
|
||||
self
|
||||
(GradientToolFsmState::Drawing, GradientToolMessage::PointerMove { constrain_axis }) => {
|
||||
if let Some(selected_gradient) = &mut tool_data.selected_gradient {
|
||||
let mouse = tool_data.snap_manager.snap_position(responses, document, input.mouse.position);
|
||||
selected_gradient.update_gradient(mouse, responses, input.keyboard.get(constrain_axis as usize), selected_gradient.gradient.gradient_type);
|
||||
}
|
||||
GradientToolFsmState::Drawing
|
||||
}
|
||||
|
||||
(GradientToolFsmState::Drawing, GradientToolMessage::PointerUp) => {
|
||||
input.mouse.finish_transaction(tool_data.drag_start, responses);
|
||||
tool_data.snap_manager.cleanup(responses);
|
||||
|
||||
GradientToolFsmState::Ready
|
||||
}
|
||||
|
||||
(_, GradientToolMessage::Abort) => {
|
||||
tool_data.snap_manager.cleanup(responses);
|
||||
|
||||
while let Some(overlay) = tool_data.gradient_overlays.pop() {
|
||||
overlay.delete_overlays(responses);
|
||||
}
|
||||
GradientToolFsmState::Ready
|
||||
}
|
||||
_ => self,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
use crate::messages::frontend::utility_types::MouseCursorIcon;
|
||||
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion};
|
||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||
use super::tool_prelude::*;
|
||||
use crate::messages::portfolio::document::node_graph::{self, IMAGINATE_NODE};
|
||||
use crate::messages::prelude::*;
|
||||
use crate::messages::tool::common_functionality::path_outline::PathOutline;
|
||||
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::Operation;
|
||||
|
||||
@@ -115,102 +110,98 @@ impl Fsm for ImaginateToolFsmState {
|
||||
_tool_options: &Self::ToolOptions,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) -> Self {
|
||||
use ImaginateToolFsmState::*;
|
||||
use ImaginateToolMessage::*;
|
||||
|
||||
let shape_data = &mut tool_data.data;
|
||||
|
||||
if let ToolMessage::Imaginate(event) = event {
|
||||
match (self, event) {
|
||||
(_, DocumentIsDirty | SelectionChanged) => {
|
||||
tool_data.path_outlines.clear_selected(responses);
|
||||
tool_data.path_outlines.update_selected(document.selected_visible_layers(), document, responses, render_data);
|
||||
let ToolMessage::Imaginate(event) = event else{
|
||||
return self;
|
||||
};
|
||||
match (self, event) {
|
||||
(_, ImaginateToolMessage::DocumentIsDirty | ImaginateToolMessage::SelectionChanged) => {
|
||||
tool_data.path_outlines.clear_selected(responses);
|
||||
tool_data.path_outlines.update_selected(document.selected_visible_layers(), document, responses, render_data);
|
||||
|
||||
self
|
||||
}
|
||||
(Ready, DragStart) => {
|
||||
tool_data.path_outlines.clear_selected(responses);
|
||||
|
||||
shape_data.start(responses, document, input, render_data);
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
shape_data.path = Some(document.get_path_for_new_layer());
|
||||
responses.add(DocumentMessage::DeselectAllLayers);
|
||||
|
||||
use graph_craft::document::*;
|
||||
|
||||
// Utility function to offset the position of each consecutive node
|
||||
let mut pos = 8;
|
||||
let mut next_pos = || {
|
||||
pos += 8;
|
||||
graph_craft::document::DocumentNodeMetadata::position((pos, 4))
|
||||
};
|
||||
|
||||
// Get the node type for the Transform and Imaginate nodes
|
||||
let Some(transform_node_type) = crate::messages::portfolio::document::node_graph::resolve_document_node_type("Transform") else {
|
||||
warn!("Transform node should be in registry");
|
||||
return Drawing;
|
||||
};
|
||||
let imaginate_node_type = &*IMAGINATE_NODE;
|
||||
|
||||
// Give them a unique ID
|
||||
let [transform_node_id, imaginate_node_id] = [100, 101];
|
||||
|
||||
// Create the network based on the Input -> Output passthrough default network
|
||||
let mut network = node_graph::new_image_network(16, imaginate_node_id);
|
||||
|
||||
// Insert the nodes into the default network
|
||||
network
|
||||
.nodes
|
||||
.insert(transform_node_id, transform_node_type.to_document_node_default_inputs([Some(NodeInput::node(0, 0))], next_pos()));
|
||||
network.nodes.insert(
|
||||
imaginate_node_id,
|
||||
imaginate_node_type.to_document_node_default_inputs([Some(graph_craft::document::NodeInput::node(transform_node_id, 0))], next_pos()),
|
||||
);
|
||||
|
||||
// Add a layer with a frame to the document
|
||||
responses.add(Operation::AddFrame {
|
||||
path: shape_data.path.clone().unwrap(),
|
||||
insert_index: -1,
|
||||
transform: DAffine2::ZERO.to_cols_array(),
|
||||
network,
|
||||
});
|
||||
responses.add(NodeGraphMessage::ShiftNode { node_id: imaginate_node_id });
|
||||
|
||||
Drawing
|
||||
}
|
||||
(state, Resize { center, lock_ratio }) => {
|
||||
let message = shape_data.calculate_transform(responses, document, input, center, lock_ratio, true);
|
||||
responses.try_add(message);
|
||||
|
||||
state
|
||||
}
|
||||
(Drawing, DragStop) => {
|
||||
if let Some(layer_path) = &shape_data.path {
|
||||
responses.add(DocumentMessage::InputFrameRasterizeRegionBelowLayer { layer_path: layer_path.to_vec() });
|
||||
}
|
||||
|
||||
input.mouse.finish_transaction(shape_data.viewport_drag_start(document), responses);
|
||||
shape_data.cleanup(responses);
|
||||
|
||||
Ready
|
||||
}
|
||||
(Drawing, Abort) => {
|
||||
responses.add(DocumentMessage::AbortTransaction);
|
||||
|
||||
shape_data.cleanup(responses);
|
||||
tool_data.path_outlines.clear_selected(responses);
|
||||
|
||||
Ready
|
||||
}
|
||||
(_, Abort) => {
|
||||
tool_data.path_outlines.clear_selected(responses);
|
||||
|
||||
Ready
|
||||
}
|
||||
_ => self,
|
||||
self
|
||||
}
|
||||
} else {
|
||||
self
|
||||
(ImaginateToolFsmState::Ready, ImaginateToolMessage::DragStart) => {
|
||||
tool_data.path_outlines.clear_selected(responses);
|
||||
|
||||
shape_data.start(responses, document, input, render_data);
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
shape_data.path = Some(document.get_path_for_new_layer());
|
||||
responses.add(DocumentMessage::DeselectAllLayers);
|
||||
|
||||
use graph_craft::document::*;
|
||||
|
||||
// Utility function to offset the position of each consecutive node
|
||||
let mut pos = 8;
|
||||
let mut next_pos = || {
|
||||
pos += 8;
|
||||
graph_craft::document::DocumentNodeMetadata::position((pos, 4))
|
||||
};
|
||||
|
||||
// Get the node type for the Transform and Imaginate nodes
|
||||
let Some(transform_node_type) = crate::messages::portfolio::document::node_graph::resolve_document_node_type("Transform") else {
|
||||
warn!("Transform node should be in registry");
|
||||
return ImaginateToolFsmState::Drawing;
|
||||
};
|
||||
let imaginate_node_type = &*IMAGINATE_NODE;
|
||||
|
||||
// Give them a unique ID
|
||||
let [transform_node_id, imaginate_node_id] = [100, 101];
|
||||
|
||||
// Create the network based on the Input -> Output passthrough default network
|
||||
let mut network = node_graph::new_image_network(16, imaginate_node_id);
|
||||
|
||||
// Insert the nodes into the default network
|
||||
network
|
||||
.nodes
|
||||
.insert(transform_node_id, transform_node_type.to_document_node_default_inputs([Some(NodeInput::node(0, 0))], next_pos()));
|
||||
network.nodes.insert(
|
||||
imaginate_node_id,
|
||||
imaginate_node_type.to_document_node_default_inputs([Some(graph_craft::document::NodeInput::node(transform_node_id, 0))], next_pos()),
|
||||
);
|
||||
|
||||
// Add a layer with a frame to the document
|
||||
responses.add(Operation::AddFrame {
|
||||
path: shape_data.path.clone().unwrap(),
|
||||
insert_index: -1,
|
||||
transform: DAffine2::ZERO.to_cols_array(),
|
||||
network,
|
||||
});
|
||||
responses.add(NodeGraphMessage::ShiftNode { node_id: imaginate_node_id });
|
||||
|
||||
ImaginateToolFsmState::Drawing
|
||||
}
|
||||
(state, ImaginateToolMessage::Resize { center, lock_ratio }) => {
|
||||
let message = shape_data.calculate_transform(responses, document, input, center, lock_ratio, true);
|
||||
responses.try_add(message);
|
||||
|
||||
state
|
||||
}
|
||||
(ImaginateToolFsmState::Drawing, ImaginateToolMessage::DragStop) => {
|
||||
if let Some(layer_path) = &shape_data.path {
|
||||
responses.add(DocumentMessage::InputFrameRasterizeRegionBelowLayer { layer_path: layer_path.to_vec() });
|
||||
}
|
||||
|
||||
input.mouse.finish_transaction(shape_data.viewport_drag_start(document), responses);
|
||||
shape_data.cleanup(responses);
|
||||
|
||||
ImaginateToolFsmState::Ready
|
||||
}
|
||||
(ImaginateToolFsmState::Drawing, ImaginateToolMessage::Abort) => {
|
||||
responses.add(DocumentMessage::AbortTransaction);
|
||||
|
||||
shape_data.cleanup(responses);
|
||||
tool_data.path_outlines.clear_selected(responses);
|
||||
|
||||
ImaginateToolFsmState::Ready
|
||||
}
|
||||
(_, ImaginateToolMessage::Abort) => {
|
||||
tool_data.path_outlines.clear_selected(responses);
|
||||
|
||||
ImaginateToolFsmState::Ready
|
||||
}
|
||||
_ => self,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,22 +1,13 @@
|
||||
use super::tool_prelude::*;
|
||||
use crate::consts::LINE_ROTATE_SNAP_ANGLE;
|
||||
use crate::messages::frontend::utility_types::MouseCursorIcon;
|
||||
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion};
|
||||
use crate::messages::input_mapper::utility_types::input_mouse::ViewportPosition;
|
||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||
use crate::messages::prelude::*;
|
||||
use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType};
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||
use crate::messages::tool::common_functionality::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::LayerId;
|
||||
use graphene_core::vector::style::Stroke;
|
||||
use graphene_core::Color;
|
||||
|
||||
use glam::DVec2;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct LineTool {
|
||||
fsm_state: LineToolFsmState,
|
||||
@@ -112,26 +103,24 @@ impl LayoutHolder for LineTool {
|
||||
|
||||
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for LineTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &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,
|
||||
LineOptionsUpdate::StrokeColor(color) => {
|
||||
self.options.stroke.custom_color = color;
|
||||
self.options.stroke.color_type = ToolColorType::Custom;
|
||||
}
|
||||
LineOptionsUpdate::StrokeColorType(color_type) => self.options.stroke.color_type = color_type,
|
||||
LineOptionsUpdate::WorkingColors(primary, secondary) => {
|
||||
self.options.stroke.primary_working_color = primary;
|
||||
self.options.stroke.secondary_working_color = secondary;
|
||||
}
|
||||
}
|
||||
|
||||
self.send_layout(responses, LayoutTarget::ToolOptions);
|
||||
|
||||
let ToolMessage::Line(LineToolMessage::UpdateOptions(action)) = message else {
|
||||
self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &self.options, responses, true);
|
||||
return;
|
||||
};
|
||||
match action {
|
||||
LineOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight,
|
||||
LineOptionsUpdate::StrokeColor(color) => {
|
||||
self.options.stroke.custom_color = color;
|
||||
self.options.stroke.color_type = ToolColorType::Custom;
|
||||
}
|
||||
LineOptionsUpdate::StrokeColorType(color_type) => self.options.stroke.color_type = color_type,
|
||||
LineOptionsUpdate::WorkingColors(primary, secondary) => {
|
||||
self.options.stroke.primary_working_color = primary;
|
||||
self.options.stroke.secondary_working_color = secondary;
|
||||
}
|
||||
}
|
||||
|
||||
self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &self.options, responses, true);
|
||||
self.send_layout(responses, LayoutTarget::ToolOptions);
|
||||
}
|
||||
|
||||
fn actions(&self) -> ActionList {
|
||||
@@ -162,8 +151,8 @@ enum LineToolFsmState {
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
struct LineToolData {
|
||||
drag_start: ViewportPosition,
|
||||
drag_current: ViewportPosition,
|
||||
drag_start: DVec2,
|
||||
drag_current: DVec2,
|
||||
angle: f64,
|
||||
weight: f64,
|
||||
path: Option<Vec<LayerId>>,
|
||||
@@ -174,76 +163,70 @@ impl Fsm for LineToolFsmState {
|
||||
type ToolData = LineToolData;
|
||||
type ToolOptions = LineOptions;
|
||||
|
||||
fn transition(
|
||||
self,
|
||||
event: ToolMessage,
|
||||
tool_data: &mut Self::ToolData,
|
||||
ToolActionHandlerData {
|
||||
fn transition(self, event: ToolMessage, tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionHandlerData, tool_options: &Self::ToolOptions, responses: &mut VecDeque<Message>) -> Self {
|
||||
let ToolActionHandlerData {
|
||||
document,
|
||||
global_tool_data,
|
||||
input,
|
||||
render_data,
|
||||
..
|
||||
}: &mut ToolActionHandlerData,
|
||||
tool_options: &Self::ToolOptions,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) -> Self {
|
||||
use LineToolFsmState::*;
|
||||
use LineToolMessage::*;
|
||||
} = tool_action_data;
|
||||
|
||||
if let ToolMessage::Line(event) = event {
|
||||
match (self, event) {
|
||||
(Ready, DragStart) => {
|
||||
tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(None, None, render_data), true, true);
|
||||
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 ToolMessage::Line(event) = event else {
|
||||
return self;
|
||||
};
|
||||
match (self, event) {
|
||||
(LineToolFsmState::Ready, LineToolMessage::DragStart) => {
|
||||
tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(None, None, render_data), true, true);
|
||||
tool_data.snap_manager.add_all_document_handles(document, input, &[], &[], &[]);
|
||||
|
||||
let subpath = bezier_rs::Subpath::new_line(DVec2::ZERO, DVec2::X);
|
||||
let viewport_start = tool_data.snap_manager.snap_position(responses, document, input.mouse.position);
|
||||
tool_data.drag_start = document.document_legacy.metadata.document_to_viewport.inverse().transform_point2(viewport_start);
|
||||
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
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,
|
||||
stroke: Stroke::new(tool_options.stroke.active_color(), tool_options.line_weight),
|
||||
});
|
||||
let subpath = bezier_rs::Subpath::new_line(DVec2::ZERO, DVec2::X);
|
||||
|
||||
tool_data.weight = tool_options.line_weight;
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
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,
|
||||
stroke: Stroke::new(tool_options.stroke.active_color(), tool_options.line_weight),
|
||||
});
|
||||
|
||||
Drawing
|
||||
}
|
||||
(Drawing, Redraw { center, snap_angle, lock_angle }) => {
|
||||
tool_data.drag_current = tool_data.snap_manager.snap_position(responses, document, input.mouse.position);
|
||||
tool_data.weight = tool_options.line_weight;
|
||||
|
||||
let keyboard = &input.keyboard;
|
||||
responses.add(generate_transform(tool_data, keyboard.key(lock_angle), keyboard.key(snap_angle), keyboard.key(center)));
|
||||
|
||||
Drawing
|
||||
}
|
||||
(Drawing, DragStop) => {
|
||||
tool_data.snap_manager.cleanup(responses);
|
||||
input.mouse.finish_transaction(tool_data.drag_start, responses);
|
||||
tool_data.path = None;
|
||||
Ready
|
||||
}
|
||||
(Drawing, Abort) => {
|
||||
tool_data.snap_manager.cleanup(responses);
|
||||
responses.add(DocumentMessage::AbortTransaction);
|
||||
tool_data.path = None;
|
||||
Ready
|
||||
}
|
||||
(_, WorkingColorChanged) => {
|
||||
responses.add(LineToolMessage::UpdateOptions(LineOptionsUpdate::WorkingColors(
|
||||
Some(global_tool_data.primary_color),
|
||||
Some(global_tool_data.secondary_color),
|
||||
)));
|
||||
self
|
||||
}
|
||||
_ => self,
|
||||
LineToolFsmState::Drawing
|
||||
}
|
||||
} else {
|
||||
self
|
||||
(LineToolFsmState::Drawing, LineToolMessage::Redraw { center, snap_angle, lock_angle }) => {
|
||||
tool_data.drag_current = tool_data.snap_manager.snap_position(responses, document, input.mouse.position);
|
||||
|
||||
let keyboard = &input.keyboard;
|
||||
let transform = document.document_legacy.metadata.document_to_viewport;
|
||||
responses.add(generate_transform(tool_data, transform, keyboard.key(lock_angle), keyboard.key(snap_angle), keyboard.key(center)));
|
||||
|
||||
LineToolFsmState::Drawing
|
||||
}
|
||||
(LineToolFsmState::Drawing, LineToolMessage::DragStop) => {
|
||||
tool_data.snap_manager.cleanup(responses);
|
||||
input.mouse.finish_transaction(tool_data.drag_start, responses);
|
||||
tool_data.path = None;
|
||||
LineToolFsmState::Ready
|
||||
}
|
||||
(LineToolFsmState::Drawing, LineToolMessage::Abort) => {
|
||||
tool_data.snap_manager.cleanup(responses);
|
||||
responses.add(DocumentMessage::AbortTransaction);
|
||||
tool_data.path = None;
|
||||
LineToolFsmState::Ready
|
||||
}
|
||||
(_, LineToolMessage::WorkingColorChanged) => {
|
||||
responses.add(LineToolMessage::UpdateOptions(LineOptionsUpdate::WorkingColors(
|
||||
Some(global_tool_data.primary_color),
|
||||
Some(global_tool_data.secondary_color),
|
||||
)));
|
||||
self
|
||||
}
|
||||
_ => self,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -270,8 +253,8 @@ impl Fsm for LineToolFsmState {
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_transform(tool_data: &mut LineToolData, lock_angle: bool, snap_angle: bool, center: bool) -> Message {
|
||||
let mut start = tool_data.drag_start;
|
||||
fn generate_transform(tool_data: &mut LineToolData, document_to_viewport: DAffine2, lock_angle: bool, snap_angle: bool, center: bool) -> Message {
|
||||
let mut start = document_to_viewport.transform_point2(tool_data.drag_start);
|
||||
let line_vector = tool_data.drag_current - start;
|
||||
|
||||
let mut angle = -line_vector.angle_between(DVec2::X);
|
||||
|
||||
@@ -16,3 +16,15 @@ pub mod rectangle_tool;
|
||||
pub mod select_tool;
|
||||
pub mod spline_tool;
|
||||
pub mod text_tool;
|
||||
|
||||
pub mod tool_prelude {
|
||||
pub use crate::messages::frontend::utility_types::MouseCursorIcon;
|
||||
pub use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion};
|
||||
pub use crate::messages::layout::utility_types::widget_prelude::*;
|
||||
pub use crate::messages::prelude::*;
|
||||
pub use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType};
|
||||
pub use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
|
||||
|
||||
pub use glam::{DAffine2, DVec2};
|
||||
pub use serde::{Deserialize, Serialize};
|
||||
}
|
||||
|
||||
@@ -1,12 +1,4 @@
|
||||
use crate::messages::frontend::utility_types::MouseCursorIcon;
|
||||
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion};
|
||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||
use crate::messages::prelude::*;
|
||||
use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType};
|
||||
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
|
||||
|
||||
use glam::DVec2;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use super::tool_prelude::*;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct NavigateTool {
|
||||
@@ -112,59 +104,57 @@ impl Fsm for NavigateToolFsmState {
|
||||
_tool_options: &Self::ToolOptions,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) -> Self {
|
||||
if let ToolMessage::Navigate(navigate) = message {
|
||||
use NavigateToolMessage::*;
|
||||
let ToolMessage::Navigate(navigate) = message else{
|
||||
return self;
|
||||
};
|
||||
|
||||
match navigate {
|
||||
ClickZoom { zoom_in } => {
|
||||
responses.add_front(NavigationMessage::TransformCanvasEnd { abort_transform: false });
|
||||
match navigate {
|
||||
NavigateToolMessage::ClickZoom { zoom_in } => {
|
||||
responses.add_front(NavigationMessage::TransformCanvasEnd { abort_transform: false });
|
||||
|
||||
// Mouse has not moved from pointerdown to pointerup
|
||||
if tool_data.drag_start == input.mouse.position {
|
||||
responses.add_front(if zoom_in {
|
||||
NavigationMessage::IncreaseCanvasZoom { center_on_mouse: true }
|
||||
} else {
|
||||
NavigationMessage::DecreaseCanvasZoom { center_on_mouse: true }
|
||||
});
|
||||
}
|
||||
|
||||
NavigateToolFsmState::Ready
|
||||
}
|
||||
PointerMove { snap_angle, snap_zoom } => {
|
||||
responses.add_front(NavigationMessage::PointerMove {
|
||||
snap_angle,
|
||||
wait_for_snap_angle_release: false,
|
||||
snap_zoom,
|
||||
zoom_from_viewport: Some(tool_data.drag_start),
|
||||
// Mouse has not moved from pointerdown to pointerup
|
||||
if tool_data.drag_start == input.mouse.position {
|
||||
responses.add_front(if zoom_in {
|
||||
NavigationMessage::IncreaseCanvasZoom { center_on_mouse: true }
|
||||
} else {
|
||||
NavigationMessage::DecreaseCanvasZoom { center_on_mouse: true }
|
||||
});
|
||||
self
|
||||
}
|
||||
TranslateCanvasBegin => {
|
||||
tool_data.drag_start = input.mouse.position;
|
||||
responses.add_front(NavigationMessage::TranslateCanvasBegin);
|
||||
NavigateToolFsmState::Panning
|
||||
}
|
||||
RotateCanvasBegin => {
|
||||
tool_data.drag_start = input.mouse.position;
|
||||
responses.add_front(NavigationMessage::RotateCanvasBegin { was_dispatched_from_menu: false });
|
||||
NavigateToolFsmState::Tilting
|
||||
}
|
||||
ZoomCanvasBegin => {
|
||||
tool_data.drag_start = input.mouse.position;
|
||||
responses.add_front(NavigationMessage::ZoomCanvasBegin);
|
||||
NavigateToolFsmState::Zooming
|
||||
}
|
||||
TransformCanvasEnd => {
|
||||
responses.add_front(NavigationMessage::TransformCanvasEnd { abort_transform: false });
|
||||
NavigateToolFsmState::Ready
|
||||
}
|
||||
Abort => {
|
||||
responses.add_front(NavigationMessage::TransformCanvasEnd { abort_transform: false });
|
||||
NavigateToolFsmState::Ready
|
||||
}
|
||||
|
||||
NavigateToolFsmState::Ready
|
||||
}
|
||||
NavigateToolMessage::PointerMove { snap_angle, snap_zoom } => {
|
||||
responses.add_front(NavigationMessage::PointerMove {
|
||||
snap_angle,
|
||||
wait_for_snap_angle_release: false,
|
||||
snap_zoom,
|
||||
zoom_from_viewport: Some(tool_data.drag_start),
|
||||
});
|
||||
self
|
||||
}
|
||||
NavigateToolMessage::TranslateCanvasBegin => {
|
||||
tool_data.drag_start = input.mouse.position;
|
||||
responses.add_front(NavigationMessage::TranslateCanvasBegin);
|
||||
NavigateToolFsmState::Panning
|
||||
}
|
||||
NavigateToolMessage::RotateCanvasBegin => {
|
||||
tool_data.drag_start = input.mouse.position;
|
||||
responses.add_front(NavigationMessage::RotateCanvasBegin { was_dispatched_from_menu: false });
|
||||
NavigateToolFsmState::Tilting
|
||||
}
|
||||
NavigateToolMessage::ZoomCanvasBegin => {
|
||||
tool_data.drag_start = input.mouse.position;
|
||||
responses.add_front(NavigationMessage::ZoomCanvasBegin);
|
||||
NavigateToolFsmState::Zooming
|
||||
}
|
||||
NavigateToolMessage::TransformCanvasEnd => {
|
||||
responses.add_front(NavigationMessage::TransformCanvasEnd { abort_transform: false });
|
||||
NavigateToolFsmState::Ready
|
||||
}
|
||||
NavigateToolMessage::Abort => {
|
||||
responses.add_front(NavigationMessage::TransformCanvasEnd { abort_transform: false });
|
||||
NavigateToolFsmState::Ready
|
||||
}
|
||||
} else {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,24 +1,18 @@
|
||||
use std::vec;
|
||||
|
||||
use super::tool_prelude::*;
|
||||
use crate::consts::{DRAG_THRESHOLD, SELECTION_THRESHOLD, SELECTION_TOLERANCE};
|
||||
use crate::messages::frontend::utility_types::MouseCursorIcon;
|
||||
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion};
|
||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||
use crate::messages::prelude::*;
|
||||
use crate::messages::tool::common_functionality::overlay_renderer::OverlayRenderer;
|
||||
use crate::messages::tool::common_functionality::shape_editor::{ManipulatorAngle, ManipulatorPointInfo, OpposingHandleLengths, ShapeState};
|
||||
use crate::messages::tool::common_functionality::snapping::SnapManager;
|
||||
use crate::messages::tool::common_functionality::transformation_cage::{add_bounding_box, transform_from_box};
|
||||
use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, HintData, HintGroup, HintInfo, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType};
|
||||
|
||||
use document_legacy::document::Document;
|
||||
use document_legacy::document_metadata::LayerNodeIdentifier;
|
||||
use document_legacy::intersection::Quad;
|
||||
use document_legacy::{LayerId, Operation};
|
||||
use graphene_core::vector::{ManipulatorPointId, SelectedType};
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct PathTool {
|
||||
fsm_state: PathToolFsmState,
|
||||
@@ -219,12 +213,13 @@ 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);
|
||||
let layer = LayerNodeIdentifier::from_path(layer_path, document.network());
|
||||
shape_overlay.layer_overlay_visibility(&document.document_legacy, layer, 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);
|
||||
for &layer in shape_editor.selected_shape_state.keys() {
|
||||
shape_overlay.render_subpath_overlays(&shape_editor.selected_shape_state, &document.document_legacy, layer, responses);
|
||||
}
|
||||
|
||||
self.opposing_handle_lengths = None;
|
||||
@@ -235,313 +230,313 @@ impl Fsm for PathToolFsmState {
|
||||
type ToolData = PathToolData;
|
||||
type ToolOptions = ();
|
||||
|
||||
fn transition(
|
||||
self,
|
||||
event: ToolMessage,
|
||||
tool_data: &mut Self::ToolData,
|
||||
ToolActionHandlerData {
|
||||
fn transition(self, event: ToolMessage, tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionHandlerData, _tool_options: &(), responses: &mut VecDeque<Message>) -> Self {
|
||||
let 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 newly targeted layers to visible
|
||||
let layer_paths = document.selected_visible_layers().map(|layer_path| layer_path.to_vec()).collect();
|
||||
shape_editor.set_selected_layers(layer_paths);
|
||||
} = tool_action_data;
|
||||
let ToolMessage::Path(event) = event else {
|
||||
return self;
|
||||
};
|
||||
match (self, event) {
|
||||
(_, PathToolMessage::SelectionChanged) => {
|
||||
// Set the newly targeted layers to visible
|
||||
let layer_paths = document
|
||||
.selected_visible_layers()
|
||||
.map(|layer_path| LayerNodeIdentifier::from_path(layer_path, document.network()))
|
||||
.collect();
|
||||
shape_editor.set_selected_layers(layer_paths);
|
||||
|
||||
tool_data.refresh_overlays(document, shape_editor, shape_overlay, responses);
|
||||
|
||||
responses.add(PathToolMessage::SelectedPointUpdated);
|
||||
// This can happen in any state (which is why we return self)
|
||||
self
|
||||
}
|
||||
(_, PathToolMessage::DocumentIsDirty) => {
|
||||
// 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() {
|
||||
let layer = LayerNodeIdentifier::from_path(layer_path, document.network());
|
||||
shape_overlay.render_subpath_overlays(&shape_editor.selected_shape_state, &document.document_legacy, layer, responses);
|
||||
}
|
||||
|
||||
responses.add(PathToolMessage::SelectedPointUpdated);
|
||||
|
||||
self
|
||||
}
|
||||
// Mouse down
|
||||
(_, PathToolMessage::DragStart { add_to_selection }) => {
|
||||
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::<Vec<_>>();
|
||||
|
||||
// Select the first point within the threshold (in pixels)
|
||||
if let Some(mut selected_points) = shape_editor.select_point(&document.document_legacy, input.mouse.position, SELECTION_THRESHOLD, shift_pressed) {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
|
||||
//tool_data
|
||||
// .snap_manager
|
||||
// .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 additional_selected_points = Vec::new();
|
||||
for point in selected_points.points.iter() {
|
||||
if point.point_id.manipulator_type == SelectedType::Anchor {
|
||||
additional_selected_points.push(ManipulatorPointInfo {
|
||||
layer: point.layer,
|
||||
point_id: ManipulatorPointId::new(point.point_id.group, SelectedType::InHandle),
|
||||
});
|
||||
additional_selected_points.push(ManipulatorPointInfo {
|
||||
layer: point.layer,
|
||||
point_id: ManipulatorPointId::new(point.point_id.group, SelectedType::OutHandle),
|
||||
});
|
||||
}
|
||||
}
|
||||
selected_points.points.extend(additional_selected_points);
|
||||
|
||||
//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);
|
||||
|
||||
responses.add(PathToolMessage::SelectedPointUpdated);
|
||||
// This can happen in any state (which is why we return self)
|
||||
self
|
||||
}
|
||||
(_, PathToolMessage::DocumentIsDirty) => {
|
||||
// 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() {
|
||||
shape_overlay.render_subpath_overlays(&shape_editor.selected_shape_state, &document.document_legacy, layer_path.to_vec(), responses);
|
||||
}
|
||||
|
||||
responses.add(PathToolMessage::SelectedPointUpdated);
|
||||
|
||||
self
|
||||
}
|
||||
// Mouse down
|
||||
(_, PathToolMessage::DragStart { add_to_selection }) => {
|
||||
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) = shape_editor.select_point(&document.document_legacy, input.mouse.position, SELECTION_THRESHOLD, shift_pressed) {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
|
||||
tool_data
|
||||
.snap_manager
|
||||
.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 additional_selected_points = Vec::new();
|
||||
for point in selected_points.points.iter() {
|
||||
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),
|
||||
});
|
||||
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(additional_selected_points);
|
||||
|
||||
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);
|
||||
|
||||
responses.add(PathToolMessage::SelectedPointUpdated);
|
||||
PathToolFsmState::Dragging
|
||||
}
|
||||
// We didn't find a point nearby, so consider selecting the nearest shape instead
|
||||
else {
|
||||
let selection_size = DVec2::new(2.0, 2.0);
|
||||
// Select shapes directly under our mouse
|
||||
let intersection = document
|
||||
.document_legacy
|
||||
.intersects_quad_root(Quad::from_box([input.mouse.position - selection_size, input.mouse.position + selection_size]), render_data);
|
||||
if !intersection.is_empty() {
|
||||
if shift_pressed {
|
||||
responses.add(DocumentMessage::AddSelectedLayers { additional_layers: intersection });
|
||||
} else {
|
||||
// Selects the topmost layer when selecting intersecting shapes
|
||||
let top_most_intersection = intersection[intersection.len() - 1].clone();
|
||||
responses.add(DocumentMessage::SetSelectedLayers {
|
||||
replacement_selected_layers: vec![top_most_intersection.clone()],
|
||||
});
|
||||
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.
|
||||
shape_editor.select_all_anchors(&document.document_legacy, &top_most_intersection);
|
||||
return PathToolFsmState::Dragging;
|
||||
}
|
||||
} else {
|
||||
// an empty intersection means that the user is drawing a box
|
||||
tool_data.drag_start_pos = input.mouse.position;
|
||||
tool_data.previous_mouse_position = input.mouse.position;
|
||||
|
||||
tool_data.drag_box_overlay_layer = Some(add_bounding_box(responses));
|
||||
return PathToolFsmState::DrawingBox;
|
||||
}
|
||||
|
||||
PathToolFsmState::Ready
|
||||
}
|
||||
}
|
||||
(PathToolFsmState::DrawingBox, PathToolMessage::PointerMove { .. }) => {
|
||||
tool_data.previous_mouse_position = input.mouse.position;
|
||||
|
||||
responses.add_front(DocumentMessage::Overlays(
|
||||
Operation::SetLayerTransformInViewport {
|
||||
path: tool_data.drag_box_overlay_layer.clone().unwrap(),
|
||||
transform: transform_from_box(tool_data.drag_start_pos, tool_data.previous_mouse_position, DAffine2::IDENTITY).to_cols_array(),
|
||||
}
|
||||
.into(),
|
||||
));
|
||||
PathToolFsmState::DrawingBox
|
||||
}
|
||||
|
||||
// Dragging
|
||||
(
|
||||
PathToolFsmState::Dragging,
|
||||
PathToolMessage::PointerMove {
|
||||
alt_mirror_angle,
|
||||
shift_mirror_distance,
|
||||
},
|
||||
) => {
|
||||
// Determine when alt state changes
|
||||
let alt_pressed = input.keyboard.get(alt_mirror_angle as usize);
|
||||
|
||||
// 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(shape_editor.opposing_handle_lengths(&document.document_legacy));
|
||||
}
|
||||
} else if let Some(opposing_handle_lengths) = &tool_data.opposing_handle_lengths {
|
||||
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);
|
||||
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
|
||||
}
|
||||
|
||||
(PathToolFsmState::DrawingBox, PathToolMessage::Enter { add_to_selection }) => {
|
||||
let shift_pressed = input.keyboard.get(add_to_selection as usize);
|
||||
|
||||
if tool_data.drag_start_pos == tool_data.previous_mouse_position {
|
||||
responses.add(DocumentMessage::DeselectAllLayers);
|
||||
// We didn't find a point nearby, so consider selecting the nearest shape instead
|
||||
else {
|
||||
let selection_size = DVec2::new(2.0, 2.0);
|
||||
// Select shapes directly under our mouse
|
||||
let intersection = document
|
||||
.document_legacy
|
||||
.intersects_quad_root(Quad::from_box([input.mouse.position - selection_size, input.mouse.position + selection_size]), render_data);
|
||||
if !intersection.is_empty() {
|
||||
if shift_pressed {
|
||||
responses.add(DocumentMessage::AddSelectedLayers { additional_layers: intersection });
|
||||
} else {
|
||||
// Selects the topmost layer when selecting intersecting shapes
|
||||
let top_most_intersection = intersection[intersection.len() - 1].clone();
|
||||
responses.add(DocumentMessage::SetSelectedLayers {
|
||||
replacement_selected_layers: vec![top_most_intersection.clone()],
|
||||
});
|
||||
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.
|
||||
let layer = LayerNodeIdentifier::from_path(&top_most_intersection, document.network());
|
||||
shape_editor.select_all_anchors(&document.document_legacy, layer);
|
||||
return PathToolFsmState::Dragging;
|
||||
}
|
||||
} else {
|
||||
shape_editor.select_all_in_quad(&document.document_legacy, [tool_data.drag_start_pos, tool_data.previous_mouse_position], !shift_pressed);
|
||||
tool_data.refresh_overlays(document, shape_editor, shape_overlay, responses);
|
||||
};
|
||||
// an empty intersection means that the user is drawing a box
|
||||
tool_data.drag_start_pos = input.mouse.position;
|
||||
tool_data.previous_mouse_position = input.mouse.position;
|
||||
|
||||
responses.add_front(DocumentMessage::Overlays(
|
||||
Operation::DeleteLayer {
|
||||
path: tool_data.drag_box_overlay_layer.take().unwrap(),
|
||||
}
|
||||
.into(),
|
||||
));
|
||||
|
||||
responses.add(PathToolMessage::SelectedPointUpdated);
|
||||
PathToolFsmState::Ready
|
||||
}
|
||||
|
||||
// Mouse up
|
||||
(PathToolFsmState::DrawingBox, PathToolMessage::DragStop { shift_mirror_distance }) => {
|
||||
let shift_pressed = input.keyboard.get(shift_mirror_distance as usize);
|
||||
|
||||
if tool_data.drag_start_pos == tool_data.previous_mouse_position {
|
||||
responses.add(DocumentMessage::DeselectAllLayers);
|
||||
} else {
|
||||
shape_editor.select_all_in_quad(&document.document_legacy, [tool_data.drag_start_pos, tool_data.previous_mouse_position], !shift_pressed);
|
||||
tool_data.refresh_overlays(document, shape_editor, shape_overlay, responses);
|
||||
};
|
||||
|
||||
responses.add_front(DocumentMessage::Overlays(
|
||||
Operation::DeleteLayer {
|
||||
path: tool_data.drag_box_overlay_layer.take().unwrap(),
|
||||
}
|
||||
.into(),
|
||||
));
|
||||
|
||||
responses.add(PathToolMessage::SelectedPointUpdated);
|
||||
PathToolFsmState::Ready
|
||||
}
|
||||
(_, PathToolMessage::DragStop { shift_mirror_distance }) => {
|
||||
let shift_pressed = input.keyboard.get(shift_mirror_distance as usize);
|
||||
|
||||
let nearest_point = shape_editor
|
||||
.find_nearest_point_indices(&document.document_legacy, input.mouse.position, SELECTION_THRESHOLD)
|
||||
.map(|(_, nearest_point)| nearest_point);
|
||||
|
||||
shape_editor.delete_selected_handles_with_zero_length(&document.document_legacy, &tool_data.opposing_handle_lengths, responses);
|
||||
|
||||
if tool_data.drag_start_pos.distance(input.mouse.position) <= DRAG_THRESHOLD && !shift_pressed {
|
||||
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);
|
||||
tool_data.refresh_overlays(document, shape_editor, shape_overlay, responses);
|
||||
}
|
||||
tool_data.drag_box_overlay_layer = Some(add_bounding_box(responses));
|
||||
return PathToolFsmState::DrawingBox;
|
||||
}
|
||||
|
||||
responses.add(PathToolMessage::SelectedPointUpdated);
|
||||
tool_data.snap_manager.cleanup(responses);
|
||||
PathToolFsmState::Ready
|
||||
}
|
||||
|
||||
// Delete key
|
||||
(_, PathToolMessage::Delete) => {
|
||||
// Delete the selected points and clean up overlays
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
shape_editor.delete_selected_points(responses);
|
||||
responses.add(PathToolMessage::SelectionChanged);
|
||||
for layer_path in document.all_layers() {
|
||||
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 !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
|
||||
shape_editor.split(&document.document_legacy, input.mouse.position, SELECTION_TOLERANCE, responses);
|
||||
}
|
||||
|
||||
responses.add(PathToolMessage::SelectedPointUpdated);
|
||||
self
|
||||
}
|
||||
(_, PathToolMessage::Abort) => {
|
||||
// TODO Tell overlay manager to remove the overlays
|
||||
for layer_path in document.all_layers() {
|
||||
shape_overlay.clear_subpath_overlays(&document.document_legacy, layer_path.to_vec(), responses);
|
||||
}
|
||||
PathToolFsmState::Ready
|
||||
}
|
||||
(
|
||||
_,
|
||||
PathToolMessage::PointerMove {
|
||||
alt_mirror_angle: _,
|
||||
shift_mirror_distance: _,
|
||||
},
|
||||
) => self,
|
||||
(_, PathToolMessage::NudgeSelectedPoints { delta_x, delta_y }) => {
|
||||
shape_editor.move_selected_points(&document.document_legacy, (delta_x, delta_y).into(), true, responses);
|
||||
PathToolFsmState::Ready
|
||||
}
|
||||
(_, PathToolMessage::SelectAllPoints) => {
|
||||
shape_editor.select_all_points(&document.document_legacy);
|
||||
tool_data.refresh_overlays(document, shape_editor, shape_overlay, responses);
|
||||
PathToolFsmState::Ready
|
||||
}
|
||||
(_, PathToolMessage::SelectedPointXChanged { new_x }) => {
|
||||
if let Some(&SingleSelectedPoint { coordinates, id, ref layer_path, .. }) = tool_data.selection_status.as_one() {
|
||||
shape_editor.reposition_control_point(&id, responses, &document.document_legacy, DVec2::new(new_x, coordinates.y), layer_path);
|
||||
}
|
||||
PathToolFsmState::Ready
|
||||
}
|
||||
(_, PathToolMessage::SelectedPointYChanged { new_y }) => {
|
||||
if let Some(&SingleSelectedPoint { coordinates, id, ref layer_path, .. }) = tool_data.selection_status.as_one() {
|
||||
shape_editor.reposition_control_point(&id, responses, &document.document_legacy, DVec2::new(coordinates.x, new_y), layer_path);
|
||||
}
|
||||
PathToolFsmState::Ready
|
||||
}
|
||||
(_, PathToolMessage::SelectedPointUpdated) => {
|
||||
tool_data.selection_status = get_selection_status(&document.document_legacy, shape_editor);
|
||||
self
|
||||
}
|
||||
(_, PathToolMessage::ManipulatorAngleMakeSmooth) => {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
shape_editor.set_handle_mirroring_on_selected(true, responses);
|
||||
shape_editor.smooth_selected_groups(responses, &document.document_legacy);
|
||||
responses.add(DocumentMessage::CommitTransaction);
|
||||
PathToolFsmState::Ready
|
||||
}
|
||||
(_, PathToolMessage::ManipulatorAngleMakeSharp) => {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
shape_editor.set_handle_mirroring_on_selected(false, responses);
|
||||
responses.add(DocumentMessage::CommitTransaction);
|
||||
PathToolFsmState::Ready
|
||||
}
|
||||
(_, _) => PathToolFsmState::Ready,
|
||||
}
|
||||
} else {
|
||||
self
|
||||
(PathToolFsmState::DrawingBox, PathToolMessage::PointerMove { .. }) => {
|
||||
tool_data.previous_mouse_position = input.mouse.position;
|
||||
|
||||
responses.add_front(DocumentMessage::Overlays(
|
||||
Operation::SetLayerTransformInViewport {
|
||||
path: tool_data.drag_box_overlay_layer.clone().unwrap(),
|
||||
transform: transform_from_box(tool_data.drag_start_pos, tool_data.previous_mouse_position, DAffine2::IDENTITY).to_cols_array(),
|
||||
}
|
||||
.into(),
|
||||
));
|
||||
PathToolFsmState::DrawingBox
|
||||
}
|
||||
|
||||
// Dragging
|
||||
(
|
||||
PathToolFsmState::Dragging,
|
||||
PathToolMessage::PointerMove {
|
||||
alt_mirror_angle,
|
||||
shift_mirror_distance,
|
||||
},
|
||||
) => {
|
||||
// Determine when alt state changes
|
||||
let alt_pressed = input.keyboard.get(alt_mirror_angle as usize);
|
||||
|
||||
// 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(shape_editor.opposing_handle_lengths(&document.document_legacy));
|
||||
}
|
||||
} else if let Some(opposing_handle_lengths) = &tool_data.opposing_handle_lengths {
|
||||
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);
|
||||
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
|
||||
}
|
||||
|
||||
(PathToolFsmState::DrawingBox, PathToolMessage::Enter { add_to_selection }) => {
|
||||
let shift_pressed = input.keyboard.get(add_to_selection as usize);
|
||||
|
||||
if tool_data.drag_start_pos == tool_data.previous_mouse_position {
|
||||
responses.add(DocumentMessage::DeselectAllLayers);
|
||||
} else {
|
||||
shape_editor.select_all_in_quad(&document.document_legacy, [tool_data.drag_start_pos, tool_data.previous_mouse_position], !shift_pressed);
|
||||
tool_data.refresh_overlays(document, shape_editor, shape_overlay, responses);
|
||||
};
|
||||
|
||||
responses.add_front(DocumentMessage::Overlays(
|
||||
Operation::DeleteLayer {
|
||||
path: tool_data.drag_box_overlay_layer.take().unwrap(),
|
||||
}
|
||||
.into(),
|
||||
));
|
||||
|
||||
responses.add(PathToolMessage::SelectedPointUpdated);
|
||||
PathToolFsmState::Ready
|
||||
}
|
||||
|
||||
// Mouse up
|
||||
(PathToolFsmState::DrawingBox, PathToolMessage::DragStop { shift_mirror_distance }) => {
|
||||
let shift_pressed = input.keyboard.get(shift_mirror_distance as usize);
|
||||
|
||||
if tool_data.drag_start_pos == tool_data.previous_mouse_position {
|
||||
responses.add(DocumentMessage::DeselectAllLayers);
|
||||
} else {
|
||||
shape_editor.select_all_in_quad(&document.document_legacy, [tool_data.drag_start_pos, tool_data.previous_mouse_position], !shift_pressed);
|
||||
tool_data.refresh_overlays(document, shape_editor, shape_overlay, responses);
|
||||
};
|
||||
|
||||
responses.add_front(DocumentMessage::Overlays(
|
||||
Operation::DeleteLayer {
|
||||
path: tool_data.drag_box_overlay_layer.take().unwrap(),
|
||||
}
|
||||
.into(),
|
||||
));
|
||||
|
||||
responses.add(PathToolMessage::SelectedPointUpdated);
|
||||
PathToolFsmState::Ready
|
||||
}
|
||||
(_, PathToolMessage::DragStop { shift_mirror_distance }) => {
|
||||
let shift_pressed = input.keyboard.get(shift_mirror_distance as usize);
|
||||
|
||||
let nearest_point = shape_editor
|
||||
.find_nearest_point_indices(&document.document_legacy, input.mouse.position, SELECTION_THRESHOLD)
|
||||
.map(|(_, nearest_point)| nearest_point);
|
||||
|
||||
shape_editor.delete_selected_handles_with_zero_length(&document.document_legacy, &tool_data.opposing_handle_lengths, responses);
|
||||
|
||||
if tool_data.drag_start_pos.distance(input.mouse.position) <= DRAG_THRESHOLD && !shift_pressed {
|
||||
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);
|
||||
tool_data.refresh_overlays(document, shape_editor, shape_overlay, responses);
|
||||
}
|
||||
}
|
||||
|
||||
responses.add(PathToolMessage::SelectedPointUpdated);
|
||||
tool_data.snap_manager.cleanup(responses);
|
||||
PathToolFsmState::Ready
|
||||
}
|
||||
|
||||
// Delete key
|
||||
(_, PathToolMessage::Delete) => {
|
||||
// Delete the selected points and clean up overlays
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
shape_editor.delete_selected_points(responses);
|
||||
responses.add(PathToolMessage::SelectionChanged);
|
||||
for layer_path in document.all_layers() {
|
||||
let layer = LayerNodeIdentifier::from_path(layer_path, document.network());
|
||||
shape_overlay.clear_subpath_overlays(&document.document_legacy, layer, responses);
|
||||
}
|
||||
PathToolFsmState::Ready
|
||||
}
|
||||
(_, PathToolMessage::InsertPoint) => {
|
||||
// First we try and flip the sharpness (if they have clicked on an anchor)
|
||||
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
|
||||
shape_editor.split(&document.document_legacy, input.mouse.position, SELECTION_TOLERANCE, responses);
|
||||
}
|
||||
|
||||
responses.add(PathToolMessage::SelectedPointUpdated);
|
||||
self
|
||||
}
|
||||
(_, PathToolMessage::Abort) => {
|
||||
// TODO Tell overlay manager to remove the overlays
|
||||
for layer_path in document.all_layers() {
|
||||
let layer = LayerNodeIdentifier::from_path(layer_path, document.network());
|
||||
shape_overlay.clear_subpath_overlays(&document.document_legacy, layer, responses);
|
||||
}
|
||||
PathToolFsmState::Ready
|
||||
}
|
||||
(
|
||||
_,
|
||||
PathToolMessage::PointerMove {
|
||||
alt_mirror_angle: _,
|
||||
shift_mirror_distance: _,
|
||||
},
|
||||
) => self,
|
||||
(_, PathToolMessage::NudgeSelectedPoints { delta_x, delta_y }) => {
|
||||
shape_editor.move_selected_points(&document.document_legacy, (delta_x, delta_y).into(), true, responses);
|
||||
PathToolFsmState::Ready
|
||||
}
|
||||
(_, PathToolMessage::SelectAllPoints) => {
|
||||
shape_editor.select_all_points(&document.document_legacy);
|
||||
tool_data.refresh_overlays(document, shape_editor, shape_overlay, responses);
|
||||
PathToolFsmState::Ready
|
||||
}
|
||||
(_, PathToolMessage::SelectedPointXChanged { new_x }) => {
|
||||
if let Some(&SingleSelectedPoint { coordinates, id, ref layer_path, .. }) = tool_data.selection_status.as_one() {
|
||||
shape_editor.reposition_control_point(&id, responses, &document.document_legacy, DVec2::new(new_x, coordinates.y), layer_path);
|
||||
}
|
||||
PathToolFsmState::Ready
|
||||
}
|
||||
(_, PathToolMessage::SelectedPointYChanged { new_y }) => {
|
||||
if let Some(&SingleSelectedPoint { coordinates, id, ref layer_path, .. }) = tool_data.selection_status.as_one() {
|
||||
shape_editor.reposition_control_point(&id, responses, &document.document_legacy, DVec2::new(coordinates.x, new_y), layer_path);
|
||||
}
|
||||
PathToolFsmState::Ready
|
||||
}
|
||||
(_, PathToolMessage::SelectedPointUpdated) => {
|
||||
tool_data.selection_status = get_selection_status(&document.document_legacy, shape_editor);
|
||||
self
|
||||
}
|
||||
(_, PathToolMessage::ManipulatorAngleMakeSmooth) => {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
shape_editor.set_handle_mirroring_on_selected(true, responses);
|
||||
shape_editor.smooth_selected_groups(responses, &document.document_legacy);
|
||||
responses.add(DocumentMessage::CommitTransaction);
|
||||
PathToolFsmState::Ready
|
||||
}
|
||||
(_, PathToolMessage::ManipulatorAngleMakeSharp) => {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
shape_editor.set_handle_mirroring_on_selected(false, responses);
|
||||
responses.add(DocumentMessage::CommitTransaction);
|
||||
PathToolFsmState::Ready
|
||||
}
|
||||
(_, _) => PathToolFsmState::Ready,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -620,7 +615,7 @@ fn get_selection_status(document: &Document, shape_state: &mut ShapeState) -> Se
|
||||
// Check to see if only one manipulator group is selected
|
||||
let selection_layers: Vec<_> = shape_state.selected_shape_state.iter().take(2).map(|(k, v)| (k, v.selected_points_count())).collect();
|
||||
if let [(layer, 1)] = selection_layers[..] {
|
||||
let Some(layer_data) = document.layer(layer).ok() else { return SelectionStatus::None };
|
||||
let Some(layer_data) = document.layer(&layer.to_path()).ok() else { return SelectionStatus::None };
|
||||
let Some(vector_data) = layer_data.as_vector_data() else { return SelectionStatus::None };
|
||||
let Some(point) = shape_state.selected_points().next() else {
|
||||
return SelectionStatus::None;
|
||||
@@ -641,7 +636,7 @@ fn get_selection_status(document: &Document, shape_state: &mut ShapeState) -> Se
|
||||
|
||||
return SelectionStatus::One(SingleSelectedPoint {
|
||||
coordinates: layer_data.transform.transform_point2(local_position) + layer_data.pivot,
|
||||
layer_path: layer.clone(),
|
||||
layer_path: layer.to_path(),
|
||||
id: *point,
|
||||
manipulator_angle,
|
||||
});
|
||||
|
||||
@@ -1,28 +1,22 @@
|
||||
use super::tool_prelude::*;
|
||||
use crate::consts::LINE_ROTATE_SNAP_ANGLE;
|
||||
use crate::messages::frontend::utility_types::MouseCursorIcon;
|
||||
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion};
|
||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||
use crate::messages::portfolio::document::node_graph::VectorDataModification;
|
||||
use crate::messages::prelude::*;
|
||||
use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType};
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||
use crate::messages::tool::common_functionality::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 crate::messages::tool::tool_messages::pen_tool::graph_modification_utils::NodeGraphLayer;
|
||||
|
||||
use bezier_rs::Subpath;
|
||||
use document_legacy::document::Document;
|
||||
use document_legacy::document_metadata::LayerNodeIdentifier;
|
||||
use document_legacy::LayerId;
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::NodeInput;
|
||||
use graphene_core::uuid::ManipulatorGroupId;
|
||||
use graphene_core::vector::style::{Fill, Stroke};
|
||||
use graphene_core::vector::{ManipulatorPointId, SelectedType};
|
||||
use graphene_core::Color;
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct PenTool {
|
||||
fsm_state: PenToolFsmState,
|
||||
@@ -144,33 +138,31 @@ impl LayoutHolder for PenTool {
|
||||
|
||||
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,
|
||||
PenOptionsUpdate::FillColor(color) => {
|
||||
self.options.fill.custom_color = color;
|
||||
self.options.fill.color_type = ToolColorType::Custom;
|
||||
}
|
||||
PenOptionsUpdate::FillColorType(color_type) => self.options.fill.color_type = color_type,
|
||||
PenOptionsUpdate::StrokeColor(color) => {
|
||||
self.options.stroke.custom_color = color;
|
||||
self.options.stroke.color_type = ToolColorType::Custom;
|
||||
}
|
||||
PenOptionsUpdate::StrokeColorType(color_type) => self.options.stroke.color_type = color_type,
|
||||
PenOptionsUpdate::WorkingColors(primary, secondary) => {
|
||||
self.options.stroke.primary_working_color = primary;
|
||||
self.options.stroke.secondary_working_color = secondary;
|
||||
self.options.fill.primary_working_color = primary;
|
||||
self.options.fill.secondary_working_color = secondary;
|
||||
}
|
||||
}
|
||||
|
||||
self.send_layout(responses, LayoutTarget::ToolOptions);
|
||||
|
||||
let ToolMessage::Pen(PenToolMessage::UpdateOptions(action)) = message else {
|
||||
self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &self.options, responses, true);
|
||||
return;
|
||||
};
|
||||
match action {
|
||||
PenOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight,
|
||||
PenOptionsUpdate::FillColor(color) => {
|
||||
self.options.fill.custom_color = color;
|
||||
self.options.fill.color_type = ToolColorType::Custom;
|
||||
}
|
||||
PenOptionsUpdate::FillColorType(color_type) => self.options.fill.color_type = color_type,
|
||||
PenOptionsUpdate::StrokeColor(color) => {
|
||||
self.options.stroke.custom_color = color;
|
||||
self.options.stroke.color_type = ToolColorType::Custom;
|
||||
}
|
||||
PenOptionsUpdate::StrokeColorType(color_type) => self.options.stroke.color_type = color_type,
|
||||
PenOptionsUpdate::WorkingColors(primary, secondary) => {
|
||||
self.options.stroke.primary_working_color = primary;
|
||||
self.options.stroke.secondary_working_color = secondary;
|
||||
self.options.fill.primary_working_color = primary;
|
||||
self.options.fill.secondary_working_color = secondary;
|
||||
}
|
||||
}
|
||||
|
||||
self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &self.options, responses, true);
|
||||
self.send_layout(responses, LayoutTarget::ToolOptions);
|
||||
}
|
||||
|
||||
fn actions(&self) -> ActionList {
|
||||
@@ -227,7 +219,7 @@ impl PenToolData {
|
||||
self.subpath_index = subpath_index;
|
||||
|
||||
// Stop the handles on the first point from mirroring
|
||||
let Some(subpaths) = get_subpaths(layer, document) else { return };
|
||||
let Some(subpaths) = get_subpaths (LayerNodeIdentifier::from_path(layer, document.network()), &document.document_legacy) else { return };
|
||||
let manipulator_groups = subpaths[subpath_index].manipulator_groups();
|
||||
let Some(last_handle) = (if from_start { manipulator_groups.first() } else { manipulator_groups.last() }) else {
|
||||
return;
|
||||
@@ -257,7 +249,7 @@ impl PenToolData {
|
||||
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 transform = document.document_legacy.metadata.document_to_viewport * 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;
|
||||
@@ -286,7 +278,7 @@ impl PenToolData {
|
||||
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 subpath = &get_subpaths(layer_path, document)?[self.subpath_index];
|
||||
let subpath = &get_subpaths(LayerNodeIdentifier::from_path(layer_path, document.network()), &document.document_legacy)?[self.subpath_index];
|
||||
|
||||
// Get the last manipulator group and the one previous to that
|
||||
let mut manipulator_groups = subpath.manipulator_groups().iter();
|
||||
@@ -327,7 +319,7 @@ impl PenToolData {
|
||||
|
||||
// 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);
|
||||
shape_overlay.clear_subpath_overlays(&document.document_legacy, LayerNodeIdentifier::from_path(layer_path, document.network()), responses);
|
||||
}
|
||||
|
||||
self.should_mirror = false;
|
||||
@@ -337,7 +329,7 @@ impl PenToolData {
|
||||
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 subpath = &get_subpaths(layer_path, document)?[self.subpath_index];
|
||||
let subpath = &get_subpaths(LayerNodeIdentifier::from_path(layer_path, document.network()), &document.document_legacy)?[self.subpath_index];
|
||||
|
||||
// Get the last manipulator group and the one previous to that
|
||||
let mut manipulator_groups = subpath.manipulator_groups().iter();
|
||||
@@ -394,7 +386,7 @@ impl PenToolData {
|
||||
|
||||
// Clean up overlays
|
||||
for layer_path in document.all_layers() {
|
||||
shape_overlay.clear_subpath_overlays(&document.document_legacy, layer_path.to_vec(), responses);
|
||||
shape_overlay.clear_subpath_overlays(&document.document_legacy, LayerNodeIdentifier::from_path(layer_path, document.network()), responses);
|
||||
}
|
||||
|
||||
// Clean up tool data
|
||||
@@ -415,7 +407,7 @@ impl PenToolData {
|
||||
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 subpath = &get_subpaths(layer_path, document)?[self.subpath_index];
|
||||
let subpath = &get_subpaths(LayerNodeIdentifier::from_path(layer_path, document.network()), &document.document_legacy)?[self.subpath_index];
|
||||
|
||||
// Get the last manipulator group
|
||||
let manipulator_groups = subpath.manipulator_groups();
|
||||
@@ -468,7 +460,7 @@ impl PenToolData {
|
||||
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 subpath = &get_subpaths(layer_path, document)?[self.subpath_index];
|
||||
let subpath = &get_subpaths(LayerNodeIdentifier::from_path(layer_path, document.network()), &document.document_legacy)?[self.subpath_index];
|
||||
|
||||
// Get the last manipulator group and the one previous to that
|
||||
let mut manipulator_groups = subpath.manipulator_groups().iter();
|
||||
@@ -513,7 +505,7 @@ impl PenToolData {
|
||||
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 subpath = &get_subpaths(layer_path, document)?[self.subpath_index];
|
||||
let subpath = &get_subpaths(LayerNodeIdentifier::from_path(layer_path, document.network()), &document.document_legacy)?[self.subpath_index];
|
||||
|
||||
// Abort if only one manipulator group has been placed
|
||||
if fsm == PenToolFsmState::PlacingAnchor && subpath.len() < 3 {
|
||||
@@ -560,11 +552,8 @@ impl Fsm for PenToolFsmState {
|
||||
type ToolData = PenToolData;
|
||||
type ToolOptions = PenOptions;
|
||||
|
||||
fn transition(
|
||||
self,
|
||||
event: ToolMessage,
|
||||
tool_data: &mut Self::ToolData,
|
||||
ToolActionHandlerData {
|
||||
fn transition(self, event: ToolMessage, tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionHandlerData, tool_options: &Self::ToolOptions, responses: &mut VecDeque<Message>) -> Self {
|
||||
let ToolActionHandlerData {
|
||||
document,
|
||||
global_tool_data,
|
||||
input,
|
||||
@@ -572,10 +561,8 @@ impl Fsm for PenToolFsmState {
|
||||
shape_editor,
|
||||
shape_overlay,
|
||||
..
|
||||
}: &mut ToolActionHandlerData,
|
||||
tool_options: &Self::ToolOptions,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) -> Self {
|
||||
} = tool_action_data;
|
||||
|
||||
let mut transform = tool_data.path.as_ref().and_then(|path| document.document_legacy.multiply_transforms(path).ok()).unwrap_or_default();
|
||||
|
||||
if !transform.inverse().is_finite() {
|
||||
@@ -591,117 +578,118 @@ impl Fsm for PenToolFsmState {
|
||||
transform = DAffine2::IDENTITY;
|
||||
}
|
||||
|
||||
if let ToolMessage::Pen(event) = event {
|
||||
match (self, event) {
|
||||
(_, PenToolMessage::CanvasTransformed) => {
|
||||
tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(None, None, render_data), true, true);
|
||||
self
|
||||
}
|
||||
(_, PenToolMessage::DocumentIsDirty) => {
|
||||
// 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() {
|
||||
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() {
|
||||
shape_overlay.layer_overlay_visibility(&document.document_legacy, layer_path.to_vec(), false, responses);
|
||||
}
|
||||
transform = document.document_legacy.metadata.document_to_viewport * transform;
|
||||
|
||||
// Redraw the overlays of the newly selected layers
|
||||
for layer_path in document.selected_visible_layers() {
|
||||
shape_overlay.render_subpath_overlays(&shape_editor.selected_shape_state, &document.document_legacy, layer_path.to_vec(), responses);
|
||||
}
|
||||
self
|
||||
}
|
||||
(_, PenToolMessage::WorkingColorChanged) => {
|
||||
responses.add(PenToolMessage::UpdateOptions(PenOptionsUpdate::WorkingColors(
|
||||
Some(global_tool_data.primary_color),
|
||||
Some(global_tool_data.secondary_color),
|
||||
)));
|
||||
self
|
||||
}
|
||||
(PenToolFsmState::Ready, PenToolMessage::DragStart) => {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
|
||||
// Initialize snapping
|
||||
tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(None, None, render_data), true, true);
|
||||
tool_data.snap_manager.add_all_document_handles(document, input, &[], &[], &[]);
|
||||
|
||||
// Disable this tool's mirroring
|
||||
tool_data.should_mirror = false;
|
||||
|
||||
// Perform extension of an existing path
|
||||
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,
|
||||
tool_options.stroke.active_color(),
|
||||
tool_options.fill.active_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) => {
|
||||
tool_data.check_break(document, transform, shape_overlay, responses);
|
||||
PenToolFsmState::DraggingHandle
|
||||
}
|
||||
(PenToolFsmState::DraggingHandle, PenToolMessage::DragStop) => {
|
||||
tool_data.should_mirror = true;
|
||||
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 modifiers = ModifierState {
|
||||
snap_angle: input.keyboard.key(snap_angle),
|
||||
lock_angle: input.keyboard.key(lock_angle),
|
||||
break_handle: input.keyboard.key(break_handle),
|
||||
};
|
||||
tool_data.drag_handle(document, transform, input.mouse.position, modifiers, responses).unwrap_or(PenToolFsmState::Ready)
|
||||
}
|
||||
(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),
|
||||
};
|
||||
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 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() {
|
||||
shape_overlay.clear_subpath_overlays(&document.document_legacy, layer_path.to_vec(), responses);
|
||||
}
|
||||
tool_data.path = None;
|
||||
tool_data.snap_manager.cleanup(responses);
|
||||
|
||||
PenToolFsmState::Ready
|
||||
}
|
||||
(_, PenToolMessage::Abort) => {
|
||||
// Clean up overlays
|
||||
for layer_path in document.all_layers() {
|
||||
shape_overlay.clear_subpath_overlays(&document.document_legacy, layer_path.to_vec(), responses);
|
||||
}
|
||||
self
|
||||
}
|
||||
_ => self,
|
||||
let ToolMessage::Pen(event) = event else {
|
||||
return self;
|
||||
};
|
||||
match (self, event) {
|
||||
(_, PenToolMessage::CanvasTransformed) => {
|
||||
tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(None, None, render_data), true, true);
|
||||
self
|
||||
}
|
||||
} else {
|
||||
self
|
||||
(_, PenToolMessage::DocumentIsDirty) => {
|
||||
// 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 in document.document_legacy.metadata.selected_layers() {
|
||||
shape_overlay.render_subpath_overlays(&shape_editor.selected_shape_state, &document.document_legacy, layer, responses);
|
||||
}
|
||||
self
|
||||
}
|
||||
(_, PenToolMessage::SelectionChanged) => {
|
||||
// Set the previously selected layers to invisible
|
||||
for layer in document.document_legacy.metadata.all_layers() {
|
||||
shape_overlay.layer_overlay_visibility(&document.document_legacy, layer, false, responses);
|
||||
}
|
||||
|
||||
// Redraw the overlays of the newly selected layers
|
||||
for layer in document.document_legacy.metadata.selected_layers() {
|
||||
shape_overlay.render_subpath_overlays(&shape_editor.selected_shape_state, &document.document_legacy, layer, responses);
|
||||
}
|
||||
self
|
||||
}
|
||||
(_, PenToolMessage::WorkingColorChanged) => {
|
||||
responses.add(PenToolMessage::UpdateOptions(PenOptionsUpdate::WorkingColors(
|
||||
Some(global_tool_data.primary_color),
|
||||
Some(global_tool_data.secondary_color),
|
||||
)));
|
||||
self
|
||||
}
|
||||
(PenToolFsmState::Ready, PenToolMessage::DragStart) => {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
|
||||
// Initialize snapping
|
||||
tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(None, None, render_data), true, true);
|
||||
tool_data.snap_manager.add_all_document_handles(document, input, &[], &[], &[]);
|
||||
|
||||
// Disable this tool's mirroring
|
||||
tool_data.should_mirror = false;
|
||||
|
||||
// Perform extension of an existing path
|
||||
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,
|
||||
tool_options.stroke.active_color(),
|
||||
tool_options.fill.active_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) => {
|
||||
tool_data.check_break(document, transform, shape_overlay, responses);
|
||||
PenToolFsmState::DraggingHandle
|
||||
}
|
||||
(PenToolFsmState::DraggingHandle, PenToolMessage::DragStop) => {
|
||||
tool_data.should_mirror = true;
|
||||
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 modifiers = ModifierState {
|
||||
snap_angle: input.keyboard.key(snap_angle),
|
||||
lock_angle: input.keyboard.key(lock_angle),
|
||||
break_handle: input.keyboard.key(break_handle),
|
||||
};
|
||||
tool_data.drag_handle(document, transform, input.mouse.position, modifiers, responses).unwrap_or(PenToolFsmState::Ready)
|
||||
}
|
||||
(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),
|
||||
};
|
||||
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 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() {
|
||||
shape_overlay.clear_subpath_overlays(&document.document_legacy, LayerNodeIdentifier::from_path(layer_path, document.network()), responses);
|
||||
}
|
||||
tool_data.path = None;
|
||||
tool_data.snap_manager.cleanup(responses);
|
||||
|
||||
PenToolFsmState::Ready
|
||||
}
|
||||
(_, PenToolMessage::Abort) => {
|
||||
// Clean up overlays
|
||||
for layer_path in document.all_layers() {
|
||||
shape_overlay.clear_subpath_overlays(&document.document_legacy, LayerNodeIdentifier::from_path(layer_path, document.network()), responses);
|
||||
}
|
||||
self
|
||||
}
|
||||
_ => self,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -772,7 +760,7 @@ fn should_extend(document: &DocumentMessageHandler, pos: DVec2, tolerance: f64)
|
||||
continue;
|
||||
};
|
||||
|
||||
let subpaths = get_subpaths(layer_path, document)?;
|
||||
let subpaths = get_subpaths(LayerNodeIdentifier::from_path(layer_path, document.network()), &document.document_legacy)?;
|
||||
for (subpath_index, subpath) in subpaths.iter().enumerate() {
|
||||
if subpath.closed() {
|
||||
continue;
|
||||
@@ -794,22 +782,25 @@ fn should_extend(document: &DocumentMessageHandler, pos: DVec2, tolerance: f64)
|
||||
best
|
||||
}
|
||||
|
||||
fn get_subpaths<'a>(layer_path: &[LayerId], document: &'a DocumentMessageHandler) -> Option<&'a Vec<Subpath<ManipulatorGroupId>>> {
|
||||
let layer = document.document_legacy.layer(layer_path).ok().and_then(|layer| layer.as_layer().ok())?;
|
||||
let network = &layer.network;
|
||||
for (node, _node_id) in network.primary_flow() {
|
||||
if node.name == "Shape" {
|
||||
let subpaths_input = node.inputs.get(0)?;
|
||||
let NodeInput::Value {
|
||||
tagged_value: TaggedValue::Subpaths(subpaths),
|
||||
..
|
||||
} = subpaths_input
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
||||
return Some(subpaths);
|
||||
}
|
||||
pub fn get_subpaths(layer: LayerNodeIdentifier, document: &Document) -> Option<&Vec<Subpath<ManipulatorGroupId>>> {
|
||||
if let TaggedValue::Subpaths(subpaths) = NodeGraphLayer::new(layer, document)?.find_input("Shape", 0)? {
|
||||
Some(subpaths)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn get_mirror_handles(layer: LayerNodeIdentifier, document: &Document) -> Option<&Vec<ManipulatorGroupId>> {
|
||||
if let TaggedValue::ManipulatorGroupIds(mirror_handles) = NodeGraphLayer::new(layer, document)?.find_input("Shape", 1)? {
|
||||
Some(mirror_handles)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_manipulator_groups(subpaths: &[Subpath<ManipulatorGroupId>]) -> impl Iterator<Item = &bezier_rs::ManipulatorGroup<ManipulatorGroupId>> + DoubleEndedIterator {
|
||||
subpaths.iter().flat_map(|subpath| subpath.manipulator_groups())
|
||||
}
|
||||
pub fn get_manipulator_from_id(subpaths: &[Subpath<ManipulatorGroupId>], id: ManipulatorGroupId) -> Option<&bezier_rs::ManipulatorGroup<ManipulatorGroupId>> {
|
||||
subpaths.iter().find_map(|subpath| subpath.manipulator_from_id(id))
|
||||
}
|
||||
|
||||
@@ -1,19 +1,11 @@
|
||||
use crate::messages::frontend::utility_types::MouseCursorIcon;
|
||||
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion};
|
||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||
use crate::messages::prelude::*;
|
||||
use super::tool_prelude::*;
|
||||
use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType};
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||
use crate::messages::tool::common_functionality::resize::Resize;
|
||||
use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType};
|
||||
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
|
||||
|
||||
use graphene_core::vector::style::{Fill, Stroke};
|
||||
use graphene_core::Color;
|
||||
|
||||
use glam::DVec2;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct PolygonTool {
|
||||
fsm_state: PolygonToolFsmState,
|
||||
@@ -158,35 +150,33 @@ impl LayoutHolder for PolygonTool {
|
||||
}
|
||||
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for PolygonTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
|
||||
if let ToolMessage::Polygon(PolygonToolMessage::UpdateOptions(action)) = message {
|
||||
match action {
|
||||
PolygonOptionsUpdate::Vertices(vertices) => self.options.vertices = vertices,
|
||||
PolygonOptionsUpdate::PrimitiveShapeType(primitive_shape_type) => self.options.primitive_shape_type = primitive_shape_type,
|
||||
PolygonOptionsUpdate::FillColor(color) => {
|
||||
self.options.fill.custom_color = color;
|
||||
self.options.fill.color_type = ToolColorType::Custom;
|
||||
}
|
||||
PolygonOptionsUpdate::FillColorType(color_type) => self.options.fill.color_type = color_type,
|
||||
PolygonOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight,
|
||||
PolygonOptionsUpdate::StrokeColor(color) => {
|
||||
self.options.stroke.custom_color = color;
|
||||
self.options.stroke.color_type = ToolColorType::Custom;
|
||||
}
|
||||
PolygonOptionsUpdate::StrokeColorType(color_type) => self.options.stroke.color_type = color_type,
|
||||
PolygonOptionsUpdate::WorkingColors(primary, secondary) => {
|
||||
self.options.stroke.primary_working_color = primary;
|
||||
self.options.stroke.secondary_working_color = secondary;
|
||||
self.options.fill.primary_working_color = primary;
|
||||
self.options.fill.secondary_working_color = secondary;
|
||||
}
|
||||
}
|
||||
|
||||
self.send_layout(responses, LayoutTarget::ToolOptions);
|
||||
|
||||
let ToolMessage::Polygon(PolygonToolMessage::UpdateOptions(action)) = message else {
|
||||
self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &self.options, responses, true);
|
||||
return;
|
||||
};
|
||||
match action {
|
||||
PolygonOptionsUpdate::Vertices(vertices) => self.options.vertices = vertices,
|
||||
PolygonOptionsUpdate::PrimitiveShapeType(primitive_shape_type) => self.options.primitive_shape_type = primitive_shape_type,
|
||||
PolygonOptionsUpdate::FillColor(color) => {
|
||||
self.options.fill.custom_color = color;
|
||||
self.options.fill.color_type = ToolColorType::Custom;
|
||||
}
|
||||
PolygonOptionsUpdate::FillColorType(color_type) => self.options.fill.color_type = color_type,
|
||||
PolygonOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight,
|
||||
PolygonOptionsUpdate::StrokeColor(color) => {
|
||||
self.options.stroke.custom_color = color;
|
||||
self.options.stroke.color_type = ToolColorType::Custom;
|
||||
}
|
||||
PolygonOptionsUpdate::StrokeColorType(color_type) => self.options.stroke.color_type = color_type,
|
||||
PolygonOptionsUpdate::WorkingColors(primary, secondary) => {
|
||||
self.options.stroke.primary_working_color = primary;
|
||||
self.options.stroke.secondary_working_color = secondary;
|
||||
self.options.fill.primary_working_color = primary;
|
||||
self.options.fill.secondary_working_color = secondary;
|
||||
}
|
||||
}
|
||||
|
||||
self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &self.options, responses, true);
|
||||
self.send_layout(responses, LayoutTarget::ToolOptions);
|
||||
}
|
||||
|
||||
fn actions(&self) -> ActionList {
|
||||
@@ -232,87 +222,78 @@ impl Fsm for PolygonToolFsmState {
|
||||
type ToolData = PolygonToolData;
|
||||
type ToolOptions = PolygonOptions;
|
||||
|
||||
fn transition(
|
||||
self,
|
||||
event: ToolMessage,
|
||||
tool_data: &mut Self::ToolData,
|
||||
ToolActionHandlerData {
|
||||
fn transition(self, event: ToolMessage, tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionHandlerData, tool_options: &Self::ToolOptions, responses: &mut VecDeque<Message>) -> Self {
|
||||
let ToolActionHandlerData {
|
||||
document,
|
||||
global_tool_data,
|
||||
input,
|
||||
render_data,
|
||||
..
|
||||
}: &mut ToolActionHandlerData,
|
||||
tool_options: &Self::ToolOptions,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) -> Self {
|
||||
use PolygonToolFsmState::*;
|
||||
use PolygonToolMessage::*;
|
||||
} = tool_action_data;
|
||||
|
||||
let polygon_data = &mut tool_data.data;
|
||||
|
||||
if let ToolMessage::Polygon(event) = event {
|
||||
match (self, event) {
|
||||
(Drawing, CanvasTransformed) => {
|
||||
tool_data.data.recalculate_snaps(document, input, render_data);
|
||||
self
|
||||
}
|
||||
(Ready, DragStart) => {
|
||||
polygon_data.start(responses, document, input, render_data);
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
let layer_path = document.get_path_for_new_layer();
|
||||
polygon_data.path = Some(layer_path.clone());
|
||||
|
||||
let subpath = match tool_options.primitive_shape_type {
|
||||
PrimitiveShapeType::Polygon => bezier_rs::Subpath::new_regular_polygon(DVec2::ZERO, tool_options.vertices as u64, 1.),
|
||||
PrimitiveShapeType::Star => bezier_rs::Subpath::new_star_polygon(DVec2::ZERO, tool_options.vertices as u64, 1., 0.5),
|
||||
};
|
||||
graph_modification_utils::new_vector_layer(vec![subpath], layer_path.clone(), responses);
|
||||
|
||||
let fill_color = tool_options.fill.active_color();
|
||||
responses.add(GraphOperationMessage::FillSet {
|
||||
layer: layer_path.clone(),
|
||||
fill: if let Some(color) = fill_color { Fill::Solid(color) } else { Fill::None },
|
||||
});
|
||||
|
||||
responses.add(GraphOperationMessage::StrokeSet {
|
||||
layer: layer_path,
|
||||
stroke: Stroke::new(tool_options.stroke.active_color(), tool_options.line_weight),
|
||||
});
|
||||
|
||||
Drawing
|
||||
}
|
||||
(state, Resize { center, lock_ratio }) => {
|
||||
if let Some(message) = polygon_data.calculate_transform(responses, document, input, center, lock_ratio, false) {
|
||||
responses.add(message);
|
||||
}
|
||||
|
||||
state
|
||||
}
|
||||
(Drawing, DragStop) => {
|
||||
input.mouse.finish_transaction(polygon_data.viewport_drag_start(document), responses);
|
||||
polygon_data.cleanup(responses);
|
||||
|
||||
Ready
|
||||
}
|
||||
(Drawing, Abort) => {
|
||||
responses.add(DocumentMessage::AbortTransaction);
|
||||
|
||||
polygon_data.cleanup(responses);
|
||||
|
||||
Ready
|
||||
}
|
||||
(_, WorkingColorChanged) => {
|
||||
responses.add(PolygonToolMessage::UpdateOptions(PolygonOptionsUpdate::WorkingColors(
|
||||
Some(global_tool_data.primary_color),
|
||||
Some(global_tool_data.secondary_color),
|
||||
)));
|
||||
self
|
||||
}
|
||||
_ => self,
|
||||
let ToolMessage::Polygon(event) = event else {
|
||||
return self;
|
||||
};
|
||||
match (self, event) {
|
||||
(PolygonToolFsmState::Drawing, PolygonToolMessage::CanvasTransformed) => {
|
||||
tool_data.data.recalculate_snaps(document, input, render_data);
|
||||
self
|
||||
}
|
||||
} else {
|
||||
self
|
||||
(PolygonToolFsmState::Ready, PolygonToolMessage::DragStart) => {
|
||||
polygon_data.start(responses, document, input, render_data);
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
let layer_path = document.get_path_for_new_layer();
|
||||
polygon_data.path = Some(layer_path.clone());
|
||||
|
||||
let subpath = match tool_options.primitive_shape_type {
|
||||
PrimitiveShapeType::Polygon => bezier_rs::Subpath::new_regular_polygon(DVec2::ZERO, tool_options.vertices as u64, 1.),
|
||||
PrimitiveShapeType::Star => bezier_rs::Subpath::new_star_polygon(DVec2::ZERO, tool_options.vertices as u64, 1., 0.5),
|
||||
};
|
||||
graph_modification_utils::new_vector_layer(vec![subpath], layer_path.clone(), responses);
|
||||
|
||||
let fill_color = tool_options.fill.active_color();
|
||||
responses.add(GraphOperationMessage::FillSet {
|
||||
layer: layer_path.clone(),
|
||||
fill: if let Some(color) = fill_color { Fill::Solid(color) } else { Fill::None },
|
||||
});
|
||||
|
||||
responses.add(GraphOperationMessage::StrokeSet {
|
||||
layer: layer_path,
|
||||
stroke: Stroke::new(tool_options.stroke.active_color(), tool_options.line_weight),
|
||||
});
|
||||
|
||||
PolygonToolFsmState::Drawing
|
||||
}
|
||||
(state, PolygonToolMessage::Resize { center, lock_ratio }) => {
|
||||
if let Some(message) = polygon_data.calculate_transform(responses, document, input, center, lock_ratio, false) {
|
||||
responses.add(message);
|
||||
}
|
||||
|
||||
state
|
||||
}
|
||||
(PolygonToolFsmState::Drawing, PolygonToolMessage::DragStop) => {
|
||||
input.mouse.finish_transaction(polygon_data.viewport_drag_start(document), responses);
|
||||
polygon_data.cleanup(responses);
|
||||
|
||||
PolygonToolFsmState::Ready
|
||||
}
|
||||
(PolygonToolFsmState::Drawing, PolygonToolMessage::Abort) => {
|
||||
responses.add(DocumentMessage::AbortTransaction);
|
||||
|
||||
polygon_data.cleanup(responses);
|
||||
|
||||
PolygonToolFsmState::Ready
|
||||
}
|
||||
(_, PolygonToolMessage::WorkingColorChanged) => {
|
||||
responses.add(PolygonToolMessage::UpdateOptions(PolygonOptionsUpdate::WorkingColors(
|
||||
Some(global_tool_data.primary_color),
|
||||
Some(global_tool_data.secondary_color),
|
||||
)));
|
||||
self
|
||||
}
|
||||
_ => self,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,17 +1,10 @@
|
||||
use crate::messages::frontend::utility_types::MouseCursorIcon;
|
||||
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion};
|
||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||
use crate::messages::prelude::*;
|
||||
use super::tool_prelude::*;
|
||||
use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType};
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||
use crate::messages::tool::common_functionality::resize::Resize;
|
||||
use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType};
|
||||
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
|
||||
|
||||
use glam::DVec2;
|
||||
use graphene_core::vector::style::{Fill, Stroke};
|
||||
use graphene_core::Color;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct RectangleTool {
|
||||
@@ -107,33 +100,32 @@ impl LayoutHolder for RectangleTool {
|
||||
|
||||
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>) {
|
||||
if let ToolMessage::Rectangle(RectangleToolMessage::UpdateOptions(action)) = message {
|
||||
match action {
|
||||
RectangleOptionsUpdate::FillColor(color) => {
|
||||
self.options.fill.custom_color = color;
|
||||
self.options.fill.color_type = ToolColorType::Custom;
|
||||
}
|
||||
RectangleOptionsUpdate::FillColorType(color_type) => self.options.fill.color_type = color_type,
|
||||
RectangleOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight,
|
||||
RectangleOptionsUpdate::StrokeColor(color) => {
|
||||
self.options.stroke.custom_color = color;
|
||||
self.options.stroke.color_type = ToolColorType::Custom;
|
||||
}
|
||||
RectangleOptionsUpdate::StrokeColorType(color_type) => self.options.stroke.color_type = color_type,
|
||||
RectangleOptionsUpdate::WorkingColors(primary, secondary) => {
|
||||
self.options.stroke.primary_working_color = primary;
|
||||
self.options.stroke.secondary_working_color = secondary;
|
||||
self.options.fill.primary_working_color = primary;
|
||||
self.options.fill.secondary_working_color = secondary;
|
||||
}
|
||||
}
|
||||
|
||||
self.send_layout(responses, LayoutTarget::ToolOptions);
|
||||
|
||||
let ToolMessage::Rectangle(RectangleToolMessage::UpdateOptions(action)) = message else {
|
||||
self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &self.options, responses, true);
|
||||
return;
|
||||
};
|
||||
|
||||
match action {
|
||||
RectangleOptionsUpdate::FillColor(color) => {
|
||||
self.options.fill.custom_color = color;
|
||||
self.options.fill.color_type = ToolColorType::Custom;
|
||||
}
|
||||
RectangleOptionsUpdate::FillColorType(color_type) => self.options.fill.color_type = color_type,
|
||||
RectangleOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight,
|
||||
RectangleOptionsUpdate::StrokeColor(color) => {
|
||||
self.options.stroke.custom_color = color;
|
||||
self.options.stroke.color_type = ToolColorType::Custom;
|
||||
}
|
||||
RectangleOptionsUpdate::StrokeColorType(color_type) => self.options.stroke.color_type = color_type,
|
||||
RectangleOptionsUpdate::WorkingColors(primary, secondary) => {
|
||||
self.options.stroke.primary_working_color = primary;
|
||||
self.options.stroke.secondary_working_color = secondary;
|
||||
self.options.fill.primary_working_color = primary;
|
||||
self.options.fill.secondary_working_color = secondary;
|
||||
}
|
||||
}
|
||||
|
||||
self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &self.options, responses, true);
|
||||
self.send_layout(responses, LayoutTarget::ToolOptions);
|
||||
}
|
||||
|
||||
fn actions(&self) -> ActionList {
|
||||
@@ -210,66 +202,66 @@ impl Fsm for RectangleToolFsmState {
|
||||
|
||||
let shape_data = &mut tool_data.data;
|
||||
|
||||
if let ToolMessage::Rectangle(event) = event {
|
||||
match (self, event) {
|
||||
(Drawing, CanvasTransformed) => {
|
||||
tool_data.data.recalculate_snaps(document, input, render_data);
|
||||
self
|
||||
}
|
||||
(Ready, DragStart) => {
|
||||
shape_data.start(responses, document, input, render_data);
|
||||
let ToolMessage::Rectangle(event) = event else {
|
||||
return self;
|
||||
};
|
||||
|
||||
let subpath = bezier_rs::Subpath::new_rect(DVec2::ZERO, DVec2::ONE);
|
||||
|
||||
let layer_path = document.get_path_for_new_layer();
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
shape_data.path = Some(layer_path.clone());
|
||||
graph_modification_utils::new_vector_layer(vec![subpath], layer_path.clone(), responses);
|
||||
|
||||
let fill_color = tool_options.fill.active_color();
|
||||
responses.add(GraphOperationMessage::FillSet {
|
||||
layer: layer_path.clone(),
|
||||
fill: if let Some(color) = fill_color { Fill::Solid(color) } else { Fill::None },
|
||||
});
|
||||
|
||||
responses.add(GraphOperationMessage::StrokeSet {
|
||||
layer: layer_path,
|
||||
stroke: Stroke::new(tool_options.stroke.active_color(), tool_options.line_weight),
|
||||
});
|
||||
|
||||
Drawing
|
||||
}
|
||||
(state, Resize { center, lock_ratio }) => {
|
||||
if let Some(message) = shape_data.calculate_transform(responses, document, input, center, lock_ratio, false) {
|
||||
responses.add(message);
|
||||
}
|
||||
|
||||
state
|
||||
}
|
||||
(Drawing, DragStop) => {
|
||||
input.mouse.finish_transaction(shape_data.viewport_drag_start(document), responses);
|
||||
shape_data.cleanup(responses);
|
||||
|
||||
Ready
|
||||
}
|
||||
(Drawing, Abort) => {
|
||||
responses.add(DocumentMessage::AbortTransaction);
|
||||
|
||||
shape_data.cleanup(responses);
|
||||
|
||||
Ready
|
||||
}
|
||||
(_, WorkingColorChanged) => {
|
||||
responses.add(RectangleToolMessage::UpdateOptions(RectangleOptionsUpdate::WorkingColors(
|
||||
Some(global_tool_data.primary_color),
|
||||
Some(global_tool_data.secondary_color),
|
||||
)));
|
||||
self
|
||||
}
|
||||
_ => self,
|
||||
match (self, event) {
|
||||
(Drawing, CanvasTransformed) => {
|
||||
tool_data.data.recalculate_snaps(document, input, render_data);
|
||||
self
|
||||
}
|
||||
} else {
|
||||
self
|
||||
(Ready, DragStart) => {
|
||||
shape_data.start(responses, document, input, render_data);
|
||||
|
||||
let subpath = bezier_rs::Subpath::new_rect(DVec2::ZERO, DVec2::ONE);
|
||||
|
||||
let layer_path = document.get_path_for_new_layer();
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
shape_data.path = Some(layer_path.clone());
|
||||
graph_modification_utils::new_vector_layer(vec![subpath], layer_path.clone(), responses);
|
||||
|
||||
let fill_color = tool_options.fill.active_color();
|
||||
responses.add(GraphOperationMessage::FillSet {
|
||||
layer: layer_path.clone(),
|
||||
fill: if let Some(color) = fill_color { Fill::Solid(color) } else { Fill::None },
|
||||
});
|
||||
|
||||
responses.add(GraphOperationMessage::StrokeSet {
|
||||
layer: layer_path,
|
||||
stroke: Stroke::new(tool_options.stroke.active_color(), tool_options.line_weight),
|
||||
});
|
||||
|
||||
Drawing
|
||||
}
|
||||
(state, Resize { center, lock_ratio }) => {
|
||||
if let Some(message) = shape_data.calculate_transform(responses, document, input, center, lock_ratio, false) {
|
||||
responses.add(message);
|
||||
}
|
||||
|
||||
state
|
||||
}
|
||||
(Drawing, DragStop) => {
|
||||
input.mouse.finish_transaction(shape_data.viewport_drag_start(document), responses);
|
||||
shape_data.cleanup(responses);
|
||||
|
||||
Ready
|
||||
}
|
||||
(Drawing, Abort) => {
|
||||
responses.add(DocumentMessage::AbortTransaction);
|
||||
|
||||
shape_data.cleanup(responses);
|
||||
|
||||
Ready
|
||||
}
|
||||
(_, WorkingColorChanged) => {
|
||||
responses.add(RectangleToolMessage::UpdateOptions(RectangleOptionsUpdate::WorkingColors(
|
||||
Some(global_tool_data.primary_color),
|
||||
Some(global_tool_data.secondary_color),
|
||||
)));
|
||||
self
|
||||
}
|
||||
_ => self,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,19 +1,14 @@
|
||||
#![allow(clippy::too_many_arguments)]
|
||||
use super::tool_prelude::*;
|
||||
use crate::application::generate_uuid;
|
||||
use crate::consts::{ROTATE_SNAP_ANGLE, SELECTION_TOLERANCE};
|
||||
use crate::messages::frontend::utility_types::MouseCursorIcon;
|
||||
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion};
|
||||
use crate::messages::input_mapper::utility_types::input_mouse::ViewportPosition;
|
||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||
use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate, AlignAxis, FlipAxis};
|
||||
use crate::messages::portfolio::document::utility_types::transformation::Selected;
|
||||
use crate::messages::prelude::*;
|
||||
use crate::messages::tool::common_functionality::path_outline::*;
|
||||
use crate::messages::tool::common_functionality::pivot::Pivot;
|
||||
use crate::messages::tool::common_functionality::snapping::{self, SnapManager};
|
||||
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::document::Document;
|
||||
use document_legacy::intersection::Quad;
|
||||
@@ -21,8 +16,6 @@ use document_legacy::layers::layer_info::{Layer, LayerDataType};
|
||||
use document_legacy::LayerId;
|
||||
use document_legacy::Operation;
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Default)]
|
||||
@@ -446,268 +439,137 @@ impl Fsm for SelectToolFsmState {
|
||||
use SelectToolFsmState::*;
|
||||
use SelectToolMessage::*;
|
||||
|
||||
if let ToolMessage::Select(event) = event {
|
||||
match (self, event) {
|
||||
(_, DocumentIsDirty | SelectionChanged) => {
|
||||
let selected_layers_count = document.selected_layers().count();
|
||||
let selected_layers_changed = selected_layers_count != tool_data.selected_layers_count;
|
||||
let ToolMessage::Select(event) = event else {
|
||||
return self;
|
||||
};
|
||||
match (self, event) {
|
||||
(_, DocumentIsDirty | SelectionChanged) => {
|
||||
let selected_layers_count = document.selected_layers().count();
|
||||
let selected_layers_changed = selected_layers_count != tool_data.selected_layers_count;
|
||||
|
||||
if selected_layers_changed {
|
||||
tool_data.selected_layers_count = selected_layers_count;
|
||||
tool_data.selected_layers_changed = true;
|
||||
} else {
|
||||
tool_data.selected_layers_changed = false;
|
||||
}
|
||||
|
||||
match (document.selected_visible_layers_bounding_box(render_data), tool_data.bounding_box_overlays.take()) {
|
||||
(None, Some(bounding_box_overlays)) => bounding_box_overlays.delete(responses),
|
||||
(Some(bounds), paths) => {
|
||||
let mut bounding_box_overlays = paths.unwrap_or_else(|| BoundingBoxOverlays::new(responses));
|
||||
|
||||
bounding_box_overlays.bounds = bounds;
|
||||
bounding_box_overlays.transform = DAffine2::IDENTITY;
|
||||
|
||||
bounding_box_overlays.transform(responses);
|
||||
|
||||
tool_data.bounding_box_overlays = Some(bounding_box_overlays);
|
||||
}
|
||||
(_, _) => {}
|
||||
};
|
||||
|
||||
tool_data.path_outlines.update_selected(document.selected_visible_layers(), document, responses, render_data);
|
||||
tool_data.path_outlines.intersect_test_hovered(input, document, responses, render_data);
|
||||
tool_data.pivot.update_pivot(document, render_data, responses);
|
||||
|
||||
self
|
||||
if selected_layers_changed {
|
||||
tool_data.selected_layers_count = selected_layers_count;
|
||||
tool_data.selected_layers_changed = true;
|
||||
} else {
|
||||
tool_data.selected_layers_changed = false;
|
||||
}
|
||||
(_, EditLayer) => {
|
||||
// Setup required data for checking the clicked layer
|
||||
let mouse_pos = input.mouse.position;
|
||||
let tolerance = DVec2::splat(SELECTION_TOLERANCE);
|
||||
let quad = Quad::from_box([mouse_pos - tolerance, mouse_pos + tolerance]);
|
||||
|
||||
// Check the last (topmost) intersection layer
|
||||
if let Some(intersect_layer_path) = document.document_legacy.intersects_quad_root(quad, render_data).last() {
|
||||
if let Ok(intersect) = document.document_legacy.layer(intersect_layer_path) {
|
||||
match tool_data.nested_selection_behavior {
|
||||
NestedSelectionBehavior::Shallowest => edit_layer_shallowest_manipulation(document, intersect_layer_path, tool_data, responses),
|
||||
NestedSelectionBehavior::Deepest => edit_layer_deepest_manipulation(intersect, responses),
|
||||
}
|
||||
match (document.selected_visible_layers_bounding_box(render_data), tool_data.bounding_box_overlays.take()) {
|
||||
(None, Some(bounding_box_overlays)) => bounding_box_overlays.delete(responses),
|
||||
(Some(bounds), paths) => {
|
||||
let mut bounding_box_overlays = paths.unwrap_or_else(|| BoundingBoxOverlays::new(responses));
|
||||
|
||||
bounding_box_overlays.bounds = bounds;
|
||||
bounding_box_overlays.transform = DAffine2::IDENTITY;
|
||||
|
||||
bounding_box_overlays.transform(responses);
|
||||
|
||||
tool_data.bounding_box_overlays = Some(bounding_box_overlays);
|
||||
}
|
||||
(_, _) => {}
|
||||
};
|
||||
|
||||
tool_data.path_outlines.update_selected(document.selected_visible_layers(), document, responses, render_data);
|
||||
tool_data.path_outlines.intersect_test_hovered(input, document, responses, render_data);
|
||||
tool_data.pivot.update_pivot(document, render_data, responses);
|
||||
|
||||
self
|
||||
}
|
||||
(_, EditLayer) => {
|
||||
// Setup required data for checking the clicked layer
|
||||
let mouse_pos = input.mouse.position;
|
||||
let tolerance = DVec2::splat(SELECTION_TOLERANCE);
|
||||
let quad = Quad::from_box([mouse_pos - tolerance, mouse_pos + tolerance]);
|
||||
|
||||
// Check the last (topmost) intersection layer
|
||||
if let Some(intersect_layer_path) = document.document_legacy.intersects_quad_root(quad, render_data).last() {
|
||||
if let Ok(intersect) = document.document_legacy.layer(intersect_layer_path) {
|
||||
match tool_data.nested_selection_behavior {
|
||||
NestedSelectionBehavior::Shallowest => edit_layer_shallowest_manipulation(document, intersect_layer_path, tool_data, responses),
|
||||
NestedSelectionBehavior::Deepest => edit_layer_deepest_manipulation(intersect, responses),
|
||||
}
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
(Ready, DragStart { add_to_selection, select_deepest }) => {
|
||||
tool_data.path_outlines.clear_hovered(responses);
|
||||
|
||||
tool_data.drag_start = input.mouse.position;
|
||||
tool_data.drag_current = input.mouse.position;
|
||||
self
|
||||
}
|
||||
(Ready, DragStart { add_to_selection, select_deepest }) => {
|
||||
tool_data.path_outlines.clear_hovered(responses);
|
||||
|
||||
let dragging_bounds = tool_data.bounding_box_overlays.as_mut().and_then(|bounding_box| {
|
||||
let edges = bounding_box.check_selected_edges(input.mouse.position);
|
||||
tool_data.drag_start = input.mouse.position;
|
||||
tool_data.drag_current = input.mouse.position;
|
||||
|
||||
bounding_box.selected_edges = edges.map(|(top, bottom, left, right)| {
|
||||
let selected_edges = SelectedEdges::new(top, bottom, left, right, bounding_box.bounds);
|
||||
bounding_box.opposite_pivot = selected_edges.calculate_pivot();
|
||||
selected_edges
|
||||
});
|
||||
let dragging_bounds = tool_data.bounding_box_overlays.as_mut().and_then(|bounding_box| {
|
||||
let edges = bounding_box.check_selected_edges(input.mouse.position);
|
||||
|
||||
edges
|
||||
bounding_box.selected_edges = edges.map(|(top, bottom, left, right)| {
|
||||
let selected_edges = SelectedEdges::new(top, bottom, left, right, bounding_box.bounds);
|
||||
bounding_box.opposite_pivot = selected_edges.calculate_pivot();
|
||||
selected_edges
|
||||
});
|
||||
|
||||
let rotating_bounds = tool_data
|
||||
.bounding_box_overlays
|
||||
.as_ref()
|
||||
.map(|bounding_box| bounding_box.check_rotate(input.mouse.position))
|
||||
.unwrap_or_default();
|
||||
edges
|
||||
});
|
||||
|
||||
let mut selected: Vec<_> = document.selected_visible_layers().map(|path| path.to_vec()).collect();
|
||||
let quad = tool_data.selection_quad();
|
||||
let mut intersection = document.document_legacy.intersects_quad_root(quad, render_data);
|
||||
let rotating_bounds = tool_data
|
||||
.bounding_box_overlays
|
||||
.as_ref()
|
||||
.map(|bounding_box| bounding_box.check_rotate(input.mouse.position))
|
||||
.unwrap_or_default();
|
||||
|
||||
// If the user is dragging the bounding box bounds, go into ResizingBounds mode.
|
||||
// If the user is dragging the rotate trigger, go into RotatingBounds mode.
|
||||
// If the user clicks on a layer that is in their current selection, go into the dragging mode.
|
||||
// If the user clicks on new shape, make that layer their new selection.
|
||||
// Otherwise enter the box select mode
|
||||
let state = if tool_data.pivot.is_over(input.mouse.position) {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
let mut selected: Vec<_> = document.selected_visible_layers().map(|path| path.to_vec()).collect();
|
||||
let quad = tool_data.selection_quad();
|
||||
let mut intersection = document.document_legacy.intersects_quad_root(quad, render_data);
|
||||
|
||||
tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(None, None, render_data), true, true);
|
||||
tool_data.snap_manager.add_all_document_handles(document, input, &[], &[], &[]);
|
||||
// If the user is dragging the bounding box bounds, go into ResizingBounds mode.
|
||||
// If the user is dragging the rotate trigger, go into RotatingBounds mode.
|
||||
// If the user clicks on a layer that is in their current selection, go into the dragging mode.
|
||||
// If the user clicks on new shape, make that layer their new selection.
|
||||
// Otherwise enter the box select mode
|
||||
let state = if tool_data.pivot.is_over(input.mouse.position) {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
|
||||
DraggingPivot
|
||||
} else if let Some(selected_edges) = dragging_bounds {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(None, None, render_data), true, true);
|
||||
tool_data.snap_manager.add_all_document_handles(document, input, &[], &[], &[]);
|
||||
|
||||
let snap_x = selected_edges.2 || selected_edges.3;
|
||||
let snap_y = selected_edges.0 || selected_edges.1;
|
||||
DraggingPivot
|
||||
} else if let Some(selected_edges) = dragging_bounds {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
|
||||
tool_data
|
||||
.snap_manager
|
||||
.start_snap(document, input, document.bounding_boxes(Some(&selected), None, render_data), snap_x, snap_y);
|
||||
tool_data
|
||||
.snap_manager
|
||||
.add_all_document_handles(document, input, &[], &selected.iter().map(|x| x.as_slice()).collect::<Vec<_>>(), &[]);
|
||||
let snap_x = selected_edges.2 || selected_edges.3;
|
||||
let snap_y = selected_edges.0 || selected_edges.1;
|
||||
|
||||
tool_data.layers_dragging = selected;
|
||||
tool_data
|
||||
.snap_manager
|
||||
.start_snap(document, input, document.bounding_boxes(Some(&selected), None, render_data), snap_x, snap_y);
|
||||
tool_data
|
||||
.snap_manager
|
||||
.add_all_document_handles(document, input, &[], &selected.iter().map(|x| x.as_slice()).collect::<Vec<_>>(), &[]);
|
||||
|
||||
if let Some(bounds) = &mut tool_data.bounding_box_overlays {
|
||||
let document = &document.document_legacy;
|
||||
tool_data.layers_dragging = selected;
|
||||
|
||||
let selected = &tool_data.layers_dragging.iter().collect::<Vec<_>>();
|
||||
let mut selected = Selected::new(
|
||||
&mut bounds.original_transforms,
|
||||
&mut bounds.center_of_transformation,
|
||||
selected,
|
||||
responses,
|
||||
document,
|
||||
None,
|
||||
&ToolType::Select,
|
||||
);
|
||||
bounds.center_of_transformation = selected.mean_average_of_pivots(render_data);
|
||||
}
|
||||
|
||||
ResizingBounds
|
||||
} else if rotating_bounds {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
|
||||
if let Some(bounds) = &mut tool_data.bounding_box_overlays {
|
||||
let selected = selected.iter().collect::<Vec<_>>();
|
||||
let mut selected = Selected::new(
|
||||
&mut bounds.original_transforms,
|
||||
&mut bounds.center_of_transformation,
|
||||
&selected,
|
||||
responses,
|
||||
&document.document_legacy,
|
||||
None,
|
||||
&ToolType::Select,
|
||||
);
|
||||
|
||||
bounds.center_of_transformation = selected.mean_average_of_pivots(render_data);
|
||||
}
|
||||
|
||||
tool_data.layers_dragging = selected;
|
||||
|
||||
RotatingBounds
|
||||
} else if intersection.last().map(|last| selected.iter().any(|selected_layer| last.starts_with(selected_layer))).unwrap_or(false)
|
||||
&& tool_data.nested_selection_behavior == NestedSelectionBehavior::Deepest
|
||||
{
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
|
||||
tool_data.layers_dragging = selected;
|
||||
|
||||
tool_data
|
||||
.snap_manager
|
||||
.start_snap(document, input, document.bounding_boxes(Some(&tool_data.layers_dragging), None, render_data), true, true);
|
||||
|
||||
Dragging
|
||||
} else {
|
||||
if !input.keyboard.get(add_to_selection as usize) && tool_data.nested_selection_behavior == NestedSelectionBehavior::Deepest {
|
||||
responses.add(DocumentMessage::DeselectAllLayers);
|
||||
tool_data.layers_dragging.clear();
|
||||
}
|
||||
|
||||
if let Some(intersection) = intersection.pop() {
|
||||
tool_data.layer_selected_on_start = Some(intersection.clone());
|
||||
selected = vec![intersection.clone()];
|
||||
|
||||
match tool_data.nested_selection_behavior {
|
||||
NestedSelectionBehavior::Shallowest => drag_shallowest_manipulation(document, selected, input, select_deepest, add_to_selection, tool_data, responses),
|
||||
NestedSelectionBehavior::Deepest => drag_deepest_manipulation(responses, selected, tool_data, document, input, render_data),
|
||||
}
|
||||
Dragging
|
||||
} else {
|
||||
// Deselect all layers if using shallowest selection behavior
|
||||
// Necessary since for shallowest mode, we need to know the current selected layers to determine the next
|
||||
if tool_data.nested_selection_behavior == NestedSelectionBehavior::Shallowest {
|
||||
responses.add(DocumentMessage::DeselectAllLayers);
|
||||
tool_data.layers_dragging.clear();
|
||||
}
|
||||
tool_data.drag_box_overlay_layer = Some(add_bounding_box(responses));
|
||||
DrawingBox
|
||||
}
|
||||
};
|
||||
tool_data.not_duplicated_layers = None;
|
||||
|
||||
state
|
||||
}
|
||||
(Dragging, PointerMove { axis_align, duplicate, .. }) => {
|
||||
tool_data.is_dragging = true;
|
||||
// TODO: This is a cheat. Break out the relevant functionality from the handler above and call it from there and here.
|
||||
responses.add_front(SelectToolMessage::DocumentIsDirty);
|
||||
|
||||
let mouse_position = axis_align_drag(input.keyboard.get(axis_align as usize), input.mouse.position, tool_data.drag_start);
|
||||
|
||||
let mouse_delta = mouse_position - tool_data.drag_current;
|
||||
|
||||
let snap = tool_data
|
||||
.layers_dragging
|
||||
.iter()
|
||||
.filter_map(|path| document.document_legacy.viewport_bounding_box(path, render_data).ok()?)
|
||||
.flat_map(snapping::expand_bounds)
|
||||
.collect();
|
||||
|
||||
let closest_move = tool_data.snap_manager.snap_layers(responses, document, snap, mouse_delta);
|
||||
// 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.add_front(GraphOperationMessage::TransformChange {
|
||||
layer: path.to_vec(),
|
||||
transform: DAffine2::from_translation(mouse_delta + closest_move),
|
||||
transform_in: TransformIn::Viewport,
|
||||
skip_rerender: true,
|
||||
});
|
||||
}
|
||||
tool_data.drag_current = mouse_position + closest_move;
|
||||
|
||||
if input.keyboard.get(duplicate as usize) && tool_data.not_duplicated_layers.is_none() {
|
||||
tool_data.start_duplicates(document, responses);
|
||||
} else if !input.keyboard.get(duplicate as usize) && tool_data.not_duplicated_layers.is_some() {
|
||||
tool_data.stop_duplicates(responses);
|
||||
}
|
||||
|
||||
Dragging
|
||||
}
|
||||
(ResizingBounds, PointerMove { axis_align, center, .. }) => {
|
||||
if let Some(bounds) = &mut tool_data.bounding_box_overlays {
|
||||
if let Some(movement) = &mut bounds.selected_edges {
|
||||
let (center, axis_align) = (input.keyboard.get(center as usize), input.keyboard.get(axis_align as usize));
|
||||
let document = &document.document_legacy;
|
||||
|
||||
let mouse_position = input.mouse.position;
|
||||
|
||||
let snapped_mouse_position = tool_data.snap_manager.snap_position(responses, document, mouse_position);
|
||||
|
||||
let (position, size) = movement.new_size(snapped_mouse_position, bounds.transform, center, bounds.center_of_transformation, axis_align);
|
||||
let (delta, mut _pivot) = movement.bounds_to_scale_transform(position, size);
|
||||
|
||||
let selected = &tool_data.layers_dragging.iter().collect::<Vec<_>>();
|
||||
let mut selected = Selected::new(&mut bounds.original_transforms, &mut _pivot, selected, responses, &document.document_legacy, None, &ToolType::Select);
|
||||
|
||||
selected.update_transforms(delta);
|
||||
}
|
||||
let selected = &tool_data.layers_dragging.iter().collect::<Vec<_>>();
|
||||
let mut selected = Selected::new(
|
||||
&mut bounds.original_transforms,
|
||||
&mut bounds.center_of_transformation,
|
||||
selected,
|
||||
responses,
|
||||
document,
|
||||
None,
|
||||
&ToolType::Select,
|
||||
);
|
||||
bounds.center_of_transformation = selected.mean_average_of_pivots(render_data);
|
||||
}
|
||||
|
||||
ResizingBounds
|
||||
}
|
||||
(RotatingBounds, PointerMove { snap_angle, .. }) => {
|
||||
} else if rotating_bounds {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
|
||||
if let Some(bounds) = &mut tool_data.bounding_box_overlays {
|
||||
let angle = {
|
||||
let start_offset = tool_data.drag_start - bounds.center_of_transformation;
|
||||
let end_offset = input.mouse.position - bounds.center_of_transformation;
|
||||
|
||||
start_offset.angle_between(end_offset)
|
||||
};
|
||||
|
||||
let snapped_angle = if input.keyboard.get(snap_angle as usize) {
|
||||
let snap_resolution = ROTATE_SNAP_ANGLE.to_radians();
|
||||
(angle / snap_resolution).round() * snap_resolution
|
||||
} else {
|
||||
angle
|
||||
};
|
||||
|
||||
let delta = DAffine2::from_angle(snapped_angle);
|
||||
|
||||
let selected = tool_data.layers_dragging.iter().collect::<Vec<_>>();
|
||||
let selected = selected.iter().collect::<Vec<_>>();
|
||||
let mut selected = Selected::new(
|
||||
&mut bounds.original_transforms,
|
||||
&mut bounds.center_of_transformation,
|
||||
@@ -718,233 +580,363 @@ impl Fsm for SelectToolFsmState {
|
||||
&ToolType::Select,
|
||||
);
|
||||
|
||||
selected.update_transforms(delta);
|
||||
bounds.center_of_transformation = selected.mean_average_of_pivots(render_data);
|
||||
}
|
||||
|
||||
tool_data.layers_dragging = selected;
|
||||
|
||||
RotatingBounds
|
||||
}
|
||||
(DraggingPivot, PointerMove { .. }) => {
|
||||
let mouse_position = input.mouse.position;
|
||||
let snapped_mouse_position = tool_data.snap_manager.snap_position(responses, document, mouse_position);
|
||||
tool_data.pivot.set_viewport_position(snapped_mouse_position, document, render_data, responses);
|
||||
} else if intersection.last().map(|last| selected.iter().any(|selected_layer| last.starts_with(selected_layer))).unwrap_or(false)
|
||||
&& tool_data.nested_selection_behavior == NestedSelectionBehavior::Deepest
|
||||
{
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
|
||||
DraggingPivot
|
||||
}
|
||||
(DrawingBox, PointerMove { .. }) => {
|
||||
tool_data.drag_current = input.mouse.position;
|
||||
tool_data.layers_dragging = selected;
|
||||
|
||||
responses.add_front(DocumentMessage::Overlays(
|
||||
Operation::SetLayerTransformInViewport {
|
||||
path: tool_data.drag_box_overlay_layer.clone().unwrap(),
|
||||
transform: transform_from_box(tool_data.drag_start, tool_data.drag_current, DAffine2::IDENTITY).to_cols_array(),
|
||||
}
|
||||
.into(),
|
||||
));
|
||||
DrawingBox
|
||||
}
|
||||
(Ready, PointerMove { .. }) => {
|
||||
let mut cursor = tool_data.bounding_box_overlays.as_ref().map_or(MouseCursorIcon::Default, |bounds| bounds.get_cursor(input, true));
|
||||
tool_data
|
||||
.snap_manager
|
||||
.start_snap(document, input, document.bounding_boxes(Some(&tool_data.layers_dragging), None, render_data), true, true);
|
||||
|
||||
// Dragging the pivot overrules the other operations
|
||||
if tool_data.pivot.is_over(input.mouse.position) {
|
||||
cursor = MouseCursorIcon::Move;
|
||||
Dragging
|
||||
} else {
|
||||
if !input.keyboard.get(add_to_selection as usize) && tool_data.nested_selection_behavior == NestedSelectionBehavior::Deepest {
|
||||
responses.add(DocumentMessage::DeselectAllLayers);
|
||||
tool_data.layers_dragging.clear();
|
||||
}
|
||||
|
||||
// Generate the select outline (but not if the user is going to use the bound overlays)
|
||||
if cursor == MouseCursorIcon::Default {
|
||||
tool_data.path_outlines.intersect_test_hovered(input, document, responses, render_data);
|
||||
if let Some(intersection) = intersection.pop() {
|
||||
tool_data.layer_selected_on_start = Some(intersection.clone());
|
||||
selected = vec![intersection];
|
||||
|
||||
match tool_data.nested_selection_behavior {
|
||||
NestedSelectionBehavior::Shallowest => drag_shallowest_manipulation(document, selected, input, select_deepest, add_to_selection, tool_data, responses),
|
||||
NestedSelectionBehavior::Deepest => drag_deepest_manipulation(responses, selected, tool_data, document, input, render_data),
|
||||
}
|
||||
Dragging
|
||||
} else {
|
||||
tool_data.path_outlines.clear_hovered(responses);
|
||||
}
|
||||
|
||||
if tool_data.cursor != cursor {
|
||||
tool_data.cursor = cursor;
|
||||
responses.add(FrontendMessage::UpdateMouseCursor { cursor });
|
||||
}
|
||||
|
||||
Ready
|
||||
}
|
||||
(Dragging, Enter) => {
|
||||
rerender_selected_layers(tool_data, responses);
|
||||
|
||||
let response = match input.mouse.position.distance(tool_data.drag_start) < 10. * f64::EPSILON {
|
||||
true => DocumentMessage::Undo,
|
||||
false => DocumentMessage::CommitTransaction,
|
||||
};
|
||||
tool_data.snap_manager.cleanup(responses);
|
||||
responses.add_front(response);
|
||||
|
||||
Ready
|
||||
}
|
||||
(Dragging, DragStop { remove_from_selection }) => {
|
||||
rerender_selected_layers(tool_data, responses);
|
||||
|
||||
// Deselect layer if not snap dragging
|
||||
if !tool_data.is_dragging && input.keyboard.get(remove_from_selection as usize) && tool_data.layer_selected_on_start.is_none() {
|
||||
let quad = tool_data.selection_quad();
|
||||
let intersection = document.document_legacy.intersects_quad_root(quad, render_data);
|
||||
|
||||
if let Some(path) = intersection.last() {
|
||||
let replacement_selected_layers: Vec<_> = document.selected_layers().filter(|&layer| !path.starts_with(layer)).map(|path| path.to_vec()).collect();
|
||||
|
||||
// Deselect all layers if using shallowest selection behavior
|
||||
// Necessary since for shallowest mode, we need to know the current selected layers to determine the next
|
||||
if tool_data.nested_selection_behavior == NestedSelectionBehavior::Shallowest {
|
||||
responses.add(DocumentMessage::DeselectAllLayers);
|
||||
tool_data.layers_dragging.clear();
|
||||
tool_data.layers_dragging.append(replacement_selected_layers.clone().as_mut());
|
||||
|
||||
responses.add(DocumentMessage::SetSelectedLayers { replacement_selected_layers });
|
||||
}
|
||||
tool_data.drag_box_overlay_layer = Some(add_bounding_box(responses));
|
||||
DrawingBox
|
||||
}
|
||||
};
|
||||
tool_data.not_duplicated_layers = None;
|
||||
|
||||
tool_data.is_dragging = false;
|
||||
tool_data.layer_selected_on_start = None;
|
||||
state
|
||||
}
|
||||
(Dragging, PointerMove { axis_align, duplicate, .. }) => {
|
||||
tool_data.is_dragging = true;
|
||||
// TODO: This is a cheat. Break out the relevant functionality from the handler above and call it from there and here.
|
||||
responses.add_front(SelectToolMessage::DocumentIsDirty);
|
||||
|
||||
responses.add(DocumentMessage::CommitTransaction);
|
||||
tool_data.snap_manager.cleanup(responses);
|
||||
let mouse_position = axis_align_drag(input.keyboard.get(axis_align as usize), input.mouse.position, tool_data.drag_start);
|
||||
|
||||
Ready
|
||||
}
|
||||
(ResizingBounds, DragStop { .. } | Enter) => {
|
||||
rerender_selected_layers(tool_data, responses);
|
||||
let mouse_delta = mouse_position - tool_data.drag_current;
|
||||
|
||||
let response = match input.mouse.position.distance(tool_data.drag_start) < 10. * f64::EPSILON {
|
||||
true => DocumentMessage::Undo,
|
||||
false => DocumentMessage::CommitTransaction,
|
||||
};
|
||||
responses.add(response);
|
||||
let snap = tool_data
|
||||
.layers_dragging
|
||||
.iter()
|
||||
.filter_map(|path| document.document_legacy.viewport_bounding_box(path, render_data).ok()?)
|
||||
.flat_map(snapping::expand_bounds)
|
||||
.collect();
|
||||
|
||||
tool_data.snap_manager.cleanup(responses);
|
||||
|
||||
if let Some(bounds) = &mut tool_data.bounding_box_overlays {
|
||||
bounds.original_transforms.clear();
|
||||
}
|
||||
|
||||
Ready
|
||||
}
|
||||
(RotatingBounds, DragStop { .. } | Enter) => {
|
||||
rerender_selected_layers(tool_data, responses);
|
||||
|
||||
let response = match input.mouse.position.distance(tool_data.drag_start) < 10. * f64::EPSILON {
|
||||
true => DocumentMessage::Undo,
|
||||
false => DocumentMessage::CommitTransaction,
|
||||
};
|
||||
responses.add(response);
|
||||
|
||||
if let Some(bounds) = &mut tool_data.bounding_box_overlays {
|
||||
bounds.original_transforms.clear();
|
||||
}
|
||||
|
||||
Ready
|
||||
}
|
||||
(DraggingPivot, DragStop { .. } | Enter) => {
|
||||
let response = match input.mouse.position.distance(tool_data.drag_start) < 10. * f64::EPSILON {
|
||||
true => DocumentMessage::Undo,
|
||||
false => DocumentMessage::CommitTransaction,
|
||||
};
|
||||
responses.add(response);
|
||||
|
||||
tool_data.snap_manager.cleanup(responses);
|
||||
|
||||
Ready
|
||||
}
|
||||
(DrawingBox, DragStop { .. } | Enter) => {
|
||||
let quad = tool_data.selection_quad();
|
||||
// For shallow select we don't update dragging layers until inside drag_start_shallowest_manipulation()
|
||||
tool_data.layers_dragging = document.document_legacy.intersects_quad_root(quad, render_data);
|
||||
responses.add_front(DocumentMessage::AddSelectedLayers {
|
||||
additional_layers: document.document_legacy.intersects_quad_root(quad, render_data),
|
||||
let closest_move = tool_data.snap_manager.snap_layers(responses, document, snap, mouse_delta);
|
||||
// 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.add_front(GraphOperationMessage::TransformChange {
|
||||
layer: path.to_vec(),
|
||||
transform: DAffine2::from_translation(mouse_delta + closest_move),
|
||||
transform_in: TransformIn::Viewport,
|
||||
skip_rerender: true,
|
||||
});
|
||||
responses.add_front(DocumentMessage::Overlays(
|
||||
Operation::DeleteLayer {
|
||||
path: tool_data.drag_box_overlay_layer.take().unwrap(),
|
||||
}
|
||||
.into(),
|
||||
));
|
||||
Ready
|
||||
}
|
||||
(Ready, Enter) => {
|
||||
let mut selected_layers = document.selected_layers();
|
||||
tool_data.drag_current = mouse_position + closest_move;
|
||||
|
||||
if let Some(layer_path) = selected_layers.next() {
|
||||
// Check that only one layer is selected
|
||||
if selected_layers.next().is_none() {
|
||||
if let Ok(layer) = document.document_legacy.layer(layer_path) {
|
||||
if let Ok(network) = layer.as_layer_network() {
|
||||
if network.nodes.values().any(|node| node.name == "Text") {
|
||||
responses.add_front(ToolMessage::ActivateTool { tool_type: ToolType::Text });
|
||||
responses.add(TextToolMessage::EditSelected);
|
||||
}
|
||||
if input.keyboard.get(duplicate as usize) && tool_data.not_duplicated_layers.is_none() {
|
||||
tool_data.start_duplicates(document, responses);
|
||||
} else if !input.keyboard.get(duplicate as usize) && tool_data.not_duplicated_layers.is_some() {
|
||||
tool_data.stop_duplicates(responses);
|
||||
}
|
||||
|
||||
Dragging
|
||||
}
|
||||
(ResizingBounds, PointerMove { axis_align, center, .. }) => {
|
||||
if let Some(bounds) = &mut tool_data.bounding_box_overlays {
|
||||
if let Some(movement) = &mut bounds.selected_edges {
|
||||
let (center, axis_align) = (input.keyboard.get(center as usize), input.keyboard.get(axis_align as usize));
|
||||
|
||||
let mouse_position = input.mouse.position;
|
||||
|
||||
let snapped_mouse_position = tool_data.snap_manager.snap_position(responses, document, mouse_position);
|
||||
|
||||
let (position, size) = movement.new_size(snapped_mouse_position, bounds.transform, center, bounds.center_of_transformation, axis_align);
|
||||
let (delta, mut _pivot) = movement.bounds_to_scale_transform(position, size);
|
||||
|
||||
let selected = &tool_data.layers_dragging.iter().collect::<Vec<_>>();
|
||||
let mut selected = Selected::new(&mut bounds.original_transforms, &mut _pivot, selected, responses, &document.document_legacy, None, &ToolType::Select);
|
||||
|
||||
selected.update_transforms(delta);
|
||||
}
|
||||
}
|
||||
ResizingBounds
|
||||
}
|
||||
(RotatingBounds, PointerMove { snap_angle, .. }) => {
|
||||
if let Some(bounds) = &mut tool_data.bounding_box_overlays {
|
||||
let angle = {
|
||||
let start_offset = tool_data.drag_start - bounds.center_of_transformation;
|
||||
let end_offset = input.mouse.position - bounds.center_of_transformation;
|
||||
|
||||
start_offset.angle_between(end_offset)
|
||||
};
|
||||
|
||||
let snapped_angle = if input.keyboard.get(snap_angle as usize) {
|
||||
let snap_resolution = ROTATE_SNAP_ANGLE.to_radians();
|
||||
(angle / snap_resolution).round() * snap_resolution
|
||||
} else {
|
||||
angle
|
||||
};
|
||||
|
||||
let delta = DAffine2::from_angle(snapped_angle);
|
||||
|
||||
let selected = tool_data.layers_dragging.iter().collect::<Vec<_>>();
|
||||
let mut selected = Selected::new(
|
||||
&mut bounds.original_transforms,
|
||||
&mut bounds.center_of_transformation,
|
||||
&selected,
|
||||
responses,
|
||||
&document.document_legacy,
|
||||
None,
|
||||
&ToolType::Select,
|
||||
);
|
||||
|
||||
selected.update_transforms(delta);
|
||||
}
|
||||
|
||||
RotatingBounds
|
||||
}
|
||||
(DraggingPivot, PointerMove { .. }) => {
|
||||
let mouse_position = input.mouse.position;
|
||||
let snapped_mouse_position = tool_data.snap_manager.snap_position(responses, document, mouse_position);
|
||||
tool_data.pivot.set_viewport_position(snapped_mouse_position, document, render_data, responses);
|
||||
|
||||
DraggingPivot
|
||||
}
|
||||
(DrawingBox, PointerMove { .. }) => {
|
||||
tool_data.drag_current = input.mouse.position;
|
||||
|
||||
responses.add_front(DocumentMessage::Overlays(
|
||||
Operation::SetLayerTransformInViewport {
|
||||
path: tool_data.drag_box_overlay_layer.clone().unwrap(),
|
||||
transform: transform_from_box(tool_data.drag_start, tool_data.drag_current, DAffine2::IDENTITY).to_cols_array(),
|
||||
}
|
||||
.into(),
|
||||
));
|
||||
DrawingBox
|
||||
}
|
||||
(Ready, PointerMove { .. }) => {
|
||||
let mut cursor = tool_data.bounding_box_overlays.as_ref().map_or(MouseCursorIcon::Default, |bounds| bounds.get_cursor(input, true));
|
||||
|
||||
// Dragging the pivot overrules the other operations
|
||||
if tool_data.pivot.is_over(input.mouse.position) {
|
||||
cursor = MouseCursorIcon::Move;
|
||||
}
|
||||
|
||||
// Generate the select outline (but not if the user is going to use the bound overlays)
|
||||
if cursor == MouseCursorIcon::Default {
|
||||
tool_data.path_outlines.intersect_test_hovered(input, document, responses, render_data);
|
||||
} else {
|
||||
tool_data.path_outlines.clear_hovered(responses);
|
||||
}
|
||||
|
||||
if tool_data.cursor != cursor {
|
||||
tool_data.cursor = cursor;
|
||||
responses.add(FrontendMessage::UpdateMouseCursor { cursor });
|
||||
}
|
||||
|
||||
Ready
|
||||
}
|
||||
(Dragging, Enter) => {
|
||||
rerender_selected_layers(tool_data, responses);
|
||||
|
||||
let response = match input.mouse.position.distance(tool_data.drag_start) < 10. * f64::EPSILON {
|
||||
true => DocumentMessage::Undo,
|
||||
false => DocumentMessage::CommitTransaction,
|
||||
};
|
||||
tool_data.snap_manager.cleanup(responses);
|
||||
responses.add_front(response);
|
||||
|
||||
Ready
|
||||
}
|
||||
(Dragging, DragStop { remove_from_selection }) => {
|
||||
rerender_selected_layers(tool_data, responses);
|
||||
|
||||
// Deselect layer if not snap dragging
|
||||
if !tool_data.is_dragging && input.keyboard.get(remove_from_selection as usize) && tool_data.layer_selected_on_start.is_none() {
|
||||
let quad = tool_data.selection_quad();
|
||||
let intersection = document.document_legacy.intersects_quad_root(quad, render_data);
|
||||
|
||||
if let Some(path) = intersection.last() {
|
||||
let replacement_selected_layers: Vec<_> = document.selected_layers().filter(|&layer| !path.starts_with(layer)).map(|path| path.to_vec()).collect();
|
||||
|
||||
tool_data.layers_dragging.clear();
|
||||
tool_data.layers_dragging.append(replacement_selected_layers.clone().as_mut());
|
||||
|
||||
responses.add(DocumentMessage::SetSelectedLayers { replacement_selected_layers });
|
||||
}
|
||||
}
|
||||
|
||||
tool_data.is_dragging = false;
|
||||
tool_data.layer_selected_on_start = None;
|
||||
|
||||
responses.add(DocumentMessage::CommitTransaction);
|
||||
tool_data.snap_manager.cleanup(responses);
|
||||
|
||||
Ready
|
||||
}
|
||||
(ResizingBounds, DragStop { .. } | Enter) => {
|
||||
rerender_selected_layers(tool_data, responses);
|
||||
|
||||
let response = match input.mouse.position.distance(tool_data.drag_start) < 10. * f64::EPSILON {
|
||||
true => DocumentMessage::Undo,
|
||||
false => DocumentMessage::CommitTransaction,
|
||||
};
|
||||
responses.add(response);
|
||||
|
||||
tool_data.snap_manager.cleanup(responses);
|
||||
|
||||
if let Some(bounds) = &mut tool_data.bounding_box_overlays {
|
||||
bounds.original_transforms.clear();
|
||||
}
|
||||
|
||||
Ready
|
||||
}
|
||||
(RotatingBounds, DragStop { .. } | Enter) => {
|
||||
rerender_selected_layers(tool_data, responses);
|
||||
|
||||
let response = match input.mouse.position.distance(tool_data.drag_start) < 10. * f64::EPSILON {
|
||||
true => DocumentMessage::Undo,
|
||||
false => DocumentMessage::CommitTransaction,
|
||||
};
|
||||
responses.add(response);
|
||||
|
||||
if let Some(bounds) = &mut tool_data.bounding_box_overlays {
|
||||
bounds.original_transforms.clear();
|
||||
}
|
||||
|
||||
Ready
|
||||
}
|
||||
(DraggingPivot, DragStop { .. } | Enter) => {
|
||||
let response = match input.mouse.position.distance(tool_data.drag_start) < 10. * f64::EPSILON {
|
||||
true => DocumentMessage::Undo,
|
||||
false => DocumentMessage::CommitTransaction,
|
||||
};
|
||||
responses.add(response);
|
||||
|
||||
tool_data.snap_manager.cleanup(responses);
|
||||
|
||||
Ready
|
||||
}
|
||||
(DrawingBox, DragStop { .. } | Enter) => {
|
||||
let quad = tool_data.selection_quad();
|
||||
// For shallow select we don't update dragging layers until inside drag_start_shallowest_manipulation()
|
||||
tool_data.layers_dragging = document.document_legacy.intersects_quad_root(quad, render_data);
|
||||
responses.add_front(DocumentMessage::AddSelectedLayers {
|
||||
additional_layers: document.document_legacy.intersects_quad_root(quad, render_data),
|
||||
});
|
||||
responses.add_front(DocumentMessage::Overlays(
|
||||
Operation::DeleteLayer {
|
||||
path: tool_data.drag_box_overlay_layer.take().unwrap(),
|
||||
}
|
||||
.into(),
|
||||
));
|
||||
Ready
|
||||
}
|
||||
(Ready, Enter) => {
|
||||
let mut selected_layers = document.selected_layers();
|
||||
|
||||
if let Some(layer_path) = selected_layers.next() {
|
||||
// Check that only one layer is selected
|
||||
if selected_layers.next().is_none() {
|
||||
if let Ok(layer) = document.document_legacy.layer(layer_path) {
|
||||
if let Ok(network) = layer.as_layer_network() {
|
||||
if network.nodes.values().any(|node| node.name == "Text") {
|
||||
responses.add_front(ToolMessage::ActivateTool { tool_type: ToolType::Text });
|
||||
responses.add(TextToolMessage::EditSelected);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ready
|
||||
}
|
||||
(Dragging, Abort) => {
|
||||
rerender_selected_layers(tool_data, responses);
|
||||
|
||||
tool_data.snap_manager.cleanup(responses);
|
||||
responses.add(DocumentMessage::Undo);
|
||||
|
||||
tool_data.path_outlines.clear_selected(responses);
|
||||
tool_data.pivot.clear_overlays(responses);
|
||||
|
||||
Ready
|
||||
}
|
||||
(_, Abort) => {
|
||||
if let Some(path) = tool_data.drag_box_overlay_layer.take() {
|
||||
responses.add_front(DocumentMessage::Overlays(Operation::DeleteLayer { path }.into()))
|
||||
};
|
||||
if let Some(mut bounding_box_overlays) = tool_data.bounding_box_overlays.take() {
|
||||
let selected = tool_data.layers_dragging.iter().collect::<Vec<_>>();
|
||||
let mut selected = Selected::new(
|
||||
&mut bounding_box_overlays.original_transforms,
|
||||
&mut bounding_box_overlays.opposite_pivot,
|
||||
&selected,
|
||||
responses,
|
||||
&document.document_legacy,
|
||||
None,
|
||||
&ToolType::Select,
|
||||
);
|
||||
|
||||
selected.revert_operation();
|
||||
|
||||
bounding_box_overlays.delete(responses);
|
||||
}
|
||||
|
||||
tool_data.path_outlines.clear_hovered(responses);
|
||||
tool_data.path_outlines.clear_selected(responses);
|
||||
tool_data.pivot.clear_overlays(responses);
|
||||
|
||||
tool_data.snap_manager.cleanup(responses);
|
||||
Ready
|
||||
}
|
||||
(_, Align { axis, aggregate }) => {
|
||||
responses.add(DocumentMessage::AlignSelectedLayers { axis, aggregate });
|
||||
|
||||
self
|
||||
}
|
||||
(_, FlipHorizontal) => {
|
||||
responses.add(DocumentMessage::FlipSelectedLayers { flip_axis: FlipAxis::X });
|
||||
|
||||
self
|
||||
}
|
||||
(_, FlipVertical) => {
|
||||
responses.add(DocumentMessage::FlipSelectedLayers { flip_axis: FlipAxis::Y });
|
||||
|
||||
self
|
||||
}
|
||||
(_, SetPivot { position }) => {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
|
||||
let pos: Option<DVec2> = position.into();
|
||||
tool_data.pivot.set_normalized_position(pos.unwrap(), document, render_data, responses);
|
||||
|
||||
self
|
||||
}
|
||||
_ => self,
|
||||
Ready
|
||||
}
|
||||
} else {
|
||||
self
|
||||
(Dragging, Abort) => {
|
||||
rerender_selected_layers(tool_data, responses);
|
||||
|
||||
tool_data.snap_manager.cleanup(responses);
|
||||
responses.add(DocumentMessage::Undo);
|
||||
|
||||
tool_data.path_outlines.clear_selected(responses);
|
||||
tool_data.pivot.clear_overlays(responses);
|
||||
|
||||
Ready
|
||||
}
|
||||
(_, Abort) => {
|
||||
if let Some(path) = tool_data.drag_box_overlay_layer.take() {
|
||||
responses.add_front(DocumentMessage::Overlays(Operation::DeleteLayer { path }.into()))
|
||||
};
|
||||
if let Some(mut bounding_box_overlays) = tool_data.bounding_box_overlays.take() {
|
||||
let selected = tool_data.layers_dragging.iter().collect::<Vec<_>>();
|
||||
let mut selected = Selected::new(
|
||||
&mut bounding_box_overlays.original_transforms,
|
||||
&mut bounding_box_overlays.opposite_pivot,
|
||||
&selected,
|
||||
responses,
|
||||
&document.document_legacy,
|
||||
None,
|
||||
&ToolType::Select,
|
||||
);
|
||||
|
||||
selected.revert_operation();
|
||||
|
||||
bounding_box_overlays.delete(responses);
|
||||
}
|
||||
|
||||
tool_data.path_outlines.clear_hovered(responses);
|
||||
tool_data.path_outlines.clear_selected(responses);
|
||||
tool_data.pivot.clear_overlays(responses);
|
||||
|
||||
tool_data.snap_manager.cleanup(responses);
|
||||
Ready
|
||||
}
|
||||
(_, Align { axis, aggregate }) => {
|
||||
responses.add(DocumentMessage::AlignSelectedLayers { axis, aggregate });
|
||||
|
||||
self
|
||||
}
|
||||
(_, FlipHorizontal) => {
|
||||
responses.add(DocumentMessage::FlipSelectedLayers { flip_axis: FlipAxis::X });
|
||||
|
||||
self
|
||||
}
|
||||
(_, FlipVertical) => {
|
||||
responses.add(DocumentMessage::FlipSelectedLayers { flip_axis: FlipAxis::Y });
|
||||
|
||||
self
|
||||
}
|
||||
(_, SetPivot { position }) => {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
|
||||
let pos: Option<DVec2> = position.into();
|
||||
tool_data.pivot.set_normalized_position(pos.unwrap(), document, render_data, responses);
|
||||
|
||||
self
|
||||
}
|
||||
_ => self,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1117,7 +1109,7 @@ fn drag_shallowest_manipulation(
|
||||
combined_layers.push(incoming_layer_path_vector);
|
||||
}
|
||||
// Shared shallowest common folder of the combined layers
|
||||
let shallowest_common_folder = document.document_legacy.shallowest_common_folder(combined_layers.iter().copied()).unwrap_or_default().to_vec();
|
||||
let shallowest_common_folder = document.document_legacy.shallowest_common_folder(combined_layers.into_iter()).unwrap_or_default().to_vec();
|
||||
let mut selected_layer_path_parent = shallowest_common_folder.to_vec();
|
||||
|
||||
// Determine if the incoming layer path is already selected
|
||||
@@ -1175,12 +1167,12 @@ fn drag_shallowest_manipulation(
|
||||
}
|
||||
} else if selected_layers_count > 1 {
|
||||
let direct_child = incoming_layer_path_vector
|
||||
.iter()
|
||||
.copied()
|
||||
.into_iter()
|
||||
.filter(|path| !shallowest_common_folder.contains(path))
|
||||
.take(1)
|
||||
.copied()
|
||||
.collect::<Vec<_>>();
|
||||
let already_selected_direct_child = selected_layers_collected.contains(&direct_child.clone().as_slice());
|
||||
let already_selected_direct_child = selected_layers_collected.contains(&direct_child.as_slice());
|
||||
|
||||
// Update layer tree by filtering any duplicate layers (e.g. If a parent and one of its children are selected)
|
||||
let mut replacement_selected_layers: Vec<Vec<u64>> = Vec::new();
|
||||
|
||||
@@ -1,21 +1,13 @@
|
||||
use super::tool_prelude::*;
|
||||
use crate::consts::DRAG_THRESHOLD;
|
||||
use crate::messages::frontend::utility_types::MouseCursorIcon;
|
||||
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion};
|
||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||
use crate::messages::prelude::*;
|
||||
use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType};
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||
use crate::messages::tool::common_functionality::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::{LayerId, Operation};
|
||||
use graphene_core::vector::style::{Fill, Stroke};
|
||||
use graphene_core::Color;
|
||||
|
||||
use glam::DVec2;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct SplineTool {
|
||||
fsm_state: SplineToolFsmState,
|
||||
@@ -128,33 +120,31 @@ impl LayoutHolder for SplineTool {
|
||||
|
||||
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,
|
||||
SplineOptionsUpdate::FillColor(color) => {
|
||||
self.options.fill.custom_color = color;
|
||||
self.options.fill.color_type = ToolColorType::Custom;
|
||||
}
|
||||
SplineOptionsUpdate::FillColorType(color_type) => self.options.fill.color_type = color_type,
|
||||
SplineOptionsUpdate::StrokeColor(color) => {
|
||||
self.options.stroke.custom_color = color;
|
||||
self.options.stroke.color_type = ToolColorType::Custom;
|
||||
}
|
||||
SplineOptionsUpdate::StrokeColorType(color_type) => self.options.stroke.color_type = color_type,
|
||||
SplineOptionsUpdate::WorkingColors(primary, secondary) => {
|
||||
self.options.stroke.primary_working_color = primary;
|
||||
self.options.stroke.secondary_working_color = secondary;
|
||||
self.options.fill.primary_working_color = primary;
|
||||
self.options.fill.secondary_working_color = secondary;
|
||||
}
|
||||
}
|
||||
|
||||
self.send_layout(responses, LayoutTarget::ToolOptions);
|
||||
|
||||
let ToolMessage::Spline(SplineToolMessage::UpdateOptions(action)) = message else {
|
||||
self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &self.options, responses, true);
|
||||
return;
|
||||
};
|
||||
match action {
|
||||
SplineOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight,
|
||||
SplineOptionsUpdate::FillColor(color) => {
|
||||
self.options.fill.custom_color = color;
|
||||
self.options.fill.color_type = ToolColorType::Custom;
|
||||
}
|
||||
SplineOptionsUpdate::FillColorType(color_type) => self.options.fill.color_type = color_type,
|
||||
SplineOptionsUpdate::StrokeColor(color) => {
|
||||
self.options.stroke.custom_color = color;
|
||||
self.options.stroke.color_type = ToolColorType::Custom;
|
||||
}
|
||||
SplineOptionsUpdate::StrokeColorType(color_type) => self.options.stroke.color_type = color_type,
|
||||
SplineOptionsUpdate::WorkingColors(primary, secondary) => {
|
||||
self.options.stroke.primary_working_color = primary;
|
||||
self.options.stroke.secondary_working_color = secondary;
|
||||
self.options.fill.primary_working_color = primary;
|
||||
self.options.fill.secondary_working_color = secondary;
|
||||
}
|
||||
}
|
||||
|
||||
self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &self.options, responses, true);
|
||||
self.send_layout(responses, LayoutTarget::ToolOptions);
|
||||
}
|
||||
|
||||
fn actions(&self) -> ActionList {
|
||||
@@ -219,86 +209,85 @@ impl Fsm for SplineToolFsmState {
|
||||
use SplineToolFsmState::*;
|
||||
use SplineToolMessage::*;
|
||||
|
||||
let transform = document.document_legacy.root.transform;
|
||||
let transform = document.document_legacy.metadata.document_to_viewport;
|
||||
|
||||
if let ToolMessage::Spline(event) = event {
|
||||
match (self, event) {
|
||||
(_, CanvasTransformed) => {
|
||||
tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(None, None, render_data), true, true);
|
||||
self
|
||||
}
|
||||
(Ready, DragStart) => {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
responses.add(DocumentMessage::DeselectAllLayers);
|
||||
tool_data.path = Some(document.get_path_for_new_layer());
|
||||
|
||||
tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(None, None, render_data), true, true);
|
||||
tool_data.snap_manager.add_all_document_handles(document, input, &[], &[], &[]);
|
||||
let snapped_position = tool_data.snap_manager.snap_position(responses, document, input.mouse.position);
|
||||
|
||||
let pos = transform.inverse().transform_point2(snapped_position);
|
||||
|
||||
tool_data.points.push(pos);
|
||||
tool_data.next_point = pos;
|
||||
|
||||
tool_data.weight = tool_options.line_weight;
|
||||
|
||||
add_spline(tool_data, true, tool_options.fill.active_color(), tool_options.stroke.active_color(), responses);
|
||||
|
||||
Drawing
|
||||
}
|
||||
(Drawing, DragStop) => {
|
||||
let snapped_position = tool_data.snap_manager.snap_position(responses, document, input.mouse.position);
|
||||
let pos = transform.inverse().transform_point2(snapped_position);
|
||||
|
||||
if let Some(last_pos) = tool_data.points.last() {
|
||||
if last_pos.distance(pos) > DRAG_THRESHOLD {
|
||||
tool_data.points.push(pos);
|
||||
tool_data.next_point = pos;
|
||||
}
|
||||
}
|
||||
|
||||
responses.add(remove_preview(tool_data));
|
||||
add_spline(tool_data, true, tool_options.fill.active_color(), tool_options.stroke.active_color(), responses);
|
||||
|
||||
Drawing
|
||||
}
|
||||
(Drawing, PointerMove) => {
|
||||
let snapped_position = tool_data.snap_manager.snap_position(responses, document, input.mouse.position);
|
||||
let pos = transform.inverse().transform_point2(snapped_position);
|
||||
tool_data.next_point = pos;
|
||||
|
||||
responses.add(remove_preview(tool_data));
|
||||
add_spline(tool_data, true, tool_options.fill.active_color(), tool_options.stroke.active_color(), responses);
|
||||
|
||||
Drawing
|
||||
}
|
||||
(Drawing, Confirm) | (Drawing, Abort) => {
|
||||
if tool_data.points.len() >= 2 {
|
||||
responses.add(remove_preview(tool_data));
|
||||
add_spline(tool_data, false, tool_options.fill.active_color(), tool_options.stroke.active_color(), responses);
|
||||
responses.add(DocumentMessage::CommitTransaction);
|
||||
} else {
|
||||
responses.add(DocumentMessage::AbortTransaction);
|
||||
}
|
||||
|
||||
tool_data.path = None;
|
||||
tool_data.points.clear();
|
||||
tool_data.snap_manager.cleanup(responses);
|
||||
|
||||
Ready
|
||||
}
|
||||
(_, WorkingColorChanged) => {
|
||||
responses.add(SplineToolMessage::UpdateOptions(SplineOptionsUpdate::WorkingColors(
|
||||
Some(global_tool_data.primary_color),
|
||||
Some(global_tool_data.secondary_color),
|
||||
)));
|
||||
self
|
||||
}
|
||||
_ => self,
|
||||
let ToolMessage::Spline(event) = event else {
|
||||
return self;
|
||||
};
|
||||
match (self, event) {
|
||||
(_, CanvasTransformed) => {
|
||||
tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(None, None, render_data), true, true);
|
||||
self
|
||||
}
|
||||
} else {
|
||||
self
|
||||
(Ready, DragStart) => {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
responses.add(DocumentMessage::DeselectAllLayers);
|
||||
tool_data.path = Some(document.get_path_for_new_layer());
|
||||
|
||||
tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(None, None, render_data), true, true);
|
||||
tool_data.snap_manager.add_all_document_handles(document, input, &[], &[], &[]);
|
||||
let snapped_position = tool_data.snap_manager.snap_position(responses, document, input.mouse.position);
|
||||
|
||||
let pos = transform.inverse().transform_point2(snapped_position);
|
||||
|
||||
tool_data.points.push(pos);
|
||||
tool_data.next_point = pos;
|
||||
|
||||
tool_data.weight = tool_options.line_weight;
|
||||
|
||||
add_spline(tool_data, true, tool_options.fill.active_color(), tool_options.stroke.active_color(), responses);
|
||||
|
||||
Drawing
|
||||
}
|
||||
(Drawing, DragStop) => {
|
||||
let snapped_position = tool_data.snap_manager.snap_position(responses, document, input.mouse.position);
|
||||
let pos = transform.inverse().transform_point2(snapped_position);
|
||||
|
||||
if let Some(last_pos) = tool_data.points.last() {
|
||||
if last_pos.distance(pos) > DRAG_THRESHOLD {
|
||||
tool_data.points.push(pos);
|
||||
tool_data.next_point = pos;
|
||||
}
|
||||
}
|
||||
|
||||
responses.add(remove_preview(tool_data));
|
||||
add_spline(tool_data, true, tool_options.fill.active_color(), tool_options.stroke.active_color(), responses);
|
||||
|
||||
Drawing
|
||||
}
|
||||
(Drawing, PointerMove) => {
|
||||
let snapped_position = tool_data.snap_manager.snap_position(responses, document, input.mouse.position);
|
||||
let pos = transform.inverse().transform_point2(snapped_position);
|
||||
tool_data.next_point = pos;
|
||||
|
||||
responses.add(remove_preview(tool_data));
|
||||
add_spline(tool_data, true, tool_options.fill.active_color(), tool_options.stroke.active_color(), responses);
|
||||
|
||||
Drawing
|
||||
}
|
||||
(Drawing, Confirm) | (Drawing, Abort) => {
|
||||
if tool_data.points.len() >= 2 {
|
||||
responses.add(remove_preview(tool_data));
|
||||
add_spline(tool_data, false, tool_options.fill.active_color(), tool_options.stroke.active_color(), responses);
|
||||
responses.add(DocumentMessage::CommitTransaction);
|
||||
} else {
|
||||
responses.add(DocumentMessage::AbortTransaction);
|
||||
}
|
||||
|
||||
tool_data.path = None;
|
||||
tool_data.points.clear();
|
||||
tool_data.snap_manager.cleanup(responses);
|
||||
|
||||
Ready
|
||||
}
|
||||
(_, WorkingColorChanged) => {
|
||||
responses.add(SplineToolMessage::UpdateOptions(SplineOptionsUpdate::WorkingColors(
|
||||
Some(global_tool_data.primary_color),
|
||||
Some(global_tool_data.secondary_color),
|
||||
)));
|
||||
self
|
||||
}
|
||||
_ => self,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +1,9 @@
|
||||
#![allow(clippy::too_many_arguments)]
|
||||
|
||||
use super::tool_prelude::*;
|
||||
use crate::application::generate_uuid;
|
||||
use crate::consts::{COLOR_ACCENT, SELECTION_TOLERANCE};
|
||||
use crate::messages::frontend::utility_types::MouseCursorIcon;
|
||||
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion};
|
||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||
use crate::messages::portfolio::document::node_graph::new_text_network;
|
||||
use crate::messages::prelude::*;
|
||||
use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType};
|
||||
use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType};
|
||||
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
|
||||
|
||||
use document_legacy::intersection::Quad;
|
||||
use document_legacy::layers::layer_info::Layer;
|
||||
@@ -21,9 +15,6 @@ use graph_craft::document::{DocumentNode, NodeId, NodeInput, NodeNetwork};
|
||||
use graphene_core::text::{load_face, Font};
|
||||
use graphene_core::Color;
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct TextTool {
|
||||
fsm_state: TextToolFsmState,
|
||||
@@ -154,32 +145,30 @@ impl LayoutHolder for TextTool {
|
||||
|
||||
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 } => {
|
||||
self.options.font_name = family;
|
||||
self.options.font_style = style;
|
||||
|
||||
self.send_layout(responses, LayoutTarget::ToolOptions);
|
||||
}
|
||||
TextOptionsUpdate::FontSize(font_size) => self.options.font_size = font_size,
|
||||
TextOptionsUpdate::FillColor(color) => {
|
||||
self.options.fill.custom_color = color;
|
||||
self.options.fill.color_type = ToolColorType::Custom;
|
||||
}
|
||||
TextOptionsUpdate::FillColorType(color_type) => self.options.fill.color_type = color_type,
|
||||
TextOptionsUpdate::WorkingColors(primary, secondary) => {
|
||||
self.options.fill.primary_working_color = primary;
|
||||
self.options.fill.secondary_working_color = secondary;
|
||||
}
|
||||
}
|
||||
|
||||
self.send_layout(responses, LayoutTarget::ToolOptions);
|
||||
|
||||
let ToolMessage::Text(TextToolMessage::UpdateOptions(action)) = message else {
|
||||
self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &self.options, responses, true);
|
||||
return;
|
||||
};
|
||||
match action {
|
||||
TextOptionsUpdate::Font { family, style } => {
|
||||
self.options.font_name = family;
|
||||
self.options.font_style = style;
|
||||
|
||||
self.send_layout(responses, LayoutTarget::ToolOptions);
|
||||
}
|
||||
TextOptionsUpdate::FontSize(font_size) => self.options.font_size = font_size,
|
||||
TextOptionsUpdate::FillColor(color) => {
|
||||
self.options.fill.custom_color = color;
|
||||
self.options.fill.color_type = ToolColorType::Custom;
|
||||
}
|
||||
TextOptionsUpdate::FillColorType(color_type) => self.options.fill.color_type = color_type,
|
||||
TextOptionsUpdate::WorkingColors(primary, secondary) => {
|
||||
self.options.fill.primary_working_color = primary;
|
||||
self.options.fill.secondary_working_color = secondary;
|
||||
}
|
||||
}
|
||||
|
||||
self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &self.options, responses, true);
|
||||
self.send_layout(responses, LayoutTarget::ToolOptions);
|
||||
}
|
||||
|
||||
fn actions(&self) -> ActionList {
|
||||
@@ -503,88 +492,87 @@ impl Fsm for TextToolFsmState {
|
||||
render_data,
|
||||
..
|
||||
} = transition_data;
|
||||
if let ToolMessage::Text(event) = event {
|
||||
match (self, event) {
|
||||
(TextToolFsmState::Editing, TextToolMessage::DocumentIsDirty) => {
|
||||
responses.add(FrontendMessage::DisplayEditableTextboxTransform {
|
||||
transform: document.document_legacy.multiply_transforms(&tool_data.layer_path).ok().unwrap_or_default().to_cols_array(),
|
||||
});
|
||||
tool_data.update_bounds_overlay(document, render_data, responses);
|
||||
TextToolFsmState::Editing
|
||||
}
|
||||
(state, TextToolMessage::DocumentIsDirty) => {
|
||||
update_overlays(document, tool_data, responses, render_data);
|
||||
|
||||
state
|
||||
}
|
||||
(state, TextToolMessage::Interact) => {
|
||||
tool_data.editing_text = Some(EditingText {
|
||||
text: String::new(),
|
||||
transform: DAffine2::from_translation(input.mouse.position),
|
||||
font_size: tool_options.font_size as f64,
|
||||
font: Font::new(tool_options.font_name.clone(), tool_options.font_style.clone()),
|
||||
color: tool_options.fill.active_color(),
|
||||
});
|
||||
tool_data.new_text = String::new();
|
||||
tool_data.layer_path = document.get_path_for_new_layer();
|
||||
|
||||
tool_data.interact(state, input.mouse.position, document, render_data, responses)
|
||||
}
|
||||
(state, TextToolMessage::EditSelected) => {
|
||||
if let Some(layer_path) = can_edit_selected(document) {
|
||||
tool_data.start_editing_layer(&layer_path, state, document, render_data, responses);
|
||||
return TextToolFsmState::Editing;
|
||||
}
|
||||
|
||||
state
|
||||
}
|
||||
(state, TextToolMessage::Abort) => {
|
||||
if state == TextToolFsmState::Editing {
|
||||
tool_data.set_editing(false, render_data, responses);
|
||||
}
|
||||
|
||||
resize_overlays(&mut tool_data.overlays, responses, 0);
|
||||
|
||||
TextToolFsmState::Ready
|
||||
}
|
||||
(TextToolFsmState::Editing, TextToolMessage::CommitText) => {
|
||||
responses.add(FrontendMessage::TriggerTextCommit);
|
||||
|
||||
TextToolFsmState::Editing
|
||||
}
|
||||
(TextToolFsmState::Editing, TextToolMessage::TextChange { new_text }) => {
|
||||
let layer_path = tool_data.layer_path.clone();
|
||||
let network = get_network(&layer_path, document).unwrap();
|
||||
tool_data.fix_text_bounds(&new_text, document, render_data, responses);
|
||||
responses.add(NodeGraphMessage::SetQualifiedInputValue {
|
||||
layer_path,
|
||||
node_path: vec![get_text_node_id(network).unwrap()],
|
||||
input_index: 1,
|
||||
value: TaggedValue::String(new_text),
|
||||
});
|
||||
|
||||
tool_data.set_editing(false, render_data, responses);
|
||||
|
||||
resize_overlays(&mut tool_data.overlays, responses, 0);
|
||||
|
||||
TextToolFsmState::Ready
|
||||
}
|
||||
(TextToolFsmState::Editing, TextToolMessage::UpdateBounds { new_text }) => {
|
||||
tool_data.new_text = new_text;
|
||||
tool_data.update_bounds_overlay(document, render_data, responses);
|
||||
TextToolFsmState::Editing
|
||||
}
|
||||
(_, TextToolMessage::WorkingColorChanged) => {
|
||||
responses.add(TextToolMessage::UpdateOptions(TextOptionsUpdate::WorkingColors(
|
||||
Some(global_tool_data.primary_color),
|
||||
Some(global_tool_data.secondary_color),
|
||||
)));
|
||||
self
|
||||
}
|
||||
_ => self,
|
||||
let ToolMessage::Text(event) = event else {
|
||||
return self;
|
||||
};
|
||||
match (self, event) {
|
||||
(TextToolFsmState::Editing, TextToolMessage::DocumentIsDirty) => {
|
||||
responses.add(FrontendMessage::DisplayEditableTextboxTransform {
|
||||
transform: document.document_legacy.multiply_transforms(&tool_data.layer_path).ok().unwrap_or_default().to_cols_array(),
|
||||
});
|
||||
tool_data.update_bounds_overlay(document, render_data, responses);
|
||||
TextToolFsmState::Editing
|
||||
}
|
||||
} else {
|
||||
self
|
||||
(state, TextToolMessage::DocumentIsDirty) => {
|
||||
update_overlays(document, tool_data, responses, render_data);
|
||||
|
||||
state
|
||||
}
|
||||
(state, TextToolMessage::Interact) => {
|
||||
tool_data.editing_text = Some(EditingText {
|
||||
text: String::new(),
|
||||
transform: DAffine2::from_translation(input.mouse.position),
|
||||
font_size: tool_options.font_size as f64,
|
||||
font: Font::new(tool_options.font_name.clone(), tool_options.font_style.clone()),
|
||||
color: tool_options.fill.active_color(),
|
||||
});
|
||||
tool_data.new_text = String::new();
|
||||
tool_data.layer_path = document.get_path_for_new_layer();
|
||||
|
||||
tool_data.interact(state, input.mouse.position, document, render_data, responses)
|
||||
}
|
||||
(state, TextToolMessage::EditSelected) => {
|
||||
if let Some(layer_path) = can_edit_selected(document) {
|
||||
tool_data.start_editing_layer(&layer_path, state, document, render_data, responses);
|
||||
return TextToolFsmState::Editing;
|
||||
}
|
||||
|
||||
state
|
||||
}
|
||||
(state, TextToolMessage::Abort) => {
|
||||
if state == TextToolFsmState::Editing {
|
||||
tool_data.set_editing(false, render_data, responses);
|
||||
}
|
||||
|
||||
resize_overlays(&mut tool_data.overlays, responses, 0);
|
||||
|
||||
TextToolFsmState::Ready
|
||||
}
|
||||
(TextToolFsmState::Editing, TextToolMessage::CommitText) => {
|
||||
responses.add(FrontendMessage::TriggerTextCommit);
|
||||
|
||||
TextToolFsmState::Editing
|
||||
}
|
||||
(TextToolFsmState::Editing, TextToolMessage::TextChange { new_text }) => {
|
||||
let layer_path = tool_data.layer_path.clone();
|
||||
let network = get_network(&layer_path, document).unwrap();
|
||||
tool_data.fix_text_bounds(&new_text, document, render_data, responses);
|
||||
responses.add(NodeGraphMessage::SetQualifiedInputValue {
|
||||
layer_path,
|
||||
node_path: vec![get_text_node_id(network).unwrap()],
|
||||
input_index: 1,
|
||||
value: TaggedValue::String(new_text),
|
||||
});
|
||||
|
||||
tool_data.set_editing(false, render_data, responses);
|
||||
|
||||
resize_overlays(&mut tool_data.overlays, responses, 0);
|
||||
|
||||
TextToolFsmState::Ready
|
||||
}
|
||||
(TextToolFsmState::Editing, TextToolMessage::UpdateBounds { new_text }) => {
|
||||
tool_data.new_text = new_text;
|
||||
tool_data.update_bounds_overlay(document, render_data, responses);
|
||||
TextToolFsmState::Editing
|
||||
}
|
||||
(_, TextToolMessage::WorkingColorChanged) => {
|
||||
responses.add(TextToolMessage::UpdateOptions(TextOptionsUpdate::WorkingColors(
|
||||
Some(global_tool_data.primary_color),
|
||||
Some(global_tool_data.secondary_color),
|
||||
)));
|
||||
self
|
||||
}
|
||||
_ => self,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user