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:
parent
a1796dbc08
commit
3a8c1b6f97
|
|
@ -126,6 +126,9 @@ pub const POINT_RADIUS_HANDLE_SNAP_THRESHOLD: f64 = 8.;
|
||||||
pub const POINT_RADIUS_HANDLE_SEGMENT_THRESHOLD: f64 = 7.9;
|
pub const POINT_RADIUS_HANDLE_SEGMENT_THRESHOLD: f64 = 7.9;
|
||||||
pub const NUMBER_OF_POINTS_DIAL_SPOKE_EXTENSION: f64 = 1.2;
|
pub const NUMBER_OF_POINTS_DIAL_SPOKE_EXTENSION: f64 = 1.2;
|
||||||
pub const NUMBER_OF_POINTS_DIAL_SPOKE_LENGTH: f64 = 10.;
|
pub const NUMBER_OF_POINTS_DIAL_SPOKE_LENGTH: f64 = 10.;
|
||||||
|
pub const ARC_SNAP_THRESHOLD: f64 = 5.;
|
||||||
|
pub const ARC_SWEEP_GIZMO_RADIUS: f64 = 14.;
|
||||||
|
pub const ARC_SWEEP_GIZMO_TEXT_HEIGHT: f64 = 12.;
|
||||||
pub const GIZMO_HIDE_THRESHOLD: f64 = 20.;
|
pub const GIZMO_HIDE_THRESHOLD: f64 = 20.;
|
||||||
|
|
||||||
// SCROLLBARS
|
// SCROLLBARS
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
use super::utility_functions::overlay_canvas_context;
|
use super::utility_functions::overlay_canvas_context;
|
||||||
use crate::consts::{
|
use crate::consts::{
|
||||||
COLOR_OVERLAY_BLUE, COLOR_OVERLAY_BLUE_50, COLOR_OVERLAY_GREEN, COLOR_OVERLAY_RED, COLOR_OVERLAY_WHITE, COLOR_OVERLAY_YELLOW, COLOR_OVERLAY_YELLOW_DULL, COMPASS_ROSE_ARROW_SIZE,
|
ARC_SWEEP_GIZMO_RADIUS, COLOR_OVERLAY_BLUE, COLOR_OVERLAY_BLUE_50, COLOR_OVERLAY_GREEN, COLOR_OVERLAY_RED, COLOR_OVERLAY_WHITE, COLOR_OVERLAY_YELLOW, COLOR_OVERLAY_YELLOW_DULL,
|
||||||
COMPASS_ROSE_HOVER_RING_DIAMETER, COMPASS_ROSE_MAIN_RING_DIAMETER, COMPASS_ROSE_RING_INNER_DIAMETER, DOWEL_PIN_RADIUS, MANIPULATOR_GROUP_MARKER_SIZE, PIVOT_CROSSHAIR_LENGTH,
|
COMPASS_ROSE_ARROW_SIZE, COMPASS_ROSE_HOVER_RING_DIAMETER, COMPASS_ROSE_MAIN_RING_DIAMETER, COMPASS_ROSE_RING_INNER_DIAMETER, DOWEL_PIN_RADIUS, MANIPULATOR_GROUP_MARKER_SIZE,
|
||||||
PIVOT_CROSSHAIR_THICKNESS, PIVOT_DIAMETER,
|
PIVOT_CROSSHAIR_LENGTH, PIVOT_CROSSHAIR_THICKNESS, PIVOT_DIAMETER,
|
||||||
};
|
};
|
||||||
use crate::messages::prelude::Message;
|
use crate::messages::prelude::Message;
|
||||||
use bezier_rs::{Bezier, Subpath};
|
use bezier_rs::{Bezier, Subpath};
|
||||||
|
|
@ -423,6 +423,14 @@ impl OverlayContext {
|
||||||
self.render_context.stroke();
|
self.render_context.stroke();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn draw_arc_gizmo_angle(&mut self, pivot: DVec2, bold_radius: f64, dash_radius: f64, arc_radius: f64, offset_angle: f64, angle: f64) {
|
||||||
|
let end_point1 = pivot + bold_radius * DVec2::from_angle(angle + offset_angle);
|
||||||
|
let end_point2 = pivot + dash_radius * DVec2::from_angle(offset_angle);
|
||||||
|
self.line(pivot, end_point1, None, None);
|
||||||
|
self.dashed_line(pivot, end_point2, None, None, Some(2.), Some(2.), Some(0.5));
|
||||||
|
self.draw_arc(pivot, arc_radius, offset_angle, (angle) % TAU + offset_angle);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn draw_angle(&mut self, pivot: DVec2, radius: f64, arc_radius: f64, offset_angle: f64, angle: f64) {
|
pub fn draw_angle(&mut self, pivot: DVec2, radius: f64, arc_radius: f64, offset_angle: f64, angle: f64) {
|
||||||
let end_point1 = pivot + radius * DVec2::from_angle(angle + offset_angle);
|
let end_point1 = pivot + radius * DVec2::from_angle(angle + offset_angle);
|
||||||
let end_point2 = pivot + radius * DVec2::from_angle(offset_angle);
|
let end_point2 = pivot + radius * DVec2::from_angle(offset_angle);
|
||||||
|
|
@ -584,6 +592,12 @@ impl OverlayContext {
|
||||||
self.end_dpi_aware_transform();
|
self.end_dpi_aware_transform();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn arc_sweep_angle(&mut self, offset_angle: f64, angle: f64, end_point_position: DVec2, bold_radius: f64, dash_radius: f64, pivot: DVec2, text: &str, transform: DAffine2) {
|
||||||
|
self.manipulator_handle(end_point_position, true, Some(COLOR_OVERLAY_RED));
|
||||||
|
self.draw_arc_gizmo_angle(pivot, bold_radius, dash_radius, ARC_SWEEP_GIZMO_RADIUS, offset_angle, angle.to_radians());
|
||||||
|
self.text(&text, COLOR_OVERLAY_BLUE, None, transform, 16., [Pivot::Middle, Pivot::Middle]);
|
||||||
|
}
|
||||||
|
|
||||||
/// Used by the Pen and Path tools to outline the path of the shape.
|
/// Used by the Pen and Path tools to outline the path of the shape.
|
||||||
pub fn outline_vector(&mut self, vector_data: &VectorData, transform: DAffine2) {
|
pub fn outline_vector(&mut self, vector_data: &VectorData, transform: DAffine2) {
|
||||||
self.start_dpi_aware_transform();
|
self.start_dpi_aware_transform();
|
||||||
|
|
|
||||||
|
|
@ -517,8 +517,8 @@ impl<'a> Selected<'a> {
|
||||||
tool_type: &'a ToolType,
|
tool_type: &'a ToolType,
|
||||||
pen_handle: Option<&'a mut DVec2>,
|
pen_handle: Option<&'a mut DVec2>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
// If user is using the Select tool then use the original layer transforms
|
// If user is using the Select tool or Shape tool then use the original layer transforms
|
||||||
if (*tool_type == ToolType::Select) && (*original_transforms == OriginalTransforms::Path(HashMap::new())) {
|
if (*tool_type == ToolType::Select || *tool_type == ToolType::Shape) && (*original_transforms == OriginalTransforms::Path(HashMap::new())) {
|
||||||
*original_transforms = OriginalTransforms::Layer(HashMap::new());
|
*original_transforms = OriginalTransforms::Layer(HashMap::new());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -338,6 +338,7 @@ const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[
|
||||||
node: graphene_std::raster_nodes::gradient_map::gradient_map::IDENTIFIER,
|
node: graphene_std::raster_nodes::gradient_map::gradient_map::IDENTIFIER,
|
||||||
aliases: &[
|
aliases: &[
|
||||||
"graphene_raster_nodes::gradient_map::GradientMapNode",
|
"graphene_raster_nodes::gradient_map::GradientMapNode",
|
||||||
|
"graphene_raster_nodes::adjustments::GradientMapNode",
|
||||||
"graphene_core::raster::adjustments::GradientMapNode",
|
"graphene_core::raster::adjustments::GradientMapNode",
|
||||||
"graphene_core::raster::GradientMapNode",
|
"graphene_core::raster::GradientMapNode",
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ use crate::messages::portfolio::document::utility_types::document_metadata::Laye
|
||||||
use crate::messages::prelude::{DocumentMessageHandler, InputPreprocessorMessageHandler};
|
use crate::messages::prelude::{DocumentMessageHandler, InputPreprocessorMessageHandler};
|
||||||
use crate::messages::tool::common_functionality::graph_modification_utils;
|
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||||
use crate::messages::tool::common_functionality::shape_editor::ShapeState;
|
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::polygon_shape::PolygonGizmoHandler;
|
||||||
use crate::messages::tool::common_functionality::shapes::shape_utility::ShapeGizmoHandler;
|
use crate::messages::tool::common_functionality::shapes::shape_utility::ShapeGizmoHandler;
|
||||||
use crate::messages::tool::common_functionality::shapes::star_shape::StarGizmoHandler;
|
use crate::messages::tool::common_functionality::shapes::star_shape::StarGizmoHandler;
|
||||||
|
|
@ -23,6 +24,7 @@ pub enum ShapeGizmoHandlers {
|
||||||
None,
|
None,
|
||||||
Star(StarGizmoHandler),
|
Star(StarGizmoHandler),
|
||||||
Polygon(PolygonGizmoHandler),
|
Polygon(PolygonGizmoHandler),
|
||||||
|
Arc(ArcGizmoHandler),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ShapeGizmoHandlers {
|
impl ShapeGizmoHandlers {
|
||||||
|
|
@ -32,6 +34,7 @@ impl ShapeGizmoHandlers {
|
||||||
match self {
|
match self {
|
||||||
Self::Star(_) => "star",
|
Self::Star(_) => "star",
|
||||||
Self::Polygon(_) => "polygon",
|
Self::Polygon(_) => "polygon",
|
||||||
|
Self::Arc(_) => "arc",
|
||||||
Self::None => "none",
|
Self::None => "none",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -41,6 +44,7 @@ impl ShapeGizmoHandlers {
|
||||||
match self {
|
match self {
|
||||||
Self::Star(h) => h.handle_state(layer, mouse_position, document, responses),
|
Self::Star(h) => h.handle_state(layer, mouse_position, document, responses),
|
||||||
Self::Polygon(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 => {}
|
Self::None => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -50,6 +54,7 @@ impl ShapeGizmoHandlers {
|
||||||
match self {
|
match self {
|
||||||
Self::Star(h) => h.is_any_gizmo_hovered(),
|
Self::Star(h) => h.is_any_gizmo_hovered(),
|
||||||
Self::Polygon(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,
|
Self::None => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -59,6 +64,7 @@ impl ShapeGizmoHandlers {
|
||||||
match self {
|
match self {
|
||||||
Self::Star(h) => h.handle_click(),
|
Self::Star(h) => h.handle_click(),
|
||||||
Self::Polygon(h) => h.handle_click(),
|
Self::Polygon(h) => h.handle_click(),
|
||||||
|
Self::Arc(h) => h.handle_click(),
|
||||||
Self::None => {}
|
Self::None => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -68,6 +74,7 @@ impl ShapeGizmoHandlers {
|
||||||
match self {
|
match self {
|
||||||
Self::Star(h) => h.handle_update(drag_start, document, input, responses),
|
Self::Star(h) => h.handle_update(drag_start, document, input, responses),
|
||||||
Self::Polygon(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 => {}
|
Self::None => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -77,6 +84,7 @@ impl ShapeGizmoHandlers {
|
||||||
match self {
|
match self {
|
||||||
Self::Star(h) => h.cleanup(),
|
Self::Star(h) => h.cleanup(),
|
||||||
Self::Polygon(h) => h.cleanup(),
|
Self::Polygon(h) => h.cleanup(),
|
||||||
|
Self::Arc(h) => h.cleanup(),
|
||||||
Self::None => {}
|
Self::None => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -94,6 +102,7 @@ impl ShapeGizmoHandlers {
|
||||||
match self {
|
match self {
|
||||||
Self::Star(h) => h.overlays(document, layer, input, shape_editor, mouse_position, overlay_context),
|
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::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 => {}
|
Self::None => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -110,6 +119,7 @@ impl ShapeGizmoHandlers {
|
||||||
match self {
|
match self {
|
||||||
Self::Star(h) => h.dragging_overlays(document, input, shape_editor, mouse_position, overlay_context),
|
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::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 => {}
|
Self::None => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -141,11 +151,14 @@ impl GizmoManager {
|
||||||
if graph_modification_utils::get_star_id(layer, &document.network_interface).is_some() {
|
if graph_modification_utils::get_star_id(layer, &document.network_interface).is_some() {
|
||||||
return Some(ShapeGizmoHandlers::Star(StarGizmoHandler::default()));
|
return Some(ShapeGizmoHandlers::Star(StarGizmoHandler::default()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Polygon
|
// Polygon
|
||||||
if graph_modification_utils::get_polygon_id(layer, &document.network_interface).is_some() {
|
if graph_modification_utils::get_polygon_id(layer, &document.network_interface).is_some() {
|
||||||
return Some(ShapeGizmoHandlers::Polygon(PolygonGizmoHandler::default()));
|
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
|
None
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,3 @@
|
||||||
pub mod number_of_points_dial;
|
pub mod number_of_points_dial;
|
||||||
pub mod point_radius_handle;
|
pub mod point_radius_handle;
|
||||||
|
pub mod sweep_angle_gizmo;
|
||||||
|
|
|
||||||
|
|
@ -262,7 +262,6 @@ impl PointRadiusHandle {
|
||||||
};
|
};
|
||||||
|
|
||||||
let viewport = document.metadata().transform_to_viewport(layer);
|
let viewport = document.metadata().transform_to_viewport(layer);
|
||||||
let center = viewport.transform_point2(DVec2::ZERO);
|
|
||||||
|
|
||||||
match snapping_index {
|
match snapping_index {
|
||||||
// Make a triangle with previous two points
|
// 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(before_outer_position, outer_position, Some(COLOR_OVERLAY_RED), Some(3.));
|
||||||
overlay_context.line(outer_position, point_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 l1 = (before_outer_position - outer_position).length() * 0.2;
|
||||||
let Some(l1_direction) = (before_outer_position - outer_position).try_normalize() else { return };
|
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(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 new_point = SQRT_2 * l1 * direction + outer_position;
|
||||||
|
|
||||||
let before_outer_position = l1 * l1_direction + outer_position;
|
let before_outer_position = l1 * l1_direction + outer_position;
|
||||||
let point_position = l1 * l2_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(
|
||||||
overlay_context.line(new_point, point_position, Some(COLOR_OVERLAY_RED), Some(3.));
|
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 => {
|
1 => {
|
||||||
let before_outer_position = star_vertex_position(viewport, (self.point as i32) - 1, sides, radius1, radius2);
|
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 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);
|
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(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.));
|
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 l1 = (before_outer_position - point_position).length() * 0.2;
|
||||||
let Some(l1_direction) = (before_outer_position - point_position).try_normalize() else { return };
|
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(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 new_point = SQRT_2 * l1 * direction + point_position;
|
||||||
|
|
||||||
let before_outer_position = l1 * l1_direction + point_position;
|
let before_outer_position = l1 * l1_direction + point_position;
|
||||||
let after_point_position = l1 * l2_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(
|
||||||
overlay_context.line(new_point, after_point_position, Some(COLOR_OVERLAY_RED), Some(3.));
|
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 => {
|
i => {
|
||||||
// Use `self.point` as absolute reference as it matches the index of vertices of the star starting from 0
|
// Use `self.point` as absolute reference as it matches the index of vertices of the star starting from 0
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -352,6 +352,10 @@ pub fn get_star_id(layer: LayerNodeIdentifier, network_interface: &NodeNetworkIn
|
||||||
NodeGraphLayer::new(layer, network_interface).upstream_node_id_from_name("Star")
|
NodeGraphLayer::new(layer, network_interface).upstream_node_id_from_name("Star")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_arc_id(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<NodeId> {
|
||||||
|
NodeGraphLayer::new(layer, network_interface).upstream_node_id_from_name("Arc")
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_text_id(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<NodeId> {
|
pub fn get_text_id(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<NodeId> {
|
||||||
NodeGraphLayer::new(layer, network_interface).upstream_node_id_from_name("Text")
|
NodeGraphLayer::new(layer, network_interface).upstream_node_id_from_name("Text")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,138 @@
|
||||||
|
use super::shape_utility::ShapeToolModifierKey;
|
||||||
|
use super::*;
|
||||||
|
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;
|
||||||
|
use crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type;
|
||||||
|
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||||
|
use crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, NodeTemplate};
|
||||||
|
use crate::messages::tool::common_functionality::gizmos::shape_gizmos::sweep_angle_gizmo::{SweepAngleGizmo, SweepAngleGizmoState};
|
||||||
|
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||||
|
use crate::messages::tool::common_functionality::shapes::shape_utility::{ShapeGizmoHandler, arc_outline};
|
||||||
|
use crate::messages::tool::tool_messages::tool_prelude::*;
|
||||||
|
use glam::DAffine2;
|
||||||
|
use graph_craft::document::NodeInput;
|
||||||
|
use graph_craft::document::value::TaggedValue;
|
||||||
|
use graphene_std::vector::misc::ArcType;
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default)]
|
||||||
|
pub struct ArcGizmoHandler {
|
||||||
|
sweep_angle_gizmo: SweepAngleGizmo,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ArcGizmoHandler {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self { ..Default::default() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 is_any_gizmo_hovered(&self) -> bool {
|
||||||
|
self.sweep_angle_gizmo.hovered()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_click(&mut self) {
|
||||||
|
if self.sweep_angle_gizmo.hovered() {
|
||||||
|
self.sweep_angle_gizmo.update_state(SweepAngleGizmoState::Dragging);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_update(&mut self, _drag_start: DVec2, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) {
|
||||||
|
if self.sweep_angle_gizmo.is_dragging_or_snapped() {
|
||||||
|
self.sweep_angle_gizmo.update_arc(document, input, responses);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dragging_overlays(
|
||||||
|
&self,
|
||||||
|
document: &DocumentMessageHandler,
|
||||||
|
input: &InputPreprocessorMessageHandler,
|
||||||
|
_shape_editor: &mut &mut crate::messages::tool::common_functionality::shape_editor::ShapeState,
|
||||||
|
mouse_position: DVec2,
|
||||||
|
overlay_context: &mut crate::messages::portfolio::document::overlays::utility_types::OverlayContext,
|
||||||
|
) {
|
||||||
|
if self.sweep_angle_gizmo.is_dragging_or_snapped() {
|
||||||
|
self.sweep_angle_gizmo.overlays(None, document, input, mouse_position, overlay_context);
|
||||||
|
arc_outline(self.sweep_angle_gizmo.layer, document, overlay_context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn overlays(
|
||||||
|
&self,
|
||||||
|
document: &DocumentMessageHandler,
|
||||||
|
selected_shape_layers: Option<LayerNodeIdentifier>,
|
||||||
|
input: &InputPreprocessorMessageHandler,
|
||||||
|
_shape_editor: &mut &mut crate::messages::tool::common_functionality::shape_editor::ShapeState,
|
||||||
|
mouse_position: DVec2,
|
||||||
|
overlay_context: &mut crate::messages::portfolio::document::overlays::utility_types::OverlayContext,
|
||||||
|
) {
|
||||||
|
self.sweep_angle_gizmo.overlays(selected_shape_layers, document, input, mouse_position, overlay_context);
|
||||||
|
|
||||||
|
arc_outline(selected_shape_layers.or(self.sweep_angle_gizmo.layer), document, overlay_context);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cleanup(&mut self) {
|
||||||
|
self.sweep_angle_gizmo.cleanup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct Arc;
|
||||||
|
|
||||||
|
impl Arc {
|
||||||
|
pub fn create_node(arc_type: ArcType) -> NodeTemplate {
|
||||||
|
let node_type = resolve_document_node_type("Arc").expect("Ellipse node does not exist");
|
||||||
|
node_type.node_template_input_override([
|
||||||
|
None,
|
||||||
|
Some(NodeInput::value(TaggedValue::F64(0.5), false)),
|
||||||
|
Some(NodeInput::value(TaggedValue::F64(0.), false)),
|
||||||
|
Some(NodeInput::value(TaggedValue::F64(270.), false)),
|
||||||
|
Some(NodeInput::value(TaggedValue::ArcType(arc_type), false)),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_shape(
|
||||||
|
document: &DocumentMessageHandler,
|
||||||
|
ipp: &InputPreprocessorMessageHandler,
|
||||||
|
layer: LayerNodeIdentifier,
|
||||||
|
shape_tool_data: &mut ShapeToolData,
|
||||||
|
modifier: ShapeToolModifierKey,
|
||||||
|
responses: &mut VecDeque<Message>,
|
||||||
|
) {
|
||||||
|
let (center, lock_ratio) = (modifier[0], modifier[1]);
|
||||||
|
if let Some([start, end]) = shape_tool_data.data.calculate_points(document, ipp, center, lock_ratio) {
|
||||||
|
let Some(node_id) = graph_modification_utils::get_arc_id(layer, &document.network_interface) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let dimensions = (start - end).abs();
|
||||||
|
let mut scale = DVec2::ONE;
|
||||||
|
let radius: f64;
|
||||||
|
|
||||||
|
// We keep the smaller dimension's scale at 1 and scale the other dimension accordingly
|
||||||
|
if dimensions.x > dimensions.y {
|
||||||
|
scale.x = dimensions.x / dimensions.y;
|
||||||
|
scale.y = 1.;
|
||||||
|
radius = dimensions.y / 2.;
|
||||||
|
} else {
|
||||||
|
scale.y = dimensions.y / dimensions.x;
|
||||||
|
scale.x = 1.;
|
||||||
|
radius = dimensions.x / 2.;
|
||||||
|
}
|
||||||
|
|
||||||
|
responses.add(NodeGraphMessage::SetInput {
|
||||||
|
input_connector: InputConnector::node(node_id, 1),
|
||||||
|
input: NodeInput::value(TaggedValue::F64(radius), false),
|
||||||
|
});
|
||||||
|
|
||||||
|
responses.add(GraphOperationMessage::TransformSet {
|
||||||
|
layer,
|
||||||
|
transform: DAffine2::from_scale_angle_translation(scale, 0., start.midpoint(end)),
|
||||||
|
transform_in: TransformIn::Viewport,
|
||||||
|
skip_rerender: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
pub mod arc_shape;
|
||||||
pub mod ellipse_shape;
|
pub mod ellipse_shape;
|
||||||
pub mod line_shape;
|
pub mod line_shape;
|
||||||
pub mod polygon_shape;
|
pub mod polygon_shape;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use super::ShapeToolData;
|
use super::ShapeToolData;
|
||||||
|
use crate::consts::{ARC_SWEEP_GIZMO_RADIUS, ARC_SWEEP_GIZMO_TEXT_HEIGHT};
|
||||||
use crate::messages::message::Message;
|
use crate::messages::message::Message;
|
||||||
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
|
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
|
||||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||||
|
|
@ -14,7 +15,7 @@ use glam::{DAffine2, DMat2, DVec2};
|
||||||
use graph_craft::document::NodeInput;
|
use graph_craft::document::NodeInput;
|
||||||
use graph_craft::document::value::TaggedValue;
|
use graph_craft::document::value::TaggedValue;
|
||||||
use graphene_std::vector::click_target::ClickTargetType;
|
use graphene_std::vector::click_target::ClickTargetType;
|
||||||
use graphene_std::vector::misc::dvec2_to_point;
|
use graphene_std::vector::misc::{ArcType, dvec2_to_point};
|
||||||
use kurbo::{BezPath, PathEl, Shape};
|
use kurbo::{BezPath, PathEl, Shape};
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
use std::f64::consts::{PI, TAU};
|
use std::f64::consts::{PI, TAU};
|
||||||
|
|
@ -23,10 +24,11 @@ use std::f64::consts::{PI, TAU};
|
||||||
pub enum ShapeType {
|
pub enum ShapeType {
|
||||||
#[default]
|
#[default]
|
||||||
Polygon = 0,
|
Polygon = 0,
|
||||||
Star = 1,
|
Star,
|
||||||
Rectangle = 2,
|
Arc,
|
||||||
Ellipse = 3,
|
Rectangle,
|
||||||
Line = 4,
|
Ellipse,
|
||||||
|
Line,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ShapeType {
|
impl ShapeType {
|
||||||
|
|
@ -34,6 +36,7 @@ impl ShapeType {
|
||||||
(match self {
|
(match self {
|
||||||
Self::Polygon => "Polygon",
|
Self::Polygon => "Polygon",
|
||||||
Self::Star => "Star",
|
Self::Star => "Star",
|
||||||
|
Self::Arc => "Arc",
|
||||||
Self::Rectangle => "Rectangle",
|
Self::Rectangle => "Rectangle",
|
||||||
Self::Ellipse => "Ellipse",
|
Self::Ellipse => "Ellipse",
|
||||||
Self::Line => "Line",
|
Self::Line => "Line",
|
||||||
|
|
@ -234,7 +237,46 @@ pub fn extract_polygon_parameters(layer: Option<LayerNodeIdentifier>, document:
|
||||||
Some((n, radius))
|
Some((n, radius))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Calculate the viewport position of as a star vertex given its index
|
/// Extract the node input values of an arc.
|
||||||
|
/// Returns an option of (radius, start angle, sweep angle, arc type).
|
||||||
|
pub fn extract_arc_parameters(layer: Option<LayerNodeIdentifier>, document: &DocumentMessageHandler) -> Option<(f64, f64, f64, ArcType)> {
|
||||||
|
let node_inputs = NodeGraphLayer::new(layer?, &document.network_interface).find_node_inputs("Arc")?;
|
||||||
|
|
||||||
|
let (Some(&TaggedValue::F64(radius)), Some(&TaggedValue::F64(start_angle)), Some(&TaggedValue::F64(sweep_angle)), Some(&TaggedValue::ArcType(arc_type))) = (
|
||||||
|
node_inputs.get(1)?.as_value(),
|
||||||
|
node_inputs.get(2)?.as_value(),
|
||||||
|
node_inputs.get(3)?.as_value(),
|
||||||
|
node_inputs.get(4)?.as_value(),
|
||||||
|
) else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
|
Some((radius, start_angle, sweep_angle, arc_type))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calculate the viewport positions of arc endpoints
|
||||||
|
pub fn arc_end_points(layer: Option<LayerNodeIdentifier>, document: &DocumentMessageHandler) -> Option<(DVec2, DVec2)> {
|
||||||
|
let (radius, start_angle, sweep_angle, _) = extract_arc_parameters(Some(layer?), document)?;
|
||||||
|
|
||||||
|
let viewport = document.metadata().transform_to_viewport(layer?);
|
||||||
|
|
||||||
|
arc_end_points_ignore_layer(radius, start_angle, sweep_angle, Some(viewport))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn arc_end_points_ignore_layer(radius: f64, start_angle: f64, sweep_angle: f64, viewport: Option<DAffine2>) -> Option<(DVec2, DVec2)> {
|
||||||
|
let end_angle = start_angle.to_radians() + sweep_angle.to_radians();
|
||||||
|
|
||||||
|
let start_point = radius * DVec2::from_angle(start_angle.to_radians());
|
||||||
|
let end_point = radius * DVec2::from_angle(end_angle);
|
||||||
|
|
||||||
|
if let Some(transform) = viewport {
|
||||||
|
return Some((transform.transform_point2(start_point), transform.transform_point2(end_point)));
|
||||||
|
}
|
||||||
|
|
||||||
|
Some((start_point, end_point))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calculate the viewport position of a star vertex given its index
|
||||||
pub fn star_vertex_position(viewport: DAffine2, vertex_index: i32, n: u32, radius1: f64, radius2: f64) -> DVec2 {
|
pub fn star_vertex_position(viewport: DAffine2, vertex_index: i32, n: u32, radius1: f64, radius2: f64) -> DVec2 {
|
||||||
let angle = ((vertex_index as f64) * PI) / (n as f64);
|
let angle = ((vertex_index as f64) * PI) / (n as f64);
|
||||||
let radius = if vertex_index % 2 == 0 { radius1 } else { radius2 };
|
let radius = if vertex_index % 2 == 0 { radius1 } else { radius2 };
|
||||||
|
|
@ -290,6 +332,29 @@ pub fn polygon_outline(layer: Option<LayerNodeIdentifier>, document: &DocumentMe
|
||||||
overlay_context.outline(subpath.iter(), viewport, None);
|
overlay_context.outline(subpath.iter(), viewport, None);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Outlines the geometric shape made by an Arc node
|
||||||
|
pub fn arc_outline(layer: Option<LayerNodeIdentifier>, document: &DocumentMessageHandler, overlay_context: &mut OverlayContext) {
|
||||||
|
let Some(layer) = layer else { return };
|
||||||
|
|
||||||
|
let Some((radius, start_angle, sweep_angle, arc_type)) = extract_arc_parameters(Some(layer), document) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let subpath: Vec<ClickTargetType> = vec![ClickTargetType::Subpath(Subpath::new_arc(
|
||||||
|
radius,
|
||||||
|
start_angle / 360. * std::f64::consts::TAU,
|
||||||
|
sweep_angle / 360. * std::f64::consts::TAU,
|
||||||
|
match arc_type {
|
||||||
|
ArcType::Open => bezier_rs::ArcType::Open,
|
||||||
|
ArcType::Closed => bezier_rs::ArcType::Closed,
|
||||||
|
ArcType::PieSlice => bezier_rs::ArcType::PieSlice,
|
||||||
|
},
|
||||||
|
))];
|
||||||
|
let viewport = document.metadata().transform_to_viewport(layer);
|
||||||
|
|
||||||
|
overlay_context.outline(subpath.iter(), viewport, None);
|
||||||
|
}
|
||||||
|
|
||||||
/// Check if the the cursor is inside the geometric star shape made by the Star node without any upstream node modifications
|
/// Check if the the cursor is inside the geometric star shape made by the Star node without any upstream node modifications
|
||||||
pub fn inside_star(viewport: DAffine2, n: u32, radius1: f64, radius2: f64, mouse_position: DVec2) -> bool {
|
pub fn inside_star(viewport: DAffine2, n: u32, radius1: f64, radius2: f64, mouse_position: DVec2) -> bool {
|
||||||
let mut paths = Vec::new();
|
let mut paths = Vec::new();
|
||||||
|
|
@ -363,3 +428,32 @@ pub fn draw_snapping_ticks(snap_radii: &[f64], direction: DVec2, viewport: DAffi
|
||||||
overlay_context.line(tick_position, tick_position - tick_direction * 5., None, Some(2.));
|
overlay_context.line(tick_position, tick_position - tick_direction * 5., None, Some(2.));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Wraps an angle (in radians) into the range [0, 2π).
|
||||||
|
pub fn wrap_to_tau(angle: f64) -> f64 {
|
||||||
|
(angle % TAU + TAU) % TAU
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn format_rounded(value: f64, precision: usize) -> String {
|
||||||
|
format!("{value:.precision$}").trim_end_matches('0').trim_end_matches('.').to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gives the approximated angle to display in degrees, given an angle in degrees.
|
||||||
|
pub fn calculate_display_angle(angle: f64) -> f64 {
|
||||||
|
if angle.is_sign_positive() {
|
||||||
|
angle - (angle / 360.).floor() * 360.
|
||||||
|
} else if angle.is_sign_negative() {
|
||||||
|
angle - ((angle / 360.).floor() + 1.) * 360.
|
||||||
|
} else {
|
||||||
|
angle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn calculate_arc_text_transform(angle: f64, offset_angle: f64, center: DVec2, width: f64) -> DAffine2 {
|
||||||
|
let text_angle_on_unit_circle = DVec2::from_angle((angle.to_radians() % TAU) / 2. + offset_angle);
|
||||||
|
let text_texture_position = DVec2::new(
|
||||||
|
(ARC_SWEEP_GIZMO_RADIUS + 4. + width) * text_angle_on_unit_circle.x,
|
||||||
|
(ARC_SWEEP_GIZMO_RADIUS + ARC_SWEEP_GIZMO_TEXT_HEIGHT) * text_angle_on_unit_circle.y,
|
||||||
|
);
|
||||||
|
DAffine2::from_translation(text_texture_position + center)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ use crate::messages::tool::common_functionality::gizmos::gizmo_manager::GizmoMan
|
||||||
use crate::messages::tool::common_functionality::graph_modification_utils;
|
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||||
use crate::messages::tool::common_functionality::graph_modification_utils::NodeGraphLayer;
|
use crate::messages::tool::common_functionality::graph_modification_utils::NodeGraphLayer;
|
||||||
use crate::messages::tool::common_functionality::resize::Resize;
|
use crate::messages::tool::common_functionality::resize::Resize;
|
||||||
|
use crate::messages::tool::common_functionality::shapes::arc_shape::Arc;
|
||||||
use crate::messages::tool::common_functionality::shapes::line_shape::{LineToolData, clicked_on_line_endpoints};
|
use crate::messages::tool::common_functionality::shapes::line_shape::{LineToolData, clicked_on_line_endpoints};
|
||||||
use crate::messages::tool::common_functionality::shapes::polygon_shape::Polygon;
|
use crate::messages::tool::common_functionality::shapes::polygon_shape::Polygon;
|
||||||
use crate::messages::tool::common_functionality::shapes::shape_utility::{ShapeToolModifierKey, ShapeType, anchor_overlays, transform_cage_overlays};
|
use crate::messages::tool::common_functionality::shapes::shape_utility::{ShapeToolModifierKey, ShapeType, anchor_overlays, transform_cage_overlays};
|
||||||
|
|
@ -109,10 +110,28 @@ fn create_shape_option_widget(shape_type: ShapeType) -> WidgetHolder {
|
||||||
MenuListEntry::new("Star")
|
MenuListEntry::new("Star")
|
||||||
.label("Star")
|
.label("Star")
|
||||||
.on_commit(move |_| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::ShapeType(ShapeType::Star)).into()),
|
.on_commit(move |_| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::ShapeType(ShapeType::Star)).into()),
|
||||||
|
MenuListEntry::new("Arc")
|
||||||
|
.label("Arc")
|
||||||
|
.on_commit(move |_| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::ShapeType(ShapeType::Arc)).into()),
|
||||||
]];
|
]];
|
||||||
DropdownInput::new(entries).selected_index(Some(shape_type as u32)).widget_holder()
|
DropdownInput::new(entries).selected_index(Some(shape_type as u32)).widget_holder()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn create_arc_type_widget(arc_type: ArcType) -> WidgetHolder {
|
||||||
|
let entries = vec![
|
||||||
|
RadioEntryData::new("Open")
|
||||||
|
.label("Open")
|
||||||
|
.on_update(move |_| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::ArcType(ArcType::Open)).into()),
|
||||||
|
RadioEntryData::new("Closed")
|
||||||
|
.label("Closed")
|
||||||
|
.on_update(move |_| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::ArcType(ArcType::Closed)).into()),
|
||||||
|
RadioEntryData::new("Pie")
|
||||||
|
.label("Pie")
|
||||||
|
.on_update(move |_| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::ArcType(ArcType::PieSlice)).into()),
|
||||||
|
];
|
||||||
|
RadioInput::new(entries).selected_index(Some(arc_type as u32)).widget_holder()
|
||||||
|
}
|
||||||
|
|
||||||
fn create_weight_widget(line_weight: f64) -> WidgetHolder {
|
fn create_weight_widget(line_weight: f64) -> WidgetHolder {
|
||||||
NumberInput::new(Some(line_weight))
|
NumberInput::new(Some(line_weight))
|
||||||
.unit(" px")
|
.unit(" px")
|
||||||
|
|
@ -135,6 +154,11 @@ impl LayoutHolder for ShapeTool {
|
||||||
widgets.push(create_sides_widget(self.options.vertices));
|
widgets.push(create_sides_widget(self.options.vertices));
|
||||||
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
|
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.options.shape_type == ShapeType::Arc {
|
||||||
|
widgets.push(create_arc_type_widget(self.options.arc_type));
|
||||||
|
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.options.shape_type != ShapeType::Line {
|
if self.options.shape_type != ShapeType::Line {
|
||||||
|
|
@ -578,7 +602,7 @@ impl Fsm for ShapeToolFsmState {
|
||||||
};
|
};
|
||||||
|
|
||||||
match tool_data.current_shape {
|
match tool_data.current_shape {
|
||||||
ShapeType::Polygon | ShapeType::Star | ShapeType::Ellipse | ShapeType::Rectangle => tool_data.data.start(document, input),
|
ShapeType::Polygon | ShapeType::Star | ShapeType::Ellipse | ShapeType::Arc | ShapeType::Rectangle => tool_data.data.start(document, input),
|
||||||
ShapeType::Line => {
|
ShapeType::Line => {
|
||||||
let point = SnapCandidatePoint::handle(document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position));
|
let point = SnapCandidatePoint::handle(document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position));
|
||||||
let snapped = tool_data.data.snap_manager.free_snap(&SnapData::new(document, input), &point, SnapTypeConfiguration::default());
|
let snapped = tool_data.data.snap_manager.free_snap(&SnapData::new(document, input), &point, SnapTypeConfiguration::default());
|
||||||
|
|
@ -591,6 +615,7 @@ impl Fsm for ShapeToolFsmState {
|
||||||
let node = match tool_data.current_shape {
|
let node = match tool_data.current_shape {
|
||||||
ShapeType::Polygon => Polygon::create_node(tool_options.vertices),
|
ShapeType::Polygon => Polygon::create_node(tool_options.vertices),
|
||||||
ShapeType::Star => Star::create_node(tool_options.vertices),
|
ShapeType::Star => Star::create_node(tool_options.vertices),
|
||||||
|
ShapeType::Arc => Arc::create_node(tool_options.arc_type),
|
||||||
ShapeType::Rectangle => Rectangle::create_node(),
|
ShapeType::Rectangle => Rectangle::create_node(),
|
||||||
ShapeType::Ellipse => Ellipse::create_node(),
|
ShapeType::Ellipse => Ellipse::create_node(),
|
||||||
ShapeType::Line => Line::create_node(document, tool_data.data.drag_start),
|
ShapeType::Line => Line::create_node(document, tool_data.data.drag_start),
|
||||||
|
|
@ -602,7 +627,7 @@ impl Fsm for ShapeToolFsmState {
|
||||||
responses.add(Message::StartBuffer);
|
responses.add(Message::StartBuffer);
|
||||||
|
|
||||||
match tool_data.current_shape {
|
match tool_data.current_shape {
|
||||||
ShapeType::Ellipse | ShapeType::Rectangle | ShapeType::Polygon | ShapeType::Star => {
|
ShapeType::Ellipse | ShapeType::Rectangle | ShapeType::Arc | ShapeType::Polygon | ShapeType::Star => {
|
||||||
responses.add(GraphOperationMessage::TransformSet {
|
responses.add(GraphOperationMessage::TransformSet {
|
||||||
layer,
|
layer,
|
||||||
transform: DAffine2::from_scale_angle_translation(DVec2::ONE, 0., input.mouse.position),
|
transform: DAffine2::from_scale_angle_translation(DVec2::ONE, 0., input.mouse.position),
|
||||||
|
|
@ -635,6 +660,7 @@ impl Fsm for ShapeToolFsmState {
|
||||||
ShapeType::Line => Line::update_shape(document, input, layer, tool_data, modifier, responses),
|
ShapeType::Line => Line::update_shape(document, input, layer, tool_data, modifier, responses),
|
||||||
ShapeType::Polygon => Polygon::update_shape(document, input, layer, tool_data, modifier, responses),
|
ShapeType::Polygon => Polygon::update_shape(document, input, layer, tool_data, modifier, responses),
|
||||||
ShapeType::Star => Star::update_shape(document, input, layer, tool_data, modifier, responses),
|
ShapeType::Star => Star::update_shape(document, input, layer, tool_data, modifier, responses),
|
||||||
|
ShapeType::Arc => Arc::update_shape(document, input, layer, tool_data, modifier, responses),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auto-panning
|
// Auto-panning
|
||||||
|
|
@ -829,7 +855,7 @@ impl Fsm for ShapeToolFsmState {
|
||||||
let hint_data = match self {
|
let hint_data = match self {
|
||||||
ShapeToolFsmState::Ready(shape) => {
|
ShapeToolFsmState::Ready(shape) => {
|
||||||
let hint_groups = match shape {
|
let hint_groups = match shape {
|
||||||
ShapeType::Polygon | ShapeType::Star => vec![
|
ShapeType::Polygon | ShapeType::Star | ShapeType::Arc => vec![
|
||||||
HintGroup(vec![
|
HintGroup(vec![
|
||||||
HintInfo::mouse(MouseMotion::LmbDrag, "Draw Polygon"),
|
HintInfo::mouse(MouseMotion::LmbDrag, "Draw Polygon"),
|
||||||
HintInfo::keys([Key::Shift], "Constrain Regular").prepend_plus(),
|
HintInfo::keys([Key::Shift], "Constrain Regular").prepend_plus(),
|
||||||
|
|
@ -859,7 +885,7 @@ impl Fsm for ShapeToolFsmState {
|
||||||
ShapeToolFsmState::Drawing(shape) => {
|
ShapeToolFsmState::Drawing(shape) => {
|
||||||
let mut common_hint_group = vec![HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()])];
|
let mut common_hint_group = vec![HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()])];
|
||||||
let tool_hint_group = match shape {
|
let tool_hint_group = match shape {
|
||||||
ShapeType::Polygon | ShapeType::Star => HintGroup(vec![HintInfo::keys([Key::Shift], "Constrain Regular"), HintInfo::keys([Key::Alt], "From Center")]),
|
ShapeType::Polygon | ShapeType::Star | ShapeType::Arc => HintGroup(vec![HintInfo::keys([Key::Shift], "Constrain Regular"), HintInfo::keys([Key::Alt], "From Center")]),
|
||||||
ShapeType::Rectangle => HintGroup(vec![HintInfo::keys([Key::Shift], "Constrain Square"), HintInfo::keys([Key::Alt], "From Center")]),
|
ShapeType::Rectangle => HintGroup(vec![HintInfo::keys([Key::Shift], "Constrain Square"), HintInfo::keys([Key::Alt], "From Center")]),
|
||||||
ShapeType::Ellipse => HintGroup(vec![HintInfo::keys([Key::Shift], "Constrain Circular"), HintInfo::keys([Key::Alt], "From Center")]),
|
ShapeType::Ellipse => HintGroup(vec![HintInfo::keys([Key::Shift], "Constrain Circular"), HintInfo::keys([Key::Alt], "From Center")]),
|
||||||
ShapeType::Line => HintGroup(vec![
|
ShapeType::Line => HintGroup(vec![
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,7 @@ pub enum GridType {
|
||||||
#[widget(Radio)]
|
#[widget(Radio)]
|
||||||
pub enum ArcType {
|
pub enum ArcType {
|
||||||
#[default]
|
#[default]
|
||||||
Open,
|
Open = 0,
|
||||||
Closed,
|
Closed,
|
||||||
PieSlice,
|
PieSlice,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue