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::formatted_text::{Run, RunSet};
27use crate::style::StyledProperty;
28use crate::{
29    brush::Brush,
30    core::{
31        algebra::Vector2, color::Color, pool::Handle, reflect::prelude::*, type_traits::prelude::*,
32        uuid_provider, visitor::prelude::*,
33    },
34    define_constructor,
35    draw::DrawingContext,
36    font::FontResource,
37    formatted_text::{FormattedText, FormattedTextBuilder, WrapMode},
38    message::{MessageDirection, UiMessage},
39    style::{resource::StyleResourceExt, Style},
40    widget::{Widget, WidgetBuilder},
41    BuildContext, Control, HorizontalAlignment, UiNode, UserInterface, VerticalAlignment,
42};
43
44use fyrox_core::algebra::Matrix3;
45use fyrox_graph::constructor::{ConstructorProvider, GraphNodeConstructor};
46use std::{
47    cell::RefCell,
48    ops::{Deref, DerefMut},
49};
50
51/// Possible messages that can be used to alternate [`Text`] widget state at runtime.
52#[derive(Debug, Clone, PartialEq)]
53pub enum TextMessage {
54    /// Used to set a new text or to receive the changed text.
55    Text(String),
56    /// Used to set new text wrapping mode of the widget. See [Text](Text#text-alignment-and-word-wrapping) for usage
57    /// examples.
58    Wrap(WrapMode),
59    /// Used to set new font of the widget.  See [Text](Text#fonts-and_colors) for usage examples.
60    Font(FontResource),
61    /// Used to set new vertical alignment of the widget. See [Text](Text#text-alignment-and-word-wrapping) for usage
62    /// examples.
63    VerticalAlignment(VerticalAlignment),
64    /// Used to set new horizontal alignment of the widget. See [Text](Text#text-alignment-and-word-wrapping) for usage
65    /// examples.
66    HorizontalAlignment(HorizontalAlignment),
67    /// Used to enable/disable shadow casting of the widget. See [Text](Text#shadows) for usage examples.
68    Shadow(bool),
69    /// Used to set new dilation factor of the shadows. See [Text](Text#shadows) for usage examples.
70    ShadowDilation(f32),
71    /// Used to set new brush that will be used to draw the shadows. See [Text](Text#shadows) for usage examples.
72    ShadowBrush(Brush),
73    /// Used to set how much the shadows will be offset from the widget. See [Text](Text#shadows) for usage examples.
74    ShadowOffset(Vector2<f32>),
75    /// Used to set font height of the widget.
76    FontSize(StyledProperty<f32>),
77    /// Used to set the new set of runs in the text.
78    Runs(RunSet),
79}
80
81impl TextMessage {
82    define_constructor!(
83        /// Creates new [`TextMessage::Text`] message.
84        TextMessage:Text => fn text(String), layout: false
85    );
86
87    define_constructor!(
88        /// Creates new [`TextMessage::Wrap`] message.
89        TextMessage:Wrap => fn wrap(WrapMode), layout: false
90    );
91
92    define_constructor!(
93        /// Creates new [`TextMessage::Font`] message.
94        TextMessage:Font => fn font(FontResource), layout: false
95    );
96
97    define_constructor!(
98        /// Creates new [`TextMessage::VerticalAlignment`] message.
99        TextMessage:VerticalAlignment => fn vertical_alignment(VerticalAlignment), layout: false
100    );
101
102    define_constructor!(
103        /// Creates new [`TextMessage::HorizontalAlignment`] message.
104        TextMessage:HorizontalAlignment => fn horizontal_alignment(HorizontalAlignment), layout: false
105    );
106
107    define_constructor!(
108        /// Creates new [`TextMessage::Shadow`] message.
109        TextMessage:Shadow => fn shadow(bool), layout: false
110    );
111
112    define_constructor!(
113        /// Creates new [`TextMessage::ShadowDilation`] message.
114        TextMessage:ShadowDilation => fn shadow_dilation(f32), layout: false
115    );
116
117    define_constructor!(
118        /// Creates new [`TextMessage::ShadowBrush`] message.
119        TextMessage:ShadowBrush => fn shadow_brush(Brush), layout: false
120    );
121
122    define_constructor!(
123        /// Creates new [`TextMessage::ShadowOffset`] message.
124        TextMessage:ShadowOffset => fn shadow_offset(Vector2<f32>), layout: false
125    );
126
127    define_constructor!(
128        /// Creates new [`TextMessage::FontSize`] message.
129        TextMessage:FontSize => fn font_size(StyledProperty<f32>), layout: false
130    );
131
132    define_constructor!(
133        /// Creates new [`TextMessage::Runs`] message.
134        TextMessage:Runs => fn runs(RunSet), layout: false
135    );
136}
137
138/// Text is a simple widget that allows you to print text on screen. It has various options like word wrapping, text
139/// alignment, and so on.
140///
141/// ## How to create
142///
143/// An instance of the [`Text`] widget could be created like so:
144///
145/// ```rust
146/// # use fyrox_ui::{
147/// #     core::pool::Handle,
148/// #     text::TextBuilder, widget::WidgetBuilder, UiNode, UserInterface
149/// # };
150/// fn create_text(ui: &mut UserInterface, text: &str) -> Handle<UiNode> {
151///     TextBuilder::new(WidgetBuilder::new())
152///         .with_text(text)
153///         .build(&mut ui.build_ctx())
154/// }
155/// ```
156///
157/// ## Text alignment and word wrapping
158///
159/// There are various text alignment options for both vertical and horizontal axes. Typical alignment values are:
160/// [`HorizontalAlignment::Left`], [`HorizontalAlignment::Center`], [`HorizontalAlignment::Right`] for horizontal axis,
161/// and [`VerticalAlignment::Top`], [`VerticalAlignment::Center`], [`VerticalAlignment::Bottom`] for vertical axis. An
162/// instance of centered text could be created like so:
163///
164/// ```rust,no_run
165/// # use fyrox_ui::{
166/// #     core::pool::Handle,
167/// #     text::TextBuilder, widget::WidgetBuilder, HorizontalAlignment, UiNode, UserInterface,
168/// #     VerticalAlignment,
169/// # };
170/// fn create_centered_text(ui: &mut UserInterface, text: &str) -> Handle<UiNode> {
171///     TextBuilder::new(WidgetBuilder::new())
172///         .with_horizontal_text_alignment(HorizontalAlignment::Center)
173///         .with_vertical_text_alignment(VerticalAlignment::Center)
174///     .with_text(text)
175///     .build(&mut ui.build_ctx())
176/// }
177/// ```
178///
179/// What's the difference between widget's alignment and text-specific? Widget's alignment operates on a bounding rectangle
180/// of the text and text-specific alignment operates on line-basis. This means that if you set [`HorizontalAlignment::Center`]
181/// as widget's alignment, your text lines won't be centered, instead they'll be aligned at the left and the entire text block
182/// will be aligned at center.
183///
184/// Long text is usually needs to wrap on available bounds, there are three possible options for word wrapping:
185/// [`WrapMode::NoWrap`], [`WrapMode::Letter`], [`WrapMode::Word`]. An instance of text with word-based wrapping could
186/// be created like so:
187///
188/// ```rust,no_run
189/// # use fyrox_ui::{
190/// #     core::pool::Handle,
191/// #     formatted_text::WrapMode, text::TextBuilder, widget::WidgetBuilder, UiNode,
192/// #     UserInterface,
193/// # };
194/// fn create_text_with_word_wrap(ui: &mut UserInterface, text: &str) -> Handle<UiNode> {
195///     TextBuilder::new(WidgetBuilder::new())
196///         .with_wrap(WrapMode::Word)
197///         .with_text(text)
198///         .build(&mut ui.build_ctx())
199/// }
200/// ```
201///
202/// ## Background
203///
204/// If you need to have a text with some background, you should use [`crate::border::Border`] widget as a parent widget of your
205/// text. **Caveat:** [`WidgetBuilder::with_background`] is ignored for [`Text`] widget!
206///
207/// ```rust,no_run
208/// # use fyrox_ui::{
209/// #     core::{color::Color, pool::Handle},
210/// #     border::BorderBuilder, brush::Brush, text::TextBuilder, widget::WidgetBuilder, UiNode,
211/// #     UserInterface,
212/// # };
213/// #
214/// fn create_text_with_background(ui: &mut UserInterface, text: &str) -> Handle<UiNode> {
215///     let text_widget =
216///         TextBuilder::new(WidgetBuilder::new().with_foreground(Brush::Solid(Color::RED).into()))
217///             .with_text(text)
218///             .build(&mut ui.build_ctx());
219///     BorderBuilder::new(
220///         WidgetBuilder::new()
221///             .with_child(text_widget) // <-- Text is now a child of the border
222///             .with_background(Brush::Solid(Color::opaque(50, 50, 50)).into()),
223///     )
224///     .build(&mut ui.build_ctx())
225/// }
226/// ```
227///
228/// 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
229/// position the border, not the text.
230///
231/// ## Fonts and colors
232///
233/// To set a color of the text just use [`WidgetBuilder::with_foreground`] while building the text instance:
234///
235/// ```rust,no_run
236/// # use fyrox_ui::{
237/// #     core::{color::Color, pool::Handle},
238/// #     brush::Brush, text::TextBuilder, widget::WidgetBuilder, UiNode, UserInterface
239/// # };
240/// fn create_text(ui: &mut UserInterface, text: &str) -> Handle<UiNode> {
241///     //               vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
242///     TextBuilder::new(WidgetBuilder::new().with_foreground(Brush::Solid(Color::RED).into()))
243///         .with_text(text)
244///         .build(&mut ui.build_ctx())
245/// }
246/// ```
247///
248/// By default, text is created with default font, however it is possible to set any custom font:
249///
250/// ```rust
251/// # use fyrox_resource::manager::ResourceManager;
252/// # use fyrox_ui::{
253/// #     core::{futures::executor::block_on, pool::Handle},
254/// #     text::TextBuilder,
255/// #     font::{Font, FontResource},
256/// #     widget::WidgetBuilder,
257/// #     UiNode, UserInterface,
258/// # };
259///
260/// fn create_text(ui: &mut UserInterface, resource_manager: &ResourceManager, text: &str) -> Handle<UiNode> {
261///     TextBuilder::new(WidgetBuilder::new())
262///         .with_font(resource_manager.request::<Font>("path/to/your/font.ttf"))
263///         .with_text(text)
264///         .with_font_size(20.0f32.into())
265///         .build(&mut ui.build_ctx())
266/// }
267/// ```
268///
269/// Please refer to [`crate::font::Font`] chapter to learn more about fonts.
270///
271/// ### Font size
272///
273/// Use [`TextBuilder::with_font_size`] or send [`TextMessage::font_size`] to your Text widget instance
274/// to set the font size of it.
275///
276/// ## Shadows
277///
278/// Text widget supports shadows effect to add contrast to your text, which could be useful to make text readable independent
279/// on the background colors. This effect could be used for subtitles. Shadows are pretty easy to add, all you need to do
280/// is to enable them, setup desired thickness, offset and brush (solid color or gradient).
281///
282/// ```rust,no_run
283/// # use fyrox_ui::{
284/// #     core::{algebra::Vector2, color::Color, pool::Handle},
285/// #     brush::Brush, text::TextBuilder, widget::WidgetBuilder, UiNode, UserInterface
286/// # };
287/// #
288/// fn create_red_text_with_black_shadows(ui: &mut UserInterface, text: &str) -> Handle<UiNode> {
289///     TextBuilder::new(WidgetBuilder::new().with_foreground(Brush::Solid(Color::RED).into()))
290///         .with_text(text)
291///         // Enable shadows.
292///         .with_shadow(true)
293///         // Black shadows.
294///         .with_shadow_brush(Brush::Solid(Color::BLACK))
295///         // 1px thick.
296///         .with_shadow_dilation(1.0)
297///         // Offset the shadow slightly to the right-bottom.
298///         .with_shadow_offset(Vector2::new(1.0, 1.0))
299///         .build(&mut ui.build_ctx())
300/// }
301/// ```
302///
303/// ## Messages
304///
305/// Text widget can accept the following list of messages at runtime (respective constructors are name with small letter -
306/// `TextMessage::Text -> TextMessage::text(widget_handle, direction, text)`):
307///
308/// - [`TextMessage::Text`] - sets new text for a `Text` widget.
309/// - [`TextMessage::Wrap`] - sets new [wrapping mode](Text#text-alignment-and-word-wrapping).
310/// - [`TextMessage::Font`] - sets new [font](Text#fonts-and-colors)
311/// - [`TextMessage::VerticalAlignment`] and `TextMessage::HorizontalAlignment` sets
312/// [vertical and horizontal](Text#text-alignment-and-word-wrapping) text alignment respectively.
313/// - [`TextMessage::Shadow`] - enables or disables [shadow casting](Text#shadows)
314/// - [`TextMessage::ShadowDilation`] - sets "thickness" of the shadows under the tex.
315/// - [`TextMessage::ShadowBrush`] - sets shadow brush (allows you to change color and even make shadow with color gradients).
316/// - [`TextMessage::ShadowOffset`] - sets offset of the shadows.
317///
318/// An example of changing text at runtime could be something like this:
319///
320/// ```rust
321/// # use fyrox_ui::{
322/// #     core::pool::Handle,
323/// #     message::{MessageDirection},
324/// #     UiNode, UserInterface,
325/// #     text::TextMessage
326/// # };
327/// fn request_change_text(ui: &UserInterface, text_widget_handle: Handle<UiNode>, text: &str) {
328///     ui.send_message(TextMessage::text(
329///         text_widget_handle,
330///         MessageDirection::ToWidget,
331///         text.to_owned(),
332///     ))
333/// }
334/// ```
335///
336/// Please keep in mind, that like any other situation when you "changing" something via messages, you should remember
337/// that the change is **not** immediate.
338#[derive(Default, Clone, Visit, Reflect, Debug, ComponentProvider)]
339#[reflect(derived_type = "UiNode")]
340pub struct Text {
341    /// Base widget of the Text widget.
342    pub widget: Widget,
343    /// [`FormattedText`] instance that is used to layout text and generate drawing commands.
344    pub formatted_text: RefCell<FormattedText>,
345}
346
347impl ConstructorProvider<UiNode, UserInterface> for Text {
348    fn constructor() -> GraphNodeConstructor<UiNode, UserInterface> {
349        GraphNodeConstructor::new::<Self>()
350            .with_variant("Text", |ui| {
351                TextBuilder::new(WidgetBuilder::new().with_name("Text"))
352                    .with_text("Text")
353                    .build(&mut ui.build_ctx())
354                    .into()
355            })
356            .with_group("Visual")
357    }
358}
359
360crate::define_widget_deref!(Text);
361
362uuid_provider!(Text = "22f7f502-7622-4ecb-8c5f-ba436e7ee823");
363
364impl Control for Text {
365    fn measure_override(&self, _: &UserInterface, available_size: Vector2<f32>) -> Vector2<f32> {
366        self.formatted_text
367            .borrow_mut()
368            .set_super_sampling_scale(self.visual_max_scaling())
369            .set_constraint(available_size)
370            .build()
371    }
372
373    fn draw(&self, drawing_context: &mut DrawingContext) {
374        self.formatted_text
375            .borrow_mut()
376            .set_brush(self.widget.foreground());
377        let bounds = self.widget.bounding_rect();
378        drawing_context.draw_text(
379            self.clip_bounds(),
380            bounds.position,
381            &self.material,
382            &self.formatted_text.borrow(),
383        );
384    }
385
386    fn on_visual_transform_changed(
387        &self,
388        old_transform: &Matrix3<f32>,
389        new_transform: &Matrix3<f32>,
390    ) {
391        if old_transform != new_transform {
392            self.formatted_text
393                .borrow_mut()
394                .set_super_sampling_scale(self.visual_max_scaling())
395                .build();
396        }
397    }
398
399    fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
400        self.widget.handle_routed_message(ui, message);
401
402        if message.destination() == self.handle() {
403            if let Some(msg) = message.data::<TextMessage>() {
404                let mut text_ref = self.formatted_text.borrow_mut();
405                match msg {
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    /// Returns current text wrapping mode of the widget.
487    pub fn wrap_mode(&self) -> WrapMode {
488        self.formatted_text.borrow().wrap_mode()
489    }
490
491    /// Returns current text of the widget.
492    pub fn text(&self) -> String {
493        self.formatted_text.borrow().text()
494    }
495
496    /// Returns current font of the widget.
497    pub fn font(&self) -> FontResource {
498        self.formatted_text.borrow().get_font()
499    }
500
501    /// Returns current vertical alignment of the widget.
502    pub fn vertical_alignment(&self) -> VerticalAlignment {
503        self.formatted_text.borrow().vertical_alignment()
504    }
505
506    /// Returns current horizontal alignment of the widget.
507    pub fn horizontal_alignment(&self) -> HorizontalAlignment {
508        self.formatted_text.borrow().horizontal_alignment()
509    }
510}
511
512/// TextBuilder is used to create instances of [`Text`] widget and register them in the user interface.
513pub struct TextBuilder {
514    widget_builder: WidgetBuilder,
515    text: Option<String>,
516    font: Option<FontResource>,
517    vertical_text_alignment: VerticalAlignment,
518    horizontal_text_alignment: HorizontalAlignment,
519    wrap: WrapMode,
520    shadow: bool,
521    shadow_brush: Brush,
522    shadow_dilation: f32,
523    shadow_offset: Vector2<f32>,
524    font_size: Option<StyledProperty<f32>>,
525    runs: Vec<Run>,
526}
527
528impl TextBuilder {
529    /// Creates new [`TextBuilder`] instance using the provided base widget builder.
530    pub fn new(widget_builder: WidgetBuilder) -> Self {
531        Self {
532            widget_builder,
533            text: None,
534            font: None,
535            vertical_text_alignment: VerticalAlignment::Top,
536            horizontal_text_alignment: HorizontalAlignment::Left,
537            wrap: WrapMode::NoWrap,
538            shadow: false,
539            shadow_brush: Brush::Solid(Color::BLACK),
540            shadow_dilation: 1.0,
541            shadow_offset: Vector2::new(1.0, 1.0),
542            font_size: None,
543            runs: Vec::default(),
544        }
545    }
546
547    /// Sets the desired text of the widget.
548    pub fn with_text<P: Into<String>>(mut self, text: P) -> Self {
549        self.text = Some(text.into());
550        self
551    }
552
553    /// Sets the desired font of the widget.
554    pub fn with_font(mut self, font: FontResource) -> Self {
555        self.font = Some(font);
556        self
557    }
558
559    /// Sets the desired font of the widget using font wrapped in [`Option`].
560    pub fn with_opt_font(mut self, font: Option<FontResource>) -> Self {
561        self.font = font;
562        self
563    }
564
565    /// Sets the desired vertical alignment of the widget.
566    pub fn with_vertical_text_alignment(mut self, valign: VerticalAlignment) -> Self {
567        self.vertical_text_alignment = valign;
568        self
569    }
570
571    /// Sets the desired height of the text.
572    pub fn with_font_size(mut self, font_size: StyledProperty<f32>) -> Self {
573        self.font_size = Some(font_size);
574        self
575    }
576
577    /// Sets the desired horizontal alignment of the widget.
578    pub fn with_horizontal_text_alignment(mut self, halign: HorizontalAlignment) -> Self {
579        self.horizontal_text_alignment = halign;
580        self
581    }
582
583    /// Sets the desired word wrapping mode of the widget.
584    pub fn with_wrap(mut self, wrap: WrapMode) -> Self {
585        self.wrap = wrap;
586        self
587    }
588
589    /// Whether the shadow enabled or not.
590    pub fn with_shadow(mut self, shadow: bool) -> Self {
591        self.shadow = shadow;
592        self
593    }
594
595    /// Sets desired shadow brush. It will be used to render the shadow.
596    pub fn with_shadow_brush(mut self, brush: Brush) -> Self {
597        self.shadow_brush = brush;
598        self
599    }
600
601    /// Sets desired shadow dilation in units. Keep in mind that the dilation is absolute,
602    /// not percentage-based.
603    pub fn with_shadow_dilation(mut self, thickness: f32) -> Self {
604        self.shadow_dilation = thickness;
605        self
606    }
607
608    /// Sets desired shadow offset in units.
609    pub fn with_shadow_offset(mut self, offset: Vector2<f32>) -> Self {
610        self.shadow_offset = offset;
611        self
612    }
613
614    /// Adds the given run to the text to set the style for a portion of the text.
615    /// Later runs potentially overriding earlier runs if the ranges of the runs overlap and the later run
616    /// sets a property that conflicts with an earlier run.
617    pub fn with_run(mut self, run: Run) -> Self {
618        self.runs.push(run);
619        self
620    }
621
622    /// Adds multiple runs to the text to set the style of portions of the text.
623    /// Later runs potentially overriding earlier runs if the ranges of the runs overlap and the later run
624    /// sets a property that conflicts with an earlier run.
625    pub fn with_runs<I: IntoIterator<Item = Run>>(mut self, runs: I) -> Self {
626        self.runs.extend(runs);
627        self
628    }
629
630    /// Finishes text widget creation and registers it in the user interface, returning its handle to you.
631    pub fn build(mut self, ctx: &mut BuildContext) -> Handle<UiNode> {
632        let font = if let Some(font) = self.font {
633            font
634        } else {
635            ctx.default_font()
636        };
637
638        if self.widget_builder.foreground.is_none() {
639            self.widget_builder.foreground = Some(ctx.style.property(Style::BRUSH_TEXT));
640        }
641
642        let text = Text {
643            widget: self.widget_builder.build(ctx),
644            formatted_text: RefCell::new(
645                FormattedTextBuilder::new(font)
646                    .with_text(self.text.unwrap_or_default())
647                    .with_vertical_alignment(self.vertical_text_alignment)
648                    .with_horizontal_alignment(self.horizontal_text_alignment)
649                    .with_wrap(self.wrap)
650                    .with_shadow(self.shadow)
651                    .with_shadow_brush(self.shadow_brush)
652                    .with_shadow_dilation(self.shadow_dilation)
653                    .with_shadow_offset(self.shadow_offset)
654                    .with_font_size(
655                        self.font_size
656                            .unwrap_or_else(|| ctx.style.property(Style::FONT_SIZE)),
657                    )
658                    .with_runs(self.runs)
659                    .build(),
660            ),
661        };
662        ctx.add_node(UiNode::new(text))
663    }
664}
665
666#[cfg(test)]
667mod test {
668    use crate::text::TextBuilder;
669    use crate::{test::test_widget_deletion, widget::WidgetBuilder};
670
671    #[test]
672    fn test_deletion() {
673        test_widget_deletion(|ctx| TextBuilder::new(WidgetBuilder::new()).build(ctx));
674    }
675}