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