notedeck

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

picture.rs (6618B)


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