From 53ec7333c318924914d318c8f00ebe64dd7d63aa Mon Sep 17 00:00:00 2001 From: Kelvin Leung Date: Wed, 17 Dec 2025 15:08:51 +0000 Subject: [PATCH 01/21] Fix media type with optional parameters (#7739) I am having an issue loading this image "https://stacks.stanford.edu/image/iiif/wy534zh7137/SULAIR_rosette/full/400,/0/default.jpg". It has a mime type of "image/jpeg; charset=utf-8", which is not common. The code doesn't parse the full string that includes the optional parameter "charset=utf-8" to create the jpeg image format. A line is added to take only the mime type in the media type before the ";" and ignore the optional parameters. * Closes * [x] I have followed the instructions in the PR template Co-authored-by: leungkkf --- crates/egui_extras/src/loaders/image_loader.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/egui_extras/src/loaders/image_loader.rs b/crates/egui_extras/src/loaders/image_loader.rs index 6d735f688..d6d056418 100644 --- a/crates/egui_extras/src/loaders/image_loader.rs +++ b/crates/egui_extras/src/loaders/image_loader.rs @@ -50,8 +50,11 @@ fn is_supported_mime(mime: &str) -> bool { } } + // Some servers may return a media type with an optional parameter, e.g. "image/jpeg; charset=utf-8". + let (mime_type, _) = mime.split_once(';').unwrap_or((mime, "")); + // Uses only the enabled image crate features - ImageFormat::from_mime_type(mime).is_some_and(|format| format.reading_enabled()) + ImageFormat::from_mime_type(mime_type).is_some_and(|format| format.reading_enabled()) } impl ImageLoader for ImageCrateLoader { From 6157a35985b5540d03462a452e484529a48e532e Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 17 Dec 2025 16:39:13 +0100 Subject: [PATCH 02/21] Deprecate `CentralPanel::show` (#7783) * Part of https://github.com/emilk/egui/issues/3524 Use `show_inside` instead, with a `Ui` instead of a `Context` --- crates/egui/src/containers/panel.rs | 1 + crates/egui/src/context.rs | 28 ++++++++++++++++------- crates/egui_demo_lib/benches/benchmark.rs | 26 ++++++++++----------- crates/egui_kittest/tests/accesskit.rs | 8 +++---- examples/custom_window_frame/src/main.rs | 14 +++++++----- 5 files changed, 46 insertions(+), 31 deletions(-) diff --git a/crates/egui/src/containers/panel.rs b/crates/egui/src/containers/panel.rs index 6ed34fd7c..24a4d24d2 100644 --- a/crates/egui/src/containers/panel.rs +++ b/crates/egui/src/containers/panel.rs @@ -1044,6 +1044,7 @@ impl CentralPanel { } /// Show the panel at the top level. + #[deprecated = "Use show_inside() instead"] pub fn show( self, ctx: &Context, diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index 67abb0556..dc5f06207 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -19,8 +19,8 @@ use crate::{ ImmediateViewportRendererCallback, Key, KeyboardShortcut, Label, LayerId, Memory, ModifierNames, Modifiers, NumExt as _, Order, Painter, RawInput, Response, RichText, SafeAreaInsets, ScrollArea, Sense, Style, TextStyle, TextureHandle, TextureOptions, Ui, - ViewportBuilder, ViewportCommand, ViewportId, ViewportIdMap, ViewportIdPair, ViewportIdSet, - ViewportOutput, Visuals, Widget as _, WidgetRect, WidgetText, + UiBuilder, ViewportBuilder, ViewportCommand, ViewportId, ViewportIdMap, ViewportIdPair, + ViewportIdSet, ViewportOutput, Visuals, Widget as _, WidgetRect, WidgetText, animation_manager::AnimationManager, containers::{self, area::AreaState}, data::output::PlatformOutput, @@ -793,11 +793,23 @@ impl Context { let plugins = self.read(|ctx| ctx.plugins.ordered_plugins()); #[expect(deprecated)] self.run(new_input, |ctx| { - crate::CentralPanel::no_frame().show(ctx, |ui| { - plugins.on_begin_pass(ui); - run_ui(ui); - plugins.on_end_pass(ui); - }); + let mut top_ui = Ui::new( + ctx.clone(), + Id::new((ctx.viewport_id(), "__top_ui")), + UiBuilder::new() + .layer_id(LayerId::background()) + .max_rect(ctx.globally_available_rect().round_ui()), + ); + + { + plugins.on_begin_pass(&mut top_ui); + run_ui(&mut top_ui); + plugins.on_end_pass(&mut top_ui); + } + + // Inform ctx about what we actually used, so we can shrink the native window to fit. + // TODO(emilk): make better use of this somehow + ctx.pass_state_mut(|state| state.allocate_central_panel(top_ui.min_rect())); }) } @@ -3628,7 +3640,7 @@ impl Context { /// If AccessKit support is active for the current frame, get or create /// a node builder with the specified ID and return a mutable reference to it. /// For newly created nodes, the parent is the parent [`Ui`]s ID. - /// And an [`Ui`]s parent can be set with [`crate::UiBuilder::accessibility_parent`]. + /// And an [`Ui`]s parent can be set with [`UiBuilder::accessibility_parent`]. /// /// The `Context` lock is held while the given closure is called! /// diff --git a/crates/egui_demo_lib/benches/benchmark.rs b/crates/egui_demo_lib/benches/benchmark.rs index 36564de25..114a818a1 100644 --- a/crates/egui_demo_lib/benches/benchmark.rs +++ b/crates/egui_demo_lib/benches/benchmark.rs @@ -140,19 +140,19 @@ pub fn criterion_benchmark(c: &mut Criterion) { let ctx = egui::Context::default(); ctx.begin_pass(RawInput::default()); - egui::CentralPanel::default().show(&ctx, |ui| { - c.bench_function("Painter::rect", |b| { - let painter = ui.painter(); - let rect = ui.max_rect(); - b.iter(|| { - painter.rect( - rect, - 2.0, - egui::Color32::RED, - (1.0, egui::Color32::WHITE), - egui::StrokeKind::Inside, - ); - }); + let painter = + egui::Painter::new(ctx.clone(), egui::LayerId::background(), ctx.content_rect()); + + c.bench_function("Painter::rect", |b| { + let rect = painter.clip_rect(); + b.iter(|| { + painter.rect( + rect, + 2.0, + egui::Color32::RED, + (1.0, egui::Color32::WHITE), + egui::StrokeKind::Inside, + ); }); }); diff --git a/crates/egui_kittest/tests/accesskit.rs b/crates/egui_kittest/tests/accesskit.rs index 40c833f71..2b14d0fa1 100644 --- a/crates/egui_kittest/tests/accesskit.rs +++ b/crates/egui_kittest/tests/accesskit.rs @@ -18,8 +18,8 @@ fn empty_ui_should_return_tree_with_only_root_window() { assert_eq!( output.nodes.len(), - 4, - "Expected the root node and two Uis and a Frame for the panel" + 2, + "Expected the root node and the top level Ui; found: {output:#?}", ); assert_eq!( @@ -28,8 +28,8 @@ fn empty_ui_should_return_tree_with_only_root_window() { .iter() .filter(|(_, n)| n.role() == Role::GenericContainer) .count(), - 3, - "Expected two Uis and one Frame as GenericContainer nodes.", + 1, + "Expected a single Ui as a GenericContainer node.", ); let (id, root) = &output.nodes[0]; diff --git a/examples/custom_window_frame/src/main.rs b/examples/custom_window_frame/src/main.rs index 4fa31c1f9..49b3276f0 100644 --- a/examples/custom_window_frame/src/main.rs +++ b/examples/custom_window_frame/src/main.rs @@ -32,7 +32,7 @@ impl eframe::App for MyApp { } fn ui(&mut self, ui: &mut egui::Ui, _frame: &mut eframe::Frame) { - custom_window_frame(ui.ctx(), "egui with custom frame", |ui| { + custom_window_frame(ui, "egui with custom frame", |ui| { ui.label("This is just the contents of the window."); ui.horizontal(|ui| { ui.label("egui theme:"); @@ -42,18 +42,20 @@ impl eframe::App for MyApp { } } -fn custom_window_frame(ctx: &egui::Context, title: &str, add_contents: impl FnOnce(&mut egui::Ui)) { - use egui::{CentralPanel, UiBuilder}; +fn custom_window_frame(ui: &mut egui::Ui, title: &str, add_contents: impl FnOnce(&mut egui::Ui)) { + use egui::UiBuilder; let panel_frame = egui::Frame::new() - .fill(ctx.global_style().visuals.window_fill()) + .fill(ui.global_style().visuals.window_fill()) .corner_radius(10) - .stroke(ctx.global_style().visuals.widgets.noninteractive.fg_stroke) + .stroke(ui.global_style().visuals.widgets.noninteractive.fg_stroke) .outer_margin(1); // so the stroke is within the bounds - CentralPanel::default().frame(panel_frame).show(ctx, |ui| { + panel_frame.show(ui, |ui| { let app_rect = ui.max_rect(); + ui.expand_to_include_rect(app_rect); // Expand frame to include it all + let title_bar_height = 32.0; let title_bar_rect = { let mut rect = app_rect; From 986c2c0ffbbe6a1b2654bb1fbb0a44606a74921d Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 17 Dec 2025 17:19:18 +0100 Subject: [PATCH 03/21] Use explicit `Arc::clone` to clarify when clones are cheap (#7784) --- Cargo.toml | 1 + crates/eframe/src/native/glow_integration.rs | 8 ++++---- crates/eframe/src/native/wgpu_integration.rs | 11 ++++++----- crates/eframe/src/web/app_runner.rs | 11 ++++++----- crates/eframe/src/web/web_painter_glow.rs | 2 +- crates/eframe/src/web/web_painter_wgpu.rs | 4 ++-- crates/egui-wgpu/src/capture.rs | 2 +- crates/egui-wgpu/src/setup.rs | 2 +- crates/egui-wgpu/src/winit.rs | 2 +- crates/egui/src/containers/window.rs | 2 +- crates/egui/src/context.rs | 14 +++++++------- crates/egui/src/drag_and_drop.rs | 6 +----- crates/egui/src/grid.rs | 4 +++- crates/egui/src/load.rs | 2 +- crates/egui/src/menu.rs | 2 +- crates/egui/src/plugin.rs | 2 +- crates/egui/src/ui.rs | 4 ++-- crates/egui/src/viewport.rs | 2 +- crates/egui/src/widget_text.rs | 2 +- crates/egui/src/widgets/text_edit/builder.rs | 2 +- crates/egui_demo_lib/src/demo/misc_demo_window.rs | 4 +++- crates/egui_demo_lib/src/demo/screenshot.rs | 4 ++-- .../src/demo/tests/tessellation_test.rs | 6 ++++-- crates/egui_extras/src/loaders/file_loader.rs | 2 +- crates/egui_extras/src/loaders/gif_loader.rs | 2 +- crates/egui_extras/src/loaders/http_loader.rs | 2 +- crates/egui_extras/src/loaders/image_loader.rs | 2 +- crates/egui_extras/src/loaders/webp_loader.rs | 4 ++-- crates/egui_glow/examples/pure_glow.rs | 2 +- crates/epaint/src/shape_transform.rs | 2 +- crates/epaint/src/text/fonts.rs | 8 ++++---- crates/epaint/src/text/text_layout_types.rs | 2 +- crates/epaint/src/texture_handle.rs | 2 +- examples/custom_3d_glow/src/main.rs | 2 +- examples/external_eventloop_async/src/app.rs | 6 ++++-- examples/multiple_viewports/src/main.rs | 2 +- examples/puffin_profiler/src/main.rs | 2 +- examples/screenshot/src/main.rs | 2 +- tests/test_ui_stack/src/main.rs | 6 ++++-- tests/test_viewports/src/main.rs | 4 ++-- 40 files changed, 80 insertions(+), 71 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b291cb36c..5cb5fbbfd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -184,6 +184,7 @@ branches_sharing_code = "warn" char_lit_as_u8 = "warn" checked_conversions = "warn" clear_with_drain = "warn" +clone_on_ref_ptr = "warn" cloned_instead_of_copied = "warn" dbg_macro = "warn" debug_assert_with_mut_call = "warn" diff --git a/crates/eframe/src/native/glow_integration.rs b/crates/eframe/src/native/glow_integration.rs index 9306cf9cc..0a338affa 100644 --- a/crates/eframe/src/native/glow_integration.rs +++ b/crates/eframe/src/native/glow_integration.rs @@ -216,7 +216,7 @@ impl<'app> GlowWinitApp<'app> { storage.as_deref(), &mut self.native_options, )?; - let gl = painter.gl().clone(); + let gl = Arc::clone(painter.gl()); let max_texture_side = painter.max_texture_side(); glutin.max_texture_side = Some(max_texture_side); @@ -234,9 +234,9 @@ impl<'app> GlowWinitApp<'app> { &self.app_name, &self.native_options, storage, - Some(gl.clone()), + Some(Arc::clone(&gl)), Some(Box::new({ - let painter = painter.clone(); + let painter = Rc::clone(&painter); move |native| painter.borrow_mut().register_native_texture(native) })), #[cfg(feature = "wgpu_no_default_features")] @@ -244,7 +244,7 @@ impl<'app> GlowWinitApp<'app> { ); { - let event_loop_proxy = self.repaint_proxy.clone(); + let event_loop_proxy = Arc::clone(&self.repaint_proxy); integration .egui_ctx .set_request_repaint_callback(move |info| { diff --git a/crates/eframe/src/native/wgpu_integration.rs b/crates/eframe/src/native/wgpu_integration.rs index b7708f6f6..5444e9cf3 100644 --- a/crates/eframe/src/native/wgpu_integration.rs +++ b/crates/eframe/src/native/wgpu_integration.rs @@ -219,7 +219,7 @@ impl<'app> WgpuWinitApp<'app> { { profiling::scope!("set_window"); - pollster::block_on(painter.set_window(ViewportId::ROOT, Some(window.clone())))?; + pollster::block_on(painter.set_window(ViewportId::ROOT, Some(Arc::clone(&window))))?; } let wgpu_render_state = painter.render_state(); @@ -238,7 +238,7 @@ impl<'app> WgpuWinitApp<'app> { ); { - let event_loop_proxy = self.repaint_proxy.clone(); + let event_loop_proxy = Arc::clone(&self.repaint_proxy); egui_ctx.set_request_repaint_callback(move |info| { log::trace!("request_repaint_callback: {info:?}"); @@ -610,7 +610,7 @@ impl WgpuWinitRunning<'_> { { profiling::scope!("set_window"); - pollster::block_on(painter.set_window(viewport_id, Some(window.clone())))?; + pollster::block_on(painter.set_window(viewport_id, Some(Arc::clone(window))))?; } let Some(egui_winit) = egui_winit.as_mut() else { @@ -919,7 +919,7 @@ impl Viewport { let window = Arc::new(window); if let Err(err) = - pollster::block_on(painter.set_window(viewport_id, Some(window.clone()))) + pollster::block_on(painter.set_window(viewport_id, Some(Arc::clone(&window)))) { log::error!("on set_window: viewport_id {viewport_id:?} {err}"); } @@ -1051,7 +1051,8 @@ fn render_immediate_viewport( { profiling::scope!("set_window"); - if let Err(err) = pollster::block_on(painter.set_window(ids.this, Some(window.clone()))) { + if let Err(err) = pollster::block_on(painter.set_window(ids.this, Some(Arc::clone(window)))) + { log::error!( "when rendering viewport_id={:?}, set_window Error {err}", ids.this diff --git a/crates/eframe/src/web/app_runner.rs b/crates/eframe/src/web/app_runner.rs index 83b2cb855..b1a19a882 100644 --- a/crates/eframe/src/web/app_runner.rs +++ b/crates/eframe/src/web/app_runner.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use egui::{TexturesDelta, UserData, ViewportCommand}; use crate::{App, epi, web::web_painter::WebPainter}; @@ -12,7 +14,7 @@ pub struct AppRunner { painter: Box, pub(crate) input: super::WebInput, app: Box, - pub(crate) needs_repaint: std::sync::Arc, + pub(crate) needs_repaint: Arc, last_save_time: f64, pub(crate) text_agent: TextAgent, @@ -63,7 +65,7 @@ impl AppRunner { canvas, &web_options, )?; - gl = Some(painter.gl().clone()); + gl = Some(Arc::clone(painter.gl())); Box::new(painter) as Box } @@ -138,10 +140,9 @@ impl AppRunner { wgpu_render_state, }; - let needs_repaint: std::sync::Arc = - std::sync::Arc::new(NeedRepaint::new(web_options.max_fps)); + let needs_repaint: Arc = Arc::new(NeedRepaint::new(web_options.max_fps)); { - let needs_repaint = needs_repaint.clone(); + let needs_repaint = Arc::clone(&needs_repaint); egui_ctx.set_request_repaint_callback(move |info| { needs_repaint.repaint_after(info.delay.as_secs_f64()); }); diff --git a/crates/eframe/src/web/web_painter_glow.rs b/crates/eframe/src/web/web_painter_glow.rs index e2fc4a6f2..a6a863b05 100644 --- a/crates/eframe/src/web/web_painter_glow.rs +++ b/crates/eframe/src/web/web_painter_glow.rs @@ -91,7 +91,7 @@ impl WebPainter for WebPainterGlow { for data in data { events.push(Event::Screenshot { viewport_id: ViewportId::default(), - image: image.clone(), + image: Arc::clone(&image), user_data: data, }); } diff --git a/crates/eframe/src/web/web_painter_wgpu.rs b/crates/eframe/src/web/web_painter_wgpu.rs index 387366e5a..ef1127f9c 100644 --- a/crates/eframe/src/web/web_painter_wgpu.rs +++ b/crates/eframe/src/web/web_painter_wgpu.rs @@ -105,7 +105,7 @@ impl WebPainterWgpu { surface_configuration, depth_stencil_format, depth_texture_view: None, - on_surface_error: options.wgpu_options.on_surface_error.clone(), + on_surface_error: Arc::clone(&options.wgpu_options.on_surface_error) as _, screen_capture_state: None, capture_tx, capture_rx, @@ -336,7 +336,7 @@ impl WebPainter for WebPainterWgpu { events.push(Event::Screenshot { viewport_id, user_data: data, - image: screenshot.clone(), + image: Arc::clone(&screenshot), }); } } diff --git a/crates/egui-wgpu/src/capture.rs b/crates/egui-wgpu/src/capture.rs index cd42b838c..f0781d97c 100644 --- a/crates/egui-wgpu/src/capture.rs +++ b/crates/egui-wgpu/src/capture.rs @@ -188,7 +188,7 @@ impl CaptureState { ) { #[allow(clippy::arc_with_non_send_sync, clippy::allow_attributes)] // For wasm let buffer = Arc::new(buffer); - let buffer_clone = buffer.clone(); + let buffer_clone = Arc::clone(&buffer); let buffer_slice = buffer_clone.slice(..); let format = self.texture.format(); let tex_extent = self.texture.size(); diff --git a/crates/egui-wgpu/src/setup.rs b/crates/egui-wgpu/src/setup.rs index bd587350e..17498923d 100644 --- a/crates/egui-wgpu/src/setup.rs +++ b/crates/egui-wgpu/src/setup.rs @@ -134,7 +134,7 @@ impl Clone for WgpuSetupCreateNew { instance_descriptor: self.instance_descriptor.clone(), power_preference: self.power_preference, native_adapter_selector: self.native_adapter_selector.clone(), - device_descriptor: self.device_descriptor.clone(), + device_descriptor: Arc::clone(&self.device_descriptor), } } } diff --git a/crates/egui-wgpu/src/winit.rs b/crates/egui-wgpu/src/winit.rs index a35466493..8169bc9be 100644 --- a/crates/egui-wgpu/src/winit.rs +++ b/crates/egui-wgpu/src/winit.rs @@ -629,7 +629,7 @@ impl Painter { events.push(Event::Screenshot { viewport_id, user_data: data, - image: screenshot.clone(), + image: Arc::clone(&screenshot), }); } } diff --git a/crates/egui/src/containers/window.rs b/crates/egui/src/containers/window.rs index 2ea949e88..411527272 100644 --- a/crates/egui/src/containers/window.rs +++ b/crates/egui/src/containers/window.rs @@ -1274,7 +1274,7 @@ impl TitleBar { let text_pos = text_pos - self.title_galley.rect.min.to_vec2(); ui.painter().galley( text_pos, - self.title_galley.clone(), + Arc::clone(&self.title_galley), ui.visuals().text_color(), ); diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index dc5f06207..02cb3cb60 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -1959,7 +1959,7 @@ impl Context { pub fn add_plugin(&self, plugin: impl plugin::Plugin + 'static) { let handle = plugin::PluginHandle::new(plugin); - let added = self.write(|ctx| ctx.plugins.add(handle.clone())); + let added = self.write(|ctx| ctx.plugins.add(Arc::clone(&handle))); if added { handle.lock().dyn_plugin_mut().setup(self); @@ -2085,13 +2085,13 @@ impl Context { /// The currently active [`Style`] used by all subsequent popups, menus, etc. pub fn global_style(&self) -> Arc