From 0561fcaba957d0e7f3966db265892a9dab4b49f8 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 22 Dec 2023 15:09:10 +0100 Subject: [PATCH] Replace a special `Color32::PLACEHOLDER` with widget fallback color (#3727) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This introduces a special `Color32::PLACEHOLDER` which, during text painting, will be replaced with `TextShape::fallback_color`. The fallback color is mandatory to set in all text painting. Usually this comes from the current visual style. This lets users color only parts of a `WidgetText` (using e.g. a `LayoutJob` or a `Galley`), where the uncolored parts (using `Color32::PLACEHOLDER`) will be replaced by a default widget color (e.g. blue for a hyperlink). For instance, you can color the `⚠️`-emoji red in a piece of text red and leave the rest of the text uncolored. The color of the rest of the text will then depend on wether or not you put that text in a label, a button, or a hyperlink. Overall this simplifies a lot of complexity in the code but comes with a few breaking changes: * `TextShape::new`, `Shape::galley`, and `Painter::galley` now take a fallback color by argument * `Shape::galley_with_color` has been deprecated (use `Shape::galley` instead) * `Painter::galley_with_color` has been deprecated (use `Painter::galley` instead) * `WidgetTextGalley` is gone (use `Arc` instead) * `WidgetTextJob` is gone (use `LayoutJob` instead) * `RichText::into_text_job` has been replaced with `RichText::into_layout_job` * `WidgetText::into_text_job` has been replaced with `WidgetText::into_layout_job` --- crates/ecolor/src/color32.rs | 13 +- .../egui/src/containers/collapsing_header.rs | 12 +- crates/egui/src/containers/combo_box.rs | 3 +- crates/egui/src/containers/window.rs | 12 +- crates/egui/src/menu.rs | 4 +- crates/egui/src/painter.rs | 59 ++++--- crates/egui/src/ui.rs | 5 +- crates/egui/src/widget_text.rs | 150 ++++-------------- crates/egui/src/widgets/button.rs | 63 ++++---- crates/egui/src/widgets/hyperlink.rs | 13 +- crates/egui/src/widgets/label.rs | 90 +++++------ crates/egui/src/widgets/progress_bar.rs | 8 +- crates/egui/src/widgets/selected_label.rs | 10 +- crates/egui/src/widgets/text_edit/builder.rs | 9 +- crates/egui_demo_app/src/apps/http_app.rs | 6 +- crates/egui_demo_lib/benches/benchmark.rs | 10 +- crates/egui_plot/src/axis.rs | 16 +- crates/egui_plot/src/items/mod.rs | 6 +- crates/egui_plot/src/legend.rs | 2 +- crates/epaint/src/shape.rs | 64 ++++++-- crates/epaint/src/tessellator.rs | 6 + crates/epaint/src/text/fonts.rs | 7 +- crates/epaint/src/text/text_layout_types.rs | 5 +- 23 files changed, 267 insertions(+), 306 deletions(-) diff --git a/crates/ecolor/src/color32.rs b/crates/ecolor/src/color32.rs index 56b96589256..32e1487bc7f 100644 --- a/crates/ecolor/src/color32.rs +++ b/crates/ecolor/src/color32.rs @@ -7,6 +7,8 @@ use crate::{gamma_u8_from_linear_f32, linear_f32_from_gamma_u8, linear_f32_from_ /// /// Internally this uses 0-255 gamma space `sRGBA` color with premultiplied alpha. /// Alpha channel is in linear space. +/// +/// The special value of alpha=0 means the color is to be treated as an additive color. #[repr(C)] #[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] @@ -61,7 +63,16 @@ impl Color32 { pub const DEBUG_COLOR: Color32 = Color32::from_rgba_premultiplied(0, 200, 0, 128); /// An ugly color that is planned to be replaced before making it to the screen. - pub const TEMPORARY_COLOR: Color32 = Color32::from_rgb(64, 254, 0); + /// + /// This is an invalid color, in that it does not correspond to a valid multiplied color, + /// nor to an additive color. + /// + /// This is used as a special color key, + /// i.e. often taken to mean "no color". + pub const PLACEHOLDER: Color32 = Color32::from_rgba_premultiplied(64, 254, 0, 128); + + #[deprecated = "Renmaed to PLACEHOLDER"] + pub const TEMPORARY_COLOR: Color32 = Self::PLACEHOLDER; #[inline] pub const fn from_rgb(r: u8, g: u8, b: u8) -> Self { diff --git a/crates/egui/src/containers/collapsing_header.rs b/crates/egui/src/containers/collapsing_header.rs index 96bf5cdef7e..f4738fc0e0d 100644 --- a/crates/egui/src/containers/collapsing_header.rs +++ b/crates/egui/src/containers/collapsing_header.rs @@ -495,22 +495,22 @@ impl CollapsingHeader { let text_pos = available.min + vec2(ui.spacing().indent, 0.0); let wrap_width = available.right() - text_pos.x; let wrap = Some(false); - let text = text.into_galley(ui, wrap, wrap_width, TextStyle::Button); - let text_max_x = text_pos.x + text.size().x; + let galley = text.into_galley(ui, wrap, wrap_width, TextStyle::Button); + let text_max_x = text_pos.x + galley.size().x; let mut desired_width = text_max_x + button_padding.x - available.left(); if ui.visuals().collapsing_header_frame { desired_width = desired_width.max(available.width()); // fill full width } - let mut desired_size = vec2(desired_width, text.size().y + 2.0 * button_padding.y); + let mut desired_size = vec2(desired_width, galley.size().y + 2.0 * button_padding.y); desired_size = desired_size.at_least(ui.spacing().interact_size); let (_, rect) = ui.allocate_space(desired_size); let mut header_response = ui.interact(rect, id, Sense::click()); let text_pos = pos2( text_pos.x, - header_response.rect.center().y - text.size().y / 2.0, + header_response.rect.center().y - galley.size().y / 2.0, ); let mut state = CollapsingState::load_with_default_open(ui.ctx(), id, default_open); @@ -525,7 +525,7 @@ impl CollapsingHeader { } header_response - .widget_info(|| WidgetInfo::labeled(WidgetType::CollapsingHeader, text.text())); + .widget_info(|| WidgetInfo::labeled(WidgetType::CollapsingHeader, galley.text())); let openness = state.openness(ui.ctx()); @@ -563,7 +563,7 @@ impl CollapsingHeader { } } - text.paint_with_visuals(ui.painter(), text_pos, &visuals); + ui.painter().galley(text_pos, galley, visuals.text_color()); } Prepared { diff --git a/crates/egui/src/containers/combo_box.rs b/crates/egui/src/containers/combo_box.rs index 4bc3edc4b57..70ed046b396 100644 --- a/crates/egui/src/containers/combo_box.rs +++ b/crates/egui/src/containers/combo_box.rs @@ -327,7 +327,8 @@ fn combo_box_dyn<'c, R>( } let text_rect = Align2::LEFT_CENTER.align_size_within_rect(galley.size(), rect); - galley.paint_with_visuals(ui.painter(), text_rect.min, visuals); + ui.painter() + .galley(text_rect.min, galley, visuals.text_color()); } }); diff --git a/crates/egui/src/containers/window.rs b/crates/egui/src/containers/window.rs index cc0f28e90e5..4182af4e8c3 100644 --- a/crates/egui/src/containers/window.rs +++ b/crates/egui/src/containers/window.rs @@ -1,7 +1,9 @@ // WARNING: the code in here is horrible. It is a behemoth that needs breaking up into simpler parts. +use std::sync::Arc; + use crate::collapsing_header::CollapsingState; -use crate::{widget_text::WidgetTextGalley, *}; +use crate::*; use epaint::*; use super::*; @@ -885,7 +887,7 @@ struct TitleBar { id: Id, /// Prepared text in the title - title_galley: WidgetTextGalley, + title_galley: Arc, /// Size of the title bar in a collapsed state (if window is collapsible), /// which includes all necessary space for showing the expand button, the @@ -984,11 +986,11 @@ impl TitleBar { let full_top_rect = Rect::from_x_y_ranges(self.rect.x_range(), self.min_rect.y_range()); let text_pos = emath::align::center_size_in_rect(self.title_galley.size(), full_top_rect).left_top(); - let text_pos = text_pos - self.title_galley.galley().rect.min.to_vec2(); + let text_pos = text_pos - self.title_galley.rect.min.to_vec2(); let text_pos = text_pos - 1.5 * Vec2::Y; // HACK: center on x-height of text (looks better) - self.title_galley.paint_with_fallback_color( - ui.painter(), + ui.painter().galley( text_pos, + self.title_galley.clone(), ui.visuals().text_color(), ); diff --git a/crates/egui/src/menu.rs b/crates/egui/src/menu.rs index 34b33a31fb6..bd7305ed80e 100644 --- a/crates/egui/src/menu.rs +++ b/crates/egui/src/menu.rs @@ -503,8 +503,8 @@ impl SubMenuButton { } let text_color = visuals.text_color(); - text_galley.paint_with_fallback_color(ui.painter(), text_pos, text_color); - icon_galley.paint_with_fallback_color(ui.painter(), icon_pos, text_color); + ui.painter().galley(text_pos, text_galley, text_color); + ui.painter().galley(icon_pos, icon_galley, text_color); } response } diff --git a/crates/egui/src/painter.rs b/crates/egui/src/painter.rs index 8376d16ed7b..dfae0a92cfe 100644 --- a/crates/egui/src/painter.rs +++ b/crates/egui/src/painter.rs @@ -88,7 +88,7 @@ impl Painter { /// ## Accessors etc impl Painter { /// Get a reference to the parent [`Context`]. - #[inline(always)] + #[inline] pub fn ctx(&self) -> &Context { &self.ctx } @@ -96,45 +96,45 @@ impl Painter { /// Read-only access to the shared [`Fonts`]. /// /// See [`Context`] documentation for how locks work. - #[inline(always)] + #[inline] pub fn fonts(&self, reader: impl FnOnce(&Fonts) -> R) -> R { self.ctx.fonts(reader) } /// Where we paint - #[inline(always)] + #[inline] pub fn layer_id(&self) -> LayerId { self.layer_id } /// Everything painted in this [`Painter`] will be clipped against this. /// This means nothing outside of this rectangle will be visible on screen. - #[inline(always)] + #[inline] pub fn clip_rect(&self) -> Rect { self.clip_rect } /// Everything painted in this [`Painter`] will be clipped against this. /// This means nothing outside of this rectangle will be visible on screen. - #[inline(always)] + #[inline] pub fn set_clip_rect(&mut self, clip_rect: Rect) { self.clip_rect = clip_rect; } /// Useful for pixel-perfect rendering. - #[inline(always)] + #[inline] pub fn round_to_pixel(&self, point: f32) -> f32 { self.ctx().round_to_pixel(point) } /// Useful for pixel-perfect rendering. - #[inline(always)] + #[inline] pub fn round_vec_to_pixels(&self, vec: Vec2) -> Vec2 { self.ctx().round_vec_to_pixels(vec) } /// Useful for pixel-perfect rendering. - #[inline(always)] + #[inline] pub fn round_pos_to_pixels(&self, pos: Pos2) -> Pos2 { self.ctx().round_pos_to_pixels(pos) } @@ -236,7 +236,7 @@ impl Painter { 0.0, Color32::from_black_alpha(150), )); - self.galley(rect.min, galley); + self.galley(rect.min, galley, color); frame_rect } } @@ -379,14 +379,15 @@ impl Painter { ) -> Rect { let galley = self.layout_no_wrap(text.to_string(), font_id, text_color); let rect = anchor.anchor_rect(Rect::from_min_size(pos, galley.size())); - self.galley(rect.min, galley); + self.galley(rect.min, galley, text_color); rect } /// Will wrap text at the given width and line break at `\n`. /// /// Paint the results with [`Self::galley`]. - #[inline(always)] + #[inline] + #[must_use] pub fn layout( &self, text: String, @@ -400,7 +401,8 @@ impl Painter { /// Will line break at `\n`. /// /// Paint the results with [`Self::galley`]. - #[inline(always)] + #[inline] + #[must_use] pub fn layout_no_wrap( &self, text: String, @@ -414,11 +416,13 @@ impl Painter { /// /// You can create the [`Galley`] with [`Self::layout`]. /// - /// If you want to change the color of the text, use [`Self::galley_with_color`]. - #[inline(always)] - pub fn galley(&self, pos: Pos2, galley: Arc) { + /// Any uncolored parts of the [`Galley`] (using [`Color32::PLACEHOLDER`]) will be replaced with the given color. + /// + /// Any non-placeholder color in the galley takes precedence over this fallback color. + #[inline] + pub fn galley(&self, pos: Pos2, galley: Arc, fallback_color: Color32) { if !galley.is_empty() { - self.add(Shape::galley(pos, galley)); + self.add(Shape::galley(pos, galley, fallback_color)); } } @@ -426,11 +430,28 @@ impl Painter { /// /// You can create the [`Galley`] with [`Self::layout`]. /// - /// The text color in the [`Galley`] will be replaced with the given color. - #[inline(always)] + /// All text color in the [`Galley`] will be replaced with the given color. + #[inline] + pub fn galley_with_override_text_color( + &self, + pos: Pos2, + galley: Arc, + text_color: Color32, + ) { + if !galley.is_empty() { + self.add(Shape::galley_with_override_text_color( + pos, galley, text_color, + )); + } + } + + #[deprecated = "Use `Painter::galley` or `Painter::galley_with_override_text_color` instead"] + #[inline] pub fn galley_with_color(&self, pos: Pos2, galley: Arc, text_color: Color32) { if !galley.is_empty() { - self.add(Shape::galley_with_color(pos, galley, text_color)); + self.add(Shape::galley_with_override_text_color( + pos, galley, text_color, + )); } } } diff --git a/crates/egui/src/ui.rs b/crates/egui/src/ui.rs index 8474bcb0e72..4c4c0501f3a 100644 --- a/crates/egui/src/ui.rs +++ b/crates/egui/src/ui.rs @@ -2269,7 +2269,8 @@ fn register_rect(ui: &Ui, rect: Rect) { if !callstack.is_empty() { let font_id = FontId::monospace(12.0); let text = format!("{callstack}\n\n(click to copy)"); - let galley = painter.layout_no_wrap(text, font_id, Color32::WHITE); + let text_color = Color32::WHITE; + let galley = painter.layout_no_wrap(text, font_id, text_color); // Position the text either under or above: let screen_rect = ui.ctx().screen_rect(); @@ -2299,7 +2300,7 @@ fn register_rect(ui: &Ui, rect: Rect) { }; let text_rect = Rect::from_min_size(text_pos, galley.size()); painter.rect(text_rect, 0.0, text_bg_color, (1.0, text_rect_stroke_color)); - painter.galley(text_pos, galley); + painter.galley(text_pos, galley, text_color); if ui.input(|i| i.pointer.any_click()) { ui.ctx().copy_text(callstack); diff --git a/crates/egui/src/widget_text.rs b/crates/egui/src/widget_text.rs index fe0de370c01..9bad481cb38 100644 --- a/crates/egui/src/widget_text.rs +++ b/crates/egui/src/widget_text.rs @@ -1,8 +1,8 @@ use std::{borrow::Cow, sync::Arc}; use crate::{ - style::WidgetVisuals, text::LayoutJob, Align, Color32, FontFamily, FontSelection, Galley, Pos2, - Style, TextStyle, Ui, Visuals, + text::LayoutJob, Align, Color32, FontFamily, FontSelection, Galley, Style, TextStyle, Ui, + Visuals, }; /// Text and optional style choices for it. @@ -247,6 +247,9 @@ impl RichText { } /// Override text color. + /// + /// If not set, [`Color32::PLACEHOLDER`] will be used, + /// which will be replaced with a color chosen by the widget that paints the text. #[inline] pub fn color(mut self, color: impl Into) -> Self { self.text_color = Some(color.into()); @@ -310,17 +313,14 @@ impl RichText { layout_job.append(&text, 0.0, format); } - fn into_text_job( + fn into_layout_job( self, style: &Style, fallback_font: FontSelection, default_valign: Align, - ) -> WidgetTextJob { - let job_has_color = self.get_text_color(&style.visuals).is_some(); + ) -> LayoutJob { let (text, text_format) = self.into_text_and_format(style, fallback_font, default_valign); - - let job = LayoutJob::single_section(text, text_format); - WidgetTextJob { job, job_has_color } + LayoutJob::single_section(text, text_format) } fn into_text_and_format( @@ -350,7 +350,7 @@ impl RichText { } = self; let line_color = text_color.unwrap_or_else(|| style.visuals.text_color()); - let text_color = text_color.unwrap_or(crate::Color32::TEMPORARY_COLOR); + let text_color = text_color.unwrap_or(crate::Color32::PLACEHOLDER); let font_id = { let mut font_id = text_style @@ -429,6 +429,9 @@ impl RichText { /// but it can be a [`RichText`] (text with color, style, etc), /// a [`LayoutJob`] (for when you want full control of how the text looks) /// or text that has already been laid out in a [`Galley`]. +/// +/// You can color the text however you want, or use [`Color32::PLACEHOLDER`] +/// which will be replaced with a color chosen by the widget that paints the text. #[derive(Clone)] pub enum WidgetText { RichText(RichText), @@ -442,9 +445,15 @@ pub enum WidgetText { /// of the [`Ui`] the widget is placed in. /// If you want all parts of the [`LayoutJob`] respected, then convert it to a /// [`Galley`] and use [`Self::Galley`] instead. + /// + /// You can color the text however you want, or use [`Color32::PLACEHOLDER`] + /// which will be replaced with a color chosen by the widget that paints the text. LayoutJob(LayoutJob), /// Use exactly this galley when painting the text. + /// + /// You can color the text however you want, or use [`Color32::PLACEHOLDER`] + /// which will be replaced with a color chosen by the widget that paints the text. Galley(Arc), } @@ -616,25 +625,16 @@ impl WidgetText { } } - pub fn into_text_job( + pub fn into_layout_job( self, style: &Style, fallback_font: FontSelection, default_valign: Align, - ) -> WidgetTextJob { + ) -> LayoutJob { match self { - Self::RichText(text) => text.into_text_job(style, fallback_font, default_valign), - Self::LayoutJob(job) => WidgetTextJob { - job, - job_has_color: true, - }, - Self::Galley(galley) => { - let job: LayoutJob = (*galley.job).clone(); - WidgetTextJob { - job, - job_has_color: true, - } - } + Self::RichText(text) => text.into_layout_job(style, fallback_font, default_valign), + Self::LayoutJob(job) => job, + Self::Galley(galley) => (*galley.job).clone(), } } @@ -647,31 +647,22 @@ impl WidgetText { wrap: Option, available_width: f32, fallback_font: impl Into, - ) -> WidgetTextGalley { + ) -> Arc { let wrap = wrap.unwrap_or_else(|| ui.wrap_text()); let wrap_width = if wrap { available_width } else { f32::INFINITY }; match self { Self::RichText(text) => { let valign = ui.layout().vertical_align(); - let mut text_job = text.into_text_job(ui.style(), fallback_font.into(), valign); - text_job.job.wrap.max_width = wrap_width; - WidgetTextGalley { - galley: ui.fonts(|f| f.layout_job(text_job.job)), - galley_has_color: text_job.job_has_color, - } + let mut layout_job = text.into_layout_job(ui.style(), fallback_font.into(), valign); + layout_job.wrap.max_width = wrap_width; + ui.fonts(|f| f.layout_job(layout_job)) } Self::LayoutJob(mut job) => { job.wrap.max_width = wrap_width; - WidgetTextGalley { - galley: ui.fonts(|f| f.layout_job(job)), - galley_has_color: true, - } + ui.fonts(|f| f.layout_job(job)) } - Self::Galley(galley) => WidgetTextGalley { - galley, - galley_has_color: true, - }, + Self::Galley(galley) => galley, } } } @@ -724,86 +715,3 @@ impl From> for WidgetText { Self::Galley(galley) } } - -// ---------------------------------------------------------------------------- - -#[derive(Clone, PartialEq)] -pub struct WidgetTextJob { - pub job: LayoutJob, - pub job_has_color: bool, -} - -impl WidgetTextJob { - pub fn into_galley(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 laid out and ready to be painted. -#[derive(Clone, PartialEq)] -pub struct WidgetTextGalley { - pub galley: Arc, - pub galley_has_color: bool, -} - -impl WidgetTextGalley { - /// Size of the laid out text. - #[inline] - pub fn size(&self) -> crate::Vec2 { - self.galley.size() - } - - /// The full, non-elided text of the input job. - #[inline] - pub fn text(&self) -> &str { - self.galley.text() - } - - #[inline] - pub fn galley(&self) -> &Arc { - &self.galley - } - - /// Use the colors in the original [`WidgetText`] if any, - /// else fall back to the one specified by the [`WidgetVisuals`]. - pub fn paint_with_visuals( - self, - painter: &crate::Painter, - text_pos: Pos2, - visuals: &WidgetVisuals, - ) { - self.paint_with_fallback_color(painter, text_pos, visuals.text_color()); - } - - /// Use the colors in the original [`WidgetText`] if any, - /// else fall back to the given color. - pub fn paint_with_fallback_color( - self, - painter: &crate::Painter, - text_pos: Pos2, - text_color: Color32, - ) { - if self.galley_has_color { - painter.galley(text_pos, self.galley); - } else { - painter.galley_with_color(text_pos, self.galley, text_color); - } - } - - /// Paint with this specific color. - pub fn paint_with_color_override( - self, - painter: &crate::Painter, - text_pos: Pos2, - text_color: Color32, - ) { - painter.galley_with_color(text_pos, self.galley, text_color); - } -} diff --git a/crates/egui/src/widgets/button.rs b/crates/egui/src/widgets/button.rs index 236d68e05c1..6ca7cf86e40 100644 --- a/crates/egui/src/widgets/button.rs +++ b/crates/egui/src/widgets/button.rs @@ -210,8 +210,9 @@ impl Widget for Button<'_> { text_wrap_width -= 60.0; // Some space for the shortcut text (which we never wrap). } - let text = text.map(|text| text.into_galley(ui, wrap, text_wrap_width, TextStyle::Button)); - let shortcut_text = (!shortcut_text.is_empty()) + let galley = + text.map(|text| text.into_galley(ui, wrap, text_wrap_width, TextStyle::Button)); + let shortcut_galley = (!shortcut_text.is_empty()) .then(|| shortcut_text.into_galley(ui, Some(false), f32::INFINITY, TextStyle::Button)); let mut desired_size = Vec2::ZERO; @@ -219,14 +220,14 @@ impl Widget for Button<'_> { desired_size.x += image_size.x; desired_size.y = desired_size.y.max(image_size.y); } - if image.is_some() && text.is_some() { + if image.is_some() && galley.is_some() { desired_size.x += ui.spacing().icon_spacing; } - if let Some(text) = &text { + if let Some(text) = &galley { desired_size.x += text.size().x; desired_size.y = desired_size.y.max(text.size().y); } - if let Some(shortcut_text) = &shortcut_text { + if let Some(shortcut_text) = &shortcut_galley { desired_size.x += ui.spacing().item_spacing.x + shortcut_text.size().x; desired_size.y = desired_size.y.max(shortcut_text.size().y); } @@ -238,8 +239,8 @@ impl Widget for Button<'_> { let (rect, mut response) = ui.allocate_at_least(desired_size, sense); response.widget_info(|| { - if let Some(text) = &text { - WidgetInfo::labeled(WidgetType::Button, text.text()) + if let Some(galley) = &galley { + WidgetInfo::labeled(WidgetType::Button, galley.text()) } else { WidgetInfo::new(WidgetType::Button) } @@ -297,30 +298,30 @@ impl Widget for Button<'_> { widgets::image::texture_load_result_response(image.source(), &tlr, response); } - if image.is_some() && text.is_some() { + if image.is_some() && galley.is_some() { cursor_x += ui.spacing().icon_spacing; } - if let Some(text) = text { - let text_pos = if image.is_some() || shortcut_text.is_some() { - pos2(cursor_x, rect.center().y - 0.5 * text.size().y) + if let Some(galley) = galley { + let text_pos = if image.is_some() || shortcut_galley.is_some() { + pos2(cursor_x, rect.center().y - 0.5 * galley.size().y) } else { // Make sure button text is centered if within a centered layout ui.layout() - .align_size_within_rect(text.size(), rect.shrink2(button_padding)) + .align_size_within_rect(galley.size(), rect.shrink2(button_padding)) .min }; - text.paint_with_visuals(ui.painter(), text_pos, visuals); + ui.painter().galley(text_pos, galley, visuals.text_color()); } - if let Some(shortcut_text) = shortcut_text { + if let Some(shortcut_galley) = shortcut_galley { let shortcut_text_pos = pos2( - rect.max.x - button_padding.x - shortcut_text.size().x, - rect.center().y - 0.5 * shortcut_text.size().y, + rect.max.x - button_padding.x - shortcut_galley.size().x, + rect.center().y - 0.5 * shortcut_galley.size().y, ); - shortcut_text.paint_with_fallback_color( - ui.painter(), + ui.painter().galley( shortcut_text_pos, + shortcut_galley, ui.visuals().weak_text_color(), ); } @@ -378,18 +379,18 @@ impl<'a> Widget for Checkbox<'a> { let icon_width = spacing.icon_width; let icon_spacing = spacing.icon_spacing; - let (text, mut desired_size) = if text.is_empty() { + let (galley, mut desired_size) = if text.is_empty() { (None, vec2(icon_width, 0.0)) } else { let total_extra = vec2(icon_width + icon_spacing, 0.0); let wrap_width = ui.available_width() - total_extra.x; - let text = text.into_galley(ui, None, wrap_width, TextStyle::Button); + let galley = text.into_galley(ui, None, wrap_width, TextStyle::Button); - let mut desired_size = total_extra + text.size(); + let mut desired_size = total_extra + galley.size(); desired_size = desired_size.at_least(spacing.interact_size); - (Some(text), desired_size) + (Some(galley), desired_size) }; desired_size = desired_size.at_least(Vec2::splat(spacing.interact_size.y)); @@ -404,7 +405,7 @@ impl<'a> Widget for Checkbox<'a> { WidgetInfo::selected( WidgetType::Checkbox, *checked, - text.as_ref().map_or("", |x| x.text()), + galley.as_ref().map_or("", |x| x.text()), ) }); @@ -430,12 +431,12 @@ impl<'a> Widget for Checkbox<'a> { visuals.fg_stroke, )); } - if let Some(text) = text { + if let Some(galley) = galley { let text_pos = pos2( rect.min.x + icon_width + icon_spacing, - rect.center().y - 0.5 * text.size().y, + rect.center().y - 0.5 * galley.size().y, ); - text.paint_with_visuals(ui.painter(), text_pos, visuals); + ui.painter().galley(text_pos, galley, visuals.text_color()); } } @@ -487,7 +488,7 @@ impl Widget for RadioButton { let icon_width = spacing.icon_width; let icon_spacing = spacing.icon_spacing; - let (text, mut desired_size) = if text.is_empty() { + let (galley, mut desired_size) = if text.is_empty() { (None, vec2(icon_width, 0.0)) } else { let total_extra = vec2(icon_width + icon_spacing, 0.0); @@ -509,7 +510,7 @@ impl Widget for RadioButton { WidgetInfo::selected( WidgetType::RadioButton, checked, - text.as_ref().map_or("", |x| x.text()), + galley.as_ref().map_or("", |x| x.text()), ) }); @@ -538,12 +539,12 @@ impl Widget for RadioButton { }); } - if let Some(text) = text { + if let Some(galley) = galley { let text_pos = pos2( rect.min.x + icon_width + icon_spacing, - rect.center().y - 0.5 * text.size().y, + rect.center().y - 0.5 * galley.size().y, ); - text.paint_with_visuals(ui.painter(), text_pos, visuals); + ui.painter().galley(text_pos, galley, visuals.text_color()); } } diff --git a/crates/egui/src/widgets/hyperlink.rs b/crates/egui/src/widgets/hyperlink.rs index 5d592272641..79a833bd77e 100644 --- a/crates/egui/src/widgets/hyperlink.rs +++ b/crates/egui/src/widgets/hyperlink.rs @@ -34,8 +34,8 @@ impl Widget for Link { let Link { text } = self; let label = Label::new(text).sense(Sense::click()); - let (pos, text_galley, response) = label.layout_in_ui(ui); - response.widget_info(|| WidgetInfo::labeled(WidgetType::Link, text_galley.text())); + let (pos, galley, response) = label.layout_in_ui(ui); + response.widget_info(|| WidgetInfo::labeled(WidgetType::Link, galley.text())); if response.hovered() { ui.ctx().set_cursor_icon(CursorIcon::PointingHand); @@ -51,13 +51,8 @@ impl Widget for Link { Stroke::NONE }; - ui.painter().add(epaint::TextShape { - pos, - galley: text_galley.galley, - override_text_color: Some(color), - underline, - angle: 0.0, - }); + ui.painter() + .add(epaint::TextShape::new(pos, galley, color).with_underline(underline)); } response diff --git a/crates/egui/src/widgets/label.rs b/crates/egui/src/widgets/label.rs index eb786e75013..dee9b1c303f 100644 --- a/crates/egui/src/widgets/label.rs +++ b/crates/egui/src/widgets/label.rs @@ -1,4 +1,6 @@ -use crate::{widget_text::WidgetTextGalley, *}; +use std::sync::Arc; + +use crate::*; /// Static text. /// @@ -94,7 +96,7 @@ impl Label { impl Label { /// Do layout and position the galley in the ui, without painting it or adding widget info. - pub fn layout_in_ui(self, ui: &mut Ui) -> (Pos2, WidgetTextGalley, Response) { + pub fn layout_in_ui(self, ui: &mut Ui) -> (Pos2, Arc, Response) { let sense = self.sense.unwrap_or_else(|| { // We only want to focus labels if the screen reader is on. if ui.memory(|mem| mem.options.screen_reader) { @@ -111,17 +113,13 @@ impl Label { Align::Center => rect.center_top(), Align::RIGHT => rect.right_top(), }; - let text_galley = WidgetTextGalley { - galley, - galley_has_color: true, - }; - return (pos, text_galley, response); + return (pos, galley, response); } let valign = ui.layout().vertical_align(); - let mut text_job = self + let mut layout_job = self .text - .into_text_job(ui.style(), FontSelection::Default, valign); + .into_layout_job(ui.style(), FontSelection::Default, valign); let truncate = self.truncate; let wrap = !truncate && self.wrap.unwrap_or_else(|| ui.wrap_text()); @@ -139,70 +137,65 @@ impl Label { let first_row_indentation = available_width - ui.available_size_before_wrap().x; egui_assert!(first_row_indentation.is_finite()); - text_job.job.wrap.max_width = available_width; - text_job.job.first_row_min_height = cursor.height(); - text_job.job.halign = Align::Min; - text_job.job.justify = false; - if let Some(first_section) = text_job.job.sections.first_mut() { + layout_job.wrap.max_width = available_width; + layout_job.first_row_min_height = cursor.height(); + layout_job.halign = Align::Min; + layout_job.justify = false; + if let Some(first_section) = layout_job.sections.first_mut() { first_section.leading_space = first_row_indentation; } - let text_galley = ui.fonts(|f| text_job.into_galley(f)); + let galley = ui.fonts(|fonts| fonts.layout_job(layout_job)); let pos = pos2(ui.max_rect().left(), ui.cursor().top()); - assert!( - !text_galley.galley.rows.is_empty(), - "Galleys are never empty" - ); + assert!(!galley.rows.is_empty(), "Galleys are never empty"); // collect a response from many rows: - let rect = text_galley.galley.rows[0] - .rect - .translate(vec2(pos.x, pos.y)); + let rect = galley.rows[0].rect.translate(vec2(pos.x, pos.y)); let mut response = ui.allocate_rect(rect, sense); - for row in text_galley.galley.rows.iter().skip(1) { + for row in galley.rows.iter().skip(1) { let rect = row.rect.translate(vec2(pos.x, pos.y)); response |= ui.allocate_rect(rect, sense); } - (pos, text_galley, response) + (pos, galley, response) } else { if truncate { - text_job.job.wrap.max_width = available_width; - text_job.job.wrap.max_rows = 1; - text_job.job.wrap.break_anywhere = true; + layout_job.wrap.max_width = available_width; + layout_job.wrap.max_rows = 1; + layout_job.wrap.break_anywhere = true; } else if wrap { - text_job.job.wrap.max_width = available_width; + layout_job.wrap.max_width = available_width; } else { - text_job.job.wrap.max_width = f32::INFINITY; + layout_job.wrap.max_width = f32::INFINITY; }; if ui.is_grid() { // TODO(emilk): remove special Grid hacks like these - text_job.job.halign = Align::LEFT; - text_job.job.justify = false; + layout_job.halign = Align::LEFT; + layout_job.justify = false; } else { - text_job.job.halign = ui.layout().horizontal_placement(); - text_job.job.justify = ui.layout().horizontal_justify(); + layout_job.halign = ui.layout().horizontal_placement(); + layout_job.justify = ui.layout().horizontal_justify(); }; - let text_galley = ui.fonts(|f| text_job.into_galley(f)); - let (rect, response) = ui.allocate_exact_size(text_galley.size(), sense); - let pos = match text_galley.galley.job.halign { + let galley = ui.fonts(|fonts| fonts.layout_job(layout_job)); + let (rect, response) = ui.allocate_exact_size(galley.size(), sense); + let pos = match galley.job.halign { Align::LEFT => rect.left_top(), Align::Center => rect.center_top(), Align::RIGHT => rect.right_top(), }; - (pos, text_galley, response) + (pos, galley, response) } } } impl Widget for Label { fn ui(self, ui: &mut Ui) -> Response { - let (pos, text_galley, mut response) = self.layout_in_ui(ui); - response.widget_info(|| WidgetInfo::labeled(WidgetType::Label, text_galley.text())); + let (pos, galley, mut response) = self.layout_in_ui(ui); + response.widget_info(|| WidgetInfo::labeled(WidgetType::Label, galley.text())); - if text_galley.galley.elided { + if galley.elided { // Show the full (non-elided) text on hover: - response = response.on_hover_text(text_galley.text()); + response = response.on_hover_text(galley.text()); } if ui.is_rect_visible(response.rect) { @@ -214,19 +207,8 @@ impl Widget for Label { Stroke::NONE }; - let override_text_color = if text_galley.galley_has_color { - None - } else { - Some(response_color) - }; - - ui.painter().add(epaint::TextShape { - pos, - galley: text_galley.galley, - override_text_color, - underline, - angle: 0.0, - }); + ui.painter() + .add(epaint::TextShape::new(pos, galley, response_color).with_underline(underline)); } response diff --git a/crates/egui/src/widgets/progress_bar.rs b/crates/egui/src/widgets/progress_bar.rs index ad3c298399a..c8439802544 100644 --- a/crates/egui/src/widgets/progress_bar.rs +++ b/crates/egui/src/widgets/progress_bar.rs @@ -161,11 +161,9 @@ impl Widget for ProgressBar { let text_color = visuals .override_text_color .unwrap_or(visuals.selection.stroke.color); - galley.paint_with_fallback_color( - &ui.painter().with_clip_rect(outer_rect), - text_pos, - text_color, - ); + ui.painter() + .with_clip_rect(outer_rect) + .galley(text_pos, galley, text_color); } } diff --git a/crates/egui/src/widgets/selected_label.rs b/crates/egui/src/widgets/selected_label.rs index 364ba0391e6..105232b40a5 100644 --- a/crates/egui/src/widgets/selected_label.rs +++ b/crates/egui/src/widgets/selected_label.rs @@ -44,19 +44,19 @@ impl Widget for SelectableLabel { let total_extra = button_padding + button_padding; let wrap_width = ui.available_width() - total_extra.x; - let text = text.into_galley(ui, None, wrap_width, TextStyle::Button); + let galley = text.into_galley(ui, None, wrap_width, TextStyle::Button); - let mut desired_size = total_extra + text.size(); + let mut desired_size = total_extra + galley.size(); desired_size.y = desired_size.y.at_least(ui.spacing().interact_size.y); let (rect, response) = ui.allocate_at_least(desired_size, Sense::click()); response.widget_info(|| { - WidgetInfo::selected(WidgetType::SelectableLabel, selected, text.text()) + WidgetInfo::selected(WidgetType::SelectableLabel, selected, galley.text()) }); if ui.is_rect_visible(response.rect) { let text_pos = ui .layout() - .align_size_within_rect(text.size(), rect.shrink2(button_padding)) + .align_size_within_rect(galley.size(), rect.shrink2(button_padding)) .min; let visuals = ui.style().interact_selectable(&response, selected); @@ -72,7 +72,7 @@ impl Widget for SelectableLabel { ); } - text.paint_with_visuals(ui.painter(), text_pos, &visuals); + ui.painter().galley(text_pos, galley, visuals.text_color()); } response diff --git a/crates/egui/src/widgets/text_edit/builder.rs b/crates/egui/src/widgets/text_edit/builder.rs index adf53149146..8b80151975f 100644 --- a/crates/egui/src/widgets/text_edit/builder.rs +++ b/crates/egui/src/widgets/text_edit/builder.rs @@ -164,13 +164,14 @@ impl<'t> TextEdit<'t> { /// .desired_width(f32::INFINITY); /// let output = text_edit.show(ui); /// let painter = ui.painter_at(output.response.rect); + /// let text_color = Color32::from_rgba_premultiplied(100, 100, 100, 100); /// let galley = painter.layout( /// String::from("Enter text"), /// FontId::default(), - /// Color32::from_rgba_premultiplied(100, 100, 100, 100), + /// text_color, /// f32::INFINITY /// ); - /// painter.galley(output.text_draw_pos, galley); + /// painter.galley(output.text_draw_pos, galley, text_color); /// # }); /// ``` #[inline] @@ -664,7 +665,7 @@ impl<'t> TextEdit<'t> { }; if ui.is_rect_visible(rect) { - painter.galley(text_draw_pos, galley.clone()); + painter.galley(text_draw_pos, galley.clone(), text_color); if text.as_str().is_empty() && !hint_text.is_empty() { let hint_text_color = ui.visuals().weak_text_color(); @@ -673,7 +674,7 @@ impl<'t> TextEdit<'t> { } else { hint_text.into_galley(ui, Some(false), f32::INFINITY, font_id) }; - galley.paint_with_fallback_color(&painter, response.rect.min, hint_text_color); + painter.galley(response.rect.min, galley, hint_text_color); } if ui.memory(|mem| mem.has_focus(id)) { diff --git a/crates/egui_demo_app/src/apps/http_app.rs b/crates/egui_demo_app/src/apps/http_app.rs index a118a90fcb6..7e8d7e54c48 100644 --- a/crates/egui_demo_app/src/apps/http_app.rs +++ b/crates/egui_demo_app/src/apps/http_app.rs @@ -260,7 +260,11 @@ impl ColoredText { job.wrap.max_width = ui.available_width(); let galley = ui.fonts(|f| f.layout_job(job)); let (response, painter) = ui.allocate_painter(galley.size(), egui::Sense::hover()); - painter.add(egui::Shape::galley(response.rect.min, galley)); + painter.add(egui::Shape::galley( + response.rect.min, + galley, + ui.visuals().text_color(), + )); } } } diff --git a/crates/egui_demo_lib/benches/benchmark.rs b/crates/egui_demo_lib/benches/benchmark.rs index 1baaefd7e01..75c68a0de32 100644 --- a/crates/egui_demo_lib/benches/benchmark.rs +++ b/crates/egui_demo_lib/benches/benchmark.rs @@ -89,7 +89,7 @@ pub fn criterion_benchmark(c: &mut Criterion) { let max_texture_side = 8 * 1024; let wrap_width = 512.0; let font_id = egui::FontId::default(); - let color = egui::Color32::WHITE; + let text_color = egui::Color32::WHITE; let fonts = egui::epaint::text::Fonts::new( pixels_per_point, max_texture_side, @@ -104,7 +104,7 @@ pub fn criterion_benchmark(c: &mut Criterion) { let job = LayoutJob::simple( LOREM_IPSUM_LONG.to_owned(), font_id.clone(), - color, + text_color, wrap_width, ); layout(&mut locked_fonts.fonts, job.into()) @@ -116,13 +116,13 @@ pub fn criterion_benchmark(c: &mut Criterion) { fonts.layout( LOREM_IPSUM_LONG.to_owned(), font_id.clone(), - color, + text_color, wrap_width, ) }); }); - let galley = fonts.layout(LOREM_IPSUM_LONG.to_owned(), font_id, color, wrap_width); + let galley = fonts.layout(LOREM_IPSUM_LONG.to_owned(), font_id, text_color, wrap_width); let font_image_size = fonts.font_image_size(); let prepared_discs = fonts.texture_atlas().lock().prepared_discs(); let mut tessellator = egui::epaint::Tessellator::new( @@ -132,7 +132,7 @@ pub fn criterion_benchmark(c: &mut Criterion) { prepared_discs, ); let mut mesh = egui::epaint::Mesh::default(); - let text_shape = TextShape::new(egui::Pos2::ZERO, galley); + let text_shape = TextShape::new(egui::Pos2::ZERO, galley, text_color); c.bench_function("tessellate_text", |b| { b.iter(|| { tessellator.tessellate_text(&text_shape, &mut mesh); diff --git a/crates/egui_plot/src/axis.rs b/crates/egui_plot/src/axis.rs index 12dcac8828c..fae09e47ed3 100644 --- a/crates/egui_plot/src/axis.rs +++ b/crates/egui_plot/src/axis.rs @@ -1,7 +1,7 @@ use std::{fmt::Debug, ops::RangeInclusive, sync::Arc}; use egui::emath::{remap_clamp, round_to_decimals, Pos2, Rect}; -use egui::epaint::{Shape, Stroke, TextShape}; +use egui::epaint::{Shape, TextShape}; use crate::{Response, Sense, TextStyle, Ui, WidgetText}; @@ -247,14 +247,9 @@ impl AxisWidget { } }, }; - let shape = TextShape { - pos: text_pos, - galley: galley.galley, - underline: Stroke::NONE, - override_text_color: Some(text_color), - angle, - }; - ui.painter().add(shape); + + ui.painter() + .add(TextShape::new(text_pos, galley, text_color).with_angle(angle)); // --- add ticks --- let font_id = TextStyle::Body.resolve(ui.style()); @@ -311,7 +306,8 @@ impl AxisWidget { } }; - ui.painter().add(Shape::galley(text_pos, galley)); + ui.painter() + .add(Shape::galley(text_pos, galley, text_color)); } } } diff --git a/crates/egui_plot/src/items/mod.rs b/crates/egui_plot/src/items/mod.rs index 92d607570d8..c8949f58ce4 100644 --- a/crates/egui_plot/src/items/mod.rs +++ b/crates/egui_plot/src/items/mod.rs @@ -732,11 +732,7 @@ impl PlotItem for Text { .anchor .anchor_rect(Rect::from_min_size(pos, galley.size())); - let mut text_shape = epaint::TextShape::new(rect.min, galley.galley); - if !galley.galley_has_color { - text_shape.override_text_color = Some(color); - } - shapes.push(text_shape.into()); + shapes.push(epaint::TextShape::new(rect.min, galley, color).into()); if self.highlight { shapes.push(Shape::rect_stroke( diff --git a/crates/egui_plot/src/legend.rs b/crates/egui_plot/src/legend.rs index 7d2a3e00954..0a0f459f2f8 100644 --- a/crates/egui_plot/src/legend.rs +++ b/crates/egui_plot/src/legend.rs @@ -144,7 +144,7 @@ impl LegendEntry { }; let text_position = pos2(text_position_x, rect.center().y - 0.5 * galley.size().y); - painter.galley_with_color(text_position, galley, visuals.text_color()); + painter.galley(text_position, galley, visuals.text_color()); *checked ^= response.clicked_by(PointerButton::Primary); *hovered = response.hovered(); diff --git a/crates/epaint/src/shape.rs b/crates/epaint/src/shape.rs index 8de1f0ffc1b..cfae4493d0f 100644 --- a/crates/epaint/src/shape.rs +++ b/crates/epaint/src/shape.rs @@ -211,24 +211,36 @@ impl Shape { ) -> Self { let galley = fonts.layout_no_wrap(text.to_string(), font_id, color); let rect = anchor.anchor_rect(Rect::from_min_size(pos, galley.size())); - Self::galley(rect.min, galley) + Self::galley(rect.min, galley, color) } + /// Any uncolored parts of the [`Galley`] (using [`Color32::PLACEHOLDER`]) will be replaced with the given color. + /// + /// Any non-placeholder color in the galley takes precedence over this fallback color. + #[inline] + pub fn galley(pos: Pos2, galley: Arc, fallback_color: Color32) -> Self { + TextShape::new(pos, galley, fallback_color).into() + } + + /// All text color in the [`Galley`] will be replaced with the given color. #[inline] - pub fn galley(pos: Pos2, galley: Arc) -> Self { - TextShape::new(pos, galley).into() + pub fn galley_with_override_text_color( + pos: Pos2, + galley: Arc, + text_color: Color32, + ) -> Self { + TextShape::new(pos, galley, text_color) + .with_override_text_color(text_color) + .into() } #[inline] - /// The text color in the [`Galley`] will be replaced with the given color. + #[deprecated = "Use `Shape::galley` or `Shape::galley_with_override_text_color` instead"] pub fn galley_with_color(pos: Pos2, galley: Arc, text_color: Color32) -> Self { - TextShape { - override_text_color: Some(text_color), - ..TextShape::new(pos, galley) - } - .into() + Self::galley_with_override_text_color(pos, galley, text_color) } + #[inline] pub fn mesh(mesh: Mesh) -> Self { crate::epaint_assert!(mesh.is_valid()); Self::Mesh(mesh) @@ -669,9 +681,14 @@ pub struct TextShape { /// You can also set an underline when creating the galley. pub underline: Stroke, + /// Any [`Color32::PLACEHOLDER`] in the galley will be replaced by the given color. + /// Affects everything: backgrounds, glyphs, strikethough, underline, etc. + pub fallback_color: Color32, + /// If set, the text color in the galley will be ignored and replaced /// with the given color. - /// This will NOT replace background color nor strikethrough/underline color. + /// + /// This only affects the glyphs and will NOT replace background color nor strikethrough/underline color. pub override_text_color: Option, /// Rotate text by this many radians clockwise. @@ -680,12 +697,16 @@ pub struct TextShape { } impl TextShape { + /// The given fallback color will be used for any uncolored part of the galley (using [`Color32::PLACEHOLDER`]). + /// + /// Any non-placeholder color in the galley takes precedence over this fallback color. #[inline] - pub fn new(pos: Pos2, galley: Arc) -> Self { + pub fn new(pos: Pos2, galley: Arc, fallback_color: Color32) -> Self { Self { pos, galley, underline: Stroke::NONE, + fallback_color, override_text_color: None, angle: 0.0, } @@ -696,6 +717,27 @@ impl TextShape { pub fn visual_bounding_rect(&self) -> Rect { self.galley.mesh_bounds.translate(self.pos.to_vec2()) } + + #[inline] + pub fn with_underline(mut self, underline: Stroke) -> Self { + self.underline = underline; + self + } + + /// Use the given color for the text, regardless of what color is already in the galley. + #[inline] + pub fn with_override_text_color(mut self, override_text_color: Color32) -> Self { + self.override_text_color = Some(override_text_color); + self + } + + /// Rotate text by this many radians clockwise. + /// The pivot is `pos` (the upper left corner of the text). + #[inline] + pub fn with_angle(mut self, angle: f32) -> Self { + self.angle = angle; + self + } } impl From for Shape { diff --git a/crates/epaint/src/tessellator.rs b/crates/epaint/src/tessellator.rs index fa50f0df435..72899f2844c 100644 --- a/crates/epaint/src/tessellator.rs +++ b/crates/epaint/src/tessellator.rs @@ -1473,6 +1473,7 @@ impl Tessellator { galley, underline, override_text_color, + fallback_color, angle, } = text_shape; @@ -1539,11 +1540,16 @@ impl Tessellator { let Vertex { pos, uv, mut color } = *vertex; if let Some(override_text_color) = override_text_color { + // Only override the glyph color (not background color, strike-through color, etc) if row.visuals.glyph_vertex_range.contains(&i) { color = *override_text_color; } + } else if color == Color32::PLACEHOLDER { + color = *fallback_color; } + crate::epaint_assert!(color != Color32::PLACEHOLDER, "A placeholder color made it to the tessellator. You forgot to set a fallback color."); + let offset = if *angle == 0.0 { pos.to_vec2() } else { diff --git a/crates/epaint/src/text/fonts.rs b/crates/epaint/src/text/fonts.rs index 61ed80fe0d4..7165b2655db 100644 --- a/crates/epaint/src/text/fonts.rs +++ b/crates/epaint/src/text/fonts.rs @@ -531,12 +531,7 @@ impl Fonts { font_id: FontId, wrap_width: f32, ) -> Arc { - self.layout_job(LayoutJob::simple( - text, - font_id, - crate::Color32::TEMPORARY_COLOR, - wrap_width, - )) + self.layout(text, font_id, crate::Color32::PLACEHOLDER, wrap_width) } } diff --git a/crates/epaint/src/text/text_layout_types.rs b/crates/epaint/src/text/text_layout_types.rs index 7b81cd82440..6d9748aa33a 100644 --- a/crates/epaint/src/text/text_layout_types.rs +++ b/crates/epaint/src/text/text_layout_types.rs @@ -509,8 +509,9 @@ pub struct RowVisuals { /// Does NOT include leading or trailing whitespace glyphs!! pub mesh_bounds: Rect, - /// The range of vertices in the mesh the contain glyphs. - /// Before comes backgrounds (if any), and after any underlines and strikethrough. + /// The range of vertices in the mesh that contain glyphs (as opposed to background, underlines, strikethorugh, etc). + /// + /// The glyph vertices comes before backgrounds (if any), and after any underlines and strikethrough. pub glyph_vertex_range: Range, }