This commit is contained in:
0SlowPoke0 2025-12-21 18:19:23 +01:00 committed by GitHub
commit b90fd49519
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 372 additions and 8 deletions

View file

@ -10,6 +10,7 @@ use crate::messages::tool::common_functionality::shapes::circle_shape::CircleGiz
use crate::messages::tool::common_functionality::shapes::grid_shape::GridGizmoHandler;
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::spiral_shape::SpiralGizmoHandler;
use crate::messages::tool::common_functionality::shapes::star_shape::StarGizmoHandler;
use glam::DVec2;
use std::collections::VecDeque;
@ -30,6 +31,7 @@ pub enum ShapeGizmoHandlers {
Arc(ArcGizmoHandler),
Circle(CircleGizmoHandler),
Grid(GridGizmoHandler),
Spiral(SpiralGizmoHandler),
}
impl ShapeGizmoHandlers {
@ -42,6 +44,7 @@ impl ShapeGizmoHandlers {
Self::Arc(_) => "arc",
Self::Circle(_) => "circle",
Self::Grid(_) => "grid",
Self::Spiral(_) => "spiral",
Self::None => "none",
}
}
@ -51,6 +54,7 @@ impl ShapeGizmoHandlers {
match self {
Self::Star(h) => h.handle_state(layer, mouse_position, document, responses),
Self::Polygon(h) => h.handle_state(layer, mouse_position, document, responses),
Self::Spiral(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::Grid(h) => h.handle_state(layer, mouse_position, document, responses),
@ -66,6 +70,7 @@ impl ShapeGizmoHandlers {
Self::Arc(h) => h.is_any_gizmo_hovered(),
Self::Circle(h) => h.is_any_gizmo_hovered(),
Self::Grid(h) => h.is_any_gizmo_hovered(),
Self::Spiral(h) => h.is_any_gizmo_hovered(),
Self::None => false,
}
}
@ -78,6 +83,7 @@ impl ShapeGizmoHandlers {
Self::Arc(h) => h.handle_click(),
Self::Circle(h) => h.handle_click(),
Self::Grid(h) => h.handle_click(),
Self::Spiral(h) => h.handle_click(),
Self::None => {}
}
}
@ -90,6 +96,7 @@ impl ShapeGizmoHandlers {
Self::Arc(h) => h.handle_update(drag_start, document, input, responses),
Self::Circle(h) => h.handle_update(drag_start, document, input, responses),
Self::Grid(h) => h.handle_update(drag_start, document, input, responses),
Self::Spiral(h) => h.handle_update(drag_start, document, input, responses),
Self::None => {}
}
}
@ -102,6 +109,7 @@ impl ShapeGizmoHandlers {
Self::Arc(h) => h.cleanup(),
Self::Circle(h) => h.cleanup(),
Self::Grid(h) => h.cleanup(),
Self::Spiral(h) => h.cleanup(),
Self::None => {}
}
}
@ -122,6 +130,7 @@ impl ShapeGizmoHandlers {
Self::Arc(h) => h.overlays(document, layer, input, shape_editor, mouse_position, overlay_context),
Self::Circle(h) => h.overlays(document, layer, input, shape_editor, mouse_position, overlay_context),
Self::Grid(h) => h.overlays(document, layer, input, shape_editor, mouse_position, overlay_context),
Self::Spiral(h) => h.overlays(document, layer, input, shape_editor, mouse_position, overlay_context),
Self::None => {}
}
}
@ -141,6 +150,7 @@ impl ShapeGizmoHandlers {
Self::Arc(h) => h.dragging_overlays(document, input, shape_editor, mouse_position, overlay_context),
Self::Circle(h) => h.dragging_overlays(document, input, shape_editor, mouse_position, overlay_context),
Self::Grid(h) => h.dragging_overlays(document, input, shape_editor, mouse_position, overlay_context),
Self::Spiral(h) => h.dragging_overlays(document, input, shape_editor, mouse_position, overlay_context),
Self::None => {}
}
}
@ -152,6 +162,7 @@ impl ShapeGizmoHandlers {
Self::Arc(h) => h.mouse_cursor_icon(),
Self::Circle(h) => h.mouse_cursor_icon(),
Self::Grid(h) => h.mouse_cursor_icon(),
Self::Spiral(h) => h.mouse_cursor_icon(),
Self::None => None,
}
}
@ -200,6 +211,11 @@ impl GizmoManager {
return Some(ShapeGizmoHandlers::Grid(GridGizmoHandler::default()));
}
// Spiral
if graph_modification_utils::get_spiral_id(layer, &document.network_interface).is_some() {
return Some(ShapeGizmoHandlers::Spiral(SpiralGizmoHandler::default()));
}
None
}

