epi/
lib.rs

1//! Backend-agnostic interface for writing apps using [`egui`].
2//!
3//! `epi` provides interfaces for window management and serialization.
4//! An app written for `epi` can then be plugged into [`eframe`](https://docs.rs/eframe),
5//! the egui framework crate.
6//!
7//! Start by looking at the [`App`] trait, and implement [`App::update`].
8
9// Forbid warnings in release builds:
10#![cfg_attr(not(debug_assertions), deny(warnings))]
11#![forbid(unsafe_code)]
12#![warn(
13    clippy::all,
14    clippy::await_holding_lock,
15    clippy::char_lit_as_u8,
16    clippy::checked_conversions,
17    clippy::dbg_macro,
18    clippy::debug_assert_with_mut_call,
19    clippy::disallowed_method,
20    clippy::doc_markdown,
21    clippy::empty_enum,
22    clippy::enum_glob_use,
23    clippy::exit,
24    clippy::expl_impl_clone_on_copy,
25    clippy::explicit_deref_methods,
26    clippy::explicit_into_iter_loop,
27    clippy::fallible_impl_from,
28    clippy::filter_map_next,
29    clippy::flat_map_option,
30    clippy::float_cmp_const,
31    clippy::fn_params_excessive_bools,
32    clippy::from_iter_instead_of_collect,
33    clippy::if_let_mutex,
34    clippy::implicit_clone,
35    clippy::imprecise_flops,
36    clippy::inefficient_to_string,
37    clippy::invalid_upcast_comparisons,
38    clippy::large_digit_groups,
39    clippy::large_stack_arrays,
40    clippy::large_types_passed_by_value,
41    clippy::let_unit_value,
42    clippy::linkedlist,
43    clippy::lossy_float_literal,
44    clippy::macro_use_imports,
45    clippy::manual_ok_or,
46    clippy::map_err_ignore,
47    clippy::map_flatten,
48    clippy::map_unwrap_or,
49    clippy::match_on_vec_items,
50    clippy::match_same_arms,
51    clippy::match_wild_err_arm,
52    clippy::match_wildcard_for_single_variants,
53    clippy::mem_forget,
54    clippy::mismatched_target_os,
55    clippy::missing_errors_doc,
56    clippy::missing_safety_doc,
57    clippy::mut_mut,
58    clippy::mutex_integer,
59    clippy::needless_borrow,
60    clippy::needless_continue,
61    clippy::needless_for_each,
62    clippy::needless_pass_by_value,
63    clippy::option_option,
64    clippy::path_buf_push_overwrite,
65    clippy::ptr_as_ptr,
66    clippy::ref_option_ref,
67    clippy::rest_pat_in_fully_bound_structs,
68    clippy::same_functions_in_if_condition,
69    clippy::semicolon_if_nothing_returned,
70    clippy::single_match_else,
71    clippy::string_add_assign,
72    clippy::string_add,
73    clippy::string_lit_as_bytes,
74    clippy::string_to_string,
75    clippy::todo,
76    clippy::trait_duplication_in_bounds,
77    clippy::unimplemented,
78    clippy::unnested_or_patterns,
79    clippy::unused_self,
80    clippy::useless_transmute,
81    clippy::verbose_file_reads,
82    clippy::zero_sized_map_values,
83    future_incompatible,
84    nonstandard_style,
85    rust_2018_idioms,
86    rustdoc::missing_crate_level_docs
87)]
88#![allow(clippy::float_cmp)]
89#![allow(clippy::manual_range_contains)]
90#![warn(missing_docs)] // Let's keep `epi` well-documented.
91
92/// File storage which can be used by native backends.
93#[cfg(feature = "file_storage")]
94pub mod file_storage;
95
96pub use egui; // Re-export for user convenience
97
98use std::sync::{Arc, Mutex};
99
100// ----------------------------------------------------------------------------
101
102/// Implement this trait to write apps that can be compiled both natively using the [`egui_glium`](https://github.com/emilk/egui/tree/master/egui_glium) crate,
103/// and deployed as a web site using the [`egui_web`](https://github.com/emilk/egui/tree/master/egui_web) crate.
104pub trait App {
105    /// Called each time the UI needs repainting, which may be many times per second.
106    ///
107    /// Put your widgets into a [`egui::SidePanel`], [`egui::TopBottomPanel`], [`egui::CentralPanel`], [`egui::Window`] or [`egui::Area`].
108    ///
109    /// The [`egui::Context`] and [`Frame`] can be cloned and saved if you like.
110    ///
111    /// To force a repaint, call either [`egui::Context::request_repaint`] during the call to `update`,
112    /// or call [`Frame::request_repaint`] at any time (e.g. from another thread).
113    fn update(&mut self, ctx: &egui::Context, frame: &Frame);
114
115    /// Called once before the first frame.
116    ///
117    /// Allows you to do setup code, e.g to call [`egui::Context::set_fonts`],
118    /// [`egui::Context::set_visuals`] etc.
119    ///
120    /// Also allows you to restore state, if there is a storage (required the "persistence" feature).
121    fn setup(&mut self, _ctx: &egui::Context, _frame: &Frame, _storage: Option<&dyn Storage>) {}
122
123    /// Called on shutdown, and perhaps at regular intervals. Allows you to save state.
124    ///
125    /// Only called when the "persistence" feature is enabled.
126    ///
127    /// On web the states is stored to "Local Storage".
128    /// On native the path is picked using [`directories_next::ProjectDirs::data_dir`](https://docs.rs/directories-next/2.0.0/directories_next/struct.ProjectDirs.html#method.data_dir) which is:
129    /// * Linux:   `/home/UserName/.local/share/APPNAME`
130    /// * macOS:   `/Users/UserName/Library/Application Support/APPNAME`
131    /// * Windows: `C:\Users\UserName\AppData\Roaming\APPNAME`
132    ///
133    /// where `APPNAME` is what is returned by [`Self::name()`].
134    fn save(&mut self, _storage: &mut dyn Storage) {}
135
136    /// Called before an exit that can be aborted.
137    /// By returning `false` the exit will be aborted. To continue the exit return `true`.
138    ///
139    /// A scenario where this method will be run is after pressing the close button on a native
140    /// window, which allows you to ask the user whether they want to do something before exiting.
141    /// See the example `eframe/examples/confirm_exit.rs` for practical usage.
142    ///
143    /// It will _not_ be called on the web or when the window is forcefully closed.
144    fn on_exit_event(&mut self) -> bool {
145        true
146    }
147
148    /// Called once on shutdown (before or after [`Self::save`]). If you need to abort an exit use
149    /// [`Self::on_exit_event`]
150    fn on_exit(&mut self) {}
151
152    // ---------
153    // Settings:
154
155    /// The name of your App, used for the title bar of native windows
156    /// and the save location of persistence (see [`Self::save`]).
157    fn name(&self) -> &str;
158
159    /// Time between automatic calls to [`Self::save`]
160    fn auto_save_interval(&self) -> std::time::Duration {
161        std::time::Duration::from_secs(30)
162    }
163
164    /// The size limit of the web app canvas.
165    ///
166    /// By default the size if limited to 1024x2048.
167    ///
168    /// A larger canvas can lead to bad frame rates on some browsers on some platforms.
169    /// In particular, Firefox on Mac and Linux is really bad at handling large WebGL canvases:
170    /// <https://bugzilla.mozilla.org/show_bug.cgi?id=1010527#c0> (unfixed since 2014).
171    fn max_size_points(&self) -> egui::Vec2 {
172        egui::Vec2::new(1024.0, 2048.0)
173    }
174
175    /// Background color for the app, e.g. what is sent to `gl.clearColor`.
176    /// This is the background of your windows if you don't set a central panel.
177    fn clear_color(&self) -> egui::Rgba {
178        // NOTE: a bright gray makes the shadows of the windows look weird.
179        // We use a bit of transparency so that if the user switches on the
180        // `transparent()` option they get immediate results.
181        egui::Color32::from_rgba_unmultiplied(12, 12, 12, 180).into()
182    }
183
184    /// Controls whether or not the native window position and size will be
185    /// persisted (only if the "persistence" feature is enabled).
186    fn persist_native_window(&self) -> bool {
187        true
188    }
189
190    /// Controls whether or not the egui memory (window positions etc) will be
191    /// persisted (only if the "persistence" feature is enabled).
192    fn persist_egui_memory(&self) -> bool {
193        true
194    }
195
196    /// If `true` a warm-up call to [`Self::update`] will be issued where
197    /// `ctx.memory().everything_is_visible()` will be set to `true`.
198    ///
199    /// This can help pre-caching resources loaded by different parts of the UI, preventing stutter later on.
200    ///
201    /// In this warm-up call, all painted shapes will be ignored.
202    ///
203    /// The default is `false`, and it is unlikely you will want to change this.
204    fn warm_up_enabled(&self) -> bool {
205        false
206    }
207}
208
209/// Options controlling the behavior of a native window.
210///
211/// Only a single native window is currently supported.
212#[derive(Clone)]
213pub struct NativeOptions {
214    /// Sets whether or not the window will always be on top of other windows.
215    pub always_on_top: bool,
216
217    /// Show window in maximized mode
218    pub maximized: bool,
219
220    /// On desktop: add window decorations (i.e. a frame around your app)?
221    /// If false it will be difficult to move and resize the app.
222    pub decorated: bool,
223
224    /// On Windows: enable drag and drop support.
225    /// Default is `false` to avoid issues with crates such as [`cpal`](https://github.com/RustAudio/cpal) which
226    /// will hang when combined with drag-and-drop.
227    /// See <https://github.com/rust-windowing/winit/issues/1255>.
228    pub drag_and_drop_support: bool,
229
230    /// The application icon, e.g. in the Windows task bar etc.
231    pub icon_data: Option<IconData>,
232
233    /// The initial (inner) position of the native window in points (logical pixels).
234    pub initial_window_pos: Option<egui::Pos2>,
235
236    /// The initial inner size of the native window in points (logical pixels).
237    pub initial_window_size: Option<egui::Vec2>,
238
239    /// The minimum inner window size
240    pub min_window_size: Option<egui::Vec2>,
241
242    /// The maximum inner window size
243    pub max_window_size: Option<egui::Vec2>,
244
245    /// Should the app window be resizable?
246    pub resizable: bool,
247
248    /// On desktop: make the window transparent.
249    /// You control the transparency with [`App::clear_color()`].
250    /// You should avoid having a [`egui::CentralPanel`], or make sure its frame is also transparent.
251    pub transparent: bool,
252}
253
254impl Default for NativeOptions {
255    fn default() -> Self {
256        Self {
257            always_on_top: false,
258            maximized: false,
259            decorated: true,
260            drag_and_drop_support: false,
261            icon_data: None,
262            initial_window_pos: None,
263            initial_window_size: None,
264            min_window_size: None,
265            max_window_size: None,
266            resizable: true,
267            transparent: false,
268        }
269    }
270}
271
272/// Image data for the icon.
273#[derive(Clone)]
274pub struct IconData {
275    /// RGBA pixels, unmultiplied.
276    pub rgba: Vec<u8>,
277
278    /// Image width. This should be a multiple of 4.
279    pub width: u32,
280
281    /// Image height. This should be a multiple of 4.
282    pub height: u32,
283}
284
285/// Represents the surroundings of your app.
286///
287/// It provides methods to inspect the surroundings (are we on the web?),
288/// allocate textures, and change settings (e.g. window size).
289///
290/// [`Frame`] is cheap to clone and is safe to pass to other threads.
291#[derive(Clone)]
292pub struct Frame(pub Arc<Mutex<backend::FrameData>>);
293
294impl Frame {
295    /// Create a `Frame` - called by the integration.
296    #[doc(hidden)]
297    pub fn new(frame_data: backend::FrameData) -> Self {
298        Self(Arc::new(Mutex::new(frame_data)))
299    }
300
301    /// Access the underlying [`backend::FrameData`].
302    #[doc(hidden)]
303    #[inline]
304    pub fn lock(&self) -> std::sync::MutexGuard<'_, backend::FrameData> {
305        self.0.lock().unwrap()
306    }
307
308    /// True if you are in a web environment.
309    pub fn is_web(&self) -> bool {
310        self.lock().info.web_info.is_some()
311    }
312
313    /// Information about the integration.
314    pub fn info(&self) -> IntegrationInfo {
315        self.lock().info.clone()
316    }
317
318    /// Signal the app to stop/exit/quit the app (only works for native apps, not web apps).
319    /// The framework will not quit immediately, but at the end of the this frame.
320    pub fn quit(&self) {
321        self.lock().output.quit = true;
322    }
323
324    /// Set the desired inner size of the window (in egui points).
325    pub fn set_window_size(&self, size: egui::Vec2) {
326        self.lock().output.window_size = Some(size);
327    }
328
329    /// Set the desired title of the window.
330    pub fn set_window_title(&self, title: &str) {
331        self.lock().output.window_title = Some(title.to_owned());
332    }
333
334    /// Set whether to show window decorations (i.e. a frame around you app).
335    /// If false it will be difficult to move and resize the app.
336    pub fn set_decorations(&self, decorated: bool) {
337        self.lock().output.decorated = Some(decorated);
338    }
339
340    /// When called, the native window will follow the
341    /// movement of the cursor while the primary mouse button is down.
342    ///
343    /// Does not work on the web.
344    pub fn drag_window(&self) {
345        self.lock().output.drag_window = true;
346    }
347
348    /// This signals the [`egui`] integration that a repaint is required.
349    ///
350    /// Call this e.g. when a background process finishes in an async context and/or background thread.
351    pub fn request_repaint(&self) {
352        self.lock().repaint_signal.request_repaint();
353    }
354
355    /// for integrations only: call once per frame
356    pub fn take_app_output(&self) -> crate::backend::AppOutput {
357        std::mem::take(&mut self.lock().output)
358    }
359}
360
361/// Information about the web environment (if applicable).
362#[derive(Clone, Debug)]
363pub struct WebInfo {
364    /// Information about the URL.
365    pub location: Location,
366}
367
368/// Information about the URL.
369///
370/// Everything has been percent decoded (`%20` -> ` ` etc).
371#[derive(Clone, Debug)]
372pub struct Location {
373    /// The full URL (`location.href`) without the hash.
374    ///
375    /// Example: `"http://www.example.com:80/index.html?foo=bar"`.
376    pub url: String,
377
378    /// `location.protocol`
379    ///
380    /// Example: `"http:"`.
381    pub protocol: String,
382
383    /// `location.host`
384    ///
385    /// Example: `"example.com:80"`.
386    pub host: String,
387
388    /// `location.hostname`
389    ///
390    /// Example: `"example.com"`.
391    pub hostname: String,
392
393    /// `location.port`
394    ///
395    /// Example: `"80"`.
396    pub port: String,
397
398    /// The "#fragment" part of "www.example.com/index.html?query#fragment".
399    ///
400    /// Note that the leading `#` is included in the string.
401    /// Also known as "hash-link" or "anchor".
402    pub hash: String,
403
404    /// The "query" part of "www.example.com/index.html?query#fragment".
405    ///
406    /// Note that the leading `?` is NOT included in the string.
407    ///
408    /// Use [`Self::web_query_map]` to get the parsed version of it.
409    pub query: String,
410
411    /// The parsed "query" part of "www.example.com/index.html?query#fragment".
412    ///
413    /// "foo=42&bar%20" is parsed as `{"foo": "42",  "bar ": ""}`
414    pub query_map: std::collections::BTreeMap<String, String>,
415
416    /// `location.origin`
417    ///
418    /// Example: `"http://www.example.com:80"`.
419    pub origin: String,
420}
421
422/// Information about the integration passed to the use app each frame.
423#[derive(Clone, Debug)]
424pub struct IntegrationInfo {
425    /// The name of the integration, e.g. `egui_web`, `egui_glium`, `egui_glow`
426    pub name: &'static str,
427
428    /// If the app is running in a Web context, this returns information about the environment.
429    pub web_info: Option<WebInfo>,
430
431    /// Does the system prefer dark mode (over light mode)?
432    /// `None` means "don't know".
433    pub prefer_dark_mode: Option<bool>,
434
435    /// Seconds of cpu usage (in seconds) of UI code on the previous frame.
436    /// `None` if this is the first frame.
437    pub cpu_usage: Option<f32>,
438
439    /// The OS native pixels-per-point
440    pub native_pixels_per_point: Option<f32>,
441}
442
443/// Abstraction for platform dependent texture reference
444pub trait NativeTexture {
445    /// The native texture type.
446    type Texture;
447
448    /// Bind native texture to an egui texture id.
449    fn register_native_texture(&mut self, native: Self::Texture) -> egui::TextureId;
450
451    /// Change what texture the given id refers to.
452    fn replace_native_texture(&mut self, id: egui::TextureId, replacing: Self::Texture);
453}
454
455// ----------------------------------------------------------------------------
456
457/// A place where you can store custom data in a way that persists when you restart the app.
458///
459/// On the web this is backed by [local storage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage).
460/// On desktop this is backed by the file system.
461pub trait Storage {
462    /// Get the value for the given key.
463    fn get_string(&self, key: &str) -> Option<String>;
464    /// Set the value for the given key.
465    fn set_string(&mut self, key: &str, value: String);
466
467    /// write-to-disk or similar
468    fn flush(&mut self);
469}
470
471/// Stores nothing.
472#[derive(Clone, Default)]
473pub struct DummyStorage {}
474
475impl Storage for DummyStorage {
476    fn get_string(&self, _key: &str) -> Option<String> {
477        None
478    }
479    fn set_string(&mut self, _key: &str, _value: String) {}
480    fn flush(&mut self) {}
481}
482
483/// Get and deserialize the [RON](https://github.com/ron-rs/ron) stored at the given key.
484#[cfg(feature = "ron")]
485pub fn get_value<T: serde::de::DeserializeOwned>(storage: &dyn Storage, key: &str) -> Option<T> {
486    storage
487        .get_string(key)
488        .and_then(|value| ron::from_str(&value).ok())
489}
490
491/// Serialize the given value as [RON](https://github.com/ron-rs/ron) and store with the given key.
492#[cfg(feature = "ron")]
493pub fn set_value<T: serde::Serialize>(storage: &mut dyn Storage, key: &str, value: &T) {
494    storage.set_string(key, ron::ser::to_string(value).unwrap());
495}
496
497/// [`Storage`] key used for app
498pub const APP_KEY: &str = "app";
499
500// ----------------------------------------------------------------------------
501
502/// You only need to look here if you are writing a backend for `epi`.
503pub mod backend {
504    use super::*;
505
506    /// How to signal the [`egui`] integration that a repaint is required.
507    pub trait RepaintSignal: Send + Sync {
508        /// This signals the [`egui`] integration that a repaint is required.
509        ///
510        /// Call this e.g. when a background process finishes in an async context and/or background thread.
511        fn request_repaint(&self);
512    }
513
514    /// The data required by [`Frame`] each frame.
515    pub struct FrameData {
516        /// Information about the integration.
517        pub info: IntegrationInfo,
518
519        /// Where the app can issue commands back to the integration.
520        pub output: AppOutput,
521
522        /// If you need to request a repaint from another thread, clone this and send it to that other thread.
523        pub repaint_signal: std::sync::Arc<dyn RepaintSignal>,
524    }
525
526    /// Action that can be taken by the user app.
527    #[derive(Default)]
528    #[must_use]
529    pub struct AppOutput {
530        /// Set to `true` to stop the app.
531        /// This does nothing for web apps.
532        pub quit: bool,
533
534        /// Set to some size to resize the outer window (e.g. glium window) to this size.
535        pub window_size: Option<egui::Vec2>,
536
537        /// Set to some string to rename the outer window (e.g. glium window) to this title.
538        pub window_title: Option<String>,
539
540        /// Set to some bool to change window decorations.
541        pub decorated: Option<bool>,
542
543        /// Set to true to drag window while primary mouse button is down.
544        pub drag_window: bool,
545    }
546}