onboarding_ui.rs

  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}