Skip to content
Closed
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
102 changes: 102 additions & 0 deletions editor/src/messages/tool/common_functionality/shapes/arrow_shape.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
use super::shape_utility::ShapeToolModifierKey;
use super::*;
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::NodeTemplate;
use crate::messages::prelude::*;
use glam::DVec2;
use graphene_std::vector::{PointId, SegmentId, VectorModificationType};
use std::collections::VecDeque;

#[derive(Default)]
pub struct Arrow;

impl Arrow {
pub fn create_node() -> NodeTemplate {
let node_type = resolve_document_node_type("Path").expect("Path node does not exist");
node_type.default_node_template()
}

pub fn update_shape(
document: &DocumentMessageHandler,
input: &InputPreprocessorMessageHandler,
layer: LayerNodeIdentifier,
tool_data: &mut ShapeToolData,
modifier: ShapeToolModifierKey,
responses: &mut VecDeque<Message>,
) {
let [center, lock_ratio, _] = modifier;

// Work in viewport space like Line does
let start_viewport = tool_data.data.viewport_drag_start(document);
let end_viewport = input.mouse.position;

let delta = end_viewport - start_viewport;
let length = delta.length();
if length < 1e-6 {
return;
}

let direction = delta.normalize();
let perpendicular = DVec2::new(-direction.y, direction.x);

let shaft_thickness = length * 0.05;
let head_width = length * 0.15;
let head_length = length * 0.2;

// Build arrow in viewport space
let viewport_anchors = vec![
start_viewport,
start_viewport + direction * head_length - perpendicular * (head_width * 0.5),
start_viewport + direction * head_length - perpendicular * (shaft_thickness * 0.5),
end_viewport - perpendicular * (shaft_thickness * 0.5),
end_viewport + perpendicular * (shaft_thickness * 0.5),
start_viewport + direction * head_length + perpendicular * (shaft_thickness * 0.5),
start_viewport + direction * head_length + perpendicular * (head_width * 0.5),
];

let vector = document.network_interface.compute_modified_vector(layer);
let existing_point_ids: Vec<PointId> = vector.as_ref().map(|v| v.point_domain.ids().to_vec()).unwrap_or_default();
let existing_segment_ids: Vec<SegmentId> = vector.as_ref().map(|v| v.segment_domain.ids().to_vec()).unwrap_or_default();

for point_id in existing_point_ids {
responses.add(GraphOperationMessage::Vector {
layer,
modification_type: VectorModificationType::RemovePoint { id: point_id },
});
}

for segment_id in existing_segment_ids {
responses.add(GraphOperationMessage::Vector {
layer,
modification_type: VectorModificationType::RemoveSegment { id: segment_id },
});
}

let point_ids: Vec<PointId> = viewport_anchors
.iter()
.map(|&pos| {
let id = PointId::generate();
responses.add(GraphOperationMessage::Vector {
layer,
modification_type: VectorModificationType::InsertPoint { id, position: pos },
});
id
})
.collect();

for i in 0..point_ids.len() {
let id = SegmentId::generate();
let points = [point_ids[i], point_ids[(i + 1) % point_ids.len()]];
responses.add(GraphOperationMessage::Vector {
layer,
modification_type: VectorModificationType::InsertSegment { id, points, handles: [None, None] },
});
}

responses.add(NodeGraphMessage::RunDocumentGraph);
}

pub fn overlays(_document: &DocumentMessageHandler, _tool_data: &ShapeToolData, _overlay_context: &mut OverlayContext) {}
}
2 changes: 2 additions & 0 deletions editor/src/messages/tool/common_functionality/shapes/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub mod arc_shape;
pub mod arrow_shape;
pub mod circle_shape;
pub mod ellipse_shape;
pub mod grid_shape;
Expand All @@ -9,6 +10,7 @@ pub mod shape_utility;
pub mod spiral_shape;
pub mod star_shape;

pub use super::shapes::arrow_shape::Arrow;
pub use super::shapes::ellipse_shape::Ellipse;
pub use super::shapes::line_shape::{Line, LineEnd};
pub use super::shapes::rectangle_shape::Rectangle;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ pub enum ShapeType {
Grid,
Rectangle,
Ellipse,
Arrow,
Line,
}

