Skip to content

Commit

Permalink
redraw on resize
Browse files Browse the repository at this point in the history
  • Loading branch information
jprochazk committed May 24, 2024
1 parent 04771f7 commit a14bc86
Show file tree
Hide file tree
Showing 5 changed files with 103 additions and 35 deletions.
4 changes: 4 additions & 0 deletions crates/eframe/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -236,10 +236,14 @@ web-sys = { workspace = true, features = [
"MediaQueryListEvent",
"MouseEvent",
"Navigator",
"Node",
"NodeList",
"Performance",
"ResizeObserver",
"ResizeObserverEntry",
"ResizeObserverBoxOptions",
"ResizeObserverOptions",
"ResizeObserverSize",
"Storage",
"Touch",
"TouchEvent",
Expand Down
2 changes: 1 addition & 1 deletion crates/eframe/src/web/app_runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ impl AppRunner {
///
/// The result can be painted later with a call to [`Self::run_and_paint`] or [`Self::paint`].
pub fn logic(&mut self) {
super::resize_canvas_to_screen_size(self.canvas(), self.web_options.max_size_points);
// super::resize_canvas_to_screen_size(self.canvas(), self.web_options.max_size_points);
let canvas_size = super::canvas_size_in_points(self.canvas(), self.egui_ctx());
let raw_input = self.input.new_frame(canvas_size);

Expand Down
71 changes: 49 additions & 22 deletions crates/eframe/src/web/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,7 @@ use super::*;

// ------------------------------------------------------------------------

/// Calls `request_animation_frame` to schedule repaint.
///
/// It will only paint if needed, but will always call `request_animation_frame` immediately.
pub(crate) fn paint_and_schedule(runner_ref: &WebRunner) -> Result<(), JsValue> {
// Only paint and schedule if there has been no panic
if let Some(mut runner_lock) = runner_ref.try_lock() {
paint_if_needed(&mut runner_lock);
drop(runner_lock);
runner_ref.request_animation_frame()?;
}
Ok(())
}

fn paint_if_needed(runner: &mut AppRunner) {
pub(crate) fn paint_if_needed(runner: &mut AppRunner) {
if runner.needs_repaint.needs_repaint() {
if runner.has_outstanding_paint_data() {
// We have already run the logic, e.g. in an on-click event,
Expand Down Expand Up @@ -595,25 +582,65 @@ pub(crate) fn install_canvas_events(runner_ref: &WebRunner) -> Result<(), JsValu
pub(crate) fn install_resize_observer(runner_ref: &WebRunner) -> Result<(), JsValue> {
let closure = Closure::wrap(Box::new({
let runner_ref = runner_ref.clone();
move |_: js_sys::Array| {
move |entries: js_sys::Array| {
// Only call the wrapped closure if the egui code has not panicked
if let Some(mut runner_lock) = runner_ref.try_lock() {
runner_lock.needs_repaint.repaint_asap();
let canvas = runner_lock.canvas();
let (width, height) = get_display_size(entries).unwrap();
canvas.set_width(width);
canvas.set_height(height);

// force an immediate repaint
runner_lock.needs_repaint.repaint_asap();
paint_if_needed(&mut runner_lock);
}
}
}) as Box<dyn FnMut(js_sys::Array)>);

let observer = web_sys::ResizeObserver::new(closure.as_ref().unchecked_ref())?;
let mut options = web_sys::ResizeObserverOptions::new();
options.box_(web_sys::ResizeObserverBoxOptions::ContentBox);
let parent_element = runner_ref
.try_lock()
.unwrap()
.canvas()
.parent_element()
.unwrap();
observer.observe_with_options(&parent_element, &options);
observer.observe_with_options(runner_ref.try_lock().unwrap().canvas(), &options);
runner_ref.set_resize_observer(observer, closure);

Ok(())
}

fn get_display_size(resize_observer_entries: js_sys::Array) -> Result<(u32, u32), JsValue> {
let width;
let height;
let mut dpr = web_sys::window().unwrap().device_pixel_ratio();

let entry: web_sys::ResizeObserverEntry = resize_observer_entries.at(0).dyn_into()?;
if JsValue::from_str("devicePixelContentBoxSize").js_in(entry.as_ref()) {
// NOTE: Only this path gives the correct answer for most browsers.
// Unfortunately this doesn't work perfectly everywhere.
let size: web_sys::ResizeObserverSize =
entry.device_pixel_content_box_size().at(0).dyn_into()?;
width = size.inline_size();
height = size.block_size();
dpr = 1.0; // no need to apply
} else if JsValue::from_str("contentBoxSize").js_in(entry.as_ref()) {
let content_box_size = entry.content_box_size();
let idx0 = content_box_size.at(0);
if !idx0.is_undefined() {
let size: web_sys::ResizeObserverSize = idx0.dyn_into()?;
width = size.inline_size();
height = size.block_size();
} else {
// legacy
let size = JsValue::clone(content_box_size.as_ref());
let size: web_sys::ResizeObserverSize = size.dyn_into()?;
width = size.inline_size();
height = size.block_size();
}
} else {
// legacy
let content_rect = entry.content_rect();
width = content_rect.width();
height = content_rect.height();
}

Ok(((width.round() * dpr) as u32, (height.round() * dpr) as u32))
}
59 changes: 47 additions & 12 deletions crates/eframe/src/web/web_runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ pub struct WebRunner {
/// the panic handler, since they aren't `Send`.
events_to_unsubscribe: Rc<RefCell<Vec<EventToUnsubscribe>>>,

/// Used in `destroy` to cancel a pending frame.
request_animation_frame_id: Cell<Option<i32>>,
/// Current animation frame in flight.
frame: Rc<RefCell<Option<AnimationFrameRequest>>>,

resize_observer: Rc<RefCell<Option<ResizeObserverContext>>>,
}
Expand All @@ -49,7 +49,7 @@ impl WebRunner {
panic_handler,
runner: Rc::new(RefCell::new(None)),
events_to_unsubscribe: Rc::new(RefCell::new(Default::default())),
request_animation_frame_id: Cell::new(None),
frame: Default::default(),
resize_observer: Default::default(),
}
}
Expand Down Expand Up @@ -116,7 +116,7 @@ impl WebRunner {
}

if let Some(context) = self.resize_observer.take() {
context.observer.disconnect();
context.resize_observer.disconnect();
drop(context.closure);
}
}
Expand All @@ -125,9 +125,9 @@ impl WebRunner {
pub fn destroy(&self) {
self.unsubscribe_from_all_events();

if let Some(id) = self.request_animation_frame_id.get() {
if let Some(frame) = self.frame.take() {
let window = web_sys::window().unwrap();
window.cancel_animation_frame(id).ok();
window.cancel_animation_frame(frame.id).ok();
}

if let Some(runner) = self.runner.replace(None) {
Expand Down Expand Up @@ -203,32 +203,67 @@ impl WebRunner {
}

pub(crate) fn request_animation_frame(&self) -> Result<(), wasm_bindgen::JsValue> {
if self.frame.borrow().is_some() {
// there is already an animation frame in flight
return Ok(());
}

let window = web_sys::window().unwrap();
let closure = Closure::once({
let runner_ref = self.clone();
move || events::paint_and_schedule(&runner_ref)
move || {
// animation frame is running and can't be cancelled anymore
let _ = runner_ref.frame.take();

// if there hasn't been a panic, then paint and schedule.
if let Some(mut runner_lock) = runner_ref.try_lock() {
events::paint_if_needed(&mut runner_lock);
drop(runner_lock);
runner_ref.request_animation_frame()?;
}

Ok(())
}
});

let id = window.request_animation_frame(closure.as_ref().unchecked_ref())?;
self.request_animation_frame_id.set(Some(id));
closure.forget(); // We must forget it, or else the callback is canceled on drop
self.frame
.borrow_mut()
.replace(AnimationFrameRequest { id, closure });

Ok(())
}

pub(crate) fn set_resize_observer(
&self,
observer: web_sys::ResizeObserver,
resize_observer: web_sys::ResizeObserver,
closure: Closure<dyn FnMut(js_sys::Array)>,
) {
self.resize_observer
.borrow_mut()
.replace(ResizeObserverContext { observer, closure });
.replace(ResizeObserverContext {
resize_observer,
closure,
});
}
}

// ----------------------------------------------------------------------------

struct AnimationFrameRequest {
/// Represents the ID of a frame in flight.
///
/// This is only set between a call to `request_animation_frame` and the invocation of its callback,
/// which means that repeated calls to `request_animation_frame` will be ignored.
id: i32,

/// The callback given to `request_animation_frame`, stored here both to prevent it
/// from being canceled, and from having to `.forget()` it.
closure: Closure<dyn FnMut() -> Result<(), JsValue>>,
}

struct ResizeObserverContext {
observer: web_sys::ResizeObserver,
resize_observer: web_sys::ResizeObserver,
closure: Closure<dyn FnMut(js_sys::Array)>,
}

Expand Down
2 changes: 2 additions & 0 deletions web_demo/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@
position: absolute;
top: 0%;
left: 50%;
width: 100%;
height: 100%;
transform: translate(-50%, 0%);
}

Expand Down

0 comments on commit a14bc86

Please sign in to comment.