From 1aceaefba3205222c108da6d924e3cd98fd804ef Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 22 Jan 2022 18:29:53 +0100 Subject: [PATCH] Implement user-named TextStyle:s --- egui/src/introspection.rs | 2 +- egui/src/style.rs | 2 +- egui_demo_lib/src/apps/demo/font_book.rs | 4 +- egui_demo_lib/src/apps/demo/plot_demo.rs | 2 +- epaint/src/text/font.rs | 11 +- epaint/src/text/fonts.rs | 160 +++++++++++++++-------- epaint/src/text/text_layout.rs | 2 +- 7 files changed, 109 insertions(+), 74 deletions(-) diff --git a/egui/src/introspection.rs b/egui/src/introspection.rs index 04525119..fba6a060 100644 --- a/egui/src/introspection.rs +++ b/egui/src/introspection.rs @@ -58,7 +58,7 @@ pub(crate) fn font_texture_ui(ui: &mut Ui, [width, height]: [usize; 2]) -> Respo 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() { + for (text_style, (size, _family)) in self.styles.iter_mut() { // TODO: radio button for family ui.add( Slider::new(size, 4.0..=40.0) diff --git a/egui/src/style.rs b/egui/src/style.rs index 0192d5e0..1bb7392d 100644 --- a/egui/src/style.rs +++ b/egui/src/style.rs @@ -597,7 +597,7 @@ impl Style { }) .show_ui(ui, |ui| { ui.selectable_value(override_text_style, None, "None"); - for style in TextStyle::all() { + for style in TextStyle::built_in() { let text = crate::RichText::new(format!("{:?}", style)).text_style(style.clone()); ui.selectable_value(override_text_style, Some(style), text); diff --git a/egui_demo_lib/src/apps/demo/font_book.rs b/egui_demo_lib/src/apps/demo/font_book.rs index 660d0296..298d2a4b 100644 --- a/egui_demo_lib/src/apps/demo/font_book.rs +++ b/egui_demo_lib/src/apps/demo/font_book.rs @@ -54,7 +54,7 @@ impl super::View for FontBook { egui::ComboBox::from_label("Text style") .selected_text(format!("{:?}", self.text_style)) .show_ui(ui, |ui| { - for style in egui::TextStyle::all() { + for style in egui::TextStyle::built_in() { ui.selectable_value( &mut self.text_style, style.clone(), @@ -81,7 +81,7 @@ impl super::View for FontBook { ui.fonts() .lock() .fonts - .font_mut(&text_style) + .font_for_style(&text_style) .characters() .iter() .filter(|chr| !chr.is_whitespace() && !chr.is_ascii_control()) diff --git a/egui_demo_lib/src/apps/demo/plot_demo.rs b/egui_demo_lib/src/apps/demo/plot_demo.rs index e191835c..ef92809b 100644 --- a/egui_demo_lib/src/apps/demo/plot_demo.rs +++ b/egui_demo_lib/src/apps/demo/plot_demo.rs @@ -261,7 +261,7 @@ impl Widget for &mut LegendDemo { egui::Grid::new("settings").show(ui, |ui| { ui.label("Text style:"); ui.horizontal(|ui| { - TextStyle::all().for_each(|style| { + TextStyle::built_in().for_each(|style| { ui.selectable_value( &mut config.text_style, style.clone(), diff --git a/epaint/src/text/font.rs b/epaint/src/text/font.rs index dca5c96f..94047e37 100644 --- a/epaint/src/text/font.rs +++ b/epaint/src/text/font.rs @@ -1,6 +1,5 @@ use crate::{ mutex::{Arc, Mutex, RwLock}, - text::TextStyle, TextureAtlas, }; use ahash::AHashMap; @@ -195,7 +194,6 @@ type FontIndex = usize; // TODO: rename? /// Wrapper over multiple `FontImpl` (e.g. a primary + fallbacks for emojis) pub struct Font { - text_style: TextStyle, fonts: Vec>, /// Lazily calculated. characters: Option>, @@ -206,10 +204,9 @@ pub struct Font { } impl Font { - pub fn new(text_style: TextStyle, fonts: Vec>) -> Self { + pub fn new(fonts: Vec>) -> Self { if fonts.is_empty() { return Self { - text_style, fonts, characters: None, replacement_glyph: Default::default(), @@ -223,7 +220,6 @@ impl Font { let row_height = fonts[0].row_height(); let mut slf = Self { - text_style, fonts, characters: None, replacement_glyph: Default::default(), @@ -269,11 +265,6 @@ impl Font { }) } - #[inline(always)] - pub fn text_style(&self) -> &TextStyle { - &self.text_style - } - #[inline(always)] pub fn round_to_pixel(&self, point: f32) -> f32 { (point * self.pixels_per_point).round() / self.pixels_per_point diff --git a/epaint/src/text/fonts.rs b/epaint/src/text/fonts.rs index e5de4352..02d4e0ad 100644 --- a/epaint/src/text/fonts.rs +++ b/epaint/src/text/fonts.rs @@ -9,55 +9,81 @@ use crate::{ TextureAtlas, }; -// TODO: rename +/// Font of unknown size. +/// +/// Which style of font: [`Monospace`][`FontFamily::Monospace`], [`Proportional`][`FontFamily::Proportional`], +/// or by user-chosen name. +#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] +pub enum FontFamily { + /// A font where each character is the same width (`w` is the same width as `i`). + Monospace, + + /// A font where some characters are wider than other (e.g. 'w' is wider than 'i'). + Proportional, + + /// ``` + /// // User-chosen names: + /// FontFamily::Name("arial".into()); + /// FontFamily::Name("serif".into()); + /// ``` + Name(Arc), +} + +/// Alias for a font of known size. +/// /// One of a few categories of styles of text, e.g. body, button or heading. +/// Useful in GUI:s. #[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] pub enum TextStyle { /// Used when small text is needed. Small, + /// Normal labels. Easily readable, doesn't take up too much space. Body, - /// Buttons. Maybe slightly bigger than `Body`. - Button, - /// Heading. Probably larger than `Body`. - Heading, - /// Same size as `Body`, but used when monospace is important (for aligning number, code snippets, etc). + + /// 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 name of a style: + /// TextStyle::Name("footing".into()); + /// ```` + Name(Arc), } impl TextStyle { - pub fn all() -> impl ExactSizeIterator { + /// All default (un-named) `TextStyle`:s. + pub fn built_in() -> impl ExactSizeIterator { [ TextStyle::Small, TextStyle::Body, + TextStyle::Monospace, TextStyle::Button, TextStyle::Heading, - TextStyle::Monospace, ] .iter() .cloned() } } -/// Which style of font: [`Monospace`][`FontFamily::Monospace`] or [`Proportional`][`FontFamily::Proportional`]. -#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] -#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -pub enum FontFamily { - /// A font where each character is the same width (`w` is the same width as `i`). - Monospace, - /// A font where some characters are wider than other (e.g. 'w' is wider than 'i'). - Proportional, -} - /// A `.ttf` or `.otf` file and a font face index. #[derive(Clone, Debug, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct FontData { /// The content of a `.ttf` or `.otf` file. pub font: std::borrow::Cow<'static, [u8]>, + /// Which font face in the file to use. /// When in doubt, use `0`. pub index: u32, @@ -105,7 +131,7 @@ fn ab_glyph_font_from_font_data(name: &str, data: &FontData) -> ab_glyph::FontAr /// let mut fonts = FontDefinitions::default(); /// /// // Large button text: -/// fonts.family_and_size.insert( +/// fonts.styles.insert( /// TextStyle::Button, /// (FontFamily::Proportional, 32.0) /// ); @@ -150,10 +176,11 @@ pub struct FontDefinitions { /// When looking for a character glyph `epaint` will start with /// the first font and then move to the second, and so on. /// So the first font is the primary, and then comes a list of fallbacks in order of priority. + // TODO: per font size-modifier. pub fonts_for_family: BTreeMap>, /// The [`FontFamily`] and size you want to use for a specific [`TextStyle`]. - pub family_and_size: BTreeMap, + pub styles: BTreeMap, } impl Default for FontDefinitions { @@ -210,17 +237,17 @@ impl Default for FontDefinitions { fonts_for_family.insert(FontFamily::Proportional, vec![]); } - let mut family_and_size = BTreeMap::new(); - family_and_size.insert(TextStyle::Small, (FontFamily::Proportional, 10.0)); - family_and_size.insert(TextStyle::Body, (FontFamily::Proportional, 14.0)); - family_and_size.insert(TextStyle::Button, (FontFamily::Proportional, 14.0)); - family_and_size.insert(TextStyle::Heading, (FontFamily::Proportional, 20.0)); - family_and_size.insert(TextStyle::Monospace, (FontFamily::Monospace, 14.0)); + let mut styles = BTreeMap::new(); + styles.insert(TextStyle::Small, (10.0, FontFamily::Proportional)); + styles.insert(TextStyle::Body, (14.0, FontFamily::Proportional)); + styles.insert(TextStyle::Button, (14.0, FontFamily::Proportional)); + styles.insert(TextStyle::Heading, (20.0, FontFamily::Proportional)); + styles.insert(TextStyle::Monospace, (14.0, FontFamily::Monospace)); Self { font_data, fonts_for_family, - family_and_size, + styles, } } } @@ -366,8 +393,9 @@ impl FontsAndCache { pub struct FontsImpl { pixels_per_point: f32, definitions: FontDefinitions, - fonts: BTreeMap, atlas: Arc>, + font_impl_cache: FontImplCache, + styles: BTreeMap, } impl FontsImpl { @@ -393,31 +421,24 @@ impl FontsImpl { let atlas = Arc::new(Mutex::new(atlas)); - let mut font_impl_cache = FontImplCache::new(atlas.clone(), pixels_per_point, &definitions); + let font_impl_cache = + FontImplCache::new(atlas.clone(), pixels_per_point, &definitions.font_data); - let fonts = definitions - .family_and_size - .iter() - .map(|(text_style, (family, scale_in_points))| { - let fonts = &definitions.fonts_for_family.get(family); - let fonts = fonts.unwrap_or_else(|| { - panic!("FontFamily::{:?} is not bound to any fonts", family) - }); - let fonts: Vec> = fonts - .iter() - .map(|font_name| font_impl_cache.font_impl(font_name, *scale_in_points)) - .collect(); - - (text_style.clone(), Font::new(text_style.clone(), fonts)) - }) - .collect(); - - Self { + let mut slf = Self { pixels_per_point, definitions, - fonts, atlas, + font_impl_cache, + styles: Default::default(), + }; + + // pre-cache all styles: + let styles: Vec = slf.definitions.styles.keys().cloned().collect(); + for style in styles { + slf.font_for_style(&style); } + + slf } #[inline(always)] @@ -425,23 +446,47 @@ impl FontsImpl { self.pixels_per_point } + #[inline] pub fn definitions(&self) -> &FontDefinitions { &self.definitions } - #[inline] - pub fn font_mut(&mut self, text_style: &TextStyle) -> &mut Font { - self.fonts.get_mut(text_style).unwrap() + pub fn font_for_style(&mut self, text_style: &TextStyle) -> &mut Font { + self.styles.entry(text_style.clone()).or_insert_with(|| { + let (scale_in_points, family) = self + .definitions + .styles + .get(text_style) + .or_else(|| { + eprintln!( + "Missing TextStyle {:?} in font definitions; falling back to Body", + text_style + ); + self.definitions.styles.get(&TextStyle::Body) + }) + .unwrap_or_else(|| panic!("Missing {:?} in font definitions", TextStyle::Body)); + + let fonts = &self.definitions.fonts_for_family.get(family); + let fonts = fonts + .unwrap_or_else(|| panic!("FontFamily::{:?} is not bound to any fonts", family)); + + let fonts: Vec> = fonts + .iter() + .map(|font_name| self.font_impl_cache.font_impl(*scale_in_points, font_name)) + .collect(); + + Font::new(fonts) + }) } /// Width of this character in points. fn glyph_width(&mut self, text_style: &TextStyle, c: char) -> f32 { - self.font_mut(text_style).glyph_width(c) + self.font_for_style(text_style).glyph_width(c) } /// Height of one row of text. In points fn row_height(&mut self, text_style: &TextStyle) -> f32 { - self.font_mut(text_style).row_height() + self.font_for_style(text_style).row_height() } } @@ -511,10 +556,9 @@ impl FontImplCache { pub fn new( atlas: Arc>, pixels_per_point: f32, - definitions: &super::FontDefinitions, + font_data: &BTreeMap, ) -> Self { - let ab_glyph_fonts = definitions - .font_data + let ab_glyph_fonts = font_data .iter() .map(|(name, font_data)| (name.clone(), ab_glyph_font_from_font_data(name, font_data))) .collect(); @@ -527,7 +571,7 @@ impl FontImplCache { } } - pub fn font_impl(&mut self, font_name: &str, scale_in_points: f32) -> Arc { + pub fn font_impl(&mut self, scale_in_points: f32, font_name: &str) -> Arc { let y_offset = if font_name == "emoji-icon-font" { scale_in_points * 0.235 // TODO: remove font alignment hack } else { diff --git a/epaint/src/text/text_layout.rs b/epaint/src/text/text_layout.rs index 5c840384..d35f5dd4 100644 --- a/epaint/src/text/text_layout.rs +++ b/epaint/src/text/text_layout.rs @@ -86,7 +86,7 @@ fn layout_section( byte_range, format, } = section; - let font = fonts.font_mut(&format.style); + let font = fonts.font_for_style(&format.style); let font_height = font.row_height(); let mut paragraph = out_paragraphs.last_mut().unwrap();