Skip to main content

cosmic/applet/
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/// A container that distributes its contents vertically.
12///
13/// # Example
14/// ```no_run
15/// # mod iced { pub mod widget { pub use iced_widget::*; } }
16/// # pub type State = ();
17/// # pub type Element<'a, Message> = iced_widget::core::Element<'a, Message, iced_widget::Theme, iced_widget::Renderer>;
18/// use iced::widget::{button, column};
19///
20/// #[derive(Debug, Clone)]
21/// enum Message {
22///     // ...
23/// }
24///
25/// fn view(state: &State) -> Element<'_, Message> {
26///     column![
27///         "I am on top!",
28///         button("I am in the center!"),
29///         "I am below.",
30///     ].into()
31/// }
32/// ```
33#[allow(missing_debug_implementations)]
34#[must_use]
35pub struct Column<'a, Message, Theme = iced::Theme, Renderer = iced::Renderer> {
36    spacing: f32,
37    padding: Padding,
38    width: Length,
39    height: Length,
40    max_width: f32,
41    align: Alignment,
42    clip: bool,
43    children: Vec<Element<'a, Message, Theme, Renderer>>,
44}
45
46impl<'a, Message, Theme, Renderer> Column<'a, Message, Theme, Renderer>
47where
48    Renderer: iced::core::Renderer,
49{
50    /// Creates an empty [`Column`].
51    pub fn new() -> Self {
52        Self::from_vec(Vec::new())
53    }
54
55    /// Creates a [`Column`] with the given capacity.
56    pub fn with_capacity(capacity: usize) -> Self {
57        Self::from_vec(Vec::with_capacity(capacity))
58    }
59
60    /// Creates a [`Column`] with the given elements.
61    pub fn with_children(
62        children: impl IntoIterator<Item = Element<'a, Message, Theme, Renderer>>,
63    ) -> Self {
64        let iterator = children.into_iter();
65
66        Self::with_capacity(iterator.size_hint().0).extend(iterator)
67    }
68
69    /// Creates a [`Column`] from an already allocated [`Vec`].
70    ///
71    /// Keep in mind that the [`Column`] will not inspect the [`Vec`], which means
72    /// it won't automatically adapt to the sizing strategy of its contents.
73    ///
74    /// If any of the children have a [`Length::Fill`] strategy, you will need to
75    /// call [`Column::width`] or [`Column::height`] accordingly.
76    pub fn from_vec(children: Vec<Element<'a, Message, Theme, Renderer>>) -> Self {
77        Self {
78            spacing: 0.0,
79            padding: Padding::ZERO,
80            width: Length::Shrink,
81            height: Length::Shrink,
82            max_width: f32::INFINITY,
83            align: Alignment::Start,
84            clip: false,
85            children,
86        }
87    }
88
89    /// Sets the vertical spacing _between_ elements.
90    ///
91    /// Custom margins per element do not exist in iced. You should use this
92    /// method instead! While less flexible, it helps you keep spacing between
93    /// elements consistent.
94    pub fn spacing(mut self, amount: impl Into<Pixels>) -> Self {
95        self.spacing = amount.into().0;
96        self
97    }
98
99    /// Sets the [`Padding`] of the [`Column`].
100    pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self {
101        self.padding = padding.into();
102        self
103    }
104
105    /// Sets the width of the [`Column`].
106    pub fn width(mut self, width: impl Into<Length>) -> Self {
107        self.width = width.into();
108        self
109    }
110
111    /// Sets the height of the [`Column`].
112    pub fn height(mut self, height: impl Into<Length>) -> Self {
113        self.height = height.into();
114        self
115    }
116
117    /// Sets the maximum width of the [`Column`].
118    pub fn max_width(mut self, max_width: impl Into<Pixels>) -> Self {
119        self.max_width = max_width.into().0;
120        self
121    }
122
123    /// Sets the horizontal alignment of the contents of the [`Column`] .
124    pub fn align_x(mut self, align: impl Into<alignment::Horizontal>) -> Self {
125        self.align = Alignment::from(align.into());
126        self
127    }
128
129    /// Sets whether the contents of the [`Column`] should be clipped on
130    /// overflow.
131    pub fn clip(mut self, clip: bool) -> Self {
132        self.clip = clip;
133        self
134    }
135
136    /// Adds an element to the [`Column`].
137    pub fn push(mut self, child: impl Into<Element<'a, Message, Theme, Renderer>>) -> Self {
138        let child = child.into();
139        let child_size = child.as_widget().size_hint();
140
141        self.width = self.width.enclose(child_size.width);
142        self.height = self.height.enclose(child_size.height);
143
144        self.children.push(child);
145        self
146    }
147
148    /// Adds an element to the [`Column`], if `Some`.
149    #[must_use]
150    pub fn push_maybe(
151        self,
152        child: Option<impl Into<Element<'a, Message, Theme, Renderer>>>,
153    ) -> Self {
154        if let Some(child) = child {
155            self.push(child)
156        } else {
157            self
158        }
159    }
160
161    /// Extends the [`Column`] with the given children.
162    pub fn extend(
163        self,
164        children: impl IntoIterator<Item = Element<'a, Message, Theme, Renderer>>,
165    ) -> Self {
166        children.into_iter().fold(self, Self::push)
167    }
168}
169
170impl<Message, Renderer> Default for Column<'_, Message, Renderer>
171where
172    Renderer: iced::core::Renderer,
173{
174    fn default() -> Self {
175        Self::new()
176    }
177}
178
179impl<'a, Message, Theme, Renderer: iced::core::Renderer>
180    FromIterator<Element<'a, Message, Theme, Renderer>> for Column<'a, Message, Theme, Renderer>
181{
182    fn from_iter<T: IntoIterator<Item = Element<'a, Message, Theme, Renderer>>>(iter: T) -> Self {
183        Self::with_children(iter)
184    }
185}
186
187impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer>
188    for Column<'_, Message, Theme, Renderer>
189where
190    Renderer: iced::core::Renderer,
191{
192    fn children(&self) -> Vec<Tree> {
193        self.children.iter().map(Tree::new).collect()
194    }
195
196    fn state(&self) -> widget::tree::State {
197        widget::tree::State::new(State::default())
198    }
199
200    fn tag(&self) -> widget::tree::Tag {
201        widget::tree::Tag::of::<State>()
202    }
203
204    fn diff(&mut self, tree: &mut Tree) {
205        tree.diff_children(self.children.as_mut_slice());
206    }
207
208    fn size(&self) -> Size<Length> {
209        Size {
210            width: self.width,
211            height: self.height,
212        }
213    }
214
215    fn layout(
216        &mut self,
217        tree: &mut Tree,
218        renderer: &Renderer,
219        limits: &layout::Limits,
220    ) -> layout::Node {
221        let limits = limits.max_width(self.max_width);
222
223        layout::flex::resolve(
224            layout::flex::Axis::Vertical,
225            renderer,
226            &limits,
227            self.width,
228            self.height,
229            self.padding,
230            self.spacing,
231            self.align,
232            &mut self.children,
233            &mut tree.children,
234        )
235    }
236
237    fn operate(
238        &mut self,
239        tree: &mut Tree,
240        layout: Layout<'_>,
241        renderer: &Renderer,
242        operation: &mut dyn Operation,
243    ) {
244        operation.container(None, layout.bounds());
245        operation.traverse(&mut |operation| {
246            self.children
247                .iter_mut()
248                .zip(&mut tree.children)
249                .zip(layout.children())
250                .for_each(|((child, state), c_layout)| {
251                    child.as_widget_mut().operate(
252                        state,
253                        c_layout.with_virtual_offset(layout.virtual_offset()),
254                        renderer,
255                        operation,
256                    );
257                });
258        });
259    }
260
261    fn update(
262        &mut self,
263        tree: &mut Tree,
264        event: &Event,
265        layout: Layout<'_>,
266        cursor: mouse::Cursor,
267        renderer: &Renderer,
268        clipboard: &mut dyn Clipboard,
269        shell: &mut Shell<'_, Message>,
270        viewport: &Rectangle,
271    ) {
272        let my_state = tree.state.downcast_mut::<State>();
273
274        if let Some(hovered) = my_state.hovered {
275            let child_layout = layout.children().nth(hovered);
276            if let Some(child_layout) = child_layout
277                && cursor.is_over(child_layout.bounds())
278            {
279                // if mouse event, we can skip checking other children
280                if let Event::Mouse(e) = &event {
281                    if !matches!(
282                        e,
283                        mouse::Event::CursorLeft | mouse::Event::ButtonReleased { .. }
284                    ) {
285                        return self.children[hovered].as_widget_mut().update(
286                            &mut tree.children[hovered],
287                            event,
288                            child_layout.with_virtual_offset(layout.virtual_offset()),
289                            cursor,
290                            renderer,
291                            clipboard,
292                            shell,
293                            viewport,
294                        );
295                    }
296                } else if let Event::Touch(t) = &event {
297                    if !matches!(
298                        t,
299                        iced::core::touch::Event::FingerLifted { .. }
300                            | iced::core::touch::Event::FingerLost { .. }
301                    ) {
302                        return self.children[hovered].as_widget_mut().update(
303                            &mut tree.children[hovered],
304                            event,
305                            child_layout.with_virtual_offset(layout.virtual_offset()),
306                            cursor,
307                            renderer,
308                            clipboard,
309                            shell,
310                            viewport,
311                        );
312                    }
313                }
314            } else {
315                my_state.hovered = None;
316            }
317        }
318
319        for (((i, child), state), c_layout) in self
320            .children
321            .iter_mut()
322            .enumerate()
323            .zip(&mut tree.children)
324            .zip(layout.children())
325        {
326            let mut cursor_virtual = cursor;
327            if matches!(
328                event,
329                Event::Mouse(mouse::Event::CursorMoved { .. } | mouse::Event::CursorEntered)
330                    | Event::Touch(
331                        iced_core::touch::Event::FingerMoved { .. }
332                            | iced_core::touch::Event::FingerPressed { .. }
333                    )
334            ) && cursor.is_over(c_layout.bounds())
335            {
336                my_state.hovered = Some(i);
337                return child.as_widget_mut().update(
338                    state,
339                    &event,
340                    c_layout.with_virtual_offset(layout.virtual_offset()),
341                    cursor_virtual,
342                    renderer,
343                    clipboard,
344                    shell,
345                    viewport,
346                );
347            } else if my_state.hovered.is_some_and(|h| i != h) {
348                cursor_virtual = mouse::Cursor::Unavailable;
349            }
350
351            child.as_widget_mut().update(
352                state,
353                &event,
354                c_layout.with_virtual_offset(layout.virtual_offset()),
355                cursor_virtual,
356                renderer,
357                clipboard,
358                shell,
359                viewport,
360            );
361        }
362    }
363
364    fn mouse_interaction(
365        &self,
366        tree: &Tree,
367        layout: Layout<'_>,
368        cursor: mouse::Cursor,
369        viewport: &Rectangle,
370        renderer: &Renderer,
371    ) -> mouse::Interaction {
372        self.children
373            .iter()
374            .zip(&tree.children)
375            .zip(layout.children())
376            .map(|((child, state), c_layout)| {
377                child.as_widget().mouse_interaction(
378                    state,
379                    c_layout.with_virtual_offset(layout.virtual_offset()),
380                    cursor,
381                    viewport,
382                    renderer,
383                )
384            })
385            .max()
386            .unwrap_or_default()
387    }
388
389    fn draw(
390        &self,
391        tree: &Tree,
392        renderer: &mut Renderer,
393        theme: &Theme,
394        style: &renderer::Style,
395        layout: Layout<'_>,
396        cursor: mouse::Cursor,
397        viewport: &Rectangle,
398    ) {
399        if let Some(clipped_viewport) = layout.bounds().intersection(viewport) {
400            let my_state = tree.state.downcast_ref::<State>();
401
402            let viewport = if self.clip {
403                &clipped_viewport
404            } else {
405                viewport
406            };
407
408            for (i, ((child, state), c_layout)) in self
409                .children
410                .iter()
411                .zip(&tree.children)
412                .zip(layout.children())
413                .filter(|(_, layout)| layout.bounds().intersects(viewport))
414                .enumerate()
415            {
416                child.as_widget().draw(
417                    state,
418                    renderer,
419                    theme,
420                    style,
421                    c_layout.with_virtual_offset(layout.virtual_offset()),
422                    if my_state.hovered.is_some_and(|h| i == h) {
423                        cursor
424                    } else {
425                        mouse::Cursor::Unavailable
426                    },
427                    viewport,
428                );
429            }
430        }
431    }
432
433    fn overlay<'b>(
434        &'b mut self,
435        tree: &'b mut Tree,
436        layout: Layout<'b>,
437        renderer: &Renderer,
438        viewport: &Rectangle,
439        translation: Vector,
440    ) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
441        overlay::from_children(
442            &mut self.children,
443            tree,
444            layout,
445            renderer,
446            viewport,
447            translation,
448        )
449    }
450
451    #[cfg(feature = "a11y")]
452    /// get the a11y nodes for the widget
453    fn a11y_nodes(
454        &self,
455        layout: Layout<'_>,
456        state: &Tree,
457        cursor: mouse::Cursor,
458    ) -> iced_accessibility::A11yTree {
459        use iced_accessibility::A11yTree;
460        A11yTree::join(
461            self.children
462                .iter()
463                .zip(layout.children())
464                .zip(state.children.iter())
465                .map(|((c, c_layout), state)| {
466                    c.as_widget().a11y_nodes(
467                        c_layout.with_virtual_offset(layout.virtual_offset()),
468                        state,
469                        cursor,
470                    )
471                }),
472        )
473    }
474
475    fn drag_destinations(
476        &self,
477        state: &Tree,
478        layout: Layout<'_>,
479        renderer: &Renderer,
480        dnd_rectangles: &mut iced::core::clipboard::DndDestinationRectangles,
481    ) {
482        for ((e, c_layout), state) in self
483            .children
484            .iter()
485            .zip(layout.children())
486            .zip(state.children.iter())
487        {
488            e.as_widget().drag_destinations(
489                state,
490                c_layout.with_virtual_offset(layout.virtual_offset()),
491                renderer,
492                dnd_rectangles,
493            );
494        }
495    }
496}
497
498impl<'a, Message, Theme, Renderer> From<Column<'a, Message, Theme, Renderer>>
499    for Element<'a, Message, Theme, Renderer>
500where
501    Message: 'a,
502    Theme: 'a,
503    Renderer: iced::core::Renderer + 'a,
504{
505    fn from(column: Column<'a, Message, Theme, Renderer>) -> Self {
506        Self::new(column)
507    }
508}
509
510#[derive(Debug, Clone, Copy, PartialEq, Default)]
511pub struct State {
512    hovered: Option<usize>,
513}