Add "Grid" to the Shape tool along with row/column gizmos (#2921)
* integrated grid shape in shape-tool * add overlays,detection,transform for gizmo * fix compile issues * handle negative correctly,fix undo redo and abort * fix missed merge conflicts * fixed mouse cursor,correctly translatiing * cleanup * fix click-target area inside rect and spacing * add 10px closer to gizmo line * resolved conflicts * Code review --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
parent
65171a5b8e
commit
3e50d177b7
|
|
@ -131,6 +131,7 @@ pub const ARC_SNAP_THRESHOLD: f64 = 5.;
|
||||||
pub const ARC_SWEEP_GIZMO_RADIUS: f64 = 14.;
|
pub const ARC_SWEEP_GIZMO_RADIUS: f64 = 14.;
|
||||||
pub const ARC_SWEEP_GIZMO_TEXT_HEIGHT: f64 = 12.;
|
pub const ARC_SWEEP_GIZMO_TEXT_HEIGHT: f64 = 12.;
|
||||||
pub const GIZMO_HIDE_THRESHOLD: f64 = 20.;
|
pub const GIZMO_HIDE_THRESHOLD: f64 = 20.;
|
||||||
|
pub const GRID_ROW_COLUMN_GIZMO_OFFSET: f64 = 15.;
|
||||||
|
|
||||||
// SCROLLBARS
|
// SCROLLBARS
|
||||||
pub const SCROLLBAR_SPACING: f64 = 0.1;
|
pub const SCROLLBAR_SPACING: f64 = 0.1;
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||||
use crate::messages::tool::common_functionality::shape_editor::ShapeState;
|
use crate::messages::tool::common_functionality::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::circle_shape::CircleGizmoHandler;
|
||||||
|
use crate::messages::tool::common_functionality::shapes::grid_shape::GridGizmoHandler;
|
||||||
use crate::messages::tool::common_functionality::shapes::polygon_shape::PolygonGizmoHandler;
|
use crate::messages::tool::common_functionality::shapes::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;
|
||||||
|
|
@ -28,6 +29,7 @@ pub enum ShapeGizmoHandlers {
|
||||||
Polygon(PolygonGizmoHandler),
|
Polygon(PolygonGizmoHandler),
|
||||||
Arc(ArcGizmoHandler),
|
Arc(ArcGizmoHandler),
|
||||||
Circle(CircleGizmoHandler),
|
Circle(CircleGizmoHandler),
|
||||||
|
Grid(GridGizmoHandler),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ShapeGizmoHandlers {
|
impl ShapeGizmoHandlers {
|
||||||
|
|
@ -39,6 +41,7 @@ impl ShapeGizmoHandlers {
|
||||||
Self::Polygon(_) => "polygon",
|
Self::Polygon(_) => "polygon",
|
||||||
Self::Arc(_) => "arc",
|
Self::Arc(_) => "arc",
|
||||||
Self::Circle(_) => "circle",
|
Self::Circle(_) => "circle",
|
||||||
|
Self::Grid(_) => "grid",
|
||||||
Self::None => "none",
|
Self::None => "none",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -50,6 +53,7 @@ impl ShapeGizmoHandlers {
|
||||||
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::Circle(h) => h.handle_state(layer, mouse_position, document, responses),
|
||||||
|
Self::Grid(h) => h.handle_state(layer, mouse_position, document, responses),
|
||||||
Self::None => {}
|
Self::None => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -61,6 +65,7 @@ impl ShapeGizmoHandlers {
|
||||||
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::Circle(h) => h.is_any_gizmo_hovered(),
|
||||||
|
Self::Grid(h) => h.is_any_gizmo_hovered(),
|
||||||
Self::None => false,
|
Self::None => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -72,6 +77,7 @@ impl ShapeGizmoHandlers {
|
||||||
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::Circle(h) => h.handle_click(),
|
||||||
|
Self::Grid(h) => h.handle_click(),
|
||||||
Self::None => {}
|
Self::None => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -83,6 +89,7 @@ impl ShapeGizmoHandlers {
|
||||||
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::Circle(h) => h.handle_update(drag_start, document, input, responses),
|
||||||
|
Self::Grid(h) => h.handle_update(drag_start, document, input, responses),
|
||||||
Self::None => {}
|
Self::None => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -94,6 +101,7 @@ impl ShapeGizmoHandlers {
|
||||||
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::Circle(h) => h.cleanup(),
|
||||||
|
Self::Grid(h) => h.cleanup(),
|
||||||
Self::None => {}
|
Self::None => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -113,6 +121,7 @@ impl ShapeGizmoHandlers {
|
||||||
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::Circle(h) => h.overlays(document, layer, input, shape_editor, mouse_position, overlay_context),
|
||||||
|
Self::Grid(h) => h.overlays(document, layer, input, shape_editor, mouse_position, overlay_context),
|
||||||
Self::None => {}
|
Self::None => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -131,6 +140,7 @@ impl ShapeGizmoHandlers {
|
||||||
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::Circle(h) => h.dragging_overlays(document, input, shape_editor, mouse_position, overlay_context),
|
||||||
|
Self::Grid(h) => h.dragging_overlays(document, input, shape_editor, mouse_position, overlay_context),
|
||||||
Self::None => {}
|
Self::None => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -141,6 +151,7 @@ impl ShapeGizmoHandlers {
|
||||||
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::Circle(h) => h.mouse_cursor_icon(),
|
||||||
|
Self::Grid(h) => h.mouse_cursor_icon(),
|
||||||
Self::None => None,
|
Self::None => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -184,6 +195,10 @@ impl GizmoManager {
|
||||||
if graph_modification_utils::get_circle_id(layer, &document.network_interface).is_some() {
|
if graph_modification_utils::get_circle_id(layer, &document.network_interface).is_some() {
|
||||||
return Some(ShapeGizmoHandlers::Circle(CircleGizmoHandler::default()));
|
return Some(ShapeGizmoHandlers::Circle(CircleGizmoHandler::default()));
|
||||||
}
|
}
|
||||||
|
// Grid
|
||||||
|
if graph_modification_utils::get_grid_id(layer, &document.network_interface).is_some() {
|
||||||
|
return Some(ShapeGizmoHandlers::Grid(GridGizmoHandler::default()));
|
||||||
|
}
|
||||||
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ impl RadiusHandle {
|
||||||
let center = viewport.transform_point2(DVec2::ZERO);
|
let center = viewport.transform_point2(DVec2::ZERO);
|
||||||
if let Some(stroke_width) = get_stroke_width(layer, &document.network_interface) {
|
if let Some(stroke_width) = get_stroke_width(layer, &document.network_interface) {
|
||||||
let circle_point = calculate_circle_point_position(angle, radius.abs());
|
let circle_point = calculate_circle_point_position(angle, radius.abs());
|
||||||
let direction = circle_point.normalize();
|
let Some(direction) = circle_point.try_normalize() else { return false };
|
||||||
let mouse_distance = mouse_position.distance(center);
|
let mouse_distance = mouse_position.distance(center);
|
||||||
|
|
||||||
let spacing = Self::calculate_extra_spacing(viewport, radius, center, stroke_width, 15.);
|
let spacing = Self::calculate_extra_spacing(viewport, radius, center, stroke_width, 15.);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,435 @@
|
||||||
|
use crate::consts::GRID_ROW_COLUMN_GIZMO_OFFSET;
|
||||||
|
use crate::messages::frontend::utility_types::MouseCursorIcon;
|
||||||
|
use crate::messages::message::Message;
|
||||||
|
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;
|
||||||
|
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
|
||||||
|
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||||
|
use crate::messages::portfolio::document::utility_types::network_interface::InputConnector;
|
||||||
|
use crate::messages::prelude::{DocumentMessageHandler, InputPreprocessorMessageHandler, NodeGraphMessage};
|
||||||
|
use crate::messages::prelude::{GraphOperationMessage, Responses};
|
||||||
|
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||||
|
use crate::messages::tool::common_functionality::shape_editor::ShapeState;
|
||||||
|
use crate::messages::tool::common_functionality::shapes::shape_utility::extract_grid_parameters;
|
||||||
|
use glam::{DAffine2, DVec2};
|
||||||
|
use graph_craft::document::NodeInput;
|
||||||
|
use graph_craft::document::value::TaggedValue;
|
||||||
|
use graphene_std::NodeInputDecleration;
|
||||||
|
use graphene_std::vector::misc::{GridType, dvec2_to_point, get_line_endpoints};
|
||||||
|
use kurbo::{Line, ParamCurveNearest, Rect};
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default, PartialEq)]
|
||||||
|
pub enum RowColumnGizmoState {
|
||||||
|
#[default]
|
||||||
|
Inactive,
|
||||||
|
Hover,
|
||||||
|
Dragging,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default)]
|
||||||
|
pub struct RowColumnGizmo {
|
||||||
|
pub layer: Option<LayerNodeIdentifier>,
|
||||||
|
pub gizmo_type: RowColumnGizmoType,
|
||||||
|
initial_rows: u32,
|
||||||
|
initial_columns: u32,
|
||||||
|
spacing: DVec2,
|
||||||
|
initial_mouse_start: Option<DVec2>,
|
||||||
|
gizmo_state: RowColumnGizmoState,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RowColumnGizmo {
|
||||||
|
pub fn cleanup(&mut self) {
|
||||||
|
self.layer = None;
|
||||||
|
self.gizmo_state = RowColumnGizmoState::Inactive;
|
||||||
|
self.initial_mouse_start = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_state(&mut self, state: RowColumnGizmoState) {
|
||||||
|
self.gizmo_state = state;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_hovered(&self) -> bool {
|
||||||
|
self.gizmo_state == RowColumnGizmoState::Hover
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_dragging(&self) -> bool {
|
||||||
|
self.gizmo_state == RowColumnGizmoState::Dragging
|
||||||
|
}
|
||||||
|
|
||||||
|
fn initial_dimension(&self) -> u32 {
|
||||||
|
match &self.gizmo_type {
|
||||||
|
RowColumnGizmoType::Top | RowColumnGizmoType::Bottom => self.initial_rows,
|
||||||
|
RowColumnGizmoType::Left | RowColumnGizmoType::Right => self.initial_columns,
|
||||||
|
RowColumnGizmoType::None => panic!("RowColumnGizmoType::None does not have a mouse_icon"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_actions(&mut self, layer: LayerNodeIdentifier, mouse_position: DVec2, document: &DocumentMessageHandler) {
|
||||||
|
let Some((grid_type, spacing, columns, rows, angles)) = extract_grid_parameters(layer, document) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let viewport = document.metadata().transform_to_viewport(layer);
|
||||||
|
|
||||||
|
if let Some(gizmo_type) = check_if_over_gizmo(grid_type, columns, rows, spacing, angles, mouse_position, viewport) {
|
||||||
|
self.layer = Some(layer);
|
||||||
|
self.gizmo_type = gizmo_type;
|
||||||
|
self.initial_rows = rows;
|
||||||
|
self.initial_columns = columns;
|
||||||
|
self.spacing = spacing;
|
||||||
|
self.initial_mouse_start = None;
|
||||||
|
self.update_state(RowColumnGizmoState::Hover);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn overlays(&self, document: &DocumentMessageHandler, layer: Option<LayerNodeIdentifier>, _shape_editor: &mut &mut ShapeState, _mouse_position: DVec2, overlay_context: &mut OverlayContext) {
|
||||||
|
let Some(layer) = layer.or(self.layer) else { return };
|
||||||
|
let Some((grid_type, spacing, columns, rows, angles)) = extract_grid_parameters(layer, document) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let viewport = document.metadata().transform_to_viewport(layer);
|
||||||
|
|
||||||
|
if !matches!(self.gizmo_state, RowColumnGizmoState::Inactive) {
|
||||||
|
let line = self.gizmo_type.line(grid_type, columns, rows, spacing, angles, viewport);
|
||||||
|
let (p0, p1) = get_line_endpoints(line);
|
||||||
|
overlay_context.dashed_line(p0, p1, None, None, Some(5.), Some(5.), Some(0.5));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>, drag_start: DVec2) {
|
||||||
|
let Some(layer) = self.layer else { return };
|
||||||
|
let viewport = document.metadata().transform_to_viewport(layer);
|
||||||
|
|
||||||
|
let Some((grid_type, _, columns, rows, angles)) = extract_grid_parameters(layer, document) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let direction = self.gizmo_type.direction(viewport);
|
||||||
|
let delta_vector = input.mouse.position - self.initial_mouse_start.unwrap_or(drag_start);
|
||||||
|
|
||||||
|
let projection = delta_vector.project_onto(self.gizmo_type.direction(viewport));
|
||||||
|
let delta = viewport.inverse().transform_vector2(projection).length() * delta_vector.dot(direction).signum();
|
||||||
|
|
||||||
|
if delta.abs() < 1e-6 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let dimensions_to_add = (delta / (self.gizmo_type.spacing(self.spacing, grid_type, angles))).floor() as i32;
|
||||||
|
let new_dimension = (self.initial_dimension() as i32 + dimensions_to_add).max(1) as u32;
|
||||||
|
|
||||||
|
let Some(node_id) = graph_modification_utils::get_grid_id(layer, &document.network_interface) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let dimensions_delta = new_dimension as i32 - self.gizmo_type.initial_dimension(rows, columns) as i32;
|
||||||
|
let transform = self.transform_grid(dimensions_delta, self.spacing, grid_type, angles, viewport);
|
||||||
|
|
||||||
|
responses.add(NodeGraphMessage::SetInput {
|
||||||
|
input_connector: InputConnector::node(node_id, self.gizmo_type.index()),
|
||||||
|
input: NodeInput::value(TaggedValue::U32((self.initial_dimension() as i32 + dimensions_to_add).max(1) as u32), false),
|
||||||
|
});
|
||||||
|
|
||||||
|
responses.add(GraphOperationMessage::TransformChange {
|
||||||
|
layer,
|
||||||
|
transform,
|
||||||
|
transform_in: TransformIn::Viewport,
|
||||||
|
skip_rerender: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||||
|
|
||||||
|
if self.initial_dimension() as i32 + dimensions_to_add < 1 {
|
||||||
|
self.initial_mouse_start = Some(input.mouse.position);
|
||||||
|
self.gizmo_type = self.gizmo_type.opposite_gizmo_type();
|
||||||
|
self.initial_rows = 1;
|
||||||
|
self.initial_columns = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn transform_grid(&self, dimensions_delta: i32, spacing: DVec2, grid_type: GridType, angles: DVec2, viewport: DAffine2) -> DAffine2 {
|
||||||
|
match &self.gizmo_type {
|
||||||
|
RowColumnGizmoType::Top => {
|
||||||
|
let move_up_by = self.gizmo_type.direction(viewport) * dimensions_delta as f64 * spacing.y;
|
||||||
|
DAffine2::from_translation(move_up_by)
|
||||||
|
}
|
||||||
|
RowColumnGizmoType::Left => {
|
||||||
|
let move_left_by = self.gizmo_type.direction(viewport) * dimensions_delta as f64 * self.gizmo_type.spacing(spacing, grid_type, angles);
|
||||||
|
DAffine2::from_translation(move_left_by)
|
||||||
|
}
|
||||||
|
RowColumnGizmoType::Bottom | RowColumnGizmoType::Right | RowColumnGizmoType::None => DAffine2::IDENTITY,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_if_over_gizmo(grid_type: GridType, columns: u32, rows: u32, spacing: DVec2, angles: DVec2, mouse_position: DVec2, viewport: DAffine2) -> Option<RowColumnGizmoType> {
|
||||||
|
let mouse_point = dvec2_to_point(mouse_position);
|
||||||
|
let accuracy = 1e-6;
|
||||||
|
let threshold = 32.;
|
||||||
|
|
||||||
|
for gizmo_type in RowColumnGizmoType::all() {
|
||||||
|
let line = gizmo_type.line(grid_type, columns, rows, spacing, angles, viewport);
|
||||||
|
let rect = gizmo_type.rect(grid_type, columns, rows, spacing, angles, viewport);
|
||||||
|
|
||||||
|
if rect.contains(mouse_point) || line.nearest(mouse_point, accuracy).distance_sq < threshold {
|
||||||
|
return Some(gizmo_type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn convert_to_gizmo_line(p0: DVec2, p1: DVec2) -> Line {
|
||||||
|
Line {
|
||||||
|
p0: dvec2_to_point(p0),
|
||||||
|
p1: dvec2_to_point(p1),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get corners of the rectangular-grid.
|
||||||
|
/// Returns a tuple of (topleft,topright,bottomright,bottomleft)
|
||||||
|
fn get_corners(columns: u32, rows: u32, spacing: DVec2) -> (DVec2, DVec2, DVec2, DVec2) {
|
||||||
|
let (width, height) = (spacing.x, spacing.y);
|
||||||
|
|
||||||
|
let x_distance = (columns - 1) as f64 * width;
|
||||||
|
let y_distance = (rows - 1) as f64 * height;
|
||||||
|
|
||||||
|
let point0 = DVec2::ZERO;
|
||||||
|
let point1 = DVec2::new(x_distance, 0.);
|
||||||
|
let point2 = DVec2::new(x_distance, y_distance);
|
||||||
|
let point3 = DVec2::new(0., y_distance);
|
||||||
|
|
||||||
|
(point0, point1, point2, point3)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_rectangle_top_line_points(columns: u32, rows: u32, spacing: DVec2) -> (DVec2, DVec2) {
|
||||||
|
let (top_left, top_right, _, _) = get_corners(columns, rows, spacing);
|
||||||
|
let offset = if columns == 1 || rows == 1 {
|
||||||
|
DVec2::ZERO
|
||||||
|
} else if columns == 2 {
|
||||||
|
DVec2::new(spacing.x * 0.25, 0.)
|
||||||
|
} else {
|
||||||
|
DVec2::new(spacing.x * 0.5, 0.)
|
||||||
|
};
|
||||||
|
|
||||||
|
(top_left + offset, top_right - offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_rectangle_bottom_line_points(columns: u32, rows: u32, spacing: DVec2) -> (DVec2, DVec2) {
|
||||||
|
let (_, _, bottom_right, bottom_left) = get_corners(columns, rows, spacing);
|
||||||
|
let offset = if columns == 1 || rows == 1 {
|
||||||
|
DVec2::ZERO
|
||||||
|
} else if columns == 2 {
|
||||||
|
DVec2::new(spacing.x * 0.25, 0.)
|
||||||
|
} else {
|
||||||
|
DVec2::new(spacing.x * 0.5, 0.)
|
||||||
|
};
|
||||||
|
|
||||||
|
(bottom_left + offset, bottom_right - offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_rectangle_right_line_points(columns: u32, rows: u32, spacing: DVec2) -> (DVec2, DVec2) {
|
||||||
|
let (_, top_right, bottom_right, _) = get_corners(columns, rows, spacing);
|
||||||
|
let offset = if columns == 1 || rows == 1 {
|
||||||
|
DVec2::ZERO
|
||||||
|
} else if rows == 2 {
|
||||||
|
DVec2::new(0., -spacing.y * 0.25)
|
||||||
|
} else {
|
||||||
|
DVec2::new(0., -spacing.y * 0.5)
|
||||||
|
};
|
||||||
|
|
||||||
|
(top_right - offset, bottom_right + offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_rectangle_left_line_points(columns: u32, rows: u32, spacing: DVec2) -> (DVec2, DVec2) {
|
||||||
|
let (top_left, _, _, bottom_left) = get_corners(columns, rows, spacing);
|
||||||
|
let offset = if columns == 1 || rows == 1 {
|
||||||
|
DVec2::ZERO
|
||||||
|
} else if rows == 2 {
|
||||||
|
DVec2::new(0., -spacing.y * 0.25)
|
||||||
|
} else {
|
||||||
|
DVec2::new(0., -spacing.y * 0.5)
|
||||||
|
};
|
||||||
|
|
||||||
|
(top_left - offset, bottom_left + offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn calculate_isometric_point(column: u32, row: u32, angles: DVec2, spacing: DVec2) -> DVec2 {
|
||||||
|
let tan_a = angles.x.to_radians().tan();
|
||||||
|
let tan_b = angles.y.to_radians().tan();
|
||||||
|
|
||||||
|
let spacing = DVec2::new(spacing.y / (tan_a + tan_b), spacing.y);
|
||||||
|
|
||||||
|
let a_angles_eaten = column.div_ceil(2) as f64;
|
||||||
|
let b_angles_eaten = (column / 2) as f64;
|
||||||
|
|
||||||
|
let offset_y_fraction = b_angles_eaten * tan_b - a_angles_eaten * tan_a;
|
||||||
|
|
||||||
|
DVec2::new(spacing.x * column as f64, spacing.y * row as f64 + offset_y_fraction * spacing.x)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn calculate_isometric_top_line_points(columns: u32, rows: u32, spacing: DVec2, angles: DVec2) -> (DVec2, DVec2) {
|
||||||
|
let top_left = calculate_isometric_point(0, 0, angles, spacing);
|
||||||
|
let top_right = calculate_isometric_point(columns - 1, 0, angles, spacing);
|
||||||
|
|
||||||
|
let offset = if columns == 1 || rows == 1 { DVec2::ZERO } else { DVec2::new(spacing.x * 0.5, 0.) };
|
||||||
|
let isometric_spacing = calculate_isometric_offset(spacing, angles);
|
||||||
|
let isometric_offset = DVec2::new(0., isometric_spacing.y);
|
||||||
|
let end_isometric_offset = if columns % 2 == 0 { DVec2::ZERO } else { DVec2::new(0., isometric_spacing.y) };
|
||||||
|
|
||||||
|
(top_left + offset - isometric_offset, top_right - offset - end_isometric_offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn calculate_isometric_bottom_line_points(columns: u32, rows: u32, spacing: DVec2, angles: DVec2) -> (DVec2, DVec2) {
|
||||||
|
let bottom_left = calculate_isometric_point(0, rows - 1, angles, spacing);
|
||||||
|
let bottom_right = calculate_isometric_point(columns - 1, rows - 1, angles, spacing);
|
||||||
|
|
||||||
|
let offset = if columns == 1 || rows == 1 { DVec2::ZERO } else { DVec2::new(spacing.x * 0.5, 0.) };
|
||||||
|
let isometric_offset = if columns % 2 == 0 {
|
||||||
|
let offset = calculate_isometric_offset(spacing, angles);
|
||||||
|
DVec2::new(0., offset.y)
|
||||||
|
} else {
|
||||||
|
DVec2::ZERO
|
||||||
|
};
|
||||||
|
|
||||||
|
(bottom_left + offset, bottom_right - offset + isometric_offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn calculate_isometric_offset(spacing: DVec2, angles: DVec2) -> DVec2 {
|
||||||
|
let first_point = calculate_isometric_point(0, 0, angles, spacing);
|
||||||
|
let second_point = calculate_isometric_point(1, 0, angles, spacing);
|
||||||
|
|
||||||
|
DVec2::new(first_point.x - second_point.x, first_point.y - second_point.y)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn calculate_isometric_right_line_points(columns: u32, rows: u32, spacing: DVec2, angles: DVec2) -> (DVec2, DVec2) {
|
||||||
|
let top_right = calculate_isometric_point(columns - 1, 0, angles, spacing);
|
||||||
|
let bottom_right = calculate_isometric_point(columns - 1, rows - 1, angles, spacing);
|
||||||
|
|
||||||
|
let offset = if columns == 1 || rows == 1 { DVec2::ZERO } else { DVec2::new(0., -spacing.y * 0.5) };
|
||||||
|
|
||||||
|
(top_right - offset, bottom_right + offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn calculate_isometric_left_line_points(columns: u32, rows: u32, spacing: DVec2, angles: DVec2) -> (DVec2, DVec2) {
|
||||||
|
let top_left = calculate_isometric_point(0, 0, angles, spacing);
|
||||||
|
let bottom_left = calculate_isometric_point(0, rows - 1, angles, spacing);
|
||||||
|
|
||||||
|
let offset = if columns == 1 || rows == 1 { DVec2::ZERO } else { DVec2::new(0., -spacing.y * 0.5) };
|
||||||
|
|
||||||
|
(top_left - offset, bottom_left + offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default, PartialEq)]
|
||||||
|
pub enum RowColumnGizmoType {
|
||||||
|
#[default]
|
||||||
|
None,
|
||||||
|
Top,
|
||||||
|
Bottom,
|
||||||
|
Left,
|
||||||
|
Right,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RowColumnGizmoType {
|
||||||
|
pub fn get_line_points(&self, grid_type: GridType, columns: u32, rows: u32, spacing: DVec2, angles: DVec2) -> (DVec2, DVec2) {
|
||||||
|
match grid_type {
|
||||||
|
GridType::Rectangular => match self {
|
||||||
|
Self::Top => get_rectangle_top_line_points(columns, rows, spacing),
|
||||||
|
Self::Right => get_rectangle_right_line_points(columns, rows, spacing),
|
||||||
|
Self::Bottom => get_rectangle_bottom_line_points(columns, rows, spacing),
|
||||||
|
Self::Left => get_rectangle_left_line_points(columns, rows, spacing),
|
||||||
|
Self::None => panic!("RowColumnGizmoType::None does not have line points"),
|
||||||
|
},
|
||||||
|
GridType::Isometric => match self {
|
||||||
|
Self::Top => calculate_isometric_top_line_points(columns, rows, spacing, angles),
|
||||||
|
Self::Right => calculate_isometric_right_line_points(columns, rows, spacing, angles),
|
||||||
|
Self::Bottom => calculate_isometric_bottom_line_points(columns, rows, spacing, angles),
|
||||||
|
Self::Left => calculate_isometric_left_line_points(columns, rows, spacing, angles),
|
||||||
|
Self::None => panic!("RowColumnGizmoType::None does not have line points"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn line(&self, grid_type: GridType, columns: u32, rows: u32, spacing: DVec2, angles: DVec2, viewport: DAffine2) -> Line {
|
||||||
|
let (p0, p1) = self.get_line_points(grid_type, columns, rows, spacing, angles);
|
||||||
|
let direction = self.direction(viewport);
|
||||||
|
let gap = GRID_ROW_COLUMN_GIZMO_OFFSET * viewport.inverse().transform_vector2(direction).normalize();
|
||||||
|
|
||||||
|
convert_to_gizmo_line(viewport.transform_point2(p0 + gap), viewport.transform_point2(p1 + gap))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rect(&self, grid_type: GridType, columns: u32, rows: u32, spacing: DVec2, angles: DVec2, viewport: DAffine2) -> Rect {
|
||||||
|
let (p0, p1) = self.get_line_points(grid_type, columns, rows, spacing, angles);
|
||||||
|
let direction = self.direction(viewport);
|
||||||
|
let gap = GRID_ROW_COLUMN_GIZMO_OFFSET * direction.normalize();
|
||||||
|
|
||||||
|
let (x0, x1) = match self {
|
||||||
|
Self::Top | Self::Left => (viewport.transform_point2(p0 + gap), viewport.transform_point2(p1)),
|
||||||
|
Self::Bottom | Self::Right => (viewport.transform_point2(p0), viewport.transform_point2(p1 + gap)),
|
||||||
|
Self::None => panic!("RowColumnGizmoType::None does not have opposite"),
|
||||||
|
};
|
||||||
|
|
||||||
|
Rect::new(x0.x, x0.y, x1.x, x1.y)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn opposite_gizmo_type(&self) -> Self {
|
||||||
|
match self {
|
||||||
|
Self::Top => Self::Bottom,
|
||||||
|
Self::Right => Self::Left,
|
||||||
|
Self::Bottom => Self::Top,
|
||||||
|
Self::Left => Self::Right,
|
||||||
|
Self::None => panic!("RowColumnGizmoType::None does not have opposite"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn direction(&self, viewport: DAffine2) -> DVec2 {
|
||||||
|
match self {
|
||||||
|
RowColumnGizmoType::Top => viewport.transform_vector2(-DVec2::Y),
|
||||||
|
RowColumnGizmoType::Bottom => viewport.transform_vector2(DVec2::Y),
|
||||||
|
RowColumnGizmoType::Right => viewport.transform_vector2(DVec2::X),
|
||||||
|
RowColumnGizmoType::Left => viewport.transform_vector2(-DVec2::X),
|
||||||
|
RowColumnGizmoType::None => panic!("RowColumnGizmoType::None does not have a line"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn initial_dimension(&self, rows: u32, columns: u32) -> u32 {
|
||||||
|
match self {
|
||||||
|
RowColumnGizmoType::Top | RowColumnGizmoType::Bottom => rows,
|
||||||
|
RowColumnGizmoType::Left | RowColumnGizmoType::Right => columns,
|
||||||
|
RowColumnGizmoType::None => panic!("RowColumnGizmoType::None does not have a mouse_icon"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn spacing(&self, spacing: DVec2, grid_type: GridType, angles: DVec2) -> f64 {
|
||||||
|
match self {
|
||||||
|
RowColumnGizmoType::Top | RowColumnGizmoType::Bottom => spacing.y,
|
||||||
|
RowColumnGizmoType::Left | RowColumnGizmoType::Right => {
|
||||||
|
if grid_type == GridType::Rectangular {
|
||||||
|
spacing.x
|
||||||
|
} else {
|
||||||
|
spacing.y / (angles.x.to_radians().tan() + angles.y.to_radians().tan())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RowColumnGizmoType::None => panic!("RowColumnGizmoType::None does not have a mouse_icon"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn index(&self) -> usize {
|
||||||
|
use graphene_std::vector::generator_nodes::grid::*;
|
||||||
|
|
||||||
|
match self {
|
||||||
|
RowColumnGizmoType::Top | RowColumnGizmoType::Bottom => RowsInput::INDEX,
|
||||||
|
RowColumnGizmoType::Left | RowColumnGizmoType::Right => ColumnsInput::INDEX,
|
||||||
|
RowColumnGizmoType::None => panic!("RowColumnGizmoType::None does not have a mouse_icon"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mouse_icon(&self) -> MouseCursorIcon {
|
||||||
|
match self {
|
||||||
|
RowColumnGizmoType::Top | RowColumnGizmoType::Bottom => MouseCursorIcon::NSResize,
|
||||||
|
RowColumnGizmoType::Left | RowColumnGizmoType::Right => MouseCursorIcon::EWResize,
|
||||||
|
RowColumnGizmoType::None => panic!("RowColumnGizmoType::None does not have a mouse_icon"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn all() -> [Self; 4] {
|
||||||
|
[Self::Top, Self::Right, Self::Bottom, Self::Left]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
pub mod circle_arc_radius_handle;
|
pub mod circle_arc_radius_handle;
|
||||||
|
pub mod grid_rows_columns_gizmo;
|
||||||
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;
|
||||||
|
|
|
||||||
|
|
@ -367,6 +367,10 @@ pub fn get_text_id(layer: LayerNodeIdentifier, network_interface: &NodeNetworkIn
|
||||||
NodeGraphLayer::new(layer, network_interface).upstream_node_id_from_name("Text")
|
NodeGraphLayer::new(layer, network_interface).upstream_node_id_from_name("Text")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_grid_id(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<NodeId> {
|
||||||
|
NodeGraphLayer::new(layer, network_interface).upstream_node_id_from_name("Grid")
|
||||||
|
}
|
||||||
|
|
||||||
/// Gets properties from the Text node
|
/// Gets properties from the Text node
|
||||||
pub fn get_text(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<(&String, &Font, TypesettingConfig, bool)> {
|
pub fn get_text(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<(&String, &Font, TypesettingConfig, bool)> {
|
||||||
let inputs = NodeGraphLayer::new(layer, network_interface).find_node_inputs("Text")?;
|
let inputs = NodeGraphLayer::new(layer, network_interface).find_node_inputs("Text")?;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,256 @@
|
||||||
|
use super::shape_utility::ShapeToolModifierKey;
|
||||||
|
use super::*;
|
||||||
|
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;
|
||||||
|
use crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type;
|
||||||
|
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
|
||||||
|
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||||
|
use crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, NodeTemplate};
|
||||||
|
use crate::messages::tool::common_functionality::gizmos::shape_gizmos::grid_rows_columns_gizmo::{RowColumnGizmo, RowColumnGizmoState};
|
||||||
|
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;
|
||||||
|
use graphene_std::NodeInputDecleration;
|
||||||
|
use graphene_std::vector::misc::GridType;
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default)]
|
||||||
|
pub struct GridGizmoHandler {
|
||||||
|
row_column_gizmo: RowColumnGizmo,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ShapeGizmoHandler for GridGizmoHandler {
|
||||||
|
fn is_any_gizmo_hovered(&self) -> bool {
|
||||||
|
self.row_column_gizmo.is_hovered()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_state(&mut self, selected_grid_layer: LayerNodeIdentifier, mouse_position: DVec2, document: &DocumentMessageHandler, _responses: &mut VecDeque<Message>) {
|
||||||
|
self.row_column_gizmo.handle_actions(selected_grid_layer, mouse_position, document);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_click(&mut self) {
|
||||||
|
if self.row_column_gizmo.is_hovered() {
|
||||||
|
self.row_column_gizmo.update_state(RowColumnGizmoState::Dragging);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_update(&mut self, drag_start: DVec2, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) {
|
||||||
|
if self.row_column_gizmo.is_dragging() {
|
||||||
|
self.row_column_gizmo.update(document, input, responses, drag_start);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn overlays(
|
||||||
|
&self,
|
||||||
|
document: &DocumentMessageHandler,
|
||||||
|
selected_grid_layer: Option<LayerNodeIdentifier>,
|
||||||
|
_input: &InputPreprocessorMessageHandler,
|
||||||
|
shape_editor: &mut &mut ShapeState,
|
||||||
|
mouse_position: DVec2,
|
||||||
|
overlay_context: &mut OverlayContext,
|
||||||
|
) {
|
||||||
|
self.row_column_gizmo.overlays(document, selected_grid_layer, shape_editor, mouse_position, overlay_context);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dragging_overlays(
|
||||||
|
&self,
|
||||||
|
document: &DocumentMessageHandler,
|
||||||
|
_input: &InputPreprocessorMessageHandler,
|
||||||
|
shape_editor: &mut &mut ShapeState,
|
||||||
|
mouse_position: DVec2,
|
||||||
|
overlay_context: &mut OverlayContext,
|
||||||
|
) {
|
||||||
|
if self.row_column_gizmo.is_dragging() {
|
||||||
|
self.row_column_gizmo.overlays(document, None, shape_editor, mouse_position, overlay_context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cleanup(&mut self) {
|
||||||
|
self.row_column_gizmo.cleanup();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mouse_cursor_icon(&self) -> Option<MouseCursorIcon> {
|
||||||
|
if self.row_column_gizmo.is_hovered() || self.row_column_gizmo.is_dragging() {
|
||||||
|
return Some(self.row_column_gizmo.gizmo_type.mouse_icon());
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct Grid;
|
||||||
|
|
||||||
|
impl Grid {
|
||||||
|
pub fn create_node(grid_type: GridType) -> NodeTemplate {
|
||||||
|
let node_type = resolve_document_node_type("Grid").expect("Grid can't be found");
|
||||||
|
node_type.node_template_input_override([
|
||||||
|
None,
|
||||||
|
Some(NodeInput::value(TaggedValue::GridType(grid_type), false)),
|
||||||
|
Some(NodeInput::value(TaggedValue::DVec2(DVec2::ZERO), false)),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_shape(
|
||||||
|
document: &DocumentMessageHandler,
|
||||||
|
ipp: &InputPreprocessorMessageHandler,
|
||||||
|
layer: LayerNodeIdentifier,
|
||||||
|
grid_type: GridType,
|
||||||
|
shape_tool_data: &mut ShapeToolData,
|
||||||
|
modifier: ShapeToolModifierKey,
|
||||||
|
responses: &mut VecDeque<Message>,
|
||||||
|
) {
|
||||||
|
use graphene_std::vector::generator_nodes::grid::*;
|
||||||
|
|
||||||
|
let [center, lock_ratio, _] = modifier;
|
||||||
|
let is_isometric = grid_type == GridType::Isometric;
|
||||||
|
|
||||||
|
let Some(node_id) = graph_modification_utils::get_grid_id(layer, &document.network_interface) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let start = shape_tool_data.data.viewport_drag_start(document);
|
||||||
|
let end = ipp.mouse.position;
|
||||||
|
|
||||||
|
let (translation, dimensions, angle) = calculate_grid_params(start, end, is_isometric, ipp.keyboard.key(center), ipp.keyboard.key(lock_ratio));
|
||||||
|
|
||||||
|
// Set dimensions/spacing
|
||||||
|
responses.add(NodeGraphMessage::SetInput {
|
||||||
|
input_connector: InputConnector::node(node_id, SpacingInput::<f64>::INDEX),
|
||||||
|
input: NodeInput::value(TaggedValue::DVec2(dimensions), false),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set angle for isometric grids
|
||||||
|
if let Some(angle_deg) = angle {
|
||||||
|
responses.add(NodeGraphMessage::SetInput {
|
||||||
|
input_connector: InputConnector::node(node_id, AnglesInput::INDEX),
|
||||||
|
input: NodeInput::value(TaggedValue::DVec2(DVec2::splat(angle_deg)), false),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set transform
|
||||||
|
responses.add(GraphOperationMessage::TransformSet {
|
||||||
|
layer,
|
||||||
|
transform: DAffine2::from_scale_angle_translation(DVec2::ONE, 0., translation),
|
||||||
|
transform_in: TransformIn::Viewport,
|
||||||
|
skip_rerender: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn calculate_grid_params(start: DVec2, end: DVec2, is_isometric: bool, center: bool, lock_ratio: bool) -> (DVec2, DVec2, Option<f64>) {
|
||||||
|
let raw_dimensions = (start - end).abs();
|
||||||
|
let mouse_delta = end - start;
|
||||||
|
let dimensions;
|
||||||
|
let mut translation = start;
|
||||||
|
let mut angle = None;
|
||||||
|
|
||||||
|
match (center, lock_ratio) {
|
||||||
|
// Both center and lock_ratio: centered + square/fixed-angle grid
|
||||||
|
(true, true) => {
|
||||||
|
if is_isometric {
|
||||||
|
// Fix angle at 30° - standardized isometric view
|
||||||
|
angle = Some(30.);
|
||||||
|
|
||||||
|
// Calculate the width based on given height and angle 30°
|
||||||
|
let width = calculate_isometric_x_position(raw_dimensions.y / 9., 30_f64.to_radians(), 30_f64.to_radians()).abs();
|
||||||
|
|
||||||
|
// To make draw from center: shift x by half of width and y by half of height (mouse_delta.y)
|
||||||
|
translation -= DVec2::new(width / 2., mouse_delta.y / 2.);
|
||||||
|
dimensions = DVec2::splat(raw_dimensions.y) / 9.;
|
||||||
|
|
||||||
|
// Adjust for negative upward drag - compensate for coordinate system
|
||||||
|
if end.y < start.y {
|
||||||
|
translation -= DVec2::new(0., start.y - end.y);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// We want to make both dimensions the same so we choose whichever is bigger and shift to make center
|
||||||
|
let max = raw_dimensions.x.max(raw_dimensions.y);
|
||||||
|
let distance_to_center = max;
|
||||||
|
translation = start - distance_to_center;
|
||||||
|
dimensions = 2. * DVec2::splat(max) / 9.; // 2x because centering halves the effective area
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only center: centered grid with free aspect ratio
|
||||||
|
(true, false) => {
|
||||||
|
if is_isometric {
|
||||||
|
// Calculate angle from mouse movement - dynamic angle based on drag direction
|
||||||
|
angle = Some((raw_dimensions.y / (mouse_delta.x * 2.)).atan().to_degrees());
|
||||||
|
|
||||||
|
// To make draw from center: shift by half of mouse movement
|
||||||
|
translation -= mouse_delta / 2.;
|
||||||
|
dimensions = DVec2::splat(raw_dimensions.y) / 9.;
|
||||||
|
|
||||||
|
// Adjust for upward drag - maintain proper grid positioning
|
||||||
|
if end.y < start.y {
|
||||||
|
translation -= DVec2::new(0., start.y - end.y);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Logic: Rectangular centered grid using exact drag proportions
|
||||||
|
let distance_to_center = raw_dimensions;
|
||||||
|
translation = start - distance_to_center;
|
||||||
|
dimensions = 2. * raw_dimensions / 9.; // 2x for centering
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only lock_ratio: square/fixed-angle grid from drag start point
|
||||||
|
(false, true) => {
|
||||||
|
let max: f64;
|
||||||
|
if is_isometric {
|
||||||
|
dimensions = DVec2::splat(raw_dimensions.y) / 9.;
|
||||||
|
|
||||||
|
// Use 30° for angle - consistent isometric standard
|
||||||
|
angle = Some(30.);
|
||||||
|
max = raw_dimensions.y;
|
||||||
|
} else {
|
||||||
|
// Logic: Force square grid by using larger dimension
|
||||||
|
max = raw_dimensions.x.max(raw_dimensions.y);
|
||||||
|
dimensions = DVec2::splat(max) / 9.;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adjust for negative drag directions - maintain grid at intended position
|
||||||
|
if end.y < start.y {
|
||||||
|
translation -= DVec2::new(0., max);
|
||||||
|
}
|
||||||
|
if end.x < start.x {
|
||||||
|
translation -= DVec2::new(max, 0.);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Neither center nor lock_ratio: free-form grid following exact user input
|
||||||
|
(false, false) => {
|
||||||
|
if is_isometric {
|
||||||
|
// Calculate angle from mouse movement - fully dynamic
|
||||||
|
// Logic: angle represents user's exact intended perspective
|
||||||
|
angle = Some((raw_dimensions.y / (mouse_delta.x * 2.)).atan().to_degrees());
|
||||||
|
dimensions = DVec2::splat(raw_dimensions.y) / 9.;
|
||||||
|
} else {
|
||||||
|
// Use exact drag dimensions for grid spacing - what you drag is what you get
|
||||||
|
// Logic: Direct mapping of user gesture to grid parameters
|
||||||
|
dimensions = raw_dimensions / 9.;
|
||||||
|
|
||||||
|
// Adjust for leftward drag - keep grid positioned correctly
|
||||||
|
if end.x < start.x {
|
||||||
|
translation -= DVec2::new(start.x - end.x, 0.);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adjust for upward drag (common to both grid types)
|
||||||
|
// Logic: compensate for coordinate system where Y increases downward
|
||||||
|
if end.y < start.y {
|
||||||
|
translation -= DVec2::new(0., start.y - end.y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(translation, dimensions, angle)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn calculate_isometric_x_position(y_spacing: f64, rad_a: f64, rad_b: f64) -> f64 {
|
||||||
|
let spacing_x = y_spacing / (rad_a.tan() + rad_b.tan());
|
||||||
|
spacing_x * 9.
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
pub mod arc_shape;
|
pub mod arc_shape;
|
||||||
pub mod circle_shape;
|
pub mod circle_shape;
|
||||||
pub mod ellipse_shape;
|
pub mod ellipse_shape;
|
||||||
|
pub mod grid_shape;
|
||||||
pub mod line_shape;
|
pub mod line_shape;
|
||||||
pub mod polygon_shape;
|
pub mod polygon_shape;
|
||||||
pub mod rectangle_shape;
|
pub mod rectangle_shape;
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,16 @@
|
||||||
use super::shape_utility::ShapeToolModifierKey;
|
use super::shape_utility::{ShapeToolModifierKey, update_radius_sign};
|
||||||
use super::shape_utility::update_radius_sign;
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;
|
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::node_graph::document_node_definitions::resolve_document_node_type;
|
||||||
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;
|
||||||
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::number_of_points_dial::NumberOfPointsDial;
|
use crate::messages::tool::common_functionality::gizmos::shape_gizmos::number_of_points_dial::{NumberOfPointsDial, NumberOfPointsDialState};
|
||||||
use crate::messages::tool::common_functionality::gizmos::shape_gizmos::number_of_points_dial::NumberOfPointsDialState;
|
use crate::messages::tool::common_functionality::gizmos::shape_gizmos::point_radius_handle::{PointRadiusHandle, PointRadiusHandleState};
|
||||||
use crate::messages::tool::common_functionality::gizmos::shape_gizmos::point_radius_handle::PointRadiusHandle;
|
use crate::messages::tool::common_functionality::graph_modification_utils::{self, NodeGraphLayer};
|
||||||
use crate::messages::tool::common_functionality::gizmos::shape_gizmos::point_radius_handle::PointRadiusHandleState;
|
|
||||||
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::shape_utility::ShapeGizmoHandler;
|
use crate::messages::tool::common_functionality::shapes::shape_utility::{ShapeGizmoHandler, polygon_outline};
|
||||||
use crate::messages::tool::common_functionality::shapes::shape_utility::polygon_outline;
|
use crate::messages::tool::tool_messages::shape_tool::ShapeOptionsUpdate;
|
||||||
use crate::messages::tool::tool_messages::tool_prelude::*;
|
use crate::messages::tool::tool_messages::tool_prelude::*;
|
||||||
use glam::DAffine2;
|
use glam::DAffine2;
|
||||||
use graph_craft::document::NodeInput;
|
use graph_craft::document::NodeInput;
|
||||||
|
|
@ -160,4 +157,35 @@ impl Polygon {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn increase_decrease_sides(increase: bool, document: &DocumentMessageHandler, shape_tool_data: &mut ShapeToolData, responses: &mut VecDeque<Message>) {
|
||||||
|
if let Some(layer) = shape_tool_data.data.layer {
|
||||||
|
let Some(node_id) = graph_modification_utils::get_polygon_id(layer, &document.network_interface).or(graph_modification_utils::get_star_id(layer, &document.network_interface)) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(node_inputs) = NodeGraphLayer::new(layer, &document.network_interface)
|
||||||
|
.find_node_inputs("Regular Polygon")
|
||||||
|
.or(NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs("Star"))
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(&TaggedValue::U32(n)) = node_inputs.get(1).unwrap().as_value() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let new_dimension = if increase { n + 1 } else { (n - 1).max(3) };
|
||||||
|
|
||||||
|
responses.add(ShapeToolMessage::UpdateOptions {
|
||||||
|
options: ShapeOptionsUpdate::Vertices(new_dimension),
|
||||||
|
});
|
||||||
|
|
||||||
|
responses.add(NodeGraphMessage::SetInput {
|
||||||
|
input_connector: InputConnector::node(node_id, 1),
|
||||||
|
input: NodeInput::value(TaggedValue::U32(new_dimension), false),
|
||||||
|
});
|
||||||
|
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,9 +14,10 @@ use crate::messages::tool::utility_types::*;
|
||||||
use glam::{DAffine2, DMat2, DVec2};
|
use glam::{DAffine2, DMat2, DVec2};
|
||||||
use graph_craft::document::NodeInput;
|
use graph_craft::document::NodeInput;
|
||||||
use graph_craft::document::value::TaggedValue;
|
use graph_craft::document::value::TaggedValue;
|
||||||
|
use graphene_std::NodeInputDecleration;
|
||||||
use graphene_std::subpath::{self, Subpath};
|
use graphene_std::subpath::{self, Subpath};
|
||||||
use graphene_std::vector::click_target::ClickTargetType;
|
use graphene_std::vector::click_target::ClickTargetType;
|
||||||
use graphene_std::vector::misc::{ArcType, dvec2_to_point};
|
use graphene_std::vector::misc::{ArcType, GridType, dvec2_to_point};
|
||||||
use kurbo::{BezPath, PathEl, Shape};
|
use kurbo::{BezPath, PathEl, Shape};
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
use std::f64::consts::{PI, TAU};
|
use std::f64::consts::{PI, TAU};
|
||||||
|
|
@ -28,6 +29,7 @@ pub enum ShapeType {
|
||||||
Star,
|
Star,
|
||||||
Circle,
|
Circle,
|
||||||
Arc,
|
Arc,
|
||||||
|
Grid,
|
||||||
Rectangle,
|
Rectangle,
|
||||||
Ellipse,
|
Ellipse,
|
||||||
Line,
|
Line,
|
||||||
|
|
@ -40,6 +42,7 @@ impl ShapeType {
|
||||||
Self::Star => "Star",
|
Self::Star => "Star",
|
||||||
Self::Circle => "Circle",
|
Self::Circle => "Circle",
|
||||||
Self::Arc => "Arc",
|
Self::Arc => "Arc",
|
||||||
|
Self::Grid => "Grid",
|
||||||
Self::Rectangle => "Rectangle",
|
Self::Rectangle => "Rectangle",
|
||||||
Self::Ellipse => "Ellipse",
|
Self::Ellipse => "Ellipse",
|
||||||
Self::Line => "Line",
|
Self::Line => "Line",
|
||||||
|
|
@ -475,3 +478,23 @@ pub fn calculate_arc_text_transform(angle: f64, offset_angle: f64, center: DVec2
|
||||||
);
|
);
|
||||||
DAffine2::from_translation(text_texture_position + center)
|
DAffine2::from_translation(text_texture_position + center)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Extract the node input values of Grid.
|
||||||
|
/// Returns an option of (grid_type, spacing, columns, rows, angles).
|
||||||
|
pub fn extract_grid_parameters(layer: LayerNodeIdentifier, document: &DocumentMessageHandler) -> Option<(GridType, DVec2, u32, u32, DVec2)> {
|
||||||
|
use graphene_std::vector::generator_nodes::grid::*;
|
||||||
|
|
||||||
|
let node_inputs = NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs("Grid")?;
|
||||||
|
|
||||||
|
let (Some(&TaggedValue::GridType(grid_type)), Some(&TaggedValue::DVec2(spacing)), Some(&TaggedValue::U32(columns)), Some(&TaggedValue::U32(rows)), Some(&TaggedValue::DVec2(angles))) = (
|
||||||
|
node_inputs.get(GridTypeInput::INDEX)?.as_value(),
|
||||||
|
node_inputs.get(SpacingInput::<f64>::INDEX)?.as_value(),
|
||||||
|
node_inputs.get(ColumnsInput::INDEX)?.as_value(),
|
||||||
|
node_inputs.get(RowsInput::INDEX)?.as_value(),
|
||||||
|
node_inputs.get(AnglesInput::INDEX)?.as_value(),
|
||||||
|
) else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
|
Some((grid_type, spacing, columns, rows, angles))
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,15 +3,14 @@ use crate::consts::{DEFAULT_STROKE_WIDTH, SNAP_POINT_TOLERANCE};
|
||||||
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;
|
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;
|
||||||
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;
|
||||||
use crate::messages::portfolio::document::utility_types::network_interface::InputConnector;
|
|
||||||
use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
|
use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
|
||||||
use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType};
|
use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType};
|
||||||
use crate::messages::tool::common_functionality::gizmos::gizmo_manager::GizmoManager;
|
use crate::messages::tool::common_functionality::gizmos::gizmo_manager::GizmoManager;
|
||||||
use crate::messages::tool::common_functionality::graph_modification_utils;
|
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::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::circle_shape::Circle;
|
||||||
|
use crate::messages::tool::common_functionality::shapes::grid_shape::Grid;
|
||||||
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};
|
||||||
|
|
@ -20,11 +19,10 @@ use crate::messages::tool::common_functionality::shapes::{Ellipse, Line, Rectang
|
||||||
use crate::messages::tool::common_functionality::snapping::{self, SnapCandidatePoint, SnapData, SnapTypeConfiguration};
|
use crate::messages::tool::common_functionality::snapping::{self, SnapCandidatePoint, SnapData, SnapTypeConfiguration};
|
||||||
use crate::messages::tool::common_functionality::transformation_cage::{BoundingBoxManager, EdgeBool};
|
use crate::messages::tool::common_functionality::transformation_cage::{BoundingBoxManager, EdgeBool};
|
||||||
use crate::messages::tool::common_functionality::utility_functions::{closest_point, resize_bounds, rotate_bounds, skew_bounds, transforming_transform_cage};
|
use crate::messages::tool::common_functionality::utility_functions::{closest_point, resize_bounds, rotate_bounds, skew_bounds, transforming_transform_cage};
|
||||||
use graph_craft::document::value::TaggedValue;
|
use graph_craft::document::NodeId;
|
||||||
use graph_craft::document::{NodeId, NodeInput};
|
|
||||||
use graphene_std::Color;
|
use graphene_std::Color;
|
||||||
use graphene_std::renderer::Quad;
|
use graphene_std::renderer::Quad;
|
||||||
use graphene_std::vector::misc::ArcType;
|
use graphene_std::vector::misc::{ArcType, GridType};
|
||||||
use std::vec;
|
use std::vec;
|
||||||
|
|
||||||
#[derive(Default, ExtractField)]
|
#[derive(Default, ExtractField)]
|
||||||
|
|
@ -41,6 +39,7 @@ pub struct ShapeToolOptions {
|
||||||
vertices: u32,
|
vertices: u32,
|
||||||
shape_type: ShapeType,
|
shape_type: ShapeType,
|
||||||
arc_type: ArcType,
|
arc_type: ArcType,
|
||||||
|
grid_type: GridType,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ShapeToolOptions {
|
impl Default for ShapeToolOptions {
|
||||||
|
|
@ -52,6 +51,7 @@ impl Default for ShapeToolOptions {
|
||||||
vertices: 5,
|
vertices: 5,
|
||||||
shape_type: ShapeType::Polygon,
|
shape_type: ShapeType::Polygon,
|
||||||
arc_type: ArcType::Open,
|
arc_type: ArcType::Open,
|
||||||
|
grid_type: GridType::Rectangular,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -67,6 +67,7 @@ pub enum ShapeOptionsUpdate {
|
||||||
Vertices(u32),
|
Vertices(u32),
|
||||||
ShapeType(ShapeType),
|
ShapeType(ShapeType),
|
||||||
ArcType(ArcType),
|
ArcType(ArcType),
|
||||||
|
GridType(GridType),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[impl_message(Message, ToolMessage, Shape)]
|
#[impl_message(Message, ToolMessage, Shape)]
|
||||||
|
|
@ -134,6 +135,12 @@ fn create_shape_option_widget(shape_type: ShapeType) -> WidgetHolder {
|
||||||
}
|
}
|
||||||
.into()
|
.into()
|
||||||
}),
|
}),
|
||||||
|
MenuListEntry::new("Grid").label("Grid").on_commit(move |_| {
|
||||||
|
ShapeToolMessage::UpdateOptions {
|
||||||
|
options: ShapeOptionsUpdate::ShapeType(ShapeType::Grid),
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
}),
|
||||||
]];
|
]];
|
||||||
DropdownInput::new(entries).selected_index(Some(shape_type as u32)).widget_holder()
|
DropdownInput::new(entries).selected_index(Some(shape_type as u32)).widget_holder()
|
||||||
}
|
}
|
||||||
|
|
@ -177,6 +184,24 @@ fn create_weight_widget(line_weight: f64) -> WidgetHolder {
|
||||||
.widget_holder()
|
.widget_holder()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn create_grid_type_widget(grid_type: GridType) -> WidgetHolder {
|
||||||
|
let entries = vec![
|
||||||
|
RadioEntryData::new("Rectangular").label("Rectangular").on_update(move |_| {
|
||||||
|
ShapeToolMessage::UpdateOptions {
|
||||||
|
options: ShapeOptionsUpdate::GridType(GridType::Rectangular),
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
}),
|
||||||
|
RadioEntryData::new("Isometric").label("Isometric").on_update(move |_| {
|
||||||
|
ShapeToolMessage::UpdateOptions {
|
||||||
|
options: ShapeOptionsUpdate::GridType(GridType::Isometric),
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
RadioInput::new(entries).selected_index(Some(grid_type as u32)).widget_holder()
|
||||||
|
}
|
||||||
|
|
||||||
impl LayoutHolder for ShapeTool {
|
impl LayoutHolder for ShapeTool {
|
||||||
fn layout(&self) -> Layout {
|
fn layout(&self) -> Layout {
|
||||||
let mut widgets = vec![];
|
let mut widgets = vec![];
|
||||||
|
|
@ -196,6 +221,11 @@ impl LayoutHolder for ShapeTool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.options.shape_type == ShapeType::Grid {
|
||||||
|
widgets.push(create_grid_type_widget(self.options.grid_type));
|
||||||
|
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
|
||||||
|
}
|
||||||
|
|
||||||
if self.options.shape_type != ShapeType::Line {
|
if self.options.shape_type != ShapeType::Line {
|
||||||
widgets.append(&mut self.options.fill.create_widgets(
|
widgets.append(&mut self.options.fill.create_widgets(
|
||||||
"Fill",
|
"Fill",
|
||||||
|
|
@ -297,6 +327,9 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for Shap
|
||||||
ShapeOptionsUpdate::ArcType(arc_type) => {
|
ShapeOptionsUpdate::ArcType(arc_type) => {
|
||||||
self.options.arc_type = arc_type;
|
self.options.arc_type = arc_type;
|
||||||
}
|
}
|
||||||
|
ShapeOptionsUpdate::GridType(grid_type) => {
|
||||||
|
self.options.grid_type = grid_type;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
update_dynamic_hints(&self.fsm_state, responses, &self.tool_data);
|
update_dynamic_hints(&self.fsm_state, responses, &self.tool_data);
|
||||||
|
|
@ -527,10 +560,11 @@ impl Fsm for ShapeToolFsmState {
|
||||||
if is_skewing || (dragging_bounds && is_near_square && !hovering_over_gizmo) {
|
if is_skewing || (dragging_bounds && is_near_square && !hovering_over_gizmo) {
|
||||||
bounds.render_skew_gizmos(&mut overlay_context, tool_data.skew_edge);
|
bounds.render_skew_gizmos(&mut overlay_context, tool_data.skew_edge);
|
||||||
}
|
}
|
||||||
if !is_skewing && dragging_bounds && !hovering_over_gizmo {
|
if dragging_bounds
|
||||||
if let Some(edges) = edges {
|
&& !is_skewing && !hovering_over_gizmo
|
||||||
tool_data.skew_edge = bounds.get_closest_edge(edges, input.mouse.position);
|
&& let Some(edges) = edges
|
||||||
}
|
{
|
||||||
|
tool_data.skew_edge = bounds.get_closest_edge(edges, input.mouse.position);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -595,64 +629,12 @@ impl Fsm for ShapeToolFsmState {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
(ShapeToolFsmState::Drawing(_), ShapeToolMessage::IncreaseSides) => {
|
(ShapeToolFsmState::Drawing(_), ShapeToolMessage::IncreaseSides) => {
|
||||||
if let Some(layer) = tool_data.data.layer {
|
Polygon::increase_decrease_sides(true, document, tool_data, responses);
|
||||||
let Some(node_id) = graph_modification_utils::get_polygon_id(layer, &document.network_interface).or(graph_modification_utils::get_star_id(layer, &document.network_interface))
|
|
||||||
else {
|
|
||||||
return self;
|
|
||||||
};
|
|
||||||
|
|
||||||
let Some(node_inputs) = NodeGraphLayer::new(layer, &document.network_interface)
|
|
||||||
.find_node_inputs("Regular Polygon")
|
|
||||||
.or(NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs("Star"))
|
|
||||||
else {
|
|
||||||
return self;
|
|
||||||
};
|
|
||||||
|
|
||||||
let Some(&TaggedValue::U32(n)) = node_inputs.get(1).unwrap().as_value() else {
|
|
||||||
return self;
|
|
||||||
};
|
|
||||||
|
|
||||||
responses.add(ShapeToolMessage::UpdateOptions {
|
|
||||||
options: ShapeOptionsUpdate::Vertices(n + 1),
|
|
||||||
});
|
|
||||||
|
|
||||||
responses.add(NodeGraphMessage::SetInput {
|
|
||||||
input_connector: InputConnector::node(node_id, 1),
|
|
||||||
input: NodeInput::value(TaggedValue::U32(n + 1), false),
|
|
||||||
});
|
|
||||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
|
||||||
}
|
|
||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
(ShapeToolFsmState::Drawing(_), ShapeToolMessage::DecreaseSides) => {
|
(ShapeToolFsmState::Drawing(_), ShapeToolMessage::DecreaseSides) => {
|
||||||
if let Some(layer) = tool_data.data.layer {
|
Polygon::increase_decrease_sides(false, document, tool_data, responses);
|
||||||
let Some(node_id) = graph_modification_utils::get_polygon_id(layer, &document.network_interface).or(graph_modification_utils::get_star_id(layer, &document.network_interface))
|
|
||||||
else {
|
|
||||||
return self;
|
|
||||||
};
|
|
||||||
|
|
||||||
let Some(node_inputs) = NodeGraphLayer::new(layer, &document.network_interface)
|
|
||||||
.find_node_inputs("Regular Polygon")
|
|
||||||
.or(NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs("Star"))
|
|
||||||
else {
|
|
||||||
return self;
|
|
||||||
};
|
|
||||||
|
|
||||||
let Some(&TaggedValue::U32(n)) = node_inputs.get(1).unwrap().as_value() else {
|
|
||||||
return self;
|
|
||||||
};
|
|
||||||
|
|
||||||
responses.add(ShapeToolMessage::UpdateOptions {
|
|
||||||
options: ShapeOptionsUpdate::Vertices((n - 1).max(3)),
|
|
||||||
});
|
|
||||||
|
|
||||||
responses.add(NodeGraphMessage::SetInput {
|
|
||||||
input_connector: InputConnector::node(node_id, 1),
|
|
||||||
input: NodeInput::value(TaggedValue::U32((n - 1).max(3)), false),
|
|
||||||
});
|
|
||||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
|
||||||
}
|
|
||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
@ -681,6 +663,8 @@ impl Fsm for ShapeToolFsmState {
|
||||||
modifier: ShapeToolData::shape_tool_modifier_keys(),
|
modifier: ShapeToolData::shape_tool_modifier_keys(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
responses.add(DocumentMessage::StartTransaction);
|
||||||
|
|
||||||
return ShapeToolFsmState::ModifyingGizmo;
|
return ShapeToolFsmState::ModifyingGizmo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -692,10 +676,10 @@ impl Fsm for ShapeToolFsmState {
|
||||||
document.network_interface.selected_nodes().selected_visible_and_unlocked_layers(&document.network_interface),
|
document.network_interface.selected_nodes().selected_visible_and_unlocked_layers(&document.network_interface),
|
||||||
|_| false,
|
|_| false,
|
||||||
preferences,
|
preferences,
|
||||||
) {
|
) && clicked_on_line_endpoints(layer, document, input, tool_data)
|
||||||
if clicked_on_line_endpoints(layer, document, input, tool_data) && !input.keyboard.key(Key::Control) {
|
&& !input.keyboard.key(Key::Control)
|
||||||
return ShapeToolFsmState::DraggingLineEndpoints;
|
{
|
||||||
}
|
return ShapeToolFsmState::DraggingLineEndpoints;
|
||||||
}
|
}
|
||||||
|
|
||||||
let (resize, rotate, skew) = transforming_transform_cage(document, &mut tool_data.bounding_box_manager, input, responses, &mut tool_data.layers_dragging, None);
|
let (resize, rotate, skew) = transforming_transform_cage(document, &mut tool_data.bounding_box_manager, input, responses, &mut tool_data.layers_dragging, None);
|
||||||
|
|
@ -735,7 +719,7 @@ impl Fsm for ShapeToolFsmState {
|
||||||
};
|
};
|
||||||
|
|
||||||
match tool_data.current_shape {
|
match tool_data.current_shape {
|
||||||
ShapeType::Polygon | ShapeType::Star | ShapeType::Circle | ShapeType::Arc | ShapeType::Rectangle | ShapeType::Ellipse => tool_data.data.start(document, input),
|
ShapeType::Polygon | ShapeType::Star | ShapeType::Circle | ShapeType::Arc | ShapeType::Grid | 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());
|
||||||
|
|
@ -750,6 +734,7 @@ impl Fsm for ShapeToolFsmState {
|
||||||
ShapeType::Star => Star::create_node(tool_options.vertices),
|
ShapeType::Star => Star::create_node(tool_options.vertices),
|
||||||
ShapeType::Circle => Circle::create_node(),
|
ShapeType::Circle => Circle::create_node(),
|
||||||
ShapeType::Arc => Arc::create_node(tool_options.arc_type),
|
ShapeType::Arc => Arc::create_node(tool_options.arc_type),
|
||||||
|
ShapeType::Grid => Grid::create_node(tool_options.grid_type),
|
||||||
ShapeType::Rectangle => Rectangle::create_node(),
|
ShapeType::Rectangle => Rectangle::create_node(),
|
||||||
ShapeType::Ellipse => Ellipse::create_node(),
|
ShapeType::Ellipse => Ellipse::create_node(),
|
||||||
ShapeType::Line => Line::create_node(document, tool_data.data.drag_start),
|
ShapeType::Line => Line::create_node(document, tool_data.data.drag_start),
|
||||||
|
|
@ -761,7 +746,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::Polygon | ShapeType::Star | ShapeType::Circle | ShapeType::Arc | ShapeType::Rectangle | ShapeType::Ellipse => {
|
ShapeType::Polygon | ShapeType::Star | ShapeType::Circle | ShapeType::Arc | ShapeType::Grid | 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),
|
||||||
|
|
@ -798,6 +783,7 @@ impl Fsm for ShapeToolFsmState {
|
||||||
ShapeType::Star => Star::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::Circle => Circle::update_shape(document, input, layer, tool_data, modifier, responses),
|
||||||
ShapeType::Arc => Arc::update_shape(document, input, layer, tool_data, modifier, responses),
|
ShapeType::Arc => Arc::update_shape(document, input, layer, tool_data, modifier, responses),
|
||||||
|
ShapeType::Grid => Grid::update_shape(document, input, layer, tool_options.grid_type, 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),
|
||||||
|
|
@ -905,11 +891,11 @@ impl Fsm for ShapeToolFsmState {
|
||||||
}
|
}
|
||||||
(ShapeToolFsmState::ResizingBounds | ShapeToolFsmState::SkewingBounds { .. }, ShapeToolMessage::PointerOutsideViewport { .. }) => {
|
(ShapeToolFsmState::ResizingBounds | ShapeToolFsmState::SkewingBounds { .. }, ShapeToolMessage::PointerOutsideViewport { .. }) => {
|
||||||
// Auto-panning
|
// Auto-panning
|
||||||
if let Some(shift) = tool_data.auto_panning.shift_viewport(input, responses) {
|
if let Some(shift) = tool_data.auto_panning.shift_viewport(input, responses)
|
||||||
if let Some(bounds) = &mut tool_data.bounding_box_manager {
|
&& let Some(bounds) = &mut tool_data.bounding_box_manager
|
||||||
bounds.center_of_transformation += shift;
|
{
|
||||||
bounds.original_bound_transform.translation += shift;
|
bounds.center_of_transformation += shift;
|
||||||
}
|
bounds.original_bound_transform.translation += shift;
|
||||||
}
|
}
|
||||||
|
|
||||||
self
|
self
|
||||||
|
|
@ -1039,6 +1025,11 @@ fn update_dynamic_hints(state: &ShapeToolFsmState, responses: &mut VecDeque<Mess
|
||||||
HintInfo::keys([Key::Shift], "Constrain Arc").prepend_plus(),
|
HintInfo::keys([Key::Shift], "Constrain Arc").prepend_plus(),
|
||||||
HintInfo::keys([Key::Alt], "From Center").prepend_plus(),
|
HintInfo::keys([Key::Alt], "From Center").prepend_plus(),
|
||||||
])],
|
])],
|
||||||
|
ShapeType::Grid => vec![HintGroup(vec![
|
||||||
|
HintInfo::mouse(MouseMotion::LmbDrag, "Draw Grid"),
|
||||||
|
HintInfo::keys([Key::Shift], "Constrain Regular").prepend_plus(),
|
||||||
|
HintInfo::keys([Key::Alt], "From Center").prepend_plus(),
|
||||||
|
])],
|
||||||
};
|
};
|
||||||
HintData(hint_groups)
|
HintData(hint_groups)
|
||||||
}
|
}
|
||||||
|
|
@ -1048,6 +1039,7 @@ fn update_dynamic_hints(state: &ShapeToolFsmState, responses: &mut VecDeque<Mess
|
||||||
ShapeType::Polygon | ShapeType::Star | ShapeType::Arc => HintGroup(vec![HintInfo::keys([Key::Shift], "Constrain Regular"), HintInfo::keys([Key::Alt], "From Center")]),
|
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::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::Ellipse => HintGroup(vec![HintInfo::keys([Key::Shift], "Constrain Circular"), HintInfo::keys([Key::Alt], "From Center")]),
|
||||||
|
ShapeType::Grid => HintGroup(vec![HintInfo::keys([Key::Shift], "Constrain Regular"), HintInfo::keys([Key::Alt], "From Center")]),
|
||||||
ShapeType::Line => HintGroup(vec![
|
ShapeType::Line => HintGroup(vec![
|
||||||
HintInfo::keys([Key::Shift], "15° Increments"),
|
HintInfo::keys([Key::Shift], "15° Increments"),
|
||||||
HintInfo::keys([Key::Alt], "From Center"),
|
HintInfo::keys([Key::Alt], "From Center"),
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,7 @@ impl AsI64 for f64 {
|
||||||
#[widget(Radio)]
|
#[widget(Radio)]
|
||||||
pub enum GridType {
|
pub enum GridType {
|
||||||
#[default]
|
#[default]
|
||||||
Rectangular,
|
Rectangular = 0,
|
||||||
Isometric,
|
Isometric,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -102,6 +102,10 @@ pub fn dvec2_to_point(value: DVec2) -> Point {
|
||||||
Point { x: value.x, y: value.y }
|
Point { x: value.x, y: value.y }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_line_endpoints(line: Line) -> (DVec2, DVec2) {
|
||||||
|
(point_to_dvec2(line.p0), point_to_dvec2(line.p1))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn segment_to_handles(segment: &PathSeg) -> BezierHandles {
|
pub fn segment_to_handles(segment: &PathSeg) -> BezierHandles {
|
||||||
match *segment {
|
match *segment {
|
||||||
PathSeg::Line(_) => BezierHandles::Linear,
|
PathSeg::Line(_) => BezierHandles::Linear,
|
||||||
|
|
|
||||||
|
|
@ -128,7 +128,10 @@ impl PointDomain {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn push(&mut self, id: PointId, position: DVec2) {
|
pub fn push(&mut self, id: PointId, position: DVec2) {
|
||||||
debug_assert!(!self.id.contains(&id));
|
if self.id.contains(&id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
self.id.push(id);
|
self.id.push(id);
|
||||||
self.position.push(position);
|
self.position.push(position);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue