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, Workspace, WorkspaceId,
  8    item::{Item, ItemEvent},
  9};
 10use zed_actions::{Extensions, OpenSettings, agent, 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                action: &agent::OpenSettings,
 59            },
 60            SectionEntry {
 61                icon: IconName::Blocks,
 62                title: "Explore Extensions",
 63                action: &Extensions {
 64                    category_filter: None,
 65                    id: None,
 66                },
 67            },
 68        ],
 69    },
 70);
 71
 72struct Section<const COLS: usize> {
 73    title: &'static str,
 74    entries: [SectionEntry; COLS],
 75}
 76
 77impl<const COLS: usize> Section<COLS> {
 78    fn render(
 79        self,
 80        index_offset: usize,
 81        focus: &FocusHandle,
 82        window: &mut Window,
 83        cx: &mut App,
 84    ) -> impl IntoElement {
 85        v_flex()
 86            .min_w_full()
 87            .gap_2()
 88            .child(
 89                h_flex()
 90                    .px_1()
 91                    .gap_4()
 92                    .child(
 93                        Label::new(self.title.to_ascii_uppercase())
 94                            .buffer_font(cx)
 95                            .color(Color::Muted)
 96                            .size(LabelSize::XSmall),
 97                    )
 98                    .child(Divider::horizontal().color(DividerColor::Border)),
 99            )
100            .children(
101                self.entries
102                    .iter()
103                    .enumerate()
104                    .map(|(index, entry)| entry.render(index_offset + index, &focus, window, cx)),
105            )
106    }
107}
108
109struct SectionEntry {
110    icon: IconName,
111    title: &'static str,
112    action: &'static dyn Action,
113}
114
115impl SectionEntry {
116    fn render(
117        &self,
118        button_index: usize,
119        focus: &FocusHandle,
120        window: &Window,
121        cx: &App,
122    ) -> impl IntoElement {
123        ButtonLike::new(("onboarding-button-id", button_index))
124            .full_width()
125            .child(
126                h_flex()
127                    .w_full()
128                    .gap_1()
129                    .justify_between()
130                    .child(
131                        h_flex()
132                            .gap_2()
133                            .child(
134                                Icon::new(self.icon)
135                                    .color(Color::Muted)
136                                    .size(IconSize::XSmall),
137                            )
138                            .child(Label::new(self.title)),
139                    )
140                    .children(KeyBinding::for_action_in(self.action, focus, window, cx)),
141            )
142            .on_click(|_, window, cx| window.dispatch_action(self.action.boxed_clone(), cx))
143    }
144}
145
146pub struct WelcomePage {
147    focus_handle: FocusHandle,
148}
149
150impl Render for WelcomePage {
151    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
152        let (first_section, second_entries) = CONTENT;
153        let first_section_entries = first_section.entries.len();
154
155        h_flex()
156            .size_full()
157            .justify_center()
158            .overflow_hidden()
159            .bg(cx.theme().colors().editor_background)
160            .key_context("Welcome")
161            .track_focus(&self.focus_handle(cx))
162            .child(
163                h_flex()
164                    .px_12()
165                    .py_40()
166                    .size_full()
167                    .relative()
168                    .max_w(px(1100.))
169                    .child(
170                        div()
171                            .size_full()
172                            .max_w_128()
173                            .mx_auto()
174                            .child(
175                                h_flex()
176                                    .w_full()
177                                    .justify_center()
178                                    .gap_4()
179                                    .child(Vector::square(VectorName::ZedLogo, rems(2.)))
180                                    .child(
181                                        div().child(Headline::new("Welcome to Zed")).child(
182                                            Label::new("The editor for what's next")
183                                                .size(LabelSize::Small)
184                                                .color(Color::Muted)
185                                                .italic(),
186                                        ),
187                                    ),
188                            )
189                            .child(
190                                v_flex()
191                                    .mt_12()
192                                    .gap_8()
193                                    .child(first_section.render(
194                                        Default::default(),
195                                        &self.focus_handle,
196                                        window,
197                                        cx,
198                                    ))
199                                    .child(second_entries.render(
200                                        first_section_entries,
201                                        &self.focus_handle,
202                                        window,
203                                        cx,
204                                    ))
205                                    .child(
206                                        h_flex()
207                                            .w_full()
208                                            .pt_4()
209                                            .justify_center()
210                                            // We call this a hack
211                                            .rounded_b_xs()
212                                            .border_t_1()
213                                            .border_color(DividerColor::Border.hsla(cx))
214                                            .border_dashed()
215                                            .child(
216                                                div().child(
217                                                    Button::new("welcome-exit", "Return to Setup")
218                                                        .full_width()
219                                                        .label_size(LabelSize::XSmall),
220                                                ),
221                                            ),
222                                    ),
223                            ),
224                    ),
225            )
226    }
227}
228
229impl WelcomePage {
230    pub fn new(window: &mut Window, cx: &mut Context<Workspace>) -> Entity<Self> {
231        cx.new(|cx| {
232            let focus_handle = cx.focus_handle();
233            cx.on_focus(&focus_handle, window, |_, _, cx| cx.notify())
234                .detach();
235
236            WelcomePage { focus_handle }
237        })
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}