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