Skip to main content

egui/
widget_text.rs

1use emath::GuiRounding as _;
2use epaint::text::{IntoTag, TextFormat, VariationCoords};
3use std::fmt::Formatter;
4use std::{borrow::Cow, sync::Arc};
5
6use crate::{
7    Align, Color32, FontFamily, FontSelection, Galley, Style, TextStyle, TextWrapMode, Ui, Visuals,
8    text::{LayoutJob, TextWrapping},
9};
10
11/// Text and optional style choices for it.
12///
13/// The style choices (font, color) are applied to the entire text.
14/// For more detailed control, use [`crate::text::LayoutJob`] instead.
15///
16/// A [`RichText`] can be used in most widgets and helper functions, e.g. [`Ui::label`] and [`Ui::button`].
17///
18/// ### Example
19/// ```
20/// use egui::{RichText, Color32};
21///
22/// RichText::new("Plain");
23/// RichText::new("colored").color(Color32::RED);
24/// RichText::new("Large and underlined").size(20.0).underline();
25/// ```
26#[derive(Clone, Debug, PartialEq)]
27pub struct RichText {
28    text: String,
29    size: Option<f32>,
30    extra_letter_spacing: f32,
31    line_height: Option<f32>,
32    family: Option<FontFamily>,
33    text_style: Option<TextStyle>,
34    background_color: Color32,
35    expand_bg: f32,
36    text_color: Option<Color32>,
37    coords: VariationCoords,
38    code: bool,
39    strong: bool,
40    weak: bool,
41    strikethrough: bool,
42    underline: bool,
43    italics: bool,
44    raised: bool,
45}
46
47impl Default for RichText {
48    fn default() -> Self {
49        Self {
50            text: Default::default(),
51            size: Default::default(),
52            extra_letter_spacing: Default::default(),
53            line_height: Default::default(),
54            family: Default::default(),
55            text_style: Default::default(),
56            background_color: Default::default(),
57            expand_bg: 1.0,
58            text_color: Default::default(),
59            coords: Default::default(),
60            code: Default::default(),
61            strong: Default::default(),
62            weak: Default::default(),
63            strikethrough: Default::default(),
64            underline: Default::default(),
65            italics: Default::default(),
66            raised: Default::default(),
67        }
68    }
69}
70
71impl From<&str> for RichText {
72    #[inline]
73    fn from(text: &str) -> Self {
74        Self::new(text)
75    }
76}
77
78impl From<&String> for RichText {
79    #[inline]
80    fn from(text: &String) -> Self {
81        Self::new(text)
82    }
83}
84
85impl From<&mut String> for RichText {
86    #[inline]
87    fn from(text: &mut String) -> Self {
88        Self::new(text.clone())
89    }
90}
91
92impl From<String> for RichText {
93    #[inline]
94    fn from(text: String) -> Self {
95        Self::new(text)
96    }
97}
98
99impl From<&Box<str>> for RichText {
100    #[inline]
101    fn from(text: &Box<str>) -> Self {
102        Self::new(text.clone())
103    }
104}
105
106impl From<&mut Box<str>> for RichText {
107    #[inline]
108    fn from(text: &mut Box<str>) -> Self {
109        Self::new(text.clone())
110    }
111}
112
113impl From<Box<str>> for RichText {
114    #[inline]
115    fn from(text: Box<str>) -> Self {
116        Self::new(text)
117    }
118}
119
120impl From<Cow<'_, str>> for RichText {
121    #[inline]
122    fn from(text: Cow<'_, str>) -> Self {
123        Self::new(text)
124    }
125}
126
127impl RichText {
128    #[inline]
129    pub fn new(text: impl Into<String>) -> Self {
130        Self {
131            text: text.into(),
132            ..Default::default()
133        }
134    }
135
136    #[inline]
137    pub fn is_empty(&self) -> bool {
138        self.text.is_empty()
139    }
140
141    #[inline]
142    pub fn text(&self) -> &str {
143        &self.text
144    }
145
146    /// Select the font size (in points).
147    /// This overrides the value from [`Self::text_style`].
148    #[inline]
149    pub fn size(mut self, size: f32) -> Self {
150        self.size = Some(size);
151        self
152    }
153
154    /// Extra spacing between letters, in points.
155    ///
156    /// Default: 0.0.
157    ///
158    /// For even text it is recommended you round this to an even number of _pixels_,
159    /// e.g. using [`crate::Painter::round_to_pixel`].
160    #[inline]
161    pub fn extra_letter_spacing(mut self, extra_letter_spacing: f32) -> Self {
162        self.extra_letter_spacing = extra_letter_spacing;
163        self
164    }
165
166    /// Explicit line height of the text in points.
167    ///
168    /// This is the distance between the bottom row of two subsequent lines of text.
169    ///
170    /// If `None` (the default), the line height is determined by the font.
171    ///
172    /// For even text it is recommended you round this to an even number of _pixels_,
173    /// e.g. using [`crate::Painter::round_to_pixel`].
174    #[inline]
175    pub fn line_height(mut self, line_height: Option<f32>) -> Self {
176        self.line_height = line_height;
177        self
178    }
179
180    /// Select the font family.
181    ///
182    /// This overrides the value from [`Self::text_style`].
183    ///
184    /// Only the families available in [`crate::FontDefinitions::families`] may be used.
185    #[inline]
186    pub fn family(mut self, family: FontFamily) -> Self {
187        self.family = Some(family);
188        self
189    }
190
191    /// Select the font and size.
192    /// This overrides the value from [`Self::text_style`].
193    #[inline]
194    pub fn font(mut self, font_id: crate::FontId) -> Self {
195        let crate::FontId { size, family } = font_id;
196        self.size = Some(size);
197        self.family = Some(family);
198        self
199    }
200
201    /// Add a variation coordinate.
202    #[inline]
203    pub fn variation(mut self, tag: impl IntoTag, coord: f32) -> Self {
204        self.coords.push(tag, coord);
205        self
206    }
207
208    /// Override the variation coordinates completely.
209    #[inline]
210    pub fn variations<T: IntoTag>(
211        mut self,
212        variations: impl IntoIterator<Item = (T, f32)>,
213    ) -> Self {
214        self.coords = VariationCoords::new(variations);
215        self
216    }
217
218    /// Override the [`TextStyle`].
219    #[inline]
220    pub fn text_style(mut self, text_style: TextStyle) -> Self {
221        self.text_style = Some(text_style);
222        self
223    }
224
225    /// Set the [`TextStyle`] unless it has already been set
226    #[inline]
227    pub fn fallback_text_style(mut self, text_style: TextStyle) -> Self {
228        self.text_style.get_or_insert(text_style);
229        self
230    }
231
232    /// Use [`TextStyle::Heading`].
233    #[inline]
234    pub fn heading(self) -> Self {
235        self.text_style(TextStyle::Heading)
236    }
237
238    /// Use [`TextStyle::Monospace`].
239    #[inline]
240    pub fn monospace(self) -> Self {
241        self.text_style(TextStyle::Monospace)
242    }
243
244    /// Monospace label with different background color.
245    #[inline]
246    pub fn code(mut self) -> Self {
247        self.code = true;
248        self.text_style(TextStyle::Monospace)
249    }
250
251    /// Extra strong text (stronger color).
252    #[inline]
253    pub fn strong(mut self) -> Self {
254        self.strong = true;
255        self
256    }
257
258    /// Extra weak text (fainter color).
259    #[inline]
260    pub fn weak(mut self) -> Self {
261        self.weak = true;
262        self
263    }
264
265    /// Draw a line under the text.
266    ///
267    /// If you want to control the line color, use [`LayoutJob`] instead.
268    #[inline]
269    pub fn underline(mut self) -> Self {
270        self.underline = true;
271        self
272    }
273
274    /// Draw a line through the text, crossing it out.
275    ///
276    /// If you want to control the strikethrough line color, use [`LayoutJob`] instead.
277    #[inline]
278    pub fn strikethrough(mut self) -> Self {
279        self.strikethrough = true;
280        self
281    }
282
283    /// Tilt the characters to the right.
284    #[inline]
285    pub fn italics(mut self) -> Self {
286        self.italics = true;
287        self
288    }
289
290    /// Smaller text.
291    #[inline]
292    pub fn small(self) -> Self {
293        self.text_style(TextStyle::Small)
294    }
295
296    /// For e.g. exponents.
297    #[inline]
298    pub fn small_raised(self) -> Self {
299        self.text_style(TextStyle::Small).raised()
300    }
301
302    /// Align text to top. Only applicable together with [`Self::small()`].
303    #[inline]
304    pub fn raised(mut self) -> Self {
305        self.raised = true;
306        self
307    }
308
309    /// Fill-color behind the text.
310    #[inline]
311    pub fn background_color(mut self, background_color: impl Into<Color32>) -> Self {
312        self.background_color = background_color.into();
313        self
314    }
315
316    /// Override text color.
317    ///
318    /// If not set, [`Color32::PLACEHOLDER`] will be used,
319    /// which will be replaced with a color chosen by the widget that paints the text.
320    #[inline]
321    pub fn color(mut self, color: impl Into<Color32>) -> Self {
322        self.text_color = Some(color.into());
323        self
324    }
325
326    /// Read the font height of the selected text style.
327    ///
328    /// Returns a value rounded to [`emath::GUI_ROUNDING`].
329    pub fn font_height(&self, fonts: &mut epaint::FontsView<'_>, style: &Style) -> f32 {
330        let mut font_id = self.text_style.as_ref().map_or_else(
331            || FontSelection::Default.resolve(style),
332            |text_style| text_style.resolve(style),
333        );
334
335        if let Some(size) = self.size {
336            font_id.size = size;
337        }
338        if let Some(family) = &self.family {
339            font_id.family = family.clone();
340        }
341        fonts.row_height(&font_id)
342    }
343
344    /// Append to an existing [`LayoutJob`]
345    ///
346    /// Note that the color of the [`RichText`] must be set, or may default to an undesirable color.
347    ///
348    /// ### Example
349    /// ```
350    /// use egui::{Style, RichText, text::LayoutJob, Color32, FontSelection, Align};
351    ///
352    /// let style = Style::default();
353    /// let mut layout_job = LayoutJob::default();
354    /// RichText::new("Normal")
355    ///     .color(style.visuals.text_color())
356    ///     .append_to(
357    ///         &mut layout_job,
358    ///         &style,
359    ///         FontSelection::Default,
360    ///         Align::Center,
361    ///     );
362    /// RichText::new("Large and underlined")
363    ///     .color(style.visuals.text_color())
364    ///     .size(20.0)
365    ///     .underline()
366    ///     .append_to(
367    ///         &mut layout_job,
368    ///         &style,
369    ///         FontSelection::Default,
370    ///         Align::Center,
371    ///     );
372    /// ```
373    pub fn append_to(
374        self,
375        layout_job: &mut LayoutJob,
376        style: &Style,
377        fallback_font: FontSelection,
378        default_valign: Align,
379    ) {
380        let (text, format) = self.into_text_and_format(style, fallback_font, default_valign);
381
382        layout_job.append(&text, 0.0, format);
383    }
384
385    fn into_layout_job(
386        self,
387        style: &Style,
388        fallback_font: FontSelection,
389        default_valign: Align,
390    ) -> LayoutJob {
391        let (text, text_format) = self.into_text_and_format(style, fallback_font, default_valign);
392        LayoutJob::single_section(text, text_format)
393    }
394
395    fn into_text_and_format(
396        self,
397        style: &Style,
398        fallback_font: FontSelection,
399        default_valign: Align,
400    ) -> (String, crate::text::TextFormat) {
401        let text_color = self.get_text_color(&style.visuals);
402
403        let Self {
404            text,
405            size,
406            extra_letter_spacing,
407            line_height,
408            family,
409            text_style,
410            background_color,
411            expand_bg,
412            text_color: _, // already used by `get_text_color`
413            coords,
414            code,
415            strong: _, // already used by `get_text_color`
416            weak: _,   // already used by `get_text_color`
417            strikethrough,
418            underline,
419            italics,
420            raised,
421        } = self;
422
423        let line_color = text_color.unwrap_or_else(|| style.visuals.text_color());
424        let text_color = text_color.unwrap_or(crate::Color32::PLACEHOLDER);
425
426        let font_id = {
427            let mut font_id = style.override_font_id.clone().unwrap_or_else(|| {
428                (text_style.as_ref().or(style.override_text_style.as_ref()))
429                    .map(|text_style| text_style.resolve(style))
430                    .unwrap_or_else(|| fallback_font.resolve(style))
431            });
432            if let Some(size) = size {
433                font_id.size = size;
434            }
435            if let Some(family) = family {
436                font_id.family = family;
437            }
438            font_id
439        };
440
441        let background_color = if code {
442            style.visuals.code_bg_color
443        } else {
444            background_color
445        };
446
447        let underline = if underline {
448            crate::Stroke::new(1.0, line_color)
449        } else {
450            crate::Stroke::NONE
451        };
452        let strikethrough = if strikethrough {
453            crate::Stroke::new(1.0, line_color)
454        } else {
455            crate::Stroke::NONE
456        };
457
458        let valign = if raised {
459            crate::Align::TOP
460        } else {
461            default_valign
462        };
463
464        (
465            text,
466            crate::text::TextFormat {
467                font_id,
468                extra_letter_spacing,
469                line_height,
470                color: text_color,
471                background: background_color,
472                coords,
473                italics,
474                underline,
475                strikethrough,
476                valign,
477                expand_bg,
478            },
479        )
480    }
481
482    fn get_text_color(&self, visuals: &Visuals) -> Option<Color32> {
483        if let Some(text_color) = self.text_color {
484            Some(text_color)
485        } else if self.strong {
486            Some(visuals.strong_text_color())
487        } else if self.weak {
488            Some(visuals.weak_text_color())
489        } else {
490            visuals.override_text_color
491        }
492    }
493}
494
495// ----------------------------------------------------------------------------
496
497/// This is how you specify text for a widget.
498///
499/// A lot of widgets use `impl Into<WidgetText>` as an argument,
500/// allowing you to pass in [`String`], [`RichText`], [`LayoutJob`], and more.
501///
502/// Often a [`WidgetText`] is just a simple [`String`],
503/// but it can be a [`RichText`] (text with color, style, etc),
504/// a [`LayoutJob`] (for when you want full control of how the text looks)
505/// or text that has already been laid out in a [`Galley`].
506///
507/// You can color the text however you want, or use [`Color32::PLACEHOLDER`]
508/// which will be replaced with a color chosen by the widget that paints the text.
509#[derive(Clone)]
510pub enum WidgetText {
511    /// Plain unstyled text.
512    ///
513    /// We have this as a special case, as it is the common-case,
514    /// and it uses less memory than [`Self::RichText`].
515    Text(String),
516
517    /// Text and optional style choices for it.
518    ///
519    /// Prefer [`Self::Text`] if there is no styling, as it will be faster.
520    RichText(Arc<RichText>),
521
522    /// Use this [`LayoutJob`] when laying out the text.
523    ///
524    /// Only [`LayoutJob::text`] and [`LayoutJob::sections`] are guaranteed to be respected.
525    ///
526    /// [`TextWrapping::max_width`](epaint::text::TextWrapping::max_width), [`LayoutJob::halign`], [`LayoutJob::justify`]
527    /// and [`LayoutJob::first_row_min_height`] will likely be determined by the [`crate::Layout`]
528    /// of the [`Ui`] the widget is placed in.
529    /// If you want all parts of the [`LayoutJob`] respected, then convert it to a
530    /// [`Galley`] and use [`Self::Galley`] instead.
531    ///
532    /// You can color the text however you want, or use [`Color32::PLACEHOLDER`]
533    /// which will be replaced with a color chosen by the widget that paints the text.
534    LayoutJob(Arc<LayoutJob>),
535
536    /// Use exactly this galley when painting the text.
537    ///
538    /// You can color the text however you want, or use [`Color32::PLACEHOLDER`]
539    /// which will be replaced with a color chosen by the widget that paints the text.
540    Galley(Arc<Galley>),
541}
542
543impl std::fmt::Debug for WidgetText {
544    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
545        let text = self.text();
546        match self {
547            Self::Text(_) => write!(f, "Text({text:?})"),
548            Self::RichText(_) => write!(f, "RichText({text:?})"),
549            Self::LayoutJob(_) => write!(f, "LayoutJob({text:?})"),
550            Self::Galley(_) => write!(f, "Galley({text:?})"),
551        }
552    }
553}
554
555impl Default for WidgetText {
556    fn default() -> Self {
557        Self::Text(String::new())
558    }
559}
560
561impl WidgetText {
562    #[inline]
563    pub fn is_empty(&self) -> bool {
564        match self {
565            Self::Text(text) => text.is_empty(),
566            Self::RichText(text) => text.is_empty(),
567            Self::LayoutJob(job) => job.is_empty(),
568            Self::Galley(galley) => galley.is_empty(),
569        }
570    }
571
572    #[inline]
573    pub fn text(&self) -> &str {
574        match self {
575            Self::Text(text) => text,
576            Self::RichText(text) => text.text(),
577            Self::LayoutJob(job) => &job.text,
578            Self::Galley(galley) => galley.text(),
579        }
580    }
581
582    /// Map the contents based on the provided closure.
583    ///
584    /// - [`Self::Text`] => convert to [`RichText`] and call f
585    /// - [`Self::RichText`] => call f
586    /// - else do nothing
587    #[must_use]
588    fn map_rich_text<F>(self, f: F) -> Self
589    where
590        F: FnOnce(RichText) -> RichText,
591    {
592        match self {
593            Self::Text(text) => Self::RichText(Arc::new(f(RichText::new(text)))),
594            Self::RichText(text) => Self::RichText(Arc::new(f(Arc::unwrap_or_clone(text)))),
595            other => other,
596        }
597    }
598
599    /// Override the [`TextStyle`] if, and only if, this is a [`RichText`].
600    ///
601    /// Prefer using [`RichText`] directly!
602    #[inline]
603    pub fn text_style(self, text_style: TextStyle) -> Self {
604        self.map_rich_text(|text| text.text_style(text_style))
605    }
606
607    /// Set the [`TextStyle`] unless it has already been set
608    ///
609    /// Prefer using [`RichText`] directly!
610    #[inline]
611    pub fn fallback_text_style(self, text_style: TextStyle) -> Self {
612        self.map_rich_text(|text| text.fallback_text_style(text_style))
613    }
614
615    /// Override text color if, and only if, this is a [`RichText`].
616    ///
617    /// Prefer using [`RichText`] directly!
618    #[inline]
619    pub fn color(self, color: impl Into<Color32>) -> Self {
620        self.map_rich_text(|text| text.color(color))
621    }
622
623    /// Prefer using [`RichText`] directly!
624    #[inline]
625    pub fn heading(self) -> Self {
626        self.map_rich_text(|text| text.heading())
627    }
628
629    /// Prefer using [`RichText`] directly!
630    #[inline]
631    pub fn monospace(self) -> Self {
632        self.map_rich_text(|text| text.monospace())
633    }
634
635    /// Prefer using [`RichText`] directly!
636    #[inline]
637    pub fn code(self) -> Self {
638        self.map_rich_text(|text| text.code())
639    }
640
641    /// Prefer using [`RichText`] directly!
642    #[inline]
643    pub fn strong(self) -> Self {
644        self.map_rich_text(|text| text.strong())
645    }
646
647    /// Prefer using [`RichText`] directly!
648    #[inline]
649    pub fn weak(self) -> Self {
650        self.map_rich_text(|text| text.weak())
651    }
652
653    /// Prefer using [`RichText`] directly!
654    #[inline]
655    pub fn underline(self) -> Self {
656        self.map_rich_text(|text| text.underline())
657    }
658
659    /// Prefer using [`RichText`] directly!
660    #[inline]
661    pub fn strikethrough(self) -> Self {
662        self.map_rich_text(|text| text.strikethrough())
663    }
664
665    /// Prefer using [`RichText`] directly!
666    #[inline]
667    pub fn italics(self) -> Self {
668        self.map_rich_text(|text| text.italics())
669    }
670
671    /// Prefer using [`RichText`] directly!
672    #[inline]
673    pub fn small(self) -> Self {
674        self.map_rich_text(|text| text.small())
675    }
676
677    /// Prefer using [`RichText`] directly!
678    #[inline]
679    pub fn small_raised(self) -> Self {
680        self.map_rich_text(|text| text.small_raised())
681    }
682
683    /// Prefer using [`RichText`] directly!
684    #[inline]
685    pub fn raised(self) -> Self {
686        self.map_rich_text(|text| text.raised())
687    }
688
689    /// Prefer using [`RichText`] directly!
690    #[inline]
691    pub fn background_color(self, background_color: impl Into<Color32>) -> Self {
692        self.map_rich_text(|text| text.background_color(background_color))
693    }
694
695    /// Returns a value rounded to [`emath::GUI_ROUNDING`].
696    pub(crate) fn font_height(&self, fonts: &mut epaint::FontsView<'_>, style: &Style) -> f32 {
697        match self {
698            Self::Text(_) => fonts.row_height(&FontSelection::Default.resolve(style)),
699            Self::RichText(text) => text.font_height(fonts, style),
700            Self::LayoutJob(job) => job.font_height(fonts),
701            Self::Galley(galley) => {
702                if let Some(placed_row) = galley.rows.first() {
703                    placed_row.height().round_ui()
704                } else {
705                    galley.size().y.round_ui()
706                }
707            }
708        }
709    }
710
711    pub fn into_layout_job(
712        self,
713        style: &Style,
714        fallback_font: FontSelection,
715        default_valign: Align,
716    ) -> Arc<LayoutJob> {
717        match self {
718            Self::Text(text) => Arc::new(LayoutJob::simple_format(
719                text,
720                TextFormat {
721                    font_id: FontSelection::Default.resolve(style),
722                    color: crate::Color32::PLACEHOLDER,
723                    valign: default_valign,
724                    ..Default::default()
725                },
726            )),
727            Self::RichText(text) => Arc::new(Arc::unwrap_or_clone(text).into_layout_job(
728                style,
729                fallback_font,
730                default_valign,
731            )),
732            Self::LayoutJob(job) => job,
733            Self::Galley(galley) => Arc::clone(&galley.job),
734        }
735    }
736
737    /// Layout with wrap mode based on the containing [`Ui`].
738    ///
739    /// `wrap_mode`: override for [`Ui::wrap_mode`]
740    pub fn into_galley(
741        self,
742        ui: &Ui,
743        wrap_mode: Option<TextWrapMode>,
744        available_width: f32,
745        fallback_font: impl Into<FontSelection>,
746    ) -> Arc<Galley> {
747        let valign = ui.text_valign();
748        let style = ui.style();
749
750        let wrap_mode = wrap_mode.unwrap_or_else(|| ui.wrap_mode());
751        let text_wrapping = TextWrapping::from_wrap_mode_and_width(wrap_mode, available_width);
752
753        self.into_galley_impl(ui.ctx(), style, text_wrapping, fallback_font.into(), valign)
754    }
755
756    pub fn into_galley_impl(
757        self,
758        ctx: &crate::Context,
759        style: &Style,
760        text_wrapping: TextWrapping,
761        fallback_font: FontSelection,
762        default_valign: Align,
763    ) -> Arc<Galley> {
764        match self {
765            Self::Text(text) => {
766                let color = style
767                    .visuals
768                    .override_text_color
769                    .unwrap_or(crate::Color32::PLACEHOLDER);
770                let mut layout_job = LayoutJob::simple_format(
771                    text,
772                    TextFormat {
773                        // We want the style overrides to take precedence over the fallback font
774                        font_id: FontSelection::default()
775                            .resolve_with_fallback(style, fallback_font),
776                        color,
777                        valign: default_valign,
778                        ..Default::default()
779                    },
780                );
781                layout_job.wrap = text_wrapping;
782                ctx.fonts_mut(|f| f.layout_job(layout_job))
783            }
784            Self::RichText(text) => {
785                let mut layout_job = Arc::unwrap_or_clone(text).into_layout_job(
786                    style,
787                    fallback_font,
788                    default_valign,
789                );
790                layout_job.wrap = text_wrapping;
791                ctx.fonts_mut(|f| f.layout_job(layout_job))
792            }
793            Self::LayoutJob(job) => {
794                let mut job = Arc::unwrap_or_clone(job);
795                job.wrap = text_wrapping;
796                ctx.fonts_mut(|f| f.layout_job(job))
797            }
798            Self::Galley(galley) => galley,
799        }
800    }
801}
802
803impl From<&str> for WidgetText {
804    #[inline]
805    fn from(text: &str) -> Self {
806        Self::Text(text.to_owned())
807    }
808}
809
810impl From<&String> for WidgetText {
811    #[inline]
812    fn from(text: &String) -> Self {
813        Self::Text(text.clone())
814    }
815}
816
817impl From<String> for WidgetText {
818    #[inline]
819    fn from(text: String) -> Self {
820        Self::Text(text)
821    }
822}
823
824impl From<&Box<str>> for WidgetText {
825    #[inline]
826    fn from(text: &Box<str>) -> Self {
827        Self::Text(text.to_string())
828    }
829}
830
831impl From<Box<str>> for WidgetText {
832    #[inline]
833    fn from(text: Box<str>) -> Self {
834        Self::Text(text.into())
835    }
836}
837
838impl From<Cow<'_, str>> for WidgetText {
839    #[inline]
840    fn from(text: Cow<'_, str>) -> Self {
841        Self::Text(text.into_owned())
842    }
843}
844
845impl From<RichText> for WidgetText {
846    #[inline]
847    fn from(rich_text: RichText) -> Self {
848        Self::RichText(Arc::new(rich_text))
849    }
850}
851
852impl From<Arc<RichText>> for WidgetText {
853    #[inline]
854    fn from(rich_text: Arc<RichText>) -> Self {
855        Self::RichText(rich_text)
856    }
857}
858
859impl From<LayoutJob> for WidgetText {
860    #[inline]
861    fn from(layout_job: LayoutJob) -> Self {
862        Self::LayoutJob(Arc::new(layout_job))
863    }
864}
865
866impl From<Arc<LayoutJob>> for WidgetText {
867    #[inline]
868    fn from(layout_job: Arc<LayoutJob>) -> Self {
869        Self::LayoutJob(layout_job)
870    }
871}
872
873impl From<Arc<Galley>> for WidgetText {
874    #[inline]
875    fn from(galley: Arc<Galley>) -> Self {
876        Self::Galley(galley)
877    }
878}
879
880#[cfg(test)]
881mod tests {
882    use crate::WidgetText;
883
884    #[test]
885    fn ensure_small_widget_text() {
886        assert_eq!(size_of::<WidgetText>(), size_of::<String>());
887    }
888}