notedeck

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

picture.rs (6624B)


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