Detailed changes
@@ -71,7 +71,7 @@ pub struct Window {
pub(crate) hovered_region_ids: Vec<MouseRegionId>,
pub(crate) clicked_region_ids: Vec<MouseRegionId>,
pub(crate) clicked_region: Option<(MouseRegionId, MouseButton)>,
- text_layout_cache: TextLayoutCache,
+ text_layout_cache: Arc<TextLayoutCache>,
refreshing: bool,
}
@@ -107,7 +107,7 @@ impl Window {
cursor_regions: Default::default(),
mouse_regions: Default::default(),
event_handlers: Default::default(),
- text_layout_cache: TextLayoutCache::new(cx.font_system.clone()),
+ text_layout_cache: Arc::new(TextLayoutCache::new(cx.font_system.clone())),
last_mouse_moved_event: None,
last_mouse_position: Vector2F::zero(),
pressed_buttons: Default::default(),
@@ -303,7 +303,7 @@ impl<'a> WindowContext<'a> {
self.window.refreshing
}
- pub fn text_layout_cache(&self) -> &TextLayoutCache {
+ pub fn text_layout_cache(&self) -> &Arc<TextLayoutCache> {
&self.window.text_layout_cache
}
@@ -5,7 +5,7 @@ use crate::{
use anyhow::Result;
use gpui::{
geometry::{vector::Vector2F, Size},
- text_layout::LineLayout,
+ text_layout::Line,
LayoutId,
};
use parking_lot::Mutex;
@@ -32,7 +32,7 @@ impl<V: 'static> Element<V> for Text {
_view: &mut V,
cx: &mut ViewContext<V>,
) -> Result<(LayoutId, Self::PaintState)> {
- let fonts = cx.platform().fonts();
+ let layout_cache = cx.text_layout_cache().clone();
let text_style = cx.text_style();
let line_height = cx.font_cache().line_height(text_style.font_size);
let text = self.text.clone();
@@ -41,14 +41,14 @@ impl<V: 'static> Element<V> for Text {
let layout_id = cx.add_measured_layout_node(Default::default(), {
let paint_state = paint_state.clone();
move |_params| {
- let line_layout = fonts.layout_line(
+ let line_layout = layout_cache.layout_str(
text.as_ref(),
text_style.font_size,
&[(text.len(), text_style.to_run())],
);
let size = Size {
- width: line_layout.width,
+ width: line_layout.width(),
height: line_height,
};
@@ -85,13 +85,9 @@ impl<V: 'static> Element<V> for Text {
line_height = paint_state.line_height;
}
- let text_style = cx.text_style();
- let line =
- gpui::text_layout::Line::new(line_layout, &[(self.text.len(), text_style.to_run())]);
-
// TODO: We haven't added visible bounds to the new element system yet, so this is a placeholder.
let visible_bounds = bounds;
- line.paint(bounds.origin(), visible_bounds, line_height, cx.legacy_cx);
+ line_layout.paint(bounds.origin(), visible_bounds, line_height, cx.legacy_cx);
}
}
@@ -104,6 +100,6 @@ impl<V: 'static> IntoElement<V> for Text {
}
pub struct TextLayout {
- line_layout: Arc<LineLayout>,
+ line_layout: Arc<Line>,
line_height: f32,
}
@@ -6,13 +6,17 @@ pub mod collab_panel;
pub mod context_menu;
pub mod facepile;
pub mod keybinding;
+pub mod language_selector;
+pub mod multi_buffer;
pub mod palette;
pub mod panel;
pub mod project_panel;
+pub mod recent_projects;
pub mod status_bar;
pub mod tab;
pub mod tab_bar;
pub mod terminal;
+pub mod theme_selector;
pub mod title_bar;
pub mod toolbar;
pub mod traffic_lights;
@@ -0,0 +1,16 @@
+use ui::prelude::*;
+use ui::LanguageSelector;
+
+use crate::story::Story;
+
+#[derive(Element, Default)]
+pub struct LanguageSelectorStory {}
+
+impl LanguageSelectorStory {
+ fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
+ Story::container(cx)
+ .child(Story::title_for::<_, LanguageSelector>(cx))
+ .child(Story::label(cx, "Default"))
+ .child(LanguageSelector::new())
+ }
+}
@@ -0,0 +1,24 @@
+use ui::prelude::*;
+use ui::{hello_world_rust_buffer_example, MultiBuffer};
+
+use crate::story::Story;
+
+#[derive(Element, Default)]
+pub struct MultiBufferStory {}
+
+impl MultiBufferStory {
+ fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
+ let theme = theme(cx);
+
+ Story::container(cx)
+ .child(Story::title_for::<_, MultiBuffer<V>>(cx))
+ .child(Story::label(cx, "Default"))
+ .child(MultiBuffer::new(vec![
+ hello_world_rust_buffer_example(&theme),
+ hello_world_rust_buffer_example(&theme),
+ hello_world_rust_buffer_example(&theme),
+ hello_world_rust_buffer_example(&theme),
+ hello_world_rust_buffer_example(&theme),
+ ]))
+ }
+}
@@ -0,0 +1,16 @@
+use ui::prelude::*;
+use ui::RecentProjects;
+
+use crate::story::Story;
+
+#[derive(Element, Default)]
+pub struct RecentProjectsStory {}
+
+impl RecentProjectsStory {
+ fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
+ Story::container(cx)
+ .child(Story::title_for::<_, RecentProjects>(cx))
+ .child(Story::label(cx, "Default"))
+ .child(RecentProjects::new())
+ }
+}
@@ -0,0 +1,16 @@
+use ui::prelude::*;
+use ui::ThemeSelector;
+
+use crate::story::Story;
+
+#[derive(Element, Default)]
+pub struct ThemeSelectorStory {}
+
+impl ThemeSelectorStory {
+ fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
+ Story::container(cx)
+ .child(Story::title_for::<_, ThemeSelector>(cx))
+ .child(Story::label(cx, "Default"))
+ .child(ThemeSelector::new())
+ }
+}
@@ -42,13 +42,17 @@ pub enum ComponentStory {
CollabPanel,
Facepile,
Keybinding,
+ LanguageSelector,
+ MultiBuffer,
Palette,
Panel,
ProjectPanel,
+ RecentProjects,
StatusBar,
Tab,
TabBar,
Terminal,
+ ThemeSelector,
TitleBar,
Toolbar,
TrafficLights,
@@ -69,15 +73,25 @@ impl ComponentStory {
Self::CollabPanel => components::collab_panel::CollabPanelStory::default().into_any(),
Self::Facepile => components::facepile::FacepileStory::default().into_any(),
Self::Keybinding => components::keybinding::KeybindingStory::default().into_any(),
+ Self::LanguageSelector => {
+ components::language_selector::LanguageSelectorStory::default().into_any()
+ }
+ Self::MultiBuffer => components::multi_buffer::MultiBufferStory::default().into_any(),
Self::Palette => components::palette::PaletteStory::default().into_any(),
Self::Panel => components::panel::PanelStory::default().into_any(),
Self::ProjectPanel => {
components::project_panel::ProjectPanelStory::default().into_any()
}
+ Self::RecentProjects => {
+ components::recent_projects::RecentProjectsStory::default().into_any()
+ }
Self::StatusBar => components::status_bar::StatusBarStory::default().into_any(),
Self::Tab => components::tab::TabStory::default().into_any(),
Self::TabBar => components::tab_bar::TabBarStory::default().into_any(),
Self::Terminal => components::terminal::TerminalStory::default().into_any(),
+ Self::ThemeSelector => {
+ components::theme_selector::ThemeSelectorStory::default().into_any()
+ }
Self::TitleBar => components::title_bar::TitleBarStory::default().into_any(),
Self::Toolbar => components::toolbar::ToolbarStory::default().into_any(),
Self::TrafficLights => {
@@ -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::*;
@@ -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),
+ )
+ }
+}
@@ -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)
+ }))
+ }
+}
@@ -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())
}
}
@@ -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),
+ )
+ }
+}
@@ -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),
+ )
+ }
+}
@@ -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()))
+ }
+}
@@ -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()),
+ // ))
}
}
@@ -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",
@@ -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]