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 12:55:13 -07:00
committed by GitHub
co-authored by Keavon Chambers
parent 3d4e3a74e5
commit b8906f344e
17 changed files with 283 additions and 238 deletions
@@ -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);