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, 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}