Implement repaint_after for eframe web (#1760)
* Implement repaint_after for eframe web Follow-up to #1694 * cargo fmt * Simplify demo UI for "repaint_after"
This commit is contained in:
parent
935913b1ec
commit
eeae485629
5 changed files with 73 additions and 86 deletions
|
@ -2,7 +2,9 @@ use super::{glow_wrapping::WrappedGlowPainter, *};
|
|||
|
||||
use crate::epi;
|
||||
|
||||
use egui::mutex::{Mutex, MutexGuard};
|
||||
use egui::TexturesDelta;
|
||||
|
||||
pub use egui::{pos2, Color32};
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
@ -34,21 +36,34 @@ impl WebInput {
|
|||
|
||||
use std::sync::atomic::Ordering::SeqCst;
|
||||
|
||||
pub struct NeedRepaint(std::sync::atomic::AtomicBool);
|
||||
/// Stores when to do the next repaint.
|
||||
pub struct NeedRepaint(Mutex<f64>);
|
||||
|
||||
impl Default for NeedRepaint {
|
||||
fn default() -> Self {
|
||||
Self(true.into())
|
||||
Self(Mutex::new(f64::NEG_INFINITY)) // start with a repaint
|
||||
}
|
||||
}
|
||||
|
||||
impl NeedRepaint {
|
||||
pub fn fetch_and_clear(&self) -> bool {
|
||||
self.0.swap(false, SeqCst)
|
||||
/// Returns the time (in [`now_sec`] scale) when
|
||||
/// we should next repaint.
|
||||
pub fn when_to_repaint(&self) -> f64 {
|
||||
*self.0.lock()
|
||||
}
|
||||
|
||||
pub fn set_true(&self) {
|
||||
self.0.store(true, SeqCst);
|
||||
/// Unschedule repainting.
|
||||
pub fn clear(&self) {
|
||||
*self.0.lock() = f64::INFINITY;
|
||||
}
|
||||
|
||||
pub fn repaint_after(&self, num_seconds: f64) {
|
||||
let mut repaint_time = self.0.lock();
|
||||
*repaint_time = repaint_time.min(now_sec() + num_seconds);
|
||||
}
|
||||
|
||||
pub fn repaint_asap(&self) {
|
||||
*self.0.lock() = f64::NEG_INFINITY;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -194,7 +209,7 @@ impl AppRunner {
|
|||
{
|
||||
let needs_repaint = needs_repaint.clone();
|
||||
egui_ctx.set_request_repaint_callback(move || {
|
||||
needs_repaint.0.store(true, SeqCst);
|
||||
needs_repaint.repaint_asap();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -420,7 +435,6 @@ fn start_runner(app_runner: AppRunner) -> Result<AppRunnerRef, JsValue> {
|
|||
super::events::install_canvas_events(&runner_container)?;
|
||||
super::events::install_document_events(&runner_container)?;
|
||||
text_agent::install_text_agent(&runner_container)?;
|
||||
super::events::repaint_every_ms(&runner_container, 1000)?; // just in case. TODO(emilk): make it a parameter
|
||||
|
||||
super::events::paint_and_schedule(&runner_container.runner, runner_container.panicked.clone())?;
|
||||
|
||||
|
|
|
@ -7,14 +7,14 @@ pub fn paint_and_schedule(
|
|||
) -> Result<(), JsValue> {
|
||||
fn paint_if_needed(runner_ref: &AppRunnerRef) -> Result<(), JsValue> {
|
||||
let mut runner_lock = runner_ref.lock();
|
||||
if runner_lock.needs_repaint.fetch_and_clear() {
|
||||
if 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()?;
|
||||
runner_lock.paint(&clipped_primitives)?;
|
||||
if repaint_after.is_zero() {
|
||||
runner_lock.needs_repaint.set_true();
|
||||
}
|
||||
// TODO: schedule a repaint after `repaint_after` when it is not zero
|
||||
runner_lock
|
||||
.needs_repaint
|
||||
.repaint_after(repaint_after.as_secs_f64());
|
||||
runner_lock.auto_save();
|
||||
}
|
||||
|
||||
|
@ -75,7 +75,7 @@ pub fn install_document_events(runner_container: &AppRunnerContainer) -> Result<
|
|||
{
|
||||
runner_lock.input.raw.events.push(egui::Event::Text(key));
|
||||
}
|
||||
runner_lock.needs_repaint.set_true();
|
||||
runner_lock.needs_repaint.repaint_asap();
|
||||
|
||||
let egui_wants_keyboard = runner_lock.egui_ctx().wants_keyboard_input();
|
||||
|
||||
|
@ -123,7 +123,7 @@ pub fn install_document_events(runner_container: &AppRunnerContainer) -> Result<
|
|||
modifiers,
|
||||
});
|
||||
}
|
||||
runner_lock.needs_repaint.set_true();
|
||||
runner_lock.needs_repaint.repaint_asap();
|
||||
},
|
||||
)?;
|
||||
|
||||
|
@ -137,7 +137,7 @@ pub fn install_document_events(runner_container: &AppRunnerContainer) -> Result<
|
|||
let text = text.replace("\r\n", "\n");
|
||||
if !text.is_empty() {
|
||||
runner_lock.input.raw.events.push(egui::Event::Paste(text));
|
||||
runner_lock.needs_repaint.set_true();
|
||||
runner_lock.needs_repaint.repaint_asap();
|
||||
}
|
||||
event.stop_propagation();
|
||||
event.prevent_default();
|
||||
|
@ -152,7 +152,7 @@ pub fn install_document_events(runner_container: &AppRunnerContainer) -> Result<
|
|||
"cut",
|
||||
|_: web_sys::ClipboardEvent, mut runner_lock| {
|
||||
runner_lock.input.raw.events.push(egui::Event::Cut);
|
||||
runner_lock.needs_repaint.set_true();
|
||||
runner_lock.needs_repaint.repaint_asap();
|
||||
},
|
||||
)?;
|
||||
|
||||
|
@ -162,7 +162,7 @@ pub fn install_document_events(runner_container: &AppRunnerContainer) -> Result<
|
|||
"copy",
|
||||
|_: web_sys::ClipboardEvent, mut runner_lock| {
|
||||
runner_lock.input.raw.events.push(egui::Event::Copy);
|
||||
runner_lock.needs_repaint.set_true();
|
||||
runner_lock.needs_repaint.repaint_asap();
|
||||
},
|
||||
)?;
|
||||
|
||||
|
@ -171,7 +171,7 @@ pub fn install_document_events(runner_container: &AppRunnerContainer) -> Result<
|
|||
&window,
|
||||
event_name,
|
||||
|_: web_sys::Event, runner_lock| {
|
||||
runner_lock.needs_repaint.set_true();
|
||||
runner_lock.needs_repaint.repaint_asap();
|
||||
},
|
||||
)?;
|
||||
}
|
||||
|
@ -190,38 +190,6 @@ pub fn install_document_events(runner_container: &AppRunnerContainer) -> Result<
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Repaint at least every `ms` milliseconds.
|
||||
pub fn repaint_every_ms(
|
||||
runner_container: &AppRunnerContainer,
|
||||
milliseconds: i32,
|
||||
) -> Result<(), JsValue> {
|
||||
assert!(milliseconds >= 0);
|
||||
|
||||
use wasm_bindgen::JsCast;
|
||||
|
||||
let window = web_sys::window().unwrap();
|
||||
|
||||
let closure = Closure::wrap(Box::new({
|
||||
let runner = runner_container.runner.clone();
|
||||
let panicked = runner_container.panicked.clone();
|
||||
|
||||
move || {
|
||||
// Do not lock the runner if the code has panicked
|
||||
if !panicked.load(Ordering::SeqCst) {
|
||||
runner.lock().needs_repaint.set_true();
|
||||
}
|
||||
}
|
||||
}) as Box<dyn FnMut()>);
|
||||
|
||||
window.set_interval_with_callback_and_timeout_and_arguments_0(
|
||||
closure.as_ref().unchecked_ref(),
|
||||
milliseconds,
|
||||
)?;
|
||||
|
||||
closure.forget();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn install_canvas_events(runner_container: &AppRunnerContainer) -> Result<(), JsValue> {
|
||||
use wasm_bindgen::JsCast;
|
||||
let canvas = canvas_element(runner_container.runner.lock().canvas_id()).unwrap();
|
||||
|
@ -254,7 +222,7 @@ pub fn install_canvas_events(runner_container: &AppRunnerContainer) -> Result<()
|
|||
pressed: true,
|
||||
modifiers,
|
||||
});
|
||||
runner_lock.needs_repaint.set_true();
|
||||
runner_lock.needs_repaint.repaint_asap();
|
||||
}
|
||||
event.stop_propagation();
|
||||
// Note: prevent_default breaks VSCode tab focusing, hence why we don't call it here.
|
||||
|
@ -271,7 +239,7 @@ pub fn install_canvas_events(runner_container: &AppRunnerContainer) -> Result<()
|
|||
.raw
|
||||
.events
|
||||
.push(egui::Event::PointerMoved(pos));
|
||||
runner_lock.needs_repaint.set_true();
|
||||
runner_lock.needs_repaint.repaint_asap();
|
||||
event.stop_propagation();
|
||||
event.prevent_default();
|
||||
},
|
||||
|
@ -294,7 +262,7 @@ pub fn install_canvas_events(runner_container: &AppRunnerContainer) -> Result<()
|
|||
pressed: false,
|
||||
modifiers,
|
||||
});
|
||||
runner_lock.needs_repaint.set_true();
|
||||
runner_lock.needs_repaint.repaint_asap();
|
||||
|
||||
text_agent::update_text_agent(runner_lock);
|
||||
}
|
||||
|
@ -308,7 +276,7 @@ pub fn install_canvas_events(runner_container: &AppRunnerContainer) -> Result<()
|
|||
"mouseleave",
|
||||
|event: web_sys::MouseEvent, mut runner_lock| {
|
||||
runner_lock.input.raw.events.push(egui::Event::PointerGone);
|
||||
runner_lock.needs_repaint.set_true();
|
||||
runner_lock.needs_repaint.repaint_asap();
|
||||
event.stop_propagation();
|
||||
event.prevent_default();
|
||||
},
|
||||
|
@ -336,7 +304,7 @@ pub fn install_canvas_events(runner_container: &AppRunnerContainer) -> Result<()
|
|||
});
|
||||
|
||||
push_touches(&mut *runner_lock, egui::TouchPhase::Start, &event);
|
||||
runner_lock.needs_repaint.set_true();
|
||||
runner_lock.needs_repaint.repaint_asap();
|
||||
event.stop_propagation();
|
||||
event.prevent_default();
|
||||
},
|
||||
|
@ -358,7 +326,7 @@ pub fn install_canvas_events(runner_container: &AppRunnerContainer) -> Result<()
|
|||
.push(egui::Event::PointerMoved(pos));
|
||||
|
||||
push_touches(&mut *runner_lock, egui::TouchPhase::Move, &event);
|
||||
runner_lock.needs_repaint.set_true();
|
||||
runner_lock.needs_repaint.repaint_asap();
|
||||
event.stop_propagation();
|
||||
event.prevent_default();
|
||||
},
|
||||
|
@ -385,7 +353,7 @@ pub fn install_canvas_events(runner_container: &AppRunnerContainer) -> Result<()
|
|||
runner_lock.input.raw.events.push(egui::Event::PointerGone);
|
||||
|
||||
push_touches(&mut *runner_lock, egui::TouchPhase::End, &event);
|
||||
runner_lock.needs_repaint.set_true();
|
||||
runner_lock.needs_repaint.repaint_asap();
|
||||
event.stop_propagation();
|
||||
event.prevent_default();
|
||||
}
|
||||
|
@ -444,7 +412,7 @@ pub fn install_canvas_events(runner_container: &AppRunnerContainer) -> Result<()
|
|||
.push(egui::Event::Scroll(delta));
|
||||
}
|
||||
|
||||
runner_lock.needs_repaint.set_true();
|
||||
runner_lock.needs_repaint.repaint_asap();
|
||||
event.stop_propagation();
|
||||
event.prevent_default();
|
||||
},
|
||||
|
@ -464,7 +432,7 @@ pub fn install_canvas_events(runner_container: &AppRunnerContainer) -> Result<()
|
|||
});
|
||||
}
|
||||
}
|
||||
runner_lock.needs_repaint.set_true();
|
||||
runner_lock.needs_repaint.repaint_asap();
|
||||
event.stop_propagation();
|
||||
event.prevent_default();
|
||||
}
|
||||
|
@ -476,7 +444,7 @@ pub fn install_canvas_events(runner_container: &AppRunnerContainer) -> Result<()
|
|||
"dragleave",
|
||||
|event: web_sys::DragEvent, mut runner_lock| {
|
||||
runner_lock.input.raw.hovered_files.clear();
|
||||
runner_lock.needs_repaint.set_true();
|
||||
runner_lock.needs_repaint.repaint_asap();
|
||||
event.stop_propagation();
|
||||
event.prevent_default();
|
||||
},
|
||||
|
@ -488,7 +456,7 @@ pub fn install_canvas_events(runner_container: &AppRunnerContainer) -> Result<()
|
|||
move |event: web_sys::DragEvent, mut runner_lock| {
|
||||
if let Some(data_transfer) = event.data_transfer() {
|
||||
runner_lock.input.raw.hovered_files.clear();
|
||||
runner_lock.needs_repaint.set_true();
|
||||
runner_lock.needs_repaint.repaint_asap();
|
||||
// Unlock the runner so it can be locked after a future await point
|
||||
drop(runner_lock);
|
||||
|
||||
|
@ -524,7 +492,7 @@ pub fn install_canvas_events(runner_container: &AppRunnerContainer) -> Result<()
|
|||
..Default::default()
|
||||
},
|
||||
);
|
||||
runner_lock.needs_repaint.set_true();
|
||||
runner_lock.needs_repaint.repaint_asap();
|
||||
}
|
||||
Err(err) => {
|
||||
tracing::error!("Failed to read file: {:?}", err);
|
||||
|
|
|
@ -20,7 +20,6 @@ use std::sync::{
|
|||
Arc,
|
||||
};
|
||||
|
||||
use egui::mutex::{Mutex, MutexGuard};
|
||||
use wasm_bindgen::prelude::*;
|
||||
use web_sys::EventTarget;
|
||||
|
||||
|
@ -30,7 +29,9 @@ use crate::Theme;
|
|||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// Current time in seconds (since undefined point in time)
|
||||
/// Current time in seconds (since undefined point in time).
|
||||
///
|
||||
/// Monotonically increasing.
|
||||
pub fn now_sec() -> f64 {
|
||||
web_sys::window()
|
||||
.expect("should have a Window")
|
||||
|
|
|
@ -55,7 +55,7 @@ pub fn install_text_agent(runner_container: &AppRunnerContainer) -> Result<(), J
|
|||
if !text.is_empty() && !is_composing.get() {
|
||||
input_clone.set_value("");
|
||||
runner_lock.input.raw.events.push(egui::Event::Text(text));
|
||||
runner_lock.needs_repaint.set_true();
|
||||
runner_lock.needs_repaint.repaint_asap();
|
||||
}
|
||||
}
|
||||
})?;
|
||||
|
@ -75,7 +75,7 @@ pub fn install_text_agent(runner_container: &AppRunnerContainer) -> Result<(), J
|
|||
.raw
|
||||
.events
|
||||
.push(egui::Event::CompositionStart);
|
||||
runner_lock.needs_repaint.set_true();
|
||||
runner_lock.needs_repaint.repaint_asap();
|
||||
}
|
||||
})?;
|
||||
|
||||
|
@ -85,7 +85,7 @@ pub fn install_text_agent(runner_container: &AppRunnerContainer) -> Result<(), J
|
|||
move |event: web_sys::CompositionEvent, mut runner_lock: MutexGuard<'_, AppRunner>| {
|
||||
if let Some(event) = event.data().map(egui::Event::CompositionUpdate) {
|
||||
runner_lock.input.raw.events.push(event);
|
||||
runner_lock.needs_repaint.set_true();
|
||||
runner_lock.needs_repaint.repaint_asap();
|
||||
}
|
||||
},
|
||||
)?;
|
||||
|
@ -99,7 +99,7 @@ pub fn install_text_agent(runner_container: &AppRunnerContainer) -> Result<(), J
|
|||
|
||||
if let Some(event) = event.data().map(egui::Event::CompositionEnd) {
|
||||
runner_lock.input.raw.events.push(event);
|
||||
runner_lock.needs_repaint.set_true();
|
||||
runner_lock.needs_repaint.repaint_asap();
|
||||
}
|
||||
}
|
||||
})?;
|
||||
|
|
|
@ -49,12 +49,11 @@ pub struct BackendPanel {
|
|||
pub open: bool,
|
||||
|
||||
#[cfg_attr(feature = "serde", serde(skip))]
|
||||
// go back to [`Reactive`] mode each time we start
|
||||
// go back to [`RunMode::Reactive`] mode each time we start
|
||||
run_mode: RunMode,
|
||||
|
||||
#[cfg_attr(feature = "serde", serde(skip))]
|
||||
// reset to 1 second as default repaint_after idle timeout.
|
||||
repaint_after_timeout: std::time::Duration,
|
||||
repaint_after_seocnds: f32,
|
||||
|
||||
/// current slider value for current gui scale
|
||||
#[cfg_attr(feature = "serde", serde(skip))]
|
||||
|
@ -71,7 +70,7 @@ impl Default for BackendPanel {
|
|||
Self {
|
||||
open: false,
|
||||
run_mode: Default::default(),
|
||||
repaint_after_timeout: std::time::Duration::from_secs(1),
|
||||
repaint_after_seocnds: 1.0,
|
||||
pixels_per_point: None,
|
||||
frame_history: Default::default(),
|
||||
egui_windows: Default::default(),
|
||||
|
@ -85,13 +84,16 @@ impl BackendPanel {
|
|||
.on_new_frame(ctx.input().time, frame.info().cpu_usage);
|
||||
|
||||
match self.run_mode {
|
||||
RunMode::Reactive => {
|
||||
ctx.request_repaint_after(self.repaint_after_timeout);
|
||||
}
|
||||
RunMode::Continuous => {
|
||||
// Tell the backend to repaint as soon as possible
|
||||
ctx.request_repaint();
|
||||
}
|
||||
RunMode::Reactive => {
|
||||
// let the computer rest for a bit
|
||||
ctx.request_repaint_after(std::time::Duration::from_secs_f32(
|
||||
self.repaint_after_seocnds,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -243,16 +245,18 @@ impl BackendPanel {
|
|||
));
|
||||
} else {
|
||||
ui.label("Only running UI code when there are animations or input.");
|
||||
ui.label("but if there's no input for the repaint_after duration, we force an update");
|
||||
ui.label("repaint_after (in seconds)");
|
||||
let mut seconds = self.repaint_after_timeout.as_secs_f32();
|
||||
if egui::DragValue::new(&mut seconds)
|
||||
.clamp_range(0.1..=10.0)
|
||||
.ui(ui)
|
||||
.changed()
|
||||
{
|
||||
self.repaint_after_timeout = std::time::Duration::from_secs_f32(seconds);
|
||||
}
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.spacing_mut().item_spacing.x = 0.0;
|
||||
ui.label("(but at least every ");
|
||||
egui::DragValue::new(&mut self.repaint_after_seocnds)
|
||||
.clamp_range(0.1..=10.0)
|
||||
.speed(0.1)
|
||||
.suffix(" s")
|
||||
.ui(ui)
|
||||
.on_hover_text("Repaint this often, even if there is no input.");
|
||||
ui.label(")");
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue