Skip to content

Commit

Permalink
Add max width/height to text layers and draggable text boxes to the T…
Browse files Browse the repository at this point in the history
…ext tool (#2118)

* Make progress in text tool

* Add line_width to gcore and gstd

* minor fix

* Dragging sets line_width correctly

* Get draw overlay to work

* Typo fix

* Make progress in text tool

* Add line_width to gcore and gstd

* minor fix

* Dragging sets line_width correctly

* Get draw overlay to work

* Typo fix

* Improve text bounding box

* Add toggle for editing line width

* Take absolute value of drag

* Fix optional properties

* Code review

* Attempt to add box height and abort with keys

* Attempt to add key modifiers and snap manager

* Use resize for improved dragging

* Refactor typesetting configuration into a struct

* Fix missing px unit in frontend

* Remove lines on rendered text

* Fix backwards compatibility

* Refactor lenient slection as an associate function in tool data

* Add dashed quad to text nodes

* Use correct names for max height and width

* Additional renames and reorder

* ReResolve conflict

* Code review and improvements

---------

Co-authored-by: hypercube <[email protected]>
Co-authored-by: Keavon Chambers <[email protected]>
  • Loading branch information
3 people authored Jan 1, 2025
1 parent f225756 commit 6635754
Show file tree
Hide file tree
Showing 34 changed files with 519 additions and 261 deletions.
3 changes: 1 addition & 2 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,5 @@
"files.insertFinalNewline": true,
"files.associations": {
"*.graphite": "json"
},
"rust-analyzer.checkOnSave": false
}
}
8 changes: 6 additions & 2 deletions editor/src/messages/frontend/frontend_message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,17 @@ pub enum FrontendMessage {
},
DisplayEditableTextbox {
text: String,
#[serde(rename = "lineWidth")]
line_width: Option<f64>,
#[serde(rename = "lineHeightRatio")]
line_height_ratio: f64,
#[serde(rename = "fontSize")]
font_size: f64,
color: Color,
url: String,
transform: [f64; 6],
#[serde(rename = "maxWidth")]
max_width: Option<f64>,
#[serde(rename = "maxHeight")]
max_height: Option<f64>,
},
DisplayEditableTextboxTransform {
transform: [f64; 6],
Expand Down
5 changes: 4 additions & 1 deletion editor/src/messages/input_mapper/input_mappings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,10 @@ pub fn input_mappings() -> Mapping {
entry!(KeyDown(Escape); action_dispatch=EyedropperToolMessage::Abort),
//
// TextToolMessage
entry!(KeyUp(MouseLeft); action_dispatch=TextToolMessage::Interact),
entry!(PointerMove; refresh_keys=[Alt, Shift], action_dispatch=TextToolMessage::PointerMove { center: Alt, lock_ratio: Shift }),
entry!(KeyDown(MouseLeft); action_dispatch=TextToolMessage::DragStart),
entry!(KeyUp(MouseLeft); action_dispatch=TextToolMessage::DragStop),
entry!(KeyDown(MouseRight); action_dispatch=TextToolMessage::CommitText),
entry!(KeyDown(Escape); action_dispatch=TextToolMessage::CommitText),
entry!(KeyDown(Enter); modifiers=[Accel], action_dispatch=TextToolMessage::CommitText),
//
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use crate::messages::prelude::*;
use bezier_rs::Subpath;
use graph_craft::document::NodeId;
use graphene_core::raster::{BlendMode, ImageFrame};
use graphene_core::text::Font;
use graphene_core::text::{Font, TypesettingConfig};
use graphene_core::vector::brush_stroke::BrushStroke;
use graphene_core::vector::style::{Fill, Stroke};
use graphene_core::vector::PointId;
Expand Down Expand Up @@ -93,9 +93,7 @@ pub enum GraphOperationMessage {
id: NodeId,
text: String,
font: Font,
size: f64,
line_height_ratio: f64,
character_spacing: f64,
typesetting: TypesettingConfig,
parent: LayerNodeIdentifier,
insert_index: usize,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use crate::messages::prelude::*;

use graph_craft::document::{NodeId, NodeInput};
use graphene_core::renderer::Quad;
use graphene_core::text::Font;
use graphene_core::text::{Font, TypesettingConfig};
use graphene_core::vector::style::{Fill, Gradient, GradientStops, GradientType, LineCap, LineJoin, Stroke};
use graphene_core::Color;
use graphene_std::vector::convert_usvg_path;
Expand Down Expand Up @@ -174,15 +174,13 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
id,
text,
font,
size,
line_height_ratio,
character_spacing,
typesetting,
parent,
insert_index,
} => {
let mut modify_inputs = ModifyInputsContext::new(network_interface, responses);
let layer = modify_inputs.create_layer(id);
modify_inputs.insert_text(text, font, size, line_height_ratio, character_spacing, layer);
modify_inputs.insert_text(text, font, typesetting, layer);
network_interface.move_layer_to_stack(layer, parent, insert_index, &[]);
responses.add(GraphOperationMessage::StrokeSet { layer, stroke: Stroke::default() });
responses.add(NodeGraphMessage::RunDocumentGraph);
Expand Down Expand Up @@ -279,7 +277,7 @@ fn import_usvg_node(modify_inputs: &mut ModifyInputsContext, node: &usvg::Node,
}
usvg::Node::Text(text) => {
let font = Font::new(graphene_core::consts::DEFAULT_FONT_FAMILY.to_string(), graphene_core::consts::DEFAULT_FONT_STYLE.to_string());
modify_inputs.insert_text(text.chunks().iter().map(|chunk| chunk.text()).collect(), font, 24., 1.2, 1., layer);
modify_inputs.insert_text(text.chunks().iter().map(|chunk| chunk.text()).collect(), font, TypesettingConfig::default(), layer);
modify_inputs.fill_set(Fill::Solid(Color::BLACK));
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use graph_craft::concrete;
use graph_craft::document::value::TaggedValue;
use graph_craft::document::{NodeId, NodeInput};
use graphene_core::raster::{BlendMode, ImageFrame};
use graphene_core::text::Font;
use graphene_core::text::{Font, TypesettingConfig};
use graphene_core::vector::brush_stroke::BrushStroke;
use graphene_core::vector::style::{Fill, Stroke};
use graphene_core::vector::{PointId, VectorModificationType};
Expand Down Expand Up @@ -179,17 +179,19 @@ impl<'a> ModifyInputsContext<'a> {
}
}

pub fn insert_text(&mut self, text: String, font: Font, size: f64, line_height_ratio: f64, character_spacing: f64, layer: LayerNodeIdentifier) {
pub fn insert_text(&mut self, text: String, font: Font, typesetting: TypesettingConfig, layer: LayerNodeIdentifier) {
let stroke = resolve_document_node_type("Stroke").expect("Stroke node does not exist").default_node_template();
let fill = resolve_document_node_type("Fill").expect("Fill node does not exist").default_node_template();
let transform = resolve_document_node_type("Transform").expect("Transform node does not exist").default_node_template();
let text = resolve_document_node_type("Text").expect("Text node does not exist").node_template_input_override([
Some(NodeInput::scope("editor-api")),
Some(NodeInput::value(TaggedValue::String(text), false)),
Some(NodeInput::value(TaggedValue::Font(font), false)),
Some(NodeInput::value(TaggedValue::F64(size), false)),
Some(NodeInput::value(TaggedValue::F64(line_height_ratio), false)),
Some(NodeInput::value(TaggedValue::F64(character_spacing), false)),
Some(NodeInput::value(TaggedValue::F64(typesetting.font_size), false)),
Some(NodeInput::value(TaggedValue::F64(typesetting.line_height_ratio), false)),
Some(NodeInput::value(TaggedValue::F64(typesetting.character_spacing), false)),
Some(NodeInput::value(TaggedValue::OptionalF64(typesetting.max_width), false)),
Some(NodeInput::value(TaggedValue::OptionalF64(typesetting.max_height), false)),
]);

let text_id = NodeId::new();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use graph_craft::imaginate_input::ImaginateSamplingMethod;
use graph_craft::ProtoNodeIdentifier;
use graphene_core::raster::brush_cache::BrushCache;
use graphene_core::raster::{CellularDistanceFunction, CellularReturnType, Color, DomainWarpType, FractalType, Image, ImageFrame, NoiseType, RedGreenBlue, RedGreenBlueAlpha};
use graphene_core::text::Font;
use graphene_core::text::{Font, TypesettingConfig};
use graphene_core::transform::Footprint;
use graphene_core::vector::VectorData;
use graphene_core::*;
Expand Down Expand Up @@ -2112,9 +2112,11 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
TaggedValue::Font(Font::new(graphene_core::consts::DEFAULT_FONT_FAMILY.into(), graphene_core::consts::DEFAULT_FONT_STYLE.into())),
false,
),
NodeInput::value(TaggedValue::F64(24.), false),
NodeInput::value(TaggedValue::F64(1.2), false),
NodeInput::value(TaggedValue::F64(1.), false),
NodeInput::value(TaggedValue::F64(TypesettingConfig::default().font_size), false),
NodeInput::value(TaggedValue::F64(TypesettingConfig::default().line_height_ratio), false),
NodeInput::value(TaggedValue::F64(TypesettingConfig::default().character_spacing), false),
NodeInput::value(TaggedValue::OptionalF64(TypesettingConfig::default().max_width), false),
NodeInput::value(TaggedValue::OptionalF64(TypesettingConfig::default().max_height), false),
],
..Default::default()
},
Expand All @@ -2126,6 +2128,8 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
"Size".to_string(),
"Line Height".to_string(),
"Character Spacing".to_string(),
"Max Width".to_string(),
"Max Height".to_string(),
],
output_names: vec!["Vector".to_string()],
..Default::default()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -661,6 +661,27 @@ fn number_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, na
.on_commit(commit_value)
.widget_holder(),
]),
Some(&TaggedValue::OptionalF64(x)) => {
// TODO: Don't wipe out the previously set value (setting it back to the default of 100) when reenabling this checkbox back to Some from None
let toggle_enabled = move |checkbox_input: &CheckboxInput| TaggedValue::OptionalF64(if checkbox_input.checked { Some(100.) } else { None });
widgets.extend_from_slice(&[
Separator::new(SeparatorType::Unrelated).widget_holder(),
Separator::new(SeparatorType::Related).widget_holder(),
// The checkbox toggles if the value is Some or None
CheckboxInput::new(x.is_some())
.on_update(update_value(toggle_enabled, node_id, index))
.on_commit(commit_value)
.widget_holder(),
Separator::new(SeparatorType::Related).widget_holder(),
Separator::new(SeparatorType::Unrelated).widget_holder(),
number_props
.value(x)
.on_update(update_value(move |x: &NumberInput| TaggedValue::OptionalF64(x.value), node_id, index))
.disabled(x.is_none())
.on_commit(commit_value)
.widget_holder(),
]);
}
_ => {}
}