View file

@ -2,4 +2,5 @@ pub mod circle_arc_radius_handle;
pub mod grid_rows_columns_gizmo;
pub mod number_of_points_dial;
pub mod point_radius_handle;
pub mod spiral_turns_handle;
pub mod sweep_angle_gizmo;

View file

@ -41,7 +41,7 @@ impl NumberOfPointsDial {
self.handle_state = state;
}
pub fn is_hovering(&self) -> bool {
pub fn hovered(&self) -> bool {
self.handle_state == NumberOfPointsDialState::Hover
}

View file

@ -0,0 +1,233 @@
use crate::consts::{COLOR_OVERLAY_RED, POINT_RADIUS_HANDLE_SNAP_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;
use crate::messages::tool::common_functionality::shape_editor::ShapeState;
use crate::messages::tool::common_functionality::shapes::shape_utility::extract_spiral_parameters;
use crate::messages::tool::common_functionality::shapes::spiral_shape::calculate_spiral_endpoints;
use glam::DVec2;
use graph_craft::document::NodeInput;
use graph_craft::document::value::TaggedValue;
use graphene_std::NodeInputDecleration;
use graphene_std::subpath::{calculate_b, spiral_point};
use graphene_std::vector::misc::SpiralType;
use std::collections::VecDeque;
use std::f64::consts::TAU;
#[derive(Clone, Debug, Default, PartialEq)]
pub enum GizmoType {
#[default]
None,
Start,
End,
}
#[derive(Clone, Debug, Default, PartialEq)]
pub enum SpiralTurnsState {
#[default]
Inactive,
Hover,
Dragging,
}
#[derive(Clone, Debug, Default)]
pub struct SpiralTurns {
pub layer: Option<LayerNodeIdentifier>,
pub handle_state: SpiralTurnsState,
initial_turns: f64,
initial_outer_radius: f64,
initial_inner_radius: f64,
initial_b: f64,
initial_start_angle: f64,
previous_mouse_position: DVec2,
total_angle_delta: f64,
gizmo_type: GizmoType,
spiral_type: SpiralType,
}
impl SpiralTurns {
pub fn cleanup(&mut self) {
self.handle_state = SpiralTurnsState::Inactive;
self.total_angle_delta = 0.;
self.gizmo_type = GizmoType::None;
self.layer = None;
}
pub fn update_state(&mut self, state: SpiralTurnsState) {
self.handle_state = state;
}
pub fn hovered(&self) -> bool {
self.handle_state == SpiralTurnsState::Hover
}
pub fn is_dragging(&self) -> bool {
self.handle_state == SpiralTurnsState::Dragging
}
pub fn store_initial_parameters(
&mut self,
layer: LayerNodeIdentifier,
a: f64,
turns: f64,
outer_radius: f64,
mouse_position: DVec2,
start_angle: f64,
gizmo_type: GizmoType,
spiral_type: SpiralType,
) {
self.layer = Some(layer);
self.initial_turns = turns;
self.initial_b = calculate_b(a, turns, outer_radius, spiral_type);
self.initial_inner_radius = a;
self.initial_outer_radius = outer_radius;
self.initial_start_angle = start_angle;
self.previous_mouse_position = mouse_position;
self.spiral_type = spiral_type;
self.gizmo_type = gizmo_type;
self.update_state(SpiralTurnsState::Hover);
}
pub fn handle_actions(&mut self, layer: LayerNodeIdentifier, mouse_position: DVec2, document: &DocumentMessageHandler, _responses: &mut VecDeque<Message>) {
let viewport = document.metadata().transform_to_viewport(layer);
match &self.handle_state {
SpiralTurnsState::Inactive => {
// Archimedean
if let Some((spiral_type, start_angle, inner_radius, outer_radius, turns, _)) = extract_spiral_parameters(layer, document) {
let b = calculate_b(inner_radius, turns, outer_radius, spiral_type);
let end_point = viewport.transform_point2(spiral_point(turns * TAU + start_angle.to_radians(), inner_radius, b, spiral_type));
let start_point = viewport.transform_point2(spiral_point(0. + start_angle.to_radians(), inner_radius, b, spiral_type));
if mouse_position.distance(end_point) < POINT_RADIUS_HANDLE_SNAP_THRESHOLD {
self.store_initial_parameters(layer, inner_radius, turns, outer_radius, mouse_position, start_angle, GizmoType::End, spiral_type);
return;
}
if mouse_position.distance(start_point) < POINT_RADIUS_HANDLE_SNAP_THRESHOLD {
self.store_initial_parameters(layer, inner_radius, turns, outer_radius, mouse_position, start_angle, GizmoType::Start, spiral_type);
return;
}
}
}
SpiralTurnsState::Hover | SpiralTurnsState::Dragging => {}
}
}
pub fn overlays(&self, document: &DocumentMessageHandler, layer: Option<LayerNodeIdentifier>, _shape_editor: &mut &mut ShapeState, _mouse_position: DVec2, overlay_context: &mut OverlayContext) {
let Some(layer) = layer.or(self.layer) else { return };
let viewport = document.metadata().transform_to_viewport(layer);
match &self.handle_state {
SpiralTurnsState::Inactive => {
if let Some((p1, p2)) = calculate_spiral_endpoints(layer, document, viewport, 0.).zip(calculate_spiral_endpoints(layer, document, viewport, TAU)) {
overlay_context.manipulator_handle(p1, false, None);
overlay_context.manipulator_handle(p2, false, None);
}
}
SpiralTurnsState::Hover | SpiralTurnsState::Dragging => {
// Is true only when hovered over the gizmo
let selected = self.layer.is_some();
let angle = match self.gizmo_type {
GizmoType::End => TAU,
GizmoType::Start => 0.,
GizmoType::None => return,
};
if let Some(endpoint) = calculate_spiral_endpoints(layer, document, viewport, angle) {
overlay_context.manipulator_handle(endpoint, selected, Some(COLOR_OVERLAY_RED));
}
}
}
}
pub fn update_number_of_turns(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) {
use graphene_std::vector::generator_nodes::spiral::*;
let Some(layer) = self.layer else {
return;
};
let viewport = document.metadata().transform_to_viewport(layer);
let center = viewport.transform_point2(DVec2::ZERO);
let angle_delta = viewport
.inverse()
.transform_vector2(input.mouse.position - center)
.angle_to(viewport.inverse().transform_vector2(self.previous_mouse_position - center))
.to_degrees();
log::info!("{:?}", input.mouse.position);
log::info!("{:?}", self.previous_mouse_position);
log::info!(
"angle is {:?}",
viewport
.inverse()
.transform_point2(input.mouse.position)
.angle_to(viewport.inverse().transform_point2(self.previous_mouse_position))
.to_degrees()
);
// Increase the number of turns and outer radius in unison such that growth and tightness remain same
let total_delta = self.total_angle_delta + angle_delta;
// Convert the total angle (in degrees) to number of full turns
let turns_delta = total_delta / 360.;
// Calculate the new outer radius based on spiral type and turn change
let outer_radius_change = match self.spiral_type {
SpiralType::Archimedean => turns_delta * (self.initial_b) * TAU,
SpiralType::Logarithmic => self.initial_outer_radius * ((self.initial_b * TAU * turns_delta).exp() - 1.),
};
let Some(node_id) = graph_modification_utils::get_spiral_id(layer, &document.network_interface) else {
return;
};
match self.gizmo_type {
GizmoType::Start => {
let sign = -1.;
responses.add(NodeGraphMessage::SetInput {
input_connector: InputConnector::node(node_id, StartAngleInput::INDEX),
input: NodeInput::value(TaggedValue::F64(self.initial_start_angle + total_delta), false),
});
responses.add(NodeGraphMessage::SetInput {
input_connector: InputConnector::node(node_id, TurnsInput::INDEX),
input: NodeInput::value(TaggedValue::F64(self.initial_turns + turns_delta * sign), false),
});
responses.add(NodeGraphMessage::SetInput {
input_connector: InputConnector::node(node_id, OuterRadiusInput::INDEX),
input: NodeInput::value(TaggedValue::F64(self.initial_outer_radius + outer_radius_change * sign), false),
});
}
GizmoType::End => {
responses.add(NodeGraphMessage::SetInput {
input_connector: InputConnector::node(node_id, TurnsInput::INDEX),
input: NodeInput::value(TaggedValue::F64(self.initial_turns + turns_delta), false),
});
responses.add(NodeGraphMessage::SetInput {
input_connector: InputConnector::node(node_id, OuterRadiusInput::INDEX),
input: NodeInput::value(TaggedValue::F64(self.initial_outer_radius + outer_radius_change), false),
});
}
GizmoType::None => {
return;
}
}
responses.add(NodeGraphMessage::RunDocumentGraph);
self.total_angle_delta += angle_delta;
self.previous_mouse_position = input.mouse.position;
}
}

View file

@ -25,7 +25,7 @@ pub struct PolygonGizmoHandler {
impl ShapeGizmoHandler for PolygonGizmoHandler {
fn is_any_gizmo_hovered(&self) -> bool {
self.number_of_points_dial.is_hovering() || self.point_radius_handle.hovered()
self.number_of_points_dial.hovered() || self.point_radius_handle.hovered()
}
fn handle_state(&mut self, selected_star_layer: LayerNodeIdentifier, mouse_position: DVec2, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
@ -34,7 +34,7 @@ impl ShapeGizmoHandler for PolygonGizmoHandler {
}
fn handle_click(&mut self) {
if self.number_of_points_dial.is_hovering() {
if self.number_of_points_dial.hovered() {
self.number_of_points_dial.update_state(NumberOfPointsDialState::Dragging);
return;
}
@ -87,7 +87,7 @@ impl ShapeGizmoHandler for PolygonGizmoHandler {
}
fn mouse_cursor_icon(&self) -> Option<MouseCursorIcon> {
if self.number_of_points_dial.is_dragging() || self.number_of_points_dial.is_hovering() {
if self.number_of_points_dial.is_dragging() || self.number_of_points_dial.hovered() {
return Some(MouseCursorIcon::EWResize);
}

View file

@ -17,7 +17,7 @@ use graph_craft::document::value::TaggedValue;
use graphene_std::NodeInputDecleration;
use graphene_std::subpath::{self, Subpath};
use graphene_std::vector::click_target::ClickTargetType;
use graphene_std::vector::misc::{ArcType, GridType, dvec2_to_point};
use graphene_std::vector::misc::{ArcType, GridType, SpiralType, dvec2_to_point};
use kurbo::{BezPath, PathEl, Shape};
use std::collections::VecDeque;
use std::f64::consts::{PI, TAU};
@ -243,6 +243,35 @@ pub fn extract_star_parameters(layer: Option<LayerNodeIdentifier>, document: &Do
Some((sides, radius_1, radius_2))
}
/// Extract the node input values of spiral.
/// Returns an option of (spiral type, start angle, inner radius, outer radius, turns, angle resolution).
pub fn extract_spiral_parameters(layer: LayerNodeIdentifier, document: &DocumentMessageHandler) -> Option<(SpiralType, f64, f64, f64, f64, f64)> {
use graphene_std::vector::generator_nodes::spiral::*;
let node_inputs = NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs("Spiral")?;
let (
Some(&TaggedValue::SpiralType(spiral_type)),
Some(&TaggedValue::F64(start_angle)),
Some(&TaggedValue::F64(inner_radius)),
Some(&TaggedValue::F64(outer_radius)),
Some(&TaggedValue::F64(turns)),
Some(&TaggedValue::F64(angle_resolution)),
) = (
node_inputs.get(SpiralTypeInput::INDEX)?.as_value(),
node_inputs.get(StartAngleInput::INDEX)?.as_value(),
node_inputs.get(InnerRadiusInput::INDEX)?.as_value(),
node_inputs.get(OuterRadiusInput::INDEX)?.as_value(),
node_inputs.get(TurnsInput::INDEX)?.as_value(),
node_inputs.get(AngularResolutionInput::INDEX)?.as_value(),
)
else {
return None;
};
Some((spiral_type, start_angle, inner_radius, outer_radius, turns, angle_resolution))
}
/// 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)> {

View file

@ -1,9 +1,13 @@
use super::*;
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;
use crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type;
use crate::messages::portfolio::document::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::spiral_turns_handle::{SpiralTurns, SpiralTurnsState};
use crate::messages::tool::common_functionality::graph_modification_utils::{self, NodeGraphLayer};
use crate::messages::tool::common_functionality::shape_editor::ShapeState;
use crate::messages::tool::common_functionality::shapes::shape_utility::{ShapeGizmoHandler, extract_spiral_parameters};
use crate::messages::tool::common_functionality::snapping::{SnapCandidatePoint, SnapData, SnapTypeConfiguration};
use crate::messages::tool::tool_messages::shape_tool::ShapeOptionsUpdate;
use crate::messages::tool::tool_messages::tool_prelude::*;
@ -11,12 +15,82 @@ use glam::DAffine2;
use graph_craft::document::NodeInput;
use graph_craft::document::value::TaggedValue;
use graphene_std::NodeInputDecleration;
use graphene_std::subpath::{calculate_b, spiral_point};
use graphene_std::vector::misc::SpiralType;
use std::collections::VecDeque;
#[derive(Default)]
pub struct Spiral;
#[derive(Clone, Debug, Default)]
pub struct SpiralGizmoHandler {
turns_handle: SpiralTurns,
}
impl ShapeGizmoHandler for SpiralGizmoHandler {
fn is_any_gizmo_hovered(&self) -> bool {
self.turns_handle.hovered()
}
fn handle_state(&mut self, selected_spiral_layer: LayerNodeIdentifier, mouse_position: DVec2, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
self.turns_handle.handle_actions(selected_spiral_layer, mouse_position, document, responses);
}
fn handle_click(&mut self) {
if self.turns_handle.hovered() {
self.turns_handle.update_state(SpiralTurnsState::Dragging);
return;
}
}
fn handle_update(&mut self, _drag_start: DVec2, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) {
if self.turns_handle.is_dragging() {
self.turns_handle.update_number_of_turns(document, input, responses);
}
}
fn overlays(
&self,
document: &DocumentMessageHandler,
selected_spiral_layer: Option<LayerNodeIdentifier>,
_input: &InputPreprocessorMessageHandler,
shape_editor: &mut &mut ShapeState,
mouse_position: DVec2,
overlay_context: &mut OverlayContext,
) {
if self.turns_handle.hovered() {
self.turns_handle.overlays(document, selected_spiral_layer, shape_editor, mouse_position, overlay_context);
return;
}
self.turns_handle.overlays(document, selected_spiral_layer, shape_editor, mouse_position, overlay_context);
}
fn dragging_overlays(
&self,
document: &DocumentMessageHandler,
_input: &InputPreprocessorMessageHandler,
shape_editor: &mut &mut ShapeState,
mouse_position: DVec2,
overlay_context: &mut OverlayContext,
) {
if self.turns_handle.is_dragging() {
self.turns_handle.overlays(document, None, shape_editor, mouse_position, overlay_context);
}
}
fn mouse_cursor_icon(&self) -> Option<MouseCursorIcon> {
if self.turns_handle.hovered() || self.turns_handle.is_dragging() {
return Some(MouseCursorIcon::Default);
}
None
}
fn cleanup(&mut self) {
self.turns_handle.cleanup();
}
}
impl Spiral {
pub fn create_node(spiral_type: SpiralType, turns: f64) -> NodeTemplate {
let inner_radius = match spiral_type {
@ -121,3 +195,14 @@ impl Spiral {
});
}
}
pub fn calculate_spiral_endpoints(layer: LayerNodeIdentifier, document: &DocumentMessageHandler, viewport: DAffine2, theta: f64) -> Option<DVec2> {
let Some((spiral_type, start_angle, a, outer_radius, turns, _)) = extract_spiral_parameters(layer, document) else {
return None;
};
let theta = turns * theta + start_angle.to_radians();
let b = calculate_b(a, turns, outer_radius, spiral_type);
Some(viewport.transform_point2(spiral_point(theta, a, b, spiral_type)))
}

View file

@ -25,7 +25,7 @@ pub struct StarGizmoHandler {
impl ShapeGizmoHandler for StarGizmoHandler {
fn is_any_gizmo_hovered(&self) -> bool {
self.number_of_points_dial.is_hovering() || self.point_radius_handle.hovered()
self.number_of_points_dial.hovered() || self.point_radius_handle.hovered()
}
fn handle_state(&mut self, selected_star_layer: LayerNodeIdentifier, mouse_position: DVec2, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
@ -34,7 +34,7 @@ impl ShapeGizmoHandler for StarGizmoHandler {
}
fn handle_click(&mut self) {
if self.number_of_points_dial.is_hovering() {
if self.number_of_points_dial.hovered() {
self.number_of_points_dial.update_state(NumberOfPointsDialState::Dragging);
return;
}
@ -92,7 +92,7 @@ impl ShapeGizmoHandler for StarGizmoHandler {
}
fn mouse_cursor_icon(&self) -> Option<MouseCursorIcon> {
if self.number_of_points_dial.is_dragging() || self.number_of_points_dial.is_hovering() {
if self.number_of_points_dial.is_dragging() || self.number_of_points_dial.hovered() {
return Some(MouseCursorIcon::EWResize);
}