Added welcome experience sketch

Mikayla Maki created

Made toolbar hideable

Change summary

crates/db/src/db.rs               | 11 +++++++++
crates/welcome/src/welcome.rs     |  8 ++++-
crates/workspace/src/dock.rs      | 40 ++++++++++++++++++++------------
crates/workspace/src/item.rs      |  8 ++++++
crates/workspace/src/pane.rs      |  7 +++--
crates/workspace/src/toolbar.rs   | 12 +++++++++
crates/workspace/src/workspace.rs | 29 ++++++++++++-----------
crates/zed/src/zed.rs             | 37 +++++++++++++++++-------------
styles/src/styleTree/workspace.ts |  2 
9 files changed, 103 insertions(+), 51 deletions(-)

Detailed changes

crates/db/src/db.rs 🔗

@@ -4,6 +4,7 @@ pub mod query;
 // Re-export
 pub use anyhow;
 use anyhow::Context;
+use gpui::MutableAppContext;
 pub use indoc::indoc;
 pub use lazy_static;
 use parking_lot::{Mutex, RwLock};
@@ -17,6 +18,7 @@ use sqlez::domain::Migrator;
 use sqlez::thread_safe_connection::ThreadSafeConnection;
 use sqlez_macros::sql;
 use std::fs::create_dir_all;
+use std::future::Future;
 use std::path::{Path, PathBuf};
 use std::sync::atomic::{AtomicBool, Ordering};
 use std::time::{SystemTime, UNIX_EPOCH};
@@ -237,6 +239,15 @@ macro_rules! define_connection {
     };
 }
 
+pub fn write_and_log<F>(cx: &mut MutableAppContext, db_write: impl FnOnce() -> F + Send + 'static)
+where
+    F: Future<Output = anyhow::Result<()>> + Send,
+{
+    cx.background()
+        .spawn(async move { db_write().await.log_err() })
+        .detach()
+}
+
 #[cfg(test)]
 mod tests {
     use std::{fs, thread};

crates/welcome/src/welcome.rs 🔗

@@ -14,7 +14,7 @@ pub fn init(cx: &mut MutableAppContext) {
     })
 }
 
