Skip to main content

ashlar_ui/content/
card.rs

1//! A bordered group of related content.
2
3use egui::{InnerResponse, Margin, RichText, Stroke, Ui};
4
5use crate::automation::{self, Details, Role};
6use crate::tokens;
7
8/// A panel-coloured frame with an optional title around content the app draws.
9pub struct Card<'a> {
10    id: &'a str,
11    title: Option<&'a str>,
12}
13
14impl<'a> Card<'a> {
15    pub fn new(id: &'a str) -> Self {
16        Card { id, title: None }
17    }
18    /// A heading at the top, also the group's accessible name.
19    pub fn title(mut self, title: &'a str) -> Self {
20        self.title = Some(title);
21        self
22    }
23
24    pub fn show<R>(self, ui: &mut Ui, add: impl FnOnce(&mut Ui) -> R) -> InnerResponse<R> {
25        let t = tokens::current(ui.ctx());
26        let title = self.title;
27        let inner = egui::Frame::new()
28            .fill(t.surface.panel)
29            .stroke(Stroke::new(1.0, t.border))
30            .corner_radius(t.radius.lg)
31            .inner_margin(Margin::same(t.space.lg.round().clamp(0.0, 127.0) as i8))
32            .show(ui, |ui| {
33                ui.ctx().accesskit_node_builder(ui.unique_id(), |node| {
34                    node.set_role(egui::accesskit::Role::Group);
35                    if let Some(title) = title {
36                        node.set_label(title);
37                    }
38                });
39                if let Some(title) = title {
40                    ui.label(RichText::new(title).size(t.type_scale.heading).strong().color(t.text.primary));
41                    ui.add_space(t.space.sm);
42                }
43                add(ui)
44            });
45        automation::register_with(ui, &inner.response, self.id, title.unwrap_or_default(), Role::Group, Details::default());
46        inner
47    }
48}