Skip to content
Merged
Show file tree
Hide file tree
Changes from 20 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: OverlaysType,
},
SetRangeSelectionLayer {
new_layer: Option<LayerNodeIdentifier>,
Expand Down
357 changes: 276 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,72 @@ 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
103 changes: 103 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,108 @@ 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 {
All,
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 +133,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
4 changes: 4 additions & 0 deletions editor/src/messages/tool/common_functionality/pivot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ impl Pivot {
}

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

self.recalculate_pivot(document);
if let (Some(pivot), Some(data)) = (self.pivot, draw_data) {
overlay_context.pivot(pivot, data.0);
Expand Down
32 changes: 32 additions & 0 deletions editor/src/messages/tool/common_functionality/shape_editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -496,6 +496,38 @@ impl ShapeState {
}
}

/// Deselects all the anchors across every selected layer.
pub fn deselect_all_anchors(&mut self) {
for (_, state) in self.selected_shape_state.iter_mut() {
let selected_anchor_points: Vec<ManipulatorPointId> = state
.selected_points
.iter()
.filter(|selected_point| matches!(selected_point, ManipulatorPointId::Anchor(_)))
.cloned()
.collect();

for point in selected_anchor_points {
state.deselect_point(point);
}
}
}

/// Deselects all the handles across every selected layer.
pub fn deselect_all_handles(&mut self) {
for (_, state) in self.selected_shape_state.iter_mut() {
let selected_handle_points: Vec<ManipulatorPointId> = state
.selected_points
.iter()
.filter(|selected_point| matches!(selected_point, ManipulatorPointId::PrimaryHandle(_) | ManipulatorPointId::EndHandle(_)))
.cloned()
.collect();

for point in selected_handle_points {
state.deselect_point(point);
}
}
}

/// Set the shapes we consider for selection, we will choose draggable manipulators from these shapes.
pub fn set_selected_layers(&mut self, target_layers: Vec<LayerNodeIdentifier>) {
self.selected_shape_state.retain(|layer_path, _| target_layers.contains(layer_path));
Expand Down
7 changes: 4 additions & 3 deletions editor/src/messages/tool/tool_messages/artboard_tool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -225,16 +225,17 @@ impl Fsm for ArtboardToolFsmState {
let ToolMessage::Artboard(event) = event else { return self };
match (self, event) {
(state, ArtboardToolMessage::Overlays(mut overlay_context)) => {
if state != ArtboardToolFsmState::Drawing {
let display_transform_cage = overlay_context.visibility_settings.transform_cage();
if display_transform_cage && state != ArtboardToolFsmState::Drawing {
if let Some(bounds) = tool_data.selected_artboard.and_then(|layer| document.metadata().bounding_box_document(layer)) {
let bounding_box_manager = tool_data.bounding_box_manager.get_or_insert(BoundingBoxManager::default());
bounding_box_manager.bounds = bounds;
bounding_box_manager.transform = document.metadata().document_to_viewport;

bounding_box_manager.render_overlays(&mut overlay_context, true);
} else {
tool_data.bounding_box_manager.take();
}
} else {
tool_data.bounding_box_manager.take();
}

tool_data.snap_manager.draw_overlays(SnapData::new(document, input), &mut overlay_context);
Expand Down
Loading