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:
0HyperCube
2023-10-24 20:55:13 +01:00
committed by GitHub
parent 3d4e3a74e5
commit b8906f344e
17 changed files with 283 additions and 238 deletions

View File

@@ -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 });
}

View File

@@ -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,

View File

@@ -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);

View File

@@ -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,
},

View File

@@ -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);

View File

@@ -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")

View File

@@ -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");

View File

@@ -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)

View File

@@ -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);

View File

@@ -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>) {

View File

@@ -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);

View File

@@ -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.