Skip to content

Commit

Permalink
Implement repaint_after for eframe web
Browse files Browse the repository at this point in the history
Follow-up to #1694
  • Loading branch information
emilk committed Jun 22, 2022
1 parent 935913b commit 798570c
Show file tree
Hide file tree
Showing 4 changed files with 50 additions and 69 deletions.
30 changes: 22 additions & 8 deletions eframe/src/web/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

// ----------------------------------------------------------------------------
Expand Down Expand Up @@ -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()
}

/// 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 set_true(&self) {
self.0.store(true, SeqCst);
pub fn repaint_asap(&self) {
*self.0.lock() = f64::NEG_INFINITY;
}
}

Expand Down Expand Up @@ -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();
});
}

Expand Down Expand Up @@ -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())?;

Expand Down
76 changes: 21 additions & 55 deletions eframe/src/web/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,12 @@ 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();
}

Expand Down Expand Up @@ -75,7 +73,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();

Expand Down Expand Up @@ -123,7 +121,7 @@ pub fn install_document_events(runner_container: &AppRunnerContainer) -> Result<
modifiers,
});
}
runner_lock.needs_repaint.set_true();
runner_lock.needs_repaint.repaint_asap();
},
)?;

Expand All @@ -137,7 +135,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();
Expand All @@ -152,7 +150,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();
},
)?;

Expand All @@ -162,7 +160,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();
},
)?;

Expand All @@ -171,7 +169,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();
},
)?;
}
Expand All @@ -190,38 +188,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();
Expand Down Expand Up @@ -254,7 +220,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.
Expand All @@ -271,7 +237,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();
},
Expand All @@ -294,7 +260,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);
}
Expand All @@ -308,7 +274,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();
},
Expand Down Expand Up @@ -336,7 +302,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();
},
Expand All @@ -358,7 +324,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();
},
Expand All @@ -385,7 +351,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();
}
Expand Down Expand Up @@ -444,7 +410,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();
},
Expand All @@ -464,7 +430,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();
}
Expand All @@ -476,7 +442,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();
},
Expand All @@ -488,7 +454,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);

Expand Down Expand Up @@ -524,7 +490,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);
Expand Down
5 changes: 3 additions & 2 deletions eframe/src/web/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ use std::sync::{
Arc,
};

use egui::mutex::{Mutex, MutexGuard};
use wasm_bindgen::prelude::*;
use web_sys::EventTarget;

Expand All @@ -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")
Expand Down
8 changes: 4 additions & 4 deletions eframe/src/web/text_agent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}
})?;
Expand All @@ -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();
}
})?;

Expand All @@ -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();
}
},
)?;
Expand All @@ -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();
}
}
})?;
Expand Down

0 comments on commit 798570c

Please sign in to comment.