Remove all references to legacy layers (#1523)

* Remove visible field from LegacyLayer

* Remove LegacyLayer wrapper around LegacyLayerType

* Remove FolderLegacyLayer and LayerLegacyLayer wrappers around their data

* Remove legacy layers
This commit is contained in:
Keavon Chambers
2023-12-19 20:50:45 -08:00
parent 9a7d7de8fa
commit dcd38f2e4c
26 changed files with 211 additions and 1078 deletions

View File

@@ -37,7 +37,6 @@ const SIDE_EFFECT_FREE_MESSAGES: &[MessageDiscriminant] = &[
MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::PropertiesPanel(
PropertiesPanelMessageDiscriminant::Refresh,
))),
MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::FolderChanged)),
MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::DocumentStructureChanged)),
MessageDiscriminant::Frontend(FrontendMessageDiscriminant::UpdateDocumentLayerTreeStructure),
MessageDiscriminant::Frontend(FrontendMessageDiscriminant::TriggerFontLoad),
@@ -453,41 +452,6 @@ mod test {
assert_eq!(layers_after_copy[5], shape_id);
}
#[test]
#[ignore] // TODO: Re-enable test, see issue #444 (https://github.com/GraphiteEditor/Graphite/pull/444)
/// - create rect, shape and ellipse
/// - select ellipse and rect
/// - move them down and back up again
fn move_selection() {
let mut editor = create_editor_with_three_layers();
fn map_to_vec(paths: Vec<&[LayerId]>) -> Vec<Vec<LayerId>> {
paths.iter().map(|layer| layer.to_vec()).collect::<Vec<_>>()
}
let sorted_layers = map_to_vec(editor.dispatcher.message_handlers.portfolio_message_handler.active_document().unwrap().all_layers_sorted());
println!("Sorted layers: {sorted_layers:?}");
let verify_order = |handler: &mut DocumentMessageHandler| {
(
map_to_vec(handler.all_layers_sorted()),
map_to_vec(handler.non_selected_layers_sorted()),
map_to_vec(handler.selected_layers_sorted()),
)
};
editor.handle_message(DocumentMessage::SelectedLayersRaise);
let (all, non_selected, selected) = verify_order(editor.dispatcher.message_handlers.portfolio_message_handler.active_document_mut().unwrap());
assert_eq!(all, non_selected.into_iter().chain(selected).collect::<Vec<_>>());
editor.handle_message(DocumentMessage::SelectedLayersLower);
let (all, non_selected, selected) = verify_order(editor.dispatcher.message_handlers.portfolio_message_handler.active_document_mut().unwrap());
assert_eq!(all, selected.into_iter().chain(non_selected).collect::<Vec<_>>());
editor.handle_message(DocumentMessage::SelectedLayersRaiseToFront);
let (all, non_selected, selected) = verify_order(editor.dispatcher.message_handlers.portfolio_message_handler.active_document_mut().unwrap());
assert_eq!(all, non_selected.into_iter().chain(selected).collect::<Vec<_>>());
}
#[test]
/// If this test is failing take a look at `GRAPHITE_DOCUMENT_VERSION` in `editor/src/consts.rs`, it may need to be updated.
/// This test will fail when you make changes to the underlying serialization format for a document.

View File

