241 lines
8.7 KiB
Rust
241 lines
8.7 KiB
Rust
use ely_gpui_component::{
|
|
buttons::{ButtonVariant, IconButton},
|
|
forms::{Choice, TextInput},
|
|
layout::Sidebar,
|
|
navigation::{
|
|
Anchor, BackForwardNavigation, GoToLine, NavGroup, NavItem, NavigationMenu, Sections,
|
|
TableOfContents,
|
|
},
|
|
primitives::IconName,
|
|
theme::{ActiveTheme, Radius},
|
|
typography::{Heading, Paragraph},
|
|
};
|
|
use gpui::{
|
|
App, Entity, InteractiveElement, IntoElement, ParentElement, ScrollHandle, SharedString,
|
|
StatefulInteractiveElement, Styled, Window, div, px,
|
|
};
|
|
|
|
use crate::{
|
|
probe::probe,
|
|
ui::{keep, section, set, specimen},
|
|
};
|
|
|
|
pub fn sidebar(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
|
|
let here = keep("nav-here", || SharedString::from("inbox"), window, cx);
|
|
let folded = keep("nav-folded", || false, window, cx);
|
|
let open = keep("nav-open", || true, window, cx);
|
|
let (now, fold, shown) = (here.read(cx).clone(), *folded.read(cx), *open.read(cx));
|
|
let item = |id: &'static str, icon: IconName, label: &'static str| {
|
|
let here = here.clone();
|
|
NavItem::new(id, icon, label)
|
|
.active(now.as_ref() == id)
|
|
.folded(fold)
|
|
.on_click(move |_, cx| set(&here, id.into(), cx))
|
|
};
|
|
let toggle = folded.clone();
|
|
let groups = open.clone();
|
|
section(
|
|
"Sidebar Navigation / NavItem / NavGroup",
|
|
"Places in a side column. Groups fold; folded to a rail, items keep their icons and name themselves on hover.",
|
|
cx,
|
|
)
|
|
.child(probe(
|
|
"nav-sidebar",
|
|
div()
|
|
.flex()
|
|
.w(px(560.0))
|
|
.h(px(300.0))
|
|
.border_1()
|
|
.border_color(cx.theme().colors.border)
|
|
.rounded(cx.theme().radius(Radius::Lg))
|
|
.overflow_hidden()
|
|
.child(
|
|
Sidebar::new("nav-sidebar", fold).child(
|
|
div()
|
|
.flex()
|
|
.flex_col()
|
|
.gap_3()
|
|
.p_2()
|
|
.child(item("inbox", IconName::Inbox, "Inbox").count("12"))
|
|
.child(item("drafts", IconName::FileText, "Drafts"))
|
|
.child(
|
|
NavGroup::new("nav-projects", "Projects", shown)
|
|
.folded(fold)
|
|
.on_toggle(move |next, _, cx| set(&groups, next, cx))
|
|
.child(item("ely", IconName::Box, "Ely"))
|
|
.child(item("website", IconName::Globe, "Website"))
|
|
.child(item("research", IconName::BookOpen, "Research")),
|
|
),
|
|
),
|
|
)
|
|
.child(
|
|
div().p_3().child(
|
|
IconButton::new("nav-fold", IconName::PanelLeft)
|
|
.variant(ButtonVariant::Ghost)
|
|
.tooltip(if fold { "Unfold" } else { "Fold" })
|
|
.on_click(move |_, _, cx| set(&toggle, !fold, cx)),
|
|
),
|
|
),
|
|
))
|
|
}
|
|
|
|
pub fn menu(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
|
|
let went = keep("menu-went", || None::<SharedString>, window, cx);
|
|
let caption = went
|
|
.read(cx)
|
|
.clone()
|
|
.map_or("pick a link".to_string(), |value| {
|
|
format!("went to {value}")
|
|
});
|
|
section(
|
|
"NavigationMenu",
|
|
"A click opens an entry's panel; while one is open, hovering another switches. Arrows walk it, Escape closes.",
|
|
cx,
|
|
)
|
|
.child(specimen(
|
|
caption,
|
|
probe(
|
|
"nav-menu",
|
|
NavigationMenu::new("nav-menu")
|
|
.entry(
|
|
"Product",
|
|
[
|
|
Choice::new("tour", "Tour").icon(IconName::Compass).note("What it does, in five minutes"),
|
|
Choice::new("pricing", "Pricing").icon(IconName::CreditCard).note("Plans for one to a thousand"),
|
|
Choice::new("changes", "Changelog").icon(IconName::History).note("Every release, dated"),
|
|
Choice::new("status", "Status").icon(IconName::Activity).note("Uptime, live"),
|
|
],
|
|
)
|
|
.entry(
|
|
"Docs",
|
|
[
|
|
Choice::new("guide", "Guide").icon(IconName::BookOpen).note("From install to ship"),
|
|
Choice::new("api", "API").icon(IconName::Code).note("Every type and call"),
|
|
Choice::new("examples", "Examples").icon(IconName::Layers).note("Small apps to copy"),
|
|
Choice::new("faq", "Questions").icon(IconName::CircleHelp).note("Asked and answered"),
|
|
],
|
|
)
|
|
.on_select(move |value, _, cx| set(&went, Some(value.clone()), cx)),
|
|
),
|
|
cx,
|
|
))
|
|
}
|
|
|
|
pub fn history(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
|
|
let at = keep("history-at", || 3usize, window, cx);
|
|
let now = *at.read(cx);
|
|
let places = [
|
|
("home", "Home", IconName::House),
|
|
("search", "Search: gpui", IconName::Search),
|
|
("repo", "zed-industries/zed", IconName::GitBranch),
|
|
("file", "crates/gpui/src/window.rs", IconName::FileText),
|
|
];
|
|
let label = places[now].1;
|
|
section(
|
|
"BackForwardNavigation / History",
|
|
"Back and forward step one place; the clock lists every place, newest first.",
|
|
cx,
|
|
)
|
|
.child(specimen(
|
|
format!("at {label}"),
|
|
probe(
|
|
"history",
|
|
BackForwardNavigation::new(
|
|
"history",
|
|
places.map(|(value, label, icon)| Choice::new(value, label).icon(icon)),
|
|
now,
|
|
)
|
|
.on_go(move |to, _, cx| set(&at, to, cx)),
|
|
),
|
|
cx,
|
|
))
|
|
}
|
|
|
|
const CHAPTERS: [(&str, &str, usize); 5] = [
|
|
("start", "Getting started", 0),
|
|
("install", "Install", 1),
|
|
("first", "A first window", 1),
|
|
("theme", "Theme and tokens", 0),
|
|
("motion", "Motion", 0),
|
|
];
|
|
|
|
pub fn contents(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
|
|
let scroll = keep("toc-scroll", ScrollHandle::new, window, cx)
|
|
.read(cx)
|
|
.clone();
|
|
let sections: Entity<Sections> = {
|
|
let scroll = scroll.clone();
|
|
keep("toc-sections", move || Sections::new(scroll), window, cx)
|
|
};
|
|
let body = "Each section is long enough to scroll past. The line in the table follows the section in view; a click brings a section to the top.";
|
|
let document = CHAPTERS.iter().map(|(value, title, depth)| {
|
|
Anchor::new(§ions, *value).child(
|
|
div()
|
|
.flex()
|
|
.flex_col()
|
|
.gap_2()
|
|
.pb_8()
|
|
.child(if *depth == 0 {
|
|
Heading::h4(*title)
|
|
} else {
|
|
Heading::h5(*title)
|
|
})
|
|
.child(Paragraph::new(body))
|
|
.child(Paragraph::new(body)),
|
|
)
|
|
});
|
|
let toc = CHAPTERS.iter().fold(
|
|
TableOfContents::new("toc", §ions),
|
|
|toc, (value, title, depth)| toc.entry(*value, *title, *depth),
|
|
);
|
|
section(
|
|
"Anchor / TableOfContents",
|
|
"Anchors mark where sections start. The table marks the one in view and brings any to the top.",
|
|
cx,
|
|
)
|
|
.child(
|
|
div()
|
|
.flex()
|
|
.items_start()
|
|
.gap_6()
|
|
.child(probe("toc", div().w(px(200.0)).child(toc)))
|
|
.child(
|
|
div()
|
|
.id("toc-document")
|
|
.w(px(420.0))
|
|
.h(px(260.0))
|
|
.overflow_y_scroll()
|
|
.track_scroll(&scroll)
|
|
.children(document),
|
|
),
|
|
)
|
|
}
|
|
|
|
pub fn go_to_line(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
|
|
let state: Entity<TextInput> = window.use_keyed_state("goto-line", cx, |window, cx| {
|
|
TextInput::new(window, cx).placeholder("Line or line:column")
|
|
});
|
|
let went = keep("goto-went", || None::<(usize, Option<usize>)>, window, cx);
|
|
let caption = match *went.read(cx) {
|
|
None => "Enter jumps".to_string(),
|
|
Some((line, None)) => format!("jumped to line {line}"),
|
|
Some((line, Some(column))) => format!("jumped to {line}:{column}"),
|
|
};
|
|
section(
|
|
"JumpTo / GoToLine",
|
|
"Line, or line and column. The note says where you will land, or why you cannot.",
|
|
cx,
|
|
)
|
|
.child(specimen(
|
|
caption,
|
|
probe(
|
|
"goto",
|
|
div().w(px(260.0)).child(
|
|
GoToLine::new("goto", &state, 240)
|
|
.on_jump(move |line, column, _, cx| set(&went, Some((line, column)), cx)),
|
|
),
|
|
),
|
|
cx,
|
|
))
|
|
}
|