fyrox_ui/text.rs
1// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"), to deal
5// in the Software without restriction, including without limitation the rights
6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7// copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in all
11// copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19// SOFTWARE.
20
21//! Text is a simple widget that allows you to print text on screen. See [`Text`] docs for more info and
22//! examples.
23
24#![warn(missing_docs)]
25
26use crate::{
27 brush::Brush,
28 core::{
29 algebra::Vector2, color::Color, pool::Handle, reflect::prelude::*, type_traits::prelude::*,
30 uuid_provider, visitor::prelude::*,
31 },
32 draw::DrawingContext,
33 font::FontResource,
34 formatted_text::{FormattedText, FormattedTextBuilder, Run, RunSet, WrapMode},
35 message::{MessageData, UiMessage},
36 style::{resource::StyleResourceExt, Style, StyledProperty},
37 widget::{Widget, WidgetBuilder},
38 BBCode, BuildContext, Control, HorizontalAlignment, UiNode, UserInterface, VerticalAlignment,
39};
40use fyrox_core::algebra::Matrix3;
41use fyrox_core::variable::InheritableVariable;
42use fyrox_graph::constructor::{ConstructorProvider, GraphNodeConstructor};
43use std::cell::RefCell;
44
45/// Possible messages that can be used to alternate [`Text`] widget state at runtime.
46#[derive(Debug, Clone, PartialEq)]
47pub enum TextMessage {
48 /// Used to set a new text and runs with BBCode tags.
49 BBCode(String),
50 /// Used to set a new text or to receive the changed text.
51 Text(String),
52 /// Used to set new text wrapping mode of the widget. See [Text](Text#text-alignment-and-word-wrapping) for usage
53 /// examples.
54 Wrap(WrapMode),
55 /// Used to set new font of the widget. See [Text](Text#fonts-and_colors) for usage examples.
56 Font(FontResource),
57 /// Used to set new vertical alignment of the widget. See [Text](Text#text-alignment-and-word-wrapping) for usage
58 /// examples.
59 VerticalAlignment(VerticalAlignment),
60 /// Used to set new horizontal alignment of the widget. See [Text](Text#text-alignment-and-word-wrapping) for usage
61 /// examples.
62 HorizontalAlignment(HorizontalAlignment),
63 /// Used to enable/disable shadow casting of the widget. See [Text](Text#shadows) for usage examples.
64 Shadow(bool),
65 /// Used to set new dilation factor of the shadows. See [Text](Text#shadows) for usage examples.
66 ShadowDilation(f32),
67 /// Used to set new brush that will be used to draw the shadows. See [Text](Text#shadows) for usage examples.
68 ShadowBrush(Brush),
69 /// Used to set how much the shadows will be offset from the widget. See [Text](Text#shadows) for usage examples.
70 ShadowOffset(Vector2<f32>),
71 /// Used to set font height of the widget.
72 FontSize(StyledProperty<f32>),
73 /// Used to set the new set of runs in the text.
74 Runs(RunSet),
75}
76impl MessageData for TextMessage {}
77
78/// Text is a simple widget that allows you to print text on screen. It has various options like word wrapping, text
79/// alignment, and so on.
80///
81/// ## How to create
82///
83/// An instance of the [`Text`] widget could be created like so:
84///
85/// ```rust
86/// # use fyrox_ui::{
87/// # core::pool::Handle,
88/// # text::{Text, TextBuilder}, widget::WidgetBuilder, UiNode, UserInterface
89/// # };
90/// fn create_text(ui: &mut UserInterface, text: &str) -> Handle<Text> {
91/// TextBuilder::new(WidgetBuilder::new())
92/// .with_text(text)
93/// .build(&mut ui.build_ctx())
94/// }
95/// ```
96///
97/// ## Text alignment and word wrapping
98///
99/// There are various text alignment options for both vertical and horizontal axes. Typical alignment values are:
100/// [`HorizontalAlignment::Left`], [`HorizontalAlignment::Center`], [`HorizontalAlignment::Right`] for horizontal axis,
101/// and [`VerticalAlignment::Top`], [`VerticalAlignment::Center`], [`VerticalAlignment::Bottom`] for vertical axis. An
102/// instance of centered text could be created like so:
103///
104/// ```rust,no_run
105/// # use fyrox_ui::{
106/// # core::pool::Handle,
107/// # text::{Text, TextBuilder}, widget::WidgetBuilder, HorizontalAlignment, UiNode, UserInterface,
108/// # VerticalAlignment,
109/// # };
110/// fn create_centered_text(ui: &mut UserInterface, text: &str) -> Handle<Text> {
111/// TextBuilder::new(WidgetBuilder::new())
112/// .with_horizontal_text_alignment(HorizontalAlignment::Center)
113/// .with_vertical_text_alignment(VerticalAlignment::Center)
114/// .with_text(text)
115/// .build(&mut ui.build_ctx())
116/// }
117/// ```
118///
119/// What's the difference between widget's alignment and text-specific? Widget's alignment operates on a bounding rectangle
120/// of the text and text-specific alignment operates on line-basis. This means that if you set [`HorizontalAlignment::Center`]
121/// as widget's alignment, your text lines won't be centered, instead they'll be aligned at the left and the entire text block
122/// will be aligned at center.
123///
124/// Long text is usually needs to wrap on available bounds, there are three possible options for word wrapping:
125/// [`WrapMode::NoWrap`], [`WrapMode::Letter`], [`WrapMode::Word`]. An instance of text with word-based wrapping could
126/// be created like so:
127///
128/// ```rust,no_run
129/// # use fyrox_ui::{
130/// # core::pool::Handle,
131/// # formatted_text::WrapMode, text::{Text, TextBuilder}, widget::WidgetBuilder, UiNode,
132/// # UserInterface,
133/// # };
134/// fn create_text_with_word_wrap(ui: &mut UserInterface, text: &str) -> Handle<Text> {
135/// TextBuilder::new(WidgetBuilder::new())
136/// .with_wrap(WrapMode::Word)
137/// .with_text(text)
138/// .build(&mut ui.build_ctx())
139/// }
140/// ```
141///
142/// ## Background
143///
144/// If you need to have a text with some background, you should use [`crate::border::Border`] widget as a parent widget of your
145/// text. **Caveat:** [`WidgetBuilder::with_background`] is ignored for [`Text`] widget!
146///
147/// ```rust,no_run
148/// # use fyrox_ui::{
149/// # core::{color::Color, pool::Handle},
150/// # border::BorderBuilder, brush::Brush, text::TextBuilder, widget::WidgetBuilder, UiNode,
151/// # UserInterface,
152/// # };
153/// # use fyrox_ui::border::Border;
154/// #
155/// fn create_text_with_background(ui: &mut UserInterface, text: &str) -> Handle<Border> {
156/// let text_widget =
157/// TextBuilder::new(WidgetBuilder::new().with_foreground(Brush::Solid(Color::RED).into()))
158/// .with_text(text)
159/// .build(&mut ui.build_ctx());
160/// BorderBuilder::new(
161/// WidgetBuilder::new()
162/// .with_child(text_widget) // <-- Text is now a child of the border
163/// .with_background(Brush::Solid(Color::opaque(50, 50, 50)).into()),
164/// )
165/// .build(&mut ui.build_ctx())
166/// }
167/// ```
168///
169/// Keep in mind that now the text widget is a child widget of the border, so if you need to position the text, you should
170/// position the border, not the text.
171///
172/// ## Fonts and colors
173///
174/// To set a color of the text, just use [`WidgetBuilder::with_foreground`] while building the text instance:
175///
176/// ```rust,no_run
177/// # use fyrox_ui::{
178/// # core::{color::Color, pool::Handle},
179/// # brush::Brush, text::{Text, TextBuilder}, widget::WidgetBuilder, UiNode, UserInterface
180/// # };
181/// fn create_text(ui: &mut UserInterface, text: &str) -> Handle<Text> {
182/// // vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
183/// TextBuilder::new(WidgetBuilder::new().with_foreground(Brush::Solid(Color::RED).into()))
184/// .with_text(text)
185/// .build(&mut ui.build_ctx())
186/// }
187/// ```
188///
189/// By default, text is created with default font, however it is possible to set any custom font:
190///
191/// ```rust
192/// # use fyrox_resource::manager::ResourceManager;
193/// # use fyrox_ui::{
194/// # core::{futures::executor::block_on, pool::Handle},
195/// # text::{Text, TextBuilder},
196/// # font::{Font, FontResource},
197/// # widget::WidgetBuilder,
198/// # UiNode, UserInterface,
199/// # };
200///
201/// fn create_text(ui: &mut UserInterface, resource_manager: &ResourceManager, text: &str) -> Handle<Text> {
202/// TextBuilder::new(WidgetBuilder::new())
203/// .with_font(resource_manager.request::<Font>("path/to/your/font.ttf"))
204/// .with_text(text)
205/// .with_font_size(20.0f32.into())
206/// .build(&mut ui.build_ctx())
207/// }
208/// ```
209///
210/// Please refer to [`crate::font::Font`] chapter to learn more about fonts.
211///
212/// ### Font size
213///
214/// Use [`TextBuilder::with_font_size`] or send [`TextMessage::FontSize`] to your Text widget instance
215/// to set the font size of it.
216///
217/// ## Shadows
218///
219/// Text widget supports shadows effect to add contrast to your text, which could be useful to make text readable independent
220/// of the background colors. This effect could be used for subtitles. Shadows are pretty easy to add, all you need to do
221/// is to enable them, setup desired thickness, offset and brush (solid color or gradient).
222///
223/// ```rust,no_run
224/// # use fyrox_ui::{
225/// # core::{algebra::Vector2, color::Color, pool::Handle},
226/// # brush::Brush, text::{Text, TextBuilder}, widget::WidgetBuilder, UiNode, UserInterface
227/// # };
228/// #
229/// fn create_red_text_with_black_shadows(ui: &mut UserInterface, text: &str) -> Handle<Text> {
230/// TextBuilder::new(WidgetBuilder::new().with_foreground(Brush::Solid(Color::RED).into()))
231/// .with_text(text)
232/// // Enable shadows.
233/// .with_shadow(true)
234/// // Black shadows.
235/// .with_shadow_brush(Brush::Solid(Color::BLACK))
236/// // 1px thick.
237/// .with_shadow_dilation(1.0)
238/// // Offset the shadow slightly to the right-bottom.
239/// .with_shadow_offset(Vector2::new(1.0, 1.0))
240/// .build(&mut ui.build_ctx())
241/// }
242/// ```
243///
244/// ## Runs
245///
246/// Formatting options such as fonts, shadows, sizes, and brushes can be independently controlled
247/// for each character in text by adding formatting runs to the text with [`Run`].
248/// Each run has a range of `char` positions within the text and fields to control formatting.
249///
250/// ```rust,no_run
251/// # use fyrox_ui::{
252/// # core::{algebra::Vector2, color::Color, pool::Handle},
253/// # brush::Brush, text::{Text, TextBuilder}, widget::WidgetBuilder, UiNode, UserInterface,
254/// # formatted_text::Run,
255/// # };
256/// #
257/// fn create_text_with_red_run(ui: &mut UserInterface, text: &str) -> Handle<Text> {
258/// TextBuilder::new(WidgetBuilder::new())
259/// .with_text(text)
260/// .with_run(Run::new(5..22).with_brush(Brush::Solid(Color::RED)).with_shadow(true))
261/// .build(&mut ui.build_ctx())
262/// }
263/// ```
264///
265/// ## BBCode
266///
267/// Text widget supports BBCode to add runs of various formatting to text.
268/// The available tags are:
269/// * `[b]` **bold text** `[/b]`
270/// * `[i]` *italic text* `[/i]`
271/// * `[color=red]` red text `[/color]` (can be shortened to `[c=red]`... `[/c]`, and can use hex color as in `[color=#FF0000]`)
272/// * `[size=24]` large text `[/size]` (can be shortened to `[s=24]` ... `[/s]`)
273/// * `[shadow]` shadowed text `[/shadow]` (can be shortened to `[sh]` ... `[/sh]` and can change shadow color with `[shadow=blue]`)
274/// * `[br]` for a line break.
275///
276/// ```rust,no_run
277/// # use fyrox_ui::{
278/// # core::{algebra::Vector2, color::Color, pool::Handle},
279/// # brush::Brush, text::{Text, TextBuilder}, widget::WidgetBuilder, UiNode, UserInterface
280/// # };
281/// #
282/// fn create_text_with_bbcode(ui: &mut UserInterface) -> Handle<Text> {
283/// TextBuilder::new(WidgetBuilder::new())
284/// .with_bbcode("BBCode example: [b][c=blue]bold and blue[/c][/b]")
285/// .build(&mut ui.build_ctx())
286/// }
287/// ```
288///
289/// ## Messages
290///
291/// Text widget can accept the following list of messages at runtime (respective constructors are named with small letter -
292/// `TextMessage::Text -> TextMessage::text(widget_handle, direction, text)`):
293///
294/// - [`TextMessage::BBCode`] - sets the text and formatting runs using BBCode.
295/// - [`TextMessage::Text`] - sets new text for a `Text` widget.
296/// - [`TextMessage::Wrap`] - sets new [wrapping mode](Text#text-alignment-and-word-wrapping).
297/// - [`TextMessage::Font`] - sets new [font](Text#fonts-and-colors)
298/// - [`TextMessage::VerticalAlignment`] and `TextMessage::HorizontalAlignment` sets
299/// [vertical and horizontal](Text#text-alignment-and-word-wrapping) text alignment respectively.
300/// - [`TextMessage::Shadow`] - enables or disables [shadow casting](Text#shadows)
301/// - [`TextMessage::ShadowDilation`] - sets "thickness" of the shadows under the tex.
302/// - [`TextMessage::ShadowBrush`] - sets shadow brush (allows you to change color and even make shadow with color gradients).
303/// - [`TextMessage::ShadowOffset`] - sets offset of the shadows.
304/// - [`TextMessage::Runs`] - sets the formatting runs for the text.
305///
306/// An example of changing text at runtime could be something like this:
307///
308/// ```rust
309/// # use fyrox_ui::{
310/// # core::pool::Handle,
311/// # message::{MessageDirection},
312/// # UiNode, UserInterface,
313/// # text::TextMessage
314/// # };
315/// fn request_change_text(ui: &UserInterface, text_widget_handle: Handle<UiNode>, text: &str) {
316/// ui.send(text_widget_handle, TextMessage::Text(text.to_owned()))
317/// }
318/// ```
319///
320/// Please keep in mind, that like any other situation when you "changing" something via messages, you should remember
321/// that the change is **not** immediate.
322#[derive(Default, Clone, Visit, Reflect, Debug, ComponentProvider)]
323#[reflect(derived_type = "UiNode")]
324pub struct Text {
325 /// Base widget of the Text widget.
326 pub widget: Widget,
327 /// Text that may have BBCode tags to automatically generate formatting runs.
328 /// The available tags are:
329 /// * `[b]` **bold text** `[/b]`
330 /// * `[i]` *italic text* `[/i]`
331 /// * `[color=red]` red text `[/color]` (can be shortened to `[c=red]`... `[/c]`, and can use hex color as in `[color=#FF0000]`)
332 /// * `[size=24]` large text `[/size]` (can be shortened to `[s=24]` ... `[/s]`)
333 /// * `[shadow]` shadowed text `[/shadow]` (can be shortened to `[sh]` ... `[/sh]` and can change shadow color with `[shadow=blue]`)
334 /// * `[br]` for a line break.
335 #[visit(optional)]
336 #[reflect(hidden)]
337 pub bbcode: InheritableVariable<String>,
338 /// [`FormattedText`] instance that is used to layout text and generate drawing commands.
339 pub formatted_text: RefCell<FormattedText>,
340}
341
342impl ConstructorProvider<UiNode, UserInterface> for Text {
343 fn constructor() -> GraphNodeConstructor<UiNode, UserInterface> {
344 GraphNodeConstructor::new::<Self>()
345 .with_variant("Text", |ui| {
346 TextBuilder::new(WidgetBuilder::new().with_name("Text"))
347 .with_text("Text")
348 .build(&mut ui.build_ctx())
349 .to_base()
350 .into()
351 })
352 .with_group("Visual")
353 }
354}
355
356crate::define_widget_deref!(Text);
357
358uuid_provider!(Text = "22f7f502-7622-4ecb-8c5f-ba436e7ee823");
359
360impl Control for Text {
361 fn measure_override(&self, _: &UserInterface, available_size: Vector2<f32>) -> Vector2<f32> {
362 self.formatted_text
363 .borrow_mut()
364 .set_super_sampling_scale(self.visual_max_scaling())
365 .set_constraint(available_size)
366 .build()
367 }
368
369 fn draw(&self, drawing_context: &mut DrawingContext) {
370 self.formatted_text
371 .borrow_mut()
372 .set_brush(self.widget.foreground());
373 let bounds = self.widget.bounding_rect();
374 drawing_context.draw_text(
375 self.clip_bounds(),
376 bounds.position,
377 &self.material,
378 &self.formatted_text.borrow(),
379 );
380 }
381
382 fn on_visual_transform_changed(
383 &self,
384 old_transform: &Matrix3<f32>,
385 new_transform: &Matrix3<f32>,
386 ) {
387 if old_transform != new_transform {
388 self.formatted_text
389 .borrow_mut()
390 .set_super_sampling_scale(self.visual_max_scaling())
391 .build();
392 }
393 }
394
395 fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
396 self.widget.handle_routed_message(ui, message);
397
398 if message.destination() == self.handle() {
399 if let Some(msg) = message.data::<TextMessage>() {
400 let mut text_ref = self.formatted_text.borrow_mut();
401 match msg {
402 TextMessage::BBCode(text) => {
403 drop(text_ref);
404 self.set_bbcode(text.clone());
405 }
406 TextMessage::Text(text) => {
407 text_ref.set_text(text);
408 drop(text_ref);
409 self.invalidate_layout();
410 }
411 &TextMessage::Wrap(wrap) => {
412 if text_ref.wrap_mode() != wrap {
413 text_ref.set_wrap(wrap);
414 drop(text_ref);
415 self.invalidate_layout();
416 }
417 }
418 TextMessage::Font(font) => {
419 if &text_ref.get_font() != font {
420 text_ref.set_font(font.clone());
421 drop(text_ref);
422 self.invalidate_layout();
423 }
424 }
425 &TextMessage::HorizontalAlignment(horizontal_alignment) => {
426 if text_ref.horizontal_alignment() != horizontal_alignment {
427 text_ref.set_horizontal_alignment(horizontal_alignment);
428 drop(text_ref);
429 self.invalidate_layout();
430 }
431 }
432 &TextMessage::VerticalAlignment(vertical_alignment) => {
433 if text_ref.vertical_alignment() != vertical_alignment {
434 text_ref.set_vertical_alignment(vertical_alignment);
435 drop(text_ref);
436 self.invalidate_layout();
437 }
438 }
439 &TextMessage::Shadow(shadow) => {
440 if *text_ref.shadow != shadow {
441 text_ref.set_shadow(shadow);
442 drop(text_ref);
443 self.invalidate_layout();
444 }
445 }
446 TextMessage::ShadowBrush(brush) => {
447 if &*text_ref.shadow_brush != brush {
448 text_ref.set_shadow_brush(brush.clone());
449 drop(text_ref);
450 self.invalidate_layout();
451 }
452 }
453 &TextMessage::ShadowDilation(dilation) => {
454 if *text_ref.shadow_dilation != dilation {
455 text_ref.set_shadow_dilation(dilation);
456 drop(text_ref);
457 self.invalidate_layout();
458 }
459 }
460 &TextMessage::ShadowOffset(offset) => {
461 if *text_ref.shadow_offset != offset {
462 text_ref.set_shadow_offset(offset);
463 drop(text_ref);
464 self.invalidate_layout();
465 }
466 }
467 TextMessage::FontSize(height) => {
468 if text_ref.font_size() != height {
469 text_ref.set_font_size(height.clone());
470 drop(text_ref);
471 self.invalidate_layout();
472 }
473 }
474 TextMessage::Runs(runs) => {
475 text_ref.set_runs(runs.clone());
476 drop(text_ref);
477 self.invalidate_layout();
478 }
479 }
480 }
481 }
482 }
483}
484
485impl Text {
486 /// Modifies the content of the text, with BBCode tags used to set the formatting runs.
487 /// The available tags are:
488 /// * `[b]` **bold text** `[/b]`
489 /// * `[i]` *italic text* `[/i]`
490 /// * `[color=red]` red text `[/color]` (can be shortened to `[c=red]`... `[/c]`, and can use hex color as in `[color=#FF0000]`)
491 /// * `[size=24]` large text `[/size]` (can be shortened to `[s=24]` ... `[/s]`)
492 /// * `[shadow]` shadowed text `[/shadow]` (can be shortened to `[sh]` ... `[/sh]` and can change shadow color with `[shadow=blue]`)
493 /// * `[br]` for a line break.
494 pub fn set_bbcode(&mut self, code: String) {
495 self.bbcode.set_value_and_mark_modified(code);
496 let code: BBCode = self.bbcode.parse().unwrap();
497 let mut formatted = self.formatted_text.borrow_mut();
498 let font = formatted.get_font();
499 formatted.set_runs(code.build_runs(&font));
500 formatted.set_text(code.text);
501 self.invalidate_layout();
502 }
503 /// Returns current text wrapping mode of the widget.
504 pub fn wrap_mode(&self) -> WrapMode {
505 self.formatted_text.borrow().wrap_mode()
506 }
507
508 /// Returns current text of the widget.
509 pub fn text(&self) -> String {
510 self.formatted_text.borrow().text()
511 }
512
513 /// Returns current font of the widget.
514 pub fn font(&self) -> FontResource {
515 self.formatted_text.borrow().get_font()
516 }
517
518 /// Returns current vertical alignment of the widget.
519 pub fn vertical_alignment(&self) -> VerticalAlignment {
520 self.formatted_text.borrow().vertical_alignment()
521 }
522
523 /// Returns current horizontal alignment of the widget.
524 pub fn horizontal_alignment(&self) -> HorizontalAlignment {
525 self.formatted_text.borrow().horizontal_alignment()
526 }
527}
528
529/// TextBuilder is used to create instances of [`Text`] widget and register them in the user interface.
530pub struct TextBuilder {
531 widget_builder: WidgetBuilder,
532 bbcode: Option<String>,
533 text: Option<String>,
534 font: Option<FontResource>,
535 vertical_text_alignment: VerticalAlignment,
536 horizontal_text_alignment: HorizontalAlignment,
537 wrap: WrapMode,
538 shadow: bool,
539 shadow_brush: Brush,
540 shadow_dilation: f32,
541 shadow_offset: Vector2<f32>,
542 font_size: Option<StyledProperty<f32>>,
543 runs: Vec<Run>,
544}
545
546impl TextBuilder {
547 /// Creates new [`TextBuilder`] instance using the provided base widget builder.
548 pub fn new(widget_builder: WidgetBuilder) -> Self {
549 Self {
550 widget_builder,
551 bbcode: None,
552 text: None,
553 font: None,
554 vertical_text_alignment: VerticalAlignment::Top,
555 horizontal_text_alignment: HorizontalAlignment::Left,
556 wrap: WrapMode::NoWrap,
557 shadow: false,
558 shadow_brush: Brush::Solid(Color::BLACK),
559 shadow_dilation: 1.0,
560 shadow_offset: Vector2::new(1.0, 1.0),
561 font_size: None,
562 runs: Vec::default(),
563 }
564 }
565
566 /// Sets the desired text of the widget, with BBcode tags that will
567 /// automatically generate the formatting runs and replace any other
568 /// runs set through this builder.
569 pub fn with_bbcode<P: Into<String>>(mut self, text: P) -> Self {
570 self.bbcode = Some(text.into());
571 self
572 }
573
574 /// Sets the desired text of the widget.
575 pub fn with_text<P: Into<String>>(mut self, text: P) -> Self {
576 self.text = Some(text.into());
577 self
578 }
579
580 /// Sets the desired font of the widget.
581 pub fn with_font(mut self, font: FontResource) -> Self {
582 self.font = Some(font);
583 self
584 }
585
586 /// Sets the desired font of the widget using font wrapped in [`Option`].
587 pub fn with_opt_font(mut self, font: Option<FontResource>) -> Self {
588 self.font = font;
589 self
590 }
591
592 /// Sets the desired vertical alignment of the widget.
593 pub fn with_vertical_text_alignment(mut self, valign: VerticalAlignment) -> Self {
594 self.vertical_text_alignment = valign;
595 self
596 }
597
598 /// Sets the desired height of the text.
599 pub fn with_font_size(mut self, font_size: StyledProperty<f32>) -> Self {
600 self.font_size = Some(font_size);
601 self
602 }
603
604 /// Sets the desired horizontal alignment of the widget.
605 pub fn with_horizontal_text_alignment(mut self, halign: HorizontalAlignment) -> Self {
606 self.horizontal_text_alignment = halign;
607 self
608 }
609
610 /// Sets the desired word wrapping mode of the widget.
611 pub fn with_wrap(mut self, wrap: WrapMode) -> Self {
612 self.wrap = wrap;
613 self
614 }
615
616 /// Whether the shadow enabled or not.
617 pub fn with_shadow(mut self, shadow: bool) -> Self {
618 self.shadow = shadow;
619 self
620 }
621
622 /// Sets desired shadow brush. It will be used to render the shadow.
623 pub fn with_shadow_brush(mut self, brush: Brush) -> Self {
624 self.shadow_brush = brush;
625 self
626 }
627
628 /// Sets desired shadow dilation in units. Keep in mind that the dilation is absolute,
629 /// not percentage-based.
630 pub fn with_shadow_dilation(mut self, thickness: f32) -> Self {
631 self.shadow_dilation = thickness;
632 self
633 }
634
635 /// Sets desired shadow offset in units.
636 pub fn with_shadow_offset(mut self, offset: Vector2<f32>) -> Self {
637 self.shadow_offset = offset;
638 self
639 }
640
641 /// Adds the given run to the text to set the style for a portion of the text.
642 /// Later runs potentially overriding earlier runs if the ranges of the runs overlap and the later run
643 /// sets a property that conflicts with an earlier run.
644 pub fn with_run(mut self, run: Run) -> Self {
645 self.runs.push(run);
646 self
647 }
648
649 /// Adds multiple runs to the text to set the style of portions of the text.
650 /// Later runs potentially overriding earlier runs if the ranges of the runs overlap and the later run
651 /// sets a property that conflicts with an earlier run.
652 pub fn with_runs<I: IntoIterator<Item = Run>>(mut self, runs: I) -> Self {
653 self.runs.extend(runs);
654 self
655 }
656
657 /// Finishes text widget creation and registers it in the user interface, returning its handle to you.
658 pub fn build(mut self, ctx: &mut BuildContext) -> Handle<Text> {
659 let font = if let Some(font) = self.font {
660 font
661 } else {
662 ctx.default_font()
663 };
664
665 if self.widget_builder.foreground.is_none() {
666 self.widget_builder.foreground = Some(ctx.style.property(Style::BRUSH_TEXT));
667 }
668
669 let text_builder = if let Some(bbcode) = &self.bbcode {
670 let code: BBCode = bbcode.parse().unwrap();
671 code.build_formatted_text(font)
672 } else {
673 FormattedTextBuilder::new(font)
674 .with_text(self.text.unwrap_or_default())
675 .with_runs(self.runs)
676 };
677 let formatted_text = text_builder
678 .with_vertical_alignment(self.vertical_text_alignment)
679 .with_horizontal_alignment(self.horizontal_text_alignment)
680 .with_wrap(self.wrap)
681 .with_shadow(self.shadow)
682 .with_shadow_brush(self.shadow_brush)
683 .with_shadow_dilation(self.shadow_dilation)
684 .with_shadow_offset(self.shadow_offset)
685 .with_font_size(
686 self.font_size
687 .unwrap_or_else(|| ctx.style.property(Style::FONT_SIZE)),
688 )
689 .build();
690
691 let text = Text {
692 widget: self.widget_builder.build(ctx),
693 bbcode: self.bbcode.unwrap_or_default().into(),
694 formatted_text: RefCell::new(formatted_text),
695 };
696 ctx.add(text)
697 }
698}
699
700#[cfg(test)]
701mod test {
702 use crate::text::TextBuilder;
703 use crate::{test::test_widget_deletion, widget::WidgetBuilder};
704
705 #[test]
706 fn test_deletion() {
707 test_widget_deletion(|ctx| TextBuilder::new(WidgetBuilder::new()).build(ctx));
708 }
709}