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