angle fade (partial, need monado fix)

This commit is contained in:
galister
2026-01-22 00:51:23 +09:00
parent ee8391a45f
commit c222c25ddf
12 changed files with 159 additions and 50 deletions

View File

@@ -114,6 +114,7 @@
</div> </div>
<div width="100%" padding="8" gap="8" justify_content="center" align_items="center"> <div width="100%" padding="8" gap="8" justify_content="center" align_items="center">
<CheckBox id="additive_box" translation="EDIT_MODE.BLENDING_ADDITIVE" tooltip="EDIT_MODE.ALPHA_BLEND_MODE" tooltip_side="bottom" /> <CheckBox id="additive_box" translation="EDIT_MODE.BLENDING_ADDITIVE" tooltip="EDIT_MODE.ALPHA_BLEND_MODE" tooltip_side="bottom" />
<CheckBox id="angle_fade_box" translation="EDIT_MODE.ANGLE_FADE" tooltip="EDIT_MODE.ANGLE_FADE_HELP" tooltip_side="bottom" />
</div> </div>
</div> </div>
<div id="tab_curve" display="none" height="100" flex_direction="column"> <div id="tab_curve" display="none" height="100" flex_direction="column">

View File

@@ -26,6 +26,8 @@
"ADJUST_CURVATURE": "Adjust curvature", "ADJUST_CURVATURE": "Adjust curvature",
"ALPHA_BLEND_MODE": "Alpha blend mode", "ALPHA_BLEND_MODE": "Alpha blend mode",
"BLENDING_ADDITIVE": "Additive blending", "BLENDING_ADDITIVE": "Additive blending",
"ANGLE_FADE": "Angle fade",
"ANGLE_FADE_HELP": "Fade when not facing HMD",
"CURVATURE": "Curvature", "CURVATURE": "Curvature",
"DELETE": "Long press to remove from current set", "DELETE": "Long press to remove from current set",
"DISABLE_GRAB": "Disable grab", "DISABLE_GRAB": "Disable grab",

View File

@@ -30,10 +30,7 @@ use crate::{
}, },
config::{save_settings, save_state}, config::{save_settings, save_state},
graphics::{GpuFutures, init_openvr_graphics}, graphics::{GpuFutures, init_openvr_graphics},
overlays::{ overlays::toast::Toast,
toast::Toast,
watch::{WATCH_NAME, watch_fade},
},
state::AppState, state::AppState,
subsystem::notifications::NotificationManager, subsystem::notifications::NotificationManager,
windowing::{ windowing::{
@@ -134,8 +131,6 @@ pub fn openvr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr
log::info!("HMD running @ {refresh_rate} Hz"); log::info!("HMD running @ {refresh_rate} Hz");
let watch_id = overlays.lookup(WATCH_NAME).unwrap(); // want panic
// want at least half refresh rate // want at least half refresh rate
let frame_timeout = 2 * (1000.0 / refresh_rate).floor() as u32; 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)); .enqueue(TaskType::Overlay(OverlayTask::ToggleDashboard));
} }
overlays overlays.values_mut().for_each(|o| o.config.tick(&mut app));
.values_mut()
.for_each(|o| o.config.auto_movement(&mut app));
watch_fade(&mut app, overlays.mut_by_id(watch_id).unwrap()); // want panic
playspace.update(&mut chaperone_mgr, &mut overlays, &app); playspace.update(&mut chaperone_mgr, &mut overlays, &app);
current_lines.clear(); current_lines.clear();

View File

