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