preview.rs (6309B)
1 use crate::app_style::NotedeckTextStyle; 2 use crate::imgcache::ImageCache; 3 use crate::ui::ProfilePic; 4 use crate::{colors, images, DisplayName}; 5 use egui::load::TexturePoll; 6 use egui::{Frame, RichText, Sense, Widget}; 7 use egui_extras::Size; 8 use nostrdb::ProfileRecord; 9 10 pub struct ProfilePreview<'a, 'cache> { 11 profile: &'a ProfileRecord<'a>, 12 cache: &'cache mut ImageCache, 13 banner_height: Size, 14 } 15 16 impl<'a, 'cache> ProfilePreview<'a, 'cache> { 17 pub fn new(profile: &'a ProfileRecord<'a>, cache: &'cache mut ImageCache) -> Self { 18 let banner_height = Size::exact(80.0); 19 ProfilePreview { 20 profile, 21 cache, 22 banner_height, 23 } 24 } 25 26 pub fn banner_height(&mut self, size: Size) { 27 self.banner_height = size; 28 } 29 30 fn banner_texture( 31 ui: &mut egui::Ui, 32 profile: &ProfileRecord<'_>, 33 ) -> Option<egui::load::SizedTexture> { 34 // TODO: cache banner 35 let banner = profile.record().profile().and_then(|p| p.banner()); 36 37 if let Some(banner) = banner { 38 let texture_load_res = 39 egui::Image::new(banner).load_for_size(ui.ctx(), ui.available_size()); 40 if let Ok(texture_poll) = texture_load_res { 41 match texture_poll { 42 TexturePoll::Pending { .. } => {} 43 TexturePoll::Ready { texture, .. } => return Some(texture), 44 } 45 } 46 } 47 48 None 49 } 50 51 fn banner(ui: &mut egui::Ui, profile: &ProfileRecord<'_>) -> egui::Response { 52 if let Some(texture) = Self::banner_texture(ui, profile) { 53 images::aspect_fill( 54 ui, 55 Sense::hover(), 56 texture.id, 57 texture.size.x / texture.size.y, 58 ) 59 } else { 60 // TODO: default banner texture 61 ui.label("") 62 } 63 } 64 65 fn body(self, ui: &mut egui::Ui) { 66 crate::ui::padding(12.0, ui, |ui| { 67 ui.add(ProfilePic::new(self.cache, get_profile_url(Some(self.profile))).size(80.0)); 68 ui.add(display_name_widget( 69 get_display_name(Some(self.profile)), 70 false, 71 )); 72 ui.add(about_section_widget(self.profile)); 73 }); 74 } 75 } 76 77 impl<'a, 'cache> egui::Widget for ProfilePreview<'a, 'cache> { 78 fn ui(self, ui: &mut egui::Ui) -> egui::Response { 79 ui.vertical(|ui| { 80 ui.add_sized([ui.available_size().x, 80.0], |ui: &mut egui::Ui| { 81 ProfilePreview::banner(ui, self.profile) 82 }); 83 84 self.body(ui); 85 }) 86 .response 87 } 88 } 89 90 pub struct SimpleProfilePreview<'a, 'cache> { 91 profile: Option<&'a ProfileRecord<'a>>, 92 cache: &'cache mut ImageCache, 93 } 94 95 impl<'a, 'cache> SimpleProfilePreview<'a, 'cache> { 96 pub fn new(profile: Option<&'a ProfileRecord<'a>>, cache: &'cache mut ImageCache) -> Self { 97 SimpleProfilePreview { profile, cache } 98 } 99 } 100 101 impl<'a, 'cache> egui::Widget for SimpleProfilePreview<'a, 'cache> { 102 fn ui(self, ui: &mut egui::Ui) -> egui::Response { 103 Frame::none() 104 .show(ui, |ui| { 105 ui.add(ProfilePic::new(self.cache, get_profile_url(self.profile)).size(48.0)); 106 ui.vertical(|ui| { 107 ui.add(display_name_widget(get_display_name(self.profile), true)); 108 }); 109 }) 110 .response 111 } 112 } 113 114 mod previews { 115 use super::*; 116 use crate::test_data::test_profile_record; 117 use crate::ui::{Preview, PreviewConfig, View}; 118 119 pub struct ProfilePreviewPreview<'a> { 120 profile: ProfileRecord<'a>, 121 cache: ImageCache, 122 } 123 124 impl<'a> ProfilePreviewPreview<'a> { 125 pub fn new() -> Self { 126 let profile = test_profile_record(); 127 let cache = ImageCache::new(ImageCache::rel_datadir().into()); 128 ProfilePreviewPreview { profile, cache } 129 } 130 } 131 132 impl<'a> Default for ProfilePreviewPreview<'a> { 133 fn default() -> Self { 134 ProfilePreviewPreview::new() 135 } 136 } 137 138 impl<'a> View for ProfilePreviewPreview<'a> { 139 fn ui(&mut self, ui: &mut egui::Ui) { 140 ProfilePreview::new(&self.profile, &mut self.cache).ui(ui); 141 } 142 } 143 144 impl<'a, 'cache> Preview for ProfilePreview<'a, 'cache> { 145 /// A preview of the profile preview :D 146 type Prev = ProfilePreviewPreview<'a>; 147 148 fn preview(_cfg: PreviewConfig) -> Self::Prev { 149 ProfilePreviewPreview::new() 150 } 151 } 152 } 153 154 pub fn get_display_name<'a>(profile: Option<&'a ProfileRecord<'a>>) -> DisplayName<'a> { 155 if let Some(name) = profile.and_then(|p| crate::profile::get_profile_name(p)) { 156 name 157 } else { 158 DisplayName::One("??") 159 } 160 } 161 162 pub fn get_profile_url<'a>(profile: Option<&'a ProfileRecord<'a>>) -> &'a str { 163 if let Some(url) = profile.and_then(|pr| pr.record().profile().and_then(|p| p.picture())) { 164 url 165 } else { 166 ProfilePic::no_pfp_url() 167 } 168 } 169 170 fn display_name_widget( 171 display_name: DisplayName<'_>, 172 add_placeholder_space: bool, 173 ) -> impl egui::Widget + '_ { 174 move |ui: &mut egui::Ui| match display_name { 175 DisplayName::One(n) => { 176 let name_response = 177 ui.label(RichText::new(n).text_style(NotedeckTextStyle::Heading3.text_style())); 178 if add_placeholder_space { 179 ui.add_space(16.0); 180 } 181 name_response 182 } 183 184 DisplayName::Both { 185 display_name, 186 username, 187 } => { 188 ui.label( 189 RichText::new(display_name).text_style(NotedeckTextStyle::Heading3.text_style()), 190 ); 191 192 ui.label( 193 RichText::new(format!("@{}", username)) 194 .size(12.0) 195 .color(colors::MID_GRAY), 196 ) 197 } 198 } 199 } 200 201 fn about_section_widget<'a>(profile: &'a ProfileRecord<'a>) -> impl egui::Widget + 'a { 202 |ui: &mut egui::Ui| { 203 if let Some(about) = profile.record().profile().and_then(|p| p.about()) { 204 ui.label(about) 205 } else { 206 // need any Response so we dont need an Option 207 ui.allocate_response(egui::Vec2::ZERO, egui::Sense::hover()) 208 } 209 } 210 }