From 61deeb03e1aa15ebe3198033b135acea37e80d8c Mon Sep 17 00:00:00 2001 From: William Casarin Date: Wed, 24 Apr 2024 08:53:21 -0700 Subject: [PATCH 1/4] note: support nprofile mentions realized we were missing this bit Signed-off-by: William Casarin --- src/ui/note/contents.rs | 51 ++++++++++++++++++++++------------------- 1 file changed, 28 insertions(+), 23 deletions(-) diff --git a/src/ui/note/contents.rs b/src/ui/note/contents.rs index 868767b6..5894e5d0 100644 --- a/src/ui/note/contents.rs +++ b/src/ui/note/contents.rs @@ -97,6 +97,29 @@ fn render_note_preview( .response } +fn mention_ui(app: &mut Damus, txn: &Transaction, pk: &[u8; 32], ui: &mut egui::Ui) { + #[cfg(feature = "profiling")] + puffin::profile_function!(); + + let profile = app.ndb.get_profile_by_pubkey(txn, pk).ok(); + + let name: String = + if let Some(name) = profile.as_ref().and_then(crate::profile::get_profile_name) { + format!("@{}", name.username()) + } else { + "??".to_string() + }; + + let resp = ui.colored_label(colors::PURPLE, &name); + + if let Some(rec) = profile.as_ref() { + resp.on_hover_ui_at_pointer(|ui| { + ui.set_max_width(300.0); + ui.add(ui::ProfilePreview::new(rec, &mut app.img_cache)); + }); + } +} + fn render_note_contents( ui: &mut egui::Ui, damus: &mut Damus, @@ -125,30 +148,12 @@ fn render_note_contents( for block in blocks.iter(note) { match block.blocktype() { BlockType::MentionBech32 => match block.as_mention().unwrap() { + Mention::Profile(profile) => { + mention_ui(damus, txn, profile.pubkey(), ui); + } + Mention::Pubkey(npub) => { - #[cfg(feature = "profiling")] - puffin::profile_scope!("pubkey contents"); - - ui.horizontal(|ui| { - let profile = damus.ndb.get_profile_by_pubkey(txn, npub.pubkey()).ok(); - - let name: String = if let Some(name) = - profile.as_ref().and_then(crate::profile::get_profile_name) - { - format!("@{}", name.username()) - } else { - "??".to_string() - }; - - let resp = ui.colored_label(colors::PURPLE, &name); - - if let Some(rec) = profile.as_ref() { - resp.on_hover_ui_at_pointer(|ui| { - ui.set_max_width(300.0); - ui.add(ui::ProfilePreview::new(rec, &mut damus.img_cache)); - }); - } - }); + mention_ui(damus, txn, npub.pubkey(), ui); } Mention::Note(note) if options.has_note_previews() => { From e8168b0004fb994b27b9f8a9cab368229f9132ec Mon Sep 17 00:00:00 2001 From: William Casarin Date: Tue, 23 Apr 2024 18:20:20 -0700 Subject: [PATCH 2/4] ui: add profile picture hover animation I wanted to practice doing animation in egui, so here is a simple profile picture hover affect. When you mouse over a profile picture, it get slightly bigger. Signed-off-by: William Casarin --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/ui/anim.rs | 19 +++++++++++++++++ src/ui/mod.rs | 1 + src/ui/note/mod.rs | 43 ++++++++++++++++++++++++++++++++++++++- src/ui/profile/picture.rs | 6 +++++- 6 files changed, 69 insertions(+), 4 deletions(-) create mode 100644 src/ui/anim.rs diff --git a/Cargo.lock b/Cargo.lock index 559db946..0132a911 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2557,7 +2557,7 @@ dependencies = [ [[package]] name = "nostrdb" version = "0.3.2" -source = "git+https://github.com/damus-io/nostrdb-rs?rev=b6c5d8fb9f3f79f14d69dd2d6eca2712e0f0a42d#b6c5d8fb9f3f79f14d69dd2d6eca2712e0f0a42d" +source = "git+https://github.com/damus-io/nostrdb-rs?rev=01d8bee4fea6e2e8f6bc3e4e6e3c989e43defe4b#01d8bee4fea6e2e8f6bc3e4e6e3c989e43defe4b" dependencies = [ "bindgen", "cc", diff --git a/Cargo.toml b/Cargo.toml index de113539..06eafa37 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,7 +31,7 @@ serde_json = "1.0.89" env_logger = "0.10.0" puffin_egui = { version = "0.27.0", optional = true } puffin = { version = "0.19.0", optional = true } -nostrdb = { git = "https://github.com/damus-io/nostrdb-rs", rev = "b6c5d8fb9f3f79f14d69dd2d6eca2712e0f0a42d" } +nostrdb = { git = "https://github.com/damus-io/nostrdb-rs", rev = "01d8bee4fea6e2e8f6bc3e4e6e3c989e43defe4b" } #nostrdb = "0.3.2" hex = "0.4.3" base32 = "0.4.0" diff --git a/src/ui/anim.rs b/src/ui/anim.rs new file mode 100644 index 00000000..142fe15b --- /dev/null +++ b/src/ui/anim.rs @@ -0,0 +1,19 @@ +pub fn hover_expand( + ui: &mut egui::Ui, + id: egui::Id, + size: f32, + expand_size: f32, + anim_speed: f32, +) -> (egui::Rect, f32) { + // Allocate space for the profile picture with a fixed size + let default_size = size + expand_size; + let (rect, response) = + ui.allocate_exact_size(egui::vec2(default_size, default_size), egui::Sense::hover()); + + let val = ui + .ctx() + .animate_bool_with_time(id, response.hovered(), anim_speed); + + let size = size + val * expand_size; + (rect, size) +} diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 7bd23f6a..60c3ad0d 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -1,3 +1,4 @@ +pub mod anim; pub mod note; pub mod preview; pub mod profile; diff --git a/src/ui/note/mod.rs b/src/ui/note/mod.rs index c0edc856..eb3b255a 100644 --- a/src/ui/note/mod.rs +++ b/src/ui/note/mod.rs @@ -6,6 +6,7 @@ pub use options::NoteOptions; use crate::{colors, ui, Damus}; use egui::{Label, RichText, Sense}; +use std::hash::{Hash, Hasher}; pub struct Note<'a> { app: &'a mut Damus, @@ -23,6 +24,20 @@ impl<'a> egui::Widget for Note<'a> { } } +#[derive(Eq, PartialEq, Debug, Clone, Copy)] +struct ProfileAnimId { + profile_key: u64, + note_key: u64, +} + +impl Hash for ProfileAnimId { + fn hash(&self, state: &mut H) { + state.write_u8(0x12); + self.profile_key.hash(state); + self.note_key.hash(state); + } +} + impl<'a> Note<'a> { pub fn new(app: &'a mut Damus, note: &'a nostrdb::Note<'a>) -> Self { let flags = NoteOptions::actionbar | NoteOptions::note_previews; @@ -104,7 +119,33 @@ impl<'a> Note<'a> { // these have different lifetimes and types, // so the calls must be separate Some(pic) => { - ui.add(ui::ProfilePic::new(&mut self.app.img_cache, pic)); + let expand_size = 5.0; + let anim_speed = 0.05; + let profile_key = profile.as_ref().unwrap().record().note_key(); + let note_key = note_key.as_u64(); + + let (rect, size) = ui::anim::hover_expand( + ui, + egui::Id::new(ProfileAnimId { + profile_key, + note_key, + }), + ui::ProfilePic::default_size(), + expand_size, + anim_speed, + ); + + ui.put( + rect, + ui::ProfilePic::new(&mut self.app.img_cache, pic).size(size), + ) + .on_hover_ui_at_pointer(|ui| { + ui.set_max_width(300.0); + ui.add(ui::ProfilePreview::new( + profile.as_ref().unwrap(), + &mut self.app.img_cache, + )); + }); } None => { ui.add(ui::ProfilePic::new( diff --git a/src/ui/profile/picture.rs b/src/ui/profile/picture.rs index 0bc18e1d..bd18472a 100644 --- a/src/ui/profile/picture.rs +++ b/src/ui/profile/picture.rs @@ -16,10 +16,14 @@ impl<'cache, 'url> egui::Widget for ProfilePic<'cache, 'url> { impl<'cache, 'url> ProfilePic<'cache, 'url> { pub fn new(cache: &'cache mut ImageCache, url: &'url str) -> Self { - let size = 32.0; + let size = Self::default_size(); ProfilePic { cache, url, size } } + pub fn default_size() -> f32 { + 32.0 + } + pub fn no_pfp_url() -> &'static str { "https://damus.io/img/no-profile.svg" } From 7ec31d0eaee2aa816d4a40f5ec8a6ad4646b6570 Mon Sep 17 00:00:00 2001 From: William Casarin Date: Tue, 23 Apr 2024 20:43:30 -0700 Subject: [PATCH 3/4] fun large profile grid preview Signed-off-by: William Casarin --- preview | 2 +- src/ui/profile/picture.rs | 62 ++++++++++++++++++++++++++++++++++++++- src/ui/relay.rs | 2 +- src/ui_preview/main.rs | 4 +-- 4 files changed, 65 insertions(+), 5 deletions(-) diff --git a/preview b/preview index 07be5d24..6746da8b 100755 --- a/preview +++ b/preview @@ -1,2 +1,2 @@ #!/usr/bin/env bash -cargo run --bin ui_preview -- "$@" +cargo run --bin ui_preview --release -- "$@" diff --git a/src/ui/profile/picture.rs b/src/ui/profile/picture.rs index bd18472a..ac14474a 100644 --- a/src/ui/profile/picture.rs +++ b/src/ui/profile/picture.rs @@ -1,5 +1,5 @@ use crate::imgcache::ImageCache; - +use crate::ui::{Preview, View}; use egui::{vec2, Sense, TextureHandle}; pub struct ProfilePic<'cache, 'url> { @@ -97,3 +97,63 @@ fn paint_circle(ui: &mut egui::Ui, size: f32) -> egui::Response { response } + +mod preview { + use super::*; + use nostrdb::*; + use std::collections::HashSet; + + pub struct ProfilePicPreview { + cache: ImageCache, + urls: Vec, + } + + impl ProfilePicPreview { + fn new() -> Self { + let config = Config::new(); + let ndb = Ndb::new(".", &config).expect("ndb"); + let txn = Transaction::new(&ndb).unwrap(); + let filters = vec![Filter::new().kinds(vec![0]).build()]; + let cache = ImageCache::new("cache/img".into()); + let mut pks = HashSet::new(); + let mut urls = HashSet::new(); + + for query_result in ndb.query(&txn, filters, 1000).unwrap() { + pks.insert(query_result.note.pubkey()); + } + + for pk in pks { + let profile = if let Ok(profile) = ndb.get_profile_by_pubkey(&txn, pk) { + profile + } else { + continue; + }; + if let Some(url) = profile.record().profile().and_then(|p| p.picture()) { + urls.insert(url.to_string()); + } + } + + let urls = urls.into_iter().collect(); + + ProfilePicPreview { cache, urls } + } + } + + impl View for ProfilePicPreview { + fn ui(&mut self, ui: &mut egui::Ui) { + ui.horizontal_wrapped(|ui| { + for url in &self.urls { + ui.add(ProfilePic::new(&mut self.cache, &url)); + } + }); + } + } + + impl<'cache, 'url> Preview for ProfilePic<'cache, 'url> { + type Prev = ProfilePicPreview; + + fn preview() -> Self::Prev { + ProfilePicPreview::new() + } + } +} diff --git a/src/ui/relay.rs b/src/ui/relay.rs index 8d0a9209..329f826b 100644 --- a/src/ui/relay.rs +++ b/src/ui/relay.rs @@ -193,7 +193,7 @@ mod preview { impl View for RelayViewPreview { fn ui(&mut self, ui: &mut egui::Ui) { self.pool.try_recv(); - RelayView::new(RelayPoolManager::new(&mut self.pool)).ui(ui) + RelayView::new(RelayPoolManager::new(&mut self.pool)).ui(ui); } } diff --git a/src/ui_preview/main.rs b/src/ui_preview/main.rs index 26ef126f..12dc080c 100644 --- a/src/ui_preview/main.rs +++ b/src/ui_preview/main.rs @@ -2,7 +2,7 @@ use notedeck::account_login_view::AccountLoginView; use notedeck::app_creation::{ generate_mobile_emulator_native_options, generate_native_options, setup_cc, }; -use notedeck::ui::{Preview, PreviewApp, ProfilePreview, RelayView}; +use notedeck::ui::{Preview, PreviewApp, ProfilePreview, RelayView, ProfilePic}; use std::env; struct PreviewRunner { @@ -73,5 +73,5 @@ async fn main() { let runner = PreviewRunner::new(is_mobile); - previews!(runner, name, RelayView, AccountLoginView, ProfilePreview,); + previews!(runner, name, RelayView, AccountLoginView, ProfilePreview, ProfilePic); } From f12ccc69a44bdcab0dd698271676dec4e97f058e Mon Sep 17 00:00:00 2001 From: William Casarin Date: Wed, 24 Apr 2024 11:00:47 -0700 Subject: [PATCH 4/4] add hover to profile map demo Signed-off-by: William Casarin --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/ui/profile/picture.rs | 53 +++++++++++++++++++++++++++++++-------- src/ui_preview/main.rs | 11 ++++++-- 4 files changed, 54 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0132a911..5f36c7b0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2557,7 +2557,7 @@ dependencies = [ [[package]] name = "nostrdb" version = "0.3.2" -source = "git+https://github.com/damus-io/nostrdb-rs?rev=01d8bee4fea6e2e8f6bc3e4e6e3c989e43defe4b#01d8bee4fea6e2e8f6bc3e4e6e3c989e43defe4b" +source = "git+https://github.com/damus-io/nostrdb-rs?rev=1489a5aee49996d8a6a54b4c3c9c8397e3e8d40f#1489a5aee49996d8a6a54b4c3c9c8397e3e8d40f" dependencies = [ "bindgen", "cc", diff --git a/Cargo.toml b/Cargo.toml index 06eafa37..3fa2596e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,7 +31,7 @@ serde_json = "1.0.89" env_logger = "0.10.0" puffin_egui = { version = "0.27.0", optional = true } puffin = { version = "0.19.0", optional = true } -nostrdb = { git = "https://github.com/damus-io/nostrdb-rs", rev = "01d8bee4fea6e2e8f6bc3e4e6e3c989e43defe4b" } +nostrdb = { git = "https://github.com/damus-io/nostrdb-rs", rev = "1489a5aee49996d8a6a54b4c3c9c8397e3e8d40f" } #nostrdb = "0.3.2" hex = "0.4.3" base32 = "0.4.0" diff --git a/src/ui/profile/picture.rs b/src/ui/profile/picture.rs index ac14474a..40998ea2 100644 --- a/src/ui/profile/picture.rs +++ b/src/ui/profile/picture.rs @@ -100,12 +100,14 @@ fn paint_circle(ui: &mut egui::Ui, size: f32) -> egui::Response { mod preview { use super::*; + use crate::ui; use nostrdb::*; use std::collections::HashSet; pub struct ProfilePicPreview { cache: ImageCache, - urls: Vec, + ndb: Ndb, + keys: Vec, } impl ProfilePicPreview { @@ -116,9 +118,9 @@ mod preview { let filters = vec![Filter::new().kinds(vec![0]).build()]; let cache = ImageCache::new("cache/img".into()); let mut pks = HashSet::new(); - let mut urls = HashSet::new(); + let mut keys = HashSet::new(); - for query_result in ndb.query(&txn, filters, 1000).unwrap() { + for query_result in ndb.query(&txn, filters, 2000).unwrap() { pks.insert(query_result.note.pubkey()); } @@ -128,22 +130,53 @@ mod preview { } else { continue; }; - if let Some(url) = profile.record().profile().and_then(|p| p.picture()) { - urls.insert(url.to_string()); + + if profile + .record() + .profile() + .and_then(|p| p.picture()) + .is_none() + { + continue; } - } - let urls = urls.into_iter().collect(); + keys.insert(profile.key().expect("should not be owned")); + } - ProfilePicPreview { cache, urls } + let keys = keys.into_iter().collect(); + ProfilePicPreview { cache, ndb, keys } } } impl View for ProfilePicPreview { fn ui(&mut self, ui: &mut egui::Ui) { ui.horizontal_wrapped(|ui| { - for url in &self.urls { - ui.add(ProfilePic::new(&mut self.cache, &url)); + let txn = Transaction::new(&self.ndb).unwrap(); + for key in &self.keys { + let profile = self.ndb.get_profile_by_key(&txn, *key).unwrap(); + let url = profile + .record() + .profile() + .expect("should have profile") + .picture() + .expect("should have picture"); + + let expand_size = 10.0; + let anim_speed = 0.05; + + let (rect, size) = ui::anim::hover_expand( + ui, + egui::Id::new(profile.key().unwrap()), + ui::ProfilePic::default_size(), + expand_size, + anim_speed, + ); + + ui.put(rect, ui::ProfilePic::new(&mut self.cache, url).size(size)) + .on_hover_ui_at_pointer(|ui| { + ui.set_max_width(300.0); + ui.add(ui::ProfilePreview::new(&profile, &mut self.cache)); + }); } }); } diff --git a/src/ui_preview/main.rs b/src/ui_preview/main.rs index 12dc080c..224f7cb8 100644 --- a/src/ui_preview/main.rs +++ b/src/ui_preview/main.rs @@ -2,7 +2,7 @@ use notedeck::account_login_view::AccountLoginView; use notedeck::app_creation::{ generate_mobile_emulator_native_options, generate_native_options, setup_cc, }; -use notedeck::ui::{Preview, PreviewApp, ProfilePreview, RelayView, ProfilePic}; +use notedeck::ui::{Preview, PreviewApp, ProfilePic, ProfilePreview, RelayView}; use std::env; struct PreviewRunner { @@ -73,5 +73,12 @@ async fn main() { let runner = PreviewRunner::new(is_mobile); - previews!(runner, name, RelayView, AccountLoginView, ProfilePreview, ProfilePic); + previews!( + runner, + name, + RelayView, + AccountLoginView, + ProfilePreview, + ProfilePic + ); }