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 <keavon@keavon.com>
This commit is contained in:
seam0s-dev
2025-04-30 04:15:46 -07:00
committed by GitHub
co-authored by Keavon Chambers
parent 1f7a9188ba
commit 1a81e45673
15 changed files with 727 additions and 265 deletions
@@ -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;
@@ -143,6 +144,7 @@ pub enum DocumentMessage {
},
SetOverlaysVisibility {
visible: bool,
overlays_type: Option<OverlaysType>,
},
SetRangeSelectionLayer {
new_layer: Option<LayerNodeIdentifier>,
@@ -12,6 +12,7 @@ use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;
use crate::messages::portfolio::document::node_graph::NodeGraphHandlerData;
use crate::messages::portfolio::document::overlays::grid_overlays::{grid_overlay, overlay_options};
use crate::messages::portfolio::document::overlays::utility_types::{OverlaysType, OverlaysVisibilitySettings};
use crate::messages::portfolio::document::properties_panel::utility_types::PropertiesPanelMessageHandlerData;
use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate, AlignAxis, DocumentMode, FlipAxis, PTZ};
@@ -84,7 +85,7 @@ pub struct DocumentMessageHandler {
pub view_mode: ViewMode,
/// Sets whether or not all the viewport overlays should be drawn on top of the artwork.
/// This includes tool interaction visualizations (like the transform cage and path anchors/handles), the grid, and more.
pub overlays_visible: bool,
pub overlays_visibility_settings: OverlaysVisibilitySettings,
/// Sets whether or not the rulers should be drawn along the top and left edges of the viewport area.
pub rulers_visible: bool,
/// The current user choices for snapping behavior, including whether snapping is enabled at all.
@@ -145,7 +146,7 @@ impl Default for DocumentMessageHandler {
document_ptz: PTZ::default(),
document_mode: DocumentMode::DesignMode,
view_mode: ViewMode::default(),
overlays_visible: true,
overlays_visibility_settings: OverlaysVisibilitySettings::default(),
rulers_visible: true,
graph_view_overlay_open: false,
snapping_state: SnappingState::default(),
@@ -199,12 +200,14 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
self.navigation_handler.process_message(message, responses, data);
}
DocumentMessage::Overlays(message) => {
let overlays_visible = self.overlays_visible;
let visibility_settings = self.overlays_visibility_settings;
// Send the overlays message to the overlays message handler
self.overlays_message_handler.process_message(
message,
responses,
OverlaysMessageData {
overlays_visible,
visibility_settings,
ipp,
device_pixel_ratio,
},
@@ -347,6 +350,10 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
responses.add(FrontendMessage::UpdateDocumentLayerStructure { data_buffer });
}
DocumentMessage::DrawArtboardOverlays(overlay_context) => {
if !overlay_context.visibility_settings.artboard_name() {
return;
}
for layer in self.metadata().all_layers() {
if !self.network_interface.is_artboard(&layer.to_node(), &[]) {
continue;
@@ -1016,6 +1023,10 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
}
}
DocumentMessage::SelectAllLayers => {
if !self.overlays_visibility_settings.selection_outline() {
return;
}
let metadata = self.metadata();
let all_layers_except_artboards_invisible_and_locked = metadata.all_layers().filter(|&layer| !self.network_interface.is_artboard(&layer.to_node(), &[])).filter(|&layer| {
self.network_interface.selected_nodes().layer_visible(layer, &self.network_interface) && !self.network_interface.selected_nodes().layer_locked(layer, &self.network_interface)
@@ -1135,8 +1146,34 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
responses.add(GraphOperationMessage::OpacitySet { layer, opacity });
}
}
DocumentMessage::SetOverlaysVisibility { visible } => {
self.overlays_visible = visible;
DocumentMessage::SetOverlaysVisibility { visible, overlays_type } => {
let visibility_settings = &mut self.overlays_visibility_settings;
let overlays_type = match overlays_type {
Some(overlays_type) => overlays_type,
None => {
visibility_settings.all = visible;
responses.add(BroadcastEvent::ToolAbort);
responses.add(OverlaysMessage::Draw);
return;
}
};
match overlays_type {
OverlaysType::ArtboardName => visibility_settings.artboard_name = visible,
OverlaysType::CompassRose => visibility_settings.compass_rose = visible,
OverlaysType::QuickMeasurement => visibility_settings.quick_measurement = visible,
OverlaysType::TransformMeasurement => visibility_settings.transform_measurement = visible,
OverlaysType::TransformCage => visibility_settings.transform_cage = visible,
OverlaysType::HoverOutline => visibility_settings.hover_outline = visible,
OverlaysType::SelectionOutline => visibility_settings.selection_outline = visible,
OverlaysType::Pivot => visibility_settings.pivot = visible,
OverlaysType::Path => visibility_settings.path = visible,
OverlaysType::Anchors => {
visibility_settings.anchors = visible;
responses.add(PortfolioMessage::UpdateDocumentWidgets);
}
OverlaysType::Handles => visibility_settings.handles = visible,
}
responses.add(BroadcastEvent::ToolAbort);
responses.add(OverlaysMessage::Draw);
}
@@ -1238,7 +1275,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
responses.add(PortfolioMessage::UpdateDocumentWidgets);
}
DocumentMessage::ToggleOverlaysVisibility => {
self.overlays_visible = !self.overlays_visible;
self.overlays_visibility_settings.all = !self.overlays_visibility_settings.all();
responses.add(OverlaysMessage::Draw);
responses.add(PortfolioMessage::UpdateDocumentWidgets);
}
@@ -1679,7 +1716,7 @@ impl DocumentMessageHandler {
pub view_mode: ViewMode,
/// Sets whether or not all the viewport overlays should be drawn on top of the artwork.
/// This includes tool interaction visualizations (like the transform cage and path anchors/handles), the grid, and more.
pub overlays_visible: bool,
pub overlays_visibility_settings: OverlaysVisibilitySettings,
/// Sets whether or not the rulers should be drawn along the top and left edges of the viewport area.
pub rulers_visible: bool,
/// Sets whether or not the node graph is drawn (as an overlay) on top of the viewport area, or otherwise if it's hidden.
@@ -1695,7 +1732,7 @@ impl DocumentMessageHandler {
document_ptz: old_message_handler.document_ptz,
document_mode: old_message_handler.document_mode,
view_mode: old_message_handler.view_mode,
overlays_visible: old_message_handler.overlays_visible,
overlays_visibility_settings: old_message_handler.overlays_visibility_settings,
rulers_visible: old_message_handler.rulers_visible,
graph_view_overlay_open: old_message_handler.graph_view_overlay_open,
snapping_state: old_message_handler.snapping_state,
@@ -2053,11 +2090,17 @@ impl DocumentMessageHandler {
.on_update(|_| AnimationMessage::ToggleLivePreview.into())
.widget_holder(),
Separator::new(SeparatorType::Unrelated).widget_holder(),
CheckboxInput::new(self.overlays_visible)
CheckboxInput::new(self.overlays_visibility_settings.all)
.icon("Overlays")
.tooltip("Overlays")
.tooltip_shortcut(action_keys!(DocumentMessageDiscriminant::ToggleOverlaysVisibility))
.on_update(|optional_input: &CheckboxInput| DocumentMessage::SetOverlaysVisibility { visible: optional_input.checked }.into())
.on_update(|optional_input: &CheckboxInput| {
DocumentMessage::SetOverlaysVisibility {
visible: optional_input.checked,
overlays_type: None,
}
.into()
})
.widget_holder(),
PopoverButton::new()
.popover_layout(vec![
@@ -2065,7 +2108,168 @@ impl DocumentMessageHandler {
widgets: vec![TextLabel::new("Overlays").bold(true).widget_holder()],
},
LayoutGroup::Row {
widgets: vec![TextLabel::new("Granular settings in this menu are coming soon").widget_holder()],
widgets: vec![TextLabel::new("General").widget_holder()],
},
LayoutGroup::Row {
widgets: vec![
CheckboxInput::new(self.overlays_visibility_settings.artboard_name)
.on_update(|optional_input: &CheckboxInput| {
DocumentMessage::SetOverlaysVisibility {
visible: optional_input.checked,
overlays_type: Some(OverlaysType::ArtboardName),
}
.into()
})
.widget_holder(),
TextLabel::new("Artboard Name".to_string()).widget_holder(),
],
},
LayoutGroup::Row {
widgets: vec![
CheckboxInput::new(self.overlays_visibility_settings.transform_measurement)
.on_update(|optional_input: &CheckboxInput| {
DocumentMessage::SetOverlaysVisibility {
visible: optional_input.checked,
overlays_type: Some(OverlaysType::TransformMeasurement),
}
.into()
})
.widget_holder(),
TextLabel::new("G/R/S Measurement".to_string()).widget_holder(),
],
},
LayoutGroup::Row {
widgets: vec![TextLabel::new("Select Tool").widget_holder()],
},
LayoutGroup::Row {
widgets: vec![
CheckboxInput::new(self.overlays_visibility_settings.quick_measurement)
.on_update(|optional_input: &CheckboxInput| {
DocumentMessage::SetOverlaysVisibility {
visible: optional_input.checked,
overlays_type: Some(OverlaysType::QuickMeasurement),
}
.into()
})
.widget_holder(),
TextLabel::new("Quick Measurement".to_string()).widget_holder(),
],
},
LayoutGroup::Row {
widgets: vec![
CheckboxInput::new(self.overlays_visibility_settings.transform_cage)
.on_update(|optional_input: &CheckboxInput| {
DocumentMessage::SetOverlaysVisibility {
visible: optional_input.checked,
overlays_type: Some(OverlaysType::TransformCage),
}
.into()
})
.widget_holder(),
TextLabel::new("Transform Cage".to_string()).widget_holder(),
],
},
LayoutGroup::Row {
widgets: vec![
CheckboxInput::new(self.overlays_visibility_settings.compass_rose)
.on_update(|optional_input: &CheckboxInput| {
DocumentMessage::SetOverlaysVisibility {
visible: optional_input.checked,
overlays_type: Some(OverlaysType::CompassRose),
}
.into()
})
.widget_holder(),
TextLabel::new("Transform Dial".to_string()).widget_holder(),
],
},
LayoutGroup::Row {
widgets: vec![
CheckboxInput::new(self.overlays_visibility_settings.pivot)
.on_update(|optional_input: &CheckboxInput| {
DocumentMessage::SetOverlaysVisibility {
visible: optional_input.checked,
overlays_type: Some(OverlaysType::Pivot),
}
.into()
})
.widget_holder(),
TextLabel::new("Transform Pivot".to_string()).widget_holder(),
],
},
LayoutGroup::Row {
widgets: vec![
CheckboxInput::new(self.overlays_visibility_settings.hover_outline)
.on_update(|optional_input: &CheckboxInput| {
DocumentMessage::SetOverlaysVisibility {
visible: optional_input.checked,
overlays_type: Some(OverlaysType::HoverOutline),
}
.into()
})
.widget_holder(),
TextLabel::new("Hover Outline".to_string()).widget_holder(),
],
},
LayoutGroup::Row {
widgets: vec![
CheckboxInput::new(self.overlays_visibility_settings.selection_outline)
.on_update(|optional_input: &CheckboxInput| {
DocumentMessage::SetOverlaysVisibility {
visible: optional_input.checked,
overlays_type: Some(OverlaysType::SelectionOutline),
}
.into()
})
.widget_holder(),
TextLabel::new("Selection Outline".to_string()).widget_holder(),
],
},
LayoutGroup::Row {
widgets: vec![TextLabel::new("Pen & Path Tools").widget_holder()],
},
LayoutGroup::Row {
widgets: vec![
CheckboxInput::new(self.overlays_visibility_settings.path)
.on_update(|optional_input: &CheckboxInput| {
DocumentMessage::SetOverlaysVisibility {
visible: optional_input.checked,
overlays_type: Some(OverlaysType::Path),
}
.into()
})
.widget_holder(),
TextLabel::new("Path".to_string()).widget_holder(),
],
},
LayoutGroup::Row {
widgets: vec![
CheckboxInput::new(self.overlays_visibility_settings.anchors)
.on_update(|optional_input: &CheckboxInput| {
DocumentMessage::SetOverlaysVisibility {
visible: optional_input.checked,
overlays_type: Some(OverlaysType::Anchors),
}
.into()
})
.widget_holder(),
TextLabel::new("Anchors".to_string()).widget_holder(),
],
},
LayoutGroup::Row {
widgets: vec![
CheckboxInput::new(self.overlays_visibility_settings.handles)
.disabled(!self.overlays_visibility_settings.anchors)
.on_update(|optional_input: &CheckboxInput| {
DocumentMessage::SetOverlaysVisibility {
visible: optional_input.checked,
overlays_type: Some(OverlaysType::Handles),
}
.into()
})
.widget_holder(),
TextLabel::new("Handles".to_string()).disabled(!self.overlays_visibility_settings.anchors).widget_holder(),
],
},
])
.widget_holder(),
@@ -2212,6 +2416,7 @@ impl DocumentMessageHandler {
layout: Layout::WidgetLayout(document_bar_layout),
layout_target: LayoutTarget::DocumentBar,
});
responses.add(NodeGraphMessage::ForceRunDocumentGraph);
}
pub fn update_layers_panel_control_bar_widgets(&self, responses: &mut VecDeque<Message>) {
@@ -2280,75 +2485,74 @@ impl DocumentMessageHandler {
.selected_layers(self.metadata())
.all(|layer| self.network_interface.is_locked(&layer.to_node(), &[]));
let layers_panel_control_bar = WidgetLayout::new(vec![LayoutGroup::Row {
widgets: vec![
DropdownInput::new(blend_mode_menu_entries)
.selected_index(blend_mode.and_then(|blend_mode| blend_mode.index_in_list_svg_subset()).map(|index| index as u32))
.disabled(disabled)
.draw_icon(false)
.widget_holder(),
Separator::new(SeparatorType::Related).widget_holder(),
NumberInput::new(opacity)
.label("Opacity")
.unit("%")
.display_decimal_places(2)
.disabled(disabled)
.min(0.)
.max(100.)
.range_min(Some(0.))
.range_max(Some(100.))
.mode_range()
.on_update(|number_input: &NumberInput| {
if let Some(value) = number_input.value {
DocumentMessage::SetOpacityForSelectedLayers { opacity: value / 100. }.into()
} else {
Message::NoOp
}
})
.on_commit(|_| DocumentMessage::AddTransaction.into())
.widget_holder(),
//
Separator::new(SeparatorType::Unrelated).widget_holder(),
//
IconButton::new("NewLayer", 24)
.tooltip("New Layer")
.tooltip_shortcut(action_keys!(DocumentMessageDiscriminant::CreateEmptyFolder))
.on_update(|_| DocumentMessage::CreateEmptyFolder.into())
.widget_holder(),
IconButton::new("Folder", 24)
.tooltip("Group Selected")
.tooltip_shortcut(action_keys!(DocumentMessageDiscriminant::GroupSelectedLayers))
.on_update(|_| {
let group_folder_type = GroupFolderType::Layer;
DocumentMessage::GroupSelectedLayers { group_folder_type }.into()
})
.disabled(!has_selection)
.widget_holder(),
IconButton::new("Trash", 24)
.tooltip("Delete Selected")
.tooltip_shortcut(action_keys!(DocumentMessageDiscriminant::DeleteSelectedLayers))
.on_update(|_| DocumentMessage::DeleteSelectedLayers.into())
.disabled(!has_selection)
.widget_holder(),
//
Separator::new(SeparatorType::Unrelated).widget_holder(),
//
IconButton::new(if selection_all_locked { "PadlockLocked" } else { "PadlockUnlocked" }, 24)
.hover_icon(Some((if selection_all_locked { "PadlockUnlocked" } else { "PadlockLocked" }).into()))
.tooltip(if selection_all_locked { "Unlock Selected" } else { "Lock Selected" })
.tooltip_shortcut(action_keys!(DocumentMessageDiscriminant::ToggleSelectedLocked))
.on_update(|_| NodeGraphMessage::ToggleSelectedLocked.into())
.disabled(!has_selection)
.widget_holder(),
IconButton::new(if selection_all_visible { "EyeVisible" } else { "EyeHidden" }, 24)
.hover_icon(Some((if selection_all_visible { "EyeHide" } else { "EyeShow" }).into()))
.tooltip(if selection_all_visible { "Hide Selected" } else { "Show Selected" })
.tooltip_shortcut(action_keys!(DocumentMessageDiscriminant::ToggleSelectedVisibility))
.on_update(|_| DocumentMessage::ToggleSelectedVisibility.into())
.disabled(!has_selection)
.widget_holder(),
],
}]);
let widgets = vec![
DropdownInput::new(blend_mode_menu_entries)
.selected_index(blend_mode.and_then(|blend_mode| blend_mode.index_in_list_svg_subset()).map(|index| index as u32))
.disabled(disabled)
.draw_icon(false)
.widget_holder(),
Separator::new(SeparatorType::Related).widget_holder(),
NumberInput::new(opacity)
.label("Opacity")
.unit("%")
.display_decimal_places(2)
.disabled(disabled)
.min(0.)
.max(100.)
.range_min(Some(0.))
.range_max(Some(100.))
.mode_range()
.on_update(|number_input: &NumberInput| {
if let Some(value) = number_input.value {
DocumentMessage::SetOpacityForSelectedLayers { opacity: value / 100. }.into()
} else {
Message::NoOp
}
})
.on_commit(|_| DocumentMessage::AddTransaction.into())
.widget_holder(),
//
Separator::new(SeparatorType::Unrelated).widget_holder(),
//
IconButton::new("NewLayer", 24)
.tooltip("New Layer")
.tooltip_shortcut(action_keys!(DocumentMessageDiscriminant::CreateEmptyFolder))
.on_update(|_| DocumentMessage::CreateEmptyFolder.into())
.widget_holder(),
IconButton::new("Folder", 24)
.tooltip("Group Selected")
.tooltip_shortcut(action_keys!(DocumentMessageDiscriminant::GroupSelectedLayers))
.on_update(|_| {
let group_folder_type = GroupFolderType::Layer;
DocumentMessage::GroupSelectedLayers { group_folder_type }.into()
})
.disabled(!has_selection)
.widget_holder(),
IconButton::new("Trash", 24)
.tooltip("Delete Selected")
.tooltip_shortcut(action_keys!(DocumentMessageDiscriminant::DeleteSelectedLayers))
.on_update(|_| DocumentMessage::DeleteSelectedLayers.into())
.disabled(!has_selection)
.widget_holder(),
//
Separator::new(SeparatorType::Unrelated).widget_holder(),
//
IconButton::new(if selection_all_locked { "PadlockLocked" } else { "PadlockUnlocked" }, 24)
.hover_icon(Some((if selection_all_locked { "PadlockUnlocked" } else { "PadlockLocked" }).into()))
.tooltip(if selection_all_locked { "Unlock Selected" } else { "Lock Selected" })
.tooltip_shortcut(action_keys!(DocumentMessageDiscriminant::ToggleSelectedLocked))
.on_update(|_| NodeGraphMessage::ToggleSelectedLocked.into())
.disabled(!has_selection)
.widget_holder(),
IconButton::new(if selection_all_visible { "EyeVisible" } else { "EyeHidden" }, 24)
.hover_icon(Some((if selection_all_visible { "EyeHide" } else { "EyeShow" }).into()))
.tooltip(if selection_all_visible { "Hide Selected" } else { "Show Selected" })
.tooltip_shortcut(action_keys!(DocumentMessageDiscriminant::ToggleSelectedVisibility))
.on_update(|_| DocumentMessage::ToggleSelectedVisibility.into())
.disabled(!has_selection)
.widget_holder(),
];
let layers_panel_control_bar = WidgetLayout::new(vec![LayoutGroup::Row { widgets }]);
responses.add(LayoutMessage::SendLayout {
layout: Layout::WidgetLayout(layers_panel_control_bar),
@@ -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,
}
@@ -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")]
@@ -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);
@@ -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),
@@ -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);
});
}
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);
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::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 => {}
}
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;
@@ -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
@@ -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 {