@@ -52,6 +52,11 @@ pub(super) fn init_xr() -> Result<(xr::Instance, xr::SystemId), anyhow::Error> {
} else { } else {
log::warn!("Missing EXT_composition_layer_equirect2 extension."); 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(); let xr_mndx_system_buttons = "XR_MNDX_system_buttons".as_bytes().to_vec();
if available_extensions.other.contains(&xr_mndx_system_buttons) { if available_extensions.other.contains(&xr_mndx_system_buttons) {

View File

@@ -22,10 +22,7 @@ use crate::{
}, },
config::{save_settings, save_state}, config::{save_settings, save_state},
graphics::{GpuFutures, init_openxr_graphics}, graphics::{GpuFutures, init_openxr_graphics},
overlays::{ overlays::{toast::Toast, watch::WATCH_NAME},
toast::Toast,
watch::{WATCH_NAME, watch_fade},
},
state::AppState, state::AppState,
subsystem::notifications::NotificationManager, subsystem::notifications::NotificationManager,
windowing::{ windowing::{
@@ -289,7 +286,6 @@ pub fn openxr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr
.enqueue(TaskType::Overlay(OverlayTask::ToggleDashboard)); .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 { if let Some(ref mut space_mover) = playspace {
space_mover.update(&mut overlays, &mut app); 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); .submit(&mut app);
} }
overlays overlays.values_mut().for_each(|o| o.config.tick(&mut app));
.values_mut()
.for_each(|o| o.config.auto_movement(&mut app));
current_lines.clear(); 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); log::trace!("{}: hidden, skip render", o.config.name);
continue; continue;
}; };
if alpha < 0.05 {
log::trace!("{}: alpha too low, skip render", o.config.name);
continue;
}
if !o.data.init { if !o.data.init {
log::trace!("{}: init", o.config.name); log::trace!("{}: init", o.config.name);

View File

@@ -18,6 +18,18 @@ pub struct OpenXrOverlayData {
pub(super) init: bool, pub(super) init: bool,
pub(super) cur_visible: bool, pub(super) cur_visible: bool,
pub(super) last_alpha: f32, pub(super) last_alpha: f32,
color_bias_khr: Option<Box<xr::sys::CompositionLayerColorScaleBiasKHR>>,
}
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<OpenXrOverlayData> { impl OverlayWindowData<OpenXrOverlayData> {
@@ -110,8 +122,10 @@ impl OverlayWindowData<OpenXrOverlayData> {
let posef = helpers::translation_rotation_to_posef(center_point, quat); let posef = helpers::translation_rotation_to_posef(center_point, quat);
let angle = 2.0 * (scale_x / (2.0 * radius)); 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 { for sub_image in sub_images {
let cylinder = xr::CompositionLayerCylinderKHR::new() let mut cylinder = xr::CompositionLayerCylinderKHR::new()
.layer_flags(flags) .layer_flags(flags)
.pose(posef) .pose(posef)
.sub_image(sub_image.0) .sub_image(sub_image.0)
@@ -120,12 +134,22 @@ impl OverlayWindowData<OpenXrOverlayData> {
.radius(radius) .radius(radius)
.central_angle(angle) .central_angle(angle)
.aspect_ratio(aspect_ratio); .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)); layers.push(CompositionLayer::Cylinder(cylinder));
} }
} else { } else {
let posef = helpers::transform_to_posef(&transform); 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 { for sub_image in sub_images {
let quad = xr::CompositionLayerQuad::new() let mut quad = xr::CompositionLayerQuad::new()
.layer_flags(flags) .layer_flags(flags)
.pose(posef) .pose(posef)
.sub_image(sub_image.0) .sub_image(sub_image.0)
@@ -135,6 +159,14 @@ impl OverlayWindowData<OpenXrOverlayData> {
width: scale_x, width: scale_x,
height: scale_y, 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)); layers.push(CompositionLayer::Quad(quad));
} }
} }
@@ -159,3 +191,33 @@ impl OverlayWindowData<OpenXrOverlayData> {
Ok(()) Ok(())
} }
} }
fn try_update_color_scale_bias(
xr_state: &XrState,
color_bias_khr: &mut Option<Box<xr::sys::CompositionLayerColorScaleBiasKHR>>,
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);
}

View File

@@ -432,6 +432,7 @@ fn make_edit_panel(app: &mut AppState) -> anyhow::Result<EditModeWrapPanel> {
set_up_checkbox(&mut panel, "additive_box", cb_assign_additive)?; 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, "align_box", cb_assign_align)?;
set_up_checkbox(&mut panel, "global_box", cb_assign_global)?; 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, "block_input_box", cb_assign_block_input)?;
set_up_checkbox( set_up_checkbox(
&mut panel, &mut panel,
@@ -499,6 +500,11 @@ fn reset_panel(
.fetch_component_as::<ComponentCheckbox>("global_box")?; .fetch_component_as::<ComponentCheckbox>("global_box")?;
c.set_checked(&mut common, owc.global); c.set_checked(&mut common, owc.global);
let c = panel
.parser_state
.fetch_component_as::<ComponentCheckbox>("angle_fade_box")?;
c.set_checked(&mut common, state.angle_fade);
let c = panel let c = panel
.parser_state .parser_state
.fetch_component_as::<ComponentCheckbox>("block_input_box")?; .fetch_component_as::<ComponentCheckbox>("block_input_box")?;
@@ -607,6 +613,14 @@ const fn cb_assign_global(_app: &mut AppState, owc: &mut OverlayWindowConfig, gl
owc.global = global; 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( const fn cb_assign_block_input(
_app: &mut AppState, _app: &mut AppState,
owc: &mut OverlayWindowConfig, owc: &mut OverlayWindowConfig,

View File

@@ -179,6 +179,7 @@ pub fn create_watch(app: &mut AppState) -> anyhow::Result<OverlayWindowConfig> {
WATCH_ROT, WATCH_ROT,
WATCH_POS, WATCH_POS,
), ),
angle_fade: true,
..OverlayWindowState::default() ..OverlayWindowState::default()
}, },
show_on_spawn: true, show_on_spawn: true,
@@ -213,18 +214,3 @@ fn sets_or_overlays(
alterables.set_style(widget[i], StyleSetRequest::Display(display[i])); alterables.set_style(widget[i], StyleSetRequest::Display(display[i]));
} }
} }
pub fn watch_fade<D>(app: &mut AppState, watch: &mut OverlayWindowData<D>) {
let Some(state) = watch.config.active_state.as_mut() else {
return;
};
let to_hmd = (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.);
}

