mirror of
https://github.com/PancakeTAS/lsfg-vk.git
synced 2026-04-22 02:11:43 +00:00
ui: bugfixes and cleanup
This commit is contained in:
parent
f4f18a6e6d
commit
717b0d2cd6
9 changed files with 254 additions and 209 deletions
|
|
@ -31,19 +31,19 @@
|
|||
<property name="title">Frame Generation</property>
|
||||
<!-- Frame Generation: Multiplier -->
|
||||
<child>
|
||||
<object class="LSPrefNumber" id="pref_multiplier">
|
||||
<object class="LSPrefNumber" id="multiplier">
|
||||
<property name="opt-name">Multiplier</property>
|
||||
</object>
|
||||
</child>
|
||||
<!-- Frame Generation: Flow Scale -->
|
||||
<child>
|
||||
<object class="LSPrefSlider" id="pref_flow_scale">
|
||||
<object class="LSPrefSlider" id="flow_scale">
|
||||
<property name="opt-name">Flow Scale</property>
|
||||
</object>
|
||||
</child>
|
||||
<!-- Frame Generation: Performance Mode -->
|
||||
<child>
|
||||
<object class="LSPrefSwitch" id="pref_performance_mode">
|
||||
<object class="LSPrefSwitch" id="performance_mode">
|
||||
<property name="opt-name">Performance Mode</property>
|
||||
<property name="default-state">false</property>
|
||||
</object>
|
||||
|
|
@ -56,14 +56,14 @@
|
|||
<property name="title">Misc</property>
|
||||
<!-- Misc: HDR Mode -->
|
||||
<child>
|
||||
<object class="LSPrefSwitch" id="pref_hdr_mode">
|
||||
<object class="LSPrefSwitch" id="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">
|
||||
<object class="LSPrefDropdown" id="experimental_present_mode">
|
||||
<property name="opt-name">Experimental Present Mode</property>
|
||||
<property name="default-selection">0</property>
|
||||
<property name="options">
|
||||
|
|
|
|||
|
|
@ -33,13 +33,18 @@ pub fn load_config() -> Result<(), anyhow::Error> {
|
|||
let path = find_config_file();
|
||||
let data = std::fs::read(path)
|
||||
.context("Failed to read conf.toml")?;
|
||||
let config: TomlConfig = toml::from_slice(&data)
|
||||
let mut 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")?;
|
||||
|
||||
// remove duplicate entries
|
||||
config.game.sort_by_key(|e| e.exe.clone());
|
||||
config.game.dedup_by_key(|e| e.exe.clone());
|
||||
config.game.retain(|e| !e.exe.is_empty());
|
||||
|
||||
// create the configuration writer thread
|
||||
let (tx, rx) = std::sync::mpsc::channel::<()>();
|
||||
CONFIG.set(Arc::new(RwLock::new(config)))
|
||||
.ok().context("Failed to set configuration state")?;
|
||||
CONFIG_WRITER.set(tx)
|
||||
.ok().context("Failed to set configuration writer")?;
|
||||
|
||||
|
|
@ -61,8 +66,6 @@ pub fn load_config() -> Result<(), anyhow::Error> {
|
|||
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");
|
||||
|
|
|
|||
|
|
@ -39,6 +39,25 @@ pub enum PresentMode {
|
|||
impl Default for PresentMode {
|
||||
fn default() -> Self { PresentMode::Vsync }
|
||||
}
|
||||
impl From<i64> for PresentMode {
|
||||
fn from(value: i64) -> Self {
|
||||
match value {
|
||||
0 => PresentMode::Vsync,
|
||||
1 => PresentMode::Mailbox,
|
||||
2 => PresentMode::Immediate,
|
||||
_ => PresentMode::Vsync,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Into<u32> for PresentMode {
|
||||
fn into(self) -> u32 {
|
||||
match self {
|
||||
PresentMode::Vsync => 0,
|
||||
PresentMode::Mailbox => 1,
|
||||
PresentMode::Immediate => 2,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Global configuration for the application
|
||||
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
|
||||
|
|
|
|||
197
ui/src/main.rs
197
ui/src/main.rs
|
|
@ -1,10 +1,9 @@
|
|||
use std::sync::{Arc, OnceLock, RwLock};
|
||||
|
||||
use adw::{self, subclass::prelude::ObjectSubclassIsExt};
|
||||
use gtk::{gio, prelude::*};
|
||||
use adw;
|
||||
|
||||
use crate::config::*;
|
||||
|
||||
mod ui;
|
||||
mod wrapper;
|
||||
mod config;
|
||||
|
||||
|
|
@ -33,196 +32,6 @@ fn main() {
|
|||
let app = adw::Application::builder()
|
||||
.application_id(APP_ID)
|
||||
.build();
|
||||
app.connect_activate(build_ui);
|
||||
app.connect_activate(ui::build);
|
||||
app.run();
|
||||
}
|
||||
|
||||
fn helper_add_deletion_signal(
|
||||
entry: wrapper::entry::Entry,
|
||||
profiles: gtk::ListBox) {
|
||||
let entry_clone = entry.clone();
|
||||
entry.imp().delete.connect_clicked(move |btn| {
|
||||
let dialog = gtk::AlertDialog::builder()
|
||||
.message("Delete Profile")
|
||||
.detail("Are you sure you want to delete this profile?")
|
||||
.buttons(vec!["Cancel".to_string(), "Delete".to_string()])
|
||||
.cancel_button(0)
|
||||
.default_button(1)
|
||||
.modal(true)
|
||||
.build();
|
||||
let window = btn.root()
|
||||
.and_downcast::<gtk::Window>()
|
||||
.expect("Button root is not a Window");
|
||||
|
||||
let profiles = profiles.clone();
|
||||
let entry_clone = entry_clone.clone();
|
||||
dialog.choose(Some(&window), None::<&gio::Cancellable>, move |result| {
|
||||
match result {
|
||||
Ok(idx) if idx == 1 => {
|
||||
let _ = config::edit_config(|config| {
|
||||
config.game.retain(|g| g.exe != entry_clone.exe());
|
||||
});
|
||||
profiles.remove(&entry_clone);
|
||||
},
|
||||
_ => return,
|
||||
};
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn build_ui(app: &adw::Application) {
|
||||
// create the main window
|
||||
let window = wrapper::Window::new(app);
|
||||
window.set_application(Some(app));
|
||||
|
||||
// load profiles from configuration
|
||||
let sidebar = window.imp().sidebar.imp();
|
||||
|
||||
let config = config::get_config()
|
||||
.expect("Failed to get configuration");
|
||||
for game in config.game.iter() {
|
||||
let entry = wrapper::entry::Entry::new();
|
||||
entry.set_exe(game.exe.clone());
|
||||
|
||||
helper_add_deletion_signal(entry.clone(), sidebar.profiles.clone());
|
||||
sidebar.profiles.append(&entry);
|
||||
}
|
||||
|
||||
// register side pane signals
|
||||
let profiles = sidebar.profiles.clone();
|
||||
let main = window.imp().main.clone();
|
||||
sidebar.create.connect_clicked(move |_| {
|
||||
let mut conf_entry = config::TomlGame::default();
|
||||
conf_entry.exe = "new profile".to_string();
|
||||
let _ = config::edit_config(|config| {
|
||||
config.game.push(conf_entry.clone());
|
||||
});
|
||||
|
||||
let entry = wrapper::entry::Entry::new();
|
||||
entry.set_exe(conf_entry.exe);
|
||||
|
||||
helper_add_deletion_signal(entry.clone(), profiles.clone());
|
||||
profiles.append(&entry);
|
||||
entry.activate();
|
||||
});
|
||||
|
||||
let state = STATE.get().unwrap().clone();
|
||||
sidebar.profiles.connect_row_activated(move |_, entry| {
|
||||
// find config entry
|
||||
let index = entry.index() as usize;
|
||||
let config = config::get_config()
|
||||
.expect("Failed to get configuration");
|
||||
let conf = config.game[index].clone();
|
||||
|
||||
// update state
|
||||
{
|
||||
let mut state = state.write()
|
||||
.expect("Failed to acquire write lock on state");
|
||||
state.selected_game = Some(index);
|
||||
}
|
||||
|
||||
// update main pane
|
||||
let main = main.imp();
|
||||
let pref_multiplier = main.pref_multiplier.imp();
|
||||
pref_multiplier.number.set_value(conf.multiplier.into());
|
||||
let pref_flow_scale = main.pref_flow_scale.imp();
|
||||
pref_flow_scale.slider.set_value(Into::<f64>::into(conf.flow_scale) * 100.0);
|
||||
let pref_performance_mode = main.pref_performance_mode.imp();
|
||||
pref_performance_mode.switch.set_state(conf.performance_mode);
|
||||
let pref_hdr_mode = main.pref_hdr_mode.imp();
|
||||
pref_hdr_mode.switch.set_state(conf.hdr_mode);
|
||||
let pref_experimental_present_mode = main.pref_experimental_present_mode.imp();
|
||||
let mode = match conf.experimental_present_mode {
|
||||
PresentMode::Vsync => 0,
|
||||
PresentMode::Mailbox => 1,
|
||||
PresentMode::Immediate => 2,
|
||||
};
|
||||
pref_experimental_present_mode.dropdown.set_selected(mode);
|
||||
});
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
|
|
|||
33
ui/src/ui.rs
Normal file
33
ui/src/ui.rs
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
use adw::{self, subclass::prelude::ObjectSubclassIsExt};
|
||||
use gtk::prelude::*;
|
||||
|
||||
use crate::config;
|
||||
use crate::wrapper;
|
||||
|
||||
pub mod entry_handler;
|
||||
pub mod main_handler;
|
||||
pub mod sidebar_handler;
|
||||
|
||||
pub fn build(app: &adw::Application) {
|
||||
// create the main window
|
||||
let window = wrapper::Window::new(app);
|
||||
window.set_application(Some(app));
|
||||
let imp = window.imp();
|
||||
|
||||
// load profiles from configuration
|
||||
let config = config::get_config().unwrap();
|
||||
for game in config.game.iter() {
|
||||
let entry = wrapper::entry::Entry::new();
|
||||
entry.set_exe(game.exe.clone());
|
||||
entry_handler::add_entry(entry, imp.sidebar.imp().profiles.clone());
|
||||
}
|
||||
|
||||
// register handlers on sidebar pane.
|
||||
sidebar_handler::register_signals(&imp.sidebar, imp.main.clone());
|
||||
|
||||
// register handlers on main pane.
|
||||
main_handler::register_signals(&imp.main);
|
||||
|
||||
// present the window
|
||||
window.present();
|
||||
}
|
||||
56
ui/src/ui/entry_handler.rs
Normal file
56
ui/src/ui/entry_handler.rs
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
use adw::subclass::prelude::ObjectSubclassIsExt;
|
||||
use gtk::{gio, glib::object::CastNone, prelude::{ButtonExt, ListBoxRowExt, WidgetExt}};
|
||||
|
||||
use crate::{config, wrapper::entry, STATE};
|
||||
|
||||
///
|
||||
/// Register signals for removing presets when adding a new entry.
|
||||
///
|
||||
pub fn add_entry(entry_: entry::Entry, profiles_: gtk::ListBox) {
|
||||
let entry = entry_.clone();
|
||||
let profiles = profiles_.clone();
|
||||
entry_.imp().delete.connect_clicked(move |btn| {
|
||||
// prompt for confirmation
|
||||
let dialog = gtk::AlertDialog::builder()
|
||||
.message("Delete Profile")
|
||||
.detail("Are you sure you want to delete this profile?")
|
||||
.buttons(vec!["Cancel".to_string(), "Delete".to_string()])
|
||||
.cancel_button(0)
|
||||
.default_button(1)
|
||||
.modal(true)
|
||||
.build();
|
||||
let window = btn.root()
|
||||
.and_downcast::<gtk::Window>()
|
||||
.expect("Button root is not a Window");
|
||||
|
||||
let profiles = profiles.clone();
|
||||
let entry = entry.clone();
|
||||
dialog.choose(Some(&window), None::<&gio::Cancellable>, move |result| {
|
||||
if result.is_err() || result.unwrap() != 1 {
|
||||
return;
|
||||
}
|
||||
|
||||
// remove config entry
|
||||
let _ = config::edit_config(|config| {
|
||||
config.game.remove(entry.index() as usize);
|
||||
});
|
||||
|
||||
// remove ui entry
|
||||
profiles.remove(&entry);
|
||||
|
||||
// select next entry
|
||||
let idx = entry.index() as usize;
|
||||
if idx == 0 {
|
||||
let state = STATE.get().unwrap().clone();
|
||||
if let Ok(mut state) = state.write() {
|
||||
state.selected_game = None;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
profiles.row_at_index(0).unwrap().activate();
|
||||
});
|
||||
});
|
||||
|
||||
profiles_.append(&entry_);
|
||||
}
|
||||
58
ui/src/ui/main_handler.rs
Normal file
58
ui/src/ui/main_handler.rs
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
use adw::subclass::prelude::ObjectSubclassIsExt;
|
||||
use gtk::prelude::RangeExt;
|
||||
|
||||
use crate::{config, wrapper::pane, STATE};
|
||||
|
||||
// update the currently selected game configuration
|
||||
fn update_game<F: FnOnce(&mut config::TomlGame)>(update: F) {
|
||||
if let Ok(state) = STATE.get().unwrap().try_read() {
|
||||
if let Some(selected_game) = state.selected_game {
|
||||
let _ = config::edit_config(|config| {
|
||||
update(&mut config.game[selected_game])
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// Register signals for preset preferences.
|
||||
///
|
||||
pub fn register_signals(main: &pane::PaneMain) {
|
||||
let main = main.imp();
|
||||
let multiplier = main.multiplier.imp();
|
||||
let flow_scale = main.flow_scale.imp();
|
||||
let performance_mode = main.performance_mode.imp();
|
||||
let hdr_mode = main.hdr_mode.imp();
|
||||
let experimental_present_mode = main.experimental_present_mode.imp();
|
||||
|
||||
multiplier.number.connect_value_changed(|dropdown| {
|
||||
update_game(|conf| {
|
||||
conf.multiplier = (dropdown.value() as i64).into();
|
||||
})
|
||||
});
|
||||
flow_scale.slider.connect_value_changed(|slider| {
|
||||
update_game(|conf| {
|
||||
conf.flow_scale = (slider.value() / 100.0).into();
|
||||
});
|
||||
});
|
||||
performance_mode.switch.connect_state_notify(|switch| {
|
||||
update_game(|conf| {
|
||||
conf.performance_mode = switch.state();
|
||||
});
|
||||
});
|
||||
hdr_mode.switch.connect_state_notify(|switch| {
|
||||
update_game(|conf| {
|
||||
conf.hdr_mode = switch.state();
|
||||
});
|
||||
});
|
||||
experimental_present_mode.dropdown.connect_selected_notify(|dropdown| {
|
||||
update_game(|conf| {
|
||||
conf.experimental_present_mode = match dropdown.selected() {
|
||||
0 => config::PresentMode::Vsync,
|
||||
1 => config::PresentMode::Mailbox,
|
||||
2 => config::PresentMode::Immediate,
|
||||
_ => config::PresentMode::Vsync,
|
||||
};
|
||||
});
|
||||
});
|
||||
}
|
||||
67
ui/src/ui/sidebar_handler.rs
Normal file
67
ui/src/ui/sidebar_handler.rs
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
use adw::subclass::prelude::ObjectSubclassIsExt;
|
||||
use gtk::prelude::{ButtonExt, ListBoxRowExt, RangeExt, WidgetExt};
|
||||
|
||||
use crate::{config, ui::entry_handler, wrapper::{pane, entry}, STATE};
|
||||
|
||||
///
|
||||
/// Register signals for adding and selecting presets.
|
||||
///
|
||||
pub fn register_signals(sidebar_: &pane::PaneSidebar, main: pane::PaneMain) {
|
||||
// activate signal
|
||||
let state = STATE.get().unwrap().clone();
|
||||
sidebar_.imp().profiles.connect_row_activated(move |_, entry| {
|
||||
// find config entry by index
|
||||
let index = entry.index() as usize;
|
||||
let config = config::get_config();
|
||||
if config.is_err() {
|
||||
return;
|
||||
}
|
||||
let config = config.unwrap();
|
||||
let conf = config.game[index].clone();
|
||||
|
||||
// update main pane
|
||||
let main = main.imp();
|
||||
let multiplier = main.multiplier.imp();
|
||||
let flow_scale = main.flow_scale.imp();
|
||||
let performance_mode = main.performance_mode.imp();
|
||||
let hdr_mode = main.hdr_mode.imp();
|
||||
let experimental_present_mode = main.experimental_present_mode.imp();
|
||||
|
||||
// (lock state early, so the ui update doesn't override the config)
|
||||
if let Ok(mut state) = state.write() {
|
||||
multiplier.number.set_value(conf.multiplier.into());
|
||||
flow_scale.slider.set_value(Into::<f64>::into(conf.flow_scale) * 100.0);
|
||||
performance_mode.switch.set_state(conf.performance_mode);
|
||||
hdr_mode.switch.set_state(conf.hdr_mode);
|
||||
experimental_present_mode.dropdown.set_selected(conf.experimental_present_mode.into());
|
||||
|
||||
// update state
|
||||
state.selected_game = Some(index);
|
||||
}
|
||||
});
|
||||
|
||||
// create signal
|
||||
let sidebar = sidebar_.clone();
|
||||
sidebar_.imp().create.connect_clicked(move |_| {
|
||||
// ensure no config entry with the same name exist
|
||||
let config = config::get_config().unwrap();
|
||||
if config.game.iter().any(|e| e.exe == "new profile") {
|
||||
return;
|
||||
}
|
||||
|
||||
// create config entry
|
||||
let mut conf_entry = config::TomlGame::default();
|
||||
conf_entry.exe = "new profile".to_string();
|
||||
let _ = config::edit_config(|config| {
|
||||
config.game.push(conf_entry.clone());
|
||||
});
|
||||
|
||||
// add entry to sidebar
|
||||
let entry = entry::Entry::new();
|
||||
entry.set_exe(conf_entry.exe);
|
||||
entry_handler::add_entry(entry.clone(), sidebar.imp().profiles.clone());
|
||||
|
||||
// select the new entry
|
||||
entry.activate();
|
||||
});
|
||||
}
|
||||
|
|
@ -7,15 +7,15 @@ use crate::wrapper::pref::*;
|
|||
#[template(resource = "/gay/pancake/lsfg-vk/pane/main.ui")]
|
||||
pub struct PaneMain {
|
||||
#[template_child]
|
||||
pub pref_multiplier: TemplateChild<PrefNumber>,
|
||||
pub multiplier: TemplateChild<PrefNumber>,
|
||||
#[template_child]
|
||||
pub pref_flow_scale: TemplateChild<PrefSlider>,
|
||||
pub flow_scale: TemplateChild<PrefSlider>,
|
||||
#[template_child]
|
||||
pub pref_performance_mode: TemplateChild<PrefSwitch>,
|
||||
pub performance_mode: TemplateChild<PrefSwitch>,
|
||||
#[template_child]
|
||||
pub pref_hdr_mode: TemplateChild<PrefSwitch>,
|
||||
pub hdr_mode: TemplateChild<PrefSwitch>,
|
||||
#[template_child]
|
||||
pub pref_experimental_present_mode: TemplateChild<PrefDropdown>
|
||||
pub experimental_present_mode: TemplateChild<PrefDropdown>
|
||||
}
|
||||
|
||||
#[glib::object_subclass]
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue