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