iced_widget/keyed/
column.rs

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