Skip to content

Commit 85021fd

Browse files
BKSalmanKeavon
andauthored
Add text alignment to the Text node (#2920)
* Add text alignment to Text node * Lots of renames and improvements * Add text alignment to the Text tool --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
1 parent 91156d2 commit 85021fd

File tree

14 files changed

+92
-28
lines changed

14 files changed

+92
-28
lines changed

editor/src/messages/frontend/frontend_message.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use crate::messages::tool::utility_types::HintData;
1010
use graph_craft::document::NodeId;
1111
use graphene_std::raster::Image;
1212
use graphene_std::raster::color::Color;
13-
use graphene_std::text::Font;
13+
use graphene_std::text::{Font, TextAlign};
1414

1515
#[impl_message(Message, Frontend)]
1616
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
@@ -38,6 +38,7 @@ pub enum FrontendMessage {
3838
max_width: Option<f64>,
3939
#[serde(rename = "maxHeight")]
4040
max_height: Option<f64>,
41+
align: TextAlign,
4142
},
4243
DisplayEditableTextboxTransform {
4344
transform: [f64; 6],

editor/src/messages/portfolio/document/graph_operation/utility_types.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,7 @@ impl<'a> ModifyInputsContext<'a> {
198198
Some(NodeInput::value(TaggedValue::OptionalF64(typesetting.max_width), false)),
199199
Some(NodeInput::value(TaggedValue::OptionalF64(typesetting.max_height), false)),
200200
Some(NodeInput::value(TaggedValue::F64(typesetting.tilt), false)),
201+
Some(NodeInput::value(TaggedValue::TextAlign(typesetting.align), false)),
201202
]);
202203

203204
let text_id = NodeId::new();

editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1304,6 +1304,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
13041304
NodeInput::value(TaggedValue::OptionalF64(TypesettingConfig::default().max_width), false),
13051305
NodeInput::value(TaggedValue::OptionalF64(TypesettingConfig::default().max_height), false),
13061306
NodeInput::value(TaggedValue::F64(TypesettingConfig::default().tilt), false),
1307+
NodeInput::value(TaggedValue::TextAlign(text::TextAlign::default()), false),
13071308
NodeInput::value(TaggedValue::Bool(false), false),
13081309
],
13091310
..Default::default()
@@ -1337,7 +1338,6 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
13371338
"TODO",
13381339
WidgetOverride::Number(NumberInputSettings {
13391340
unit: Some(" px".to_string()),
1340-
min: Some(0.),
13411341
step: Some(0.1),
13421342
..Default::default()
13431343
}),
@@ -1372,6 +1372,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
13721372
..Default::default()
13731373
}),
13741374
),
1375+
InputMetadata::with_name_description_override("Align", "TODO", WidgetOverride::Custom("text_align".to_string())),
13751376
("Per-Glyph Instances", "Splits each text glyph into its own instance, i.e. row in the table of vector data.").into(),
13761377
],
13771378
output_names: vec!["Vector".to_string()],
@@ -2404,6 +2405,13 @@ fn static_input_properties() -> InputProperties {
24042405
)])
24052406
}),
24062407
);
2408+
map.insert(
2409+
"text_align".to_string(),
2410+
Box::new(|node_id, index, context| {
2411+
let choices = enum_choice::<text::TextAlign>().for_socket(ParameterWidgetsInfo::new(node_id, index, true, context)).property_row();
2412+
Ok(vec![choices])
2413+
}),
2414+
);
24072415
map
24082416
}
24092417

