iced_widget/lazy/
responsive.rs

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