From 9835b583dac3659e3d4b6f026e5b88a6141ec801 Mon Sep 17 00:00:00 2001 From: 0SlowPoke0 Date: Mon, 14 Jul 2025 16:46:21 +0530 Subject: [PATCH] impl circle shape and circle gizmos --- .../document/overlays/utility_types.rs | 84 +++++++++--- .../gizmos/gizmo_manager.rs | 15 +++ .../shape_gizmos/circle_radius_handle.rs | 124 ++++++++++++++++++ .../gizmos/shape_gizmos/mod.rs | 1 + .../graph_modification_utils.rs | 4 + .../shapes/circle_shape.rs | 104 +++++++++++++++ .../tool/common_functionality/shapes/mod.rs | 1 + .../shapes/shape_utility.rs | 20 ++- .../messages/tool/tool_messages/shape_tool.rs | 16 ++- 9 files changed, 347 insertions(+), 22 deletions(-) create mode 100644 editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/circle_radius_handle.rs create mode 100644 editor/src/messages/tool/common_functionality/shapes/circle_shape.rs diff --git a/editor/src/messages/portfolio/document/overlays/utility_types.rs b/editor/src/messages/portfolio/document/overlays/utility_types.rs index 86cdd85824..0526d56444 100644 --- a/editor/src/messages/portfolio/document/overlays/utility_types.rs +++ b/editor/src/messages/portfolio/document/overlays/utility_types.rs @@ -294,6 +294,73 @@ impl OverlayContext { self.end_dpi_aware_transform(); } + pub fn dashed_circle( + &mut self, + position: DVec2, + radius: f64, + color_fill: Option<&str>, + color_stroke: Option<&str>, + dash_width: Option, + dash_gap_width: Option, + dash_offset: Option, + transform: Option, + ) { + let color_stroke = color_stroke.unwrap_or(COLOR_OVERLAY_BLUE); + let position = position.round(); + + self.start_dpi_aware_transform(); + + if let Some(transform) = transform { + let [a, b, c, d, e, f] = transform.to_cols_array(); + self.render_context.transform(a, b, c, d, e, f).expect("Failed to transform circle"); + } + + 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.arc(position.x, position.y, radius, 0., TAU).expect("Failed to draw the circle"); + 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 circle(&mut self, position: DVec2, radius: f64, color_fill: Option<&str>, color_stroke: Option<&str>) { + self.dashed_circle(position, radius, color_fill, color_stroke, None, None, None, None); + } + pub fn manipulator_handle(&mut self, position: DVec2, selected: bool, color: Option<&str>) { self.start_dpi_aware_transform(); @@ -374,23 +441,6 @@ impl OverlayContext { self.end_dpi_aware_transform(); } - pub fn circle(&mut self, position: DVec2, radius: f64, color_fill: Option<&str>, color_stroke: Option<&str>) { - let color_fill = color_fill.unwrap_or(COLOR_OVERLAY_WHITE); - let color_stroke = color_stroke.unwrap_or(COLOR_OVERLAY_BLUE); - let position = position.round(); - - self.start_dpi_aware_transform(); - - self.render_context.begin_path(); - self.render_context.arc(position.x, position.y, radius, 0., TAU).expect("Failed to draw the circle"); - self.render_context.set_fill_style_str(color_fill); - self.render_context.set_stroke_style_str(color_stroke); - self.render_context.fill(); - self.render_context.stroke(); - - self.end_dpi_aware_transform(); - } - pub fn draw_arc(&mut self, center: DVec2, radius: f64, start_from: f64, end_at: f64) { let segments = ((end_at - start_from).abs() / (std::f64::consts::PI / 4.)).ceil() as usize; let step = (end_at - start_from) / segments as f64; diff --git a/editor/src/messages/tool/common_functionality/gizmos/gizmo_manager.rs b/editor/src/messages/tool/common_functionality/gizmos/gizmo_manager.rs index 703c85e14d..d03ba85b47 100644 --- a/editor/src/messages/tool/common_functionality/gizmos/gizmo_manager.rs +++ b/editor/src/messages/tool/common_functionality/gizmos/gizmo_manager.rs @@ -4,6 +4,7 @@ use crate::messages::portfolio::document::utility_types::document_metadata::Laye use crate::messages::prelude::{DocumentMessageHandler, InputPreprocessorMessageHandler}; use crate::messages::tool::common_functionality::graph_modification_utils; use crate::messages::tool::common_functionality::shape_editor::ShapeState; +use crate::messages::tool::common_functionality::shapes::circle_shape::CircleGizmoHandler; 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; @@ -23,6 +24,7 @@ pub enum ShapeGizmoHandlers { None, Star(StarGizmoHandler), Polygon(PolygonGizmoHandler), + Circle(CircleGizmoHandler), } impl ShapeGizmoHandlers { @@ -32,6 +34,7 @@ impl ShapeGizmoHandlers { match self { Self::Star(_) => "star", Self::Polygon(_) => "polygon", + Self::Circle(_) => "circle", Self::None => "none", } } @@ -41,6 +44,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::Circle(h) => h.handle_state(layer, mouse_position, document, responses), Self::None => {} } } @@ -50,6 +54,7 @@ impl ShapeGizmoHandlers { match self { Self::Star(h) => h.is_any_gizmo_hovered(), Self::Polygon(h) => h.is_any_gizmo_hovered(), + Self::Circle(h) => h.is_any_gizmo_hovered(), Self::None => false, } } @@ -59,6 +64,7 @@ impl ShapeGizmoHandlers { match self { Self::Star(h) => h.handle_click(), Self::Polygon(h) => h.handle_click(), + Self::Circle(h) => h.handle_click(), Self::None => {} } } @@ -68,6 +74,7 @@ impl ShapeGizmoHandlers { 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::Circle(h) => h.handle_update(drag_start, document, input, responses), Self::None => {} } } @@ -77,6 +84,7 @@ impl ShapeGizmoHandlers { match self { Self::Star(h) => h.cleanup(), Self::Polygon(h) => h.cleanup(), + Self::Circle(h) => h.cleanup(), Self::None => {} } } @@ -94,6 +102,7 @@ impl ShapeGizmoHandlers { match self { Self::Star(h) => h.overlays(document, layer, input, shape_editor, mouse_position, overlay_context), Self::Polygon(h) => h.overlays(document, layer, input, shape_editor, mouse_position, overlay_context), + Self::Circle(h) => h.overlays(document, layer, input, shape_editor, mouse_position, overlay_context), Self::None => {} } } @@ -110,6 +119,7 @@ impl ShapeGizmoHandlers { match self { Self::Star(h) => h.dragging_overlays(document, input, shape_editor, mouse_position, overlay_context), Self::Polygon(h) => h.dragging_overlays(document, input, shape_editor, mouse_position, overlay_context), + Self::Circle(h) => h.dragging_overlays(document, input, shape_editor, mouse_position, overlay_context), Self::None => {} } } @@ -147,6 +157,11 @@ impl GizmoManager { return Some(ShapeGizmoHandlers::Polygon(PolygonGizmoHandler::default())); } + // Polygon + if graph_modification_utils::get_circle_id(layer, &document.network_interface).is_some() { + return Some(ShapeGizmoHandlers::Circle(CircleGizmoHandler::default())); + } + None } diff --git a/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/circle_radius_handle.rs b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/circle_radius_handle.rs new file mode 100644 index 0000000000..a817232be5 --- /dev/null +++ b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/circle_radius_handle.rs @@ -0,0 +1,124 @@ +use crate::consts::GIZMO_HIDE_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::{DocumentMessageHandler, InputPreprocessorMessageHandler, NodeGraphMessage}; +use crate::messages::prelude::{FrontendMessage, Responses}; +use crate::messages::tool::common_functionality::graph_modification_utils::{self}; +use crate::messages::tool::common_functionality::shapes::shape_utility::extract_circle_radius; +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 RadiusHandleState { + #[default] + Inactive, + Hover, + Dragging, +} + +#[derive(Clone, Debug, Default, PartialEq)] +pub struct RadiusHandle { + pub layer: Option, + initial_radius: f64, + handle_state: RadiusHandleState, + angle: f64, + previous_mouse_position: DVec2, +} + +impl RadiusHandle { + pub fn cleanup(&mut self) { + self.handle_state = RadiusHandleState::Inactive; + self.layer = None; + } + + pub fn hovered(&self) -> bool { + self.handle_state == RadiusHandleState::Hover + } + + pub fn is_dragging_or_snapped(&self) -> bool { + self.handle_state == RadiusHandleState::Dragging + } + + pub fn update_state(&mut self, state: RadiusHandleState) { + self.handle_state = state; + } + + pub fn handle_actions(&mut self, layer: LayerNodeIdentifier, document: &DocumentMessageHandler, mouse_position: DVec2, responses: &mut VecDeque) { + match &self.handle_state { + RadiusHandleState::Inactive => { + let Some(radius) = extract_circle_radius(layer, document) else { return }; + let viewport = document.metadata().transform_to_viewport(layer); + + let angle = viewport.inverse().transform_point2(mouse_position).angle_to(DVec2::X); + + let point_position = viewport.transform_point2(get_circle_point_position(angle, radius.abs())); + let center = viewport.transform_point2(DVec2::ZERO); + + log::info!("reaching here"); + if point_position.distance(center) < GIZMO_HIDE_THRESHOLD { + return; + } + + if mouse_position.distance(center) <= point_position.distance(center) { + self.layer = Some(layer); + self.initial_radius = radius; + self.previous_mouse_position = mouse_position; + self.angle = angle; + self.update_state(RadiusHandleState::Hover); + responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::EWResize }); + } + } + + RadiusHandleState::Dragging | RadiusHandleState::Hover => {} + } + } + + pub fn overlays(&self, document: &DocumentMessageHandler, overlay_context: &mut OverlayContext) { + match &self.handle_state { + RadiusHandleState::Inactive => {} + + RadiusHandleState::Dragging | RadiusHandleState::Hover => { + let Some(layer) = self.layer else { return }; + let Some(radius) = extract_circle_radius(layer, document) else { return }; + let viewport = document.metadata().transform_to_viewport(layer); + + overlay_context.dashed_circle(DVec2::ZERO, radius.abs(), None, None, Some(20.), Some(4.), Some(0.5), Some(viewport)); + // overlay_context.dashed_line(center, point_position, None, None, Some(4.), Some(4.), Some(0.5)); + } + } + } + + pub fn update_inner_radius(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque, drag_start: DVec2) { + let Some(layer) = self.layer else { return }; + + let Some(node_id) = graph_modification_utils::get_circle_id(layer, &document.network_interface) else { + return; + }; + + let Some(current_radius) = extract_circle_radius(layer, document) else { return }; + + let viewport_transform = document.network_interface.document_metadata().transform_to_viewport(layer); + let center = viewport_transform.transform_point2(DVec2::ZERO); + + let delta_vector = viewport_transform.inverse().transform_point2(input.mouse.position) - viewport_transform.inverse().transform_point2(self.previous_mouse_position); + let radius = document.metadata().document_to_viewport.transform_point2(drag_start) - center; + let sign = radius.dot(delta_vector).signum(); + + let net_delta = delta_vector.length() * sign * self.initial_radius.signum(); + self.previous_mouse_position = input.mouse.position; + + responses.add(NodeGraphMessage::SetInput { + input_connector: InputConnector::node(node_id, 1), + input: NodeInput::value(TaggedValue::F64(current_radius + net_delta), false), + }); + responses.add(NodeGraphMessage::RunDocumentGraph); + } +} + +fn get_circle_point_position(theta: f64, radius: f64) -> DVec2 { + DVec2::new(radius * theta.cos(), -radius * theta.sin()) +} diff --git a/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/mod.rs b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/mod.rs index 2b88dddd5e..3d1683b1a4 100644 --- a/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/mod.rs +++ b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/mod.rs @@ -1,2 +1,3 @@ +pub mod circle_radius_handle; pub mod number_of_points_dial; pub mod point_radius_handle; diff --git a/editor/src/messages/tool/common_functionality/graph_modification_utils.rs b/editor/src/messages/tool/common_functionality/graph_modification_utils.rs index 33c19d0c0d..ee190dccfb 100644 --- a/editor/src/messages/tool/common_functionality/graph_modification_utils.rs +++ b/editor/src/messages/tool/common_functionality/graph_modification_utils.rs @@ -332,6 +332,10 @@ pub fn get_fill_id(layer: LayerNodeIdentifier, network_interface: &NodeNetworkIn NodeGraphLayer::new(layer, network_interface).upstream_node_id_from_name("Fill") } +pub fn get_circle_id(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option { + NodeGraphLayer::new(layer, network_interface).upstream_node_id_from_name("Circle") +} + pub fn get_ellipse_id(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option { NodeGraphLayer::new(layer, network_interface).upstream_node_id_from_name("Ellipse") } diff --git a/editor/src/messages/tool/common_functionality/shapes/circle_shape.rs b/editor/src/messages/tool/common_functionality/shapes/circle_shape.rs new file mode 100644 index 0000000000..a69b085e63 --- /dev/null +++ b/editor/src/messages/tool/common_functionality/shapes/circle_shape.rs @@ -0,0 +1,104 @@ +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::circle_radius_handle::{RadiusHandle, RadiusHandleState}; +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::tool_messages::tool_prelude::*; +use glam::DAffine2; +use graph_craft::document::NodeInput; +use graph_craft::document::value::TaggedValue; + +#[derive(Clone, Debug, Default)] +pub struct CircleGizmoHandler { + circle_radius_handle: RadiusHandle, +} + +impl ShapeGizmoHandler for CircleGizmoHandler { + fn is_any_gizmo_hovered(&self) -> bool { + self.circle_radius_handle.hovered() + } + + fn handle_state(&mut self, selected_circle_layer: LayerNodeIdentifier, mouse_position: DVec2, document: &DocumentMessageHandler, responses: &mut VecDeque) { + self.circle_radius_handle.handle_actions(selected_circle_layer, document, mouse_position, responses); + } + + fn handle_click(&mut self) { + if self.circle_radius_handle.hovered() { + self.circle_radius_handle.update_state(RadiusHandleState::Dragging); + } + } + + fn handle_update(&mut self, drag_start: DVec2, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque) { + if self.circle_radius_handle.is_dragging_or_snapped() { + self.circle_radius_handle.update_inner_radius(document, input, responses, drag_start); + } + } + + fn overlays( + &self, + document: &DocumentMessageHandler, + _selected_circle_layer: Option, + _input: &InputPreprocessorMessageHandler, + _shape_editor: &mut &mut ShapeState, + _mouse_position: DVec2, + overlay_context: &mut OverlayContext, + ) { + self.circle_radius_handle.overlays(document, overlay_context); + } + + fn dragging_overlays( + &self, + document: &DocumentMessageHandler, + _input: &InputPreprocessorMessageHandler, + _shape_editor: &mut &mut ShapeState, + _mouse_position: DVec2, + overlay_context: &mut OverlayContext, + ) { + if self.circle_radius_handle.is_dragging_or_snapped() { + self.circle_radius_handle.overlays(document, overlay_context); + } + } + + fn cleanup(&mut self) { + self.circle_radius_handle.cleanup(); + } +} + +#[derive(Default)] +pub struct Circle; + +impl Circle { + pub fn create_node() -> NodeTemplate { + let node_type = resolve_document_node_type("Circle").expect("Circle can't be found"); + node_type.node_template_input_override([None, Some(NodeInput::value(TaggedValue::F64(0.), false))]) + } + + pub fn update_shape(document: &DocumentMessageHandler, ipp: &InputPreprocessorMessageHandler, layer: LayerNodeIdentifier, responses: &mut VecDeque) { + let Some(node_id) = graph_modification_utils::get_circle_id(layer, &document.network_interface) else { + return; + }; + + let viewport = document.metadata().transform_to_viewport(layer); + let center = viewport.transform_point2(DVec2::ZERO); + + let radius = ipp.mouse.position.distance(center); + + responses.add(NodeGraphMessage::SetInput { + input_connector: InputConnector::node(node_id, 1), + input: NodeInput::value(TaggedValue::F64(radius), false), + }); + + responses.add(GraphOperationMessage::TransformSet { + layer, + transform: DAffine2::from_translation(center), + transform_in: TransformIn::Viewport, + skip_rerender: false, + }); + } +} diff --git a/editor/src/messages/tool/common_functionality/shapes/mod.rs b/editor/src/messages/tool/common_functionality/shapes/mod.rs index 44f40b5982..130207a8c1 100644 --- a/editor/src/messages/tool/common_functionality/shapes/mod.rs +++ b/editor/src/messages/tool/common_functionality/shapes/mod.rs @@ -1,3 +1,4 @@ +pub mod circle_shape; pub mod ellipse_shape; pub mod line_shape; pub mod polygon_shape; diff --git a/editor/src/messages/tool/common_functionality/shapes/shape_utility.rs b/editor/src/messages/tool/common_functionality/shapes/shape_utility.rs index 955984150b..8685e04a9a 100644 --- a/editor/src/messages/tool/common_functionality/shapes/shape_utility.rs +++ b/editor/src/messages/tool/common_functionality/shapes/shape_utility.rs @@ -24,9 +24,10 @@ pub enum ShapeType { #[default] Polygon = 0, Star = 1, - Rectangle = 2, - Ellipse = 3, - Line = 4, + Circle = 2, + Rectangle = 3, + Ellipse = 4, + Line = 5, } impl ShapeType { @@ -37,6 +38,7 @@ impl ShapeType { Self::Rectangle => "Rectangle", Self::Ellipse => "Ellipse", Self::Line => "Line", + Self::Circle => "Circle", }) .into() } @@ -234,6 +236,18 @@ pub fn extract_polygon_parameters(layer: Option, document: Some((n, radius)) } +/// Extract the node input values of Circle. +/// Returns an option of (radius). +pub fn extract_circle_radius(layer: LayerNodeIdentifier, document: &DocumentMessageHandler) -> Option { + let node_inputs = NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs("Circle")?; + + let Some(&TaggedValue::F64(radius)) = node_inputs.get(1)?.as_value() else { + return None; + }; + + Some(radius) +} + /// Calculate the viewport position of as a star vertex given its index pub fn star_vertex_position(viewport: DAffine2, vertex_index: i32, n: u32, radius1: f64, radius2: f64) -> DVec2 { let angle = ((vertex_index as f64) * PI) / (n as f64); diff --git a/editor/src/messages/tool/tool_messages/shape_tool.rs b/editor/src/messages/tool/tool_messages/shape_tool.rs index 0f43adf286..a68879dd3e 100644 --- a/editor/src/messages/tool/tool_messages/shape_tool.rs +++ b/editor/src/messages/tool/tool_messages/shape_tool.rs @@ -10,6 +10,7 @@ use crate::messages::tool::common_functionality::gizmos::gizmo_manager::GizmoMan use crate::messages::tool::common_functionality::graph_modification_utils; use crate::messages::tool::common_functionality::graph_modification_utils::NodeGraphLayer; use crate::messages::tool::common_functionality::resize::Resize; +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::polygon_shape::Polygon; use crate::messages::tool::common_functionality::shapes::shape_utility::{ShapeToolModifierKey, ShapeType, anchor_overlays, transform_cage_overlays}; @@ -109,6 +110,9 @@ fn create_shape_option_widget(shape_type: ShapeType) -> WidgetHolder { MenuListEntry::new("Star") .label("Star") .on_commit(move |_| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::ShapeType(ShapeType::Star)).into()), + MenuListEntry::new("Circle") + .label("Circle") + .on_commit(move |_| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::ShapeType(ShapeType::Circle)).into()), ]]; DropdownInput::new(entries).selected_index(Some(shape_type as u32)).widget_holder() } @@ -422,6 +426,9 @@ impl Fsm for ShapeToolFsmState { if matches!(self, ShapeToolFsmState::Drawing(_) | ShapeToolFsmState::DraggingLineEndpoints) { Line::overlays(document, tool_data, &mut overlay_context); + if matches!(tool_options.shape_type, ShapeType::Circle) { + tool_data.gizmo_manger.overlays(document, input, shape_editor, mouse_position, &mut overlay_context); + } } self @@ -578,7 +585,7 @@ impl Fsm for ShapeToolFsmState { }; match tool_data.current_shape { - ShapeType::Polygon | ShapeType::Star | ShapeType::Ellipse | ShapeType::Rectangle => tool_data.data.start(document, input), + ShapeType::Polygon | ShapeType::Star | ShapeType::Ellipse | ShapeType::Rectangle | ShapeType::Circle => tool_data.data.start(document, input), ShapeType::Line => { let point = SnapCandidatePoint::handle(document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position)); let snapped = tool_data.data.snap_manager.free_snap(&SnapData::new(document, input), &point, SnapTypeConfiguration::default()); @@ -594,6 +601,7 @@ impl Fsm for ShapeToolFsmState { ShapeType::Rectangle => Rectangle::create_node(), ShapeType::Ellipse => Ellipse::create_node(), ShapeType::Line => Line::create_node(document, tool_data.data.drag_start), + ShapeType::Circle => Circle::create_node(), }; let nodes = vec![(NodeId(0), node)]; @@ -602,7 +610,7 @@ impl Fsm for ShapeToolFsmState { responses.add(Message::StartBuffer); match tool_data.current_shape { - ShapeType::Ellipse | ShapeType::Rectangle | ShapeType::Polygon | ShapeType::Star => { + ShapeType::Ellipse | ShapeType::Rectangle | ShapeType::Polygon | ShapeType::Star | ShapeType::Circle => { responses.add(GraphOperationMessage::TransformSet { layer, transform: DAffine2::from_scale_angle_translation(DVec2::ONE, 0., input.mouse.position), @@ -635,6 +643,7 @@ impl Fsm for ShapeToolFsmState { ShapeType::Line => Line::update_shape(document, input, layer, tool_data, modifier, responses), ShapeType::Polygon => Polygon::update_shape(document, input, layer, tool_data, modifier, responses), ShapeType::Star => Star::update_shape(document, input, layer, tool_data, modifier, responses), + ShapeType::Circle => Circle::update_shape(document, input, layer, responses), } // Auto-panning @@ -814,6 +823,7 @@ impl Fsm for ShapeToolFsmState { tool_data.data.cleanup(responses); tool_data.current_shape = shape; + responses.add(ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::ShapeType(shape))); ShapeToolFsmState::Ready(shape) } (_, ShapeToolMessage::HideShapeTypeWidget(hide)) => { @@ -853,6 +863,7 @@ impl Fsm for ShapeToolFsmState { HintInfo::keys([Key::Shift], "Constrain Square").prepend_plus(), HintInfo::keys([Key::Alt], "From Center").prepend_plus(), ])], + ShapeType::Circle => vec![HintGroup(vec![HintInfo::mouse(MouseMotion::LmbDrag, "Draw Circle")])], }; HintData(hint_groups) } @@ -867,6 +878,7 @@ impl Fsm for ShapeToolFsmState { HintInfo::keys([Key::Alt], "From Center"), HintInfo::keys([Key::Control], "Lock Angle"), ]), + ShapeType::Circle => HintGroup(vec![]), }; common_hint_group.push(tool_hint_group);