Skip to content

Commit f053464

Browse files
authored
Make TextBox placeholder translatable (#1908)
This makes the `TextBox` placeholder a `LabelText` which can be initialized with a `LocalizedString` and thus translated. One limitation is that the `LabelText` is bound to the same `Data` type parameter as the lensed `TextBox`, so it is not possible currently to use translation parameters from the broader application state.
1 parent ac34d2c commit f053464

File tree

2 files changed

+45
-25
lines changed

2 files changed

+45
-25
lines changed

druid/examples/textbox.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ const EXPLAINER: &str = "\
3434
This example demonstrates some of the possible configurations \
3535
of the TextBox widget.\n\
3636
The top textbox allows a single line of input, with horizontal scrolling \
37-
but no scrollbars. The bottom textbox allows mutliple lines of text, wrapping \
37+
but no scrollbars. The bottom textbox allows multiple lines of text, wrapping \
3838
words to fit the width, and allowing vertical scrolling when it runs out \
3939
of room to grow vertically.";
4040

druid/src/widget/textbox.rs

Lines changed: 44 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,12 @@ use crate::text::{
2525
use crate::widget::prelude::*;
2626
use crate::widget::{Padding, Scroll, WidgetWrapper};
2727
use crate::{
28-
theme, Color, Command, FontDescriptor, HotKey, KeyEvent, KeyOrValue, Point, Rect, SysMods,
29-
TextAlignment, TimerToken, Vec2,
28+
theme, ArcStr, Color, Command, FontDescriptor, HotKey, KeyEvent, KeyOrValue, Point, Rect,
29+
SysMods, TextAlignment, TimerToken, Vec2,
3030
};
3131

32+
use super::LabelText;
33+
3234
const CURSOR_BLINK_DURATION: Duration = Duration::from_millis(500);
3335
const MAC_OR_LINUX: bool = cfg!(any(target_os = "macos", target_os = "linux"));
3436

@@ -47,7 +49,8 @@ const SCROLL_TO_INSETS: Insets = Insets::uniform_xy(40.0, 0.0);
4749
/// [`Formatter`]: crate::text::format::Formatter
4850
/// [`ValueTextBox`]: super::ValueTextBox
4951
pub struct TextBox<T> {
50-
placeholder: TextLayout<String>,
52+
placeholder_text: LabelText<T>,
53+
placeholder_layout: TextLayout<ArcStr>,
5154
inner: Scroll<T, Padding<T, TextComponent<T>>>,
5255
scroll_to_selection_after_layout: bool,
5356
multiline: bool,
@@ -70,8 +73,9 @@ pub struct TextBox<T> {
7073
impl<T: EditableText + TextStorage> TextBox<T> {
7174
/// Create a new TextBox widget.
7275
pub fn new() -> Self {
73-
let mut placeholder = TextLayout::from_text("");
74-
placeholder.set_text_color(theme::PLACEHOLDER_COLOR);
76+
let mut placeholder_layout = TextLayout::new();
77+
placeholder_layout.set_text_color(theme::PLACEHOLDER_COLOR);
78+
let placeholder_text = "".into();
7579
let mut scroll = Scroll::new(Padding::new(
7680
theme::TEXTBOX_INSETS,
7781
TextComponent::default(),
@@ -81,7 +85,8 @@ impl<T: EditableText + TextStorage> TextBox<T> {
8185
Self {
8286
inner: scroll,
8387
scroll_to_selection_after_layout: false,
84-
placeholder,
88+
placeholder_text,
89+
placeholder_layout,
8590
multiline: false,
8691
was_focused_from_click: false,
8792
cursor_on: false,
@@ -116,12 +121,6 @@ impl<T: EditableText + TextStorage> TextBox<T> {
116121
}
117122

118123
impl<T> TextBox<T> {
119-
/// Builder-style method to set the `TextBox`'s placeholder text.
120-
pub fn with_placeholder(mut self, placeholder: impl Into<String>) -> Self {
121-
self.placeholder.set_text(placeholder.into());
122-
self
123-
}
124-
125124
/// Builder-style method for setting the text size.
126125
///
127126
/// The argument can be either an `f64` or a [`Key<f64>`].
@@ -178,11 +177,6 @@ impl<T> TextBox<T> {
178177
self
179178
}
180179

181-
/// Set the `TextBox`'s placeholder text.
182-
pub fn set_placeholder(&mut self, placeholder: impl Into<String>) {
183-
self.placeholder.set_text(placeholder.into());
184-
}
185-
186180
/// Set the text size.
187181
///
188182
/// The argument can be either an `f64` or a [`Key<f64>`].
@@ -199,7 +193,7 @@ impl<T> TextBox<T> {
199193
.borrow_mut()
200194
.layout
201195
.set_text_size(size.clone());
202-
self.placeholder.set_text_size(size);
196+
self.placeholder_layout.set_text_size(size);
203197
}
204198

205199
/// Set the font.
@@ -217,7 +211,7 @@ impl<T> TextBox<T> {
217211
}
218212
let font = font.into();
219213
self.text_mut().borrow_mut().layout.set_font(font.clone());
220-
self.placeholder.set_font(font);
214+
self.placeholder_layout.set_font(font);
221215
}
222216

223217
/// Set the [`TextAlignment`] for this `TextBox``.
@@ -275,6 +269,21 @@ impl<T> TextBox<T> {
275269
}
276270
}
277271

272+
impl<T: Data> TextBox<T> {
273+
/// Builder-style method to set the `TextBox`'s placeholder text.
274+
pub fn with_placeholder(mut self, placeholder: impl Into<LabelText<T>>) -> Self {
275+
self.set_placeholder(placeholder);
276+
self
277+
}
278+
279+
/// Set the `TextBox`'s placeholder text.
280+
pub fn set_placeholder(&mut self, placeholder: impl Into<LabelText<T>>) {
281+
self.placeholder_text = placeholder.into();
282+
self.placeholder_layout
283+
.set_text(self.placeholder_text.display_text());
284+
}
285+
}
286+
278287
impl<T> TextBox<T> {
279288
/// An immutable reference to the inner [`TextComponent`].
280289
///
@@ -466,6 +475,9 @@ impl<T: TextStorage + EditableText> Widget<T> for TextBox<T> {
466475
fn lifecycle(&mut self, ctx: &mut LifeCycleCtx, event: &LifeCycle, data: &T, env: &Env) {
467476
match event {
468477
LifeCycle::WidgetAdded => {
478+
if matches!(event, LifeCycle::WidgetAdded) {
479+
self.placeholder_text.resolve(data, env);
480+
}
469481
ctx.register_text_input(self.text().input_handler());
470482
}
471483
LifeCycle::BuildFocusChain => {
@@ -505,8 +517,16 @@ impl<T: TextStorage + EditableText> Widget<T> for TextBox<T> {
505517

506518
#[instrument(name = "TextBox", level = "trace", skip(self, ctx, old, data, env))]
507519
fn update(&mut self, ctx: &mut UpdateCtx, old: &T, data: &T, env: &Env) {
520+
let placeholder_changed = self.placeholder_text.resolve(data, env);
521+
if placeholder_changed {
522+
let new_text = self.placeholder_text.display_text();
523+
self.placeholder_layout.set_text(new_text);
524+
}
525+
508526
self.inner.update(ctx, old, data, env);
509-
if ctx.env_changed() && self.placeholder.needs_rebuild_after_update(ctx) {
527+
if placeholder_changed
528+
|| (ctx.env_changed() && self.placeholder_layout.needs_rebuild_after_update(ctx))
529+
{
510530
ctx.request_layout();
511531
}
512532
if self.text().can_write() {
@@ -525,14 +545,14 @@ impl<T: TextStorage + EditableText> Widget<T> for TextBox<T> {
525545
let min_width = env.get(theme::WIDE_WIDGET_WIDTH);
526546
let textbox_insets = env.get(theme::TEXTBOX_INSETS);
527547

528-
self.placeholder.rebuild_if_needed(ctx.text(), env);
548+
self.placeholder_layout.rebuild_if_needed(ctx.text(), env);
529549
let min_size = bc.constrain((min_width, 0.0));
530550
let child_bc = BoxConstraints::new(min_size, bc.max());
531551

532552
let size = self.inner.layout(ctx, &child_bc, data, env);
533553

534554
let text_metrics = if !self.text().can_read() || data.is_empty() {
535-
self.placeholder.layout_metrics()
555+
self.placeholder_layout.layout_metrics()
536556
} else {
537557
self.text().borrow().layout.layout_metrics()
538558
};
@@ -586,7 +606,7 @@ impl<T: TextStorage + EditableText> Widget<T> for TextBox<T> {
586606
if !data.is_empty() {
587607
self.inner.paint(ctx, data, env);
588608
} else {
589-
let text_width = self.placeholder.layout_metrics().size.width;
609+
let text_width = self.placeholder_layout.layout_metrics().size.width;
590610
let extra_width = (size.width - text_width - textbox_insets.x_value()).max(0.);
591611
let alignment = self.text().borrow().text_alignment();
592612
// alignment is only used for single-line text boxes
@@ -599,7 +619,7 @@ impl<T: TextStorage + EditableText> Widget<T> for TextBox<T> {
599619
// clip when we draw the placeholder, since it isn't in a clipbox
600620
ctx.with_save(|ctx| {
601621
ctx.clip(clip_rect);
602-
self.placeholder
622+
self.placeholder_layout
603623
.draw(ctx, (textbox_insets.x0 + x_offset, textbox_insets.y0));
604624
})
605625
}

0 commit comments

Comments
 (0)