hide watch, switch hands, store actions.json on disk
This commit is contained in:
@@ -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;
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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)>,
|
||||||
|
|||||||
@@ -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.);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user