enjoy dashboard in vr
This commit is contained in:
@@ -20,7 +20,7 @@ use wlx_common::overlays::ToastTopic;
|
|||||||
use crate::{
|
use crate::{
|
||||||
RUNNING,
|
RUNNING,
|
||||||
backend::task::{OverlayTask, PlayspaceTask, TaskType},
|
backend::task::{OverlayTask, PlayspaceTask, TaskType},
|
||||||
overlays::toast::Toast,
|
overlays::{dashboard::DASH_NAME, toast::Toast},
|
||||||
state::AppState,
|
state::AppState,
|
||||||
subsystem::hid::VirtualKey,
|
subsystem::hid::VirtualKey,
|
||||||
windowing::OverlaySelector,
|
windowing::OverlaySelector,
|
||||||
@@ -191,13 +191,21 @@ pub(super) fn setup_custom_button<S: 'static>(
|
|||||||
let button = button.clone();
|
let button = button.clone();
|
||||||
|
|
||||||
let callback: EventCallback<AppState, S> = match command {
|
let callback: EventCallback<AppState, S> = match command {
|
||||||
#[cfg(feature = "wayvr")]
|
|
||||||
"::DashToggle" => Box::new(move |_common, data, app, _| {
|
"::DashToggle" => Box::new(move |_common, data, app, _| {
|
||||||
if !test_button(data) || !test_duration(&button, app) {
|
if !test_button(data) || !test_duration(&button, app) {
|
||||||
return Ok(EventResult::Pass);
|
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)
|
Ok(EventResult::Consumed)
|
||||||
}),
|
}),
|
||||||
"::SetToggle" => {
|
"::SetToggle" => {
|
||||||
|
|||||||
297
wlx-overlay-s/src/overlays/dashboard.rs
Normal file
297
wlx-overlay-s/src/overlays/dashboard.rs
Normal 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)?))
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
pub mod anchor;
|
pub mod anchor;
|
||||||
pub mod custom;
|
pub mod custom;
|
||||||
|
pub mod dashboard;
|
||||||
pub mod edit;
|
pub mod edit;
|
||||||
pub mod keyboard;
|
pub mod keyboard;
|
||||||
pub mod screen;
|
pub mod screen;
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ pub fn create_wl_window_overlay(
|
|||||||
transform: Affine3A::from_scale_rotation_translation(
|
transform: Affine3A::from_scale_rotation_translation(
|
||||||
Vec3::ONE,
|
Vec3::ONE,
|
||||||
Quat::IDENTITY,
|
Quat::IDENTITY,
|
||||||
vec3(0.0, 0.0, -0.4),
|
vec3(0.0, 0.0, -0.95),
|
||||||
),
|
),
|
||||||
..OverlayWindowState::default()
|
..OverlayWindowState::default()
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ use crate::{
|
|||||||
overlays::{
|
overlays::{
|
||||||
anchor::{create_anchor, create_grab_help},
|
anchor::{create_anchor, create_grab_help},
|
||||||
custom::create_custom,
|
custom::create_custom,
|
||||||
|
dashboard::create_dash_frontend,
|
||||||
edit::EditWrapperManager,
|
edit::EditWrapperManager,
|
||||||
keyboard::create_keyboard,
|
keyboard::create_keyboard,
|
||||||
screen::create_screens,
|
screen::create_screens,
|
||||||
@@ -122,6 +123,9 @@ where
|
|||||||
let watch = OverlayWindowData::from_config(create_watch(app)?);
|
let watch = OverlayWindowData::from_config(create_watch(app)?);
|
||||||
me.watch_id = me.add(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)?);
|
let grab_help = OverlayWindowData::from_config(create_grab_help(app)?);
|
||||||
me.add(grab_help, app);
|
me.add(grab_help, app);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user