Skip to content

Add the settings popover menu for the Overlays toggle #2523

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 35 commits into from
Apr 30, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
48dca48
Added granular overlays control based on features
seam0s-dev Apr 6, 2025
3beb5cf
Added basic support for pivot, path, anchors and handles overlay sett…
seam0s-dev Apr 9, 2025
ba5aa04
Added more overlay checks on anchors and handles
seam0s-dev Apr 9, 2025
938ad07
Add new settings over measurements, hover and selection overlays
seam0s-dev Apr 15, 2025
f1f2a7b
Fix errors introduced while rebasing
seam0s-dev Apr 16, 2025
d335e3c
Disable anchors and handles functionality with their overlays, extend…
seam0s-dev Apr 17, 2025
61f2d82
Merge branch 'GraphiteEditor:master' into granular-overlays-settings
seam0s-dev Apr 17, 2025
d48cfde
Add check to enable/disable outlines on selected layers
seam0s-dev Apr 18, 2025
c4c5379
Toggle handles checkbox in sync with anchors checkbox
seam0s-dev Apr 18, 2025
d5d5d4c
Refactor overlays checks
seam0s-dev Apr 18, 2025
c55d679
Remove debug statements
seam0s-dev Apr 18, 2025
09729d1
Merge branch 'master' into granular-overlays-settings
seam0s-dev Apr 18, 2025
e13afc8
Update select_tool.rs to resolve conflict
seam0s-dev Apr 18, 2025
792893a
Minor fix to reflect anchor checkbox state on the handles
seam0s-dev Apr 19, 2025
0b0e57c
Merge branch 'master' into granular-overlays-settings
seam0s-dev Apr 19, 2025
b721315
Merge branch 'GraphiteEditor:master' into granular-overlays-settings
seam0s-dev Apr 21, 2025
39449d3
Merge branch 'master' into granular-overlays-settings
Keavon Apr 23, 2025
857fb70
Minor fix to make anchors checkbox work
seam0s-dev Apr 24, 2025
24719ad
Merge branch 'master' into granular-overlays-settings
Keavon Apr 24, 2025
4151b79
Rearrange menu items, and code review
Keavon Apr 24, 2025
2fcc50f
Fix pivot dragging
seam0s-dev Apr 27, 2025
5fd735b
Add handles overlay check when drawing with pen tool
seam0s-dev Apr 27, 2025
b7fb453
Fix constrained dragging when transform cage is disabled
seam0s-dev Apr 27, 2025
623e263
Fix deselecting user selection when anchors are disabled
seam0s-dev Apr 27, 2025
16c0c4f
Minor fix for disabling anchors
seam0s-dev Apr 28, 2025
1634862
Remove All from OverlaysType
seam0s-dev Apr 28, 2025
ddcbafc
Remove debug statements
seam0s-dev Apr 28, 2025
fb523b5
Merge branch 'master' into granular-overlays-settings
seam0s-dev Apr 28, 2025
4e6f773
Fix editor crash when selecting other layers with path tool and ancho…
seam0s-dev Apr 29, 2025
73d3ea0
Merge branch 'master' into granular-overlays-settings
seam0s-dev Apr 29, 2025
5770a93
Merge branch 'master' into granular-overlays-settings
Keavon Apr 30, 2025
97ca65d
Minor fix on overlays check for all overlays
seam0s-dev Apr 30, 2025
293612e
Add proper code formatting
seam0s-dev Apr 30, 2025
ceeb7f2
Merge branch 'master' into granular-overlays-settings
Keavon Apr 30, 2025
fd5629b
Nits
Keavon Apr 30, 2025
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
2 changes: 2 additions & 0 deletions editor/src/messages/portfolio/document/document_message.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use super::utility_types::misc::{GroupFolderType, SnappingState};
use crate::messages::input_mapper::utility_types::input_keyboard::Key;
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
use crate::messages::portfolio::document::overlays::utility_types::OverlaysType;
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate, AlignAxis, FlipAxis, GridSnapping};
use crate::messages::portfolio::utility_types::PanelType;
Expand Down Expand Up @@ -143,6 +144,7 @@ pub enum DocumentMessage {
},
SetOverlaysVisibility {
visible: bool,
overlays_type: Option<OverlaysType>,
},
SetRangeSelectionLayer {
new_layer: Option<LayerNodeIdentifier>,
Expand Down
366 changes: 285 additions & 81 deletions editor/src/messages/portfolio/document/document_message_handler.rs

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use super::utility_types::OverlayProvider;
use super::utility_types::{OverlayProvider, OverlaysVisibilitySettings};
use crate::messages::prelude::*;

pub struct OverlaysMessageData<'a> {
pub overlays_visible: bool,
pub visibility_settings: OverlaysVisibilitySettings,
pub ipp: &'a InputPreprocessorMessageHandler,
pub device_pixel_ratio: f64,
}
Expand All @@ -18,7 +18,7 @@ pub struct OverlaysMessageHandler {

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

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

if overlays_visible {
if visibility_settings.all() {
responses.add(DocumentMessage::GridOverlays(OverlayContext {
render_context: context.clone(),
size: size.as_dvec2(),
device_pixel_ratio,
visibility_settings: visibility_settings.clone(),
}));
for provider in &self.overlay_providers {
responses.add(provider(OverlayContext {
render_context: context.clone(),
size: size.as_dvec2(),
device_pixel_ratio,
visibility_settings: visibility_settings.clone(),
}));
}
}
}
#[cfg(not(target_arch = "wasm32"))]
OverlaysMessage::Draw => {
warn!("Cannot render overlays on non-Wasm targets.\n{responses:?} {overlays_visible} {ipp:?}",);
warn!("Cannot render overlays on non-Wasm targets.\n{responses:?} {visibility_settings:?} {ipp:?}",);
}
OverlaysMessage::AddProvider(message) => {
self.overlay_providers.insert(message);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ fn overlay_bezier_handles(bezier: Bezier, segment_id: SegmentId, transform: DAff
}
}

pub fn overlay_bezier_handle_specific_point(
fn overlay_bezier_handle_specific_point(
bezier: Bezier,
segment_id: SegmentId,
(start, end): (PointId, PointId),
Expand Down Expand Up @@ -112,59 +112,73 @@ pub fn overlay_bezier_handle_specific_point(
}

pub fn path_overlays(document: &DocumentMessageHandler, draw_handles: DrawHandles, shape_editor: &mut ShapeState, overlay_context: &mut OverlayContext) {
let display_path = overlay_context.visibility_settings.path();
let display_handles = overlay_context.visibility_settings.handles();
let display_anchors = overlay_context.visibility_settings.anchors();

for layer in document.network_interface.selected_nodes().selected_layers(document.metadata()) {
let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else { continue };
let transform = document.metadata().transform_to_viewport(layer);
overlay_context.outline_vector(&vector_data, transform);
if display_path {
overlay_context.outline_vector(&vector_data, transform);
}

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

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

match draw_handles {
DrawHandles::All => {
vector_data.segment_bezier_iter().for_each(|(segment_id, bezier, _start, _end)| {
overlay_bezier_handles(bezier, segment_id, transform, is_selected, overlay_context);
});
}
DrawHandles::SelectedAnchors(ref selected_segments) => {
vector_data
.segment_bezier_iter()
.filter(|(segment_id, ..)| selected_segments.contains(segment_id))
.for_each(|(segment_id, bezier, _start, _end)| {
match draw_handles {
DrawHandles::All => {
vector_data.segment_bezier_iter().for_each(|(segment_id, bezier, _start, _end)| {
overlay_bezier_handles(bezier, segment_id, transform, is_selected, overlay_context);
});

for (segment_id, bezier, start, end) in vector_data.segment_bezier_iter() {
if let Some((corresponding_anchor, _)) = opposite_handles_data.iter().find(|(_, adj_segment_id)| adj_segment_id == &segment_id) {
overlay_bezier_handle_specific_point(bezier, segment_id, (start, end), *corresponding_anchor, transform, is_selected, overlay_context);
}
}
}
DrawHandles::FrontierHandles(ref segment_endpoints) => {
vector_data
.segment_bezier_iter()
.filter(|(segment_id, ..)| segment_endpoints.contains_key(segment_id))
.for_each(|(segment_id, bezier, start, end)| {
if segment_endpoints.get(&segment_id).unwrap().len() == 1 {
let point_to_render = segment_endpoints.get(&segment_id).unwrap()[0];
overlay_bezier_handle_specific_point(bezier, segment_id, (start, end), point_to_render, transform, is_selected, overlay_context);
} else {
DrawHandles::SelectedAnchors(ref selected_segments) => {
vector_data
.segment_bezier_iter()
.filter(|(segment_id, ..)| selected_segments.contains(segment_id))
.for_each(|(segment_id, bezier, _start, _end)| {
overlay_bezier_handles(bezier, segment_id, transform, is_selected, overlay_context);
});

for (segment_id, bezier, start, end) in vector_data.segment_bezier_iter() {
if let Some((corresponding_anchor, _)) = opposite_handles_data.iter().find(|(_, adj_segment_id)| adj_segment_id == &segment_id) {
overlay_bezier_handle_specific_point(bezier, segment_id, (start, end), *corresponding_anchor, transform, is_selected, overlay_context);
}
});
}
}
DrawHandles::FrontierHandles(ref segment_endpoints) => {
vector_data
.segment_bezier_iter()
.filter(|(segment_id, ..)| segment_endpoints.contains_key(segment_id))
.for_each(|(segment_id, bezier, start, end)| {
if segment_endpoints.get(&segment_id).unwrap().len() == 1 {
let point_to_render = segment_endpoints.get(&segment_id).unwrap()[0];
overlay_bezier_handle_specific_point(bezier, segment_id, (start, end), point_to_render, transform, is_selected, overlay_context);
} else {
overlay_bezier_handles(bezier, segment_id, transform, is_selected, overlay_context);
}
});
}
DrawHandles::None => {}
}
DrawHandles::None => {}
}

for (&id, &position) in vector_data.point_domain.ids().iter().zip(vector_data.point_domain.positions()) {
overlay_context.manipulator_anchor(transform.transform_point2(position), is_selected(ManipulatorPointId::Anchor(id)), None);
if display_anchors {
for (&id, &position) in vector_data.point_domain.ids().iter().zip(vector_data.point_domain.positions()) {
overlay_context.manipulator_anchor(transform.transform_point2(position), is_selected(ManipulatorPointId::Anchor(id)), None);
}
}
}
}

pub fn path_endpoint_overlays(document: &DocumentMessageHandler, shape_editor: &mut ShapeState, overlay_context: &mut OverlayContext, preferences: &PreferencesMessageHandler) {
if !overlay_context.visibility_settings.anchors() {
return;
}

for layer in document.network_interface.selected_nodes().selected_layers(document.metadata()) {
let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else {
continue;
Expand Down
102 changes: 102 additions & 0 deletions editor/src/messages/portfolio/document/overlays/utility_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,107 @@ pub fn empty_provider() -> OverlayProvider {
|_| Message::NoOp
}

// Types of overlays used by DocumentMessage to enable/disable select group of overlays in the frontend
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
pub enum OverlaysType {
ArtboardName,
CompassRose,
QuickMeasurement,
TransformMeasurement,
TransformCage,
HoverOutline,
SelectionOutline,
Pivot,
Path,
Anchors,
Handles,
}

#[derive(PartialEq, Copy, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
pub struct OverlaysVisibilitySettings {
pub all: bool,
pub artboard_name: bool,
pub compass_rose: bool,
pub quick_measurement: bool,
pub transform_measurement: bool,
pub transform_cage: bool,
pub hover_outline: bool,
pub selection_outline: bool,
pub pivot: bool,
pub path: bool,
pub anchors: bool,
pub handles: bool,
}

impl Default for OverlaysVisibilitySettings {
fn default() -> Self {
Self {
all: true,
artboard_name: true,
compass_rose: true,
quick_measurement: true,
transform_measurement: true,
transform_cage: true,
hover_outline: true,
selection_outline: true,
pivot: true,
path: true,
anchors: true,
handles: true,
}
}
}

impl OverlaysVisibilitySettings {
pub fn all(&self) -> bool {
self.all
}

pub fn artboard_name(&self) -> bool {
self.all && self.artboard_name
}

pub fn compass_rose(&self) -> bool {
self.all && self.compass_rose
}

pub fn quick_measurement(&self) -> bool {
self.all && self.quick_measurement
}

pub fn transform_measurement(&self) -> bool {
self.all && self.transform_measurement
}

pub fn transform_cage(&self) -> bool {
self.all && self.transform_cage
}

pub fn hover_outline(&self) -> bool {
self.all && self.hover_outline
}

pub fn selection_outline(&self) -> bool {
self.all && self.selection_outline
}

pub fn pivot(&self) -> bool {
self.all && self.pivot
}

pub fn path(&self) -> bool {
self.all && self.path
}

pub fn anchors(&self) -> bool {
self.all && self.anchors
}

pub fn handles(&self) -> bool {
self.all && self.anchors && self.handles
}
}

#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
pub struct OverlayContext {
// Serde functionality isn't used but is required by the message system macros
Expand All @@ -31,6 +132,7 @@ pub struct OverlayContext {
// 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.
// 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.
pub device_pixel_ratio: f64,
pub visibility_settings: OverlaysVisibilitySettings,
}
// Message hashing isn't used but is required by the message system macros
impl core::hash::Hash for OverlayContext {
Expand Down
29 changes: 29 additions & 0 deletions editor/src/messages/tool/common_functionality/pivot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ pub struct Pivot {
pivot: Option<DVec2>,
/// The old pivot position in the GUI, used to reduce refreshes of the document bar
old_pivot_position: ReferencePoint,
/// Used to enable and disable the pivot
active: bool,
}

impl Default for Pivot {
Expand All @@ -28,6 +30,7 @@ impl Default for Pivot {
transform_from_normalized: Default::default(),
pivot: Default::default(),
old_pivot_position: ReferencePoint::Center,
active: true,
}
}
}
Expand All @@ -44,6 +47,10 @@ impl Pivot {

/// Recomputes the pivot position and transform.
fn recalculate_pivot(&mut self, document: &DocumentMessageHandler) {
if !self.active {
return;
}

let selected_nodes = document.network_interface.selected_nodes();
let mut layers = selected_nodes.selected_visible_and_unlocked_layers(&document.network_interface);
let Some(first) = layers.next() else {
Expand Down Expand Up @@ -82,6 +89,13 @@ impl Pivot {
}

pub fn update_pivot(&mut self, document: &DocumentMessageHandler, overlay_context: &mut OverlayContext, draw_data: Option<(f64,)>) {
if !overlay_context.visibility_settings.pivot() {
self.active = false;
return;
} else {
self.active = true;
}

self.recalculate_pivot(document);
if let (Some(pivot), Some(data)) = (self.pivot, draw_data) {
overlay_context.pivot(pivot, data.0);
Expand All @@ -90,6 +104,10 @@ impl Pivot {

/// Answers if the pivot widget has changed (so we should refresh the tool bar at the top of the canvas).
pub fn should_refresh_pivot_position(&mut self) -> bool {
if !self.active {
return false;
}

let new = self.to_pivot_position();
let should_refresh = new != self.old_pivot_position;
self.old_pivot_position = new;
Expand All @@ -102,6 +120,10 @@ impl Pivot {

/// Sets the viewport position of the pivot for all selected layers.
pub fn set_viewport_position(&self, position: DVec2, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
if !self.active {
return;
}

for layer in document.network_interface.selected_nodes().selected_visible_and_unlocked_layers(&document.network_interface) {
let transform = Self::get_layer_pivot_transform(layer, document);
// Only update the pivot when computed position is finite.
Expand All @@ -115,11 +137,18 @@ impl Pivot {

/// Set the pivot using the normalized transform that is set above.
pub fn set_normalized_position(&self, position: DVec2, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
if !self.active {
return;
}

self.set_viewport_position(self.transform_from_normalized.transform_point2(position), document, responses);
}

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