notedeck

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

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 }