1
0
mirror of https://github.com/emilk/egui.git synced 2026-09-02 06:40:06 -04:00

Merge branch 'main' into lucas/experiments/measure-widget-size

# Conflicts:
#	crates/egui/src/atomics/atom.rs
#	crates/egui/src/atomics/atom_kind.rs
#	crates/egui/src/atomics/atom_layout.rs
#	crates/egui/src/containers/sides.rs
#	crates/egui/src/placer.rs
#	crates/egui/src/widgets/label.rs
#	crates/egui/src/widgets/selected_label.rs
#	crates/egui_demo_lib/src/demo/dancing_strings.rs
#	crates/egui_extras/src/layout.rs
#	crates/epaint/src/text/text_layout_types.rs
This commit is contained in:
lucasmerlin
2025-07-14 15:35:38 +02:00
336 changed files with 3896 additions and 1837 deletions

View File

@@ -5,6 +5,29 @@ This file is updated upon each release.
Changes since the last release can be found at <https://github.com/emilk/egui/compare/latest...HEAD> or by running the `scripts/generate_changelog.py` script.
## 0.32.0 - 2025-07-10 - Improved SVG support
### ⭐ Added
* Allow loading multi-MIME formats using the image_loader [#5769](https://github.com/emilk/egui/pull/5769) by [@MYDIH](https://github.com/MYDIH)
* Make ImageLoader use background thread [#5394](https://github.com/emilk/egui/pull/5394) by [@bircni](https://github.com/bircni)
* Add overline option for Table rows [#5637](https://github.com/emilk/egui/pull/5637) by [@akx](https://github.com/akx)
* Support text in SVGs [#5979](https://github.com/emilk/egui/pull/5979) by [@cernec1999](https://github.com/cernec1999)
* Enable setting DatePickerButton start and end year explicitly [#7061](https://github.com/emilk/egui/pull/7061) by [@zachbateman](https://github.com/zachbateman)
* Support custom syntect settings in syntax highlighter [#7084](https://github.com/emilk/egui/pull/7084) by [@mkeeter](https://github.com/mkeeter)
### 🔧 Changed
* Use enum-map serde feature only when serde is enabled [#5748](https://github.com/emilk/egui/pull/5748) by [@tyssyt](https://github.com/tyssyt)
* Better define the meaning of `SizeHint` [#7079](https://github.com/emilk/egui/pull/7079) by [@emilk](https://github.com/emilk)
### 🔥 Removed
* Remove things that have been deprecated for over a year [#7099](https://github.com/emilk/egui/pull/7099) by [@emilk](https://github.com/emilk)
### 🐛 Fixed
* Refactor MIME type support detection in image loader to allow for deferred handling and appended encoding info [#5686](https://github.com/emilk/egui/pull/5686) by [@markusdd](https://github.com/markusdd)
* Fix incorrect color fringe colors on SVG:s [#7069](https://github.com/emilk/egui/pull/7069) by [@emilk](https://github.com/emilk)
* Fix sometimes blurry SVGs [#7071](https://github.com/emilk/egui/pull/7071) by [@emilk](https://github.com/emilk)
* Fix crash in `egui_extras::FileLoader` after `forget_image` [#6995](https://github.com/emilk/egui/pull/6995) by [@bircni](https://github.com/bircni)
## 0.31.1 - 2025-03-05
* Fix image_loader for animated image types [#5688](https://github.com/emilk/egui/pull/5688) by [@BSteffaniak](https://github.com/BSteffaniak)

View File

@@ -1,6 +1,7 @@
use super::popup::DatePickerPopup;
use chrono::NaiveDate;
use egui::{Area, Button, Frame, InnerResponse, Key, Order, RichText, Ui, Widget};
use std::ops::RangeInclusive;
#[derive(Default, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
@@ -19,6 +20,7 @@ pub struct DatePickerButton<'a> {
show_icon: bool,
format: String,
highlight_weekends: bool,
start_end_years: Option<RangeInclusive<i32>>,
}
impl<'a> DatePickerButton<'a> {
@@ -33,6 +35,7 @@ impl<'a> DatePickerButton<'a> {
show_icon: true,
format: "%Y-%m-%d".to_owned(),
highlight_weekends: true,
start_end_years: None,
}
}
@@ -101,6 +104,17 @@ impl<'a> DatePickerButton<'a> {
self.highlight_weekends = highlight_weekends;
self
}
/// Set the start and end years for the date picker. (Default: today's year - 100 to today's year + 10)
/// This will limit the years you can choose from in the dropdown to the specified range.
///
/// For example, if you want to provide the range of years from 2000 to 2035, you can use:
/// `start_end_years(2000..=2035)`.
#[inline]
pub fn start_end_years(mut self, start_end_years: RangeInclusive<i32>) -> Self {
self.start_end_years = Some(start_end_years);
self
}
}
impl Widget for DatePickerButton<'_> {
@@ -167,6 +181,7 @@ impl Widget for DatePickerButton<'_> {
calendar: self.calendar,
calendar_week: self.calendar_week,
highlight_weekends: self.highlight_weekends,
start_end_years: self.start_end_years,
}
.draw(ui)
})

View File

@@ -35,6 +35,7 @@ pub(crate) struct DatePickerPopup<'a> {
pub calendar: bool,
pub calendar_week: bool,
pub highlight_weekends: bool,
pub start_end_years: Option<std::ops::RangeInclusive<i32>>,
}
impl DatePickerPopup<'_> {
@@ -84,7 +85,11 @@ impl DatePickerPopup<'_> {
ComboBox::from_id_salt("date_picker_year")
.selected_text(popup_state.year.to_string())
.show_ui(ui, |ui| {
for year in today.year() - 100..today.year() + 10 {
let (start_year, end_year) = match &self.start_end_years {
Some(range) => (*range.start(), *range.end()),
None => (today.year() - 100, today.year() + 10),
};
for year in start_year..=end_year {
if ui
.selectable_value(
&mut popup_state.year,

View File

@@ -162,7 +162,7 @@ impl<'l> StripLayout<'l> {
} else if flags.clip {
max_rect
} else {
max_rect.union(used_rect)
max_rect | used_rect
};
self.set_pos(allocation_rect);

View File

@@ -19,13 +19,13 @@ impl File {
return Err(format!(
"failed to load {uri:?}: {} {} {response_text}",
response.status, response.status_text
))
));
}
None => {
return Err(format!(
"failed to load {uri:?}: {} {}",
response.status, response.status_text
))
));
}
}
}

View File

@@ -95,10 +95,15 @@ impl BytesLoader for FileLoader {
}
Err(err) => Err(err.to_string()),
};
let prev = cache.lock().insert(uri.clone(), Poll::Ready(result));
assert!(matches!(prev, Some(Poll::Pending)), "unexpected state");
ctx.request_repaint();
log::trace!("finished loading {uri:?}");
let mut cache = cache.lock();
if let std::collections::hash_map::Entry::Occupied(mut entry) = cache.entry(uri.clone()) {
let entry = entry.get_mut();
*entry = Poll::Ready(result);
ctx.request_repaint();
log::trace!("Finished loading {uri:?}");
} else {
log::trace!("Canceled loading {uri:?}\nNote: This can happen if `forget_image` is called while the image is still loading.");
}
}
})
.expect("failed to spawn thread");

View File

@@ -1,9 +1,8 @@
use ahash::HashMap;
use egui::{
decode_animated_image_uri, has_gif_magic_header,
ColorImage, FrameDurations, Id, decode_animated_image_uri, has_gif_magic_header,
load::{BytesPoll, ImageLoadResult, ImageLoader, ImagePoll, LoadError, SizeHint},
mutex::Mutex,
ColorImage, FrameDurations, Id,
};
use image::AnimationDecoder as _;
use std::{io::Cursor, mem::size_of, sync::Arc, time::Duration};

View File

@@ -1,9 +1,8 @@
use ahash::HashMap;
use egui::{
decode_animated_image_uri,
ColorImage, decode_animated_image_uri,
load::{Bytes, BytesPoll, ImageLoadResult, ImageLoader, ImagePoll, LoadError, SizeHint},
mutex::Mutex,
ColorImage,
};
use image::ImageFormat;
use std::{mem::size_of, path::Path, sync::Arc, task::Poll};

View File

@@ -1,17 +1,17 @@
use std::{
mem::size_of,
sync::{
atomic::{AtomicU64, Ordering::Relaxed},
Arc,
atomic::{AtomicU64, Ordering::Relaxed},
},
};
use ahash::HashMap;
use egui::{
ColorImage,
load::{BytesPoll, ImageLoadResult, ImageLoader, ImagePoll, LoadError, SizeHint},
mutex::Mutex,
ColorImage,
};
struct Entry {

View File

@@ -1,11 +1,10 @@
use ahash::HashMap;
use egui::{
decode_animated_image_uri, has_webp_header,
ColorImage, FrameDurations, Id, decode_animated_image_uri, has_webp_header,
load::{BytesPoll, ImageLoadResult, ImageLoader, ImagePoll, LoadError, SizeHint},
mutex::Mutex,
ColorImage, FrameDurations, Id,
};
use image::{codecs::webp::WebPDecoder, AnimationDecoder as _, ColorType, ImageDecoder as _, Rgba};
use image::{AnimationDecoder as _, ColorType, ImageDecoder as _, Rgba, codecs::webp::WebPDecoder};
use std::{io::Cursor, mem::size_of, sync::Arc, time::Duration};
#[derive(Clone)]
@@ -55,7 +54,7 @@ impl WebP {
unreachable => {
return Err(format!(
"Unreachable WebP color type, expected Rgb8/Rgba8, got {unreachable:?}"
))
));
}
};

View File

@@ -1,7 +1,7 @@
use crate::{
Size,
layout::{CellDirection, CellSize, StripLayout, StripLayoutFlags},
sizing::Sizing,
Size,
};
use egui::{Response, Ui};

View File

@@ -5,8 +5,8 @@
#![allow(clippy::mem_forget)] // False positive from enum_map macro
use egui::text::LayoutJob;
use egui::TextStyle;
use egui::text::LayoutJob;
/// View some code with syntax highlighting and selection.
pub fn code_view_ui(
@@ -28,18 +28,65 @@ pub fn highlight(
theme: &CodeTheme,
code: &str,
language: &str,
) -> LayoutJob {
highlight_inner(ctx, style, theme, code, language, None)
}
/// Add syntax highlighting to a code string, with custom `syntect` settings
///
/// The results are memoized, so you can call this every frame without performance penalty.
///
/// The `syntect` settings are memoized by *address*, so a stable reference should
/// be used to avoid unnecessary recomputation.
#[cfg(feature = "syntect")]
pub fn highlight_with(
ctx: &egui::Context,
style: &egui::Style,
theme: &CodeTheme,
code: &str,
language: &str,
settings: &SyntectSettings,
) -> LayoutJob {
highlight_inner(
ctx,
style,
theme,
code,
language,
Some(HighlightSettings(settings)),
)
}
fn highlight_inner(
ctx: &egui::Context,
style: &egui::Style,
theme: &CodeTheme,
code: &str,
language: &str,
settings: Option<HighlightSettings<'_>>,
) -> 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
#[expect(non_local_definitions)]
impl egui::cache::ComputerMut<(&egui::FontId, &CodeTheme, &str, &str), LayoutJob> for Highlighter {
impl
egui::cache::ComputerMut<
(&egui::FontId, &CodeTheme, &str, &str, HighlightSettings<'_>),
LayoutJob,
> for Highlighter
{
fn compute(
&mut self,
(font_id, theme, code, lang): (&egui::FontId, &CodeTheme, &str, &str),
(font_id, theme, code, lang, settings): (
&egui::FontId,
&CodeTheme,
&str,
&str,
HighlightSettings<'_>,
),
) -> LayoutJob {
self.highlight(font_id.clone(), theme, code, lang)
Self::highlight(font_id.clone(), theme, code, lang, settings)
}
}
@@ -50,10 +97,27 @@ pub fn highlight(
.clone()
.unwrap_or_else(|| TextStyle::Monospace.resolve(style));
// Private type, so that users can't interfere with it in the `IdTypeMap`
#[cfg(feature = "syntect")]
#[derive(Clone, Default)]
struct PrivateSettings(std::sync::Arc<SyntectSettings>);
// Dummy private settings, to minimize code changes without `syntect`
#[cfg(not(feature = "syntect"))]
#[derive(Clone, Default)]
struct PrivateSettings(std::sync::Arc<()>);
ctx.memory_mut(|mem| {
let settings = settings.unwrap_or_else(|| {
HighlightSettings(
&mem.data
.get_temp_mut_or_default::<PrivateSettings>(egui::Id::NULL)
.0,
)
});
mem.caches
.cache::<HighlightCache>()
.get((&font_id, theme, code, language))
.get((&font_id, theme, code, language, settings))
})
}
@@ -396,13 +460,13 @@ impl CodeTheme {
// ----------------------------------------------------------------------------
#[cfg(feature = "syntect")]
struct Highlighter {
ps: syntect::parsing::SyntaxSet,
ts: syntect::highlighting::ThemeSet,
pub struct SyntectSettings {
pub ps: syntect::parsing::SyntaxSet,
pub ts: syntect::highlighting::ThemeSet,
}
#[cfg(feature = "syntect")]
impl Default for Highlighter {
impl Default for SyntectSettings {
fn default() -> Self {
profiling::function_scope!();
Self {
@@ -412,15 +476,33 @@ impl Default for Highlighter {
}
}
/// Highlight settings are memoized by reference address, rather than value
#[cfg(feature = "syntect")]
#[derive(Copy, Clone)]
struct HighlightSettings<'a>(&'a SyntectSettings);
#[cfg(not(feature = "syntect"))]
#[derive(Copy, Clone)]
struct HighlightSettings<'a>(&'a ());
impl std::hash::Hash for HighlightSettings<'_> {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
std::ptr::hash(self.0, state);
}
}
#[derive(Default)]
struct Highlighter;
impl Highlighter {
fn highlight(
&self,
font_id: egui::FontId,
theme: &CodeTheme,
code: &str,
lang: &str,
settings: HighlightSettings<'_>,
) -> LayoutJob {
self.highlight_impl(theme, code, lang).unwrap_or_else(|| {
Self::highlight_impl(theme, code, lang, settings).unwrap_or_else(|| {
// Fallback:
LayoutJob::simple(
code.into(),
@@ -436,19 +518,25 @@ impl Highlighter {
}
#[cfg(feature = "syntect")]
fn highlight_impl(&self, theme: &CodeTheme, text: &str, language: &str) -> Option<LayoutJob> {
fn highlight_impl(
theme: &CodeTheme,
text: &str,
language: &str,
highlighter: HighlightSettings<'_>,
) -> Option<LayoutJob> {
profiling::function_scope!();
use syntect::easy::HighlightLines;
use syntect::highlighting::FontStyle;
use syntect::util::LinesWithEndings;
let syntax = self
let syntax = highlighter
.0
.ps
.find_syntax_by_name(language)
.or_else(|| self.ps.find_syntax_by_extension(language))?;
.or_else(|| highlighter.0.ps.find_syntax_by_extension(language))?;
let syn_theme = theme.syntect_theme.syntect_key_name();
let mut h = HighlightLines::new(syntax, &self.ts.themes[syn_theme]);
let mut h = HighlightLines::new(syntax, &highlighter.0.ts.themes[syn_theme]);
use egui::text::{LayoutSection, TextFormat};
@@ -458,7 +546,7 @@ impl Highlighter {
};
for line in LinesWithEndings::from(text) {
for (style, range) in h.highlight_line(line, &self.ps).ok()? {
for (style, range) in h.highlight_line(line, &highlighter.0.ps).ok()? {
let fg = style.foreground;
let text_color = egui::Color32::from_rgb(fg.r, fg.g, fg.b);
let italics = style.font_style.contains(FontStyle::ITALIC);
@@ -505,18 +593,13 @@ fn as_byte_range(whole: &str, range: &str) -> std::ops::Range<usize> {
// ----------------------------------------------------------------------------
#[cfg(not(feature = "syntect"))]
#[derive(Default)]
struct Highlighter {}
#[cfg(not(feature = "syntect"))]
impl Highlighter {
#[expect(clippy::unused_self)]
fn highlight_impl(
&self,
theme: &CodeTheme,
mut text: &str,
language: &str,
_settings: HighlightSettings<'_>,
) -> Option<LayoutJob> {
profiling::function_scope!();

View File

@@ -4,13 +4,13 @@
//! Takes all available height, so if you want something below the table, put it in a strip.
use egui::{
scroll_area::{ScrollAreaOutput, ScrollBarVisibility, ScrollSource},
Align, Id, NumExt as _, Rangef, Rect, Response, ScrollArea, Ui, Vec2, Vec2b,
scroll_area::{ScrollAreaOutput, ScrollBarVisibility, ScrollSource},
};
use crate::{
layout::{CellDirection, CellSize, StripLayoutFlags},
StripLayout,
layout::{CellDirection, CellSize, StripLayoutFlags},
};
// -----------------------------------------------------------------=----------