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