Browse Source

Use Style's font size in egui_extras::syntax_highlighting (#5090)

<!--
Please read the "Making a PR" section of
[`CONTRIBUTING.md`](https://github.com/emilk/egui/blob/master/CONTRIBUTING.md)
before opening a Pull Request!

* Keep your PR:s small and focused.
* The PR title is what ends up in the changelog, so make it descriptive!
* If applicable, add a screenshot or gif.
* If it is a non-trivial addition, consider adding a demo for it to
`egui_demo_lib`, or a new example.
* Do NOT open PR:s from your `master` branch, as that makes it hard for
maintainers to test and add commits to your PR.
* Remember to run `cargo fmt` and `cargo clippy`.
* Open the PR as a draft until you have self-reviewed it and run
`./scripts/check.sh`.
* When you have addressed a PR comment, mark it as resolved.

Please be patient! I will review your PR, but my time is limited!
-->
* 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`
pull/5038/head
lampsitter 2 months ago
committed by GitHub
parent
commit
f4697bc007
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 6
      crates/egui_demo_app/src/apps/http_app.rs
  2. 12
      crates/egui_demo_lib/src/demo/code_editor.rs
  3. 3
      crates/egui_demo_lib/src/demo/code_example.rs
  4. 2
      crates/egui_demo_lib/src/lib.rs
  5. 129
      crates/egui_extras/src/syntax_highlighting.rs

6
crates/egui_demo_app/src/apps/http_app.rs

@ -224,7 +224,11 @@ fn syntax_highlighting(
let extension = extension_and_rest.first()?; let extension = extension_and_rest.first()?;
let theme = egui_extras::syntax_highlighting::CodeTheme::from_style(&ctx.style()); let theme = egui_extras::syntax_highlighting::CodeTheme::from_style(&ctx.style());
Some(ColoredText(egui_extras::syntax_highlighting::highlight( Some(ColoredText(egui_extras::syntax_highlighting::highlight(
ctx, &theme, text, extension, ctx,
&ctx.style(),
&theme,
text,
extension,
))) )))
} }

12
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.collapsing("Theme", |ui| {
ui.group(|ui| { ui.group(|ui| {
theme.ui(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 layouter = |ui: &egui::Ui, string: &str, wrap_width: f32| {
let mut layout_job = let mut layout_job = egui_extras::syntax_highlighting::highlight(
egui_extras::syntax_highlighting::highlight(ui.ctx(), &theme, string, language); ui.ctx(),
ui.style(),
&theme,
string,
language,
);
layout_job.wrap.max_width = wrap_width; layout_job.wrap.max_width = wrap_width;
ui.fonts(|f| f.layout_job(layout_job)) ui.fonts(|f| f.layout_job(layout_job))
}; };

3
crates/egui_demo_lib/src/demo/code_example.rs

@ -130,7 +130,8 @@ impl crate::View for CodeExample {
ui.separator(); 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| { ui.collapsing("Theme", |ui| {
theme.ui(ui); theme.ui(ui);
theme.store_in_memory(ui.ctx()); theme.store_in_memory(ui.ctx());

2
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. /// View some Rust code with syntax highlighting and selection.
pub(crate) fn rust_view_ui(ui: &mut egui::Ui, code: &str) { pub(crate) fn rust_view_ui(ui: &mut egui::Ui, code: &str) {
let language = "rs"; 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); egui_extras::syntax_highlighting::code_view_ui(ui, &theme, code, language);
} }

129
crates/egui_extras/src/syntax_highlighting.rs

@ -6,6 +6,7 @@
#![allow(clippy::mem_forget)] // False positive from enum_map macro #![allow(clippy::mem_forget)] // False positive from enum_map macro
use egui::text::LayoutJob; use egui::text::LayoutJob;
use egui::TextStyle;
/// View some code with syntax highlighting and selection. /// View some code with syntax highlighting and selection.
pub fn code_view_ui( pub fn code_view_ui(
@ -14,29 +15,53 @@ pub fn code_view_ui(
code: &str, code: &str,
language: &str, language: &str,
) -> egui::Response { ) -> 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)) ui.add(egui::Label::new(layout_job).selectable(true))
} }
/// Add syntax highlighting to a code string. /// Add syntax highlighting to a code string.
/// ///
/// The results are memoized, so you can call this every frame without performance penalty. /// 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 { pub fn highlight(
impl egui::util::cache::ComputerMut<(&CodeTheme, &str, &str), LayoutJob> for Highlighter { ctx: &egui::Context,
fn compute(&mut self, (theme, code, lang): (&CodeTheme, &str, &str)) -> LayoutJob { style: &egui::Style,
self.highlight(theme, code, lang) 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<LayoutJob, Highlighter>; type HighlightCache = egui::util::cache::FrameCache<LayoutJob, Highlighter>;
let font_id = style
.override_font_id
.clone()
.unwrap_or_else(|| TextStyle::Monospace.resolve(style));
ctx.memory_mut(|mem| { ctx.memory_mut(|mem| {
mem.caches mem.caches
.cache::<HighlightCache>() .cache::<HighlightCache>()
.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"))] #[cfg(not(feature = "syntect"))]
@ -128,6 +153,8 @@ pub struct CodeTheme {
#[cfg(feature = "syntect")] #[cfg(feature = "syntect")]
syntect_theme: SyntectTheme, syntect_theme: SyntectTheme,
#[cfg(feature = "syntect")]
font_id: egui::FontId,
#[cfg(not(feature = "syntect"))] #[cfg(not(feature = "syntect"))]
formats: enum_map::EnumMap<TokenType, egui::TextFormat>, formats: enum_map::EnumMap<TokenType, egui::TextFormat>,
@ -135,40 +162,75 @@ pub struct CodeTheme {
impl Default for CodeTheme { impl Default for CodeTheme {
fn default() -> Self { fn default() -> Self {
Self::dark() Self::dark(12.0)
} }
} }
impl CodeTheme { impl CodeTheme {
/// Selects either dark or light theme based on the given style. /// Selects either dark or light theme based on the given style.
pub fn from_style(style: &egui::Style) -> Self { 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 { if style.visuals.dark_mode {
Self::dark() Self::dark_with_font_id(font_id)
} else { } 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. /// Load code theme from egui memory.
/// ///
/// There is one dark and one light theme stored at any one time. /// 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)] #![allow(clippy::needless_return)]
let (id, default) = if ctx.style().visuals.dark_mode { let (id, default) = if style.visuals.dark_mode {
(egui::Id::new("dark"), Self::dark as fn() -> Self) (egui::Id::new("dark"), Self::dark as fn(f32) -> Self)
} else { } else {
(egui::Id::new("light"), Self::light as fn() -> Self) (egui::Id::new("light"), Self::light as fn(f32) -> Self)
}; };
#[cfg(feature = "serde")] #[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"))] #[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")] #[cfg(feature = "syntect")]
impl CodeTheme { impl CodeTheme {
pub fn dark() -> Self { fn dark_with_font_id(font_id: egui::FontId) -> Self {
Self { Self {
dark_mode: true, dark_mode: true,
syntect_theme: SyntectTheme::Base16MochaDark, syntect_theme: SyntectTheme::Base16MochaDark,
font_id,
} }
} }
pub fn light() -> Self { fn light_with_font_id(font_id: egui::FontId) -> Self {
Self { Self {
dark_mode: false, dark_mode: false,
syntect_theme: SyntectTheme::SolarizedLight, syntect_theme: SyntectTheme::SolarizedLight,
font_id,
} }
} }
@ -220,8 +284,10 @@ impl CodeTheme {
#[cfg(not(feature = "syntect"))] #[cfg(not(feature = "syntect"))]
impl CodeTheme { impl CodeTheme {
pub fn dark() -> Self { // The syntect version takes it by value. This could be avoided by specializing the from_style
let font_id = egui::FontId::monospace(10.0); // 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}; use egui::{Color32, TextFormat};
Self { Self {
dark_mode: true, dark_mode: true,
@ -236,8 +302,9 @@ impl CodeTheme {
} }
} }
pub fn light() -> Self { // The syntect version takes it by value
let font_id = egui::FontId::monospace(10.0); #[allow(clippy::needless_pass_by_value)]
fn light_with_font_id(font_id: egui::FontId) -> Self {
use egui::{Color32, TextFormat}; use egui::{Color32, TextFormat};
Self { Self {
dark_mode: false, dark_mode: false,
@ -291,9 +358,9 @@ impl CodeTheme {
}); });
let reset_value = if self.dark_mode { let reset_value = if self.dark_mode {
Self::dark() Self::dark(monospace_font_size(ui.style()))
} else { } else {
Self::light() Self::light(monospace_font_size(ui.style()))
}; };
if ui if ui
@ -348,12 +415,18 @@ impl Default for Highlighter {
impl Highlighter { impl Highlighter {
#[allow(clippy::unused_self, clippy::unnecessary_wraps)] #[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(|| { self.highlight_impl(theme, code, lang).unwrap_or_else(|| {
// Fallback: // Fallback:
LayoutJob::simple( LayoutJob::simple(
code.into(), code.into(),
egui::FontId::monospace(12.0), font_id,
if theme.dark_mode { if theme.dark_mode {
egui::Color32::LIGHT_GRAY egui::Color32::LIGHT_GRAY
} else { } else {
@ -377,8 +450,8 @@ impl Highlighter {
.find_syntax_by_name(language) .find_syntax_by_name(language)
.or_else(|| self.ps.find_syntax_by_extension(language))?; .or_else(|| self.ps.find_syntax_by_extension(language))?;
let theme = theme.syntect_theme.syntect_key_name(); let syn_theme = theme.syntect_theme.syntect_key_name();
let mut h = HighlightLines::new(syntax, &self.ts.themes[theme]); let mut h = HighlightLines::new(syntax, &self.ts.themes[syn_theme]);
use egui::text::{LayoutSection, TextFormat}; use egui::text::{LayoutSection, TextFormat};
@ -402,7 +475,7 @@ impl Highlighter {
leading_space: 0.0, leading_space: 0.0,
byte_range: as_byte_range(text, range), byte_range: as_byte_range(text, range),
format: TextFormat { format: TextFormat {
font_id: egui::FontId::monospace(12.0), font_id: theme.font_id.clone(),
color: text_color, color: text_color,
italics, italics,
underline, underline,

Loading…
Cancel
Save