1use std::{borrow::Cow, sync::Arc};
2
3use crate::{
4 text::{LayoutJob, TextWrapping},
5 Align, Color32, FontFamily, FontSelection, Galley, Style, TextStyle, TextWrapMode, Ui, Visuals,
6};
7
8#[derive(Clone, Default, PartialEq)]
24pub struct RichText {
25 text: String,
26 size: Option<f32>,
27 extra_letter_spacing: f32,
28 line_height: Option<f32>,
29 family: Option<FontFamily>,
30 text_style: Option<TextStyle>,
31 background_color: Color32,
32 text_color: Option<Color32>,
33 code: bool,
34 strong: bool,
35 weak: bool,
36 strikethrough: bool,
37 underline: bool,
38 italics: bool,
39 raised: bool,
40}
41
42impl From<&str> for RichText {
43 #[inline]
44 fn from(text: &str) -> Self {
45 Self::new(text)
46 }
47}
48
49impl From<&String> for RichText {
50 #[inline]
51 fn from(text: &String) -> Self {
52 Self::new(text)
53 }
54}
55
56impl From<&mut String> for RichText {
57 #[inline]
58 fn from(text: &mut String) -> Self {
59 Self::new(text.clone())
60 }
61}
62
63impl From<String> for RichText {
64 #[inline]
65 fn from(text: String) -> Self {
66 Self::new(text)
67 }
68}
69
70impl From<&Box<str>> for RichText {
71 #[inline]
72 fn from(text: &Box<str>) -> Self {
73 Self::new(text.clone())
74 }
75}
76
77impl From<&mut Box<str>> for RichText {
78 #[inline]
79 fn from(text: &mut Box<str>) -> Self {
80 Self::new(text.clone())
81 }
82}
83
84impl From<Box<str>> for RichText {
85 #[inline]
86 fn from(text: Box<str>) -> Self {
87 Self::new(text)
88 }
89}
90
91impl From<Cow<'_, str>> for RichText {
92 #[inline]
93 fn from(text: Cow<'_, str>) -> Self {
94 Self::new(text)
95 }
96}
97
98impl RichText {
99 #[inline]
100 pub fn new(text: impl Into<String>) -> Self {
101 Self {
102 text: text.into(),
103 ..Default::default()
104 }
105 }
106
107 #[inline]
108 pub fn is_empty(&self) -> bool {
109 self.text.is_empty()
110 }
111
112 #[inline]
113 pub fn text(&self) -> &str {
114 &self.text
115 }
116
117 #[inline]
120 pub fn size(mut self, size: f32) -> Self {
121 self.size = Some(size);
122 self
123 }
124
125 #[inline]
132 pub fn extra_letter_spacing(mut self, extra_letter_spacing: f32) -> Self {
133 self.extra_letter_spacing = extra_letter_spacing;
134 self
135 }
136
137 #[inline]
146 pub fn line_height(mut self, line_height: Option<f32>) -> Self {
147 self.line_height = line_height;
148 self
149 }
150
151 #[inline]
157 pub fn family(mut self, family: FontFamily) -> Self {
158 self.family = Some(family);
159 self
160 }
161
162 #[inline]
165 pub fn font(mut self, font_id: crate::FontId) -> Self {
166 let crate::FontId { size, family } = font_id;
167 self.size = Some(size);
168 self.family = Some(family);
169 self
170 }
171
172 #[inline]
174 pub fn text_style(mut self, text_style: TextStyle) -> Self {
175 self.text_style = Some(text_style);
176 self
177 }
178
179 #[inline]
181 pub fn fallback_text_style(mut self, text_style: TextStyle) -> Self {
182 self.text_style.get_or_insert(text_style);
183 self
184 }
185
186 #[inline]
188 pub fn heading(self) -> Self {
189 self.text_style(TextStyle::Heading)
190 }
191
192 #[inline]
194 pub fn monospace(self) -> Self {
195 self.text_style(TextStyle::Monospace)
196 }
197
198 #[inline]
200 pub fn code(mut self) -> Self {
201 self.code = true;
202 self.text_style(TextStyle::Monospace)
203 }
204
205 #[inline]
207 pub fn strong(mut self) -> Self {
208 self.strong = true;
209 self
210 }
211
212 #[inline]
214 pub fn weak(mut self) -> Self {
215 self.weak = true;
216 self
217 }
218
219 #[inline]
223 pub fn underline(mut self) -> Self {
224 self.underline = true;
225 self
226 }
227
228 #[inline]
232 pub fn strikethrough(mut self) -> Self {
233 self.strikethrough = true;
234 self
235 }
236
237 #[inline]
239 pub fn italics(mut self) -> Self {
240 self.italics = true;
241 self
242 }
243
244 #[inline]
246 pub fn small(self) -> Self {
247 self.text_style(TextStyle::Small)
248 }
249
250 #[inline]
252 pub fn small_raised(self) -> Self {
253 self.text_style(TextStyle::Small).raised()
254 }
255
256 #[inline]
258 pub fn raised(mut self) -> Self {
259 self.raised = true;
260 self
261 }
262
263 #[inline]
265 pub fn background_color(mut self, background_color: impl Into<Color32>) -> Self {
266 self.background_color = background_color.into();
267 self
268 }
269
270 #[inline]
275 pub fn color(mut self, color: impl Into<Color32>) -> Self {
276 self.text_color = Some(color.into());
277 self
278 }
279
280 pub fn font_height(&self, fonts: &epaint::Fonts, style: &Style) -> f32 {
282 let mut font_id = self.text_style.as_ref().map_or_else(
283 || FontSelection::Default.resolve(style),
284 |text_style| text_style.resolve(style),
285 );
286
287 if let Some(size) = self.size {
288 font_id.size = size;
289 }
290 if let Some(family) = &self.family {
291 font_id.family = family.clone();
292 }
293 fonts.row_height(&font_id)
294 }
295
296 pub fn append_to(
326 self,
327 layout_job: &mut LayoutJob,
328 style: &Style,
329 fallback_font: FontSelection,
330 default_valign: Align,
331 ) {
332 let (text, format) = self.into_text_and_format(style, fallback_font, default_valign);
333
334 layout_job.append(&text, 0.0, format);
335 }
336
337 fn into_layout_job(
338 self,
339 style: &Style,
340 fallback_font: FontSelection,
341 default_valign: Align,
342 ) -> LayoutJob {
343 let (text, text_format) = self.into_text_and_format(style, fallback_font, default_valign);
344 LayoutJob::single_section(text, text_format)
345 }
346
347 fn into_text_and_format(
348 self,
349 style: &Style,
350 fallback_font: FontSelection,
351 default_valign: Align,
352 ) -> (String, crate::text::TextFormat) {
353 let text_color = self.get_text_color(&style.visuals);
354
355 let Self {
356 text,
357 size,
358 extra_letter_spacing,
359 line_height,
360 family,
361 text_style,
362 background_color,
363 text_color: _, code,
365 strong: _, weak: _, strikethrough,
368 underline,
369 italics,
370 raised,
371 } = self;
372
373 let line_color = text_color.unwrap_or_else(|| style.visuals.text_color());
374 let text_color = text_color.unwrap_or(crate::Color32::PLACEHOLDER);
375
376 let font_id = {
377 let mut font_id = text_style
378 .or_else(|| style.override_text_style.clone())
379 .map_or_else(
380 || fallback_font.resolve(style),
381 |text_style| text_style.resolve(style),
382 );
383 if let Some(fid) = style.override_font_id.clone() {
384 font_id = fid;
385 }
386 if let Some(size) = size {
387 font_id.size = size;
388 }
389 if let Some(family) = family {
390 font_id.family = family;
391 }
392 font_id
393 };
394
395 let mut background_color = background_color;
396 if code {
397 background_color = style.visuals.code_bg_color;
398 }
399 let underline = if underline {
400 crate::Stroke::new(1.0, line_color)
401 } else {
402 crate::Stroke::NONE
403 };
404 let strikethrough = if strikethrough {
405 crate::Stroke::new(1.0, line_color)
406 } else {
407 crate::Stroke::NONE
408 };
409
410 let valign = if raised {
411 crate::Align::TOP
412 } else {
413 default_valign
414 };
415
416 (
417 text,
418 crate::text::TextFormat {
419 font_id,
420 extra_letter_spacing,
421 line_height,
422 color: text_color,
423 background: background_color,
424 italics,
425 underline,
426 strikethrough,
427 valign,
428 },
429 )
430 }
431
432 fn get_text_color(&self, visuals: &Visuals) -> Option<Color32> {
433 if let Some(text_color) = self.text_color {
434 Some(text_color)
435 } else if self.strong {
436 Some(visuals.strong_text_color())
437 } else if self.weak {
438 Some(visuals.weak_text_color())
439 } else {
440 visuals.override_text_color
441 }
442 }
443}
444
445#[derive(Clone)]
460pub enum WidgetText {
461 RichText(RichText),
462
463 LayoutJob(LayoutJob),
476
477 Galley(Arc<Galley>),
482}
483
484impl Default for WidgetText {
485 fn default() -> Self {
486 Self::RichText(RichText::default())
487 }
488}
489
490impl WidgetText {
491 #[inline]
492 pub fn is_empty(&self) -> bool {
493 match self {
494 Self::RichText(text) => text.is_empty(),
495 Self::LayoutJob(job) => job.is_empty(),
496 Self::Galley(galley) => galley.is_empty(),
497 }
498 }
499
500 #[inline]
501 pub fn text(&self) -> &str {
502 match self {
503 Self::RichText(text) => text.text(),
504 Self::LayoutJob(job) => &job.text,
505 Self::Galley(galley) => galley.text(),
506 }
507 }
508
509 #[inline]
513 pub fn text_style(self, text_style: TextStyle) -> Self {
514 match self {
515 Self::RichText(text) => Self::RichText(text.text_style(text_style)),
516 Self::LayoutJob(_) | Self::Galley(_) => self,
517 }
518 }
519
520 #[inline]
524 pub fn fallback_text_style(self, text_style: TextStyle) -> Self {
525 match self {
526 Self::RichText(text) => Self::RichText(text.fallback_text_style(text_style)),
527 Self::LayoutJob(_) | Self::Galley(_) => self,
528 }
529 }
530
531 #[inline]
535 pub fn color(self, color: impl Into<Color32>) -> Self {
536 match self {
537 Self::RichText(text) => Self::RichText(text.color(color)),
538 Self::LayoutJob(_) | Self::Galley(_) => self,
539 }
540 }
541
542 pub fn heading(self) -> Self {
544 match self {
545 Self::RichText(text) => Self::RichText(text.heading()),
546 Self::LayoutJob(_) | Self::Galley(_) => self,
547 }
548 }
549
550 pub fn monospace(self) -> Self {
552 match self {
553 Self::RichText(text) => Self::RichText(text.monospace()),
554 Self::LayoutJob(_) | Self::Galley(_) => self,
555 }
556 }
557
558 pub fn code(self) -> Self {
560 match self {
561 Self::RichText(text) => Self::RichText(text.code()),
562 Self::LayoutJob(_) | Self::Galley(_) => self,
563 }
564 }
565
566 pub fn strong(self) -> Self {
568 match self {
569 Self::RichText(text) => Self::RichText(text.strong()),
570 Self::LayoutJob(_) | Self::Galley(_) => self,
571 }
572 }
573
574 pub fn weak(self) -> Self {
576 match self {
577 Self::RichText(text) => Self::RichText(text.weak()),
578 Self::LayoutJob(_) | Self::Galley(_) => self,
579 }
580 }
581
582 pub fn underline(self) -> Self {
584 match self {
585 Self::RichText(text) => Self::RichText(text.underline()),
586 Self::LayoutJob(_) | Self::Galley(_) => self,
587 }
588 }
589
590 pub fn strikethrough(self) -> Self {
592 match self {
593 Self::RichText(text) => Self::RichText(text.strikethrough()),
594 Self::LayoutJob(_) | Self::Galley(_) => self,
595 }
596 }
597
598 pub fn italics(self) -> Self {
600 match self {
601 Self::RichText(text) => Self::RichText(text.italics()),
602 Self::LayoutJob(_) | Self::Galley(_) => self,
603 }
604 }
605
606 pub fn small(self) -> Self {
608 match self {
609 Self::RichText(text) => Self::RichText(text.small()),
610 Self::LayoutJob(_) | Self::Galley(_) => self,
611 }
612 }
613
614 pub fn small_raised(self) -> Self {
616 match self {
617 Self::RichText(text) => Self::RichText(text.small_raised()),
618 Self::LayoutJob(_) | Self::Galley(_) => self,
619 }
620 }
621
622 pub fn raised(self) -> Self {
624 match self {
625 Self::RichText(text) => Self::RichText(text.raised()),
626 Self::LayoutJob(_) | Self::Galley(_) => self,
627 }
628 }
629
630 pub fn background_color(self, background_color: impl Into<Color32>) -> Self {
632 match self {
633 Self::RichText(text) => Self::RichText(text.background_color(background_color)),
634 Self::LayoutJob(_) | Self::Galley(_) => self,
635 }
636 }
637
638 pub(crate) fn font_height(&self, fonts: &epaint::Fonts, style: &Style) -> f32 {
639 match self {
640 Self::RichText(text) => text.font_height(fonts, style),
641 Self::LayoutJob(job) => job.font_height(fonts),
642 Self::Galley(galley) => {
643 if let Some(row) = galley.rows.first() {
644 row.height()
645 } else {
646 galley.size().y
647 }
648 }
649 }
650 }
651
652 pub fn into_layout_job(
653 self,
654 style: &Style,
655 fallback_font: FontSelection,
656 default_valign: Align,
657 ) -> LayoutJob {
658 match self {
659 Self::RichText(text) => text.into_layout_job(style, fallback_font, default_valign),
660 Self::LayoutJob(job) => job,
661 Self::Galley(galley) => (*galley.job).clone(),
662 }
663 }
664
665 pub fn into_galley(
669 self,
670 ui: &Ui,
671 wrap_mode: Option<TextWrapMode>,
672 available_width: f32,
673 fallback_font: impl Into<FontSelection>,
674 ) -> Arc<Galley> {
675 let valign = ui.text_valign();
676 let style = ui.style();
677
678 let wrap_mode = wrap_mode.unwrap_or_else(|| ui.wrap_mode());
679 let text_wrapping = TextWrapping::from_wrap_mode_and_width(wrap_mode, available_width);
680
681 self.into_galley_impl(ui.ctx(), style, text_wrapping, fallback_font.into(), valign)
682 }
683
684 pub fn into_galley_impl(
685 self,
686 ctx: &crate::Context,
687 style: &Style,
688 text_wrapping: TextWrapping,
689 fallback_font: FontSelection,
690 default_valign: Align,
691 ) -> Arc<Galley> {
692 match self {
693 Self::RichText(text) => {
694 let mut layout_job = text.into_layout_job(style, fallback_font, default_valign);
695 layout_job.wrap = text_wrapping;
696 ctx.fonts(|f| f.layout_job(layout_job))
697 }
698 Self::LayoutJob(mut job) => {
699 job.wrap = text_wrapping;
700 ctx.fonts(|f| f.layout_job(job))
701 }
702 Self::Galley(galley) => galley,
703 }
704 }
705}
706
707impl From<&str> for WidgetText {
708 #[inline]
709 fn from(text: &str) -> Self {
710 Self::RichText(RichText::new(text))
711 }
712}
713
714impl From<&String> for WidgetText {
715 #[inline]
716 fn from(text: &String) -> Self {
717 Self::RichText(RichText::new(text))
718 }
719}
720
721impl From<String> for WidgetText {
722 #[inline]
723 fn from(text: String) -> Self {
724 Self::RichText(RichText::new(text))
725 }
726}
727
728impl From<&Box<str>> for WidgetText {
729 #[inline]
730 fn from(text: &Box<str>) -> Self {
731 Self::RichText(RichText::new(text.clone()))
732 }
733}
734
735impl From<Box<str>> for WidgetText {
736 #[inline]
737 fn from(text: Box<str>) -> Self {
738 Self::RichText(RichText::new(text))
739 }
740}
741
742impl From<Cow<'_, str>> for WidgetText {
743 #[inline]
744 fn from(text: Cow<'_, str>) -> Self {
745 Self::RichText(RichText::new(text))
746 }
747}
748
749impl From<RichText> for WidgetText {
750 #[inline]
751 fn from(rich_text: RichText) -> Self {
752 Self::RichText(rich_text)
753 }
754}
755
756impl From<LayoutJob> for WidgetText {
757 #[inline]
758 fn from(layout_job: LayoutJob) -> Self {
759 Self::LayoutJob(layout_job)
760 }
761}
762
763impl From<Arc<Galley>> for WidgetText {
764 #[inline]
765 fn from(galley: Arc<Galley>) -> Self {
766 Self::Galley(galley)
767 }
768}