refactor overlay windowing
This commit is contained in:
@@ -73,6 +73,7 @@ xcb = { version = "1.6.0", optional = true, features = [
|
||||
image_dds = { version = "0.7.2", default-features = false, features = [
|
||||
"ddsfile",
|
||||
] }
|
||||
interprocess = { version = "2.2.3" }
|
||||
mint = "0.5.9"
|
||||
tracing-subscriber = { version = "0.3.20", features = ["env-filter"] }
|
||||
tracing = "0.1.41"
|
||||
@@ -94,7 +95,6 @@ smithay = { version = "0.7.0", default-features = false, features = [
|
||||
uuid = { version = "1.18.1", features = ["v4", "fast-rng"], optional = true }
|
||||
wayland-client = { workspace = true, optional = true }
|
||||
wayland-egl = { version = "0.32.8", optional = true }
|
||||
interprocess = { version = "2.2.3", optional = true }
|
||||
bytes = { version = "1.10.1", optional = true }
|
||||
wayvr_ipc = { git = "https://github.com/olekolek1000/wayvr-ipc.git", rev = "a72587d23f3bb8624d9aeb1f13c0a21e65350f51", default-features = false, optional = true }
|
||||
rust-embed = { workspace = true }
|
||||
@@ -119,7 +119,6 @@ wayvr = [
|
||||
"dep:uuid",
|
||||
"dep:wayland-client",
|
||||
"dep:wayland-egl",
|
||||
"dep:interprocess",
|
||||
"dep:bytes",
|
||||
"dep:wayvr_ipc",
|
||||
]
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
</template>
|
||||
|
||||
<template name="Set">
|
||||
<Button macro="button_style" _press="::OverlayToggle ${handle}">
|
||||
<Button macro="button_style" _press="::SetToggle ${handle}">
|
||||
<sprite width="40" height="40" color="~set_color" src="watch/set2.svg" />
|
||||
<div position="absolute" margin_top="9">
|
||||
<label text="${display}" size="24" color="#00050F" weight="bold" />
|
||||
|
||||
@@ -1,210 +0,0 @@
|
||||
use std::sync::{Arc, LazyLock};
|
||||
|
||||
#[cfg(feature = "openxr")]
|
||||
use openxr as xr;
|
||||
|
||||
use glam::{Affine3A, Vec3, Vec3A};
|
||||
use slotmap::HopSlotMap;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::{
|
||||
config::AStrSetExt,
|
||||
overlays::{
|
||||
anchor::create_anchor,
|
||||
keyboard::{builder::create_keyboard, KEYBOARD_NAME},
|
||||
screen::create_screens,
|
||||
watch::{create_watch, WATCH_NAME},
|
||||
},
|
||||
state::AppState,
|
||||
};
|
||||
|
||||
use super::overlay::{OverlayData, OverlayID};
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum BackendError {
|
||||
#[error("backend not supported")]
|
||||
NotSupported,
|
||||
#[cfg(feature = "openxr")]
|
||||
#[error("OpenXR Error: {0:?}")]
|
||||
OpenXrError(#[from] xr::sys::Result),
|
||||
#[error("Shutdown")]
|
||||
Shutdown,
|
||||
#[error("Restart")]
|
||||
Restart,
|
||||
#[error("Fatal: {0:?}")]
|
||||
Fatal(#[from] anyhow::Error),
|
||||
}
|
||||
|
||||
pub struct OverlayContainer<T>
|
||||
where
|
||||
T: Default,
|
||||
{
|
||||
overlays: HopSlotMap<OverlayID, OverlayData<T>>,
|
||||
}
|
||||
|
||||
impl<T> OverlayContainer<T>
|
||||
where
|
||||
T: Default,
|
||||
{
|
||||
pub fn new(app: &mut AppState, headless: bool) -> anyhow::Result<Self> {
|
||||
let mut overlays = HopSlotMap::with_key();
|
||||
let mut show_screens = app.session.config.show_screens.clone();
|
||||
let mut maybe_keymap = None;
|
||||
|
||||
if headless {
|
||||
log::info!("Running in headless mode; keyboard will be en-US");
|
||||
} else {
|
||||
match create_screens(app) {
|
||||
Ok((data, keymap)) => {
|
||||
if show_screens.is_empty()
|
||||
&& let Some((_, s, _)) = data.screens.first()
|
||||
{
|
||||
show_screens.arc_set(s.name.clone());
|
||||
}
|
||||
for (meta, mut state, backend) in data.screens {
|
||||
if show_screens.arc_get(state.name.as_ref()) {
|
||||
state.show_hide = true;
|
||||
}
|
||||
overlays.insert(OverlayData::<T> {
|
||||
state,
|
||||
..OverlayData::from_backend(backend)
|
||||
});
|
||||
app.screens.push(meta);
|
||||
}
|
||||
|
||||
maybe_keymap = keymap;
|
||||
}
|
||||
Err(e) => log::error!("Unable to initialize screens: {e:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
let anchor = create_anchor(app)?;
|
||||
overlays.insert(anchor);
|
||||
|
||||
let mut watch = create_watch::<T>(app)?;
|
||||
watch.state.want_visible = true;
|
||||
overlays.insert(watch);
|
||||
|
||||
let mut keyboard = create_keyboard(app, maybe_keymap)?;
|
||||
keyboard.state.show_hide = show_screens.arc_get(KEYBOARD_NAME);
|
||||
keyboard.state.want_visible = false;
|
||||
overlays.insert(keyboard);
|
||||
|
||||
Ok(Self { overlays })
|
||||
}
|
||||
|
||||
pub fn mut_by_selector(&mut self, selector: &OverlaySelector) -> Option<&mut OverlayData<T>> {
|
||||
match selector {
|
||||
OverlaySelector::Id(id) => self.mut_by_id(*id),
|
||||
OverlaySelector::Name(name) => self.mut_by_name(name),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove_by_selector(&mut self, selector: &OverlaySelector) -> Option<OverlayData<T>> {
|
||||
match selector {
|
||||
OverlaySelector::Id(id) => self.overlays.remove(*id),
|
||||
OverlaySelector::Name(name) => {
|
||||
self.lookup(name).and_then(|id| self.overlays.remove(id))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_by_id(&mut self, id: OverlayID) -> Option<&OverlayData<T>> {
|
||||
self.overlays.get(id)
|
||||
}
|
||||
|
||||
pub fn mut_by_id(&mut self, id: OverlayID) -> Option<&mut OverlayData<T>> {
|
||||
self.overlays.get_mut(id)
|
||||
}
|
||||
|
||||
pub fn get_by_name<'a>(&'a mut self, name: &str) -> Option<&'a OverlayData<T>> {
|
||||
self.overlays.values().find(|o| *o.state.name == *name)
|
||||
}
|
||||
|
||||
pub fn mut_by_name<'a>(&'a mut self, name: &str) -> Option<&'a mut OverlayData<T>> {
|
||||
self.overlays.values_mut().find(|o| *o.state.name == *name)
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = (OverlayID, &'_ OverlayData<T>)> {
|
||||
self.overlays.iter()
|
||||
}
|
||||
|
||||
pub fn iter_mut(&mut self) -> impl Iterator<Item = (OverlayID, &'_ mut OverlayData<T>)> {
|
||||
self.overlays.iter_mut()
|
||||
}
|
||||
|
||||
pub fn values(&self) -> impl Iterator<Item = &'_ OverlayData<T>> {
|
||||
self.overlays.values()
|
||||
}
|
||||
|
||||
pub fn values_mut(&mut self) -> impl Iterator<Item = &'_ mut OverlayData<T>> {
|
||||
self.overlays.values_mut()
|
||||
}
|
||||
|
||||
pub fn lookup(&self, name: &str) -> Option<OverlayID> {
|
||||
self.overlays
|
||||
.iter()
|
||||
.find(|(_, v)| v.state.name.as_ref() == name)
|
||||
.map(|(k, _)| k)
|
||||
}
|
||||
|
||||
pub fn add(&mut self, overlay: OverlayData<T>) -> OverlayID {
|
||||
self.overlays.insert(overlay)
|
||||
}
|
||||
|
||||
pub fn show_hide(&mut self, app: &mut AppState) {
|
||||
let any_shown = self
|
||||
.overlays
|
||||
.values()
|
||||
.any(|o| o.state.show_hide && o.state.want_visible);
|
||||
|
||||
if !any_shown {
|
||||
static ANCHOR_LOCAL: LazyLock<Affine3A> =
|
||||
LazyLock::new(|| Affine3A::from_translation(Vec3::NEG_Z));
|
||||
let hmd = snap_upright(app.input_state.hmd, Vec3A::Y);
|
||||
app.anchor = hmd * *ANCHOR_LOCAL;
|
||||
}
|
||||
|
||||
self.overlays.values_mut().for_each(|o| {
|
||||
if o.state.show_hide {
|
||||
o.state.want_visible = !any_shown;
|
||||
if o.state.want_visible
|
||||
&& app.session.config.realign_on_showhide
|
||||
&& o.state.recenter
|
||||
{
|
||||
o.state.reset(app, false);
|
||||
}
|
||||
}
|
||||
// toggle watch back on if it was hidden
|
||||
if !any_shown && *o.state.name == *WATCH_NAME {
|
||||
o.state.reset(app, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum OverlaySelector {
|
||||
Id(OverlayID),
|
||||
Name(Arc<str>),
|
||||
}
|
||||
|
||||
pub fn snap_upright(transform: Affine3A, up_dir: Vec3A) -> Affine3A {
|
||||
if transform.x_axis.dot(up_dir).abs() < 0.2 {
|
||||
let scale = transform.x_axis.length();
|
||||
let col_z = transform.z_axis.normalize();
|
||||
let col_y = up_dir;
|
||||
let col_x = col_y.cross(col_z);
|
||||
let col_y = col_z.cross(col_x).normalize();
|
||||
let col_x = col_x.normalize();
|
||||
|
||||
Affine3A::from_cols(
|
||||
col_x * scale,
|
||||
col_y * scale,
|
||||
col_z * scale,
|
||||
transform.translation,
|
||||
)
|
||||
} else {
|
||||
transform
|
||||
}
|
||||
}
|
||||
@@ -2,20 +2,18 @@ use std::f32::consts::PI;
|
||||
use std::process::{Child, Command};
|
||||
use std::{collections::VecDeque, time::Instant};
|
||||
|
||||
use glam::{Affine3A, Vec2, Vec3, Vec3A, Vec3Swizzles};
|
||||
use glam::{Affine3A, Vec2, Vec3A, Vec3Swizzles};
|
||||
|
||||
use smallvec::{smallvec, SmallVec};
|
||||
|
||||
use crate::backend::common::OverlaySelector;
|
||||
use crate::backend::overlay::Positioning;
|
||||
use crate::config::AStrMapExt;
|
||||
use crate::overlays::anchor::ANCHOR_NAME;
|
||||
use crate::state::{AppSession, AppState};
|
||||
use crate::subsystem::input::KeyboardFocus;
|
||||
use crate::windowing::manager::OverlayWindowManager;
|
||||
use crate::windowing::window::{OverlayWindowData, OverlayWindowState, Positioning};
|
||||
use crate::windowing::{OverlayID, OverlaySelector};
|
||||
|
||||
use super::overlay::{OverlayID, OverlayState};
|
||||
use super::task::{TaskContainer, TaskType};
|
||||
use super::{common::OverlayContainer, overlay::OverlayData};
|
||||
|
||||
pub struct TrackedDevice {
|
||||
pub soc: Option<f32>,
|
||||
@@ -282,17 +280,17 @@ pub enum PointerMode {
|
||||
Special,
|
||||
}
|
||||
|
||||
fn update_focus(focus: &mut KeyboardFocus, state: &OverlayState) {
|
||||
if let Some(f) = &state.keyboard_focus
|
||||
fn update_focus(focus: &mut KeyboardFocus, overlay_keyboard_focus: &Option<KeyboardFocus>) {
|
||||
if let Some(f) = &overlay_keyboard_focus
|
||||
&& *focus != *f
|
||||
{
|
||||
log::info!("Setting keyboard focus to {:?}", *f);
|
||||
log::debug!("Setting keyboard focus to {:?}", *f);
|
||||
*focus = *f;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn interact<O>(
|
||||
overlays: &mut OverlayContainer<O>,
|
||||
overlays: &mut OverlayWindowManager<O>,
|
||||
app: &mut AppState,
|
||||
) -> [(f32, Option<Haptics>); 2]
|
||||
where
|
||||
@@ -312,7 +310,7 @@ where
|
||||
#[allow(clippy::too_many_lines, clippy::cognitive_complexity)]
|
||||
fn interact_hand<O>(
|
||||
idx: usize,
|
||||
overlays: &mut OverlayContainer<O>,
|
||||
overlays: &mut OverlayWindowManager<O>,
|
||||
app: &mut AppState,
|
||||
) -> (f32, Option<Haptics>)
|
||||
where
|
||||
@@ -332,7 +330,7 @@ where
|
||||
let Some(mut hit) = pointer.get_nearest_hit(overlays) else {
|
||||
if let Some(hovered_id) = pointer.interaction.hovered_id.take() {
|
||||
if let Some(hovered) = overlays.mut_by_id(hovered_id) {
|
||||
hovered.backend.on_left(app, idx);
|
||||
hovered.config.backend.on_left(app, idx);
|
||||
}
|
||||
pointer = &mut app.input_state.pointers[idx];
|
||||
pointer.interaction.hovered_id = None;
|
||||
@@ -348,7 +346,7 @@ where
|
||||
mode: pointer.interaction.mode,
|
||||
..Default::default()
|
||||
};
|
||||
clicked.backend.on_pointer(app, &hit, false);
|
||||
clicked.config.backend.on_pointer(app, &hit, false);
|
||||
}
|
||||
return (0.0, None); // no hit
|
||||
};
|
||||
@@ -360,7 +358,7 @@ where
|
||||
if Some(pointer.idx) == old_hovered.primary_pointer {
|
||||
old_hovered.primary_pointer = None;
|
||||
}
|
||||
old_hovered.backend.on_left(app, idx);
|
||||
old_hovered.config.backend.on_left(app, idx);
|
||||
pointer = &mut app.input_state.pointers[idx];
|
||||
}
|
||||
let Some(hovered) = overlays.mut_by_id(hit.overlay) else {
|
||||
@@ -381,11 +379,17 @@ where
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
log::trace!("Hit: {} {:?}", hovered.state.name, hit);
|
||||
log::trace!("Hit: {} {:?}", hovered.config.name, hit);
|
||||
|
||||
if pointer.now.grab && !pointer.before.grab && hovered.state.grabbable {
|
||||
update_focus(&mut app.hid_provider.keyboard_focus, &hovered.state);
|
||||
pointer.start_grab(hit.overlay, hovered, &mut app.tasks);
|
||||
let hovered_state = hovered.config.active_state.as_mut().unwrap();
|
||||
|
||||
if pointer.now.grab && !pointer.before.grab && hovered_state.grabbable {
|
||||
update_focus(
|
||||
&mut app.hid_provider.keyboard_focus,
|
||||
&hovered.config.keyboard_focus,
|
||||
);
|
||||
pointer.start_grab(hit.overlay, hovered_state, &mut app.tasks);
|
||||
log::debug!("Hand {}: grabbed {}", hit.pointer, hovered.config.name);
|
||||
return (
|
||||
hit.dist,
|
||||
Some(Haptics {
|
||||
@@ -398,7 +402,7 @@ where
|
||||
|
||||
// Pass mouse motion events only if not scrolling
|
||||
// (allows scrolling on all Chromium-based applications)
|
||||
let haptics = hovered.backend.on_hover(app, &hit);
|
||||
let haptics = hovered.config.backend.on_hover(app, &hit);
|
||||
|
||||
pointer = &mut app.input_state.pointers[idx];
|
||||
|
||||
@@ -414,55 +418,66 @@ where
|
||||
.frame_meta()
|
||||
.is_some_and(|e| e.extent[0] >= e.extent[1]);
|
||||
|
||||
// re-borrow
|
||||
let hovered_state = hovered.config.active_state.as_mut().unwrap();
|
||||
if can_curve {
|
||||
let cur = hovered.state.curvature.unwrap_or(0.0);
|
||||
let cur = hovered_state.curvature.unwrap_or(0.0);
|
||||
let new = scroll_y.mul_add(-0.01, cur).min(0.5);
|
||||
if new <= f32::EPSILON {
|
||||
hovered.state.curvature = None;
|
||||
hovered_state.curvature = None;
|
||||
} else {
|
||||
hovered.state.curvature = Some(new);
|
||||
hovered_state.curvature = Some(new);
|
||||
}
|
||||
} else {
|
||||
hovered.state.curvature = None;
|
||||
hovered_state.curvature = None;
|
||||
}
|
||||
} else {
|
||||
hovered.backend.on_scroll(app, &hit, scroll_y, scroll_x);
|
||||
hovered
|
||||
.config
|
||||
.backend
|
||||
.on_scroll(app, &hit, scroll_y, scroll_x);
|
||||
}
|
||||
pointer = &mut app.input_state.pointers[idx];
|
||||
}
|
||||
|
||||
if pointer.now.click && !pointer.before.click {
|
||||
pointer.interaction.clicked_id = Some(hit.overlay);
|
||||
update_focus(&mut app.hid_provider.keyboard_focus, &hovered.state);
|
||||
hovered.backend.on_pointer(app, &hit, true);
|
||||
update_focus(
|
||||
&mut app.hid_provider.keyboard_focus,
|
||||
&hovered.config.keyboard_focus,
|
||||
);
|
||||
hovered.config.backend.on_pointer(app, &hit, true);
|
||||
} else if !pointer.now.click && pointer.before.click {
|
||||
if let Some(clicked_id) = pointer.interaction.clicked_id.take() {
|
||||
if let Some(clicked) = overlays.mut_by_id(clicked_id) {
|
||||
clicked.backend.on_pointer(app, &hit, false);
|
||||
clicked.config.backend.on_pointer(app, &hit, false);
|
||||
}
|
||||
} else {
|
||||
hovered.backend.on_pointer(app, &hit, false);
|
||||
hovered.config.backend.on_pointer(app, &hit, false);
|
||||
}
|
||||
}
|
||||
(hit.dist, haptics)
|
||||
}
|
||||
|
||||
impl Pointer {
|
||||
fn get_nearest_hit<O>(&mut self, overlays: &mut OverlayContainer<O>) -> Option<PointerHit>
|
||||
fn get_nearest_hit<O>(&mut self, overlays: &mut OverlayWindowManager<O>) -> Option<PointerHit>
|
||||
where
|
||||
O: Default,
|
||||
{
|
||||
let mut hits: SmallVec<[RayHit; 8]> = smallvec!();
|
||||
|
||||
for (id, overlay) in overlays.iter() {
|
||||
if !overlay.state.want_visible || !overlay.state.interactable {
|
||||
let Some(overlay_state) = overlay.config.active_state.as_ref() else {
|
||||
continue;
|
||||
};
|
||||
if !overlay_state.interactable {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(hit) = self.ray_test(
|
||||
id,
|
||||
&overlay.state.transform,
|
||||
overlay.state.curvature.as_ref(),
|
||||
&overlay_state.transform,
|
||||
overlay_state.curvature.as_ref(),
|
||||
) {
|
||||
if hit.dist.is_infinite() || hit.dist.is_nan() {
|
||||
continue;
|
||||
@@ -477,6 +492,7 @@ impl Pointer {
|
||||
let overlay = overlays.mut_by_id(hit.overlay).unwrap(); // safe because we just got the id from the overlay
|
||||
|
||||
let Some(uv) = overlay
|
||||
.config
|
||||
.backend
|
||||
.as_mut()
|
||||
.get_interaction_transform()
|
||||
@@ -502,26 +518,24 @@ impl Pointer {
|
||||
None
|
||||
}
|
||||
|
||||
fn start_grab<O>(
|
||||
fn start_grab(
|
||||
&mut self,
|
||||
id: OverlayID,
|
||||
overlay: &mut OverlayData<O>,
|
||||
state: &mut OverlayWindowState,
|
||||
tasks: &mut TaskContainer,
|
||||
) where
|
||||
O: Default,
|
||||
{
|
||||
) {
|
||||
let offset = self
|
||||
.pose
|
||||
.inverse()
|
||||
.transform_point3a(overlay.state.transform.translation);
|
||||
.transform_point3a(state.transform.translation);
|
||||
|
||||
self.interaction.grabbed = Some(GrabData {
|
||||
offset,
|
||||
grabbed_id: id,
|
||||
old_curvature: overlay.state.curvature,
|
||||
old_curvature: state.curvature,
|
||||
grab_all: matches!(self.interaction.mode, PointerMode::Right),
|
||||
});
|
||||
overlay.state.positioning = match overlay.state.positioning {
|
||||
state.positioning = match state.positioning {
|
||||
Positioning::FollowHand { hand, lerp } => Positioning::FollowHandPaused { hand, lerp },
|
||||
Positioning::FollowHead { lerp } => Positioning::FollowHeadPaused { lerp },
|
||||
x => x,
|
||||
@@ -531,29 +545,26 @@ impl Pointer {
|
||||
tasks.enqueue(TaskType::Overlay(
|
||||
OverlaySelector::Name(ANCHOR_NAME.clone()),
|
||||
Box::new(|app, o| {
|
||||
o.transform = app.anchor
|
||||
* Affine3A::from_scale_rotation_translation(
|
||||
Vec3::ONE * o.spawn_scale,
|
||||
o.spawn_rotation,
|
||||
o.spawn_point.into(),
|
||||
);
|
||||
o.dirty = true;
|
||||
o.want_visible = true;
|
||||
o.saved_transform = Some(app.anchor);
|
||||
o.activate(app);
|
||||
}),
|
||||
));
|
||||
log::info!("Hand {}: grabbed {}", self.idx, overlay.state.name);
|
||||
}
|
||||
|
||||
fn handle_grabbed<O>(idx: usize, overlay: &mut OverlayData<O>, app: &mut AppState)
|
||||
fn handle_grabbed<O>(idx: usize, overlay: &mut OverlayWindowData<O>, app: &mut AppState)
|
||||
where
|
||||
O: Default,
|
||||
{
|
||||
let mut pointer = &mut app.input_state.pointers[idx];
|
||||
let Some(overlay_state) = overlay.config.active_state.as_mut() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let pointer = &mut app.input_state.pointers[idx];
|
||||
if pointer.now.grab {
|
||||
if let Some(grab_data) = pointer.interaction.grabbed.as_mut() {
|
||||
if pointer.now.click {
|
||||
pointer.interaction.mode = PointerMode::Special;
|
||||
let cur_scale = overlay.state.transform.x_axis.length();
|
||||
let cur_scale = overlay_state.transform.x_axis.length();
|
||||
if cur_scale < 0.1 && pointer.now.scroll_y > 0.0 {
|
||||
return;
|
||||
}
|
||||
@@ -561,24 +572,22 @@ impl Pointer {
|
||||
return;
|
||||
}
|
||||
|
||||
overlay.state.transform.matrix3 = overlay
|
||||
.state
|
||||
overlay_state.transform.matrix3 = overlay_state
|
||||
.transform
|
||||
.matrix3
|
||||
.mul_scalar(0.025f32.mul_add(-pointer.now.scroll_y, 1.0));
|
||||
} else if app.session.config.allow_sliding && pointer.now.scroll_y.is_finite() {
|
||||
grab_data.offset.z -= pointer.now.scroll_y * 0.05;
|
||||
}
|
||||
overlay.state.transform.translation =
|
||||
overlay_state.transform.translation =
|
||||
pointer.pose.transform_point3a(grab_data.offset);
|
||||
overlay.state.realign(&app.input_state.hmd);
|
||||
overlay.state.dirty = true;
|
||||
overlay.config.realign(&app.input_state.hmd);
|
||||
} else {
|
||||
log::error!("Grabbed overlay does not exist");
|
||||
pointer.interaction.grabbed = None;
|
||||
}
|
||||
} else {
|
||||
overlay.state.positioning = match overlay.state.positioning {
|
||||
overlay_state.positioning = match overlay_state.positioning {
|
||||
Positioning::FollowHandPaused { hand, lerp } => {
|
||||
Positioning::FollowHand { hand, lerp }
|
||||
}
|
||||
@@ -586,41 +595,17 @@ impl Pointer {
|
||||
x => x,
|
||||
};
|
||||
|
||||
let save_success = overlay.state.save_transform(app);
|
||||
|
||||
// re-borrow
|
||||
pointer = &mut app.input_state.pointers[idx];
|
||||
|
||||
if save_success {
|
||||
if let Some(grab_data) = pointer.interaction.grabbed.as_ref()
|
||||
&& overlay.state.curvature != grab_data.old_curvature
|
||||
{
|
||||
if let Some(val) = overlay.state.curvature {
|
||||
app.session
|
||||
.config
|
||||
.curve_values
|
||||
.arc_set(overlay.state.name.clone(), val);
|
||||
} else {
|
||||
let ref_name = overlay.state.name.as_ref();
|
||||
app.session.config.curve_values.arc_rm(ref_name);
|
||||
}
|
||||
}
|
||||
app.session.config.transform_values.arc_set(
|
||||
overlay.state.name.clone(),
|
||||
overlay.state.saved_transform.unwrap(), // safe
|
||||
);
|
||||
}
|
||||
|
||||
pointer.interaction.grabbed = None;
|
||||
overlay.config.save_transform(app);
|
||||
|
||||
// Hide anchor
|
||||
app.tasks.enqueue(TaskType::Overlay(
|
||||
OverlaySelector::Name(ANCHOR_NAME.clone()),
|
||||
Box::new(|_app, o| {
|
||||
o.want_visible = false;
|
||||
o.deactivate();
|
||||
}),
|
||||
));
|
||||
log::info!("Hand {}: dropped {}", idx, overlay.state.name);
|
||||
log::debug!("Hand {}: dropped {}", idx, overlay.config.name);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
pub mod common;
|
||||
pub mod input;
|
||||
|
||||
#[cfg(feature = "openvr")]
|
||||
@@ -10,7 +9,23 @@ pub mod openxr;
|
||||
#[cfg(feature = "wayvr")]
|
||||
pub mod wayvr;
|
||||
|
||||
pub mod overlay;
|
||||
pub mod set;
|
||||
|
||||
pub mod task;
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum BackendError {
|
||||
#[error("backend not supported")]
|
||||
NotSupported,
|
||||
#[cfg(feature = "openxr")]
|
||||
#[error("OpenXR Error: {0:?}")]
|
||||
OpenXrError(#[from] ::openxr::sys::Result),
|
||||
#[error("Shutdown")]
|
||||
Shutdown,
|
||||
#[error("Restart")]
|
||||
Restart,
|
||||
#[error("Fatal: {0:?}")]
|
||||
Fatal(#[from] anyhow::Error),
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ use glam::Affine3A;
|
||||
use ovr_overlay::{pose::Matrix3x4, settings::SettingsManager, sys::HmdMatrix34_t};
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::backend::{common::BackendError, task::ColorChannel};
|
||||
use crate::backend::{BackendError, task::ColorChannel};
|
||||
|
||||
pub trait Affine3AConvert {
|
||||
fn from_affine(affine: &Affine3A) -> Self;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::f32::consts::PI;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::Arc;
|
||||
|
||||
use ash::vk::SubmitInfo;
|
||||
use glam::{Affine3A, Vec3, Vec3A, Vec4};
|
||||
@@ -8,7 +8,6 @@ use idmap::IdMap;
|
||||
use ovr_overlay::overlay::OverlayManager;
|
||||
use ovr_overlay::sys::ETrackingUniverseOrigin;
|
||||
use vulkano::{
|
||||
VulkanObject,
|
||||
command_buffer::{
|
||||
CommandBufferBeginInfo, CommandBufferLevel, CommandBufferUsage, RecordingCommandBuffer,
|
||||
},
|
||||
@@ -16,25 +15,26 @@ use vulkano::{
|
||||
image::view::ImageView,
|
||||
image::{Image, ImageLayout},
|
||||
sync::{
|
||||
AccessFlags, DependencyInfo, ImageMemoryBarrier, PipelineStages,
|
||||
fence::{Fence, FenceCreateInfo},
|
||||
AccessFlags, DependencyInfo, ImageMemoryBarrier, PipelineStages,
|
||||
},
|
||||
VulkanObject,
|
||||
};
|
||||
use wgui::gfx::WGfx;
|
||||
|
||||
use crate::backend::input::{Haptics, PointerHit};
|
||||
use crate::backend::overlay::{
|
||||
FrameMeta, OverlayBackend, OverlayData, OverlayState, ShouldRender, Z_ORDER_LINES,
|
||||
};
|
||||
use crate::graphics::CommandBuffers;
|
||||
use crate::state::AppState;
|
||||
use crate::windowing::backend::{FrameMeta, OverlayBackend, ShouldRender};
|
||||
use crate::windowing::window::{OverlayWindowConfig, OverlayWindowData, OverlayWindowState};
|
||||
use crate::windowing::Z_ORDER_LINES;
|
||||
|
||||
use super::overlay::OpenVrOverlayData;
|
||||
|
||||
static LINE_AUTO_INCREMENT: AtomicUsize = AtomicUsize::new(1);
|
||||
|
||||
pub(super) struct LinePool {
|
||||
lines: IdMap<usize, OverlayData<OpenVrOverlayData>>,
|
||||
lines: IdMap<usize, OverlayWindowData<OpenVrOverlayData>>,
|
||||
view: Arc<ImageView>,
|
||||
colors: [Vec4; 5],
|
||||
}
|
||||
@@ -75,12 +75,7 @@ impl LinePool {
|
||||
pub fn allocate(&mut self) -> usize {
|
||||
let id = LINE_AUTO_INCREMENT.fetch_add(1, Ordering::Relaxed);
|
||||
|
||||
let mut data = OverlayData::<OpenVrOverlayData> {
|
||||
state: OverlayState {
|
||||
name: Arc::from(format!("wlx-line{id}")),
|
||||
show_hide: true,
|
||||
..Default::default()
|
||||
},
|
||||
let data = OverlayWindowData::<OpenVrOverlayData> {
|
||||
data: OpenVrOverlayData {
|
||||
width: 0.002,
|
||||
override_width: true,
|
||||
@@ -88,12 +83,17 @@ impl LinePool {
|
||||
image_dirty: true,
|
||||
..Default::default()
|
||||
},
|
||||
..OverlayData::from_backend(Box::new(LineBackend {
|
||||
..OverlayWindowData::from_config(OverlayWindowConfig {
|
||||
name: Arc::from(format!("wlx-line{id}")),
|
||||
default_state: OverlayWindowState {
|
||||
..Default::default()
|
||||
},
|
||||
z_order: Z_ORDER_LINES,
|
||||
..OverlayWindowConfig::from_backend(Box::new(LineBackend {
|
||||
view: self.view.clone(),
|
||||
}))
|
||||
})
|
||||
};
|
||||
data.state.z_order = Z_ORDER_LINES;
|
||||
data.state.dirty = true;
|
||||
|
||||
self.lines.insert(id, data);
|
||||
id
|
||||
@@ -137,8 +137,8 @@ impl LinePool {
|
||||
|
||||
fn draw_transform(&mut self, id: usize, transform: Affine3A, color: Vec4) {
|
||||
if let Some(data) = self.lines.get_mut(id) {
|
||||
data.state.want_visible = true;
|
||||
data.state.transform = transform;
|
||||
data.config.default_state.alpha = 1.0;
|
||||
data.config.default_state.transform = transform;
|
||||
data.data.color = color;
|
||||
} else {
|
||||
log::warn!("Line {id} does not exist");
|
||||
@@ -153,10 +153,10 @@ impl LinePool {
|
||||
) -> anyhow::Result<()> {
|
||||
for data in self.lines.values_mut() {
|
||||
data.after_input(overlay, app)?;
|
||||
if data.state.want_visible {
|
||||
if data.state.dirty {
|
||||
if data.config.default_state.alpha > 0.01 {
|
||||
if data.config.dirty {
|
||||
data.upload_texture(overlay, &app.gfx);
|
||||
data.state.dirty = false;
|
||||
data.config.dirty = false;
|
||||
}
|
||||
|
||||
data.upload_transform(universe.clone(), overlay);
|
||||
@@ -168,7 +168,7 @@ impl LinePool {
|
||||
|
||||
pub fn mark_dirty(&mut self) {
|
||||
for data in self.lines.values_mut() {
|
||||
data.state.dirty = true;
|
||||
data.config.dirty = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ use vulkano::{device::physical::PhysicalDevice, Handle, VulkanObject};
|
||||
|
||||
use crate::{
|
||||
backend::{
|
||||
common::{BackendError, OverlayContainer},
|
||||
input::interact,
|
||||
openvr::{
|
||||
helpers::adjust_gain,
|
||||
@@ -26,8 +25,8 @@ use crate::{
|
||||
manifest::{install_manifest, uninstall_manifest},
|
||||
overlay::OpenVrOverlayData,
|
||||
},
|
||||
overlay::{OverlayData, ShouldRender},
|
||||
task::{SystemTask, TaskType},
|
||||
BackendError,
|
||||
},
|
||||
graphics::{init_openvr_graphics, CommandBuffers},
|
||||
overlays::{
|
||||
@@ -36,6 +35,7 @@ use crate::{
|
||||
},
|
||||
state::AppState,
|
||||
subsystem::notifications::NotificationManager,
|
||||
windowing::{backend::ShouldRender, manager::OverlayWindowManager, window::OverlayWindowData},
|
||||
};
|
||||
|
||||
#[cfg(feature = "wayvr")]
|
||||
@@ -94,13 +94,13 @@ pub fn openvr_run(
|
||||
names.iter().map(std::string::String::as_str).collect()
|
||||
};
|
||||
|
||||
let mut state = {
|
||||
let mut app = {
|
||||
let (gfx, gfx_extras) = init_openvr_graphics(instance_extensions, device_extensions_fn)?;
|
||||
AppState::from_graphics(gfx, gfx_extras)?
|
||||
};
|
||||
|
||||
if show_by_default {
|
||||
state.tasks.enqueue_at(
|
||||
app.tasks.enqueue_at(
|
||||
TaskType::System(SystemTask::ShowHide),
|
||||
Instant::now().add(Duration::from_secs(1)),
|
||||
);
|
||||
@@ -110,13 +110,13 @@ pub fn openvr_run(
|
||||
TrackedDeviceIndex::HMD,
|
||||
ETrackedDeviceProperty::Prop_UserIpdMeters_Float,
|
||||
) {
|
||||
state.input_state.ipd = (ipd * 1000.0).round();
|
||||
log::info!("IPD: {:.0} mm", state.input_state.ipd);
|
||||
app.input_state.ipd = (ipd * 1000.0).round();
|
||||
log::info!("IPD: {:.0} mm", app.input_state.ipd);
|
||||
}
|
||||
|
||||
let _ = install_manifest(&mut app_mgr);
|
||||
|
||||
let mut overlays = OverlayContainer::<OpenVrOverlayData>::new(&mut state, headless)?;
|
||||
let mut overlays = OverlayWindowManager::<OpenVrOverlayData>::new(&mut app, headless)?;
|
||||
let mut notifications = NotificationManager::new();
|
||||
notifications.run_dbus();
|
||||
notifications.run_udp();
|
||||
@@ -139,7 +139,7 @@ pub fn openvr_run(
|
||||
|
||||
log::info!("HMD running @ {refresh_rate} Hz");
|
||||
|
||||
let watch_id = overlays.get_by_name(WATCH_NAME).unwrap().state.id; // want panic
|
||||
let watch_id = overlays.lookup(WATCH_NAME).unwrap(); // want panic
|
||||
|
||||
// want at least half refresh rate
|
||||
let frame_timeout = 2 * (1000.0 / refresh_rate).floor() as u32;
|
||||
@@ -147,7 +147,7 @@ pub fn openvr_run(
|
||||
let mut next_device_update = Instant::now();
|
||||
let mut due_tasks = VecDeque::with_capacity(4);
|
||||
|
||||
let mut lines = LinePool::new(state.gfx.clone())?;
|
||||
let mut lines = LinePool::new(app.gfx.clone())?;
|
||||
let pointer_lines = [lines.allocate(), lines.allocate()];
|
||||
|
||||
'main_loop: loop {
|
||||
@@ -183,12 +183,12 @@ pub fn openvr_run(
|
||||
ETrackedDeviceProperty::Prop_UserIpdMeters_Float,
|
||||
) {
|
||||
let ipd = (ipd * 1000.0).round();
|
||||
if (ipd - state.input_state.ipd).abs() > 0.05 {
|
||||
log::info!("IPD: {:.1} mm -> {:.1} mm", state.input_state.ipd, ipd);
|
||||
if (ipd - app.input_state.ipd).abs() > 0.05 {
|
||||
log::info!("IPD: {:.1} mm -> {:.1} mm", app.input_state.ipd, ipd);
|
||||
Toast::new(ToastTopic::IpdChange, "IPD".into(), format!("{ipd:.1} mm"))
|
||||
.submit(&mut state);
|
||||
.submit(&mut app);
|
||||
}
|
||||
state.input_state.ipd = ipd;
|
||||
app.input_state.ipd = ipd;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
@@ -196,19 +196,19 @@ pub fn openvr_run(
|
||||
}
|
||||
|
||||
if next_device_update <= Instant::now() {
|
||||
input_source.update_devices(&mut system_mgr, &mut state);
|
||||
input_source.update_devices(&mut system_mgr, &mut app);
|
||||
next_device_update = Instant::now() + Duration::from_secs(30);
|
||||
}
|
||||
|
||||
notifications.submit_pending(&mut state);
|
||||
notifications.submit_pending(&mut app);
|
||||
|
||||
state.tasks.retrieve_due(&mut due_tasks);
|
||||
app.tasks.retrieve_due(&mut due_tasks);
|
||||
|
||||
while let Some(task) = due_tasks.pop_front() {
|
||||
match task {
|
||||
TaskType::Overlay(sel, f) => {
|
||||
if let Some(o) = overlays.mut_by_selector(&sel) {
|
||||
f(&mut state, &mut o.state);
|
||||
f(&mut app, &mut o.config);
|
||||
} else {
|
||||
log::warn!("Overlay not found for task: {sel:?}");
|
||||
}
|
||||
@@ -218,19 +218,21 @@ pub fn openvr_run(
|
||||
continue;
|
||||
};
|
||||
|
||||
let Some((mut state, backend)) = f(&mut state) else {
|
||||
let Some(overlay_config) = f(&mut app) else {
|
||||
continue;
|
||||
};
|
||||
state.birthframe = cur_frame;
|
||||
|
||||
overlays.add(OverlayData {
|
||||
state,
|
||||
..OverlayData::from_backend(backend)
|
||||
});
|
||||
overlays.add(
|
||||
OverlayWindowData {
|
||||
birthframe: cur_frame,
|
||||
..OverlayWindowData::from_config(overlay_config)
|
||||
},
|
||||
&mut app,
|
||||
);
|
||||
}
|
||||
TaskType::DropOverlay(sel) => {
|
||||
if let Some(o) = overlays.mut_by_selector(&sel)
|
||||
&& o.state.birthframe < cur_frame
|
||||
&& o.birthframe < cur_frame
|
||||
{
|
||||
o.destroy(&mut overlay_mgr);
|
||||
overlays.remove_by_selector(&sel);
|
||||
@@ -241,91 +243,89 @@ pub fn openvr_run(
|
||||
let _ = adjust_gain(&mut settings_mgr, channel, value);
|
||||
}
|
||||
SystemTask::FixFloor => {
|
||||
playspace.fix_floor(&mut chaperone_mgr, &state.input_state);
|
||||
playspace.fix_floor(&mut chaperone_mgr, &app.input_state);
|
||||
}
|
||||
SystemTask::ResetPlayspace => {
|
||||
playspace.reset_offset(&mut chaperone_mgr, &state.input_state);
|
||||
playspace.reset_offset(&mut chaperone_mgr, &app.input_state);
|
||||
}
|
||||
SystemTask::ShowHide => {
|
||||
overlays.show_hide(&mut state);
|
||||
overlays.show_hide(&mut app);
|
||||
}
|
||||
},
|
||||
TaskType::ToggleSet(set) => {
|
||||
overlays.switch_or_toggle_set(&mut app, set);
|
||||
}
|
||||
#[cfg(feature = "wayvr")]
|
||||
TaskType::WayVR(action) => {
|
||||
wayvr_action(&mut state, &mut overlays, &action);
|
||||
wayvr_action(&mut app, &mut overlays, &action);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let universe = playspace.get_universe();
|
||||
|
||||
state.input_state.pre_update();
|
||||
input_source.update(
|
||||
universe.clone(),
|
||||
&mut input_mgr,
|
||||
&mut system_mgr,
|
||||
&mut state,
|
||||
);
|
||||
state.input_state.post_update(&state.session);
|
||||
app.input_state.pre_update();
|
||||
input_source.update(universe.clone(), &mut input_mgr, &mut system_mgr, &mut app);
|
||||
app.input_state.post_update(&app.session);
|
||||
|
||||
if state
|
||||
if app
|
||||
.input_state
|
||||
.pointers
|
||||
.iter()
|
||||
.any(|p| p.now.show_hide && !p.before.show_hide)
|
||||
{
|
||||
lines.mark_dirty(); // workaround to prevent lines from not showing
|
||||
overlays.show_hide(&mut state);
|
||||
overlays.show_hide(&mut app);
|
||||
}
|
||||
|
||||
#[cfg(feature = "wayvr")]
|
||||
if state
|
||||
if app
|
||||
.input_state
|
||||
.pointers
|
||||
.iter()
|
||||
.any(|p| p.now.toggle_dashboard && !p.before.toggle_dashboard)
|
||||
{
|
||||
wayvr_action(&mut state, &mut overlays, &WayVRAction::ToggleDashboard);
|
||||
wayvr_action(&mut app, &mut overlays, &WayVRAction::ToggleDashboard);
|
||||
}
|
||||
|
||||
overlays
|
||||
.values_mut()
|
||||
.for_each(|o| o.state.auto_movement(&mut state));
|
||||
.for_each(|o| o.config.auto_movement(&mut app));
|
||||
|
||||
watch_fade(&mut state, overlays.mut_by_id(watch_id).unwrap()); // want panic
|
||||
playspace.update(&mut chaperone_mgr, &mut overlays, &state);
|
||||
watch_fade(&mut app, overlays.mut_by_id(watch_id).unwrap()); // want panic
|
||||
playspace.update(&mut chaperone_mgr, &mut overlays, &app);
|
||||
|
||||
let lengths_haptics = interact(&mut overlays, &mut state);
|
||||
let lengths_haptics = interact(&mut overlays, &mut app);
|
||||
for (idx, (len, haptics)) in lengths_haptics.iter().enumerate() {
|
||||
lines.draw_from(
|
||||
pointer_lines[idx],
|
||||
state.input_state.pointers[idx].pose,
|
||||
app.input_state.pointers[idx].pose,
|
||||
*len,
|
||||
state.input_state.pointers[idx].interaction.mode as usize + 1,
|
||||
&state.input_state.hmd,
|
||||
app.input_state.pointers[idx].interaction.mode as usize + 1,
|
||||
&app.input_state.hmd,
|
||||
);
|
||||
if let Some(haptics) = haptics {
|
||||
input_source.haptics(&mut input_mgr, idx, haptics);
|
||||
}
|
||||
}
|
||||
|
||||
state.hid_provider.inner.commit();
|
||||
app.hid_provider.inner.commit();
|
||||
let mut buffers = CommandBuffers::default();
|
||||
|
||||
lines.update(universe.clone(), &mut overlay_mgr, &mut state)?;
|
||||
lines.update(universe.clone(), &mut overlay_mgr, &mut app)?;
|
||||
|
||||
for o in overlays.values_mut() {
|
||||
o.after_input(&mut overlay_mgr, &mut state)?;
|
||||
o.after_input(&mut overlay_mgr, &mut app)?;
|
||||
}
|
||||
|
||||
#[cfg(feature = "osc")]
|
||||
if let Some(ref mut sender) = state.osc_sender {
|
||||
let _ = sender.send_params(&overlays, &state.input_state.devices);
|
||||
if let Some(ref mut sender) = app.osc_sender {
|
||||
let _ = sender.send_params(&overlays, &app.input_state.devices);
|
||||
}
|
||||
|
||||
#[cfg(feature = "wayvr")]
|
||||
if let Err(e) =
|
||||
crate::overlays::wayvr::tick_events::<OpenVrOverlayData>(&mut state, &mut overlays)
|
||||
crate::overlays::wayvr::tick_events::<OpenVrOverlayData>(&mut app, &mut overlays)
|
||||
{
|
||||
log::error!("WayVR tick_events failed: {e:?}");
|
||||
}
|
||||
@@ -333,15 +333,15 @@ pub fn openvr_run(
|
||||
log::trace!("Rendering frame");
|
||||
|
||||
for o in overlays.values_mut() {
|
||||
if o.state.want_visible {
|
||||
let ShouldRender::Should = o.should_render(&mut state)? else {
|
||||
if o.config.active_state.is_some() {
|
||||
let ShouldRender::Should = o.should_render(&mut app)? else {
|
||||
continue;
|
||||
};
|
||||
if !o.ensure_image_allocated(&mut state)? {
|
||||
if !o.ensure_image_allocated(&mut app)? {
|
||||
continue;
|
||||
}
|
||||
o.data.image_dirty = o.render(
|
||||
&mut state,
|
||||
&mut app,
|
||||
o.data.image_view.as_ref().unwrap().clone(),
|
||||
&mut buffers,
|
||||
1.0, // alpha is instead set using OVR API
|
||||
@@ -351,7 +351,7 @@ pub fn openvr_run(
|
||||
|
||||
log::trace!("Rendering overlays");
|
||||
|
||||
if let Some(mut future) = buffers.execute_now(state.gfx.queue_gfx.clone())? {
|
||||
if let Some(mut future) = buffers.execute_now(app.gfx.queue_gfx.clone())? {
|
||||
if let Err(e) = future.flush() {
|
||||
return Err(BackendError::Fatal(e.into()));
|
||||
}
|
||||
@@ -360,10 +360,10 @@ pub fn openvr_run(
|
||||
|
||||
overlays
|
||||
.values_mut()
|
||||
.for_each(|o| o.after_render(universe.clone(), &mut overlay_mgr, &state.gfx));
|
||||
.for_each(|o| o.after_render(universe.clone(), &mut overlay_mgr, &app.gfx));
|
||||
|
||||
#[cfg(feature = "wayvr")]
|
||||
if let Some(wayvr) = &state.wayvr {
|
||||
if let Some(wayvr) = &app.wayvr {
|
||||
wayvr.borrow_mut().data.tick_finish()?;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ use vulkano::{
|
||||
};
|
||||
use wgui::gfx::WGfx;
|
||||
|
||||
use crate::{backend::overlay::OverlayData, state::AppState};
|
||||
use crate::{state::AppState, windowing::window::OverlayWindowData};
|
||||
|
||||
use super::helpers::Affine3AConvert;
|
||||
|
||||
@@ -28,13 +28,13 @@ pub(super) struct OpenVrOverlayData {
|
||||
pub(super) image_dirty: bool,
|
||||
}
|
||||
|
||||
impl OverlayData<OpenVrOverlayData> {
|
||||
impl OverlayWindowData<OpenVrOverlayData> {
|
||||
pub(super) fn initialize(
|
||||
&mut self,
|
||||
overlay: &mut OverlayManager,
|
||||
app: &mut AppState,
|
||||
) -> anyhow::Result<OverlayHandle> {
|
||||
let key = format!("wlx-{}", self.state.name);
|
||||
let key = format!("wlx-{}", self.config.name);
|
||||
log::debug!("Create overlay with key: {}", &key);
|
||||
let handle = match overlay.create_overlay(&key, &key) {
|
||||
Ok(handle) => handle,
|
||||
@@ -42,7 +42,7 @@ impl OverlayData<OpenVrOverlayData> {
|
||||
panic!("Failed to create overlay: {e}");
|
||||
}
|
||||
};
|
||||
log::debug!("{}: initialize", self.state.name);
|
||||
log::debug!("{}: initialize", self.config.name);
|
||||
|
||||
self.data.handle = Some(handle);
|
||||
self.data.color = Vec4::ONE;
|
||||
@@ -66,7 +66,7 @@ impl OverlayData<OpenVrOverlayData> {
|
||||
if self.data.image_view.is_some() {
|
||||
return Ok(true);
|
||||
}
|
||||
let Some(meta) = self.backend.frame_meta() else {
|
||||
let Some(meta) = self.config.backend.frame_meta() else {
|
||||
return Ok(false);
|
||||
};
|
||||
let image = app.gfx.new_image(
|
||||
@@ -84,9 +84,16 @@ impl OverlayData<OpenVrOverlayData> {
|
||||
overlay: &mut OverlayManager,
|
||||
app: &mut AppState,
|
||||
) -> anyhow::Result<()> {
|
||||
if self.state.want_visible && !self.data.visible {
|
||||
let want_visible = self
|
||||
.config
|
||||
.active_state
|
||||
.as_ref()
|
||||
.map(|x| x.alpha > 0.05)
|
||||
.unwrap_or(false);
|
||||
|
||||
if want_visible && !self.data.visible {
|
||||
self.show_internal(overlay, app)?;
|
||||
} else if !self.state.want_visible && self.data.visible {
|
||||
} else if !want_visible && self.data.visible {
|
||||
self.hide_internal(overlay, app)?;
|
||||
}
|
||||
Ok(())
|
||||
@@ -99,12 +106,12 @@ impl OverlayData<OpenVrOverlayData> {
|
||||
graphics: &WGfx,
|
||||
) {
|
||||
if self.data.visible {
|
||||
if self.state.dirty {
|
||||
if self.config.dirty {
|
||||
self.upload_curvature(overlay);
|
||||
|
||||
self.upload_transform(universe, overlay);
|
||||
self.upload_alpha(overlay);
|
||||
self.state.dirty = false;
|
||||
self.config.dirty = false;
|
||||
}
|
||||
self.upload_texture(overlay, graphics);
|
||||
}
|
||||
@@ -119,12 +126,12 @@ impl OverlayData<OpenVrOverlayData> {
|
||||
Some(handle) => handle,
|
||||
None => self.initialize(overlay, app)?,
|
||||
};
|
||||
log::debug!("{}: show", self.state.name);
|
||||
log::debug!("{}: show", self.config.name);
|
||||
if let Err(e) = overlay.set_visibility(handle, true) {
|
||||
log::error!("{}: Failed to show overlay: {}", self.state.name, e);
|
||||
log::error!("{}: Failed to show overlay: {}", self.config.name, e);
|
||||
}
|
||||
self.data.visible = true;
|
||||
self.backend.resume(app)
|
||||
self.config.backend.resume(app)
|
||||
}
|
||||
|
||||
fn hide_internal(
|
||||
@@ -135,27 +142,30 @@ impl OverlayData<OpenVrOverlayData> {
|
||||
let Some(handle) = self.data.handle else {
|
||||
return Ok(());
|
||||
};
|
||||
log::debug!("{}: hide", self.state.name);
|
||||
log::debug!("{}: hide", self.config.name);
|
||||
if let Err(e) = overlay.set_visibility(handle, false) {
|
||||
log::error!("{}: Failed to hide overlay: {}", self.state.name, e);
|
||||
log::error!("{}: Failed to hide overlay: {}", self.config.name, e);
|
||||
}
|
||||
self.data.visible = false;
|
||||
self.backend.pause(app)
|
||||
self.config.backend.pause(app)
|
||||
}
|
||||
|
||||
pub(super) fn upload_alpha(&self, overlay: &mut OverlayManager) {
|
||||
let Some(handle) = self.data.handle else {
|
||||
log::debug!("{}: No overlay handle", self.state.name);
|
||||
log::debug!("{}: No overlay handle", self.config.name);
|
||||
return;
|
||||
};
|
||||
if let Err(e) = overlay.set_opacity(handle, self.state.alpha) {
|
||||
log::error!("{}: Failed to set overlay alpha: {}", self.state.name, e);
|
||||
let Some(state) = self.config.active_state.as_ref() else {
|
||||
return;
|
||||
};
|
||||
if let Err(e) = overlay.set_opacity(handle, state.alpha) {
|
||||
log::error!("{}: Failed to set overlay alpha: {}", self.config.name, e);
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn upload_color(&self, overlay: &mut OverlayManager) {
|
||||
let Some(handle) = self.data.handle else {
|
||||
log::debug!("{}: No overlay handle", self.state.name);
|
||||
log::debug!("{}: No overlay handle", self.config.name);
|
||||
return;
|
||||
};
|
||||
if let Err(e) = overlay.set_tint(
|
||||
@@ -167,29 +177,37 @@ impl OverlayData<OpenVrOverlayData> {
|
||||
a: self.data.color.w,
|
||||
},
|
||||
) {
|
||||
log::error!("{}: Failed to set overlay tint: {}", self.state.name, e);
|
||||
log::error!("{}: Failed to set overlay tint: {}", self.config.name, e);
|
||||
}
|
||||
}
|
||||
|
||||
fn upload_width(&self, overlay: &mut OverlayManager) {
|
||||
let Some(handle) = self.data.handle else {
|
||||
log::debug!("{}: No overlay handle", self.state.name);
|
||||
log::debug!("{}: No overlay handle", self.config.name);
|
||||
return;
|
||||
};
|
||||
if let Err(e) = overlay.set_width(handle, self.data.width) {
|
||||
log::error!("{}: Failed to set overlay width: {}", self.state.name, e);
|
||||
log::error!("{}: Failed to set overlay width: {}", self.config.name, e);
|
||||
}
|
||||
}
|
||||
|
||||
fn upload_curvature(&self, overlay: &mut OverlayManager) {
|
||||
let Some(handle) = self.data.handle else {
|
||||
log::debug!("{}: No overlay handle", self.state.name);
|
||||
log::debug!("{}: No overlay handle", self.config.name);
|
||||
return;
|
||||
};
|
||||
if let Err(e) = overlay.set_curvature(handle, self.state.curvature.unwrap_or(0.0)) {
|
||||
if let Err(e) = overlay.set_curvature(
|
||||
handle,
|
||||
self.config
|
||||
.active_state
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.curvature
|
||||
.unwrap_or(0.0),
|
||||
) {
|
||||
log::error!(
|
||||
"{}: Failed to set overlay curvature: {}",
|
||||
self.state.name,
|
||||
self.config.name,
|
||||
e
|
||||
);
|
||||
}
|
||||
@@ -197,11 +215,11 @@ impl OverlayData<OpenVrOverlayData> {
|
||||
|
||||
fn upload_sort_order(&self, overlay: &mut OverlayManager) {
|
||||
let Some(handle) = self.data.handle else {
|
||||
log::debug!("{}: No overlay handle", self.state.name);
|
||||
log::debug!("{}: No overlay handle", self.config.name);
|
||||
return;
|
||||
};
|
||||
if let Err(e) = overlay.set_sort_order(handle, self.state.z_order) {
|
||||
log::error!("{}: Failed to set overlay z order: {}", self.state.name, e);
|
||||
if let Err(e) = overlay.set_sort_order(handle, self.config.z_order) {
|
||||
log::error!("{}: Failed to set overlay z order: {}", self.config.name, e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,12 +229,16 @@ impl OverlayData<OpenVrOverlayData> {
|
||||
overlay: &mut OverlayManager,
|
||||
) {
|
||||
let Some(handle) = self.data.handle else {
|
||||
log::debug!("{}: No overlay handle", self.state.name);
|
||||
log::debug!("{}: No overlay handle", self.config.name);
|
||||
return;
|
||||
};
|
||||
let Some(state) = self.config.active_state.as_ref() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let effective = self.state.transform
|
||||
let effective = state.transform
|
||||
* self
|
||||
.config
|
||||
.backend
|
||||
.frame_meta()
|
||||
.map(|f| f.transform)
|
||||
@@ -227,7 +249,7 @@ impl OverlayData<OpenVrOverlayData> {
|
||||
if let Err(e) = overlay.set_transform_absolute(handle, universe, &transform) {
|
||||
log::error!(
|
||||
"{}: Failed to set overlay transform: {}",
|
||||
self.state.name,
|
||||
self.config.name,
|
||||
e
|
||||
);
|
||||
}
|
||||
@@ -235,12 +257,12 @@ impl OverlayData<OpenVrOverlayData> {
|
||||
|
||||
pub(super) fn upload_texture(&mut self, overlay: &mut OverlayManager, graphics: &WGfx) {
|
||||
let Some(handle) = self.data.handle else {
|
||||
log::debug!("{}: No overlay handle", self.state.name);
|
||||
log::debug!("{}: No overlay handle", self.config.name);
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(view) = self.data.image_view.as_ref() else {
|
||||
log::debug!("{}: Not rendered", self.state.name);
|
||||
log::debug!("{}: Not rendered", self.config.name);
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -254,7 +276,7 @@ impl OverlayData<OpenVrOverlayData> {
|
||||
if !self.data.override_width {
|
||||
let new_width = ((dimensions[0] as f32) / (dimensions[1] as f32)).min(1.0);
|
||||
if (new_width - self.data.width).abs() > f32::EPSILON {
|
||||
log::info!("{}: New width {}", self.state.name, new_width);
|
||||
log::info!("{}: New width {}", self.config.name, new_width);
|
||||
self.data.width = new_width;
|
||||
self.upload_width(overlay);
|
||||
}
|
||||
@@ -277,22 +299,22 @@ impl OverlayData<OpenVrOverlayData> {
|
||||
};
|
||||
log::trace!(
|
||||
"{}: UploadTex {:?}, {}x{}, {:?}",
|
||||
self.state.name,
|
||||
self.config.name,
|
||||
format,
|
||||
texture.m_nWidth,
|
||||
texture.m_nHeight,
|
||||
image.usage()
|
||||
);
|
||||
if let Err(e) = overlay.set_image_vulkan(handle, &mut texture) {
|
||||
log::error!("{}: Failed to set overlay texture: {}", self.state.name, e);
|
||||
log::error!("{}: Failed to set overlay texture: {}", self.config.name, e);
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn destroy(&mut self, overlay: &mut OverlayManager) {
|
||||
if let Some(handle) = self.data.handle {
|
||||
log::debug!("{}: destroy", self.state.name);
|
||||
log::debug!("{}: destroy", self.config.name);
|
||||
if let Err(e) = overlay.destroy_overlay(handle) {
|
||||
log::error!("{}: Failed to destroy overlay: {}", self.state.name, e);
|
||||
log::error!("{}: Failed to destroy overlay: {}", self.config.name, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,8 +6,7 @@ use ovr_overlay::{
|
||||
};
|
||||
|
||||
use crate::{
|
||||
backend::{common::OverlayContainer, input::InputState},
|
||||
state::AppState,
|
||||
backend::input::InputState, state::AppState, windowing::manager::OverlayWindowManager,
|
||||
};
|
||||
|
||||
use super::{helpers::Affine3AConvert, overlay::OpenVrOverlayData};
|
||||
@@ -37,7 +36,7 @@ impl PlayspaceMover {
|
||||
pub fn update(
|
||||
&mut self,
|
||||
chaperone_mgr: &mut ChaperoneSetupManager,
|
||||
overlays: &mut OverlayContainer<OpenVrOverlayData>,
|
||||
overlays: &mut OverlayWindowManager<OpenVrOverlayData>,
|
||||
state: &AppState,
|
||||
) {
|
||||
let universe = self.universe.clone();
|
||||
@@ -68,14 +67,6 @@ impl PlayspaceMover {
|
||||
overlay_transform.translation = offset;
|
||||
space_transform.translation = offset;
|
||||
|
||||
overlays.values_mut().for_each(|overlay| {
|
||||
if overlay.state.grabbable {
|
||||
overlay.state.dirty = true;
|
||||
overlay.state.transform.translation =
|
||||
overlay_transform.transform_point3a(overlay.state.transform.translation);
|
||||
}
|
||||
});
|
||||
|
||||
data.pose *= space_transform;
|
||||
data.hand_pose = new_hand;
|
||||
|
||||
@@ -126,9 +117,12 @@ impl PlayspaceMover {
|
||||
let overlay_offset = data.pose.inverse().transform_vector3a(relative_pos) * -1.0;
|
||||
|
||||
overlays.values_mut().for_each(|overlay| {
|
||||
if overlay.state.grabbable {
|
||||
overlay.state.dirty = true;
|
||||
overlay.state.transform.translation += overlay_offset;
|
||||
let Some(state) = overlay.config.active_state.as_mut() else {
|
||||
return;
|
||||
};
|
||||
if state.positioning.moves_with_space() {
|
||||
state.transform.translation += overlay_offset;
|
||||
overlay.config.dirty = true;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use libmonado::{ClientState, Monado};
|
||||
use log::{info, warn};
|
||||
|
||||
use crate::{backend::overlay::OverlayID, state::AppState};
|
||||
use crate::{state::AppState, windowing::OverlayID};
|
||||
|
||||
pub(super) struct InputBlocker {
|
||||
hovered_last_frame: bool,
|
||||
|
||||
@@ -17,11 +17,10 @@ use vulkano::{Handle, VulkanObject};
|
||||
|
||||
use crate::{
|
||||
backend::{
|
||||
common::{BackendError, OverlayContainer},
|
||||
input::interact,
|
||||
openxr::{lines::LinePool, overlay::OpenXrOverlayData},
|
||||
overlay::{OverlayData, ShouldRender},
|
||||
task::{SystemTask, TaskType},
|
||||
BackendError,
|
||||
},
|
||||
graphics::{init_openxr_graphics, CommandBuffers},
|
||||
overlays::{
|
||||
@@ -30,6 +29,7 @@ use crate::{
|
||||
},
|
||||
state::AppState,
|
||||
subsystem::notifications::NotificationManager,
|
||||
windowing::{backend::ShouldRender, manager::OverlayWindowManager, window::OverlayWindowData},
|
||||
};
|
||||
|
||||
#[cfg(feature = "wayvr")]
|
||||
@@ -94,7 +94,7 @@ pub fn openxr_run(
|
||||
);
|
||||
}
|
||||
|
||||
let mut overlays = OverlayContainer::<OpenXrOverlayData>::new(&mut app, headless)?;
|
||||
let mut overlays = OverlayWindowManager::<OpenXrOverlayData>::new(&mut app, headless)?;
|
||||
let mut lines = LinePool::new(&app)?;
|
||||
|
||||
let mut notifications = NotificationManager::new();
|
||||
@@ -336,7 +336,7 @@ pub fn openxr_run(
|
||||
|
||||
overlays
|
||||
.values_mut()
|
||||
.for_each(|o| o.state.auto_movement(&mut app));
|
||||
.for_each(|o| o.config.auto_movement(&mut app));
|
||||
|
||||
let lengths_haptics = interact(&mut overlays, &mut app);
|
||||
for (idx, (len, haptics)) in lengths_haptics.iter().enumerate() {
|
||||
@@ -355,10 +355,11 @@ pub fn openxr_run(
|
||||
app.hid_provider.inner.commit();
|
||||
|
||||
let watch = overlays.mut_by_id(watch_id).unwrap(); // want panic
|
||||
let watch_transform = watch.state.transform;
|
||||
if !watch.state.want_visible {
|
||||
watch.state.want_visible = true;
|
||||
watch.state.transform = Affine3A::from_scale(Vec3 {
|
||||
let watch_state = watch.config.active_state.as_mut().unwrap();
|
||||
let watch_transform = watch_state.transform;
|
||||
if watch_state.alpha < 0.05 {
|
||||
//FIXME: Temporary workaround for Monado bug
|
||||
watch_state.transform = Affine3A::from_scale(Vec3 {
|
||||
x: 0.001,
|
||||
y: 0.001,
|
||||
z: 0.001,
|
||||
@@ -381,9 +382,9 @@ pub fn openxr_run(
|
||||
|
||||
for o in overlays.values_mut() {
|
||||
o.data.cur_visible = false;
|
||||
if !o.state.want_visible {
|
||||
let Some(alpha) = o.config.active_state.as_ref().map(|x| x.alpha) else {
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
if !o.data.init {
|
||||
o.init(&mut app)?;
|
||||
@@ -392,7 +393,7 @@ pub fn openxr_run(
|
||||
|
||||
let should_render = match o.should_render(&mut app)? {
|
||||
ShouldRender::Should => true,
|
||||
ShouldRender::Can => (o.data.last_alpha - o.state.alpha).abs() > f32::EPSILON,
|
||||
ShouldRender::Can => (o.data.last_alpha - alpha).abs() > f32::EPSILON,
|
||||
ShouldRender::Unable => false, //try show old image if exists
|
||||
};
|
||||
|
||||
@@ -401,11 +402,11 @@ pub fn openxr_run(
|
||||
continue;
|
||||
}
|
||||
let tgt = o.data.swapchain.as_mut().unwrap().acquire_wait_image()?; // want
|
||||
if !o.render(&mut app, tgt, &mut buffers, o.state.alpha)? {
|
||||
if !o.render(&mut app, tgt, &mut buffers, alpha)? {
|
||||
o.data.swapchain.as_mut().unwrap().ensure_image_released()?; // want
|
||||
continue;
|
||||
}
|
||||
o.data.last_alpha = o.state.alpha;
|
||||
o.data.last_alpha = alpha;
|
||||
} else if o.data.swapchain.is_none() {
|
||||
continue;
|
||||
}
|
||||
@@ -435,9 +436,11 @@ pub fn openxr_run(
|
||||
if !o.data.cur_visible {
|
||||
continue;
|
||||
}
|
||||
let dist_sq = (app.input_state.hmd.translation - o.state.transform.translation)
|
||||
// unwrap: above if only passes if active_state is some
|
||||
let active_state = o.config.active_state.as_ref().unwrap();
|
||||
let dist_sq = (app.input_state.hmd.translation - active_state.transform.translation)
|
||||
.length_squared()
|
||||
+ (100f32 - o.state.z_order as f32);
|
||||
+ (100f32 - o.config.z_order as f32);
|
||||
if !dist_sq.is_normal() {
|
||||
o.data.swapchain.as_mut().unwrap().ensure_image_released()?;
|
||||
continue;
|
||||
@@ -489,7 +492,7 @@ pub fn openxr_run(
|
||||
match task {
|
||||
TaskType::Overlay(sel, f) => {
|
||||
if let Some(o) = overlays.mut_by_selector(&sel) {
|
||||
f(&mut app, &mut o.state);
|
||||
f(&mut app, &mut o.config);
|
||||
} else {
|
||||
log::warn!("Overlay not found for task: {sel:?}");
|
||||
}
|
||||
@@ -498,22 +501,22 @@ pub fn openxr_run(
|
||||
let None = overlays.mut_by_selector(&sel) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let Some((mut overlay_state, overlay_backend)) = f(&mut app) else {
|
||||
let Some(overlay_config) = f(&mut app) else {
|
||||
continue;
|
||||
};
|
||||
overlay_state.birthframe = cur_frame;
|
||||
|
||||
overlays.add(OverlayData {
|
||||
state: overlay_state,
|
||||
..OverlayData::from_backend(overlay_backend)
|
||||
});
|
||||
overlays.add(
|
||||
OverlayWindowData {
|
||||
birthframe: cur_frame,
|
||||
..OverlayWindowData::from_config(overlay_config)
|
||||
},
|
||||
&mut app,
|
||||
);
|
||||
}
|
||||
TaskType::DropOverlay(sel) => {
|
||||
if let Some(o) = overlays.mut_by_selector(&sel)
|
||||
&& o.state.birthframe < cur_frame
|
||||
&& o.birthframe < cur_frame
|
||||
{
|
||||
log::debug!("{}: destroy", o.state.name);
|
||||
log::debug!("{}: destroy", o.config.name);
|
||||
if let Some(o) = overlays.remove_by_selector(&sel) {
|
||||
// set for deletion after all images are done showing
|
||||
delete_queue.push((o, cur_frame + 5));
|
||||
@@ -539,6 +542,9 @@ pub fn openxr_run(
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
TaskType::ToggleSet(set) => {
|
||||
overlays.switch_or_toggle_set(&mut app, set);
|
||||
}
|
||||
#[cfg(feature = "wayvr")]
|
||||
TaskType::WayVR(action) => {
|
||||
wayvr_action(&mut app, &mut overlays, &action);
|
||||
@@ -548,8 +554,9 @@ pub fn openxr_run(
|
||||
|
||||
delete_queue.retain(|(_, frame)| *frame > cur_frame);
|
||||
|
||||
//FIXME: Temporary workaround for Monado bug
|
||||
let watch = overlays.mut_by_id(watch_id).unwrap(); // want panic
|
||||
watch.state.transform = watch_transform;
|
||||
watch.config.active_state.as_mut().unwrap().transform = watch_transform;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -5,11 +5,9 @@ use xr::EyeVisibility;
|
||||
|
||||
use super::{CompositionLayer, XrState, helpers, swapchain::WlxSwapchain};
|
||||
use crate::{
|
||||
backend::{
|
||||
openxr::swapchain::{SwapchainOpts, create_swapchain},
|
||||
overlay::OverlayData,
|
||||
},
|
||||
backend::openxr::swapchain::{SwapchainOpts, create_swapchain},
|
||||
state::AppState,
|
||||
windowing::window::OverlayWindowData,
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
@@ -21,7 +19,7 @@ pub struct OpenXrOverlayData {
|
||||
pub(super) last_alpha: f32,
|
||||
}
|
||||
|
||||
impl OverlayData<OpenXrOverlayData> {
|
||||
impl OverlayWindowData<OpenXrOverlayData> {
|
||||
pub(super) fn ensure_swapchain<'a>(
|
||||
&'a mut self,
|
||||
app: &AppState,
|
||||
@@ -30,7 +28,7 @@ impl OverlayData<OpenXrOverlayData> {
|
||||
let Some(meta) = self.frame_meta() else {
|
||||
log::warn!(
|
||||
"{}: swapchain cannot be created due to missing metadata",
|
||||
self.state.name
|
||||
self.config.name
|
||||
);
|
||||
return Ok(false);
|
||||
};
|
||||
@@ -46,7 +44,7 @@ impl OverlayData<OpenXrOverlayData> {
|
||||
|
||||
log::debug!(
|
||||
"{}: recreating swapchain at {}x{}",
|
||||
self.state.name,
|
||||
self.config.name,
|
||||
meta.extent[0],
|
||||
meta.extent[1],
|
||||
);
|
||||
@@ -64,17 +62,20 @@ impl OverlayData<OpenXrOverlayData> {
|
||||
xr: &'a XrState,
|
||||
) -> anyhow::Result<CompositionLayer<'a>> {
|
||||
let Some(swapchain) = self.data.swapchain.as_mut() else {
|
||||
log::warn!("{}: swapchain not ready", self.state.name);
|
||||
log::warn!("{}: swapchain not ready", self.config.name);
|
||||
return Ok(CompositionLayer::None);
|
||||
};
|
||||
if !swapchain.ever_acquired {
|
||||
log::warn!("{}: swapchain not rendered", self.state.name);
|
||||
log::warn!("{}: swapchain not rendered", self.config.name);
|
||||
return Ok(CompositionLayer::None);
|
||||
}
|
||||
swapchain.ensure_image_released()?;
|
||||
|
||||
// overlays without active_state don't get queued for present
|
||||
let state = self.config.active_state.as_ref().unwrap();
|
||||
|
||||
let sub_image = swapchain.get_subimage();
|
||||
let transform = self.state.transform * self.backend.frame_meta().unwrap().transform; // contract
|
||||
let transform = state.transform * self.config.backend.frame_meta().unwrap().transform; // contract
|
||||
|
||||
let aspect_ratio = swapchain.extent[1] as f32 / swapchain.extent[0] as f32;
|
||||
let (scale_x, scale_y) = if aspect_ratio < 1.0 {
|
||||
@@ -85,7 +86,7 @@ impl OverlayData<OpenXrOverlayData> {
|
||||
(major / aspect_ratio, major)
|
||||
};
|
||||
|
||||
if let Some(curvature) = self.state.curvature {
|
||||
if let Some(curvature) = state.curvature {
|
||||
let radius = scale_x / (2.0 * PI * curvature);
|
||||
let quat = helpers::transform_to_norm_quat(&transform);
|
||||
let center_point = transform.translation + quat.mul_vec3a(Vec3A::Z * radius);
|
||||
@@ -120,14 +121,21 @@ impl OverlayData<OpenXrOverlayData> {
|
||||
}
|
||||
|
||||
pub(super) fn after_input(&mut self, app: &mut AppState) -> anyhow::Result<()> {
|
||||
if self.data.last_visible != self.state.want_visible {
|
||||
if self.state.want_visible {
|
||||
self.backend.resume(app)?;
|
||||
let want_visible = self
|
||||
.config
|
||||
.active_state
|
||||
.as_ref()
|
||||
.map(|x| x.alpha > 0.05)
|
||||
.unwrap_or(false);
|
||||
|
||||
if self.data.last_visible != want_visible {
|
||||
if want_visible {
|
||||
self.config.backend.resume(app)?;
|
||||
} else {
|
||||
self.backend.pause(app)?;
|
||||
self.config.backend.pause(app)?;
|
||||
}
|
||||
}
|
||||
self.data.last_visible = self.state.want_visible;
|
||||
self.data.last_visible = want_visible;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,7 @@ use glam::{Affine3A, Quat, Vec3A};
|
||||
use libmonado::{Monado, Pose, ReferenceSpaceType};
|
||||
|
||||
use crate::{
|
||||
backend::{common::OverlayContainer, input::InputState},
|
||||
state::AppState,
|
||||
backend::input::InputState, state::AppState, windowing::manager::OverlayWindowManager,
|
||||
};
|
||||
|
||||
use super::overlay::OpenXrOverlayData;
|
||||
@@ -44,7 +43,7 @@ impl PlayspaceMover {
|
||||
|
||||
pub fn update(
|
||||
&mut self,
|
||||
overlays: &mut OverlayContainer<OpenXrOverlayData>,
|
||||
overlays: &mut OverlayWindowManager<OpenXrOverlayData>,
|
||||
state: &AppState,
|
||||
monado: &mut Monado,
|
||||
) {
|
||||
@@ -129,10 +128,13 @@ impl PlayspaceMover {
|
||||
let overlay_offset = data.pose.inverse().transform_vector3a(relative_pos) * -1.0;
|
||||
|
||||
overlays.values_mut().for_each(|overlay| {
|
||||
if overlay.state.grabbable {
|
||||
overlay.state.dirty = true;
|
||||
overlay.state.transform.translation += overlay_offset;
|
||||
let Some(state) = overlay.config.active_state.as_mut() else {
|
||||
return;
|
||||
};
|
||||
if state.positioning.moves_with_space() {
|
||||
state.transform.translation += overlay_offset;
|
||||
}
|
||||
overlay.config.dirty = true;
|
||||
});
|
||||
|
||||
data.pose.translation += relative_pos;
|
||||
|
||||
@@ -1,358 +0,0 @@
|
||||
use std::{
|
||||
f32::consts::PI,
|
||||
sync::{atomic::AtomicUsize, Arc},
|
||||
};
|
||||
|
||||
use glam::{Affine2, Affine3A, Mat3A, Quat, Vec2, Vec3, Vec3A};
|
||||
use slotmap::new_key_type;
|
||||
use vulkano::{format::Format, image::view::ImageView};
|
||||
|
||||
use crate::{
|
||||
config::AStrMapExt, graphics::CommandBuffers, state::AppState, subsystem::input::KeyboardFocus,
|
||||
};
|
||||
|
||||
use super::{
|
||||
common::snap_upright,
|
||||
input::{Haptics, PointerHit},
|
||||
};
|
||||
|
||||
static OVERLAY_AUTO_INCREMENT: AtomicUsize = AtomicUsize::new(0);
|
||||
|
||||
new_key_type! {
|
||||
pub struct OverlayID;
|
||||
}
|
||||
|
||||
pub const Z_ORDER_TOAST: u32 = 70;
|
||||
pub const Z_ORDER_LINES: u32 = 69;
|
||||
pub const Z_ORDER_WATCH: u32 = 68;
|
||||
pub const Z_ORDER_ANCHOR: u32 = 67;
|
||||
pub const Z_ORDER_DEFAULT: u32 = 0;
|
||||
pub const Z_ORDER_DASHBOARD: u32 = Z_ORDER_DEFAULT;
|
||||
|
||||
pub struct OverlayState {
|
||||
pub name: Arc<str>,
|
||||
pub want_visible: bool,
|
||||
pub show_hide: bool,
|
||||
pub grabbable: bool,
|
||||
pub interactable: bool,
|
||||
pub recenter: bool,
|
||||
pub keyboard_focus: Option<KeyboardFocus>,
|
||||
pub dirty: bool,
|
||||
pub alpha: f32,
|
||||
pub z_order: u32,
|
||||
pub transform: Affine3A,
|
||||
pub spawn_scale: f32, // aka width
|
||||
pub spawn_point: Vec3A,
|
||||
pub spawn_rotation: Quat,
|
||||
pub saved_transform: Option<Affine3A>,
|
||||
pub positioning: Positioning,
|
||||
pub curvature: Option<f32>,
|
||||
pub birthframe: usize,
|
||||
}
|
||||
|
||||
impl Default for OverlayState {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
name: Arc::from(""),
|
||||
want_visible: false,
|
||||
show_hide: false,
|
||||
grabbable: false,
|
||||
recenter: false,
|
||||
interactable: false,
|
||||
keyboard_focus: None,
|
||||
dirty: true,
|
||||
alpha: 1.0,
|
||||
z_order: Z_ORDER_DEFAULT,
|
||||
positioning: Positioning::Floating,
|
||||
curvature: None,
|
||||
spawn_scale: 1.0,
|
||||
spawn_point: Vec3A::NEG_Z,
|
||||
spawn_rotation: Quat::IDENTITY,
|
||||
saved_transform: None,
|
||||
transform: Affine3A::IDENTITY,
|
||||
birthframe: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct OverlayData<T> {
|
||||
pub state: OverlayState,
|
||||
pub backend: Box<dyn OverlayBackend>,
|
||||
pub primary_pointer: Option<usize>,
|
||||
pub data: T,
|
||||
}
|
||||
|
||||
impl<T> OverlayData<T>
|
||||
where
|
||||
T: Default,
|
||||
{
|
||||
pub fn from_backend(backend: Box<dyn OverlayBackend>) -> Self {
|
||||
Self {
|
||||
state: OverlayState::default(),
|
||||
backend,
|
||||
primary_pointer: None,
|
||||
data: T::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl OverlayState {
|
||||
fn get_transform(&self) -> Affine3A {
|
||||
self.saved_transform.unwrap_or_else(|| {
|
||||
Affine3A::from_scale_rotation_translation(
|
||||
Vec3::ONE * self.spawn_scale,
|
||||
self.spawn_rotation,
|
||||
self.spawn_point.into(),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn auto_movement(&mut self, app: &mut AppState) {
|
||||
let (target_transform, lerp) = match self.positioning {
|
||||
Positioning::FollowHead { lerp } => (app.input_state.hmd * self.get_transform(), lerp),
|
||||
Positioning::FollowHand { hand, lerp } => (
|
||||
app.input_state.pointers[hand].pose * self.get_transform(),
|
||||
lerp,
|
||||
),
|
||||
_ => return,
|
||||
};
|
||||
|
||||
self.transform = match lerp {
|
||||
1.0 => target_transform,
|
||||
lerp => {
|
||||
let scale = target_transform.matrix3.x_axis.length();
|
||||
|
||||
let rot_from = Quat::from_mat3a(&self.transform.matrix3.div_scalar(scale));
|
||||
let rot_to = Quat::from_mat3a(&target_transform.matrix3.div_scalar(scale));
|
||||
|
||||
let rotation = rot_from.slerp(rot_to, lerp);
|
||||
let translation = self
|
||||
.transform
|
||||
.translation
|
||||
.slerp(target_transform.translation, lerp);
|
||||
|
||||
Affine3A::from_scale_rotation_translation(
|
||||
Vec3::ONE * scale,
|
||||
rotation,
|
||||
translation.into(),
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
self.dirty = true;
|
||||
}
|
||||
|
||||
pub fn reset(&mut self, app: &mut AppState, hard_reset: bool) {
|
||||
let parent_transform = match self.positioning {
|
||||
Positioning::Floating
|
||||
| Positioning::FollowHead { .. }
|
||||
| Positioning::FollowHeadPaused { .. } => app.input_state.hmd,
|
||||
Positioning::FollowHand { hand, .. } | Positioning::FollowHandPaused { hand, .. } => {
|
||||
app.input_state.pointers[hand].pose
|
||||
}
|
||||
Positioning::Anchored => app.anchor,
|
||||
Positioning::FollowOverlay { .. } | Positioning::Static => return,
|
||||
};
|
||||
|
||||
if hard_reset {
|
||||
self.saved_transform = None;
|
||||
}
|
||||
|
||||
self.transform = parent_transform * self.get_transform();
|
||||
|
||||
if self.grabbable && hard_reset {
|
||||
self.realign(&app.input_state.hmd);
|
||||
}
|
||||
self.dirty = true;
|
||||
}
|
||||
|
||||
pub fn save_transform(&mut self, app: &mut AppState) -> bool {
|
||||
let parent_transform = match self.positioning {
|
||||
Positioning::Floating => snap_upright(app.input_state.hmd, Vec3A::Y),
|
||||
Positioning::FollowHead { .. } | Positioning::FollowHeadPaused { .. } => {
|
||||
app.input_state.hmd
|
||||
}
|
||||
Positioning::FollowHand { hand, .. } | Positioning::FollowHandPaused { hand, .. } => {
|
||||
app.input_state.pointers[hand].pose
|
||||
}
|
||||
Positioning::Anchored => snap_upright(app.anchor, Vec3A::Y),
|
||||
Positioning::FollowOverlay { .. } | Positioning::Static => return false,
|
||||
};
|
||||
|
||||
self.saved_transform = Some(parent_transform.inverse() * self.transform);
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
pub fn realign(&mut self, hmd: &Affine3A) {
|
||||
let to_hmd = hmd.translation - self.transform.translation;
|
||||
let up_dir: Vec3A;
|
||||
|
||||
if hmd.x_axis.dot(Vec3A::Y).abs() > 0.2 {
|
||||
// Snap upright
|
||||
up_dir = hmd.y_axis;
|
||||
} else {
|
||||
let dot = to_hmd.normalize().dot(hmd.z_axis);
|
||||
let z_dist = to_hmd.length();
|
||||
let y_dist = (self.transform.translation.y - hmd.translation.y).abs();
|
||||
let x_angle = (y_dist / z_dist).asin();
|
||||
|
||||
if dot < -f32::EPSILON {
|
||||
// facing down
|
||||
let up_point = hmd.translation + z_dist / x_angle.cos() * Vec3A::Y;
|
||||
up_dir = (up_point - self.transform.translation).normalize();
|
||||
} else if dot > f32::EPSILON {
|
||||
// facing up
|
||||
let dn_point = hmd.translation + z_dist / x_angle.cos() * Vec3A::NEG_Y;
|
||||
up_dir = (self.transform.translation - dn_point).normalize();
|
||||
} else {
|
||||
// perfectly upright
|
||||
up_dir = Vec3A::Y;
|
||||
}
|
||||
}
|
||||
|
||||
let scale = self.transform.x_axis.length();
|
||||
|
||||
let col_z = (self.transform.translation - hmd.translation).normalize();
|
||||
let col_y = up_dir;
|
||||
let col_x = col_y.cross(col_z);
|
||||
let col_y = col_z.cross(col_x).normalize();
|
||||
let col_x = col_x.normalize();
|
||||
|
||||
let rot = Mat3A::from_quat(self.spawn_rotation)
|
||||
* Mat3A::from_quat(Quat::from_axis_angle(Vec3::Y, PI));
|
||||
self.transform.matrix3 = Mat3A::from_cols(col_x, col_y, col_z).mul_scalar(scale) * rot;
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> OverlayData<T>
|
||||
where
|
||||
T: Default,
|
||||
{
|
||||
pub fn init(&mut self, app: &mut AppState) -> anyhow::Result<()> {
|
||||
if self.state.curvature.is_none() {
|
||||
self.state.curvature = app
|
||||
.session
|
||||
.config
|
||||
.curve_values
|
||||
.arc_get(self.state.name.as_ref())
|
||||
.copied();
|
||||
}
|
||||
|
||||
if matches!(
|
||||
self.state.positioning,
|
||||
Positioning::Floating | Positioning::Anchored
|
||||
) {
|
||||
let hard_reset = if let Some(transform) = app
|
||||
.session
|
||||
.config
|
||||
.transform_values
|
||||
.arc_get(self.state.name.as_ref())
|
||||
{
|
||||
self.state.saved_transform = Some(*transform);
|
||||
false
|
||||
} else {
|
||||
true
|
||||
};
|
||||
self.state.reset(app, hard_reset);
|
||||
}
|
||||
self.backend.init(app)
|
||||
}
|
||||
pub fn should_render(&mut self, app: &mut AppState) -> anyhow::Result<ShouldRender> {
|
||||
self.backend.should_render(app)
|
||||
}
|
||||
pub fn render(
|
||||
&mut self,
|
||||
app: &mut AppState,
|
||||
tgt: Arc<ImageView>,
|
||||
buf: &mut CommandBuffers,
|
||||
alpha: f32,
|
||||
) -> anyhow::Result<bool> {
|
||||
self.backend.render(app, tgt, buf, alpha)
|
||||
}
|
||||
pub fn frame_meta(&mut self) -> Option<FrameMeta> {
|
||||
self.backend.frame_meta()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Copy)]
|
||||
pub struct FrameMeta {
|
||||
pub extent: [u32; 3],
|
||||
pub transform: Affine3A,
|
||||
pub format: Format,
|
||||
}
|
||||
|
||||
pub enum ShouldRender {
|
||||
/// The overlay is dirty and needs to be rendered.
|
||||
Should,
|
||||
/// The overlay is not dirty but is ready to be rendered.
|
||||
Can,
|
||||
/// The overlay is not ready to be rendered.
|
||||
Unable,
|
||||
}
|
||||
|
||||
pub trait OverlayBackend {
|
||||
/// Called once, before the first frame is rendered
|
||||
fn init(&mut self, app: &mut AppState) -> anyhow::Result<()>;
|
||||
fn pause(&mut self, app: &mut AppState) -> anyhow::Result<()>;
|
||||
fn resume(&mut self, app: &mut AppState) -> anyhow::Result<()>;
|
||||
|
||||
/// Called when the presentation layer is ready to present a new frame
|
||||
fn should_render(&mut self, app: &mut AppState) -> anyhow::Result<ShouldRender>;
|
||||
|
||||
/// Called when the contents need to be rendered to the swapchain
|
||||
fn render(
|
||||
&mut self,
|
||||
app: &mut AppState,
|
||||
tgt: Arc<ImageView>,
|
||||
buf: &mut CommandBuffers,
|
||||
alpha: f32,
|
||||
) -> anyhow::Result<bool>;
|
||||
|
||||
/// Called to retrieve the effective extent of the image
|
||||
/// Used for creating swapchains.
|
||||
///
|
||||
/// Must be true if should_render was also true on the same frame.
|
||||
fn frame_meta(&mut self) -> Option<FrameMeta>;
|
||||
|
||||
fn on_hover(&mut self, app: &mut AppState, hit: &PointerHit) -> Option<Haptics>;
|
||||
fn on_left(&mut self, app: &mut AppState, pointer: usize);
|
||||
fn on_pointer(&mut self, app: &mut AppState, hit: &PointerHit, pressed: bool);
|
||||
fn on_scroll(&mut self, app: &mut AppState, hit: &PointerHit, delta_y: f32, delta_x: f32);
|
||||
fn get_interaction_transform(&mut self) -> Option<Affine2>;
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub enum Positioning {
|
||||
/// Stays in place, recenters relative to HMD
|
||||
#[default]
|
||||
Floating,
|
||||
/// Stays in place, recenters relative to anchor
|
||||
Anchored,
|
||||
/// Following HMD
|
||||
FollowHead { lerp: f32 },
|
||||
/// Normally follows HMD, but paused due to interaction
|
||||
FollowHeadPaused { lerp: f32 },
|
||||
/// Following hand
|
||||
FollowHand { hand: usize, lerp: f32 },
|
||||
/// Normally follows hand, but paused due to interaction
|
||||
FollowHandPaused { hand: usize, lerp: f32 },
|
||||
/// Follow another overlay
|
||||
FollowOverlay { id: usize },
|
||||
/// Stays in place, no recentering
|
||||
Static,
|
||||
}
|
||||
|
||||
pub fn ui_transform(extent: [u32; 2]) -> Affine2 {
|
||||
let aspect = extent[0] as f32 / extent[1] as f32;
|
||||
let scale = if aspect < 1.0 {
|
||||
Vec2 {
|
||||
x: 1.0 / aspect,
|
||||
y: -1.0,
|
||||
}
|
||||
} else {
|
||||
Vec2 { x: 1.0, y: -aspect }
|
||||
};
|
||||
let center = Vec2 { x: 0.5, y: 0.5 };
|
||||
Affine2::from_scale_angle_translation(scale, 0.0, center)
|
||||
}
|
||||
@@ -1,9 +1 @@
|
||||
use glam::Affine3A;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct OverlaySetItem {
|
||||
name: Arc<str>,
|
||||
transform: Affine3A,
|
||||
}
|
||||
|
||||
pub struct OverlaySet {}
|
||||
|
||||
@@ -7,16 +7,14 @@ use std::{
|
||||
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::state::AppState;
|
||||
use crate::{
|
||||
state::AppState,
|
||||
windowing::{window::OverlayWindowConfig, OverlaySelector},
|
||||
};
|
||||
|
||||
#[cfg(feature = "wayvr")]
|
||||
use crate::backend::wayvr::WayVRAction;
|
||||
|
||||
use super::{
|
||||
common::OverlaySelector,
|
||||
overlay::{OverlayBackend, OverlayState},
|
||||
};
|
||||
|
||||
static TASK_AUTO_INCREMENT: AtomicUsize = AtomicUsize::new(0);
|
||||
|
||||
struct AppTask {
|
||||
@@ -52,14 +50,14 @@ pub enum SystemTask {
|
||||
ShowHide,
|
||||
}
|
||||
|
||||
pub type OverlayTask = dyn FnOnce(&mut AppState, &mut OverlayState) + Send;
|
||||
pub type CreateOverlayTask =
|
||||
dyn FnOnce(&mut AppState) -> Option<(OverlayState, Box<dyn OverlayBackend>)> + Send;
|
||||
pub type OverlayTask = dyn FnOnce(&mut AppState, &mut OverlayWindowConfig) + Send;
|
||||
pub type CreateOverlayTask = dyn FnOnce(&mut AppState) -> Option<OverlayWindowConfig> + Send;
|
||||
|
||||
pub enum TaskType {
|
||||
Overlay(OverlaySelector, Box<OverlayTask>),
|
||||
CreateOverlay(OverlaySelector, Box<CreateOverlayTask>),
|
||||
DropOverlay(OverlaySelector),
|
||||
ToggleSet(usize),
|
||||
System(SystemTask),
|
||||
#[cfg(feature = "wayvr")]
|
||||
WayVR(WayVRAction),
|
||||
|
||||
@@ -2,13 +2,13 @@ use std::{cell::RefCell, rc::Rc, sync::Arc};
|
||||
|
||||
use smithay::{
|
||||
backend::renderer::{
|
||||
element::{
|
||||
surface::{render_elements_from_surface_tree, WaylandSurfaceRenderElement},
|
||||
Kind,
|
||||
},
|
||||
gles::{ffi, GlesRenderer, GlesTexture},
|
||||
utils::draw_render_elements,
|
||||
Bind, Color32F, Frame, Renderer,
|
||||
element::{
|
||||
Kind,
|
||||
surface::{WaylandSurfaceRenderElement, render_elements_from_surface_tree},
|
||||
},
|
||||
gles::{GlesRenderer, GlesTexture, ffi},
|
||||
utils::draw_render_elements,
|
||||
},
|
||||
input,
|
||||
utils::{Logical, Point, Rectangle, Size, Transform},
|
||||
@@ -16,14 +16,11 @@ use smithay::{
|
||||
};
|
||||
use wayvr_ipc::packet_server;
|
||||
|
||||
use crate::{
|
||||
backend::{overlay::OverlayID, wayvr::time::get_millis},
|
||||
gen_id,
|
||||
};
|
||||
use crate::{backend::wayvr::time::get_millis, gen_id, windowing::OverlayID};
|
||||
|
||||
use super::{
|
||||
client::WayVRCompositor, comp::send_frames_surface_tree, egl_data, event_queue::SyncEventQueue,
|
||||
process, smithay_wrapper, time, window, BlitMethod, WayVRSignal,
|
||||
BlitMethod, WayVRSignal, client::WayVRCompositor, comp::send_frames_surface_tree, egl_data,
|
||||
event_queue::SyncEventQueue, process, smithay_wrapper, time, window,
|
||||
};
|
||||
|
||||
fn generate_auth_key() -> String {
|
||||
|
||||
@@ -87,7 +87,7 @@ pub enum WayVRSignal {
|
||||
packet_server::WvrDisplayWindowLayout,
|
||||
),
|
||||
BroadcastStateChanged(packet_server::WvrStateChanged),
|
||||
DropOverlay(super::overlay::OverlayID),
|
||||
DropOverlay(crate::windowing::OverlayID),
|
||||
Haptics(super::input::Haptics),
|
||||
}
|
||||
|
||||
|
||||
@@ -2,20 +2,14 @@ use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::config_io;
|
||||
use crate::overlays::toast::DisplayMethod;
|
||||
use crate::overlays::toast::ToastTopic;
|
||||
use crate::overlays::toast::{DisplayMethod, ToastTopic};
|
||||
use crate::state::LeftRight;
|
||||
use chrono::Offset;
|
||||
use config::Config;
|
||||
use config::File;
|
||||
use glam::Affine3A;
|
||||
use glam::Quat;
|
||||
use glam::Vec3A;
|
||||
use glam::vec3a;
|
||||
use config::{Config, File};
|
||||
use glam::{vec3, Affine3A, Quat, Vec3};
|
||||
use idmap::IdMap;
|
||||
use log::error;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub type AStrMap<V> = Vec<(Arc<str>, V)>;
|
||||
|
||||
@@ -76,8 +70,8 @@ impl AStrSetExt for AStrSet {
|
||||
|
||||
pub type PwTokenMap = AStrMap<String>;
|
||||
|
||||
pub const fn def_watch_pos() -> Vec3A {
|
||||
vec3a(-0.03, -0.01, 0.125)
|
||||
pub const fn def_watch_pos() -> Vec3 {
|
||||
vec3(-0.03, -0.01, 0.125)
|
||||
}
|
||||
|
||||
pub const fn def_watch_rot() -> Quat {
|
||||
@@ -179,7 +173,7 @@ const fn def_max_height() -> u16 {
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct GeneralConfig {
|
||||
#[serde(default = "def_watch_pos")]
|
||||
pub watch_pos: Vec3A,
|
||||
pub watch_pos: Vec3,
|
||||
|
||||
#[serde(default = "def_watch_rot")]
|
||||
pub watch_rot: Quat,
|
||||
@@ -444,7 +438,7 @@ pub fn load_general() -> GeneralConfig {
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct AutoSettings {
|
||||
pub watch_pos: Vec3A,
|
||||
pub watch_pos: Vec3,
|
||||
pub watch_rot: Quat,
|
||||
pub watch_hand: LeftRight,
|
||||
pub watch_view_angle_min: f32,
|
||||
|
||||
@@ -12,13 +12,13 @@ use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
backend::{
|
||||
overlay::Positioning,
|
||||
task::{TaskContainer, TaskType},
|
||||
wayvr::{self, WayVRAction},
|
||||
},
|
||||
config::load_config_with_conf_d,
|
||||
config_io,
|
||||
overlays::wayvr::{WayVRData, executable_exists_in_path},
|
||||
windowing::window::Positioning,
|
||||
};
|
||||
|
||||
// Flat version of RelativeTo
|
||||
|
||||
@@ -9,11 +9,10 @@ use wgui::{
|
||||
parser::CustomAttribsInfoOwned,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
backend::{common::OverlaySelector, task::TaskType, wayvr::WayVRAction},
|
||||
config::{save_layout, AStrSetExt},
|
||||
state::AppState,
|
||||
};
|
||||
use crate::state::AppState;
|
||||
|
||||
#[cfg(feature = "wayvr")]
|
||||
use crate::backend::{task::TaskType, wayvr::WayVRAction};
|
||||
|
||||
use super::helper::read_label_from_pipe;
|
||||
|
||||
@@ -41,47 +40,19 @@ pub(super) fn setup_custom_button<S>(
|
||||
|
||||
let callback: EventCallback<AppState, S> = match command {
|
||||
"::DashToggle" => Box::new(move |_common, _data, app, _| {
|
||||
#[cfg(feature = "wayvr")]
|
||||
app.tasks
|
||||
.enqueue(TaskType::WayVR(WayVRAction::ToggleDashboard));
|
||||
Ok(())
|
||||
}),
|
||||
"::OverlayToggle" => {
|
||||
let Some(selector) = args.next() else {
|
||||
log::warn!("Missing argument for {command}");
|
||||
continue;
|
||||
"::SetToggle" => {
|
||||
let arg = args.next().unwrap_or_default();
|
||||
let Ok(set_idx) = arg.parse() else {
|
||||
log::error!("::SetToggle has invalid argument: \"{arg}\"");
|
||||
return;
|
||||
};
|
||||
|
||||
let selector = OverlaySelector::Name(selector.into());
|
||||
|
||||
Box::new(move |_common, _data, app, _| {
|
||||
app.tasks.enqueue(TaskType::Overlay(
|
||||
selector.clone(),
|
||||
Box::new(|app, o| {
|
||||
o.want_visible = !o.want_visible;
|
||||
if o.recenter {
|
||||
o.show_hide = o.want_visible;
|
||||
o.reset(app, false);
|
||||
}
|
||||
|
||||
let mut state_dirty = false;
|
||||
if !o.want_visible {
|
||||
state_dirty |=
|
||||
app.session.config.show_screens.arc_rm(o.name.as_ref());
|
||||
} else if o.want_visible {
|
||||
state_dirty |=
|
||||
app.session.config.show_screens.arc_set(o.name.clone());
|
||||
}
|
||||
|
||||
if state_dirty {
|
||||
match save_layout(&app.session.config) {
|
||||
Ok(()) => log::debug!("Saved state"),
|
||||
Err(e) => {
|
||||
log::error!("Failed to save state: {e:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
));
|
||||
app.tasks.enqueue(TaskType::ToggleSet(set_idx));
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
@@ -94,7 +65,7 @@ pub(super) fn setup_custom_button<S>(
|
||||
#[allow(clippy::match_same_arms)]
|
||||
"::OscSend" => return,
|
||||
// shell
|
||||
_ => todo!(),
|
||||
_ => return,
|
||||
};
|
||||
|
||||
listeners.register(listener_handles, attribs.widget_id, *kind, callback);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::{cell::RefCell, rc::Rc, sync::Arc};
|
||||
|
||||
use button::setup_custom_button;
|
||||
use glam::{vec2, Affine2, Vec2};
|
||||
use glam::{Affine2, Vec2, vec2};
|
||||
use label::setup_custom_label;
|
||||
use vulkano::{command_buffer::CommandBufferUsage, image::view::ImageView};
|
||||
use wgui::{
|
||||
@@ -18,12 +18,10 @@ use wgui::{
|
||||
};
|
||||
|
||||
use crate::{
|
||||
backend::{
|
||||
input::{Haptics, PointerHit, PointerMode},
|
||||
overlay::{ui_transform, FrameMeta, OverlayBackend, ShouldRender},
|
||||
},
|
||||
backend::input::{Haptics, PointerHit, PointerMode},
|
||||
graphics::{CommandBuffers, ExtentExt},
|
||||
state::AppState,
|
||||
windowing::backend::{FrameMeta, OverlayBackend, ShouldRender, ui_transform},
|
||||
};
|
||||
|
||||
use super::{timer::GuiTimer, timestep::Timestep};
|
||||
|
||||
@@ -23,6 +23,7 @@ mod overlays;
|
||||
mod shaders;
|
||||
mod state;
|
||||
mod subsystem;
|
||||
mod windowing;
|
||||
|
||||
#[cfg(feature = "wayvr")]
|
||||
mod config_wayvr;
|
||||
@@ -30,8 +31,8 @@ mod config_wayvr;
|
||||
use std::{
|
||||
path::PathBuf,
|
||||
sync::{
|
||||
Arc,
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -39,7 +40,7 @@ use clap::Parser;
|
||||
use subsystem::notifications::DbusNotificationSender;
|
||||
use sysinfo::Pid;
|
||||
use tracing::level_filters::LevelFilter;
|
||||
use tracing_subscriber::{EnvFilter, layer::SubscriberExt, util::SubscriberInitExt};
|
||||
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter};
|
||||
|
||||
/// The lightweight desktop overlay for OpenVR and OpenXR
|
||||
#[derive(Default, Parser, Debug)]
|
||||
@@ -129,14 +130,12 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
|
||||
#[allow(unused_mut, clippy::similar_names)]
|
||||
fn auto_run(running: Arc<AtomicBool>, args: Args) {
|
||||
use backend::common::BackendError;
|
||||
|
||||
let mut tried_xr = false;
|
||||
let mut tried_vr = false;
|
||||
|
||||
#[cfg(feature = "openxr")]
|
||||
if !args_get_openvr(&args) {
|
||||
use crate::backend::openxr::openxr_run;
|
||||
use crate::backend::{openxr::openxr_run, BackendError};
|
||||
tried_xr = true;
|
||||
match openxr_run(running.clone(), args.show, args.headless) {
|
||||
Ok(()) => return,
|
||||
@@ -150,7 +149,7 @@ fn auto_run(running: Arc<AtomicBool>, args: Args) {
|
||||
|
||||
#[cfg(feature = "openvr")]
|
||||
if !args_get_openxr(&args) {
|
||||
use crate::backend::openvr::openvr_run;
|
||||
use crate::backend::{openvr::openvr_run, BackendError};
|
||||
tried_vr = true;
|
||||
match openvr_run(running, args.show, args.headless) {
|
||||
Ok(()) => return,
|
||||
@@ -239,6 +238,7 @@ fn logging_init(args: &mut Args) {
|
||||
EnvFilter::builder()
|
||||
.with_default_directive(LevelFilter::INFO.into())
|
||||
.from_env_lossy()
|
||||
.add_directive("symphonia_core::probe=warn".parse().unwrap())
|
||||
.add_directive("zbus=warn".parse().unwrap())
|
||||
.add_directive("cosmic_text=warn".parse().unwrap())
|
||||
.add_directive("wlx_capture::wayland=info".parse().unwrap())
|
||||
|
||||
@@ -1,30 +1,31 @@
|
||||
use glam::Vec3A;
|
||||
use glam::{Affine3A, Quat, Vec3};
|
||||
use std::sync::{Arc, LazyLock};
|
||||
|
||||
use crate::backend::overlay::{OverlayData, OverlayState, Positioning, Z_ORDER_ANCHOR};
|
||||
use crate::gui::panel::GuiPanel;
|
||||
use crate::state::AppState;
|
||||
use crate::windowing::window::{OverlayWindowConfig, OverlayWindowState, Positioning};
|
||||
use crate::windowing::Z_ORDER_ANCHOR;
|
||||
|
||||
pub static ANCHOR_NAME: LazyLock<Arc<str>> = LazyLock::new(|| Arc::from("anchor"));
|
||||
|
||||
pub fn create_anchor<O>(app: &mut AppState) -> anyhow::Result<OverlayData<O>>
|
||||
where
|
||||
O: Default,
|
||||
{
|
||||
pub fn create_anchor(app: &mut AppState) -> anyhow::Result<OverlayWindowConfig> {
|
||||
let panel = GuiPanel::new_from_template(app, "gui/anchor.xml", (), None)?;
|
||||
|
||||
Ok(OverlayData {
|
||||
state: OverlayState {
|
||||
Ok(OverlayWindowConfig {
|
||||
name: ANCHOR_NAME.clone(),
|
||||
want_visible: false,
|
||||
z_order: Z_ORDER_ANCHOR,
|
||||
default_state: OverlayWindowState {
|
||||
interactable: false,
|
||||
grabbable: false,
|
||||
z_order: Z_ORDER_ANCHOR,
|
||||
spawn_scale: 0.1,
|
||||
spawn_point: Vec3A::NEG_Z * 0.5,
|
||||
positioning: Positioning::Static,
|
||||
..Default::default()
|
||||
transform: Affine3A::from_scale_rotation_translation(
|
||||
Vec3::ONE * 0.1,
|
||||
Quat::IDENTITY,
|
||||
Vec3::NEG_Z * 0.5,
|
||||
),
|
||||
..OverlayWindowState::default()
|
||||
},
|
||||
..OverlayData::from_backend(Box::new(panel))
|
||||
global: true,
|
||||
..OverlayWindowConfig::from_backend(Box::new(panel))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
use glam::{Affine3A, Vec3};
|
||||
|
||||
use crate::{
|
||||
backend::overlay::{OverlayData, OverlayState},
|
||||
gui::panel::GuiPanel,
|
||||
state::AppState,
|
||||
windowing::window::{OverlayWindowConfig, OverlayWindowState},
|
||||
};
|
||||
|
||||
pub const BAR_NAME: &str = "bar";
|
||||
@@ -11,7 +13,7 @@ struct BarState {}
|
||||
#[allow(clippy::significant_drop_tightening)]
|
||||
#[allow(clippy::for_kv_map)] // TODO: remove later
|
||||
#[allow(clippy::match_same_arms)] // TODO: remove later
|
||||
pub fn create_bar<O>(app: &mut AppState) -> anyhow::Result<OverlayData<O>>
|
||||
pub fn create_bar<O>(app: &mut AppState) -> anyhow::Result<OverlayWindowConfig>
|
||||
where
|
||||
O: Default,
|
||||
{
|
||||
@@ -34,14 +36,14 @@ where
|
||||
|
||||
panel.update_layout()?;
|
||||
|
||||
Ok(OverlayData {
|
||||
state: OverlayState {
|
||||
Ok(OverlayWindowConfig {
|
||||
name: BAR_NAME.into(),
|
||||
want_visible: true,
|
||||
default_state: OverlayWindowState {
|
||||
interactable: true,
|
||||
spawn_scale: 0.15,
|
||||
..Default::default()
|
||||
transform: Affine3A::from_scale(Vec3::ONE * 0.15),
|
||||
..OverlayWindowState::default()
|
||||
},
|
||||
..OverlayData::from_backend(Box::new(panel))
|
||||
global: true,
|
||||
..OverlayWindowConfig::from_backend(Box::new(panel))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use glam::Vec3A;
|
||||
use glam::{vec3, Affine3A, Quat, Vec3};
|
||||
|
||||
use crate::{
|
||||
backend::overlay::{OverlayBackend, OverlayState},
|
||||
gui::panel::GuiPanel,
|
||||
state::AppState,
|
||||
windowing::window::{OverlayWindowConfig, OverlayWindowState},
|
||||
};
|
||||
|
||||
const SETTINGS_NAME: &str = "settings";
|
||||
@@ -13,10 +13,7 @@ const SETTINGS_NAME: &str = "settings";
|
||||
#[allow(unreachable_code)]
|
||||
#[allow(unused_variables)]
|
||||
#[allow(dead_code)]
|
||||
pub fn create_custom(
|
||||
app: &mut AppState,
|
||||
name: Arc<str>,
|
||||
) -> Option<(OverlayState, Box<dyn OverlayBackend>)> {
|
||||
pub fn create_custom(app: &mut AppState, name: Arc<str>) -> Option<OverlayWindowConfig> {
|
||||
return None;
|
||||
|
||||
unreachable!();
|
||||
@@ -24,17 +21,18 @@ pub fn create_custom(
|
||||
let panel = GuiPanel::new_blank(app, ()).ok()?;
|
||||
panel.update_layout().ok()?;
|
||||
|
||||
let state = OverlayState {
|
||||
Some(OverlayWindowConfig {
|
||||
name,
|
||||
want_visible: true,
|
||||
default_state: OverlayWindowState {
|
||||
interactable: true,
|
||||
grabbable: true,
|
||||
spawn_scale: 0.1, //TODO: this
|
||||
spawn_point: Vec3A::from_array([0., 0., -0.5]),
|
||||
//interaction_transform: ui_transform(config.size),
|
||||
..Default::default()
|
||||
};
|
||||
let backend = Box::new(panel);
|
||||
|
||||
Some((state, backend))
|
||||
transform: Affine3A::from_scale_rotation_translation(
|
||||
Vec3::ONE * 0.1, // TODO scale
|
||||
Quat::IDENTITY,
|
||||
vec3(0.0, 0.0, -0.5),
|
||||
),
|
||||
..OverlayWindowState::default()
|
||||
},
|
||||
..OverlayWindowConfig::from_backend(Box::new(panel))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::{collections::HashMap, rc::Rc};
|
||||
|
||||
use glam::{vec2, vec3a, Mat4, Vec2, Vec3};
|
||||
use glam::{vec2, vec3, Affine3A, Mat4, Quat, Vec2, Vec3};
|
||||
use wgui::{
|
||||
animation::{Animation, AnimationEasing},
|
||||
drawing::Color,
|
||||
@@ -17,10 +17,10 @@ use wgui::{
|
||||
};
|
||||
|
||||
use crate::{
|
||||
backend::overlay::{OverlayData, OverlayState, Positioning},
|
||||
gui::panel::GuiPanel,
|
||||
state::AppState,
|
||||
subsystem::hid::{XkbKeymap, ALT, CTRL, META, SHIFT, SUPER},
|
||||
windowing::window::{OverlayWindowConfig, OverlayWindowState, Positioning},
|
||||
};
|
||||
|
||||
use super::{
|
||||
@@ -33,13 +33,10 @@ const BACKGROUND_PADDING: f32 = 4.;
|
||||
const PIXELS_PER_UNIT: f32 = 80.;
|
||||
|
||||
#[allow(clippy::too_many_lines, clippy::significant_drop_tightening)]
|
||||
pub fn create_keyboard<O>(
|
||||
pub fn create_keyboard(
|
||||
app: &mut AppState,
|
||||
mut keymap: Option<XkbKeymap>,
|
||||
) -> anyhow::Result<OverlayData<O>>
|
||||
where
|
||||
O: Default,
|
||||
{
|
||||
) -> anyhow::Result<OverlayWindowConfig> {
|
||||
let layout = layout::Layout::load_from_disk();
|
||||
let state = KeyboardState {
|
||||
modifiers: 0,
|
||||
@@ -278,18 +275,21 @@ where
|
||||
|
||||
let width = layout.row_size * 0.05 * app.session.config.keyboard_scale;
|
||||
|
||||
Ok(OverlayData {
|
||||
state: OverlayState {
|
||||
Ok(OverlayWindowConfig {
|
||||
name: KEYBOARD_NAME.into(),
|
||||
default_state: OverlayWindowState {
|
||||
grabbable: true,
|
||||
recenter: true,
|
||||
positioning: Positioning::Anchored,
|
||||
interactable: true,
|
||||
spawn_scale: width,
|
||||
spawn_point: vec3a(0., -0.5, 0.),
|
||||
..Default::default()
|
||||
curvature: Some(0.15),
|
||||
transform: Affine3A::from_scale_rotation_translation(
|
||||
Vec3::ONE * width,
|
||||
Quat::from_rotation_x(-10f32.to_radians()),
|
||||
vec3(0.0, -0.5, -0.5),
|
||||
),
|
||||
..OverlayWindowState::default()
|
||||
},
|
||||
..OverlayData::from_backend(Box::new(KeyboardBackend { panel }))
|
||||
..OverlayWindowConfig::from_backend(Box::new(KeyboardBackend { panel }))
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -11,14 +11,12 @@ use wgui::{
|
||||
};
|
||||
|
||||
use crate::{
|
||||
backend::{
|
||||
input::{Haptics, PointerHit},
|
||||
overlay::{FrameMeta, OverlayBackend, ShouldRender},
|
||||
},
|
||||
backend::input::{Haptics, PointerHit},
|
||||
graphics::CommandBuffers,
|
||||
gui::panel::GuiPanel,
|
||||
state::AppState,
|
||||
subsystem::hid::{ALT, CTRL, KeyModifier, META, SHIFT, SUPER, VirtualKey},
|
||||
windowing::backend::{FrameMeta, OverlayBackend, ShouldRender},
|
||||
};
|
||||
|
||||
pub mod builder;
|
||||
|
||||
@@ -4,19 +4,22 @@ use std::{
|
||||
};
|
||||
|
||||
use futures::{Future, FutureExt};
|
||||
use glam::Affine2;
|
||||
use glam::{Affine2, Affine3A, Vec3};
|
||||
use vulkano::image::view::ImageView;
|
||||
use wlx_capture::pipewire::{PipewireCapture, PipewireSelectScreenResult, pipewire_select_screen};
|
||||
use wlx_capture::pipewire::{pipewire_select_screen, PipewireCapture, PipewireSelectScreenResult};
|
||||
|
||||
use crate::{
|
||||
backend::{
|
||||
common::OverlaySelector,
|
||||
input::{Haptics, PointerHit},
|
||||
overlay::{FrameMeta, OverlayBackend, OverlayState, ShouldRender, ui_transform},
|
||||
task::TaskType,
|
||||
},
|
||||
graphics::CommandBuffers,
|
||||
state::{AppSession, AppState},
|
||||
windowing::{
|
||||
backend::{ui_transform, FrameMeta, OverlayBackend, ShouldRender},
|
||||
window::{OverlayWindowConfig, OverlayWindowState},
|
||||
OverlaySelector,
|
||||
},
|
||||
};
|
||||
|
||||
use super::screen::backend::ScreenBackend;
|
||||
@@ -70,9 +73,7 @@ impl OverlayBackend for MirrorBackend {
|
||||
app.tasks.enqueue(TaskType::Overlay(
|
||||
OverlaySelector::Name(self.name.clone()),
|
||||
Box::new(|app, o| {
|
||||
o.grabbable = true;
|
||||
o.interactable = true;
|
||||
o.reset(app, false);
|
||||
o.activate(app);
|
||||
}),
|
||||
));
|
||||
}
|
||||
@@ -140,19 +141,15 @@ impl OverlayBackend for MirrorBackend {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_mirror(
|
||||
name: Arc<str>,
|
||||
show_hide: bool,
|
||||
session: &AppSession,
|
||||
) -> (OverlayState, Box<dyn OverlayBackend>) {
|
||||
let state = OverlayState {
|
||||
pub fn new_mirror(name: Arc<str>, session: &AppSession) -> OverlayWindowConfig {
|
||||
OverlayWindowConfig {
|
||||
name: name.clone(),
|
||||
show_hide,
|
||||
want_visible: true,
|
||||
spawn_scale: 0.5 * session.config.desktop_view_scale,
|
||||
..Default::default()
|
||||
};
|
||||
let backend = Box::new(MirrorBackend::new(name));
|
||||
|
||||
(state, backend)
|
||||
default_state: OverlayWindowState {
|
||||
interactable: true,
|
||||
grabbable: true,
|
||||
transform: Affine3A::from_scale(Vec3::ONE * 0.5 * session.config.desktop_view_scale),
|
||||
..OverlayWindowState::default()
|
||||
},
|
||||
..OverlayWindowConfig::from_backend(Box::new(MirrorBackend::new(name)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +1,21 @@
|
||||
use std::{
|
||||
sync::{atomic::AtomicU64, Arc, LazyLock},
|
||||
sync::{Arc, LazyLock, atomic::AtomicU64},
|
||||
time::Instant,
|
||||
};
|
||||
|
||||
use glam::{vec2, Affine2, Vec2};
|
||||
use glam::{Affine2, Vec2, vec2};
|
||||
use vulkano::image::view::ImageView;
|
||||
use wlx_capture::{frame::Transform, WlxCapture};
|
||||
use wlx_capture::{WlxCapture, frame::Transform};
|
||||
|
||||
use crate::{
|
||||
backend::{
|
||||
input::{Haptics, PointerHit, PointerMode},
|
||||
overlay::{FrameMeta, OverlayBackend, ShouldRender},
|
||||
},
|
||||
backend::input::{Haptics, PointerHit, PointerMode},
|
||||
graphics::{CommandBuffers, ExtentExt},
|
||||
state::AppState,
|
||||
subsystem::hid::{MOUSE_LEFT, MOUSE_MIDDLE, MOUSE_RIGHT},
|
||||
windowing::backend::{FrameMeta, OverlayBackend, ShouldRender},
|
||||
};
|
||||
|
||||
use super::capture::{receive_callback, ScreenPipeline, WlxCaptureIn, WlxCaptureOut};
|
||||
use super::capture::{ScreenPipeline, WlxCaptureIn, WlxCaptureOut, receive_callback};
|
||||
|
||||
const CURSOR_SIZE: f32 = 16. / 1440.;
|
||||
|
||||
|
||||
@@ -7,28 +7,29 @@ use vulkano::{
|
||||
command_buffer::CommandBufferUsage,
|
||||
device::Queue,
|
||||
format::Format,
|
||||
image::{sampler::Filter, view::ImageView, Image},
|
||||
image::{Image, sampler::Filter, view::ImageView},
|
||||
pipeline::graphics::color_blend::AttachmentBlend,
|
||||
};
|
||||
use wgui::gfx::{
|
||||
WGfx,
|
||||
cmd::WGfxClearMode,
|
||||
pass::WGfxPass,
|
||||
pipeline::{WGfxPipeline, WPipelineCreateInfo},
|
||||
WGfx,
|
||||
};
|
||||
use wlx_capture::{
|
||||
frame::{self as wlx_frame, DrmFormat, FrameFormat, MouseMeta, Transform, WlxFrame},
|
||||
WlxCapture,
|
||||
frame::{self as wlx_frame, DrmFormat, FrameFormat, MouseMeta, Transform, WlxFrame},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
backend::overlay::FrameMeta,
|
||||
config::GeneralConfig,
|
||||
graphics::{
|
||||
dmabuf::{fourcc_to_vk, WGfxDmabuf},
|
||||
upload_quad_vertices, CommandBuffers, Vert2Uv,
|
||||
CommandBuffers, Vert2Uv,
|
||||
dmabuf::{WGfxDmabuf, fourcc_to_vk},
|
||||
upload_quad_vertices,
|
||||
},
|
||||
state::AppState,
|
||||
windowing::backend::FrameMeta,
|
||||
};
|
||||
|
||||
const CURSOR_SIZE: f32 = 16. / 1440.;
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
use std::{f32::consts::PI, sync::Arc};
|
||||
|
||||
use backend::ScreenBackend;
|
||||
use glam::{vec3a, Quat, Vec3};
|
||||
use wl::create_screens_wayland;
|
||||
use glam::{vec3, Affine3A, Quat, Vec3};
|
||||
use wlx_capture::frame::Transform;
|
||||
|
||||
use crate::{
|
||||
backend::overlay::{OverlayState, Positioning},
|
||||
state::{AppSession, AppState, ScreenMeta},
|
||||
subsystem::{hid::XkbKeymap, input::KeyboardFocus},
|
||||
windowing::{
|
||||
backend::OverlayBackend,
|
||||
window::{OverlayWindowConfig, OverlayWindowState, Positioning},
|
||||
},
|
||||
};
|
||||
|
||||
pub mod backend;
|
||||
@@ -20,7 +21,12 @@ pub mod wl;
|
||||
#[cfg(feature = "x11")]
|
||||
pub mod x11;
|
||||
|
||||
fn create_screen_state(name: Arc<str>, transform: Transform, session: &AppSession) -> OverlayState {
|
||||
fn create_screen_from_backend(
|
||||
name: Arc<str>,
|
||||
transform: Transform,
|
||||
session: &AppSession,
|
||||
backend: Box<dyn OverlayBackend>,
|
||||
) -> OverlayWindowConfig {
|
||||
let angle = if session.config.upright_screen_fix {
|
||||
match transform {
|
||||
Transform::Rotated90 | Transform::Flipped90 => PI / 2.,
|
||||
@@ -32,22 +38,27 @@ fn create_screen_state(name: Arc<str>, transform: Transform, session: &AppSessio
|
||||
0.
|
||||
};
|
||||
|
||||
OverlayState {
|
||||
OverlayWindowConfig {
|
||||
name,
|
||||
keyboard_focus: Some(KeyboardFocus::PhysicalScreen),
|
||||
default_state: OverlayWindowState {
|
||||
grabbable: true,
|
||||
recenter: true,
|
||||
positioning: Positioning::Anchored,
|
||||
interactable: true,
|
||||
spawn_scale: 1.5 * session.config.desktop_view_scale,
|
||||
spawn_point: vec3a(0., 0.5, 0.),
|
||||
spawn_rotation: Quat::from_axis_angle(Vec3::Z, angle),
|
||||
..Default::default()
|
||||
curvature: Some(0.15),
|
||||
transform: Affine3A::from_scale_rotation_translation(
|
||||
Vec3::ONE * 1.5 * session.config.desktop_view_scale,
|
||||
Quat::from_rotation_z(angle),
|
||||
vec3(0.0, 0.2, -0.5),
|
||||
),
|
||||
..OverlayWindowState::default()
|
||||
},
|
||||
keyboard_focus: Some(KeyboardFocus::PhysicalScreen),
|
||||
..OverlayWindowConfig::from_backend(backend)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ScreenCreateData {
|
||||
pub screens: Vec<(ScreenMeta, OverlayState, Box<ScreenBackend>)>,
|
||||
pub screens: Vec<(ScreenMeta, OverlayWindowConfig)>,
|
||||
}
|
||||
|
||||
pub fn create_screens(app: &mut AppState) -> anyhow::Result<(ScreenCreateData, Option<XkbKeymap>)> {
|
||||
@@ -61,7 +72,7 @@ pub fn create_screens(app: &mut AppState) -> anyhow::Result<(ScreenCreateData, O
|
||||
.map_err(|f| log::warn!("Could not load keyboard layout: {f}"))
|
||||
.ok();
|
||||
|
||||
return Ok((create_screens_wayland(&mut wl, app), keymap));
|
||||
return Ok((wl::create_screens_wayland(&mut wl, app), keymap));
|
||||
}
|
||||
log::info!("Wayland not detected, assuming X11.");
|
||||
}
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
use glam::vec2;
|
||||
use wlx_capture::{
|
||||
WlxCapture,
|
||||
wayland::{WlxClient, WlxOutput},
|
||||
wlr_dmabuf::WlrDmabufCapture,
|
||||
wlr_screencopy::WlrScreencopyCapture,
|
||||
WlxCapture,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
config::{AStrMapExt, PwTokenMap},
|
||||
overlays::screen::create_screen_state,
|
||||
overlays::screen::create_screen_from_backend,
|
||||
state::{AppState, ScreenMeta},
|
||||
};
|
||||
|
||||
use super::{
|
||||
backend::ScreenBackend,
|
||||
capture::{new_wlx_capture, MainThreadWlxCapture},
|
||||
pw::{load_pw_token_config, save_pw_token_config},
|
||||
ScreenCreateData,
|
||||
backend::ScreenBackend,
|
||||
capture::{MainThreadWlxCapture, new_wlx_capture},
|
||||
pw::{load_pw_token_config, save_pw_token_config},
|
||||
};
|
||||
|
||||
impl ScreenBackend {
|
||||
@@ -130,15 +130,19 @@ pub fn create_screens_wayland(wl: &mut WlxClient, app: &mut AppState) -> ScreenC
|
||||
|
||||
backend.set_mouse_transform(logical_pos, logical_size, transform);
|
||||
|
||||
let state = create_screen_state(output.name.clone(), transform, &app.session);
|
||||
let window_config = create_screen_from_backend(
|
||||
output.name.clone(),
|
||||
transform,
|
||||
&app.session,
|
||||
Box::new(backend),
|
||||
);
|
||||
|
||||
let meta = ScreenMeta {
|
||||
name: wl.outputs[id].name.clone(),
|
||||
native_handle: *id,
|
||||
};
|
||||
|
||||
let backend = Box::new(backend);
|
||||
screens.push((meta, state, backend));
|
||||
screens.push((meta, window_config));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,20 +2,20 @@ use std::sync::Arc;
|
||||
|
||||
use glam::vec2;
|
||||
use wlx_capture::{
|
||||
WlxCapture,
|
||||
frame::Transform,
|
||||
xshm::{XshmCapture, XshmScreen},
|
||||
WlxCapture,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
overlays::screen::create_screen_state,
|
||||
overlays::screen::create_screen_from_backend,
|
||||
state::{AppState, ScreenMeta},
|
||||
};
|
||||
|
||||
use super::{
|
||||
backend::ScreenBackend,
|
||||
capture::{new_wlx_capture, MainThreadWlxCapture},
|
||||
ScreenCreateData,
|
||||
backend::ScreenBackend,
|
||||
capture::{MainThreadWlxCapture, new_wlx_capture},
|
||||
};
|
||||
|
||||
#[cfg(feature = "pipewire")]
|
||||
@@ -39,7 +39,7 @@ pub fn create_screens_x11pw(app: &mut AppState) -> anyhow::Result<ScreenCreateDa
|
||||
use crate::{
|
||||
config::{AStrMapExt, PwTokenMap},
|
||||
overlays::screen::{
|
||||
create_screen_state,
|
||||
create_screen_from_backend,
|
||||
pw::{load_pw_token_config, save_pw_token_config, select_pw_screen},
|
||||
},
|
||||
state::ScreenMeta,
|
||||
@@ -108,15 +108,19 @@ pub fn create_screens_x11pw(app: &mut AppState) -> anyhow::Result<ScreenCreateDa
|
||||
Transform::Normal,
|
||||
);
|
||||
|
||||
let state = create_screen_state(m.name.clone(), Transform::Normal, &app.session);
|
||||
let window_data = create_screen_from_backend(
|
||||
m.name.clone(),
|
||||
Transform::Normal,
|
||||
&app.session,
|
||||
Box::new(backend),
|
||||
);
|
||||
|
||||
let meta = ScreenMeta {
|
||||
name: m.name.clone(),
|
||||
native_handle: 0,
|
||||
};
|
||||
|
||||
let backend = Box::new(backend);
|
||||
(meta, state, backend)
|
||||
(meta, window_data)
|
||||
})
|
||||
.collect();
|
||||
|
||||
@@ -189,15 +193,19 @@ pub fn create_screens_xshm(app: &mut AppState) -> anyhow::Result<ScreenCreateDat
|
||||
Transform::Normal,
|
||||
);
|
||||
|
||||
let state = create_screen_state(s.name.clone(), Transform::Normal, &app.session);
|
||||
let window_data = create_screen_from_backend(
|
||||
s.name.clone(),
|
||||
Transform::Normal,
|
||||
&app.session,
|
||||
Box::new(backend),
|
||||
);
|
||||
|
||||
let meta = ScreenMeta {
|
||||
name: s.name.clone(),
|
||||
native_handle: 0,
|
||||
};
|
||||
|
||||
let backend = Box::new(backend);
|
||||
(meta, state, backend)
|
||||
(meta, window_data)
|
||||
})
|
||||
.collect();
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ use std::{
|
||||
time::Instant,
|
||||
};
|
||||
|
||||
use glam::{vec3a, Quat};
|
||||
use glam::{vec3, Affine3A, Quat, Vec3};
|
||||
use idmap_derive::IntegerId;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use wgui::{
|
||||
@@ -24,13 +24,13 @@ use wgui::{
|
||||
};
|
||||
|
||||
use crate::{
|
||||
backend::{
|
||||
common::OverlaySelector,
|
||||
overlay::{OverlayBackend, OverlayState, Positioning, Z_ORDER_TOAST},
|
||||
task::TaskType,
|
||||
},
|
||||
backend::task::TaskType,
|
||||
gui::panel::GuiPanel,
|
||||
state::{AppState, LeftRight},
|
||||
windowing::{
|
||||
window::{OverlayWindowConfig, OverlayWindowState, Positioning},
|
||||
OverlaySelector, Z_ORDER_TOAST,
|
||||
},
|
||||
};
|
||||
|
||||
const FONT_SIZE: isize = 16;
|
||||
@@ -112,16 +112,13 @@ impl Toast {
|
||||
TaskType::CreateOverlay(
|
||||
selector.clone(),
|
||||
Box::new(move |app| {
|
||||
let mut maybe_toast = new_toast(self, app);
|
||||
if let Some((state, _)) = maybe_toast.as_mut() {
|
||||
state.auto_movement(app);
|
||||
let maybe_toast = new_toast(self, app);
|
||||
app.tasks.enqueue_at(
|
||||
// at timeout, drop the overlay by ID instead
|
||||
// in order to avoid dropping any newer toasts
|
||||
TaskType::DropOverlay(selector),
|
||||
destroy_at,
|
||||
);
|
||||
}
|
||||
maybe_toast
|
||||
}),
|
||||
),
|
||||
@@ -131,7 +128,7 @@ impl Toast {
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
fn new_toast(toast: Toast, app: &mut AppState) -> Option<(OverlayState, Box<dyn OverlayBackend>)> {
|
||||
fn new_toast(toast: Toast, app: &mut AppState) -> Option<OverlayWindowConfig> {
|
||||
let current_method = app
|
||||
.session
|
||||
.toast_topics
|
||||
@@ -142,12 +139,13 @@ fn new_toast(toast: Toast, app: &mut AppState) -> Option<(OverlayState, Box<dyn
|
||||
let (spawn_point, spawn_rotation, positioning) = match current_method {
|
||||
DisplayMethod::Hide => return None,
|
||||
DisplayMethod::Center => (
|
||||
vec3a(0., -0.2, -0.5),
|
||||
vec3(0., -0.2, -0.5),
|
||||
Quat::IDENTITY,
|
||||
Positioning::FollowHead { lerp: 0.1 },
|
||||
),
|
||||
DisplayMethod::Watch => {
|
||||
let mut watch_pos = app.session.config.watch_pos + vec3a(-0.005, -0.05, 0.02);
|
||||
let mut watch_pos =
|
||||
Vec3::from(app.session.config.watch_pos) + vec3(-0.005, -0.05, 0.02);
|
||||
let mut watch_rot = app.session.config.watch_rot;
|
||||
let relative_to = match app.session.config.watch_hand {
|
||||
LeftRight::Left => Positioning::FollowHand { hand: 0, lerp: 1.0 },
|
||||
@@ -239,19 +237,21 @@ fn new_toast(toast: Toast, app: &mut AppState) -> Option<(OverlayState, Box<dyn
|
||||
|
||||
panel.update_layout().ok()?;
|
||||
|
||||
let state = OverlayState {
|
||||
Some(OverlayWindowConfig {
|
||||
name: TOAST_NAME.clone(),
|
||||
want_visible: true,
|
||||
spawn_scale: panel.layout.content_size.x * PIXELS_TO_METERS,
|
||||
default_state: OverlayWindowState {
|
||||
positioning,
|
||||
transform: Affine3A::from_scale_rotation_translation(
|
||||
Vec3::ONE * panel.layout.content_size.x * PIXELS_TO_METERS,
|
||||
spawn_rotation,
|
||||
spawn_point,
|
||||
),
|
||||
..OverlayWindowState::default()
|
||||
},
|
||||
global: true,
|
||||
z_order: Z_ORDER_TOAST,
|
||||
positioning,
|
||||
..Default::default()
|
||||
};
|
||||
let backend = Box::new(panel);
|
||||
|
||||
Some((state, backend))
|
||||
..OverlayWindowConfig::from_backend(Box::new(panel))
|
||||
})
|
||||
}
|
||||
|
||||
fn msg_err(app: &mut AppState, message: &str) {
|
||||
|
||||
@@ -1,98 +0,0 @@
|
||||
use glam::{Affine3A, Quat, Vec3A};
|
||||
use wgui::{
|
||||
i18n::Translation,
|
||||
parser::parse_color_hex,
|
||||
renderer_vk::text::TextStyle,
|
||||
taffy::{
|
||||
self,
|
||||
prelude::{auto, length, percent},
|
||||
},
|
||||
widget::{
|
||||
rectangle::{Rectangle, RectangleParams},
|
||||
text::{TextLabel, TextParams},
|
||||
util::WLength,
|
||||
},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
backend::overlay::{OverlayBackend, OverlayState, Z_ORDER_TOAST},
|
||||
gui::panel::GuiPanel,
|
||||
state::AppState,
|
||||
};
|
||||
|
||||
const FONT_SIZE: isize = 16;
|
||||
const PADDING: (f32, f32) = (25., 7.);
|
||||
const PIXELS_TO_METERS: f32 = 1. / 2000.;
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
fn new_tooltip(
|
||||
text: &str,
|
||||
transform: Affine3A,
|
||||
app: &mut AppState,
|
||||
) -> Option<(OverlayState, Box<dyn OverlayBackend>)> {
|
||||
let mut panel = GuiPanel::new_blank(app, ()).ok()?;
|
||||
|
||||
let globals = panel.layout.state.globals.clone();
|
||||
let mut i18n = globals.i18n();
|
||||
|
||||
let (rect, _) = panel
|
||||
.layout
|
||||
.add_child(
|
||||
panel.layout.root_widget,
|
||||
Rectangle::create(RectangleParams {
|
||||
color: parse_color_hex("#1e2030").unwrap(),
|
||||
border_color: parse_color_hex("#5e7090").unwrap(),
|
||||
border: 1.0,
|
||||
round: WLength::Units(4.0),
|
||||
..Default::default()
|
||||
})
|
||||
.unwrap(),
|
||||
taffy::Style {
|
||||
align_items: Some(taffy::AlignItems::Center),
|
||||
justify_content: Some(taffy::JustifyContent::Center),
|
||||
flex_direction: taffy::FlexDirection::Column,
|
||||
padding: length(4.0),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.ok()?;
|
||||
|
||||
let _ = panel.layout.add_child(
|
||||
rect.id,
|
||||
TextLabel::create(
|
||||
&mut i18n,
|
||||
TextParams {
|
||||
content: Translation::from_raw_text(text),
|
||||
style: TextStyle {
|
||||
color: parse_color_hex("#ffffff"),
|
||||
..Default::default()
|
||||
},
|
||||
},
|
||||
)
|
||||
.unwrap(),
|
||||
taffy::Style {
|
||||
size: taffy::Size {
|
||||
width: percent(1.0),
|
||||
height: auto(),
|
||||
},
|
||||
padding: length(8.0),
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
|
||||
panel.update_layout().ok()?;
|
||||
|
||||
let state = OverlayState {
|
||||
name: "tooltip".into(),
|
||||
want_visible: true,
|
||||
spawn_scale: panel.layout.content_size.x * PIXELS_TO_METERS,
|
||||
spawn_rotation: Quat::IDENTITY,
|
||||
spawn_point: Vec3A::ZERO,
|
||||
z_order: Z_ORDER_TOAST,
|
||||
positioning: crate::backend::overlay::Positioning::Static,
|
||||
..Default::default()
|
||||
};
|
||||
let backend = Box::new(panel);
|
||||
|
||||
Some((state, backend))
|
||||
}
|
||||
@@ -1,12 +1,14 @@
|
||||
use std::{collections::HashMap, rc::Rc, sync::Arc, time::Duration};
|
||||
use std::{collections::HashMap, rc::Rc, time::Duration};
|
||||
|
||||
use glam::Vec3A;
|
||||
use smallvec::SmallVec;
|
||||
use glam::{Affine3A, Vec3, Vec3A};
|
||||
|
||||
use crate::{
|
||||
backend::overlay::{OverlayData, OverlayState, Positioning, Z_ORDER_WATCH},
|
||||
gui::{panel::GuiPanel, timer::GuiTimer},
|
||||
state::AppState,
|
||||
windowing::{
|
||||
window::{OverlayWindowConfig, OverlayWindowData, OverlayWindowState, Positioning},
|
||||
Z_ORDER_WATCH,
|
||||
},
|
||||
};
|
||||
|
||||
pub const WATCH_NAME: &str = "watch";
|
||||
@@ -14,16 +16,7 @@ pub const WATCH_NAME: &str = "watch";
|
||||
struct WatchState {}
|
||||
|
||||
#[allow(clippy::significant_drop_tightening)]
|
||||
pub fn create_watch<O>(app: &mut AppState) -> anyhow::Result<OverlayData<O>>
|
||||
where
|
||||
O: Default,
|
||||
{
|
||||
let screens = app
|
||||
.screens
|
||||
.iter()
|
||||
.map(|s| s.name.clone())
|
||||
.collect::<SmallVec<[Arc<str>; 8]>>();
|
||||
|
||||
pub fn create_watch(app: &mut AppState, num_sets: usize) -> anyhow::Result<OverlayWindowConfig> {
|
||||
let state = WatchState {};
|
||||
let mut panel = GuiPanel::new_from_template(
|
||||
app,
|
||||
@@ -35,10 +28,10 @@ where
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
for (idx, handle) in screens.iter().enumerate() {
|
||||
for idx in 0..num_sets {
|
||||
let mut params: HashMap<Rc<str>, Rc<str>> = HashMap::new();
|
||||
params.insert("display".into(), (idx + 1).to_string().into());
|
||||
params.insert("handle".into(), handle.as_ref().into());
|
||||
params.insert("handle".into(), idx.to_string().into());
|
||||
parser_state.instantiate_template(
|
||||
doc_params, "Set", layout, listeners, widget, params,
|
||||
)?;
|
||||
@@ -59,47 +52,36 @@ where
|
||||
|
||||
panel.update_layout()?;
|
||||
|
||||
Ok(OverlayData {
|
||||
state: OverlayState {
|
||||
Ok(OverlayWindowConfig {
|
||||
name: WATCH_NAME.into(),
|
||||
want_visible: true,
|
||||
interactable: true,
|
||||
z_order: Z_ORDER_WATCH,
|
||||
spawn_scale: 0.115, //TODO:configurable
|
||||
spawn_point: app.session.config.watch_pos,
|
||||
spawn_rotation: app.session.config.watch_rot,
|
||||
default_state: OverlayWindowState {
|
||||
interactable: true,
|
||||
positioning,
|
||||
..Default::default()
|
||||
transform: Affine3A::from_scale_rotation_translation(
|
||||
Vec3::ONE * 0.115,
|
||||
app.session.config.watch_rot,
|
||||
app.session.config.watch_pos,
|
||||
),
|
||||
..OverlayWindowState::default()
|
||||
},
|
||||
..OverlayData::from_backend(Box::new(panel))
|
||||
show_on_spawn: true,
|
||||
global: true,
|
||||
..OverlayWindowConfig::from_backend(Box::new(panel))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn watch_fade<D>(app: &mut AppState, watch: &mut OverlayData<D>)
|
||||
where
|
||||
D: Default,
|
||||
{
|
||||
if watch.state.saved_transform.is_some() {
|
||||
watch.state.want_visible = false;
|
||||
pub fn watch_fade<D>(app: &mut AppState, watch: &mut OverlayWindowData<D>) {
|
||||
let Some(state) = watch.config.active_state.as_mut() else {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let to_hmd = (watch.state.transform.translation - app.input_state.hmd.translation).normalize();
|
||||
let watch_normal = watch
|
||||
.state
|
||||
.transform
|
||||
.transform_vector3a(Vec3A::NEG_Z)
|
||||
.normalize();
|
||||
let to_hmd = (state.transform.translation - app.input_state.hmd.translation).normalize();
|
||||
let watch_normal = state.transform.transform_vector3a(Vec3A::NEG_Z).normalize();
|
||||
let dot = to_hmd.dot(watch_normal);
|
||||
|
||||
if dot < app.session.config.watch_view_angle_min {
|
||||
watch.state.want_visible = false;
|
||||
} else {
|
||||
watch.state.want_visible = true;
|
||||
|
||||
watch.state.alpha = (dot - app.session.config.watch_view_angle_min)
|
||||
state.alpha = (dot - app.session.config.watch_view_angle_min)
|
||||
/ (app.session.config.watch_view_angle_max - app.session.config.watch_view_angle_min);
|
||||
watch.state.alpha += 0.1;
|
||||
watch.state.alpha = watch.state.alpha.clamp(0., 1.);
|
||||
}
|
||||
state.alpha += 0.1;
|
||||
state.alpha = state.alpha.clamp(0., 1.);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use glam::{vec3a, Affine2, Vec3, Vec3A};
|
||||
use glam::{vec3, Affine2, Affine3A, Quat, Vec3};
|
||||
use smallvec::smallvec;
|
||||
use std::{cell::RefCell, collections::HashMap, rc::Rc, sync::Arc};
|
||||
use vulkano::{
|
||||
@@ -17,12 +17,7 @@ use wlx_capture::frame::{DmabufFrame, FourCC, FrameFormat, FramePlane};
|
||||
|
||||
use crate::{
|
||||
backend::{
|
||||
common::{OverlayContainer, OverlaySelector},
|
||||
input::{self},
|
||||
overlay::{
|
||||
ui_transform, FrameMeta, OverlayBackend, OverlayData, OverlayID, OverlayState,
|
||||
ShouldRender, Z_ORDER_DASHBOARD,
|
||||
},
|
||||
task::TaskType,
|
||||
wayvr::{
|
||||
self, display,
|
||||
@@ -34,6 +29,12 @@ use crate::{
|
||||
graphics::{dmabuf::WGfxDmabuf, CommandBuffers, Vert2Uv},
|
||||
state::{self, AppState},
|
||||
subsystem::input::KeyboardFocus,
|
||||
windowing::{
|
||||
backend::{ui_transform, FrameMeta, OverlayBackend, ShouldRender},
|
||||
manager::OverlayWindowManager,
|
||||
window::{OverlayWindowConfig, OverlayWindowData, OverlayWindowState},
|
||||
OverlayID, OverlaySelector, Z_ORDER_DASHBOARD,
|
||||
},
|
||||
};
|
||||
|
||||
use super::toast::error_toast;
|
||||
@@ -222,7 +223,7 @@ pub fn executable_exists_in_path(command: &str) -> bool {
|
||||
|
||||
fn toggle_dashboard<O>(
|
||||
app: &mut AppState,
|
||||
overlays: &mut OverlayContainer<O>,
|
||||
overlays: &mut OverlayWindowManager<O>,
|
||||
wayvr: &mut WayVRData,
|
||||
) -> anyhow::Result<()>
|
||||
where
|
||||
@@ -245,7 +246,19 @@ where
|
||||
if newly_created {
|
||||
log::info!("Creating dashboard overlay");
|
||||
|
||||
let mut overlay = create_overlay::<O>(
|
||||
let mut overlay = OverlayWindowData::from_config(OverlayWindowConfig {
|
||||
default_state: OverlayWindowState {
|
||||
curvature: Some(0.15),
|
||||
transform: Affine3A::from_scale_rotation_translation(
|
||||
Vec3::ONE * 2.0,
|
||||
Quat::IDENTITY,
|
||||
vec3(0.0, -0.35, -1.75),
|
||||
),
|
||||
..OverlayWindowState::default()
|
||||
},
|
||||
z_order: Z_ORDER_DASHBOARD,
|
||||
show_on_spawn: true,
|
||||
..create_overlay(
|
||||
app,
|
||||
DASHBOARD_DISPLAY_NAME,
|
||||
OverlayToCreate {
|
||||
@@ -260,16 +273,12 @@ where
|
||||
primary: None,
|
||||
},
|
||||
},
|
||||
)?;
|
||||
)?
|
||||
});
|
||||
|
||||
overlay.state.curvature = Some(0.15);
|
||||
overlay.state.want_visible = true;
|
||||
overlay.state.spawn_scale = 2.0;
|
||||
overlay.state.spawn_point = vec3a(0.0, -0.35, -1.75);
|
||||
overlay.state.z_order = Z_ORDER_DASHBOARD;
|
||||
overlay.state.reset(app, true);
|
||||
overlay.config.reset(app, true);
|
||||
|
||||
let overlay_id = overlays.add(overlay);
|
||||
let overlay_id = overlays.add(overlay, app);
|
||||
wayvr.set_overlay_display_handle(overlay_id, disp_handle);
|
||||
|
||||
let args_vec = &conf_dash
|
||||
@@ -319,29 +328,22 @@ where
|
||||
app.tasks.enqueue(TaskType::Overlay(
|
||||
OverlaySelector::Id(overlay_id),
|
||||
Box::new(move |app, o| {
|
||||
// Toggle visibility
|
||||
o.want_visible = cur_visibility;
|
||||
if cur_visibility {
|
||||
o.reset(app, true);
|
||||
}
|
||||
o.toggle(app);
|
||||
}),
|
||||
));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_overlay<O>(
|
||||
fn create_overlay(
|
||||
app: &mut AppState,
|
||||
name: &str,
|
||||
cell: OverlayToCreate,
|
||||
) -> anyhow::Result<OverlayData<O>>
|
||||
where
|
||||
O: Default,
|
||||
{
|
||||
) -> anyhow::Result<OverlayWindowConfig> {
|
||||
let conf_display = &cell.conf_display;
|
||||
let disp_handle = cell.disp_handle;
|
||||
|
||||
let mut overlay = create_wayvr_display_overlay::<O>(
|
||||
let mut overlay = create_wayvr_display_overlay(
|
||||
app,
|
||||
conf_display.width,
|
||||
conf_display.height,
|
||||
@@ -351,17 +353,22 @@ where
|
||||
)?;
|
||||
|
||||
if let Some(attach_to) = &conf_display.attach_to {
|
||||
overlay.state.positioning = attach_to.get_positioning();
|
||||
overlay.default_state.positioning = attach_to.get_positioning();
|
||||
}
|
||||
|
||||
if let Some(rot) = &conf_display.rotation {
|
||||
overlay.state.spawn_rotation =
|
||||
glam::Quat::from_axis_angle(Vec3::from_slice(&rot.axis), f32::to_radians(rot.angle));
|
||||
}
|
||||
let rot = if let Some(rot) = &conf_display.rotation {
|
||||
glam::Quat::from_axis_angle(Vec3::from_slice(&rot.axis), f32::to_radians(rot.angle))
|
||||
} else {
|
||||
glam::Quat::IDENTITY
|
||||
};
|
||||
|
||||
if let Some(pos) = &conf_display.pos {
|
||||
overlay.state.spawn_point = Vec3A::from_slice(pos);
|
||||
}
|
||||
let pos = if let Some(pos) = &conf_display.pos {
|
||||
Vec3::from_slice(pos)
|
||||
} else {
|
||||
Vec3::NEG_Z
|
||||
};
|
||||
|
||||
overlay.default_state.transform = Affine3A::from_rotation_translation(rot, pos);
|
||||
|
||||
Ok(overlay)
|
||||
}
|
||||
@@ -369,7 +376,7 @@ where
|
||||
fn create_queued_displays<O>(
|
||||
app: &mut AppState,
|
||||
data: &mut WayVRData,
|
||||
overlays: &mut OverlayContainer<O>,
|
||||
overlays: &mut OverlayWindowManager<O>,
|
||||
) -> anyhow::Result<()>
|
||||
where
|
||||
O: Default,
|
||||
@@ -384,8 +391,8 @@ where
|
||||
let name = disp.name.clone();
|
||||
|
||||
let disp_handle = cell.disp_handle;
|
||||
let overlay = create_overlay::<O>(app, name.as_str(), cell)?;
|
||||
let overlay_id = overlays.add(overlay); // Insert freshly created WayVR overlay into wlx stack
|
||||
let overlay = OverlayWindowData::from_config(create_overlay(app, name.as_str(), cell)?);
|
||||
let overlay_id = overlays.add(overlay, app); // Insert freshly created WayVR overlay into wlx stack
|
||||
data.set_overlay_display_handle(overlay_id, disp_handle);
|
||||
}
|
||||
|
||||
@@ -393,7 +400,10 @@ where
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
pub fn tick_events<O>(app: &mut AppState, overlays: &mut OverlayContainer<O>) -> anyhow::Result<()>
|
||||
pub fn tick_events<O>(
|
||||
app: &mut AppState,
|
||||
overlays: &mut OverlayWindowManager<O>,
|
||||
) -> anyhow::Result<()>
|
||||
where
|
||||
O: Default,
|
||||
{
|
||||
@@ -414,8 +424,8 @@ where
|
||||
.set_display_visible(display_handle, visible);
|
||||
app.tasks.enqueue(TaskType::Overlay(
|
||||
OverlaySelector::Id(overlay_id),
|
||||
Box::new(move |_app, o| {
|
||||
o.want_visible = visible;
|
||||
Box::new(move |app, o| {
|
||||
o.toggle(app);
|
||||
}),
|
||||
));
|
||||
}
|
||||
@@ -775,28 +785,14 @@ impl OverlayBackend for WayVRBackend {
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn create_wayvr_display_overlay<O>(
|
||||
pub fn create_wayvr_display_overlay(
|
||||
app: &mut state::AppState,
|
||||
display_width: u16,
|
||||
display_height: u16,
|
||||
display_handle: wayvr::display::DisplayHandle,
|
||||
display_scale: f32,
|
||||
name: &str,
|
||||
) -> anyhow::Result<OverlayData<O>>
|
||||
where
|
||||
O: Default,
|
||||
{
|
||||
let state = OverlayState {
|
||||
name: format!("WayVR - {name}").into(),
|
||||
keyboard_focus: Some(KeyboardFocus::WayVR),
|
||||
want_visible: true,
|
||||
interactable: true,
|
||||
grabbable: true,
|
||||
spawn_scale: display_scale,
|
||||
spawn_point: vec3a(0.0, -0.1, -1.0),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
) -> anyhow::Result<OverlayWindowConfig> {
|
||||
let wayvr = app.get_wayvr()?;
|
||||
|
||||
let backend = Box::new(WayVRBackend::new(
|
||||
@@ -806,21 +802,36 @@ where
|
||||
[display_width, display_height],
|
||||
)?);
|
||||
|
||||
Ok(OverlayData {
|
||||
state,
|
||||
..OverlayData::from_backend(backend)
|
||||
Ok(OverlayWindowConfig {
|
||||
name: format!("WayVR - {name}").into(),
|
||||
keyboard_focus: Some(KeyboardFocus::WayVR),
|
||||
default_state: OverlayWindowState {
|
||||
interactable: true,
|
||||
grabbable: true,
|
||||
transform: Affine3A::from_scale_rotation_translation(
|
||||
Vec3::ONE * display_scale,
|
||||
Quat::IDENTITY,
|
||||
vec3(0.0, -0.1, -1.0),
|
||||
),
|
||||
..OverlayWindowState::default()
|
||||
},
|
||||
..OverlayWindowConfig::from_backend(backend)
|
||||
})
|
||||
}
|
||||
|
||||
fn show_display<O>(wayvr: &mut WayVRData, overlays: &mut OverlayContainer<O>, display_name: &str)
|
||||
where
|
||||
fn show_display<O>(
|
||||
wayvr: &mut WayVRData,
|
||||
overlays: &mut OverlayWindowManager<O>,
|
||||
app: &mut AppState,
|
||||
display_name: &str,
|
||||
) where
|
||||
O: Default,
|
||||
{
|
||||
if let Some(display) = WayVR::get_display_by_name(&wayvr.data.state.displays, display_name) {
|
||||
if let Some(overlay_id) = wayvr.display_handle_map.get(&display)
|
||||
&& let Some(overlay) = overlays.mut_by_id(*overlay_id)
|
||||
{
|
||||
overlay.state.want_visible = true;
|
||||
overlay.config.activate(app);
|
||||
}
|
||||
|
||||
wayvr.data.state.set_display_visible(display, true);
|
||||
@@ -829,7 +840,7 @@ where
|
||||
|
||||
fn action_app_click<O>(
|
||||
app: &mut AppState,
|
||||
overlays: &mut OverlayContainer<O>,
|
||||
overlays: &mut OverlayWindowManager<O>,
|
||||
catalog_name: &Arc<str>,
|
||||
app_name: &Arc<str>,
|
||||
) -> anyhow::Result<()>
|
||||
@@ -884,7 +895,7 @@ where
|
||||
HashMap::default(),
|
||||
)?;
|
||||
|
||||
show_display::<O>(&mut wayvr, overlays, app_entry.target_display.as_str());
|
||||
show_display::<O>(&mut wayvr, overlays, app, app_entry.target_display.as_str());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -893,7 +904,7 @@ where
|
||||
|
||||
pub fn action_display_click<O>(
|
||||
app: &mut AppState,
|
||||
overlays: &mut OverlayContainer<O>,
|
||||
overlays: &mut OverlayWindowManager<O>,
|
||||
display_name: &Arc<str>,
|
||||
action: &WayVRDisplayClickAction,
|
||||
) -> anyhow::Result<()>
|
||||
@@ -922,20 +933,22 @@ where
|
||||
match action {
|
||||
WayVRDisplayClickAction::ToggleVisibility => {
|
||||
// Toggle visibility
|
||||
overlay.state.want_visible = !overlay.state.want_visible;
|
||||
overlay.config.toggle(app);
|
||||
}
|
||||
WayVRDisplayClickAction::Reset => {
|
||||
// Show it at the front
|
||||
overlay.state.want_visible = true;
|
||||
overlay.state.reset(app, true);
|
||||
overlay.config.reset(app, true);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn wayvr_action<O>(app: &mut AppState, overlays: &mut OverlayContainer<O>, action: &WayVRAction)
|
||||
where
|
||||
pub fn wayvr_action<O>(
|
||||
app: &mut AppState,
|
||||
overlays: &mut OverlayWindowManager<O>,
|
||||
action: &WayVRAction,
|
||||
) where
|
||||
O: Default,
|
||||
{
|
||||
match action {
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
|
||||
use super::hid::{self, HidProvider, VirtualKey};
|
||||
|
||||
#[cfg(feature = "wayvr")]
|
||||
use crate::overlays::wayvr::WayVRData;
|
||||
#[cfg(feature = "wayvr")]
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub enum KeyboardFocus {
|
||||
@@ -14,6 +16,7 @@ pub enum KeyboardFocus {
|
||||
pub struct HidWrapper {
|
||||
pub keyboard_focus: KeyboardFocus,
|
||||
pub inner: Box<dyn HidProvider>,
|
||||
#[cfg(feature = "wayvr")]
|
||||
pub wayvr: Option<Rc<RefCell<WayVRData>>>, // Dynamically created if requested
|
||||
}
|
||||
|
||||
@@ -22,6 +25,7 @@ impl HidWrapper {
|
||||
Self {
|
||||
keyboard_focus: KeyboardFocus::PhysicalScreen,
|
||||
inner: hid::initialize(),
|
||||
#[cfg(feature = "wayvr")]
|
||||
wayvr: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,8 +7,9 @@ use anyhow::bail;
|
||||
use rosc::{OscMessage, OscPacket, OscType};
|
||||
|
||||
use crate::{
|
||||
backend::{common::OverlayContainer, input::TrackedDevice},
|
||||
backend::input::TrackedDevice,
|
||||
overlays::{keyboard::KEYBOARD_NAME, watch::WATCH_NAME},
|
||||
windowing::manager::OverlayWindowManager,
|
||||
};
|
||||
|
||||
use crate::backend::input::TrackedDeviceRole;
|
||||
@@ -54,7 +55,7 @@ impl OscSender {
|
||||
#[allow(clippy::too_many_lines)]
|
||||
pub fn send_params<D>(
|
||||
&mut self,
|
||||
overlays: &OverlayContainer<D>,
|
||||
overlays: &OverlayWindowManager<D>,
|
||||
devices: &Vec<TrackedDevice>,
|
||||
) -> anyhow::Result<()>
|
||||
where
|
||||
@@ -69,14 +70,14 @@ impl OscSender {
|
||||
let mut has_wrist = false;
|
||||
|
||||
for o in overlays.values() {
|
||||
if !o.state.want_visible {
|
||||
let Some(state) = o.config.active_state.as_ref() else {
|
||||
continue;
|
||||
}
|
||||
match o.state.name.as_ref() {
|
||||
};
|
||||
match o.config.name.as_ref() {
|
||||
WATCH_NAME => has_wrist = true,
|
||||
KEYBOARD_NAME => has_keyboard = true,
|
||||
_ => {
|
||||
if o.state.interactable {
|
||||
if state.interactable {
|
||||
num_overlays += 1;
|
||||
}
|
||||
}
|
||||
|
||||
70
wlx-overlay-s/src/windowing/backend.rs
Normal file
70
wlx-overlay-s/src/windowing/backend.rs
Normal file
@@ -0,0 +1,70 @@
|
||||
use glam::{Affine2, Affine3A, Vec2};
|
||||
use std::sync::Arc;
|
||||
use vulkano::{format::Format, image::view::ImageView};
|
||||
|
||||
use crate::{
|
||||
backend::input::{Haptics, PointerHit},
|
||||
graphics::CommandBuffers,
|
||||
state::AppState,
|
||||
};
|
||||
|
||||
#[derive(Default, Clone, Copy)]
|
||||
pub struct FrameMeta {
|
||||
pub extent: [u32; 3],
|
||||
pub transform: Affine3A,
|
||||
pub format: Format,
|
||||
}
|
||||
|
||||
pub enum ShouldRender {
|
||||
/// The overlay is dirty and needs to be rendered.
|
||||
Should,
|
||||
/// The overlay is not dirty but is ready to be rendered.
|
||||
Can,
|
||||
/// The overlay is not ready to be rendered.
|
||||
Unable,
|
||||
}
|
||||
|
||||
pub trait OverlayBackend {
|
||||
/// Called once, before the first frame is rendered
|
||||
fn init(&mut self, app: &mut AppState) -> anyhow::Result<()>;
|
||||
fn pause(&mut self, app: &mut AppState) -> anyhow::Result<()>;
|
||||
fn resume(&mut self, app: &mut AppState) -> anyhow::Result<()>;
|
||||
|
||||
/// Called when the presentation layer is ready to present a new frame
|
||||
fn should_render(&mut self, app: &mut AppState) -> anyhow::Result<ShouldRender>;
|
||||
|
||||
/// Called when the contents need to be rendered to the swapchain
|
||||
fn render(
|
||||
&mut self,
|
||||
app: &mut AppState,
|
||||
tgt: Arc<ImageView>,
|
||||
buf: &mut CommandBuffers,
|
||||
alpha: f32,
|
||||
) -> anyhow::Result<bool>;
|
||||
|
||||
/// Called to retrieve the effective extent of the image
|
||||
/// Used for creating swapchains.
|
||||
///
|
||||
/// Must be true if should_render was also true on the same frame.
|
||||
fn frame_meta(&mut self) -> Option<FrameMeta>;
|
||||
|
||||
fn on_hover(&mut self, app: &mut AppState, hit: &PointerHit) -> Option<Haptics>;
|
||||
fn on_left(&mut self, app: &mut AppState, pointer: usize);
|
||||
fn on_pointer(&mut self, app: &mut AppState, hit: &PointerHit, pressed: bool);
|
||||
fn on_scroll(&mut self, app: &mut AppState, hit: &PointerHit, delta_y: f32, delta_x: f32);
|
||||
fn get_interaction_transform(&mut self) -> Option<Affine2>;
|
||||
}
|
||||
|
||||
pub fn ui_transform(extent: [u32; 2]) -> Affine2 {
|
||||
let aspect = extent[0] as f32 / extent[1] as f32;
|
||||
let scale = if aspect < 1.0 {
|
||||
Vec2 {
|
||||
x: 1.0 / aspect,
|
||||
y: -1.0,
|
||||
}
|
||||
} else {
|
||||
Vec2 { x: 1.0, y: -aspect }
|
||||
};
|
||||
let center = Vec2 { x: 0.5, y: 0.5 };
|
||||
Affine2::from_scale_angle_translation(scale, 0.0, center)
|
||||
}
|
||||
219
wlx-overlay-s/src/windowing/manager.rs
Normal file
219
wlx-overlay-s/src/windowing/manager.rs
Normal file
@@ -0,0 +1,219 @@
|
||||
use glam::{Affine3A, Vec3, Vec3A};
|
||||
use slotmap::{HopSlotMap, Key};
|
||||
|
||||
use crate::{
|
||||
overlays::{
|
||||
anchor::create_anchor, keyboard::builder::create_keyboard, screen::create_screens,
|
||||
watch::create_watch,
|
||||
},
|
||||
state::AppState,
|
||||
windowing::{
|
||||
set::OverlayWindowSet, snap_upright, window::OverlayWindowData, OverlayID, OverlaySelector,
|
||||
},
|
||||
};
|
||||
|
||||
pub struct OverlayWindowManager<T> {
|
||||
overlays: HopSlotMap<OverlayID, OverlayWindowData<T>>,
|
||||
sets: Vec<OverlayWindowSet>,
|
||||
current_set: Option<usize>,
|
||||
last_set: usize,
|
||||
anchor_local: Affine3A,
|
||||
watch_id: OverlayID,
|
||||
}
|
||||
|
||||
impl<T> OverlayWindowManager<T>
|
||||
where
|
||||
T: Default,
|
||||
{
|
||||
pub fn new(app: &mut AppState, headless: bool) -> anyhow::Result<Self> {
|
||||
let mut maybe_keymap = None;
|
||||
|
||||
let mut me = Self {
|
||||
overlays: HopSlotMap::with_key(),
|
||||
current_set: Some(0),
|
||||
last_set: 0,
|
||||
sets: vec![OverlayWindowSet::default()],
|
||||
anchor_local: Affine3A::from_translation(Vec3::NEG_Z),
|
||||
watch_id: OverlayID::null(), // set down below
|
||||
};
|
||||
|
||||
if headless {
|
||||
log::info!("Running in headless mode; keyboard will be en-US");
|
||||
} else {
|
||||
// create one work set for each screen.
|
||||
match create_screens(app) {
|
||||
Ok((data, keymap)) => {
|
||||
let last_idx = data.screens.len() - 1;
|
||||
for (idx, (meta, mut config)) in data.screens.into_iter().enumerate() {
|
||||
config.show_on_spawn = true;
|
||||
me.add(OverlayWindowData::from_config(config), app);
|
||||
|
||||
if idx < last_idx {
|
||||
me.sets.push(OverlayWindowSet::default());
|
||||
me.switch_to_set(app, Some(me.current_set.unwrap() + 1));
|
||||
}
|
||||
app.screens.push(meta);
|
||||
}
|
||||
|
||||
maybe_keymap = keymap;
|
||||
}
|
||||
Err(e) => log::error!("Unable to initialize screens: {e:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
let mut keyboard = OverlayWindowData::from_config(create_keyboard(app, maybe_keymap)?);
|
||||
keyboard.config.show_on_spawn = true;
|
||||
let keyboard_id = me.add(keyboard, app);
|
||||
|
||||
me.switch_to_set(app, None);
|
||||
|
||||
// copy keyboard to all sets
|
||||
let kbd_state = me
|
||||
.sets
|
||||
.last()
|
||||
.and_then(|s| s.overlays.get(keyboard_id))
|
||||
.unwrap()
|
||||
.clone();
|
||||
for set in me.sets.iter_mut() {
|
||||
set.overlays.insert(keyboard_id, kbd_state.clone());
|
||||
}
|
||||
|
||||
let anchor = OverlayWindowData::from_config(create_anchor(app)?);
|
||||
me.add(anchor, app);
|
||||
|
||||
let watch = OverlayWindowData::from_config(create_watch(app, me.sets.len())?);
|
||||
me.watch_id = me.add(watch, app);
|
||||
|
||||
me.switch_to_set(app, None);
|
||||
|
||||
Ok(me)
|
||||
}
|
||||
|
||||
pub fn mut_by_selector(
|
||||
&mut self,
|
||||
selector: &OverlaySelector,
|
||||
) -> Option<&mut OverlayWindowData<T>> {
|
||||
match selector {
|
||||
OverlaySelector::Id(id) => self.mut_by_id(*id),
|
||||
OverlaySelector::Name(name) => self.lookup(name).and_then(|id| self.mut_by_id(id)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove_by_selector(
|
||||
&mut self,
|
||||
selector: &OverlaySelector,
|
||||
) -> Option<OverlayWindowData<T>> {
|
||||
match selector {
|
||||
OverlaySelector::Id(id) => self.overlays.remove(*id),
|
||||
OverlaySelector::Name(name) => {
|
||||
self.lookup(name).and_then(|id| self.overlays.remove(id))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_by_id(&mut self, id: OverlayID) -> Option<&OverlayWindowData<T>> {
|
||||
self.overlays.get(id)
|
||||
}
|
||||
|
||||
pub fn mut_by_id(&mut self, id: OverlayID) -> Option<&mut OverlayWindowData<T>> {
|
||||
self.overlays.get_mut(id)
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = (OverlayID, &'_ OverlayWindowData<T>)> {
|
||||
self.overlays.iter()
|
||||
}
|
||||
|
||||
pub fn iter_mut(&mut self) -> impl Iterator<Item = (OverlayID, &'_ mut OverlayWindowData<T>)> {
|
||||
self.overlays.iter_mut()
|
||||
}
|
||||
|
||||
pub fn values(&self) -> impl Iterator<Item = &'_ OverlayWindowData<T>> {
|
||||
self.overlays.values()
|
||||
}
|
||||
|
||||
pub fn values_mut(&mut self) -> impl Iterator<Item = &'_ mut OverlayWindowData<T>> {
|
||||
self.overlays.values_mut()
|
||||
}
|
||||
|
||||
pub fn lookup(&self, name: &str) -> Option<OverlayID> {
|
||||
self.overlays
|
||||
.iter()
|
||||
.find(|(_, v)| v.config.name.as_ref() == name)
|
||||
.map(|(k, _)| k)
|
||||
}
|
||||
|
||||
pub fn add(&mut self, mut overlay: OverlayWindowData<T>, app: &mut AppState) -> OverlayID {
|
||||
if overlay.config.show_on_spawn {
|
||||
overlay.config.activate(app);
|
||||
}
|
||||
let id = self.overlays.insert(overlay);
|
||||
id
|
||||
}
|
||||
|
||||
pub fn switch_or_toggle_set(&mut self, app: &mut AppState, set: usize) {
|
||||
let new_set = if self.current_set.iter().any(|cur| *cur == set) {
|
||||
None
|
||||
} else {
|
||||
Some(set)
|
||||
};
|
||||
|
||||
self.switch_to_set(app, new_set);
|
||||
}
|
||||
|
||||
pub fn switch_to_set(&mut self, app: &mut AppState, new_set: Option<usize>) {
|
||||
if new_set == self.current_set {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(current_set) = self.current_set.as_ref() {
|
||||
let ws = &mut self.sets[*current_set];
|
||||
ws.overlays.clear();
|
||||
for (id, data) in self.overlays.iter_mut().filter(|(_, d)| !d.config.global) {
|
||||
if let Some(mut state) = data.config.active_state.take() {
|
||||
if let Some(transform) = data.config.saved_transform.take() {
|
||||
state.transform = transform;
|
||||
} else {
|
||||
state.transform = Affine3A::ZERO;
|
||||
}
|
||||
log::warn!("{}: active_state → ws{}", data.config.name, current_set);
|
||||
ws.overlays.insert(id, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(new_set) = new_set {
|
||||
if new_set >= self.sets.len() {
|
||||
log::warn!("switch_to_set: new_set is out of range ({new_set:?})");
|
||||
return;
|
||||
}
|
||||
|
||||
let ws = &mut self.sets[new_set];
|
||||
for (id, data) in self.overlays.iter_mut().filter(|(_, d)| !d.config.global) {
|
||||
if let Some(mut state) = ws.overlays.remove(id) {
|
||||
if state.transform.x_axis.length_squared() > f32::EPSILON {
|
||||
data.config.saved_transform = Some(state.transform);
|
||||
}
|
||||
state.transform = Affine3A::IDENTITY;
|
||||
log::warn!("{}: ws{} → active_state", data.config.name, new_set);
|
||||
data.config.active_state = Some(state);
|
||||
data.config.reset(app, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
self.current_set = new_set;
|
||||
}
|
||||
|
||||
pub fn show_hide(&mut self, app: &mut AppState) {
|
||||
if self.current_set.is_none() {
|
||||
let hmd = snap_upright(app.input_state.hmd, Vec3A::Y);
|
||||
app.anchor = hmd * self.anchor_local;
|
||||
|
||||
self.switch_to_set(app, Some(self.last_set));
|
||||
} else {
|
||||
self.switch_to_set(app, None);
|
||||
}
|
||||
|
||||
// toggle watch back on if it was hidden
|
||||
self.mut_by_id(self.watch_id).unwrap().config.activate(app);
|
||||
}
|
||||
}
|
||||
45
wlx-overlay-s/src/windowing/mod.rs
Normal file
45
wlx-overlay-s/src/windowing/mod.rs
Normal file
@@ -0,0 +1,45 @@
|
||||
use glam::{Affine3A, Vec3A};
|
||||
use slotmap::new_key_type;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub mod backend;
|
||||
pub mod manager;
|
||||
mod set;
|
||||
pub mod window;
|
||||
|
||||
new_key_type! {
|
||||
pub struct OverlayID;
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum OverlaySelector {
|
||||
Id(OverlayID),
|
||||
Name(Arc<str>),
|
||||
}
|
||||
|
||||
pub const Z_ORDER_TOAST: u32 = 70;
|
||||
pub const Z_ORDER_LINES: u32 = 69;
|
||||
pub const Z_ORDER_WATCH: u32 = 68;
|
||||
pub const Z_ORDER_ANCHOR: u32 = 67;
|
||||
pub const Z_ORDER_DEFAULT: u32 = 0;
|
||||
pub const Z_ORDER_DASHBOARD: u32 = Z_ORDER_DEFAULT;
|
||||
|
||||
pub fn snap_upright(transform: Affine3A, up_dir: Vec3A) -> Affine3A {
|
||||
if transform.x_axis.dot(up_dir).abs() < 0.2 {
|
||||
let scale = transform.x_axis.length();
|
||||
let col_z = transform.z_axis.normalize();
|
||||
let col_y = up_dir;
|
||||
let col_x = col_y.cross(col_z);
|
||||
let col_y = col_z.cross(col_x).normalize();
|
||||
let col_x = col_x.normalize();
|
||||
|
||||
Affine3A::from_cols(
|
||||
col_x * scale,
|
||||
col_y * scale,
|
||||
col_z * scale,
|
||||
transform.translation,
|
||||
)
|
||||
} else {
|
||||
transform
|
||||
}
|
||||
}
|
||||
8
wlx-overlay-s/src/windowing/set.rs
Normal file
8
wlx-overlay-s/src/windowing/set.rs
Normal file
@@ -0,0 +1,8 @@
|
||||
use slotmap::SecondaryMap;
|
||||
|
||||
use crate::windowing::{window::OverlayWindowState, OverlayID};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct OverlayWindowSet {
|
||||
pub(super) overlays: SecondaryMap<OverlayID, OverlayWindowState>,
|
||||
}
|
||||
311
wlx-overlay-s/src/windowing/window.rs
Normal file
311
wlx-overlay-s/src/windowing/window.rs
Normal file
@@ -0,0 +1,311 @@
|
||||
use glam::{Affine3A, Mat3A, Quat, Vec3, Vec3A};
|
||||
use std::{f32::consts::PI, sync::Arc};
|
||||
use vulkano::image::view::ImageView;
|
||||
|
||||
use crate::{
|
||||
graphics::CommandBuffers,
|
||||
state::AppState,
|
||||
subsystem::input::KeyboardFocus,
|
||||
windowing::{
|
||||
backend::{FrameMeta, OverlayBackend, ShouldRender},
|
||||
snap_upright,
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub enum Positioning {
|
||||
/// Stays in place, recenters relative to HMD
|
||||
#[default]
|
||||
Floating,
|
||||
/// Stays in place, recenters relative to anchor
|
||||
Anchored,
|
||||
/// Stays in place, no recentering
|
||||
Static,
|
||||
/// Following HMD
|
||||
FollowHead { lerp: f32 },
|
||||
/// Normally follows HMD, but paused due to interaction
|
||||
FollowHeadPaused { lerp: f32 },
|
||||
/// Following hand
|
||||
FollowHand { hand: usize, lerp: f32 },
|
||||
/// Normally follows hand, but paused due to interaction
|
||||
FollowHandPaused { hand: usize, lerp: f32 },
|
||||
/// Follow another overlay
|
||||
FollowOverlay { id: usize },
|
||||
}
|
||||
|
||||
impl Positioning {
|
||||
pub fn moves_with_space(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
Positioning::Floating | Positioning::Anchored | Positioning::Static
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct OverlayWindowData<T> {
|
||||
pub config: OverlayWindowConfig,
|
||||
pub data: T,
|
||||
pub birthframe: usize,
|
||||
pub primary_pointer: Option<usize>,
|
||||
}
|
||||
|
||||
impl<T> OverlayWindowData<T>
|
||||
where
|
||||
T: Default,
|
||||
{
|
||||
pub fn from_config(config: OverlayWindowConfig) -> Self {
|
||||
Self {
|
||||
data: T::default(),
|
||||
config,
|
||||
primary_pointer: None,
|
||||
birthframe: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> OverlayWindowData<T>
|
||||
where
|
||||
T: Default,
|
||||
{
|
||||
pub fn init(&mut self, app: &mut AppState) -> anyhow::Result<()> {
|
||||
//TODO: load state?
|
||||
|
||||
self.config.backend.init(app)
|
||||
}
|
||||
pub fn should_render(&mut self, app: &mut AppState) -> anyhow::Result<ShouldRender> {
|
||||
self.config.backend.should_render(app)
|
||||
}
|
||||
pub fn render(
|
||||
&mut self,
|
||||
app: &mut AppState,
|
||||
tgt: Arc<ImageView>,
|
||||
buf: &mut CommandBuffers,
|
||||
alpha: f32,
|
||||
) -> anyhow::Result<bool> {
|
||||
self.config.backend.render(app, tgt, buf, alpha)
|
||||
}
|
||||
pub fn frame_meta(&mut self) -> Option<FrameMeta> {
|
||||
self.config.backend.frame_meta()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct OverlayWindowConfig {
|
||||
pub name: Arc<str>,
|
||||
pub backend: Box<dyn OverlayBackend>,
|
||||
/// The default state to show when the overlay is newly spawned.
|
||||
pub default_state: OverlayWindowState,
|
||||
/// The current state to show. None if the overlay is hidden.
|
||||
pub active_state: Option<OverlayWindowState>,
|
||||
/// Order to draw overlays in. Overlays with higher numbers will be drawn over ones with lower numbers.
|
||||
pub z_order: u32,
|
||||
/// If set, hovering this overlay will cause the HID provider to switch focus.
|
||||
pub keyboard_focus: Option<KeyboardFocus>,
|
||||
/// Should the overlay be displayed on the next frame?
|
||||
pub show_on_spawn: bool,
|
||||
/// Does not belong to any set; switching sets does not affect this overlay.
|
||||
pub global: bool,
|
||||
/// True if transform, curvature, alpha has changed. Only used by OpenVR.
|
||||
pub dirty: bool,
|
||||
pub saved_transform: Option<Affine3A>,
|
||||
}
|
||||
|
||||
impl OverlayWindowConfig {
|
||||
pub fn from_backend(backend: Box<dyn OverlayBackend>) -> Self {
|
||||
Self {
|
||||
name: "".into(),
|
||||
backend,
|
||||
default_state: OverlayWindowState {
|
||||
transform: Affine3A::from_translation(Vec3::NEG_Z),
|
||||
..OverlayWindowState::default()
|
||||
},
|
||||
active_state: None,
|
||||
saved_transform: None,
|
||||
z_order: 0,
|
||||
keyboard_focus: None,
|
||||
show_on_spawn: false,
|
||||
global: false,
|
||||
dirty: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn activate(&mut self, app: &mut AppState) {
|
||||
log::warn!("activate {}", self.name.as_ref());
|
||||
self.dirty = true;
|
||||
self.active_state = Some(self.default_state.clone());
|
||||
self.reset(app, true);
|
||||
}
|
||||
|
||||
pub fn deactivate(&mut self) {
|
||||
log::warn!("deactivate {}", self.name.as_ref());
|
||||
self.active_state = None;
|
||||
}
|
||||
|
||||
pub fn toggle(&mut self, app: &mut AppState) {
|
||||
if self.active_state.take().is_none() {
|
||||
self.activate(app);
|
||||
} else {
|
||||
log::warn!("deactivate {}", self.name.as_ref());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn auto_movement(&mut self, app: &mut AppState) {
|
||||
let Some(state) = self.active_state.as_mut() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let cur_transform = self.saved_transform.unwrap_or(self.default_state.transform);
|
||||
|
||||
let (target_transform, lerp) = match state.positioning {
|
||||
Positioning::FollowHead { lerp } => (app.input_state.hmd * cur_transform, lerp),
|
||||
Positioning::FollowHand { hand, lerp } => {
|
||||
(app.input_state.pointers[hand].pose * cur_transform, lerp)
|
||||
}
|
||||
_ => return,
|
||||
};
|
||||
|
||||
state.transform = match lerp {
|
||||
1.0 => target_transform,
|
||||
lerp => {
|
||||
let scale = target_transform.matrix3.x_axis.length();
|
||||
|
||||
let rot_from = Quat::from_mat3a(&state.transform.matrix3.div_scalar(scale));
|
||||
let rot_to = Quat::from_mat3a(&target_transform.matrix3.div_scalar(scale));
|
||||
|
||||
let rotation = rot_from.slerp(rot_to, lerp);
|
||||
let translation = state
|
||||
.transform
|
||||
.translation
|
||||
.slerp(target_transform.translation, lerp);
|
||||
|
||||
Affine3A::from_scale_rotation_translation(
|
||||
Vec3::ONE * scale,
|
||||
rotation,
|
||||
translation.into(),
|
||||
)
|
||||
}
|
||||
};
|
||||
self.dirty = true;
|
||||
}
|
||||
|
||||
/// Returns true if changes were saved.
|
||||
pub fn save_transform(&mut self, app: &mut AppState) -> bool {
|
||||
let Some(state) = self.active_state.as_mut() else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let parent_transform = match state.positioning {
|
||||
Positioning::Floating => snap_upright(app.input_state.hmd, Vec3A::Y),
|
||||
Positioning::FollowHead { .. } | Positioning::FollowHeadPaused { .. } => {
|
||||
app.input_state.hmd
|
||||
}
|
||||
Positioning::FollowHand { hand, .. } | Positioning::FollowHandPaused { hand, .. } => {
|
||||
app.input_state.pointers[hand].pose
|
||||
}
|
||||
Positioning::Anchored => snap_upright(app.anchor, Vec3A::Y),
|
||||
Positioning::FollowOverlay { .. } | Positioning::Static => return false,
|
||||
};
|
||||
|
||||
self.saved_transform = Some(parent_transform.inverse() * state.transform);
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
pub fn reset(&mut self, app: &mut AppState, hard_reset: bool) {
|
||||
let Some(state) = self.active_state.as_mut() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let cur_transform = self.saved_transform.unwrap_or(self.default_state.transform);
|
||||
|
||||
let parent_transform = match state.positioning {
|
||||
Positioning::Floating
|
||||
| Positioning::FollowHead { .. }
|
||||
| Positioning::FollowHeadPaused { .. } => app.input_state.hmd,
|
||||
Positioning::FollowHand { hand, .. } | Positioning::FollowHandPaused { hand, .. } => {
|
||||
app.input_state.pointers[hand].pose
|
||||
}
|
||||
Positioning::Anchored => app.anchor,
|
||||
Positioning::FollowOverlay { .. } | Positioning::Static => return,
|
||||
};
|
||||
|
||||
if hard_reset {
|
||||
self.saved_transform = None;
|
||||
}
|
||||
|
||||
state.transform = parent_transform * cur_transform;
|
||||
|
||||
if state.grabbable && hard_reset {
|
||||
self.realign(&app.input_state.hmd);
|
||||
}
|
||||
self.dirty = true;
|
||||
}
|
||||
|
||||
pub fn realign(&mut self, hmd: &Affine3A) {
|
||||
let Some(state) = self.active_state.as_mut() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let to_hmd = hmd.translation - state.transform.translation;
|
||||
let up_dir: Vec3A;
|
||||
|
||||
if hmd.x_axis.dot(Vec3A::Y).abs() > 0.2 {
|
||||
// Snap upright
|
||||
up_dir = hmd.y_axis;
|
||||
} else {
|
||||
let dot = to_hmd.normalize().dot(hmd.z_axis);
|
||||
let z_dist = to_hmd.length();
|
||||
let y_dist = (state.transform.translation.y - hmd.translation.y).abs();
|
||||
let x_angle = (y_dist / z_dist).asin();
|
||||
|
||||
if dot < -f32::EPSILON {
|
||||
// facing down
|
||||
let up_point = hmd.translation + z_dist / x_angle.cos() * Vec3A::Y;
|
||||
up_dir = (up_point - state.transform.translation).normalize();
|
||||
} else if dot > f32::EPSILON {
|
||||
// facing up
|
||||
let dn_point = hmd.translation + z_dist / x_angle.cos() * Vec3A::NEG_Y;
|
||||
up_dir = (state.transform.translation - dn_point).normalize();
|
||||
} else {
|
||||
// perfectly upright
|
||||
up_dir = Vec3A::Y;
|
||||
}
|
||||
}
|
||||
|
||||
let scale = state.transform.x_axis.length();
|
||||
|
||||
let col_z = (state.transform.translation - hmd.translation).normalize();
|
||||
let col_y = up_dir;
|
||||
let col_x = col_y.cross(col_z);
|
||||
let col_y = col_z.cross(col_x).normalize();
|
||||
let col_x = col_x.normalize();
|
||||
|
||||
let rot = Mat3A::from_quat(Quat::from_axis_angle(Vec3::Y, PI));
|
||||
state.transform.matrix3 = Mat3A::from_cols(col_x, col_y, col_z).mul_scalar(scale) * rot;
|
||||
|
||||
self.dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Contains the window state for a given set
|
||||
#[derive(Clone)]
|
||||
pub struct OverlayWindowState {
|
||||
pub transform: Affine3A,
|
||||
pub alpha: f32,
|
||||
pub grabbable: bool,
|
||||
pub interactable: bool,
|
||||
pub positioning: Positioning,
|
||||
pub curvature: Option<f32>,
|
||||
}
|
||||
|
||||
impl Default for OverlayWindowState {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
grabbable: false,
|
||||
interactable: false,
|
||||
alpha: 1.0,
|
||||
positioning: Positioning::Floating,
|
||||
curvature: None,
|
||||
transform: Affine3A::IDENTITY,
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user