notedeck

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

picture.rs (7009B)


      1 use crate::images::ImageType;
      2 use crate::ui::{Preview, PreviewConfig};
      3 use egui::{vec2, Sense, TextureHandle};
      4 use nostrdb::{Ndb, Transaction};
      5 use tracing::info;
      6 
      7 use notedeck::{AppContext, ImageCache};
      8 
      9 pub struct ProfilePic<'cache, 'url> {
     10     cache: &'cache mut ImageCache,
     11     url: &'url str,
     12     size: f32,
     13 }
     14 
     15 impl egui::Widget for ProfilePic<'_, '_> {
     16     fn ui(self, ui: &mut egui::Ui) -> egui::Response {
     17         render_pfp(ui, self.cache, self.url, self.size)
     18     }
     19 }
     20 
     21 impl<'cache, 'url> ProfilePic<'cache, 'url> {
     22     pub fn new(cache: &'cache mut ImageCache, url: &'url str) -> Self {
     23         let size = Self::default_size();
     24         ProfilePic { cache, url, size }
     25     }
     26 
     27     pub fn from_profile(
     28         cache: &'cache mut ImageCache,
     29         profile: &nostrdb::ProfileRecord<'url>,
     30     ) -> Option<Self> {
     31         profile
     32             .record()
     33             .profile()
     34             .and_then(|p| p.picture())
     35             .map(|url| ProfilePic::new(cache, url))
     36     }
     37 
     38     #[inline]
     39     pub fn default_size() -> f32 {
     40         38.0
     41     }
     42 
     43     #[inline]
     44     pub fn medium_size() -> f32 {
     45         32.0
     46     }
     47 
     48     #[inline]
     49     pub fn small_size() -> f32 {
     50         24.0
     51     }
     52 
     53     #[inline]
     54     pub fn no_pfp_url() -> &'static str {
     55         "https://damus.io/img/no-profile.svg"
     56     }
     57 
     58     #[inline]
     59     pub fn size(mut self, size: f32) -> Self {
     60         self.size = size;
     61         self
     62     }
     63 }
     64 
     65 fn render_pfp(
     66     ui: &mut egui::Ui,
     67     img_cache: &mut ImageCache,
     68     url: &str,
     69     ui_size: f32,
     70 ) -> egui::Response {
     71     #[cfg(feature = "profiling")]
     72     puffin::profile_function!();
     73 
     74     // We will want to downsample these so it's not blurry on hi res displays
     75     let img_size = 128u32;
     76 
     77     let m_cached_promise = img_cache.map().get(url);
     78     if m_cached_promise.is_none() {
     79         let res = crate::images::fetch_img(img_cache, ui.ctx(), url, ImageType::Profile(img_size));
     80         img_cache.map_mut().insert(url.to_owned(), res);
     81     }
     82 
     83     match img_cache.map()[url].ready() {
     84         None => paint_circle(ui, ui_size),
     85 
     86         // Failed to fetch profile!
     87         Some(Err(_err)) => {
     88             let m_failed_promise = img_cache.map().get(url);
     89             if m_failed_promise.is_none() {
     90                 let no_pfp = crate::images::fetch_img(
     91                     img_cache,
     92                     ui.ctx(),
     93                     ProfilePic::no_pfp_url(),
     94                     ImageType::Profile(img_size),
     95                 );
     96                 img_cache.map_mut().insert(url.to_owned(), no_pfp);
     97             }
     98 
     99             match img_cache.map().get(url).unwrap().ready() {
    100                 None => paint_circle(ui, ui_size),
    101                 Some(Err(_e)) => {
    102                     //error!("Image load error: {:?}", e);
    103                     paint_circle(ui, ui_size)
    104                 }
    105                 Some(Ok(img)) => pfp_image(ui, img, ui_size),
    106             }
    107         }
    108         Some(Ok(img)) => pfp_image(ui, img, ui_size),
    109     }
    110 }
    111 
    112 fn pfp_image(ui: &mut egui::Ui, img: &TextureHandle, size: f32) -> egui::Response {
    113     #[cfg(feature = "profiling")]
    114     puffin::profile_function!();
    115 
    116     //img.show_max_size(ui, egui::vec2(size, size))
    117     ui.add(egui::Image::new(img).max_width(size))
    118     //.with_options()
    119 }
    120 
    121 fn paint_circle(ui: &mut egui::Ui, size: f32) -> egui::Response {
    122     let (rect, response) = ui.allocate_at_least(vec2(size, size), Sense::hover());
    123     ui.painter()
    124         .circle_filled(rect.center(), size / 2.0, ui.visuals().weak_text_color());
    125 
    126     response
    127 }
    128 
    129 mod preview {
    130     use super::*;
    131     use crate::ui;
    132     use nostrdb::*;
    133     use std::collections::HashSet;
    134 
    135     pub struct ProfilePicPreview {
    136         keys: Option<Vec<ProfileKey>>,
    137     }
    138 
    139     impl ProfilePicPreview {
    140         fn new() -> Self {
    141             ProfilePicPreview { keys: None }
    142         }
    143 
    144         fn show(&mut self, app: &mut AppContext<'_>, ui: &mut egui::Ui) {
    145             egui::ScrollArea::both().show(ui, |ui| {
    146                 ui.horizontal_wrapped(|ui| {
    147                     let txn = Transaction::new(app.ndb).unwrap();
    148 
    149                     let keys = if let Some(keys) = &self.keys {
    150                         keys
    151                     } else {
    152                         return;
    153                     };
    154 
    155                     for key in keys {
    156                         let profile = app.ndb.get_profile_by_key(&txn, *key).unwrap();
    157                         let url = profile
    158                             .record()
    159                             .profile()
    160                             .expect("should have profile")
    161                             .picture()
    162                             .expect("should have picture");
    163 
    164                         let expand_size = 10.0;
    165                         let anim_speed = 0.05;
    166 
    167                         let (rect, size, _resp) = ui::anim::hover_expand(
    168                             ui,
    169                             egui::Id::new(profile.key().unwrap()),
    170                             ui::ProfilePic::default_size(),
    171                             expand_size,
    172                             anim_speed,
    173                         );
    174 
    175                         ui.put(rect, ui::ProfilePic::new(app.img_cache, url).size(size))
    176                             .on_hover_ui_at_pointer(|ui| {
    177                                 ui.set_max_width(300.0);
    178                                 ui.add(ui::ProfilePreview::new(&profile, app.img_cache));
    179                             });
    180                     }
    181                 });
    182             });
    183         }
    184 
    185         fn setup(&mut self, ndb: &Ndb) {
    186             let txn = Transaction::new(ndb).unwrap();
    187             let filters = vec![Filter::new().kinds(vec![0]).build()];
    188             let mut pks = HashSet::new();
    189             let mut keys = HashSet::new();
    190 
    191             for query_result in ndb.query(&txn, &filters, 20000).unwrap() {
    192                 pks.insert(query_result.note.pubkey());
    193             }
    194 
    195             for pk in pks {
    196                 let profile = if let Ok(profile) = ndb.get_profile_by_pubkey(&txn, pk) {
    197                     profile
    198                 } else {
    199                     continue;
    200                 };
    201 
    202                 if profile
    203                     .record()
    204                     .profile()
    205                     .and_then(|p| p.picture())
    206                     .is_none()
    207                 {
    208                     continue;
    209                 }
    210 
    211                 keys.insert(profile.key().expect("should not be owned"));
    212             }
    213 
    214             let keys: Vec<ProfileKey> = keys.into_iter().collect();
    215             info!("Loaded {} profiles", keys.len());
    216             self.keys = Some(keys);
    217         }
    218     }
    219 
    220     impl notedeck::App for ProfilePicPreview {
    221         fn update(&mut self, ctx: &mut AppContext<'_>, ui: &mut egui::Ui) {
    222             if self.keys.is_none() {
    223                 self.setup(ctx.ndb);
    224             }
    225 
    226             self.show(ctx, ui)
    227         }
    228     }
    229 
    230     impl Preview for ProfilePic<'_, '_> {
    231         type Prev = ProfilePicPreview;
    232 
    233         fn preview(_cfg: PreviewConfig) -> Self::Prev {
    234             ProfilePicPreview::new()
    235         }
    236     }
    237 }