feat: theme manager

This commit is contained in:
Gabriele Musco 2025-05-01 01:18:46 +02:00
commit e0eae7c13a
5 changed files with 77 additions and 7 deletions

23
Cargo.lock generated
View file

@ -445,6 +445,16 @@ dependencies = [
"typenum",
]
[[package]]
name = "delicious-adwaita"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e53548c789a95211e0ce6d26c213067002b9b4360f8de69046d84de78ad9da3f"
dependencies = [
"gtk4",
"libadwaita",
]
[[package]]
name = "deranged"
version = "0.3.11"
@ -567,6 +577,7 @@ version = "3.1.1"
dependencies = [
"anyhow",
"ash",
"delicious-adwaita",
"gettext-rs",
"git2",
"gtk4",
@ -1073,9 +1084,9 @@ dependencies = [
[[package]]
name = "gtk4"
version = "0.9.4"
version = "0.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9376d14d7e33486c54823a42bef296e882b9f25cb4c52b52f4d1d57bbadb5b6d"
checksum = "af1c491051f030994fd0cde6f3c44f3f5640210308cff1298c7673c47408091d"
dependencies = [
"cairo-rs",
"field-offset",
@ -1106,9 +1117,9 @@ dependencies = [
[[package]]
name = "gtk4-sys"
version = "0.9.4"
version = "0.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e653b0a9001ba9be1ffddb9373bfe9a111f688222f5aeee2841481300d91b55a"
checksum = "41e03b01e54d77c310e1d98647d73f996d04b2f29b9121fe493ea525a7ec03d6"
dependencies = [
"cairo-sys-rs",
"gdk-pixbuf-sys",
@ -1523,9 +1534,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
[[package]]
name = "libadwaita"
version = "0.7.1"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8611ee9fb85e7606c362b513afcaf5b59853f79e4d98caaaf581d99465014247"
checksum = "500135d29c16aabf67baafd3e7741d48e8b8978ca98bac39e589165c8dc78191"
dependencies = [
"gdk4",
"gio",

View file

@ -44,3 +44,4 @@ tracing-subscriber = { version = "0.3.19", features = ["env-filter", "json"] }
tracing = "0.1.41"
tracing-appender = "0.2.3"
serde_yaml = "0.9.34"
delicious-adwaita = { version = "0.3.0", features = ["all_themes"] }

View file

@ -45,6 +45,10 @@ const fn default_win_size() -> [i32; 2] {
DEFAULT_WIN_SIZE
}
fn default_theme_name() -> String {
"Follow system".into()
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Config {
pub selected_profile_uuid: String,
@ -54,6 +58,8 @@ pub struct Config {
pub win_size: [i32; 2],
#[serde(default)]
pub plugins: HashMap<String, PluginConfig>,
#[serde(default = "default_theme_name")]
pub theme_name: String,
}
impl Default for Config {
@ -65,6 +71,7 @@ impl Default for Config {
user_profiles: Vec::default(),
win_size: DEFAULT_WIN_SIZE,
plugins: HashMap::default(),
theme_name: default_theme_name(),
}
}
}

View file

@ -51,6 +51,7 @@ use crate::{
xr_devices::XRDevice,
};
use adw::{prelude::*, ResponseAppearance};
use delicious_adwaita::{theme::Theme, ThemeEngine};
use gtk::glib::{self, clone};
use notify_rust::NotificationHandle;
use relm4::{
@ -96,6 +97,8 @@ pub struct App {
inhibit_fail_notif: Option<NotificationHandle>,
pluginstore: Option<AsyncController<PluginStore>>,
theme_engine: ThemeEngine,
}
#[derive(Debug)]
@ -130,6 +133,8 @@ pub enum Msg {
WivrnCheckPairMode,
OpenPluginStore,
UpdateConfigPlugins(HashMap<String, PluginConfig>),
ShowThemeManager,
SaveThemeConfig,
NoOp,
}
@ -367,7 +372,7 @@ impl AsyncComponent for App {
set_content: Some(&adw::NavigationPage::new(model.debug_view.widget(), "Debug View")),
set_show_content: false,
set_collapsed: !model.config.debug_view_enabled,
}
},
},
connect_close_request[sender] => move |win| {
sender.input(Msg::SaveWinSize(win.width(), win.height()));
@ -391,6 +396,27 @@ impl AsyncComponent for App {
) {
match message {
Msg::NoOp => {}
Msg::ShowThemeManager => {
let dialog = self
.theme_engine
.theme_chooser_dialog(Theme::default_themes().as_ref());
dialog.set_content_height(2000);
dialog.present(Some(&self.app_win));
dialog.connect_closed(clone!(
#[strong]
sender,
move |_| {
sender.input(Msg::SaveThemeConfig);
}
));
}
Msg::SaveThemeConfig => {
let name = self.theme_engine.current_theme_name();
if self.config.theme_name != name {
self.config.theme_name = name;
self.config.save();
}
}
Msg::OnServiceLog(rows) => {
if !rows.is_empty() {
self.debug_view
@ -974,6 +1000,17 @@ impl AsyncComponent for App {
}
)
);
stateless_action!(
actions,
ThemeManagerAction,
clone!(
#[strong]
sender,
move |_| {
sender.input(Msg::ShowThemeManager);
}
)
);
// this bypasses the macro because I need the underlying gio action
// to enable/disable it in update()
let configure_wivrn_action = {
@ -1042,6 +1079,17 @@ impl AsyncComponent for App {
.detach(),
split_view: None,
setcap_confirm_dialog,
theme_engine: ThemeEngine::new_with_theme(&{
if config.theme_name == "Follow system" {
Theme::default()
} else {
Theme::default_themes()
.into_iter()
.find(|t| t.name == config.theme_name)
.unwrap_or_default()
}
})
.unwrap(),
config,
profiles,
xrservice_worker: None,
@ -1130,6 +1178,7 @@ new_stateless_action!(pub QuitAction, AppActionGroup, "quit");
new_stateful_action!(pub DebugViewToggleAction, AppActionGroup, "debugviewtoggle", (), bool);
new_stateless_action!(pub ConfigureWivrnAction, AppActionGroup, "configurewivrn");
new_stateless_action!(pub PluginStoreAction, AppActionGroup, "store");
new_stateless_action!(pub ThemeManagerAction, AppActionGroup, "thememanager");
new_stateless_action!(pub DebugOpenDataAction, AppActionGroup, "debugopendata");
new_stateless_action!(pub DebugOpenPrefixAction, AppActionGroup, "debugopenprefix");

View file

@ -21,6 +21,7 @@ use crate::{
paths::{get_data_dir, get_home_dir},
profile::{LighthouseDriver, Profile, XRServiceType},
stateless_action,
ui::app::ThemeManagerAction,
util::{
file_utils::{get_writer, mount_has_nosuid},
steamvr_utils::chaperone_info_exists,
@ -159,6 +160,7 @@ impl AsyncComponent for MainView {
"Configure _WiVRn" => ConfigureWivrnAction,
},
section! {
"Change _Theme" => ThemeManagerAction,
"_About" => AboutAction,
},
},