hide watch, switch hands, store actions.json on disk

This commit is contained in:
galister
2024-02-09 00:31:04 +01:00
parent b14e70c2e9
commit 8116864416
9 changed files with 320 additions and 151 deletions

View File

@@ -12,7 +12,7 @@ use crate::{
keyboard::create_keyboard, keyboard::create_keyboard,
screen::{get_screens_wayland, get_screens_x11}, screen::{get_screens_wayland, get_screens_x11},
toast::Toast, toast::Toast,
watch::create_watch, watch::{create_watch, WATCH_NAME, WATCH_SCALE},
}, },
state::AppState, state::AppState,
}; };
@@ -111,6 +111,10 @@ where
o.state.reset(app, false); o.state.reset(app, false);
} }
} }
// toggle watch back on if it was hidden
if !any_shown && *o.state.name == *WATCH_NAME {
o.state.spawn_scale = WATCH_SCALE * app.session.config.watch_scale;
}
}) })
} }
} }

View File

@@ -1,5 +1,6 @@
use std::{array, io::Write, path::Path, time::Duration}; use std::{array, fs::File, io::Write, time::Duration};
use anyhow::bail;
use ovr_overlay::{ use ovr_overlay::{
input::{ActionHandle, ActionSetHandle, ActiveActionSet, InputManager, InputValueHandle}, input::{ActionHandle, ActionSetHandle, ActiveActionSet, InputManager, InputValueHandle},
sys::{ sys::{
@@ -12,6 +13,7 @@ use ovr_overlay::{
use crate::{ use crate::{
backend::input::{Haptics, TrackedDevice, TrackedDeviceRole}, backend::input::{Haptics, TrackedDevice, TrackedDeviceRole},
config_io::CONFIG_ROOT_PATH,
state::AppState, state::AppState,
}; };
@@ -282,29 +284,42 @@ fn get_tracked_device(
}) })
} }
pub fn action_manifest_path() -> &'static Path { pub fn set_action_manifest(input: &mut InputManager) -> anyhow::Result<()> {
let action_path = "/tmp/wlxoverlay-s/actions.json"; let action_path = CONFIG_ROOT_PATH.join("actions.json");
std::fs::create_dir_all("/tmp/wlxoverlay-s").unwrap();
std::fs::File::create(action_path) if !action_path.is_file() {
.unwrap() File::create(&action_path)
.write_all(include_bytes!("../../res/actions.json")) .unwrap()
.unwrap(); .write_all(include_bytes!("../../res/actions.json"))
.unwrap();
}
std::fs::File::create("/tmp/wlxoverlay-s/actions_binding_knuckles.json") let binding_path = CONFIG_ROOT_PATH.join("actions_binding_knuckles.json");
.unwrap() if !binding_path.is_file() {
.write_all(include_bytes!("../../res/actions_binding_knuckles.json")) File::create(&binding_path)
.unwrap(); .unwrap()
.write_all(include_bytes!("../../res/actions_binding_knuckles.json"))
.unwrap();
}
std::fs::File::create("/tmp/wlxoverlay-s/actions_binding_vive.json") let binding_path = CONFIG_ROOT_PATH.join("actions_binding_vive.json");
.unwrap() if !binding_path.is_file() {
.write_all(include_bytes!("../../res/actions_binding_vive.json")) File::create(&binding_path)
.unwrap(); .unwrap()
.write_all(include_bytes!("../../res/actions_binding_vive.json"))
.unwrap();
}
std::fs::File::create("/tmp/wlxoverlay-s/actions_binding_oculus.json") let binding_path = CONFIG_ROOT_PATH.join("actions_binding_oculus.json");
.unwrap() if !binding_path.is_file() {
.write_all(include_bytes!("../../res/actions_binding_oculus.json")) File::create(&binding_path)
.unwrap(); .unwrap()
.write_all(include_bytes!("../../res/actions_binding_oculus.json"))
.unwrap();
}
Path::new(action_path) if let Err(e) = input.set_action_manifest(action_path.as_path()) {
bail!("Failed to set action manifest: {}", e.description());
}
Ok(())
} }

