mirrors, recenter, fix floor

This commit is contained in:
galister
2025-12-11 23:55:18 +09:00
parent 4314610244
commit a5cacc2e70
44 changed files with 218 additions and 19 deletions

View File

@@ -75,8 +75,22 @@
<label text="New York" _source="clock" _display="name" _timezone="1" color="~clock_alt2_color" size="~clock_alt_tz_size" weight="bold" />
<label text="23:59" _source="clock" _display="time" _timezone="1" color="~clock_alt2_color" size="~clock_alt_size" weight="bold" />
</div>
<div flex_direction="column" padding="4">
<div padding="8" />
<div flex_direction="column" padding="4" gap="4">
<Button macro="button_style" _press="::NewMirror" tooltip="WATCH.MIRROR" tooltip_side="bottom">
<sprite width="40" height="40" color="~set_color" src="edit/mirror.svg" />
</Button>
<Button macro="button_style" _press="::CleanupMirrors" tooltip="WATCH.CLEANUP_MIRRORS" tooltip_side="bottom">
<sprite width="40" height="40" color="~set_color" src="watch/mirror-off.svg" />
</Button>
</div>
<div flex_direction="column" padding="4" gap="4">
<Button macro="button_style" _press="::PlayspaceRecenter" tooltip="WATCH.RECENTER" tooltip_side="bottom">
<sprite width="40" height="40" color="~set_color" src="watch/recenter.svg" />
</Button>
<Button macro="button_style" _press="::PlayspaceFixFloor" tooltip="WATCH.FIX-FLOOR" tooltip_side="bottom">
<sprite width="40" height="40" color="~set_color" src="watch/fix-floor.svg" />
</Button>
</div>
</div>
<div flex_direction="column" id="edit_pane" display="none" >

View File

