diff --git a/crates/eframe/src/epi/mod.rs b/crates/eframe/src/epi/mod.rs index 6172bf42..85433e7a 100644 --- a/crates/eframe/src/epi/mod.rs +++ b/crates/eframe/src/epi/mod.rs @@ -118,7 +118,7 @@ pub trait App { /// /// Can be used from web to interact or other external context. /// - /// You need to implement this if you want to be able to access the application from JS using [`crate::web::backend::AppRunner`]. + /// You need to implement this if you want to be able to access the application from JS using [`crate::WebRunner::app_mut`]. /// /// This is needed because downcasting `Box` -> `Box` to get &`ConcreteApp` is not simple in current rust. /// diff --git a/crates/eframe/src/lib.rs b/crates/eframe/src/lib.rs index 63c7cdee..2972d672 100644 --- a/crates/eframe/src/lib.rs +++ b/crates/eframe/src/lib.rs @@ -50,7 +50,7 @@ //! #[derive(Clone)] //! #[wasm_bindgen] //! pub struct WebHandle { -//! runner: WebRunner, +//! runner: eframe::WebRunner, //! } //! //! # #[cfg(target_arch = "wasm32")] @@ -64,7 +64,7 @@ //! eframe::WebLogger::init(log::LevelFilter::Debug).ok(); //! //! Self { -//! runner: WebRunner::new(), +//! runner: eframe::WebRunner::new(), //! } //! } //! @@ -82,6 +82,7 @@ //! //! // The following are optional: //! +//! /// Shut down eframe and clean up resources. //! #[wasm_bindgen] //! pub fn destroy(&self) { //! self.runner.destroy(); @@ -121,6 +122,7 @@ #![cfg_attr(feature = "document-features", doc = document_features::document_features!())] //! +#![warn(missing_docs)] // let's keep eframe well-documented #![allow(clippy::needless_doctest_main)] // Re-export all useful libraries: @@ -296,23 +298,28 @@ pub fn run_simple_native( /// The different problems that can occur when trying to run `eframe`. #[derive(thiserror::Error, Debug)] pub enum Error { + /// An error from [`winit`]. #[cfg(not(target_arch = "wasm32"))] #[error("winit error: {0}")] Winit(#[from] winit::error::OsError), + /// An error from [`glutin`] when using [`glow`]. #[cfg(all(feature = "glow", not(target_arch = "wasm32")))] #[error("glutin error: {0}")] Glutin(#[from] glutin::error::Error), + /// An error from [`glutin`] when using [`glow`]. #[cfg(all(feature = "glow", not(target_arch = "wasm32")))] - #[error("Found no glutin configs matching the template: {0:?}. error: {1:?}")] + #[error("Found no glutin configs matching the template: {0:?}. Error: {1:?}")] NoGlutinConfigs(glutin::config::ConfigTemplate, Box), + /// An error from [`wgpu`]. #[cfg(feature = "wgpu")] #[error("WGPU error: {0}")] Wgpu(#[from] egui_wgpu::WgpuError), } +/// Short for `Result`. pub type Result = std::result::Result; // --------------------------------------------------------------------------- diff --git a/crates/eframe/src/native/run.rs b/crates/eframe/src/native/run.rs index 4a508db9..c1db4492 100644 --- a/crates/eframe/src/native/run.rs +++ b/crates/eframe/src/native/run.rs @@ -18,15 +18,19 @@ use super::epi_integration::{self, EpiIntegration}; // ---------------------------------------------------------------------------- +/// The custom even `eframe` uses with the [`winit`] event loop. #[derive(Debug)] pub enum UserEvent { + /// A repaint is requested. RequestRepaint { + /// When to repaint. when: Instant, /// What the frame number was when the repaint was _requested_. frame_nr: u64, }, + /// A request related to [`accesskit`](https://accesskit.dev/). #[cfg(feature = "accesskit")] AccessKitActionRequest(accesskit_winit::ActionRequestEvent), } diff --git a/crates/eframe/src/web/app_runner.rs b/crates/eframe/src/web/app_runner.rs index 4da93c52..6f4579b6 100644 --- a/crates/eframe/src/web/app_runner.rs +++ b/crates/eframe/src/web/app_runner.rs @@ -56,7 +56,7 @@ impl AppRunner { egui_ctx.set_os(egui::os::OperatingSystem::from_user_agent( &super::user_agent().unwrap_or_default(), )); - super::load_memory(&egui_ctx); + super::storage::load_memory(&egui_ctx); let theme = system_theme.unwrap_or(web_options.default_theme); egui_ctx.set_visuals(theme.egui_visuals()); @@ -140,7 +140,7 @@ impl AppRunner { pub fn save(&mut self) { if self.app.persist_egui_memory() { - super::save_memory(&self.egui_ctx); + super::storage::save_memory(&self.egui_ctx); } if let Some(storage) = self.frame.storage_mut() { self.app.save(storage); @@ -262,11 +262,11 @@ struct LocalStorage {} impl epi::Storage for LocalStorage { fn get_string(&self, key: &str) -> Option { - super::local_storage_get(key) + super::storage::local_storage_get(key) } fn set_string(&mut self, key: &str, value: String) { - super::local_storage_set(key, &value); + super::storage::local_storage_set(key, &value); } fn flush(&mut self) {} diff --git a/crates/eframe/src/web/backend.rs b/crates/eframe/src/web/backend.rs index dde1d897..8c5edec1 100644 --- a/crates/eframe/src/web/backend.rs +++ b/crates/eframe/src/web/backend.rs @@ -10,13 +10,14 @@ use super::percent_decode; /// Data gathered between frames. #[derive(Default)] -pub struct WebInput { +pub(crate) struct WebInput { /// Required because we don't get a position on touched pub latest_touch_pos: Option, /// Required to maintain a stable touch position for multi-touch gestures. pub latest_touch_pos_id: Option, + /// The raw input to `egui`. pub raw: egui::RawInput, } @@ -41,10 +42,8 @@ impl WebInput { // ---------------------------------------------------------------------------- -use std::sync::atomic::Ordering::SeqCst; - /// Stores when to do the next repaint. -pub struct NeedRepaint(Mutex); +pub(crate) struct NeedRepaint(Mutex); impl Default for NeedRepaint { fn default() -> Self { @@ -74,30 +73,14 @@ impl NeedRepaint { } } -pub struct IsDestroyed(std::sync::atomic::AtomicBool); - -impl Default for IsDestroyed { - fn default() -> Self { - Self(false.into()) - } -} - -impl IsDestroyed { - pub fn fetch(&self) -> bool { - self.0.load(SeqCst) - } - - pub fn set_true(&self) { - self.0.store(true, SeqCst); - } -} - // ---------------------------------------------------------------------------- +/// The User-Agent of the user's browser. pub fn user_agent() -> Option { web_sys::window()?.navigator().user_agent().ok() } +/// Get the [`epi::Location`] from the browser. pub fn web_location() -> epi::Location { let location = web_sys::window().unwrap().location(); diff --git a/crates/eframe/src/web/events.rs b/crates/eframe/src/web/events.rs index cbfa11a6..ea5a5bf0 100644 --- a/crates/eframe/src/web/events.rs +++ b/crates/eframe/src/web/events.rs @@ -29,7 +29,7 @@ fn paint_if_needed(runner: &mut AppRunner) -> Result<(), JsValue> { Ok(()) } -pub fn request_animation_frame(runner_ref: WebRunner) -> Result<(), JsValue> { +pub(crate) fn request_animation_frame(runner_ref: WebRunner) -> Result<(), JsValue> { let window = web_sys::window().unwrap(); let closure = Closure::once(move || paint_and_schedule(&runner_ref)); window.request_animation_frame(closure.as_ref().unchecked_ref())?; @@ -39,7 +39,7 @@ pub fn request_animation_frame(runner_ref: WebRunner) -> Result<(), JsValue> { // ------------------------------------------------------------------------ -pub fn install_document_events(runner_ref: &WebRunner) -> Result<(), JsValue> { +pub(crate) fn install_document_events(runner_ref: &WebRunner) -> Result<(), JsValue> { let document = web_sys::window().unwrap().document().unwrap(); { @@ -189,7 +189,7 @@ pub fn install_document_events(runner_ref: &WebRunner) -> Result<(), JsValue> { Ok(()) } -pub fn install_window_events(runner_ref: &WebRunner) -> Result<(), JsValue> { +pub(crate) fn install_window_events(runner_ref: &WebRunner) -> Result<(), JsValue> { let window = web_sys::window().unwrap(); // Save-on-close @@ -211,7 +211,7 @@ pub fn install_window_events(runner_ref: &WebRunner) -> Result<(), JsValue> { Ok(()) } -pub fn install_color_scheme_change_event(runner_ref: &WebRunner) -> Result<(), JsValue> { +pub(crate) fn install_color_scheme_change_event(runner_ref: &WebRunner) -> Result<(), JsValue> { let window = web_sys::window().unwrap(); if let Some(media_query_list) = prefers_color_scheme_dark(&window)? { @@ -230,7 +230,7 @@ pub fn install_color_scheme_change_event(runner_ref: &WebRunner) -> Result<(), J Ok(()) } -pub fn install_canvas_events(runner_ref: &WebRunner) -> Result<(), JsValue> { +pub(crate) fn install_canvas_events(runner_ref: &WebRunner) -> Result<(), JsValue> { let canvas = canvas_element(runner_ref.try_lock().unwrap().canvas_id()).unwrap(); { diff --git a/crates/eframe/src/web/mod.rs b/crates/eframe/src/web/mod.rs index 9c3920a5..252dddd3 100644 --- a/crates/eframe/src/web/mod.rs +++ b/crates/eframe/src/web/mod.rs @@ -3,16 +3,20 @@ #![allow(clippy::missing_errors_doc)] // So many `-> Result<_, JsValue>` mod app_runner; -pub mod backend; +mod backend; mod events; mod input; mod panic_handler; -pub mod screen_reader; -pub mod storage; mod text_agent; mod web_logger; mod web_runner; +/// Access to the browser screen reader. +pub mod screen_reader; + +/// Access to local browser storage. +pub mod storage; + pub(crate) use app_runner::AppRunner; pub use panic_handler::{PanicHandler, PanicSummary}; pub use web_logger::WebLogger; @@ -34,8 +38,6 @@ mod web_painter_wgpu; pub(crate) type ActiveWebPainter = web_painter_wgpu::WebPainterWgpu; pub use backend::*; -pub use events::*; -pub use storage::*; use egui::Vec2; use wasm_bindgen::prelude::*; @@ -59,15 +61,9 @@ pub fn now_sec() -> f64 { / 1000.0 } -#[allow(dead_code)] -pub fn screen_size_in_native_points() -> Option { - let window = web_sys::window()?; - Some(egui::vec2( - window.inner_width().ok()?.as_f64()? as f32, - window.inner_height().ok()?.as_f64()? as f32, - )) -} - +/// The native GUI scale factor, taking into account the browser zoom. +/// +/// Corresponds to [`window.devicePixelRatio`](https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio) in JavaScript. pub fn native_pixels_per_point() -> f32 { let pixels_per_point = web_sys::window().unwrap().device_pixel_ratio() as f32; if pixels_per_point > 0.0 && pixels_per_point.is_finite() { @@ -77,6 +73,9 @@ pub fn native_pixels_per_point() -> f32 { } } +/// Ask the browser about the preferred system theme. +/// +/// `None` means unknown. pub fn system_theme() -> Option { let dark_mode = prefers_color_scheme_dark(&web_sys::window()?) .ok()?? @@ -96,13 +95,13 @@ fn theme_from_dark_mode(dark_mode: bool) -> Theme { } } -pub fn canvas_element(canvas_id: &str) -> Option { +fn canvas_element(canvas_id: &str) -> Option { let document = web_sys::window()?.document()?; let canvas = document.get_element_by_id(canvas_id)?; canvas.dyn_into::().ok() } -pub fn canvas_element_or_die(canvas_id: &str) -> web_sys::HtmlCanvasElement { +fn canvas_element_or_die(canvas_id: &str) -> web_sys::HtmlCanvasElement { canvas_element(canvas_id) .unwrap_or_else(|| panic!("Failed to find canvas with id {canvas_id:?}")) } @@ -114,7 +113,7 @@ fn canvas_origin(canvas_id: &str) -> egui::Pos2 { egui::pos2(rect.left() as f32, rect.top() as f32) } -pub fn canvas_size_in_points(canvas_id: &str) -> egui::Vec2 { +fn canvas_size_in_points(canvas_id: &str) -> egui::Vec2 { let canvas = canvas_element(canvas_id).unwrap(); let pixels_per_point = native_pixels_per_point(); egui::vec2( @@ -123,7 +122,7 @@ pub fn canvas_size_in_points(canvas_id: &str) -> egui::Vec2 { ) } -pub fn resize_canvas_to_screen_size(canvas_id: &str, max_size_points: egui::Vec2) -> Option<()> { +fn resize_canvas_to_screen_size(canvas_id: &str, max_size_points: egui::Vec2) -> Option<()> { let canvas = canvas_element(canvas_id)?; let parent = canvas.parent_element()?; @@ -178,7 +177,8 @@ pub fn resize_canvas_to_screen_size(canvas_id: &str, max_size_points: egui::Vec2 // ---------------------------------------------------------------------------- -pub fn set_cursor_icon(cursor: egui::CursorIcon) -> Option<()> { +/// Set the cursor icon. +fn set_cursor_icon(cursor: egui::CursorIcon) -> Option<()> { let document = web_sys::window()?.document()?; document .body()? @@ -187,8 +187,9 @@ pub fn set_cursor_icon(cursor: egui::CursorIcon) -> Option<()> { .ok() } +/// Set the clipboard text. #[cfg(web_sys_unstable_apis)] -pub fn set_clipboard_text(s: &str) { +fn set_clipboard_text(s: &str) { if let Some(window) = web_sys::window() { if let Some(clipboard) = window.navigator().clipboard() { let promise = clipboard.write_text(s); @@ -245,6 +246,7 @@ fn cursor_web_name(cursor: egui::CursorIcon) -> &'static str { } } +/// Open the given url in the browser. pub fn open_url(url: &str, new_tab: bool) -> Option<()> { let name = if new_tab { "_blank" } else { "_self" }; @@ -267,6 +269,7 @@ pub fn location_hash() -> String { ) } +/// Percent-decodes a string. pub fn percent_decode(s: &str) -> String { percent_encoding::percent_decode_str(s) .decode_utf8_lossy() diff --git a/crates/eframe/src/web/panic_handler.rs b/crates/eframe/src/web/panic_handler.rs index a22bca9d..b379f775 100644 --- a/crates/eframe/src/web/panic_handler.rs +++ b/crates/eframe/src/web/panic_handler.rs @@ -65,16 +65,19 @@ pub struct PanicSummary { } impl PanicSummary { + /// Construct a summary from a panic. pub fn new(info: &std::panic::PanicInfo<'_>) -> Self { let message = info.to_string(); let callstack = Error::new().stack(); Self { message, callstack } } + /// The panic message. pub fn message(&self) -> String { self.message.clone() } + /// The backtrace. pub fn callstack(&self) -> String { self.callstack.clone() } diff --git a/crates/eframe/src/web/screen_reader.rs b/crates/eframe/src/web/screen_reader.rs index 30895723..f4b37f20 100644 --- a/crates/eframe/src/web/screen_reader.rs +++ b/crates/eframe/src/web/screen_reader.rs @@ -1,3 +1,4 @@ +/// Screen reader support. pub struct ScreenReader { #[cfg(feature = "tts")] tts: Option, @@ -29,10 +30,12 @@ impl Default for ScreenReader { } impl ScreenReader { + /// Speak the given text out loud. #[cfg(not(feature = "tts"))] #[allow(clippy::unused_self)] pub fn speak(&mut self, _text: &str) {} + /// Speak the given text out loud. #[cfg(feature = "tts")] pub fn speak(&mut self, text: &str) { if text.is_empty() { diff --git a/crates/eframe/src/web/storage.rs b/crates/eframe/src/web/storage.rs index 9ab8b042..4a2a5332 100644 --- a/crates/eframe/src/web/storage.rs +++ b/crates/eframe/src/web/storage.rs @@ -2,16 +2,18 @@ fn local_storage() -> Option { web_sys::window()?.local_storage().ok()? } +/// Read data from local storage. pub fn local_storage_get(key: &str) -> Option { local_storage().map(|storage| storage.get_item(key).ok())?? } +/// Write data to local storage. pub fn local_storage_set(key: &str, value: &str) { local_storage().map(|storage| storage.set_item(key, value)); } #[cfg(feature = "persistence")] -pub fn load_memory(ctx: &egui::Context) { +pub(crate) fn load_memory(ctx: &egui::Context) { if let Some(memory_string) = local_storage_get("egui_memory_ron") { match ron::from_str(&memory_string) { Ok(memory) => { @@ -25,10 +27,10 @@ pub fn load_memory(ctx: &egui::Context) { } #[cfg(not(feature = "persistence"))] -pub fn load_memory(_: &egui::Context) {} +pub(crate) fn load_memory(_: &egui::Context) {} #[cfg(feature = "persistence")] -pub fn save_memory(ctx: &egui::Context) { +pub(crate) fn save_memory(ctx: &egui::Context) { match ctx.memory(|mem| ron::to_string(mem)) { Ok(ron) => { local_storage_set("egui_memory_ron", &ron); @@ -40,4 +42,4 @@ pub fn save_memory(ctx: &egui::Context) { } #[cfg(not(feature = "persistence"))] -pub fn save_memory(_: &egui::Context) {} +pub(crate) fn save_memory(_: &egui::Context) {} diff --git a/crates/eframe/src/web/web_logger.rs b/crates/eframe/src/web/web_logger.rs index 09f73106..90dfc1b3 100644 --- a/crates/eframe/src/web/web_logger.rs +++ b/crates/eframe/src/web/web_logger.rs @@ -4,12 +4,14 @@ pub struct WebLogger { } impl WebLogger { - /// Pipe all [`log`] events to the web console. + /// Install a new `WebLogger`, piping all [`log`] events to the web console. pub fn init(filter: log::LevelFilter) -> Result<(), log::SetLoggerError> { log::set_max_level(filter); log::set_boxed_logger(Box::new(WebLogger::new(filter))) } + /// Create a new [`WebLogger`] with the given filter, + /// but don't install it. pub fn new(filter: log::LevelFilter) -> Self { Self { filter } } diff --git a/crates/eframe/src/web/web_runner.rs b/crates/eframe/src/web/web_runner.rs index 2c039544..ff7ae377 100644 --- a/crates/eframe/src/web/web_runner.rs +++ b/crates/eframe/src/web/web_runner.rs @@ -27,7 +27,7 @@ pub struct WebRunner { } impl WebRunner { - // Will install a panic handler that will catch and log any panics + /// Will install a panic handler that will catch and log any panics #[allow(clippy::new_without_default)] pub fn new() -> Self { #[cfg(not(web_sys_unstable_apis))] @@ -102,6 +102,7 @@ impl WebRunner { } } + /// Shut down eframe and clean up resources. pub fn destroy(&self) { self.unsubscribe_from_all_events();