mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-28 08:58:12 +08:00
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:
co-authored by
Keavon Chambers
parent
1f7a9188ba
commit
1a81e45673
@@ -1,6 +1,7 @@
|
||||
use super::utility_types::misc::{GroupFolderType, SnappingState};
|
||||
use crate::messages::input_mapper::utility_types::input_keyboard::Key;
|
||||
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
|
||||
use crate::messages::portfolio::document::overlays::utility_types::OverlaysType;
|
||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||
use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate, AlignAxis, FlipAxis, GridSnapping};
|
||||
use crate::messages::portfolio::utility_types::PanelType;
|
||||
@@ -143,6 +144,7 @@ pub enum DocumentMessage {
|
||||
},
|
||||
SetOverlaysVisibility {
|
||||
visible: bool,
|
||||
overlays_type: Option<OverlaysType>,
|
||||
},
|
||||
SetRangeSelectionLayer {
|
||||
new_layer: Option<LayerNodeIdentifier>,
|
||||
|
||||
@@ -12,6 +12,7 @@ use crate::messages::layout::utility_types::widget_prelude::*;
|
||||
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;
|
||||
use crate::messages::portfolio::document::node_graph::NodeGraphHandlerData;
|
||||
use crate::messages::portfolio::document::overlays::grid_overlays::{grid_overlay, overlay_options};
|
||||
use crate::messages::portfolio::document::overlays::utility_types::{OverlaysType, OverlaysVisibilitySettings};
|
||||
use crate::messages::portfolio::document::properties_panel::utility_types::PropertiesPanelMessageHandlerData;
|
||||
use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
|
||||
use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate, AlignAxis, DocumentMode, FlipAxis, PTZ};
|
||||
@@ -84,7 +85,7 @@ pub struct DocumentMessageHandler {
|
||||
pub view_mode: ViewMode,
|
||||
/// Sets whether or not all the viewport overlays should be drawn on top of the artwork.
|
||||
/// This includes tool interaction visualizations (like the transform cage and path anchors/handles), the grid, and more.
|
||||
pub overlays_visible: bool,
|
||||
pub overlays_visibility_settings: OverlaysVisibilitySettings,
|
||||
/// Sets whether or not the rulers should be drawn along the top and left edges of the viewport area.
|
||||
pub rulers_visible: bool,
|
||||
/// The current user choices for snapping behavior, including whether snapping is enabled at all.
|
||||
@@ -145,7 +146,7 @@ impl Default for DocumentMessageHandler {
|
||||
document_ptz: PTZ::default(),
|
||||
document_mode: DocumentMode::DesignMode,
|
||||
view_mode: ViewMode::default(),
|
||||
overlays_visible: true,
|
||||
overlays_visibility_settings: OverlaysVisibilitySettings::default(),
|
||||
rulers_visible: true,
|
||||
graph_view_overlay_open: false,
|
||||
snapping_state: SnappingState::default(),
|
||||
@@ -199,12 +200,14 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
||||
self.navigation_handler.process_message(message, responses, data);
|
||||
}
|
||||
DocumentMessage::Overlays(message) => {
|
||||
let overlays_visible = self.overlays_visible;
|
||||
let visibility_settings = self.overlays_visibility_settings;
|
||||
|
||||
// Send the overlays message to the overlays message handler
|
||||
self.overlays_message_handler.process_message(
|
||||
message,
|
||||
responses,
|
||||
OverlaysMessageData {
|
||||
overlays_visible,
|
||||
visibility_settings,
|
||||
ipp,
|
||||
device_pixel_ratio,
|
||||
},
|
||||
@@ -347,6 +350,10 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
||||
responses.add(FrontendMessage::UpdateDocumentLayerStructure { data_buffer });
|
||||
}
|
||||
DocumentMessage::DrawArtboardOverlays(overlay_context) => {
|
||||
if !overlay_context.visibility_settings.artboard_name() {
|
||||
return;
|
||||
}
|
||||
|
||||
for layer in self.metadata().all_layers() {
|
||||
if !self.network_interface.is_artboard(&layer.to_node(), &[]) {
|
||||
continue;
|
||||
@@ -1016,6 +1023,10 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
||||
}
|
||||
}
|
||||
DocumentMessage::SelectAllLayers => {
|
||||
if !self.overlays_visibility_settings.selection_outline() {
|
||||
return;
|
||||
}
|
||||
|
||||
let metadata = self.metadata();
|
||||
let all_layers_except_artboards_invisible_and_locked = metadata.all_layers().filter(|&layer| !self.network_interface.is_artboard(&layer.to_node(), &[])).filter(|&layer| {
|
||||
self.network_interface.selected_nodes().layer_visible(layer, &self.network_interface) && !self.network_interface.selected_nodes().layer_locked(layer, &self.network_interface)
|
||||
@@ -1135,8 +1146,34 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
||||
responses.add(GraphOperationMessage::OpacitySet { layer, opacity });
|
||||
}
|
||||
}
|
||||
DocumentMessage::SetOverlaysVisibility { visible } => {
|
||||
self.overlays_visible = visible;
|
||||
DocumentMessage::SetOverlaysVisibility { visible, overlays_type } => {
|
||||
let visibility_settings = &mut self.overlays_visibility_settings;
|
||||
let overlays_type = match overlays_type {
|
||||
Some(overlays_type) => overlays_type,
|
||||
None => {
|
||||
visibility_settings.all = visible;
|
||||
responses.add(BroadcastEvent::ToolAbort);
|
||||
responses.add(OverlaysMessage::Draw);
|
||||
return;
|
||||
}
|
||||
};
|
||||
match overlays_type {
|
||||
OverlaysType::ArtboardName => visibility_settings.artboard_name = visible,
|
||||
OverlaysType::CompassRose => visibility_settings.compass_rose = visible,
|
||||
OverlaysType::QuickMeasurement => visibility_settings.quick_measurement = visible,
|
||||
OverlaysType::TransformMeasurement => visibility_settings.transform_measurement = visible,
|
||||
OverlaysType::TransformCage => visibility_settings.transform_cage = visible,
|
||||
OverlaysType::HoverOutline => visibility_settings.hover_outline = visible,
|
||||
OverlaysType::SelectionOutline => visibility_settings.selection_outline = visible,
|
||||
OverlaysType::Pivot => visibility_settings.pivot = visible,
|
||||
OverlaysType::Path => visibility_settings.path = visible,
|
||||
OverlaysType::Anchors => {
|
||||
visibility_settings.anchors = visible;
|
||||
responses.add(PortfolioMessage::UpdateDocumentWidgets);
|
||||
}
|
||||
OverlaysType::Handles => visibility_settings.handles = visible,
|
||||
}
|
||||
|
||||
responses.add(BroadcastEvent::ToolAbort);
|
||||
responses.add(OverlaysMessage::Draw);
|
||||
}
|
||||
@@ -1238,7 +1275,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
||||
responses.add(PortfolioMessage::UpdateDocumentWidgets);
|
||||
}
|
||||
DocumentMessage::ToggleOverlaysVisibility => {
|
||||
self.overlays_visible = !self.overlays_visible;
|
||||
self.overlays_visibility_settings.all = !self.overlays_visibility_settings.all();
|
||||
responses.add(OverlaysMessage::Draw);
|
||||
responses.add(PortfolioMessage::UpdateDocumentWidgets);
|
||||
}
|
||||
@@ -1679,7 +1716,7 @@ impl DocumentMessageHandler {
|
||||
pub view_mode: ViewMode,
|
||||
/// Sets whether or not all the viewport overlays should be drawn on top of the artwork.
|
||||
/// This includes tool interaction visualizations (like the transform cage and path anchors/handles), the grid, and more.
|
||||
pub overlays_visible: bool,
|
||||
pub overlays_visibility_settings: OverlaysVisibilitySettings,
|
||||
/// Sets whether or not the rulers should be drawn along the top and left edges of the viewport area.
|
||||
pub rulers_visible: bool,
|
||||
/// Sets whether or not the node graph is drawn (as an overlay) on top of the viewport area, or otherwise if it's hidden.
|
||||
@@ -1695,7 +1732,7 @@ impl DocumentMessageHandler {
|
||||
document_ptz: old_message_handler.document_ptz,
|
||||
document_mode: old_message_handler.document_mode,
|
||||
view_mode: old_message_handler.view_mode,
|
||||
overlays_visible: old_message_handler.overlays_visible,
|
||||
overlays_visibility_settings: old_message_handler.overlays_visibility_settings,
|
||||
rulers_visible: old_message_handler.rulers_visible,
|
||||
graph_view_overlay_open: old_message_handler.graph_view_overlay_open,
|
||||
snapping_state: old_message_handler.snapping_state,
|
||||
@@ -2053,11 +2090,17 @@ impl DocumentMessageHandler {
|
||||
.on_update(|_| AnimationMessage::ToggleLivePreview.into())
|
||||
.widget_holder(),
|
||||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||
CheckboxInput::new(self.overlays_visible)
|
||||
CheckboxInput::new(self.overlays_visibility_settings.all)
|
||||
.icon("Overlays")
|
||||
.tooltip("Overlays")
|
||||
.tooltip_shortcut(action_keys!(DocumentMessageDiscriminant::ToggleOverlaysVisibility))
|
||||
.on_update(|optional_input: &CheckboxInput| DocumentMessage::SetOverlaysVisibility { visible: optional_input.checked }.into())
|
||||
.on_update(|optional_input: &CheckboxInput| {
|
||||
DocumentMessage::SetOverlaysVisibility {
|
||||
visible: optional_input.checked,
|
||||
overlays_type: None,
|
||||
}
|
||||
.into()
|
||||
})
|
||||
.widget_holder(),
|
||||
PopoverButton::new()
|
||||
.popover_layout(vec![
|
||||
@@ -2065,7 +2108,168 @@ impl DocumentMessageHandler {
|
||||
widgets: vec![TextLabel::new("Overlays").bold(true).widget_holder()],
|
||||
},
|
||||
LayoutGroup::Row {
|
||||
widgets: vec![TextLabel::new("Granular settings in this menu are coming soon").widget_holder()],
|
||||
widgets: vec![TextLabel::new("General").widget_holder()],
|
||||
},
|
||||
LayoutGroup::Row {
|
||||
widgets: vec![
|
||||
CheckboxInput::new(self.overlays_visibility_settings.artboard_name)
|
||||
.on_update(|optional_input: &CheckboxInput| {
|
||||
DocumentMessage::SetOverlaysVisibility {
|
||||
visible: optional_input.checked,
|
||||
overlays_type: Some(OverlaysType::ArtboardName),
|
||||
}
|
||||
.into()
|
||||
})
|
||||
.widget_holder(),
|
||||
TextLabel::new("Artboard Name".to_string()).widget_holder(),
|
||||
],
|
||||
},
|
||||
LayoutGroup::Row {
|
||||
widgets: vec![
|
||||
CheckboxInput::new(self.overlays_visibility_settings.transform_measurement)
|
||||
.on_update(|optional_input: &CheckboxInput| {
|
||||
DocumentMessage::SetOverlaysVisibility {
|
||||
visible: optional_input.checked,
|
||||
overlays_type: Some(OverlaysType::TransformMeasurement),
|
||||
}
|
||||
.into()
|
||||
})
|
||||
.widget_holder(),
|
||||
TextLabel::new("G/R/S Measurement".to_string()).widget_holder(),
|
||||
],
|
||||
},
|
||||
LayoutGroup::Row {
|
||||
widgets: vec![TextLabel::new("Select Tool").widget_holder()],
|
||||
},
|
||||
LayoutGroup::Row {
|
||||
widgets: vec![
|
||||
CheckboxInput::new(self.overlays_visibility_settings.quick_measurement)
|
||||
.on_update(|optional_input: &CheckboxInput| {
|
||||
DocumentMessage::SetOverlaysVisibility {
|
||||
visible: optional_input.checked,
|
||||
overlays_type: Some(OverlaysType::QuickMeasurement),
|
||||
}
|
||||
.into()
|
||||
})
|
||||
.widget_holder(),
|
||||
TextLabel::new("Quick Measurement".to_string()).widget_holder(),
|
||||
],
|
||||
},
|
||||
LayoutGroup::Row {
|
||||
widgets: vec![
|
||||
CheckboxInput::new(self.overlays_visibility_settings.transform_cage)
|
||||
.on_update(|optional_input: &CheckboxInput| {
|
||||
DocumentMessage::SetOverlaysVisibility {
|
||||
visible: optional_input.checked,
|
||||
overlays_type: Some(OverlaysType::TransformCage),
|
||||
}
|
||||
.into()
|
||||
})
|
||||
.widget_holder(),
|
||||
TextLabel::new("Transform Cage".to_string()).widget_holder(),
|
||||
],
|
||||
},
|
||||
LayoutGroup::Row {
|
||||
widgets: vec![
|
||||
CheckboxInput::new(self.overlays_visibility_settings.compass_rose)
|
||||
.on_update(|optional_input: &CheckboxInput| {
|
||||
DocumentMessage::SetOverlaysVisibility {
|
||||
visible: optional_input.checked,
|
||||
overlays_type: Some(OverlaysType::CompassRose),
|
||||
}
|
||||
.into()
|
||||
})
|
||||
.widget_holder(),
|
||||
TextLabel::new("Transform Dial".to_string()).widget_holder(),
|
||||
],
|
||||
},
|
||||
LayoutGroup::Row {
|
||||
widgets: vec![
|
||||
CheckboxInput::new(self.overlays_visibility_settings.pivot)
|
||||
.on_update(|optional_input: &CheckboxInput| {
|
||||
DocumentMessage::SetOverlaysVisibility {
|
||||
visible: optional_input.checked,
|
||||
overlays_type: Some(OverlaysType::Pivot),
|
||||
}
|
||||
.into()
|
||||
})
|
||||
.widget_holder(),
|
||||
TextLabel::new("Transform Pivot".to_string()).widget_holder(),
|
||||
],
|
||||
},
|
||||
LayoutGroup::Row {
|
||||
widgets: vec![
|
||||
CheckboxInput::new(self.overlays_visibility_settings.hover_outline)
|
||||
.on_update(|optional_input: &CheckboxInput| {
|
||||
DocumentMessage::SetOverlaysVisibility {
|
||||
visible: optional_input.checked,
|
||||
overlays_type: Some(OverlaysType::HoverOutline),
|
||||
}
|
||||
.into()
|
||||
})
|
||||
.widget_holder(),
|
||||
TextLabel::new("Hover Outline".to_string()).widget_holder(),
|
||||
],
|
||||
},
|
||||
LayoutGroup::Row {
|
||||
widgets: vec![
|
||||
CheckboxInput::new(self.overlays_visibility_settings.selection_outline)
|
||||
.on_update(|optional_input: &CheckboxInput| {
|
||||
DocumentMessage::SetOverlaysVisibility {
|
||||
visible: optional_input.checked,
|
||||
overlays_type: Some(OverlaysType::SelectionOutline),
|
||||
}
|
||||
.into()
|
||||
})
|
||||
.widget_holder(),
|
||||
TextLabel::new("Selection Outline".to_string()).widget_holder(),
|
||||
],
|
||||
},
|
||||
LayoutGroup::Row {
|
||||
widgets: vec![TextLabel::new("Pen & Path Tools").widget_holder()],
|
||||
},
|
||||
LayoutGroup::Row {
|
||||
widgets: vec![
|
||||
CheckboxInput::new(self.overlays_visibility_settings.path)
|
||||
.on_update(|optional_input: &CheckboxInput| {
|
||||
DocumentMessage::SetOverlaysVisibility {
|
||||
visible: optional_input.checked,
|
||||
overlays_type: Some(OverlaysType::Path),
|
||||
}
|
||||
.into()
|
||||
})
|
||||
.widget_holder(),
|
||||
TextLabel::new("Path".to_string()).widget_holder(),
|
||||
],
|
||||
},
|
||||
LayoutGroup::Row {
|
||||
widgets: vec![
|
||||
CheckboxInput::new(self.overlays_visibility_settings.anchors)
|
||||
.on_update(|optional_input: &CheckboxInput| {
|
||||
DocumentMessage::SetOverlaysVisibility {
|
||||
visible: optional_input.checked,
|
||||
overlays_type: Some(OverlaysType::Anchors),
|
||||
}
|
||||
.into()
|
||||
})
|
||||
.widget_holder(),
|
||||
TextLabel::new("Anchors".to_string()).widget_holder(),
|
||||
],
|
||||
},
|
||||
LayoutGroup::Row {
|
||||
widgets: vec![
|
||||
CheckboxInput::new(self.overlays_visibility_settings.handles)
|
||||
.disabled(!self.overlays_visibility_settings.anchors)
|
||||
.on_update(|optional_input: &CheckboxInput| {
|
||||
DocumentMessage::SetOverlaysVisibility {
|
||||
visible: optional_input.checked,
|
||||
overlays_type: Some(OverlaysType::Handles),
|
||||
}
|
||||
.into()
|
||||
})
|
||||
.widget_holder(),
|
||||
TextLabel::new("Handles".to_string()).disabled(!self.overlays_visibility_settings.anchors).widget_holder(),
|
||||
],
|
||||
},
|
||||
])
|
||||
.widget_holder(),
|
||||
@@ -2212,6 +2416,7 @@ impl DocumentMessageHandler {
|
||||
layout: Layout::WidgetLayout(document_bar_layout),
|
||||
layout_target: LayoutTarget::DocumentBar,
|
||||
});
|
||||
responses.add(NodeGraphMessage::ForceRunDocumentGraph);
|
||||
}
|
||||
|
||||
pub fn update_layers_panel_control_bar_widgets(&self, responses: &mut VecDeque<Message>) {
|
||||
@@ -2280,75 +2485,74 @@ impl DocumentMessageHandler {
|
||||
.selected_layers(self.metadata())
|
||||
.all(|layer| self.network_interface.is_locked(&layer.to_node(), &[]));
|
||||
|
||||
let layers_panel_control_bar = WidgetLayout::new(vec![LayoutGroup::Row {
|
||||
widgets: vec![
|
||||
DropdownInput::new(blend_mode_menu_entries)
|
||||
.selected_index(blend_mode.and_then(|blend_mode| blend_mode.index_in_list_svg_subset()).map(|index| index as u32))
|
||||
.disabled(disabled)
|
||||
.draw_icon(false)
|
||||
.widget_holder(),
|
||||
Separator::new(SeparatorType::Related).widget_holder(),
|
||||
NumberInput::new(opacity)
|
||||
.label("Opacity")
|
||||
.unit("%")
|
||||
.display_decimal_places(2)
|
||||
.disabled(disabled)
|
||||
.min(0.)
|
||||
.max(100.)
|
||||
.range_min(Some(0.))
|
||||
.range_max(Some(100.))
|
||||
.mode_range()
|
||||
.on_update(|number_input: &NumberInput| {
|
||||
if let Some(value) = number_input.value {
|
||||
DocumentMessage::SetOpacityForSelectedLayers { opacity: value / 100. }.into()
|
||||
} else {
|
||||
Message::NoOp
|
||||
}
|
||||
})
|
||||
.on_commit(|_| DocumentMessage::AddTransaction.into())
|
||||
.widget_holder(),
|
||||
//
|
||||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||
//
|
||||
IconButton::new("NewLayer", 24)
|
||||
.tooltip("New Layer")
|
||||
.tooltip_shortcut(action_keys!(DocumentMessageDiscriminant::CreateEmptyFolder))
|
||||
.on_update(|_| DocumentMessage::CreateEmptyFolder.into())
|
||||
.widget_holder(),
|
||||
IconButton::new("Folder", 24)
|
||||
.tooltip("Group Selected")
|
||||
.tooltip_shortcut(action_keys!(DocumentMessageDiscriminant::GroupSelectedLayers))
|
||||
.on_update(|_| {
|
||||
let group_folder_type = GroupFolderType::Layer;
|
||||
DocumentMessage::GroupSelectedLayers { group_folder_type }.into()
|
||||
})
|
||||
.disabled(!has_selection)
|
||||
.widget_holder(),
|
||||
IconButton::new("Trash", 24)
|
||||
.tooltip("Delete Selected")
|
||||
.tooltip_shortcut(action_keys!(DocumentMessageDiscriminant::DeleteSelectedLayers))
|
||||
.on_update(|_| DocumentMessage::DeleteSelectedLayers.into())
|
||||
.disabled(!has_selection)
|
||||
.widget_holder(),
|
||||
//
|
||||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||
//
|
||||
IconButton::new(if selection_all_locked { "PadlockLocked" } else { "PadlockUnlocked" }, 24)
|
||||
.hover_icon(Some((if selection_all_locked { "PadlockUnlocked" } else { "PadlockLocked" }).into()))
|
||||
.tooltip(if selection_all_locked { "Unlock Selected" } else { "Lock Selected" })
|
||||
.tooltip_shortcut(action_keys!(DocumentMessageDiscriminant::ToggleSelectedLocked))
|
||||
.on_update(|_| NodeGraphMessage::ToggleSelectedLocked.into())
|
||||
.disabled(!has_selection)
|
||||
.widget_holder(),
|
||||
IconButton::new(if selection_all_visible { "EyeVisible" } else { "EyeHidden" }, 24)
|
||||
.hover_icon(Some((if selection_all_visible { "EyeHide" } else { "EyeShow" }).into()))
|
||||
.tooltip(if selection_all_visible { "Hide Selected" } else { "Show Selected" })
|
||||
.tooltip_shortcut(action_keys!(DocumentMessageDiscriminant::ToggleSelectedVisibility))
|
||||
.on_update(|_| DocumentMessage::ToggleSelectedVisibility.into())
|
||||
.disabled(!has_selection)
|
||||
.widget_holder(),
|
||||
],
|
||||
}]);
|
||||
let widgets = vec![
|
||||
DropdownInput::new(blend_mode_menu_entries)
|
||||
.selected_index(blend_mode.and_then(|blend_mode| blend_mode.index_in_list_svg_subset()).map(|index| index as u32))
|
||||
.disabled(disabled)
|
||||
.draw_icon(false)
|
||||
.widget_holder(),
|
||||
Separator::new(SeparatorType::Related).widget_holder(),
|
||||
NumberInput::new(opacity)
|
||||
.label("Opacity")
|
||||
.unit("%")
|
||||
.display_decimal_places(2)
|
||||
.disabled(disabled)
|
||||
.min(0.)
|
||||
.max(100.)
|
||||
.range_min(Some(0.))
|
||||
.range_max(Some(100.))
|
||||
.mode_range()
|
||||
.on_update(|number_input: &NumberInput| {
|
||||
if let Some(value) = number_input.value {
|
||||
DocumentMessage::SetOpacityForSelectedLayers { opacity: value / 100. }.into()
|
||||
} else {
|
||||
Message::NoOp
|
||||
}
|
||||
})
|
||||
.on_commit(|_| DocumentMessage::AddTransaction.into())
|
||||
.widget_holder(),
|
||||
//
|
||||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||
//
|
||||
IconButton::new("NewLayer", 24)
|
||||
.tooltip("New Layer")
|
||||
.tooltip_shortcut(action_keys!(DocumentMessageDiscriminant::CreateEmptyFolder))
|
||||
.on_update(|_| DocumentMessage::CreateEmptyFolder.into())
|
||||
.widget_holder(),
|
||||
IconButton::new("Folder", 24)
|
||||
.tooltip("Group Selected")
|
||||
.tooltip_shortcut(action_keys!(DocumentMessageDiscriminant::GroupSelectedLayers))
|
||||
.on_update(|_| {
|
||||
let group_folder_type = GroupFolderType::Layer;
|
||||
DocumentMessage::GroupSelectedLayers { group_folder_type }.into()
|
||||
})
|
||||
.disabled(!has_selection)
|
||||
.widget_holder(),
|
||||
IconButton::new("Trash", 24)
|
||||
.tooltip("Delete Selected")
|
||||
.tooltip_shortcut(action_keys!(DocumentMessageDiscriminant::DeleteSelectedLayers))
|
||||
.on_update(|_| DocumentMessage::DeleteSelectedLayers.into())
|
||||
.disabled(!has_selection)
|
||||
.widget_holder(),
|
||||
//
|
||||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||
//
|
||||
IconButton::new(if selection_all_locked { "PadlockLocked" } else { "PadlockUnlocked" }, 24)
|
||||
.hover_icon(Some((if selection_all_locked { "PadlockUnlocked" } else { "PadlockLocked" }).into()))
|
||||
.tooltip(if selection_all_locked { "Unlock Selected" } else { "Lock Selected" })
|
||||
.tooltip_shortcut(action_keys!(DocumentMessageDiscriminant::ToggleSelectedLocked))
|
||||
.on_update(|_| NodeGraphMessage::ToggleSelectedLocked.into())
|
||||
.disabled(!has_selection)
|
||||
.widget_holder(),
|
||||
IconButton::new(if selection_all_visible { "EyeVisible" } else { "EyeHidden" }, 24)
|
||||
.hover_icon(Some((if selection_all_visible { "EyeHide" } else { "EyeShow" }).into()))
|
||||
.tooltip(if selection_all_visible { "Hide Selected" } else { "Show Selected" })
|
||||
.tooltip_shortcut(action_keys!(DocumentMessageDiscriminant::ToggleSelectedVisibility))
|
||||
.on_update(|_| DocumentMessage::ToggleSelectedVisibility.into())
|
||||
.disabled(!has_selection)
|
||||
.widget_holder(),
|
||||
];
|
||||
let layers_panel_control_bar = WidgetLayout::new(vec![LayoutGroup::Row { widgets }]);
|
||||
|
||||
responses.add(LayoutMessage::SendLayout {
|
||||
layout: Layout::WidgetLayout(layers_panel_control_bar),
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use super::utility_types::OverlayProvider;
|
||||
use super::utility_types::{OverlayProvider, OverlaysVisibilitySettings};
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
pub struct OverlaysMessageData<'a> {
|
||||
pub overlays_visible: bool,
|
||||
pub visibility_settings: OverlaysVisibilitySettings,
|
||||
pub ipp: &'a InputPreprocessorMessageHandler,
|
||||
pub device_pixel_ratio: f64,
|
||||
}
|
||||
@@ -18,7 +18,7 @@ pub struct OverlaysMessageHandler {
|
||||
|
||||
impl MessageHandler<OverlaysMessage, OverlaysMessageData<'_>> for OverlaysMessageHandler {
|
||||
fn process_message(&mut self, message: OverlaysMessage, responses: &mut VecDeque<Message>, data: OverlaysMessageData) {
|
||||
let OverlaysMessageData { overlays_visible, ipp, .. } = data;
|
||||
let OverlaysMessageData { visibility_settings, ipp, .. } = data;
|
||||
|
||||
match message {
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
@@ -50,24 +50,26 @@ impl MessageHandler<OverlaysMessage, OverlaysMessageData<'_>> for OverlaysMessag
|
||||
context.clear_rect(0., 0., canvas.width().into(), canvas.height().into());
|
||||
let _ = context.reset_transform();
|
||||
|
||||
if overlays_visible {
|
||||
if visibility_settings.all() {
|
||||
responses.add(DocumentMessage::GridOverlays(OverlayContext {
|
||||
render_context: context.clone(),
|
||||
size: size.as_dvec2(),
|
||||
device_pixel_ratio,
|
||||
visibility_settings: visibility_settings.clone(),
|
||||
}));
|
||||
for provider in &self.overlay_providers {
|
||||
responses.add(provider(OverlayContext {
|
||||
render_context: context.clone(),
|
||||
size: size.as_dvec2(),
|
||||
device_pixel_ratio,
|
||||
visibility_settings: visibility_settings.clone(),
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
OverlaysMessage::Draw => {
|
||||
warn!("Cannot render overlays on non-Wasm targets.\n{responses:?} {overlays_visible} {ipp:?}",);
|
||||
warn!("Cannot render overlays on non-Wasm targets.\n{responses:?} {visibility_settings:?} {ipp:?}",);
|
||||
}
|
||||
OverlaysMessage::AddProvider(message) => {
|
||||
self.overlay_providers.insert(message);
|
||||
|
||||
@@ -77,7 +77,7 @@ fn overlay_bezier_handles(bezier: Bezier, segment_id: SegmentId, transform: DAff
|
||||
}
|
||||
}
|
||||
|
||||
pub fn overlay_bezier_handle_specific_point(
|
||||
fn overlay_bezier_handle_specific_point(
|
||||
bezier: Bezier,
|
||||
segment_id: SegmentId,
|
||||
(start, end): (PointId, PointId),
|
||||
@@ -112,59 +112,73 @@ pub fn overlay_bezier_handle_specific_point(
|
||||
}
|
||||
|
||||
pub fn path_overlays(document: &DocumentMessageHandler, draw_handles: DrawHandles, shape_editor: &mut ShapeState, overlay_context: &mut OverlayContext) {
|
||||
let display_path = overlay_context.visibility_settings.path();
|
||||
let display_handles = overlay_context.visibility_settings.handles();
|
||||
let display_anchors = overlay_context.visibility_settings.anchors();
|
||||
|
||||
for layer in document.network_interface.selected_nodes().selected_layers(document.metadata()) {
|
||||
let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else { continue };
|
||||
let transform = document.metadata().transform_to_viewport(layer);
|
||||
overlay_context.outline_vector(&vector_data, transform);
|
||||
if display_path {
|
||||
overlay_context.outline_vector(&vector_data, transform);
|
||||
}
|
||||
|
||||
let selected = shape_editor.selected_shape_state.get(&layer);
|
||||
let is_selected = |point: ManipulatorPointId| selected.is_some_and(|selected| selected.is_selected(point));
|
||||
|
||||
let opposite_handles_data: Vec<(PointId, SegmentId)> = shape_editor.selected_points().filter_map(|point_id| vector_data.adjacent_segment(point_id)).collect();
|
||||
if display_handles {
|
||||
let opposite_handles_data: Vec<(PointId, SegmentId)> = shape_editor.selected_points().filter_map(|point_id| vector_data.adjacent_segment(point_id)).collect();
|
||||
|
||||
match draw_handles {
|
||||
DrawHandles::All => {
|
||||
vector_data.segment_bezier_iter().for_each(|(segment_id, bezier, _start, _end)| {
|
||||
overlay_bezier_handles(bezier, segment_id, transform, is_selected, overlay_context);
|
||||
});
|
||||
}
|
||||
DrawHandles::SelectedAnchors(ref selected_segments) => {
|
||||
vector_data
|
||||
.segment_bezier_iter()
|
||||
.filter(|(segment_id, ..)| selected_segments.contains(segment_id))
|
||||
.for_each(|(segment_id, bezier, _start, _end)| {
|
||||
match draw_handles {
|
||||
DrawHandles::All => {
|
||||
vector_data.segment_bezier_iter().for_each(|(segment_id, bezier, _start, _end)| {
|
||||
overlay_bezier_handles(bezier, segment_id, transform, is_selected, overlay_context);
|
||||
});
|
||||
}
|
||||
DrawHandles::SelectedAnchors(ref selected_segments) => {
|
||||
vector_data
|
||||
.segment_bezier_iter()
|
||||
.filter(|(segment_id, ..)| selected_segments.contains(segment_id))
|
||||
.for_each(|(segment_id, bezier, _start, _end)| {
|
||||
overlay_bezier_handles(bezier, segment_id, transform, is_selected, overlay_context);
|
||||
});
|
||||
|
||||
for (segment_id, bezier, start, end) in vector_data.segment_bezier_iter() {
|
||||
if let Some((corresponding_anchor, _)) = opposite_handles_data.iter().find(|(_, adj_segment_id)| adj_segment_id == &segment_id) {
|
||||
overlay_bezier_handle_specific_point(bezier, segment_id, (start, end), *corresponding_anchor, transform, is_selected, overlay_context);
|
||||
for (segment_id, bezier, start, end) in vector_data.segment_bezier_iter() {
|
||||
if let Some((corresponding_anchor, _)) = opposite_handles_data.iter().find(|(_, adj_segment_id)| adj_segment_id == &segment_id) {
|
||||
overlay_bezier_handle_specific_point(bezier, segment_id, (start, end), *corresponding_anchor, transform, is_selected, overlay_context);
|
||||
}
|
||||
}
|
||||
}
|
||||
DrawHandles::FrontierHandles(ref segment_endpoints) => {
|
||||
vector_data
|
||||
.segment_bezier_iter()
|
||||
.filter(|(segment_id, ..)| segment_endpoints.contains_key(segment_id))
|
||||
.for_each(|(segment_id, bezier, start, end)| {
|
||||
if segment_endpoints.get(&segment_id).unwrap().len() == 1 {
|
||||
let point_to_render = segment_endpoints.get(&segment_id).unwrap()[0];
|
||||
overlay_bezier_handle_specific_point(bezier, segment_id, (start, end), point_to_render, transform, is_selected, overlay_context);
|
||||
} else {
|
||||
overlay_bezier_handles(bezier, segment_id, transform, is_selected, overlay_context);
|
||||
}
|
||||
});
|
||||
}
|
||||
DrawHandles::None => {}
|
||||
}
|
||||
DrawHandles::FrontierHandles(ref segment_endpoints) => {
|
||||
vector_data
|
||||
.segment_bezier_iter()
|
||||
.filter(|(segment_id, ..)| segment_endpoints.contains_key(segment_id))
|
||||
.for_each(|(segment_id, bezier, start, end)| {
|
||||
if segment_endpoints.get(&segment_id).unwrap().len() == 1 {
|
||||
let point_to_render = segment_endpoints.get(&segment_id).unwrap()[0];
|
||||
overlay_bezier_handle_specific_point(bezier, segment_id, (start, end), point_to_render, transform, is_selected, overlay_context);
|
||||
} else {
|
||||
overlay_bezier_handles(bezier, segment_id, transform, is_selected, overlay_context);
|
||||
}
|
||||
});
|
||||
}
|
||||
DrawHandles::None => {}
|
||||
}
|
||||
|
||||
for (&id, &position) in vector_data.point_domain.ids().iter().zip(vector_data.point_domain.positions()) {
|
||||
overlay_context.manipulator_anchor(transform.transform_point2(position), is_selected(ManipulatorPointId::Anchor(id)), None);
|
||||
if display_anchors {
|
||||
for (&id, &position) in vector_data.point_domain.ids().iter().zip(vector_data.point_domain.positions()) {
|
||||
overlay_context.manipulator_anchor(transform.transform_point2(position), is_selected(ManipulatorPointId::Anchor(id)), None);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn path_endpoint_overlays(document: &DocumentMessageHandler, shape_editor: &mut ShapeState, overlay_context: &mut OverlayContext, preferences: &PreferencesMessageHandler) {
|
||||
if !overlay_context.visibility_settings.anchors() {
|
||||
return;
|
||||
}
|
||||
|
||||
for layer in document.network_interface.selected_nodes().selected_layers(document.metadata()) {
|
||||
let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else {
|
||||
continue;
|
||||
|
||||
@@ -21,6 +21,107 @@ pub fn empty_provider() -> OverlayProvider {
|
||||
|_| Message::NoOp
|
||||
}
|
||||
|
||||
// Types of overlays used by DocumentMessage to enable/disable select group of overlays in the frontend
|
||||
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
pub enum OverlaysType {
|
||||
ArtboardName,
|
||||
CompassRose,
|
||||
QuickMeasurement,
|
||||
TransformMeasurement,
|
||||
TransformCage,
|
||||
HoverOutline,
|
||||
SelectionOutline,
|
||||
Pivot,
|
||||
Path,
|
||||
Anchors,
|
||||
Handles,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Copy, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
pub struct OverlaysVisibilitySettings {
|
||||
pub all: bool,
|
||||
pub artboard_name: bool,
|
||||
pub compass_rose: bool,
|
||||
pub quick_measurement: bool,
|
||||
pub transform_measurement: bool,
|
||||
pub transform_cage: bool,
|
||||
pub hover_outline: bool,
|
||||
pub selection_outline: bool,
|
||||
pub pivot: bool,
|
||||
pub path: bool,
|
||||
pub anchors: bool,
|
||||
pub handles: bool,
|
||||
}
|
||||
|
||||
impl Default for OverlaysVisibilitySettings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
all: true,
|
||||
artboard_name: true,
|
||||
compass_rose: true,
|
||||
quick_measurement: true,
|
||||
transform_measurement: true,
|
||||
transform_cage: true,
|
||||
hover_outline: true,
|
||||
selection_outline: true,
|
||||
pivot: true,
|
||||
path: true,
|
||||
anchors: true,
|
||||
handles: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl OverlaysVisibilitySettings {
|
||||
pub fn all(&self) -> bool {
|
||||
self.all
|
||||
}
|
||||
|
||||
pub fn artboard_name(&self) -> bool {
|
||||
self.all && self.artboard_name
|
||||
}
|
||||
|
||||
pub fn compass_rose(&self) -> bool {
|
||||
self.all && self.compass_rose
|
||||
}
|
||||
|
||||
pub fn quick_measurement(&self) -> bool {
|
||||
self.all && self.quick_measurement
|
||||
}
|
||||
|
||||
pub fn transform_measurement(&self) -> bool {
|
||||
self.all && self.transform_measurement
|
||||
}
|
||||
|
||||
pub fn transform_cage(&self) -> bool {
|
||||
self.all && self.transform_cage
|
||||
}
|
||||
|
||||
pub fn hover_outline(&self) -> bool {
|
||||
self.all && self.hover_outline
|
||||
}
|
||||
|
||||
pub fn selection_outline(&self) -> bool {
|
||||
self.all && self.selection_outline
|
||||
}
|
||||
|
||||
pub fn pivot(&self) -> bool {
|
||||
self.all && self.pivot
|
||||
}
|
||||
|
||||
pub fn path(&self) -> bool {
|
||||
self.all && self.path
|
||||
}
|
||||
|
||||
pub fn anchors(&self) -> bool {
|
||||
self.all && self.anchors
|
||||
}
|
||||
|
||||
pub fn handles(&self) -> bool {
|
||||
self.all && self.anchors && self.handles
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
pub struct OverlayContext {
|
||||
// Serde functionality isn't used but is required by the message system macros
|
||||
@@ -31,6 +132,7 @@ pub struct OverlayContext {
|
||||
// The device pixel ratio is a property provided by the browser window and is the CSS pixel size divided by the physical monitor's pixel size.
|
||||
// It allows better pixel density of visualizations on high-DPI displays where the OS display scaling is not 100%, or where the browser is zoomed.
|
||||
pub device_pixel_ratio: f64,
|
||||
pub visibility_settings: OverlaysVisibilitySettings,
|
||||
}
|
||||
// Message hashing isn't used but is required by the message system macros
|
||||
impl core::hash::Hash for OverlayContext {
|
||||
|
||||
Reference in New Issue
Block a user