mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-24 21:08:12 +08:00
Fix Shape tool arc gizmo snap visualization and pointer cursor icon when hovering or dragging (#2957)
* fixed cursor,arc bugs * Update cursor icon * send pointer-move only when required * make it compile * Code review * remove repeated modifier key --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
co-authored by
Keavon Chambers
parent
3cc9dd79fb
commit
7e0a274bd1
@@ -1,3 +1,4 @@
|
||||
use crate::messages::frontend::utility_types::MouseCursorIcon;
|
||||
use crate::messages::message::Message;
|
||||
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
|
||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||
@@ -123,6 +124,15 @@ impl ShapeGizmoHandlers {
|
||||
Self::None => {}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn gizmo_cursor_icon(&self) -> Option<MouseCursorIcon> {
|
||||
match self {
|
||||
Self::Star(h) => h.mouse_cursor_icon(),
|
||||
Self::Polygon(h) => h.mouse_cursor_icon(),
|
||||
Self::Arc(h) => h.mouse_cursor_icon(),
|
||||
Self::None => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Central manager that coordinates shape gizmo handlers for interactive editing on the canvas.
|
||||
@@ -256,4 +266,12 @@ impl GizmoManager {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the cursor icon to display when hovering or dragging a gizmo.
|
||||
///
|
||||
/// If a gizmo is active (hovered or being manipulated), it returns the cursor icon associated with that gizmo;
|
||||
/// otherwise, returns `None` to indicate the default crosshair cursor should be used.
|
||||
pub fn mouse_cursor_icon(&self) -> Option<MouseCursorIcon> {
|
||||
self.active_shape_handler.as_ref().and_then(|h| h.gizmo_cursor_icon())
|
||||
}
|
||||
}
|
||||
|
||||
+17
-13
@@ -1,10 +1,9 @@
|
||||
use crate::consts::{ARC_SNAP_THRESHOLD, COLOR_OVERLAY_RED, GIZMO_HIDE_THRESHOLD};
|
||||
use crate::messages::frontend::utility_types::MouseCursorIcon;
|
||||
use crate::messages::message::Message;
|
||||
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
|
||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||
use crate::messages::portfolio::document::utility_types::network_interface::InputConnector;
|
||||
use crate::messages::prelude::{DocumentMessageHandler, FrontendMessage};
|
||||
use crate::messages::prelude::DocumentMessageHandler;
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||
use crate::messages::tool::common_functionality::shapes::shape_utility::{arc_end_points, calculate_arc_text_transform, extract_arc_parameters, format_rounded};
|
||||
use crate::messages::tool::tool_messages::tool_prelude::*;
|
||||
@@ -57,7 +56,7 @@ impl SweepAngleGizmo {
|
||||
self.handle_state == SweepAngleGizmoState::Dragging || self.handle_state == SweepAngleGizmoState::Snapped
|
||||
}
|
||||
|
||||
pub fn handle_actions(&mut self, layer: LayerNodeIdentifier, document: &DocumentMessageHandler, mouse_position: DVec2, responses: &mut VecDeque<Message>) {
|
||||
pub fn handle_actions(&mut self, layer: LayerNodeIdentifier, document: &DocumentMessageHandler, mouse_position: DVec2) {
|
||||
if self.handle_state == SweepAngleGizmoState::Inactive {
|
||||
let Some((start, end)) = arc_end_points(Some(layer), document) else { return };
|
||||
let Some((_, start_angle, sweep_angle, _)) = extract_arc_parameters(Some(layer), document) else {
|
||||
@@ -89,8 +88,6 @@ impl SweepAngleGizmo {
|
||||
self.snap_angles = Self::calculate_snap_angles();
|
||||
|
||||
self.update_state(SweepAngleGizmoState::Hover);
|
||||
|
||||
responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Default });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -127,6 +124,11 @@ impl SweepAngleGizmo {
|
||||
|
||||
// Depending on which endpoint is being dragged, draw guides relative to the static point
|
||||
let point = if self.endpoint == EndpointType::End { current_end } else { current_start };
|
||||
|
||||
// Draw the dashed line from center to drag start position
|
||||
overlay_context.dashed_line(self.position_before_rotation, viewport.transform_point2(DVec2::ZERO), None, None, Some(5.), Some(5.), Some(0.5));
|
||||
|
||||
// Draw the angle, text and the bold line
|
||||
self.dragging_snapping_overlays(self.position_before_rotation, point, tilt_offset, viewport, overlay_context);
|
||||
}
|
||||
SweepAngleGizmoState::Snapped => {
|
||||
@@ -143,6 +145,9 @@ impl SweepAngleGizmo {
|
||||
// Draw lines from endpoints to the arc center
|
||||
overlay_context.line(start, center, Some(COLOR_OVERLAY_RED), Some(2.));
|
||||
overlay_context.line(end, center, Some(COLOR_OVERLAY_RED), Some(2.));
|
||||
|
||||
// Draw the line from drag start to arc center
|
||||
overlay_context.dashed_line(self.position_before_rotation, center, None, None, Some(5.), Some(5.), Some(0.5));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -155,7 +160,6 @@ impl SweepAngleGizmo {
|
||||
let final_vector = final_point - center;
|
||||
let offset_angle = initial_vector.to_angle() + tilt_offset;
|
||||
|
||||
let dash_radius = initial_point.distance(center);
|
||||
let bold_radius = final_point.distance(center);
|
||||
|
||||
let angle = initial_vector.angle_to(final_vector).to_degrees();
|
||||
@@ -170,7 +174,7 @@ impl SweepAngleGizmo {
|
||||
|
||||
let transform = calculate_arc_text_transform(angle, offset_angle, center, text_texture_width);
|
||||
|
||||
overlay_context.arc_sweep_angle(offset_angle, angle, final_point, bold_radius, dash_radius, center, &text, transform);
|
||||
overlay_context.arc_sweep_angle(offset_angle, angle, final_point, bold_radius, center, &text, transform);
|
||||
}
|
||||
|
||||
pub fn update_arc(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) {
|
||||
@@ -209,12 +213,11 @@ impl SweepAngleGizmo {
|
||||
let wrapped = new_sweep_angle % 360.;
|
||||
self.total_angle_delta = -wrapped;
|
||||
|
||||
// Remaining drag gets passed to the ending endpoint
|
||||
let rest_angle = angle_delta + wrapped;
|
||||
self.endpoint = EndpointType::End;
|
||||
|
||||
self.initial_sweep_angle = 360.;
|
||||
self.initial_start_angle = current_start_angle + rest_angle;
|
||||
self.initial_start_angle = current_start_angle;
|
||||
self.update_state(SweepAngleGizmoState::Snapped);
|
||||
|
||||
self.apply_arc_update(node_id, self.initial_start_angle, self.initial_sweep_angle - wrapped, input, responses);
|
||||
}
|
||||
@@ -288,12 +291,13 @@ impl SweepAngleGizmo {
|
||||
}
|
||||
// Clamp sweep angle above 360°, switch to start
|
||||
() if new_sweep_angle > 360. => {
|
||||
let delta = angle_delta - (360. - current_sweep_angle);
|
||||
let delta = angle_delta - (360. - new_sweep_angle);
|
||||
let sign = -delta.signum();
|
||||
|
||||
self.total_angle_delta = angle_delta;
|
||||
self.total_angle_delta = angle_delta - (360. - new_sweep_angle);
|
||||
self.initial_sweep_angle = 360.;
|
||||
self.endpoint = EndpointType::Start;
|
||||
self.update_state(SweepAngleGizmoState::Snapped);
|
||||
|
||||
self.apply_arc_update(node_id, self.initial_start_angle + angle_delta, self.initial_sweep_angle + angle_delta.abs() * sign, input, responses);
|
||||
}
|
||||
@@ -339,7 +343,7 @@ impl SweepAngleGizmo {
|
||||
pub fn calculate_snap_angles() -> Vec<f64> {
|
||||
let mut snap_points = Vec::new();
|
||||
|
||||
for i in 0..8 {
|
||||
for i in 0..=8 {
|
||||
let snap_point = i as f64 * FRAC_PI_4;
|
||||
snap_points.push(snap_point.to_degrees());
|
||||
}
|
||||
|
||||
@@ -26,8 +26,8 @@ impl ArcGizmoHandler {
|
||||
}
|
||||
|
||||
impl ShapeGizmoHandler for ArcGizmoHandler {
|
||||
fn handle_state(&mut self, selected_shape_layers: LayerNodeIdentifier, mouse_position: DVec2, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
|
||||
self.sweep_angle_gizmo.handle_actions(selected_shape_layers, document, mouse_position, responses);
|
||||
fn handle_state(&mut self, selected_shape_layers: LayerNodeIdentifier, mouse_position: DVec2, document: &DocumentMessageHandler, _responses: &mut VecDeque<Message>) {
|
||||
self.sweep_angle_gizmo.handle_actions(selected_shape_layers, document, mouse_position);
|
||||
}
|
||||
|
||||
fn is_any_gizmo_hovered(&self) -> bool {
|
||||
@@ -74,6 +74,14 @@ impl ShapeGizmoHandler for ArcGizmoHandler {
|
||||
arc_outline(selected_shape_layers.or(self.sweep_angle_gizmo.layer), document, overlay_context);
|
||||
}
|
||||
|
||||
fn mouse_cursor_icon(&self) -> Option<MouseCursorIcon> {
|
||||
if self.sweep_angle_gizmo.hovered() || self.sweep_angle_gizmo.is_dragging_or_snapped() {
|
||||
return Some(MouseCursorIcon::Default);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn cleanup(&mut self) {
|
||||
self.sweep_angle_gizmo.cleanup();
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ impl Ellipse {
|
||||
modifier: ShapeToolModifierKey,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) {
|
||||
let [center, lock_ratio, _, _] = modifier;
|
||||
let [center, lock_ratio, _] = modifier;
|
||||
|
||||
if let Some([start, end]) = shape_tool_data.data.calculate_points(document, ipp, center, lock_ratio) {
|
||||
let Some(node_id) = graph_modification_utils::get_ellipse_id(layer, &document.network_interface) else {
|
||||
|
||||
@@ -53,7 +53,7 @@ impl Line {
|
||||
modifier: ShapeToolModifierKey,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) {
|
||||
let [center, _, lock_angle, snap_angle] = modifier;
|
||||
let [center, snap_angle, lock_angle] = modifier;
|
||||
|
||||
shape_tool_data.line_data.drag_current = ipp.mouse.position;
|
||||
|
||||
|
||||
@@ -89,6 +89,18 @@ impl ShapeGizmoHandler for PolygonGizmoHandler {
|
||||
}
|
||||
}
|
||||
|
||||
fn mouse_cursor_icon(&self) -> Option<MouseCursorIcon> {
|
||||
if self.number_of_points_dial.is_dragging() || self.number_of_points_dial.is_hovering() {
|
||||
return Some(MouseCursorIcon::EWResize);
|
||||
}
|
||||
|
||||
if self.point_radius_handle.is_dragging_or_snapped() || self.point_radius_handle.hovered() {
|
||||
return Some(MouseCursorIcon::Default);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn cleanup(&mut self) {
|
||||
self.number_of_points_dial.cleanup();
|
||||
self.point_radius_handle.cleanup();
|
||||
@@ -112,7 +124,7 @@ impl Polygon {
|
||||
modifier: ShapeToolModifierKey,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) {
|
||||
let [center, lock_ratio, _, _] = modifier;
|
||||
let [center, lock_ratio, _] = modifier;
|
||||
|
||||
if let Some([start, end]) = shape_tool_data.data.calculate_points(document, ipp, center, lock_ratio) {
|
||||
// TODO: We need to determine how to allow the polygon node to make irregular shapes
|
||||
|
||||
@@ -28,7 +28,7 @@ impl Rectangle {
|
||||
modifier: ShapeToolModifierKey,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) {
|
||||
let [center, lock_ratio, _, _] = modifier;
|
||||
let [center, lock_ratio, _] = modifier;
|
||||
|
||||
if let Some([start, end]) = shape_tool_data.data.calculate_points(document, ipp, center, lock_ratio) {
|
||||
let Some(node_id) = graph_modification_utils::get_rectangle_id(layer, &document.network_interface) else {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use super::ShapeToolData;
|
||||
use crate::consts::{ARC_SWEEP_GIZMO_RADIUS, ARC_SWEEP_GIZMO_TEXT_HEIGHT};
|
||||
use crate::messages::frontend::utility_types::MouseCursorIcon;
|
||||
use crate::messages::message::Message;
|
||||
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
|
||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||
@@ -74,7 +75,7 @@ impl ShapeType {
|
||||
}
|
||||
}
|
||||
|
||||
pub type ShapeToolModifierKey = [Key; 4];
|
||||
pub type ShapeToolModifierKey = [Key; 3];
|
||||
|
||||
/// The `ShapeGizmoHandler` trait defines the interactive behavior and overlay logic for shape-specific tools in the editor.
|
||||
/// A gizmo is a visual handle or control point used to manipulate a shape's properties (e.g., number of sides, radius, angle).
|
||||
@@ -127,6 +128,8 @@ pub trait ShapeGizmoHandler {
|
||||
///
|
||||
/// For example, dragging states or hover flags should be cleared to avoid visual glitches when switching tools or shapes.
|
||||
fn cleanup(&mut self);
|
||||
|
||||
fn mouse_cursor_icon(&self) -> Option<MouseCursorIcon>;
|
||||
}
|
||||
|
||||
/// Center, Lock Ratio, Lock Angle, Snap Angle, Increase/Decrease Side
|
||||
|
||||
@@ -90,6 +90,18 @@ impl ShapeGizmoHandler for StarGizmoHandler {
|
||||
self.number_of_points_dial.cleanup();
|
||||
self.point_radius_handle.cleanup();
|
||||
}
|
||||
|
||||
fn mouse_cursor_icon(&self) -> Option<MouseCursorIcon> {
|
||||
if self.number_of_points_dial.is_dragging() || self.number_of_points_dial.is_hovering() {
|
||||
return Some(MouseCursorIcon::EWResize);
|
||||
}
|
||||
|
||||
if self.point_radius_handle.is_dragging_or_snapped() || self.point_radius_handle.hovered() {
|
||||
return Some(MouseCursorIcon::Default);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
@@ -114,7 +126,7 @@ impl Star {
|
||||
modifier: ShapeToolModifierKey,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) {
|
||||
let [center, lock_ratio, _, _] = modifier;
|
||||
let [center, lock_ratio, _] = modifier;
|
||||
|
||||
if let Some([start, end]) = shape_tool_data.data.calculate_points(document, ipp, center, lock_ratio) {
|
||||
// TODO: We need to determine how to allow the polygon node to make irregular shapes
|
||||
|
||||
Reference in New Issue
Block a user