Skip to content

Commit 4044363

Browse files
committed
Guide-1: Core guide infrastructure + ruler drag creation + overlays + frontend
1 parent 7250b09 commit 4044363

File tree

15 files changed

+479
-5
lines changed

15 files changed

+479
-5
lines changed

editor/src/dispatcher.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,7 @@ impl Dispatcher {
253253
menu_bar_message_handler.canvas_tilted = document.document_ptz.tilt() != 0.;
254254
menu_bar_message_handler.canvas_flipped = document.document_ptz.flip;
255255
menu_bar_message_handler.rulers_visible = document.rulers_visible;
256+
menu_bar_message_handler.guides_visible = document.guide_handler.guides_visible;
256257
menu_bar_message_handler.node_graph_open = document.is_graph_overlay_open();
257258
menu_bar_message_handler.has_selected_nodes = selected_nodes.selected_nodes().next().is_some();
258259
menu_bar_message_handler.has_selected_layers = selected_nodes.selected_visible_layers(&document.network_interface).next().is_some();
@@ -263,6 +264,7 @@ impl Dispatcher {
263264
menu_bar_message_handler.canvas_tilted = false;
264265
menu_bar_message_handler.canvas_flipped = false;
265266
menu_bar_message_handler.rulers_visible = false;
267+
menu_bar_message_handler.guides_visible = false;
266268
menu_bar_message_handler.node_graph_open = false;
267269
menu_bar_message_handler.has_selected_nodes = false;
268270
menu_bar_message_handler.has_selected_layers = false;

editor/src/messages/menu_bar/menu_bar_message_handler.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use crate::messages::debug::utility_types::MessageLoggingVerbosity;
22
use crate::messages::input_mapper::utility_types::macros::action_shortcut;
33
use crate::messages::layout::utility_types::widget_prelude::*;
4+
use crate::messages::portfolio::document::guide_message::GuideMessage;
45
use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate, AlignAxis, FlipAxis, GroupFolderType};
56
use crate::messages::prelude::*;
67
use graphene_std::path_bool::BooleanOperation;
@@ -11,6 +12,7 @@ pub struct MenuBarMessageHandler {
1112
pub canvas_tilted: bool,
1213
pub canvas_flipped: bool,
1314
pub rulers_visible: bool,
15+
pub guides_visible: bool,
1416
pub node_graph_open: bool,
1517
pub has_selected_nodes: bool,
1618
pub has_selected_layers: bool,
@@ -616,6 +618,11 @@ impl LayoutHolder for MenuBarMessageHandler {
616618
.tooltip_shortcut(action_shortcut!(PortfolioMessageDiscriminant::ToggleRulers))
617619
.on_commit(|_| PortfolioMessage::ToggleRulers.into())
618620
.disabled(no_active_document),
621+
MenuListEntry::new("Guides")
622+
.label("Guides")
623+
.icon(if self.guides_visible { "CheckboxChecked" } else { "CheckboxUnchecked" })
624+
.on_commit(|_| GuideMessage::ToggleGuidesVisibility.into())
625+
.disabled(no_active_document),
619626
],
620627
])
621628
.widget_instance(),

