Improve documentation of `eframe`, especially for wasm32 (#3295)
* Improve documentation of `eframe`, especially for wasm32 * remove dead code * fix
This commit is contained in:
parent
209cbeb030
commit
5f742b9aba
|
|
@ -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.
|
||||
///
|
||||
|
|
|
|||
|
|
@ -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>;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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) {}
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue