Merge remote-tracking branch 'github/virtual-list'

This commit is contained in:
William Casarin
2024-04-28 17:55:29 -07:00
7 changed files with 143 additions and 103 deletions

31
Cargo.lock generated
View File

@@ -1055,7 +1055,7 @@ dependencies = [
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
"web-time",
"web-time 0.2.4",
"wgpu",
"winapi",
"winit",
@@ -1090,7 +1090,7 @@ dependencies = [
"puffin",
"thiserror",
"type-map",
"web-time",
"web-time 0.2.4",
"wgpu",
"winit",
]
@@ -1107,7 +1107,7 @@ dependencies = [
"puffin",
"raw-window-handle 0.6.0",
"smithay-clipboard",
"web-time",
"web-time 0.2.4",
"webbrowser",
"winit",
]
@@ -1145,6 +1145,16 @@ dependencies = [
"web-sys",
]
[[package]]
name = "egui_virtual_list"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "142d3a0ad2ae4743e323ad1cb384bffd45abc36dac6f9833f0980c2b4d76af1a"
dependencies = [
"egui",
"web-time 1.1.0",
]
[[package]]
name = "ehttp"
version = "0.2.0"
@@ -2580,6 +2590,7 @@ dependencies = [
"eframe",
"egui",
"egui_extras",
"egui_virtual_list",
"ehttp 0.2.0",
"enostr",
"env_logger 0.10.2",
@@ -3059,7 +3070,7 @@ dependencies = [
"puffin",
"time",
"vec1",
"web-time",
"web-time 0.2.4",
]
[[package]]
@@ -4679,6 +4690,16 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "web-time"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "webbrowser"
version = "0.8.12"
@@ -5156,7 +5177,7 @@ dependencies = [
"wayland-protocols",
"wayland-protocols-plasma",
"web-sys",
"web-time",
"web-time 0.2.4",
"windows-sys 0.48.0",
"x11-dl",
"x11rb 0.13.0",

View File

@@ -39,6 +39,7 @@ nostr-sdk = "0.29.0"
strum = "0.26"
strum_macros = "0.26"
bitflags = "2.5.0"
egui_virtual_list = "0.3.0"
[features]

View File

@@ -30,7 +30,7 @@ $ ./target/release/notedeck "$(cat queries/timeline.json)" "$(cat queries/notifi
First, install [nix][nix] if you don't have it.
The `shell.nix` provides a reproducible build environment for android and rust. I recommend using [direnv][direnv] to load this environment when you `cd` into the directory.
The `shell.nix` provides a reproducible build environment, mainly for android but it also includes rust tools if you don't have those installed. It will likely work without nix if you are just looking to do non-android dev and have the rust toolchain already installed. If you decide to use nix, I recommend using [direnv][direnv] to load the nix shell environment when you `cd` into the directory.
If you don't have [direnv][direnv], enter the dev shell via:

View File

@@ -1,4 +1,4 @@
[{"limit": 100,
[{"limit": 1000,
"kinds": [
1
],

View File

@@ -1 +1 @@
[{"limit": 100, "kinds":[1], "#p": ["32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245"]}]
[{"limit": 1000, "kinds":[1], "#p": ["32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245"]}]

View File

@@ -5,18 +5,16 @@ use crate::frame_history::FrameHistory;
use crate::imgcache::ImageCache;
use crate::notecache::NoteCache;
use crate::timeline;
use crate::ui;
use crate::timeline::{NoteRef, Timeline};
use crate::ui::is_mobile;
use crate::Result;
use egui::containers::scroll_area::ScrollBarVisibility;
use egui::{Context, Frame, Margin, Style};
use egui_extras::{Size, StripBuilder};
use enostr::{ClientMessage, Filter, Pubkey, RelayEvent, RelayMessage};
use nostrdb::{BlockType, Config, Mention, Ndb, Note, NoteKey, Subscription, Transaction};
use nostrdb::{BlockType, Config, Mention, Ndb, Note, NoteKey, Transaction};
use std::cmp::Ordering;
use std::collections::{HashMap, HashSet};
use std::hash::Hash;
use std::path::Path;
@@ -31,47 +29,6 @@ pub enum DamusState {
Initialized,
}
#[derive(Debug, Eq, PartialEq, Copy, Clone)]
pub struct NoteRef {
pub key: NoteKey,
pub created_at: u64,
}
impl Ord for NoteRef {
fn cmp(&self, other: &Self) -> Ordering {
match self.created_at.cmp(&other.created_at) {
Ordering::Equal => self.key.cmp(&other.key),
Ordering::Less => Ordering::Greater,
Ordering::Greater => Ordering::Less,
}
}
}
impl PartialOrd for NoteRef {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
struct Timeline {
pub filter: Vec<Filter>,
pub notes: Vec<NoteRef>,
pub subscription: Option<Subscription>,
}
impl Timeline {
pub fn new(filter: Vec<Filter>) -> Self {
let notes: Vec<NoteRef> = Vec::with_capacity(1000);
let subscription: Option<Subscription> = None;
Timeline {
filter,
notes,
subscription,
}
}
}
/// We derive Deserialize/Serialize so we can persist app state on shutdown.
pub struct Damus {
state: DamusState,
@@ -80,7 +37,7 @@ pub struct Damus {
pool: RelayPool,
pub textmode: bool,
timelines: Vec<Timeline>,
pub timelines: Vec<Timeline>,
pub img_cache: ImageCache,
pub ndb: Ndb,
@@ -557,52 +514,6 @@ fn circle_icon(ui: &mut egui::Ui, openness: f32, response: &egui::Response) {
}
*/
fn render_notes(ui: &mut egui::Ui, damus: &mut Damus, timeline: usize) -> Result<()> {
#[cfg(feature = "profiling")]
puffin::profile_function!();
let num_notes = damus.timelines[timeline].notes.len();
let txn = Transaction::new(&damus.ndb)?;
for i in 0..num_notes {
let note_key = damus.timelines[timeline].notes[i].key;
let note = if let Ok(note) = damus.ndb.get_note_by_key(&txn, note_key) {
note
} else {
warn!("failed to query note {:?}", note_key);
continue;
};
let note_ui = ui::Note::new(damus, &note);
ui.add(note_ui);
ui.add(egui::Separator::default().spacing(0.0));
}
Ok(())
}
fn timeline_view(ui: &mut egui::Ui, app: &mut Damus, timeline: usize) {
//padding(4.0, ui, |ui| ui.heading("Notifications"));
/*
let font_id = egui::TextStyle::Body.resolve(ui.style());
let row_height = ui.fonts(|f| f.row_height(&font_id)) + ui.spacing().item_spacing.y;
*/
egui::ScrollArea::vertical()
.scroll_bar_visibility(ScrollBarVisibility::AlwaysVisible)
//.auto_shrink([false; 2])
/*
.show_viewport(ui, |ui, viewport| {
render_notes_in_viewport(ui, app, viewport, row_height, font_id);
});
*/
.show(ui, |ui| {
ui.spacing_mut().item_spacing.y = 0.0;
ui.spacing_mut().item_spacing.x = 4.0;
let _ = render_notes(ui, app, timeline);
});
}
fn top_panel(ctx: &egui::Context) -> egui::TopBottomPanel {
let top_margin = egui::Margin {
top: 4.0,
@@ -684,7 +595,7 @@ fn render_damus_mobile(ctx: &egui::Context, app: &mut Damus) {
puffin::profile_function!();
main_panel(&ctx.style()).show(ctx, |ui| {
timeline_view(ui, app, 0);
timeline::timeline_view(ui, app, 0);
});
}
@@ -713,7 +624,7 @@ fn render_damus_desktop(ctx: &egui::Context, app: &mut Damus) {
if app.timelines.len() == 1 {
main_panel(&ctx.style()).show(ctx, |ui| {
timeline_view(ui, app, 0);
timeline::timeline_view(ui, app, 0);
});
return;
@@ -737,7 +648,7 @@ fn timelines_view(ui: &mut egui::Ui, sizes: Size, app: &mut Damus, timelines: us
.clip(true)
.horizontal(|mut strip| {
for timeline_ind in 0..timelines {
strip.cell(|ui| timeline_view(ui, app, timeline_ind));
strip.cell(|ui| timeline::timeline_view(ui, app, timeline_ind));
}
});
}

View File

@@ -1,3 +1,110 @@
use crate::{ui, Damus};
use egui::containers::scroll_area::ScrollBarVisibility;
use egui_virtual_list::VirtualList;
use enostr::Filter;
use nostrdb::{NoteKey, Subscription, Transaction};
use std::cmp::Ordering;
use std::sync::{Arc, Mutex};
use log::warn;
#[derive(Debug, Eq, PartialEq, Copy, Clone)]
pub struct NoteRef {
pub key: NoteKey,
pub created_at: u64,
}
impl Ord for NoteRef {
fn cmp(&self, other: &Self) -> Ordering {
match self.created_at.cmp(&other.created_at) {
Ordering::Equal => self.key.cmp(&other.key),
Ordering::Less => Ordering::Greater,
Ordering::Greater => Ordering::Less,
}
}
}
impl PartialOrd for NoteRef {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
pub struct Timeline {
pub filter: Vec<Filter>,
pub notes: Vec<NoteRef>,
/// Our nostrdb subscription
pub subscription: Option<Subscription>,
/// State for our virtual list egui widget
pub list: Arc<Mutex<VirtualList>>,
}
impl Timeline {
pub fn new(filter: Vec<Filter>) -> Self {
let notes: Vec<NoteRef> = Vec::with_capacity(1000);
let subscription: Option<Subscription> = None;
let list = Arc::new(Mutex::new(VirtualList::new()));
Timeline {
filter,
notes,
subscription,
list,
}
}
}
pub fn timeline_view(ui: &mut egui::Ui, app: &mut Damus, timeline: usize) {
//padding(4.0, ui, |ui| ui.heading("Notifications"));
/*
let font_id = egui::TextStyle::Body.resolve(ui.style());
let row_height = ui.fonts(|f| f.row_height(&font_id)) + ui.spacing().item_spacing.y;
*/
egui::ScrollArea::vertical()
.scroll_bar_visibility(ScrollBarVisibility::AlwaysVisible)
//.auto_shrink([false; 2])
/*
.show_viewport(ui, |ui, viewport| {
render_notes_in_viewport(ui, app, viewport, row_height, font_id);
});
*/
.show(ui, |ui| {
let len = app.timelines[timeline].notes.len();
let list = app.timelines[timeline].list.clone();
list.lock()
.unwrap()
.ui_custom_layout(ui, len, |ui, start_index| {
ui.spacing_mut().item_spacing.y = 0.0;
ui.spacing_mut().item_spacing.x = 4.0;
let note_key = app.timelines[timeline].notes[start_index].key;
let txn = if let Ok(txn) = Transaction::new(&app.ndb) {
txn
} else {
warn!("failed to create transaction for {:?}", note_key);
return 0;
};
let note = if let Ok(note) = app.ndb.get_note_by_key(&txn, note_key) {
note
} else {
warn!("failed to query note {:?}", note_key);
return 0;
};
let note_ui = ui::Note::new(app, &note);
ui.add(note_ui);
ui.add(egui::Separator::default().spacing(0.0));
1
});
});
}
pub fn merge_sorted_vecs<T: Ord + Copy>(vec1: &[T], vec2: &[T]) -> Vec<T> {
let mut merged = Vec::with_capacity(vec1.len() + vec2.len());
let mut i = 0;