use crate::stack;
use gpui::{AnyElement, Div, SharedString, div, prelude::*, px};
use icons::Icon;
use theme::{TextStyle, Theme, ThemeExt, Typeset};
pub const OPTION_CARD_HEIGHT: f32 = 148.0;
pub const OPTION_CARD_RADIUS: f32 = 10.0;
const ROW_INSET: f32 = 16.0;
const ROW_ICON: f32 = 16.0;
const ROW_GAP: f32 = Theme::SPACE;
const ROW_PAD_Y: 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_style(TextStyle::Title2)
.text_color(theme.text)
.child(title.into()),
)
.when_some(count, |el, count| {
el.child(
div()
.text_style(TextStyle::Body)
.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_style(TextStyle::Body)
.text_color(theme.text_muted)
.child(copy.into())
}
fn field_label(&self, label: impl Into<SharedString>) -> Div {
let theme = self.theme();
div()
.text_style(TextStyle::Body)
.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);
stack::column()
.flex_1()
.min_w_0()
.items_center()
.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_style(TextStyle::Body)
.text_color(if selected {
theme.text
} else {
theme.text_muted
})
.child(label.into()),
)
}
fn group_box(&self) -> Div {
let theme = self.theme();
div()
.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(ROW_INSET))
.py(px(ROW_PAD_Y))
.when(!first, |el| el.border_t_1().border_color(theme.border))
.flex()
.flex_row()
.items_center()
.gap(px(ROW_GAP))
}
fn row_icon(&self, icon: impl Into<Icon>) -> Div {
let theme = self.theme();
div()
.flex_none()
.size(px(ROW_ICON))
.flex()
.items_center()
.justify_center()
.child(
crate::icons::icon(icon)
.size(px(ROW_ICON))
.text_color(theme.text_muted),
)
}
fn row_title(&self, title: impl Into<SharedString>) -> Div {
let theme = self.theme();
div()
.min_w_0()
.overflow_hidden()
.whitespace_nowrap()
.text_style(TextStyle::Body)
.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(Theme::SPACE))
.gap_y(px(2.0))
.text_style(TextStyle::Subheadline)
.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 {}