View File

@@ -79,7 +79,7 @@ impl OscSender {
}; };
// skip overlays that are fully transparent; e.g. the watch when not looking at it // 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; continue;
} }

View File

@@ -12,6 +12,7 @@ use wlx_common::{
astr_containers::{AStrMap, AStrMapExt}, astr_containers::{AStrMap, AStrMapExt},
config::SerializedWindowSet, config::SerializedWindowSet,
overlays::{BackendAttrib, BackendAttribValue, ToastTopic}, overlays::{BackendAttrib, BackendAttribValue, ToastTopic},
windowing::Positioning,
}; };
use crate::{ use crate::{
@@ -26,7 +27,7 @@ use crate::{
keyboard::create_keyboard, keyboard::create_keyboard,
screen::create_screens, screen::create_screens,
toast::Toast, toast::Toast,
watch::create_watch, watch::{WATCH_NAME, create_watch},
}, },
state::AppState, state::AppState,
windowing::{ windowing::{
@@ -44,6 +45,7 @@ pub struct OverlayWindowManager<T> {
wrappers: EditWrapperManager, wrappers: EditWrapperManager,
overlays: HopSlotMap<OverlayID, OverlayWindowData<T>>, overlays: HopSlotMap<OverlayID, OverlayWindowData<T>>,
sets: Vec<OverlayWindowSet>, sets: Vec<OverlayWindowSet>,
global_set: OverlayWindowSet,
/// The set that is currently visible. /// The set that is currently visible.
current_set: Option<usize>, current_set: Option<usize>,
/// The set that will be restored by show_hide. /// The set that will be restored by show_hide.
@@ -68,6 +70,7 @@ where
current_set: Some(0), current_set: Some(0),
restore_set: 0, restore_set: 0,
sets: vec![OverlayWindowSet::default()], sets: vec![OverlayWindowSet::default()],
global_set: OverlayWindowSet::default(),
anchor_local: Affine3A::from_translation(Vec3::NEG_Z), anchor_local: Affine3A::from_translation(Vec3::NEG_Z),
watch_id: OverlayID::null(), // set down below watch_id: OverlayID::null(), // set down below
keyboard_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() { if let Some(active_state) = o.config.active_state.take() {
log::debug!("{}: toggle off", o.config.name); log::debug!("{}: toggle off", o.config.name);
self.sets[self.restore_set] parent_set
.hidden_overlays .hidden_overlays
.arc_set(o.config.name.clone(), active_state); .arc_set(o.config.name.clone(), active_state);
} else if let Some(state) = self.sets[self.restore_set] } else if let Some(state) = parent_set.hidden_overlays.arc_rm(&o.config.name) {
.hidden_overlays
.arc_rm(&o.config.name)
{
let o = &mut self.overlays[id]; let o = &mut self.overlays[id];
log::debug!("{}: toggle on", o.config.name); log::debug!("{}: toggle on", o.config.name);
o.config.dirty = true; o.config.dirty = true;
@@ -509,15 +515,32 @@ impl<T> OverlayWindowManager<T> {
} }
// global overlays // global overlays
for oid in &[self.watch_id] { for (name, ows) in app.session.config.global_set.clone().into_iter() {
if let Some(o) = self.mut_by_id(*oid) { let mut ows = ows.clone();
if let Some(state) = app.session.config.global_set.get(&*o.config.name).cloned() {
o.config.active_state = Some(state); // fix angle_fade missing on watch if loading older state
o.config.reset(app, false); if name.as_ref() == WATCH_NAME {
log::debug!("global set: loaded state for {}", o.config.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 { } 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());
} }
} }

View File

@@ -129,7 +129,12 @@ impl OverlayWindowConfig {
self.active_state = None; 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 { if self.pause_movement {
return; return;
} }
@@ -188,6 +193,25 @@ impl OverlayWindowConfig {
self.dirty = true; 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. /// Returns true if changes were saved.
pub fn reset(&mut self, app: &mut AppState, hard_reset: bool) { pub fn reset(&mut self, app: &mut AppState, hard_reset: bool) {
let Some(state) = self.active_state.as_mut() else { let Some(state) = self.active_state.as_mut() else {

View File

@@ -77,6 +77,7 @@ pub struct OverlayWindowState {
pub additive: bool, pub additive: bool,
pub saved_transform: Option<Affine3A>, pub saved_transform: Option<Affine3A>,
pub block_input: bool, pub block_input: bool,
pub angle_fade: bool,
} }
impl Default for OverlayWindowState { impl Default for OverlayWindowState {
@@ -91,6 +92,7 @@ impl Default for OverlayWindowState {
additive: false, additive: false,
saved_transform: None, saved_transform: None,
block_input: true, block_input: true,
angle_fade: false,
} }
} }
} }