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 14:15:46 +03:00 committed by GitHub
parent 1f7a9188ba
commit 1a81e45673
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 727 additions and 265 deletions

View File

@ -1,6 +1,7 @@
use super::utility_types::misc::{GroupFolderType, SnappingState}; use super::utility_types::misc::{GroupFolderType, SnappingState};
use crate::messages::input_mapper::utility_types::input_keyboard::Key; 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::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::document_metadata::LayerNodeIdentifier;
use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate, AlignAxis, FlipAxis, GridSnapping}; use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate, AlignAxis, FlipAxis, GridSnapping};
use crate::messages::portfolio::utility_types::PanelType; use crate::messages::portfolio::utility_types::PanelType;
@ -143,6 +144,7 @@ pub enum DocumentMessage {
}, },
SetOverlaysVisibility { SetOverlaysVisibility {
visible: bool, visible: bool,
overlays_type: Option<OverlaysType>,
}, },
SetRangeSelectionLayer { SetRangeSelectionLayer {
new_layer: Option<LayerNodeIdentifier>, new_layer: Option<LayerNodeIdentifier>,

View File

@ -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::graph_operation::utility_types::TransformIn;
use crate::messages::portfolio::document::node_graph::NodeGraphHandlerData; 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::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::properties_panel::utility_types::PropertiesPanelMessageHandlerData;
use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier}; use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate, AlignAxis, DocumentMode, FlipAxis, PTZ}; use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate, AlignAxis, DocumentMode, FlipAxis, PTZ};
@ -84,7 +85,7 @@ pub struct DocumentMessageHandler {
pub view_mode: ViewMode, pub view_mode: ViewMode,
/// Sets whether or not all the viewport overlays should be drawn on top of the artwork. /// 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. /// 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. /// Sets whether or not the rulers should be drawn along the top and left edges of the viewport area.
pub rulers_visible: bool, pub rulers_visible: bool,
/// The current user choices for snapping behavior, including whether snapping is enabled at all. /// 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_ptz: PTZ::default(),
document_mode: DocumentMode::DesignMode, document_mode: DocumentMode::DesignMode,
view_mode: ViewMode::default(), view_mode: ViewMode::default(),
overlays_visible: true, overlays_visibility_settings: OverlaysVisibilitySettings::default(),
rulers_visible: true, rulers_visible: true,
graph_view_overlay_open: false, graph_view_overlay_open: false,
snapping_state: SnappingState::default(), snapping_state: SnappingState::default(),
@ -199,12 +200,14 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
self.navigation_handler.process_message(message, responses, data); self.navigation_handler.process_message(message, responses, data);
} }
DocumentMessage::Overlays(message) => { 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( self.overlays_message_handler.process_message(
message, message,
responses, responses,
OverlaysMessageData { OverlaysMessageData {
overlays_visible, visibility_settings,
ipp, ipp,
device_pixel_ratio, device_pixel_ratio,
}, },
@ -347,6 +350,10 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
responses.add(FrontendMessage::UpdateDocumentLayerStructure { data_buffer }); responses.add(FrontendMessage::UpdateDocumentLayerStructure { data_buffer });
} }
DocumentMessage::DrawArtboardOverlays(overlay_context) => { DocumentMessage::DrawArtboardOverlays(overlay_context) => {
if !overlay_context.visibility_settings.artboard_name() {
return;
}
for layer in self.metadata().all_layers() { for layer in self.metadata().all_layers() {
if !self.network_interface.is_artboard(&layer.to_node(), &[]) { if !self.network_interface.is_artboard(&layer.to_node(), &[]) {
continue; continue;
@ -1016,6 +1023,10 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
} }
} }
DocumentMessage::SelectAllLayers => { DocumentMessage::SelectAllLayers => {
if !self.overlays_visibility_settings.selection_outline() {
return;
}
let metadata = self.metadata(); 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| { 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) 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 }); responses.add(GraphOperationMessage::OpacitySet { layer, opacity });
} }
} }
DocumentMessage::SetOverlaysVisibility { visible } => { DocumentMessage::SetOverlaysVisibility { visible, overlays_type } => {
self.overlays_visible = visible; 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(BroadcastEvent::ToolAbort);
responses.add(OverlaysMessage::Draw); responses.add(OverlaysMessage::Draw);
} }
@ -1238,7 +1275,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
responses.add(PortfolioMessage::UpdateDocumentWidgets); responses.add(PortfolioMessage::UpdateDocumentWidgets);
} }
DocumentMessage::ToggleOverlaysVisibility => { DocumentMessage::ToggleOverlaysVisibility => {
self.overlays_visible = !self.overlays_visible; self.overlays_visibility_settings.all = !self.overlays_visibility_settings.all();
responses.add(OverlaysMessage::Draw); responses.add(OverlaysMessage::Draw);
responses.add(PortfolioMessage::UpdateDocumentWidgets); responses.add(PortfolioMessage::UpdateDocumentWidgets);
} }
@ -1679,7 +1716,7 @@ impl DocumentMessageHandler {
pub view_mode: ViewMode, pub view_mode: ViewMode,
/// Sets whether or not all the viewport overlays should be drawn on top of the artwork. /// 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. /// 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. /// Sets whether or not the rulers should be drawn along the top and left edges of the viewport area.
pub rulers_visible: bool, 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. /// 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_ptz: old_message_handler.document_ptz,
document_mode: old_message_handler.document_mode, document_mode: old_message_handler.document_mode,
view_mode: old_message_handler.view_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, rulers_visible: old_message_handler.rulers_visible,
graph_view_overlay_open: old_message_handler.graph_view_overlay_open, graph_view_overlay_open: old_message_handler.graph_view_overlay_open,
snapping_state: old_message_handler.snapping_state, snapping_state: old_message_handler.snapping_state,
@ -2053,11 +2090,17 @@ impl DocumentMessageHandler {
.on_update(|_| AnimationMessage::ToggleLivePreview.into()) .on_update(|_| AnimationMessage::ToggleLivePreview.into())
.widget_holder(), .widget_holder(),
Separator::new(SeparatorType::Unrelated).widget_holder(), Separator::new(SeparatorType::Unrelated).widget_holder(),
CheckboxInput::new(self.overlays_visible) CheckboxInput::new(self.overlays_visibility_settings.all)
.icon("Overlays") .icon("Overlays")
.tooltip("Overlays") .tooltip("Overlays")
.tooltip_shortcut(action_keys!(DocumentMessageDiscriminant::ToggleOverlaysVisibility)) .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(), .widget_holder(),
PopoverButton::new() PopoverButton::new()
.popover_layout(vec![ .popover_layout(vec![
@ -2065,7 +2108,168 @@ impl DocumentMessageHandler {
widgets: vec![TextLabel::new("Overlays").bold(true).widget_holder()], widgets: vec![TextLabel::new("Overlays").bold(true).widget_holder()],
}, },
LayoutGroup::Row { 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(), .widget_holder(),
@ -2212,6 +2416,7 @@ impl DocumentMessageHandler {
layout: Layout::WidgetLayout(document_bar_layout), layout: Layout::WidgetLayout(document_bar_layout),
layout_target: LayoutTarget::DocumentBar, layout_target: LayoutTarget::DocumentBar,
}); });
responses.add(NodeGraphMessage::ForceRunDocumentGraph);
} }
pub fn update_layers_panel_control_bar_widgets(&self, responses: &mut VecDeque<Message>) { pub fn update_layers_panel_control_bar_widgets(&self, responses: &mut VecDeque<Message>) {
@ -2280,8 +2485,7 @@ impl DocumentMessageHandler {
.selected_layers(self.metadata()) .selected_layers(self.metadata())
.all(|layer| self.network_interface.is_locked(&layer.to_node(), &[])); .all(|layer| self.network_interface.is_locked(&layer.to_node(), &[]));
let layers_panel_control_bar = WidgetLayout::new(vec![LayoutGroup::Row { let widgets = vec![
widgets: vec![
DropdownInput::new(blend_mode_menu_entries) 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)) .selected_index(blend_mode.and_then(|blend_mode| blend_mode.index_in_list_svg_subset()).map(|index| index as u32))
.disabled(disabled) .disabled(disabled)
@ -2347,8 +2551,8 @@ impl DocumentMessageHandler {
.on_update(|_| DocumentMessage::ToggleSelectedVisibility.into()) .on_update(|_| DocumentMessage::ToggleSelectedVisibility.into())
.disabled(!has_selection) .disabled(!has_selection)
.widget_holder(), .widget_holder(),
], ];
}]); let layers_panel_control_bar = WidgetLayout::new(vec![LayoutGroup::Row { widgets }]);
responses.add(LayoutMessage::SendLayout { responses.add(LayoutMessage::SendLayout {
layout: Layout::WidgetLayout(layers_panel_control_bar), layout: Layout::WidgetLayout(layers_panel_control_bar),

View File

@ -1,8 +1,8 @@
use super::utility_types::OverlayProvider; use super::utility_types::{OverlayProvider, OverlaysVisibilitySettings};
use crate::messages::prelude::*; use crate::messages::prelude::*;
pub struct OverlaysMessageData<'a> { pub struct OverlaysMessageData<'a> {
pub overlays_visible: bool, pub visibility_settings: OverlaysVisibilitySettings,
pub ipp: &'a InputPreprocessorMessageHandler, pub ipp: &'a InputPreprocessorMessageHandler,
pub device_pixel_ratio: f64, pub device_pixel_ratio: f64,
} }
@ -18,7 +18,7 @@ pub struct OverlaysMessageHandler {
impl MessageHandler<OverlaysMessage, OverlaysMessageData<'_>> for OverlaysMessageHandler { impl MessageHandler<OverlaysMessage, OverlaysMessageData<'_>> for OverlaysMessageHandler {
fn process_message(&mut self, message: OverlaysMessage, responses: &mut VecDeque<Message>, data: OverlaysMessageData) { 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 { match message {
#[cfg(target_arch = "wasm32")] #[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()); context.clear_rect(0., 0., canvas.width().into(), canvas.height().into());
let _ = context.reset_transform(); let _ = context.reset_transform();
if overlays_visible { if visibility_settings.all() {
responses.add(DocumentMessage::GridOverlays(OverlayContext { responses.add(DocumentMessage::GridOverlays(OverlayContext {
render_context: context.clone(), render_context: context.clone(),
size: size.as_dvec2(), size: size.as_dvec2(),
device_pixel_ratio, device_pixel_ratio,
visibility_settings: visibility_settings.clone(),
})); }));
for provider in &self.overlay_providers { for provider in &self.overlay_providers {
responses.add(provider(OverlayContext { responses.add(provider(OverlayContext {
render_context: context.clone(), render_context: context.clone(),
size: size.as_dvec2(), size: size.as_dvec2(),
device_pixel_ratio, device_pixel_ratio,
visibility_settings: visibility_settings.clone(),
})); }));
} }
} }
} }
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
OverlaysMessage::Draw => { 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) => { OverlaysMessage::AddProvider(message) => {
self.overlay_providers.insert(message); self.overlay_providers.insert(message);

View File

@ -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, bezier: Bezier,
segment_id: SegmentId, segment_id: SegmentId,
(start, end): (PointId, PointId), (start, end): (PointId, PointId),
@ -112,14 +112,21 @@ pub fn overlay_bezier_handle_specific_point(
} }
pub fn path_overlays(document: &DocumentMessageHandler, draw_handles: DrawHandles, shape_editor: &mut ShapeState, overlay_context: &mut OverlayContext) { 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()) { 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 Some(vector_data) = document.network_interface.compute_modified_vector(layer) else { continue };
let transform = document.metadata().transform_to_viewport(layer); let transform = document.metadata().transform_to_viewport(layer);
if display_path {
overlay_context.outline_vector(&vector_data, transform); overlay_context.outline_vector(&vector_data, transform);
}
let selected = shape_editor.selected_shape_state.get(&layer); let selected = shape_editor.selected_shape_state.get(&layer);
let is_selected = |point: ManipulatorPointId| selected.is_some_and(|selected| selected.is_selected(point)); let is_selected = |point: ManipulatorPointId| selected.is_some_and(|selected| selected.is_selected(point));
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(); 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 { match draw_handles {
@ -157,14 +164,21 @@ pub fn path_overlays(document: &DocumentMessageHandler, draw_handles: DrawHandle
} }
DrawHandles::None => {} DrawHandles::None => {}
} }
}
if display_anchors {
for (&id, &position) in vector_data.point_domain.ids().iter().zip(vector_data.point_domain.positions()) { 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); 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) { 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()) { for layer in document.network_interface.selected_nodes().selected_layers(document.metadata()) {
let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else { let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else {
continue; continue;

View File

@ -21,6 +21,107 @@ pub fn empty_provider() -> OverlayProvider {
|_| Message::NoOp |_| 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)] #[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
pub struct OverlayContext { pub struct OverlayContext {
// Serde functionality isn't used but is required by the message system macros // 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. // 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. // 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 device_pixel_ratio: f64,
pub visibility_settings: OverlaysVisibilitySettings,
} }
// Message hashing isn't used but is required by the message system macros // Message hashing isn't used but is required by the message system macros
impl core::hash::Hash for OverlayContext { impl core::hash::Hash for OverlayContext {

View File

@ -19,6 +19,8 @@ pub struct Pivot {
pivot: Option<DVec2>, pivot: Option<DVec2>,
/// The old pivot position in the GUI, used to reduce refreshes of the document bar /// The old pivot position in the GUI, used to reduce refreshes of the document bar
old_pivot_position: ReferencePoint, old_pivot_position: ReferencePoint,
/// Used to enable and disable the pivot
active: bool,
} }
impl Default for Pivot { impl Default for Pivot {
@ -28,6 +30,7 @@ impl Default for Pivot {
transform_from_normalized: Default::default(), transform_from_normalized: Default::default(),
pivot: Default::default(), pivot: Default::default(),
old_pivot_position: ReferencePoint::Center, old_pivot_position: ReferencePoint::Center,
active: true,
} }
} }
} }
@ -44,6 +47,10 @@ impl Pivot {
/// Recomputes the pivot position and transform. /// Recomputes the pivot position and transform.
fn recalculate_pivot(&mut self, document: &DocumentMessageHandler) { fn recalculate_pivot(&mut self, document: &DocumentMessageHandler) {
if !self.active {
return;
}
let selected_nodes = document.network_interface.selected_nodes(); let selected_nodes = document.network_interface.selected_nodes();
let mut layers = selected_nodes.selected_visible_and_unlocked_layers(&document.network_interface); let mut layers = selected_nodes.selected_visible_and_unlocked_layers(&document.network_interface);
let Some(first) = layers.next() else { let Some(first) = layers.next() else {
@ -82,6 +89,13 @@ impl Pivot {
} }
pub fn update_pivot(&mut self, document: &DocumentMessageHandler, overlay_context: &mut OverlayContext, draw_data: Option<(f64,)>) { 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); self.recalculate_pivot(document);
if let (Some(pivot), Some(data)) = (self.pivot, draw_data) { if let (Some(pivot), Some(data)) = (self.pivot, draw_data) {
overlay_context.pivot(pivot, data.0); overlay_context.pivot(pivot, data.0);
@ -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). /// 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 { pub fn should_refresh_pivot_position(&mut self) -> bool {
if !self.active {
return false;
}
let new = self.to_pivot_position(); let new = self.to_pivot_position();
let should_refresh = new != self.old_pivot_position; let should_refresh = new != self.old_pivot_position;
self.old_pivot_position = new; self.old_pivot_position = new;
@ -102,6 +120,10 @@ impl Pivot {
/// Sets the viewport position of the pivot for all selected layers. /// 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>) { 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) { 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); let transform = Self::get_layer_pivot_transform(layer, document);
// Only update the pivot when computed position is finite. // Only update the pivot when computed position is finite.
@ -115,11 +137,18 @@ impl Pivot {
/// Set the pivot using the normalized transform that is set above. /// 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>) { 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); self.set_viewport_position(self.transform_from_normalized.transform_point2(position), document, responses);
} }
/// Answers if the pointer is currently positioned over the pivot. /// Answers if the pointer is currently positioned over the pivot.
pub fn is_over(&self, mouse: DVec2) -> bool { 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() self.pivot.filter(|&pivot| mouse.distance_squared(pivot) < (PIVOT_DIAMETER / 2.).powi(2)).is_some()
} }
} }

View File

@ -42,6 +42,8 @@ pub enum ManipulatorAngle {
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
pub struct SelectedLayerState { pub struct SelectedLayerState {
selected_points: HashSet<ManipulatorPointId>, selected_points: HashSet<ManipulatorPointId>,
ignore_handles: bool,
ignore_anchors: bool,
} }
impl SelectedLayerState { impl SelectedLayerState {
@ -52,12 +54,32 @@ impl SelectedLayerState {
self.selected_points.contains(&point) self.selected_points.contains(&point)
} }
pub fn select_point(&mut self, point: ManipulatorPointId) { pub fn select_point(&mut self, point: ManipulatorPointId) {
if (point.as_handle().is_some() && self.ignore_handles) || (point.as_anchor().is_some() && self.ignore_anchors) {
return;
}
self.selected_points.insert(point); self.selected_points.insert(point);
} }
pub fn deselect_point(&mut self, point: ManipulatorPointId) { pub fn deselect_point(&mut self, point: ManipulatorPointId) {
if (point.as_handle().is_some() && self.ignore_handles) || (point.as_anchor().is_some() && self.ignore_anchors) {
return;
}
self.selected_points.remove(&point); self.selected_points.remove(&point);
} }
pub fn set_handles_status(&mut self, ignore: bool) {
self.ignore_handles = ignore;
}
pub fn set_anchors_status(&mut self, ignore: bool) {
self.ignore_anchors = ignore;
}
pub fn clear_points_force(&mut self) {
self.selected_points.clear();
self.ignore_handles = false;
self.ignore_anchors = false;
}
pub fn clear_points(&mut self) { pub fn clear_points(&mut self) {
if self.ignore_handles || self.ignore_anchors {
return;
}
self.selected_points.clear(); self.selected_points.clear();
} }
pub fn selected_points_count(&self) -> usize { pub fn selected_points_count(&self) -> usize {
@ -524,6 +546,52 @@ impl ShapeState {
} }
} }
pub fn mark_selected_anchors(&mut self) {
for state in self.selected_shape_state.values_mut() {
state.set_anchors_status(false);
}
}
pub fn mark_selected_handles(&mut self) {
for state in self.selected_shape_state.values_mut() {
state.set_handles_status(false);
}
}
pub fn ignore_selected_anchors(&mut self) {
for state in self.selected_shape_state.values_mut() {
state.set_anchors_status(true);
}
}
pub fn ignore_selected_handles(&mut self) {
for state in self.selected_shape_state.values_mut() {
state.set_handles_status(true);
}
}
/// 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| selected_point.as_anchor().is_some()).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| selected_point.as_handle().is_some()).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. /// 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>) { pub fn set_selected_layers(&mut self, target_layers: Vec<LayerNodeIdentifier>) {
self.selected_shape_state.retain(|layer_path, _| target_layers.contains(layer_path)); self.selected_shape_state.retain(|layer_path, _| target_layers.contains(layer_path));
@ -632,7 +700,7 @@ impl ShapeState {
Some(()) Some(())
} }
/// Iterates over the selected manipulator groups exluding endpoints, returning whether their handles have mixed, colinear, or free angles. /// Iterates over the selected manipulator groups excluding endpoints, returning whether their handles have mixed, colinear, or free angles.
/// If there are no points selected this function returns mixed. /// If there are no points selected this function returns mixed.
pub fn selected_manipulator_angles(&self, network_interface: &NodeNetworkInterface) -> ManipulatorAngle { pub fn selected_manipulator_angles(&self, network_interface: &NodeNetworkInterface) -> ManipulatorAngle {
// This iterator contains a bool indicating whether or not selected points' manipulator groups have colinear handles. // This iterator contains a bool indicating whether or not selected points' manipulator groups have colinear handles.
@ -1495,7 +1563,7 @@ impl ShapeState {
pub fn select_all_in_shape(&mut self, network_interface: &NodeNetworkInterface, selection_shape: SelectionShape, selection_change: SelectionChange) { pub fn select_all_in_shape(&mut self, network_interface: &NodeNetworkInterface, selection_shape: SelectionShape, selection_change: SelectionChange) {
for (&layer, state) in &mut self.selected_shape_state { for (&layer, state) in &mut self.selected_shape_state {
if selection_change == SelectionChange::Clear { if selection_change == SelectionChange::Clear {
state.clear_points() state.clear_points_force()
} }
let vector_data = network_interface.compute_modified_vector(layer); let vector_data = network_interface.compute_modified_vector(layer);

View File

@ -225,17 +225,18 @@ impl Fsm for ArtboardToolFsmState {
let ToolMessage::Artboard(event) = event else { return self }; let ToolMessage::Artboard(event) = event else { return self };
match (self, event) { match (self, event) {
(state, ArtboardToolMessage::Overlays(mut overlay_context)) => { (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)) { 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()); let bounding_box_manager = tool_data.bounding_box_manager.get_or_insert(BoundingBoxManager::default());
bounding_box_manager.bounds = bounds; bounding_box_manager.bounds = bounds;
bounding_box_manager.transform = document.metadata().document_to_viewport; bounding_box_manager.transform = document.metadata().document_to_viewport;
bounding_box_manager.render_overlays(&mut overlay_context, true); bounding_box_manager.render_overlays(&mut overlay_context, true);
}
} else { } else {
tool_data.bounding_box_manager.take(); tool_data.bounding_box_manager.take();
} }
}
tool_data.snap_manager.draw_overlays(SnapData::new(document, input), &mut overlay_context); tool_data.snap_manager.draw_overlays(SnapData::new(document, input), &mut overlay_context);

View File

@ -960,6 +960,19 @@ impl Fsm for PathToolFsmState {
self self
} }
(_, PathToolMessage::Overlays(mut overlay_context)) => { (_, PathToolMessage::Overlays(mut overlay_context)) => {
let display_anchors = overlay_context.visibility_settings.anchors();
let display_handles = overlay_context.visibility_settings.handles();
if !display_handles {
shape_editor.ignore_selected_handles();
} else {
shape_editor.mark_selected_handles();
}
if !display_anchors {
shape_editor.ignore_selected_anchors();
} else {
shape_editor.mark_selected_anchors();
}
// TODO: find the segment ids of which the selected points are a part of // TODO: find the segment ids of which the selected points are a part of
match tool_options.path_overlay_mode { match tool_options.path_overlay_mode {

View File

@ -1497,6 +1497,9 @@ impl Fsm for PenToolFsmState {
self self
} }
(_, PenToolMessage::Overlays(mut overlay_context)) => { (_, PenToolMessage::Overlays(mut overlay_context)) => {
let display_anchors = overlay_context.visibility_settings.anchors();
let display_handles = overlay_context.visibility_settings.handles();
let valid = |point: DVec2, handle: DVec2| point.distance_squared(handle) >= HIDE_HANDLE_DISTANCE * HIDE_HANDLE_DISTANCE; let valid = |point: DVec2, handle: DVec2| point.distance_squared(handle) >= HIDE_HANDLE_DISTANCE * HIDE_HANDLE_DISTANCE;
let transform = document.metadata().document_to_viewport * transform; let transform = document.metadata().document_to_viewport * transform;
@ -1523,9 +1526,10 @@ impl Fsm for PenToolFsmState {
} }
} }
if display_handles {
// Draw the line between the currently-being-placed anchor and its currently-being-dragged-out outgoing handle (opposite the one currently being dragged out) // Draw the line between the currently-being-placed anchor and its currently-being-dragged-out outgoing handle (opposite the one currently being dragged out)
overlay_context.line(next_anchor, next_handle_start, None, None); overlay_context.line(next_anchor, next_handle_start, None, None);
}
match tool_options.pen_overlay_mode { match tool_options.pen_overlay_mode {
PenOverlayMode::AllHandles => { PenOverlayMode::AllHandles => {
path_overlays(document, DrawHandles::All, shape_editor, &mut overlay_context); path_overlays(document, DrawHandles::All, shape_editor, &mut overlay_context);
@ -1540,11 +1544,13 @@ impl Fsm for PenToolFsmState {
} }
if let (Some(anchor_start), Some(handle_start), Some(handle_end)) = (anchor_start, handle_start, handle_end) { if let (Some(anchor_start), Some(handle_start), Some(handle_end)) = (anchor_start, handle_start, handle_end) {
if display_handles {
// Draw the line between the most recently placed anchor and its outgoing handle (which is currently influencing the currently-being-placed segment) // Draw the line between the most recently placed anchor and its outgoing handle (which is currently influencing the currently-being-placed segment)
overlay_context.line(anchor_start, handle_start, None, None); overlay_context.line(anchor_start, handle_start, None, None);
// Draw the line between the currently-being-placed anchor and its incoming handle (opposite the one currently being dragged out) // Draw the line between the currently-being-placed anchor and its incoming handle (opposite the one currently being dragged out)
overlay_context.line(next_anchor, handle_end, None, None); overlay_context.line(next_anchor, handle_end, None, None);
}
if self == PenToolFsmState::PlacingAnchor && anchor_start != handle_start && tool_data.modifiers.lock_angle { if self == PenToolFsmState::PlacingAnchor && anchor_start != handle_start && tool_data.modifiers.lock_angle {
// Draw the line between the currently-being-placed anchor and last-placed point (lock angle bent overlays) // Draw the line between the currently-being-placed anchor and last-placed point (lock angle bent overlays)
@ -1556,13 +1562,16 @@ impl Fsm for PenToolFsmState {
overlay_context.dashed_line(anchor_start, next_anchor, None, None, Some(4.), Some(4.), Some(0.5)); overlay_context.dashed_line(anchor_start, next_anchor, None, None, Some(4.), Some(4.), Some(0.5));
} }
if self == PenToolFsmState::DraggingHandle(tool_data.handle_mode) && valid(next_anchor, handle_end) { if self == PenToolFsmState::DraggingHandle(tool_data.handle_mode) && valid(next_anchor, handle_end) && display_handles {
// Draw the handle circle for the currently-being-dragged-out incoming handle (opposite the one currently being dragged out) // Draw the handle circle for the currently-being-dragged-out incoming handle (opposite the one currently being dragged out)
let selected = tool_data.handle_type == TargetHandle::PreviewInHandle; let selected = tool_data.handle_type == TargetHandle::PreviewInHandle;
if display_handles {
overlay_context.manipulator_handle(handle_end, selected, None);
overlay_context.manipulator_handle(handle_end, selected, None); overlay_context.manipulator_handle(handle_end, selected, None);
} }
}
if valid(anchor_start, handle_start) { if valid(anchor_start, handle_start) && display_handles {
// Draw the handle circle for the most recently placed anchor's outgoing handle (which is currently influencing the currently-being-placed segment) // Draw the handle circle for the most recently placed anchor's outgoing handle (which is currently influencing the currently-being-placed segment)
overlay_context.manipulator_handle(handle_start, false, None); overlay_context.manipulator_handle(handle_start, false, None);
} }
@ -1578,13 +1587,13 @@ impl Fsm for PenToolFsmState {
} }
} }
if self == PenToolFsmState::DraggingHandle(tool_data.handle_mode) && valid(next_anchor, next_handle_start) { if self == PenToolFsmState::DraggingHandle(tool_data.handle_mode) && valid(next_anchor, next_handle_start) && display_handles {
// Draw the handle circle for the currently-being-dragged-out outgoing handle (the one currently being dragged out, under the user's cursor) // Draw the handle circle for the currently-being-dragged-out outgoing handle (the one currently being dragged out, under the user's cursor)
let selected = tool_data.handle_type == TargetHandle::FuturePreviewOutHandle; let selected = tool_data.handle_type == TargetHandle::FuturePreviewOutHandle;
overlay_context.manipulator_handle(next_handle_start, selected, None); overlay_context.manipulator_handle(next_handle_start, selected, None);
} }
if self == PenToolFsmState::DraggingHandle(tool_data.handle_mode) { if self == PenToolFsmState::DraggingHandle(tool_data.handle_mode) && display_anchors {
// Draw the anchor square for the most recently placed anchor // Draw the anchor square for the most recently placed anchor
overlay_context.manipulator_anchor(next_anchor, false, None); overlay_context.manipulator_anchor(next_anchor, false, None);
} }

View File

@ -516,6 +516,7 @@ impl Fsm for SelectToolFsmState {
tool_data.selected_layers_count = selected_layers_count; tool_data.selected_layers_count = selected_layers_count;
// Outline selected layers, but not artboards // Outline selected layers, but not artboards
if overlay_context.visibility_settings.selection_outline() {
for layer in document for layer in document
.network_interface .network_interface
.selected_nodes() .selected_nodes()
@ -529,6 +530,7 @@ impl Fsm for SelectToolFsmState {
overlay_context.dashed_quad(transformed_quad, None, Some(7.), Some(5.), None); overlay_context.dashed_quad(transformed_quad, None, Some(7.), Some(5.), None);
} }
} }
}
// Update bounds // Update bounds
let mut transform = document let mut transform = document
@ -566,11 +568,13 @@ impl Fsm for SelectToolFsmState {
let click = document.click(input); let click = document.click(input);
let not_selected_click = click.filter(|&hovered_layer| !document.network_interface.selected_nodes().selected_layers_contains(hovered_layer, document.metadata())); let not_selected_click = click.filter(|&hovered_layer| !document.network_interface.selected_nodes().selected_layers_contains(hovered_layer, document.metadata()));
if let Some(layer) = not_selected_click { if let Some(layer) = not_selected_click {
if overlay_context.visibility_settings.hover_outline() {
overlay_context.outline(document.metadata().layer_outline(layer), document.metadata().transform_to_viewport(layer)); overlay_context.outline(document.metadata().layer_outline(layer), document.metadata().transform_to_viewport(layer));
}
// Measure with Alt held down // Measure with Alt held down
// TODO: Don't use `Key::Alt` directly, instead take it as a variable from the input mappings list like in all other places // TODO: Don't use `Key::Alt` directly, instead take it as a variable from the input mappings list like in all other places
if !matches!(self, Self::ResizingBounds { .. }) && input.keyboard.get(Key::Alt as usize) { if overlay_context.visibility_settings.quick_measurement() && !matches!(self, Self::ResizingBounds { .. }) && input.keyboard.get(Key::Alt as usize) {
// Get all selected layers and compute their viewport-aligned AABB // Get all selected layers and compute their viewport-aligned AABB
let selected_bounds_viewport = document let selected_bounds_viewport = document
.network_interface .network_interface
@ -602,6 +606,7 @@ impl Fsm for SelectToolFsmState {
} }
} }
if overlay_context.visibility_settings.transform_cage() {
if let Some(bounds) = bounds { if let Some(bounds) = bounds {
let bounding_box_manager = tool_data.bounding_box_manager.get_or_insert(BoundingBoxManager::default()); let bounding_box_manager = tool_data.bounding_box_manager.get_or_insert(BoundingBoxManager::default());
@ -609,6 +614,7 @@ impl Fsm for SelectToolFsmState {
bounding_box_manager.transform = transform; bounding_box_manager.transform = transform;
bounding_box_manager.transform_tampered = transform_tampered; bounding_box_manager.transform_tampered = transform_tampered;
bounding_box_manager.render_overlays(&mut overlay_context, true); bounding_box_manager.render_overlays(&mut overlay_context, true);
}
} else { } else {
tool_data.bounding_box_manager.take(); tool_data.bounding_box_manager.take();
} }
@ -673,11 +679,13 @@ impl Fsm for SelectToolFsmState {
tool_data.pivot.update_pivot(document, &mut overlay_context, Some((angle,))); tool_data.pivot.update_pivot(document, &mut overlay_context, Some((angle,)));
// Update compass rose // Update compass rose
if overlay_context.visibility_settings.compass_rose() {
tool_data.compass_rose.refresh_position(document); tool_data.compass_rose.refresh_position(document);
let compass_center = tool_data.compass_rose.compass_rose_position(); let compass_center = tool_data.compass_rose.compass_rose_position();
if !matches!(self, Self::Dragging { .. }) { if !matches!(self, Self::Dragging { .. }) {
tool_data.line_center = compass_center; tool_data.line_center = compass_center;
} }
overlay_context.compass_rose(compass_center, angle, show_compass_with_ring); overlay_context.compass_rose(compass_center, angle, show_compass_with_ring);
let axis_state = if let SelectToolFsmState::Dragging { axis, .. } = self { let axis_state = if let SelectToolFsmState::Dragging { axis, .. } = self {
@ -739,6 +747,7 @@ impl Fsm for SelectToolFsmState {
overlay_context.line(origin - edge * viewport_diagonal, origin + edge * viewport_diagonal, Some(edge_color), None); overlay_context.line(origin - edge * viewport_diagonal, origin + edge * viewport_diagonal, Some(edge_color), None);
overlay_context.line(origin - perp * viewport_diagonal, origin + perp * viewport_diagonal, Some(perp_color), None); overlay_context.line(origin - perp * viewport_diagonal, origin + perp * viewport_diagonal, Some(perp_color), None);
} }
}
// Check if the tool is in selection mode // Check if the tool is in selection mode
if let Self::Drawing { selection_shape, .. } = self { if let Self::Drawing { selection_shape, .. } = self {
@ -768,9 +777,12 @@ impl Fsm for SelectToolFsmState {
SelectionMode::Directional => unreachable!(), SelectionMode::Directional => unreachable!(),
}); });
if overlay_context.visibility_settings.selection_outline() {
// Draws a temporary outline on the layers that will be selected by the current box/lasso area
for layer in layers_to_outline { for layer in layers_to_outline {
overlay_context.outline(document.metadata().layer_outline(layer), document.metadata().transform_to_viewport(layer)); overlay_context.outline(document.metadata().layer_outline(layer), document.metadata().transform_to_viewport(layer));
} }
}
// Update the selection box // Update the selection box
let mut fill_color = graphene_std::Color::from_rgb_str(COLOR_OVERLAY_BLUE.strip_prefix('#').unwrap()) let mut fill_color = graphene_std::Color::from_rgb_str(COLOR_OVERLAY_BLUE.strip_prefix('#').unwrap())
@ -854,7 +866,7 @@ impl Fsm for SelectToolFsmState {
let is_over_pivot = tool_data.pivot.is_over(mouse_position); let is_over_pivot = tool_data.pivot.is_over(mouse_position);
let show_compass = bounds.is_some_and(|quad| quad.all_sides_at_least_width(COMPASS_ROSE_HOVER_RING_DIAMETER) && quad.contains(mouse_position)); let show_compass = bounds.is_some_and(|quad| quad.all_sides_at_least_width(COMPASS_ROSE_HOVER_RING_DIAMETER) && quad.contains(mouse_position));
let can_grab_compass_rose = compass_rose_state.can_grab() && show_compass; let can_grab_compass_rose = compass_rose_state.can_grab() && (show_compass || bounds.is_none());
let is_flat_layer = tool_data let is_flat_layer = tool_data
.bounding_box_manager .bounding_box_manager
.as_ref() .as_ref()

View File

@ -506,6 +506,7 @@ impl Fsm for TextToolFsmState {
return self; return self;
} }
if overlay_context.visibility_settings.transform_cage() {
if let Some(bounds) = bounds { if let Some(bounds) = bounds {
let bounding_box_manager = tool_data.bounding_box_manager.get_or_insert(BoundingBoxManager::default()); let bounding_box_manager = tool_data.bounding_box_manager.get_or_insert(BoundingBoxManager::default());
bounding_box_manager.bounds = [bounds.0[0], bounds.0[2]]; bounding_box_manager.bounds = [bounds.0[0], bounds.0[2]];
@ -523,6 +524,7 @@ impl Fsm for TextToolFsmState {
bounding_box_manager.render_overlays(&mut overlay_context, false); bounding_box_manager.render_overlays(&mut overlay_context, false);
tool_data.pivot.update_pivot(document, &mut overlay_context, None); tool_data.pivot.update_pivot(document, &mut overlay_context, None);
}
} else { } else {
tool_data.bounding_box_manager.take(); tool_data.bounding_box_manager.take();
} }

View File

@ -209,6 +209,10 @@ impl MessageHandler<TransformLayerMessage, TransformData<'_>> for TransformLayer
match message { match message {
// Overlays // Overlays
TransformLayerMessage::Overlays(mut overlay_context) => { TransformLayerMessage::Overlays(mut overlay_context) => {
if !overlay_context.visibility_settings.transform_measurement() {
return;
}
for layer in document.metadata().all_layers() { for layer in document.metadata().all_layers() {
if !document.network_interface.is_artboard(&layer.to_node(), &[]) { if !document.network_interface.is_artboard(&layer.to_node(), &[]) {
continue; continue;