mirror of
https://github.com/emilk/egui.git
synced 2026-09-01 14:20:04 -04:00
Choose your own font and size (#1154)
* Refactor text layout: don't need &Fonts in all functions * Replace indexing in Fonts with member function * Wrap Fonts in a Mutex * Remove mutex for Font::glyph_info_cache * Remove RwLock around Font::characters * Put FontsImpl and GalleyCache behind the same Mutex * Round font sizes to whole pixels before deduplicating them * Make TextStyle !Copy * Implement user-named TextStyle:s * round font size earlier * Cache fonts based on family and size * Move TextStyle into egui and Style * Remove body_text_style * Query graphics about max texture size and use that as font atlas size * Recreate texture atlas when it is getting full
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
use std::hash::Hash;
|
||||
|
||||
use crate::*;
|
||||
use epaint::{Shape, TextStyle};
|
||||
use epaint::Shape;
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
|
||||
@@ -374,7 +374,7 @@ impl ScrollArea {
|
||||
/// ```
|
||||
/// # egui::__run_test_ui(|ui| {
|
||||
/// let text_style = egui::TextStyle::Body;
|
||||
/// let row_height = ui.fonts()[text_style].row_height();
|
||||
/// let row_height = ui.text_style_height(&text_style);
|
||||
/// // let row_height = ui.spacing().interact_size.y; // if you are adding buttons instead of labels.
|
||||
/// let total_rows = 10_000;
|
||||
/// egui::ScrollArea::vertical().show_rows(ui, row_height, total_rows, |ui, row_range| {
|
||||
|
||||
@@ -296,7 +296,8 @@ impl<'open> Window<'open> {
|
||||
.and_then(|window_interaction| {
|
||||
// Calculate roughly how much larger the window size is compared to the inner rect
|
||||
let title_bar_height = if with_title_bar {
|
||||
title.font_height(ctx) + title_content_spacing
|
||||
let style = ctx.style();
|
||||
title.font_height(&ctx.fonts(), &style) + title_content_spacing
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
@@ -764,7 +765,7 @@ fn show_title_bar(
|
||||
) -> TitleBar {
|
||||
let inner_response = ui.horizontal(|ui| {
|
||||
let height = title
|
||||
.font_height(ui.ctx())
|
||||
.font_height(&ui.fonts(), ui.style())
|
||||
.max(ui.spacing().interact_size.y);
|
||||
ui.set_min_height(height);
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ impl ContextImpl {
|
||||
self.input = input.begin_frame(new_raw_input);
|
||||
self.frame_state.begin_frame(&self.input);
|
||||
|
||||
self.update_fonts_mut(self.input.pixels_per_point());
|
||||
self.update_fonts_mut();
|
||||
|
||||
// Ensure we register the background area so panels and background ui can catch clicks:
|
||||
let screen_rect = self.input.screen_rect();
|
||||
@@ -77,27 +77,21 @@ impl ContextImpl {
|
||||
}
|
||||
|
||||
/// Load fonts unless already loaded.
|
||||
fn update_fonts_mut(&mut self, pixels_per_point: f32) {
|
||||
let new_font_definitions = self.memory.new_font_definitions.take();
|
||||
fn update_fonts_mut(&mut self) {
|
||||
let pixels_per_point = self.input.pixels_per_point();
|
||||
let max_texture_side = self.input.raw.max_texture_side;
|
||||
|
||||
let pixels_per_point_changed = match &self.fonts {
|
||||
None => true,
|
||||
Some(current_fonts) => {
|
||||
(current_fonts.pixels_per_point() - pixels_per_point).abs() > 1e-3
|
||||
}
|
||||
};
|
||||
|
||||
if self.fonts.is_none() || new_font_definitions.is_some() || pixels_per_point_changed {
|
||||
self.fonts = Some(Fonts::new(
|
||||
pixels_per_point,
|
||||
new_font_definitions.unwrap_or_else(|| {
|
||||
self.fonts
|
||||
.as_ref()
|
||||
.map(|font| font.definitions().clone())
|
||||
.unwrap_or_default()
|
||||
}),
|
||||
));
|
||||
if let Some(font_definitions) = self.memory.new_font_definitions.take() {
|
||||
let fonts = Fonts::new(pixels_per_point, max_texture_side, font_definitions);
|
||||
self.fonts = Some(fonts);
|
||||
}
|
||||
|
||||
let fonts = self.fonts.get_or_insert_with(|| {
|
||||
let font_definitions = FontDefinitions::default();
|
||||
Fonts::new(pixels_per_point, max_texture_side, font_definitions)
|
||||
});
|
||||
|
||||
fonts.begin_frame(pixels_per_point, max_texture_side);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -521,7 +515,7 @@ impl Context {
|
||||
pub fn set_fonts(&self, font_definitions: FontDefinitions) {
|
||||
if let Some(current_fonts) = &*self.fonts_mut() {
|
||||
// NOTE: this comparison is expensive since it checks TTF data for equality
|
||||
if current_fonts.definitions() == &font_definitions {
|
||||
if current_fonts.lock().fonts.definitions() == &font_definitions {
|
||||
return; // no change - save us from reloading font textures
|
||||
}
|
||||
}
|
||||
@@ -700,8 +694,6 @@ impl Context {
|
||||
self.request_repaint();
|
||||
}
|
||||
|
||||
self.fonts().end_frame();
|
||||
|
||||
{
|
||||
let ctx_impl = &mut *self.write();
|
||||
ctx_impl
|
||||
@@ -953,16 +945,6 @@ impl Context {
|
||||
self.style_ui(ui);
|
||||
});
|
||||
|
||||
CollapsingHeader::new("🔠 Fonts")
|
||||
.default_open(false)
|
||||
.show(ui, |ui| {
|
||||
let mut font_definitions = self.fonts().definitions().clone();
|
||||
font_definitions.ui(ui);
|
||||
let font_image_size = self.fonts().font_image_size();
|
||||
crate::introspection::font_texture_ui(ui, font_image_size);
|
||||
self.set_fonts(font_definitions);
|
||||
});
|
||||
|
||||
CollapsingHeader::new("✒ Painting")
|
||||
.default_open(true)
|
||||
.show(ui, |ui| {
|
||||
@@ -1039,6 +1021,13 @@ impl Context {
|
||||
.show(ui, |ui| {
|
||||
self.texture_ui(ui);
|
||||
});
|
||||
|
||||
CollapsingHeader::new("🔠 Font texture")
|
||||
.default_open(false)
|
||||
.show(ui, |ui| {
|
||||
let font_image_size = self.fonts().font_image_size();
|
||||
crate::introspection::font_texture_ui(ui, font_image_size);
|
||||
});
|
||||
}
|
||||
|
||||
/// Show stats about the allocated textures.
|
||||
@@ -1080,8 +1069,12 @@ impl Context {
|
||||
size *= (max_preview_size.x / size.x).min(1.0);
|
||||
size *= (max_preview_size.y / size.y).min(1.0);
|
||||
ui.image(texture_id, size).on_hover_ui(|ui| {
|
||||
// show full size on hover
|
||||
ui.image(texture_id, Vec2::new(w as f32, h as f32));
|
||||
// show larger on hover
|
||||
let max_size = 0.5 * ui.ctx().input().screen_rect().size();
|
||||
let mut size = Vec2::new(w as f32, h as f32);
|
||||
size *= max_size.x / size.x.max(max_size.x);
|
||||
size *= max_size.y / size.y.max(max_size.y);
|
||||
ui.image(texture_id, size);
|
||||
});
|
||||
|
||||
ui.label(format!("{} x {}", w, h));
|
||||
|
||||
@@ -28,6 +28,13 @@ pub struct RawInput {
|
||||
/// Set this the first frame, whenever it changes, or just on every frame.
|
||||
pub pixels_per_point: Option<f32>,
|
||||
|
||||
/// Maximum size of one side of the font texture.
|
||||
///
|
||||
/// Ask your graphics drivers about this. This corresponds to `GL_MAX_TEXTURE_SIZE`.
|
||||
///
|
||||
/// The default is a very small (but very portable) 2048.
|
||||
pub max_texture_side: usize,
|
||||
|
||||
/// Monotonically increasing time, in seconds. Relative to whatever. Used for animations.
|
||||
/// If `None` is provided, egui will assume a time delta of `predicted_dt` (default 1/60 seconds).
|
||||
pub time: Option<f64>,
|
||||
@@ -62,6 +69,7 @@ impl Default for RawInput {
|
||||
Self {
|
||||
screen_rect: None,
|
||||
pixels_per_point: None,
|
||||
max_texture_side: 2048,
|
||||
time: None,
|
||||
predicted_dt: 1.0 / 60.0,
|
||||
modifiers: Modifiers::default(),
|
||||
@@ -81,6 +89,7 @@ impl RawInput {
|
||||
RawInput {
|
||||
screen_rect: self.screen_rect.take(),
|
||||
pixels_per_point: self.pixels_per_point.take(),
|
||||
max_texture_side: self.max_texture_side,
|
||||
time: self.time.take(),
|
||||
predicted_dt: self.predicted_dt,
|
||||
modifiers: self.modifiers,
|
||||
@@ -95,6 +104,7 @@ impl RawInput {
|
||||
let Self {
|
||||
screen_rect,
|
||||
pixels_per_point,
|
||||
max_texture_side,
|
||||
time,
|
||||
predicted_dt,
|
||||
modifiers,
|
||||
@@ -105,6 +115,7 @@ impl RawInput {
|
||||
|
||||
self.screen_rect = screen_rect.or(self.screen_rect);
|
||||
self.pixels_per_point = pixels_per_point.or(self.pixels_per_point);
|
||||
self.max_texture_side = max_texture_side; // use latest
|
||||
self.time = time; // use latest time
|
||||
self.predicted_dt = predicted_dt; // use latest dt
|
||||
self.modifiers = modifiers; // use latest
|
||||
@@ -357,6 +368,7 @@ impl RawInput {
|
||||
let Self {
|
||||
screen_rect,
|
||||
pixels_per_point,
|
||||
max_texture_side,
|
||||
time,
|
||||
predicted_dt,
|
||||
modifiers,
|
||||
@@ -370,6 +382,7 @@ impl RawInput {
|
||||
.on_hover_text(
|
||||
"Also called HDPI factor.\nNumber of physical pixels per each logical pixel.",
|
||||
);
|
||||
ui.label(format!("max_texture_side: {}", max_texture_side));
|
||||
if let Some(time) = time {
|
||||
ui.label(format!("time: {:.3} s", time));
|
||||
} else {
|
||||
|
||||
@@ -700,7 +700,12 @@ impl InputState {
|
||||
events,
|
||||
} = self;
|
||||
|
||||
ui.style_mut().body_text_style = epaint::TextStyle::Monospace;
|
||||
ui.style_mut()
|
||||
.text_styles
|
||||
.get_mut(&crate::TextStyle::Body)
|
||||
.unwrap()
|
||||
.family = crate::FontFamily::Monospace;
|
||||
|
||||
ui.collapsing("Raw Input", |ui| raw.ui(ui));
|
||||
|
||||
crate::containers::CollapsingHeader::new("🖱 Pointer")
|
||||
|
||||
@@ -1,6 +1,27 @@
|
||||
//! uis for egui types.
|
||||
//! Showing UI:s for egui/epaint types.
|
||||
use crate::*;
|
||||
|
||||
pub fn font_family_ui(ui: &mut Ui, font_family: &mut FontFamily) {
|
||||
let families = ui.fonts().families();
|
||||
ui.horizontal(|ui| {
|
||||
for alternative in families {
|
||||
let text = alternative.to_string();
|
||||
ui.radio_value(font_family, alternative, text);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub fn font_id_ui(ui: &mut Ui, font_id: &mut FontId) {
|
||||
let families = ui.fonts().families();
|
||||
ui.horizontal(|ui| {
|
||||
ui.add(Slider::new(&mut font_id.size, 4.0..=40.0).max_decimals(0));
|
||||
for alternative in families {
|
||||
let text = alternative.to_string();
|
||||
ui.radio_value(&mut font_id.family, alternative, text);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Show font texture in demo Ui
|
||||
pub(crate) fn font_texture_ui(ui: &mut Ui, [width, height]: [usize; 2]) -> Response {
|
||||
use epaint::Mesh;
|
||||
@@ -55,33 +76,16 @@ pub(crate) fn font_texture_ui(ui: &mut Ui, [width, height]: [usize; 2]) -> Respo
|
||||
.response
|
||||
}
|
||||
|
||||
impl Widget for &mut epaint::text::FontDefinitions {
|
||||
fn ui(self, ui: &mut Ui) -> Response {
|
||||
ui.vertical(|ui| {
|
||||
for (text_style, (_family, size)) in self.family_and_size.iter_mut() {
|
||||
// TODO: radio button for family
|
||||
ui.add(
|
||||
Slider::new(size, 4.0..=40.0)
|
||||
.max_decimals(0)
|
||||
.text(format!("{:?}", text_style)),
|
||||
);
|
||||
}
|
||||
crate::reset_button(ui, self);
|
||||
})
|
||||
.response
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for &epaint::stats::PaintStats {
|
||||
fn ui(self, ui: &mut Ui) -> Response {
|
||||
ui.vertical(|ui| {
|
||||
ui.label(
|
||||
"egui generates intermediate level shapes like circles and text. \
|
||||
These are later tessellated into triangles.",
|
||||
These are later tessellated into triangles.",
|
||||
);
|
||||
ui.add_space(10.0);
|
||||
|
||||
ui.style_mut().body_text_style = TextStyle::Monospace;
|
||||
ui.style_mut().override_text_style = Some(TextStyle::Monospace);
|
||||
|
||||
let epaint::stats::PaintStats {
|
||||
shapes,
|
||||
@@ -124,7 +128,7 @@ impl Widget for &epaint::stats::PaintStats {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn label(ui: &mut Ui, alloc_info: &epaint::stats::AllocInfo, what: &str) -> Response {
|
||||
fn label(ui: &mut Ui, alloc_info: &epaint::stats::AllocInfo, what: &str) -> Response {
|
||||
ui.add(Label::new(alloc_info.format(what)).wrap(false))
|
||||
}
|
||||
|
||||
|
||||
@@ -77,7 +77,6 @@ impl Region {
|
||||
/// Layout direction, one of `LeftToRight`, `RightToLeft`, `TopDown`, `BottomUp`.
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
|
||||
pub enum Direction {
|
||||
LeftToRight,
|
||||
RightToLeft,
|
||||
|
||||
@@ -364,7 +364,7 @@ mod frame_state;
|
||||
pub(crate) mod grid;
|
||||
mod id;
|
||||
mod input_state;
|
||||
mod introspection;
|
||||
pub mod introspection;
|
||||
pub mod layers;
|
||||
mod layout;
|
||||
mod memory;
|
||||
@@ -385,7 +385,7 @@ pub use epaint::emath;
|
||||
pub use emath::{lerp, pos2, remap, remap_clamp, vec2, Align, Align2, NumExt, Pos2, Rect, Vec2};
|
||||
pub use epaint::{
|
||||
color, mutex,
|
||||
text::{FontData, FontDefinitions, FontFamily, TextStyle},
|
||||
text::{FontData, FontDefinitions, FontFamily, FontId},
|
||||
textures::TexturesDelta,
|
||||
AlphaImage, ClippedMesh, Color32, ColorImage, ImageData, Rgba, Shape, Stroke, TextureHandle,
|
||||
TextureId,
|
||||
@@ -394,7 +394,7 @@ pub use epaint::{
|
||||
pub mod text {
|
||||
pub use epaint::text::{
|
||||
FontData, FontDefinitions, FontFamily, Fonts, Galley, LayoutJob, LayoutSection, TextFormat,
|
||||
TextStyle, TAB_SIZE,
|
||||
TAB_SIZE,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -414,7 +414,7 @@ pub use {
|
||||
painter::Painter,
|
||||
response::{InnerResponse, Response},
|
||||
sense::Sense,
|
||||
style::{Style, Visuals},
|
||||
style::{FontSelection, Style, TextStyle, Visuals},
|
||||
text::{Galley, TextFormat},
|
||||
ui::Ui,
|
||||
widget_text::{RichText, WidgetText},
|
||||
@@ -511,7 +511,7 @@ macro_rules! egui_assert {
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// egui supports around 1216 emojis in total.
|
||||
/// The default egui fonts supports around 1216 emojis in total.
|
||||
/// Here are some of the most useful:
|
||||
/// ∞⊗⎗⎘⎙⏏⏴⏵⏶⏷
|
||||
/// ⏩⏪⏭⏮⏸⏹⏺■▶📾🔀🔁🔃
|
||||
|
||||
@@ -414,7 +414,8 @@ impl SubMenuButton {
|
||||
let button_padding = ui.spacing().button_padding;
|
||||
let total_extra = button_padding + button_padding;
|
||||
let text_available_width = ui.available_width() - total_extra.x;
|
||||
let text_galley = text.into_galley(ui, Some(true), text_available_width, text_style);
|
||||
let text_galley =
|
||||
text.into_galley(ui, Some(true), text_available_width, text_style.clone());
|
||||
|
||||
let icon_available_width = text_available_width - text_galley.size().x;
|
||||
let icon_galley = icon.into_galley(ui, Some(true), icon_available_width, text_style);
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
use crate::{
|
||||
emath::{Align2, Pos2, Rect, Vec2},
|
||||
layers::{LayerId, PaintList, ShapeIdx},
|
||||
Color32, Context,
|
||||
Color32, Context, FontId,
|
||||
};
|
||||
use epaint::{
|
||||
mutex::{Arc, RwLockReadGuard, RwLockWriteGuard},
|
||||
text::{Fonts, Galley, TextStyle},
|
||||
text::{Fonts, Galley},
|
||||
CircleShape, RectShape, Shape, Stroke, TextShape,
|
||||
};
|
||||
|
||||
@@ -30,6 +30,7 @@ pub struct Painter {
|
||||
}
|
||||
|
||||
impl Painter {
|
||||
/// Create a painter to a specific layer within a certain clip rectangle.
|
||||
pub fn new(ctx: Context, layer_id: LayerId, clip_rect: Rect) -> Self {
|
||||
Self {
|
||||
ctx,
|
||||
@@ -39,6 +40,7 @@ impl Painter {
|
||||
}
|
||||
}
|
||||
|
||||
/// Redirect where you are painting.
|
||||
#[must_use]
|
||||
pub fn with_layer_id(self, layer_id: LayerId) -> Self {
|
||||
Self {
|
||||
@@ -49,7 +51,7 @@ impl Painter {
|
||||
}
|
||||
}
|
||||
|
||||
/// redirect
|
||||
/// Redirect where you are painting.
|
||||
pub fn set_layer_id(&mut self, layer_id: LayerId) {
|
||||
self.layer_id = layer_id;
|
||||
}
|
||||
@@ -194,12 +196,11 @@ impl Painter {
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
pub fn debug_rect(&mut self, rect: Rect, color: Color32, text: impl ToString) {
|
||||
self.rect_stroke(rect, 0.0, (1.0, color));
|
||||
let text_style = TextStyle::Monospace;
|
||||
self.text(
|
||||
rect.min,
|
||||
Align2::LEFT_TOP,
|
||||
text.to_string(),
|
||||
text_style,
|
||||
FontId::monospace(14.0),
|
||||
color,
|
||||
);
|
||||
}
|
||||
@@ -217,7 +218,7 @@ impl Painter {
|
||||
color: Color32,
|
||||
text: impl ToString,
|
||||
) -> Rect {
|
||||
let galley = self.layout_no_wrap(text.to_string(), TextStyle::Monospace, color);
|
||||
let galley = self.layout_no_wrap(text.to_string(), FontId::monospace(14.0), color);
|
||||
let rect = anchor.anchor_rect(Rect::from_min_size(pos, galley.size()));
|
||||
let frame_rect = rect.expand(2.0);
|
||||
self.add(Shape::rect_filled(
|
||||
@@ -324,7 +325,7 @@ impl Painter {
|
||||
impl Painter {
|
||||
/// Lay out and paint some text.
|
||||
///
|
||||
/// To center the text at the given position, use `anchor: (Center, Center)`.
|
||||
/// To center the text at the given position, use `Align2::CENTER_CENTER`.
|
||||
///
|
||||
/// To find out the size of text before painting it, use
|
||||
/// [`Self::layout`] or [`Self::layout_no_wrap`].
|
||||
@@ -336,10 +337,10 @@ impl Painter {
|
||||
pos: Pos2,
|
||||
anchor: Align2,
|
||||
text: impl ToString,
|
||||
text_style: TextStyle,
|
||||
font_id: FontId,
|
||||
text_color: Color32,
|
||||
) -> Rect {
|
||||
let galley = self.layout_no_wrap(text.to_string(), text_style, text_color);
|
||||
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);
|
||||
rect
|
||||
@@ -352,11 +353,11 @@ impl Painter {
|
||||
pub fn layout(
|
||||
&self,
|
||||
text: String,
|
||||
text_style: TextStyle,
|
||||
font_id: FontId,
|
||||
color: crate::Color32,
|
||||
wrap_width: f32,
|
||||
) -> Arc<Galley> {
|
||||
self.fonts().layout(text, text_style, color, wrap_width)
|
||||
self.fonts().layout(text, font_id, color, wrap_width)
|
||||
}
|
||||
|
||||
/// Will line break at `\n`.
|
||||
@@ -366,10 +367,10 @@ impl Painter {
|
||||
pub fn layout_no_wrap(
|
||||
&self,
|
||||
text: String,
|
||||
text_style: TextStyle,
|
||||
font_id: FontId,
|
||||
color: crate::Color32,
|
||||
) -> Arc<Galley> {
|
||||
self.fonts().layout(text, text_style, color, f32::INFINITY)
|
||||
self.fonts().layout(text, font_id, color, f32::INFINITY)
|
||||
}
|
||||
|
||||
/// Paint text that has already been layed out in a [`Galley`].
|
||||
|
||||
@@ -2,8 +2,123 @@
|
||||
|
||||
#![allow(clippy::if_same_then_else)]
|
||||
|
||||
use crate::{color::*, emath::*, Response, RichText, WidgetText};
|
||||
use epaint::{Shadow, Stroke, TextStyle};
|
||||
use crate::{color::*, emath::*, FontFamily, FontId, Response, RichText, WidgetText};
|
||||
use epaint::{mutex::Arc, Shadow, Stroke};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// Alias for a [`FontId`] (font of a certain size).
|
||||
///
|
||||
/// The font is found via look-up in [`Style::text_styles`].
|
||||
/// You can use [`TextStyle::resolve`] to do this lookup.
|
||||
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub enum TextStyle {
|
||||
/// Used when small text is needed.
|
||||
Small,
|
||||
|
||||
/// Normal labels. Easily readable, doesn't take up too much space.
|
||||
Body,
|
||||
|
||||
/// Same size as [`Self::Body]`, but used when monospace is important (for aligning number, code snippets, etc).
|
||||
Monospace,
|
||||
|
||||
/// Buttons. Maybe slightly bigger than [`Self::Body]`.
|
||||
/// Signifies that he item is interactive.
|
||||
Button,
|
||||
|
||||
/// Heading. Probably larger than [`Self::Body]`.
|
||||
Heading,
|
||||
|
||||
/// A user-chosen style, found in [`Style::text_styles`].
|
||||
/// ```
|
||||
/// egui::TextStyle::Name("footing".into());
|
||||
/// ````
|
||||
Name(Arc<str>),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for TextStyle {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Small => "Small".fmt(f),
|
||||
Self::Body => "Body".fmt(f),
|
||||
Self::Monospace => "Monospace".fmt(f),
|
||||
Self::Button => "Button".fmt(f),
|
||||
Self::Heading => "Heading".fmt(f),
|
||||
Self::Name(name) => (*name).fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TextStyle {
|
||||
/// Look up this [`TextStyle`] in [`Style::text_styles`].
|
||||
pub fn resolve(&self, style: &Style) -> FontId {
|
||||
style.text_styles.get(self).cloned().unwrap_or_else(|| {
|
||||
panic!(
|
||||
"Failed to find {:?} in Style::text_styles. Available styles:\n{:#?}",
|
||||
self,
|
||||
style.text_styles()
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// A way to select [`FontId`], either by picking one directly or by using a [`TextStyle`].
|
||||
pub enum FontSelection {
|
||||
/// Default text style - will use [`TextStyle::Body`], unless
|
||||
/// [`Style::override_font_id`] or [`Style::override_text_style`] is set.
|
||||
Default,
|
||||
|
||||
/// Directly select size and font family
|
||||
FontId(FontId),
|
||||
|
||||
/// Use a [`TextStyle`] to look up the [`FontId`] in [`Style::text_styles`].
|
||||
Style(TextStyle),
|
||||
}
|
||||
|
||||
impl Default for FontSelection {
|
||||
#[inline]
|
||||
fn default() -> Self {
|
||||
Self::Default
|
||||
}
|
||||
}
|
||||
|
||||
impl FontSelection {
|
||||
pub fn resolve(self, style: &Style) -> FontId {
|
||||
match self {
|
||||
Self::Default => {
|
||||
if let Some(override_font_id) = &style.override_font_id {
|
||||
override_font_id.clone()
|
||||
} else if let Some(text_style) = &style.override_text_style {
|
||||
text_style.resolve(style)
|
||||
} else {
|
||||
TextStyle::Body.resolve(style)
|
||||
}
|
||||
}
|
||||
Self::FontId(font_id) => font_id,
|
||||
Self::Style(text_style) => text_style.resolve(style),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<FontId> for FontSelection {
|
||||
#[inline(always)]
|
||||
fn from(font_id: FontId) -> Self {
|
||||
Self::FontId(font_id)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TextStyle> for FontSelection {
|
||||
#[inline(always)]
|
||||
fn from(text_style: TextStyle) -> Self {
|
||||
Self::Style(text_style)
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// Specifies the look and feel of egui.
|
||||
///
|
||||
@@ -15,15 +130,23 @@ use epaint::{Shadow, Stroke, TextStyle};
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
#[cfg_attr(feature = "serde", serde(default))]
|
||||
pub struct Style {
|
||||
/// Default `TextStyle` for normal text (i.e. for `Label` and `TextEdit`).
|
||||
pub body_text_style: TextStyle,
|
||||
|
||||
/// If set this will change the default [`TextStyle`] for all widgets.
|
||||
///
|
||||
/// On most widgets you can also set an explicit text style,
|
||||
/// which will take precedence over this.
|
||||
pub override_text_style: Option<TextStyle>,
|
||||
|
||||
/// If set this will change the font family and size for all widgets.
|
||||
///
|
||||
/// On most widgets you can also set an explicit text style,
|
||||
/// which will take precedence over this.
|
||||
pub override_font_id: Option<FontId>,
|
||||
|
||||
/// The [`FontFamily`] and size you want to use for a specific [`TextStyle`].
|
||||
///
|
||||
/// The most convenient way to look something up in this is to use [`TextStyle::resolve`].
|
||||
pub text_styles: BTreeMap<TextStyle, FontId>,
|
||||
|
||||
/// If set, labels buttons wtc will use this to determine whether or not
|
||||
/// to wrap the text at the right edge of the `Ui` they are in.
|
||||
/// By default this is `None`.
|
||||
@@ -77,6 +200,11 @@ impl Style {
|
||||
pub fn noninteractive(&self) -> &WidgetVisuals {
|
||||
&self.visuals.widgets.noninteractive
|
||||
}
|
||||
|
||||
/// All known text styles.
|
||||
pub fn text_styles(&self) -> Vec<TextStyle> {
|
||||
self.text_styles.keys().cloned().collect()
|
||||
}
|
||||
}
|
||||
|
||||
/// Controls the sizes and distances between widgets.
|
||||
@@ -356,11 +484,35 @@ pub struct DebugOptions {
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// The default text styles of the default egui theme.
|
||||
pub fn default_text_styles() -> BTreeMap<TextStyle, FontId> {
|
||||
let mut text_styles = BTreeMap::new();
|
||||
text_styles.insert(
|
||||
TextStyle::Small,
|
||||
FontId::new(10.0, FontFamily::Proportional),
|
||||
);
|
||||
text_styles.insert(TextStyle::Body, FontId::new(14.0, FontFamily::Proportional));
|
||||
text_styles.insert(
|
||||
TextStyle::Button,
|
||||
FontId::new(14.0, FontFamily::Proportional),
|
||||
);
|
||||
text_styles.insert(
|
||||
TextStyle::Heading,
|
||||
FontId::new(20.0, FontFamily::Proportional),
|
||||
);
|
||||
text_styles.insert(
|
||||
TextStyle::Monospace,
|
||||
FontId::new(14.0, FontFamily::Monospace),
|
||||
);
|
||||
text_styles
|
||||
}
|
||||
|
||||
impl Default for Style {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
body_text_style: TextStyle::Body,
|
||||
override_font_id: None,
|
||||
override_text_style: None,
|
||||
text_styles: default_text_styles(),
|
||||
wrap: None,
|
||||
spacing: Spacing::default(),
|
||||
interaction: Interaction::default(),
|
||||
@@ -565,8 +717,9 @@ use crate::{widgets::*, Ui};
|
||||
impl Style {
|
||||
pub fn ui(&mut self, ui: &mut crate::Ui) {
|
||||
let Self {
|
||||
body_text_style,
|
||||
override_font_id,
|
||||
override_text_style,
|
||||
text_styles,
|
||||
wrap: _,
|
||||
spacing,
|
||||
interaction,
|
||||
@@ -579,11 +732,14 @@ impl Style {
|
||||
visuals.light_dark_radio_buttons(ui);
|
||||
|
||||
crate::Grid::new("_options").show(ui, |ui| {
|
||||
ui.label("Default body text style:");
|
||||
ui.label("Override font id:");
|
||||
ui.horizontal(|ui| {
|
||||
for &style in &[TextStyle::Body, TextStyle::Monospace] {
|
||||
let text = crate::RichText::new(format!("{:?}", style)).text_style(style);
|
||||
ui.radio_value(body_text_style, style, text);
|
||||
ui.radio_value(override_font_id, None, "None");
|
||||
if ui.radio(override_font_id.is_some(), "override").clicked() {
|
||||
*override_font_id = Some(FontId::default());
|
||||
}
|
||||
if let Some(override_font_id) = override_font_id {
|
||||
crate::introspection::font_id_ui(ui, override_font_id);
|
||||
}
|
||||
});
|
||||
ui.end_row();
|
||||
@@ -592,12 +748,14 @@ impl Style {
|
||||
crate::ComboBox::from_id_source("Override text style")
|
||||
.selected_text(match override_text_style {
|
||||
None => "None".to_owned(),
|
||||
Some(override_text_style) => format!("{:?}", override_text_style),
|
||||
Some(override_text_style) => override_text_style.to_string(),
|
||||
})
|
||||
.show_ui(ui, |ui| {
|
||||
ui.selectable_value(override_text_style, None, "None");
|
||||
for style in TextStyle::all() {
|
||||
let text = crate::RichText::new(format!("{:?}", style)).text_style(style);
|
||||
let all_text_styles = ui.style().text_styles();
|
||||
for style in all_text_styles {
|
||||
let text =
|
||||
crate::RichText::new(style.to_string()).text_style(style.clone());
|
||||
ui.selectable_value(override_text_style, Some(style), text);
|
||||
}
|
||||
});
|
||||
@@ -612,6 +770,7 @@ impl Style {
|
||||
ui.end_row();
|
||||
});
|
||||
|
||||
ui.collapsing("🔠 Text Styles", |ui| text_styles_ui(ui, text_styles));
|
||||
ui.collapsing("📏 Spacing", |ui| spacing.ui(ui));
|
||||
ui.collapsing("☝ Interaction", |ui| interaction.ui(ui));
|
||||
ui.collapsing("🎨 Visuals", |ui| visuals.ui(ui));
|
||||
@@ -626,6 +785,20 @@ impl Style {
|
||||
}
|
||||
}
|
||||
|
||||
fn text_styles_ui(ui: &mut Ui, text_styles: &mut BTreeMap<TextStyle, FontId>) -> Response {
|
||||
ui.vertical(|ui| {
|
||||
crate::Grid::new("text_styles").show(ui, |ui| {
|
||||
for (text_style, font_id) in text_styles.iter_mut() {
|
||||
ui.label(RichText::new(text_style.to_string()).font(font_id.clone()));
|
||||
crate::introspection::font_id_ui(ui, font_id);
|
||||
ui.end_row();
|
||||
}
|
||||
});
|
||||
crate::reset_button_with(ui, text_styles, default_text_styles());
|
||||
})
|
||||
.response
|
||||
}
|
||||
|
||||
impl Spacing {
|
||||
pub fn ui(&mut self, ui: &mut crate::Ui) {
|
||||
let Self {
|
||||
|
||||
@@ -133,7 +133,7 @@ impl Ui {
|
||||
/// Example:
|
||||
/// ```
|
||||
/// # egui::__run_test_ui(|ui| {
|
||||
/// ui.style_mut().body_text_style = egui::TextStyle::Heading;
|
||||
/// ui.style_mut().override_text_style = Some(egui::TextStyle::Heading);
|
||||
/// # });
|
||||
/// ```
|
||||
pub fn style_mut(&mut self) -> &mut Style {
|
||||
@@ -359,6 +359,11 @@ impl Ui {
|
||||
self.ctx().fonts()
|
||||
}
|
||||
|
||||
/// The height of text of this text style
|
||||
pub fn text_style_height(&self, style: &TextStyle) -> f32 {
|
||||
self.fonts().row_height(&style.resolve(self.style()))
|
||||
}
|
||||
|
||||
/// Screen-space rectangle for clipping what we paint in this ui.
|
||||
/// This is used, for instance, to avoid painting outside a window that is smaller than its contents.
|
||||
#[inline]
|
||||
@@ -1086,6 +1091,16 @@ impl Ui {
|
||||
/// Shortcut for `add(Label::new(text))`
|
||||
///
|
||||
/// See also [`Label`].
|
||||
///
|
||||
/// ### Example
|
||||
/// ```
|
||||
/// # egui::__run_test_ui(|ui| {
|
||||
/// use egui::{RichText, FontId, Color32};
|
||||
/// ui.label("Normal text");
|
||||
/// ui.label(RichText::new("Large text").font(FontId::proportional(40.0)));
|
||||
/// ui.label(RichText::new("Red text").color(Color32::RED));
|
||||
/// # });
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn label(&mut self, text: impl Into<WidgetText>) -> Response {
|
||||
Label::new(text).ui(self)
|
||||
@@ -1256,12 +1271,12 @@ impl Ui {
|
||||
pub fn radio_value<Value: PartialEq>(
|
||||
&mut self,
|
||||
current_value: &mut Value,
|
||||
selected_value: Value,
|
||||
alternative: Value,
|
||||
text: impl Into<WidgetText>,
|
||||
) -> Response {
|
||||
let mut response = self.radio(*current_value == selected_value, text);
|
||||
let mut response = self.radio(*current_value == alternative, text);
|
||||
if response.clicked() {
|
||||
*current_value = selected_value;
|
||||
*current_value = alternative;
|
||||
response.mark_changed();
|
||||
}
|
||||
response
|
||||
|
||||
@@ -1,17 +1,30 @@
|
||||
use epaint::mutex::Arc;
|
||||
|
||||
use crate::{
|
||||
style::WidgetVisuals, text::LayoutJob, Align, Color32, Context, Galley, Pos2, Style, TextStyle,
|
||||
Ui, Visuals,
|
||||
style::WidgetVisuals, text::LayoutJob, Align, Color32, FontFamily, FontSelection, 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.
|
||||
///
|
||||
/// A `RichText` can be used in most widgets and helper functions, e.g. [`Ui::label`] and [`Ui::button`].
|
||||
///
|
||||
/// ### Example
|
||||
/// ```
|
||||
/// use egui::{RichText, Color32};
|
||||
///
|
||||
/// RichText::new("Plain");
|
||||
/// RichText::new("colored").color(Color32::RED);
|
||||
/// RichText::new("Large and underlined").size(20.0).underline();
|
||||
/// ```
|
||||
#[derive(Clone, Default, PartialEq)]
|
||||
pub struct RichText {
|
||||
text: String,
|
||||
size: Option<f32>,
|
||||
family: Option<FontFamily>,
|
||||
text_style: Option<TextStyle>,
|
||||
background_color: Color32,
|
||||
text_color: Option<Color32>,
|
||||
@@ -64,6 +77,35 @@ impl RichText {
|
||||
&self.text
|
||||
}
|
||||
|
||||
/// Select the font size (in points).
|
||||
/// This overrides the value from [`Self::text_style`].
|
||||
#[inline]
|
||||
pub fn size(mut self, size: f32) -> Self {
|
||||
self.size = Some(size);
|
||||
self
|
||||
}
|
||||
|
||||
/// Select the font family.
|
||||
///
|
||||
/// This overrides the value from [`Self::text_style`].
|
||||
///
|
||||
/// Only the families available in [`crate::FontDefinitions::families`] may be used.
|
||||
#[inline]
|
||||
pub fn family(mut self, family: FontFamily) -> Self {
|
||||
self.family = Some(family);
|
||||
self
|
||||
}
|
||||
|
||||
/// Select the font and size.
|
||||
/// This overrides the value from [`Self::text_style`].
|
||||
#[inline]
|
||||
pub fn font(mut self, font_id: crate::FontId) -> Self {
|
||||
let crate::FontId { size, family } = font_id;
|
||||
self.size = Some(size);
|
||||
self.family = Some(family);
|
||||
self
|
||||
}
|
||||
|
||||
/// Override the [`TextStyle`].
|
||||
#[inline]
|
||||
pub fn text_style(mut self, text_style: TextStyle) -> Self {
|
||||
@@ -170,24 +212,33 @@ impl RichText {
|
||||
}
|
||||
|
||||
/// Read the font height of the selected text style.
|
||||
pub fn font_height(&self, ctx: &Context) -> f32 {
|
||||
let text_style = self
|
||||
.text_style
|
||||
.or(ctx.style().override_text_style)
|
||||
.unwrap_or(ctx.style().body_text_style);
|
||||
ctx.fonts().row_height(text_style)
|
||||
pub fn font_height(&self, fonts: &epaint::Fonts, style: &Style) -> f32 {
|
||||
let mut font_id = self.text_style.as_ref().map_or_else(
|
||||
|| FontSelection::Default.resolve(style),
|
||||
|text_style| text_style.resolve(style),
|
||||
);
|
||||
|
||||
if let Some(size) = self.size {
|
||||
font_id.size = size;
|
||||
}
|
||||
if let Some(family) = &self.family {
|
||||
font_id.family = family.clone();
|
||||
}
|
||||
fonts.row_height(&font_id)
|
||||
}
|
||||
|
||||
fn into_text_job(
|
||||
self,
|
||||
style: &Style,
|
||||
default_text_style: TextStyle,
|
||||
fallback_font: FontSelection,
|
||||
default_valign: Align,
|
||||
) -> WidgetTextJob {
|
||||
let text_color = self.get_text_color(&style.visuals);
|
||||
|
||||
let Self {
|
||||
text,
|
||||
size,
|
||||
family,
|
||||
text_style,
|
||||
background_color,
|
||||
text_color: _, // already used by `get_text_color`
|
||||
@@ -204,9 +255,21 @@ impl RichText {
|
||||
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(style.override_text_style)
|
||||
.unwrap_or(default_text_style);
|
||||
let font_id = {
|
||||
let mut font_id = text_style
|
||||
.or_else(|| style.override_text_style.clone())
|
||||
.map_or_else(
|
||||
|| fallback_font.resolve(style),
|
||||
|text_style| text_style.resolve(style),
|
||||
);
|
||||
if let Some(size) = size {
|
||||
font_id.size = size;
|
||||
}
|
||||
if let Some(family) = family {
|
||||
font_id.family = family;
|
||||
}
|
||||
font_id
|
||||
};
|
||||
|
||||
let mut background_color = background_color;
|
||||
if code {
|
||||
@@ -230,7 +293,7 @@ impl RichText {
|
||||
};
|
||||
|
||||
let text_format = crate::text::TextFormat {
|
||||
style: text_style,
|
||||
font_id,
|
||||
color: text_color,
|
||||
background: background_color,
|
||||
italics,
|
||||
@@ -270,6 +333,7 @@ impl RichText {
|
||||
#[derive(Clone)]
|
||||
pub enum WidgetText {
|
||||
RichText(RichText),
|
||||
|
||||
/// Use this [`LayoutJob`] when laying out the text.
|
||||
///
|
||||
/// Only [`LayoutJob::text`] and [`LayoutJob::sections`] are guaranteed to be respected.
|
||||
@@ -280,6 +344,7 @@ pub enum WidgetText {
|
||||
/// 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>),
|
||||
}
|
||||
@@ -438,10 +503,10 @@ impl WidgetText {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn font_height(&self, ctx: &Context) -> f32 {
|
||||
pub(crate) fn font_height(&self, fonts: &epaint::Fonts, style: &Style) -> f32 {
|
||||
match self {
|
||||
Self::RichText(text) => text.font_height(ctx),
|
||||
Self::LayoutJob(job) => job.font_height(&*ctx.fonts()),
|
||||
Self::RichText(text) => text.font_height(fonts, style),
|
||||
Self::LayoutJob(job) => job.font_height(fonts),
|
||||
Self::Galley(galley) => {
|
||||
if let Some(row) = galley.rows.first() {
|
||||
row.height()
|
||||
@@ -455,11 +520,11 @@ impl WidgetText {
|
||||
pub fn into_text_job(
|
||||
self,
|
||||
style: &Style,
|
||||
default_text_style: TextStyle,
|
||||
fallback_font: FontSelection,
|
||||
default_valign: Align,
|
||||
) -> WidgetTextJob {
|
||||
match self {
|
||||
Self::RichText(text) => text.into_text_job(style, default_text_style, default_valign),
|
||||
Self::RichText(text) => text.into_text_job(style, fallback_font, default_valign),
|
||||
Self::LayoutJob(job) => WidgetTextJob {
|
||||
job,
|
||||
job_has_color: true,
|
||||
@@ -482,7 +547,7 @@ impl WidgetText {
|
||||
ui: &Ui,
|
||||
wrap: Option<bool>,
|
||||
available_width: f32,
|
||||
default_text_style: TextStyle,
|
||||
fallback_font: impl Into<FontSelection>,
|
||||
) -> WidgetTextGalley {
|
||||
let wrap = wrap.unwrap_or_else(|| ui.wrap_text());
|
||||
let wrap_width = if wrap { available_width } else { f32::INFINITY };
|
||||
@@ -490,7 +555,7 @@ impl WidgetText {
|
||||
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);
|
||||
let mut text_job = text.into_text_job(ui.style(), fallback_font.into(), valign);
|
||||
text_job.job.wrap_width = wrap_width;
|
||||
WidgetTextGalley {
|
||||
galley: ui.fonts().layout_job(text_job.job),
|
||||
|
||||
@@ -195,7 +195,7 @@ impl<'a> Widget for DragValue<'a> {
|
||||
TextEdit::singleline(&mut value_text)
|
||||
.id(kb_edit_id)
|
||||
.desired_width(button_width)
|
||||
.text_style(TextStyle::Monospace),
|
||||
.font(TextStyle::Monospace),
|
||||
);
|
||||
if let Ok(parsed_value) = value_text.parse() {
|
||||
let parsed_value = clamp_to_range(parsed_value, clamp_range);
|
||||
|
||||
@@ -2,6 +2,8 @@ use crate::{widget_text::WidgetTextGalley, *};
|
||||
|
||||
/// Static text.
|
||||
///
|
||||
/// Usually it is more convenient to use [`Ui::label`].
|
||||
///
|
||||
/// ```
|
||||
/// # egui::__run_test_ui(|ui| {
|
||||
/// ui.label("Equivalent");
|
||||
@@ -84,7 +86,7 @@ impl Label {
|
||||
let valign = ui.layout().vertical_align();
|
||||
let mut text_job = self
|
||||
.text
|
||||
.into_text_job(ui.style(), ui.style().body_text_style, valign);
|
||||
.into_text_job(ui.style(), FontSelection::Default, valign);
|
||||
|
||||
let should_wrap = self.wrap.unwrap_or_else(|| ui.wrap_text());
|
||||
let available_width = ui.available_width();
|
||||
|
||||
@@ -1621,13 +1621,15 @@ fn add_rulers_and_text(
|
||||
text
|
||||
});
|
||||
|
||||
let font_id = TextStyle::Body.resolve(plot.ui.style());
|
||||
|
||||
let corner_value = elem.corner_value();
|
||||
shapes.push(Shape::text(
|
||||
&*plot.ui.fonts(),
|
||||
plot.transform.position_from_value(&corner_value) + vec2(3.0, -2.0),
|
||||
Align2::LEFT_BOTTOM,
|
||||
text,
|
||||
TextStyle::Body,
|
||||
font_id,
|
||||
plot.ui.visuals().text_color(),
|
||||
));
|
||||
}
|
||||
@@ -1677,12 +1679,14 @@ pub(super) fn rulers_at_value(
|
||||
}
|
||||
};
|
||||
|
||||
let font_id = TextStyle::Body.resolve(plot.ui.style());
|
||||
|
||||
shapes.push(Shape::text(
|
||||
&*plot.ui.fonts(),
|
||||
pointer + vec2(3.0, -2.0),
|
||||
Align2::LEFT_BOTTOM,
|
||||
text,
|
||||
TextStyle::Body,
|
||||
font_id,
|
||||
plot.ui.visuals().text_color(),
|
||||
));
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ impl Corner {
|
||||
}
|
||||
|
||||
/// The configuration for a plot legend.
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub struct Legend {
|
||||
pub text_style: TextStyle,
|
||||
pub background_alpha: f32,
|
||||
@@ -82,16 +82,18 @@ impl LegendEntry {
|
||||
}
|
||||
}
|
||||
|
||||
fn ui(&mut self, ui: &mut Ui, text: String) -> Response {
|
||||
fn ui(&mut self, ui: &mut Ui, text: String, text_style: &TextStyle) -> Response {
|
||||
let Self {
|
||||
color,
|
||||
checked,
|
||||
hovered,
|
||||
} = self;
|
||||
|
||||
let galley =
|
||||
ui.fonts()
|
||||
.layout_delayed_color(text, ui.style().body_text_style, f32::INFINITY);
|
||||
let font_id = text_style.resolve(ui.style());
|
||||
|
||||
let galley = ui
|
||||
.fonts()
|
||||
.layout_delayed_color(text, font_id, f32::INFINITY);
|
||||
|
||||
let icon_size = galley.size().y;
|
||||
let icon_spacing = icon_size / 5.0;
|
||||
@@ -236,7 +238,6 @@ impl Widget for &mut LegendWidget {
|
||||
let mut legend_ui = ui.child_ui(legend_rect, layout);
|
||||
legend_ui
|
||||
.scope(|ui| {
|
||||
ui.style_mut().body_text_style = config.text_style;
|
||||
let background_frame = Frame {
|
||||
margin: vec2(8.0, 4.0),
|
||||
corner_radius: ui.style().visuals.window_corner_radius,
|
||||
@@ -249,7 +250,7 @@ impl Widget for &mut LegendWidget {
|
||||
.show(ui, |ui| {
|
||||
entries
|
||||
.iter_mut()
|
||||
.map(|(name, entry)| entry.ui(ui, name.clone()))
|
||||
.map(|(name, entry)| entry.ui(ui, name.clone(), &config.text_style))
|
||||
.reduce(|r1, r2| r1.union(r2))
|
||||
.unwrap()
|
||||
})
|
||||
|
||||
@@ -683,7 +683,8 @@ impl PreparedPlot {
|
||||
let Self { transform, .. } = self;
|
||||
|
||||
let bounds = transform.bounds();
|
||||
let text_style = TextStyle::Body;
|
||||
|
||||
let font_id = TextStyle::Body.resolve(ui.style());
|
||||
|
||||
let base: i64 = 10;
|
||||
let basef = base as f64;
|
||||
@@ -741,7 +742,7 @@ impl PreparedPlot {
|
||||
let color = color_from_alpha(ui, text_alpha);
|
||||
let text = emath::round_to_decimals(value_main, 5).to_string(); // hack
|
||||
|
||||
let galley = ui.painter().layout_no_wrap(text, text_style, color);
|
||||
let galley = ui.painter().layout_no_wrap(text, font_id.clone(), color);
|
||||
|
||||
let mut text_pos = pos_in_gui + vec2(1.0, -galley.size().y);
|
||||
|
||||
|
||||
@@ -466,10 +466,8 @@ impl<'a> Slider<'a> {
|
||||
}
|
||||
|
||||
fn add_contents(&mut self, ui: &mut Ui) -> Response {
|
||||
let text_style = TextStyle::Button;
|
||||
let perpendicular = ui
|
||||
.fonts()
|
||||
.row_height(text_style)
|
||||
.text_style_height(&TextStyle::Body)
|
||||
.at_least(ui.spacing().interact_size.y);
|
||||
let slider_response = self.allocate_slider_space(ui, perpendicular);
|
||||
self.slider_ui(ui, &slider_response);
|
||||
|
||||
@@ -52,7 +52,7 @@ pub struct TextEdit<'t> {
|
||||
hint_text: WidgetText,
|
||||
id: Option<Id>,
|
||||
id_source: Option<Id>,
|
||||
text_style: Option<TextStyle>,
|
||||
font_selection: FontSelection,
|
||||
text_color: Option<Color32>,
|
||||
layouter: Option<&'t mut dyn FnMut(&Ui, &str, f32) -> Arc<Galley>>,
|
||||
password: bool,
|
||||
@@ -97,7 +97,7 @@ impl<'t> TextEdit<'t> {
|
||||
hint_text: Default::default(),
|
||||
id: None,
|
||||
id_source: None,
|
||||
text_style: None,
|
||||
font_selection: Default::default(),
|
||||
text_color: None,
|
||||
layouter: None,
|
||||
password: false,
|
||||
@@ -117,7 +117,7 @@ impl<'t> TextEdit<'t> {
|
||||
/// - monospaced font
|
||||
/// - focus lock
|
||||
pub fn code_editor(self) -> Self {
|
||||
self.text_style(TextStyle::Monospace).lock_focus(true)
|
||||
self.font(TextStyle::Monospace).lock_focus(true)
|
||||
}
|
||||
|
||||
/// Use if you want to set an explicit `Id` for this widget.
|
||||
@@ -144,11 +144,17 @@ impl<'t> TextEdit<'t> {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn text_style(mut self, text_style: TextStyle) -> Self {
|
||||
self.text_style = Some(text_style);
|
||||
/// Pick a [`FontId`] or [`TextStyle`].
|
||||
pub fn font(mut self, font_selection: impl Into<FontSelection>) -> Self {
|
||||
self.font_selection = font_selection.into();
|
||||
self
|
||||
}
|
||||
|
||||
#[deprecated = "Use .font(…) instead"]
|
||||
pub fn text_style(self, text_style: TextStyle) -> Self {
|
||||
self.font(text_style)
|
||||
}
|
||||
|
||||
pub fn text_color(mut self, text_color: Color32) -> Self {
|
||||
self.text_color = Some(text_color);
|
||||
self
|
||||
@@ -330,7 +336,7 @@ impl<'t> TextEdit<'t> {
|
||||
hint_text,
|
||||
id,
|
||||
id_source,
|
||||
text_style,
|
||||
font_selection,
|
||||
text_color,
|
||||
layouter,
|
||||
password,
|
||||
@@ -350,10 +356,9 @@ impl<'t> TextEdit<'t> {
|
||||
.unwrap_or_else(|| ui.visuals().widgets.inactive.text_color());
|
||||
|
||||
let prev_text = text.as_ref().to_owned();
|
||||
let text_style = text_style
|
||||
.or(ui.style().override_text_style)
|
||||
.unwrap_or_else(|| ui.style().body_text_style);
|
||||
let row_height = ui.fonts().row_height(text_style);
|
||||
|
||||
let font_id = font_selection.resolve(ui.style());
|
||||
let row_height = ui.fonts().row_height(&font_id);
|
||||
const MIN_WIDTH: f32 = 24.0; // Never make a `TextEdit` more narrow than this.
|
||||
let available_width = ui.available_width().at_least(MIN_WIDTH);
|
||||
let desired_width = desired_width.unwrap_or_else(|| ui.spacing().text_edit_width);
|
||||
@@ -363,12 +368,13 @@ impl<'t> TextEdit<'t> {
|
||||
desired_width.min(available_width)
|
||||
};
|
||||
|
||||
let font_id_clone = font_id.clone();
|
||||
let mut default_layouter = move |ui: &Ui, text: &str, wrap_width: f32| {
|
||||
let text = mask_if_password(password, text);
|
||||
ui.fonts().layout_job(if multiline {
|
||||
LayoutJob::simple(text, text_style, text_color, wrap_width)
|
||||
LayoutJob::simple(text, font_id_clone.clone(), text_color, wrap_width)
|
||||
} else {
|
||||
LayoutJob::simple_singleline(text, text_style, text_color)
|
||||
LayoutJob::simple_singleline(text, font_id_clone.clone(), text_color)
|
||||
})
|
||||
};
|
||||
|
||||
@@ -543,9 +549,9 @@ impl<'t> TextEdit<'t> {
|
||||
if text.as_ref().is_empty() && !hint_text.is_empty() {
|
||||
let hint_text_color = ui.visuals().weak_text_color();
|
||||
let galley = if multiline {
|
||||
hint_text.into_galley(ui, Some(true), desired_size.x, text_style)
|
||||
hint_text.into_galley(ui, Some(true), desired_size.x, font_id)
|
||||
} else {
|
||||
hint_text.into_galley(ui, Some(false), f32::INFINITY, text_style)
|
||||
hint_text.into_galley(ui, Some(false), f32::INFINITY, font_id)
|
||||
};
|
||||
galley.paint_with_fallback_color(&painter, response.rect.min, hint_text_color);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user