Mainline GPUI2 UI work (#3099)

This PR mainlines the current state of new GPUI2-based UI from the
`gpui2-ui` branch.

Included in this is a performance improvement to make use of the
`TextLayoutCache` when calling `layout` for `Text` elements.

Release Notes:

- N/A

---------

Co-authored-by: Nate Butler <iamnbutler@gmail.com>
Co-authored-by: Antonio Scandurra <me@as-cii.com>
This commit is contained in:
Marshall Bowers 2023-10-06 13:18:56 -04:00 committed by GitHub
parent c46137e40d
commit 456baaa112
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 371 additions and 19 deletions

View file

@ -9,17 +9,22 @@ mod editor_pane;
mod facepile;
mod icon_button;
mod keybinding;
mod language_selector;
mod list;
mod multi_buffer;
mod palette;
mod panel;
mod panes;
mod player_stack;
mod project_panel;
mod recent_projects;
mod status_bar;
mod tab;
mod tab_bar;
mod terminal;
mod theme_selector;
mod title_bar;
mod toast;
mod toolbar;
mod traffic_lights;
mod workspace;
@ -35,17 +40,22 @@ pub use editor_pane::*;
pub use facepile::*;
pub use icon_button::*;
pub use keybinding::*;
pub use language_selector::*;
pub use list::*;
pub use multi_buffer::*;
pub use palette::*;
pub use panel::*;
pub use panes::*;
pub use player_stack::*;
pub use project_panel::*;
pub use recent_projects::*;
pub use status_bar::*;
pub use tab::*;
pub use tab_bar::*;
pub use terminal::*;
pub use theme_selector::*;
pub use title_bar::*;
pub use toast::*;
pub use toolbar::*;
pub use traffic_lights::*;
pub use workspace::*;

View file

@ -0,0 +1,36 @@
use crate::prelude::*;
use crate::{OrderMethod, Palette, PaletteItem};
#[derive(Element)]
pub struct LanguageSelector {
scroll_state: ScrollState,
}
impl LanguageSelector {
pub fn new() -> Self {
Self {
scroll_state: ScrollState::default(),
}
}
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
div().child(
Palette::new(self.scroll_state.clone())
.items(vec![
PaletteItem::new("C"),
PaletteItem::new("C++"),
PaletteItem::new("CSS"),
PaletteItem::new("Elixir"),
PaletteItem::new("Elm"),
PaletteItem::new("ERB"),
PaletteItem::new("Rust (current)"),
PaletteItem::new("Scheme"),
PaletteItem::new("TOML"),
PaletteItem::new("TypeScript"),
])
.placeholder("Select a language...")
.empty_string("No matches")
.default_order(OrderMethod::Ascending),
)
}
}

View file

@ -0,0 +1,42 @@
use std::marker::PhantomData;
use crate::prelude::*;
use crate::{v_stack, Buffer, Icon, IconButton, Label, LabelSize};
#[derive(Element)]
pub struct MultiBuffer<V: 'static> {
view_type: PhantomData<V>,
buffers: Vec<Buffer>,
}
impl<V: 'static> MultiBuffer<V> {
pub fn new(buffers: Vec<Buffer>) -> Self {
Self {
view_type: PhantomData,
buffers,
}
}
fn render(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
let theme = theme(cx);
v_stack()
.w_full()
.h_full()
.flex_1()
.children(self.buffers.clone().into_iter().map(|buffer| {
v_stack()
.child(
div()
.flex()
.items_center()
.justify_between()
.p_4()
.fill(theme.lowest.base.default.background)
.child(Label::new("main.rs").size(LabelSize::Small))
.child(IconButton::new(Icon::ArrowUpRight)),
)
.child(buffer)
}))
}
}

View file

@ -93,19 +93,17 @@ impl<V: 'static> Palette<V> {
.fill(theme.lowest.base.hovered.background)
.active()
.fill(theme.lowest.base.pressed.background)
.child(
PaletteItem::new(item.label)
.keybinding(item.keybinding.clone()),
)
.child(item.clone())
})),
),
)
}
}
#[derive(Element)]
#[derive(Element, Clone)]
pub struct PaletteItem {
pub label: &'static str,
pub sublabel: Option<&'static str>,
pub keybinding: Option<Keybinding>,
}
@ -113,6 +111,7 @@ impl PaletteItem {
pub fn new(label: &'static str) -> Self {
Self {
label,
sublabel: None,
keybinding: None,
}
}
@ -122,6 +121,11 @@ impl PaletteItem {
self
}
pub fn sublabel<L: Into<Option<&'static str>>>(mut self, sublabel: L) -> Self {
self.sublabel = sublabel.into();
self
}
pub fn keybinding<K>(mut self, keybinding: K) -> Self
where
K: Into<Option<Keybinding>>,
@ -138,7 +142,11 @@ impl PaletteItem {
.flex_row()
.grow()
.justify_between()
.child(Label::new(self.label))
.child(
v_stack()
.child(Label::new(self.label))
.children(self.sublabel.map(|sublabel| Label::new(sublabel))),
)
.children(self.keybinding.clone())
}
}

View file

