iced_aw/widget/
card.rs

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