feat: Connect GUI overlay to JSON config and implement interactive GTK settings dialogs
This commit is contained in:
parent
85b90b73f8
commit
826166d925
6 changed files with 334 additions and 11 deletions
63
Cargo.lock
generated
63
Cargo.lock
generated
|
|
@ -35,7 +35,7 @@ checksum = "fb8421aaa9644a5faf26735f258b669b15f063313ef8f8e2bdb28912a1a6f111"
|
|||
dependencies = [
|
||||
"enumflags2",
|
||||
"futures-util",
|
||||
"getrandom",
|
||||
"getrandom 0.4.3",
|
||||
"serde",
|
||||
"serde_repr",
|
||||
"tokio",
|
||||
|
|
@ -253,6 +253,27 @@ version = "0.8.22"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17"
|
||||
|
||||
[[package]]
|
||||
name = "dirs"
|
||||
version = "6.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e"
|
||||
dependencies = [
|
||||
"dirs-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs-sys"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"option-ext",
|
||||
"redox_users",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dispatch2"
|
||||
version = "0.3.1"
|
||||
|
|
@ -522,6 +543,17 @@ dependencies = [
|
|||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.4.3"
|
||||
|
|
@ -886,6 +918,15 @@ dependencies = [
|
|||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libredox"
|
||||
version = "0.1.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8d8f1ea3f21fd3405dcaf6c9b5c1630af9afc422d9073ea39c5f6d6c772e08ed"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libspa"
|
||||
version = "0.10.1"
|
||||
|
|
@ -1097,6 +1138,12 @@ version = "1.21.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
||||
|
||||
[[package]]
|
||||
name = "option-ext"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
|
||||
|
||||
[[package]]
|
||||
name = "ordered-stream"
|
||||
version = "0.2.0"
|
||||
|
|
@ -1276,6 +1323,17 @@ version = "6.0.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
|
||||
|
||||
[[package]]
|
||||
name = "redox_users"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac"
|
||||
dependencies = [
|
||||
"getrandom 0.2.17",
|
||||
"libredox",
|
||||
"thiserror 2.0.20",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.13.1"
|
||||
|
|
@ -1562,7 +1620,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
|
||||
dependencies = [
|
||||
"fastrand",
|
||||
"getrandom",
|
||||
"getrandom 0.4.3",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
"windows-sys 0.61.2",
|
||||
|
|
@ -1827,6 +1885,7 @@ version = "0.1.0"
|
|||
dependencies = [
|
||||
"ashpd",
|
||||
"crossbeam-channel",
|
||||
"dirs",
|
||||
"ffmpeg-next",
|
||||
"ffmpeg-sys-next",
|
||||
"gdk",
|
||||
|
|
|
|||
|
|
@ -22,3 +22,4 @@ serde = { version = "1.0.229", features = ["derive"] }
|
|||
serde_json = "1.0.151"
|
||||
tokio = { version = "1.53.1", features = ["rt", "macros", "rt-multi-thread"] }
|
||||
xcb = { version = "1.7.1", features = ["shm", "xfixes"] }
|
||||
dirs = "6.0.0"
|
||||
|
|
|
|||
69
src/config.rs
Normal file
69
src/config.rs
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct VrecConfig {
|
||||
pub replay_enabled: bool,
|
||||
pub replay_duration_sec: u32,
|
||||
pub replay_bitrate_kbps: u32,
|
||||
|
||||
pub record_enabled: bool,
|
||||
pub record_bitrate_kbps: u32,
|
||||
|
||||
pub autostart: bool,
|
||||
pub language: String,
|
||||
pub ui_color_theme: String,
|
||||
pub save_hotkey: String,
|
||||
pub menu_hotkey: String,
|
||||
}
|
||||
|
||||
impl Default for VrecConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
replay_enabled: true,
|
||||
replay_duration_sec: 60,
|
||||
replay_bitrate_kbps: 10000,
|
||||
|
||||
record_enabled: false,
|
||||
record_bitrate_kbps: 15000,
|
||||
|
||||
autostart: false,
|
||||
language: "en".to_string(),
|
||||
ui_color_theme: "dark".to_string(),
|
||||
save_hotkey: "Ctrl+Shift+R".to_string(),
|
||||
menu_hotkey: "Alt+Z".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl VrecConfig {
|
||||
pub fn config_path() -> PathBuf {
|
||||
let mut path = dirs::config_dir().unwrap_or_else(|| PathBuf::from("."));
|
||||
path.push("vrec");
|
||||
path.push("config.json");
|
||||
path
|
||||
}
|
||||
|
||||
pub fn load() -> Self {
|
||||
let path = Self::config_path();
|
||||
if path.exists() {
|
||||
if let Ok(data) = fs::read_to_string(path) {
|
||||
if let Ok(config) = serde_json::from_str(&data) {
|
||||
return config;
|
||||
}
|
||||
}
|
||||
}
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn save(&self) -> Result<(), std::io::Error> {
|
||||
let path = Self::config_path();
|
||||
if let Some(parent) = path.parent() {
|
||||
fs::create_dir_all(parent)?;
|
||||
}
|
||||
let data = serde_json::to_string_pretty(self)?;
|
||||
fs::write(path, data)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
@ -5,3 +5,5 @@ pub mod muxer;
|
|||
pub mod hotkey;
|
||||
pub mod ipc;
|
||||
pub mod overlay;
|
||||
pub mod config;
|
||||
pub mod settings_ui;
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ use gtk::prelude::*;
|
|||
use gtk::{Application, ApplicationWindow, Label, CssProvider, StyleContext};
|
||||
use gtk_layer_shell::{LayerShell, Layer};
|
||||
use std::time::Duration;
|
||||
use crate::config::VrecConfig;
|
||||
use crate::settings_ui;
|
||||
|
||||
pub fn show_notification_overlay() {
|
||||
let app = Application::builder()
|
||||
|
|
@ -21,7 +23,6 @@ pub fn show_notification_overlay() {
|
|||
window.set_layer_shell_margin(gtk_layer_shell::Edge::Top, 40);
|
||||
window.set_anchor(gtk_layer_shell::Edge::Top, true);
|
||||
|
||||
// Make it transparent and borderless
|
||||
let css_provider = CssProvider::new();
|
||||
let css = r#"
|
||||
window {
|
||||
|
|
@ -46,7 +47,6 @@ pub fn show_notification_overlay() {
|
|||
|
||||
window.show_all();
|
||||
|
||||
// Close after 2 seconds
|
||||
let window_clone = window.clone();
|
||||
glib::timeout_add_local(Duration::from_secs(2), move || {
|
||||
window_clone.close();
|
||||
|
|
@ -63,6 +63,8 @@ pub fn show_menu_overlay() {
|
|||
.build();
|
||||
|
||||
app.connect_activate(|app| {
|
||||
let config = VrecConfig::load();
|
||||
|
||||
let window = ApplicationWindow::builder()
|
||||
.application(app)
|
||||
.default_width(700)
|
||||
|
|
@ -76,7 +78,6 @@ pub fn show_menu_overlay() {
|
|||
window.set_layer_shell_margin(gtk_layer_shell::Edge::Top, 100);
|
||||
window.set_anchor(gtk_layer_shell::Edge::Top, true);
|
||||
|
||||
// Main layout
|
||||
let main_box = gtk::Box::new(gtk::Orientation::Horizontal, 20);
|
||||
main_box.set_margin_start(20);
|
||||
main_box.set_margin_end(20);
|
||||
|
|
@ -84,26 +85,43 @@ pub fn show_menu_overlay() {
|
|||
main_box.set_margin_bottom(20);
|
||||
main_box.set_halign(gtk::Align::Center);
|
||||
|
||||
// 1. Replay Box
|
||||
let replay_box = gtk::Box::new(gtk::Orientation::Vertical, 10);
|
||||
let replay_btn = gtk::Button::with_label("Instant Replay\n🟢 ON");
|
||||
let replay_label = if config.replay_enabled { "Instant Replay\n🟢 ON" } else { "Instant Replay\n⚪ OFF" };
|
||||
let replay_btn = gtk::Button::with_label(replay_label);
|
||||
replay_btn.set_size_request(200, 100);
|
||||
let replay_settings_btn = gtk::Button::with_label("⚙️ Replay Settings");
|
||||
|
||||
let cfg_clone1 = config.clone();
|
||||
replay_settings_btn.connect_clicked(move |_| {
|
||||
settings_ui::open_replay_settings(&cfg_clone1);
|
||||
});
|
||||
|
||||
replay_box.pack_start(&replay_btn, true, true, 0);
|
||||
replay_box.pack_start(&replay_settings_btn, false, false, 0);
|
||||
|
||||
// 2. Record Box
|
||||
let record_box = gtk::Box::new(gtk::Orientation::Vertical, 10);
|
||||
let record_btn = gtk::Button::with_label("Recording\n⚪ OFF");
|
||||
let record_label = if config.record_enabled { "Recording\n🔴 ON" } else { "Recording\n⚪ OFF" };
|
||||
let record_btn = gtk::Button::with_label(record_label);
|
||||
record_btn.set_size_request(200, 100);
|
||||
let record_settings_btn = gtk::Button::with_label("⚙️ Record Settings");
|
||||
|
||||
let cfg_clone2 = config.clone();
|
||||
record_settings_btn.connect_clicked(move |_| {
|
||||
settings_ui::open_record_settings(&cfg_clone2);
|
||||
});
|
||||
|
||||
record_box.pack_start(&record_btn, true, true, 0);
|
||||
record_box.pack_start(&record_settings_btn, false, false, 0);
|
||||
|
||||
// 3. Settings Box
|
||||
let settings_box = gtk::Box::new(gtk::Orientation::Vertical, 10);
|
||||
let gen_settings_btn = gtk::Button::with_label("General Settings");
|
||||
gen_settings_btn.set_size_request(200, 50);
|
||||
|
||||
let cfg_clone3 = config.clone();
|
||||
gen_settings_btn.connect_clicked(move |_| {
|
||||
settings_ui::open_general_settings(&cfg_clone3);
|
||||
});
|
||||
|
||||
let close_btn = gtk::Button::with_label("❌ Close Menu");
|
||||
close_btn.set_size_request(200, 50);
|
||||
|
||||
|
|
@ -123,7 +141,34 @@ pub fn show_menu_overlay() {
|
|||
window_clone.close();
|
||||
});
|
||||
|
||||
// Add CSS styling
|
||||
// Toggle buttons logic (just UI for now, backend IPC will be needed to actually toggle daemon state)
|
||||
let btn_cfg_1 = config.clone();
|
||||
let replay_btn_clone = replay_btn.clone();
|
||||
replay_btn.connect_clicked(move |_| {
|
||||
let mut cfg = VrecConfig::load();
|
||||
cfg.replay_enabled = !cfg.replay_enabled;
|
||||
cfg.save().unwrap();
|
||||
if cfg.replay_enabled {
|
||||
replay_btn_clone.set_label("Instant Replay\n🟢 ON");
|
||||
} else {
|
||||
replay_btn_clone.set_label("Instant Replay\n⚪ OFF");
|
||||
}
|
||||
});
|
||||
|
||||
let btn_cfg_2 = config.clone();
|
||||
let record_btn_clone = record_btn.clone();
|
||||
record_btn.connect_clicked(move |_| {
|
||||
let mut cfg = VrecConfig::load();
|
||||
cfg.record_enabled = !cfg.record_enabled;
|
||||
cfg.save().unwrap();
|
||||
if cfg.record_enabled {
|
||||
record_btn_clone.set_label("Recording\n🔴 ON");
|
||||
} else {
|
||||
record_btn_clone.set_label("Recording\n⚪ OFF");
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
let css_provider = CssProvider::new();
|
||||
let css = r#"
|
||||
window {
|
||||
|
|
|
|||
147
src/settings_ui.rs
Normal file
147
src/settings_ui.rs
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
use gtk::prelude::*;
|
||||
use gtk::{Window, WindowType, Box, Orientation, Label, Entry, Button, Switch, ComboBoxText};
|
||||
use crate::config::VrecConfig;
|
||||
|
||||
pub fn open_replay_settings(config_in: &VrecConfig) {
|
||||
let window = Window::new(WindowType::Toplevel);
|
||||
window.set_title("Replay Settings");
|
||||
window.set_default_size(300, 200);
|
||||
|
||||
let vbox = Box::new(Orientation::Vertical, 10);
|
||||
vbox.set_margin(15);
|
||||
|
||||
let duration_label = Label::new(Some("Replay Duration (seconds):"));
|
||||
let duration_entry = Entry::new();
|
||||
duration_entry.set_text(&config_in.replay_duration_sec.to_string());
|
||||
|
||||
let bitrate_label = Label::new(Some("Bitrate (kbps):"));
|
||||
let bitrate_entry = Entry::new();
|
||||
bitrate_entry.set_text(&config_in.replay_bitrate_kbps.to_string());
|
||||
|
||||
let save_btn = Button::with_label("Save");
|
||||
|
||||
vbox.pack_start(&duration_label, false, false, 0);
|
||||
vbox.pack_start(&duration_entry, false, false, 0);
|
||||
vbox.pack_start(&bitrate_label, false, false, 0);
|
||||
vbox.pack_start(&bitrate_entry, false, false, 0);
|
||||
vbox.pack_start(&save_btn, false, false, 0);
|
||||
|
||||
window.add(&vbox);
|
||||
|
||||
let window_clone = window.clone();
|
||||
save_btn.connect_clicked(move |_| {
|
||||
let mut cfg = VrecConfig::load();
|
||||
if let Ok(val) = duration_entry.text().parse() {
|
||||
cfg.replay_duration_sec = val;
|
||||
}
|
||||
if let Ok(val) = bitrate_entry.text().parse() {
|
||||
cfg.replay_bitrate_kbps = val;
|
||||
}
|
||||
let _ = cfg.save();
|
||||
window_clone.close();
|
||||
});
|
||||
|
||||
window.show_all();
|
||||
}
|
||||
|
||||
pub fn open_record_settings(config_in: &VrecConfig) {
|
||||
let window = Window::new(WindowType::Toplevel);
|
||||
window.set_title("Record Settings");
|
||||
window.set_default_size(300, 150);
|
||||
|
||||
let vbox = Box::new(Orientation::Vertical, 10);
|
||||
vbox.set_margin(15);
|
||||
|
||||
let bitrate_label = Label::new(Some("Recording Bitrate (kbps):"));
|
||||
let bitrate_entry = Entry::new();
|
||||
bitrate_entry.set_text(&config_in.record_bitrate_kbps.to_string());
|
||||
|
||||
let save_btn = Button::with_label("Save");
|
||||
|
||||
vbox.pack_start(&bitrate_label, false, false, 0);
|
||||
vbox.pack_start(&bitrate_entry, false, false, 0);
|
||||
vbox.pack_start(&save_btn, false, false, 0);
|
||||
|
||||
window.add(&vbox);
|
||||
|
||||
let window_clone = window.clone();
|
||||
save_btn.connect_clicked(move |_| {
|
||||
let mut cfg = VrecConfig::load();
|
||||
if let Ok(val) = bitrate_entry.text().parse() {
|
||||
cfg.record_bitrate_kbps = val;
|
||||
}
|
||||
let _ = cfg.save();
|
||||
window_clone.close();
|
||||
});
|
||||
|
||||
window.show_all();
|
||||
}
|
||||
|
||||
pub fn open_general_settings(config_in: &VrecConfig) {
|
||||
let window = Window::new(WindowType::Toplevel);
|
||||
window.set_title("General Settings");
|
||||
window.set_default_size(350, 300);
|
||||
|
||||
let vbox = Box::new(Orientation::Vertical, 10);
|
||||
vbox.set_margin(15);
|
||||
|
||||
// Autostart
|
||||
let hbox_auto = Box::new(Orientation::Horizontal, 10);
|
||||
let auto_label = Label::new(Some("Autostart with system:"));
|
||||
let auto_switch = Switch::new();
|
||||
auto_switch.set_active(config_in.autostart);
|
||||
hbox_auto.pack_start(&auto_label, false, false, 0);
|
||||
hbox_auto.pack_start(&auto_switch, false, false, 0);
|
||||
|
||||
// Theme
|
||||
let theme_label = Label::new(Some("UI Theme:"));
|
||||
let theme_combo = ComboBoxText::new();
|
||||
theme_combo.append_text("dark");
|
||||
theme_combo.append_text("light");
|
||||
theme_combo.set_active(Some(if config_in.ui_color_theme == "dark" { 0 } else { 1 }));
|
||||
|
||||
// Language
|
||||
let lang_label = Label::new(Some("Language Code (e.g. en, fr):"));
|
||||
let lang_entry = Entry::new();
|
||||
lang_entry.set_text(&config_in.language);
|
||||
|
||||
// Hotkeys
|
||||
let menu_hk_label = Label::new(Some("Menu Hotkey:"));
|
||||
let menu_hk_entry = Entry::new();
|
||||
menu_hk_entry.set_text(&config_in.menu_hotkey);
|
||||
|
||||
let save_hk_label = Label::new(Some("Save Replay Hotkey:"));
|
||||
let save_hk_entry = Entry::new();
|
||||
save_hk_entry.set_text(&config_in.save_hotkey);
|
||||
|
||||
let save_btn = Button::with_label("Save General Settings");
|
||||
|
||||
vbox.pack_start(&hbox_auto, false, false, 0);
|
||||
vbox.pack_start(&theme_label, false, false, 0);
|
||||
vbox.pack_start(&theme_combo, false, false, 0);
|
||||
vbox.pack_start(&lang_label, false, false, 0);
|
||||
vbox.pack_start(&lang_entry, false, false, 0);
|
||||
vbox.pack_start(&menu_hk_label, false, false, 0);
|
||||
vbox.pack_start(&menu_hk_entry, false, false, 0);
|
||||
vbox.pack_start(&save_hk_label, false, false, 0);
|
||||
vbox.pack_start(&save_hk_entry, false, false, 0);
|
||||
vbox.pack_start(&save_btn, false, false, 0);
|
||||
|
||||
window.add(&vbox);
|
||||
|
||||
let window_clone = window.clone();
|
||||
save_btn.connect_clicked(move |_| {
|
||||
let mut cfg = VrecConfig::load();
|
||||
cfg.autostart = auto_switch.is_active();
|
||||
if let Some(txt) = theme_combo.active_text() {
|
||||
cfg.ui_color_theme = txt.to_string();
|
||||
}
|
||||
cfg.language = lang_entry.text().to_string();
|
||||
cfg.menu_hotkey = menu_hk_entry.text().to_string();
|
||||
cfg.save_hotkey = save_hk_entry.text().to_string();
|
||||
let _ = cfg.save();
|
||||
window_clone.close();
|
||||
});
|
||||
|
||||
window.show_all();
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue