Artboard nodes (#1328)
* Create artboard nodes * Update node when resizing artboard * Render clipped artboards * More stable feature * Do not render old artboards * Fix some issues with transforms * Fix crash when drawing rectangle * Format * Allow renaming document from Properties panel * Adjust artboard label styling * Fix document graph refresh so artboards show up * Make "Clear Artboards" coming soon * Fix displaying an infinite canvas * Show document name in node graph options bar * info!() to debug!() * Fix Properties panel not being cleared when all docs closed * Remove dead code * Remove debug logs added in this branch --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
parent
ad0dc3276e
commit
08f9be6aaf
|
|
@ -397,7 +397,7 @@ dependencies = [
|
|||
"http",
|
||||
"http-body",
|
||||
"hyper",
|
||||
"itoa 1.0.8",
|
||||
"itoa 1.0.9",
|
||||
"matchit",
|
||||
"memchr",
|
||||
"mime",
|
||||
|
|
@ -1191,9 +1191,9 @@ checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650"
|
|||
|
||||
[[package]]
|
||||
name = "dtoa"
|
||||
version = "1.0.8"
|
||||
version = "1.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "519b83cd10f5f6e969625a409f735182bea5558cd8b64c655806ceaae36f1999"
|
||||
checksum = "dcbb2bf8e87535c23f7a8a321e364ce21462d0ff10cb6407820e8e96dfff6653"
|
||||
|
||||
[[package]]
|
||||
name = "dtoa-short"
|
||||
|
|
@ -1231,9 +1231,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "dyn-clone"
|
||||
version = "1.0.11"
|
||||
version = "1.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68b0cf012f1230e43cd00ebb729c6bb58707ecfa8ad08b52ef3a4ccd2697fc30"
|
||||
checksum = "304e6508efa593091e97a9abbc10f90aa7ca635b6d2784feff3c89d41dd12272"
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
|
|
@ -2320,7 +2320,7 @@ checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482"
|
|||
dependencies = [
|
||||
"bytes",
|
||||
"fnv",
|
||||
"itoa 1.0.8",
|
||||
"itoa 1.0.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -2379,7 +2379,7 @@ dependencies = [
|
|||
"http-body",
|
||||
"httparse",
|
||||
"httpdate",
|
||||
"itoa 1.0.8",
|
||||
"itoa 1.0.9",
|
||||
"pin-project-lite",
|
||||
"socket2",
|
||||
"tokio",
|
||||
|
|
@ -2598,9 +2598,9 @@ checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4"
|
|||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.8"
|
||||
version = "1.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62b02a5381cc465bd3041d84623d0fa3b66738b52b8e2fc3bab8ad63ab032f4a"
|
||||
checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"
|
||||
|
||||
[[package]]
|
||||
name = "javascriptcore-rs"
|
||||
|
|
@ -3597,9 +3597,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "paste"
|
||||
version = "1.0.13"
|
||||
version = "1.0.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4b27ab7be369122c218afc2079489cdcb4b517c0a3fc386ff11e1fedfcc2b35"
|
||||
checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
|
||||
|
||||
[[package]]
|
||||
name = "pathdiff"
|
||||
|
|
@ -3856,9 +3856,9 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068"
|
|||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.64"
|
||||
version = "1.0.65"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78803b62cbf1f46fde80d7c0e803111524b9877184cfe7c3033659490ac7a7da"
|
||||
checksum = "92de25114670a878b1261c79c9f8f729fb97e95bac93f6312f583c60dd6a1dfe"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
|
@ -3889,9 +3889,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.29"
|
||||
version = "1.0.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105"
|
||||
checksum = "5907a1b7c277254a8b15170f6e7c97cfa60ee7872a3217663bb81151e48184bb"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
|
@ -4281,9 +4281,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.13"
|
||||
version = "1.0.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc31bd9b61a32c31f9650d18add92aa83a49ba979c143eefd27fe7177b05bd5f"
|
||||
checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4"
|
||||
|
||||
[[package]]
|
||||
name = "rustybuzz"
|
||||
|
|
@ -4303,9 +4303,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.14"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe232bdf6be8c8de797b22184ee71118d63780ea42ac85b61d1baa6d3b782ae9"
|
||||
checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"
|
||||
|
||||
[[package]]
|
||||
name = "safe_arch"
|
||||
|
|
@ -4420,9 +4420,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.17"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed"
|
||||
checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
|
@ -4471,22 +4471,22 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.102"
|
||||
version = "1.0.103"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5062a995d481b2308b6064e9af76011f2921c35f97b0468811ed9f6cd91dfed"
|
||||
checksum = "d03b412469450d4404fe8499a268edd7f8b79fecb074b0d812ad64ca21f4031b"
|
||||
dependencies = [
|
||||
"itoa 1.0.8",
|
||||
"itoa 1.0.9",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_path_to_error"
|
||||
version = "0.1.13"
|
||||
version = "0.1.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8acc4422959dd87a76cb117c191dcbffc20467f06c9100b76721dab370f24d3a"
|
||||
checksum = "4beec8bce849d58d06238cb50db2e1c417cfeafa4c63f692b15c82b7c80f8335"
|
||||
dependencies = [
|
||||
"itoa 1.0.8",
|
||||
"itoa 1.0.9",
|
||||
"serde",
|
||||
]
|
||||
|
||||
|
|
@ -4517,7 +4517,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
|
||||
dependencies = [
|
||||
"form_urlencoded",
|
||||
"itoa 1.0.8",
|
||||
"itoa 1.0.9",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
|
@ -5405,7 +5405,7 @@ version = "0.3.23"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59e399c068f43a5d116fedaf73b203fa4f9c519f17e2b34f63221d3792f81446"
|
||||
dependencies = [
|
||||
"itoa 1.0.8",
|
||||
"itoa 1.0.9",
|
||||
"libc",
|
||||
"num_threads",
|
||||
"serde",
|
||||
|
|
|
|||
|
|
@ -5,7 +5,9 @@ use crate::messages::layout::utility_types::widgets::input_widgets::{CheckboxInp
|
|||
use crate::messages::layout::utility_types::widgets::label_widgets::{Separator, SeparatorDirection, SeparatorType, TextLabel};
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
use glam::UVec2;
|
||||
use graphene_core::uuid::generate_uuid;
|
||||
|
||||
use glam::{IVec2, UVec2};
|
||||
|
||||
/// A dialog to allow users to set some initial options about a new document.
|
||||
#[derive(Debug, Clone, Default)]
|
||||
|
|
@ -27,13 +29,20 @@ impl MessageHandler<NewDocumentDialogMessage, ()> for NewDocumentDialogMessageHa
|
|||
responses.add(PortfolioMessage::NewDocumentWithName { name: self.name.clone() });
|
||||
|
||||
if !self.infinite && self.dimensions.x > 0 && self.dimensions.y > 0 {
|
||||
let id = generate_uuid();
|
||||
responses.add(ArtboardMessage::AddArtboard {
|
||||
id: None,
|
||||
id: Some(id),
|
||||
position: (0., 0.),
|
||||
size: (self.dimensions.x as f64, self.dimensions.y as f64),
|
||||
});
|
||||
responses.add(GraphOperationMessage::NewArtboard {
|
||||
id,
|
||||
artboard: graphene_core::Artboard::new(IVec2::ZERO, self.dimensions.as_ivec2()),
|
||||
});
|
||||
responses.add(DocumentMessage::ZoomCanvasToFitAll);
|
||||
}
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
responses.add(NodeGraphMessage::UpdateNewNodeGraph);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -150,6 +150,9 @@ pub enum FrontendMessage {
|
|||
layout_target: LayoutTarget,
|
||||
diff: Vec<WidgetDiff>,
|
||||
},
|
||||
UpdateDocumentNodeRender {
|
||||
svg: String,
|
||||
},
|
||||
UpdateDocumentOverlays {
|
||||
svg: String,
|
||||
},
|
||||
|
|
@ -163,6 +166,9 @@ pub enum FrontendMessage {
|
|||
size: (f64, f64),
|
||||
multiplier: (f64, f64),
|
||||
},
|
||||
UpdateDocumentTransform {
|
||||
transform: String,
|
||||
},
|
||||
UpdateEyedropperSamplingState {
|
||||
#[serde(rename = "mousePosition")]
|
||||
mouse_position: Option<(f64, f64)>,
|
||||
|
|
|
|||
|
|
@ -32,17 +32,7 @@ impl MessageHandler<InputPreprocessorMessage, KeyboardPlatformLayout> for InputP
|
|||
// TODO: Extend this to multiple viewports instead of setting it to the value of this last loop iteration
|
||||
self.viewport_bounds = bounds;
|
||||
|
||||
responses.add(Operation::TransformLayer {
|
||||
path: vec![],
|
||||
transform: glam::DAffine2::from_translation(translation).to_cols_array(),
|
||||
});
|
||||
responses.add(DocumentMessage::Artboard(
|
||||
Operation::TransformLayer {
|
||||
path: vec![],
|
||||
transform: glam::DAffine2::from_translation(translation).to_cols_array(),
|
||||
}
|
||||
.into(),
|
||||
));
|
||||
responses.add(NavigationMessage::TranslateCanvas { delta: DVec2::ZERO });
|
||||
responses.add(FrontendMessage::TriggerViewportResize);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -61,9 +61,11 @@ impl MessageHandler<ArtboardMessage, &PersistentData> for ArtboardMessageHandler
|
|||
responses.add(DocumentMessage::RenderDocument);
|
||||
}
|
||||
ClearArtboards => {
|
||||
for &artboard in self.artboard_ids.iter() {
|
||||
responses.add_front(ArtboardMessage::DeleteArtboard { artboard });
|
||||
}
|
||||
// TODO: Make this remove the artboard layers from the graph (and cleanly reconnect the artwork)
|
||||
responses.add(DialogMessage::RequestComingSoonDialog { issue: None });
|
||||
// for &artboard in self.artboard_ids.iter() {
|
||||
// responses.add_front(ArtboardMessage::DeleteArtboard { artboard });
|
||||
// }
|
||||
}
|
||||
DeleteArtboard { artboard } => {
|
||||
self.artboard_ids.retain(|&id| id != artboard);
|
||||
|
|
@ -78,11 +80,13 @@ impl MessageHandler<ArtboardMessage, &PersistentData> for ArtboardMessageHandler
|
|||
responses.add(FrontendMessage::UpdateDocumentArtboards {
|
||||
svg: r##"<rect width="100%" height="100%" fill="#ffffff" />"##.to_string(),
|
||||
})
|
||||
} else {
|
||||
let render_data = RenderData::new(&persistent_data.font_cache, ViewMode::Normal, None);
|
||||
responses.add(FrontendMessage::UpdateDocumentArtboards {
|
||||
svg: self.artboards_document.render_root(&render_data),
|
||||
});
|
||||
// TODO: Delete this whole legacy code path when cleaning up/removing the old (non-node based) artboard implementation
|
||||
// TODO: The below code was used to draw the non-node based artboards, but we still need the above code to draw the infinite canvas until the refactor is complete and all this code can be removed
|
||||
// } else {
|
||||
// let render_data = RenderData::new(&persistent_data.font_cache, ViewMode::Normal, None);
|
||||
// responses.add(FrontendMessage::UpdateDocumentArtboards {
|
||||
// svg: self.artboards_document.render_root(&render_data),
|
||||
// });
|
||||
}
|
||||
}
|
||||
ResizeArtboard { artboard, position, mut size } => {
|
||||
|
|
|
|||
|
|
@ -124,6 +124,9 @@ pub enum DocumentMessage {
|
|||
mouse: Option<(f64, f64)>,
|
||||
},
|
||||
Redo,
|
||||
RenameDocument {
|
||||
new_name: String,
|
||||
},
|
||||
RenameLayer {
|
||||
layer_path: Vec<LayerId>,
|
||||
new_name: String,
|
||||
|
|
|
|||
|
|
@ -197,6 +197,7 @@ impl MessageHandler<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &Pe
|
|||
#[remain::unsorted]
|
||||
PropertiesPanel(message) => {
|
||||
let properties_panel_message_handler_data = PropertiesPanelMessageHandlerData {
|
||||
document_name: &self.name.as_str(),
|
||||
artwork_document: &self.document_legacy,
|
||||
artboard_document: &self.artboard_message_handler.artboards_document,
|
||||
selected_layers: &mut self.layer_metadata.iter().filter_map(|(path, data)| data.selected.then_some(path.as_slice())),
|
||||
|
|
@ -208,7 +209,8 @@ impl MessageHandler<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &Pe
|
|||
}
|
||||
#[remain::unsorted]
|
||||
NodeGraph(message) => {
|
||||
self.node_graph_handler.process_message(message, responses, (&mut self.document_legacy, executor, document_id));
|
||||
self.node_graph_handler
|
||||
.process_message(message, responses, (&mut self.document_legacy, executor, document_id, self.name.as_str()));
|
||||
}
|
||||
#[remain::unsorted]
|
||||
GraphOperation(message) => GraphOperationMessageHandler.process_message(message, responses, (&mut self.document_legacy, &mut self.node_graph_handler)),
|
||||
|
|
@ -659,6 +661,11 @@ impl MessageHandler<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &Pe
|
|||
responses.add(RenderDocument);
|
||||
responses.add(FolderChanged { affected_folder_path: vec![] });
|
||||
}
|
||||
RenameDocument { new_name } => {
|
||||
self.name = new_name;
|
||||
responses.add(PortfolioMessage::UpdateOpenDocumentsList);
|
||||
responses.add(NodeGraphMessage::UpdateNewNodeGraph);
|
||||
}
|
||||
RenameLayer { layer_path, new_name } => responses.add(DocumentOperation::RenameLayer { layer_path, new_name }),
|
||||
RenderDocument => {
|
||||
responses.add(FrontendMessage::UpdateDocumentArtwork {
|
||||
|
|
@ -1096,6 +1103,7 @@ impl DocumentMessageHandler {
|
|||
let starting_root_transform = document.navigation_handler.calculate_offset_transform(ipp.viewport_bounds.size() / 2.);
|
||||
document.document_legacy.root.transform = starting_root_transform;
|
||||
document.artboard_message_handler.artboards_document.root.transform = starting_root_transform;
|
||||
|
||||
document
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
|
|||
|
||||
use document_legacy::document::Document;
|
||||
use document_legacy::Operation as DocumentOperation;
|
||||
use graphene_core::renderer::format_transform_matrix;
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
|
@ -352,6 +353,9 @@ impl NavigationMessageHandler {
|
|||
}
|
||||
.into(),
|
||||
));
|
||||
let transform = format_transform_matrix(self.calculate_offset_transform(scaled_half_viewport));
|
||||
responses.add(FrontendMessage::UpdateDocumentTransform { transform });
|
||||
// TODO: Artboard pos
|
||||
}
|
||||
|
||||
pub fn center_zoom(&self, viewport_bounds: DVec2, zoom_factor: f64, mouse: DVec2) -> Message {
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
use crate::messages::prelude::*;
|
||||
|
||||
use graph_craft::document::NodeId;
|
||||
use graphene_core::uuid::ManipulatorGroupId;
|
||||
use graphene_core::vector::brush_stroke::BrushStroke;
|
||||
use graphene_core::vector::style::{Fill, Stroke};
|
||||
use graphene_core::vector::ManipulatorPointId;
|
||||
use graphene_core::Artboard;
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
use glam::{DAffine2, DVec2, IVec2};
|
||||
|
||||
pub type LayerIdentifier = Vec<document_legacy::LayerId>;
|
||||
|
||||
|
|
@ -51,6 +53,20 @@ pub enum GraphOperationMessage {
|
|||
layer: LayerIdentifier,
|
||||
strokes: Vec<BrushStroke>,
|
||||
},
|
||||
|
||||
NewArtboard {
|
||||
id: NodeId,
|
||||
artboard: Artboard,
|
||||
},
|
||||
ResizeArtboard {
|
||||
id: NodeId,
|
||||
location: IVec2,
|
||||
dimensions: IVec2,
|
||||
},
|
||||
DeleteArtboard {
|
||||
id: NodeId,
|
||||
},
|
||||
ClearArtboards,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Copy, Debug, serde::Serialize, serde::Deserialize)]
|
||||
|
|
|
|||
|
|
@ -4,12 +4,13 @@ use crate::messages::prelude::*;
|
|||
use document_legacy::document::Document;
|
||||
use document_legacy::{LayerId, Operation};
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::{generate_uuid, NodeId, NodeInput, NodeNetwork};
|
||||
use graph_craft::document::{generate_uuid, DocumentNode, DocumentNodeMetadata, NodeId, NodeInput, NodeNetwork, NodeOutput};
|
||||
use graphene_core::vector::brush_stroke::BrushStroke;
|
||||
use graphene_core::vector::style::{Fill, FillType, Stroke};
|
||||
use graphene_core::Artboard;
|
||||
use transform_utils::LayerBounds;
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
use glam::{DAffine2, DVec2, IVec2};
|
||||
|
||||
pub mod transform_utils;
|
||||
|
||||
|
|
@ -21,27 +22,152 @@ struct ModifyInputsContext<'a> {
|
|||
node_graph: &'a mut NodeGraphMessageHandler,
|
||||
responses: &'a mut VecDeque<Message>,
|
||||
layer: &'a [LayerId],
|
||||
outwards_links: HashMap<NodeId, Vec<NodeId>>,
|
||||
layer_node: Option<NodeId>,
|
||||
}
|
||||
impl<'a> ModifyInputsContext<'a> {
|
||||
/// Get the node network from the document
|
||||
fn new(layer: &'a [LayerId], document: &'a mut Document, node_graph: &'a mut NodeGraphMessageHandler, responses: &'a mut VecDeque<Message>) -> Option<Self> {
|
||||
document.layer_mut(layer).ok().and_then(|layer| layer.as_layer_network_mut().ok()).map(|network| Self {
|
||||
outwards_links: network.collect_outwards_links(),
|
||||
network,
|
||||
node_graph,
|
||||
responses,
|
||||
layer,
|
||||
layer_node: None,
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the node network from the document
|
||||
fn new_doc(document: &'a mut Document, node_graph: &'a mut NodeGraphMessageHandler, responses: &'a mut VecDeque<Message>) -> Self {
|
||||
Self {
|
||||
outwards_links: document.document_network.collect_outwards_links(),
|
||||
network: &mut document.document_network,
|
||||
node_graph,
|
||||
responses,
|
||||
layer: &[],
|
||||
layer_node: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn locate_layer(&mut self, mut id: NodeId) -> Option<NodeId> {
|
||||
while self.network.nodes.get(&id)?.name != "Layer" {
|
||||
id = self.outwards_links.get(&id)?.first().copied()?;
|
||||
}
|
||||
self.layer_node = Some(id);
|
||||
Some(id)
|
||||
}
|
||||
|
||||
/// Updates the input of an existing node
|
||||
fn modify_existing_node_inputs(&mut self, node_id: NodeId, update_input: impl FnOnce(&mut Vec<NodeInput>)) {
|
||||
let document_node = self.network.nodes.get_mut(&node_id).unwrap();
|
||||
update_input(&mut document_node.inputs);
|
||||
}
|
||||
|
||||
pub fn insert_between(&mut self, pre: NodeOutput, post: NodeOutput, mut node: DocumentNode, input: usize, output: usize) -> Option<NodeId> {
|
||||
let id = generate_uuid();
|
||||
let pre_node = self.network.nodes.get_mut(&pre.node_id)?;
|
||||
node.metadata.position = pre_node.metadata.position;
|
||||
|
||||
let post_node = self.network.nodes.get_mut(&post.node_id)?;
|
||||
node.inputs[input] = NodeInput::node(pre.node_id, pre.node_output_index);
|
||||
post_node.inputs[post.node_output_index] = NodeInput::node(id, output);
|
||||
|
||||
self.network.nodes.insert(id, node);
|
||||
|
||||
self.shift_upstream(id, IVec2::new(-8, 0));
|
||||
|
||||
Some(id)
|
||||
}
|
||||
|
||||
pub fn insert_layer_below(&mut self, node_id: NodeId, input_index: usize) -> Option<NodeId> {
|
||||
let layer_node = resolve_document_node_type("Layer").expect("Layer node");
|
||||
|
||||
let new_id = generate_uuid();
|
||||
let post_node = self.network.nodes.get_mut(&node_id)?;
|
||||
post_node.inputs[input_index] = NodeInput::node(new_id, 0);
|
||||
let document_node = layer_node.to_document_node_default_inputs([], DocumentNodeMetadata::position(post_node.metadata.position + IVec2::new(0, 2)));
|
||||
|
||||
self.network.nodes.insert(new_id, document_node);
|
||||
|
||||
Some(new_id)
|
||||
}
|
||||
|
||||
pub fn insert_node_before(&mut self, new_id: NodeId, node_id: NodeId, input_index: usize, mut document_node: DocumentNode, offset: IVec2) -> Option<NodeId> {
|
||||
let post_node = self.network.nodes.get_mut(&node_id)?;
|
||||
|
||||
post_node.inputs[input_index] = NodeInput::node(new_id, 0);
|
||||
document_node.metadata.position = post_node.metadata.position + offset;
|
||||
self.network.nodes.insert(new_id, document_node);
|
||||
|
||||
Some(new_id)
|
||||
}
|
||||
|
||||
pub fn create_layer(&mut self, new_id: NodeId, output_node_id: NodeId) -> Option<NodeId> {
|
||||
let mut current_node = output_node_id;
|
||||
let mut input_index = 0;
|
||||
let mut current_input = &self.network.nodes.get(¤t_node)?.inputs[input_index];
|
||||
|
||||
while let NodeInput::Node { node_id, output_index, .. } = current_input {
|
||||
let mut sibling_node = &self.network.nodes.get(node_id)?;
|
||||
if sibling_node.name == "Layer" {
|
||||
current_node = *node_id;
|
||||
input_index = 7;
|
||||
current_input = &self.network.nodes.get(¤t_node)?.inputs[input_index];
|
||||
} else {
|
||||
// Insert a layer node between the output and the new
|
||||
let layer_node = resolve_document_node_type("Layer").expect("Layer node");
|
||||
let node = layer_node.to_document_node_default_inputs([], DocumentNodeMetadata::default());
|
||||
let node_id = self.insert_between(NodeOutput::new(*node_id, *output_index), NodeOutput::new(current_node, input_index), node, 0, 0)?;
|
||||
current_node = node_id;
|
||||
input_index = 7;
|
||||
current_input = &self.network.nodes.get(¤t_node)?.inputs[input_index];
|
||||
}
|
||||
}
|
||||
|
||||
let layer_node = resolve_document_node_type("Layer").expect("Node").to_document_node_default_inputs([], Default::default());
|
||||
let layer_node = self.insert_node_before(new_id, current_node, input_index, layer_node, IVec2::new(0, 3))?;
|
||||
|
||||
Some(layer_node)
|
||||
}
|
||||
|
||||
fn insert_artboard(&mut self, artboard: Artboard, layer: NodeId) -> Option<NodeId> {
|
||||
let artboard_node = resolve_document_node_type("Artboard").expect("Node").to_document_node_default_inputs(
|
||||
[
|
||||
None,
|
||||
Some(NodeInput::value(TaggedValue::IVec2(artboard.location), false)),
|
||||
Some(NodeInput::value(TaggedValue::IVec2(artboard.dimensions), false)),
|
||||
Some(NodeInput::value(TaggedValue::Color(artboard.background), false)),
|
||||
Some(NodeInput::value(TaggedValue::Bool(artboard.clip), false)),
|
||||
],
|
||||
Default::default(),
|
||||
);
|
||||
self.insert_node_before(generate_uuid(), layer, 0, artboard_node, IVec2::new(-8, 0))
|
||||
}
|
||||
|
||||
fn shift_upstream(&mut self, node_id: NodeId, shift: IVec2) {
|
||||
let mut shift_nodes = HashSet::new();
|
||||
let mut stack = vec![node_id];
|
||||
while let Some(node) = stack.pop() {
|
||||
let Some(node) = self.network.nodes.get(&node_id) else { continue };
|
||||
for input in &node.inputs {
|
||||
let NodeInput::Node { node_id, .. } = input else { continue };
|
||||
if shift_nodes.insert(*node_id) {
|
||||
stack.push(*node_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for node_id in shift_nodes {
|
||||
if let Some(node) = self.network.nodes.get_mut(&node_id) {
|
||||
node.metadata.position += shift;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Inserts a new node and modifies the inputs
|
||||
fn modify_new_node(&mut self, name: &'static str, update_input: impl FnOnce(&mut Vec<NodeInput>)) {
|
||||
let output_node_id = self.network.outputs[0].node_id;
|
||||
let output_node_id = self.layer_node.unwrap_or(self.network.outputs[0].node_id);
|
||||
let Some(output_node) = self.network.nodes.get_mut(&output_node_id) else {
|
||||
warn!("Output node doesn't exist");
|
||||
return;
|
||||
|
|
@ -65,7 +191,7 @@ impl<'a> ModifyInputsContext<'a> {
|
|||
|
||||
/// Changes the inputs of a specific node
|
||||
fn modify_inputs(&mut self, name: &'static str, skip_rerender: bool, update_input: impl FnOnce(&mut Vec<NodeInput>)) {
|
||||
let existing_node_id = self.network.primary_flow().find(|(node, _)| node.name == name).map(|(_, id)| id);
|
||||
let existing_node_id = self.network.primary_flow_from_opt(self.layer_node).find(|(node, _)| node.name == name).map(|(_, id)| id);
|
||||
if let Some(node_id) = existing_node_id {
|
||||
self.modify_existing_node_inputs(node_id, update_input);
|
||||
} else {
|
||||
|
|
@ -229,6 +355,36 @@ impl<'a> ModifyInputsContext<'a> {
|
|||
inputs[2] = NodeInput::value(TaggedValue::BrushStrokes(strokes), false);
|
||||
});
|
||||
}
|
||||
|
||||
fn resize_artboard(&mut self, location: IVec2, dimensions: IVec2) {
|
||||
self.modify_inputs("Artboard", false, |inputs| {
|
||||
inputs[1] = NodeInput::value(TaggedValue::IVec2(location), false);
|
||||
inputs[2] = NodeInput::value(TaggedValue::IVec2(dimensions), false);
|
||||
});
|
||||
}
|
||||
|
||||
fn delete_layer(&mut self, id: NodeId) {
|
||||
let mut new_input = None;
|
||||
let post_node = self.outwards_links.get(&id).and_then(|links| links.first().copied());
|
||||
let mut delete_nodes = vec![id];
|
||||
for (node, id) in self.network.primary_flow_from_opt(Some(id)) {
|
||||
delete_nodes.push(id);
|
||||
if node.name == "Artboard" {
|
||||
new_input = Some(node.inputs[0].clone());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for node_id in delete_nodes {
|
||||
self.network.nodes.remove(&node_id);
|
||||
}
|
||||
|
||||
if let (Some(new_input), Some(post_node)) = (new_input, post_node) {
|
||||
if let Some(node) = self.network.nodes.get_mut(&post_node) {
|
||||
node.inputs[0] = new_input;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MessageHandler<GraphOperationMessage, (&mut Document, &mut NodeGraphMessageHandler)> for GraphOperationMessageHandler {
|
||||
|
|
@ -316,6 +472,31 @@ impl MessageHandler<GraphOperationMessage, (&mut Document, &mut NodeGraphMessage
|
|||
modify_inputs.brush_modify(strokes);
|
||||
}
|
||||
}
|
||||
GraphOperationMessage::NewArtboard { id, artboard } => {
|
||||
let mut modify_inputs = ModifyInputsContext::new_doc(document, node_graph, responses);
|
||||
if let Some(layer) = modify_inputs.create_layer(id, modify_inputs.network.outputs[0].node_id) {
|
||||
modify_inputs.insert_artboard(artboard, layer);
|
||||
}
|
||||
|
||||
//modify_inputs.brush_modify(strokes);
|
||||
}
|
||||
GraphOperationMessage::ResizeArtboard { id, location, dimensions } => {
|
||||
let mut modify_inputs = ModifyInputsContext::new_doc(document, node_graph, responses);
|
||||
if let Some(layer) = modify_inputs.locate_layer(id) {
|
||||
modify_inputs.resize_artboard(location, dimensions);
|
||||
}
|
||||
}
|
||||
GraphOperationMessage::DeleteArtboard { id } => {
|
||||
let mut modify_inputs = ModifyInputsContext::new_doc(document, node_graph, responses);
|
||||
modify_inputs.delete_layer(id);
|
||||
}
|
||||
GraphOperationMessage::ClearArtboards => {
|
||||
let mut modify_inputs = ModifyInputsContext::new_doc(document, node_graph, responses);
|
||||
let artboard_nodes = modify_inputs.network.nodes.iter().filter(|(_, node)| node.name == "Artboard").map(|(id, _)| *id).collect::<Vec<_>>();
|
||||
for id in artboard_nodes {
|
||||
modify_inputs.delete_layer(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -156,7 +156,7 @@ impl NodeGraphMessageHandler {
|
|||
self.get_root_network_mut(document).nested_network_mut(&self.nested_path)
|
||||
}
|
||||
|
||||
/// Send the cached layout for the bar at the top of the node panel to the frontend
|
||||
/// 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>) {
|
||||
responses.add(LayoutMessage::SendLayout {
|
||||
layout: Layout::WidgetLayout(WidgetLayout::new(self.widgets.to_vec())),
|
||||
|
|
@ -165,34 +165,48 @@ impl NodeGraphMessageHandler {
|
|||
}
|
||||
|
||||
/// Collect the addresses of the currently viewed nested node e.g. Root -> MyFunFilter -> Exposure
|
||||
fn collect_nested_addresses(&mut self, document: &Document, responses: &mut VecDeque<Message>) {
|
||||
// // Build path list
|
||||
let mut path = vec![];
|
||||
if let Some(layer) = self.layer_path.as_ref().and_then(|path| document.layer(path).ok()) {
|
||||
path.push(format!("Layer: {}", layer.name.as_deref().unwrap_or("Untitled Layer")));
|
||||
} else {
|
||||
path.push("Document Network".to_string());
|
||||
}
|
||||
fn collect_nested_addresses(&mut self, document: &Document, document_name: &str, responses: &mut VecDeque<Message>) {
|
||||
let layer_if_selected = self.layer_path.as_ref().and_then(|path| document.layer(path).ok());
|
||||
|
||||
// Build path list for the layer, or otherwise the root document
|
||||
let path_root = match layer_if_selected {
|
||||
Some(layer) => layer.name.as_deref().unwrap_or("Untitled Layer"),
|
||||
None => document_name,
|
||||
};
|
||||
let mut path = vec![path_root.to_string()];
|
||||
|
||||
let (icon, tooltip) = match layer_if_selected {
|
||||
Some(_) => ("Layer", "Layer"),
|
||||
None => ("File", "Document"),
|
||||
};
|
||||
|
||||
let mut network = Some(self.get_root_network(document));
|
||||
for node_id in &self.nested_path {
|
||||
let node = network.and_then(|network| network.nodes.get(node_id));
|
||||
|
||||
if let Some(DocumentNode { name, .. }) = node {
|
||||
path.push(name.clone());
|
||||
}
|
||||
|
||||
network = node.and_then(|node| node.implementation.get_network());
|
||||
}
|
||||
|
||||
let nesting = path.len();
|
||||
|
||||
// Update UI
|
||||
self.widgets[0] = LayoutGroup::Row {
|
||||
widgets: vec![BreadcrumbTrailButtons::new(path.clone())
|
||||
.on_update(move |input: &u64| {
|
||||
NodeGraphMessage::ExitNestedNetwork {
|
||||
depth_of_nesting: nesting - (*input as usize) - 1,
|
||||
}
|
||||
.into()
|
||||
})
|
||||
.widget_holder()],
|
||||
widgets: vec![
|
||||
IconLabel::new(icon).tooltip(tooltip).widget_holder(),
|
||||
WidgetHolder::unrelated_separator(),
|
||||
BreadcrumbTrailButtons::new(path.clone())
|
||||
.on_update(move |input: &u64| {
|
||||
NodeGraphMessage::ExitNestedNetwork {
|
||||
depth_of_nesting: nesting - (*input as usize) - 1,
|
||||
}
|
||||
.into()
|
||||
})
|
||||
.widget_holder(),
|
||||
],
|
||||
};
|
||||
|
||||
self.send_node_bar_layout(responses);
|
||||
|
|
@ -425,9 +439,9 @@ impl NodeGraphMessageHandler {
|
|||
}
|
||||
}
|
||||
|
||||
impl MessageHandler<NodeGraphMessage, (&mut Document, &NodeGraphExecutor, u64)> for NodeGraphMessageHandler {
|
||||
impl MessageHandler<NodeGraphMessage, (&mut Document, &NodeGraphExecutor, u64, &str)> for NodeGraphMessageHandler {
|
||||
#[remain::check]
|
||||
fn process_message(&mut self, message: NodeGraphMessage, responses: &mut VecDeque<Message>, (document, executor, document_id): (&mut Document, &NodeGraphExecutor, u64)) {
|
||||
fn process_message(&mut self, message: NodeGraphMessage, responses: &mut VecDeque<Message>, (document, executor, document_id, document_name): (&mut Document, &NodeGraphExecutor, u64, &str)) {
|
||||
#[remain::sorted]
|
||||
match message {
|
||||
NodeGraphMessage::CloseNodeGraph => {
|
||||
|
|
@ -564,7 +578,7 @@ impl MessageHandler<NodeGraphMessage, (&mut Document, &NodeGraphExecutor, u64)>
|
|||
if let Some(network) = self.get_active_network(document) {
|
||||
Self::send_graph(network, executor, &self.layer_path, responses);
|
||||
}
|
||||
self.collect_nested_addresses(document, responses);
|
||||
self.collect_nested_addresses(document, document_name, responses);
|
||||
self.update_selected(document, responses);
|
||||
}
|
||||
NodeGraphMessage::DuplicateSelectedNodes => {
|
||||
|
|
@ -600,7 +614,7 @@ impl MessageHandler<NodeGraphMessage, (&mut Document, &NodeGraphExecutor, u64)>
|
|||
if let Some(network) = self.get_active_network(document) {
|
||||
Self::send_graph(network, executor, &self.layer_path, responses);
|
||||
}
|
||||
self.collect_nested_addresses(document, responses);
|
||||
self.collect_nested_addresses(document, document_name, responses);
|
||||
self.update_selected(document, responses);
|
||||
}
|
||||
NodeGraphMessage::ExposeInput { node_id, input_index, new_exposed } => {
|
||||
|
|
@ -662,7 +676,7 @@ impl MessageHandler<NodeGraphMessage, (&mut Document, &NodeGraphExecutor, u64)>
|
|||
let node_types = document_node_types::collect_node_types();
|
||||
responses.add(FrontendMessage::UpdateNodeTypes { node_types });
|
||||
}
|
||||
self.collect_nested_addresses(document, responses);
|
||||
self.collect_nested_addresses(document, document_name, responses);
|
||||
self.update_selected(document, responses);
|
||||
}
|
||||
NodeGraphMessage::PasteNodes { serialized_nodes } => {
|
||||
|
|
@ -895,7 +909,7 @@ impl MessageHandler<NodeGraphMessage, (&mut Document, &NodeGraphExecutor, u64)>
|
|||
let node_types = document_node_types::collect_node_types();
|
||||
responses.add(FrontendMessage::UpdateNodeTypes { node_types });
|
||||
}
|
||||
self.collect_nested_addresses(document, responses);
|
||||
self.collect_nested_addresses(document, document_name, responses);
|
||||
self.update_selected(document, responses);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -204,12 +204,13 @@ fn static_nodes() -> Vec<DocumentNodeType> {
|
|||
DocumentNodeType {
|
||||
name: "Artboard",
|
||||
category: "General",
|
||||
identifier: NodeImplementation::proto("graphene_core::ConstructArtboardNode<_, _, _>"),
|
||||
identifier: NodeImplementation::proto("graphene_core::ConstructArtboardNode<_, _, _, _>"),
|
||||
inputs: vec![
|
||||
DocumentInputType::value("Graphic Group", TaggedValue::GraphicGroup(GraphicGroup::EMPTY), true),
|
||||
DocumentInputType::value("Location", TaggedValue::IVec2(glam::IVec2::ZERO), false),
|
||||
DocumentInputType::value("Dimensions", TaggedValue::IVec2(glam::IVec2::new(1920, 1080)), false),
|
||||
DocumentInputType::value("Background", TaggedValue::Color(Color::WHITE), false),
|
||||
DocumentInputType::value("Clip", TaggedValue::Bool(false), false),
|
||||
],
|
||||
outputs: vec![DocumentOutputType::new("Out", FrontendGraphDataType::Artboard)],
|
||||
properties: node_properties::artboard_properties,
|
||||
|
|
|
|||
|
|
@ -1623,5 +1623,8 @@ pub fn artboard_properties(document_node: &DocumentNode, node_id: NodeId, _conte
|
|||
let location = vec2_widget(document_node, node_id, 1, "Location", "X", "Y", " px", add_blank_assist);
|
||||
let dimensions = vec2_widget(document_node, node_id, 2, "Dimensions", "W", "H", " px", add_blank_assist);
|
||||
let background = color_widget(document_node, node_id, 3, "Background", ColorInput::default().allow_none(false), true);
|
||||
vec![location, dimensions, background]
|
||||
let clip = LayoutGroup::Row {
|
||||
widgets: bool_widget(document_node, node_id, 4, "Clip", true),
|
||||
};
|
||||
vec![location, dimensions, background, clip]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ impl<'a> MessageHandler<PropertiesPanelMessage, (&PersistentData, PropertiesPane
|
|||
use PropertiesPanelMessage::*;
|
||||
|
||||
let PropertiesPanelMessageHandlerData {
|
||||
document_name,
|
||||
artwork_document,
|
||||
artboard_document,
|
||||
selected_layers,
|
||||
|
|
@ -63,6 +64,9 @@ impl<'a> MessageHandler<PropertiesPanelMessage, (&PersistentData, PropertiesPane
|
|||
}
|
||||
}
|
||||
ClearSelection => {
|
||||
// This causes the Properties panel to change, so this needs to happen before the following lines clear the Properties panel
|
||||
responses.add(NodeGraphMessage::CloseNodeGraph);
|
||||
|
||||
responses.add(LayoutMessage::SendLayout {
|
||||
layout: Layout::WidgetLayout(WidgetLayout::new(vec![])),
|
||||
layout_target: LayoutTarget::PropertiesOptions,
|
||||
|
|
@ -71,7 +75,6 @@ impl<'a> MessageHandler<PropertiesPanelMessage, (&PersistentData, PropertiesPane
|
|||
layout: Layout::WidgetLayout(WidgetLayout::new(vec![])),
|
||||
layout_target: LayoutTarget::PropertiesSections,
|
||||
});
|
||||
responses.add(NodeGraphMessage::CloseNodeGraph);
|
||||
self.active_selection = None;
|
||||
}
|
||||
Deactivate => responses.add(BroadcastMessage::UnsubscribeEvent {
|
||||
|
|
@ -151,7 +154,7 @@ impl<'a> MessageHandler<PropertiesPanelMessage, (&PersistentData, PropertiesPane
|
|||
executor,
|
||||
network: &artwork_document.document_network,
|
||||
};
|
||||
register_document_graph_properties(context, node_graph_message_handler);
|
||||
register_document_graph_properties(context, node_graph_message_handler, document_name);
|
||||
}
|
||||
}
|
||||
UpdateSelectedDocumentProperties => responses.add(PropertiesPanelMessage::SetActiveLayers {
|
||||
|
|
|
|||
|
|
@ -78,19 +78,14 @@ pub fn register_artboard_layer_properties(layer: &Layer, responses: &mut VecDequ
|
|||
..Default::default()
|
||||
})),
|
||||
WidgetHolder::unrelated_separator(),
|
||||
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||
value: "Artboard".into(),
|
||||
..TextLabel::default()
|
||||
})),
|
||||
WidgetHolder::unrelated_separator(),
|
||||
WidgetHolder::new(Widget::TextInput(TextInput {
|
||||
value: layer.name.clone().unwrap_or_else(|| "Untitled".to_string()),
|
||||
value: layer.name.clone().unwrap_or_else(|| "Untitled Artboard".to_string()),
|
||||
on_update: WidgetCallback::new(|text_input: &TextInput| PropertiesPanelMessage::ModifyName { name: text_input.value.clone() }.into()),
|
||||
..Default::default()
|
||||
})),
|
||||
WidgetHolder::related_separator(),
|
||||
WidgetHolder::new(Widget::PopoverButton(PopoverButton {
|
||||
header: "Options Bar".into(),
|
||||
header: "Additional Options".into(),
|
||||
text: "Coming soon".into(),
|
||||
..Default::default()
|
||||
})),
|
||||
|
|
@ -261,22 +256,14 @@ pub fn register_artwork_layer_properties(
|
|||
})),
|
||||
},
|
||||
WidgetHolder::unrelated_separator(),
|
||||
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||
value: match &layer.data {
|
||||
LayerDataType::Layer(_) => "Layer".into(),
|
||||
other => LayerDataTypeDiscriminant::from(other).to_string(),
|
||||
},
|
||||
..TextLabel::default()
|
||||
})),
|
||||
WidgetHolder::unrelated_separator(),
|
||||
WidgetHolder::new(Widget::TextInput(TextInput {
|
||||
value: layer.name.clone().unwrap_or_else(|| "Untitled".to_string()),
|
||||
value: layer.name.clone().unwrap_or_else(|| "Untitled Layer".to_string()),
|
||||
on_update: WidgetCallback::new(|text_input: &TextInput| PropertiesPanelMessage::ModifyName { name: text_input.value.clone() }.into()),
|
||||
..Default::default()
|
||||
})),
|
||||
WidgetHolder::related_separator(),
|
||||
WidgetHolder::new(Widget::PopoverButton(PopoverButton {
|
||||
header: "Options Bar".into(),
|
||||
header: "Additional Options".into(),
|
||||
text: "Coming soon".into(),
|
||||
..Default::default()
|
||||
})),
|
||||
|
|
@ -326,18 +313,18 @@ pub fn register_artwork_layer_properties(
|
|||
});
|
||||
}
|
||||
|
||||
pub fn register_document_graph_properties(mut context: NodePropertiesContext, node_graph_message_handler: &NodeGraphMessageHandler) {
|
||||
pub fn register_document_graph_properties(mut context: NodePropertiesContext, node_graph_message_handler: &NodeGraphMessageHandler, document_name: &str) {
|
||||
let mut properties_sections = Vec::new();
|
||||
node_graph_message_handler.collate_properties(&mut context, &mut properties_sections);
|
||||
let options_bar = vec![LayoutGroup::Row {
|
||||
widgets: vec![
|
||||
IconLabel::new("File").widget_holder(),
|
||||
IconLabel::new("File").tooltip("Document").widget_holder(),
|
||||
WidgetHolder::unrelated_separator(),
|
||||
TextLabel::new("Document graph").widget_holder(),
|
||||
WidgetHolder::unrelated_separator(),
|
||||
TextInput::new("No layer selected").disabled(true).widget_holder(),
|
||||
TextInput::new(document_name)
|
||||
.on_update(|text_input| DocumentMessage::RenameDocument { new_name: text_input.value.clone() }.into())
|
||||
.widget_holder(),
|
||||
WidgetHolder::related_separator(),
|
||||
PopoverButton::new("Options Bar", "Coming soon").widget_holder(),
|
||||
PopoverButton::new("Additional Options", "Coming soon").widget_holder(),
|
||||
],
|
||||
}];
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ use serde::{Deserialize, Serialize};
|
|||
use crate::{messages::prelude::NodeGraphMessageHandler, node_graph_executor::NodeGraphExecutor};
|
||||
|
||||
pub struct PropertiesPanelMessageHandlerData<'a> {
|
||||
pub document_name: &'a str,
|
||||
pub artwork_document: &'a DocumentLegacy,
|
||||
pub artboard_document: &'a DocumentLegacy,
|
||||
pub selected_layers: &'a mut dyn Iterator<Item = &'a [LayerId]>,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use super::utility_types::PersistentData;
|
||||
use crate::application::generate_uuid;
|
||||
use crate::consts::{DEFAULT_DOCUMENT_NAME, GRAPHITE_DOCUMENT_VERSION};
|
||||
|
|
@ -19,6 +17,9 @@ use graph_craft::document::value::TaggedValue;
|
|||
use graph_craft::document::{NodeId, NodeInput};
|
||||
use graphene_core::text::Font;
|
||||
|
||||
use glam::DAffine2;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct PortfolioMessageHandler {
|
||||
menu_bar_message_handler: MenuBarMessageHandler,
|
||||
|
|
@ -114,12 +115,13 @@ impl MessageHandler<PortfolioMessage, (&InputPreprocessorMessageHandler, &Prefer
|
|||
|
||||
// Actually delete the document (delay to delete document is required to let the document and properties panel messages above get processed)
|
||||
responses.add(PortfolioMessage::DeleteDocument { document_id });
|
||||
responses.add(FrontendMessage::TriggerIndexedDbRemoveDocument { document_id });
|
||||
|
||||
// Send the new list of document tab names
|
||||
responses.add(PortfolioMessage::UpdateOpenDocumentsList);
|
||||
responses.add(FrontendMessage::TriggerIndexedDbRemoveDocument { document_id });
|
||||
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() });
|
||||
|
|
@ -246,7 +248,7 @@ impl MessageHandler<PortfolioMessage, (&InputPreprocessorMessageHandler, &Prefer
|
|||
}
|
||||
PortfolioMessage::ImaginatePreferences => self.executor.update_imaginate_preferences(preferences.get_imaginate_preferences()),
|
||||
PortfolioMessage::ImaginateServerHostname => {
|
||||
info!("setting imaginate persistent data");
|
||||
debug!("setting imaginate persistent data");
|
||||
self.persistent_data.imaginate.set_host_name(&preferences.imaginate_server_hostname);
|
||||
}
|
||||
PortfolioMessage::Import => {
|
||||
|
|
@ -466,6 +468,7 @@ impl MessageHandler<PortfolioMessage, (&InputPreprocessorMessageHandler, &Prefer
|
|||
responses.add(BroadcastEvent::DocumentIsDirty);
|
||||
responses.add(PortfolioMessage::UpdateDocumentWidgets);
|
||||
responses.add(NavigationMessage::TranslateCanvas { delta: (0., 0.).into() });
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
}
|
||||
PortfolioMessage::SetActiveDocument { document_id } => {
|
||||
self.active_document_id = Some(document_id);
|
||||
|
|
@ -664,7 +667,8 @@ impl PortfolioMessageHandler {
|
|||
}
|
||||
|
||||
pub fn poll_node_graph_evaluation(&mut self, responses: &mut VecDeque<Message>) {
|
||||
self.executor.poll_node_graph_evaluation(responses).unwrap_or_else(|e| {
|
||||
let transform = self.active_document().map(|document| document.document_legacy.root.transform).unwrap_or(DAffine2::IDENTITY);
|
||||
self.executor.poll_node_graph_evaluation(transform, responses).unwrap_or_else(|e| {
|
||||
log::error!("Error while evaluating node graph: {}", e);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
|
|||
use document_legacy::intersection::Quad;
|
||||
use document_legacy::LayerId;
|
||||
|
||||
use glam::{DVec2, Vec2Swizzles};
|
||||
use glam::{DVec2, IVec2, Vec2Swizzles};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Default)]
|
||||
|
|
@ -133,10 +133,6 @@ impl Fsm for ArtboardToolFsmState {
|
|||
tool_data.bounding_box_overlays = Some(bounding_box_overlays);
|
||||
|
||||
responses.add(OverlaysMessage::Rerender);
|
||||
responses.add(PropertiesPanelMessage::SetActiveLayers {
|
||||
paths: vec![vec![tool_data.selected_artboard.unwrap()]],
|
||||
document: TargetDocument::Artboard,
|
||||
});
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
|
|
@ -196,11 +192,6 @@ impl Fsm for ArtboardToolFsmState {
|
|||
.start_snap(document, input, document.bounding_boxes(None, Some(intersection[0]), render_data), true, true);
|
||||
tool_data.snap_manager.add_all_document_handles(document, input, &[], &[], &[]);
|
||||
|
||||
responses.add(PropertiesPanelMessage::SetActiveLayers {
|
||||
paths: vec![intersection.clone()],
|
||||
document: TargetDocument::Artboard,
|
||||
});
|
||||
|
||||
ArtboardToolFsmState::Dragging
|
||||
} else {
|
||||
tool_data.selected_artboard = None;
|
||||
|
|
@ -226,6 +217,11 @@ impl Fsm for ArtboardToolFsmState {
|
|||
position: position.round().into(),
|
||||
size: size.round().into(),
|
||||
});
|
||||
responses.add(GraphOperationMessage::ResizeArtboard {
|
||||
id: tool_data.selected_artboard.unwrap(),
|
||||
location: position.round().as_ivec2(),
|
||||
dimensions: size.round().as_ivec2(),
|
||||
});
|
||||
|
||||
responses.add(BroadcastEvent::DocumentIsDirty);
|
||||
}
|
||||
|
|
@ -251,6 +247,11 @@ impl Fsm for ArtboardToolFsmState {
|
|||
position: position.round().into(),
|
||||
size: size.round().into(),
|
||||
});
|
||||
responses.add(GraphOperationMessage::ResizeArtboard {
|
||||
id: tool_data.selected_artboard.unwrap(),
|
||||
location: position.round().as_ivec2(),
|
||||
dimensions: size.round().as_ivec2(),
|
||||
});
|
||||
|
||||
responses.add(BroadcastEvent::DocumentIsDirty);
|
||||
|
||||
|
|
@ -285,6 +286,11 @@ impl Fsm for ArtboardToolFsmState {
|
|||
position: start.round().into(),
|
||||
size: size.round().into(),
|
||||
});
|
||||
responses.add(GraphOperationMessage::ResizeArtboard {
|
||||
id: tool_data.selected_artboard.unwrap(),
|
||||
location: start.round().as_ivec2(),
|
||||
dimensions: size.round().as_ivec2(),
|
||||
});
|
||||
} else {
|
||||
let id = generate_uuid();
|
||||
tool_data.selected_artboard = Some(id);
|
||||
|
|
@ -297,14 +303,20 @@ impl Fsm for ArtboardToolFsmState {
|
|||
position: start.round().into(),
|
||||
size: (1., 1.),
|
||||
});
|
||||
responses.add(GraphOperationMessage::NewArtboard {
|
||||
id,
|
||||
artboard: graphene_core::Artboard {
|
||||
graphic_group: graphene_core::GraphicGroup::EMPTY,
|
||||
location: start.round().as_ivec2(),
|
||||
dimensions: IVec2::splat(1),
|
||||
background: graphene_core::Color::WHITE,
|
||||
clip: false,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// Have to put message here instead of when Artboard is created
|
||||
// This might result in a few more calls but it is not reliant on the order of messages
|
||||
responses.add(PropertiesPanelMessage::SetActiveLayers {
|
||||
paths: vec![vec![tool_data.selected_artboard.unwrap()]],
|
||||
document: TargetDocument::Artboard,
|
||||
});
|
||||
|
||||
responses.add(BroadcastEvent::DocumentIsDirty);
|
||||
|
||||
|
|
@ -352,6 +364,8 @@ impl Fsm for ArtboardToolFsmState {
|
|||
(_, ArtboardToolMessage::DeleteSelected) => {
|
||||
if let Some(artboard) = tool_data.selected_artboard.take() {
|
||||
responses.add(ArtboardMessage::DeleteArtboard { artboard });
|
||||
responses.add(GraphOperationMessage::DeleteArtboard { id: artboard });
|
||||
|
||||
responses.add(BroadcastEvent::DocumentIsDirty);
|
||||
}
|
||||
ArtboardToolFsmState::Ready
|
||||
|
|
@ -363,6 +377,11 @@ impl Fsm for ArtboardToolFsmState {
|
|||
position: (bounds.bounds[0].x + delta_x, bounds.bounds[0].y + delta_y),
|
||||
size: (bounds.bounds[1] - bounds.bounds[0]).round().into(),
|
||||
});
|
||||
responses.add(GraphOperationMessage::ResizeArtboard {
|
||||
id: tool_data.selected_artboard.unwrap(),
|
||||
location: DVec2::new(bounds.bounds[0].x + delta_x, bounds.bounds[0].y + delta_y).round().as_ivec2(),
|
||||
dimensions: (bounds.bounds[1] - bounds.bounds[0]).round().as_ivec2(),
|
||||
});
|
||||
}
|
||||
|
||||
ArtboardToolFsmState::Ready
|
||||
|
|
|
|||
|
|
@ -214,7 +214,7 @@ impl NodeRuntime {
|
|||
graphic_group.render_svg(&mut render, &render_params);
|
||||
let [min, max] = bounds.unwrap_or_default();
|
||||
render.format_svg(min, max);
|
||||
info!("SVG {}", render.svg);
|
||||
debug!("SVG {}", render.svg);
|
||||
|
||||
if let (Some(layer_id), Some(node_id)) = (layer_path.last().copied(), node_path.get(node_path.len() - 2).copied()) {
|
||||
let old_thumbnail = self.thumbnails.entry(layer_id).or_default().entry(node_id).or_default();
|
||||
|
|
@ -283,16 +283,16 @@ struct ExecutionContext {
|
|||
|
||||
impl Default for NodeGraphExecutor {
|
||||
fn default() -> Self {
|
||||
let (request_sender, request_reciever) = std::sync::mpsc::channel();
|
||||
let (response_sender, response_reciever) = std::sync::mpsc::channel();
|
||||
let (request_sender, request_receiver) = std::sync::mpsc::channel();
|
||||
let (response_sender, response_receiver) = std::sync::mpsc::channel();
|
||||
NODE_RUNTIME.with(|runtime| {
|
||||
runtime.borrow_mut().replace(NodeRuntime::new(request_reciever, response_sender));
|
||||
runtime.borrow_mut().replace(NodeRuntime::new(request_receiver, response_sender));
|
||||
});
|
||||
|
||||
Self {
|
||||
futures: Default::default(),
|
||||
sender: request_sender,
|
||||
receiver: response_reciever,
|
||||
receiver: response_receiver,
|
||||
last_output_type: Default::default(),
|
||||
thumbnails: Default::default(),
|
||||
}
|
||||
|
|
@ -423,7 +423,7 @@ impl NodeGraphExecutor {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn poll_node_graph_evaluation(&mut self, responses: &mut VecDeque<Message>) -> Result<(), String> {
|
||||
pub fn poll_node_graph_evaluation(&mut self, transform: DAffine2, responses: &mut VecDeque<Message>) -> Result<(), String> {
|
||||
let results = self.receiver.try_iter().collect::<Vec<_>>();
|
||||
for response in results {
|
||||
match response {
|
||||
|
|
@ -437,7 +437,7 @@ impl NodeGraphExecutor {
|
|||
let node_graph_output = result.map_err(|e| format!("Node graph evaluation failed: {:?}", e))?;
|
||||
let execution_context = self.futures.remove(&generation_id).ok_or_else(|| "Invalid generation ID".to_string())?;
|
||||
responses.extend(updates);
|
||||
self.process_node_graph_output(node_graph_output, execution_context.layer_path.clone(), responses, execution_context.document_id)?;
|
||||
self.process_node_graph_output(node_graph_output, execution_context.layer_path.clone(), transform, responses, execution_context.document_id)?;
|
||||
responses.add(DocumentMessage::LayerChanged {
|
||||
affected_layer_path: execution_context.layer_path,
|
||||
});
|
||||
|
|
@ -456,7 +456,7 @@ impl NodeGraphExecutor {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn process_node_graph_output(&mut self, node_graph_output: TaggedValue, layer_path: Vec<LayerId>, responses: &mut VecDeque<Message>, document_id: u64) -> Result<(), String> {
|
||||
fn process_node_graph_output(&mut self, node_graph_output: TaggedValue, layer_path: Vec<LayerId>, transform: DAffine2, responses: &mut VecDeque<Message>, document_id: u64) -> Result<(), String> {
|
||||
self.last_output_type.insert(layer_path.clone(), Some(node_graph_output.ty()));
|
||||
match node_graph_output {
|
||||
TaggedValue::VectorData(vector_data) => {
|
||||
|
|
@ -489,11 +489,30 @@ impl NodeGraphExecutor {
|
|||
}
|
||||
}
|
||||
TaggedValue::Artboard(artboard) => {
|
||||
info!("{artboard:#?}");
|
||||
debug!("{artboard:#?}");
|
||||
return Err("Artboard (see console)".to_string());
|
||||
}
|
||||
TaggedValue::GraphicGroup(graphic_group) => {
|
||||
info!("{graphic_group:#?}");
|
||||
debug!("{graphic_group:#?}");
|
||||
use graphene_core::renderer::{format_transform_matrix, GraphicElementRendered, RenderParams, SvgRender};
|
||||
|
||||
// Setup rendering
|
||||
let mut render = SvgRender::new();
|
||||
let render_params = RenderParams::new(ViewMode::Normal, None, false);
|
||||
|
||||
// Render svg
|
||||
graphic_group.render_svg(&mut render, &render_params);
|
||||
|
||||
// Conctenate the defs and the svg into one string
|
||||
let mut svg = "<defs>".to_string();
|
||||
svg.push_str(&render.svg_defs);
|
||||
svg.push_str("</defs>");
|
||||
use std::fmt::Write;
|
||||
write!(svg, "{}", render.svg).unwrap();
|
||||
|
||||
// Send to frontend
|
||||
responses.add(FrontendMessage::UpdateDocumentNodeRender { svg });
|
||||
|
||||
return Err("Graphic group (see console)".to_string());
|
||||
}
|
||||
_ => {
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@
|
|||
UpdateDocumentScrollbars,
|
||||
UpdateEyedropperSamplingState,
|
||||
UpdateMouseCursor,
|
||||
UpdateDocumentNodeRender,
|
||||
UpdateDocumentTransform,
|
||||
} from "@graphite/wasm-communication/messages";
|
||||
|
||||
import EyedropperPreview, { ZOOM_WINDOW_DIMENSIONS } from "@graphite/components/floating-menus/EyedropperPreview.svelte";
|
||||
|
|
@ -58,8 +60,10 @@
|
|||
|
||||
// Rendered SVG viewport data
|
||||
let artworkSvg = "";
|
||||
let nodeRenderSvg = "";
|
||||
let artboardSvg = "";
|
||||
let overlaysSvg = "";
|
||||
let artworkTransform = "";
|
||||
|
||||
// Rasterized SVG viewport data, or none if it's not up-to-date
|
||||
let rasterizedCanvas: HTMLCanvasElement | undefined = undefined;
|
||||
|
|
@ -140,12 +144,21 @@
|
|||
export function updateDocumentOverlays(svg: string) {
|
||||
overlaysSvg = svg;
|
||||
}
|
||||
|
||||
|
||||
export function updateDocumentArtboards(svg: string) {
|
||||
artboardSvg = svg;
|
||||
rasterizedCanvas = undefined;
|
||||
}
|
||||
|
||||
export function updateDocumentNodeRender(svg: string) {
|
||||
nodeRenderSvg = svg;
|
||||
rasterizedCanvas = undefined;
|
||||
}
|
||||
|
||||
export function updateDocumentTransform(transform: string) {
|
||||
artworkTransform = transform;
|
||||
}
|
||||
|
||||
export async function updateEyedropperSamplingState(mousePosition: XY | undefined, colorPrimary: string, colorSecondary: string): Promise<[number, number, number] | undefined> {
|
||||
if (mousePosition === undefined) {
|
||||
cursorEyedropper = false;
|
||||
|
|
@ -335,6 +348,16 @@
|
|||
|
||||
updateDocumentArtboards(data.svg);
|
||||
});
|
||||
editor.subscriptions.subscribeJsMessage(UpdateDocumentNodeRender, async (data) => {
|
||||
await tick();
|
||||
|
||||
updateDocumentNodeRender(data.svg);
|
||||
});
|
||||
editor.subscriptions.subscribeJsMessage(UpdateDocumentTransform, async (data) => {
|
||||
await tick();
|
||||
|
||||
updateDocumentTransform(data.transform);
|
||||
});
|
||||
editor.subscriptions.subscribeJsMessage(UpdateEyedropperSamplingState, async (data) => {
|
||||
await tick();
|
||||
|
||||
|
|
@ -445,6 +468,11 @@
|
|||
<svg class="artboards" style:width={canvasWidthCSS} style:height={canvasHeightCSS}>
|
||||
{@html artboardSvg}
|
||||
</svg>
|
||||
<svg class="artboards" style:width={canvasWidthCSS} style:height={canvasHeightCSS}>
|
||||
<g id="transform-group" transform={artworkTransform}>
|
||||
{@html nodeRenderSvg}
|
||||
</g>
|
||||
</svg>
|
||||
<svg class="artwork" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style:width={canvasWidthCSS} style:height={canvasHeightCSS}>
|
||||
{@html artworkSvg}
|
||||
</svg>
|
||||
|
|
|
|||
|
|
@ -445,6 +445,14 @@ export class UpdateDocumentArtboards extends JsMessage {
|
|||
readonly svg!: string;
|
||||
}
|
||||
|
||||
export class UpdateDocumentNodeRender extends JsMessage {
|
||||
readonly svg!: string;
|
||||
}
|
||||
|
||||
export class UpdateDocumentTransform extends JsMessage {
|
||||
readonly transform!: string;
|
||||
}
|
||||
|
||||
export class UpdateDocumentScrollbars extends JsMessage {
|
||||
@TupleToVec2
|
||||
readonly position!: XY;
|
||||
|
|
@ -1351,6 +1359,7 @@ export const messageMakers: Record<string, MessageMaker> = {
|
|||
UpdateActiveDocument,
|
||||
UpdateDialogDetails,
|
||||
UpdateDocumentArtboards,
|
||||
UpdateDocumentNodeRender,
|
||||
UpdateDocumentArtwork,
|
||||
UpdateDocumentBarLayout,
|
||||
UpdateDocumentLayerDetails,
|
||||
|
|
@ -1359,6 +1368,7 @@ export const messageMakers: Record<string, MessageMaker> = {
|
|||
UpdateDocumentOverlays,
|
||||
UpdateDocumentRulers,
|
||||
UpdateDocumentScrollbars,
|
||||
UpdateDocumentTransform,
|
||||
UpdateEyedropperSamplingState,
|
||||
UpdateImageData,
|
||||
UpdateInputHints,
|
||||
|
|
|
|||
|
|
@ -49,6 +49,19 @@ pub struct Artboard {
|
|||
pub location: IVec2,
|
||||
pub dimensions: IVec2,
|
||||
pub background: Color,
|
||||
pub clip: bool,
|
||||
}
|
||||
|
||||
impl Artboard {
|
||||
pub fn new(location: IVec2, dimensions: IVec2) -> Self {
|
||||
Self {
|
||||
graphic_group: GraphicGroup::EMPTY,
|
||||
location: location.min(location + dimensions),
|
||||
dimensions: dimensions.abs(),
|
||||
background: Color::WHITE,
|
||||
clip: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ConstructLayerNode<Name, BlendMode, Opacity, Visible, Locked, Collapsed, Stack> {
|
||||
|
|
@ -84,19 +97,21 @@ fn construct_layer<Data: Into<GraphicElementData>>(
|
|||
stack
|
||||
}
|
||||
|
||||
pub struct ConstructArtboardNode<Location, Dimensions, Background> {
|
||||
pub struct ConstructArtboardNode<Location, Dimensions, Background, Clip> {
|
||||
location: Location,
|
||||
dimensions: Dimensions,
|
||||
background: Background,
|
||||
clip: Clip,
|
||||
}
|
||||
|
||||
#[node_fn(ConstructArtboardNode)]
|
||||
fn construct_artboard(graphic_group: GraphicGroup, location: IVec2, dimensions: IVec2, background: Color) -> Artboard {
|
||||
fn construct_artboard(graphic_group: GraphicGroup, location: IVec2, dimensions: IVec2, background: Color, clip: bool) -> Artboard {
|
||||
Artboard {
|
||||
graphic_group,
|
||||
location,
|
||||
dimensions,
|
||||
location: location.min(location + dimensions),
|
||||
dimensions: dimensions.abs(),
|
||||
background,
|
||||
clip,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ pub struct SvgRender {
|
|||
pub svg_defs: String,
|
||||
pub transform: DAffine2,
|
||||
pub image_data: Vec<(u64, Image<Color>)>,
|
||||
indent: usize,
|
||||
}
|
||||
|
||||
impl SvgRender {
|
||||
|
|
@ -21,9 +22,15 @@ impl SvgRender {
|
|||
svg_defs: String::new(),
|
||||
transform: DAffine2::IDENTITY,
|
||||
image_data: Vec::new(),
|
||||
indent: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn indent(&mut self) {
|
||||
self.svg.push("\n");
|
||||
self.svg.push("\t".repeat(self.indent));
|
||||
}
|
||||
|
||||
/// Add an outer `<svg />` tag with a `viewBox` and the `<defs />`
|
||||
pub fn format_svg(&mut self, bounds_min: DVec2, bounds_max: DVec2) {
|
||||
let (x, y) = bounds_min.into();
|
||||
|
|
@ -31,7 +38,38 @@ impl SvgRender {
|
|||
let defs = &self.svg_defs;
|
||||
let svg_header = format!(r#"<svg xmlns="http://www.w3.org/2000/svg" viewBox="{x} {y} {size_x} {size_y}"><defs>{defs}</defs>"#,);
|
||||
self.svg.insert(0, svg_header.into());
|
||||
self.svg.push("</svg>".into());
|
||||
self.svg.push("</svg>");
|
||||
}
|
||||
|
||||
pub fn leaf_tag(&mut self, name: impl Into<SvgSegment>, attributes: impl FnOnce(&mut SvgRenderAttrs)) {
|
||||
self.indent();
|
||||
self.svg.push("<");
|
||||
self.svg.push(name);
|
||||
attributes(&mut SvgRenderAttrs(self));
|
||||
|
||||
self.svg.push("/>");
|
||||
}
|
||||
|
||||
pub fn parent_tag(&mut self, name: impl Into<SvgSegment>, attributes: impl FnOnce(&mut SvgRenderAttrs), inner: impl FnOnce(&mut Self)) {
|
||||
let name = name.into();
|
||||
self.indent();
|
||||
self.svg.push("<");
|
||||
self.svg.push(name.clone());
|
||||
attributes(&mut SvgRenderAttrs(self));
|
||||
self.svg.push(">");
|
||||
let length = self.svg.len();
|
||||
self.indent += 1;
|
||||
inner(self);
|
||||
self.indent -= 1;
|
||||
if self.svg.len() != length {
|
||||
self.indent();
|
||||
self.svg.push("</");
|
||||
self.svg.push(name);
|
||||
self.svg.push(">");
|
||||
} else {
|
||||
self.svg.pop();
|
||||
self.svg.push("/>");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -54,8 +92,18 @@ impl RenderParams {
|
|||
}
|
||||
}
|
||||
|
||||
fn format_transform_matrix(transform: DAffine2) -> String {
|
||||
transform.to_cols_array().iter().map(ToString::to_string).collect::<Vec<_>>().join(",")
|
||||
pub fn format_transform_matrix(transform: DAffine2) -> String {
|
||||
use std::fmt::Write;
|
||||
let mut result = "matrix(".to_string();
|
||||
let cols = transform.to_cols_array();
|
||||
for (index, item) in cols.iter().enumerate() {
|
||||
write!(result, "{}", item).unwrap();
|
||||
if index != cols.len() - 1 {
|
||||
result.push_str(", ");
|
||||
}
|
||||
}
|
||||
result.push(')');
|
||||
result
|
||||
}
|
||||
|
||||
pub trait GraphicElementRendered {
|
||||
|
|
@ -77,17 +125,17 @@ impl GraphicElementRendered for VectorData {
|
|||
let layer_bounds = self.bounding_box().unwrap_or_default();
|
||||
let transformed_bounds = self.bounding_box_with_transform(render.transform).unwrap_or_default();
|
||||
|
||||
render.svg.push("<path d=\"".into());
|
||||
let mut path = String::new();
|
||||
for subpath in &self.subpaths {
|
||||
let _ = subpath.subpath_to_svg(&mut path, self.transform * render.transform);
|
||||
}
|
||||
render.svg.push(path.into());
|
||||
render.svg.push("\"".into());
|
||||
|
||||
let style = self.style.render(render_params.view_mode, &mut render.svg_defs, render.transform, layer_bounds, transformed_bounds);
|
||||
render.svg.push(style.into());
|
||||
render.svg.push("/>".into());
|
||||
render.leaf_tag("path", |attributes| {
|
||||
attributes.push("class", "vector-data");
|
||||
attributes.push("d", path);
|
||||
let render = &mut attributes.0;
|
||||
let style = self.style.render(render_params.view_mode, &mut render.svg_defs, render.transform, layer_bounds, transformed_bounds);
|
||||
attributes.push_val(style);
|
||||
});
|
||||
}
|
||||
fn bounding_box(&self, transform: DAffine2) -> Option<[DVec2; 2]> {
|
||||
self.bounding_box_with_transform(self.transform * transform)
|
||||
|
|
@ -96,7 +144,54 @@ impl GraphicElementRendered for VectorData {
|
|||
|
||||
impl GraphicElementRendered for Artboard {
|
||||
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
|
||||
self.graphic_group.render_svg(render, render_params)
|
||||
// Background
|
||||
render.leaf_tag("rect", |attributes| {
|
||||
attributes.push("class", "artboard-bg");
|
||||
attributes.push("fill", format!("#{}", self.background.rgba_hex()));
|
||||
attributes.push("x", self.location.x.min(self.location.x + self.dimensions.x).to_string());
|
||||
attributes.push("y", self.location.y.min(self.location.y + self.dimensions.y).to_string());
|
||||
attributes.push("width", self.dimensions.x.abs().to_string());
|
||||
attributes.push("height", self.dimensions.y.abs().to_string());
|
||||
});
|
||||
|
||||
// Label
|
||||
render.parent_tag(
|
||||
"text",
|
||||
|attributes| {
|
||||
attributes.push("class", "artboard-label");
|
||||
attributes.push("fill", "white");
|
||||
attributes.push("x", (self.location.x.min(self.location.x + self.dimensions.x)).to_string());
|
||||
attributes.push("y", (self.location.y.min(self.location.y + self.dimensions.y) - 4).to_string());
|
||||
attributes.push("font-size", "14px");
|
||||
},
|
||||
|render| {
|
||||
render.svg.push("Artboard");
|
||||
},
|
||||
);
|
||||
|
||||
// Contents group
|
||||
render.parent_tag(
|
||||
"g",
|
||||
|attributes| {
|
||||
attributes.push("class", "artboard");
|
||||
if self.clip {
|
||||
let id = format!("artboard-{}", generate_uuid());
|
||||
let selector = format!("url(#{id})");
|
||||
use std::fmt::Write;
|
||||
write!(
|
||||
&mut attributes.0.svg_defs,
|
||||
r##"<clipPath id="{id}"><rect x="{}" y="{}" width="{}" height="{}"/></clipPath>"##,
|
||||
self.location.x, self.location.y, self.dimensions.x, self.dimensions.y
|
||||
)
|
||||
.unwrap();
|
||||
attributes.push("clip-path", selector);
|
||||
}
|
||||
},
|
||||
|render| {
|
||||
// Contents
|
||||
self.graphic_group.render_svg(render, render_params);
|
||||
},
|
||||
);
|
||||
}
|
||||
fn bounding_box(&self, transform: DAffine2) -> Option<[DVec2; 2]> {
|
||||
let artboard_bounds = (transform * Quad::from_box([self.location.as_dvec2(), self.location.as_dvec2() + self.dimensions.as_dvec2()])).bounding_box();
|
||||
|
|
@ -107,12 +202,14 @@ impl GraphicElementRendered for Artboard {
|
|||
impl GraphicElementRendered for ImageFrame<Color> {
|
||||
fn render_svg(&self, render: &mut SvgRender, _render_params: &RenderParams) {
|
||||
let transform: String = format_transform_matrix(self.transform * render.transform);
|
||||
render
|
||||
.svg
|
||||
.push(format!(r#"<image width="1" height="1" preserveAspectRatio="none" transform="matrix({transform})" href=""#).into());
|
||||
let uuid = generate_uuid();
|
||||
render.svg.push(SvgSegment::BlobUrl(uuid));
|
||||
render.svg.push("\" />".into());
|
||||
render.leaf_tag("image", |attributes| {
|
||||
attributes.push("width", 1.to_string());
|
||||
attributes.push("height", 1.to_string());
|
||||
attributes.push("preserveAspectRatio", "none");
|
||||
attributes.push("transform", transform);
|
||||
attributes.push("href", SvgSegment::BlobUrl(uuid))
|
||||
});
|
||||
render.image_data.push((uuid, self.image.clone()))
|
||||
}
|
||||
fn bounding_box(&self, transform: DAffine2) -> Option<[DVec2; 2]> {
|
||||
|
|
@ -193,3 +290,27 @@ impl core::fmt::Display for SvgSegmentList {
|
|||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SvgRenderAttrs<'a>(&'a mut SvgRender);
|
||||
|
||||
impl<'a> SvgRenderAttrs<'a> {
|
||||
pub fn push_complex(&mut self, name: impl Into<SvgSegment>, value: impl FnOnce(&mut SvgRender)) {
|
||||
self.0.svg.push(" ");
|
||||
self.0.svg.push(name);
|
||||
self.0.svg.push("=\"");
|
||||
value(self.0);
|
||||
self.0.svg.push("\"");
|
||||
}
|
||||
pub fn push(&mut self, name: impl Into<SvgSegment>, value: impl Into<SvgSegment>) {
|
||||
self.push_complex(name, move |renderer| renderer.svg.push(value));
|
||||
}
|
||||
pub fn push_val(&mut self, value: impl Into<SvgSegment>) {
|
||||
self.0.svg.push(value);
|
||||
}
|
||||
}
|
||||
|
||||
impl SvgSegmentList {
|
||||
pub fn push(&mut self, value: impl Into<SvgSegment>) {
|
||||
self.0.push(value.into());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -496,34 +496,19 @@ impl NodeNetwork {
|
|||
///
|
||||
/// Used for the properties panel and tools.
|
||||
pub fn primary_flow(&self) -> impl Iterator<Item = (&DocumentNode, u64)> {
|
||||
struct FlowIter<'a> {
|
||||
stack: Vec<NodeId>,
|
||||
network: &'a NodeNetwork,
|
||||
}
|
||||
impl<'a> Iterator for FlowIter<'a> {
|
||||
type Item = (&'a DocumentNode, NodeId);
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
loop {
|
||||
let node_id = self.stack.pop()?;
|
||||
if let Some(document_node) = self.network.nodes.get(&node_id) {
|
||||
self.stack.extend(
|
||||
document_node
|
||||
.inputs
|
||||
.iter()
|
||||
.take(1) // Only show the primary input
|
||||
.filter_map(|input| if let NodeInput::Node { node_id: ref_id, .. } = input { Some(*ref_id) } else { None }),
|
||||
);
|
||||
return Some((document_node, node_id));
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
FlowIter {
|
||||
stack: self.outputs.iter().map(|output| output.node_id).collect(),
|
||||
network: self,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn primary_flow_from_opt(&self, id: Option<NodeId>) -> impl Iterator<Item = (&DocumentNode, u64)> {
|
||||
FlowIter {
|
||||
stack: id.map_or_else(|| self.outputs.iter().map(|output| output.node_id).collect(), |id| vec![id]),
|
||||
network: self,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_acyclic(&self) -> bool {
|
||||
let mut dependencies: HashMap<u64, Vec<u64>> = HashMap::new();
|
||||
for (node_id, node) in &self.nodes {
|
||||
|
|
@ -549,6 +534,29 @@ impl NodeNetwork {
|
|||
}
|
||||
}
|
||||
|
||||
struct FlowIter<'a> {
|
||||
stack: Vec<NodeId>,
|
||||
network: &'a NodeNetwork,
|
||||
}
|
||||
impl<'a> Iterator for FlowIter<'a> {
|
||||
type Item = (&'a DocumentNode, NodeId);
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
loop {
|
||||
let node_id = self.stack.pop()?;
|
||||
if let Some(document_node) = self.network.nodes.get(&node_id) {
|
||||
self.stack.extend(
|
||||
document_node
|
||||
.inputs
|
||||
.iter()
|
||||
.take(1) // Only show the primary input
|
||||
.filter_map(|input| if let NodeInput::Node { node_id: ref_id, .. } = input { Some(*ref_id) } else { None }),
|
||||
);
|
||||
return Some((document_node, node_id));
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Functions for compiling the network
|
||||
impl NodeNetwork {
|
||||
pub fn map_ids(&mut self, f: impl Fn(NodeId) -> NodeId + Copy) {
|
||||
|
|
@ -835,7 +843,7 @@ impl NodeNetwork {
|
|||
self.nodes.retain(|_, node| !matches!(node.implementation, DocumentNodeImplementation::Extract));
|
||||
|
||||
for (_, node) in &mut extraction_nodes {
|
||||
log::info!("extraction network: {:#?}", &self);
|
||||
log::debug!("extraction network: {:#?}", &self);
|
||||
if let DocumentNodeImplementation::Extract = node.implementation {
|
||||
assert_eq!(node.inputs.len(), 1);
|
||||
log::debug!("Resolving extract node {:?}", node);
|
||||
|
|
|
|||
|
|
@ -169,7 +169,7 @@ impl ApplicationIo for WasmApplicationIo {
|
|||
|
||||
fn load_resource(&self, url: impl AsRef<str>) -> Result<ResourceFuture, ApplicationError> {
|
||||
let url = url::Url::parse(url.as_ref()).map_err(|_| ApplicationError::InvalidUrl)?;
|
||||
log::info!("Loading resource: {:?}", url);
|
||||
log::trace!("Loading resource: {:?}", url);
|
||||
match url.scheme() {
|
||||
#[cfg(feature = "tokio")]
|
||||
"file" => {
|
||||
|
|
@ -196,7 +196,7 @@ impl ApplicationIo for WasmApplicationIo {
|
|||
"graphite" => {
|
||||
let path = url.path();
|
||||
let path = path.to_owned();
|
||||
log::info!("Loading resource: {}", path);
|
||||
log::trace!("Loading local resource: {}", path);
|
||||
let data = self.resources.get(&path).ok_or(ApplicationError::NotFound)?.clone();
|
||||
Ok(Box::pin(async move { Ok(data.clone()) }) as Pin<Box<dyn Future<Output = Result<Arc<[u8]>, _>>>>)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -539,7 +539,7 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
|
|||
register_node!(graphene_core::ConstructLayerNode<_, _, _, _, _, _, _>, input: ImageFrame<Color>, params: [String, BlendMode, f32, bool, bool, bool, graphene_core::GraphicGroup]),
|
||||
register_node!(graphene_core::ConstructLayerNode<_, _, _, _, _, _, _>, input: graphene_core::GraphicGroup, params: [String, BlendMode, f32, bool, bool, bool, graphene_core::GraphicGroup]),
|
||||
register_node!(graphene_core::ConstructLayerNode<_, _, _, _, _, _, _>, input: graphene_core::Artboard, params: [String, BlendMode, f32, bool, bool, bool, graphene_core::GraphicGroup]),
|
||||
register_node!(graphene_core::ConstructArtboardNode<_, _, _>, input: graphene_core::GraphicGroup, params: [glam::IVec2, glam::IVec2, Color]),
|
||||
register_node!(graphene_core::ConstructArtboardNode<_, _, _, _>, input: graphene_core::GraphicGroup, params: [glam::IVec2, glam::IVec2, Color, bool]),
|
||||
];
|
||||
let mut map: HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstructor>> = HashMap::new();
|
||||
for (id, c, types) in node_types.into_iter().flatten() {
|
||||
|
|
|
|||
Loading…
Reference in New Issue