diff --git a/zed/assets/icons/comment-16.svg b/zed/assets/icons/comment-16.svg
new file mode 100644
index 0000000000..6316d3a4a9
--- /dev/null
+++ b/zed/assets/icons/comment-16.svg
@@ -0,0 +1,3 @@
+
diff --git a/zed/assets/icons/folder-tree-16.svg b/zed/assets/icons/folder-tree-16.svg
new file mode 100644
index 0000000000..f22773b159
--- /dev/null
+++ b/zed/assets/icons/folder-tree-16.svg
@@ -0,0 +1,3 @@
+
diff --git a/zed/assets/icons/user-16.svg b/zed/assets/icons/user-16.svg
new file mode 100644
index 0000000000..4ec153f538
--- /dev/null
+++ b/zed/assets/icons/user-16.svg
@@ -0,0 +1,3 @@
+
diff --git a/zed/assets/themes/_base.toml b/zed/assets/themes/_base.toml
index 2ad222c174..cf340282d5 100644
--- a/zed/assets/themes/_base.toml
+++ b/zed/assets/themes/_base.toml
@@ -2,22 +2,29 @@
background = "$surface.0"
[tab]
-background = "$surface.1"
-text = "$text_color.dull"
-border = { color = "#000000", width = 1.0 }
+text = "$text.2"
padding = { left = 10, right = 10 }
-icon_close = "#383839"
-icon_dirty = "#556de8"
-icon_conflict = "#e45349"
+icon_close = "$text.0"
+icon_dirty = "$status.info"
+icon_conflict = "$status.warn"
[active_tab]
extends = "$tab"
-background = "$surface.2"
-text = "$text_color.bright"
+background = "$surface.1"
+text = "$text.0"
+
+[sidebar]
+padding = { left = 10, right = 10 }
+
+[sidebar_icon]
+color = "$text.2"
+
+[active_sidebar_icon]
+color = "$text.0"
[selector]
-background = "$surface.3"
-text = "$text_color.bright"
+background = "$surface.2"
+text = "$text.0"
padding = { top = 6.0, bottom = 6.0, left = 6.0, right = 6.0 }
margin.top = 12.0
corner_radius = 6.0
@@ -35,13 +42,13 @@ extends = "$selector.item"
background = "#094771"
[editor]
-background = "$surface.2"
-gutter_background = "$surface.2"
-active_line_background = "$surface.3"
-line_number = "$text_color.dull"
-line_number_active = "$text_color.bright"
-text = "$text_color.normal"
+background = "$surface.1"
+gutter_background = "$surface.1"
+active_line_background = "$surface.2"
+line_number = "$text.2"
+line_number_active = "$text.0"
+text = "$text.1"
replicas = [
- { selection = "#264f78", cursor = "$text_color.bright" },
+ { selection = "#264f78", cursor = "$text.0" },
{ selection = "#504f31", cursor = "#fcf154" },
]
diff --git a/zed/assets/themes/dark.toml b/zed/assets/themes/dark.toml
index 2bbd37e934..b019057f65 100644
--- a/zed/assets/themes/dark.toml
+++ b/zed/assets/themes/dark.toml
@@ -1,15 +1,20 @@
extends = "_base"
[surface]
-0 = "#050101"
-1 = "#131415"
-2 = "#1c1d1e"
-3 = "#3a3b3c"
+0 = "#222324"
+1 = "#141516"
+2 = "#131415"
-[text_color]
-dull = "#5a5a5b"
-bright = "#ffffff"
-normal = "#d4d4d4"
+[text]
+0 = "#ffffff"
+1 = "#b3b3b3"
+2 = "#7b7d80"
+
+[status]
+good = "#4fac63"
+info = "#3c5dd4"
+warn = "#faca50"
+bad = "#b7372e"
[syntax]
keyword = { color = "#0086c0", weight = "bold" }
diff --git a/zed/src/lib.rs b/zed/src/lib.rs
index 492eaaf104..4cec473dfc 100644
--- a/zed/src/lib.rs
+++ b/zed/src/lib.rs
@@ -7,6 +7,7 @@ mod fuzzy;
pub mod language;
pub mod menus;
mod operation_queue;
+pub mod project_browser;
pub mod rpc;
pub mod settings;
mod sum_tree;
diff --git a/zed/src/project_browser.rs b/zed/src/project_browser.rs
new file mode 100644
index 0000000000..552eab851c
--- /dev/null
+++ b/zed/src/project_browser.rs
@@ -0,0 +1,19 @@
+use gpui::{elements::Empty, Element, Entity, View};
+
+pub struct ProjectBrowser;
+
+pub enum Event {}
+
+impl Entity for ProjectBrowser {
+ type Event = Event;
+}
+
+impl View for ProjectBrowser {
+ fn ui_name() -> &'static str {
+ "ProjectBrowser"
+ }
+
+ fn render(&self, _: &gpui::RenderContext<'_, Self>) -> gpui::ElementBox {
+ Empty::new().boxed()
+ }
+}
diff --git a/zed/src/theme.rs b/zed/src/theme.rs
index 52b87860c7..a8111d8b0a 100644
--- a/zed/src/theme.rs
+++ b/zed/src/theme.rs
@@ -33,6 +33,9 @@ pub struct Theme {
pub workspace: Workspace,
pub tab: Tab,
pub active_tab: Tab,
+ pub sidebar: ContainerStyle,
+ pub sidebar_icon: SidebarIcon,
+ pub active_sidebar_icon: SidebarIcon,
pub selector: Selector,
pub editor: Editor,
#[serde(deserialize_with = "deserialize_syntax_theme")]
@@ -72,6 +75,11 @@ pub struct Tab {
pub icon_conflict: Color,
}
+#[derive(Debug, Default, Deserialize)]
+pub struct SidebarIcon {
+ pub color: Color,
+}
+
#[derive(Debug, Default, Deserialize)]
pub struct Selector {
#[serde(flatten)]
diff --git a/zed/src/workspace.rs b/zed/src/workspace.rs
index 3169e9e58d..7c133944f1 100644
--- a/zed/src/workspace.rs
+++ b/zed/src/workspace.rs
@@ -1,10 +1,12 @@
pub mod pane;
pub mod pane_group;
+pub mod sidebar;
use crate::{
editor::{Buffer, Editor},
fs::Fs,
language::LanguageRegistry,
+ project_browser::ProjectBrowser,
rpc,
settings::Settings,
worktree::{File, Worktree},
@@ -25,6 +27,7 @@ use log::error;
pub use pane::*;
pub use pane_group::*;
use postage::watch;
+use sidebar::{Side, Sidebar};
use smol::prelude::*;
use std::{
collections::{hash_map::Entry, HashMap, HashSet},
@@ -46,6 +49,10 @@ pub fn init(cx: &mut MutableAppContext) {
cx.add_action("workspace:new_file", Workspace::open_new_file);
cx.add_action("workspace:share_worktree", Workspace::share_worktree);
cx.add_action("workspace:join_worktree", Workspace::join_worktree);
+ cx.add_action(
+ "workspace:toggle_sidebar_item",
+ Workspace::toggle_sidebar_item,
+ );
cx.add_bindings(vec![
Binding::new("cmd-s", "workspace:save", None),
Binding::new("cmd-alt-i", "workspace:debug_elements", None),
@@ -318,12 +325,6 @@ impl Clone for Box {
}
}
-#[derive(Debug)]
-pub struct State {
- pub modal: Option,
- pub center: PaneGroup,
-}
-
pub struct Workspace {
pub settings: watch::Receiver,
languages: Arc,
@@ -331,6 +332,8 @@ pub struct Workspace {
fs: Arc,
modal: Option,
center: PaneGroup,
+ left_sidebar: Sidebar,
+ right_sidebar: Sidebar,
panes: Vec>,
active_pane: ViewHandle,
worktrees: HashSet>,
@@ -350,6 +353,19 @@ impl Workspace {
});
cx.focus(&pane);
+ let mut left_sidebar = Sidebar::new(Side::Left);
+ left_sidebar.add_item(
+ "icons/folder-tree-16.svg",
+ cx.add_view(|_| ProjectBrowser).into(),
+ );
+
+ let mut right_sidebar = Sidebar::new(Side::Right);
+ right_sidebar.add_item(
+ "icons/comment-16.svg",
+ cx.add_view(|_| ProjectBrowser).into(),
+ );
+ right_sidebar.add_item("icons/user-16.svg", cx.add_view(|_| ProjectBrowser).into());
+
Workspace {
modal: None,
center: PaneGroup::new(pane.id()),
@@ -359,6 +375,8 @@ impl Workspace {
languages: app_state.languages.clone(),
rpc: app_state.rpc.clone(),
fs: app_state.fs.clone(),
+ left_sidebar,
+ right_sidebar,
worktrees: Default::default(),
items: Default::default(),
loading_items: Default::default(),
@@ -724,6 +742,19 @@ impl Workspace {
}
}
+ pub fn toggle_sidebar_item(
+ &mut self,
+ (side, item_ix): &(Side, usize),
+ cx: &mut ViewContext,
+ ) {
+ let sidebar = match side {
+ Side::Left => &mut self.left_sidebar,
+ Side::Right => &mut self.right_sidebar,
+ };
+ sidebar.toggle_item(*item_ix);
+ cx.notify();
+ }
+
pub fn debug_elements(&mut self, _: &(), cx: &mut ViewContext) {
match to_string_pretty(&cx.debug_elements()) {
Ok(json) => {
@@ -892,12 +923,47 @@ impl View for Workspace {
"Workspace"
}
- fn render(&self, _: &RenderContext) -> ElementBox {
+ fn render(&self, cx: &RenderContext) -> ElementBox {
let settings = self.settings.borrow();
Container::new(
- Stack::new()
- .with_child(self.center.render())
- .with_children(self.modal.as_ref().map(|m| ChildView::new(m.id()).boxed()))
+ Flex::column()
+ .with_child(
+ ConstrainedBox::new(Empty::new().boxed())
+ .with_height(cx.titlebar_height)
+ .named("titlebar"),
+ )
+ .with_child(
+ Expanded::new(
+ 1.0,
+ Stack::new()
+ .with_child({
+ let mut content = Flex::row();
+ content.add_child(self.left_sidebar.render(&settings, cx));
+ if let Some(panel) = self.left_sidebar.active_item() {
+ content.add_child(
+ ConstrainedBox::new(ChildView::new(panel.id()).boxed())
+ .with_width(200.0)
+ .named("left panel"),
+ );
+ }
+ content.add_child(Expanded::new(1.0, self.center.render()).boxed());
+ if let Some(panel) = self.right_sidebar.active_item() {
+ content.add_child(
+ ConstrainedBox::new(ChildView::new(panel.id()).boxed())
+ .with_width(200.0)
+ .named("right panel"),
+ );
+ }
+ content.add_child(self.right_sidebar.render(&settings, cx));
+ content.boxed()
+ })
+ .with_children(
+ self.modal.as_ref().map(|m| ChildView::new(m.id()).boxed()),
+ )
+ .boxed(),
+ )
+ .boxed(),
+ )
.boxed(),
)
.with_background_color(settings.theme.workspace.background)
diff --git a/zed/src/workspace/sidebar.rs b/zed/src/workspace/sidebar.rs
new file mode 100644
index 0000000000..7d7e1f7c6e
--- /dev/null
+++ b/zed/src/workspace/sidebar.rs
@@ -0,0 +1,93 @@
+use crate::Settings;
+use gpui::{
+ elements::{
+ Align, ConstrainedBox, Container, Flex, MouseEventHandler, ParentElement as _, Svg,
+ },
+ AnyViewHandle, AppContext, Element as _, ElementBox,
+};
+
+pub struct Sidebar {
+ side: Side,
+ items: Vec- ,
+ active_item_ix: Option,
+}
+
+#[derive(Clone, Copy)]
+pub enum Side {
+ Left,
+ Right,
+}
+
+struct Item {
+ icon_path: &'static str,
+ view: AnyViewHandle,
+}
+
+impl Sidebar {
+ pub fn new(side: Side) -> Self {
+ Self {
+ side,
+ items: Default::default(),
+ active_item_ix: None,
+ }
+ }
+
+ pub fn add_item(&mut self, icon_path: &'static str, view: AnyViewHandle) {
+ self.items.push(Item { icon_path, view });
+ }
+
+ pub fn toggle_item(&mut self, item_ix: usize) {
+ if self.active_item_ix == Some(item_ix) {
+ self.active_item_ix = None;
+ } else {
+ self.active_item_ix = Some(item_ix)
+ }
+ }
+
+ pub fn active_item(&self) -> Option<&AnyViewHandle> {
+ self.active_item_ix
+ .and_then(|ix| self.items.get(ix))
+ .map(|item| &item.view)
+ }
+
+ pub fn render(&self, settings: &Settings, cx: &AppContext) -> ElementBox {
+ let side = self.side;
+ let line_height = cx.font_cache().line_height(
+ cx.font_cache().default_font(settings.ui_font_family),
+ settings.ui_font_size,
+ );
+
+ Container::new(
+ Flex::column()
+ .with_children(self.items.iter().enumerate().map(|(item_ix, item)| {
+ let theme = if Some(item_ix) == self.active_item_ix {
+ &settings.theme.active_sidebar_icon
+ } else {
+ &settings.theme.sidebar_icon
+ };
+ enum SidebarButton {}
+ MouseEventHandler::new::(item.view.id(), cx, |_| {
+ ConstrainedBox::new(
+ Align::new(
+ ConstrainedBox::new(
+ Svg::new(item.icon_path).with_color(theme.color).boxed(),
+ )
+ .with_height(line_height)
+ .boxed(),
+ )
+ .boxed(),
+ )
+ .with_height(line_height + 16.0)
+ .boxed()
+ })
+ .on_click(move |cx| {
+ cx.dispatch_action("workspace:toggle_sidebar_item", (side, item_ix))
+ })
+ .boxed()
+ }))
+ .boxed(),
+ )
+ .with_style(&settings.theme.sidebar)
+ .boxed()
+ }
+}