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:
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
))
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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:?}"
|
||||
))
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::{
|
||||
Size,
|
||||
layout::{CellDirection, CellSize, StripLayout, StripLayoutFlags},
|
||||
sizing::Sizing,
|
||||
Size,
|
||||
};
|
||||
use egui::{Response, Ui};
|
||||
|
||||
|
||||
@@ -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!();
|
||||
|
||||
|
||||
@@ -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},
|
||||
};
|
||||
|
||||
// -----------------------------------------------------------------=----------
|
||||
|
||||
Reference in New Issue
Block a user