Skip to main content

eframe/
lib.rs

1//! eframe - the [`egui`] framework crate
2//!
3//! If you are planning to write an app for web or native,
4//! and want to use [`egui`] for everything, then `eframe` is for you!
5//!
6//! To get started, see the [examples](https://github.com/emilk/egui/tree/main/examples).
7//! To learn how to set up `eframe` for web and native, go to <https://github.com/emilk/eframe_template/> and follow the instructions there!
8//!
9//! In short, you implement [`App`] (especially [`App::ui`]) and then
10//! call [`crate::run_native`] from your `main.rs`, and/or use `eframe::WebRunner` from your `lib.rs`.
11//!
12//! ## Compiling for web
13//! You need to install the `wasm32` target with `rustup target add wasm32-unknown-unknown`.
14//!
15//! Build the `.wasm` using `cargo build --target wasm32-unknown-unknown`
16//! and then use [`wasm-bindgen`](https://github.com/rustwasm/wasm-bindgen) to generate the JavaScript glue code.
17//!
18//! See the [`eframe_template` repository](https://github.com/emilk/eframe_template/) for more.
19//!
20//! ## Simplified usage
21//! If your app is only for native, and you don't need advanced features like state persistence,
22//! then you can use the simpler function [`run_ui_native`].
23//!
24//! ## Usage, native:
25//! ``` no_run
26//! use eframe::egui;
27//!
28//! fn main() {
29//!     let native_options = eframe::NativeOptions::default();
30//!     eframe::run_native("My egui App", native_options, Box::new(|cc| Ok(Box::new(MyEguiApp::new(cc)))));
31//! }
32//!
33//! #[derive(Default)]
34//! struct MyEguiApp {}
35//!
36//! impl MyEguiApp {
37//!     fn new(cc: &eframe::CreationContext<'_>) -> Self {
38//!         // Customize egui here with cc.egui_ctx.set_fonts and cc.egui_ctx.set_global_style.
39//!         // Restore app state using cc.storage (requires the "persistence" feature).
40//!         // Use the cc.gl (a glow::Context) to create graphics shaders and buffers that you can use
41//!         // for e.g. egui::PaintCallback.
42//!         Self::default()
43//!     }
44//! }
45//!
46//! impl eframe::App for MyEguiApp {
47//!    fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) {
48//!        egui::CentralPanel::default().show(ui, |ui| {
49//!            ui.heading("Hello World!");
50//!        });
51//!    }
52//! }
53//! ```
54//!
55//! ## Usage, web:
56//! ``` no_run
57//! # #[cfg(target_arch = "wasm32")]
58//! use wasm_bindgen::prelude::*;
59//!
60//! /// Your handle to the web app from JavaScript.
61//! # #[cfg(target_arch = "wasm32")]
62//! #[derive(Clone)]
63//! #[wasm_bindgen]
64//! pub struct WebHandle {
65//!     runner: eframe::WebRunner,
66//! }
67//!
68//! # #[cfg(target_arch = "wasm32")]
69//! #[wasm_bindgen]
70//! impl WebHandle {
71//!     /// Installs a panic hook, then returns.
72//!     #[expect(clippy::new_without_default)]
73//!     #[wasm_bindgen(constructor)]
74//!     pub fn new() -> Self {
75//!         // Redirect [`log`] message to `console.log` and friends:
76//!         eframe::WebLogger::init(log::LevelFilter::Debug).ok();
77//!
78//!         Self {
79//!             runner: eframe::WebRunner::new(),
80//!         }
81//!     }
82//!
83//!     /// Call this once from JavaScript to start your app.
84//!     #[wasm_bindgen]
85//!     pub async fn start(&self, canvas: web_sys::HtmlCanvasElement) -> Result<(), wasm_bindgen::JsValue> {
86//!         self.runner
87//!             .start(
88//!                 canvas,
89//!                 eframe::WebOptions::default(),
90//!                 Box::new(|cc| Ok(Box::new(MyEguiApp::new(cc))),)
91//!             )
92//!             .await
93//!     }
94//!
95//!     // The following are optional:
96//!
97//!     /// Shut down eframe and clean up resources.
98//!     #[wasm_bindgen]
99//!     pub fn destroy(&self) {
100//!         self.runner.destroy();
101//!     }
102//!
103//!     /// Example on how to call into your app from JavaScript.
104//!     #[wasm_bindgen]
105//!     pub fn example(&self) {
106//!         if let Some(app) = self.runner.app_mut::<MyEguiApp>() {
107//!             app.example();
108//!         }
109//!     }
110//!
111//!     /// The JavaScript can check whether or not your app has crashed:
112//!     #[wasm_bindgen]
113//!     pub fn has_panicked(&self) -> bool {
114//!         self.runner.has_panicked()
115//!     }
116//!
117//!     #[wasm_bindgen]
118//!     pub fn panic_message(&self) -> Option<String> {
119//!         self.runner.panic_summary().map(|s| s.message())
120//!     }
121//!
122//!     #[wasm_bindgen]
123//!     pub fn panic_callstack(&self) -> Option<String> {
124//!         self.runner.panic_summary().map(|s| s.callstack())
125//!     }
126//! }
127//! ```
128//!
129//! ## Feature flags
130#![doc = document_features::document_features!()]
131//!
132//! ## Instrumentation
133//! This crate supports using the [profiling](https://crates.io/crates/profiling) crate for instrumentation.
134//! You can enable features on the profiling crates in your application to add instrumentation for all
135//! crates that support it, including egui. See the profiling crate docs for more information.
136//! ```toml
137//! [dependencies]
138//! profiling = "1.0"
139//! [features]
140//! profile-with-puffin = ["profiling/profile-with-puffin"]
141//! ```
142//!
143
144#![warn(missing_docs)] // let's keep eframe well-documented
145
146// Limitation imposed by `accesskit_winit`:
147// https://github.com/AccessKit/accesskit/tree/accesskit-v0.18.0/platforms/winit#android-activity-compatibility`
148#[cfg(all(
149    target_os = "android",
150    feature = "accesskit",
151    feature = "android-native-activity"
152))]
153compile_error!("`accesskit` feature is only available with `android-game-activity`");
154
155// Re-export all useful libraries:
156pub use {egui, egui::emath, egui::epaint};
157
158#[cfg(feature = "glow")]
159pub use {egui_glow, glow};
160
161#[cfg(feature = "wgpu_no_default_features")]
162pub use {egui_wgpu, egui_wgpu::SurfaceConfig, egui_wgpu::WgpuConfiguration, egui_wgpu::wgpu};
163
164mod epi;
165
166// Re-export everything in `epi` so `eframe` users don't have to care about what `epi` is:
167pub use epi::*;
168
169pub(crate) mod stopwatch;
170
171// ----------------------------------------------------------------------------
172// When compiling for web
173
174#[cfg(target_arch = "wasm32")]
175pub use wasm_bindgen;
176
177#[cfg(target_arch = "wasm32")]
178pub use web_sys;
179
180#[cfg(target_arch = "wasm32")]
181pub mod web;
182
183#[cfg(target_arch = "wasm32")]
184pub use web::{WebLogger, WebRunner};
185
186// ----------------------------------------------------------------------------
187// When compiling natively
188
189#[cfg(not(target_arch = "wasm32"))]
190#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))]
191mod native;
192
193#[cfg(target_os = "macos")]
194pub use native::macos::WindowChromeMetrics;
195
196#[cfg(not(target_arch = "wasm32"))]
197#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))]
198pub use native::run::EframeWinitApplication;
199
200#[cfg(not(any(target_arch = "wasm32", target_os = "ios")))]
201#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))]
202pub use native::run::EframePumpStatus;
203
204#[cfg(not(target_arch = "wasm32"))]
205#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))]
206#[cfg(feature = "persistence")]
207pub use native::file_storage::storage_dir;
208
209#[cfg(not(target_arch = "wasm32"))]
210pub mod icon_data;
211
212// ----------------------------------------------------------------------------
213
214/// Attach an [`egui_inspection::InspectionPlugin`] to `ctx` when enabled via the environment.
215#[cfg(all(feature = "inspection", not(target_arch = "wasm32")))]
216pub(crate) fn maybe_attach_inspection_plugin(ctx: &egui::Context, label: Option<String>) {
217    match egui_inspection::attach_from_env(ctx, label) {
218        Ok(true) => log::info!("egui_inspection plugin attached"),
219        Ok(false) => {}
220        Err(err) => log::warn!("egui_inspection attach failed: {err}"),
221    }
222}
223
224/// Fallback for native builds without the `inspection` feature. Logs warning if inspection env
225/// var was set.
226#[cfg(all(
227    not(feature = "inspection"),
228    not(target_arch = "wasm32"),
229    any(feature = "glow", feature = "wgpu_no_default_features")
230))]
231pub(crate) fn maybe_attach_inspection_plugin(_ctx: &egui::Context, _label: Option<String>) {
232    if let Ok(value) = std::env::var("EGUI_INSPECTION")
233        && value != "0"
234        && value != "false"
235        && !value.is_empty()
236    {
237        log::warn!("Inspection env var set but app was compiled without eframe/inspection feature");
238    }
239}
240
241/// This is how you start a native (desktop) app.
242///
243/// The first argument is name of your app, which is an identifier
244/// used for the save location of persistence (see [`App::save`]).
245/// It is also used as the application id on wayland.
246/// If you set no title on the viewport, the app id will be used
247/// as the title.
248///
249/// For details about application ID conventions, see the
250/// [Desktop Entry Spec](https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#desktop-file-id)
251///
252/// Call from `fn main` like this:
253/// ``` no_run
254/// use eframe::egui;
255///
256/// fn main() -> eframe::Result {
257///     let native_options = eframe::NativeOptions::default();
258///     eframe::run_native("MyApp", native_options, Box::new(|cc| Ok(Box::new(MyEguiApp::new(cc)))))
259/// }
260///
261/// #[derive(Default)]
262/// struct MyEguiApp {}
263///
264/// impl MyEguiApp {
265///     fn new(cc: &eframe::CreationContext<'_>) -> Self {
266///         // Customize egui here with cc.egui_ctx.set_fonts and cc.egui_ctx.set_global_style.
267///         // Restore app state using cc.storage (requires the "persistence" feature).
268///         // Use the cc.gl (a glow::Context) to create graphics shaders and buffers that you can use
269///         // for e.g. egui::PaintCallback.
270///         Self::default()
271///     }
272/// }
273///
274/// impl eframe::App for MyEguiApp {
275///    fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) {
276///        egui::CentralPanel::default().show(ui, |ui| {
277///            ui.heading("Hello World!");
278///        });
279///    }
280/// }
281/// ```
282///
283/// # Errors
284/// This function can fail if we fail to set up a graphics context.
285#[cfg(not(target_arch = "wasm32"))]
286#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))]
287#[allow(clippy::allow_attributes, clippy::needless_pass_by_value)]
288pub fn run_native(
289    app_name: &str,
290    native_options: NativeOptions,
291    app_creator: AppCreator<'_>,
292) -> Result {
293    run_native_ext(app_name, native_options, None, app_creator)
294}
295
296/// Like [`run_native`], but lets you supply a pre-existing [`egui::Context`].
297///
298/// If `egui_ctx` is `Some`, that context will be used by eframe instead of creating a fresh one.
299/// If it is `None`, eframe creates a new context (same behavior as [`run_native`]).
300///
301/// # Errors
302/// This function can fail if we fail to set up a graphics context.
303#[cfg(not(target_arch = "wasm32"))]
304#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))]
305#[allow(clippy::allow_attributes, clippy::needless_pass_by_value)]
306pub fn run_native_ext(
307    app_name: &str,
308    mut native_options: NativeOptions,
309    egui_ctx: Option<egui::Context>,
310    app_creator: AppCreator<'_>,
311) -> Result {
312    let renderer = init_native(app_name, &mut native_options);
313
314    match renderer {
315        #[cfg(feature = "glow")]
316        Renderer::Glow => {
317            log::debug!("Using the glow renderer");
318            native::run::run_glow(app_name, native_options, egui_ctx, app_creator)
319        }
320
321        #[cfg(feature = "wgpu_no_default_features")]
322        Renderer::Wgpu => {
323            log::debug!("Using the wgpu renderer");
324            native::run::run_wgpu(app_name, native_options, egui_ctx, app_creator)
325        }
326    }
327}
328
329/// Provides a proxy for your native eframe application to run on your own event loop.
330///
331/// See `run_native` for details about `app_name`.
332///
333/// Call from `fn main` like this:
334/// ``` no_run
335/// use eframe::{egui, UserEvent};
336/// use winit::event_loop::{ControlFlow, EventLoop};
337///
338/// fn main() -> eframe::Result {
339///     let native_options = eframe::NativeOptions::default();
340///     let eventloop = EventLoop::<UserEvent>::with_user_event().build()?;
341///     eventloop.set_control_flow(ControlFlow::Poll);
342///
343///     let mut winit_app = eframe::create_native(
344///         "MyExtApp",
345///         native_options,
346///         Box::new(|cc| Ok(Box::new(MyEguiApp::new(cc)))),
347///         &eventloop,
348///     );
349///
350///     eventloop.run_app(&mut winit_app)?;
351///
352///     Ok(())
353/// }
354///
355/// #[derive(Default)]
356/// struct MyEguiApp {}
357///
358/// impl MyEguiApp {
359///     fn new(cc: &eframe::CreationContext<'_>) -> Self {
360///         Self::default()
361///     }
362/// }
363///
364/// impl eframe::App for MyEguiApp {
365///    fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) {
366///        egui::CentralPanel::default().show(ui, |ui| {
367///            ui.heading("Hello World!");
368///        });
369///    }
370/// }
371/// ```
372///
373/// See the `external_eventloop` example for a more complete example.
374#[cfg(not(target_arch = "wasm32"))]
375#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))]
376pub fn create_native<'a>(
377    app_name: &str,
378    mut native_options: NativeOptions,
379    app_creator: AppCreator<'a>,
380    event_loop: &winit::event_loop::EventLoop<UserEvent>,
381) -> EframeWinitApplication<'a> {
382    let renderer = init_native(app_name, &mut native_options);
383
384    match renderer {
385        #[cfg(feature = "glow")]
386        Renderer::Glow => {
387            log::debug!("Using the glow renderer");
388            EframeWinitApplication::new(native::run::create_glow(
389                app_name,
390                native_options,
391                app_creator,
392                event_loop,
393            ))
394        }
395
396        #[cfg(feature = "wgpu_no_default_features")]
397        Renderer::Wgpu => {
398            log::debug!("Using the wgpu renderer");
399            EframeWinitApplication::new(native::run::create_wgpu(
400                app_name,
401                native_options,
402                app_creator,
403                event_loop,
404            ))
405        }
406    }
407}
408
409#[cfg(not(target_arch = "wasm32"))]
410#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))]
411fn init_native(app_name: &str, native_options: &mut NativeOptions) -> Renderer {
412    #[cfg(not(feature = "__screenshot"))]
413    assert!(
414        std::env::var("EFRAME_SCREENSHOT_TO").is_err(),
415        "EFRAME_SCREENSHOT_TO found without compiling with the '__screenshot' feature"
416    );
417
418    if native_options.viewport.title.is_none() {
419        native_options.viewport.title = Some(app_name.to_owned());
420    }
421    if native_options.viewport.app_id.is_none() {
422        native_options.viewport.app_id = Some(app_name.to_owned());
423    }
424
425    let renderer = native_options.renderer;
426
427    #[cfg(all(feature = "glow", feature = "wgpu_no_default_features"))]
428    {
429        match native_options.renderer {
430            Renderer::Glow => "glow",
431            Renderer::Wgpu => "wgpu",
432        };
433        log::info!("Both the glow and wgpu renderers are available. Using {renderer}.");
434    }
435
436    renderer
437}
438
439// ----------------------------------------------------------------------------
440
441/// The simplest way to get started when writing a native app.
442///
443/// This does NOT support persistence of custom user data. For that you need to use [`run_native`].
444/// However, it DOES support persistence of egui data (window positions and sizes, how far the user has scrolled in a
445/// [`ScrollArea`](egui::ScrollArea), etc.) if the persistence feature is enabled.
446///
447/// # Example
448/// ``` no_run
449/// fn main() -> eframe::Result {
450///     // Our application state:
451///     let mut name = "Arthur".to_owned();
452///     let mut age = 42;
453///
454///     let options = eframe::NativeOptions::default();
455///     eframe::run_ui_native("My egui App", options, move |ui, _frame| {
456///         // Wrap everything in a CentralPanel so we get some margins and a background color:
457///         egui::CentralPanel::default().show(ui, |ui| {
458///             ui.heading("My egui Application");
459///             ui.horizontal(|ui| {
460///                 let name_label = ui.label("Your name: ");
461///                 ui.text_edit_singleline(&mut name)
462///                     .labelled_by(name_label.id);
463///             });
464///             ui.add(egui::Slider::new(&mut age, 0..=120).text("age"));
465///             if ui.button("Increment").clicked() {
466///                 age += 1;
467///             }
468///             ui.label(format!("Hello '{name}', age {age}"));
469///         });
470///     })
471/// }
472/// ```
473///
474/// # Errors
475/// This function can fail if we fail to set up a graphics context.
476#[cfg(not(target_arch = "wasm32"))]
477#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))]
478pub fn run_ui_native(
479    app_name: &str,
480    native_options: NativeOptions,
481    ui_fun: impl FnMut(&mut egui::Ui, &mut Frame) + 'static,
482) -> Result {
483    struct SimpleApp<U> {
484        ui_fun: U,
485    }
486
487    impl<U: FnMut(&mut egui::Ui, &mut Frame) + 'static> App for SimpleApp<U> {
488        fn ui(&mut self, ui: &mut egui::Ui, frame: &mut Frame) {
489            (self.ui_fun)(ui, frame);
490        }
491    }
492
493    run_native(
494        app_name,
495        native_options,
496        Box::new(|_cc| Ok(Box::new(SimpleApp { ui_fun }))),
497    )
498}
499
500// ----------------------------------------------------------------------------
501
502/// The different problems that can occur when trying to run `eframe`.
503#[derive(Debug)]
504pub enum Error {
505    /// Something went wrong in user code when creating the app.
506    AppCreation(Box<dyn std::error::Error + Send + Sync>),
507
508    /// An error from [`winit`].
509    #[cfg(not(target_arch = "wasm32"))]
510    Winit(winit::error::OsError),
511
512    /// An error from [`winit::event_loop::EventLoop`].
513    #[cfg(not(target_arch = "wasm32"))]
514    WinitEventLoop(winit::error::EventLoopError),
515
516    /// An error from [`glutin`] when using [`glow`].
517    #[cfg(all(feature = "glow", not(target_arch = "wasm32")))]
518    Glutin(glutin::error::Error),
519
520    /// An error from [`glutin`] when using [`glow`].
521    #[cfg(all(feature = "glow", not(target_arch = "wasm32")))]
522    NoGlutinConfigs(glutin::config::ConfigTemplate, Box<dyn std::error::Error>),
523
524    /// An error from [`glutin`] when using [`glow`].
525    #[cfg(feature = "glow")]
526    OpenGL(egui_glow::PainterError),
527
528    /// An error from [`wgpu`].
529    #[cfg(feature = "wgpu_no_default_features")]
530    Wgpu(egui_wgpu::WgpuError),
531}
532
533impl std::error::Error for Error {}
534
535#[cfg(not(target_arch = "wasm32"))]
536impl From<winit::error::OsError> for Error {
537    #[inline]
538    fn from(err: winit::error::OsError) -> Self {
539        Self::Winit(err)
540    }
541}
542
543#[cfg(not(target_arch = "wasm32"))]
544impl From<winit::error::EventLoopError> for Error {
545    #[inline]
546    fn from(err: winit::error::EventLoopError) -> Self {
547        Self::WinitEventLoop(err)
548    }
549}
550
551#[cfg(all(feature = "glow", not(target_arch = "wasm32")))]
552impl From<glutin::error::Error> for Error {
553    #[inline]
554    fn from(err: glutin::error::Error) -> Self {
555        Self::Glutin(err)
556    }
557}
558
559#[cfg(feature = "glow")]
560impl From<egui_glow::PainterError> for Error {
561    #[inline]
562    fn from(err: egui_glow::PainterError) -> Self {
563        Self::OpenGL(err)
564    }
565}
566
567#[cfg(feature = "wgpu_no_default_features")]
568impl From<egui_wgpu::WgpuError> for Error {
569    #[inline]
570    fn from(err: egui_wgpu::WgpuError) -> Self {
571        Self::Wgpu(err)
572    }
573}
574
575impl std::fmt::Display for Error {
576    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
577        match self {
578            Self::AppCreation(err) => write!(f, "app creation error: {err}"),
579
580            #[cfg(not(target_arch = "wasm32"))]
581            Self::Winit(err) => {
582                write!(f, "winit error: {err}")
583            }
584
585            #[cfg(not(target_arch = "wasm32"))]
586            Self::WinitEventLoop(err) => {
587                write!(f, "winit EventLoopError: {err}")
588            }
589
590            #[cfg(all(feature = "glow", not(target_arch = "wasm32")))]
591            Self::Glutin(err) => {
592                write!(f, "glutin error: {err}")
593            }
594
595            #[cfg(all(feature = "glow", not(target_arch = "wasm32")))]
596            Self::NoGlutinConfigs(template, err) => {
597                write!(
598                    f,
599                    "Found no glutin configs matching the template: {template:?}. Error: {err}"
600                )
601            }
602
603            #[cfg(feature = "glow")]
604            Self::OpenGL(err) => {
605                write!(f, "egui_glow: {err}")
606            }
607
608            #[cfg(feature = "wgpu_no_default_features")]
609            Self::Wgpu(err) => {
610                write!(f, "WGPU error: {err}")
611            }
612        }
613    }
614}
615
616/// Short for `Result<T, eframe::Error>`.
617pub type Result<T = (), E = Error> = std::result::Result<T, E>;