diff --git a/egui/src/widget_text.rs b/egui/src/widget_text.rs index 4efda002091..5174683a2aa 100644 --- a/egui/src/widget_text.rs +++ b/egui/src/widget_text.rs @@ -9,9 +9,17 @@ use crate::{style::WidgetVisuals, text::LayoutJob, Color32, Galley, Pos2, TextSt #[derive(Default)] pub struct RichText { text: String, + wrap: Option, text_style: Option, + background_color: Color32, text_color: Option, - wrap: Option, + code: bool, + strong: bool, + weak: bool, + strikethrough: bool, + underline: bool, + italics: bool, + raised: bool, } impl RichText { @@ -23,6 +31,19 @@ impl RichText { } } + /// If `true`, the text will wrap at the `max_width`. + /// + /// By default [`Self::wrap`] will be true in vertical layouts + /// and horizontal layouts with wrapping, + /// and false on non-wrapping horizontal layouts. + /// + /// Note that any `\n` in the text will always produce a new line. + #[inline] + pub fn wrap(mut self, wrap: bool) -> Self { + self.wrap = Some(wrap); + self + } + #[inline] pub fn text(&self) -> &str { &self.text @@ -54,26 +75,86 @@ impl RichText { self.text_style(TextStyle::Monospace) } - /// Override text color. + /// Monospace label with different background color. #[inline] - pub fn color(mut self, color: impl Into) -> Self { - self.text_color = Some(color.into()); + pub fn code(mut self) -> Self { + self.code = true; + self.text_style(TextStyle::Monospace) + } + + /// Extra strong text (stronger color). + #[inline] + pub fn strong(mut self) -> Self { + self.strong = true; self } - /// If `true`, the text will wrap at the `max_width`. + /// Extra weak text (fainter color). + #[inline] + pub fn weak(mut self) -> Self { + self.weak = true; + self + } + + /// Draw a line under the text. /// - /// By default [`Self::wrap`] will be true in vertical layouts - /// and horizontal layouts with wrapping, - /// and false on non-wrapping horizontal layouts. + /// If you want to control the line color, use [`LayoutJob`] instead. + #[inline] + pub fn underline(mut self) -> Self { + self.underline = true; + self + } + + /// Draw a line through the text, crossing it out. /// - /// Note that any `\n` in the text will always produce a new line. + /// If you want to control the strikethrough line color, use [`LayoutJob`] instead. #[inline] - pub fn wrap(mut self, wrap: bool) -> Self { - self.wrap = Some(wrap); + pub fn strikethrough(mut self) -> Self { + self.strikethrough = true; + self + } + + /// Tilt the characters to the right. + #[inline] + pub fn italics(mut self) -> Self { + self.italics = true; self } + /// Smaller text. + #[inline] + pub fn small(self) -> Self { + self.text_style(TextStyle::Small) + } + + /// For e.g. exponents. + #[inline] + pub fn small_raised(self) -> Self { + self.text_style(TextStyle::Small).raised() + } + + /// Align text to top. Only applicable together with [`Self::small()`]. + #[inline] + pub fn raised(mut self) -> Self { + self.raised = true; + self + } + + /// Fill-color behind the text. + #[inline] + pub fn background_color(mut self, background_color: impl Into) -> Self { + self.background_color = background_color.into(); + self + } + + /// Override text color. + #[inline] + pub fn color(mut self, color: impl Into) -> Self { + self.text_color = Some(color.into()); + self + } + + /// Read the font height of the selected text style. pub fn font_height(&self, fonts: &epaint::text::Fonts, style: &crate::Style) -> f32 { let text_style = self .text_style @@ -82,19 +163,33 @@ impl RichText { fonts.row_height(text_style) } - pub fn layout( + pub fn layout_job( self, ui: &Ui, wrap_width: f32, default_text_style: TextStyle, - ) -> WidgetTextGalley { + ) -> WidgetTextJob { + let text_color = self.get_text_color(ui); + let Self { text, - text_style, - text_color, wrap, + text_style, + background_color, + text_color: _, // already used by `get_text_color` + code, + strong: _, // already used by `get_text_color` + weak: _, // already used by `get_text_color` + strikethrough, + underline, + italics, + raised, } = self; + let job_has_color = text_color.is_some(); + let line_color = text_color.unwrap_or_else(|| ui.visuals().text_color()); + let text_color = text_color.unwrap_or(crate::Color32::TEMPORARY_COLOR); + let wrap = wrap.unwrap_or_else(|| ui.wrap_text()); let wrap_width = if wrap { wrap_width } else { f32::INFINITY }; @@ -102,26 +197,64 @@ impl RichText { .or(ui.style().override_text_style) .unwrap_or(default_text_style); - let text_color = text_color.or(ui.visuals().override_text_color); - - if let Some(text_color) = text_color { - let galley = ui.fonts().layout(text, text_style, text_color, wrap_width); + let mut background_color = background_color; + if code { + background_color = ui.visuals().code_bg_color; + } + let underline = if underline { + crate::Stroke::new(1.0, line_color) + } else { + crate::Stroke::none() + }; + let strikethrough = if strikethrough { + crate::Stroke::new(1.0, line_color) + } else { + crate::Stroke::none() + }; - WidgetTextGalley { - galley, - galley_has_color: true, - } + let valign = if raised { + crate::Align::TOP } else { - let galley = ui - .fonts() - .layout_delayed_color(text, text_style, wrap_width); + ui.layout().vertical_align() + }; + + let text_format = crate::text::TextFormat { + style: text_style, + color: text_color, + background: background_color, + italics, + underline, + strikethrough, + valign, + }; + + let mut job = LayoutJob::single_section(text, text_format); + job.wrap_width = wrap_width; + + WidgetTextJob { job, job_has_color } + } - WidgetTextGalley { - galley, - galley_has_color: false, - } + fn get_text_color(&self, ui: &Ui) -> Option { + if let Some(text_color) = self.text_color { + Some(text_color) + } else if self.strong { + Some(ui.visuals().strong_text_color()) + } else if self.weak { + Some(ui.visuals().weak_text_color()) + } else { + ui.visuals().override_text_color } } + + pub fn layout( + self, + ui: &Ui, + wrap_width: f32, + default_text_style: TextStyle, + ) -> WidgetTextGalley { + let job = self.layout_job(ui, wrap_width, default_text_style); + job.layout(ui.fonts()) + } } // ---------------------------------------------------------------------------- @@ -258,6 +391,24 @@ impl From> for WidgetText { // ---------------------------------------------------------------------------- +pub struct WidgetTextJob { + job: LayoutJob, + job_has_color: bool, +} + +impl WidgetTextJob { + pub fn layout(self, fonts: &crate::text::Fonts) -> WidgetTextGalley { + let Self { job, job_has_color } = self; + let galley = fonts.layout_job(job); + WidgetTextGalley { + galley, + galley_has_color: job_has_color, + } + } +} + +// ---------------------------------------------------------------------------- + /// Text that has been layed out and ready to be painted. pub struct WidgetTextGalley { galley: Arc, diff --git a/epaint/src/text/text_layout_types.rs b/epaint/src/text/text_layout_types.rs index c70efcfe58e..d1e80de9a2b 100644 --- a/epaint/src/text/text_layout_types.rs +++ b/epaint/src/text/text_layout_types.rs @@ -118,6 +118,21 @@ impl LayoutJob { } } + #[inline] + pub fn single_section(text: String, format: TextFormat) -> Self { + Self { + sections: vec![LayoutSection { + leading_space: 0.0, + byte_range: 0..text.len(), + format, + }], + text, + wrap_width: f32::INFINITY, + break_on_newline: true, + ..Default::default() + } + } + #[inline(always)] pub fn is_empty(&self) -> bool { self.sections.is_empty()