theme: persist across app close

Signed-off-by: kernelkind <kernelkind@gmail.com>
This commit is contained in:
kernelkind
2024-12-11 13:59:34 -05:00
parent 2ce845c1fc
commit 9e67f9dc8c
5 changed files with 137 additions and 35 deletions

View File

@@ -2,6 +2,7 @@ use crate::{
accounts::Accounts, accounts::Accounts,
app_creation::setup_cc, app_creation::setup_cc,
app_size_handler::AppSizeHandler, app_size_handler::AppSizeHandler,
app_style::{dark_mode, light_mode},
args::Args, args::Args,
column::Columns, column::Columns,
decks::{Decks, DecksCache, FALLBACK_PUBKEY}, decks::{Decks, DecksCache, FALLBACK_PUBKEY},
@@ -16,9 +17,10 @@ use crate::{
storage::{self, DataPath, DataPathType, Directory, FileKeyStorage, KeyStorageType}, storage::{self, DataPath, DataPathType, Directory, FileKeyStorage, KeyStorageType},
subscriptions::{SubKind, Subscriptions}, subscriptions::{SubKind, Subscriptions},
support::Support, support::Support,
theme_handler::ThemeHandler,
thread::Thread, thread::Thread,
timeline::{self, Timeline}, timeline::{self, Timeline},
ui::{self, DesktopSidePanel}, ui::{self, is_compiled_as_mobile, DesktopSidePanel},
unknowns::UnknownIds, unknowns::UnknownIds,
view_state::ViewState, view_state::ViewState,
Result, Result,
@@ -61,6 +63,7 @@ pub struct Damus {
pub subscriptions: Subscriptions, pub subscriptions: Subscriptions,
pub app_rect_handler: AppSizeHandler, pub app_rect_handler: AppSizeHandler,
pub support: Support, pub support: Support,
pub theme: ThemeHandler,
frame_history: crate::frame_history::FrameHistory, frame_history: crate::frame_history::FrameHistory,
@@ -408,6 +411,15 @@ impl Damus {
1024usize * 1024usize * 1024usize * 1024usize 1024usize * 1024usize * 1024usize * 1024usize
}; };
let theme = ThemeHandler::new(&path);
ctx.options_mut(|o| {
let cur_theme = theme.load();
info!("Loaded theme {:?} from disk", cur_theme);
o.theme_preference = cur_theme;
});
ctx.set_visuals_of(egui::Theme::Dark, dark_mode(is_compiled_as_mobile()));
ctx.set_visuals_of(egui::Theme::Light, light_mode());
let config = Config::new().set_ingester_threads(4).set_mapsize(mapsize); let config = Config::new().set_ingester_threads(4).set_mapsize(mapsize);
let keystore = if parsed_args.use_keystore { let keystore = if parsed_args.use_keystore {
@@ -509,6 +521,7 @@ impl Damus {
app_rect_handler, app_rect_handler,
support, support,
decks_cache, decks_cache,
theme,
} }
} }
@@ -560,6 +573,7 @@ impl Damus {
let decks_cache = DecksCache::default(); let decks_cache = DecksCache::default();
let path = DataPath::new(&data_path); let path = DataPath::new(&data_path);
let theme = ThemeHandler::new(&path);
let imgcache_dir = path.path(DataPathType::Cache).join(ImageCache::rel_dir()); let imgcache_dir = path.path(DataPathType::Cache).join(ImageCache::rel_dir());
let _ = std::fs::create_dir_all(imgcache_dir.clone()); let _ = std::fs::create_dir_all(imgcache_dir.clone());
let debug = true; let debug = true;
@@ -596,6 +610,7 @@ impl Damus {
app_rect_handler, app_rect_handler,
support, support,
decks_cache, decks_cache,
theme,
} }
} }
@@ -715,6 +730,7 @@ fn timelines_view(ui: &mut egui::Ui, sizes: Size, app: &mut Damus) {
&mut app.decks_cache, &mut app.decks_cache,
&app.accounts, &app.accounts,
&mut app.support, &mut app.support,
&mut app.theme,
side_panel.action, side_panel.action,
) { ) {
side_panel_action = Some(action); side_panel_action = Some(action);

View File

@@ -6,7 +6,7 @@ use crate::{
use egui::{ use egui::{
epaint::Shadow, epaint::Shadow,
style::{Interaction, Selection, WidgetVisuals, Widgets}, style::{Interaction, Selection, WidgetVisuals, Widgets},
Button, FontFamily, FontId, Rounding, Stroke, Style, TextStyle, Ui, Visuals, FontFamily, FontId, Rounding, Stroke, Style, TextStyle, Visuals,
}; };
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
use strum_macros::EnumIter; use strum_macros::EnumIter;
@@ -28,29 +28,6 @@ pub fn dark_mode(mobile: bool) -> Visuals {
) )
} }
pub fn user_requested_visuals_change(
oled: bool,
cur_darkmode: bool,
ui: &mut Ui,
) -> Option<Visuals> {
if cur_darkmode {
if ui
.add(Button::new("").frame(false))
.on_hover_text("Switch to light mode")
.clicked()
{
return Some(light_mode());
}
} else if ui
.add(Button::new("🌙").frame(false))
.on_hover_text("Switch to dark mode")
.clicked()
{
return Some(dark_mode(oled));
}
None
}
/// Create custom text sizes for any FontSizes /// Create custom text sizes for any FontSizes
pub fn add_custom_style(is_mobile: bool, style: &mut Style) { pub fn add_custom_style(is_mobile: bool, style: &mut Style) {
let font_size = if is_mobile { let font_size = if is_mobile {

View File

@@ -36,6 +36,7 @@ mod route;
mod subscriptions; mod subscriptions;
mod support; mod support;
mod test_data; mod test_data;
mod theme_handler;
mod thread; mod thread;
mod time; mod time;
mod timecache; mod timecache;

View File

@@ -0,0 +1,76 @@
use egui::ThemePreference;
use tracing::{error, info};
use crate::storage::{write_file, DataPath, DataPathType, Directory};
pub struct ThemeHandler {
directory: Directory,
fallback_theme: ThemePreference,
}
const THEME_FILE: &str = "theme.txt";
impl ThemeHandler {
pub fn new(path: &DataPath) -> Self {
let directory = Directory::new(path.path(DataPathType::Setting));
let fallback_theme = ThemePreference::Light;
Self {
directory,
fallback_theme,
}
}
pub fn load(&self) -> ThemePreference {
match self.directory.get_file(THEME_FILE.to_owned()) {
Ok(contents) => match deserialize_theme(contents) {
Some(theme) => theme,
None => {
error!(
"Could not deserialize theme. Using fallback {:?} instead",
self.fallback_theme
);
self.fallback_theme
}
},
Err(e) => {
error!(
"Could not read {} file: {:?}\nUsing fallback {:?} instead",
THEME_FILE, e, self.fallback_theme
);
self.fallback_theme
}
}
}
pub fn save(&self, theme: ThemePreference) {
match write_file(
&self.directory.file_path,
THEME_FILE.to_owned(),
&theme_to_serialized(&theme),
) {
Ok(_) => info!(
"Successfully saved {:?} theme change to {}",
theme, THEME_FILE
),
Err(_) => error!("Could not save {:?} theme change to {}", theme, THEME_FILE),
}
}
}
fn theme_to_serialized(theme: &ThemePreference) -> String {
match theme {
ThemePreference::Dark => "dark",
ThemePreference::Light => "light",
ThemePreference::System => "system",
}
.to_owned()
}
fn deserialize_theme(serialized_theme: String) -> Option<ThemePreference> {
match serialized_theme.as_str() {
"dark" => Some(ThemePreference::Dark),
"light" => Some(ThemePreference::Light),
"system" => Some(ThemePreference::System),
_ => None,
}
}

View File

@@ -1,13 +1,13 @@
use egui::{ use egui::{
vec2, Color32, InnerResponse, Label, Layout, Margin, RichText, ScrollArea, Separator, Stroke, vec2, Button, Color32, InnerResponse, Label, Layout, Margin, RichText, ScrollArea, Separator,
Widget, Stroke, ThemePreference, Widget,
}; };
use tracing::{error, info}; use tracing::{error, info};
use crate::{ use crate::{
accounts::{Accounts, AccountsRoute}, accounts::{Accounts, AccountsRoute},
app::{get_active_columns_mut, get_decks_mut}, app::{get_active_columns_mut, get_decks_mut},
app_style::{self, DECK_ICON_SIZE}, app_style::DECK_ICON_SIZE,
colors, colors,
column::Column, column::Column,
decks::{DecksAction, DecksCache}, decks::{DecksAction, DecksCache},
@@ -15,6 +15,7 @@ use crate::{
nav::SwitchingAction, nav::SwitchingAction,
route::Route, route::Route,
support::Support, support::Support,
theme_handler::ThemeHandler,
user_account::UserAccount, user_account::UserAccount,
Damus, Damus,
}; };
@@ -55,6 +56,7 @@ pub enum SidePanelAction {
NewDeck, NewDeck,
SwitchDeck(usize), SwitchDeck(usize),
EditDeck(usize), EditDeck(usize),
SaveTheme(ThemePreference),
} }
pub struct SidePanelResponse { pub struct SidePanelResponse {
@@ -186,13 +188,33 @@ impl<'a> DesktopSidePanel<'a> {
let pfp_resp = self.pfp_button(ui); let pfp_resp = self.pfp_button(ui);
let settings_resp = ui.add(settings_button(dark_mode)); let settings_resp = ui.add(settings_button(dark_mode));
if let Some(new_visuals) = app_style::user_requested_visuals_change( let save_theme = if let Some((theme, resp)) = match ui.ctx().theme() {
super::is_oled(), egui::Theme::Dark => {
ui.ctx().style().visuals.dark_mode, let resp = ui
ui, .add(Button::new("").frame(false))
) { .on_hover_text("Switch to light mode");
ui.ctx().set_visuals(new_visuals) if resp.clicked() {
} Some((ThemePreference::Light, resp))
} else {
None
}
}
egui::Theme::Light => {
let resp = ui
.add(Button::new("🌙").frame(false))
.on_hover_text("Switch to dark mode");
if resp.clicked() {
Some((ThemePreference::Dark, resp))
} else {
None
}
}
} {
ui.ctx().set_theme(theme);
Some((theme, resp))
} else {
None
};
let support_resp = ui.add(support_button()); let support_resp = ui.add(support_button());
@@ -211,6 +233,11 @@ impl<'a> DesktopSidePanel<'a> {
SidePanelAction::Support, SidePanelAction::Support,
support_resp, support_resp,
)) ))
} else if let Some((theme, resp)) = save_theme {
Some(egui::InnerResponse::new(
SidePanelAction::SaveTheme(theme),
resp,
))
} else { } else {
None None
}; };
@@ -253,6 +280,7 @@ impl<'a> DesktopSidePanel<'a> {
decks_cache: &mut DecksCache, decks_cache: &mut DecksCache,
accounts: &Accounts, accounts: &Accounts,
support: &mut Support, support: &mut Support,
theme_handler: &mut ThemeHandler,
action: SidePanelAction, action: SidePanelAction,
) -> Option<SwitchingAction> { ) -> Option<SwitchingAction> {
let router = get_active_columns_mut(accounts, decks_cache).get_first_router(); let router = get_active_columns_mut(accounts, decks_cache).get_first_router();
@@ -345,6 +373,9 @@ impl<'a> DesktopSidePanel<'a> {
} }
} }
} }
SidePanelAction::SaveTheme(theme) => {
theme_handler.save(theme);
}
} }
switching_response switching_response
} }
@@ -656,6 +687,7 @@ mod preview {
&mut self.app.decks_cache, &mut self.app.decks_cache,
&self.app.accounts, &self.app.accounts,
&mut self.app.support, &mut self.app.support,
&mut self.app.theme,
response.action, response.action,
); );
}); });