Skip to content

Commit 5291c12

Browse files
committed
Use ArcStr in LocalizedString and LabelText
This lets us share our text between druid and the piet layout object, and starts the process of moving us away from using String where possible. This was motivated by explorations around making Label optionally work as a `Widget<ArcStr>`, and using lens-like adapters to generate that text from some other data types as needed; this would be a small improvement of the label API.
1 parent 8426d38 commit 5291c12

File tree

6 files changed

+69
-43
lines changed

6 files changed

+69
-43
lines changed

CHANGELOG.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ You can find its changes [documented below](#060---2020-06-01).
3434
- `TextLayout` type simplifies drawing text ([#1182] by [@cmyr])
3535
- Implementation of `Data` trait for `i128` and `u128` primitive data types. ([#1214] by [@koutoftimer])
3636
- `LineBreaking` enum allows configuration of label line-breaking ([#1195] by [@cmyr])
37-
- `TextAlignment` support in `TextLayout` and `Label` ([#1210] by [@cmyr])`
37+
- `TextAlignment` support in `TextLayout` and `Label` ([#1210] by [@cmyr])
3838
- `UpdateCtx` gets `env_changed` and `env_key_changed` methods ([#1207] by [@cmyr])
3939
- `Button::from_label` to construct a `Button` with a provided `Label`. ([#1226] by [@ForLoveOfCats])
4040
- Lens: Added Unit lens for type erased / display only widgets that do not need data. ([#1232] by [@rjwittams])
@@ -57,7 +57,7 @@ You can find its changes [documented below](#060---2020-06-01).
5757
- Moved `Target` parameter from `submit_command` to `Command::new` and `Command::to`. ([#1185] by [@finnerale])
5858
- `Movement::RightOfLine` to `Movement::NextLineBreak`, and `Movement::LeftOfLine` to `Movement::PrecedingLineBreak`. ([#1092] by [@sysint64])
5959
- `AnimFrame` was moved from `lifecycle` to `event` ([#1155] by [@jneem])
60-
- Contexts' `text()` methods return `&mut PietText` instead of cloning ([#1205] by [@cmyr])
60+
- `LocalizedString` and `LabelText` use `ArcStr` instead of String ([#1245] by [@cmyr])
6161
- `LensWrap` widget moved into widget module ([#1251] by [@cmyr])
6262

6363
### Deprecated
@@ -462,6 +462,7 @@ Last release without a changelog :(
462462
[#1220]: https://github.com/linebender/druid/pull/1220
463463
[#1238]: https://github.com/linebender/druid/pull/1238
464464
[#1241]: https://github.com/linebender/druid/pull/1241
465+
[#1245]: https://github.com/linebender/druid/pull/1245
465466
[#1251]: https://github.com/linebender/druid/pull/1251
466467

467468
[Unreleased]: https://github.com/linebender/druid/compare/v0.6.0...master

druid/src/app.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,7 @@ impl<T: Data> WindowDesc<T> {
281281

282282
builder.set_window_state(self.state);
283283

284-
builder.set_title(self.title.display_text());
284+
builder.set_title(self.title.display_text().to_string());
285285
if let Some(menu) = platform_menu {
286286
builder.set_menu(menu);
287287
}

druid/src/localization.rs

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,7 @@ use std::{fs, io};
4040

4141
use log::{debug, error, warn};
4242

43-
use crate::env::Env;
44-
use crate::shell::Application;
43+
use crate::{Application, ArcStr, Env};
4544

4645
use fluent_bundle::{
4746
FluentArgs, FluentBundle, FluentError, FluentMessage, FluentResource, FluentValue,
@@ -91,9 +90,9 @@ struct ArgSource<T>(ArgClosure<T>);
9190
#[derive(Debug, Clone)]
9291
pub struct LocalizedString<T> {
9392
pub(crate) key: &'static str,
94-
placeholder: Option<String>,
93+
placeholder: Option<ArcStr>,
9594
args: Option<Vec<(&'static str, ArgSource<T>)>>,
96-
resolved: Option<String>,
95+
resolved: Option<ArcStr>,
9796
resolved_lang: Option<LanguageIdentifier>,
9897
}
9998

@@ -253,7 +252,7 @@ impl L10nManager {
253252
&'args self,
254253
key: &str,
255254
args: impl Into<Option<&'args FluentArgs<'args>>>,
256-
) -> Option<String> {
255+
) -> Option<ArcStr> {
257256
let args = args.into();
258257
let value = match self
259258
.current_bundle
@@ -281,10 +280,11 @@ impl L10nManager {
281280
result
282281
.chars()
283282
.filter(|c| c != &START_ISOLATE && c != &END_ISOLATE)
284-
.collect(),
283+
.collect::<String>()
284+
.into(),
285285
)
286286
} else {
287-
Some(result)
287+
Some(result.into())
288288
}
289289
}
290290
//TODO: handle locale change
@@ -305,18 +305,18 @@ impl<T> LocalizedString<T> {
305305
/// Add a placeholder value. This will be used if localization fails.
306306
///
307307
/// This is intended for use during prototyping.
308-
pub fn with_placeholder(mut self, placeholder: impl Into<String>) -> Self {
308+
pub fn with_placeholder(mut self, placeholder: impl Into<ArcStr>) -> Self {
309309
self.placeholder = Some(placeholder.into());
310310
self
311311
}
312312

313313
/// Return the localized value for this string, or the placeholder, if
314314
/// the localization is missing, or the key if there is no placeholder.
315-
pub fn localized_str(&self) -> &str {
315+
pub fn localized_str(&self) -> ArcStr {
316316
self.resolved
317-
.as_deref()
318-
.or_else(|| self.placeholder.as_deref())
319-
.unwrap_or(self.key)
317+
.clone()
318+
.or_else(|| self.placeholder.clone())
319+
.unwrap_or_else(|| self.key.into())
320320
}
321321

322322
/// Add a named argument and a corresponding closure. This closure

druid/src/menu.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -347,7 +347,7 @@ impl<T: Data> MenuDesc<T> {
347347
item.platform_id = MenuItemId::next();
348348
menu.add_item(
349349
item.platform_id.as_u32(),
350-
item.title.localized_str(),
350+
&item.title.localized_str(),
351351
item.hotkey.as_ref(),
352352
item.enabled,
353353
item.selected,

druid/src/widget/label.rs

Lines changed: 51 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -17,25 +17,36 @@
1717
use crate::piet::{Color, PietText};
1818
use crate::widget::prelude::*;
1919
use crate::{
20-
BoxConstraints, Data, FontDescriptor, KeyOrValue, LocalizedString, Point, Size, TextAlignment,
21-
TextLayout,
20+
ArcStr, BoxConstraints, Data, FontDescriptor, KeyOrValue, LocalizedString, Point, Size,
21+
TextAlignment, TextLayout,
2222
};
2323

2424
// added padding between the edges of the widget and the text.
2525
const LABEL_X_PADDING: f64 = 2.0;
2626

2727
/// The text for the label.
2828
///
29-
/// This can be one of three things; either a `String`, a [`LocalizedString`],
29+
/// This can be one of three things; either a [`ArcStr`], a [`LocalizedString`],
3030
/// or a closure with the signature, `Fn(&T, &Env) -> String`, where `T` is
31-
/// the `Data` at this point in the tree.
31+
/// the [`Data`] at this point in the tree.
3232
///
33+
/// [`ArcStr`]: ../type.ArcStr.html
3334
/// [`LocalizedString`]: ../struct.LocalizedString.html
35+
/// [`Data`]: ../trait.Data.html
3436
pub enum LabelText<T> {
3537
/// Localized string that will be resolved through `Env`.
3638
Localized(LocalizedString<T>),
37-
/// Specific text
38-
Specific(String),
39+
/// Static text.
40+
Static {
41+
/// The text.
42+
string: ArcStr,
43+
/// Whether or not the `resolved` method has been called yet.
44+
///
45+
/// We want to return `true` from that method when it is first called,
46+
/// so that callers will know to retrieve the text. This matches
47+
/// the behaviour of the other variants.
48+
resolved: bool,
49+
},
3950
/// The provided closure is called on update, and its return
4051
/// value is used as the text for the label.
4152
Dynamic(Dynamic<T>),
@@ -45,13 +56,13 @@ pub enum LabelText<T> {
4556
#[doc(hidden)]
4657
pub struct Dynamic<T> {
4758
f: Box<dyn Fn(&T, &Env) -> String>,
48-
resolved: String,
59+
resolved: ArcStr,
4960
}
5061

5162
/// A label that draws some text.
5263
///
5364
/// A label is the easiest way to display text in Druid. A label is instantiated
54-
/// with some [`LabelText`] type, such as a `String` or a [`LocalizedString`],
65+
/// with some [`LabelText`] type, such as an [`ArcStr`] or a [`LocalizedString`],
5566
/// and also has methods for setting the default font, font-size, text color,
5667
/// and other attributes.
5768
///
@@ -80,7 +91,7 @@ pub struct Dynamic<T> {
8091
/// # let _ = SizedBox::<()>::new(important_label);
8192
/// ```
8293
///
83-
///
94+
/// [`ArcStr`]: ../type.ArcStr.html
8495
/// [`LabelText`]: struct.LabelText.html
8596
/// [`LocalizedString`]: ../struct.LocalizedString.html
8697
/// [`draw_at`]: #method.draw_at
@@ -218,11 +229,6 @@ impl<T: Data> Label<T> {
218229
self.needs_rebuild = true;
219230
}
220231

221-
/// Returns this label's current text.
222-
pub fn text(&self) -> &str {
223-
self.text.display_text()
224-
}
225-
226232
/// Set the text color.
227233
///
228234
/// The argument can be either a `Color` or a [`Key<Color>`].
@@ -310,8 +316,8 @@ impl<T: Data> Label<T> {
310316
impl<T> Dynamic<T> {
311317
fn resolve(&mut self, data: &T, env: &Env) -> bool {
312318
let new = (self.f)(data, env);
313-
let changed = new != self.resolved;
314-
self.resolved = new;
319+
let changed = new.as_str() != &*self.resolved;
320+
self.resolved = new.into();
315321
changed
316322
}
317323
}
@@ -320,18 +326,18 @@ impl<T: Data> LabelText<T> {
320326
/// Call callback with the text that should be displayed.
321327
pub fn with_display_text<V>(&self, mut cb: impl FnMut(&str) -> V) -> V {
322328
match self {
323-
LabelText::Specific(s) => cb(s.as_str()),
324-
LabelText::Localized(s) => cb(s.localized_str()),
325-
LabelText::Dynamic(s) => cb(s.resolved.as_str()),
329+
LabelText::Static { string, .. } => cb(&string),
330+
LabelText::Localized(s) => cb(&s.localized_str()),
331+
LabelText::Dynamic(s) => cb(&s.resolved),
326332
}
327333
}
328334

329335
/// Return the current resolved text.
330-
pub fn display_text(&self) -> &str {
336+
pub fn display_text(&self) -> ArcStr {
331337
match self {
332-
LabelText::Specific(s) => s.as_str(),
338+
LabelText::Static { string, .. } => string.clone(),
333339
LabelText::Localized(s) => s.localized_str(),
334-
LabelText::Dynamic(s) => s.resolved.as_str(),
340+
LabelText::Dynamic(s) => s.resolved.clone(),
335341
}
336342
}
337343

@@ -341,7 +347,11 @@ impl<T: Data> LabelText<T> {
341347
/// Returns `true` if the string has changed.
342348
pub fn resolve(&mut self, data: &T, env: &Env) -> bool {
343349
match self {
344-
LabelText::Specific(_) => false,
350+
LabelText::Static { resolved, .. } => {
351+
let is_first_call = !*resolved;
352+
*resolved = true;
353+
is_first_call
354+
}
345355
LabelText::Localized(s) => s.resolve(data, env),
346356
LabelText::Dynamic(s) => s.resolve(data, env),
347357
}
@@ -389,13 +399,28 @@ impl<T: Data> Widget<T> for Label<T> {
389399

390400
impl<T> From<String> for LabelText<T> {
391401
fn from(src: String) -> LabelText<T> {
392-
LabelText::Specific(src)
402+
LabelText::Static {
403+
string: src.into(),
404+
resolved: false,
405+
}
393406
}
394407
}
395408

396409
impl<T> From<&str> for LabelText<T> {
397410
fn from(src: &str) -> LabelText<T> {
398-
LabelText::Specific(src.to_string())
411+
LabelText::Static {
412+
string: src.into(),
413+
resolved: false,
414+
}
415+
}
416+
}
417+
418+
impl<T> From<ArcStr> for LabelText<T> {
419+
fn from(string: ArcStr) -> LabelText<T> {
420+
LabelText::Static {
421+
string,
422+
resolved: false,
423+
}
399424
}
400425
}
401426

@@ -410,7 +435,7 @@ impl<T, F: Fn(&T, &Env) -> String + 'static> From<F> for LabelText<T> {
410435
let f = Box::new(src);
411436
LabelText::Dynamic(Dynamic {
412437
f,
413-
resolved: String::default(),
438+
resolved: ArcStr::from(""),
414439
})
415440
}
416441
}

druid/src/window.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -422,7 +422,7 @@ impl<T: Data> Window<T> {
422422

423423
pub(crate) fn update_title(&mut self, data: &T, env: &Env) {
424424
if self.title.resolve(data, env) {
425-
self.handle.set_title(self.title.display_text());
425+
self.handle.set_title(&self.title.display_text());
426426
}
427427
}
428428

0 commit comments

Comments
 (0)