mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-10-01 09:48:11 +08:00
Add Arc drawing mode to the Shape tool and the associated angle gizmos (#2757)
* implement arc gizmo handler * fixed wrapping need to fix snapping and overlays * fixed all the issues * Code review --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
co-authored by
Keavon Chambers
parent
a1796dbc08
commit
3a8c1b6f97
@@ -4,6 +4,7 @@ use crate::messages::portfolio::document::utility_types::document_metadata::Laye
|
||||
use crate::messages::prelude::{DocumentMessageHandler, InputPreprocessorMessageHandler};
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||
use crate::messages::tool::common_functionality::shape_editor::ShapeState;
|
||||
use crate::messages::tool::common_functionality::shapes::arc_shape::ArcGizmoHandler;
|
||||
use crate::messages::tool::common_functionality::shapes::polygon_shape::PolygonGizmoHandler;
|
||||
use crate::messages::tool::common_functionality::shapes::shape_utility::ShapeGizmoHandler;
|
||||
use crate::messages::tool::common_functionality::shapes::star_shape::StarGizmoHandler;
|
||||
@@ -23,6 +24,7 @@ pub enum ShapeGizmoHandlers {
|
||||
None,
|
||||
Star(StarGizmoHandler),
|
||||
Polygon(PolygonGizmoHandler),
|
||||
Arc(ArcGizmoHandler),
|
||||
}
|
||||
|
||||
impl ShapeGizmoHandlers {
|
||||
@@ -32,6 +34,7 @@ impl ShapeGizmoHandlers {
|
||||
match self {
|
||||
Self::Star(_) => "star",
|
||||
Self::Polygon(_) => "polygon",
|
||||
Self::Arc(_) => "arc",
|
||||
Self::None => "none",
|
||||
}
|
||||
}
|
||||
@@ -41,6 +44,7 @@ impl ShapeGizmoHandlers {
|
||||
match self {
|
||||
Self::Star(h) => h.handle_state(layer, mouse_position, document, responses),
|
||||
Self::Polygon(h) => h.handle_state(layer, mouse_position, document, responses),
|
||||
Self::Arc(h) => h.handle_state(layer, mouse_position, document, responses),
|
||||
Self::None => {}
|
||||
}
|
||||
}
|
||||
@@ -50,6 +54,7 @@ impl ShapeGizmoHandlers {
|
||||
match self {
|
||||
Self::Star(h) => h.is_any_gizmo_hovered(),
|
||||
Self::Polygon(h) => h.is_any_gizmo_hovered(),
|
||||
Self::Arc(h) => h.is_any_gizmo_hovered(),
|
||||
Self::None => false,
|
||||
}
|
||||
}
|
||||
@@ -59,6 +64,7 @@ impl ShapeGizmoHandlers {
|
||||
match self {
|
||||
Self::Star(h) => h.handle_click(),
|
||||
Self::Polygon(h) => h.handle_click(),
|
||||
Self::Arc(h) => h.handle_click(),
|
||||
Self::None => {}
|
||||
}
|
||||
}
|
||||
@@ -68,6 +74,7 @@ impl ShapeGizmoHandlers {
|
||||
match self {
|
||||
Self::Star(h) => h.handle_update(drag_start, document, input, responses),
|
||||
Self::Polygon(h) => h.handle_update(drag_start, document, input, responses),
|
||||
Self::Arc(h) => h.handle_update(drag_start, document, input, responses),
|
||||
Self::None => {}
|
||||
}
|
||||
}
|
||||
@@ -77,6 +84,7 @@ impl ShapeGizmoHandlers {
|
||||
match self {
|
||||
Self::Star(h) => h.cleanup(),
|
||||
Self::Polygon(h) => h.cleanup(),
|
||||
Self::Arc(h) => h.cleanup(),
|
||||
Self::None => {}
|
||||
}
|
||||
}
|
||||
@@ -94,6 +102,7 @@ impl ShapeGizmoHandlers {
|
||||
match self {
|
||||
Self::Star(h) => h.overlays(document, layer, input, shape_editor, mouse_position, overlay_context),
|
||||
Self::Polygon(h) => h.overlays(document, layer, input, shape_editor, mouse_position, overlay_context),
|
||||
Self::Arc(h) => h.overlays(document, layer, input, shape_editor, mouse_position, overlay_context),
|
||||
Self::None => {}
|
||||
}
|
||||
}
|
||||
@@ -110,6 +119,7 @@ impl ShapeGizmoHandlers {
|
||||
match self {
|
||||
Self::Star(h) => h.dragging_overlays(document, input, shape_editor, mouse_position, overlay_context),
|
||||
Self::Polygon(h) => h.dragging_overlays(document, input, shape_editor, mouse_position, overlay_context),
|
||||
Self::Arc(h) => h.dragging_overlays(document, input, shape_editor, mouse_position, overlay_context),
|
||||
Self::None => {}
|
||||
}
|
||||
}
|
||||
@@ -141,11 +151,14 @@ impl GizmoManager {
|
||||
if graph_modification_utils::get_star_id(layer, &document.network_interface).is_some() {
|
||||
return Some(ShapeGizmoHandlers::Star(StarGizmoHandler::default()));
|
||||
}
|
||||
|
||||
// Polygon
|
||||
if graph_modification_utils::get_polygon_id(layer, &document.network_interface).is_some() {
|
||||
return Some(ShapeGizmoHandlers::Polygon(PolygonGizmoHandler::default()));
|
||||
}
|
||||
// Arc
|
||||
if graph_modification_utils::get_arc_id(layer, &document.network_interface).is_some() {
|
||||
return Some(ShapeGizmoHandlers::Arc(ArcGizmoHandler::new()));
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
pub mod number_of_points_dial;
|
||||
pub mod point_radius_handle;
|
||||
pub mod sweep_angle_gizmo;
|
||||
|
||||
+24
-9
@@ -262,7 +262,6 @@ impl PointRadiusHandle {
|
||||
};
|
||||
|
||||
let viewport = document.metadata().transform_to_viewport(layer);
|
||||
let center = viewport.transform_point2(DVec2::ZERO);
|
||||
|
||||
match snapping_index {
|
||||
// Make a triangle with previous two points
|
||||
@@ -274,41 +273,57 @@ impl PointRadiusHandle {
|
||||
overlay_context.line(before_outer_position, outer_position, Some(COLOR_OVERLAY_RED), Some(3.));
|
||||
overlay_context.line(outer_position, point_position, Some(COLOR_OVERLAY_RED), Some(3.));
|
||||
|
||||
let before_outer_position = viewport.inverse().transform_point2(before_outer_position);
|
||||
let outer_position = viewport.inverse().transform_point2(outer_position);
|
||||
let point_position = viewport.inverse().transform_point2(point_position);
|
||||
|
||||
let l1 = (before_outer_position - outer_position).length() * 0.2;
|
||||
let Some(l1_direction) = (before_outer_position - outer_position).try_normalize() else { return };
|
||||
let Some(l2_direction) = (point_position - outer_position).try_normalize() else { return };
|
||||
let Some(direction) = (center - outer_position).try_normalize() else { return };
|
||||
let Some(direction) = (-outer_position).try_normalize() else { return };
|
||||
|
||||
let new_point = SQRT_2 * l1 * direction + outer_position;
|
||||
|
||||
let before_outer_position = l1 * l1_direction + outer_position;
|
||||
let point_position = l1 * l2_direction + outer_position;
|
||||
|
||||
overlay_context.line(before_outer_position, new_point, Some(COLOR_OVERLAY_RED), Some(3.));
|
||||
overlay_context.line(new_point, point_position, Some(COLOR_OVERLAY_RED), Some(3.));
|
||||
overlay_context.line(
|
||||
viewport.transform_point2(before_outer_position),
|
||||
viewport.transform_point2(new_point),
|
||||
Some(COLOR_OVERLAY_RED),
|
||||
Some(3.),
|
||||
);
|
||||
overlay_context.line(viewport.transform_point2(new_point), viewport.transform_point2(point_position), Some(COLOR_OVERLAY_RED), Some(3.));
|
||||
}
|
||||
1 => {
|
||||
let before_outer_position = star_vertex_position(viewport, (self.point as i32) - 1, sides, radius1, radius2);
|
||||
|
||||
let after_point_position = star_vertex_position(viewport, (self.point as i32) + 1, sides, radius1, radius2);
|
||||
|
||||
let point_position = star_vertex_position(viewport, self.point as i32, sides, radius1, radius2);
|
||||
|
||||
overlay_context.line(before_outer_position, point_position, Some(COLOR_OVERLAY_RED), Some(3.));
|
||||
overlay_context.line(point_position, after_point_position, Some(COLOR_OVERLAY_RED), Some(3.));
|
||||
|
||||
let before_outer_position = viewport.inverse().transform_point2(before_outer_position);
|
||||
let after_point_position = viewport.inverse().transform_point2(after_point_position);
|
||||
let point_position = viewport.inverse().transform_point2(point_position);
|
||||
|
||||
let l1 = (before_outer_position - point_position).length() * 0.2;
|
||||
let Some(l1_direction) = (before_outer_position - point_position).try_normalize() else { return };
|
||||
let Some(l2_direction) = (after_point_position - point_position).try_normalize() else { return };
|
||||
let Some(direction) = (center - point_position).try_normalize() else { return };
|
||||
let Some(direction) = (-point_position).try_normalize() else { return };
|
||||
|
||||
let new_point = SQRT_2 * l1 * direction + point_position;
|
||||
|
||||
let before_outer_position = l1 * l1_direction + point_position;
|
||||
let after_point_position = l1 * l2_direction + point_position;
|
||||
|
||||
overlay_context.line(before_outer_position, new_point, Some(COLOR_OVERLAY_RED), Some(3.));
|
||||
overlay_context.line(new_point, after_point_position, Some(COLOR_OVERLAY_RED), Some(3.));
|
||||
overlay_context.line(
|
||||
viewport.transform_point2(before_outer_position),
|
||||
viewport.transform_point2(new_point),
|
||||
Some(COLOR_OVERLAY_RED),
|
||||
Some(3.),
|
||||
);
|
||||
overlay_context.line(viewport.transform_point2(new_point), viewport.transform_point2(after_point_position), Some(COLOR_OVERLAY_RED), Some(3.));
|
||||
}
|
||||
i => {
|
||||
// Use `self.point` as absolute reference as it matches the index of vertices of the star starting from 0
|
||||
|
||||
+355
@@ -0,0 +1,355 @@
|
||||
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::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::*;
|
||||
use glam::DVec2;
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::{NodeId, NodeInput};
|
||||
use std::collections::VecDeque;
|
||||
use std::f64::consts::FRAC_PI_4;
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
pub enum SweepAngleGizmoState {
|
||||
#[default]
|
||||
Inactive,
|
||||
Hover,
|
||||
Dragging,
|
||||
Snapped,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
pub enum EndpointType {
|
||||
#[default]
|
||||
None,
|
||||
Start,
|
||||
End,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct SweepAngleGizmo {
|
||||
pub layer: Option<LayerNodeIdentifier>,
|
||||
endpoint: EndpointType,
|
||||
initial_start_angle: f64,
|
||||
initial_sweep_angle: f64,
|
||||
position_before_rotation: DVec2,
|
||||
previous_mouse_position: DVec2,
|
||||
total_angle_delta: f64,
|
||||
snap_angles: Vec<f64>,
|
||||
handle_state: SweepAngleGizmoState,
|
||||
}
|
||||
|
||||
impl SweepAngleGizmo {
|
||||
pub fn hovered(&self) -> bool {
|
||||
self.handle_state == SweepAngleGizmoState::Hover
|
||||
}
|
||||
|
||||
pub fn update_state(&mut self, state: SweepAngleGizmoState) {
|
||||
self.handle_state = state;
|
||||
}
|
||||
|
||||
pub fn is_dragging_or_snapped(&self) -> bool {
|
||||
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>) {
|
||||
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 {
|
||||
return;
|
||||
};
|
||||
|
||||
let center = document.metadata().transform_to_viewport(layer).transform_point2(DVec2::ZERO);
|
||||
|
||||
if center.distance(start) < GIZMO_HIDE_THRESHOLD {
|
||||
return;
|
||||
}
|
||||
|
||||
let (close_to_gizmo, endpoint_type) = if mouse_position.distance(start) < 5. {
|
||||
(true, EndpointType::Start)
|
||||
} else if mouse_position.distance(end) < 5. {
|
||||
(true, EndpointType::End)
|
||||
} else {
|
||||
(false, EndpointType::None)
|
||||
};
|
||||
|
||||
if close_to_gizmo {
|
||||
self.layer = Some(layer);
|
||||
self.initial_start_angle = start_angle;
|
||||
self.initial_sweep_angle = sweep_angle;
|
||||
self.previous_mouse_position = mouse_position;
|
||||
self.total_angle_delta = 0.;
|
||||
self.position_before_rotation = if endpoint_type == EndpointType::End { end } else { start };
|
||||
self.endpoint = endpoint_type;
|
||||
self.snap_angles = Self::calculate_snap_angles();
|
||||
|
||||
self.update_state(SweepAngleGizmoState::Hover);
|
||||
|
||||
responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Default });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn overlays(
|
||||
&self,
|
||||
selected_arc_layer: Option<LayerNodeIdentifier>,
|
||||
document: &DocumentMessageHandler,
|
||||
_input: &InputPreprocessorMessageHandler,
|
||||
_mouse_position: DVec2,
|
||||
overlay_context: &mut OverlayContext,
|
||||
) {
|
||||
let tilt_offset = document.document_ptz.unmodified_tilt();
|
||||
|
||||
match self.handle_state {
|
||||
SweepAngleGizmoState::Inactive => {
|
||||
// Draw both endpoint handles if an arc is selected
|
||||
let Some((point1, point2)) = arc_end_points(selected_arc_layer, document) else { return };
|
||||
overlay_context.manipulator_handle(point1, false, Some(COLOR_OVERLAY_RED));
|
||||
overlay_context.manipulator_handle(point2, false, Some(COLOR_OVERLAY_RED));
|
||||
}
|
||||
SweepAngleGizmoState::Hover => {
|
||||
// Highlight the currently hovered endpoint only
|
||||
let Some((point1, point2)) = arc_end_points(self.layer, document) else { return };
|
||||
|
||||
let point = if self.endpoint == EndpointType::Start { point1 } else { point2 };
|
||||
overlay_context.manipulator_handle(point, true, Some(COLOR_OVERLAY_RED));
|
||||
}
|
||||
SweepAngleGizmoState::Dragging => {
|
||||
// Show snapping guides and angle arc while dragging
|
||||
let Some(layer) = self.layer else { return };
|
||||
let Some((current_start, current_end)) = arc_end_points(self.layer, document) else { return };
|
||||
let viewport = document.metadata().transform_to_viewport(layer);
|
||||
|
||||
// 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 };
|
||||
self.dragging_snapping_overlays(self.position_before_rotation, point, tilt_offset, viewport, overlay_context);
|
||||
}
|
||||
SweepAngleGizmoState::Snapped => {
|
||||
// When snapping is active, draw snapping arcs and angular guidelines
|
||||
let Some((start, end)) = arc_end_points(self.layer, document) else { return };
|
||||
let Some(layer) = self.layer else { return };
|
||||
let viewport = document.metadata().transform_to_viewport(layer);
|
||||
let center = viewport.transform_point2(DVec2::ZERO);
|
||||
|
||||
// Draw snapping arc and angle overlays between the two points
|
||||
let (a, b) = if self.endpoint == EndpointType::Start { (end, start) } else { (start, end) };
|
||||
self.dragging_snapping_overlays(a, b, tilt_offset, viewport, overlay_context);
|
||||
|
||||
// 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.));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Draws the visual overlay during arc handle dragging or snapping interactions.
|
||||
/// This includes the dynamic arc sweep, angle label, and visual guides centered around the arc's origin.
|
||||
pub fn dragging_snapping_overlays(&self, initial_point: DVec2, final_point: DVec2, tilt_offset: f64, viewport: DAffine2, overlay_context: &mut OverlayContext) {
|
||||
let center = viewport.transform_point2(DVec2::ZERO);
|
||||
let initial_vector = initial_point - center;
|
||||
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();
|
||||
let display_angle = viewport
|
||||
.inverse()
|
||||
.transform_point2(final_point)
|
||||
.angle_to(viewport.inverse().transform_point2(initial_point))
|
||||
.to_degrees();
|
||||
|
||||
let text = format!("{}°", format_rounded(display_angle, 2));
|
||||
let text_texture_width = overlay_context.get_width(&text) / 2.;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
pub fn update_arc(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) {
|
||||
let Some(layer) = self.layer else { return };
|
||||
let Some((_, current_start_angle, current_sweep_angle, _)) = extract_arc_parameters(Some(layer), document) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let viewport = document.metadata().transform_to_viewport(layer);
|
||||
let angle_delta = viewport
|
||||
.inverse()
|
||||
.transform_point2(self.previous_mouse_position)
|
||||
.angle_to(viewport.inverse().transform_point2(input.mouse.position))
|
||||
.to_degrees();
|
||||
let angle = self.total_angle_delta + angle_delta;
|
||||
|
||||
let Some(node_id) = graph_modification_utils::get_arc_id(layer, &document.network_interface) else {
|
||||
return;
|
||||
};
|
||||
|
||||
self.update_state(SweepAngleGizmoState::Dragging);
|
||||
|
||||
match self.endpoint {
|
||||
EndpointType::Start => {
|
||||
// Dragging start changes both start and sweep
|
||||
|
||||
let sign = -angle.signum();
|
||||
let mut total = angle;
|
||||
|
||||
let new_start_angle = self.initial_start_angle + total;
|
||||
let new_sweep_angle = self.initial_sweep_angle + total.abs() * sign;
|
||||
|
||||
match () {
|
||||
// Clamp sweep angle to 360°
|
||||
() if new_sweep_angle > 360. => {
|
||||
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.apply_arc_update(node_id, self.initial_start_angle, self.initial_sweep_angle - wrapped, input, responses);
|
||||
}
|
||||
() if new_sweep_angle < 0. => {
|
||||
let rest_angle = angle_delta + new_sweep_angle;
|
||||
|
||||
self.total_angle_delta = new_sweep_angle.abs();
|
||||
self.endpoint = EndpointType::End;
|
||||
|
||||
self.initial_sweep_angle = 0.;
|
||||
self.initial_start_angle = current_start_angle + rest_angle;
|
||||
|
||||
self.apply_arc_update(node_id, self.initial_start_angle, new_sweep_angle.abs(), input, responses);
|
||||
}
|
||||
// Wrap start angle > 180° back into [-180°, 180°] and adjust sweep
|
||||
() if new_start_angle > 180. => {
|
||||
let overflow = new_start_angle % 180.;
|
||||
let rest_angle = angle_delta - overflow;
|
||||
|
||||
// We wrap the angle back into [-180°, 180°] range by jumping from +180° to -180°
|
||||
// Example: dragging past 190° becomes -170°, and we subtract the overshoot from sweep
|
||||
// Sweep angle must shrink to maintain consistent arc
|
||||
self.total_angle_delta = rest_angle;
|
||||
self.initial_start_angle = -180.;
|
||||
self.initial_sweep_angle = current_sweep_angle - rest_angle;
|
||||
|
||||
self.apply_arc_update(node_id, self.initial_start_angle + overflow, self.initial_sweep_angle - overflow, input, responses);
|
||||
}
|
||||
// Wrap start angle < -180° back into [-180°, 180°] and adjust sweep
|
||||
() if new_start_angle < -180. => {
|
||||
let underflow = new_start_angle % 180.;
|
||||
let rest_angle = angle_delta - underflow;
|
||||
|
||||
// We wrap the angle back into [-180°, 180°] by jumping from -190° to +170°
|
||||
// Sweep must grow to reflect continued clockwise drag past -180°
|
||||
// Start angle flips from -190° to +170°, and sweep increases accordingly
|
||||
self.total_angle_delta = underflow;
|
||||
self.initial_start_angle = 180.;
|
||||
self.initial_sweep_angle = current_sweep_angle + rest_angle.abs();
|
||||
|
||||
self.apply_arc_update(node_id, self.initial_start_angle + underflow, self.initial_sweep_angle + underflow.abs(), input, responses);
|
||||
}
|
||||
_ => {
|
||||
if let Some(snapped_delta) = self.check_snapping(self.initial_sweep_angle + total.abs() * sign) {
|
||||
total += snapped_delta;
|
||||
self.update_state(SweepAngleGizmoState::Snapped);
|
||||
}
|
||||
|
||||
self.total_angle_delta = angle;
|
||||
self.apply_arc_update(node_id, self.initial_start_angle + total, self.initial_sweep_angle + total.abs() * sign, input, responses);
|
||||
}
|
||||
}
|
||||
}
|
||||
EndpointType::End => {
|
||||
// Dragging the end only changes sweep angle
|
||||
|
||||
let mut total = angle;
|
||||
let new_sweep_angle = self.initial_sweep_angle + angle;
|
||||
|
||||
match () {
|
||||
// Clamp sweep angle below 0°, switch to start
|
||||
() if new_sweep_angle < 0. => {
|
||||
let delta = angle_delta - current_sweep_angle;
|
||||
let sign = -delta.signum();
|
||||
|
||||
self.initial_sweep_angle = 0.;
|
||||
self.total_angle_delta = delta;
|
||||
self.endpoint = EndpointType::Start;
|
||||
|
||||
self.apply_arc_update(node_id, self.initial_start_angle + delta, self.initial_sweep_angle + delta.abs() * sign, input, responses);
|
||||
}
|
||||
// Clamp sweep angle above 360°, switch to start
|
||||
() if new_sweep_angle > 360. => {
|
||||
let delta = angle_delta - (360. - current_sweep_angle);
|
||||
let sign = -delta.signum();
|
||||
|
||||
self.total_angle_delta = angle_delta;
|
||||
self.initial_sweep_angle = 360.;
|
||||
self.endpoint = EndpointType::Start;
|
||||
|
||||
self.apply_arc_update(node_id, self.initial_start_angle + angle_delta, self.initial_sweep_angle + angle_delta.abs() * sign, input, responses);
|
||||
}
|
||||
_ => {
|
||||
if let Some(snapped_delta) = self.check_snapping(self.initial_sweep_angle + angle) {
|
||||
total += snapped_delta;
|
||||
self.update_state(SweepAngleGizmoState::Snapped);
|
||||
}
|
||||
|
||||
self.total_angle_delta = angle;
|
||||
self.apply_arc_update(node_id, self.initial_start_angle, self.initial_sweep_angle + total, input, responses);
|
||||
}
|
||||
}
|
||||
}
|
||||
EndpointType::None => {}
|
||||
}
|
||||
}
|
||||
|
||||
/// Applies the updated start and sweep angles to the arc.
|
||||
fn apply_arc_update(&mut self, node_id: NodeId, start_angle: f64, sweep_angle: f64, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) {
|
||||
self.snap_angles = Self::calculate_snap_angles();
|
||||
|
||||
responses.add(NodeGraphMessage::SetInput {
|
||||
input_connector: InputConnector::node(node_id, 2),
|
||||
input: NodeInput::value(TaggedValue::F64(start_angle), false),
|
||||
});
|
||||
responses.add(NodeGraphMessage::SetInput {
|
||||
input_connector: InputConnector::node(node_id, 3),
|
||||
input: NodeInput::value(TaggedValue::F64(sweep_angle), false),
|
||||
});
|
||||
|
||||
self.previous_mouse_position = input.mouse.position;
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
}
|
||||
|
||||
pub fn check_snapping(&self, new_sweep_angle: f64) -> Option<f64> {
|
||||
self.snap_angles.iter().find(|angle| (**angle - new_sweep_angle).abs() <= ARC_SNAP_THRESHOLD).map(|angle| {
|
||||
let delta = angle - new_sweep_angle;
|
||||
if self.endpoint == EndpointType::End { delta } else { -delta }
|
||||
})
|
||||
}
|
||||
|
||||
pub fn calculate_snap_angles() -> Vec<f64> {
|
||||
let mut snap_points = Vec::new();
|
||||
|
||||
for i in 0..8 {
|
||||
let snap_point = i as f64 * FRAC_PI_4;
|
||||
snap_points.push(snap_point.to_degrees());
|
||||
}
|
||||
|
||||
snap_points
|
||||
}
|
||||
|
||||
pub fn cleanup(&mut self) {
|
||||
self.layer = None;
|
||||
self.endpoint = EndpointType::None;
|
||||
self.handle_state = SweepAngleGizmoState::Inactive;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user