Skip to content

Commit 217e817

Browse files
committed
Add setAutoPause() method, to minimize system resource use when offscreen
1 parent 9415075 commit 217e817

9 files changed

Lines changed: 276 additions & 76 deletions

File tree

default.profraw

14.7 MB
Binary file not shown.

rust/perspective-viewer/src/rust/custom_elements/viewer.rs

Lines changed: 24 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -33,78 +33,6 @@ use crate::session::Session;
3333
use crate::utils::*;
3434
use crate::*;
3535

36-
struct ResizeObserverHandle {
37-
elem: HtmlElement,
38-
observer: ResizeObserver,
39-
_callback: Closure<dyn FnMut(js_sys::Array)>,
40-
}
41-
42-
impl ResizeObserverHandle {
43-
fn new(elem: &HtmlElement, renderer: &Renderer, root: &AppHandle<PerspectiveViewer>) -> Self {
44-
let on_resize = root.callback(|()| PerspectiveViewerMsg::Resize);
45-
let mut state = ResizeObserverState {
46-
elem: elem.clone(),
47-
renderer: renderer.clone(),
48-
width: elem.offset_width(),
49-
height: elem.offset_height(),
50-
on_resize,
51-
};
52-
53-
let _callback = (move |xs| state.on_resize(&xs)).into_closure_mut();
54-
let func = _callback.as_ref().unchecked_ref::<js_sys::Function>();
55-
let observer = ResizeObserver::new(func);
56-
observer.observe(elem);
57-
Self {
58-
elem: elem.clone(),
59-
_callback,
60-
observer,
61-
}
62-
}
63-
}
64-
65-
impl Drop for ResizeObserverHandle {
66-
fn drop(&mut self) {
67-
self.observer.unobserve(&self.elem);
68-
}
69-
}
70-
71-
struct ResizeObserverState {
72-
elem: HtmlElement,
73-
renderer: Renderer,
74-
width: i32,
75-
height: i32,
76-
on_resize: Callback<()>,
77-
}
78-
79-
impl ResizeObserverState {
80-
fn on_resize(&mut self, entries: &js_sys::Array) {
81-
let is_visible = self
82-
.elem
83-
.offset_parent()
84-
.map(|x| !x.is_null())
85-
.unwrap_or(false);
86-
87-
for y in entries.iter() {
88-
let entry: ResizeObserverEntry = y.unchecked_into();
89-
let content = entry.content_rect();
90-
let content_width = content.width().floor() as i32;
91-
let content_height = content.height().floor() as i32;
92-
let resized = self.width != content_width || self.height != content_height;
93-
if resized && is_visible {
94-
clone!(self.on_resize, self.renderer);
95-
ApiFuture::spawn(async move {
96-
renderer.resize().await?;
97-
on_resize.emit(());
98-
Ok(())
99-
});
100-
}
101-
102-
self.width = content_width;
103-
self.height = content_height;
104-
}
105-
}
106-
}
107-
10836
/// A `customElements` class which encapsulates both the `<perspective-viewer>`
10937
/// public API, as well as the Rust component state.
11038
///
@@ -131,6 +59,7 @@ pub struct PerspectiveViewerElement {
13159
elem: HtmlElement,
13260
root: Rc<RefCell<Option<AppHandle<PerspectiveViewer>>>>,
13361
resize_handle: Rc<RefCell<Option<ResizeObserverHandle>>>,
62+
intersection_handle: Rc<RefCell<Option<IntersectionObserverHandle>>>,
13463
session: Session,
13564
renderer: Renderer,
13665
presentation: Presentation,
@@ -192,6 +121,7 @@ impl PerspectiveViewerElement {
192121
renderer,
193122
presentation,
194123
resize_handle: Rc::new(RefCell::new(Some(resize_handle))),
124+
intersection_handle: Rc::new(RefCell::new(None)),
195125
_events: events,
196126
_subscriptions: Rc::new(update_sub),
197127
}
@@ -485,13 +415,12 @@ impl PerspectiveViewerElement {
485415
ApiFuture::new(async move { renderer.resize().await })
486416
}
487417

488-
/// Sets the auto-size behavior of this component. When `true`, this
418+
/// Sets the auto-size behavior of this component. When `true`, this
489419
/// `<perspective-viewer>` will register a `ResizeObserver` on itself and
490420
/// call `resize()` whenever its own dimensions change.
491421
///
492422
/// # Arguments
493-
/// - `autosize` Whether to register a `ResizeObserver` on this element or
494-
/// not.
423+
/// - `autosize` Whether to enable `auto-size` behavior or not.
495424
#[wasm_bindgen(js_name = "setAutoSize")]
496425
pub fn set_auto_size(&mut self, autosize: bool) {
497426
if autosize {
@@ -506,6 +435,26 @@ impl PerspectiveViewerElement {
506435
}
507436
}
508437

438+
/// Sets the auto-pause behavior of this component. When `true`, this
439+
/// `<perspective-viewer>` will register an `IntersectionObserver` on
440+
/// itself and call `pause()` whenever its viewport visibility changes.
441+
///
442+
/// # Arguments
443+
/// - `autopause` Whether to enable `auto-pause` behavior or not.
444+
#[wasm_bindgen(js_name = "setAutoPause")]
445+
pub fn set_auto_pause(&mut self, autopause: bool) {
446+
if autopause {
447+
let handle = Some(IntersectionObserverHandle::new(
448+
&self.elem,
449+
&self.session,
450+
&self.renderer,
451+
));
452+
*self.intersection_handle.borrow_mut() = handle;
453+
} else {
454+
*self.intersection_handle.borrow_mut() = None;
455+
}
456+
}
457+
509458
/// Get this viewer's edit port for the currently loaded `Table`.
510459
#[wasm_bindgen(js_name = "getEditPort")]
511460
pub fn get_edit_port(&self) -> Result<f64, JsValue> {
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
2+
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
3+
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
4+
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
5+
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
6+
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
7+
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
8+
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
9+
// ┃ This file is part of the Perspective library, distributed under the terms ┃
10+
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
11+
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12+
13+
use wasm_bindgen::prelude::*;
14+
15+
#[wasm_bindgen(inline_js = "export const IntersectionObserver = window.IntersectionObserver")]
16+
extern "C" {
17+
pub type IntersectionObserver;
18+
19+
#[wasm_bindgen(constructor, js_class = "IntersectionObserver")]
20+
pub fn new(callback: &js_sys::Function) -> IntersectionObserver;
21+
22+
#[wasm_bindgen(method)]
23+
pub fn observe(this: &IntersectionObserver, elem: &web_sys::HtmlElement);
24+
25+
#[wasm_bindgen(method)]
26+
pub fn unobserve(this: &IntersectionObserver, elem: &web_sys::HtmlElement);
27+
28+
pub type IntersectionObserverEntry;
29+
30+
#[wasm_bindgen(method, getter, js_name = "isIntersecting")]
31+
pub fn is_intersecting(this: &IntersectionObserverEntry) -> bool;
32+
}

rust/perspective-viewer/src/rust/js/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
1717
mod clipboard;
1818
pub mod clipboard_item;
19+
mod intersection_observer;
1920
mod mimetype;
2021
pub mod perspective;
2122
pub mod plugin;
@@ -26,6 +27,7 @@ mod testing;
2627
mod tests;
2728

2829
pub use self::clipboard::*;
30+
pub use self::intersection_observer::*;
2931
pub use self::mimetype::*;
3032
pub use self::perspective::*;
3133
pub use self::plugin::*;
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
2+
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
3+
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
4+
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
5+
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
6+
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
7+
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
8+
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
9+
// ┃ This file is part of the Perspective library, distributed under the terms ┃
10+
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
11+
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12+
13+
use wasm_bindgen::prelude::*;
14+
use wasm_bindgen::JsCast;
15+
use web_sys::*;
16+
17+
use crate::config::*;
18+
use crate::js::*;
19+
use crate::model::*;
20+
use crate::renderer::*;
21+
use crate::session::Session;
22+
use crate::utils::*;
23+
use crate::*;
24+
25+
pub struct IntersectionObserverHandle {
26+
elem: HtmlElement,
27+
observer: IntersectionObserver,
28+
_callback: Closure<dyn FnMut(js_sys::Array)>,
29+
}
30+
31+
impl IntersectionObserverHandle {
32+
pub fn new(elem: &HtmlElement, session: &Session, renderer: &Renderer) -> Self {
33+
clone!(session, renderer);
34+
let _callback = (move |xs: js_sys::Array| {
35+
let intersect = xs
36+
.get(0)
37+
.unchecked_into::<IntersectionObserverEntry>()
38+
.is_intersecting();
39+
40+
clone!(session, renderer);
41+
let state = IntersectionObserverState { session, renderer };
42+
ApiFuture::spawn(state.set_pause(intersect));
43+
})
44+
.into_closure_mut();
45+
46+
let func = _callback.as_ref().unchecked_ref::<js_sys::Function>();
47+
let observer = IntersectionObserver::new(func);
48+
observer.observe(elem);
49+
Self {
50+
elem: elem.clone(),
51+
_callback,
52+
observer,
53+
}
54+
}
55+
}
56+
57+
impl Drop for IntersectionObserverHandle {
58+
fn drop(&mut self) {
59+
self.observer.unobserve(&self.elem);
60+
}
61+
}
62+
63+
struct IntersectionObserverState {
64+
session: Session,
65+
renderer: Renderer,
66+
}
67+
68+
impl IntersectionObserverState {
69+
async fn set_pause(self, intersect: bool) -> ApiResult<()> {
70+
if intersect {
71+
if self.session.set_pause(false) {
72+
tracing::error!("Shellac-ed");
73+
self.update_and_render(ViewConfigUpdate::default()).await?;
74+
}
75+
} else {
76+
self.session.set_pause(true);
77+
};
78+
79+
Ok(())
80+
}
81+
}
82+
83+
derive_model!(Renderer, Session for IntersectionObserverState);

rust/perspective-viewer/src/rust/model/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,14 +70,18 @@ mod copy_export;
7070
mod export_app;
7171
mod export_method;
7272
mod get_viewer_config;
73+
mod intersection_observer;
7374
mod plugin_config;
75+
mod resize_observer;
7476
mod structural;
7577
mod update_and_render;
7678

7779
pub use self::columns_iter_set::*;
7880
pub use self::copy_export::*;
7981
pub use self::export_method::*;
8082
pub use self::get_viewer_config::*;
83+
pub use self::intersection_observer::*;
8184
pub use self::plugin_config::*;
85+
pub use self::resize_observer::*;
8286
pub use self::structural::*;
8387
pub use self::update_and_render::*;
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
2+
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
3+
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
4+
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
5+
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
6+
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
7+
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
8+
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
9+
// ┃ This file is part of the Perspective library, distributed under the terms ┃
10+
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
11+
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12+
13+
use wasm_bindgen::prelude::*;
14+
use wasm_bindgen::JsCast;
15+
use web_sys::*;
16+
use yew::prelude::*;
17+
18+
use crate::components::viewer::{PerspectiveViewer, PerspectiveViewerMsg};
19+
use crate::js::*;
20+
use crate::renderer::*;
21+
use crate::utils::*;
22+
use crate::*;
23+
24+
pub struct ResizeObserverHandle {
25+
elem: HtmlElement,
26+
observer: ResizeObserver,
27+
_callback: Closure<dyn FnMut(js_sys::Array)>,
28+
}
29+
30+
impl ResizeObserverHandle {
31+
pub fn new(
32+
elem: &HtmlElement,
33+
renderer: &Renderer,
34+
root: &AppHandle<PerspectiveViewer>,
35+
) -> Self {
36+
let on_resize = root.callback(|()| PerspectiveViewerMsg::Resize);
37+
let mut state = ResizeObserverState {
38+
elem: elem.clone(),
39+
renderer: renderer.clone(),
40+
width: elem.offset_width(),
41+
height: elem.offset_height(),
42+
on_resize,
43+
};
44+
45+
let _callback = (move |xs| state.on_resize(&xs)).into_closure_mut();
46+
let func = _callback.as_ref().unchecked_ref::<js_sys::Function>();
47+
let observer = ResizeObserver::new(func);
48+
observer.observe(elem);
49+
Self {
50+
elem: elem.clone(),
51+
_callback,
52+
observer,
53+
}
54+
}
55+
}
56+
57+
impl Drop for ResizeObserverHandle {
58+
fn drop(&mut self) {
59+
self.observer.unobserve(&self.elem);
60+
}
61+
}
62+
63+
struct ResizeObserverState {
64+
elem: HtmlElement,
65+
renderer: Renderer,
66+
width: i32,
67+
height: i32,
68+
on_resize: Callback<()>,
69+
}
70+
71+
impl ResizeObserverState {
72+
fn on_resize(&mut self, entries: &js_sys::Array) {
73+
let is_visible = self
74+
.elem
75+
.offset_parent()
76+
.map(|x| !x.is_null())
77+
.unwrap_or(false);
78+
79+
for y in entries.iter() {
80+
let entry: ResizeObserverEntry = y.unchecked_into();
81+
let content = entry.content_rect();
82+
let content_width = content.width().floor() as i32;
83+
let content_height = content.height().floor() as i32;
84+
let resized = self.width != content_width || self.height != content_height;
85+
if resized && is_visible {
86+
clone!(self.on_resize, self.renderer);
87+
ApiFuture::spawn(async move {
88+
renderer.resize().await?;
89+
on_resize.emit(());
90+
Ok(())
91+
});
92+
}
93+
94+
self.width = content_width;
95+
self.height = content_height;
96+
}
97+
}
98+
}

0 commit comments

Comments
 (0)