1#![allow(unused, dead_code)]
2use command_palette_hooks::CommandPaletteFilter;
3use feature_flags::FeatureFlagAppExt as _;
4use gpui::{Entity, EventEmitter, FocusHandle, Focusable, WeakEntity, actions, prelude::*};
5use settings_ui::SettingsUiFeatureFlag;
6use ui::prelude::*;
7use workspace::{
8 Workspace, WorkspaceId,
9 item::{Item, ItemEvent},
10};
11
12actions!(
13 onboarding,
14 [
15 ShowOnboarding,
16 JumpToBasics,
17 JumpToEditing,
18 JumpToAiSetup,
19 JumpToWelcome,
20 NextPage,
21 PreviousPage,
22 ToggleFocus,
23 ResetOnboarding,
24 ]
25);
26
27pub fn init(cx: &mut App) {
28 cx.observe_new(|workspace: &mut Workspace, _, _cx| {
29 workspace.register_action(|workspace, _: &ShowOnboarding, window, cx| {
30 let onboarding = cx.new(|cx| OnboardingUI::new(workspace, cx));
31 workspace.add_item_to_active_pane(Box::new(onboarding), None, true, window, cx);
32 });
33
34 workspace.register_action(|_workspace, _: &JumpToBasics, _window, _cx| {
35 // Jump to basics implementation
36 });
37
38 workspace.register_action(|_workspace, _: &JumpToEditing, _window, _cx| {
39 // Jump to editing implementation
40 });
41
42 workspace.register_action(|_workspace, _: &JumpToAiSetup, _window, _cx| {
43 // Jump to AI setup implementation
44 });
45
46 workspace.register_action(|_workspace, _: &JumpToWelcome, _window, _cx| {
47 // Jump to welcome implementation
48 });
49
50 workspace.register_action(|_workspace, _: &NextPage, _window, _cx| {
51 // Next page implementation
52 });
53
54 workspace.register_action(|_workspace, _: &PreviousPage, _window, _cx| {
55 // Previous page implementation
56 });
57
58 workspace.register_action(|_workspace, _: &ToggleFocus, _window, _cx| {
59 // Toggle focus implementation
60 });
61
62 workspace.register_action(|_workspace, _: &ResetOnboarding, _window, _cx| {
63 // Reset onboarding implementation
64 });
65 })
66 .detach();
67
68 feature_gate_onboarding_ui_actions(cx);
69}
70
71fn feature_gate_onboarding_ui_actions(cx: &mut App) {
72 const ONBOARDING_ACTION_NAMESPACE: &str = "onboarding";
73
74 CommandPaletteFilter::update_global(cx, |filter, _cx| {
75 filter.hide_namespace(ONBOARDING_ACTION_NAMESPACE);
76 });
77
78 cx.observe_flag::<SettingsUiFeatureFlag, _>({
79 move |is_enabled, cx| {
80 CommandPaletteFilter::update_global(cx, |filter, _cx| {
81 if is_enabled {
82 filter.show_namespace(ONBOARDING_ACTION_NAMESPACE);
83 } else {
84 filter.hide_namespace(ONBOARDING_ACTION_NAMESPACE);
85 }
86 });
87 }
88 })
89 .detach();
90}
91
92#[derive(Debug, Clone, Copy, PartialEq, Eq)]
93pub enum OnboardingPage {
94 Basics,
95 Editing,
96 AiSetup,
97 Welcome,
98}
99
100impl OnboardingPage {
101 fn next(&self) -> Option<Self> {
102 match self {
103 Self::Basics => Some(Self::Editing),
104 Self::Editing => Some(Self::AiSetup),
105 Self::AiSetup => Some(Self::Welcome),
106 Self::Welcome => None,
107 }
108 }
109
110 fn previous(&self) -> Option<Self> {
111 match self {
112 Self::Basics => None,
113 Self::Editing => Some(Self::Basics),
114 Self::AiSetup => Some(Self::Editing),
115 Self::Welcome => Some(Self::AiSetup),
116 }
117 }
118}
119
120#[derive(Debug, Clone, Copy, PartialEq, Eq)]
121pub enum OnboardingFocus {
122 Navigation,
123 Page,
124}
125
126pub struct OnboardingUI {
127 focus_handle: FocusHandle,
128 current_page: OnboardingPage,
129 current_focus: OnboardingFocus,
130 completed_pages: [bool; 4],
131
132 // Page entities
133 basics_page: Entity<BasicsPage>,
134 editing_page: Entity<EditingPage>,
135 ai_setup_page: Entity<AiSetupPage>,
136 welcome_page: Entity<WelcomePage>,
137
138 // Workspace reference for Item trait
139 workspace: WeakEntity<Workspace>,
140}
141
142impl EventEmitter<ItemEvent> for OnboardingUI {}
143
144impl Focusable for OnboardingUI {
145 fn focus_handle(&self, _: &App) -> gpui::FocusHandle {
146 self.focus_handle.clone()
147 }
148}
149
150pub struct BasicsPage {
151 focus_handle: FocusHandle,
152 parent: WeakEntity<OnboardingUI>,
153}
154
155pub struct EditingPage {
156 focus_handle: FocusHandle,
157 parent: WeakEntity<OnboardingUI>,
158}
159
160pub struct AiSetupPage {
161 focus_handle: FocusHandle,
162 parent: WeakEntity<OnboardingUI>,
163}
164
165pub struct WelcomePage {
166 focus_handle: FocusHandle,
167 parent: WeakEntity<OnboardingUI>,
168}
169
170// Event types for communication between pages and main UI
171#[derive(Clone)]
172pub enum OnboardingEvent {
173 PageCompleted(OnboardingPage),
174}
175
176// Implement EventEmitter for all entities
177impl EventEmitter<OnboardingEvent> for BasicsPage {}
178impl EventEmitter<OnboardingEvent> for EditingPage {}
179impl EventEmitter<OnboardingEvent> for AiSetupPage {}
180impl EventEmitter<OnboardingEvent> for WelcomePage {}
181
182impl Focusable for BasicsPage {
183 fn focus_handle(&self, _cx: &App) -> FocusHandle {
184 self.focus_handle.clone()
185 }
186}
187
188impl Focusable for EditingPage {
189 fn focus_handle(&self, _cx: &App) -> FocusHandle {
190 self.focus_handle.clone()
191 }
192}
193
194impl Focusable for AiSetupPage {
195 fn focus_handle(&self, _cx: &App) -> FocusHandle {
196 self.focus_handle.clone()
197 }
198}
199
200impl Focusable for WelcomePage {
201 fn focus_handle(&self, _cx: &App) -> FocusHandle {
202 self.focus_handle.clone()
203 }
204}
205
206// Placeholder Render implementations
207impl Render for OnboardingUI {
208 fn render(
209 &mut self,
210 _window: &mut gpui::Window,
211 _cx: &mut Context<Self>,
212 ) -> impl gpui::IntoElement {
213 h_flex()
214 .id("onboarding-ui")
215 .key_context("Onboarding")
216 .track_focus(&self.focus_handle)
217 .w(px(904.))
218 .h(px(500.))
219 .gap(px(48.))
220 .child(v_flex().h_full().w(px(256.)).child("nav"))
221 }
222}
223
224impl Render for BasicsPage {
225 fn render(
226 &mut self,
227 _window: &mut gpui::Window,
228 _cx: &mut Context<Self>,
229 ) -> impl gpui::IntoElement {
230 gpui::div()
231 }
232}
233
234impl Render for EditingPage {
235 fn render(
236 &mut self,
237 _window: &mut gpui::Window,
238 _cx: &mut Context<Self>,
239 ) -> impl gpui::IntoElement {
240 gpui::div()
241 }
242}
243
244impl Render for AiSetupPage {
245 fn render(
246 &mut self,
247 _window: &mut gpui::Window,
248 _cx: &mut Context<Self>,
249 ) -> impl gpui::IntoElement {
250 gpui::div()
251 }
252}
253
254impl Render for WelcomePage {
255 fn render(
256 &mut self,
257 _window: &mut gpui::Window,
258 _cx: &mut Context<Self>,
259 ) -> impl gpui::IntoElement {
260 gpui::div()
261 }
262}
263
264impl OnboardingUI {
265 pub fn new(workspace: &Workspace, cx: &mut Context<Self>) -> Self {
266 let parent_handle = cx.entity().downgrade();
267
268 let basics_page = cx.new(|cx| BasicsPage {
269 focus_handle: cx.focus_handle(),
270 parent: parent_handle.clone(),
271 });
272
273 let editing_page = cx.new(|cx| EditingPage {
274 focus_handle: cx.focus_handle(),
275 parent: parent_handle.clone(),
276 });
277
278 let ai_setup_page = cx.new(|cx| AiSetupPage {
279 focus_handle: cx.focus_handle(),
280 parent: parent_handle.clone(),
281 });
282
283 let welcome_page = cx.new(|cx| WelcomePage {
284 focus_handle: cx.focus_handle(),
285 parent: parent_handle.clone(),
286 });
287
288 Self {
289 focus_handle: cx.focus_handle(),
290 current_page: OnboardingPage::Basics,
291 current_focus: OnboardingFocus::Page,
292 completed_pages: [false; 4],
293 basics_page,
294 editing_page,
295 ai_setup_page,
296 welcome_page,
297 workspace: workspace.weak_handle(),
298 }
299 }
300
301 fn jump_to_page(
302 &mut self,
303 page: OnboardingPage,
304 _window: &mut gpui::Window,
305 cx: &mut Context<Self>,
306 ) {
307 self.current_page = page;
308 cx.notify();
309 }
310
311 fn next_page(&mut self, _window: &mut gpui::Window, cx: &mut Context<Self>) {
312 if let Some(next) = self.current_page.next() {
313 self.current_page = next;
314 cx.notify();
315 }
316 }
317
318 fn previous_page(&mut self, _window: &mut gpui::Window, cx: &mut Context<Self>) {
319 if let Some(prev) = self.current_page.previous() {
320 self.current_page = prev;
321 cx.notify();
322 }
323 }
324
325 fn toggle_focus(&mut self, _window: &mut gpui::Window, cx: &mut Context<Self>) {
326 self.current_focus = match self.current_focus {
327 OnboardingFocus::Navigation => OnboardingFocus::Page,
328 OnboardingFocus::Page => OnboardingFocus::Navigation,
329 };
330 cx.notify();
331 }
332
333 fn reset(&mut self, _window: &mut gpui::Window, cx: &mut Context<Self>) {
334 self.current_page = OnboardingPage::Basics;
335 self.current_focus = OnboardingFocus::Page;
336 self.completed_pages = [false; 4];
337 cx.notify();
338 }
339
340 fn mark_page_completed(
341 &mut self,
342 page: OnboardingPage,
343 _window: &mut gpui::Window,
344 cx: &mut Context<Self>,
345 ) {
346 let index = match page {
347 OnboardingPage::Basics => 0,
348 OnboardingPage::Editing => 1,
349 OnboardingPage::AiSetup => 2,
350 OnboardingPage::Welcome => 3,
351 };
352 self.completed_pages[index] = true;
353 cx.notify();
354 }
355}
356
357impl Item for OnboardingUI {
358 type Event = ItemEvent;
359
360 fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString {
361 "Onboarding".into()
362 }
363
364 fn to_item_events(event: &Self::Event, mut f: impl FnMut(ItemEvent)) {
365 f(event.clone())
366 }
367
368 fn show_toolbar(&self) -> bool {
369 false
370 }
371
372 fn clone_on_split(
373 &self,
374 _workspace_id: Option<WorkspaceId>,
375 window: &mut Window,
376 cx: &mut Context<Self>,
377 ) -> Option<Entity<Self>> {
378 let weak_workspace = self.workspace.clone();
379 if let Some(workspace) = weak_workspace.upgrade() {
380 workspace.update(cx, |workspace, cx| {
381 Some(cx.new(|cx| OnboardingUI::new(workspace, cx)))
382 })
383 } else {
384 None
385 }
386 }
387}