ui: configuration system and basic switching

This commit is contained in:
PancakeTAS 2025-07-23 23:27:29 +02:00 committed by Pancake
parent 3dba2a7a3d
commit cbc536c23e
22 changed files with 709 additions and 272 deletions

67
ui/Cargo.lock generated
View file

@ -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]]

View file

@ -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
View 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
View 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>

View file

@ -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>

View file

@ -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
View 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
View 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>
}

View file

@ -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();
}

View file

@ -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();
}
}

View file

@ -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()
}
}

View file

@ -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
View 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
View 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()
}
}

View 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 {}

View 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 {}