editor/src/messages/portfolio/document/node_graph/node_properties.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ use graphene_std::raster::{
2020
SelectiveColorChoice,
2121
};
2222
use graphene_std::raster_types::{CPU, GPU, RasterDataTable};
23-
use graphene_std::text::Font;
23+
use graphene_std::text::{Font, TextAlign};
2424
use graphene_std::transform::{Footprint, ReferencePoint, Transform};
2525
use graphene_std::vector::VectorDataTable;
2626
use graphene_std::vector::misc::GridType;
@@ -223,6 +223,7 @@ pub(crate) fn property_from_type(
223223
Some(x) if x == TypeId::of::<StrokeAlign>() => enum_choice::<StrokeAlign>().for_socket(default_info).property_row(),
224224
Some(x) if x == TypeId::of::<PaintOrder>() => enum_choice::<PaintOrder>().for_socket(default_info).property_row(),
225225
Some(x) if x == TypeId::of::<ArcType>() => enum_choice::<ArcType>().for_socket(default_info).property_row(),
226+
Some(x) if x == TypeId::of::<TextAlign>() => enum_choice::<TextAlign>().for_socket(default_info).property_row(),
226227
Some(x) if x == TypeId::of::<MergeByDistanceAlgorithm>() => enum_choice::<MergeByDistanceAlgorithm>().for_socket(default_info).property_row(),
227228
Some(x) if x == TypeId::of::<PointSpacingType>() => enum_choice::<PointSpacingType>().for_socket(default_info).property_row(),
228229
Some(x) if x == TypeId::of::<BooleanOperation>() => enum_choice::<BooleanOperation>().for_socket(default_info).property_row(),
@@ -2018,7 +2019,7 @@ pub mod choice {
20182019
let updater = updater_factory();
20192020
let committer = committer_factory();
20202021
let entry = RadioEntryData::new(var_meta.name).on_update(move |_| updater(item)).on_commit(committer);
2021-
match (var_meta.icon.as_deref(), var_meta.docstring.as_deref()) {
2022+
match (var_meta.icon, var_meta.docstring) {
20222023
(None, None) => entry.label(var_meta.label),
20232024
(None, Some(doc)) => entry.label(var_meta.label).tooltip(doc),
20242025
(Some(icon), None) => entry.icon(icon).tooltip(var_meta.label),

editor/src/messages/portfolio/document_migration.rs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use glam::IVec2;
1010
use graph_craft::document::DocumentNode;
1111
use graph_craft::document::{DocumentNodeImplementation, NodeInput, value::TaggedValue};
1212
use graphene_std::ProtoNodeIdentifier;
13-
use graphene_std::text::TypesettingConfig;
13+
use graphene_std::text::{TextAlign, TypesettingConfig};
1414
use graphene_std::uuid::NodeId;
1515
use graphene_std::vector::style::{PaintOrder, StrokeAlign};
1616
use graphene_std::vector::{VectorData, VectorDataTable};
@@ -646,7 +646,7 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
646646
}
647647

648648
// Upgrade Text node to include line height and character spacing, which were previously hardcoded to 1, from https://github.com/GraphiteEditor/Graphite/pull/2016
649-
if reference == "Text" && inputs_count != 10 {
649+
if reference == "Text" && inputs_count != 11 {
650650
let mut template = resolve_document_node_type(reference)?.default_node_template();
651651
document.network_interface.replace_implementation(node_id, network_path, &mut template);
652652
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut template)?;
@@ -702,8 +702,17 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
702702
);
703703
document.network_interface.set_input(
704704
&InputConnector::node(*node_id, 9),
705-
if inputs_count >= 10 {
705+
if inputs_count >= 11 {
706706
old_inputs[9].clone()
707+
} else {
708+
NodeInput::value(TaggedValue::TextAlign(TextAlign::default()), false)
709+
},
710+
network_path,
711+
);
712+
document.network_interface.set_input(
713+
&InputConnector::node(*node_id, 10),
714+
if inputs_count >= 11 {
715+
old_inputs[10].clone()
707716
} else {
708717
NodeInput::value(TaggedValue::Bool(false), false)
709718
},

editor/src/messages/tool/common_functionality/graph_modification_utils.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -368,9 +368,8 @@ pub fn get_text(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInter
368368
let Some(&TaggedValue::OptionalF64(max_width)) = inputs[6].as_value() else { return None };
369369
let Some(&TaggedValue::OptionalF64(max_height)) = inputs[7].as_value() else { return None };
370370
let Some(&TaggedValue::F64(tilt)) = inputs[8].as_value() else { return None };
371-
let Some(TaggedValue::Bool(per_glyph_instances)) = &inputs[9].as_value() else {
372-
return None;
373-
};
371+
let Some(&TaggedValue::TextAlign(align)) = inputs[9].as_value() else { return None };
372+
let Some(&TaggedValue::Bool(per_glyph_instances)) = inputs[10].as_value() else { return None };
374373

375374
let typesetting = TypesettingConfig {
376375
font_size,
@@ -379,8 +378,9 @@ pub fn get_text(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInter
379378
character_spacing,
380379
max_height,
381380
tilt,
381+
align,
382382
};
383-
Some((text, font, typesetting, *per_glyph_instances))
383+
Some((text, font, typesetting, per_glyph_instances))
384384
}
385385

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

editor/src/messages/tool/tool_messages/brush_tool.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,11 @@ impl LayoutHolder for BrushTool {
144144

145145
let draw_mode_entries: Vec<_> = [DrawMode::Draw, DrawMode::Erase, DrawMode::Restore]
146146
.into_iter()
147-
.map(|draw_mode| RadioEntryData::new(format!("{draw_mode:?}")).on_update(move |_| BrushToolMessage::UpdateOptions(BrushToolMessageOptionsUpdate::DrawMode(draw_mode)).into()))
147+
.map(|draw_mode| {
148+
RadioEntryData::new(format!("{draw_mode:?}"))
149+
.label(format!("{draw_mode:?}"))
150+
.on_update(move |_| BrushToolMessage::UpdateOptions(BrushToolMessageOptionsUpdate::DrawMode(draw_mode)).into())
151+
})
148152
.collect();
149153
widgets.push(RadioInput::new(draw_mode_entries).selected_index(Some(self.options.draw_mode as u32)).widget_holder());
150154

editor/src/messages/tool/tool_messages/text_tool.rs

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use graph_craft::document::value::TaggedValue;
1717
use graph_craft::document::{NodeId, NodeInput};
1818
use graphene_std::Color;
1919
use graphene_std::renderer::Quad;
20-
use graphene_std::text::{Font, FontCache, TypesettingConfig, lines_clipping, load_font};
20+
use graphene_std::text::{Font, FontCache, TextAlign, TypesettingConfig, lines_clipping, load_font};
2121
use graphene_std::vector::style::Fill;
2222

2323
#[derive(Default, ExtractField)]
@@ -35,6 +35,7 @@ pub struct TextOptions {
3535
font_style: String,
3636
fill: ToolColorOptions,
3737
tilt: f64,
38+
align: TextAlign,
3839
}
3940

4041
impl Default for TextOptions {
@@ -47,6 +48,7 @@ impl Default for TextOptions {
4748
font_style: graphene_std::consts::DEFAULT_FONT_STYLE.into(),
4849
fill: ToolColorOptions::new_primary(),
4950
tilt: 0.,
51+
align: TextAlign::default(),
5052
}
5153
}
5254
}
@@ -78,7 +80,7 @@ pub enum TextOptionsUpdate {
7880
Font { family: String, style: String },
7981
FontSize(f64),
8082
LineHeightRatio(f64),
81-
CharacterSpacing(f64),
83+
Align(TextAlign),
8284
WorkingColors(Option<Color>, Option<Color>),
8385
}
8486

@@ -131,14 +133,15 @@ fn create_text_widgets(tool: &TextTool) -> Vec<WidgetHolder> {
131133
.step(0.1)
132134
.on_update(|number_input: &NumberInput| TextToolMessage::UpdateOptions(TextOptionsUpdate::LineHeightRatio(number_input.value.unwrap())).into())
133135
.widget_holder();
134-
let character_spacing = NumberInput::new(Some(tool.options.character_spacing))
135-
.label("Char. Spacing")
136-
.int()
137-
.min(0.)
138-
.max((1_u64 << f64::MANTISSA_DIGITS) as f64)
139-
.step(0.1)
140-
.on_update(|number_input: &NumberInput| TextToolMessage::UpdateOptions(TextOptionsUpdate::CharacterSpacing(number_input.value.unwrap())).into())
141-
.widget_holder();
136+
let align_entries: Vec<_> = [TextAlign::Left, TextAlign::Center, TextAlign::Right, TextAlign::JustifyLeft]
137+
.into_iter()
138+
.map(|align| {
139+
RadioEntryData::new(format!("{align:?}"))
140+
.label(align.to_string())
141+
.on_update(move |_| TextToolMessage::UpdateOptions(TextOptionsUpdate::Align(align)).into())
142+
})
143+
.collect();
144+
let align = RadioInput::new(align_entries).selected_index(Some(tool.options.align as u32)).widget_holder();
142145
vec![
143146
font,
144147
Separator::new(SeparatorType::Related).widget_holder(),
@@ -148,7 +151,7 @@ fn create_text_widgets(tool: &TextTool) -> Vec<WidgetHolder> {
148151
Separator::new(SeparatorType::Related).widget_holder(),
149152
line_height_ratio,
150153
Separator::new(SeparatorType::Related).widget_holder(),
151-
character_spacing,
154+
align,
152155
]
153156
}
154157

@@ -186,7 +189,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for Text
186189
}
187190
TextOptionsUpdate::FontSize(font_size) => self.options.font_size = font_size,
188191
TextOptionsUpdate::LineHeightRatio(line_height_ratio) => self.options.line_height_ratio = line_height_ratio,
189-
TextOptionsUpdate::CharacterSpacing(character_spacing) => self.options.character_spacing = character_spacing,
192+
TextOptionsUpdate::Align(align) => self.options.align = align,
190193
TextOptionsUpdate::FillColor(color) => {
191194
self.options.fill.custom_color = color;
192195
self.options.fill.color_type = ToolColorType::Custom;
@@ -314,6 +317,7 @@ impl TextToolData {
314317
transform: editing_text.transform.to_cols_array(),
315318
max_width: editing_text.typesetting.max_width,
316319
max_height: editing_text.typesetting.max_height,
320+
align: editing_text.typesetting.align,
317321
});
318322
} else {
319323
// Check if DisplayRemoveEditableTextbox is already in the responses queue
@@ -792,6 +796,7 @@ impl Fsm for TextToolFsmState {
792796
character_spacing: tool_options.character_spacing,
793797
max_height: constraint_size.map(|size| size.y),
794798
tilt: tool_options.tilt,
799+
align: tool_options.align,
795800
},
796801
font: Font::new(tool_options.font_name.clone(), tool_options.font_style.clone()),
797802
color: tool_options.fill.active_color(),

frontend/src/components/panels/Document.svelte

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,7 @@
343343
textInput.style.lineHeight = `${displayEditableTextbox.lineHeightRatio}`;
344344
textInput.style.fontSize = `${displayEditableTextbox.fontSize}px`;
345345
textInput.style.color = displayEditableTextbox.color.toHexOptionalAlpha() || "transparent";
346+
textInput.style.textAlign = displayEditableTextbox.align;
346347
347348
textInput.oninput = () => {
348349
if (!textInput) return;
@@ -774,7 +775,6 @@
774775
.text-input {
775776
word-break: break-all;
776777
unicode-bidi: plaintext;
777-
text-align: left;
778778
}
779779
780780
.text-input div {
@@ -789,7 +789,6 @@
789789
white-space: pre-wrap;
790790
word-break: normal;
791791
unicode-bidi: plaintext;
792-
text-align: left;
793792
display: inline-block;
794793
// Workaround to force Chrome to display the flashing text entry cursor when text is empty
795794
padding-left: 1px;

frontend/src/messages.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -814,6 +814,8 @@ export class UpdateDocumentLayerStructureJs extends JsMessage {
814814
readonly dataBuffer!: DataBuffer;
815815
}
816816

817+
export type TextAlign = "Left" | "Center" | "Right" | "JustifyLeft";
818+
817819
export class DisplayEditableTextbox extends JsMessage {
818820
readonly text!: string;
819821

@@ -831,6 +833,8 @@ export class DisplayEditableTextbox extends JsMessage {
831833
readonly maxWidth!: undefined | number;
832834

833835
readonly maxHeight!: undefined | number;
836+
837+
readonly align!: TextAlign;
834838
}
835839

836840
export class DisplayEditableTextboxTransform extends JsMessage {

0 commit comments

Comments
 (0)