feat(#1101): auto/force light/force dark color scheme preference (#1103)

* feat(#1101): auto/force light/force dark color scheme preference

* fix: make colors override work with breeze

---------

Co-authored-by: Gabriele Musco <gabmus@disroot.org>
Co-authored-by: Ilya Zlobintsev <ilya.zl@protonmail.com>
This commit is contained in:
Gabriele Musco
2026-07-01 21:56:53 +03:00
committed by GitHub
co-authored by Gabriele Musco Ilya Zlobintsev
parent 7ae78f16ad
commit 5c18e7acd8
8 changed files with 111 additions and 5 deletions
+5
View File
@@ -295,6 +295,11 @@ ui = UI
daemon = Daemon
about = About
color-scheme = Color Scheme
color-scheme-auto = System
color-scheme-light = Light
color-scheme-dark = Dark
# Crash page
crash-page-title = Application Crashed
exit = Exit
+4
View File
@@ -267,3 +267,7 @@ version-mismatch-description =
Se hai aggiornato LACT, è necessario riavviare il servizio con:
close = Chiudi
menu = Menu
color-scheme = Schema di Colori
color-scheme-auto = Sistema
color-scheme-light = Chiaro
color-scheme-dark = Scuro
+1
View File
@@ -365,6 +365,7 @@ impl AsyncComponent for AppModel {
if let Err(err) = styles::apply_theme(CONFIG.read().theme) {
error!("could not apply theme: {err:#}");
}
CONFIG.read().color_scheme.apply();
let (daemon_client, conn_err) = match args.tcp_address {
Some(remote_addr) => {
+59 -1
View File
@@ -3,7 +3,10 @@ use crate::{
app::{
APP_BROKER,
msg::AppMsg,
utils::styles::{self, AppTheme},
utils::{
color_scheme::AppColorScheme,
styles::{self, AppTheme},
},
},
config::{MAX_STATS_POLL_INTERVAL_MS, MIN_STATS_POLL_INTERVAL_MS},
};
@@ -27,6 +30,7 @@ pub struct PreferencesDialog {
pub enum PreferencesDialogMsg {
Show,
ThemeSelected(AppTheme),
ColorSchemeSelected(AppColorScheme),
}
#[relm4::component(pub)]
@@ -90,6 +94,52 @@ impl relm4::Component for PreferencesDialog {
},
},
adw::ActionRow {
set_title: &fl!(I18N, "color-scheme"),
add_suffix = &gtk::Box {
set_orientation: gtk::Orientation::Horizontal,
add_css_class: "linked",
set_valign: gtk::Align::Center,
#[name = "color_scheme_auto_btn"]
gtk::ToggleButton {
set_label: &fl!(I18N, "color-scheme-auto"),
#[watch]
set_active: CONFIG.read().color_scheme == AppColorScheme::Auto,
connect_toggled[sender] => move |btn| {
if btn.is_active() {
sender.input(PreferencesDialogMsg::ColorSchemeSelected(AppColorScheme::Auto));
}
},
},
gtk::ToggleButton {
set_label: &fl!(I18N, "color-scheme-light"),
set_group: Some(&color_scheme_auto_btn),
#[watch]
set_active: CONFIG.read().color_scheme == AppColorScheme::Light,
connect_toggled[sender] => move |btn| {
if btn.is_active() {
sender.input(PreferencesDialogMsg::ColorSchemeSelected(AppColorScheme::Light));
}
},
},
gtk::ToggleButton {
set_label: &fl!(I18N, "color-scheme-dark"),
set_group: Some(&color_scheme_auto_btn),
#[watch]
set_active: CONFIG.read().color_scheme == AppColorScheme::Dark,
connect_toggled[sender] => move |btn| {
if btn.is_active() {
sender.input(PreferencesDialogMsg::ColorSchemeSelected(AppColorScheme::Dark));
}
},
},
},
},
adw::ActionRow {
set_title: &fl!(I18N, "stats-update-interval"),
@@ -174,6 +224,14 @@ impl relm4::Component for PreferencesDialog {
config.theme = theme;
});
}
PreferencesDialogMsg::ColorSchemeSelected(scheme) => {
scheme.apply();
CONFIG.write().edit(|config| {
config.color_scheme = scheme;
});
styles::apply_theme(CONFIG.read().theme).expect("Could not apply theme");
}
}
self.update_view(widgets, sender);
}
+25
View File
@@ -0,0 +1,25 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum AppColorScheme {
#[default]
Auto,
Light,
Dark,
}
impl From<AppColorScheme> for adw::ColorScheme {
fn from(value: AppColorScheme) -> Self {
match value {
AppColorScheme::Auto => adw::ColorScheme::Default,
AppColorScheme::Light => adw::ColorScheme::ForceLight,
AppColorScheme::Dark => adw::ColorScheme::ForceDark,
}
}
}
impl AppColorScheme {
pub fn apply(&self) {
adw::StyleManager::default().set_color_scheme((*self).into());
}
}
+1
View File
@@ -1,3 +1,4 @@
pub(crate) mod color_scheme;
pub(crate) mod ext;
pub(crate) mod formatting;
pub(crate) mod styles;
+9 -3
View File
@@ -5,6 +5,8 @@ use gtk::{
use serde::{Deserialize, Serialize};
use std::cell::RefCell;
use crate::{CONFIG, app::utils::color_scheme::AppColorScheme};
pub const COMBINED_CSS: &str = include_str!(concat!(env!("OUT_DIR"), "/combined.css"));
macro_rules! include_theme_str {
@@ -72,9 +74,13 @@ pub fn apply_theme(theme: AppTheme) -> anyhow::Result<()> {
fn breeze_css() -> &'static str {
let settings = gio::Settings::new("org.gnome.desktop.interface");
match settings.string("color-scheme").as_str() {
"prefer-dark" => BREEZE_DARK_CSS,
"prefer-light" => BREEZE_LIGHT_CSS,
let system_colors = settings.string("color-scheme");
let app_colors = CONFIG.read().color_scheme;
match (app_colors, system_colors.as_str()) {
(AppColorScheme::Dark, _) | (AppColorScheme::Auto, "prefer-dark") => BREEZE_DARK_CSS,
(AppColorScheme::Light, _) | (AppColorScheme::Auto, "prefer-light") => BREEZE_LIGHT_CSS,
_ => BREEZE_LIGHT_CSS,
}
}
+7 -1
View File
@@ -1,4 +1,7 @@
use crate::app::{graphs_window::stat::StatType, utils::styles::AppTheme};
use crate::app::{
graphs_window::stat::StatType,
utils::{color_scheme::AppColorScheme, styles::AppTheme},
};
use serde::{Deserialize, Deserializer, Serialize};
use serde_with::skip_serializing_none;
use std::{collections::HashMap, env, fs, path::PathBuf};
@@ -24,6 +27,8 @@ pub struct UiConfig {
pub gpus: HashMap<String, UiGpuConfig>,
#[serde(default)]
pub theme: AppTheme,
#[serde(default)]
pub color_scheme: AppColorScheme,
pub window_size: Option<WindowSize>,
}
@@ -37,6 +42,7 @@ impl Default for UiConfig {
stats_poll_interval_ms: default_stats_poll_interval(),
gpus: HashMap::new(),
theme: AppTheme::Automatic,
color_scheme: AppColorScheme::default(),
window_size: None,
}
}