1use 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#[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 pub fn new() -> Self {
56 Self::from_vec(Vec::new())
57 }
58
59 pub fn with_capacity(capacity: usize) -> Self {
61 Self::from_vec(Vec::with_capacity(capacity))
62 }
63
64 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 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 pub fn spacing(mut self, amount: impl Into<Pixels>) -> Self {
99 self.spacing = amount.into().0;
100 self
101 }
102
103 pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self {
105 self.padding = padding.into();
106 self
107 }
108
109 pub fn width(mut self, width: impl Into<Length>) -> Self {
111 self.width = width.into();
112 self
113 }
114
115 pub fn height(mut self, height: impl Into<Length>) -> Self {
117 self.height = height.into();
118 self
119 }
120
121 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 pub fn align_x(mut self, align: impl Into<alignment::Horizontal>) -> Self {
129 self.align = Alignment::from(align.into());
130 self
131 }
132
133 pub fn clip(mut self, clip: bool) -> Self {
136 self.clip = clip;
137 self
138 }
139
140 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 #[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 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 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 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}