Introducing Damus Notedeck: a nostr browser

This splits notedeck into:

- notedeck
- notedeck_chrome
- notedeck_columns

The `notedeck` crate is the library that `notedeck_chrome` and
`notedeck_columns`, use. It contains common functionality related to
notedeck apps such as the NoteCache, ImageCache, etc.

The `notedeck_chrome` crate is the binary and ui chrome. It is
responsible for managing themes, user accounts, signing, data paths,
nostrdb, image caches etc. It will eventually have its own ui which has
yet to be determined.  For now it just manages the browser data, which
is passed to apps via a new struct called `AppContext`.

`notedeck_columns` is our columns app, with less responsibility now that
more things are handled by `notedeck_chrome`

There is still much work left to do before this is a proper browser:

- process isolation
- sandboxing
- etc

This is the beginning of a new era! We're just getting started.

Signed-off-by: William Casarin <jb55@jb55.com>
This commit is contained in:
William Casarin
2024-12-11 04:22:05 -08:00
parent aa14fb092d
commit ec755493d9
146 changed files with 2820 additions and 2794 deletions

View File

@@ -1,38 +1,31 @@
use crate::{
accounts::Accounts,
app_creation::setup_cc,
app_size_handler::AppSizeHandler,
app_style::{dark_mode, light_mode},
args::Args,
args::ColumnsArgs,
column::Columns,
decks::{Decks, DecksCache, FALLBACK_PUBKEY},
draft::Drafts,
filter::FilterState,
frame_history::FrameHistory,
imgcache::ImageCache,
nav,
notecache::NoteCache,
notes_holder::NotesHolderStorage,
profile::Profile,
storage::{self, DataPath, DataPathType, Directory, FileKeyStorage, KeyStorageType},
storage,
subscriptions::{SubKind, Subscriptions},
support::Support,
theme_handler::ThemeHandler,
thread::Thread,
timeline::{self, Timeline},
ui::{self, is_compiled_as_mobile, DesktopSidePanel},
unknowns::UnknownIds,
ui::{self, DesktopSidePanel},
unknowns,
view_state::ViewState,
Result,
};
use notedeck::{Accounts, AppContext, DataPath, DataPathType, FilterState, ImageCache, UnknownIds};
use enostr::{ClientMessage, Keypair, Pubkey, RelayEvent, RelayMessage, RelayPool};
use uuid::Uuid;
use egui::{Context, Frame, Style};
use egui::{Frame, Style};
use egui_extras::{Size, StripBuilder};
use nostrdb::{Config, Ndb, Transaction};
use nostrdb::{Ndb, Transaction};
use std::collections::HashMap;
use std::path::Path;
@@ -48,26 +41,16 @@ pub enum DamusState {
/// We derive Deserialize/Serialize so we can persist app state on shutdown.
pub struct Damus {
state: DamusState,
pub note_cache: NoteCache,
pub pool: RelayPool,
pub decks_cache: DecksCache,
pub ndb: Ndb,
pub view_state: ViewState,
pub unknown_ids: UnknownIds,
pub drafts: Drafts,
pub threads: NotesHolderStorage<Thread>,
pub profiles: NotesHolderStorage<Profile>,
pub img_cache: ImageCache,
pub accounts: Accounts,
pub subscriptions: Subscriptions,
pub app_rect_handler: AppSizeHandler,
pub support: Support,
pub theme: ThemeHandler,
frame_history: crate::frame_history::FrameHistory,
//frame_history: crate::frame_history::FrameHistory,
pub path: DataPath,
// TODO: make these bitflags
pub debug: bool,
pub since_optimize: bool,
@@ -99,21 +82,26 @@ fn handle_key_events(input: &egui::InputState, _pixels_per_point: f32, columns:
}
}
fn try_process_event(damus: &mut Damus, ctx: &egui::Context) -> Result<()> {
fn try_process_event(
damus: &mut Damus,
app_ctx: &mut AppContext<'_>,
ctx: &egui::Context,
) -> Result<()> {
let ppp = ctx.pixels_per_point();
let current_columns = get_active_columns_mut(&damus.accounts, &mut damus.decks_cache);
let current_columns = get_active_columns_mut(app_ctx.accounts, &mut damus.decks_cache);
ctx.input(|i| handle_key_events(i, ppp, current_columns));
let ctx2 = ctx.clone();
let wakeup = move || {
ctx2.request_repaint();
};
damus.pool.keepalive_ping(wakeup);
app_ctx.pool.keepalive_ping(wakeup);
// NOTE: we don't use the while let loop due to borrow issues
#[allow(clippy::while_let_loop)]
loop {
let ev = if let Some(ev) = damus.pool.try_recv() {
let ev = if let Some(ev) = app_ctx.pool.try_recv() {
ev.into_owned()
} else {
break;
@@ -121,16 +109,16 @@ fn try_process_event(damus: &mut Damus, ctx: &egui::Context) -> Result<()> {
match (&ev.event).into() {
RelayEvent::Opened => {
damus
app_ctx
.accounts
.send_initial_filters(&mut damus.pool, &ev.relay);
.send_initial_filters(app_ctx.pool, &ev.relay);
timeline::send_initial_timeline_filters(
&damus.ndb,
app_ctx.ndb,
damus.since_optimize,
get_active_columns_mut(&damus.accounts, &mut damus.decks_cache),
get_active_columns_mut(app_ctx.accounts, &mut damus.decks_cache),
&mut damus.subscriptions,
&mut damus.pool,
app_ctx.pool,
&ev.relay,
);
}
@@ -138,35 +126,35 @@ fn try_process_event(damus: &mut Damus, ctx: &egui::Context) -> Result<()> {
RelayEvent::Closed => warn!("{} connection closed", &ev.relay),
RelayEvent::Error(e) => error!("{}: {}", &ev.relay, e),
RelayEvent::Other(msg) => trace!("other event {:?}", &msg),
RelayEvent::Message(msg) => process_message(damus, &ev.relay, &msg),
RelayEvent::Message(msg) => process_message(damus, app_ctx, &ev.relay, &msg),
}
}
let current_columns = get_active_columns_mut(&damus.accounts, &mut damus.decks_cache);
let current_columns = get_active_columns_mut(app_ctx.accounts, &mut damus.decks_cache);
let n_timelines = current_columns.timelines().len();
for timeline_ind in 0..n_timelines {
let is_ready = {
let timeline = &mut current_columns.timelines[timeline_ind];
timeline::is_timeline_ready(
&damus.ndb,
&mut damus.pool,
&mut damus.note_cache,
app_ctx.ndb,
app_ctx.pool,
app_ctx.note_cache,
timeline,
&damus.accounts.mutefun(),
&app_ctx.accounts.mutefun(),
)
};
if is_ready {
let txn = Transaction::new(&damus.ndb).expect("txn");
let txn = Transaction::new(app_ctx.ndb).expect("txn");
if let Err(err) = Timeline::poll_notes_into_view(
timeline_ind,
current_columns.timelines_mut(),
&damus.ndb,
app_ctx.ndb,
&txn,
&mut damus.unknown_ids,
&mut damus.note_cache,
&damus.accounts.mutefun(),
app_ctx.unknown_ids,
app_ctx.note_cache,
&app_ctx.accounts.mutefun(),
) {
error!("poll_notes_into_view: {err}");
}
@@ -175,22 +163,22 @@ fn try_process_event(damus: &mut Damus, ctx: &egui::Context) -> Result<()> {
}
}
if damus.unknown_ids.ready_to_send() {
unknown_id_send(damus);
if app_ctx.unknown_ids.ready_to_send() {
unknown_id_send(app_ctx.unknown_ids, app_ctx.pool);
}
Ok(())
}
fn unknown_id_send(damus: &mut Damus) {
let filter = damus.unknown_ids.filter().expect("filter");
fn unknown_id_send(unknown_ids: &mut UnknownIds, pool: &mut RelayPool) {
let filter = unknown_ids.filter().expect("filter");
info!(
"Getting {} unknown ids from relays",
damus.unknown_ids.ids().len()
unknown_ids.ids().len()
);
let msg = ClientMessage::req("unknownids".to_string(), filter);
damus.unknown_ids.clear();
damus.pool.send(&msg);
unknown_ids.clear();
pool.send(&msg);
}
#[cfg(feature = "profiling")]
@@ -198,8 +186,11 @@ fn setup_profiling() {
puffin::set_scopes_on(true); // tell puffin to collect data
}
fn update_damus(damus: &mut Damus, ctx: &egui::Context) {
damus.accounts.update(&damus.ndb, &mut damus.pool, ctx); // update user relay and mute lists
fn update_damus(damus: &mut Damus, app_ctx: &mut AppContext<'_>) {
let _ctx = app_ctx.egui.clone();
let ctx = &_ctx;
app_ctx.accounts.update(app_ctx.ndb, app_ctx.pool, ctx); // update user relay and mute lists
match damus.state {
DamusState::Initializing => {
@@ -212,10 +203,10 @@ fn update_damus(damus: &mut Damus, ctx: &egui::Context) {
.subscriptions()
.insert("unknownids".to_string(), SubKind::OneShot);
if let Err(err) = timeline::setup_initial_nostrdb_subs(
&damus.ndb,
&mut damus.note_cache,
app_ctx.ndb,
app_ctx.note_cache,
&mut damus.decks_cache,
&damus.accounts.mutefun(),
&app_ctx.accounts.mutefun(),
) {
warn!("update_damus init: {err}");
}
@@ -224,24 +215,27 @@ fn update_damus(damus: &mut Damus, ctx: &egui::Context) {
DamusState::Initialized => (),
};
if let Err(err) = try_process_event(damus, ctx) {
if let Err(err) = try_process_event(damus, app_ctx, ctx) {
error!("error processing event: {}", err);
}
damus.app_rect_handler.try_save_app_size(ctx);
}
fn process_event(damus: &mut Damus, _subid: &str, event: &str) {
fn process_event(ndb: &Ndb, _subid: &str, event: &str) {
#[cfg(feature = "profiling")]
puffin::profile_function!();
//info!("processing event {}", event);
if let Err(_err) = damus.ndb.process_event(event) {
if let Err(_err) = ndb.process_event(event) {
error!("error processing event {}", event);
}
}
fn handle_eose(damus: &mut Damus, subid: &str, relay_url: &str) -> Result<()> {
fn handle_eose(
damus: &mut Damus,
ctx: &mut AppContext<'_>,
subid: &str,
relay_url: &str,
) -> Result<()> {
let sub_kind = if let Some(sub_kind) = damus.subscriptions().get(subid) {
sub_kind
} else {
@@ -258,29 +252,29 @@ fn handle_eose(damus: &mut Damus, subid: &str, relay_url: &str) -> Result<()> {
// eose on timeline? whatevs
}
SubKind::Initial => {
let txn = Transaction::new(&damus.ndb)?;
UnknownIds::update(
let txn = Transaction::new(ctx.ndb)?;
unknowns::update_from_columns(
&txn,
&mut damus.unknown_ids,
get_active_columns(&damus.accounts, &damus.decks_cache),
&damus.ndb,
&mut damus.note_cache,
ctx.unknown_ids,
get_active_columns(ctx.accounts, &damus.decks_cache),
ctx.ndb,
ctx.note_cache,
);
// this is possible if this is the first time
if damus.unknown_ids.ready_to_send() {
unknown_id_send(damus);
if ctx.unknown_ids.ready_to_send() {
unknown_id_send(ctx.unknown_ids, ctx.pool);
}
}
// oneshot subs just close when they're done
SubKind::OneShot => {
let msg = ClientMessage::close(subid.to_string());
damus.pool.send_to(&msg, relay_url);
ctx.pool.send_to(&msg, relay_url);
}
SubKind::FetchingContactList(timeline_uid) => {
let timeline = if let Some(tl) =
get_active_columns_mut(&damus.accounts, &mut damus.decks_cache)
get_active_columns_mut(ctx.accounts, &mut damus.decks_cache)
.find_timeline_mut(timeline_uid)
{
tl
@@ -326,27 +320,28 @@ fn handle_eose(damus: &mut Damus, subid: &str, relay_url: &str) -> Result<()> {
Ok(())
}
fn process_message(damus: &mut Damus, relay: &str, msg: &RelayMessage) {
fn process_message(damus: &mut Damus, ctx: &mut AppContext<'_>, relay: &str, msg: &RelayMessage) {
match msg {
RelayMessage::Event(subid, ev) => process_event(damus, subid, ev),
RelayMessage::Event(subid, ev) => process_event(ctx.ndb, subid, ev),
RelayMessage::Notice(msg) => warn!("Notice from {}: {}", relay, msg),
RelayMessage::OK(cr) => info!("OK {:?}", cr),
RelayMessage::Eose(sid) => {
if let Err(err) = handle_eose(damus, sid, relay) {
if let Err(err) = handle_eose(damus, ctx, sid, relay) {
error!("error handling eose: {}", err);
}
}
}
}
fn render_damus(damus: &mut Damus, ctx: &Context) {
if ui::is_narrow(ctx) {
render_damus_mobile(ctx, damus);
fn render_damus(damus: &mut Damus, app_ctx: &mut AppContext<'_>) {
if notedeck::ui::is_narrow(app_ctx.egui) {
render_damus_mobile(damus, app_ctx);
} else {
render_damus_desktop(ctx, damus);
render_damus_desktop(damus, app_ctx);
}
ctx.request_repaint_after(Duration::from_secs(1));
// We use this for keeping timestamps and things up to date
app_ctx.egui.request_repaint_after(Duration::from_secs(1));
#[cfg(feature = "profiling")]
puffin_egui::profiler_window(ctx);
@@ -373,91 +368,12 @@ fn determine_key_storage_type() -> KeyStorageType {
impl Damus {
/// Called once before the first frame.
pub fn new<P: AsRef<Path>>(ctx: &egui::Context, data_path: P, args: Vec<String>) -> Self {
pub fn new(ctx: &mut AppContext<'_>, args: &[String]) -> Self {
// arg parsing
let parsed_args = Args::parse(&args);
let is_mobile = parsed_args.is_mobile.unwrap_or(ui::is_compiled_as_mobile());
// Some people have been running notedeck in debug, let's catch that!
if !cfg!(test) && 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
.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
.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, 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 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);
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)
.process_action(&mut unknown_ids, &ndb, &txn);
}
}
if num_keys != 0 {
accounts.select_account(0);
}
// AccountManager will setup the pool on first update
let pool = RelayPool::new();
let account = accounts
let parsed_args = ColumnsArgs::parse(args);
let account = ctx
.accounts
.get_selected_account()
.as_ref()
.map(|a| a.pubkey.bytes());
@@ -466,19 +382,19 @@ impl Damus {
info!("DecksCache: loading from command line arguments");
let mut columns: Columns = Columns::new();
for col in parsed_args.columns {
if let Some(timeline) = col.into_timeline(&ndb, account) {
if let Some(timeline) = col.into_timeline(ctx.ndb, account) {
columns.add_new_timeline_column(timeline);
}
}
columns_to_decks_cache(columns, account)
} else if let Some(decks_cache) = storage::load_decks_cache(&path, &ndb) {
} else if let Some(decks_cache) = crate::storage::load_decks_cache(ctx.path, ctx.ndb) {
info!(
"DecksCache: loading from disk {}",
crate::storage::DECKS_CACHE_FILE
);
decks_cache
} else if let Some(cols) = storage::deserialize_columns(&path, &ndb, account) {
} else if let Some(cols) = storage::deserialize_columns(ctx.path, ctx.ndb, account) {
info!(
"DecksCache: loading from disk at depreciated location {}",
crate::storage::COLUMNS_FILE
@@ -486,79 +402,40 @@ impl Damus {
columns_to_decks_cache(cols, account)
} else {
info!("DecksCache: creating new with demo configuration");
let mut cache = DecksCache::new_with_demo_config(&ndb);
for account in accounts.get_accounts() {
let mut cache = DecksCache::new_with_demo_config(ctx.ndb);
for account in ctx.accounts.get_accounts() {
cache.add_deck_default(account.pubkey);
}
set_demo(&mut cache, &ndb, &mut accounts, &mut unknown_ids);
set_demo(&mut cache, ctx.ndb, ctx.accounts, ctx.unknown_ids);
cache
};
let debug = parsed_args.debug;
let app_rect_handler = AppSizeHandler::new(&path);
let support = Support::new(&path);
let debug = ctx.args.debug;
let support = Support::new(ctx.path);
Self {
pool,
debug,
unknown_ids,
subscriptions: Subscriptions::default(),
since_optimize: parsed_args.since_optimize,
threads: NotesHolderStorage::default(),
profiles: NotesHolderStorage::default(),
drafts: Drafts::default(),
state: DamusState::Initializing,
img_cache: ImageCache::new(imgcache_dir),
note_cache: NoteCache::default(),
textmode: parsed_args.textmode,
ndb,
accounts,
frame_history: FrameHistory::default(),
//frame_history: FrameHistory::default(),
view_state: ViewState::default(),
path,
app_rect_handler,
support,
decks_cache,
theme,
debug,
}
}
pub fn pool_mut(&mut self) -> &mut RelayPool {
&mut self.pool
pub fn columns_mut(&mut self, accounts: &Accounts) -> &mut Columns {
get_active_columns_mut(accounts, &mut self.decks_cache)
}
pub fn ndb(&self) -> &Ndb {
&self.ndb
}
pub fn drafts_mut(&mut self) -> &mut Drafts {
&mut self.drafts
}
pub fn img_cache_mut(&mut self) -> &mut ImageCache {
&mut self.img_cache
}
pub fn accounts(&self) -> &Accounts {
&self.accounts
}
pub fn accounts_mut(&mut self) -> &mut Accounts {
&mut self.accounts
}
pub fn view_state_mut(&mut self) -> &mut ViewState {
&mut self.view_state
}
pub fn columns_mut(&mut self) -> &mut Columns {
get_active_columns_mut(&self.accounts, &mut self.decks_cache)
}
pub fn columns(&self) -> &Columns {
get_active_columns(&self.accounts, &self.decks_cache)
pub fn columns(&self, accounts: &Accounts) -> &Columns {
get_active_columns(accounts, &self.decks_cache)
}
pub fn gen_subid(&self, kind: &SubKind) -> String {
@@ -573,44 +450,25 @@ impl Damus {
let decks_cache = DecksCache::default();
let path = DataPath::new(&data_path);
let theme = ThemeHandler::new(&path);
let imgcache_dir = path.path(DataPathType::Cache).join(ImageCache::rel_dir());
let _ = std::fs::create_dir_all(imgcache_dir.clone());
let debug = true;
let app_rect_handler = AppSizeHandler::new(&path);
let support = Support::new(&path);
let config = Config::new().set_ingester_threads(2);
Self {
debug,
unknown_ids: UnknownIds::default(),
subscriptions: Subscriptions::default(),
since_optimize: true,
threads: NotesHolderStorage::default(),
profiles: NotesHolderStorage::default(),
drafts: Drafts::default(),
state: DamusState::Initializing,
pool: RelayPool::new(),
img_cache: ImageCache::new(imgcache_dir),
note_cache: NoteCache::default(),
textmode: false,
ndb: Ndb::new(
path.path(DataPathType::Db)
.to_str()
.expect("db path should be ok"),
&config,
)
.expect("ndb"),
accounts: Accounts::new(KeyStorageType::None, vec![]),
frame_history: FrameHistory::default(),
//frame_history: FrameHistory::default(),
view_state: ViewState::default(),
path,
app_rect_handler,
support,
decks_cache,
theme,
}
}
@@ -618,14 +476,6 @@ impl Damus {
&mut self.subscriptions.subs
}
pub fn note_cache_mut(&mut self) -> &mut NoteCache {
&mut self.note_cache
}
pub fn unknown_ids_mut(&mut self) -> &mut UnknownIds {
&mut self.unknown_ids
}
pub fn threads(&self) -> &NotesHolderStorage<Thread> {
&self.threads
}
@@ -633,10 +483,6 @@ impl Damus {
pub fn threads_mut(&mut self) -> &mut NotesHolderStorage<Thread> {
&mut self.threads
}
pub fn note_cache(&self) -> &NoteCache {
&self.note_cache
}
}
/*
@@ -648,17 +494,20 @@ fn circle_icon(ui: &mut egui::Ui, openness: f32, response: &egui::Response) {
}
*/
fn render_damus_mobile(ctx: &egui::Context, app: &mut Damus) {
fn render_damus_mobile(app: &mut Damus, app_ctx: &mut AppContext<'_>) {
let _ctx = app_ctx.egui.clone();
let ctx = &_ctx;
#[cfg(feature = "profiling")]
puffin::profile_function!();
//let routes = app.timelines[0].routes.clone();
main_panel(&ctx.style(), ui::is_narrow(ctx)).show(ctx, |ui| {
if !app.columns().columns().is_empty()
&& nav::render_nav(0, app, ui).process_render_nav_response(app)
main_panel(&ctx.style(), notedeck::ui::is_narrow(ctx)).show(ctx, |ui| {
if !app.columns(app_ctx.accounts).columns().is_empty()
&& nav::render_nav(0, app, app_ctx, ui).process_render_nav_response(app, app_ctx)
{
storage::save_decks_cache(&app.path, &app.decks_cache);
storage::save_decks_cache(app_ctx.path, &app.decks_cache);
}
});
}
@@ -677,13 +526,16 @@ fn main_panel(style: &Style, narrow: bool) -> egui::CentralPanel {
})
}
fn render_damus_desktop(ctx: &egui::Context, app: &mut Damus) {
fn render_damus_desktop(app: &mut Damus, app_ctx: &mut AppContext<'_>) {
let _ctx = app_ctx.egui.clone();
let ctx = &_ctx;
#[cfg(feature = "profiling")]
puffin::profile_function!();
let screen_size = ctx.screen_rect().width();
let calc_panel_width = (screen_size
/ get_active_columns(&app.accounts, &app.decks_cache).num_columns() as f32)
/ get_active_columns(app_ctx.accounts, &app.decks_cache).num_columns() as f32)
- 30.0;
let min_width = 320.0;
let need_scroll = calc_panel_width < min_width;
@@ -693,24 +545,24 @@ fn render_damus_desktop(ctx: &egui::Context, app: &mut Damus) {
Size::remainder()
};
main_panel(&ctx.style(), ui::is_narrow(ctx)).show(ctx, |ui| {
main_panel(&ctx.style(), notedeck::ui::is_narrow(ctx)).show(ctx, |ui| {
ui.spacing_mut().item_spacing.x = 0.0;
if need_scroll {
egui::ScrollArea::horizontal().show(ui, |ui| {
timelines_view(ui, panel_sizes, app);
timelines_view(ui, panel_sizes, app, app_ctx);
});
} else {
timelines_view(ui, panel_sizes, app);
timelines_view(ui, panel_sizes, app, app_ctx);
}
});
}
fn timelines_view(ui: &mut egui::Ui, sizes: Size, app: &mut Damus) {
fn timelines_view(ui: &mut egui::Ui, sizes: Size, app: &mut Damus, ctx: &mut AppContext<'_>) {
StripBuilder::new(ui)
.size(Size::exact(ui::side_panel::SIDE_PANEL_WIDTH))
.sizes(
sizes,
get_active_columns(&app.accounts, &app.decks_cache).num_columns(),
get_active_columns(ctx.accounts, &app.decks_cache).num_columns(),
)
.clip(true)
.horizontal(|mut strip| {
@@ -718,9 +570,9 @@ fn timelines_view(ui: &mut egui::Ui, sizes: Size, app: &mut Damus) {
strip.cell(|ui| {
let rect = ui.available_rect_before_wrap();
let side_panel = DesktopSidePanel::new(
&app.ndb,
&mut app.img_cache,
app.accounts.get_selected_account(),
ctx.ndb,
ctx.img_cache,
ctx.accounts.get_selected_account(),
&app.decks_cache,
)
.show(ui);
@@ -728,9 +580,9 @@ fn timelines_view(ui: &mut egui::Ui, sizes: Size, app: &mut Damus) {
if side_panel.response.clicked() || side_panel.response.secondary_clicked() {
if let Some(action) = DesktopSidePanel::perform_action(
&mut app.decks_cache,
&app.accounts,
ctx.accounts,
&mut app.support,
&mut app.theme,
ctx.theme,
side_panel.action,
) {
side_panel_action = Some(action);
@@ -747,15 +599,15 @@ fn timelines_view(ui: &mut egui::Ui, sizes: Size, app: &mut Damus) {
let mut save_cols = false;
if let Some(action) = side_panel_action {
save_cols = save_cols || action.process(app);
save_cols = save_cols || action.process(app, ctx);
}
let num_cols = app.columns().num_columns();
let num_cols = app.columns(ctx.accounts).num_columns();
let mut responses = Vec::with_capacity(num_cols);
for col_index in 0..num_cols {
strip.cell(|ui| {
let rect = ui.available_rect_before_wrap();
responses.push(nav::render_nav(col_index, app, ui));
responses.push(nav::render_nav(col_index, app, ctx, ui));
// vertical line
ui.painter().vline(
@@ -769,27 +621,23 @@ fn timelines_view(ui: &mut egui::Ui, sizes: Size, app: &mut Damus) {
}
for response in responses {
let save = response.process_render_nav_response(app);
let save = response.process_render_nav_response(app, ctx);
save_cols = save_cols || save;
}
if save_cols {
storage::save_decks_cache(&app.path, &app.decks_cache);
storage::save_decks_cache(ctx.path, &app.decks_cache);
}
});
}
impl eframe::App for Damus {
/// 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);
}
/// Called each time the UI needs repainting, which may be many times per second.
/// Put your widgets into a `SidePanel`, `TopPanel`, `CentralPanel`, `Window` or `Area`.
fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
self.frame_history
impl notedeck::App for Damus {
fn update(&mut self, ctx: &mut AppContext<'_>) {
/*
self.app
.frame_history
.on_new_frame(ctx.input(|i| i.time), frame.info().cpu_usage);
*/
#[cfg(feature = "profiling")]
puffin::GlobalProfiler::lock().new_frame();