1use gpui::{
2 Action, App, Context, Entity, EventEmitter, FocusHandle, Focusable, InteractiveElement,
3 NoAction, ParentElement, Render, Styled, Window, actions,
4};
5use ui::{ButtonLike, Divider, DividerColor, KeyBinding, Vector, VectorName, prelude::*};
6use workspace::{
7 NewFile, Open, Workspace, WorkspaceId,
8 item::{Item, ItemEvent},
9};
10use zed_actions::{Extensions, OpenSettings, command_palette};
11
12actions!(
13 zed,
14 [
15 /// Show the Zed welcome screen
16 ShowWelcome
17 ]
18);
19
20const CONTENT: (Section<4>, Section<3>) = (
21 Section {
22 title: "Get Started",
23 entries: [
24 SectionEntry {
25 icon: IconName::Plus,
26 title: "New File",
27 action: &NewFile,
28 },
29 SectionEntry {
30 icon: IconName::FolderOpen,
31 title: "Open Project",
32 action: &Open,
33 },
34 SectionEntry {
35 icon: IconName::CloudDownload,
36 title: "Clone a Repo",
37 // TODO: use proper action
38 action: &NoAction,
39 },
40 SectionEntry {
41 icon: IconName::ListCollapse,
42 title: "Open Command Palette",
43 action: &command_palette::Toggle,
44 },
45 ],
46 },
47 Section {
48 title: "Configure",
49 entries: [
50 SectionEntry {
51 icon: IconName::Settings,
52 title: "Open Settings",
53 action: &OpenSettings,
54 },
55 SectionEntry {
56 icon: IconName::ZedAssistant,
57 title: "View AI Settings",
58 // TODO: use proper action
59 action: &NoAction,
60 },
61 SectionEntry {
62 icon: IconName::Blocks,
63 title: "Explore Extensions",
64 action: &Extensions {
65 category_filter: None,
66 id: None,
67 },
68 },
69 ],
70 },
71);
72
73struct Section<const COLS: usize> {
74 title: &'static str,
75 entries: [SectionEntry; COLS],
76}
77
78impl<const COLS: usize> Section<COLS> {
79 fn render(
80 self,
81 index_offset: usize,
82 focus: &FocusHandle,
83 window: &mut Window,
84 cx: &mut App,
85 ) -> impl IntoElement {
86 v_flex()
87 .min_w_full()
88 .gap_2()
89 .child(
90 h_flex()
91 .px_1()
92 .gap_4()
93 .child(
94 Label::new(self.title.to_ascii_uppercase())
95 .buffer_font(cx)
96 .color(Color::Muted)
97 .size(LabelSize::XSmall),
98 )
99 .child(Divider::horizontal().color(DividerColor::Border)),
100 )
101 .children(
102 self.entries
103 .iter()
104 .enumerate()
105 .map(|(index, entry)| entry.render(index_offset + index, &focus, window, cx)),
106 )
107 }
108}
109
110struct SectionEntry {
111 icon: IconName,
112 title: &'static str,
113 action: &'static dyn Action,
114}
115
116impl SectionEntry {
117 fn render(
118 &self,
119 button_index: usize,
120 focus: &FocusHandle,
121 window: &Window,
122 cx: &App,
123 ) -> impl IntoElement {
124 ButtonLike::new(("onboarding-button-id", button_index))
125 .full_width()
126 .child(
127 h_flex()
128 .w_full()
129 .gap_1()
130 .justify_between()
131 .child(
132 h_flex()
133 .gap_2()
134 .child(
135 Icon::new(self.icon)
136 .color(Color::Muted)
137 .size(IconSize::XSmall),
138 )
139 .child(Label::new(self.title)),
140 )
141 .children(KeyBinding::for_action_in(self.action, focus, window, cx)),
142 )
143 .on_click(|_, window, cx| window.dispatch_action(self.action.boxed_clone(), cx))
144 }
145}
146
147pub struct WelcomePage {
148 focus_handle: FocusHandle,
149}
150
151impl Render for WelcomePage {
152 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
153 let (first_section, second_entries) = CONTENT;
154 let first_section_entries = first_section.entries.len();
155
156 h_flex()
157 .size_full()
158 .justify_center()
159 .overflow_hidden()
160 .bg(cx.theme().colors().editor_background)
161 .key_context("Welcome")
162 .track_focus(&self.focus_handle(cx))
163 .child(
164 h_flex()
165 .px_12()
166 .py_40()
167 .size_full()
168 .relative()
169 .max_w(px(1100.))
170 .child(
171 div()
172 .size_full()
173 .max_w_128()
174 .mx_auto()
175 .child(
176 h_flex()
177 .w_full()
178 .justify_center()
179 .gap_4()
180 .child(Vector::square(VectorName::ZedLogo, rems(2.)))
181 .child(
182 div().child(Headline::new("Welcome to Zed")).child(
183 Label::new("The editor for what's next")
184 .size(LabelSize::Small)
185 .color(Color::Muted)
186 .italic(),
187 ),
188 ),
189 )
190 .child(
191 v_flex()
192 .mt_12()
193 .gap_8()
194 .child(first_section.render(
195 Default::default(),
196 &self.focus_handle,
197 window,
198 cx,
199 ))
200 .child(second_entries.render(
201 first_section_entries,
202 &self.focus_handle,
203 window,
204 cx,
205 ))
206 .child(
207 h_flex()
208 .w_full()
209 .pt_4()
210 .justify_center()
211 // We call this a hack
212 .rounded_b_xs()
213 .border_t_1()
214 .border_color(DividerColor::Border.hsla(cx))
215 .border_dashed()
216 .child(
217 div().child(
218 Button::new("welcome-exit", "Return to Setup")
219 .full_width()
220 .label_size(LabelSize::XSmall),
221 ),
222 ),
223 ),
224 ),
225 ),
226 )
227 }
228}
229
230impl WelcomePage {
231 pub fn new(cx: &mut Context<Workspace>) -> Entity<Self> {
232 let this = cx.new(|cx| WelcomePage {
233 focus_handle: cx.focus_handle(),
234 });
235
236 this
237 }
238}
239
240impl EventEmitter<ItemEvent> for WelcomePage {}
241
242impl Focusable for WelcomePage {
243 fn focus_handle(&self, _: &App) -> gpui::FocusHandle {
244 self.focus_handle.clone()
245 }
246}
247
248impl Item for WelcomePage {
249 type Event = ItemEvent;
250
251 fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString {
252 "Welcome".into()
253 }
254
255 fn telemetry_event_text(&self) -> Option<&'static str> {
256 Some("New Welcome Page Opened")
257 }
258
259 fn show_toolbar(&self) -> bool {
260 false
261 }
262
263 fn clone_on_split(
264 &self,
265 _workspace_id: Option<WorkspaceId>,
266 _: &mut Window,
267 _: &mut Context<Self>,
268 ) -> Option<Entity<Self>> {
269 None
270 }
271
272 fn to_item_events(event: &Self::Event, mut f: impl FnMut(workspace::item::ItemEvent)) {
273 f(*event)
274 }
275}