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,