mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-07-08 00:05:00 +00:00
Refactor shape gizmo interactivity to support future shape tools (#2748)
* impl GizmoHandlerTrait,Gizmo-manager and add comments * Code review --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
parent
1875779b0a
commit
d8d2a51926
19 changed files with 1000 additions and 619 deletions
|
@ -122,8 +122,8 @@ pub const DEFAULT_BRUSH_SIZE: f64 = 20.;
|
||||||
// GIZMOS
|
// GIZMOS
|
||||||
pub const POINT_RADIUS_HANDLE_SNAP_THRESHOLD: f64 = 8.;
|
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_HANDLE_SPOKE_EXTENSION: f64 = 1.2;
|
pub const NUMBER_OF_POINTS_DIAL_SPOKE_EXTENSION: f64 = 1.2;
|
||||||
pub const NUMBER_OF_POINTS_HANDLE_SPOKE_LENGTH: f64 = 10.;
|
pub const NUMBER_OF_POINTS_DIAL_SPOKE_LENGTH: f64 = 10.;
|
||||||
pub const GIZMO_HIDE_THRESHOLD: f64 = 20.;
|
pub const GIZMO_HIDE_THRESHOLD: f64 = 20.;
|
||||||
|
|
||||||
// SCROLLBARS
|
// SCROLLBARS
|
||||||
|
|
|
@ -1624,7 +1624,7 @@ impl DocumentMessageHandler {
|
||||||
subpath.is_inside_subpath(&viewport_polygon, None, None)
|
subpath.is_inside_subpath(&viewport_polygon, None, None)
|
||||||
}
|
}
|
||||||
ClickTargetType::FreePoint(point) => {
|
ClickTargetType::FreePoint(point) => {
|
||||||
let mut point = point.clone();
|
let mut point = *point;
|
||||||
point.apply_transform(layer_transform);
|
point.apply_transform(layer_transform);
|
||||||
viewport_polygon.contains_point(point.position)
|
viewport_polygon.contains_point(point.position)
|
||||||
}
|
}
|
||||||
|
@ -3346,9 +3346,9 @@ mod document_message_handler_tests {
|
||||||
let rect_bbox_after = document.metadata().bounding_box_viewport(rect_layer).unwrap();
|
let rect_bbox_after = document.metadata().bounding_box_viewport(rect_layer).unwrap();
|
||||||
|
|
||||||
// Verifing the rectangle maintains approximately the same position in viewport space
|
// Verifing the rectangle maintains approximately the same position in viewport space
|
||||||
let before_center = (rect_bbox_before[0] + rect_bbox_before[1]) / 2.; // TODO: Should be: DVec2(0.0, -25.0), regression (#2688) causes it to be: DVec2(100.0, 25.0)
|
let before_center = (rect_bbox_before[0] + rect_bbox_before[1]) / 2.; // TODO: Should be: DVec2(0., -25.), regression (#2688) causes it to be: DVec2(100., 25.)
|
||||||
let after_center = (rect_bbox_after[0] + rect_bbox_after[1]) / 2.; // TODO: Should be: DVec2(0.0, -25.0), regression (#2688) causes it to be: DVec2(200.0, 75.0)
|
let after_center = (rect_bbox_after[0] + rect_bbox_after[1]) / 2.; // TODO: Should be: DVec2(0., -25.), regression (#2688) causes it to be: DVec2(200., 75.)
|
||||||
let distance = before_center.distance(after_center); // TODO: Should be: 0.0, regression (#2688) causes it to be: 111.80339887498948
|
let distance = before_center.distance(after_center); // TODO: Should be: 0., regression (#2688) causes it to be: 111.80339887498948
|
||||||
|
|
||||||
assert!(
|
assert!(
|
||||||
distance < 1.,
|
distance < 1.,
|
||||||
|
|
|
@ -0,0 +1,246 @@
|
||||||
|
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::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::polygon_shape::PolygonGizmoHandler;
|
||||||
|
use crate::messages::tool::common_functionality::shapes::shape_utility::ShapeGizmoHandler;
|
||||||
|
use crate::messages::tool::common_functionality::shapes::star_shape::StarGizmoHandler;
|
||||||
|
use glam::DVec2;
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
|
/// A unified enum wrapper around all available shape-specific gizmo handlers.
|
||||||
|
///
|
||||||
|
/// This abstraction allows `GizmoManager` to interact with different shape gizmos (like Star or Polygon)
|
||||||
|
/// using a common interface without needing to know the specific shape type at compile time.
|
||||||
|
///
|
||||||
|
/// Each variant stores a concrete handler (e.g., `StarGizmoHandler`, `PolygonGizmoHandler`) that implements
|
||||||
|
/// the shape-specific logic for rendering overlays, responding to input, and modifying shape parameters.
|
||||||
|
#[derive(Clone, Debug, Default)]
|
||||||
|
pub enum ShapeGizmoHandlers {
|
||||||
|
#[default]
|
||||||
|
None,
|
||||||
|
Star(StarGizmoHandler),
|
||||||
|
Polygon(PolygonGizmoHandler),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ShapeGizmoHandlers {
|
||||||
|
/// Returns the kind of shape the handler is managing, such as `"star"` or `"polygon"`.
|
||||||
|
/// Used for grouping logic and distinguishing between handler types at runtime.
|
||||||
|
pub fn kind(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Self::Star(_) => "star",
|
||||||
|
Self::Polygon(_) => "polygon",
|
||||||
|
Self::None => "none",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Dispatches interaction state updates to the corresponding shape-specific handler.
|
||||||
|
pub fn handle_state(&mut self, layer: LayerNodeIdentifier, mouse_position: DVec2, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
|
||||||
|
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::None => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if any interactive part of the gizmo is currently hovered.
|
||||||
|
pub fn is_any_gizmo_hovered(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Self::Star(h) => h.is_any_gizmo_hovered(),
|
||||||
|
Self::Polygon(h) => h.is_any_gizmo_hovered(),
|
||||||
|
Self::None => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Passes the click interaction to the appropriate gizmo handler if one is hovered.
|
||||||
|
pub fn handle_click(&mut self) {
|
||||||
|
match self {
|
||||||
|
Self::Star(h) => h.handle_click(),
|
||||||
|
Self::Polygon(h) => h.handle_click(),
|
||||||
|
Self::None => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Updates the gizmo state while the user is dragging a handle (e.g., adjusting radius).
|
||||||
|
pub fn handle_update(&mut self, drag_start: DVec2, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) {
|
||||||
|
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::None => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Cleans up any state used by the gizmo handler.
|
||||||
|
pub fn cleanup(&mut self) {
|
||||||
|
match self {
|
||||||
|
Self::Star(h) => h.cleanup(),
|
||||||
|
Self::Polygon(h) => h.cleanup(),
|
||||||
|
Self::None => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Draws overlays like control points or outlines for the shape handled by this gizmo.
|
||||||
|
pub fn overlays(
|
||||||
|
&self,
|
||||||
|
document: &DocumentMessageHandler,
|
||||||
|
layer: Option<LayerNodeIdentifier>,
|
||||||
|
input: &InputPreprocessorMessageHandler,
|
||||||
|
shape_editor: &mut &mut ShapeState,
|
||||||
|
mouse_position: DVec2,
|
||||||
|
overlay_context: &mut OverlayContext,
|
||||||
|
) {
|
||||||
|
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::None => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Draws live-updating overlays during drag interactions for the shape handled by this gizmo.
|
||||||
|
pub fn dragging_overlays(
|
||||||
|
&self,
|
||||||
|
document: &DocumentMessageHandler,
|
||||||
|
input: &InputPreprocessorMessageHandler,
|
||||||
|
shape_editor: &mut &mut ShapeState,
|
||||||
|
mouse_position: DVec2,
|
||||||
|
overlay_context: &mut OverlayContext,
|
||||||
|
) {
|
||||||
|
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::None => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Central manager that coordinates shape gizmo handlers for interactive editing on the canvas.
|
||||||
|
///
|
||||||
|
/// The `GizmoManager` is responsible for detecting which shapes are selected, activating the appropriate
|
||||||
|
/// shape-specific gizmo, and routing user interactions (hover, click, drag) to the correct handler.
|
||||||
|
/// It allows editing multiple shapes of the same type or focusing on a single active shape when a gizmo is hovered.
|
||||||
|
///
|
||||||
|
/// ## Responsibilities:
|
||||||
|
/// - Detect which selected layers support shape gizmos (e.g., stars, polygons)
|
||||||
|
/// - Activate the correct handler and manage state between frames
|
||||||
|
/// - Route click, hover, and drag events to the proper shape gizmo
|
||||||
|
/// - Render overlays and dragging visuals
|
||||||
|
#[derive(Clone, Debug, Default)]
|
||||||
|
pub struct GizmoManager {
|
||||||
|
active_shape_handler: Option<ShapeGizmoHandlers>,
|
||||||
|
layers_handlers: Vec<(ShapeGizmoHandlers, Vec<LayerNodeIdentifier>)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GizmoManager {
|
||||||
|
/// Detects and returns a shape gizmo handler based on the layer type (e.g., star, polygon).
|
||||||
|
///
|
||||||
|
/// Returns `None` if the given layer does not represent a shape with a registered gizmo.
|
||||||
|
pub fn detect_shape_handler(layer: LayerNodeIdentifier, document: &DocumentMessageHandler) -> Option<ShapeGizmoHandlers> {
|
||||||
|
// Star
|
||||||
|
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()));
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if a gizmo is currently active (hovered or being interacted with).
|
||||||
|
pub fn hovering_over_gizmo(&self) -> bool {
|
||||||
|
self.active_shape_handler.is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Called every frame to check selected layers and update the active shape gizmo, if hovered.
|
||||||
|
///
|
||||||
|
/// Also groups all shape layers with the same kind of gizmo to support overlays for multi-shape editing.
|
||||||
|
pub fn handle_actions(&mut self, mouse_position: DVec2, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
|
||||||
|
let mut handlers_layer: Vec<(ShapeGizmoHandlers, Vec<LayerNodeIdentifier>)> = Vec::new();
|
||||||
|
|
||||||
|
for layer in document.network_interface.selected_nodes().selected_visible_and_unlocked_layers(&document.network_interface) {
|
||||||
|
if let Some(mut handler) = Self::detect_shape_handler(layer, document) {
|
||||||
|
handler.handle_state(layer, mouse_position, document, responses);
|
||||||
|
let is_hovered = handler.is_any_gizmo_hovered();
|
||||||
|
|
||||||
|
if is_hovered {
|
||||||
|
self.layers_handlers.clear();
|
||||||
|
self.active_shape_handler = Some(handler);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to group this handler with others of the same type
|
||||||
|
if let Some((_, layers)) = handlers_layer.iter_mut().find(|(existing_handler, _)| existing_handler.kind() == handler.kind()) {
|
||||||
|
layers.push(layer);
|
||||||
|
} else {
|
||||||
|
handlers_layer.push((handler, vec![layer]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.layers_handlers = handlers_layer;
|
||||||
|
self.active_shape_handler = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handles click interactions if a gizmo is active. Returns `true` if a gizmo handled the click.
|
||||||
|
pub fn handle_click(&mut self) -> bool {
|
||||||
|
if let Some(handle) = &mut self.active_shape_handler {
|
||||||
|
handle.handle_click();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_cleanup(&mut self) {
|
||||||
|
if let Some(handle) = &mut self.active_shape_handler {
|
||||||
|
handle.cleanup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Passes drag update data to the active gizmo to update shape parameters live.
|
||||||
|
pub fn handle_update(&mut self, drag_start: DVec2, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) {
|
||||||
|
if let Some(handle) = &mut self.active_shape_handler {
|
||||||
|
handle.handle_update(drag_start, document, input, responses);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Draws overlays for the currently active shape gizmo during a drag interaction.
|
||||||
|
pub fn dragging_overlays(
|
||||||
|
&self,
|
||||||
|
document: &DocumentMessageHandler,
|
||||||
|
input: &InputPreprocessorMessageHandler,
|
||||||
|
shape_editor: &mut &mut ShapeState,
|
||||||
|
mouse_position: DVec2,
|
||||||
|
overlay_context: &mut OverlayContext,
|
||||||
|
) {
|
||||||
|
if let Some(handle) = &self.active_shape_handler {
|
||||||
|
handle.dragging_overlays(document, input, shape_editor, mouse_position, overlay_context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Draws overlays for either the active gizmo (if hovered) or all grouped selected gizmos.
|
||||||
|
///
|
||||||
|
/// If no single gizmo is active, it renders overlays for all grouped layers with associated handlers.
|
||||||
|
pub fn overlays(
|
||||||
|
&self,
|
||||||
|
document: &DocumentMessageHandler,
|
||||||
|
input: &InputPreprocessorMessageHandler,
|
||||||
|
shape_editor: &mut &mut ShapeState,
|
||||||
|
mouse_position: DVec2,
|
||||||
|
overlay_context: &mut OverlayContext,
|
||||||
|
) {
|
||||||
|
if let Some(handler) = &self.active_shape_handler {
|
||||||
|
handler.overlays(document, None, input, shape_editor, mouse_position, overlay_context);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (handler, selected_layers) in &self.layers_handlers {
|
||||||
|
for layer in selected_layers {
|
||||||
|
handler.overlays(document, Some(*layer), input, shape_editor, mouse_position, overlay_context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,2 @@
|
||||||
|
pub mod gizmo_manager;
|
||||||
|
pub mod shape_gizmos;
|
|
@ -0,0 +1,2 @@
|
||||||
|
pub mod number_of_points_dial;
|
||||||
|
pub mod point_radius_handle;
|
|
@ -0,0 +1,209 @@
|
||||||
|
use crate::consts::{GIZMO_HIDE_THRESHOLD, NUMBER_OF_POINTS_DIAL_SPOKE_EXTENSION, NUMBER_OF_POINTS_DIAL_SPOKE_LENGTH, POINT_RADIUS_HANDLE_SEGMENT_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::Responses;
|
||||||
|
use crate::messages::prelude::{DocumentMessageHandler, FrontendMessage, InputPreprocessorMessageHandler, NodeGraphMessage};
|
||||||
|
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||||
|
use crate::messages::tool::common_functionality::shape_editor::ShapeState;
|
||||||
|
use crate::messages::tool::common_functionality::shapes::shape_utility::{extract_polygon_parameters, inside_polygon, inside_star, polygon_outline, polygon_vertex_position, star_outline};
|
||||||
|
use crate::messages::tool::common_functionality::shapes::shape_utility::{extract_star_parameters, star_vertex_position};
|
||||||
|
use glam::{DAffine2, DVec2};
|
||||||
|
use graph_craft::document::NodeInput;
|
||||||
|
use graph_craft::document::value::TaggedValue;
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
use std::f64::consts::TAU;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default, PartialEq)]
|
||||||
|
pub enum NumberOfPointsDialState {
|
||||||
|
#[default]
|
||||||
|
Inactive,
|
||||||
|
Hover,
|
||||||
|
Dragging,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default)]
|
||||||
|
pub struct NumberOfPointsDial {
|
||||||
|
pub layer: Option<LayerNodeIdentifier>,
|
||||||
|
pub initial_points: u32,
|
||||||
|
pub handle_state: NumberOfPointsDialState,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NumberOfPointsDial {
|
||||||
|
pub fn cleanup(&mut self) {
|
||||||
|
self.handle_state = NumberOfPointsDialState::Inactive;
|
||||||
|
self.layer = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_state(&mut self, state: NumberOfPointsDialState) {
|
||||||
|
self.handle_state = state;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_hovering(&self) -> bool {
|
||||||
|
self.handle_state == NumberOfPointsDialState::Hover
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_dragging(&self) -> bool {
|
||||||
|
self.handle_state == NumberOfPointsDialState::Dragging
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_actions(&mut self, layer: LayerNodeIdentifier, mouse_position: DVec2, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
|
||||||
|
match &self.handle_state {
|
||||||
|
NumberOfPointsDialState::Inactive => {
|
||||||
|
// Star
|
||||||
|
if let Some((sides, radius1, radius2)) = extract_star_parameters(Some(layer), document) {
|
||||||
|
let viewport = document.metadata().transform_to_viewport(layer);
|
||||||
|
let center = viewport.transform_point2(DVec2::ZERO);
|
||||||
|
|
||||||
|
let point_on_max_radius = star_vertex_position(viewport, 0, sides, radius1, radius2);
|
||||||
|
|
||||||
|
if mouse_position.distance(center) < NUMBER_OF_POINTS_DIAL_SPOKE_LENGTH && point_on_max_radius.distance(center) > GIZMO_HIDE_THRESHOLD {
|
||||||
|
self.layer = Some(layer);
|
||||||
|
self.initial_points = sides;
|
||||||
|
self.update_state(NumberOfPointsDialState::Hover);
|
||||||
|
responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::EWResize });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polygon
|
||||||
|
if let Some((sides, radius)) = extract_polygon_parameters(Some(layer), document) {
|
||||||
|
let viewport = document.metadata().transform_to_viewport(layer);
|
||||||
|
let center = viewport.transform_point2(DVec2::ZERO);
|
||||||
|
|
||||||
|
let point_on_max_radius = polygon_vertex_position(viewport, 0, sides, radius);
|
||||||
|
|
||||||
|
if mouse_position.distance(center) < NUMBER_OF_POINTS_DIAL_SPOKE_LENGTH && point_on_max_radius.distance(center) > GIZMO_HIDE_THRESHOLD {
|
||||||
|
self.layer = Some(layer);
|
||||||
|
self.initial_points = sides;
|
||||||
|
self.update_state(NumberOfPointsDialState::Hover);
|
||||||
|
responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::EWResize });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NumberOfPointsDialState::Hover | NumberOfPointsDialState::Dragging => {
|
||||||
|
let Some(layer) = self.layer else { return };
|
||||||
|
|
||||||
|
let viewport = document.metadata().transform_to_viewport(layer);
|
||||||
|
let center = viewport.transform_point2(DVec2::ZERO);
|
||||||
|
|
||||||
|
if mouse_position.distance(center) > NUMBER_OF_POINTS_DIAL_SPOKE_LENGTH && matches!(&self.handle_state, NumberOfPointsDialState::Hover) {
|
||||||
|
self.update_state(NumberOfPointsDialState::Inactive);
|
||||||
|
self.layer = None;
|
||||||
|
responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Default });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn overlays(&self, document: &DocumentMessageHandler, layer: Option<LayerNodeIdentifier>, shape_editor: &mut &mut ShapeState, mouse_position: DVec2, overlay_context: &mut OverlayContext) {
|
||||||
|
match &self.handle_state {
|
||||||
|
NumberOfPointsDialState::Inactive => {
|
||||||
|
let Some(layer) = layer else { return };
|
||||||
|
|
||||||
|
// Star
|
||||||
|
if let Some((sides, radius1, radius2)) = extract_star_parameters(Some(layer), document) {
|
||||||
|
let radius = radius1.max(radius2);
|
||||||
|
let viewport = document.metadata().transform_to_viewport(layer);
|
||||||
|
let center = viewport.transform_point2(DVec2::ZERO);
|
||||||
|
|
||||||
|
if let Some(closest_segment) = shape_editor.upper_closest_segment(&document.network_interface, mouse_position, POINT_RADIUS_HANDLE_SEGMENT_THRESHOLD) {
|
||||||
|
if closest_segment.layer() == layer {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let point_on_max_radius = star_vertex_position(viewport, 0, sides, radius1, radius2);
|
||||||
|
|
||||||
|
if inside_star(viewport, sides, radius1, radius2, mouse_position) && point_on_max_radius.distance(center) > GIZMO_HIDE_THRESHOLD {
|
||||||
|
self.draw_spokes(center, viewport, sides, radius, overlay_context);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polygon
|
||||||
|
if let Some((sides, radius)) = extract_polygon_parameters(Some(layer), document) {
|
||||||
|
let viewport = document.metadata().transform_to_viewport(layer);
|
||||||
|
let center = viewport.transform_point2(DVec2::ZERO);
|
||||||
|
|
||||||
|
if let Some(closest_segment) = shape_editor.upper_closest_segment(&document.network_interface, mouse_position, POINT_RADIUS_HANDLE_SEGMENT_THRESHOLD) {
|
||||||
|
if closest_segment.layer() == layer {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let point_on_max_radius = polygon_vertex_position(viewport, 0, sides, radius);
|
||||||
|
|
||||||
|
if inside_polygon(viewport, sides, radius, mouse_position) && point_on_max_radius.distance(center) > GIZMO_HIDE_THRESHOLD {
|
||||||
|
self.draw_spokes(center, viewport, sides, radius, overlay_context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NumberOfPointsDialState::Hover | NumberOfPointsDialState::Dragging => {
|
||||||
|
let Some(layer) = self.layer else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get the star's greater radius or polygon's radius, as well as the number of sides
|
||||||
|
let Some((sides, radius)) = extract_star_parameters(Some(layer), document)
|
||||||
|
.map(|(sides, r1, r2)| (sides, r1.max(r2)))
|
||||||
|
.or_else(|| extract_polygon_parameters(Some(layer), document))
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let viewport = document.metadata().transform_to_viewport(layer);
|
||||||
|
let center = viewport.transform_point2(DVec2::ZERO);
|
||||||
|
|
||||||
|
// Draw either the star or polygon outline
|
||||||
|
star_outline(Some(layer), document, overlay_context);
|
||||||
|
polygon_outline(Some(layer), document, overlay_context);
|
||||||
|
|
||||||
|
self.draw_spokes(center, viewport, sides, radius, overlay_context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_spokes(&self, center: DVec2, viewport: DAffine2, sides: u32, radius: f64, overlay_context: &mut OverlayContext) {
|
||||||
|
for i in 0..sides {
|
||||||
|
let angle = ((i as f64) * TAU) / (sides as f64);
|
||||||
|
|
||||||
|
let point = viewport.transform_point2(DVec2 {
|
||||||
|
x: radius * angle.sin(),
|
||||||
|
y: -radius * angle.cos(),
|
||||||
|
});
|
||||||
|
|
||||||
|
let Some(direction) = (point - center).try_normalize() else { continue };
|
||||||
|
|
||||||
|
// If the user zooms out such that shape is very small hide the gizmo
|
||||||
|
if point.distance(center) < GIZMO_HIDE_THRESHOLD {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let end_point = direction * NUMBER_OF_POINTS_DIAL_SPOKE_LENGTH;
|
||||||
|
if matches!(self.handle_state, NumberOfPointsDialState::Hover | NumberOfPointsDialState::Dragging) {
|
||||||
|
overlay_context.line(center, end_point * NUMBER_OF_POINTS_DIAL_SPOKE_EXTENSION + center, None, None);
|
||||||
|
} else {
|
||||||
|
overlay_context.line(center, end_point + center, None, None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_number_of_sides(&self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>, drag_start: DVec2) {
|
||||||
|
let delta = input.mouse.position - document.metadata().document_to_viewport.transform_point2(drag_start);
|
||||||
|
let sign = (input.mouse.position.x - document.metadata().document_to_viewport.transform_point2(drag_start).x).signum();
|
||||||
|
let net_delta = (delta.length() / 25.).round() * sign;
|
||||||
|
|
||||||
|
let Some(layer) = self.layer else { return };
|
||||||
|
let Some(node_id) = graph_modification_utils::get_star_id(layer, &document.network_interface).or(graph_modification_utils::get_polygon_id(layer, &document.network_interface)) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let new_point_count = ((self.initial_points as i32) + (net_delta as i32)).max(3);
|
||||||
|
|
||||||
|
responses.add(NodeGraphMessage::SetInput {
|
||||||
|
input_connector: InputConnector::node(node_id, 1),
|
||||||
|
input: NodeInput::value(TaggedValue::U32(new_point_count as u32), false),
|
||||||
|
});
|
||||||
|
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,12 +1,15 @@
|
||||||
use crate::consts::{COLOR_OVERLAY_RED, GIZMO_HIDE_THRESHOLD, POINT_RADIUS_HANDLE_SNAP_THRESHOLD};
|
use crate::consts::GIZMO_HIDE_THRESHOLD;
|
||||||
|
use crate::consts::{COLOR_OVERLAY_RED, POINT_RADIUS_HANDLE_SNAP_THRESHOLD};
|
||||||
|
use crate::messages::frontend::utility_types::MouseCursorIcon;
|
||||||
use crate::messages::message::Message;
|
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::document_metadata::LayerNodeIdentifier;
|
||||||
use crate::messages::portfolio::document::utility_types::network_interface::InputConnector;
|
use crate::messages::portfolio::document::{overlays::utility_types::OverlayContext, utility_types::network_interface::InputConnector};
|
||||||
|
use crate::messages::prelude::FrontendMessage;
|
||||||
use crate::messages::prelude::Responses;
|
use crate::messages::prelude::Responses;
|
||||||
use crate::messages::prelude::{DocumentMessageHandler, InputPreprocessorMessageHandler, NodeGraphMessage};
|
use crate::messages::prelude::{DocumentMessageHandler, InputPreprocessorMessageHandler, NodeGraphMessage};
|
||||||
use crate::messages::tool::common_functionality::graph_modification_utils::{self, NodeGraphLayer};
|
use crate::messages::tool::common_functionality::graph_modification_utils::{self, NodeGraphLayer};
|
||||||
use crate::messages::tool::common_functionality::shapes::shape_utility::{draw_snapping_ticks, extract_polygon_parameters, extract_star_parameters, polygon_vertex_position, star_vertex_position};
|
use crate::messages::tool::common_functionality::shapes::shape_utility::{draw_snapping_ticks, extract_polygon_parameters, polygon_outline, polygon_vertex_position, star_outline};
|
||||||
|
use crate::messages::tool::common_functionality::shapes::shape_utility::{extract_star_parameters, star_vertex_position};
|
||||||
use glam::DVec2;
|
use glam::DVec2;
|
||||||
use graph_craft::document::NodeInput;
|
use graph_craft::document::NodeInput;
|
||||||
use graph_craft::document::value::TaggedValue;
|
use graph_craft::document::value::TaggedValue;
|
||||||
|
@ -39,78 +42,71 @@ impl PointRadiusHandle {
|
||||||
self.layer = None;
|
self.layer = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_inactive(&self) -> bool {
|
|
||||||
self.handle_state == PointRadiusHandleState::Inactive
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn hovered(&self) -> bool {
|
pub fn hovered(&self) -> bool {
|
||||||
self.handle_state == PointRadiusHandleState::Hover
|
self.handle_state == PointRadiusHandleState::Hover
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_dragging_or_snapped(&self) -> bool {
|
||||||
|
self.handle_state == PointRadiusHandleState::Dragging || matches!(self.handle_state, PointRadiusHandleState::Snapped(_))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn update_state(&mut self, state: PointRadiusHandleState) {
|
pub fn update_state(&mut self, state: PointRadiusHandleState) {
|
||||||
self.handle_state = state;
|
self.handle_state = state;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_actions(&mut self, document: &DocumentMessageHandler, mouse_position: DVec2) {
|
pub fn handle_actions(&mut self, layer: LayerNodeIdentifier, document: &DocumentMessageHandler, mouse_position: DVec2, responses: &mut VecDeque<Message>) {
|
||||||
match &self.handle_state {
|
match &self.handle_state {
|
||||||
PointRadiusHandleState::Inactive => {
|
PointRadiusHandleState::Inactive => {
|
||||||
for layer in document
|
// Draw the point handle gizmo for the star shape
|
||||||
.network_interface
|
if let Some((sides, radius1, radius2)) = extract_star_parameters(Some(layer), document) {
|
||||||
.selected_nodes()
|
let viewport = document.metadata().transform_to_viewport(layer);
|
||||||
.selected_visible_and_unlocked_layers(&document.network_interface)
|
|
||||||
.filter(|layer| {
|
|
||||||
graph_modification_utils::get_star_id(*layer, &document.network_interface).is_some() || graph_modification_utils::get_polygon_id(*layer, &document.network_interface).is_some()
|
|
||||||
}) {
|
|
||||||
// Draw the point handle gizmo for the star shape
|
|
||||||
if let Some((n, radius1, radius2)) = extract_star_parameters(Some(layer), document) {
|
|
||||||
let viewport = document.metadata().transform_to_viewport(layer);
|
|
||||||
|
|
||||||
for i in 0..2 * n {
|
for i in 0..2 * sides {
|
||||||
let (radius, radius_index) = if i % 2 == 0 { (radius1, 2) } else { (radius2, 3) };
|
let (radius, radius_index) = if i % 2 == 0 { (radius1, 2) } else { (radius2, 3) };
|
||||||
let point = star_vertex_position(viewport, i as i32, n, radius1, radius2);
|
let point = star_vertex_position(viewport, i as i32, sides, radius1, radius2);
|
||||||
let center = viewport.transform_point2(DVec2::ZERO);
|
let center = viewport.transform_point2(DVec2::ZERO);
|
||||||
|
|
||||||
// If the user zooms out such that shape is very small hide the gizmo
|
// If the user zooms out such that shape is very small hide the gizmo
|
||||||
if point.distance(center) < GIZMO_HIDE_THRESHOLD {
|
if point.distance(center) < GIZMO_HIDE_THRESHOLD {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if point.distance(mouse_position) < 5. {
|
if point.distance(mouse_position) < 5. {
|
||||||
self.radius_index = radius_index;
|
self.radius_index = radius_index;
|
||||||
self.layer = Some(layer);
|
self.layer = Some(layer);
|
||||||
self.point = i;
|
self.point = i;
|
||||||
self.snap_radii = Self::calculate_snap_radii(document, layer, radius_index);
|
self.snap_radii = Self::calculate_snap_radii(document, layer, radius_index);
|
||||||
self.initial_radius = radius;
|
self.initial_radius = radius;
|
||||||
self.update_state(PointRadiusHandleState::Hover);
|
responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Default });
|
||||||
|
self.update_state(PointRadiusHandleState::Hover);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Draw the point handle gizmo for the polygon shape
|
// Draw the point handle gizmo for the polygon shape
|
||||||
if let Some((n, radius)) = extract_polygon_parameters(Some(layer), document) {
|
if let Some((sides, radius)) = extract_polygon_parameters(Some(layer), document) {
|
||||||
let viewport = document.metadata().transform_to_viewport(layer);
|
let viewport = document.metadata().transform_to_viewport(layer);
|
||||||
|
|
||||||
for i in 0..n {
|
for i in 0..sides {
|
||||||
let point = polygon_vertex_position(viewport, i as i32, n, radius);
|
let point = polygon_vertex_position(viewport, i as i32, sides, radius);
|
||||||
let center = viewport.transform_point2(DVec2::ZERO);
|
let center = viewport.transform_point2(DVec2::ZERO);
|
||||||
|
|
||||||
// If the user zooms out so the shape is very small, hide the gizmo
|
// If the user zooms out such that shape is very small hide the gizmo
|
||||||
if point.distance(center) < GIZMO_HIDE_THRESHOLD {
|
if point.distance(center) < GIZMO_HIDE_THRESHOLD {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if point.distance(mouse_position) < 5. {
|
if point.distance(mouse_position) < 5. {
|
||||||
self.radius_index = 2;
|
self.radius_index = 2;
|
||||||
self.layer = Some(layer);
|
self.layer = Some(layer);
|
||||||
self.point = i;
|
self.point = i;
|
||||||
self.snap_radii.clear();
|
self.snap_radii.clear();
|
||||||
self.initial_radius = radius;
|
self.initial_radius = radius;
|
||||||
self.update_state(PointRadiusHandleState::Hover);
|
self.update_state(PointRadiusHandleState::Hover);
|
||||||
|
responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Default });
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -121,8 +117,9 @@ impl PointRadiusHandle {
|
||||||
|
|
||||||
let viewport = document.metadata().transform_to_viewport(layer);
|
let viewport = document.metadata().transform_to_viewport(layer);
|
||||||
|
|
||||||
if let Some((n, radius1, radius2)) = extract_star_parameters(Some(layer), document) {
|
// Star
|
||||||
let point = star_vertex_position(viewport, self.point as i32, n, radius1, radius2);
|
if let Some((sides, radius1, radius2)) = extract_star_parameters(Some(layer), document) {
|
||||||
|
let point = star_vertex_position(viewport, self.point as i32, sides, radius1, radius2);
|
||||||
|
|
||||||
if matches!(&self.handle_state, PointRadiusHandleState::Hover) && (mouse_position - point).length() > 5. {
|
if matches!(&self.handle_state, PointRadiusHandleState::Hover) && (mouse_position - point).length() > 5. {
|
||||||
self.update_state(PointRadiusHandleState::Inactive);
|
self.update_state(PointRadiusHandleState::Inactive);
|
||||||
|
@ -131,8 +128,9 @@ impl PointRadiusHandle {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some((n, radius)) = extract_polygon_parameters(Some(layer), document) {
|
// Polygon
|
||||||
let point = polygon_vertex_position(viewport, self.point as i32, n, radius);
|
if let Some((sides, radius)) = extract_polygon_parameters(Some(layer), document) {
|
||||||
|
let point = polygon_vertex_position(viewport, self.point as i32, sides, radius);
|
||||||
|
|
||||||
if matches!(&self.handle_state, PointRadiusHandleState::Hover) && (mouse_position - point).length() > 5. {
|
if matches!(&self.handle_state, PointRadiusHandleState::Hover) && (mouse_position - point).length() > 5. {
|
||||||
self.update_state(PointRadiusHandleState::Inactive);
|
self.update_state(PointRadiusHandleState::Inactive);
|
||||||
|
@ -144,85 +142,109 @@ impl PointRadiusHandle {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn overlays(&mut self, other_gizmo_active: bool, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, mouse_position: DVec2, overlay_context: &mut OverlayContext) {
|
pub fn overlays(
|
||||||
|
&self,
|
||||||
|
selected_star_layer: Option<LayerNodeIdentifier>,
|
||||||
|
document: &DocumentMessageHandler,
|
||||||
|
input: &InputPreprocessorMessageHandler,
|
||||||
|
mouse_position: DVec2,
|
||||||
|
overlay_context: &mut OverlayContext,
|
||||||
|
) {
|
||||||
match &self.handle_state {
|
match &self.handle_state {
|
||||||
PointRadiusHandleState::Inactive => {
|
PointRadiusHandleState::Inactive => {
|
||||||
let selected_nodes = document.network_interface.selected_nodes();
|
let Some(layer) = selected_star_layer else { return };
|
||||||
let layers = selected_nodes.selected_visible_and_unlocked_layers(&document.network_interface).filter(|layer| {
|
|
||||||
graph_modification_utils::get_star_id(*layer, &document.network_interface).is_some() || graph_modification_utils::get_polygon_id(*layer, &document.network_interface).is_some()
|
|
||||||
});
|
|
||||||
for layer in layers {
|
|
||||||
if other_gizmo_active {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Draw the point handle gizmo for the star shape
|
|
||||||
if let Some((n, radius1, radius2)) = extract_star_parameters(Some(layer), document) {
|
|
||||||
let viewport = document.metadata().transform_to_viewport(layer);
|
|
||||||
|
|
||||||
for i in 0..(2 * n) {
|
// Draw the point handle gizmo for the star shape
|
||||||
let point = star_vertex_position(viewport, i as i32, n, radius1, radius2);
|
if let Some((sides, radius1, radius2)) = extract_star_parameters(Some(layer), document) {
|
||||||
let center = viewport.transform_point2(DVec2::ZERO);
|
let viewport = document.metadata().transform_to_viewport(layer);
|
||||||
let viewport_diagonal = input.viewport_bounds.size().length();
|
|
||||||
|
|
||||||
// If the user zooms out such that shape is very small hide the gizmo
|
for i in 0..(2 * sides) {
|
||||||
if point.distance(center) < GIZMO_HIDE_THRESHOLD {
|
let point = star_vertex_position(viewport, i as i32, sides, radius1, radius2);
|
||||||
return;
|
let center = viewport.transform_point2(DVec2::ZERO);
|
||||||
}
|
let viewport_diagonal = input.viewport_bounds.size().length();
|
||||||
|
|
||||||
if point.distance(mouse_position) < 5. {
|
// If the user zooms out such that shape is very small hide the gizmo
|
||||||
let Some(direction) = (point - center).try_normalize() else { continue };
|
if point.distance(center) < GIZMO_HIDE_THRESHOLD {
|
||||||
|
return;
|
||||||
overlay_context.manipulator_handle(point, true, None);
|
|
||||||
let angle = ((i as f64) * PI) / (n as f64);
|
|
||||||
overlay_context.line(center, center + direction * viewport_diagonal, None, None);
|
|
||||||
|
|
||||||
draw_snapping_ticks(&self.snap_radii, direction, viewport, angle, overlay_context);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
overlay_context.manipulator_handle(point, false, None);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Draw the point handle gizmo for the Polygon shape
|
if point.distance(mouse_position) < 5. {
|
||||||
if let Some((n, radius)) = extract_polygon_parameters(Some(layer), document) {
|
let Some(direction) = (point - center).try_normalize() else { continue };
|
||||||
let viewport = document.metadata().transform_to_viewport(layer);
|
|
||||||
|
|
||||||
for i in 0..n {
|
overlay_context.manipulator_handle(point, true, None);
|
||||||
let point = polygon_vertex_position(viewport, i as i32, n, radius);
|
let angle = ((i as f64) * PI) / (sides as f64);
|
||||||
let center = viewport.transform_point2(DVec2::ZERO);
|
overlay_context.line(center, center + direction * viewport_diagonal, None, None);
|
||||||
let viewport_diagonal = input.viewport_bounds.size().length();
|
|
||||||
|
|
||||||
// If the user zooms out such that shape is very small hide the gizmo
|
draw_snapping_ticks(&self.snap_radii, direction, viewport, angle, overlay_context);
|
||||||
if point.distance(center) < GIZMO_HIDE_THRESHOLD {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if point.distance(mouse_position) < 5. {
|
return;
|
||||||
let Some(direction) = (point - center).try_normalize() else { continue };
|
|
||||||
|
|
||||||
overlay_context.manipulator_handle(point, true, None);
|
|
||||||
overlay_context.line(center, center + direction * viewport_diagonal, None, None);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
overlay_context.manipulator_handle(point, false, None);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
overlay_context.manipulator_handle(point, false, None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw the point handle gizmo for the Polygon shape
|
||||||
|
if let Some((sides, radius)) = extract_polygon_parameters(Some(layer), document) {
|
||||||
|
let viewport = document.metadata().transform_to_viewport(layer);
|
||||||
|
|
||||||
|
for i in 0..sides {
|
||||||
|
let point = polygon_vertex_position(viewport, i as i32, sides, radius);
|
||||||
|
let center = viewport.transform_point2(DVec2::ZERO);
|
||||||
|
let viewport_diagonal = input.viewport_bounds.size().length();
|
||||||
|
|
||||||
|
// If the user zooms out such that shape is very small hide the gizmo
|
||||||
|
if point.distance(center) < GIZMO_HIDE_THRESHOLD {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if point.distance(mouse_position) < 5. {
|
||||||
|
let Some(direction) = (point - center).try_normalize() else { continue };
|
||||||
|
|
||||||
|
overlay_context.manipulator_handle(point, true, None);
|
||||||
|
overlay_context.line(center, center + direction * viewport_diagonal, None, None);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
overlay_context.manipulator_handle(point, false, None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PointRadiusHandleState::Dragging | PointRadiusHandleState::Hover => {
|
PointRadiusHandleState::Dragging | PointRadiusHandleState::Hover => {
|
||||||
let Some(layer) = self.layer else { return };
|
let Some(layer) = self.layer else { return };
|
||||||
|
|
||||||
let viewport = document.metadata().transform_to_viewport(layer);
|
let viewport = document.metadata().transform_to_viewport(layer);
|
||||||
let center = viewport.transform_point2(DVec2::ZERO);
|
let center = viewport.transform_point2(DVec2::ZERO);
|
||||||
let viewport_diagonal = input.viewport_bounds.size().length();
|
let viewport_diagonal = input.viewport_bounds.size().length();
|
||||||
|
|
||||||
if let Some((n, radius1, radius2)) = extract_star_parameters(Some(layer), document) {
|
// Star
|
||||||
let angle = ((self.point as f64) * PI) / (n as f64);
|
if let Some((sides, radius1, radius2)) = extract_star_parameters(Some(layer), document) {
|
||||||
let point = star_vertex_position(viewport, self.point as i32, n, radius1, radius2);
|
let angle = ((self.point as f64) * PI) / (sides as f64);
|
||||||
|
let point = star_vertex_position(viewport, self.point as i32, sides, radius1, radius2);
|
||||||
|
|
||||||
|
let Some(direction) = (point - center).try_normalize() else { return };
|
||||||
|
|
||||||
|
// Draws the ray from the center to the dragging point extending till the viewport
|
||||||
|
overlay_context.manipulator_handle(point, true, None);
|
||||||
|
overlay_context.line(center, center + direction * viewport_diagonal, None, None);
|
||||||
|
star_outline(Some(layer), document, overlay_context);
|
||||||
|
|
||||||
|
// Make the ticks for snapping
|
||||||
|
|
||||||
|
// If dragging to make radius negative don't show the
|
||||||
|
if (mouse_position - center).dot(direction) < 0. {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
draw_snapping_ticks(&self.snap_radii, direction, viewport, angle, overlay_context);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polygon
|
||||||
|
if let Some((sides, radius)) = extract_polygon_parameters(Some(layer), document) {
|
||||||
|
let point = polygon_vertex_position(viewport, self.point as i32, sides, radius);
|
||||||
|
|
||||||
let Some(direction) = (point - center).try_normalize() else { return };
|
let Some(direction) = (point - center).try_normalize() else { return };
|
||||||
|
|
||||||
|
@ -230,47 +252,33 @@ impl PointRadiusHandle {
|
||||||
overlay_context.manipulator_handle(point, true, None);
|
overlay_context.manipulator_handle(point, true, None);
|
||||||
overlay_context.line(center, center + direction * viewport_diagonal, None, None);
|
overlay_context.line(center, center + direction * viewport_diagonal, None, None);
|
||||||
|
|
||||||
// Makes the tick marks for snapping
|
polygon_outline(Some(layer), document, overlay_context);
|
||||||
|
|
||||||
// Only show the snapping ticks if the radius is positive
|
|
||||||
if (mouse_position - center).dot(direction) >= 0. {
|
|
||||||
draw_snapping_ticks(&self.snap_radii, direction, viewport, angle, overlay_context);
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some((n, radius)) = extract_polygon_parameters(Some(layer), document) {
|
|
||||||
let point = polygon_vertex_position(viewport, self.point as i32, n, radius);
|
|
||||||
|
|
||||||
let Some(direction) = (point - center).try_normalize() else { return };
|
|
||||||
|
|
||||||
// Draws the ray from the center to the dragging point and extending until the viewport edge is reached
|
|
||||||
overlay_context.manipulator_handle(point, true, None);
|
|
||||||
overlay_context.line(center, center + direction * viewport_diagonal, None, None);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PointRadiusHandleState::Snapped(snapping_index) => {
|
PointRadiusHandleState::Snapped(snapping_index) => {
|
||||||
let Some(layer) = self.layer else { return };
|
let Some(layer) = self.layer else { return };
|
||||||
let Some((n, radius1, radius2)) = extract_star_parameters(Some(layer), document) else { return };
|
let Some((sides, radius1, radius2)) = extract_star_parameters(Some(layer), document) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
let viewport = document.metadata().transform_to_viewport(layer);
|
let viewport = document.metadata().transform_to_viewport(layer);
|
||||||
let center = viewport.transform_point2(DVec2::ZERO);
|
let center = viewport.transform_point2(DVec2::ZERO);
|
||||||
|
|
||||||
match snapping_index {
|
match snapping_index {
|
||||||
// Make a triangle with the previous two points
|
// Make a triangle with previous two points
|
||||||
0 => {
|
0 => {
|
||||||
let before_outer_position = star_vertex_position(viewport, (self.point as i32) - 2, n, radius1, radius2);
|
let before_outer_position = star_vertex_position(viewport, (self.point as i32) - 2, sides, radius1, radius2);
|
||||||
let outer_position = star_vertex_position(viewport, (self.point as i32) - 1, n, radius1, radius2);
|
let outer_position = star_vertex_position(viewport, (self.point as i32) - 1, sides, radius1, radius2);
|
||||||
let point_position = star_vertex_position(viewport, self.point as i32, n, radius1, radius2);
|
let point_position = star_vertex_position(viewport, self.point as i32, sides, radius1, radius2);
|
||||||
|
|
||||||
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 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) = (center - outer_position).try_normalize() else { return };
|
||||||
|
|
||||||
let l1 = 0.2 * (before_outer_position - outer_position).length();
|
|
||||||
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;
|
||||||
|
@ -280,18 +288,20 @@ impl PointRadiusHandle {
|
||||||
overlay_context.line(new_point, point_position, Some(COLOR_OVERLAY_RED), Some(3.));
|
overlay_context.line(new_point, point_position, Some(COLOR_OVERLAY_RED), Some(3.));
|
||||||
}
|
}
|
||||||
1 => {
|
1 => {
|
||||||
let before_outer_position = star_vertex_position(viewport, (self.point as i32) - 1, n, 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, n, radius1, radius2);
|
|
||||||
let point_position = star_vertex_position(viewport, self.point as i32, n, 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(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 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) = (center - point_position).try_normalize() else { return };
|
||||||
|
|
||||||
let l1 = 0.2 * (before_outer_position - point_position).length();
|
|
||||||
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;
|
||||||
|
@ -301,35 +311,37 @@ impl PointRadiusHandle {
|
||||||
overlay_context.line(new_point, after_point_position, Some(COLOR_OVERLAY_RED), Some(3.));
|
overlay_context.line(new_point, after_point_position, Some(COLOR_OVERLAY_RED), Some(3.));
|
||||||
}
|
}
|
||||||
i => {
|
i => {
|
||||||
// Use `self.point` as an absolute reference, as it matches the index of the star's vertices starting from 0
|
// Use `self.point` as absolute reference as it matches the index of vertices of the star starting from 0
|
||||||
if i % 2 != 0 {
|
if i % 2 != 0 {
|
||||||
// Flipped case
|
// Flipped case
|
||||||
let point_position = star_vertex_position(viewport, self.point as i32, n, radius1, radius2);
|
let point_position = star_vertex_position(viewport, self.point as i32, sides, radius1, radius2);
|
||||||
let target_index = (1 - (*i as i32)).abs() + (self.point as i32);
|
let target_index = (1 - (*i as i32)).abs() + (self.point as i32);
|
||||||
let target_point_position = star_vertex_position(viewport, target_index, n, radius1, radius2);
|
let target_point_position = star_vertex_position(viewport, target_index, sides, radius1, radius2);
|
||||||
|
|
||||||
let mirrored_index = 2 * (self.point as i32) - target_index;
|
let mirrored_index = 2 * (self.point as i32) - target_index;
|
||||||
let mirrored = star_vertex_position(viewport, mirrored_index, n, radius1, radius2);
|
let mirrored = star_vertex_position(viewport, mirrored_index, sides, radius1, radius2);
|
||||||
|
|
||||||
overlay_context.line(point_position, target_point_position, Some(COLOR_OVERLAY_RED), Some(3.));
|
overlay_context.line(point_position, target_point_position, Some(COLOR_OVERLAY_RED), Some(3.));
|
||||||
overlay_context.line(point_position, mirrored, Some(COLOR_OVERLAY_RED), Some(3.));
|
overlay_context.line(point_position, mirrored, Some(COLOR_OVERLAY_RED), Some(3.));
|
||||||
} else {
|
} else {
|
||||||
let outer_index = (self.point as i32) - 1;
|
let outer_index = (self.point as i32) - 1;
|
||||||
let outer_position = star_vertex_position(viewport, outer_index, n, radius1, radius2);
|
let outer_position = star_vertex_position(viewport, outer_index, sides, radius1, radius2);
|
||||||
|
|
||||||
// The vertex which is colinear with the point we are dragging and its previous outer vertex
|
// The vertex which is colinear with the point we are dragging and its previous outer vertex
|
||||||
let target_index = (self.point as i32) + (*i as i32) - 1;
|
let target_index = (self.point as i32) + (*i as i32) - 1;
|
||||||
let target_point_position = star_vertex_position(viewport, target_index, n, radius1, radius2);
|
let target_point_position = star_vertex_position(viewport, target_index, sides, radius1, radius2);
|
||||||
|
|
||||||
let mirrored_index = 2 * outer_index - target_index;
|
let mirrored_index = 2 * outer_index - target_index;
|
||||||
|
|
||||||
let mirrored = star_vertex_position(viewport, mirrored_index, n, radius1, radius2);
|
let mirrored = star_vertex_position(viewport, mirrored_index, sides, radius1, radius2);
|
||||||
|
|
||||||
overlay_context.line(outer_position, target_point_position, Some(COLOR_OVERLAY_RED), Some(3.));
|
overlay_context.line(outer_position, target_point_position, Some(COLOR_OVERLAY_RED), Some(3.));
|
||||||
overlay_context.line(outer_position, mirrored, Some(COLOR_OVERLAY_RED), Some(3.));
|
overlay_context.line(outer_position, mirrored, Some(COLOR_OVERLAY_RED), Some(3.));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
star_outline(Some(layer), document, overlay_context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -342,29 +354,32 @@ impl PointRadiusHandle {
|
||||||
};
|
};
|
||||||
|
|
||||||
let other_index = if radius_index == 3 { 2 } else { 3 };
|
let other_index = if radius_index == 3 { 2 } else { 3 };
|
||||||
|
|
||||||
let Some(&TaggedValue::F64(other_radius)) = node_inputs[other_index].as_value() else {
|
let Some(&TaggedValue::F64(other_radius)) = node_inputs[other_index].as_value() else {
|
||||||
return snap_radii;
|
return snap_radii;
|
||||||
};
|
};
|
||||||
let Some(&TaggedValue::U32(n)) = node_inputs[1].as_value() else {
|
let Some(&TaggedValue::U32(sides)) = node_inputs[1].as_value() else {
|
||||||
return snap_radii;
|
return snap_radii;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Inner radius for 90°
|
// Inner radius for 90°
|
||||||
let b = FRAC_PI_4 * 3. - PI / (n as f64);
|
let b = FRAC_PI_4 * 3. - PI / (sides as f64);
|
||||||
let angle = b.sin();
|
let angle = b.sin();
|
||||||
let required_radius = (other_radius / angle) * FRAC_1_SQRT_2;
|
let required_radius = (other_radius / angle) * FRAC_1_SQRT_2;
|
||||||
|
|
||||||
snap_radii.push(required_radius);
|
snap_radii.push(required_radius);
|
||||||
|
|
||||||
// Also add the case where the radius exceeds the other radius (the "flipped" case)
|
// Also push the case when the when it length increases more than the other
|
||||||
|
|
||||||
let flipped = other_radius * angle * SQRT_2;
|
let flipped = other_radius * angle * SQRT_2;
|
||||||
|
|
||||||
snap_radii.push(flipped);
|
snap_radii.push(flipped);
|
||||||
|
|
||||||
for i in 1..n {
|
for i in 1..sides {
|
||||||
let n = n as f64;
|
let sides = sides as f64;
|
||||||
let i = i as f64;
|
let i = i as f64;
|
||||||
let denominator = 2. * ((PI * (i - 1.)) / n).cos() * ((PI * i) / n).sin();
|
let denominator = 2. * ((PI * (i - 1.)) / sides).cos() * ((PI * i) / sides).sin();
|
||||||
let numerator = ((2. * PI * i) / n).sin();
|
let numerator = ((2. * PI * i) / sides).sin();
|
||||||
let factor = numerator / denominator;
|
let factor = numerator / denominator;
|
||||||
|
|
||||||
if factor < 0. {
|
if factor < 0. {
|
||||||
|
@ -392,33 +407,32 @@ impl PointRadiusHandle {
|
||||||
|
|
||||||
// Check if either index is 0 or 1 and prioritize them
|
// Check if either index is 0 or 1 and prioritize them
|
||||||
match (*i_a == 0 || *i_a == 1, *i_b == 0 || *i_b == 1) {
|
match (*i_a == 0 || *i_a == 1, *i_b == 0 || *i_b == 1) {
|
||||||
(true, false) => std::cmp::Ordering::Less, // a is priority index, b is not
|
// `a` is priority index, `b` is not
|
||||||
(false, true) => std::cmp::Ordering::Greater, // b is priority index, a is not
|
(true, false) => std::cmp::Ordering::Less,
|
||||||
_ => dist_a.partial_cmp(&dist_b).unwrap_or(std::cmp::Ordering::Equal), // normal comparison
|
// `b` is priority index, `a` is not
|
||||||
|
(false, true) => std::cmp::Ordering::Greater,
|
||||||
|
// Normal comparison
|
||||||
|
_ => dist_a.partial_cmp(&dist_b).unwrap_or(std::cmp::Ordering::Equal),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.map(|(i, rad)| (i, *rad - original_radius))
|
.map(|(i, rad)| (i, *rad - original_radius))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_inner_radius(
|
pub fn update_inner_radius(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>, drag_start: DVec2) {
|
||||||
&mut self,
|
let Some(layer) = self.layer else { return };
|
||||||
document: &DocumentMessageHandler,
|
|
||||||
input: &InputPreprocessorMessageHandler,
|
|
||||||
layer: LayerNodeIdentifier,
|
|
||||||
responses: &mut VecDeque<Message>,
|
|
||||||
drag_start: DVec2,
|
|
||||||
) {
|
|
||||||
let Some(node_id) = graph_modification_utils::get_star_id(layer, &document.network_interface).or(graph_modification_utils::get_polygon_id(layer, &document.network_interface)) else {
|
let Some(node_id) = graph_modification_utils::get_star_id(layer, &document.network_interface).or(graph_modification_utils::get_polygon_id(layer, &document.network_interface)) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let transform = document.network_interface.document_metadata().transform_to_viewport(layer);
|
let viewport_transform = document.network_interface.document_metadata().transform_to_viewport(layer);
|
||||||
let center = transform.transform_point2(DVec2::ZERO);
|
let document_transform = document.network_interface.document_metadata().transform_to_document(layer);
|
||||||
|
let center = viewport_transform.transform_point2(DVec2::ZERO);
|
||||||
let radius_index = self.radius_index;
|
let radius_index = self.radius_index;
|
||||||
|
|
||||||
let original_radius = self.initial_radius;
|
let original_radius = self.initial_radius;
|
||||||
|
|
||||||
let delta = input.mouse.position - document.metadata().document_to_viewport.transform_point2(drag_start);
|
let delta = viewport_transform.inverse().transform_point2(input.mouse.position) - document_transform.inverse().transform_point2(drag_start);
|
||||||
let radius = document.metadata().document_to_viewport.transform_point2(drag_start) - center;
|
let radius = document.metadata().document_to_viewport.transform_point2(drag_start) - center;
|
||||||
let projection = delta.project_onto(radius);
|
let projection = delta.project_onto(radius);
|
||||||
let sign = radius.dot(delta).signum();
|
let sign = radius.dot(delta).signum();
|
|
@ -1,12 +1,12 @@
|
||||||
pub mod auto_panning;
|
pub mod auto_panning;
|
||||||
pub mod color_selector;
|
pub mod color_selector;
|
||||||
pub mod compass_rose;
|
pub mod compass_rose;
|
||||||
|
pub mod gizmos;
|
||||||
pub mod graph_modification_utils;
|
pub mod graph_modification_utils;
|
||||||
pub mod measure;
|
pub mod measure;
|
||||||
pub mod pivot;
|
pub mod pivot;
|
||||||
pub mod resize;
|
pub mod resize;
|
||||||
pub mod shape_editor;
|
pub mod shape_editor;
|
||||||
pub mod shape_gizmos;
|
|
||||||
pub mod shapes;
|
pub mod shapes;
|
||||||
pub mod snapping;
|
pub mod snapping;
|
||||||
pub mod transformation_cage;
|
pub mod transformation_cage;
|
||||||
|
|
|
@ -1,2 +0,0 @@
|
||||||
pub mod number_of_points_handle;
|
|
||||||
pub mod point_radius_handle;
|
|
|
@ -1,241 +0,0 @@
|
||||||
use crate::consts::{GIZMO_HIDE_THRESHOLD, NUMBER_OF_POINTS_HANDLE_SPOKE_EXTENSION, NUMBER_OF_POINTS_HANDLE_SPOKE_LENGTH, POINT_RADIUS_HANDLE_SEGMENT_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::Responses;
|
|
||||||
use crate::messages::prelude::{DocumentMessageHandler, FrontendMessage, InputPreprocessorMessageHandler, NodeGraphMessage};
|
|
||||||
use crate::messages::tool::common_functionality::graph_modification_utils;
|
|
||||||
use crate::messages::tool::common_functionality::shape_editor::ShapeState;
|
|
||||||
use crate::messages::tool::common_functionality::shapes::shape_utility::{
|
|
||||||
extract_polygon_parameters, extract_star_parameters, inside_polygon, inside_star, polygon_vertex_position, star_vertex_position,
|
|
||||||
};
|
|
||||||
use crate::messages::tool::tool_messages::tool_prelude::Key;
|
|
||||||
use glam::{DAffine2, DVec2};
|
|
||||||
use graph_craft::document::NodeInput;
|
|
||||||
use graph_craft::document::value::TaggedValue;
|
|
||||||
use std::collections::VecDeque;
|
|
||||||
use std::f64::consts::TAU;
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, PartialEq)]
|
|
||||||
pub enum NumberOfPointsHandleState {
|
|
||||||
#[default]
|
|
||||||
Inactive,
|
|
||||||
Hover,
|
|
||||||
Dragging,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default)]
|
|
||||||
pub struct NumberOfPointsHandle {
|
|
||||||
pub layer: Option<LayerNodeIdentifier>,
|
|
||||||
pub initial_points: u32,
|
|
||||||
pub handle_state: NumberOfPointsHandleState,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl NumberOfPointsHandle {
|
|
||||||
pub fn cleanup(&mut self) {
|
|
||||||
self.handle_state = NumberOfPointsHandleState::Inactive;
|
|
||||||
self.layer = None;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update_state(&mut self, state: NumberOfPointsHandleState) {
|
|
||||||
self.handle_state = state;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_hovering(&self) -> bool {
|
|
||||||
self.handle_state == NumberOfPointsHandleState::Hover
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_dragging(&self) -> bool {
|
|
||||||
self.handle_state == NumberOfPointsHandleState::Dragging
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn handle_actions(
|
|
||||||
&mut self,
|
|
||||||
document: &DocumentMessageHandler,
|
|
||||||
input: &InputPreprocessorMessageHandler,
|
|
||||||
mouse_position: DVec2,
|
|
||||||
overlay_context: &mut OverlayContext,
|
|
||||||
responses: &mut VecDeque<Message>,
|
|
||||||
) {
|
|
||||||
if input.keyboard.key(Key::Control) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
match &self.handle_state {
|
|
||||||
NumberOfPointsHandleState::Inactive => {
|
|
||||||
let selected_nodes = document.network_interface.selected_nodes();
|
|
||||||
let layers = selected_nodes.selected_visible_and_unlocked_layers(&document.network_interface).filter(|layer| {
|
|
||||||
graph_modification_utils::get_star_id(*layer, &document.network_interface).is_some() || graph_modification_utils::get_polygon_id(*layer, &document.network_interface).is_some()
|
|
||||||
});
|
|
||||||
for layer in layers {
|
|
||||||
if let Some((n, radius1, radius2)) = extract_star_parameters(Some(layer), document) {
|
|
||||||
let viewport = document.metadata().transform_to_viewport(layer);
|
|
||||||
let center = viewport.transform_point2(DVec2::ZERO);
|
|
||||||
|
|
||||||
let point_on_max_radius = star_vertex_position(viewport, 0, n, radius1, radius2);
|
|
||||||
|
|
||||||
if mouse_position.distance(center) < NUMBER_OF_POINTS_HANDLE_SPOKE_LENGTH && point_on_max_radius.distance(center) > GIZMO_HIDE_THRESHOLD {
|
|
||||||
self.layer = Some(layer);
|
|
||||||
self.initial_points = n;
|
|
||||||
self.update_state(NumberOfPointsHandleState::Hover);
|
|
||||||
responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::EWResize });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some((n, radius)) = extract_polygon_parameters(Some(layer), document) {
|
|
||||||
let viewport = document.metadata().transform_to_viewport(layer);
|
|
||||||
let center = viewport.transform_point2(DVec2::ZERO);
|
|
||||||
|
|
||||||
let point_on_max_radius = polygon_vertex_position(viewport, 0, n, radius);
|
|
||||||
|
|
||||||
if mouse_position.distance(center) < NUMBER_OF_POINTS_HANDLE_SPOKE_LENGTH && point_on_max_radius.distance(center) > GIZMO_HIDE_THRESHOLD {
|
|
||||||
self.layer = Some(layer);
|
|
||||||
self.initial_points = n;
|
|
||||||
self.update_state(NumberOfPointsHandleState::Hover);
|
|
||||||
responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::EWResize });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
NumberOfPointsHandleState::Hover | NumberOfPointsHandleState::Dragging => {
|
|
||||||
let Some(layer) = self.layer else { return };
|
|
||||||
|
|
||||||
let Some((n, radius)) = extract_star_parameters(Some(layer), document)
|
|
||||||
.map(|(n, r1, r2)| (n, r1.max(r2)))
|
|
||||||
.or_else(|| extract_polygon_parameters(Some(layer), document))
|
|
||||||
else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
let viewport = document.metadata().transform_to_viewport(layer);
|
|
||||||
let center = viewport.transform_point2(DVec2::ZERO);
|
|
||||||
|
|
||||||
if mouse_position.distance(center) > NUMBER_OF_POINTS_HANDLE_SPOKE_LENGTH && matches!(&self.handle_state, NumberOfPointsHandleState::Hover) {
|
|
||||||
self.update_state(NumberOfPointsHandleState::Inactive);
|
|
||||||
self.layer = None;
|
|
||||||
self.draw_spokes(center, viewport, n, radius, overlay_context);
|
|
||||||
responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Default });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn overlays(
|
|
||||||
&mut self,
|
|
||||||
document: &DocumentMessageHandler,
|
|
||||||
input: &InputPreprocessorMessageHandler,
|
|
||||||
shape_editor: &mut &mut ShapeState,
|
|
||||||
mouse_position: DVec2,
|
|
||||||
overlay_context: &mut OverlayContext,
|
|
||||||
) {
|
|
||||||
if input.keyboard.key(Key::Control) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
match &self.handle_state {
|
|
||||||
NumberOfPointsHandleState::Inactive => {
|
|
||||||
let selected_nodes = document.network_interface.selected_nodes();
|
|
||||||
let layers = selected_nodes.selected_visible_and_unlocked_layers(&document.network_interface).filter(|layer| {
|
|
||||||
graph_modification_utils::get_star_id(*layer, &document.network_interface).is_some() || graph_modification_utils::get_polygon_id(*layer, &document.network_interface).is_some()
|
|
||||||
});
|
|
||||||
for layer in layers {
|
|
||||||
if let Some((n, radius1, radius2)) = extract_star_parameters(Some(layer), document) {
|
|
||||||
let radius = radius1.max(radius2);
|
|
||||||
let viewport = document.metadata().transform_to_viewport(layer);
|
|
||||||
let center = viewport.transform_point2(DVec2::ZERO);
|
|
||||||
|
|
||||||
if let Some(closest_segment) = shape_editor.upper_closest_segment(&document.network_interface, mouse_position, POINT_RADIUS_HANDLE_SEGMENT_THRESHOLD) {
|
|
||||||
if closest_segment.layer() == layer {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let point_on_max_radius = star_vertex_position(viewport, 0, n, radius1, radius2);
|
|
||||||
|
|
||||||
if inside_star(viewport, n, radius1, radius2, mouse_position) && point_on_max_radius.distance(center) > GIZMO_HIDE_THRESHOLD {
|
|
||||||
self.draw_spokes(center, viewport, n, radius, overlay_context);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some((n, radius)) = extract_polygon_parameters(Some(layer), document) {
|
|
||||||
let viewport = document.metadata().transform_to_viewport(layer);
|
|
||||||
let center = viewport.transform_point2(DVec2::ZERO);
|
|
||||||
|
|
||||||
if let Some(closest_segment) = shape_editor.upper_closest_segment(&document.network_interface, mouse_position, POINT_RADIUS_HANDLE_SEGMENT_THRESHOLD) {
|
|
||||||
if closest_segment.layer() == layer {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let point_on_max_radius = polygon_vertex_position(viewport, 0, n, radius);
|
|
||||||
|
|
||||||
if inside_polygon(viewport, n, radius, mouse_position) && point_on_max_radius.distance(center) > GIZMO_HIDE_THRESHOLD {
|
|
||||||
self.draw_spokes(center, viewport, n, radius, overlay_context);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
NumberOfPointsHandleState::Hover | NumberOfPointsHandleState::Dragging => {
|
|
||||||
let Some(layer) = self.layer else { return };
|
|
||||||
|
|
||||||
let Some((n, radius)) = extract_star_parameters(Some(layer), document)
|
|
||||||
.map(|(n, r1, r2)| (n, r1.max(r2)))
|
|
||||||
.or_else(|| extract_polygon_parameters(Some(layer), document))
|
|
||||||
else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
let viewport = document.metadata().transform_to_viewport(layer);
|
|
||||||
let center = viewport.transform_point2(DVec2::ZERO);
|
|
||||||
|
|
||||||
self.draw_spokes(center, viewport, n, radius, overlay_context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn draw_spokes(&self, center: DVec2, viewport: DAffine2, n: u32, radius: f64, overlay_context: &mut OverlayContext) {
|
|
||||||
for i in 0..n {
|
|
||||||
let angle = ((i as f64) * TAU) / (n as f64);
|
|
||||||
|
|
||||||
let point = viewport.transform_point2(DVec2 {
|
|
||||||
x: radius * angle.sin(),
|
|
||||||
y: -radius * angle.cos(),
|
|
||||||
});
|
|
||||||
|
|
||||||
let Some(direction) = (point - center).try_normalize() else { continue };
|
|
||||||
|
|
||||||
// If the user zooms out such that shape is very small hide the gizmo
|
|
||||||
if point.distance(center) < GIZMO_HIDE_THRESHOLD {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let end_point = direction * NUMBER_OF_POINTS_HANDLE_SPOKE_LENGTH;
|
|
||||||
if matches!(self.handle_state, NumberOfPointsHandleState::Hover | NumberOfPointsHandleState::Dragging) {
|
|
||||||
overlay_context.line(center, end_point * NUMBER_OF_POINTS_HANDLE_SPOKE_EXTENSION + center, None, None);
|
|
||||||
} else {
|
|
||||||
overlay_context.line(center, end_point + center, None, None);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update_number_of_sides(&self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>, drag_start: DVec2) {
|
|
||||||
let delta = input.mouse.position - document.metadata().document_to_viewport.transform_point2(drag_start);
|
|
||||||
let sign = (input.mouse.position.x - document.metadata().document_to_viewport.transform_point2(drag_start).x).signum();
|
|
||||||
let net_delta = (delta.length() / 25.).round() * sign;
|
|
||||||
|
|
||||||
let Some(layer) = self.layer else { return };
|
|
||||||
let Some(node_id) = graph_modification_utils::get_star_id(layer, &document.network_interface).or(graph_modification_utils::get_polygon_id(layer, &document.network_interface)) else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
let new_point_count = ((self.initial_points as i32) + (net_delta as i32)).max(3);
|
|
||||||
|
|
||||||
responses.add(NodeGraphMessage::SetInput {
|
|
||||||
input_connector: InputConnector::node(node_id, 1),
|
|
||||||
input: NodeInput::value(TaggedValue::U32(new_point_count as u32), false),
|
|
||||||
});
|
|
||||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -3,15 +3,98 @@ use super::shape_utility::update_radius_sign;
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;
|
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::node_graph::document_node_definitions::resolve_document_node_type;
|
||||||
|
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
|
||||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||||
use crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, NodeTemplate};
|
use crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, NodeTemplate};
|
||||||
|
use crate::messages::tool::common_functionality::gizmos::shape_gizmos::number_of_points_dial::NumberOfPointsDial;
|
||||||
|
use crate::messages::tool::common_functionality::gizmos::shape_gizmos::number_of_points_dial::NumberOfPointsDialState;
|
||||||
|
use crate::messages::tool::common_functionality::gizmos::shape_gizmos::point_radius_handle::PointRadiusHandle;
|
||||||
|
use crate::messages::tool::common_functionality::gizmos::shape_gizmos::point_radius_handle::PointRadiusHandleState;
|
||||||
use crate::messages::tool::common_functionality::graph_modification_utils;
|
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||||
|
use crate::messages::tool::common_functionality::shape_editor::ShapeState;
|
||||||
|
use crate::messages::tool::common_functionality::shapes::shape_utility::ShapeGizmoHandler;
|
||||||
|
use crate::messages::tool::common_functionality::shapes::shape_utility::polygon_outline;
|
||||||
use crate::messages::tool::tool_messages::tool_prelude::*;
|
use crate::messages::tool::tool_messages::tool_prelude::*;
|
||||||
use glam::DAffine2;
|
use glam::DAffine2;
|
||||||
use graph_craft::document::NodeInput;
|
use graph_craft::document::NodeInput;
|
||||||
use graph_craft::document::value::TaggedValue;
|
use graph_craft::document::value::TaggedValue;
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default)]
|
||||||
|
pub struct PolygonGizmoHandler {
|
||||||
|
number_of_points_dial: NumberOfPointsDial,
|
||||||
|
point_radius_handle: PointRadiusHandle,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ShapeGizmoHandler for PolygonGizmoHandler {
|
||||||
|
fn is_any_gizmo_hovered(&self) -> bool {
|
||||||
|
self.number_of_points_dial.is_hovering() || self.point_radius_handle.hovered()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_state(&mut self, selected_star_layer: LayerNodeIdentifier, mouse_position: DVec2, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
|
||||||
|
self.number_of_points_dial.handle_actions(selected_star_layer, mouse_position, document, responses);
|
||||||
|
self.point_radius_handle.handle_actions(selected_star_layer, document, mouse_position, responses);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_click(&mut self) {
|
||||||
|
if self.number_of_points_dial.is_hovering() {
|
||||||
|
self.number_of_points_dial.update_state(NumberOfPointsDialState::Dragging);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.point_radius_handle.hovered() {
|
||||||
|
self.point_radius_handle.update_state(PointRadiusHandleState::Dragging);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_update(&mut self, drag_start: DVec2, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) {
|
||||||
|
if self.number_of_points_dial.is_dragging() {
|
||||||
|
self.number_of_points_dial.update_number_of_sides(document, input, responses, drag_start);
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.point_radius_handle.is_dragging_or_snapped() {
|
||||||
|
self.point_radius_handle.update_inner_radius(document, input, responses, drag_start);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn overlays(
|
||||||
|
&self,
|
||||||
|
document: &DocumentMessageHandler,
|
||||||
|
selected_polygon_layer: Option<LayerNodeIdentifier>,
|
||||||
|
input: &InputPreprocessorMessageHandler,
|
||||||
|
shape_editor: &mut &mut ShapeState,
|
||||||
|
mouse_position: DVec2,
|
||||||
|
overlay_context: &mut OverlayContext,
|
||||||
|
) {
|
||||||
|
self.number_of_points_dial.overlays(document, selected_polygon_layer, shape_editor, mouse_position, overlay_context);
|
||||||
|
self.point_radius_handle.overlays(selected_polygon_layer, document, input, mouse_position, overlay_context);
|
||||||
|
|
||||||
|
polygon_outline(selected_polygon_layer, document, overlay_context);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dragging_overlays(
|
||||||
|
&self,
|
||||||
|
document: &DocumentMessageHandler,
|
||||||
|
input: &InputPreprocessorMessageHandler,
|
||||||
|
shape_editor: &mut &mut ShapeState,
|
||||||
|
mouse_position: DVec2,
|
||||||
|
overlay_context: &mut OverlayContext,
|
||||||
|
) {
|
||||||
|
if self.number_of_points_dial.is_dragging() {
|
||||||
|
self.number_of_points_dial.overlays(document, None, shape_editor, mouse_position, overlay_context);
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.point_radius_handle.is_dragging_or_snapped() {
|
||||||
|
self.point_radius_handle.overlays(None, document, input, mouse_position, overlay_context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cleanup(&mut self) {
|
||||||
|
self.number_of_points_dial.cleanup();
|
||||||
|
self.point_radius_handle.cleanup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Polygon;
|
pub struct Polygon;
|
||||||
|
|
||||||
|
|
|
@ -3,8 +3,9 @@ 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;
|
||||||
use crate::messages::portfolio::document::utility_types::network_interface::InputConnector;
|
use crate::messages::portfolio::document::utility_types::network_interface::InputConnector;
|
||||||
use crate::messages::prelude::{DocumentMessageHandler, NodeGraphMessage, Responses};
|
use crate::messages::prelude::{DocumentMessageHandler, InputPreprocessorMessageHandler, NodeGraphMessage, Responses};
|
||||||
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::shape_editor::ShapeState;
|
||||||
use crate::messages::tool::common_functionality::transformation_cage::BoundingBoxManager;
|
use crate::messages::tool::common_functionality::transformation_cage::BoundingBoxManager;
|
||||||
use crate::messages::tool::tool_messages::tool_prelude::Key;
|
use crate::messages::tool::tool_messages::tool_prelude::Key;
|
||||||
use crate::messages::tool::utility_types::*;
|
use crate::messages::tool::utility_types::*;
|
||||||
|
@ -70,9 +71,62 @@ impl ShapeType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Center, Lock Ratio, Lock Angle, Snap Angle, Increase/Decrease Side
|
|
||||||
pub type ShapeToolModifierKey = [Key; 4];
|
pub type ShapeToolModifierKey = [Key; 4];
|
||||||
|
|
||||||
|
/// 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).
|
||||||
|
pub trait ShapeGizmoHandler {
|
||||||
|
/// Called every frame to update the gizmo's interaction state based on the mouse position and selection.
|
||||||
|
///
|
||||||
|
/// This includes detecting hover states and preparing interaction flags or visual feedback (e.g., highlighting a hovered handle).
|
||||||
|
fn handle_state(&mut self, selected_shape_layers: LayerNodeIdentifier, mouse_position: DVec2, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>);
|
||||||
|
|
||||||
|
/// Called when a mouse click occurs over the canvas and a gizmo handle is hovered.
|
||||||
|
///
|
||||||
|
/// Used to initiate drag interactions or toggle states on the handle, depending on the tool.
|
||||||
|
/// For example, a hovered "number of points" handle might enter a "Dragging" state.
|
||||||
|
fn handle_click(&mut self);
|
||||||
|
|
||||||
|
/// Called during a drag interaction to update the shape's parameters in real time.
|
||||||
|
///
|
||||||
|
/// For example, a handle might calculate the distance from the drag start to determine a new radius or update the number of points.
|
||||||
|
fn handle_update(&mut self, drag_start: DVec2, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>);
|
||||||
|
|
||||||
|
/// Draws the static or hover-dependent overlays associated with the gizmo.
|
||||||
|
///
|
||||||
|
/// These overlays include visual indicators like shape outlines, control points, and hover highlights.
|
||||||
|
fn overlays(
|
||||||
|
&self,
|
||||||
|
document: &DocumentMessageHandler,
|
||||||
|
selected_shape_layers: Option<LayerNodeIdentifier>,
|
||||||
|
input: &InputPreprocessorMessageHandler,
|
||||||
|
shape_editor: &mut &mut ShapeState,
|
||||||
|
mouse_position: DVec2,
|
||||||
|
overlay_context: &mut OverlayContext,
|
||||||
|
);
|
||||||
|
|
||||||
|
/// Draws overlays specifically during a drag operation.
|
||||||
|
///
|
||||||
|
/// Used to give real-time visual feedback based on drag progress, such as showing the updated shape preview or snapping guides.
|
||||||
|
fn dragging_overlays(
|
||||||
|
&self,
|
||||||
|
document: &DocumentMessageHandler,
|
||||||
|
input: &InputPreprocessorMessageHandler,
|
||||||
|
shape_editor: &mut &mut ShapeState,
|
||||||
|
mouse_position: DVec2,
|
||||||
|
overlay_context: &mut OverlayContext,
|
||||||
|
);
|
||||||
|
|
||||||
|
/// Returns `true` if any handle or control point in the gizmo is currently being hovered.
|
||||||
|
fn is_any_gizmo_hovered(&self) -> bool;
|
||||||
|
|
||||||
|
/// Resets or clears any internal state maintained by the gizmo when it is no longer active.
|
||||||
|
///
|
||||||
|
/// For example, dragging states or hover flags should be cleared to avoid visual glitches when switching tools or shapes.
|
||||||
|
fn cleanup(&mut self);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Center, Lock Ratio, Lock Angle, Snap Angle, Increase/Decrease Side
|
||||||
pub fn update_radius_sign(end: DVec2, start: DVec2, layer: LayerNodeIdentifier, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
|
pub fn update_radius_sign(end: DVec2, start: DVec2, layer: LayerNodeIdentifier, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
|
||||||
let sign_num = if end[1] > start[1] { 1. } else { -1. };
|
let sign_num = if end[1] > start[1] { 1. } else { -1. };
|
||||||
let new_layer = NodeGraphLayer::new(layer, &document.network_interface);
|
let new_layer = NodeGraphLayer::new(layer, &document.network_interface);
|
||||||
|
@ -154,19 +208,22 @@ pub fn anchor_overlays(document: &DocumentMessageHandler, overlay_context: &mut
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extract the node input values of Star
|
/// Extract the node input values of Star.
|
||||||
|
/// Returns an option of (sides, radius1, radius2).
|
||||||
pub fn extract_star_parameters(layer: Option<LayerNodeIdentifier>, document: &DocumentMessageHandler) -> Option<(u32, f64, f64)> {
|
pub fn extract_star_parameters(layer: Option<LayerNodeIdentifier>, document: &DocumentMessageHandler) -> Option<(u32, f64, f64)> {
|
||||||
let node_inputs = NodeGraphLayer::new(layer?, &document.network_interface).find_node_inputs("Star")?;
|
let node_inputs = NodeGraphLayer::new(layer?, &document.network_interface).find_node_inputs("Star")?;
|
||||||
|
|
||||||
let (Some(&TaggedValue::U32(n)), Some(&TaggedValue::F64(outer)), Some(&TaggedValue::F64(inner))) = (node_inputs.get(1)?.as_value(), node_inputs.get(2)?.as_value(), node_inputs.get(3)?.as_value())
|
let (Some(&TaggedValue::U32(sides)), Some(&TaggedValue::F64(radius_1)), Some(&TaggedValue::F64(radius_2))) =
|
||||||
|
(node_inputs.get(1)?.as_value(), node_inputs.get(2)?.as_value(), node_inputs.get(3)?.as_value())
|
||||||
else {
|
else {
|
||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
|
|
||||||
Some((n, outer, inner))
|
Some((sides, radius_1, radius_2))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extract the node input values of Polygon
|
/// Extract the node input values of Polygon.
|
||||||
|
/// Returns an option of (sides, radius).
|
||||||
pub fn extract_polygon_parameters(layer: Option<LayerNodeIdentifier>, document: &DocumentMessageHandler) -> Option<(u32, f64)> {
|
pub fn extract_polygon_parameters(layer: Option<LayerNodeIdentifier>, document: &DocumentMessageHandler) -> Option<(u32, f64)> {
|
||||||
let node_inputs = NodeGraphLayer::new(layer?, &document.network_interface).find_node_inputs("Regular Polygon")?;
|
let node_inputs = NodeGraphLayer::new(layer?, &document.network_interface).find_node_inputs("Regular Polygon")?;
|
||||||
|
|
||||||
|
@ -188,7 +245,7 @@ pub fn star_vertex_position(viewport: DAffine2, vertex_index: i32, n: u32, radiu
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Calculate the viewport position of as a polygon vertex given its index
|
/// Calculate the viewport position of a polygon vertex given its index
|
||||||
pub fn polygon_vertex_position(viewport: DAffine2, vertex_index: i32, n: u32, radius: f64) -> DVec2 {
|
pub fn polygon_vertex_position(viewport: DAffine2, vertex_index: i32, n: u32, radius: f64) -> DVec2 {
|
||||||
let angle = ((vertex_index as f64) * TAU) / (n as f64);
|
let angle = ((vertex_index as f64) * TAU) / (n as f64);
|
||||||
|
|
||||||
|
@ -198,49 +255,37 @@ pub fn polygon_vertex_position(viewport: DAffine2, vertex_index: i32, n: u32, ra
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Outlines the geometric shape made by the Star node
|
/// Outlines the geometric shape made by star-node
|
||||||
pub fn star_outline(layer: LayerNodeIdentifier, document: &DocumentMessageHandler, overlay_context: &mut OverlayContext) {
|
pub fn star_outline(layer: Option<LayerNodeIdentifier>, document: &DocumentMessageHandler, overlay_context: &mut OverlayContext) {
|
||||||
let mut anchors = Vec::new();
|
let Some(layer) = layer else { return };
|
||||||
let Some((n, radius1, radius2)) = extract_star_parameters(Some(layer), document) else { return };
|
let Some((sides, radius1, radius2)) = extract_star_parameters(Some(layer), document) else {
|
||||||
|
|
||||||
let viewport = document.metadata().transform_to_viewport(layer);
|
|
||||||
for i in 0..2 * n {
|
|
||||||
let angle = ((i as f64) * PI) / (n as f64);
|
|
||||||
let radius = if i % 2 == 0 { radius1 } else { radius2 };
|
|
||||||
|
|
||||||
let point = DVec2 {
|
|
||||||
x: radius * angle.sin(),
|
|
||||||
y: -radius * angle.cos(),
|
|
||||||
};
|
|
||||||
|
|
||||||
anchors.push(point);
|
|
||||||
}
|
|
||||||
|
|
||||||
let subpath = [ClickTargetType::Subpath(Subpath::from_anchors_linear(anchors, true))];
|
|
||||||
overlay_context.outline(subpath.iter(), viewport, None);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Outlines the geometric shape made by the Polygon node
|
|
||||||
pub fn polygon_outline(layer: LayerNodeIdentifier, document: &DocumentMessageHandler, overlay_context: &mut OverlayContext) {
|
|
||||||
let mut anchors = Vec::new();
|
|
||||||
|
|
||||||
let Some((n, radius)) = extract_polygon_parameters(Some(layer), document) else {
|
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let viewport = document.metadata().transform_to_viewport(layer);
|
let viewport = document.metadata().transform_to_viewport(layer);
|
||||||
for i in 0..2 * n {
|
|
||||||
let angle = ((i as f64) * TAU) / (n as f64);
|
|
||||||
|
|
||||||
let point = DVec2 {
|
let points = sides as u64;
|
||||||
x: radius * angle.sin(),
|
let diameter: f64 = radius1 * 2.;
|
||||||
y: -radius * angle.cos(),
|
let inner_diameter = radius2 * 2.;
|
||||||
};
|
|
||||||
|
|
||||||
anchors.push(point);
|
let subpath: Vec<ClickTargetType> = vec![ClickTargetType::Subpath(Subpath::new_star_polygon(DVec2::splat(-diameter), points, diameter, inner_diameter))];
|
||||||
}
|
|
||||||
|
|
||||||
let subpath: Vec<ClickTargetType> = vec![ClickTargetType::Subpath(Subpath::from_anchors_linear(anchors, true))];
|
overlay_context.outline(subpath.iter(), viewport, None);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Outlines the geometric shape made by polygon-node
|
||||||
|
pub fn polygon_outline(layer: Option<LayerNodeIdentifier>, document: &DocumentMessageHandler, overlay_context: &mut OverlayContext) {
|
||||||
|
let Some(layer) = layer else { return };
|
||||||
|
let Some((sides, radius)) = extract_polygon_parameters(Some(layer), document) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let viewport = document.metadata().transform_to_viewport(layer);
|
||||||
|
|
||||||
|
let points = sides as u64;
|
||||||
|
let radius: f64 = radius * 2.;
|
||||||
|
|
||||||
|
let subpath: Vec<ClickTargetType> = vec![ClickTargetType::Subpath(Subpath::new_regular_polygon(DVec2::splat(-radius), points, radius))];
|
||||||
|
|
||||||
overlay_context.outline(subpath.iter(), viewport, None);
|
overlay_context.outline(subpath.iter(), viewport, None);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,9 +2,14 @@ use super::shape_utility::{ShapeToolModifierKey, update_radius_sign};
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;
|
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::node_graph::document_node_definitions::resolve_document_node_type;
|
||||||
|
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
|
||||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||||
use crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, NodeTemplate};
|
use crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, NodeTemplate};
|
||||||
|
use crate::messages::tool::common_functionality::gizmos::shape_gizmos::number_of_points_dial::{NumberOfPointsDial, NumberOfPointsDialState};
|
||||||
|
use crate::messages::tool::common_functionality::gizmos::shape_gizmos::point_radius_handle::{PointRadiusHandle, PointRadiusHandleState};
|
||||||
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::shapes::shape_utility::{ShapeGizmoHandler, star_outline};
|
||||||
use crate::messages::tool::tool_messages::tool_prelude::*;
|
use crate::messages::tool::tool_messages::tool_prelude::*;
|
||||||
use core::f64;
|
use core::f64;
|
||||||
use glam::DAffine2;
|
use glam::DAffine2;
|
||||||
|
@ -12,6 +17,81 @@ use graph_craft::document::NodeInput;
|
||||||
use graph_craft::document::value::TaggedValue;
|
use graph_craft::document::value::TaggedValue;
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default)]
|
||||||
|
pub struct StarGizmoHandler {
|
||||||
|
number_of_points_dial: NumberOfPointsDial,
|
||||||
|
point_radius_handle: PointRadiusHandle,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ShapeGizmoHandler for StarGizmoHandler {
|
||||||
|
fn is_any_gizmo_hovered(&self) -> bool {
|
||||||
|
self.number_of_points_dial.is_hovering() || self.point_radius_handle.hovered()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_state(&mut self, selected_star_layer: LayerNodeIdentifier, mouse_position: DVec2, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
|
||||||
|
self.number_of_points_dial.handle_actions(selected_star_layer, mouse_position, document, responses);
|
||||||
|
self.point_radius_handle.handle_actions(selected_star_layer, document, mouse_position, responses);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_click(&mut self) {
|
||||||
|
if self.number_of_points_dial.is_hovering() {
|
||||||
|
self.number_of_points_dial.update_state(NumberOfPointsDialState::Dragging);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.point_radius_handle.hovered() {
|
||||||
|
self.point_radius_handle.update_state(PointRadiusHandleState::Dragging);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_update(&mut self, drag_start: DVec2, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) {
|
||||||
|
if self.number_of_points_dial.is_dragging() {
|
||||||
|
self.number_of_points_dial.update_number_of_sides(document, input, responses, drag_start);
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.point_radius_handle.is_dragging_or_snapped() {
|
||||||
|
self.point_radius_handle.update_inner_radius(document, input, responses, drag_start);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn overlays(
|
||||||
|
&self,
|
||||||
|
document: &DocumentMessageHandler,
|
||||||
|
selected_star_layer: Option<LayerNodeIdentifier>,
|
||||||
|
input: &InputPreprocessorMessageHandler,
|
||||||
|
shape_editor: &mut &mut ShapeState,
|
||||||
|
mouse_position: DVec2,
|
||||||
|
overlay_context: &mut OverlayContext,
|
||||||
|
) {
|
||||||
|
self.number_of_points_dial.overlays(document, selected_star_layer, shape_editor, mouse_position, overlay_context);
|
||||||
|
self.point_radius_handle.overlays(selected_star_layer, document, input, mouse_position, overlay_context);
|
||||||
|
|
||||||
|
star_outline(selected_star_layer, document, overlay_context);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dragging_overlays(
|
||||||
|
&self,
|
||||||
|
document: &DocumentMessageHandler,
|
||||||
|
input: &InputPreprocessorMessageHandler,
|
||||||
|
shape_editor: &mut &mut ShapeState,
|
||||||
|
mouse_position: DVec2,
|
||||||
|
overlay_context: &mut OverlayContext,
|
||||||
|
) {
|
||||||
|
if self.number_of_points_dial.is_dragging() {
|
||||||
|
self.number_of_points_dial.overlays(document, None, shape_editor, mouse_position, overlay_context);
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.point_radius_handle.is_dragging_or_snapped() {
|
||||||
|
self.point_radius_handle.overlays(None, document, input, mouse_position, overlay_context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cleanup(&mut self) {
|
||||||
|
self.number_of_points_dial.cleanup();
|
||||||
|
self.point_radius_handle.cleanup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Star;
|
pub struct Star;
|
||||||
|
|
||||||
|
|
|
@ -416,7 +416,7 @@ mod test_freehand {
|
||||||
editor
|
editor
|
||||||
.handle_message(GraphOperationMessage::TransformSet {
|
.handle_message(GraphOperationMessage::TransformSet {
|
||||||
layer: artboard,
|
layer: artboard,
|
||||||
transform: DAffine2::from_scale_angle_translation(DVec2::new(1.5, 0.8), 0.3, DVec2::new(10.0, -5.0)),
|
transform: DAffine2::from_scale_angle_translation(DVec2::new(1.5, 0.8), 0.3, DVec2::new(10., -5.)),
|
||||||
transform_in: TransformIn::Local,
|
transform_in: TransformIn::Local,
|
||||||
skip_rerender: false,
|
skip_rerender: false,
|
||||||
})
|
})
|
||||||
|
@ -424,14 +424,14 @@ mod test_freehand {
|
||||||
|
|
||||||
editor.select_tool(ToolType::Freehand).await;
|
editor.select_tool(ToolType::Freehand).await;
|
||||||
|
|
||||||
let mouse_points = [DVec2::new(150.0, 100.0), DVec2::new(200.0, 150.0), DVec2::new(250.0, 130.0), DVec2::new(300.0, 170.0)];
|
let mouse_points = [DVec2::new(150., 100.), DVec2::new(200., 150.), DVec2::new(250., 130.), DVec2::new(300., 170.)];
|
||||||
|
|
||||||
// Expected points that will actually be captured by the tool
|
// Expected points that will actually be captured by the tool
|
||||||
let expected_captured_points = &mouse_points[1..];
|
let expected_captured_points = &mouse_points[1..];
|
||||||
editor.drag_path(&mouse_points, ModifierKeys::empty()).await;
|
editor.drag_path(&mouse_points, ModifierKeys::empty()).await;
|
||||||
|
|
||||||
let vector_data_list = get_vector_data(&mut editor).await;
|
let vector_data_list = get_vector_data(&mut editor).await;
|
||||||
verify_path_points(&vector_data_list, expected_captured_points, 1.0).expect("Path points verification failed");
|
verify_path_points(&vector_data_list, expected_captured_points, 1.).expect("Path points verification failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
@ -439,7 +439,7 @@ mod test_freehand {
|
||||||
let mut editor = EditorTestUtils::create();
|
let mut editor = EditorTestUtils::create();
|
||||||
editor.new_document().await;
|
editor.new_document().await;
|
||||||
|
|
||||||
let initial_points = [DVec2::new(100.0, 100.0), DVec2::new(200.0, 200.0), DVec2::new(300.0, 100.0)];
|
let initial_points = [DVec2::new(100., 100.), DVec2::new(200., 200.), DVec2::new(300., 100.)];
|
||||||
|
|
||||||
editor.select_tool(ToolType::Freehand).await;
|
editor.select_tool(ToolType::Freehand).await;
|
||||||
|
|
||||||
|
@ -491,7 +491,7 @@ mod test_freehand {
|
||||||
|
|
||||||
assert!(endpoint_viewport_pos.is_finite(), "Endpoint position is not finite");
|
assert!(endpoint_viewport_pos.is_finite(), "Endpoint position is not finite");
|
||||||
|
|
||||||
let extension_points = [DVec2::new(400.0, 200.0), DVec2::new(500.0, 100.0)];
|
let extension_points = [DVec2::new(400., 200.), DVec2::new(500., 100.)];
|
||||||
|
|
||||||
let layer_node_id = {
|
let layer_node_id = {
|
||||||
let document = editor.active_document();
|
let document = editor.active_document();
|
||||||
|
@ -558,7 +558,7 @@ mod test_freehand {
|
||||||
|
|
||||||
editor.select_tool(ToolType::Freehand).await;
|
editor.select_tool(ToolType::Freehand).await;
|
||||||
|
|
||||||
let initial_points = [DVec2::new(100.0, 100.0), DVec2::new(200.0, 200.0), DVec2::new(300.0, 100.0)];
|
let initial_points = [DVec2::new(100., 100.), DVec2::new(200., 200.), DVec2::new(300., 100.)];
|
||||||
|
|
||||||
let first_point = initial_points[0];
|
let first_point = initial_points[0];
|
||||||
editor.move_mouse(first_point.x, first_point.y, ModifierKeys::empty(), MouseKeys::empty()).await;
|
editor.move_mouse(first_point.x, first_point.y, ModifierKeys::empty(), MouseKeys::empty()).await;
|
||||||
|
@ -599,7 +599,7 @@ mod test_freehand {
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let second_path_points = [DVec2::new(400.0, 100.0), DVec2::new(500.0, 200.0), DVec2::new(600.0, 100.0)];
|
let second_path_points = [DVec2::new(400., 100.), DVec2::new(500., 200.), DVec2::new(600., 100.)];
|
||||||
|
|
||||||
let first_second_point = second_path_points[0];
|
let first_second_point = second_path_points[0];
|
||||||
editor.move_mouse(first_second_point.x, first_second_point.y, ModifierKeys::SHIFT, MouseKeys::empty()).await;
|
editor.move_mouse(first_second_point.x, first_second_point.y, ModifierKeys::SHIFT, MouseKeys::empty()).await;
|
||||||
|
@ -677,12 +677,12 @@ mod test_freehand {
|
||||||
|
|
||||||
editor.select_tool(ToolType::Freehand).await;
|
editor.select_tool(ToolType::Freehand).await;
|
||||||
|
|
||||||
let custom_line_weight = 5.0;
|
let custom_line_weight = 5.;
|
||||||
editor
|
editor
|
||||||
.handle_message(ToolMessage::Freehand(FreehandToolMessage::UpdateOptions(FreehandOptionsUpdate::LineWeight(custom_line_weight))))
|
.handle_message(ToolMessage::Freehand(FreehandToolMessage::UpdateOptions(FreehandOptionsUpdate::LineWeight(custom_line_weight))))
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let points = [DVec2::new(100.0, 100.0), DVec2::new(200.0, 200.0), DVec2::new(300.0, 100.0)];
|
let points = [DVec2::new(100., 100.), DVec2::new(200., 200.), DVec2::new(300., 100.)];
|
||||||
|
|
||||||
let first_point = points[0];
|
let first_point = points[0];
|
||||||
editor.move_mouse(first_point.x, first_point.y, ModifierKeys::empty(), MouseKeys::empty()).await;
|
editor.move_mouse(first_point.x, first_point.y, ModifierKeys::empty(), MouseKeys::empty()).await;
|
||||||
|
|
|
@ -718,7 +718,7 @@ mod test_gradient {
|
||||||
let mut editor = EditorTestUtils::create();
|
let mut editor = EditorTestUtils::create();
|
||||||
editor.new_document().await;
|
editor.new_document().await;
|
||||||
|
|
||||||
editor.handle_message(NavigationMessage::CanvasZoomSet { zoom_factor: 2.0 }).await;
|
editor.handle_message(NavigationMessage::CanvasZoomSet { zoom_factor: 2. }).await;
|
||||||
|
|
||||||
editor.drag_tool(ToolType::Rectangle, -5., -3., 100., 100., ModifierKeys::empty()).await;
|
editor.drag_tool(ToolType::Rectangle, -5., -3., 100., 100., ModifierKeys::empty()).await;
|
||||||
|
|
||||||
|
@ -727,7 +727,7 @@ mod test_gradient {
|
||||||
editor
|
editor
|
||||||
.handle_message(GraphOperationMessage::TransformSet {
|
.handle_message(GraphOperationMessage::TransformSet {
|
||||||
layer: selected_layer,
|
layer: selected_layer,
|
||||||
transform: DAffine2::from_scale_angle_translation(DVec2::new(1.5, 0.8), 0.3, DVec2::new(10.0, -5.0)),
|
transform: DAffine2::from_scale_angle_translation(DVec2::new(1.5, 0.8), 0.3, DVec2::new(10., -5.)),
|
||||||
transform_in: TransformIn::Local,
|
transform_in: TransformIn::Local,
|
||||||
skip_rerender: false,
|
skip_rerender: false,
|
||||||
})
|
})
|
||||||
|
@ -803,7 +803,7 @@ mod test_gradient {
|
||||||
stops.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap());
|
stops.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap());
|
||||||
|
|
||||||
let positions: Vec<f64> = stops.iter().map(|(pos, _)| *pos).collect();
|
let positions: Vec<f64> = stops.iter().map(|(pos, _)| *pos).collect();
|
||||||
assert_stops_at_positions(&positions, &[0.0, 0.5, 1.0], 0.1);
|
assert_stops_at_positions(&positions, &[0., 0.5, 1.], 0.1);
|
||||||
|
|
||||||
let middle_color = stops[1].1.to_rgba8_srgb();
|
let middle_color = stops[1].1.to_rgba8_srgb();
|
||||||
|
|
||||||
|
@ -843,7 +843,7 @@ mod test_gradient {
|
||||||
|
|
||||||
// Check positions are now correctly ordered
|
// Check positions are now correctly ordered
|
||||||
let updated_positions: Vec<f64> = updated_stops.iter().map(|(pos, _)| *pos).collect();
|
let updated_positions: Vec<f64> = updated_stops.iter().map(|(pos, _)| *pos).collect();
|
||||||
assert_stops_at_positions(&updated_positions, &[0.0, 0.8, 1.0], 0.1);
|
assert_stops_at_positions(&updated_positions, &[0., 0.8, 1.], 0.1);
|
||||||
|
|
||||||
// Colors should maintain their associations with the stop points
|
// Colors should maintain their associations with the stop points
|
||||||
assert_eq!(updated_stops[0].1.to_rgba8_srgb(), Color::BLUE.to_rgba8_srgb());
|
assert_eq!(updated_stops[0].1.to_rgba8_srgb(), Color::BLUE.to_rgba8_srgb());
|
||||||
|
@ -877,7 +877,7 @@ mod test_gradient {
|
||||||
let positions: Vec<f64> = updated_gradient.stops.iter().map(|(pos, _)| *pos).collect();
|
let positions: Vec<f64> = updated_gradient.stops.iter().map(|(pos, _)| *pos).collect();
|
||||||
|
|
||||||
// Use helper function to verify positions
|
// Use helper function to verify positions
|
||||||
assert_stops_at_positions(&positions, &[0.0, 0.25, 0.75, 1.0], 0.05);
|
assert_stops_at_positions(&positions, &[0., 0.25, 0.75, 1.], 0.05);
|
||||||
|
|
||||||
// Select the stop at position 0.75 and delete it
|
// Select the stop at position 0.75 and delete it
|
||||||
let position2 = DVec2::new(75., 0.);
|
let position2 = DVec2::new(75., 0.);
|
||||||
|
@ -903,7 +903,7 @@ mod test_gradient {
|
||||||
let final_positions: Vec<f64> = final_gradient.stops.iter().map(|(pos, _)| *pos).collect();
|
let final_positions: Vec<f64> = final_gradient.stops.iter().map(|(pos, _)| *pos).collect();
|
||||||
|
|
||||||
// Verify final positions with helper function
|
// Verify final positions with helper function
|
||||||
assert_stops_at_positions(&final_positions, &[0.0, 0.25, 1.0], 0.05);
|
assert_stops_at_positions(&final_positions, &[0., 0.25, 1.], 0.05);
|
||||||
|
|
||||||
// Additional verification that 0.75 stop is gone
|
// Additional verification that 0.75 stop is gone
|
||||||
assert!(!final_positions.iter().any(|pos| (pos - 0.75).abs() < 0.05), "Stop at position 0.75 should have been deleted");
|
assert!(!final_positions.iter().any(|pos| (pos - 0.75).abs() < 0.05), "Stop at position 0.75 should have been deleted");
|
||||||
|
|
|
@ -6,13 +6,13 @@ use crate::messages::portfolio::document::utility_types::document_metadata::Laye
|
||||||
use crate::messages::portfolio::document::utility_types::network_interface::InputConnector;
|
use crate::messages::portfolio::document::utility_types::network_interface::InputConnector;
|
||||||
use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
|
use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
|
||||||
use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType};
|
use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType};
|
||||||
use crate::messages::tool::common_functionality::graph_modification_utils::{self, NodeGraphLayer};
|
use crate::messages::tool::common_functionality::gizmos::gizmo_manager::GizmoManager;
|
||||||
|
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||||
|
use crate::messages::tool::common_functionality::graph_modification_utils::NodeGraphLayer;
|
||||||
use crate::messages::tool::common_functionality::resize::Resize;
|
use crate::messages::tool::common_functionality::resize::Resize;
|
||||||
use crate::messages::tool::common_functionality::shape_gizmos::number_of_points_handle::{NumberOfPointsHandle, NumberOfPointsHandleState};
|
|
||||||
use crate::messages::tool::common_functionality::shape_gizmos::point_radius_handle::{PointRadiusHandle, PointRadiusHandleState};
|
|
||||||
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, polygon_outline, star_outline, transform_cage_overlays};
|
use crate::messages::tool::common_functionality::shapes::shape_utility::{ShapeToolModifierKey, ShapeType, anchor_overlays, transform_cage_overlays};
|
||||||
use crate::messages::tool::common_functionality::shapes::star_shape::Star;
|
use crate::messages::tool::common_functionality::shapes::star_shape::Star;
|
||||||
use crate::messages::tool::common_functionality::shapes::{Ellipse, Line, Rectangle};
|
use crate::messages::tool::common_functionality::shapes::{Ellipse, Line, Rectangle};
|
||||||
use crate::messages::tool::common_functionality::snapping::{self, SnapCandidatePoint, SnapData, SnapTypeConfiguration};
|
use crate::messages::tool::common_functionality::snapping::{self, SnapCandidatePoint, SnapData, SnapTypeConfiguration};
|
||||||
|
@ -22,6 +22,8 @@ use graph_craft::document::value::TaggedValue;
|
||||||
use graph_craft::document::{NodeId, NodeInput};
|
use graph_craft::document::{NodeId, NodeInput};
|
||||||
use graphene_std::Color;
|
use graphene_std::Color;
|
||||||
use graphene_std::renderer::Quad;
|
use graphene_std::renderer::Quad;
|
||||||
|
use graphene_std::vector::misc::ArcType;
|
||||||
|
use std::vec;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct ShapeTool {
|
pub struct ShapeTool {
|
||||||
|
@ -36,6 +38,7 @@ pub struct ShapeToolOptions {
|
||||||
stroke: ToolColorOptions,
|
stroke: ToolColorOptions,
|
||||||
vertices: u32,
|
vertices: u32,
|
||||||
shape_type: ShapeType,
|
shape_type: ShapeType,
|
||||||
|
arc_type: ArcType,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ShapeToolOptions {
|
impl Default for ShapeToolOptions {
|
||||||
|
@ -44,8 +47,9 @@ impl Default for ShapeToolOptions {
|
||||||
line_weight: DEFAULT_STROKE_WIDTH,
|
line_weight: DEFAULT_STROKE_WIDTH,
|
||||||
fill: ToolColorOptions::new_secondary(),
|
fill: ToolColorOptions::new_secondary(),
|
||||||
stroke: ToolColorOptions::new_primary(),
|
stroke: ToolColorOptions::new_primary(),
|
||||||
shape_type: ShapeType::Polygon,
|
|
||||||
vertices: 5,
|
vertices: 5,
|
||||||
|
shape_type: ShapeType::Polygon,
|
||||||
|
arc_type: ArcType::Open,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -60,6 +64,7 @@ pub enum ShapeOptionsUpdate {
|
||||||
WorkingColors(Option<Color>, Option<Color>),
|
WorkingColors(Option<Color>, Option<Color>),
|
||||||
Vertices(u32),
|
Vertices(u32),
|
||||||
ShapeType(ShapeType),
|
ShapeType(ShapeType),
|
||||||
|
ArcType(ArcType),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[impl_message(Message, ToolMessage, Shape)]
|
#[impl_message(Message, ToolMessage, Shape)]
|
||||||
|
@ -195,6 +200,9 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for ShapeTo
|
||||||
ShapeOptionsUpdate::Vertices(vertices) => {
|
ShapeOptionsUpdate::Vertices(vertices) => {
|
||||||
self.options.vertices = vertices;
|
self.options.vertices = vertices;
|
||||||
}
|
}
|
||||||
|
ShapeOptionsUpdate::ArcType(arc_type) => {
|
||||||
|
self.options.arc_type = arc_type;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.fsm_state.update_hints(responses);
|
self.fsm_state.update_hints(responses);
|
||||||
|
@ -217,8 +225,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for ShapeTo
|
||||||
| ShapeToolFsmState::ResizingBounds
|
| ShapeToolFsmState::ResizingBounds
|
||||||
| ShapeToolFsmState::DraggingLineEndpoints
|
| ShapeToolFsmState::DraggingLineEndpoints
|
||||||
| ShapeToolFsmState::RotatingBounds
|
| ShapeToolFsmState::RotatingBounds
|
||||||
| ShapeToolFsmState::DraggingStarInnerRadius
|
| ShapeToolFsmState::ModifyingGizmo
|
||||||
| ShapeToolFsmState::DraggingStarNumberPointHandle
|
|
||||||
| ShapeToolFsmState::SkewingBounds { .. } => {
|
| ShapeToolFsmState::SkewingBounds { .. } => {
|
||||||
actions!(ShapeToolMessageDiscriminant;
|
actions!(ShapeToolMessageDiscriminant;
|
||||||
DragStop,
|
DragStop,
|
||||||
|
@ -263,12 +270,9 @@ pub enum ShapeToolFsmState {
|
||||||
Ready(ShapeType),
|
Ready(ShapeType),
|
||||||
Drawing(ShapeType),
|
Drawing(ShapeType),
|
||||||
|
|
||||||
// Line shape-specific
|
// Gizmos
|
||||||
DraggingLineEndpoints,
|
DraggingLineEndpoints,
|
||||||
|
ModifyingGizmo,
|
||||||
// Star shape-specific
|
|
||||||
DraggingStarInnerRadius,
|
|
||||||
DraggingStarNumberPointHandle,
|
|
||||||
|
|
||||||
// Transform cage
|
// Transform cage
|
||||||
ResizingBounds,
|
ResizingBounds,
|
||||||
|
@ -306,9 +310,8 @@ pub struct ShapeToolData {
|
||||||
// Current shape which is being drawn
|
// Current shape which is being drawn
|
||||||
current_shape: ShapeType,
|
current_shape: ShapeType,
|
||||||
|
|
||||||
// Gizmo data
|
// Gizmos
|
||||||
pub point_radius_handle: PointRadiusHandle,
|
gizmo_manger: GizmoManager,
|
||||||
pub number_of_points_handle: NumberOfPointsHandle,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ShapeToolData {
|
impl ShapeToolData {
|
||||||
|
@ -324,26 +327,6 @@ impl ShapeToolData {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn outlines(&self, document: &DocumentMessageHandler, overlay_context: &mut OverlayContext) {
|
|
||||||
if let Some(layer) = self.number_of_points_handle.layer.or(self.point_radius_handle.layer) {
|
|
||||||
star_outline(layer, document, overlay_context);
|
|
||||||
polygon_outline(layer, document, overlay_context);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback: apply to all selected visible & unlocked star layers
|
|
||||||
for layer in document
|
|
||||||
.network_interface
|
|
||||||
.selected_nodes()
|
|
||||||
.selected_visible_and_unlocked_layers(&document.network_interface)
|
|
||||||
.filter(|layer| {
|
|
||||||
graph_modification_utils::get_star_id(*layer, &document.network_interface).is_some() || graph_modification_utils::get_polygon_id(*layer, &document.network_interface).is_some()
|
|
||||||
}) {
|
|
||||||
star_outline(layer, document, overlay_context);
|
|
||||||
polygon_outline(layer, document, overlay_context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Fsm for ShapeToolFsmState {
|
impl Fsm for ShapeToolFsmState {
|
||||||
|
@ -382,30 +365,24 @@ impl Fsm for ShapeToolFsmState {
|
||||||
.map(|pos| document.metadata().document_to_viewport.transform_point2(pos))
|
.map(|pos| document.metadata().document_to_viewport.transform_point2(pos))
|
||||||
.unwrap_or(input.mouse.position);
|
.unwrap_or(input.mouse.position);
|
||||||
let is_resizing_or_rotating = matches!(self, ShapeToolFsmState::ResizingBounds | ShapeToolFsmState::SkewingBounds { .. } | ShapeToolFsmState::RotatingBounds);
|
let is_resizing_or_rotating = matches!(self, ShapeToolFsmState::ResizingBounds | ShapeToolFsmState::SkewingBounds { .. } | ShapeToolFsmState::RotatingBounds);
|
||||||
let dragging_start_gizmos = matches!(self, Self::DraggingStarInnerRadius);
|
|
||||||
|
|
||||||
if matches!(self, ShapeToolFsmState::DraggingStarInnerRadius | Self::DraggingStarNumberPointHandle | Self::Ready(_)) && !input.keyboard.key(Key::Control) {
|
if matches!(self, Self::Ready(_)) && !input.keyboard.key(Key::Control) {
|
||||||
// Manage state handling of the number of point gizmos
|
tool_data.gizmo_manger.handle_actions(mouse_position, document, responses);
|
||||||
tool_data.number_of_points_handle.handle_actions(document, input, mouse_position, &mut overlay_context, responses);
|
tool_data.gizmo_manger.overlays(document, input, shape_editor, mouse_position, &mut overlay_context);
|
||||||
|
|
||||||
// Manage state handling of point radius handle gizmo
|
|
||||||
tool_data.point_radius_handle.handle_actions(document, mouse_position);
|
|
||||||
|
|
||||||
tool_data.number_of_points_handle.overlays(document, input, shape_editor, mouse_position, &mut overlay_context);
|
|
||||||
tool_data
|
|
||||||
.point_radius_handle
|
|
||||||
.overlays(tool_data.number_of_points_handle.layer.is_some(), document, input, mouse_position, &mut overlay_context);
|
|
||||||
tool_data.outlines(document, &mut overlay_context);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let hovered = tool_data.number_of_points_handle.is_hovering() || tool_data.number_of_points_handle.is_dragging() || !tool_data.point_radius_handle.is_inactive();
|
if matches!(self, ShapeToolFsmState::ModifyingGizmo) && !input.keyboard.key(Key::Control) {
|
||||||
let modifying_transform_cage = matches!(self, ShapeToolFsmState::ResizingBounds | ShapeToolFsmState::RotatingBounds | ShapeToolFsmState::SkewingBounds { .. });
|
tool_data.gizmo_manger.dragging_overlays(document, input, shape_editor, mouse_position, &mut overlay_context);
|
||||||
|
}
|
||||||
|
|
||||||
if !is_resizing_or_rotating && !dragging_start_gizmos && !hovered && !modifying_transform_cage {
|
let modifying_transform_cage = matches!(self, ShapeToolFsmState::ResizingBounds | ShapeToolFsmState::RotatingBounds | ShapeToolFsmState::SkewingBounds { .. });
|
||||||
|
let hovering_over_gizmo = tool_data.gizmo_manger.hovering_over_gizmo();
|
||||||
|
|
||||||
|
if !is_resizing_or_rotating && !matches!(self, ShapeToolFsmState::ModifyingGizmo) && !modifying_transform_cage && !hovering_over_gizmo {
|
||||||
tool_data.data.snap_manager.draw_overlays(SnapData::new(document, input), &mut overlay_context);
|
tool_data.data.snap_manager.draw_overlays(SnapData::new(document, input), &mut overlay_context);
|
||||||
}
|
}
|
||||||
|
|
||||||
if modifying_transform_cage {
|
if modifying_transform_cage && !matches!(self, ShapeToolFsmState::ModifyingGizmo) {
|
||||||
transform_cage_overlays(document, tool_data, &mut overlay_context);
|
transform_cage_overlays(document, tool_data, &mut overlay_context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -418,7 +395,9 @@ impl Fsm for ShapeToolFsmState {
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
transform_cage_overlays(document, tool_data, &mut overlay_context);
|
if !hovering_over_gizmo {
|
||||||
|
transform_cage_overlays(document, tool_data, &mut overlay_context);
|
||||||
|
}
|
||||||
|
|
||||||
let dragging_bounds = tool_data
|
let dragging_bounds = tool_data
|
||||||
.bounding_box_manager
|
.bounding_box_manager
|
||||||
|
@ -430,10 +409,10 @@ impl Fsm for ShapeToolFsmState {
|
||||||
let edges = bounds.check_selected_edges(input.mouse.position);
|
let edges = bounds.check_selected_edges(input.mouse.position);
|
||||||
let is_skewing = matches!(self, ShapeToolFsmState::SkewingBounds { .. });
|
let is_skewing = matches!(self, ShapeToolFsmState::SkewingBounds { .. });
|
||||||
let is_near_square = edges.is_some_and(|hover_edge| bounds.over_extended_edge_midpoint(input.mouse.position, hover_edge));
|
let is_near_square = edges.is_some_and(|hover_edge| bounds.over_extended_edge_midpoint(input.mouse.position, hover_edge));
|
||||||
if is_skewing || (dragging_bounds && is_near_square && !is_resizing_or_rotating) {
|
if is_skewing || (dragging_bounds && is_near_square && !is_resizing_or_rotating && !hovering_over_gizmo) {
|
||||||
bounds.render_skew_gizmos(&mut overlay_context, tool_data.skew_edge);
|
bounds.render_skew_gizmos(&mut overlay_context, tool_data.skew_edge);
|
||||||
}
|
}
|
||||||
if !is_skewing && dragging_bounds {
|
if !is_skewing && dragging_bounds && !hovering_over_gizmo {
|
||||||
if let Some(edges) = edges {
|
if let Some(edges) = edges {
|
||||||
tool_data.skew_edge = bounds.get_closest_edge(edges, input.mouse.position);
|
tool_data.skew_edge = bounds.get_closest_edge(edges, input.mouse.position);
|
||||||
}
|
}
|
||||||
|
@ -559,28 +538,9 @@ impl Fsm for ShapeToolFsmState {
|
||||||
|
|
||||||
tool_data.line_data.drag_current = mouse_pos;
|
tool_data.line_data.drag_current = mouse_pos;
|
||||||
|
|
||||||
// Check if dragging the inner vertices of a star
|
if tool_data.gizmo_manger.handle_click() {
|
||||||
if tool_data.point_radius_handle.hovered() {
|
|
||||||
tool_data.last_mouse_position = mouse_pos;
|
|
||||||
tool_data.point_radius_handle.update_state(PointRadiusHandleState::Dragging);
|
|
||||||
|
|
||||||
// Always store it in document space
|
|
||||||
tool_data.data.drag_start = document.metadata().document_to_viewport.inverse().transform_point2(mouse_pos);
|
tool_data.data.drag_start = document.metadata().document_to_viewport.inverse().transform_point2(mouse_pos);
|
||||||
|
return ShapeToolFsmState::ModifyingGizmo;
|
||||||
responses.add(DocumentMessage::StartTransaction);
|
|
||||||
return ShapeToolFsmState::DraggingStarInnerRadius;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if dragging the number of points handle of a star or polygon
|
|
||||||
if tool_data.number_of_points_handle.is_hovering() {
|
|
||||||
tool_data.last_mouse_position = mouse_pos;
|
|
||||||
tool_data.number_of_points_handle.update_state(NumberOfPointsHandleState::Dragging);
|
|
||||||
|
|
||||||
// Always store it in document space
|
|
||||||
tool_data.data.drag_start = document.metadata().document_to_viewport.inverse().transform_point2(mouse_pos);
|
|
||||||
|
|
||||||
responses.add(DocumentMessage::StartTransaction);
|
|
||||||
return ShapeToolFsmState::DraggingStarNumberPointHandle;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If clicked on endpoints of a selected line, drag its endpoints
|
// If clicked on endpoints of a selected line, drag its endpoints
|
||||||
|
@ -653,13 +613,13 @@ impl Fsm for ShapeToolFsmState {
|
||||||
tool_options.fill.apply_fill(layer, responses);
|
tool_options.fill.apply_fill(layer, responses);
|
||||||
}
|
}
|
||||||
ShapeType::Line => {
|
ShapeType::Line => {
|
||||||
tool_data.line_data.angle = 0.;
|
|
||||||
tool_data.line_data.weight = tool_options.line_weight;
|
tool_data.line_data.weight = tool_options.line_weight;
|
||||||
tool_data.line_data.editing_layer = Some(layer);
|
tool_data.line_data.editing_layer = Some(layer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tool_options.stroke.apply_stroke(tool_options.line_weight, layer, responses);
|
tool_options.stroke.apply_stroke(tool_options.line_weight, layer, responses);
|
||||||
|
|
||||||
|
tool_options.stroke.apply_stroke(tool_options.line_weight, layer, responses);
|
||||||
tool_data.data.layer = Some(layer);
|
tool_data.data.layer = Some(layer);
|
||||||
|
|
||||||
ShapeToolFsmState::Drawing(tool_data.current_shape)
|
ShapeToolFsmState::Drawing(tool_data.current_shape)
|
||||||
|
@ -695,23 +655,13 @@ impl Fsm for ShapeToolFsmState {
|
||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
(ShapeToolFsmState::DraggingStarInnerRadius, ShapeToolMessage::PointerMove(..)) => {
|
(ShapeToolFsmState::ModifyingGizmo, ShapeToolMessage::PointerMove(..)) => {
|
||||||
if let Some(layer) = tool_data.point_radius_handle.layer {
|
responses.add(DocumentMessage::StartTransaction);
|
||||||
tool_data.point_radius_handle.update_inner_radius(document, input, layer, responses, tool_data.data.drag_start);
|
tool_data.gizmo_manger.handle_update(tool_data.data.drag_start, document, input, responses);
|
||||||
tool_data.last_mouse_position = input.mouse.position;
|
|
||||||
}
|
|
||||||
|
|
||||||
responses.add(OverlaysMessage::Draw);
|
responses.add(OverlaysMessage::Draw);
|
||||||
|
|
||||||
ShapeToolFsmState::DraggingStarInnerRadius
|
ShapeToolFsmState::ModifyingGizmo
|
||||||
}
|
|
||||||
(ShapeToolFsmState::DraggingStarNumberPointHandle, ShapeToolMessage::PointerMove(..)) => {
|
|
||||||
tool_data.number_of_points_handle.update_number_of_sides(document, input, responses, tool_data.data.drag_start);
|
|
||||||
|
|
||||||
tool_data.last_mouse_position = input.mouse.position;
|
|
||||||
responses.add(OverlaysMessage::Draw);
|
|
||||||
|
|
||||||
ShapeToolFsmState::DraggingStarNumberPointHandle
|
|
||||||
}
|
}
|
||||||
(ShapeToolFsmState::ResizingBounds, ShapeToolMessage::PointerMove(modifier)) => {
|
(ShapeToolFsmState::ResizingBounds, ShapeToolMessage::PointerMove(modifier)) => {
|
||||||
if let Some(bounds) = &mut tool_data.bounding_box_manager {
|
if let Some(bounds) = &mut tool_data.bounding_box_manager {
|
||||||
|
@ -773,12 +723,12 @@ impl Fsm for ShapeToolFsmState {
|
||||||
.and_then(|bounding_box| bounding_box.check_selected_edges(input.mouse.position))
|
.and_then(|bounding_box| bounding_box.check_selected_edges(input.mouse.position))
|
||||||
.is_some();
|
.is_some();
|
||||||
|
|
||||||
let cursor = tool_data
|
let cursor = tool_data.bounding_box_manager.as_ref().map_or(MouseCursorIcon::Crosshair, |bounds| {
|
||||||
.bounding_box_manager
|
let cursor = bounds.get_cursor(input, true, dragging_bounds, Some(tool_data.skew_edge));
|
||||||
.as_ref()
|
if cursor == MouseCursorIcon::Default { MouseCursorIcon::Crosshair } else { cursor }
|
||||||
.map_or(MouseCursorIcon::Default, |bounds| bounds.get_cursor(input, true, dragging_bounds, Some(tool_data.skew_edge)));
|
});
|
||||||
|
|
||||||
if tool_data.cursor != cursor && !input.keyboard.key(Key::Control) && tool_data.point_radius_handle.is_inactive() && !all_selected_layers_line {
|
if tool_data.cursor != cursor && !input.keyboard.key(Key::Control) && !all_selected_layers_line {
|
||||||
tool_data.cursor = cursor;
|
tool_data.cursor = cursor;
|
||||||
responses.add(FrontendMessage::UpdateMouseCursor { cursor });
|
responses.add(FrontendMessage::UpdateMouseCursor { cursor });
|
||||||
}
|
}
|
||||||
|
@ -811,15 +761,13 @@ impl Fsm for ShapeToolFsmState {
|
||||||
| ShapeToolFsmState::ResizingBounds
|
| ShapeToolFsmState::ResizingBounds
|
||||||
| ShapeToolFsmState::RotatingBounds
|
| ShapeToolFsmState::RotatingBounds
|
||||||
| ShapeToolFsmState::SkewingBounds { .. }
|
| ShapeToolFsmState::SkewingBounds { .. }
|
||||||
| ShapeToolFsmState::DraggingStarInnerRadius
|
| ShapeToolFsmState::ModifyingGizmo,
|
||||||
| ShapeToolFsmState::DraggingStarNumberPointHandle,
|
|
||||||
ShapeToolMessage::DragStop,
|
ShapeToolMessage::DragStop,
|
||||||
) => {
|
) => {
|
||||||
input.mouse.finish_transaction(tool_data.data.drag_start, responses);
|
input.mouse.finish_transaction(tool_data.data.drag_start, responses);
|
||||||
tool_data.data.cleanup(responses);
|
tool_data.data.cleanup(responses);
|
||||||
|
|
||||||
tool_data.number_of_points_handle.cleanup();
|
tool_data.gizmo_manger.handle_cleanup();
|
||||||
tool_data.point_radius_handle.cleanup();
|
|
||||||
|
|
||||||
if let Some(bounds) = &mut tool_data.bounding_box_manager {
|
if let Some(bounds) = &mut tool_data.bounding_box_manager {
|
||||||
bounds.original_transforms.clear();
|
bounds.original_transforms.clear();
|
||||||
|
@ -837,17 +785,14 @@ impl Fsm for ShapeToolFsmState {
|
||||||
| ShapeToolFsmState::ResizingBounds
|
| ShapeToolFsmState::ResizingBounds
|
||||||
| ShapeToolFsmState::RotatingBounds
|
| ShapeToolFsmState::RotatingBounds
|
||||||
| ShapeToolFsmState::SkewingBounds { .. }
|
| ShapeToolFsmState::SkewingBounds { .. }
|
||||||
| ShapeToolFsmState::DraggingStarInnerRadius
|
| ShapeToolFsmState::ModifyingGizmo,
|
||||||
| ShapeToolFsmState::DraggingStarNumberPointHandle,
|
|
||||||
ShapeToolMessage::Abort,
|
ShapeToolMessage::Abort,
|
||||||
) => {
|
) => {
|
||||||
responses.add(DocumentMessage::AbortTransaction);
|
responses.add(DocumentMessage::AbortTransaction);
|
||||||
tool_data.data.cleanup(responses);
|
tool_data.data.cleanup(responses);
|
||||||
tool_data.line_data.dragging_endpoint = None;
|
tool_data.line_data.dragging_endpoint = None;
|
||||||
|
|
||||||
// Reset gizmo state
|
tool_data.gizmo_manger.handle_cleanup();
|
||||||
tool_data.number_of_points_handle.cleanup();
|
|
||||||
tool_data.point_radius_handle.cleanup();
|
|
||||||
|
|
||||||
if let Some(bounds) = &mut tool_data.bounding_box_manager {
|
if let Some(bounds) = &mut tool_data.bounding_box_manager {
|
||||||
bounds.original_transforms.clear();
|
bounds.original_transforms.clear();
|
||||||
|
@ -952,9 +897,7 @@ impl Fsm for ShapeToolFsmState {
|
||||||
HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]),
|
HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]),
|
||||||
HintGroup(vec![HintInfo::keys([Key::Control], "Unlock Slide")]),
|
HintGroup(vec![HintInfo::keys([Key::Control], "Unlock Slide")]),
|
||||||
]),
|
]),
|
||||||
ShapeToolFsmState::DraggingStarInnerRadius | ShapeToolFsmState::DraggingStarNumberPointHandle => {
|
ShapeToolFsmState::ModifyingGizmo => HintData(vec![HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()])]),
|
||||||
HintData(vec![HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()])])
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
responses.add(FrontendMessage::UpdateInputHints { hint_data });
|
responses.add(FrontendMessage::UpdateInputHints { hint_data });
|
||||||
|
|
|
@ -656,15 +656,15 @@ mod test_spline_tool {
|
||||||
editor.new_document().await;
|
editor.new_document().await;
|
||||||
|
|
||||||
// Zooming the viewport
|
// Zooming the viewport
|
||||||
editor.handle_message(NavigationMessage::CanvasZoomSet { zoom_factor: 2.0 }).await;
|
editor.handle_message(NavigationMessage::CanvasZoomSet { zoom_factor: 2. }).await;
|
||||||
|
|
||||||
// Selecting the spline tool
|
// Selecting the spline tool
|
||||||
editor.select_tool(ToolType::Spline).await;
|
editor.select_tool(ToolType::Spline).await;
|
||||||
|
|
||||||
// Adding points by clicking at different positions
|
// Adding points by clicking at different positions
|
||||||
editor.click_tool(ToolType::Spline, MouseKeys::LEFT, DVec2::new(50.0, 50.0), ModifierKeys::empty()).await;
|
editor.click_tool(ToolType::Spline, MouseKeys::LEFT, DVec2::new(50., 50.), ModifierKeys::empty()).await;
|
||||||
editor.click_tool(ToolType::Spline, MouseKeys::LEFT, DVec2::new(100.0, 50.0), ModifierKeys::empty()).await;
|
editor.click_tool(ToolType::Spline, MouseKeys::LEFT, DVec2::new(100., 50.), ModifierKeys::empty()).await;
|
||||||
editor.click_tool(ToolType::Spline, MouseKeys::LEFT, DVec2::new(150.0, 100.0), ModifierKeys::empty()).await;
|
editor.click_tool(ToolType::Spline, MouseKeys::LEFT, DVec2::new(150., 100.), ModifierKeys::empty()).await;
|
||||||
|
|
||||||
// Finish the spline
|
// Finish the spline
|
||||||
editor.handle_message(SplineToolMessage::Confirm).await;
|
editor.handle_message(SplineToolMessage::Confirm).await;
|
||||||
|
@ -686,7 +686,7 @@ mod test_spline_tool {
|
||||||
let layer_to_viewport = document.metadata().transform_to_viewport(layer);
|
let layer_to_viewport = document.metadata().transform_to_viewport(layer);
|
||||||
|
|
||||||
// Expected points in viewport coordinates
|
// Expected points in viewport coordinates
|
||||||
let expected_points = vec![DVec2::new(50.0, 50.0), DVec2::new(100.0, 50.0), DVec2::new(150.0, 100.0)];
|
let expected_points = vec![DVec2::new(50., 50.), DVec2::new(100., 50.), DVec2::new(150., 100.)];
|
||||||
|
|
||||||
// Assert all points are correctly positioned
|
// Assert all points are correctly positioned
|
||||||
assert_point_positions(&vector_data, layer_to_viewport, &expected_points, 1e-10);
|
assert_point_positions(&vector_data, layer_to_viewport, &expected_points, 1e-10);
|
||||||
|
@ -697,15 +697,15 @@ mod test_spline_tool {
|
||||||
let mut editor = EditorTestUtils::create();
|
let mut editor = EditorTestUtils::create();
|
||||||
editor.new_document().await;
|
editor.new_document().await;
|
||||||
|
|
||||||
let pan_amount = DVec2::new(200.0, 150.0);
|
let pan_amount = DVec2::new(200., 150.);
|
||||||
editor.handle_message(NavigationMessage::CanvasPan { delta: pan_amount }).await;
|
editor.handle_message(NavigationMessage::CanvasPan { delta: pan_amount }).await;
|
||||||
|
|
||||||
editor.select_tool(ToolType::Spline).await;
|
editor.select_tool(ToolType::Spline).await;
|
||||||
|
|
||||||
// Add points by clicking at different positions
|
// Add points by clicking at different positions
|
||||||
editor.click_tool(ToolType::Spline, MouseKeys::LEFT, DVec2::new(50.0, 50.0), ModifierKeys::empty()).await;
|
editor.click_tool(ToolType::Spline, MouseKeys::LEFT, DVec2::new(50., 50.), ModifierKeys::empty()).await;
|
||||||
editor.click_tool(ToolType::Spline, MouseKeys::LEFT, DVec2::new(100.0, 50.0), ModifierKeys::empty()).await;
|
editor.click_tool(ToolType::Spline, MouseKeys::LEFT, DVec2::new(100., 50.), ModifierKeys::empty()).await;
|
||||||
editor.click_tool(ToolType::Spline, MouseKeys::LEFT, DVec2::new(150.0, 100.0), ModifierKeys::empty()).await;
|
editor.click_tool(ToolType::Spline, MouseKeys::LEFT, DVec2::new(150., 100.), ModifierKeys::empty()).await;
|
||||||
|
|
||||||
editor.handle_message(SplineToolMessage::Confirm).await;
|
editor.handle_message(SplineToolMessage::Confirm).await;
|
||||||
|
|
||||||
|
@ -726,7 +726,7 @@ mod test_spline_tool {
|
||||||
let layer_to_viewport = document.metadata().transform_to_viewport(layer);
|
let layer_to_viewport = document.metadata().transform_to_viewport(layer);
|
||||||
|
|
||||||
// Expected points in viewport coordinates
|
// Expected points in viewport coordinates
|
||||||
let expected_points = vec![DVec2::new(50.0, 50.0), DVec2::new(100.0, 50.0), DVec2::new(150.0, 100.0)];
|
let expected_points = vec![DVec2::new(50., 50.), DVec2::new(100., 50.), DVec2::new(150., 100.)];
|
||||||
|
|
||||||
// Assert all points are correctly positioned
|
// Assert all points are correctly positioned
|
||||||
assert_point_positions(&vector_data, layer_to_viewport, &expected_points, 1e-10);
|
assert_point_positions(&vector_data, layer_to_viewport, &expected_points, 1e-10);
|
||||||
|
@ -738,12 +738,12 @@ mod test_spline_tool {
|
||||||
editor.new_document().await;
|
editor.new_document().await;
|
||||||
|
|
||||||
// Tilt/rotate the viewport (45 degrees)
|
// Tilt/rotate the viewport (45 degrees)
|
||||||
editor.handle_message(NavigationMessage::CanvasTiltSet { angle_radians: 45.0_f64.to_radians() }).await;
|
editor.handle_message(NavigationMessage::CanvasTiltSet { angle_radians: 45_f64.to_radians() }).await;
|
||||||
editor.select_tool(ToolType::Spline).await;
|
editor.select_tool(ToolType::Spline).await;
|
||||||
|
|
||||||
editor.click_tool(ToolType::Spline, MouseKeys::LEFT, DVec2::new(50.0, 50.0), ModifierKeys::empty()).await;
|
editor.click_tool(ToolType::Spline, MouseKeys::LEFT, DVec2::new(50., 50.), ModifierKeys::empty()).await;
|
||||||
editor.click_tool(ToolType::Spline, MouseKeys::LEFT, DVec2::new(100.0, 50.0), ModifierKeys::empty()).await;
|
editor.click_tool(ToolType::Spline, MouseKeys::LEFT, DVec2::new(100., 50.), ModifierKeys::empty()).await;
|
||||||
editor.click_tool(ToolType::Spline, MouseKeys::LEFT, DVec2::new(150.0, 100.0), ModifierKeys::empty()).await;
|
editor.click_tool(ToolType::Spline, MouseKeys::LEFT, DVec2::new(150., 100.), ModifierKeys::empty()).await;
|
||||||
|
|
||||||
editor.handle_message(SplineToolMessage::Confirm).await;
|
editor.handle_message(SplineToolMessage::Confirm).await;
|
||||||
|
|
||||||
|
@ -764,7 +764,7 @@ mod test_spline_tool {
|
||||||
let layer_to_viewport = document.metadata().transform_to_viewport(layer);
|
let layer_to_viewport = document.metadata().transform_to_viewport(layer);
|
||||||
|
|
||||||
// Expected points in viewport coordinates
|
// Expected points in viewport coordinates
|
||||||
let expected_points = vec![DVec2::new(50.0, 50.0), DVec2::new(100.0, 50.0), DVec2::new(150.0, 100.0)];
|
let expected_points = vec![DVec2::new(50., 50.), DVec2::new(100., 50.), DVec2::new(150., 100.)];
|
||||||
|
|
||||||
// Assert all points are correctly positioned
|
// Assert all points are correctly positioned
|
||||||
assert_point_positions(&vector_data, layer_to_viewport, &expected_points, 1e-10);
|
assert_point_positions(&vector_data, layer_to_viewport, &expected_points, 1e-10);
|
||||||
|
@ -777,14 +777,14 @@ mod test_spline_tool {
|
||||||
|
|
||||||
// Applying multiple transformations
|
// Applying multiple transformations
|
||||||
editor.handle_message(NavigationMessage::CanvasZoomSet { zoom_factor: 1.5 }).await;
|
editor.handle_message(NavigationMessage::CanvasZoomSet { zoom_factor: 1.5 }).await;
|
||||||
editor.handle_message(NavigationMessage::CanvasPan { delta: DVec2::new(100.0, 75.0) }).await;
|
editor.handle_message(NavigationMessage::CanvasPan { delta: DVec2::new(100., 75.) }).await;
|
||||||
editor.handle_message(NavigationMessage::CanvasTiltSet { angle_radians: 30.0_f64.to_radians() }).await;
|
editor.handle_message(NavigationMessage::CanvasTiltSet { angle_radians: 30_f64.to_radians() }).await;
|
||||||
|
|
||||||
editor.select_tool(ToolType::Spline).await;
|
editor.select_tool(ToolType::Spline).await;
|
||||||
|
|
||||||
editor.click_tool(ToolType::Spline, MouseKeys::LEFT, DVec2::new(50.0, 50.0), ModifierKeys::empty()).await;
|
editor.click_tool(ToolType::Spline, MouseKeys::LEFT, DVec2::new(50., 50.), ModifierKeys::empty()).await;
|
||||||
editor.click_tool(ToolType::Spline, MouseKeys::LEFT, DVec2::new(100.0, 50.0), ModifierKeys::empty()).await;
|
editor.click_tool(ToolType::Spline, MouseKeys::LEFT, DVec2::new(100., 50.), ModifierKeys::empty()).await;
|
||||||
editor.click_tool(ToolType::Spline, MouseKeys::LEFT, DVec2::new(150.0, 100.0), ModifierKeys::empty()).await;
|
editor.click_tool(ToolType::Spline, MouseKeys::LEFT, DVec2::new(150., 100.), ModifierKeys::empty()).await;
|
||||||
|
|
||||||
editor.handle_message(SplineToolMessage::Confirm).await;
|
editor.handle_message(SplineToolMessage::Confirm).await;
|
||||||
if let Err(e) = editor.eval_graph().await {
|
if let Err(e) = editor.eval_graph().await {
|
||||||
|
@ -803,7 +803,7 @@ mod test_spline_tool {
|
||||||
let layer_to_viewport = document.metadata().transform_to_viewport(layer);
|
let layer_to_viewport = document.metadata().transform_to_viewport(layer);
|
||||||
|
|
||||||
// Expected points in viewport coordinates
|
// Expected points in viewport coordinates
|
||||||
let expected_points = vec![DVec2::new(50.0, 50.0), DVec2::new(100.0, 50.0), DVec2::new(150.0, 100.0)];
|
let expected_points = vec![DVec2::new(50., 50.), DVec2::new(100., 50.), DVec2::new(150., 100.)];
|
||||||
|
|
||||||
// Assert all points are correctly positioned
|
// Assert all points are correctly positioned
|
||||||
assert_point_positions(&vector_data, layer_to_viewport, &expected_points, 1e-10);
|
assert_point_positions(&vector_data, layer_to_viewport, &expected_points, 1e-10);
|
||||||
|
|
|
@ -1021,8 +1021,8 @@ mod test_transform_layer {
|
||||||
let scale_x = final_transform.matrix2.x_axis.length() / original_transform.matrix2.x_axis.length();
|
let scale_x = final_transform.matrix2.x_axis.length() / original_transform.matrix2.x_axis.length();
|
||||||
let scale_y = final_transform.matrix2.y_axis.length() / original_transform.matrix2.y_axis.length();
|
let scale_y = final_transform.matrix2.y_axis.length() / original_transform.matrix2.y_axis.length();
|
||||||
|
|
||||||
assert!((scale_x - 2.).abs() < 0.1, "Expected scale factor X of 2.0, got: {}", scale_x);
|
assert!((scale_x - 2.).abs() < 0.1, "Expected scale factor X of 2, got: {}", scale_x);
|
||||||
assert!((scale_y - 2.).abs() < 0.1, "Expected scale factor Y of 2.0, got: {}", scale_y);
|
assert!((scale_y - 2.).abs() < 0.1, "Expected scale factor Y of 2, got: {}", scale_y);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
@ -1047,8 +1047,8 @@ mod test_transform_layer {
|
||||||
let scale_x = final_transform.matrix2.x_axis.length() / original_transform.matrix2.x_axis.length();
|
let scale_x = final_transform.matrix2.x_axis.length() / original_transform.matrix2.x_axis.length();
|
||||||
let scale_y = final_transform.matrix2.y_axis.length() / original_transform.matrix2.y_axis.length();
|
let scale_y = final_transform.matrix2.y_axis.length() / original_transform.matrix2.y_axis.length();
|
||||||
|
|
||||||
assert!((scale_x - 2.).abs() < 0.1, "Expected scale factor X of 2.0, got: {}", scale_x);
|
assert!((scale_x - 2.).abs() < 0.1, "Expected scale factor X of 2, got: {}", scale_x);
|
||||||
assert!((scale_y - 2.).abs() < 0.1, "Expected scale factor Y of 2.0, got: {}", scale_y);
|
assert!((scale_y - 2.).abs() < 0.1, "Expected scale factor Y of 2, got: {}", scale_y);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
|
|
@ -119,12 +119,12 @@ fn star<T: AsU64>(
|
||||||
#[hard_min(2.)]
|
#[hard_min(2.)]
|
||||||
#[implementations(u32, u64, f64)]
|
#[implementations(u32, u64, f64)]
|
||||||
sides: T,
|
sides: T,
|
||||||
#[default(50)] radius: f64,
|
#[default(50)] radius_1: f64,
|
||||||
#[default(25)] inner_radius: f64,
|
#[default(25)] radius_2: f64,
|
||||||
) -> VectorDataTable {
|
) -> VectorDataTable {
|
||||||
let points = sides.as_u64();
|
let points = sides.as_u64();
|
||||||
let diameter: f64 = radius * 2.;
|
let diameter: f64 = radius_1 * 2.;
|
||||||
let inner_diameter = inner_radius * 2.;
|
let inner_diameter = radius_2 * 2.;
|
||||||
|
|
||||||
VectorDataTable::new(VectorData::from_subpath(Subpath::new_star_polygon(DVec2::splat(-diameter), points, diameter, inner_diameter)))
|
VectorDataTable::new(VectorData::from_subpath(Subpath::new_star_polygon(DVec2::splat(-diameter), points, diameter, inner_diameter)))
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue