mirror of
https://github.com/emilk/egui.git
synced 2026-09-01 14:20:04 -04:00
Replace a special Color32::PLACEHOLDER with widget fallback color (#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`
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -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<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
|
||||
@@ -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(),
|
||||
);
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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<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)
|
||||
}
|
||||
@@ -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<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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<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,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<Color32>) -> 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<Galley>),
|
||||
}
|
||||
|
||||
@@ -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<bool>,
|
||||
available_width: f32,
|
||||
fallback_font: impl Into<FontSelection>,
|
||||
) -> WidgetTextGalley {
|
||||
) -> Arc<Galley> {
|
||||
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<Arc<Galley>> 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<Galley>,
|
||||
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<Galley> {
|
||||
&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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<Galley>, 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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
Reference in New Issue
Block a user