Skip to content

Commit 1a81e45

Browse files
seam0s-devKeavon
andauthored
Add the settings popover menu for the Overlays toggle (#2523)
* Added granular overlays control based on features * Added basic support for pivot, path, anchors and handles overlay settings * Added more overlay checks on anchors and handles * Add new settings over measurements, hover and selection overlays * Fix errors introduced while rebasing * Disable anchors and handles functionality with their overlays, extended selection outline check * Add check to enable/disable outlines on selected layers * Toggle handles checkbox in sync with anchors checkbox * Refactor overlays checks * Remove debug statements * Update select_tool.rs to resolve conflict * Minor fix to reflect anchor checkbox state on the handles * Minor fix to make anchors checkbox work * Rearrange menu items, and code review * Fix pivot dragging * Add handles overlay check when drawing with pen tool * Fix constrained dragging when transform cage is disabled * Fix deselecting user selection when anchors are disabled * Minor fix for disabling anchors * Remove All from OverlaysType * Remove debug statements * Fix editor crash when selecting other layers with path tool and anchors disabled * Minor fix on overlays check for all overlays * Add proper code formatting * Nits --------- Co-authored-by: Keavon Chambers <[email protected]>
1 parent 1f7a918 commit 1a81e45

File tree

15 files changed

+731
-269
lines changed

15 files changed

+731
-269
lines changed

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

+2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use super::utility_types::misc::{GroupFolderType, SnappingState};
22
use crate::messages::input_mapper::utility_types::input_keyboard::Key;
33
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
4+
use crate::messages::portfolio::document::overlays::utility_types::OverlaysType;
45
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
56
use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate, AlignAxis, FlipAxis, GridSnapping};
67
use crate::messages::portfolio::utility_types::PanelType;
@@ -143,6 +144,7 @@ pub enum DocumentMessage {
143144
},
144145
SetOverlaysVisibility {
145146
visible: bool,
147+
overlays_type: Option<OverlaysType>,
146148
},
147149
SetRangeSelectionLayer {
148150
new_layer: Option<LayerNodeIdentifier>,

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

+285-81
Large diffs are not rendered by default.

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

+7-5
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
use super::utility_types::OverlayProvider;
1+
use super::utility_types::{OverlayProvider, OverlaysVisibilitySettings};
22
use crate::messages::prelude::*;
33

44
pub struct OverlaysMessageData<'a> {
5-
pub overlays_visible: bool,
5+
pub visibility_settings: OverlaysVisibilitySettings,
66
pub ipp: &'a InputPreprocessorMessageHandler,
77
pub device_pixel_ratio: f64,
88
}
@@ -18,7 +18,7 @@ pub struct OverlaysMessageHandler {
1818

1919
impl MessageHandler<OverlaysMessage, OverlaysMessageData<'_>> for OverlaysMessageHandler {
2020
fn process_message(&mut self, message: OverlaysMessage, responses: &mut VecDeque<Message>, data: OverlaysMessageData) {
21-
let OverlaysMessageData { overlays_visible, ipp, .. } = data;
21+
let OverlaysMessageData { visibility_settings, ipp, .. } = data;
2222

2323
match message {
2424
#[cfg(target_arch = "wasm32")]
@@ -50,24 +50,26 @@ impl MessageHandler<OverlaysMessage, OverlaysMessageData<'_>> for OverlaysMessag
5050
context.clear_rect(0., 0., canvas.width().into(), canvas.height().into());
5151
let _ = context.reset_transform();
5252

53-
if overlays_visible {
53+
if visibility_settings.all() {
5454
responses.add(DocumentMessage::GridOverlays(OverlayContext {
5555
render_context: context.clone(),
5656
size: size.as_dvec2(),
5757
device_pixel_ratio,
58+
visibility_settings: visibility_settings.clone(),
5859
}));
5960
for provider in &self.overlay_providers {
6061
responses.add(provider(OverlayContext {
6162
render_context: context.clone(),
6263
size: size.as_dvec2(),
6364
device_pixel_ratio,
65+
visibility_settings: visibility_settings.clone(),
6466
}));
6567
}
6668
}
6769
}
6870
#[cfg(not(target_arch = "wasm32"))]
6971
OverlaysMessage::Draw => {
70-
warn!("Cannot render overlays on non-Wasm targets.\n{responses:?} {overlays_visible} {ipp:?}",);
72+
warn!("Cannot render overlays on non-Wasm targets.\n{responses:?} {visibility_settings:?} {ipp:?}",);
7173
}
7274
OverlaysMessage::AddProvider(message) => {
7375
self.overlay_providers.insert(message);

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

+47-33
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ fn overlay_bezier_handles(bezier: Bezier, segment_id: SegmentId, transform: DAff
7777
}
7878
}
7979

80-
pub fn overlay_bezier_handle_specific_point(
80+
fn overlay_bezier_handle_specific_point(
8181
bezier: Bezier,
8282
segment_id: SegmentId,
8383
(start, end): (PointId, PointId),
@@ -112,59 +112,73 @@ pub fn overlay_bezier_handle_specific_point(
112112
}
113113

114114
pub fn path_overlays(document: &DocumentMessageHandler, draw_handles: DrawHandles, shape_editor: &mut ShapeState, overlay_context: &mut OverlayContext) {
115+
let display_path = overlay_context.visibility_settings.path();
116+
let display_handles = overlay_context.visibility_settings.handles();
117+
let display_anchors = overlay_context.visibility_settings.anchors();
118+
115119
for layer in document.network_interface.selected_nodes().selected_layers(document.metadata()) {
116120
let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else { continue };
117121
let transform = document.metadata().transform_to_viewport(layer);
118-
overlay_context.outline_vector(&vector_data, transform);
122+
if display_path {
123+
overlay_context.outline_vector(&vector_data, transform);
124+
}
119125

120126
let selected = shape_editor.selected_shape_state.get(&layer);
121127
let is_selected = |point: ManipulatorPointId| selected.is_some_and(|selected| selected.is_selected(point));
122128

123-
let opposite_handles_data: Vec<(PointId, SegmentId)> = shape_editor.selected_points().filter_map(|point_id| vector_data.adjacent_segment(point_id)).collect();
129+
if display_handles {
130+
let opposite_handles_data: Vec<(PointId, SegmentId)> = shape_editor.selected_points().filter_map(|point_id| vector_data.adjacent_segment(point_id)).collect();
124131

125-
match draw_handles {
126-
DrawHandles::All => {
127-
vector_data.segment_bezier_iter().for_each(|(segment_id, bezier, _start, _end)| {
128-
overlay_bezier_handles(bezier, segment_id, transform, is_selected, overlay_context);
129-
});
130-
}
131-
DrawHandles::SelectedAnchors(ref selected_segments) => {
132-
vector_data
133-
.segment_bezier_iter()
134-
.filter(|(segment_id, ..)| selected_segments.contains(segment_id))
135-
.for_each(|(segment_id, bezier, _start, _end)| {
132+
match draw_handles {
133+
DrawHandles::All => {
134+
vector_data.segment_bezier_iter().for_each(|(segment_id, bezier, _start, _end)| {
136135
overlay_bezier_handles(bezier, segment_id, transform, is_selected, overlay_context);
137136
});
138-
139-
for (segment_id, bezier, start, end) in vector_data.segment_bezier_iter() {
140-
if let Some((corresponding_anchor, _)) = opposite_handles_data.iter().find(|(_, adj_segment_id)| adj_segment_id == &segment_id) {
141-
overlay_bezier_handle_specific_point(bezier, segment_id, (start, end), *corresponding_anchor, transform, is_selected, overlay_context);
142-
}
143137
}
144-
}
145-
DrawHandles::FrontierHandles(ref segment_endpoints) => {
146-
vector_data
147-
.segment_bezier_iter()
148-
.filter(|(segment_id, ..)| segment_endpoints.contains_key(segment_id))
149-
.for_each(|(segment_id, bezier, start, end)| {
150-
if segment_endpoints.get(&segment_id).unwrap().len() == 1 {
151-
let point_to_render = segment_endpoints.get(&segment_id).unwrap()[0];
152-
overlay_bezier_handle_specific_point(bezier, segment_id, (start, end), point_to_render, transform, is_selected, overlay_context);
153-
} else {
138+
DrawHandles::SelectedAnchors(ref selected_segments) => {
139+
vector_data
140+
.segment_bezier_iter()
141+
.filter(|(segment_id, ..)| selected_segments.contains(segment_id))
142+
.for_each(|(segment_id, bezier, _start, _end)| {
154143
overlay_bezier_handles(bezier, segment_id, transform, is_selected, overlay_context);
144+
});
145+
146+
for (segment_id, bezier, start, end) in vector_data.segment_bezier_iter() {
147+
if let Some((corresponding_anchor, _)) = opposite_handles_data.iter().find(|(_, adj_segment_id)| adj_segment_id == &segment_id) {
148+
overlay_bezier_handle_specific_point(bezier, segment_id, (start, end), *corresponding_anchor, transform, is_selected, overlay_context);
155149
}
156-
});
150+
}
151+
}
152+
DrawHandles::FrontierHandles(ref segment_endpoints) => {
153+
vector_data
154+
.segment_bezier_iter()
155+
.filter(|(segment_id, ..)| segment_endpoints.contains_key(segment_id))
156+
.for_each(|(segment_id, bezier, start, end)| {
157+
if segment_endpoints.get(&segment_id).unwrap().len() == 1 {
158+
let point_to_render = segment_endpoints.get(&segment_id).unwrap()[0];
159+
overlay_bezier_handle_specific_point(bezier, segment_id, (start, end), point_to_render, transform, is_selected, overlay_context);
160+
} else {
161+
overlay_bezier_handles(bezier, segment_id, transform, is_selected, overlay_context);
162+
}
163+
});
164+
}
165+
DrawHandles::None => {}
157166
}
158-
DrawHandles::None => {}
159167
}
160168

161-
for (&id, &position) in vector_data.point_domain.ids().iter().zip(vector_data.point_domain.positions()) {
162-
overlay_context.manipulator_anchor(transform.transform_point2(position), is_selected(ManipulatorPointId::Anchor(id)), None);
169+
if display_anchors {
170+
for (&id, &position) in vector_data.point_domain.ids().iter().zip(vector_data.point_domain.positions()) {
171+
overlay_context.manipulator_anchor(transform.transform_point2(position), is_selected(ManipulatorPointId::Anchor(id)), None);
172+
}
163173
}
164174
}
165175
}
166176

167177
pub fn path_endpoint_overlays(document: &DocumentMessageHandler, shape_editor: &mut ShapeState, overlay_context: &mut OverlayContext, preferences: &PreferencesMessageHandler) {
178+
if !overlay_context.visibility_settings.anchors() {
179+
return;
180+
}
181+
168182
for layer in document.network_interface.selected_nodes().selected_layers(document.metadata()) {
169183
let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else {
170184
continue;

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

+102
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,107 @@ pub fn empty_provider() -> OverlayProvider {
2121
|_| Message::NoOp
2222
}
2323

24+
// Types of overlays used by DocumentMessage to enable/disable select group of overlays in the frontend
25+
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
26+
pub enum OverlaysType {
27+
ArtboardName,
28+
CompassRose,
29+
QuickMeasurement,
30+
TransformMeasurement,
31+
TransformCage,
32+
HoverOutline,
33+
SelectionOutline,
34+
Pivot,
35+
Path,
36+
Anchors,
37+
Handles,
38+
}
39+
40+
#[derive(PartialEq, Copy, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
41+
pub struct OverlaysVisibilitySettings {
42+
pub all: bool,
43+
pub artboard_name: bool,
44+
pub compass_rose: bool,
45+
pub quick_measurement: bool,
46+
pub transform_measurement: bool,
47+
pub transform_cage: bool,
48+
pub hover_outline: bool,
49+
pub selection_outline: bool,
50+
pub pivot: bool,
51+
pub path: bool,
52+
pub anchors: bool,
53+
pub handles: bool,
54+
}
55+
56+
impl Default for OverlaysVisibilitySettings {
57+
fn default() -> Self {
58+
Self {
59+
all: true,
60+
artboard_name: true,
61+
compass_rose: true,
62+
quick_measurement: true,
63+
transform_measurement: true,
64+
transform_cage: true,
65+
hover_outline: true,
66+
selection_outline: true,
67+
pivot: true,
68+
path: true,
69+
anchors: true,
70+
handles: true,
71+
}
72+
}
73+
}
74+
75+
impl OverlaysVisibilitySettings {
76+
pub fn all(&self) -> bool {
77+
self.all
78+
}
79+
80+
pub fn artboard_name(&self) -> bool {
81+
self.all && self.artboard_name
82+
}
83+
84+
pub fn compass_rose(&self) -> bool {
85+
self.all && self.compass_rose
86+
}
87+
88+
pub fn quick_measurement(&self) -> bool {
89+
self.all && self.quick_measurement
90+
}
91+
92+
pub fn transform_measurement(&self) -> bool {
93+
self.all && self.transform_measurement
94+
}
95+
96+
pub fn transform_cage(&self) -> bool {
97+
self.all && self.transform_cage
98+
}
99+
100+
pub fn hover_outline(&self) -> bool {
101+
self.all && self.hover_outline
102+
}
103+
104+
pub fn selection_outline(&self) -> bool {
105+
self.all && self.selection_outline
106+
}
107+
108+
pub fn pivot(&self) -> bool {
109+
self.all && self.pivot
110+
}
111+
112+
pub fn path(&self) -> bool {
113+
self.all && self.path
114+
}
115+
116+
pub fn anchors(&self) -> bool {
117+
self.all && self.anchors
118+
}
119+
120+
pub fn handles(&self) -> bool {
121+
self.all && self.anchors && self.handles
122+
}
123+
}
124+
24125
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
25126
pub struct OverlayContext {
26127
// Serde functionality isn't used but is required by the message system macros
@@ -31,6 +132,7 @@ pub struct OverlayContext {
31132
// The device pixel ratio is a property provided by the browser window and is the CSS pixel size divided by the physical monitor's pixel size.
32133
// It allows better pixel density of visualizations on high-DPI displays where the OS display scaling is not 100%, or where the browser is zoomed.
33134
pub device_pixel_ratio: f64,
135+
pub visibility_settings: OverlaysVisibilitySettings,
34136
}
35137
// Message hashing isn't used but is required by the message system macros
36138
impl core::hash::Hash for OverlayContext {

editor/src/messages/tool/common_functionality/pivot.rs

+29
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ pub struct Pivot {
1919
pivot: Option<DVec2>,
2020
/// The old pivot position in the GUI, used to reduce refreshes of the document bar
2121
old_pivot_position: ReferencePoint,
22+
/// Used to enable and disable the pivot
23+
active: bool,
2224
}
2325

2426
impl Default for Pivot {
@@ -28,6 +30,7 @@ impl Default for Pivot {
2830
transform_from_normalized: Default::default(),
2931
pivot: Default::default(),
3032
old_pivot_position: ReferencePoint::Center,
33+
active: true,
3134
}
3235
}
3336
}
@@ -44,6 +47,10 @@ impl Pivot {
4447

4548
/// Recomputes the pivot position and transform.
4649
fn recalculate_pivot(&mut self, document: &DocumentMessageHandler) {
50+
if !self.active {
51+
return;
52+
}
53+
4754
let selected_nodes = document.network_interface.selected_nodes();
4855
let mut layers = selected_nodes.selected_visible_and_unlocked_layers(&document.network_interface);
4956
let Some(first) = layers.next() else {
@@ -82,6 +89,13 @@ impl Pivot {
8289
}
8390

8491
pub fn update_pivot(&mut self, document: &DocumentMessageHandler, overlay_context: &mut OverlayContext, draw_data: Option<(f64,)>) {
92+
if !overlay_context.visibility_settings.pivot() {
93+
self.active = false;
94+
return;
95+
} else {
96+
self.active = true;
97+
}
98+
8599
self.recalculate_pivot(document);
86100
if let (Some(pivot), Some(data)) = (self.pivot, draw_data) {
87101
overlay_context.pivot(pivot, data.0);
@@ -90,6 +104,10 @@ impl Pivot {
90104

91105
/// Answers if the pivot widget has changed (so we should refresh the tool bar at the top of the canvas).
92106
pub fn should_refresh_pivot_position(&mut self) -> bool {
107+
if !self.active {
108+
return false;
109+
}
110+
93111
let new = self.to_pivot_position();
94112
let should_refresh = new != self.old_pivot_position;
95113
self.old_pivot_position = new;
@@ -102,6 +120,10 @@ impl Pivot {
102120

103121
/// Sets the viewport position of the pivot for all selected layers.
104122
pub fn set_viewport_position(&self, position: DVec2, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
123+
if !self.active {
124+
return;
125+
}
126+
105127
for layer in document.network_interface.selected_nodes().selected_visible_and_unlocked_layers(&document.network_interface) {
106128
let transform = Self::get_layer_pivot_transform(layer, document);
107129
// Only update the pivot when computed position is finite.
@@ -115,11 +137,18 @@ impl Pivot {
115137

116138
/// Set the pivot using the normalized transform that is set above.
117139
pub fn set_normalized_position(&self, position: DVec2, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
140+
if !self.active {
141+
return;
142+
}
143+
118144
self.set_viewport_position(self.transform_from_normalized.transform_point2(position), document, responses);
119145
}
120146

121147
/// Answers if the pointer is currently positioned over the pivot.
122148
pub fn is_over(&self, mouse: DVec2) -> bool {
149+
if !self.active {
150+
return false;
151+
}
123152
self.pivot.filter(|&pivot| mouse.distance_squared(pivot) < (PIVOT_DIAMETER / 2.).powi(2)).is_some()
124153
}
125154
}

0 commit comments

Comments
 (0)