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