Struct druid::LocalizedString
source · pub struct LocalizedString<T> { /* private fields */ }
Expand description
A string that can be localized based on the current locale.
At its simplest, a LocalizedString
is a key that can be resolved
against a map of localized strings for a given locale.
Implementations§
source§impl<T> LocalizedString<T>
impl<T> LocalizedString<T>
sourcepub const fn new(key: &'static str) -> Self
pub const fn new(key: &'static str) -> Self
Create a new LocalizedString
with the given key.
Examples found in repository?
More examples
examples/textbox.rs (line 31)
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119
const WINDOW_TITLE: LocalizedString<AppState> = LocalizedString::new("Text Options");
const EXPLAINER: &str = "\
This example demonstrates some of the possible configurations \
of the TextBox widget.\n\
The top textbox allows a single line of input, with horizontal scrolling \
but no scrollbars. The bottom textbox allows multiple lines of text, wrapping \
words to fit the width, and allowing vertical scrolling when it runs out \
of room to grow vertically.";
#[derive(Clone, Data, Lens)]
struct AppState {
multi: Arc<String>,
single: Arc<String>,
}
pub fn main() {
// describe the main window
let main_window = WindowDesc::new(build_root_widget())
.title(WINDOW_TITLE)
.menu(make_menu)
.window_size((400.0, 600.0));
// create the initial app state
let initial_state = AppState {
single: "".to_string().into(),
multi: "".to_string().into(),
};
// start the application
AppLauncher::with_window(main_window)
.log_to_console()
.launch(initial_state)
.expect("Failed to launch application");
}
fn build_root_widget() -> impl Widget<AppState> {
let blurb = Label::new(EXPLAINER)
.with_line_break_mode(druid::widget::LineBreaking::WordWrap)
.padding(8.0)
.border(Color::grey(0.6), 2.0)
.rounded(5.0);
Flex::column()
.cross_axis_alignment(druid::widget::CrossAxisAlignment::Start)
.with_child(blurb)
.with_spacer(24.0)
.with_child(
TextBox::new()
.with_placeholder("Single")
.lens(AppState::single),
)
.with_default_spacer()
.with_flex_child(
TextBox::multiline()
.with_placeholder("Multi")
.lens(AppState::multi)
.expand_width(),
1.0,
)
.padding(8.0)
}
#[allow(unused_assignments, unused_mut)]
fn make_menu<T: Data>(_window: Option<WindowId>, _data: &AppState, _env: &Env) -> Menu<T> {
let mut base = Menu::empty();
#[cfg(target_os = "macos")]
{
base = base.entry(druid::platform_menus::mac::application::default())
}
#[cfg(any(
target_os = "windows",
target_os = "freebsd",
target_os = "linux",
target_os = "openbsd"
))]
{
base = base.entry(druid::platform_menus::win::file::default());
}
base.entry(
Menu::new(LocalizedString::new("common-menu-edit-menu"))
.entry(druid::platform_menus::common::undo())
.entry(druid::platform_menus::common::redo())
.separator()
.entry(druid::platform_menus::common::cut())
.entry(druid::platform_menus::common::copy())
.entry(druid::platform_menus::common::paste()),
)
}
examples/markdown_preview.rs (line 31)
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260
const WINDOW_TITLE: LocalizedString<AppState> = LocalizedString::new("Minimal Markdown");
const TEXT: &str = "*Hello* ***world***! This is a `TextBox` where you can \
use limited markdown notation, which is reflected in the \
**styling** of the `Label` on the left. ~~Strikethrough even works!~~\n\n\
If you're curious about Druid, a good place to ask questions \
and discuss development work is our [Zulip chat instance], \
in the #druid-help and #druid channels, respectively.\n\n\n\
[Zulip chat instance]: https://xi.zulipchat.com";
const SPACER_SIZE: f64 = 8.0;
const BLOCKQUOTE_COLOR: Color = Color::grey8(0x88);
const LINK_COLOR: Color = Color::rgb8(0, 0, 0xEE);
const OPEN_LINK: Selector<String> = Selector::new("druid-example.open-link");
#[derive(Clone, Data, Lens)]
struct AppState {
raw: String,
rendered: RichText,
}
/// A controller that rebuilds the preview when edits occur
struct RichTextRebuilder;
impl<W: Widget<AppState>> Controller<AppState, W> for RichTextRebuilder {
fn event(
&mut self,
child: &mut W,
ctx: &mut EventCtx,
event: &Event,
data: &mut AppState,
env: &Env,
) {
let pre_data = data.raw.to_owned();
child.event(ctx, event, data, env);
if !data.raw.same(&pre_data) {
data.rendered = rebuild_rendered_text(&data.raw);
}
}
}
struct Delegate;
impl<T: Data> AppDelegate<T> for Delegate {
fn command(
&mut self,
_ctx: &mut DelegateCtx,
_target: Target,
cmd: &Command,
_data: &mut T,
_env: &Env,
) -> Handled {
if let Some(url) = cmd.get(OPEN_LINK) {
#[cfg(not(target_arch = "wasm32"))]
open::that_in_background(url);
#[cfg(target_arch = "wasm32")]
tracing::warn!("opening link({}) not supported on web yet.", url);
Handled::Yes
} else {
Handled::No
}
}
}
pub fn main() {
// describe the main window
let main_window = WindowDesc::new(build_root_widget())
.title(WINDOW_TITLE)
.menu(make_menu)
.window_size((700.0, 600.0));
// create the initial app state
let initial_state = AppState {
raw: TEXT.to_owned(),
rendered: rebuild_rendered_text(TEXT),
};
// start the application
AppLauncher::with_window(main_window)
.log_to_console()
.delegate(Delegate)
.launch(initial_state)
.expect("Failed to launch application");
}
fn build_root_widget() -> impl Widget<AppState> {
let label = Scroll::new(
RawLabel::new()
.with_text_color(Color::BLACK)
.with_line_break_mode(LineBreaking::WordWrap)
.lens(AppState::rendered)
.expand_width()
.padding((SPACER_SIZE * 4.0, SPACER_SIZE)),
)
.vertical()
.background(Color::grey8(222))
.expand();
let textbox = TextBox::multiline()
.lens(AppState::raw)
.controller(RichTextRebuilder)
.expand()
.padding(5.0);
Split::columns(label, textbox)
}
/// Parse a markdown string and generate a `RichText` object with
/// the appropriate attributes.
fn rebuild_rendered_text(text: &str) -> RichText {
let mut current_pos = 0;
let mut builder = RichTextBuilder::new();
let mut tag_stack = Vec::new();
let parser = Parser::new_ext(text, Options::ENABLE_STRIKETHROUGH);
for event in parser {
match event {
ParseEvent::Start(tag) => {
tag_stack.push((current_pos, tag));
}
ParseEvent::Text(txt) => {
builder.push(&txt);
current_pos += txt.len();
}
ParseEvent::End(end_tag) => {
let (start_off, tag) = tag_stack
.pop()
.expect("parser does not return unbalanced tags");
assert_eq!(end_tag, tag, "mismatched tags?");
add_attribute_for_tag(
&tag,
builder.add_attributes_for_range(start_off..current_pos),
);
if add_newline_after_tag(&tag) {
builder.push("\n\n");
current_pos += 2;
}
}
ParseEvent::Code(txt) => {
builder.push(&txt).font_family(FontFamily::MONOSPACE);
current_pos += txt.len();
}
ParseEvent::Html(txt) => {
builder
.push(&txt)
.font_family(FontFamily::MONOSPACE)
.text_color(BLOCKQUOTE_COLOR);
current_pos += txt.len();
}
ParseEvent::HardBreak => {
builder.push("\n\n");
current_pos += 2;
}
_ => (),
}
}
builder.build()
}
fn add_newline_after_tag(tag: &Tag) -> bool {
!matches!(
tag,
Tag::Emphasis | Tag::Strong | Tag::Strikethrough | Tag::Link(..)
)
}
fn add_attribute_for_tag(tag: &Tag, mut attrs: AttributesAdder) {
match tag {
Tag::Heading(lvl) => {
let font_size = match lvl {
1 => 38.,
2 => 32.0,
3 => 26.0,
4 => 20.0,
5 => 16.0,
_ => 12.0,
};
attrs.size(font_size).weight(FontWeight::BOLD);
}
Tag::BlockQuote => {
attrs.style(FontStyle::Italic).text_color(BLOCKQUOTE_COLOR);
}
Tag::CodeBlock(_) => {
attrs.font_family(FontFamily::MONOSPACE);
}
Tag::Emphasis => {
attrs.style(FontStyle::Italic);
}
Tag::Strong => {
attrs.weight(FontWeight::BOLD);
}
Tag::Strikethrough => {
attrs.strikethrough(true);
}
Tag::Link(_link_ty, target, _title) => {
attrs
.underline(true)
.text_color(LINK_COLOR)
.link(OPEN_LINK.with(target.to_string()));
}
// ignore other tags for now
_ => (),
}
}
#[allow(unused_assignments, unused_mut)]
fn make_menu<T: Data>(_window_id: Option<WindowId>, _app_state: &AppState, _env: &Env) -> Menu<T> {
let mut base = Menu::empty();
#[cfg(target_os = "macos")]
{
base = base.entry(druid::platform_menus::mac::application::default())
}
#[cfg(any(
target_os = "windows",
target_os = "freebsd",
target_os = "linux",
target_os = "openbsd"
))]
{
base = base.entry(druid::platform_menus::win::file::default());
}
base.entry(
Menu::new(LocalizedString::new("common-menu-edit-menu"))
.entry(druid::platform_menus::common::undo())
.entry(druid::platform_menus::common::redo())
.separator()
.entry(druid::platform_menus::common::cut().enabled(false))
.entry(druid::platform_menus::common::copy())
.entry(druid::platform_menus::common::paste()),
)
}
Additional examples can be found in:
- examples/scroll.rs
- examples/panels.rs
- examples/svg.rs
- examples/anim.rs
- examples/scroll_colors.rs
- examples/view_switcher.rs
- examples/lens.rs
- examples/open_save.rs
- examples/switches.rs
- examples/invalidation.rs
- examples/multiwin.rs
- examples/timer.rs
- examples/styled_text.rs
- examples/disabled.rs
- examples/list.rs
- examples/calc.rs
- examples/blocking_function.rs
sourcepub fn with_placeholder(self, placeholder: impl Into<ArcStr>) -> Self
pub fn with_placeholder(self, placeholder: impl Into<ArcStr>) -> Self
Add a placeholder value. This will be used if localization fails.
This is intended for use during prototyping.
sourcepub fn localized_str(&self) -> ArcStr
pub fn localized_str(&self) -> ArcStr
Return the localized value for this string, or the placeholder, if the localization is missing, or the key if there is no placeholder.
sourcepub fn with_arg(
self,
key: &'static str,
f: impl Fn(&T, &Env) -> FluentValue<'static> + 'static
) -> Self
pub fn with_arg( self, key: &'static str, f: impl Fn(&T, &Env) -> FluentValue<'static> + 'static ) -> Self
Add a named argument and a corresponding closure. This closure is a function that will return a value for the given key from the current environment and data.
Examples found in repository?
examples/blocking_function.rs (line 57)
39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
fn ui_builder() -> impl Widget<AppState> {
let button = Button::new("Start slow increment")
.on_click(|ctx, data: &mut AppState, _env| {
data.processing = true;
// In order to make sure that the other thread can communicate with the main thread we
// have to pass an external handle to the second thread.
// Using this handle we can send commands back to the main thread.
wrapped_slow_function(ctx.get_external_handle(), data.value);
})
.padding(5.0);
let button_placeholder = Flex::column()
.with_child(Label::new(LocalizedString::new("Processing...")).padding(5.0))
.with_child(Spinner::new());
// Hello-counter is defined in the built-in localisation file. This maps to "Current value is {count}"
// localised in english, french, or german. Every time the value is updated it shows the new value.
let text = LocalizedString::new("hello-counter")
.with_arg("count", |data: &AppState, _env| (data.value).into());
let label = Label::new(text).padding(5.0).center();
let either = Either::new(|data, _env| data.processing, button_placeholder, button);
Flex::column().with_child(label).with_child(either)
}
More examples
examples/multiwin.rs (line 53)
51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229
fn ui_builder() -> impl Widget<State> {
let text = LocalizedString::new("hello-counter")
.with_arg("count", |data: &State, _env| data.menu_count.into());
let label = Label::new(text);
let inc_button =
Button::<State>::new("Add menu item").on_click(|_ctx, data, _env| data.menu_count += 1);
let dec_button = Button::<State>::new("Remove menu item")
.on_click(|_ctx, data, _env| data.menu_count = data.menu_count.saturating_sub(1));
let new_button = Button::<State>::new("New window").on_click(|ctx, _data, _env| {
ctx.submit_command(sys_cmds::NEW_FILE.to(Global));
});
let quit_button = Button::<State>::new("Quit app").on_click(|_ctx, _data, _env| {
Application::global().quit();
});
let mut col = Flex::column();
col.add_flex_child(Align::centered(Padding::new(5.0, label)), 1.0);
let mut row = Flex::row();
row.add_child(Padding::new(5.0, inc_button));
row.add_child(Padding::new(5.0, dec_button));
col.add_flex_child(Align::centered(row), 1.0);
let mut row = Flex::row();
row.add_child(Padding::new(5.0, new_button));
row.add_child(Padding::new(5.0, quit_button));
col.add_flex_child(Align::centered(row), 1.0);
let content = ControllerHost::new(col, ContextMenuController);
Glow::new(content)
}
struct Glow<W> {
inner: W,
}
impl<W> Glow<W> {
pub fn new(inner: W) -> Glow<W> {
Glow { inner }
}
}
impl<W: Widget<State>> Widget<State> for Glow<W> {
fn event(&mut self, ctx: &mut EventCtx, event: &Event, data: &mut State, env: &Env) {
self.inner.event(ctx, event, data, env);
}
fn lifecycle(&mut self, ctx: &mut LifeCycleCtx, event: &LifeCycle, data: &State, env: &Env) {
if let LifeCycle::HotChanged(_) = event {
ctx.request_paint();
}
self.inner.lifecycle(ctx, event, data, env);
}
fn update(&mut self, ctx: &mut UpdateCtx, old_data: &State, data: &State, env: &Env) {
if old_data.glow_hot != data.glow_hot {
ctx.request_paint();
}
self.inner.update(ctx, old_data, data, env);
}
fn layout(
&mut self,
ctx: &mut LayoutCtx,
bc: &BoxConstraints,
data: &State,
env: &Env,
) -> Size {
self.inner.layout(ctx, bc, data, env)
}
fn paint(&mut self, ctx: &mut PaintCtx, data: &State, env: &Env) {
if data.glow_hot && ctx.is_hot() {
BackgroundBrush::Color(Color::rgb8(200, 55, 55)).paint(ctx, data, env);
}
self.inner.paint(ctx, data, env);
}
}
struct ContextMenuController;
struct Delegate {
windows: Vec<WindowId>,
}
impl<W: Widget<State>> Controller<State, W> for ContextMenuController {
fn event(
&mut self,
child: &mut W,
ctx: &mut EventCtx,
event: &Event,
data: &mut State,
env: &Env,
) {
match event {
Event::MouseDown(ref mouse) if mouse.button.is_right() => {
ctx.show_context_menu(make_context_menu(), mouse.pos);
}
_ => child.event(ctx, event, data, env),
}
}
}
impl AppDelegate<State> for Delegate {
fn command(
&mut self,
ctx: &mut DelegateCtx,
_target: Target,
cmd: &Command,
data: &mut State,
_env: &Env,
) -> Handled {
if cmd.is(sys_cmds::NEW_FILE) {
let new_win = WindowDesc::new(ui_builder())
.menu(make_menu)
.window_size((data.selected as f64 * 100.0 + 300.0, 500.0));
ctx.new_window(new_win);
Handled::Yes
} else {
Handled::No
}
}
fn window_added(
&mut self,
id: WindowId,
_handle: WindowHandle,
_data: &mut State,
_env: &Env,
_ctx: &mut DelegateCtx,
) {
info!("Window added, id: {:?}", id);
self.windows.push(id);
}
fn window_removed(
&mut self,
id: WindowId,
_data: &mut State,
_env: &Env,
_ctx: &mut DelegateCtx,
) {
info!("Window removed, id: {:?}", id);
if let Some(pos) = self.windows.iter().position(|x| *x == id) {
self.windows.remove(pos);
}
}
}
#[allow(unused_assignments)]
fn make_menu(_: Option<WindowId>, state: &State, _: &Env) -> Menu<State> {
let mut base = Menu::empty();
#[cfg(target_os = "macos")]
{
base = druid::platform_menus::mac::menu_bar();
}
#[cfg(any(
target_os = "windows",
target_os = "freebsd",
target_os = "linux",
target_os = "openbsd"
))]
{
base = base.entry(druid::platform_menus::win::file::default());
}
if state.menu_count != 0 {
let mut custom = Menu::new(LocalizedString::new("Custom"));
for i in 1..=state.menu_count {
custom = custom.entry(
MenuItem::new(
LocalizedString::new("hello-counter")
.with_arg("count", move |_: &State, _| i.into()),
)
.on_activate(move |_ctx, data, _env| data.selected = i)
.enabled_if(move |_data, _env| i % 3 != 0)
.selected_if(move |data, _env| i == data.selected),
);
}
base = base.entry(custom);
}
base.rebuild_on(|old_data, data, _env| old_data.menu_count != data.menu_count)
}
Trait Implementations§
source§impl<T: Clone> Clone for LocalizedString<T>
impl<T: Clone> Clone for LocalizedString<T>
source§fn clone(&self) -> LocalizedString<T>
fn clone(&self) -> LocalizedString<T>
Returns a copy of the value. Read more
1.0.0 · source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
Performs copy-assignment from
source
. Read moresource§impl<T: Debug> Debug for LocalizedString<T>
impl<T: Debug> Debug for LocalizedString<T>
source§impl<T> From<LocalizedString<T>> for LabelText<T>
impl<T> From<LocalizedString<T>> for LabelText<T>
source§fn from(src: LocalizedString<T>) -> LabelText<T>
fn from(src: LocalizedString<T>) -> LabelText<T>
Converts to this type from the input type.
Auto Trait Implementations§
impl<T> !RefUnwindSafe for LocalizedString<T>
impl<T> !Send for LocalizedString<T>
impl<T> !Sync for LocalizedString<T>
impl<T> Unpin for LocalizedString<T>
impl<T> !UnwindSafe for LocalizedString<T>
Blanket Implementations§
source§impl<T> Instrument for T
impl<T> Instrument for T
source§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
source§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
§impl<T> RoundFrom<T> for T
impl<T> RoundFrom<T> for T
§fn round_from(x: T) -> T
fn round_from(x: T) -> T
Performs the conversion.
§impl<T, U> RoundInto<U> for Twhere
U: RoundFrom<T>,
impl<T, U> RoundInto<U> for Twhere U: RoundFrom<T>,
§fn round_into(self) -> U
fn round_into(self) -> U
Performs the conversion.