Expand Down Expand Up @@ -1734,6 +1755,8 @@ pub(crate) fn text_properties(document_node: &DocumentNode, node_id: NodeId, _co
let size = number_widget(document_node, node_id, 3, "Size", NumberInput::default().unit(" px").min(1.), true);
let line_height_ratio = number_widget(document_node, node_id, 4, "Line Height", NumberInput::default().min(0.).step(0.1), true);
let character_spacing = number_widget(document_node, node_id, 5, "Character Spacing", NumberInput::default().min(0.).step(0.1), true);
let max_width = number_widget(document_node, node_id, 6, "Max Width", NumberInput::default().unit(" px").min(1.), false);
let max_height = number_widget(document_node, node_id, 7, "Max Height", NumberInput::default().unit(" px").min(1.), false);

let mut result = vec![LayoutGroup::Row { widgets: text }, LayoutGroup::Row { widgets: font }];
if let Some(style) = style {
Expand All @@ -1742,6 +1765,8 @@ pub(crate) fn text_properties(document_node: &DocumentNode, node_id: NodeId, _co
result.push(LayoutGroup::Row { widgets: size });
result.push(LayoutGroup::Row { widgets: line_height_ratio });
result.push(LayoutGroup::Row { widgets: character_spacing });
result.push(LayoutGroup::Row { widgets: max_width });
result.push(LayoutGroup::Row { widgets: max_height });
result
}

Expand Down
52 changes: 39 additions & 13 deletions editor/src/messages/portfolio/document/overlays/utility_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,26 +32,52 @@ impl core::hash::Hash for OverlayContext {

impl OverlayContext {
pub fn quad(&mut self, quad: Quad, color_fill: Option<&str>) {
self.dashed_quad(quad, color_fill, None, None);
}

pub fn dashed_quad(&mut self, quad: Quad, color_fill: Option<&str>, dash_width: Option<f64>, gap_width: Option<f64>) {
// Set the dash pattern
if let Some(dash_width) = dash_width {
let gap_width = gap_width.unwrap_or(1.);
let array = js_sys::Array::new();
array.push(&JsValue::from(dash_width - 1.));
array.push(&JsValue::from(gap_width));
self.render_context
.set_line_dash(&JsValue::from(array))
.map_err(|error| log::warn!("Error drawing dashed line: {:?}", error))
.ok();
}

self.render_context.begin_path();
self.render_context.move_to(quad.0[3].x.round() - 0.5, quad.0[3].y.round() - 0.5);

for i in 0..4 {
self.render_context.line_to(quad.0[i].x.round() - 0.5, quad.0[i].y.round() - 0.5);
}

if let Some(color_fill) = color_fill {
self.render_context.set_fill_style_str(color_fill);
self.render_context.fill();
}

self.render_context.set_stroke_style_str(COLOR_OVERLAY_BLUE);
self.render_context.stroke();

// Reset the dash pattern back to solid
if dash_width.is_some() {
self.render_context
.set_line_dash(&JsValue::from(js_sys::Array::new()))
.map_err(|error| log::warn!("Error drawing dashed line: {:?}", error))
.ok();
}
}

pub fn line(&mut self, start: DVec2, end: DVec2, color: Option<&str>) {
self.dashed_line(start, end, color, None, None)
}

pub fn dashed_line(&mut self, start: DVec2, end: DVec2, color: Option<&str>, dash_width: Option<f64>, gap_width: Option<f64>) {
let start = start.round() - DVec2::splat(0.5);
let end = end.round() - DVec2::splat(0.5);
// Set the dash pattern
if let Some(dash_width) = dash_width {
let gap_width = gap_width.unwrap_or(1.);
let array = js_sys::Array::new();
Expand All @@ -61,24 +87,24 @@ impl OverlayContext {
.set_line_dash(&JsValue::from(array))
.map_err(|error| log::warn!("Error drawing dashed line: {:?}", error))
.ok();
} else {
let array = js_sys::Array::new();
self.render_context
.set_line_dash(&JsValue::from(array))
.map_err(|error| log::warn!("Error drawing dashed line: {:?}", error))
.ok();
}

let start = start.round() - DVec2::splat(0.5);
let end = end.round() - DVec2::splat(0.5);

self.render_context.begin_path();
self.render_context.move_to(start.x, start.y);
self.render_context.line_to(end.x, end.y);
self.render_context.set_stroke_style_str(color.unwrap_or(COLOR_OVERLAY_BLUE));
self.render_context.stroke();

// Reset the dash pattern to solid after drawing
self.render_context
.set_line_dash(&JsValue::from(js_sys::Array::new()))
.map_err(|error| log::warn!("Error drawing dashed line: {:?}", error))
.ok();
// Reset the dash pattern back to solid
if dash_width.is_some() {
self.render_context
.set_line_dash(&JsValue::from(js_sys::Array::new()))
.map_err(|error| log::warn!("Error drawing dashed line: {:?}", error))
.ok();
}
}

pub fn manipulator_handle(&mut self, position: DVec2, selected: bool) {
Expand Down
38 changes: 30 additions & 8 deletions editor/src/messages/portfolio/portfolio_message_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use crate::node_graph_executor::{ExportConfig, NodeGraphExecutor};

use graph_craft::document::value::TaggedValue;
use graph_craft::document::{NodeId, NodeInput};
use graphene_core::text::Font;
use graphene_core::text::{Font, TypesettingConfig};
use graphene_std::vector::style::{Fill, FillType, Gradient};
use interpreted_executor::dynamic_executor::IntrospectError;

Expand Down Expand Up @@ -530,7 +530,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
}

// Upgrade Text node to include line height and character spacing, which were previously hardcoded to 1, from https://github.com/GraphiteEditor/Graphite/pull/2016
if reference == "Text" && inputs_count == 4 {
if reference == "Text" && inputs_count != 8 {
let node_definition = resolve_document_node_type(reference).unwrap();
let document_node = node_definition.default_node_template().document_node;
document.network_interface.replace_implementation(node_id, &[], document_node.implementation.clone());
Expand All @@ -541,12 +541,34 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), &[]);
document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[2].clone(), &[]);
document.network_interface.set_input(&InputConnector::node(*node_id, 3), old_inputs[3].clone(), &[]);
document
.network_interface
.set_input(&InputConnector::node(*node_id, 4), NodeInput::value(TaggedValue::F64(1.), false), &[]);
document
.network_interface
.set_input(&InputConnector::node(*node_id, 5), NodeInput::value(TaggedValue::F64(1.), false), &[]);
document.network_interface.set_input(
&InputConnector::node(*node_id, 4),
if inputs_count == 6 {
old_inputs[4].clone()
} else {
NodeInput::value(TaggedValue::F64(TypesettingConfig::default().line_height_ratio), false)
},
&[],
);
document.network_interface.set_input(
&InputConnector::node(*node_id, 5),
if inputs_count == 6 {
old_inputs[5].clone()
} else {
NodeInput::value(TaggedValue::F64(TypesettingConfig::default().character_spacing), false)
},
&[],
);
document.network_interface.set_input(
&InputConnector::node(*node_id, 6),
NodeInput::value(TaggedValue::OptionalF64(TypesettingConfig::default().max_width), false),
&[],
);
document.network_interface.set_input(
&InputConnector::node(*node_id, 7),
NodeInput::value(TaggedValue::OptionalF64(TypesettingConfig::default().max_height), false),
&[],
);
}

// Upgrade Sine, Cosine, and Tangent nodes to include a boolean input for whether the output should be in radians, which was previously the only option but is now not the default
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use crate::messages::prelude::*;
use bezier_rs::Subpath;
use graph_craft::document::{value::TaggedValue, NodeId, NodeInput};
use graphene_core::raster::{BlendMode, ImageFrame};
use graphene_core::text::Font;
use graphene_core::text::{Font, TypesettingConfig};
use graphene_core::vector::style::Gradient;
use graphene_core::vector::PointId;
use graphene_core::Color;
Expand Down Expand Up @@ -127,16 +127,25 @@ pub fn get_text_id(layer: LayerNodeIdentifier, network_interface: &NodeNetworkIn
}

/// Gets properties from the Text node
pub fn get_text(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<(&String, &Font, f64, f64, f64)> {
pub fn get_text(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<(&String, &Font, TypesettingConfig)> {
let inputs = NodeGraphLayer::new(layer, network_interface).find_node_inputs("Text")?;

let Some(TaggedValue::String(text)) = &inputs[1].as_value() else { return None };
let Some(TaggedValue::Font(font)) = &inputs[2].as_value() else { return None };
let Some(&TaggedValue::F64(font_size)) = inputs[3].as_value() else { return None };
let Some(&TaggedValue::F64(line_height_ratio)) = inputs[4].as_value() else { return None };
let Some(&TaggedValue::F64(character_spacing)) = inputs[5].as_value() else { return None };

Some((text, font, font_size, line_height_ratio, character_spacing))
let Some(&TaggedValue::OptionalF64(max_width)) = inputs[6].as_value() else { return None };
let Some(&TaggedValue::OptionalF64(max_height)) = inputs[7].as_value() else { return None };

let typesetting = TypesettingConfig {
font_size,
line_height_ratio,
max_width,
character_spacing,
max_height,
};
Some((text, font, typesetting))
}

pub fn get_stroke_width(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<f64> {
Expand Down
Loading

0 comments on commit 6635754

Please sign in to comment.