commit 835851ee5243f90f994b386b40ef3e28941d355a parent c401f4c48402f1414309932b8d0620008c1aab14 Author: William Casarin <jb55@jb55.com> Date: Mon, 20 Jan 2025 15:51:03 -0800 Merge remote-tracking branch 'pr/656' Diffstat:
20 files changed, 449 insertions(+), 451 deletions(-)
diff --git a/.gitignore b/.gitignore @@ -17,3 +17,4 @@ scripts/macos_build_secrets.sh *.txt /tags *.mdb +.idea/+ \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock @@ -2495,6 +2495,7 @@ version = "0.1.0" dependencies = [ "base32", "dirs", + "eframe", "egui", "enostr", "hex", @@ -2502,6 +2503,7 @@ dependencies = [ "nostrdb", "poll-promise", "puffin 0.19.1 (git+https://github.com/jb55/puffin?rev=70ff86d5503815219b01a009afd3669b7903a057)", + "puffin_egui", "security-framework", "serde", "serde_json", @@ -2523,9 +2525,7 @@ dependencies = [ "eframe", "egui", "egui_extras", - "enostr", "log", - "nostrdb", "notedeck", "notedeck_columns", "puffin 0.19.1 (git+https://github.com/jb55/puffin?rev=70ff86d5503815219b01a009afd3669b7903a057)", diff --git a/crates/notedeck/Cargo.toml b/crates/notedeck/Cargo.toml @@ -12,6 +12,7 @@ strum_macros = { workspace = true } dirs = { workspace = true } enostr = { workspace = true } egui = { workspace = true } +eframe = { workspace = true } image = { workspace = true } base32 = { workspace = true } poll-promise = { workspace = true } @@ -22,6 +23,7 @@ serde = { workspace = true } hex = { workspace = true } thiserror = { workspace = true } puffin = { workspace = true, optional = true } +puffin_egui = { workspace = true, optional = true } sha2 = { workspace = true } [dev-dependencies] @@ -31,4 +33,4 @@ tempfile = { workspace = true } security-framework = { workspace = true } [features] -profiling = ["puffin"] +profiling = ["puffin", "puffin_egui"] diff --git a/crates/notedeck/src/app.rs b/crates/notedeck/src/app.rs @@ -1,5 +1,236 @@ -use crate::AppContext; +use crate::persist::{AppSizeHandler, ZoomHandler}; +use crate::{ + Accounts, AppContext, Args, DataPath, DataPathType, Directory, FileKeyStorage, ImageCache, + KeyStorageType, NoteCache, ThemeHandler, UnknownIds, +}; +use egui::ThemePreference; +use enostr::RelayPool; +use nostrdb::{Config, Ndb, Transaction}; +use std::cell::RefCell; +use std::path::Path; +use std::rc::Rc; +use tracing::{error, info}; pub trait App { fn update(&mut self, ctx: &mut AppContext<'_>, ui: &mut egui::Ui); } + +/// Main notedeck app framework +pub struct Notedeck { + ndb: Ndb, + img_cache: ImageCache, + unknown_ids: UnknownIds, + pool: RelayPool, + note_cache: NoteCache, + accounts: Accounts, + path: DataPath, + args: Args, + theme: ThemeHandler, + app: Option<Rc<RefCell<dyn App>>>, + zoom: ZoomHandler, + app_size: AppSizeHandler, +} + +fn margin_top(narrow: bool) -> f32 { + #[cfg(target_os = "android")] + { + // FIXME - query the system bar height and adjust more precisely + let _ = narrow; // suppress compiler warning on android + 40.0 + } + #[cfg(not(target_os = "android"))] + { + if narrow { + 50.0 + } else { + 0.0 + } + } +} + +/// Our chrome, which is basically nothing +fn main_panel(style: &egui::Style, narrow: bool) -> egui::CentralPanel { + let inner_margin = egui::Margin { + top: margin_top(narrow), + left: 0.0, + right: 0.0, + bottom: 0.0, + }; + egui::CentralPanel::default().frame(egui::Frame { + inner_margin, + fill: style.visuals.panel_fill, + ..Default::default() + }) +} + +impl eframe::App for Notedeck { + fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { + #[cfg(feature = "profiling")] + puffin::GlobalProfiler::lock().new_frame(); + + main_panel(&ctx.style(), crate::ui::is_narrow(ctx)).show(ctx, |ui| { + // render app + if let Some(app) = &self.app { + let app = app.clone(); + app.borrow_mut().update(&mut self.app_context(), ui); + } + }); + + self.zoom.try_save_zoom_factor(ctx); + self.app_size.try_save_app_size(ctx); + + if self.args.relay_debug && self.pool.debug.is_none() { + self.pool.use_debug(); + } + + #[cfg(feature = "profiling")] + puffin_egui::profiler_window(ctx); + } + + /// Called by the framework to save state before shutdown. + fn save(&mut self, _storage: &mut dyn eframe::Storage) { + //eframe::set_value(storage, eframe::APP_KEY, self); + } +} + +#[cfg(feature = "profiling")] +fn setup_profiling() { + puffin::set_scopes_on(true); // tell puffin to collect data +} + +impl Notedeck { + pub fn new<P: AsRef<Path>>(ctx: &egui::Context, data_path: P, args: &[String]) -> Self { + #[cfg(feature = "profiling")] + setup_profiling(); + + let parsed_args = Args::parse(args); + + let data_path = parsed_args + .datapath + .clone() + .unwrap_or(data_path.as_ref().to_str().expect("db path ok").to_string()); + let path = DataPath::new(&data_path); + let dbpath_str = parsed_args + .dbpath + .clone() + .unwrap_or_else(|| path.path(DataPathType::Db).to_str().unwrap().to_string()); + + let _ = std::fs::create_dir_all(&dbpath_str); + + let img_cache_dir = path.path(DataPathType::Cache).join(ImageCache::rel_dir()); + let _ = std::fs::create_dir_all(img_cache_dir.clone()); + + let map_size = if cfg!(target_os = "windows") { + // 16 Gib on windows because it actually creates the file + 1024usize * 1024usize * 1024usize * 16usize + } else { + // 1 TiB for everything else since its just virtually mapped + 1024usize * 1024usize * 1024usize * 1024usize + }; + + let theme = ThemeHandler::new(&path); + let config = Config::new().set_ingester_threads(4).set_mapsize(map_size); + + let keystore = if parsed_args.use_keystore { + let keys_path = path.path(DataPathType::Keys); + let selected_key_path = path.path(DataPathType::SelectedKey); + KeyStorageType::FileSystem(FileKeyStorage::new( + Directory::new(keys_path), + Directory::new(selected_key_path), + )) + } else { + KeyStorageType::None + }; + + let mut accounts = Accounts::new(keystore, parsed_args.relays.clone()); + + let num_keys = parsed_args.keys.len(); + + let mut unknown_ids = UnknownIds::default(); + let ndb = Ndb::new(&dbpath_str, &config).expect("ndb"); + + { + let txn = Transaction::new(&ndb).expect("txn"); + for key in &parsed_args.keys { + info!("adding account: {}", &key.pubkey); + accounts + .add_account(key.clone()) + .process_action(&mut unknown_ids, &ndb, &txn); + } + } + + if num_keys != 0 { + accounts.select_account(0); + } + + // AccountManager will setup the pool on first update + let mut pool = RelayPool::new(); + { + let ctx = ctx.clone(); + if let Err(err) = pool.add_multicast_relay(move || ctx.request_repaint()) { + error!("error setting up multicast relay: {err}"); + } + } + + let img_cache = ImageCache::new(img_cache_dir); + let note_cache = NoteCache::default(); + let unknown_ids = UnknownIds::default(); + let zoom = ZoomHandler::new(&path); + let app_size = AppSizeHandler::new(&path); + + if let Some(z) = zoom.get_zoom_factor() { + ctx.set_zoom_factor(z); + } + + // migrate + if let Err(e) = img_cache.migrate_v0() { + error!("error migrating image cache: {e}"); + } + + Self { + ndb, + img_cache, + unknown_ids, + pool, + note_cache, + accounts, + path: path.clone(), + args: parsed_args, + theme, + app: None, + zoom, + app_size, + } + } + + pub fn app<A: App + 'static>(mut self, app: A) -> Self { + self.set_app(app); + self + } + + pub fn app_context(&mut self) -> AppContext<'_> { + AppContext { + ndb: &mut self.ndb, + img_cache: &mut self.img_cache, + unknown_ids: &mut self.unknown_ids, + pool: &mut self.pool, + note_cache: &mut self.note_cache, + accounts: &mut self.accounts, + path: &self.path, + args: &self.args, + theme: &mut self.theme, + } + } + + pub fn set_app<T: App + 'static>(&mut self, app: T) { + self.app = Some(Rc::new(RefCell::new(app))); + } + + pub fn args(&self) -> &Args { + &self.args + } + + pub fn theme(&self) -> ThemePreference { + self.theme.load() + } +} diff --git a/crates/notedeck/src/lib.rs b/crates/notedeck/src/lib.rs @@ -9,19 +9,20 @@ mod imgcache; mod muted; pub mod note; mod notecache; +mod persist; mod result; pub mod storage; mod style; pub mod theme; -mod theme_handler; mod time; mod timecache; +mod timed_serializer; pub mod ui; mod unknowns; mod user_account; pub use accounts::{AccountData, Accounts, AccountsAction, AddAccountAction, SwitchAccountAction}; -pub use app::App; +pub use app::{App, Notedeck}; pub use args::Args; pub use context::AppContext; pub use error::{Error, FilterError}; @@ -31,13 +32,13 @@ pub use imgcache::ImageCache; pub use muted::{MuteFun, Muted}; pub use note::{NoteRef, RootIdError, RootNoteId, RootNoteIdBuf}; pub use notecache::{CachedNote, NoteCache}; +pub use persist::*; pub use result::Result; pub use storage::{ DataPath, DataPathType, Directory, FileKeyStorage, KeyStorageResponse, KeyStorageType, }; pub use style::NotedeckTextStyle; pub use theme::ColorTheme; -pub use theme_handler::ThemeHandler; pub use time::time_ago_since; pub use timecache::TimeCached; pub use unknowns::{get_unknown_note_ids, NoteRefsUnkIdAction, SingleUnkIdAction, UnknownIds}; diff --git a/crates/notedeck/src/persist/app_size.rs b/crates/notedeck/src/persist/app_size.rs @@ -0,0 +1,30 @@ +use std::time::Duration; + +use egui::Context; + +use crate::timed_serializer::TimedSerializer; +use crate::{DataPath, DataPathType}; + +pub struct AppSizeHandler { + serializer: TimedSerializer<egui::Vec2>, +} + +impl AppSizeHandler { + pub fn new(path: &DataPath) -> Self { + let serializer = + TimedSerializer::new(path, DataPathType::Setting, "app_size.json".to_owned()) + .with_delay(Duration::from_millis(500)); + + Self { serializer } + } + + pub fn try_save_app_size(&mut self, ctx: &Context) { + // There doesn't seem to be a way to check if user is resizing window, so if the rect is different than last saved, we'll wait DELAY before saving again to avoid spamming io + let cur_size = ctx.input(|i| i.screen_rect.size()); + self.serializer.try_save(cur_size); + } + + pub fn get_app_size(&self) -> Option<egui::Vec2> { + self.serializer.get_item() + } +} diff --git a/crates/notedeck/src/persist/mod.rs b/crates/notedeck/src/persist/mod.rs @@ -0,0 +1,7 @@ +mod app_size; +mod theme_handler; +mod zoom; + +pub use app_size::AppSizeHandler; +pub use theme_handler::ThemeHandler; +pub use zoom::ZoomHandler; diff --git a/crates/notedeck/src/theme_handler.rs b/crates/notedeck/src/persist/theme_handler.rs diff --git a/crates/notedeck/src/persist/zoom.rs b/crates/notedeck/src/persist/zoom.rs @@ -0,0 +1,26 @@ +use crate::{DataPath, DataPathType}; +use egui::Context; + +use crate::timed_serializer::TimedSerializer; + +pub struct ZoomHandler { + serializer: TimedSerializer<f32>, +} + +impl ZoomHandler { + pub fn new(path: &DataPath) -> Self { + let serializer = + TimedSerializer::new(path, DataPathType::Setting, "zoom_level.json".to_owned()); + + Self { serializer } + } + + pub fn try_save_zoom_factor(&mut self, ctx: &Context) { + let cur_zoom_level = ctx.zoom_factor(); + self.serializer.try_save(cur_zoom_level); + } + + pub fn get_zoom_factor(&self) -> Option<f32> { + self.serializer.get_item() + } +} diff --git a/crates/notedeck/src/timed_serializer.rs b/crates/notedeck/src/timed_serializer.rs @@ -0,0 +1,86 @@ +use std::time::{Duration, Instant}; + +use crate::{storage, DataPath, DataPathType, Directory}; +use serde::{Deserialize, Serialize}; +use tracing::info; + +pub struct TimedSerializer<T: PartialEq + Copy + Serialize + for<'de> Deserialize<'de>> { + directory: Directory, + file_name: String, + delay: Duration, + last_saved: Instant, + saved_item: Option<T>, +} + +impl<T: PartialEq + Copy + Serialize + for<'de> Deserialize<'de>> TimedSerializer<T> { + pub fn new(path: &DataPath, path_type: DataPathType, file_name: String) -> Self { + let directory = Directory::new(path.path(path_type)); + let delay = Duration::from_millis(1000); + + Self { + directory, + file_name, + delay, + last_saved: Instant::now() - delay, + saved_item: None, + } + } + + pub fn with_delay(mut self, delay: Duration) -> Self { + self.delay = delay; + self + } + + fn should_save(&self) -> bool { + self.last_saved.elapsed() >= self.delay + } + + // returns whether successful + pub fn try_save(&mut self, cur_item: T) -> bool { + if self.should_save() { + if let Some(saved_item) = self.saved_item { + if saved_item != cur_item { + return self.save(cur_item); + } + } else { + return self.save(cur_item); + } + } + false + } + + pub fn get_item(&self) -> Option<T> { + if self.saved_item.is_some() { + return self.saved_item; + } + + if let Ok(file_contents) = self.directory.get_file(self.file_name.clone()) { + if let Ok(item) = serde_json::from_str::<T>(&file_contents) { + return Some(item); + } + } else { + info!("Could not find file {}", self.file_name); + } + + None + } + + fn save(&mut self, cur_item: T) -> bool { + if let Ok(serialized_item) = serde_json::to_string(&cur_item) { + if storage::write_file( + &self.directory.file_path, + self.file_name.clone(), + &serialized_item, + ) + .is_ok() + { + info!("wrote item {}", serialized_item); + self.last_saved = Instant::now(); + self.saved_item = Some(cur_item); + return true; + } + } + + false + } +} diff --git a/crates/notedeck_chrome/Cargo.toml b/crates/notedeck_chrome/Cargo.toml @@ -12,8 +12,6 @@ description = "The nostr browser" eframe = { workspace = true } egui_extras = { workspace = true } egui = { workspace = true } -enostr = { workspace = true } -nostrdb = { workspace = true } notedeck_columns = { workspace = true } notedeck = { workspace = true } puffin = { workspace = true, optional = true } diff --git a/crates/notedeck_chrome/src/android.rs b/crates/notedeck_chrome/src/android.rs @@ -1,11 +1,16 @@ //#[cfg(target_os = "android")] //use egui_android::run_android; -use crate::app::Notedeck; use notedeck_columns::Damus; use winit::platform::android::activity::AndroidApp; use winit::platform::android::EventLoopBuilderExtAndroid; +use crate::setup::setup_chrome; +use notedeck::Notedeck; +use serde_json::Value; +use std::fs; +use std::path::PathBuf; + #[no_mangle] #[tokio::main] pub async fn android_main(app: AndroidApp) { @@ -50,18 +55,17 @@ pub async fn android_main(app: AndroidApp) { "Damus Notedeck", options, Box::new(move |cc| { - let mut notedeck = Notedeck::new(&cc.egui_ctx, path, &app_args); + let ctx = &cc.egui_ctx; + let mut notedeck = Notedeck::new(ctx, path, &app_args); + setup_chrome(ctx, ¬edeck.args(), notedeck.theme()); + let damus = Damus::new(&mut notedeck.app_context(), &app_args); - notedeck.add_app(damus); + notedeck.set_app(damus); + Ok(Box::new(notedeck)) }), ); } - -use serde_json::Value; -use std::fs; -use std::path::PathBuf; - /* Read args from a config file: - allows use of more interesting args w/o risk of checking them in by mistake diff --git a/crates/notedeck_chrome/src/app.rs b/crates/notedeck_chrome/src/app.rs @@ -1,265 +0,0 @@ -use crate::{app_size::AppSizeHandler, persist_zoom::ZoomHandler, setup::setup_cc, theme}; - -use notedeck::{ - Accounts, AppContext, Args, DataPath, DataPathType, Directory, FileKeyStorage, ImageCache, - KeyStorageType, NoteCache, ThemeHandler, UnknownIds, -}; - -use enostr::RelayPool; -use nostrdb::{Config, Ndb, Transaction}; -use notedeck_columns::ui::relay_debug::RelayDebugView; -use std::cell::RefCell; -use std::path::Path; -use std::rc::Rc; -use tracing::{error, info}; - -/// Our browser app state -pub struct Notedeck { - ndb: Ndb, - img_cache: ImageCache, - unknown_ids: UnknownIds, - pool: RelayPool, - note_cache: NoteCache, - accounts: Accounts, - path: DataPath, - args: Args, - theme: ThemeHandler, - tabs: Tabs, - app_rect_handler: AppSizeHandler, - zoom_handler: ZoomHandler, -} - -fn margin_top(narrow: bool) -> f32 { - #[cfg(target_os = "android")] - { - // FIXME - query the system bar height and adjust more precisely - let _ = narrow; // suppress compiler warning on android - 40.0 - } - #[cfg(not(target_os = "android"))] - { - if narrow { - 50.0 - } else { - 0.0 - } - } -} - -/// Our chrome, which is basically nothing -fn main_panel(style: &egui::Style, narrow: bool) -> egui::CentralPanel { - let inner_margin = egui::Margin { - top: margin_top(narrow), - left: 0.0, - right: 0.0, - bottom: 0.0, - }; - egui::CentralPanel::default().frame(egui::Frame { - inner_margin, - fill: style.visuals.panel_fill, - ..Default::default() - }) -} - -impl eframe::App for Notedeck { - /// Called by the frame work to save state before shutdown. - fn save(&mut self, _storage: &mut dyn eframe::Storage) { - //eframe::set_value(storage, eframe::APP_KEY, self); - } - - fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { - // TODO: render chrome - #[cfg(feature = "profiling")] - puffin::GlobalProfiler::lock().new_frame(); - - main_panel(&ctx.style(), notedeck::ui::is_narrow(ctx)).show(ctx, |ui| { - // render app - if let Some(app) = &self.tabs.app { - let app = app.clone(); - app.borrow_mut().update(&mut self.app_context(), ui); - } - }); - - self.app_rect_handler.try_save_app_size(ctx); - self.zoom_handler.try_save_zoom_factor(ctx); - - if self.args.relay_debug { - if self.pool.debug.is_none() { - self.pool.use_debug(); - } - - if let Some(debug) = &mut self.pool.debug { - RelayDebugView::window(ctx, debug); - } - } - - #[cfg(feature = "profiling")] - puffin_egui::profiler_window(ctx); - } -} - -#[cfg(feature = "profiling")] -fn setup_profiling() { - puffin::set_scopes_on(true); // tell puffin to collect data -} - -impl Notedeck { - pub fn new<P: AsRef<Path>>(ctx: &egui::Context, data_path: P, args: &[String]) -> Self { - #[cfg(feature = "profiling")] - setup_profiling(); - - let parsed_args = Args::parse(args); - let is_mobile = parsed_args - .is_mobile - .unwrap_or(notedeck::ui::is_compiled_as_mobile()); - - // Some people have been running notedeck in debug, let's catch that! - if !parsed_args.tests && cfg!(debug_assertions) && !parsed_args.debug { - println!("--- WELCOME TO DAMUS NOTEDECK! ---"); - println!("It looks like are running notedeck in debug mode, unless you are a developer, this is not likely what you want."); - println!("If you are a developer, run `cargo run -- --debug` to skip this message."); - println!("For everyone else, try again with `cargo run --release`. Enjoy!"); - println!("---------------------------------"); - panic!(); - } - - setup_cc(ctx, is_mobile, parsed_args.light); - - let data_path = parsed_args - .datapath - .clone() - .unwrap_or(data_path.as_ref().to_str().expect("db path ok").to_string()); - let path = DataPath::new(&data_path); - let dbpath_str = parsed_args - .dbpath - .clone() - .unwrap_or_else(|| path.path(DataPathType::Db).to_str().unwrap().to_string()); - - let _ = std::fs::create_dir_all(&dbpath_str); - - let imgcache_dir = path.path(DataPathType::Cache).join(ImageCache::rel_dir()); - let _ = std::fs::create_dir_all(imgcache_dir.clone()); - - let mapsize = if cfg!(target_os = "windows") { - // 16 Gib on windows because it actually creates the file - 1024usize * 1024usize * 1024usize * 16usize - } else { - // 1 TiB for everything else since its just virtually mapped - 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, - theme::dark_mode(notedeck::ui::is_compiled_as_mobile()), - ); - ctx.set_visuals_of(egui::Theme::Light, theme::light_mode()); - - let config = Config::new().set_ingester_threads(4).set_mapsize(mapsize); - - let keystore = if parsed_args.use_keystore { - let keys_path = path.path(DataPathType::Keys); - let selected_key_path = path.path(DataPathType::SelectedKey); - KeyStorageType::FileSystem(FileKeyStorage::new( - Directory::new(keys_path), - Directory::new(selected_key_path), - )) - } else { - KeyStorageType::None - }; - - let mut accounts = Accounts::new(keystore, parsed_args.relays.clone()); - - let num_keys = parsed_args.keys.len(); - - let mut unknown_ids = UnknownIds::default(); - let ndb = Ndb::new(&dbpath_str, &config).expect("ndb"); - - { - let txn = Transaction::new(&ndb).expect("txn"); - for key in &parsed_args.keys { - info!("adding account: {}", &key.pubkey); - accounts - .add_account(key.clone()) - .process_action(&mut unknown_ids, &ndb, &txn); - } - } - - if num_keys != 0 { - accounts.select_account(0); - } - - // AccountManager will setup the pool on first update - let mut pool = RelayPool::new(); - { - let ctx = ctx.clone(); - if let Err(err) = pool.add_multicast_relay(move || ctx.request_repaint()) { - error!("error setting up multicast relay: {err}"); - } - } - - let img_cache = ImageCache::new(imgcache_dir); - let note_cache = NoteCache::default(); - let unknown_ids = UnknownIds::default(); - let tabs = Tabs::new(None); - let app_rect_handler = AppSizeHandler::new(&path); - let zoom_handler = ZoomHandler::new(&path); - - if let Some(zoom_factor) = zoom_handler.get_zoom_factor() { - ctx.set_zoom_factor(zoom_factor); - } - - // migrate - if let Err(e) = img_cache.migrate_v0() { - error!("error migrating image cache: {e}"); - } - - Self { - ndb, - img_cache, - app_rect_handler, - unknown_ids, - pool, - note_cache, - accounts, - path: path.clone(), - args: parsed_args, - theme, - tabs, - zoom_handler, - } - } - - pub fn app_context(&mut self) -> AppContext<'_> { - AppContext { - ndb: &mut self.ndb, - img_cache: &mut self.img_cache, - unknown_ids: &mut self.unknown_ids, - pool: &mut self.pool, - note_cache: &mut self.note_cache, - accounts: &mut self.accounts, - path: &self.path, - args: &self.args, - theme: &mut self.theme, - } - } - - pub fn add_app<T: notedeck::App + 'static>(&mut self, app: T) { - self.tabs.app = Some(Rc::new(RefCell::new(app))); - } -} - -struct Tabs { - app: Option<Rc<RefCell<dyn notedeck::App>>>, -} - -impl Tabs { - pub fn new(app: Option<Rc<RefCell<dyn notedeck::App>>>) -> Self { - Self { app } - } -} diff --git a/crates/notedeck_chrome/src/app_size.rs b/crates/notedeck_chrome/src/app_size.rs @@ -1,31 +0,0 @@ -use std::time::Duration; - -use egui::Context; - -use notedeck::{DataPath, DataPathType}; - -use crate::timed_serializer::TimedSerializer; - -pub struct AppSizeHandler { - serializer: TimedSerializer<egui::Vec2>, -} - -impl AppSizeHandler { - pub fn new(path: &DataPath) -> Self { - let serializer = - TimedSerializer::new(path, DataPathType::Setting, "app_size.json".to_owned()) - .with_delay(Duration::from_millis(500)); - - Self { serializer } - } - - pub fn try_save_app_size(&mut self, ctx: &Context) { - // There doesn't seem to be a way to check if user is resizing window, so if the rect is different than last saved, we'll wait DELAY before saving again to avoid spamming io - let cur_size = ctx.input(|i| i.screen_rect.size()); - self.serializer.try_save(cur_size); - } - - pub fn get_app_size(&self) -> Option<egui::Vec2> { - self.serializer.get_item() - } -} diff --git a/crates/notedeck_chrome/src/lib.rs b/crates/notedeck_chrome/src/lib.rs @@ -1,13 +1,6 @@ -pub mod app_size; pub mod fonts; -pub mod persist_zoom; pub mod setup; pub mod theme; -pub mod timed_serializer; - -mod app; - -pub use app::Notedeck; #[cfg(target_os = "android")] mod android; diff --git a/crates/notedeck_chrome/src/notedeck.rs b/crates/notedeck_chrome/src/notedeck.rs @@ -1,7 +1,8 @@ -#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release -use notedeck_chrome::{setup::generate_native_options, Notedeck}; +#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] +// hide console window on Windows in release +use notedeck_chrome::setup::{generate_native_options, setup_chrome}; -use notedeck::{DataPath, DataPathType}; +use notedeck::{DataPath, DataPathType, Notedeck}; use notedeck_columns::Damus; use tracing_subscriber::EnvFilter; @@ -72,10 +73,13 @@ async fn main() { generate_native_options(path), Box::new(|cc| { let args: Vec<String> = std::env::args().collect(); - let mut notedeck = Notedeck::new(&cc.egui_ctx, base_path, &args); + let ctx = &cc.egui_ctx; + let mut notedeck = Notedeck::new(ctx, base_path, &args); + setup_chrome(ctx, ¬edeck.args(), notedeck.theme()); let damus = Damus::new(&mut notedeck.app_context(), &args); - notedeck.add_app(damus); + // TODO: move "chrome" frame over Damus app somehow + notedeck.set_app(damus); Ok(Box::new(notedeck)) }), diff --git a/crates/notedeck_chrome/src/persist_zoom.rs b/crates/notedeck_chrome/src/persist_zoom.rs @@ -1,26 +0,0 @@ -use egui::Context; -use notedeck::{DataPath, DataPathType}; - -use crate::timed_serializer::TimedSerializer; - -pub struct ZoomHandler { - serializer: TimedSerializer<f32>, -} - -impl ZoomHandler { - pub fn new(path: &DataPath) -> Self { - let serializer = - TimedSerializer::new(path, DataPathType::Setting, "zoom_level.json".to_owned()); - - Self { serializer } - } - - pub fn try_save_zoom_factor(&mut self, ctx: &Context) { - let cur_zoom_level = ctx.zoom_factor(); - self.serializer.try_save(cur_zoom_level); - } - - pub fn get_zoom_factor(&self) -> Option<f32> { - self.serializer.get_item() - } -} diff --git a/crates/notedeck_chrome/src/preview.rs b/crates/notedeck_chrome/src/preview.rs @@ -1,6 +1,5 @@ -use notedeck::DataPath; -use notedeck_chrome::setup::generate_native_options; -use notedeck_chrome::Notedeck; +use notedeck::{DataPath, Notedeck}; +use notedeck_chrome::setup::{generate_native_options, setup_chrome}; use notedeck_columns::ui::configure_deck::ConfigureDeckView; use notedeck_columns::ui::edit_deck::EditDeckView; use notedeck_columns::ui::profile::EditProfileView; @@ -31,9 +30,12 @@ impl PreviewRunner { generate_native_options(path), Box::new(|cc| { let args: Vec<String> = std::env::args().collect(); - let mut notedeck = Notedeck::new(&cc.egui_ctx, &base_path, &args); + let ctx = &cc.egui_ctx; - notedeck.add_app(PreviewApp::new(preview)); + let mut notedeck = Notedeck::new(ctx, &base_path, &args); + setup_chrome(ctx, ¬edeck.args(), notedeck.theme()); + + notedeck.set_app(PreviewApp::new(preview)); Ok(Box::new(notedeck)) }), diff --git a/crates/notedeck_chrome/src/setup.rs b/crates/notedeck_chrome/src/setup.rs @@ -1,9 +1,35 @@ -use crate::{app_size::AppSizeHandler, fonts, theme}; +use crate::{fonts, theme}; use eframe::NativeOptions; -use notedeck::DataPath; +use egui::ThemePreference; +use notedeck::{AppSizeHandler, DataPath}; +use tracing::info; -pub fn setup_cc(ctx: &egui::Context, is_mobile: bool, light: bool) { +pub fn setup_chrome(ctx: &egui::Context, args: ¬edeck::Args, theme: ThemePreference) { + let is_mobile = args + .is_mobile + .unwrap_or(notedeck::ui::is_compiled_as_mobile()); + + // Some people have been running notedeck in debug, let's catch that! + if !args.tests && cfg!(debug_assertions) && !args.debug { + println!("--- WELCOME TO DAMUS NOTEDECK! ---"); + println!("It looks like are running notedeck in debug mode, unless you are a developer, this is not likely what you want."); + println!("If you are a developer, run `cargo run -- --debug` to skip this message."); + println!("For everyone else, try again with `cargo run --release`. Enjoy!"); + println!("---------------------------------"); + panic!(); + } + + ctx.options_mut(|o| { + info!("Loaded theme {:?} from disk", theme); + o.theme_preference = theme; + }); + ctx.set_visuals_of(egui::Theme::Dark, theme::dark_mode(is_mobile)); + ctx.set_visuals_of(egui::Theme::Light, theme::light_mode()); + setup_cc(ctx, is_mobile); +} + +pub fn setup_cc(ctx: &egui::Context, is_mobile: bool) { fonts::setup_fonts(ctx); //ctx.set_pixels_per_point(ctx.pixels_per_point() + UI_SCALE_FACTOR); @@ -14,12 +40,6 @@ pub fn setup_cc(ctx: &egui::Context, is_mobile: bool, light: bool) { egui_extras::install_image_loaders(ctx); - if light { - ctx.set_visuals(theme::light_mode()) - } else { - ctx.set_visuals(theme::dark_mode(is_mobile)); - } - ctx.all_styles_mut(|style| theme::add_custom_style(is_mobile, style)); } diff --git a/crates/notedeck_chrome/src/timed_serializer.rs b/crates/notedeck_chrome/src/timed_serializer.rs @@ -1,86 +0,0 @@ -use std::time::{Duration, Instant}; - -use notedeck::{storage, DataPath, DataPathType, Directory}; -use serde::{Deserialize, Serialize}; -use tracing::info; - -pub struct TimedSerializer<T: PartialEq + Copy + Serialize + for<'de> Deserialize<'de>> { - directory: Directory, - file_name: String, - delay: Duration, - last_saved: Instant, - saved_item: Option<T>, -} - -impl<T: PartialEq + Copy + Serialize + for<'de> Deserialize<'de>> TimedSerializer<T> { - pub fn new(path: &DataPath, path_type: DataPathType, file_name: String) -> Self { - let directory = Directory::new(path.path(path_type)); - let delay = Duration::from_millis(1000); - - Self { - directory, - file_name, - delay, - last_saved: Instant::now() - delay, - saved_item: None, - } - } - - pub fn with_delay(mut self, delay: Duration) -> Self { - self.delay = delay; - self - } - - fn should_save(&self) -> bool { - self.last_saved.elapsed() >= self.delay - } - - // returns whether successful - pub fn try_save(&mut self, cur_item: T) -> bool { - if self.should_save() { - if let Some(saved_item) = self.saved_item { - if saved_item != cur_item { - return self.save(cur_item); - } - } else { - return self.save(cur_item); - } - } - false - } - - pub fn get_item(&self) -> Option<T> { - if self.saved_item.is_some() { - return self.saved_item; - } - - if let Ok(file_contents) = self.directory.get_file(self.file_name.clone()) { - if let Ok(item) = serde_json::from_str::<T>(&file_contents) { - return Some(item); - } - } else { - info!("Could not find file {}", self.file_name); - } - - None - } - - fn save(&mut self, cur_item: T) -> bool { - if let Ok(serialized_item) = serde_json::to_string(&cur_item) { - if storage::write_file( - &self.directory.file_path, - self.file_name.clone(), - &serialized_item, - ) - .is_ok() - { - info!("wrote item {}", serialized_item); - self.last_saved = Instant::now(); - self.saved_item = Some(cur_item); - return true; - } - } - - false - } -}