Skip to content

Commit

Permalink
Replace a special Color32::PLACEHOLDER with widget fallback color (#…
Browse files Browse the repository at this point in the history
…3727)

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<Galley>` 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`
  • Loading branch information
emilk authored Dec 22, 2023
1 parent e36b981 commit 0561fca
Show file tree
Hide file tree
Showing 23 changed files with 267 additions and 306 deletions.
13 changes: 12 additions & 1 deletion crates/ecolor/src/color32.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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))]
Expand Down Expand Up @@ -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 {
Expand Down
12 changes: 6 additions & 6 deletions crates/egui/src/containers/collapsing_header.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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());

Expand Down Expand Up @@ -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 {
Expand Down
3 changes: 2 additions & 1 deletion crates/egui/src/containers/combo_box.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
});

Expand Down
12 changes: 7 additions & 5 deletions crates/egui/src/containers/window.rs
Original file line number Diff line number Diff line change
@@ -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::*;
Expand Down Expand Up @@ -885,7 +887,7 @@ struct TitleBar {
id: Id,

/// Prepared text in the title
title_galley: WidgetTextGalley,
title_galley: Arc<Galley>,

/// Size of the title bar in a collapsed state (if window is collapsible),
/// which includes all necessary space for showing the expand button, the
Expand Down Expand Up @@ -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(),
);

Expand Down
4 changes: 2 additions & 2 deletions crates/egui/src/menu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
59 changes: 40 additions & 19 deletions crates/egui/src/painter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,53 +88,53 @@ impl Painter {
/// ## Accessors etc
impl Painter {
/// Get a reference to the parent [`Context`].
#[inline(always)]
#[inline]
pub fn ctx(&self) -> &Context {
&self.ctx
}

/// Read-only access to the shared [`Fonts`].
///
/// See [`Context`] documentation for how locks work.
#[inline(always)]
#[inline]
pub fn fonts<R>(&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)
}
Expand Down Expand Up @@ -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
}
}
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -414,23 +416,42 @@ 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<Galley>) {
/// 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<Galley>, fallback_color: Color32) {
if !galley.is_empty() {
self.add(Shape::galley(pos, galley));
self.add(Shape::galley(pos, galley, fallback_color));
}
}

/// Paint text that has already been laid out in a [`Galley`].
///
/// 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<Galley>,
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<Galley>, 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,
));
}
}
}
Expand Down
5 changes: 3 additions & 2 deletions crates/egui/src/ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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);
Expand Down
Loading

0 comments on commit 0561fca

Please sign in to comment.