1use egui::{Color32, CornerRadius, InnerResponse, Margin, Stroke, Ui, WidgetText};
4
5use crate::theme::Theme;
6
7#[derive(Default)]
18#[must_use = "Call `.show(ui, ...)` to render the card."]
19pub struct Card {
20 heading: Option<WidgetText>,
21 padding: Option<f32>,
22 fill: Option<Color32>,
23 bordered: bool,
24 corner_radius: Option<CornerRadius>,
25}
26
27impl std::fmt::Debug for Card {
28 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
29 f.debug_struct("Card")
30 .field("heading", &self.heading.as_ref().map(|h| h.text()))
31 .field("padding", &self.padding)
32 .field("fill", &self.fill)
33 .field("bordered", &self.bordered)
34 .field("corner_radius", &self.corner_radius)
35 .finish()
36 }
37}
38
39impl Card {
40 pub fn new() -> Self {
42 Self {
43 heading: None,
44 padding: None,
45 fill: None,
46 bordered: true,
47 corner_radius: None,
48 }
49 }
50
51 pub fn heading(mut self, heading: impl Into<WidgetText>) -> Self {
53 self.heading = Some(heading.into());
54 self
55 }
56
57 pub fn padding(mut self, padding: f32) -> Self {
59 self.padding = Some(padding);
60 self
61 }
62
63 pub fn fill(mut self, fill: Color32) -> Self {
65 self.fill = Some(fill);
66 self
67 }
68
69 pub fn bordered(mut self, bordered: bool) -> Self {
71 self.bordered = bordered;
72 self
73 }
74
75 pub fn corner_radius(mut self, radius: impl Into<CornerRadius>) -> Self {
78 self.corner_radius = Some(radius.into());
79 self
80 }
81
82 pub fn show<R>(self, ui: &mut Ui, add_contents: impl FnOnce(&mut Ui) -> R) -> InnerResponse<R> {
85 let theme = Theme::current(ui.ctx());
86 let p = &theme.palette;
87
88 let padding = self.padding.unwrap_or(theme.card_padding);
89 let stroke = if self.bordered {
90 Stroke::new(1.0, p.border)
91 } else {
92 Stroke::NONE
93 };
94
95 let radius = self
96 .corner_radius
97 .unwrap_or_else(|| CornerRadius::same(theme.card_radius as u8));
98
99 let frame = egui::Frame::new()
100 .fill(self.fill.unwrap_or(p.card))
101 .stroke(stroke)
102 .corner_radius(radius)
103 .inner_margin(Margin::same(padding as i8));
104
105 frame.show(ui, |ui| {
106 if let Some(h) = &self.heading {
107 let rt = egui::RichText::new(h.text())
108 .color(p.text_muted)
109 .size(theme.typography.heading)
110 .strong();
111 ui.add(egui::Label::new(rt).wrap_mode(egui::TextWrapMode::Extend));
112 ui.add_space(8.0);
113 }
114 add_contents(ui)
115 })
116 }
117}