Improve documentation of `eframe`, especially for wasm32 (#3295)

* Improve documentation of `eframe`, especially for wasm32

* remove dead code

* fix
This commit is contained in:
Emil Ernerfeldt 2023-09-04 09:55:47 +02:00 committed by GitHub
parent 209cbeb030
commit 5f742b9aba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 69 additions and 61 deletions

View File

@ -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<dyn App>` -> `Box<dyn Any>` to get &`ConcreteApp` is not simple in current rust.
///

View File

@ -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<dyn std::error::Error>),
/// An error from [`wgpu`].
#[cfg(feature = "wgpu")]
#[error("WGPU error: {0}")]
Wgpu(#[from] egui_wgpu::WgpuError),
}
/// Short for `Result<T, eframe::Error>`.
pub type Result<T> = std::result::Result<T, Error>;
// ---------------------------------------------------------------------------

View File

@ -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),
}

View File

@ -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<String> {
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) {}

View File

@ -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<egui::Pos2>,
/// Required to maintain a stable touch position for multi-touch gestures.
pub latest_touch_pos_id: Option<egui::TouchId>,
/// 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<f64>);
pub(crate) struct NeedRepaint(Mutex<f64>);
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<String> {
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();

View File

@ -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();
{

View File

@ -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<egui::Vec2> {
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<Theme> {
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<web_sys::HtmlCanvasElement> {
fn canvas_element(canvas_id: &str) -> Option<web_sys::HtmlCanvasElement> {
let document = web_sys::window()?.document()?;
let canvas = document.get_element_by_id(canvas_id)?;
canvas.dyn_into::<web_sys::HtmlCanvasElement>().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()

View File

@ -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()
}

View File

@ -1,3 +1,4 @@
/// Screen reader support.
pub struct ScreenReader {
#[cfg(feature = "tts")]
tts: Option<tts::Tts>,
@ -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() {

View File

@ -2,16 +2,18 @@ fn local_storage() -> Option<web_sys::Storage> {
web_sys::window()?.local_storage().ok()?
}
/// Read data from local storage.
pub fn local_storage_get(key: &str) -> Option<String> {
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) {}

View File

@ -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 }
}

View File

@ -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();