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}