199 lines
5.5 KiB
Rust
199 lines
5.5 KiB
Rust
use gpui::{
|
|
AnyElement, App, Axis, ElementId, FontWeight, IntoElement, ParentElement, Pixels, RenderOnce,
|
|
SharedString, Styled, Window, div, prelude::*,
|
|
};
|
|
use smallvec::SmallVec;
|
|
|
|
use super::SplitPane;
|
|
use crate::theme::{ActiveTheme, TextSize};
|
|
|
|
/// Page header and content: title, note, actions, then the body.
|
|
#[derive(IntoElement)]
|
|
pub struct Page {
|
|
title: SharedString,
|
|
subtitle: Option<SharedString>,
|
|
actions: SmallVec<[AnyElement; 2]>,
|
|
body: SmallVec<[AnyElement; 4]>,
|
|
}
|
|
|
|
impl Page {
|
|
pub fn new(title: impl Into<SharedString>) -> Self {
|
|
Self {
|
|
title: title.into(),
|
|
subtitle: None,
|
|
actions: SmallVec::new(),
|
|
body: SmallVec::new(),
|
|
}
|
|
}
|
|
|
|
pub fn subtitle(mut self, text: impl Into<SharedString>) -> Self {
|
|
self.subtitle = Some(text.into());
|
|
self
|
|
}
|
|
|
|
pub fn action(mut self, action: impl IntoElement) -> Self {
|
|
self.actions.push(action.into_any_element());
|
|
self
|
|
}
|
|
}
|
|
|
|
impl ParentElement for Page {
|
|
fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
|
|
self.body.extend(elements);
|
|
}
|
|
}
|
|
|
|
impl RenderOnce for Page {
|
|
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
|
|
let theme = cx.theme();
|
|
div()
|
|
.flex()
|
|
.flex_col()
|
|
.gap_8()
|
|
.px_10()
|
|
.py_8()
|
|
.child(
|
|
div()
|
|
.flex()
|
|
.items_end()
|
|
.justify_between()
|
|
.gap_6()
|
|
.pb_6()
|
|
.border_b_1()
|
|
.border_color(theme.colors.border)
|
|
.child(
|
|
div()
|
|
.flex()
|
|
.flex_col()
|
|
.gap_1()
|
|
.child(
|
|
div()
|
|
.text_size(theme.text_size(TextSize::Xxl))
|
|
.font_weight(FontWeight::SEMIBOLD)
|
|
.child(self.title),
|
|
)
|
|
.when_some(self.subtitle, |block, text| {
|
|
block.child(
|
|
div()
|
|
.text_size(theme.text_size(TextSize::Md))
|
|
.text_color(theme.colors.fg_muted)
|
|
.child(text),
|
|
)
|
|
}),
|
|
)
|
|
.child(div().flex().items_center().gap_2().children(self.actions)),
|
|
)
|
|
.child(div().flex().flex_col().gap_6().children(self.body))
|
|
}
|
|
}
|
|
|
|
/// Window frame: title bar, sidebar, content, status bar.
|
|
#[derive(IntoElement)]
|
|
pub struct AppShell {
|
|
title_bar: Option<AnyElement>,
|
|
sidebar: Option<AnyElement>,
|
|
status_bar: Option<AnyElement>,
|
|
body: SmallVec<[AnyElement; 2]>,
|
|
}
|
|
|
|
impl AppShell {
|
|
pub fn new() -> Self {
|
|
Self {
|
|
title_bar: None,
|
|
sidebar: None,
|
|
status_bar: None,
|
|
body: SmallVec::new(),
|
|
}
|
|
}
|
|
|
|
pub fn title_bar(mut self, bar: impl IntoElement) -> Self {
|
|
self.title_bar = Some(bar.into_any_element());
|
|
self
|
|
}
|
|
|
|
pub fn sidebar(mut self, sidebar: impl IntoElement) -> Self {
|
|
self.sidebar = Some(sidebar.into_any_element());
|
|
self
|
|
}
|
|
|
|
pub fn status_bar(mut self, bar: impl IntoElement) -> Self {
|
|
self.status_bar = Some(bar.into_any_element());
|
|
self
|
|
}
|
|
}
|
|
|
|
impl Default for AppShell {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
impl ParentElement for AppShell {
|
|
fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
|
|
self.body.extend(elements);
|
|
}
|
|
}
|
|
|
|
impl RenderOnce for AppShell {
|
|
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
|
|
let theme = cx.theme();
|
|
div()
|
|
.size_full()
|
|
.flex()
|
|
.flex_col()
|
|
.bg(theme.colors.bg)
|
|
.text_color(theme.colors.fg)
|
|
.children(self.title_bar)
|
|
.child(
|
|
div()
|
|
.flex_1()
|
|
.min_h_0()
|
|
.flex()
|
|
.children(self.sidebar)
|
|
.child(
|
|
div()
|
|
.flex_1()
|
|
.min_w_0()
|
|
.flex()
|
|
.flex_col()
|
|
.children(self.body),
|
|
),
|
|
)
|
|
.children(self.status_bar)
|
|
}
|
|
}
|
|
|
|
/// List on the left, the selected item on the right.
|
|
#[derive(IntoElement)]
|
|
pub struct MasterDetail {
|
|
id: ElementId,
|
|
master: AnyElement,
|
|
detail: AnyElement,
|
|
min: Pixels,
|
|
}
|
|
|
|
impl MasterDetail {
|
|
pub fn new(
|
|
id: impl Into<ElementId>,
|
|
master: impl IntoElement,
|
|
detail: impl IntoElement,
|
|
min: Pixels,
|
|
) -> Self {
|
|
Self {
|
|
id: id.into(),
|
|
master: master.into_any_element(),
|
|
detail: detail.into_any_element(),
|
|
min,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl RenderOnce for MasterDetail {
|
|
fn render(self, _: &mut Window, _: &mut App) -> impl IntoElement {
|
|
SplitPane::new(self.id, Axis::Horizontal, self.min)
|
|
.sizes(&[0.36, 0.64])
|
|
.pane(self.master)
|
|
.pane(self.detail)
|
|
}
|
|
}
|