mirror of
https://github.com/PancakeTAS/lsfg-vk.git
synced 2025-10-30 07:01:10 +00:00
ui: configuration system and basic switching
This commit is contained in:
parent
3dba2a7a3d
commit
cbc536c23e
22 changed files with 709 additions and 272 deletions
67
ui/Cargo.lock
generated
67
ui/Cargo.lock
generated
|
|
@ -2,6 +2,12 @@
|
|||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.98"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.5.0"
|
||||
|
|
@ -570,6 +576,15 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "40734c41988f7306bb04f0ecf60ec0f3f1caa34290e4e8ea471dcd3346483b83"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.10"
|
||||
|
|
@ -602,7 +617,7 @@ dependencies = [
|
|||
"cfg-expr",
|
||||
"heck",
|
||||
"pkg-config",
|
||||
"toml",
|
||||
"toml 0.8.23",
|
||||
"version-compare",
|
||||
]
|
||||
|
||||
|
|
@ -619,11 +634,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"serde_spanned 0.6.9",
|
||||
"toml_datetime 0.6.11",
|
||||
"toml_edit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed0aee96c12fa71097902e0bb061a5e1ebd766a6636bb605ba401c45c1650eac"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
"serde_spanned 1.0.0",
|
||||
"toml_datetime 0.7.0",
|
||||
"toml_parser",
|
||||
"toml_writer",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.6.11"
|
||||
|
|
@ -633,6 +663,15 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bade1c3e902f58d73d3f294cd7f20391c1cb2fbcb643b73566bc773971df91e3"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.22.27"
|
||||
|
|
@ -641,18 +680,36 @@ checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
|
|||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"serde_spanned 0.6.9",
|
||||
"toml_datetime 0.6.11",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_parser"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97200572db069e74c512a14117b296ba0a80a30123fbbb5aa1f4a348f639ca30"
|
||||
dependencies = [
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_writer"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fcc842091f2def52017664b53082ecbbeb5c7731092bad69d2c63050401dfd64"
|
||||
|
||||
[[package]]
|
||||
name = "ui"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"glib-build-tools",
|
||||
"gtk4",
|
||||
"libadwaita",
|
||||
"serde",
|
||||
"toml 0.9.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
|||
|
|
@ -5,7 +5,10 @@ edition = "2024"
|
|||
|
||||
[dependencies]
|
||||
gtk = { version = "0.10.0", package = "gtk4" }
|
||||
adw = { version = "0.8.0", package = "libadwaita" }
|
||||
adw = { version = "0.8.0", package = "libadwaita", features = ["v1_4"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
toml = "0.9.2"
|
||||
anyhow = "1.0"
|
||||
|
||||
[build-dependencies]
|
||||
glib-build-tools = "0.21.0"
|
||||
|
|
|
|||
89
ui/rsc/pane/main.ui
Normal file
89
ui/rsc/pane/main.ui
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<interface>
|
||||
<template class="LSPaneMain" parent="AdwNavigationPage">
|
||||
<child>
|
||||
<object class="AdwToolbarView">
|
||||
<!-- Header -->
|
||||
<child type="top">
|
||||
<object class="AdwHeaderBar">
|
||||
<property name="title-widget">
|
||||
<object class="AdwWindowTitle">
|
||||
<property name="title">lsfg-vk Configuration Menu</property>
|
||||
</object>
|
||||
</property>
|
||||
</object>
|
||||
</child>
|
||||
<!-- Content -->
|
||||
<property name="content">
|
||||
<object class="GtkScrolledWindow">
|
||||
<property name="hscrollbar-policy">never</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="main_box">
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="margin-start">48</property>
|
||||
<property name="margin-end">48</property>
|
||||
<property name="margin-top">32</property>
|
||||
<property name="margin-bottom">32</property>
|
||||
<property name="spacing">32</property>
|
||||
<child>
|
||||
<!-- Frame Generation -->
|
||||
<object class="AdwPreferencesGroup">
|
||||
<property name="title">Frame Generation</property>
|
||||
<!-- Frame Generation: Multiplier -->
|
||||
<child>
|
||||
<object class="LSPrefNumber" id="pref_multiplier">
|
||||
<property name="opt-name">Multiplier</property>
|
||||
</object>
|
||||
</child>
|
||||
<!-- Frame Generation: Flow Scale -->
|
||||
<child>
|
||||
<object class="LSPrefSlider" id="pref_flow_scale">
|
||||
<property name="opt-name">Flow Scale</property>
|
||||
</object>
|
||||
</child>
|
||||
<!-- Frame Generation: Performance Mode -->
|
||||
<child>
|
||||
<object class="LSPrefSwitch" id="pref_performance_mode">
|
||||
<property name="opt-name">Performance Mode</property>
|
||||
<property name="default-state">false</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<!-- Misc -->
|
||||
<object class="AdwPreferencesGroup">
|
||||
<property name="title">Misc</property>
|
||||
<!-- Misc: HDR Mode -->
|
||||
<child>
|
||||
<object class="LSPrefSwitch" id="pref_hdr_mode">
|
||||
<property name="opt-name">HDR Mode</property>
|
||||
<property name="default-state">false</property>
|
||||
</object>
|
||||
</child>
|
||||
<!-- Misc: Experimental Present Mode -->
|
||||
<child>
|
||||
<object class="LSPrefDropdown" id="pref_experimental_present_mode">
|
||||
<property name="opt-name">Experimental Present Mode</property>
|
||||
<property name="default-selection">0</property>
|
||||
<property name="options">
|
||||
<object class="GtkStringList">
|
||||
<items>
|
||||
<item>VSync/FIFO</item>
|
||||
<item>Mailbox</item>
|
||||
<item>Immediate</item>
|
||||
</items>
|
||||
</object>
|
||||
</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
</object>
|
||||
</child>
|
||||
</template>
|
||||
</interface>
|
||||
39
ui/rsc/pane/sidebar.ui
Normal file
39
ui/rsc/pane/sidebar.ui
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<interface>
|
||||
<template class="LSPaneSidebar" parent="AdwNavigationPage">
|
||||
<child>
|
||||
<object class="AdwToolbarView">
|
||||
<!-- Header -->
|
||||
<child type="top">
|
||||
<object class="AdwHeaderBar">
|
||||
<property name="title-widget">
|
||||
<object class="AdwWindowTitle">
|
||||
<property name="title">Profiles</property>
|
||||
</object>
|
||||
</property>
|
||||
</object>
|
||||
</child>
|
||||
|
||||
<!-- Content -->
|
||||
<!-- TODO: content -->
|
||||
|
||||
<!-- Footer -->
|
||||
<child type="bottom">
|
||||
<object class="GtkBox">
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="margin-start">12</property>
|
||||
<property name="margin-end">12</property>
|
||||
<property name="margin-top">12</property>
|
||||
<property name="margin-bottom">12</property>
|
||||
<child>
|
||||
<object class="GtkButton">
|
||||
<property name="label">Create New Profile</property>
|
||||
<property name="css-classes">suggested-action</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</template>
|
||||
</interface>
|
||||
|
|
@ -2,6 +2,8 @@
|
|||
<gresources>
|
||||
<gresource prefix="/gay/pancake/lsfg-vk/">
|
||||
<file compressed="true" preprocess="xml-stripblanks">window.ui</file>
|
||||
<file compressed="true" preprocess="xml-stripblanks">pane/main.ui</file>
|
||||
<file compressed="true" preprocess="xml-stripblanks">pane/sidebar.ui</file>
|
||||
<file compressed="true" preprocess="xml-stripblanks">pref/dropdown.ui</file>
|
||||
<file compressed="true" preprocess="xml-stripblanks">pref/number.ui</file>
|
||||
<file compressed="true" preprocess="xml-stripblanks">pref/entry.ui</file>
|
||||
|
|
|
|||
125
ui/rsc/window.ui
125
ui/rsc/window.ui
|
|
@ -11,132 +11,11 @@
|
|||
<property name="max-sidebar-width">300</property>
|
||||
<!-- Split View: Left Sidebar -->
|
||||
<property name="sidebar">
|
||||
<object class="AdwNavigationPage">
|
||||
<child>
|
||||
<object class="AdwToolbarView">
|
||||
<!-- Left Sidebar: Header -->
|
||||
<child type="top">
|
||||
<object class="AdwHeaderBar">
|
||||
<property name="title-widget">
|
||||
<object class="AdwWindowTitle">
|
||||
<property name="title">Profiles</property>
|
||||
</object>
|
||||
</property>
|
||||
</object>
|
||||
</child>
|
||||
|
||||
<!-- Left Sidebar: Content -->
|
||||
<!-- TODO: content -->
|
||||
|
||||
<!-- Left Sidebar: Footer -->
|
||||
<child type="bottom">
|
||||
<object class="GtkBox">
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="margin-start">12</property>
|
||||
<property name="margin-end">12</property>
|
||||
<property name="margin-top">12</property>
|
||||
<property name="margin-bottom">12</property>
|
||||
<child>
|
||||
<object class="GtkButton">
|
||||
<property name="label">Create New Profile</property>
|
||||
<property name="css-classes">suggested-action</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<object class="LSPaneSidebar" id="sidebar"/>
|
||||
</property>
|
||||
<!-- Split View: Main Content -->
|
||||
<property name="content">
|
||||
<object class="AdwNavigationPage">
|
||||
<child>
|
||||
<object class="AdwToolbarView">
|
||||
<!-- Main Content: Header -->
|
||||
<child type="top">
|
||||
<object class="AdwHeaderBar">
|
||||
<property name="title-widget">
|
||||
<object class="AdwWindowTitle">
|
||||
<property name="title">lsfg-vk Configuration Menu</property>
|
||||
</object>
|
||||
</property>
|
||||
</object>
|
||||
</child>
|
||||
|
||||
<!-- Main Content: Content -->
|
||||
<property name="content">
|
||||
<object class="GtkScrolledWindow">
|
||||
<property name="hscrollbar-policy">never</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="main_box">
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="margin-start">48</property>
|
||||
<property name="margin-end">48</property>
|
||||
<property name="margin-top">32</property>
|
||||
<property name="margin-bottom">32</property>
|
||||
<property name="spacing">32</property>
|
||||
<child>
|
||||
<!-- Content: Frame Generation -->
|
||||
<object class="AdwPreferencesGroup">
|
||||
<property name="title">Frame Generation</property>
|
||||
<!-- Frame Generation: Multiplier -->
|
||||
<child>
|
||||
<object class="LSPrefNumber" id="pref_multiplier">
|
||||
<property name="opt-name">Multiplier</property>
|
||||
</object>
|
||||
</child>
|
||||
<!-- Frame Generation: Flow Scale -->
|
||||
<child>
|
||||
<object class="LSPrefSlider" id="pref_flow_scale">
|
||||
<property name="opt-name">Flow Scale</property>
|
||||
</object>
|
||||
</child>
|
||||
<!-- Frame Generation: Performance Mode -->
|
||||
<child>
|
||||
<object class="LSPrefSwitch" id="pref_performance_mode">
|
||||
<property name="opt-name">Performance Mode</property>
|
||||
<property name="default-state">false</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<!-- Content: Misc -->
|
||||
<object class="AdwPreferencesGroup">
|
||||
<property name="title">Frame Generation</property>
|
||||
<!-- Misc: HDR Mode -->
|
||||
<child>
|
||||
<object class="LSPrefSwitch" id="pref_hdr_mode">
|
||||
<property name="opt-name">HDR Mode</property>
|
||||
<property name="default-state">false</property>
|
||||
</object>
|
||||
</child>
|
||||
<!-- Misc: Experimental Present Mode -->
|
||||
<child>
|
||||
<object class="LSPrefDropdown" id="pref_experimental_present_mode">
|
||||
<property name="opt-name">Experimental Present Mode</property>
|
||||
<property name="default-selection">0</property>
|
||||
<property name="options">
|
||||
<object class="GtkStringList">
|
||||
<items>
|
||||
<item>vsync/fifo</item>
|
||||
<item>mailbox</item>
|
||||
<item>immediate</item>
|
||||
</items>
|
||||
</object>
|
||||
</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<object class="LSPaneMain" id="main"/>
|
||||
</property>
|
||||
</object>
|
||||
</property>
|
||||
|
|
|
|||
129
ui/src/config.rs
Normal file
129
ui/src/config.rs
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
use std::sync::{Arc, OnceLock, RwLock};
|
||||
|
||||
use anyhow::Context;
|
||||
|
||||
pub mod structs;
|
||||
pub use structs::*;
|
||||
|
||||
/// Find the configuration file path based on environment variables
|
||||
fn find_config_file() -> String {
|
||||
if let Some(path) = std::env::var("LSFG_CONFIG").ok() {
|
||||
return path;
|
||||
}
|
||||
|
||||
if let Some(xdg) = std::env::var("XDG_CONFIG_HOME").ok() {
|
||||
return format!("{}/lsfg-vk/conf.toml", xdg);
|
||||
}
|
||||
|
||||
if let Some(home) = std::env::var("HOME").ok() {
|
||||
return format!("{}/.config/lsfg-vk/conf.toml", home);
|
||||
}
|
||||
|
||||
"conf.toml".to_string()
|
||||
}
|
||||
|
||||
static CONFIG: OnceLock<Arc<RwLock<TomlConfig>>> = OnceLock::new();
|
||||
static CONFIG_WRITER: OnceLock<std::sync::mpsc::Sender<()>> = OnceLock::new();
|
||||
|
||||
///
|
||||
/// Load the configuration from the file and create a writer.
|
||||
///
|
||||
pub fn load_config() -> Result<(), anyhow::Error> {
|
||||
// load the configuration file
|
||||
let path = find_config_file();
|
||||
let data = std::fs::read(path)
|
||||
.context("Failed to read conf.toml")?;
|
||||
let config: TomlConfig = toml::from_slice(&data)
|
||||
.context("Failed to parse conf.toml")?;
|
||||
CONFIG.set(Arc::new(RwLock::new(config)))
|
||||
.ok().context("Failed to set configuration state")?;
|
||||
|
||||
// create the configuration writer thread
|
||||
let (tx, rx) = std::sync::mpsc::channel::<()>();
|
||||
CONFIG_WRITER.set(tx)
|
||||
.ok().context("Failed to set configuration writer")?;
|
||||
|
||||
std::thread::spawn(move || {
|
||||
let config = CONFIG.get().unwrap();
|
||||
loop {
|
||||
// wait for a signal to write the configuration
|
||||
if let Err(_) = rx.recv() {
|
||||
break;
|
||||
}
|
||||
|
||||
// wait a bit to avoid excessive writes
|
||||
std::thread::sleep(std::time::Duration::from_millis(200));
|
||||
|
||||
// empty the channel
|
||||
while rx.try_recv().is_ok() {}
|
||||
|
||||
// write the configuration
|
||||
eprintln!("Saving configuration...");
|
||||
if let Ok(config) = config.try_read() {
|
||||
if let Err(e) = save_config(&config) {
|
||||
eprintln!("Failed to save configuration: {}", e);
|
||||
} else {
|
||||
eprintln!("Configuration saved successfully");
|
||||
}
|
||||
} else {
|
||||
eprintln!("Failed to read configuration state");
|
||||
}
|
||||
}
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
///
|
||||
/// Get a snapshot of the current configuration
|
||||
///
|
||||
pub fn get_config() -> Result<TomlConfig, anyhow::Error> {
|
||||
let conf = CONFIG.get()
|
||||
.expect("Configuration not loaded")
|
||||
.try_read()
|
||||
.map(|config| config.clone());
|
||||
if let Ok(config) = conf {
|
||||
return Ok(config)
|
||||
}
|
||||
|
||||
anyhow::bail!("Failed to read configuration state")
|
||||
}
|
||||
|
||||
///
|
||||
/// Safely edit the configuration.
|
||||
///
|
||||
pub fn edit_config<F>(f: F) -> Result<(), anyhow::Error>
|
||||
where
|
||||
F: FnOnce(&mut TomlConfig)
|
||||
{
|
||||
let mut config = CONFIG.get()
|
||||
.expect("Configuration not loaded")
|
||||
.write()
|
||||
.map_err(|_| anyhow::anyhow!("Failed to acquire write lock on configuration"))?;
|
||||
|
||||
f(&mut config);
|
||||
|
||||
CONFIG_WRITER.get().unwrap().send(())
|
||||
.context("Failed to send configuration update signal")
|
||||
}
|
||||
|
||||
///
|
||||
/// Save the configuration to the file
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// `config` - The configuration to save
|
||||
///
|
||||
pub fn save_config(config: &TomlConfig) -> Result<(), anyhow::Error> {
|
||||
let path = find_config_file();
|
||||
|
||||
let parent = std::path::Path::new(&path).parent()
|
||||
.context("Failed to get parent directory of config path")?;
|
||||
std::fs::create_dir_all(parent)
|
||||
.context("Failed to create config directory")?;
|
||||
|
||||
let data = toml::to_string(config)
|
||||
.context("Failed to serialize conf.toml")?;
|
||||
std::fs::write(path, data)
|
||||
.context("Failed to write conf.toml")?;
|
||||
Ok(())
|
||||
}
|
||||
68
ui/src/config/structs.rs
Normal file
68
ui/src/config/structs.rs
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
// multiplier
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct Multiplier(i64);
|
||||
impl Default for Multiplier {
|
||||
fn default() -> Self { Multiplier(2) }
|
||||
}
|
||||
impl From<i64> for Multiplier {
|
||||
fn from(value: i64) -> Self { Multiplier(value) }
|
||||
}
|
||||
|
||||
// flow scale
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct FlowScale(f64);
|
||||
impl Default for FlowScale {
|
||||
fn default() -> Self { FlowScale(1.0) }
|
||||
}
|
||||
impl From<f64> for FlowScale {
|
||||
fn from(value: f64) -> Self { FlowScale(value) }
|
||||
}
|
||||
|
||||
// present mode
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub enum PresentMode {
|
||||
#[serde(rename = "fifo", alias = "vsync")]
|
||||
Vsync,
|
||||
#[serde(rename = "immediate")]
|
||||
Immediate,
|
||||
#[serde(rename = "mailbox")]
|
||||
Mailbox,
|
||||
}
|
||||
impl Default for PresentMode {
|
||||
fn default() -> Self { PresentMode::Vsync }
|
||||
}
|
||||
|
||||
/// Global configuration for the application
|
||||
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
|
||||
pub struct TomlGlobal {
|
||||
pub dll: Option<String>
|
||||
}
|
||||
|
||||
/// Game-specific configuration
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct TomlGame {
|
||||
pub exe: String,
|
||||
|
||||
#[serde(default)]
|
||||
pub multiplier: Multiplier,
|
||||
#[serde(default)]
|
||||
pub flow_scale: FlowScale,
|
||||
#[serde(default)]
|
||||
pub performance_mode: bool,
|
||||
#[serde(default)]
|
||||
pub hdr_mode: bool,
|
||||
#[serde(default)]
|
||||
pub experimental_present_mode: PresentMode
|
||||
}
|
||||
|
||||
/// Main configuration structure
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct TomlConfig {
|
||||
pub version: i64,
|
||||
#[serde(default)]
|
||||
pub global: TomlGlobal,
|
||||
#[serde(default)]
|
||||
pub game: Vec<TomlGame>
|
||||
}
|
||||
131
ui/src/main.rs
131
ui/src/main.rs
|
|
@ -1,9 +1,130 @@
|
|||
mod ui;
|
||||
use std::sync::{Arc, OnceLock, RwLock};
|
||||
|
||||
use adw::{self, subclass::prelude::ObjectSubclassIsExt};
|
||||
use gtk::{gio, prelude::*};
|
||||
|
||||
use crate::config::*;
|
||||
|
||||
mod wrapper;
|
||||
mod config;
|
||||
|
||||
const APP_ID: &str = "gay.pancake.lsfg-vk.ConfigurationUi";
|
||||
|
||||
fn main() {
|
||||
let app = ui::App::new(APP_ID)
|
||||
.expect("Failed to create application");
|
||||
app.run()
|
||||
#[derive(Debug)]
|
||||
struct State {
|
||||
// ui state
|
||||
selected_game: Option<usize>
|
||||
}
|
||||
|
||||
static STATE: OnceLock<Arc<RwLock<State>>> = OnceLock::new();
|
||||
|
||||
fn main() {
|
||||
gio::resources_register_include!("ui.gresource")
|
||||
.expect("Failed to register resources");
|
||||
config::load_config()
|
||||
.expect("Failed to load configuration");
|
||||
|
||||
// prepare the application state
|
||||
STATE.set(Arc::new(RwLock::new(State {
|
||||
selected_game: Some(0)
|
||||
}))).expect("Failed to set application state");
|
||||
|
||||
// start the application
|
||||
let app = adw::Application::builder()
|
||||
.application_id(APP_ID)
|
||||
.build();
|
||||
app.connect_activate(build_ui);
|
||||
app.run();
|
||||
}
|
||||
|
||||
fn build_ui(app: &adw::Application) {
|
||||
// create the main window
|
||||
let window = wrapper::Window::new(app);
|
||||
window.set_application(Some(app));
|
||||
|
||||
// register main pane signals
|
||||
let main = window.imp().main.imp();
|
||||
|
||||
let pref_multiplier = main.pref_multiplier.imp();
|
||||
pref_multiplier.number.connect_value_changed(|dropdown| {
|
||||
if let Ok(state) = STATE.get().unwrap().try_read() {
|
||||
if state.selected_game.is_none() {
|
||||
return;
|
||||
}
|
||||
|
||||
let multiplier = (dropdown.value() as i64).into();
|
||||
let _ = config::edit_config(|config| {
|
||||
config.game[state.selected_game.unwrap()]
|
||||
.multiplier = multiplier;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
let pref_flow_scale = main.pref_flow_scale.imp();
|
||||
pref_flow_scale.slider.connect_value_changed(|slider| {
|
||||
if let Ok(state) = STATE.get().unwrap().try_read() {
|
||||
if state.selected_game.is_none() {
|
||||
return;
|
||||
}
|
||||
|
||||
let flow_scale = (slider.value() / 100.0).into();
|
||||
let _ = config::edit_config(|config| {
|
||||
config.game[state.selected_game.unwrap()]
|
||||
.flow_scale = flow_scale;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
let pref_performance_mode = main.pref_performance_mode.imp();
|
||||
pref_performance_mode.switch.connect_state_notify(|switch| {
|
||||
if let Ok(state) = STATE.get().unwrap().try_read() {
|
||||
if state.selected_game.is_none() {
|
||||
return;
|
||||
}
|
||||
|
||||
let performance_mode = switch.state();
|
||||
let _ = config::edit_config(|config| {
|
||||
config.game[state.selected_game.unwrap()]
|
||||
.performance_mode = performance_mode;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
let pref_hdr_mode = main.pref_hdr_mode.imp();
|
||||
pref_hdr_mode.switch.connect_state_notify(|switch| {
|
||||
if let Ok(state) = STATE.get().unwrap().try_read() {
|
||||
if state.selected_game.is_none() {
|
||||
return;
|
||||
}
|
||||
|
||||
let hdr_mode = switch.state();
|
||||
let _ = config::edit_config(|config| {
|
||||
config.game[state.selected_game.unwrap()]
|
||||
.hdr_mode = hdr_mode;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
let pref_experimental_present_mode = main.pref_experimental_present_mode.imp();
|
||||
pref_experimental_present_mode.dropdown.connect_selected_notify(|dropdown| {
|
||||
if let Ok(state) = STATE.get().unwrap().try_read() {
|
||||
if state.selected_game.is_none() {
|
||||
return;
|
||||
}
|
||||
|
||||
let selected = match dropdown.selected() {
|
||||
0 => PresentMode::Vsync,
|
||||
1 => PresentMode::Mailbox,
|
||||
2 => PresentMode::Immediate,
|
||||
_ => PresentMode::Vsync,
|
||||
};
|
||||
config::edit_config(|config| {
|
||||
config.game[state.selected_game.unwrap()]
|
||||
.experimental_present_mode = selected;
|
||||
}).unwrap();
|
||||
}
|
||||
});
|
||||
|
||||
// present the window
|
||||
window.present();
|
||||
}
|
||||
|
|
|
|||
36
ui/src/ui.rs
36
ui/src/ui.rs
|
|
@ -1,36 +0,0 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use adw;
|
||||
use gtk::{gio, glib, prelude::*};
|
||||
|
||||
pub mod ui;
|
||||
pub mod pref;
|
||||
|
||||
pub struct App {
|
||||
app: Arc<adw::Application>,
|
||||
}
|
||||
|
||||
impl App {
|
||||
pub fn new(appid: &str) -> Result<Self, glib::Error> {
|
||||
gio::resources_register_include!("ui.gresource")?;
|
||||
|
||||
let app = adw::Application::builder()
|
||||
.application_id(appid)
|
||||
.build();
|
||||
app.connect_activate(Self::build_ui);
|
||||
|
||||
Ok(App {
|
||||
app: Arc::new(app)
|
||||
})
|
||||
}
|
||||
|
||||
fn build_ui(app: &adw::Application) {
|
||||
let window = ui::Window::new(app);
|
||||
window.set_application(Some(app));
|
||||
window.present();
|
||||
}
|
||||
|
||||
pub fn run(self){
|
||||
self.app.run();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
use gtk::glib;
|
||||
use gtk;
|
||||
use adw;
|
||||
use gtk::glib::types::StaticTypeExt;
|
||||
|
||||
pub mod window;
|
||||
|
||||
glib::wrapper! {
|
||||
pub struct Window(ObjectSubclass<window::Window>)
|
||||
@extends
|
||||
adw::ApplicationWindow, adw::Window,
|
||||
gtk::ApplicationWindow, gtk::Window, gtk::Widget,
|
||||
@implements
|
||||
gtk::gio::ActionGroup, gtk::gio::ActionMap,
|
||||
gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget,
|
||||
gtk::Native, gtk::Root, gtk::ShortcutManager;
|
||||
}
|
||||
|
||||
impl Window {
|
||||
pub fn new(app: &adw::Application) -> Self {
|
||||
super::pref::PrefDropdown::ensure_type();
|
||||
super::pref::PrefSwitch::ensure_type();
|
||||
super::pref::PrefEntry::ensure_type();
|
||||
|
||||
glib::Object::builder()
|
||||
.property("application", app)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,73 +0,0 @@
|
|||
use gtk::{prelude::RangeExt, subclass::prelude::*};
|
||||
use adw::subclass::prelude::*;
|
||||
use crate::ui::pref::*;
|
||||
use gtk::{glib, CompositeTemplate};
|
||||
|
||||
#[derive(CompositeTemplate, Default)]
|
||||
#[template(resource = "/gay/pancake/lsfg-vk/window.ui")]
|
||||
pub struct Window {
|
||||
// main config elements
|
||||
#[template_child]
|
||||
pref_multiplier: TemplateChild<PrefNumber>,
|
||||
#[template_child]
|
||||
pref_flow_scale: TemplateChild<PrefSlider>,
|
||||
#[template_child]
|
||||
pref_performance_mode: TemplateChild<PrefSwitch>,
|
||||
#[template_child]
|
||||
pref_hdr_mode: TemplateChild<PrefSwitch>,
|
||||
#[template_child]
|
||||
pref_experimental_present_mode: TemplateChild<PrefDropdown>,
|
||||
}
|
||||
|
||||
#[glib::object_subclass]
|
||||
impl ObjectSubclass for Window {
|
||||
const NAME: &'static str = "LSApplicationWindow";
|
||||
type Type = super::Window;
|
||||
type ParentType = adw::ApplicationWindow;
|
||||
|
||||
fn class_init(klass: &mut Self::Class) {
|
||||
klass.bind_template();
|
||||
}
|
||||
|
||||
fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
|
||||
obj.init_template();
|
||||
}
|
||||
}
|
||||
|
||||
impl ObjectImpl for Window {
|
||||
fn constructed(&self) {
|
||||
self.parent_constructed();
|
||||
|
||||
self.pref_multiplier.get()
|
||||
.imp().number.get().connect_value_changed(|dropdown| {
|
||||
let selected = dropdown.value();
|
||||
println!("Multiplier changed: {}", selected);
|
||||
});
|
||||
self.pref_flow_scale.get()
|
||||
.imp().slider.get().connect_value_changed(|slider| {
|
||||
let value = slider.value();
|
||||
println!("Flow scale changed: {}", value);
|
||||
});
|
||||
self.pref_performance_mode.get()
|
||||
.imp().switch.get().connect_state_notify(|switch| {
|
||||
let state = switch.state();
|
||||
println!("Performance mode changed: {}", state);
|
||||
});
|
||||
self.pref_hdr_mode.get()
|
||||
.imp().switch.get().connect_state_notify(|switch| {
|
||||
let state = switch.state();
|
||||
println!("HDR mode changed: {}", state);
|
||||
});
|
||||
self.pref_experimental_present_mode.get()
|
||||
.imp().dropdown.get().connect_selected_notify(|dropdown| {
|
||||
let selected = dropdown.selected();
|
||||
println!("Experimental present mode changed: {}", selected);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl WidgetImpl for Window {}
|
||||
impl WindowImpl for Window {}
|
||||
impl ApplicationWindowImpl for Window {}
|
||||
impl AdwWindowImpl for Window {}
|
||||
impl AdwApplicationWindowImpl for Window {}
|
||||
77
ui/src/wrapper.rs
Normal file
77
ui/src/wrapper.rs
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
use gtk::glib;
|
||||
use gtk;
|
||||
use adw;
|
||||
use gtk::glib::types::StaticTypeExt;
|
||||
|
||||
pub mod pane;
|
||||
pub mod pref;
|
||||
|
||||
pub mod imp {
|
||||
use gtk::subclass::prelude::*;
|
||||
use adw::subclass::prelude::*;
|
||||
use crate::wrapper::pane::*;
|
||||
use gtk::{glib, CompositeTemplate};
|
||||
|
||||
#[derive(CompositeTemplate, Default)]
|
||||
#[template(resource = "/gay/pancake/lsfg-vk/window.ui")]
|
||||
pub struct Window {
|
||||
#[template_child]
|
||||
pub main: TemplateChild<PaneMain>,
|
||||
#[template_child]
|
||||
pub sidebar: TemplateChild<PaneSidebar>,
|
||||
}
|
||||
|
||||
#[glib::object_subclass]
|
||||
impl ObjectSubclass for Window {
|
||||
const NAME: &'static str = "LSApplicationWindow";
|
||||
type Type = super::Window;
|
||||
type ParentType = adw::ApplicationWindow;
|
||||
|
||||
fn class_init(klass: &mut Self::Class) {
|
||||
klass.bind_template();
|
||||
}
|
||||
|
||||
fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
|
||||
obj.init_template();
|
||||
}
|
||||
}
|
||||
|
||||
impl ObjectImpl for Window {
|
||||
fn constructed(&self) {
|
||||
self.parent_constructed();
|
||||
}
|
||||
}
|
||||
|
||||
impl WidgetImpl for Window {}
|
||||
impl WindowImpl for Window {}
|
||||
impl ApplicationWindowImpl for Window {}
|
||||
impl AdwWindowImpl for Window {}
|
||||
impl AdwApplicationWindowImpl for Window {}
|
||||
}
|
||||
|
||||
glib::wrapper! {
|
||||
pub struct Window(ObjectSubclass<imp::Window>)
|
||||
@extends
|
||||
adw::ApplicationWindow, adw::Window,
|
||||
gtk::ApplicationWindow, gtk::Window, gtk::Widget,
|
||||
@implements
|
||||
gtk::gio::ActionGroup, gtk::gio::ActionMap,
|
||||
gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget,
|
||||
gtk::Native, gtk::Root, gtk::ShortcutManager;
|
||||
}
|
||||
|
||||
impl Window {
|
||||
pub fn new(app: &adw::Application) -> Self {
|
||||
pref::PrefDropdown::ensure_type();
|
||||
pref::PrefEntry::ensure_type();
|
||||
pref::PrefNumber::ensure_type();
|
||||
pref::PrefSlider::ensure_type();
|
||||
pref::PrefSwitch::ensure_type();
|
||||
pane::PaneMain::ensure_type();
|
||||
pane::PaneSidebar::ensure_type();
|
||||
|
||||
glib::Object::builder()
|
||||
.property("application", app)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
35
ui/src/wrapper/pane.rs
Normal file
35
ui/src/wrapper/pane.rs
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
use gtk::glib;
|
||||
use gtk;
|
||||
use adw;
|
||||
|
||||
pub mod main;
|
||||
pub mod sidebar;
|
||||
|
||||
glib::wrapper! {
|
||||
pub struct PaneMain(ObjectSubclass<main::PaneMain>)
|
||||
@extends
|
||||
adw::NavigationPage, gtk::Widget,
|
||||
@implements
|
||||
gtk::Accessible, gtk::Actionable, gtk::Buildable, gtk::ConstraintTarget;
|
||||
}
|
||||
|
||||
glib::wrapper! {
|
||||
pub struct PaneSidebar(ObjectSubclass<sidebar::PaneSidebar>)
|
||||
@extends
|
||||
adw::NavigationPage, gtk::Widget,
|
||||
@implements
|
||||
gtk::Accessible, gtk::Actionable, gtk::Buildable, gtk::ConstraintTarget;
|
||||
}
|
||||
|
||||
|
||||
impl PaneMain {
|
||||
pub fn new() -> Self {
|
||||
glib::Object::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl PaneSidebar {
|
||||
pub fn new() -> Self {
|
||||
glib::Object::new()
|
||||
}
|
||||
}
|
||||
43
ui/src/wrapper/pane/main.rs
Normal file
43
ui/src/wrapper/pane/main.rs
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
use gtk::glib;
|
||||
use gtk::subclass::prelude::*;
|
||||
use adw::subclass::prelude::*;
|
||||
use crate::wrapper::pref::*;
|
||||
|
||||
#[derive(gtk::CompositeTemplate, Default)]
|
||||
#[template(resource = "/gay/pancake/lsfg-vk/pane/main.ui")]
|
||||
pub struct PaneMain {
|
||||
#[template_child]
|
||||
pub pref_multiplier: TemplateChild<PrefNumber>,
|
||||
#[template_child]
|
||||
pub pref_flow_scale: TemplateChild<PrefSlider>,
|
||||
#[template_child]
|
||||
pub pref_performance_mode: TemplateChild<PrefSwitch>,
|
||||
#[template_child]
|
||||
pub pref_hdr_mode: TemplateChild<PrefSwitch>,
|
||||
#[template_child]
|
||||
pub pref_experimental_present_mode: TemplateChild<PrefDropdown>
|
||||
}
|
||||
|
||||
#[glib::object_subclass]
|
||||
impl ObjectSubclass for PaneMain {
|
||||
const NAME: &'static str = "LSPaneMain";
|
||||
type Type = super::PaneMain;
|
||||
type ParentType = adw::NavigationPage;
|
||||
|
||||
fn class_init(klass: &mut Self::Class) {
|
||||
klass.bind_template();
|
||||
}
|
||||
|
||||
fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
|
||||
obj.init_template();
|
||||
}
|
||||
}
|
||||
|
||||
impl ObjectImpl for PaneMain {
|
||||
fn constructed(&self) {
|
||||
self.parent_constructed();
|
||||
}
|
||||
}
|
||||
|
||||
impl WidgetImpl for PaneMain {}
|
||||
impl NavigationPageImpl for PaneMain {}
|
||||
33
ui/src/wrapper/pane/sidebar.rs
Normal file
33
ui/src/wrapper/pane/sidebar.rs
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
use gtk::glib;
|
||||
use gtk::subclass::prelude::*;
|
||||
use adw::subclass::prelude::*;
|
||||
|
||||
#[derive(gtk::CompositeTemplate, Default)]
|
||||
#[template(resource = "/gay/pancake/lsfg-vk/pane/sidebar.ui")]
|
||||
pub struct PaneSidebar {
|
||||
|
||||
}
|
||||
|
||||
#[glib::object_subclass]
|
||||
impl ObjectSubclass for PaneSidebar {
|
||||
const NAME: &'static str = "LSPaneSidebar";
|
||||
type Type = super::PaneSidebar;
|
||||
type ParentType = adw::NavigationPage;
|
||||
|
||||
fn class_init(klass: &mut Self::Class) {
|
||||
klass.bind_template();
|
||||
}
|
||||
|
||||
fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
|
||||
obj.init_template();
|
||||
}
|
||||
}
|
||||
|
||||
impl ObjectImpl for PaneSidebar {
|
||||
fn constructed(&self) {
|
||||
self.parent_constructed();
|
||||
}
|
||||
}
|
||||
|
||||
impl WidgetImpl for PaneSidebar {}
|
||||
impl NavigationPageImpl for PaneSidebar {}
|
||||
Loading…
Add table
Reference in a new issue