mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-17 23:38:06 +08:00
make outer and inner same gizmo and fix turns and tightness handle
This commit is contained in:
@@ -372,6 +372,80 @@ impl OverlayContext {
|
||||
self.end_dpi_aware_transform();
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn dashed_ellipse(
|
||||
&mut self,
|
||||
center: DVec2,
|
||||
radius_x: f64,
|
||||
radius_y: f64,
|
||||
rotation: Option<f64>,
|
||||
start_angle: Option<f64>,
|
||||
end_angle: Option<f64>,
|
||||
counterclockwise: Option<bool>,
|
||||
color_fill: Option<&str>,
|
||||
color_stroke: Option<&str>,
|
||||
dash_width: Option<f64>,
|
||||
dash_gap_width: Option<f64>,
|
||||
dash_offset: Option<f64>,
|
||||
) {
|
||||
let color_stroke = color_stroke.unwrap_or(COLOR_OVERLAY_BLUE);
|
||||
let center = center.round();
|
||||
|
||||
self.start_dpi_aware_transform();
|
||||
|
||||
if let Some(dash_width) = dash_width {
|
||||
let dash_gap_width = dash_gap_width.unwrap_or(1.);
|
||||
let array = js_sys::Array::new();
|
||||
array.push(&JsValue::from(dash_width));
|
||||
array.push(&JsValue::from(dash_gap_width));
|
||||
|
||||
if let Some(dash_offset) = dash_offset {
|
||||
if dash_offset != 0. {
|
||||
self.render_context.set_line_dash_offset(dash_offset);
|
||||
}
|
||||
}
|
||||
|
||||
self.render_context
|
||||
.set_line_dash(&JsValue::from(array))
|
||||
.map_err(|error| log::warn!("Error drawing dashed line: {:?}", error))
|
||||
.ok();
|
||||
}
|
||||
|
||||
self.render_context.begin_path();
|
||||
self.render_context
|
||||
.ellipse_with_anticlockwise(
|
||||
center.x,
|
||||
center.y,
|
||||
radius_x,
|
||||
radius_y,
|
||||
rotation.unwrap_or_default(),
|
||||
start_angle.unwrap_or_default(),
|
||||
end_angle.unwrap_or(TAU),
|
||||
counterclockwise.unwrap_or_default(),
|
||||
)
|
||||
.expect("Failed to draw ellipse");
|
||||
self.render_context.set_stroke_style_str(color_stroke);
|
||||
|
||||
if let Some(fill_color) = color_fill {
|
||||
self.render_context.set_fill_style_str(fill_color);
|
||||
self.render_context.fill();
|
||||
}
|
||||
self.render_context.stroke();
|
||||
|
||||
// Reset the dash pattern back to solid
|
||||
if dash_width.is_some() {
|
||||
self.render_context
|
||||
.set_line_dash(&JsValue::from(js_sys::Array::new()))
|
||||
.map_err(|error| log::warn!("Error drawing dashed line: {:?}", error))
|
||||
.ok();
|
||||
}
|
||||
if dash_offset.is_some() && dash_offset != Some(0.) {
|
||||
self.render_context.set_line_dash_offset(0.);
|
||||
}
|
||||
|
||||
self.end_dpi_aware_transform();
|
||||
}
|
||||
|
||||
pub fn dashed_circle(
|
||||
&mut self,
|
||||
position: DVec2,
|
||||
|
||||
@@ -56,19 +56,44 @@ impl RadiusGizmo {
|
||||
pub fn handle_actions(&mut self, layer: LayerNodeIdentifier, document: &DocumentMessageHandler, mouse_position: DVec2, responses: &mut VecDeque<Message>) {
|
||||
match &self.handle_state {
|
||||
RadiusGizmoState::Inactive => {
|
||||
if let Some(((inner_radius, outer_radius, _, _), spiral_type)) = extract_arc_or_log_spiral_parameters(layer, document).zip(get_spiral_type(layer, document)) {
|
||||
let smaller_radius = (inner_radius.min(outer_radius)).max(5.);
|
||||
if let Some(((inner_radius, outer_radius, turns, start_angle), spiral_type)) = extract_arc_or_log_spiral_parameters(layer, document).zip(get_spiral_type(layer, document)) {
|
||||
let viewport = document.metadata().transform_to_viewport(layer);
|
||||
let layer_mouse = viewport.inverse().transform_point2(mouse_position);
|
||||
|
||||
if DVec2::ZERO.distance(layer_mouse) < smaller_radius.max(5.) {
|
||||
let center = viewport.transform_point2(DVec2::ZERO);
|
||||
|
||||
let b = calculate_b(inner_radius, turns, outer_radius, spiral_type);
|
||||
|
||||
let start_radius = spiral_point(0. + start_angle, inner_radius, b, spiral_type).distance(DVec2::ZERO);
|
||||
let end_radius = spiral_point(turns * TAU + start_angle, inner_radius, b, spiral_type).distance(DVec2::ZERO);
|
||||
|
||||
log::info!("start_radius {:?}", start_radius);
|
||||
log::info!("end radius {:?}", end_radius);
|
||||
|
||||
let larger_radius = (start_radius.max(end_radius)).max(5.);
|
||||
let smaller_radius = (start_radius.min(end_radius)).max(5.);
|
||||
|
||||
if layer_mouse.distance(DVec2::ZERO) < smaller_radius {
|
||||
log::info!("reaching heeee");
|
||||
self.layer = Some(layer);
|
||||
self.initial_radius = inner_radius;
|
||||
self.initial_radius = smaller_radius;
|
||||
self.spiral_type = spiral_type;
|
||||
self.previous_mouse_position = mouse_position;
|
||||
self.radius_index = if inner_radius > outer_radius { SPIRAL_OUTER_RADIUS_INDEX } else { SPIRAL_INNER_RADIUS_INDEX };
|
||||
self.update_state(RadiusGizmoState::Hover);
|
||||
responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::EWResize });
|
||||
return;
|
||||
}
|
||||
|
||||
if (layer_mouse.distance(DVec2::ZERO) - larger_radius).abs() < 5. {
|
||||
self.layer = Some(layer);
|
||||
self.initial_radius = larger_radius;
|
||||
self.spiral_type = spiral_type;
|
||||
self.previous_mouse_position = mouse_position;
|
||||
self.radius_index = if inner_radius > outer_radius { SPIRAL_INNER_RADIUS_INDEX } else { SPIRAL_OUTER_RADIUS_INDEX };
|
||||
self.update_state(RadiusGizmoState::Hover);
|
||||
responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::EWResize });
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -82,16 +107,20 @@ impl RadiusGizmo {
|
||||
let Some(layer) = selected_spiral_layer.or(self.layer) else { return };
|
||||
|
||||
let viewport = document.metadata().transform_to_viewport(layer);
|
||||
if let Some(((inner_radius, outer_radius, turns, _), spiral_type)) = extract_arc_or_log_spiral_parameters(layer, document).zip(get_spiral_type(layer, document)) {
|
||||
if let Some(((inner_radius, outer_radius, turns, start_angle), spiral_type)) = extract_arc_or_log_spiral_parameters(layer, document).zip(get_spiral_type(layer, document)) {
|
||||
let b = calculate_b(inner_radius, turns, outer_radius, spiral_type);
|
||||
let (radius, endpoint) = if self.radius_index == SPIRAL_INNER_RADIUS_INDEX {
|
||||
(inner_radius, spiral_point(0., inner_radius, b, spiral_type))
|
||||
let endpoint = if self.radius_index == SPIRAL_INNER_RADIUS_INDEX {
|
||||
spiral_point(0. + start_angle, inner_radius, b, spiral_type)
|
||||
} else {
|
||||
(outer_radius, spiral_point(turns * TAU, inner_radius, b, spiral_type))
|
||||
spiral_point(turns * TAU + start_angle, inner_radius, b, spiral_type)
|
||||
};
|
||||
|
||||
let viewport_center = viewport.transform_point2(DVec2::ZERO);
|
||||
|
||||
let radius = viewport.transform_point2(endpoint).distance(viewport_center);
|
||||
|
||||
overlay_context.manipulator_handle(viewport.transform_point2(endpoint), true, Some(COLOR_OVERLAY_RED));
|
||||
overlay_context.dashed_circle(DVec2::ZERO, radius.max(5.), None, None, Some(4.), Some(4.), Some(0.5), Some(viewport));
|
||||
overlay_context.dashed_ellipse(viewport_center, radius, radius, None, None, None, None, None, None, Some(4.), Some(4.), Some(0.5));
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
@@ -110,19 +139,14 @@ impl RadiusGizmo {
|
||||
.expect("Failed to find inputs of Spiral");
|
||||
|
||||
let viewport_transform = document.network_interface.document_metadata().transform_to_viewport(layer);
|
||||
|
||||
let center = DVec2::ZERO;
|
||||
let layer_drag_start = viewport_transform.inverse().transform_point2(drag_start);
|
||||
|
||||
let current_mouse_layer = viewport_transform.inverse().transform_point2(input.mouse.position);
|
||||
let previous_mouse_layer = viewport_transform.inverse().transform_point2(self.previous_mouse_position);
|
||||
|
||||
let sign = (current_mouse_layer - previous_mouse_layer).dot(layer_drag_start).signum();
|
||||
|
||||
let delta = current_mouse_layer.distance(previous_mouse_layer) * sign;
|
||||
|
||||
let net_radius = current_mouse_layer.distance(DVec2::ZERO);
|
||||
|
||||
let Some(&TaggedValue::F64(radius)) = node_inputs.get(self.radius_index).expect("Failed to get radius of Spiral").as_value() else {
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::consts::{COLOR_OVERLAY_RED, SPIRAL_INNER_RADIUS_INDEX, SPIRAL_INNER_RADIUS_INDEX_GIZMO_THRESHOLD, SPIRAL_OUTER_RADIUS_INDEX};
|
||||
use crate::consts::{SPIRAL_INNER_RADIUS_INDEX, SPIRAL_OUTER_RADIUS_INDEX};
|
||||
use crate::messages::frontend::utility_types::MouseCursorIcon;
|
||||
use crate::messages::message::Message;
|
||||
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
|
||||
@@ -8,15 +8,12 @@ use crate::messages::prelude::Responses;
|
||||
use crate::messages::prelude::{DocumentMessageHandler, FrontendMessage, InputPreprocessorMessageHandler, NodeGraphMessage};
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils::{self};
|
||||
use crate::messages::tool::common_functionality::shape_editor::ShapeState;
|
||||
use crate::messages::tool::common_functionality::shapes::shape_utility::{
|
||||
calculate_b, extract_arc_or_log_spiral_parameters, get_arc_spiral_end_point, get_log_spiral_end_point, get_spiral_type, spiral_point,
|
||||
};
|
||||
use crate::messages::tool::common_functionality::shapes::shape_utility::{calculate_b, extract_arc_or_log_spiral_parameters, get_arc_spiral_end_point, get_spiral_type, spiral_point};
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graph_craft::document::NodeInput;
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graphene_std::uuid::NodeId;
|
||||
use graphene_std::vector::misc::{SpiralType, dvec2_to_point};
|
||||
use kurbo::{Line, ParamCurveNearest};
|
||||
use graphene_std::vector::misc::SpiralType;
|
||||
use std::collections::VecDeque;
|
||||
use std::f64::consts::TAU;
|
||||
|
||||
@@ -28,14 +25,6 @@ pub enum TightnessGizmoState {
|
||||
Dragging,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
enum TightnessGizmoType {
|
||||
#[default]
|
||||
None,
|
||||
Circle,
|
||||
DashLines,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct TightnessGizmo {
|
||||
pub layer: Option<LayerNodeIdentifier>,
|
||||
@@ -47,7 +36,6 @@ pub struct TightnessGizmo {
|
||||
angle: f64,
|
||||
spiral_slot: i32,
|
||||
previous_mouse: DVec2,
|
||||
gizmo_type: TightnessGizmoType,
|
||||
}
|
||||
|
||||
impl TightnessGizmo {
|
||||
@@ -55,7 +43,6 @@ impl TightnessGizmo {
|
||||
self.handle_state = TightnessGizmoState::Inactive;
|
||||
self.layer = None;
|
||||
self.gizmo_line_points = None;
|
||||
self.gizmo_type = TightnessGizmoType::None;
|
||||
}
|
||||
|
||||
pub fn update_state(&mut self, state: TightnessGizmoState) {
|
||||
@@ -76,8 +63,10 @@ impl TightnessGizmo {
|
||||
match &self.handle_state {
|
||||
TightnessGizmoState::Inactive => {
|
||||
// Archimedean
|
||||
if let Some(((a, outer_radius, turns, _), spiral_type)) = extract_arc_or_log_spiral_parameters(layer, document).zip(get_spiral_type(layer, document)) {
|
||||
if let Some((start, end, slot_index)) = Self::check_which_inter_segment(viewport.inverse().transform_point2(mouse_position), outer_radius, turns, a, spiral_type, viewport) {
|
||||
if let Some(((a, outer_radius, turns, start_angle), spiral_type)) = extract_arc_or_log_spiral_parameters(layer, document).zip(get_spiral_type(layer, document)) {
|
||||
if let Some((start, end, slot_index)) =
|
||||
Self::check_which_inter_segment(viewport.inverse().transform_point2(mouse_position), a, outer_radius, turns, start_angle, spiral_type, viewport)
|
||||
{
|
||||
self.layer = Some(layer);
|
||||
self.initial_outer_radius = outer_radius;
|
||||
self.previous_mouse = mouse_position;
|
||||
@@ -85,34 +74,12 @@ impl TightnessGizmo {
|
||||
self.spiral_type = spiral_type;
|
||||
self.spiral_slot = slot_index;
|
||||
self.inner_radius = a;
|
||||
self.gizmo_type = TightnessGizmoType::DashLines;
|
||||
self.angle = viewport.inverse().transform_point2(mouse_position).angle_to(DVec2::X).rem_euclid(TAU);
|
||||
self.update_state(TightnessGizmoState::Hover);
|
||||
responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Default });
|
||||
|
||||
return;
|
||||
};
|
||||
|
||||
let center = viewport.transform_point2(DVec2::ZERO);
|
||||
|
||||
let angle = if a > outer_radius { 0. } else { TAU };
|
||||
|
||||
let Some(endpoint) = get_arc_spiral_end_point(layer, document, viewport, angle).or(get_log_spiral_end_point(layer, document, viewport, angle)) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let close_to_circle = (endpoint.distance(center) - mouse_position.distance(center)).abs() < 5.;
|
||||
|
||||
if close_to_circle {
|
||||
self.layer = Some(layer);
|
||||
self.inner_radius = a;
|
||||
self.initial_outer_radius = outer_radius;
|
||||
self.previous_mouse = mouse_position;
|
||||
self.gizmo_type = TightnessGizmoType::Circle;
|
||||
self.spiral_type = spiral_type;
|
||||
self.update_state(TightnessGizmoState::Hover);
|
||||
responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Default });
|
||||
}
|
||||
}
|
||||
}
|
||||
TightnessGizmoState::Hover | TightnessGizmoState::Dragging => {}
|
||||
@@ -126,35 +93,43 @@ impl TightnessGizmo {
|
||||
let viewport = document.metadata().transform_to_viewport(layer);
|
||||
|
||||
match &self.handle_state {
|
||||
TightnessGizmoState::Hover | TightnessGizmoState::Dragging => match self.gizmo_type {
|
||||
TightnessGizmoType::Circle => {
|
||||
if let Some((inner_radius, outer_radius, _, _)) = extract_arc_or_log_spiral_parameters(layer, document) {
|
||||
let required_radius = if self.inner_radius > self.initial_outer_radius { inner_radius } else { outer_radius };
|
||||
overlay_context.dashed_circle(DVec2::ZERO, required_radius.max(5.), None, None, Some(8.), Some(4.), Some(0.5), Some(viewport));
|
||||
TightnessGizmoState::Hover | TightnessGizmoState::Dragging => {
|
||||
if let Some((start, end)) = self.gizmo_line_points {
|
||||
overlay_context.dashed_line(start, end, None, None, Some(4.0), Some(4.0), Some(0.5));
|
||||
if self.spiral_slot == 0 {
|
||||
let required_radius = if self.inner_radius > self.initial_outer_radius {
|
||||
viewport
|
||||
.inverse()
|
||||
.transform_point2(get_arc_spiral_end_point(layer, document, viewport, TAU).expect("Failed to get endpoints"))
|
||||
.distance(DVec2::ZERO)
|
||||
} else {
|
||||
viewport
|
||||
.inverse()
|
||||
.transform_point2(get_arc_spiral_end_point(layer, document, viewport, 0.).expect("Failed to get endpoints"))
|
||||
.distance(DVec2::ZERO)
|
||||
};
|
||||
overlay_context.dashed_circle(DVec2::ZERO, required_radius.max(5.), None, None, Some(4.), Some(4.), Some(0.5), Some(viewport));
|
||||
}
|
||||
}
|
||||
TightnessGizmoType::DashLines => {
|
||||
if let Some((start, end)) = self.gizmo_line_points {
|
||||
overlay_context.dashed_line(start, end, None, None, Some(4.0), Some(4.0), Some(0.5));
|
||||
if self.spiral_slot == 0 {
|
||||
let required_radius = if self.inner_radius > self.initial_outer_radius {
|
||||
self.initial_outer_radius
|
||||
} else {
|
||||
self.inner_radius
|
||||
};
|
||||
overlay_context.dashed_circle(DVec2::ZERO, required_radius.max(5.), None, None, Some(4.), Some(4.), Some(0.5), Some(viewport));
|
||||
}
|
||||
};
|
||||
}
|
||||
TightnessGizmoType::None => {}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
TightnessGizmoState::Inactive => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_which_inter_segment(layer_mouse_position: DVec2, outer_radius: f64, turns: f64, inner_radius: f64, spiral_type: SpiralType, viewport: DAffine2) -> Option<(DVec2, DVec2, i32)> {
|
||||
fn check_which_inter_segment(
|
||||
layer_mouse_position: DVec2,
|
||||
inner_radius: f64,
|
||||
outer_radius: f64,
|
||||
turns: f64,
|
||||
start_angle: f64,
|
||||
spiral_type: SpiralType,
|
||||
viewport: DAffine2,
|
||||
) -> Option<(DVec2, DVec2, i32)> {
|
||||
let center = DVec2::ZERO;
|
||||
let mut angle = layer_mouse_position.angle_to(DVec2::X).rem_euclid(TAU);
|
||||
let angle = layer_mouse_position.angle_to(DVec2::X).rem_euclid(TAU);
|
||||
let start_angle_rad = start_angle.to_radians();
|
||||
let spiral_theta = (angle - start_angle_rad).rem_euclid(TAU);
|
||||
|
||||
let is_reversed = inner_radius > outer_radius;
|
||||
let b = calculate_b(inner_radius, turns, outer_radius, spiral_type);
|
||||
@@ -163,54 +138,52 @@ impl TightnessGizmo {
|
||||
let viewport_mouse = viewport.transform_point2(layer_mouse_position);
|
||||
let viewport_center = viewport.transform_point2(center);
|
||||
|
||||
// Compute spiral endpoints at θ = 0 and θ = max
|
||||
let spiral_outer = spiral_point(max_theta, inner_radius, b, spiral_type);
|
||||
let spiral_inner = spiral_point(0., inner_radius, b, spiral_type);
|
||||
let viewport_outer = viewport.transform_point2(spiral_outer);
|
||||
let viewport_inner = viewport.transform_point2(spiral_inner);
|
||||
|
||||
let smaller_radius = inner_radius.min(outer_radius);
|
||||
let adjusted_angle = if is_reversed { max_theta - (TAU - angle) } else { angle };
|
||||
let spiral_inner = spiral_point(0.0, inner_radius, b, spiral_type);
|
||||
let viewport_outer = viewport.transform_point2(Self::rotate_point(spiral_outer, start_angle_rad));
|
||||
let viewport_inner = viewport.transform_point2(Self::rotate_point(spiral_inner, start_angle_rad));
|
||||
|
||||
let required_endpoint = if is_reversed { viewport_inner } else { viewport_outer };
|
||||
let mouse_distance = viewport_mouse.distance(viewport_center);
|
||||
let max_radius = required_endpoint.distance(viewport_center);
|
||||
|
||||
// Reject if mouse is beyond spiral's radial extent
|
||||
if viewport_mouse.distance(viewport_center) > required_endpoint.distance(viewport_center) {
|
||||
if mouse_distance > max_radius {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mouse_distance = viewport_mouse.distance(viewport_center);
|
||||
let mut segment_index = 0;
|
||||
|
||||
// First segment: from center to first spiral point at θ = adjusted_angle
|
||||
// First segment: from center to first spiral point at spiral_theta
|
||||
{
|
||||
let spiral_end = spiral_point(adjusted_angle, inner_radius, b, spiral_type);
|
||||
let first_point = viewport.transform_point2(spiral_end);
|
||||
let spiral_end = spiral_point(spiral_theta, inner_radius, b, spiral_type);
|
||||
let first_point = viewport.transform_point2(Self::rotate_point(spiral_end, start_angle_rad));
|
||||
|
||||
let r_end = first_point.distance(viewport_center);
|
||||
|
||||
if mouse_distance <= r_end {
|
||||
let direction = DVec2::new(adjusted_angle.cos(), -adjusted_angle.sin());
|
||||
return Some((viewport.transform_point2(smaller_radius.max(5.) * direction), first_point, segment_index));
|
||||
let direction = DVec2::new(angle.cos(), -angle.sin());
|
||||
let inner_point = viewport.transform_point2(inner_radius.max(5.0) * direction);
|
||||
return Some((inner_point, first_point, segment_index));
|
||||
}
|
||||
|
||||
segment_index += 1;
|
||||
}
|
||||
|
||||
// Loop through each full turn segment along the spiral ray
|
||||
let mut base_theta = adjusted_angle;
|
||||
while if is_reversed { base_theta >= 0. } else { base_theta <= max_theta } {
|
||||
// Remaining segments: full spiral loops
|
||||
let mut base_theta = spiral_theta;
|
||||
while if is_reversed { base_theta >= 0.0 } else { base_theta <= max_theta } {
|
||||
let theta_start = base_theta;
|
||||
let theta_end = if is_reversed { base_theta - TAU } else { base_theta + TAU };
|
||||
|
||||
if (!is_reversed && theta_end > max_theta) || (is_reversed && theta_end < 0.) {
|
||||
if (!is_reversed && theta_end > max_theta) || (is_reversed && theta_end < 0.0) {
|
||||
break;
|
||||
}
|
||||
|
||||
let spiral_start = spiral_point(theta_start, inner_radius, b, spiral_type);
|
||||
let spiral_end = spiral_point(theta_end, inner_radius, b, spiral_type);
|
||||
|
||||
let viewport_start = viewport.transform_point2(spiral_start);
|
||||
let viewport_end = viewport.transform_point2(spiral_end);
|
||||
let viewport_start = viewport.transform_point2(Self::rotate_point(spiral_start, start_angle_rad));
|
||||
let viewport_end = viewport.transform_point2(Self::rotate_point(spiral_end, start_angle_rad));
|
||||
|
||||
let r_start = viewport_start.distance(viewport_center);
|
||||
let r_end = viewport_end.distance(viewport_center);
|
||||
@@ -221,20 +194,37 @@ impl TightnessGizmo {
|
||||
}
|
||||
|
||||
base_theta = if is_reversed { base_theta - TAU } else { base_theta + TAU };
|
||||
|
||||
segment_index += 1;
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn calculate_updated_dash_lines(&self, inner_radius: f64, outer_radius: f64, turns: f64, spiral_type: SpiralType, viewport: DAffine2, drag_start: DVec2, reversed: bool) -> (DVec2, DVec2) {
|
||||
let b = calculate_b(inner_radius, turns, outer_radius, spiral_type);
|
||||
let max_theta = turns * TAU;
|
||||
let base_angle = if reversed { max_theta - (TAU - self.angle) } else { self.angle };
|
||||
let smaller_radius = inner_radius.min(outer_radius);
|
||||
fn rotate_point(p: DVec2, angle: f64) -> DVec2 {
|
||||
let cos_a = angle.cos();
|
||||
let sin_a = angle.sin();
|
||||
DVec2::new(p.x * cos_a - p.y * sin_a, p.x * sin_a + p.y * cos_a)
|
||||
}
|
||||
|
||||
let center = DVec2::ZERO;
|
||||
pub fn calculate_updated_dash_lines(
|
||||
&self,
|
||||
inner_radius: f64,
|
||||
outer_radius: f64,
|
||||
turns: f64,
|
||||
start_angle: f64,
|
||||
spiral_type: SpiralType,
|
||||
viewport: DAffine2,
|
||||
_drag_start: DVec2,
|
||||
reversed: bool,
|
||||
) -> (DVec2, DVec2) {
|
||||
let b = calculate_b(inner_radius, turns, outer_radius, spiral_type);
|
||||
let max_theta = turns * TAU + start_angle.to_radians();
|
||||
let base_angle = if reversed {
|
||||
max_theta - (TAU - self.angle - start_angle.to_radians())
|
||||
} else {
|
||||
self.angle - start_angle.to_radians()
|
||||
};
|
||||
let smaller_radius = inner_radius.min(outer_radius);
|
||||
|
||||
let (start_point, end_point) = if self.spiral_slot == 0 {
|
||||
(
|
||||
@@ -287,7 +277,7 @@ impl TightnessGizmo {
|
||||
let reversed = self.inner_radius > self.initial_outer_radius;
|
||||
self.previous_mouse = input.mouse.position;
|
||||
|
||||
let (a, outer_radius, turns, _) = extract_arc_or_log_spiral_parameters(layer, document).expect("Failed to get archimedean spiral inner radius");
|
||||
let (a, outer_radius, turns, start_angle) = extract_arc_or_log_spiral_parameters(layer, document).expect("Failed to get archimedean spiral inner radius");
|
||||
let (new_inner_radius, turns, new_outer_radius) = match self.spiral_type {
|
||||
SpiralType::Archimedean => {
|
||||
if reversed {
|
||||
@@ -305,8 +295,7 @@ impl TightnessGizmo {
|
||||
}
|
||||
};
|
||||
|
||||
let b = calculate_b(new_inner_radius, turns, new_outer_radius, self.spiral_type);
|
||||
self.gizmo_line_points = Some(self.calculate_updated_dash_lines(new_inner_radius, new_outer_radius, turns, self.spiral_type, viewport_transform, drag_start, reversed));
|
||||
self.gizmo_line_points = Some(self.calculate_updated_dash_lines(new_inner_radius, new_outer_radius, turns, start_angle, self.spiral_type, viewport_transform, drag_start, reversed));
|
||||
|
||||
let (index, new_radius) = if reversed {
|
||||
(SPIRAL_INNER_RADIUS_INDEX, new_inner_radius)
|
||||
@@ -320,27 +309,6 @@ impl TightnessGizmo {
|
||||
});
|
||||
}
|
||||
|
||||
pub fn update_outer_radius_via_circle(&mut self, node_id: NodeId, viewport_transform: DAffine2, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) {
|
||||
let current_mouse_layer = viewport_transform.inverse().transform_point2(input.mouse.position);
|
||||
let net_radius = current_mouse_layer.distance(DVec2::ZERO);
|
||||
|
||||
let net_radius = match self.spiral_type {
|
||||
SpiralType::Archimedean => net_radius.max(0.),
|
||||
SpiralType::Logarithmic => net_radius.max(0.001),
|
||||
};
|
||||
|
||||
let index = if self.initial_outer_radius > self.inner_radius {
|
||||
SPIRAL_OUTER_RADIUS_INDEX
|
||||
} else {
|
||||
SPIRAL_INNER_RADIUS_INDEX
|
||||
};
|
||||
|
||||
responses.add(NodeGraphMessage::SetInput {
|
||||
input_connector: InputConnector::node(node_id, index),
|
||||
input: NodeInput::value(TaggedValue::F64(net_radius), false),
|
||||
});
|
||||
}
|
||||
|
||||
pub fn update_outer_radius(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>, drag_start: DVec2) {
|
||||
let Some(layer) = self.layer else {
|
||||
return;
|
||||
@@ -352,11 +320,7 @@ impl TightnessGizmo {
|
||||
|
||||
let viewport_transform = document.network_interface.document_metadata().transform_to_viewport(layer);
|
||||
|
||||
match &self.gizmo_type {
|
||||
TightnessGizmoType::Circle => self.update_outer_radius_via_circle(node_id, viewport_transform, input, responses),
|
||||
TightnessGizmoType::DashLines => self.update_outer_radius_via_dashed_lines(layer, node_id, viewport_transform, document, input, responses, drag_start),
|
||||
TightnessGizmoType::None => {}
|
||||
}
|
||||
self.update_outer_radius_via_dashed_lines(layer, node_id, viewport_transform, document, input, responses, drag_start);
|
||||
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::consts::{COLOR_OVERLAY_RED, POINT_RADIUS_HANDLE_SNAP_THRESHOLD, SPIRAL_OUTER_RADIUS_INDEX, SPIRAL_TURNS_INDEX};
|
||||
use crate::consts::{COLOR_OVERLAY_RED, POINT_RADIUS_HANDLE_SNAP_THRESHOLD, SPIRAL_OUTER_RADIUS_INDEX, SPIRAL_START_ANGLE, SPIRAL_TURNS_INDEX};
|
||||
use crate::messages::frontend::utility_types::MouseCursorIcon;
|
||||
use crate::messages::message::Message;
|
||||
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
|
||||
@@ -9,7 +9,7 @@ use crate::messages::prelude::{DocumentMessageHandler, FrontendMessage, InputPre
|
||||
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::{
|
||||
calculate_b, extract_arc_or_log_spiral_parameters, get_arc_spiral_end_point, get_log_spiral_end_point, get_spiral_type, spiral_point,
|
||||
calculate_b, extract_arc_or_log_spiral_parameters, get_arc_or_log_spiral_endpoints, get_arc_spiral_end_point, get_log_spiral_end_point, get_spiral_type, spiral_point,
|
||||
};
|
||||
use glam::DVec2;
|
||||
use graph_craft::document::NodeInput;
|
||||
@@ -18,6 +18,14 @@ 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]
|
||||
@@ -34,8 +42,10 @@ pub struct SpiralTurns {
|
||||
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,
|
||||
}
|
||||
|
||||
@@ -43,6 +53,7 @@ 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;
|
||||
}
|
||||
|
||||
@@ -58,14 +69,26 @@ impl SpiralTurns {
|
||||
self.handle_state == SpiralTurnsState::Dragging
|
||||
}
|
||||
|
||||
pub fn store_initial_parameters(&mut self, layer: LayerNodeIdentifier, a: f64, turns: f64, outer_radius: f64, mouse_position: DVec2, spiral_type: SpiralType) {
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -75,12 +98,21 @@ impl SpiralTurns {
|
||||
match &self.handle_state {
|
||||
SpiralTurnsState::Inactive => {
|
||||
// Archimedean
|
||||
if let Some(((inner_radius, outer_radius, turns, _), spiral_type)) = extract_arc_or_log_spiral_parameters(layer, document).zip(get_spiral_type(layer, document)) {
|
||||
if let Some(((inner_radius, outer_radius, turns, start_angle), spiral_type)) = extract_arc_or_log_spiral_parameters(layer, document).zip(get_spiral_type(layer, document)) {
|
||||
let b = calculate_b(inner_radius, turns, outer_radius, spiral_type);
|
||||
let end_point = viewport.transform_point2(spiral_point(turns * TAU, inner_radius, b, 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, spiral_type);
|
||||
self.store_initial_parameters(layer, inner_radius, turns, outer_radius, mouse_position, start_angle, GizmoType::End, spiral_type);
|
||||
responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Default });
|
||||
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);
|
||||
responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Default });
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -97,15 +129,15 @@ impl SpiralTurns {
|
||||
// Is true only when hovered over the gizmo
|
||||
let selected = self.layer.is_some();
|
||||
|
||||
if let Some(endpoint) = get_arc_spiral_end_point(layer, document, viewport, TAU) {
|
||||
overlay_context.manipulator_handle(endpoint, selected, Some(COLOR_OVERLAY_RED));
|
||||
return;
|
||||
let angle = match self.gizmo_type {
|
||||
GizmoType::End => TAU,
|
||||
GizmoType::Start => 0.,
|
||||
GizmoType::None => return,
|
||||
};
|
||||
|
||||
if let Some(endpoint) = get_log_spiral_end_point(layer, document, viewport, TAU) {
|
||||
if let Some(endpoint) = get_arc_or_log_spiral_endpoints(layer, document, viewport, angle) {
|
||||
overlay_context.manipulator_handle(endpoint, selected, Some(COLOR_OVERLAY_RED));
|
||||
return;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -132,22 +164,46 @@ impl SpiralTurns {
|
||||
// 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_inner_radius * (self.initial_b * TAU * turns_delta).exp(),
|
||||
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;
|
||||
};
|
||||
|
||||
responses.add(NodeGraphMessage::SetInput {
|
||||
input_connector: InputConnector::node(node_id, SPIRAL_TURNS_INDEX),
|
||||
input: NodeInput::value(TaggedValue::F64(self.initial_turns + turns_delta), false),
|
||||
});
|
||||
match self.gizmo_type {
|
||||
GizmoType::Start => {
|
||||
let sign = total_delta.signum() * -1.;
|
||||
responses.add(NodeGraphMessage::SetInput {
|
||||
input_connector: InputConnector::node(node_id, SPIRAL_START_ANGLE),
|
||||
input: NodeInput::value(TaggedValue::F64(self.initial_start_angle + total_delta), false),
|
||||
});
|
||||
|
||||
responses.add(NodeGraphMessage::SetInput {
|
||||
input_connector: InputConnector::node(node_id, SPIRAL_OUTER_RADIUS_INDEX),
|
||||
input: NodeInput::value(TaggedValue::F64(self.initial_outer_radius + outer_radius_change), false),
|
||||
});
|
||||
responses.add(NodeGraphMessage::SetInput {
|
||||
input_connector: InputConnector::node(node_id, SPIRAL_TURNS_INDEX),
|
||||
input: NodeInput::value(TaggedValue::F64(self.initial_turns + turns_delta * sign), false),
|
||||
});
|
||||
|
||||
responses.add(NodeGraphMessage::SetInput {
|
||||
input_connector: InputConnector::node(node_id, SPIRAL_OUTER_RADIUS_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, SPIRAL_TURNS_INDEX),
|
||||
input: NodeInput::value(TaggedValue::F64(self.initial_turns + turns_delta), false),
|
||||
});
|
||||
|
||||
responses.add(NodeGraphMessage::SetInput {
|
||||
input_connector: InputConnector::node(node_id, SPIRAL_OUTER_RADIUS_INDEX),
|
||||
input: NodeInput::value(TaggedValue::F64(self.initial_outer_radius + outer_radius_change), false),
|
||||
});
|
||||
}
|
||||
GizmoType::None => {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use super::ShapeToolData;
|
||||
use crate::consts::{SPIRAL_INNER_RADIUS_INDEX, SPIRAL_OUTER_RADIUS_INDEX, SPIRAL_START_ANGLE, SPIRAL_TURNS_INDEX};
|
||||
use crate::consts::{SPIRAL_INNER_RADIUS_INDEX, SPIRAL_OUTER_RADIUS_INDEX, SPIRAL_START_ANGLE, SPIRAL_TURNS_INDEX, SPIRAL_TYPE_INDEX};
|
||||
use crate::messages::message::Message;
|
||||
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
|
||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||
@@ -237,42 +237,42 @@ pub fn extract_star_parameters(layer: Option<LayerNodeIdentifier>, document: &Do
|
||||
pub fn extract_arc_or_log_spiral_parameters(layer: LayerNodeIdentifier, document: &DocumentMessageHandler) -> Option<(f64, f64, f64, f64)> {
|
||||
let node_inputs = NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs("Spiral")?;
|
||||
|
||||
let Some(spiral_type) = get_spiral_type(layer, document) else {
|
||||
let (Some(&TaggedValue::F64(inner_radius)), Some(&TaggedValue::F64(tightness)), Some(&TaggedValue::F64(turns)), Some(&TaggedValue::F64(start_angle))) = (
|
||||
node_inputs.get(SPIRAL_INNER_RADIUS_INDEX)?.as_value(),
|
||||
node_inputs.get(SPIRAL_OUTER_RADIUS_INDEX)?.as_value(),
|
||||
node_inputs.get(SPIRAL_TURNS_INDEX)?.as_value(),
|
||||
node_inputs.get(SPIRAL_START_ANGLE)?.as_value(),
|
||||
) else {
|
||||
return None;
|
||||
};
|
||||
|
||||
if spiral_type == SpiralType::Logarithmic {
|
||||
let (Some(&TaggedValue::F64(inner_radius)), Some(&TaggedValue::F64(tightness)), Some(&TaggedValue::F64(turns)), Some(&TaggedValue::F64(start_angle))) = (
|
||||
node_inputs.get(SPIRAL_INNER_RADIUS_INDEX)?.as_value(),
|
||||
node_inputs.get(SPIRAL_OUTER_RADIUS_INDEX)?.as_value(),
|
||||
node_inputs.get(SPIRAL_TURNS_INDEX)?.as_value(),
|
||||
node_inputs.get(SPIRAL_START_ANGLE)?.as_value(),
|
||||
) else {
|
||||
return None;
|
||||
};
|
||||
|
||||
return Some((inner_radius, tightness, turns, start_angle));
|
||||
}
|
||||
|
||||
None
|
||||
return Some((inner_radius, tightness, turns, start_angle));
|
||||
}
|
||||
|
||||
pub fn get_spiral_type(layer: LayerNodeIdentifier, document: &DocumentMessageHandler) -> Option<SpiralType> {
|
||||
let node_inputs = NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs("Spiral")?;
|
||||
|
||||
let Some(&TaggedValue::SpiralType(spiral_type)) = node_inputs.get(1).expect("Failed to get Spiral Type").as_value() else {
|
||||
let Some(&TaggedValue::SpiralType(spiral_type)) = node_inputs.get(SPIRAL_TYPE_INDEX).expect("Failed to get Spiral Type").as_value() else {
|
||||
return None;
|
||||
};
|
||||
|
||||
Some(spiral_type)
|
||||
}
|
||||
|
||||
pub fn get_arc_or_log_spiral_endpoints(layer: LayerNodeIdentifier, document: &DocumentMessageHandler, viewport: DAffine2, theta: f64) -> Option<DVec2> {
|
||||
let Some(spiral_type) = get_spiral_type(layer, document) else { return None };
|
||||
return match spiral_type {
|
||||
SpiralType::Archimedean => get_arc_spiral_end_point(layer, document, viewport, theta),
|
||||
SpiralType::Logarithmic => get_log_spiral_end_point(layer, document, viewport, theta),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn get_arc_spiral_end_point(layer: LayerNodeIdentifier, document: &DocumentMessageHandler, viewport: DAffine2, theta: f64) -> Option<DVec2> {
|
||||
let Some((a, outer_radius, turns, _)) = extract_arc_or_log_spiral_parameters(layer, document) else {
|
||||
let Some((a, outer_radius, turns, start_angle)) = extract_arc_or_log_spiral_parameters(layer, document) else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let theta = turns * theta;
|
||||
let theta = turns * theta + start_angle.to_radians();
|
||||
let b = calculate_b(a, turns, outer_radius, SpiralType::Archimedean);
|
||||
let r = a + b * theta;
|
||||
|
||||
@@ -280,28 +280,27 @@ pub fn get_arc_spiral_end_point(layer: LayerNodeIdentifier, document: &DocumentM
|
||||
}
|
||||
|
||||
pub fn get_log_spiral_end_point(layer: LayerNodeIdentifier, document: &DocumentMessageHandler, viewport: DAffine2, theta: f64) -> Option<DVec2> {
|
||||
let Some((_, outer_radius, turns, _)) = extract_arc_or_log_spiral_parameters(layer, document) else {
|
||||
let Some((a, outer_radius, turns, start_angle)) = extract_arc_or_log_spiral_parameters(layer, document) else {
|
||||
return None;
|
||||
};
|
||||
|
||||
Some(viewport.transform_point2(outer_radius * DVec2::new((turns * theta).cos(), -(turns * theta).sin())))
|
||||
let theta = turns * theta + start_angle.to_radians();
|
||||
let b = calculate_b(a, turns, outer_radius, SpiralType::Logarithmic);
|
||||
let r = a * (b * theta).exp();
|
||||
Some(viewport.transform_point2(DVec2::new(r * theta.cos(), -r * theta.sin())))
|
||||
}
|
||||
|
||||
pub fn calculate_b(a: f64, turns: f64, outer_radius: f64, spiral_type: SpiralType) -> f64 {
|
||||
let total_theta = turns * TAU;
|
||||
match spiral_type {
|
||||
SpiralType::Archimedean => {
|
||||
let total_theta = turns * TAU;
|
||||
(outer_radius - a) / total_theta
|
||||
}
|
||||
SpiralType::Logarithmic => {
|
||||
let total_theta = turns * TAU;
|
||||
((outer_radius.abs() / a).ln()) / total_theta
|
||||
}
|
||||
SpiralType::Archimedean => (outer_radius - a) / total_theta,
|
||||
SpiralType::Logarithmic => ((outer_radius.abs() / a).ln()) / total_theta,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a point on the given spiral type at angle `theta`.
|
||||
pub fn spiral_point(theta: f64, a: f64, b: f64, spiral_type: SpiralType) -> DVec2 {
|
||||
let theta = theta;
|
||||
match spiral_type {
|
||||
SpiralType::Archimedean => archimedean_spiral_point(theta, a, b),
|
||||
SpiralType::Logarithmic => log_spiral_point(theta, a, b),
|
||||
|
||||
@@ -41,9 +41,9 @@ impl ShapeGizmoHandler for SpiralGizmoHandler {
|
||||
input: &InputPreprocessorMessageHandler,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) {
|
||||
self.radius_handle.handle_actions(selected_spiral_layer, document, input.mouse.position, responses);
|
||||
// self.radius_handle.handle_actions(selected_spiral_layer, document, input.mouse.position, responses);
|
||||
self.turns_handle.handle_actions(selected_spiral_layer, mouse_position, document, responses);
|
||||
self.tightness_handle.handle_actions(selected_spiral_layer, input.mouse.position, document, responses);
|
||||
// self.tightness_handle.handle_actions(selected_spiral_layer, input.mouse.position, document, responses);
|
||||
}
|
||||
|
||||
fn handle_click(&mut self) {
|
||||
@@ -196,6 +196,8 @@ impl Spiral {
|
||||
SpiralType::Logarithmic => (dragged_distance).max(0.1),
|
||||
};
|
||||
|
||||
let angle = ipp.mouse.position.angle_to(DVec2::X);
|
||||
|
||||
responses.add(GraphOperationMessage::TransformSet {
|
||||
layer,
|
||||
transform: DAffine2::from_scale_angle_translation(DVec2::ONE, 0., viewport_drag_start),
|
||||
@@ -228,3 +230,7 @@ impl Spiral {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn calculate_circle_point(theta: f64, radius: f64) -> DVec2 {
|
||||
radius * DVec2::new(theta.cos(), -theta.sin())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user