Skip to main content

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";