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