From 3bc206843ad027136cb5766a0fa418c29d5c6bc0 Mon Sep 17 00:00:00 2001 From: 0SlowPoke0 Date: Sat, 19 Jul 2025 00:52:53 +0530 Subject: [PATCH 1/9] integrated grid shape in shape-tool --- .vscode/settings.json | 3 +- .../graph_modification_utils.rs | 4 + .../common_functionality/shapes/grid_shape.rs | 179 ++++++++++++++++++ .../tool/common_functionality/shapes/mod.rs | 1 + .../shapes/shape_utility.rs | 8 +- .../messages/tool/tool_messages/shape_tool.rs | 41 +++- node-graph/gcore/src/vector/misc.rs | 4 +- .../src/vector/vector_data/attributes.rs | 2 +- 8 files changed, 232 insertions(+), 10 deletions(-) create mode 100644 editor/src/messages/tool/common_functionality/shapes/grid_shape.rs diff --git a/.vscode/settings.json b/.vscode/settings.json index 7f48a1c56a..59b061584f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -52,5 +52,6 @@ "files.insertFinalNewline": true, "files.associations": { "*.graphite": "json" - } + }, + "rust-analyzer.checkOnSave": false } diff --git a/editor/src/messages/tool/common_functionality/graph_modification_utils.rs b/editor/src/messages/tool/common_functionality/graph_modification_utils.rs index 126e5b160c..e2d83a9550 100644 --- a/editor/src/messages/tool/common_functionality/graph_modification_utils.rs +++ b/editor/src/messages/tool/common_functionality/graph_modification_utils.rs @@ -356,6 +356,10 @@ pub fn get_text_id(layer: LayerNodeIdentifier, network_interface: &NodeNetworkIn NodeGraphLayer::new(layer, network_interface).upstream_node_id_from_name("Text") } +pub fn get_grid_id(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option { + NodeGraphLayer::new(layer, network_interface).upstream_node_id_from_name("Grid") +} + /// Gets properties from the Text node pub fn get_text(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<(&String, &Font, TypesettingConfig, bool)> { let inputs = NodeGraphLayer::new(layer, network_interface).find_node_inputs("Text")?; diff --git a/editor/src/messages/tool/common_functionality/shapes/grid_shape.rs b/editor/src/messages/tool/common_functionality/shapes/grid_shape.rs new file mode 100644 index 0000000000..4cab8c0839 --- /dev/null +++ b/editor/src/messages/tool/common_functionality/shapes/grid_shape.rs @@ -0,0 +1,179 @@ +use super::shape_utility::ShapeToolModifierKey; +use super::shape_utility::update_radius_sign; +use super::*; +use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn; +use crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type; +use crate::messages::portfolio::document::overlays::utility_types::OverlayContext; +use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; +use crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, NodeTemplate}; +use crate::messages::tool::common_functionality::gizmos::shape_gizmos::number_of_points_dial::NumberOfPointsDial; +use crate::messages::tool::common_functionality::gizmos::shape_gizmos::number_of_points_dial::NumberOfPointsDialState; +use crate::messages::tool::common_functionality::gizmos::shape_gizmos::point_radius_handle::PointRadiusHandle; +use crate::messages::tool::common_functionality::gizmos::shape_gizmos::point_radius_handle::PointRadiusHandleState; +use crate::messages::tool::common_functionality::graph_modification_utils; +use crate::messages::tool::common_functionality::shape_editor::ShapeState; +use crate::messages::tool::common_functionality::shapes::shape_utility::ShapeGizmoHandler; +use crate::messages::tool::common_functionality::shapes::shape_utility::polygon_outline; +use crate::messages::tool::tool_messages::tool_prelude::*; +use glam::DAffine2; +use graph_craft::document::NodeInput; +use graph_craft::document::value::TaggedValue; +use graphene_std::vector::misc::GridType; +use std::collections::VecDeque; + +// #[derive(Clone, Debug, Default)] +// pub struct PolygonGizmoHandler { +// number_of_points_dial: NumberOfPointsDial, +// point_radius_handle: PointRadiusHandle, +// } + +// impl ShapeGizmoHandler for PolygonGizmoHandler { +// fn is_any_gizmo_hovered(&self) -> bool { +// self.number_of_points_dial.is_hovering() || self.point_radius_handle.hovered() +// } + +// fn handle_state(&mut self, selected_star_layer: LayerNodeIdentifier, mouse_position: DVec2, document: &DocumentMessageHandler, responses: &mut VecDeque) { +// self.number_of_points_dial.handle_actions(selected_star_layer, mouse_position, document, responses); +// self.point_radius_handle.handle_actions(selected_star_layer, document, mouse_position, responses); +// } + +// fn handle_click(&mut self) { +// if self.number_of_points_dial.is_hovering() { +// self.number_of_points_dial.update_state(NumberOfPointsDialState::Dragging); +// return; +// } + +// if self.point_radius_handle.hovered() { +// self.point_radius_handle.update_state(PointRadiusHandleState::Dragging); +// } +// } + +// fn handle_update(&mut self, drag_start: DVec2, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque) { +// if self.number_of_points_dial.is_dragging() { +// self.number_of_points_dial.update_number_of_sides(document, input, responses, drag_start); +// } + +// if self.point_radius_handle.is_dragging_or_snapped() { +// self.point_radius_handle.update_inner_radius(document, input, responses, drag_start); +// } +// } + +// fn overlays( +// &self, +// document: &DocumentMessageHandler, +// selected_polygon_layer: Option, +// input: &InputPreprocessorMessageHandler, +// shape_editor: &mut &mut ShapeState, +// mouse_position: DVec2, +// overlay_context: &mut OverlayContext, +// ) { +// self.number_of_points_dial.overlays(document, selected_polygon_layer, shape_editor, mouse_position, overlay_context); +// self.point_radius_handle.overlays(selected_polygon_layer, document, input, mouse_position, overlay_context); + +// polygon_outline(selected_polygon_layer, document, overlay_context); +// } + +// fn dragging_overlays( +// &self, +// document: &DocumentMessageHandler, +// input: &InputPreprocessorMessageHandler, +// shape_editor: &mut &mut ShapeState, +// mouse_position: DVec2, +// overlay_context: &mut OverlayContext, +// ) { +// if self.number_of_points_dial.is_dragging() { +// self.number_of_points_dial.overlays(document, None, shape_editor, mouse_position, overlay_context); +// } + +// if self.point_radius_handle.is_dragging_or_snapped() { +// self.point_radius_handle.overlays(None, document, input, mouse_position, overlay_context); +// } +// } + +// fn cleanup(&mut self) { +// self.number_of_points_dial.cleanup(); +// self.point_radius_handle.cleanup(); +// } +// } + +#[derive(Default)] +pub struct Grid; + +impl Grid { + pub fn create_node(grid_type: GridType) -> NodeTemplate { + let node_type = resolve_document_node_type("Grid").expect("Grid can't be found"); + node_type.node_template_input_override([ + None, + Some(NodeInput::value(TaggedValue::GridType(grid_type), false)), + Some(NodeInput::value(TaggedValue::DVec2(DVec2::ZERO), false)), + ]) + } + + pub fn update_shape( + document: &DocumentMessageHandler, + ipp: &InputPreprocessorMessageHandler, + layer: LayerNodeIdentifier, + grid_type: GridType, + shape_tool_data: &mut ShapeToolData, + modifier: ShapeToolModifierKey, + responses: &mut VecDeque, + ) { + let [center, lock_ratio, _, _] = modifier; + + let start = shape_tool_data.data.viewport_drag_start(document); + let end = ipp.mouse.position; + + let mut dimensions = (start - end).abs(); + + let mut translation = shape_tool_data.data.viewport_drag_start(document); + let mut scale = (end - start).signum(); + + match grid_type { + GridType::Rectangular => { + if ipp.keyboard.key(center) && ipp.keyboard.key(lock_ratio) { + let max = dimensions.x.max(dimensions.y); + let distance_to_make_center = max; + translation = shape_tool_data.data.viewport_drag_start(document) - distance_to_make_center; + dimensions = 2. * DVec2::splat(max) / 9.; + scale = DVec2::ONE; + } else if ipp.keyboard.key(lock_ratio) { + let max = dimensions.x.max(dimensions.y); + dimensions = DVec2::splat(max) / 9. + } else if ipp.keyboard.key(center) { + let distance_to_make_center = dimensions; + translation = shape_tool_data.data.viewport_drag_start(document) - distance_to_make_center; + dimensions = 2. * dimensions / 9.; + scale = DVec2::ONE; + } else { + dimensions = dimensions / 9.; + }; + } + GridType::Isometric => { + if ipp.keyboard.key(center) { + let distance_to_make_center = DVec2::splat(dimensions.y); + translation = shape_tool_data.data.viewport_drag_start(document) - distance_to_make_center; + dimensions = 2. * DVec2::splat(dimensions.y) / 9.; + scale = DVec2::ONE; + } else { + dimensions = DVec2::splat(dimensions.y) / 9.; + }; + } + } + + let Some(node_id) = graph_modification_utils::get_grid_id(layer, &document.network_interface) else { + return; + }; + + responses.add(NodeGraphMessage::SetInput { + input_connector: InputConnector::node(node_id, 2), + input: NodeInput::value(TaggedValue::DVec2(dimensions), false), + }); + + responses.add(GraphOperationMessage::TransformSet { + layer, + transform: DAffine2::from_scale_angle_translation(scale, 0., translation), + transform_in: TransformIn::Viewport, + skip_rerender: false, + }); + } +} diff --git a/editor/src/messages/tool/common_functionality/shapes/mod.rs b/editor/src/messages/tool/common_functionality/shapes/mod.rs index 44f40b5982..e22fce11ed 100644 --- a/editor/src/messages/tool/common_functionality/shapes/mod.rs +++ b/editor/src/messages/tool/common_functionality/shapes/mod.rs @@ -1,4 +1,5 @@ pub mod ellipse_shape; +pub mod grid_shape; pub mod line_shape; pub mod polygon_shape; pub mod rectangle_shape; diff --git a/editor/src/messages/tool/common_functionality/shapes/shape_utility.rs b/editor/src/messages/tool/common_functionality/shapes/shape_utility.rs index 955984150b..948cc7cbe7 100644 --- a/editor/src/messages/tool/common_functionality/shapes/shape_utility.rs +++ b/editor/src/messages/tool/common_functionality/shapes/shape_utility.rs @@ -24,9 +24,10 @@ pub enum ShapeType { #[default] Polygon = 0, Star = 1, - Rectangle = 2, - Ellipse = 3, - Line = 4, + Grid = 2, + Rectangle = 3, + Ellipse = 4, + Line = 5, } impl ShapeType { @@ -37,6 +38,7 @@ impl ShapeType { Self::Rectangle => "Rectangle", Self::Ellipse => "Ellipse", Self::Line => "Line", + Self::Grid => "Grid", }) .into() } diff --git a/editor/src/messages/tool/tool_messages/shape_tool.rs b/editor/src/messages/tool/tool_messages/shape_tool.rs index 0f43adf286..689954786b 100644 --- a/editor/src/messages/tool/tool_messages/shape_tool.rs +++ b/editor/src/messages/tool/tool_messages/shape_tool.rs @@ -10,6 +10,7 @@ use crate::messages::tool::common_functionality::gizmos::gizmo_manager::GizmoMan use crate::messages::tool::common_functionality::graph_modification_utils; use crate::messages::tool::common_functionality::graph_modification_utils::NodeGraphLayer; use crate::messages::tool::common_functionality::resize::Resize; +use crate::messages::tool::common_functionality::shapes::grid_shape::Grid; use crate::messages::tool::common_functionality::shapes::line_shape::{LineToolData, clicked_on_line_endpoints}; use crate::messages::tool::common_functionality::shapes::polygon_shape::Polygon; use crate::messages::tool::common_functionality::shapes::shape_utility::{ShapeToolModifierKey, ShapeType, anchor_overlays, transform_cage_overlays}; @@ -22,7 +23,7 @@ use graph_craft::document::value::TaggedValue; use graph_craft::document::{NodeId, NodeInput}; use graphene_std::Color; use graphene_std::renderer::Quad; -use graphene_std::vector::misc::ArcType; +use graphene_std::vector::misc::{ArcType, GridType}; use std::vec; #[derive(Default)] @@ -39,6 +40,7 @@ pub struct ShapeToolOptions { vertices: u32, shape_type: ShapeType, arc_type: ArcType, + grid_type: GridType, } impl Default for ShapeToolOptions { @@ -50,6 +52,7 @@ impl Default for ShapeToolOptions { vertices: 5, shape_type: ShapeType::Polygon, arc_type: ArcType::Open, + grid_type: GridType::Rectangular, } } } @@ -65,6 +68,7 @@ pub enum ShapeOptionsUpdate { Vertices(u32), ShapeType(ShapeType), ArcType(ArcType), + GridType(GridType), } #[impl_message(Message, ToolMessage, Shape)] @@ -109,6 +113,9 @@ fn create_shape_option_widget(shape_type: ShapeType) -> WidgetHolder { MenuListEntry::new("Star") .label("Star") .on_commit(move |_| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::ShapeType(ShapeType::Star)).into()), + MenuListEntry::new("Grid") + .label("Grid") + .on_commit(move |_| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::ShapeType(ShapeType::Grid)).into()), ]]; DropdownInput::new(entries).selected_index(Some(shape_type as u32)).widget_holder() } @@ -123,6 +130,18 @@ fn create_weight_widget(line_weight: f64) -> WidgetHolder { .widget_holder() } +fn create_grid_type_widget(grid_type: GridType) -> WidgetHolder { + let entries = vec![ + RadioEntryData::new("Rectangular") + .label("Rectangular") + .on_update(move |_| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::GridType(GridType::Rectangular)).into()), + RadioEntryData::new("Isometric") + .label("Isometric") + .on_update(move |_| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::GridType(GridType::Isometric)).into()), + ]; + RadioInput::new(entries).selected_index(Some(grid_type as u32)).widget_holder() +} + impl LayoutHolder for ShapeTool { fn layout(&self) -> Layout { let mut widgets = vec![]; @@ -137,6 +156,11 @@ impl LayoutHolder for ShapeTool { } } + if self.options.shape_type == ShapeType::Grid { + widgets.push(create_grid_type_widget(self.options.grid_type)); + widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder()); + } + if self.options.shape_type != ShapeType::Line { widgets.append(&mut self.options.fill.create_widgets( "Fill", @@ -203,6 +227,9 @@ impl<'a> MessageHandler> for Shap ShapeOptionsUpdate::ArcType(arc_type) => { self.options.arc_type = arc_type; } + ShapeOptionsUpdate::GridType(grid_type) => { + self.options.grid_type = grid_type; + } } self.fsm_state.update_hints(responses); @@ -578,7 +605,7 @@ impl Fsm for ShapeToolFsmState { }; match tool_data.current_shape { - ShapeType::Polygon | ShapeType::Star | ShapeType::Ellipse | ShapeType::Rectangle => tool_data.data.start(document, input), + ShapeType::Polygon | ShapeType::Star | ShapeType::Ellipse | ShapeType::Rectangle | ShapeType::Grid => tool_data.data.start(document, input), ShapeType::Line => { let point = SnapCandidatePoint::handle(document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position)); let snapped = tool_data.data.snap_manager.free_snap(&SnapData::new(document, input), &point, SnapTypeConfiguration::default()); @@ -594,6 +621,7 @@ impl Fsm for ShapeToolFsmState { ShapeType::Rectangle => Rectangle::create_node(), ShapeType::Ellipse => Ellipse::create_node(), ShapeType::Line => Line::create_node(document, tool_data.data.drag_start), + ShapeType::Grid => Grid::create_node(tool_options.grid_type), }; let nodes = vec![(NodeId(0), node)]; @@ -602,7 +630,7 @@ impl Fsm for ShapeToolFsmState { responses.add(Message::StartBuffer); match tool_data.current_shape { - ShapeType::Ellipse | ShapeType::Rectangle | ShapeType::Polygon | ShapeType::Star => { + ShapeType::Ellipse | ShapeType::Rectangle | ShapeType::Polygon | ShapeType::Star | ShapeType::Grid => { responses.add(GraphOperationMessage::TransformSet { layer, transform: DAffine2::from_scale_angle_translation(DVec2::ONE, 0., input.mouse.position), @@ -635,6 +663,7 @@ impl Fsm for ShapeToolFsmState { ShapeType::Line => Line::update_shape(document, input, layer, tool_data, modifier, responses), ShapeType::Polygon => Polygon::update_shape(document, input, layer, tool_data, modifier, responses), ShapeType::Star => Star::update_shape(document, input, layer, tool_data, modifier, responses), + ShapeType::Grid => Grid::update_shape(document, input, layer, tool_options.grid_type, tool_data, modifier, responses), } // Auto-panning @@ -853,6 +882,11 @@ impl Fsm for ShapeToolFsmState { HintInfo::keys([Key::Shift], "Constrain Square").prepend_plus(), HintInfo::keys([Key::Alt], "From Center").prepend_plus(), ])], + ShapeType::Grid => vec![HintGroup(vec![ + HintInfo::mouse(MouseMotion::LmbDrag, "Draw Grid"), + HintInfo::keys([Key::Shift], "Constrain Grid").prepend_plus(), + HintInfo::keys([Key::Alt], "From Center").prepend_plus(), + ])], }; HintData(hint_groups) } @@ -862,6 +896,7 @@ impl Fsm for ShapeToolFsmState { ShapeType::Polygon | ShapeType::Star => HintGroup(vec![HintInfo::keys([Key::Shift], "Constrain Regular"), HintInfo::keys([Key::Alt], "From Center")]), ShapeType::Rectangle => HintGroup(vec![HintInfo::keys([Key::Shift], "Constrain Square"), HintInfo::keys([Key::Alt], "From Center")]), ShapeType::Ellipse => HintGroup(vec![HintInfo::keys([Key::Shift], "Constrain Circular"), HintInfo::keys([Key::Alt], "From Center")]), + ShapeType::Grid => HintGroup(vec![HintInfo::keys([Key::Shift], "Constrain Grid"), HintInfo::keys([Key::Alt], "From Center")]), ShapeType::Line => HintGroup(vec![ HintInfo::keys([Key::Shift], "15° Increments"), HintInfo::keys([Key::Alt], "From Center"), diff --git a/node-graph/gcore/src/vector/misc.rs b/node-graph/gcore/src/vector/misc.rs index 64e4f012e9..79890ce2d2 100644 --- a/node-graph/gcore/src/vector/misc.rs +++ b/node-graph/gcore/src/vector/misc.rs @@ -56,8 +56,8 @@ impl AsI64 for f64 { #[widget(Radio)] pub enum GridType { #[default] - Rectangular, - Isometric, + Rectangular = 0, + Isometric = 1, } #[repr(C)] diff --git a/node-graph/gcore/src/vector/vector_data/attributes.rs b/node-graph/gcore/src/vector/vector_data/attributes.rs index ea4460c78c..c21e978e84 100644 --- a/node-graph/gcore/src/vector/vector_data/attributes.rs +++ b/node-graph/gcore/src/vector/vector_data/attributes.rs @@ -127,7 +127,7 @@ impl PointDomain { } pub fn push(&mut self, id: PointId, position: DVec2) { - debug_assert!(!self.id.contains(&id)); + // debug_assert!(!self.id.contains(&id)); self.id.push(id); self.position.push(position); } From c66b4fcb706ceeb2e79fa217da100a6a53fb83de Mon Sep 17 00:00:00 2001 From: 0SlowPoke0 Date: Thu, 24 Jul 2025 03:30:19 +0530 Subject: [PATCH 2/9] add overlays,detection,transform for gizmo --- editor/src/consts.rs | 19 + .../gizmos/gizmo_manager.rs | 15 + .../shape_gizmos/grid_row_columns_gizmo.rs | 475 ++++++++++++++++++ .../gizmos/shape_gizmos/mod.rs | 1 + .../common_functionality/shapes/grid_shape.rs | 136 +++-- .../shapes/shape_utility.rs | 21 +- .../messages/tool/tool_messages/shape_tool.rs | 2 +- node-graph/gcore/src/vector/misc.rs | 7 + 8 files changed, 594 insertions(+), 82 deletions(-) create mode 100644 editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/grid_row_columns_gizmo.rs diff --git a/editor/src/consts.rs b/editor/src/consts.rs index 7c55c05a68..8521914abc 100644 --- a/editor/src/consts.rs +++ b/editor/src/consts.rs @@ -127,6 +127,7 @@ pub const POINT_RADIUS_HANDLE_SEGMENT_THRESHOLD: f64 = 7.9; pub const NUMBER_OF_POINTS_DIAL_SPOKE_EXTENSION: f64 = 1.2; pub const NUMBER_OF_POINTS_DIAL_SPOKE_LENGTH: f64 = 10.; pub const GIZMO_HIDE_THRESHOLD: f64 = 20.; +pub const GRID_ROW_COLUMN_GIZMO_OFFSET: f64 = 10.; // SCROLLBARS pub const SCROLLBAR_SPACING: f64 = 0.1; @@ -153,3 +154,21 @@ pub const AUTO_SAVE_TIMEOUT_SECONDS: u64 = 15; // INPUT pub const DOUBLE_CLICK_MILLISECONDS: u64 = 500; + +// GRID INPUT INDICES + +// grid_type: GridType, +// #[unit(" px")] +// #[hard_min(0.)] +// #[default(10)] +// #[implementations(f64, DVec2)] +// spacing: T, +// #[default(10)] columns: u32, +// #[default(10)] rows: u32, +// #[default(30., 30.)] angles: DVec2, + +pub const GRID_TYPE_INDEX: usize = 1; +pub const GRID_SPACING_INDEX: usize = 2; +pub const GRID_COLUMNS_INDEX: usize = 3; +pub const GRID_ROW_INDEX: usize = 4; +pub const GRID_ANGLE_INDEX: usize = 5; diff --git a/editor/src/messages/tool/common_functionality/gizmos/gizmo_manager.rs b/editor/src/messages/tool/common_functionality/gizmos/gizmo_manager.rs index 703c85e14d..e4fc9f54a1 100644 --- a/editor/src/messages/tool/common_functionality/gizmos/gizmo_manager.rs +++ b/editor/src/messages/tool/common_functionality/gizmos/gizmo_manager.rs @@ -4,6 +4,7 @@ use crate::messages::portfolio::document::utility_types::document_metadata::Laye use crate::messages::prelude::{DocumentMessageHandler, InputPreprocessorMessageHandler}; use crate::messages::tool::common_functionality::graph_modification_utils; use crate::messages::tool::common_functionality::shape_editor::ShapeState; +use crate::messages::tool::common_functionality::shapes::grid_shape::GridGizmoHandler; use crate::messages::tool::common_functionality::shapes::polygon_shape::PolygonGizmoHandler; use crate::messages::tool::common_functionality::shapes::shape_utility::ShapeGizmoHandler; use crate::messages::tool::common_functionality::shapes::star_shape::StarGizmoHandler; @@ -23,6 +24,7 @@ pub enum ShapeGizmoHandlers { None, Star(StarGizmoHandler), Polygon(PolygonGizmoHandler), + Grid(GridGizmoHandler), } impl ShapeGizmoHandlers { @@ -32,6 +34,7 @@ impl ShapeGizmoHandlers { match self { Self::Star(_) => "star", Self::Polygon(_) => "polygon", + Self::Grid(_) => "grid", Self::None => "none", } } @@ -41,6 +44,7 @@ impl ShapeGizmoHandlers { match self { Self::Star(h) => h.handle_state(layer, mouse_position, document, responses), Self::Polygon(h) => h.handle_state(layer, mouse_position, document, responses), + Self::Grid(h) => h.handle_state(layer, mouse_position, document, responses), Self::None => {} } } @@ -50,6 +54,7 @@ impl ShapeGizmoHandlers { match self { Self::Star(h) => h.is_any_gizmo_hovered(), Self::Polygon(h) => h.is_any_gizmo_hovered(), + Self::Grid(h) => h.is_any_gizmo_hovered(), Self::None => false, } } @@ -59,6 +64,7 @@ impl ShapeGizmoHandlers { match self { Self::Star(h) => h.handle_click(), Self::Polygon(h) => h.handle_click(), + Self::Grid(h) => h.handle_click(), Self::None => {} } } @@ -68,6 +74,7 @@ impl ShapeGizmoHandlers { match self { Self::Star(h) => h.handle_update(drag_start, document, input, responses), Self::Polygon(h) => h.handle_update(drag_start, document, input, responses), + Self::Grid(h) => h.handle_update(drag_start, document, input, responses), Self::None => {} } } @@ -77,6 +84,7 @@ impl ShapeGizmoHandlers { match self { Self::Star(h) => h.cleanup(), Self::Polygon(h) => h.cleanup(), + Self::Grid(h) => h.cleanup(), Self::None => {} } } @@ -94,6 +102,7 @@ impl ShapeGizmoHandlers { match self { Self::Star(h) => h.overlays(document, layer, input, shape_editor, mouse_position, overlay_context), Self::Polygon(h) => h.overlays(document, layer, input, shape_editor, mouse_position, overlay_context), + Self::Grid(h) => h.overlays(document, layer, input, shape_editor, mouse_position, overlay_context), Self::None => {} } } @@ -110,6 +119,7 @@ impl ShapeGizmoHandlers { match self { Self::Star(h) => h.dragging_overlays(document, input, shape_editor, mouse_position, overlay_context), Self::Polygon(h) => h.dragging_overlays(document, input, shape_editor, mouse_position, overlay_context), + Self::Grid(h) => h.dragging_overlays(document, input, shape_editor, mouse_position, overlay_context), Self::None => {} } } @@ -147,6 +157,11 @@ impl GizmoManager { return Some(ShapeGizmoHandlers::Polygon(PolygonGizmoHandler::default())); } + // Grid + if graph_modification_utils::get_grid_id(layer, &document.network_interface).is_some() { + return Some(ShapeGizmoHandlers::Grid(GridGizmoHandler::default())); + } + None } diff --git a/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/grid_row_columns_gizmo.rs b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/grid_row_columns_gizmo.rs new file mode 100644 index 0000000000..5efd6438d3 --- /dev/null +++ b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/grid_row_columns_gizmo.rs @@ -0,0 +1,475 @@ +use crate::consts::{GRID_COLUMNS_INDEX, GRID_ROW_COLUMN_GIZMO_OFFSET, GRID_ROW_INDEX}; +use crate::messages::frontend::utility_types::MouseCursorIcon; +use crate::messages::message::Message; +use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn; +use crate::messages::portfolio::document::overlays::utility_types::OverlayContext; +use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; +use crate::messages::portfolio::document::utility_types::network_interface::InputConnector; +use crate::messages::prelude::{DocumentMessageHandler, FrontendMessage, InputPreprocessorMessageHandler, NodeGraphMessage}; +use crate::messages::prelude::{GraphOperationMessage, Responses}; +use crate::messages::tool::common_functionality::graph_modification_utils; +use crate::messages::tool::common_functionality::shape_editor::ShapeState; +use crate::messages::tool::common_functionality::shapes::shape_utility::extract_grid_parameters; +use glam::{DAffine2, DVec2}; +use graph_craft::document::NodeInput; +use graph_craft::document::value::TaggedValue; +use graphene_std::num_traits::Zero; +use graphene_std::vector::misc::{GridType, dvec2_to_point, get_line_endpoints}; +use kurbo::{Line, ParamCurveNearest}; +use std::collections::VecDeque; + +#[derive(Clone, Debug, Default, PartialEq)] +pub enum RowColumnGizmoState { + #[default] + Inactive, + Hover, + Dragging, +} + +#[derive(Clone, Debug, Default, PartialEq)] +pub enum RowColumnGizmoType { + #[default] + None, + Top, + Down, + Left, + Right, +} + +impl RowColumnGizmoType { + pub fn get_line_points(&self, grid_type: GridType, columns: u32, rows: u32, spacing: DVec2, angles: DVec2) -> (DVec2, DVec2) { + match grid_type { + GridType::Rectangular => match self { + Self::Top => get_rectangle_top_line_points(columns, rows, spacing), + Self::Right => get_rectangle_right_line_points(columns, rows, spacing), + Self::Down => get_rectangle_bottom_line_points(columns, rows, spacing), + Self::Left => get_rectangle_left_line_points(columns, rows, spacing), + Self::None => panic!("RowColumnGizmoType::None does not have line points"), + }, + GridType::Isometric => match self { + Self::Top => calculate_isometric_top_line_points(columns, spacing, angles), + Self::Right => calculate_isometric_right_line_points(columns, rows, spacing, angles), + Self::Down => calculate_isometric_bottom_line_points(columns, rows, spacing, angles), + Self::Left => calculate_isometric_left_line_points(rows, spacing, angles), + Self::None => panic!("RowColumnGizmoType::None does not have line points"), + }, + } + } + fn line(&self, grid_type: GridType, columns: u32, rows: u32, spacing: DVec2, angles: DVec2, viewport: DAffine2) -> Line { + let (p0, p1) = self.get_line_points(grid_type, columns, rows, spacing, angles); + convert_to_gizmo_line(viewport.transform_point2(p0), viewport.transform_point2(p1)) + } + fn opposite(&self, grid_type: GridType, columns: u32, rows: u32, spacing: DVec2, angles: DVec2, viewport: DAffine2) -> Line { + let opposite_gizmo_type = match self { + Self::Top => Self::Down, + Self::Right => Self::Left, + Self::Down => Self::Top, + Self::Left => Self::Right, + Self::None => panic!("RowColumnGizmoType::None does not have opposite"), + }; + + opposite_gizmo_type.line(grid_type, columns, rows, spacing, angles, viewport) + } + fn direction(&self, grid_type: GridType, columns: u32, rows: u32, spacing: DVec2, angles: DVec2, viewport: DAffine2) -> DVec2 { + match self { + RowColumnGizmoType::Top => { + if grid_type == GridType::Rectangular { + calculate_rectangle_top_direction(columns, rows, spacing, viewport) + } else { + -calculate_isometric_top_direction(angles, spacing, Some(viewport)) + } + } + RowColumnGizmoType::Down => { + if grid_type == GridType::Rectangular { + -calculate_rectangle_top_direction(columns, rows, spacing, viewport) + } else { + calculate_isometric_top_direction(angles, spacing, Some(viewport)) + } + } + RowColumnGizmoType::Right => { + if grid_type == GridType::Rectangular { + calculate_rectangle_side_direction(columns, rows, spacing, viewport) + } else { + calculate_isometric_side_direction(angles, spacing, Some(viewport)) + } + } + RowColumnGizmoType::Left => { + if grid_type == GridType::Rectangular { + -calculate_rectangle_side_direction(columns, rows, spacing, viewport) + } else { + -calculate_isometric_side_direction(angles, spacing, Some(viewport)) + } + } + RowColumnGizmoType::None => panic!("RowColumnGizmoType::None does not have a line"), + } + } + + fn initial_dimension(&self, rows: u32, columns: u32) -> u32 { + match self { + RowColumnGizmoType::Top | RowColumnGizmoType::Down => rows, + RowColumnGizmoType::Right | RowColumnGizmoType::Left => columns, + RowColumnGizmoType::None => panic!("RowColumnGizmoType::None does not have a mouse_icon"), + } + } + + fn spacing(&self, spacing: DVec2) -> f64 { + match self { + RowColumnGizmoType::Top | RowColumnGizmoType::Down => spacing.y, + RowColumnGizmoType::Right | RowColumnGizmoType::Left => spacing.x, + RowColumnGizmoType::None => panic!("RowColumnGizmoType::None does not have a mouse_icon"), + } + } + + fn index(&self) -> usize { + match self { + RowColumnGizmoType::Top | RowColumnGizmoType::Down => GRID_ROW_INDEX, + RowColumnGizmoType::Right | RowColumnGizmoType::Left => GRID_COLUMNS_INDEX, + RowColumnGizmoType::None => panic!("RowColumnGizmoType::None does not have a mouse_icon"), + } + } + + fn mouse_icon(&self) -> MouseCursorIcon { + match self { + RowColumnGizmoType::Top | RowColumnGizmoType::Down => MouseCursorIcon::NSResize, + RowColumnGizmoType::Right | RowColumnGizmoType::Left => MouseCursorIcon::EWResize, + RowColumnGizmoType::None => panic!("RowColumnGizmoType::None does not have a mouse_icon"), + } + } + + pub fn all() -> [Self; 4] { + [Self::Top, Self::Right, Self::Down, Self::Left] + } +} + +#[derive(Clone, Debug, Default)] +pub struct RowColumnGizmo { + pub layer: Option, + pub gizmo_type: RowColumnGizmoType, + initial_rows: u32, + initial_columns: u32, + spacing: DVec2, + gizmo_state: RowColumnGizmoState, +} + +impl RowColumnGizmo { + pub fn cleanup(&mut self) { + self.layer = None; + } + + pub fn update_state(&mut self, state: RowColumnGizmoState) { + self.gizmo_state = state; + } + + pub fn is_hovered(&self) -> bool { + self.gizmo_state == RowColumnGizmoState::Hover + } + + pub fn is_dragging(&self) -> bool { + self.gizmo_state == RowColumnGizmoState::Dragging + } + + fn initial_dimension(&self) -> u32 { + match &self.gizmo_type { + RowColumnGizmoType::Top | RowColumnGizmoType::Down => self.initial_rows, + RowColumnGizmoType::Right | RowColumnGizmoType::Left => self.initial_columns, + RowColumnGizmoType::None => panic!("RowColumnGizmoType::None does not have a mouse_icon"), + } + } + + pub fn handle_actions(&mut self, layer: LayerNodeIdentifier, mouse_position: DVec2, document: &DocumentMessageHandler, responses: &mut VecDeque) { + let Some((grid_type, spacing, columns, rows, angles)) = extract_grid_parameters(layer, document) else { + return; + }; + let viewport = document.metadata().transform_to_viewport(layer); + if let Some(gizmo_type) = check_if_over_gizmo(grid_type, columns, rows, spacing, angles, mouse_position, viewport) { + self.layer = Some(layer); + self.gizmo_type = gizmo_type; + self.initial_rows = rows; + self.initial_columns = columns; + self.spacing = spacing; + self.update_state(RowColumnGizmoState::Hover); + responses.add(FrontendMessage::UpdateMouseCursor { cursor: self.gizmo_type.mouse_icon() }); + } + } + + pub fn overlays(&self, document: &DocumentMessageHandler, layer: Option, _shape_editor: &mut &mut ShapeState, _mouse_position: DVec2, overlay_context: &mut OverlayContext) { + let Some(layer) = layer.or(self.layer) else { return }; + let Some((grid_type, spacing, columns, rows, angles)) = extract_grid_parameters(layer, document) else { + return; + }; + let viewport = document.metadata().transform_to_viewport(layer); + + if !matches!(self.gizmo_state, RowColumnGizmoState::Inactive) { + let (p0, p1) = self.gizmo_type.get_line_points(grid_type, columns, rows, spacing, angles); + let line = convert_to_gizmo_line(viewport.transform_point2(p0), viewport.transform_point2(p1)); + let (p0, p1) = get_line_endpoints(line); + overlay_context.dashed_line(p0, p1, None, None, Some(5.), Some(5.), Some(0.5)); + + if matches!(self.gizmo_state, RowColumnGizmoState::Hover) { + let line = self.gizmo_type.opposite(grid_type, columns, rows, spacing, angles, viewport); + let (p0, p1) = get_line_endpoints(line); + overlay_context.dashed_line(p0, p1, None, None, Some(5.), Some(5.), Some(0.5)); + } + } + } + + pub fn update(&self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque, drag_start: DVec2) { + let Some(layer) = self.layer else { return }; + let viewport = document.metadata().transform_to_viewport(layer); + + let Some((grid_type, spacing, columns, rows, angles)) = extract_grid_parameters(layer, document) else { + return; + }; + let direction = self.gizmo_type.direction(grid_type, columns, rows, spacing, angles, viewport); + let delta_vector = input.mouse.position - drag_start; + + let viewport_spacing = get_viewport_grid_spacing(grid_type, angles, self.spacing, viewport); + let delta = delta_vector.dot(direction); + + let dimensions_to_add = (delta / (self.gizmo_type.spacing(viewport_spacing))).floor() as i32; + let new_dimension = (self.initial_dimension() as i32 + dimensions_to_add).max(1) as u32; + + let Some(node_id) = graph_modification_utils::get_grid_id(layer, &document.network_interface) else { + return; + }; + + let dimensions_delta = new_dimension as i32 - self.gizmo_type.initial_dimension(rows, columns) as i32; + let transform = self.transform_grid(dimensions_delta, grid_type, columns, rows, viewport_spacing, angles, viewport); + + responses.add(NodeGraphMessage::SetInput { + input_connector: InputConnector::node(node_id, self.gizmo_type.index()), + input: NodeInput::value(TaggedValue::U32((self.initial_dimension() as i32 + dimensions_to_add).max(1) as u32), false), + }); + + responses.add(GraphOperationMessage::TransformChange { + layer, + transform: transform, + transform_in: TransformIn::Viewport, + skip_rerender: false, + }); + + responses.add(NodeGraphMessage::RunDocumentGraph); + } + + fn transform_grid(&self, dimensions_delta: i32, grid_type: GridType, columns: u32, rows: u32, spacing: DVec2, angles: DVec2, viewport: DAffine2) -> DAffine2 { + match self.gizmo_type { + RowColumnGizmoType::Top => { + let move_up_by = self.gizmo_type.direction(grid_type, columns, rows, spacing, angles, viewport) * dimensions_delta as f64 * spacing.y; + DAffine2::from_translation(move_up_by) + } + RowColumnGizmoType::Left => { + let move_left_by = self.gizmo_type.direction(grid_type, columns, rows, spacing, angles, viewport) * dimensions_delta as f64 * spacing.x; + DAffine2::from_translation(move_left_by) + } + RowColumnGizmoType::Down | RowColumnGizmoType::Right | RowColumnGizmoType::None => DAffine2::IDENTITY, + } + } +} + +fn check_if_over_gizmo(grid_type: GridType, columns: u32, rows: u32, spacing: DVec2, angles: DVec2, mouse_position: DVec2, viewport: DAffine2) -> Option { + let mouse_point = dvec2_to_point(mouse_position); + let accuracy = 1e-6; + let threshold = 20.; + + for gizmo_type in RowColumnGizmoType::all() { + let line = gizmo_type.line(grid_type, columns, rows, spacing, angles, viewport); + if line.nearest(mouse_point, accuracy).distance_sq < threshold { + return Some(gizmo_type); + } + } + + None +} + +fn convert_to_gizmo_line(p0: DVec2, p1: DVec2) -> Line { + Line { + p0: dvec2_to_point(p0), + p1: dvec2_to_point(p1), + } +} + +/// Get corners of the rectangular-grid. +/// Returns a tuple of (topleft,topright,bottomright,bottomleft) +fn get_corners(columns: u32, rows: u32, spacing: DVec2) -> (DVec2, DVec2, DVec2, DVec2) { + let (width, height) = (spacing.x, spacing.y); + + let x_distance = (columns - 1) as f64 * width; + let y_distance = (rows - 1) as f64 * height; + + let point0 = DVec2::ZERO; + let point1 = DVec2::new(x_distance, 0.); + let point2 = DVec2::new(x_distance, y_distance); + let point3 = DVec2::new(0., y_distance); + + (point0, point1, point2, point3) +} + +fn get_viewport_grid_spacing(grid_type: GridType, angles: DVec2, spacing: DVec2, viewport: DAffine2) -> DVec2 { + match grid_type { + GridType::Rectangular => { + let p0 = DVec2::ZERO; + let p1 = DVec2::new(spacing.x, 0.); + let p2 = DVec2::new(0., spacing.y); + + let viewport_spacing_x = (viewport.transform_point2(p0) - viewport.transform_point2(p1)).length(); + let viewport_spacing_y = (viewport.transform_point2(p0) - viewport.transform_point2(p2)).length(); + + DVec2::new(viewport_spacing_x, viewport_spacing_y) + } + GridType::Isometric => { + let p0 = calculate_isometric_point(0, 0, angles, spacing); + let p1 = calculate_isometric_point(1, 0, angles, spacing); + let p2 = calculate_isometric_point(0, 1, angles, spacing); + + let viewport_spacing_x = viewport.transform_point2(p0).x - viewport.transform_point2(p1).x; + let viewport_spacing_y = viewport.transform_point2(p0).y - viewport.transform_point2(p2).y; + + DVec2::new(viewport_spacing_x.abs(), viewport_spacing_y.abs()) + } + } +} + +fn get_rectangle_top_line_points(columns: u32, rows: u32, spacing: DVec2) -> (DVec2, DVec2) { + let (top_left, top_right, _, _) = get_corners(columns, rows, spacing); + + let offset = DVec2::new(spacing.x * 0.25, 0.); + let spacing_offset = DVec2::new(0., -GRID_ROW_COLUMN_GIZMO_OFFSET); + + (top_left + offset + spacing_offset, top_right - offset + spacing_offset) +} + +fn get_rectangle_bottom_line_points(columns: u32, rows: u32, spacing: DVec2) -> (DVec2, DVec2) { + let (_, _, bottom_right, bottom_left) = get_corners(columns, rows, spacing); + + let offset = DVec2::new(spacing.x * 0.25, 0.); + let spacing_offset = DVec2::new(0., GRID_ROW_COLUMN_GIZMO_OFFSET); + + (bottom_left + offset + spacing_offset, bottom_right - offset + spacing_offset) +} + +fn get_rectangle_right_line_points(columns: u32, rows: u32, spacing: DVec2) -> (DVec2, DVec2) { + let (_, top_right, bottom_right, _) = get_corners(columns, rows, spacing); + + let offset = DVec2::new(0., -spacing.y * 0.25); + let spacing_offset = DVec2::new(GRID_ROW_COLUMN_GIZMO_OFFSET, 0.); + + (top_right - offset + spacing_offset, bottom_right + offset + spacing_offset) +} + +fn get_rectangle_left_line_points(columns: u32, rows: u32, spacing: DVec2) -> (DVec2, DVec2) { + let (top_left, _, _, bottom_left) = get_corners(columns, rows, spacing); + let offset = DVec2::new(0., -spacing.y * 0.25); + + let spacing_offset = DVec2::new(GRID_ROW_COLUMN_GIZMO_OFFSET, 0.); + + (top_left - offset - spacing_offset, bottom_left + offset - spacing_offset) +} + +fn calculate_isometric_point(column: u32, row: u32, angles: DVec2, spacing: DVec2) -> DVec2 { + let tan_a = angles.x.to_radians().tan(); + let tan_b = angles.y.to_radians().tan(); + + let spacing = DVec2::new(spacing.y / (tan_a + tan_b), spacing.y); + + let a_angles_eaten = column.div_ceil(2) as f64; + let b_angles_eaten = (column / 2) as f64; + + let offset_y_fraction = b_angles_eaten * tan_b - a_angles_eaten * tan_a; + + DVec2::new(spacing.x * column as f64, spacing.y * row as f64 + offset_y_fraction * spacing.x) +} + +fn calculate_isometric_top_line_points(columns: u32, spacing: DVec2, angles: DVec2) -> (DVec2, DVec2) { + let top_left = calculate_isometric_point(0, 0, angles, spacing); + let top_right = calculate_isometric_point(columns - 1, 0, angles, spacing); + + let offset = DVec2::new(spacing.x * 0.25, 0.); + let isometric_spacing = calculate_isometric_offset(spacing, angles); + let isometric_offset = DVec2::new(0., isometric_spacing.y); + let end_isometric_offset = if columns % 2 == 0 { DVec2::ZERO } else { DVec2::new(0., isometric_spacing.y) }; + let spacing_offset = DVec2::new(0., -GRID_ROW_COLUMN_GIZMO_OFFSET); + + (top_left + offset + spacing_offset - isometric_offset, top_right - offset + spacing_offset - end_isometric_offset) +} + +fn calculate_isometric_bottom_line_points(columns: u32, rows: u32, spacing: DVec2, angles: DVec2) -> (DVec2, DVec2) { + let bottom_left = calculate_isometric_point(0, rows - 1, angles, spacing); + let bottom_right = calculate_isometric_point(columns - 1, rows - 1, angles, spacing); + + let offset = DVec2::new(spacing.x * 0.25, 0.); + let isometric_offset = if columns % 2 == 0 { + let offset = calculate_isometric_offset(spacing, angles); + DVec2::new(0., offset.y) + } else { + DVec2::ZERO + }; + let spacing_offset = DVec2::new(0., GRID_ROW_COLUMN_GIZMO_OFFSET); + + (bottom_left + offset + spacing_offset, bottom_right - offset + spacing_offset + isometric_offset) +} + +fn calculate_isometric_offset(spacing: DVec2, angles: DVec2) -> DVec2 { + let first_point = calculate_isometric_point(0, 0, angles, spacing); + let second_point = calculate_isometric_point(1, 0, angles, spacing); + + DVec2::new(first_point.x - second_point.x, first_point.y - second_point.y) +} + +fn calculate_isometric_right_line_points(columns: u32, rows: u32, spacing: DVec2, angles: DVec2) -> (DVec2, DVec2) { + let top_right = calculate_isometric_point(columns - 1, 0, angles, spacing); + let bottom_right = calculate_isometric_point(columns - 1, rows - 1, angles, spacing); + let side_direction = calculate_isometric_side_direction(angles, spacing, None); + + let offset = DVec2::new(0., -spacing.y * 0.25); + let spacing_offset = GRID_ROW_COLUMN_GIZMO_OFFSET * side_direction; + + (top_right - offset + spacing_offset, bottom_right + offset + spacing_offset) +} + +fn calculate_isometric_left_line_points(rows: u32, spacing: DVec2, angles: DVec2) -> (DVec2, DVec2) { + let top_left = calculate_isometric_point(0, 0, angles, spacing); + let bottom_left = calculate_isometric_point(0, rows - 1, angles, spacing); + let side_direction = calculate_isometric_side_direction(angles, spacing, None); + + let offset = DVec2::new(0., -spacing.y * 0.25); + let spacing_offset = GRID_ROW_COLUMN_GIZMO_OFFSET * side_direction; + + (top_left - offset - spacing_offset, bottom_left + offset - spacing_offset) +} + +fn calculate_rectangle_side_direction(columns: u32, rows: u32, spacing: DVec2, viewport: DAffine2) -> DVec2 { + let (left, right, _, _) = get_corners(columns, rows, spacing); + (viewport.transform_point2(right) - viewport.transform_point2(left)).try_normalize().unwrap_or(DVec2::ZERO) +} + +fn calculate_rectangle_top_direction(columns: u32, rows: u32, spacing: DVec2, viewport: DAffine2) -> DVec2 { + let (left, _, _, bottom_left) = get_corners(columns, rows, spacing); + + (viewport.transform_point2(left) - viewport.transform_point2(bottom_left)).try_normalize().unwrap_or(DVec2::ZERO) +} + +fn calculate_isometric_side_direction(angles: DVec2, spacing: DVec2, viewport: Option) -> DVec2 { + let first_point = calculate_isometric_point(0, 0, angles, spacing); + let first_row_last_column = calculate_isometric_point(2, 0, angles, spacing); + + if let Some(viewport) = viewport { + return (viewport.transform_point2(first_row_last_column) - viewport.transform_point2(first_point)) + .try_normalize() + .unwrap_or_default(); + } + + (first_row_last_column - first_point).try_normalize().unwrap_or_default() +} + +fn calculate_isometric_top_direction(angles: DVec2, spacing: DVec2, viewport: Option) -> DVec2 { + let first_point = calculate_isometric_point(0, 0, angles, spacing); + let first_row_last_column = calculate_isometric_point(0, 1, angles, spacing); + + if let Some(viewport) = viewport { + return (viewport.transform_point2(first_row_last_column) - viewport.transform_point2(first_point)) + .try_normalize() + .unwrap_or_default(); + } + + (first_point - first_row_last_column).try_normalize().unwrap_or_default() +} diff --git a/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/mod.rs b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/mod.rs index 2b88dddd5e..74a3f9ab34 100644 --- a/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/mod.rs +++ b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/mod.rs @@ -1,2 +1,3 @@ +pub mod grid_row_colums_gizmo; pub mod number_of_points_dial; pub mod point_radius_handle; diff --git a/editor/src/messages/tool/common_functionality/shapes/grid_shape.rs b/editor/src/messages/tool/common_functionality/shapes/grid_shape.rs index 4cab8c0839..992e873c59 100644 --- a/editor/src/messages/tool/common_functionality/shapes/grid_shape.rs +++ b/editor/src/messages/tool/common_functionality/shapes/grid_shape.rs @@ -1,19 +1,16 @@ use super::shape_utility::ShapeToolModifierKey; -use super::shape_utility::update_radius_sign; use super::*; use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn; use crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type; use crate::messages::portfolio::document::overlays::utility_types::OverlayContext; use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; use crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, NodeTemplate}; -use crate::messages::tool::common_functionality::gizmos::shape_gizmos::number_of_points_dial::NumberOfPointsDial; -use crate::messages::tool::common_functionality::gizmos::shape_gizmos::number_of_points_dial::NumberOfPointsDialState; -use crate::messages::tool::common_functionality::gizmos::shape_gizmos::point_radius_handle::PointRadiusHandle; -use crate::messages::tool::common_functionality::gizmos::shape_gizmos::point_radius_handle::PointRadiusHandleState; +use crate::messages::tool::common_functionality::gizmos::shape_gizmos::grid_row_columns_gizmo::RowColumnGizmo; +use crate::messages::tool::common_functionality::gizmos::shape_gizmos::grid_row_columns_gizmo::RowColumnGizmoState; + use crate::messages::tool::common_functionality::graph_modification_utils; use crate::messages::tool::common_functionality::shape_editor::ShapeState; use crate::messages::tool::common_functionality::shapes::shape_utility::ShapeGizmoHandler; -use crate::messages::tool::common_functionality::shapes::shape_utility::polygon_outline; use crate::messages::tool::tool_messages::tool_prelude::*; use glam::DAffine2; use graph_craft::document::NodeInput; @@ -21,80 +18,59 @@ use graph_craft::document::value::TaggedValue; use graphene_std::vector::misc::GridType; use std::collections::VecDeque; -// #[derive(Clone, Debug, Default)] -// pub struct PolygonGizmoHandler { -// number_of_points_dial: NumberOfPointsDial, -// point_radius_handle: PointRadiusHandle, -// } - -// impl ShapeGizmoHandler for PolygonGizmoHandler { -// fn is_any_gizmo_hovered(&self) -> bool { -// self.number_of_points_dial.is_hovering() || self.point_radius_handle.hovered() -// } - -// fn handle_state(&mut self, selected_star_layer: LayerNodeIdentifier, mouse_position: DVec2, document: &DocumentMessageHandler, responses: &mut VecDeque) { -// self.number_of_points_dial.handle_actions(selected_star_layer, mouse_position, document, responses); -// self.point_radius_handle.handle_actions(selected_star_layer, document, mouse_position, responses); -// } - -// fn handle_click(&mut self) { -// if self.number_of_points_dial.is_hovering() { -// self.number_of_points_dial.update_state(NumberOfPointsDialState::Dragging); -// return; -// } - -// if self.point_radius_handle.hovered() { -// self.point_radius_handle.update_state(PointRadiusHandleState::Dragging); -// } -// } - -// fn handle_update(&mut self, drag_start: DVec2, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque) { -// if self.number_of_points_dial.is_dragging() { -// self.number_of_points_dial.update_number_of_sides(document, input, responses, drag_start); -// } - -// if self.point_radius_handle.is_dragging_or_snapped() { -// self.point_radius_handle.update_inner_radius(document, input, responses, drag_start); -// } -// } - -// fn overlays( -// &self, -// document: &DocumentMessageHandler, -// selected_polygon_layer: Option, -// input: &InputPreprocessorMessageHandler, -// shape_editor: &mut &mut ShapeState, -// mouse_position: DVec2, -// overlay_context: &mut OverlayContext, -// ) { -// self.number_of_points_dial.overlays(document, selected_polygon_layer, shape_editor, mouse_position, overlay_context); -// self.point_radius_handle.overlays(selected_polygon_layer, document, input, mouse_position, overlay_context); - -// polygon_outline(selected_polygon_layer, document, overlay_context); -// } - -// fn dragging_overlays( -// &self, -// document: &DocumentMessageHandler, -// input: &InputPreprocessorMessageHandler, -// shape_editor: &mut &mut ShapeState, -// mouse_position: DVec2, -// overlay_context: &mut OverlayContext, -// ) { -// if self.number_of_points_dial.is_dragging() { -// self.number_of_points_dial.overlays(document, None, shape_editor, mouse_position, overlay_context); -// } - -// if self.point_radius_handle.is_dragging_or_snapped() { -// self.point_radius_handle.overlays(None, document, input, mouse_position, overlay_context); -// } -// } - -// fn cleanup(&mut self) { -// self.number_of_points_dial.cleanup(); -// self.point_radius_handle.cleanup(); -// } -// } +#[derive(Clone, Debug, Default)] +pub struct GridGizmoHandler { + row_column_gizmo: RowColumnGizmo, +} + +impl ShapeGizmoHandler for GridGizmoHandler { + fn is_any_gizmo_hovered(&self) -> bool { + self.row_column_gizmo.is_hovered() + } + + fn handle_state(&mut self, selected_grid_layer: LayerNodeIdentifier, mouse_position: DVec2, document: &DocumentMessageHandler, responses: &mut VecDeque) { + self.row_column_gizmo.handle_actions(selected_grid_layer, mouse_position, document, responses); + } + + fn handle_click(&mut self) { + if self.row_column_gizmo.is_hovered() { + self.row_column_gizmo.update_state(RowColumnGizmoState::Dragging); + } + } + + fn handle_update(&mut self, drag_start: DVec2, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque) { + if self.row_column_gizmo.is_dragging() { + self.row_column_gizmo.update(document, input, responses, drag_start); + } + } + + fn overlays( + &self, + document: &DocumentMessageHandler, + selected_grid_layer: Option, + input: &InputPreprocessorMessageHandler, + shape_editor: &mut &mut ShapeState, + mouse_position: DVec2, + overlay_context: &mut OverlayContext, + ) { + self.row_column_gizmo.overlays(document, selected_grid_layer, shape_editor, mouse_position, overlay_context); + } + + fn dragging_overlays( + &self, + document: &DocumentMessageHandler, + input: &InputPreprocessorMessageHandler, + shape_editor: &mut &mut ShapeState, + mouse_position: DVec2, + overlay_context: &mut OverlayContext, + ) { + if self.row_column_gizmo.is_dragging() { + self.row_column_gizmo.overlays(document, None, shape_editor, mouse_position, overlay_context); + } + } + + fn cleanup(&mut self) {} +} #[derive(Default)] pub struct Grid; diff --git a/editor/src/messages/tool/common_functionality/shapes/shape_utility.rs b/editor/src/messages/tool/common_functionality/shapes/shape_utility.rs index 948cc7cbe7..123040d4ab 100644 --- a/editor/src/messages/tool/common_functionality/shapes/shape_utility.rs +++ b/editor/src/messages/tool/common_functionality/shapes/shape_utility.rs @@ -1,4 +1,5 @@ use super::ShapeToolData; +use crate::consts::{GRID_ANGLE_INDEX, GRID_COLUMNS_INDEX, GRID_ROW_INDEX, GRID_SPACING_INDEX, GRID_TYPE_INDEX}; use crate::messages::message::Message; use crate::messages::portfolio::document::overlays::utility_types::OverlayContext; use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; @@ -14,7 +15,7 @@ use glam::{DAffine2, DMat2, DVec2}; use graph_craft::document::NodeInput; use graph_craft::document::value::TaggedValue; use graphene_std::vector::click_target::ClickTargetType; -use graphene_std::vector::misc::dvec2_to_point; +use graphene_std::vector::misc::{GridType, dvec2_to_point}; use kurbo::{BezPath, PathEl, Shape}; use std::collections::VecDeque; use std::f64::consts::{PI, TAU}; @@ -365,3 +366,21 @@ pub fn draw_snapping_ticks(snap_radii: &[f64], direction: DVec2, viewport: DAffi overlay_context.line(tick_position, tick_position - tick_direction * 5., None, Some(2.)); } } + +/// Extract the node input values of Grid. +/// Returns an option of (Grid-type, spacing,columns,rows,angles). +pub fn extract_grid_parameters(layer: LayerNodeIdentifier, document: &DocumentMessageHandler) -> Option<(GridType, DVec2, u32, u32, DVec2)> { + let node_inputs = NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs("Grid")?; + + let (Some(&TaggedValue::GridType(grid_type)), Some(&TaggedValue::DVec2(spacing)), Some(&TaggedValue::U32(columns)), Some(&TaggedValue::U32(rows)), Some(&TaggedValue::DVec2(angles))) = ( + node_inputs.get(GRID_TYPE_INDEX)?.as_value(), + node_inputs.get(GRID_SPACING_INDEX)?.as_value(), + node_inputs.get(GRID_COLUMNS_INDEX)?.as_value(), + node_inputs.get(GRID_ROW_INDEX)?.as_value(), + node_inputs.get(GRID_ANGLE_INDEX)?.as_value(), + ) else { + return None; + }; + + Some((grid_type, spacing, columns, rows, angles)) +} diff --git a/editor/src/messages/tool/tool_messages/shape_tool.rs b/editor/src/messages/tool/tool_messages/shape_tool.rs index 689954786b..148b48d7c4 100644 --- a/editor/src/messages/tool/tool_messages/shape_tool.rs +++ b/editor/src/messages/tool/tool_messages/shape_tool.rs @@ -686,7 +686,7 @@ impl Fsm for ShapeToolFsmState { } (ShapeToolFsmState::ModifyingGizmo, ShapeToolMessage::PointerMove(..)) => { responses.add(DocumentMessage::StartTransaction); - tool_data.gizmo_manger.handle_update(tool_data.data.drag_start, document, input, responses); + tool_data.gizmo_manger.handle_update(tool_data.data.viewport_drag_start(document), document, input, responses); responses.add(OverlaysMessage::Draw); diff --git a/node-graph/gcore/src/vector/misc.rs b/node-graph/gcore/src/vector/misc.rs index 79890ce2d2..4a3ffd2d9a 100644 --- a/node-graph/gcore/src/vector/misc.rs +++ b/node-graph/gcore/src/vector/misc.rs @@ -98,6 +98,13 @@ pub fn dvec2_to_point(value: DVec2) -> Point { Point { x: value.x, y: value.y } } +pub fn get_line_endpoints(line: Line) -> (DVec2, DVec2) { + let po = line.p0; + let p1 = line.p1; + + (point_to_dvec2(po), point_to_dvec2(p1)) +} + pub fn segment_to_handles(segment: &PathSeg) -> BezierHandles { match *segment { PathSeg::Line(_) => BezierHandles::Linear, From 7ff11b2c865b09d414b6377938408f4fb6f172e1 Mon Sep 17 00:00:00 2001 From: 0SlowPoke0 Date: Thu, 24 Jul 2025 04:38:17 +0530 Subject: [PATCH 3/9] fix compile issues --- .../gizmos/shape_gizmos/grid_row_columns_gizmo.rs | 1 - .../tool/common_functionality/gizmos/shape_gizmos/mod.rs | 2 +- .../messages/tool/common_functionality/shapes/grid_shape.rs | 4 ++-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/grid_row_columns_gizmo.rs b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/grid_row_columns_gizmo.rs index 5efd6438d3..187259603d 100644 --- a/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/grid_row_columns_gizmo.rs +++ b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/grid_row_columns_gizmo.rs @@ -13,7 +13,6 @@ use crate::messages::tool::common_functionality::shapes::shape_utility::extract_ use glam::{DAffine2, DVec2}; use graph_craft::document::NodeInput; use graph_craft::document::value::TaggedValue; -use graphene_std::num_traits::Zero; use graphene_std::vector::misc::{GridType, dvec2_to_point, get_line_endpoints}; use kurbo::{Line, ParamCurveNearest}; use std::collections::VecDeque; diff --git a/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/mod.rs b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/mod.rs index 74a3f9ab34..8999f5c5b9 100644 --- a/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/mod.rs +++ b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/mod.rs @@ -1,3 +1,3 @@ -pub mod grid_row_colums_gizmo; +pub mod grid_row_columns_gizmo; pub mod number_of_points_dial; pub mod point_radius_handle; diff --git a/editor/src/messages/tool/common_functionality/shapes/grid_shape.rs b/editor/src/messages/tool/common_functionality/shapes/grid_shape.rs index 992e873c59..9b11c13340 100644 --- a/editor/src/messages/tool/common_functionality/shapes/grid_shape.rs +++ b/editor/src/messages/tool/common_functionality/shapes/grid_shape.rs @@ -48,7 +48,7 @@ impl ShapeGizmoHandler for GridGizmoHandler { &self, document: &DocumentMessageHandler, selected_grid_layer: Option, - input: &InputPreprocessorMessageHandler, + _input: &InputPreprocessorMessageHandler, shape_editor: &mut &mut ShapeState, mouse_position: DVec2, overlay_context: &mut OverlayContext, @@ -59,7 +59,7 @@ impl ShapeGizmoHandler for GridGizmoHandler { fn dragging_overlays( &self, document: &DocumentMessageHandler, - input: &InputPreprocessorMessageHandler, + _input: &InputPreprocessorMessageHandler, shape_editor: &mut &mut ShapeState, mouse_position: DVec2, overlay_context: &mut OverlayContext, From 616f4abf10b3baa8c6cf2fd73704812a9463a269 Mon Sep 17 00:00:00 2001 From: 0SlowPoke0 Date: Thu, 24 Jul 2025 15:56:51 +0530 Subject: [PATCH 4/9] handle negative correctly,fix undo redo and abort --- editor/src/consts.rs | 2 +- .../document/utility_types/transformation.rs | 2 +- .../shape_gizmos/grid_row_columns_gizmo.rs | 332 +++++++++--------- .../messages/tool/tool_messages/shape_tool.rs | 2 +- 4 files changed, 172 insertions(+), 166 deletions(-) diff --git a/editor/src/consts.rs b/editor/src/consts.rs index 8521914abc..8a32fb7dad 100644 --- a/editor/src/consts.rs +++ b/editor/src/consts.rs @@ -127,7 +127,7 @@ pub const POINT_RADIUS_HANDLE_SEGMENT_THRESHOLD: f64 = 7.9; pub const NUMBER_OF_POINTS_DIAL_SPOKE_EXTENSION: f64 = 1.2; pub const NUMBER_OF_POINTS_DIAL_SPOKE_LENGTH: f64 = 10.; pub const GIZMO_HIDE_THRESHOLD: f64 = 20.; -pub const GRID_ROW_COLUMN_GIZMO_OFFSET: f64 = 10.; +pub const GRID_ROW_COLUMN_GIZMO_OFFSET: f64 = 15.; // SCROLLBARS pub const SCROLLBAR_SPACING: f64 = 0.1; diff --git a/editor/src/messages/portfolio/document/utility_types/transformation.rs b/editor/src/messages/portfolio/document/utility_types/transformation.rs index cefcb61418..c4f908c7c5 100644 --- a/editor/src/messages/portfolio/document/utility_types/transformation.rs +++ b/editor/src/messages/portfolio/document/utility_types/transformation.rs @@ -518,7 +518,7 @@ impl<'a> Selected<'a> { pen_handle: Option<&'a mut DVec2>, ) -> Self { // If user is using the Select tool then use the original layer transforms - if (*tool_type == ToolType::Select) && (*original_transforms == OriginalTransforms::Path(HashMap::new())) { + if (*tool_type == ToolType::Select || *tool_type == ToolType::Shape) && (*original_transforms == OriginalTransforms::Path(HashMap::new())) { *original_transforms = OriginalTransforms::Layer(HashMap::new()); } diff --git a/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/grid_row_columns_gizmo.rs b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/grid_row_columns_gizmo.rs index 187259603d..5d24e0aa91 100644 --- a/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/grid_row_columns_gizmo.rs +++ b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/grid_row_columns_gizmo.rs @@ -25,121 +25,6 @@ pub enum RowColumnGizmoState { Dragging, } -#[derive(Clone, Debug, Default, PartialEq)] -pub enum RowColumnGizmoType { - #[default] - None, - Top, - Down, - Left, - Right, -} - -impl RowColumnGizmoType { - pub fn get_line_points(&self, grid_type: GridType, columns: u32, rows: u32, spacing: DVec2, angles: DVec2) -> (DVec2, DVec2) { - match grid_type { - GridType::Rectangular => match self { - Self::Top => get_rectangle_top_line_points(columns, rows, spacing), - Self::Right => get_rectangle_right_line_points(columns, rows, spacing), - Self::Down => get_rectangle_bottom_line_points(columns, rows, spacing), - Self::Left => get_rectangle_left_line_points(columns, rows, spacing), - Self::None => panic!("RowColumnGizmoType::None does not have line points"), - }, - GridType::Isometric => match self { - Self::Top => calculate_isometric_top_line_points(columns, spacing, angles), - Self::Right => calculate_isometric_right_line_points(columns, rows, spacing, angles), - Self::Down => calculate_isometric_bottom_line_points(columns, rows, spacing, angles), - Self::Left => calculate_isometric_left_line_points(rows, spacing, angles), - Self::None => panic!("RowColumnGizmoType::None does not have line points"), - }, - } - } - fn line(&self, grid_type: GridType, columns: u32, rows: u32, spacing: DVec2, angles: DVec2, viewport: DAffine2) -> Line { - let (p0, p1) = self.get_line_points(grid_type, columns, rows, spacing, angles); - convert_to_gizmo_line(viewport.transform_point2(p0), viewport.transform_point2(p1)) - } - fn opposite(&self, grid_type: GridType, columns: u32, rows: u32, spacing: DVec2, angles: DVec2, viewport: DAffine2) -> Line { - let opposite_gizmo_type = match self { - Self::Top => Self::Down, - Self::Right => Self::Left, - Self::Down => Self::Top, - Self::Left => Self::Right, - Self::None => panic!("RowColumnGizmoType::None does not have opposite"), - }; - - opposite_gizmo_type.line(grid_type, columns, rows, spacing, angles, viewport) - } - fn direction(&self, grid_type: GridType, columns: u32, rows: u32, spacing: DVec2, angles: DVec2, viewport: DAffine2) -> DVec2 { - match self { - RowColumnGizmoType::Top => { - if grid_type == GridType::Rectangular { - calculate_rectangle_top_direction(columns, rows, spacing, viewport) - } else { - -calculate_isometric_top_direction(angles, spacing, Some(viewport)) - } - } - RowColumnGizmoType::Down => { - if grid_type == GridType::Rectangular { - -calculate_rectangle_top_direction(columns, rows, spacing, viewport) - } else { - calculate_isometric_top_direction(angles, spacing, Some(viewport)) - } - } - RowColumnGizmoType::Right => { - if grid_type == GridType::Rectangular { - calculate_rectangle_side_direction(columns, rows, spacing, viewport) - } else { - calculate_isometric_side_direction(angles, spacing, Some(viewport)) - } - } - RowColumnGizmoType::Left => { - if grid_type == GridType::Rectangular { - -calculate_rectangle_side_direction(columns, rows, spacing, viewport) - } else { - -calculate_isometric_side_direction(angles, spacing, Some(viewport)) - } - } - RowColumnGizmoType::None => panic!("RowColumnGizmoType::None does not have a line"), - } - } - - fn initial_dimension(&self, rows: u32, columns: u32) -> u32 { - match self { - RowColumnGizmoType::Top | RowColumnGizmoType::Down => rows, - RowColumnGizmoType::Right | RowColumnGizmoType::Left => columns, - RowColumnGizmoType::None => panic!("RowColumnGizmoType::None does not have a mouse_icon"), - } - } - - fn spacing(&self, spacing: DVec2) -> f64 { - match self { - RowColumnGizmoType::Top | RowColumnGizmoType::Down => spacing.y, - RowColumnGizmoType::Right | RowColumnGizmoType::Left => spacing.x, - RowColumnGizmoType::None => panic!("RowColumnGizmoType::None does not have a mouse_icon"), - } - } - - fn index(&self) -> usize { - match self { - RowColumnGizmoType::Top | RowColumnGizmoType::Down => GRID_ROW_INDEX, - RowColumnGizmoType::Right | RowColumnGizmoType::Left => GRID_COLUMNS_INDEX, - RowColumnGizmoType::None => panic!("RowColumnGizmoType::None does not have a mouse_icon"), - } - } - - fn mouse_icon(&self) -> MouseCursorIcon { - match self { - RowColumnGizmoType::Top | RowColumnGizmoType::Down => MouseCursorIcon::NSResize, - RowColumnGizmoType::Right | RowColumnGizmoType::Left => MouseCursorIcon::EWResize, - RowColumnGizmoType::None => panic!("RowColumnGizmoType::None does not have a mouse_icon"), - } - } - - pub fn all() -> [Self; 4] { - [Self::Top, Self::Right, Self::Down, Self::Left] - } -} - #[derive(Clone, Debug, Default)] pub struct RowColumnGizmo { pub layer: Option, @@ -147,12 +32,15 @@ pub struct RowColumnGizmo { initial_rows: u32, initial_columns: u32, spacing: DVec2, + initial_mouse_start: Option, gizmo_state: RowColumnGizmoState, } impl RowColumnGizmo { pub fn cleanup(&mut self) { self.layer = None; + self.gizmo_state = RowColumnGizmoState::Inactive; + self.initial_mouse_start = None; } pub fn update_state(&mut self, state: RowColumnGizmoState) { @@ -186,6 +74,7 @@ impl RowColumnGizmo { self.initial_rows = rows; self.initial_columns = columns; self.spacing = spacing; + self.initial_mouse_start = None; self.update_state(RowColumnGizmoState::Hover); responses.add(FrontendMessage::UpdateMouseCursor { cursor: self.gizmo_type.mouse_icon() }); } @@ -199,8 +88,7 @@ impl RowColumnGizmo { let viewport = document.metadata().transform_to_viewport(layer); if !matches!(self.gizmo_state, RowColumnGizmoState::Inactive) { - let (p0, p1) = self.gizmo_type.get_line_points(grid_type, columns, rows, spacing, angles); - let line = convert_to_gizmo_line(viewport.transform_point2(p0), viewport.transform_point2(p1)); + let line = self.gizmo_type.line(grid_type, columns, rows, spacing, angles, viewport); let (p0, p1) = get_line_endpoints(line); overlay_context.dashed_line(p0, p1, None, None, Some(5.), Some(5.), Some(0.5)); @@ -212,15 +100,15 @@ impl RowColumnGizmo { } } - pub fn update(&self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque, drag_start: DVec2) { + pub fn update(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque, drag_start: DVec2) { let Some(layer) = self.layer else { return }; let viewport = document.metadata().transform_to_viewport(layer); let Some((grid_type, spacing, columns, rows, angles)) = extract_grid_parameters(layer, document) else { return; }; - let direction = self.gizmo_type.direction(grid_type, columns, rows, spacing, angles, viewport); - let delta_vector = input.mouse.position - drag_start; + let direction = self.gizmo_type.direction(grid_type, spacing, angles, viewport); + let delta_vector = input.mouse.position - self.initial_mouse_start.unwrap_or(drag_start); let viewport_spacing = get_viewport_grid_spacing(grid_type, angles, self.spacing, viewport); let delta = delta_vector.dot(direction); @@ -233,7 +121,7 @@ impl RowColumnGizmo { }; let dimensions_delta = new_dimension as i32 - self.gizmo_type.initial_dimension(rows, columns) as i32; - let transform = self.transform_grid(dimensions_delta, grid_type, columns, rows, viewport_spacing, angles, viewport); + let transform = self.transform_grid(dimensions_delta, grid_type, viewport_spacing, angles, viewport); responses.add(NodeGraphMessage::SetInput { input_connector: InputConnector::node(node_id, self.gizmo_type.index()), @@ -248,16 +136,23 @@ impl RowColumnGizmo { }); responses.add(NodeGraphMessage::RunDocumentGraph); + + if self.initial_dimension() as i32 + dimensions_to_add < 1 { + self.initial_mouse_start = Some(input.mouse.position); + self.gizmo_type = self.gizmo_type.opposite_gizmo_type(); + self.initial_rows = 1; + self.initial_columns = 1; + } } - fn transform_grid(&self, dimensions_delta: i32, grid_type: GridType, columns: u32, rows: u32, spacing: DVec2, angles: DVec2, viewport: DAffine2) -> DAffine2 { + fn transform_grid(&self, dimensions_delta: i32, grid_type: GridType, spacing: DVec2, angles: DVec2, viewport: DAffine2) -> DAffine2 { match self.gizmo_type { RowColumnGizmoType::Top => { - let move_up_by = self.gizmo_type.direction(grid_type, columns, rows, spacing, angles, viewport) * dimensions_delta as f64 * spacing.y; + let move_up_by = self.gizmo_type.direction(grid_type, spacing, angles, viewport) * dimensions_delta as f64 * spacing.y; DAffine2::from_translation(move_up_by) } RowColumnGizmoType::Left => { - let move_left_by = self.gizmo_type.direction(grid_type, columns, rows, spacing, angles, viewport) * dimensions_delta as f64 * spacing.x; + let move_left_by = self.gizmo_type.direction(grid_type, spacing, angles, viewport) * dimensions_delta as f64 * spacing.x; DAffine2::from_translation(move_left_by) } RowColumnGizmoType::Down | RowColumnGizmoType::Right | RowColumnGizmoType::None => DAffine2::IDENTITY, @@ -330,38 +225,30 @@ fn get_viewport_grid_spacing(grid_type: GridType, angles: DVec2, spacing: DVec2, fn get_rectangle_top_line_points(columns: u32, rows: u32, spacing: DVec2) -> (DVec2, DVec2) { let (top_left, top_right, _, _) = get_corners(columns, rows, spacing); + let offset = if columns == 1 || rows == 1 { DVec2::ZERO } else { DVec2::new(spacing.x * 0.5, 0.) }; - let offset = DVec2::new(spacing.x * 0.25, 0.); - let spacing_offset = DVec2::new(0., -GRID_ROW_COLUMN_GIZMO_OFFSET); - - (top_left + offset + spacing_offset, top_right - offset + spacing_offset) + (top_left + offset, top_right - offset) } fn get_rectangle_bottom_line_points(columns: u32, rows: u32, spacing: DVec2) -> (DVec2, DVec2) { let (_, _, bottom_right, bottom_left) = get_corners(columns, rows, spacing); + let offset = if columns == 1 || rows == 1 { DVec2::ZERO } else { DVec2::new(spacing.x * 0.5, 0.) }; - let offset = DVec2::new(spacing.x * 0.25, 0.); - let spacing_offset = DVec2::new(0., GRID_ROW_COLUMN_GIZMO_OFFSET); - - (bottom_left + offset + spacing_offset, bottom_right - offset + spacing_offset) + (bottom_left + offset, bottom_right - offset) } fn get_rectangle_right_line_points(columns: u32, rows: u32, spacing: DVec2) -> (DVec2, DVec2) { let (_, top_right, bottom_right, _) = get_corners(columns, rows, spacing); + let offset = if columns == 1 || rows == 1 { DVec2::ZERO } else { DVec2::new(0., -spacing.y * 0.5) }; - let offset = DVec2::new(0., -spacing.y * 0.25); - let spacing_offset = DVec2::new(GRID_ROW_COLUMN_GIZMO_OFFSET, 0.); - - (top_right - offset + spacing_offset, bottom_right + offset + spacing_offset) + (top_right - offset, bottom_right + offset) } fn get_rectangle_left_line_points(columns: u32, rows: u32, spacing: DVec2) -> (DVec2, DVec2) { let (top_left, _, _, bottom_left) = get_corners(columns, rows, spacing); - let offset = DVec2::new(0., -spacing.y * 0.25); - - let spacing_offset = DVec2::new(GRID_ROW_COLUMN_GIZMO_OFFSET, 0.); + let offset = if columns == 1 || rows == 1 { DVec2::ZERO } else { DVec2::new(0., -spacing.y * 0.5) }; - (top_left - offset - spacing_offset, bottom_left + offset - spacing_offset) + (top_left - offset, bottom_left + offset) } fn calculate_isometric_point(column: u32, row: u32, angles: DVec2, spacing: DVec2) -> DVec2 { @@ -378,33 +265,31 @@ fn calculate_isometric_point(column: u32, row: u32, angles: DVec2, spacing: DVec DVec2::new(spacing.x * column as f64, spacing.y * row as f64 + offset_y_fraction * spacing.x) } -fn calculate_isometric_top_line_points(columns: u32, spacing: DVec2, angles: DVec2) -> (DVec2, DVec2) { +fn calculate_isometric_top_line_points(columns: u32, rows: u32, spacing: DVec2, angles: DVec2) -> (DVec2, DVec2) { let top_left = calculate_isometric_point(0, 0, angles, spacing); let top_right = calculate_isometric_point(columns - 1, 0, angles, spacing); - let offset = DVec2::new(spacing.x * 0.25, 0.); + let offset = if columns == 1 || rows == 1 { DVec2::ZERO } else { DVec2::new(spacing.x * 0.5, 0.) }; let isometric_spacing = calculate_isometric_offset(spacing, angles); let isometric_offset = DVec2::new(0., isometric_spacing.y); let end_isometric_offset = if columns % 2 == 0 { DVec2::ZERO } else { DVec2::new(0., isometric_spacing.y) }; - let spacing_offset = DVec2::new(0., -GRID_ROW_COLUMN_GIZMO_OFFSET); - (top_left + offset + spacing_offset - isometric_offset, top_right - offset + spacing_offset - end_isometric_offset) + (top_left + offset - isometric_offset, top_right - offset - end_isometric_offset) } fn calculate_isometric_bottom_line_points(columns: u32, rows: u32, spacing: DVec2, angles: DVec2) -> (DVec2, DVec2) { let bottom_left = calculate_isometric_point(0, rows - 1, angles, spacing); let bottom_right = calculate_isometric_point(columns - 1, rows - 1, angles, spacing); - let offset = DVec2::new(spacing.x * 0.25, 0.); + let offset = if columns == 1 || rows == 1 { DVec2::ZERO } else { DVec2::new(spacing.x * 0.5, 0.) }; let isometric_offset = if columns % 2 == 0 { let offset = calculate_isometric_offset(spacing, angles); DVec2::new(0., offset.y) } else { DVec2::ZERO }; - let spacing_offset = DVec2::new(0., GRID_ROW_COLUMN_GIZMO_OFFSET); - (bottom_left + offset + spacing_offset, bottom_right - offset + spacing_offset + isometric_offset) + (bottom_left + offset, bottom_right - offset + isometric_offset) } fn calculate_isometric_offset(spacing: DVec2, angles: DVec2) -> DVec2 { @@ -417,34 +302,31 @@ fn calculate_isometric_offset(spacing: DVec2, angles: DVec2) -> DVec2 { fn calculate_isometric_right_line_points(columns: u32, rows: u32, spacing: DVec2, angles: DVec2) -> (DVec2, DVec2) { let top_right = calculate_isometric_point(columns - 1, 0, angles, spacing); let bottom_right = calculate_isometric_point(columns - 1, rows - 1, angles, spacing); - let side_direction = calculate_isometric_side_direction(angles, spacing, None); - let offset = DVec2::new(0., -spacing.y * 0.25); - let spacing_offset = GRID_ROW_COLUMN_GIZMO_OFFSET * side_direction; + let offset = if columns == 1 || rows == 1 { DVec2::ZERO } else { DVec2::new(0., -spacing.y * 0.5) }; - (top_right - offset + spacing_offset, bottom_right + offset + spacing_offset) + (top_right - offset, bottom_right + offset) } -fn calculate_isometric_left_line_points(rows: u32, spacing: DVec2, angles: DVec2) -> (DVec2, DVec2) { +fn calculate_isometric_left_line_points(columns: u32, rows: u32, spacing: DVec2, angles: DVec2) -> (DVec2, DVec2) { let top_left = calculate_isometric_point(0, 0, angles, spacing); let bottom_left = calculate_isometric_point(0, rows - 1, angles, spacing); - let side_direction = calculate_isometric_side_direction(angles, spacing, None); - let offset = DVec2::new(0., -spacing.y * 0.25); - let spacing_offset = GRID_ROW_COLUMN_GIZMO_OFFSET * side_direction; + let offset = if columns == 1 || rows == 1 { DVec2::ZERO } else { DVec2::new(0., -spacing.y * 0.5) }; - (top_left - offset - spacing_offset, bottom_left + offset - spacing_offset) + (top_left - offset, bottom_left + offset) } -fn calculate_rectangle_side_direction(columns: u32, rows: u32, spacing: DVec2, viewport: DAffine2) -> DVec2 { - let (left, right, _, _) = get_corners(columns, rows, spacing); - (viewport.transform_point2(right) - viewport.transform_point2(left)).try_normalize().unwrap_or(DVec2::ZERO) +fn calculate_rectangle_side_direction(spacing: DVec2, viewport: DAffine2) -> DVec2 { + let p0 = DVec2::ZERO; + let p1 = DVec2::new(spacing.x, 0.); + (viewport.transform_point2(p1) - viewport.transform_point2(p0)).normalize() } -fn calculate_rectangle_top_direction(columns: u32, rows: u32, spacing: DVec2, viewport: DAffine2) -> DVec2 { - let (left, _, _, bottom_left) = get_corners(columns, rows, spacing); - - (viewport.transform_point2(left) - viewport.transform_point2(bottom_left)).try_normalize().unwrap_or(DVec2::ZERO) +fn calculate_rectangle_top_direction(spacing: DVec2, viewport: DAffine2) -> DVec2 { + let p0 = DVec2::ZERO; + let p1 = DVec2::new(0., spacing.y); + (viewport.transform_point2(p0) - viewport.transform_point2(p1)).try_normalize().unwrap_or(DVec2::ZERO) } fn calculate_isometric_side_direction(angles: DVec2, spacing: DVec2, viewport: Option) -> DVec2 { @@ -472,3 +354,127 @@ fn calculate_isometric_top_direction(angles: DVec2, spacing: DVec2, viewport: Op (first_point - first_row_last_column).try_normalize().unwrap_or_default() } + +#[derive(Clone, Debug, Default, PartialEq)] +pub enum RowColumnGizmoType { + #[default] + None, + Top, + Down, + Left, + Right, +} + +impl RowColumnGizmoType { + pub fn get_line_points(&self, grid_type: GridType, columns: u32, rows: u32, spacing: DVec2, angles: DVec2) -> (DVec2, DVec2) { + match grid_type { + GridType::Rectangular => match self { + Self::Top => get_rectangle_top_line_points(columns, rows, spacing), + Self::Right => get_rectangle_right_line_points(columns, rows, spacing), + Self::Down => get_rectangle_bottom_line_points(columns, rows, spacing), + Self::Left => get_rectangle_left_line_points(columns, rows, spacing), + Self::None => panic!("RowColumnGizmoType::None does not have line points"), + }, + GridType::Isometric => match self { + Self::Top => calculate_isometric_top_line_points(columns, rows, spacing, angles), + Self::Right => calculate_isometric_right_line_points(columns, rows, spacing, angles), + Self::Down => calculate_isometric_bottom_line_points(columns, rows, spacing, angles), + Self::Left => calculate_isometric_left_line_points(columns, rows, spacing, angles), + Self::None => panic!("RowColumnGizmoType::None does not have line points"), + }, + } + } + + fn line(&self, grid_type: GridType, columns: u32, rows: u32, spacing: DVec2, angles: DVec2, viewport: DAffine2) -> Line { + let (p0, p1) = self.get_line_points(grid_type, columns, rows, spacing, angles); + let direction = self.direction(grid_type, spacing, angles, viewport); + let gap = GRID_ROW_COLUMN_GIZMO_OFFSET * direction; + + convert_to_gizmo_line(viewport.transform_point2(p0) + gap, viewport.transform_point2(p1) + gap) + } + + fn opposite(&self, grid_type: GridType, columns: u32, rows: u32, spacing: DVec2, angles: DVec2, viewport: DAffine2) -> Line { + let opposite_gizmo_type = self.opposite_gizmo_type(); + opposite_gizmo_type.line(grid_type, columns, rows, spacing, angles, viewport) + } + + fn opposite_gizmo_type(&self) -> Self { + return match self { + Self::Top => Self::Down, + Self::Right => Self::Left, + Self::Down => Self::Top, + Self::Left => Self::Right, + Self::None => panic!("RowColumnGizmoType::None does not have opposite"), + }; + } + + fn direction(&self, grid_type: GridType, spacing: DVec2, angles: DVec2, viewport: DAffine2) -> DVec2 { + match self { + RowColumnGizmoType::Top => { + if grid_type == GridType::Rectangular { + calculate_rectangle_top_direction(spacing, viewport) + } else { + -calculate_isometric_top_direction(angles, spacing, Some(viewport)) + } + } + RowColumnGizmoType::Down => { + if grid_type == GridType::Rectangular { + -calculate_rectangle_top_direction(spacing, viewport) + } else { + calculate_isometric_top_direction(angles, spacing, Some(viewport)) + } + } + RowColumnGizmoType::Right => { + if grid_type == GridType::Rectangular { + calculate_rectangle_side_direction(spacing, viewport) + } else { + calculate_isometric_side_direction(angles, spacing, Some(viewport)) + } + } + RowColumnGizmoType::Left => { + if grid_type == GridType::Rectangular { + -calculate_rectangle_side_direction(spacing, viewport) + } else { + -calculate_isometric_side_direction(angles, spacing, Some(viewport)) + } + } + RowColumnGizmoType::None => panic!("RowColumnGizmoType::None does not have a line"), + } + } + + fn initial_dimension(&self, rows: u32, columns: u32) -> u32 { + match self { + RowColumnGizmoType::Top | RowColumnGizmoType::Down => rows, + RowColumnGizmoType::Right | RowColumnGizmoType::Left => columns, + RowColumnGizmoType::None => panic!("RowColumnGizmoType::None does not have a mouse_icon"), + } + } + + fn spacing(&self, spacing: DVec2) -> f64 { + match self { + RowColumnGizmoType::Top | RowColumnGizmoType::Down => spacing.y, + RowColumnGizmoType::Right | RowColumnGizmoType::Left => spacing.x, + RowColumnGizmoType::None => panic!("RowColumnGizmoType::None does not have a mouse_icon"), + } + } + + fn index(&self) -> usize { + match self { + RowColumnGizmoType::Top | RowColumnGizmoType::Down => GRID_ROW_INDEX, + RowColumnGizmoType::Right | RowColumnGizmoType::Left => GRID_COLUMNS_INDEX, + RowColumnGizmoType::None => panic!("RowColumnGizmoType::None does not have a mouse_icon"), + } + } + + fn mouse_icon(&self) -> MouseCursorIcon { + match self { + RowColumnGizmoType::Top | RowColumnGizmoType::Down => MouseCursorIcon::NSResize, + RowColumnGizmoType::Right | RowColumnGizmoType::Left => MouseCursorIcon::EWResize, + RowColumnGizmoType::None => panic!("RowColumnGizmoType::None does not have a mouse_icon"), + } + } + + pub fn all() -> [Self; 4] { + [Self::Top, Self::Right, Self::Down, Self::Left] + } +} diff --git a/editor/src/messages/tool/tool_messages/shape_tool.rs b/editor/src/messages/tool/tool_messages/shape_tool.rs index 148b48d7c4..605d5a5ae5 100644 --- a/editor/src/messages/tool/tool_messages/shape_tool.rs +++ b/editor/src/messages/tool/tool_messages/shape_tool.rs @@ -567,6 +567,7 @@ impl Fsm for ShapeToolFsmState { if tool_data.gizmo_manger.handle_click() { tool_data.data.drag_start = document.metadata().document_to_viewport.inverse().transform_point2(mouse_pos); + responses.add(DocumentMessage::StartTransaction); return ShapeToolFsmState::ModifyingGizmo; } @@ -685,7 +686,6 @@ impl Fsm for ShapeToolFsmState { self } (ShapeToolFsmState::ModifyingGizmo, ShapeToolMessage::PointerMove(..)) => { - responses.add(DocumentMessage::StartTransaction); tool_data.gizmo_manger.handle_update(tool_data.data.viewport_drag_start(document), document, input, responses); responses.add(OverlaysMessage::Draw); From 7d53b0cb22e4dddcbcc9ff1fce77cb3448ad4013 Mon Sep 17 00:00:00 2001 From: 0SlowPoke0 Date: Tue, 29 Jul 2025 00:44:45 +0530 Subject: [PATCH 5/9] impl spacing gizmo-not fully integrated --- .../shape_gizmos/grid_row_columns_gizmo.rs | 22 +- .../shape_gizmos/grid_spacing_gizmos.rs | 296 ++++++++++++++++++ .../gizmos/shape_gizmos/mod.rs | 1 + .../common_functionality/shapes/grid_shape.rs | 29 +- 4 files changed, 332 insertions(+), 16 deletions(-) create mode 100644 editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/grid_spacing_gizmos.rs diff --git a/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/grid_row_columns_gizmo.rs b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/grid_row_columns_gizmo.rs index 5d24e0aa91..bb5ac5f972 100644 --- a/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/grid_row_columns_gizmo.rs +++ b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/grid_row_columns_gizmo.rs @@ -175,7 +175,7 @@ fn check_if_over_gizmo(grid_type: GridType, columns: u32, rows: u32, spacing: DV None } -fn convert_to_gizmo_line(p0: DVec2, p1: DVec2) -> Line { +pub fn convert_to_gizmo_line(p0: DVec2, p1: DVec2) -> Line { Line { p0: dvec2_to_point(p0), p1: dvec2_to_point(p1), @@ -198,7 +198,7 @@ fn get_corners(columns: u32, rows: u32, spacing: DVec2) -> (DVec2, DVec2, DVec2, (point0, point1, point2, point3) } -fn get_viewport_grid_spacing(grid_type: GridType, angles: DVec2, spacing: DVec2, viewport: DAffine2) -> DVec2 { +pub fn get_viewport_grid_spacing(grid_type: GridType, angles: DVec2, spacing: DVec2, viewport: DAffine2) -> DVec2 { match grid_type { GridType::Rectangular => { let p0 = DVec2::ZERO; @@ -223,28 +223,28 @@ fn get_viewport_grid_spacing(grid_type: GridType, angles: DVec2, spacing: DVec2, } } -fn get_rectangle_top_line_points(columns: u32, rows: u32, spacing: DVec2) -> (DVec2, DVec2) { +pub fn get_rectangle_top_line_points(columns: u32, rows: u32, spacing: DVec2) -> (DVec2, DVec2) { let (top_left, top_right, _, _) = get_corners(columns, rows, spacing); let offset = if columns == 1 || rows == 1 { DVec2::ZERO } else { DVec2::new(spacing.x * 0.5, 0.) }; (top_left + offset, top_right - offset) } -fn get_rectangle_bottom_line_points(columns: u32, rows: u32, spacing: DVec2) -> (DVec2, DVec2) { +pub fn get_rectangle_bottom_line_points(columns: u32, rows: u32, spacing: DVec2) -> (DVec2, DVec2) { let (_, _, bottom_right, bottom_left) = get_corners(columns, rows, spacing); let offset = if columns == 1 || rows == 1 { DVec2::ZERO } else { DVec2::new(spacing.x * 0.5, 0.) }; (bottom_left + offset, bottom_right - offset) } -fn get_rectangle_right_line_points(columns: u32, rows: u32, spacing: DVec2) -> (DVec2, DVec2) { +pub fn get_rectangle_right_line_points(columns: u32, rows: u32, spacing: DVec2) -> (DVec2, DVec2) { let (_, top_right, bottom_right, _) = get_corners(columns, rows, spacing); let offset = if columns == 1 || rows == 1 { DVec2::ZERO } else { DVec2::new(0., -spacing.y * 0.5) }; (top_right - offset, bottom_right + offset) } -fn get_rectangle_left_line_points(columns: u32, rows: u32, spacing: DVec2) -> (DVec2, DVec2) { +pub fn get_rectangle_left_line_points(columns: u32, rows: u32, spacing: DVec2) -> (DVec2, DVec2) { let (top_left, _, _, bottom_left) = get_corners(columns, rows, spacing); let offset = if columns == 1 || rows == 1 { DVec2::ZERO } else { DVec2::new(0., -spacing.y * 0.5) }; @@ -292,14 +292,14 @@ fn calculate_isometric_bottom_line_points(columns: u32, rows: u32, spacing: DVec (bottom_left + offset, bottom_right - offset + isometric_offset) } -fn calculate_isometric_offset(spacing: DVec2, angles: DVec2) -> DVec2 { +pub fn calculate_isometric_offset(spacing: DVec2, angles: DVec2) -> DVec2 { let first_point = calculate_isometric_point(0, 0, angles, spacing); let second_point = calculate_isometric_point(1, 0, angles, spacing); DVec2::new(first_point.x - second_point.x, first_point.y - second_point.y) } -fn calculate_isometric_right_line_points(columns: u32, rows: u32, spacing: DVec2, angles: DVec2) -> (DVec2, DVec2) { +pub fn calculate_isometric_right_line_points(columns: u32, rows: u32, spacing: DVec2, angles: DVec2) -> (DVec2, DVec2) { let top_right = calculate_isometric_point(columns - 1, 0, angles, spacing); let bottom_right = calculate_isometric_point(columns - 1, rows - 1, angles, spacing); @@ -308,7 +308,7 @@ fn calculate_isometric_right_line_points(columns: u32, rows: u32, spacing: DVec2 (top_right - offset, bottom_right + offset) } -fn calculate_isometric_left_line_points(columns: u32, rows: u32, spacing: DVec2, angles: DVec2) -> (DVec2, DVec2) { +pub fn calculate_isometric_left_line_points(columns: u32, rows: u32, spacing: DVec2, angles: DVec2) -> (DVec2, DVec2) { let top_left = calculate_isometric_point(0, 0, angles, spacing); let bottom_left = calculate_isometric_point(0, rows - 1, angles, spacing); @@ -317,13 +317,13 @@ fn calculate_isometric_left_line_points(columns: u32, rows: u32, spacing: DVec2, (top_left - offset, bottom_left + offset) } -fn calculate_rectangle_side_direction(spacing: DVec2, viewport: DAffine2) -> DVec2 { +pub fn calculate_rectangle_side_direction(spacing: DVec2, viewport: DAffine2) -> DVec2 { let p0 = DVec2::ZERO; let p1 = DVec2::new(spacing.x, 0.); (viewport.transform_point2(p1) - viewport.transform_point2(p0)).normalize() } -fn calculate_rectangle_top_direction(spacing: DVec2, viewport: DAffine2) -> DVec2 { +pub fn calculate_rectangle_top_direction(spacing: DVec2, viewport: DAffine2) -> DVec2 { let p0 = DVec2::ZERO; let p1 = DVec2::new(0., spacing.y); (viewport.transform_point2(p0) - viewport.transform_point2(p1)).try_normalize().unwrap_or(DVec2::ZERO) diff --git a/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/grid_spacing_gizmos.rs b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/grid_spacing_gizmos.rs new file mode 100644 index 0000000000..9621104648 --- /dev/null +++ b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/grid_spacing_gizmos.rs @@ -0,0 +1,296 @@ +use crate::consts::{GRID_COLUMNS_INDEX, GRID_ROW_COLUMN_GIZMO_OFFSET, GRID_ROW_INDEX, GRID_SPACING_INDEX}; +use crate::messages::frontend::utility_types::MouseCursorIcon; +use crate::messages::message::Message; +use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn; +use crate::messages::portfolio::document::overlays::utility_types::OverlayContext; +use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; +use crate::messages::portfolio::document::utility_types::network_interface::InputConnector; +use crate::messages::prelude::{DocumentMessageHandler, FrontendMessage, InputPreprocessorMessageHandler, NodeGraphMessage}; +use crate::messages::prelude::{GraphOperationMessage, Responses}; +use crate::messages::tool::common_functionality::gizmos::shape_gizmos::grid_row_columns_gizmo::{ + calculate_rectangle_side_direction, calculate_rectangle_top_direction, convert_to_gizmo_line, get_viewport_grid_spacing, +}; +use crate::messages::tool::common_functionality::graph_modification_utils; +use crate::messages::tool::common_functionality::shape_editor::ShapeState; +use crate::messages::tool::common_functionality::shapes::shape_utility::extract_grid_parameters; +use glam::{DAffine2, DVec2}; +use graph_craft::document::NodeInput; +use graph_craft::document::value::TaggedValue; +use graphene_std::renderer::Quad; +use graphene_std::vector::misc::{GridType, dvec2_to_point, get_line_endpoints}; +use kurbo::{Line, ParamCurveNearest, Rect}; +use std::collections::VecDeque; + +#[derive(Clone, Debug, Default, PartialEq)] +pub enum GridSpacingGizmoState { + #[default] + Inactive, + Hover, + Dragging, +} + +#[derive(Clone, Debug, Default)] +pub struct GridSpacingGizmo { + pub layer: Option, + gizmo_state: GridSpacingGizmoState, + column_index: u32, + row_index: u32, + initial_spacing: DVec2, + gizmo_type: Option, +} + +impl GridSpacingGizmo { + pub fn cleanup(&mut self) { + self.layer = None; + self.gizmo_state = GridSpacingGizmoState::Inactive; + } + + pub fn update_state(&mut self, state: GridSpacingGizmoState) { + self.gizmo_state = state; + } + + pub fn is_hovered(&self) -> bool { + self.gizmo_state == GridSpacingGizmoState::Hover + } + + pub fn is_dragging(&self) -> bool { + self.gizmo_state == GridSpacingGizmoState::Dragging + } + + pub fn handle_actions(&mut self, layer: LayerNodeIdentifier, mouse_position: DVec2, document: &DocumentMessageHandler, responses: &mut VecDeque) { + let Some((grid_type, spacing, columns, rows, angles)) = extract_grid_parameters(layer, document) else { + return; + }; + let stroke_width = graph_modification_utils::get_stroke_width(layer, &document.network_interface); + let viewport = document.metadata().transform_to_viewport(layer); + if let Some((col, row)) = check_if_over_gizmo(grid_type, columns, rows, spacing, mouse_position, viewport) { + self.layer = Some(layer); + self.column_index = col; + self.row_index = row; + self.initial_spacing = spacing; + self.update_state(GridSpacingGizmoState::Hover); + let closest_gizmo = GridSpacingGizmoType::get_closest_line(mouse_position, col, row, spacing, viewport, stroke_width); + responses.add(FrontendMessage::UpdateMouseCursor { cursor: closest_gizmo.mouse_icon() }); + self.gizmo_type = Some(closest_gizmo); + } + } + + pub fn overlays(&self, document: &DocumentMessageHandler, layer: Option, _shape_editor: &mut &mut ShapeState, _mouse_position: DVec2, overlay_context: &mut OverlayContext) { + let Some(layer) = layer.or(self.layer) else { return }; + let Some((grid_type, spacing, columns, rows, angles)) = extract_grid_parameters(layer, document) else { + return; + }; + let viewport = document.metadata().transform_to_viewport(layer); + let stroke_width = graph_modification_utils::get_stroke_width(layer, &document.network_interface); + + match self.gizmo_state { + GridSpacingGizmoState::Inactive => {} + GridSpacingGizmoState::Hover | GridSpacingGizmoState::Dragging => { + if let Some(gizmo_type) = &self.gizmo_type { + let line = gizmo_type.line(self.column_index, self.row_index, spacing, viewport, stroke_width); + let (p0, p1) = get_line_endpoints(line); + overlay_context.dashed_line(p0, p1, None, None, Some(5.), Some(5.), Some(0.5)); + + if matches!(self.gizmo_state, GridSpacingGizmoState::Hover) { + let line = gizmo_type.opposite_gizmo_type().line(self.column_index, self.row_index, spacing, viewport, stroke_width); + let (p0, p1) = get_line_endpoints(line); + overlay_context.dashed_line(p0, p1, None, None, Some(5.), Some(5.), Some(0.5)); + } + } + } + } + } + + pub fn update(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque, drag_start: DVec2) { + let Some(layer) = self.layer else { return }; + let viewport = document.metadata().transform_to_viewport(layer); + + let Some((grid_type, spacing, columns, rows, angles)) = extract_grid_parameters(layer, document) else { + return; + }; + + let Some(gizmo_type) = &self.gizmo_type else { return }; + let direction = gizmo_type.direction(spacing, viewport); + let delta_vector = input.mouse.position - drag_start; + + let delta = delta_vector.dot(direction); + + let Some(node_id) = graph_modification_utils::get_grid_id(layer, &document.network_interface) else { + return; + }; + + let new_spacing = gizmo_type.new_spacing(delta, self.initial_spacing); + let delta_spacing = new_spacing - spacing; + + // let transform = self.transform_grid(dimensions_delta, grid_type, viewport_spacing, angles, viewport); + + responses.add(NodeGraphMessage::SetInput { + input_connector: InputConnector::node(node_id, GRID_SPACING_INDEX), + input: NodeInput::value(TaggedValue::DVec2(new_spacing), false), + }); + + // responses.add(GraphOperationMessage::TransformChange { + // layer, + // transform: DAffine2::from_translation(-delta_spacing * direction), + // transform_in: TransformIn::Viewport, + // skip_rerender: false, + // }); + + responses.add(NodeGraphMessage::RunDocumentGraph); + + // if self.initial_dimension() as i32 + dimensions_to_add < 1 { + // self.initial_mouse_start = Some(input.mouse.position); + // self.gizmo_type = self.gizmo_type.opposite_gizmo_type(); + // self.initial_rows = 1; + // self.initial_columns = 1; + // } + } + + fn transform_grid(&self, dimensions_delta: i32, grid_type: GridType, spacing: DVec2, angles: DVec2, viewport: DAffine2) {} +} + +fn check_if_over_gizmo(grid_type: GridType, columns: u32, rows: u32, spacing: DVec2, mouse_position: DVec2, viewport: DAffine2) -> Option<(u32, u32)> { + let layer_mouse = viewport.inverse().transform_point2(mouse_position); + for column in 0..columns - 1 { + for row in 0..rows - 1 { + let p0 = DVec2::new(spacing.x * column as f64, spacing.y * row as f64); + let p1 = DVec2::new((1 + column) as f64 * spacing.x, (1 + row) as f64 * spacing.y); + let rect = Rect::from_points(dvec2_to_point(p0), dvec2_to_point(p1)); + + if rect.contains(dvec2_to_point(layer_mouse)) { + return Some((column, row)); + }; + } + } + + None +} + +#[derive(Clone, Debug, Default, PartialEq)] +pub enum GridSpacingGizmoType { + #[default] + None, + Top, + Down, + Left, + Right, +} + +impl GridSpacingGizmoType { + pub fn get_line_points(&self, column_index: u32, row_index: u32, spacing: DVec2, stroke: Option) -> (DVec2, DVec2) { + match self { + Self::Top => get_rectangular_top_points(column_index, row_index, spacing, stroke), + Self::Right => get_rectangular_right_points(column_index, row_index, spacing, stroke), + Self::Down => get_rectangular_down_points(column_index, row_index, spacing, stroke), + Self::Left => get_rectangular_left_points(column_index, row_index, spacing, stroke), + Self::None => panic!("RowColumnGizmoType::None does not have line points"), + } + } + + pub fn get_closest_line(mouse_position: DVec2, column_index: u32, row_index: u32, spacing: DVec2, viewport: DAffine2, stroke_width: Option) -> Self { + let mut gizmo_type = GridSpacingGizmoType::Top; + let mut closest_distance = gizmo_type + .line(column_index, row_index, spacing, viewport, stroke_width) + .nearest(dvec2_to_point(mouse_position), 1e-6) + .distance_sq; + + for t in Self::all() { + if matches!(t, GridSpacingGizmoType::Top) { + continue; + } + let line = t.line(column_index, row_index, spacing, viewport, stroke_width); + let nearest = line.nearest(dvec2_to_point(mouse_position), 1e-6); + if nearest.distance_sq < closest_distance { + gizmo_type = t; + closest_distance = nearest.distance_sq; + } + } + gizmo_type + } + + pub fn line(&self, column_index: u32, row_index: u32, spacing: DVec2, viewport: DAffine2, stroke_width: Option) -> Line { + let (p0, p1) = self.get_line_points(column_index, row_index, spacing, stroke_width); + let gap = -5. * self.direction(spacing, viewport); + + convert_to_gizmo_line(viewport.transform_point2(p0) + gap, viewport.transform_point2(p1) + gap) + } + + fn opposite(&self, grid_type: GridType, column_index: u32, row_index: u32, spacing: DVec2, angles: DVec2, viewport: DAffine2, stroke: Option) -> Line { + let opposite_gizmo_type = self.opposite_gizmo_type(); + opposite_gizmo_type.line(column_index, row_index, spacing, viewport, stroke) + } + + fn opposite_gizmo_type(&self) -> Self { + return match self { + Self::Top => Self::Down, + Self::Right => Self::Left, + Self::Down => Self::Top, + Self::Left => Self::Right, + Self::None => panic!("RowColumnGizmoType::None does not have opposite"), + }; + } + + fn new_spacing(&self, delta: f64, spacing: DVec2) -> DVec2 { + match self { + GridSpacingGizmoType::Top | GridSpacingGizmoType::Down => DVec2::new(spacing.x, spacing.y + delta), + GridSpacingGizmoType::Right | GridSpacingGizmoType::Left => DVec2::new(spacing.x + delta, spacing.y), + GridSpacingGizmoType::None => panic!("RowColumnGizmoType::None does not have a mouse_icon"), + } + } + + fn direction(&self, spacing: DVec2, viewport: DAffine2) -> DVec2 { + match self { + GridSpacingGizmoType::Top => calculate_rectangle_top_direction(spacing, viewport), + GridSpacingGizmoType::Down => -calculate_rectangle_top_direction(spacing, viewport), + GridSpacingGizmoType::Right => calculate_rectangle_side_direction(spacing, viewport), + GridSpacingGizmoType::Left => -calculate_rectangle_side_direction(spacing, viewport), + GridSpacingGizmoType::None => panic!("RowColumnGizmoType::None does not have a line"), + } + } + + fn mouse_icon(&self) -> MouseCursorIcon { + match self { + GridSpacingGizmoType::Top | GridSpacingGizmoType::Down => MouseCursorIcon::NSResize, + GridSpacingGizmoType::Right | GridSpacingGizmoType::Left => MouseCursorIcon::EWResize, + GridSpacingGizmoType::None => panic!("RowColumnGizmoType::None does not have a mouse_icon"), + } + } + + pub fn all() -> [Self; 4] { + [Self::Top, Self::Right, Self::Down, Self::Left] + } +} + +fn get_rectangular_top_points(column_index: u32, row_index: u32, spacing: DVec2, stroke_width: Option) -> (DVec2, DVec2) { + let stroke_width = stroke_width.unwrap_or_default(); + let p0 = DVec2::new(column_index as f64 * spacing.x, row_index as f64 * spacing.y) + DVec2::new(stroke_width, stroke_width); + let p1 = p0 + DVec2::new(spacing.x - 2. * stroke_width, 0.); + + (p0, p1) +} + +fn get_rectangular_right_points(column_index: u32, row_index: u32, spacing: DVec2, stroke_width: Option) -> (DVec2, DVec2) { + let stroke_width = stroke_width.unwrap_or_default(); + let p0 = DVec2::new((1 + column_index) as f64 * spacing.x, row_index as f64 * spacing.y) + DVec2::new(-stroke_width, stroke_width); + let p1 = p0 + DVec2::new(0., spacing.y - 2. * stroke_width); + + (p0, p1) +} + +fn get_rectangular_down_points(column_index: u32, row_index: u32, spacing: DVec2, stroke_width: Option) -> (DVec2, DVec2) { + let stroke_width = stroke_width.unwrap_or_default(); + + let p0 = DVec2::new(column_index as f64 * spacing.x, (1 + row_index) as f64 * spacing.y) + DVec2::new(stroke_width, -stroke_width); + let p1 = p0 + DVec2::new(spacing.x - 2. * stroke_width, 0.); + + (p0, p1) +} + +fn get_rectangular_left_points(column_index: u32, row_index: u32, spacing: DVec2, stroke_width: Option) -> (DVec2, DVec2) { + let stroke_width = stroke_width.unwrap_or_default(); + + let p0 = DVec2::new(column_index as f64 * spacing.x, row_index as f64 * spacing.y) + DVec2::new(stroke_width, stroke_width); + let p1 = p0 + DVec2::new(0., spacing.y - 2. * stroke_width); + + (p0, p1) +} diff --git a/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/mod.rs b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/mod.rs index 8999f5c5b9..b2af3b4b46 100644 --- a/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/mod.rs +++ b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/mod.rs @@ -1,3 +1,4 @@ pub mod grid_row_columns_gizmo; +pub mod grid_spacing_gizmos; pub mod number_of_points_dial; pub mod point_radius_handle; diff --git a/editor/src/messages/tool/common_functionality/shapes/grid_shape.rs b/editor/src/messages/tool/common_functionality/shapes/grid_shape.rs index 9b11c13340..99eda8b279 100644 --- a/editor/src/messages/tool/common_functionality/shapes/grid_shape.rs +++ b/editor/src/messages/tool/common_functionality/shapes/grid_shape.rs @@ -5,9 +5,9 @@ use crate::messages::portfolio::document::node_graph::document_node_definitions: use crate::messages::portfolio::document::overlays::utility_types::OverlayContext; use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; use crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, NodeTemplate}; -use crate::messages::tool::common_functionality::gizmos::shape_gizmos::grid_row_columns_gizmo::RowColumnGizmo; use crate::messages::tool::common_functionality::gizmos::shape_gizmos::grid_row_columns_gizmo::RowColumnGizmoState; - +use crate::messages::tool::common_functionality::gizmos::shape_gizmos::grid_row_columns_gizmo::{RowColumnGizmo, calculate_rectangle_side_direction, calculate_rectangle_top_direction}; +use crate::messages::tool::common_functionality::gizmos::shape_gizmos::grid_spacing_gizmos::{GridSpacingGizmo, GridSpacingGizmoState}; use crate::messages::tool::common_functionality::graph_modification_utils; use crate::messages::tool::common_functionality::shape_editor::ShapeState; use crate::messages::tool::common_functionality::shapes::shape_utility::ShapeGizmoHandler; @@ -15,33 +15,44 @@ use crate::messages::tool::tool_messages::tool_prelude::*; use glam::DAffine2; use graph_craft::document::NodeInput; use graph_craft::document::value::TaggedValue; -use graphene_std::vector::misc::GridType; +use graphene_std::vector::misc::{GridType, dvec2_to_point}; +use kurbo::{Line, ParamCurveNearest}; use std::collections::VecDeque; #[derive(Clone, Debug, Default)] pub struct GridGizmoHandler { row_column_gizmo: RowColumnGizmo, + grid_spacing_gizmo: GridSpacingGizmo, } impl ShapeGizmoHandler for GridGizmoHandler { fn is_any_gizmo_hovered(&self) -> bool { - self.row_column_gizmo.is_hovered() + self.row_column_gizmo.is_hovered() || self.grid_spacing_gizmo.is_hovered() } fn handle_state(&mut self, selected_grid_layer: LayerNodeIdentifier, mouse_position: DVec2, document: &DocumentMessageHandler, responses: &mut VecDeque) { self.row_column_gizmo.handle_actions(selected_grid_layer, mouse_position, document, responses); + self.grid_spacing_gizmo.handle_actions(selected_grid_layer, mouse_position, document, responses); } fn handle_click(&mut self) { if self.row_column_gizmo.is_hovered() { self.row_column_gizmo.update_state(RowColumnGizmoState::Dragging); } + + if self.grid_spacing_gizmo.is_hovered() { + self.grid_spacing_gizmo.update_state(GridSpacingGizmoState::Dragging); + } } fn handle_update(&mut self, drag_start: DVec2, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque) { if self.row_column_gizmo.is_dragging() { self.row_column_gizmo.update(document, input, responses, drag_start); } + + if self.grid_spacing_gizmo.is_dragging() { + self.grid_spacing_gizmo.update(document, input, responses, drag_start); + } } fn overlays( @@ -54,6 +65,7 @@ impl ShapeGizmoHandler for GridGizmoHandler { overlay_context: &mut OverlayContext, ) { self.row_column_gizmo.overlays(document, selected_grid_layer, shape_editor, mouse_position, overlay_context); + self.grid_spacing_gizmo.overlays(document, selected_grid_layer, shape_editor, mouse_position, overlay_context); } fn dragging_overlays( @@ -67,9 +79,16 @@ impl ShapeGizmoHandler for GridGizmoHandler { if self.row_column_gizmo.is_dragging() { self.row_column_gizmo.overlays(document, None, shape_editor, mouse_position, overlay_context); } + + if self.grid_spacing_gizmo.is_dragging() { + self.grid_spacing_gizmo.overlays(document, None, shape_editor, mouse_position, overlay_context); + } } - fn cleanup(&mut self) {} + fn cleanup(&mut self) { + self.grid_spacing_gizmo.cleanup(); + self.row_column_gizmo.cleanup(); + } } #[derive(Default)] From 6ac56e149088e9622e3e74468b70b4d6621973a5 Mon Sep 17 00:00:00 2001 From: 0SlowPoke0 Date: Thu, 7 Aug 2025 23:52:01 +0530 Subject: [PATCH 6/9] fixed rectangular spacing --- .../shape_gizmos/grid_row_columns_gizmo.rs | 6 -- .../shape_gizmos/grid_spacing_gizmos.rs | 67 ++++++++++++++----- 2 files changed, 50 insertions(+), 23 deletions(-) diff --git a/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/grid_row_columns_gizmo.rs b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/grid_row_columns_gizmo.rs index bb5ac5f972..3ec7f66e5e 100644 --- a/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/grid_row_columns_gizmo.rs +++ b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/grid_row_columns_gizmo.rs @@ -91,12 +91,6 @@ impl RowColumnGizmo { let line = self.gizmo_type.line(grid_type, columns, rows, spacing, angles, viewport); let (p0, p1) = get_line_endpoints(line); overlay_context.dashed_line(p0, p1, None, None, Some(5.), Some(5.), Some(0.5)); - - if matches!(self.gizmo_state, RowColumnGizmoState::Hover) { - let line = self.gizmo_type.opposite(grid_type, columns, rows, spacing, angles, viewport); - let (p0, p1) = get_line_endpoints(line); - overlay_context.dashed_line(p0, p1, None, None, Some(5.), Some(5.), Some(0.5)); - } } } diff --git a/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/grid_spacing_gizmos.rs b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/grid_spacing_gizmos.rs index 9621104648..f8f8788998 100644 --- a/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/grid_spacing_gizmos.rs +++ b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/grid_spacing_gizmos.rs @@ -90,12 +90,6 @@ impl GridSpacingGizmo { let line = gizmo_type.line(self.column_index, self.row_index, spacing, viewport, stroke_width); let (p0, p1) = get_line_endpoints(line); overlay_context.dashed_line(p0, p1, None, None, Some(5.), Some(5.), Some(0.5)); - - if matches!(self.gizmo_state, GridSpacingGizmoState::Hover) { - let line = gizmo_type.opposite_gizmo_type().line(self.column_index, self.row_index, spacing, viewport, stroke_width); - let (p0, p1) = get_line_endpoints(line); - overlay_context.dashed_line(p0, p1, None, None, Some(5.), Some(5.), Some(0.5)); - } } } } @@ -110,7 +104,7 @@ impl GridSpacingGizmo { }; let Some(gizmo_type) = &self.gizmo_type else { return }; - let direction = gizmo_type.direction(spacing, viewport); + let direction = gizmo_type.direction(self.initial_spacing, viewport); let delta_vector = input.mouse.position - drag_start; let delta = delta_vector.dot(direction); @@ -120,22 +114,23 @@ impl GridSpacingGizmo { }; let new_spacing = gizmo_type.new_spacing(delta, self.initial_spacing); - let delta_spacing = new_spacing - spacing; - - // let transform = self.transform_grid(dimensions_delta, grid_type, viewport_spacing, angles, viewport); + let spacing_delta = new_spacing - spacing; responses.add(NodeGraphMessage::SetInput { input_connector: InputConnector::node(node_id, GRID_SPACING_INDEX), input: NodeInput::value(TaggedValue::DVec2(new_spacing), false), }); - // responses.add(GraphOperationMessage::TransformChange { - // layer, - // transform: DAffine2::from_translation(-delta_spacing * direction), - // transform_in: TransformIn::Viewport, - // skip_rerender: false, - // }); + let transform = self.transform_grid(spacing_delta, direction); + + responses.add(GraphOperationMessage::TransformChange { + layer, + transform, + transform_in: TransformIn::Viewport, + skip_rerender: false, + }); + log::info!("{:?}", (self.row_index, self.column_index)); responses.add(NodeGraphMessage::RunDocumentGraph); // if self.initial_dimension() as i32 + dimensions_to_add < 1 { @@ -146,7 +141,45 @@ impl GridSpacingGizmo { // } } - fn transform_grid(&self, dimensions_delta: i32, grid_type: GridType, spacing: DVec2, angles: DVec2, viewport: DAffine2) {} + fn transform_grid(&self, spacing_delta: DVec2, direction: DVec2) -> DAffine2 { + if let Some(gizmo_type) = &self.gizmo_type { + match gizmo_type { + GridSpacingGizmoType::Right => { + if self.column_index == 0 { + DAffine2::IDENTITY + } else { + DAffine2::from_translation(-spacing_delta * direction * (self.column_index) as f64) + } + } + GridSpacingGizmoType::Down => { + if self.row_index == 0 { + DAffine2::IDENTITY + } else { + DAffine2::from_translation(-spacing_delta * direction * (self.row_index) as f64) + } + } + GridSpacingGizmoType::Left => { + if self.column_index == 0 { + DAffine2::from_translation(spacing_delta * direction) + } else { + DAffine2::from_translation(spacing_delta * direction * (self.column_index + 1) as f64) + } + } + GridSpacingGizmoType::Top => { + if self.row_index == 0 { + DAffine2::from_translation(spacing_delta * direction) + } else { + DAffine2::from_translation(spacing_delta * direction * (self.row_index + 1) as f64) + } + } + + GridSpacingGizmoType::None => DAffine2::IDENTITY, + _ => DAffine2::from_translation(spacing_delta * direction), + } + } else { + DAffine2::IDENTITY + } + } } fn check_if_over_gizmo(grid_type: GridType, columns: u32, rows: u32, spacing: DVec2, mouse_position: DVec2, viewport: DAffine2) -> Option<(u32, u32)> { From f7629e3443252d476e1c412308124a24338f8ea5 Mon Sep 17 00:00:00 2001 From: 0SlowPoke0 Date: Sun, 10 Aug 2025 20:25:31 +0530 Subject: [PATCH 7/9] impl detection --- .vscode/settings.json | 2 +- .../shape_gizmos/grid_spacing_gizmos.rs | 675 ++++++++++++++---- 2 files changed, 531 insertions(+), 146 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 59b061584f..4b736574e8 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -53,5 +53,5 @@ "files.associations": { "*.graphite": "json" }, - "rust-analyzer.checkOnSave": false + "rust-analyzer.checkOnSave": true } diff --git a/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/grid_spacing_gizmos.rs b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/grid_spacing_gizmos.rs index f8f8788998..6c97333e0f 100644 --- a/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/grid_spacing_gizmos.rs +++ b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/grid_spacing_gizmos.rs @@ -18,7 +18,7 @@ use graph_craft::document::NodeInput; use graph_craft::document::value::TaggedValue; use graphene_std::renderer::Quad; use graphene_std::vector::misc::{GridType, dvec2_to_point, get_line_endpoints}; -use kurbo::{Line, ParamCurveNearest, Rect}; +use kurbo::{Line, ParamCurveNearest, Rect, Shape, Triangle}; use std::collections::VecDeque; #[derive(Clone, Debug, Default, PartialEq)] @@ -63,13 +63,15 @@ impl GridSpacingGizmo { }; let stroke_width = graph_modification_utils::get_stroke_width(layer, &document.network_interface); let viewport = document.metadata().transform_to_viewport(layer); - if let Some((col, row)) = check_if_over_gizmo(grid_type, columns, rows, spacing, mouse_position, viewport) { + if let Some((col, row)) = check_if_over_gizmo(grid_type, columns, rows, spacing, angles, mouse_position, viewport) { + log::info!("col {:?} , row {:?}", col, row); self.layer = Some(layer); self.column_index = col; self.row_index = row; self.initial_spacing = spacing; self.update_state(GridSpacingGizmoState::Hover); - let closest_gizmo = GridSpacingGizmoType::get_closest_line(mouse_position, col, row, spacing, viewport, stroke_width); + let closest_gizmo = GridSpacingGizmoType::get_closest_line(grid_type, mouse_position, col, row, spacing, angles, viewport, stroke_width); + log::info!("gizmo type {:?}", closest_gizmo); responses.add(FrontendMessage::UpdateMouseCursor { cursor: closest_gizmo.mouse_icon() }); self.gizmo_type = Some(closest_gizmo); } @@ -85,13 +87,22 @@ impl GridSpacingGizmo { match self.gizmo_state { GridSpacingGizmoState::Inactive => {} - GridSpacingGizmoState::Hover | GridSpacingGizmoState::Dragging => { - if let Some(gizmo_type) = &self.gizmo_type { - let line = gizmo_type.line(self.column_index, self.row_index, spacing, viewport, stroke_width); - let (p0, p1) = get_line_endpoints(line); - overlay_context.dashed_line(p0, p1, None, None, Some(5.), Some(5.), Some(0.5)); + GridSpacingGizmoState::Hover | GridSpacingGizmoState::Dragging => match grid_type { + GridType::Rectangular => { + if let Some(gizmo_type) = &self.gizmo_type { + let line = gizmo_type.line(grid_type, self.column_index, self.row_index, angles, spacing, viewport, stroke_width); + let (p0, p1) = get_line_endpoints(line); + overlay_context.dashed_line(p0, p1, None, None, Some(5.), Some(5.), Some(0.5)); + } } - } + GridType::Isometric => { + if let Some(gizmo_type) = &self.gizmo_type { + let line = gizmo_type.line(grid_type, self.column_index, self.row_index, angles, spacing, viewport, stroke_width); + let (p0, p1) = get_line_endpoints(line); + overlay_context.dashed_line(p0, p1, None, None, Some(5.), Some(5.), Some(0.5)); + } + } + }, } } @@ -103,8 +114,9 @@ impl GridSpacingGizmo { return; }; + let stroke_width = graph_modification_utils::get_stroke_width(layer, &document.network_interface); let Some(gizmo_type) = &self.gizmo_type else { return }; - let direction = gizmo_type.direction(self.initial_spacing, viewport); + let direction = gizmo_type.direction(self.column_index, self.row_index, angles, self.initial_spacing, viewport); let delta_vector = input.mouse.position - drag_start; let delta = delta_vector.dot(direction); @@ -121,7 +133,7 @@ impl GridSpacingGizmo { input: NodeInput::value(TaggedValue::DVec2(new_spacing), false), }); - let transform = self.transform_grid(spacing_delta, direction); + let transform = self.gizmo_type.as_ref().unwrap().transform_grid(spacing_delta, direction, self.column_index, self.row_index); responses.add(GraphOperationMessage::TransformChange { layer, @@ -140,190 +152,563 @@ impl GridSpacingGizmo { // self.initial_columns = 1; // } } +} - fn transform_grid(&self, spacing_delta: DVec2, direction: DVec2) -> DAffine2 { - if let Some(gizmo_type) = &self.gizmo_type { - match gizmo_type { - GridSpacingGizmoType::Right => { - if self.column_index == 0 { - DAffine2::IDENTITY - } else { - DAffine2::from_translation(-spacing_delta * direction * (self.column_index) as f64) - } - } - GridSpacingGizmoType::Down => { - if self.row_index == 0 { - DAffine2::IDENTITY - } else { - DAffine2::from_translation(-spacing_delta * direction * (self.row_index) as f64) - } +fn check_if_over_gizmo(grid_type: GridType, columns: u32, rows: u32, spacing: DVec2, angles: DVec2, mouse_position: DVec2, viewport: DAffine2) -> Option<(u32, u32)> { + let layer_mouse = viewport.inverse().transform_point2(mouse_position); + match grid_type { + GridType::Rectangular => { + for column in 0..columns - 1 { + for row in 0..rows - 1 { + let p0 = DVec2::new(spacing.x * column as f64, spacing.y * row as f64); + let p1 = DVec2::new((1 + column) as f64 * spacing.x, (1 + row) as f64 * spacing.y); + let rect = Rect::from_points(dvec2_to_point(p0), dvec2_to_point(p1)); + + if rect.contains(dvec2_to_point(layer_mouse)) { + return Some((column, row)); + }; } - GridSpacingGizmoType::Left => { - if self.column_index == 0 { - DAffine2::from_translation(spacing_delta * direction) - } else { - DAffine2::from_translation(spacing_delta * direction * (self.column_index + 1) as f64) + } + } + GridType::Isometric => { + for column in 0..columns - 1 { + for row in 0..rows - 1 { + let p0 = isometric_point_position(row, column, spacing, angles); + let p1 = isometric_point_position(row, column + 1, spacing, angles); + let p2 = isometric_point_position(row + 1, column + 1, spacing, angles); + let p4 = isometric_point_position(row + 1, column, spacing, angles); + + let triangle1 = Triangle::new(dvec2_to_point(p0), dvec2_to_point(p1), dvec2_to_point(p2)); + let triangle2 = Triangle::new(dvec2_to_point(p0), dvec2_to_point(p2), dvec2_to_point(p4)); + + if triangle2.contains(dvec2_to_point(layer_mouse)) { + return Some((column, row)); } - } - GridSpacingGizmoType::Top => { - if self.row_index == 0 { - DAffine2::from_translation(spacing_delta * direction) - } else { - DAffine2::from_translation(spacing_delta * direction * (self.row_index + 1) as f64) + + if triangle1.contains(dvec2_to_point(layer_mouse)) { + return Some((column, row)); } } - - GridSpacingGizmoType::None => DAffine2::IDENTITY, - _ => DAffine2::from_translation(spacing_delta * direction), } - } else { - DAffine2::IDENTITY } } + + None } -fn check_if_over_gizmo(grid_type: GridType, columns: u32, rows: u32, spacing: DVec2, mouse_position: DVec2, viewport: DAffine2) -> Option<(u32, u32)> { - let layer_mouse = viewport.inverse().transform_point2(mouse_position); - for column in 0..columns - 1 { - for row in 0..rows - 1 { - let p0 = DVec2::new(spacing.x * column as f64, spacing.y * row as f64); - let p1 = DVec2::new((1 + column) as f64 * spacing.x, (1 + row) as f64 * spacing.y); - let rect = Rect::from_points(dvec2_to_point(p0), dvec2_to_point(p1)); - - if rect.contains(dvec2_to_point(layer_mouse)) { - return Some((column, row)); - }; - } - } +// #[derive(Clone, Debug, Default, PartialEq)] +// pub enum GridSpacingGizmoType { +// #[default] +// None, +// Top, +// Down, +// Left, +// Right, +// IsometricMiddleUp, +// IsometricMiddleDown, +// } + +// impl GridSpacingGizmoType { +// pub fn get_line_points(&self, grid_type: GridType, column_index: u32, row_index: u32, angles: DVec2, spacing: DVec2, stroke: Option) -> (DVec2, DVec2) { +// match grid_type { +// GridType::Rectangular => match self { +// Self::Top => get_rectangular_top_points(column_index, row_index, spacing, stroke), +// Self::Right => get_rectangular_right_points(column_index, row_index, spacing, stroke), +// Self::Down => get_rectangular_down_points(column_index, row_index, spacing, stroke), +// Self::Left => get_rectangular_left_points(column_index, row_index, spacing, stroke), +// Self::None => panic!("RowColumnGizmoType::None does not have line points"), +// }, +// GridType::Isometric => match self { +// Self::Top => get_isometric_top_points(column_index, row_index, angles, spacing, stroke), +// Self::Right => get_isometric_right_points(column_index, row_index, angles, spacing, stroke), +// Self::Down => get_isometric_down_points(column_index, row_index, angles, spacing, stroke), +// Self::Left => get_isometric_left_points(column_index, row_index, angles, spacing, stroke), +// Self::None => panic!("RowColumnGizmoType::None does not have line points"), +// }, +// } +// } + +// pub fn get_closest_line(mouse_position: DVec2, grid_type: GridType, column_index: u32, row_index: u32, angles: DVec2, spacing: DVec2, viewport: DAffine2, stroke_width: Option) -> Self { +// let mut gizmo_type = GridSpacingGizmoType::Top; +// let mut closest_distance = gizmo_type +// .line(grid_type, column_index, row_index, angles, spacing, viewport, stroke_width) +// .nearest(dvec2_to_point(mouse_position), 1e-6) +// .distance_sq; + +// for t in Self::all() { +// if matches!(t, GridSpacingGizmoType::Top) { +// continue; +// } +// let line = t.line(grid_type, column_index, row_index, angles, spacing, viewport, stroke_width); +// let nearest = line.nearest(dvec2_to_point(mouse_position), 1e-6); +// if nearest.distance_sq < closest_distance { +// gizmo_type = t; +// closest_distance = nearest.distance_sq; +// } +// } +// gizmo_type +// } + +// pub fn line(&self, grid_type: GridType, column_index: u32, row_index: u32, angles: DVec2, spacing: DVec2, viewport: DAffine2, stroke_width: Option) -> Line { +// let (p0, p1) = self.get_line_points(grid_type, column_index, row_index, angles, spacing, stroke_width); +// // let gap = 2. * stroke_width.unwrap_or(3.) * (p1 - p0).perp().normalize(); + +// convert_to_gizmo_line(viewport.transform_point2(p0), viewport.transform_point2(p1)) +// } + +// fn opposite_gizmo_type(&self) -> Self { +// return match self { +// Self::Top => Self::Down, +// Self::Right => Self::Left, +// Self::Down => Self::Top, +// Self::Left => Self::Right, +// Self::None => panic!("RowColumnGizmoType::None does not have opposite"), +// }; +// } + +// fn new_spacing(&self, delta: f64, spacing: DVec2) -> DVec2 { +// match self { +// GridSpacingGizmoType::Top | GridSpacingGizmoType::Down => DVec2::new(spacing.x, spacing.y + delta), +// GridSpacingGizmoType::Right | GridSpacingGizmoType::Left => DVec2::new(spacing.x + delta, spacing.y), +// GridSpacingGizmoType::None => panic!("RowColumnGizmoType::None does not have a mouse_icon"), +// } +// } + +// fn direction(&self, grid_type: GridType, column_index: u32, row_index: u32, angles: DVec2, spacing: DVec2, viewport: DAffine2) -> DVec2 { +// match grid_type { +// GridType::Rectangular => match self { +// GridSpacingGizmoType::Top => viewport.transform_vector2(DVec2::Y), +// GridSpacingGizmoType::Down => -viewport.transform_vector2(DVec2::Y), +// GridSpacingGizmoType::Right => calculate_rectangle_side_direction(spacing, viewport), +// GridSpacingGizmoType::Left => -calculate_rectangle_side_direction(spacing, viewport), +// GridSpacingGizmoType::None => panic!("RowColumnGizmoType::None does not have a line"), +// }, +// GridType::Isometric => match &self { +// GridSpacingGizmoType::Top | GridSpacingGizmoType::Down | GridSpacingGizmoType::Left | GridSpacingGizmoType::Right => { +// let (p1, p2) = self.get_line_points(grid_type, column_index, row_index, angles, spacing, None); +// (p1 - p2).perp().normalize() +// } +// GridSpacingGizmoType::None => panic!("RowColumnGizmoType::None does not have a line"), +// }, +// } +// } + +// fn mouse_icon(&self) -> MouseCursorIcon { +// match self { +// GridSpacingGizmoType::Top | GridSpacingGizmoType::Down => MouseCursorIcon::NSResize, +// GridSpacingGizmoType::Right | GridSpacingGizmoType::Left => MouseCursorIcon::EWResize, +// GridSpacingGizmoType::None => panic!("RowColumnGizmoType::None does not have a mouse_icon"), +// } +// } + +// pub fn all() -> [Self; 4] { +// [Self::Top, Self::Right, Self::Down, Self::Left] +// } +// } - None +fn get_rectangular_top_points(column_index: u32, row_index: u32, spacing: DVec2, stroke_width: Option) -> (DVec2, DVec2) { + let stroke_width = stroke_width.unwrap_or_default(); + let p0 = DVec2::new(column_index as f64 * spacing.x, row_index as f64 * spacing.y) + DVec2::new(stroke_width, stroke_width); + let p1 = p0 + DVec2::new(spacing.x - 2. * stroke_width, 0.); + + (p0, p1) } -#[derive(Clone, Debug, Default, PartialEq)] -pub enum GridSpacingGizmoType { +fn get_rectangular_right_points(column_index: u32, row_index: u32, spacing: DVec2, stroke_width: Option) -> (DVec2, DVec2) { + let stroke_width = stroke_width.unwrap_or_default(); + let p0 = DVec2::new((1 + column_index) as f64 * spacing.x, row_index as f64 * spacing.y) + DVec2::new(-stroke_width, stroke_width); + let p1 = p0 + DVec2::new(0., spacing.y - 2. * stroke_width); + + (p0, p1) +} + +fn get_rectangular_down_points(column_index: u32, row_index: u32, spacing: DVec2, stroke_width: Option) -> (DVec2, DVec2) { + let stroke_width = stroke_width.unwrap_or_default(); + + let p0 = DVec2::new(column_index as f64 * spacing.x, (1 + row_index) as f64 * spacing.y) + DVec2::new(stroke_width, -stroke_width); + let p1 = p0 + DVec2::new(spacing.x - 2. * stroke_width, 0.); + + (p0, p1) +} + +fn get_rectangular_left_points(column_index: u32, row_index: u32, spacing: DVec2, stroke_width: Option) -> (DVec2, DVec2) { + let stroke_width = stroke_width.unwrap_or_default(); + + let p0 = DVec2::new(column_index as f64 * spacing.x, row_index as f64 * spacing.y) + DVec2::new(stroke_width, stroke_width); + let p1 = p0 + DVec2::new(0., spacing.y - 2. * stroke_width); + + (p0, p1) +} + +fn isometric_point_position(row: u32, col: u32, spacing: DVec2, angles: DVec2) -> DVec2 { + let (angle_a, angle_b) = angles.into(); + let tan_a = angle_a.to_radians().tan(); + let tan_b = angle_b.to_radians().tan(); + + let spacing = DVec2::new(spacing.y / (tan_a + tan_b), spacing.y); + + let a_angles_eaten = col.div_ceil(2) as f64; + let b_angles_eaten = (col / 2) as f64; + let offset_y_fraction = b_angles_eaten * tan_b - a_angles_eaten * tan_a; + + DVec2::new(spacing.x * col as f64, spacing.y * row as f64 + offset_y_fraction * spacing.x) +} + +fn get_isometric_top_points(column_index: u32, row_index: u32, angles: DVec2, spacing: DVec2, stroke_width: Option) -> (DVec2, DVec2) { + let stroke_width = stroke_width.unwrap_or_default(); + + let x0 = isometric_point_position(row_index, column_index, spacing, angles); + let x1 = isometric_point_position(row_index, column_index + 1, spacing, angles); + let direction = (x1 - x0).normalize(); + + // in the direction of edge + let padding = (x1 - x0).length() * 0.1 * direction; + // perpendicular to the edge + let push_out = calculate_gap_vector(direction, stroke_width); + + (x0 + push_out + padding, x1 + push_out - padding) +} + +fn get_isometric_right_points(column_index: u32, row_index: u32, angles: DVec2, spacing: DVec2, stroke_width: Option) -> (DVec2, DVec2) { + let stroke_width = stroke_width.unwrap_or_default(); + + let x0 = isometric_point_position(row_index, column_index + 1, spacing, angles); + let x1 = isometric_point_position(row_index + 1, column_index + 1, spacing, angles); + let direction = (x1 - x0).normalize(); + + // in the direction of edge + let padding = (x1 - x0).length() * 0.1 * direction; + // perpendicular to the edge + let push_out = calculate_gap_vector(direction, stroke_width); + + (x0 + push_out + padding, x1 + push_out - padding) +} + +fn get_isometric_down_points(column_index: u32, row_index: u32, angles: DVec2, spacing: DVec2, stroke_width: Option) -> (DVec2, DVec2) { + let stroke_width = stroke_width.unwrap_or_default(); + + let x0 = isometric_point_position(row_index + 1, column_index, spacing, angles); + let x1 = isometric_point_position(row_index + 1, column_index + 1, spacing, angles); + let direction = (x1 - x0).normalize(); + + // in the direction of edge + let padding = (x1 - x0).length() * 0.1 * direction; + // perpendicular to the edge + let push_out = calculate_gap_vector(direction, stroke_width); + + (x0 - push_out + padding, x1 - push_out - padding) +} + +fn get_isometric_left_points(column_index: u32, row_index: u32, angles: DVec2, spacing: DVec2, stroke_width: Option) -> (DVec2, DVec2) { + let stroke_width = stroke_width.unwrap_or_default(); + + let x0 = isometric_point_position(row_index, column_index, spacing, angles); + let x1 = isometric_point_position(row_index + 1, column_index, spacing, angles); + let direction = (x1 - x0).normalize(); + + // in the direction of edge + let padding = (x1 - x0).length() * 0.1 * direction; + // perpendicular to the edge + let push_out = calculate_gap_vector(direction, stroke_width); + + (x0 - push_out + padding, x1 - push_out - padding) +} + +fn get_isometric_middle_up_points(column_index: u32, row_index: u32, angles: DVec2, spacing: DVec2, stroke_width: Option) -> (DVec2, DVec2) { + let stroke_width = stroke_width.unwrap_or_default(); + + let (x0, x1) = if column_index % 2 == 0 { + ( + isometric_point_position(row_index, column_index, spacing, angles), + isometric_point_position(row_index + 1, column_index + 1, spacing, angles), + ) + } else { + // ref point is changed + ( + isometric_point_position(row_index + 1, column_index, spacing, angles), + isometric_point_position(row_index, column_index + 1, spacing, angles), + ) + }; + let direction = (x1 - x0).normalize(); + + // in the direction of edge + let padding = (x1 - x0).length() * 0.1 * direction; + // perpendicular to the edge + let push_out = calculate_gap_vector(direction, stroke_width); + + (x0 - push_out + padding, x1 - push_out - padding) +} + +fn get_isometric_middle_down_points(column_index: u32, row_index: u32, angles: DVec2, spacing: DVec2, stroke_width: Option) -> (DVec2, DVec2) { + let stroke_width = stroke_width.unwrap_or_default(); + + let (x0, x1) = if column_index % 2 == 0 { + ( + isometric_point_position(row_index, column_index, spacing, angles), + isometric_point_position(row_index + 1, column_index + 1, spacing, angles), + ) + } else { + // ref point is changed + ( + isometric_point_position(row_index + 1, column_index, spacing, angles), + isometric_point_position(row_index, column_index + 1, spacing, angles), + ) + }; + let direction = (x1 - x0).normalize(); + // in the direction of edge + let padding = (x1 - x0).length() * 0.1 * direction; + // perpendicular to the edge + let push_out = calculate_gap_vector(direction, stroke_width); + + (x0 + push_out + padding, x1 + push_out - padding) +} + +fn calculate_gap_vector(direction: DVec2, stroke_width: f64) -> DVec2 { + let perp = direction.perp().normalize(); + (stroke_width + 0.5) * perp +} + +#[derive(Clone, Copy, Debug, Default, PartialEq)] +pub enum RectangularGizmoType { #[default] - None, Top, + Right, Down, Left, +} + +#[derive(Clone, Copy, Debug, Default, PartialEq)] +pub enum IsometricGizmoType { + #[default] + Top, Right, + Down, + Left, + IsometricMiddleUp, + IsometricMiddleDown, } -impl GridSpacingGizmoType { - pub fn get_line_points(&self, column_index: u32, row_index: u32, spacing: DVec2, stroke: Option) -> (DVec2, DVec2) { - match self { - Self::Top => get_rectangular_top_points(column_index, row_index, spacing, stroke), - Self::Right => get_rectangular_right_points(column_index, row_index, spacing, stroke), - Self::Down => get_rectangular_down_points(column_index, row_index, spacing, stroke), - Self::Left => get_rectangular_left_points(column_index, row_index, spacing, stroke), - Self::None => panic!("RowColumnGizmoType::None does not have line points"), - } +#[derive(Clone, Debug, PartialEq)] +pub enum GridSpacingGizmoType { + Rect(RectangularGizmoType), + Iso(IsometricGizmoType), +} + +pub fn get_line_points_for_rect(gizmo: RectangularGizmoType, column_index: u32, row_index: u32, spacing: DVec2, stroke: Option) -> (DVec2, DVec2) { + match gizmo { + RectangularGizmoType::Top => get_rectangular_top_points(column_index, row_index, spacing, stroke), + RectangularGizmoType::Right => get_rectangular_right_points(column_index, row_index, spacing, stroke), + RectangularGizmoType::Down => get_rectangular_down_points(column_index, row_index, spacing, stroke), + RectangularGizmoType::Left => get_rectangular_left_points(column_index, row_index, spacing, stroke), } +} - pub fn get_closest_line(mouse_position: DVec2, column_index: u32, row_index: u32, spacing: DVec2, viewport: DAffine2, stroke_width: Option) -> Self { - let mut gizmo_type = GridSpacingGizmoType::Top; - let mut closest_distance = gizmo_type - .line(column_index, row_index, spacing, viewport, stroke_width) - .nearest(dvec2_to_point(mouse_position), 1e-6) - .distance_sq; +pub fn get_line_points_for_iso(gizmo: IsometricGizmoType, column_index: u32, row_index: u32, angles: DVec2, spacing: DVec2, stroke: Option) -> (DVec2, DVec2) { + match gizmo { + IsometricGizmoType::Top => get_isometric_top_points(column_index, row_index, angles, spacing, stroke), + IsometricGizmoType::Right => get_isometric_right_points(column_index, row_index, angles, spacing, stroke), + IsometricGizmoType::Down => get_isometric_down_points(column_index, row_index, angles, spacing, stroke), + IsometricGizmoType::Left => get_isometric_left_points(column_index, row_index, angles, spacing, stroke), + IsometricGizmoType::IsometricMiddleUp => get_isometric_middle_up_points(column_index, row_index, angles, spacing, stroke), + IsometricGizmoType::IsometricMiddleDown => get_isometric_middle_down_points(column_index, row_index, angles, spacing, stroke), + } +} - for t in Self::all() { - if matches!(t, GridSpacingGizmoType::Top) { - continue; - } - let line = t.line(column_index, row_index, spacing, viewport, stroke_width); - let nearest = line.nearest(dvec2_to_point(mouse_position), 1e-6); - if nearest.distance_sq < closest_distance { - gizmo_type = t; - closest_distance = nearest.distance_sq; - } +// Builds a Line after viewport transform +pub fn gizmo_line_from_points(p0: DVec2, p1: DVec2, viewport: DAffine2) -> Line { + convert_to_gizmo_line(viewport.transform_point2(p0), viewport.transform_point2(p1)) +} + +// pub fn gizmo_opposite_rect(g: RectangularGizmoType) -> RectangularGizmoType { +// match g { +// RectangularGizmoType::Top => RectangularGizmoType::Down, +// RectangularGizmoType::Down => RectangularGizmoType::Top, +// RectangularGizmoType::Right => RectangularGizmoType::Left, +// RectangularGizmoType::Left => RectangularGizmoType::Right, +// } +// } + +// pub fn gizmo_opposite_iso(g: IsometricGizmoType) -> IsometricGizmoType { +// match g { +// IsometricGizmoType::Top => IsometricGizmoType::Down, +// IsometricGizmoType::Down => IsometricGizmoType::Top, +// IsometricGizmoType::Right => IsometricGizmoType::Left, +// IsometricGizmoType::Left => IsometricGizmoType::Right, +// IsometricGizmoType::IsometricMiddleUp => IsometricGizmoType::IsometricMiddleDown, +// IsometricGizmoType::IsometricMiddleDown => IsometricGizmoType::IsometricMiddleUp, +// } +// } + +pub fn gizmo_new_spacing_rect(g: RectangularGizmoType, delta: f64, spacing: DVec2) -> DVec2 { + match g { + RectangularGizmoType::Top | RectangularGizmoType::Down => DVec2::new(spacing.x, spacing.y + delta), + RectangularGizmoType::Right | RectangularGizmoType::Left => DVec2::new(spacing.x + delta, spacing.y), + } +} + +pub fn gizmo_new_spacing_iso(g: IsometricGizmoType, delta: f64, spacing: DVec2) -> DVec2 { + match g { + IsometricGizmoType::Top | IsometricGizmoType::Down => DVec2::new(spacing.x, spacing.y + delta), + IsometricGizmoType::Right | IsometricGizmoType::Left => DVec2::new(spacing.x + delta, spacing.y), + IsometricGizmoType::IsometricMiddleUp | IsometricGizmoType::IsometricMiddleDown => { + // Adjust both? depends on your desired behavior + DVec2::new(spacing.x + delta, spacing.y + delta) } - gizmo_type } +} - pub fn line(&self, column_index: u32, row_index: u32, spacing: DVec2, viewport: DAffine2, stroke_width: Option) -> Line { - let (p0, p1) = self.get_line_points(column_index, row_index, spacing, stroke_width); - let gap = -5. * self.direction(spacing, viewport); +pub fn gizmo_direction_rect(g: RectangularGizmoType, spacing: DVec2, viewport: DAffine2) -> DVec2 { + match g { + RectangularGizmoType::Top => viewport.transform_vector2(DVec2::Y), + RectangularGizmoType::Down => -viewport.transform_vector2(DVec2::Y), + RectangularGizmoType::Right => viewport.transform_vector2(DVec2::X), + RectangularGizmoType::Left => -viewport.transform_vector2(-DVec2::X), + } +} - convert_to_gizmo_line(viewport.transform_point2(p0) + gap, viewport.transform_point2(p1) + gap) +pub fn gizmo_direction_iso(g: IsometricGizmoType, column_index: u32, row_index: u32, angles: DVec2, spacing: DVec2) -> DVec2 { + let (p1, p2) = get_line_points_for_iso(g, column_index, row_index, angles, spacing, None); + (p1 - p2).perp().normalize() +} + +pub fn gizmo_mouse_icon_rect(g: RectangularGizmoType) -> MouseCursorIcon { + match g { + RectangularGizmoType::Top | RectangularGizmoType::Down => MouseCursorIcon::NSResize, + RectangularGizmoType::Right | RectangularGizmoType::Left => MouseCursorIcon::EWResize, } +} - fn opposite(&self, grid_type: GridType, column_index: u32, row_index: u32, spacing: DVec2, angles: DVec2, viewport: DAffine2, stroke: Option) -> Line { - let opposite_gizmo_type = self.opposite_gizmo_type(); - opposite_gizmo_type.line(column_index, row_index, spacing, viewport, stroke) +pub fn gizmo_mouse_icon_iso(g: IsometricGizmoType) -> MouseCursorIcon { + match g { + IsometricGizmoType::Top | IsometricGizmoType::Down | IsometricGizmoType::IsometricMiddleUp | IsometricGizmoType::IsometricMiddleDown => MouseCursorIcon::NSResize, + IsometricGizmoType::Right | IsometricGizmoType::Left => MouseCursorIcon::EWResize, } +} - fn opposite_gizmo_type(&self) -> Self { - return match self { - Self::Top => Self::Down, - Self::Right => Self::Left, - Self::Down => Self::Top, - Self::Left => Self::Right, - Self::None => panic!("RowColumnGizmoType::None does not have opposite"), - }; +impl RectangularGizmoType { + pub fn all() -> [Self; 4] { + [Self::Top, Self::Right, Self::Down, Self::Left] } +} - fn new_spacing(&self, delta: f64, spacing: DVec2) -> DVec2 { - match self { - GridSpacingGizmoType::Top | GridSpacingGizmoType::Down => DVec2::new(spacing.x, spacing.y + delta), - GridSpacingGizmoType::Right | GridSpacingGizmoType::Left => DVec2::new(spacing.x + delta, spacing.y), - GridSpacingGizmoType::None => panic!("RowColumnGizmoType::None does not have a mouse_icon"), - } +impl IsometricGizmoType { + pub fn all() -> [Self; 6] { + [Self::Top, Self::Right, Self::Down, Self::Left, Self::IsometricMiddleUp, Self::IsometricMiddleDown] } +} - fn direction(&self, spacing: DVec2, viewport: DAffine2) -> DVec2 { +impl GridSpacingGizmoType { + pub fn line(&self, grid_type: GridType, column_index: u32, row_index: u32, angles: DVec2, spacing: DVec2, viewport: DAffine2, stroke_width: Option) -> Line { match self { - GridSpacingGizmoType::Top => calculate_rectangle_top_direction(spacing, viewport), - GridSpacingGizmoType::Down => -calculate_rectangle_top_direction(spacing, viewport), - GridSpacingGizmoType::Right => calculate_rectangle_side_direction(spacing, viewport), - GridSpacingGizmoType::Left => -calculate_rectangle_side_direction(spacing, viewport), - GridSpacingGizmoType::None => panic!("RowColumnGizmoType::None does not have a line"), + GridSpacingGizmoType::Rect(g) => { + let (p0, p1) = get_line_points_for_rect(*g, column_index, row_index, spacing, stroke_width); + gizmo_line_from_points(p0, p1, viewport) + } + GridSpacingGizmoType::Iso(g) => { + let (p0, p1) = get_line_points_for_iso(*g, column_index, row_index, angles, spacing, stroke_width); + gizmo_line_from_points(p0, p1, viewport) + } } } - fn mouse_icon(&self) -> MouseCursorIcon { - match self { - GridSpacingGizmoType::Top | GridSpacingGizmoType::Down => MouseCursorIcon::NSResize, - GridSpacingGizmoType::Right | GridSpacingGizmoType::Left => MouseCursorIcon::EWResize, - GridSpacingGizmoType::None => panic!("RowColumnGizmoType::None does not have a mouse_icon"), + pub fn get_closest_line(grid_type: GridType, mouse_position: DVec2, column_index: u32, row_index: u32, spacing: DVec2, angles: DVec2, viewport: DAffine2, stroke_width: Option) -> Self { + match grid_type { + GridType::Rectangular => Self::Rect(closest_line_rect(mouse_position, column_index, row_index, spacing, viewport, stroke_width)), + GridType::Isometric => Self::Iso(closest_line_iso(mouse_position, column_index, row_index, angles, spacing, viewport, stroke_width)), } } - pub fn all() -> [Self; 4] { - [Self::Top, Self::Right, Self::Down, Self::Left] + pub fn direction(&self, column_index: u32, row_index: u32, angles: DVec2, spacing: DVec2, viewport: DAffine2) -> DVec2 { + match &self { + GridSpacingGizmoType::Rect(g) => gizmo_direction_rect(*g, spacing, viewport), + GridSpacingGizmoType::Iso(g) => gizmo_direction_iso(*g, column_index, row_index, angles, spacing), + } } -} -fn get_rectangular_top_points(column_index: u32, row_index: u32, spacing: DVec2, stroke_width: Option) -> (DVec2, DVec2) { - let stroke_width = stroke_width.unwrap_or_default(); - let p0 = DVec2::new(column_index as f64 * spacing.x, row_index as f64 * spacing.y) + DVec2::new(stroke_width, stroke_width); - let p1 = p0 + DVec2::new(spacing.x - 2. * stroke_width, 0.); + fn new_spacing(&self, delta: f64, spacing: DVec2) -> DVec2 { + match &self { + GridSpacingGizmoType::Rect(g) => gizmo_new_spacing_rect(*g, delta, spacing), + GridSpacingGizmoType::Iso(g) => gizmo_new_spacing_iso(*g, delta, spacing), + } + } - (p0, p1) -} + fn mouse_icon(&self) -> MouseCursorIcon { + match self { + GridSpacingGizmoType::Rect(g) => gizmo_mouse_icon_rect(*g), + GridSpacingGizmoType::Iso(g) => gizmo_mouse_icon_iso(*g), + } + } -fn get_rectangular_right_points(column_index: u32, row_index: u32, spacing: DVec2, stroke_width: Option) -> (DVec2, DVec2) { - let stroke_width = stroke_width.unwrap_or_default(); - let p0 = DVec2::new((1 + column_index) as f64 * spacing.x, row_index as f64 * spacing.y) + DVec2::new(-stroke_width, stroke_width); - let p1 = p0 + DVec2::new(0., spacing.y - 2. * stroke_width); + pub fn transform_grid(&self, spacing_delta: DVec2, direction: DVec2, column_index: u32, row_index: u32) -> DAffine2 { + match self { + GridSpacingGizmoType::Rect(gizmo_type) => match gizmo_type { + RectangularGizmoType::Right => { + if column_index == 0 { + DAffine2::IDENTITY + } else { + DAffine2::from_translation(-spacing_delta * direction * column_index as f64) + } + } + RectangularGizmoType::Down => { + if row_index == 0 { + DAffine2::IDENTITY + } else { + DAffine2::from_translation(-spacing_delta * direction * row_index as f64) + } + } + RectangularGizmoType::Left => { + if column_index == 0 { + DAffine2::from_translation(spacing_delta * direction) + } else { + DAffine2::from_translation(spacing_delta * direction * (column_index + 1) as f64) + } + } + RectangularGizmoType::Top => { + if row_index == 0 { + DAffine2::from_translation(spacing_delta * direction) + } else { + DAffine2::from_translation(spacing_delta * direction * (row_index + 1) as f64) + } + } + }, - (p0, p1) + GridSpacingGizmoType::Iso(_) => { + // Placeholder: no transformation for now + DAffine2::IDENTITY + } + } + } } -fn get_rectangular_down_points(column_index: u32, row_index: u32, spacing: DVec2, stroke_width: Option) -> (DVec2, DVec2) { - let stroke_width = stroke_width.unwrap_or_default(); - - let p0 = DVec2::new(column_index as f64 * spacing.x, (1 + row_index) as f64 * spacing.y) + DVec2::new(stroke_width, -stroke_width); - let p1 = p0 + DVec2::new(spacing.x - 2. * stroke_width, 0.); - - (p0, p1) +fn closest_line_generic(mouse_position: DVec2, viewport: DAffine2, all_variants: &[T], get_line_points: impl Fn(T) -> (DVec2, DVec2)) -> T +where + T: Copy + PartialEq, +{ + let mut gizmo_type = all_variants[0]; + let mut closest_distance = { + let (p0, p1) = get_line_points(gizmo_type); + gizmo_line_from_points(p0, p1, viewport).nearest(dvec2_to_point(mouse_position), 1e-6).distance_sq + }; + + for &t in all_variants.iter().skip(1) { + let (p0, p1) = get_line_points(t); + let nearest = gizmo_line_from_points(p0, p1, viewport).nearest(dvec2_to_point(mouse_position), 1e-6); + if nearest.distance_sq < closest_distance { + gizmo_type = t; + closest_distance = nearest.distance_sq; + } + } + gizmo_type } -fn get_rectangular_left_points(column_index: u32, row_index: u32, spacing: DVec2, stroke_width: Option) -> (DVec2, DVec2) { - let stroke_width = stroke_width.unwrap_or_default(); - - let p0 = DVec2::new(column_index as f64 * spacing.x, row_index as f64 * spacing.y) + DVec2::new(stroke_width, stroke_width); - let p1 = p0 + DVec2::new(0., spacing.y - 2. * stroke_width); +pub fn closest_line_rect(mouse_position: DVec2, column_index: u32, row_index: u32, spacing: DVec2, viewport: DAffine2, stroke_width: Option) -> RectangularGizmoType { + closest_line_generic(mouse_position, viewport, &RectangularGizmoType::all(), |t| { + get_line_points_for_rect(t, column_index, row_index, spacing, stroke_width) + }) +} - (p0, p1) +pub fn closest_line_iso(mouse_position: DVec2, column_index: u32, row_index: u32, angles: DVec2, spacing: DVec2, viewport: DAffine2, stroke_width: Option) -> IsometricGizmoType { + closest_line_generic(mouse_position, viewport, &IsometricGizmoType::all(), |t| { + get_line_points_for_iso(t, column_index, row_index, angles, spacing, stroke_width) + }) } From b24315e16209c583a4ecc72a673cc61b3dec3c28 Mon Sep 17 00:00:00 2001 From: 0SlowPoke0 Date: Mon, 18 Aug 2025 11:58:03 +0530 Subject: [PATCH 8/9] impl spacing gizmos for isometric grid --- .../shape_gizmos/grid_row_columns_gizmo.rs | 5 - .../shape_gizmos/grid_spacing_gizmos.rs | 486 ++++++++++-------- 2 files changed, 259 insertions(+), 232 deletions(-) diff --git a/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/grid_row_columns_gizmo.rs b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/grid_row_columns_gizmo.rs index 3ec7f66e5e..0ed69f66ce 100644 --- a/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/grid_row_columns_gizmo.rs +++ b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/grid_row_columns_gizmo.rs @@ -387,11 +387,6 @@ impl RowColumnGizmoType { convert_to_gizmo_line(viewport.transform_point2(p0) + gap, viewport.transform_point2(p1) + gap) } - fn opposite(&self, grid_type: GridType, columns: u32, rows: u32, spacing: DVec2, angles: DVec2, viewport: DAffine2) -> Line { - let opposite_gizmo_type = self.opposite_gizmo_type(); - opposite_gizmo_type.line(grid_type, columns, rows, spacing, angles, viewport) - } - fn opposite_gizmo_type(&self) -> Self { return match self { Self::Top => Self::Down, diff --git a/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/grid_spacing_gizmos.rs b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/grid_spacing_gizmos.rs index 6c97333e0f..16002c5274 100644 --- a/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/grid_spacing_gizmos.rs +++ b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/grid_spacing_gizmos.rs @@ -1,4 +1,4 @@ -use crate::consts::{GRID_COLUMNS_INDEX, GRID_ROW_COLUMN_GIZMO_OFFSET, GRID_ROW_INDEX, GRID_SPACING_INDEX}; +use crate::consts::{GRID_ANGLE_INDEX, GRID_SPACING_INDEX}; use crate::messages::frontend::utility_types::MouseCursorIcon; use crate::messages::message::Message; use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn; @@ -7,16 +7,14 @@ use crate::messages::portfolio::document::utility_types::document_metadata::Laye use crate::messages::portfolio::document::utility_types::network_interface::InputConnector; use crate::messages::prelude::{DocumentMessageHandler, FrontendMessage, InputPreprocessorMessageHandler, NodeGraphMessage}; use crate::messages::prelude::{GraphOperationMessage, Responses}; -use crate::messages::tool::common_functionality::gizmos::shape_gizmos::grid_row_columns_gizmo::{ - calculate_rectangle_side_direction, calculate_rectangle_top_direction, convert_to_gizmo_line, get_viewport_grid_spacing, -}; +use crate::messages::tool::common_functionality::gizmos::shape_gizmos::grid_row_columns_gizmo::convert_to_gizmo_line; use crate::messages::tool::common_functionality::graph_modification_utils; use crate::messages::tool::common_functionality::shape_editor::ShapeState; use crate::messages::tool::common_functionality::shapes::shape_utility::extract_grid_parameters; use glam::{DAffine2, DVec2}; use graph_craft::document::NodeInput; use graph_craft::document::value::TaggedValue; -use graphene_std::renderer::Quad; +use graphene_std::uuid::NodeId; use graphene_std::vector::misc::{GridType, dvec2_to_point, get_line_endpoints}; use kurbo::{Line, ParamCurveNearest, Rect, Shape, Triangle}; use std::collections::VecDeque; @@ -36,6 +34,7 @@ pub struct GridSpacingGizmo { column_index: u32, row_index: u32, initial_spacing: DVec2, + angles: DVec2, gizmo_type: Option, } @@ -64,14 +63,13 @@ impl GridSpacingGizmo { let stroke_width = graph_modification_utils::get_stroke_width(layer, &document.network_interface); let viewport = document.metadata().transform_to_viewport(layer); if let Some((col, row)) = check_if_over_gizmo(grid_type, columns, rows, spacing, angles, mouse_position, viewport) { - log::info!("col {:?} , row {:?}", col, row); self.layer = Some(layer); self.column_index = col; self.row_index = row; self.initial_spacing = spacing; + self.angles = angles; self.update_state(GridSpacingGizmoState::Hover); let closest_gizmo = GridSpacingGizmoType::get_closest_line(grid_type, mouse_position, col, row, spacing, angles, viewport, stroke_width); - log::info!("gizmo type {:?}", closest_gizmo); responses.add(FrontendMessage::UpdateMouseCursor { cursor: closest_gizmo.mouse_icon() }); self.gizmo_type = Some(closest_gizmo); } @@ -79,7 +77,7 @@ impl GridSpacingGizmo { pub fn overlays(&self, document: &DocumentMessageHandler, layer: Option, _shape_editor: &mut &mut ShapeState, _mouse_position: DVec2, overlay_context: &mut OverlayContext) { let Some(layer) = layer.or(self.layer) else { return }; - let Some((grid_type, spacing, columns, rows, angles)) = extract_grid_parameters(layer, document) else { + let Some((_grid_type, spacing, _columns, _rows, angles)) = extract_grid_parameters(layer, document) else { return; }; let viewport = document.metadata().transform_to_viewport(layer); @@ -87,34 +85,54 @@ impl GridSpacingGizmo { match self.gizmo_state { GridSpacingGizmoState::Inactive => {} - GridSpacingGizmoState::Hover | GridSpacingGizmoState::Dragging => match grid_type { - GridType::Rectangular => { - if let Some(gizmo_type) = &self.gizmo_type { - let line = gizmo_type.line(grid_type, self.column_index, self.row_index, angles, spacing, viewport, stroke_width); - let (p0, p1) = get_line_endpoints(line); - overlay_context.dashed_line(p0, p1, None, None, Some(5.), Some(5.), Some(0.5)); - } - } - GridType::Isometric => { - if let Some(gizmo_type) = &self.gizmo_type { - let line = gizmo_type.line(grid_type, self.column_index, self.row_index, angles, spacing, viewport, stroke_width); - let (p0, p1) = get_line_endpoints(line); - overlay_context.dashed_line(p0, p1, None, None, Some(5.), Some(5.), Some(0.5)); - } + GridSpacingGizmoState::Hover | GridSpacingGizmoState::Dragging => { + if let Some(gizmo_type) = &self.gizmo_type { + let line = gizmo_type.line(self.column_index, self.row_index, angles, spacing, viewport, stroke_width); + let (p0, p1) = get_line_endpoints(line); + overlay_context.dashed_line(p0, p1, None, None, Some(5.), Some(5.), Some(0.5)); } - }, + } } } + pub fn update_rectangle_grid( + &self, + node_id: NodeId, + layer: LayerNodeIdentifier, + gizmo_type: &GridSpacingGizmoType, + current_spacing: DVec2, + angles: DVec2, + delta: f64, + viewport: DAffine2, + responses: &mut VecDeque, + ) { + let direction = gizmo_type.direction(self.column_index, self.row_index, angles, self.initial_spacing, viewport); + let new_spacing = gizmo_type.new_spacing(delta, self.initial_spacing); + let spacing_delta = new_spacing - current_spacing; + + responses.add(NodeGraphMessage::SetInput { + input_connector: InputConnector::node(node_id, GRID_SPACING_INDEX), + input: NodeInput::value(TaggedValue::DVec2(new_spacing), false), + }); + + let transform = gizmo_type.transform_grid(spacing_delta, direction, self.column_index, self.row_index); + + responses.add(GraphOperationMessage::TransformChange { + layer, + transform, + transform_in: TransformIn::Viewport, + skip_rerender: false, + }); + } + pub fn update(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque, drag_start: DVec2) { let Some(layer) = self.layer else { return }; let viewport = document.metadata().transform_to_viewport(layer); - let Some((grid_type, spacing, columns, rows, angles)) = extract_grid_parameters(layer, document) else { + let Some((grid_type, spacing, _columns, _rows, angles)) = extract_grid_parameters(layer, document) else { return; }; - let stroke_width = graph_modification_utils::get_stroke_width(layer, &document.network_interface); let Some(gizmo_type) = &self.gizmo_type else { return }; let direction = gizmo_type.direction(self.column_index, self.row_index, angles, self.initial_spacing, viewport); let delta_vector = input.mouse.position - drag_start; @@ -125,32 +143,176 @@ impl GridSpacingGizmo { return; }; - let new_spacing = gizmo_type.new_spacing(delta, self.initial_spacing); - let spacing_delta = new_spacing - spacing; + if grid_type == GridType::Rectangular { + self.update_rectangle_grid(node_id, layer, gizmo_type, spacing, angles, delta, viewport, responses); + } else { + match gizmo_type { + GridSpacingGizmoType::Rect(_) => unreachable!(), + GridSpacingGizmoType::Iso(h) => { + if *h == IsometricGizmoType::Right || *h == IsometricGizmoType::Left { + self.update_isometric_x_spacing(layer, delta_vector, node_id, spacing, angles, gizmo_type, h, viewport, responses); + } else { + self.update_isometric_y_spacing(layer, delta_vector, node_id, spacing, angles, gizmo_type, viewport, responses); + } + } + }; + } + responses.add(NodeGraphMessage::RunDocumentGraph); + } + + fn update_isometric_y_spacing( + &self, + layer: LayerNodeIdentifier, + delta: DVec2, + node_id: NodeId, + spacing: DVec2, + angles: DVec2, + gizmo_type: &GridSpacingGizmoType, + viewport: DAffine2, + responses: &mut VecDeque, + ) { + let (a, b) = self.angles.into(); + let (tan_a_old, tan_b_old) = (a.to_radians().tan(), b.to_radians().tan()); + let direction = gizmo_type.direction(self.column_index, self.row_index, self.angles, spacing, viewport); + + let ((old_prev_row, old_prev_col), _sign) = match gizmo_type { + GridSpacingGizmoType::Rect(_) => unreachable!(), + GridSpacingGizmoType::Iso(h) => (h.old_row_col_index(self.row_index, self.column_index), h.delta_sign()), + }; + let projection = viewport.inverse().transform_vector2(delta.project_onto(direction)); + let a = (self.column_index + 1).div_ceil(2) as f64; + let b = ((self.column_index + 1) / 2) as f64; + + let p = self.initial_spacing.y / (tan_a_old + tan_b_old); // spacing_x, must stay constant + + let y = self.row_index as f64; + let delta = projection.y; + + // 1) Put the whole vertical move into y-spacing (for y>0): + let new_y_spacing = if y > 0.0 { + self.initial_spacing.y + delta / y + } else { + self.initial_spacing.y // y==0 handled below in edge cases + }; + + // 2) S' = sum of new tans required to keep spacing_x (=p) constant: + let s_prime = new_y_spacing / p; + + // 3) R = b*tb - a*ta (OLD values) + let r = b * tan_b_old - a * tan_a_old; + + // 4) Solve for new tangents: + let denom = a + b; // safe when col > 0 + let tan_a_new = (b * s_prime - r) / denom; + let tan_b_new = (r + a * s_prime) / denom; + + // 5) Convert to degrees and set: + let angle_a_new_deg = tan_a_new.atan().to_degrees(); + let angle_b_new_deg = tan_b_new.atan().to_degrees(); responses.add(NodeGraphMessage::SetInput { - input_connector: InputConnector::node(node_id, GRID_SPACING_INDEX), - input: NodeInput::value(TaggedValue::DVec2(new_spacing), false), + input_connector: InputConnector::node(node_id, GRID_ANGLE_INDEX), + input: NodeInput::value(TaggedValue::DVec2((angle_a_new_deg, angle_b_new_deg).into()), false), }); - let transform = self.gizmo_type.as_ref().unwrap().transform_grid(spacing_delta, direction, self.column_index, self.row_index); + let old_position = isometric_point_position(old_prev_row, old_prev_col, spacing, angles); + let new_position = isometric_point_position(old_prev_row, old_prev_col, (new_y_spacing, new_y_spacing).into(), (angle_a_new_deg, angle_b_new_deg).into()); responses.add(GraphOperationMessage::TransformChange { layer, - transform, + transform: DAffine2::from_translation(-DVec2::new(0., viewport.transform_vector2(new_position - old_position).y)), transform_in: TransformIn::Viewport, skip_rerender: false, }); - log::info!("{:?}", (self.row_index, self.column_index)); - responses.add(NodeGraphMessage::RunDocumentGraph); + responses.add(NodeGraphMessage::SetInput { + input_connector: InputConnector::node(node_id, GRID_SPACING_INDEX), + input: NodeInput::value(TaggedValue::DVec2((new_y_spacing, new_y_spacing).into()), false), + }); + } + + fn update_isometric_x_spacing( + &self, + layer: LayerNodeIdentifier, + delta: DVec2, + node_id: NodeId, + spacing: DVec2, + angles: DVec2, + gizmo_type: &GridSpacingGizmoType, + iso_gizmo_type: &IsometricGizmoType, + viewport: DAffine2, + responses: &mut VecDeque, + ) { + let (row, column) = if *iso_gizmo_type == IsometricGizmoType::Right { + (self.row_index + 1, self.column_index + 1) + } else { + (self.row_index, self.column_index) + }; + + let (a, b) = self.angles.into(); + let (tan_a_old, tan_b_old) = (a.to_radians().tan(), b.to_radians().tan()); + let direction = gizmo_type.direction(column, row, self.angles, spacing, viewport); + + let ((old_prev_row, old_prev_col), sign) = (iso_gizmo_type.old_row_col_index(self.row_index, self.column_index), iso_gizmo_type.delta_sign()); + + let projection = viewport.inverse().transform_vector2(sign * delta.project_onto(direction)); + let old_spacing_x = spacing.y / (tan_a_old + tan_b_old); + + let a_steps = ((column) as f64 / 2.0).ceil(); + let b_steps = ((column) / 2) as f64; + + let old_offset_y_fraction = b_steps * tan_b_old - a_steps * tan_a_old; + + let old_x_pos = old_spacing_x * (column) as f64; + let old_y_pos = spacing.y * (row) as f64 + old_offset_y_fraction * old_spacing_x; + + // --- Step 1: Apply delta to get new position --- + let new_x_pos = old_x_pos + projection.x; + let new_y_pos = old_y_pos + projection.y; + + // --- Step 2: New spacing.x from horizontal position --- + let spacing_x_new = if (column) != 0 { + new_x_pos / (column) as f64 + } else { + old_spacing_x // Can't deduce from vertical column + }; + + // --- Step 3: Sum of tangents --- + let sum_tan = spacing.y / spacing_x_new; - // if self.initial_dimension() as i32 + dimensions_to_add < 1 { - // self.initial_mouse_start = Some(input.mouse.position); - // self.gizmo_type = self.gizmo_type.opposite_gizmo_type(); - // self.initial_rows = 1; - // self.initial_columns = 1; - // } + // --- Step 4: RHS from vertical position --- + let rhs = (new_y_pos - spacing.y * row as f64) / spacing_x_new; + + // --- Step 5: Difference of tangents --- + let denom = b_steps + a_steps; + let diff_tan = if denom.abs() > f64::EPSILON { (2.0 * rhs - (b_steps - a_steps) * sum_tan) / denom } else { 0.0 }; + + // --- Step 6: Compute tangents and angles --- + let tan_a_new = (sum_tan - diff_tan) / 2.0; + let tan_b_new = (sum_tan + diff_tan) / 2.0; + + let new_angles = DVec2::new(tan_a_new.atan().to_degrees(), tan_b_new.atan().to_degrees()); + + responses.add(NodeGraphMessage::SetInput { + input_connector: InputConnector::node(node_id, GRID_ANGLE_INDEX), + input: NodeInput::value(TaggedValue::DVec2(new_angles), false), + }); + + let new_point = isometric_point_position(old_prev_row, old_prev_col, spacing, new_angles); + let old_point = isometric_point_position(old_prev_row, old_prev_col, spacing, angles); + + let transform = self + .gizmo_type + .as_ref() + .unwrap() + .transform_grid(viewport.transform_vector2(new_point - old_point), direction, self.column_index, self.row_index); + + responses.add(GraphOperationMessage::TransformChange { + layer, + transform, + transform_in: TransformIn::Viewport, + skip_rerender: false, + }); } } @@ -196,116 +358,6 @@ fn check_if_over_gizmo(grid_type: GridType, columns: u32, rows: u32, spacing: DV None } -// #[derive(Clone, Debug, Default, PartialEq)] -// pub enum GridSpacingGizmoType { -// #[default] -// None, -// Top, -// Down, -// Left, -// Right, -// IsometricMiddleUp, -// IsometricMiddleDown, -// } - -// impl GridSpacingGizmoType { -// pub fn get_line_points(&self, grid_type: GridType, column_index: u32, row_index: u32, angles: DVec2, spacing: DVec2, stroke: Option) -> (DVec2, DVec2) { -// match grid_type { -// GridType::Rectangular => match self { -// Self::Top => get_rectangular_top_points(column_index, row_index, spacing, stroke), -// Self::Right => get_rectangular_right_points(column_index, row_index, spacing, stroke), -// Self::Down => get_rectangular_down_points(column_index, row_index, spacing, stroke), -// Self::Left => get_rectangular_left_points(column_index, row_index, spacing, stroke), -// Self::None => panic!("RowColumnGizmoType::None does not have line points"), -// }, -// GridType::Isometric => match self { -// Self::Top => get_isometric_top_points(column_index, row_index, angles, spacing, stroke), -// Self::Right => get_isometric_right_points(column_index, row_index, angles, spacing, stroke), -// Self::Down => get_isometric_down_points(column_index, row_index, angles, spacing, stroke), -// Self::Left => get_isometric_left_points(column_index, row_index, angles, spacing, stroke), -// Self::None => panic!("RowColumnGizmoType::None does not have line points"), -// }, -// } -// } - -// pub fn get_closest_line(mouse_position: DVec2, grid_type: GridType, column_index: u32, row_index: u32, angles: DVec2, spacing: DVec2, viewport: DAffine2, stroke_width: Option) -> Self { -// let mut gizmo_type = GridSpacingGizmoType::Top; -// let mut closest_distance = gizmo_type -// .line(grid_type, column_index, row_index, angles, spacing, viewport, stroke_width) -// .nearest(dvec2_to_point(mouse_position), 1e-6) -// .distance_sq; - -// for t in Self::all() { -// if matches!(t, GridSpacingGizmoType::Top) { -// continue; -// } -// let line = t.line(grid_type, column_index, row_index, angles, spacing, viewport, stroke_width); -// let nearest = line.nearest(dvec2_to_point(mouse_position), 1e-6); -// if nearest.distance_sq < closest_distance { -// gizmo_type = t; -// closest_distance = nearest.distance_sq; -// } -// } -// gizmo_type -// } - -// pub fn line(&self, grid_type: GridType, column_index: u32, row_index: u32, angles: DVec2, spacing: DVec2, viewport: DAffine2, stroke_width: Option) -> Line { -// let (p0, p1) = self.get_line_points(grid_type, column_index, row_index, angles, spacing, stroke_width); -// // let gap = 2. * stroke_width.unwrap_or(3.) * (p1 - p0).perp().normalize(); - -// convert_to_gizmo_line(viewport.transform_point2(p0), viewport.transform_point2(p1)) -// } - -// fn opposite_gizmo_type(&self) -> Self { -// return match self { -// Self::Top => Self::Down, -// Self::Right => Self::Left, -// Self::Down => Self::Top, -// Self::Left => Self::Right, -// Self::None => panic!("RowColumnGizmoType::None does not have opposite"), -// }; -// } - -// fn new_spacing(&self, delta: f64, spacing: DVec2) -> DVec2 { -// match self { -// GridSpacingGizmoType::Top | GridSpacingGizmoType::Down => DVec2::new(spacing.x, spacing.y + delta), -// GridSpacingGizmoType::Right | GridSpacingGizmoType::Left => DVec2::new(spacing.x + delta, spacing.y), -// GridSpacingGizmoType::None => panic!("RowColumnGizmoType::None does not have a mouse_icon"), -// } -// } - -// fn direction(&self, grid_type: GridType, column_index: u32, row_index: u32, angles: DVec2, spacing: DVec2, viewport: DAffine2) -> DVec2 { -// match grid_type { -// GridType::Rectangular => match self { -// GridSpacingGizmoType::Top => viewport.transform_vector2(DVec2::Y), -// GridSpacingGizmoType::Down => -viewport.transform_vector2(DVec2::Y), -// GridSpacingGizmoType::Right => calculate_rectangle_side_direction(spacing, viewport), -// GridSpacingGizmoType::Left => -calculate_rectangle_side_direction(spacing, viewport), -// GridSpacingGizmoType::None => panic!("RowColumnGizmoType::None does not have a line"), -// }, -// GridType::Isometric => match &self { -// GridSpacingGizmoType::Top | GridSpacingGizmoType::Down | GridSpacingGizmoType::Left | GridSpacingGizmoType::Right => { -// let (p1, p2) = self.get_line_points(grid_type, column_index, row_index, angles, spacing, None); -// (p1 - p2).perp().normalize() -// } -// GridSpacingGizmoType::None => panic!("RowColumnGizmoType::None does not have a line"), -// }, -// } -// } - -// fn mouse_icon(&self) -> MouseCursorIcon { -// match self { -// GridSpacingGizmoType::Top | GridSpacingGizmoType::Down => MouseCursorIcon::NSResize, -// GridSpacingGizmoType::Right | GridSpacingGizmoType::Left => MouseCursorIcon::EWResize, -// GridSpacingGizmoType::None => panic!("RowColumnGizmoType::None does not have a mouse_icon"), -// } -// } - -// pub fn all() -> [Self; 4] { -// [Self::Top, Self::Right, Self::Down, Self::Left] -// } -// } - fn get_rectangular_top_points(column_index: u32, row_index: u32, spacing: DVec2, stroke_width: Option) -> (DVec2, DVec2) { let stroke_width = stroke_width.unwrap_or_default(); let p0 = DVec2::new(column_index as f64 * spacing.x, row_index as f64 * spacing.y) + DVec2::new(stroke_width, stroke_width); @@ -354,19 +406,26 @@ fn isometric_point_position(row: u32, col: u32, spacing: DVec2, angles: DVec2) - DVec2::new(spacing.x * col as f64, spacing.y * row as f64 + offset_y_fraction * spacing.x) } +fn apply_gizmo_padding_and_offset(x0: DVec2, x1: DVec2, stroke_width: f64, inward: bool) -> (DVec2, DVec2) { + let Some(direction) = (x1 - x0).try_normalize() else { + // No valid direction, return original points unchanged + return (x0, x1); + }; + + // Apply normal padding and offset logic + let padding = (x1 - x0).length() * 0.1 * direction; + let push_out = calculate_gap_vector(direction, stroke_width); + let push_out_vector = if inward { -push_out } else { push_out }; + + (x0 + push_out_vector + padding, x1 + push_out_vector - padding) +} + fn get_isometric_top_points(column_index: u32, row_index: u32, angles: DVec2, spacing: DVec2, stroke_width: Option) -> (DVec2, DVec2) { let stroke_width = stroke_width.unwrap_or_default(); let x0 = isometric_point_position(row_index, column_index, spacing, angles); let x1 = isometric_point_position(row_index, column_index + 1, spacing, angles); - let direction = (x1 - x0).normalize(); - - // in the direction of edge - let padding = (x1 - x0).length() * 0.1 * direction; - // perpendicular to the edge - let push_out = calculate_gap_vector(direction, stroke_width); - - (x0 + push_out + padding, x1 + push_out - padding) + apply_gizmo_padding_and_offset(x0, x1, stroke_width, false) // push_out outward } fn get_isometric_right_points(column_index: u32, row_index: u32, angles: DVec2, spacing: DVec2, stroke_width: Option) -> (DVec2, DVec2) { @@ -374,14 +433,7 @@ fn get_isometric_right_points(column_index: u32, row_index: u32, angles: DVec2, let x0 = isometric_point_position(row_index, column_index + 1, spacing, angles); let x1 = isometric_point_position(row_index + 1, column_index + 1, spacing, angles); - let direction = (x1 - x0).normalize(); - - // in the direction of edge - let padding = (x1 - x0).length() * 0.1 * direction; - // perpendicular to the edge - let push_out = calculate_gap_vector(direction, stroke_width); - - (x0 + push_out + padding, x1 + push_out - padding) + apply_gizmo_padding_and_offset(x0, x1, stroke_width, false) // push_out outward } fn get_isometric_down_points(column_index: u32, row_index: u32, angles: DVec2, spacing: DVec2, stroke_width: Option) -> (DVec2, DVec2) { @@ -389,14 +441,7 @@ fn get_isometric_down_points(column_index: u32, row_index: u32, angles: DVec2, s let x0 = isometric_point_position(row_index + 1, column_index, spacing, angles); let x1 = isometric_point_position(row_index + 1, column_index + 1, spacing, angles); - let direction = (x1 - x0).normalize(); - - // in the direction of edge - let padding = (x1 - x0).length() * 0.1 * direction; - // perpendicular to the edge - let push_out = calculate_gap_vector(direction, stroke_width); - - (x0 - push_out + padding, x1 - push_out - padding) + apply_gizmo_padding_and_offset(x0, x1, stroke_width, true) // push_out inward } fn get_isometric_left_points(column_index: u32, row_index: u32, angles: DVec2, spacing: DVec2, stroke_width: Option) -> (DVec2, DVec2) { @@ -404,14 +449,7 @@ fn get_isometric_left_points(column_index: u32, row_index: u32, angles: DVec2, s let x0 = isometric_point_position(row_index, column_index, spacing, angles); let x1 = isometric_point_position(row_index + 1, column_index, spacing, angles); - let direction = (x1 - x0).normalize(); - - // in the direction of edge - let padding = (x1 - x0).length() * 0.1 * direction; - // perpendicular to the edge - let push_out = calculate_gap_vector(direction, stroke_width); - - (x0 - push_out + padding, x1 - push_out - padding) + apply_gizmo_padding_and_offset(x0, x1, stroke_width, true) // push_out inward } fn get_isometric_middle_up_points(column_index: u32, row_index: u32, angles: DVec2, spacing: DVec2, stroke_width: Option) -> (DVec2, DVec2) { @@ -429,14 +467,7 @@ fn get_isometric_middle_up_points(column_index: u32, row_index: u32, angles: DVe isometric_point_position(row_index, column_index + 1, spacing, angles), ) }; - let direction = (x1 - x0).normalize(); - - // in the direction of edge - let padding = (x1 - x0).length() * 0.1 * direction; - // perpendicular to the edge - let push_out = calculate_gap_vector(direction, stroke_width); - - (x0 - push_out + padding, x1 - push_out - padding) + apply_gizmo_padding_and_offset(x0, x1, stroke_width, true) // push_out inward } fn get_isometric_middle_down_points(column_index: u32, row_index: u32, angles: DVec2, spacing: DVec2, stroke_width: Option) -> (DVec2, DVec2) { @@ -454,18 +485,12 @@ fn get_isometric_middle_down_points(column_index: u32, row_index: u32, angles: D isometric_point_position(row_index, column_index + 1, spacing, angles), ) }; - let direction = (x1 - x0).normalize(); - // in the direction of edge - let padding = (x1 - x0).length() * 0.1 * direction; - // perpendicular to the edge - let push_out = calculate_gap_vector(direction, stroke_width); - - (x0 + push_out + padding, x1 + push_out - padding) + apply_gizmo_padding_and_offset(x0, x1, stroke_width, false) // push_out inward } fn calculate_gap_vector(direction: DVec2, stroke_width: f64) -> DVec2 { let perp = direction.perp().normalize(); - (stroke_width + 0.5) * perp + (stroke_width + 1.) * perp } #[derive(Clone, Copy, Debug, Default, PartialEq)] @@ -519,26 +544,6 @@ pub fn gizmo_line_from_points(p0: DVec2, p1: DVec2, viewport: DAffine2) -> Line convert_to_gizmo_line(viewport.transform_point2(p0), viewport.transform_point2(p1)) } -// pub fn gizmo_opposite_rect(g: RectangularGizmoType) -> RectangularGizmoType { -// match g { -// RectangularGizmoType::Top => RectangularGizmoType::Down, -// RectangularGizmoType::Down => RectangularGizmoType::Top, -// RectangularGizmoType::Right => RectangularGizmoType::Left, -// RectangularGizmoType::Left => RectangularGizmoType::Right, -// } -// } - -// pub fn gizmo_opposite_iso(g: IsometricGizmoType) -> IsometricGizmoType { -// match g { -// IsometricGizmoType::Top => IsometricGizmoType::Down, -// IsometricGizmoType::Down => IsometricGizmoType::Top, -// IsometricGizmoType::Right => IsometricGizmoType::Left, -// IsometricGizmoType::Left => IsometricGizmoType::Right, -// IsometricGizmoType::IsometricMiddleUp => IsometricGizmoType::IsometricMiddleDown, -// IsometricGizmoType::IsometricMiddleDown => IsometricGizmoType::IsometricMiddleUp, -// } -// } - pub fn gizmo_new_spacing_rect(g: RectangularGizmoType, delta: f64, spacing: DVec2) -> DVec2 { match g { RectangularGizmoType::Top | RectangularGizmoType::Down => DVec2::new(spacing.x, spacing.y + delta), @@ -550,10 +555,7 @@ pub fn gizmo_new_spacing_iso(g: IsometricGizmoType, delta: f64, spacing: DVec2) match g { IsometricGizmoType::Top | IsometricGizmoType::Down => DVec2::new(spacing.x, spacing.y + delta), IsometricGizmoType::Right | IsometricGizmoType::Left => DVec2::new(spacing.x + delta, spacing.y), - IsometricGizmoType::IsometricMiddleUp | IsometricGizmoType::IsometricMiddleDown => { - // Adjust both? depends on your desired behavior - DVec2::new(spacing.x + delta, spacing.y + delta) - } + IsometricGizmoType::IsometricMiddleUp | IsometricGizmoType::IsometricMiddleDown => DVec2::new(spacing.x + delta, spacing.y + delta), } } @@ -568,7 +570,7 @@ pub fn gizmo_direction_rect(g: RectangularGizmoType, spacing: DVec2, viewport: D pub fn gizmo_direction_iso(g: IsometricGizmoType, column_index: u32, row_index: u32, angles: DVec2, spacing: DVec2) -> DVec2 { let (p1, p2) = get_line_points_for_iso(g, column_index, row_index, angles, spacing, None); - (p1 - p2).perp().normalize() + (p1 - p2).perp().try_normalize().unwrap_or(DVec2::X) } pub fn gizmo_mouse_icon_rect(g: RectangularGizmoType) -> MouseCursorIcon { @@ -595,10 +597,40 @@ impl IsometricGizmoType { pub fn all() -> [Self; 6] { [Self::Top, Self::Right, Self::Down, Self::Left, Self::IsometricMiddleUp, Self::IsometricMiddleDown] } + + pub fn old_row_col_index(&self, row_index: u32, column_index: u32) -> (u32, u32) { + match self { + IsometricGizmoType::Right => (row_index, column_index), + IsometricGizmoType::Left => (row_index, column_index + 1), + IsometricGizmoType::Down => { + if column_index % 2 == 0 { + (row_index, column_index) + } else { + (row_index, column_index + 1) + } + } + IsometricGizmoType::Top => (row_index, column_index), + IsometricGizmoType::IsometricMiddleUp | IsometricGizmoType::IsometricMiddleDown => { + if column_index % 2 == 0 { + (row_index - 1, column_index) + } else { + (row_index, column_index) + } + } + } + } + + pub fn delta_sign(&self) -> f64 { + match self { + IsometricGizmoType::Right => 1., + IsometricGizmoType::Left => -1., + _ => 1., + } + } } impl GridSpacingGizmoType { - pub fn line(&self, grid_type: GridType, column_index: u32, row_index: u32, angles: DVec2, spacing: DVec2, viewport: DAffine2, stroke_width: Option) -> Line { + pub fn line(&self, column_index: u32, row_index: u32, angles: DVec2, spacing: DVec2, viewport: DAffine2, stroke_width: Option) -> Line { match self { GridSpacingGizmoType::Rect(g) => { let (p0, p1) = get_line_points_for_rect(*g, column_index, row_index, spacing, stroke_width); @@ -672,10 +704,10 @@ impl GridSpacingGizmoType { } }, - GridSpacingGizmoType::Iso(_) => { - // Placeholder: no transformation for now - DAffine2::IDENTITY - } + GridSpacingGizmoType::Iso(gizmo_type) => match gizmo_type { + IsometricGizmoType::Right | IsometricGizmoType::Left => DAffine2::from_translation(-spacing_delta), + _ => DAffine2::IDENTITY, + }, // Placeholder: no transformation for now } } } From 0679a4318df9f2a0a7b79ec81e4720f083dbeca8 Mon Sep 17 00:00:00 2001 From: 0SlowPoke0 Date: Wed, 20 Aug 2025 02:34:20 +0530 Subject: [PATCH 9/9] fixed crash when index 0 --- .../shape_gizmos/grid_spacing_gizmos.rs | 45 ++++++++++--------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/grid_spacing_gizmos.rs b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/grid_spacing_gizmos.rs index 16002c5274..e924b61824 100644 --- a/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/grid_spacing_gizmos.rs +++ b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/grid_spacing_gizmos.rs @@ -175,11 +175,11 @@ impl GridSpacingGizmo { let (tan_a_old, tan_b_old) = (a.to_radians().tan(), b.to_radians().tan()); let direction = gizmo_type.direction(self.column_index, self.row_index, self.angles, spacing, viewport); - let ((old_prev_row, old_prev_col), _sign) = match gizmo_type { + let ((old_prev_row, old_prev_col), sign) = match gizmo_type { GridSpacingGizmoType::Rect(_) => unreachable!(), GridSpacingGizmoType::Iso(h) => (h.old_row_col_index(self.row_index, self.column_index), h.delta_sign()), }; - let projection = viewport.inverse().transform_vector2(delta.project_onto(direction)); + let projection = viewport.inverse().transform_vector2(sign * delta.project_onto(direction)); let a = (self.column_index + 1).div_ceil(2) as f64; let b = ((self.column_index + 1) / 2) as f64; @@ -190,9 +190,9 @@ impl GridSpacingGizmo { // 1) Put the whole vertical move into y-spacing (for y>0): let new_y_spacing = if y > 0.0 { - self.initial_spacing.y + delta / y + (self.initial_spacing.y + delta / y).abs() } else { - self.initial_spacing.y // y==0 handled below in edge cases + (self.initial_spacing.y + delta).abs() }; // 2) S' = sum of new tans required to keep spacing_x (=p) constant: @@ -253,8 +253,8 @@ impl GridSpacingGizmo { let (tan_a_old, tan_b_old) = (a.to_radians().tan(), b.to_radians().tan()); let direction = gizmo_type.direction(column, row, self.angles, spacing, viewport); - let ((old_prev_row, old_prev_col), sign) = (iso_gizmo_type.old_row_col_index(self.row_index, self.column_index), iso_gizmo_type.delta_sign()); - + let (old_prev_row, old_prev_col) = iso_gizmo_type.old_row_col_index(self.row_index, self.column_index); + let sign = if *iso_gizmo_type == IsometricGizmoType::Left && column == 0 { -1. } else { 1. }; let projection = viewport.inverse().transform_vector2(sign * delta.project_onto(direction)); let old_spacing_x = spacing.y / (tan_a_old + tan_b_old); @@ -274,7 +274,7 @@ impl GridSpacingGizmo { let spacing_x_new = if (column) != 0 { new_x_pos / (column) as f64 } else { - old_spacing_x // Can't deduce from vertical column + old_spacing_x + projection.x // Can't deduce from vertical column }; // --- Step 3: Sum of tangents --- @@ -301,18 +301,20 @@ impl GridSpacingGizmo { let new_point = isometric_point_position(old_prev_row, old_prev_col, spacing, new_angles); let old_point = isometric_point_position(old_prev_row, old_prev_col, spacing, angles); - let transform = self - .gizmo_type - .as_ref() - .unwrap() - .transform_grid(viewport.transform_vector2(new_point - old_point), direction, self.column_index, self.row_index); - - responses.add(GraphOperationMessage::TransformChange { - layer, - transform, - transform_in: TransformIn::Viewport, - skip_rerender: false, - }); + if column == 0 { + let transform = self + .gizmo_type + .as_ref() + .unwrap() + .transform_grid(viewport.transform_vector2(new_point - old_point), direction, self.column_index, self.row_index); + + responses.add(GraphOperationMessage::TransformChange { + layer, + transform, + transform_in: TransformIn::Viewport, + skip_rerender: false, + }); + } } } @@ -609,10 +611,10 @@ impl IsometricGizmoType { (row_index, column_index + 1) } } - IsometricGizmoType::Top => (row_index, column_index), + IsometricGizmoType::Top => (row_index + 1, column_index), IsometricGizmoType::IsometricMiddleUp | IsometricGizmoType::IsometricMiddleDown => { if column_index % 2 == 0 { - (row_index - 1, column_index) + (row_index, column_index + 1) } else { (row_index, column_index) } @@ -624,6 +626,7 @@ impl IsometricGizmoType { match self { IsometricGizmoType::Right => 1., IsometricGizmoType::Left => -1., + IsometricGizmoType::Top => -1., _ => 1., } }