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
111 changes: 109 additions & 2 deletions editor/src/messages/portfolio/document/document_message_handler.rs
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 All @@ -34,7 +34,7 @@
use graphene_std::math::quad::Quad;
use graphene_std::path_bool_nodes::boolean_intersect;
use graphene_std::raster::BlendMode;
use graphene_std::raster_types::Raster;
use graphene_std::raster_types::{CPU, Raster};

Check failure on line 37 in editor/src/messages/portfolio/document/document_message_handler.rs

View workflow job for this annotation

GitHub Actions / test

unused import: `CPU`

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

View workflow job for this annotation

GitHub Actions / build / web

unused import: `CPU`
use graphene_std::render_node::wgpu_available;
use graphene_std::subpath::Subpath;
use graphene_std::table::Table;
Expand Down Expand Up @@ -116,6 +116,19 @@
/// 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 rasterized bitmap of the mask group, captured on exit for application to target layers.
#[serde(skip)]
pub mask_raster_bitmap: Option<Table<Raster<graphene_std::raster_types::CPU>>>,
/// The path of the to the document file.
#[serde(skip)]
pub(crate) path: Option<PathBuf>,
Expand Down Expand Up @@ -174,6 +187,10 @@
// 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(),
mask_raster_bitmap: None,
path: None,
breadcrumb_network_path: Vec::new(),
selection_network_path: Vec::new(),
Expand Down Expand Up @@ -1115,6 +1132,87 @@
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);
}
}
Comment thread
krVatsal marked this conversation as resolved.
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);
}
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
if let Some(mask_group_id) = self.mask_group_id {
responses.add(DocumentMessage::AddTransaction);
// Store the mask bitmap placeholder (TODO: render mask group with for_mask=true)
self.mask_raster_bitmap = Some(Table::new());
responses.add(GraphOperationMessage::ApplyMaskStencil {
layers: self.mask_target_layers.clone(),
mask_group: LayerNodeIdentifier::new_unchecked(mask_group_id),
});
responses.add(DocumentMessage::EndTransaction);
}
}

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

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 +1568,8 @@
SaveDocument,
SelectAllLayers,
SetSnapping,
ToggleDocumentMode,
SetDocumentMode,
ToggleGridVisibility,
ToggleOverlaysVisibility,
ToggleSnapping,
Expand Down Expand Up @@ -2084,6 +2184,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,8 @@ 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>,
mask_group: LayerNodeIdentifier,
},
}
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,27 @@ 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, mask_group } => {
// Use the provided mask source by placing it above the first target and clipping it to that target.
if let Some(primary_target) = layers.first().copied() {
if primary_target != mask_group {
if let Some(parent) = primary_target.parent(network_interface.document_metadata()) {
if let Some(insert_index) = parent.children(network_interface.document_metadata()).position(|child| child == primary_target) {
network_interface.move_layer_to_stack(mask_group, parent, insert_index, &[]);
responses.add(GraphOperationMessage::ClipModeToggle { layer: mask_group });
}
}
}
}

// Remaining targets keep existing fallback behavior until raster stencil wiring is implemented.
for layer in layers.into_iter().skip(1) {
responses.add(GraphOperationMessage::ClipModeToggle { layer });
Comment thread
krVatsal marked this conversation as resolved.
}
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
6 changes: 6 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading