mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-25 23:08:13 +08:00
Migrate text layers to nodes (#1155)
* Initial work towards text to node * Add the text generate node * Implement live edit * Fix merge error * Cleanup text tool * Implement text * Fix transforms * Fix broken image frame * Double click to edit text * Fix rendering text on load * Moving whilst editing * Better text properties * Prevent changing vector when there is a Text node * Push node api * Use node fn macro * Stable ids * Image module as a seperate file * Explain check for "Input Frame" node * Code review --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
committed by
Keavon Chambers
co-authored by
Keavon Chambers
parent
271f9d5158
commit
ef93f8442a
@@ -34,7 +34,6 @@ impl PathOutline {
|
||||
// Get the bezpath from the shape or text
|
||||
let subpath = match &document_layer.data {
|
||||
LayerDataType::Shape(layer_shape) => Some(layer_shape.shape.clone()),
|
||||
LayerDataType::Text(text) => Some(text.to_subpath_nonmut(render_data)),
|
||||
LayerDataType::NodeGraphFrame(frame) => frame.as_vector_data().map(|vector_data| Subpath::from_bezier_crate(&vector_data.subpaths)),
|
||||
_ => document_layer.aabb_for_transform(DAffine2::IDENTITY, render_data).map(|[p1, p2]| Subpath::new_rect(p1, p2)),
|
||||
}?;
|
||||
|
||||
@@ -863,9 +863,11 @@ impl Fsm for SelectToolFsmState {
|
||||
// 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 LayerDataType::Text(_) = layer.data {
|
||||
responses.push_front(ToolMessage::ActivateTool { tool_type: ToolType::Text }.into());
|
||||
responses.push_back(TextToolMessage::EditSelected.into());
|
||||
if let Ok(network) = layer.as_node_graph() {
|
||||
if network.nodes.values().any(|node| node.name == "Text") {
|
||||
responses.push_front(ToolMessage::ActivateTool { tool_type: ToolType::Text }.into());
|
||||
responses.push_back(TextToolMessage::EditSelected.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1249,15 +1251,16 @@ fn edit_layer_shallowest_manipulation(document: &DocumentMessageHandler, interse
|
||||
|
||||
fn edit_layer_deepest_manipulation(intersect: &Layer, responses: &mut VecDeque<Message>) {
|
||||
match &intersect.data {
|
||||
LayerDataType::Text(_) => {
|
||||
responses.push_front(ToolMessage::ActivateTool { tool_type: ToolType::Text }.into());
|
||||
responses.push_back(TextToolMessage::Interact.into());
|
||||
}
|
||||
LayerDataType::Shape(_) => {
|
||||
responses.push_front(ToolMessage::ActivateTool { tool_type: ToolType::Path }.into());
|
||||
}
|
||||
LayerDataType::NodeGraphFrame(frame) if frame.as_vector_data().is_some() => {
|
||||
responses.push_front(ToolMessage::ActivateTool { tool_type: ToolType::Path }.into());
|
||||
LayerDataType::NodeGraphFrame(graph_frame) if graph_frame.as_vector_data().is_some() => {
|
||||
if graph_frame.network.nodes.values().any(|node| node.name == "Text") {
|
||||
responses.push_front(ToolMessage::ActivateTool { tool_type: ToolType::Text }.into());
|
||||
responses.push_back(TextToolMessage::EditSelected.into());
|
||||
} else {
|
||||
responses.push_front(ToolMessage::ActivateTool { tool_type: ToolType::Path }.into());
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
@@ -5,17 +5,22 @@ use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMot
|
||||
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, WidgetCallback, WidgetHolder, WidgetLayout};
|
||||
use crate::messages::layout::utility_types::misc::LayoutTarget;
|
||||
use crate::messages::layout::utility_types::widgets::input_widgets::{FontInput, NumberInput};
|
||||
use crate::messages::portfolio::document::node_graph::new_text_network;
|
||||
use crate::messages::prelude::*;
|
||||
use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType};
|
||||
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
|
||||
|
||||
use document_legacy::intersection::Quad;
|
||||
use document_legacy::layers::layer_info::LayerDataType;
|
||||
use document_legacy::layers::layer_info::Layer;
|
||||
use document_legacy::layers::style::{self, Fill, RenderData, Stroke};
|
||||
use document_legacy::LayerId;
|
||||
use document_legacy::Operation;
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::{DocumentNode, NodeId, NodeInput, NodeNetwork};
|
||||
use graphene_core::text::{load_face, Font};
|
||||
use graphene_core::Color;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Default)]
|
||||
@@ -166,7 +171,7 @@ impl ToolTransition for TextTool {
|
||||
EventToMessageMap {
|
||||
document_dirty: Some(TextToolMessage::DocumentIsDirty.into()),
|
||||
tool_abort: Some(TextToolMessage::Abort.into()),
|
||||
selection_changed: None,
|
||||
selection_changed: Some(TextToolMessage::DocumentIsDirty.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -177,17 +182,175 @@ enum TextToolFsmState {
|
||||
Ready,
|
||||
Editing,
|
||||
}
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct EditingText {
|
||||
text: String,
|
||||
font: Font,
|
||||
font_size: f64,
|
||||
color: Color,
|
||||
transform: DAffine2,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
struct TextToolData {
|
||||
layer_path: Vec<LayerId>,
|
||||
overlays: Vec<Vec<LayerId>>,
|
||||
editing_text: Option<EditingText>,
|
||||
new_text: String,
|
||||
}
|
||||
|
||||
impl TextToolData {
|
||||
/// Set the editing state of the currently modifying layer
|
||||
fn set_editing(&self, editable: bool, responses: &mut VecDeque<Message>) {
|
||||
fn set_editing(&self, editable: bool, render_data: &RenderData, responses: &mut VecDeque<Message>) {
|
||||
let path = self.layer_path.clone();
|
||||
responses.push_back(DocumentMessage::SetTextboxEditability { path, editable }.into());
|
||||
responses.add(Operation::SetLayerVisibility { path, visible: !editable });
|
||||
|
||||
if let Some(editing_text) = self.editing_text.as_ref().filter(|_| editable) {
|
||||
responses.add(FrontendMessage::DisplayEditableTextbox {
|
||||
text: editing_text.text.clone(),
|
||||
line_width: None,
|
||||
font_size: editing_text.font_size,
|
||||
color: editing_text.color,
|
||||
url: render_data.font_cache.get_preview_url(&editing_text.font).cloned().unwrap_or_default(),
|
||||
transform: editing_text.transform.to_cols_array(),
|
||||
});
|
||||
} else {
|
||||
responses.add(FrontendMessage::DisplayRemoveEditableTextbox);
|
||||
}
|
||||
}
|
||||
|
||||
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)?;
|
||||
self.editing_text = Some(EditingText {
|
||||
text: text.clone(),
|
||||
font: font.clone(),
|
||||
font_size,
|
||||
color,
|
||||
transform,
|
||||
});
|
||||
self.new_text = text.clone();
|
||||
Some(())
|
||||
}
|
||||
|
||||
fn start_editing_layer(&mut self, layer_path: &[LayerId], 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.layer_path = layer_path.into();
|
||||
self.load_layer_text_node(document);
|
||||
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
|
||||
self.set_editing(true, render_data, 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))
|
||||
}
|
||||
|
||||
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)) {
|
||||
self.start_editing_layer(clicked_text_layer_path, state, document, render_data, responses);
|
||||
|
||||
TextToolFsmState::Editing
|
||||
}
|
||||
// Create new text
|
||||
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 as f64);
|
||||
|
||||
responses.add(Operation::AddNodeGraphFrame {
|
||||
path: self.layer_path.clone(),
|
||||
insert_index: -1,
|
||||
transform: DAffine2::ZERO.to_cols_array(),
|
||||
network,
|
||||
});
|
||||
responses.add(GraphOperationMessage::FillSet {
|
||||
layer: self.layer_path.clone(),
|
||||
fill: Fill::solid(editing_text.color),
|
||||
});
|
||||
responses.add(GraphOperationMessage::TransformSet {
|
||||
layer: self.layer_path.clone(),
|
||||
transform: editing_text.transform,
|
||||
transform_in: TransformIn::Viewport,
|
||||
skip_rerender: true,
|
||||
});
|
||||
|
||||
self.set_editing(true, render_data, responses);
|
||||
|
||||
let replacement_selected_layers = vec![self.layer_path.clone()];
|
||||
|
||||
responses.add(DocumentMessage::SetSelectedLayers { replacement_selected_layers });
|
||||
|
||||
TextToolFsmState::Editing
|
||||
} else {
|
||||
// Removing old text as editable
|
||||
self.set_editing(false, render_data, responses);
|
||||
|
||||
resize_overlays(&mut self.overlays, responses, 0);
|
||||
|
||||
TextToolFsmState::Ready
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_bounds_overlay(&mut self, document: &DocumentMessageHandler, render_data: &RenderData, responses: &mut VecDeque<Message>) -> Option<()> {
|
||||
resize_overlays(&mut self.overlays, responses, 1);
|
||||
|
||||
let editing_text = self.editing_text.as_ref()?;
|
||||
let buzz_face = render_data.font_cache.get(&editing_text.font).map(|data| load_face(&data));
|
||||
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 bounds = transformed_quad.bounding_box();
|
||||
|
||||
let operation = Operation::SetLayerTransformInViewport {
|
||||
path: self.overlays[0].clone(),
|
||||
transform: transform_from_box(bounds[0], bounds[1]),
|
||||
};
|
||||
responses.add(DocumentMessage::Overlays(operation.into()));
|
||||
Some(())
|
||||
}
|
||||
|
||||
fn get_bounds(&self, text: &str, render_data: &RenderData) -> Option<[DVec2; 2]> {
|
||||
let editing_text = self.editing_text.as_ref()?;
|
||||
let buzz_face = render_data.font_cache.get(&editing_text.font).map(|data| load_face(&data));
|
||||
let subpaths = graphene_core::text::to_path(&text, buzz_face, editing_text.font_size, None);
|
||||
let bounds = subpaths.iter().filter_map(|subpath| subpath.bounding_box());
|
||||
let combined_bounds = bounds.reduce(|a, b| [a[0].min(b[0]), a[1].max(b[1])]).unwrap_or_default();
|
||||
Some(combined_bounds)
|
||||
}
|
||||
|
||||
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 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 });
|
||||
|
||||
Some(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -198,7 +361,7 @@ fn transform_from_box(pos1: DVec2, pos2: DVec2) -> [f64; 6] {
|
||||
fn resize_overlays(overlays: &mut Vec<Vec<LayerId>>, responses: &mut VecDeque<Message>, newlen: usize) {
|
||||
while overlays.len() > newlen {
|
||||
let operation = Operation::DeleteLayer { path: overlays.pop().unwrap() };
|
||||
responses.push_back(DocumentMessage::Overlays(operation.into()).into());
|
||||
responses.add(DocumentMessage::Overlays(operation.into()));
|
||||
}
|
||||
while overlays.len() < newlen {
|
||||
let path = vec![generate_uuid()];
|
||||
@@ -210,206 +373,152 @@ fn resize_overlays(overlays: &mut Vec<Vec<LayerId>>, responses: &mut VecDeque<Me
|
||||
style: style::PathStyle::new(Some(Stroke::new(COLOR_ACCENT, 1.0)), Fill::None),
|
||||
insert_index: -1,
|
||||
};
|
||||
responses.push_back(DocumentMessage::Overlays(operation.into()).into());
|
||||
responses.add(DocumentMessage::Overlays(operation.into()));
|
||||
}
|
||||
}
|
||||
|
||||
fn update_overlays(document: &DocumentMessageHandler, tool_data: &mut TextToolData, responses: &mut VecDeque<Message>, render_data: &RenderData) {
|
||||
let visible_text_layers = document.selected_visible_text_layers().collect::<Vec<_>>();
|
||||
resize_overlays(&mut tool_data.overlays, responses, visible_text_layers.len());
|
||||
|
||||
let bounds = visible_text_layers
|
||||
.into_iter()
|
||||
.zip(&tool_data.overlays)
|
||||
.filter_map(|(layer_path, overlay_path)| {
|
||||
document
|
||||
.document_legacy
|
||||
.layer(layer_path)
|
||||
.unwrap()
|
||||
.aabb_for_transform(document.document_legacy.multiply_transforms(layer_path).unwrap(), render_data)
|
||||
.map(|bounds| (bounds, overlay_path))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let get_bounds = |layer: &Layer, path: &[LayerId], document: &DocumentMessageHandler, render_data: &RenderData| {
|
||||
let node_graph = layer.as_node_graph().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 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;
|
||||
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 = bounds.collect::<Vec<_>>();
|
||||
|
||||
let new_len = bounds.len();
|
||||
|
||||
for (bounds, overlay_path) in bounds {
|
||||
for (bounds, overlay_path) in bounds.iter().zip(&tool_data.overlays) {
|
||||
let operation = Operation::SetLayerTransformInViewport {
|
||||
path: overlay_path.to_vec(),
|
||||
transform: transform_from_box(bounds[0], bounds[1]),
|
||||
};
|
||||
responses.push_back(DocumentMessage::Overlays(operation.into()).into());
|
||||
responses.add(DocumentMessage::Overlays(operation.into()));
|
||||
}
|
||||
resize_overlays(&mut tool_data.overlays, responses, new_len);
|
||||
}
|
||||
|
||||
fn set_edit_layer(layer_path: &[LayerId], tool_state: TextToolFsmState, tool_data: &mut TextToolData, responses: &mut VecDeque<Message>) {
|
||||
if tool_state == TextToolFsmState::Editing {
|
||||
tool_data.set_editing(false, responses);
|
||||
fn get_network<'a>(layer_path: &[LayerId], document: &'a DocumentMessageHandler) -> Option<&'a NodeNetwork> {
|
||||
let layer = document.document_legacy.layer(layer_path).ok()?;
|
||||
layer.as_node_graph().ok()
|
||||
}
|
||||
|
||||
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();
|
||||
// Check that only one layer is selected
|
||||
if selected_layers.next().is_some() {
|
||||
return None;
|
||||
}
|
||||
|
||||
tool_data.layer_path = layer_path.into();
|
||||
if !is_text_layer(document, &layer_path) {
|
||||
return None;
|
||||
}
|
||||
|
||||
responses.push_back(DocumentMessage::StartTransaction.into());
|
||||
|
||||
tool_data.set_editing(true, responses);
|
||||
|
||||
let replacement_selected_layers = vec![tool_data.layer_path.clone()];
|
||||
responses.push_back(DocumentMessage::SetSelectedLayers { replacement_selected_layers }.into());
|
||||
Some(layer_path)
|
||||
}
|
||||
|
||||
impl Fsm for TextToolFsmState {
|
||||
type ToolData = TextToolData;
|
||||
type ToolOptions = TextOptions;
|
||||
|
||||
fn transition(
|
||||
self,
|
||||
event: ToolMessage,
|
||||
tool_data: &mut Self::ToolData,
|
||||
ToolActionHandlerData {
|
||||
fn transition(self, event: ToolMessage, tool_data: &mut Self::ToolData, transition_data: &mut ToolActionHandlerData, tool_options: &Self::ToolOptions, responses: &mut VecDeque<Message>) -> Self {
|
||||
let ToolActionHandlerData {
|
||||
document,
|
||||
global_tool_data,
|
||||
input,
|
||||
render_data,
|
||||
..
|
||||
}: &mut ToolActionHandlerData,
|
||||
tool_options: &Self::ToolOptions,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) -> Self {
|
||||
use TextToolFsmState::*;
|
||||
use TextToolMessage::*;
|
||||
|
||||
} = transition_data;
|
||||
if let ToolMessage::Text(event) = event {
|
||||
match (self, event) {
|
||||
(state, DocumentIsDirty) => {
|
||||
(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(),
|
||||
});
|
||||
tool_data.update_bounds_overlay(document, render_data, responses);
|
||||
TextToolFsmState::Editing
|
||||
}
|
||||
(state, TextToolMessage::DocumentIsDirty) => {
|
||||
update_overlays(document, tool_data, responses, render_data);
|
||||
|
||||
state
|
||||
}
|
||||
(state, Interact) => {
|
||||
let mouse_pos = input.mouse.position;
|
||||
let tolerance = DVec2::splat(SELECTION_TOLERANCE);
|
||||
let quad = Quad::from_box([mouse_pos - tolerance, mouse_pos + tolerance]);
|
||||
(state, TextToolMessage::Interact) => {
|
||||
tool_data.editing_text = Some(EditingText {
|
||||
text: String::new(),
|
||||
transform: DAffine2::from_translation(input.mouse.position),
|
||||
font_size: tool_options.font_size as f64,
|
||||
font: Font::new(tool_options.font_name.clone(), tool_options.font_style.clone()),
|
||||
color: global_tool_data.primary_color,
|
||||
});
|
||||
tool_data.new_text = String::new();
|
||||
tool_data.layer_path = document.get_path_for_new_layer();
|
||||
|
||||
// Check if the user has selected an existing text layer
|
||||
let new_state = if let Some(clicked_text_layer_path) = document
|
||||
.document_legacy
|
||||
.intersects_quad_root(quad, render_data)
|
||||
.last()
|
||||
.filter(|l| document.document_legacy.layer(l).map(|l| l.as_text().is_ok()).unwrap_or(false))
|
||||
{
|
||||
set_edit_layer(clicked_text_layer_path, state, tool_data, responses);
|
||||
|
||||
Editing
|
||||
}
|
||||
// Create new text
|
||||
else if state == TextToolFsmState::Ready {
|
||||
responses.push_back(DocumentMessage::StartTransaction.into());
|
||||
|
||||
let transform = DAffine2::from_translation(input.mouse.position);
|
||||
let font_size = tool_options.font_size;
|
||||
let font_name = tool_options.font_name.clone();
|
||||
let font_style = tool_options.font_style.clone();
|
||||
tool_data.layer_path = document.get_path_for_new_layer();
|
||||
|
||||
responses.push_back(
|
||||
Operation::AddText {
|
||||
path: tool_data.layer_path.clone(),
|
||||
transform: DAffine2::ZERO.to_cols_array(),
|
||||
insert_index: -1,
|
||||
text: String::new(),
|
||||
style: style::PathStyle::new(None, Fill::solid(global_tool_data.primary_color)),
|
||||
size: font_size as f64,
|
||||
font_name,
|
||||
font_style,
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
responses.push_back(
|
||||
GraphOperationMessage::TransformSet {
|
||||
layer: tool_data.layer_path.clone(),
|
||||
transform,
|
||||
transform_in: TransformIn::Viewport,
|
||||
skip_rerender: false,
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
|
||||
tool_data.set_editing(true, responses);
|
||||
|
||||
let replacement_selected_layers = vec![tool_data.layer_path.clone()];
|
||||
|
||||
responses.push_back(DocumentMessage::SetSelectedLayers { replacement_selected_layers }.into());
|
||||
|
||||
Editing
|
||||
} else {
|
||||
// Removing old text as editable
|
||||
tool_data.set_editing(false, responses);
|
||||
|
||||
resize_overlays(&mut tool_data.overlays, responses, 0);
|
||||
|
||||
Ready
|
||||
};
|
||||
|
||||
new_state
|
||||
tool_data.interact(state, input.mouse.position, document, &render_data, responses)
|
||||
}
|
||||
(state, EditSelected) => {
|
||||
let mut selected_layers = document.selected_layers();
|
||||
|
||||
if let Some(layer_path) = 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 LayerDataType::Text(_) = layer.data {
|
||||
set_edit_layer(layer_path, state, tool_data, responses);
|
||||
|
||||
return Editing;
|
||||
}
|
||||
}
|
||||
}
|
||||
(state, TextToolMessage::EditSelected) => {
|
||||
if let Some(layer_path) = can_edit_selected(document) {
|
||||
tool_data.start_editing_layer(&layer_path, state, document, render_data, responses);
|
||||
return TextToolFsmState::Editing;
|
||||
}
|
||||
|
||||
state
|
||||
}
|
||||
(state, Abort) => {
|
||||
(state, TextToolMessage::Abort) => {
|
||||
if state == TextToolFsmState::Editing {
|
||||
tool_data.set_editing(false, responses);
|
||||
tool_data.set_editing(false, render_data, responses);
|
||||
}
|
||||
|
||||
resize_overlays(&mut tool_data.overlays, responses, 0);
|
||||
|
||||
Ready
|
||||
TextToolFsmState::Ready
|
||||
}
|
||||
(Editing, CommitText) => {
|
||||
responses.push_back(FrontendMessage::TriggerTextCommit.into());
|
||||
(TextToolFsmState::Editing, TextToolMessage::CommitText) => {
|
||||
responses.add(FrontendMessage::TriggerTextCommit);
|
||||
|
||||
Editing
|
||||
TextToolFsmState::Editing
|
||||
}
|
||||
(Editing, TextChange { new_text }) => {
|
||||
let path = tool_data.layer_path.clone();
|
||||
responses.push_back(Operation::SetTextContent { path, new_text }.into());
|
||||
(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()],
|
||||
input_index: 1,
|
||||
value: TaggedValue::String(new_text),
|
||||
});
|
||||
|
||||
tool_data.set_editing(false, responses);
|
||||
tool_data.set_editing(false, render_data, responses);
|
||||
|
||||
resize_overlays(&mut tool_data.overlays, responses, 0);
|
||||
|
||||
Ready
|
||||
TextToolFsmState::Ready
|
||||
}
|
||||
(Editing, UpdateBounds { new_text }) => {
|
||||
resize_overlays(&mut tool_data.overlays, responses, 1);
|
||||
let text = document.document_legacy.layer(&tool_data.layer_path).unwrap().as_text().unwrap();
|
||||
let quad = text.bounding_box(&new_text, text.load_face(render_data));
|
||||
|
||||
let transformed_quad = document.document_legacy.multiply_transforms(&tool_data.layer_path).unwrap() * quad;
|
||||
let bounds = transformed_quad.bounding_box();
|
||||
|
||||
let operation = Operation::SetLayerTransformInViewport {
|
||||
path: tool_data.overlays[0].clone(),
|
||||
transform: transform_from_box(bounds[0], bounds[1]),
|
||||
};
|
||||
responses.push_back(DocumentMessage::Overlays(operation.into()).into());
|
||||
|
||||
Editing
|
||||
(TextToolFsmState::Editing, TextToolMessage::UpdateBounds { new_text }) => {
|
||||
tool_data.new_text = new_text;
|
||||
tool_data.update_bounds_overlay(document, render_data, responses);
|
||||
TextToolFsmState::Editing
|
||||
}
|
||||
_ => self,
|
||||
}
|
||||
@@ -427,10 +536,10 @@ impl Fsm for TextToolFsmState {
|
||||
])]),
|
||||
};
|
||||
|
||||
responses.push_back(FrontendMessage::UpdateInputHints { hint_data }.into());
|
||||
responses.add(FrontendMessage::UpdateInputHints { hint_data });
|
||||
}
|
||||
|
||||
fn update_cursor(&self, responses: &mut VecDeque<Message>) {
|
||||
responses.push_back(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Text }.into());
|
||||
responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Text });
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user