Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions editor/src/messages/input_mapper/input_mappings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,7 @@ pub fn input_mappings(zoom_with_scroll: bool) -> Mapping {
//
// DocumentMessage
entry!(KeyDown(Space); modifiers=[Control], action_dispatch=DocumentMessage::GraphViewOverlayToggle),
entry!(KeyDown(KeyQ); action_dispatch=DocumentMessage::ToggleDocumentMode),
entry!(KeyDownNoRepeat(Escape); action_dispatch=DocumentMessage::Escape),
entry!(KeyDown(Delete); action_dispatch=DocumentMessage::DeleteSelectedLayers),
entry!(KeyDown(Backspace); action_dispatch=DocumentMessage::DeleteSelectedLayers),
Expand Down
13 changes: 12 additions & 1 deletion editor/src/messages/portfolio/document/document_message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use crate::messages::input_mapper::utility_types::input_keyboard::Key;
use crate::messages::portfolio::document::data_panel::DataPanelMessage;
use crate::messages::portfolio::document::overlays::utility_types::{OverlayContext, 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::document::utility_types::misc::{AlignAggregate, AlignAxis, DocumentMode, FlipAxis, GridSnapping};
use crate::messages::portfolio::utility_types::PanelType;
use crate::messages::prelude::*;
use glam::{DAffine2, IVec2};
Expand Down Expand Up @@ -189,6 +189,17 @@ pub enum DocumentMessage {
SetRenderMode {
render_mode: RenderMode,
},
ToggleDocumentMode,
SetDocumentMode {
document_mode: DocumentMode,
},
EnterMaskMode,
ExitMaskMode {
discard: bool,
},
DrawMarchingAntsOverlay {
context: OverlayContext,
},
AddTransaction,
StartTransaction,
EndTransaction,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
use crate::messages::portfolio::document::overlays::utility_types::{OverlaysType, OverlaysVisibilitySettings, Pivot};
use crate::messages::portfolio::document::properties_panel::properties_panel_message_handler::PropertiesPanelMessageContext;
use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate, AlignAxis, FlipAxis, PTZ};
use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate, AlignAxis, DocumentMode, FlipAxis, PTZ};
use crate::messages::portfolio::document::utility_types::network_interface::{FlowType, InputConnector, NodeTemplate};
use crate::messages::portfolio::utility_types::{PanelType, PersistentData};
use crate::messages::prelude::*;
Expand Down Expand Up @@ -116,6 +116,16 @@
/// The name of the document, which is displayed in the tab and title bar of the editor.
#[serde(skip)]
pub name: String,
/// The current editor-only mode for the active document.
#[serde(skip)]
pub document_mode: DocumentMode,
/// The NodeId of the mask group layer when in MaskMode, or None otherwise.
#[serde(skip)]
pub mask_group_id: Option<NodeId>,
/// The layers that are targets for the current mask operation.
/// When entering MaskMode, this stores the selected layers so the mask can be applied to them on exit.
#[serde(skip)]
pub mask_target_layers: Vec<LayerNodeIdentifier>,
/// The path of the to the document file.
#[serde(skip)]
pub(crate) path: Option<PathBuf>,
Expand Down Expand Up @@ -174,6 +184,9 @@
// Fields omitted from the saved document format
// =============================================
name: DEFAULT_DOCUMENT_NAME.to_string(),
document_mode: DocumentMode::default(),
mask_group_id: None,
mask_target_layers: Vec::new(),
path: None,
breadcrumb_network_path: Vec::new(),
selection_network_path: Vec::new(),
Expand Down Expand Up @@ -1115,6 +1128,81 @@
self.render_mode = render_mode;
responses.add_front(NodeGraphMessage::RunDocumentGraph);
}
DocumentMessage::ToggleDocumentMode => {
self.document_mode = match self.document_mode {
DocumentMode::MaskMode => DocumentMode::DesignMode,
_ => DocumentMode::MaskMode,
};
responses.add(PortfolioMessage::UpdateDocumentWidgets);
}
DocumentMessage::SetDocumentMode { document_mode } => {
if self.document_mode != document_mode {
self.document_mode = document_mode;
responses.add(PortfolioMessage::UpdateDocumentWidgets);
}
}
DocumentMessage::EnterMaskMode => {
if self.document_mode == DocumentMode::MaskMode {
return;
}

self.document_mode = DocumentMode::MaskMode;

// Store the currently selected layers as mask targets
self.mask_target_layers = self.network_interface.selected_nodes().selected_layers(self.network_interface.document_metadata()).collect();
let layer_parent = self.new_layer_parent(true);

// Create a new group layer for the mask
let mask_group_id = NodeId::new();
self.mask_group_id = Some(mask_group_id);

responses.add(DocumentMessage::AddTransaction);

// Create the mask group layer as an empty custom layer container
graph_modification_utils::new_custom(mask_group_id, Vec::new(), layer_parent, responses);

// Set the mask group opacity to 50% so the user can see artwork beneath
responses.add(GraphOperationMessage::OpacitySet {
layer: LayerNodeIdentifier::new_unchecked(mask_group_id),
opacity: 0.5,
});

responses.add(DocumentMessage::EndTransaction);
responses.add(PortfolioMessage::UpdateDocumentWidgets);
}
Comment thread
krVatsal marked this conversation as resolved.
DocumentMessage::ExitMaskMode { discard } => {
if self.document_mode != DocumentMode::MaskMode {
return;
}

self.document_mode = DocumentMode::DesignMode;

if discard {
// Delete the mask group without applying it
if let Some(mask_group_id) = self.mask_group_id {
responses.add(DocumentMessage::AddTransaction);
responses.add(NodeGraphMessage::DeleteNodes {
node_ids: vec![mask_group_id],
delete_children: true,
});
responses.add(DocumentMessage::EndTransaction);
}
} else {
// Apply the mask group to the target layers

Check warning on line 1191 in editor/src/messages/portfolio/document/document_message_handler.rs

View workflow job for this annotation

GitHub Actions / rust-fmt

Diff in /home/runner/work/Graphite/Graphite/editor/src/messages/portfolio/document/document_message_handler.rs
if self.mask_group_id.is_some() {
responses.add(DocumentMessage::AddTransaction);
responses.add(GraphOperationMessage::ApplyMaskStencil { layers: self.mask_target_layers.clone() });
responses.add(DocumentMessage::EndTransaction);
}
}

// Clear mask state
self.mask_group_id = None;
self.mask_target_layers.clear();

responses.add(PortfolioMessage::UpdateDocumentWidgets);
}
DocumentMessage::DrawMarchingAntsOverlay { context: _ } => {}
DocumentMessage::AddTransaction => {
// Reverse order since they are added to the front
responses.add_front(DocumentMessage::CommitTransaction);
Expand Down Expand Up @@ -1470,6 +1558,8 @@
SaveDocument,
SelectAllLayers,
SetSnapping,
ToggleDocumentMode,
SetDocumentMode,
ToggleGridVisibility,
ToggleOverlaysVisibility,
ToggleSnapping,
Expand Down Expand Up @@ -2084,6 +2174,13 @@

/// Finds the parent folder which, based on the current selections, should be the container of any newly added layers.
pub fn new_layer_parent(&self, include_self: bool) -> LayerNodeIdentifier {
// If we're in MaskMode and have a mask group, all new layers should go into the mask group
if self.document_mode == DocumentMode::MaskMode
&& let Some(mask_group_id) = self.mask_group_id
{
return LayerNodeIdentifier::new_unchecked(mask_group_id);
}

let Some(selected_nodes) = self.network_interface.selected_nodes_in_nested_network(&self.selection_network_path) else {
warn!("No selected nodes found in new_layer_parent. Defaulting to ROOT_PARENT.");
return LayerNodeIdentifier::ROOT_PARENT;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,4 +126,7 @@ pub enum GraphOperationMessage {
/// When true, centers the SVG at the transform origin (clipboard paste / drag-drop). When false, keeps natural SVG coordinates (file-open flow).
center: bool,
},
ApplyMaskStencil {
layers: Vec<LayerNodeIdentifier>,
},
}
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,15 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageContext<'_>> for
// (skipped automatically when identity, so file-open with content at origin creates no Transform node).
modify_inputs.transform_set(placement_transform, TransformIn::Local, false);
}
GraphOperationMessage::ApplyMaskStencil { layers } => {
// For each target layer, toggle clip mode so the existing clip infrastructure is engaged.
for layer in layers {
responses.add(GraphOperationMessage::ClipModeToggle { layer });
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: ApplyMaskStencil uses ClipModeToggle, making mask application state-dependent and capable of un-clipping already clipped target layers.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At editor/src/messages/portfolio/document/graph_operation/graph_operation_message_handler.rs, line 447:

<comment>`ApplyMaskStencil` uses `ClipModeToggle`, making mask application state-dependent and capable of un-clipping already clipped target layers.</comment>

<file context>
@@ -439,6 +439,17 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageContext<'_>> for
+
+				// For each target layer, toggle clip mode so the existing clip infrastructure is engaged.
+				for layer in layers {
+					responses.add(GraphOperationMessage::ClipModeToggle { layer });
+				}
+				responses.add(NodeGraphMessage::RunDocumentGraph);
</file context>

}
responses.add(NodeGraphMessage::RunDocumentGraph);
responses.add(NodeGraphMessage::SelectedNodesUpdated);
responses.add(NodeGraphMessage::SendGraph);
}
}
}

