Add 'Circle' to the Shape tool and its associated gizmos (#2914)
* merged with circle and impl inner radius gizmo for arc * impl radius-gizmo for arc * fix only one gizmo shown at a time * Code review * make hints update when changing shape,add default behaviour when dragging to make circle earlier fixed to from center * fixed arc-radius hover threshold and show arc-endpoint when hover over arc-radius gizmo --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
parent
97bd0ebac4
commit
523132da17
|
|
@ -294,6 +294,147 @@ impl OverlayContext {
|
||||||
self.end_dpi_aware_transform();
|
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,
|
||||||
|
radius: f64,
|
||||||
|
color_fill: Option<&str>,
|
||||||
|
color_stroke: Option<&str>,
|
||||||
|
dash_width: Option<f64>,
|
||||||
|
dash_gap_width: Option<f64>,
|
||||||
|
dash_offset: Option<f64>,
|
||||||
|
transform: Option<DAffine2>,
|
||||||
|
) {
|
||||||
|
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>) {
|
pub fn manipulator_handle(&mut self, position: DVec2, selected: bool, color: Option<&str>) {
|
||||||
self.start_dpi_aware_transform();
|
self.start_dpi_aware_transform();
|
||||||
|
|
||||||
|
|
@ -374,23 +515,6 @@ impl OverlayContext {
|
||||||
self.end_dpi_aware_transform();
|
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) {
|
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 segments = ((end_at - start_from).abs() / (std::f64::consts::PI / 4.)).ceil() as usize;
|
||||||
let step = (end_at - start_from) / segments as f64;
|
let step = (end_at - start_from) / segments as f64;
|
||||||
|
|
@ -591,7 +715,7 @@ impl OverlayContext {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn arc_sweep_angle(&mut self, offset_angle: f64, angle: f64, end_point_position: DVec2, bold_radius: f64, pivot: DVec2, text: &str, transform: DAffine2) {
|
pub fn arc_sweep_angle(&mut self, offset_angle: f64, angle: f64, end_point_position: DVec2, bold_radius: f64, pivot: DVec2, text: &str, transform: DAffine2) {
|
||||||
self.manipulator_handle(end_point_position, true, Some(COLOR_OVERLAY_RED));
|
self.manipulator_handle(end_point_position, true, None);
|
||||||
self.draw_arc_gizmo_angle(pivot, bold_radius, ARC_SWEEP_GIZMO_RADIUS, offset_angle, angle.to_radians());
|
self.draw_arc_gizmo_angle(pivot, bold_radius, ARC_SWEEP_GIZMO_RADIUS, offset_angle, angle.to_radians());
|
||||||
self.text(&text, COLOR_OVERLAY_BLUE, None, transform, 16., [Pivot::Middle, Pivot::Middle]);
|
self.text(&text, COLOR_OVERLAY_BLUE, None, transform, 16., [Pivot::Middle, Pivot::Middle]);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -345,6 +345,23 @@ impl OverlayContext {
|
||||||
self.scene.stroke(&kurbo::Stroke::new(1.0), transform, Self::parse_color(color_stroke), None, &circle);
|
self.scene.stroke(&kurbo::Stroke::new(1.0), transform, Self::parse_color(color_stroke), None, &circle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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>,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
pub fn draw_arc(&mut self, center: DVec2, radius: f64, start_from: f64, end_at: f64) {
|
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 segments = ((end_at - start_from).abs() / (std::f64::consts::PI / 4.)).ceil() as usize;
|
||||||
let step = (end_at - start_from) / segments as f64;
|
let step = (end_at - start_from) / segments as f64;
|
||||||
|
|
@ -541,7 +558,7 @@ impl OverlayContext {
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn arc_sweep_angle(&mut self, offset_angle: f64, angle: f64, end_point_position: DVec2, bold_radius: f64, pivot: DVec2, text: &str, transform: DAffine2) {
|
pub fn arc_sweep_angle(&mut self, offset_angle: f64, angle: f64, end_point_position: DVec2, bold_radius: f64, pivot: DVec2, text: &str, transform: DAffine2) {
|
||||||
self.manipulator_handle(end_point_position, true, Some(COLOR_OVERLAY_RED));
|
self.manipulator_handle(end_point_position, true, None);
|
||||||
self.draw_arc_gizmo_angle(pivot, bold_radius, ARC_SWEEP_GIZMO_RADIUS, offset_angle, angle.to_radians());
|
self.draw_arc_gizmo_angle(pivot, bold_radius, ARC_SWEEP_GIZMO_RADIUS, offset_angle, angle.to_radians());
|
||||||
self.text(text, COLOR_OVERLAY_BLUE, None, transform, 16., [Pivot::Middle, Pivot::Middle]);
|
self.text(text, COLOR_OVERLAY_BLUE, None, transform, 16., [Pivot::Middle, Pivot::Middle]);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ use crate::messages::prelude::{DocumentMessageHandler, InputPreprocessorMessageH
|
||||||
use crate::messages::tool::common_functionality::graph_modification_utils;
|
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||||
use crate::messages::tool::common_functionality::shape_editor::ShapeState;
|
use crate::messages::tool::common_functionality::shape_editor::ShapeState;
|
||||||
use crate::messages::tool::common_functionality::shapes::arc_shape::ArcGizmoHandler;
|
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::polygon_shape::PolygonGizmoHandler;
|
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::shape_utility::ShapeGizmoHandler;
|
||||||
use crate::messages::tool::common_functionality::shapes::star_shape::StarGizmoHandler;
|
use crate::messages::tool::common_functionality::shapes::star_shape::StarGizmoHandler;
|
||||||
|
|
@ -26,6 +27,7 @@ pub enum ShapeGizmoHandlers {
|
||||||
Star(StarGizmoHandler),
|
Star(StarGizmoHandler),
|
||||||
Polygon(PolygonGizmoHandler),
|
Polygon(PolygonGizmoHandler),
|
||||||
Arc(ArcGizmoHandler),
|
Arc(ArcGizmoHandler),
|
||||||
|
Circle(CircleGizmoHandler),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ShapeGizmoHandlers {
|
impl ShapeGizmoHandlers {
|
||||||
|
|
@ -36,6 +38,7 @@ impl ShapeGizmoHandlers {
|
||||||
Self::Star(_) => "star",
|
Self::Star(_) => "star",
|
||||||
Self::Polygon(_) => "polygon",
|
Self::Polygon(_) => "polygon",
|
||||||
Self::Arc(_) => "arc",
|
Self::Arc(_) => "arc",
|
||||||
|
Self::Circle(_) => "circle",
|
||||||
Self::None => "none",
|
Self::None => "none",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -46,6 +49,7 @@ impl ShapeGizmoHandlers {
|
||||||
Self::Star(h) => h.handle_state(layer, mouse_position, document, responses),
|
Self::Star(h) => h.handle_state(layer, mouse_position, document, responses),
|
||||||
Self::Polygon(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::Arc(h) => h.handle_state(layer, mouse_position, document, responses),
|
||||||
|
Self::Circle(h) => h.handle_state(layer, mouse_position, document, responses),
|
||||||
Self::None => {}
|
Self::None => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -56,6 +60,7 @@ impl ShapeGizmoHandlers {
|
||||||
Self::Star(h) => h.is_any_gizmo_hovered(),
|
Self::Star(h) => h.is_any_gizmo_hovered(),
|
||||||
Self::Polygon(h) => h.is_any_gizmo_hovered(),
|
Self::Polygon(h) => h.is_any_gizmo_hovered(),
|
||||||
Self::Arc(h) => h.is_any_gizmo_hovered(),
|
Self::Arc(h) => h.is_any_gizmo_hovered(),
|
||||||
|
Self::Circle(h) => h.is_any_gizmo_hovered(),
|
||||||
Self::None => false,
|
Self::None => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -66,6 +71,7 @@ impl ShapeGizmoHandlers {
|
||||||
Self::Star(h) => h.handle_click(),
|
Self::Star(h) => h.handle_click(),
|
||||||
Self::Polygon(h) => h.handle_click(),
|
Self::Polygon(h) => h.handle_click(),
|
||||||
Self::Arc(h) => h.handle_click(),
|
Self::Arc(h) => h.handle_click(),
|
||||||
|
Self::Circle(h) => h.handle_click(),
|
||||||
Self::None => {}
|
Self::None => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -76,6 +82,7 @@ impl ShapeGizmoHandlers {
|
||||||
Self::Star(h) => h.handle_update(drag_start, document, input, responses),
|
Self::Star(h) => h.handle_update(drag_start, document, input, responses),
|
||||||
Self::Polygon(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::Arc(h) => h.handle_update(drag_start, document, input, responses),
|
||||||
|
Self::Circle(h) => h.handle_update(drag_start, document, input, responses),
|
||||||
Self::None => {}
|
Self::None => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -86,6 +93,7 @@ impl ShapeGizmoHandlers {
|
||||||
Self::Star(h) => h.cleanup(),
|
Self::Star(h) => h.cleanup(),
|
||||||
Self::Polygon(h) => h.cleanup(),
|
Self::Polygon(h) => h.cleanup(),
|
||||||
Self::Arc(h) => h.cleanup(),
|
Self::Arc(h) => h.cleanup(),
|
||||||
|
Self::Circle(h) => h.cleanup(),
|
||||||
Self::None => {}
|
Self::None => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -104,6 +112,7 @@ impl ShapeGizmoHandlers {
|
||||||
Self::Star(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::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),
|
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::None => {}
|
Self::None => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -121,6 +130,7 @@ impl ShapeGizmoHandlers {
|
||||||
Self::Star(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::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),
|
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::None => {}
|
Self::None => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -130,6 +140,7 @@ impl ShapeGizmoHandlers {
|
||||||
Self::Star(h) => h.mouse_cursor_icon(),
|
Self::Star(h) => h.mouse_cursor_icon(),
|
||||||
Self::Polygon(h) => h.mouse_cursor_icon(),
|
Self::Polygon(h) => h.mouse_cursor_icon(),
|
||||||
Self::Arc(h) => h.mouse_cursor_icon(),
|
Self::Arc(h) => h.mouse_cursor_icon(),
|
||||||
|
Self::Circle(h) => h.mouse_cursor_icon(),
|
||||||
Self::None => None,
|
Self::None => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -169,6 +180,10 @@ impl GizmoManager {
|
||||||
if graph_modification_utils::get_arc_id(layer, &document.network_interface).is_some() {
|
if graph_modification_utils::get_arc_id(layer, &document.network_interface).is_some() {
|
||||||
return Some(ShapeGizmoHandlers::Arc(ArcGizmoHandler::new()));
|
return Some(ShapeGizmoHandlers::Arc(ArcGizmoHandler::new()));
|
||||||
}
|
}
|
||||||
|
// Circle
|
||||||
|
if graph_modification_utils::get_circle_id(layer, &document.network_interface).is_some() {
|
||||||
|
return Some(ShapeGizmoHandlers::Circle(CircleGizmoHandler::default()));
|
||||||
|
}
|
||||||
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,172 @@
|
||||||
|
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::{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};
|
||||||
|
use graph_craft::document::NodeInput;
|
||||||
|
use graph_craft::document::value::TaggedValue;
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
use std::f64::consts::FRAC_PI_2;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default, PartialEq)]
|
||||||
|
pub enum RadiusHandleState {
|
||||||
|
#[default]
|
||||||
|
Inactive,
|
||||||
|
Hover,
|
||||||
|
Dragging,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default, PartialEq)]
|
||||||
|
pub struct RadiusHandle {
|
||||||
|
pub layer: Option<LayerNodeIdentifier>,
|
||||||
|
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(&self) -> bool {
|
||||||
|
self.handle_state == RadiusHandleState::Dragging
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_state(&mut self, state: RadiusHandleState) {
|
||||||
|
self.handle_state = state;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn check_if_inside_dash_lines(angle: f64, mouse_position: DVec2, viewport: DAffine2, radius: f64, document: &DocumentMessageHandler, layer: LayerNodeIdentifier) -> bool {
|
||||||
|
let center = viewport.transform_point2(DVec2::ZERO);
|
||||||
|
if let Some(stroke_width) = get_stroke_width(layer, &document.network_interface) {
|
||||||
|
let circle_point = calculate_circle_point_position(angle, radius.abs());
|
||||||
|
let direction = circle_point.normalize();
|
||||||
|
let mouse_distance = mouse_position.distance(center);
|
||||||
|
|
||||||
|
let spacing = Self::calculate_extra_spacing(viewport, radius, center, stroke_width, 15.);
|
||||||
|
|
||||||
|
let inner_point = viewport.transform_point2(circle_point - direction * spacing).distance(center);
|
||||||
|
let outer_point = viewport.transform_point2(circle_point + direction * spacing).distance(center);
|
||||||
|
|
||||||
|
mouse_distance >= inner_point && mouse_distance <= outer_point
|
||||||
|
} else {
|
||||||
|
let point_position = viewport.transform_point2(calculate_circle_point_position(angle, radius.abs()));
|
||||||
|
mouse_position.distance(center) <= point_position.distance(center)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn calculate_extra_spacing(viewport: DAffine2, radius: f64, viewport_center: DVec2, stroke_width: f64, threshold: f64) -> f64 {
|
||||||
|
let start_point = viewport.transform_point2(calculate_circle_point_position(0., radius)).distance(viewport_center);
|
||||||
|
let end_point = viewport.transform_point2(calculate_circle_point_position(FRAC_PI_2, radius)).distance(viewport_center);
|
||||||
|
let min_radius = start_point.min(end_point);
|
||||||
|
let extra_spacing = if min_radius < threshold { 10. * (min_radius / threshold) } else { 10. };
|
||||||
|
|
||||||
|
stroke_width + extra_spacing
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_actions(&mut self, layer: LayerNodeIdentifier, document: &DocumentMessageHandler, mouse_position: DVec2, responses: &mut VecDeque<Message>) {
|
||||||
|
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 {
|
||||||
|
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(calculate_circle_point_position(angle, radius.abs()));
|
||||||
|
let center = viewport.transform_point2(DVec2::ZERO);
|
||||||
|
|
||||||
|
if point_position.distance(center) < GIZMO_HIDE_THRESHOLD {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if Self::check_if_inside_dash_lines(angle, mouse_position, viewport, radius.abs(), document, layer) {
|
||||||
|
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).or(extract_arc_parameters(Some(layer), document).map(|(r, _, _, _)| r)) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let viewport = document.metadata().transform_to_viewport(layer);
|
||||||
|
let center = viewport.transform_point2(DVec2::ZERO);
|
||||||
|
|
||||||
|
let start_point = viewport.transform_point2(calculate_circle_point_position(0., radius)).distance(center);
|
||||||
|
let end_point = viewport.transform_point2(calculate_circle_point_position(FRAC_PI_2, radius)).distance(center);
|
||||||
|
|
||||||
|
if let Some(stroke_width) = get_stroke_width(layer, &document.network_interface) {
|
||||||
|
let spacing = Self::calculate_extra_spacing(viewport, radius, center, stroke_width, 15.);
|
||||||
|
let smaller_radius_x = (start_point - spacing).abs();
|
||||||
|
let smaller_radius_y = (end_point - spacing).abs();
|
||||||
|
|
||||||
|
let larger_radius_x = (start_point + spacing).abs();
|
||||||
|
let larger_radius_y = (end_point + spacing).abs();
|
||||||
|
|
||||||
|
overlay_context.dashed_ellipse(center, smaller_radius_x, smaller_radius_y, None, None, None, None, None, None, Some(4.), Some(4.), Some(0.5));
|
||||||
|
overlay_context.dashed_ellipse(center, larger_radius_x, larger_radius_y, None, None, None, None, None, None, Some(4.), Some(4.), Some(0.5));
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
overlay_context.dashed_ellipse(center, start_point, end_point, None, None, None, None, None, None, Some(4.), Some(4.), Some(0.5));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_inner_radius(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>, drag_start: DVec2) {
|
||||||
|
let Some(layer) = self.layer else { return };
|
||||||
|
let Some(node_id) = graph_modification_utils::get_circle_id(layer, &document.network_interface).or(get_arc_id(layer, &document.network_interface)) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let Some(current_radius) = extract_circle_radius(layer, document).or(extract_arc_parameters(Some(layer), document).map(|(r, _, _, _)| r)) 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 calculate_circle_point_position(theta: f64, radius: f64) -> DVec2 {
|
||||||
|
DVec2::new(radius * theta.cos(), -radius * theta.sin())
|
||||||
|
}
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
pub mod circle_arc_radius_handle;
|
||||||
pub mod number_of_points_dial;
|
pub mod number_of_points_dial;
|
||||||
pub mod point_radius_handle;
|
pub mod point_radius_handle;
|
||||||
pub mod sweep_angle_gizmo;
|
pub mod sweep_angle_gizmo;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::consts::{ARC_SNAP_THRESHOLD, COLOR_OVERLAY_RED, GIZMO_HIDE_THRESHOLD};
|
use crate::consts::{ARC_SNAP_THRESHOLD, GIZMO_HIDE_THRESHOLD};
|
||||||
use crate::messages::message::Message;
|
use crate::messages::message::Message;
|
||||||
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
|
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
|
||||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||||
|
|
@ -104,17 +104,17 @@ impl SweepAngleGizmo {
|
||||||
|
|
||||||
match self.handle_state {
|
match self.handle_state {
|
||||||
SweepAngleGizmoState::Inactive => {
|
SweepAngleGizmoState::Inactive => {
|
||||||
// Draw both endpoint handles if an arc is selected
|
|
||||||
let Some((point1, point2)) = arc_end_points(selected_arc_layer, document) else { return };
|
let Some((point1, point2)) = arc_end_points(selected_arc_layer, document) else { return };
|
||||||
overlay_context.manipulator_handle(point1, false, Some(COLOR_OVERLAY_RED));
|
overlay_context.manipulator_handle(point1, false, None);
|
||||||
overlay_context.manipulator_handle(point2, false, Some(COLOR_OVERLAY_RED));
|
overlay_context.manipulator_handle(point2, false, None);
|
||||||
}
|
}
|
||||||
SweepAngleGizmoState::Hover => {
|
SweepAngleGizmoState::Hover => {
|
||||||
// Highlight the currently hovered endpoint only
|
// Highlight the currently hovered endpoint only
|
||||||
let Some((point1, point2)) = arc_end_points(self.layer, document) else { return };
|
let Some((point1, point2)) = arc_end_points(self.layer, document) else { return };
|
||||||
|
|
||||||
let point = if self.endpoint == EndpointType::Start { point1 } else { point2 };
|
let (point, other_point) = if self.endpoint == EndpointType::Start { (point1, point2) } else { (point2, point1) };
|
||||||
overlay_context.manipulator_handle(point, true, Some(COLOR_OVERLAY_RED));
|
overlay_context.manipulator_handle(point, true, None);
|
||||||
|
overlay_context.manipulator_handle(other_point, false, None);
|
||||||
}
|
}
|
||||||
SweepAngleGizmoState::Dragging => {
|
SweepAngleGizmoState::Dragging => {
|
||||||
// Show snapping guides and angle arc while dragging
|
// Show snapping guides and angle arc while dragging
|
||||||
|
|
@ -123,11 +123,17 @@ impl SweepAngleGizmo {
|
||||||
let viewport = document.metadata().transform_to_viewport(layer);
|
let viewport = document.metadata().transform_to_viewport(layer);
|
||||||
|
|
||||||
// Depending on which endpoint is being dragged, draw guides relative to the static point
|
// Depending on which endpoint is being dragged, draw guides relative to the static point
|
||||||
let point = if self.endpoint == EndpointType::End { current_end } else { current_start };
|
let (point, other_point) = if self.endpoint == EndpointType::End {
|
||||||
|
(current_end, current_start)
|
||||||
|
} else {
|
||||||
|
(current_start, current_end)
|
||||||
|
};
|
||||||
|
|
||||||
// Draw the dashed line from center to drag start position
|
// Draw the dashed line from center to drag start position
|
||||||
overlay_context.dashed_line(self.position_before_rotation, viewport.transform_point2(DVec2::ZERO), None, None, Some(5.), Some(5.), Some(0.5));
|
overlay_context.dashed_line(self.position_before_rotation, viewport.transform_point2(DVec2::ZERO), None, None, Some(5.), Some(5.), Some(0.5));
|
||||||
|
|
||||||
|
overlay_context.manipulator_handle(other_point, false, None);
|
||||||
|
|
||||||
// Draw the angle, text and the bold line
|
// Draw the angle, text and the bold line
|
||||||
self.dragging_snapping_overlays(self.position_before_rotation, point, tilt_offset, viewport, overlay_context);
|
self.dragging_snapping_overlays(self.position_before_rotation, point, tilt_offset, viewport, overlay_context);
|
||||||
}
|
}
|
||||||
|
|
@ -143,8 +149,8 @@ impl SweepAngleGizmo {
|
||||||
self.dragging_snapping_overlays(a, b, tilt_offset, viewport, overlay_context);
|
self.dragging_snapping_overlays(a, b, tilt_offset, viewport, overlay_context);
|
||||||
|
|
||||||
// Draw lines from endpoints to the arc center
|
// Draw lines from endpoints to the arc center
|
||||||
overlay_context.line(start, center, Some(COLOR_OVERLAY_RED), Some(2.));
|
overlay_context.line(start, center, None, Some(2.));
|
||||||
overlay_context.line(end, center, Some(COLOR_OVERLAY_RED), Some(2.));
|
overlay_context.line(end, center, None, Some(2.));
|
||||||
|
|
||||||
// Draw the line from drag start to arc center
|
// Draw the line from drag start to arc center
|
||||||
overlay_context.dashed_line(self.position_before_rotation, center, None, None, Some(5.), Some(5.), Some(0.5));
|
overlay_context.dashed_line(self.position_before_rotation, center, None, None, Some(5.), Some(5.), Some(0.5));
|
||||||
|
|
|
||||||
|
|
@ -333,6 +333,10 @@ pub fn get_fill_id(layer: LayerNodeIdentifier, network_interface: &NodeNetworkIn
|
||||||
NodeGraphLayer::new(layer, network_interface).upstream_node_id_from_name("Fill")
|
NodeGraphLayer::new(layer, network_interface).upstream_node_id_from_name("Fill")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_circle_id(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<NodeId> {
|
||||||
|
NodeGraphLayer::new(layer, network_interface).upstream_node_id_from_name("Circle")
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_ellipse_id(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<NodeId> {
|
pub fn get_ellipse_id(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<NodeId> {
|
||||||
NodeGraphLayer::new(layer, network_interface).upstream_node_id_from_name("Ellipse")
|
NodeGraphLayer::new(layer, network_interface).upstream_node_id_from_name("Ellipse")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -48,56 +48,11 @@ impl Resize {
|
||||||
/// Compute the drag start and end based on the current mouse position. Ignores the state of the layer.
|
/// Compute the drag start and end based on the current mouse position. Ignores the state of the layer.
|
||||||
/// If you want to only draw whilst a layer exists, use [`Resize::calculate_points`].
|
/// If you want to only draw whilst a layer exists, use [`Resize::calculate_points`].
|
||||||
pub fn calculate_points_ignore_layer(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, center: Key, lock_ratio: Key, in_document: bool) -> [DVec2; 2] {
|
pub fn calculate_points_ignore_layer(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, center: Key, lock_ratio: Key, in_document: bool) -> [DVec2; 2] {
|
||||||
let start = self.viewport_drag_start(document);
|
|
||||||
let mouse = input.mouse.position;
|
|
||||||
let document_to_viewport = document.navigation_handler.calculate_offset_transform(input.viewport_bounds.center(), &document.document_ptz);
|
|
||||||
let document_mouse = document_to_viewport.inverse().transform_point2(mouse);
|
|
||||||
let mut points_viewport = [start, mouse];
|
|
||||||
let ignore = if let Some(layer) = self.layer { vec![layer] } else { vec![] };
|
|
||||||
let ratio = input.keyboard.get(lock_ratio as usize);
|
let ratio = input.keyboard.get(lock_ratio as usize);
|
||||||
let center = input.keyboard.get(center as usize);
|
let center = input.keyboard.get(center as usize);
|
||||||
let snap_data = SnapData::ignore(document, input, &ignore);
|
|
||||||
let config = SnapTypeConfiguration::default();
|
|
||||||
if ratio {
|
|
||||||
let viewport_size = points_viewport[1] - points_viewport[0];
|
|
||||||
let raw_size = if in_document { document_to_viewport.inverse() } else { DAffine2::IDENTITY }.transform_vector2(viewport_size);
|
|
||||||
let adjusted_size = raw_size.abs().max(raw_size.abs().yx()) * raw_size.signum();
|
|
||||||
let size = if in_document { document_to_viewport.transform_vector2(adjusted_size) } else { adjusted_size };
|
|
||||||
points_viewport[1] = points_viewport[0] + size;
|
|
||||||
|
|
||||||
let end_document = document_to_viewport.inverse().transform_point2(points_viewport[1]);
|
// Use shared snapping logic with optional center and ratio constraints, considering if coordinates are in document space.
|
||||||
let constraint = SnapConstraint::Line {
|
self.compute_snapped_resize_points(document, input, center, ratio, in_document)
|
||||||
origin: self.drag_start,
|
|
||||||
direction: end_document - self.drag_start,
|
|
||||||
};
|
|
||||||
if center {
|
|
||||||
let snapped = self.snap_manager.constrained_snap(&snap_data, &SnapCandidatePoint::handle(end_document), constraint, config);
|
|
||||||
let far = SnapCandidatePoint::handle(2. * self.drag_start - end_document);
|
|
||||||
let snapped_far = self.snap_manager.constrained_snap(&snap_data, &far, constraint, config);
|
|
||||||
let best = if snapped_far.other_snap_better(&snapped) { snapped } else { snapped_far };
|
|
||||||
points_viewport[0] = document_to_viewport.transform_point2(best.snapped_point_document);
|
|
||||||
points_viewport[1] = document_to_viewport.transform_point2(self.drag_start * 2. - best.snapped_point_document);
|
|
||||||
self.snap_manager.update_indicator(best);
|
|
||||||
} else {
|
|
||||||
let snapped = self.snap_manager.constrained_snap(&snap_data, &SnapCandidatePoint::handle(end_document), constraint, config);
|
|
||||||
points_viewport[1] = document_to_viewport.transform_point2(snapped.snapped_point_document);
|
|
||||||
self.snap_manager.update_indicator(snapped);
|
|
||||||
}
|
|
||||||
} else if center {
|
|
||||||
let snapped = self.snap_manager.free_snap(&snap_data, &SnapCandidatePoint::handle(document_mouse), config);
|
|
||||||
let opposite = 2. * self.drag_start - document_mouse;
|
|
||||||
let snapped_far = self.snap_manager.free_snap(&snap_data, &SnapCandidatePoint::handle(opposite), config);
|
|
||||||
let best = if snapped_far.other_snap_better(&snapped) { snapped } else { snapped_far };
|
|
||||||
points_viewport[0] = document_to_viewport.transform_point2(best.snapped_point_document);
|
|
||||||
points_viewport[1] = document_to_viewport.transform_point2(self.drag_start * 2. - best.snapped_point_document);
|
|
||||||
self.snap_manager.update_indicator(best);
|
|
||||||
} else {
|
|
||||||
let snapped = self.snap_manager.free_snap(&snap_data, &SnapCandidatePoint::handle(document_mouse), config);
|
|
||||||
points_viewport[1] = document_to_viewport.transform_point2(snapped.snapped_point_document);
|
|
||||||
self.snap_manager.update_indicator(snapped);
|
|
||||||
}
|
|
||||||
|
|
||||||
points_viewport
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn calculate_transform(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, center: Key, lock_ratio: Key, skip_rerender: bool) -> Option<Message> {
|
pub fn calculate_transform(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, center: Key, lock_ratio: Key, skip_rerender: bool) -> Option<Message> {
|
||||||
|
|
@ -113,6 +68,81 @@ impl Resize {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn calculate_circle_points(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, center: Key) -> [DVec2; 2] {
|
||||||
|
let center = input.keyboard.get(center as usize);
|
||||||
|
|
||||||
|
// Use shared snapping logic with enforced aspect ratio and optional center snapping.
|
||||||
|
self.compute_snapped_resize_points(document, input, center, true, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calculates two points in viewport space from a drag, applying snapping, optional center mode, and aspect ratio locking.
|
||||||
|
fn compute_snapped_resize_points(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, center: bool, lock_ratio: bool, in_document: bool) -> [DVec2; 2] {
|
||||||
|
let start = self.viewport_drag_start(document);
|
||||||
|
let mouse = input.mouse.position;
|
||||||
|
let document_to_viewport = document.navigation_handler.calculate_offset_transform(input.viewport_bounds.center(), &document.document_ptz);
|
||||||
|
let drag_start = self.drag_start;
|
||||||
|
let mut points_viewport = [start, mouse];
|
||||||
|
|
||||||
|
let ignore = if let Some(layer) = self.layer { vec![layer] } else { vec![] };
|
||||||
|
let snap_data = &SnapData::ignore(document, input, &ignore);
|
||||||
|
|
||||||
|
if lock_ratio {
|
||||||
|
let viewport_size = points_viewport[1] - points_viewport[0];
|
||||||
|
let raw_size = if in_document {
|
||||||
|
document_to_viewport.inverse().transform_vector2(viewport_size)
|
||||||
|
} else {
|
||||||
|
viewport_size
|
||||||
|
};
|
||||||
|
|
||||||
|
let adjusted_size = raw_size.abs().max(raw_size.abs().yx()) * raw_size.signum();
|
||||||
|
let size = if in_document { document_to_viewport.transform_vector2(adjusted_size) } else { adjusted_size };
|
||||||
|
|
||||||
|
points_viewport[1] = points_viewport[0] + size;
|
||||||
|
let end_document = document_to_viewport.inverse().transform_point2(points_viewport[1]);
|
||||||
|
let constraint = SnapConstraint::Line {
|
||||||
|
origin: drag_start,
|
||||||
|
direction: end_document - drag_start,
|
||||||
|
};
|
||||||
|
|
||||||
|
if center {
|
||||||
|
let snapped = self
|
||||||
|
.snap_manager
|
||||||
|
.constrained_snap(snap_data, &SnapCandidatePoint::handle(end_document), constraint, SnapTypeConfiguration::default());
|
||||||
|
let far = SnapCandidatePoint::handle(2. * drag_start - end_document);
|
||||||
|
let snapped_far = self.snap_manager.constrained_snap(snap_data, &far, constraint, SnapTypeConfiguration::default());
|
||||||
|
let best = if snapped_far.other_snap_better(&snapped) { snapped } else { snapped_far };
|
||||||
|
|
||||||
|
points_viewport[0] = document_to_viewport.transform_point2(best.snapped_point_document);
|
||||||
|
points_viewport[1] = document_to_viewport.transform_point2(drag_start * 2. - best.snapped_point_document);
|
||||||
|
self.snap_manager.update_indicator(best);
|
||||||
|
} else {
|
||||||
|
let snapped = self
|
||||||
|
.snap_manager
|
||||||
|
.constrained_snap(snap_data, &SnapCandidatePoint::handle(end_document), constraint, SnapTypeConfiguration::default());
|
||||||
|
points_viewport[1] = document_to_viewport.transform_point2(snapped.snapped_point_document);
|
||||||
|
self.snap_manager.update_indicator(snapped);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let document_mouse = document_to_viewport.inverse().transform_point2(mouse);
|
||||||
|
if center {
|
||||||
|
let snapped = self.snap_manager.free_snap(snap_data, &SnapCandidatePoint::handle(document_mouse), SnapTypeConfiguration::default());
|
||||||
|
let opposite = 2. * drag_start - document_mouse;
|
||||||
|
let snapped_far = self.snap_manager.free_snap(snap_data, &SnapCandidatePoint::handle(opposite), SnapTypeConfiguration::default());
|
||||||
|
let best = if snapped_far.other_snap_better(&snapped) { snapped } else { snapped_far };
|
||||||
|
|
||||||
|
points_viewport[0] = document_to_viewport.transform_point2(best.snapped_point_document);
|
||||||
|
points_viewport[1] = document_to_viewport.transform_point2(drag_start * 2. - best.snapped_point_document);
|
||||||
|
self.snap_manager.update_indicator(best);
|
||||||
|
} else {
|
||||||
|
let snapped = self.snap_manager.free_snap(snap_data, &SnapCandidatePoint::handle(document_mouse), SnapTypeConfiguration::default());
|
||||||
|
points_viewport[1] = document_to_viewport.transform_point2(snapped.snapped_point_document);
|
||||||
|
self.snap_manager.update_indicator(snapped);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
points_viewport
|
||||||
|
}
|
||||||
|
|
||||||
pub fn cleanup(&mut self, responses: &mut VecDeque<Message>) {
|
pub fn cleanup(&mut self, responses: &mut VecDeque<Message>) {
|
||||||
self.snap_manager.cleanup(responses);
|
self.snap_manager.cleanup(responses);
|
||||||
self.layer = None;
|
self.layer = None;
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ use crate::messages::portfolio::document::graph_operation::utility_types::Transf
|
||||||
use crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type;
|
use crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type;
|
||||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||||
use crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, NodeTemplate};
|
use crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, NodeTemplate};
|
||||||
|
use crate::messages::tool::common_functionality::gizmos::shape_gizmos::circle_arc_radius_handle::{RadiusHandle, RadiusHandleState};
|
||||||
use crate::messages::tool::common_functionality::gizmos::shape_gizmos::sweep_angle_gizmo::{SweepAngleGizmo, SweepAngleGizmoState};
|
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::graph_modification_utils;
|
||||||
use crate::messages::tool::common_functionality::shapes::shape_utility::{ShapeGizmoHandler, arc_outline};
|
use crate::messages::tool::common_functionality::shapes::shape_utility::{ShapeGizmoHandler, arc_outline};
|
||||||
|
|
@ -17,6 +18,7 @@ use std::collections::VecDeque;
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
pub struct ArcGizmoHandler {
|
pub struct ArcGizmoHandler {
|
||||||
sweep_angle_gizmo: SweepAngleGizmo,
|
sweep_angle_gizmo: SweepAngleGizmo,
|
||||||
|
arc_radius_handle: RadiusHandle,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ArcGizmoHandler {
|
impl ArcGizmoHandler {
|
||||||
|
|
@ -26,24 +28,40 @@ impl ArcGizmoHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ShapeGizmoHandler for ArcGizmoHandler {
|
impl ShapeGizmoHandler for ArcGizmoHandler {
|
||||||
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_layer: LayerNodeIdentifier, mouse_position: DVec2, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
|
||||||
self.sweep_angle_gizmo.handle_actions(selected_shape_layers, document, mouse_position);
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_any_gizmo_hovered(&self) -> bool {
|
fn is_any_gizmo_hovered(&self) -> bool {
|
||||||
self.sweep_angle_gizmo.hovered()
|
self.sweep_angle_gizmo.hovered() || self.arc_radius_handle.hovered()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_click(&mut self) {
|
fn handle_click(&mut self) {
|
||||||
|
// If hovering over both the gizmos give priority to sweep angle gizmo
|
||||||
|
if self.sweep_angle_gizmo.hovered() && self.arc_radius_handle.hovered() {
|
||||||
|
self.sweep_angle_gizmo.update_state(SweepAngleGizmoState::Dragging);
|
||||||
|
self.arc_radius_handle.update_state(RadiusHandleState::Inactive);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if self.sweep_angle_gizmo.hovered() {
|
if self.sweep_angle_gizmo.hovered() {
|
||||||
self.sweep_angle_gizmo.update_state(SweepAngleGizmoState::Dragging);
|
self.sweep_angle_gizmo.update_state(SweepAngleGizmoState::Dragging);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.arc_radius_handle.hovered() {
|
||||||
|
self.arc_radius_handle.update_state(RadiusHandleState::Dragging);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_update(&mut self, _drag_start: DVec2, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) {
|
fn handle_update(&mut self, drag_start: DVec2, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) {
|
||||||
if self.sweep_angle_gizmo.is_dragging_or_snapped() {
|
if self.sweep_angle_gizmo.is_dragging_or_snapped() {
|
||||||
self.sweep_angle_gizmo.update_arc(document, input, responses);
|
self.sweep_angle_gizmo.update_arc(document, input, responses);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.arc_radius_handle.is_dragging() {
|
||||||
|
self.arc_radius_handle.update_inner_radius(document, input, responses, drag_start);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dragging_overlays(
|
fn dragging_overlays(
|
||||||
|
|
@ -58,20 +76,39 @@ impl ShapeGizmoHandler for ArcGizmoHandler {
|
||||||
self.sweep_angle_gizmo.overlays(None, document, input, mouse_position, overlay_context);
|
self.sweep_angle_gizmo.overlays(None, document, input, mouse_position, overlay_context);
|
||||||
arc_outline(self.sweep_angle_gizmo.layer, document, overlay_context);
|
arc_outline(self.sweep_angle_gizmo.layer, document, overlay_context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.arc_radius_handle.is_dragging() {
|
||||||
|
self.sweep_angle_gizmo.overlays(self.arc_radius_handle.layer, document, input, mouse_position, overlay_context);
|
||||||
|
self.arc_radius_handle.overlays(document, overlay_context);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn overlays(
|
fn overlays(
|
||||||
&self,
|
&self,
|
||||||
document: &DocumentMessageHandler,
|
document: &DocumentMessageHandler,
|
||||||
selected_shape_layers: Option<LayerNodeIdentifier>,
|
selected_shape_layer: Option<LayerNodeIdentifier>,
|
||||||
input: &InputPreprocessorMessageHandler,
|
input: &InputPreprocessorMessageHandler,
|
||||||
_shape_editor: &mut &mut crate::messages::tool::common_functionality::shape_editor::ShapeState,
|
_shape_editor: &mut &mut crate::messages::tool::common_functionality::shape_editor::ShapeState,
|
||||||
mouse_position: DVec2,
|
mouse_position: DVec2,
|
||||||
overlay_context: &mut crate::messages::portfolio::document::overlays::utility_types::OverlayContext,
|
overlay_context: &mut crate::messages::portfolio::document::overlays::utility_types::OverlayContext,
|
||||||
) {
|
) {
|
||||||
self.sweep_angle_gizmo.overlays(selected_shape_layers, document, input, mouse_position, overlay_context);
|
// If hovering over both the gizmos give priority to sweep angle gizmo
|
||||||
|
if self.sweep_angle_gizmo.hovered() && self.arc_radius_handle.hovered() {
|
||||||
|
self.sweep_angle_gizmo.overlays(selected_shape_layer, document, input, mouse_position, overlay_context);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
arc_outline(selected_shape_layers.or(self.sweep_angle_gizmo.layer), document, overlay_context);
|
if self.arc_radius_handle.hovered() {
|
||||||
|
let layer = self.arc_radius_handle.layer;
|
||||||
|
|
||||||
|
self.arc_radius_handle.overlays(document, overlay_context);
|
||||||
|
self.sweep_angle_gizmo.overlays(layer, document, input, mouse_position, overlay_context);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.sweep_angle_gizmo.overlays(selected_shape_layer, document, input, mouse_position, overlay_context);
|
||||||
|
self.arc_radius_handle.overlays(document, overlay_context);
|
||||||
|
|
||||||
|
arc_outline(selected_shape_layer.or(self.sweep_angle_gizmo.layer), document, overlay_context);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mouse_cursor_icon(&self) -> Option<MouseCursorIcon> {
|
fn mouse_cursor_icon(&self) -> Option<MouseCursorIcon> {
|
||||||
|
|
@ -79,11 +116,16 @@ impl ShapeGizmoHandler for ArcGizmoHandler {
|
||||||
return Some(MouseCursorIcon::Default);
|
return Some(MouseCursorIcon::Default);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.arc_radius_handle.hovered() || self.arc_radius_handle.is_dragging() {
|
||||||
|
return Some(MouseCursorIcon::EWResize);
|
||||||
|
}
|
||||||
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cleanup(&mut self) {
|
fn cleanup(&mut self) {
|
||||||
self.sweep_angle_gizmo.cleanup();
|
self.sweep_angle_gizmo.cleanup();
|
||||||
|
self.arc_radius_handle.cleanup();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
|
@ -122,11 +164,9 @@ impl Arc {
|
||||||
// We keep the smaller dimension's scale at 1 and scale the other dimension accordingly
|
// We keep the smaller dimension's scale at 1 and scale the other dimension accordingly
|
||||||
if dimensions.x > dimensions.y {
|
if dimensions.x > dimensions.y {
|
||||||
scale.x = dimensions.x / dimensions.y;
|
scale.x = dimensions.x / dimensions.y;
|
||||||
scale.y = 1.;
|
|
||||||
radius = dimensions.y / 2.;
|
radius = dimensions.y / 2.;
|
||||||
} else {
|
} else {
|
||||||
scale.y = dimensions.y / dimensions.x;
|
scale.y = dimensions.y / dimensions.x;
|
||||||
scale.x = 1.;
|
|
||||||
radius = dimensions.x / 2.;
|
radius = dimensions.x / 2.;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,125 @@
|
||||||
|
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_arc_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, ShapeToolModifierKey};
|
||||||
|
use crate::messages::tool::tool_messages::shape_tool::ShapeToolData;
|
||||||
|
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<Message>) {
|
||||||
|
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<Message>) {
|
||||||
|
if self.circle_radius_handle.is_dragging() {
|
||||||
|
self.circle_radius_handle.update_inner_radius(document, input, responses, drag_start);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn overlays(
|
||||||
|
&self,
|
||||||
|
document: &DocumentMessageHandler,
|
||||||
|
_selected_circle_layer: Option<LayerNodeIdentifier>,
|
||||||
|
_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() {
|
||||||
|
self.circle_radius_handle.overlays(document, overlay_context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cleanup(&mut self) {
|
||||||
|
self.circle_radius_handle.cleanup();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mouse_cursor_icon(&self) -> Option<MouseCursorIcon> {
|
||||||
|
if self.circle_radius_handle.hovered() || self.circle_radius_handle.is_dragging() {
|
||||||
|
return Some(MouseCursorIcon::EWResize);
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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,
|
||||||
|
shape_tool_data: &mut ShapeToolData,
|
||||||
|
modifier: ShapeToolModifierKey,
|
||||||
|
responses: &mut VecDeque<Message>,
|
||||||
|
) {
|
||||||
|
let center = modifier[0];
|
||||||
|
let [start, end] = shape_tool_data.data.calculate_circle_points(document, ipp, center);
|
||||||
|
let Some(node_id) = graph_modification_utils::get_circle_id(layer, &document.network_interface) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let dimensions = (start - end).abs();
|
||||||
|
let radius: f64;
|
||||||
|
|
||||||
|
// We keep the smaller dimension's scale at 1 and scale the other dimension accordingly
|
||||||
|
if dimensions.x > dimensions.y {
|
||||||
|
radius = dimensions.y / 2.;
|
||||||
|
} else {
|
||||||
|
radius = dimensions.x / 2.;
|
||||||
|
}
|
||||||
|
|
||||||
|
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_scale_angle_translation(DVec2::ONE, 0., start.midpoint(end)),
|
||||||
|
transform_in: TransformIn::Viewport,
|
||||||
|
skip_rerender: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
pub mod arc_shape;
|
pub mod arc_shape;
|
||||||
|
pub mod circle_shape;
|
||||||
pub mod ellipse_shape;
|
pub mod ellipse_shape;
|
||||||
pub mod line_shape;
|
pub mod line_shape;
|
||||||
pub mod polygon_shape;
|
pub mod polygon_shape;
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ pub enum ShapeType {
|
||||||
#[default]
|
#[default]
|
||||||
Polygon = 0,
|
Polygon = 0,
|
||||||
Star,
|
Star,
|
||||||
|
Circle,
|
||||||
Arc,
|
Arc,
|
||||||
Rectangle,
|
Rectangle,
|
||||||
Ellipse,
|
Ellipse,
|
||||||
|
|
@ -37,6 +38,7 @@ impl ShapeType {
|
||||||
(match self {
|
(match self {
|
||||||
Self::Polygon => "Polygon",
|
Self::Polygon => "Polygon",
|
||||||
Self::Star => "Star",
|
Self::Star => "Star",
|
||||||
|
Self::Circle => "Circle",
|
||||||
Self::Arc => "Arc",
|
Self::Arc => "Arc",
|
||||||
Self::Rectangle => "Rectangle",
|
Self::Rectangle => "Rectangle",
|
||||||
Self::Ellipse => "Ellipse",
|
Self::Ellipse => "Ellipse",
|
||||||
|
|
@ -280,6 +282,19 @@ pub fn arc_end_points_ignore_layer(radius: f64, start_angle: f64, sweep_angle: f
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Calculate the viewport position of a star vertex given its index
|
/// Calculate the viewport position of a star vertex given its index
|
||||||
|
/// Extract the node input values of Circle.
|
||||||
|
/// Returns an option of (radius).
|
||||||
|
pub fn extract_circle_radius(layer: LayerNodeIdentifier, document: &DocumentMessageHandler) -> Option<f64> {
|
||||||
|
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 {
|
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);
|
let angle = ((vertex_index as f64) * PI) / (n as f64);
|
||||||
let radius = if vertex_index % 2 == 0 { radius1 } else { radius2 };
|
let radius = if vertex_index % 2 == 0 { radius1 } else { radius2 };
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ 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::graph_modification_utils::NodeGraphLayer;
|
||||||
use crate::messages::tool::common_functionality::resize::Resize;
|
use crate::messages::tool::common_functionality::resize::Resize;
|
||||||
use crate::messages::tool::common_functionality::shapes::arc_shape::Arc;
|
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, clicked_on_line_endpoints};
|
||||||
use crate::messages::tool::common_functionality::shapes::polygon_shape::Polygon;
|
use crate::messages::tool::common_functionality::shapes::polygon_shape::Polygon;
|
||||||
use crate::messages::tool::common_functionality::shapes::shape_utility::{ShapeToolModifierKey, ShapeType, anchor_overlays, transform_cage_overlays};
|
use crate::messages::tool::common_functionality::shapes::shape_utility::{ShapeToolModifierKey, ShapeType, anchor_overlays, transform_cage_overlays};
|
||||||
|
|
@ -110,6 +111,9 @@ fn create_shape_option_widget(shape_type: ShapeType) -> WidgetHolder {
|
||||||
MenuListEntry::new("Star")
|
MenuListEntry::new("Star")
|
||||||
.label("Star")
|
.label("Star")
|
||||||
.on_commit(move |_| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::ShapeType(ShapeType::Star)).into()),
|
.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()),
|
||||||
MenuListEntry::new("Arc")
|
MenuListEntry::new("Arc")
|
||||||
.label("Arc")
|
.label("Arc")
|
||||||
.on_commit(move |_| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::ShapeType(ShapeType::Arc)).into()),
|
.on_commit(move |_| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::ShapeType(ShapeType::Arc)).into()),
|
||||||
|
|
@ -229,7 +233,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for Shap
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.fsm_state.update_hints(responses);
|
update_dynamic_hints(&self.fsm_state, responses, &self.tool_data);
|
||||||
self.send_layout(responses, LayoutTarget::ToolOptions);
|
self.send_layout(responses, LayoutTarget::ToolOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -472,6 +476,9 @@ impl Fsm for ShapeToolFsmState {
|
||||||
|
|
||||||
if matches!(self, ShapeToolFsmState::Drawing(_) | ShapeToolFsmState::DraggingLineEndpoints) {
|
if matches!(self, ShapeToolFsmState::Drawing(_) | ShapeToolFsmState::DraggingLineEndpoints) {
|
||||||
Line::overlays(document, tool_data, &mut overlay_context);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self
|
self
|
||||||
|
|
@ -650,7 +657,7 @@ impl Fsm for ShapeToolFsmState {
|
||||||
};
|
};
|
||||||
|
|
||||||
match tool_data.current_shape {
|
match tool_data.current_shape {
|
||||||
ShapeType::Polygon | ShapeType::Star | ShapeType::Ellipse | ShapeType::Arc | ShapeType::Rectangle => tool_data.data.start(document, input),
|
ShapeType::Polygon | ShapeType::Star | ShapeType::Circle | ShapeType::Arc | ShapeType::Rectangle | ShapeType::Ellipse => tool_data.data.start(document, input),
|
||||||
ShapeType::Line => {
|
ShapeType::Line => {
|
||||||
let point = SnapCandidatePoint::handle(document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position));
|
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());
|
let snapped = tool_data.data.snap_manager.free_snap(&SnapData::new(document, input), &point, SnapTypeConfiguration::default());
|
||||||
|
|
@ -663,6 +670,7 @@ impl Fsm for ShapeToolFsmState {
|
||||||
let node = match tool_data.current_shape {
|
let node = match tool_data.current_shape {
|
||||||
ShapeType::Polygon => Polygon::create_node(tool_options.vertices),
|
ShapeType::Polygon => Polygon::create_node(tool_options.vertices),
|
||||||
ShapeType::Star => Star::create_node(tool_options.vertices),
|
ShapeType::Star => Star::create_node(tool_options.vertices),
|
||||||
|
ShapeType::Circle => Circle::create_node(),
|
||||||
ShapeType::Arc => Arc::create_node(tool_options.arc_type),
|
ShapeType::Arc => Arc::create_node(tool_options.arc_type),
|
||||||
ShapeType::Rectangle => Rectangle::create_node(),
|
ShapeType::Rectangle => Rectangle::create_node(),
|
||||||
ShapeType::Ellipse => Ellipse::create_node(),
|
ShapeType::Ellipse => Ellipse::create_node(),
|
||||||
|
|
@ -675,7 +683,7 @@ impl Fsm for ShapeToolFsmState {
|
||||||
let defered_responses = &mut VecDeque::new();
|
let defered_responses = &mut VecDeque::new();
|
||||||
|
|
||||||
match tool_data.current_shape {
|
match tool_data.current_shape {
|
||||||
ShapeType::Ellipse | ShapeType::Rectangle | ShapeType::Arc | ShapeType::Polygon | ShapeType::Star => {
|
ShapeType::Polygon | ShapeType::Star | ShapeType::Circle | ShapeType::Arc | ShapeType::Rectangle | ShapeType::Ellipse => {
|
||||||
defered_responses.add(GraphOperationMessage::TransformSet {
|
defered_responses.add(GraphOperationMessage::TransformSet {
|
||||||
layer,
|
layer,
|
||||||
transform: DAffine2::from_scale_angle_translation(DVec2::ONE, 0., input.mouse.position),
|
transform: DAffine2::from_scale_angle_translation(DVec2::ONE, 0., input.mouse.position),
|
||||||
|
|
@ -707,12 +715,13 @@ impl Fsm for ShapeToolFsmState {
|
||||||
};
|
};
|
||||||
|
|
||||||
match tool_data.current_shape {
|
match tool_data.current_shape {
|
||||||
|
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, tool_data, modifier, responses),
|
||||||
|
ShapeType::Arc => Arc::update_shape(document, input, layer, tool_data, modifier, responses),
|
||||||
ShapeType::Rectangle => Rectangle::update_shape(document, input, layer, tool_data, modifier, responses),
|
ShapeType::Rectangle => Rectangle::update_shape(document, input, layer, tool_data, modifier, responses),
|
||||||
ShapeType::Ellipse => Ellipse::update_shape(document, input, layer, tool_data, modifier, responses),
|
ShapeType::Ellipse => Ellipse::update_shape(document, input, layer, tool_data, modifier, responses),
|
||||||
ShapeType::Line => Line::update_shape(document, input, layer, tool_data, modifier, responses),
|
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::Arc => Arc::update_shape(document, input, layer, tool_data, modifier, responses),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auto-panning
|
// Auto-panning
|
||||||
|
|
@ -892,6 +901,7 @@ impl Fsm for ShapeToolFsmState {
|
||||||
tool_data.data.cleanup(responses);
|
tool_data.data.cleanup(responses);
|
||||||
tool_data.current_shape = shape;
|
tool_data.current_shape = shape;
|
||||||
|
|
||||||
|
responses.add(ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::ShapeType(shape)));
|
||||||
ShapeToolFsmState::Ready(shape)
|
ShapeToolFsmState::Ready(shape)
|
||||||
}
|
}
|
||||||
(_, ShapeToolMessage::HideShapeTypeWidget(hide)) => {
|
(_, ShapeToolMessage::HideShapeTypeWidget(hide)) => {
|
||||||
|
|
@ -903,85 +913,100 @@ impl Fsm for ShapeToolFsmState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_hints(&self, responses: &mut VecDeque<Message>) {
|
fn update_hints(&self, _responses: &mut VecDeque<Message>) {
|
||||||
let hint_data = match self {
|
// Moved logic to update_dynamic_hints
|
||||||
ShapeToolFsmState::Ready(shape) => {
|
|
||||||
let hint_groups = match shape {
|
|
||||||
ShapeType::Polygon | ShapeType::Star | ShapeType::Arc => vec![
|
|
||||||
HintGroup(vec![
|
|
||||||
HintInfo::mouse(MouseMotion::LmbDrag, "Draw Polygon"),
|
|
||||||
HintInfo::keys([Key::Shift], "Constrain Regular").prepend_plus(),
|
|
||||||
HintInfo::keys([Key::Alt], "From Center").prepend_plus(),
|
|
||||||
]),
|
|
||||||
HintGroup(vec![HintInfo::multi_keys([[Key::BracketLeft], [Key::BracketRight]], "Decrease/Increase Sides")]),
|
|
||||||
],
|
|
||||||
ShapeType::Ellipse => vec![HintGroup(vec![
|
|
||||||
HintInfo::mouse(MouseMotion::LmbDrag, "Draw Ellipse"),
|
|
||||||
HintInfo::keys([Key::Shift], "Constrain Circular").prepend_plus(),
|
|
||||||
HintInfo::keys([Key::Alt], "From Center").prepend_plus(),
|
|
||||||
])],
|
|
||||||
ShapeType::Line => vec![HintGroup(vec![
|
|
||||||
HintInfo::mouse(MouseMotion::LmbDrag, "Draw Line"),
|
|
||||||
HintInfo::keys([Key::Shift], "15° Increments").prepend_plus(),
|
|
||||||
HintInfo::keys([Key::Alt], "From Center").prepend_plus(),
|
|
||||||
HintInfo::keys([Key::Control], "Lock Angle").prepend_plus(),
|
|
||||||
])],
|
|
||||||
ShapeType::Rectangle => vec![HintGroup(vec![
|
|
||||||
HintInfo::mouse(MouseMotion::LmbDrag, "Draw Rectangle"),
|
|
||||||
HintInfo::keys([Key::Shift], "Constrain Square").prepend_plus(),
|
|
||||||
HintInfo::keys([Key::Alt], "From Center").prepend_plus(),
|
|
||||||
])],
|
|
||||||
};
|
|
||||||
HintData(hint_groups)
|
|
||||||
}
|
|
||||||
ShapeToolFsmState::Drawing(shape) => {
|
|
||||||
let mut common_hint_group = vec![HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()])];
|
|
||||||
let tool_hint_group = match shape {
|
|
||||||
ShapeType::Polygon | ShapeType::Star | ShapeType::Arc => HintGroup(vec![HintInfo::keys([Key::Shift], "Constrain Regular"), HintInfo::keys([Key::Alt], "From Center")]),
|
|
||||||
ShapeType::Rectangle => HintGroup(vec![HintInfo::keys([Key::Shift], "Constrain Square"), HintInfo::keys([Key::Alt], "From Center")]),
|
|
||||||
ShapeType::Ellipse => HintGroup(vec![HintInfo::keys([Key::Shift], "Constrain Circular"), HintInfo::keys([Key::Alt], "From Center")]),
|
|
||||||
ShapeType::Line => HintGroup(vec![
|
|
||||||
HintInfo::keys([Key::Shift], "15° Increments"),
|
|
||||||
HintInfo::keys([Key::Alt], "From Center"),
|
|
||||||
HintInfo::keys([Key::Control], "Lock Angle"),
|
|
||||||
]),
|
|
||||||
};
|
|
||||||
|
|
||||||
common_hint_group.push(tool_hint_group);
|
|
||||||
|
|
||||||
if matches!(shape, ShapeType::Polygon | ShapeType::Star) {
|
|
||||||
common_hint_group.push(HintGroup(vec![HintInfo::multi_keys([[Key::BracketLeft], [Key::BracketRight]], "Decrease/Increase Sides")]));
|
|
||||||
}
|
|
||||||
|
|
||||||
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")]),
|
|
||||||
]),
|
|
||||||
ShapeToolFsmState::RotatingBounds => HintData(vec![
|
|
||||||
HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]),
|
|
||||||
HintGroup(vec![HintInfo::keys([Key::Shift], "15° Increments")]),
|
|
||||||
]),
|
|
||||||
ShapeToolFsmState::SkewingBounds { .. } => HintData(vec![
|
|
||||||
HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]),
|
|
||||||
HintGroup(vec![HintInfo::keys([Key::Control], "Unlock Slide")]),
|
|
||||||
]),
|
|
||||||
ShapeToolFsmState::ModifyingGizmo => HintData(vec![HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()])]),
|
|
||||||
};
|
|
||||||
|
|
||||||
responses.add(FrontendMessage::UpdateInputHints { hint_data });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_cursor(&self, responses: &mut VecDeque<Message>) {
|
fn update_cursor(&self, responses: &mut VecDeque<Message>) {
|
||||||
responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Crosshair });
|
responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Crosshair });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn update_dynamic_hints(state: &ShapeToolFsmState, responses: &mut VecDeque<Message>, tool_data: &ShapeToolData) {
|
||||||
|
let hint_data = match state {
|
||||||
|
ShapeToolFsmState::Ready(_) => {
|
||||||
|
let hint_groups = match tool_data.current_shape {
|
||||||
|
ShapeType::Polygon | ShapeType::Star => vec![
|
||||||
|
HintGroup(vec![
|
||||||
|
HintInfo::mouse(MouseMotion::LmbDrag, "Draw Polygon"),
|
||||||
|
HintInfo::keys([Key::Shift], "Constrain Regular").prepend_plus(),
|
||||||
|
HintInfo::keys([Key::Alt], "From Center").prepend_plus(),
|
||||||
|
]),
|
||||||
|
HintGroup(vec![HintInfo::multi_keys([[Key::BracketLeft], [Key::BracketRight]], "Decrease/Increase Sides")]),
|
||||||
|
],
|
||||||
|
ShapeType::Ellipse => vec![HintGroup(vec![
|
||||||
|
HintInfo::mouse(MouseMotion::LmbDrag, "Draw Ellipse"),
|
||||||
|
HintInfo::keys([Key::Shift], "Constrain Circular").prepend_plus(),
|
||||||
|
HintInfo::keys([Key::Alt], "From Center").prepend_plus(),
|
||||||
|
])],
|
||||||
|
ShapeType::Line => vec![HintGroup(vec![
|
||||||
|
HintInfo::mouse(MouseMotion::LmbDrag, "Draw Line"),
|
||||||
|
HintInfo::keys([Key::Shift], "15° Increments").prepend_plus(),
|
||||||
|
HintInfo::keys([Key::Alt], "From Center").prepend_plus(),
|
||||||
|
HintInfo::keys([Key::Control], "Lock Angle").prepend_plus(),
|
||||||
|
])],
|
||||||
|
ShapeType::Rectangle => vec![HintGroup(vec![
|
||||||
|
HintInfo::mouse(MouseMotion::LmbDrag, "Draw Rectangle"),
|
||||||
|
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"),
|
||||||
|
HintInfo::keys([Key::Alt], "From Center").prepend_plus(),
|
||||||
|
])],
|
||||||
|
ShapeType::Arc => vec![HintGroup(vec![
|
||||||
|
HintInfo::mouse(MouseMotion::LmbDrag, "Draw Arc"),
|
||||||
|
HintInfo::keys([Key::Shift], "Constrain Arc").prepend_plus(),
|
||||||
|
HintInfo::keys([Key::Alt], "From Center").prepend_plus(),
|
||||||
|
])],
|
||||||
|
};
|
||||||
|
HintData(hint_groups)
|
||||||
|
}
|
||||||
|
ShapeToolFsmState::Drawing(shape) => {
|
||||||
|
let mut common_hint_group = vec![HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()])];
|
||||||
|
let tool_hint_group = match shape {
|
||||||
|
ShapeType::Polygon | ShapeType::Star | ShapeType::Arc => HintGroup(vec![HintInfo::keys([Key::Shift], "Constrain Regular"), HintInfo::keys([Key::Alt], "From Center")]),
|
||||||
|
ShapeType::Rectangle => HintGroup(vec![HintInfo::keys([Key::Shift], "Constrain Square"), HintInfo::keys([Key::Alt], "From Center")]),
|
||||||
|
ShapeType::Ellipse => HintGroup(vec![HintInfo::keys([Key::Shift], "Constrain Circular"), HintInfo::keys([Key::Alt], "From Center")]),
|
||||||
|
ShapeType::Line => HintGroup(vec![
|
||||||
|
HintInfo::keys([Key::Shift], "15° Increments"),
|
||||||
|
HintInfo::keys([Key::Alt], "From Center"),
|
||||||
|
HintInfo::keys([Key::Control], "Lock Angle"),
|
||||||
|
]),
|
||||||
|
ShapeType::Circle => HintGroup(vec![HintInfo::keys([Key::Alt], "From Center")]),
|
||||||
|
};
|
||||||
|
|
||||||
|
if !tool_hint_group.0.is_empty() {
|
||||||
|
common_hint_group.push(tool_hint_group);
|
||||||
|
}
|
||||||
|
|
||||||
|
if matches!(shape, ShapeType::Polygon | ShapeType::Star) {
|
||||||
|
common_hint_group.push(HintGroup(vec![HintInfo::multi_keys([[Key::BracketLeft], [Key::BracketRight]], "Decrease/Increase Sides")]));
|
||||||
|
}
|
||||||
|
|
||||||
|
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")]),
|
||||||
|
]),
|
||||||
|
ShapeToolFsmState::RotatingBounds => HintData(vec![
|
||||||
|
HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]),
|
||||||
|
HintGroup(vec![HintInfo::keys([Key::Shift], "15° Increments")]),
|
||||||
|
]),
|
||||||
|
ShapeToolFsmState::SkewingBounds { .. } => HintData(vec![
|
||||||
|
HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]),
|
||||||
|
HintGroup(vec![HintInfo::keys([Key::Control], "Unlock Slide")]),
|
||||||
|
]),
|
||||||
|
ShapeToolFsmState::ModifyingGizmo => HintData(vec![HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()])]),
|
||||||
|
};
|
||||||
|
responses.add(FrontendMessage::UpdateInputHints { hint_data });
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue