Skip to main content

cosmic/applet/
row.rs

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