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::{
11    self, 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        shell: &mut Shell<'_, Message>,
176        viewport: &Rectangle,
177    ) {
178        if let Event::Window(window::Event::RedrawRequested(now)) = &event {
179            let state = tree.state.downcast_mut::<State<Key::Owned>>();
180
181            if state.has_popped_in && !self.key.eq(&state.last_key) {
182                state.has_popped_in = false;
183                state.should_notify_at = None;
184                state.last_key = self.key.to_owned();
185            }
186
187            let bounds = layout.bounds();
188            let top_left_distance = viewport.distance(bounds.position());
189
190            let bottom_right_distance =
191                viewport.distance(bounds.position() + Vector::from(bounds.size()));
192
193            let distance = top_left_distance.min(bottom_right_distance);
194
195            if self.on_show.is_none() {
196                if let Some(on_resize) = &self.on_resize {
197                    let size = bounds.size();
198
199                    if Some(size) != state.last_size {
200                        state.last_size = Some(size);
201                        shell.publish(on_resize(size));
202                    }
203                }
204            } else if state.has_popped_in {
205                if distance <= self.anticipate.0 {
206                    if let Some(on_resize) = &self.on_resize {
207                        let size = bounds.size();
208
209                        if Some(size) != state.last_size {
210                            state.last_size = Some(size);
211                            shell.publish(on_resize(size));
212                        }
213                    }
214                } else if self.on_hide.is_some() {
215                    state.has_popped_in = false;
216                    state.should_notify_at = Some((false, *now + self.delay));
217                }
218            } else if distance <= self.anticipate.0 {
219                let size = bounds.size();
220
221                state.has_popped_in = true;
222                state.should_notify_at = Some((true, *now + self.delay));
223                state.last_size = Some(size);
224            }
225
226            match &state.should_notify_at {
227                Some((has_popped_in, at)) if at <= now => {
228                    if *has_popped_in {
229                        if let Some(on_show) = &self.on_show {
230                            shell.publish(on_show(layout.bounds().size()));
231                        }
232                    } else if let Some(on_hide) = self.on_hide.take() {
233                        shell.publish(on_hide);
234                    }
235
236                    state.should_notify_at = None;
237                }
238                Some((_, at)) => {
239                    shell.request_redraw_at(*at);
240                }
241                None => {}
242            }
243        }
244
245        self.content.as_widget_mut().update(
246            &mut tree.children[0],
247            event,
248            layout,
249            cursor,
250            renderer,
251            shell,
252            viewport,
253        );
254    }
255
256    fn size(&self) -> Size<Length> {
257        self.content.as_widget().size()
258    }
259
260    fn size_hint(&self) -> Size<Length> {
261        self.content.as_widget().size_hint()
262    }
263
264    fn layout(
265        &mut self,
266        tree: &mut Tree,
267        renderer: &Renderer,
268        limits: &layout::Limits,
269    ) -> layout::Node {
270        self.content
271            .as_widget_mut()
272            .layout(&mut tree.children[0], renderer, limits)
273    }
274
275    fn draw(
276        &self,
277        tree: &Tree,
278        renderer: &mut Renderer,
279        theme: &Theme,
280        style: &renderer::Style,
281        layout: layout::Layout<'_>,
282        cursor: mouse::Cursor,
283        viewport: &Rectangle,
284    ) {
285        self.content.as_widget().draw(
286            &tree.children[0],
287            renderer,
288            theme,
289            style,
290            layout,
291            cursor,
292            viewport,
293        );
294    }
295
296    fn operate(
297        &mut self,
298        tree: &mut Tree,
299        layout: core::Layout<'_>,
300        renderer: &Renderer,
301        operation: &mut dyn widget::Operation,
302    ) {
303        self.content
304            .as_widget_mut()
305            .operate(&mut tree.children[0], layout, renderer, operation);
306    }
307
308    fn mouse_interaction(
309        &self,
310        tree: &Tree,
311        layout: core::Layout<'_>,
312        cursor: mouse::Cursor,
313        viewport: &Rectangle,
314        renderer: &Renderer,
315    ) -> mouse::Interaction {
316        self.content.as_widget().mouse_interaction(
317            &tree.children[0],
318            layout,
319            cursor,
320            viewport,
321            renderer,
322        )
323    }
324
325    fn overlay<'b>(
326        &'b mut self,
327        tree: &'b mut Tree,
328        layout: core::Layout<'b>,
329        renderer: &Renderer,
330        viewport: &Rectangle,
331        translation: core::Vector,
332    ) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
333        self.content.as_widget_mut().overlay(
334            &mut tree.children[0],
335            layout,
336            renderer,
337            viewport,
338            translation,
339        )
340    }
341}
342
343impl<'a, Key, Message, Theme, Renderer> From<Sensor<'a, Key, Message, Theme, Renderer>>
344    for Element<'a, Message, Theme, Renderer>
345where
346    Message: 'a,
347    Key: self::Key + 'a,
348    Renderer: core::Renderer + 'a,
349    Theme: 'a,
350{
351    fn from(pop: Sensor<'a, Key, Message, Theme, Renderer>) -> Self {
352        Element::new(pop)
353    }
354}
355
356/// The key of a widget.
357///
358/// You should generally not need to care about this trait.
359pub trait Key {
360    /// The owned version of the key.
361    type Owned: 'static;
362
363    /// Returns the owned version of the key.
364    fn to_owned(&self) -> Self::Owned;
365
366    /// Compares the key with the given owned version.
367    fn eq(&self, other: &Self::Owned) -> bool;
368}
369
370impl<T> Key for &T
371where
372    T: ToOwned + PartialEq<T::Owned> + ?Sized,
373    T::Owned: 'static,
374{
375    type Owned = T::Owned;
376
377    fn to_owned(&self) -> <Self as Key>::Owned {
378        ToOwned::to_owned(*self)
379    }
380
381    fn eq(&self, other: &Self::Owned) -> bool {
382        *self == other
383    }
384}
385
386struct OwnedKey<T>(T);
387
388impl<T> Key for OwnedKey<T>
389where
390    T: PartialEq + Clone + 'static,
391{
392    type Owned = T;
393
394    fn to_owned(&self) -> Self::Owned {
395        self.0.clone()
396    }
397
398    fn eq(&self, other: &Self::Owned) -> bool {
399        &self.0 == other
400    }
401}
402
403impl<T> PartialEq<T> for OwnedKey<T>
404where
405    T: PartialEq,
406{
407    fn eq(&self, other: &T) -> bool {
408        &self.0 == other
409    }
410}
411
412impl Key for () {
413    type Owned = ();
414
415    fn to_owned(&self) -> Self::Owned {}
416
417    fn eq(&self, _other: &Self::Owned) -> bool {
418        true
419    }
420}