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 // Workspace reference for Item trait
133 workspace: WeakEntity<Workspace>,
134}
135
136impl EventEmitter<ItemEvent> for OnboardingUI {}
137
138impl Focusable for OnboardingUI {
139 fn focus_handle(&self, _: &App) -> gpui::FocusHandle {
140 self.focus_handle.clone()
141 }
142}
143
144#[derive(Clone)]
145pub enum OnboardingEvent {
146 PageCompleted(OnboardingPage),
147}
148
149impl Render for OnboardingUI {
150 fn render(
151 &mut self,
152 window: &mut gpui::Window,
153 cx: &mut Context<Self>,
154 ) -> impl gpui::IntoElement {
155 h_flex()
156 .id("onboarding-ui")
157 .key_context("Onboarding")
158 .track_focus(&self.focus_handle)
159 .w(px(904.))
160 .h(px(500.))
161 .gap(px(48.))
162 .child(v_flex().h_full().w(px(256.)).child("nav"))
163 .child(self.render_active_page(window, cx))
164 }
165}
166
167impl OnboardingUI {
168 pub fn new(workspace: &Workspace, cx: &mut Context<Self>) -> Self {
169 Self {
170 focus_handle: cx.focus_handle(),
171 current_page: OnboardingPage::Basics,
172 current_focus: OnboardingFocus::Page,
173 completed_pages: [false; 4],
174 workspace: workspace.weak_handle(),
175 }
176 }
177
178 fn jump_to_page(
179 &mut self,
180 page: OnboardingPage,
181 _window: &mut gpui::Window,
182 cx: &mut Context<Self>,
183 ) {
184 self.current_page = page;
185 cx.notify();
186 }
187
188 fn next_page(&mut self, _window: &mut gpui::Window, cx: &mut Context<Self>) {
189 if let Some(next) = self.current_page.next() {
190 self.current_page = next;
191 cx.notify();
192 }
193 }
194
195 fn previous_page(&mut self, _window: &mut gpui::Window, cx: &mut Context<Self>) {
196 if let Some(prev) = self.current_page.previous() {
197 self.current_page = prev;
198 cx.notify();
199 }
200 }
201
202 fn toggle_focus(&mut self, _window: &mut gpui::Window, cx: &mut Context<Self>) {
203 self.current_focus = match self.current_focus {
204 OnboardingFocus::Navigation => OnboardingFocus::Page,
205 OnboardingFocus::Page => OnboardingFocus::Navigation,
206 };
207 cx.notify();
208 }
209
210 fn reset(&mut self, _window: &mut gpui::Window, cx: &mut Context<Self>) {
211 self.current_page = OnboardingPage::Basics;
212 self.current_focus = OnboardingFocus::Page;
213 self.completed_pages = [false; 4];
214 cx.notify();
215 }
216
217 fn mark_page_completed(
218 &mut self,
219 page: OnboardingPage,
220 _window: &mut gpui::Window,
221 cx: &mut Context<Self>,
222 ) {
223 let index = match page {
224 OnboardingPage::Basics => 0,
225 OnboardingPage::Editing => 1,
226 OnboardingPage::AiSetup => 2,
227 OnboardingPage::Welcome => 3,
228 };
229 self.completed_pages[index] = true;
230 cx.notify();
231 }
232
233 fn render_active_page(
234 &mut self,
235 _window: &mut gpui::Window,
236 _cx: &mut Context<Self>,
237 ) -> impl gpui::IntoElement {
238 match self.current_page {
239 OnboardingPage::Basics => self.render_basics_page(),
240 OnboardingPage::Editing => self.render_editing_page(),
241 OnboardingPage::AiSetup => self.render_ai_setup_page(),
242 OnboardingPage::Welcome => self.render_welcome_page(),
243 }
244 }
245
246 fn render_basics_page(&self) -> impl gpui::IntoElement {
247 v_flex().h_full().w_full().child("Basics Page")
248 }
249
250 fn render_editing_page(&self) -> impl gpui::IntoElement {
251 v_flex().h_full().w_full().child("Editing Page")
252 }
253
254 fn render_ai_setup_page(&self) -> impl gpui::IntoElement {
255 v_flex().h_full().w_full().child("AI Setup Page")
256 }
257
258 fn render_welcome_page(&self) -> impl gpui::IntoElement {
259 v_flex().h_full().w_full().child("Welcome Page")
260 }
261}
262
263impl Item for OnboardingUI {
264 type Event = ItemEvent;
265
266 fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString {
267 "Onboarding".into()
268 }
269
270 fn to_item_events(event: &Self::Event, mut f: impl FnMut(ItemEvent)) {
271 f(event.clone())
272 }
273
274 fn show_toolbar(&self) -> bool {
275 false
276 }
277
278 fn clone_on_split(
279 &self,
280 _workspace_id: Option<WorkspaceId>,
281 window: &mut Window,
282 cx: &mut Context<Self>,
283 ) -> Option<Entity<Self>> {
284 let weak_workspace = self.workspace.clone();
285 if let Some(workspace) = weak_workspace.upgrade() {
286 workspace.update(cx, |workspace, cx| {
287 Some(cx.new(|cx| OnboardingUI::new(workspace, cx)))
288 })
289 } else {
290 None
291 }
292 }
293}