[egui_web] Repaint on finished fetch in example app

This commit is contained in:
Emil Ernerfeldt 2020-11-20 20:35:16 +01:00
parent 99a2a52510
commit 633b19ee99
8 changed files with 129 additions and 71 deletions

View file

@ -62,8 +62,8 @@ TODO-list for the Egui project. If you looking for something to do, look here.
* [x] Read url fragment and redirect to a subpage (e.g. different examples apps)]
* [x] Copy/paste support
* [x] Async HTTP requests
* [x] Repaint on completed fetch request (etc)
* [ ] Local storage of app state
* [ ] Repaint on completed fetch request (etc)
* [ ] Fix WebGL colors/blending (try EXT_sRGB)
* [ ] Embeddability
* [ ] Support canvas that does NOT cover entire screen.

View file

@ -213,35 +213,35 @@ function makeMutClosure(arg0, arg1, dtor, f) {
return real;
}
function __wbg_adapter_26(arg0, arg1) {
wasm._dyn_core__ops__function__FnMut_____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__hcf118a068d67b888(arg0, arg1);
wasm._dyn_core__ops__function__FnMut_____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h131d7a2b30673af8(arg0, arg1);
}
function __wbg_adapter_29(arg0, arg1, arg2) {
wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h232550930f57d037(arg0, arg1, addHeapObject(arg2));
wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h1fcff379f8a91500(arg0, arg1, addHeapObject(arg2));
}
function __wbg_adapter_32(arg0, arg1) {
wasm._dyn_core__ops__function__FnMut_____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__ha5199f647b73bd92(arg0, arg1);
function __wbg_adapter_32(arg0, arg1, arg2) {
wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h1fcff379f8a91500(arg0, arg1, addHeapObject(arg2));
}
function __wbg_adapter_35(arg0, arg1, arg2) {
wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h232550930f57d037(arg0, arg1, addHeapObject(arg2));
function __wbg_adapter_35(arg0, arg1) {
wasm._dyn_core__ops__function__FnMut_____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h41cd408ae7dd46f7(arg0, arg1);
}
function __wbg_adapter_38(arg0, arg1, arg2) {
wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h232550930f57d037(arg0, arg1, addHeapObject(arg2));
wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h1fcff379f8a91500(arg0, arg1, addHeapObject(arg2));
}
function __wbg_adapter_41(arg0, arg1, arg2) {
wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h232550930f57d037(arg0, arg1, addHeapObject(arg2));
wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h1fcff379f8a91500(arg0, arg1, addHeapObject(arg2));
}
function __wbg_adapter_44(arg0, arg1, arg2) {
wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h232550930f57d037(arg0, arg1, addHeapObject(arg2));
wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h1fcff379f8a91500(arg0, arg1, addHeapObject(arg2));
}
function __wbg_adapter_47(arg0, arg1, arg2) {
wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__hd5c13e870f8bfcb3(arg0, arg1, addHeapObject(arg2));
wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h42bc6cd1f6265a35(arg0, arg1, addHeapObject(arg2));
}
/**
@ -381,10 +381,6 @@ async function init(input) {
var ret = getObject(arg0).fetch(getObject(arg1));
return addHeapObject(ret);
};
imports.wbg.__wbg_setInterval_06df6f1ebeafd66f = handleError(function(arg0, arg1, arg2) {
var ret = getObject(arg0).setInterval(getObject(arg1), arg2);
return ret;
});
imports.wbg.__wbg_body_8c888fe47d81765f = function(arg0) {
var ret = getObject(arg0).body;
return isLikeNone(ret) ? 0 : addHeapObject(ret);
@ -870,36 +866,36 @@ async function init(input) {
var ret = wasm.memory;
return addHeapObject(ret);
};
imports.wbg.__wbindgen_closure_wrapper738 = function(arg0, arg1, arg2) {
var ret = makeMutClosure(arg0, arg1, 211, __wbg_adapter_26);
return addHeapObject(ret);
};
imports.wbg.__wbindgen_closure_wrapper739 = function(arg0, arg1, arg2) {
var ret = makeMutClosure(arg0, arg1, 211, __wbg_adapter_29);
return addHeapObject(ret);
};
imports.wbg.__wbindgen_closure_wrapper741 = function(arg0, arg1, arg2) {
var ret = makeMutClosure(arg0, arg1, 211, __wbg_adapter_32);
return addHeapObject(ret);
};
imports.wbg.__wbindgen_closure_wrapper743 = function(arg0, arg1, arg2) {
var ret = makeMutClosure(arg0, arg1, 211, __wbg_adapter_35);
return addHeapObject(ret);
};
imports.wbg.__wbindgen_closure_wrapper745 = function(arg0, arg1, arg2) {
var ret = makeMutClosure(arg0, arg1, 211, __wbg_adapter_38);
return addHeapObject(ret);
};
imports.wbg.__wbindgen_closure_wrapper748 = function(arg0, arg1, arg2) {
var ret = makeMutClosure(arg0, arg1, 211, __wbg_adapter_41);
var ret = makeMutClosure(arg0, arg1, 214, __wbg_adapter_26);
return addHeapObject(ret);
};
imports.wbg.__wbindgen_closure_wrapper750 = function(arg0, arg1, arg2) {
var ret = makeMutClosure(arg0, arg1, 211, __wbg_adapter_44);
imports.wbg.__wbindgen_closure_wrapper749 = function(arg0, arg1, arg2) {
var ret = makeMutClosure(arg0, arg1, 214, __wbg_adapter_29);
return addHeapObject(ret);
};
imports.wbg.__wbindgen_closure_wrapper1475 = function(arg0, arg1, arg2) {
var ret = makeMutClosure(arg0, arg1, 242, __wbg_adapter_47);
imports.wbg.__wbindgen_closure_wrapper751 = function(arg0, arg1, arg2) {
var ret = makeMutClosure(arg0, arg1, 214, __wbg_adapter_32);
return addHeapObject(ret);
};
imports.wbg.__wbindgen_closure_wrapper753 = function(arg0, arg1, arg2) {
var ret = makeMutClosure(arg0, arg1, 214, __wbg_adapter_35);
return addHeapObject(ret);
};
imports.wbg.__wbindgen_closure_wrapper755 = function(arg0, arg1, arg2) {
var ret = makeMutClosure(arg0, arg1, 214, __wbg_adapter_38);
return addHeapObject(ret);
};
imports.wbg.__wbindgen_closure_wrapper758 = function(arg0, arg1, arg2) {
var ret = makeMutClosure(arg0, arg1, 214, __wbg_adapter_41);
return addHeapObject(ret);
};
imports.wbg.__wbindgen_closure_wrapper760 = function(arg0, arg1, arg2) {
var ret = makeMutClosure(arg0, arg1, 214, __wbg_adapter_44);
return addHeapObject(ret);
};
imports.wbg.__wbindgen_closure_wrapper1496 = function(arg0, arg1, arg2) {
var ret = makeMutClosure(arg0, arg1, 252, __wbg_adapter_47);
return addHeapObject(ret);
};

Binary file not shown.

View file

@ -36,6 +36,8 @@ pub struct IntegrationContext<'a> {
pub tex_allocator: Option<&'a mut dyn TextureAllocator>,
/// Where the app can issue commands back to the integration.
pub output: AppOutput,
/// If you need to request a repaint from another thread, clone this and give to that other thread
pub repaint_signal: std::sync::Arc<dyn RepaintSignal>,
}
#[derive(Clone, Debug)]
@ -92,6 +94,12 @@ pub trait TextureAllocator {
fn free(&mut self, id: crate::TextureId);
}
pub trait RepaintSignal: Send {
/// This signals the Egui integration that a repaint is required.
/// This is meant to be called when a background process finishes in an async context and/or background thread.
fn request_repaint(&self);
}
/// A place where you can store custom data in a way that persists when you restart the app.
///
/// On the web this is backed by [local storage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage).

View file

@ -29,10 +29,20 @@ impl egui::app::TextureAllocator for Painter {
}
}
struct RequestRepaintEvent;
struct GliumRepaintSignal(glutin::event_loop::EventLoopProxy<RequestRepaintEvent>);
impl egui::app::RepaintSignal for GliumRepaintSignal {
fn request_repaint(&self) {
self.0.send_event(RequestRepaintEvent).ok();
}
}
fn create_display(
title: &str,
window_settings: Option<WindowSettings>,
event_loop: &glutin::event_loop::EventLoop<()>,
event_loop: &glutin::event_loop::EventLoop<RequestRepaintEvent>,
) -> glium::Display {
let mut window_builder = glutin::window::WindowBuilder::new()
.with_decorations(true)
@ -61,9 +71,11 @@ pub fn run(
) -> ! {
let window_settings: Option<WindowSettings> =
egui::app::get_value(storage.as_ref(), WINDOW_KEY);
let event_loop = glutin::event_loop::EventLoop::new();
let event_loop = glutin::event_loop::EventLoop::with_user_event();
let display = create_display(title, window_settings, &event_loop);
let repaint_signal = std::sync::Arc::new(GliumRepaintSignal(event_loop.create_proxy()));
let mut ctx = egui::Context::new();
*ctx.memory() = egui::app::get_value(storage.as_ref(), EGUI_MEMORY_KEY).unwrap_or_default();
app.setup(&ctx);
@ -92,6 +104,7 @@ pub fn run(
},
tex_allocator: Some(&mut painter),
output: Default::default(),
repaint_signal: repaint_signal.clone(),
};
app.ui(&ctx, &mut integration_context);
let app_output = integration_context.output;
@ -158,6 +171,11 @@ pub fn run(
app.on_exit(storage.as_mut());
storage.flush();
}
glutin::event::Event::UserEvent(RequestRepaintEvent) => {
display.gl_window().window().request_redraw();
}
_ => (),
}
});

View file

@ -134,12 +134,40 @@ impl WebInput {
// ----------------------------------------------------------------------------
use std::sync::atomic::Ordering::SeqCst;
pub struct NeedRepaint(std::sync::atomic::AtomicBool);
impl Default for NeedRepaint {
fn default() -> Self {
Self(true.into())
}
}
impl NeedRepaint {
pub fn fetch_and_clear(&self) -> bool {
self.0.swap(false, SeqCst)
}
pub fn set_true(&self) {
self.0.store(true, SeqCst);
}
}
impl egui::app::RepaintSignal for NeedRepaint {
fn request_repaint(&self) {
self.0.store(true, SeqCst);
}
}
// ----------------------------------------------------------------------------
pub struct AppRunner {
pixels_per_point: f32,
pub web_backend: WebBackend,
pub input: WebInput,
pub app: Box<dyn App>,
pub needs_repaint: bool, // TODO: move
pub needs_repaint: std::sync::Arc<NeedRepaint>,
}
impl AppRunner {
@ -150,7 +178,7 @@ impl AppRunner {
web_backend,
input: Default::default(),
app,
needs_repaint: true, // TODO: move
needs_repaint: Default::default(),
})
}
@ -175,6 +203,7 @@ impl AppRunner {
},
tex_allocator: Some(&mut self.web_backend.painter),
output: Default::default(),
repaint_signal: self.needs_repaint.clone(),
};
let egui_ctx = &self.web_backend.ctx;

View file

@ -15,18 +15,22 @@ use wasm_bindgen::prelude::*;
// ----------------------------------------------------------------------------
// Helpers to hide some of the verbosity of web_sys
/// Log some text to the developer console (`console.log(...)` in JS)
pub fn console_log(s: impl Into<JsValue>) {
web_sys::console::log_1(&s.into());
}
/// Log a warning to the developer console (`console.warn(...)` in JS)
pub fn console_warn(s: impl Into<JsValue>) {
web_sys::console::warn_1(&s.into());
}
/// Log an error to the developer console (`console.error(...)` in JS)
pub fn console_error(s: impl Into<JsValue>) {
web_sys::console::error_1(&s.into());
}
/// Current time in seconds (since undefined point in time)
pub fn now_sec() -> f64 {
web_sys::window()
.expect("should have a Window")
@ -298,11 +302,12 @@ pub struct AppRunnerRef(Arc<Mutex<AppRunner>>);
fn paint_and_schedule(runner_ref: AppRunnerRef) -> Result<(), JsValue> {
fn paint_if_needed(runner_ref: &AppRunnerRef) -> Result<(), JsValue> {
let mut runner_lock = runner_ref.0.lock();
if runner_lock.needs_repaint {
runner_lock.needs_repaint = false;
if runner_lock.needs_repaint.fetch_and_clear() {
let (output, paint_jobs) = runner_lock.logic()?;
runner_lock.paint(paint_jobs)?;
runner_lock.needs_repaint |= output.needs_repaint;
if output.needs_repaint {
runner_lock.needs_repaint.set_true();
}
}
Ok(())
}
@ -350,7 +355,7 @@ fn install_document_events(runner_ref: &AppRunnerRef) -> Result<(), JsValue> {
if !modifiers.ctrl && !modifiers.command && !should_ignore_key(&key) {
runner_lock.input.raw.events.push(egui::Event::Text(key));
}
runner_lock.needs_repaint = true;
runner_lock.needs_repaint.set_true();
// So, shall we call prevent_default?
// YES:
@ -392,7 +397,7 @@ fn install_document_events(runner_ref: &AppRunnerRef) -> Result<(), JsValue> {
modifiers,
});
}
runner_lock.needs_repaint = true;
runner_lock.needs_repaint.set_true();
}) as Box<dyn FnMut(_)>);
document.add_event_listener_with_callback("keyup", closure.as_ref().unchecked_ref())?;
closure.forget();
@ -406,7 +411,7 @@ fn install_document_events(runner_ref: &AppRunnerRef) -> Result<(), JsValue> {
if let Ok(text) = data.get_data("text") {
let mut runner_lock = runner_ref.0.lock();
runner_lock.input.raw.events.push(egui::Event::Text(text));
runner_lock.needs_repaint = true;
runner_lock.needs_repaint.set_true();
}
}
}) as Box<dyn FnMut(_)>);
@ -420,7 +425,7 @@ fn install_document_events(runner_ref: &AppRunnerRef) -> Result<(), JsValue> {
let closure = Closure::wrap(Box::new(move |_: web_sys::ClipboardEvent| {
let mut runner_lock = runner_ref.0.lock();
runner_lock.input.raw.events.push(egui::Event::Cut);
runner_lock.needs_repaint = true;
runner_lock.needs_repaint.set_true();
}) as Box<dyn FnMut(_)>);
document.add_event_listener_with_callback("cut", closure.as_ref().unchecked_ref())?;
closure.forget();
@ -432,7 +437,7 @@ fn install_document_events(runner_ref: &AppRunnerRef) -> Result<(), JsValue> {
let closure = Closure::wrap(Box::new(move |_: web_sys::ClipboardEvent| {
let mut runner_lock = runner_ref.0.lock();
runner_lock.input.raw.events.push(egui::Event::Copy);
runner_lock.needs_repaint = true;
runner_lock.needs_repaint.set_true();
}) as Box<dyn FnMut(_)>);
document.add_event_listener_with_callback("copy", closure.as_ref().unchecked_ref())?;
closure.forget();
@ -441,7 +446,7 @@ fn install_document_events(runner_ref: &AppRunnerRef) -> Result<(), JsValue> {
for event_name in &["load", "pagehide", "pageshow", "resize"] {
let runner_ref = runner_ref.clone();
let closure = Closure::wrap(Box::new(move || {
runner_ref.0.lock().needs_repaint = true;
runner_ref.0.lock().needs_repaint.set_true();
}) as Box<dyn FnMut()>);
window.add_event_listener_with_callback(event_name, closure.as_ref().unchecked_ref())?;
closure.forget();
@ -457,7 +462,7 @@ fn repaint_every_ms(runner_ref: &AppRunnerRef, milliseconds: i32) -> Result<(),
let window = web_sys::window().unwrap();
let runner_ref = runner_ref.clone();
let closure = Closure::wrap(Box::new(move || {
runner_ref.0.lock().needs_repaint = true;
runner_ref.0.lock().needs_repaint.set_true();
}) as Box<dyn FnMut()>);
window.set_interval_with_callback_and_timeout_and_arguments_0(
closure.as_ref().unchecked_ref(),
@ -497,7 +502,7 @@ fn install_canvas_events(runner_ref: &AppRunnerRef) -> Result<(), JsValue> {
Some(pos_from_mouse_event(runner_lock.canvas_id(), &event));
runner_lock.input.raw.mouse_down = true;
runner_lock.logic().unwrap(); // in case we get "mouseup" the same frame. TODO: handle via events instead
runner_lock.needs_repaint = true;
runner_lock.needs_repaint.set_true();
event.stop_propagation();
event.prevent_default();
}
@ -514,7 +519,7 @@ fn install_canvas_events(runner_ref: &AppRunnerRef) -> Result<(), JsValue> {
if !runner_lock.input.is_touch {
runner_lock.input.mouse_pos =
Some(pos_from_mouse_event(runner_lock.canvas_id(), &event));
runner_lock.needs_repaint = true;
runner_lock.needs_repaint.set_true();
event.stop_propagation();
event.prevent_default();
}
@ -532,7 +537,7 @@ fn install_canvas_events(runner_ref: &AppRunnerRef) -> Result<(), JsValue> {
runner_lock.input.mouse_pos =
Some(pos_from_mouse_event(runner_lock.canvas_id(), &event));
runner_lock.input.raw.mouse_down = false;
runner_lock.needs_repaint = true;
runner_lock.needs_repaint.set_true();
event.stop_propagation();
event.prevent_default();
}
@ -548,7 +553,7 @@ fn install_canvas_events(runner_ref: &AppRunnerRef) -> Result<(), JsValue> {
let mut runner_lock = runner_ref.0.lock();
if !runner_lock.input.is_touch {
runner_lock.input.mouse_pos = None;
runner_lock.needs_repaint = true;
runner_lock.needs_repaint.set_true();
event.stop_propagation();
event.prevent_default();
}
@ -565,7 +570,7 @@ fn install_canvas_events(runner_ref: &AppRunnerRef) -> Result<(), JsValue> {
runner_lock.input.is_touch = true;
runner_lock.input.mouse_pos = Some(pos_from_touch_event(&event));
runner_lock.input.raw.mouse_down = true;
runner_lock.needs_repaint = true;
runner_lock.needs_repaint.set_true();
event.stop_propagation();
event.prevent_default();
}) as Box<dyn FnMut(_)>);
@ -580,7 +585,7 @@ fn install_canvas_events(runner_ref: &AppRunnerRef) -> Result<(), JsValue> {
let mut runner_lock = runner_ref.0.lock();
runner_lock.input.is_touch = true;
runner_lock.input.mouse_pos = Some(pos_from_touch_event(&event));
runner_lock.needs_repaint = true;
runner_lock.needs_repaint.set_true();
event.stop_propagation();
event.prevent_default();
}) as Box<dyn FnMut(_)>);
@ -597,7 +602,7 @@ fn install_canvas_events(runner_ref: &AppRunnerRef) -> Result<(), JsValue> {
runner_lock.input.raw.mouse_down = false; // First release mouse to click...
runner_lock.logic().unwrap(); // ...do the clicking... (TODO: handle via events instead)
runner_lock.input.mouse_pos = None; // ...remove hover effect
runner_lock.needs_repaint = true;
runner_lock.needs_repaint.set_true();
event.stop_propagation();
event.prevent_default();
}) as Box<dyn FnMut(_)>);
@ -612,7 +617,7 @@ fn install_canvas_events(runner_ref: &AppRunnerRef) -> Result<(), JsValue> {
let mut runner_lock = runner_ref.0.lock();
runner_lock.input.scroll_delta.x -= event.delta_x() as f32;
runner_lock.input.scroll_delta.y -= event.delta_y() as f32;
runner_lock.needs_repaint = true;
runner_lock.needs_repaint.set_true();
event.stop_propagation();
event.prevent_default();
}) as Box<dyn FnMut(_)>);

View file

@ -46,6 +46,15 @@ impl egui::app::App for ExampleApp {
ctx: &std::sync::Arc<egui::Context>,
integration_context: &mut egui::app::IntegrationContext,
) {
if let Some(receiver) = &mut self.in_progress {
// Are we there yet?
if let Ok(result) = receiver.try_recv() {
self.in_progress = None;
self.result = Some(result.map(Resource::from_response));
} else {
}
}
egui::CentralPanel::default().show(ctx, |ui| {
ui.heading("Egui Example App");
ui.add(egui::github_link_file!(
@ -54,12 +63,13 @@ impl egui::app::App for ExampleApp {
));
if ui_url(ui, &mut self.url) {
let repaint_signal = integration_context.repaint_signal.clone();
let (sender, receiver) = std::sync::mpsc::channel();
self.in_progress = Some(receiver);
let url = self.url.clone();
egui_web::spawn_future(async move {
sender.send(egui_web::fetch::get(&url).await).ok();
// TODO: trigger egui repaint somehow
repaint_signal.request_repaint();
});
}
@ -79,14 +89,6 @@ impl egui::app::App for ExampleApp {
}
}
});
if let Some(receiver) = &mut self.in_progress {
// Are we there yet?
if let Ok(result) = receiver.try_recv() {
self.in_progress = None;
self.result = Some(result.map(Resource::from_response));
}
}
}
}