From 6090be3d9c64b229028f70c0cf73cdb335ebd066 Mon Sep 17 00:00:00 2001 From: galister <22305755+galister@users.noreply.github.com> Date: Fri, 3 Jul 2026 02:12:40 +0900 Subject: [PATCH] dashboard welcome mode --- dash-frontend/src/frontend.rs | 13 +++- uidev/src/testbed/testbed_dashboard.rs | 1 + wayvr/src/backend/input.rs | 14 +++++ wayvr/src/config.rs | 6 +- wayvr/src/overlays/dashboard.rs | 85 ++++++++++++++++++++++++-- wlx-common/src/config.rs | 3 + 6 files changed, 114 insertions(+), 8 deletions(-) diff --git a/dash-frontend/src/frontend.rs b/dash-frontend/src/frontend.rs index 4de9329c..f1ddf3b5 100644 --- a/dash-frontend/src/frontend.rs +++ b/dash-frontend/src/frontend.rs @@ -29,8 +29,8 @@ use wlx_common::{ use crate::{ assets, tab::{ - Tab, TabType, apps::TabApps, games::TabGames, home::TabHome, monado::TabMonado, settings::TabSettings, - welcome::TabWelcome, + apps::TabApps, games::TabGames, home::TabHome, monado::TabMonado, settings::TabSettings, welcome::TabWelcome, Tab, + TabType, }, util::{ popup_manager::{MountPopupOnceParams, PopupManager, PopupManagerParams}, @@ -91,6 +91,7 @@ pub struct FrontendUpdateResult { pub struct InitParams<'a, T> { pub interface: BoxDashInterface, pub lang_provider: &'a WayVRLangProvider, + pub show_welcome: bool, pub has_monado: bool, pub theme: Rc, } @@ -156,7 +157,13 @@ impl Frontend { let toast_manager = ToastManager::new(); let tasks = FrontendTasks::new(); - tasks.push(FrontendTask::SetTab(TabType::Home)); + + let init_tab = if params.show_welcome { + TabType::Welcome + } else { + TabType::Home + }; + tasks.push(FrontendTask::SetTab(init_tab)); let id_label_time = state.get_widget_id("label_time")?; let id_rect_content = state.get_widget_id("rect_content")?; diff --git a/uidev/src/testbed/testbed_dashboard.rs b/uidev/src/testbed/testbed_dashboard.rs index c6aaba5a..0a3b2432 100644 --- a/uidev/src/testbed/testbed_dashboard.rs +++ b/uidev/src/testbed/testbed_dashboard.rs @@ -16,6 +16,7 @@ impl TestbedDashboard { let frontend = frontend::Frontend::new(frontend::InitParams { interface: Box::new(interface), + show_welcome: false, has_monado: true, lang_provider: &lang_provider, theme: Rc::new(WguiTheme::default()), diff --git a/wayvr/src/backend/input.rs b/wayvr/src/backend/input.rs index 6fb658f8..f45c4b5b 100644 --- a/wayvr/src/backend/input.rs +++ b/wayvr/src/backend/input.rs @@ -743,6 +743,20 @@ fn start_grab( grab_anchor, }); + app.tasks.enqueue(TaskType::Overlay(OverlayTask::Modify( + OverlaySelector::Id(id), + Box::new({ + let pos = state.positioning; + let name = name.clone(); + move |app, o| { + let _ = o + .backend + .notify(app, OverlayEventData::OverlayGrabbed { name, pos, editing }) + .inspect_err(|e| log::warn!("Error during Notify OverlayGrabbed: {e:?}")); + } + }), + ))); + // Show anchor app.tasks.enqueue(TaskType::Overlay(OverlayTask::Modify( OverlaySelector::Name(ANCHOR_NAME.clone()), diff --git a/wayvr/src/config.rs b/wayvr/src/config.rs index 0ce51c4b..5229906b 100644 --- a/wayvr/src/config.rs +++ b/wayvr/src/config.rs @@ -10,7 +10,7 @@ use wlx_common::{ astr_containers::AStrMap, config::{ AltModifier, CaptureMethod, ChromaKeyParams, GeneralConfig, HandsfreePointer, - SerializedWindowSet, SerializedWindowStates, + InputEmulationMethod, SerializedWindowSet, SerializedWindowStates, }, config_io, locale::Language, @@ -182,6 +182,8 @@ pub struct AutoSettings { pub handsfree_pointer: HandsfreePointer, pub language: Option, pub chroma_key_params: ChromaKeyParams, + pub input_emulation_method: InputEmulationMethod, + pub tutorial_graduated: bool, } fn get_settings_path() -> PathBuf { @@ -242,6 +244,8 @@ pub fn save_settings(config: &GeneralConfig) -> anyhow::Result<()> { handsfree_pointer: config.handsfree_pointer, language: config.language, chroma_key_params: config.chroma_key_params.clone(), + input_emulation_method: config.input_emulation_method, + tutorial_graduated: config.tutorial_graduated, }; let json = serde_json::to_string_pretty(&conf).unwrap(); // want panic diff --git a/wayvr/src/overlays/dashboard.rs b/wayvr/src/overlays/dashboard.rs index d1a3f038..873eead1 100644 --- a/wayvr/src/overlays/dashboard.rs +++ b/wayvr/src/overlays/dashboard.rs @@ -1,7 +1,11 @@ -use std::sync::atomic::Ordering; +use std::{ + ops::Add, + sync::{Arc, atomic::Ordering}, + time::{Duration, Instant}, +}; use dash_frontend::frontend::{self, FrontendTask, FrontendUpdateParams}; -use glam::{Affine2, Affine3A, Vec2, vec2, vec3}; +use glam::{Affine2, Affine3A, Quat, Vec2, vec2, vec3}; use wayvr_ipc::{ packet_client::WvrProcessLaunchParams, packet_server::{WvrProcess, WvrProcessHandle, WvrWindow, WvrWindowHandle}, @@ -49,7 +53,7 @@ use crate::{ FrameMeta, OverlayBackend, OverlayEventData, RenderResources, ShouldRender, ui_transform, }, - window::{OverlayCategory, OverlayWindowConfig}, + window::{OverlayCategory, OverlayWindowConfig, realign}, }, }; @@ -65,6 +69,7 @@ pub struct DashFrontend { timestep: Timestep, has_focus: [bool; 2], context: WguiContext, + tutorial: bool, } const GUI_SCALE: f32 = 2.0; @@ -81,9 +86,12 @@ impl DashFrontend { } } + let tutorial = !app.session.config.tutorial_graduated; + let frontend = frontend::Frontend::new(frontend::InitParams { interface: Box::new(interface), lang_provider: &WayVRLangProvider::from_config(&app.session.config), + show_welcome: tutorial, has_monado: matches!(app.xr_backend, XrBackend::OpenXR), theme: app.wgui_theme.clone(), })?; @@ -100,6 +108,7 @@ impl DashFrontend { timestep: Timestep::new(60.0), has_focus: [false, false], context, + tutorial, }) } @@ -197,7 +206,26 @@ impl OverlayBackend for DashFrontend { }) } - fn notify(&mut self, _app: &mut AppState, _data: OverlayEventData) -> anyhow::Result<()> { + fn notify(&mut self, app: &mut AppState, data: OverlayEventData) -> anyhow::Result<()> { + if !self.tutorial { + return Ok(()); + } + + // if we're grabbed, stop following the hmd + if let OverlayEventData::OverlayGrabbed { name, .. } = data { + if &*name == DASH_NAME { + self.tutorial = false; + app.tasks.enqueue(TaskType::Overlay(OverlayTask::Modify( + OverlaySelector::Name(name), + Box::new(|_app, owc| { + if let Some(active_state) = owc.active_state.as_mut() { + active_state.positioning = Positioning::Floating; + } + }), + ))); + } + } + Ok(()) } @@ -290,7 +318,56 @@ impl OverlayBackend for DashFrontend { } } +fn tutorial_spawn_effect(app: &mut AppState) -> anyhow::Result<()> { + let dash_name: Arc = DASH_NAME.into(); + + app.tasks.enqueue_at( + TaskType::Overlay(OverlayTask::Modify( + OverlaySelector::Name(dash_name.clone()), + Box::new(|app, owc| { + let hmd = &app.input_state.hmd; + let pos = hmd.translation + hmd.z_axis * -2.0; + let mut transform = Affine3A::from_rotation_translation(Quat::IDENTITY, pos.into()); + realign(&mut transform, hmd, 1.0); + + owc.active_state = Some(OverlayWindowState { + saved_transform: Some(Affine3A::from_translation(vec3(0., 0., -0.9))), + transform, + grabbable: true, + interactable: true, + positioning: Positioning::FollowHead { lerp: 0.01 }, + curvature: Some(0.15), + alpha: 0.1, + ..Default::default() + }); + }), + )), + Instant::now().add(Duration::from_millis(500)), + ); + + // hacky fade in over time + for i in 0..46 { + app.tasks.enqueue_at( + TaskType::Overlay(OverlayTask::Modify( + OverlaySelector::Name(dash_name.clone()), + Box::new(|_app, owc| { + if let Some(active_state) = owc.active_state.as_mut() { + active_state.alpha = (active_state.alpha + 0.025).min(1.0); + } + }), + )), + Instant::now().add(Duration::from_millis(500 + 40 * i)), + ); + } + + Ok(()) +} + pub fn create_dash_frontend(app: &mut AppState) -> anyhow::Result { + if !app.session.config.tutorial_graduated { + tutorial_spawn_effect(app)?; + } + Ok(OverlayWindowConfig { name: DASH_NAME.into(), default_state: OverlayWindowState { diff --git a/wlx-common/src/config.rs b/wlx-common/src/config.rs index 229168a5..d0745fc5 100644 --- a/wlx-common/src/config.rs +++ b/wlx-common/src/config.rs @@ -427,4 +427,7 @@ pub struct GeneralConfig { #[serde(default)] pub chroma_key_params: ChromaKeyParams, + + #[serde(default)] + pub tutorial_graduated: bool, }