welcome.rs

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