notedeck

One damus client to rule them all
git clone git://jb55.com/notedeck
Log | Files | Refs | README | LICENSE

commit e8168b0004fb994b27b9f8a9cab368229f9132ec
parent 61deeb03e1aa15ebe3198033b135acea37e80d8c
Author: William Casarin <jb55@jb55.com>
Date:   Tue, 23 Apr 2024 18:20:20 -0700

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 <jb55@jb55.com>

Diffstat:
MCargo.lock | 2+-
MCargo.toml | 2+-
Asrc/ui/anim.rs | 19+++++++++++++++++++
Msrc/ui/mod.rs | 1+
Msrc/ui/note/mod.rs | 43++++++++++++++++++++++++++++++++++++++++++-
Msrc/ui/profile/picture.rs | 6+++++-
6 files changed, 69 insertions(+), 4 deletions(-)

diff --git 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 @@ -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 @@ -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 @@ -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 @@ -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<H: Hasher>(&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 @@ -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" }