Skip to main content

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}