diff --git a/src/backend/common.rs b/src/backend/common.rs index 0a9e532..947af58 100644 --- a/src/backend/common.rs +++ b/src/backend/common.rs @@ -12,7 +12,7 @@ use crate::{ keyboard::create_keyboard, screen::{get_screens_wayland, get_screens_x11}, toast::Toast, - watch::create_watch, + watch::{create_watch, WATCH_NAME, WATCH_SCALE}, }, state::AppState, }; @@ -111,6 +111,10 @@ where 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; + } }) } } diff --git a/src/backend/openvr/input.rs b/src/backend/openvr/input.rs index 4ac313c..ee4fdcb 100644 --- a/src/backend/openvr/input.rs +++ b/src/backend/openvr/input.rs @@ -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::{ input::{ActionHandle, ActionSetHandle, ActiveActionSet, InputManager, InputValueHandle}, sys::{ @@ -12,6 +13,7 @@ use ovr_overlay::{ use crate::{ backend::input::{Haptics, TrackedDevice, TrackedDeviceRole}, + config_io::CONFIG_ROOT_PATH, state::AppState, }; @@ -282,29 +284,42 @@ fn get_tracked_device( }) } -pub fn action_manifest_path() -> &'static Path { - let action_path = "/tmp/wlxoverlay-s/actions.json"; - std::fs::create_dir_all("/tmp/wlxoverlay-s").unwrap(); +pub fn set_action_manifest(input: &mut InputManager) -> anyhow::Result<()> { + let action_path = CONFIG_ROOT_PATH.join("actions.json"); - std::fs::File::create(action_path) - .unwrap() - .write_all(include_bytes!("../../res/actions.json")) - .unwrap(); + if !action_path.is_file() { + File::create(&action_path) + .unwrap() + .write_all(include_bytes!("../../res/actions.json")) + .unwrap(); + } - std::fs::File::create("/tmp/wlxoverlay-s/actions_binding_knuckles.json") - .unwrap() - .write_all(include_bytes!("../../res/actions_binding_knuckles.json")) - .unwrap(); + let binding_path = CONFIG_ROOT_PATH.join("actions_binding_knuckles.json"); + if !binding_path.is_file() { + File::create(&binding_path) + .unwrap() + .write_all(include_bytes!("../../res/actions_binding_knuckles.json")) + .unwrap(); + } - std::fs::File::create("/tmp/wlxoverlay-s/actions_binding_vive.json") - .unwrap() - .write_all(include_bytes!("../../res/actions_binding_vive.json")) - .unwrap(); + let binding_path = CONFIG_ROOT_PATH.join("actions_binding_vive.json"); + if !binding_path.is_file() { + File::create(&binding_path) + .unwrap() + .write_all(include_bytes!("../../res/actions_binding_vive.json")) + .unwrap(); + } - std::fs::File::create("/tmp/wlxoverlay-s/actions_binding_oculus.json") - .unwrap() - .write_all(include_bytes!("../../res/actions_binding_oculus.json")) - .unwrap(); + let binding_path = CONFIG_ROOT_PATH.join("actions_binding_oculus.json"); + if !binding_path.is_file() { + File::create(&binding_path) + .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(()) } diff --git a/src/backend/openvr/mod.rs b/src/backend/openvr/mod.rs index 314d27d..889dc52 100644 --- a/src/backend/openvr/mod.rs +++ b/src/backend/openvr/mod.rs @@ -20,7 +20,12 @@ use vulkano::{ use crate::{ backend::{ 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, }, graphics::WlxGraphics, @@ -28,8 +33,6 @@ use crate::{ state::AppState, }; -use self::{input::action_manifest_path, manifest::uninstall_manifest, overlay::OpenVrOverlayData}; - use super::common::{BackendError, OverlayContainer, TaskType}; pub mod helpers; @@ -94,8 +97,8 @@ pub fn openvr_run(running: Arc) -> Result<(), BackendError> { state.hid_provider.set_desktop_extent(overlays.extent); - if let Err(e) = input_mngr.set_action_manifest(action_manifest_path()) { - log::error!("Failed to set action manifest: {}", e.description()); + if let Err(e) = set_action_manifest(&mut input_mngr) { + log::error!("{}", e.to_string()); return Err(BackendError::Fatal); }; diff --git a/src/backend/openvr/overlay.rs b/src/backend/openvr/overlay.rs index b505a16..459caa1 100644 --- a/src/backend/openvr/overlay.rs +++ b/src/backend/openvr/overlay.rs @@ -59,6 +59,7 @@ impl OverlayData { self.upload_width(overlay); self.upload_color(overlay); + self.upload_alpha(overlay); self.upload_curvature(overlay); self.upload_sort_order(overlay); @@ -77,6 +78,7 @@ impl OverlayData { if self.data.visible { if self.state.dirty { self.upload_transform(overlay); + self.upload_alpha(overlay); self.state.dirty = false; } self.upload_texture(overlay, graphics); @@ -108,14 +110,21 @@ impl OverlayData { 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) { 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.data.color.w) { - panic!("Failed to set overlay opacity: {}", e); - } if let Err(e) = overlay.set_tint( handle, ovr_overlay::ColorTint { diff --git a/src/backend/overlay.rs b/src/backend/overlay.rs index ae5a04f..451d7db 100644 --- a/src/backend/overlay.rs +++ b/src/backend/overlay.rs @@ -27,6 +27,7 @@ pub struct OverlayState { pub interactable: bool, pub recenter: bool, pub dirty: bool, + pub alpha: f32, pub transform: Affine3A, pub saved_point: Option, pub spawn_scale: f32, // aka width @@ -49,6 +50,7 @@ impl Default for OverlayState { recenter: false, interactable: false, dirty: true, + alpha: 1.0, relative_to: RelativeTo::None, saved_point: None, spawn_scale: 1.0, diff --git a/src/config.rs b/src/config.rs index 58030da..ad5dbdc 100644 --- a/src/config.rs +++ b/src/config.rs @@ -31,6 +31,10 @@ fn def_half() -> f32 { 0.5 } +fn def_point7() -> f32 { + 0.7 +} + fn def_osc_port() -> u16 { 9000 } @@ -53,7 +57,10 @@ pub struct GeneralConfig { pub watch_scale: f32, #[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")] pub pw_tokens: Vec<(String, String)>, diff --git a/src/overlays/watch.rs b/src/overlays/watch.rs index 6bfa480..df35714 100644 --- a/src/overlays/watch.rs +++ b/src/overlays/watch.rs @@ -1,4 +1,5 @@ use std::{ + f32::consts::PI, io::Read, process::{self, Stdio}, sync::Arc, @@ -7,7 +8,7 @@ use std::{ use chrono::Local; use chrono_tz::Tz; -use glam::{vec2, Affine2, Vec3, Vec3A}; +use glam::{vec2, Affine2, Quat, Vec3, Vec3A}; use serde::Deserialize; use crate::{ @@ -30,6 +31,7 @@ const FALLBACK_COLOR: Vec3 = Vec3 { }; pub const WATCH_NAME: &str = "watch"; +pub const WATCH_SCALE: f32 = 0.11; pub fn create_watch(state: &AppState, screens: &[OverlayData]) -> OverlayData where @@ -85,12 +87,9 @@ where }; let label = canvas.label(x, y, w, h, empty_str.clone()); - label.state = Some(ElemState { - clock: Some(ClockState { - timezone: tz, - format, - }), - ..Default::default() + label.state = Some(ElemState::Clock { + timezone: tz, + format, }); label.on_update = Some(clock_update); } @@ -104,15 +103,11 @@ where canvas.font_size = font_size; canvas.fg_color = color_parse(&fg_color).unwrap_or(FALLBACK_COLOR); let label = canvas.label(x, y, w, h, empty_str.clone()); - label.state = Some(ElemState { - exec: Some(ExecState { - last_exec: Instant::now(), - interval, - exec, - child: None, - }), - button: None, - ..Default::default() + label.state = Some(ElemState::AutoExec { + last_exec: Instant::now(), + interval, + exec, + child: None, }); label.on_update = Some(exec_label_update); } @@ -128,22 +123,30 @@ where 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 { - 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.state = Some(ElemState::ExecButton { exec, child: None }); 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 { rect, font_size, @@ -171,10 +174,7 @@ where button_h - 4., empty_str.clone(), ); - label.state = Some(ElemState { - battery: Some(i as usize), - ..Default::default() - }); + label.state = Some(ElemState::Battery { device: i as _ }); label.on_update = Some(battery_update); button_x += match layout { @@ -215,13 +215,10 @@ where button_h - 4., KEYBOARD_NAME.into(), ); - keyboard.state = Some(ElemState { - button: Some(WatchButtonState { - pressed_at: Instant::now(), - overlay: Some(OverlaySelector::Name(KEYBOARD_NAME.into())), - mode: PointerMode::Left, - }), - ..Default::default() + keyboard.state = Some(ElemState::OverlayButton { + pressed_at: Instant::now(), + mode: PointerMode::Left, + overlay: OverlaySelector::Name(KEYBOARD_NAME.into()), }); keyboard.on_press = Some(overlay_button_dn); keyboard.on_release = Some(overlay_button_up); @@ -246,13 +243,10 @@ where button_h - 4., screen.state.name.clone(), ); - button.state = Some(ElemState { - button: Some(WatchButtonState { - pressed_at: Instant::now(), - overlay: Some(OverlaySelector::Id(screen.state.id)), - mode: PointerMode::Left, - }), - ..Default::default() + button.state = Some(ElemState::OverlayButton { + pressed_at: Instant::now(), + mode: PointerMode::Left, + overlay: OverlaySelector::Id(screen.state.id), }); button.on_press = Some(overlay_button_dn); @@ -273,7 +267,7 @@ where size: (400, 200), want_visible: 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_rotation: state.session.watch_rot, interaction_transform, @@ -285,37 +279,105 @@ where } } -#[derive(Default)] -struct ElemState { - battery: Option, - clock: Option, - exec: Option, - button: Option, +enum ElemState { + Battery { + device: usize, + }, + Clock { + timezone: Option, + format: Arc, + }, + AutoExec { + last_exec: Instant, + interval: f32, + exec: Vec>, + child: Option, + }, + OverlayButton { + pressed_at: Instant, + mode: PointerMode, + overlay: OverlaySelector, + }, + ExecButton { + exec: Vec>, + child: Option, + }, + FuncButton { + func: ButtonFunc, + func_right: Option, + func_middle: Option, + }, } -struct ClockState { - timezone: Option, - format: Arc, -} - -struct WatchButtonState { - pressed_at: Instant, +fn btn_func_dn( + control: &mut Control<(), ElemState>, + _: &mut (), + app: &mut AppState, mode: PointerMode, - overlay: Option, -} +) { + let ElemState::FuncButton { + func, + func_right, + func_middle, + } = control.state.as_ref().unwrap() + else { + log::error!("FuncButton state not found"); + return; + }; -struct ExecState { - last_exec: Instant, - interval: f32, - exec: Vec>, - child: Option, + let func = match mode { + PointerMode::Left => func, + PointerMode::Right => func_right.as_ref().unwrap_or(func), + PointerMode::Middle => func_middle.as_ref().unwrap_or(func), + _ => 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) { - let state = control.state.as_ref().unwrap(); - let device_idx = state.battery.unwrap(); - - let device = app.input_state.devices.get(device_idx); + let ElemState::Battery { device } = control.state.as_ref().unwrap() else { + return; + }; + let device = app.input_state.devices.get(*device); let tags = ["", "H", "L", "R", "T"]; @@ -336,30 +398,37 @@ fn exec_button( _: &mut AppState, _mode: PointerMode, ) { - let state = control.state.as_mut().unwrap(); - let exec = state.exec.as_mut().unwrap(); - if let Some(child) = &mut exec.child { - match child.try_wait() { + let ElemState::ExecButton { + exec, + ref mut child, + .. + } = 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)) => { if !code.success() { log::error!("Child process exited with code: {}", code); } - exec.child = None; + *child = None; } Ok(None) => { log::warn!("Unable to launch child process: previous child not exited yet"); return; } Err(e) => { - exec.child = None; + *child = None; log::error!("Error checking child process: {:?}", e); } } } - let args = exec.exec.iter().map(|s| s.as_ref()).collect::>(); + let args = exec.iter().map(|s| s.as_ref()).collect::>(); match process::Command::new(args[0]).args(&args[1..]).spawn() { - Ok(child) => { - exec.child = Some(child); + Ok(proc) => { + *child = Some(proc); } Err(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) { - let state = control.state.as_mut().unwrap(); - let exec = state.exec.as_mut().unwrap(); + let ElemState::AutoExec { + 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() { - match child.try_wait() { + if let Some(mut proc) = child.take() { + match proc.try_wait() { Ok(Some(code)) => { if !code.success() { log::error!("Child process exited with code: {}", code); } else { - if let Some(mut stdout) = child.stdout.take() { + if let Some(mut stdout) = proc.stdout.take() { let mut buf = String::new(); if let Ok(_) = stdout.read_to_string(&mut buf) { control.set_text(&buf); @@ -393,12 +470,12 @@ fn exec_label_update(control: &mut Control<(), ElemState>, _: &mut (), _: &mut A } } Ok(None) => { - exec.child = Some(child); + *child = Some(proc); // not exited yet return; } Err(e) => { - exec.child = None; + *child = None; log::error!("Error checking child process: {:?}", e); return; } @@ -406,20 +483,20 @@ fn exec_label_update(control: &mut Control<(), ElemState>, _: &mut (), _: &mut A } if Instant::now() - .saturating_duration_since(exec.last_exec) + .saturating_duration_since(*last_exec) .as_secs_f32() - > exec.interval + > *interval { - exec.last_exec = Instant::now(); - let args = exec.exec.iter().map(|s| s.as_ref()).collect::>(); + *last_exec = Instant::now(); + let args = exec.iter().map(|s| s.as_ref()).collect::>(); match process::Command::new(args[0]) .args(&args[1..]) .stdout(Stdio::piped()) .spawn() { - Ok(child) => { - exec.child = Some(child); + Ok(proc) => { + *child = Some(proc); } Err(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) { - let state = control.state.as_mut().unwrap(); - let clock = state.clock.as_mut().unwrap(); + let ElemState::Clock { timezone, format } = control.state.as_ref().unwrap() else { + log::error!("Clock state not found"); + return; + }; - let fmt = clock.format.clone(); - - if let Some(tz) = clock.timezone { - let date = Local::now().with_timezone(&tz); - control.set_text(&format!("{}", &date.format(&fmt))); + if let Some(tz) = timezone { + let date = Local::now().with_timezone(tz); + control.set_text(&format!("{}", &date.format(format))); } else { 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>, _: &mut (), _: &mut AppState, - mode: PointerMode, + ptr_mode: PointerMode, ) { - let btn = control.state.as_mut().unwrap().button.as_mut().unwrap(); - btn.pressed_at = Instant::now(); - btn.mode = mode; + let ElemState::OverlayButton { + ref mut pressed_at, + 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) { - let btn = control.state.as_mut().unwrap().button.as_mut().unwrap(); - let selector = btn.overlay.as_ref().unwrap().clone(); + let ElemState::OverlayButton { + pressed_at, + mode, + overlay, + } = control.state.as_ref().unwrap() + else { + log::error!("OverlayButton state not found"); + return; + }; + if Instant::now() - .saturating_duration_since(btn.pressed_at) + .saturating_duration_since(*pressed_at) .as_millis() < 2000 { - match btn.mode { + match mode { PointerMode::Left => { app.tasks.enqueue(TaskType::Overlay( - selector, + overlay.clone(), Box::new(|app, o| { o.want_visible = !o.want_visible; if o.recenter { @@ -477,7 +570,7 @@ fn overlay_button_up(control: &mut Control<(), ElemState>, _: &mut (), app: &mut } PointerMode::Right => { app.tasks.enqueue(TaskType::Overlay( - selector, + overlay.clone(), Box::new(|app, o| { o.recenter = !o.recenter; o.grabbable = o.recenter; @@ -493,7 +586,7 @@ fn overlay_button_up(control: &mut Control<(), ElemState>, _: &mut (), app: &mut } PointerMode::Middle => { app.tasks.enqueue(TaskType::Overlay( - selector, + overlay.clone(), Box::new(|app, o| { o.interactable = !o.interactable; if !o.interactable { @@ -509,7 +602,7 @@ fn overlay_button_up(control: &mut Control<(), ElemState>, _: &mut (), app: &mut } } else { app.tasks.enqueue(TaskType::Overlay( - selector, + overlay.clone(), Box::new(|app, o| { o.reset(app, true); }), @@ -582,6 +675,22 @@ enum WatchElement { scr_bg_color: Arc, layout: ListLayout, }, + FuncButton { + rect: [f32; 4], + font_size: isize, + bg_color: Arc, + fg_color: Arc, + func: ButtonFunc, + func_right: Option, + func_middle: Option, + text: Arc, + }, +} + +#[derive(Deserialize)] +enum ButtonFunc { + HideWatch, + SwitchWatchHand, } #[derive(Deserialize)] @@ -604,6 +713,11 @@ where .mut_by_selector(&OverlaySelector::Name(WATCH_NAME.into())) .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 watch_normal = watch .state @@ -612,9 +726,14 @@ where .normalize(); 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; } else { 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.); } } diff --git a/src/res/watch.yaml b/src/res/watch.yaml index 521e4f9..5dadff5 100644 --- a/src/res/watch.yaml +++ b/src/res/watch.yaml @@ -40,9 +40,19 @@ watch_elements: rect: [0, 0, 400, 200] 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 - type: OverlayList - rect: [0, 160, 400, 40] + rect: [30, 160, 370, 40] font_size: 14 kbd_fg_color: "#FFFFFF" kbd_bg_color: "#406050" @@ -96,9 +106,9 @@ watch_elements: text: "Chicago" # change TZ2 label here - type: Batteries - rect: [0, 0, 400, 40] + rect: [0, 0, 400, 30] font_size: 14 - num_devices: 9 + num_devices: 6 low_threshold: 15 layout: Horizontal normal_fg_color: "#99BBAA" diff --git a/src/state.rs b/src/state.rs index fc9cdb7..f82c503 100644 --- a/src/state.rs +++ b/src/state.rs @@ -13,7 +13,7 @@ use crate::{ 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 struct AppState {