Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ 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::circle_shape::CircleGizmoHandler;
use crate::messages::tool::common_functionality::shapes::grid_shape::GridGizmoHandler;
use crate::messages::tool::common_functionality::shapes::heart_shape::HeartGizmoHandler;
use crate::messages::tool::common_functionality::shapes::polygon_shape::PolygonGizmoHandler;
use crate::messages::tool::common_functionality::shapes::shape_utility::ShapeGizmoHandler;
use crate::messages::tool::common_functionality::shapes::spiral_shape::SpiralGizmoHandler;
Expand All @@ -32,6 +33,7 @@ pub enum ShapeGizmoHandlers {
Circle(CircleGizmoHandler),
Grid(GridGizmoHandler),
Spiral(SpiralGizmoHandler),
Heart(HeartGizmoHandler),
}

impl ShapeGizmoHandlers {
Expand All @@ -45,6 +47,7 @@ impl ShapeGizmoHandlers {
Self::Circle(_) => "circle",
Self::Grid(_) => "grid",
Self::Spiral(_) => "spiral",
Self::Heart(_) => "heart",
Self::None => "none",
}
}
Expand All @@ -58,6 +61,7 @@ impl ShapeGizmoHandlers {
Self::Circle(h) => h.handle_state(layer, mouse_position, document, responses),
Self::Grid(h) => h.handle_state(layer, mouse_position, document, responses),
Self::Spiral(h) => h.handle_state(layer, mouse_position, document, responses),
Self::Heart(h) => h.handle_state(layer, mouse_position, document, responses),
Self::None => {}
}
}
Expand All @@ -71,6 +75,7 @@ impl ShapeGizmoHandlers {
Self::Circle(h) => h.is_any_gizmo_hovered(),
Self::Grid(h) => h.is_any_gizmo_hovered(),
Self::Spiral(h) => h.is_any_gizmo_hovered(),
Self::Heart(h) => h.is_any_gizmo_hovered(),
Self::None => false,
}
}
Expand All @@ -84,6 +89,7 @@ impl ShapeGizmoHandlers {
Self::Circle(h) => h.handle_click(),
Self::Grid(h) => h.handle_click(),
Self::Spiral(h) => h.handle_click(),
Self::Heart(h) => h.handle_click(),
Self::None => {}
}
}
Expand All @@ -97,6 +103,7 @@ impl ShapeGizmoHandlers {
Self::Circle(h) => h.handle_update(drag_start, document, input, responses),
Self::Grid(h) => h.handle_update(drag_start, document, input, responses),
Self::Spiral(h) => h.handle_update(drag_start, document, input, responses),
Self::Heart(h) => h.handle_update(drag_start, document, input, responses),
Self::None => {}
}
}
Expand All @@ -110,6 +117,7 @@ impl ShapeGizmoHandlers {
Self::Circle(h) => h.cleanup(),
Self::Grid(h) => h.cleanup(),
Self::Spiral(h) => h.cleanup(),
Self::Heart(h) => h.cleanup(),
Self::None => {}
}
}
Expand All @@ -131,6 +139,7 @@ impl ShapeGizmoHandlers {
Self::Circle(h) => h.overlays(document, layer, input, shape_editor, mouse_position, overlay_context),
Self::Grid(h) => h.overlays(document, layer, input, shape_editor, mouse_position, overlay_context),
Self::Spiral(h) => h.overlays(document, layer, input, shape_editor, mouse_position, overlay_context),
Self::Heart(h) => h.overlays(document, layer, input, shape_editor, mouse_position, overlay_context),
Self::None => {}
}
}
Expand All @@ -151,6 +160,7 @@ impl ShapeGizmoHandlers {
Self::Circle(h) => h.dragging_overlays(document, input, shape_editor, mouse_position, overlay_context),
Self::Grid(h) => h.dragging_overlays(document, input, shape_editor, mouse_position, overlay_context),
Self::Spiral(h) => h.dragging_overlays(document, input, shape_editor, mouse_position, overlay_context),
Self::Heart(h) => h.dragging_overlays(document, input, shape_editor, mouse_position, overlay_context),
Self::None => {}
}
}
Expand All @@ -163,6 +173,7 @@ impl ShapeGizmoHandlers {
Self::Circle(h) => h.mouse_cursor_icon(),
Self::Grid(h) => h.mouse_cursor_icon(),
Self::Spiral(h) => h.mouse_cursor_icon(),
Self::Heart(h) => h.mouse_cursor_icon(),
Self::None => None,
}
}
Expand Down Expand Up @@ -214,6 +225,10 @@ impl GizmoManager {
if graph_modification_utils::get_spiral_id(layer, &document.network_interface).is_some() {
return Some(ShapeGizmoHandlers::Spiral(SpiralGizmoHandler::default()));
}
// Heart
if graph_modification_utils::get_heart_id(layer, &document.network_interface).is_some() {
return Some(ShapeGizmoHandlers::Heart(HeartGizmoHandler::default()));
}

None
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,10 @@ pub fn get_text_id(layer: LayerNodeIdentifier, network_interface: &NodeNetworkIn
NodeGraphLayer::new(layer, network_interface).upstream_node_id_from_name(&DefinitionIdentifier::ProtoNode(graphene_std::text::text::IDENTIFIER))
}

pub fn get_heart_id(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<NodeId> {
NodeGraphLayer::new(layer, network_interface).upstream_node_id_from_name(&DefinitionIdentifier::ProtoNode(graphene_std::vector::generator_nodes::heart::IDENTIFIER))
}

pub fn get_grid_id(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<NodeId> {
NodeGraphLayer::new(layer, network_interface).upstream_node_id_from_name(&DefinitionIdentifier::ProtoNode(graphene_std::vector::generator_nodes::grid::IDENTIFIER))
}
Expand Down
113 changes: 113 additions & 0 deletions editor/src/messages/tool/common_functionality/shapes/heart_shape.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
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::node_graph::document_node_definitions::resolve_proto_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::prelude::{DocumentMessageHandler, InputPreprocessorMessageHandler};
use crate::messages::tool::common_functionality::graph_modification_utils;
use crate::messages::tool::common_functionality::shape_editor::ShapeState;
use crate::messages::tool::common_functionality::shapes::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;
use std::collections::VecDeque;

/// Placeholder gizmo handler for the Heart shape.
/// The heart's parametric controls (cleavage, lobes, shoulder, etc.) are adjusted via the Properties panel.
#[derive(Clone, Debug, Default)]
pub struct HeartGizmoHandler;

impl ShapeGizmoHandler for HeartGizmoHandler {
fn is_any_gizmo_hovered(&self) -> bool {
false
}

fn handle_state(&mut self, _layer: LayerNodeIdentifier, _mouse_position: DVec2, _document: &DocumentMessageHandler, _responses: &mut VecDeque<Message>) {}

fn handle_click(&mut self) {}

fn handle_update(&mut self, _drag_start: DVec2, _document: &DocumentMessageHandler, _input: &InputPreprocessorMessageHandler, _responses: &mut VecDeque<Message>) {}

fn overlays(
&self,
_document: &DocumentMessageHandler,
_selected_layer: Option<LayerNodeIdentifier>,
_input: &InputPreprocessorMessageHandler,
_shape_editor: &mut &mut ShapeState,
_mouse_position: DVec2,
_overlay_context: &mut OverlayContext,
) {
}

fn dragging_overlays(
&self,
_document: &DocumentMessageHandler,
_input: &InputPreprocessorMessageHandler,
_shape_editor: &mut &mut ShapeState,
_mouse_position: DVec2,
_overlay_context: &mut OverlayContext,
) {
}

fn cleanup(&mut self) {}

fn mouse_cursor_icon(&self) -> Option<MouseCursorIcon> {
None
}
}

#[derive(Default)]
pub struct Heart;

impl Heart {
pub fn create_node() -> NodeTemplate {
let node_type = resolve_proto_node_type(graphene_std::vector::generator_nodes::heart::IDENTIFIER).expect("Heart node 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,
viewport: &ViewportMessageHandler,
layer: LayerNodeIdentifier,
shape_tool_data: &mut ShapeToolData,
modifier: ShapeToolModifierKey,
responses: &mut VecDeque<Message>,
) {
let [center, lock_ratio, _] = modifier;

if let Some([start, end]) = shape_tool_data.data.calculate_points(document, ipp, viewport, center, lock_ratio) {
let Some(node_id) = graph_modification_utils::get_heart_id(layer, &document.network_interface) else {
return;
};

let dimensions = (start - end).abs();

let mut scale = DVec2::ONE;
let radius: f64;
if dimensions.x > dimensions.y {
scale.x = dimensions.x / dimensions.y;
radius = dimensions.y / 2.;
} else {
scale.y = dimensions.y / dimensions.x;
radius = dimensions.x / 2.;
}
Comment on lines +90 to +98
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Potential division by zero or NaN result if dimensions.x or dimensions.y is zero (e.g., if the user clicks without dragging). This can lead to invalid transforms and rendering issues. A check should be added to ensure dimensions are non-zero before calculating the scale.

			if dimensions.x < 1e-6 || dimensions.y < 1e-6 {
				return;
			}

			let mut scale = DVec2::ONE;
			let radius: f64;
			if dimensions.x > dimensions.y {
				scale.x = dimensions.x / dimensions.y;
				radius = dimensions.y / 2.;
			} else {
				scale.y = dimensions.y / dimensions.x;
				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(scale, 0., (start + end) / 2.),
transform_in: TransformIn::Viewport,
skip_rerender: false,
});
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ pub mod arrow_shape;
pub mod circle_shape;
pub mod ellipse_shape;
pub mod grid_shape;
pub mod heart_shape;
pub mod line_shape;
pub mod polygon_shape;
pub mod rectangle_shape;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ pub enum ShapeType {
Spiral,
Grid,
Arrow,
Heart,
Line, // KEEP THIS AT THE END
Rectangle, // KEEP THIS AT THE END
Ellipse, // KEEP THIS AT THE END
Expand All @@ -50,6 +51,7 @@ impl ShapeType {
ShapeType::Spiral,
ShapeType::Grid,
ShapeType::Arrow,
ShapeType::Heart,
ShapeType::Line, // KEEP THIS AT THE END
ShapeType::Rectangle, // KEEP THIS AT THE END
ShapeType::Ellipse, // KEEP THIS AT THE END
Expand All @@ -58,7 +60,10 @@ impl ShapeType {
/// True if this shape mode's fill checkbox is ticked by default when nothing is selected.
/// Spiral/Grid/Line are open paths and default to fill-off, the closed shapes default to fill-on.
pub fn defaults_to_fill(&self) -> bool {
matches!(self, Self::Polygon | Self::Star | Self::Circle | Self::Arc | Self::Rectangle | Self::Ellipse | Self::Arrow)
matches!(
self,
Self::Polygon | Self::Star | Self::Circle | Self::Arc | Self::Rectangle | Self::Ellipse | Self::Arrow | Self::Heart
)
}

pub fn name(&self) -> String {
Expand All @@ -70,6 +75,7 @@ impl ShapeType {
Self::Spiral => "Spiral",
Self::Grid => "Grid",
Self::Arrow => "Arrow",
Self::Heart => "Heart",
Self::Line => "Line", // KEEP THIS AT THE END
Self::Rectangle => "Rectangle", // KEEP THIS AT THE END
Self::Ellipse => "Ellipse", // KEEP THIS AT THE END
Expand Down
25 changes: 21 additions & 4 deletions editor/src/messages/tool/tool_messages/shape_tool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ use crate::messages::tool::common_functionality::shapes::arc_shape::Arc;
use crate::messages::tool::common_functionality::shapes::arrow_shape::Arrow;
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::heart_shape::Heart;
use crate::messages::tool::common_functionality::shapes::line_shape::LineToolData;
use crate::messages::tool::common_functionality::shapes::polygon_shape::Polygon;
use crate::messages::tool::common_functionality::shapes::shape_utility::{ShapeToolModifierKey, ShapeType, anchor_overlays, clicked_on_shape_endpoints, transform_cage_overlays};
Expand Down Expand Up @@ -212,6 +213,12 @@ fn create_shape_option_widget(shape_type: ShapeType) -> WidgetInstance {
}
.into()
}),
MenuListEntry::new("Heart").label("Heart").on_commit(move |_| {
ShapeToolMessage::UpdateOptions {
options: ShapeOptionsUpdate::ShapeType(ShapeType::Heart),
}
.into()
}),
]];
DropdownInput::new(entries).selected_index(Some(shape_type as u32)).widget_instance()
}
Expand Down Expand Up @@ -347,6 +354,7 @@ fn sync_shape_options_from_selection(options: &mut ShapeToolOptions, tool_data:
(spiral::IDENTIFIER, ShapeType::Spiral),
(grid::IDENTIFIER, ShapeType::Grid),
(arrow::IDENTIFIER, ShapeType::Arrow),
(heart::IDENTIFIER, ShapeType::Heart),
]
.into_iter()
.find_map(|(id, shape)| layer_view.upstream_node_id_from_name(&proto(id)).map(|_| shape)) else {
Expand Down Expand Up @@ -430,7 +438,7 @@ fn sync_shape_options_from_selection(options: &mut ShapeToolOptions, tool_data:
changed = true;
}
}
ShapeType::Ellipse | ShapeType::Rectangle | ShapeType::Line | ShapeType::Circle => {}
ShapeType::Ellipse | ShapeType::Rectangle | ShapeType::Line | ShapeType::Circle | ShapeType::Heart => {}
}

changed
Expand Down Expand Up @@ -1116,7 +1124,7 @@ impl Fsm for ShapeToolFsmState {
};

match tool_data.current_shape {
ShapeType::Polygon | ShapeType::Star | ShapeType::Circle | ShapeType::Arc | ShapeType::Spiral | ShapeType::Grid | ShapeType::Rectangle | ShapeType::Ellipse => {
ShapeType::Polygon | ShapeType::Star | ShapeType::Circle | ShapeType::Arc | ShapeType::Spiral | ShapeType::Grid | ShapeType::Rectangle | ShapeType::Ellipse | ShapeType::Heart => {
tool_data.data.start(document, input, viewport);
}
ShapeType::Arrow | ShapeType::Line => {
Expand All @@ -1139,6 +1147,7 @@ impl Fsm for ShapeToolFsmState {
ShapeType::Spiral => Spiral::create_node(tool_options.spiral_type, tool_options.turns),
ShapeType::Grid => Grid::create_node(tool_options.grid_type),
ShapeType::Arrow => Arrow::create_node(tool_options.arrow_shaft_width, tool_options.arrow_head_width, tool_options.arrow_head_length),
ShapeType::Heart => Heart::create_node(),
ShapeType::Line => Line::create_node(),
ShapeType::Rectangle => Rectangle::create_node(),
ShapeType::Ellipse => Ellipse::create_node(),
Expand All @@ -1150,7 +1159,7 @@ impl Fsm for ShapeToolFsmState {
let defered_responses = &mut VecDeque::new();

match tool_data.current_shape {
ShapeType::Polygon | ShapeType::Star | ShapeType::Circle | ShapeType::Arc | ShapeType::Spiral | ShapeType::Grid | ShapeType::Rectangle | ShapeType::Ellipse => {
ShapeType::Polygon | ShapeType::Star | ShapeType::Circle | ShapeType::Arc | ShapeType::Spiral | ShapeType::Grid | ShapeType::Rectangle | ShapeType::Ellipse | ShapeType::Heart => {
defered_responses.add(GraphOperationMessage::TransformSet {
layer,
transform: DAffine2::from_scale_angle_translation(DVec2::ONE, 0., input.mouse.position),
Expand Down Expand Up @@ -1212,6 +1221,7 @@ impl Fsm for ShapeToolFsmState {
ShapeType::Spiral => Spiral::update_shape(document, input, viewport, layer, tool_data, responses),
ShapeType::Grid => Grid::update_shape(document, input, layer, tool_options.grid_type, tool_data, modifier, responses),
ShapeType::Arrow => Arrow::update_shape(document, input, viewport, layer, tool_data, modifier, responses),
ShapeType::Heart => Heart::update_shape(document, input, viewport, layer, tool_data, modifier, responses),
ShapeType::Line => Line::update_shape(document, input, viewport, layer, tool_data, modifier, responses),
ShapeType::Rectangle => Rectangle::update_shape(document, input, viewport, layer, tool_data, modifier, responses),
ShapeType::Ellipse => Ellipse::update_shape(document, input, viewport, layer, tool_data, modifier, responses),
Expand Down Expand Up @@ -1480,13 +1490,20 @@ fn update_dynamic_hints(state: &ShapeToolFsmState, responses: &mut VecDeque<Mess
HintInfo::keys([Key::Shift], "Constrain Circular").prepend_plus(),
HintInfo::keys([Key::Alt], "From Center").prepend_plus(),
])],
ShapeType::Heart => vec![HintGroup(vec![
HintInfo::mouse(MouseMotion::LmbDrag, "Draw Heart"),
HintInfo::keys([Key::Shift], "Constrain Regular").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::Polygon | ShapeType::Star | ShapeType::Arc | ShapeType::Heart => {
HintGroup(vec![HintInfo::keys([Key::Shift], "Constrain Regular"), HintInfo::keys([Key::Alt], "From Center")])
}
ShapeType::Circle => HintGroup(vec![HintInfo::keys([Key::Alt], "From Center")]),
ShapeType::Spiral => HintGroup(vec![]),
ShapeType::Grid => HintGroup(vec![HintInfo::keys([Key::Shift], "Constrain Regular"), HintInfo::keys([Key::Alt], "From Center")]),
Expand Down
Loading
Loading