diff --git a/docs/example_wasm.js b/docs/example_wasm.js index 79fed762..cdfd84c1 100644 --- a/docs/example_wasm.js +++ b/docs/example_wasm.js @@ -207,28 +207,28 @@ function makeMutClosure(arg0, arg1, dtor, f) { real.original = state; return real; } -function __wbg_adapter_26(arg0, arg1) { - wasm._dyn_core__ops__function__FnMut_____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h5402719cc6dde927(arg0, arg1); +function __wbg_adapter_26(arg0, arg1, arg2) { + wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h22fd33d9f501a695(arg0, arg1, addHeapObject(arg2)); } function __wbg_adapter_29(arg0, arg1, arg2) { wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h22fd33d9f501a695(arg0, arg1, addHeapObject(arg2)); } -function __wbg_adapter_32(arg0, arg1, arg2) { - wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h22fd33d9f501a695(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__h5402719cc6dde927(arg0, arg1); } function __wbg_adapter_35(arg0, arg1, arg2) { wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h22fd33d9f501a695(arg0, arg1, addHeapObject(arg2)); } -function __wbg_adapter_38(arg0, arg1) { - wasm._dyn_core__ops__function__FnMut_____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h84da5f062b972f09(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__h22fd33d9f501a695(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__h22fd33d9f501a695(arg0, arg1, addHeapObject(arg2)); +function __wbg_adapter_41(arg0, arg1) { + wasm._dyn_core__ops__function__FnMut_____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h84da5f062b972f09(arg0, arg1); } /** @@ -725,28 +725,28 @@ async function init(input) { var ret = wasm.memory; return addHeapObject(ret); }; - imports.wbg.__wbindgen_closure_wrapper373 = function(arg0, arg1, arg2) { - var ret = makeMutClosure(arg0, arg1, 71, __wbg_adapter_35); - return addHeapObject(ret); - }; - imports.wbg.__wbindgen_closure_wrapper375 = function(arg0, arg1, arg2) { - var ret = makeMutClosure(arg0, arg1, 71, __wbg_adapter_29); - return addHeapObject(ret); - }; - imports.wbg.__wbindgen_closure_wrapper371 = function(arg0, arg1, arg2) { + imports.wbg.__wbindgen_closure_wrapper380 = function(arg0, arg1, arg2) { var ret = makeMutClosure(arg0, arg1, 71, __wbg_adapter_38); return addHeapObject(ret); }; - imports.wbg.__wbindgen_closure_wrapper365 = function(arg0, arg1, arg2) { - var ret = makeMutClosure(arg0, arg1, 71, __wbg_adapter_26); + imports.wbg.__wbindgen_closure_wrapper377 = function(arg0, arg1, arg2) { + var ret = makeMutClosure(arg0, arg1, 71, __wbg_adapter_35); return addHeapObject(ret); }; - imports.wbg.__wbindgen_closure_wrapper369 = function(arg0, arg1, arg2) { + imports.wbg.__wbindgen_closure_wrapper384 = function(arg0, arg1, arg2) { + var ret = makeMutClosure(arg0, arg1, 71, __wbg_adapter_29); + return addHeapObject(ret); + }; + imports.wbg.__wbindgen_closure_wrapper376 = function(arg0, arg1, arg2) { + var ret = makeMutClosure(arg0, arg1, 71, __wbg_adapter_32); + return addHeapObject(ret); + }; + imports.wbg.__wbindgen_closure_wrapper382 = function(arg0, arg1, arg2) { var ret = makeMutClosure(arg0, arg1, 71, __wbg_adapter_41); return addHeapObject(ret); }; - imports.wbg.__wbindgen_closure_wrapper366 = function(arg0, arg1, arg2) { - var ret = makeMutClosure(arg0, arg1, 71, __wbg_adapter_32); + imports.wbg.__wbindgen_closure_wrapper386 = function(arg0, arg1, arg2) { + var ret = makeMutClosure(arg0, arg1, 71, __wbg_adapter_26); return addHeapObject(ret); }; diff --git a/docs/example_wasm_bg.wasm b/docs/example_wasm_bg.wasm index e761843c..0b6a903e 100644 Binary files a/docs/example_wasm_bg.wasm and b/docs/example_wasm_bg.wasm differ diff --git a/egui/src/containers/area.rs b/egui/src/containers/area.rs index f455d296..8f8d0697 100644 --- a/egui/src/containers/area.rs +++ b/egui/src/containers/area.rs @@ -195,6 +195,7 @@ impl Prepared { } else { state.vel -= friction * state.vel.normalized(); state.pos += state.vel * input.dt; + ctx.request_repaint(); } } diff --git a/egui/src/containers/collapsing_header.rs b/egui/src/containers/collapsing_header.rs index 5c663507..b4b689c7 100644 --- a/egui/src/containers/collapsing_header.rs +++ b/egui/src/containers/collapsing_header.rs @@ -57,6 +57,9 @@ impl State { let animation_time = ui.style().animation_time; let time_since_toggle = (ui.input().time - self.toggle_time) as f32; let time_since_toggle = time_since_toggle + ui.input().dt; // Instant feedback + if time_since_toggle <= animation_time { + ui.ctx().request_repaint(); + } if self.open { remap_clamp(time_since_toggle, 0.0..=animation_time, 0.0..=1.0) } else { diff --git a/egui/src/containers/scroll_area.rs b/egui/src/containers/scroll_area.rs index ab2ce908..33350b2b 100644 --- a/egui/src/containers/scroll_area.rs +++ b/egui/src/containers/scroll_area.rs @@ -92,6 +92,9 @@ impl ScrollArea { } else { let time_since_toggle = (ui.input().time - state.toggle_time) as f32; let animation_time = ui.style().animation_time; + if time_since_toggle <= animation_time { + ui.ctx().request_repaint(); + } if state.show_scroll { remap_clamp( time_since_toggle, @@ -194,6 +197,7 @@ impl Prepared { 0.0..=ui.style().animation_time, 0.0..=max_scroll_bar_width, ); + ui.ctx().request_repaint(); } if current_scroll_bar_width > 0.0 { diff --git a/egui/src/context.rs b/egui/src/context.rs index 3e8a0d72..261073c3 100644 --- a/egui/src/context.rs +++ b/egui/src/context.rs @@ -75,6 +75,13 @@ impl Context { self.output.try_lock().expect("output already locked") } + /// Call this if there is need to repaint the UI, i.e. if you are showing an animation. + /// If this is called at least once in a frame, then there will be another frame right after this. + /// Call as many times as you wish, only one repaint will be issued. + pub fn request_repaint(&self) { + self.output().needs_repaint = true; + } + pub fn input(&self) -> &InputState { &self.input } @@ -150,7 +157,6 @@ impl Context { self.used_ids.lock().clear(); self.input = std::mem::take(&mut self.input).begin_frame(new_raw_input); - let mut font_definitions = self.font_definitions.lock(); font_definitions.pixels_per_point = self.input.pixels_per_point; if self.fonts.is_none() || *self.fonts.as_ref().unwrap().definitions() != *font_definitions @@ -163,6 +169,10 @@ impl Context { /// Returns what has happened this frame (`Output`) as well as what you need to paint. #[must_use] pub fn end_frame(&self) -> (Output, PaintBatches) { + if self.input.wants_repaint() { + self.request_repaint(); + } + self.memory().end_frame(); let output: Output = std::mem::take(&mut self.output()); let paint_batches = self.paint(); diff --git a/egui/src/examples/fractal_clock.rs b/egui/src/examples/fractal_clock.rs index 5df11956..b713f67f 100644 --- a/egui/src/examples/fractal_clock.rs +++ b/egui/src/examples/fractal_clock.rs @@ -47,6 +47,7 @@ impl FractalClock { .input() .seconds_since_midnight .unwrap_or_else(|| ui.input().time); + ui.ctx().request_repaint(); } self.fractal_ui(ui, ui.available_finite()); diff --git a/egui/src/input.rs b/egui/src/input.rs index a265c5a0..daf2032b 100644 --- a/egui/src/input.rs +++ b/egui/src/input.rs @@ -190,6 +190,14 @@ impl InputState { raw: new, } } + + pub fn wants_repaint(&self) -> bool { + self.mouse.pressed + || self.mouse.released + || self.mouse.delta != Vec2::zero() + || self.scroll_delta != Vec2::zero() + || !self.events.is_empty() + } } impl MouseInput { diff --git a/egui/src/types.rs b/egui/src/types.rs index 73668e6a..f11eab4d 100644 --- a/egui/src/types.rs +++ b/egui/src/types.rs @@ -14,6 +14,11 @@ pub struct Output { /// Response to Event::Copy or Event::Cut. Ignore if empty. pub copied_text: String, + + /// Set to `true` to request another repaint right after this one. + /// This is only used in reactive backends (i.e. backends where we repaint on new input). + /// For instance, you may want to set this to `true` while there is an animation. + pub needs_repaint: bool, } #[derive(Clone, Copy)] diff --git a/egui/src/ui.rs b/egui/src/ui.rs index 0f531fea..4cd060b1 100644 --- a/egui/src/ui.rs +++ b/egui/src/ui.rs @@ -547,16 +547,29 @@ impl Ui { self.add(Button::new(text)) } - // TODO: argument order? + // Argument order matching that of Dear ImGui pub fn checkbox(&mut self, text: impl Into, checked: &mut bool) -> GuiResponse { self.add(Checkbox::new(checked, text)) } - // TODO: argument order? + // Argument order matching that of Dear ImGui pub fn radio(&mut self, text: impl Into, checked: bool) -> GuiResponse { self.add(RadioButton::new(checked, text)) } + pub fn radio_value( + &mut self, + text: impl Into, + curr_value: &mut Value, + radio_value: Value, + ) -> GuiResponse { + let response = self.radio(text, *curr_value == radio_value); + if response.clicked { + *curr_value = radio_value; + } + response + } + pub fn separator(&mut self) -> GuiResponse { self.add(Separator::new()) } diff --git a/egui_web/src/lib.rs b/egui_web/src/lib.rs index 2530b5ef..a65bebeb 100644 --- a/egui_web/src/lib.rs +++ b/egui_web/src/lib.rs @@ -9,19 +9,24 @@ use wasm_bindgen::prelude::*; // ---------------------------------------------------------------------------- -pub struct BackendInfo { - pub painter_debug_info: String, - /// excludes call to paint backend - pub cpu_time: f32, - pub fps: f32, - +pub struct WebInfo { /// e.g. "#fragment" part of "www.example.com/index.html#fragment" pub web_location_hash: String, } /// Implement this and use `egui_web::AppRunner` to run your app. pub trait App { - fn ui(&mut self, ui: &mut egui::Ui, info: &BackendInfo); + fn ui(&mut self, ui: &mut egui::Ui, backend: &mut Backend, info: &WebInfo); +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum RunMode { + /// Uses `request_animation_frame` to repaint the UI on each display Hz. + /// This is good for games and stuff where you want to run logic at e.g. 60 FPS. + Continuous, + + /// Only repaint when there is new input (mouse movement, keyboard input etc). + Reactive, } // ---------------------------------------------------------------------------- @@ -31,10 +36,13 @@ pub struct Backend { painter: webgl::Painter, frame_times: egui::MovementTracker, frame_start: Option, + /// If true, paint at full framerate always. + /// If false, only paint on input. + run_mode: RunMode, } impl Backend { - pub fn new(canvas_id: &str) -> Result { + pub fn new(canvas_id: &str, run_mode: RunMode) -> Result { let ctx = egui::Context::new(); load_memory(&ctx); Ok(Backend { @@ -42,9 +50,18 @@ impl Backend { painter: webgl::Painter::new(canvas_id)?, frame_times: egui::MovementTracker::new(1000, 1.0), frame_start: None, + run_mode, }) } + pub fn run_mode(&self) -> RunMode { + self.run_mode + } + + pub fn set_run_mode(&mut self, run_mode: RunMode) { + self.run_mode = run_mode; + } + /// id of the canvas html element containing the rendering pub fn canvas_id(&self) -> &str { self.painter.canvas_id() @@ -127,14 +144,17 @@ pub struct AppRunner { pub backend: Backend, pub web_input: WebInput, pub app: Box, + /// Used to prevent calling paint() twice in one frame + pub has_requested_animaiton_frame: bool, } impl AppRunner { - pub fn new(canvas_id: &str, app: Box) -> Result { + pub fn new(backend: Backend, app: Box) -> Result { Ok(Self { - backend: Backend::new(canvas_id)?, + backend, web_input: Default::default(), app, + has_requested_animaiton_frame: false, }) } @@ -142,36 +162,33 @@ impl AppRunner { self.backend.canvas_id() } - pub fn paint(&mut self) -> Result<(), JsValue> { + pub fn paint(&mut self) -> Result { resize_to_screen_size(self.backend.canvas_id()); let raw_input = self.web_input.new_frame(); - let info = BackendInfo { - painter_debug_info: self.backend.painter_debug_info(), - cpu_time: self.backend.cpu_time(), - fps: self.backend.fps(), + let info = WebInfo { web_location_hash: location_hash().unwrap_or_default(), }; let mut ui = self.backend.begin_frame(raw_input); - self.app.ui(&mut ui, &info); + self.app.ui(&mut ui, &mut self.backend, &info); let output = self.backend.end_frame()?; handle_output(&output); - Ok(()) + Ok(output) } } /// Install event listeners to register different input events /// and starts running the given `AppRunner`. -pub fn run(runner: AppRunner) -> Result { - let runner = AppRunnerRef(Arc::new(Mutex::new(runner))); - install_canvas_events(&runner)?; - install_document_events(&runner)?; - paint_and_schedule(runner.clone())?; - Ok(runner) +pub fn run(app_runner: AppRunner) -> Result { + let runner_ref = AppRunnerRef(Arc::new(Mutex::new(app_runner))); + install_canvas_events(&runner_ref)?; + install_document_events(&runner_ref)?; + paint_and_schedule(runner_ref.clone())?; + Ok(runner_ref) } // ---------------------------------------------------------------------------- @@ -377,55 +394,57 @@ pub fn translate_key(key: &str) -> Option { #[derive(Clone)] pub struct AppRunnerRef(Arc>); -/// If true, paint at full framerate always. -/// If false, only paint on input. -/// TODO: if this is turned off we must turn off animations too (which hasn't been implemented yet). -const ANIMATION_FRAME: bool = true; - -fn paint_and_schedule(runner: AppRunnerRef) -> Result<(), JsValue> { - runner.0.lock().paint()?; - if ANIMATION_FRAME { - request_animation_frame(runner)?; +fn paint_and_schedule(runner_ref: AppRunnerRef) -> Result<(), JsValue> { + let needs_repaint = { + let mut runner_lock = runner_ref.0.lock(); + runner_lock.has_requested_animaiton_frame = false; + let output = runner_lock.paint()?; + let run_mode = runner_lock.backend.run_mode(); + run_mode == RunMode::Continuous || output.needs_repaint + }; + if needs_repaint { + request_animation_frame(runner_ref)?; } Ok(()) } -fn request_animation_frame(runner: AppRunnerRef) -> Result<(), JsValue> { - use wasm_bindgen::JsCast; - let window = web_sys::window().unwrap(); - let closure = Closure::once(move || paint_and_schedule(runner)); - window.request_animation_frame(closure.as_ref().unchecked_ref())?; - closure.forget(); // We must forget it, or else the callback is canceled on drop +fn request_animation_frame(runner_ref: AppRunnerRef) -> Result<(), JsValue> { + if !{ runner_ref.0.lock().has_requested_animaiton_frame } { + runner_ref.0.lock().has_requested_animaiton_frame = true; + + use wasm_bindgen::JsCast; + 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())?; + closure.forget(); // We must forget it, or else the callback is canceled on drop + } Ok(()) } -fn invalidate(runner: &mut AppRunner) -> Result<(), JsValue> { - if ANIMATION_FRAME { - Ok(()) // No need to invalidate - we repaint all the time - } else { - runner.paint() // TODO: schedule repaint instead? - } +fn invalidate(runner_ref: AppRunnerRef) -> Result<(), JsValue> { + request_animation_frame(runner_ref) } -fn install_document_events(runner: &AppRunnerRef) -> Result<(), JsValue> { +fn install_document_events(runner_ref: &AppRunnerRef) -> Result<(), JsValue> { use wasm_bindgen::JsCast; let document = web_sys::window().unwrap().document().unwrap(); { // keydown - let runner = runner.clone(); + let runner_ref = runner_ref.clone(); let closure = Closure::wrap(Box::new(move |event: web_sys::KeyboardEvent| { - let mut runner = runner.0.lock(); + let mut runner_lock = runner_ref.0.lock(); let key = event.key(); if let Some(key) = translate_key(&key) { - runner + runner_lock .web_input .events .push(egui::Event::Key { key, pressed: true }); } else { - runner.web_input.events.push(egui::Event::Text(key)); + runner_lock.web_input.events.push(egui::Event::Text(key)); } - invalidate(&mut runner).unwrap(); + drop(runner_lock); + invalidate(runner_ref.clone()).unwrap(); }) as Box); document.add_event_listener_with_callback("keydown", closure.as_ref().unchecked_ref())?; closure.forget(); @@ -433,16 +452,17 @@ fn install_document_events(runner: &AppRunnerRef) -> Result<(), JsValue> { { // keyup - let runner = runner.clone(); + let runner_ref = runner_ref.clone(); let closure = Closure::wrap(Box::new(move |event: web_sys::KeyboardEvent| { - let mut runner = runner.0.lock(); + let mut runner_lock = runner_ref.0.lock(); let key = event.key(); if let Some(key) = translate_key(&key) { - runner.web_input.events.push(egui::Event::Key { + runner_lock.web_input.events.push(egui::Event::Key { key, pressed: false, }); - invalidate(&mut runner).unwrap(); + drop(runner_lock); + invalidate(runner_ref.clone()).unwrap(); } }) as Box); document.add_event_listener_with_callback("keyup", closure.as_ref().unchecked_ref())?; @@ -450,9 +470,9 @@ fn install_document_events(runner: &AppRunnerRef) -> Result<(), JsValue> { } for event_name in &["load", "pagehide", "pageshow", "resize"] { - let runner = runner.clone(); + let runner_ref = runner_ref.clone(); let closure = Closure::wrap(Box::new(move || { - invalidate(&mut runner.0.lock()).unwrap(); + invalidate(runner_ref.clone()).unwrap(); }) as Box); document.add_event_listener_with_callback(event_name, closure.as_ref().unchecked_ref())?; closure.forget(); @@ -461,19 +481,21 @@ fn install_document_events(runner: &AppRunnerRef) -> Result<(), JsValue> { Ok(()) } -fn install_canvas_events(runner: &AppRunnerRef) -> Result<(), JsValue> { +fn install_canvas_events(runner_ref: &AppRunnerRef) -> Result<(), JsValue> { use wasm_bindgen::JsCast; - let canvas = canvas_element(runner.0.lock().canvas_id()).unwrap(); + let canvas = canvas_element(runner_ref.0.lock().canvas_id()).unwrap(); { let event_name = "mousedown"; - let runner = runner.clone(); + let runner_ref = runner_ref.clone(); let closure = Closure::wrap(Box::new(move |event: web_sys::MouseEvent| { - let mut runner = runner.0.lock(); - if !runner.web_input.is_touch { - runner.web_input.mouse_pos = Some(pos_from_mouse_event(runner.canvas_id(), &event)); - runner.web_input.mouse_down = true; - invalidate(&mut runner).unwrap(); + let mut runner_lock = runner_ref.0.lock(); + if !runner_lock.web_input.is_touch { + runner_lock.web_input.mouse_pos = + Some(pos_from_mouse_event(runner_lock.canvas_id(), &event)); + runner_lock.web_input.mouse_down = true; + drop(runner_lock); + invalidate(runner_ref.clone()).unwrap(); event.stop_propagation(); event.prevent_default(); } @@ -484,12 +506,14 @@ fn install_canvas_events(runner: &AppRunnerRef) -> Result<(), JsValue> { { let event_name = "mousemove"; - let runner = runner.clone(); + let runner_ref = runner_ref.clone(); let closure = Closure::wrap(Box::new(move |event: web_sys::MouseEvent| { - let mut runner = runner.0.lock(); - if !runner.web_input.is_touch { - runner.web_input.mouse_pos = Some(pos_from_mouse_event(runner.canvas_id(), &event)); - invalidate(&mut runner).unwrap(); + let mut runner_lock = runner_ref.0.lock(); + if !runner_lock.web_input.is_touch { + runner_lock.web_input.mouse_pos = + Some(pos_from_mouse_event(runner_lock.canvas_id(), &event)); + drop(runner_lock); + invalidate(runner_ref.clone()).unwrap(); event.stop_propagation(); event.prevent_default(); } @@ -500,13 +524,15 @@ fn install_canvas_events(runner: &AppRunnerRef) -> Result<(), JsValue> { { let event_name = "mouseup"; - let runner = runner.clone(); + let runner_ref = runner_ref.clone(); let closure = Closure::wrap(Box::new(move |event: web_sys::MouseEvent| { - let mut runner = runner.0.lock(); - if !runner.web_input.is_touch { - runner.web_input.mouse_pos = Some(pos_from_mouse_event(runner.canvas_id(), &event)); - runner.web_input.mouse_down = false; - invalidate(&mut runner).unwrap(); + let mut runner_lock = runner_ref.0.lock(); + if !runner_lock.web_input.is_touch { + runner_lock.web_input.mouse_pos = + Some(pos_from_mouse_event(runner_lock.canvas_id(), &event)); + runner_lock.web_input.mouse_down = false; + drop(runner_lock); + invalidate(runner_ref.clone()).unwrap(); event.stop_propagation(); event.prevent_default(); } @@ -517,12 +543,13 @@ fn install_canvas_events(runner: &AppRunnerRef) -> Result<(), JsValue> { { let event_name = "mouseleave"; - let runner = runner.clone(); + let runner_ref = runner_ref.clone(); let closure = Closure::wrap(Box::new(move |event: web_sys::MouseEvent| { - let mut runner = runner.0.lock(); - if !runner.web_input.is_touch { - runner.web_input.mouse_pos = None; - invalidate(&mut runner).unwrap(); + let mut runner_lock = runner_ref.0.lock(); + if !runner_lock.web_input.is_touch { + runner_lock.web_input.mouse_pos = None; + drop(runner_lock); + invalidate(runner_ref.clone()).unwrap(); event.stop_propagation(); event.prevent_default(); } @@ -533,13 +560,14 @@ fn install_canvas_events(runner: &AppRunnerRef) -> Result<(), JsValue> { { let event_name = "touchstart"; - let runner = runner.clone(); + let runner_ref = runner_ref.clone(); let closure = Closure::wrap(Box::new(move |event: web_sys::TouchEvent| { - let mut runner = runner.0.lock(); - runner.web_input.is_touch = true; - runner.web_input.mouse_pos = Some(pos_from_touch_event(&event)); - runner.web_input.mouse_down = true; - invalidate(&mut runner).unwrap(); + let mut runner_lock = runner_ref.0.lock(); + runner_lock.web_input.is_touch = true; + runner_lock.web_input.mouse_pos = Some(pos_from_touch_event(&event)); + runner_lock.web_input.mouse_down = true; + drop(runner_lock); + invalidate(runner_ref.clone()).unwrap(); event.stop_propagation(); event.prevent_default(); }) as Box); @@ -549,12 +577,13 @@ fn install_canvas_events(runner: &AppRunnerRef) -> Result<(), JsValue> { { let event_name = "touchmove"; - let runner = runner.clone(); + let runner_ref = runner_ref.clone(); let closure = Closure::wrap(Box::new(move |event: web_sys::TouchEvent| { - let mut runner = runner.0.lock(); - runner.web_input.is_touch = true; - runner.web_input.mouse_pos = Some(pos_from_touch_event(&event)); - invalidate(&mut runner).unwrap(); + let mut runner_lock = runner_ref.0.lock(); + runner_lock.web_input.is_touch = true; + runner_lock.web_input.mouse_pos = Some(pos_from_touch_event(&event)); + drop(runner_lock); + invalidate(runner_ref.clone()).unwrap(); event.stop_propagation(); event.prevent_default(); }) as Box); @@ -564,14 +593,16 @@ fn install_canvas_events(runner: &AppRunnerRef) -> Result<(), JsValue> { { let event_name = "touchend"; - let runner = runner.clone(); + let runner_ref = runner_ref.clone(); let closure = Closure::wrap(Box::new(move |event: web_sys::TouchEvent| { - let mut runner = runner.0.lock(); - runner.web_input.is_touch = true; - runner.web_input.mouse_down = false; // First release mouse to click... - runner.paint().unwrap(); // ...do the clicking... - runner.web_input.mouse_pos = None; // ...remove hover effect - invalidate(&mut runner).unwrap(); + // TODO: this paints twice in one frame, which is not great + let mut runner_lock = runner_ref.0.lock(); + runner_lock.web_input.is_touch = true; + runner_lock.web_input.mouse_down = false; // First release mouse to click... + runner_lock.paint().unwrap(); // ...do the clicking... + runner_lock.web_input.mouse_pos = None; // ...remove hover effect + drop(runner_lock); + invalidate(runner_ref.clone()).unwrap(); event.stop_propagation(); event.prevent_default(); }) as Box); @@ -581,12 +612,13 @@ fn install_canvas_events(runner: &AppRunnerRef) -> Result<(), JsValue> { { let event_name = "wheel"; - let runner = runner.clone(); + let runner_ref = runner_ref.clone(); let closure = Closure::wrap(Box::new(move |event: web_sys::WheelEvent| { - let mut runner = runner.0.lock(); - runner.web_input.scroll_delta.x -= event.delta_x() as f32; - runner.web_input.scroll_delta.y -= event.delta_y() as f32; - invalidate(&mut runner).unwrap(); + let mut runner_lock = runner_ref.0.lock(); + runner_lock.web_input.scroll_delta.x -= event.delta_x() as f32; + runner_lock.web_input.scroll_delta.y -= event.delta_y() as f32; + drop(runner_lock); + invalidate(runner_ref.clone()).unwrap(); event.stop_propagation(); event.prevent_default(); }) as Box); diff --git a/example_wasm/src/lib.rs b/example_wasm/src/lib.rs index 6f5c0192..e6d81e99 100644 --- a/example_wasm/src/lib.rs +++ b/example_wasm/src/lib.rs @@ -10,8 +10,9 @@ use wasm_bindgen::prelude::*; /// This is the entry-point for all the web-assembly. #[wasm_bindgen] pub fn start(canvas_id: &str) -> Result<(), JsValue> { + let backend = egui_web::Backend::new(canvas_id, egui_web::RunMode::Continuous)?; let app = Box::new(MyApp::default()); - let runner = egui_web::AppRunner::new(canvas_id, app)?; + let runner = egui_web::AppRunner::new(backend, app)?; egui_web::run(runner)?; Ok(()) } @@ -24,7 +25,7 @@ pub struct MyApp { } impl egui_web::App for MyApp { - fn ui(&mut self, ui: &mut egui::Ui, info: &egui_web::BackendInfo) { + fn ui(&mut self, ui: &mut egui::Ui, backend: &mut egui_web::Backend, info: &egui_web::WebInfo) { self.example_app.ui(ui, &info.web_location_hash); let mut ui = ui.centered_column(ui.available().width().min(480.0)); @@ -44,16 +45,34 @@ impl egui_web::App for MyApp { ui.label("WebGl painter info:"); ui.indent("webgl region id", |ui| { - ui.label(&info.painter_debug_info); + ui.label(&backend.painter_debug_info()); }); ui.add( label!( - "CPU usage: {:.2} ms (excludes painting)", - 1e3 * info.cpu_time + "CPU usage: {:.2} ms / frame (excludes painting)", + 1e3 * backend.cpu_time() ) .text_style(TextStyle::Monospace), ); - ui.add(label!("FPS: {:.1}", info.fps).text_style(TextStyle::Monospace)); + + ui.horizontal(|ui| { + let mut run_mode = backend.run_mode(); + ui.label("Run mode:"); + ui.radio_value("Continuous", &mut run_mode, egui_web::RunMode::Continuous) + .tooltip_text("Repaint everything each frame"); + ui.radio_value("Reactive", &mut run_mode, egui_web::RunMode::Reactive) + .tooltip_text("Repaint when there is new input (e.g. mouse movement)"); + backend.set_run_mode(run_mode); + }); + + if backend.run_mode() == egui_web::RunMode::Continuous { + ui.add( + label!("Repainting the UI each frame. FPS: {:.1}", backend.fps()) + .text_style(TextStyle::Monospace), + ); + } else { + ui.label("Only running UI code when there is new input"); + } } }