Expand All @@ -47,6 +48,7 @@ impl ShapeType {
Self::Spiral => "Spiral",
Self::Rectangle => "Rectangle",
Self::Ellipse => "Ellipse",
Self::Arrow => "Arrow",
Self::Line => "Line",
})
.into()
Expand All @@ -57,6 +59,7 @@ impl ShapeType {
Self::Line => "Line Tool",
Self::Rectangle => "Rectangle Tool",
Self::Ellipse => "Ellipse Tool",
Self::Arrow => "Arrow Tool",
_ => "",
})
.into()
Expand All @@ -67,6 +70,7 @@ impl ShapeType {
Self::Line => "VectorLineTool",
Self::Rectangle => "VectorRectangleTool",
Self::Ellipse => "VectorEllipseTool",
Self::Arrow => "VectorArrowTool",
_ => "",
})
.into()
Expand All @@ -77,6 +81,7 @@ impl ShapeType {
Self::Line => ToolType::Line,
Self::Rectangle => ToolType::Rectangle,
Self::Ellipse => ToolType::Ellipse,
Self::Arrow => ToolType::Shape,
_ => ToolType::Shape,
}
}
Expand Down
38 changes: 35 additions & 3 deletions editor/src/messages/tool/tool_messages/pen_tool.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use super::tool_prelude::*;
use crate::consts::{COLOR_OVERLAY_BLUE, DEFAULT_STROKE_WIDTH, HIDE_HANDLE_DISTANCE, LINE_ROTATE_SNAP_ANGLE, SEGMENT_OVERLAY_SIZE};
use crate::consts::{COLOR_OVERLAY_BLUE, DEFAULT_STROKE_WIDTH, DOUBLE_CLICK_MILLISECONDS, DRAG_THRESHOLD, HIDE_HANDLE_DISTANCE, LINE_ROTATE_SNAP_ANGLE, SEGMENT_OVERLAY_SIZE};
use crate::messages::input_mapper::utility_types::input_mouse::MouseKeys;
use crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type;
use crate::messages::portfolio::document::overlays::utility_functions::path_overlays;
Expand Down Expand Up @@ -410,6 +410,9 @@ struct PenToolData {
/// and Ctrl is pressed near the anchor to make it colinear with its opposite handle.
angle_locked: bool,
path_closed: bool,
last_click_time: Option<u64>,
last_click_pos: Option<DVec2>,
pending_double_click_confirm: bool,

handle_mode: HandleMode,
prior_segment_layer: Option<LayerNodeIdentifier>,
Expand Down Expand Up @@ -446,6 +449,18 @@ impl PenToolData {
self.latest_points.clear();
self.point_index = 0;
self.snap_manager.cleanup(responses);
self.pending_double_click_confirm = false;
self.last_click_time = None;
self.last_click_pos = None;
}

fn update_click_timing(&mut self, time: u64, position: DVec2) -> bool {
let within_time = self.last_click_time.map(|last_time| time.saturating_sub(last_time) <= DOUBLE_CLICK_MILLISECONDS).unwrap_or(false);
let within_distance = self.last_click_pos.map(|last_pos| last_pos.distance(position) <= DRAG_THRESHOLD).unwrap_or(false);
let is_double_click = within_time && within_distance;
self.last_click_time = Some(time);
self.last_click_pos = Some(position);
is_double_click
}

/// Check whether target handle is primary, end, or `self.handle_end`
Expand Down Expand Up @@ -1816,6 +1831,8 @@ impl Fsm for PenToolFsmState {
self
}
(PenToolFsmState::Ready, PenToolMessage::DragStart { append_to_selected }) => {
tool_data.pending_double_click_confirm = false;
let _ = tool_data.update_click_timing(input.time, input.mouse.position);
responses.add(DocumentMessage::StartTransaction);
tool_data.handle_mode = HandleMode::Free;

Expand All @@ -1838,6 +1855,14 @@ impl Fsm for PenToolFsmState {
state
}
(PenToolFsmState::PlacingAnchor, PenToolMessage::DragStart { append_to_selected }) => {
let double_click = if tool_data.buffering_merged_vector {
false
} else {
tool_data.update_click_timing(input.time, input.mouse.position)
};
if !tool_data.buffering_merged_vector {
tool_data.pending_double_click_confirm = double_click;
}
let point = SnapCandidatePoint::handle(document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position));
let snapped = tool_data.snap_manager.free_snap(&SnapData::new(document, input), &point, SnapTypeConfiguration::default());
let viewport = document.metadata().document_to_viewport.transform_point2(snapped.snapped_point_document);
Expand Down Expand Up @@ -1892,9 +1917,16 @@ impl Fsm for PenToolFsmState {
}
(PenToolFsmState::DraggingHandle(_), PenToolMessage::DragStop) => {
tool_data.cleanup_target_selections(shape_editor, layer, document, responses);
tool_data
let next_state = tool_data
.finish_placing_handle(SnapData::new(document, input), transform, preferences, responses)
.unwrap_or(PenToolFsmState::PlacingAnchor)
.unwrap_or(PenToolFsmState::PlacingAnchor);
if tool_data.pending_double_click_confirm && matches!(next_state, PenToolFsmState::PlacingAnchor) {
tool_data.pending_double_click_confirm = false;
responses.add(PenToolMessage::Confirm);
} else {
tool_data.pending_double_click_confirm = false;
}
next_state
}
(
PenToolFsmState::DraggingHandle(_),
Expand Down
33 changes: 31 additions & 2 deletions editor/src/messages/tool/tool_messages/shape_tool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use crate::messages::tool::common_functionality::gizmos::gizmo_manager::GizmoMan
use crate::messages::tool::common_functionality::graph_modification_utils;
use crate::messages::tool::common_functionality::resize::Resize;
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::line_shape::{LineToolData, clicked_on_line_endpoints};
Expand Down Expand Up @@ -168,6 +169,30 @@ fn create_shape_option_widget(shape_type: ShapeType) -> WidgetHolder {
}
.into()
}),
MenuListEntry::new("Rectangle").label("Rectangle").on_commit(move |_| {
ShapeToolMessage::UpdateOptions {
options: ShapeOptionsUpdate::ShapeType(ShapeType::Rectangle),
}
.into()
}),
MenuListEntry::new("Ellipse").label("Ellipse").on_commit(move |_| {
ShapeToolMessage::UpdateOptions {
options: ShapeOptionsUpdate::ShapeType(ShapeType::Ellipse),
}
.into()
}),
MenuListEntry::new("Arrow").label("Arrow").on_commit(move |_| {
ShapeToolMessage::UpdateOptions {
options: ShapeOptionsUpdate::ShapeType(ShapeType::Arrow),
}
.into()
}),
MenuListEntry::new("Line").label("Line").on_commit(move |_| {
ShapeToolMessage::UpdateOptions {
options: ShapeOptionsUpdate::ShapeType(ShapeType::Line),
}
.into()
}),
]];
DropdownInput::new(entries).selected_index(Some(shape_type as u32)).widget_holder()
}
Expand Down Expand Up @@ -805,7 +830,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::Arrow => {
tool_data.data.start(document, input)
}
ShapeType::Line => {
Expand All @@ -822,6 +847,7 @@ impl Fsm for ShapeToolFsmState {
ShapeType::Star => Star::create_node(tool_options.vertices),
ShapeType::Circle => Circle::create_node(),
ShapeType::Arc => Arc::create_node(tool_options.arc_type),
ShapeType::Arrow => Arrow::create_node(),
ShapeType::Spiral => Spiral::create_node(tool_options.spiral_type, tool_options.turns),
ShapeType::Grid => Grid::create_node(tool_options.grid_type),
ShapeType::Rectangle => Rectangle::create_node(),
Expand All @@ -835,7 +861,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::Arrow | ShapeType::Polygon | ShapeType::Star | ShapeType::Circle | ShapeType::Arc | ShapeType::Spiral | ShapeType::Grid | ShapeType::Rectangle | ShapeType::Ellipse => {
defered_responses.add(GraphOperationMessage::TransformSet {
layer,
transform: DAffine2::from_scale_angle_translation(DVec2::ONE, 0., input.mouse.position),
Expand Down Expand Up @@ -872,6 +898,7 @@ impl Fsm for ShapeToolFsmState {
ShapeType::Star => Star::update_shape(document, input, layer, tool_data, modifier, responses),
ShapeType::Circle => Circle::update_shape(document, input, layer, tool_data, modifier, responses),
ShapeType::Arc => Arc::update_shape(document, input, layer, tool_data, modifier, responses),
ShapeType::Arrow => Arrow::update_shape(document, input, layer, tool_data, modifier, responses),
ShapeType::Spiral => Spiral::update_shape(document, input, layer, tool_data, 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),
Expand Down Expand Up @@ -1127,6 +1154,7 @@ fn update_dynamic_hints(state: &ShapeToolFsmState, responses: &mut VecDeque<Mess
HintInfo::keys([Key::Shift], "Constrain Regular").prepend_plus(),
HintInfo::keys([Key::Alt], "From Center").prepend_plus(),
])],
ShapeType::Arrow => vec![HintGroup(vec![HintInfo::mouse(MouseMotion::LmbDrag, "Draw Arrow")])],
};
HintData(hint_groups)
}
Expand All @@ -1142,6 +1170,7 @@ fn update_dynamic_hints(state: &ShapeToolFsmState, responses: &mut VecDeque<Mess
HintInfo::keys([Key::Alt], "From Center"),
HintInfo::keys([Key::Control], "Lock Angle"),
]),
ShapeType::Arrow => HintGroup(vec![HintInfo::keys([Key::Shift], "Constrain Angle")]),
ShapeType::Circle => HintGroup(vec![HintInfo::keys([Key::Alt], "From Center")]),
ShapeType::Spiral => HintGroup(vec![]),
};
Expand Down