iced_aw/widget/
card.rs

1//! Displays a [`Card`].
2//!
3//! *This API requires the following crate features to be activated: card*
4
5use crate::iced_aw_font::advanced_text::cancel;
6pub use crate::style::{
7    card::{Catalog, Style},
8    status::{Status, StyleFn},
9};
10use iced_core::{
11    Alignment, Border, Clipboard, Color, Element, Event, Layout, Length, Padding, Pixels, Point,
12    Rectangle, Shadow, Shell, Size, Vector, Widget,
13    alignment::Vertical,
14    layout::{Limits, Node},
15    mouse::{self, Cursor},
16    overlay, renderer,
17    text::LineHeight,
18    touch,
19    widget::{Operation, Tree},
20};
21use iced_widget::text::Wrapping;
22
23/// The default padding of a [`Card`].
24const DEFAULT_PADDING: Padding = Padding::new(10.0);
25
26/// A card consisting of a head, body and optional foot.
27///
28/// # Example
29/// ```ignore
30/// # use iced_widget::Text;
31/// # use iced_aw::Card;
32/// #
33/// #[derive(Debug, Clone)]
34/// enum Message {
35///     ClosingCard,
36/// }
37///
38/// let card = Card::new(
39///     Text::new("Head"),
40///     Text::new("Body")
41/// )
42/// .foot(Text::new("Foot"))
43/// .on_close(Message::ClosingCard);
44///
45/// ```
46#[allow(missing_debug_implementations)]
47pub struct Card<'a, Message, Theme = iced_widget::Theme, Renderer = iced_widget::Renderer>
48where
49    Renderer: renderer::Renderer,
50    Theme: Catalog,
51{
52    /// The width of the [`Card`].
53    width: Length,
54    /// The height of the [`Card`].
55    height: Length,
56    /// The maximum width of the [`Card`].
57    max_width: f32,
58    /// The maximum height of the [`Card`].
59    max_height: f32,
60    /// The padding of the head of the [`Card`].
61    padding_head: Padding,
62    /// The padding of the body of the [`Card`].
63    padding_body: Padding,
64    /// The padding of the foot of the [`Card`].
65    padding_foot: Padding,
66    /// The optional size of the close icon of the [`Card`].
67    close_size: Option<f32>,
68    /// The optional message that is send if the close icon of the [`Card`] is pressed.
69    on_close: Option<Message>,
70    /// The head [`Element`] of the [`Card`].
71    head: Element<'a, Message, Theme, Renderer>,
72    /// The body [`Element`] of the [`Card`].
73    body: Element<'a, Message, Theme, Renderer>,
74    /// The optional foot [`Element`] of the [`Card`].
75    foot: Option<Element<'a, Message, Theme, Renderer>>,
76    /// The style of the [`Card`].
77    class: Theme::Class<'a>,
78    /// Used to display the mouse over action on the close button.
79    is_mouse_over_close: bool,
80}
81
82impl<'a, Message, Theme, Renderer> Card<'a, Message, Theme, Renderer>
83where
84    Renderer: renderer::Renderer,
85    Theme: Catalog,
86{
87    /// Creates a new [`Card`] containing the given head and body.
88    ///
89    /// It expects:
90    ///     * the head [`Element`] to display at the top of the [`Card`].
91    ///     * the body [`Element`] to display at the middle of the [`Card`].
92    pub fn new<H, B>(head: H, body: B) -> Self
93    where
94        H: Into<Element<'a, Message, Theme, Renderer>>,
95        B: Into<Element<'a, Message, Theme, Renderer>>,
96    {
97        Card {
98            width: Length::Fill,
99            height: Length::Shrink,
100            max_width: u32::MAX as f32,
101            max_height: u32::MAX as f32,
102            padding_head: DEFAULT_PADDING,
103            padding_body: DEFAULT_PADDING,
104            padding_foot: DEFAULT_PADDING,
105            close_size: None,
106            on_close: None,
107            head: head.into(),
108            body: body.into(),
109            foot: None,
110            class: Theme::default(),
111            is_mouse_over_close: false,
112        }
113    }
114
115    /// Sets the [`Element`] of the foot of the [`Card`].
116    #[must_use]
117    pub fn foot<F>(mut self, foot: F) -> Self
118    where
119        F: Into<Element<'a, Message, Theme, Renderer>>,
120    {
121        self.foot = Some(foot.into());
122        self
123    }
124
125    /// Sets the size of the close icon of the [`Card`].
126    #[must_use]
127    pub fn close_size(mut self, size: f32) -> Self {
128        self.close_size = Some(size);
129        self
130    }
131
132    /// Sets the height of the [`Card`].
133    #[must_use]
134    pub fn height(mut self, height: impl Into<Length>) -> Self {
135        self.height = height.into();
136        self
137    }
138
139    /// Sets the maximum height of the [`Card`].
140    #[must_use]
141    pub fn max_height(mut self, height: f32) -> Self {
142        self.max_height = height;
143        self
144    }
145
146    /// Sets the maximum width of the [`Card`].
147    #[must_use]
148    pub fn max_width(mut self, width: f32) -> Self {
149        self.max_width = width;
150        self
151    }
152
153    /// Sets the message that will be produced when the close icon of the
154    /// [`Card`] is pressed.
155    ///
156    /// Setting this enables the drawing of a close icon on the [`Card`].
157    #[must_use]
158    pub fn on_close(mut self, msg: Message) -> Self {
159        self.on_close = Some(msg);
160        self
161    }
162
163    /// Sets the padding of the [`Card`].
164    ///
165    /// This will set the padding of the head, body and foot to the
166    /// same value.
167    #[must_use]
168    pub fn padding(mut self, padding: Padding) -> Self {
169        self.padding_head = padding;
170        self.padding_body = padding;
171        self.padding_foot = padding;
172        self
173    }
174
175    /// Sets the padding of the head of the [`Card`].
176    #[must_use]
177    pub fn padding_head(mut self, padding: Padding) -> Self {
178        self.padding_head = padding;
179        self
180    }
181
182    /// Sets the padding of the body of the [`Card`].
183    #[must_use]
184    pub fn padding_body(mut self, padding: Padding) -> Self {
185        self.padding_body = padding;
186        self
187    }
188
189    /// Sets the padding of the foot of the [`Card`].
190    #[must_use]
191    pub fn padding_foot(mut self, padding: Padding) -> Self {
192        self.padding_foot = padding;
193        self
194    }
195
196    /// Sets the style of the [`Card`].
197    #[must_use]
198    pub fn style(mut self, style: impl Fn(&Theme, Status) -> Style + 'a) -> Self
199    where
200        Theme::Class<'a>: From<StyleFn<'a, Theme, Style>>,
201    {
202        self.class = (Box::new(style) as StyleFn<'a, Theme, Style>).into();
203        self
204    }
205
206    /// Sets the class of the input of the [`Card`].
207    #[must_use]
208    pub fn class(mut self, class: impl Into<Theme::Class<'a>>) -> Self {
209        self.class = class.into();
210        self
211    }
212
213    /// Sets the width of the [`Card`].
214    #[must_use]
215    pub fn width(mut self, width: impl Into<Length>) -> Self {
216        self.width = width.into();
217        self
218    }
219}
220
221impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
222    for Card<'a, Message, Theme, Renderer>
223where
224    Message: 'a + Clone,
225    Renderer: 'a + renderer::Renderer + iced_core::text::Renderer<Font = iced_core::Font>,
226    Theme: Catalog,
227{
228    fn children(&self) -> Vec<Tree> {
229        self.foot.as_ref().map_or_else(
230            || vec![Tree::new(&self.head), Tree::new(&self.body)],
231            |foot| {
232                vec![
233                    Tree::new(&self.head),
234                    Tree::new(&self.body),
235                    Tree::new(foot),
236                ]
237            },
238        )
239    }
240
241    fn diff(&self, tree: &mut Tree) {
242        if let Some(foot) = self.foot.as_ref() {
243            tree.diff_children(&[&self.head, &self.body, foot]);
244        } else {
245            tree.diff_children(&[&self.head, &self.body]);
246        }
247    }
248
249    fn size(&self) -> Size<Length> {
250        Size {
251            width: self.width,
252            height: self.height,
253        }
254    }
255
256    fn layout(&mut self, tree: &mut Tree, renderer: &Renderer, limits: &Limits) -> Node {
257        let limits = limits.max_width(self.max_width).max_height(self.max_height);
258
259        let head_node = head_node(
260            renderer,
261            &limits,
262            &mut self.head,
263            self.padding_head,
264            self.width,
265            self.on_close.is_some(),
266            self.close_size,
267            tree,
268        );
269
270        let limits = limits.shrink(Size::new(0.0, head_node.size().height));
271
272        let mut foot_node = self.foot.as_mut().map_or_else(Node::default, |foot| {
273            foot_node(renderer, &limits, foot, self.padding_foot, self.width, tree)
274        });
275        let limits = limits.shrink(Size::new(0.0, foot_node.size().height));
276        let mut body_node = body_node(
277            renderer,
278            &limits,
279            &mut self.body,
280            self.padding_body,
281            self.width,
282            tree,
283        );
284        let body_bounds = body_node.bounds();
285        body_node = body_node.move_to(Point::new(
286            body_bounds.x,
287            body_bounds.y + head_node.bounds().height,
288        ));
289
290        let foot_bounds = foot_node.bounds();
291
292        foot_node = foot_node.move_to(Point::new(
293            foot_bounds.x,
294            foot_bounds.y + head_node.bounds().height + body_node.bounds().height,
295        ));
296
297        Node::with_children(
298            Size::new(
299                body_node.size().width,
300                head_node.size().height + body_node.size().height + foot_node.size().height,
301            ),
302            vec![head_node, body_node, foot_node],
303        )
304    }
305
306    fn update(
307        &mut self,
308        state: &mut Tree,
309        event: &Event,
310        layout: Layout<'_>,
311        cursor: Cursor,
312        renderer: &Renderer,
313        clipboard: &mut dyn Clipboard,
314        shell: &mut Shell<Message>,
315        viewport: &Rectangle,
316    ) {
317        let mut children = layout.children();
318        let head_layout = children
319            .next()
320            .expect("widget: Layout should have a head layout");
321        let mut head_children = head_layout.children();
322
323        self.head.as_widget_mut().update(
324            &mut state.children[0],
325            event,
326            head_children
327                .next()
328                .expect("widget: Layout should have a head content layout"),
329            cursor,
330            renderer,
331            clipboard,
332            shell,
333            viewport,
334        );
335
336        if let Some(close_layout) = head_children.next() {
337            match event {
338                Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
339                | Event::Touch(touch::Event::FingerPressed { .. }) => {
340                    if let Some(on_close) = &self.on_close {
341                        if close_layout
342                            .bounds()
343                            .contains(cursor.position().unwrap_or_default())
344                        {
345                            shell.publish(on_close.clone());
346                            self.is_mouse_over_close = true;
347                            shell.capture_event();
348                            shell.request_redraw();
349                        } else if self.is_mouse_over_close {
350                            self.is_mouse_over_close = false;
351                            shell.request_redraw();
352                        }
353                    }
354                }
355                _ => {}
356            }
357        }
358
359        let body_layout = children
360            .next()
361            .expect("widget: Layout should have a body layout");
362
363        self.body.as_widget_mut().update(
364            &mut state.children[1],
365            event,
366            body_layout
367                .children()
368                .next()
369                .expect("widget: Layout should have a body content layout"),
370            cursor,
371            renderer,
372            clipboard,
373            shell,
374            viewport,
375        );
376
377        let foot_layout = children
378            .next()
379            .expect("widget: Layout should have a foot layout");
380
381        if let Some(foot) = self.foot.as_mut() {
382            foot.as_widget_mut().update(
383                &mut state.children[2],
384                event,
385                foot_layout
386                    .children()
387                    .next()
388                    .expect("widget: Layout should have a foot content layout"),
389                cursor,
390                renderer,
391                clipboard,
392                shell,
393                viewport,
394            );
395        }
396    }
397
398    fn mouse_interaction(
399        &self,
400        state: &Tree,
401        layout: Layout<'_>,
402        cursor: Cursor,
403        viewport: &Rectangle,
404        renderer: &Renderer,
405    ) -> mouse::Interaction {
406        let mut children = layout.children();
407
408        let head_layout = children
409            .next()
410            .expect("widget: Layout should have a head layout");
411        let mut head_children = head_layout.children();
412
413        let head = head_children
414            .next()
415            .expect("widget: Layout should have a head layout");
416        let close_layout = head_children.next();
417
418        let is_mouse_over_close = close_layout.is_some_and(|layout| {
419            let bounds = layout.bounds();
420            bounds.contains(cursor.position().unwrap_or_default())
421        });
422
423        let mouse_interaction = if is_mouse_over_close {
424            mouse::Interaction::Pointer
425        } else {
426            mouse::Interaction::default()
427        };
428
429        let body_layout = children
430            .next()
431            .expect("widget: Layout should have a body layout");
432        let mut body_children = body_layout.children();
433
434        let foot_layout = children
435            .next()
436            .expect("widget: Layout should have a foot layout");
437        let mut foot_children = foot_layout.children();
438
439        mouse_interaction
440            .max(self.head.as_widget().mouse_interaction(
441                &state.children[0],
442                head,
443                cursor,
444                viewport,
445                renderer,
446            ))
447            .max(
448                self.body.as_widget().mouse_interaction(
449                    &state.children[1],
450                    body_children
451                        .next()
452                        .expect("widget: Layout should have a body content layout"),
453                    cursor,
454                    viewport,
455                    renderer,
456                ),
457            )
458            .max(
459                self.foot
460                    .as_ref()
461                    .map_or_else(mouse::Interaction::default, |foot| {
462                        foot.as_widget().mouse_interaction(
463                            &state.children[2],
464                            foot_children
465                                .next()
466                                .expect("widget: Layout should have a foot content layout"),
467                            cursor,
468                            viewport,
469                            renderer,
470                        )
471                    }),
472            )
473    }
474
475    fn operate<'b>(
476        &'b mut self,
477        state: &'b mut Tree,
478        layout: Layout<'_>,
479        renderer: &Renderer,
480        operation: &mut dyn Operation<()>,
481    ) {
482        let mut children = layout.children();
483        let head_layout = children.next().expect("Missing Head Layout");
484        let body_layout = children.next().expect("Missing Body Layout");
485        let foot_layout = children.next().expect("Missing Footer Layout");
486
487        self.head
488            .as_widget_mut()
489            .operate(&mut state.children[0], head_layout, renderer, operation);
490        self.body
491            .as_widget_mut()
492            .operate(&mut state.children[1], body_layout, renderer, operation);
493
494        if let Some(footer) = &mut self.foot {
495            footer.as_widget_mut().operate(
496                &mut state.children[2],
497                foot_layout,
498                renderer,
499                operation,
500            );
501        }
502    }
503
504    fn draw(
505        &self,
506        state: &Tree,
507        renderer: &mut Renderer,
508        theme: &Theme,
509        _style: &renderer::Style,
510        layout: Layout<'_>,
511        cursor: Cursor,
512        viewport: &Rectangle,
513    ) {
514        let bounds = layout.bounds();
515        let mut children = layout.children();
516        let style_sheet = theme.style(&self.class, Status::Active);
517
518        if bounds.intersects(viewport) {
519            // Background
520            renderer.fill_quad(
521                renderer::Quad {
522                    bounds,
523                    border: Border {
524                        radius: style_sheet.border_radius.into(),
525                        width: style_sheet.border_width,
526                        color: style_sheet.border_color,
527                    },
528                    shadow: Shadow::default(),
529                    snap: false,
530                },
531                style_sheet.background,
532            );
533
534            // Border
535            renderer.fill_quad(
536                // TODO: fill not necessary
537                renderer::Quad {
538                    bounds,
539                    border: Border {
540                        radius: style_sheet.border_radius.into(),
541                        width: style_sheet.border_width,
542                        color: style_sheet.border_color,
543                    },
544                    shadow: Shadow::default(),
545                    snap: false,
546                },
547                Color::TRANSPARENT,
548            );
549        }
550
551        // ----------- Head ----------------------
552        let head_layout = children
553            .next()
554            .expect("Graphics: Layout should have a head layout");
555        draw_head(
556            &state.children[0],
557            renderer,
558            &self.head,
559            head_layout,
560            cursor,
561            viewport,
562            theme,
563            &style_sheet,
564            self.close_size,
565            self.is_mouse_over_close,
566        );
567
568        // ----------- Body ----------------------
569        let body_layout = children
570            .next()
571            .expect("Graphics: Layout should have a body layout");
572        draw_body(
573            &state.children[1],
574            renderer,
575            &self.body,
576            body_layout,
577            cursor,
578            viewport,
579            theme,
580            &style_sheet,
581        );
582
583        // ----------- Foot ----------------------
584        let foot_layout = children
585            .next()
586            .expect("Graphics: Layout should have a foot layout");
587        draw_foot(
588            state.children.get(2),
589            renderer,
590            self.foot.as_ref(),
591            foot_layout,
592            cursor,
593            viewport,
594            theme,
595            &style_sheet,
596        );
597    }
598
599    fn overlay<'b>(
600        &'b mut self,
601        tree: &'b mut Tree,
602        layout: Layout<'b>,
603        renderer: &Renderer,
604        viewport: &Rectangle,
605        translation: Vector,
606    ) -> Option<iced_core::overlay::Element<'b, Message, Theme, Renderer>> {
607        let mut children = vec![&mut self.head, &mut self.body];
608        if let Some(foot) = &mut self.foot {
609            children.push(foot);
610        }
611        let children = children
612            .into_iter()
613            .zip(&mut tree.children)
614            .zip(layout.children())
615            .filter_map(|((child, state), layout)| {
616                layout.children().next().and_then(|child_layout| {
617                    child.as_widget_mut().overlay(
618                        state,
619                        child_layout,
620                        renderer,
621                        viewport,
622                        translation,
623                    )
624                })
625            })
626            .collect::<Vec<_>>();
627
628        if children.is_empty() {
629            None
630        } else {
631            Some(overlay::Group::with_children(children).overlay())
632        }
633    }
634}
635
636/// Calculates the layout of the head.
637#[allow(clippy::too_many_arguments)]
638fn head_node<Message, Theme, Renderer>(
639    renderer: &Renderer,
640    limits: &Limits,
641    head: &mut Element<'_, Message, Theme, Renderer>,
642    padding: Padding,
643    width: Length,
644    on_close: bool,
645    close_size: Option<f32>,
646    tree: &mut Tree,
647) -> Node
648where
649    Renderer: renderer::Renderer + iced_core::text::Renderer<Font = iced_core::Font>,
650{
651    let header_size = head.as_widget().size();
652
653    let mut limits = limits
654        .loose()
655        .width(width)
656        .height(header_size.height)
657        .shrink(padding);
658
659    let close_size = close_size.unwrap_or_else(|| renderer.default_size().0);
660
661    if on_close {
662        limits = limits.shrink(Size::new(close_size, 0.0));
663    }
664
665    let mut head = head
666        .as_widget_mut()
667        .layout(&mut tree.children[0], renderer, &limits);
668    let mut size = limits.resolve(width, header_size.height, head.size());
669
670    head = head.move_to(Point::new(padding.left, padding.top));
671    let head_size = head.size();
672    head = head.align(Alignment::Start, Alignment::Center, head_size);
673
674    let close = if on_close {
675        let node = Node::new(Size::new(close_size + 1.0, close_size + 1.0));
676        let node_size = node.size();
677
678        size = Size::new(size.width + close_size, size.height);
679
680        Some(
681            node.move_to(Point::new(size.width - padding.right, padding.top))
682                .align(Alignment::End, Alignment::Center, node_size),
683        )
684    } else {
685        None
686    };
687
688    Node::with_children(
689        size.expand(padding),
690        match close {
691            Some(node) => vec![head, node],
692            None => vec![head],
693        },
694    )
695}
696
697/// Calculates the layout of the body.
698fn body_node<Message, Theme, Renderer>(
699    renderer: &Renderer,
700    limits: &Limits,
701    body: &mut Element<'_, Message, Theme, Renderer>,
702    padding: Padding,
703    width: Length,
704    tree: &mut Tree,
705) -> Node
706where
707    Renderer: renderer::Renderer,
708{
709    let body_size = body.as_widget().size();
710
711    let limits = limits
712        .loose()
713        .width(width)
714        .height(body_size.height)
715        .shrink(padding);
716
717    let mut body = body
718        .as_widget_mut()
719        .layout(&mut tree.children[1], renderer, &limits);
720    let size = limits.resolve(width, body_size.height, body.size());
721
722    body = body.move_to(Point::new(padding.left, padding.top)).align(
723        Alignment::Start,
724        Alignment::Start,
725        size,
726    );
727
728    Node::with_children(size.expand(padding), vec![body])
729}
730
731/// Calculates the layout of the foot.
732fn foot_node<Message, Theme, Renderer>(
733    renderer: &Renderer,
734    limits: &Limits,
735    foot: &mut Element<'_, Message, Theme, Renderer>,
736    padding: Padding,
737    width: Length,
738    tree: &mut Tree,
739) -> Node
740where
741    Renderer: renderer::Renderer,
742{
743    let foot_size = foot.as_widget().size();
744
745    let limits = limits
746        .loose()
747        .width(width)
748        .height(foot_size.height)
749        .shrink(padding);
750
751    let mut foot = foot
752        .as_widget_mut()
753        .layout(&mut tree.children[2], renderer, &limits);
754    let size = limits.resolve(width, foot_size.height, foot.size());
755
756    foot = foot.move_to(Point::new(padding.left, padding.right)).align(
757        Alignment::Start,
758        Alignment::Center,
759        size,
760    );
761
762    Node::with_children(size.expand(padding), vec![foot])
763}
764
765/// Draws the head of the card.
766#[allow(clippy::too_many_arguments)]
767fn draw_head<Message, Theme, Renderer>(
768    state: &Tree,
769    renderer: &mut Renderer,
770    head: &Element<'_, Message, Theme, Renderer>,
771    layout: Layout<'_>,
772    cursor: Cursor,
773    viewport: &Rectangle,
774    theme: &Theme,
775    style: &Style,
776    close_size: Option<f32>,
777    is_mouse_over_close: bool,
778) where
779    Renderer: renderer::Renderer + iced_core::text::Renderer<Font = iced_core::Font>,
780    Theme: Catalog,
781{
782    let mut head_children = layout.children();
783    let bounds = layout.bounds();
784    let border_radius = style.border_radius;
785
786    // Head background
787    if bounds.intersects(viewport) {
788        renderer.fill_quad(
789            renderer::Quad {
790                bounds,
791                border: Border {
792                    radius: border_radius.into(),
793                    width: 0.0,
794                    color: Color::TRANSPARENT,
795                },
796                shadow: Shadow::default(),
797                snap: false,
798            },
799            style.head_background,
800        );
801    }
802
803    // cover rounded button of header
804    let button_bounds = Rectangle {
805        x: bounds.x,
806        y: bounds.y + bounds.height - border_radius,
807        width: bounds.width,
808        height: border_radius,
809    };
810    if button_bounds.intersects(viewport) {
811        renderer.fill_quad(
812            renderer::Quad {
813                bounds: button_bounds,
814                border: Border {
815                    radius: (0.0).into(),
816                    width: 0.0,
817                    color: Color::TRANSPARENT,
818                },
819                shadow: Shadow::default(),
820                snap: false,
821            },
822            style.head_background,
823        );
824    }
825
826    head.as_widget().draw(
827        state,
828        renderer,
829        theme,
830        &renderer::Style {
831            text_color: style.head_text_color,
832        },
833        head_children
834            .next()
835            .expect("Graphics: Layout should have a head content layout"),
836        cursor,
837        viewport,
838    );
839
840    if let Some(close_layout) = head_children.next() {
841        let close_bounds = close_layout.bounds();
842        let (content, font, shaping) = cancel();
843
844        renderer.fill_text(
845            iced_core::text::Text {
846                content,
847                bounds: Size::new(close_bounds.width, close_bounds.height),
848                size: Pixels(
849                    close_size.unwrap_or_else(|| renderer.default_size().0)
850                        + if is_mouse_over_close { 3.0 } else { 0.0 },
851                ),
852                font,
853                align_x: iced_widget::text::Alignment::Center,
854                align_y: Vertical::Center,
855                line_height: LineHeight::Relative(1.3),
856                shaping,
857                wrapping: Wrapping::default(),
858            },
859            Point::new(close_bounds.center_x(), close_bounds.center_y()),
860            style.close_color,
861            close_bounds,
862        );
863    }
864}
865
866/// Draws the body of the card.
867#[allow(clippy::too_many_arguments)]
868fn draw_body<Message, Theme, Renderer>(
869    state: &Tree,
870    renderer: &mut Renderer,
871    body: &Element<'_, Message, Theme, Renderer>,
872    layout: Layout<'_>,
873    cursor: Cursor,
874    viewport: &Rectangle,
875    theme: &Theme,
876    style: &Style,
877) where
878    Renderer: renderer::Renderer + iced_core::text::Renderer<Font = iced_core::Font>,
879    Theme: Catalog,
880{
881    let mut body_children = layout.children();
882    let bounds = layout.bounds();
883
884    // Body background
885    if bounds.intersects(viewport) {
886        renderer.fill_quad(
887            renderer::Quad {
888                bounds,
889                border: Border {
890                    radius: (0.0).into(),
891                    width: 0.0,
892                    color: Color::TRANSPARENT,
893                },
894                shadow: Shadow::default(),
895                snap: false,
896            },
897            style.body_background,
898        );
899    }
900
901    body.as_widget().draw(
902        state,
903        renderer,
904        theme,
905        &renderer::Style {
906            text_color: style.body_text_color,
907        },
908        body_children
909            .next()
910            .expect("Graphics: Layout should have a body content layout"),
911        cursor,
912        viewport,
913    );
914}
915
916/// Draws the foot of the card.
917#[allow(clippy::too_many_arguments)]
918fn draw_foot<Message, Theme, Renderer>(
919    state: Option<&Tree>,
920    renderer: &mut Renderer,
921    foot: Option<&Element<'_, Message, Theme, Renderer>>,
922    layout: Layout<'_>,
923    cursor: Cursor,
924    viewport: &Rectangle,
925    theme: &Theme,
926    style: &Style,
927) where
928    Renderer: renderer::Renderer + iced_core::text::Renderer<Font = iced_core::Font>,
929    Theme: Catalog,
930{
931    let mut foot_children = layout.children();
932    let bounds = layout.bounds();
933
934    // Foot background
935    if bounds.intersects(viewport) {
936        renderer.fill_quad(
937            renderer::Quad {
938                bounds,
939                border: Border {
940                    radius: style.border_radius.into(),
941                    width: 0.0,
942                    color: Color::TRANSPARENT,
943                },
944                shadow: Shadow::default(),
945                snap: false,
946            },
947            style.foot_background,
948        );
949    }
950
951    if let Some((foot, state)) = foot.as_ref().zip(state) {
952        foot.as_widget().draw(
953            state,
954            renderer,
955            theme,
956            &renderer::Style {
957                text_color: style.foot_text_color,
958            },
959            foot_children
960                .next()
961                .expect("Graphics: Layout should have a foot content layout"),
962            cursor,
963            viewport,
964        );
965    }
966}
967
968impl<'a, Message, Theme, Renderer> From<Card<'a, Message, Theme, Renderer>>
969    for Element<'a, Message, Theme, Renderer>
970where
971    Renderer: 'a + renderer::Renderer + iced_core::text::Renderer<Font = iced_core::Font>,
972    Theme: 'a + Catalog,
973    Message: Clone + 'a,
974{
975    fn from(card: Card<'a, Message, Theme, Renderer>) -> Self {
976        Element::new(card)
977    }
978}