From 4dfa3ec62ba6d6373be7f88fee3cce040c53a390 Mon Sep 17 00:00:00 2001 From: mtvare6 Date: Thu, 30 Jan 2025 18:23:53 +0530 Subject: [PATCH 01/16] Rotate pivot and squares to orient along quad --- .../portfolio/document/overlays/utility_types.rs | 11 ++++++----- .../src/messages/tool/common_functionality/pivot.rs | 4 ++-- .../tool/common_functionality/transformation_cage.rs | 6 +++++- editor/src/messages/tool/tool_messages/select_tool.rs | 6 +++++- 4 files changed, 18 insertions(+), 9 deletions(-) diff --git a/editor/src/messages/portfolio/document/overlays/utility_types.rs b/editor/src/messages/portfolio/document/overlays/utility_types.rs index b93f296f54..0b27149e76 100644 --- a/editor/src/messages/portfolio/document/overlays/utility_types.rs +++ b/editor/src/messages/portfolio/document/overlays/utility_types.rs @@ -294,8 +294,9 @@ impl OverlayContext { ) } - pub fn pivot(&mut self, position: DVec2) { + pub fn pivot(&mut self, position: DVec2, angle: f64) { let (x, y) = (position.round() - DVec2::splat(0.5)).into(); + let uv = DVec2::from_angle(angle); self.start_dpi_aware_transform(); @@ -315,13 +316,13 @@ impl OverlayContext { self.render_context.set_line_cap("round"); self.render_context.begin_path(); - self.render_context.move_to(x - crosshair_radius, y); - self.render_context.line_to(x + crosshair_radius, y); + self.render_context.move_to(x + crosshair_radius * uv.x, y + crosshair_radius * uv.y); + self.render_context.line_to(x - crosshair_radius * uv.x, y - crosshair_radius * uv.y); self.render_context.stroke(); self.render_context.begin_path(); - self.render_context.move_to(x, y - crosshair_radius); - self.render_context.line_to(x, y + crosshair_radius); + self.render_context.move_to(x - crosshair_radius * uv.y, y + crosshair_radius * uv.x); + self.render_context.line_to(x + crosshair_radius * uv.y, y - crosshair_radius * uv.x); self.render_context.stroke(); self.render_context.set_line_cap("butt"); diff --git a/editor/src/messages/tool/common_functionality/pivot.rs b/editor/src/messages/tool/common_functionality/pivot.rs index c56f12127e..2c924602fb 100644 --- a/editor/src/messages/tool/common_functionality/pivot.rs +++ b/editor/src/messages/tool/common_functionality/pivot.rs @@ -83,10 +83,10 @@ impl Pivot { } } - pub fn update_pivot(&mut self, document: &DocumentMessageHandler, overlay_context: &mut OverlayContext) { + pub fn update_pivot(&mut self, document: &DocumentMessageHandler, overlay_context: &mut OverlayContext, angle: f64) { self.recalculate_pivot(document); if let Some(pivot) = self.pivot { - overlay_context.pivot(pivot); + overlay_context.pivot(pivot, angle); } } diff --git a/editor/src/messages/tool/common_functionality/transformation_cage.rs b/editor/src/messages/tool/common_functionality/transformation_cage.rs index 32be2277b0..a883ad7a2e 100644 --- a/editor/src/messages/tool/common_functionality/transformation_cage.rs +++ b/editor/src/messages/tool/common_functionality/transformation_cage.rs @@ -322,7 +322,11 @@ impl BoundingBoxManager { // Draw the bounding box rectangle overlay_context.quad(quad, None); - let mut draw_handle = |point: DVec2| overlay_context.square(point, Some(6.), None, None); + let mut draw_handle = |point: DVec2| { + use crate::consts::COLOR_OVERLAY_WHITE; + let quad = DAffine2::from_angle_translation((quad.top_left() - quad.top_right()).to_angle(), point) * Quad::from_box([DVec2::splat(-3.), DVec2::splat(3.)]); + overlay_context.quad(quad, Some(COLOR_OVERLAY_WHITE)); + }; // Draw the horizontal midpoint drag handles if matches!(category, HandleDisplayCategory::Full | HandleDisplayCategory::Narrow | HandleDisplayCategory::ReducedLandscape) { diff --git a/editor/src/messages/tool/tool_messages/select_tool.rs b/editor/src/messages/tool/tool_messages/select_tool.rs index 677acfaeda..91374dc7b0 100644 --- a/editor/src/messages/tool/tool_messages/select_tool.rs +++ b/editor/src/messages/tool/tool_messages/select_tool.rs @@ -487,8 +487,12 @@ impl Fsm for SelectToolFsmState { tool_data.bounding_box_manager.take(); } + let angle = bounds + .map(|bounds| transform * Quad::from_box(bounds)) + .map_or(0., |quad| (quad.top_left() - quad.top_right()).to_angle()); + // Update pivot - tool_data.pivot.update_pivot(document, &mut overlay_context); + tool_data.pivot.update_pivot(document, &mut overlay_context, angle); // Check if the tool is in box selection mode if matches!(self, Self::DrawingBox) { From dcfa4a8a034f781e6fb29a2262301b28ac1b486d Mon Sep 17 00:00:00 2001 From: mtvare6 Date: Mon, 10 Feb 2025 19:05:17 +0530 Subject: [PATCH 02/16] Add compass rose UI --- editor/src/consts.rs | 9 ++- .../document/overlays/utility_types.rs | 58 ++++++++++++++++++- .../tool/common_functionality/pivot.rs | 35 +++++++++-- .../tool/tool_messages/select_tool.rs | 14 +++-- 4 files changed, 101 insertions(+), 15 deletions(-) diff --git a/editor/src/consts.rs b/editor/src/consts.rs index 2df02163a8..213c56bd6f 100644 --- a/editor/src/consts.rs +++ b/editor/src/consts.rs @@ -55,7 +55,13 @@ pub const SELECTION_TOLERANCE: f64 = 5.; pub const SELECTION_DRAG_ANGLE: f64 = 90.; pub const PIVOT_CROSSHAIR_THICKNESS: f64 = 1.; pub const PIVOT_CROSSHAIR_LENGTH: f64 = 9.; -pub const PIVOT_DIAMETER: f64 = 5.; + +pub const COMPASS_ROSE_PIVOT_DIAMETER: f64 = 5.; +pub const COMPASS_ROSE_RING_INNER_DIAMETER: f64 = 13.; +pub const COMPASS_ROSE_MAIN_RING_DIAMETER: f64 = 15.; +pub const COMPASS_ROSE_HOVER_RING_DIAMETER: f64 = 23.; + +pub const COMPASS_ROSE_ARROW_SIZE: f64 = 5.; // TRANSFORM OVERLAY pub const ANGLE_MEASURE_RADIUS_FACTOR: f64 = 0.04; @@ -96,6 +102,7 @@ pub const SCALE_EFFECT: f64 = 0.5; // COLORS pub const COLOR_OVERLAY_BLUE: &str = "#00a8ff"; +pub const COLOR_OVERLAY_BLUE_TRANSLUCENT: &str = "#00a8ff77"; pub const COLOR_OVERLAY_YELLOW: &str = "#ffc848"; pub const COLOR_OVERLAY_GREEN: &str = "#63ce63"; pub const COLOR_OVERLAY_RED: &str = "#ef5454"; diff --git a/editor/src/messages/portfolio/document/overlays/utility_types.rs b/editor/src/messages/portfolio/document/overlays/utility_types.rs index 0b27149e76..1343102ab9 100644 --- a/editor/src/messages/portfolio/document/overlays/utility_types.rs +++ b/editor/src/messages/portfolio/document/overlays/utility_types.rs @@ -1,7 +1,9 @@ use super::utility_functions::overlay_canvas_context; use crate::consts::{ - COLOR_OVERLAY_BLUE, COLOR_OVERLAY_TRANSPARENT, COLOR_OVERLAY_WHITE, COLOR_OVERLAY_YELLOW, MANIPULATOR_GROUP_MARKER_SIZE, PIVOT_CROSSHAIR_LENGTH, PIVOT_CROSSHAIR_THICKNESS, PIVOT_DIAMETER, + COLOR_OVERLAY_BLUE, COLOR_OVERLAY_BLUE_TRANSLUCENT, COLOR_OVERLAY_TRANSPARENT, COLOR_OVERLAY_WHITE, COLOR_OVERLAY_YELLOW, MANIPULATOR_GROUP_MARKER_SIZE, PIVOT_CROSSHAIR_LENGTH, + PIVOT_CROSSHAIR_THICKNESS, }; +use crate::consts::{COMPASS_ROSE_HOVER_RING_DIAMETER, COMPASS_ROSE_MAIN_RING_DIAMETER, COMPASS_ROSE_PIVOT_DIAMETER, COMPASS_ROSE_ARROW_SIZE}; use crate::messages::prelude::Message; use bezier_rs::{Bezier, Subpath}; @@ -294,7 +296,7 @@ impl OverlayContext { ) } - pub fn pivot(&mut self, position: DVec2, angle: f64) { + pub fn pivot(&mut self, position: DVec2, angle: f64, show_hover_ring: bool) { let (x, y) = (position.round() - DVec2::splat(0.5)).into(); let uv = DVec2::from_angle(angle); @@ -303,7 +305,7 @@ impl OverlayContext { // Circle self.render_context.begin_path(); - self.render_context.arc(x, y, PIVOT_DIAMETER / 2., 0., TAU).expect("Failed to draw the circle"); + self.render_context.arc(x, y, COMPASS_ROSE_PIVOT_DIAMETER / 2., 0., TAU).expect("Failed to draw the circle"); self.render_context.set_fill_style_str(COLOR_OVERLAY_YELLOW); self.render_context.fill(); @@ -327,6 +329,56 @@ impl OverlayContext { self.render_context.set_line_cap("butt"); + let old_line_width = self.render_context.line_width(); + + if show_hover_ring { + self.render_context.set_line_width((COMPASS_ROSE_HOVER_RING_DIAMETER - COMPASS_ROSE_MAIN_RING_DIAMETER) / 2.); + self.render_context.begin_path(); + self.render_context + .arc(position.x, position.y, (COMPASS_ROSE_MAIN_RING_DIAMETER + COMPASS_ROSE_HOVER_RING_DIAMETER) / 4., 0., TAU) + .expect("Failed to draw outer circle"); + self.render_context.set_stroke_style_str(COLOR_OVERLAY_BLUE_TRANSLUCENT); + self.render_context.stroke(); + } + + self.render_context.set_line_width(2.); + self.render_context.begin_path(); + self.render_context + .arc(position.x, position.y, (COMPASS_ROSE_MAIN_RING_DIAMETER + 1.) / 2., 0., TAU) + .expect("Failed to draw inner circle"); + self.render_context.set_stroke_style_str(COLOR_OVERLAY_BLUE); + self.render_context.stroke(); + +self.render_context.set_line_width(1.); +self.render_context.set_stroke_style_str(COLOR_OVERLAY_BLUE); +self.render_context.set_fill_style_str(COLOR_OVERLAY_BLUE); + +// Draw 4 arrows at cardinal directions +let ring_radius = (COMPASS_ROSE_MAIN_RING_DIAMETER + 1.) / 2.; + +for i in 0..4 { + let base_angle = i as f64 * TAU / 4.0 + angle; + let direction = DVec2::from_angle(base_angle); + let perpendicular = DVec2::from_angle(base_angle + TAU / 4.); + + let base = DVec2::new(x, y) + direction * ring_radius; + + self.render_context.begin_path(); + + let tip = base + direction * COMPASS_ROSE_ARROW_SIZE; + let side1 = base + perpendicular * COMPASS_ROSE_ARROW_SIZE / 2.; + let side2 = base - perpendicular * COMPASS_ROSE_ARROW_SIZE / 2.; + + self.render_context.move_to(tip.x, tip.y); + self.render_context.line_to(side1.x, side1.y); + self.render_context.line_to(side2.x, side2.y); + + self.render_context.close_path(); + self.render_context.fill(); +} + + self.render_context.set_line_width(old_line_width); + self.end_dpi_aware_transform(); } diff --git a/editor/src/messages/tool/common_functionality/pivot.rs b/editor/src/messages/tool/common_functionality/pivot.rs index 2c924602fb..f8c37b0d58 100644 --- a/editor/src/messages/tool/common_functionality/pivot.rs +++ b/editor/src/messages/tool/common_functionality/pivot.rs @@ -1,7 +1,7 @@ //! Handler for the pivot overlay visible on the selected layer(s) whilst using the Select tool which controls the center of rotation/scale and origin of the layer. use super::graph_modification_utils; -use crate::consts::PIVOT_DIAMETER; +use crate::consts::{COMPASS_ROSE_HOVER_RING_DIAMETER, COMPASS_ROSE_MAIN_RING_DIAMETER, COMPASS_ROSE_PIVOT_DIAMETER, COMPASS_ROSE_RING_INNER_DIAMETER}; use crate::messages::layout::utility_types::widget_prelude::*; use crate::messages::portfolio::document::overlays::utility_types::OverlayContext; use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; @@ -10,6 +10,16 @@ use crate::messages::prelude::*; use glam::{DAffine2, DVec2}; use std::collections::VecDeque; +#[derive(Clone, Debug, PartialEq)] +pub enum CompassRoseState { + Pivot, + HoverRing, + MainRing, + AxisX, + AxisY, + None, +} + #[derive(Clone, Debug)] pub struct Pivot { /// Pivot between (0,0) and (1,1) @@ -83,10 +93,11 @@ impl Pivot { } } - pub fn update_pivot(&mut self, document: &DocumentMessageHandler, overlay_context: &mut OverlayContext, angle: f64) { + pub fn update_pivot(&mut self, document: &DocumentMessageHandler, overlay_context: &mut OverlayContext, angle: f64, mouse_position: DVec2) { self.recalculate_pivot(document); + let show_hover_ring = matches!(self.is_over(mouse_position), CompassRoseState::HoverRing | CompassRoseState::MainRing); if let Some(pivot) = self.pivot { - overlay_context.pivot(pivot, angle); + overlay_context.pivot(pivot, angle, show_hover_ring); } } @@ -125,7 +136,21 @@ impl Pivot { } /// Answers if the pointer is currently positioned over the pivot. - pub fn is_over(&self, mouse: DVec2) -> bool { - self.pivot.filter(|&pivot| mouse.distance_squared(pivot) < (PIVOT_DIAMETER / 2.).powi(2)).is_some() + pub fn is_over(&self, mouse: DVec2) -> CompassRoseState { + match self.pivot { + None => CompassRoseState::None, + Some(pivot) => { + let distance_squared = mouse.distance_squared(pivot); + if distance_squared < (COMPASS_ROSE_PIVOT_DIAMETER / 2.).powi(2) { + CompassRoseState::Pivot + } else if (COMPASS_ROSE_RING_INNER_DIAMETER / 2.).powi(2) < distance_squared && distance_squared < (COMPASS_ROSE_MAIN_RING_DIAMETER / 2.).powi(2) { + CompassRoseState::MainRing + } else if (COMPASS_ROSE_MAIN_RING_DIAMETER / 2.).powi(2) < distance_squared && distance_squared < (COMPASS_ROSE_HOVER_RING_DIAMETER / 2.).powi(2) { + CompassRoseState::HoverRing + } else { + CompassRoseState::None + } + } + } } } diff --git a/editor/src/messages/tool/tool_messages/select_tool.rs b/editor/src/messages/tool/tool_messages/select_tool.rs index 91374dc7b0..e3576a9240 100644 --- a/editor/src/messages/tool/tool_messages/select_tool.rs +++ b/editor/src/messages/tool/tool_messages/select_tool.rs @@ -11,7 +11,7 @@ use crate::messages::portfolio::document::utility_types::network_interface::{Flo use crate::messages::portfolio::document::utility_types::transformation::Selected; use crate::messages::preferences::SelectionMode; use crate::messages::tool::common_functionality::graph_modification_utils::{get_text, is_layer_fed_by_node_of_name}; -use crate::messages::tool::common_functionality::pivot::Pivot; +use crate::messages::tool::common_functionality::pivot::{CompassRoseState, Pivot}; use crate::messages::tool::common_functionality::snapping::{self, SnapCandidatePoint, SnapData, SnapManager}; use crate::messages::tool::common_functionality::transformation_cage::*; use crate::messages::tool::common_functionality::{auto_panning::AutoPanning, measure}; @@ -490,9 +490,9 @@ impl Fsm for SelectToolFsmState { let angle = bounds .map(|bounds| transform * Quad::from_box(bounds)) .map_or(0., |quad| (quad.top_left() - quad.top_right()).to_angle()); - + let mouse_position = input.mouse.position; // Update pivot - tool_data.pivot.update_pivot(document, &mut overlay_context, angle); + tool_data.pivot.update_pivot(document, &mut overlay_context, angle, mouse_position); // Check if the tool is in box selection mode if matches!(self, Self::DrawingBox) { @@ -604,9 +604,11 @@ impl Fsm for SelectToolFsmState { // If the user clicks on new shape, make that layer their new selection. // Otherwise enter the box select mode + let compass_ross_state = tool_data.pivot.is_over(input.mouse.position); + let state = // Dragging the pivot - if tool_data.pivot.is_over(input.mouse.position) { + if compass_ross_state == CompassRoseState::Pivot{ responses.add(DocumentMessage::StartTransaction); // tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(), true, true); @@ -680,7 +682,7 @@ impl Fsm for SelectToolFsmState { SelectToolFsmState::RotatingBounds } // Dragging the selected layers around to transform them - else if intersection.is_some_and(|intersection| selected.iter().any(|selected_layer| intersection.starts_with(*selected_layer, document.metadata()))) { + else if compass_ross_state == CompassRoseState::HoverRing || intersection.is_some_and(|intersection| selected.iter().any(|selected_layer| intersection.starts_with(*selected_layer, document.metadata()))) { responses.add(DocumentMessage::StartTransaction); if tool_data.nested_selection_behavior == NestedSelectionBehavior::Deepest { @@ -891,7 +893,7 @@ impl Fsm for SelectToolFsmState { let mut cursor = tool_data.bounding_box_manager.as_ref().map_or(MouseCursorIcon::Default, |bounds| bounds.get_cursor(input, true)); // Dragging the pivot overrules the other operations - if tool_data.pivot.is_over(input.mouse.position) { + if tool_data.pivot.is_over(input.mouse.position) == CompassRoseState::Pivot { cursor = MouseCursorIcon::Move; } From 6eac06d46f477f1f4b281026143385d89f8841a1 Mon Sep 17 00:00:00 2001 From: mtvare6 Date: Tue, 11 Feb 2025 17:01:36 +0530 Subject: [PATCH 03/16] Add compass rose functionality --- .../document/overlays/utility_types.rs | 48 ++++++------- .../tool/common_functionality/pivot.rs | 52 +++++++++++++- .../tool/tool_messages/select_tool.rs | 68 ++++++++++++++----- 3 files changed, 125 insertions(+), 43 deletions(-) diff --git a/editor/src/messages/portfolio/document/overlays/utility_types.rs b/editor/src/messages/portfolio/document/overlays/utility_types.rs index fa81a8d74c..acf28a705f 100644 --- a/editor/src/messages/portfolio/document/overlays/utility_types.rs +++ b/editor/src/messages/portfolio/document/overlays/utility_types.rs @@ -1,9 +1,9 @@ use super::utility_functions::overlay_canvas_context; use crate::consts::{ - COLOR_OVERLAY_BLUE, COLOR_OVERLAY_BLUE_TRANSLUCENT, COLOR_OVERLAY_TRANSPARENT, COLOR_OVERLAY_WHITE, COLOR_OVERLAY_YELLOW, MANIPULATOR_GROUP_MARKER_SIZE, PIVOT_CROSSHAIR_LENGTH, - PIVOT_CROSSHAIR_THICKNESS, + COLOR_OVERLAY_BLUE, COLOR_OVERLAY_BLUE_TRANSLUCENT, COLOR_OVERLAY_GREEN, COLOR_OVERLAY_RED, COLOR_OVERLAY_TRANSPARENT, COLOR_OVERLAY_WHITE, COLOR_OVERLAY_YELLOW, MANIPULATOR_GROUP_MARKER_SIZE, + PIVOT_CROSSHAIR_LENGTH, PIVOT_CROSSHAIR_THICKNESS, }; -use crate::consts::{COMPASS_ROSE_HOVER_RING_DIAMETER, COMPASS_ROSE_MAIN_RING_DIAMETER, COMPASS_ROSE_PIVOT_DIAMETER, COMPASS_ROSE_ARROW_SIZE}; +use crate::consts::{COMPASS_ROSE_ARROW_SIZE, COMPASS_ROSE_HOVER_RING_DIAMETER, COMPASS_ROSE_MAIN_RING_DIAMETER, COMPASS_ROSE_PIVOT_DIAMETER}; use crate::messages::prelude::Message; use bezier_rs::{Bezier, Subpath}; @@ -361,33 +361,35 @@ impl OverlayContext { self.render_context.set_stroke_style_str(COLOR_OVERLAY_BLUE); self.render_context.stroke(); -self.render_context.set_line_width(1.); -self.render_context.set_stroke_style_str(COLOR_OVERLAY_BLUE); -self.render_context.set_fill_style_str(COLOR_OVERLAY_BLUE); + self.render_context.set_line_width(1.); + self.render_context.set_stroke_style_str(COLOR_OVERLAY_BLUE); + self.render_context.set_fill_style_str(COLOR_OVERLAY_BLUE); -// Draw 4 arrows at cardinal directions -let ring_radius = (COMPASS_ROSE_MAIN_RING_DIAMETER + 1.) / 2.; + // Draw 4 arrows at cardinal directions + let ring_radius = (COMPASS_ROSE_MAIN_RING_DIAMETER + 1.) / 2.; -for i in 0..4 { - let base_angle = i as f64 * TAU / 4.0 + angle; - let direction = DVec2::from_angle(base_angle); - let perpendicular = DVec2::from_angle(base_angle + TAU / 4.); + for i in 0..4 { + let base_angle = i as f64 * TAU / 4.0 + angle; + let direction = DVec2::from_angle(base_angle); + let perpendicular = DVec2::from_angle(base_angle + TAU / 4.); + let color = if i % 2 == 0 { COLOR_OVERLAY_RED } else { COLOR_OVERLAY_GREEN }; - let base = DVec2::new(x, y) + direction * ring_radius; + let base = DVec2::new(x, y) + direction * ring_radius; - self.render_context.begin_path(); + self.render_context.begin_path(); - let tip = base + direction * COMPASS_ROSE_ARROW_SIZE; - let side1 = base + perpendicular * COMPASS_ROSE_ARROW_SIZE / 2.; - let side2 = base - perpendicular * COMPASS_ROSE_ARROW_SIZE / 2.; + let tip = base + direction * COMPASS_ROSE_ARROW_SIZE; + let side1 = base + perpendicular * COMPASS_ROSE_ARROW_SIZE / 2.; + let side2 = base - perpendicular * COMPASS_ROSE_ARROW_SIZE / 2.; - self.render_context.move_to(tip.x, tip.y); - self.render_context.line_to(side1.x, side1.y); - self.render_context.line_to(side2.x, side2.y); + self.render_context.move_to(tip.x, tip.y); + self.render_context.line_to(side1.x, side1.y); + self.render_context.line_to(side2.x, side2.y); - self.render_context.close_path(); - self.render_context.fill(); -} + self.render_context.close_path(); + self.render_context.set_fill_style_str(color); + self.render_context.fill(); + } self.render_context.set_line_width(old_line_width); diff --git a/editor/src/messages/tool/common_functionality/pivot.rs b/editor/src/messages/tool/common_functionality/pivot.rs index f8c37b0d58..02aa85549f 100644 --- a/editor/src/messages/tool/common_functionality/pivot.rs +++ b/editor/src/messages/tool/common_functionality/pivot.rs @@ -1,7 +1,7 @@ //! Handler for the pivot overlay visible on the selected layer(s) whilst using the Select tool which controls the center of rotation/scale and origin of the layer. use super::graph_modification_utils; -use crate::consts::{COMPASS_ROSE_HOVER_RING_DIAMETER, COMPASS_ROSE_MAIN_RING_DIAMETER, COMPASS_ROSE_PIVOT_DIAMETER, COMPASS_ROSE_RING_INNER_DIAMETER}; +use crate::consts::{COMPASS_ROSE_ARROW_SIZE, COMPASS_ROSE_HOVER_RING_DIAMETER, COMPASS_ROSE_MAIN_RING_DIAMETER, COMPASS_ROSE_PIVOT_DIAMETER, COMPASS_ROSE_RING_INNER_DIAMETER}; use crate::messages::layout::utility_types::widget_prelude::*; use crate::messages::portfolio::document::overlays::utility_types::OverlayContext; use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; @@ -9,6 +9,7 @@ use crate::messages::prelude::*; use glam::{DAffine2, DVec2}; use std::collections::VecDeque; +use std::f64::consts::TAU; #[derive(Clone, Debug, PartialEq)] pub enum CompassRoseState { @@ -20,6 +21,15 @@ pub enum CompassRoseState { None, } +impl CompassRoseState { + pub fn can_grab(&self) -> bool { + matches!(self, Self::HoverRing | Self::AxisX | Self::AxisY) + } + pub fn is_pivot(&self) -> bool { + matches!(self, Self::Pivot) + } +} + #[derive(Clone, Debug)] pub struct Pivot { /// Pivot between (0,0) and (1,1) @@ -95,7 +105,7 @@ impl Pivot { pub fn update_pivot(&mut self, document: &DocumentMessageHandler, overlay_context: &mut OverlayContext, angle: f64, mouse_position: DVec2) { self.recalculate_pivot(document); - let show_hover_ring = matches!(self.is_over(mouse_position), CompassRoseState::HoverRing | CompassRoseState::MainRing); + let show_hover_ring = matches!(self.is_over(mouse_position, angle), CompassRoseState::HoverRing | CompassRoseState::MainRing); if let Some(pivot) = self.pivot { overlay_context.pivot(pivot, angle, show_hover_ring); } @@ -136,11 +146,28 @@ impl Pivot { } /// Answers if the pointer is currently positioned over the pivot. - pub fn is_over(&self, mouse: DVec2) -> CompassRoseState { + pub fn is_over(&self, mouse: DVec2, angle: f64) -> CompassRoseState { match self.pivot { None => CompassRoseState::None, Some(pivot) => { let distance_squared = mouse.distance_squared(pivot); + let ring_radius = (COMPASS_ROSE_MAIN_RING_DIAMETER + 1.) / 2.; + + for i in 0..4 { + let base_angle = i as f64 * TAU / 4.0 + angle; + let direction = DVec2::from_angle(base_angle); + + let arrow_base = pivot + direction * ring_radius; + let arrow_tip = arrow_base + direction * COMPASS_ROSE_ARROW_SIZE; + + let perp = DVec2::from_angle(base_angle + TAU / 4.) * COMPASS_ROSE_ARROW_SIZE / 2.; + let side1 = arrow_base + perp; + let side2 = arrow_base - perp; + + if is_point_in_triangle(mouse, arrow_tip, side1, side2) { + return if i % 2 == 0 { CompassRoseState::AxisX } else { CompassRoseState::AxisY }; + } + } if distance_squared < (COMPASS_ROSE_PIVOT_DIAMETER / 2.).powi(2) { CompassRoseState::Pivot } else if (COMPASS_ROSE_RING_INNER_DIAMETER / 2.).powi(2) < distance_squared && distance_squared < (COMPASS_ROSE_MAIN_RING_DIAMETER / 2.).powi(2) { @@ -154,3 +181,22 @@ impl Pivot { } } } +fn is_point_in_triangle(p: DVec2, a: DVec2, b: DVec2, c: DVec2) -> bool { + // Calculate barycentric coordinates + let v0 = c - a; + let v1 = b - a; + let v2 = p - a; + + let dot00 = v0.dot(v0); + let dot01 = v0.dot(v1); + let dot02 = v0.dot(v2); + let dot11 = v1.dot(v1); + let dot12 = v1.dot(v2); + + let inv_denom = 1. / (dot00 * dot11 - dot01 * dot01); + let u = (dot11 * dot02 - dot01 * dot12) * inv_denom; + let v = (dot00 * dot12 - dot01 * dot02) * inv_denom; + + // Check if point is inside triangle + u >= 0. && v >= 0. && u + v <= 1. +} diff --git a/editor/src/messages/tool/tool_messages/select_tool.rs b/editor/src/messages/tool/tool_messages/select_tool.rs index 6e65a428e0..56c435225c 100644 --- a/editor/src/messages/tool/tool_messages/select_tool.rs +++ b/editor/src/messages/tool/tool_messages/select_tool.rs @@ -270,11 +270,19 @@ impl ToolTransition for SelectTool { } } +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)] +pub enum Axis { + #[default] + None, + X, + Y, +} + #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] enum SelectToolFsmState { Ready { selection: NestedSelectionBehavior }, Drawing { selection_shape: SelectionShapeType }, - Dragging, + Dragging { axis: Axis }, ResizingBounds, SkewingBounds, RotatingBounds, @@ -680,11 +688,16 @@ impl Fsm for SelectToolFsmState { // If the user clicks on new shape, make that layer their new selection. // Otherwise enter the box select mode - let compass_ross_state = tool_data.pivot.is_over(input.mouse.position); + let angle = tool_data + .bounding_box_manager + .as_ref() + .map(|man| man.transform * Quad::from_box(man.bounds)) + .map_or(0., |quad| (quad.top_left() - quad.top_right()).to_angle()); + let compass_ross_state = tool_data.pivot.is_over(input.mouse.position, angle); let state = // Dragging the pivot - if compass_ross_state == CompassRoseState::Pivot{ + if compass_ross_state.is_pivot() { responses.add(DocumentMessage::StartTransaction); // tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(), true, true); @@ -693,7 +706,7 @@ impl Fsm for SelectToolFsmState { SelectToolFsmState::DraggingPivot } // Dragging one (or two, forming a corner) of the transform cage bounding box edges - else if let Some(_selected_edges) = dragging_bounds { + else if let Some(_) = dragging_bounds { responses.add(DocumentMessage::StartTransaction); tool_data.layers_dragging = selected; @@ -762,7 +775,7 @@ impl Fsm for SelectToolFsmState { SelectToolFsmState::RotatingBounds } // Dragging the selected layers around to transform them - else if compass_ross_state == CompassRoseState::HoverRing || intersection.is_some_and(|intersection| selected.iter().any(|selected_layer| intersection.starts_with(*selected_layer, document.metadata()))) { + else if compass_ross_state.can_grab() || intersection.is_some_and(|intersection| selected.iter().any(|selected_layer| intersection.starts_with(*selected_layer, document.metadata()))) { responses.add(DocumentMessage::StartTransaction); if input.keyboard.key(select_deepest) || tool_data.nested_selection_behavior == NestedSelectionBehavior::Deepest { @@ -774,8 +787,14 @@ impl Fsm for SelectToolFsmState { tool_data.layers_dragging = selected; tool_data.get_snap_candidates(document, input); + let axis = match compass_ross_state{ + CompassRoseState::AxisX => Axis::X, + CompassRoseState::AxisY => Axis::Y, + CompassRoseState::HoverRing => Axis::None, + _ => unreachable!() + }; - SelectToolFsmState::Dragging + SelectToolFsmState::Dragging{axis} } // Dragging a selection box else { @@ -796,7 +815,7 @@ impl Fsm for SelectToolFsmState { tool_data.get_snap_candidates(document, input); responses.add(DocumentMessage::StartTransaction); - SelectToolFsmState::Dragging + SelectToolFsmState::Dragging { axis: Axis::None } } else { let selection_shape = if input.keyboard.key(lasso_select) { SelectionShapeType::Lasso } else { SelectionShapeType::Box }; SelectToolFsmState::Drawing { selection_shape } @@ -812,7 +831,7 @@ impl Fsm for SelectToolFsmState { let selection = tool_data.nested_selection_behavior; SelectToolFsmState::Ready { selection } } - (SelectToolFsmState::Dragging, SelectToolMessage::PointerMove(modifier_keys)) => { + (SelectToolFsmState::Dragging { axis }, SelectToolMessage::PointerMove(modifier_keys)) => { tool_data.has_dragged = true; if input.keyboard.key(modifier_keys.duplicate) && tool_data.non_duplicated_layers.is_none() { @@ -830,6 +849,16 @@ impl Fsm for SelectToolFsmState { let snap_data = SnapData::ignore(document, input, ignore); let (start, current) = (tool_data.drag_start, tool_data.drag_current); let mouse_delta = snap_drag(start, current, axis_align, snap_data, &mut tool_data.snap_manager, &tool_data.snap_candidates); + let e0 = tool_data + .bounding_box_manager + .as_ref() + .map(|man| man.transform * Quad::from_box(man.bounds)) + .map_or(DVec2::X, |quad| (quad.top_left() - quad.top_right()).normalize_or(DVec2::X)); + let mouse_delta = match axis { + Axis::X => mouse_delta.project_onto(e0), + Axis::Y => mouse_delta.project_onto(e0.perp()), + Axis::None => mouse_delta, + }; // TODO: Cache the result of `shallowest_unique_layers` to avoid this heavy computation every frame of movement, see https://github.com/GraphiteEditor/Graphite/pull/481 for layer in document.network_interface.shallowest_unique_layers(&[]) { @@ -849,7 +878,7 @@ impl Fsm for SelectToolFsmState { ]; tool_data.auto_panning.setup_by_mouse_position(input, &messages, responses); - SelectToolFsmState::Dragging + SelectToolFsmState::Dragging { axis } } (SelectToolFsmState::ResizingBounds, SelectToolMessage::PointerMove(modifier_keys)) => { if let Some(ref mut bounds) = &mut tool_data.bounding_box_manager { @@ -1007,8 +1036,13 @@ impl Fsm for SelectToolFsmState { (SelectToolFsmState::Ready { .. }, SelectToolMessage::PointerMove(_)) => { let mut cursor = tool_data.bounding_box_manager.as_ref().map_or(MouseCursorIcon::Default, |bounds| bounds.get_cursor(input, true)); + let angle = tool_data + .bounding_box_manager + .as_ref() + .map(|man| man.transform * Quad::from_box(man.bounds)) + .map_or(0., |quad| (quad.top_left() - quad.top_right()).to_angle()); // Dragging the pivot overrules the other operations - if tool_data.pivot.is_over(input.mouse.position) == CompassRoseState::Pivot { + if tool_data.pivot.is_over(input.mouse.position, angle).is_pivot() { cursor = MouseCursorIcon::Move; } @@ -1023,14 +1057,14 @@ impl Fsm for SelectToolFsmState { let selection = tool_data.nested_selection_behavior; SelectToolFsmState::Ready { selection } } - (SelectToolFsmState::Dragging, SelectToolMessage::PointerOutsideViewport(_)) => { + (SelectToolFsmState::Dragging { axis }, SelectToolMessage::PointerOutsideViewport(_)) => { // AutoPanning if let Some(shift) = tool_data.auto_panning.shift_viewport(input, responses) { tool_data.drag_current += shift; tool_data.drag_start += shift; } - SelectToolFsmState::Dragging + SelectToolFsmState::Dragging { axis } } (SelectToolFsmState::ResizingBounds | SelectToolFsmState::SkewingBounds, SelectToolMessage::PointerOutsideViewport(_)) => { // AutoPanning @@ -1067,7 +1101,7 @@ impl Fsm for SelectToolFsmState { state } - (SelectToolFsmState::Dragging, SelectToolMessage::Enter) => { + (SelectToolFsmState::Dragging { .. }, SelectToolMessage::Enter) => { let response = match input.mouse.position.distance(tool_data.drag_start) < 10. * f64::EPSILON { true => DocumentMessage::AbortTransaction, false => DocumentMessage::EndTransaction, @@ -1078,7 +1112,7 @@ impl Fsm for SelectToolFsmState { let selection = tool_data.nested_selection_behavior; SelectToolFsmState::Ready { selection } } - (SelectToolFsmState::Dragging, SelectToolMessage::DragStop { remove_from_selection }) => { + (SelectToolFsmState::Dragging { .. }, SelectToolMessage::DragStop { remove_from_selection }) => { // Deselect layer if not snap dragging responses.add(DocumentMessage::EndTransaction); @@ -1259,7 +1293,7 @@ impl Fsm for SelectToolFsmState { let selection = tool_data.nested_selection_behavior; SelectToolFsmState::Ready { selection } } - (SelectToolFsmState::Dragging, SelectToolMessage::Abort) => { + (SelectToolFsmState::Dragging { .. }, SelectToolMessage::Abort) => { responses.add(DocumentMessage::AbortTransaction); tool_data.snap_manager.cleanup(responses); responses.add(OverlaysMessage::Draw); @@ -1342,7 +1376,7 @@ impl Fsm for SelectToolFsmState { ]); responses.add(FrontendMessage::UpdateInputHints { hint_data }); } - SelectToolFsmState::Dragging if tool_data.has_dragged => { + SelectToolFsmState::Dragging { axis: Axis::None } if tool_data.has_dragged => { let hint_data = HintData(vec![ HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]), HintGroup(vec![HintInfo::keys([Key::Shift], "Constrain to Axis")]), @@ -1363,7 +1397,7 @@ impl Fsm for SelectToolFsmState { ]); responses.add(FrontendMessage::UpdateInputHints { hint_data }); } - SelectToolFsmState::Drawing { .. } | SelectToolFsmState::Dragging => {} + SelectToolFsmState::Drawing { .. } | SelectToolFsmState::Dragging { .. } => {} SelectToolFsmState::ResizingBounds => { let hint_data = HintData(vec![ HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]), From 13e2a3c25d88c0b1c600059871d131bac5dd84e9 Mon Sep 17 00:00:00 2001 From: mtvare6 Date: Tue, 11 Feb 2025 18:56:07 +0530 Subject: [PATCH 04/16] Refactor code and polish things --- editor/src/consts.rs | 2 - .../document/overlays/utility_types.rs | 68 +++++++++++-------- .../tool/common_functionality/pivot.rs | 43 ++++++++++-- .../tool/tool_messages/select_tool.rs | 52 ++++++++------ 4 files changed, 107 insertions(+), 58 deletions(-) diff --git a/editor/src/consts.rs b/editor/src/consts.rs index 9f0e4653c3..1dc454309f 100644 --- a/editor/src/consts.rs +++ b/editor/src/consts.rs @@ -108,14 +108,12 @@ pub const SCALE_EFFECT: f64 = 0.5; // COLORS pub const COLOR_OVERLAY_BLUE: &str = "#00a8ff"; -pub const COLOR_OVERLAY_BLUE_TRANSLUCENT: &str = "#00a8ff77"; pub const COLOR_OVERLAY_YELLOW: &str = "#ffc848"; pub const COLOR_OVERLAY_GREEN: &str = "#63ce63"; pub const COLOR_OVERLAY_RED: &str = "#ef5454"; pub const COLOR_OVERLAY_GRAY: &str = "#cccccc"; pub const COLOR_OVERLAY_WHITE: &str = "#ffffff"; pub const COLOR_OVERLAY_LABEL_BACKGROUND: &str = "#000000cc"; -pub const COLOR_OVERLAY_TRANSPARENT: &str = "#ffffff00"; // DOCUMENT pub const DEFAULT_DOCUMENT_NAME: &str = "Untitled Document"; diff --git a/editor/src/messages/portfolio/document/overlays/utility_types.rs b/editor/src/messages/portfolio/document/overlays/utility_types.rs index acf28a705f..f60e2200ab 100644 --- a/editor/src/messages/portfolio/document/overlays/utility_types.rs +++ b/editor/src/messages/portfolio/document/overlays/utility_types.rs @@ -1,7 +1,6 @@ use super::utility_functions::overlay_canvas_context; use crate::consts::{ - COLOR_OVERLAY_BLUE, COLOR_OVERLAY_BLUE_TRANSLUCENT, COLOR_OVERLAY_GREEN, COLOR_OVERLAY_RED, COLOR_OVERLAY_TRANSPARENT, COLOR_OVERLAY_WHITE, COLOR_OVERLAY_YELLOW, MANIPULATOR_GROUP_MARKER_SIZE, - PIVOT_CROSSHAIR_LENGTH, PIVOT_CROSSHAIR_THICKNESS, + COLOR_OVERLAY_BLUE, COLOR_OVERLAY_GREEN, COLOR_OVERLAY_RED, COLOR_OVERLAY_WHITE, COLOR_OVERLAY_YELLOW, MANIPULATOR_GROUP_MARKER_SIZE, PIVOT_CROSSHAIR_LENGTH, PIVOT_CROSSHAIR_THICKNESS, }; use crate::consts::{COMPASS_ROSE_ARROW_SIZE, COMPASS_ROSE_HOVER_RING_DIAMETER, COMPASS_ROSE_MAIN_RING_DIAMETER, COMPASS_ROSE_PIVOT_DIAMETER}; use crate::messages::prelude::Message; @@ -295,9 +294,15 @@ impl OverlayContext { pub fn draw_scale(&mut self, start: DVec2, scale: f64, radius: f64, text: &str) { let sign = scale.signum(); + let mut fill_color = graphene_std::Color::from_rgb_str(crate::consts::COLOR_OVERLAY_WHITE.strip_prefix('#').unwrap()) + .unwrap() + .with_alpha(0.05) + .rgba_hex(); + fill_color.insert(0, '#'); + let fill_color = Some(fill_color.as_str()); self.line(start + DVec2::X * radius * sign, start + DVec2::X * (radius * scale), None); - self.circle(start, radius, Some(COLOR_OVERLAY_TRANSPARENT), None); - self.circle(start, radius * scale.abs(), Some(COLOR_OVERLAY_TRANSPARENT), None); + self.circle(start, radius, fill_color, None); + self.circle(start, radius * scale.abs(), fill_color, None); self.text( text, COLOR_OVERLAY_BLUE, @@ -343,29 +348,6 @@ impl OverlayContext { let old_line_width = self.render_context.line_width(); - if show_hover_ring { - self.render_context.set_line_width((COMPASS_ROSE_HOVER_RING_DIAMETER - COMPASS_ROSE_MAIN_RING_DIAMETER) / 2.); - self.render_context.begin_path(); - self.render_context - .arc(position.x, position.y, (COMPASS_ROSE_MAIN_RING_DIAMETER + COMPASS_ROSE_HOVER_RING_DIAMETER) / 4., 0., TAU) - .expect("Failed to draw outer circle"); - self.render_context.set_stroke_style_str(COLOR_OVERLAY_BLUE_TRANSLUCENT); - self.render_context.stroke(); - } - - self.render_context.set_line_width(2.); - self.render_context.begin_path(); - self.render_context - .arc(position.x, position.y, (COMPASS_ROSE_MAIN_RING_DIAMETER + 1.) / 2., 0., TAU) - .expect("Failed to draw inner circle"); - self.render_context.set_stroke_style_str(COLOR_OVERLAY_BLUE); - self.render_context.stroke(); - - self.render_context.set_line_width(1.); - self.render_context.set_stroke_style_str(COLOR_OVERLAY_BLUE); - self.render_context.set_fill_style_str(COLOR_OVERLAY_BLUE); - - // Draw 4 arrows at cardinal directions let ring_radius = (COMPASS_ROSE_MAIN_RING_DIAMETER + 1.) / 2.; for i in 0..4 { @@ -374,7 +356,7 @@ impl OverlayContext { let perpendicular = DVec2::from_angle(base_angle + TAU / 4.); let color = if i % 2 == 0 { COLOR_OVERLAY_RED } else { COLOR_OVERLAY_GREEN }; - let base = DVec2::new(x, y) + direction * ring_radius; + let base = DVec2::new(x, y) + direction * (ring_radius - COMPASS_ROSE_ARROW_SIZE * 0.1); self.render_context.begin_path(); @@ -389,8 +371,38 @@ impl OverlayContext { self.render_context.close_path(); self.render_context.set_fill_style_str(color); self.render_context.fill(); + self.render_context.set_stroke_style_str(color); + self.render_context.stroke(); } + if show_hover_ring { + let mut fill_color = graphene_std::Color::from_rgb_str(crate::consts::COLOR_OVERLAY_BLUE.strip_prefix('#').unwrap()) + .unwrap() + .with_alpha(0.3) + .rgba_hex(); + fill_color.insert(0, '#'); + + self.render_context.set_line_width((COMPASS_ROSE_HOVER_RING_DIAMETER - COMPASS_ROSE_MAIN_RING_DIAMETER) / 2.); + self.render_context.begin_path(); + self.render_context + .arc(position.x, position.y, (COMPASS_ROSE_MAIN_RING_DIAMETER + COMPASS_ROSE_HOVER_RING_DIAMETER) / 4., 0., TAU) + .expect("Failed to draw outer circle"); + self.render_context.set_stroke_style_str(fill_color.as_str()); + self.render_context.stroke(); + } + + self.render_context.set_line_width(2.); + self.render_context.begin_path(); + self.render_context + .arc(position.x, position.y, (COMPASS_ROSE_MAIN_RING_DIAMETER + 1.) / 2., 0., TAU) + .expect("Failed to draw inner circle"); + self.render_context.set_stroke_style_str(COLOR_OVERLAY_BLUE); + self.render_context.stroke(); + + self.render_context.set_line_width(1.); + self.render_context.set_stroke_style_str(COLOR_OVERLAY_BLUE); + self.render_context.set_fill_style_str(COLOR_OVERLAY_BLUE); + self.render_context.set_line_width(old_line_width); self.end_dpi_aware_transform(); diff --git a/editor/src/messages/tool/common_functionality/pivot.rs b/editor/src/messages/tool/common_functionality/pivot.rs index 02aa85549f..a49f7b8ebf 100644 --- a/editor/src/messages/tool/common_functionality/pivot.rs +++ b/editor/src/messages/tool/common_functionality/pivot.rs @@ -9,7 +9,7 @@ use crate::messages::prelude::*; use glam::{DAffine2, DVec2}; use std::collections::VecDeque; -use std::f64::consts::TAU; +use std::f64::consts::FRAC_PI_2; #[derive(Clone, Debug, PartialEq)] pub enum CompassRoseState { @@ -21,13 +21,41 @@ pub enum CompassRoseState { None, } +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)] +pub enum Axis { + #[default] + None, + X, + Y, +} + +impl Axis { + pub fn is_constraint(&self) -> bool { + matches!(self, Self::X | Self::Y) + } +} + impl CompassRoseState { pub fn can_grab(&self) -> bool { matches!(self, Self::HoverRing | Self::AxisX | Self::AxisY) } + pub fn is_pivot(&self) -> bool { matches!(self, Self::Pivot) } + + pub fn is_ring(&self) -> bool { + matches!(self, Self::HoverRing | Self::MainRing) + } + + pub fn axis_type(&self) -> Axis { + match self { + CompassRoseState::AxisX => Axis::X, + CompassRoseState::AxisY => Axis::Y, + CompassRoseState::HoverRing => Axis::None, + _ => unreachable!(), + } + } } #[derive(Clone, Debug)] @@ -63,6 +91,10 @@ impl Pivot { layer_transform * bounds_transform } + pub fn get_position(&self) -> Option { + self.pivot + } + /// Recomputes the pivot position and transform. fn recalculate_pivot(&mut self, document: &DocumentMessageHandler) { let selected_nodes = document.network_interface.selected_nodes(&[]).unwrap(); @@ -103,9 +135,8 @@ impl Pivot { } } - pub fn update_pivot(&mut self, document: &DocumentMessageHandler, overlay_context: &mut OverlayContext, angle: f64, mouse_position: DVec2) { + pub fn update_pivot(&mut self, document: &DocumentMessageHandler, overlay_context: &mut OverlayContext, angle: f64, show_hover_ring: bool) { self.recalculate_pivot(document); - let show_hover_ring = matches!(self.is_over(mouse_position, angle), CompassRoseState::HoverRing | CompassRoseState::MainRing); if let Some(pivot) = self.pivot { overlay_context.pivot(pivot, angle, show_hover_ring); } @@ -146,7 +177,7 @@ impl Pivot { } /// Answers if the pointer is currently positioned over the pivot. - pub fn is_over(&self, mouse: DVec2, angle: f64) -> CompassRoseState { + pub fn compass_rose_state(&self, mouse: DVec2, angle: f64) -> CompassRoseState { match self.pivot { None => CompassRoseState::None, Some(pivot) => { @@ -154,13 +185,13 @@ impl Pivot { let ring_radius = (COMPASS_ROSE_MAIN_RING_DIAMETER + 1.) / 2.; for i in 0..4 { - let base_angle = i as f64 * TAU / 4.0 + angle; + let base_angle = i as f64 * FRAC_PI_2 + angle; let direction = DVec2::from_angle(base_angle); let arrow_base = pivot + direction * ring_radius; let arrow_tip = arrow_base + direction * COMPASS_ROSE_ARROW_SIZE; - let perp = DVec2::from_angle(base_angle + TAU / 4.) * COMPASS_ROSE_ARROW_SIZE / 2.; + let perp = direction.perp() * COMPASS_ROSE_ARROW_SIZE / 2.; let side1 = arrow_base + perp; let side2 = arrow_base - perp; diff --git a/editor/src/messages/tool/tool_messages/select_tool.rs b/editor/src/messages/tool/tool_messages/select_tool.rs index 56c435225c..8b30189dcd 100644 --- a/editor/src/messages/tool/tool_messages/select_tool.rs +++ b/editor/src/messages/tool/tool_messages/select_tool.rs @@ -1,7 +1,7 @@ #![allow(clippy::too_many_arguments)] use super::tool_prelude::*; -use crate::consts::{DRAG_DIRECTION_MODE_DETERMINATION_THRESHOLD, ROTATE_INCREMENT, SELECTION_TOLERANCE}; +use crate::consts::{COLOR_OVERLAY_GREEN, COLOR_OVERLAY_RED, DRAG_DIRECTION_MODE_DETERMINATION_THRESHOLD, ROTATE_INCREMENT, SELECTION_TOLERANCE}; use crate::messages::input_mapper::utility_types::input_mouse::ViewportPosition; use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn; use crate::messages::portfolio::document::overlays::utility_types::OverlayContext; @@ -12,7 +12,7 @@ use crate::messages::portfolio::document::utility_types::nodes::SelectedNodes; use crate::messages::portfolio::document::utility_types::transformation::Selected; use crate::messages::preferences::SelectionMode; use crate::messages::tool::common_functionality::graph_modification_utils::{get_text, is_layer_fed_by_node_of_name}; -use crate::messages::tool::common_functionality::pivot::{CompassRoseState, Pivot}; +use crate::messages::tool::common_functionality::pivot::{Axis, Pivot}; use crate::messages::tool::common_functionality::shape_editor::SelectionShapeType; use crate::messages::tool::common_functionality::snapping::{self, SnapCandidatePoint, SnapData, SnapManager}; use crate::messages::tool::common_functionality::transformation_cage::*; @@ -270,14 +270,6 @@ impl ToolTransition for SelectTool { } } -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)] -pub enum Axis { - #[default] - None, - X, - Y, -} - #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] enum SelectToolFsmState { Ready { selection: NestedSelectionBehavior }, @@ -555,8 +547,30 @@ impl Fsm for SelectToolFsmState { .map(|bounds| transform * Quad::from_box(bounds)) .map_or(0., |quad| (quad.top_left() - quad.top_right()).to_angle()); let mouse_position = input.mouse.position; + let compass_rose_state = tool_data.pivot.compass_rose_state(mouse_position, angle); + let show_hover_ring = compass_rose_state.is_ring(); // Update pivot - tool_data.pivot.update_pivot(document, &mut overlay_context, angle, mouse_position); + tool_data.pivot.update_pivot(document, &mut overlay_context, angle, show_hover_ring); + if let SelectToolFsmState::Dragging { axis } = self { + if axis.is_constraint() { + let e0 = tool_data + .bounding_box_manager + .as_ref() + .map(|man| man.transform * Quad::from_box(man.bounds)) + .map_or(DVec2::X, |quad| (quad.top_left() - quad.top_right()).normalize_or(DVec2::X)); + + let origin = tool_data.pivot.get_position().unwrap_or(tool_data.drag_start); + let (direction, color) = match axis { + Axis::X => (e0, COLOR_OVERLAY_RED), + Axis::Y => (e0.perp(), COLOR_OVERLAY_GREEN), + _ => unreachable!(), + }; + + let viewport_diagonal = input.viewport_bounds.size().length(); + + overlay_context.line(origin - direction * viewport_diagonal, origin + direction * viewport_diagonal, Some(color)); + } + } // Check if the tool is in selection mode if let Self::Drawing { selection_shape } = self { @@ -693,11 +707,11 @@ impl Fsm for SelectToolFsmState { .as_ref() .map(|man| man.transform * Quad::from_box(man.bounds)) .map_or(0., |quad| (quad.top_left() - quad.top_right()).to_angle()); - let compass_ross_state = tool_data.pivot.is_over(input.mouse.position, angle); + let compass_rose_state = tool_data.pivot.compass_rose_state(input.mouse.position, angle); let state = // Dragging the pivot - if compass_ross_state.is_pivot() { + if compass_rose_state.is_pivot() { responses.add(DocumentMessage::StartTransaction); // tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(), true, true); @@ -775,7 +789,7 @@ impl Fsm for SelectToolFsmState { SelectToolFsmState::RotatingBounds } // Dragging the selected layers around to transform them - else if compass_ross_state.can_grab() || intersection.is_some_and(|intersection| selected.iter().any(|selected_layer| intersection.starts_with(*selected_layer, document.metadata()))) { + else if compass_rose_state.can_grab() || intersection.is_some_and(|intersection| selected.iter().any(|selected_layer| intersection.starts_with(*selected_layer, document.metadata()))) { responses.add(DocumentMessage::StartTransaction); if input.keyboard.key(select_deepest) || tool_data.nested_selection_behavior == NestedSelectionBehavior::Deepest { @@ -787,13 +801,7 @@ impl Fsm for SelectToolFsmState { tool_data.layers_dragging = selected; tool_data.get_snap_candidates(document, input); - let axis = match compass_ross_state{ - CompassRoseState::AxisX => Axis::X, - CompassRoseState::AxisY => Axis::Y, - CompassRoseState::HoverRing => Axis::None, - _ => unreachable!() - }; - + let axis = compass_rose_state.axis_type(); SelectToolFsmState::Dragging{axis} } // Dragging a selection box @@ -1042,7 +1050,7 @@ impl Fsm for SelectToolFsmState { .map(|man| man.transform * Quad::from_box(man.bounds)) .map_or(0., |quad| (quad.top_left() - quad.top_right()).to_angle()); // Dragging the pivot overrules the other operations - if tool_data.pivot.is_over(input.mouse.position, angle).is_pivot() { + if tool_data.pivot.compass_rose_state(input.mouse.position, angle).is_pivot() { cursor = MouseCursorIcon::Move; } From d7bdda39b249f57d0d3decac50beb6a8f889c64a Mon Sep 17 00:00:00 2001 From: mtvare6 Date: Wed, 12 Feb 2025 09:56:09 +0530 Subject: [PATCH 05/16] Fix UI --- .../document/overlays/utility_types.rs | 251 +++++++++++++----- 1 file changed, 179 insertions(+), 72 deletions(-) diff --git a/editor/src/messages/portfolio/document/overlays/utility_types.rs b/editor/src/messages/portfolio/document/overlays/utility_types.rs index f60e2200ab..9f1b0b3be5 100644 --- a/editor/src/messages/portfolio/document/overlays/utility_types.rs +++ b/editor/src/messages/portfolio/document/overlays/utility_types.rs @@ -2,7 +2,7 @@ use super::utility_functions::overlay_canvas_context; use crate::consts::{ COLOR_OVERLAY_BLUE, COLOR_OVERLAY_GREEN, COLOR_OVERLAY_RED, COLOR_OVERLAY_WHITE, COLOR_OVERLAY_YELLOW, MANIPULATOR_GROUP_MARKER_SIZE, PIVOT_CROSSHAIR_LENGTH, PIVOT_CROSSHAIR_THICKNESS, }; -use crate::consts::{COMPASS_ROSE_ARROW_SIZE, COMPASS_ROSE_HOVER_RING_DIAMETER, COMPASS_ROSE_MAIN_RING_DIAMETER, COMPASS_ROSE_PIVOT_DIAMETER}; +use crate::consts::{COMPASS_ROSE_ARROW_SIZE, COMPASS_ROSE_HOVER_RING_DIAMETER, COMPASS_ROSE_MAIN_RING_DIAMETER, COMPASS_ROSE_PIVOT_DIAMETER, COMPASS_ROSE_RING_INNER_DIAMETER}; use crate::messages::prelude::Message; use bezier_rs::{Bezier, Subpath}; @@ -10,7 +10,7 @@ use graphene_core::renderer::Quad; use graphene_std::vector::{PointId, VectorData}; use core::borrow::Borrow; -use core::f64::consts::TAU; +use core::f64::consts::{TAU, FRAC_PI_2}; use glam::{DAffine2, DVec2}; use wasm_bindgen::JsValue; @@ -313,18 +313,114 @@ impl OverlayContext { ) } - pub fn pivot(&mut self, position: DVec2, angle: f64, show_hover_ring: bool) { - let (x, y) = (position.round() - DVec2::splat(0.5)).into(); + //pub fn pivot(&mut self, position: DVec2, angle: f64, show_hover_ring: bool) { + // let (x, y) = (position.round() - DVec2::splat(0.5)).into(); + // let uv = DVec2::from_angle(angle); + // + // self.start_dpi_aware_transform(); + // + // // Circle + // + // self.render_context.begin_path(); + // self.render_context.arc(x, y, COMPASS_ROSE_PIVOT_DIAMETER / 2., 0., TAU).expect("Failed to draw the circle"); + // self.render_context.set_fill_style_str(COLOR_OVERLAY_YELLOW); + // self.render_context.fill(); + // + // // Crosshair + // + // // Round line caps add half the stroke width to the length on each end, so we subtract that here before halving to get the radius + // let crosshair_radius = (PIVOT_CROSSHAIR_LENGTH - PIVOT_CROSSHAIR_THICKNESS) / 2.; + // + // self.render_context.set_stroke_style_str(COLOR_OVERLAY_YELLOW); + // self.render_context.set_line_cap("round"); + // + // self.render_context.begin_path(); + // self.render_context.move_to(x + crosshair_radius * uv.x, y + crosshair_radius * uv.y); + // self.render_context.line_to(x - crosshair_radius * uv.x, y - crosshair_radius * uv.y); + // self.render_context.stroke(); + // + // self.render_context.begin_path(); + // self.render_context.move_to(x - crosshair_radius * uv.y, y + crosshair_radius * uv.x); + // self.render_context.line_to(x + crosshair_radius * uv.y, y - crosshair_radius * uv.x); + // self.render_context.stroke(); + // + // self.render_context.set_line_cap("butt"); + // + // let old_line_width = self.render_context.line_width(); + // + // + // if show_hover_ring { + // let mut fill_color = graphene_std::Color::from_rgb_str(crate::consts::COLOR_OVERLAY_BLUE.strip_prefix('#').unwrap()) + // .unwrap() + // .with_alpha(0.3) + // .rgba_hex(); + // fill_color.insert(0, '#'); + // + // self.render_context.set_line_width((COMPASS_ROSE_HOVER_RING_DIAMETER - COMPASS_ROSE_MAIN_RING_DIAMETER) / 2.); + // self.render_context.begin_path(); + // self.render_context + // .arc(position.x, position.y, (COMPASS_ROSE_MAIN_RING_DIAMETER + COMPASS_ROSE_HOVER_RING_DIAMETER) / 4., 0., TAU) + // .expect("Failed to draw outer circle"); + // self.render_context.set_stroke_style_str(fill_color.as_str()); + // self.render_context.stroke(); + // } + // + // let ring_radius = COMPASS_ROSE_RING_INNER_DIAMETER / 2.; + // + // for i in 0..4 { + // let base_angle = i as f64 * FRAC_PI_2 + angle; + // let direction = DVec2::from_angle(base_angle); + // let perpendicular = DVec2::from_angle(base_angle + FRAC_PI_2); + // let color = if i % 2 == 0 { COLOR_OVERLAY_RED } else { COLOR_OVERLAY_GREEN }; + // + // let base = DVec2::new(x, y) + direction * ring_radius; + // + // self.render_context.begin_path(); + // + // let tip = base + direction * COMPASS_ROSE_ARROW_SIZE; + // let side1 = base + perpendicular * COMPASS_ROSE_ARROW_SIZE / 2.; + // let side2 = base - perpendicular * COMPASS_ROSE_ARROW_SIZE / 2.; + // + // self.render_context.move_to(tip.x, tip.y); + // self.render_context.line_to(side1.x, side1.y); + // self.render_context.line_to(side2.x, side2.y); + // + // self.render_context.close_path(); + // self.render_context.set_fill_style_str(color); + // self.render_context.fill(); + // self.render_context.set_stroke_style_str(color); + // self.render_context.stroke(); + // } + // + // self.render_context.set_line_width(1.); + // self.render_context.begin_path(); + // self.render_context + // .arc(position.x, position.y, (COMPASS_ROSE_MAIN_RING_DIAMETER + COMPASS_ROSE_RING_INNER_DIAMETER) / 4., 0., TAU) + // .expect("Failed to draw inner circle"); + // self.render_context.set_stroke_style_str(COLOR_OVERLAY_BLUE); + // self.render_context.stroke(); + // + // self.render_context.set_line_width(1.); + // self.render_context.set_stroke_style_str(COLOR_OVERLAY_BLUE); + // self.render_context.set_fill_style_str(COLOR_OVERLAY_BLUE); + // + // self.render_context.set_line_width(old_line_width); + // + // self.end_dpi_aware_transform(); + //} +pub fn pivot(&mut self, position: DVec2, angle: f64, show_hover_ring: bool) { + let (x, y) = (position.round() - DVec2::splat(0.5)).into(); let uv = DVec2::from_angle(angle); + self.start_dpi_aware_transform(); - self.start_dpi_aware_transform(); - - // Circle - self.render_context.begin_path(); - self.render_context.arc(x, y, COMPASS_ROSE_PIVOT_DIAMETER / 2., 0., TAU).expect("Failed to draw the circle"); - self.render_context.set_fill_style_str(COLOR_OVERLAY_YELLOW); - self.render_context.fill(); + // Center dot + self.render_context.begin_path(); + self.render_context + .arc(x, y, COMPASS_ROSE_PIVOT_DIAMETER / 2., 0., TAU) + .expect("Failed to draw center dot"); + self.render_context.set_fill_style_str(COLOR_OVERLAY_YELLOW); + self.render_context.fill(); // Crosshair @@ -346,67 +442,78 @@ impl OverlayContext { self.render_context.set_line_cap("butt"); - let old_line_width = self.render_context.line_width(); - - let ring_radius = (COMPASS_ROSE_MAIN_RING_DIAMETER + 1.) / 2.; - - for i in 0..4 { - let base_angle = i as f64 * TAU / 4.0 + angle; - let direction = DVec2::from_angle(base_angle); - let perpendicular = DVec2::from_angle(base_angle + TAU / 4.); - let color = if i % 2 == 0 { COLOR_OVERLAY_RED } else { COLOR_OVERLAY_GREEN }; - - let base = DVec2::new(x, y) + direction * (ring_radius - COMPASS_ROSE_ARROW_SIZE * 0.1); - - self.render_context.begin_path(); - - let tip = base + direction * COMPASS_ROSE_ARROW_SIZE; - let side1 = base + perpendicular * COMPASS_ROSE_ARROW_SIZE / 2.; - let side2 = base - perpendicular * COMPASS_ROSE_ARROW_SIZE / 2.; - - self.render_context.move_to(tip.x, tip.y); - self.render_context.line_to(side1.x, side1.y); - self.render_context.line_to(side2.x, side2.y); - - self.render_context.close_path(); - self.render_context.set_fill_style_str(color); - self.render_context.fill(); - self.render_context.set_stroke_style_str(color); - self.render_context.stroke(); - } - - if show_hover_ring { - let mut fill_color = graphene_std::Color::from_rgb_str(crate::consts::COLOR_OVERLAY_BLUE.strip_prefix('#').unwrap()) - .unwrap() - .with_alpha(0.3) - .rgba_hex(); - fill_color.insert(0, '#'); - - self.render_context.set_line_width((COMPASS_ROSE_HOVER_RING_DIAMETER - COMPASS_ROSE_MAIN_RING_DIAMETER) / 2.); - self.render_context.begin_path(); - self.render_context - .arc(position.x, position.y, (COMPASS_ROSE_MAIN_RING_DIAMETER + COMPASS_ROSE_HOVER_RING_DIAMETER) / 4., 0., TAU) - .expect("Failed to draw outer circle"); - self.render_context.set_stroke_style_str(fill_color.as_str()); - self.render_context.stroke(); - } - - self.render_context.set_line_width(2.); - self.render_context.begin_path(); - self.render_context - .arc(position.x, position.y, (COMPASS_ROSE_MAIN_RING_DIAMETER + 1.) / 2., 0., TAU) - .expect("Failed to draw inner circle"); - self.render_context.set_stroke_style_str(COLOR_OVERLAY_BLUE); - self.render_context.stroke(); - - self.render_context.set_line_width(1.); - self.render_context.set_stroke_style_str(COLOR_OVERLAY_BLUE); - self.render_context.set_fill_style_str(COLOR_OVERLAY_BLUE); - - self.render_context.set_line_width(old_line_width); - - self.end_dpi_aware_transform(); - } + // Constants for sizes (in pixels) + const HOVER_RING_OUTER_RADIUS: f64 = COMPASS_ROSE_HOVER_RING_DIAMETER / 2.; + const HOVER_RING_INNER_RADIUS: f64 = (COMPASS_ROSE_MAIN_RING_DIAMETER - 1.)/2.; + const MAIN_RING_OUTER_RADIUS: f64 = (COMPASS_ROSE_MAIN_RING_DIAMETER)/2.; + const MAIN_RING_INNER_RADIUS: f64 = (COMPASS_ROSE_RING_INNER_DIAMETER)/2.; + const ARROW_SIZE: f64 = COMPASS_ROSE_ARROW_SIZE; + const ARROW_BASE_RADIUS: f64 = HOVER_RING_INNER_RADIUS; + + // Hover ring + if show_hover_ring { + let hover_ring_stroke_width = HOVER_RING_OUTER_RADIUS - HOVER_RING_INNER_RADIUS; + let hover_ring_center_radius = (HOVER_RING_OUTER_RADIUS + HOVER_RING_INNER_RADIUS) / 2.0; + + let mut fill_color = graphene_std::Color::from_rgb_str(COLOR_OVERLAY_BLUE.strip_prefix('#').unwrap()) + .unwrap() + .with_alpha(0.3) + .rgba_hex(); + fill_color.insert(0, '#'); + + self.render_context.set_line_width(hover_ring_stroke_width); + self.render_context.begin_path(); + self.render_context + .arc(x, y, hover_ring_center_radius, 0., TAU) + .expect("Failed to draw hover ring"); + self.render_context.set_stroke_style_str(&fill_color); + self.render_context.stroke(); + } + + // Main ring + let main_ring_stroke_width = MAIN_RING_OUTER_RADIUS - MAIN_RING_INNER_RADIUS; + let main_ring_center_radius = (MAIN_RING_OUTER_RADIUS + MAIN_RING_INNER_RADIUS) / 2.0; + + self.render_context.set_line_width(main_ring_stroke_width); + self.render_context.begin_path(); + self.render_context + .arc(x, y, main_ring_center_radius, 0., TAU) + .expect("Failed to draw main ring"); + self.render_context.set_stroke_style_str(COLOR_OVERLAY_BLUE); + self.render_context.stroke(); + + + // Arrows + for i in 0..4 { + let base_angle = i as f64 * FRAC_PI_2 + angle; + let direction = DVec2::from_angle(base_angle); + let perpendicular = DVec2::from_angle(base_angle + FRAC_PI_2); + let color = if i % 2 == 0 { COLOR_OVERLAY_RED } else { COLOR_OVERLAY_GREEN }; + + // Start from slightly outside MAIN_RING_INNER_RADIUS to avoid antialiasing artifacts + let base = DVec2::new(x, y) + direction * (ARROW_BASE_RADIUS + 0.5); + + self.render_context.begin_path(); + + // Arrow tip at HOVER_RING_OUTER_RADIUS + let tip = DVec2::new(x, y) + direction * HOVER_RING_OUTER_RADIUS; + // Arrow sides form a chevron + let side1 = base + perpendicular * (ARROW_SIZE / 2.0); + let side2 = base - perpendicular * (ARROW_SIZE / 2.0); + + self.render_context.move_to(tip.x, tip.y); + self.render_context.line_to(side1.x, side1.y); + self.render_context.line_to(side2.x, side2.y); + self.render_context.close_path(); + + self.render_context.set_fill_style_str(color); + self.render_context.fill(); + self.render_context.set_stroke_style_str(color); + self.render_context.stroke(); + } + + self.end_dpi_aware_transform(); +} pub fn outline_vector(&mut self, vector_data: &VectorData, transform: DAffine2) { self.start_dpi_aware_transform(); From f635a9366a0eb9caa96f89afdec25be36b00a07c Mon Sep 17 00:00:00 2001 From: mtvare6 Date: Wed, 12 Feb 2025 14:21:06 +0530 Subject: [PATCH 06/16] Fix crash --- .../document/overlays/utility_types.rs | 250 +++++------------- .../tool/common_functionality/pivot.rs | 10 +- .../tool/tool_messages/select_tool.rs | 4 +- 3 files changed, 78 insertions(+), 186 deletions(-) diff --git a/editor/src/messages/portfolio/document/overlays/utility_types.rs b/editor/src/messages/portfolio/document/overlays/utility_types.rs index 9f1b0b3be5..def58a5f44 100644 --- a/editor/src/messages/portfolio/document/overlays/utility_types.rs +++ b/editor/src/messages/portfolio/document/overlays/utility_types.rs @@ -10,7 +10,7 @@ use graphene_core::renderer::Quad; use graphene_std::vector::{PointId, VectorData}; use core::borrow::Borrow; -use core::f64::consts::{TAU, FRAC_PI_2}; +use core::f64::consts::{FRAC_PI_2, TAU}; use glam::{DAffine2, DVec2}; use wasm_bindgen::JsValue; @@ -313,114 +313,16 @@ impl OverlayContext { ) } - //pub fn pivot(&mut self, position: DVec2, angle: f64, show_hover_ring: bool) { - // let (x, y) = (position.round() - DVec2::splat(0.5)).into(); - // let uv = DVec2::from_angle(angle); - // - // self.start_dpi_aware_transform(); - // - // // Circle - // - // self.render_context.begin_path(); - // self.render_context.arc(x, y, COMPASS_ROSE_PIVOT_DIAMETER / 2., 0., TAU).expect("Failed to draw the circle"); - // self.render_context.set_fill_style_str(COLOR_OVERLAY_YELLOW); - // self.render_context.fill(); - // - // // Crosshair - // - // // Round line caps add half the stroke width to the length on each end, so we subtract that here before halving to get the radius - // let crosshair_radius = (PIVOT_CROSSHAIR_LENGTH - PIVOT_CROSSHAIR_THICKNESS) / 2.; - // - // self.render_context.set_stroke_style_str(COLOR_OVERLAY_YELLOW); - // self.render_context.set_line_cap("round"); - // - // self.render_context.begin_path(); - // self.render_context.move_to(x + crosshair_radius * uv.x, y + crosshair_radius * uv.y); - // self.render_context.line_to(x - crosshair_radius * uv.x, y - crosshair_radius * uv.y); - // self.render_context.stroke(); - // - // self.render_context.begin_path(); - // self.render_context.move_to(x - crosshair_radius * uv.y, y + crosshair_radius * uv.x); - // self.render_context.line_to(x + crosshair_radius * uv.y, y - crosshair_radius * uv.x); - // self.render_context.stroke(); - // - // self.render_context.set_line_cap("butt"); - // - // let old_line_width = self.render_context.line_width(); - // - // - // if show_hover_ring { - // let mut fill_color = graphene_std::Color::from_rgb_str(crate::consts::COLOR_OVERLAY_BLUE.strip_prefix('#').unwrap()) - // .unwrap() - // .with_alpha(0.3) - // .rgba_hex(); - // fill_color.insert(0, '#'); - // - // self.render_context.set_line_width((COMPASS_ROSE_HOVER_RING_DIAMETER - COMPASS_ROSE_MAIN_RING_DIAMETER) / 2.); - // self.render_context.begin_path(); - // self.render_context - // .arc(position.x, position.y, (COMPASS_ROSE_MAIN_RING_DIAMETER + COMPASS_ROSE_HOVER_RING_DIAMETER) / 4., 0., TAU) - // .expect("Failed to draw outer circle"); - // self.render_context.set_stroke_style_str(fill_color.as_str()); - // self.render_context.stroke(); - // } - // - // let ring_radius = COMPASS_ROSE_RING_INNER_DIAMETER / 2.; - // - // for i in 0..4 { - // let base_angle = i as f64 * FRAC_PI_2 + angle; - // let direction = DVec2::from_angle(base_angle); - // let perpendicular = DVec2::from_angle(base_angle + FRAC_PI_2); - // let color = if i % 2 == 0 { COLOR_OVERLAY_RED } else { COLOR_OVERLAY_GREEN }; - // - // let base = DVec2::new(x, y) + direction * ring_radius; - // - // self.render_context.begin_path(); - // - // let tip = base + direction * COMPASS_ROSE_ARROW_SIZE; - // let side1 = base + perpendicular * COMPASS_ROSE_ARROW_SIZE / 2.; - // let side2 = base - perpendicular * COMPASS_ROSE_ARROW_SIZE / 2.; - // - // self.render_context.move_to(tip.x, tip.y); - // self.render_context.line_to(side1.x, side1.y); - // self.render_context.line_to(side2.x, side2.y); - // - // self.render_context.close_path(); - // self.render_context.set_fill_style_str(color); - // self.render_context.fill(); - // self.render_context.set_stroke_style_str(color); - // self.render_context.stroke(); - // } - // - // self.render_context.set_line_width(1.); - // self.render_context.begin_path(); - // self.render_context - // .arc(position.x, position.y, (COMPASS_ROSE_MAIN_RING_DIAMETER + COMPASS_ROSE_RING_INNER_DIAMETER) / 4., 0., TAU) - // .expect("Failed to draw inner circle"); - // self.render_context.set_stroke_style_str(COLOR_OVERLAY_BLUE); - // self.render_context.stroke(); - // - // self.render_context.set_line_width(1.); - // self.render_context.set_stroke_style_str(COLOR_OVERLAY_BLUE); - // self.render_context.set_fill_style_str(COLOR_OVERLAY_BLUE); - // - // self.render_context.set_line_width(old_line_width); - // - // self.end_dpi_aware_transform(); - //} -pub fn pivot(&mut self, position: DVec2, angle: f64, show_hover_ring: bool) { - let (x, y) = (position.round() - DVec2::splat(0.5)).into(); + pub fn pivot(&mut self, position: DVec2, angle: f64, show_hover_ring: bool) { + let (x, y) = (position.round() - DVec2::splat(0.5)).into(); let uv = DVec2::from_angle(angle); - self.start_dpi_aware_transform(); - + self.start_dpi_aware_transform(); - // Center dot - self.render_context.begin_path(); - self.render_context - .arc(x, y, COMPASS_ROSE_PIVOT_DIAMETER / 2., 0., TAU) - .expect("Failed to draw center dot"); - self.render_context.set_fill_style_str(COLOR_OVERLAY_YELLOW); - self.render_context.fill(); + // Center dot + self.render_context.begin_path(); + self.render_context.arc(x, y, COMPASS_ROSE_PIVOT_DIAMETER / 2., 0., TAU).expect("Failed to draw center dot"); + self.render_context.set_fill_style_str(COLOR_OVERLAY_YELLOW); + self.render_context.fill(); // Crosshair @@ -442,78 +344,68 @@ pub fn pivot(&mut self, position: DVec2, angle: f64, show_hover_ring: bool) { self.render_context.set_line_cap("butt"); - // Constants for sizes (in pixels) - const HOVER_RING_OUTER_RADIUS: f64 = COMPASS_ROSE_HOVER_RING_DIAMETER / 2.; - const HOVER_RING_INNER_RADIUS: f64 = (COMPASS_ROSE_MAIN_RING_DIAMETER - 1.)/2.; - const MAIN_RING_OUTER_RADIUS: f64 = (COMPASS_ROSE_MAIN_RING_DIAMETER)/2.; - const MAIN_RING_INNER_RADIUS: f64 = (COMPASS_ROSE_RING_INNER_DIAMETER)/2.; - const ARROW_SIZE: f64 = COMPASS_ROSE_ARROW_SIZE; - const ARROW_BASE_RADIUS: f64 = HOVER_RING_INNER_RADIUS; - - // Hover ring - if show_hover_ring { - let hover_ring_stroke_width = HOVER_RING_OUTER_RADIUS - HOVER_RING_INNER_RADIUS; - let hover_ring_center_radius = (HOVER_RING_OUTER_RADIUS + HOVER_RING_INNER_RADIUS) / 2.0; - - let mut fill_color = graphene_std::Color::from_rgb_str(COLOR_OVERLAY_BLUE.strip_prefix('#').unwrap()) - .unwrap() - .with_alpha(0.3) - .rgba_hex(); - fill_color.insert(0, '#'); - - self.render_context.set_line_width(hover_ring_stroke_width); - self.render_context.begin_path(); - self.render_context - .arc(x, y, hover_ring_center_radius, 0., TAU) - .expect("Failed to draw hover ring"); - self.render_context.set_stroke_style_str(&fill_color); - self.render_context.stroke(); - } - - // Main ring - let main_ring_stroke_width = MAIN_RING_OUTER_RADIUS - MAIN_RING_INNER_RADIUS; - let main_ring_center_radius = (MAIN_RING_OUTER_RADIUS + MAIN_RING_INNER_RADIUS) / 2.0; - - self.render_context.set_line_width(main_ring_stroke_width); - self.render_context.begin_path(); - self.render_context - .arc(x, y, main_ring_center_radius, 0., TAU) - .expect("Failed to draw main ring"); - self.render_context.set_stroke_style_str(COLOR_OVERLAY_BLUE); - self.render_context.stroke(); - - - // Arrows - for i in 0..4 { - let base_angle = i as f64 * FRAC_PI_2 + angle; - let direction = DVec2::from_angle(base_angle); - let perpendicular = DVec2::from_angle(base_angle + FRAC_PI_2); - let color = if i % 2 == 0 { COLOR_OVERLAY_RED } else { COLOR_OVERLAY_GREEN }; - - // Start from slightly outside MAIN_RING_INNER_RADIUS to avoid antialiasing artifacts - let base = DVec2::new(x, y) + direction * (ARROW_BASE_RADIUS + 0.5); - - self.render_context.begin_path(); - - // Arrow tip at HOVER_RING_OUTER_RADIUS - let tip = DVec2::new(x, y) + direction * HOVER_RING_OUTER_RADIUS; - // Arrow sides form a chevron - let side1 = base + perpendicular * (ARROW_SIZE / 2.0); - let side2 = base - perpendicular * (ARROW_SIZE / 2.0); - - self.render_context.move_to(tip.x, tip.y); - self.render_context.line_to(side1.x, side1.y); - self.render_context.line_to(side2.x, side2.y); - self.render_context.close_path(); - - self.render_context.set_fill_style_str(color); - self.render_context.fill(); - self.render_context.set_stroke_style_str(color); - self.render_context.stroke(); - } - - self.end_dpi_aware_transform(); -} + const HOVER_RING_OUTER_RADIUS: f64 = COMPASS_ROSE_HOVER_RING_DIAMETER / 2.; + const HOVER_RING_INNER_RADIUS: f64 = (COMPASS_ROSE_MAIN_RING_DIAMETER) / 2.; + const MAIN_RING_OUTER_RADIUS: f64 = (COMPASS_ROSE_MAIN_RING_DIAMETER) / 2.; + const MAIN_RING_INNER_RADIUS: f64 = (COMPASS_ROSE_RING_INNER_DIAMETER) / 2.; + const ARROW_RADIUS: f64 = COMPASS_ROSE_ARROW_SIZE/2.; + + // Hover ring + if show_hover_ring { + let hover_ring_stroke_width = HOVER_RING_OUTER_RADIUS - HOVER_RING_INNER_RADIUS; + let hover_ring_center_radius = (HOVER_RING_OUTER_RADIUS + HOVER_RING_INNER_RADIUS) / 2.0; + + let mut fill_color = graphene_std::Color::from_rgb_str(COLOR_OVERLAY_BLUE.strip_prefix('#').unwrap()).unwrap().with_alpha(0.3).rgba_hex(); + fill_color.insert(0, '#'); + + self.render_context.set_line_width(hover_ring_stroke_width); + self.render_context.begin_path(); + self.render_context.arc(x, y, hover_ring_center_radius, 0., TAU).expect("Failed to draw hover ring"); + self.render_context.set_stroke_style_str(&fill_color); + self.render_context.stroke(); + } + + // Main ring + let main_ring_stroke_width = MAIN_RING_OUTER_RADIUS - MAIN_RING_INNER_RADIUS; + let main_ring_center_radius = (MAIN_RING_OUTER_RADIUS + MAIN_RING_INNER_RADIUS) / 2.0; + + self.render_context.set_line_width(main_ring_stroke_width); + self.render_context.begin_path(); + self.render_context.arc(x, y, main_ring_center_radius, 0., TAU).expect("Failed to draw main ring"); + self.render_context.set_stroke_style_str(COLOR_OVERLAY_BLUE); + self.render_context.stroke(); + + // Arrows + for i in 0..4 { + let base_angle = i as f64 * FRAC_PI_2 + angle; + let direction = DVec2::from_angle(base_angle); + let color = if i % 2 == 0 { COLOR_OVERLAY_RED } else { COLOR_OVERLAY_GREEN }; + + let center = DVec2::new(x, y); + + let tip = center + direction * HOVER_RING_OUTER_RADIUS; + let base = center + direction * (MAIN_RING_INNER_RADIUS+MAIN_RING_OUTER_RADIUS)/2.; + + let r = ( ARROW_RADIUS.powi(2) + MAIN_RING_INNER_RADIUS.powi(2) ).sqrt(); + let (cos, sin) = (MAIN_RING_INNER_RADIUS / r, ARROW_RADIUS / r); + let side1 = center + r * DVec2::new(cos * direction.x - sin * direction.y, sin * direction.x + direction.y * cos); + let side2 = center + r * DVec2::new(cos * direction.x + sin * direction.y, -sin * direction.x + direction.y * cos); + + self.render_context.begin_path(); + self.render_context.move_to(tip.x, tip.y); + self.render_context.line_to(side1.x, side1.y); + self.render_context.line_to(base.x, base.y); + self.render_context.line_to(side2.x, side2.y); + self.render_context.close_path(); + + self.render_context.set_fill_style_str(color); + self.render_context.fill(); + self.render_context.set_stroke_style_str(color); + self.render_context.stroke(); + } + + self.end_dpi_aware_transform(); + } pub fn outline_vector(&mut self, vector_data: &VectorData, transform: DAffine2) { self.start_dpi_aware_transform(); diff --git a/editor/src/messages/tool/common_functionality/pivot.rs b/editor/src/messages/tool/common_functionality/pivot.rs index a49f7b8ebf..e477846f73 100644 --- a/editor/src/messages/tool/common_functionality/pivot.rs +++ b/editor/src/messages/tool/common_functionality/pivot.rs @@ -48,12 +48,12 @@ impl CompassRoseState { matches!(self, Self::HoverRing | Self::MainRing) } - pub fn axis_type(&self) -> Axis { + pub fn axis_type(&self) -> Option { match self { - CompassRoseState::AxisX => Axis::X, - CompassRoseState::AxisY => Axis::Y, - CompassRoseState::HoverRing => Axis::None, - _ => unreachable!(), + CompassRoseState::AxisX => Some(Axis::X), + CompassRoseState::AxisY => Some(Axis::Y), + CompassRoseState::HoverRing => Some(Axis::None), + _ => None, } } } diff --git a/editor/src/messages/tool/tool_messages/select_tool.rs b/editor/src/messages/tool/tool_messages/select_tool.rs index 8b30189dcd..f13ca1e31e 100644 --- a/editor/src/messages/tool/tool_messages/select_tool.rs +++ b/editor/src/messages/tool/tool_messages/select_tool.rs @@ -563,7 +563,7 @@ impl Fsm for SelectToolFsmState { let (direction, color) = match axis { Axis::X => (e0, COLOR_OVERLAY_RED), Axis::Y => (e0.perp(), COLOR_OVERLAY_GREEN), - _ => unreachable!(), + _ => panic!("WTF 0"), }; let viewport_diagonal = input.viewport_bounds.size().length(); @@ -801,7 +801,7 @@ impl Fsm for SelectToolFsmState { tool_data.layers_dragging = selected; tool_data.get_snap_candidates(document, input); - let axis = compass_rose_state.axis_type(); + let axis = compass_rose_state.axis_type().unwrap_or(Axis::None); SelectToolFsmState::Dragging{axis} } // Dragging a selection box From ad4f229c0e405d920decccb9aa5e161db4edd6b6 Mon Sep 17 00:00:00 2001 From: mtvare6 Date: Wed, 12 Feb 2025 15:14:47 +0530 Subject: [PATCH 07/16] More polish --- .../document/overlays/utility_types.rs | 36 ++++++++++--------- .../tool/common_functionality/pivot.rs | 26 +++++++++----- .../tool/tool_messages/select_tool.rs | 4 +-- 3 files changed, 39 insertions(+), 27 deletions(-) diff --git a/editor/src/messages/portfolio/document/overlays/utility_types.rs b/editor/src/messages/portfolio/document/overlays/utility_types.rs index def58a5f44..1bd5c3a84b 100644 --- a/editor/src/messages/portfolio/document/overlays/utility_types.rs +++ b/editor/src/messages/portfolio/document/overlays/utility_types.rs @@ -313,14 +313,15 @@ impl OverlayContext { ) } - pub fn pivot(&mut self, position: DVec2, angle: f64, show_hover_ring: bool) { - let (x, y) = (position.round() - DVec2::splat(0.5)).into(); + pub fn pivot(&mut self, pivot_position: DVec2, compass_center: DVec2, angle: f64, show_hover_ring: bool) { + let (pivot_x, pivot_y) = (pivot_position.round() - DVec2::splat(0.5)).into(); + let (compass_x, compass_y) = (compass_center.round() - DVec2::splat(0.5)).into(); let uv = DVec2::from_angle(angle); self.start_dpi_aware_transform(); // Center dot self.render_context.begin_path(); - self.render_context.arc(x, y, COMPASS_ROSE_PIVOT_DIAMETER / 2., 0., TAU).expect("Failed to draw center dot"); + self.render_context.arc(pivot_x, pivot_y, COMPASS_ROSE_PIVOT_DIAMETER / 2., 0., TAU).expect("Failed to draw center dot"); self.render_context.set_fill_style_str(COLOR_OVERLAY_YELLOW); self.render_context.fill(); @@ -333,22 +334,23 @@ impl OverlayContext { self.render_context.set_line_cap("round"); self.render_context.begin_path(); - self.render_context.move_to(x + crosshair_radius * uv.x, y + crosshair_radius * uv.y); - self.render_context.line_to(x - crosshair_radius * uv.x, y - crosshair_radius * uv.y); + self.render_context.move_to(pivot_x + crosshair_radius * uv.x, pivot_y + crosshair_radius * uv.y); + self.render_context.line_to(pivot_x - crosshair_radius * uv.x, pivot_y - crosshair_radius * uv.y); self.render_context.stroke(); self.render_context.begin_path(); - self.render_context.move_to(x - crosshair_radius * uv.y, y + crosshair_radius * uv.x); - self.render_context.line_to(x + crosshair_radius * uv.y, y - crosshair_radius * uv.x); + self.render_context.move_to(pivot_x - crosshair_radius * uv.y, pivot_y + crosshair_radius * uv.x); + self.render_context.line_to(pivot_x + crosshair_radius * uv.y, pivot_y - crosshair_radius * uv.x); self.render_context.stroke(); self.render_context.set_line_cap("butt"); + let old_line_width = self.render_context.line_width(); const HOVER_RING_OUTER_RADIUS: f64 = COMPASS_ROSE_HOVER_RING_DIAMETER / 2.; - const HOVER_RING_INNER_RADIUS: f64 = (COMPASS_ROSE_MAIN_RING_DIAMETER) / 2.; - const MAIN_RING_OUTER_RADIUS: f64 = (COMPASS_ROSE_MAIN_RING_DIAMETER) / 2.; - const MAIN_RING_INNER_RADIUS: f64 = (COMPASS_ROSE_RING_INNER_DIAMETER) / 2.; - const ARROW_RADIUS: f64 = COMPASS_ROSE_ARROW_SIZE/2.; + const HOVER_RING_INNER_RADIUS: f64 = COMPASS_ROSE_MAIN_RING_DIAMETER / 2.; + const MAIN_RING_OUTER_RADIUS: f64 = COMPASS_ROSE_MAIN_RING_DIAMETER / 2.; + const MAIN_RING_INNER_RADIUS: f64 = COMPASS_ROSE_RING_INNER_DIAMETER / 2.; + const ARROW_RADIUS: f64 = COMPASS_ROSE_ARROW_SIZE / 2.; // Hover ring if show_hover_ring { @@ -360,7 +362,7 @@ impl OverlayContext { self.render_context.set_line_width(hover_ring_stroke_width); self.render_context.begin_path(); - self.render_context.arc(x, y, hover_ring_center_radius, 0., TAU).expect("Failed to draw hover ring"); + self.render_context.arc(compass_x, compass_y, hover_ring_center_radius, 0., TAU).expect("Failed to draw hover ring"); self.render_context.set_stroke_style_str(&fill_color); self.render_context.stroke(); } @@ -371,22 +373,23 @@ impl OverlayContext { self.render_context.set_line_width(main_ring_stroke_width); self.render_context.begin_path(); - self.render_context.arc(x, y, main_ring_center_radius, 0., TAU).expect("Failed to draw main ring"); + self.render_context.arc(compass_x, compass_y, main_ring_center_radius, 0., TAU).expect("Failed to draw main ring"); self.render_context.set_stroke_style_str(COLOR_OVERLAY_BLUE); self.render_context.stroke(); // Arrows + self.render_context.set_line_width(0.01); for i in 0..4 { let base_angle = i as f64 * FRAC_PI_2 + angle; let direction = DVec2::from_angle(base_angle); let color = if i % 2 == 0 { COLOR_OVERLAY_RED } else { COLOR_OVERLAY_GREEN }; - let center = DVec2::new(x, y); + let center = DVec2::new(compass_x, compass_y); let tip = center + direction * HOVER_RING_OUTER_RADIUS; - let base = center + direction * (MAIN_RING_INNER_RADIUS+MAIN_RING_OUTER_RADIUS)/2.; + let base = center + direction * (MAIN_RING_INNER_RADIUS + MAIN_RING_OUTER_RADIUS) / 2.; - let r = ( ARROW_RADIUS.powi(2) + MAIN_RING_INNER_RADIUS.powi(2) ).sqrt(); + let r = (ARROW_RADIUS.powi(2) + MAIN_RING_INNER_RADIUS.powi(2)).sqrt(); let (cos, sin) = (MAIN_RING_INNER_RADIUS / r, ARROW_RADIUS / r); let side1 = center + r * DVec2::new(cos * direction.x - sin * direction.y, sin * direction.x + direction.y * cos); let side2 = center + r * DVec2::new(cos * direction.x + sin * direction.y, -sin * direction.x + direction.y * cos); @@ -403,6 +406,7 @@ impl OverlayContext { self.render_context.set_stroke_style_str(color); self.render_context.stroke(); } + self.render_context.set_line_width(old_line_width); self.end_dpi_aware_transform(); } diff --git a/editor/src/messages/tool/common_functionality/pivot.rs b/editor/src/messages/tool/common_functionality/pivot.rs index e477846f73..ab81067174 100644 --- a/editor/src/messages/tool/common_functionality/pivot.rs +++ b/editor/src/messages/tool/common_functionality/pivot.rs @@ -91,8 +91,8 @@ impl Pivot { layer_transform * bounds_transform } - pub fn get_position(&self) -> Option { - self.pivot + pub fn get_compass_position(&self) -> DVec2 { + self.transform_from_normalized.transform_point2(DVec2::splat(0.5)) } /// Recomputes the pivot position and transform. @@ -138,7 +138,8 @@ impl Pivot { pub fn update_pivot(&mut self, document: &DocumentMessageHandler, overlay_context: &mut OverlayContext, angle: f64, show_hover_ring: bool) { self.recalculate_pivot(document); if let Some(pivot) = self.pivot { - overlay_context.pivot(pivot, angle, show_hover_ring); + let compass_center = self.get_compass_position(); + overlay_context.pivot(pivot, compass_center, angle, show_hover_ring); } } @@ -181,14 +182,22 @@ impl Pivot { match self.pivot { None => CompassRoseState::None, Some(pivot) => { - let distance_squared = mouse.distance_squared(pivot); + let compass_center = self.get_compass_position(); + + let compass_distance_squared = mouse.distance_squared(compass_center); + let pivot_distance_squared = mouse.distance_squared(pivot); + + if pivot_distance_squared < (COMPASS_ROSE_PIVOT_DIAMETER / 2.).powi(2) { + return CompassRoseState::Pivot; + } + let ring_radius = (COMPASS_ROSE_MAIN_RING_DIAMETER + 1.) / 2.; for i in 0..4 { let base_angle = i as f64 * FRAC_PI_2 + angle; let direction = DVec2::from_angle(base_angle); - let arrow_base = pivot + direction * ring_radius; + let arrow_base = compass_center + direction * ring_radius; let arrow_tip = arrow_base + direction * COMPASS_ROSE_ARROW_SIZE; let perp = direction.perp() * COMPASS_ROSE_ARROW_SIZE / 2.; @@ -199,11 +208,9 @@ impl Pivot { return if i % 2 == 0 { CompassRoseState::AxisX } else { CompassRoseState::AxisY }; } } - if distance_squared < (COMPASS_ROSE_PIVOT_DIAMETER / 2.).powi(2) { - CompassRoseState::Pivot - } else if (COMPASS_ROSE_RING_INNER_DIAMETER / 2.).powi(2) < distance_squared && distance_squared < (COMPASS_ROSE_MAIN_RING_DIAMETER / 2.).powi(2) { + if (COMPASS_ROSE_RING_INNER_DIAMETER / 2.).powi(2) < compass_distance_squared && compass_distance_squared < (COMPASS_ROSE_MAIN_RING_DIAMETER / 2.).powi(2) { CompassRoseState::MainRing - } else if (COMPASS_ROSE_MAIN_RING_DIAMETER / 2.).powi(2) < distance_squared && distance_squared < (COMPASS_ROSE_HOVER_RING_DIAMETER / 2.).powi(2) { + } else if (COMPASS_ROSE_MAIN_RING_DIAMETER / 2.).powi(2) < compass_distance_squared && compass_distance_squared < (COMPASS_ROSE_HOVER_RING_DIAMETER / 2.).powi(2) { CompassRoseState::HoverRing } else { CompassRoseState::None @@ -212,6 +219,7 @@ impl Pivot { } } } + fn is_point_in_triangle(p: DVec2, a: DVec2, b: DVec2, c: DVec2) -> bool { // Calculate barycentric coordinates let v0 = c - a; diff --git a/editor/src/messages/tool/tool_messages/select_tool.rs b/editor/src/messages/tool/tool_messages/select_tool.rs index f13ca1e31e..af30c16514 100644 --- a/editor/src/messages/tool/tool_messages/select_tool.rs +++ b/editor/src/messages/tool/tool_messages/select_tool.rs @@ -559,11 +559,11 @@ impl Fsm for SelectToolFsmState { .map(|man| man.transform * Quad::from_box(man.bounds)) .map_or(DVec2::X, |quad| (quad.top_left() - quad.top_right()).normalize_or(DVec2::X)); - let origin = tool_data.pivot.get_position().unwrap_or(tool_data.drag_start); + let origin = tool_data.pivot.get_compass_position(); let (direction, color) = match axis { Axis::X => (e0, COLOR_OVERLAY_RED), Axis::Y => (e0.perp(), COLOR_OVERLAY_GREEN), - _ => panic!("WTF 0"), + _ => unreachable!(), }; let viewport_diagonal = input.viewport_bounds.size().length(); From 201d66767b6ccdb080d7b4b4ffe4fbbbcd776edb Mon Sep 17 00:00:00 2001 From: mtvare6 Date: Wed, 12 Feb 2025 18:46:39 +0530 Subject: [PATCH 08/16] Rework arrow to use different selection method --- editor/src/consts.rs | 2 + .../tool/common_functionality/pivot.rs | 66 ++++++------------- .../tool/tool_messages/select_tool.rs | 48 ++++++++++---- 3 files changed, 57 insertions(+), 59 deletions(-) diff --git a/editor/src/consts.rs b/editor/src/consts.rs index 1dc454309f..eda0558ac1 100644 --- a/editor/src/consts.rs +++ b/editor/src/consts.rs @@ -57,6 +57,8 @@ pub const SELECTION_DRAG_ANGLE: f64 = 90.; pub const PIVOT_CROSSHAIR_THICKNESS: f64 = 1.; pub const PIVOT_CROSSHAIR_LENGTH: f64 = 9.; +pub const COMPASS_ROSE_ANGLE_WIDTH: f64 = 20.; // Must be less than 45 + pub const COMPASS_ROSE_PIVOT_DIAMETER: f64 = 5.; pub const COMPASS_ROSE_RING_INNER_DIAMETER: f64 = 13.; pub const COMPASS_ROSE_MAIN_RING_DIAMETER: f64 = 15.; diff --git a/editor/src/messages/tool/common_functionality/pivot.rs b/editor/src/messages/tool/common_functionality/pivot.rs index ab81067174..f663382ec2 100644 --- a/editor/src/messages/tool/common_functionality/pivot.rs +++ b/editor/src/messages/tool/common_functionality/pivot.rs @@ -1,7 +1,9 @@ //! Handler for the pivot overlay visible on the selected layer(s) whilst using the Select tool which controls the center of rotation/scale and origin of the layer. use super::graph_modification_utils; -use crate::consts::{COMPASS_ROSE_ARROW_SIZE, COMPASS_ROSE_HOVER_RING_DIAMETER, COMPASS_ROSE_MAIN_RING_DIAMETER, COMPASS_ROSE_PIVOT_DIAMETER, COMPASS_ROSE_RING_INNER_DIAMETER}; +use crate::consts::{ + COMPASS_ROSE_ANGLE_WIDTH, COMPASS_ROSE_ARROW_SIZE, COMPASS_ROSE_HOVER_RING_DIAMETER, COMPASS_ROSE_MAIN_RING_DIAMETER, COMPASS_ROSE_PIVOT_DIAMETER, COMPASS_ROSE_RING_INNER_DIAMETER, +}; use crate::messages::layout::utility_types::widget_prelude::*; use crate::messages::portfolio::document::overlays::utility_types::OverlayContext; use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; @@ -9,13 +11,12 @@ use crate::messages::prelude::*; use glam::{DAffine2, DVec2}; use std::collections::VecDeque; -use std::f64::consts::FRAC_PI_2; +use std::f64::consts::{FRAC_PI_2, FRAC_PI_4}; #[derive(Clone, Debug, PartialEq)] pub enum CompassRoseState { Pivot, - HoverRing, - MainRing, + Ring, AxisX, AxisY, None, @@ -37,7 +38,7 @@ impl Axis { impl CompassRoseState { pub fn can_grab(&self) -> bool { - matches!(self, Self::HoverRing | Self::AxisX | Self::AxisY) + matches!(self, Self::Ring | Self::AxisX | Self::AxisY) } pub fn is_pivot(&self) -> bool { @@ -45,14 +46,14 @@ impl CompassRoseState { } pub fn is_ring(&self) -> bool { - matches!(self, Self::HoverRing | Self::MainRing) + matches!(self, Self::Ring) } pub fn axis_type(&self) -> Option { match self { CompassRoseState::AxisX => Some(Axis::X), CompassRoseState::AxisY => Some(Axis::Y), - CompassRoseState::HoverRing => Some(Axis::None), + CompassRoseState::Ring => Some(Axis::None), _ => None, } } @@ -191,27 +192,18 @@ impl Pivot { return CompassRoseState::Pivot; } - let ring_radius = (COMPASS_ROSE_MAIN_RING_DIAMETER + 1.) / 2.; - - for i in 0..4 { - let base_angle = i as f64 * FRAC_PI_2 + angle; - let direction = DVec2::from_angle(base_angle); - - let arrow_base = compass_center + direction * ring_radius; - let arrow_tip = arrow_base + direction * COMPASS_ROSE_ARROW_SIZE; - - let perp = direction.perp() * COMPASS_ROSE_ARROW_SIZE / 2.; - let side1 = arrow_base + perp; - let side2 = arrow_base - perp; - - if is_point_in_triangle(mouse, arrow_tip, side1, side2) { - return if i % 2 == 0 { CompassRoseState::AxisX } else { CompassRoseState::AxisY }; + if (COMPASS_ROSE_RING_INNER_DIAMETER / 2.).powi(2) < compass_distance_squared && compass_distance_squared < (COMPASS_ROSE_HOVER_RING_DIAMETER / 2.).powi(2) { + let angle = (mouse - compass_center).to_angle().abs(); + let resolved_angle = (FRAC_PI_2 - angle).abs(); + let width = COMPASS_ROSE_ANGLE_WIDTH.to_radians(); + + if resolved_angle < width { + CompassRoseState::AxisY + } else if resolved_angle > (FRAC_PI_2 - width) { + CompassRoseState::AxisX + } else { + CompassRoseState::Ring } - } - if (COMPASS_ROSE_RING_INNER_DIAMETER / 2.).powi(2) < compass_distance_squared && compass_distance_squared < (COMPASS_ROSE_MAIN_RING_DIAMETER / 2.).powi(2) { - CompassRoseState::MainRing - } else if (COMPASS_ROSE_MAIN_RING_DIAMETER / 2.).powi(2) < compass_distance_squared && compass_distance_squared < (COMPASS_ROSE_HOVER_RING_DIAMETER / 2.).powi(2) { - CompassRoseState::HoverRing } else { CompassRoseState::None } @@ -219,23 +211,3 @@ impl Pivot { } } } - -fn is_point_in_triangle(p: DVec2, a: DVec2, b: DVec2, c: DVec2) -> bool { - // Calculate barycentric coordinates - let v0 = c - a; - let v1 = b - a; - let v2 = p - a; - - let dot00 = v0.dot(v0); - let dot01 = v0.dot(v1); - let dot02 = v0.dot(v2); - let dot11 = v1.dot(v1); - let dot12 = v1.dot(v2); - - let inv_denom = 1. / (dot00 * dot11 - dot01 * dot01); - let u = (dot11 * dot02 - dot01 * dot12) * inv_denom; - let v = (dot00 * dot12 - dot01 * dot02) * inv_denom; - - // Check if point is inside triangle - u >= 0. && v >= 0. && u + v <= 1. -} diff --git a/editor/src/messages/tool/tool_messages/select_tool.rs b/editor/src/messages/tool/tool_messages/select_tool.rs index af30c16514..216f07a70f 100644 --- a/editor/src/messages/tool/tool_messages/select_tool.rs +++ b/editor/src/messages/tool/tool_messages/select_tool.rs @@ -274,7 +274,7 @@ impl ToolTransition for SelectTool { enum SelectToolFsmState { Ready { selection: NestedSelectionBehavior }, Drawing { selection_shape: SelectionShapeType }, - Dragging { axis: Axis }, + Dragging { axis: Axis, using_compass: bool }, ResizingBounds, SkewingBounds, RotatingBounds, @@ -546,12 +546,23 @@ impl Fsm for SelectToolFsmState { let angle = bounds .map(|bounds| transform * Quad::from_box(bounds)) .map_or(0., |quad| (quad.top_left() - quad.top_right()).to_angle()); + let mouse_position = input.mouse.position; let compass_rose_state = tool_data.pivot.compass_rose_state(mouse_position, angle); - let show_hover_ring = compass_rose_state.is_ring(); + + let show_hover_ring = if let SelectToolFsmState::Dragging { axis, using_compass } = self { + using_compass && !axis.is_constraint() + } else { + compass_rose_state.is_ring() + }; // Update pivot tool_data.pivot.update_pivot(document, &mut overlay_context, angle, show_hover_ring); - if let SelectToolFsmState::Dragging { axis } = self { + let axis_state = if let SelectToolFsmState::Dragging { axis, .. } = self { + Some((axis, false)) + } else { + compass_rose_state.axis_type().and_then(|axis| if axis.is_constraint() { Some((axis, true)) } else { None }) + }; + if let Some((axis, hover)) = axis_state { if axis.is_constraint() { let e0 = tool_data .bounding_box_manager @@ -568,7 +579,14 @@ impl Fsm for SelectToolFsmState { let viewport_diagonal = input.viewport_bounds.size().length(); - overlay_context.line(origin - direction * viewport_diagonal, origin + direction * viewport_diagonal, Some(color)); + if hover { + let color_string = &graphene_std::Color::from_rgb_str(color.strip_prefix('#').unwrap()).unwrap().with_alpha(0.25).rgba_hex(); + let color = &format!("#{}", color_string); + + overlay_context.line(origin - direction * viewport_diagonal, origin + direction * viewport_diagonal, Some(color)); + } else { + overlay_context.line(origin - direction * viewport_diagonal, origin + direction * viewport_diagonal, Some(color)); + } } } @@ -801,8 +819,11 @@ impl Fsm for SelectToolFsmState { tool_data.layers_dragging = selected; tool_data.get_snap_candidates(document, input); - let axis = compass_rose_state.axis_type().unwrap_or(Axis::None); - SelectToolFsmState::Dragging{axis} + let axis = compass_rose_state.axis_type(); + match axis { + Some(axis) => SelectToolFsmState::Dragging{axis, using_compass: true}, + None => SelectToolFsmState::Dragging{axis: Axis::None, using_compass: false} + } } // Dragging a selection box else { @@ -823,7 +844,7 @@ impl Fsm for SelectToolFsmState { tool_data.get_snap_candidates(document, input); responses.add(DocumentMessage::StartTransaction); - SelectToolFsmState::Dragging { axis: Axis::None } + SelectToolFsmState::Dragging { axis: Axis::None, using_compass: false } } else { let selection_shape = if input.keyboard.key(lasso_select) { SelectionShapeType::Lasso } else { SelectionShapeType::Box }; SelectToolFsmState::Drawing { selection_shape } @@ -839,7 +860,7 @@ impl Fsm for SelectToolFsmState { let selection = tool_data.nested_selection_behavior; SelectToolFsmState::Ready { selection } } - (SelectToolFsmState::Dragging { axis }, SelectToolMessage::PointerMove(modifier_keys)) => { + (SelectToolFsmState::Dragging { axis, using_compass }, SelectToolMessage::PointerMove(modifier_keys)) => { tool_data.has_dragged = true; if input.keyboard.key(modifier_keys.duplicate) && tool_data.non_duplicated_layers.is_none() { @@ -886,7 +907,7 @@ impl Fsm for SelectToolFsmState { ]; tool_data.auto_panning.setup_by_mouse_position(input, &messages, responses); - SelectToolFsmState::Dragging { axis } + SelectToolFsmState::Dragging { axis, using_compass } } (SelectToolFsmState::ResizingBounds, SelectToolMessage::PointerMove(modifier_keys)) => { if let Some(ref mut bounds) = &mut tool_data.bounding_box_manager { @@ -1065,14 +1086,14 @@ impl Fsm for SelectToolFsmState { let selection = tool_data.nested_selection_behavior; SelectToolFsmState::Ready { selection } } - (SelectToolFsmState::Dragging { axis }, SelectToolMessage::PointerOutsideViewport(_)) => { + (SelectToolFsmState::Dragging { axis, using_compass }, SelectToolMessage::PointerOutsideViewport(_)) => { // AutoPanning if let Some(shift) = tool_data.auto_panning.shift_viewport(input, responses) { tool_data.drag_current += shift; tool_data.drag_start += shift; } - SelectToolFsmState::Dragging { axis } + SelectToolFsmState::Dragging { axis, using_compass } } (SelectToolFsmState::ResizingBounds | SelectToolFsmState::SkewingBounds, SelectToolMessage::PointerOutsideViewport(_)) => { // AutoPanning @@ -1384,7 +1405,10 @@ impl Fsm for SelectToolFsmState { ]); responses.add(FrontendMessage::UpdateInputHints { hint_data }); } - SelectToolFsmState::Dragging { axis: Axis::None } if tool_data.has_dragged => { + SelectToolFsmState::Dragging { + axis: Axis::None, + using_compass: false, + } if tool_data.has_dragged => { let hint_data = HintData(vec![ HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]), HintGroup(vec![HintInfo::keys([Key::Shift], "Constrain to Axis")]), From 1bf90d0824246e65ca2db3cc8c224843b8b7f79e Mon Sep 17 00:00:00 2001 From: mtvare6 Date: Thu, 13 Feb 2025 12:24:29 +0530 Subject: [PATCH 09/16] Adjust for rotated layer and show when within cage --- .../document/overlays/utility_types.rs | 106 +++++++++--------- .../tool/common_functionality/pivot.rs | 12 +- .../tool/tool_messages/select_tool.rs | 5 +- 3 files changed, 63 insertions(+), 60 deletions(-) diff --git a/editor/src/messages/portfolio/document/overlays/utility_types.rs b/editor/src/messages/portfolio/document/overlays/utility_types.rs index fe1dc2487d..72e4fb6e99 100644 --- a/editor/src/messages/portfolio/document/overlays/utility_types.rs +++ b/editor/src/messages/portfolio/document/overlays/utility_types.rs @@ -314,7 +314,7 @@ impl OverlayContext { ) } - pub fn pivot(&mut self, pivot_position: DVec2, compass_center: DVec2, angle: f64, show_hover_ring: bool) { + pub fn pivot(&mut self, pivot_position: DVec2, compass_center: DVec2, angle: f64, show_compass_with_hover_ring: Option) { let (pivot_x, pivot_y) = (pivot_position.round() - DVec2::splat(0.5)).into(); let (compass_x, compass_y) = (compass_center.round() - DVec2::splat(0.5)).into(); let uv = DVec2::from_angle(angle); @@ -345,70 +345,72 @@ impl OverlayContext { self.render_context.stroke(); self.render_context.set_line_cap("butt"); - let old_line_width = self.render_context.line_width(); - const HOVER_RING_OUTER_RADIUS: f64 = COMPASS_ROSE_HOVER_RING_DIAMETER / 2.; - const HOVER_RING_INNER_RADIUS: f64 = COMPASS_ROSE_MAIN_RING_DIAMETER / 2.; - const MAIN_RING_OUTER_RADIUS: f64 = COMPASS_ROSE_MAIN_RING_DIAMETER / 2.; - const MAIN_RING_INNER_RADIUS: f64 = COMPASS_ROSE_RING_INNER_DIAMETER / 2.; - const ARROW_RADIUS: f64 = COMPASS_ROSE_ARROW_SIZE / 2.; + if let Some(show_hover_ring) = show_compass_with_hover_ring { + let old_line_width = self.render_context.line_width(); - // Hover ring - if show_hover_ring { - let hover_ring_stroke_width = HOVER_RING_OUTER_RADIUS - HOVER_RING_INNER_RADIUS; - let hover_ring_center_radius = (HOVER_RING_OUTER_RADIUS + HOVER_RING_INNER_RADIUS) / 2.0; + const HOVER_RING_OUTER_RADIUS: f64 = COMPASS_ROSE_HOVER_RING_DIAMETER / 2.; + const HOVER_RING_INNER_RADIUS: f64 = COMPASS_ROSE_MAIN_RING_DIAMETER / 2.; + const MAIN_RING_OUTER_RADIUS: f64 = COMPASS_ROSE_MAIN_RING_DIAMETER / 2.; + const MAIN_RING_INNER_RADIUS: f64 = COMPASS_ROSE_RING_INNER_DIAMETER / 2.; + const ARROW_RADIUS: f64 = COMPASS_ROSE_ARROW_SIZE / 2.; - let mut fill_color = graphene_std::Color::from_rgb_str(COLOR_OVERLAY_BLUE.strip_prefix('#').unwrap()).unwrap().with_alpha(0.3).rgba_hex(); - fill_color.insert(0, '#'); + // Hover ring + if show_hover_ring { + let hover_ring_stroke_width = HOVER_RING_OUTER_RADIUS - HOVER_RING_INNER_RADIUS; + let hover_ring_center_radius = (HOVER_RING_OUTER_RADIUS + HOVER_RING_INNER_RADIUS) / 2.0; - self.render_context.set_line_width(hover_ring_stroke_width); - self.render_context.begin_path(); - self.render_context.arc(compass_x, compass_y, hover_ring_center_radius, 0., TAU).expect("Failed to draw hover ring"); - self.render_context.set_stroke_style_str(&fill_color); - self.render_context.stroke(); - } + let mut fill_color = graphene_std::Color::from_rgb_str(COLOR_OVERLAY_BLUE.strip_prefix('#').unwrap()).unwrap().with_alpha(0.3).rgba_hex(); + fill_color.insert(0, '#'); + + self.render_context.set_line_width(hover_ring_stroke_width); + self.render_context.begin_path(); + self.render_context.arc(compass_x, compass_y, hover_ring_center_radius, 0., TAU).expect("Failed to draw hover ring"); + self.render_context.set_stroke_style_str(&fill_color); + self.render_context.stroke(); + } - // Main ring - let main_ring_stroke_width = MAIN_RING_OUTER_RADIUS - MAIN_RING_INNER_RADIUS; - let main_ring_center_radius = (MAIN_RING_OUTER_RADIUS + MAIN_RING_INNER_RADIUS) / 2.0; + // Main ring + let main_ring_stroke_width = MAIN_RING_OUTER_RADIUS - MAIN_RING_INNER_RADIUS; + let main_ring_center_radius = (MAIN_RING_OUTER_RADIUS + MAIN_RING_INNER_RADIUS) / 2.0; - self.render_context.set_line_width(main_ring_stroke_width); - self.render_context.begin_path(); - self.render_context.arc(compass_x, compass_y, main_ring_center_radius, 0., TAU).expect("Failed to draw main ring"); - self.render_context.set_stroke_style_str(COLOR_OVERLAY_BLUE); - self.render_context.stroke(); + self.render_context.set_line_width(main_ring_stroke_width); + self.render_context.begin_path(); + self.render_context.arc(compass_x, compass_y, main_ring_center_radius, 0., TAU).expect("Failed to draw main ring"); + self.render_context.set_stroke_style_str(COLOR_OVERLAY_BLUE); + self.render_context.stroke(); - // Arrows - self.render_context.set_line_width(0.01); - for i in 0..4 { - let base_angle = i as f64 * FRAC_PI_2 + angle; - let direction = DVec2::from_angle(base_angle); - let color = if i % 2 == 0 { COLOR_OVERLAY_RED } else { COLOR_OVERLAY_GREEN }; + // Arrows + self.render_context.set_line_width(0.01); + for i in 0..4 { + let base_angle = i as f64 * FRAC_PI_2 + angle; + let direction = DVec2::from_angle(base_angle); + let color = if i % 2 == 0 { COLOR_OVERLAY_RED } else { COLOR_OVERLAY_GREEN }; - let center = DVec2::new(compass_x, compass_y); + let center = DVec2::new(compass_x, compass_y); - let tip = center + direction * HOVER_RING_OUTER_RADIUS; - let base = center + direction * (MAIN_RING_INNER_RADIUS + MAIN_RING_OUTER_RADIUS) / 2.; + let tip = center + direction * HOVER_RING_OUTER_RADIUS; + let base = center + direction * (MAIN_RING_INNER_RADIUS + MAIN_RING_OUTER_RADIUS) / 2.; - let r = (ARROW_RADIUS.powi(2) + MAIN_RING_INNER_RADIUS.powi(2)).sqrt(); - let (cos, sin) = (MAIN_RING_INNER_RADIUS / r, ARROW_RADIUS / r); - let side1 = center + r * DVec2::new(cos * direction.x - sin * direction.y, sin * direction.x + direction.y * cos); - let side2 = center + r * DVec2::new(cos * direction.x + sin * direction.y, -sin * direction.x + direction.y * cos); + let r = (ARROW_RADIUS.powi(2) + MAIN_RING_INNER_RADIUS.powi(2)).sqrt(); + let (cos, sin) = (MAIN_RING_INNER_RADIUS / r, ARROW_RADIUS / r); + let side1 = center + r * DVec2::new(cos * direction.x - sin * direction.y, sin * direction.x + direction.y * cos); + let side2 = center + r * DVec2::new(cos * direction.x + sin * direction.y, -sin * direction.x + direction.y * cos); - self.render_context.begin_path(); - self.render_context.move_to(tip.x, tip.y); - self.render_context.line_to(side1.x, side1.y); - self.render_context.line_to(base.x, base.y); - self.render_context.line_to(side2.x, side2.y); - self.render_context.close_path(); + self.render_context.begin_path(); + self.render_context.move_to(tip.x, tip.y); + self.render_context.line_to(side1.x, side1.y); + self.render_context.line_to(base.x, base.y); + self.render_context.line_to(side2.x, side2.y); + self.render_context.close_path(); - self.render_context.set_fill_style_str(color); - self.render_context.fill(); - self.render_context.set_stroke_style_str(color); - self.render_context.stroke(); + self.render_context.set_fill_style_str(color); + self.render_context.fill(); + self.render_context.set_stroke_style_str(color); + self.render_context.stroke(); + } + self.render_context.set_line_width(old_line_width); } - self.render_context.set_line_width(old_line_width); - self.end_dpi_aware_transform(); } diff --git a/editor/src/messages/tool/common_functionality/pivot.rs b/editor/src/messages/tool/common_functionality/pivot.rs index f663382ec2..eacb82b7d7 100644 --- a/editor/src/messages/tool/common_functionality/pivot.rs +++ b/editor/src/messages/tool/common_functionality/pivot.rs @@ -1,9 +1,7 @@ //! Handler for the pivot overlay visible on the selected layer(s) whilst using the Select tool which controls the center of rotation/scale and origin of the layer. use super::graph_modification_utils; -use crate::consts::{ - COMPASS_ROSE_ANGLE_WIDTH, COMPASS_ROSE_ARROW_SIZE, COMPASS_ROSE_HOVER_RING_DIAMETER, COMPASS_ROSE_MAIN_RING_DIAMETER, COMPASS_ROSE_PIVOT_DIAMETER, COMPASS_ROSE_RING_INNER_DIAMETER, -}; +use crate::consts::{COMPASS_ROSE_ANGLE_WIDTH, COMPASS_ROSE_HOVER_RING_DIAMETER, COMPASS_ROSE_PIVOT_DIAMETER, COMPASS_ROSE_RING_INNER_DIAMETER}; use crate::messages::layout::utility_types::widget_prelude::*; use crate::messages::portfolio::document::overlays::utility_types::OverlayContext; use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; @@ -11,7 +9,7 @@ use crate::messages::prelude::*; use glam::{DAffine2, DVec2}; use std::collections::VecDeque; -use std::f64::consts::{FRAC_PI_2, FRAC_PI_4}; +use std::f64::consts::FRAC_PI_2; #[derive(Clone, Debug, PartialEq)] pub enum CompassRoseState { @@ -136,11 +134,11 @@ impl Pivot { } } - pub fn update_pivot(&mut self, document: &DocumentMessageHandler, overlay_context: &mut OverlayContext, angle: f64, show_hover_ring: bool) { + pub fn update_pivot(&mut self, document: &DocumentMessageHandler, overlay_context: &mut OverlayContext, angle: f64, show_compass_with_hover_ring: Option) { self.recalculate_pivot(document); if let Some(pivot) = self.pivot { let compass_center = self.get_compass_position(); - overlay_context.pivot(pivot, compass_center, angle, show_hover_ring); + overlay_context.pivot(pivot, compass_center, angle, show_compass_with_hover_ring); } } @@ -193,7 +191,7 @@ impl Pivot { } if (COMPASS_ROSE_RING_INNER_DIAMETER / 2.).powi(2) < compass_distance_squared && compass_distance_squared < (COMPASS_ROSE_HOVER_RING_DIAMETER / 2.).powi(2) { - let angle = (mouse - compass_center).to_angle().abs(); + let angle = (mouse - compass_center).angle_to(DVec2::from_angle(angle)).abs(); let resolved_angle = (FRAC_PI_2 - angle).abs(); let width = COMPASS_ROSE_ANGLE_WIDTH.to_radians(); diff --git a/editor/src/messages/tool/tool_messages/select_tool.rs b/editor/src/messages/tool/tool_messages/select_tool.rs index 216f07a70f..710c0f854c 100644 --- a/editor/src/messages/tool/tool_messages/select_tool.rs +++ b/editor/src/messages/tool/tool_messages/select_tool.rs @@ -556,7 +556,10 @@ impl Fsm for SelectToolFsmState { compass_rose_state.is_ring() }; // Update pivot - tool_data.pivot.update_pivot(document, &mut overlay_context, angle, show_hover_ring); + let show_compass_with_ring = bounds + .map(|bounds| transform * Quad::from_box(bounds)) + .and_then(|quad| if quad.contains(mouse_position) { Some(show_hover_ring) } else { None }); + tool_data.pivot.update_pivot(document, &mut overlay_context, angle, show_compass_with_ring); let axis_state = if let SelectToolFsmState::Dragging { axis, .. } = self { Some((axis, false)) } else { From 862cfff1c962719df91c077c1c514930722fd992 Mon Sep 17 00:00:00 2001 From: mtvare6 Date: Thu, 13 Feb 2025 13:00:09 +0530 Subject: [PATCH 10/16] Don't show when other modes are possible --- .../tool/tool_messages/select_tool.rs | 29 ++++++++++++++++--- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/editor/src/messages/tool/tool_messages/select_tool.rs b/editor/src/messages/tool/tool_messages/select_tool.rs index 710c0f854c..318056601c 100644 --- a/editor/src/messages/tool/tool_messages/select_tool.rs +++ b/editor/src/messages/tool/tool_messages/select_tool.rs @@ -555,16 +555,37 @@ impl Fsm for SelectToolFsmState { } else { compass_rose_state.is_ring() }; + + let dragging_bounds = tool_data + .bounding_box_manager + .as_mut() + .and_then(|bounding_box| bounding_box.check_selected_edges(input.mouse.position)) + .is_some(); + + let rotating_bounds = tool_data + .bounding_box_manager + .as_ref() + .map(|bounding_box| bounding_box.check_rotate(input.mouse.position)) + .unwrap_or_default(); + // Update pivot - let show_compass_with_ring = bounds - .map(|bounds| transform * Quad::from_box(bounds)) - .and_then(|quad| if quad.contains(mouse_position) { Some(show_hover_ring) } else { None }); + let can_get_into_other_states = dragging_bounds || rotating_bounds; + let show_compass_with_ring = bounds.map(|bounds| transform * Quad::from_box(bounds)).and_then(|quad| { + if can_get_into_other_states || matches!(self, SelectToolFsmState::ResizingBounds { .. } | SelectToolFsmState::SkewingBounds | SelectToolFsmState::RotatingBounds) { + None + } else { + quad.contains(mouse_position).then_some(show_hover_ring) + } + }); + tool_data.pivot.update_pivot(document, &mut overlay_context, angle, show_compass_with_ring); + let axis_state = if let SelectToolFsmState::Dragging { axis, .. } = self { Some((axis, false)) } else { - compass_rose_state.axis_type().and_then(|axis| if axis.is_constraint() { Some((axis, true)) } else { None }) + compass_rose_state.axis_type().and_then(|axis| axis.is_constraint().then_some((axis, true))) }; + if let Some((axis, hover)) = axis_state { if axis.is_constraint() { let e0 = tool_data From b4ca9419fee5dcd3cd68c80fcc2919d816baed9f Mon Sep 17 00:00:00 2001 From: mtvare6 Date: Thu, 13 Feb 2025 14:13:47 +0530 Subject: [PATCH 11/16] Fix glitchy compass --- .../document/overlays/utility_types.rs | 2 +- .../tool/tool_messages/select_tool.rs | 20 ++++++++++++------- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/editor/src/messages/portfolio/document/overlays/utility_types.rs b/editor/src/messages/portfolio/document/overlays/utility_types.rs index 72e4fb6e99..41378e4774 100644 --- a/editor/src/messages/portfolio/document/overlays/utility_types.rs +++ b/editor/src/messages/portfolio/document/overlays/utility_types.rs @@ -360,7 +360,7 @@ impl OverlayContext { let hover_ring_stroke_width = HOVER_RING_OUTER_RADIUS - HOVER_RING_INNER_RADIUS; let hover_ring_center_radius = (HOVER_RING_OUTER_RADIUS + HOVER_RING_INNER_RADIUS) / 2.0; - let mut fill_color = graphene_std::Color::from_rgb_str(COLOR_OVERLAY_BLUE.strip_prefix('#').unwrap()).unwrap().with_alpha(0.3).rgba_hex(); + let mut fill_color = graphene_std::Color::from_rgb_str(COLOR_OVERLAY_BLUE.strip_prefix('#').unwrap()).unwrap().with_alpha(0.5).rgba_hex(); fill_color.insert(0, '#'); self.render_context.set_line_width(hover_ring_stroke_width); diff --git a/editor/src/messages/tool/tool_messages/select_tool.rs b/editor/src/messages/tool/tool_messages/select_tool.rs index 318056601c..f8deb27496 100644 --- a/editor/src/messages/tool/tool_messages/select_tool.rs +++ b/editor/src/messages/tool/tool_messages/select_tool.rs @@ -568,16 +568,22 @@ impl Fsm for SelectToolFsmState { .map(|bounding_box| bounding_box.check_rotate(input.mouse.position)) .unwrap_or_default(); - // Update pivot - let can_get_into_other_states = dragging_bounds || rotating_bounds; + let might_resize_or_rotate = dragging_bounds || rotating_bounds; + let is_resizing_or_rotating = matches!(self, SelectToolFsmState::ResizingBounds { .. } | SelectToolFsmState::SkewingBounds | SelectToolFsmState::RotatingBounds); + let can_get_into_other_states = might_resize_or_rotate && !matches!(self, SelectToolFsmState::Dragging { .. }); + + let show_compass = !(can_get_into_other_states || is_resizing_or_rotating); let show_compass_with_ring = bounds.map(|bounds| transform * Quad::from_box(bounds)).and_then(|quad| { - if can_get_into_other_states || matches!(self, SelectToolFsmState::ResizingBounds { .. } | SelectToolFsmState::SkewingBounds | SelectToolFsmState::RotatingBounds) { - None - } else { - quad.contains(mouse_position).then_some(show_hover_ring) - } + show_compass + .then_some( + matches!(self, SelectToolFsmState::Dragging { .. }) + .then_some(show_hover_ring) + .or(quad.contains(mouse_position).then_some(show_hover_ring)), + ) + .flatten() }); + // Update pivot tool_data.pivot.update_pivot(document, &mut overlay_context, angle, show_compass_with_ring); let axis_state = if let SelectToolFsmState::Dragging { axis, .. } = self { From 7e9872b7c7bad9e05f8dd8182a678f540ce52e62 Mon Sep 17 00:00:00 2001 From: mtvare6 Date: Thu, 13 Feb 2025 16:33:57 +0530 Subject: [PATCH 12/16] fixes --- .../document/overlays/utility_types.rs | 26 ++++++------ .../tool/tool_messages/select_tool.rs | 42 ++++++++++++++----- 2 files changed, 45 insertions(+), 23 deletions(-) diff --git a/editor/src/messages/portfolio/document/overlays/utility_types.rs b/editor/src/messages/portfolio/document/overlays/utility_types.rs index 41378e4774..ec75ed1958 100644 --- a/editor/src/messages/portfolio/document/overlays/utility_types.rs +++ b/editor/src/messages/portfolio/document/overlays/utility_types.rs @@ -350,15 +350,14 @@ impl OverlayContext { let old_line_width = self.render_context.line_width(); const HOVER_RING_OUTER_RADIUS: f64 = COMPASS_ROSE_HOVER_RING_DIAMETER / 2.; - const HOVER_RING_INNER_RADIUS: f64 = COMPASS_ROSE_MAIN_RING_DIAMETER / 2.; const MAIN_RING_OUTER_RADIUS: f64 = COMPASS_ROSE_MAIN_RING_DIAMETER / 2.; const MAIN_RING_INNER_RADIUS: f64 = COMPASS_ROSE_RING_INNER_DIAMETER / 2.; const ARROW_RADIUS: f64 = COMPASS_ROSE_ARROW_SIZE / 2.; // Hover ring if show_hover_ring { - let hover_ring_stroke_width = HOVER_RING_OUTER_RADIUS - HOVER_RING_INNER_RADIUS; - let hover_ring_center_radius = (HOVER_RING_OUTER_RADIUS + HOVER_RING_INNER_RADIUS) / 2.0; + let hover_ring_stroke_width = HOVER_RING_OUTER_RADIUS - MAIN_RING_INNER_RADIUS; + let hover_ring_center_radius = (HOVER_RING_OUTER_RADIUS + MAIN_RING_INNER_RADIUS) / 2.0; let mut fill_color = graphene_std::Color::from_rgb_str(COLOR_OVERLAY_BLUE.strip_prefix('#').unwrap()).unwrap().with_alpha(0.5).rgba_hex(); fill_color.insert(0, '#'); @@ -370,16 +369,6 @@ impl OverlayContext { self.render_context.stroke(); } - // Main ring - let main_ring_stroke_width = MAIN_RING_OUTER_RADIUS - MAIN_RING_INNER_RADIUS; - let main_ring_center_radius = (MAIN_RING_OUTER_RADIUS + MAIN_RING_INNER_RADIUS) / 2.0; - - self.render_context.set_line_width(main_ring_stroke_width); - self.render_context.begin_path(); - self.render_context.arc(compass_x, compass_y, main_ring_center_radius, 0., TAU).expect("Failed to draw main ring"); - self.render_context.set_stroke_style_str(COLOR_OVERLAY_BLUE); - self.render_context.stroke(); - // Arrows self.render_context.set_line_width(0.01); for i in 0..4 { @@ -409,6 +398,17 @@ impl OverlayContext { self.render_context.set_stroke_style_str(color); self.render_context.stroke(); } + + // Main ring + let main_ring_stroke_width = MAIN_RING_OUTER_RADIUS - MAIN_RING_INNER_RADIUS; + let main_ring_center_radius = (MAIN_RING_OUTER_RADIUS + MAIN_RING_INNER_RADIUS) / 2.0; + + self.render_context.set_line_width(main_ring_stroke_width); + self.render_context.begin_path(); + self.render_context.arc(compass_x, compass_y, main_ring_center_radius, 0., TAU).expect("Failed to draw main ring"); + self.render_context.set_stroke_style_str(COLOR_OVERLAY_BLUE); + self.render_context.stroke(); + self.render_context.set_line_width(old_line_width); } self.end_dpi_aware_transform(); diff --git a/editor/src/messages/tool/tool_messages/select_tool.rs b/editor/src/messages/tool/tool_messages/select_tool.rs index f8deb27496..c1447f7274 100644 --- a/editor/src/messages/tool/tool_messages/select_tool.rs +++ b/editor/src/messages/tool/tool_messages/select_tool.rs @@ -1,7 +1,7 @@ #![allow(clippy::too_many_arguments)] use super::tool_prelude::*; -use crate::consts::{COLOR_OVERLAY_GREEN, COLOR_OVERLAY_RED, DRAG_DIRECTION_MODE_DETERMINATION_THRESHOLD, ROTATE_INCREMENT, SELECTION_TOLERANCE}; +use crate::consts::{COLOR_OVERLAY_BLUE, COLOR_OVERLAY_GREEN, COLOR_OVERLAY_RED, DRAG_DIRECTION_MODE_DETERMINATION_THRESHOLD, ROTATE_INCREMENT, SELECTION_DRAG_ANGLE, SELECTION_TOLERANCE}; use crate::messages::input_mapper::utility_types::input_mouse::ViewportPosition; use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn; use crate::messages::portfolio::document::overlays::utility_types::OverlayContext; @@ -298,6 +298,7 @@ struct SelectToolData { layer_selected_on_start: Option, select_single_layer: Option, has_dragged: bool, + axis_align: bool, non_duplicated_layers: Option>, bounding_box_manager: Option, snap_manager: SnapManager, @@ -620,6 +621,26 @@ impl Fsm for SelectToolFsmState { } } + if axis_state.is_none_or(|(axis, _)| !axis.is_constraint()) && tool_data.axis_align { + let mouse_position = mouse_position - tool_data.drag_start; + let snap_resolution = SELECTION_DRAG_ANGLE.to_radians(); + let angle = -mouse_position.angle_to(DVec2::X); + let snapped_angle = (angle / snap_resolution).round() * snap_resolution; + + let mut other = graphene_std::Color::from_rgb_str(COLOR_OVERLAY_BLUE.strip_prefix('#').unwrap()).unwrap().with_alpha(0.25).rgba_hex(); + other.insert(0, '#'); + let other = other.as_str(); + + let origin = tool_data.pivot.get_compass_position(); + let viewport_diagonal = input.viewport_bounds.size().length(); + + let edge = DVec2::from_angle(snapped_angle) * viewport_diagonal; + let perp = edge.perp(); + + overlay_context.line(origin - edge * viewport_diagonal, origin + edge * viewport_diagonal, Some(COLOR_OVERLAY_BLUE)); + overlay_context.line(origin - perp * viewport_diagonal, origin + perp * viewport_diagonal, Some(other)); + } + // Check if the tool is in selection mode if let Self::Drawing { selection_shape } = self { // Get the updated selection box bounds @@ -899,7 +920,7 @@ impl Fsm for SelectToolFsmState { tool_data.stop_duplicates(document, responses); } - let axis_align = input.keyboard.key(modifier_keys.axis_align); + tool_data.axis_align = input.keyboard.key(modifier_keys.axis_align) && !axis.is_constraint(); // Ignore the non duplicated layers if the current layers have not spawned yet. let layers_exist = tool_data.layers_dragging.iter().all(|&layer| document.metadata().click_targets(layer).is_some()); @@ -907,7 +928,7 @@ impl Fsm for SelectToolFsmState { let snap_data = SnapData::ignore(document, input, ignore); let (start, current) = (tool_data.drag_start, tool_data.drag_current); - let mouse_delta = snap_drag(start, current, axis_align, snap_data, &mut tool_data.snap_manager, &tool_data.snap_candidates); + let mouse_delta = snap_drag(start, current, tool_data.axis_align, snap_data, &mut tool_data.snap_manager, &tool_data.snap_candidates); let e0 = tool_data .bounding_box_manager .as_ref() @@ -1435,18 +1456,19 @@ impl Fsm for SelectToolFsmState { ]); responses.add(FrontendMessage::UpdateInputHints { hint_data }); } - SelectToolFsmState::Dragging { - axis: Axis::None, - using_compass: false, - } if tool_data.has_dragged => { - let hint_data = HintData(vec![ + SelectToolFsmState::Dragging { axis, using_compass } if tool_data.has_dragged => { + let mut hint_data = vec![ HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]), - HintGroup(vec![HintInfo::keys([Key::Shift], "Constrain to Axis")]), HintGroup(vec![ HintInfo::keys([Key::Alt], "Move Duplicate"), HintInfo::keys([Key::Control, Key::KeyD], "Place Duplicate").add_mac_keys([Key::Command, Key::KeyD]), ]), - ]); + ]; + + if !(*using_compass && axis.is_constraint()) { + hint_data.push(HintGroup(vec![HintInfo::keys([Key::Shift], "Constrain to Axis")])); + }; + let hint_data = HintData(hint_data); responses.add(FrontendMessage::UpdateInputHints { hint_data }); } SelectToolFsmState::Drawing { .. } if tool_data.drag_start != tool_data.drag_current => { From 23b7bc0db121df35ce624bb1472945583e5daa72 Mon Sep 17 00:00:00 2001 From: mtvare6 Date: Thu, 13 Feb 2025 17:27:24 +0530 Subject: [PATCH 13/16] fixes --- editor/src/messages/tool/tool_messages/select_tool.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/editor/src/messages/tool/tool_messages/select_tool.rs b/editor/src/messages/tool/tool_messages/select_tool.rs index c1447f7274..bc9cc18059 100644 --- a/editor/src/messages/tool/tool_messages/select_tool.rs +++ b/editor/src/messages/tool/tool_messages/select_tool.rs @@ -631,7 +631,8 @@ impl Fsm for SelectToolFsmState { other.insert(0, '#'); let other = other.as_str(); - let origin = tool_data.pivot.get_compass_position(); + let extension = tool_data.drag_current - tool_data.drag_start; + let origin = tool_data.pivot.get_compass_position() - extension; let viewport_diagonal = input.viewport_bounds.size().length(); let edge = DVec2::from_angle(snapped_angle) * viewport_diagonal; @@ -1195,6 +1196,7 @@ impl Fsm for SelectToolFsmState { (SelectToolFsmState::Dragging { .. }, SelectToolMessage::DragStop { remove_from_selection }) => { // Deselect layer if not snap dragging responses.add(DocumentMessage::EndTransaction); + tool_data.axis_align = false; if !tool_data.has_dragged && input.keyboard.key(remove_from_selection) && tool_data.layer_selected_on_start.is_none() { // When you click on the layer with remove from selection key (shift) pressed, we deselect all nodes that are children. From d830bde1b564d8912d8507b1e9092bfcb5480af5 Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Thu, 13 Feb 2025 14:08:15 -0800 Subject: [PATCH 14/16] WIP separate pivot and compass rose (not compiling) --- editor/src/consts.rs | 7 +- .../document/overlays/utility_types.rs | 67 +++++++------- .../tool/common_functionality/compass_rose.rs | 80 +++++++++++++++++ .../messages/tool/common_functionality/mod.rs | 1 + .../tool/common_functionality/pivot.rs | 90 ++----------------- .../transformation_cage.rs | 5 +- .../tool/tool_messages/select_tool.rs | 22 +++-- 7 files changed, 143 insertions(+), 129 deletions(-) create mode 100644 editor/src/messages/tool/common_functionality/compass_rose.rs diff --git a/editor/src/consts.rs b/editor/src/consts.rs index eda0558ac1..2b7abfbbcd 100644 --- a/editor/src/consts.rs +++ b/editor/src/consts.rs @@ -54,16 +54,17 @@ pub const DEFAULT_STROKE_WIDTH: f64 = 2.; pub const SELECTION_TOLERANCE: f64 = 5.; pub const DRAG_DIRECTION_MODE_DETERMINATION_THRESHOLD: f64 = 15.; pub const SELECTION_DRAG_ANGLE: f64 = 90.; + +// PIVOT pub const PIVOT_CROSSHAIR_THICKNESS: f64 = 1.; pub const PIVOT_CROSSHAIR_LENGTH: f64 = 9.; +pub const PIVOT_DIAMETER: f64 = 5.; +// COMPASS ROSE pub const COMPASS_ROSE_ANGLE_WIDTH: f64 = 20.; // Must be less than 45 - -pub const COMPASS_ROSE_PIVOT_DIAMETER: f64 = 5.; pub const COMPASS_ROSE_RING_INNER_DIAMETER: f64 = 13.; pub const COMPASS_ROSE_MAIN_RING_DIAMETER: f64 = 15.; pub const COMPASS_ROSE_HOVER_RING_DIAMETER: f64 = 23.; - pub const COMPASS_ROSE_ARROW_SIZE: f64 = 5.; // TRANSFORM OVERLAY diff --git a/editor/src/messages/portfolio/document/overlays/utility_types.rs b/editor/src/messages/portfolio/document/overlays/utility_types.rs index ec75ed1958..d187e70483 100644 --- a/editor/src/messages/portfolio/document/overlays/utility_types.rs +++ b/editor/src/messages/portfolio/document/overlays/utility_types.rs @@ -2,7 +2,7 @@ use super::utility_functions::overlay_canvas_context; use crate::consts::{ COLOR_OVERLAY_BLUE, COLOR_OVERLAY_GREEN, COLOR_OVERLAY_RED, COLOR_OVERLAY_WHITE, COLOR_OVERLAY_YELLOW, MANIPULATOR_GROUP_MARKER_SIZE, PIVOT_CROSSHAIR_LENGTH, PIVOT_CROSSHAIR_THICKNESS, }; -use crate::consts::{COMPASS_ROSE_ARROW_SIZE, COMPASS_ROSE_HOVER_RING_DIAMETER, COMPASS_ROSE_MAIN_RING_DIAMETER, COMPASS_ROSE_PIVOT_DIAMETER, COMPASS_ROSE_RING_INNER_DIAMETER}; +use crate::consts::{COMPASS_ROSE_ARROW_SIZE, COMPASS_ROSE_HOVER_RING_DIAMETER, COMPASS_ROSE_MAIN_RING_DIAMETER, COMPASS_ROSE_RING_INNER_DIAMETER, PIVOT_DIAMETER}; use crate::messages::prelude::Message; use bezier_rs::{Bezier, Subpath}; @@ -314,38 +314,10 @@ impl OverlayContext { ) } - pub fn pivot(&mut self, pivot_position: DVec2, compass_center: DVec2, angle: f64, show_compass_with_hover_ring: Option) { - let (pivot_x, pivot_y) = (pivot_position.round() - DVec2::splat(0.5)).into(); + pub fn compass_rose(&mut self, compass_center: DVec2, angle: f64, show_compass_with_hover_ring: Option) { let (compass_x, compass_y) = (compass_center.round() - DVec2::splat(0.5)).into(); - let uv = DVec2::from_angle(angle); self.start_dpi_aware_transform(); - // Center dot - self.render_context.begin_path(); - self.render_context.arc(pivot_x, pivot_y, COMPASS_ROSE_PIVOT_DIAMETER / 2., 0., TAU).expect("Failed to draw center dot"); - self.render_context.set_fill_style_str(COLOR_OVERLAY_YELLOW); - self.render_context.fill(); - - // Crosshair - - // Round line caps add half the stroke width to the length on each end, so we subtract that here before halving to get the radius - let crosshair_radius = (PIVOT_CROSSHAIR_LENGTH - PIVOT_CROSSHAIR_THICKNESS) / 2.; - - self.render_context.set_stroke_style_str(COLOR_OVERLAY_YELLOW); - self.render_context.set_line_cap("round"); - - self.render_context.begin_path(); - self.render_context.move_to(pivot_x + crosshair_radius * uv.x, pivot_y + crosshair_radius * uv.y); - self.render_context.line_to(pivot_x - crosshair_radius * uv.x, pivot_y - crosshair_radius * uv.y); - self.render_context.stroke(); - - self.render_context.begin_path(); - self.render_context.move_to(pivot_x - crosshair_radius * uv.y, pivot_y + crosshair_radius * uv.x); - self.render_context.line_to(pivot_x + crosshair_radius * uv.y, pivot_y - crosshair_radius * uv.x); - self.render_context.stroke(); - - self.render_context.set_line_cap("butt"); - if let Some(show_hover_ring) = show_compass_with_hover_ring { let old_line_width = self.render_context.line_width(); @@ -411,6 +383,41 @@ impl OverlayContext { self.render_context.set_line_width(old_line_width); } + } + + pub fn pivot(&mut self, position: DVec2, angle: f64) { + let uv = DVec2::from_angle(angle); + let (x, y) = (position.round() - DVec2::splat(0.5)).into(); + + self.start_dpi_aware_transform(); + + // Circle + + self.render_context.begin_path(); + self.render_context.arc(x, y, PIVOT_DIAMETER / 2., 0., TAU).expect("Failed to draw the circle"); + self.render_context.set_fill_style_str(COLOR_OVERLAY_YELLOW); + self.render_context.fill(); + + // Crosshair + + // Round line caps add half the stroke width to the length on each end, so we subtract that here before halving to get the radius + let crosshair_radius = (PIVOT_CROSSHAIR_LENGTH - PIVOT_CROSSHAIR_THICKNESS) / 2.; + + self.render_context.set_stroke_style_str(COLOR_OVERLAY_YELLOW); + self.render_context.set_line_cap("round"); + + self.render_context.begin_path(); + self.render_context.move_to(x + crosshair_radius * uv.x, y + crosshair_radius * uv.y); + self.render_context.line_to(x - crosshair_radius * uv.x, y - crosshair_radius * uv.y); + self.render_context.stroke(); + + self.render_context.begin_path(); + self.render_context.move_to(x - crosshair_radius * uv.y, y + crosshair_radius * uv.x); + self.render_context.line_to(x + crosshair_radius * uv.y, y - crosshair_radius * uv.x); + self.render_context.stroke(); + + self.render_context.set_line_cap("butt"); + self.end_dpi_aware_transform(); } diff --git a/editor/src/messages/tool/common_functionality/compass_rose.rs b/editor/src/messages/tool/common_functionality/compass_rose.rs new file mode 100644 index 0000000000..5c7be8d906 --- /dev/null +++ b/editor/src/messages/tool/common_functionality/compass_rose.rs @@ -0,0 +1,80 @@ +use glam::{DAffine2, DVec2}; + +use crate::consts::{COMPASS_ROSE_ANGLE_WIDTH, COMPASS_ROSE_HOVER_RING_DIAMETER, COMPASS_ROSE_RING_INNER_DIAMETER}; +use std::f64::consts::FRAC_PI_2; + +#[derive(Clone, Debug)] +pub struct CompassRose { + /// Transform to get from normalized pivot to viewspace + transform_from_normalized: DAffine2, +} + +impl CompassRose { + pub fn get_compass_position(&self) -> DVec2 { + self.transform_from_normalized.transform_point2(DVec2::splat(0.5)) + } + + /// Answers if the pointer is currently positioned over the pivot. + pub fn compass_rose_state(&self, mouse: DVec2, angle: f64) -> CompassRoseState { + let compass_center = self.get_compass_position(); + + let compass_distance_squared = mouse.distance_squared(compass_center); + + if (COMPASS_ROSE_RING_INNER_DIAMETER / 2.).powi(2) < compass_distance_squared && compass_distance_squared < (COMPASS_ROSE_HOVER_RING_DIAMETER / 2.).powi(2) { + let angle = (mouse - compass_center).angle_to(DVec2::from_angle(angle)).abs(); + let resolved_angle = (FRAC_PI_2 - angle).abs(); + let width = COMPASS_ROSE_ANGLE_WIDTH.to_radians(); + + if resolved_angle < width { + CompassRoseState::AxisY + } else if resolved_angle > (FRAC_PI_2 - width) { + CompassRoseState::AxisX + } else { + CompassRoseState::Ring + } + } else { + CompassRoseState::None + } + } +} + +#[derive(Clone, Debug, PartialEq)] +pub enum CompassRoseState { + Ring, + AxisX, + AxisY, + None, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)] +pub enum Axis { + #[default] + None, + X, + Y, +} + +impl Axis { + pub fn is_constraint(&self) -> bool { + matches!(self, Self::X | Self::Y) + } +} + +impl CompassRoseState { + pub fn can_grab(&self) -> bool { + matches!(self, Self::Ring | Self::AxisX | Self::AxisY) + } + + pub fn is_ring(&self) -> bool { + matches!(self, Self::Ring) + } + + pub fn axis_type(&self) -> Option { + match self { + CompassRoseState::AxisX => Some(Axis::X), + CompassRoseState::AxisY => Some(Axis::Y), + CompassRoseState::Ring => Some(Axis::None), + _ => None, + } + } +} diff --git a/editor/src/messages/tool/common_functionality/mod.rs b/editor/src/messages/tool/common_functionality/mod.rs index e3e273fa2e..9bc2236061 100644 --- a/editor/src/messages/tool/common_functionality/mod.rs +++ b/editor/src/messages/tool/common_functionality/mod.rs @@ -1,5 +1,6 @@ pub mod auto_panning; pub mod color_selector; +pub mod compass_rose; pub mod graph_modification_utils; pub mod measure; pub mod pivot; diff --git a/editor/src/messages/tool/common_functionality/pivot.rs b/editor/src/messages/tool/common_functionality/pivot.rs index eacb82b7d7..2c924602fb 100644 --- a/editor/src/messages/tool/common_functionality/pivot.rs +++ b/editor/src/messages/tool/common_functionality/pivot.rs @@ -1,7 +1,7 @@ //! Handler for the pivot overlay visible on the selected layer(s) whilst using the Select tool which controls the center of rotation/scale and origin of the layer. use super::graph_modification_utils; -use crate::consts::{COMPASS_ROSE_ANGLE_WIDTH, COMPASS_ROSE_HOVER_RING_DIAMETER, COMPASS_ROSE_PIVOT_DIAMETER, COMPASS_ROSE_RING_INNER_DIAMETER}; +use crate::consts::PIVOT_DIAMETER; use crate::messages::layout::utility_types::widget_prelude::*; use crate::messages::portfolio::document::overlays::utility_types::OverlayContext; use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; @@ -9,53 +9,6 @@ use crate::messages::prelude::*; use glam::{DAffine2, DVec2}; use std::collections::VecDeque; -use std::f64::consts::FRAC_PI_2; - -#[derive(Clone, Debug, PartialEq)] -pub enum CompassRoseState { - Pivot, - Ring, - AxisX, - AxisY, - None, -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)] -pub enum Axis { - #[default] - None, - X, - Y, -} - -impl Axis { - pub fn is_constraint(&self) -> bool { - matches!(self, Self::X | Self::Y) - } -} - -impl CompassRoseState { - pub fn can_grab(&self) -> bool { - matches!(self, Self::Ring | Self::AxisX | Self::AxisY) - } - - pub fn is_pivot(&self) -> bool { - matches!(self, Self::Pivot) - } - - pub fn is_ring(&self) -> bool { - matches!(self, Self::Ring) - } - - pub fn axis_type(&self) -> Option { - match self { - CompassRoseState::AxisX => Some(Axis::X), - CompassRoseState::AxisY => Some(Axis::Y), - CompassRoseState::Ring => Some(Axis::None), - _ => None, - } - } -} #[derive(Clone, Debug)] pub struct Pivot { @@ -90,10 +43,6 @@ impl Pivot { layer_transform * bounds_transform } - pub fn get_compass_position(&self) -> DVec2 { - self.transform_from_normalized.transform_point2(DVec2::splat(0.5)) - } - /// Recomputes the pivot position and transform. fn recalculate_pivot(&mut self, document: &DocumentMessageHandler) { let selected_nodes = document.network_interface.selected_nodes(&[]).unwrap(); @@ -134,11 +83,10 @@ impl Pivot { } } - pub fn update_pivot(&mut self, document: &DocumentMessageHandler, overlay_context: &mut OverlayContext, angle: f64, show_compass_with_hover_ring: Option) { + pub fn update_pivot(&mut self, document: &DocumentMessageHandler, overlay_context: &mut OverlayContext, angle: f64) { self.recalculate_pivot(document); if let Some(pivot) = self.pivot { - let compass_center = self.get_compass_position(); - overlay_context.pivot(pivot, compass_center, angle, show_compass_with_hover_ring); + overlay_context.pivot(pivot, angle); } } @@ -177,35 +125,7 @@ impl Pivot { } /// Answers if the pointer is currently positioned over the pivot. - pub fn compass_rose_state(&self, mouse: DVec2, angle: f64) -> CompassRoseState { - match self.pivot { - None => CompassRoseState::None, - Some(pivot) => { - let compass_center = self.get_compass_position(); - - let compass_distance_squared = mouse.distance_squared(compass_center); - let pivot_distance_squared = mouse.distance_squared(pivot); - - if pivot_distance_squared < (COMPASS_ROSE_PIVOT_DIAMETER / 2.).powi(2) { - return CompassRoseState::Pivot; - } - - if (COMPASS_ROSE_RING_INNER_DIAMETER / 2.).powi(2) < compass_distance_squared && compass_distance_squared < (COMPASS_ROSE_HOVER_RING_DIAMETER / 2.).powi(2) { - let angle = (mouse - compass_center).angle_to(DVec2::from_angle(angle)).abs(); - let resolved_angle = (FRAC_PI_2 - angle).abs(); - let width = COMPASS_ROSE_ANGLE_WIDTH.to_radians(); - - if resolved_angle < width { - CompassRoseState::AxisY - } else if resolved_angle > (FRAC_PI_2 - width) { - CompassRoseState::AxisX - } else { - CompassRoseState::Ring - } - } else { - CompassRoseState::None - } - } - } + pub fn is_over(&self, mouse: DVec2) -> bool { + self.pivot.filter(|&pivot| mouse.distance_squared(pivot) < (PIVOT_DIAMETER / 2.).powi(2)).is_some() } } diff --git a/editor/src/messages/tool/common_functionality/transformation_cage.rs b/editor/src/messages/tool/common_functionality/transformation_cage.rs index 246d254a2b..da129c9b9a 100644 --- a/editor/src/messages/tool/common_functionality/transformation_cage.rs +++ b/editor/src/messages/tool/common_functionality/transformation_cage.rs @@ -1,6 +1,6 @@ use crate::consts::{ - BOUNDS_ROTATE_THRESHOLD, BOUNDS_SELECT_THRESHOLD, MAXIMUM_ALT_SCALE_FACTOR, MIN_LENGTH_FOR_CORNERS_VISIBILITY, MIN_LENGTH_FOR_MIDPOINT_VISIBILITY, MIN_LENGTH_FOR_RESIZE_TO_INCLUDE_INTERIOR, - SELECTION_DRAG_ANGLE, + BOUNDS_ROTATE_THRESHOLD, BOUNDS_SELECT_THRESHOLD, COLOR_OVERLAY_WHITE, MAXIMUM_ALT_SCALE_FACTOR, MIN_LENGTH_FOR_CORNERS_VISIBILITY, MIN_LENGTH_FOR_MIDPOINT_VISIBILITY, + MIN_LENGTH_FOR_RESIZE_TO_INCLUDE_INTERIOR, SELECTION_DRAG_ANGLE, }; use crate::messages::frontend::utility_types::MouseCursorIcon; use crate::messages::portfolio::document::overlays::utility_types::OverlayContext; @@ -380,7 +380,6 @@ impl BoundingBoxManager { overlay_context.quad(quad, None); let mut draw_handle = |point: DVec2| { - use crate::consts::COLOR_OVERLAY_WHITE; let quad = DAffine2::from_angle_translation((quad.top_left() - quad.top_right()).to_angle(), point) * Quad::from_box([DVec2::splat(-3.), DVec2::splat(3.)]); overlay_context.quad(quad, Some(COLOR_OVERLAY_WHITE)); }; diff --git a/editor/src/messages/tool/tool_messages/select_tool.rs b/editor/src/messages/tool/tool_messages/select_tool.rs index bc9cc18059..56098dbb9d 100644 --- a/editor/src/messages/tool/tool_messages/select_tool.rs +++ b/editor/src/messages/tool/tool_messages/select_tool.rs @@ -11,8 +11,9 @@ use crate::messages::portfolio::document::utility_types::network_interface::{Flo use crate::messages::portfolio::document::utility_types::nodes::SelectedNodes; use crate::messages::portfolio::document::utility_types::transformation::Selected; use crate::messages::preferences::SelectionMode; +use crate::messages::tool::common_functionality::compass_rose::{Axis, CompassRose}; use crate::messages::tool::common_functionality::graph_modification_utils::{get_text, is_layer_fed_by_node_of_name}; -use crate::messages::tool::common_functionality::pivot::{Axis, Pivot}; +use crate::messages::tool::common_functionality::pivot::Pivot; use crate::messages::tool::common_functionality::shape_editor::SelectionShapeType; use crate::messages::tool::common_functionality::snapping::{self, SnapCandidatePoint, SnapData, SnapManager}; use crate::messages::tool::common_functionality::transformation_cage::*; @@ -304,6 +305,7 @@ struct SelectToolData { snap_manager: SnapManager, cursor: MouseCursorIcon, pivot: Pivot, + compass_rose: CompassRose, nested_selection_behavior: NestedSelectionBehavior, selected_layers_count: usize, selected_layers_changed: bool, @@ -549,7 +551,7 @@ impl Fsm for SelectToolFsmState { .map_or(0., |quad| (quad.top_left() - quad.top_right()).to_angle()); let mouse_position = input.mouse.position; - let compass_rose_state = tool_data.pivot.compass_rose_state(mouse_position, angle); + let compass_rose_state = tool_data.compass_rose.compass_rose_state(mouse_position, angle); let show_hover_ring = if let SelectToolFsmState::Dragging { axis, using_compass } = self { using_compass && !axis.is_constraint() @@ -585,7 +587,11 @@ impl Fsm for SelectToolFsmState { }); // Update pivot - tool_data.pivot.update_pivot(document, &mut overlay_context, angle, show_compass_with_ring); + tool_data.pivot.update_pivot(document, &mut overlay_context, angle); + + // Update compass rose + let compass_center = tool_data.compass_rose.get_compass_position(); + overlay_context.compass_rose(compass_center, angle, show_compass_with_ring); let axis_state = if let SelectToolFsmState::Dragging { axis, .. } = self { Some((axis, false)) @@ -601,7 +607,7 @@ impl Fsm for SelectToolFsmState { .map(|man| man.transform * Quad::from_box(man.bounds)) .map_or(DVec2::X, |quad| (quad.top_left() - quad.top_right()).normalize_or(DVec2::X)); - let origin = tool_data.pivot.get_compass_position(); + let origin = tool_data.compass_rose.get_compass_position(); let (direction, color) = match axis { Axis::X => (e0, COLOR_OVERLAY_RED), Axis::Y => (e0.perp(), COLOR_OVERLAY_GREEN), @@ -632,7 +638,7 @@ impl Fsm for SelectToolFsmState { let other = other.as_str(); let extension = tool_data.drag_current - tool_data.drag_start; - let origin = tool_data.pivot.get_compass_position() - extension; + let origin = tool_data.compass_rose.get_compass_position() - extension; let viewport_diagonal = input.viewport_bounds.size().length(); let edge = DVec2::from_angle(snapped_angle) * viewport_diagonal; @@ -777,7 +783,7 @@ impl Fsm for SelectToolFsmState { .as_ref() .map(|man| man.transform * Quad::from_box(man.bounds)) .map_or(0., |quad| (quad.top_left() - quad.top_right()).to_angle()); - let compass_rose_state = tool_data.pivot.compass_rose_state(input.mouse.position, angle); + let compass_rose_state = tool_data.compass_rose.compass_rose_state(input.mouse.position, angle); let state = // Dragging the pivot @@ -790,7 +796,7 @@ impl Fsm for SelectToolFsmState { SelectToolFsmState::DraggingPivot } // Dragging one (or two, forming a corner) of the transform cage bounding box edges - else if let Some(_) = dragging_bounds { + else if dragging_bounds.is_some() { responses.add(DocumentMessage::StartTransaction); tool_data.layers_dragging = selected; @@ -1123,7 +1129,7 @@ impl Fsm for SelectToolFsmState { .map(|man| man.transform * Quad::from_box(man.bounds)) .map_or(0., |quad| (quad.top_left() - quad.top_right()).to_angle()); // Dragging the pivot overrules the other operations - if tool_data.pivot.compass_rose_state(input.mouse.position, angle).is_pivot() { + if tool_data.compass_rose.compass_rose_state(input.mouse.position, angle).is_pivot() { cursor = MouseCursorIcon::Move; } From 363ce11754b5d73ebf173570ce7782167465e654 Mon Sep 17 00:00:00 2001 From: mtvare6 Date: Fri, 14 Feb 2025 17:42:29 +0530 Subject: [PATCH 15/16] Complete file moving fixes --- .../tool/common_functionality/compass_rose.rs | 8 +++++++- .../src/messages/tool/tool_messages/select_tool.rs | 14 ++++++-------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/editor/src/messages/tool/common_functionality/compass_rose.rs b/editor/src/messages/tool/common_functionality/compass_rose.rs index 5c7be8d906..182fcae94d 100644 --- a/editor/src/messages/tool/common_functionality/compass_rose.rs +++ b/editor/src/messages/tool/common_functionality/compass_rose.rs @@ -1,9 +1,10 @@ use glam::{DAffine2, DVec2}; use crate::consts::{COMPASS_ROSE_ANGLE_WIDTH, COMPASS_ROSE_HOVER_RING_DIAMETER, COMPASS_ROSE_RING_INNER_DIAMETER}; +use crate::messages::prelude::DocumentMessageHandler; use std::f64::consts::FRAC_PI_2; -#[derive(Clone, Debug)] +#[derive(Clone, Default, Debug)] pub struct CompassRose { /// Transform to get from normalized pivot to viewspace transform_from_normalized: DAffine2, @@ -14,6 +15,11 @@ impl CompassRose { self.transform_from_normalized.transform_point2(DVec2::splat(0.5)) } + pub fn change_transform(&mut self, document: &DocumentMessageHandler) { + let [min, max] = document.selected_visible_and_unlock_layers_bounding_box_viewport().unwrap_or([DVec2::ZERO, DVec2::ONE]); + self.transform_from_normalized = DAffine2::from_translation(min) * DAffine2::from_scale(max - min); + } + /// Answers if the pointer is currently positioned over the pivot. pub fn compass_rose_state(&self, mouse: DVec2, angle: f64) -> CompassRoseState { let compass_center = self.get_compass_position(); diff --git a/editor/src/messages/tool/tool_messages/select_tool.rs b/editor/src/messages/tool/tool_messages/select_tool.rs index 56098dbb9d..f1ce57134a 100644 --- a/editor/src/messages/tool/tool_messages/select_tool.rs +++ b/editor/src/messages/tool/tool_messages/select_tool.rs @@ -588,6 +588,7 @@ impl Fsm for SelectToolFsmState { // Update pivot tool_data.pivot.update_pivot(document, &mut overlay_context, angle); + tool_data.compass_rose.change_transform(document); // Update compass rose let compass_center = tool_data.compass_rose.get_compass_position(); @@ -783,11 +784,13 @@ impl Fsm for SelectToolFsmState { .as_ref() .map(|man| man.transform * Quad::from_box(man.bounds)) .map_or(0., |quad| (quad.top_left() - quad.top_right()).to_angle()); - let compass_rose_state = tool_data.compass_rose.compass_rose_state(input.mouse.position, angle); + let mouse_position = input.mouse.position; + let compass_rose_state = tool_data.compass_rose.compass_rose_state(mouse_position, angle); + let is_over_pivot = tool_data.pivot.is_over(mouse_position); let state = // Dragging the pivot - if compass_rose_state.is_pivot() { + if is_over_pivot { responses.add(DocumentMessage::StartTransaction); // tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(), true, true); @@ -1123,13 +1126,8 @@ impl Fsm for SelectToolFsmState { (SelectToolFsmState::Ready { .. }, SelectToolMessage::PointerMove(_)) => { let mut cursor = tool_data.bounding_box_manager.as_ref().map_or(MouseCursorIcon::Default, |bounds| bounds.get_cursor(input, true)); - let angle = tool_data - .bounding_box_manager - .as_ref() - .map(|man| man.transform * Quad::from_box(man.bounds)) - .map_or(0., |quad| (quad.top_left() - quad.top_right()).to_angle()); // Dragging the pivot overrules the other operations - if tool_data.compass_rose.compass_rose_state(input.mouse.position, angle).is_pivot() { + if tool_data.pivot.is_over(input.mouse.position) { cursor = MouseCursorIcon::Move; } From 65e03cc25516ee8b7ab2b3de433e1e7affe230fc Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Fri, 14 Feb 2025 21:59:59 -0800 Subject: [PATCH 16/16] Code review --- editor/Cargo.toml | 2 +- editor/src/consts.rs | 3 +- .../document/overlays/utility_types.rs | 122 +++++++++--------- .../tool/common_functionality/compass_rose.rs | 77 ++++++----- .../tool/tool_messages/select_tool.rs | 24 ++-- 5 files changed, 112 insertions(+), 116 deletions(-) diff --git a/editor/Cargo.toml b/editor/Cargo.toml index 499703b2be..042d9eeeb1 100644 --- a/editor/Cargo.toml +++ b/editor/Cargo.toml @@ -2,7 +2,7 @@ name = "graphite-editor" publish = false version = "0.0.0" -rust-version = "1.79" +rust-version = "1.82" authors = ["Graphite Authors "] edition = "2021" readme = "../README.md" diff --git a/editor/src/consts.rs b/editor/src/consts.rs index 2b7abfbbcd..c10a8d8615 100644 --- a/editor/src/consts.rs +++ b/editor/src/consts.rs @@ -61,11 +61,12 @@ pub const PIVOT_CROSSHAIR_LENGTH: f64 = 9.; pub const PIVOT_DIAMETER: f64 = 5.; // COMPASS ROSE -pub const COMPASS_ROSE_ANGLE_WIDTH: f64 = 20.; // Must be less than 45 pub const COMPASS_ROSE_RING_INNER_DIAMETER: f64 = 13.; pub const COMPASS_ROSE_MAIN_RING_DIAMETER: f64 = 15.; pub const COMPASS_ROSE_HOVER_RING_DIAMETER: f64 = 23.; pub const COMPASS_ROSE_ARROW_SIZE: f64 = 5.; +// Angle to either side of the compass arrows where they are targetted by the cursor (in degrees, must be less than 45°) +pub const COMPASS_ROSE_ARROW_CLICK_TARGET_ANGLE: f64 = 20.; // TRANSFORM OVERLAY pub const ANGLE_MEASURE_RADIUS_FACTOR: f64 = 0.04; diff --git a/editor/src/messages/portfolio/document/overlays/utility_types.rs b/editor/src/messages/portfolio/document/overlays/utility_types.rs index d187e70483..125a1ed091 100644 --- a/editor/src/messages/portfolio/document/overlays/utility_types.rs +++ b/editor/src/messages/portfolio/document/overlays/utility_types.rs @@ -1,8 +1,8 @@ use super::utility_functions::overlay_canvas_context; use crate::consts::{ - COLOR_OVERLAY_BLUE, COLOR_OVERLAY_GREEN, COLOR_OVERLAY_RED, COLOR_OVERLAY_WHITE, COLOR_OVERLAY_YELLOW, MANIPULATOR_GROUP_MARKER_SIZE, PIVOT_CROSSHAIR_LENGTH, PIVOT_CROSSHAIR_THICKNESS, + COLOR_OVERLAY_BLUE, COLOR_OVERLAY_GREEN, COLOR_OVERLAY_RED, COLOR_OVERLAY_WHITE, COLOR_OVERLAY_YELLOW, COMPASS_ROSE_ARROW_SIZE, COMPASS_ROSE_HOVER_RING_DIAMETER, COMPASS_ROSE_MAIN_RING_DIAMETER, + COMPASS_ROSE_RING_INNER_DIAMETER, MANIPULATOR_GROUP_MARKER_SIZE, PIVOT_CROSSHAIR_LENGTH, PIVOT_CROSSHAIR_THICKNESS, PIVOT_DIAMETER, }; -use crate::consts::{COMPASS_ROSE_ARROW_SIZE, COMPASS_ROSE_HOVER_RING_DIAMETER, COMPASS_ROSE_MAIN_RING_DIAMETER, COMPASS_ROSE_RING_INNER_DIAMETER, PIVOT_DIAMETER}; use crate::messages::prelude::Message; use bezier_rs::{Bezier, Subpath}; @@ -315,74 +315,72 @@ impl OverlayContext { } pub fn compass_rose(&mut self, compass_center: DVec2, angle: f64, show_compass_with_hover_ring: Option) { - let (compass_x, compass_y) = (compass_center.round() - DVec2::splat(0.5)).into(); - self.start_dpi_aware_transform(); - - if let Some(show_hover_ring) = show_compass_with_hover_ring { - let old_line_width = self.render_context.line_width(); - - const HOVER_RING_OUTER_RADIUS: f64 = COMPASS_ROSE_HOVER_RING_DIAMETER / 2.; - const MAIN_RING_OUTER_RADIUS: f64 = COMPASS_ROSE_MAIN_RING_DIAMETER / 2.; - const MAIN_RING_INNER_RADIUS: f64 = COMPASS_ROSE_RING_INNER_DIAMETER / 2.; - const ARROW_RADIUS: f64 = COMPASS_ROSE_ARROW_SIZE / 2.; + const HOVER_RING_OUTER_RADIUS: f64 = COMPASS_ROSE_HOVER_RING_DIAMETER / 2.; + const MAIN_RING_OUTER_RADIUS: f64 = COMPASS_ROSE_MAIN_RING_DIAMETER / 2.; + const MAIN_RING_INNER_RADIUS: f64 = COMPASS_ROSE_RING_INNER_DIAMETER / 2.; + const ARROW_RADIUS: f64 = COMPASS_ROSE_ARROW_SIZE / 2.; + const HOVER_RING_STROKE_WIDTH: f64 = HOVER_RING_OUTER_RADIUS - MAIN_RING_INNER_RADIUS; + const HOVER_RING_CENTERLINE_RADIUS: f64 = (HOVER_RING_OUTER_RADIUS + MAIN_RING_INNER_RADIUS) / 2.; + const MAIN_RING_STROKE_WIDTH: f64 = MAIN_RING_OUTER_RADIUS - MAIN_RING_INNER_RADIUS; + const MAIN_RING_CENTERLINE_RADIUS: f64 = (MAIN_RING_OUTER_RADIUS + MAIN_RING_INNER_RADIUS) / 2.; - // Hover ring - if show_hover_ring { - let hover_ring_stroke_width = HOVER_RING_OUTER_RADIUS - MAIN_RING_INNER_RADIUS; - let hover_ring_center_radius = (HOVER_RING_OUTER_RADIUS + MAIN_RING_INNER_RADIUS) / 2.0; + let Some(show_hover_ring) = show_compass_with_hover_ring else { return }; - let mut fill_color = graphene_std::Color::from_rgb_str(COLOR_OVERLAY_BLUE.strip_prefix('#').unwrap()).unwrap().with_alpha(0.5).rgba_hex(); - fill_color.insert(0, '#'); + self.start_dpi_aware_transform(); - self.render_context.set_line_width(hover_ring_stroke_width); - self.render_context.begin_path(); - self.render_context.arc(compass_x, compass_y, hover_ring_center_radius, 0., TAU).expect("Failed to draw hover ring"); - self.render_context.set_stroke_style_str(&fill_color); - self.render_context.stroke(); - } + let center = compass_center.round() - DVec2::splat(0.5); - // Arrows - self.render_context.set_line_width(0.01); - for i in 0..4 { - let base_angle = i as f64 * FRAC_PI_2 + angle; - let direction = DVec2::from_angle(base_angle); - let color = if i % 2 == 0 { COLOR_OVERLAY_RED } else { COLOR_OVERLAY_GREEN }; + // Save the old line width to restore it later + let old_line_width = self.render_context.line_width(); - let center = DVec2::new(compass_x, compass_y); + // Hover ring + if show_hover_ring { + let mut fill_color = graphene_std::Color::from_rgb_str(COLOR_OVERLAY_BLUE.strip_prefix('#').unwrap()).unwrap().with_alpha(0.5).rgba_hex(); + fill_color.insert(0, '#'); - let tip = center + direction * HOVER_RING_OUTER_RADIUS; - let base = center + direction * (MAIN_RING_INNER_RADIUS + MAIN_RING_OUTER_RADIUS) / 2.; + self.render_context.set_line_width(HOVER_RING_STROKE_WIDTH); + self.render_context.begin_path(); + self.render_context.arc(center.x, center.y, HOVER_RING_CENTERLINE_RADIUS, 0., TAU).expect("Failed to draw hover ring"); + self.render_context.set_stroke_style_str(&fill_color); + self.render_context.stroke(); + } - let r = (ARROW_RADIUS.powi(2) + MAIN_RING_INNER_RADIUS.powi(2)).sqrt(); - let (cos, sin) = (MAIN_RING_INNER_RADIUS / r, ARROW_RADIUS / r); - let side1 = center + r * DVec2::new(cos * direction.x - sin * direction.y, sin * direction.x + direction.y * cos); - let side2 = center + r * DVec2::new(cos * direction.x + sin * direction.y, -sin * direction.x + direction.y * cos); + // Arrows + self.render_context.set_line_width(0.01); + for i in 0..4 { + let direction = DVec2::from_angle(i as f64 * FRAC_PI_2 + angle); + let color = if i % 2 == 0 { COLOR_OVERLAY_RED } else { COLOR_OVERLAY_GREEN }; - self.render_context.begin_path(); - self.render_context.move_to(tip.x, tip.y); - self.render_context.line_to(side1.x, side1.y); - self.render_context.line_to(base.x, base.y); - self.render_context.line_to(side2.x, side2.y); - self.render_context.close_path(); + let tip = center + direction * HOVER_RING_OUTER_RADIUS; + let base = center + direction * (MAIN_RING_INNER_RADIUS + MAIN_RING_OUTER_RADIUS) / 2.; - self.render_context.set_fill_style_str(color); - self.render_context.fill(); - self.render_context.set_stroke_style_str(color); - self.render_context.stroke(); - } + let r = (ARROW_RADIUS.powi(2) + MAIN_RING_INNER_RADIUS.powi(2)).sqrt(); + let (cos, sin) = (MAIN_RING_INNER_RADIUS / r, ARROW_RADIUS / r); + let side1 = center + r * DVec2::new(cos * direction.x - sin * direction.y, sin * direction.x + direction.y * cos); + let side2 = center + r * DVec2::new(cos * direction.x + sin * direction.y, -sin * direction.x + direction.y * cos); - // Main ring - let main_ring_stroke_width = MAIN_RING_OUTER_RADIUS - MAIN_RING_INNER_RADIUS; - let main_ring_center_radius = (MAIN_RING_OUTER_RADIUS + MAIN_RING_INNER_RADIUS) / 2.0; - - self.render_context.set_line_width(main_ring_stroke_width); self.render_context.begin_path(); - self.render_context.arc(compass_x, compass_y, main_ring_center_radius, 0., TAU).expect("Failed to draw main ring"); - self.render_context.set_stroke_style_str(COLOR_OVERLAY_BLUE); - self.render_context.stroke(); + self.render_context.move_to(tip.x, tip.y); + self.render_context.line_to(side1.x, side1.y); + self.render_context.line_to(base.x, base.y); + self.render_context.line_to(side2.x, side2.y); + self.render_context.close_path(); - self.render_context.set_line_width(old_line_width); + self.render_context.set_fill_style_str(color); + self.render_context.fill(); + self.render_context.set_stroke_style_str(color); + self.render_context.stroke(); } + + // Main ring + self.render_context.set_line_width(MAIN_RING_STROKE_WIDTH); + self.render_context.begin_path(); + self.render_context.arc(center.x, center.y, MAIN_RING_CENTERLINE_RADIUS, 0., TAU).expect("Failed to draw main ring"); + self.render_context.set_stroke_style_str(COLOR_OVERLAY_BLUE); + self.render_context.stroke(); + + // Restore the old line width + self.render_context.set_line_width(old_line_width); } pub fn pivot(&mut self, position: DVec2, angle: f64) { @@ -401,19 +399,19 @@ impl OverlayContext { // Crosshair // Round line caps add half the stroke width to the length on each end, so we subtract that here before halving to get the radius - let crosshair_radius = (PIVOT_CROSSHAIR_LENGTH - PIVOT_CROSSHAIR_THICKNESS) / 2.; + const CROSSHAIR_RADIUS: f64 = (PIVOT_CROSSHAIR_LENGTH - PIVOT_CROSSHAIR_THICKNESS) / 2.; self.render_context.set_stroke_style_str(COLOR_OVERLAY_YELLOW); self.render_context.set_line_cap("round"); self.render_context.begin_path(); - self.render_context.move_to(x + crosshair_radius * uv.x, y + crosshair_radius * uv.y); - self.render_context.line_to(x - crosshair_radius * uv.x, y - crosshair_radius * uv.y); + self.render_context.move_to(x + CROSSHAIR_RADIUS * uv.x, y + CROSSHAIR_RADIUS * uv.y); + self.render_context.line_to(x - CROSSHAIR_RADIUS * uv.x, y - CROSSHAIR_RADIUS * uv.y); self.render_context.stroke(); self.render_context.begin_path(); - self.render_context.move_to(x - crosshair_radius * uv.y, y + crosshair_radius * uv.x); - self.render_context.line_to(x + crosshair_radius * uv.y, y - crosshair_radius * uv.x); + self.render_context.move_to(x - CROSSHAIR_RADIUS * uv.y, y + CROSSHAIR_RADIUS * uv.x); + self.render_context.line_to(x + CROSSHAIR_RADIUS * uv.y, y - CROSSHAIR_RADIUS * uv.x); self.render_context.stroke(); self.render_context.set_line_cap("butt"); diff --git a/editor/src/messages/tool/common_functionality/compass_rose.rs b/editor/src/messages/tool/common_functionality/compass_rose.rs index 182fcae94d..be7af7ca79 100644 --- a/editor/src/messages/tool/common_functionality/compass_rose.rs +++ b/editor/src/messages/tool/common_functionality/compass_rose.rs @@ -1,45 +1,44 @@ -use glam::{DAffine2, DVec2}; - -use crate::consts::{COMPASS_ROSE_ANGLE_WIDTH, COMPASS_ROSE_HOVER_RING_DIAMETER, COMPASS_ROSE_RING_INNER_DIAMETER}; +use crate::consts::{COMPASS_ROSE_ARROW_CLICK_TARGET_ANGLE, COMPASS_ROSE_HOVER_RING_DIAMETER, COMPASS_ROSE_RING_INNER_DIAMETER}; use crate::messages::prelude::DocumentMessageHandler; + +use glam::{DAffine2, DVec2}; use std::f64::consts::FRAC_PI_2; #[derive(Clone, Default, Debug)] pub struct CompassRose { - /// Transform to get from normalized pivot to viewspace - transform_from_normalized: DAffine2, + compass_center: DVec2, } impl CompassRose { - pub fn get_compass_position(&self) -> DVec2 { - self.transform_from_normalized.transform_point2(DVec2::splat(0.5)) + pub fn refresh_transform(&mut self, document: &DocumentMessageHandler) { + let [min, max] = document.selected_visible_and_unlock_layers_bounding_box_viewport().unwrap_or([DVec2::ZERO, DVec2::ONE]); + self.compass_center = (DAffine2::from_translation(min) * DAffine2::from_scale(max - min)).transform_point2(DVec2::splat(0.5)); } - pub fn change_transform(&mut self, document: &DocumentMessageHandler) { - let [min, max] = document.selected_visible_and_unlock_layers_bounding_box_viewport().unwrap_or([DVec2::ZERO, DVec2::ONE]); - self.transform_from_normalized = DAffine2::from_translation(min) * DAffine2::from_scale(max - min); + pub fn compass_rose_position(&self) -> DVec2 { + self.compass_center } - /// Answers if the pointer is currently positioned over the pivot. pub fn compass_rose_state(&self, mouse: DVec2, angle: f64) -> CompassRoseState { - let compass_center = self.get_compass_position(); + const COMPASS_ROSE_RING_INNER_RADIUS_SQUARED: f64 = (COMPASS_ROSE_RING_INNER_DIAMETER / 2.) * (COMPASS_ROSE_RING_INNER_DIAMETER / 2.); + const COMPASS_ROSE_HOVER_RING_RADIUS_SQUARED: f64 = (COMPASS_ROSE_HOVER_RING_DIAMETER / 2.) * (COMPASS_ROSE_HOVER_RING_DIAMETER / 2.); + + let compass_distance_squared = mouse.distance_squared(self.compass_center); - let compass_distance_squared = mouse.distance_squared(compass_center); + if !(COMPASS_ROSE_RING_INNER_RADIUS_SQUARED..COMPASS_ROSE_HOVER_RING_RADIUS_SQUARED).contains(&compass_distance_squared) { + return CompassRoseState::None; + } - if (COMPASS_ROSE_RING_INNER_DIAMETER / 2.).powi(2) < compass_distance_squared && compass_distance_squared < (COMPASS_ROSE_HOVER_RING_DIAMETER / 2.).powi(2) { - let angle = (mouse - compass_center).angle_to(DVec2::from_angle(angle)).abs(); - let resolved_angle = (FRAC_PI_2 - angle).abs(); - let width = COMPASS_ROSE_ANGLE_WIDTH.to_radians(); + let angle = (mouse - self.compass_center).angle_to(DVec2::from_angle(angle)).abs(); + let resolved_angle = (FRAC_PI_2 - angle).abs(); + let angular_width = COMPASS_ROSE_ARROW_CLICK_TARGET_ANGLE.to_radians(); - if resolved_angle < width { - CompassRoseState::AxisY - } else if resolved_angle > (FRAC_PI_2 - width) { - CompassRoseState::AxisX - } else { - CompassRoseState::Ring - } + if resolved_angle < angular_width { + CompassRoseState::AxisY + } else if resolved_angle > (FRAC_PI_2 - angular_width) { + CompassRoseState::AxisX } else { - CompassRoseState::None + CompassRoseState::Ring } } } @@ -52,20 +51,6 @@ pub enum CompassRoseState { None, } -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)] -pub enum Axis { - #[default] - None, - X, - Y, -} - -impl Axis { - pub fn is_constraint(&self) -> bool { - matches!(self, Self::X | Self::Y) - } -} - impl CompassRoseState { pub fn can_grab(&self) -> bool { matches!(self, Self::Ring | Self::AxisX | Self::AxisY) @@ -84,3 +69,17 @@ impl CompassRoseState { } } } + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)] +pub enum Axis { + #[default] + None, + X, + Y, +} + +impl Axis { + pub fn is_constraint(&self) -> bool { + matches!(self, Self::X | Self::Y) + } +} diff --git a/editor/src/messages/tool/tool_messages/select_tool.rs b/editor/src/messages/tool/tool_messages/select_tool.rs index f1ce57134a..a2befd59c1 100644 --- a/editor/src/messages/tool/tool_messages/select_tool.rs +++ b/editor/src/messages/tool/tool_messages/select_tool.rs @@ -588,10 +588,10 @@ impl Fsm for SelectToolFsmState { // Update pivot tool_data.pivot.update_pivot(document, &mut overlay_context, angle); - tool_data.compass_rose.change_transform(document); // Update compass rose - let compass_center = tool_data.compass_rose.get_compass_position(); + tool_data.compass_rose.refresh_transform(document); + let compass_center = tool_data.compass_rose.compass_rose_position(); overlay_context.compass_rose(compass_center, angle, show_compass_with_ring); let axis_state = if let SelectToolFsmState::Dragging { axis, .. } = self { @@ -608,7 +608,6 @@ impl Fsm for SelectToolFsmState { .map(|man| man.transform * Quad::from_box(man.bounds)) .map_or(DVec2::X, |quad| (quad.top_left() - quad.top_right()).normalize_or(DVec2::X)); - let origin = tool_data.compass_rose.get_compass_position(); let (direction, color) = match axis { Axis::X => (e0, COLOR_OVERLAY_RED), Axis::Y => (e0.perp(), COLOR_OVERLAY_GREEN), @@ -617,14 +616,13 @@ impl Fsm for SelectToolFsmState { let viewport_diagonal = input.viewport_bounds.size().length(); - if hover { - let color_string = &graphene_std::Color::from_rgb_str(color.strip_prefix('#').unwrap()).unwrap().with_alpha(0.25).rgba_hex(); - let color = &format!("#{}", color_string); - - overlay_context.line(origin - direction * viewport_diagonal, origin + direction * viewport_diagonal, Some(color)); + let color = if !hover { + color } else { - overlay_context.line(origin - direction * viewport_diagonal, origin + direction * viewport_diagonal, Some(color)); - } + let color_string = &graphene_std::Color::from_rgb_str(color.strip_prefix('#').unwrap()).unwrap().with_alpha(0.25).rgba_hex(); + &format!("#{}", color_string) + }; + overlay_context.line(compass_center - direction * viewport_diagonal, compass_center + direction * viewport_diagonal, Some(color)); } } @@ -639,7 +637,7 @@ impl Fsm for SelectToolFsmState { let other = other.as_str(); let extension = tool_data.drag_current - tool_data.drag_start; - let origin = tool_data.compass_rose.get_compass_position() - extension; + let origin = compass_center - extension; let viewport_diagonal = input.viewport_bounds.size().length(); let edge = DVec2::from_angle(snapped_angle) * viewport_diagonal; @@ -882,8 +880,8 @@ impl Fsm for SelectToolFsmState { tool_data.get_snap_candidates(document, input); let axis = compass_rose_state.axis_type(); match axis { - Some(axis) => SelectToolFsmState::Dragging{axis, using_compass: true}, - None => SelectToolFsmState::Dragging{axis: Axis::None, using_compass: false} + Some(axis) => SelectToolFsmState::Dragging { axis, using_compass: true }, + None => SelectToolFsmState::Dragging { axis: Axis::None, using_compass: false } } } // Dragging a selection box