mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-17 07:18:04 +08:00
moved line gizmos into gizmo manager
This commit is contained in:
@@ -7,9 +7,11 @@ use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||
use crate::messages::tool::common_functionality::shape_editor::ShapeState;
|
||||
use crate::messages::tool::common_functionality::shapes::arc_shape::ArcGizmoHandler;
|
||||
use crate::messages::tool::common_functionality::shapes::circle_shape::CircleGizmoHandler;
|
||||
use crate::messages::tool::common_functionality::shapes::line_shape::LineGizmoHandler;
|
||||
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 crate::messages::tool::common_functionality::snapping::SnapManager;
|
||||
use glam::DVec2;
|
||||
use std::collections::VecDeque;
|
||||
|
||||
@@ -24,6 +26,7 @@ use std::collections::VecDeque;
|
||||
pub enum ShapeGizmoHandlers {
|
||||
#[default]
|
||||
None,
|
||||
Line(LineGizmoHandler),
|
||||
Star(StarGizmoHandler),
|
||||
Polygon(PolygonGizmoHandler),
|
||||
Arc(ArcGizmoHandler),
|
||||
@@ -35,6 +38,7 @@ impl ShapeGizmoHandlers {
|
||||
/// Used for grouping logic and distinguishing between handler types at runtime.
|
||||
pub fn kind(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Line(_) => "Line",
|
||||
Self::Star(_) => "star",
|
||||
Self::Polygon(_) => "polygon",
|
||||
Self::Arc(_) => "arc",
|
||||
@@ -44,12 +48,13 @@ impl ShapeGizmoHandlers {
|
||||
}
|
||||
|
||||
/// 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>) {
|
||||
pub fn handle_state(&mut self, layer: LayerNodeIdentifier, mouse_position: DVec2, document: &DocumentMessageHandler) {
|
||||
match self {
|
||||
Self::Star(h) => h.handle_state(layer, mouse_position, document, responses),
|
||||
Self::Polygon(h) => h.handle_state(layer, mouse_position, document, responses),
|
||||
Self::Arc(h) => h.handle_state(layer, mouse_position, document, responses),
|
||||
Self::Circle(h) => h.handle_state(layer, mouse_position, document, responses),
|
||||
Self::Line(h) => h.handle_state(layer, mouse_position, document),
|
||||
Self::Star(h) => h.handle_state(layer, mouse_position, document),
|
||||
Self::Polygon(h) => h.handle_state(layer, mouse_position, document),
|
||||
Self::Arc(h) => h.handle_state(layer, mouse_position, document),
|
||||
Self::Circle(h) => h.handle_state(layer, mouse_position, document),
|
||||
Self::None => {}
|
||||
}
|
||||
}
|
||||
@@ -57,6 +62,7 @@ impl ShapeGizmoHandlers {
|
||||
/// Checks if any interactive part of the gizmo is currently hovered.
|
||||
pub fn is_any_gizmo_hovered(&self) -> bool {
|
||||
match self {
|
||||
Self::Line(h) => h.is_any_gizmo_hovered(),
|
||||
Self::Star(h) => h.is_any_gizmo_hovered(),
|
||||
Self::Polygon(h) => h.is_any_gizmo_hovered(),
|
||||
Self::Arc(h) => h.is_any_gizmo_hovered(),
|
||||
@@ -68,6 +74,7 @@ impl ShapeGizmoHandlers {
|
||||
/// Passes the click interaction to the appropriate gizmo handler if one is hovered.
|
||||
pub fn handle_click(&mut self) {
|
||||
match self {
|
||||
Self::Line(h) => h.handle_click(),
|
||||
Self::Star(h) => h.handle_click(),
|
||||
Self::Polygon(h) => h.handle_click(),
|
||||
Self::Arc(h) => h.handle_click(),
|
||||
@@ -77,12 +84,13 @@ impl ShapeGizmoHandlers {
|
||||
}
|
||||
|
||||
/// 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>) {
|
||||
pub fn handle_update(&mut self, drag_start: DVec2, snap_manager: &mut SnapManager, 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::Arc(h) => h.handle_update(drag_start, document, input, responses),
|
||||
Self::Circle(h) => h.handle_update(drag_start, document, input, responses),
|
||||
Self::Line(h) => h.handle_update(drag_start, snap_manager, document, input, responses),
|
||||
Self::Star(h) => h.handle_update(drag_start, snap_manager, document, input, responses),
|
||||
Self::Polygon(h) => h.handle_update(drag_start, snap_manager, document, input, responses),
|
||||
Self::Arc(h) => h.handle_update(drag_start, snap_manager, document, input, responses),
|
||||
Self::Circle(h) => h.handle_update(drag_start, snap_manager, document, input, responses),
|
||||
Self::None => {}
|
||||
}
|
||||
}
|
||||
@@ -90,6 +98,7 @@ impl ShapeGizmoHandlers {
|
||||
/// Cleans up any state used by the gizmo handler.
|
||||
pub fn cleanup(&mut self) {
|
||||
match self {
|
||||
Self::Line(h) => h.cleanup(),
|
||||
Self::Star(h) => h.cleanup(),
|
||||
Self::Polygon(h) => h.cleanup(),
|
||||
Self::Arc(h) => h.cleanup(),
|
||||
@@ -109,6 +118,7 @@ impl ShapeGizmoHandlers {
|
||||
overlay_context: &mut OverlayContext,
|
||||
) {
|
||||
match self {
|
||||
Self::Line(h) => h.overlays(document, layer, input, shape_editor, mouse_position, overlay_context),
|
||||
Self::Star(h) => h.overlays(document, layer, input, shape_editor, mouse_position, overlay_context),
|
||||
Self::Polygon(h) => h.overlays(document, layer, input, shape_editor, mouse_position, overlay_context),
|
||||
Self::Arc(h) => h.overlays(document, layer, input, shape_editor, mouse_position, overlay_context),
|
||||
@@ -127,6 +137,7 @@ impl ShapeGizmoHandlers {
|
||||
overlay_context: &mut OverlayContext,
|
||||
) {
|
||||
match self {
|
||||
Self::Line(h) => h.dragging_overlays(document, input, shape_editor, mouse_position, overlay_context),
|
||||
Self::Star(h) => h.dragging_overlays(document, input, shape_editor, mouse_position, overlay_context),
|
||||
Self::Polygon(h) => h.dragging_overlays(document, input, shape_editor, mouse_position, overlay_context),
|
||||
Self::Arc(h) => h.dragging_overlays(document, input, shape_editor, mouse_position, overlay_context),
|
||||
@@ -137,6 +148,7 @@ impl ShapeGizmoHandlers {
|
||||
|
||||
pub fn gizmo_cursor_icon(&self) -> Option<MouseCursorIcon> {
|
||||
match self {
|
||||
Self::Line(h) => h.mouse_cursor_icon(),
|
||||
Self::Star(h) => h.mouse_cursor_icon(),
|
||||
Self::Polygon(h) => h.mouse_cursor_icon(),
|
||||
Self::Arc(h) => h.mouse_cursor_icon(),
|
||||
@@ -168,6 +180,10 @@ impl GizmoManager {
|
||||
///
|
||||
/// 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> {
|
||||
// Line
|
||||
if graph_modification_utils::get_line_id(layer, &document.network_interface).is_some() {
|
||||
return Some(ShapeGizmoHandlers::Line(LineGizmoHandler::default()));
|
||||
}
|
||||
// Star
|
||||
if graph_modification_utils::get_star_id(layer, &document.network_interface).is_some() {
|
||||
return Some(ShapeGizmoHandlers::Star(StarGizmoHandler::default()));
|
||||
@@ -196,12 +212,12 @@ impl GizmoManager {
|
||||
/// 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>) {
|
||||
pub fn handle_actions(&mut self, mouse_position: DVec2, document: &DocumentMessageHandler) {
|
||||
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);
|
||||
handler.handle_state(layer, mouse_position, document);
|
||||
let is_hovered = handler.is_any_gizmo_hovered();
|
||||
|
||||
if is_hovered {
|
||||
@@ -239,9 +255,9 @@ impl GizmoManager {
|
||||
}
|
||||
|
||||
/// 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>) {
|
||||
pub fn handle_update(&mut self, drag_start: DVec2, snap_manager: &mut SnapManager, 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);
|
||||
handle.handle_update(drag_start, snap_manager, document, input, responses);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
use crate::consts::GIZMO_HIDE_THRESHOLD;
|
||||
use crate::messages::frontend::utility_types::MouseCursorIcon;
|
||||
use crate::messages::message::Message;
|
||||
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
|
||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||
use crate::messages::portfolio::document::utility_types::network_interface::InputConnector;
|
||||
use crate::messages::prelude::Responses;
|
||||
use crate::messages::prelude::{DocumentMessageHandler, InputPreprocessorMessageHandler, NodeGraphMessage};
|
||||
use crate::messages::prelude::{FrontendMessage, Responses};
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils::{self, get_arc_id, get_stroke_width};
|
||||
use crate::messages::tool::common_functionality::shapes::shape_utility::{extract_arc_parameters, extract_circle_radius};
|
||||
use glam::{DAffine2, DVec2};
|
||||
@@ -77,7 +76,7 @@ impl RadiusHandle {
|
||||
stroke_width + extra_spacing
|
||||
}
|
||||
|
||||
pub fn handle_actions(&mut self, layer: LayerNodeIdentifier, document: &DocumentMessageHandler, mouse_position: DVec2, responses: &mut VecDeque<Message>) {
|
||||
pub fn handle_actions(&mut self, layer: LayerNodeIdentifier, document: &DocumentMessageHandler, mouse_position: DVec2) {
|
||||
match &self.handle_state {
|
||||
RadiusHandleState::Inactive => {
|
||||
let Some(radius) = extract_circle_radius(layer, document).or(extract_arc_parameters(Some(layer), document).map(|(r, _, _, _)| r)) else {
|
||||
@@ -99,8 +98,6 @@ impl RadiusHandle {
|
||||
self.angle = angle;
|
||||
|
||||
self.update_state(RadiusHandleState::Hover);
|
||||
|
||||
responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::EWResize });
|
||||
}
|
||||
}
|
||||
RadiusHandleState::Dragging | RadiusHandleState::Hover => {}
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
use crate::consts::BOUNDS_SELECT_THRESHOLD;
|
||||
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, InputPreprocessorMessageHandler, NodeGraphMessage};
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils::{self};
|
||||
use crate::messages::tool::common_functionality::shapes::LineEnd;
|
||||
use crate::messages::tool::common_functionality::shapes::shape_utility::{extract_line_parameters, generate_line};
|
||||
use crate::messages::tool::common_functionality::snapping::{SnapData, SnapManager};
|
||||
use crate::messages::tool::tool_messages::tool_prelude::Key;
|
||||
use glam::DVec2;
|
||||
use graph_craft::document::NodeInput;
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use std::collections::VecDeque;
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
pub enum LineEndPointHandleState {
|
||||
#[default]
|
||||
Inactive,
|
||||
Hover,
|
||||
Dragging,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
pub struct LineEndPointHandle {
|
||||
pub layer: Option<LayerNodeIdentifier>,
|
||||
pub handle_state: LineEndPointHandleState,
|
||||
end_point: LineEnd,
|
||||
drag_start: DVec2,
|
||||
drag_current: DVec2,
|
||||
angle: f64,
|
||||
}
|
||||
|
||||
impl LineEndPointHandle {
|
||||
pub fn cleanup(&mut self) {
|
||||
self.handle_state = LineEndPointHandleState::Inactive;
|
||||
self.layer = None;
|
||||
}
|
||||
|
||||
pub fn hovered(&self) -> bool {
|
||||
self.handle_state == LineEndPointHandleState::Hover
|
||||
}
|
||||
|
||||
pub fn is_dragging(&self) -> bool {
|
||||
self.handle_state == LineEndPointHandleState::Dragging
|
||||
}
|
||||
|
||||
pub fn update_state(&mut self, state: LineEndPointHandleState) {
|
||||
self.handle_state = state;
|
||||
}
|
||||
|
||||
pub fn handle_actions(&mut self, layer: LayerNodeIdentifier, document: &DocumentMessageHandler, mouse_position: DVec2) {
|
||||
match &self.handle_state {
|
||||
LineEndPointHandleState::Inactive => {
|
||||
if self.clicked_on_line_endpoints(layer, document, mouse_position) {
|
||||
self.drag_current = mouse_position;
|
||||
self.update_state(LineEndPointHandleState::Hover);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn overlays(&self, selected_line_layer: Option<LayerNodeIdentifier>, document: &DocumentMessageHandler, overlay_context: &mut OverlayContext) {
|
||||
let Some(layer) = selected_line_layer.or(self.layer) else { return };
|
||||
let Some((start, end)) = extract_line_parameters(Some(layer), document) else { return };
|
||||
|
||||
let [viewport_start, viewport_end] = [start, end].map(|point| document.metadata().transform_to_viewport(layer).transform_point2(point));
|
||||
overlay_context.line(viewport_start, viewport_end, None, None);
|
||||
if !start.abs_diff_eq(end, f64::EPSILON * 1000.) {
|
||||
overlay_context.square(viewport_start, Some(6.), None, None);
|
||||
overlay_context.square(viewport_end, Some(6.), None, None);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_endpoint_position(&mut self, document: &DocumentMessageHandler, snap: &mut SnapManager, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) {
|
||||
let Some(layer) = self.layer else { return };
|
||||
|
||||
self.drag_current = input.mouse.position;
|
||||
let ignore = [layer];
|
||||
let snap_data = SnapData::ignore(document, input, &ignore);
|
||||
let to_document = document.metadata().transform_to_document(layer);
|
||||
let (mut document_points, angle) = generate_line(
|
||||
self.angle,
|
||||
to_document.transform_point2(self.drag_start),
|
||||
input.mouse.position,
|
||||
snap,
|
||||
snap_data,
|
||||
input.keyboard.key(Key::Shift),
|
||||
input.keyboard.key(Key::Control),
|
||||
input.keyboard.key(Key::Alt),
|
||||
);
|
||||
|
||||
self.angle = angle;
|
||||
|
||||
if self.end_point == LineEnd::Start {
|
||||
document_points.swap(0, 1);
|
||||
}
|
||||
let Some(node_id) = graph_modification_utils::get_line_id(layer, &document.network_interface) else {
|
||||
return;
|
||||
};
|
||||
responses.add(NodeGraphMessage::SetInput {
|
||||
input_connector: InputConnector::node(node_id, 1),
|
||||
input: NodeInput::value(TaggedValue::DVec2(to_document.inverse().transform_point2(document_points[0])), false),
|
||||
});
|
||||
responses.add(NodeGraphMessage::SetInput {
|
||||
input_connector: InputConnector::node(node_id, 2),
|
||||
input: NodeInput::value(TaggedValue::DVec2(to_document.inverse().transform_point2(document_points[1])), false),
|
||||
});
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
}
|
||||
|
||||
pub fn clicked_on_line_endpoints(&mut self, layer: LayerNodeIdentifier, document: &DocumentMessageHandler, drag_start: DVec2) -> bool {
|
||||
let Some((document_start, document_end)) = extract_line_parameters(Some(layer), document) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let transform = document.metadata().transform_to_viewport(layer);
|
||||
let viewport_x = transform.transform_vector2(DVec2::X).normalize_or_zero() * BOUNDS_SELECT_THRESHOLD;
|
||||
let viewport_y = transform.transform_vector2(DVec2::Y).normalize_or_zero() * BOUNDS_SELECT_THRESHOLD;
|
||||
let threshold_x = transform.inverse().transform_vector2(viewport_x).length();
|
||||
let threshold_y = transform.inverse().transform_vector2(viewport_y).length();
|
||||
|
||||
let [start, end] = [document_start, document_end].map(|point| transform.transform_point2(point));
|
||||
|
||||
let start_click = (drag_start.y - start.y).abs() < threshold_y && (drag_start.x - start.x).abs() < threshold_x;
|
||||
let end_click = (drag_start.y - end.y).abs() < threshold_y && (drag_start.x - end.x).abs() < threshold_x;
|
||||
|
||||
if start_click || end_click {
|
||||
self.end_point = if end_click { LineEnd::End } else { LineEnd::Start };
|
||||
self.drag_start = if end_click { document_start } else { document_end };
|
||||
self.layer = Some(layer);
|
||||
return true;
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
pub mod circle_arc_radius_handle;
|
||||
pub mod line_endpoint_handle;
|
||||
pub mod number_of_points_dial;
|
||||
pub mod point_radius_handle;
|
||||
pub mod sweep_angle_gizmo;
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
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::prelude::{DocumentMessageHandler, 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};
|
||||
@@ -49,7 +48,7 @@ impl NumberOfPointsDial {
|
||||
self.handle_state == NumberOfPointsDialState::Dragging
|
||||
}
|
||||
|
||||
pub fn handle_actions(&mut self, layer: LayerNodeIdentifier, mouse_position: DVec2, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
|
||||
pub fn handle_actions(&mut self, layer: LayerNodeIdentifier, mouse_position: DVec2, document: &DocumentMessageHandler) {
|
||||
match &self.handle_state {
|
||||
NumberOfPointsDialState::Inactive => {
|
||||
// Star
|
||||
@@ -63,7 +62,6 @@ impl NumberOfPointsDial {
|
||||
self.layer = Some(layer);
|
||||
self.initial_points = sides;
|
||||
self.update_state(NumberOfPointsDialState::Hover);
|
||||
responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::EWResize });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,7 +76,6 @@ impl NumberOfPointsDial {
|
||||
self.layer = Some(layer);
|
||||
self.initial_points = sides;
|
||||
self.update_state(NumberOfPointsDialState::Hover);
|
||||
responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::EWResize });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -91,7 +88,6 @@ impl NumberOfPointsDial {
|
||||
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 });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
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::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||
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::{DocumentMessageHandler, InputPreprocessorMessageHandler, NodeGraphMessage};
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils::{self, NodeGraphLayer};
|
||||
@@ -54,7 +52,7 @@ impl PointRadiusHandle {
|
||||
self.handle_state = state;
|
||||
}
|
||||
|
||||
pub fn handle_actions(&mut self, layer: LayerNodeIdentifier, document: &DocumentMessageHandler, mouse_position: DVec2, responses: &mut VecDeque<Message>) {
|
||||
pub fn handle_actions(&mut self, layer: LayerNodeIdentifier, document: &DocumentMessageHandler, mouse_position: DVec2) {
|
||||
match &self.handle_state {
|
||||
PointRadiusHandleState::Inactive => {
|
||||
// Draw the point handle gizmo for the star shape
|
||||
@@ -77,7 +75,6 @@ impl PointRadiusHandle {
|
||||
self.point = i;
|
||||
self.snap_radii = Self::calculate_snap_radii(document, layer, radius_index);
|
||||
self.initial_radius = radius;
|
||||
responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Default });
|
||||
self.update_state(PointRadiusHandleState::Hover);
|
||||
|
||||
return;
|
||||
@@ -105,7 +102,6 @@ impl PointRadiusHandle {
|
||||
self.snap_radii.clear();
|
||||
self.initial_radius = radius;
|
||||
self.update_state(PointRadiusHandleState::Hover);
|
||||
responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Default });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ use crate::messages::tool::common_functionality::gizmos::shape_gizmos::circle_ar
|
||||
use crate::messages::tool::common_functionality::gizmos::shape_gizmos::sweep_angle_gizmo::{SweepAngleGizmo, SweepAngleGizmoState};
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||
use crate::messages::tool::common_functionality::shapes::shape_utility::{ShapeGizmoHandler, arc_outline};
|
||||
use crate::messages::tool::common_functionality::snapping::SnapManager;
|
||||
use crate::messages::tool::tool_messages::tool_prelude::*;
|
||||
use glam::DAffine2;
|
||||
use graph_craft::document::NodeInput;
|
||||
@@ -28,9 +29,9 @@ impl ArcGizmoHandler {
|
||||
}
|
||||
|
||||
impl ShapeGizmoHandler for ArcGizmoHandler {
|
||||
fn handle_state(&mut self, selected_shape_layer: LayerNodeIdentifier, mouse_position: DVec2, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
|
||||
fn handle_state(&mut self, selected_shape_layer: LayerNodeIdentifier, mouse_position: DVec2, document: &DocumentMessageHandler) {
|
||||
self.sweep_angle_gizmo.handle_actions(selected_shape_layer, document, mouse_position);
|
||||
self.arc_radius_handle.handle_actions(selected_shape_layer, document, mouse_position, responses);
|
||||
self.arc_radius_handle.handle_actions(selected_shape_layer, document, mouse_position);
|
||||
}
|
||||
|
||||
fn is_any_gizmo_hovered(&self) -> bool {
|
||||
@@ -54,7 +55,7 @@ impl ShapeGizmoHandler for ArcGizmoHandler {
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_update(&mut self, drag_start: DVec2, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) {
|
||||
fn handle_update(&mut self, drag_start: DVec2, _snap_manager: &mut SnapManager, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) {
|
||||
if self.sweep_angle_gizmo.is_dragging_or_snapped() {
|
||||
self.sweep_angle_gizmo.update_arc(document, input, responses);
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ use crate::messages::tool::common_functionality::gizmos::shape_gizmos::circle_ar
|
||||
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, ShapeToolModifierKey};
|
||||
use crate::messages::tool::common_functionality::snapping::SnapManager;
|
||||
use crate::messages::tool::tool_messages::shape_tool::ShapeToolData;
|
||||
use crate::messages::tool::tool_messages::tool_prelude::*;
|
||||
use glam::DAffine2;
|
||||
@@ -23,8 +24,8 @@ impl ShapeGizmoHandler for CircleGizmoHandler {
|
||||
self.circle_radius_handle.hovered()
|
||||
}
|
||||
|
||||
fn handle_state(&mut self, selected_circle_layer: LayerNodeIdentifier, mouse_position: DVec2, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
|
||||
self.circle_radius_handle.handle_actions(selected_circle_layer, document, mouse_position, responses);
|
||||
fn handle_state(&mut self, selected_circle_layer: LayerNodeIdentifier, mouse_position: DVec2, document: &DocumentMessageHandler) {
|
||||
self.circle_radius_handle.handle_actions(selected_circle_layer, document, mouse_position);
|
||||
}
|
||||
|
||||
fn handle_click(&mut self) {
|
||||
@@ -33,7 +34,7 @@ impl ShapeGizmoHandler for CircleGizmoHandler {
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_update(&mut self, drag_start: DVec2, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) {
|
||||
fn handle_update(&mut self, drag_start: DVec2, _snap_manager: &mut SnapManager, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) {
|
||||
if self.circle_radius_handle.is_dragging() {
|
||||
self.circle_radius_handle.update_inner_radius(document, input, responses, drag_start);
|
||||
}
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
use super::shape_utility::ShapeToolModifierKey;
|
||||
use crate::consts::{BOUNDS_SELECT_THRESHOLD, LINE_ROTATE_SNAP_ANGLE};
|
||||
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::network_interface::{InputConnector, NodeTemplate};
|
||||
use crate::messages::tool::common_functionality::gizmos::shape_gizmos::line_endpoint_handle::{LineEndPointHandle, LineEndPointHandleState};
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||
pub use crate::messages::tool::common_functionality::graph_modification_utils::NodeGraphLayer;
|
||||
use crate::messages::tool::common_functionality::snapping::{SnapCandidatePoint, SnapConstraint, SnapData, SnapTypeConfiguration};
|
||||
use crate::messages::tool::common_functionality::shape_editor::ShapeState;
|
||||
use crate::messages::tool::common_functionality::shapes::shape_utility::{ShapeGizmoHandler, generate_line};
|
||||
use crate::messages::tool::common_functionality::snapping::{SnapData, SnapManager};
|
||||
use crate::messages::tool::tool_messages::shape_tool::ShapeToolData;
|
||||
use crate::messages::tool::tool_messages::tool_prelude::*;
|
||||
use glam::DVec2;
|
||||
@@ -14,6 +16,77 @@ use graph_craft::document::NodeInput;
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use std::collections::VecDeque;
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct LineGizmoHandler {
|
||||
line_endpoint_handle: LineEndPointHandle,
|
||||
}
|
||||
|
||||
impl LineGizmoHandler {
|
||||
pub fn new() -> Self {
|
||||
Self { ..Default::default() }
|
||||
}
|
||||
}
|
||||
|
||||
impl ShapeGizmoHandler for LineGizmoHandler {
|
||||
fn handle_state(&mut self, selected_shape_layer: LayerNodeIdentifier, mouse_position: DVec2, document: &DocumentMessageHandler) {
|
||||
self.line_endpoint_handle.handle_actions(selected_shape_layer, document, mouse_position);
|
||||
}
|
||||
|
||||
fn is_any_gizmo_hovered(&self) -> bool {
|
||||
self.line_endpoint_handle.hovered()
|
||||
}
|
||||
|
||||
fn handle_click(&mut self) {
|
||||
// If hovering over both the gizmos give priority to sweep angle gizmo
|
||||
if self.line_endpoint_handle.hovered() {
|
||||
self.line_endpoint_handle.update_state(LineEndPointHandleState::Dragging);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_update(&mut self, _drag_start: DVec2, snap_manager: &mut SnapManager, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) {
|
||||
if self.line_endpoint_handle.is_dragging() {
|
||||
self.line_endpoint_handle.update_endpoint_position(document, snap_manager, input, responses);
|
||||
}
|
||||
}
|
||||
|
||||
fn dragging_overlays(
|
||||
&self,
|
||||
document: &DocumentMessageHandler,
|
||||
_input: &InputPreprocessorMessageHandler,
|
||||
_shape_editor: &mut &mut crate::messages::tool::common_functionality::shape_editor::ShapeState,
|
||||
_mouse_position: DVec2,
|
||||
overlay_context: &mut crate::messages::portfolio::document::overlays::utility_types::OverlayContext,
|
||||
) {
|
||||
if self.line_endpoint_handle.is_dragging() {
|
||||
self.line_endpoint_handle.overlays(None, document, overlay_context);
|
||||
}
|
||||
}
|
||||
|
||||
fn overlays(
|
||||
&self,
|
||||
document: &DocumentMessageHandler,
|
||||
selected_shape_layer: Option<LayerNodeIdentifier>,
|
||||
_input: &InputPreprocessorMessageHandler,
|
||||
_shape_editor: &mut &mut ShapeState,
|
||||
_mouse_position: DVec2,
|
||||
overlay_context: &mut OverlayContext,
|
||||
) {
|
||||
self.line_endpoint_handle.overlays(selected_shape_layer, document, overlay_context);
|
||||
}
|
||||
|
||||
fn mouse_cursor_icon(&self) -> Option<MouseCursorIcon> {
|
||||
if self.line_endpoint_handle.hovered() {
|
||||
return Some(MouseCursorIcon::Default);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn cleanup(&mut self) {
|
||||
self.line_endpoint_handle.cleanup();
|
||||
}
|
||||
}
|
||||
#[derive(Clone, PartialEq, Debug, Default)]
|
||||
pub enum LineEnd {
|
||||
#[default]
|
||||
@@ -24,12 +97,8 @@ pub enum LineEnd {
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct LineToolData {
|
||||
pub drag_start: DVec2,
|
||||
pub drag_current: DVec2,
|
||||
pub angle: f64,
|
||||
pub weight: f64,
|
||||
pub selected_layers_with_position: HashMap<LayerNodeIdentifier, [DVec2; 2]>,
|
||||
pub editing_layer: Option<LayerNodeIdentifier>,
|
||||
pub dragging_endpoint: Option<LineEnd>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
@@ -55,16 +124,23 @@ impl Line {
|
||||
) {
|
||||
let [center, snap_angle, lock_angle] = modifier;
|
||||
|
||||
shape_tool_data.line_data.drag_current = ipp.mouse.position;
|
||||
|
||||
let keyboard = &ipp.keyboard;
|
||||
let ignore = [layer];
|
||||
let snap_data = SnapData::ignore(document, ipp, &ignore);
|
||||
let mut document_points = generate_line(shape_tool_data, snap_data, keyboard.key(lock_angle), keyboard.key(snap_angle), keyboard.key(center));
|
||||
let (document_points, angle) = generate_line(
|
||||
shape_tool_data.line_data.angle,
|
||||
shape_tool_data.data.drag_start,
|
||||
ipp.mouse.position,
|
||||
&mut shape_tool_data.data.snap_manager,
|
||||
snap_data,
|
||||
keyboard.key(lock_angle),
|
||||
keyboard.key(snap_angle),
|
||||
keyboard.key(center),
|
||||
);
|
||||
|
||||
if shape_tool_data.line_data.dragging_endpoint == Some(LineEnd::Start) {
|
||||
document_points.swap(0, 1);
|
||||
}
|
||||
shape_tool_data.line_data.angle = angle;
|
||||
|
||||
let to_document = document.metadata().transform_to_document(layer);
|
||||
|
||||
let Some(node_id) = graph_modification_utils::get_line_id(layer, &document.network_interface) else {
|
||||
return;
|
||||
@@ -72,131 +148,14 @@ impl Line {
|
||||
|
||||
responses.add(NodeGraphMessage::SetInput {
|
||||
input_connector: InputConnector::node(node_id, 1),
|
||||
input: NodeInput::value(TaggedValue::DVec2(document_points[0]), false),
|
||||
input: NodeInput::value(TaggedValue::DVec2(to_document.inverse().transform_point2(document_points[0])), false),
|
||||
});
|
||||
responses.add(NodeGraphMessage::SetInput {
|
||||
input_connector: InputConnector::node(node_id, 2),
|
||||
input: NodeInput::value(TaggedValue::DVec2(document_points[1]), false),
|
||||
input: NodeInput::value(TaggedValue::DVec2(to_document.inverse().transform_point2(document_points[1])), false),
|
||||
});
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
}
|
||||
|
||||
pub fn overlays(document: &DocumentMessageHandler, shape_tool_data: &mut ShapeToolData, overlay_context: &mut OverlayContext) {
|
||||
shape_tool_data.line_data.selected_layers_with_position = document
|
||||
.network_interface
|
||||
.selected_nodes()
|
||||
.selected_visible_and_unlocked_layers(&document.network_interface)
|
||||
.filter_map(|layer| {
|
||||
let node_inputs = NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs("Line")?;
|
||||
|
||||
let (Some(&TaggedValue::DVec2(start)), Some(&TaggedValue::DVec2(end))) = (node_inputs[1].as_value(), node_inputs[2].as_value()) else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let [viewport_start, viewport_end] = [start, end].map(|point| document.metadata().transform_to_viewport(layer).transform_point2(point));
|
||||
if !start.abs_diff_eq(end, f64::EPSILON * 1000.) {
|
||||
overlay_context.line(viewport_start, viewport_end, None, None);
|
||||
overlay_context.square(viewport_start, Some(6.), None, None);
|
||||
overlay_context.square(viewport_end, Some(6.), None, None);
|
||||
}
|
||||
|
||||
Some((layer, [start, end]))
|
||||
})
|
||||
.collect::<HashMap<LayerNodeIdentifier, [DVec2; 2]>>();
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_line(tool_data: &mut ShapeToolData, snap_data: SnapData, lock_angle: bool, snap_angle: bool, center: bool) -> [DVec2; 2] {
|
||||
let document_to_viewport = snap_data.document.metadata().document_to_viewport;
|
||||
let mut document_points = [tool_data.data.drag_start, document_to_viewport.inverse().transform_point2(tool_data.line_data.drag_current)];
|
||||
|
||||
let mut angle = -(document_points[1] - document_points[0]).angle_to(DVec2::X);
|
||||
let mut line_length = (document_points[1] - document_points[0]).length();
|
||||
|
||||
if lock_angle {
|
||||
angle = tool_data.line_data.angle;
|
||||
} else if snap_angle {
|
||||
let snap_resolution = LINE_ROTATE_SNAP_ANGLE.to_radians();
|
||||
angle = (angle / snap_resolution).round() * snap_resolution;
|
||||
}
|
||||
|
||||
tool_data.line_data.angle = angle;
|
||||
|
||||
if lock_angle {
|
||||
let angle_vec = DVec2::new(angle.cos(), angle.sin());
|
||||
line_length = (document_points[1] - document_points[0]).dot(angle_vec);
|
||||
}
|
||||
|
||||
document_points[1] = document_points[0] + line_length * DVec2::new(angle.cos(), angle.sin());
|
||||
|
||||
let constrained = snap_angle || lock_angle;
|
||||
let snap = &mut tool_data.data.snap_manager;
|
||||
|
||||
let near_point = SnapCandidatePoint::handle_neighbors(document_points[1], [tool_data.data.drag_start]);
|
||||
let far_point = SnapCandidatePoint::handle_neighbors(2. * document_points[0] - document_points[1], [tool_data.data.drag_start]);
|
||||
let config = SnapTypeConfiguration::default();
|
||||
|
||||
if constrained {
|
||||
let constraint = SnapConstraint::Line {
|
||||
origin: document_points[0],
|
||||
direction: document_points[1] - document_points[0],
|
||||
};
|
||||
if center {
|
||||
let snapped = snap.constrained_snap(&snap_data, &near_point, constraint, config);
|
||||
let snapped_far = snap.constrained_snap(&snap_data, &far_point, constraint, config);
|
||||
let best = if snapped_far.other_snap_better(&snapped) { snapped } else { snapped_far };
|
||||
document_points[1] = document_points[0] * 2. - best.snapped_point_document;
|
||||
document_points[0] = best.snapped_point_document;
|
||||
snap.update_indicator(best);
|
||||
} else {
|
||||
let snapped = snap.constrained_snap(&snap_data, &near_point, constraint, config);
|
||||
document_points[1] = snapped.snapped_point_document;
|
||||
snap.update_indicator(snapped);
|
||||
}
|
||||
} else if center {
|
||||
let snapped = snap.free_snap(&snap_data, &near_point, config);
|
||||
let snapped_far = snap.free_snap(&snap_data, &far_point, config);
|
||||
let best = if snapped_far.other_snap_better(&snapped) { snapped } else { snapped_far };
|
||||
document_points[1] = document_points[0] * 2. - best.snapped_point_document;
|
||||
document_points[0] = best.snapped_point_document;
|
||||
snap.update_indicator(best);
|
||||
} else {
|
||||
let snapped = snap.free_snap(&snap_data, &near_point, config);
|
||||
document_points[1] = snapped.snapped_point_document;
|
||||
snap.update_indicator(snapped);
|
||||
}
|
||||
|
||||
document_points
|
||||
}
|
||||
|
||||
pub fn clicked_on_line_endpoints(layer: LayerNodeIdentifier, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, shape_tool_data: &mut ShapeToolData) -> bool {
|
||||
let Some(node_inputs) = NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs("Line") else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let (Some(&TaggedValue::DVec2(document_start)), Some(&TaggedValue::DVec2(document_end))) = (node_inputs[1].as_value(), node_inputs[2].as_value()) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let transform = document.metadata().transform_to_viewport(layer);
|
||||
let viewport_x = transform.transform_vector2(DVec2::X).normalize_or_zero() * BOUNDS_SELECT_THRESHOLD;
|
||||
let viewport_y = transform.transform_vector2(DVec2::Y).normalize_or_zero() * BOUNDS_SELECT_THRESHOLD;
|
||||
let threshold_x = transform.inverse().transform_vector2(viewport_x).length();
|
||||
let threshold_y = transform.inverse().transform_vector2(viewport_y).length();
|
||||
|
||||
let drag_start = input.mouse.position;
|
||||
let [start, end] = [document_start, document_end].map(|point| transform.transform_point2(point));
|
||||
|
||||
let start_click = (drag_start.y - start.y).abs() < threshold_y && (drag_start.x - start.x).abs() < threshold_x;
|
||||
let end_click = (drag_start.y - end.y).abs() < threshold_y && (drag_start.x - end.x).abs() < threshold_x;
|
||||
|
||||
if start_click || end_click {
|
||||
shape_tool_data.line_data.dragging_endpoint = Some(if end_click { LineEnd::End } else { LineEnd::Start });
|
||||
shape_tool_data.data.drag_start = if end_click { document_start } else { document_end };
|
||||
shape_tool_data.line_data.editing_layer = Some(layer);
|
||||
return true;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -14,6 +14,7 @@ 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::common_functionality::snapping::SnapManager;
|
||||
use crate::messages::tool::tool_messages::tool_prelude::*;
|
||||
use glam::DAffine2;
|
||||
use graph_craft::document::NodeInput;
|
||||
@@ -31,9 +32,9 @@ impl ShapeGizmoHandler for PolygonGizmoHandler {
|
||||
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_state(&mut self, selected_star_layer: LayerNodeIdentifier, mouse_position: DVec2, document: &DocumentMessageHandler) {
|
||||
self.number_of_points_dial.handle_actions(selected_star_layer, mouse_position, document);
|
||||
self.point_radius_handle.handle_actions(selected_star_layer, document, mouse_position);
|
||||
}
|
||||
|
||||
fn handle_click(&mut self) {
|
||||
@@ -47,7 +48,7 @@ impl ShapeGizmoHandler for PolygonGizmoHandler {
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_update(&mut self, drag_start: DVec2, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) {
|
||||
fn handle_update(&mut self, drag_start: DVec2, _snap_manager: &mut SnapManager, 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);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use super::ShapeToolData;
|
||||
use crate::consts::{ARC_SWEEP_GIZMO_RADIUS, ARC_SWEEP_GIZMO_TEXT_HEIGHT};
|
||||
use crate::consts::{ARC_SWEEP_GIZMO_RADIUS, ARC_SWEEP_GIZMO_TEXT_HEIGHT, LINE_ROTATE_SNAP_ANGLE};
|
||||
use crate::messages::frontend::utility_types::MouseCursorIcon;
|
||||
use crate::messages::message::Message;
|
||||
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
|
||||
@@ -8,6 +8,7 @@ use crate::messages::portfolio::document::utility_types::network_interface::Inpu
|
||||
use crate::messages::prelude::{DocumentMessageHandler, InputPreprocessorMessageHandler, NodeGraphMessage, Responses};
|
||||
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::snapping::{SnapCandidatePoint, SnapConstraint, SnapData, SnapManager, SnapTypeConfiguration};
|
||||
use crate::messages::tool::common_functionality::transformation_cage::BoundingBoxManager;
|
||||
use crate::messages::tool::tool_messages::tool_prelude::Key;
|
||||
use crate::messages::tool::utility_types::*;
|
||||
@@ -85,7 +86,7 @@ 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>);
|
||||
fn handle_state(&mut self, selected_shape_layers: LayerNodeIdentifier, mouse_position: DVec2, document: &DocumentMessageHandler);
|
||||
|
||||
/// Called when a mouse click occurs over the canvas and a gizmo handle is hovered.
|
||||
///
|
||||
@@ -96,7 +97,7 @@ pub trait ShapeGizmoHandler {
|
||||
/// 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>);
|
||||
fn handle_update(&mut self, drag_start: DVec2, snap_manager: &mut SnapManager, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>);
|
||||
|
||||
/// Draws the static or hover-dependent overlays associated with the gizmo.
|
||||
///
|
||||
@@ -259,6 +260,18 @@ pub fn extract_arc_parameters(layer: Option<LayerNodeIdentifier>, document: &Doc
|
||||
Some((radius, start_angle, sweep_angle, arc_type))
|
||||
}
|
||||
|
||||
// Extract the node input values of an Line.
|
||||
/// Returns an option of (start point, end point).
|
||||
pub fn extract_line_parameters(layer: Option<LayerNodeIdentifier>, document: &DocumentMessageHandler) -> Option<(DVec2, DVec2)> {
|
||||
let node_inputs = NodeGraphLayer::new(layer?, &document.network_interface).find_node_inputs("Line")?;
|
||||
|
||||
let (Some(&TaggedValue::DVec2(start)), Some(&TaggedValue::DVec2(end))) = (node_inputs.get(1)?.as_value(), node_inputs.get(2)?.as_value()) else {
|
||||
return None;
|
||||
};
|
||||
|
||||
Some((start, end))
|
||||
}
|
||||
|
||||
/// Calculate the viewport positions of arc endpoints
|
||||
pub fn arc_end_points(layer: Option<LayerNodeIdentifier>, document: &DocumentMessageHandler) -> Option<(DVec2, DVec2)> {
|
||||
let (radius, start_angle, sweep_angle, _) = extract_arc_parameters(Some(layer?), document)?;
|
||||
@@ -475,3 +488,72 @@ pub fn calculate_arc_text_transform(angle: f64, offset_angle: f64, center: DVec2
|
||||
);
|
||||
DAffine2::from_translation(text_texture_position + center)
|
||||
}
|
||||
|
||||
pub fn generate_line(
|
||||
previous_angle: f64,
|
||||
drag_start: DVec2,
|
||||
current_position: DVec2,
|
||||
snap: &mut SnapManager,
|
||||
snap_data: SnapData,
|
||||
lock_angle: bool,
|
||||
snap_angle: bool,
|
||||
center: bool,
|
||||
) -> ([DVec2; 2], f64) {
|
||||
let document_to_viewport = snap_data.document.metadata().document_to_viewport;
|
||||
let mut document_points = [drag_start, document_to_viewport.inverse().transform_point2(current_position)];
|
||||
|
||||
let mut angle = -(document_points[1] - document_points[0]).angle_to(DVec2::X);
|
||||
let mut line_length = (document_points[1] - document_points[0]).length();
|
||||
|
||||
if lock_angle {
|
||||
angle = previous_angle;
|
||||
} else if snap_angle {
|
||||
let snap_resolution = LINE_ROTATE_SNAP_ANGLE.to_radians();
|
||||
angle = (angle / snap_resolution).round() * snap_resolution;
|
||||
}
|
||||
|
||||
if lock_angle {
|
||||
let angle_vec = DVec2::new(angle.cos(), angle.sin());
|
||||
line_length = (document_points[1] - document_points[0]).dot(angle_vec);
|
||||
}
|
||||
|
||||
document_points[1] = document_points[0] + line_length * DVec2::new(angle.cos(), angle.sin());
|
||||
|
||||
let constrained = snap_angle || lock_angle;
|
||||
|
||||
let near_point = SnapCandidatePoint::handle_neighbors(document_points[1], [drag_start]);
|
||||
let far_point = SnapCandidatePoint::handle_neighbors(2. * document_points[0] - document_points[1], [drag_start]);
|
||||
let config = SnapTypeConfiguration::default();
|
||||
|
||||
if constrained {
|
||||
let constraint = SnapConstraint::Line {
|
||||
origin: document_points[0],
|
||||
direction: document_points[1] - document_points[0],
|
||||
};
|
||||
if center {
|
||||
let snapped = snap.constrained_snap(&snap_data, &near_point, constraint, config);
|
||||
let snapped_far = snap.constrained_snap(&snap_data, &far_point, constraint, config);
|
||||
let best = if snapped_far.other_snap_better(&snapped) { snapped } else { snapped_far };
|
||||
document_points[1] = document_points[0] * 2. - best.snapped_point_document;
|
||||
document_points[0] = best.snapped_point_document;
|
||||
snap.update_indicator(best);
|
||||
} else {
|
||||
let snapped = snap.constrained_snap(&snap_data, &near_point, constraint, config);
|
||||
document_points[1] = snapped.snapped_point_document;
|
||||
snap.update_indicator(snapped);
|
||||
}
|
||||
} else if center {
|
||||
let snapped = snap.free_snap(&snap_data, &near_point, config);
|
||||
let snapped_far = snap.free_snap(&snap_data, &far_point, config);
|
||||
let best = if snapped_far.other_snap_better(&snapped) { snapped } else { snapped_far };
|
||||
document_points[1] = document_points[0] * 2. - best.snapped_point_document;
|
||||
document_points[0] = best.snapped_point_document;
|
||||
snap.update_indicator(best);
|
||||
} else {
|
||||
let snapped = snap.free_snap(&snap_data, &near_point, config);
|
||||
document_points[1] = snapped.snapped_point_document;
|
||||
snap.update_indicator(snapped);
|
||||
}
|
||||
|
||||
([document_points[0], document_points[1]], angle)
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ use crate::messages::tool::common_functionality::gizmos::shape_gizmos::point_rad
|
||||
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::common_functionality::snapping::SnapManager;
|
||||
use crate::messages::tool::tool_messages::tool_prelude::*;
|
||||
use core::f64;
|
||||
use glam::DAffine2;
|
||||
@@ -28,9 +29,9 @@ impl ShapeGizmoHandler for StarGizmoHandler {
|
||||
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_state(&mut self, selected_star_layer: LayerNodeIdentifier, mouse_position: DVec2, document: &DocumentMessageHandler) {
|
||||
self.number_of_points_dial.handle_actions(selected_star_layer, mouse_position, document);
|
||||
self.point_radius_handle.handle_actions(selected_star_layer, document, mouse_position);
|
||||
}
|
||||
|
||||
fn handle_click(&mut self) {
|
||||
@@ -44,7 +45,7 @@ impl ShapeGizmoHandler for StarGizmoHandler {
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_update(&mut self, drag_start: DVec2, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) {
|
||||
fn handle_update(&mut self, drag_start: DVec2, _snap_manager: &mut SnapManager, 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);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use super::tool_prelude::*;
|
||||
use crate::consts::{DEFAULT_STROKE_WIDTH, SNAP_POINT_TOLERANCE};
|
||||
use crate::consts::DEFAULT_STROKE_WIDTH;
|
||||
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;
|
||||
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
|
||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||
@@ -12,14 +12,14 @@ use crate::messages::tool::common_functionality::graph_modification_utils::NodeG
|
||||
use crate::messages::tool::common_functionality::resize::Resize;
|
||||
use crate::messages::tool::common_functionality::shapes::arc_shape::Arc;
|
||||
use crate::messages::tool::common_functionality::shapes::circle_shape::Circle;
|
||||
use crate::messages::tool::common_functionality::shapes::line_shape::{LineToolData, clicked_on_line_endpoints};
|
||||
use crate::messages::tool::common_functionality::shapes::line_shape::LineToolData;
|
||||
use crate::messages::tool::common_functionality::shapes::polygon_shape::Polygon;
|
||||
use crate::messages::tool::common_functionality::shapes::shape_utility::{ShapeToolModifierKey, ShapeType, anchor_overlays, transform_cage_overlays};
|
||||
use crate::messages::tool::common_functionality::shapes::star_shape::Star;
|
||||
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::transformation_cage::{BoundingBoxManager, EdgeBool};
|
||||
use crate::messages::tool::common_functionality::utility_functions::{closest_point, resize_bounds, rotate_bounds, skew_bounds, transforming_transform_cage};
|
||||
use crate::messages::tool::common_functionality::utility_functions::{resize_bounds, rotate_bounds, skew_bounds, transforming_transform_cage};
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::{NodeId, NodeInput};
|
||||
use graphene_std::Color;
|
||||
@@ -315,12 +315,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for Shap
|
||||
DecreaseSides,
|
||||
NudgeSelectedLayers,
|
||||
),
|
||||
ShapeToolFsmState::Drawing(_)
|
||||
| ShapeToolFsmState::ResizingBounds
|
||||
| ShapeToolFsmState::DraggingLineEndpoints
|
||||
| ShapeToolFsmState::RotatingBounds
|
||||
| ShapeToolFsmState::ModifyingGizmo
|
||||
| ShapeToolFsmState::SkewingBounds { .. } => {
|
||||
ShapeToolFsmState::Drawing(_) | ShapeToolFsmState::ResizingBounds | ShapeToolFsmState::RotatingBounds | ShapeToolFsmState::ModifyingGizmo | ShapeToolFsmState::SkewingBounds { .. } => {
|
||||
actions!(ShapeToolMessageDiscriminant;
|
||||
DragStop,
|
||||
Abort,
|
||||
@@ -365,7 +360,6 @@ pub enum ShapeToolFsmState {
|
||||
Drawing(ShapeType),
|
||||
|
||||
// Gizmos
|
||||
DraggingLineEndpoints,
|
||||
ModifyingGizmo,
|
||||
|
||||
// Transform cage
|
||||
@@ -452,7 +446,6 @@ impl Fsm for ShapeToolFsmState {
|
||||
document,
|
||||
global_tool_data,
|
||||
input,
|
||||
preferences,
|
||||
shape_editor,
|
||||
..
|
||||
}: &mut ToolActionMessageContext,
|
||||
@@ -477,7 +470,7 @@ impl Fsm for ShapeToolFsmState {
|
||||
.unwrap_or(input.mouse.position);
|
||||
|
||||
if matches!(self, Self::Ready(_)) && !input.keyboard.key(Key::Control) {
|
||||
tool_data.gizmo_manager.handle_actions(mouse_position, document, responses);
|
||||
tool_data.gizmo_manager.handle_actions(mouse_position, document);
|
||||
tool_data.gizmo_manager.overlays(document, input, shape_editor, mouse_position, &mut overlay_context);
|
||||
}
|
||||
|
||||
@@ -504,8 +497,6 @@ impl Fsm for ShapeToolFsmState {
|
||||
anchor_overlays(document, &mut overlay_context);
|
||||
responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Crosshair });
|
||||
} else if matches!(self, ShapeToolFsmState::Ready(_)) {
|
||||
Line::overlays(document, tool_data, &mut overlay_context);
|
||||
|
||||
if all_selected_layers_line {
|
||||
return self;
|
||||
}
|
||||
@@ -540,11 +531,8 @@ impl Fsm for ShapeToolFsmState {
|
||||
responses.add(FrontendMessage::UpdateMouseCursor { cursor });
|
||||
}
|
||||
|
||||
if matches!(self, ShapeToolFsmState::Drawing(_) | ShapeToolFsmState::DraggingLineEndpoints) {
|
||||
Line::overlays(document, tool_data, &mut overlay_context);
|
||||
if tool_options.shape_type == ShapeType::Circle {
|
||||
tool_data.gizmo_manager.overlays(document, input, shape_editor, mouse_position, &mut overlay_context);
|
||||
}
|
||||
if matches!(self, ShapeToolFsmState::Drawing(_)) {
|
||||
tool_data.gizmo_manager.overlays(document, input, shape_editor, mouse_position, &mut overlay_context);
|
||||
}
|
||||
|
||||
self
|
||||
@@ -657,8 +645,6 @@ impl Fsm for ShapeToolFsmState {
|
||||
self
|
||||
}
|
||||
(ShapeToolFsmState::Ready(_), ShapeToolMessage::DragStart) => {
|
||||
tool_data.line_data.drag_start = input.mouse.position;
|
||||
|
||||
// Snapped position in viewport space
|
||||
let mouse_pos = tool_data
|
||||
.data
|
||||
@@ -667,8 +653,6 @@ impl Fsm for ShapeToolFsmState {
|
||||
.map(|pos| document.metadata().document_to_viewport.transform_point2(pos))
|
||||
.unwrap_or(input.mouse.position);
|
||||
|
||||
tool_data.line_data.drag_current = mouse_pos;
|
||||
|
||||
if tool_data.gizmo_manager.handle_click() && !input.keyboard.key(Key::Accel) {
|
||||
tool_data.data.drag_start = document.metadata().document_to_viewport.inverse().transform_point2(mouse_pos);
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
@@ -684,20 +668,6 @@ impl Fsm for ShapeToolFsmState {
|
||||
return ShapeToolFsmState::ModifyingGizmo;
|
||||
}
|
||||
|
||||
// If clicked on endpoints of a selected line, drag its endpoints
|
||||
if let Some((layer, _, _)) = closest_point(
|
||||
document,
|
||||
mouse_pos,
|
||||
SNAP_POINT_TOLERANCE,
|
||||
document.network_interface.selected_nodes().selected_visible_and_unlocked_layers(&document.network_interface),
|
||||
|_| false,
|
||||
preferences,
|
||||
) {
|
||||
if clicked_on_line_endpoints(layer, document, input, tool_data) && !input.keyboard.key(Key::Control) {
|
||||
return ShapeToolFsmState::DraggingLineEndpoints;
|
||||
}
|
||||
}
|
||||
|
||||
let (resize, rotate, skew) = transforming_transform_cage(document, &mut tool_data.bounding_box_manager, input, responses, &mut tool_data.layers_dragging, None);
|
||||
|
||||
if !input.keyboard.key(Key::Control) {
|
||||
@@ -773,10 +743,8 @@ impl Fsm for ShapeToolFsmState {
|
||||
}
|
||||
ShapeType::Line => {
|
||||
tool_data.line_data.weight = tool_options.line_weight;
|
||||
tool_data.line_data.editing_layer = Some(layer);
|
||||
}
|
||||
}
|
||||
tool_options.stroke.apply_stroke(tool_options.line_weight, layer, defered_responses);
|
||||
|
||||
tool_options.stroke.apply_stroke(tool_options.line_weight, layer, defered_responses);
|
||||
tool_data.data.layer = Some(layer);
|
||||
@@ -809,21 +777,15 @@ impl Fsm for ShapeToolFsmState {
|
||||
|
||||
self
|
||||
}
|
||||
(ShapeToolFsmState::DraggingLineEndpoints, ShapeToolMessage::PointerMove { modifier }) => {
|
||||
let Some(layer) = tool_data.line_data.editing_layer else {
|
||||
return ShapeToolFsmState::Ready(tool_data.current_shape);
|
||||
};
|
||||
|
||||
Line::update_shape(document, input, layer, tool_data, modifier, responses);
|
||||
// Auto-panning
|
||||
(ShapeToolFsmState::ModifyingGizmo, ShapeToolMessage::PointerMove { modifier }) => {
|
||||
tool_data
|
||||
.gizmo_manager
|
||||
.handle_update(tool_data.data.viewport_drag_start(document), &mut tool_data.data.snap_manager, document, input, responses);
|
||||
|
||||
let messages = [ShapeToolMessage::PointerOutsideViewport { modifier }.into(), ShapeToolMessage::PointerMove { modifier }.into()];
|
||||
tool_data.auto_panning.setup_by_mouse_position(input, &messages, responses);
|
||||
|
||||
self
|
||||
}
|
||||
(ShapeToolFsmState::ModifyingGizmo, ShapeToolMessage::PointerMove { .. }) => {
|
||||
tool_data.gizmo_manager.handle_update(tool_data.data.viewport_drag_start(document), document, input, responses);
|
||||
|
||||
responses.add(OverlaysMessage::Draw);
|
||||
|
||||
ShapeToolFsmState::ModifyingGizmo
|
||||
@@ -921,12 +883,7 @@ impl Fsm for ShapeToolFsmState {
|
||||
self
|
||||
}
|
||||
(
|
||||
ShapeToolFsmState::Drawing(_)
|
||||
| ShapeToolFsmState::DraggingLineEndpoints
|
||||
| ShapeToolFsmState::ResizingBounds
|
||||
| ShapeToolFsmState::RotatingBounds
|
||||
| ShapeToolFsmState::SkewingBounds { .. }
|
||||
| ShapeToolFsmState::ModifyingGizmo,
|
||||
ShapeToolFsmState::Drawing(_) | ShapeToolFsmState::ResizingBounds | ShapeToolFsmState::RotatingBounds | ShapeToolFsmState::SkewingBounds { .. } | ShapeToolFsmState::ModifyingGizmo,
|
||||
ShapeToolMessage::DragStop,
|
||||
) => {
|
||||
input.mouse.finish_transaction(tool_data.data.drag_start, responses);
|
||||
@@ -938,24 +895,16 @@ impl Fsm for ShapeToolFsmState {
|
||||
bounds.original_transforms.clear();
|
||||
}
|
||||
|
||||
tool_data.line_data.dragging_endpoint = None;
|
||||
|
||||
responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Crosshair });
|
||||
|
||||
ShapeToolFsmState::Ready(tool_data.current_shape)
|
||||
}
|
||||
(
|
||||
ShapeToolFsmState::Drawing(_)
|
||||
| ShapeToolFsmState::DraggingLineEndpoints
|
||||
| ShapeToolFsmState::ResizingBounds
|
||||
| ShapeToolFsmState::RotatingBounds
|
||||
| ShapeToolFsmState::SkewingBounds { .. }
|
||||
| ShapeToolFsmState::ModifyingGizmo,
|
||||
ShapeToolFsmState::Drawing(_) | ShapeToolFsmState::ResizingBounds | ShapeToolFsmState::RotatingBounds | ShapeToolFsmState::SkewingBounds { .. } | ShapeToolFsmState::ModifyingGizmo,
|
||||
ShapeToolMessage::Abort,
|
||||
) => {
|
||||
responses.add(DocumentMessage::AbortTransaction);
|
||||
tool_data.data.cleanup(responses);
|
||||
tool_data.line_data.dragging_endpoint = None;
|
||||
|
||||
tool_data.gizmo_manager.handle_cleanup();
|
||||
|
||||
@@ -1066,14 +1015,6 @@ fn update_dynamic_hints(state: &ShapeToolFsmState, responses: &mut VecDeque<Mess
|
||||
|
||||
HintData(common_hint_group)
|
||||
}
|
||||
ShapeToolFsmState::DraggingLineEndpoints => HintData(vec![
|
||||
HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]),
|
||||
HintGroup(vec![
|
||||
HintInfo::keys([Key::Shift], "15° Increments"),
|
||||
HintInfo::keys([Key::Alt], "From Center"),
|
||||
HintInfo::keys([Key::Control], "Lock Angle"),
|
||||
]),
|
||||
]),
|
||||
ShapeToolFsmState::ResizingBounds => HintData(vec![
|
||||
HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]),
|
||||
HintGroup(vec![HintInfo::keys([Key::Alt], "From Pivot"), HintInfo::keys([Key::Shift], "Preserve Aspect Ratio")]),
|
||||
|
||||
Reference in New Issue
Block a user