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 }