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