View File

@@ -20,7 +20,12 @@ use vulkano::{
use crate::{ use crate::{
backend::{ backend::{
input::interact, input::interact,
openvr::{input::OpenVrInputSource, lines::LinePool, manifest::install_manifest}, openvr::{
input::{set_action_manifest, OpenVrInputSource},
lines::LinePool,
manifest::{install_manifest, uninstall_manifest},
overlay::OpenVrOverlayData,
},
osc::OscSender, osc::OscSender,
}, },
graphics::WlxGraphics, graphics::WlxGraphics,
@@ -28,8 +33,6 @@ use crate::{
state::AppState, state::AppState,
}; };
use self::{input::action_manifest_path, manifest::uninstall_manifest, overlay::OpenVrOverlayData};
use super::common::{BackendError, OverlayContainer, TaskType}; use super::common::{BackendError, OverlayContainer, TaskType};
pub mod helpers; pub mod helpers;
@@ -94,8 +97,8 @@ pub fn openvr_run(running: Arc<AtomicBool>) -> Result<(), BackendError> {
state.hid_provider.set_desktop_extent(overlays.extent); state.hid_provider.set_desktop_extent(overlays.extent);
if let Err(e) = input_mngr.set_action_manifest(action_manifest_path()) { if let Err(e) = set_action_manifest(&mut input_mngr) {
log::error!("Failed to set action manifest: {}", e.description()); log::error!("{}", e.to_string());
return Err(BackendError::Fatal); return Err(BackendError::Fatal);
}; };

View File

@@ -59,6 +59,7 @@ impl OverlayData<OpenVrOverlayData> {
self.upload_width(overlay); self.upload_width(overlay);
self.upload_color(overlay); self.upload_color(overlay);
self.upload_alpha(overlay);
self.upload_curvature(overlay); self.upload_curvature(overlay);
self.upload_sort_order(overlay); self.upload_sort_order(overlay);
@@ -77,6 +78,7 @@ impl OverlayData<OpenVrOverlayData> {
if self.data.visible { if self.data.visible {
if self.state.dirty { if self.state.dirty {
self.upload_transform(overlay); self.upload_transform(overlay);
self.upload_alpha(overlay);
self.state.dirty = false; self.state.dirty = false;
} }
self.upload_texture(overlay, graphics); self.upload_texture(overlay, graphics);
@@ -108,14 +110,21 @@ impl OverlayData<OpenVrOverlayData> {
self.backend.pause(app); self.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);
return;
};
if let Err(e) = overlay.set_opacity(handle, self.state.alpha) {
panic!("Failed to set overlay alpha: {}", e);
}
}
pub(super) fn upload_color(&self, overlay: &mut OverlayManager) { pub(super) fn upload_color(&self, overlay: &mut OverlayManager) {
let Some(handle) = self.data.handle else { let Some(handle) = self.data.handle else {
log::debug!("{}: No overlay handle", self.state.name); log::debug!("{}: No overlay handle", self.state.name);
return; return;
}; };
if let Err(e) = overlay.set_opacity(handle, self.data.color.w) {
panic!("Failed to set overlay opacity: {}", e);
}
if let Err(e) = overlay.set_tint( if let Err(e) = overlay.set_tint(
handle, handle,
ovr_overlay::ColorTint { ovr_overlay::ColorTint {

View File

@@ -27,6 +27,7 @@ pub struct OverlayState {
pub interactable: bool, pub interactable: bool,
pub recenter: bool, pub recenter: bool,
pub dirty: bool, pub dirty: bool,
pub alpha: f32,
pub transform: Affine3A, pub transform: Affine3A,
pub saved_point: Option<Vec3A>, pub saved_point: Option<Vec3A>,
pub spawn_scale: f32, // aka width pub spawn_scale: f32, // aka width
@@ -49,6 +50,7 @@ impl Default for OverlayState {
recenter: false, recenter: false,
interactable: false, interactable: false,
dirty: true, dirty: true,
alpha: 1.0,
relative_to: RelativeTo::None, relative_to: RelativeTo::None,
saved_point: None, saved_point: None,
spawn_scale: 1.0, spawn_scale: 1.0,

View File

@@ -31,6 +31,10 @@ fn def_half() -> f32 {
0.5 0.5
} }
fn def_point7() -> f32 {
0.7
}
fn def_osc_port() -> u16 { fn def_osc_port() -> u16 {
9000 9000
} }
@@ -53,7 +57,10 @@ pub struct GeneralConfig {
pub watch_scale: f32, pub watch_scale: f32,
#[serde(default = "def_half")] #[serde(default = "def_half")]
pub watch_view_angle: f32, pub watch_view_angle_min: f32,
#[serde(default = "def_point7")]
pub watch_view_angle_max: f32,
#[serde(default = "def_pw_tokens")] #[serde(default = "def_pw_tokens")]
pub pw_tokens: Vec<(String, String)>, pub pw_tokens: Vec<(String, String)>,

View File

@@ -1,4 +1,5 @@
use std::{ use std::{
f32::consts::PI,
io::Read, io::Read,
process::{self, Stdio}, process::{self, Stdio},
sync::Arc, sync::Arc,
@@ -7,7 +8,7 @@ use std::{
use chrono::Local; use chrono::Local;
use chrono_tz::Tz; use chrono_tz::Tz;
use glam::{vec2, Affine2, Vec3, Vec3A}; use glam::{vec2, Affine2, Quat, Vec3, Vec3A};
use serde::Deserialize; use serde::Deserialize;
use crate::{ use crate::{
@@ -30,6 +31,7 @@ const FALLBACK_COLOR: Vec3 = Vec3 {
}; };
pub const WATCH_NAME: &str = "watch"; pub const WATCH_NAME: &str = "watch";
pub const WATCH_SCALE: f32 = 0.11;
pub fn create_watch<O>(state: &AppState, screens: &[OverlayData<O>]) -> OverlayData<O> pub fn create_watch<O>(state: &AppState, screens: &[OverlayData<O>]) -> OverlayData<O>
where where
@@ -85,12 +87,9 @@ where
}; };
let label = canvas.label(x, y, w, h, empty_str.clone()); let label = canvas.label(x, y, w, h, empty_str.clone());
label.state = Some(ElemState { label.state = Some(ElemState::Clock {
clock: Some(ClockState { timezone: tz,
timezone: tz, format,
format,
}),
..Default::default()
}); });
label.on_update = Some(clock_update); label.on_update = Some(clock_update);
} }
@@ -104,15 +103,11 @@ where
canvas.font_size = font_size; canvas.font_size = font_size;
canvas.fg_color = color_parse(&fg_color).unwrap_or(FALLBACK_COLOR); canvas.fg_color = color_parse(&fg_color).unwrap_or(FALLBACK_COLOR);
let label = canvas.label(x, y, w, h, empty_str.clone()); let label = canvas.label(x, y, w, h, empty_str.clone());
label.state = Some(ElemState { label.state = Some(ElemState::AutoExec {
exec: Some(ExecState { last_exec: Instant::now(),
last_exec: Instant::now(), interval,
interval, exec,
exec, child: None,
child: None,
}),
button: None,
..Default::default()
}); });
label.on_update = Some(exec_label_update); label.on_update = Some(exec_label_update);
} }
@@ -128,22 +123,30 @@ where
canvas.fg_color = color_parse(&fg_color).unwrap_or(FALLBACK_COLOR); canvas.fg_color = color_parse(&fg_color).unwrap_or(FALLBACK_COLOR);
canvas.font_size = font_size; canvas.font_size = font_size;
let button = canvas.button(x, y, w, h, text.clone()); let button = canvas.button(x, y, w, h, text.clone());
button.state = Some(ElemState { button.state = Some(ElemState::ExecButton { exec, child: None });
exec: Some(ExecState {
last_exec: Instant::now(),
interval: 0.,
exec,
child: None,
}),
button: Some(WatchButtonState {
pressed_at: Instant::now(),
mode: PointerMode::Left,
overlay: None,
}),
..Default::default()
});
button.on_press = Some(exec_button); button.on_press = Some(exec_button);
} }
WatchElement::FuncButton {
rect: [x, y, w, h],
font_size,
bg_color,
fg_color,
func,
func_right,
func_middle,
text,
} => {
canvas.bg_color = color_parse(&bg_color).unwrap_or(FALLBACK_COLOR);
canvas.fg_color = color_parse(&fg_color).unwrap_or(FALLBACK_COLOR);
canvas.font_size = font_size;
let button = canvas.button(x, y, w, h, text.clone());
button.state = Some(ElemState::FuncButton {
func,
func_right,
func_middle,
});
button.on_press = Some(btn_func_dn);
}
WatchElement::Batteries { WatchElement::Batteries {
rect, rect,
font_size, font_size,
@@ -171,10 +174,7 @@ where
button_h - 4., button_h - 4.,
empty_str.clone(), empty_str.clone(),
); );
label.state = Some(ElemState { label.state = Some(ElemState::Battery { device: i as _ });
battery: Some(i as usize),
..Default::default()
});
label.on_update = Some(battery_update); label.on_update = Some(battery_update);
button_x += match layout { button_x += match layout {
@@ -215,13 +215,10 @@ where
button_h - 4., button_h - 4.,
KEYBOARD_NAME.into(), KEYBOARD_NAME.into(),
); );
keyboard.state = Some(ElemState { keyboard.state = Some(ElemState::OverlayButton {
button: Some(WatchButtonState { pressed_at: Instant::now(),
pressed_at: Instant::now(), mode: PointerMode::Left,
overlay: Some(OverlaySelector::Name(KEYBOARD_NAME.into())), overlay: OverlaySelector::Name(KEYBOARD_NAME.into()),
mode: PointerMode::Left,
}),
..Default::default()
}); });
keyboard.on_press = Some(overlay_button_dn); keyboard.on_press = Some(overlay_button_dn);
keyboard.on_release = Some(overlay_button_up); keyboard.on_release = Some(overlay_button_up);
@@ -246,13 +243,10 @@ where
button_h - 4., button_h - 4.,
screen.state.name.clone(), screen.state.name.clone(),
); );
button.state = Some(ElemState { button.state = Some(ElemState::OverlayButton {
button: Some(WatchButtonState { pressed_at: Instant::now(),
pressed_at: Instant::now(), mode: PointerMode::Left,
overlay: Some(OverlaySelector::Id(screen.state.id)), overlay: OverlaySelector::Id(screen.state.id),
mode: PointerMode::Left,
}),
..Default::default()
}); });
button.on_press = Some(overlay_button_dn); button.on_press = Some(overlay_button_dn);
@@ -273,7 +267,7 @@ where
size: (400, 200), size: (400, 200),
want_visible: true, want_visible: true,
interactable: true, interactable: true,
spawn_scale: 0.11 * state.session.config.watch_scale, spawn_scale: WATCH_SCALE * state.session.config.watch_scale,
spawn_point: state.session.watch_pos.into(), spawn_point: state.session.watch_pos.into(),
spawn_rotation: state.session.watch_rot, spawn_rotation: state.session.watch_rot,
interaction_transform, interaction_transform,
@@ -285,37 +279,105 @@ where
} }
} }
#[derive(Default)] enum ElemState {
struct ElemState { Battery {
battery: Option<usize>, device: usize,
clock: Option<ClockState>, },
exec: Option<ExecState>, Clock {
button: Option<WatchButtonState>, timezone: Option<Tz>,
format: Arc<str>,
},
AutoExec {
last_exec: Instant,
interval: f32,
exec: Vec<Arc<str>>,
child: Option<process::Child>,
},
OverlayButton {
pressed_at: Instant,
mode: PointerMode,
overlay: OverlaySelector,
},
ExecButton {
exec: Vec<Arc<str>>,
child: Option<process::Child>,
},
FuncButton {
func: ButtonFunc,
func_right: Option<ButtonFunc>,
func_middle: Option<ButtonFunc>,
},
} }
struct ClockState { fn btn_func_dn(
timezone: Option<Tz>, control: &mut Control<(), ElemState>,
format: Arc<str>, _: &mut (),
} app: &mut AppState,
struct WatchButtonState {
pressed_at: Instant,
mode: PointerMode, mode: PointerMode,
overlay: Option<OverlaySelector>, ) {
} let ElemState::FuncButton {
func,
func_right,
func_middle,
} = control.state.as_ref().unwrap()
else {
log::error!("FuncButton state not found");
return;
};
struct ExecState { let func = match mode {
last_exec: Instant, PointerMode::Left => func,
interval: f32, PointerMode::Right => func_right.as_ref().unwrap_or(func),
exec: Vec<Arc<str>>, PointerMode::Middle => func_middle.as_ref().unwrap_or(func),
child: Option<process::Child>, _ => return,
};
match func {
ButtonFunc::HideWatch => {
app.tasks.enqueue(TaskType::Overlay(
OverlaySelector::Name(WATCH_NAME.into()),
Box::new(|app, o| {
o.want_visible = false;
o.spawn_scale = 0.0;
app.tasks.enqueue(TaskType::Toast(Toast::new(
"Watch hidden".into(),
"Use show/hide button to restore.".into(),
)))
}),
));
}
ButtonFunc::SwitchWatchHand => {
app.tasks.enqueue(TaskType::Overlay(
OverlaySelector::Name(WATCH_NAME.into()),
Box::new(|app, o| {
if let RelativeTo::Hand(0) = o.relative_to {
o.relative_to = RelativeTo::Hand(1);
o.spawn_rotation = app.session.watch_rot;
o.spawn_rotation = app.session.watch_rot
* Quat::from_rotation_x(PI)
* Quat::from_rotation_z(PI);
o.spawn_point = app.session.watch_pos.into();
o.spawn_point.x *= -1.;
} else {
o.relative_to = RelativeTo::Hand(0);
o.spawn_rotation = app.session.watch_rot;
o.spawn_point = app.session.watch_pos.into();
}
app.tasks.enqueue(TaskType::Toast(Toast::new(
"Watch switched".into(),
"Check your other hand".into(),
)))
}),
));
}
}
} }
fn battery_update(control: &mut Control<(), ElemState>, _: &mut (), app: &mut AppState) { fn battery_update(control: &mut Control<(), ElemState>, _: &mut (), app: &mut AppState) {
let state = control.state.as_ref().unwrap(); let ElemState::Battery { device } = control.state.as_ref().unwrap() else {
let device_idx = state.battery.unwrap(); return;
};
let device = app.input_state.devices.get(device_idx); let device = app.input_state.devices.get(*device);
let tags = ["", "H", "L", "R", "T"]; let tags = ["", "H", "L", "R", "T"];
@@ -336,30 +398,37 @@ fn exec_button(
_: &mut AppState, _: &mut AppState,
_mode: PointerMode, _mode: PointerMode,
) { ) {
let state = control.state.as_mut().unwrap(); let ElemState::ExecButton {
let exec = state.exec.as_mut().unwrap(); exec,
if let Some(child) = &mut exec.child { ref mut child,
match child.try_wait() { ..
} = control.state.as_mut().unwrap()
else {
log::error!("ExecButton state not found");
return;
};
if let Some(proc) = child {
match proc.try_wait() {
Ok(Some(code)) => { Ok(Some(code)) => {
if !code.success() { if !code.success() {
log::error!("Child process exited with code: {}", code); log::error!("Child process exited with code: {}", code);
} }
exec.child = None; *child = None;
} }
Ok(None) => { Ok(None) => {
log::warn!("Unable to launch child process: previous child not exited yet"); log::warn!("Unable to launch child process: previous child not exited yet");
return; return;
} }
Err(e) => { Err(e) => {
exec.child = None; *child = None;
log::error!("Error checking child process: {:?}", e); log::error!("Error checking child process: {:?}", e);
} }
} }
} }
let args = exec.exec.iter().map(|s| s.as_ref()).collect::<Vec<&str>>(); let args = exec.iter().map(|s| s.as_ref()).collect::<Vec<&str>>();
match process::Command::new(args[0]).args(&args[1..]).spawn() { match process::Command::new(args[0]).args(&args[1..]).spawn() {
Ok(child) => { Ok(proc) => {
exec.child = Some(child); *child = Some(proc);
} }
Err(e) => { Err(e) => {
log::error!("Failed to spawn process {:?}: {:?}", args, e); log::error!("Failed to spawn process {:?}: {:?}", args, e);
@@ -368,16 +437,24 @@ fn exec_button(
} }
fn exec_label_update(control: &mut Control<(), ElemState>, _: &mut (), _: &mut AppState) { fn exec_label_update(control: &mut Control<(), ElemState>, _: &mut (), _: &mut AppState) {
let state = control.state.as_mut().unwrap(); let ElemState::AutoExec {
let exec = state.exec.as_mut().unwrap(); ref mut last_exec,
interval,
exec,
ref mut child,
} = control.state.as_mut().unwrap()
else {
log::error!("AutoExec state not found");
return;
};
if let Some(mut child) = exec.child.take() { if let Some(mut proc) = child.take() {
match child.try_wait() { match proc.try_wait() {
Ok(Some(code)) => { Ok(Some(code)) => {
if !code.success() { if !code.success() {
log::error!("Child process exited with code: {}", code); log::error!("Child process exited with code: {}", code);
} else { } else {
if let Some(mut stdout) = child.stdout.take() { if let Some(mut stdout) = proc.stdout.take() {
let mut buf = String::new(); let mut buf = String::new();
if let Ok(_) = stdout.read_to_string(&mut buf) { if let Ok(_) = stdout.read_to_string(&mut buf) {
control.set_text(&buf); control.set_text(&buf);
@@ -393,12 +470,12 @@ fn exec_label_update(control: &mut Control<(), ElemState>, _: &mut (), _: &mut A
} }
} }
Ok(None) => { Ok(None) => {
exec.child = Some(child); *child = Some(proc);
// not exited yet // not exited yet
return; return;
} }
Err(e) => { Err(e) => {
exec.child = None; *child = None;
log::error!("Error checking child process: {:?}", e); log::error!("Error checking child process: {:?}", e);
return; return;
} }
@@ -406,20 +483,20 @@ fn exec_label_update(control: &mut Control<(), ElemState>, _: &mut (), _: &mut A
} }
if Instant::now() if Instant::now()
.saturating_duration_since(exec.last_exec) .saturating_duration_since(*last_exec)
.as_secs_f32() .as_secs_f32()
> exec.interval > *interval
{ {
exec.last_exec = Instant::now(); *last_exec = Instant::now();
let args = exec.exec.iter().map(|s| s.as_ref()).collect::<Vec<&str>>(); let args = exec.iter().map(|s| s.as_ref()).collect::<Vec<&str>>();
match process::Command::new(args[0]) match process::Command::new(args[0])
.args(&args[1..]) .args(&args[1..])
.stdout(Stdio::piped()) .stdout(Stdio::piped())
.spawn() .spawn()
{ {
Ok(child) => { Ok(proc) => {
exec.child = Some(child); *child = Some(proc);
} }
Err(e) => { Err(e) => {
log::error!("Failed to spawn process {:?}: {:?}", args, e); log::error!("Failed to spawn process {:?}: {:?}", args, e);
@@ -429,17 +506,17 @@ fn exec_label_update(control: &mut Control<(), ElemState>, _: &mut (), _: &mut A
} }
fn clock_update(control: &mut Control<(), ElemState>, _: &mut (), _: &mut AppState) { fn clock_update(control: &mut Control<(), ElemState>, _: &mut (), _: &mut AppState) {
let state = control.state.as_mut().unwrap(); let ElemState::Clock { timezone, format } = control.state.as_ref().unwrap() else {
let clock = state.clock.as_mut().unwrap(); log::error!("Clock state not found");
return;
};
let fmt = clock.format.clone(); if let Some(tz) = timezone {
let date = Local::now().with_timezone(tz);
if let Some(tz) = clock.timezone { control.set_text(&format!("{}", &date.format(format)));
let date = Local::now().with_timezone(&tz);
control.set_text(&format!("{}", &date.format(&fmt)));
} else { } else {
let date = Local::now(); let date = Local::now();
control.set_text(&format!("{}", &date.format(&fmt))); control.set_text(&format!("{}", &date.format(format)));
} }
} }
@@ -447,25 +524,41 @@ fn overlay_button_dn(
control: &mut Control<(), ElemState>, control: &mut Control<(), ElemState>,
_: &mut (), _: &mut (),
_: &mut AppState, _: &mut AppState,
mode: PointerMode, ptr_mode: PointerMode,
) { ) {
let btn = control.state.as_mut().unwrap().button.as_mut().unwrap(); let ElemState::OverlayButton {
btn.pressed_at = Instant::now(); ref mut pressed_at,
btn.mode = mode; ref mut mode,
..
} = control.state.as_mut().unwrap()
else {
log::error!("OverlayButton state not found");
return;
};
*pressed_at = Instant::now();
*mode = ptr_mode;
} }
fn overlay_button_up(control: &mut Control<(), ElemState>, _: &mut (), app: &mut AppState) { fn overlay_button_up(control: &mut Control<(), ElemState>, _: &mut (), app: &mut AppState) {
let btn = control.state.as_mut().unwrap().button.as_mut().unwrap(); let ElemState::OverlayButton {
let selector = btn.overlay.as_ref().unwrap().clone(); pressed_at,
mode,
overlay,
} = control.state.as_ref().unwrap()
else {
log::error!("OverlayButton state not found");
return;
};
if Instant::now() if Instant::now()
.saturating_duration_since(btn.pressed_at) .saturating_duration_since(*pressed_at)
.as_millis() .as_millis()
< 2000 < 2000
{ {
match btn.mode { match mode {
PointerMode::Left => { PointerMode::Left => {
app.tasks.enqueue(TaskType::Overlay( app.tasks.enqueue(TaskType::Overlay(
selector, overlay.clone(),
Box::new(|app, o| { Box::new(|app, o| {
o.want_visible = !o.want_visible; o.want_visible = !o.want_visible;
if o.recenter { if o.recenter {
@@ -477,7 +570,7 @@ fn overlay_button_up(control: &mut Control<(), ElemState>, _: &mut (), app: &mut
} }
PointerMode::Right => { PointerMode::Right => {
app.tasks.enqueue(TaskType::Overlay( app.tasks.enqueue(TaskType::Overlay(
selector, overlay.clone(),
Box::new(|app, o| { Box::new(|app, o| {
o.recenter = !o.recenter; o.recenter = !o.recenter;
o.grabbable = o.recenter; o.grabbable = o.recenter;
@@ -493,7 +586,7 @@ fn overlay_button_up(control: &mut Control<(), ElemState>, _: &mut (), app: &mut
} }
PointerMode::Middle => { PointerMode::Middle => {
app.tasks.enqueue(TaskType::Overlay( app.tasks.enqueue(TaskType::Overlay(
selector, overlay.clone(),
Box::new(|app, o| { Box::new(|app, o| {
o.interactable = !o.interactable; o.interactable = !o.interactable;
if !o.interactable { if !o.interactable {
@@ -509,7 +602,7 @@ fn overlay_button_up(control: &mut Control<(), ElemState>, _: &mut (), app: &mut
} }
} else { } else {
app.tasks.enqueue(TaskType::Overlay( app.tasks.enqueue(TaskType::Overlay(
selector, overlay.clone(),
Box::new(|app, o| { Box::new(|app, o| {
o.reset(app, true); o.reset(app, true);
}), }),
@@ -582,6 +675,22 @@ enum WatchElement {
scr_bg_color: Arc<str>, scr_bg_color: Arc<str>,
layout: ListLayout, layout: ListLayout,
}, },
FuncButton {
rect: [f32; 4],
font_size: isize,
bg_color: Arc<str>,
fg_color: Arc<str>,
func: ButtonFunc,
func_right: Option<ButtonFunc>,
func_middle: Option<ButtonFunc>,
text: Arc<str>,
},
}
#[derive(Deserialize)]
enum ButtonFunc {
HideWatch,
SwitchWatchHand,
} }
#[derive(Deserialize)] #[derive(Deserialize)]
@@ -604,6 +713,11 @@ where
.mut_by_selector(&OverlaySelector::Name(WATCH_NAME.into())) .mut_by_selector(&OverlaySelector::Name(WATCH_NAME.into()))
.unwrap(); .unwrap();
if watch.state.spawn_scale < f32::EPSILON {
watch.state.want_visible = false;
return;
}
let to_hmd = (watch.state.transform.translation - app.input_state.hmd.translation).normalize(); let to_hmd = (watch.state.transform.translation - app.input_state.hmd.translation).normalize();
let watch_normal = watch let watch_normal = watch
.state .state
@@ -612,9 +726,14 @@ where
.normalize(); .normalize();
let dot = to_hmd.dot(watch_normal); let dot = to_hmd.dot(watch_normal);
if dot < app.session.config.watch_view_angle { if dot < app.session.config.watch_view_angle_min {
watch.state.want_visible = false; watch.state.want_visible = false;
} else { } else {
watch.state.want_visible = true; watch.state.want_visible = true;
watch.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.);
} }
} }

View File

@@ -40,9 +40,19 @@ watch_elements:
rect: [0, 0, 400, 200] rect: [0, 0, 400, 200]
bg_color: "#353535" bg_color: "#353535"
- type: FuncButton
rect: [2, 162, 26, 36]
font_size: 14
bg_color: "#808040"
fg_color: "#ffffff"
func: SwitchWatchHand
func_right: HideWatch
func_middle: ~
text: "W"
# bottom row, of keyboard + overlays # bottom row, of keyboard + overlays
- type: OverlayList - type: OverlayList
rect: [0, 160, 400, 40] rect: [30, 160, 370, 40]
font_size: 14 font_size: 14
kbd_fg_color: "#FFFFFF" kbd_fg_color: "#FFFFFF"
kbd_bg_color: "#406050" kbd_bg_color: "#406050"
@@ -96,9 +106,9 @@ watch_elements:
text: "Chicago" # change TZ2 label here text: "Chicago" # change TZ2 label here
- type: Batteries - type: Batteries
rect: [0, 0, 400, 40] rect: [0, 0, 400, 30]
font_size: 14 font_size: 14
num_devices: 9 num_devices: 6
low_threshold: 15 low_threshold: 15
layout: Horizontal layout: Horizontal
normal_fg_color: "#99BBAA" normal_fg_color: "#99BBAA"

View File

@@ -13,7 +13,7 @@ use crate::{
shaders::{frag_color, frag_glyph, frag_sprite, frag_srgb, vert_common}, shaders::{frag_color, frag_glyph, frag_sprite, frag_srgb, vert_common},
}; };
pub const WATCH_DEFAULT_POS: Vec3 = Vec3::new(-0.03, -0.01, 0.1); pub const WATCH_DEFAULT_POS: Vec3 = Vec3::new(-0.03, -0.01, 0.125);
pub const WATCH_DEFAULT_ROT: Quat = Quat::from_xyzw(0.7071066, 0., 0.7071066, 0.0007963); pub const WATCH_DEFAULT_ROT: Quat = Quat::from_xyzw(0.7071066, 0., 0.7071066, 0.0007963);
pub struct AppState { pub struct AppState {