use gpui::{AnyElement, Div, SharedString, div, prelude::*, px};
use theme::{Theme, ThemeExt, ink};
pub const OPTION_CARD_HEIGHT: f32 = 148.0;
pub const OPTION_CARD_RADIUS: f32 = 10.0;
const RING_GAP: f32 = 2.0;
const RING_WIDTH: f32 = 2.0;
pub trait Scaffolding: ThemeExt {
fn page_column(&self) -> Div {
div()
.w_full()
.max_w(px(768.0))
.mx_auto()
.px(px(24.0))
.pt(px(32.0))
.pb(px(64.0))
.flex()
.flex_col()
}
fn page_header(&self, title: impl Into<SharedString>, count: Option<usize>) -> Div {
let theme = self.theme();
div()
.flex()
.flex_row()
.items_baseline()
.gap(px(10.0))
.child(
div()
.text_size(px(16.0))
.font_weight(gpui::FontWeight::SEMIBOLD)
.text_color(theme.text)
.child(title.into()),
)
.when_some(count, |el, count| {
el.child(
div()
.text_size(px(13.0))
.text_color(theme.text_muted.opacity(0.7))
.child(format!("{count}")),
)
})
}
fn page_subtitle(&self, copy: impl Into<SharedString>) -> Div {
let theme = self.theme();
div()
.mt(px(4.0))
.text_size(px(13.0))
.text_color(theme.text_muted)
.child(copy.into())
}
fn field_label(&self, label: impl Into<SharedString>) -> Div {
let theme = self.theme();
div()
.text_size(px(13.0))
.font_weight(gpui::FontWeight::MEDIUM)
.text_color(theme.text)
.child(label.into())
}
fn option_card_row(&self) -> Div {
div().flex().flex_row().items_start().gap(px(16.0)).w_full()
}
fn option_card(
&self,
label: impl Into<SharedString>,
selected: bool,
preview: AnyElement,
) -> Div {
let theme = self.theme();
let frame = div()
.h(px(OPTION_CARD_HEIGHT))
.w_full()
.rounded(px(OPTION_CARD_RADIUS))
.overflow_hidden()
.border_1()
.border_color(theme.border)
.child(preview);
div()
.flex_1()
.min_w_0()
.flex()
.flex_col()
.items_center()
.gap(px(8.0))
.cursor_pointer()
.child(
div()
.w_full()
.rounded(px(OPTION_CARD_RADIUS + RING_GAP + RING_WIDTH))
.p(px(RING_GAP))
.border_2()
.border_color(if selected {
theme.accent
} else {
gpui::transparent_black()
})
.child(frame),
)
.child(
div()
.text_size(px(13.0))
.text_color(if selected {
theme.text
} else {
theme.text_muted
})
.child(label.into()),
)
}
fn group_box(&self) -> Div {
let theme = self.theme();
div()
.mt(px(24.0))
.rounded(px(Theme::SURFACE_RADIUS))
.border_1()
.border_color(theme.border)
.bg(theme.card_glass_bg())
.overflow_hidden()
.flex()
.flex_col()
}
fn card_row(&self, first: bool) -> Div {
let theme = self.theme();
div()
.px(px(20.0))
.py(px(14.0))
.when(!first, |el| el.border_t_1().border_color(theme.border))
.flex()
.flex_row()
.items_center()
.gap(px(14.0))
}
fn row_tile(&self, icon_path: &'static str) -> Div {
let theme = self.theme();
div()
.flex_none()
.size(px(36.0))
.rounded(px(Theme::PANEL_RADIUS))
.border_1()
.border_color(theme.border)
.bg(ink(0.03))
.flex()
.items_center()
.justify_center()
.child(
crate::icons::icon(icon_path)
.size(px(16.0))
.text_color(theme.text_muted),
)
}
fn row_title(&self, title: impl Into<SharedString>) -> Div {
let theme = self.theme();
div()
.min_w_0()
.truncate()
.text_size(px(13.5))
.font_weight(gpui::FontWeight::MEDIUM)
.text_color(theme.text)
.child(title.into())
}
fn meta_line(&self, fragments: Vec<AnyElement>) -> Div {
let theme = self.theme();
let mut line = div()
.mt(px(4.0))
.flex()
.flex_row()
.flex_wrap()
.items_center()
.gap_x(px(8.0))
.gap_y(px(2.0))
.text_size(px(11.5))
.text_color(theme.text_muted.opacity(0.65));
let mut first = true;
for fragment in fragments {
if !first {
line = line.child(
div()
.text_color(theme.text_muted.opacity(0.3))
.child(SharedString::from("·")),
);
}
line = line.child(fragment);
first = false;
}
line
}
}
impl Scaffolding for Theme {}