Skip to main content

euv_ui/hook/use_scroll/
impl.rs

1use super::*;
2
3/// Inherent implementation of [`UseScroll`].
4impl UseScroll {
5    /// Creates a handle with both offsets starting at zero.
6    ///
7    /// # Returns
8    ///
9    /// - `UseScroll` - The fresh handle.
10    pub(crate) fn new() -> Self {
11        Self {
12            x: Signal::create(0.0),
13            y: Signal::create(0.0),
14            teardown: Rc::new(RefCell::new(None)),
15        }
16    }
17
18    /// Returns the vertical offset as a snapshot.
19    ///
20    /// # Returns
21    ///
22    /// - `f64` - The vertical scroll offset in CSS pixels.
23    pub fn current_y(&self) -> f64 {
24        self.get_y().get()
25    }
26
27    /// Returns the horizontal offset as a snapshot.
28    ///
29    /// # Returns
30    ///
31    /// - `f64` - The horizontal scroll offset in CSS pixels.
32    pub fn current_x(&self) -> f64 {
33        self.get_x().get()
34    }
35
36    /// Scrolls the page back to the top.
37    ///
38    /// The scroll is smooth when the browser supports it, so the
39    /// reader sees the page travel rather than jump.
40    ///
41    /// On a host without a browser this is a no-op.
42    pub fn scroll_to_top(&self) {
43        #[cfg(target_arch = "wasm32")]
44        {
45            let Some(window) = browser_window() else {
46                return;
47            };
48            window.scroll_to_with_x_and_y(0.0, 0.0);
49        }
50    }
51
52    /// Subscribes to the window's `scroll` event and seeds the offsets
53    /// with the browser's current values.
54    ///
55    /// On a host without a browser this is a no-op and the offsets stay
56    /// at zero.
57    pub(crate) fn attach(&self) {
58        #[cfg(target_arch = "wasm32")]
59        {
60            let Some(window) = browser_window() else {
61                return;
62            };
63            let x: Signal<f64> = self.get_x();
64            let y: Signal<f64> = self.get_y();
65            let scrolled: Window = window.clone();
66            let on_scroll: Closure<dyn FnMut(Event)> =
67                Closure::wrap(Box::new(move |_event: Event| {
68                    x.set(scrolled.scroll_x().unwrap_or(0.0));
69                    y.set(scrolled.scroll_y().unwrap_or(0.0));
70                }));
71            if window
72                .add_event_listener_with_callback(EVENT_SCROLL, on_scroll.as_ref().unchecked_ref())
73                .is_err()
74            {
75                return;
76            }
77            x.set(window.scroll_x().unwrap_or(0.0));
78            y.set(window.scroll_y().unwrap_or(0.0));
79            *self.get_teardown().borrow_mut() = Some(Box::new(move || {
80                let _: Result<(), JsValue> = window.remove_event_listener_with_callback(
81                    EVENT_SCROLL,
82                    on_scroll.as_ref().unchecked_ref(),
83                );
84            }));
85        }
86    }
87
88    /// Removes the window listener.
89    pub(crate) fn detach(&self) {
90        let taken: Option<Box<dyn Fn()>> = self.get_teardown().borrow_mut().take();
91        if let Some(teardown) = taken {
92            teardown();
93        }
94    }
95}