diff --git a/editor/src/messages/portfolio/document/document_message.rs b/editor/src/messages/portfolio/document/document_message.rs index 9470392893..f6d2f1470a 100644 --- a/editor/src/messages/portfolio/document/document_message.rs +++ b/editor/src/messages/portfolio/document/document_message.rs @@ -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, }, SetRangeSelectionLayer { new_layer: Option, diff --git a/editor/src/messages/portfolio/document/document_message_handler.rs b/editor/src/messages/portfolio/document/document_message_handler.rs index 61bccb3971..d324c4931d 100644 --- a/editor/src/messages/portfolio/document/document_message_handler.rs +++ b/editor/src/messages/portfolio/document/document_message_handler.rs @@ -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> 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> 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> 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> 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> 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) { @@ -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), diff --git a/editor/src/messages/portfolio/document/overlays/overlays_message_handler.rs b/editor/src/messages/portfolio/document/overlays/overlays_message_handler.rs index 0a6f0b6902..97daaa91ea 100644 --- a/editor/src/messages/portfolio/document/overlays/overlays_message_handler.rs +++ b/editor/src/messages/portfolio/document/overlays/overlays_message_handler.rs @@ -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> for OverlaysMessageHandler { fn process_message(&mut self, message: OverlaysMessage, responses: &mut VecDeque, 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> 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); diff --git a/editor/src/messages/portfolio/document/overlays/utility_functions.rs b/editor/src/messages/portfolio/document/overlays/utility_functions.rs index a6a77b1726..6f2b1aef6a 100644 --- a/editor/src/messages/portfolio/document/overlays/utility_functions.rs +++ b/editor/src/messages/portfolio/document/overlays/utility_functions.rs @@ -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); }); - - 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; diff --git a/editor/src/messages/portfolio/document/overlays/utility_types.rs b/editor/src/messages/portfolio/document/overlays/utility_types.rs index 25e8a32b77..c7d2bd70da 100644 --- a/editor/src/messages/portfolio/document/overlays/utility_types.rs +++ b/editor/src/messages/portfolio/document/overlays/utility_types.rs @@ -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 { diff --git a/editor/src/messages/tool/common_functionality/pivot.rs b/editor/src/messages/tool/common_functionality/pivot.rs index 3abe180f80..67ce7f5dc5 100644 --- a/editor/src/messages/tool/common_functionality/pivot.rs +++ b/editor/src/messages/tool/common_functionality/pivot.rs @@ -19,6 +19,8 @@ pub struct Pivot { pivot: Option, /// The old pivot position in the GUI, used to reduce refreshes of the document bar old_pivot_position: ReferencePoint, + /// Used to enable and disable the pivot + active: bool, } impl Default for Pivot { @@ -28,6 +30,7 @@ impl Default for Pivot { transform_from_normalized: Default::default(), pivot: Default::default(), old_pivot_position: ReferencePoint::Center, + active: true, } } } @@ -44,6 +47,10 @@ impl Pivot { /// Recomputes the pivot position and transform. fn recalculate_pivot(&mut self, document: &DocumentMessageHandler) { + if !self.active { + return; + } + let selected_nodes = document.network_interface.selected_nodes(); let mut layers = selected_nodes.selected_visible_and_unlocked_layers(&document.network_interface); let Some(first) = layers.next() else { @@ -82,6 +89,13 @@ impl Pivot { } pub fn update_pivot(&mut self, document: &DocumentMessageHandler, overlay_context: &mut OverlayContext, draw_data: Option<(f64,)>) { + if !overlay_context.visibility_settings.pivot() { + self.active = false; + return; + } else { + self.active = true; + } + self.recalculate_pivot(document); if let (Some(pivot), Some(data)) = (self.pivot, draw_data) { overlay_context.pivot(pivot, data.0); @@ -90,6 +104,10 @@ impl Pivot { /// Answers if the pivot widget has changed (so we should refresh the tool bar at the top of the canvas). pub fn should_refresh_pivot_position(&mut self) -> bool { + if !self.active { + return false; + } + let new = self.to_pivot_position(); let should_refresh = new != self.old_pivot_position; self.old_pivot_position = new; @@ -102,6 +120,10 @@ impl Pivot { /// Sets the viewport position of the pivot for all selected layers. pub fn set_viewport_position(&self, position: DVec2, document: &DocumentMessageHandler, responses: &mut VecDeque) { + if !self.active { + return; + } + for layer in document.network_interface.selected_nodes().selected_visible_and_unlocked_layers(&document.network_interface) { let transform = Self::get_layer_pivot_transform(layer, document); // Only update the pivot when computed position is finite. @@ -115,11 +137,18 @@ impl Pivot { /// Set the pivot using the normalized transform that is set above. pub fn set_normalized_position(&self, position: DVec2, document: &DocumentMessageHandler, responses: &mut VecDeque) { + if !self.active { + return; + } + self.set_viewport_position(self.transform_from_normalized.transform_point2(position), document, responses); } /// Answers if the pointer is currently positioned over the pivot. pub fn is_over(&self, mouse: DVec2) -> bool { + if !self.active { + return false; + } self.pivot.filter(|&pivot| mouse.distance_squared(pivot) < (PIVOT_DIAMETER / 2.).powi(2)).is_some() } } diff --git a/editor/src/messages/tool/common_functionality/shape_editor.rs b/editor/src/messages/tool/common_functionality/shape_editor.rs index 53faeeeb29..7fa761acdc 100644 --- a/editor/src/messages/tool/common_functionality/shape_editor.rs +++ b/editor/src/messages/tool/common_functionality/shape_editor.rs @@ -42,6 +42,8 @@ pub enum ManipulatorAngle { #[derive(Clone, Debug, Default)] pub struct SelectedLayerState { selected_points: HashSet, + ignore_handles: bool, + ignore_anchors: bool, } impl SelectedLayerState { @@ -52,12 +54,32 @@ impl SelectedLayerState { self.selected_points.contains(&point) } 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); } 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); } + 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) { + if self.ignore_handles || self.ignore_anchors { + return; + } self.selected_points.clear(); } 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 = 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 = 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. pub fn set_selected_layers(&mut self, target_layers: Vec) { self.selected_shape_state.retain(|layer_path, _| target_layers.contains(layer_path)); @@ -632,7 +700,7 @@ impl ShapeState { 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. 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. @@ -1495,7 +1563,7 @@ impl ShapeState { 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 { if selection_change == SelectionChange::Clear { - state.clear_points() + state.clear_points_force() } let vector_data = network_interface.compute_modified_vector(layer); diff --git a/editor/src/messages/tool/tool_messages/artboard_tool.rs b/editor/src/messages/tool/tool_messages/artboard_tool.rs index 0c9b0f7fb9..8b2e685ae1 100644 --- a/editor/src/messages/tool/tool_messages/artboard_tool.rs +++ b/editor/src/messages/tool/tool_messages/artboard_tool.rs @@ -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); diff --git a/editor/src/messages/tool/tool_messages/path_tool.rs b/editor/src/messages/tool/tool_messages/path_tool.rs index 85e28dbdef..30a05ed198 100644 --- a/editor/src/messages/tool/tool_messages/path_tool.rs +++ b/editor/src/messages/tool/tool_messages/path_tool.rs @@ -960,6 +960,19 @@ impl Fsm for PathToolFsmState { self } (_, 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 match tool_options.path_overlay_mode { diff --git a/editor/src/messages/tool/tool_messages/pen_tool.rs b/editor/src/messages/tool/tool_messages/pen_tool.rs index a7e4eebed3..f0ceb00b9c 100644 --- a/editor/src/messages/tool/tool_messages/pen_tool.rs +++ b/editor/src/messages/tool/tool_messages/pen_tool.rs @@ -1497,6 +1497,9 @@ impl Fsm for PenToolFsmState { self } (_, 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 transform = document.metadata().document_to_viewport * transform; @@ -1523,9 +1526,10 @@ impl Fsm for PenToolFsmState { } } - // 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); - + 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) + overlay_context.line(next_anchor, next_handle_start, None, None); + } match tool_options.pen_overlay_mode { PenOverlayMode::AllHandles => { 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) { - // 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); + 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) + 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) - overlay_context.line(next_anchor, handle_end, None, None); + // 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); + } 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) @@ -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)); } - 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) let selected = tool_data.handle_type == TargetHandle::PreviewInHandle; - overlay_context.manipulator_handle(handle_end, selected, None); + if display_handles { + 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) 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) let selected = tool_data.handle_type == TargetHandle::FuturePreviewOutHandle; 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 overlay_context.manipulator_anchor(next_anchor, false, None); } diff --git a/editor/src/messages/tool/tool_messages/select_tool.rs b/editor/src/messages/tool/tool_messages/select_tool.rs index a6f6eb63e2..e92980d27c 100644 --- a/editor/src/messages/tool/tool_messages/select_tool.rs +++ b/editor/src/messages/tool/tool_messages/select_tool.rs @@ -516,17 +516,19 @@ impl Fsm for SelectToolFsmState { tool_data.selected_layers_count = selected_layers_count; // Outline selected layers, but not artboards - for layer in document - .network_interface - .selected_nodes() - .selected_visible_and_unlocked_layers(&document.network_interface) - .filter(|layer| !document.network_interface.is_artboard(&layer.to_node(), &[])) - { - overlay_context.outline(document.metadata().layer_outline(layer), document.metadata().transform_to_viewport(layer)); + if overlay_context.visibility_settings.selection_outline() { + for layer in document + .network_interface + .selected_nodes() + .selected_visible_and_unlocked_layers(&document.network_interface) + .filter(|layer| !document.network_interface.is_artboard(&layer.to_node(), &[])) + { + overlay_context.outline(document.metadata().layer_outline(layer), document.metadata().transform_to_viewport(layer)); - if is_layer_fed_by_node_of_name(layer, &document.network_interface, "Text") { - let transformed_quad = document.metadata().transform_to_viewport(layer) * text_bounding_box(layer, document, font_cache); - overlay_context.dashed_quad(transformed_quad, None, Some(7.), Some(5.), None); + if is_layer_fed_by_node_of_name(layer, &document.network_interface, "Text") { + let transformed_quad = document.metadata().transform_to_viewport(layer) * text_bounding_box(layer, document, font_cache); + overlay_context.dashed_quad(transformed_quad, None, Some(7.), Some(5.), None); + } } } @@ -566,11 +568,13 @@ impl Fsm for SelectToolFsmState { 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())); if let Some(layer) = not_selected_click { - overlay_context.outline(document.metadata().layer_outline(layer), document.metadata().transform_to_viewport(layer)); + if overlay_context.visibility_settings.hover_outline() { + overlay_context.outline(document.metadata().layer_outline(layer), document.metadata().transform_to_viewport(layer)); + } // 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 - 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 let selected_bounds_viewport = document .network_interface @@ -602,13 +606,15 @@ impl Fsm for SelectToolFsmState { } } - if let Some(bounds) = bounds { - let bounding_box_manager = tool_data.bounding_box_manager.get_or_insert(BoundingBoxManager::default()); + if overlay_context.visibility_settings.transform_cage() { + if let Some(bounds) = bounds { + let bounding_box_manager = tool_data.bounding_box_manager.get_or_insert(BoundingBoxManager::default()); - bounding_box_manager.bounds = bounds; - bounding_box_manager.transform = transform; - bounding_box_manager.transform_tampered = transform_tampered; - bounding_box_manager.render_overlays(&mut overlay_context, true); + bounding_box_manager.bounds = bounds; + bounding_box_manager.transform = transform; + bounding_box_manager.transform_tampered = transform_tampered; + bounding_box_manager.render_overlays(&mut overlay_context, true); + } } else { tool_data.bounding_box_manager.take(); } @@ -673,71 +679,74 @@ impl Fsm for SelectToolFsmState { tool_data.pivot.update_pivot(document, &mut overlay_context, Some((angle,))); // Update compass rose - tool_data.compass_rose.refresh_position(document); - let compass_center = tool_data.compass_rose.compass_rose_position(); - if !matches!(self, Self::Dragging { .. }) { - tool_data.line_center = compass_center; - } - overlay_context.compass_rose(compass_center, angle, show_compass_with_ring); + if overlay_context.visibility_settings.compass_rose() { + tool_data.compass_rose.refresh_position(document); + let compass_center = tool_data.compass_rose.compass_rose_position(); + if !matches!(self, Self::Dragging { .. }) { + tool_data.line_center = compass_center; + } - let axis_state = if let SelectToolFsmState::Dragging { axis, .. } = self { - Some((axis, false)) - } else { - compass_rose_state.axis_type().and_then(|axis| axis.is_constraint().then_some((axis, true))) - }; + overlay_context.compass_rose(compass_center, angle, show_compass_with_ring); - if show_compass_with_ring.is_some() { - if let Some((axis, hover)) = axis_state { - if axis.is_constraint() { - let e0 = tool_data - .bounding_box_manager - .as_ref() - .map(|bounding_box_manager| bounding_box_manager.transform * Quad::from_box(bounding_box_manager.bounds)) - .map_or(DVec2::X, |quad| (quad.top_left() - quad.top_right()).normalize_or(DVec2::X)); - - let (direction, color) = match axis { - Axis::X => (e0, COLOR_OVERLAY_RED), - Axis::Y => (e0.perp(), COLOR_OVERLAY_GREEN), - _ => unreachable!(), - }; - - let viewport_diagonal = input.viewport_bounds.size().length(); - - let color = if !hover { - color - } else { - let color_string = &graphene_std::Color::from_rgb_str(color.strip_prefix('#').unwrap()).unwrap().with_alpha(0.25).to_rgba_hex_srgb(); - &format!("#{}", color_string) - }; - let line_center = tool_data.line_center; - overlay_context.line(line_center - direction * viewport_diagonal, line_center + direction * viewport_diagonal, Some(color), None); + let axis_state = if let SelectToolFsmState::Dragging { axis, .. } = self { + Some((axis, false)) + } else { + compass_rose_state.axis_type().and_then(|axis| axis.is_constraint().then_some((axis, true))) + }; + + if show_compass_with_ring.is_some() { + if let Some((axis, hover)) = axis_state { + if axis.is_constraint() { + let e0 = tool_data + .bounding_box_manager + .as_ref() + .map(|bounding_box_manager| bounding_box_manager.transform * Quad::from_box(bounding_box_manager.bounds)) + .map_or(DVec2::X, |quad| (quad.top_left() - quad.top_right()).normalize_or(DVec2::X)); + + let (direction, color) = match axis { + Axis::X => (e0, COLOR_OVERLAY_RED), + Axis::Y => (e0.perp(), COLOR_OVERLAY_GREEN), + _ => unreachable!(), + }; + + let viewport_diagonal = input.viewport_bounds.size().length(); + + let color = if !hover { + color + } else { + let color_string = &graphene_std::Color::from_rgb_str(color.strip_prefix('#').unwrap()).unwrap().with_alpha(0.25).to_rgba_hex_srgb(); + &format!("#{}", color_string) + }; + let line_center = tool_data.line_center; + overlay_context.line(line_center - direction * viewport_diagonal, line_center + direction * viewport_diagonal, Some(color), None); + } } } - } - if axis_state.is_none_or(|(axis, _)| !axis.is_constraint()) && tool_data.axis_align { - let mouse_position = mouse_position - tool_data.drag_start; - let snap_resolution = SELECTION_DRAG_ANGLE.to_radians(); - let angle = -mouse_position.angle_to(DVec2::X); - let snapped_angle = (angle / snap_resolution).round() * snap_resolution; + if axis_state.is_none_or(|(axis, _)| !axis.is_constraint()) && tool_data.axis_align { + let mouse_position = mouse_position - tool_data.drag_start; + let snap_resolution = SELECTION_DRAG_ANGLE.to_radians(); + let angle = -mouse_position.angle_to(DVec2::X); + let snapped_angle = (angle / snap_resolution).round() * snap_resolution; - let extension = tool_data.drag_current - tool_data.drag_start; - let origin = compass_center - extension; - let viewport_diagonal = input.viewport_bounds.size().length(); + let extension = tool_data.drag_current - tool_data.drag_start; + let origin = compass_center - extension; + let viewport_diagonal = input.viewport_bounds.size().length(); - let edge = DVec2::from_angle(snapped_angle).normalize_or(DVec2::X) * viewport_diagonal; - let perp = edge.perp(); + let edge = DVec2::from_angle(snapped_angle).normalize_or(DVec2::X) * viewport_diagonal; + let perp = edge.perp(); - let (edge_color, perp_color) = if edge.x.abs() > edge.y.abs() { - (COLOR_OVERLAY_RED, COLOR_OVERLAY_GREEN) - } else { - (COLOR_OVERLAY_GREEN, COLOR_OVERLAY_RED) - }; - let mut perp_color = graphene_std::Color::from_rgb_str(perp_color.strip_prefix('#').unwrap()).unwrap().with_alpha(0.25).to_rgba_hex_srgb(); - perp_color.insert(0, '#'); - let perp_color = perp_color.as_str(); - 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); + let (edge_color, perp_color) = if edge.x.abs() > edge.y.abs() { + (COLOR_OVERLAY_RED, COLOR_OVERLAY_GREEN) + } else { + (COLOR_OVERLAY_GREEN, COLOR_OVERLAY_RED) + }; + let mut perp_color = graphene_std::Color::from_rgb_str(perp_color.strip_prefix('#').unwrap()).unwrap().with_alpha(0.25).to_rgba_hex_srgb(); + perp_color.insert(0, '#'); + let perp_color = perp_color.as_str(); + 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); + } } // Check if the tool is in selection mode @@ -768,8 +777,11 @@ impl Fsm for SelectToolFsmState { SelectionMode::Directional => unreachable!(), }); - for layer in layers_to_outline { - overlay_context.outline(document.metadata().layer_outline(layer), document.metadata().transform_to_viewport(layer)); + 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 { + overlay_context.outline(document.metadata().layer_outline(layer), document.metadata().transform_to_viewport(layer)); + } } // Update the selection box @@ -854,7 +866,7 @@ impl Fsm for SelectToolFsmState { 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 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 .bounding_box_manager .as_ref() diff --git a/editor/src/messages/tool/tool_messages/text_tool.rs b/editor/src/messages/tool/tool_messages/text_tool.rs index a503fbae8f..40c5469aae 100644 --- a/editor/src/messages/tool/tool_messages/text_tool.rs +++ b/editor/src/messages/tool/tool_messages/text_tool.rs @@ -506,23 +506,25 @@ impl Fsm for TextToolFsmState { return self; } - if let Some(bounds) = bounds { - 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.transform = layer_transform; - - bounding_box_manager.render_quad(&mut overlay_context); - // Draw red overlay if text is clipped - let transformed_quad = layer_transform * bounds; - if let Some((text, font, typesetting)) = graph_modification_utils::get_text(layer.unwrap(), &document.network_interface) { - let buzz_face = font_cache.get(font).map(|data| load_face(data)); - if lines_clipping(text.as_str(), buzz_face, typesetting) { - overlay_context.line(transformed_quad.0[2], transformed_quad.0[3], Some(COLOR_OVERLAY_RED), Some(3.)); + if overlay_context.visibility_settings.transform_cage() { + if let Some(bounds) = bounds { + 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.transform = layer_transform; + + bounding_box_manager.render_quad(&mut overlay_context); + // Draw red overlay if text is clipped + let transformed_quad = layer_transform * bounds; + if let Some((text, font, typesetting)) = graph_modification_utils::get_text(layer.unwrap(), &document.network_interface) { + let buzz_face = font_cache.get(font).map(|data| load_face(data)); + if lines_clipping(text.as_str(), buzz_face, typesetting) { + overlay_context.line(transformed_quad.0[2], transformed_quad.0[3], Some(COLOR_OVERLAY_RED), Some(3.)); + } } - } - bounding_box_manager.render_overlays(&mut overlay_context, false); - tool_data.pivot.update_pivot(document, &mut overlay_context, None); + bounding_box_manager.render_overlays(&mut overlay_context, false); + tool_data.pivot.update_pivot(document, &mut overlay_context, None); + } } else { tool_data.bounding_box_manager.take(); } diff --git a/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs b/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs index e8487ce7fb..0e44efabca 100644 --- a/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs +++ b/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs @@ -209,6 +209,10 @@ impl MessageHandler> for TransformLayer match message { // Overlays TransformLayerMessage::Overlays(mut overlay_context) => { + if !overlay_context.visibility_settings.transform_measurement() { + return; + } + for layer in document.metadata().all_layers() { if !document.network_interface.is_artboard(&layer.to_node(), &[]) { continue; diff --git a/editor/src/messages/tool/utility_types.rs b/editor/src/messages/tool/utility_types.rs index 2fca871cdc..88a1a8423d 100644 --- a/editor/src/messages/tool/utility_types.rs +++ b/editor/src/messages/tool/utility_types.rs @@ -240,9 +240,9 @@ impl LayoutHolder for ToolData { let separator = std::iter::once(Separator::new(SeparatorType::Section).direction(SeparatorDirection::Vertical).widget_holder()); let buttons = group.into_iter().map(|ToolEntry { tooltip, tooltip_shortcut, tool_type, icon_name }| { IconButton::new(icon_name, 32) - .disabled( false) - .active( self.active_tool_type == tool_type) - .tooltip( tooltip.clone()) + .disabled(false) + .active(self.active_tool_type == tool_type) + .tooltip(tooltip.clone()) .tooltip_shortcut(tooltip_shortcut) .on_update(move |_| { if !tooltip.contains("Coming Soon") { diff --git a/libraries/math-parser/benches/bench.rs b/libraries/math-parser/benches/bench.rs index a1952bc2fa..fd1824c9a0 100644 --- a/libraries/math-parser/benches/bench.rs +++ b/libraries/math-parser/benches/bench.rs @@ -3,47 +3,47 @@ use math_parser::ast; use math_parser::context::EvalContext; macro_rules! generate_benchmarks { - ($( $input:expr_2021 ),* $(,)?) => { - fn parsing_bench(c: &mut Criterion) { - $( - c.bench_function(concat!("parse ", $input), |b| { - b.iter(|| { - let _ = black_box(ast::Node::try_parse_from_str($input)).unwrap(); - }); - }); - )* - } + ($( $input:expr_2021 ),* $(,)?) => { + fn parsing_bench(c: &mut Criterion) { + $( + c.bench_function(concat!("parse ", $input), |b| { + b.iter(|| { + let _ = black_box(ast::Node::try_parse_from_str($input)).unwrap(); + }); + }); + )* + } - fn evaluation_bench(c: &mut Criterion) { - $( - let expr = ast::Node::try_parse_from_str($input).unwrap().0; - let context = EvalContext::default(); + fn evaluation_bench(c: &mut Criterion) { + $( + let expr = ast::Node::try_parse_from_str($input).unwrap().0; + let context = EvalContext::default(); - c.bench_function(concat!("eval ", $input), |b| { - b.iter(|| { - let _ = black_box(expr.eval(&context)); - }); - }); - )* - } + c.bench_function(concat!("eval ", $input), |b| { + b.iter(|| { + let _ = black_box(expr.eval(&context)); + }); + }); + )* + } - criterion_group!(benches, parsing_bench, evaluation_bench); - criterion_main!(benches); - }; + criterion_group!(benches, parsing_bench, evaluation_bench); + criterion_main!(benches); + }; } generate_benchmarks! { - "(3 * (4 + sqrt(25)) - cos(pi/3) * (2^3)) + 5 * e", // Mixed nested functions, constants, and operations - "((5 + 2 * (3 - sqrt(49)))^2) / (1 + sqrt(16)) + tau / 2", // Complex nested expression with constants - "log(100, 10) + (5 * sin(pi/4) + sqrt(81)) / (2 * phi)", // Logarithmic and trigonometric functions - "(sqrt(144) * 2 + 5) / (3 * (4 - sin(pi / 6))) + e^2", // Combined square root, trigonometric, and exponential operations - "cos(2 * pi) + tan(pi / 3) * log(32, 2) - sqrt(256)", // Multiple trigonometric and logarithmic functions - "(10 * (3 + 2) - 8 / 2)^2 + 7 * (2^4) - sqrt(225) + phi", // Mixed arithmetic with constants - "(5^2 + 3^3) * (sqrt(81) + sqrt(64)) - tau * log(1000, 10)", // Power and square root with constants - "((8 * sqrt(49) - 2 * e) + log(256, 2) / (2 + cos(pi))) * 1.5", // Nested functions and constants - "(tan(pi / 4) + 5) * (3 + sqrt(36)) / (log(1024, 2) - 4)", // Nested functions with trigonometry and logarithm - "((3 * e + 2 * sqrt(100)) - cos(tau / 4)) * log(27, 3) + phi", // Mixed constant usage and functions - "(sqrt(100) + 5 * sin(pi / 6) - 8 / log(64, 2)) + e^(1.5)", // Complex mix of square root, division, and exponentiation - "((sin(pi/2) + cos(0)) * (e^2 - 2 * sqrt(16))) / (log(100, 10) + pi)", // Nested trigonometric, exponential, and logarithmic functions - "(5 * (7 + sqrt(121)) - (log(243, 3) * phi)) + 3^5 / tau", // + "(3 * (4 + sqrt(25)) - cos(pi/3) * (2^3)) + 5 * e", // Mixed nested functions, constants, and operations + "((5 + 2 * (3 - sqrt(49)))^2) / (1 + sqrt(16)) + tau / 2", // Complex nested expression with constants + "log(100, 10) + (5 * sin(pi/4) + sqrt(81)) / (2 * phi)", // Logarithmic and trigonometric functions + "(sqrt(144) * 2 + 5) / (3 * (4 - sin(pi / 6))) + e^2", // Combined square root, trigonometric, and exponential operations + "cos(2 * pi) + tan(pi / 3) * log(32, 2) - sqrt(256)", // Multiple trigonometric and logarithmic functions + "(10 * (3 + 2) - 8 / 2)^2 + 7 * (2^4) - sqrt(225) + phi", // Mixed arithmetic with constants + "(5^2 + 3^3) * (sqrt(81) + sqrt(64)) - tau * log(1000, 10)", // Power and square root with constants + "((8 * sqrt(49) - 2 * e) + log(256, 2) / (2 + cos(pi))) * 1.5", // Nested functions and constants + "(tan(pi / 4) + 5) * (3 + sqrt(36)) / (log(1024, 2) - 4)", // Nested functions with trigonometry and logarithm + "((3 * e + 2 * sqrt(100)) - cos(tau / 4)) * log(27, 3) + phi", // Mixed constant usage and functions + "(sqrt(100) + 5 * sin(pi / 6) - 8 / log(64, 2)) + e^(1.5)", // Complex mix of square root, division, and exponentiation + "((sin(pi/2) + cos(0)) * (e^2 - 2 * sqrt(16))) / (log(100, 10) + pi)", // Nested trigonometric, exponential, and logarithmic functions + "(5 * (7 + sqrt(121)) - (log(243, 3) * phi)) + 3^5 / tau", // }