use crate::stack;
use gpui::{Div, SharedString, Svg, div, prelude::*, px};
use icons::Icon;
use motion::{self, Fade};
use theme::{TextStyle, Theme, ThemeExt, Typeset};
pub struct SplitDrag;
pub const SPLIT_HANDLE_HIT: f32 = 9.0;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SplitStyle {
Line {
dragging: bool,
},
Ghost,
}
pub trait Layout: ThemeExt {
fn disclosure(&self, expanded: bool) -> Svg {
let theme = self.theme();
crate::icons::icon(if expanded {
crate::icons::glyph::ChevronDown
} else {
crate::icons::glyph::ChevronRight
})
.size(px(14.0))
.text_color(theme.text_muted)
}
fn collapsible_header(&self, label: impl Into<SharedString>, expanded: bool) -> Div {
let theme = self.theme();
div()
.self_start()
.flex()
.flex_row()
.items_center()
.gap(px(6.0))
.px(px(4.0))
.py(px(5.0))
.rounded(px(Theme::control_radius()))
.cursor_pointer()
.child(Layout::disclosure(self.theme(), expanded))
.child(
div()
.text_style(TextStyle::Callout)
.font_weight(gpui::FontWeight::MEDIUM)
.text_color(theme.text)
.child(label.into()),
)
}
fn nav_row(
&self,
icon: Option<Icon>,
label: impl Into<SharedString>,
selected: bool,
fade: Fade,
) -> Div {
let theme = self.theme();
let tint = if selected {
theme.text
} else {
motion::hover_blend(&fade, theme.text_muted, theme.text)
};
let mut row = stack::row()
.w_full()
.px(px(8.0))
.py(px(6.0))
.rounded(px(Theme::control_radius()))
.text_style(TextStyle::Body)
.text_color(tint)
.cursor_pointer();
if selected {
row = row.bg(theme.card_selected_bg());
} else {
row = row.bg(motion::hover_blend(
&fade,
theme.ink(0.0),
theme.element_hover,
));
row.interactivity().on_hover(motion::hover_listener(fade));
}
row.when_some(icon, |row, icon| {
row.child(
crate::icons::icon(icon)
.size(px(16.0))
.flex_none()
.text_color(tint),
)
})
.child(div().min_w_0().flex_1().truncate().child(label.into()))
}
fn split_handle(&self, axis: gpui::Axis, style: SplitStyle) -> Div {
let theme = self.theme();
let line = match style {
SplitStyle::Line { dragging } => {
Some(if dragging { theme.caret } else { theme.border })
}
SplitStyle::Ghost => None,
};
let handle = div().flex_none().flex().items_center().justify_center();
match axis {
gpui::Axis::Horizontal => handle
.w(px(SPLIT_HANDLE_HIT))
.h_full()
.cursor_col_resize()
.when_some(line, |el, line| {
el.child(div().w(px(1.0)).h_full().bg(line))
}),
gpui::Axis::Vertical => handle
.h(px(SPLIT_HANDLE_HIT))
.w_full()
.cursor_row_resize()
.when_some(line, |el, line| {
el.child(div().h(px(1.0)).w_full().bg(line))
}),
}
}
fn tab_bar(&self) -> Div {
let theme = self.theme();
div()
.flex()
.flex_row()
.items_center()
.gap(px(2.0))
.border_b_1()
.border_color(theme.border)
}
fn tab(&self, label: impl Into<SharedString>, active: bool) -> Div {
let theme = self.theme();
div()
.relative()
.px(px(10.0))
.pb(px(7.0))
.pt(px(6.0))
.rounded_t(px(Theme::control_radius()))
.border_1()
.border_color(crate::widgets::RING_SLOT)
.text_style(TextStyle::Body)
.font_weight(if active {
gpui::FontWeight::MEDIUM
} else {
gpui::FontWeight::NORMAL
})
.text_color(if active { theme.text } else { theme.text_muted })
.cursor_pointer()
.child(label.into())
.when(active, |t| {
t.child(
div()
.absolute()
.bottom(px(-2.0))
.left(px(-1.0))
.right(px(-1.0))
.h(px(2.0))
.bg(theme.text),
)
})
}
}
impl Layout for Theme {}