@ -0,0 +1,32 @@
use crate::prelude::*;
use crate::{OrderMethod, Palette, PaletteItem};
#[derive(Element)]
pub struct RecentProjects {
scroll_state: ScrollState,
}
impl RecentProjects {
pub fn new() -> Self {
Self {
scroll_state: ScrollState::default(),
}
}
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
div().child(
Palette::new(self.scroll_state.clone())
.items(vec![
PaletteItem::new("zed").sublabel("~/projects/zed"),
PaletteItem::new("saga").sublabel("~/projects/saga"),
PaletteItem::new("journal").sublabel("~/journal"),
PaletteItem::new("dotfiles").sublabel("~/dotfiles"),
PaletteItem::new("zed.dev").sublabel("~/projects/zed.dev"),
PaletteItem::new("laminar").sublabel("~/projects/laminar"),
])
.placeholder("Recent Projects...")
.empty_string("No matches")
.default_order(OrderMethod::Ascending),
)
}
}

View file

@ -0,0 +1,37 @@
use crate::prelude::*;
use crate::{OrderMethod, Palette, PaletteItem};
#[derive(Element)]
pub struct ThemeSelector {
scroll_state: ScrollState,
}
impl ThemeSelector {
pub fn new() -> Self {
Self {
scroll_state: ScrollState::default(),
}
}
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
div().child(
Palette::new(self.scroll_state.clone())
.items(vec![
PaletteItem::new("One Dark"),
PaletteItem::new("Rosé Pine"),
PaletteItem::new("Rosé Pine Moon"),
PaletteItem::new("Sandcastle"),
PaletteItem::new("Solarized Dark"),
PaletteItem::new("Summercamp"),
PaletteItem::new("Atelier Cave Light"),
PaletteItem::new("Atelier Dune Light"),
PaletteItem::new("Atelier Estuary Light"),
PaletteItem::new("Atelier Forest Light"),
PaletteItem::new("Atelier Heath Light"),
])
.placeholder("Select Theme...")
.empty_string("No matches")
.default_order(OrderMethod::Ascending),
)
}
}

View file

@ -0,0 +1,66 @@
use crate::prelude::*;
#[derive(Default, Debug, PartialEq, Eq, Clone, Copy)]
pub enum ToastOrigin {
#[default]
Bottom,
BottomRight,
}
#[derive(Default, Debug, PartialEq, Eq, Clone, Copy)]
pub enum ToastVariant {
#[default]
Toast,
Status,
}
/// A toast is a small, temporary window that appears to show a message to the user
/// or indicate a required action.
///
/// Toasts should not persist on the screen for more than a few seconds unless
/// they are actively showing the a process in progress.
///
/// Only one toast may be visible at a time.
#[derive(Element)]
pub struct Toast<V: 'static> {
origin: ToastOrigin,
children: HackyChildren<V>,
payload: HackyChildrenPayload,
}
impl<V: 'static> Toast<V> {
pub fn new(
origin: ToastOrigin,
children: HackyChildren<V>,
payload: HackyChildrenPayload,
) -> Self {
Self {
origin,
children,
payload,
}
}
fn render(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
let color = ThemeColor::new(cx);
let mut div = div();
if self.origin == ToastOrigin::Bottom {
div = div.right_1_2();
} else {
div = div.right_4();
}
div.absolute()
.bottom_4()
.flex()
.py_2()
.px_1p5()
.min_w_40()
.rounded_md()
.fill(color.elevated_surface)
.max_w_64()
.children_any((self.children)(cx, self.payload.as_ref()))
}
}

View file

@ -82,6 +82,7 @@ impl WorkspaceElement {
);
div()
.relative()
.size_full()
.flex()
.flex_col()
@ -169,5 +170,17 @@ impl WorkspaceElement {
),
)
.child(StatusBar::new())
// An example of a toast is below
// Currently because of stacking order this gets obscured by other elements
// .child(Toast::new(
// ToastOrigin::Bottom,
// |_, payload| {
// let theme = payload.downcast_ref::<Arc<Theme>>().unwrap();
// vec![Label::new("label").into_any()]
// },
// Box::new(theme.clone()),
// ))
}
}

View file

@ -60,6 +60,7 @@ pub enum Icon {
ChevronUp,
Close,
ExclamationTriangle,
ExternalLink,
File,
FileGeneric,
FileDoc,
@ -109,6 +110,7 @@ impl Icon {
Icon::ChevronUp => "icons/chevron_up.svg",
Icon::Close => "icons/x.svg",
Icon::ExclamationTriangle => "icons/warning.svg",
Icon::ExternalLink => "icons/external_link.svg",
Icon::File => "icons/file.svg",
Icon::FileGeneric => "icons/file_icons/file.svg",
Icon::FileDoc => "icons/file_icons/book.svg",

View file

@ -29,6 +29,26 @@ impl SystemColor {
}
}
#[derive(Clone, Copy)]
pub struct ThemeColor {
pub border: Hsla,
pub border_variant: Hsla,
/// The background color of an elevated surface, like a modal, tooltip or toast.
pub elevated_surface: Hsla,
}
impl ThemeColor {
pub fn new(cx: &WindowContext) -> Self {
let theme = theme(cx);
Self {
border: theme.lowest.base.default.border,
border_variant: theme.lowest.variant.default.border,
elevated_surface: theme.middle.base.default.background,
}
}
}
#[derive(Default, PartialEq, EnumIter, Clone, Copy)]
pub enum HighlightColor {
#[default]