editor/src/messages/portfolio/document/document_message.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use std::sync::Arc;
44
use super::utility_types::misc::{GroupFolderType, SnappingState};
55
use crate::messages::input_mapper::utility_types::input_keyboard::Key;
66
use crate::messages::portfolio::document::data_panel::DataPanelMessage;
7+
use crate::messages::portfolio::document::guide_message::GuideMessage;
78
use crate::messages::portfolio::document::overlays::utility_types::{OverlayContext, OverlaysType};
89
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
910
use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate, AlignAxis, FlipAxis, GridSnapping};
@@ -37,6 +38,8 @@ pub enum DocumentMessage {
3738
PropertiesPanel(PropertiesPanelMessage),
3839
#[child]
3940
DataPanel(DataPanelMessage),
41+
#[child]
42+
Guide(GuideMessage),
4043

4144
// Messages
4245
AlignSelectedLayers {

editor/src/messages/portfolio/document/document_message_handler.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use crate::messages::input_mapper::utility_types::macros::action_shortcut;
1010
use crate::messages::layout::utility_types::widget_prelude::*;
1111
use crate::messages::portfolio::document::data_panel::{DataPanelMessageContext, DataPanelMessageHandler};
1212
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;
13+
use crate::messages::portfolio::document::guide_message_handler::{GuideMessageContext, GuideMessageHandler};
1314
use crate::messages::portfolio::document::node_graph::NodeGraphMessageContext;
1415
use crate::messages::portfolio::document::node_graph::document_node_definitions::DefinitionIdentifier;
1516
use crate::messages::portfolio::document::node_graph::utility_types::FrontendGraphDataType;
@@ -76,6 +77,8 @@ pub struct DocumentMessageHandler {
7677
pub properties_panel_message_handler: PropertiesPanelMessageHandler,
7778
#[serde(skip)]
7879
pub data_panel_message_handler: DataPanelMessageHandler,
80+
#[serde(flatten)]
81+
pub guide_handler: GuideMessageHandler,
7982

8083
// ============================================
8184
// Fields that are saved in the document format
@@ -154,6 +157,7 @@ impl Default for DocumentMessageHandler {
154157
overlays_message_handler: OverlaysMessageHandler::default(),
155158
properties_panel_message_handler: PropertiesPanelMessageHandler::default(),
156159
data_panel_message_handler: DataPanelMessageHandler::default(),
160+
guide_handler: GuideMessageHandler::default(),
157161
// ============================================
158162
// Fields that are saved in the document format
159163
// ============================================
@@ -179,6 +183,7 @@ impl Default for DocumentMessageHandler {
179183
saved_hash: None,
180184
auto_saved_hash: None,
181185
layer_range_selection_reference: None,
186+
182187
is_loaded: false,
183188
}
184189
}
@@ -215,6 +220,14 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
215220

216221
self.navigation_handler.process_message(message, responses, context);
217222
}
223+
DocumentMessage::Guide(message) => {
224+
let context = GuideMessageContext {
225+
navigation_handler: &self.navigation_handler,
226+
document_ptz: &self.document_ptz,
227+
viewport,
228+
};
229+
self.guide_handler.process_message(message, responses, context);
230+
}
218231
DocumentMessage::Overlays(message) => {
219232
let visibility_settings = self.overlays_visibility_settings;
220233

@@ -622,6 +635,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
622635
self.snapping_state.grid_snapping = visible;
623636
responses.add(OverlaysMessage::Draw);
624637
}
638+
// Guide messages
625639
DocumentMessage::GroupSelectedLayers { group_folder_type } => {
626640
self.handle_group_selected_layers(group_folder_type, responses);
627641
}
@@ -1433,6 +1447,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
14331447
ZoomCanvasTo200Percent,
14341448
ZoomCanvasToFitAll,
14351449
);
1450+
common.extend(self.guide_handler.actions());
14361451

14371452
// Additional actions available on desktop
14381453
#[cfg(not(target_family = "wasm"))]
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
2+
use crate::messages::portfolio::document::utility_types::guide::{GuideDirection, GuideId};
3+
use crate::messages::prelude::*;
4+
5+
#[impl_message(Message, DocumentMessage, Guide)]
6+
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
7+
pub enum GuideMessage {
8+
CreateGuide { id: GuideId, direction: GuideDirection, mouse_x: f64, mouse_y: f64 },
9+
MoveGuide { id: GuideId, mouse_x: f64, mouse_y: f64 },
10+
DeleteGuide { id: GuideId },
11+
GuideOverlays { context: OverlayContext },
12+
ToggleGuidesVisibility,
13+
SetHoveredGuide { id: Option<GuideId> },
14+
}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
use super::utility_types::guide::{Guide, GuideDirection, GuideId};
2+
use crate::messages::portfolio::document::guide_message::{GuideMessage, GuideMessageDiscriminant};
3+
use crate::messages::portfolio::document::overlays::guide_overlays::guide_overlay;
4+
use crate::messages::portfolio::document::utility_types::misc::PTZ;
5+
use crate::messages::prelude::*;
6+
use glam::DVec2;
7+
8+
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, ExtractField)]
9+
#[serde(default)]
10+
pub struct GuideMessageHandler {
11+
#[serde(default)]
12+
pub guides: Vec<Guide>,
13+
#[serde(default = "default_guides_visible")]
14+
pub guides_visible: bool,
15+
#[serde(skip)]
16+
pub hovered_guide_id: Option<GuideId>,
17+
}
18+
19+
fn default_guides_visible() -> bool {
20+
true
21+
}
22+
23+
impl Default for GuideMessageHandler {
24+
fn default() -> Self {
25+
Self {
26+
guides: Vec::new(),
27+
guides_visible: true,
28+
hovered_guide_id: None,
29+
}
30+
}
31+
}
32+
33+
#[derive(ExtractField)]
34+
pub struct GuideMessageContext<'a> {
35+
pub navigation_handler: &'a NavigationMessageHandler,
36+
pub document_ptz: &'a PTZ,
37+
pub viewport: &'a ViewportMessageHandler,
38+
}
39+
40+
#[message_handler_data]
41+
impl MessageHandler<GuideMessage, GuideMessageContext<'_>> for GuideMessageHandler {
42+
fn actions(&self) -> ActionList {
43+
actions!(GuideMessageDiscriminant; ToggleGuidesVisibility)
44+
}
45+
46+
fn process_message(&mut self, message: GuideMessage, responses: &mut VecDeque<Message>, context: GuideMessageContext) {
47+
let GuideMessageContext {
48+
navigation_handler,
49+
document_ptz,
50+
viewport,
51+
} = context;
52+
53+
match message {
54+
GuideMessage::CreateGuide { id, direction, mouse_x, mouse_y } => {
55+
let document_to_viewport = navigation_handler.calculate_offset_transform(viewport.center_in_viewport_space().into(), document_ptz);
56+
let viewport_to_document = document_to_viewport.inverse();
57+
58+
let viewport_point = DVec2::new(mouse_x, mouse_y);
59+
let document_point = viewport_to_document.transform_point2(viewport_point);
60+
61+
let document_position = match direction {
62+
GuideDirection::Horizontal => document_point.y,
63+
GuideDirection::Vertical => document_point.x,
64+
};
65+
66+
let guide = Guide::with_id(id, direction, document_position);
67+
self.guides.push(guide);
68+
responses.add(OverlaysMessage::Draw);
69+
responses.add(PortfolioMessage::UpdateDocumentWidgets);
70+
}
71+
GuideMessage::MoveGuide { id, mouse_x, mouse_y } => {
72+
let document_to_viewport = navigation_handler.calculate_offset_transform(viewport.center_in_viewport_space().into(), document_ptz);
73+
let viewport_to_document = document_to_viewport.inverse();
74+
75+
let viewport_point = DVec2::new(mouse_x, mouse_y);
76+
let document_point = viewport_to_document.transform_point2(viewport_point);
77+
78+
if let Some(guide) = self.guides.iter_mut().find(|guide| guide.id == id) {
79+
guide.position = match guide.direction {
80+
GuideDirection::Horizontal => document_point.y,
81+
GuideDirection::Vertical => document_point.x,
82+
};
83+
}
84+
responses.add(OverlaysMessage::Draw);
85+
}
86+
GuideMessage::DeleteGuide { id } => {
87+
self.guides.retain(|g| g.id != id);
88+
responses.add(OverlaysMessage::Draw);
89+
responses.add(PortfolioMessage::UpdateDocumentWidgets);
90+
}
91+
GuideMessage::GuideOverlays { context: mut overlay_context } => {
92+
if self.guides_visible {
93+
let document_to_viewport = navigation_handler.calculate_offset_transform(overlay_context.viewport.center_in_viewport_space().into(), document_ptz);
94+
guide_overlay(self, &mut overlay_context, document_to_viewport);
95+
}
96+
}
97+
GuideMessage::ToggleGuidesVisibility => {
98+
self.guides_visible = !self.guides_visible;
99+
responses.add(OverlaysMessage::Draw);
100+
responses.add(PortfolioMessage::UpdateDocumentWidgets);
101+
responses.add(MenuBarMessage::SendLayout);
102+
}
103+
GuideMessage::SetHoveredGuide { id } => {
104+
if self.hovered_guide_id != id {
105+
self.hovered_guide_id = id;
106+
responses.add(OverlaysMessage::Draw);
107+
}
108+
}
109+
}
110+
}
111+
}

