Cargo.lock 🔗
@@ -11028,6 +11028,7 @@ dependencies = [
"ui",
"workspace",
"workspace-hack",
+ "zed_actions",
]
[[package]]
Finn Evers created
This PR continues the work on the new and revamped onboarding flow.
Release Notes:
- N/A
Cargo.lock | 1
crates/onboarding/Cargo.toml | 1
crates/onboarding/src/onboarding.rs | 36 +++
crates/onboarding/src/welcome.rs | 276 ++++++++++++++++++++++++++++
crates/ui/src/components/keybinding.rs | 2
5 files changed, 314 insertions(+), 2 deletions(-)
@@ -11028,6 +11028,7 @@ dependencies = [
"ui",
"workspace",
"workspace-hack",
+ "zed_actions",
]
[[package]]
@@ -26,3 +26,4 @@ theme.workspace = true
ui.workspace = true
workspace.workspace = true
workspace-hack.workspace = true
+zed_actions.workspace = true
@@ -1,3 +1,4 @@
+use crate::welcome::{ShowWelcome, WelcomePage};
use command_palette_hooks::CommandPaletteFilter;
use db::kvp::KEY_VALUE_STORE;
use feature_flags::{FeatureFlag, FeatureFlagViewExt as _};
@@ -20,6 +21,8 @@ use workspace::{
open_new, with_active_or_new_workspace,
};
+mod welcome;
+
pub struct OnBoardingFeatureFlag {}
impl FeatureFlag for OnBoardingFeatureFlag {
@@ -63,12 +66,43 @@ pub fn init(cx: &mut App) {
.detach();
});
});
+
+ cx.on_action(|_: &ShowWelcome, cx| {
+ with_active_or_new_workspace(cx, |workspace, window, cx| {
+ workspace
+ .with_local_workspace(window, cx, |workspace, window, cx| {
+ let existing = workspace
+ .active_pane()
+ .read(cx)
+ .items()
+ .find_map(|item| item.downcast::<WelcomePage>());
+
+ if let Some(existing) = existing {
+ workspace.activate_item(&existing, true, true, window, cx);
+ } else {
+ let settings_page = WelcomePage::new(cx);
+ workspace.add_item_to_active_pane(
+ Box::new(settings_page),
+ None,
+ true,
+ window,
+ cx,
+ )
+ }
+ })
+ .detach();
+ });
+ });
+
cx.observe_new::<Workspace>(|_, window, cx| {
let Some(window) = window else {
return;
};
- let onboarding_actions = [std::any::TypeId::of::<OpenOnboarding>()];
+ let onboarding_actions = [
+ std::any::TypeId::of::<OpenOnboarding>(),
+ std::any::TypeId::of::<ShowWelcome>(),
+ ];
CommandPaletteFilter::update_global(cx, |filter, _cx| {
filter.hide_action_types(&onboarding_actions);
@@ -0,0 +1,276 @@
+use gpui::{
+ Action, App, Context, Entity, EventEmitter, FocusHandle, Focusable, InteractiveElement,
+ NoAction, ParentElement, Render, Styled, Window, actions,
+};
+use ui::{ButtonLike, Divider, DividerColor, KeyBinding, Vector, VectorName, prelude::*};
+use workspace::{
+ NewFile, Open, Workspace, WorkspaceId,
+ item::{Item, ItemEvent},
+};
+use zed_actions::{Extensions, OpenSettings, command_palette};
+
+actions!(
+ zed,
+ [
+ /// Show the Zed welcome screen
+ ShowWelcome
+ ]
+);
+
+const CONTENT: (Section<4>, Section<3>) = (
+ Section {
+ title: "Get Started",
+ entries: [
+ SectionEntry {
+ icon: IconName::Plus,
+ title: "New File",
+ action: &NewFile,
+ },
+ SectionEntry {
+ icon: IconName::FolderOpen,
+ title: "Open Project",
+ action: &Open,
+ },
+ SectionEntry {
+ // TODO: use proper icon
+ icon: IconName::Download,
+ title: "Clone a Repo",
+ // TODO: use proper action
+ action: &NoAction,
+ },
+ SectionEntry {
+ icon: IconName::ListCollapse,
+ title: "Open Command Palette",
+ action: &command_palette::Toggle,
+ },
+ ],
+ },
+ Section {
+ title: "Configure",
+ entries: [
+ SectionEntry {
+ icon: IconName::Settings,
+ title: "Open Settings",
+ action: &OpenSettings,
+ },
+ SectionEntry {
+ icon: IconName::ZedAssistant,
+ title: "View AI Settings",
+ // TODO: use proper action
+ action: &NoAction,
+ },
+ SectionEntry {
+ icon: IconName::Blocks,
+ title: "Explore Extensions",
+ action: &Extensions {
+ category_filter: None,
+ id: None,
+ },
+ },
+ ],
+ },
+);
+
+struct Section<const COLS: usize> {
+ title: &'static str,
+ entries: [SectionEntry; COLS],
+}
+
+impl<const COLS: usize> Section<COLS> {
+ fn render(
+ self,
+ index_offset: usize,
+ focus: &FocusHandle,
+ window: &mut Window,
+ cx: &mut App,
+ ) -> impl IntoElement {
+ v_flex()
+ .min_w_full()
+ .gap_2()
+ .child(
+ h_flex()
+ .px_1()
+ .gap_4()
+ .child(
+ Label::new(self.title.to_ascii_uppercase())
+ .buffer_font(cx)
+ .color(Color::Muted)
+ .size(LabelSize::XSmall),
+ )
+ .child(Divider::horizontal().color(DividerColor::Border)),
+ )
+ .children(
+ self.entries
+ .iter()
+ .enumerate()
+ .map(|(index, entry)| entry.render(index_offset + index, &focus, window, cx)),
+ )
+ }
+}
+
+struct SectionEntry {
+ icon: IconName,
+ title: &'static str,
+ action: &'static dyn Action,
+}
+
+impl SectionEntry {
+ fn render(
+ &self,
+ button_index: usize,
+ focus: &FocusHandle,
+ window: &Window,
+ cx: &App,
+ ) -> impl IntoElement {
+ ButtonLike::new(("onboarding-button-id", button_index))
+ .full_width()
+ .child(
+ h_flex()
+ .w_full()
+ .gap_1()
+ .justify_between()
+ .child(
+ h_flex()
+ .gap_2()
+ .child(
+ Icon::new(self.icon)
+ .color(Color::Muted)
+ .size(IconSize::XSmall),
+ )
+ .child(Label::new(self.title)),
+ )
+ .children(KeyBinding::for_action_in(self.action, focus, window, cx)),
+ )
+ .on_click(|_, window, cx| window.dispatch_action(self.action.boxed_clone(), cx))
+ }
+}
+
+pub struct WelcomePage {
+ focus_handle: FocusHandle,
+}
+
+impl Render for WelcomePage {
+ fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
+ let (first_section, second_entries) = CONTENT;
+ let first_section_entries = first_section.entries.len();
+
+ h_flex()
+ .size_full()
+ .justify_center()
+ .overflow_hidden()
+ .bg(cx.theme().colors().editor_background)
+ .key_context("Welcome")
+ .track_focus(&self.focus_handle(cx))
+ .child(
+ h_flex()
+ .px_12()
+ .py_40()
+ .size_full()
+ .relative()
+ .max_w(px(1100.))
+ .child(
+ div()
+ .size_full()
+ .max_w_128()
+ .mx_auto()
+ .child(
+ h_flex()
+ .w_full()
+ .justify_center()
+ .gap_4()
+ .child(Vector::square(VectorName::ZedLogo, rems(2.)))
+ .child(
+ div().child(Headline::new("Welcome to Zed")).child(
+ Label::new("The editor for what's next")
+ .size(LabelSize::Small)
+ .color(Color::Muted)
+ .italic(),
+ ),
+ ),
+ )
+ .child(
+ v_flex()
+ .mt_12()
+ .gap_8()
+ .child(first_section.render(
+ Default::default(),
+ &self.focus_handle,
+ window,
+ cx,
+ ))
+ .child(second_entries.render(
+ first_section_entries,
+ &self.focus_handle,
+ window,
+ cx,
+ ))
+ .child(
+ h_flex()
+ .w_full()
+ .pt_4()
+ .justify_center()
+ // We call this a hack
+ .rounded_b_xs()
+ .border_t_1()
+ .border_color(DividerColor::Border.hsla(cx))
+ .border_dashed()
+ .child(
+ div().child(
+ Button::new("welcome-exit", "Return to Setup")
+ .full_width()
+ .label_size(LabelSize::XSmall),
+ ),
+ ),
+ ),
+ ),
+ ),
+ )
+ }
+}
+
+impl WelcomePage {
+ pub fn new(cx: &mut Context<Workspace>) -> Entity<Self> {
+ let this = cx.new(|cx| WelcomePage {
+ focus_handle: cx.focus_handle(),
+ });
+
+ this
+ }
+}
+
+impl EventEmitter<ItemEvent> for WelcomePage {}
+
+impl Focusable for WelcomePage {
+ fn focus_handle(&self, _: &App) -> gpui::FocusHandle {
+ self.focus_handle.clone()
+ }
+}
+
+impl Item for WelcomePage {
+ type Event = ItemEvent;
+
+ fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString {
+ "Welcome".into()
+ }
+
+ fn telemetry_event_text(&self) -> Option<&'static str> {
+ Some("New Welcome Page Opened")
+ }
+
+ fn show_toolbar(&self) -> bool {
+ false
+ }
+
+ fn clone_on_split(
+ &self,
+ _workspace_id: Option<WorkspaceId>,
+ _: &mut Window,
+ _: &mut Context<Self>,
+ ) -> Option<Entity<Self>> {
+ None
+ }
+
+ fn to_item_events(event: &Self::Event, mut f: impl FnMut(workspace::item::ItemEvent)) {
+ f(*event)
+ }
+}
@@ -44,7 +44,7 @@ impl KeyBinding {
pub fn for_action_in(
action: &dyn Action,
focus: &FocusHandle,
- window: &mut Window,
+ window: &Window,
cx: &App,
) -> Option<Self> {
let key_binding = window.highest_precedence_binding_for_action_in(action, focus)?;