cosmic/widget/
responsive_container.rs

1//! Responsive Container, which will notify of size changes.
2
3use iced::{Limits, Size};
4use iced_core::event::{self, Event};
5use iced_core::layout;
6use iced_core::mouse;
7use iced_core::overlay;
8use iced_core::renderer;
9use iced_core::widget::{Id, Operation, Tree, tree};
10use iced_core::{Clipboard, Element, Layout, Length, Rectangle, Shell, Vector, Widget};
11
12pub(crate) fn responsive_container<'a, Message: 'static, Theme, E>(
13    content: E,
14    id: Id,
15    on_action: impl Fn(crate::surface::Action) -> Message + 'static,
16) -> ResponsiveContainer<'a, Message, Theme, crate::Renderer>
17where
18    E: Into<Element<'a, Message, Theme, crate::Renderer>>,
19    Theme: iced_widget::container::Catalog,
20    <Theme as iced_widget::container::Catalog>::Class<'a>: From<crate::theme::Container<'a>>,
21{
22    ResponsiveContainer::new(content, id, on_action)
23}
24
25/// An element decorating some content.
26///
27/// It is normally used for alignment purposes.
28#[allow(missing_debug_implementations)]
29pub struct ResponsiveContainer<'a, Message, Theme, Renderer>
30where
31    Renderer: iced_core::Renderer,
32{
33    content: Element<'a, Message, Theme, Renderer>,
34    id: Id,
35    size: Option<Size>,
36    on_action: Box<dyn Fn(crate::surface::Action) -> Message>,
37}
38
39impl<'a, Message, Theme, Renderer> ResponsiveContainer<'a, Message, Theme, Renderer>
40where
41    Renderer: iced_core::Renderer,
42{
43    /// Creates an empty [`IdContainer`].
44    pub(crate) fn new<T>(
45        content: T,
46        id: Id,
47        on_action: impl Fn(crate::surface::Action) -> Message + 'static,
48    ) -> Self
49    where
50        T: Into<Element<'a, Message, Theme, Renderer>>,
51    {
52        ResponsiveContainer {
53            content: content.into(),
54            id,
55            size: None,
56            on_action: Box::new(on_action),
57        }
58    }
59
60    pub(crate) fn size(mut self, size: Size) -> Self {
61        self.size = Some(size);
62        self
63    }
64}
65
66impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer>
67    for ResponsiveContainer<'_, Message, Theme, Renderer>
68where
69    Renderer: iced_core::Renderer,
70{
71    fn tag(&self) -> tree::Tag {
72        tree::Tag::of::<State>()
73    }
74
75    fn state(&self) -> tree::State {
76        tree::State::new(State::new())
77    }
78
79    fn children(&self) -> Vec<Tree> {
80        vec![Tree::new(&self.content)]
81    }
82
83    fn diff(&mut self, tree: &mut Tree) {
84        tree.diff_children(std::slice::from_mut(&mut self.content));
85    }
86
87    fn size(&self) -> iced_core::Size<Length> {
88        self.content.as_widget().size()
89    }
90
91    fn layout(
92        &mut self,
93        tree: &mut Tree,
94        renderer: &Renderer,
95        limits: &layout::Limits,
96    ) -> layout::Node {
97        let state = tree.state.downcast_mut::<State>();
98        let mut unrestricted_size = self.size.unwrap_or_else(|| {
99            let node =
100                self.content
101                    .as_widget_mut()
102                    .layout(&mut tree.children[0], renderer, &Limits::NONE);
103            node.size()
104        });
105
106        let cur_unrestricted_size = {
107            let node =
108                self.content
109                    .as_widget_mut()
110                    .layout(&mut tree.children[0], renderer, &Limits::NONE);
111            node.size()
112        };
113
114        let max_size = limits.max();
115
116        let old_max = state.limits.max();
117
118        state.needs_update = (cur_unrestricted_size.width > max_size.width)
119            || (cur_unrestricted_size.width > old_max.width)
120            || (cur_unrestricted_size.height > max_size.height)
121            || (cur_unrestricted_size.height > old_max.height)
122            || ((unrestricted_size.width <= max_size.width)
123                && (unrestricted_size.height <= max_size.height)
124                && (unrestricted_size.width - cur_unrestricted_size.width > 1.
125                    || unrestricted_size.height - cur_unrestricted_size.height > 1.));
126
127        if unrestricted_size.width < cur_unrestricted_size.width {
128            state.needs_update = true;
129            unrestricted_size.width = cur_unrestricted_size.width;
130        } else if unrestricted_size.height < cur_unrestricted_size.height {
131            state.needs_update = true;
132            unrestricted_size.height = cur_unrestricted_size.height;
133        }
134        let node = self
135            .content
136            .as_widget_mut()
137            .layout(&mut tree.children[0], renderer, limits);
138        let size = node.size();
139
140        if state.needs_update {
141            state.limits = *limits;
142            state.size = unrestricted_size;
143        }
144
145        layout::Node::with_children(size, vec![node])
146    }
147
148    fn operate(
149        &mut self,
150        tree: &mut Tree,
151        layout: Layout<'_>,
152        renderer: &Renderer,
153        operation: &mut dyn Operation,
154    ) {
155        operation.container(Some(&self.id), layout.bounds());
156        operation.traverse(&mut |operation| {
157            self.content.as_widget_mut().operate(
158                &mut tree.children[0],
159                layout
160                    .children()
161                    .next()
162                    .unwrap()
163                    .with_virtual_offset(layout.virtual_offset()),
164                renderer,
165                operation,
166            );
167        });
168    }
169
170    fn update(
171        &mut self,
172        tree: &mut Tree,
173        event: &Event,
174        layout: Layout<'_>,
175        cursor_position: mouse::Cursor,
176        renderer: &Renderer,
177        clipboard: &mut dyn Clipboard,
178        shell: &mut Shell<'_, Message>,
179        viewport: &Rectangle,
180    ) {
181        let state = tree.state.downcast_mut::<State>();
182
183        if state.needs_update {
184            shell.publish((self.on_action)(
185                crate::surface::Action::ResponsiveMenuBar {
186                    menu_bar: self.id.clone(),
187                    limits: state.limits,
188                    size: state.size,
189                },
190            ));
191            state.needs_update = false;
192        }
193
194        self.content.as_widget_mut().update(
195            &mut tree.children[0],
196            event,
197            layout
198                .children()
199                .next()
200                .unwrap()
201                .with_virtual_offset(layout.virtual_offset()),
202            cursor_position,
203            renderer,
204            clipboard,
205            shell,
206            viewport,
207        )
208    }
209
210    fn mouse_interaction(
211        &self,
212        tree: &Tree,
213        layout: Layout<'_>,
214        cursor_position: mouse::Cursor,
215        viewport: &Rectangle,
216        renderer: &Renderer,
217    ) -> mouse::Interaction {
218        let content_layout = layout.children().next().unwrap();
219        self.content.as_widget().mouse_interaction(
220            &tree.children[0],
221            content_layout.with_virtual_offset(layout.virtual_offset()),
222            cursor_position,
223            viewport,
224            renderer,
225        )
226    }
227
228    fn draw(
229        &self,
230        tree: &Tree,
231        renderer: &mut Renderer,
232        theme: &Theme,
233        renderer_style: &renderer::Style,
234        layout: Layout<'_>,
235        cursor_position: mouse::Cursor,
236        viewport: &Rectangle,
237    ) {
238        let content_layout = layout.children().next().unwrap();
239        self.content.as_widget().draw(
240            &tree.children[0],
241            renderer,
242            theme,
243            renderer_style,
244            content_layout.with_virtual_offset(layout.virtual_offset()),
245            cursor_position,
246            viewport,
247        );
248    }
249
250    fn overlay<'b>(
251        &'b mut self,
252        tree: &'b mut Tree,
253        layout: Layout<'b>,
254        renderer: &Renderer,
255        viewport: &Rectangle,
256        translation: Vector,
257    ) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
258        self.content.as_widget_mut().overlay(
259            &mut tree.children[0],
260            layout
261                .children()
262                .next()
263                .unwrap()
264                .with_virtual_offset(layout.virtual_offset()),
265            renderer,
266            viewport,
267            translation,
268        )
269    }
270
271    fn drag_destinations(
272        &self,
273        state: &Tree,
274        layout: Layout<'_>,
275        renderer: &Renderer,
276        dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles,
277    ) {
278        let content_layout = layout.children().next().unwrap();
279        self.content.as_widget().drag_destinations(
280            &state.children[0],
281            content_layout.with_virtual_offset(layout.virtual_offset()),
282            renderer,
283            dnd_rectangles,
284        );
285    }
286
287    fn id(&self) -> Option<crate::widget::Id> {
288        Some(self.id.clone())
289    }
290
291    fn set_id(&mut self, id: crate::widget::Id) {
292        self.id = id;
293    }
294
295    #[cfg(feature = "a11y")]
296    /// get the a11y nodes for the widget
297    fn a11y_nodes(
298        &self,
299        layout: Layout<'_>,
300        state: &Tree,
301        p: mouse::Cursor,
302    ) -> iced_accessibility::A11yTree {
303        let c_layout = layout.children().next().unwrap();
304        let c_state = &state.children[0];
305        self.content.as_widget().a11y_nodes(
306            c_layout.with_virtual_offset(layout.virtual_offset()),
307            c_state,
308            p,
309        )
310    }
311}
312
313impl<'a, Message, Theme, Renderer> From<ResponsiveContainer<'a, Message, Theme, Renderer>>
314    for Element<'a, Message, Theme, Renderer>
315where
316    Message: 'a,
317    Renderer: 'a + iced_core::Renderer,
318    Theme: 'a,
319{
320    fn from(
321        c: ResponsiveContainer<'a, Message, Theme, Renderer>,
322    ) -> Element<'a, Message, Theme, Renderer> {
323        Element::new(c)
324    }
325}
326
327#[derive(Debug, Clone, Copy)]
328struct State {
329    limits: Limits,
330    size: Size,
331    needs_update: bool,
332}
333
334impl State {
335    fn new() -> Self {
336        Self {
337            limits: Limits::NONE,
338            size: Size::new(0., 0.),
339            needs_update: false,
340        }
341    }
342}