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