-struct WelcomePage {
+pub struct WelcomePage {
     _settings_subscription: Subscription,
 }
 
@@ -98,7 +98,7 @@ impl View for WelcomePage {
 }
 
 impl WelcomePage {
-    fn new(cx: &mut ViewContext<Self>) -> Self {
+    pub fn new(cx: &mut ViewContext<Self>) -> Self {
         let handle = cx.weak_handle();
 
         let settings_subscription = cx.observe_global::<Settings, _>(move |cx| {
@@ -163,4 +163,8 @@ impl Item for WelcomePage {
             )
             .boxed()
     }
+
+    fn show_toolbar(&self) -> bool {
+        false
+    }
 }

crates/workspace/src/dock.rs 🔗

@@ -39,20 +39,24 @@ impl_internal_actions!(dock, [MoveDock, AddDefaultItemToDock]);
 pub fn init(cx: &mut MutableAppContext) {
     cx.add_action(Dock::focus_dock);
     cx.add_action(Dock::hide_dock);
-    cx.add_action(Dock::move_dock);
+    cx.add_action(
+        |workspace: &mut Workspace, &MoveDock(dock_anchor), cx: &mut ViewContext<Workspace>| {
+            Dock::move_dock(workspace, dock_anchor, true, cx);
+        },
+    );
     cx.add_action(
         |workspace: &mut Workspace, _: &AnchorDockRight, cx: &mut ViewContext<Workspace>| {
-            Dock::move_dock(workspace, &MoveDock(DockAnchor::Right), cx)
+            Dock::move_dock(workspace, DockAnchor::Right, true, cx);
         },
     );
     cx.add_action(
         |workspace: &mut Workspace, _: &AnchorDockBottom, cx: &mut ViewContext<Workspace>| {
-            Dock::move_dock(workspace, &MoveDock(DockAnchor::Bottom), cx)
+            Dock::move_dock(workspace, DockAnchor::Bottom, true, cx)
         },
     );
     cx.add_action(
         |workspace: &mut Workspace, _: &ExpandDock, cx: &mut ViewContext<Workspace>| {
-            Dock::move_dock(workspace, &MoveDock(DockAnchor::Expanded), cx)
+            Dock::move_dock(workspace, DockAnchor::Expanded, true, cx)
         },
     );
     cx.add_action(
@@ -215,6 +219,7 @@ impl Dock {
     pub(crate) fn set_dock_position(
         workspace: &mut Workspace,
         new_position: DockPosition,
+        focus: bool,
         cx: &mut ViewContext<Workspace>,
     ) {
         workspace.dock.position = new_position;
@@ -235,19 +240,23 @@ impl Dock {
             let pane = workspace.dock.pane.clone();
             if pane.read(cx).items().next().is_none() {
                 if let Some(item_to_add) = (workspace.dock.default_item_factory)(workspace, cx) {
-                    Pane::add_item(workspace, &pane, item_to_add, true, true, None, cx);
+                    Pane::add_item(workspace, &pane, item_to_add, focus, focus, None, cx);
                 } else {
                     workspace.dock.position = workspace.dock.position.hide();
                 }
             } else {
-                cx.focus(pane);
+                if focus {
+                    cx.focus(pane);
+                }
             }
         } else if let Some(last_active_center_pane) = workspace
             .last_active_center_pane
             .as_ref()
             .and_then(|pane| pane.upgrade(cx))
         {
-            cx.focus(last_active_center_pane);
+            if focus {
+                cx.focus(last_active_center_pane);
+            }
         }
         cx.emit(crate::Event::DockAnchorChanged);
         workspace.serialize_workspace(cx);
@@ -255,11 +264,11 @@ impl Dock {
     }
 
     pub fn hide(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
-        Self::set_dock_position(workspace, workspace.dock.position.hide(), cx);
+        Self::set_dock_position(workspace, workspace.dock.position.hide(), true, cx);
     }
 
-    pub fn show(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
-        Self::set_dock_position(workspace, workspace.dock.position.show(), cx);
+    pub fn show(workspace: &mut Workspace, focus: bool, cx: &mut ViewContext<Workspace>) {
+        Self::set_dock_position(workspace, workspace.dock.position.show(), focus, cx);
     }
 
     pub fn hide_on_sidebar_shown(
@@ -275,19 +284,20 @@ impl Dock {
     }
 
     fn focus_dock(workspace: &mut Workspace, _: &FocusDock, cx: &mut ViewContext<Workspace>) {
-        Self::set_dock_position(workspace, workspace.dock.position.show(), cx);
+        Self::set_dock_position(workspace, workspace.dock.position.show(), true, cx);
     }
 
     fn hide_dock(workspace: &mut Workspace, _: &HideDock, cx: &mut ViewContext<Workspace>) {
-        Self::set_dock_position(workspace, workspace.dock.position.hide(), cx);
+        Self::set_dock_position(workspace, workspace.dock.position.hide(), true, cx);
     }
 
-    fn move_dock(
+    pub fn move_dock(
         workspace: &mut Workspace,
-        &MoveDock(new_anchor): &MoveDock,
+        new_anchor: DockAnchor,
+        focus: bool,
         cx: &mut ViewContext<Workspace>,
     ) {
-        Self::set_dock_position(workspace, DockPosition::Shown(new_anchor), cx);
+        Self::set_dock_position(workspace, DockPosition::Shown(new_anchor), focus, cx);
     }
 
     pub fn render(

crates/workspace/src/item.rs 🔗

@@ -151,6 +151,9 @@ pub trait Item: View {
             "deserialize() must be implemented if serialized_item_kind() returns Some(_)"
         )
     }
+    fn show_toolbar(&self) -> bool {
+        true
+    }
 }
 
 pub trait ItemHandle: 'static + fmt::Debug {
@@ -213,6 +216,7 @@ pub trait ItemHandle: 'static + fmt::Debug {
     fn breadcrumb_location(&self, cx: &AppContext) -> ToolbarItemLocation;
     fn breadcrumbs(&self, theme: &Theme, cx: &AppContext) -> Option<Vec<ElementBox>>;
     fn serialized_item_kind(&self) -> Option<&'static str>;
+    fn show_toolbar(&self, cx: &AppContext) -> bool;
 }
 
 pub trait WeakItemHandle {
@@ -591,6 +595,10 @@ impl<T: Item> ItemHandle for ViewHandle<T> {
     fn serialized_item_kind(&self) -> Option<&'static str> {
         T::serialized_item_kind()
     }
+
+    fn show_toolbar(&self, cx: &AppContext) -> bool {
+        self.read(cx).show_toolbar()
+    }
 }
 
 impl From<Box<dyn ItemHandle>> for AnyViewHandle {

crates/workspace/src/pane.rs 🔗

@@ -1485,11 +1485,12 @@ impl View for Pane {
                                     cx,
                                     {
                                         let toolbar = self.toolbar.clone();
+                                        let toolbar_hidden = toolbar.read(cx).hidden();
                                         move |_, cx| {
                                             Flex::column()
-                                                .with_child(
-                                                    ChildView::new(&toolbar, cx).expanded().boxed(),
-                                                )
+                                                .with_children((!toolbar_hidden).then(|| {
+                                                    ChildView::new(&toolbar, cx).expanded().boxed()
+                                                }))
                                                 .with_child(
                                                     ChildView::new(active_item, cx)
                                                         .flex(1., true)

crates/workspace/src/toolbar.rs 🔗

@@ -42,6 +42,7 @@ pub enum ToolbarItemLocation {
 
 pub struct Toolbar {
     active_pane_item: Option<Box<dyn ItemHandle>>,
+    hidden: bool,
     pane: WeakViewHandle<Pane>,
     items: Vec<(Box<dyn ToolbarItemViewHandle>, ToolbarItemLocation)>,
 }
@@ -211,6 +212,7 @@ impl Toolbar {
             active_pane_item: None,
             pane,
             items: Default::default(),
+            hidden: false,
         }
     }
 
@@ -243,6 +245,12 @@ impl Toolbar {
         cx: &mut ViewContext<Self>,
     ) {
         self.active_pane_item = pane_item.map(|item| item.boxed_clone());
+        self.hidden = self
+            .active_pane_item
+            .as_ref()
+            .map(|item| !item.show_toolbar(cx))
+            .unwrap_or(false);
+
         for (toolbar_item, current_location) in self.items.iter_mut() {
             let new_location = toolbar_item.set_active_pane_item(pane_item, cx);
             if new_location != *current_location {
@@ -257,6 +265,10 @@ impl Toolbar {
             .iter()
             .find_map(|(item, _)| item.to_any().downcast())
     }
+
+    pub fn hidden(&self) -> bool {
+        self.hidden
+    }
 }
 
 impl<T: ToolbarItemView> ToolbarItemViewHandle for ViewHandle<T> {

crates/workspace/src/workspace.rs 🔗

@@ -197,20 +197,12 @@ pub fn init(app_state: Arc<AppState>, cx: &mut MutableAppContext) {
             }
         }
     });
-    cx.add_global_action({
-        let app_state = Arc::downgrade(&app_state);
-        move |_: &Welcome, cx: &mut MutableAppContext| {
-            if let Some(app_state) = app_state.upgrade() {
-                open_new(&app_state, cx).detach();
-            }
-        }
-    });
 
     cx.add_global_action({
         let app_state = Arc::downgrade(&app_state);
         move |_: &NewWindow, cx: &mut MutableAppContext| {
             if let Some(app_state) = app_state.upgrade() {
-                open_new(&app_state, cx).detach();
+                open_new(&app_state, cx, |_, cx| cx.dispatch_action(NewFile)).detach();
             }
         }
     });
@@ -1514,7 +1506,7 @@ impl Workspace {
             self.active_item_path_changed(cx);
 
             if &pane == self.dock_pane() {
-                Dock::show(self, cx);
+                Dock::show(self, true, cx);
             } else {
                 self.last_active_center_pane = Some(pane.downgrade());
                 if self.dock.is_anchored_at(DockAnchor::Expanded) {
@@ -2527,7 +2519,12 @@ impl Workspace {
                     // the focus the dock generates start generating alternating
                     // focus due to the deferred execution each triggering each other
                     cx.after_window_update(move |workspace, cx| {
-                        Dock::set_dock_position(workspace, serialized_workspace.dock_position, cx);
+                        Dock::set_dock_position(
+                            workspace,
+                            serialized_workspace.dock_position,
+                            true,
+                            cx,
+                        );
                     });
 
                     cx.notify();
@@ -2859,14 +2856,18 @@ pub fn open_paths(
     })
 }
 
-pub fn open_new(app_state: &Arc<AppState>, cx: &mut MutableAppContext) -> Task<()> {
+pub fn open_new(
+    app_state: &Arc<AppState>,
+    cx: &mut MutableAppContext,
+    init: impl FnOnce(&mut Workspace, &mut ViewContext<Workspace>) + 'static,
+) -> Task<()> {
     let task = Workspace::new_local(Vec::new(), app_state.clone(), cx);
     cx.spawn(|mut cx| async move {
         let (workspace, opened_paths) = task.await;
 
-        workspace.update(&mut cx, |_, cx| {
+        workspace.update(&mut cx, |workspace, cx| {
             if opened_paths.is_empty() {
-                cx.dispatch_action(Welcome);
+                init(workspace, cx)
             }
         })
     })

crates/zed/src/zed.rs 🔗

@@ -35,7 +35,7 @@ use std::{borrow::Cow, env, path::Path, str, sync::Arc};
 use util::{channel::ReleaseChannel, paths, ResultExt, StaffMode};
 use uuid::Uuid;
 pub use workspace;
-use workspace::{sidebar::SidebarSide, AppState, Restart, Welcome, Workspace};
+use workspace::{dock::Dock, open_new, sidebar::SidebarSide, AppState, Restart, Workspace};
 
 pub const FIRST_OPEN: &str = "first_open";
 
@@ -256,23 +256,27 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut gpui::MutableAppContext) {
         },
     );
 
-    cx.add_global_action(|_: &WelcomeExperience, cx| {
-        if !matches!(KEY_VALUE_STORE.read_kvp(FIRST_OPEN), Ok(None)) {
-            return; //noop, in case someone fires this from the command palette
-        }
-
-        // Make a workspace, set it up with an open bottom dock and the welcome page
-
-        cx.dispatch_global_action(Welcome);
+    cx.add_global_action({
+        let app_state = app_state.clone();
+        move |_: &WelcomeExperience, cx| {
+            if !matches!(KEY_VALUE_STORE.read_kvp(FIRST_OPEN), Ok(None)) {
+                return; //noop, in case someone fires this from the command palette
+            }
 
-        cx.background()
-            .spawn(async move {
-                KEY_VALUE_STORE
-                    .write_kvp(FIRST_OPEN.to_string(), "false".to_string())
-                    .await
-                    .log_err();
+            open_new(&app_state, cx, |workspace, cx| {
+                workspace.toggle_sidebar(SidebarSide::Left, cx);
+                let welcome_page = cx.add_view(|cx| welcome::WelcomePage::new(cx));
+                workspace.add_item(Box::new(welcome_page.clone()), cx);
+                Dock::move_dock(workspace, settings::DockAnchor::Bottom, false, cx);
+                cx.focus(welcome_page);
+                cx.notify();
             })
             .detach();
+
+            db::write_and_log(cx, || {
+                KEY_VALUE_STORE.write_kvp(FIRST_OPEN.to_string(), "false".to_string())
+            });
+        }
     });
 
     activity_indicator::init(cx);
@@ -881,7 +885,8 @@ mod tests {
     #[gpui::test]
     async fn test_new_empty_workspace(cx: &mut TestAppContext) {
         let app_state = init(cx);
-        cx.update(|cx| open_new(&app_state, cx)).await;
+        cx.update(|cx| open_new(&app_state, cx, |_, cx| cx.dispatch_action(NewFile)))
+            .await;
 
         let window_id = *cx.window_ids().first().unwrap();
         let workspace = cx.root_view::<Workspace>(window_id).unwrap();

styles/src/styleTree/workspace.ts 🔗

@@ -248,7 +248,7 @@ export default function workspace(colorScheme: ColorScheme) {
         },
         dock: {
             initialSizeRight: 640,
-            initialSizeBottom: 480,
+            initialSizeBottom: 300,
             wash_color: withOpacity(background(colorScheme.highest), 0.5),
             panel: {
                 border: border(colorScheme.middle),