@@ -1,5 +1,9 @@
{
"WATCH": {
"MIRROR": "Add a new mirror overlay",
"CLEANUP_MIRRORS": "Remove mirrors that are\nnot currently visible",
"RECENTER": "Recenter playspace",
"FIX-FLOOR": "Fix floor height",
"DASHBOARD": "Dashboard",
"EDIT_MODE": "Edit Mode",
"ADD_NEW_SET": "Add a new set",

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><!-- Icon from Material Symbols by Google - https://github.com/google/material-design-icons/blob/master/LICENSE --><path fill="white" d="M4 21v-2h16v2zm8-4l-5-5l1.4-1.4l2.6 2.6V3h2v10.2l2.6-2.6L17 12z"/></svg>

After

Width:  |  Height:  |  Size: 293 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><!-- Icon from Material Symbols by Google - https://github.com/google/material-design-icons/blob/master/LICENSE --><path fill="white" d="m20.45 23.3l-3.3-3.3H3.4l6.875-6.875l-1.4-1.4L2 18.6v-4.175l4.775-4.8l-1.4-1.4L2 11.6V6q0-.25.1-.438t.25-.362L.65 3.5l1.425-1.425l19.8 19.8L20.45 23.3ZM22 19.15l-8.875-8.875L19.4 4h.6q.825 0 1.413.588T22 6v13.15ZM11.725 8.875l-2.1-2.1L12.4 4h4.175l-4.85 4.875Zm-3.5-3.5L6.85 4H9.6L8.225 5.375Z"/></svg>

After

Width:  |  Height:  |  Size: 523 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><!-- Icon from Material Symbols by Google - https://github.com/google/material-design-icons/blob/master/LICENSE --><path fill="white" d="M11 23v-4.175L9.9 19.9l-1.4-1.4L12 15l3.5 3.5l-1.4 1.4l-1.1-1.075V23zm-5.5-7.5l-1.4-1.4L5.175 13H1v-2h4.175L4.1 9.9l1.4-1.4L9 12zm13 0L15 12l3.5-3.5l1.4 1.4l-1.075 1.1H23v2h-4.175l1.075 1.1zm-6.5-2q-.625 0-1.062-.437T10.5 12t.438-1.062T12 10.5t1.063.438T13.5 12t-.437 1.063T12 13.5M12 9L8.5 5.5l1.4-1.4L11 5.175V1h2v4.175L14.1 4.1l1.4 1.4z"/></svg>

After

Width:  |  Height:  |  Size: 569 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><!-- Icon from Material Symbols by Google - https://github.com/google/material-design-icons/blob/master/LICENSE --><path fill="white" d="M12 21q-3.15 0-5.575-1.912T3.275 14.2q-.1-.375.15-.687t.675-.363q.4-.05.725.15t.45.6q.6 2.25 2.475 3.675T12 19q2.925 0 4.963-2.037T19 12t-2.037-4.962T12 5q-1.725 0-3.225.8T6.25 8H8q.425 0 .713.288T9 9t-.288.713T8 10H4q-.425 0-.712-.288T3 9V5q0-.425.288-.712T4 4t.713.288T5 5v1.35q1.275-1.6 3.113-2.475T12 3q1.875 0 3.513.713t2.85 1.924t1.925 2.85T21 12t-.712 3.513t-1.925 2.85t-2.85 1.925T12 21m0-7q-.825 0-1.412-.587T10 12t.588-1.412T12 10t1.413.588T14 12t-.587 1.413T12 14"/></svg>

After

Width:  |  Height:  |  Size: 704 B

View File

@@ -44,9 +44,12 @@ impl PlayspaceMover {
PlayspaceTask::FixFloor => {
self.fix_floor(chaperone_mgr, &app.input_state);
}
PlayspaceTask::ResetPlayspace => {
PlayspaceTask::Reset => {
self.reset_offset(chaperone_mgr, &app.input_state);
}
PlayspaceTask::Recenter => {
self.recenter(chaperone_mgr, &app.input_state);
}
}
}
@@ -224,6 +227,28 @@ impl PlayspaceMover {
}
}
pub fn recenter(&mut self, chaperone_mgr: &mut ChaperoneSetupManager, input: &InputState) {
let Some(mut mat) = get_working_copy(&self.universe, chaperone_mgr) else {
log::warn!("Can't fix floor - failed to get zero pose");
return;
};
mat.translation.x += input.hmd.translation.x;
mat.translation.z += input.hmd.translation.z;
set_working_copy(&self.universe, chaperone_mgr, &mat);
chaperone_mgr.commit_working_copy(EChaperoneConfigFile::EChaperoneConfigFile_Live);
if self.drag.is_some() {
log::info!("Space drag interrupted by fix floor");
self.drag = None;
}
if self.rotate.is_some() {
log::info!("Space rotate interrupted by fix floor");
self.rotate = None;
}
}
pub fn playspace_changed(
&mut self,
compositor_mgr: &mut CompositorManager,

View File

@@ -48,9 +48,12 @@ impl PlayspaceMover {
PlayspaceTask::FixFloor => {
self.fix_floor(&app.input_state, monado);
}
PlayspaceTask::ResetPlayspace => {
PlayspaceTask::Reset => {
self.reset_offset(monado);
}
PlayspaceTask::Recenter => {
self.recenter(&app.input_state, monado);
}
}
}
@@ -173,6 +176,31 @@ impl PlayspaceMover {
}
}
pub fn recenter(&mut self, input: &InputState, monado: &mut Monado) {
if self.drag.is_some() {
log::info!("Space drag interrupted by recenter");
self.drag = None;
}
if self.rotate.is_some() {
log::info!("Space rotate interrupted by recenter");
self.rotate = None;
}
let Ok(mut pose) = monado
.get_reference_space_offset(ReferenceSpaceType::Stage)
.inspect_err(|e| log::warn!("Could not recenter due to libmonado error: {e:?}"))
else {
return;
};
pose.position.x += input.hmd.translation.x;
pose.position.z += input.hmd.translation.z;
let _ = monado
.set_reference_space_offset(ReferenceSpaceType::Stage, pose)
.inspect_err(|e| log::warn!("Could not recenter due to libmonado error: {e:?}"));
}
pub fn reset_offset(&mut self, monado: &mut Monado) {
if self.drag.is_some() {
log::info!("Space drag interrupted by manual reset");
@@ -197,11 +225,22 @@ impl PlayspaceMover {
self.rotate = None;
}
let Ok(mut pose) = monado
.get_reference_space_offset(ReferenceSpaceType::Stage)
.inspect_err(|e| log::warn!("Could not fix floor due to libmonado error: {e:?}"))
else {
return;
};
let y1 = input.pointers[0].raw_pose.translation.y;
let y2 = input.pointers[1].raw_pose.translation.y;
let delta = y1.min(y2) - 0.03;
self.last_transform.translation.y += delta;
apply_offset(self.last_transform, monado);
pose.position.y += delta;
let _ = monado
.set_reference_space_offset(ReferenceSpaceType::Stage, pose)
.inspect_err(|e| log::warn!("Could not fix floor due to libmonado error: {e:?}"));
}
}

View File

@@ -49,7 +49,8 @@ pub enum OpenVrTask {
}
pub enum PlayspaceTask {
ResetPlayspace,
Recenter,
Reset,
FixFloor,
}
@@ -61,6 +62,7 @@ pub enum OverlayTask {
DeleteActiveSet,
ToggleEditMode,
ShowHide,
CleanupMirrors,
Modify(OverlaySelector, Box<ModifyOverlayTask>),
Create(OverlaySelector, Box<CreateOverlayTask>),
Drop(OverlaySelector),

View File

@@ -2,7 +2,8 @@ use std::{
cell::RefCell,
io::BufReader,
process::{Child, ChildStdout},
sync::Arc,
sync::{atomic::Ordering, Arc},
time::{Duration, Instant},
};
use wgui::{
@@ -11,11 +12,17 @@ use wgui::{
parser::CustomAttribsInfoOwned,
widget::EventResult,
};
use wlx_common::overlays::ToastTopic;
use crate::{
backend::task::{OverlayTask, TaskType},
backend::task::{OverlayTask, PlayspaceTask, TaskType},
overlays::{
mirror::{new_mirror, new_mirror_name},
toast::Toast,
},
state::AppState,
windowing::OverlaySelector,
RUNNING,
};
#[cfg(feature = "wayvr")]
@@ -87,9 +94,49 @@ pub(super) fn setup_custom_button<S: 'static>(
.enqueue(TaskType::Overlay(OverlayTask::ToggleEditMode));
Ok(EventResult::Consumed)
}),
"::WatchHide" => todo!(),
"::WatchSwapHand" => todo!(),
// TODO
"::NewMirror" => Box::new(move |_common, _data, app, _| {
let name = new_mirror_name();
app.tasks.enqueue(TaskType::Overlay(OverlayTask::Create(
OverlaySelector::Name(name.clone()),
Box::new(move |app| Some(new_mirror(name, &app.session))),
)));
Ok(EventResult::Consumed)
}),
"::CleanupMirrors" => Box::new(move |_common, _data, app, _| {
app.tasks
.enqueue(TaskType::Overlay(OverlayTask::CleanupMirrors));
Ok(EventResult::Consumed)
}),
"::PlayspaceReset" => Box::new(move |_common, _data, app, _| {
app.tasks.enqueue(TaskType::Playspace(PlayspaceTask::Reset));
Ok(EventResult::Consumed)
}),
"::PlayspaceRecenter" => Box::new(move |_common, _data, app, _| {
app.tasks
.enqueue(TaskType::Playspace(PlayspaceTask::Recenter));
Ok(EventResult::Consumed)
}),
"::PlayspaceFixFloor" => Box::new(move |_common, _data, app, _| {
for i in 0..5 {
Toast::new(
ToastTopic::System,
format!("Fixing floor in {}", 5 - i).into(),
"Touch your controller to the floor!".into(),
)
.with_timeout(1.)
.with_sound(true)
.submit_at(app, Instant::now() + Duration::from_secs(i));
}
app.tasks.enqueue_at(
TaskType::Playspace(PlayspaceTask::FixFloor),
Instant::now() + Duration::from_secs(5),
);
Ok(EventResult::Consumed)
}),
"::Shutdown" => Box::new(move |_common, _data, _app, _| {
RUNNING.store(false, Ordering::Relaxed);
Ok(EventResult::Consumed)
}),
#[allow(clippy::match_same_arms)]
"::OscSend" => return,
// shell

View File

@@ -1,10 +1,13 @@
use std::{
sync::Arc,
sync::{
atomic::{AtomicUsize, Ordering},
Arc,
},
task::{Context, Poll},
};
use futures::{Future, FutureExt};
use glam::{Affine2, Affine3A, Vec3};
use glam::{vec3, Affine2, Affine3A, Quat, Vec3};
use wlx_capture::pipewire::{pipewire_select_screen, PipewireCapture, PipewireSelectScreenResult};
use wlx_common::windowing::OverlayWindowState;
@@ -30,6 +33,8 @@ type PinnedSelectorFuture = core::pin::Pin<
Box<dyn Future<Output = Result<PipewireSelectScreenResult, wlx_capture::pipewire::AshpdError>>>,
>;
static MIRROR_COUNTER: AtomicUsize = AtomicUsize::new(1);
pub struct MirrorBackend {
name: Arc<str>,
renderer: Option<ScreenBackend>,
@@ -149,14 +154,24 @@ impl OverlayBackend for MirrorBackend {
}
}
pub fn new_mirror_name() -> Arc<str> {
format!("M-{}", MIRROR_COUNTER.fetch_add(1, Ordering::Relaxed)).into()
}
pub fn new_mirror(name: Arc<str>, session: &AppSession) -> OverlayWindowConfig {
OverlayWindowConfig {
name: name.clone(),
category: OverlayCategory::Mirror,
show_on_spawn: true,
default_state: OverlayWindowState {
interactable: true,
grabbable: true,
transform: Affine3A::from_scale(Vec3::ONE * 0.5 * session.config.desktop_view_scale),
curvature: Some(0.15),
transform: Affine3A::from_scale_rotation_translation(
Vec3::ONE * session.config.desktop_view_scale,
Quat::IDENTITY,
vec3(0.0, 0.2, -0.35),
),
..OverlayWindowState::default()
},
..OverlayWindowConfig::from_backend(Box::new(MirrorBackend::new(name)))

View File

@@ -187,6 +187,22 @@ where
.notify(app, OverlayEventData::NumSetsChanged(len))?;
}
}
OverlayTask::CleanupMirrors => {
let mut ids_to_remove = vec![];
for (oid, o) in self.overlays.iter() {
if !matches!(o.config.category, OverlayCategory::Mirror) {
continue;
}
if o.config.active_state.is_some() {
continue;
}
ids_to_remove.push(oid);
}
for oid in ids_to_remove {
self.remove_by_selector(&OverlaySelector::Id(oid), app);
}
}
OverlayTask::Modify(sel, f) => {
if let Some(o) = self.mut_by_selector(&sel) {
f(app, &mut o.config);
@@ -216,10 +232,11 @@ where
OverlayTask::Drop(sel) => {
if let Some(o) = self.mut_by_selector(&sel)
&& o.birthframe < FRAME_COUNTER.load(Ordering::Relaxed)
&& let Some(o) = self.remove_by_selector(&sel, app) {
log::debug!("Dropping overlay {}", o.config.name);
self.dropped_overlays.push_back(o);
}
&& let Some(o) = self.remove_by_selector(&sel, app)
{
log::debug!("Dropping overlay {}", o.config.name);
self.dropped_overlays.push_back(o);
}
}
}
Ok(())