Skip to main content

cosmic/widget/menu/
menu_column.rs

1//! Distribute content vertically.
2use crate::iced;
3use iced::core::alignment::{self, Alignment};
4use iced::core::event::{self, Event};
5use iced::core::widget::{Operation, Tree};
6use iced::core::{
7    Clipboard, Element, Layout, Length, Padding, Pixels, Rectangle, Shell, Size, Vector, Widget,
8    layout, mouse, overlay, renderer, widget,
9};
10
11#[allow(missing_debug_implementations)]
12#[must_use]
13pub struct MenuColumn<'a, Message, Renderer = iced::Renderer> {
14    spacing: f32,
15    padding: Padding,
16    width: Length,
17    height: Length,
18    max_width: f32,
19    align: Alignment,
20    clip: bool,
21    children: Vec<Element<'a, Message, crate::Theme, Renderer>>,
22}
23
24impl<'a, Message, Renderer> MenuColumn<'a, Message, Renderer>
25where
26    Renderer: iced::core::Renderer,
27{
28    /// Creates an empty [`MenuColumn`].
29    pub fn new() -> Self {
30        Self::from_vec(Vec::new())
31    }
32
33    /// Creates a [`MenuColumn`] with the given capacity.
34    pub fn with_capacity(capacity: usize) -> Self {
35        Self::from_vec(Vec::with_capacity(capacity))
36    }
37
38    /// Creates a [`MenuColumn`] with the given elements.
39    pub fn with_children(
40        children: impl IntoIterator<Item = Element<'a, Message, crate::Theme, Renderer>>,
41    ) -> Self {
42        let iterator = children.into_iter();
43
44        Self::with_capacity(iterator.size_hint().0).extend(iterator)
45    }
46
47    /// Creates a [`MenuColumn`] from an already allocated [`Vec`].
48    ///
49    /// Keep in mind that the [`MenuColumn`] will not inspect the [`Vec`], which means
50    /// it won't automatically adapt to the sizing strategy of its contents.
51    ///
52    /// If any of the children have a [`Length::Fill`] strategy, you will need to
53    /// call [`MenuColumn::width`] or [`MenuColumn::height`] accordingly.
54    pub fn from_vec(children: Vec<Element<'a, Message, crate::Theme, Renderer>>) -> Self {
55        Self {
56            spacing: 0.0,
57            padding: Padding::ZERO,
58            width: Length::Shrink,
59            height: Length::Shrink,
60            max_width: f32::INFINITY,
61            align: Alignment::Start,
62            clip: false,
63            children,
64        }
65    }
66
67    /// Sets the vertical spacing _between_ elements.
68    ///
69    /// Custom margins per element do not exist in iced. You should use this
70    /// method instead! While less flexible, it helps you keep spacing between
71    /// elements consistent.
72    pub fn spacing(mut self, amount: impl Into<Pixels>) -> Self {
73        self.spacing = amount.into().0;
74        self
75    }
76
77    /// Sets the [`Padding`] of the [`MenuColumn`].
78    pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self {
79        self.padding = padding.into();
80        self
81    }
82
83    /// Sets the width of the [`MenuColumn`].
84    pub fn width(mut self, width: impl Into<Length>) -> Self {
85        self.width = width.into();
86        self
87    }
88
89    /// Sets the height of the [`MenuColumn`].
90    pub fn height(mut self, height: impl Into<Length>) -> Self {
91        self.height = height.into();
92        self
93    }
94
95    /// Sets the maximum width of the [`MenuColumn`].
96    pub fn max_width(mut self, max_width: impl Into<Pixels>) -> Self {
97        self.max_width = max_width.into().0;
98        self
99    }
100
101    /// Sets the horizontal alignment of the contents of the [`MenuColumn`] .
102    pub fn align_x(mut self, align: impl Into<alignment::Horizontal>) -> Self {
103        self.align = Alignment::from(align.into());
104        self
105    }
106
107    /// Sets whether the contents of the [`MenuColumn`] should be clipped on
108    /// overflow.
109    pub fn clip(mut self, clip: bool) -> Self {
110        self.clip = clip;
111        self
112    }
113
114    /// Adds an element to the [`MenuColumn`].
115    pub fn push(mut self, child: impl Into<Element<'a, Message, crate::Theme, Renderer>>) -> Self {
116        let child = child.into();
117        let child_size = child.as_widget().size_hint();
118
119        self.width = self.width.enclose(child_size.width);
120        self.height = self.height.enclose(child_size.height);
121
122        self.children.push(child);
123        self
124    }
125
126    /// Adds an element to the [`MenuColumn`], if `Some`.
127    #[must_use]
128    pub fn push_maybe(
129        self,
130        child: Option<impl Into<Element<'a, Message, crate::Theme, Renderer>>>,
131    ) -> Self {
132        if let Some(child) = child {
133            self.push(child)
134        } else {
135            self
136        }
137    }
138
139    /// Extends the [`MenuColumn`] with the given children.
140    pub fn extend(
141        self,
142        children: impl IntoIterator<Item = Element<'a, Message, crate::Theme, Renderer>>,
143    ) -> Self {
144        children.into_iter().fold(self, Self::push)
145    }
146}
147
148impl<Message, Renderer> Default for MenuColumn<'_, Message, Renderer>
149where
150    Renderer: iced::core::Renderer,
151{
152    fn default() -> Self {
153        Self::new()
154    }
155}
156
157impl<'a, Message, Renderer: iced::core::Renderer>
158    FromIterator<Element<'a, Message, crate::Theme, Renderer>>
159    for MenuColumn<'a, Message, Renderer>
160{
161    fn from_iter<T: IntoIterator<Item = Element<'a, Message, crate::Theme, Renderer>>>(
162        iter: T,
163    ) -> Self {
164        Self::with_children(iter)
165    }
166}
167
168impl<Message, Renderer> Widget<Message, crate::Theme, Renderer>
169    for MenuColumn<'_, Message, Renderer>
170where
171    Renderer: iced::core::Renderer,
172{
173    fn children(&self) -> Vec<Tree> {
174        self.children.iter().map(Tree::new).collect()
175    }
176
177    fn diff(&mut self, tree: &mut Tree) {
178        tree.diff_children(self.children.as_mut_slice());
179    }
180
181    fn size(&self) -> Size<Length> {
182        Size {
183            width: self.width,
184            height: self.height,
185        }
186    }
187
188    fn layout(
189        &mut self,
190        tree: &mut Tree,
191        renderer: &Renderer,
192        limits: &layout::Limits,
193    ) -> layout::Node {
194        let limits = limits.max_width(self.max_width);
195
196        layout::flex::resolve(
197            layout::flex::Axis::Vertical,
198            renderer,
199            &limits,
200            self.width,
201            self.height,
202            self.padding,
203            self.spacing,
204            self.align,
205            &mut self.children,
206            &mut tree.children,
207        )
208    }
209
210    fn operate(
211        &mut self,
212        tree: &mut Tree,
213        layout: Layout<'_>,
214        renderer: &Renderer,
215        operation: &mut dyn Operation,
216    ) {
217        operation.container(None, layout.bounds());
218        operation.traverse(&mut |operation| {
219            self.children
220                .iter_mut()
221                .zip(&mut tree.children)
222                .zip(layout.children())
223                .for_each(|((child, state), c_layout)| {
224                    child.as_widget_mut().operate(
225                        state,
226                        c_layout.with_virtual_offset(layout.virtual_offset()),
227                        renderer,
228                        operation,
229                    );
230                });
231        });
232    }
233
234    fn update(
235        &mut self,
236        tree: &mut Tree,
237        event: &Event,
238        layout: Layout<'_>,
239        cursor: mouse::Cursor,
240        renderer: &Renderer,
241        clipboard: &mut dyn Clipboard,
242        shell: &mut Shell<'_, Message>,
243        viewport: &Rectangle,
244    ) {
245        for (((i, child), state), c_layout) in self
246            .children
247            .iter_mut()
248            .enumerate()
249            .zip(&mut tree.children)
250            .zip(layout.children())
251        {
252            child.as_widget_mut().update(
253                state,
254                &event,
255                c_layout.with_virtual_offset(layout.virtual_offset()),
256                cursor,
257                renderer,
258                clipboard,
259                shell,
260                viewport,
261            );
262        }
263    }
264
265    fn mouse_interaction(
266        &self,
267        tree: &Tree,
268        layout: Layout<'_>,
269        cursor: mouse::Cursor,
270        viewport: &Rectangle,
271        renderer: &Renderer,
272    ) -> mouse::Interaction {
273        self.children
274            .iter()
275            .zip(&tree.children)
276            .zip(layout.children())
277            .map(|((child, state), c_layout)| {
278                child.as_widget().mouse_interaction(
279                    state,
280                    c_layout.with_virtual_offset(layout.virtual_offset()),
281                    cursor,
282                    viewport,
283                    renderer,
284                )
285            })
286            .max()
287            .unwrap_or_default()
288    }
289
290    fn draw(
291        &self,
292        tree: &Tree,
293        renderer: &mut Renderer,
294        theme: &crate::Theme,
295        style: &renderer::Style,
296        layout: Layout<'_>,
297        cursor: mouse::Cursor,
298        viewport: &Rectangle,
299    ) {
300        if let Some(clipped_viewport) = layout.bounds().intersection(viewport) {
301            let viewport = if self.clip {
302                &clipped_viewport
303            } else {
304                viewport
305            };
306
307            for (i, ((child, state), c_layout)) in self
308                .children
309                .iter()
310                .zip(&tree.children)
311                .zip(layout.children())
312                .filter(|(_, layout)| layout.bounds().intersects(viewport))
313                .enumerate()
314            {
315                let t = theme.with_list_item_position(if self.children.len() == 1 {
316                    Some((Alignment::Center, i))
317                } else if 0 == i {
318                    Some((Alignment::Start, i))
319                } else if i == self.children.len() - 1 {
320                    Some((Alignment::End, i))
321                } else {
322                    None
323                });
324                child.as_widget().draw(
325                    state,
326                    renderer,
327                    &t,
328                    style,
329                    c_layout.with_virtual_offset(layout.virtual_offset()),
330                    cursor,
331                    viewport,
332                );
333            }
334        }
335    }
336
337    fn overlay<'b>(
338        &'b mut self,
339        tree: &'b mut Tree,
340        layout: Layout<'b>,
341        renderer: &Renderer,
342        viewport: &Rectangle,
343        translation: Vector,
344    ) -> Option<overlay::Element<'b, Message, crate::Theme, Renderer>> {
345        overlay::from_children(
346            &mut self.children,
347            tree,
348            layout,
349            renderer,
350            viewport,
351            translation,
352        )
353    }
354
355    #[cfg(feature = "a11y")]
356    /// get the a11y nodes for the widget
357    fn a11y_nodes(
358        &self,
359        layout: Layout<'_>,
360        state: &Tree,
361        cursor: mouse::Cursor,
362    ) -> iced_accessibility::A11yTree {
363        use iced_accessibility::A11yTree;
364        A11yTree::join(
365            self.children
366                .iter()
367                .zip(layout.children())
368                .zip(state.children.iter())
369                .map(|((c, c_layout), state)| {
370                    c.as_widget().a11y_nodes(
371                        c_layout.with_virtual_offset(layout.virtual_offset()),
372                        state,
373                        cursor,
374                    )
375                }),
376        )
377    }
378
379    fn drag_destinations(
380        &self,
381        state: &Tree,
382        layout: Layout<'_>,
383        renderer: &Renderer,
384        dnd_rectangles: &mut iced::core::clipboard::DndDestinationRectangles,
385    ) {
386        for ((e, c_layout), state) in self
387            .children
388            .iter()
389            .zip(layout.children())
390            .zip(state.children.iter())
391        {
392            e.as_widget().drag_destinations(
393                state,
394                c_layout.with_virtual_offset(layout.virtual_offset()),
395                renderer,
396                dnd_rectangles,
397            );
398        }
399    }
400}
401
402impl<'a, Message, Renderer> From<MenuColumn<'a, Message, Renderer>>
403    for Element<'a, Message, crate::Theme, Renderer>
404where
405    Message: 'a,
406    Renderer: iced::core::Renderer + 'a,
407{
408    fn from(column: MenuColumn<'a, Message, Renderer>) -> Self {
409        Self::new(column)
410    }
411}