Store overlays, snapping, and grid state in saved documents and toggle them with hotkeys

This commit is contained in:
Keavon Chambers 2024-05-06 22:08:19 -07:00
parent 14262e1527
commit 7845302c50
10 changed files with 149 additions and 71 deletions

View File

@ -22,6 +22,6 @@ impl MessageHandler<BroadcastMessage, ()> for BroadcastMessageHandler {
}
fn actions(&self) -> ActionList {
vec![]
actions!(BroadcastEventDiscriminant;)
}
}

View File

@ -15,13 +15,13 @@ use glam::DVec2;
impl From<MappingVariant> for Mapping {
fn from(value: MappingVariant) -> Self {
match value {
MappingVariant::Default => default_mapping(),
MappingVariant::Default => input_mappings(),
MappingVariant::ZoomWithScroll => zoom_with_scroll(),
}
}
}
pub fn default_mapping() -> Mapping {
pub fn input_mappings() -> Mapping {
use InputMapperMessage::*;
use Key::*;
@ -61,7 +61,7 @@ pub fn default_mapping() -> Mapping {
entry!(KeyDown(KeyD); modifiers=[Accel], action_dispatch=NodeGraphMessage::DuplicateSelectedNodes),
entry!(KeyDown(KeyH); modifiers=[Accel], action_dispatch=NodeGraphMessage::ToggleSelectedVisibility),
entry!(KeyDown(KeyL); modifiers=[Accel], action_dispatch=NodeGraphMessage::ToggleSelectedLocked),
entry!(KeyDown(KeyL); modifiers=[Alt], action_dispatch=NodeGraphMessage::ToggleSelectedLayers),
entry!(KeyDown(KeyL); modifiers=[Alt], action_dispatch=NodeGraphMessage::ToggleSelectedAsLayersOrNodes),
//
// TransformLayerMessage
entry!(KeyDown(Enter); action_dispatch=TransformLayerMessage::ApplyTransformOperation),
@ -292,6 +292,9 @@ pub fn default_mapping() -> Mapping {
entry!(KeyDown(Delete); action_dispatch=DocumentMessage::DeleteSelectedLayers),
entry!(KeyDown(Backspace); action_dispatch=DocumentMessage::DeleteSelectedLayers),
entry!(KeyDown(KeyP); modifiers=[Alt], action_dispatch=DocumentMessage::DebugPrintDocument),
entry!(KeyDown(KeyO); modifiers=[Alt], action_dispatch=DocumentMessage::ToggleOverlaysVisibility),
entry!(KeyDown(KeyS); modifiers=[Alt], action_dispatch=DocumentMessage::ToggleSnapping),
entry!(KeyDown(KeyG); modifiers=[Alt], action_dispatch=DocumentMessage::ToggleGridVisibility),
entry!(KeyDown(KeyZ); modifiers=[Accel, Shift], action_dispatch=DocumentMessage::Redo),
entry!(KeyDown(KeyY); modifiers=[Accel], action_dispatch=DocumentMessage::Redo),
entry!(KeyDown(KeyZ); modifiers=[Accel], action_dispatch=DocumentMessage::Undo),
@ -422,7 +425,7 @@ pub fn default_mapping() -> Mapping {
pub fn zoom_with_scroll() -> Mapping {
use InputMapperMessage::*;
let mut mapping = default_mapping();
let mut mapping = input_mappings();
let remove = [
entry!(WheelScroll; modifiers=[Control], action_dispatch=NavigationMessage::CanvasZoomMouseWheel),

View File

@ -1,7 +1,7 @@
mod input_mapper_message;
mod input_mapper_message_handler;
pub mod default_mapping;
pub mod input_mappings;
pub mod key_mapping;
pub mod utility_types;

View File

@ -296,7 +296,7 @@ impl<F: Fn(&MessageDiscriminant) -> Vec<KeysGroup>> MessageHandler<LayoutMessage
}
fn actions(&self) -> ActionList {
actions!()
actions!(LayoutMessageDiscriminant;)
}
}

View File

@ -62,7 +62,7 @@ pub enum DocumentMessage {
GraphViewOverlayToggle,
GridOptions(GridSnapping),
GridOverlays(OverlayContext),
GridVisible(bool),
GridVisibility(bool),
GroupSelectedLayers,
ImaginateGenerate,
ImaginateRandom {
@ -138,6 +138,9 @@ pub enum DocumentMessage {
ToggleLayerExpansion {
id: NodeId,
},
ToggleGridVisibility,
ToggleOverlaysVisibility,
ToggleSnapping,
Undo,
UndoFinished,
UngroupSelectedLayers,

View File

@ -46,6 +46,7 @@ pub struct DocumentMessageHandler {
// ======================
// Child message handlers
// ======================
//
#[serde(skip)]
navigation_handler: NavigationMessageHandler,
#[serde(skip)]
@ -54,49 +55,78 @@ pub struct DocumentMessageHandler {
overlays_message_handler: OverlaysMessageHandler,
#[serde(skip)]
properties_panel_message_handler: PropertiesPanelMessageHandler,
// ============================================
// Fields that are saved in the document format
// ============================================
//
/// The node graph that generates this document's artwork.
/// It recursively stores its sub-graphs, so this root graph is the whole snapshot of the document content.
#[serde(default = "default_network")]
pub network: NodeNetwork,
/// List of the [`NodeId`]s that are currently selected by the user.
#[serde(default = "default_selected_nodes")]
pub selected_nodes: SelectedNodes,
/// List of the [`LayerNodeIdentifier`]s that are currently collapsed by the user in the Layers panel.
/// Collapsed means that the expansion arrow isn't set to show the children of these layers.
#[serde(default = "default_collapsed")]
pub collapsed: CollapsedLayers,
/// The name of the document, which is displayed in the tab and title bar of the editor.
#[serde(default = "default_name")]
pub name: String,
/// The full Git commit hash of the Graphite repository that was used to build the editor.
/// We save this to provide a hint about which version of the editor was used to create the document.
#[serde(default = "default_commit_hash")]
commit_hash: String,
/// The current pan, tilt, and zoom state of the viewport's view of the document canvas.
#[serde(default = "default_pan_tilt_zoom")]
pub navigation: PTZ,
/// The current mode that the document is in, which starts out as Design Mode. This choice affects the editing behavior of the tools.
#[serde(default = "default_document_mode")]
document_mode: DocumentMode,
/// The current view mode that the user has set for rendering the document within the viewport.
/// This is usually "Normal" but can be set to "Outline" or "Pixels" to see the canvas differently.
#[serde(default = "default_view_mode")]
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.
#[serde(default = "default_overlays_visible")]
overlays_visible: bool,
/// Sets whether or not the rulers should be drawn along the top and left edges of the viewport area.
#[serde(default = "default_rulers_visible")]
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.
#[serde(default = "default_graph_view_overlay_open")]
graph_view_overlay_open: bool,
/// The current user choices for snapping behavior, including whether snapping is enabled at all.
#[serde(default = "default_snapping_state")]
pub snapping_state: SnappingState,
// =============================================
// Fields omitted from the saved document format
// =============================================
//
/// Stack of document network snapshots for previous history states.
#[serde(skip)]
document_undo_history: VecDeque<NodeNetwork>,
/// Stack of document network snapshots for future history states.
#[serde(skip)]
document_redo_history: VecDeque<NodeNetwork>,
/// Hash of the document snapshot that was most recently saved to disk by the user.
#[serde(skip)]
saved_hash: Option<u64>,
/// Hash of the document snapshot that was most recently auto-saved to the IndexedDB storage that will reopen when the editor is reloaded.
#[serde(skip)]
auto_saved_hash: Option<u64>,
/// Don't allow aborting transactions whilst undoing to avoid #559
/// Disallow aborting transactions whilst undoing to avoid #559.
#[serde(skip)]
undo_in_progress: bool,
#[serde(skip)]
graph_view_overlay_open: bool,
#[serde(skip)]
pub snapping_state: SnappingState,
/// The ID of the layer at the start of a range selection in the Layers panel.
/// If the user clicks or Ctrl-clicks one layer, it becomes the start of the range selection and then Shift-clicking another layer selects all layers between the start and end.
#[serde(skip)]
layer_range_selection_reference: Option<LayerNodeIdentifier>,
/// Stores stateful information about the document's network such as the graph's structural topology and which layers are hidden, locked, etc.
/// This is updated frequently, whenever the information it's derived from changes.
#[serde(skip)]
pub metadata: DocumentMetadata,
}
@ -330,7 +360,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
grid_overlay(self, &mut overlay_context)
}
}
DocumentMessage::GridVisible(enabled) => {
DocumentMessage::GridVisibility(enabled) => {
self.snapping_state.grid_snapping = enabled;
responses.add(OverlaysMessage::Draw);
}
@ -844,6 +874,20 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
}
responses.add(NodeGraphMessage::RunDocumentGraph);
}
DocumentMessage::ToggleGridVisibility => {
self.snapping_state.grid_snapping = !self.snapping_state.grid_snapping;
responses.add(OverlaysMessage::Draw);
responses.add(PortfolioMessage::UpdateDocumentWidgets);
}
DocumentMessage::ToggleOverlaysVisibility => {
self.overlays_visible = !self.overlays_visible;
responses.add(OverlaysMessage::Draw);
responses.add(PortfolioMessage::UpdateDocumentWidgets);
}
DocumentMessage::ToggleSnapping => {
self.snapping_state.snapping_enabled = !self.snapping_state.snapping_enabled;
responses.add(PortfolioMessage::UpdateDocumentWidgets);
}
DocumentMessage::Undo => {
self.undo_in_progress = true;
responses.add(ToolMessage::PreUndo);
@ -967,7 +1011,52 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
}
fn actions(&self) -> ActionList {
unimplemented!("Must use `actions_with_graph_open` instead (unless we change every implementation of the MessageHandler trait).")
let mut common = actions!(DocumentMessageDiscriminant;
CreateEmptyFolder,
DebugPrintDocument,
DeselectAllLayers,
GraphViewOverlayToggle,
Noop,
Redo,
SaveDocument,
SelectAllLayers,
SetSnapping,
ToggleGridVisibility,
ToggleOverlaysVisibility,
ToggleSnapping,
Undo,
ZoomCanvasTo100Percent,
ZoomCanvasTo200Percent,
ZoomCanvasToFitAll,
);
// Additional actions if there are any selected layers
if self.selected_nodes.selected_layers(self.metadata()).next().is_some() {
let select = actions!(DocumentMessageDiscriminant;
DeleteSelectedLayers,
DuplicateSelectedLayers,
GroupSelectedLayers,
NudgeSelectedLayers,
SelectedLayersLower,
SelectedLayersLowerToBack,
SelectedLayersRaise,
SelectedLayersRaiseToFront,
UngroupSelectedLayers,
);
common.extend(select);
}
// Additional actions if the node graph is open
if self.graph_view_overlay_open {
common.extend(actions!(DocumentMessageDiscriminant;
GraphViewOverlay,
));
common.extend(self.node_graph_handler.actions_additional_if_node_graph_is_open());
}
// More additional actions
common.extend(self.node_graph_handler.actions());
common.extend(self.navigation_handler.actions());
common
}
}
@ -1377,6 +1466,7 @@ impl DocumentMessageHandler {
CheckboxInput::new(self.overlays_visible)
.icon("Overlays")
.tooltip("Overlays")
.tooltip_shortcut(action_keys!(DocumentMessageDiscriminant::ToggleOverlaysVisibility))
.on_update(|optional_input: &CheckboxInput| DocumentMessage::SetOverlaysVisibility { visible: optional_input.checked }.into())
.widget_holder(),
PopoverButton::new()
@ -1393,6 +1483,7 @@ impl DocumentMessageHandler {
CheckboxInput::new(snapping_state.snapping_enabled)
.icon("Snapping")
.tooltip("Snapping")
.tooltip_shortcut(action_keys!(DocumentMessageDiscriminant::ToggleSnapping))
.on_update(move |optional_input: &CheckboxInput| {
let snapping_enabled = optional_input.checked;
DocumentMessage::SetSnapping {
@ -1508,7 +1599,8 @@ impl DocumentMessageHandler {
CheckboxInput::new(self.snapping_state.grid_snapping)
.icon("Grid")
.tooltip("Grid")
.on_update(|optional_input: &CheckboxInput| DocumentMessage::GridVisible(optional_input.checked).into())
.tooltip_shortcut(action_keys!(DocumentMessageDiscriminant::ToggleGridVisibility))
.on_update(|optional_input: &CheckboxInput| DocumentMessage::GridVisibility(optional_input.checked).into())
.widget_holder(),
PopoverButton::new()
.popover_layout(overlay_options(&self.snapping_state.grid))
@ -1819,47 +1911,6 @@ impl DocumentMessageHandler {
let insert_index = if relative_index_offset < 0 { neighbor_index } else { neighbor_index + 1 } as isize;
responses.add(DocumentMessage::MoveSelectedLayersTo { parent, insert_index });
}
pub fn actions_with_graph_open(&self) -> ActionList {
let mut common = actions!(DocumentMessageDiscriminant;
Noop,
Undo,
Redo,
SelectAllLayers,
DeselectAllLayers,
SaveDocument,
SetSnapping,
DebugPrintDocument,
ZoomCanvasToFitAll,
ZoomCanvasTo100Percent,
ZoomCanvasTo200Percent,
GraphViewOverlayToggle,
CreateEmptyFolder,
);
if self.graph_view_overlay_open {
let escape = actions!(DocumentMessageDiscriminant; GraphViewOverlay);
common.extend(escape);
}
if self.selected_nodes.selected_layers(self.metadata()).next().is_some() {
let select = actions!(DocumentMessageDiscriminant;
DeleteSelectedLayers,
DuplicateSelectedLayers,
NudgeSelectedLayers,
SelectedLayersLower,
SelectedLayersLowerToBack,
SelectedLayersRaise,
SelectedLayersRaiseToFront,
GroupSelectedLayers,
UngroupSelectedLayers,
);
common.extend(select);
}
common.extend(self.navigation_handler.actions());
common.extend(self.node_graph_handler.actions_with_node_graph_open(self.graph_view_overlay_open));
common
}
}
impl Default for DocumentMessageHandler {
@ -1941,6 +1992,14 @@ fn default_overlays_visible() -> bool {
fn default_rulers_visible() -> bool {
DocumentMessageHandler::default().rulers_visible
}
#[inline(always)]
fn default_graph_view_overlay_open() -> bool {
DocumentMessageHandler::default().graph_view_overlay_open
}
#[inline(always)]
fn default_snapping_state() -> SnappingState {
DocumentMessageHandler::default().snapping_state
}
fn root_network() -> NodeNetwork {
{

View File

@ -144,7 +144,7 @@ pub enum NodeGraphMessage {
TogglePreviewImpl {
node_id: NodeId,
},
ToggleSelectedLayers,
ToggleSelectedAsLayersOrNodes,
ToggleSelectedLocked,
ToggleSelectedVisibility,
ToggleVisibility {

View File

@ -702,7 +702,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
document_metadata.load_structure(document_network, selected_nodes);
self.update_selection_action_buttons(document_network, document_metadata, selected_nodes, responses);
}
NodeGraphMessage::ToggleSelectedLayers => {
NodeGraphMessage::ToggleSelectedAsLayersOrNodes => {
let Some(network) = document_network.nested_network_mut(&self.network) else { return };
for node_id in selected_nodes.selected_nodes() {
@ -788,16 +788,28 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
}
fn actions(&self) -> ActionList {
unimplemented!("Must use `actions_with_node_graph_open` instead (unless we change every implementation of the MessageHandler trait).")
if self.has_selection {
actions!(NodeGraphMessageDiscriminant;
ToggleSelectedLocked,
ToggleSelectedVisibility,
)
} else {
actions!(NodeGraphMessageDiscriminant;)
}
}
}
impl NodeGraphMessageHandler {
pub fn actions_with_node_graph_open(&self, graph_open: bool) -> ActionList {
if self.has_selection && graph_open {
actions!(NodeGraphMessageDiscriminant; ToggleSelectedVisibility, ToggleSelectedLocked, ToggleSelectedLayers, DuplicateSelectedNodes, DeleteSelectedNodes, Cut, Copy)
} else if self.has_selection {
actions!(NodeGraphMessageDiscriminant; ToggleSelectedVisibility, ToggleSelectedLocked)
/// Similar to [`NodeGraphMessageHandler::actions`], but this provides additional actions if the node graph is open and should only be called in that circumstance.
pub fn actions_additional_if_node_graph_is_open(&self) -> ActionList {
if self.has_selection {
actions!(NodeGraphMessageDiscriminant;
Copy,
Cut,
DeleteSelectedNodes,
DuplicateSelectedNodes,
ToggleSelectedAsLayersOrNodes,
)
} else {
actions!(NodeGraphMessageDiscriminant;)
}

View File

@ -53,7 +53,7 @@ impl DocumentMode {
}
}
#[derive(Clone, Debug)]
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
/// SnappingState determines the current individual snapping states
pub struct SnappingState {
pub snapping_enabled: bool,

View File

@ -536,16 +536,17 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
ToggleRulers,
);
// Extend with actions that require an active document
if let Some(document) = self.active_document() {
common.extend(document.actions());
// Extend with actions that must have a selected layer
if document.selected_nodes.selected_layers(document.metadata()).next().is_some() {
let select = actions!(PortfolioMessageDiscriminant;
common.extend(actions!(PortfolioMessageDiscriminant;
Copy,
Cut,
);
common.extend(select);
));
}
common.extend(document.actions_with_graph_open());
}
common