editor/src/messages/portfolio/document/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ mod document_message_handler;
33

44
pub mod data_panel;
55
pub mod graph_operation;
6+
pub mod guide_message;
7+
pub mod guide_message_handler;
68
pub mod navigation;
79
pub mod node_graph;
810
pub mod overlays;
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
use crate::consts::{COLOR_OVERLAY_BLUE, COLOR_OVERLAY_BLUE_50};
2+
use crate::messages::portfolio::document::guide_message_handler::GuideMessageHandler;
3+
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
4+
use crate::messages::portfolio::document::utility_types::guide::GuideDirection;
5+
use glam::{DAffine2, DVec2};
6+
7+
fn extend_line_to_viewport(point: DVec2, direction: DVec2, viewport_size: DVec2) -> Option<(DVec2, DVec2)> {
8+
let dir = direction.try_normalize()?;
9+
10+
// Calculates t values for intersections with viewport edges
11+
let mut t_values = Vec::new();
12+
13+
let edges = graphene_std::renderer::Quad::from_box([DVec2::ZERO, viewport_size]).all_edges();
14+
for [start, end] in edges {
15+
let t_along_viewport = (point - start).perp_dot(dir) / (end - start).perp_dot(dir);
16+
let t_along_direction = (point - start).perp_dot(end - start) / (end - start).perp_dot(dir);
17+
if 0. <= t_along_viewport && t_along_viewport <= 1. && t_along_direction.is_finite() {
18+
t_values.push(t_along_direction);
19+
}
20+
}
21+
22+
if t_values.len() < 2 {
23+
return None;
24+
}
25+
26+
let t_min = t_values.iter().cloned().fold(f64::INFINITY, f64::min);
27+
let t_max = t_values.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
28+
29+
let start = point + dir * t_min;
30+
let end = point + dir * t_max;
31+
32+
Some((start, end))
33+
}
34+
35+
pub fn guide_overlay(guide_handler: &GuideMessageHandler, overlay_context: &mut OverlayContext, document_to_viewport: DAffine2) {
36+
let viewport_size: DVec2 = overlay_context.viewport.size().into();
37+
38+
for guide in &guide_handler.guides {
39+
let (doc_point, doc_direction) = match guide.direction {
40+
GuideDirection::Horizontal => (DVec2::new(0.0, guide.position), DVec2::X),
41+
GuideDirection::Vertical => (DVec2::new(guide.position, 0.0), DVec2::Y),
42+
};
43+
44+
let viewport_point = document_to_viewport.transform_point2(doc_point);
45+
let viewport_direction = document_to_viewport.transform_vector2(doc_direction);
46+
47+
let color = if guide_handler.hovered_guide_id == Some(guide.id) {
48+
COLOR_OVERLAY_BLUE_50
49+
} else {
50+
COLOR_OVERLAY_BLUE
51+
};
52+
53+
if let Some((start, end)) = extend_line_to_viewport(viewport_point, viewport_direction, viewport_size) {
54+
overlay_context.line(start, end, Some(color), None);
55+
}
56+
}
57+
}

