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, 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.children[0].diff(&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        &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 unrestricted_size = self.size.unwrap_or_else(|| {
99            let node =
100                self.content
101                    .as_widget()
102                    .layout(&mut tree.children[0], renderer, &Limits::NONE);
103            node.size()
104        });
105
106        let max_size = limits.max();
107        let old_max = state.limits.max();
108        state.needs_update = (unrestricted_size.width > max_size.width)
109            ^ (state.size.width > old_max.width)
110            || (unrestricted_size.height > max_size.height) ^ (state.size.height > old_max.height);
111        if state.needs_update {
112            state.limits = *limits;
113            state.size = unrestricted_size;
114        }
115
116        let node = self
117            .content
118            .as_widget()
119            .layout(&mut tree.children[0], renderer, limits);
120        let size = node.size();
121        layout::Node::with_children(size, vec![node])
122    }
123
124    fn operate(
125        &self,
126        tree: &mut Tree,
127        layout: Layout<'_>,
128        renderer: &Renderer,
129        operation: &mut dyn iced_core::widget::Operation<()>,
130    ) {
131        operation.container(Some(&self.id), layout.bounds(), &mut |operation| {
132            self.content.as_widget().operate(
133                &mut tree.children[0],
134                layout
135                    .children()
136                    .next()
137                    .unwrap()
138                    .with_virtual_offset(layout.virtual_offset()),
139                renderer,
140                operation,
141            );
142        });
143    }
144
145    fn on_event(
146        &mut self,
147        tree: &mut Tree,
148        event: Event,
149        layout: Layout<'_>,
150        cursor_position: mouse::Cursor,
151        renderer: &Renderer,
152        clipboard: &mut dyn Clipboard,
153        shell: &mut Shell<'_, Message>,
154        viewport: &Rectangle,
155    ) -> event::Status {
156        let state = tree.state.downcast_mut::<State>();
157
158        if state.needs_update {
159            shell.publish((self.on_action)(
160                crate::surface::Action::ResponsiveMenuBar {
161                    menu_bar: self.id.clone(),
162                    limits: state.limits,
163                    size: state.size,
164                },
165            ));
166            state.needs_update = false;
167        }
168
169        self.content.as_widget_mut().on_event(
170            &mut tree.children[0],
171            event.clone(),
172            layout
173                .children()
174                .next()
175                .unwrap()
176                .with_virtual_offset(layout.virtual_offset()),
177            cursor_position,
178            renderer,
179            clipboard,
180            shell,
181            viewport,
182        )
183    }
184
185    fn mouse_interaction(
186        &self,
187        tree: &Tree,
188        layout: Layout<'_>,
189        cursor_position: mouse::Cursor,
190        viewport: &Rectangle,
191        renderer: &Renderer,
192    ) -> mouse::Interaction {
193        let content_layout = layout.children().next().unwrap();
194        self.content.as_widget().mouse_interaction(
195            &tree.children[0],
196            content_layout.with_virtual_offset(layout.virtual_offset()),
197            cursor_position,
198            viewport,
199            renderer,
200        )
201    }
202
203    fn draw(
204        &self,
205        tree: &Tree,
206        renderer: &mut Renderer,
207        theme: &Theme,
208        renderer_style: &renderer::Style,
209        layout: Layout<'_>,
210        cursor_position: mouse::Cursor,
211        viewport: &Rectangle,
212    ) {
213        let content_layout = layout.children().next().unwrap();
214        self.content.as_widget().draw(
215            &tree.children[0],
216            renderer,
217            theme,
218            renderer_style,
219            content_layout.with_virtual_offset(layout.virtual_offset()),
220            cursor_position,
221            viewport,
222        );
223    }
224
225    fn overlay<'b>(
226        &'b mut self,
227        tree: &'b mut Tree,
228        layout: Layout<'_>,
229        renderer: &Renderer,
230        translation: Vector,
231    ) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
232        self.content.as_widget_mut().overlay(
233            &mut tree.children[0],
234            layout
235                .children()
236                .next()
237                .unwrap()
238                .with_virtual_offset(layout.virtual_offset()),
239            renderer,
240            translation,
241        )
242    }
243
244    fn drag_destinations(
245        &self,
246        state: &Tree,
247        layout: Layout<'_>,
248        renderer: &Renderer,
249        dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles,
250    ) {
251        let content_layout = layout.children().next().unwrap();
252        self.content.as_widget().drag_destinations(
253            &state.children[0],
254            content_layout.with_virtual_offset(layout.virtual_offset()),
255            renderer,
256            dnd_rectangles,
257        );
258    }
259
260    fn id(&self) -> Option<crate::widget::Id> {
261        Some(self.id.clone())
262    }
263
264    fn set_id(&mut self, id: crate::widget::Id) {
265        self.id = id;
266    }
267
268    #[cfg(feature = "a11y")]
269    /// get the a11y nodes for the widget
270    fn a11y_nodes(
271        &self,
272        layout: Layout<'_>,
273        state: &Tree,
274        p: mouse::Cursor,
275    ) -> iced_accessibility::A11yTree {
276        let c_layout = layout.children().next().unwrap();
277        let c_state = &state.children[0];
278        self.content.as_widget().a11y_nodes(
279            c_layout.with_virtual_offset(layout.virtual_offset()),
280            c_state,
281            p,
282        )
283    }
284}
285
286impl<'a, Message, Theme, Renderer> From<ResponsiveContainer<'a, Message, Theme, Renderer>>
287    for Element<'a, Message, Theme, Renderer>
288where
289    Message: 'a,
290    Renderer: 'a + iced_core::Renderer,
291    Theme: 'a,
292{
293    fn from(
294        c: ResponsiveContainer<'a, Message, Theme, Renderer>,
295    ) -> Element<'a, Message, Theme, Renderer> {
296        Element::new(c)
297    }
298}
299
300#[derive(Debug, Clone, Copy)]
301struct State {
302    limits: Limits,
303    size: Size,
304    needs_update: bool,
305}
306
307impl State {
308    fn new() -> Self {
309        Self {
310            limits: Limits::NONE,
311            size: Size::new(0., 0.),
312            needs_update: false,
313        }
314    }
315}