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

@ -1,7 +1,4 @@
use crate::document_metadata::{is_artboard, DocumentMetadata, LayerNodeIdentifier}; use crate::document_metadata::{is_artboard, DocumentMetadata, LayerNodeIdentifier};
use crate::layers::folder_layer::FolderLegacyLayer;
use crate::layers::layer_info::{LegacyLayer, LegacyLayerType};
use crate::DocumentError;
use graph_craft::document::{DocumentNode, DocumentNodeImplementation, NodeId, NodeNetwork, NodeOutput}; use graph_craft::document::{DocumentNode, DocumentNodeImplementation, NodeId, NodeNetwork, NodeOutput};
use graphene_core::renderer::ClickTarget; use graphene_core::renderer::ClickTarget;
@ -23,9 +20,6 @@ pub type LayerId = u64;
pub struct Document { pub struct Document {
#[serde(default)] #[serde(default)]
pub document_network: NodeNetwork, pub document_network: NodeNetwork,
/// The root layer, usually a [FolderLegacyLayer](layers::folder_layer::FolderLegacyLayer) that contains all other [LegacyLayers](layers::layer_info::LegacyLayer).
#[serde(skip)]
pub root: LegacyLayer,
/// The state_identifier serves to provide a way to uniquely identify a particular state that the document is in. /// The state_identifier serves to provide a way to uniquely identify a particular state that the document is in.
/// This identifier is not a hash and is not guaranteed to be equal for equivalent documents. /// This identifier is not a hash and is not guaranteed to be equal for equivalent documents.
#[serde(skip)] #[serde(skip)]
@ -43,11 +37,6 @@ impl PartialEq for Document {
impl Default for Document { impl Default for Document {
fn default() -> Self { fn default() -> Self {
Self { Self {
root: LegacyLayer {
name: None,
visible: true,
data: LegacyLayerType::Folder(FolderLegacyLayer::default()),
},
state_identifier: DefaultHasher::new(), state_identifier: DefaultHasher::new(),
document_network: { document_network: {
use graph_craft::document::{value::TaggedValue, NodeInput}; use graph_craft::document::{value::TaggedValue, NodeInput};
@ -156,100 +145,4 @@ impl Document {
pub fn current_state_identifier(&self) -> u64 { pub fn current_state_identifier(&self) -> u64 {
self.state_identifier.finish() self.state_identifier.finish()
} }
/// Returns a reference to the requested folder. Fails if the path does not exist,
/// or if the requested layer is not of type folder.
pub fn folder(&self, path: impl AsRef<[LayerId]>) -> Result<&FolderLegacyLayer, DocumentError> {
let mut root = &self.root;
for id in path.as_ref() {
root = root.as_folder()?.layer(*id).ok_or_else(|| DocumentError::LayerNotFound(path.as_ref().into()))?;
}
root.as_folder()
}
/// Returns a mutable reference to the requested folder. Fails if the path does not exist,
/// or if the requested layer is not of type folder.
fn folder_mut(&mut self, path: &[LayerId]) -> Result<&mut FolderLegacyLayer, DocumentError> {
let mut root = &mut self.root;
for id in path {
root = root.as_folder_mut()?.layer_mut(*id).ok_or_else(|| DocumentError::LayerNotFound(path.into()))?;
}
root.as_folder_mut()
}
/// Returns a reference to the layer or folder at the path.
pub fn layer(&self, path: &[LayerId]) -> Result<&LegacyLayer, DocumentError> {
if path.is_empty() {
return Ok(&self.root);
}
let (path, id) = split_path(path)?;
self.folder(path)?.layer(id).ok_or_else(|| DocumentError::LayerNotFound(path.into()))
}
/// Returns a mutable reference to the layer or folder at the path.
pub fn layer_mut(&mut self, path: &[LayerId]) -> Result<&mut LegacyLayer, DocumentError> {
if path.is_empty() {
return Ok(&mut self.root);
}
let (path, id) = split_path(path)?;
self.folder_mut(path)?.layer_mut(id).ok_or_else(|| DocumentError::LayerNotFound(path.into()))
}
pub fn common_layer_path_prefix<'a>(&self, layers: impl Iterator<Item = &'a [LayerId]>) -> &'a [LayerId] {
layers.reduce(|a, b| &a[..a.iter().zip(b.iter()).take_while(|&(a, b)| a == b).count()]).unwrap_or_default()
}
/// Returns the shallowest folder given the selection, even if the selection doesn't contain any folders
pub fn shallowest_common_folder<'a>(&self, layers: impl Iterator<Item = &'a [LayerId]>) -> Result<&'a [LayerId], DocumentError> {
let common_prefix_of_path = self.common_layer_path_prefix(layers);
Ok(match self.layer(common_prefix_of_path)?.data {
LegacyLayerType::Folder(_) => common_prefix_of_path,
_ => &common_prefix_of_path[..common_prefix_of_path.len() - 1],
})
}
/// Returns all layers that are not contained in any other of the given folders
/// Takes and Iterator over &[LayerId] or &Vec<LayerId>.
pub fn shallowest_unique_layers<'a, T>(layers: impl Iterator<Item = T>) -> Vec<T>
where
T: AsRef<[LayerId]> + std::cmp::Ord + 'a,
{
let mut sorted_layers: Vec<_> = layers.collect();
sorted_layers.sort();
// Sorting here creates groups of similar UUID paths
sorted_layers.dedup_by(|a, b| a.as_ref().starts_with(b.as_ref()));
sorted_layers
}
/// Given a path to a layer, returns a vector of the indices in the layer tree
/// These indices can be used to order a list of layers
pub fn indices_for_path(&self, path: &[LayerId]) -> Result<Vec<usize>, DocumentError> {
let mut root = self.root.as_folder()?;
let mut indices = vec![];
let (path, layer_id) = split_path(path)?;
// TODO: appears to be n^2? should we maintain a lookup table?
for id in path {
let pos = root.layer_ids.iter().position(|x| *x == *id).ok_or_else(|| DocumentError::LayerNotFound(path.into()))?;
indices.push(pos);
root = match root.layer(*id) {
Some(LegacyLayer {
data: LegacyLayerType::Folder(folder),
..
}) => Some(folder),
_ => None,
}
.ok_or_else(|| DocumentError::LayerNotFound(path.into()))?;
}
indices.push(root.layer_ids.iter().position(|x| *x == layer_id).ok_or_else(|| DocumentError::LayerNotFound(path.into()))?);
Ok(indices)
}
}
fn split_path(path: &[LayerId]) -> Result<(&[LayerId], LayerId), DocumentError> {
let (id, path) = path.split_last().ok_or(DocumentError::InvalidPath)?;
Ok((path, *id))
} }

View File

@ -154,24 +154,20 @@ impl DocumentMetadata {
// selected layer modifications // selected layer modifications
impl DocumentMetadata { impl DocumentMetadata {
#[must_use] pub fn retain_selected_nodes(&mut self, f: impl FnMut(&NodeId) -> bool) {
pub fn retain_selected_nodes(&mut self, f: impl FnMut(&NodeId) -> bool) -> SelectionChanged {
self.selected_nodes.retain(f); self.selected_nodes.retain(f);
SelectionChanged
} }
#[must_use]
pub fn set_selected_nodes(&mut self, new: Vec<NodeId>) -> SelectionChanged { pub fn set_selected_nodes(&mut self, new: Vec<NodeId>) {
self.selected_nodes = new; self.selected_nodes = new;
SelectionChanged
} }
#[must_use]
pub fn add_selected_nodes(&mut self, iter: impl IntoIterator<Item = NodeId>) -> SelectionChanged { pub fn add_selected_nodes(&mut self, iter: impl IntoIterator<Item = NodeId>) {
self.selected_nodes.extend(iter); self.selected_nodes.extend(iter);
SelectionChanged
} }
#[must_use]
pub fn clear_selected_nodes(&mut self) -> SelectionChanged { pub fn clear_selected_nodes(&mut self) {
self.set_selected_nodes(Vec::new()) self.set_selected_nodes(Vec::new());
} }
/// Loads the structure of layer nodes from a node graph. /// Loads the structure of layer nodes from a node graph.
@ -374,8 +370,8 @@ impl LayerNodeIdentifier {
#[track_caller] #[track_caller]
pub fn new(node_id: NodeId, network: &NodeNetwork) -> Self { pub fn new(node_id: NodeId, network: &NodeNetwork) -> Self {
debug_assert!( debug_assert!(
is_layer_node(node_id, network), node_id == LayerNodeIdentifier::ROOT.to_node() || network.nodes.get(&node_id).is_some_and(|node| node.is_layer()),
"Layer identifier constructed from non layer node {node_id}: {:#?}", "Layer identifier constructed from non-layer node {node_id}: {:#?}",
network.nodes.get(&node_id) network.nodes.get(&node_id)
); );
Self::new_unchecked(node_id) Self::new_unchecked(node_id)
@ -633,10 +629,6 @@ pub struct NodeRelations {
last_child: Option<LayerNodeIdentifier>, last_child: Option<LayerNodeIdentifier>,
} }
fn is_layer_node(node: NodeId, network: &NodeNetwork) -> bool {
node == LayerNodeIdentifier::ROOT.to_node() || network.nodes.get(&node).is_some_and(|node| node.is_layer())
}
#[test] #[test]
fn test_tree() { fn test_tree() {
let mut document_metadata = DocumentMetadata::default(); let mut document_metadata = DocumentMetadata::default();

View File

@ -1,27 +0,0 @@
use super::layer_info::LegacyLayer;
use crate::document::LayerId;
use crate::DocumentError;
use serde::{Deserialize, Serialize};
/// A layer that encapsulates other layers, including potentially more folders.
/// The contained layers are rendered in the same order they are stored.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Default)]
pub struct FolderLegacyLayer {
/// The IDs of the [Layer]s contained within the Folder
pub layer_ids: Vec<LayerId>,
/// The [Layer]s contained in the folder
pub layers: Vec<LegacyLayer>,
}
impl FolderLegacyLayer {
pub fn layer(&self, layer_id: LayerId) -> Option<&LegacyLayer> {
let index = self.layer_ids.iter().position(|x| *x == layer_id).ok_or_else(|| DocumentError::LayerNotFound([layer_id].into())).ok()?;
Some(&self.layers[index])
}
pub fn layer_mut(&mut self, layer_id: LayerId) -> Option<&mut LegacyLayer> {
let index = self.layer_ids.iter().position(|x| *x == layer_id).ok_or_else(|| DocumentError::LayerNotFound([layer_id].into())).ok()?;
Some(&mut self.layers[index])
}
}

View File

@ -1,124 +0,0 @@
use super::folder_layer::FolderLegacyLayer;
use super::layer_layer::LayerLegacyLayer;
use crate::DocumentError;
use core::fmt;
use serde::{Deserialize, Serialize};
// ===============
// LegacyLayerType
// ===============
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
/// Represents different types of layers.
pub enum LegacyLayerType {
/// A layer that wraps a [FolderLegacyLayer] struct.
Folder(FolderLegacyLayer),
/// A layer that wraps an [LayerLegacyLayer] struct.
Layer(LayerLegacyLayer),
}
impl Default for LegacyLayerType {
fn default() -> Self {
LegacyLayerType::Layer(Default::default())
}
}
// =========================
// LayerDataTypeDiscriminant
// =========================
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash, specta::Type)]
pub enum LayerDataTypeDiscriminant {
Folder,
Layer,
}
impl fmt::Display for LayerDataTypeDiscriminant {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
LayerDataTypeDiscriminant::Folder => write!(f, "Folder"),
LayerDataTypeDiscriminant::Layer => write!(f, "Layer"),
}
}
}
impl From<&LegacyLayerType> for LayerDataTypeDiscriminant {
fn from(data: &LegacyLayerType) -> Self {
use LegacyLayerType::*;
match data {
Folder(_) => LayerDataTypeDiscriminant::Folder,
Layer(_) => LayerDataTypeDiscriminant::Layer,
}
}
}
// ===========
// LegacyLayer
// ===========
#[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)]
pub struct LegacyLayer {
/// The user-given name of the layer.
pub name: Option<String>,
/// Whether the layer is currently visible or hidden.
pub visible: bool,
/// The type of layer, such as folder or shape.
pub data: LegacyLayerType,
}
impl LegacyLayer {
/// Iterate over the layers encapsulated by this layer.
/// If the [Layer type](Layer::data) is not a folder, the only item in the iterator will be the layer itself.
/// If the [Layer type](Layer::data) wraps a [Folder](LegacyLayerType::Folder), the iterator will recursively yield all the layers contained in the folder as well as potential sub-folders.
pub fn iter(&self) -> LayerIter<'_> {
LayerIter { stack: vec![self] }
}
/// Get a mutable reference to the Folder wrapped by the layer.
/// This operation will fail if the [Layer type](Layer::data) is not `LegacyLayerType::Folder`.
pub fn as_folder_mut(&mut self) -> Result<&mut FolderLegacyLayer, DocumentError> {
match &mut self.data {
LegacyLayerType::Folder(f) => Ok(f),
_ => Err(DocumentError::NotFolder),
}
}
/// Get a reference to the Folder wrapped by the layer.
/// This operation will fail if the [Layer type](Layer::data) is not `LegacyLayerType::Folder`.
pub fn as_folder(&self) -> Result<&FolderLegacyLayer, DocumentError> {
match &self.data {
LegacyLayerType::Folder(f) => Ok(f),
_ => Err(DocumentError::NotFolder),
}
}
}
// =========
// LayerIter
// =========
/// An iterator over the layers encapsulated by this layer.
/// See [Layer::iter] for more information.
#[derive(Debug, Default)]
pub struct LayerIter<'a> {
pub stack: Vec<&'a LegacyLayer>,
}
impl<'a> Iterator for LayerIter<'a> {
type Item = &'a LegacyLayer;
fn next(&mut self) -> Option<Self::Item> {
match self.stack.pop() {
Some(layer) => {
if let LegacyLayerType::Folder(folder) = &layer.data {
let layers = folder.layers.as_slice();
self.stack.extend(layers);
};
Some(layer)
}
None => None,
}
}
}

View File

@ -1,11 +0,0 @@
use serde::{Deserialize, Serialize};
// ================
// LayerLegacyLayer
// ================
#[derive(Clone, Debug, Default, PartialEq, Deserialize, Serialize)]
pub struct LayerLegacyLayer {
/// The document node network that this layer contains
pub network: graph_craft::document::NodeNetwork,
}

View File

@ -1,20 +0,0 @@
//! # Layers
//! A document consists of a set of [Layers](layer_info::Layer).
//! Layers allow the user to mutate part of the document while leaving the rest unchanged.
//! There are currently these different types of layers:
//! * [Folder layers](folder_layer::FolderLegacyLayer), which encapsulate sub-layers
//! * [Layer layers](layer_layer::LayerLegacyLayer), which contain a node graph layer
//!
//! Refer to the module-level documentation for detailed information on each layer.
//!
//! ## Overlapping layers
//! Layers are rendered on top of each other.
//! When different layers overlap, they are blended together according to the [BlendMode](blend_mode::BlendMode)
//! using the CSS [`mix-blend-mode`](https://developer.mozilla.org/en-US/docs/Web/CSS/mix-blend-mode) property and the layer opacity.
/// Contains the [FolderLegacyLayer](folder_layer::FolderLegacyLayer) type that encapsulates other layers, including more folders.
pub mod folder_layer;
/// Contains the base [Layer](layer_info::Layer) type, an abstraction over the different types of layers.
pub mod layer_info;
/// Contains the [LayerLegacyLayer](nodegraph_layer::LayerLegacyLayer) type that contains a node graph.
pub mod layer_layer;

View File

@ -1,16 +1,2 @@
// `macro_use` puts the log macros (`error!`, `warn!`, `debug!`, `info!` and `trace!`) in scope for the crate
// #[macro_use]
extern crate log;
pub mod document; pub mod document;
pub mod document_metadata; pub mod document_metadata;
pub mod layers;
/// A set of different errors that can occur when using this crate.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DocumentError {
LayerNotFound(Vec<document::LayerId>),
InvalidPath,
NotFolder,
InvalidFile(String),
}

View File

@ -37,7 +37,6 @@ const SIDE_EFFECT_FREE_MESSAGES: &[MessageDiscriminant] = &[
MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::PropertiesPanel( MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::PropertiesPanel(
PropertiesPanelMessageDiscriminant::Refresh, PropertiesPanelMessageDiscriminant::Refresh,
))), ))),
MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::FolderChanged)),
MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::DocumentStructureChanged)), MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::DocumentStructureChanged)),
MessageDiscriminant::Frontend(FrontendMessageDiscriminant::UpdateDocumentLayerTreeStructure), MessageDiscriminant::Frontend(FrontendMessageDiscriminant::UpdateDocumentLayerTreeStructure),
MessageDiscriminant::Frontend(FrontendMessageDiscriminant::TriggerFontLoad), MessageDiscriminant::Frontend(FrontendMessageDiscriminant::TriggerFontLoad),
@ -453,41 +452,6 @@ mod test {
assert_eq!(layers_after_copy[5], shape_id); 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] #[test]
/// If this test is failing take a look at `GRAPHITE_DOCUMENT_VERSION` in `editor/src/consts.rs`, it may need to be updated. /// 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. /// 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::layout::utility_types::widget_prelude::*;
use crate::messages::prelude::*; use crate::messages::prelude::*;
use document_legacy::document::LayerId;
use graphene_core::raster::color::Color; use graphene_core::raster::color::Color;
use graphene_core::text::Font; use graphene_core::text::Font;
use serde_json::Value; use serde_json::Value;
use std::ops::Not;
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
pub struct LayoutMessageHandler { pub struct LayoutMessageHandler {
@ -157,19 +155,6 @@ impl<F: Fn(&MessageDiscriminant) -> Vec<KeysGroup>> MessageHandler<LayoutMessage
let callback_message = (invisible.on_update.callback)(&()); let callback_message = (invisible.on_update.callback)(&());
responses.add(callback_message); 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 { Widget::NumberInput(number_input) => match value {
Value::Number(num) => { Value::Number(num) => {
let update_value = num.as_f64().unwrap(); let update_value = num.as_f64().unwrap();

View File

@ -326,7 +326,6 @@ impl LayoutGroup {
Widget::IconButton(x) => &mut x.tooltip, Widget::IconButton(x) => &mut x.tooltip,
Widget::IconLabel(x) => &mut x.tooltip, Widget::IconLabel(x) => &mut x.tooltip,
Widget::ImageLabel(x) => &mut x.tooltip, Widget::ImageLabel(x) => &mut x.tooltip,
Widget::LayerReferenceInput(x) => &mut x.tooltip,
Widget::NumberInput(x) => &mut x.tooltip, Widget::NumberInput(x) => &mut x.tooltip,
Widget::OptionalInput(x) => &mut x.tooltip, Widget::OptionalInput(x) => &mut x.tooltip,
Widget::ParameterExposeButton(x) => &mut x.tooltip, Widget::ParameterExposeButton(x) => &mut x.tooltip,
@ -478,7 +477,6 @@ pub enum Widget {
IconLabel(IconLabel), IconLabel(IconLabel),
ImageLabel(ImageLabel), ImageLabel(ImageLabel),
InvisibleStandinInput(InvisibleStandinInput), InvisibleStandinInput(InvisibleStandinInput),
LayerReferenceInput(LayerReferenceInput),
NumberInput(NumberInput), NumberInput(NumberInput),
OptionalInput(OptionalInput), OptionalInput(OptionalInput),
ParameterExposeButton(ParameterExposeButton), ParameterExposeButton(ParameterExposeButton),
@ -548,7 +546,6 @@ impl DiffUpdate {
Widget::DropdownInput(widget) => Some((&mut widget.tooltip, &mut widget.tooltip_shortcut)), Widget::DropdownInput(widget) => Some((&mut widget.tooltip, &mut widget.tooltip_shortcut)),
Widget::FontInput(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::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::NumberInput(widget) => Some((&mut widget.tooltip, &mut widget.tooltip_shortcut)),
Widget::OptionalInput(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)), 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::input_mapper::utility_types::misc::ActionKeys;
use crate::messages::layout::utility_types::widget_prelude::*; 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 graphene_core::raster::curve::Curve;
use graphite_proc_macros::WidgetBuilder; use graphite_proc_macros::WidgetBuilder;
@ -137,33 +135,6 @@ pub struct InvisibleStandinInput {
pub on_update: WidgetCallback<()>, 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)] #[derive(Clone, Serialize, Deserialize, Derivative, WidgetBuilder, specta::Type)]
#[derivative(Debug, PartialEq, Default)] #[derivative(Debug, PartialEq, Default)]
pub struct NumberInput { pub struct NumberInput {

View File

@ -36,9 +36,6 @@ pub enum DocumentMessage {
// Messages // Messages
AbortTransaction, AbortTransaction,
AddSelectedLayers {
additional_layers: Vec<Vec<LayerId>>,
},
AlignSelectedLayers { AlignSelectedLayers {
axis: AlignAxis, axis: AlignAxis,
aggregate: AlignAggregate, aggregate: AlignAggregate,
@ -65,9 +62,6 @@ pub enum DocumentMessage {
FlipSelectedLayers { FlipSelectedLayers {
flip_axis: FlipAxis, flip_axis: FlipAxis,
}, },
FolderChanged {
affected_folder_path: Vec<LayerId>,
},
GroupSelectedLayers, GroupSelectedLayers,
ImaginateClear { ImaginateClear {
layer_path: Vec<LayerId>, layer_path: Vec<LayerId>,
@ -83,9 +77,6 @@ pub enum DocumentMessage {
InputFrameRasterizeRegionBelowLayer { InputFrameRasterizeRegionBelowLayer {
layer_path: Vec<LayerId>, layer_path: Vec<LayerId>,
}, },
LayerChanged {
affected_layer_path: Vec<LayerId>,
},
MoveSelectedLayersTo { MoveSelectedLayersTo {
parent: LayerNodeIdentifier, parent: LayerNodeIdentifier,
insert_index: isize, 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::node_graph::NodeGraphHandlerData;
use crate::messages::portfolio::document::properties_panel::utility_types::PropertiesPanelMessageHandlerData; 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::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::misc::{AlignAggregate, AlignAxis, DocumentMode, DocumentSave, FlipAxis};
use crate::messages::portfolio::document::utility_types::vectorize_layer_metadata; use crate::messages::portfolio::document::utility_types::vectorize_layer_metadata;
use crate::messages::portfolio::utility_types::PersistentData; 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::Document as DocumentLegacy;
use document_legacy::document::LayerId; use document_legacy::document::LayerId;
use document_legacy::document_metadata::LayerNodeIdentifier; 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::value::TaggedValue;
use graph_craft::document::{NodeInput, NodeNetwork}; use graph_craft::document::{NodeInput, NodeNetwork};
use graphene_core::raster::BlendMode; use graphene_core::raster::BlendMode;
@ -175,17 +173,6 @@ impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHand
responses.extend([RenderDocument.into(), DocumentStructureChanged.into()]); 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 } => { AlignSelectedLayers { axis, aggregate } => {
self.backup(responses); self.backup(responses);
@ -271,6 +258,8 @@ impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHand
DocumentHistoryBackward => self.undo(responses), DocumentHistoryBackward => self.undo(responses),
DocumentHistoryForward => self.redo(responses), DocumentHistoryForward => self.redo(responses),
DocumentStructureChanged => { DocumentStructureChanged => {
self.update_layers_panel_options_bar_widgets(responses);
let data_buffer: RawBuffer = self.serialize_root().as_slice().into(); let data_buffer: RawBuffer = self.serialize_root().as_slice().into();
responses.add(FrontendMessage::UpdateDocumentLayerTreeStructure { data_buffer }) responses.add(FrontendMessage::UpdateDocumentLayerTreeStructure { data_buffer })
} }
@ -302,10 +291,6 @@ impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHand
responses.add(BroadcastEvent::DocumentIsDirty); 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 => { GroupSelectedLayers => {
// TODO: Add code that changes the insert index of the new folder based on the selected layer // 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); 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 }), 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 } => { MoveSelectedLayersTo { parent, insert_index } => {
let selected_layers = self.metadata().selected_layers().collect::<Vec<_>>(); let selected_layers = self.metadata().selected_layers().collect::<Vec<_>>();
@ -368,7 +347,7 @@ impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHand
return; 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(PortfolioMessage::Copy { clipboard: Clipboard::Internal });
responses.add(DocumentMessage::DeleteSelectedLayers); responses.add(DocumentMessage::DeleteSelectedLayers);
@ -472,7 +451,7 @@ impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHand
responses.add(DocumentHistoryForward); responses.add(DocumentHistoryForward);
responses.add(BroadcastEvent::DocumentIsDirty); responses.add(BroadcastEvent::DocumentIsDirty);
responses.add(RenderDocument); responses.add(RenderDocument);
responses.add(FolderChanged { affected_folder_path: vec![] }); responses.add(DocumentStructureChanged);
} }
RenameDocument { new_name } => { RenameDocument { new_name } => {
self.name = new_name; self.name = new_name;
@ -654,7 +633,7 @@ impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHand
responses.add(DocumentHistoryBackward); responses.add(DocumentHistoryBackward);
responses.add(BroadcastEvent::DocumentIsDirty); responses.add(BroadcastEvent::DocumentIsDirty);
responses.add(RenderDocument); responses.add(RenderDocument);
responses.add(FolderChanged { affected_folder_path: vec![] }); responses.add(DocumentStructureChanged);
responses.add(UndoFinished); responses.add(UndoFinished);
} }
UndoFinished => self.undo_in_progress = false, UndoFinished => self.undo_in_progress = false,
@ -764,14 +743,14 @@ impl DocumentMessageHandler {
val.unwrap() val.unwrap()
} }
pub fn deserialize_document(serialized_content: &str) -> Result<Self, DocumentError> { pub fn deserialize_document(serialized_content: &str) -> Result<Self, EditorError> {
let deserialized_result: Result<Self, DocumentError> = serde_json::from_str(serialized_content).map_err(|e| DocumentError::InvalidFile(e.to_string())); let deserialized_result: Result<Self, EditorError> = serde_json::from_str(serialized_content).map_err(|e| EditorError::DocumentDeserialization(e.to_string()));
match deserialized_result { match deserialized_result {
Ok(document) => { Ok(document) => {
if document.version == GRAPHITE_DOCUMENT_VERSION { if document.version == GRAPHITE_DOCUMENT_VERSION {
Ok(document) Ok(document)
} else { } else {
Err(DocumentError::InvalidFile("Graphite document version mismatch".to_string())) Err(EditorError::DocumentDeserialization("Graphite document version mismatch".to_string()))
} }
} }
Err(e) => Err(e), Err(e) => Err(e),
@ -788,71 +767,15 @@ impl DocumentMessageHandler {
} }
pub fn with_name_and_content(name: String, serialized_content: String) -> Result<Self, EditorError> { pub fn with_name_and_content(name: String, serialized_content: String) -> Result<Self, EditorError> {
match Self::deserialize_document(&serialized_content) { let mut document = Self::deserialize_document(&serialized_content)?;
Ok(mut document) => {
document.name = name; document.name = name;
Ok(document) 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
}
}
pub fn selected_layers(&self) -> impl Iterator<Item = &[LayerId]> { pub fn selected_layers(&self) -> impl Iterator<Item = &[LayerId]> {
self.layer_metadata.iter().filter_map(|(path, data)| data.selected.then_some(path.as_slice())) 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. /// Returns the bounding boxes for all visible layers.
pub fn bounding_boxes<'a>(&'a self) -> impl Iterator<Item = [DVec2; 2]> + 'a { pub fn bounding_boxes<'a>(&'a self) -> impl Iterator<Item = [DVec2; 2]> + 'a {
// TODO: Remove this function entirely? // TODO: Remove this function entirely?
@ -920,63 +843,10 @@ impl DocumentMessageHandler {
structure 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 { 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")) 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 /// 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>) { fn backup_with_document(&mut self, document: DocumentLegacy, layer_metadata: HashMap<Vec<LayerId>, LayerMetadata>, responses: &mut VecDeque<Message>) {
self.document_redo_history.clear(); 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. /// Replace the document with a new document save, returning the document save.
pub fn replace_document(&mut self, DocumentSave { document, layer_metadata }: DocumentSave) -> DocumentSave { 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 // 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(); self.document_redo_history.pop_front();
} }
for layer in self.layer_metadata.keys() { responses.add(DocumentMessage::DocumentStructureChanged);
responses.add(DocumentMessage::LayerChanged { affected_layer_path: layer.clone() })
}
responses.add(NodeGraphMessage::SendGraph { should_rerender: true }); responses.add(NodeGraphMessage::SendGraph { should_rerender: true });
} }
} }
@ -1071,10 +932,7 @@ impl DocumentMessageHandler {
self.document_undo_history.pop_front(); self.document_undo_history.pop_front();
} }
for layer in self.layer_metadata.keys() { responses.add(DocumentMessage::DocumentStructureChanged);
responses.add(DocumentMessage::LayerChanged { affected_layer_path: layer.clone() })
}
responses.add(NodeGraphMessage::SendGraph { should_rerender: true }); 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). /// 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. /// 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 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; let new_insert_index = layer_ids_above.filter(|layer_id| !layers.contains(layer_id)).count() as isize;
Ok(new_insert_index) 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
} }
pub fn new_layer_parent(&self) -> LayerNodeIdentifier { 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(DocumentMessage::DocumentStructureChanged);
self.responses.add(NodeGraphMessage::SendGraph { should_rerender: true }); 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 { impl NodeGraphMessageHandler {
/// Send the cached layout to the frontend for the options bar at the top of the node panel /// 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>) { fn send_node_bar_layout(&self, responses: &mut VecDeque<Message>) {
@ -429,7 +423,8 @@ impl NodeGraphMessageHandler {
return false; return false;
} }
network.nodes.remove(&node_id); 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 true
} }
@ -627,13 +622,16 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
responses.add(DocumentMessage::StartTransaction); responses.add(DocumentMessage::StartTransaction);
let new_ids = &document.metadata.selected_nodes().map(|&id| (id, crate::application::generate_uuid())).collect(); 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 // Copy the selected nodes
let copied_nodes = Self::copy_nodes(network, new_ids).collect::<Vec<_>>(); let copied_nodes = Self::copy_nodes(network, new_ids).collect::<Vec<_>>();
// Select the new nodes // 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 { for (node_id, mut document_node) in copied_nodes {
// Shift duplicated node // Shift duplicated node
@ -649,7 +647,9 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
} }
} }
NodeGraphMessage::ExitNestedNetwork { depth_of_nesting } => { 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 { for _ in 0..depth_of_nesting {
self.network.pop(); 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::RunDocumentGraph => responses.add(PortfolioMessage::SubmitGraphRender { document_id, layer_path: Vec::new() }),
NodeGraphMessage::SelectedNodesAdd { nodes } => { NodeGraphMessage::SelectedNodesAdd { nodes } => {
responses.add(document.metadata.add_selected_nodes(nodes)); document.metadata.add_selected_nodes(nodes);
responses.add(BroadcastEvent::SelectionChanged);
} }
NodeGraphMessage::SelectedNodesRemove { nodes } => { 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 } => { 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); responses.add(PropertiesPanelMessage::Refresh);
} }
NodeGraphMessage::SendGraph { should_rerender } => { NodeGraphMessage::SendGraph { should_rerender } => {
@ -953,7 +956,8 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
} }
NodeGraphMessage::UpdateNewNodeGraph => { NodeGraphMessage::UpdateNewNodeGraph => {
if let Some(network) = document.document_network.nested_network(&self.network) { 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); 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::layout::utility_types::widget_prelude::*;
use crate::messages::prelude::*; use crate::messages::prelude::*;
use document_legacy::layers::layer_info::LayerDataTypeDiscriminant;
use graph_craft::document::value::TaggedValue; use graph_craft::document::value::TaggedValue;
use graph_craft::document::{DocumentNode, NodeId, NodeInput}; 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::memo::IORecord;
use graphene_core::raster::{ use graphene_core::raster::{
BlendMode, CellularDistanceFunction, CellularReturnType, Color, DomainWarpType, FractalType, ImageFrame, LuminanceCalculation, NoiseType, RedGreenBlue, RelativeAbsolute, SelectiveColorChoice, 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 neg_index = resolve_input("Negative Prompt");
let base_img_index = resolve_input("Adapt Input Image"); let base_img_index = resolve_input("Adapt Input Image");
let img_creativity_index = resolve_input("Image Creativity"); let img_creativity_index = resolve_input("Image Creativity");
let mask_index = resolve_input("Masking Layer"); // let mask_index = resolve_input("Masking Layer");
let inpaint_index = resolve_input("Inpaint"); // let inpaint_index = resolve_input("Inpaint");
let mask_blur_index = resolve_input("Mask Blur"); // let mask_blur_index = resolve_input("Mask Blur");
let mask_fill_index = resolve_input("Mask Starting Fill"); // let mask_fill_index = resolve_input("Mask Starting Fill");
let faces_index = resolve_input("Improve Faces"); let faces_index = resolve_input("Improve Faces");
let tiling_index = resolve_input("Tiling"); 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![ let mut layout = vec![
server_status, server_status,
progress, progress,
@ -1928,73 +1889,73 @@ pub fn imaginate_properties(document_node: &DocumentNode, node_id: NodeId, conte
negative_prompt, negative_prompt,
base_image, base_image,
image_creativity, image_creativity,
layer_mask, // layer_mask,
]; ];
if use_base_image && layer_reference_input_layer_is_some { // if use_base_image && layer_reference_input_layer_is_some {
let in_paint = { // let in_paint = {
let mut widgets = start_widgets(document_node, node_id, inpaint_index, "Inpaint", FrontendGraphDataType::Boolean, true); // let mut widgets = start_widgets(document_node, node_id, inpaint_index, "Inpaint", FrontendGraphDataType::Boolean, true);
if let &NodeInput::Value { // if let &NodeInput::Value {
tagged_value: TaggedValue::Bool(in_paint), // tagged_value: TaggedValue::Bool(in_paint),
exposed: false, // exposed: false,
} = &document_node.inputs[inpaint_index] // } = &document_node.inputs[inpaint_index]
{ // {
widgets.extend_from_slice(&[ // widgets.extend_from_slice(&[
Separator::new(SeparatorType::Unrelated).widget_holder(), // Separator::new(SeparatorType::Unrelated).widget_holder(),
RadioInput::new( // RadioInput::new(
[(true, "Inpaint"), (false, "Outpaint")] // [(true, "Inpaint"), (false, "Outpaint")]
.into_iter() // .into_iter()
.map(|(paint, name)| RadioEntryData::new(name).on_update(update_value(move |_| TaggedValue::Bool(paint), node_id, inpaint_index))) // .map(|(paint, name)| RadioEntryData::new(name).on_update(update_value(move |_| TaggedValue::Bool(paint), node_id, inpaint_index)))
.collect(), // .collect(),
) // )
.selected_index(Some(1 - in_paint as u32)) // .selected_index(Some(1 - in_paint as u32))
.widget_holder(), // .widget_holder(),
]); // ]);
} // }
LayoutGroup::Row { widgets }.with_tooltip( // 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\ // "Constrain image generation to the interior (inpaint) or exterior (outpaint) of the mask, while referencing the other unchanged parts as context imagery.\n\
\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\ // 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\ // \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.", // 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 blur_radius = {
let number_props = NumberInput::default().unit(" px").min(0.).max(25.).int(); // 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); // 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.") // 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 mask_starting_fill = {
let mut widgets = start_widgets(document_node, node_id, mask_fill_index, "Mask Starting Fill", FrontendGraphDataType::General, true); // let mut widgets = start_widgets(document_node, node_id, mask_fill_index, "Mask Starting Fill", FrontendGraphDataType::General, true);
if let &NodeInput::Value { // if let &NodeInput::Value {
tagged_value: TaggedValue::ImaginateMaskStartingFill(starting_fill), // tagged_value: TaggedValue::ImaginateMaskStartingFill(starting_fill),
exposed: false, // exposed: false,
} = &document_node.inputs[mask_fill_index] // } = &document_node.inputs[mask_fill_index]
{ // {
let mask_fill_content_modes = ImaginateMaskStartingFill::list(); // let mask_fill_content_modes = ImaginateMaskStartingFill::list();
let mut entries = Vec::with_capacity(mask_fill_content_modes.len()); // let mut entries = Vec::with_capacity(mask_fill_content_modes.len());
for mode in mask_fill_content_modes { // 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))); // entries.push(MenuListEntry::new(mode.to_string()).on_update(update_value(move |_| TaggedValue::ImaginateMaskStartingFill(mode), node_id, mask_fill_index)));
} // }
let entries = vec![entries]; // let entries = vec![entries];
widgets.extend_from_slice(&[ // widgets.extend_from_slice(&[
Separator::new(SeparatorType::Unrelated).widget_holder(), // Separator::new(SeparatorType::Unrelated).widget_holder(),
DropdownInput::new(entries).selected_index(Some(starting_fill as u32)).widget_holder(), // DropdownInput::new(entries).selected_index(Some(starting_fill as u32)).widget_holder(),
]); // ]);
} // }
LayoutGroup::Row { widgets }.with_tooltip( // LayoutGroup::Row { widgets }.with_tooltip(
"Begin in/outpainting the masked areas using this fill content as the starting input image.\n\ // "Begin in/outpainting the masked areas using this fill content as the starting input image.\n\
\n\ // \n\
Each option can be visualized by generating with 'Sampling Steps' set to 0.", // Each option can be visualized by generating with 'Sampling Steps' set to 0.",
) // )
}; // };
layout.extend_from_slice(&[in_paint, blur_radius, mask_starting_fill]); // layout.extend_from_slice(&[in_paint, blur_radius, mask_starting_fill]);
} // }
let improve_faces = { let improve_faces = {
let widgets = bool_widget(document_node, node_id, faces_index, "Improve Faces", true); 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 graphene_core::raster::color::Color;
use thiserror::Error; use thiserror::Error;
@ -38,4 +37,3 @@ macro_rules! derive_from {
derive_from!(&str, Misc); derive_from!(&str, Misc);
derive_from!(String, Misc); derive_from!(String, Misc);
derive_from!(Color, Color); derive_from!(Color, Color);
derive_from!(DocumentError, Document);

View File

@ -1,5 +1,4 @@
use document_legacy::document::LayerId; use document_legacy::document::LayerId;
use document_legacy::layers::layer_info::{LayerDataTypeDiscriminant, LegacyLayer};
use serde::ser::SerializeStruct; use serde::ser::SerializeStruct;
use serde::{Deserialize, Serialize}; 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 struct LayerMetadata {
pub selected: bool, pub selected: bool,
pub expanded: 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)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, specta::Type)]
pub struct LayerPanelEntry { pub struct LayerPanelEntry {
pub name: String, pub name: String,
pub tooltip: String, pub tooltip: String,
pub visible: bool, #[serde(rename = "layerClassification")]
#[serde(rename = "layerType")] pub layer_classification: LayerClassification,
pub layer_type: LayerDataTypeDiscriminant,
#[serde(rename = "layerMetadata")] #[serde(rename = "layerMetadata")]
pub layer_metadata: LayerMetadata, pub layer_metadata: LayerMetadata,
pub path: Vec<LayerId>, pub path: Vec<LayerId>,
pub thumbnail: String, 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(PortfolioMessage::UpdateOpenDocumentsList);
responses.add(DocumentMessage::RenderDocument); responses.add(DocumentMessage::RenderDocument);
responses.add(DocumentMessage::DocumentStructureChanged); 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 } => { PortfolioMessage::CloseDocumentWithConfirmation { document_id } => {
let target_document = self.documents.get(&document_id).unwrap(); 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(FrontendMessage::UpdateActiveDocument { document_id });
responses.add(DocumentMessage::RenderDocument); responses.add(DocumentMessage::RenderDocument);
responses.add(DocumentMessage::DocumentStructureChanged); 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::SelectionChanged);
responses.add(BroadcastEvent::DocumentIsDirty); responses.add(BroadcastEvent::DocumentIsDirty);
responses.add(PortfolioMessage::UpdateDocumentWidgets); 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>) { fn load_document(&mut self, new_document: DocumentMessageHandler, document_id: u64, responses: &mut VecDeque<Message>) {
self.document_ids.push(document_id); 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); new_document.update_layers_panel_options_bar_widgets(responses);
self.documents.insert(document_id, new_document); 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 crate::messages::prelude::*;
use document_legacy::document::LayerId; use document_legacy::document::LayerId;
use document_legacy::layers::layer_info::LegacyLayer;
use glam::DVec2; 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 /// Adds all of the shape handles in the document, including bézier handles of the points specified
pub fn add_all_document_handles( pub fn add_all_document_handles(
&mut self, &mut self,
document_message_handler: &DocumentMessageHandler, _document_message_handler: &DocumentMessageHandler,
input: &InputPreprocessorMessageHandler, _input: &InputPreprocessorMessageHandler,
include_handles: &[&[LayerId]], _include_handles: &[&[LayerId]],
exclude: &[&[LayerId]], _exclude: &[&[LayerId]],
ignore_points: &[ManipulatorPointInfo], _ignore_points: &[ManipulatorPointInfo],
) { ) {
for path in document_message_handler.all_layers() { // for path in document_message_handler.all_layers() {
if !exclude.contains(&path) { // if !exclude.contains(&path) {
let layer = document_message_handler.document_legacy.layer(path).expect("Could not get layer for snapping"); // 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); // 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. /// 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::FrontendImageData;
use crate::messages::frontend::utility_types::{ExportBounds, FileType}; use crate::messages::frontend::utility_types::{ExportBounds, FileType};
use crate::messages::portfolio::document::node_graph::wrap_network_in_scope; 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::portfolio::document::utility_types::misc::{LayerMetadata, LayerPanelEntry};
use crate::messages::prelude::*; use crate::messages::prelude::*;
use document_legacy::document::Document as DocumentLegacy; use document_legacy::document::Document as DocumentLegacy;
use document_legacy::document::LayerId; use document_legacy::document::LayerId;
use document_legacy::document_metadata::LayerNodeIdentifier; use document_legacy::document_metadata::LayerNodeIdentifier;
use document_legacy::layers::layer_info::{LayerDataTypeDiscriminant, LegacyLayerType};
use graph_craft::document::value::TaggedValue; use graph_craft::document::value::TaggedValue;
use graph_craft::document::{generate_uuid, DocumentNodeImplementation, NodeId, NodeNetwork}; use graph_craft::document::{generate_uuid, DocumentNodeImplementation, NodeId, NodeNetwork};
use graph_craft::graphene_compiler::Compiler; use graph_craft::graphene_compiler::Compiler;
@ -430,10 +430,6 @@ impl NodeGraphExecutor {
.expect("Failed to send imaginate preferences"); .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>( 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, &mut self,
network: &NodeNetwork, network: &NodeNetwork,
@ -493,17 +489,7 @@ impl NodeGraphExecutor {
/// Evaluates a node graph, computing the entire graph /// 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> { 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 // Get the node graph layer
let network = if layer_path.is_empty() { let network = document.network().clone();
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 render_config = RenderConfig { let render_config = RenderConfig {
viewport: Footprint { viewport: Footprint {
@ -625,11 +611,12 @@ impl NodeGraphExecutor {
data: LayerPanelEntry { data: LayerPanelEntry {
name: document.document_network.nodes.get(&node_id).map(|node| node.alias.clone()).unwrap_or_default(), 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() }, tooltip: if cfg!(debug_assertions) { format!("Layer ID: {node_id}") } else { "".into() },
visible: !document.document_network.disabled.contains(&layer.to_node()), layer_classification: if document.metadata.is_artboard(layer) {
layer_type: if document.metadata.is_folder(layer) { LayerClassification::Artboard
LayerDataTypeDiscriminant::Folder } else if document.metadata.is_folder(layer) {
LayerClassification::Folder
} else { } else {
LayerDataTypeDiscriminant::Layer LayerClassification::Layer
}, },
layer_metadata: LayerMetadata { layer_metadata: LayerMetadata {
expanded: layer.has_children(&document.metadata) && !collapsed_folders.contains(&layer), expanded: layer.has_children(&document.metadata) && !collapsed_folders.contains(&layer),
@ -645,9 +632,6 @@ impl NodeGraphExecutor {
document.metadata.update_click_targets(new_click_targets); document.metadata.update_click_targets(new_click_targets);
responses.extend(updates); responses.extend(updates);
self.process_node_graph_output(node_graph_output, execution_context.layer_path.clone(), transform, responses)?; 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::RenderDocument);
responses.add(DocumentMessage::DocumentStructureChanged); responses.add(DocumentMessage::DocumentStructureChanged);
responses.add(BroadcastEvent::DocumentIsDirty); responses.add(BroadcastEvent::DocumentIsDirty);

View File

@ -5,7 +5,7 @@
import { platformIsMac } from "@graphite/utility-functions/platform"; import { platformIsMac } from "@graphite/utility-functions/platform";
import type { Editor } from "@graphite/wasm-communication/editor"; import type { Editor } from "@graphite/wasm-communication/editor";
import { defaultWidgetLayout, patchWidgetLayout, UpdateDocumentLayerDetails, UpdateDocumentLayerTreeStructureJs, UpdateLayersPanelOptionsLayout } from "@graphite/wasm-communication/messages"; import { defaultWidgetLayout, patchWidgetLayout, UpdateDocumentLayerDetails, UpdateDocumentLayerTreeStructureJs, UpdateLayersPanelOptionsLayout } from "@graphite/wasm-communication/messages";
import type { LayerType, LayerPanelEntry } from "@graphite/wasm-communication/messages"; import type { LayerClassification, LayerPanelEntry } from "@graphite/wasm-communication/messages";
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte"; import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte"; import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
@ -136,8 +136,8 @@
editor.instance.deselectAllLayers(); editor.instance.deselectAllLayers();
} }
function isGroupOrArtboard(layerType: LayerType) { function isNestingLayer(layerClassification: LayerClassification) {
return layerType === "Folder" || layerType === "Artboard"; return layerClassification === "Folder" || layerClassification === "Artboard";
} }
function calculateDragIndex(tree: LayoutCol, clientY: number, select?: () => void): DraggingData { function calculateDragIndex(tree: LayoutCol, clientY: number, select?: () => void): DraggingData {
@ -179,9 +179,9 @@
} }
// Inserting below current row // Inserting below current row
else if (distance > -closest && distance > -RANGE_TO_INSERT_WITHIN_BOTTOM_FOLDER_NOT_ROOT && distance < 0) { else if (distance > -closest && distance > -RANGE_TO_INSERT_WITHIN_BOTTOM_FOLDER_NOT_ROOT && distance < 0) {
insertFolder = isGroupOrArtboard(layer.layerType) ? layer.path : layer.path.slice(0, layer.path.length - 1); insertFolder = isNestingLayer(layer.layerClassification) ? layer.path : layer.path.slice(0, layer.path.length - 1);
insertIndex = isGroupOrArtboard(layer.layerType) ? 0 : folderIndex + 1; insertIndex = isNestingLayer(layer.layerClassification) ? 0 : folderIndex + 1;
highlightFolder = isGroupOrArtboard(layer.layerType); highlightFolder = isNestingLayer(layer.layerClassification);
closest = -distance; closest = -distance;
markerHeight = index === treeChildren.length - 1 ? rect.bottom - INSERT_MARK_OFFSET : rect.bottom; markerHeight = index === treeChildren.length - 1 ? rect.bottom - INSERT_MARK_OFFSET : rect.bottom;
} }
@ -320,11 +320,11 @@
on:dragstart={(e) => draggable && dragStart(e, listing)} on:dragstart={(e) => draggable && dragStart(e, listing)}
on:click={(e) => selectLayerWithModifiers(e, listing)} on:click={(e) => selectLayerWithModifiers(e, listing)}
> >
{#if isGroupOrArtboard(listing.entry.layerType)} {#if isNestingLayer(listing.entry.layerClassification)}
<button class="expand-arrow" class:expanded={listing.entry.layerMetadata.expanded} on:click|stopPropagation={() => handleExpandArrowClick(listing.entry.path)} tabindex="0" /> <button class="expand-arrow" class:expanded={listing.entry.layerMetadata.expanded} on:click|stopPropagation={() => handleExpandArrowClick(listing.entry.path)} tabindex="0" />
{#if listing.entry.layerType === "Artboard"} {#if listing.entry.layerClassification === "Artboard"}
<IconLabel icon="Artboard" class={"layer-type-icon"} /> <IconLabel icon="Artboard" class={"layer-type-icon"} />
{:else if listing.entry.layerType === "Folder"} {:else if listing.entry.layerClassification === "Folder"}
<IconLabel icon="Folder" class={"layer-type-icon"} /> <IconLabel icon="Folder" class={"layer-type-icon"} />
{/if} {/if}
{:else} {:else}
@ -337,7 +337,7 @@
data-text-input data-text-input
type="text" type="text"
value={listing.entry.name} value={listing.entry.name}
placeholder={listing.entry.layerType} placeholder={listing.entry.layerClassification}
disabled={!listing.editingName} disabled={!listing.editingName}
on:blur={() => onEditLayerNameDeselect(listing)} on:blur={() => onEditLayerNameDeselect(listing)}
on:keydown={(e) => e.key === "Escape" && onEditLayerNameDeselect(listing)} on:keydown={(e) => e.key === "Escape" && onEditLayerNameDeselect(listing)}
@ -349,8 +349,8 @@
class={"visibility"} class={"visibility"}
action={(e) => (toggleLayerVisibility(listing.entry.path), e?.stopPropagation())} action={(e) => (toggleLayerVisibility(listing.entry.path), e?.stopPropagation())}
size={24} size={24}
icon={listing.entry.visible ? "EyeVisible" : "EyeHidden"} icon={(() => true)() ? "EyeVisible" : "EyeHidden"}
tooltip={listing.entry.visible ? "Visible" : "Hidden"} tooltip={(() => true)() ? "Visible" : "Hidden"}
/> />
</LayoutRow> </LayoutRow>
{/each} {/each}

View File

@ -17,7 +17,6 @@
import CurveInput from "@graphite/components/widgets/inputs/CurveInput.svelte"; import CurveInput from "@graphite/components/widgets/inputs/CurveInput.svelte";
import DropdownInput from "@graphite/components/widgets/inputs/DropdownInput.svelte"; import DropdownInput from "@graphite/components/widgets/inputs/DropdownInput.svelte";
import FontInput from "@graphite/components/widgets/inputs/FontInput.svelte"; import FontInput from "@graphite/components/widgets/inputs/FontInput.svelte";
import LayerReferenceInput from "@graphite/components/widgets/inputs/LayerReferenceInput.svelte";
import NumberInput from "@graphite/components/widgets/inputs/NumberInput.svelte"; import NumberInput from "@graphite/components/widgets/inputs/NumberInput.svelte";
import OptionalInput from "@graphite/components/widgets/inputs/OptionalInput.svelte"; import OptionalInput from "@graphite/components/widgets/inputs/OptionalInput.svelte";
import PivotInput from "@graphite/components/widgets/inputs/PivotInput.svelte"; import PivotInput from "@graphite/components/widgets/inputs/PivotInput.svelte";
@ -126,10 +125,6 @@
{#if imageLabel} {#if imageLabel}
<ImageLabel {...exclude(imageLabel)} /> <ImageLabel {...exclude(imageLabel)} />
{/if} {/if}
{@const layerReferenceInput = narrowWidgetProps(component.props, "LayerReferenceInput")}
{#if layerReferenceInput}
<LayerReferenceInput {...exclude(layerReferenceInput)} on:value={({ detail }) => updateLayout(index, detail)} />
{/if}
{@const numberInput = narrowWidgetProps(component.props, "NumberInput")} {@const numberInput = narrowWidgetProps(component.props, "NumberInput")}
{#if numberInput} {#if numberInput}
<NumberInput <NumberInput

View File

@ -1,136 +0,0 @@
<script lang="ts">
import { createEventDispatcher } from "svelte";
import { currentDraggingElement } from "@graphite/io-managers/drag";
import type { LayerType } from "@graphite/wasm-communication/messages";
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
import IconButton from "@graphite/components/widgets/buttons/IconButton.svelte";
import IconLabel from "@graphite/components/widgets/labels/IconLabel.svelte";
import TextLabel from "@graphite/components/widgets/labels/TextLabel.svelte";
const dispatch = createEventDispatcher<{ value: string | undefined }>();
export let value: string | undefined = undefined;
export let layerName: string | undefined = undefined;
export let layerType: LayerType | undefined = undefined;
export let disabled = false;
export let tooltip: string | undefined = undefined;
export let sharpRightCorners = false;
let hoveringDrop = false;
$: droppable = hoveringDrop && Boolean(currentDraggingElement());
function dragOver(e: DragEvent) {
hoveringDrop = true;
e.preventDefault();
}
function drop(e: DragEvent) {
hoveringDrop = false;
const element = currentDraggingElement();
const layerPath = element?.getAttribute("data-layer") || undefined;
if (layerPath) {
e.preventDefault();
dispatch("value", layerPath);
}
}
</script>
<LayoutRow
class="layer-reference-input"
classes={{ disabled, droppable, "sharp-right-corners": sharpRightCorners }}
{tooltip}
on:dragover={(e) => !disabled && dragOver(e)}
on:dragleave={() => !disabled && (hoveringDrop = false)}
on:drop={(e) => !disabled && drop(e)}
>
{#if value === undefined || droppable}
<LayoutRow class="drop-zone" />
<TextLabel italic={true}>{droppable ? "Drop" : "Drag"} Layer Here</TextLabel>
{:else}
{#if layerName !== undefined && layerType}
<IconLabel icon={layerType} class="layer-icon" />
<TextLabel italic={layerName === ""} class="layer-name">{layerName || `Untitled ${layerType || "[Unknown Layer Type]"}`}</TextLabel>
{:else}
<TextLabel bold={true} italic={true} class="missing">Layer Missing</TextLabel>
{/if}
<IconButton icon="CloseX" size={16} {disabled} action={() => dispatch("value", undefined)} />
{/if}
</LayoutRow>
<style lang="scss" global>
.layer-reference-input {
position: relative;
flex: 1 0 auto;
height: 24px;
border-radius: 2px;
background: var(--color-1-nearblack);
.drop-zone {
pointer-events: none;
border: 1px dashed var(--color-4-dimgray);
border-radius: 1px;
position: absolute;
top: 2px;
bottom: 2px;
left: 2px;
right: 2px;
}
&.droppable .drop-zone {
border: 1px dashed var(--color-e-nearwhite);
}
.layer-icon {
margin: 4px 8px;
+ .text-label {
padding-left: 0;
}
}
.text-label {
line-height: 18px;
padding: 3px calc(8px + 2px);
width: 100%;
text-align: center;
&.missing {
// TODO: Define this as a permanent color palette choice (search the project for all uses of this hex code)
color: #d6536e;
}
&.layer-name {
text-align: left;
}
}
.icon-button {
margin: 4px;
margin-left: 0;
}
&.disabled {
background: var(--color-2-mildblack);
.drop-zone {
border: 1px dashed var(--color-4-dimgray);
}
.text-label {
color: var(--color-8-uppergray);
}
.icon-label svg {
fill: var(--color-8-uppergray);
}
}
}
</style>

View File

@ -667,9 +667,7 @@ export class LayerPanelEntry {
@Transform(({ value }: { value: string }) => value || undefined) @Transform(({ value }: { value: string }) => value || undefined)
tooltip!: string | undefined; tooltip!: string | undefined;
visible!: boolean; layerClassification!: LayerClassification;
layerType!: LayerType;
@Transform(({ value }: { value: bigint[] }) => new BigUint64Array(value)) @Transform(({ value }: { value: bigint[] }) => new BigUint64Array(value))
path!: BigUint64Array; path!: BigUint64Array;
@ -686,7 +684,7 @@ export class LayerMetadata {
selected!: boolean; selected!: boolean;
} }
export type LayerType = "Folder" | "Layer" | "Artboard"; export type LayerClassification = "Folder" | "Artboard" | "Layer";
export class RenderedImageData { export class RenderedImageData {
readonly path!: BigUint64Array; readonly path!: BigUint64Array;
@ -877,24 +875,6 @@ export class ImageLabel extends WidgetProps {
tooltip!: string | undefined; tooltip!: string | undefined;
} }
export class LayerReferenceInput extends WidgetProps {
@Transform(({ value }: { value: BigUint64Array | undefined }) => (value ? String(value) : undefined))
value!: string | undefined;
layerName!: string | undefined;
layerType!: LayerType | undefined;
disabled!: boolean;
@Transform(({ value }: { value: string }) => value || undefined)
tooltip!: string | undefined;
// Styling
minWidth!: number;
}
export type NumberInputIncrementBehavior = "Add" | "Multiply" | "Callback" | "None"; export type NumberInputIncrementBehavior = "Add" | "Multiply" | "Callback" | "None";
export type NumberInputMode = "Increment" | "Range"; export type NumberInputMode = "Increment" | "Range";
@ -1129,7 +1109,6 @@ const widgetSubTypes = [
{ value: IconButton, name: "IconButton" }, { value: IconButton, name: "IconButton" },
{ value: IconLabel, name: "IconLabel" }, { value: IconLabel, name: "IconLabel" },
{ value: ImageLabel, name: "ImageLabel" }, { value: ImageLabel, name: "ImageLabel" },
{ value: LayerReferenceInput, name: "LayerReferenceInput" },
{ value: NumberInput, name: "NumberInput" }, { value: NumberInput, name: "NumberInput" },
{ value: OptionalInput, name: "OptionalInput" }, { value: OptionalInput, name: "OptionalInput" },
{ value: ParameterExposeButton, name: "ParameterExposeButton" }, { value: ParameterExposeButton, name: "ParameterExposeButton" },

View File

@ -1,22 +1,16 @@
use fern::colors::{Color, ColoredLevelConfig}; use graph_craft::document::*;
use std::{error::Error, sync::Arc}; use graph_craft::graphene_compiler::Executor;
use graph_craft::imaginate_input::ImaginatePreferences;
use document_legacy::{document::Document, layers::layer_info::LegacyLayerType}; use graph_craft::{concrete, ProtoNodeIdentifier};
use futures::executor::block_on; use graphene_core::application_io::{ApplicationIo, NodeGraphUpdateSender};
use graph_craft::{ use graphene_core::text::FontCache;
concrete,
document::*,
graphene_compiler::{Compiler, Executor},
imaginate_input::ImaginatePreferences,
ProtoNodeIdentifier,
};
use graphene_core::{
application_io::{ApplicationIo, NodeGraphUpdateSender},
text::FontCache,
};
use graphene_std::wasm_application_io::{WasmApplicationIo, WasmEditorApi}; use graphene_std::wasm_application_io::{WasmApplicationIo, WasmEditorApi};
use interpreted_executor::dynamic_executor::DynamicExecutor; use interpreted_executor::dynamic_executor::DynamicExecutor;
use fern::colors::{Color, ColoredLevelConfig};
use futures::executor::block_on;
use std::{error::Error, sync::Arc};
struct UpdateLogger {} struct UpdateLogger {}
impl NodeGraphUpdateSender for UpdateLogger { impl NodeGraphUpdateSender for UpdateLogger {
@ -85,18 +79,17 @@ fn init_logging() {
.unwrap(); .unwrap();
} }
fn create_executor(document_string: String) -> Result<DynamicExecutor, Box<dyn Error>> { fn create_executor(_document_string: String) -> Result<DynamicExecutor, Box<dyn Error>> {
let document: serde_json::Value = serde_json::from_str(&document_string).expect("Failed to parse document"); // let document: serde_json::Value = serde_json::from_str(&document_string).expect("Failed to parse document");
let document = serde_json::from_value::<Document>(document["document_legacy"].clone()).expect("Failed to parse document"); // let document = serde_json::from_value::<Document>(document["document_legacy"].clone()).expect("Failed to parse document");
let Some(LegacyLayerType::Layer(ref node_graph)) = document.root.iter().find(|layer| matches!(layer.data, LegacyLayerType::Layer(_))).map(|x| &x.data) else { // let Some(LegacyLayerType::Layer(ref network)) = document.root.iter().find(|layer| matches!(layer, LegacyLayerType::Layer(_))) else {
panic!("Failed to extract node graph from document") panic!("Failed to extract node graph from document")
}; // };
let network = &node_graph.network; // let wrapped_network = wrap_network_in_scope(network.clone());
let wrapped_network = wrap_network_in_scope(network.clone()); // let compiler = Compiler {};
let compiler = Compiler {}; // let protograph = compiler.compile_single(wrapped_network)?;
let protograph = compiler.compile_single(wrapped_network)?; // let executor = block_on(DynamicExecutor::new(protograph))?;
let executor = block_on(DynamicExecutor::new(protograph))?; // Ok(executor)
Ok(executor)
} }
pub fn wrap_network_in_scope(mut network: NodeNetwork) -> NodeNetwork { pub fn wrap_network_in_scope(mut network: NodeNetwork) -> NodeNetwork {
@ -193,41 +186,41 @@ fn begin_scope() -> DocumentNode {
} }
} }
#[cfg(test)] // #[cfg(test)]
mod test { // mod test {
use super::*; // use super::*;
#[tokio::test] // #[tokio::test]
#[cfg_attr(not(feature = "wayland"), ignore)] // #[cfg_attr(not(feature = "wayland"), ignore)]
async fn grays_scale() { // async fn grays_scale() {
let document_string = include_str!("../test_files/gray.graphite"); // let document_string = include_str!("../test_files/gray.graphite");
let executor = create_executor(document_string.to_string()).unwrap(); // let executor = create_executor(document_string.to_string()).unwrap();
let editor_api = WasmEditorApi { // let editor_api = WasmEditorApi {
image_frame: None, // image_frame: None,
font_cache: &FontCache::default(), // font_cache: &FontCache::default(),
application_io: &block_on(WasmApplicationIo::new()), // application_io: &block_on(WasmApplicationIo::new()),
node_graph_message_sender: &UpdateLogger {}, // node_graph_message_sender: &UpdateLogger {},
imaginate_preferences: &ImaginatePreferences::default(), // imaginate_preferences: &ImaginatePreferences::default(),
render_config: graphene_core::application_io::RenderConfig::default(), // render_config: graphene_core::application_io::RenderConfig::default(),
}; // };
let result = (&executor).execute(editor_api.clone()).await.unwrap(); // let result = (&executor).execute(editor_api.clone()).await.unwrap();
println!("result: {result:?}"); // println!("result: {result:?}");
} // }
#[tokio::test] // #[tokio::test]
#[cfg_attr(not(feature = "wayland"), ignore)] // #[cfg_attr(not(feature = "wayland"), ignore)]
async fn hue() { // async fn hue() {
let document_string = include_str!("../test_files/hue.graphite"); // let document_string = include_str!("../test_files/hue.graphite");
let executor = create_executor(document_string.to_string()).unwrap(); // let executor = create_executor(document_string.to_string()).unwrap();
let editor_api = WasmEditorApi { // let editor_api = WasmEditorApi {
image_frame: None, // image_frame: None,
font_cache: &FontCache::default(), // font_cache: &FontCache::default(),
application_io: &block_on(WasmApplicationIo::new()), // application_io: &block_on(WasmApplicationIo::new()),
node_graph_message_sender: &UpdateLogger {}, // node_graph_message_sender: &UpdateLogger {},
imaginate_preferences: &ImaginatePreferences::default(), // imaginate_preferences: &ImaginatePreferences::default(),
render_config: graphene_core::application_io::RenderConfig::default(), // render_config: graphene_core::application_io::RenderConfig::default(),
}; // };
let result = (&executor).execute(editor_api.clone()).await.unwrap(); // let result = (&executor).execute(editor_api.clone()).await.unwrap();
println!("result: {result:?}"); // println!("result: {result:?}");
} // }
} // }