1use 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#[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 pub fn new() -> Self {
53 Self::from_vec(Vec::new())
54 }
55
56 pub fn with_capacity(capacity: usize) -> Self {
58 Self::from_vec(Vec::with_capacity(capacity))
59 }
60
61 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 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 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 align_y(mut self, align: impl Into<alignment::Vertical>) -> Self {
119 self.align = Alignment::from(align.into());
120 self
121 }
122
123 pub fn clip(mut self, clip: bool) -> Self {
126 self.clip = clip;
127 self
128 }
129
130 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 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 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 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 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}