eframe/epi.rs
1//! Platform-agnostic interface for writing apps using [`egui`] (epi = egui programming interface).
2//!
3//! `epi` provides interfaces for window management and serialization.
4//!
5//! Start by looking at the [`App`] trait, and implement [`App::update`].
6
7#![warn(missing_docs)] // Let's keep `epi` well-documented.
8
9#[cfg(target_arch = "wasm32")]
10use std::any::Any;
11
12#[cfg(not(target_arch = "wasm32"))]
13#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))]
14pub use crate::native::winit_integration::UserEvent;
15
16#[cfg(not(target_arch = "wasm32"))]
17use raw_window_handle::{
18 DisplayHandle, HandleError, HasDisplayHandle, HasWindowHandle, RawDisplayHandle,
19 RawWindowHandle, WindowHandle,
20};
21#[cfg(not(target_arch = "wasm32"))]
22use static_assertions::assert_not_impl_any;
23
24#[cfg(not(target_arch = "wasm32"))]
25#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))]
26pub use winit::{event_loop::EventLoopBuilder, window::WindowAttributes};
27
28/// Hook into the building of an event loop before it is run
29///
30/// You can configure any platform specific details required on top of the default configuration
31/// done by `EFrame`.
32#[cfg(not(target_arch = "wasm32"))]
33#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))]
34pub type EventLoopBuilderHook = Box<dyn FnOnce(&mut EventLoopBuilder<UserEvent>)>;
35
36/// Hook into the building of a the native window.
37///
38/// You can configure any platform specific details required on top of the default configuration
39/// done by `eframe`.
40#[cfg(not(target_arch = "wasm32"))]
41#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))]
42pub type WindowBuilderHook = Box<dyn FnOnce(egui::ViewportBuilder) -> egui::ViewportBuilder>;
43
44type DynError = Box<dyn std::error::Error + Send + Sync>;
45
46/// This is how your app is created.
47///
48/// You can use the [`CreationContext`] to setup egui, restore state, setup OpenGL things, etc.
49pub type AppCreator<'app> =
50 Box<dyn 'app + FnOnce(&CreationContext<'_>) -> Result<Box<dyn 'app + App>, DynError>>;
51
52/// Data that is passed to [`AppCreator`] that can be used to setup and initialize your app.
53pub struct CreationContext<'s> {
54 /// The egui Context.
55 ///
56 /// You can use this to customize the look of egui, e.g to call [`egui::Context::set_fonts`],
57 /// [`egui::Context::set_visuals_of`] etc.
58 pub egui_ctx: egui::Context,
59
60 /// Information about the surrounding environment.
61 pub integration_info: IntegrationInfo,
62
63 /// You can use the storage to restore app state(requires the "persistence" feature).
64 pub storage: Option<&'s dyn Storage>,
65
66 /// The [`glow::Context`] allows you to initialize OpenGL resources (e.g. shaders) that
67 /// you might want to use later from a [`egui::PaintCallback`].
68 ///
69 /// Only available when compiling with the `glow` feature and using [`Renderer::Glow`].
70 #[cfg(feature = "glow")]
71 pub gl: Option<std::sync::Arc<glow::Context>>,
72
73 /// The `get_proc_address` wrapper of underlying GL context
74 #[cfg(feature = "glow")]
75 pub get_proc_address:
76 Option<std::sync::Arc<dyn Fn(&std::ffi::CStr) -> *const std::ffi::c_void + Send + Sync>>,
77
78 /// The underlying WGPU render state.
79 ///
80 /// Only available when compiling with the `wgpu` feature and using [`Renderer::Wgpu`].
81 ///
82 /// Can be used to manage GPU resources for custom rendering with WGPU using [`egui::PaintCallback`]s.
83 #[cfg(feature = "wgpu_no_default_features")]
84 pub wgpu_render_state: Option<egui_wgpu::RenderState>,
85
86 /// Raw platform window handle
87 #[cfg(not(target_arch = "wasm32"))]
88 pub(crate) raw_window_handle: Result<RawWindowHandle, HandleError>,
89
90 /// Raw platform display handle for window
91 #[cfg(not(target_arch = "wasm32"))]
92 pub(crate) raw_display_handle: Result<RawDisplayHandle, HandleError>,
93}
94
95#[expect(unsafe_code)]
96#[cfg(not(target_arch = "wasm32"))]
97impl HasWindowHandle for CreationContext<'_> {
98 fn window_handle(&self) -> Result<WindowHandle<'_>, HandleError> {
99 // Safety: the lifetime is correct.
100 unsafe { Ok(WindowHandle::borrow_raw(self.raw_window_handle.clone()?)) }
101 }
102}
103
104#[expect(unsafe_code)]
105#[cfg(not(target_arch = "wasm32"))]
106impl HasDisplayHandle for CreationContext<'_> {
107 fn display_handle(&self) -> Result<DisplayHandle<'_>, HandleError> {
108 // Safety: the lifetime is correct.
109 unsafe { Ok(DisplayHandle::borrow_raw(self.raw_display_handle.clone()?)) }
110 }
111}
112
113impl CreationContext<'_> {
114 /// Create a new empty [CreationContext] for testing [App]s in kittest.
115 #[doc(hidden)]
116 pub fn _new_kittest(egui_ctx: egui::Context) -> Self {
117 Self {
118 egui_ctx,
119 integration_info: IntegrationInfo::mock(),
120 storage: None,
121 #[cfg(feature = "glow")]
122 gl: None,
123 #[cfg(feature = "glow")]
124 get_proc_address: None,
125 #[cfg(feature = "wgpu_no_default_features")]
126 wgpu_render_state: None,
127 #[cfg(not(target_arch = "wasm32"))]
128 raw_window_handle: Err(HandleError::NotSupported),
129 #[cfg(not(target_arch = "wasm32"))]
130 raw_display_handle: Err(HandleError::NotSupported),
131 }
132 }
133}
134
135// ----------------------------------------------------------------------------
136
137/// Implement this trait to write apps that can be compiled for both web/wasm and desktop/native using [`eframe`](https://github.com/emilk/egui/tree/main/crates/eframe).
138pub trait App {
139 /// Called once before each call to [`Self::ui`],
140 /// and additionally also called when the UI is hidden, but [`egui::Context::request_repaint`] was called.
141 ///
142 /// You may NOT show any ui or do any painting during the call to [`Self::logic`].
143 ///
144 /// The [`egui::Context`] can be cloned and saved if you like.
145 ///
146 /// To force another call to [`Self::logic`], call [`egui::Context::request_repaint`] at any time (e.g. from another thread).
147 fn logic(&mut self, ctx: &egui::Context, frame: &mut Frame) {
148 _ = (ctx, frame);
149 }
150
151 /// Called each time the UI needs repainting, which may be many times per second.
152 ///
153 /// The given [`egui::Ui`] has no margin or background color.
154 /// You can wrap your UI code in [`egui::CentralPanel`] or a [`egui::Frame::central_panel`] to remedy this.
155 ///
156 /// The [`egui::Ui::ctx`] can be cloned and saved if you like.
157 /// To force a repaint, call [`egui::Context::request_repaint`] at any time (e.g. from another thread).
158 ///
159 /// This is called for the root viewport ([`egui::ViewportId::ROOT`]).
160 /// Use [`egui::Context::show_viewport_deferred`] to spawn additional viewports (windows).
161 /// (A "viewport" in egui means an native OS window).
162 fn ui(&mut self, ui: &mut egui::Ui, frame: &mut Frame);
163
164 /// Called each time the UI needs repainting, which may be many times per second.
165 ///
166 /// Put your widgets into a [`egui::Panel`], [`egui::CentralPanel`], [`egui::Window`] or [`egui::Area`].
167 ///
168 /// The [`egui::Context`] can be cloned and saved if you like.
169 ///
170 /// To force a repaint, call [`egui::Context::request_repaint`] at any time (e.g. from another thread).
171 ///
172 /// This is called for the root viewport ([`egui::ViewportId::ROOT`]).
173 /// Use [`egui::Context::show_viewport_deferred`] to spawn additional viewports (windows).
174 /// (A "viewport" in egui means an native OS window).
175 #[deprecated = "Use Self::ui instead"]
176 fn update(&mut self, ctx: &egui::Context, frame: &mut Frame) {
177 _ = (ctx, frame);
178 }
179
180 /// Get a handle to the app.
181 ///
182 /// Can be used from web to interact or other external context.
183 ///
184 /// You need to implement this if you want to be able to access the application from JS using [`crate::WebRunner::app_mut`].
185 ///
186 /// This is needed because downcasting `Box<dyn App>` -> `Box<dyn Any>` to get &`ConcreteApp` is not simple in current rust.
187 ///
188 /// Just copy-paste this as your implementation:
189 /// ```ignore
190 /// #[cfg(target_arch = "wasm32")]
191 /// fn as_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
192 /// Some(&mut *self)
193 /// }
194 /// ```
195 #[cfg(target_arch = "wasm32")]
196 fn as_any_mut(&mut self) -> Option<&mut dyn Any> {
197 None
198 }
199
200 /// Called on shutdown, and perhaps at regular intervals. Allows you to save state.
201 ///
202 /// Only called when the "persistence" feature is enabled.
203 ///
204 /// On web the state is stored to "Local Storage".
205 ///
206 /// On native the path is picked using [`crate::storage_dir`].
207 /// The path can be customized via [`NativeOptions::persistence_path`].
208 fn save(&mut self, _storage: &mut dyn Storage) {}
209
210 /// Called once on shutdown, after [`Self::save`].
211 ///
212 /// If you need to abort an exit check `ctx.input(|i| i.viewport().close_requested())`
213 /// and respond with [`egui::ViewportCommand::CancelClose`].
214 ///
215 /// To get a [`glow`] context you need to compile with the `glow` feature flag,
216 /// and run eframe with the glow backend.
217 #[cfg(feature = "glow")]
218 fn on_exit(&mut self, _gl: Option<&glow::Context>) {}
219
220 /// Called once on shutdown, after [`Self::save`].
221 ///
222 /// If you need to abort an exit use [`Self::on_close_event`].
223 #[cfg(not(feature = "glow"))]
224 fn on_exit(&mut self) {}
225
226 // ---------
227 // Settings:
228
229 /// Time between automatic calls to [`Self::save`]
230 fn auto_save_interval(&self) -> std::time::Duration {
231 std::time::Duration::from_secs(30)
232 }
233
234 /// Background color values for the app, e.g. what is sent to `gl.clearColor`.
235 ///
236 /// This is the background of your windows if you don't set a central panel.
237 ///
238 /// ATTENTION:
239 /// Since these float values go to the render as-is, any color space conversion as done
240 /// e.g. by converting from [`egui::Color32`] to [`egui::Rgba`] may cause incorrect results.
241 /// egui recommends that rendering backends use a normal "gamma-space" (non-sRGB-aware) blending,
242 /// which means the values you return here should also be in `sRGB` gamma-space in the 0-1 range.
243 /// You can use [`egui::Color32::to_normalized_gamma_f32`] for this.
244 fn clear_color(&self, _visuals: &egui::Visuals) -> [f32; 4] {
245 // NOTE: a bright gray makes the shadows of the windows look weird.
246 // We use a bit of transparency so that if the user switches on the
247 // `transparent()` option they get immediate results.
248 egui::Color32::from_rgba_unmultiplied(12, 12, 12, 180).to_normalized_gamma_f32()
249
250 // _visuals.window_fill() would also be a natural choice
251 }
252
253 /// Controls whether or not the egui memory (window positions etc) will be
254 /// persisted (only if the "persistence" feature is enabled).
255 fn persist_egui_memory(&self) -> bool {
256 true
257 }
258
259 /// A hook for manipulating or filtering raw input before it is processed by [`Self::update`].
260 ///
261 /// This function provides a way to modify or filter input events before they are processed by egui.
262 ///
263 /// It can be used to prevent specific keyboard shortcuts or mouse events from being processed by egui.
264 ///
265 /// Additionally, it can be used to inject custom keyboard or mouse events into the input stream, which can be useful for implementing features like a virtual keyboard.
266 ///
267 /// # Arguments
268 ///
269 /// * `_ctx` - The context of the egui, which provides access to the current state of the egui.
270 /// * `_raw_input` - The raw input events that are about to be processed. This can be modified to change the input that egui processes.
271 ///
272 /// # Note
273 ///
274 /// This function does not return a value. Any changes to the input should be made directly to `_raw_input`.
275 fn raw_input_hook(&mut self, _ctx: &egui::Context, _raw_input: &mut egui::RawInput) {}
276}
277
278/// Selects the level of hardware graphics acceleration.
279#[cfg(not(target_arch = "wasm32"))]
280#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
281pub enum HardwareAcceleration {
282 /// Require graphics acceleration.
283 Required,
284
285 /// Prefer graphics acceleration, but fall back to software.
286 Preferred,
287
288 /// Do NOT use graphics acceleration.
289 ///
290 /// On some platforms (macOS) this is ignored and treated the same as [`Self::Preferred`].
291 Off,
292}
293
294/// Options controlling the behavior of a native window.
295///
296/// Additional windows can be opened using (egui viewports)[`egui::viewport`].
297///
298/// Set the window title and size using [`Self::viewport`].
299///
300/// ### Application id
301/// [`egui::ViewportBuilder::with_app_id`] is used for determining the folder to persist the app to.
302///
303/// On native the path is picked using [`crate::storage_dir`].
304///
305/// If you don't set an app id, the title argument to [`crate::run_native`]
306/// will be used as app id instead.
307#[cfg(not(target_arch = "wasm32"))]
308pub struct NativeOptions {
309 /// Controls the native window of the root viewport.
310 ///
311 /// This is where you set things like window title and size.
312 ///
313 /// If you don't set an icon, a default egui icon will be used.
314 /// To avoid this, set the icon to [`egui::IconData::default`].
315 pub viewport: egui::ViewportBuilder,
316
317 /// Turn on vertical syncing, limiting the FPS to the display refresh rate.
318 ///
319 /// The default is `true`.
320 ///
321 /// Only affects the `glow` backend.
322 pub vsync: bool,
323
324 /// Set the level of the multisampling anti-aliasing (MSAA).
325 ///
326 /// Must be a power-of-two. Higher = more smooth 3D.
327 ///
328 /// A value of `0` turns it off (default).
329 ///
330 /// `egui` already performs anti-aliasing via "feathering"
331 /// (controlled by [`egui::epaint::TessellationOptions`]),
332 /// but if you are embedding 3D in egui you may want to turn on multisampling.
333 pub multisampling: u16,
334
335 /// Sets the number of bits in the depth buffer.
336 ///
337 /// `egui` doesn't need the depth buffer, so the default value is 0.
338 pub depth_buffer: u8,
339
340 /// Sets the number of bits in the stencil buffer.
341 ///
342 /// `egui` doesn't need the stencil buffer, so the default value is 0.
343 pub stencil_buffer: u8,
344
345 /// Specify whether or not hardware acceleration is preferred, required, or not.
346 ///
347 /// Default: [`HardwareAcceleration::Preferred`].
348 ///
349 /// Only affects the `glow` backend.
350 pub hardware_acceleration: HardwareAcceleration,
351
352 /// What rendering backend to use.
353 #[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))]
354 pub renderer: Renderer,
355
356 /// This controls what happens when you close the main eframe window.
357 ///
358 /// If `true`, execution will continue after the eframe window is closed.
359 /// If `false`, the app will close once the eframe window is closed.
360 ///
361 /// This is `true` by default, and the `false` option is only there
362 /// so we can revert if we find any bugs.
363 ///
364 /// This feature was introduced in <https://github.com/emilk/egui/pull/1889>.
365 ///
366 /// When `true`, [`winit::platform::run_on_demand::EventLoopExtRunOnDemand`] is used.
367 /// When `false`, [`winit::event_loop::EventLoop::run`] is used.
368 pub run_and_return: bool,
369
370 /// Hook into the building of an event loop before it is run.
371 ///
372 /// Specify a callback here in case you need to make platform specific changes to the
373 /// event loop before it is run.
374 ///
375 /// Note: A [`NativeOptions`] clone will not include any `event_loop_builder` hook.
376 #[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))]
377 pub event_loop_builder: Option<EventLoopBuilderHook>,
378
379 /// Hook into the building of a window.
380 ///
381 /// Specify a callback here in case you need to make platform specific changes to the
382 /// window appearance.
383 ///
384 /// Note: A [`NativeOptions`] clone will not include any `window_builder` hook.
385 #[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))]
386 pub window_builder: Option<WindowBuilderHook>,
387
388 #[cfg(feature = "glow")]
389 /// Needed for cross compiling for VirtualBox VMSVGA driver with OpenGL ES 2.0 and OpenGL 2.1 which doesn't support SRGB texture.
390 /// See <https://github.com/emilk/egui/pull/1993>.
391 ///
392 /// For OpenGL ES 2.0: set this to [`egui_glow::ShaderVersion::Es100`] to solve blank texture problem (by using the "fallback shader").
393 pub shader_version: Option<egui_glow::ShaderVersion>,
394
395 /// On desktop: make the window position to be centered at initialization.
396 ///
397 /// Platform specific:
398 ///
399 /// Wayland desktop currently not supported.
400 pub centered: bool,
401
402 /// Configures wgpu instance/device/adapter/surface creation and renderloop.
403 #[cfg(feature = "wgpu_no_default_features")]
404 pub wgpu_options: egui_wgpu::WgpuConfiguration,
405
406 /// Controls whether or not the native window position and size will be
407 /// persisted (only if the "persistence" feature is enabled).
408 pub persist_window: bool,
409
410 /// The folder where `eframe` will store the app state. If not set, eframe will use a default
411 /// data storage path for each target system.
412 pub persistence_path: Option<std::path::PathBuf>,
413
414 /// Controls whether to apply dithering to minimize banding artifacts.
415 ///
416 /// Dithering assumes an sRGB output and thus will apply noise to any input value that lies between
417 /// two 8bit values after applying the sRGB OETF function, i.e. if it's not a whole 8bit value in "gamma space".
418 /// This means that only inputs from texture interpolation and vertex colors should be affected in practice.
419 ///
420 /// Defaults to true.
421 pub dithering: bool,
422
423 /// Android application for `winit`'s event loop.
424 ///
425 /// This value is required on Android to correctly create the event loop. See
426 /// [`EventLoopBuilder::build`] and [`with_android_app`] for details.
427 ///
428 /// [`EventLoopBuilder::build`]: winit::event_loop::EventLoopBuilder::build
429 /// [`with_android_app`]: winit::platform::android::EventLoopBuilderExtAndroid::with_android_app
430 #[cfg(target_os = "android")]
431 pub android_app: Option<winit::platform::android::activity::AndroidApp>,
432}
433
434#[cfg(not(target_arch = "wasm32"))]
435impl Clone for NativeOptions {
436 fn clone(&self) -> Self {
437 Self {
438 viewport: self.viewport.clone(),
439
440 #[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))]
441 event_loop_builder: None, // Skip any builder callbacks if cloning
442
443 #[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))]
444 window_builder: None, // Skip any builder callbacks if cloning
445
446 #[cfg(feature = "wgpu_no_default_features")]
447 wgpu_options: self.wgpu_options.clone(),
448
449 persistence_path: self.persistence_path.clone(),
450
451 #[cfg(target_os = "android")]
452 android_app: self.android_app.clone(),
453
454 ..*self
455 }
456 }
457}
458
459#[cfg(not(target_arch = "wasm32"))]
460impl Default for NativeOptions {
461 fn default() -> Self {
462 Self {
463 viewport: Default::default(),
464
465 vsync: true,
466 multisampling: 0,
467 depth_buffer: 0,
468 stencil_buffer: 0,
469 hardware_acceleration: HardwareAcceleration::Preferred,
470
471 #[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))]
472 renderer: Renderer::default(),
473
474 run_and_return: true,
475
476 #[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))]
477 event_loop_builder: None,
478
479 #[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))]
480 window_builder: None,
481
482 #[cfg(feature = "glow")]
483 shader_version: None,
484
485 centered: false,
486
487 #[cfg(feature = "wgpu_no_default_features")]
488 wgpu_options: egui_wgpu::WgpuConfiguration::default(),
489
490 persist_window: true,
491
492 persistence_path: None,
493
494 dithering: true,
495
496 #[cfg(target_os = "android")]
497 android_app: None,
498 }
499 }
500}
501
502// ----------------------------------------------------------------------------
503
504/// Options when using `eframe` in a web page.
505#[cfg(target_arch = "wasm32")]
506pub struct WebOptions {
507 /// What rendering backend to use.
508 #[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))]
509 pub renderer: Renderer,
510
511 /// Sets the number of bits in the depth buffer.
512 ///
513 /// `egui` doesn't need the depth buffer, so the default value is 0.
514 /// Unused by webgl context as of writing.
515 pub depth_buffer: u8,
516
517 /// Which version of WebGL context to select
518 ///
519 /// Default: [`WebGlContextOption::BestFirst`].
520 #[cfg(feature = "glow")]
521 pub webgl_context_option: WebGlContextOption,
522
523 /// Configures wgpu instance/device/adapter/surface creation and renderloop.
524 #[cfg(feature = "wgpu_no_default_features")]
525 pub wgpu_options: egui_wgpu::WgpuConfiguration,
526
527 /// Controls whether to apply dithering to minimize banding artifacts.
528 ///
529 /// Dithering assumes an sRGB output and thus will apply noise to any input value that lies between
530 /// two 8bit values after applying the sRGB OETF function, i.e. if it's not a whole 8bit value in "gamma space".
531 /// This means that only inputs from texture interpolation and vertex colors should be affected in practice.
532 ///
533 /// Defaults to true.
534 pub dithering: bool,
535
536 /// If the web event corresponding to an egui event should be propagated
537 /// to the rest of the web page.
538 ///
539 /// The default is `true`, meaning
540 /// [`stopPropagation`](https://developer.mozilla.org/en-US/docs/Web/API/Event/stopPropagation)
541 /// is called on every event, and the event is not propagated to the rest of the web page.
542 pub should_stop_propagation: Box<dyn Fn(&egui::Event) -> bool>,
543
544 /// Whether the web event corresponding to an egui event should have `prevent_default` called
545 /// on it or not.
546 ///
547 /// Defaults to true.
548 pub should_prevent_default: Box<dyn Fn(&egui::Event) -> bool>,
549
550 /// Maximum rate at which to repaint. This can be used to artificially reduce the repaint rate below
551 /// vsync in order to save resources.
552 pub max_fps: Option<u32>,
553}
554
555#[cfg(target_arch = "wasm32")]
556impl Default for WebOptions {
557 fn default() -> Self {
558 Self {
559 #[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))]
560 renderer: Renderer::default(),
561
562 depth_buffer: 0,
563
564 #[cfg(feature = "glow")]
565 webgl_context_option: WebGlContextOption::BestFirst,
566
567 #[cfg(feature = "wgpu_no_default_features")]
568 wgpu_options: egui_wgpu::WgpuConfiguration::default(),
569
570 dithering: true,
571
572 should_stop_propagation: Box::new(|_| true),
573 should_prevent_default: Box::new(|_| true),
574
575 max_fps: None,
576 }
577 }
578}
579
580// ----------------------------------------------------------------------------
581
582/// WebGL Context options
583#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
584#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
585pub enum WebGlContextOption {
586 /// Force Use WebGL1.
587 WebGl1,
588
589 /// Force use WebGL2.
590 WebGl2,
591
592 /// Use WebGL2 first.
593 BestFirst,
594
595 /// Use WebGL1 first
596 CompatibilityFirst,
597}
598
599// ----------------------------------------------------------------------------
600
601/// What rendering backend to use.
602///
603/// You need to enable the "glow" and "wgpu" features to have a choice.
604#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))]
605#[derive(Clone, Copy, Debug, PartialEq, Eq)]
606#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
607#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
608pub enum Renderer {
609 /// Use [`egui_glow`] renderer for [`glow`](https://github.com/grovesNL/glow).
610 #[cfg(feature = "glow")]
611 Glow,
612
613 /// Use [`egui_wgpu`] renderer for [`wgpu`](https://github.com/gfx-rs/wgpu).
614 #[cfg(feature = "wgpu_no_default_features")]
615 Wgpu,
616}
617
618#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))]
619impl Default for Renderer {
620 fn default() -> Self {
621 #[cfg(not(feature = "glow"))]
622 #[cfg(not(feature = "wgpu_no_default_features"))]
623 compile_error!(
624 "eframe: you must enable at least one of the rendering backend features: 'glow' or 'wgpu'"
625 );
626
627 #[cfg(feature = "glow")]
628 #[cfg(not(feature = "wgpu_no_default_features"))]
629 return Self::Glow;
630
631 #[cfg(not(feature = "glow"))]
632 #[cfg(feature = "wgpu_no_default_features")]
633 return Self::Wgpu;
634
635 // It's weird that the user has enabled both glow and wgpu,
636 // but let's pick the better of the two (wgpu):
637 #[cfg(feature = "glow")]
638 #[cfg(feature = "wgpu_no_default_features")]
639 return Self::Wgpu;
640 }
641}
642
643#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))]
644impl std::fmt::Display for Renderer {
645 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
646 match self {
647 #[cfg(feature = "glow")]
648 Self::Glow => "glow".fmt(f),
649
650 #[cfg(feature = "wgpu_no_default_features")]
651 Self::Wgpu => "wgpu".fmt(f),
652 }
653 }
654}
655
656#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))]
657impl std::str::FromStr for Renderer {
658 type Err = String;
659
660 fn from_str(name: &str) -> Result<Self, String> {
661 match name.to_lowercase().as_str() {
662 #[cfg(feature = "glow")]
663 "glow" => Ok(Self::Glow),
664
665 #[cfg(feature = "wgpu_no_default_features")]
666 "wgpu" => Ok(Self::Wgpu),
667
668 _ => Err(format!(
669 "eframe renderer {name:?} is not available. Make sure that the corresponding eframe feature is enabled."
670 )),
671 }
672 }
673}
674
675// ----------------------------------------------------------------------------
676
677/// Represents the surroundings of your app.
678///
679/// It provides methods to inspect the surroundings (are we on the web?),
680/// access to persistent storage, and access to the rendering backend.
681pub struct Frame {
682 /// Information about the integration.
683 pub(crate) info: IntegrationInfo,
684
685 /// A place where you can store custom data in a way that persists when you restart the app.
686 pub(crate) storage: Option<Box<dyn Storage>>,
687
688 /// A reference to the underlying [`glow`] (OpenGL) context.
689 #[cfg(feature = "glow")]
690 pub(crate) gl: Option<std::sync::Arc<glow::Context>>,
691
692 /// Used to convert user custom [`glow::Texture`] to [`egui::TextureId`]
693 #[cfg(all(feature = "glow", not(target_arch = "wasm32")))]
694 pub(crate) glow_register_native_texture:
695 Option<Box<dyn FnMut(glow::Texture) -> egui::TextureId>>,
696
697 /// Can be used to manage GPU resources for custom rendering with WGPU using [`egui::PaintCallback`]s.
698 #[cfg(feature = "wgpu_no_default_features")]
699 #[doc(hidden)]
700 pub wgpu_render_state: Option<egui_wgpu::RenderState>,
701
702 /// Raw platform window handle
703 #[cfg(not(target_arch = "wasm32"))]
704 pub(crate) raw_window_handle: Result<RawWindowHandle, HandleError>,
705
706 /// Raw platform display handle for window
707 #[cfg(not(target_arch = "wasm32"))]
708 pub(crate) raw_display_handle: Result<RawDisplayHandle, HandleError>,
709}
710
711// Implementing `Clone` would violate the guarantees of `HasWindowHandle` and `HasDisplayHandle`.
712#[cfg(not(target_arch = "wasm32"))]
713assert_not_impl_any!(Frame: Clone);
714
715#[expect(unsafe_code)]
716#[cfg(not(target_arch = "wasm32"))]
717impl HasWindowHandle for Frame {
718 fn window_handle(&self) -> Result<WindowHandle<'_>, HandleError> {
719 // Safety: the lifetime is correct.
720 unsafe { Ok(WindowHandle::borrow_raw(self.raw_window_handle.clone()?)) }
721 }
722}
723
724#[expect(unsafe_code)]
725#[cfg(not(target_arch = "wasm32"))]
726impl HasDisplayHandle for Frame {
727 fn display_handle(&self) -> Result<DisplayHandle<'_>, HandleError> {
728 // Safety: the lifetime is correct.
729 unsafe { Ok(DisplayHandle::borrow_raw(self.raw_display_handle.clone()?)) }
730 }
731}
732
733impl Frame {
734 /// Create a new empty [Frame] for testing [App]s in kittest.
735 #[doc(hidden)]
736 pub fn _new_kittest() -> Self {
737 Self {
738 #[cfg(feature = "glow")]
739 gl: None,
740 #[cfg(all(feature = "glow", not(target_arch = "wasm32")))]
741 glow_register_native_texture: None,
742 info: IntegrationInfo::mock(),
743 #[cfg(not(target_arch = "wasm32"))]
744 raw_display_handle: Err(HandleError::NotSupported),
745 #[cfg(not(target_arch = "wasm32"))]
746 raw_window_handle: Err(HandleError::NotSupported),
747 storage: None,
748 #[cfg(feature = "wgpu_no_default_features")]
749 wgpu_render_state: None,
750 }
751 }
752
753 /// True if you are in a web environment.
754 ///
755 /// Equivalent to `cfg!(target_arch = "wasm32")`
756 #[expect(clippy::unused_self)]
757 pub fn is_web(&self) -> bool {
758 cfg!(target_arch = "wasm32")
759 }
760
761 /// Information about the integration.
762 pub fn info(&self) -> &IntegrationInfo {
763 &self.info
764 }
765
766 /// A place where you can store custom data in a way that persists when you restart the app.
767 pub fn storage(&self) -> Option<&dyn Storage> {
768 self.storage.as_deref()
769 }
770
771 /// A place where you can store custom data in a way that persists when you restart the app.
772 pub fn storage_mut(&mut self) -> Option<&mut (dyn Storage + 'static)> {
773 self.storage.as_deref_mut()
774 }
775
776 /// A reference to the underlying [`glow`] (OpenGL) context.
777 ///
778 /// This can be used, for instance, to:
779 /// * Render things to offscreen buffers.
780 /// * Read the pixel buffer from the previous frame (`glow::Context::read_pixels`).
781 /// * Render things behind the egui windows.
782 ///
783 /// Note that all egui painting is deferred to after the call to [`App::update`]
784 /// ([`egui`] only collects [`egui::Shape`]s and then eframe paints them all in one go later on).
785 ///
786 /// To get a [`glow`] context you need to compile with the `glow` feature flag,
787 /// and run eframe using [`Renderer::Glow`].
788 #[cfg(feature = "glow")]
789 pub fn gl(&self) -> Option<&std::sync::Arc<glow::Context>> {
790 self.gl.as_ref()
791 }
792
793 /// Register your own [`glow::Texture`],
794 /// and then you can use the returned [`egui::TextureId`] to render your texture with [`egui`].
795 ///
796 /// This function will take the ownership of your [`glow::Texture`], so please do not delete your [`glow::Texture`] after registering.
797 #[cfg(all(feature = "glow", not(target_arch = "wasm32")))]
798 pub fn register_native_glow_texture(&mut self, native: glow::Texture) -> egui::TextureId {
799 #[expect(clippy::unwrap_used)]
800 self.glow_register_native_texture.as_mut().unwrap()(native)
801 }
802
803 /// The underlying WGPU render state.
804 ///
805 /// Only available when compiling with the `wgpu` feature and using [`Renderer::Wgpu`].
806 ///
807 /// Can be used to manage GPU resources for custom rendering with WGPU using [`egui::PaintCallback`]s.
808 #[cfg(feature = "wgpu_no_default_features")]
809 pub fn wgpu_render_state(&self) -> Option<&egui_wgpu::RenderState> {
810 self.wgpu_render_state.as_ref()
811 }
812}
813
814/// Information about the web environment (if applicable).
815#[derive(Clone, Debug)]
816#[cfg(target_arch = "wasm32")]
817pub struct WebInfo {
818 /// The browser user agent.
819 pub user_agent: String,
820
821 /// Information about the URL.
822 pub location: Location,
823}
824
825/// Information about the URL.
826///
827/// Everything has been percent decoded (`%20` -> ` ` etc).
828#[cfg(target_arch = "wasm32")]
829#[derive(Clone, Debug)]
830pub struct Location {
831 /// The full URL (`location.href`) without the hash, percent-decoded.
832 ///
833 /// Example: `"http://www.example.com:80/index.html?foo=bar"`.
834 pub url: String,
835
836 /// `location.protocol`
837 ///
838 /// Example: `"http:"`.
839 pub protocol: String,
840
841 /// `location.host`
842 ///
843 /// Example: `"example.com:80"`.
844 pub host: String,
845
846 /// `location.hostname`
847 ///
848 /// Example: `"example.com"`.
849 pub hostname: String,
850
851 /// `location.port`
852 ///
853 /// Example: `"80"`.
854 pub port: String,
855
856 /// The "#fragment" part of "www.example.com/index.html?query#fragment".
857 ///
858 /// Note that the leading `#` is included in the string.
859 /// Also known as "hash-link" or "anchor".
860 pub hash: String,
861
862 /// The "query" part of "www.example.com/index.html?query#fragment".
863 ///
864 /// Note that the leading `?` is NOT included in the string.
865 ///
866 /// Use [`Self::query_map`] to get the parsed version of it.
867 pub query: String,
868
869 /// The parsed "query" part of "www.example.com/index.html?query#fragment".
870 ///
871 /// "foo=hello&bar%20&foo=world" is parsed as `{"bar ": [""], "foo": ["hello", "world"]}`
872 pub query_map: std::collections::BTreeMap<String, Vec<String>>,
873
874 /// `location.origin`
875 ///
876 /// Example: `"http://www.example.com:80"`.
877 pub origin: String,
878}
879
880/// Information about the integration passed to the use app each frame.
881#[derive(Clone, Debug)]
882pub struct IntegrationInfo {
883 /// Information about the surrounding web environment.
884 #[cfg(target_arch = "wasm32")]
885 pub web_info: WebInfo,
886
887 /// Seconds of cpu usage (in seconds) on the previous frame.
888 ///
889 /// This includes [`App::update`] as well as rendering (except for vsync waiting).
890 ///
891 /// For a more detailed view of cpu usage, connect your preferred profiler by enabling it's feature in [`profiling`](https://crates.io/crates/profiling).
892 ///
893 /// `None` if this is the first frame.
894 pub cpu_usage: Option<f32>,
895}
896
897impl IntegrationInfo {
898 fn mock() -> Self {
899 Self {
900 #[cfg(target_arch = "wasm32")]
901 web_info: WebInfo {
902 user_agent: "kittest".to_owned(),
903 location: Location {
904 url: "http://localhost".to_owned(),
905 protocol: "http:".to_owned(),
906 host: "localhost".to_owned(),
907 hostname: "localhost".to_owned(),
908 port: "80".to_owned(),
909 hash: String::new(),
910 query: String::new(),
911 query_map: Default::default(),
912 origin: "http://localhost".to_owned(),
913 },
914 },
915 cpu_usage: None,
916 }
917 }
918}
919
920// ----------------------------------------------------------------------------
921
922/// A place where you can store custom data in a way that persists when you restart the app.
923///
924/// On the web this is backed by [local storage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage).
925/// On desktop this is backed by the file system.
926///
927/// See [`CreationContext::storage`] and [`App::save`].
928pub trait Storage {
929 /// Get the value for the given key.
930 fn get_string(&self, key: &str) -> Option<String>;
931
932 /// Set the value for the given key.
933 fn set_string(&mut self, key: &str, value: String);
934
935 /// write-to-disk or similar
936 fn flush(&mut self);
937}
938
939/// Get and deserialize the [RON](https://github.com/ron-rs/ron) stored at the given key.
940#[cfg(feature = "ron")]
941pub fn get_value<T: serde::de::DeserializeOwned>(storage: &dyn Storage, key: &str) -> Option<T> {
942 profiling::function_scope!(key);
943 let value = storage.get_string(key)?;
944 match ron::from_str(&value) {
945 Ok(value) => Some(value),
946 Err(err) => {
947 // This happens on when we break the format, e.g. when updating egui.
948 log::debug!("Failed to decode RON: {err}");
949 None
950 }
951 }
952}
953
954/// Serialize the given value as [RON](https://github.com/ron-rs/ron) and store with the given key.
955#[cfg(feature = "ron")]
956pub fn set_value<T: serde::Serialize>(storage: &mut dyn Storage, key: &str, value: &T) {
957 profiling::function_scope!(key);
958 match ron::ser::to_string(value) {
959 Ok(string) => storage.set_string(key, string),
960 Err(err) => log::error!("eframe failed to encode data using ron: {err}"),
961 }
962}
963
964/// [`Storage`] key used for app
965pub const APP_KEY: &str = "app";