From c222c25ddfa5bf303e0b37aea5a7e53480082602 Mon Sep 17 00:00:00 2001 From: galister <22305755+galister@users.noreply.github.com> Date: Thu, 22 Jan 2026 00:51:23 +0900 Subject: [PATCH] angle fade (partial, need monado fix) --- wayvr/src/assets/gui/edit.xml | 1 + wayvr/src/assets/lang/en.json | 2 + wayvr/src/backend/openvr/mod.rs | 12 +----- wayvr/src/backend/openxr/helpers.rs | 5 +++ wayvr/src/backend/openxr/mod.rs | 14 +++--- wayvr/src/backend/openxr/overlay.rs | 66 ++++++++++++++++++++++++++++- wayvr/src/overlays/edit/mod.rs | 14 ++++++ wayvr/src/overlays/watch.rs | 16 +------ wayvr/src/subsystem/osc.rs | 2 +- wayvr/src/windowing/manager.rs | 49 +++++++++++++++------ wayvr/src/windowing/window.rs | 26 +++++++++++- wlx-common/src/windowing.rs | 2 + 12 files changed, 159 insertions(+), 50 deletions(-) diff --git a/wayvr/src/assets/gui/edit.xml b/wayvr/src/assets/gui/edit.xml index 0abe10a..ca50072 100644 --- a/wayvr/src/assets/gui/edit.xml +++ b/wayvr/src/assets/gui/edit.xml @@ -114,6 +114,7 @@
+
diff --git a/wayvr/src/assets/lang/en.json b/wayvr/src/assets/lang/en.json index a09a5a5..55525d4 100644 --- a/wayvr/src/assets/lang/en.json +++ b/wayvr/src/assets/lang/en.json @@ -26,6 +26,8 @@ "ADJUST_CURVATURE": "Adjust curvature", "ALPHA_BLEND_MODE": "Alpha blend mode", "BLENDING_ADDITIVE": "Additive blending", + "ANGLE_FADE": "Angle fade", + "ANGLE_FADE_HELP": "Fade when not facing HMD", "CURVATURE": "Curvature", "DELETE": "Long press to remove from current set", "DISABLE_GRAB": "Disable grab", diff --git a/wayvr/src/backend/openvr/mod.rs b/wayvr/src/backend/openvr/mod.rs index 9eef177..cc6ca6b 100644 --- a/wayvr/src/backend/openvr/mod.rs +++ b/wayvr/src/backend/openvr/mod.rs @@ -30,10 +30,7 @@ use crate::{ }, config::{save_settings, save_state}, graphics::{GpuFutures, init_openvr_graphics}, - overlays::{ - toast::Toast, - watch::{WATCH_NAME, watch_fade}, - }, + overlays::toast::Toast, state::AppState, subsystem::notifications::NotificationManager, windowing::{ @@ -134,8 +131,6 @@ pub fn openvr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr log::info!("HMD running @ {refresh_rate} Hz"); - 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; @@ -266,11 +261,8 @@ pub fn openvr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr .enqueue(TaskType::Overlay(OverlayTask::ToggleDashboard)); } - overlays - .values_mut() - .for_each(|o| o.config.auto_movement(&mut app)); + overlays.values_mut().for_each(|o| o.config.tick(&mut app)); - watch_fade(&mut app, overlays.mut_by_id(watch_id).unwrap()); // want panic playspace.update(&mut chaperone_mgr, &mut overlays, &app); current_lines.clear(); diff --git a/wayvr/src/backend/openxr/helpers.rs b/wayvr/src/backend/openxr/helpers.rs index 5d1905e..722e25c 100644 --- a/wayvr/src/backend/openxr/helpers.rs +++ b/wayvr/src/backend/openxr/helpers.rs @@ -52,6 +52,11 @@ pub(super) fn init_xr() -> Result<(xr::Instance, xr::SystemId), anyhow::Error> { } else { log::warn!("Missing EXT_composition_layer_equirect2 extension."); } + if available_extensions.khr_composition_layer_color_scale_bias { + enabled_extensions.khr_composition_layer_color_scale_bias = true; + } else { + log::warn!("Missing XR_KHR_composition_layer_color_scale_bias extension."); + } let xr_mndx_system_buttons = "XR_MNDX_system_buttons".as_bytes().to_vec(); if available_extensions.other.contains(&xr_mndx_system_buttons) { diff --git a/wayvr/src/backend/openxr/mod.rs b/wayvr/src/backend/openxr/mod.rs index 8c71106..40fb5d6 100644 --- a/wayvr/src/backend/openxr/mod.rs +++ b/wayvr/src/backend/openxr/mod.rs @@ -22,10 +22,7 @@ use crate::{ }, config::{save_settings, save_state}, graphics::{GpuFutures, init_openxr_graphics}, - overlays::{ - toast::Toast, - watch::{WATCH_NAME, watch_fade}, - }, + overlays::{toast::Toast, watch::WATCH_NAME}, state::AppState, subsystem::notifications::NotificationManager, windowing::{ @@ -289,7 +286,6 @@ pub fn openxr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr .enqueue(TaskType::Overlay(OverlayTask::ToggleDashboard)); } - watch_fade(&mut app, overlays.mut_by_id(watch_id).unwrap()); // want panic if let Some(ref mut space_mover) = playspace { space_mover.update(&mut overlays, &mut app); } @@ -317,9 +313,7 @@ pub fn openxr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr .submit(&mut app); } - overlays - .values_mut() - .for_each(|o| o.config.auto_movement(&mut app)); + overlays.values_mut().for_each(|o| o.config.tick(&mut app)); current_lines.clear(); @@ -372,6 +366,10 @@ pub fn openxr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr log::trace!("{}: hidden, skip render", o.config.name); continue; }; + if alpha < 0.05 { + log::trace!("{}: alpha too low, skip render", o.config.name); + continue; + } if !o.data.init { log::trace!("{}: init", o.config.name); diff --git a/wayvr/src/backend/openxr/overlay.rs b/wayvr/src/backend/openxr/overlay.rs index 00fc738..9d19f7d 100644 --- a/wayvr/src/backend/openxr/overlay.rs +++ b/wayvr/src/backend/openxr/overlay.rs @@ -18,6 +18,18 @@ pub struct OpenXrOverlayData { pub(super) init: bool, pub(super) cur_visible: bool, pub(super) last_alpha: f32, + color_bias_khr: Option>, +} + +macro_rules! next_chain_insert { + ($layer:expr, $payload:expr) => {{ + let payload_ptr = $payload.as_mut() as *mut _ as *mut xr::sys::BaseInStructure; + let new_elem = payload_ptr.as_mut().unwrap(); + let mut raw = $layer.into_raw(); + new_elem.next = raw.next as _; + raw.next = payload_ptr as *const _; + raw + }}; } impl OverlayWindowData { @@ -110,8 +122,10 @@ impl OverlayWindowData { let posef = helpers::translation_rotation_to_posef(center_point, quat); let angle = 2.0 * (scale_x / (2.0 * radius)); + try_update_color_scale_bias(xr, &mut self.data.color_bias_khr, state.alpha); + for sub_image in sub_images { - let cylinder = xr::CompositionLayerCylinderKHR::new() + let mut cylinder = xr::CompositionLayerCylinderKHR::new() .layer_flags(flags) .pose(posef) .sub_image(sub_image.0) @@ -120,12 +134,22 @@ impl OverlayWindowData { .radius(radius) .central_angle(angle) .aspect_ratio(aspect_ratio); + + if let Some(color_bias_khr) = self.data.color_bias_khr.as_mut() { + unsafe { + let raw = next_chain_insert!(cylinder, color_bias_khr); + cylinder = xr::CompositionLayerCylinderKHR::from_raw(raw); + } + } + layers.push(CompositionLayer::Cylinder(cylinder)); } } else { let posef = helpers::transform_to_posef(&transform); + try_update_color_scale_bias(xr, &mut self.data.color_bias_khr, state.alpha); + for sub_image in sub_images { - let quad = xr::CompositionLayerQuad::new() + let mut quad = xr::CompositionLayerQuad::new() .layer_flags(flags) .pose(posef) .sub_image(sub_image.0) @@ -135,6 +159,14 @@ impl OverlayWindowData { width: scale_x, height: scale_y, }); + + if let Some(color_bias_khr) = self.data.color_bias_khr.as_mut() { + unsafe { + let raw = next_chain_insert!(quad, color_bias_khr); + quad = xr::CompositionLayerQuad::from_raw(raw); + } + } + layers.push(CompositionLayer::Quad(quad)); } } @@ -159,3 +191,33 @@ impl OverlayWindowData { Ok(()) } } + +fn try_update_color_scale_bias( + xr_state: &XrState, + color_bias_khr: &mut Option>, + alpha: f32, +) { + if let Some(item) = color_bias_khr.as_mut() { + item.color_scale.a = alpha; + return; + } + + if xr_state + .instance + .exts() + .khr_composition_layer_color_scale_bias + .is_none() + { + return; + } + let new_item = Box::new(xr::sys::CompositionLayerColorScaleBiasKHR { + ty: xr::StructureType::COMPOSITION_LAYER_COLOR_SCALE_BIAS_KHR, + next: std::ptr::null(), + color_bias: Default::default(), + color_scale: xr::Color4f { + a: alpha, + ..Default::default() + }, + }); + *color_bias_khr = Some(new_item); +} diff --git a/wayvr/src/overlays/edit/mod.rs b/wayvr/src/overlays/edit/mod.rs index 597ef1f..c3b5270 100644 --- a/wayvr/src/overlays/edit/mod.rs +++ b/wayvr/src/overlays/edit/mod.rs @@ -432,6 +432,7 @@ fn make_edit_panel(app: &mut AppState) -> anyhow::Result { set_up_checkbox(&mut panel, "additive_box", cb_assign_additive)?; set_up_checkbox(&mut panel, "align_box", cb_assign_align)?; set_up_checkbox(&mut panel, "global_box", cb_assign_global)?; + set_up_checkbox(&mut panel, "angle_fade_box", cb_assign_angle_fade)?; set_up_checkbox(&mut panel, "block_input_box", cb_assign_block_input)?; set_up_checkbox( &mut panel, @@ -499,6 +500,11 @@ fn reset_panel( .fetch_component_as::("global_box")?; c.set_checked(&mut common, owc.global); + let c = panel + .parser_state + .fetch_component_as::("angle_fade_box")?; + c.set_checked(&mut common, state.angle_fade); + let c = panel .parser_state .fetch_component_as::("block_input_box")?; @@ -607,6 +613,14 @@ const fn cb_assign_global(_app: &mut AppState, owc: &mut OverlayWindowConfig, gl owc.global = global; } +const fn cb_assign_angle_fade( + _app: &mut AppState, + owc: &mut OverlayWindowConfig, + angle_fade: bool, +) { + owc.active_state.as_mut().unwrap().angle_fade = angle_fade; +} + const fn cb_assign_block_input( _app: &mut AppState, owc: &mut OverlayWindowConfig, diff --git a/wayvr/src/overlays/watch.rs b/wayvr/src/overlays/watch.rs index 1032164..d7c20d6 100644 --- a/wayvr/src/overlays/watch.rs +++ b/wayvr/src/overlays/watch.rs @@ -179,6 +179,7 @@ pub fn create_watch(app: &mut AppState) -> anyhow::Result { WATCH_ROT, WATCH_POS, ), + angle_fade: true, ..OverlayWindowState::default() }, show_on_spawn: true, @@ -213,18 +214,3 @@ fn sets_or_overlays( alterables.set_style(widget[i], StyleSetRequest::Display(display[i])); } } - -pub fn watch_fade(app: &mut AppState, watch: &mut OverlayWindowData) { - let Some(state) = watch.config.active_state.as_mut() else { - return; - }; - - 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); - - state.alpha = (dot - app.session.config.watch_view_angle_min) - / (app.session.config.watch_view_angle_max - app.session.config.watch_view_angle_min); - state.alpha += 0.1; - state.alpha = state.alpha.clamp(0., 1.); -} diff --git a/wayvr/src/subsystem/osc.rs b/wayvr/src/subsystem/osc.rs index 0a1f26b..5677f1f 100644 --- a/wayvr/src/subsystem/osc.rs +++ b/wayvr/src/subsystem/osc.rs @@ -79,7 +79,7 @@ impl OscSender { }; // skip overlays that are fully transparent; e.g. the watch when not looking at it - if state.alpha <= 0f32 { + if state.alpha <= 0.05 { continue; } diff --git a/wayvr/src/windowing/manager.rs b/wayvr/src/windowing/manager.rs index dbd7eb6..45fa4ae 100644 --- a/wayvr/src/windowing/manager.rs +++ b/wayvr/src/windowing/manager.rs @@ -12,6 +12,7 @@ use wlx_common::{ astr_containers::{AStrMap, AStrMapExt}, config::SerializedWindowSet, overlays::{BackendAttrib, BackendAttribValue, ToastTopic}, + windowing::Positioning, }; use crate::{ @@ -26,7 +27,7 @@ use crate::{ keyboard::create_keyboard, screen::create_screens, toast::Toast, - watch::create_watch, + watch::{WATCH_NAME, create_watch}, }, state::AppState, windowing::{ @@ -44,6 +45,7 @@ pub struct OverlayWindowManager { wrappers: EditWrapperManager, overlays: HopSlotMap>, sets: Vec, + global_set: OverlayWindowSet, /// The set that is currently visible. current_set: Option, /// The set that will be restored by show_hide. @@ -68,6 +70,7 @@ where current_set: Some(0), restore_set: 0, sets: vec![OverlayWindowSet::default()], + global_set: OverlayWindowSet::default(), anchor_local: Affine3A::from_translation(Vec3::NEG_Z), watch_id: OverlayID::null(), // set down below keyboard_id: OverlayID::null(), // set down below @@ -195,16 +198,19 @@ where _ => {} }; + let parent_set = if o.config.global { + &mut self.global_set + } else { + &mut self.sets[self.restore_set] + }; + if let Some(active_state) = o.config.active_state.take() { log::debug!("{}: toggle off", o.config.name); - self.sets[self.restore_set] + parent_set .hidden_overlays .arc_set(o.config.name.clone(), active_state); - } else if let Some(state) = self.sets[self.restore_set] - .hidden_overlays - .arc_rm(&o.config.name) - { + } else if let Some(state) = parent_set.hidden_overlays.arc_rm(&o.config.name) { let o = &mut self.overlays[id]; log::debug!("{}: toggle on", o.config.name); o.config.dirty = true; @@ -509,15 +515,32 @@ impl OverlayWindowManager { } // global overlays - for oid in &[self.watch_id] { - if let Some(o) = self.mut_by_id(*oid) { - if let Some(state) = app.session.config.global_set.get(&*o.config.name).cloned() { - o.config.active_state = Some(state); - o.config.reset(app, false); - log::debug!("global set: loaded state for {}", o.config.name); + for (name, ows) in app.session.config.global_set.clone().into_iter() { + let mut ows = ows.clone(); + + // fix angle_fade missing on watch if loading older state + if name.as_ref() == WATCH_NAME { + ows.angle_fade = true; + } + + if let Some(oid) = self.lookup(&*name) + && let Some(o) = self.mut_by_id(oid) + { + o.config.global = true; + if o.config.active_state.is_none() { + self.global_set.hidden_overlays.arc_set(name.clone(), ows); } else { - log::debug!("global set: no state for {}", o.config.name); + o.config.active_state = Some(ows); + o.config.reset(app, false); } + log::debug!("global set: loaded state for {name}"); + } else { + log::debug!( + "global set has saved state for {name} which doesn't exist. will apply state once added." + ); + self.global_set + .inactive_overlays + .arc_set(name.clone(), ows.clone()); } } diff --git a/wayvr/src/windowing/window.rs b/wayvr/src/windowing/window.rs index cbcc1df..9b48ac7 100644 --- a/wayvr/src/windowing/window.rs +++ b/wayvr/src/windowing/window.rs @@ -129,7 +129,12 @@ impl OverlayWindowConfig { self.active_state = None; } - pub fn auto_movement(&mut self, app: &mut AppState) { + pub fn tick(&mut self, app: &mut AppState) { + self.auto_movement(app); + self.angle_fade(app); + } + + fn auto_movement(&mut self, app: &mut AppState) { if self.pause_movement { return; } @@ -188,6 +193,25 @@ impl OverlayWindowConfig { self.dirty = true; } + fn angle_fade(&mut self, app: &mut AppState) { + let Some(state) = self.active_state.as_mut() else { + return; + }; + + if !state.angle_fade { + return; + } + + 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); + + state.alpha = (dot - app.session.config.watch_view_angle_min) + / (app.session.config.watch_view_angle_max - app.session.config.watch_view_angle_min); + state.alpha += 0.1; + state.alpha = state.alpha.clamp(0., 1.); + } + /// Returns true if changes were saved. pub fn reset(&mut self, app: &mut AppState, hard_reset: bool) { let Some(state) = self.active_state.as_mut() else { diff --git a/wlx-common/src/windowing.rs b/wlx-common/src/windowing.rs index f921ed0..139d6da 100644 --- a/wlx-common/src/windowing.rs +++ b/wlx-common/src/windowing.rs @@ -77,6 +77,7 @@ pub struct OverlayWindowState { pub additive: bool, pub saved_transform: Option, pub block_input: bool, + pub angle_fade: bool, } impl Default for OverlayWindowState { @@ -91,6 +92,7 @@ impl Default for OverlayWindowState { additive: false, saved_transform: None, block_input: true, + angle_fade: false, } } }