editor/src/messages/portfolio/document/overlays/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
pub mod grid_overlays;
2+
pub mod guide_overlays;
23
mod overlays_message;
34
mod overlays_message_handler;
45
pub mod utility_functions;

editor/src/messages/portfolio/document/overlays/overlays_message_handler.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use super::utility_types::{OverlayProvider, OverlaysVisibilitySettings};
2+
use crate::messages::portfolio::document::guide_message::GuideMessage;
23
use crate::messages::prelude::*;
34

45
#[derive(ExtractField)]
@@ -57,6 +58,13 @@ impl MessageHandler<OverlaysMessage, OverlaysMessageContext<'_>> for OverlaysMes
5758
viewport: *viewport,
5859
},
5960
});
61+
responses.add(GuideMessage::GuideOverlays {
62+
context: OverlayContext {
63+
render_context: canvas_context.clone(),
64+
visibility_settings: visibility_settings.clone(),
65+
viewport: *viewport,
66+
},
67+
});
6068
for provider in &self.overlay_providers {
6169
responses.add(provider(OverlayContext {
6270
render_context: canvas_context.clone(),
@@ -74,6 +82,7 @@ impl MessageHandler<OverlaysMessage, OverlaysMessageContext<'_>> for OverlaysMes
7482

7583
if visibility_settings.all() {
7684
responses.add(DocumentMessage::GridOverlays { context: overlay_context.clone() });
85+
responses.add(GuideMessage::GuideOverlays { context: overlay_context.clone() });
7786

7887
for provider in &self.overlay_providers {
7988
responses.add(provider(overlay_context.clone()));

0 commit comments

Comments
 (0)