iced_widget/
lazy.rs

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