mirror of
https://gitlab.com/gabmus/envision.git
synced 2025-04-21 03:54:49 +00:00
feat: full body tracking tracker role assignment ui
This commit is contained in:
parent
7580988ed8
commit
ac340ae6f2
8 changed files with 400 additions and 6 deletions
|
@ -1,3 +1,5 @@
|
|||
use std::slice::Iter;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
|
@ -41,6 +43,101 @@ pub enum XrtTrackerRole {
|
|||
ViveTrackerHtcxKeyboard,
|
||||
}
|
||||
|
||||
impl XrtTrackerRole {
|
||||
pub fn to_picker_string(&self) -> &str {
|
||||
match self {
|
||||
Self::ViveTrackerHtcxWaist => "Waist",
|
||||
Self::ViveTrackerHtcxLeftFoot => "Left foot",
|
||||
Self::ViveTrackerHtcxRightFoot => "Right foot",
|
||||
Self::ViveTrackerHtcxHandheldObject => "Handheld object",
|
||||
Self::ViveTrackerHtcxLeftShoulder => "Left shoulder",
|
||||
Self::ViveTrackerHtcxRightShoulder => "Right shoulder",
|
||||
Self::ViveTrackerHtcxLeftElbow => "Left elbow",
|
||||
Self::ViveTrackerHtcxRightElbow => "Right elbow",
|
||||
Self::ViveTrackerHtcxLeftKnee => "Left knee",
|
||||
Self::ViveTrackerHtcxRightKnee => "Right knee",
|
||||
Self::ViveTrackerHtcxChest => "Chest",
|
||||
Self::ViveTrackerHtcxCamera => "Camera",
|
||||
Self::ViveTrackerHtcxKeyboard => "Keyboard",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_picker_string(s: &str) -> Self {
|
||||
match s.to_lowercase().trim() {
|
||||
"waist" => Self::ViveTrackerHtcxWaist,
|
||||
"left foot" => Self::ViveTrackerHtcxLeftFoot,
|
||||
"right foot" => Self::ViveTrackerHtcxRightFoot,
|
||||
"handheld object" => Self::ViveTrackerHtcxHandheldObject,
|
||||
"left shoulder" => Self::ViveTrackerHtcxLeftShoulder,
|
||||
"right shoulder" => Self::ViveTrackerHtcxRightShoulder,
|
||||
"left elbow" => Self::ViveTrackerHtcxLeftElbow,
|
||||
"right elbow" => Self::ViveTrackerHtcxRightElbow,
|
||||
"left knee" => Self::ViveTrackerHtcxLeftKnee,
|
||||
"right knee" => Self::ViveTrackerHtcxRightKnee,
|
||||
"chest" => Self::ViveTrackerHtcxChest,
|
||||
"camera" => Self::ViveTrackerHtcxCamera,
|
||||
"keyboard" => Self::ViveTrackerHtcxKeyboard,
|
||||
_ => Self::ViveTrackerHtcxWaist,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn iter() -> Iter<'static, Self> {
|
||||
[
|
||||
Self::ViveTrackerHtcxWaist,
|
||||
Self::ViveTrackerHtcxLeftFoot,
|
||||
Self::ViveTrackerHtcxRightFoot,
|
||||
Self::ViveTrackerHtcxHandheldObject,
|
||||
Self::ViveTrackerHtcxLeftShoulder,
|
||||
Self::ViveTrackerHtcxRightShoulder,
|
||||
Self::ViveTrackerHtcxLeftElbow,
|
||||
Self::ViveTrackerHtcxRightElbow,
|
||||
Self::ViveTrackerHtcxLeftKnee,
|
||||
Self::ViveTrackerHtcxRightKnee,
|
||||
Self::ViveTrackerHtcxChest,
|
||||
Self::ViveTrackerHtcxCamera,
|
||||
Self::ViveTrackerHtcxKeyboard,
|
||||
]
|
||||
.iter()
|
||||
}
|
||||
|
||||
pub fn to_number(&self) -> u32 {
|
||||
match self {
|
||||
Self::ViveTrackerHtcxWaist => 0,
|
||||
Self::ViveTrackerHtcxLeftFoot => 1,
|
||||
Self::ViveTrackerHtcxRightFoot => 2,
|
||||
Self::ViveTrackerHtcxHandheldObject => 3,
|
||||
Self::ViveTrackerHtcxLeftShoulder => 4,
|
||||
Self::ViveTrackerHtcxRightShoulder => 5,
|
||||
Self::ViveTrackerHtcxLeftElbow => 6,
|
||||
Self::ViveTrackerHtcxRightElbow => 7,
|
||||
Self::ViveTrackerHtcxLeftKnee => 8,
|
||||
Self::ViveTrackerHtcxRightKnee => 9,
|
||||
Self::ViveTrackerHtcxChest => 10,
|
||||
Self::ViveTrackerHtcxCamera => 11,
|
||||
Self::ViveTrackerHtcxKeyboard => 12,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_number(n: &u32) -> Self {
|
||||
match n {
|
||||
0 => Self::ViveTrackerHtcxWaist,
|
||||
1 => Self::ViveTrackerHtcxLeftFoot,
|
||||
2 => Self::ViveTrackerHtcxRightFoot,
|
||||
3 => Self::ViveTrackerHtcxHandheldObject,
|
||||
4 => Self::ViveTrackerHtcxLeftShoulder,
|
||||
5 => Self::ViveTrackerHtcxRightShoulder,
|
||||
6 => Self::ViveTrackerHtcxLeftElbow,
|
||||
7 => Self::ViveTrackerHtcxRightElbow,
|
||||
8 => Self::ViveTrackerHtcxLeftKnee,
|
||||
9 => Self::ViveTrackerHtcxRightKnee,
|
||||
10 => Self::ViveTrackerHtcxChest,
|
||||
11 => Self::ViveTrackerHtcxCamera,
|
||||
12 => Self::ViveTrackerHtcxKeyboard,
|
||||
_ => Self::ViveTrackerHtcxWaist,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct TrackerRole {
|
||||
pub device_serial: String,
|
||||
|
@ -48,6 +145,16 @@ pub struct TrackerRole {
|
|||
pub xrt_input_name: XrtInputName,
|
||||
}
|
||||
|
||||
impl Default for TrackerRole {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
device_serial: "".into(),
|
||||
role: XrtTrackerRole::ViveTrackerHtcxWaist,
|
||||
xrt_input_name: XrtInputName::XrtInputGenericTrackerPose,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
|
||||
pub struct MonadoConfigV0 {
|
||||
#[serde(skip_serializing_if = "Option::is_none", rename = "$schema")]
|
||||
|
|
|
@ -2,6 +2,7 @@ use super::about_dialog::AboutDialog;
|
|||
use super::alert::alert;
|
||||
use super::build_window::{BuildStatus, BuildWindow};
|
||||
use super::debug_view::{DebugView, DebugViewMsg};
|
||||
use super::fbt_config_editor::{FbtConfigEditor, FbtConfigEditorInit, FbtConfigEditorMsg};
|
||||
use super::libsurvive_setup_window::LibsurviveSetupWindow;
|
||||
use super::main_view::MainViewMsg;
|
||||
use crate::builders::build_basalt::get_build_basalt_runners;
|
||||
|
@ -86,6 +87,8 @@ pub struct App {
|
|||
profiles: Vec<Profile>,
|
||||
#[tracker::do_not_track]
|
||||
xr_devices: XRDevices,
|
||||
#[tracker::do_not_track]
|
||||
fbt_config_editor: Option<Controller<FbtConfigEditor>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -103,6 +106,7 @@ pub enum Msg {
|
|||
SaveWinSize(i32, i32),
|
||||
Quit,
|
||||
ParseLog(Vec<String>),
|
||||
ConfigFbt,
|
||||
}
|
||||
|
||||
impl App {
|
||||
|
@ -528,6 +532,19 @@ impl SimpleComponent for App {
|
|||
))
|
||||
.expect("Failed to present Libsurvive Setup Window");
|
||||
}
|
||||
Msg::ConfigFbt => {
|
||||
self.fbt_config_editor = Some(
|
||||
FbtConfigEditor::builder()
|
||||
.launch(FbtConfigEditorInit {
|
||||
root_win: self.app_win.clone().upcast::<gtk::Window>(),
|
||||
})
|
||||
.detach(),
|
||||
);
|
||||
self.fbt_config_editor
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.emit(FbtConfigEditorMsg::Present);
|
||||
}
|
||||
Msg::SaveWinSize(w, h) => {
|
||||
self.config.win_size = [w, h];
|
||||
self.config.save();
|
||||
|
@ -621,6 +638,7 @@ impl SimpleComponent for App {
|
|||
xrservice_runner: None,
|
||||
build_pipeline: None,
|
||||
xr_devices: XRDevices::default(),
|
||||
fbt_config_editor: None,
|
||||
};
|
||||
let widgets = view_output!();
|
||||
|
||||
|
@ -638,6 +656,12 @@ impl SimpleComponent for App {
|
|||
sender.input_sender().emit(Msg::BuildProfile(true));
|
||||
});
|
||||
}
|
||||
{
|
||||
withclones![sender];
|
||||
stateless_action!(actions, ConfigFbtAction, {
|
||||
sender.input_sender().emit(Msg::ConfigFbt);
|
||||
});
|
||||
}
|
||||
{
|
||||
let abd_sender = model.about_dialog.sender().clone();
|
||||
stateless_action!(actions, AboutAction, {
|
||||
|
@ -692,5 +716,6 @@ new_action_group!(pub AppActionGroup, "win");
|
|||
new_stateless_action!(pub AboutAction, AppActionGroup, "about");
|
||||
new_stateless_action!(pub BuildProfileAction, AppActionGroup, "buildprofile");
|
||||
new_stateless_action!(pub BuildProfileCleanAction, AppActionGroup, "buildprofileclean");
|
||||
new_stateless_action!(pub ConfigFbtAction, AppActionGroup, "configfbt");
|
||||
new_stateless_action!(pub QuitAction, AppActionGroup, "quit");
|
||||
new_stateful_action!(pub DebugViewToggleAction, AppActionGroup, "debugviewtoggle", (), bool);
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
pub mod env_var_row_factory;
|
||||
pub mod tracker_role_group_factory;
|
||||
|
|
109
src/ui/factories/tracker_role_group_factory.rs
Normal file
109
src/ui/factories/tracker_role_group_factory.rs
Normal file
|
@ -0,0 +1,109 @@
|
|||
use crate::{
|
||||
file_builders::monado_config_v0::{TrackerRole, XrtTrackerRole},
|
||||
ui::fbt_config_editor::FbtConfigEditorMsg,
|
||||
ui::preference_rows::{combo_row, entry_row},
|
||||
withclones,
|
||||
};
|
||||
use adw::prelude::*;
|
||||
use relm4::prelude::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TrackerRoleModel {
|
||||
index: usize,
|
||||
tracker_role: TrackerRole,
|
||||
}
|
||||
|
||||
pub struct TrackerRoleModelInit {
|
||||
pub index: usize,
|
||||
pub tracker_role: Option<TrackerRole>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum TrackerRoleModelMsg {
|
||||
Changed(TrackerRole),
|
||||
Delete,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum TrackerRoleModelOutMsg {
|
||||
Changed(usize, TrackerRole),
|
||||
Delete(usize),
|
||||
}
|
||||
|
||||
#[relm4::factory(pub)]
|
||||
impl FactoryComponent for TrackerRoleModel {
|
||||
type Init = TrackerRoleModelInit;
|
||||
type Input = TrackerRoleModelMsg;
|
||||
type Output = TrackerRoleModelOutMsg;
|
||||
type CommandOutput = ();
|
||||
type Widgets = TrackerRoleModelWidgets;
|
||||
type ParentInput = FbtConfigEditorMsg;
|
||||
type ParentWidget = adw::PreferencesPage;
|
||||
|
||||
view! {
|
||||
root = adw::PreferencesGroup {
|
||||
set_title: "Tracker",
|
||||
#[wrap(Some)]
|
||||
set_header_suffix: del_btn = >k::Button {
|
||||
set_icon_name: "edit-delete-symbolic",
|
||||
set_tooltip_text: Some("Delete Tracker"),
|
||||
set_valign: gtk::Align::Center,
|
||||
add_css_class: "flat",
|
||||
add_css_class: "circular",
|
||||
connect_clicked[sender] => move |_| {
|
||||
sender.input(Self::Input::Delete);
|
||||
}
|
||||
},
|
||||
add: {
|
||||
withclones![sender];
|
||||
let tr = self.tracker_role.clone();
|
||||
&entry_row("Device serial", self.tracker_role.device_serial.as_str(), move |row| {
|
||||
let mut ntr = tr.clone();
|
||||
ntr.device_serial = row.text().to_string();
|
||||
sender.input(Self::Input::Changed(ntr));
|
||||
})
|
||||
},
|
||||
add: {
|
||||
withclones![sender];
|
||||
let tr = self.tracker_role.clone();
|
||||
&combo_row("Tracker role", None, &tr.role.clone().to_picker_string(),
|
||||
XrtTrackerRole::iter()
|
||||
.map(XrtTrackerRole::to_picker_string)
|
||||
.map(String::from)
|
||||
.collect::<Vec<String>>(),
|
||||
move |row| {
|
||||
let mut ntr = tr.clone();
|
||||
ntr.role = XrtTrackerRole::from_number(&row.selected());
|
||||
sender.input(Self::Input::Changed(ntr));
|
||||
}
|
||||
)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn forward_to_parent(output: Self::Output) -> Option<Self::ParentInput> {
|
||||
Some(match output {
|
||||
Self::Output::Changed(index, tracker_role) => {
|
||||
Self::ParentInput::TrackerRoleChanged(index, tracker_role.clone())
|
||||
}
|
||||
Self::Output::Delete(index) => Self::ParentInput::TrackerRoleDeleted(index),
|
||||
})
|
||||
}
|
||||
|
||||
fn update(&mut self, message: Self::Input, sender: FactorySender<Self>) {
|
||||
match message {
|
||||
Self::Input::Changed(r) => {
|
||||
self.tracker_role = r;
|
||||
sender.output(Self::Output::Changed(self.index, self.tracker_role.clone()));
|
||||
}
|
||||
Self::Input::Delete => sender.output(Self::Output::Delete(self.index)),
|
||||
}
|
||||
}
|
||||
|
||||
fn init_model(init: Self::Init, _index: &Self::Index, _sender: FactorySender<Self>) -> Self {
|
||||
Self {
|
||||
index: init.index,
|
||||
tracker_role: init.tracker_role.unwrap_or_else(|| TrackerRole::default()),
|
||||
}
|
||||
}
|
||||
}
|
152
src/ui/fbt_config_editor.rs
Normal file
152
src/ui/fbt_config_editor.rs
Normal file
|
@ -0,0 +1,152 @@
|
|||
use super::factories::tracker_role_group_factory::TrackerRoleModel;
|
||||
use crate::{
|
||||
file_builders::monado_config_v0::{
|
||||
dump_monado_config_v0, get_monado_config_v0, MonadoConfigV0, TrackerRole,
|
||||
},
|
||||
ui::factories::tracker_role_group_factory::TrackerRoleModelInit,
|
||||
withclones,
|
||||
};
|
||||
use adw::prelude::*;
|
||||
use relm4::{factory::FactoryVecDeque, prelude::*};
|
||||
|
||||
#[tracker::track]
|
||||
pub struct FbtConfigEditor {
|
||||
monado_config_v0: MonadoConfigV0,
|
||||
|
||||
#[tracker::do_not_track]
|
||||
win: Option<adw::PreferencesWindow>,
|
||||
|
||||
#[tracker::do_not_track]
|
||||
tracker_role_groups: FactoryVecDeque<TrackerRoleModel>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum FbtConfigEditorMsg {
|
||||
Present,
|
||||
Save,
|
||||
TrackerRoleNew,
|
||||
TrackerRoleChanged(usize, TrackerRole),
|
||||
TrackerRoleDeleted(usize),
|
||||
Repopulate,
|
||||
}
|
||||
|
||||
pub struct FbtConfigEditorInit {
|
||||
pub root_win: gtk::Window,
|
||||
}
|
||||
#[relm4::component(pub)]
|
||||
impl SimpleComponent for FbtConfigEditor {
|
||||
type Init = FbtConfigEditorInit;
|
||||
type Input = FbtConfigEditorMsg;
|
||||
type Output = ();
|
||||
|
||||
view! {
|
||||
#[name(win)]
|
||||
adw::PreferencesWindow {
|
||||
set_title: Some("Full Body Trackers"),
|
||||
set_modal: true,
|
||||
set_transient_for: Some(&init.root_win),
|
||||
add: model.tracker_role_groups.widget(),
|
||||
}
|
||||
}
|
||||
|
||||
fn update(&mut self, message: Self::Input, sender: ComponentSender<Self>) {
|
||||
self.reset();
|
||||
|
||||
match message {
|
||||
Self::Input::Present => {
|
||||
self.win.as_ref().unwrap().present();
|
||||
}
|
||||
Self::Input::Save => {
|
||||
dump_monado_config_v0(&self.monado_config_v0);
|
||||
self.win.as_ref().unwrap().close();
|
||||
}
|
||||
Self::Input::TrackerRoleChanged(index, n_role) => {
|
||||
let role = self.monado_config_v0.tracker_roles.get_mut(index).unwrap();
|
||||
role.device_serial = n_role.device_serial;
|
||||
role.role = n_role.role;
|
||||
role.xrt_input_name = n_role.xrt_input_name;
|
||||
}
|
||||
Self::Input::TrackerRoleDeleted(index) => {
|
||||
self.monado_config_v0.tracker_roles.remove(index);
|
||||
sender.input(Self::Input::Repopulate);
|
||||
}
|
||||
Self::Input::TrackerRoleNew => {
|
||||
self.monado_config_v0
|
||||
.tracker_roles
|
||||
.push(TrackerRole::default());
|
||||
self.tracker_role_groups
|
||||
.guard()
|
||||
.push_back(TrackerRoleModelInit {
|
||||
index: self.monado_config_v0.tracker_roles.len() - 1,
|
||||
tracker_role: None,
|
||||
});
|
||||
}
|
||||
Self::Input::Repopulate => {
|
||||
self.populate_tracker_roles();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn init(
|
||||
init: Self::Init,
|
||||
root: &Self::Root,
|
||||
sender: ComponentSender<Self>,
|
||||
) -> ComponentParts<Self> {
|
||||
let page = adw::PreferencesPage::builder().build();
|
||||
let grp = adw::PreferencesGroup::builder().build();
|
||||
let add_btn = gtk::Button::builder()
|
||||
.label("Add Tracker")
|
||||
.margin_bottom(12)
|
||||
.css_classes(["pill"])
|
||||
.halign(gtk::Align::Center)
|
||||
.build();
|
||||
let save_btn = gtk::Button::builder()
|
||||
.label("Save")
|
||||
.margin_bottom(12)
|
||||
.halign(gtk::Align::Center)
|
||||
.css_classes(["suggested-action", "pill"])
|
||||
.build();
|
||||
{
|
||||
withclones![sender];
|
||||
add_btn.connect_clicked(move |_| {
|
||||
sender.input(Self::Input::TrackerRoleNew);
|
||||
});
|
||||
};
|
||||
{
|
||||
withclones![sender];
|
||||
save_btn.connect_clicked(move |_| {
|
||||
sender.input(Self::Input::Save);
|
||||
});
|
||||
};
|
||||
grp.add(&save_btn);
|
||||
grp.add(&add_btn);
|
||||
page.add(&grp);
|
||||
|
||||
let mut model = Self {
|
||||
win: None,
|
||||
tracker: 0,
|
||||
monado_config_v0: get_monado_config_v0(),
|
||||
tracker_role_groups: FactoryVecDeque::new(page, sender.input_sender()),
|
||||
};
|
||||
|
||||
model.populate_tracker_roles();
|
||||
|
||||
let widgets = view_output!();
|
||||
model.win = Some(widgets.win.clone());
|
||||
|
||||
ComponentParts { model, widgets }
|
||||
}
|
||||
}
|
||||
|
||||
impl FbtConfigEditor {
|
||||
fn populate_tracker_roles(&mut self) {
|
||||
let mut guard = self.tracker_role_groups.guard();
|
||||
guard.clear();
|
||||
for (i, v) in self.monado_config_v0.tracker_roles.iter().enumerate() {
|
||||
guard.push_back(TrackerRoleModelInit {
|
||||
index: i,
|
||||
tracker_role: Some(v.clone()),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -10,7 +10,8 @@ use crate::gpu_profile::{get_gpu_power_profile, get_set_vr_pow_prof_cmd, GpuPowe
|
|||
use crate::profile::{LighthouseDriver, Profile};
|
||||
use crate::steamvr_utils::chaperone_info_exists;
|
||||
use crate::ui::app::{
|
||||
AboutAction, BuildProfileAction, BuildProfileCleanAction, DebugViewToggleAction,
|
||||
AboutAction, BuildProfileAction, BuildProfileCleanAction, ConfigFbtAction,
|
||||
DebugViewToggleAction,
|
||||
};
|
||||
use crate::ui::profile_editor::ProfileEditorInit;
|
||||
use crate::ui::util::limit_dropdown_width;
|
||||
|
@ -109,6 +110,7 @@ impl SimpleComponent for MainView {
|
|||
"_Debug View" => DebugViewToggleAction,
|
||||
"_Build Profile" => BuildProfileAction,
|
||||
"C_lean Build Profile" => BuildProfileCleanAction,
|
||||
"Configure Full Body _Tracking" => ConfigFbtAction,
|
||||
},
|
||||
section! {
|
||||
"_About" => AboutAction,
|
||||
|
|
|
@ -5,6 +5,7 @@ pub mod build_window;
|
|||
pub mod debug_view;
|
||||
pub mod devices_box;
|
||||
pub mod factories;
|
||||
pub mod fbt_config_editor;
|
||||
pub mod install_wivrn_box;
|
||||
pub mod libsurvive_setup_window;
|
||||
pub mod macros;
|
||||
|
|
|
@ -1,11 +1,8 @@
|
|||
use super::{
|
||||
factories::env_var_row_factory::{EnvVarModel, EnvVarModelInit},
|
||||
preference_rows::combo_row,
|
||||
};
|
||||
use super::factories::env_var_row_factory::{EnvVarModel, EnvVarModelInit};
|
||||
use crate::{
|
||||
env_var_descriptions::env_var_descriptions_as_paragraph,
|
||||
profile::{LighthouseDriver, Profile, XRServiceType},
|
||||
ui::preference_rows::{entry_row, path_row, switch_row},
|
||||
ui::preference_rows::{combo_row, entry_row, path_row, switch_row},
|
||||
withclones,
|
||||
};
|
||||
use adw::prelude::*;
|
||||
|
|
Loading…
Add table
Reference in a new issue