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