mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-10-04 04:58:11 +08:00
separate operation into different files
This commit is contained in:
@@ -176,7 +176,7 @@ pub fn input_mappings() -> Mapping {
|
|||||||
entry!(KeyDown(MouseLeft); action_dispatch=OperationToolMessage::DragStart),
|
entry!(KeyDown(MouseLeft); action_dispatch=OperationToolMessage::DragStart),
|
||||||
entry!(KeyUp(MouseLeft); action_dispatch=OperationToolMessage::DragStop),
|
entry!(KeyUp(MouseLeft); action_dispatch=OperationToolMessage::DragStop),
|
||||||
entry!(KeyDown(MouseRight); action_dispatch=OperationToolMessage::Confirm),
|
entry!(KeyDown(MouseRight); action_dispatch=OperationToolMessage::Confirm),
|
||||||
entry!(KeyDown(Escape); action_dispatch=OperationToolMessage::Confirm),
|
entry!(KeyDown(Escape); action_dispatch=OperationToolMessage::Abort),
|
||||||
entry!(KeyDown(Enter); action_dispatch=OperationToolMessage::Confirm),
|
entry!(KeyDown(Enter); action_dispatch=OperationToolMessage::Confirm),
|
||||||
//
|
//
|
||||||
// ShapeToolMessage
|
// ShapeToolMessage
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ pub mod compass_rose;
|
|||||||
pub mod gizmos;
|
pub mod gizmos;
|
||||||
pub mod graph_modification_utils;
|
pub mod graph_modification_utils;
|
||||||
pub mod measure;
|
pub mod measure;
|
||||||
|
pub mod operations;
|
||||||
pub mod pivot;
|
pub mod pivot;
|
||||||
pub mod resize;
|
pub mod resize;
|
||||||
pub mod shape_editor;
|
pub mod shape_editor;
|
||||||
|
|||||||
@@ -0,0 +1,143 @@
|
|||||||
|
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
|
||||||
|
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||||
|
use crate::messages::tool::common_functionality::shapes::shape_utility::extract_circular_repeat_parameters;
|
||||||
|
use crate::messages::tool::tool_messages::operation_tool::{OperationToolData, OperationToolFsmState};
|
||||||
|
use crate::messages::tool::tool_messages::tool_prelude::*;
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct CircularRepeatOperation;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default)]
|
||||||
|
pub struct CircularRepeatOperationData {
|
||||||
|
clicked_layer_radius: (LayerNodeIdentifier, f64),
|
||||||
|
layers_dragging: Vec<(LayerNodeIdentifier, f64)>,
|
||||||
|
initial_center: DVec2,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CircularRepeatOperation {
|
||||||
|
pub fn create_node(tool_data: &mut OperationToolData, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>, input: &InputPreprocessorMessageHandler) {
|
||||||
|
let selected_layers = document
|
||||||
|
.network_interface
|
||||||
|
.selected_nodes()
|
||||||
|
.selected_layers(document.metadata())
|
||||||
|
.collect::<HashSet<LayerNodeIdentifier>>();
|
||||||
|
|
||||||
|
let Some(clicked_layer) = document.click(&input) else { return };
|
||||||
|
responses.add(DocumentMessage::StartTransaction);
|
||||||
|
let viewport = document.metadata().transform_to_viewport(clicked_layer);
|
||||||
|
let center = viewport.transform_point2(DVec2::ZERO);
|
||||||
|
|
||||||
|
// Only activate the operation if the click is close enough to the repeat center
|
||||||
|
if center.distance(input.mouse.position) > 5. {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
// If the clicked layer is part of the current selection, apply the operation to all selected layers
|
||||||
|
if selected_layers.contains(&clicked_layer) {
|
||||||
|
tool_data.circular_operation_data.layers_dragging = selected_layers
|
||||||
|
.iter()
|
||||||
|
.map(|layer| {
|
||||||
|
let (_angle_offset, radius, _count) = extract_circular_repeat_parameters(Some(*layer), document).unwrap_or((0.0, 0.0, 6));
|
||||||
|
if *layer == clicked_layer {
|
||||||
|
tool_data.circular_operation_data.clicked_layer_radius = (*layer, radius)
|
||||||
|
}
|
||||||
|
(*layer, radius)
|
||||||
|
})
|
||||||
|
.collect::<Vec<(LayerNodeIdentifier, f64)>>();
|
||||||
|
} else {
|
||||||
|
// If the clicked layer is not in the selection, deselect all and only apply the operation to the clicked layer
|
||||||
|
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![clicked_layer.to_node()] });
|
||||||
|
|
||||||
|
let (_angle_offset, radius, _count) = extract_circular_repeat_parameters(Some(clicked_layer), document).unwrap_or((0.0, 0.0, 6));
|
||||||
|
|
||||||
|
tool_data.circular_operation_data.clicked_layer_radius = (clicked_layer, radius);
|
||||||
|
tool_data.circular_operation_data.layers_dragging = vec![(clicked_layer, radius)];
|
||||||
|
}
|
||||||
|
tool_data.drag_start = input.mouse.position;
|
||||||
|
tool_data.circular_operation_data.initial_center = viewport.transform_point2(DVec2::ZERO);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_shape(tool_data: &mut OperationToolData, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>, input: &InputPreprocessorMessageHandler) {
|
||||||
|
let (_clicked_layer, clicked_radius) = tool_data.circular_operation_data.clicked_layer_radius;
|
||||||
|
|
||||||
|
let viewport = document.metadata().transform_to_viewport(tool_data.circular_operation_data.clicked_layer_radius.0);
|
||||||
|
let sign = (input.mouse.position - tool_data.circular_operation_data.initial_center)
|
||||||
|
.dot(viewport.transform_vector2(DVec2::Y))
|
||||||
|
.signum();
|
||||||
|
|
||||||
|
// Compute mouse movement in local space, ignoring the layer’s own transform
|
||||||
|
let delta = document
|
||||||
|
.metadata()
|
||||||
|
.downstream_transform_to_viewport(tool_data.circular_operation_data.clicked_layer_radius.0)
|
||||||
|
.inverse()
|
||||||
|
.transform_vector2(input.mouse.position - tool_data.circular_operation_data.initial_center)
|
||||||
|
.length() * sign;
|
||||||
|
|
||||||
|
for (layer, initial_radius) in &tool_data.circular_operation_data.layers_dragging {
|
||||||
|
// If the layer’s sign differs from the clicked layer, invert delta to preserve consistent in/out dragging behavior
|
||||||
|
let new_radius = if initial_radius.signum() == clicked_radius.signum() {
|
||||||
|
*initial_radius + delta
|
||||||
|
} else {
|
||||||
|
*initial_radius + delta.signum() * -1. * delta.abs()
|
||||||
|
};
|
||||||
|
|
||||||
|
responses.add(GraphOperationMessage::CircularRepeatSet {
|
||||||
|
layer: *layer,
|
||||||
|
angle: 0.,
|
||||||
|
radius: new_radius,
|
||||||
|
count: 6,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn overlays(
|
||||||
|
tool_state: &OperationToolFsmState,
|
||||||
|
tool_data: &mut OperationToolData,
|
||||||
|
document: &DocumentMessageHandler,
|
||||||
|
input: &InputPreprocessorMessageHandler,
|
||||||
|
overlay_context: &mut OverlayContext,
|
||||||
|
) {
|
||||||
|
match tool_state {
|
||||||
|
OperationToolFsmState::Ready => {
|
||||||
|
// Draw overlays for all selected layers
|
||||||
|
for layer in document.network_interface.selected_nodes().selected_layers(document.metadata()) {
|
||||||
|
Self::draw_layer_overlay(layer, document, input, overlay_context)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also highlight the hovered layer if it’s not selected
|
||||||
|
if let Some(layer) = document.click(&input) {
|
||||||
|
Self::draw_layer_overlay(layer, document, input, overlay_context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// While dragging, only draw overlays for the layers being modified
|
||||||
|
for layer in tool_data.circular_operation_data.layers_dragging.iter().map(|(l, _)| l) {
|
||||||
|
let Some(vector) = document.network_interface.compute_modified_vector(*layer) else { continue };
|
||||||
|
let viewport = document.metadata().transform_to_viewport(*layer);
|
||||||
|
|
||||||
|
overlay_context.outline_vector(&vector, viewport);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_layer_overlay(layer: LayerNodeIdentifier, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, overlay_context: &mut OverlayContext) {
|
||||||
|
if let Some(vector) = document.network_interface.compute_modified_vector(layer) {
|
||||||
|
let viewport = document.metadata().transform_to_viewport(layer);
|
||||||
|
let center = viewport.transform_point2(DVec2::ZERO);
|
||||||
|
// Show a small circle if the mouse is near the repeat center
|
||||||
|
if center.distance(input.mouse.position) < 5. {
|
||||||
|
overlay_context.circle(center, 3., None, None);
|
||||||
|
}
|
||||||
|
overlay_context.outline_vector(&vector, viewport);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cleanup(tool_data: &mut OperationToolData) {
|
||||||
|
// Clear stored drag state at the end of the operation
|
||||||
|
tool_data.circular_operation_data.layers_dragging.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
pub mod circular_repeat;
|
||||||
@@ -1,7 +1,13 @@
|
|||||||
use super::tool_prelude::*;
|
use super::tool_prelude::*;
|
||||||
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
|
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
|
||||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
use crate::messages::tool::common_functionality::operations::circular_repeat::{CircularRepeatOperation, CircularRepeatOperationData};
|
||||||
use crate::messages::tool::common_functionality::shapes::shape_utility::extract_circular_repeat_parameters;
|
|
||||||
|
#[derive(Debug, Clone, Copy, Eq, PartialEq, Default, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||||
|
pub enum OperationType {
|
||||||
|
#[default]
|
||||||
|
CircularRepeat = 0,
|
||||||
|
Repeat,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default, ExtractField)]
|
#[derive(Default, ExtractField)]
|
||||||
pub struct OperationTool {
|
pub struct OperationTool {
|
||||||
@@ -41,7 +47,7 @@ pub enum OperationToolMessage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||||
enum OperationToolFsmState {
|
pub enum OperationToolFsmState {
|
||||||
#[default]
|
#[default]
|
||||||
Ready,
|
Ready,
|
||||||
Drawing,
|
Drawing,
|
||||||
@@ -66,15 +72,15 @@ impl ToolMetadata for OperationTool {
|
|||||||
|
|
||||||
fn create_operation_type_option_widget(operation_type: OperationType) -> WidgetHolder {
|
fn create_operation_type_option_widget(operation_type: OperationType) -> WidgetHolder {
|
||||||
let entries = vec![vec![
|
let entries = vec![vec![
|
||||||
MenuListEntry::new("Repeat").label("Repeat").on_commit(move |_| {
|
MenuListEntry::new("Circular Repeat").label("Circular Repeat").on_commit(move |_| {
|
||||||
OperationToolMessage::UpdateOptions {
|
OperationToolMessage::UpdateOptions {
|
||||||
options: OperationOptionsUpdate::OperationType(OperationType::Repeat),
|
options: OperationOptionsUpdate::OperationType(OperationType::CircularRepeat),
|
||||||
}
|
}
|
||||||
.into()
|
.into()
|
||||||
}),
|
}),
|
||||||
MenuListEntry::new("Repeat").label("Circular Repeat").on_commit(move |_| {
|
MenuListEntry::new("Repeat").label("Repeat").on_commit(move |_| {
|
||||||
OperationToolMessage::UpdateOptions {
|
OperationToolMessage::UpdateOptions {
|
||||||
options: OperationOptionsUpdate::OperationType(OperationType::CircularRepeat),
|
options: OperationOptionsUpdate::OperationType(OperationType::Repeat),
|
||||||
}
|
}
|
||||||
.into()
|
.into()
|
||||||
}),
|
}),
|
||||||
@@ -138,16 +144,14 @@ impl ToolTransition for OperationTool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
struct OperationToolData {
|
pub struct OperationToolData {
|
||||||
drag_start: DVec2,
|
pub drag_start: DVec2,
|
||||||
clicked_layer_radius: (LayerNodeIdentifier, f64),
|
pub circular_operation_data: CircularRepeatOperationData,
|
||||||
layers_dragging: Vec<(LayerNodeIdentifier, f64)>,
|
|
||||||
initial_center: DVec2,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OperationToolData {
|
impl OperationToolData {
|
||||||
fn cleanup(&mut self) {
|
fn cleanup(&mut self) {
|
||||||
self.layers_dragging.clear();
|
CircularRepeatOperation::cleanup(self);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,7 +164,7 @@ impl Fsm for OperationToolFsmState {
|
|||||||
event: ToolMessage,
|
event: ToolMessage,
|
||||||
tool_data: &mut Self::ToolData,
|
tool_data: &mut Self::ToolData,
|
||||||
tool_action_data: &mut ToolActionMessageContext,
|
tool_action_data: &mut ToolActionMessageContext,
|
||||||
_tool_options: &Self::ToolOptions,
|
tool_options: &Self::ToolOptions,
|
||||||
responses: &mut VecDeque<Message>,
|
responses: &mut VecDeque<Message>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let ToolActionMessageContext { document, input, .. } = tool_action_data;
|
let ToolActionMessageContext { document, input, .. } = tool_action_data;
|
||||||
@@ -168,78 +172,20 @@ impl Fsm for OperationToolFsmState {
|
|||||||
let ToolMessage::Operation(event) = event else { return self };
|
let ToolMessage::Operation(event) = event else { return self };
|
||||||
match (self, event) {
|
match (self, event) {
|
||||||
(_, OperationToolMessage::Overlays { context: mut overlay_context }) => {
|
(_, OperationToolMessage::Overlays { context: mut overlay_context }) => {
|
||||||
match self {
|
match tool_options.operation_type {
|
||||||
OperationToolFsmState::Ready => {
|
OperationType::CircularRepeat => CircularRepeatOperation::overlays(&self, tool_data, document, input, &mut overlay_context),
|
||||||
for layer in document.network_interface.selected_nodes().selected_layers(document.metadata()) {
|
_ => {}
|
||||||
let Some(vector) = document.network_interface.compute_modified_vector(layer) else { continue };
|
|
||||||
let viewport = document.metadata().transform_to_viewport(layer);
|
|
||||||
let center = viewport.transform_point2(DVec2::ZERO);
|
|
||||||
if center.distance(input.mouse.position) < 5. {
|
|
||||||
overlay_context.circle(center, 3., None, None);
|
|
||||||
}
|
|
||||||
|
|
||||||
overlay_context.outline_vector(&vector, viewport);
|
|
||||||
}
|
|
||||||
if let Some(layer) = document.click(&input) {
|
|
||||||
let Some(vector) = document.network_interface.compute_modified_vector(layer) else { return self };
|
|
||||||
let viewport = document.metadata().transform_to_viewport(layer);
|
|
||||||
let center = viewport.transform_point2(DVec2::ZERO);
|
|
||||||
if center.distance(input.mouse.position) < 5. {
|
|
||||||
overlay_context.circle(center, 3., None, None);
|
|
||||||
}
|
|
||||||
|
|
||||||
overlay_context.outline_vector(&vector, viewport);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
for layer in tool_data.layers_dragging.iter().map(|(l, _)| l) {
|
|
||||||
let Some(vector) = document.network_interface.compute_modified_vector(*layer) else { continue };
|
|
||||||
let viewport = document.metadata().transform_to_viewport(*layer);
|
|
||||||
|
|
||||||
overlay_context.outline_vector(&vector, viewport);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
(OperationToolFsmState::Ready, OperationToolMessage::DragStart) => {
|
(OperationToolFsmState::Ready, OperationToolMessage::DragStart) => {
|
||||||
let selected_layers = document
|
match tool_options.operation_type {
|
||||||
.network_interface
|
OperationType::CircularRepeat => {
|
||||||
.selected_nodes()
|
CircularRepeatOperation::create_node(tool_data, document, responses, input);
|
||||||
.selected_layers(document.metadata())
|
}
|
||||||
.collect::<HashSet<LayerNodeIdentifier>>();
|
OperationType::Repeat => {}
|
||||||
let Some(clicked_layer) = document.click(&input) else { return self };
|
|
||||||
responses.add(DocumentMessage::StartTransaction);
|
|
||||||
let viewport = document.metadata().transform_to_viewport(clicked_layer);
|
|
||||||
let center = viewport.transform_point2(DVec2::ZERO);
|
|
||||||
|
|
||||||
if center.distance(input.mouse.position) > 5. {
|
|
||||||
return self;
|
|
||||||
};
|
|
||||||
|
|
||||||
if selected_layers.contains(&clicked_layer) {
|
|
||||||
// store all
|
|
||||||
tool_data.layers_dragging = selected_layers
|
|
||||||
.iter()
|
|
||||||
.map(|layer| {
|
|
||||||
let (_angle_offset, radius, _count) = extract_circular_repeat_parameters(Some(*layer), document).unwrap_or((0.0, 0.0, 6));
|
|
||||||
if *layer == clicked_layer {
|
|
||||||
tool_data.clicked_layer_radius = (*layer, radius)
|
|
||||||
}
|
|
||||||
(*layer, radius)
|
|
||||||
})
|
|
||||||
.collect::<Vec<(LayerNodeIdentifier, f64)>>();
|
|
||||||
} else {
|
|
||||||
// deselect all the layer and store the clicked layer for repeat and dragging
|
|
||||||
|
|
||||||
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![clicked_layer.to_node()] });
|
|
||||||
let (_angle_offset, radius, _count) = extract_circular_repeat_parameters(Some(clicked_layer), document).unwrap_or((0.0, 0.0, 6));
|
|
||||||
tool_data.clicked_layer_radius = (clicked_layer, radius);
|
|
||||||
tool_data.layers_dragging = vec![(clicked_layer, radius)];
|
|
||||||
}
|
}
|
||||||
tool_data.drag_start = input.mouse.position;
|
|
||||||
tool_data.initial_center = viewport.transform_point2(DVec2::ZERO);
|
|
||||||
|
|
||||||
OperationToolFsmState::Drawing
|
OperationToolFsmState::Drawing
|
||||||
}
|
}
|
||||||
@@ -257,31 +203,12 @@ impl Fsm for OperationToolFsmState {
|
|||||||
return self;
|
return self;
|
||||||
};
|
};
|
||||||
|
|
||||||
let (_clicked_layer, clicked_radius) = tool_data.clicked_layer_radius;
|
match tool_options.operation_type {
|
||||||
let viewport = document.metadata().transform_to_viewport(tool_data.clicked_layer_radius.0);
|
OperationType::CircularRepeat => {
|
||||||
let sign = (input.mouse.position - tool_data.initial_center).dot(viewport.transform_vector2(DVec2::Y)).signum();
|
CircularRepeatOperation::update_shape(tool_data, document, responses, input);
|
||||||
let delta = document
|
}
|
||||||
.metadata()
|
OperationType::Repeat => {}
|
||||||
.downstream_transform_to_viewport(tool_data.clicked_layer_radius.0)
|
|
||||||
.inverse()
|
|
||||||
.transform_vector2(input.mouse.position - tool_data.initial_center)
|
|
||||||
.length() * sign;
|
|
||||||
|
|
||||||
for (layer, initial_radius) in &tool_data.layers_dragging {
|
|
||||||
let new_radius = if initial_radius.signum() == clicked_radius.signum() {
|
|
||||||
*initial_radius + delta
|
|
||||||
} else {
|
|
||||||
*initial_radius + delta.signum() * -1. * delta.abs()
|
|
||||||
};
|
|
||||||
|
|
||||||
responses.add(GraphOperationMessage::CircularRepeatSet {
|
|
||||||
layer: *layer,
|
|
||||||
angle: 0.,
|
|
||||||
radius: new_radius,
|
|
||||||
count: 6,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
|
||||||
|
|
||||||
OperationToolFsmState::Drawing
|
OperationToolFsmState::Drawing
|
||||||
}
|
}
|
||||||
@@ -321,10 +248,3 @@ impl Fsm for OperationToolFsmState {
|
|||||||
responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Default });
|
responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Default });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, Default, serde::Serialize, serde::Deserialize, specta::Type)]
|
|
||||||
pub enum OperationType {
|
|
||||||
#[default]
|
|
||||||
CircularRepeat = 0,
|
|
||||||
Repeat = 1,
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user