@@ -2,12 +2,10 @@ use crate::messages::input_mapper::utility_types::input_keyboard::KeysGroup;
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::prelude::*;
use document_legacy::document::LayerId;
use graphene_core::raster::color::Color;
use graphene_core::text::Font;
use serde_json::Value;
use std::ops::Not;
#[derive(Debug, Clone, Default)]
pub struct LayoutMessageHandler {
@@ -157,19 +155,6 @@ impl<F: Fn(&MessageDiscriminant) -> Vec<KeysGroup>> MessageHandler<LayoutMessage
let callback_message = (invisible.on_update.callback)(&());
responses.add(callback_message);
}
Widget::LayerReferenceInput(layer_reference_input) => {
let update_value = value.is_null().not().then(|| {
value
.as_str()
.expect("LayerReferenceInput update was not of type: string")
.split(',')
.map(|id| id.parse::<LayerId>().unwrap())
.collect::<Vec<_>>()
});
layer_reference_input.value = update_value;
let callback_message = (layer_reference_input.on_update.callback)(layer_reference_input);
responses.add(callback_message);
}
Widget::NumberInput(number_input) => match value {
Value::Number(num) => {
let update_value = num.as_f64().unwrap();

View File

@@ -326,7 +326,6 @@ impl LayoutGroup {
Widget::IconButton(x) => &mut x.tooltip,
Widget::IconLabel(x) => &mut x.tooltip,
Widget::ImageLabel(x) => &mut x.tooltip,
Widget::LayerReferenceInput(x) => &mut x.tooltip,
Widget::NumberInput(x) => &mut x.tooltip,
Widget::OptionalInput(x) => &mut x.tooltip,
Widget::ParameterExposeButton(x) => &mut x.tooltip,
@@ -478,7 +477,6 @@ pub enum Widget {
IconLabel(IconLabel),
ImageLabel(ImageLabel),
InvisibleStandinInput(InvisibleStandinInput),
LayerReferenceInput(LayerReferenceInput),
NumberInput(NumberInput),
OptionalInput(OptionalInput),
ParameterExposeButton(ParameterExposeButton),
@@ -548,7 +546,6 @@ impl DiffUpdate {
Widget::DropdownInput(widget) => Some((&mut widget.tooltip, &mut widget.tooltip_shortcut)),
Widget::FontInput(widget) => Some((&mut widget.tooltip, &mut widget.tooltip_shortcut)),
Widget::IconButton(widget) => Some((&mut widget.tooltip, &mut widget.tooltip_shortcut)),
Widget::LayerReferenceInput(widget) => Some((&mut widget.tooltip, &mut widget.tooltip_shortcut)),
Widget::NumberInput(widget) => Some((&mut widget.tooltip, &mut widget.tooltip_shortcut)),
Widget::OptionalInput(widget) => Some((&mut widget.tooltip, &mut widget.tooltip_shortcut)),
Widget::ParameterExposeButton(widget) => Some((&mut widget.tooltip, &mut widget.tooltip_shortcut)),

View File

@@ -1,8 +1,6 @@
use crate::messages::input_mapper::utility_types::misc::ActionKeys;
use crate::messages::layout::utility_types::widget_prelude::*;
use document_legacy::document::LayerId;
use document_legacy::layers::layer_info::LayerDataTypeDiscriminant;
use graphene_core::raster::curve::Curve;
use graphite_proc_macros::WidgetBuilder;
@@ -137,33 +135,6 @@ pub struct InvisibleStandinInput {
pub on_update: WidgetCallback<()>,
}
#[derive(Clone, Serialize, Deserialize, Derivative, WidgetBuilder, specta::Type)]
#[derivative(Debug, PartialEq, Default)]
pub struct LayerReferenceInput {
#[widget_builder(constructor)]
pub value: Option<Vec<LayerId>>,
#[serde(rename = "layerName")]
#[widget_builder(constructor)]
pub layer_name: Option<String>,
#[serde(rename = "layerType")]
#[widget_builder(constructor)]
pub layer_type: Option<LayerDataTypeDiscriminant>,
pub disabled: bool,
pub tooltip: String,
#[serde(skip)]
pub tooltip_shortcut: Option<ActionKeys>,
// Callbacks
#[serde(skip)]
#[derivative(Debug = "ignore", PartialEq = "ignore")]
pub on_update: WidgetCallback<LayerReferenceInput>,
}
#[derive(Clone, Serialize, Deserialize, Derivative, WidgetBuilder, specta::Type)]
#[derivative(Debug, PartialEq, Default)]
pub struct NumberInput {

View File

@@ -36,9 +36,6 @@ pub enum DocumentMessage {
// Messages
AbortTransaction,
AddSelectedLayers {
additional_layers: Vec<Vec<LayerId>>,
},
AlignSelectedLayers {
axis: AlignAxis,
aggregate: AlignAggregate,
@@ -65,9 +62,6 @@ pub enum DocumentMessage {
FlipSelectedLayers {
flip_axis: FlipAxis,
},
FolderChanged {
affected_folder_path: Vec<LayerId>,
},
GroupSelectedLayers,
ImaginateClear {
layer_path: Vec<LayerId>,
@@ -83,9 +77,6 @@ pub enum DocumentMessage {
InputFrameRasterizeRegionBelowLayer {
layer_path: Vec<LayerId>,
},
LayerChanged {
affected_layer_path: Vec<LayerId>,
},
MoveSelectedLayersTo {
parent: LayerNodeIdentifier,
insert_index: isize,

View File

@@ -7,7 +7,7 @@ use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::portfolio::document::node_graph::NodeGraphHandlerData;
use crate::messages::portfolio::document::properties_panel::utility_types::PropertiesPanelMessageHandlerData;
use crate::messages::portfolio::document::utility_types::clipboards::Clipboard;
use crate::messages::portfolio::document::utility_types::layer_panel::{LayerMetadata, LayerPanelEntry, RawBuffer};
use crate::messages::portfolio::document::utility_types::layer_panel::{LayerMetadata, RawBuffer};
use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate, AlignAxis, DocumentMode, DocumentSave, FlipAxis};
use crate::messages::portfolio::document::utility_types::vectorize_layer_metadata;
use crate::messages::portfolio::utility_types::PersistentData;
@@ -19,8 +19,6 @@ use crate::node_graph_executor::NodeGraphExecutor;
use document_legacy::document::Document as DocumentLegacy;
use document_legacy::document::LayerId;
use document_legacy::document_metadata::LayerNodeIdentifier;
use document_legacy::layers::layer_info::LayerDataTypeDiscriminant;
use document_legacy::DocumentError;
use graph_craft::document::value::TaggedValue;
use graph_craft::document::{NodeInput, NodeNetwork};
use graphene_core::raster::BlendMode;
@@ -175,17 +173,6 @@ impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHand
responses.extend([RenderDocument.into(), DocumentStructureChanged.into()]);
}
}
AddSelectedLayers { additional_layers } => {
for layer_path in &additional_layers {
responses.extend(self.select_layer(layer_path));
}
// TODO: Correctly update layer panel in clear_selection instead of here
responses.add(FolderChanged { affected_folder_path: vec![] });
responses.add(BroadcastEvent::SelectionChanged);
self.update_layers_panel_options_bar_widgets(responses);
}
AlignSelectedLayers { axis, aggregate } => {
self.backup(responses);
@@ -271,6 +258,8 @@ impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHand
DocumentHistoryBackward => self.undo(responses),
DocumentHistoryForward => self.redo(responses),
DocumentStructureChanged => {
self.update_layers_panel_options_bar_widgets(responses);
let data_buffer: RawBuffer = self.serialize_root().as_slice().into();
responses.add(FrontendMessage::UpdateDocumentLayerTreeStructure { data_buffer })
}
@@ -302,10 +291,6 @@ impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHand
responses.add(BroadcastEvent::DocumentIsDirty);
}
}
FolderChanged { affected_folder_path } => {
let affected_layer_path = affected_folder_path;
responses.extend([LayerChanged { affected_layer_path }.into(), DocumentStructureChanged.into()]);
}
GroupSelectedLayers => {
// TODO: Add code that changes the insert index of the new folder based on the selected layer
let parent = self.metadata().deepest_common_ancestor(self.metadata().selected_layers(), true).unwrap_or(LayerNodeIdentifier::ROOT);
@@ -354,12 +339,6 @@ impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHand
}
}
InputFrameRasterizeRegionBelowLayer { layer_path } => responses.add(PortfolioMessage::SubmitGraphRender { document_id, layer_path }),
LayerChanged { affected_layer_path } => {
if let Ok(layer_entry) = self.layer_panel_entry(affected_layer_path.clone()) {
responses.add(FrontendMessage::UpdateDocumentLayerDetails { data: layer_entry });
}
self.update_layers_panel_options_bar_widgets(responses);
}
MoveSelectedLayersTo { parent, insert_index } => {
let selected_layers = self.metadata().selected_layers().collect::<Vec<_>>();
@@ -368,7 +347,7 @@ impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHand
return;
}
let insert_index = self.update_insert_index(&selected_layers, parent, insert_index).unwrap();
let insert_index = self.update_insert_index(&selected_layers, parent, insert_index);
responses.add(PortfolioMessage::Copy { clipboard: Clipboard::Internal });
responses.add(DocumentMessage::DeleteSelectedLayers);
@@ -472,7 +451,7 @@ impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHand
responses.add(DocumentHistoryForward);
responses.add(BroadcastEvent::DocumentIsDirty);
responses.add(RenderDocument);
responses.add(FolderChanged { affected_folder_path: vec![] });
responses.add(DocumentStructureChanged);
}
RenameDocument { new_name } => {
self.name = new_name;
@@ -654,7 +633,7 @@ impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHand
responses.add(DocumentHistoryBackward);
responses.add(BroadcastEvent::DocumentIsDirty);
responses.add(RenderDocument);
responses.add(FolderChanged { affected_folder_path: vec![] });
responses.add(DocumentStructureChanged);
responses.add(UndoFinished);
}
UndoFinished => self.undo_in_progress = false,
@@ -764,14 +743,14 @@ impl DocumentMessageHandler {
val.unwrap()
}
pub fn deserialize_document(serialized_content: &str) -> Result<Self, DocumentError> {
let deserialized_result: Result<Self, DocumentError> = serde_json::from_str(serialized_content).map_err(|e| DocumentError::InvalidFile(e.to_string()));
pub fn deserialize_document(serialized_content: &str) -> Result<Self, EditorError> {
let deserialized_result: Result<Self, EditorError> = serde_json::from_str(serialized_content).map_err(|e| EditorError::DocumentDeserialization(e.to_string()));
match deserialized_result {
Ok(document) => {
if document.version == GRAPHITE_DOCUMENT_VERSION {
Ok(document)
} else {
Err(DocumentError::InvalidFile("Graphite document version mismatch".to_string()))
Err(EditorError::DocumentDeserialization("Graphite document version mismatch".to_string()))
}
}
Err(e) => Err(e),
@@ -788,71 +767,15 @@ impl DocumentMessageHandler {
}
pub fn with_name_and_content(name: String, serialized_content: String) -> Result<Self, EditorError> {
match Self::deserialize_document(&serialized_content) {
Ok(mut document) => {
document.name = name;
Ok(document)
}
Err(DocumentError::InvalidFile(msg)) => Err(EditorError::DocumentDeserialization(msg)),
_ => Err(EditorError::Document(String::from("Failed to open file"))),
}
}
pub fn is_unmodified_default(&self) -> bool {
self.serialize_root().len() == Self::default().serialize_root().len()
&& self.document_undo_history.is_empty()
&& self.document_redo_history.is_empty()
&& self.name.starts_with(DEFAULT_DOCUMENT_NAME)
}
fn select_layer(&mut self, path: &[LayerId]) -> Option<Message> {
println!("Select_layer fail: {:?}", self.all_layers_sorted());
if let Some(layer) = self.layer_metadata.get_mut(path) {
layer.selected = true;
let data = self.layer_panel_entry(path.to_vec()).ok()?;
(!path.is_empty()).then(|| FrontendMessage::UpdateDocumentLayerDetails { data }.into())
} else {
warn!("Tried to select non-existing layer {path:?}");
None
}
let mut document = Self::deserialize_document(&serialized_content)?;
document.name = name;
Ok(document)
}
pub fn selected_layers(&self) -> impl Iterator<Item = &[LayerId]> {
self.layer_metadata.iter().filter_map(|(path, data)| data.selected.then_some(path.as_slice()))
}
pub fn selected_layers_with_type(&self, discriminant: LayerDataTypeDiscriminant) -> impl Iterator<Item = &[LayerId]> {
self.selected_layers().filter(move |path| {
self.document_legacy
.layer(path)
.map(|layer| LayerDataTypeDiscriminant::from(&layer.data) == discriminant)
.unwrap_or(false)
})
}
pub fn non_selected_layers(&self) -> impl Iterator<Item = &[LayerId]> {
self.layer_metadata.iter().filter_map(|(path, data)| (!data.selected).then_some(path.as_slice()))
}
pub fn selected_layers_without_children(&self) -> Vec<&[LayerId]> {
let unique_layers = DocumentLegacy::shallowest_unique_layers(self.selected_layers());
// We need to maintain layer ordering
self.sort_layers(unique_layers.iter().copied())
}
pub fn selected_layers_contains(&self, path: &[LayerId]) -> bool {
self.layer_metadata.get(path).map(|layer| layer.selected).unwrap_or(false)
}
pub fn visible_layers(&self) -> impl Iterator<Item = &[LayerId]> {
self.all_layers().filter(|path| match self.document_legacy.layer(path) {
Ok(layer) => layer.visible,
Err(_) => false,
})
}
/// Returns the bounding boxes for all visible layers.
pub fn bounding_boxes<'a>(&'a self) -> impl Iterator<Item = [DVec2; 2]> + 'a {
// TODO: Remove this function entirely?
@@ -920,63 +843,10 @@ impl DocumentMessageHandler {
structure
}
/// Returns an unsorted list of all layer paths including folders at all levels, except the document's top-level root folder itself
pub fn all_layers(&self) -> impl Iterator<Item = &[LayerId]> {
self.layer_metadata.keys().filter_map(|path| (!path.is_empty()).then_some(path.as_slice()))
}
/// Returns the paths to all layers in order
fn sort_layers<'a>(&self, paths: impl Iterator<Item = &'a [LayerId]>) -> Vec<&'a [LayerId]> {
// Compute the indices for each layer to be able to sort them
let mut layers_with_indices: Vec<(&[LayerId], Vec<usize>)> = paths
// 'path.len() > 0' filters out root layer since it has no indices
.filter(|path| !path.is_empty())
.filter_map(|path| {
// TODO: `indices_for_path` can return an error. We currently skip these layers and log a warning. Once this problem is solved this code can be simplified.
match self.document_legacy.indices_for_path(path) {
Err(err) => {
warn!("layers_sorted: Could not get indices for the layer {path:?}: {err:?}");
None
}
Ok(indices) => Some((path, indices)),
}
})
.collect();
layers_with_indices.sort_by_key(|(_, indices)| indices.clone());
layers_with_indices.into_iter().map(|(path, _)| path).collect()
}
/// Returns the paths to all layers in order
pub fn all_layers_sorted(&self) -> Vec<&[LayerId]> {
self.sort_layers(self.all_layers())
}
/// Returns the paths to all selected layers in order
pub fn selected_layers_sorted(&self) -> Vec<&[LayerId]> {
self.sort_layers(self.selected_layers())
}
/// Returns the paths to all non_selected layers in order
#[allow(dead_code)] // used for test cases
pub fn non_selected_layers_sorted(&self) -> Vec<&[LayerId]> {
self.sort_layers(self.non_selected_layers())
}
pub fn layer_metadata(&self, path: &[LayerId]) -> &LayerMetadata {
self.layer_metadata.get(path).unwrap_or_else(|| panic!("Editor's layer metadata for {path:?} does not exist"))
}
pub fn layer_metadata_mut(&mut self, path: &[LayerId]) -> &mut LayerMetadata {
Self::layer_metadata_mut_no_borrow_self(&mut self.layer_metadata, path)
}
pub fn layer_metadata_mut_no_borrow_self<'a>(layer_metadata: &'a mut HashMap<Vec<LayerId>, LayerMetadata>, path: &[LayerId]) -> &'a mut LayerMetadata {
layer_metadata
.get_mut(path)
.unwrap_or_else(|| panic!("Layer data cannot be found because the path {path:?} does not exist"))
}
/// Places a document into the history system
fn backup_with_document(&mut self, document: DocumentLegacy, layer_metadata: HashMap<Vec<LayerId>, LayerMetadata>, responses: &mut VecDeque<Message>) {
self.document_redo_history.clear();
@@ -1002,12 +872,6 @@ impl DocumentMessageHandler {
});
}
pub fn rollback(&mut self, responses: &mut VecDeque<Message>) {
self.backup(responses);
self.undo(responses);
// TODO: Consider if we should check if the document is saved
}
/// Replace the document with a new document save, returning the document save.
pub fn replace_document(&mut self, DocumentSave { document, layer_metadata }: DocumentSave) -> DocumentSave {
// Keeping the root is required if the bounds of the viewport have changed during the operation
@@ -1042,10 +906,7 @@ impl DocumentMessageHandler {
self.document_redo_history.pop_front();
}
for layer in self.layer_metadata.keys() {
responses.add(DocumentMessage::LayerChanged { affected_layer_path: layer.clone() })
}
responses.add(DocumentMessage::DocumentStructureChanged);
responses.add(NodeGraphMessage::SendGraph { should_rerender: true });
}
}
@@ -1071,10 +932,7 @@ impl DocumentMessageHandler {
self.document_undo_history.pop_front();
}
for layer in self.layer_metadata.keys() {
responses.add(DocumentMessage::LayerChanged { affected_layer_path: layer.clone() })
}
responses.add(DocumentMessage::DocumentStructureChanged);
responses.add(NodeGraphMessage::SendGraph { should_rerender: true });
}
}
@@ -1113,42 +971,14 @@ impl DocumentMessageHandler {
}
}
// TODO: This should probably take a slice not a vec, also why does this even exist when `layer_panel_entry_from_path` also exists?
pub fn layer_panel_entry(&mut self, path: Vec<LayerId>) -> Result<LayerPanelEntry, EditorError> {
let data: LayerMetadata = *self
.layer_metadata
.get_mut(&path)
.ok_or_else(|| EditorError::Document(format!("Could not get layer metadata for {path:?}")))?;
let layer = self.document_legacy.layer(&path)?;
let entry = LayerPanelEntry::new(&data, layer, path);
Ok(entry)
}
pub fn layer_panel_entry_from_path(&self, path: &[LayerId]) -> Option<LayerPanelEntry> {
let layer_metadata = self.layer_metadata(path);
let layer = self.document_legacy.layer(path).ok()?;
Some(LayerPanelEntry::new(layer_metadata, layer, path.to_vec()))
}
/// When working with an insert index, deleting the layers may cause the insert index to point to a different location (if the layer being deleted was located before the insert index).
///
/// This function updates the insert index so that it points to the same place after the specified `layers` are deleted.
fn update_insert_index(&self, layers: &[LayerNodeIdentifier], parent: LayerNodeIdentifier, insert_index: isize) -> Result<isize, DocumentError> {
fn update_insert_index(&self, layers: &[LayerNodeIdentifier], parent: LayerNodeIdentifier, insert_index: isize) -> isize {
let layer_ids_above = parent.children(self.metadata()).take(if insert_index < 0 { usize::MAX } else { insert_index as usize });
let new_insert_index = layer_ids_above.filter(|layer_id| !layers.contains(layer_id)).count() as isize;
Ok(new_insert_index)
}
/// Calculate the path that new layers should be inserted to.
/// Depends on the selected layers as well as their types (Folder/Non-Folder)
pub fn get_path_for_new_layer(&self) -> Vec<u64> {
// If the selected layers don't actually exist, a new uuid for the
// root folder will be returned
let mut path = self.document_legacy.shallowest_common_folder(self.selected_layers()).map_or(vec![], |v| v.to_vec());
path.push(generate_uuid());
path
new_insert_index
}
pub fn new_layer_parent(&self) -> LayerNodeIdentifier {

View File

@@ -564,7 +564,8 @@ impl<'a> ModifyInputsContext<'a> {
}
}
self.responses.add(self.document_metadata.retain_selected_nodes(|id| !delete_nodes.contains(id)));
self.document_metadata.retain_selected_nodes(|id| !delete_nodes.contains(id));
self.responses.add(BroadcastEvent::SelectionChanged);
self.responses.add(DocumentMessage::DocumentStructureChanged);
self.responses.add(NodeGraphMessage::SendGraph { should_rerender: true });

View File

@@ -147,12 +147,6 @@ impl Default for NodeGraphMessageHandler {
}
}
impl Into<Message> for document_legacy::document_metadata::SelectionChanged {
fn into(self) -> Message {
BroadcastMessage::TriggerEvent(BroadcastEvent::SelectionChanged).into()
}
}
impl NodeGraphMessageHandler {
/// Send the cached layout to the frontend for the options bar at the top of the node panel
fn send_node_bar_layout(&self, responses: &mut VecDeque<Message>) {
@@ -429,7 +423,8 @@ impl NodeGraphMessageHandler {
return false;
}
network.nodes.remove(&node_id);
responses.add(document.metadata.retain_selected_nodes(|&id| id != node_id));
document.metadata.retain_selected_nodes(|&id| id != node_id);
responses.add(BroadcastEvent::SelectionChanged);
true
}
@@ -627,13 +622,16 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
responses.add(DocumentMessage::StartTransaction);
let new_ids = &document.metadata.selected_nodes().map(|&id| (id, crate::application::generate_uuid())).collect();
responses.add(document.metadata.clear_selected_nodes());
document.metadata.clear_selected_nodes();
responses.add(BroadcastEvent::SelectionChanged);
// Copy the selected nodes
let copied_nodes = Self::copy_nodes(network, new_ids).collect::<Vec<_>>();
// Select the new nodes
responses.add(document.metadata.add_selected_nodes(copied_nodes.iter().map(|(node_id, _)| *node_id)));
document.metadata.add_selected_nodes(copied_nodes.iter().map(|(node_id, _)| *node_id));
responses.add(BroadcastEvent::SelectionChanged);
for (node_id, mut document_node) in copied_nodes {
// Shift duplicated node
@@ -649,7 +647,9 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
}
}
NodeGraphMessage::ExitNestedNetwork { depth_of_nesting } => {
responses.add(document.metadata.clear_selected_nodes());
document.metadata.clear_selected_nodes();
responses.add(BroadcastEvent::SelectionChanged);
for _ in 0..depth_of_nesting {
self.network.pop();
}
@@ -755,13 +755,16 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
}
NodeGraphMessage::RunDocumentGraph => responses.add(PortfolioMessage::SubmitGraphRender { document_id, layer_path: Vec::new() }),
NodeGraphMessage::SelectedNodesAdd { nodes } => {
responses.add(document.metadata.add_selected_nodes(nodes));
document.metadata.add_selected_nodes(nodes);
responses.add(BroadcastEvent::SelectionChanged);
}
NodeGraphMessage::SelectedNodesRemove { nodes } => {
responses.add(document.metadata.retain_selected_nodes(|node| !nodes.contains(node)));
document.metadata.retain_selected_nodes(|node| !nodes.contains(node));
responses.add(BroadcastEvent::SelectionChanged);
}
NodeGraphMessage::SelectedNodesSet { nodes } => {
responses.add(document.metadata.set_selected_nodes(nodes));
document.metadata.set_selected_nodes(nodes);
responses.add(BroadcastEvent::SelectionChanged);
responses.add(PropertiesPanelMessage::Refresh);
}
NodeGraphMessage::SendGraph { should_rerender } => {
@@ -953,7 +956,8 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
}
NodeGraphMessage::UpdateNewNodeGraph => {
if let Some(network) = document.document_network.nested_network(&self.network) {
responses.add(document.metadata.clear_selected_nodes());
document.metadata.clear_selected_nodes();
responses.add(BroadcastEvent::SelectionChanged);
Self::send_graph(network, &self.layer_path, graph_view_overlay_open, responses);

View File

@@ -5,10 +5,9 @@ use super::FrontendGraphDataType;
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::prelude::*;
use document_legacy::layers::layer_info::LayerDataTypeDiscriminant;
use graph_craft::document::value::TaggedValue;
use graph_craft::document::{DocumentNode, NodeId, NodeInput};
use graph_craft::imaginate_input::{ImaginateMaskStartingFill, ImaginateSamplingMethod, ImaginateServerStatus, ImaginateStatus};
use graph_craft::imaginate_input::{ImaginateSamplingMethod, ImaginateServerStatus, ImaginateStatus};
use graphene_core::memo::IORecord;
use graphene_core::raster::{
BlendMode, CellularDistanceFunction, CellularReturnType, Color, DomainWarpType, FractalType, ImageFrame, LuminanceCalculation, NoiseType, RedGreenBlue, RelativeAbsolute, SelectiveColorChoice,
@@ -1505,10 +1504,10 @@ pub fn imaginate_properties(document_node: &DocumentNode, node_id: NodeId, conte
let neg_index = resolve_input("Negative Prompt");
let base_img_index = resolve_input("Adapt Input Image");
let img_creativity_index = resolve_input("Image Creativity");
let mask_index = resolve_input("Masking Layer");
let inpaint_index = resolve_input("Inpaint");
let mask_blur_index = resolve_input("Mask Blur");
let mask_fill_index = resolve_input("Mask Starting Fill");
// let mask_index = resolve_input("Masking Layer");
// let inpaint_index = resolve_input("Inpaint");
// let mask_blur_index = resolve_input("Mask Blur");
// let mask_fill_index = resolve_input("Mask Starting Fill");
let faces_index = resolve_input("Improve Faces");
let tiling_index = resolve_input("Tiling");
@@ -1877,44 +1876,6 @@ pub fn imaginate_properties(document_node: &DocumentNode, node_id: NodeId, conte
)
};
let mut layer_reference_input_layer_is_some = false;
let layer_mask = {
let mut widgets = start_widgets(document_node, node_id, mask_index, "Masking Layer", FrontendGraphDataType::General, true);
if let NodeInput::Value {
tagged_value: TaggedValue::LayerPath(layer_path),
exposed: false,
} = &document_node.inputs[mask_index]
{
let layer_reference_input_layer = layer_path
.as_ref()
.and_then(|path| context.document.layer(path).ok())
.map(|layer| (layer.name.clone().unwrap_or_default(), LayerDataTypeDiscriminant::from(&layer.data)));
layer_reference_input_layer_is_some = layer_reference_input_layer.is_some();
let layer_reference_input_layer_name = layer_reference_input_layer.as_ref().map(|(layer_name, _)| layer_name);
let layer_reference_input_layer_type = layer_reference_input_layer.as_ref().map(|(_, layer_type)| layer_type);
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
if !transform_not_connected {
widgets.push(
LayerReferenceInput::new(layer_path.clone(), layer_reference_input_layer_name.cloned(), layer_reference_input_layer_type.cloned())
.disabled(!use_base_image)
.on_update(update_value(|input: &LayerReferenceInput| TaggedValue::LayerPath(input.value.clone()), node_id, mask_index))
.widget_holder(),
);
} else {
widgets.push(TextLabel::new("Requires Transform Input").italic(true).widget_holder());
}
}
LayoutGroup::Row { widgets }.with_tooltip(
"Reference to a layer or folder which masks parts of the input image. Image generation is constrained to masked areas.\n\
\n\
Black shapes represent the masked regions. Lighter shades of gray act as a partial mask, and colors become grayscale. (This is the reverse of traditional masks because it is easier to draw black shapes; this will be changed later when the mask input is a bitmap.)",
)
};
let mut layout = vec![
server_status,
progress,
@@ -1928,73 +1889,73 @@ pub fn imaginate_properties(document_node: &DocumentNode, node_id: NodeId, conte
negative_prompt,
base_image,
image_creativity,
layer_mask,
// layer_mask,
];
if use_base_image && layer_reference_input_layer_is_some {
let in_paint = {
let mut widgets = start_widgets(document_node, node_id, inpaint_index, "Inpaint", FrontendGraphDataType::Boolean, true);
// if use_base_image && layer_reference_input_layer_is_some {
// let in_paint = {
// let mut widgets = start_widgets(document_node, node_id, inpaint_index, "Inpaint", FrontendGraphDataType::Boolean, true);
if let &NodeInput::Value {
tagged_value: TaggedValue::Bool(in_paint),
exposed: false,
} = &document_node.inputs[inpaint_index]
{
widgets.extend_from_slice(&[
Separator::new(SeparatorType::Unrelated).widget_holder(),
RadioInput::new(
[(true, "Inpaint"), (false, "Outpaint")]
.into_iter()
.map(|(paint, name)| RadioEntryData::new(name).on_update(update_value(move |_| TaggedValue::Bool(paint), node_id, inpaint_index)))
.collect(),
)
.selected_index(Some(1 - in_paint as u32))
.widget_holder(),
]);
}
LayoutGroup::Row { widgets }.with_tooltip(
"Constrain image generation to the interior (inpaint) or exterior (outpaint) of the mask, while referencing the other unchanged parts as context imagery.\n\
\n\
An unwanted part of an image can be replaced by drawing around it with a black shape and inpainting with that mask layer.\n\
\n\
An image can be uncropped by resizing the Imaginate layer to the target bounds and outpainting with a black rectangle mask matching the original image bounds.",
)
};
// if let &NodeInput::Value {
// tagged_value: TaggedValue::Bool(in_paint),
// exposed: false,
// } = &document_node.inputs[inpaint_index]
// {
// widgets.extend_from_slice(&[
// Separator::new(SeparatorType::Unrelated).widget_holder(),
// RadioInput::new(
// [(true, "Inpaint"), (false, "Outpaint")]
// .into_iter()
// .map(|(paint, name)| RadioEntryData::new(name).on_update(update_value(move |_| TaggedValue::Bool(paint), node_id, inpaint_index)))
// .collect(),
// )
// .selected_index(Some(1 - in_paint as u32))
// .widget_holder(),
// ]);
// }
// LayoutGroup::Row { widgets }.with_tooltip(
// "Constrain image generation to the interior (inpaint) or exterior (outpaint) of the mask, while referencing the other unchanged parts as context imagery.\n\
// \n\
// An unwanted part of an image can be replaced by drawing around it with a black shape and inpainting with that mask layer.\n\
// \n\
// An image can be uncropped by resizing the Imaginate layer to the target bounds and outpainting with a black rectangle mask matching the original image bounds.",
// )
// };
let blur_radius = {
let number_props = NumberInput::default().unit(" px").min(0.).max(25.).int();
let widgets = number_widget(document_node, node_id, mask_blur_index, "Mask Blur", number_props, true);
LayoutGroup::Row { widgets }.with_tooltip("Blur radius for the mask. Useful for softening sharp edges to blend the masked area with the rest of the image.")
};
// let blur_radius = {
// let number_props = NumberInput::default().unit(" px").min(0.).max(25.).int();
// let widgets = number_widget(document_node, node_id, mask_blur_index, "Mask Blur", number_props, true);
// LayoutGroup::Row { widgets }.with_tooltip("Blur radius for the mask. Useful for softening sharp edges to blend the masked area with the rest of the image.")
// };
let mask_starting_fill = {
let mut widgets = start_widgets(document_node, node_id, mask_fill_index, "Mask Starting Fill", FrontendGraphDataType::General, true);
// let mask_starting_fill = {
// let mut widgets = start_widgets(document_node, node_id, mask_fill_index, "Mask Starting Fill", FrontendGraphDataType::General, true);
if let &NodeInput::Value {
tagged_value: TaggedValue::ImaginateMaskStartingFill(starting_fill),
exposed: false,
} = &document_node.inputs[mask_fill_index]
{
let mask_fill_content_modes = ImaginateMaskStartingFill::list();
let mut entries = Vec::with_capacity(mask_fill_content_modes.len());
for mode in mask_fill_content_modes {
entries.push(MenuListEntry::new(mode.to_string()).on_update(update_value(move |_| TaggedValue::ImaginateMaskStartingFill(mode), node_id, mask_fill_index)));
}
let entries = vec![entries];
// if let &NodeInput::Value {
// tagged_value: TaggedValue::ImaginateMaskStartingFill(starting_fill),
// exposed: false,
// } = &document_node.inputs[mask_fill_index]
// {
// let mask_fill_content_modes = ImaginateMaskStartingFill::list();
// let mut entries = Vec::with_capacity(mask_fill_content_modes.len());
// for mode in mask_fill_content_modes {
// entries.push(MenuListEntry::new(mode.to_string()).on_update(update_value(move |_| TaggedValue::ImaginateMaskStartingFill(mode), node_id, mask_fill_index)));
// }
// let entries = vec![entries];
widgets.extend_from_slice(&[
Separator::new(SeparatorType::Unrelated).widget_holder(),
DropdownInput::new(entries).selected_index(Some(starting_fill as u32)).widget_holder(),
]);
}
LayoutGroup::Row { widgets }.with_tooltip(
"Begin in/outpainting the masked areas using this fill content as the starting input image.\n\
\n\
Each option can be visualized by generating with 'Sampling Steps' set to 0.",
)
};
layout.extend_from_slice(&[in_paint, blur_radius, mask_starting_fill]);
}
// widgets.extend_from_slice(&[
// Separator::new(SeparatorType::Unrelated).widget_holder(),
// DropdownInput::new(entries).selected_index(Some(starting_fill as u32)).widget_holder(),
// ]);
// }
// LayoutGroup::Row { widgets }.with_tooltip(
// "Begin in/outpainting the masked areas using this fill content as the starting input image.\n\
// \n\
// Each option can be visualized by generating with 'Sampling Steps' set to 0.",
// )
// };
// layout.extend_from_slice(&[in_paint, blur_radius, mask_starting_fill]);
// }
let improve_faces = {
let widgets = bool_widget(document_node, node_id, faces_index, "Improve Faces", true);

View File

@@ -1,4 +1,3 @@
use document_legacy::DocumentError;
use graphene_core::raster::color::Color;
use thiserror::Error;
@@ -38,4 +37,3 @@ macro_rules! derive_from {
derive_from!(&str, Misc);
derive_from!(String, Misc);
derive_from!(Color, Color);
derive_from!(DocumentError, Document);

View File

@@ -1,5 +1,4 @@
use document_legacy::document::LayerId;
use document_legacy::layers::layer_info::{LayerDataTypeDiscriminant, LegacyLayer};
use serde::ser::SerializeStruct;
use serde::{Deserialize, Serialize};
@@ -30,7 +29,7 @@ impl Serialize for JsRawBuffer {
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Copy, specta::Type)]
#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq, Eq, Copy, specta::Type)]
pub struct LayerMetadata {
pub selected: bool,
pub expanded: bool,
@@ -42,31 +41,22 @@ impl LayerMetadata {
}
}
#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq, Eq, specta::Type)]
pub enum LayerClassification {
#[default]
Folder,
Artboard,
Layer,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, specta::Type)]
pub struct LayerPanelEntry {
pub name: String,
pub tooltip: String,
pub visible: bool,
#[serde(rename = "layerType")]
pub layer_type: LayerDataTypeDiscriminant,
#[serde(rename = "layerClassification")]
pub layer_classification: LayerClassification,
#[serde(rename = "layerMetadata")]
pub layer_metadata: LayerMetadata,
pub path: Vec<LayerId>,
pub thumbnail: String,
}
impl LayerPanelEntry {
// TODO: Deprecate this because it's using document-legacy layer data which is no longer linked to data from the node graph,
// TODO: so this doesn't feed `name` (that's fed elsewhere) or `visible` (that's broken entirely), etc.
pub fn new(layer_metadata: &LayerMetadata, layer: &LegacyLayer, path: Vec<LayerId>) -> Self {
Self {
name: "".to_string(), // Replaced before it gets used
tooltip: "".to_string(), // Replaced before it gets used
visible: layer.visible,
layer_type: (&layer.data).into(),
layer_metadata: *layer_metadata,
path,
thumbnail: r#"<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 0 0"></svg>"#.to_string(),
}
}
}

View File

@@ -146,12 +146,6 @@ impl MessageHandler<PortfolioMessage, (&InputPreprocessorMessageHandler, &Prefer
responses.add(PortfolioMessage::UpdateOpenDocumentsList);
responses.add(DocumentMessage::RenderDocument);
responses.add(DocumentMessage::DocumentStructureChanged);
if let Some(document) = self.active_document() {
for layer in document.layer_metadata.keys() {
responses.add(DocumentMessage::LayerChanged { affected_layer_path: layer.clone() });
}
}
}
PortfolioMessage::CloseDocumentWithConfirmation { document_id } => {
let target_document = self.documents.get(&document_id).unwrap();
@@ -474,9 +468,6 @@ impl MessageHandler<PortfolioMessage, (&InputPreprocessorMessageHandler, &Prefer
responses.add(FrontendMessage::UpdateActiveDocument { document_id });
responses.add(DocumentMessage::RenderDocument);
responses.add(DocumentMessage::DocumentStructureChanged);
for layer in self.documents.get(&document_id).unwrap().layer_metadata.keys() {
responses.add(DocumentMessage::LayerChanged { affected_layer_path: layer.clone() });
}
responses.add(BroadcastEvent::SelectionChanged);
responses.add(BroadcastEvent::DocumentIsDirty);
responses.add(PortfolioMessage::UpdateDocumentWidgets);
@@ -649,14 +640,6 @@ impl PortfolioMessageHandler {
fn load_document(&mut self, new_document: DocumentMessageHandler, document_id: u64, responses: &mut VecDeque<Message>) {
self.document_ids.push(document_id);
responses.extend(
new_document
.layer_metadata
.keys()
.filter_map(|path| new_document.layer_panel_entry_from_path(path))
.map(|entry| FrontendMessage::UpdateDocumentLayerDetails { data: entry }.into())
.collect::<Vec<_>>(),
);
new_document.update_layers_panel_options_bar_widgets(responses);
self.documents.insert(document_id, new_document);

View File

@@ -3,7 +3,6 @@ use crate::consts::{SNAP_AXIS_TOLERANCE, SNAP_POINT_TOLERANCE};
use crate::messages::prelude::*;
use document_legacy::document::LayerId;
use document_legacy::layers::layer_info::LegacyLayer;
use glam::DVec2;
@@ -102,66 +101,21 @@ impl SnapManager {
}
}
/// Add the [ManipulatorGroup]s (optionally including handles) of the specified shape layer to the snapping points
///
/// This should be called after start_snap
pub fn add_snap_path(
&mut self,
_document_message_handler: &DocumentMessageHandler,
_input: &InputPreprocessorMessageHandler,
_layer: &LegacyLayer,
_path: &[LayerId],
_include_handles: bool,
_ignore_points: &[ManipulatorPointInfo],
) {
todo!();
// let Some(vector_data) = &layer.as_vector_data() else { return };
// if !document_message_handler.snapping_state.node_snapping {
// return;
// };
// let transform = document_message_handler.document_legacy.multiply_transforms(path).unwrap();
// let snap_points = vector_data
// .manipulator_groups()
// .flat_map(|group| {
// if include_handles {
// [
// Some((ManipulatorPointId::new(group.id, SelectedType::Anchor), group.anchor)),
// group.in_handle.map(|pos| (ManipulatorPointId::new(group.id, SelectedType::InHandle), pos)),
// group.out_handle.map(|pos| (ManipulatorPointId::new(group.id, SelectedType::OutHandle), pos)),
// ]
// } else {
// [Some((ManipulatorPointId::new(group.id, SelectedType::Anchor), group.anchor)), None, None]
// }
// })
// .flatten()
// .filter(|&(point_id, _)| {
// !ignore_points.contains(&ManipulatorPointInfo {
// layer: LayerNodeIdentifier::from_path(path, document_message_handler.network()),
// point_id,
// })
// })
// .map(|(_, pos)| transform.transform_point2(pos));
// self.add_snap_points(document_message_handler, input, snap_points);
}
/// Adds all of the shape handles in the document, including bézier handles of the points specified
pub fn add_all_document_handles(
&mut self,
document_message_handler: &DocumentMessageHandler,
input: &InputPreprocessorMessageHandler,
include_handles: &[&[LayerId]],
exclude: &[&[LayerId]],
ignore_points: &[ManipulatorPointInfo],
_document_message_handler: &DocumentMessageHandler,
_input: &InputPreprocessorMessageHandler,
_include_handles: &[&[LayerId]],
_exclude: &[&[LayerId]],
_ignore_points: &[ManipulatorPointInfo],
) {
for path in document_message_handler.all_layers() {
if !exclude.contains(&path) {
let layer = document_message_handler.document_legacy.layer(path).expect("Could not get layer for snapping");
self.add_snap_path(document_message_handler, input, layer, path, include_handles.contains(&path), ignore_points);
}
}
// for path in document_message_handler.all_layers() {
// if !exclude.contains(&path) {
// let layer = document_message_handler.document_legacy.layer(path).expect("Could not get layer for snapping");
// self.add_snap_path(document_message_handler, input, layer, path, include_handles.contains(&path), ignore_points);
// }
// }
}
/// Finds the closest snap from an array of layers to the specified snap targets in viewport coords.

View File

@@ -2,13 +2,13 @@ use crate::consts::FILE_SAVE_SUFFIX;
use crate::messages::frontend::utility_types::FrontendImageData;
use crate::messages::frontend::utility_types::{ExportBounds, FileType};
use crate::messages::portfolio::document::node_graph::wrap_network_in_scope;
use crate::messages::portfolio::document::utility_types::layer_panel::LayerClassification;
use crate::messages::portfolio::document::utility_types::misc::{LayerMetadata, LayerPanelEntry};
use crate::messages::prelude::*;
use document_legacy::document::Document as DocumentLegacy;
use document_legacy::document::LayerId;
use document_legacy::document_metadata::LayerNodeIdentifier;
use document_legacy::layers::layer_info::{LayerDataTypeDiscriminant, LegacyLayerType};
use graph_craft::document::value::TaggedValue;
use graph_craft::document::{generate_uuid, DocumentNodeImplementation, NodeId, NodeNetwork};
use graph_craft::graphene_compiler::Compiler;
@@ -430,10 +430,6 @@ impl NodeGraphExecutor {
.expect("Failed to send imaginate preferences");
}
pub fn previous_output_type(&self, path: &[LayerId]) -> Option<Type> {
self.last_output_type.get(path).cloned().flatten()
}
pub fn introspect_node_in_network<T: std::any::Any + core::fmt::Debug, U, F1: FnOnce(&NodeNetwork) -> Option<NodeId>, F2: FnOnce(&T) -> U>(
&mut self,
network: &NodeNetwork,
@@ -493,17 +489,7 @@ impl NodeGraphExecutor {
/// Evaluates a node graph, computing the entire graph
pub fn submit_node_graph_evaluation(&mut self, document: &mut DocumentMessageHandler, layer_path: Vec<LayerId>, viewport_resolution: UVec2) -> Result<(), String> {
// Get the node graph layer
let network = if layer_path.is_empty() {
document.network().clone()
} else {
let layer = document.document_legacy.layer(&layer_path).map_err(|e| format!("No layer: {e:?}"))?;
let layer_layer = match &layer.data {
LegacyLayerType::Layer(layer) => Ok(layer),
_ => Err("Invalid layer type".to_string()),
}?;
layer_layer.network.clone()
};
let network = document.network().clone();
let render_config = RenderConfig {
viewport: Footprint {
@@ -625,11 +611,12 @@ impl NodeGraphExecutor {
data: LayerPanelEntry {
name: document.document_network.nodes.get(&node_id).map(|node| node.alias.clone()).unwrap_or_default(),
tooltip: if cfg!(debug_assertions) { format!("Layer ID: {node_id}") } else { "".into() },
visible: !document.document_network.disabled.contains(&layer.to_node()),
layer_type: if document.metadata.is_folder(layer) {
LayerDataTypeDiscriminant::Folder
layer_classification: if document.metadata.is_artboard(layer) {
LayerClassification::Artboard
} else if document.metadata.is_folder(layer) {
LayerClassification::Folder
} else {
LayerDataTypeDiscriminant::Layer
LayerClassification::Layer
},
layer_metadata: LayerMetadata {
expanded: layer.has_children(&document.metadata) && !collapsed_folders.contains(&layer),
@@ -645,9 +632,6 @@ impl NodeGraphExecutor {
document.metadata.update_click_targets(new_click_targets);
responses.extend(updates);
self.process_node_graph_output(node_graph_output, execution_context.layer_path.clone(), transform, responses)?;
responses.add(DocumentMessage::LayerChanged {
affected_layer_path: execution_context.layer_path,
});
responses.add(DocumentMessage::RenderDocument);
responses.add(DocumentMessage::DocumentStructureChanged);
responses.add(BroadcastEvent::DocumentIsDirty);