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