Skip to main content

iced_widget/
sensor.rs

1//! Generate messages when content pops in and out of view.
2use crate::core::layout;
3use crate::core::mouse;
4use crate::core::overlay;
5use crate::core::renderer;
6use crate::core::time::{Duration, Instant};
7use crate::core::widget;
8use crate::core::widget::tree::{self, Tree};
9use crate::core::window;
10use crate::core::{self, Event, Layout, Length, Pixels, Rectangle, Shell, Size, Vector, Widget};
11
12/// A widget that can generate messages when its content pops in and out of view.
13///
14/// It can even notify you with anticipation at a given distance!
15pub struct Sensor<'a, Key, Message, W = crate::Element<'a, Message>> {
16    content: W,
17    key: Key,
18    on_show: Option<Box<dyn Fn(Size) -> Message + 'a>>,
19    on_resize: Option<Box<dyn Fn(Size) -> Option<Message> + 'a>>,
20    on_hide: Option<Message>,
21    anticipate: Pixels,
22    delay: Duration,
23}
24
25impl<'a, Message, W> Sensor<'a, (), Message, W> {
26    /// Creates a new [`Sensor`] widget with the given content.
27    pub fn new(content: W) -> Self {
28        Self {
29            content,
30            key: (),
31            on_show: None,
32            on_resize: None,
33            on_hide: None,
34            anticipate: Pixels::ZERO,
35            delay: Duration::ZERO,
36        }
37    }
38}
39
40impl<'a, Key, Message, W> Sensor<'a, Key, Message, W>
41where
42    Key: self::Key,
43{
44    /// Sets the message to be produced when the content pops into view.
45    ///
46    /// The closure will receive the [`Size`] of the content in that moment.
47    pub fn on_show(mut self, on_show: impl Fn(Size) -> Message + 'a) -> Self {
48        self.on_show = Some(Box::new(on_show));
49        self
50    }
51
52    /// Sets the message to be produced when the content changes [`Size`] once its in view.
53    ///
54    /// The closure will receive the new [`Size`] of the content.
55    pub fn on_resize<T>(mut self, on_resize: impl Fn(Size) -> T + 'a) -> Self
56    where
57        T: Into<Option<Message>>,
58    {
59        self.on_resize = Some(Box::new(move |size| on_resize(size).into()));
60        self
61    }
62
63    /// Sets the message to be produced when the content pops out of view.
64    pub fn on_hide(mut self, on_hide: Message) -> Self {
65        self.on_hide = Some(on_hide);
66        self
67    }
68
69    /// Sets the key of the [`Sensor`] widget, for continuity.
70    ///
71    /// If the key changes, the [`Sensor`] widget will trigger again.
72    pub fn key<K>(self, key: K) -> Sensor<'a, impl self::Key, Message, W>
73    where
74        K: Clone + PartialEq + 'static,
75    {
76        Sensor {
77            content: self.content,
78            key: OwnedKey(key),
79            on_show: self.on_show,
80            on_resize: self.on_resize,
81            on_hide: self.on_hide,
82            anticipate: self.anticipate,
83            delay: self.delay,
84        }
85    }
86
87    /// Sets the key of the [`Sensor`], for continuity; using a reference.
88    ///
89    /// If the key changes, the [`Sensor`] will trigger again.
90    pub fn key_ref<K>(self, key: &'a K) -> Sensor<'a, &'a K, Message, W>
91    where
92        K: ToOwned + PartialEq<K::Owned> + ?Sized,
93        K::Owned: 'static,
94    {
95        Sensor {
96            content: self.content,
97            key,
98            on_show: self.on_show,
99            on_resize: self.on_resize,
100            on_hide: self.on_hide,
101            anticipate: self.anticipate,
102            delay: self.delay,
103        }
104    }
105
106    /// Sets the distance in [`Pixels`] to use in anticipation of the
107    /// content popping into view.
108    ///
109    /// This can be quite useful to lazily load items in a long scrollable
110    /// behind the scenes before the user can notice it!
111    pub fn anticipate(mut self, distance: impl Into<Pixels>) -> Self {
112        self.anticipate = distance.into();
113        self
114    }
115
116    /// Sets the amount of time to wait before firing an [`on_show`] or
117    /// [`on_hide`] event; after the content is shown or hidden.
118    ///
119    /// When combined with [`key`], this can be useful to debounce key changes.
120    ///
121    /// [`on_show`]: Self::on_show
122    /// [`on_hide`]: Self::on_hide
123    /// [`key`]: Self::key
124    pub fn delay(mut self, delay: impl Into<Duration>) -> Self {
125        self.delay = delay.into();
126        self
127    }
128}
129
130#[derive(Debug, Clone)]
131struct State<Key> {
132    has_popped_in: bool,
133    should_notify_at: Option<(bool, Instant)>,
134    last_size: Option<Size>,
135    last_key: Key,
136}
137
138impl<Key, Message, W> widget::Meta for Sensor<'_, Key, Message, W> {}
139
140impl<Key, Message, W, Theme, Renderer> Widget<Message, Theme, Renderer>
141    for Sensor<'_, Key, Message, W>
142where
143    Key: self::Key,
144    W: Widget<Message, Theme, Renderer>,
145{
146    fn tag(&self) -> tree::Tag {
147        tree::Tag::of::<State<Key::Owned>>()
148    }
149
150    fn state(&self) -> tree::State {
151        tree::State::new(State {
152            has_popped_in: false,
153            should_notify_at: None,
154            last_size: None,
155            last_key: self.key.to_owned(),
156        })
157    }
158
159    fn diff(&mut self, tree: &mut Tree) {
160        tree.diff_children(std::slice::from_mut(&mut self.content));
161    }
162
163    fn update(
164        &mut self,
165        tree: &mut Tree,
166        event: &Event,
167        layout: Layout,
168        cursor: mouse::Cursor,
169        renderer: &Renderer,
170        shell: &mut Shell<'_, Message>,
171        viewport: &Rectangle,
172    ) {
173        if let Event::Window(window::Event::RedrawRequested(now)) = &event {
174            let state = tree.state.downcast_mut::<State<Key::Owned>>();
175
176            if state.has_popped_in && !self.key.eq(&state.last_key) {
177                state.has_popped_in = false;
178                state.should_notify_at = None;
179                state.last_key = self.key.to_owned();
180            }
181
182            let bounds = layout.bounds();
183            let top_left_distance = viewport.distance(bounds.position());
184
185            let bottom_right_distance =
186                viewport.distance(bounds.position() + Vector::from(bounds.size()));
187
188            let distance = top_left_distance.min(bottom_right_distance);
189
190            if self.on_show.is_none() {
191                if let Some(on_resize) = &self.on_resize {
192                    let size = bounds.size();
193
194                    if Some(size) != state.last_size
195                        && let Some(message) = on_resize(size)
196                    {
197                        state.last_size = Some(size);
198                        shell.publish(message);
199                    }
200                }
201            } else if state.has_popped_in {
202                if distance <= self.anticipate.0 {
203                    if let Some(on_resize) = &self.on_resize {
204                        let size = bounds.size();
205
206                        if Some(size) != state.last_size
207                            && let Some(message) = on_resize(size)
208                        {
209                            state.last_size = Some(size);
210                            shell.publish(message);
211                        }
212                    }
213                } else if self.on_hide.is_some() {
214                    state.has_popped_in = false;
215                    state.should_notify_at = Some((false, *now + self.delay));
216                }
217            } else if distance <= self.anticipate.0 {
218                let size = bounds.size();
219
220                state.has_popped_in = true;
221                state.should_notify_at = Some((true, *now + self.delay));
222                state.last_size = Some(size);
223            }
224
225            match &state.should_notify_at {
226                Some((has_popped_in, at)) if at <= now => {
227                    if *has_popped_in {
228                        if let Some(on_show) = &self.on_show {
229                            shell.publish(on_show(layout.bounds().size()));
230                        }
231                    } else if let Some(on_hide) = self.on_hide.take() {
232                        shell.publish(on_hide);
233                    }
234
235                    state.should_notify_at = None;
236                }
237                Some((_, at)) => {
238                    shell.request_redraw_at(*at);
239                }
240                None => {}
241            }
242        }
243
244        self.content.update(
245            &mut tree.children[0],
246            event,
247            layout,
248            cursor,
249            renderer,
250            shell,
251            viewport,
252        );
253    }
254
255    fn size(&self) -> Size<Length> {
256        self.content.size()
257    }
258
259    fn layout(&mut self, tree: &mut Tree, renderer: &Renderer, limits: &layout::Limits) {
260        self.content.layout(&mut tree.children[0], renderer, limits);
261
262        tree.size = tree.children[0].size;
263    }
264
265    fn draw(
266        &self,
267        tree: &Tree,
268        renderer: &mut Renderer,
269        theme: &Theme,
270        style: &renderer::Style,
271        layout: layout::Layout,
272        cursor: mouse::Cursor,
273        viewport: &Rectangle,
274    ) {
275        self.content.draw(
276            &tree.children[0],
277            renderer,
278            theme,
279            style,
280            layout,
281            cursor,
282            viewport,
283        );
284    }
285
286    fn operate(
287        &mut self,
288        tree: &mut Tree,
289        layout: core::Layout,
290        viewport: &Rectangle,
291        renderer: &Renderer,
292        operation: &mut dyn widget::Operation,
293    ) {
294        self.content
295            .operate(&mut tree.children[0], layout, viewport, renderer, operation);
296    }
297
298    fn mouse_interaction(
299        &self,
300        tree: &Tree,
301        layout: core::Layout,
302        cursor: mouse::Cursor,
303        viewport: &Rectangle,
304        renderer: &Renderer,
305    ) -> mouse::Interaction {
306        self.content
307            .mouse_interaction(&tree.children[0], layout, cursor, viewport, renderer)
308    }
309
310    fn overlay<'b>(
311        &'b mut self,
312        tree: &'b mut Tree,
313        layout: core::Layout,
314        renderer: &Renderer,
315        viewport: &Rectangle,
316        translation: core::Vector,
317        window: Size,
318    ) -> Vec<overlay::Element<'b, Message, Theme, Renderer>> {
319        self.content.overlay(
320            &mut tree.children[0],
321            layout,
322            renderer,
323            viewport,
324            translation,
325            window,
326        )
327    }
328}
329
330/// The key of a widget.
331///
332/// You should generally not need to care about this trait.
333pub trait Key {
334    /// The owned version of the key.
335    type Owned: 'static;
336
337    /// Returns the owned version of the key.
338    fn to_owned(&self) -> Self::Owned;
339
340    /// Compares the key with the given owned version.
341    fn eq(&self, other: &Self::Owned) -> bool;
342}
343
344impl<T> Key for &T
345where
346    T: ToOwned + PartialEq<T::Owned> + ?Sized,
347    T::Owned: 'static,
348{
349    type Owned = T::Owned;
350
351    fn to_owned(&self) -> <Self as Key>::Owned {
352        ToOwned::to_owned(*self)
353    }
354
355    fn eq(&self, other: &Self::Owned) -> bool {
356        *self == other
357    }
358}
359
360struct OwnedKey<T>(T);
361
362impl<T> Key for OwnedKey<T>
363where
364    T: PartialEq + Clone + 'static,
365{
366    type Owned = T;
367
368    fn to_owned(&self) -> Self::Owned {
369        self.0.clone()
370    }
371
372    fn eq(&self, other: &Self::Owned) -> bool {
373        &self.0 == other
374    }
375}
376
377impl<T> PartialEq<T> for OwnedKey<T>
378where
379    T: PartialEq,
380{
381    fn eq(&self, other: &T) -> bool {
382        &self.0 == other
383    }
384}
385
386impl Key for () {
387    type Owned = ();
388
389    fn to_owned(&self) -> Self::Owned {}
390
391    fn eq(&self, _other: &Self::Owned) -> bool {
392        true
393    }
394}