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 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="angle_fade_box" translation="EDIT_MODE.ANGLE_FADE" tooltip="EDIT_MODE.ANGLE_FADE_HELP" tooltip_side="bottom" />
</div>
</div>
<div id="tab_curve" display="none" height="100" flex_direction="column">

View File

@@ -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",

View File

@@ -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();

View File

@@ -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) {

View File

@@ -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);

View File

@@ -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<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> {
@@ -110,8 +122,10 @@ impl OverlayWindowData<OpenXrOverlayData> {
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<OpenXrOverlayData> {
.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<OpenXrOverlayData> {
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<OpenXrOverlayData> {
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, "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::<ComponentCheckbox>("global_box")?;
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
.parser_state
.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;
}
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,

View File

@@ -179,6 +179,7 @@ pub fn create_watch(app: &mut AppState) -> anyhow::Result<OverlayWindowConfig> {
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<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
if state.alpha <= 0f32 {
if state.alpha <= 0.05 {
continue;
}

View File

@@ -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<T> {
wrappers: EditWrapperManager,
overlays: HopSlotMap<OverlayID, OverlayWindowData<T>>,
sets: Vec<OverlayWindowSet>,
global_set: OverlayWindowSet,
/// The set that is currently visible.
current_set: Option<usize>,
/// 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<T> OverlayWindowManager<T> {
}
// 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());
}
}

View File

@@ -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 {

View File

@@ -77,6 +77,7 @@ pub struct OverlayWindowState {
pub additive: bool,
pub saved_transform: Option<Affine3A>,
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,
}
}
}