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 }