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