use egui_virtual_list for rendering
absolutely insane performance increase Fixes: https://github.com/damus-io/notedeck/issues/32 Suggested-by: @lucasmerlin Signed-off-by: William Casarin <jb55@jb55.com>
This commit is contained in:
31
Cargo.lock
generated
31
Cargo.lock
generated
@@ -1055,7 +1055,7 @@ dependencies = [
|
|||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"wasm-bindgen-futures",
|
"wasm-bindgen-futures",
|
||||||
"web-sys",
|
"web-sys",
|
||||||
"web-time",
|
"web-time 0.2.4",
|
||||||
"wgpu",
|
"wgpu",
|
||||||
"winapi",
|
"winapi",
|
||||||
"winit",
|
"winit",
|
||||||
@@ -1090,7 +1090,7 @@ dependencies = [
|
|||||||
"puffin",
|
"puffin",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"type-map",
|
"type-map",
|
||||||
"web-time",
|
"web-time 0.2.4",
|
||||||
"wgpu",
|
"wgpu",
|
||||||
"winit",
|
"winit",
|
||||||
]
|
]
|
||||||
@@ -1107,7 +1107,7 @@ dependencies = [
|
|||||||
"puffin",
|
"puffin",
|
||||||
"raw-window-handle 0.6.0",
|
"raw-window-handle 0.6.0",
|
||||||
"smithay-clipboard",
|
"smithay-clipboard",
|
||||||
"web-time",
|
"web-time 0.2.4",
|
||||||
"webbrowser",
|
"webbrowser",
|
||||||
"winit",
|
"winit",
|
||||||
]
|
]
|
||||||
@@ -1145,6 +1145,16 @@ dependencies = [
|
|||||||
"web-sys",
|
"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]]
|
[[package]]
|
||||||
name = "ehttp"
|
name = "ehttp"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
@@ -2580,6 +2590,7 @@ dependencies = [
|
|||||||
"eframe",
|
"eframe",
|
||||||
"egui",
|
"egui",
|
||||||
"egui_extras",
|
"egui_extras",
|
||||||
|
"egui_virtual_list",
|
||||||
"ehttp 0.2.0",
|
"ehttp 0.2.0",
|
||||||
"enostr",
|
"enostr",
|
||||||
"env_logger 0.10.2",
|
"env_logger 0.10.2",
|
||||||
@@ -3059,7 +3070,7 @@ dependencies = [
|
|||||||
"puffin",
|
"puffin",
|
||||||
"time",
|
"time",
|
||||||
"vec1",
|
"vec1",
|
||||||
"web-time",
|
"web-time 0.2.4",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -4679,6 +4690,16 @@ dependencies = [
|
|||||||
"wasm-bindgen",
|
"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]]
|
[[package]]
|
||||||
name = "webbrowser"
|
name = "webbrowser"
|
||||||
version = "0.8.12"
|
version = "0.8.12"
|
||||||
@@ -5156,7 +5177,7 @@ dependencies = [
|
|||||||
"wayland-protocols",
|
"wayland-protocols",
|
||||||
"wayland-protocols-plasma",
|
"wayland-protocols-plasma",
|
||||||
"web-sys",
|
"web-sys",
|
||||||
"web-time",
|
"web-time 0.2.4",
|
||||||
"windows-sys 0.48.0",
|
"windows-sys 0.48.0",
|
||||||
"x11-dl",
|
"x11-dl",
|
||||||
"x11rb 0.13.0",
|
"x11rb 0.13.0",
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ nostr-sdk = "0.29.0"
|
|||||||
strum = "0.26"
|
strum = "0.26"
|
||||||
strum_macros = "0.26"
|
strum_macros = "0.26"
|
||||||
bitflags = "2.5.0"
|
bitflags = "2.5.0"
|
||||||
|
egui_virtual_list = "0.3.0"
|
||||||
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
[{"limit": 100,
|
[{"limit": 1000,
|
||||||
"kinds": [
|
"kinds": [
|
||||||
1
|
1
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
[{"limit": 100, "kinds":[1], "#p": ["32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245"]}]
|
[{"limit": 1000, "kinds":[1], "#p": ["32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245"]}]
|
||||||
|
|||||||
101
src/app.rs
101
src/app.rs
@@ -5,18 +5,16 @@ use crate::frame_history::FrameHistory;
|
|||||||
use crate::imgcache::ImageCache;
|
use crate::imgcache::ImageCache;
|
||||||
use crate::notecache::NoteCache;
|
use crate::notecache::NoteCache;
|
||||||
use crate::timeline;
|
use crate::timeline;
|
||||||
use crate::ui;
|
use crate::timeline::{NoteRef, Timeline};
|
||||||
use crate::ui::is_mobile;
|
use crate::ui::is_mobile;
|
||||||
use crate::Result;
|
use crate::Result;
|
||||||
|
|
||||||
use egui::containers::scroll_area::ScrollBarVisibility;
|
|
||||||
use egui::{Context, Frame, Margin, Style};
|
use egui::{Context, Frame, Margin, Style};
|
||||||
use egui_extras::{Size, StripBuilder};
|
use egui_extras::{Size, StripBuilder};
|
||||||
|
|
||||||
use enostr::{ClientMessage, Filter, Pubkey, RelayEvent, RelayMessage};
|
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::collections::{HashMap, HashSet};
|
||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
@@ -31,47 +29,6 @@ pub enum DamusState {
|
|||||||
Initialized,
|
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.
|
/// We derive Deserialize/Serialize so we can persist app state on shutdown.
|
||||||
pub struct Damus {
|
pub struct Damus {
|
||||||
state: DamusState,
|
state: DamusState,
|
||||||
@@ -80,7 +37,7 @@ pub struct Damus {
|
|||||||
pool: RelayPool,
|
pool: RelayPool,
|
||||||
pub textmode: bool,
|
pub textmode: bool,
|
||||||
|
|
||||||
timelines: Vec<Timeline>,
|
pub timelines: Vec<Timeline>,
|
||||||
|
|
||||||
pub img_cache: ImageCache,
|
pub img_cache: ImageCache,
|
||||||
pub ndb: Ndb,
|
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, ¬e);
|
|
||||||
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 {
|
fn top_panel(ctx: &egui::Context) -> egui::TopBottomPanel {
|
||||||
let top_margin = egui::Margin {
|
let top_margin = egui::Margin {
|
||||||
top: 4.0,
|
top: 4.0,
|
||||||
@@ -684,7 +595,7 @@ fn render_damus_mobile(ctx: &egui::Context, app: &mut Damus) {
|
|||||||
puffin::profile_function!();
|
puffin::profile_function!();
|
||||||
|
|
||||||
main_panel(&ctx.style()).show(ctx, |ui| {
|
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 {
|
if app.timelines.len() == 1 {
|
||||||
main_panel(&ctx.style()).show(ctx, |ui| {
|
main_panel(&ctx.style()).show(ctx, |ui| {
|
||||||
timeline_view(ui, app, 0);
|
timeline::timeline_view(ui, app, 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
return;
|
return;
|
||||||
@@ -737,7 +648,7 @@ fn timelines_view(ui: &mut egui::Ui, sizes: Size, app: &mut Damus, timelines: us
|
|||||||
.clip(true)
|
.clip(true)
|
||||||
.horizontal(|mut strip| {
|
.horizontal(|mut strip| {
|
||||||
for timeline_ind in 0..timelines {
|
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));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
107
src/timeline.rs
107
src/timeline.rs
@@ -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, ¬e);
|
||||||
|
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> {
|
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 merged = Vec::with_capacity(vec1.len() + vec2.len());
|
||||||
let mut i = 0;
|
let mut i = 0;
|
||||||
|
|||||||
Reference in New Issue
Block a user