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