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}