egui/
widget_text.rs

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