From f4697bc007447c6c2674beb4e25f599fb7afa093 Mon Sep 17 00:00:00 2001 From: lampsitter <96946613+lampsitter@users.noreply.github.com> Date: Tue, 10 Sep 2024 11:38:26 +0200 Subject: [PATCH] Use Style's font size in egui_extras::syntax_highlighting (#5090) * Closes https://github.com/emilk/egui/issues/3549 * [X] I have followed the instructions in the PR template The syntax highlighting font size was always hardcoded to 12 or 10 depending on what case it was hitting (so not consistent). This is particularly noticeable when you increase the font size to something larger for the rest of the ui. With this the default monospace font size is used by default. Since the issue is closely related to #3549 I decided to implement the ability to use override_font_id too. ## Visualized Default monospace is set to 15 in all the pictures Before/After without syntect: ![normal](https://github.com/user-attachments/assets/0d058720-47ff-49e7-af77-30d48f5e138c) Before/after _with_ syntect: ![syntect](https://github.com/user-attachments/assets/e5c380fe-ced1-40ee-b4b1-c26cec18a840) Font override after without/with syntect (monospace = 20): ![override](https://github.com/user-attachments/assets/efd1b759-3f97-4673-864a-5a18afc64099) ### Breaking changes - `CodeTheme::dark` and `CodeTheme::light` takes in the font size - `CodeTheme::from_memory` takes in `Style` - `highlight` function takes in `Style` --- crates/egui_demo_app/src/apps/http_app.rs | 6 +- crates/egui_demo_lib/src/demo/code_editor.rs | 12 +- crates/egui_demo_lib/src/demo/code_example.rs | 3 +- crates/egui_demo_lib/src/lib.rs | 2 +- crates/egui_extras/src/syntax_highlighting.rs | 129 ++++++++++++++---- 5 files changed, 118 insertions(+), 34 deletions(-) diff --git a/crates/egui_demo_app/src/apps/http_app.rs b/crates/egui_demo_app/src/apps/http_app.rs index d6b572842..abc728fd5 100644 --- a/crates/egui_demo_app/src/apps/http_app.rs +++ b/crates/egui_demo_app/src/apps/http_app.rs @@ -224,7 +224,11 @@ fn syntax_highlighting( let extension = extension_and_rest.first()?; let theme = egui_extras::syntax_highlighting::CodeTheme::from_style(&ctx.style()); Some(ColoredText(egui_extras::syntax_highlighting::highlight( - ctx, &theme, text, extension, + ctx, + &ctx.style(), + &theme, + text, + extension, ))) } diff --git a/crates/egui_demo_lib/src/demo/code_editor.rs b/crates/egui_demo_lib/src/demo/code_editor.rs index 4dad60d3c..fe39e1be8 100644 --- a/crates/egui_demo_lib/src/demo/code_editor.rs +++ b/crates/egui_demo_lib/src/demo/code_editor.rs @@ -67,7 +67,8 @@ impl crate::View for CodeEditor { }); } - let mut theme = egui_extras::syntax_highlighting::CodeTheme::from_memory(ui.ctx()); + let mut theme = + egui_extras::syntax_highlighting::CodeTheme::from_memory(ui.ctx(), ui.style()); ui.collapsing("Theme", |ui| { ui.group(|ui| { theme.ui(ui); @@ -76,8 +77,13 @@ impl crate::View for CodeEditor { }); let mut layouter = |ui: &egui::Ui, string: &str, wrap_width: f32| { - let mut layout_job = - egui_extras::syntax_highlighting::highlight(ui.ctx(), &theme, string, language); + let mut layout_job = egui_extras::syntax_highlighting::highlight( + ui.ctx(), + ui.style(), + &theme, + string, + language, + ); layout_job.wrap.max_width = wrap_width; ui.fonts(|f| f.layout_job(layout_job)) }; diff --git a/crates/egui_demo_lib/src/demo/code_example.rs b/crates/egui_demo_lib/src/demo/code_example.rs index 18a251077..3db90ad3e 100644 --- a/crates/egui_demo_lib/src/demo/code_example.rs +++ b/crates/egui_demo_lib/src/demo/code_example.rs @@ -130,7 +130,8 @@ impl crate::View for CodeExample { ui.separator(); - let mut theme = egui_extras::syntax_highlighting::CodeTheme::from_memory(ui.ctx()); + let mut theme = + egui_extras::syntax_highlighting::CodeTheme::from_memory(ui.ctx(), ui.style()); ui.collapsing("Theme", |ui| { theme.ui(ui); theme.store_in_memory(ui.ctx()); diff --git a/crates/egui_demo_lib/src/lib.rs b/crates/egui_demo_lib/src/lib.rs index ce18e0910..23c28bcb6 100644 --- a/crates/egui_demo_lib/src/lib.rs +++ b/crates/egui_demo_lib/src/lib.rs @@ -21,7 +21,7 @@ pub use rendering_test::ColorTest; /// View some Rust code with syntax highlighting and selection. pub(crate) fn rust_view_ui(ui: &mut egui::Ui, code: &str) { let language = "rs"; - let theme = egui_extras::syntax_highlighting::CodeTheme::from_memory(ui.ctx()); + let theme = egui_extras::syntax_highlighting::CodeTheme::from_memory(ui.ctx(), ui.style()); egui_extras::syntax_highlighting::code_view_ui(ui, &theme, code, language); } diff --git a/crates/egui_extras/src/syntax_highlighting.rs b/crates/egui_extras/src/syntax_highlighting.rs index 8ddd46065..e782677c9 100644 --- a/crates/egui_extras/src/syntax_highlighting.rs +++ b/crates/egui_extras/src/syntax_highlighting.rs @@ -6,6 +6,7 @@ #![allow(clippy::mem_forget)] // False positive from enum_map macro use egui::text::LayoutJob; +use egui::TextStyle; /// View some code with syntax highlighting and selection. pub fn code_view_ui( @@ -14,29 +15,53 @@ pub fn code_view_ui( code: &str, language: &str, ) -> egui::Response { - let layout_job = highlight(ui.ctx(), theme, code, language); + let layout_job = highlight(ui.ctx(), ui.style(), theme, code, language); ui.add(egui::Label::new(layout_job).selectable(true)) } /// Add syntax highlighting to a code string. /// /// The results are memoized, so you can call this every frame without performance penalty. -pub fn highlight(ctx: &egui::Context, theme: &CodeTheme, code: &str, language: &str) -> LayoutJob { - impl egui::util::cache::ComputerMut<(&CodeTheme, &str, &str), LayoutJob> for Highlighter { - fn compute(&mut self, (theme, code, lang): (&CodeTheme, &str, &str)) -> LayoutJob { - self.highlight(theme, code, lang) +pub fn highlight( + ctx: &egui::Context, + style: &egui::Style, + theme: &CodeTheme, + code: &str, + language: &str, +) -> LayoutJob { + // We take in both context and style so that in situations where ui is not available such as when + // performing it at a separate thread (ctx, ctx.style()) can be used and when ui is available + // (ui.ctx(), ui.style()) can be used + + impl egui::util::cache::ComputerMut<(&egui::FontId, &CodeTheme, &str, &str), LayoutJob> + for Highlighter + { + fn compute( + &mut self, + (font_id, theme, code, lang): (&egui::FontId, &CodeTheme, &str, &str), + ) -> LayoutJob { + self.highlight(font_id.clone(), theme, code, lang) } } type HighlightCache = egui::util::cache::FrameCache; + let font_id = style + .override_font_id + .clone() + .unwrap_or_else(|| TextStyle::Monospace.resolve(style)); + ctx.memory_mut(|mem| { mem.caches .cache::() - .get((theme, code, language)) + .get((&font_id, theme, code, language)) }) } +fn monospace_font_size(style: &egui::Style) -> f32 { + TextStyle::Monospace.resolve(style).size +} + // ---------------------------------------------------------------------------- #[cfg(not(feature = "syntect"))] @@ -128,6 +153,8 @@ pub struct CodeTheme { #[cfg(feature = "syntect")] syntect_theme: SyntectTheme, + #[cfg(feature = "syntect")] + font_id: egui::FontId, #[cfg(not(feature = "syntect"))] formats: enum_map::EnumMap, @@ -135,40 +162,75 @@ pub struct CodeTheme { impl Default for CodeTheme { fn default() -> Self { - Self::dark() + Self::dark(12.0) } } impl CodeTheme { /// Selects either dark or light theme based on the given style. pub fn from_style(style: &egui::Style) -> Self { + let font_id = style + .override_font_id + .clone() + .unwrap_or_else(|| TextStyle::Monospace.resolve(style)); + if style.visuals.dark_mode { - Self::dark() + Self::dark_with_font_id(font_id) } else { - Self::light() + Self::light_with_font_id(font_id) } } + /// ### Example + /// + /// ``` + /// # egui::__run_test_ui(|ui| { + /// use egui_extras::syntax_highlighting::CodeTheme; + /// let theme = CodeTheme::dark(12.0); + /// # }); + /// ``` + pub fn dark(font_size: f32) -> Self { + Self::dark_with_font_id(egui::FontId::monospace(font_size)) + } + + /// ### Example + /// + /// ``` + /// # egui::__run_test_ui(|ui| { + /// use egui_extras::syntax_highlighting::CodeTheme; + /// let theme = CodeTheme::light(12.0); + /// # }); + /// ``` + pub fn light(font_size: f32) -> Self { + Self::light_with_font_id(egui::FontId::monospace(font_size)) + } + /// Load code theme from egui memory. /// /// There is one dark and one light theme stored at any one time. - pub fn from_memory(ctx: &egui::Context) -> Self { + pub fn from_memory(ctx: &egui::Context, style: &egui::Style) -> Self { #![allow(clippy::needless_return)] - let (id, default) = if ctx.style().visuals.dark_mode { - (egui::Id::new("dark"), Self::dark as fn() -> Self) + let (id, default) = if style.visuals.dark_mode { + (egui::Id::new("dark"), Self::dark as fn(f32) -> Self) } else { - (egui::Id::new("light"), Self::light as fn() -> Self) + (egui::Id::new("light"), Self::light as fn(f32) -> Self) }; #[cfg(feature = "serde")] { - return ctx.data_mut(|d| d.get_persisted(id).unwrap_or_else(default)); + return ctx.data_mut(|d| { + d.get_persisted(id) + .unwrap_or_else(|| default(monospace_font_size(style))) + }); } #[cfg(not(feature = "serde"))] { - return ctx.data_mut(|d| d.get_temp(id).unwrap_or_else(default)); + return ctx.data_mut(|d| { + d.get_temp(id) + .unwrap_or_else(|| default(monospace_font_size(style))) + }); } } @@ -192,17 +254,19 @@ impl CodeTheme { #[cfg(feature = "syntect")] impl CodeTheme { - pub fn dark() -> Self { + fn dark_with_font_id(font_id: egui::FontId) -> Self { Self { dark_mode: true, syntect_theme: SyntectTheme::Base16MochaDark, + font_id, } } - pub fn light() -> Self { + fn light_with_font_id(font_id: egui::FontId) -> Self { Self { dark_mode: false, syntect_theme: SyntectTheme::SolarizedLight, + font_id, } } @@ -220,8 +284,10 @@ impl CodeTheme { #[cfg(not(feature = "syntect"))] impl CodeTheme { - pub fn dark() -> Self { - let font_id = egui::FontId::monospace(10.0); + // The syntect version takes it by value. This could be avoided by specializing the from_style + // function, but at the cost of more code duplication. + #[allow(clippy::needless_pass_by_value)] + fn dark_with_font_id(font_id: egui::FontId) -> Self { use egui::{Color32, TextFormat}; Self { dark_mode: true, @@ -236,8 +302,9 @@ impl CodeTheme { } } - pub fn light() -> Self { - let font_id = egui::FontId::monospace(10.0); + // The syntect version takes it by value + #[allow(clippy::needless_pass_by_value)] + fn light_with_font_id(font_id: egui::FontId) -> Self { use egui::{Color32, TextFormat}; Self { dark_mode: false, @@ -291,9 +358,9 @@ impl CodeTheme { }); let reset_value = if self.dark_mode { - Self::dark() + Self::dark(monospace_font_size(ui.style())) } else { - Self::light() + Self::light(monospace_font_size(ui.style())) }; if ui @@ -348,12 +415,18 @@ impl Default for Highlighter { impl Highlighter { #[allow(clippy::unused_self, clippy::unnecessary_wraps)] - fn highlight(&self, theme: &CodeTheme, code: &str, lang: &str) -> LayoutJob { + fn highlight( + &self, + font_id: egui::FontId, + theme: &CodeTheme, + code: &str, + lang: &str, + ) -> LayoutJob { self.highlight_impl(theme, code, lang).unwrap_or_else(|| { // Fallback: LayoutJob::simple( code.into(), - egui::FontId::monospace(12.0), + font_id, if theme.dark_mode { egui::Color32::LIGHT_GRAY } else { @@ -377,8 +450,8 @@ impl Highlighter { .find_syntax_by_name(language) .or_else(|| self.ps.find_syntax_by_extension(language))?; - let theme = theme.syntect_theme.syntect_key_name(); - let mut h = HighlightLines::new(syntax, &self.ts.themes[theme]); + let syn_theme = theme.syntect_theme.syntect_key_name(); + let mut h = HighlightLines::new(syntax, &self.ts.themes[syn_theme]); use egui::text::{LayoutSection, TextFormat}; @@ -402,7 +475,7 @@ impl Highlighter { leading_space: 0.0, byte_range: as_byte_range(text, range), format: TextFormat { - font_id: egui::FontId::monospace(12.0), + font_id: theme.font_id.clone(), color: text_color, italics, underline,