notedeck

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

add_column.rs (8283B)


      1 use egui::{pos2, vec2, Color32, FontId, ImageSource, Pos2, Rect, Separator, Ui};
      2 use nostrdb::Ndb;
      3 
      4 use crate::{
      5     app_style::{get_font_size, NotedeckTextStyle},
      6     timeline::{PubkeySource, Timeline, TimelineKind},
      7     ui::anim::ICON_EXPANSION_MULTIPLE,
      8     user_account::UserAccount,
      9 };
     10 
     11 use super::anim::AnimationHelper;
     12 
     13 pub enum AddColumnResponse {
     14     Timeline(Timeline),
     15 }
     16 
     17 #[derive(Clone, Debug)]
     18 enum AddColumnOption {
     19     Universe,
     20     Notification(PubkeySource),
     21     Home(PubkeySource),
     22 }
     23 
     24 impl AddColumnOption {
     25     pub fn take_as_response(
     26         self,
     27         ndb: &Ndb,
     28         cur_account: Option<&UserAccount>,
     29     ) -> Option<AddColumnResponse> {
     30         match self {
     31             AddColumnOption::Universe => TimelineKind::Universe
     32                 .into_timeline(ndb, None)
     33                 .map(AddColumnResponse::Timeline),
     34             AddColumnOption::Notification(pubkey) => TimelineKind::Notifications(pubkey)
     35                 .into_timeline(ndb, cur_account.map(|a| a.pubkey.bytes()))
     36                 .map(AddColumnResponse::Timeline),
     37             AddColumnOption::Home(pubkey) => {
     38                 let tlk = TimelineKind::contact_list(pubkey);
     39                 tlk.into_timeline(ndb, cur_account.map(|a| a.pubkey.bytes()))
     40                     .map(AddColumnResponse::Timeline)
     41             }
     42         }
     43     }
     44 }
     45 
     46 pub struct AddColumnView<'a> {
     47     ndb: &'a Ndb,
     48     cur_account: Option<&'a UserAccount>,
     49 }
     50 
     51 impl<'a> AddColumnView<'a> {
     52     pub fn new(ndb: &'a Ndb, cur_account: Option<&'a UserAccount>) -> Self {
     53         Self { ndb, cur_account }
     54     }
     55 
     56     pub fn ui(&mut self, ui: &mut Ui) -> Option<AddColumnResponse> {
     57         let mut selected_option: Option<AddColumnResponse> = None;
     58         for column_option_data in self.get_column_options() {
     59             let option = column_option_data.option.clone();
     60             if self.column_option_ui(ui, column_option_data).clicked() {
     61                 selected_option = option.take_as_response(self.ndb, self.cur_account);
     62             }
     63 
     64             ui.add(Separator::default().spacing(0.0));
     65         }
     66 
     67         selected_option
     68     }
     69 
     70     fn column_option_ui(&mut self, ui: &mut Ui, data: ColumnOptionData) -> egui::Response {
     71         let icon_padding = 8.0;
     72         let min_icon_width = 32.0;
     73         let height_padding = 12.0;
     74         let max_width = ui.available_width();
     75         let title_style = NotedeckTextStyle::Body;
     76         let desc_style = NotedeckTextStyle::Button;
     77         let title_min_font_size = get_font_size(ui.ctx(), &title_style);
     78         let desc_min_font_size = get_font_size(ui.ctx(), &desc_style);
     79 
     80         let max_height = {
     81             let max_wrap_width =
     82                 max_width - ((icon_padding * 2.0) + (min_icon_width * ICON_EXPANSION_MULTIPLE));
     83             let title_max_font = FontId::new(
     84                 title_min_font_size * ICON_EXPANSION_MULTIPLE,
     85                 title_style.font_family(),
     86             );
     87             let desc_max_font = FontId::new(
     88                 desc_min_font_size * ICON_EXPANSION_MULTIPLE,
     89                 desc_style.font_family(),
     90             );
     91             let max_desc_galley = ui.fonts(|f| {
     92                 f.layout(
     93                     data.description.to_string(),
     94                     desc_max_font,
     95                     Color32::WHITE,
     96                     max_wrap_width,
     97                 )
     98             });
     99 
    100             let max_title_galley = ui.fonts(|f| {
    101                 f.layout(
    102                     data.title.to_string(),
    103                     title_max_font,
    104                     Color32::WHITE,
    105                     max_wrap_width,
    106                 )
    107             });
    108 
    109             let desc_font_max_size = max_desc_galley.rect.height();
    110             let title_font_max_size = max_title_galley.rect.height();
    111             title_font_max_size + desc_font_max_size + (2.0 * height_padding)
    112         };
    113 
    114         let helper = AnimationHelper::new(ui, data.title, vec2(max_width, max_height));
    115         let animation_rect = helper.get_animation_rect();
    116 
    117         let cur_icon_width = helper.scale_1d_pos(min_icon_width);
    118         let painter = ui.painter_at(animation_rect);
    119 
    120         let cur_icon_size = vec2(cur_icon_width, cur_icon_width);
    121         let cur_icon_x_pos = animation_rect.left() + (icon_padding) + (cur_icon_width / 2.0);
    122 
    123         let title_cur_font = FontId::new(
    124             helper.scale_1d_pos(title_min_font_size),
    125             title_style.font_family(),
    126         );
    127 
    128         let desc_cur_font = FontId::new(
    129             helper.scale_1d_pos(desc_min_font_size),
    130             desc_style.font_family(),
    131         );
    132 
    133         let wrap_width = max_width - (cur_icon_width + (icon_padding * 2.0));
    134         let text_color = ui.ctx().style().visuals.text_color();
    135         let fallback_color = ui.ctx().style().visuals.weak_text_color();
    136 
    137         let title_galley = painter.layout(
    138             data.title.to_string(),
    139             title_cur_font,
    140             text_color,
    141             wrap_width,
    142         );
    143         let desc_galley = painter.layout(
    144             data.description.to_string(),
    145             desc_cur_font,
    146             text_color,
    147             wrap_width,
    148         );
    149 
    150         let galley_heights = title_galley.rect.height() + desc_galley.rect.height();
    151 
    152         let cur_height_padding = (animation_rect.height() - galley_heights) / 2.0;
    153         let corner_x_pos = cur_icon_x_pos + (cur_icon_width / 2.0) + icon_padding;
    154         let title_corner_pos = Pos2::new(corner_x_pos, animation_rect.top() + cur_height_padding);
    155         let desc_corner_pos = Pos2::new(
    156             corner_x_pos,
    157             title_corner_pos.y + title_galley.rect.height(),
    158         );
    159 
    160         let icon_cur_y = animation_rect.top() + cur_height_padding + (galley_heights / 2.0);
    161         let icon_img = egui::Image::new(data.icon).fit_to_exact_size(cur_icon_size);
    162         let icon_rect = Rect::from_center_size(pos2(cur_icon_x_pos, icon_cur_y), cur_icon_size);
    163 
    164         icon_img.paint_at(ui, icon_rect);
    165         painter.galley(title_corner_pos, title_galley, fallback_color);
    166         painter.galley(desc_corner_pos, desc_galley, fallback_color);
    167 
    168         helper.take_animation_response()
    169     }
    170 
    171     fn get_column_options(&self) -> Vec<ColumnOptionData> {
    172         let mut vec = Vec::new();
    173         vec.push(ColumnOptionData {
    174             title: "Universe",
    175             description: "See the whole nostr universe",
    176             icon: egui::include_image!("../../assets/icons/universe_icon_dark_4x.png"),
    177             option: AddColumnOption::Universe,
    178         });
    179 
    180         if let Some(acc) = self.cur_account {
    181             let source = if acc.secret_key.is_some() {
    182                 PubkeySource::DeckAuthor
    183             } else {
    184                 PubkeySource::Explicit(acc.pubkey)
    185             };
    186 
    187             vec.push(ColumnOptionData {
    188                 title: "Home timeline",
    189                 description: "See recommended notes first",
    190                 icon: egui::include_image!("../../assets/icons/home_icon_dark_4x.png"),
    191                 option: AddColumnOption::Home(source.clone()),
    192             });
    193             vec.push(ColumnOptionData {
    194                 title: "Notifications",
    195                 description: "Stay up to date with notifications and mentions",
    196                 icon: egui::include_image!("../../assets/icons/notifications_icon_dark_4x.png"),
    197                 option: AddColumnOption::Notification(source),
    198             });
    199         }
    200 
    201         vec
    202     }
    203 }
    204 
    205 struct ColumnOptionData {
    206     title: &'static str,
    207     description: &'static str,
    208     icon: ImageSource<'static>,
    209     option: AddColumnOption,
    210 }
    211 
    212 mod preview {
    213     use crate::{
    214         test_data,
    215         ui::{Preview, PreviewConfig, View},
    216         Damus,
    217     };
    218 
    219     use super::AddColumnView;
    220 
    221     pub struct AddColumnPreview {
    222         app: Damus,
    223     }
    224 
    225     impl AddColumnPreview {
    226         fn new() -> Self {
    227             let app = test_data::test_app();
    228 
    229             AddColumnPreview { app }
    230         }
    231     }
    232 
    233     impl View for AddColumnPreview {
    234         fn ui(&mut self, ui: &mut egui::Ui) {
    235             AddColumnView::new(&self.app.ndb, self.app.accounts.get_selected_account()).ui(ui);
    236         }
    237     }
    238 
    239     impl<'a> Preview for AddColumnView<'a> {
    240         type Prev = AddColumnPreview;
    241 
    242         fn preview(_cfg: PreviewConfig) -> Self::Prev {
    243             AddColumnPreview::new()
    244         }
    245     }
    246 }