notedeck

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

picture.rs (6648B)


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