mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-17 23:38:06 +08:00
Migrate the Text tool to the document graph (#1435)
* Update text tool to document graph * Fix selection issue * Log graph reruns and text node evals * Hash to set node * Fix let node crash * Fix loading document with fonts * Allow pressing enter to edit * Cleanup * Code review nits --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
@@ -1430,37 +1430,18 @@ impl DocumentMessageHandler {
|
||||
|
||||
/// Loads layer resources such as creating the blob URLs for the images and loading all of the fonts in the document
|
||||
pub fn load_layer_resources(&self, responses: &mut VecDeque<Message>, root: &LayerDataType, mut path: Vec<LayerId>, _document_id: u64) {
|
||||
fn walk_layers(data: &LayerDataType, path: &mut Vec<LayerId>, responses: &mut VecDeque<Message>, fonts: &mut HashSet<Font>) {
|
||||
match data {
|
||||
LayerDataType::Folder(folder) => {
|
||||
for (id, layer) in folder.layer_ids.iter().zip(folder.layers().iter()) {
|
||||
path.push(*id);
|
||||
walk_layers(&layer.data, path, responses, fonts);
|
||||
path.pop();
|
||||
}
|
||||
let mut fonts = HashSet::new();
|
||||
for (_node_id, node) in self.document_legacy.document_network.recursive_nodes() {
|
||||
for input in &node.inputs {
|
||||
if let NodeInput::Value {
|
||||
tagged_value: TaggedValue::Font(font),
|
||||
..
|
||||
} = input
|
||||
{
|
||||
fonts.insert(font.clone());
|
||||
}
|
||||
LayerDataType::Layer(layer) => {
|
||||
if layer.cached_output_data == CachedOutputData::None {
|
||||
responses.add(DocumentMessage::InputFrameRasterizeRegionBelowLayer { layer_path: path.clone() });
|
||||
}
|
||||
for node in layer.network.nodes.values() {
|
||||
for input in &node.inputs {
|
||||
if let NodeInput::Value {
|
||||
tagged_value: TaggedValue::Font(font),
|
||||
..
|
||||
} = input
|
||||
{
|
||||
fonts.insert(font.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
let mut fonts = HashSet::new();
|
||||
walk_layers(root, &mut path, responses, &mut fonts);
|
||||
for font in fonts {
|
||||
responses.add_front(FrontendMessage::TriggerFontLoad { font, is_default: false });
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ use crate::messages::prelude::*;
|
||||
use bezier_rs::Subpath;
|
||||
use graph_craft::document::NodeId;
|
||||
use graphene_core::raster::ImageFrame;
|
||||
use graphene_core::text::Font;
|
||||
use graphene_core::uuid::ManipulatorGroupId;
|
||||
use graphene_core::vector::brush_stroke::BrushStroke;
|
||||
use graphene_core::vector::style::{Fill, Stroke};
|
||||
@@ -68,6 +69,12 @@ pub enum GraphOperationMessage {
|
||||
id: NodeId,
|
||||
subpaths: Vec<Subpath<ManipulatorGroupId>>,
|
||||
},
|
||||
NewTextLayer {
|
||||
id: NodeId,
|
||||
text: String,
|
||||
font: Font,
|
||||
size: f64,
|
||||
},
|
||||
ResizeArtboard {
|
||||
id: NodeId,
|
||||
location: IVec2,
|
||||
|
||||
@@ -8,6 +8,7 @@ use document_legacy::{LayerId, Operation};
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::{generate_uuid, DocumentNode, NodeId, NodeInput, NodeNetwork, NodeOutput};
|
||||
use graphene_core::raster::ImageFrame;
|
||||
use graphene_core::text::Font;
|
||||
use graphene_core::uuid::ManipulatorGroupId;
|
||||
use graphene_core::vector::brush_stroke::BrushStroke;
|
||||
use graphene_core::vector::style::{Fill, FillType, Stroke};
|
||||
@@ -178,6 +179,34 @@ impl<'a> ModifyInputsContext<'a> {
|
||||
self.responses.add(NodeGraphMessage::SendGraph { should_rerender: true });
|
||||
}
|
||||
|
||||
fn insert_text(&mut self, text: String, font: Font, size: f64, layer: NodeId) {
|
||||
let text = resolve_document_node_type("Text").expect("Text node does not exist").to_document_node(
|
||||
[
|
||||
NodeInput::Network(graph_craft::concrete!(graphene_std::wasm_application_io::WasmEditorApi)),
|
||||
NodeInput::value(TaggedValue::String(text), false),
|
||||
NodeInput::value(TaggedValue::Font(font), false),
|
||||
NodeInput::value(TaggedValue::F64(size), false),
|
||||
],
|
||||
Default::default(),
|
||||
);
|
||||
let cull = resolve_document_node_type("Cull").expect("Cull node does not exist").default_document_node();
|
||||
let transform = resolve_document_node_type("Transform").expect("Transform node does not exist").default_document_node();
|
||||
let fill = resolve_document_node_type("Fill").expect("Fill node does not exist").default_document_node();
|
||||
let stroke = resolve_document_node_type("Stroke").expect("Stroke node does not exist").default_document_node();
|
||||
|
||||
let stroke_id = generate_uuid();
|
||||
self.insert_node_before(stroke_id, layer, 0, stroke, IVec2::new(-8, 0));
|
||||
let fill_id = generate_uuid();
|
||||
self.insert_node_before(fill_id, stroke_id, 0, fill, IVec2::new(-8, 0));
|
||||
let transform_id = generate_uuid();
|
||||
self.insert_node_before(transform_id, fill_id, 0, transform, IVec2::new(-8, 0));
|
||||
let cull_id = generate_uuid();
|
||||
self.insert_node_before(cull_id, transform_id, 0, cull, IVec2::new(-8, 0));
|
||||
let text_id = generate_uuid();
|
||||
self.insert_node_before(text_id, cull_id, 0, text, IVec2::new(-8, 0));
|
||||
self.responses.add(NodeGraphMessage::SendGraph { should_rerender: true });
|
||||
}
|
||||
|
||||
fn insert_image_data(&mut self, image_frame: ImageFrame<Color>, layer: NodeId) {
|
||||
let image = {
|
||||
let node_type = resolve_document_node_type("Image").expect("Image node does not exist");
|
||||
@@ -560,6 +589,12 @@ impl MessageHandler<GraphOperationMessage, (&mut Document, &mut NodeGraphMessage
|
||||
modify_inputs.insert_vector_data(subpaths, layer);
|
||||
}
|
||||
}
|
||||
GraphOperationMessage::NewTextLayer { id, text, font, size } => {
|
||||
let mut modify_inputs = ModifyInputsContext::new(document, node_graph, responses);
|
||||
if let Some(layer) = modify_inputs.create_layer(id, modify_inputs.network.outputs[0].node_id, 0) {
|
||||
modify_inputs.insert_text(text, font, size, layer);
|
||||
}
|
||||
}
|
||||
GraphOperationMessage::ResizeArtboard { id, location, dimensions } => {
|
||||
if let Some(mut modify_inputs) = ModifyInputsContext::new_layer(&[id], document, node_graph, responses) {
|
||||
modify_inputs.resize_artboard(location, dimensions);
|
||||
|
||||
@@ -86,14 +86,17 @@ pub enum NodeGraphMessage {
|
||||
input_index: usize,
|
||||
value: TaggedValue,
|
||||
},
|
||||
SetSelectNodes {
|
||||
SetSelectedNodes {
|
||||
nodes: Vec<NodeId>,
|
||||
},
|
||||
ShiftNode {
|
||||
node_id: NodeId,
|
||||
},
|
||||
ToggleHidden,
|
||||
ToggleHiddenImpl,
|
||||
SetHidden {
|
||||
node_id: NodeId,
|
||||
hidden: bool,
|
||||
},
|
||||
TogglePreview {
|
||||
node_id: NodeId,
|
||||
},
|
||||
|
||||
@@ -746,7 +746,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
||||
}
|
||||
|
||||
let nodes = new_ids.values().copied().collect();
|
||||
responses.add(NodeGraphMessage::SetSelectNodes { nodes });
|
||||
responses.add(NodeGraphMessage::SetSelectedNodes { nodes });
|
||||
|
||||
responses.add(NodeGraphMessage::SendGraph { should_rerender: false });
|
||||
}
|
||||
@@ -817,7 +817,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
||||
}
|
||||
}
|
||||
}
|
||||
NodeGraphMessage::SetSelectNodes { nodes } => {
|
||||
NodeGraphMessage::SetSelectedNodes { nodes } => {
|
||||
responses.add(document.metadata.set_selected_nodes(nodes));
|
||||
responses.add(PropertiesPanelMessage::ResendActiveProperties);
|
||||
}
|
||||
@@ -870,31 +870,27 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
||||
responses.add(NodeGraphMessage::SendGraph { should_rerender: false });
|
||||
}
|
||||
NodeGraphMessage::ToggleHidden => {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
responses.add(NodeGraphMessage::ToggleHiddenImpl);
|
||||
if let Some(network) = document.document_network.nested_network(&self.network) {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
|
||||
let new_hidden = !document.metadata.selected_nodes().any(|id| network.disabled.contains(id));
|
||||
for &node_id in document.metadata.selected_nodes() {
|
||||
responses.add(NodeGraphMessage::SetHidden { node_id, hidden: new_hidden });
|
||||
}
|
||||
}
|
||||
}
|
||||
NodeGraphMessage::ToggleHiddenImpl => {
|
||||
NodeGraphMessage::SetHidden { node_id, hidden } => {
|
||||
if let Some(network) = document.document_network.nested_network_mut(&self.network) {
|
||||
// Check if any of the selected nodes are hidden
|
||||
if document.metadata.selected_nodes().any(|id| network.disabled.contains(id)) {
|
||||
// Remove all selected nodes from the disabled list
|
||||
network.disabled.retain(|id| !document.metadata.selected_nodes_ref().contains(id));
|
||||
} else {
|
||||
let original_outputs = network.original_outputs().iter().map(|output| output.node_id).collect::<Vec<_>>();
|
||||
// Add all selected nodes to the disabled list (excluding input or output nodes)
|
||||
network
|
||||
.disabled
|
||||
.extend(document.metadata.selected_nodes().filter(|&id| !network.inputs.contains(id) && !original_outputs.contains(id)));
|
||||
if !hidden {
|
||||
network.disabled.retain(|&id| node_id != id);
|
||||
} else if !network.inputs.contains(&node_id) && !network.original_outputs().iter().any(|output| output.node_id == node_id) {
|
||||
network.disabled.push(node_id);
|
||||
}
|
||||
Self::send_graph(network, &self.layer_path, responses);
|
||||
|
||||
// Only generate node graph if one of the selected nodes is connected to the output
|
||||
if document.metadata.selected_nodes().any(|&node_id| network.connected_to_output(node_id)) {
|
||||
if let Some(layer_path) = self.layer_path.clone() {
|
||||
responses.add(DocumentMessage::InputFrameRasterizeRegionBelowLayer { layer_path });
|
||||
} else {
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
}
|
||||
if network.connected_to_output(node_id) {
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
}
|
||||
}
|
||||
self.update_selection_action_buttons(document, responses);
|
||||
|
||||
@@ -2483,7 +2483,7 @@ impl DocumentNodeType {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn wrap_network_in_scope(mut network: NodeNetwork) -> NodeNetwork {
|
||||
pub fn wrap_network_in_scope(mut network: NodeNetwork, hash: u64) -> NodeNetwork {
|
||||
network.generate_node_paths(&[]);
|
||||
|
||||
let node_ids = network.nodes.keys().copied().collect::<Vec<_>>();
|
||||
@@ -2520,11 +2520,18 @@ pub fn wrap_network_in_scope(mut network: NodeNetwork) -> NodeNetwork {
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let mut begin_scope = resolve_document_node_type("Begin Scope")
|
||||
.expect("Begin Scope node type not found")
|
||||
.to_document_node(vec![input_type.unwrap()], DocumentNodeMetadata::default());
|
||||
if let DocumentNodeImplementation::Network(g) = &mut begin_scope.implementation {
|
||||
if let Some(node) = g.nodes.get_mut(&0) {
|
||||
node.hash = hash;
|
||||
}
|
||||
}
|
||||
|
||||
// wrap the inner network in a scope
|
||||
let nodes = vec![
|
||||
resolve_document_node_type("Begin Scope")
|
||||
.expect("Begin Scope node type not found")
|
||||
.to_document_node(vec![input_type.unwrap()], DocumentNodeMetadata::default()),
|
||||
begin_scope,
|
||||
inner_network,
|
||||
resolve_document_node_type("End Scope")
|
||||
.expect("End Scope node type not found")
|
||||
|
||||
@@ -219,14 +219,13 @@ impl MessageHandler<PortfolioMessage, (&InputPreprocessorMessageHandler, &Prefer
|
||||
} => {
|
||||
let font = Font::new(font_family, font_style);
|
||||
|
||||
if let Some(document) = self.active_document_mut() {
|
||||
Self::uploaded_new_font(document, &font, responses);
|
||||
responses.add(DocumentMessage::RenderDocument);
|
||||
responses.add(BroadcastEvent::DocumentIsDirty);
|
||||
}
|
||||
|
||||
self.persistent_data.font_cache.insert(font, preview_url, data, is_default);
|
||||
self.executor.update_font_cache(self.persistent_data.font_cache.clone());
|
||||
|
||||
if let Some(document) = self.active_document_mut() {
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
responses.add(BroadcastEvent::DocumentIsDirty);
|
||||
}
|
||||
}
|
||||
PortfolioMessage::GraphViewOverlay { open } => {
|
||||
self.graph_view_overlay_open = open;
|
||||
@@ -675,37 +674,6 @@ impl PortfolioMessageHandler {
|
||||
self.document_ids.iter().position(|id| id == &document_id).expect("Active document is missing from document ids")
|
||||
}
|
||||
|
||||
fn uploaded_new_font(document: &mut DocumentMessageHandler, target_font: &Font, responses: &mut VecDeque<Message>) {
|
||||
let mut stack = vec![(&document.document_legacy.root, Vec::new())];
|
||||
|
||||
while let Some((layer, layer_path)) = stack.pop() {
|
||||
match &layer.data {
|
||||
LayerDataType::Folder(folder) => stack.extend(folder.layers.iter().zip(folder.layer_ids.iter().map(|id| {
|
||||
let mut x = layer_path.clone();
|
||||
x.push(*id);
|
||||
x
|
||||
}))),
|
||||
LayerDataType::Layer(layer) => {
|
||||
let input_is_font = |input: &NodeInput| {
|
||||
let NodeInput::Value {
|
||||
tagged_value: TaggedValue::Font(font),
|
||||
..
|
||||
} = input
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
font == target_font
|
||||
};
|
||||
let should_rerender = layer.network.nodes.values().any(|node| node.inputs.iter().any(input_is_font));
|
||||
if should_rerender {
|
||||
responses.add(DocumentMessage::InputFrameRasterizeRegionBelowLayer { layer_path });
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn poll_node_graph_evaluation(&mut self, responses: &mut VecDeque<Message>) {
|
||||
let Some(active_document) = self.active_document_id.and_then(|id| self.documents.get_mut(&id)) else {
|
||||
warn!("Polling node graph with no document");
|
||||
|
||||
@@ -5,6 +5,7 @@ use bezier_rs::{ManipulatorGroup, Subpath};
|
||||
use document_legacy::{document::Document, document_metadata::LayerNodeIdentifier, LayerId, Operation};
|
||||
use graph_craft::document::{value::TaggedValue, DocumentNode, NodeId, NodeInput, NodeNetwork};
|
||||
use graphene_core::raster::ImageFrame;
|
||||
use graphene_core::text::Font;
|
||||
use graphene_core::uuid::ManipulatorGroupId;
|
||||
use graphene_core::vector::style::{FillType, Gradient};
|
||||
use graphene_core::Color;
|
||||
@@ -16,10 +17,10 @@ use std::collections::VecDeque;
|
||||
pub fn new_vector_layer(subpaths: Vec<Subpath<ManipulatorGroupId>>, layer_path: Vec<LayerId>, responses: &mut VecDeque<Message>) {
|
||||
let id = *layer_path.last().unwrap();
|
||||
responses.add(GraphOperationMessage::NewVectorLayer { id, subpaths });
|
||||
responses.add(NodeGraphMessage::SetSelectNodes { nodes: vec![id] })
|
||||
responses.add(NodeGraphMessage::SetSelectedNodes { nodes: vec![id] })
|
||||
}
|
||||
|
||||
/// Creat a new bitmap layer from an [`graphene_core::raster::ImageFrame<Color>`]
|
||||
/// Create a new bitmap layer from an [`graphene_core::raster::ImageFrame<Color>`]
|
||||
pub fn new_image_layer(image_frame: ImageFrame<Color>, layer_path: Vec<LayerId>, responses: &mut VecDeque<Message>) {
|
||||
responses.add(GraphOperationMessage::NewBitmapLayer {
|
||||
id: *layer_path.last().unwrap(),
|
||||
@@ -119,6 +120,52 @@ pub fn get_gradient(layer: LayerNodeIdentifier, document: &Document) -> Option<G
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the current fill of a layer from the closest fill node
|
||||
pub fn get_fill_color(layer: LayerNodeIdentifier, document: &Document) -> Option<Color> {
|
||||
let inputs = NodeGraphLayer::new(layer, document)?.find_node_inputs("Fill")?;
|
||||
let TaggedValue::Color(color) = inputs.get(2)?.as_value()? else {
|
||||
return None;
|
||||
};
|
||||
Some(*color)
|
||||
}
|
||||
|
||||
pub fn get_text_id(layer: LayerNodeIdentifier, document: &Document) -> Option<NodeId> {
|
||||
NodeGraphLayer::new(layer, document)?.node_id("Text")
|
||||
}
|
||||
pub fn get_fill_id(layer: LayerNodeIdentifier, document: &Document) -> Option<NodeId> {
|
||||
NodeGraphLayer::new(layer, document)?.node_id("Fill")
|
||||
}
|
||||
|
||||
/// Gets properties from the text node
|
||||
pub fn get_text(layer: LayerNodeIdentifier, document: &Document) -> Option<(&String, &Font, f64)> {
|
||||
let inputs = NodeGraphLayer::new(layer, document)?.find_node_inputs("Text")?;
|
||||
let NodeInput::Value {
|
||||
tagged_value: TaggedValue::String(text),
|
||||
..
|
||||
} = &inputs[1]
|
||||
else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let NodeInput::Value {
|
||||
tagged_value: TaggedValue::Font(font),
|
||||
..
|
||||
} = &inputs[2]
|
||||
else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let NodeInput::Value {
|
||||
tagged_value: TaggedValue::F64(font_size),
|
||||
..
|
||||
} = inputs[3]
|
||||
else {
|
||||
return None;
|
||||
};
|
||||
|
||||
Some((text, font, font_size))
|
||||
}
|
||||
|
||||
/// Is a specified layer an artboard?
|
||||
pub fn is_artboard(layer: LayerNodeIdentifier, document: &Document) -> bool {
|
||||
NodeGraphLayer::new(layer, document).is_some_and(|layer| layer.uses_node("Artboard"))
|
||||
@@ -193,6 +240,11 @@ impl<'a> NodeGraphLayer<'a> {
|
||||
self.primary_layer_flow().any(|(node, _id)| node.name == node_name)
|
||||
}
|
||||
|
||||
/// Node id of a node if it exists in the layer's primary flow
|
||||
pub fn node_id(&self, node_name: &str) -> Option<NodeId> {
|
||||
self.primary_layer_flow().find(|(node, _id)| node.name == node_name).map(|(_node, id)| id)
|
||||
}
|
||||
|
||||
/// Find all of the inputs of a specific node within the layer's primary flow
|
||||
pub fn find_node_inputs(&self, node_name: &str) -> Option<&'a Vec<NodeInput>> {
|
||||
self.primary_layer_flow().find(|(node, _id)| node.name == node_name).map(|(node, _id)| &node.inputs)
|
||||
|
||||
@@ -246,7 +246,7 @@ impl PathToolData {
|
||||
if shift {
|
||||
responses.add(NodeGraphMessage::AddSelectNodes { nodes: vec![layer.to_node()] });
|
||||
} else {
|
||||
responses.add(NodeGraphMessage::SetSelectNodes { nodes: vec![layer.to_node()] });
|
||||
responses.add(NodeGraphMessage::SetSelectedNodes { nodes: vec![layer.to_node()] });
|
||||
}
|
||||
self.drag_start_pos = input.mouse.position;
|
||||
self.previous_mouse_position = input.mouse.position;
|
||||
@@ -382,7 +382,7 @@ impl Fsm for PathToolFsmState {
|
||||
let shift_pressed = input.keyboard.get(add_to_selection as usize);
|
||||
|
||||
if tool_data.drag_start_pos == tool_data.previous_mouse_position {
|
||||
responses.add(NodeGraphMessage::SetSelectNodes { nodes: vec![] });
|
||||
responses.add(NodeGraphMessage::SetSelectedNodes { nodes: vec![] });
|
||||
} else {
|
||||
shape_editor.select_all_in_quad(&document.document_legacy, [tool_data.drag_start_pos, tool_data.previous_mouse_position], !shift_pressed);
|
||||
tool_data.refresh_overlays(document, shape_editor, shape_overlay, responses);
|
||||
@@ -397,7 +397,7 @@ impl Fsm for PathToolFsmState {
|
||||
let shift_pressed = input.keyboard.get(shift_mirror_distance as usize);
|
||||
|
||||
if tool_data.drag_start_pos == tool_data.previous_mouse_position {
|
||||
responses.add(NodeGraphMessage::SetSelectNodes { nodes: vec![] });
|
||||
responses.add(NodeGraphMessage::SetSelectedNodes { nodes: vec![] });
|
||||
} else {
|
||||
shape_editor.select_all_in_quad(&document.document_legacy, [tool_data.drag_start_pos, tool_data.previous_mouse_position], !shift_pressed);
|
||||
tool_data.refresh_overlays(document, shape_editor, shape_overlay, responses);
|
||||
|
||||
@@ -369,7 +369,7 @@ impl SelectToolData {
|
||||
}
|
||||
|
||||
// Select the originals
|
||||
responses.add(NodeGraphMessage::SetSelectNodes {
|
||||
responses.add(NodeGraphMessage::SetSelectedNodes {
|
||||
nodes: originals.iter().map(|layer| layer.to_node()).collect::<Vec<_>>(),
|
||||
});
|
||||
|
||||
@@ -719,13 +719,13 @@ impl Fsm for SelectToolFsmState {
|
||||
tool_data.layers_dragging.clear();
|
||||
tool_data.layers_dragging.extend(replacement_selected_layers.iter());
|
||||
|
||||
responses.add(NodeGraphMessage::SetSelectNodes {
|
||||
responses.add(NodeGraphMessage::SetSelectedNodes {
|
||||
nodes: replacement_selected_layers.iter().map(|layer| layer.to_node()).collect(),
|
||||
});
|
||||
}
|
||||
} else if let Some(selecting_layer) = tool_data.select_single_layer.take() {
|
||||
if !tool_data.has_dragged {
|
||||
responses.add(NodeGraphMessage::SetSelectNodes {
|
||||
responses.add(NodeGraphMessage::SetSelectedNodes {
|
||||
nodes: vec![selecting_layer.to_node()],
|
||||
});
|
||||
}
|
||||
@@ -787,7 +787,7 @@ impl Fsm for SelectToolFsmState {
|
||||
let quad = tool_data.selection_quad();
|
||||
// For shallow select we don't update dragging layers until inside drag_start_shallowest_manipulation()
|
||||
tool_data.layers_dragging = document.metadata().intersect_quad(quad, &document.document_legacy.document_network).collect();
|
||||
responses.add_front(NodeGraphMessage::SetSelectNodes {
|
||||
responses.add_front(NodeGraphMessage::SetSelectedNodes {
|
||||
nodes: tool_data.layers_dragging.iter().map(|layer| layer.to_node()).collect(),
|
||||
});
|
||||
responses.add_front(DocumentMessage::Overlays(
|
||||
@@ -799,18 +799,14 @@ impl Fsm for SelectToolFsmState {
|
||||
SelectToolFsmState::Ready
|
||||
}
|
||||
(SelectToolFsmState::Ready, SelectToolMessage::Enter) => {
|
||||
let mut selected_layers = document.selected_layers();
|
||||
let mut selected_layers = document.metadata().selected_layers();
|
||||
|
||||
if let Some(layer_path) = selected_layers.next() {
|
||||
if let Some(layer) = selected_layers.next() {
|
||||
// Check that only one layer is selected
|
||||
if selected_layers.next().is_none() {
|
||||
if let Ok(layer) = document.document_legacy.layer(layer_path) {
|
||||
if let Ok(network) = layer.as_layer_network() {
|
||||
if network.nodes.values().any(|node| node.name == "Text") {
|
||||
responses.add_front(ToolMessage::ActivateTool { tool_type: ToolType::Text });
|
||||
responses.add(TextToolMessage::EditSelected);
|
||||
}
|
||||
}
|
||||
if is_text_layer(layer, &document.document_legacy) {
|
||||
responses.add_front(ToolMessage::ActivateTool { tool_type: ToolType::Text });
|
||||
responses.add(TextToolMessage::EditSelected);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -928,7 +924,7 @@ fn drag_shallowest_manipulation(responses: &mut VecDeque<Message>, mut selected:
|
||||
let new_selected = ancestor.unwrap_or_else(|| layer.child_of_root(document.metadata()));
|
||||
|
||||
tool_data.layers_dragging = vec![new_selected];
|
||||
responses.add(NodeGraphMessage::SetSelectNodes {
|
||||
responses.add(NodeGraphMessage::SetSelectedNodes {
|
||||
nodes: tool_data.layers_dragging.iter().map(|layer| layer.to_node()).collect(),
|
||||
});
|
||||
// tool_data
|
||||
@@ -938,7 +934,7 @@ fn drag_shallowest_manipulation(responses: &mut VecDeque<Message>, mut selected:
|
||||
|
||||
fn drag_deepest_manipulation(responses: &mut VecDeque<Message>, mut selected: Vec<LayerNodeIdentifier>, tool_data: &mut SelectToolData) {
|
||||
tool_data.layers_dragging.append(&mut selected);
|
||||
responses.add(NodeGraphMessage::SetSelectNodes {
|
||||
responses.add(NodeGraphMessage::SetSelectedNodes {
|
||||
nodes: tool_data.layers_dragging.iter().map(|layer| layer.to_node()).collect(),
|
||||
});
|
||||
// tool_data
|
||||
@@ -961,7 +957,7 @@ fn edit_layer_shallowest_manipulation(document: &DocumentMessageHandler, layer:
|
||||
return;
|
||||
};
|
||||
|
||||
responses.add(NodeGraphMessage::SetSelectNodes { nodes: vec![new_selected.to_node()] });
|
||||
responses.add(NodeGraphMessage::SetSelectedNodes { nodes: vec![new_selected.to_node()] });
|
||||
}
|
||||
|
||||
fn edit_layer_deepest_manipulation(layer: LayerNodeIdentifier, document: &Document, responses: &mut VecDeque<Message>) {
|
||||
|
||||
@@ -4,7 +4,9 @@ use crate::application::generate_uuid;
|
||||
use crate::consts::{COLOR_ACCENT, SELECTION_TOLERANCE};
|
||||
use crate::messages::portfolio::document::node_graph::new_text_network;
|
||||
use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType};
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils::{self, is_text_layer};
|
||||
|
||||
use document_legacy::document_metadata::LayerNodeIdentifier;
|
||||
use document_legacy::intersection::Quad;
|
||||
use document_legacy::layers::layer_info::Layer;
|
||||
use document_legacy::layers::style::{self, Fill, RenderData, Stroke};
|
||||
@@ -216,7 +218,7 @@ pub struct EditingText {
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
struct TextToolData {
|
||||
layer_path: Vec<LayerId>,
|
||||
layer: LayerNodeIdentifier,
|
||||
overlays: Vec<Vec<LayerId>>,
|
||||
editing_text: Option<EditingText>,
|
||||
new_text: String,
|
||||
@@ -224,9 +226,10 @@ struct TextToolData {
|
||||
|
||||
impl TextToolData {
|
||||
/// Set the editing state of the currently modifying layer
|
||||
fn set_editing(&self, editable: bool, render_data: &RenderData, responses: &mut VecDeque<Message>) {
|
||||
let path = self.layer_path.clone();
|
||||
responses.add(Operation::SetLayerVisibility { path, visible: !editable });
|
||||
fn set_editing(&self, editable: bool, render_data: &RenderData, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
|
||||
if let Some(node_id) = graph_modification_utils::get_fill_id(self.layer, &document.document_legacy) {
|
||||
responses.add(NodeGraphMessage::SetHidden { node_id, hidden: editable });
|
||||
}
|
||||
|
||||
if let Some(editing_text) = self.editing_text.as_ref().filter(|_| editable) {
|
||||
responses.add(FrontendMessage::DisplayEditableTextbox {
|
||||
@@ -243,18 +246,9 @@ impl TextToolData {
|
||||
}
|
||||
|
||||
fn load_layer_text_node(&mut self, document: &DocumentMessageHandler) -> Option<()> {
|
||||
let transform = document.document_legacy.multiply_transforms(&self.layer_path).ok()?;
|
||||
let layer = document.document_legacy.layer(&self.layer_path).ok()?;
|
||||
let color = layer
|
||||
.style()
|
||||
.ok()
|
||||
.map_or(Color::BLACK, |style| if let Fill::Solid(solid_color) = style.fill() { *solid_color } else { Color::BLACK });
|
||||
|
||||
let network = get_network(&self.layer_path, document)?;
|
||||
let node_id = get_text_node_id(network)?;
|
||||
let node = network.nodes.get(&node_id)?;
|
||||
|
||||
let (text, font, font_size) = Self::extract_text_node_inputs(node)?;
|
||||
let transform = document.metadata().transform_to_viewport(self.layer);
|
||||
let color = graph_modification_utils::get_fill_color(self.layer, &document.document_legacy).unwrap_or(Color::BLACK);
|
||||
let (text, font, font_size) = graph_modification_utils::get_text(self.layer, &document.document_legacy)?;
|
||||
self.editing_text = Some(EditingText {
|
||||
text: text.clone(),
|
||||
font: font.clone(),
|
||||
@@ -266,53 +260,24 @@ impl TextToolData {
|
||||
Some(())
|
||||
}
|
||||
|
||||
fn start_editing_layer(&mut self, layer_path: &[LayerId], tool_state: TextToolFsmState, document: &DocumentMessageHandler, render_data: &RenderData, responses: &mut VecDeque<Message>) {
|
||||
fn start_editing_layer(&mut self, layer: LayerNodeIdentifier, tool_state: TextToolFsmState, document: &DocumentMessageHandler, render_data: &RenderData, responses: &mut VecDeque<Message>) {
|
||||
if tool_state == TextToolFsmState::Editing {
|
||||
self.set_editing(false, render_data, responses);
|
||||
self.set_editing(false, render_data, document, responses);
|
||||
}
|
||||
|
||||
self.layer_path = layer_path.into();
|
||||
self.layer = layer;
|
||||
self.load_layer_text_node(document);
|
||||
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
|
||||
self.set_editing(true, render_data, responses);
|
||||
self.set_editing(true, render_data, document, responses);
|
||||
|
||||
let replacement_selected_layers = vec![self.layer_path.clone()];
|
||||
responses.add(DocumentMessage::SetSelectedLayers { replacement_selected_layers });
|
||||
}
|
||||
|
||||
fn extract_text_node_inputs(node: &DocumentNode) -> Option<(&String, &Font, f64)> {
|
||||
let NodeInput::Value {
|
||||
tagged_value: TaggedValue::String(text),
|
||||
..
|
||||
} = &node.inputs[1]
|
||||
else {
|
||||
return None;
|
||||
};
|
||||
let NodeInput::Value {
|
||||
tagged_value: TaggedValue::Font(font),
|
||||
..
|
||||
} = &node.inputs[2]
|
||||
else {
|
||||
return None;
|
||||
};
|
||||
let NodeInput::Value {
|
||||
tagged_value: TaggedValue::F64(font_size),
|
||||
..
|
||||
} = &node.inputs[3]
|
||||
else {
|
||||
return None;
|
||||
};
|
||||
Some((text, font, *font_size))
|
||||
responses.add(NodeGraphMessage::SetSelectedNodes { nodes: vec![self.layer.to_node()] });
|
||||
}
|
||||
|
||||
fn interact(&mut self, state: TextToolFsmState, mouse: DVec2, document: &DocumentMessageHandler, render_data: &RenderData, responses: &mut VecDeque<Message>) -> TextToolFsmState {
|
||||
let tolerance = DVec2::splat(SELECTION_TOLERANCE);
|
||||
let quad = Quad::from_box([mouse - tolerance, mouse + tolerance]);
|
||||
|
||||
// Check if the user has selected an existing text layer
|
||||
if let Some(clicked_text_layer_path) = document.document_legacy.intersects_quad_root(quad, render_data).last().filter(|l| is_text_layer(document, l)) {
|
||||
if let Some(clicked_text_layer_path) = document.metadata().click(mouse, &document.network()).filter(|&layer| is_text_layer(layer, &document.document_legacy)) {
|
||||
self.start_editing_layer(clicked_text_layer_path, state, document, render_data, responses);
|
||||
|
||||
TextToolFsmState::Editing
|
||||
@@ -321,35 +286,33 @@ impl TextToolData {
|
||||
else if let Some(editing_text) = self.editing_text.as_ref().filter(|_| state == TextToolFsmState::Ready) {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
|
||||
let network = new_text_network(String::new(), editing_text.font.clone(), editing_text.font_size);
|
||||
self.layer = LayerNodeIdentifier::new_unchecked(generate_uuid());
|
||||
|
||||
responses.add(Operation::AddFrame {
|
||||
path: self.layer_path.clone(),
|
||||
insert_index: -1,
|
||||
transform: DAffine2::ZERO.to_cols_array(),
|
||||
network,
|
||||
responses.add(GraphOperationMessage::NewTextLayer {
|
||||
id: self.layer.to_node(),
|
||||
text: String::new(),
|
||||
font: editing_text.font.clone(),
|
||||
size: editing_text.font_size,
|
||||
});
|
||||
responses.add(GraphOperationMessage::FillSet {
|
||||
layer: self.layer_path.clone(),
|
||||
layer: self.layer.to_path(),
|
||||
fill: if editing_text.color.is_some() { Fill::Solid(editing_text.color.unwrap()) } else { Fill::None },
|
||||
});
|
||||
responses.add(GraphOperationMessage::TransformSet {
|
||||
layer: self.layer_path.clone(),
|
||||
layer: self.layer.to_path(),
|
||||
transform: editing_text.transform,
|
||||
transform_in: TransformIn::Viewport,
|
||||
skip_rerender: true,
|
||||
});
|
||||
|
||||
self.set_editing(true, render_data, responses);
|
||||
self.set_editing(true, render_data, document, responses);
|
||||
|
||||
let replacement_selected_layers = vec![self.layer_path.clone()];
|
||||
|
||||
responses.add(DocumentMessage::SetSelectedLayers { replacement_selected_layers });
|
||||
responses.add(NodeGraphMessage::SetSelectedNodes { nodes: self.layer.to_path() });
|
||||
|
||||
TextToolFsmState::Editing
|
||||
} else {
|
||||
// Removing old text as editable
|
||||
self.set_editing(false, render_data, responses);
|
||||
self.set_editing(false, render_data, document, responses);
|
||||
|
||||
resize_overlays(&mut self.overlays, responses, 0);
|
||||
|
||||
@@ -365,7 +328,7 @@ impl TextToolData {
|
||||
let far = graphene_core::text::bounding_box(&self.new_text, buzz_face, editing_text.font_size, None);
|
||||
let quad = Quad::from_box([DVec2::ZERO, far]);
|
||||
|
||||
let transformed_quad = document.document_legacy.multiply_transforms(&self.layer_path).ok()? * quad;
|
||||
let transformed_quad = document.metadata().transform_to_viewport(self.layer) * quad;
|
||||
let bounds = transformed_quad.bounding_box();
|
||||
|
||||
let operation = Operation::SetLayerTransformInViewport {
|
||||
@@ -386,7 +349,7 @@ impl TextToolData {
|
||||
}
|
||||
|
||||
fn fix_text_bounds(&self, new_text: &str, _document: &DocumentMessageHandler, render_data: &RenderData, responses: &mut VecDeque<Message>) -> Option<()> {
|
||||
let layer = self.layer_path.clone();
|
||||
let layer = self.layer.to_path();
|
||||
let old_bounds = self.get_bounds(&self.editing_text.as_ref()?.text, render_data)?;
|
||||
let new_bounds = self.get_bounds(new_text, render_data)?;
|
||||
responses.add(GraphOperationMessage::UpdateBounds { layer, old_bounds, new_bounds });
|
||||
@@ -419,21 +382,15 @@ fn resize_overlays(overlays: &mut Vec<Vec<LayerId>>, responses: &mut VecDeque<Me
|
||||
}
|
||||
|
||||
fn update_overlays(document: &DocumentMessageHandler, tool_data: &mut TextToolData, responses: &mut VecDeque<Message>, render_data: &RenderData) {
|
||||
let get_bounds = |layer: &Layer, path: &[LayerId], document: &DocumentMessageHandler, render_data: &RenderData| {
|
||||
let node_graph = layer.as_layer_network().ok()?;
|
||||
let node_id = get_text_node_id(node_graph)?;
|
||||
let document_node = node_graph.nodes.get(&node_id)?;
|
||||
let (text, font, font_size) = TextToolData::extract_text_node_inputs(document_node)?;
|
||||
let get_bounds = |layer: LayerNodeIdentifier, document: &DocumentMessageHandler, render_data: &RenderData| {
|
||||
let (text, font, font_size) = graph_modification_utils::get_text(layer, &document.document_legacy)?;
|
||||
let buzz_face = render_data.font_cache.get(font).map(|data| load_face(data));
|
||||
let far = graphene_core::text::bounding_box(text, buzz_face, font_size, None);
|
||||
let quad = Quad::from_box([DVec2::ZERO, far]);
|
||||
let multiplied = document.document_legacy.multiply_transforms(path).ok()? * quad;
|
||||
let multiplied = document.metadata().transform_to_viewport(layer) * quad;
|
||||
Some(multiplied.bounding_box())
|
||||
};
|
||||
let bounds = document.selected_layers().filter_map(|path| match document.document_legacy.layer(path) {
|
||||
Ok(layer) => get_bounds(layer, path, document, render_data),
|
||||
Err(_) => None,
|
||||
});
|
||||
let bounds = document.metadata().selected_layers().filter_map(|layer| get_bounds(layer, document, render_data));
|
||||
let bounds = bounds.collect::<Vec<_>>();
|
||||
|
||||
let new_len = bounds.len();
|
||||
@@ -448,36 +405,20 @@ fn update_overlays(document: &DocumentMessageHandler, tool_data: &mut TextToolDa
|
||||
resize_overlays(&mut tool_data.overlays, responses, new_len);
|
||||
}
|
||||
|
||||
fn get_network<'a>(layer_path: &[LayerId], document: &'a DocumentMessageHandler) -> Option<&'a NodeNetwork> {
|
||||
let layer = document.document_legacy.layer(layer_path).ok()?;
|
||||
layer.as_layer_network().ok()
|
||||
}
|
||||
fn can_edit_selected(document: &DocumentMessageHandler) -> Option<LayerNodeIdentifier> {
|
||||
let mut selected_layers = document.metadata().selected_layers();
|
||||
|
||||
fn get_text_node_id(network: &NodeNetwork) -> Option<NodeId> {
|
||||
network.nodes.iter().find(|(_, node)| node.name == "Text").map(|(&id, _)| id)
|
||||
}
|
||||
|
||||
fn is_text_layer(document: &DocumentMessageHandler, layer_path: &[LayerId]) -> bool {
|
||||
let Some(network) = get_network(layer_path, document) else {
|
||||
return false;
|
||||
};
|
||||
get_text_node_id(network).is_some()
|
||||
}
|
||||
|
||||
fn can_edit_selected(document: &DocumentMessageHandler) -> Option<Vec<LayerId>> {
|
||||
let mut selected_layers = document.selected_layers();
|
||||
|
||||
let layer_path = selected_layers.next()?.to_vec();
|
||||
let layer = selected_layers.next()?;
|
||||
// Check that only one layer is selected
|
||||
if selected_layers.next().is_some() {
|
||||
return None;
|
||||
}
|
||||
|
||||
if !is_text_layer(document, &layer_path) {
|
||||
if !is_text_layer(layer, &document.document_legacy) {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(layer_path)
|
||||
Some(layer)
|
||||
}
|
||||
|
||||
impl Fsm for TextToolFsmState {
|
||||
@@ -498,7 +439,7 @@ impl Fsm for TextToolFsmState {
|
||||
match (self, event) {
|
||||
(TextToolFsmState::Editing, TextToolMessage::DocumentIsDirty) => {
|
||||
responses.add(FrontendMessage::DisplayEditableTextboxTransform {
|
||||
transform: document.document_legacy.multiply_transforms(&tool_data.layer_path).ok().unwrap_or_default().to_cols_array(),
|
||||
transform: document.metadata().transform_to_viewport(tool_data.layer).to_cols_array(),
|
||||
});
|
||||
tool_data.update_bounds_overlay(document, render_data, responses);
|
||||
TextToolFsmState::Editing
|
||||
@@ -517,13 +458,12 @@ impl Fsm for TextToolFsmState {
|
||||
color: tool_options.fill.active_color(),
|
||||
});
|
||||
tool_data.new_text = String::new();
|
||||
tool_data.layer_path = document.get_path_for_new_layer();
|
||||
|
||||
tool_data.interact(state, input.mouse.position, document, render_data, responses)
|
||||
}
|
||||
(state, TextToolMessage::EditSelected) => {
|
||||
if let Some(layer_path) = can_edit_selected(document) {
|
||||
tool_data.start_editing_layer(&layer_path, state, document, render_data, responses);
|
||||
if let Some(layer) = can_edit_selected(document) {
|
||||
tool_data.start_editing_layer(layer, state, document, render_data, responses);
|
||||
return TextToolFsmState::Editing;
|
||||
}
|
||||
|
||||
@@ -531,7 +471,7 @@ impl Fsm for TextToolFsmState {
|
||||
}
|
||||
(state, TextToolMessage::Abort) => {
|
||||
if state == TextToolFsmState::Editing {
|
||||
tool_data.set_editing(false, render_data, responses);
|
||||
tool_data.set_editing(false, render_data, document, responses);
|
||||
}
|
||||
|
||||
resize_overlays(&mut tool_data.overlays, responses, 0);
|
||||
@@ -544,17 +484,15 @@ impl Fsm for TextToolFsmState {
|
||||
TextToolFsmState::Editing
|
||||
}
|
||||
(TextToolFsmState::Editing, TextToolMessage::TextChange { new_text }) => {
|
||||
let layer_path = tool_data.layer_path.clone();
|
||||
let network = get_network(&layer_path, document).unwrap();
|
||||
tool_data.fix_text_bounds(&new_text, document, render_data, responses);
|
||||
responses.add(NodeGraphMessage::SetQualifiedInputValue {
|
||||
layer_path,
|
||||
node_path: vec![get_text_node_id(network).unwrap()],
|
||||
layer_path: Vec::new(),
|
||||
node_path: vec![graph_modification_utils::get_text_id(tool_data.layer, &document.document_legacy).unwrap()],
|
||||
input_index: 1,
|
||||
value: TaggedValue::String(new_text),
|
||||
});
|
||||
|
||||
tool_data.set_editing(false, render_data, responses);
|
||||
tool_data.set_editing(false, render_data, document, responses);
|
||||
|
||||
resize_overlays(&mut tool_data.overlays, responses, 0);
|
||||
|
||||
|
||||
@@ -107,6 +107,8 @@ thread_local! {
|
||||
pub(crate) static NODE_RUNTIME: Rc<RefCell<Option<NodeRuntime>>> = Rc::new(RefCell::new(None));
|
||||
}
|
||||
|
||||
type MonitorNodes = Vec<Vec<NodeId>>;
|
||||
|
||||
impl NodeRuntime {
|
||||
fn new(receiver: Receiver<NodeRuntimeMessage>, sender: Sender<NodeGraphUpdate>) -> Self {
|
||||
let executor = DynamicExecutor::default();
|
||||
@@ -146,15 +148,7 @@ impl NodeRuntime {
|
||||
viewport_resolution,
|
||||
..
|
||||
}) => {
|
||||
let network = wrap_network_in_scope(graph);
|
||||
|
||||
let monitor_nodes = network
|
||||
.recursive_nodes()
|
||||
.filter(|(_, node)| node.implementation == DocumentNodeImplementation::proto("graphene_core::memo::MonitorNode<_>"))
|
||||
.map(|(_, node)| node.path.clone().unwrap_or_default())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let result = self.execute_network(&path, network, transform, viewport_resolution).await;
|
||||
let (result, monitor_nodes) = self.execute_network(&path, graph, transform, viewport_resolution).await;
|
||||
let mut responses = VecDeque::new();
|
||||
self.update_thumbnails(&path, &monitor_nodes, &mut responses);
|
||||
self.update_upstream_transforms(&path, &monitor_nodes, &mut responses);
|
||||
@@ -173,11 +167,10 @@ impl NodeRuntime {
|
||||
}
|
||||
}
|
||||
|
||||
async fn execute_network<'a>(&'a mut self, path: &[LayerId], scoped_network: NodeNetwork, transform: DAffine2, viewport_resolution: UVec2) -> Result<TaggedValue, String> {
|
||||
async fn execute_network<'a>(&'a mut self, path: &[LayerId], graph: NodeNetwork, transform: DAffine2, viewport_resolution: UVec2) -> (Result<TaggedValue, String>, MonitorNodes) {
|
||||
if self.wasm_io.is_none() {
|
||||
self.wasm_io = Some(WasmApplicationIo::new().await);
|
||||
}
|
||||
|
||||
let editor_api = WasmEditorApi {
|
||||
font_cache: &self.font_cache,
|
||||
application_io: self.wasm_io.as_ref().unwrap(),
|
||||
@@ -197,15 +190,33 @@ impl NodeRuntime {
|
||||
image_frame: None,
|
||||
};
|
||||
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
use std::hash::Hash;
|
||||
use std::hash::Hasher;
|
||||
// Required to ensure that the appropriate protonodes are reinserted when the Editor API changes.
|
||||
let mut graph_input_hash = DefaultHasher::new();
|
||||
editor_api.font_cache.hash(&mut graph_input_hash);
|
||||
|
||||
let scoped_network = wrap_network_in_scope(graph, graph_input_hash.finish());
|
||||
|
||||
let monitor_nodes = scoped_network
|
||||
.recursive_nodes()
|
||||
.filter(|(_, node)| node.implementation == DocumentNodeImplementation::proto("graphene_core::memo::MonitorNode<_>"))
|
||||
.map(|(_, node)| node.path.clone().unwrap_or_default())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// We assume only one output
|
||||
assert_eq!(scoped_network.outputs.len(), 1, "Graph with multiple outputs not yet handled");
|
||||
let c = Compiler {};
|
||||
let proto_network = c.compile_single(scoped_network)?;
|
||||
let proto_network = match c.compile_single(scoped_network) {
|
||||
Ok(network) => network,
|
||||
Err(e) => return (Err(e), monitor_nodes),
|
||||
};
|
||||
|
||||
assert_ne!(proto_network.nodes.len(), 0, "No protonodes exist?");
|
||||
if let Err(e) = self.executor.update(proto_network).await {
|
||||
error!("Failed to update executor:\n{e}");
|
||||
return Err(e);
|
||||
return (Err(e), monitor_nodes);
|
||||
}
|
||||
|
||||
use graph_craft::graphene_compiler::Executor;
|
||||
@@ -215,7 +226,11 @@ impl NodeRuntime {
|
||||
Some(t) if t == concrete!(()) => (&self.executor).execute(()).await.map_err(|e| e.to_string()),
|
||||
Some(t) => Err(format!("Invalid input type {t:?}")),
|
||||
_ => Err("No input type".to_string()),
|
||||
}?;
|
||||
};
|
||||
let result = match result {
|
||||
Ok(value) => value,
|
||||
Err(e) => return (Err(e), monitor_nodes),
|
||||
};
|
||||
|
||||
if let TaggedValue::SurfaceFrame(SurfaceFrame { surface_id, transform: _ }) = result {
|
||||
let old_id = self.canvas_cache.insert(path.to_vec(), surface_id);
|
||||
@@ -227,7 +242,7 @@ impl NodeRuntime {
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(result)
|
||||
(Ok((result)), monitor_nodes)
|
||||
}
|
||||
|
||||
/// Recomputes the thumbnails for the layers in the graph, modifying the state and updating the UI.
|
||||
|
||||
@@ -652,7 +652,7 @@ impl JsEditorHandle {
|
||||
#[wasm_bindgen(js_name = selectNodes)]
|
||||
pub fn select_nodes(&self, nodes: Option<Vec<u64>>) {
|
||||
let nodes = nodes.unwrap_or_default();
|
||||
let message = NodeGraphMessage::SetSelectNodes { nodes };
|
||||
let message = NodeGraphMessage::SetSelectedNodes { nodes };
|
||||
self.dispatch(message);
|
||||
}
|
||||
|
||||
|
||||
@@ -41,6 +41,9 @@ pub struct DocumentNode {
|
||||
pub metadata: DocumentNodeMetadata,
|
||||
#[serde(default)]
|
||||
pub skip_deduplication: bool,
|
||||
/// Used as a hash of the graph input where applicable. This ensures that protonodes that depend on the graph's input are always regenerated.
|
||||
#[serde(default)]
|
||||
pub hash: u64,
|
||||
pub path: Option<Vec<NodeId>>,
|
||||
}
|
||||
|
||||
@@ -103,6 +106,7 @@ impl DocumentNode {
|
||||
construction_args: args,
|
||||
document_node_path: self.path.unwrap_or(Vec::new()),
|
||||
skip_deduplication: self.skip_deduplication,
|
||||
hash: self.hash,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -197,6 +197,7 @@ pub struct ProtoNode {
|
||||
pub identifier: NodeIdentifier,
|
||||
pub document_node_path: Vec<NodeId>,
|
||||
pub skip_deduplication: bool,
|
||||
pub hash: u64,
|
||||
}
|
||||
|
||||
/// A ProtoNodeInput represents the input of a node in a ProtoNetwork.
|
||||
@@ -235,6 +236,7 @@ impl ProtoNode {
|
||||
if self.skip_deduplication {
|
||||
self.document_node_path.hash(&mut hasher);
|
||||
}
|
||||
self.hash.hash(&mut hasher);
|
||||
std::mem::discriminant(&self.input).hash(&mut hasher);
|
||||
match self.input {
|
||||
ProtoNodeInput::None => (),
|
||||
@@ -256,6 +258,7 @@ impl ProtoNode {
|
||||
input: ProtoNodeInput::None,
|
||||
document_node_path: path,
|
||||
skip_deduplication: false,
|
||||
hash: 0,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -361,6 +364,7 @@ impl ProtoNetwork {
|
||||
input,
|
||||
document_node_path: path,
|
||||
skip_deduplication: false,
|
||||
hash: 0,
|
||||
},
|
||||
));
|
||||
|
||||
|
||||
@@ -102,6 +102,7 @@ impl BorrowTree {
|
||||
old_nodes.remove(&id);
|
||||
}
|
||||
self.source_map.retain(|_, nid| !old_nodes.contains(nid));
|
||||
self.nodes.retain(|nid, _| !old_nodes.contains(nid));
|
||||
Ok(old_nodes.into_iter().collect())
|
||||
}
|
||||
|
||||
|
||||
@@ -730,9 +730,47 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
|
||||
register_node!(graphene_core::vector::RepeatNode<_, _>, input: VectorData, params: [DVec2, u32]),
|
||||
register_node!(graphene_core::vector::BoundingBoxNode, input: VectorData, params: []),
|
||||
register_node!(graphene_core::vector::CircularRepeatNode<_, _, _>, input: VectorData, params: [f32, f32, u32]),
|
||||
register_node!(graphene_core::transform::CullNode<_>, input: Footprint, params: [VectorData]),
|
||||
vec![(
|
||||
NodeIdentifier::new("graphene_core::transform::CullNode<_>"),
|
||||
|args| {
|
||||
Box::pin(async move {
|
||||
let mut args = args.clone();
|
||||
args.reverse();
|
||||
let node = <graphene_core::transform::CullNode<_>>::new(graphene_std::any::input_node::<VectorData>(args.pop().expect("Not enough arguments provided to construct node")));
|
||||
let any: DynAnyNode<Footprint, _, _> = graphene_std::any::DynAnyNode::new(node);
|
||||
Box::new(any) as Box<dyn for<'i> NodeIO<'i, graph_craft::proto::Any<'i>, Output = (core::pin::Pin<Box<dyn core::future::Future<Output = graph_craft::proto::Any<'i>> + 'i>>)> + '_>
|
||||
})
|
||||
},
|
||||
{
|
||||
let node = <graphene_core::transform::CullNode<_>>::new((graphene_std::any::PanicNode::<(), VectorData>::new()));
|
||||
let params = vec![fn_type!((), VectorData)];
|
||||
let mut node_io = <graphene_core::transform::CullNode<_> as NodeIO<'_, Footprint>>::to_node_io(&node, params);
|
||||
node_io.input = concrete!(<Footprint as StaticType>::Static);
|
||||
node_io
|
||||
},
|
||||
)],
|
||||
register_node!(graphene_core::transform::CullNode<_>, input: Footprint, params: [graphene_core::Artboard]),
|
||||
register_node!(graphene_core::transform::CullNode<_>, input: Footprint, params: [graphene_core::GraphicGroup]),
|
||||
vec![(
|
||||
NodeIdentifier::new("graphene_core::transform::CullNode<_>"),
|
||||
|args| {
|
||||
Box::pin(async move {
|
||||
let mut args = args.clone();
|
||||
args.reverse();
|
||||
let node = <graphene_core::transform::CullNode<_>>::new(graphene_std::any::input_node::<graphene_core::GraphicGroup>(
|
||||
args.pop().expect("Not enough arguments provided to construct node"),
|
||||
));
|
||||
let any: DynAnyNode<Footprint, _, _> = graphene_std::any::DynAnyNode::new(node);
|
||||
Box::new(any) as Box<dyn for<'i> NodeIO<'i, graph_craft::proto::Any<'i>, Output = (core::pin::Pin<Box<dyn core::future::Future<Output = graph_craft::proto::Any<'i>> + 'i>>)> + '_>
|
||||
})
|
||||
},
|
||||
{
|
||||
let node = <graphene_core::transform::CullNode<_>>::new((graphene_std::any::PanicNode::<(), graphene_core::GraphicGroup>::new()));
|
||||
let params = vec![fn_type!((), graphene_core::GraphicGroup)];
|
||||
let mut node_io = <graphene_core::transform::CullNode<_> as NodeIO<'_, Footprint>>::to_node_io(&node, params);
|
||||
node_io.input = concrete!(<Footprint as StaticType>::Static);
|
||||
node_io
|
||||
},
|
||||
)],
|
||||
register_node!(graphene_std::raster::SampleNode<_>, input: Footprint, params: [ImageFrame<Color>]),
|
||||
register_node!(graphene_std::raster::MandelbrotNode, input: Footprint, params: []),
|
||||
register_node!(graphene_core::vector::ResamplePoints<_>, input: VectorData, params: [f64]),
|
||||
|
||||
Reference in New Issue
Block a user