Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
200 changes: 126 additions & 74 deletions crates/egui/src/widget_text.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::{borrow::Cow, sync::Arc};

use emath::GuiRounding as _;
use epaint::text::TextFormat;

use crate::{
text::{LayoutJob, TextWrapping},
Expand Down Expand Up @@ -488,7 +489,16 @@ impl RichText {
/// which will be replaced with a color chosen by the widget that paints the text.
#[derive(Clone)]
pub enum WidgetText {
RichText(RichText),
/// Plain unstyled text.
///
/// We have this as a special case, as it is the common-case,
/// and it uses less memory than [`Self::RichText`].
Text(String),

/// Text and optional style choices for it.
///
/// Prefer [`Self::Text`] if there is no styling, as it will be faster.
RichText(Arc<RichText>),

/// Use this [`LayoutJob`] when laying out the text.
///
Expand All @@ -502,7 +512,7 @@ pub enum WidgetText {
///
/// 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),
LayoutJob(Arc<LayoutJob>),

/// Use exactly this galley when painting the text.
///
Expand All @@ -513,14 +523,15 @@ pub enum WidgetText {

impl Default for WidgetText {
fn default() -> Self {
Self::RichText(RichText::default())
Self::Text(String::new())
}
}

impl WidgetText {
#[inline]
pub fn is_empty(&self) -> bool {
match self {
Self::Text(text) => text.is_empty(),
Self::RichText(text) => text.is_empty(),
Self::LayoutJob(job) => job.is_empty(),
Self::Galley(galley) => galley.is_empty(),
Expand All @@ -530,144 +541,130 @@ impl WidgetText {
#[inline]
pub fn text(&self) -> &str {
match self {
Self::Text(text) => text,
Self::RichText(text) => text.text(),
Self::LayoutJob(job) => &job.text,
Self::Galley(galley) => galley.text(),
}
}

/// Map the contents based on the provided closure.
///
/// - [`Self::Text`] => convert to [`RichText`] and call f
/// - [`Self::RichText`] => call f
/// - else do nothing
#[must_use]
fn map_rich_text<F>(self, f: F) -> Self
where
F: FnOnce(RichText) -> RichText,
{
match self {
Self::Text(text) => Self::RichText(Arc::new(f(RichText::new(text)))),
Self::RichText(text) => Self::RichText(Arc::new(f(Arc::unwrap_or_clone(text)))),
other => other,
}
}

/// Override the [`TextStyle`] if, and only if, this is a [`RichText`].
///
/// Prefer using [`RichText`] directly!
#[inline]
pub fn text_style(self, text_style: TextStyle) -> Self {
match self {
Self::RichText(text) => Self::RichText(text.text_style(text_style)),
Self::LayoutJob(_) | Self::Galley(_) => self,
}
self.map_rich_text(|text| text.text_style(text_style))
}

/// Set the [`TextStyle`] unless it has already been set
///
/// Prefer using [`RichText`] directly!
#[inline]
pub fn fallback_text_style(self, text_style: TextStyle) -> Self {
match self {
Self::RichText(text) => Self::RichText(text.fallback_text_style(text_style)),
Self::LayoutJob(_) | Self::Galley(_) => self,
}
self.map_rich_text(|text| text.fallback_text_style(text_style))
}

/// Override text color if, and only if, this is a [`RichText`].
///
/// Prefer using [`RichText`] directly!
#[inline]
pub fn color(self, color: impl Into<Color32>) -> Self {
match self {
Self::RichText(text) => Self::RichText(text.color(color)),
Self::LayoutJob(_) | Self::Galley(_) => self,
}
self.map_rich_text(|text| text.color(color))
}

/// Prefer using [`RichText`] directly!
#[inline]
pub fn heading(self) -> Self {
match self {
Self::RichText(text) => Self::RichText(text.heading()),
Self::LayoutJob(_) | Self::Galley(_) => self,
}
self.map_rich_text(|text| text.heading())
}

/// Prefer using [`RichText`] directly!
#[inline]
pub fn monospace(self) -> Self {
match self {
Self::RichText(text) => Self::RichText(text.monospace()),
Self::LayoutJob(_) | Self::Galley(_) => self,
}
self.map_rich_text(|text| text.monospace())
}

/// Prefer using [`RichText`] directly!
#[inline]
pub fn code(self) -> Self {
match self {
Self::RichText(text) => Self::RichText(text.code()),
Self::LayoutJob(_) | Self::Galley(_) => self,
}
self.map_rich_text(|text| text.code())
}

/// Prefer using [`RichText`] directly!
#[inline]
pub fn strong(self) -> Self {
match self {
Self::RichText(text) => Self::RichText(text.strong()),
Self::LayoutJob(_) | Self::Galley(_) => self,
}
self.map_rich_text(|text| text.strong())
}

/// Prefer using [`RichText`] directly!
#[inline]
pub fn weak(self) -> Self {
match self {
Self::RichText(text) => Self::RichText(text.weak()),
Self::LayoutJob(_) | Self::Galley(_) => self,
}
self.map_rich_text(|text| text.weak())
}

/// Prefer using [`RichText`] directly!
#[inline]
pub fn underline(self) -> Self {
match self {
Self::RichText(text) => Self::RichText(text.underline()),
Self::LayoutJob(_) | Self::Galley(_) => self,
}
self.map_rich_text(|text| text.underline())
}

/// Prefer using [`RichText`] directly!
#[inline]
pub fn strikethrough(self) -> Self {
match self {
Self::RichText(text) => Self::RichText(text.strikethrough()),
Self::LayoutJob(_) | Self::Galley(_) => self,
}
self.map_rich_text(|text| text.strikethrough())
}

/// Prefer using [`RichText`] directly!
#[inline]
pub fn italics(self) -> Self {
match self {
Self::RichText(text) => Self::RichText(text.italics()),
Self::LayoutJob(_) | Self::Galley(_) => self,
}
self.map_rich_text(|text| text.italics())
}

/// Prefer using [`RichText`] directly!
#[inline]
pub fn small(self) -> Self {
match self {
Self::RichText(text) => Self::RichText(text.small()),
Self::LayoutJob(_) | Self::Galley(_) => self,
}
self.map_rich_text(|text| text.small())
}

/// Prefer using [`RichText`] directly!
#[inline]
pub fn small_raised(self) -> Self {
match self {
Self::RichText(text) => Self::RichText(text.small_raised()),
Self::LayoutJob(_) | Self::Galley(_) => self,
}
self.map_rich_text(|text| text.small_raised())
}

/// Prefer using [`RichText`] directly!
#[inline]
pub fn raised(self) -> Self {
match self {
Self::RichText(text) => Self::RichText(text.raised()),
Self::LayoutJob(_) | Self::Galley(_) => self,
}
self.map_rich_text(|text| text.raised())
}

/// Prefer using [`RichText`] directly!
#[inline]
pub fn background_color(self, background_color: impl Into<Color32>) -> Self {
match self {
Self::RichText(text) => Self::RichText(text.background_color(background_color)),
Self::LayoutJob(_) | Self::Galley(_) => self,
}
self.map_rich_text(|text| text.background_color(background_color))
}

/// Returns a value rounded to [`emath::GUI_ROUNDING`].
pub(crate) fn font_height(&self, fonts: &epaint::Fonts, style: &Style) -> f32 {
match self {
Self::Text(_) => fonts.row_height(&FontSelection::Default.resolve(style)),
Self::RichText(text) => text.font_height(fonts, style),
Self::LayoutJob(job) => job.font_height(fonts),
Self::Galley(galley) => {
Expand All @@ -685,11 +682,24 @@ impl WidgetText {
style: &Style,
fallback_font: FontSelection,
default_valign: Align,
) -> LayoutJob {
) -> Arc<LayoutJob> {
match self {
Self::RichText(text) => text.into_layout_job(style, fallback_font, default_valign),
Self::Text(text) => Arc::new(LayoutJob::simple_format(
text,
TextFormat {
font_id: FontSelection::Default.resolve(style),
color: crate::Color32::PLACEHOLDER,
valign: default_valign,
..Default::default()
},
)),
Self::RichText(text) => Arc::new(Arc::unwrap_or_clone(text).into_layout_job(
style,
fallback_font,
default_valign,
)),
Self::LayoutJob(job) => job,
Self::Galley(galley) => (*galley.job).clone(),
Self::Galley(galley) => galley.job.clone(),
}
}

Expand Down Expand Up @@ -721,12 +731,30 @@ impl WidgetText {
default_valign: Align,
) -> Arc<Galley> {
match self {
Self::Text(text) => {
let mut layout_job = LayoutJob::simple_format(
text,
TextFormat {
font_id: FontSelection::Default.resolve(style),
color: crate::Color32::PLACEHOLDER,
valign: default_valign,
..Default::default()
},
);
layout_job.wrap = text_wrapping;
ctx.fonts(|f| f.layout_job(layout_job))
}
Self::RichText(text) => {
let mut layout_job = text.into_layout_job(style, fallback_font, default_valign);
let mut layout_job = Arc::unwrap_or_clone(text).into_layout_job(
style,
fallback_font,
default_valign,
);
layout_job.wrap = text_wrapping;
ctx.fonts(|f| f.layout_job(layout_job))
}
Self::LayoutJob(mut job) => {
Self::LayoutJob(job) => {
let mut job = Arc::unwrap_or_clone(job);
job.wrap = text_wrapping;
ctx.fonts(|f| f.layout_job(job))
}
Expand All @@ -738,55 +766,69 @@ impl WidgetText {
impl From<&str> for WidgetText {
#[inline]
fn from(text: &str) -> Self {
Self::RichText(RichText::new(text))
Self::Text(text.to_owned())
}
}

impl From<&String> for WidgetText {
#[inline]
fn from(text: &String) -> Self {
Self::RichText(RichText::new(text))
Self::Text(text.clone())
}
}

impl From<String> for WidgetText {
#[inline]
fn from(text: String) -> Self {
Self::RichText(RichText::new(text))
Self::Text(text)
}
}

impl From<&Box<str>> for WidgetText {
#[inline]
fn from(text: &Box<str>) -> Self {
Self::RichText(RichText::new(text.clone()))
Self::Text(text.to_string())
}
}

impl From<Box<str>> for WidgetText {
#[inline]
fn from(text: Box<str>) -> Self {
Self::RichText(RichText::new(text))
Self::Text(text.into())
}
}

impl From<Cow<'_, str>> for WidgetText {
#[inline]
fn from(text: Cow<'_, str>) -> Self {
Self::RichText(RichText::new(text))
Self::Text(text.into_owned())
}
}

impl From<RichText> for WidgetText {
#[inline]
fn from(rich_text: RichText) -> Self {
Self::RichText(Arc::new(rich_text))
}
}

impl From<Arc<RichText>> for WidgetText {
#[inline]
fn from(rich_text: Arc<RichText>) -> Self {
Self::RichText(rich_text)
}
}

impl From<LayoutJob> for WidgetText {
#[inline]
fn from(layout_job: LayoutJob) -> Self {
Self::LayoutJob(Arc::new(layout_job))
}
}

impl From<Arc<LayoutJob>> for WidgetText {
#[inline]
fn from(layout_job: Arc<LayoutJob>) -> Self {
Self::LayoutJob(layout_job)
}
}
Expand All @@ -797,3 +839,13 @@ impl From<Arc<Galley>> for WidgetText {
Self::Galley(galley)
}
}

#[cfg(test)]
mod tests {
use crate::WidgetText;

#[test]
fn ensure_small_widget_text() {
assert_eq!(size_of::<WidgetText>(), size_of::<String>());
}
}
Loading
Loading