Expand Down
35 changes: 19 additions & 16 deletions editor/src/messages/portfolio/document/utility_types/misc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,23 +29,26 @@ pub enum AlignAggregate {
Center,
}

// #[derive(Default, PartialEq, Eq, Clone, Copy, Debug, serde::Serialize, serde::Deserialize)]
// pub enum DocumentMode {
// #[default]
// DesignMode,
// SelectMode,
// GuideMode,
// }
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
#[derive(Default, PartialEq, Eq, Clone, Copy, Debug, serde::Serialize, serde::Deserialize)]
pub enum DocumentMode {
#[default]
DesignMode,
SelectMode,
GuideMode,
MaskMode,
}

// impl fmt::Display for DocumentMode {
// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// match self {
// DocumentMode::DesignMode => write!(f, "Design Mode"),
// DocumentMode::SelectMode => write!(f, "Select Mode"),
// DocumentMode::GuideMode => write!(f, "Guide Mode"),
// }
// }
// }
impl fmt::Display for DocumentMode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
DocumentMode::DesignMode => write!(f, "Design Mode"),
DocumentMode::SelectMode => write!(f, "Select Mode"),
DocumentMode::GuideMode => write!(f, "Guide Mode"),
DocumentMode::MaskMode => write!(f, "Mask Mode"),
}
}
}

// impl DocumentMode {
// pub fn icon_name(&self) -> String {
Expand Down
Loading