@@ -119,6 +119,13 @@ pub struct DocumentMessageHandler {
119119 /// The current editor-only mode for the active document.
120120 #[ serde( skip) ]
121121 pub document_mode : DocumentMode ,
122+ /// The NodeId of the mask group layer when in MaskMode, or None otherwise.
123+ #[ serde( skip) ]
124+ pub mask_group_id : Option < NodeId > ,
125+ /// The layers that are targets for the current mask operation.
126+ /// When entering MaskMode, this stores the selected layers so the mask can be applied to them on exit.
127+ #[ serde( skip) ]
128+ pub mask_target_layers : Vec < LayerNodeIdentifier > ,
122129 /// The path of the to the document file.
123130 #[ serde( skip) ]
124131 pub ( crate ) path : Option < PathBuf > ,
@@ -178,6 +185,8 @@ impl Default for DocumentMessageHandler {
178185 // =============================================
179186 name : DEFAULT_DOCUMENT_NAME . to_string ( ) ,
180187 document_mode : DocumentMode :: default ( ) ,
188+ mask_group_id : None ,
189+ mask_target_layers : Vec :: new ( ) ,
181190 path : None ,
182191 breadcrumb_network_path : Vec :: new ( ) ,
183192 selection_network_path : Vec :: new ( ) ,
@@ -1133,16 +1142,69 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
11331142 }
11341143 }
11351144 DocumentMessage :: EnterMaskMode => {
1136- if self . document_mode != DocumentMode :: MaskMode {
1137- self . document_mode = DocumentMode :: MaskMode ;
1138- responses. add ( PortfolioMessage :: UpdateDocumentWidgets ) ;
1145+ if self . document_mode == DocumentMode :: MaskMode {
1146+ return ;
11391147 }
1148+
1149+ self . document_mode = DocumentMode :: MaskMode ;
1150+
1151+ // Store the currently selected layers as mask targets
1152+ self . mask_target_layers = self . network_interface . selected_nodes ( ) . selected_layers ( self . network_interface . document_metadata ( ) ) . collect ( ) ;
1153+
1154+ // Create a new group layer for the mask
1155+ let mask_group_id = NodeId :: new ( ) ;
1156+ self . mask_group_id = Some ( mask_group_id) ;
1157+
1158+ responses. add ( DocumentMessage :: AddTransaction ) ;
1159+
1160+ // Create the mask group layer as an empty custom layer container
1161+ graph_modification_utils:: new_custom ( mask_group_id, Vec :: new ( ) , self . new_layer_parent ( true ) , responses) ;
1162+
1163+ // Set the mask group opacity to 50% so the user can see artwork beneath
1164+ responses. add ( GraphOperationMessage :: OpacitySet {
1165+ layer : LayerNodeIdentifier :: new_unchecked ( mask_group_id) ,
1166+ opacity : 0.5 ,
1167+ } ) ;
1168+
1169+ responses. add ( DocumentMessage :: EndTransaction ) ;
1170+ responses. add ( PortfolioMessage :: UpdateDocumentWidgets ) ;
11401171 }
1141- DocumentMessage :: ExitMaskMode { discard : _ } => {
1142- if self . document_mode != DocumentMode :: DesignMode {
1143- self . document_mode = DocumentMode :: DesignMode ;
1144- responses. add ( PortfolioMessage :: UpdateDocumentWidgets ) ;
1172+ DocumentMessage :: ExitMaskMode { discard } => {
1173+ if self . document_mode != DocumentMode :: MaskMode {
1174+ return ;
11451175 }
1176+
1177+ self . document_mode = DocumentMode :: DesignMode ;
1178+
1179+ if discard {
1180+ // Delete the mask group without applying it
1181+ if let Some ( mask_group_id) = self . mask_group_id {
1182+ responses. add ( DocumentMessage :: AddTransaction ) ;
1183+ responses. add ( NodeGraphMessage :: DeleteNodes {
1184+ node_ids : vec ! [ mask_group_id] ,
1185+ delete_children : true ,
1186+ } ) ;
1187+ responses. add ( DocumentMessage :: EndTransaction ) ;
1188+ }
1189+ } else {
1190+ // Rasterize the mask group and apply it to target layers
1191+ if let Some ( mask_group_id) = self . mask_group_id {
1192+ responses. add ( GraphOperationMessage :: ApplyMaskStencil {
1193+ layers : self . mask_target_layers . clone ( ) ,
1194+ mask_image : graphene_std:: raster:: Image :: new ( 1 , 1 , graphene_std:: Color :: WHITE ) ,
1195+ } ) ;
1196+ responses. add ( NodeGraphMessage :: DeleteNodes {
1197+ node_ids : vec ! [ mask_group_id] ,
1198+ delete_children : true ,
1199+ } ) ;
1200+ }
1201+ }
1202+
1203+ // Clear mask state
1204+ self . mask_group_id = None ;
1205+ self . mask_target_layers . clear ( ) ;
1206+
1207+ responses. add ( PortfolioMessage :: UpdateDocumentWidgets ) ;
11461208 }
11471209 DocumentMessage :: DrawMarchingAntsOverlay { context : _ } => { }
11481210 DocumentMessage :: AddTransaction => {
@@ -2116,6 +2178,13 @@ impl DocumentMessageHandler {
21162178
21172179 /// Finds the parent folder which, based on the current selections, should be the container of any newly added layers.
21182180 pub fn new_layer_parent ( & self , include_self : bool ) -> LayerNodeIdentifier {
2181+ // If we're in MaskMode and have a mask group, all new layers should go into the mask group
2182+ if self . document_mode == DocumentMode :: MaskMode
2183+ && let Some ( mask_group_id) = self . mask_group_id
2184+ {
2185+ return LayerNodeIdentifier :: new_unchecked ( mask_group_id) ;
2186+ }
2187+
21192188 let Some ( selected_nodes) = self . network_interface . selected_nodes_in_nested_network ( & self . selection_network_path ) else {
21202189 warn ! ( "No selected nodes found in new_layer_parent. Defaulting to ROOT_PARENT." ) ;
21212190 return LayerNodeIdentifier :: ROOT_PARENT ;
0 commit comments