iced_widget/lazy/
responsive.rs

1use crate::core::layout::{self, Layout};
2use crate::core::mouse;
3use crate::core::overlay;
4use crate::core::renderer;
5use crate::core::widget;
6use crate::core::widget::tree::{self, Tree};
7use crate::core::{
8    self, Clipboard, Element, Event, Length, Point, Rectangle, Shell, Size,
9    Vector, Widget,
10};
11use crate::horizontal_space;
12use crate::runtime::overlay::Nested;
13
14use ouroboros::self_referencing;
15use std::cell::{RefCell, RefMut};
16use std::marker::PhantomData;
17use std::ops::Deref;
18
19/// A widget that is aware of its dimensions.
20///
21/// A [`Responsive`] widget will always try to fill all the available space of
22/// its parent.
23#[cfg(feature = "lazy")]
24#[allow(missing_debug_implementations)]
25pub struct Responsive<
26    'a,
27    Message,
28    Theme = crate::Theme,
29    Renderer = crate::Renderer,
30> {
31    view: Box<dyn Fn(Size) -> Element<'a, Message, Theme, Renderer> + 'a>,
32    content: RefCell<Content<'a, Message, Theme, Renderer>>,
33}
34
35impl<'a, Message, Theme, Renderer> Responsive<'a, Message, Theme, Renderer>
36where
37    Renderer: core::Renderer,
38{
39    /// Creates a new [`Responsive`] widget with a closure that produces its
40    /// contents.
41    ///
42    /// The `view` closure will be provided with the current [`Size`] of
43    /// the [`Responsive`] widget and, therefore, can be used to build the
44    /// contents of the widget in a responsive way.
45    pub fn new(
46        view: impl Fn(Size) -> Element<'a, Message, Theme, Renderer> + 'a,
47    ) -> Self {
48        Self {
49            view: Box::new(view),
50            content: RefCell::new(Content {
51                size: Size::ZERO,
52                layout: None,
53                is_layout_invalid: true,
54                element: Element::new(horizontal_space().width(0)),
55            }),
56        }
57    }
58}
59
60struct Content<'a, Message, Theme, Renderer> {
61    size: Size,
62    layout: Option<layout::Node>,
63    is_layout_invalid: bool,
64    element: Element<'a, Message, Theme, Renderer>,
65}
66
67impl<'a, Message, Theme, Renderer> Content<'a, Message, Theme, Renderer>
68where
69    Renderer: core::Renderer,
70{
71    fn layout(&mut self, tree: &mut Tree, renderer: &Renderer) {
72        if self.layout.is_none() || self.is_layout_invalid {
73            self.layout = Some(self.element.as_widget().layout(
74                tree,
75                renderer,
76                &layout::Limits::new(Size::ZERO, self.size),
77            ));
78            self.is_layout_invalid = false;
79        }
80    }
81
82    fn update(
83        &mut self,
84        tree: &mut Tree,
85        new_size: Size,
86        view: &dyn Fn(Size) -> Element<'a, Message, Theme, Renderer>,
87    ) {
88        if self.size != new_size {
89            self.element = view(new_size);
90            self.size = new_size;
91            self.layout = None;
92
93            tree.diff(&self.element);
94        } else {
95            let is_tree_empty =
96                tree.tag == tree::Tag::stateless() && tree.children.is_empty();
97
98            if is_tree_empty {
99                self.layout = None;
100                tree.diff(&self.element);
101            }
102        }
103    }
104
105    fn resolve<R, T>(
106        &mut self,
107        tree: &mut Tree,
108        renderer: R,
109        layout: Layout<'_>,
110        view: &dyn Fn(Size) -> Element<'a, Message, Theme, Renderer>,
111        f: impl FnOnce(
112            &mut Tree,
113            R,
114            Layout<'_>,
115            &mut Element<'a, Message, Theme, Renderer>,
116        ) -> T,
117    ) -> T
118    where
119        R: Deref<Target = Renderer>,
120    {
121        self.update(tree, layout.bounds().size(), view);
122        self.layout(tree, renderer.deref());
123
124        let content_layout = Layout::with_offset(
125            layout.position() - Point::ORIGIN,
126            self.layout.as_ref().unwrap(),
127        );
128
129        f(tree, renderer, content_layout, &mut self.element)
130    }
131}
132
133struct State {
134    tree: RefCell<Tree>,
135}
136
137impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer>
138    for Responsive<'_, Message, Theme, Renderer>
139where
140    Renderer: core::Renderer,
141{
142    fn tag(&self) -> tree::Tag {
143        tree::Tag::of::<State>()
144    }
145
146    fn state(&self) -> tree::State {
147        tree::State::new(State {
148            tree: RefCell::new(Tree::empty()),
149        })
150    }
151
152    fn size(&self) -> Size<Length> {
153        Size {
154            width: Length::Fill,
155            height: Length::Fill,
156        }
157    }
158
159    fn layout(
160        &self,
161        _tree: &mut Tree,
162        _renderer: &Renderer,
163        limits: &layout::Limits,
164    ) -> layout::Node {
165        layout::Node::new(limits.max())
166    }
167
168    fn operate(
169        &self,
170        tree: &mut Tree,
171        layout: Layout<'_>,
172        renderer: &Renderer,
173        operation: &mut dyn widget::Operation,
174    ) {
175        let state = tree.state.downcast_mut::<State>();
176        let mut content = self.content.borrow_mut();
177
178        content.resolve(
179            &mut state.tree.borrow_mut(),
180            renderer,
181            layout,
182            &self.view,
183            |tree, renderer, layout, element| {
184                element
185                    .as_widget()
186                    .operate(tree, layout, renderer, operation);
187            },
188        );
189    }
190
191    fn update(
192        &mut self,
193        tree: &mut Tree,
194        event: &Event,
195        layout: Layout<'_>,
196        cursor: mouse::Cursor,
197        renderer: &Renderer,
198        clipboard: &mut dyn Clipboard,
199        shell: &mut Shell<'_, Message>,
200        viewport: &Rectangle,
201    ) {
202        let state = tree.state.downcast_mut::<State>();
203        let mut content = self.content.borrow_mut();
204
205        let mut local_messages = vec![];
206        let mut local_shell = Shell::new(&mut local_messages);
207
208        content.resolve(
209            &mut state.tree.borrow_mut(),
210            renderer,
211            layout,
212            &self.view,
213            |tree, renderer, layout, element| {
214                element.as_widget_mut().update(
215                    tree,
216                    event,
217                    layout,
218                    cursor,
219                    renderer,
220                    clipboard,
221                    &mut local_shell,
222                    viewport,
223                );
224            },
225        );
226
227        if local_shell.is_layout_invalid() {
228            content.layout = None;
229        }
230
231        shell.merge(local_shell, std::convert::identity);
232    }
233
234    fn draw(
235        &self,
236        tree: &Tree,
237        renderer: &mut Renderer,
238        theme: &Theme,
239        style: &renderer::Style,
240        layout: Layout<'_>,
241        cursor: mouse::Cursor,
242        viewport: &Rectangle,
243    ) {
244        let state = tree.state.downcast_ref::<State>();
245        let mut content = self.content.borrow_mut();
246
247        content.resolve(
248            &mut state.tree.borrow_mut(),
249            renderer,
250            layout,
251            &self.view,
252            |tree, renderer, layout, element| {
253                element.as_widget().draw(
254                    tree, renderer, theme, style, layout, cursor, viewport,
255                );
256            },
257        );
258    }
259
260    fn mouse_interaction(
261        &self,
262        tree: &Tree,
263        layout: Layout<'_>,
264        cursor: mouse::Cursor,
265        viewport: &Rectangle,
266        renderer: &Renderer,
267    ) -> mouse::Interaction {
268        let state = tree.state.downcast_ref::<State>();
269        let mut content = self.content.borrow_mut();
270
271        content.resolve(
272            &mut state.tree.borrow_mut(),
273            renderer,
274            layout,
275            &self.view,
276            |tree, renderer, layout, element| {
277                element
278                    .as_widget()
279                    .mouse_interaction(tree, layout, cursor, viewport, renderer)
280            },
281        )
282    }
283
284    fn overlay<'b>(
285        &'b mut self,
286        tree: &'b mut Tree,
287        layout: Layout<'b>,
288        renderer: &Renderer,
289        viewport: &Rectangle,
290        translation: Vector,
291    ) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
292        use std::ops::DerefMut;
293
294        let state = tree.state.downcast_ref::<State>();
295
296        let overlay = OverlayBuilder {
297            content: self.content.borrow_mut(),
298            tree: state.tree.borrow_mut(),
299            types: PhantomData,
300            overlay_builder: |content: &mut RefMut<
301                '_,
302                Content<'_, _, _, _>,
303            >,
304                              tree| {
305                content.update(tree, layout.bounds().size(), &self.view);
306                content.layout(tree, renderer);
307
308                let Content {
309                    element,
310                    layout: content_layout_node,
311                    is_layout_invalid,
312                    ..
313                } = content.deref_mut();
314
315                let content_layout = Layout::with_offset(
316                    layout.bounds().position() - Point::ORIGIN,
317                    content_layout_node.as_ref().unwrap(),
318                );
319
320                (
321                    element
322                        .as_widget_mut()
323                        .overlay(
324                            tree,
325                            content_layout,
326                            renderer,
327                            viewport,
328                            translation,
329                        )
330                        .map(|overlay| RefCell::new(Nested::new(overlay))),
331                    is_layout_invalid,
332                )
333            },
334        }
335        .build();
336
337        if overlay.with_overlay(|(overlay, _layout)| overlay.is_some()) {
338            Some(overlay::Element::new(Box::new(overlay)))
339        } else {
340            None
341        }
342    }
343}
344
345impl<'a, Message, Theme, Renderer>
346    From<Responsive<'a, Message, Theme, Renderer>>
347    for Element<'a, Message, Theme, Renderer>
348where
349    Message: 'a,
350    Theme: 'a,
351    Renderer: core::Renderer + 'a,
352{
353    fn from(responsive: Responsive<'a, Message, Theme, Renderer>) -> Self {
354        Self::new(responsive)
355    }
356}
357
358#[self_referencing]
359struct Overlay<'a, 'b, Message, Theme, Renderer> {
360    content: RefMut<'a, Content<'b, Message, Theme, Renderer>>,
361    tree: RefMut<'a, Tree>,
362    types: PhantomData<Message>,
363
364    #[borrows(mut content, mut tree)]
365    #[not_covariant]
366    overlay: (
367        Option<RefCell<Nested<'this, Message, Theme, Renderer>>>,
368        &'this mut bool,
369    ),
370}
371
372impl<Message, Theme, Renderer> Overlay<'_, '_, Message, Theme, Renderer> {
373    fn with_overlay_maybe<T>(
374        &self,
375        f: impl FnOnce(&mut Nested<'_, Message, Theme, Renderer>) -> T,
376    ) -> Option<T> {
377        self.with_overlay(|(overlay, _layout)| {
378            overlay.as_ref().map(|nested| (f)(&mut nested.borrow_mut()))
379        })
380    }
381
382    fn with_overlay_mut_maybe<T>(
383        &mut self,
384        f: impl FnOnce(&mut Nested<'_, Message, Theme, Renderer>) -> T,
385    ) -> Option<T> {
386        self.with_overlay_mut(|(overlay, _layout)| {
387            overlay.as_mut().map(|nested| (f)(nested.get_mut()))
388        })
389    }
390}
391
392impl<Message, Theme, Renderer> overlay::Overlay<Message, Theme, Renderer>
393    for Overlay<'_, '_, Message, Theme, Renderer>
394where
395    Renderer: core::Renderer,
396{
397    fn layout(&mut self, renderer: &Renderer, bounds: Size) -> layout::Node {
398        self.with_overlay_maybe(|overlay| overlay.layout(renderer, bounds))
399            .unwrap_or_default()
400    }
401
402    fn draw(
403        &self,
404        renderer: &mut Renderer,
405        theme: &Theme,
406        style: &renderer::Style,
407        layout: Layout<'_>,
408        cursor: mouse::Cursor,
409    ) {
410        let _ = self.with_overlay_maybe(|overlay| {
411            overlay.draw(renderer, theme, style, layout, cursor);
412        });
413    }
414
415    fn mouse_interaction(
416        &self,
417        layout: Layout<'_>,
418        cursor: mouse::Cursor,
419        renderer: &Renderer,
420    ) -> mouse::Interaction {
421        self.with_overlay_maybe(|overlay| {
422            overlay.mouse_interaction(layout, cursor, renderer)
423        })
424        .unwrap_or_default()
425    }
426
427    fn update(
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    ) {
436        let mut is_layout_invalid = false;
437
438        let _ = self.with_overlay_mut_maybe(|overlay| {
439            overlay.update(event, layout, cursor, renderer, clipboard, shell);
440
441            is_layout_invalid = shell.is_layout_invalid();
442        });
443
444        if is_layout_invalid {
445            self.with_overlay_mut(|(_overlay, layout)| {
446                **layout = true;
447            });
448        }
449    }
450
451    fn operate(
452        &mut self,
453        layout: Layout<'_>,
454        renderer: &Renderer,
455        operation: &mut dyn widget::Operation,
456    ) {
457        let _ = self.with_overlay_mut_maybe(|overlay| {
458            overlay.operate(layout, renderer, operation);
459        });
460    }
461}