1
0
mirror of https://github.com/emilk/egui.git synced 2026-06-27 07:03:14 -04:00
Files
egui/egui/src/widget_text.rs
2022-01-22 18:30:13 +01:00

639 lines
18 KiB
Rust

use epaint::mutex::Arc;
use crate::{
style::WidgetVisuals, text::LayoutJob, Align, Color32, Context, Galley, Pos2, Style, TextStyle,
Ui, Visuals,
};
/// Text and optional style choices for it.
///
/// The style choices (font, color) are applied to the entire text.
/// For more detailed control, use [`crate::text::LayoutJob`] instead.
#[derive(Clone, Default, PartialEq)]
pub struct RichText {
text: String,
text_style: Option<TextStyle>,
background_color: Color32,
text_color: Option<Color32>,
code: bool,
strong: bool,
weak: bool,
strikethrough: bool,
underline: bool,
italics: bool,
raised: bool,
}
impl From<&str> for RichText {
#[inline]
fn from(text: &str) -> Self {
RichText::new(text)
}
}
impl From<&String> for RichText {
#[inline]
fn from(text: &String) -> Self {
RichText::new(text)
}
}
impl From<String> for RichText {
#[inline]
fn from(text: String) -> Self {
RichText::new(text)
}
}
impl RichText {
#[inline]
pub fn new(text: impl Into<String>) -> Self {
Self {
text: text.into(),
..Default::default()
}
}
#[inline]
pub fn is_empty(&self) -> bool {
self.text.is_empty()
}
#[inline]
pub fn text(&self) -> &str {
&self.text
}
/// Override the [`TextStyle`].
#[inline]
pub fn text_style(mut self, text_style: TextStyle) -> Self {
self.text_style = Some(text_style);
self
}
/// Set the [`TextStyle`] unless it has already been set
#[inline]
pub fn fallback_text_style(mut self, text_style: TextStyle) -> Self {
self.text_style.get_or_insert(text_style);
self
}
/// Use [`TextStyle::Heading`].
#[inline]
pub fn heading(self) -> Self {
self.text_style(TextStyle::Heading)
}
/// Use [`TextStyle::Monospace`].
#[inline]
pub fn monospace(self) -> Self {
self.text_style(TextStyle::Monospace)
}
/// Monospace label with different background color.
#[inline]
pub fn code(mut self) -> Self {
self.code = true;
self.text_style(TextStyle::Monospace)
}
/// Extra strong text (stronger color).
#[inline]
pub fn strong(mut self) -> Self {
self.strong = true;
self
}
/// Extra weak text (fainter color).
#[inline]
pub fn weak(mut self) -> Self {
self.weak = true;
self
}
/// Draw a line under the text.
///
/// If you want to control the line color, use [`LayoutJob`] instead.
#[inline]
pub fn underline(mut self) -> Self {
self.underline = true;
self
}
/// Draw a line through the text, crossing it out.
///
/// If you want to control the strikethrough line color, use [`LayoutJob`] instead.
#[inline]
pub fn strikethrough(mut self) -> Self {
self.strikethrough = true;
self
}
/// Tilt the characters to the right.
#[inline]
pub fn italics(mut self) -> Self {
self.italics = true;
self
}
/// Smaller text.
#[inline]
pub fn small(self) -> Self {
self.text_style(TextStyle::Small)
}
/// For e.g. exponents.
#[inline]
pub fn small_raised(self) -> Self {
self.text_style(TextStyle::Small).raised()
}
/// Align text to top. Only applicable together with [`Self::small()`].
#[inline]
pub fn raised(mut self) -> Self {
self.raised = true;
self
}
/// Fill-color behind the text.
#[inline]
pub fn background_color(mut self, background_color: impl Into<Color32>) -> Self {
self.background_color = background_color.into();
self
}
/// Override text color.
#[inline]
pub fn color(mut self, color: impl Into<Color32>) -> Self {
self.text_color = Some(color.into());
self
}
/// Read the font height of the selected text style.
pub fn font_height(&self, ctx: &Context) -> f32 {
let text_style = self
.text_style
.clone()
.or_else(|| ctx.style().override_text_style.clone())
.unwrap_or_else(|| ctx.style().body_text_style.clone());
ctx.fonts().row_height(&text_style)
}
fn into_text_job(
self,
style: &Style,
default_text_style: TextStyle,
default_valign: Align,
) -> WidgetTextJob {
let text_color = self.get_text_color(&style.visuals);
let Self {
text,
text_style,
background_color,
text_color: _, // already used by `get_text_color`
code,
strong: _, // already used by `get_text_color`
weak: _, // already used by `get_text_color`
strikethrough,
underline,
italics,
raised,
} = self;
let job_has_color = text_color.is_some();
let line_color = text_color.unwrap_or_else(|| style.visuals.text_color());
let text_color = text_color.unwrap_or(crate::Color32::TEMPORARY_COLOR);
let text_style = text_style
.or_else(|| style.override_text_style.clone())
.unwrap_or(default_text_style);
let mut background_color = background_color;
if code {
background_color = style.visuals.code_bg_color;
}
let underline = if underline {
crate::Stroke::new(1.0, line_color)
} else {
crate::Stroke::none()
};
let strikethrough = if strikethrough {
crate::Stroke::new(1.0, line_color)
} else {
crate::Stroke::none()
};
let valign = if raised {
crate::Align::TOP
} else {
default_valign
};
let text_format = crate::text::TextFormat {
style: text_style,
color: text_color,
background: background_color,
italics,
underline,
strikethrough,
valign,
};
let job = LayoutJob::single_section(text, text_format);
WidgetTextJob { job, job_has_color }
}
fn get_text_color(&self, visuals: &Visuals) -> Option<Color32> {
if let Some(text_color) = self.text_color {
Some(text_color)
} else if self.strong {
Some(visuals.strong_text_color())
} else if self.weak {
Some(visuals.weak_text_color())
} else {
visuals.override_text_color
}
}
}
// ----------------------------------------------------------------------------
/// This is how you specify text for a widget.
///
/// A lot of widgets use `impl Into<WidgetText>` as an argument,
/// allowing you to pass in [`String`], [`RichText`], [`LayoutJob`], and more.
///
/// Often a [`WidgetText`] is just a simple [`String`],
/// 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 layed out in a [`Galley`].
pub enum WidgetText {
RichText(RichText),
/// Use this [`LayoutJob`] when laying out the text.
///
/// Only [`LayoutJob::text`] and [`LayoutJob::sections`] are guaranteed to be respected.
///
/// [`LayoutJob::wrap_width`], [`LayoutJob::halign`], [`LayoutJob::justify`]
/// and [`LayoutJob::first_row_min_height`] will likely be determined by the [`crate::Layout`]
/// 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.
LayoutJob(LayoutJob),
/// Use exactly this galley when painting the text.
Galley(Arc<Galley>),
}
impl Default for WidgetText {
fn default() -> Self {
Self::RichText(RichText::default())
}
}
impl WidgetText {
#[inline]
pub fn is_empty(&self) -> bool {
match self {
Self::RichText(text) => text.is_empty(),
Self::LayoutJob(job) => job.is_empty(),
Self::Galley(galley) => galley.is_empty(),
}
}
#[inline]
pub fn text(&self) -> &str {
match self {
Self::RichText(text) => text.text(),
Self::LayoutJob(job) => &job.text,
Self::Galley(galley) => galley.text(),
}
}
/// 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,
}
}
/// 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,
}
}
/// 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,
}
}
/// Prefer using [`RichText`] directly!
pub fn heading(self) -> Self {
match self {
Self::RichText(text) => Self::RichText(text.heading()),
Self::LayoutJob(_) | Self::Galley(_) => self,
}
}
/// Prefer using [`RichText`] directly!
pub fn monospace(self) -> Self {
match self {
Self::RichText(text) => Self::RichText(text.monospace()),
Self::LayoutJob(_) | Self::Galley(_) => self,
}
}
/// Prefer using [`RichText`] directly!
pub fn code(self) -> Self {
match self {
Self::RichText(text) => Self::RichText(text.code()),
Self::LayoutJob(_) | Self::Galley(_) => self,
}
}
/// Prefer using [`RichText`] directly!
pub fn strong(self) -> Self {
match self {
Self::RichText(text) => Self::RichText(text.strong()),
Self::LayoutJob(_) | Self::Galley(_) => self,
}
}
/// Prefer using [`RichText`] directly!
pub fn weak(self) -> Self {
match self {
Self::RichText(text) => Self::RichText(text.weak()),
Self::LayoutJob(_) | Self::Galley(_) => self,
}
}
/// Prefer using [`RichText`] directly!
pub fn underline(self) -> Self {
match self {
Self::RichText(text) => Self::RichText(text.underline()),
Self::LayoutJob(_) | Self::Galley(_) => self,
}
}
/// Prefer using [`RichText`] directly!
pub fn strikethrough(self) -> Self {
match self {
Self::RichText(text) => Self::RichText(text.strikethrough()),
Self::LayoutJob(_) | Self::Galley(_) => self,
}
}
/// Prefer using [`RichText`] directly!
pub fn italics(self) -> Self {
match self {
Self::RichText(text) => Self::RichText(text.italics()),
Self::LayoutJob(_) | Self::Galley(_) => self,
}
}
/// Prefer using [`RichText`] directly!
pub fn small(self) -> Self {
match self {
Self::RichText(text) => Self::RichText(text.small()),
Self::LayoutJob(_) | Self::Galley(_) => self,
}
}
/// Prefer using [`RichText`] directly!
pub fn small_raised(self) -> Self {
match self {
Self::RichText(text) => Self::RichText(text.small_raised()),
Self::LayoutJob(_) | Self::Galley(_) => self,
}
}
/// Prefer using [`RichText`] directly!
pub fn raised(self) -> Self {
match self {
Self::RichText(text) => Self::RichText(text.raised()),
Self::LayoutJob(_) | Self::Galley(_) => self,
}
}
/// Prefer using [`RichText`] directly!
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,
}
}
pub(crate) fn font_height(&self, ctx: &Context) -> f32 {
match self {
Self::RichText(text) => text.font_height(ctx),
Self::LayoutJob(job) => job.font_height(&*ctx.fonts()),
Self::Galley(galley) => {
if let Some(row) = galley.rows.first() {
row.height()
} else {
galley.size().y
}
}
}
}
pub fn into_text_job(
self,
style: &Style,
default_text_style: TextStyle,
default_valign: Align,
) -> WidgetTextJob {
match self {
Self::RichText(text) => text.into_text_job(style, default_text_style, 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,
}
}
}
}
/// Layout with wrap mode based on the containing `Ui`.
///
/// wrap: override for [`Ui::wrap_text`].
pub fn into_galley(
self,
ui: &Ui,
wrap: Option<bool>,
available_width: f32,
default_text_style: TextStyle,
) -> WidgetTextGalley {
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(), default_text_style, valign);
text_job.job.wrap_width = wrap_width;
WidgetTextGalley {
galley: ui.fonts().layout_job(text_job.job),
galley_has_color: text_job.job_has_color,
}
}
Self::LayoutJob(mut job) => {
job.wrap_width = wrap_width;
WidgetTextGalley {
galley: ui.fonts().layout_job(job),
galley_has_color: true,
}
}
Self::Galley(galley) => WidgetTextGalley {
galley,
galley_has_color: true,
},
}
}
}
impl From<&str> for WidgetText {
#[inline]
fn from(text: &str) -> Self {
Self::RichText(RichText::new(text))
}
}
impl From<&String> for WidgetText {
#[inline]
fn from(text: &String) -> Self {
Self::RichText(RichText::new(text))
}
}
impl From<String> for WidgetText {
#[inline]
fn from(text: String) -> Self {
Self::RichText(RichText::new(text))
}
}
impl From<RichText> for WidgetText {
#[inline]
fn from(rich_text: RichText) -> Self {
Self::RichText(rich_text)
}
}
impl From<LayoutJob> for WidgetText {
#[inline]
fn from(layout_job: LayoutJob) -> Self {
Self::LayoutJob(layout_job)
}
}
impl From<Arc<Galley>> for WidgetText {
#[inline]
fn from(galley: Arc<Galley>) -> Self {
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 layed 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 layed out text.
#[inline]
pub fn size(&self) -> crate::Vec2 {
self.galley.size()
}
/// Size of the layed out text.
#[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);
}
}