mirror of https://github.com/wayvr-org/wayvr.git
ensure pose & haptics for bindings
This commit is contained in:
parent
025c6a0998
commit
5e316fe0d3
|
|
@ -1,6 +1,6 @@
|
||||||
use std::{collections::BTreeMap, io::Read, rc::Rc};
|
use std::{collections::BTreeMap, io::Read, rc::Rc};
|
||||||
|
|
||||||
use anyhow::{bail, Context};
|
use anyhow::{Context, bail};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use strum::{AsRefStr, EnumProperty, EnumString};
|
use strum::{AsRefStr, EnumProperty, EnumString};
|
||||||
use wgui::i18n::Translation;
|
use wgui::i18n::Translation;
|
||||||
|
|
@ -85,7 +85,7 @@ impl Subpath {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
pub enum SubpathType {
|
pub enum SubpathType {
|
||||||
Button,
|
Button,
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,10 @@ use std::{borrow::Cow, collections::HashMap, rc::Rc};
|
||||||
use glam::Vec2;
|
use glam::Vec2;
|
||||||
use wgui::{
|
use wgui::{
|
||||||
assets::AssetPath,
|
assets::AssetPath,
|
||||||
components::{button::{ButtonClickEvent, ComponentButton}, slider::ComponentSlider},
|
components::{
|
||||||
|
button::{ButtonClickEvent, ComponentButton},
|
||||||
|
slider::ComponentSlider,
|
||||||
|
},
|
||||||
globals::WguiGlobals,
|
globals::WguiGlobals,
|
||||||
i18n::Translation,
|
i18n::Translation,
|
||||||
layout::{Layout, WidgetID},
|
layout::{Layout, WidgetID},
|
||||||
|
|
@ -22,7 +25,9 @@ use crate::{
|
||||||
frontend::{FrontendTask, FrontendTasks},
|
frontend::{FrontendTask, FrontendTasks},
|
||||||
tab::settings::horiz_cell,
|
tab::settings::horiz_cell,
|
||||||
util::{
|
util::{
|
||||||
openxr_bindings_schema::{BindingsDropdown, ClickType, Component, IdentifierType, ParsedOpenXrInputPath, Profile, Side},
|
openxr_bindings_schema::{
|
||||||
|
BindingsDropdown, ClickType, Component, IdentifierType, ParsedOpenXrInputPath, Profile, Side, SubpathType,
|
||||||
|
},
|
||||||
popup_manager::{MountPopupOnceParams, MountPopupOnceParamsExtra, PopupHolder, PopupPadding},
|
popup_manager::{MountPopupOnceParams, MountPopupOnceParamsExtra, PopupHolder, PopupPadding},
|
||||||
wgui_simple,
|
wgui_simple,
|
||||||
},
|
},
|
||||||
|
|
@ -34,7 +39,7 @@ enum Task {
|
||||||
Save,
|
Save,
|
||||||
Cancel,
|
Cancel,
|
||||||
OpenContextMenu(glam::Vec2, Vec<context_menu::Cell>),
|
OpenContextMenu(glam::Vec2, Vec<context_menu::Cell>),
|
||||||
UpdateThreshold(Rc<str>, f32, f32)
|
UpdateThreshold(Rc<str>, f32, f32),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Params<'a> {
|
pub struct Params<'a> {
|
||||||
|
|
@ -194,6 +199,8 @@ impl View {
|
||||||
close_callback: Some(params.close_callback),
|
close_callback: Some(params.close_callback),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
me.ensure_pose_and_haptics();
|
||||||
|
|
||||||
me.refresh(params.layout)?;
|
me.refresh(params.layout)?;
|
||||||
|
|
||||||
Ok(me)
|
Ok(me)
|
||||||
|
|
@ -236,6 +243,71 @@ impl View {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn ensure_pose_and_haptics(&mut self) {
|
||||||
|
let cur_profile = &mut self.profiles[self.cur_profile_idx];
|
||||||
|
|
||||||
|
if cur_profile.pose.is_none() {
|
||||||
|
let schema = &self.schema;
|
||||||
|
let mut aim_pose_name: Option<&str> = None;
|
||||||
|
let mut first_pose_name: Option<&str> = None;
|
||||||
|
|
||||||
|
for (key, subpath) in &schema.subpaths {
|
||||||
|
if subpath.kind != SubpathType::Pose {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let name = key.strip_prefix("/input/");
|
||||||
|
let Some(name) = name else { continue };
|
||||||
|
if first_pose_name.is_none() {
|
||||||
|
first_pose_name = Some(name);
|
||||||
|
}
|
||||||
|
if name == "aim" {
|
||||||
|
aim_pose_name = Some(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// no aim pose → use first one
|
||||||
|
if let Some(name) = aim_pose_name.or(first_pose_name) {
|
||||||
|
let pose_action = cur_profile.pose.get_or_insert_default();
|
||||||
|
if pose_action.left.is_none() {
|
||||||
|
let left_path = format!("/user/hand/left/input/{name}/pose");
|
||||||
|
pose_action.left = Some(OneOrMany::One(left_path));
|
||||||
|
}
|
||||||
|
if pose_action.right.is_none() {
|
||||||
|
let right_path = format!("/user/hand/right/input/{name}/pose");
|
||||||
|
pose_action.right = Some(OneOrMany::One(right_path));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if cur_profile.haptic.is_none() {
|
||||||
|
let schema = &self.schema;
|
||||||
|
let mut first_haptic_name: Option<&str> = None;
|
||||||
|
|
||||||
|
for (key, subpath) in &schema.subpaths {
|
||||||
|
if subpath.kind != SubpathType::Vibration {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let name = key.strip_prefix("/output/");
|
||||||
|
let Some(name) = name else { continue };
|
||||||
|
if first_haptic_name.is_none() {
|
||||||
|
first_haptic_name = Some(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(name) = first_haptic_name {
|
||||||
|
let haptic_action = cur_profile.haptic.get_or_insert_with(OpenXrInputAction::default);
|
||||||
|
if haptic_action.left.is_none() {
|
||||||
|
let left_path = format!("/user/hand/left/output/{name}");
|
||||||
|
haptic_action.left = Some(OneOrMany::One(left_path));
|
||||||
|
}
|
||||||
|
if haptic_action.right.is_none() {
|
||||||
|
let right_path = format!("/user/hand/right/output/{name}");
|
||||||
|
haptic_action.right = Some(OneOrMany::One(right_path));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_action_mut<'a>(profile: &'a mut OpenXrInputProfile, action_name: &str) -> &'a mut OpenXrInputAction {
|
fn get_action_mut<'a>(profile: &'a mut OpenXrInputProfile, action_name: &str) -> &'a mut OpenXrInputAction {
|
||||||
|
|
@ -351,7 +423,16 @@ fn input_controls_for_action(
|
||||||
OneOrMany::Many(s) => s.first().unwrap().as_str(), // safe
|
OneOrMany::Many(s) => s.first().unwrap().as_str(), // safe
|
||||||
});
|
});
|
||||||
|
|
||||||
input_controls_for_hand(mp, parent, current_right, Side::Right, action, click_type, profile, current.threshold)
|
input_controls_for_hand(
|
||||||
|
mp,
|
||||||
|
parent,
|
||||||
|
current_right,
|
||||||
|
Side::Right,
|
||||||
|
action,
|
||||||
|
click_type,
|
||||||
|
profile,
|
||||||
|
current.threshold,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn input_controls_for_hand(
|
fn input_controls_for_hand(
|
||||||
|
|
@ -416,7 +497,9 @@ fn input_controls_for_hand(
|
||||||
|
|
||||||
clicks_dropdown(mp, parent, action.clone(), click_type)?;
|
clicks_dropdown(mp, parent, action.clone(), click_type)?;
|
||||||
|
|
||||||
if let Some(component) = current.as_ref().map(|x| x.component) && component.is_analog() {
|
if let Some(component) = current.as_ref().map(|x| x.component)
|
||||||
|
&& component.is_analog()
|
||||||
|
{
|
||||||
threshold_slider(mp, parent, action, threshold)?;
|
threshold_slider(mp, parent, action, threshold)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -491,7 +574,12 @@ fn clicks_dropdown(mp: &mut MacroParams, parent: WidgetID, action: Rc<str>, curr
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn threshold_slider(mp: &mut MacroParams, parent: WidgetID, action: Rc<str>, current: Option<[f32; 2]>) -> anyhow::Result<()> {
|
fn threshold_slider(
|
||||||
|
mp: &mut MacroParams,
|
||||||
|
parent: WidgetID,
|
||||||
|
action: Rc<str>,
|
||||||
|
current: Option<[f32; 2]>,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
let id = mp.idx.to_string();
|
let id = mp.idx.to_string();
|
||||||
mp.idx += 1;
|
mp.idx += 1;
|
||||||
|
|
||||||
|
|
@ -508,7 +596,7 @@ fn threshold_slider(mp: &mut MacroParams, parent: WidgetID, action: Rc<str>, cur
|
||||||
|
|
||||||
mp.parser_state
|
mp.parser_state
|
||||||
.instantiate_template(mp.doc_params, "ThresholdSlider", mp.layout, parent, params)?;
|
.instantiate_template(mp.doc_params, "ThresholdSlider", mp.layout, parent, params)?;
|
||||||
|
|
||||||
let slider = mp.parser_state.fetch_component_as::<ComponentSlider>(&id)?;
|
let slider = mp.parser_state.fetch_component_as::<ComponentSlider>(&id)?;
|
||||||
slider.on_value_changed(Box::new({
|
slider.on_value_changed(Box::new({
|
||||||
let tasks = mp.tasks.clone();
|
let tasks = mp.tasks.clone();
|
||||||
|
|
@ -524,14 +612,22 @@ fn threshold_slider(mp: &mut MacroParams, parent: WidgetID, action: Rc<str>, cur
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_dropdown<B: 'static + BindingsDropdown>(mp: &mut MacroParams, parent: WidgetID, mut params: HashMap<Rc<str>, Rc<str>>, action: Rc<str>, side: Side, current_text: Translation, available: Rc<[B]>) -> anyhow::Result<()> {
|
fn create_dropdown<B: 'static + BindingsDropdown>(
|
||||||
|
mp: &mut MacroParams,
|
||||||
|
parent: WidgetID,
|
||||||
|
mut params: HashMap<Rc<str>, Rc<str>>,
|
||||||
|
action: Rc<str>,
|
||||||
|
side: Side,
|
||||||
|
current_text: Translation,
|
||||||
|
available: Rc<[B]>,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
let id = mp.idx.to_string();
|
let id = mp.idx.to_string();
|
||||||
mp.idx += 1;
|
mp.idx += 1;
|
||||||
params.insert(Rc::from("id"), Rc::from(id.as_ref()));
|
params.insert(Rc::from("id"), Rc::from(id.as_ref()));
|
||||||
|
|
||||||
mp.parser_state
|
mp.parser_state
|
||||||
.instantiate_template(mp.doc_params, "DropdownButton", mp.layout, parent, params)?;
|
.instantiate_template(mp.doc_params, "DropdownButton", mp.layout, parent, params)?;
|
||||||
|
|
||||||
{
|
{
|
||||||
let mut label = mp
|
let mut label = mp
|
||||||
.parser_state
|
.parser_state
|
||||||
|
|
@ -545,20 +641,20 @@ fn create_dropdown<B: 'static + BindingsDropdown>(mp: &mut MacroParams, parent:
|
||||||
let available = available.clone();
|
let available = available.clone();
|
||||||
move |_common, e: ButtonClickEvent| {
|
move |_common, e: ButtonClickEvent| {
|
||||||
let mut cells = available
|
let mut cells = available
|
||||||
.iter()
|
.iter()
|
||||||
.map(|item| {
|
.map(|item| {
|
||||||
let title = item.translation();
|
let title = item.translation();
|
||||||
|
|
||||||
context_menu::Cell {
|
context_menu::Cell {
|
||||||
action_name: Some(item.action_str(&*action, side)),
|
action_name: Some(item.action_str(&*action, side)),
|
||||||
title,
|
title,
|
||||||
tooltip: None,
|
tooltip: None,
|
||||||
attribs: vec![],
|
attribs: vec![],
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
if let Some(action_str) = B::clear_str(&*action, side) {
|
if let Some(action_str) = B::clear_str(&*action, side) {
|
||||||
cells.insert(
|
cells.insert(
|
||||||
0,
|
0,
|
||||||
context_menu::Cell {
|
context_menu::Cell {
|
||||||
|
|
@ -570,10 +666,7 @@ fn create_dropdown<B: 'static + BindingsDropdown>(mp: &mut MacroParams, parent:
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.push(Task::OpenContextMenu(
|
tasks.push(Task::OpenContextMenu(e.mouse_pos_absolute.unwrap_or_default(), cells));
|
||||||
e.mouse_pos_absolute.unwrap_or_default(),
|
|
||||||
cells,
|
|
||||||
));
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue