Graceful exit from web (#1650)
Return a handle that can be used to stop a running egui instance.
This commit is contained in:
parent
3eccd341ad
commit
64496cacb9
10 changed files with 432 additions and 50 deletions
|
@ -33,6 +33,8 @@
|
|||
overflow: hidden;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Position canvas in center-top: */
|
||||
|
@ -131,7 +133,11 @@
|
|||
console.debug("wasm loaded. starting app…");
|
||||
|
||||
// This call installs a bunch of callbacks and then returns:
|
||||
wasm_bindgen.start("the_canvas_id");
|
||||
const handle = wasm_bindgen.start("the_canvas_id");
|
||||
|
||||
// call `handle.stop_web()` to stop
|
||||
// uncomment to quick result
|
||||
// setTimeout(() => {handle.stop_web(); handle.free())}, 2000)
|
||||
|
||||
console.debug("app started.");
|
||||
document.getElementById("center_text").remove();
|
||||
|
|
200
docs/multiple_apps.html
Normal file
200
docs/multiple_apps.html
Normal file
|
@ -0,0 +1,200 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
|
||||
<!-- Disable zooming: -->
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
|
||||
|
||||
<head>
|
||||
<title>egui – An immediate mode GUI written in Rust</title>
|
||||
<style>
|
||||
html {
|
||||
/* Remove touch delay: */
|
||||
touch-action: manipulation;
|
||||
}
|
||||
|
||||
body {
|
||||
/* Light mode background color for what is not covered by the egui canvas,
|
||||
or where the egui canvas is translucent. */
|
||||
background: #909090;
|
||||
display:flex;
|
||||
}
|
||||
|
||||
.canvas_wrap{
|
||||
/* height: 200px; */
|
||||
width: 400px;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body {
|
||||
/* Dark mode background color for what is not covered by the egui canvas,
|
||||
or where the egui canvas is translucent. */
|
||||
background: #404040;
|
||||
}
|
||||
}
|
||||
|
||||
/* Allow canvas to fill entire web page: */
|
||||
html,
|
||||
body {
|
||||
overflow: hidden;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Position canvas in center-top: */
|
||||
canvas {
|
||||
/* margin-right: auto;
|
||||
margin-left: auto; */
|
||||
/* display: block;
|
||||
position: absolute;
|
||||
top: 0%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, 0%); */
|
||||
width:90%;
|
||||
height:90%;
|
||||
}
|
||||
|
||||
.centered {
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
color: #f0f0f0;
|
||||
font-size: 24px;
|
||||
font-family: Ubuntu-Light, Helvetica, sans-serif;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* ---------------------------------------------- */
|
||||
/* Loading animation from https://loading.io/css/ */
|
||||
.lds-dual-ring {
|
||||
display: inline-block;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.lds-dual-ring:after {
|
||||
content: " ";
|
||||
display: block;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
margin: 0px;
|
||||
border-radius: 50%;
|
||||
border: 3px solid #fff;
|
||||
border-color: #fff transparent #fff transparent;
|
||||
animation: lds-dual-ring 1.2s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes lds-dual-ring {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<!-- The WASM code will resize the canvas dynamically -->
|
||||
|
||||
<div>controls</div>
|
||||
|
||||
<button class="stop_one">
|
||||
stop
|
||||
</button>
|
||||
|
||||
<div class="canvas_wrap one">
|
||||
<canvas id="the_canvas_id_one"></canvas>
|
||||
</div>
|
||||
|
||||
<div class="canvas_wrap two">
|
||||
<canvas id="the_canvas_id_two"></canvas>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="centered" id="center_text">
|
||||
<p style="font-size:16px">
|
||||
Loading…
|
||||
</p>
|
||||
<div class="lds-dual-ring"></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// The `--no-modules`-generated JS from `wasm-bindgen` attempts to use
|
||||
// `WebAssembly.instantiateStreaming` to instantiate the wasm module,
|
||||
// but this doesn't work with `file://` urls. This example is frequently
|
||||
// viewed by simply opening `index.html` in a browser (with a `file://`
|
||||
// url), so it would fail if we were to call this function!
|
||||
//
|
||||
// Work around this for now by deleting the function to ensure that the
|
||||
// `no_modules.js` script doesn't have access to it. You won't need this
|
||||
// hack when deploying over HTTP.
|
||||
delete WebAssembly.instantiateStreaming;
|
||||
</script>
|
||||
|
||||
<!-- this is the JS generated by the `wasm-bindgen` CLI tool -->
|
||||
<script src="egui_demo_app.js"></script>
|
||||
|
||||
<script>
|
||||
// We'll defer our execution until the wasm is ready to go.
|
||||
// Here we tell bindgen the path to the wasm file so it can start
|
||||
// initialization and return to us a promise when it's done.
|
||||
console.debug("loading wasm…");
|
||||
wasm_bindgen("./egui_demo_app_bg.wasm")
|
||||
.then(on_wasm_loaded)
|
||||
.catch(on_wasm_error);
|
||||
|
||||
function on_wasm_loaded() {
|
||||
console.debug("wasm loaded. starting app…");
|
||||
|
||||
// This call installs a bunch of callbacks and then returns:
|
||||
|
||||
wasm_bindgen.init_wasm_hooks()
|
||||
|
||||
const handle_one = wasm_bindgen.start_separate("the_canvas_id_one");
|
||||
const handle_two = wasm_bindgen.start_separate("the_canvas_id_two");
|
||||
|
||||
const button = document.getElementsByClassName("stop_one")[0]
|
||||
|
||||
button.addEventListener("click", ()=>{
|
||||
handle_one.stop_web()
|
||||
handle_one.free()
|
||||
});
|
||||
|
||||
|
||||
// call `handle.stop_web()` to stop
|
||||
// uncomment to quick result
|
||||
// setTimeout(() => {handle.stop_web()}, 2000)
|
||||
|
||||
console.debug("app started.");
|
||||
document.getElementById("center_text").remove();
|
||||
}
|
||||
|
||||
function on_wasm_error(error) {
|
||||
console.error("Failed to start: " + error);
|
||||
document.getElementById("center_text").innerHTML = `
|
||||
<p>
|
||||
An error occurred during loading:
|
||||
</p>
|
||||
<p style="font-family:Courier New">
|
||||
${error}
|
||||
</p>
|
||||
<p style="font-size:14px">
|
||||
Make sure you use a modern browser with WebGL and WASM enabled.
|
||||
</p>`;
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
||||
<!-- Powered by egui: https://github.com/emilk/egui/ -->
|
|
@ -28,6 +28,8 @@ NOTE: [`egui-winit`](../egui-winit/CHANGELOG.md), [`egui_glium`](../egui_glium/C
|
|||
|
||||
#### Web:
|
||||
* Added option to select WebGL version ([#1803](https://github.com/emilk/egui/pull/1803)).
|
||||
* Added ability to stop/re-run web app from JavaScript. ⚠️ You need to update your CSS with `html, body: { height: 100%; width: 100%; }` ([#1803](https://github.com/emilk/egui/pull/1650)).
|
||||
|
||||
|
||||
|
||||
## 0.18.0 - 2022-04-30
|
||||
|
|
|
@ -77,11 +77,19 @@ pub use epi::*;
|
|||
// When compiling for web
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
mod web;
|
||||
use egui::mutex::Mutex;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use std::sync::Arc;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub mod web;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub use wasm_bindgen;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use web::AppRunner;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub use web_sys;
|
||||
|
||||
|
@ -93,12 +101,13 @@ pub use web_sys;
|
|||
/// use wasm_bindgen::prelude::*;
|
||||
///
|
||||
/// /// This is the entry-point for all the web-assembly.
|
||||
/// /// This is called once from the HTML.
|
||||
/// /// This is called from the HTML.
|
||||
/// /// It loads the app, installs some callbacks, then returns.
|
||||
/// /// It returns a handle to the running app that can be stopped calling `AppRunner::stop_web`.
|
||||
/// /// You can add more callbacks like this if you want to call in to your code.
|
||||
/// #[cfg(target_arch = "wasm32")]
|
||||
/// #[wasm_bindgen]
|
||||
/// pub fn start(canvas_id: &str) -> Result<(), eframe::wasm_bindgen::JsValue> {
|
||||
/// pub fn start(canvas_id: &str) -> Result<Arc<Mutex<AppRunner>>, eframe::wasm_bindgen::JsValue> {
|
||||
/// let web_options = eframe::WebOptions::default();
|
||||
/// eframe::start_web(canvas_id, web_options, Box::new(|cc| Box::new(MyEguiApp::new(cc))))
|
||||
/// }
|
||||
|
@ -108,9 +117,10 @@ pub fn start_web(
|
|||
canvas_id: &str,
|
||||
web_options: WebOptions,
|
||||
app_creator: AppCreator,
|
||||
) -> Result<(), wasm_bindgen::JsValue> {
|
||||
web::start(canvas_id, web_options, app_creator)?;
|
||||
Ok(())
|
||||
) -> Result<Arc<Mutex<AppRunner>>, wasm_bindgen::JsValue> {
|
||||
let handle = web::start(canvas_id, web_options, app_creator)?;
|
||||
|
||||
Ok(handle)
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
|
|
@ -2,9 +2,10 @@ use super::{glow_wrapping::WrappedGlowPainter, *};
|
|||
|
||||
use crate::epi;
|
||||
|
||||
use egui::mutex::{Mutex, MutexGuard};
|
||||
use egui::TexturesDelta;
|
||||
|
||||
use egui::{
|
||||
mutex::{Mutex, MutexGuard},
|
||||
TexturesDelta,
|
||||
};
|
||||
pub use egui::{pos2, Color32};
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
@ -67,6 +68,24 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
fn web_location() -> epi::Location {
|
||||
|
@ -147,11 +166,19 @@ pub struct AppRunner {
|
|||
pub(crate) input: WebInput,
|
||||
app: Box<dyn epi::App>,
|
||||
pub(crate) needs_repaint: std::sync::Arc<NeedRepaint>,
|
||||
pub(crate) is_destroyed: std::sync::Arc<IsDestroyed>,
|
||||
last_save_time: f64,
|
||||
screen_reader: super::screen_reader::ScreenReader,
|
||||
pub(crate) text_cursor_pos: Option<egui::Pos2>,
|
||||
pub(crate) mutable_text_under_cursor: bool,
|
||||
textures_delta: TexturesDelta,
|
||||
pub events_to_unsubscribe: Vec<EventToUnsubscribe>,
|
||||
}
|
||||
|
||||
impl Drop for AppRunner {
|
||||
fn drop(&mut self) {
|
||||
tracing::debug!("AppRunner has fully dropped");
|
||||
}
|
||||
}
|
||||
|
||||
impl AppRunner {
|
||||
|
@ -220,11 +247,13 @@ impl AppRunner {
|
|||
input: Default::default(),
|
||||
app,
|
||||
needs_repaint,
|
||||
is_destroyed: Default::default(),
|
||||
last_save_time: now_sec(),
|
||||
screen_reader: Default::default(),
|
||||
text_cursor_pos: None,
|
||||
mutable_text_under_cursor: false,
|
||||
textures_delta: Default::default(),
|
||||
events_to_unsubscribe: Default::default(),
|
||||
};
|
||||
|
||||
runner.input.raw.max_texture_side = Some(runner.painter.max_texture_side());
|
||||
|
@ -266,6 +295,24 @@ impl AppRunner {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn destroy(&mut self) -> Result<(), JsValue> {
|
||||
let is_destroyed_already = self.is_destroyed.fetch();
|
||||
|
||||
if is_destroyed_already {
|
||||
tracing::warn!("App was destroyed already");
|
||||
Ok(())
|
||||
} else {
|
||||
tracing::debug!("Destroying");
|
||||
for x in self.events_to_unsubscribe.drain(..) {
|
||||
x.unsubscribe()?;
|
||||
}
|
||||
|
||||
self.painter.destroy();
|
||||
self.is_destroyed.set_true();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns how long to wait until the next repaint.
|
||||
///
|
||||
/// Call [`Self::paint`] later to paint
|
||||
|
@ -358,19 +405,60 @@ impl AppRunner {
|
|||
|
||||
pub type AppRunnerRef = Arc<Mutex<AppRunner>>;
|
||||
|
||||
pub struct TargetEvent {
|
||||
target: EventTarget,
|
||||
event_name: String,
|
||||
closure: Closure<dyn FnMut(web_sys::Event)>,
|
||||
}
|
||||
|
||||
pub struct IntervalHandle {
|
||||
pub handle: i32,
|
||||
pub closure: Closure<dyn FnMut()>,
|
||||
}
|
||||
|
||||
pub enum EventToUnsubscribe {
|
||||
TargetEvent(TargetEvent),
|
||||
#[allow(dead_code)]
|
||||
IntervalHandle(IntervalHandle),
|
||||
}
|
||||
|
||||
impl EventToUnsubscribe {
|
||||
pub fn unsubscribe(self) -> Result<(), JsValue> {
|
||||
use wasm_bindgen::JsCast;
|
||||
|
||||
match self {
|
||||
EventToUnsubscribe::TargetEvent(handle) => {
|
||||
handle.target.remove_event_listener_with_callback(
|
||||
handle.event_name.as_str(),
|
||||
handle.closure.as_ref().unchecked_ref(),
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
EventToUnsubscribe::IntervalHandle(handle) => {
|
||||
let window = web_sys::window().unwrap();
|
||||
window.clear_interval_with_handle(handle.handle);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
pub struct AppRunnerContainer {
|
||||
pub runner: AppRunnerRef,
|
||||
|
||||
/// Set to `true` if there is a panic.
|
||||
/// Used to ignore callbacks after a panic.
|
||||
pub panicked: Arc<AtomicBool>,
|
||||
pub events: Vec<EventToUnsubscribe>,
|
||||
}
|
||||
|
||||
impl AppRunnerContainer {
|
||||
/// Convenience function to reduce boilerplate and ensure that all event handlers
|
||||
/// are dealt with in the same way
|
||||
///
|
||||
|
||||
#[must_use]
|
||||
pub fn add_event_listener<E: wasm_bindgen::JsCast>(
|
||||
&self,
|
||||
&mut self,
|
||||
target: &EventTarget,
|
||||
event_name: &'static str,
|
||||
mut closure: impl FnMut(E, MutexGuard<'_, AppRunner>) + 'static,
|
||||
|
@ -391,14 +479,19 @@ impl AppRunnerContainer {
|
|||
|
||||
closure(event, runner_ref.lock());
|
||||
}
|
||||
}) as Box<dyn FnMut(_)>
|
||||
}) as Box<dyn FnMut(web_sys::Event)>
|
||||
});
|
||||
|
||||
// Add the event listener to the target
|
||||
target.add_event_listener_with_callback(event_name, closure.as_ref().unchecked_ref())?;
|
||||
|
||||
// Bypass closure drop so that event handler can call the closure
|
||||
closure.forget();
|
||||
let handle = TargetEvent {
|
||||
target: target.clone(),
|
||||
event_name: event_name.to_string(),
|
||||
closure,
|
||||
};
|
||||
|
||||
self.events.push(EventToUnsubscribe::TargetEvent(handle));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -421,23 +514,26 @@ pub fn start(
|
|||
/// Install event listeners to register different input events
|
||||
/// and starts running the given [`AppRunner`].
|
||||
fn start_runner(app_runner: AppRunner) -> Result<AppRunnerRef, JsValue> {
|
||||
let runner_container = AppRunnerContainer {
|
||||
let mut runner_container = AppRunnerContainer {
|
||||
runner: Arc::new(Mutex::new(app_runner)),
|
||||
panicked: Arc::new(AtomicBool::new(false)),
|
||||
events: Vec::with_capacity(20),
|
||||
};
|
||||
|
||||
super::events::install_canvas_events(&runner_container)?;
|
||||
super::events::install_document_events(&runner_container)?;
|
||||
text_agent::install_text_agent(&runner_container)?;
|
||||
super::events::install_canvas_events(&mut runner_container)?;
|
||||
super::events::install_document_events(&mut runner_container)?;
|
||||
text_agent::install_text_agent(&mut runner_container)?;
|
||||
|
||||
super::events::paint_and_schedule(&runner_container.runner, runner_container.panicked.clone())?;
|
||||
|
||||
// Disable all event handlers on panic
|
||||
let previous_hook = std::panic::take_hook();
|
||||
let panicked = runner_container.panicked;
|
||||
|
||||
runner_container.runner.lock().events_to_unsubscribe = runner_container.events;
|
||||
|
||||
std::panic::set_hook(Box::new(move |panic_info| {
|
||||
tracing::info!("egui disabled all event handlers due to panic");
|
||||
panicked.store(true, SeqCst);
|
||||
runner_container.panicked.store(true, SeqCst);
|
||||
|
||||
// Propagate panic info to the previously registered panic hook
|
||||
previous_hook(panic_info);
|
||||
|
|
|
@ -1,13 +1,17 @@
|
|||
use super::*;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
struct IsDestroyed(pub bool);
|
||||
|
||||
pub fn paint_and_schedule(
|
||||
runner_ref: &AppRunnerRef,
|
||||
panicked: Arc<AtomicBool>,
|
||||
) -> Result<(), JsValue> {
|
||||
fn paint_if_needed(runner_ref: &AppRunnerRef) -> Result<(), JsValue> {
|
||||
fn paint_if_needed(runner_ref: &AppRunnerRef) -> Result<IsDestroyed, JsValue> {
|
||||
let mut runner_lock = runner_ref.lock();
|
||||
if runner_lock.needs_repaint.when_to_repaint() <= now_sec() {
|
||||
let is_destroyed = runner_lock.is_destroyed.fetch();
|
||||
|
||||
if !is_destroyed && runner_lock.needs_repaint.when_to_repaint() <= now_sec() {
|
||||
runner_lock.needs_repaint.clear();
|
||||
runner_lock.clear_color_buffer();
|
||||
let (repaint_after, clipped_primitives) = runner_lock.logic()?;
|
||||
|
@ -18,7 +22,7 @@ pub fn paint_and_schedule(
|
|||
runner_lock.auto_save();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
Ok(IsDestroyed(is_destroyed))
|
||||
}
|
||||
|
||||
fn request_animation_frame(
|
||||
|
@ -35,14 +39,16 @@ pub fn paint_and_schedule(
|
|||
|
||||
// Only paint and schedule if there has been no panic
|
||||
if !panicked.load(Ordering::SeqCst) {
|
||||
paint_if_needed(runner_ref)?;
|
||||
request_animation_frame(runner_ref.clone(), panicked)?;
|
||||
let is_destroyed = paint_if_needed(runner_ref)?;
|
||||
if !is_destroyed.0 {
|
||||
request_animation_frame(runner_ref.clone(), panicked)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn install_document_events(runner_container: &AppRunnerContainer) -> Result<(), JsValue> {
|
||||
pub fn install_document_events(runner_container: &mut AppRunnerContainer) -> Result<(), JsValue> {
|
||||
let window = web_sys::window().unwrap();
|
||||
let document = window.document().unwrap();
|
||||
|
||||
|
@ -188,25 +194,27 @@ pub fn install_document_events(runner_container: &AppRunnerContainer) -> Result<
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn install_canvas_events(runner_container: &AppRunnerContainer) -> Result<(), JsValue> {
|
||||
use wasm_bindgen::JsCast;
|
||||
pub fn install_canvas_events(runner_container: &mut AppRunnerContainer) -> Result<(), JsValue> {
|
||||
let canvas = canvas_element(runner_container.runner.lock().canvas_id()).unwrap();
|
||||
|
||||
{
|
||||
// By default, right-clicks open a context menu.
|
||||
// We don't want to do that (right clicks is handled by egui):
|
||||
let event_name = "contextmenu";
|
||||
let closure = Closure::wrap(Box::new(move |event: web_sys::MouseEvent| {
|
||||
event.prevent_default();
|
||||
}) as Box<dyn FnMut(_)>);
|
||||
canvas.add_event_listener_with_callback(event_name, closure.as_ref().unchecked_ref())?;
|
||||
closure.forget();
|
||||
|
||||
let closure =
|
||||
move |event: web_sys::MouseEvent,
|
||||
mut _runner_lock: egui::mutex::MutexGuard<AppRunner>| {
|
||||
event.prevent_default();
|
||||
};
|
||||
|
||||
runner_container.add_event_listener(&canvas, event_name, closure)?;
|
||||
}
|
||||
|
||||
runner_container.add_event_listener(
|
||||
&canvas,
|
||||
"mousedown",
|
||||
|event: web_sys::MouseEvent, mut runner_lock| {
|
||||
|event: web_sys::MouseEvent, mut runner_lock: egui::mutex::MutexGuard<AppRunner>| {
|
||||
if let Some(button) = button_from_mouse_event(&event) {
|
||||
let pos = pos_from_mouse_event(runner_lock.canvas_id(), &event);
|
||||
let modifiers = runner_lock.input.raw.modifiers;
|
||||
|
|
|
@ -87,6 +87,10 @@ impl WrappedGlowPainter {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn destroy(&mut self) {
|
||||
self.painter.destroy()
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns glow context and shader prefix.
|
||||
|
|
|
@ -11,6 +11,7 @@ pub mod storage;
|
|||
mod text_agent;
|
||||
|
||||
pub use backend::*;
|
||||
use egui::Vec2;
|
||||
pub use events::*;
|
||||
pub use storage::*;
|
||||
|
||||
|
@ -41,6 +42,7 @@ 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(
|
||||
|
@ -96,13 +98,21 @@ 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<()> {
|
||||
let canvas = canvas_element(canvas_id)?;
|
||||
let parent = canvas.parent_element()?;
|
||||
|
||||
let width = parent.scroll_width();
|
||||
let height = parent.scroll_height();
|
||||
|
||||
let canvas_real_size = Vec2 {
|
||||
x: width as f32,
|
||||
y: height as f32,
|
||||
};
|
||||
|
||||
let screen_size_points = screen_size_in_native_points()?;
|
||||
let pixels_per_point = native_pixels_per_point();
|
||||
|
||||
let max_size_pixels = pixels_per_point * max_size_points;
|
||||
|
||||
let canvas_size_pixels = pixels_per_point * screen_size_points;
|
||||
let canvas_size_pixels = pixels_per_point * canvas_real_size;
|
||||
let canvas_size_pixels = canvas_size_pixels.min(max_size_pixels);
|
||||
let canvas_size_points = canvas_size_pixels / pixels_per_point;
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ pub fn text_agent() -> web_sys::HtmlInputElement {
|
|||
}
|
||||
|
||||
/// Text event handler,
|
||||
pub fn install_text_agent(runner_container: &AppRunnerContainer) -> Result<(), JsValue> {
|
||||
pub fn install_text_agent(runner_container: &mut AppRunnerContainer) -> Result<(), JsValue> {
|
||||
use wasm_bindgen::JsCast;
|
||||
let window = web_sys::window().unwrap();
|
||||
let document = window.document().unwrap();
|
||||
|
|
|
@ -5,6 +5,15 @@ mod backend_panel;
|
|||
pub(crate) mod frame_history;
|
||||
mod wrap_app;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use std::sync::Arc;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use eframe::web::AppRunner;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use egui::mutex::Mutex;
|
||||
|
||||
pub use wrap_app::WrapApp;
|
||||
|
||||
/// Time of day as seconds since midnight. Used for clock in demo app.
|
||||
|
@ -19,23 +28,60 @@ pub(crate) fn seconds_since_midnight() -> f64 {
|
|||
#[cfg(target_arch = "wasm32")]
|
||||
use eframe::wasm_bindgen::{self, prelude::*};
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[wasm_bindgen]
|
||||
pub struct WebHandle {
|
||||
handle: Arc<Mutex<AppRunner>>,
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[wasm_bindgen]
|
||||
impl WebHandle {
|
||||
#[wasm_bindgen]
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub fn stop_web(&self) -> Result<(), wasm_bindgen::JsValue> {
|
||||
let mut app = self.handle.lock();
|
||||
let res = app.destroy();
|
||||
|
||||
// let numw = Arc::weak_count(&app);
|
||||
// let nums = Arc::strong_count(&app);
|
||||
// tracing::debug!("runner ref {:?}, {:?}", numw, nums);
|
||||
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[wasm_bindgen]
|
||||
pub fn init_wasm_hooks() {
|
||||
// Make sure panics are logged using `console.error`.
|
||||
console_error_panic_hook::set_once();
|
||||
|
||||
// Redirect tracing to console.log and friends:
|
||||
tracing_wasm::set_as_global_default();
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[wasm_bindgen]
|
||||
pub fn start_separate(canvas_id: &str) -> Result<WebHandle, wasm_bindgen::JsValue> {
|
||||
let web_options = eframe::WebOptions::default();
|
||||
let handle = eframe::start_web(
|
||||
canvas_id,
|
||||
web_options,
|
||||
Box::new(|cc| Box::new(WrapApp::new(cc))),
|
||||
)
|
||||
.map(|handle| WebHandle { handle });
|
||||
|
||||
handle
|
||||
}
|
||||
|
||||
/// This is the entry-point for all the web-assembly.
|
||||
/// This is called once from the HTML.
|
||||
/// It loads the app, installs some callbacks, then returns.
|
||||
/// You can add more callbacks like this if you want to call in to your code.
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[wasm_bindgen]
|
||||
pub fn start(canvas_id: &str) -> Result<(), wasm_bindgen::JsValue> {
|
||||
// Make sure panics are logged using `console.error`.
|
||||
console_error_panic_hook::set_once();
|
||||
|
||||
// Redirect tracing to console.log and friends:
|
||||
tracing_wasm::set_as_global_default();
|
||||
|
||||
let web_options = eframe::WebOptions::default();
|
||||
eframe::start_web(
|
||||
canvas_id,
|
||||
web_options,
|
||||
Box::new(|cc| Box::new(WrapApp::new(cc))),
|
||||
)
|
||||
pub fn start(canvas_id: &str) -> Result<WebHandle, wasm_bindgen::JsValue> {
|
||||
init_wasm_hooks();
|
||||
start_separate(canvas_id)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue