enjoy dashboard in vr

This commit is contained in:
galister
2025-12-26 21:49:10 +09:00
parent 1d7617c155
commit a849d1aed4
5 changed files with 314 additions and 4 deletions

View File

@@ -20,7 +20,7 @@ use wlx_common::overlays::ToastTopic;
use crate::{
RUNNING,
backend::task::{OverlayTask, PlayspaceTask, TaskType},
overlays::toast::Toast,
overlays::{dashboard::DASH_NAME, toast::Toast},
state::AppState,
subsystem::hid::VirtualKey,
windowing::OverlaySelector,
@@ -191,13 +191,21 @@ pub(super) fn setup_custom_button<S: 'static>(
let button = button.clone();
let callback: EventCallback<AppState, S> = match command {
#[cfg(feature = "wayvr")]
"::DashToggle" => Box::new(move |_common, data, app, _| {
if !test_button(data) || !test_duration(&button, app) {
return Ok(EventResult::Pass);
}
//FIXME
app.tasks.enqueue(TaskType::Overlay(OverlayTask::Modify(
OverlaySelector::Name(DASH_NAME.into()),
Box::new(move |app, owc| {
if owc.active_state.is_none() {
owc.activate(app);
} else {
owc.deactivate();
}
}),
)));
Ok(EventResult::Consumed)
}),
"::SetToggle" => {

View File

@@ -0,0 +1,297 @@
use dash_frontend::{
frontend,
settings::{self, SettingsIO},
};
use glam::{Affine2, Affine3A, Vec2, vec2, vec3};
use wgui::{
event::{
Event as WguiEvent, MouseButtonIndex, MouseDownEvent, MouseLeaveEvent, MouseMotionEvent,
MouseUpEvent, MouseWheelEvent,
},
gfx::cmd::WGfxClearMode,
renderer_vk::context::Context as WguiContext,
widget::EventResult,
};
use wlx_common::{
dash_interface_emulated::DashInterfaceEmulated,
overlays::{BackendAttrib, BackendAttribValue},
};
use wlx_common::{
timestep::Timestep,
windowing::{OverlayWindowState, Positioning},
};
use crate::{
backend::input::{Haptics, HoverResult, PointerHit, PointerMode},
state::AppState,
subsystem::hid::WheelDelta,
windowing::{
Z_ORDER_DASHBOARD,
backend::{
FrameMeta, OverlayBackend, OverlayEventData, RenderResources, ShouldRender,
ui_transform,
},
window::{OverlayCategory, OverlayWindowConfig},
},
};
pub const DASH_NAME: &str = "Dashboard";
const DASH_RES_U32A: [u32; 2] = [1280, 720];
const DASH_RES_VEC2: Vec2 = vec2(DASH_RES_U32A[0] as _, DASH_RES_U32A[1] as _);
//FIXME: replace with proper impl
struct SimpleSettingsIO {
settings: settings::Settings,
}
impl SimpleSettingsIO {
fn new() -> Self {
let mut res = Self {
settings: settings::Settings::default(),
};
res.read_from_disk();
res
}
}
impl settings::SettingsIO for SimpleSettingsIO {
fn get_mut(&mut self) -> &mut settings::Settings {
&mut self.settings
}
fn get(&self) -> &dash_frontend::settings::Settings {
&self.settings
}
fn save_to_disk(&mut self) {
log::info!("saving settings");
let data = self.settings.save();
std::fs::write("/tmp/testbed_settings.json", data).unwrap();
}
fn read_from_disk(&mut self) {
log::info!("loading settings");
if let Ok(res) = std::fs::read("/tmp/testbed_settings.json") {
let data = String::from_utf8(res).unwrap();
self.settings = settings::Settings::load(&data).unwrap();
}
}
fn mark_as_dirty(&mut self) {
self.save_to_disk();
}
}
pub struct DashFrontend {
inner: frontend::Frontend,
initialized: bool,
interaction_transform: Option<Affine2>,
timestep: Timestep,
has_focus: [bool; 2],
context: WguiContext,
}
impl DashFrontend {
fn new(app: &mut AppState) -> anyhow::Result<Self> {
let settings = SimpleSettingsIO::new();
let interface = DashInterfaceEmulated::new();
let frontend = frontend::Frontend::new(frontend::InitParams {
settings: Box::new(settings),
interface: Box::new(interface),
})?;
let context = WguiContext::new(&mut app.wgui_shared, 1.0)?;
Ok(Self {
inner: frontend,
initialized: false,
interaction_transform: None,
timestep: Timestep::new(),
has_focus: [false, false],
context,
})
}
fn update_layout(&mut self) -> anyhow::Result<()> {
self.inner.update(DASH_RES_VEC2.x, DASH_RES_VEC2.y, 0.0)
}
fn push_event(&mut self, event: &WguiEvent) -> EventResult {
match self.inner.layout.push_event(event, &mut (), &mut ()) {
Ok(r) => r,
Err(e) => {
log::error!("Failed to push event: {e:?}");
EventResult::NoHit
}
}
}
}
impl OverlayBackend for DashFrontend {
fn init(&mut self, app: &mut AppState) -> anyhow::Result<()> {
self.context
.update_viewport(&mut app.wgui_shared, DASH_RES_U32A, 1.0)?;
self.interaction_transform = Some(ui_transform(DASH_RES_U32A));
if self.inner.layout.content_size.x * self.inner.layout.content_size.y != 0.0 {
self.update_layout()?;
self.initialized = true;
}
Ok(())
}
fn pause(&mut self, _app: &mut AppState) -> anyhow::Result<()> {
Ok(())
}
fn resume(&mut self, _app: &mut AppState) -> anyhow::Result<()> {
self.inner.layout.needs_redraw = true;
self.timestep.reset();
Ok(())
}
fn should_render(&mut self, _app: &mut AppState) -> anyhow::Result<ShouldRender> {
while self.timestep.on_tick() {
self.inner.layout.tick()?;
}
Ok(if self.inner.layout.check_toggle_needs_redraw() {
ShouldRender::Should
} else {
ShouldRender::Can
})
}
fn render(&mut self, app: &mut AppState, rdr: &mut RenderResources) -> anyhow::Result<()> {
self.inner
.layout
.update(DASH_RES_VEC2, self.timestep.alpha)?;
let globals = self.inner.layout.state.globals.clone(); // sorry
let mut globals = globals.get();
let primitives = wgui::drawing::draw(&mut wgui::drawing::DrawParams {
globals: &mut globals,
layout: &mut self.inner.layout,
debug_draw: false,
timestep_alpha: self.timestep.alpha,
})?;
self.context.draw(
&globals.font_system,
&mut app.wgui_shared,
&mut rdr.cmd_buf_single(),
&primitives,
)?;
Ok(())
}
fn frame_meta(&mut self) -> Option<FrameMeta> {
Some(FrameMeta {
clear: WGfxClearMode::Clear([0., 0., 0., 0.]),
extent: [DASH_RES_U32A[0], DASH_RES_U32A[1], 1],
..Default::default()
})
}
fn notify(&mut self, _app: &mut AppState, _data: OverlayEventData) -> anyhow::Result<()> {
Ok(())
}
fn on_scroll(&mut self, _app: &mut AppState, hit: &PointerHit, delta: WheelDelta) {
let e = WguiEvent::MouseWheel(MouseWheelEvent {
delta: vec2(delta.x, delta.y),
pos: hit.uv * self.inner.layout.content_size,
device: hit.pointer,
});
self.push_event(&e);
}
fn on_hover(&mut self, _app: &mut AppState, hit: &PointerHit) -> HoverResult {
let e = &WguiEvent::MouseMotion(MouseMotionEvent {
pos: hit.uv * self.inner.layout.content_size,
device: hit.pointer,
});
self.has_focus[hit.pointer] = true;
let result = self.push_event(e);
HoverResult {
consume: result != EventResult::NoHit,
haptics: self
.inner
.layout
.check_toggle_haptics_triggered()
.then_some(Haptics {
intensity: 0.1,
duration: 0.01,
frequency: 5.0,
}),
}
}
fn on_left(&mut self, _app: &mut AppState, pointer: usize) {
let e = WguiEvent::MouseLeave(MouseLeaveEvent { device: pointer });
self.has_focus[pointer] = false;
self.push_event(&e);
}
fn on_pointer(&mut self, _app: &mut AppState, hit: &PointerHit, pressed: bool) {
let index = match hit.mode {
PointerMode::Left => MouseButtonIndex::Left,
PointerMode::Right => MouseButtonIndex::Right,
PointerMode::Middle => MouseButtonIndex::Middle,
_ => return,
};
let e = if pressed {
WguiEvent::MouseDown(MouseDownEvent {
pos: hit.uv * self.inner.layout.content_size,
index,
device: hit.pointer,
})
} else {
WguiEvent::MouseUp(MouseUpEvent {
pos: hit.uv * self.inner.layout.content_size,
index,
device: hit.pointer,
})
};
self.push_event(&e);
// released while off-panel → send mouse leave as well
if !pressed && !self.has_focus[hit.pointer] {
let e = WguiEvent::MouseMotion(MouseMotionEvent {
pos: vec2(-1., -1.),
device: hit.pointer,
});
self.push_event(&e);
let e = WguiEvent::MouseLeave(MouseLeaveEvent {
device: hit.pointer,
});
self.push_event(&e);
}
}
fn get_interaction_transform(&mut self) -> Option<Affine2> {
self.interaction_transform
}
fn get_attrib(&self, _attrib: BackendAttrib) -> Option<BackendAttribValue> {
None
}
fn set_attrib(&mut self, _app: &mut AppState, _value: BackendAttribValue) -> bool {
false
}
}
pub fn create_dash_frontend(app: &mut AppState) -> anyhow::Result<OverlayWindowConfig> {
Ok(OverlayWindowConfig {
name: DASH_NAME.into(),
default_state: OverlayWindowState {
transform: Affine3A::from_translation(vec3(0., 0., -0.9)),
grabbable: true,
interactable: true,
positioning: Positioning::Floating,
curvature: Some(0.15),
..OverlayWindowState::default()
},
z_order: Z_ORDER_DASHBOARD,
category: OverlayCategory::Dashboard,
global: true,
..OverlayWindowConfig::from_backend(Box::new(DashFrontend::new(app)?))
})
}

View File

@@ -1,5 +1,6 @@
pub mod anchor;
pub mod custom;
pub mod dashboard;
pub mod edit;
pub mod keyboard;
pub mod screen;

View File

@@ -43,7 +43,7 @@ pub fn create_wl_window_overlay(
transform: Affine3A::from_scale_rotation_translation(
Vec3::ONE,
Quat::IDENTITY,
vec3(0.0, 0.0, -0.4),
vec3(0.0, 0.0, -0.95),
),
..OverlayWindowState::default()
},

View File

@@ -18,6 +18,7 @@ use crate::{
overlays::{
anchor::{create_anchor, create_grab_help},
custom::create_custom,
dashboard::create_dash_frontend,
edit::EditWrapperManager,
keyboard::create_keyboard,
screen::create_screens,
@@ -122,6 +123,9 @@ where
let watch = OverlayWindowData::from_config(create_watch(app)?);
me.watch_id = me.add(watch, app);
let dash_frontend = OverlayWindowData::from_config(create_dash_frontend(app)?);
me.add(dash_frontend, app);
let grab_help = OverlayWindowData::from_config(create_grab_help(app)?);
me.add(grab_help, app);