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