mirror of
https://gitlab.com/gabmus/envision.git
synced 2025-08-03 06:38:52 +00:00
feat: initial implementation of profile editing
This commit is contained in:
parent
6440a4f8b4
commit
13a1a951a9
12 changed files with 368 additions and 63 deletions
32
Cargo.lock
generated
32
Cargo.lock
generated
|
@ -1351,6 +1351,12 @@ version = "0.3.27"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964"
|
checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ppv-lite86"
|
||||||
|
version = "0.2.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro-crate"
|
name = "proc-macro-crate"
|
||||||
version = "1.3.1"
|
version = "1.3.1"
|
||||||
|
@ -1419,6 +1425,18 @@ version = "0.8.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"rand_chacha",
|
||||||
|
"rand_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_chacha"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||||
|
dependencies = [
|
||||||
|
"ppv-lite86",
|
||||||
"rand_core",
|
"rand_core",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -1427,6 +1445,9 @@ name = "rand_core"
|
||||||
version = "0.6.4"
|
version = "0.6.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "raw-window-handle"
|
name = "raw-window-handle"
|
||||||
|
@ -1588,6 +1609,7 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"tracker",
|
"tracker",
|
||||||
|
"uuid",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2042,6 +2064,16 @@ dependencies = [
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "uuid"
|
||||||
|
version = "1.3.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0fa2982af2eec27de306107c027578ff7f423d65f7250e40ce0fea8f45248b81"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom",
|
||||||
|
"rand",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "vcpkg"
|
name = "vcpkg"
|
||||||
version = "0.2.15"
|
version = "0.2.15"
|
||||||
|
|
|
@ -33,3 +33,4 @@ serde = { version = "1.0.163", features = [
|
||||||
] }
|
] }
|
||||||
serde_json = "1.0.96"
|
serde_json = "1.0.96"
|
||||||
tracker = "0.2.1"
|
tracker = "0.2.1"
|
||||||
|
uuid = { version = "1.3.4", features = ["v4", "fast-rng"] }
|
||||||
|
|
|
@ -8,7 +8,7 @@ use std::{fs::File, io::BufReader};
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
pub selected_profile_name: String,
|
pub selected_profile_uuid: String,
|
||||||
pub debug_view_enabled: bool,
|
pub debug_view_enabled: bool,
|
||||||
pub user_profiles: Vec<Profile>,
|
pub user_profiles: Vec<Profile>,
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@ impl Default for Config {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Config {
|
Config {
|
||||||
// TODO: handle first start with no profile selected
|
// TODO: handle first start with no profile selected
|
||||||
selected_profile_name: "".to_string(),
|
selected_profile_uuid: "".to_string(),
|
||||||
debug_view_enabled: false,
|
debug_view_enabled: false,
|
||||||
user_profiles: vec![],
|
user_profiles: vec![],
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,7 @@ impl Default for Config {
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
pub fn get_selected_profile(&self, profiles: &Vec<Profile>) -> Profile {
|
pub fn get_selected_profile(&self, profiles: &Vec<Profile>) -> Profile {
|
||||||
match profiles.iter().find(|p| {p.name == self.selected_profile_name}) {
|
match profiles.iter().find(|p| {p.uuid == self.selected_profile_uuid}) {
|
||||||
Some(p) => p.clone(),
|
Some(p) => p.clone(),
|
||||||
None => profiles.get(0).expect_dialog("No profiles found").clone(),
|
None => profiles.get(0).expect_dialog("No profiles found").clone(),
|
||||||
}
|
}
|
||||||
|
@ -65,6 +65,10 @@ impl Config {
|
||||||
pub fn get_config() -> Self {
|
pub fn get_config() -> Self {
|
||||||
Self::from_path(Self::config_file_path())
|
Self::from_path(Self::config_file_path())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_profiles(&mut self, profiles: &Vec<Profile>) {
|
||||||
|
self.user_profiles = profiles.iter().filter(|p| p.editable).map(Profile::clone).collect();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -5,6 +5,7 @@ use crate::{
|
||||||
use expect_dialog::ExpectDialog;
|
use expect_dialog::ExpectDialog;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::{collections::HashMap, fmt::Display, fs::File, io::BufReader, slice::Iter};
|
use std::{collections::HashMap, fmt::Display, fs::File, io::BufReader, slice::Iter};
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub enum XRServiceType {
|
pub enum XRServiceType {
|
||||||
|
@ -27,7 +28,8 @@ impl XRServiceType {
|
||||||
|
|
||||||
pub fn as_number(&self) -> u32 {
|
pub fn as_number(&self) -> u32 {
|
||||||
match self {
|
match self {
|
||||||
Self::Monado => 0, Self::Wivrn => 1,
|
Self::Monado => 0,
|
||||||
|
Self::Wivrn => 1,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -43,6 +45,7 @@ impl Display for XRServiceType {
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct Profile {
|
pub struct Profile {
|
||||||
|
pub uuid: String,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub xrservice_path: String,
|
pub xrservice_path: String,
|
||||||
pub xrservice_type: XRServiceType,
|
pub xrservice_type: XRServiceType,
|
||||||
|
@ -69,6 +72,7 @@ impl Display for Profile {
|
||||||
impl Default for Profile {
|
impl Default for Profile {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
uuid: Uuid::new_v4().to_string(),
|
||||||
name: "Default profile name".into(),
|
name: "Default profile name".into(),
|
||||||
xrservice_path: data_monado_path(),
|
xrservice_path: data_monado_path(),
|
||||||
xrservice_type: XRServiceType::Monado,
|
xrservice_type: XRServiceType::Monado,
|
||||||
|
@ -125,6 +129,26 @@ impl Profile {
|
||||||
let writer = get_writer(path_s);
|
let writer = get_writer(path_s);
|
||||||
serde_json::to_writer_pretty(writer, self).expect_dialog("Could not write profile")
|
serde_json::to_writer_pretty(writer, self).expect_dialog("Could not write profile")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn create_duplicate(&self) -> Self {
|
||||||
|
let mut dup = self.clone();
|
||||||
|
dup.uuid = Uuid::new_v4().to_string();
|
||||||
|
dup.editable = true;
|
||||||
|
dup.name = format!("Duplicate of {}", dup.name);
|
||||||
|
dup
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn validate(&self) -> bool {
|
||||||
|
!self.name.is_empty()
|
||||||
|
&& self.editable
|
||||||
|
&& !self.uuid.is_empty()
|
||||||
|
&& !self.xrservice_path.is_empty()
|
||||||
|
&& !self.prefix.is_empty()
|
||||||
|
&& (!self.libsurvive_enabled
|
||||||
|
|| self.libsurvive_path.as_ref().is_some_and(|p| !p.is_empty()))
|
||||||
|
&& (!self.basalt_enabled || self.basalt_path.as_ref().is_some_and(|p| !p.is_empty()))
|
||||||
|
&& (!self.mercury_enabled || self.mercury_path.as_ref().is_some_and(|p| !p.is_empty()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -166,6 +190,7 @@ mod tests {
|
||||||
env.insert("XRT_COMPOSITOR_SCALE_PERCENTAGE".into(), "140".into());
|
env.insert("XRT_COMPOSITOR_SCALE_PERCENTAGE".into(), "140".into());
|
||||||
env.insert("XRT_COMPOSITOR_COMPUTE".into(), "1".into());
|
env.insert("XRT_COMPOSITOR_COMPUTE".into(), "1".into());
|
||||||
let p = Profile {
|
let p = Profile {
|
||||||
|
uuid: "demo".into(),
|
||||||
name: "Demo profile".into(),
|
name: "Demo profile".into(),
|
||||||
xrservice_path: String::from("/home/user/monado"),
|
xrservice_path: String::from("/home/user/monado"),
|
||||||
xrservice_type: XRServiceType::Monado,
|
xrservice_type: XRServiceType::Monado,
|
||||||
|
|
|
@ -8,6 +8,7 @@ pub fn system_valve_index_profile() -> Profile {
|
||||||
environment.insert("SURVIVE_GLOBALSCENESOLVER".into(), "0".into());
|
environment.insert("SURVIVE_GLOBALSCENESOLVER".into(), "0".into());
|
||||||
environment.insert("SURVIVE_TIMECODE_OFFSET_MS".into(), "-6.94".into());
|
environment.insert("SURVIVE_TIMECODE_OFFSET_MS".into(), "-6.94".into());
|
||||||
Profile {
|
Profile {
|
||||||
|
uuid: "system-valve-index-default".into(),
|
||||||
name: format!("Valve Index (System) - {name} Default", name = APP_NAME),
|
name: format!("Valve Index (System) - {name} Default", name = APP_NAME),
|
||||||
opencomposite_path: data_opencomposite_path(),
|
opencomposite_path: data_opencomposite_path(),
|
||||||
xrservice_path: "".into(),
|
xrservice_path: "".into(),
|
||||||
|
|
|
@ -11,6 +11,7 @@ pub fn valve_index_profile() -> Profile {
|
||||||
environment.insert("SURVIVE_TIMECODE_OFFSET_MS".into(), "-6.94".into());
|
environment.insert("SURVIVE_TIMECODE_OFFSET_MS".into(), "-6.94".into());
|
||||||
environment.insert("LD_LIBRARY_PATH".into(), format!("{pfx}/lib", pfx = prefix));
|
environment.insert("LD_LIBRARY_PATH".into(), format!("{pfx}/lib", pfx = prefix));
|
||||||
Profile {
|
Profile {
|
||||||
|
uuid: "valve-index-default".into(),
|
||||||
name: format!("Valve Index - {name} Default", name = APP_NAME),
|
name: format!("Valve Index - {name} Default", name = APP_NAME),
|
||||||
xrservice_path: data_monado_path(),
|
xrservice_path: data_monado_path(),
|
||||||
xrservice_type: XRServiceType::Monado,
|
xrservice_type: XRServiceType::Monado,
|
||||||
|
|
|
@ -8,6 +8,7 @@ pub fn wivrn_profile() -> Profile {
|
||||||
let mut environment: HashMap<String, String> = HashMap::new();
|
let mut environment: HashMap<String, String> = HashMap::new();
|
||||||
environment.insert("LD_LIBRARY_PATH".into(), format!("{pfx}/lib", pfx = prefix));
|
environment.insert("LD_LIBRARY_PATH".into(), format!("{pfx}/lib", pfx = prefix));
|
||||||
Profile {
|
Profile {
|
||||||
|
uuid: "wivrn-default".into(),
|
||||||
name: format!("WiVRn - {name} Default", name = APP_NAME),
|
name: format!("WiVRn - {name} Default", name = APP_NAME),
|
||||||
xrservice_path: data_wivrn_path(),
|
xrservice_path: data_wivrn_path(),
|
||||||
xrservice_type: XRServiceType::Wivrn,
|
xrservice_type: XRServiceType::Wivrn,
|
||||||
|
|
|
@ -29,7 +29,7 @@ use crate::ui::libsurvive_setup_window::LibsurviveSetupMsg;
|
||||||
use crate::ui::main_view::{MainView, MainViewInit, MainViewOutMsg};
|
use crate::ui::main_view::{MainView, MainViewInit, MainViewOutMsg};
|
||||||
use expect_dialog::ExpectDialog;
|
use expect_dialog::ExpectDialog;
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
use relm4::actions::{ActionGroupName, RelmAction, RelmActionGroup, AccelsPlus};
|
use relm4::actions::{AccelsPlus, ActionGroupName, RelmAction, RelmActionGroup};
|
||||||
use relm4::adw::traits::MessageDialogExt;
|
use relm4::adw::traits::MessageDialogExt;
|
||||||
use relm4::adw::ResponseAppearance;
|
use relm4::adw::ResponseAppearance;
|
||||||
use relm4::gtk::glib;
|
use relm4::gtk::glib;
|
||||||
|
@ -77,7 +77,9 @@ pub enum Msg {
|
||||||
BuildProfile,
|
BuildProfile,
|
||||||
EnableDebugViewChanged(bool),
|
EnableDebugViewChanged(bool),
|
||||||
DoStartStopXRService,
|
DoStartStopXRService,
|
||||||
ProfileSelected(String),
|
ProfileSelected(Profile),
|
||||||
|
DeleteProfile,
|
||||||
|
SaveProfile(Profile),
|
||||||
RunSetCap,
|
RunSetCap,
|
||||||
OpenLibsurviveSetup,
|
OpenLibsurviveSetup,
|
||||||
Quit,
|
Quit,
|
||||||
|
@ -103,6 +105,17 @@ impl App {
|
||||||
runner.start();
|
runner.start();
|
||||||
self.xrservice_runner = Some(runner);
|
self.xrservice_runner = Some(runner);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn profiles_list(config: &Config) -> Vec<Profile> {
|
||||||
|
let mut profiles = vec![
|
||||||
|
valve_index_profile(),
|
||||||
|
system_valve_index_profile(),
|
||||||
|
wivrn_profile(),
|
||||||
|
];
|
||||||
|
profiles.extend(config.user_profiles.clone());
|
||||||
|
profiles.sort_unstable_by(|a, b| a.name.cmp(&b.name));
|
||||||
|
profiles
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -196,7 +209,9 @@ impl SimpleComponent for App {
|
||||||
);
|
);
|
||||||
// apparently setcap on wivrn causes issues, so in case
|
// apparently setcap on wivrn causes issues, so in case
|
||||||
// it's not monado, we're just skipping this
|
// it's not monado, we're just skipping this
|
||||||
if self.get_selected_profile().xrservice_type == XRServiceType::Monado {
|
if self.get_selected_profile().xrservice_type
|
||||||
|
== XRServiceType::Monado
|
||||||
|
{
|
||||||
self.setcap_confirm_dialog.present();
|
self.setcap_confirm_dialog.present();
|
||||||
}
|
}
|
||||||
self.build_window
|
self.build_window
|
||||||
|
@ -320,6 +335,47 @@ impl SimpleComponent for App {
|
||||||
.emit(BuildWindowMsg::UpdateCanClose(false));
|
.emit(BuildWindowMsg::UpdateCanClose(false));
|
||||||
self.build_pipeline = Some(pipeline);
|
self.build_pipeline = Some(pipeline);
|
||||||
}
|
}
|
||||||
|
Msg::DeleteProfile => {
|
||||||
|
let todel = self.get_selected_profile();
|
||||||
|
if todel.editable {
|
||||||
|
self.config.user_profiles = self
|
||||||
|
.config
|
||||||
|
.user_profiles
|
||||||
|
.iter()
|
||||||
|
.filter(|p| p.uuid != todel.uuid)
|
||||||
|
.map(|p| p.clone())
|
||||||
|
.collect();
|
||||||
|
self.config.save();
|
||||||
|
self.profiles = Self::profiles_list(&self.config);
|
||||||
|
self.main_view.sender().emit(MainViewMsg::UpdateSelectedProfile(
|
||||||
|
self.get_selected_profile()
|
||||||
|
));
|
||||||
|
self.main_view
|
||||||
|
.sender()
|
||||||
|
.emit(MainViewMsg::UpdateProfiles(
|
||||||
|
self.profiles.clone(),
|
||||||
|
self.config.clone(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Msg::SaveProfile(prof) => {
|
||||||
|
match self.profiles.iter().position(|p| p.uuid == prof.uuid) {
|
||||||
|
None => {},
|
||||||
|
Some(index) => {
|
||||||
|
self.profiles.remove(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.profiles.push(prof);
|
||||||
|
self.profiles.sort_unstable_by(|a, b| a.name.cmp(&b.name));
|
||||||
|
self.config.set_profiles(&self.profiles);
|
||||||
|
self.config.save();
|
||||||
|
self.main_view
|
||||||
|
.sender()
|
||||||
|
.emit(MainViewMsg::UpdateProfiles(
|
||||||
|
self.profiles.clone(),
|
||||||
|
self.config.clone(),
|
||||||
|
))
|
||||||
|
}
|
||||||
Msg::RunSetCap => {
|
Msg::RunSetCap => {
|
||||||
if !check_dependency(pkexec_dep()) {
|
if !check_dependency(pkexec_dep()) {
|
||||||
println!("pkexec not found, skipping setcap");
|
println!("pkexec not found, skipping setcap");
|
||||||
|
@ -331,11 +387,11 @@ impl SimpleComponent for App {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Msg::ProfileSelected(prof_name) => {
|
Msg::ProfileSelected(prof) => {
|
||||||
if prof_name == self.config.selected_profile_name {
|
if prof.uuid == self.config.selected_profile_uuid {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
self.config.selected_profile_name = prof_name;
|
self.config.selected_profile_uuid = prof.uuid;
|
||||||
self.config.save();
|
self.config.save();
|
||||||
let profile = self.get_selected_profile();
|
let profile = self.get_selected_profile();
|
||||||
self.main_view
|
self.main_view
|
||||||
|
@ -349,7 +405,7 @@ impl SimpleComponent for App {
|
||||||
self.get_selected_profile().clone(),
|
self.get_selected_profile().clone(),
|
||||||
))
|
))
|
||||||
.expect_dialog("Failed to present Libsurvive Setup Window");
|
.expect_dialog("Failed to present Libsurvive Setup Window");
|
||||||
},
|
}
|
||||||
Msg::Quit => {
|
Msg::Quit => {
|
||||||
self.application.quit();
|
self.application.quit();
|
||||||
}
|
}
|
||||||
|
@ -362,12 +418,7 @@ impl SimpleComponent for App {
|
||||||
sender: ComponentSender<Self>,
|
sender: ComponentSender<Self>,
|
||||||
) -> ComponentParts<Self> {
|
) -> ComponentParts<Self> {
|
||||||
let config = Config::get_config();
|
let config = Config::get_config();
|
||||||
let mut profiles = vec![
|
let profiles = Self::profiles_list(&config);
|
||||||
valve_index_profile(),
|
|
||||||
system_valve_index_profile(),
|
|
||||||
wivrn_profile(),
|
|
||||||
];
|
|
||||||
profiles.extend(config.user_profiles.clone());
|
|
||||||
let dependencies_dialog = adw::MessageDialog::builder()
|
let dependencies_dialog = adw::MessageDialog::builder()
|
||||||
.modal(true)
|
.modal(true)
|
||||||
.transient_for(root)
|
.transient_for(root)
|
||||||
|
@ -410,7 +461,9 @@ impl SimpleComponent for App {
|
||||||
.forward(sender.input_sender(), |message| match message {
|
.forward(sender.input_sender(), |message| match message {
|
||||||
MainViewOutMsg::EnableDebugViewChanged(val) => Msg::EnableDebugViewChanged(val),
|
MainViewOutMsg::EnableDebugViewChanged(val) => Msg::EnableDebugViewChanged(val),
|
||||||
MainViewOutMsg::DoStartStopXRService => Msg::DoStartStopXRService,
|
MainViewOutMsg::DoStartStopXRService => Msg::DoStartStopXRService,
|
||||||
MainViewOutMsg::ProfileSelected(name) => Msg::ProfileSelected(name),
|
MainViewOutMsg::ProfileSelected(uuid) => Msg::ProfileSelected(uuid),
|
||||||
|
MainViewOutMsg::DeleteProfile => Msg::DeleteProfile,
|
||||||
|
MainViewOutMsg::SaveProfile(p) => Msg::SaveProfile(p),
|
||||||
}),
|
}),
|
||||||
debug_view: DebugView::builder()
|
debug_view: DebugView::builder()
|
||||||
.launch(DebugViewInit {
|
.launch(DebugViewInit {
|
||||||
|
@ -492,13 +545,15 @@ impl SimpleComponent for App {
|
||||||
actions.add_action(libsurvive_setup_action);
|
actions.add_action(libsurvive_setup_action);
|
||||||
|
|
||||||
root.insert_action_group(AppActionGroup::NAME, Some(&actions.into_action_group()));
|
root.insert_action_group(AppActionGroup::NAME, Some(&actions.into_action_group()));
|
||||||
model.application.set_accelerators_for_action::<QuitAction>(&["<Control>q"]);
|
model
|
||||||
|
.application
|
||||||
|
.set_accelerators_for_action::<QuitAction>(&["<Control>q"]);
|
||||||
|
|
||||||
model
|
model
|
||||||
.main_view
|
.main_view
|
||||||
.sender()
|
.sender()
|
||||||
.emit(MainViewMsg::UpdateProfileNames(
|
.emit(MainViewMsg::UpdateProfiles(
|
||||||
model.profiles.iter().map(|p| p.clone().name).collect(),
|
model.profiles.clone(),
|
||||||
model.config.clone(),
|
model.config.clone(),
|
||||||
));
|
));
|
||||||
|
|
||||||
|
|
|
@ -8,8 +8,9 @@ use crate::ui::profile_editor::ProfileEditorMsg;
|
||||||
pub struct PathModel {
|
pub struct PathModel {
|
||||||
name: String,
|
name: String,
|
||||||
key: String,
|
key: String,
|
||||||
value: String,
|
value: Option<String>,
|
||||||
path_label: gtk::Label,
|
path_label: gtk::Label,
|
||||||
|
filedialog: gtk::FileDialog,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct PathModelInit {
|
pub struct PathModelInit {
|
||||||
|
@ -20,14 +21,14 @@ pub struct PathModelInit {
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum PathModelMsg {
|
pub enum PathModelMsg {
|
||||||
Changed(String),
|
Changed(Option<String>),
|
||||||
OpenFileChooser,
|
OpenFileChooser,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum PathModelOutMsg {
|
pub enum PathModelOutMsg {
|
||||||
/** key, value */
|
/** key, value */
|
||||||
Changed(String, String),
|
Changed(String, Option<String>),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[relm4::factory(pub)]
|
#[relm4::factory(pub)]
|
||||||
|
@ -47,41 +48,77 @@ impl FactoryComponent for PathModel {
|
||||||
set_icon_name: Some("folder-open-symbolic"),
|
set_icon_name: Some("folder-open-symbolic"),
|
||||||
add_suffix: &self.path_label,
|
add_suffix: &self.path_label,
|
||||||
set_activatable: true,
|
set_activatable: true,
|
||||||
|
add_suffix: unset_btn = >k::Button {
|
||||||
|
set_valign: gtk::Align::Center,
|
||||||
|
add_css_class: "flat",
|
||||||
|
add_css_class: "circular",
|
||||||
|
set_icon_name: "edit-clear",
|
||||||
|
set_tooltip_text: Some("Clear Path"),
|
||||||
|
connect_clicked[sender] => move |_| {
|
||||||
|
sender.input(Self::Input::Changed(None));
|
||||||
|
}
|
||||||
|
},
|
||||||
connect_activated[sender] => move |_| {
|
connect_activated[sender] => move |_| {
|
||||||
sender.input(Self::Input::OpenFileChooser)
|
sender.input(Self::Input::OpenFileChooser)
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(&mut self, message: Self::Input, sender: FactorySender<Self>) {
|
fn update(&mut self, message: Self::Input, sender: FactorySender<Self>) {
|
||||||
match message {
|
match message {
|
||||||
Self::Input::Changed(val) => {
|
Self::Input::Changed(val) => {
|
||||||
self.value = val.clone();
|
self.value = val;
|
||||||
self.path_label.set_label(val.as_str());
|
self.path_label.set_label(match self.value.as_ref() {
|
||||||
|
Some(val) => val.as_str(),
|
||||||
|
None => "(None)".into(),
|
||||||
|
});
|
||||||
sender
|
sender
|
||||||
.output_sender()
|
.output_sender()
|
||||||
.emit(Self::Output::Changed(self.key.clone(), val))
|
.emit(Self::Output::Changed(self.key.clone(), self.value.clone()))
|
||||||
}
|
}
|
||||||
Self::Input::OpenFileChooser => {
|
Self::Input::OpenFileChooser => {
|
||||||
println!("file chooser");
|
let fd_sender = sender.clone();
|
||||||
|
self.filedialog.select_folder(
|
||||||
|
self.init_root().root().and_downcast_ref::<gtk::Window>(),
|
||||||
|
gtk::gio::Cancellable::NONE,
|
||||||
|
move |res| match res {
|
||||||
|
Ok(file) => {
|
||||||
|
let path = file.path();
|
||||||
|
if path.is_some() {
|
||||||
|
fd_sender.input(Self::Input::Changed(
|
||||||
|
Some(path.unwrap().to_str().unwrap().to_string())
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn forward_to_parent(_output: Self::Output) -> Option<Self::ParentInput> {
|
fn forward_to_parent(output: Self::Output) -> Option<Self::ParentInput> {
|
||||||
None
|
Some(match output {
|
||||||
|
Self::Output::Changed(key, value) => ProfileEditorMsg::PathChanged(key, value)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init_model(init: Self::Init, index: &Self::Index, sender: FactorySender<Self>) -> Self {
|
fn init_model(init: Self::Init, index: &Self::Index, sender: FactorySender<Self>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
name: init.name,
|
name: init.name.clone(),
|
||||||
key: init.key,
|
key: init.key,
|
||||||
value: match init.value.as_ref() {
|
value: init.value.clone(),
|
||||||
Some(val) => val.clone(), None => "".into()
|
path_label: gtk::Label::builder()
|
||||||
},
|
.label(match init.value {
|
||||||
path_label: gtk::Label::builder().label(match init.value {
|
Some(val) => val,
|
||||||
Some(val) => val, None => "(None)".into()
|
None => "(None)".into(),
|
||||||
}).ellipsize(gtk::pango::EllipsizeMode::Start).build(),
|
})
|
||||||
|
.ellipsize(gtk::pango::EllipsizeMode::Start)
|
||||||
|
.build(),
|
||||||
|
filedialog: gtk::FileDialog::builder()
|
||||||
|
.modal(true)
|
||||||
|
.title(format!("Select Path for {}", init.name.clone()))
|
||||||
|
.build(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,8 +71,10 @@ impl FactoryComponent for SwitchModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn forward_to_parent(_output: Self::Output) -> Option<Self::ParentInput> {
|
fn forward_to_parent(output: Self::Output) -> Option<Self::ParentInput> {
|
||||||
None
|
Some(match output {
|
||||||
|
Self::Output::Changed(key, value) => ProfileEditorMsg::SwitchChanged(key, value)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init_model(init: Self::Init, index: &Self::Index, sender: FactorySender<Self>) -> Self {
|
fn init_model(init: Self::Init, index: &Self::Index, sender: FactorySender<Self>) -> Self {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use super::install_wivrn_box::{InstallWivrnBox, InstallWivrnBoxInit, InstallWivrnBoxMsg};
|
use super::install_wivrn_box::{InstallWivrnBox, InstallWivrnBoxInit, InstallWivrnBoxMsg};
|
||||||
use super::profile_editor::{ProfileEditor, ProfileEditorMsg};
|
use super::profile_editor::{ProfileEditor, ProfileEditorMsg, ProfileEditorOutMsg};
|
||||||
use super::runtime_switcher_box::{
|
use super::runtime_switcher_box::{
|
||||||
RuntimeSwitcherBox, RuntimeSwitcherBoxInit, RuntimeSwitcherBoxMsg,
|
RuntimeSwitcherBox, RuntimeSwitcherBoxInit, RuntimeSwitcherBoxMsg,
|
||||||
};
|
};
|
||||||
|
@ -22,7 +22,7 @@ use relm4_icons::icon_name;
|
||||||
pub struct MainView {
|
pub struct MainView {
|
||||||
xrservice_active: bool,
|
xrservice_active: bool,
|
||||||
enable_debug_view: bool,
|
enable_debug_view: bool,
|
||||||
profile_names: Vec<String>,
|
profiles: Vec<Profile>,
|
||||||
selected_profile: Profile,
|
selected_profile: Profile,
|
||||||
#[tracker::do_not_track]
|
#[tracker::do_not_track]
|
||||||
profiles_dropdown: Option<gtk::DropDown>,
|
profiles_dropdown: Option<gtk::DropDown>,
|
||||||
|
@ -36,6 +36,8 @@ pub struct MainView {
|
||||||
profile_editor: Controller<ProfileEditor>,
|
profile_editor: Controller<ProfileEditor>,
|
||||||
#[tracker::do_not_track]
|
#[tracker::do_not_track]
|
||||||
profile_not_editable_dialog: adw::MessageDialog,
|
profile_not_editable_dialog: adw::MessageDialog,
|
||||||
|
#[tracker::do_not_track]
|
||||||
|
profile_delete_confirm_dialog: adw::MessageDialog,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -44,20 +46,24 @@ pub enum MainViewMsg {
|
||||||
StartStopClicked,
|
StartStopClicked,
|
||||||
XRServiceActiveChanged(bool, Option<Profile>),
|
XRServiceActiveChanged(bool, Option<Profile>),
|
||||||
EnableDebugViewChanged(bool),
|
EnableDebugViewChanged(bool),
|
||||||
UpdateProfileNames(Vec<String>, Config),
|
UpdateProfiles(Vec<Profile>, Config),
|
||||||
SetSelectedProfile(u32),
|
SetSelectedProfile(u32),
|
||||||
ProfileSelected(u32),
|
ProfileSelected(u32),
|
||||||
UpdateSelectedProfile(Profile),
|
UpdateSelectedProfile(Profile),
|
||||||
EditProfile,
|
EditProfile,
|
||||||
CreateProfile,
|
CreateProfile,
|
||||||
|
DeleteProfile,
|
||||||
DuplicateProfile,
|
DuplicateProfile,
|
||||||
|
SaveProfile(Profile),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum MainViewOutMsg {
|
pub enum MainViewOutMsg {
|
||||||
EnableDebugViewChanged(bool),
|
EnableDebugViewChanged(bool),
|
||||||
DoStartStopXRService,
|
DoStartStopXRService,
|
||||||
ProfileSelected(String),
|
ProfileSelected(Profile),
|
||||||
|
DeleteProfile,
|
||||||
|
SaveProfile(Profile),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct MainViewInit {
|
pub struct MainViewInit {
|
||||||
|
@ -156,9 +162,9 @@ impl SimpleComponent for MainView {
|
||||||
gtk::DropDown {
|
gtk::DropDown {
|
||||||
set_hexpand: true,
|
set_hexpand: true,
|
||||||
set_tooltip_text: Some("Profiles"),
|
set_tooltip_text: Some("Profiles"),
|
||||||
#[track = "model.changed(Self::profile_names())"]
|
#[track = "model.changed(Self::profiles())"]
|
||||||
set_model: Some(&{
|
set_model: Some(&{
|
||||||
let names: Vec<_> = model.profile_names.iter().map(String::as_str).collect();
|
let names: Vec<_> = model.profiles.iter().map(|p| p.name.as_str()).collect();
|
||||||
gtk::StringList::new(&names)
|
gtk::StringList::new(&names)
|
||||||
}),
|
}),
|
||||||
connect_selected_item_notify[sender] => move |this| {
|
connect_selected_item_notify[sender] => move |this| {
|
||||||
|
@ -178,7 +184,17 @@ impl SimpleComponent for MainView {
|
||||||
connect_clicked[sender] => move |_| {
|
connect_clicked[sender] => move |_| {
|
||||||
sender.input(Self::Input::CreateProfile);
|
sender.input(Self::Input::CreateProfile);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
gtk::Button {
|
||||||
|
set_icon_name: "edit-delete-symbolic",
|
||||||
|
add_css_class: "destructive-action",
|
||||||
|
set_tooltip_text: Some("Delete Profile"),
|
||||||
|
#[track = "model.changed(Self::selected_profile())"]
|
||||||
|
set_sensitive: model.selected_profile.editable,
|
||||||
|
connect_clicked[sender] => move |_| {
|
||||||
|
sender.input(Self::Input::DeleteProfile);
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -222,17 +238,17 @@ impl SimpleComponent for MainView {
|
||||||
.sender()
|
.sender()
|
||||||
.emit(RuntimeSwitcherBoxMsg::UpdateSelectedProfile(prof.clone()));
|
.emit(RuntimeSwitcherBoxMsg::UpdateSelectedProfile(prof.clone()));
|
||||||
}
|
}
|
||||||
Self::Input::UpdateProfileNames(names, config) => {
|
Self::Input::UpdateProfiles(profiles, config) => {
|
||||||
self.set_profile_names(names);
|
self.set_profiles(profiles);
|
||||||
// why send another message to set the dropdown selection?
|
// why send another message to set the dropdown selection?
|
||||||
// set_* from tracker likely updates the view obj in the next
|
// set_* from tracker likely updates the view obj in the next
|
||||||
// draw, so selecting here will result in nothing cause the
|
// draw, so selecting here will result in nothing cause the
|
||||||
// dropdown is effectively empty
|
// dropdown is effectively empty
|
||||||
sender.input(MainViewMsg::SetSelectedProfile({
|
sender.input(MainViewMsg::SetSelectedProfile({
|
||||||
let pos = self
|
let pos = self
|
||||||
.profile_names
|
.profiles
|
||||||
.iter()
|
.iter()
|
||||||
.position(|p| p.clone() == config.selected_profile_name);
|
.position(|p| p.uuid == config.selected_profile_uuid);
|
||||||
match pos {
|
match pos {
|
||||||
Some(idx) => idx as u32,
|
Some(idx) => idx as u32,
|
||||||
None => 0,
|
None => 0,
|
||||||
|
@ -247,10 +263,8 @@ impl SimpleComponent for MainView {
|
||||||
.set_selected(index);
|
.set_selected(index);
|
||||||
}
|
}
|
||||||
Self::Input::ProfileSelected(position) => {
|
Self::Input::ProfileSelected(position) => {
|
||||||
// self.install_wivrn_box.sender.emit(InstallWivrnBoxMsg::Upda);
|
|
||||||
// TODO: send profile to install_wivrn_box
|
|
||||||
sender.output(MainViewOutMsg::ProfileSelected(
|
sender.output(MainViewOutMsg::ProfileSelected(
|
||||||
self.profile_names.get(position as usize).unwrap().clone(),
|
self.profiles.get(position as usize).unwrap().clone(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
Self::Input::EditProfile => {
|
Self::Input::EditProfile => {
|
||||||
|
@ -262,10 +276,20 @@ impl SimpleComponent for MainView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Self::Input::CreateProfile => {
|
Self::Input::CreateProfile => {
|
||||||
println!("create");
|
self.profile_editor
|
||||||
|
.sender()
|
||||||
|
.emit(ProfileEditorMsg::Present(Profile::default()));
|
||||||
|
}
|
||||||
|
Self::Input::DeleteProfile => {
|
||||||
|
self.profile_delete_confirm_dialog.present();
|
||||||
|
}
|
||||||
|
Self::Input::SaveProfile(prof) => {
|
||||||
|
sender.output(Self::Output::SaveProfile(prof));
|
||||||
}
|
}
|
||||||
Self::Input::DuplicateProfile => {
|
Self::Input::DuplicateProfile => {
|
||||||
println!("dup");
|
self.profile_editor.sender().emit(ProfileEditorMsg::Present(
|
||||||
|
self.selected_profile.create_duplicate(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -277,12 +301,12 @@ impl SimpleComponent for MainView {
|
||||||
) -> ComponentParts<Self> {
|
) -> ComponentParts<Self> {
|
||||||
let profile_not_editable_dialog = adw::MessageDialog::builder()
|
let profile_not_editable_dialog = adw::MessageDialog::builder()
|
||||||
.modal(true)
|
.modal(true)
|
||||||
|
.hide_on_close(true)
|
||||||
.heading("This profile is not editable")
|
.heading("This profile is not editable")
|
||||||
.body(concat!(
|
.body(concat!(
|
||||||
"You can duplicate it and edit the new copy. ",
|
"You can duplicate it and edit the new copy. ",
|
||||||
"Do you want to duplicate the current profile?"
|
"Do you want to duplicate the current profile?"
|
||||||
))
|
))
|
||||||
.hide_on_close(true)
|
|
||||||
.build();
|
.build();
|
||||||
profile_not_editable_dialog.add_response("no", "_No");
|
profile_not_editable_dialog.add_response("no", "_No");
|
||||||
profile_not_editable_dialog.add_response("yes", "_Yes");
|
profile_not_editable_dialog.add_response("yes", "_Yes");
|
||||||
|
@ -298,11 +322,31 @@ impl SimpleComponent for MainView {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let profile_delete_confirm_dialog = adw::MessageDialog::builder()
|
||||||
|
.modal(true)
|
||||||
|
.hide_on_close(true)
|
||||||
|
.heading("Are you sure you want to delete this profile?")
|
||||||
|
.build();
|
||||||
|
profile_delete_confirm_dialog.add_response("no", "_No");
|
||||||
|
profile_delete_confirm_dialog.add_response("yes", "_Yes");
|
||||||
|
profile_delete_confirm_dialog
|
||||||
|
.set_response_appearance("no", ResponseAppearance::Destructive);
|
||||||
|
profile_delete_confirm_dialog.set_response_appearance("yes", ResponseAppearance::Suggested);
|
||||||
|
|
||||||
|
{
|
||||||
|
let pdc_sender = sender.clone();
|
||||||
|
profile_delete_confirm_dialog.connect_response(None, move |_, res| {
|
||||||
|
if res == "yes" {
|
||||||
|
pdc_sender.output(Self::Output::DeleteProfile);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
let mut model = Self {
|
let mut model = Self {
|
||||||
xrservice_active: false,
|
xrservice_active: false,
|
||||||
enable_debug_view: init.config.debug_view_enabled,
|
enable_debug_view: init.config.debug_view_enabled,
|
||||||
profiles_dropdown: None,
|
profiles_dropdown: None,
|
||||||
profile_names: vec![],
|
profiles: vec![],
|
||||||
steam_launch_options_box: SteamLaunchOptionsBox::builder().launch(()).detach(),
|
steam_launch_options_box: SteamLaunchOptionsBox::builder().launch(()).detach(),
|
||||||
install_wivrn_box: InstallWivrnBox::builder()
|
install_wivrn_box: InstallWivrnBox::builder()
|
||||||
.launch(InstallWivrnBoxInit {
|
.launch(InstallWivrnBoxInit {
|
||||||
|
@ -316,9 +360,12 @@ impl SimpleComponent for MainView {
|
||||||
.detach(),
|
.detach(),
|
||||||
profile_editor: ProfileEditor::builder()
|
profile_editor: ProfileEditor::builder()
|
||||||
.launch(ProfileEditorInit {})
|
.launch(ProfileEditorInit {})
|
||||||
.detach(),
|
.forward(sender.input_sender(), |message| match message {
|
||||||
|
ProfileEditorOutMsg::SaveProfile(p) => Self::Input::SaveProfile(p),
|
||||||
|
}),
|
||||||
selected_profile: init.selected_profile.clone(),
|
selected_profile: init.selected_profile.clone(),
|
||||||
profile_not_editable_dialog,
|
profile_not_editable_dialog,
|
||||||
|
profile_delete_confirm_dialog,
|
||||||
tracker: 0,
|
tracker: 0,
|
||||||
};
|
};
|
||||||
let widgets = view_output!();
|
let widgets = view_output!();
|
||||||
|
|
|
@ -35,8 +35,17 @@ pub struct ProfileEditor {
|
||||||
pub enum ProfileEditorMsg {
|
pub enum ProfileEditorMsg {
|
||||||
Present(Profile),
|
Present(Profile),
|
||||||
EntryChanged(String, String),
|
EntryChanged(String, String),
|
||||||
|
TextChanged(String, String),
|
||||||
|
PathChanged(String, Option<String>),
|
||||||
|
SwitchChanged(String, bool),
|
||||||
|
ComboChanged(String, String),
|
||||||
AddEnvVar,
|
AddEnvVar,
|
||||||
SaveProfile, // ?
|
SaveProfile,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum ProfileEditorOutMsg {
|
||||||
|
SaveProfile(Profile),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ProfileEditorInit {}
|
pub struct ProfileEditorInit {}
|
||||||
|
@ -45,7 +54,7 @@ pub struct ProfileEditorInit {}
|
||||||
impl SimpleComponent for ProfileEditor {
|
impl SimpleComponent for ProfileEditor {
|
||||||
type Init = ProfileEditorInit;
|
type Init = ProfileEditorInit;
|
||||||
type Input = ProfileEditorMsg;
|
type Input = ProfileEditorMsg;
|
||||||
type Output = ();
|
type Output = ProfileEditorOutMsg;
|
||||||
|
|
||||||
view! {
|
view! {
|
||||||
#[name(win)]
|
#[name(win)]
|
||||||
|
@ -66,6 +75,21 @@ impl SimpleComponent for ProfileEditor {
|
||||||
add: model.env_rows.widget(),
|
add: model.env_rows.widget(),
|
||||||
add: model.switch_rows.widget(),
|
add: model.switch_rows.widget(),
|
||||||
add: model.path_rows.widget(),
|
add: model.path_rows.widget(),
|
||||||
|
add: save_grp = &adw::PreferencesGroup {
|
||||||
|
add: save_box = >k::Box {
|
||||||
|
set_orientation: gtk::Orientation::Vertical,
|
||||||
|
set_hexpand: true,
|
||||||
|
gtk::Button {
|
||||||
|
set_halign: gtk::Align::Center,
|
||||||
|
set_label: "Save",
|
||||||
|
add_css_class: "pill",
|
||||||
|
add_css_class: "suggested-action",
|
||||||
|
connect_clicked[sender] => move |_| {
|
||||||
|
sender.input(Self::Input::SaveProfile);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -135,20 +159,71 @@ impl SimpleComponent for ProfileEditor {
|
||||||
key: "mercury_path".into(),
|
key: "mercury_path".into(),
|
||||||
value: p.mercury_path,
|
value: p.mercury_path,
|
||||||
});
|
});
|
||||||
|
self.path_rows.guard().push_back(PathModelInit {
|
||||||
|
name: "Install Prefix".into(),
|
||||||
|
key: "prefix".into(),
|
||||||
|
value: Some(p.prefix),
|
||||||
|
});
|
||||||
|
|
||||||
self.set_profile(Some(prof.clone()));
|
self.set_profile(Some(prof.clone()));
|
||||||
|
|
||||||
self.win.as_ref().unwrap().present();
|
self.win.as_ref().unwrap().present();
|
||||||
}
|
}
|
||||||
Self::Input::SaveProfile => {}
|
Self::Input::SaveProfile => {
|
||||||
|
let prof = self.profile.as_ref().unwrap();
|
||||||
|
if prof.validate() {
|
||||||
|
sender.output(ProfileEditorOutMsg::SaveProfile(prof.clone()));
|
||||||
|
self.win.as_ref().unwrap().close();
|
||||||
|
} else {
|
||||||
|
self.win.as_ref().unwrap().add_toast(adw::Toast::builder().title("Profile failed validation").build());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// TODO: rename to reflect this is only for env
|
||||||
|
// + make entryfactory take a signal to send to parent
|
||||||
Self::Input::EntryChanged(name, value) => {
|
Self::Input::EntryChanged(name, value) => {
|
||||||
println!("{}: {}", name, value);
|
|
||||||
self.profile
|
self.profile
|
||||||
.as_mut()
|
.as_mut()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.environment
|
.environment
|
||||||
.insert(name, value);
|
.insert(name, value);
|
||||||
}
|
}
|
||||||
|
Self::Input::PathChanged(key, value) => {
|
||||||
|
let prof = self.profile.as_mut().unwrap();
|
||||||
|
match key.as_str() {
|
||||||
|
"xrservice_path" => prof.xrservice_path = value.unwrap_or("".to_string()),
|
||||||
|
"opencomposite_path" => {
|
||||||
|
prof.opencomposite_path = value.unwrap_or("".to_string())
|
||||||
|
}
|
||||||
|
"libsurvive_path" => prof.libsurvive_path = value,
|
||||||
|
"basalt_path" => prof.basalt_path = value,
|
||||||
|
"mercury_path" => prof.mercury_path = value,
|
||||||
|
"prefix" => prof.prefix = value.unwrap_or("".to_string()),
|
||||||
|
_ => panic!("Unknown profile path key"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Self::Input::SwitchChanged(key, value) => {
|
||||||
|
let prof = self.profile.as_mut().unwrap();
|
||||||
|
match key.as_str() {
|
||||||
|
"libsurvive_enabled" => prof.libsurvive_enabled = value,
|
||||||
|
"basalt_enabled" => prof.basalt_enabled = value,
|
||||||
|
"mercury_enabled" => prof.mercury_enabled = value,
|
||||||
|
_ => panic!("Unknown profile switch key"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Self::Input::TextChanged(key, value) => {
|
||||||
|
let prof = self.profile.as_mut().unwrap();
|
||||||
|
match key.as_str() {
|
||||||
|
"name" => prof.name = value,
|
||||||
|
_ => panic!("Unknown profile text key"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Self::Input::ComboChanged(key, value) => {
|
||||||
|
let prof = self.profile.as_mut().unwrap();
|
||||||
|
match key.as_str() {
|
||||||
|
"xrservice_type" => prof.xrservice_type = XRServiceType::from_string(value),
|
||||||
|
_ => panic!("Unknown profile text key"),
|
||||||
|
}
|
||||||
|
}
|
||||||
Self::Input::AddEnvVar => {
|
Self::Input::AddEnvVar => {
|
||||||
println!("Add env var");
|
println!("Add env var");
|
||||||
}
|
}
|
||||||
|
@ -218,6 +293,30 @@ impl SimpleComponent for ProfileEditor {
|
||||||
tracker: 0,
|
tracker: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
{
|
||||||
|
let name_sender = sender.clone();
|
||||||
|
model.name_row.connect_changed(move |nr| {
|
||||||
|
name_sender.input(Self::Input::TextChanged(
|
||||||
|
"name".to_string(),
|
||||||
|
nr.text().to_string(),
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let type_sender = sender.clone();
|
||||||
|
model.type_row.connect_selected_item_notify(move |tr| {
|
||||||
|
type_sender.input(Self::Input::ComboChanged(
|
||||||
|
"xrservice_type".to_string(),
|
||||||
|
match tr.selected() {
|
||||||
|
0 => "monado".to_string(),
|
||||||
|
1 => "wivrn".to_string(),
|
||||||
|
_ => panic!("XRServiceType combo row cannot have more than 2 choices"),
|
||||||
|
},
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
let widgets = view_output!();
|
let widgets = view_output!();
|
||||||
model.win = Some(widgets.win.clone());
|
model.win = Some(widgets.win.clone());
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue