1use 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#[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 pub fn new() -> Self {
55 Self::from_vec(Vec::new())
56 }
57
58 pub fn with_capacity(capacity: usize) -> Self {
60 Self::from_vec(Vec::with_capacity(capacity))
61 }
62
63 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 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 pub fn spacing(mut self, amount: impl Into<Pixels>) -> Self {
97 self.spacing = amount.into().0;
98 self
99 }
100
101 pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self {
103 self.padding = padding.into();
104 self
105 }
106
107 pub fn width(mut self, width: impl Into<Length>) -> Self {
109 self.width = width.into();
110 self
111 }
112
113 pub fn height(mut self, height: impl Into<Length>) -> Self {
115 self.height = height.into();
116 self
117 }
118
119 pub fn align_y(mut self, align: impl Into<alignment::Vertical>) -> Self {
121 self.align = Alignment::from(align.into());
122 self
123 }
124
125 pub fn clip(mut self, clip: bool) -> Self {
128 self.clip = clip;
129 self
130 }
131
132 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 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 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 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 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}