iced_widget/
lazy.rs

1#![allow(clippy::await_holding_refcell_ref, clippy::type_complexity)]
2pub(crate) mod helpers;
3
4pub mod component;
5
6#[allow(deprecated)]
7pub use component::Component;
8
9mod cache;
10
11use crate::core::Element;
12use crate::core::layout::{self, Layout};
13use crate::core::mouse;
14use crate::core::overlay;
15use crate::core::renderer;
16use crate::core::widget::tree::{self, Tree};
17use crate::core::widget::{self, Widget};
18use crate::core::{
19    self, Clipboard, Event, Length, Rectangle, Shell, Size, Vector,
20};
21
22use ouroboros::self_referencing;
23use rustc_hash::FxHasher;
24use std::cell::RefCell;
25use std::hash::{Hash, Hasher as H};
26use std::rc::Rc;
27
28/// A widget that only rebuilds its contents when necessary.
29#[cfg(feature = "lazy")]
30pub struct Lazy<'a, Message, Theme, Renderer, Dependency, View> {
31    dependency: Dependency,
32    view: Box<dyn Fn(&Dependency) -> View + 'a>,
33    element: RefCell<
34        Option<Rc<RefCell<Option<Element<'static, Message, Theme, Renderer>>>>>,
35    >,
36}
37
38impl<'a, Message, Theme, Renderer, Dependency, View>
39    Lazy<'a, Message, Theme, Renderer, Dependency, View>
40where
41    Dependency: Hash + 'a,
42    View: Into<Element<'static, Message, Theme, Renderer>>,
43{
44    /// Creates a new [`Lazy`] widget with the given data `Dependency` and a
45    /// closure that can turn this data into a widget tree.
46    pub fn new(
47        dependency: Dependency,
48        view: impl Fn(&Dependency) -> View + 'a,
49    ) -> Self {
50        Self {
51            dependency,
52            view: Box::new(view),
53            element: RefCell::new(None),
54        }
55    }
56
57    fn with_element<T>(
58        &self,
59        f: impl FnOnce(&Element<'_, Message, Theme, Renderer>) -> T,
60    ) -> T {
61        f(self
62            .element
63            .borrow()
64            .as_ref()
65            .unwrap()
66            .borrow()
67            .as_ref()
68            .unwrap())
69    }
70
71    fn with_element_mut<T>(
72        &self,
73        f: impl FnOnce(&mut Element<'_, Message, Theme, Renderer>) -> T,
74    ) -> T {
75        f(self
76            .element
77            .borrow()
78            .as_ref()
79            .unwrap()
80            .borrow_mut()
81            .as_mut()
82            .unwrap())
83    }
84}
85
86struct Internal<Message, Theme, Renderer> {
87    element: Rc<RefCell<Option<Element<'static, Message, Theme, Renderer>>>>,
88    hash: u64,
89}
90
91impl<'a, Message, Theme, Renderer, Dependency, View>
92    Widget<Message, Theme, Renderer>
93    for Lazy<'a, Message, Theme, Renderer, Dependency, View>
94where
95    View: Into<Element<'static, Message, Theme, Renderer>> + 'static,
96    Dependency: Hash + 'a,
97    Message: 'static,
98    Theme: 'static,
99    Renderer: core::Renderer + 'static,
100{
101    fn tag(&self) -> tree::Tag {
102        struct Tag<T>(T);
103        tree::Tag::of::<Tag<View>>()
104    }
105
106    fn state(&self) -> tree::State {
107        let hash = {
108            let mut hasher = FxHasher::default();
109            self.dependency.hash(&mut hasher);
110
111            hasher.finish()
112        };
113
114        let element =
115            Rc::new(RefCell::new(Some((self.view)(&self.dependency).into())));
116
117        (*self.element.borrow_mut()) = Some(element.clone());
118
119        tree::State::new(Internal { element, hash })
120    }
121
122    fn children(&self) -> Vec<Tree> {
123        self.with_element(|element| vec![Tree::new(element.as_widget())])
124    }
125
126    fn diff(&self, tree: &mut Tree) {
127        let current = tree
128            .state
129            .downcast_mut::<Internal<Message, Theme, Renderer>>();
130
131        let new_hash = {
132            let mut hasher = FxHasher::default();
133            self.dependency.hash(&mut hasher);
134
135            hasher.finish()
136        };
137
138        if current.hash != new_hash {
139            current.hash = new_hash;
140
141            let element = (self.view)(&self.dependency).into();
142            current.element = Rc::new(RefCell::new(Some(element)));
143
144            (*self.element.borrow_mut()) = Some(current.element.clone());
145            self.with_element(|element| {
146                tree.diff_children(std::slice::from_ref(&element.as_widget()));
147            });
148        } else {
149            (*self.element.borrow_mut()) = Some(current.element.clone());
150        }
151    }
152
153    fn size(&self) -> Size<Length> {
154        self.with_element(|element| element.as_widget().size())
155    }
156
157    fn size_hint(&self) -> Size<Length> {
158        Size {
159            width: Length::Shrink,
160            height: Length::Shrink,
161        }
162    }
163
164    fn layout(
165        &mut self,
166        tree: &mut Tree,
167        renderer: &Renderer,
168        limits: &layout::Limits,
169    ) -> layout::Node {
170        self.with_element_mut(|element| {
171            element.as_widget_mut().layout(
172                &mut tree.children[0],
173                renderer,
174                limits,
175            )
176        })
177    }
178
179    fn operate(
180        &mut self,
181        tree: &mut Tree,
182        layout: Layout<'_>,
183        renderer: &Renderer,
184        operation: &mut dyn widget::Operation,
185    ) {
186        self.with_element_mut(|element| {
187            element.as_widget_mut().operate(
188                &mut tree.children[0],
189                layout,
190                renderer,
191                operation,
192            );
193        });
194    }
195
196    fn update(
197        &mut self,
198        tree: &mut Tree,
199        event: &Event,
200        layout: Layout<'_>,
201        cursor: mouse::Cursor,
202        renderer: &Renderer,
203        clipboard: &mut dyn Clipboard,
204        shell: &mut Shell<'_, Message>,
205        viewport: &Rectangle,
206    ) {
207        self.with_element_mut(|element| {
208            element.as_widget_mut().update(
209                &mut tree.children[0],
210                event,
211                layout,
212                cursor,
213                renderer,
214                clipboard,
215                shell,
216                viewport,
217            );
218        });
219    }
220
221    fn mouse_interaction(
222        &self,
223        tree: &Tree,
224        layout: Layout<'_>,
225        cursor: mouse::Cursor,
226        viewport: &Rectangle,
227        renderer: &Renderer,
228    ) -> mouse::Interaction {
229        self.with_element(|element| {
230            element.as_widget().mouse_interaction(
231                &tree.children[0],
232                layout,
233                cursor,
234                viewport,
235                renderer,
236            )
237        })
238    }
239
240    fn draw(
241        &self,
242        tree: &Tree,
243        renderer: &mut Renderer,
244        theme: &Theme,
245        style: &renderer::Style,
246        layout: Layout<'_>,
247        cursor: mouse::Cursor,
248        viewport: &Rectangle,
249    ) {
250        self.with_element(|element| {
251            element.as_widget().draw(
252                &tree.children[0],
253                renderer,
254                theme,
255                style,
256                layout,
257                cursor,
258                viewport,
259            );
260        });
261    }
262
263    fn overlay<'b>(
264        &'b mut self,
265        tree: &'b mut Tree,
266        layout: Layout<'b>,
267        renderer: &Renderer,
268        viewport: &Rectangle,
269        translation: Vector,
270    ) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
271        let overlay = InnerBuilder {
272            cell: self.element.borrow().as_ref().unwrap().clone(),
273            element: self
274                .element
275                .borrow()
276                .as_ref()
277                .unwrap()
278                .borrow_mut()
279                .take()
280                .unwrap(),
281            tree: &mut tree.children[0],
282            layout,
283            overlay_builder: |element, tree, layout| {
284                element
285                    .as_widget_mut()
286                    .overlay(tree, *layout, renderer, viewport, translation)
287                    .map(|overlay| RefCell::new(overlay::Nested::new(overlay)))
288            },
289        }
290        .build();
291
292        #[allow(clippy::redundant_closure_for_method_calls)]
293        if overlay.with_overlay(|overlay| overlay.is_some()) {
294            Some(overlay::Element::new(Box::new(Overlay(Some(overlay)))))
295        } else {
296            let heads = overlay.into_heads();
297
298            // - You may not like it, but this is what peak performance looks like
299            // - TODO: Get rid of ouroboros, for good
300            // - What?!
301            *self.element.borrow().as_ref().unwrap().borrow_mut() =
302                Some(heads.element);
303
304            None
305        }
306    }
307}
308
309#[self_referencing]
310struct Inner<'a, Message: 'a, Theme: 'a, Renderer: 'a> {
311    cell: Rc<RefCell<Option<Element<'static, Message, Theme, Renderer>>>>,
312    element: Element<'static, Message, Theme, Renderer>,
313    tree: &'a mut Tree,
314    layout: Layout<'a>,
315
316    #[borrows(mut element, mut tree, layout)]
317    #[not_covariant]
318    overlay: Option<RefCell<overlay::Nested<'this, Message, Theme, Renderer>>>,
319}
320
321struct Overlay<'a, Message, Theme, Renderer>(
322    Option<Inner<'a, Message, Theme, Renderer>>,
323);
324
325impl<Message, Theme, Renderer> Drop for Overlay<'_, Message, Theme, Renderer> {
326    fn drop(&mut self) {
327        let heads = self.0.take().unwrap().into_heads();
328        (*heads.cell.borrow_mut()) = Some(heads.element);
329    }
330}
331
332impl<Message, Theme, Renderer> Overlay<'_, Message, Theme, Renderer> {
333    fn with_overlay_maybe<T>(
334        &self,
335        f: impl FnOnce(&mut overlay::Nested<'_, Message, Theme, Renderer>) -> T,
336    ) -> Option<T> {
337        self.0.as_ref().unwrap().with_overlay(|overlay| {
338            overlay.as_ref().map(|nested| (f)(&mut nested.borrow_mut()))
339        })
340    }
341
342    fn with_overlay_mut_maybe<T>(
343        &mut self,
344        f: impl FnOnce(&mut overlay::Nested<'_, Message, Theme, Renderer>) -> T,
345    ) -> Option<T> {
346        self.0.as_mut().unwrap().with_overlay_mut(|overlay| {
347            overlay.as_mut().map(|nested| (f)(nested.get_mut()))
348        })
349    }
350}
351
352impl<Message, Theme, Renderer> overlay::Overlay<Message, Theme, Renderer>
353    for Overlay<'_, Message, Theme, Renderer>
354where
355    Renderer: core::Renderer,
356{
357    fn layout(&mut self, renderer: &Renderer, bounds: Size) -> layout::Node {
358        self.with_overlay_maybe(|overlay| overlay.layout(renderer, bounds))
359            .unwrap_or_default()
360    }
361
362    fn draw(
363        &self,
364        renderer: &mut Renderer,
365        theme: &Theme,
366        style: &renderer::Style,
367        layout: Layout<'_>,
368        cursor: mouse::Cursor,
369    ) {
370        let _ = self.with_overlay_maybe(|overlay| {
371            overlay.draw(renderer, theme, style, layout, cursor);
372        });
373    }
374
375    fn mouse_interaction(
376        &self,
377        layout: Layout<'_>,
378        cursor: mouse::Cursor,
379        renderer: &Renderer,
380    ) -> mouse::Interaction {
381        self.with_overlay_maybe(|overlay| {
382            overlay.mouse_interaction(layout, cursor, renderer)
383        })
384        .unwrap_or_default()
385    }
386
387    fn update(
388        &mut self,
389        event: &Event,
390        layout: Layout<'_>,
391        cursor: mouse::Cursor,
392        renderer: &Renderer,
393        clipboard: &mut dyn Clipboard,
394        shell: &mut Shell<'_, Message>,
395    ) {
396        let _ = self.with_overlay_mut_maybe(|overlay| {
397            overlay.update(event, layout, cursor, renderer, clipboard, shell);
398        });
399    }
400}
401
402impl<'a, Message, Theme, Renderer, Dependency, View>
403    From<Lazy<'a, Message, Theme, Renderer, Dependency, View>>
404    for Element<'a, Message, Theme, Renderer>
405where
406    View: Into<Element<'static, Message, Theme, Renderer>> + 'static,
407    Renderer: core::Renderer + 'static,
408    Message: 'static,
409    Theme: 'static,
410    Dependency: Hash + 'a,
411{
412    fn from(
413        lazy: Lazy<'a, Message, Theme, Renderer, Dependency, View>,
414    ) -> Self {
415        Self::new(lazy)
416    }
417}