angle fade (partial, need monado fix)
This commit is contained in:
@@ -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">
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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.);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user