notedeck

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

mentions_picker.rs (6612B)


      1 use egui::{vec2, FontId, Layout, Pos2, Rect, ScrollArea, UiBuilder, Vec2b};
      2 use nostrdb::{Ndb, ProfileRecord, Transaction};
      3 use notedeck::{
      4     fonts::get_font_size, name::get_display_name, profile::get_profile_url, Images,
      5     NotedeckTextStyle,
      6 };
      7 use notedeck_ui::{
      8     anim::{AnimationHelper, ICON_EXPANSION_MULTIPLE},
      9     widgets::x_button,
     10     ProfilePic,
     11 };
     12 use tracing::error;
     13 
     14 /// Displays user profiles for the user to pick from.
     15 /// Useful for manually typing a username and selecting the profile desired
     16 pub struct MentionPickerView<'a> {
     17     ndb: &'a Ndb,
     18     txn: &'a Transaction,
     19     img_cache: &'a mut Images,
     20     results: &'a Vec<&'a [u8; 32]>,
     21 }
     22 
     23 pub enum MentionPickerResponse {
     24     SelectResult(Option<usize>),
     25     DeleteMention,
     26 }
     27 
     28 impl<'a> MentionPickerView<'a> {
     29     pub fn new(
     30         img_cache: &'a mut Images,
     31         ndb: &'a Ndb,
     32         txn: &'a Transaction,
     33         results: &'a Vec<&'a [u8; 32]>,
     34     ) -> Self {
     35         Self {
     36             ndb,
     37             txn,
     38             img_cache,
     39             results,
     40         }
     41     }
     42 
     43     fn show(&mut self, ui: &mut egui::Ui, width: f32) -> MentionPickerResponse {
     44         let mut selection = None;
     45         ui.vertical(|ui| {
     46             for (i, res) in self.results.iter().enumerate() {
     47                 let profile = match self.ndb.get_profile_by_pubkey(self.txn, res) {
     48                     Ok(rec) => rec,
     49                     Err(e) => {
     50                         error!("Error fetching profile for pubkey {:?}: {e}", res);
     51                         return;
     52                     }
     53                 };
     54 
     55                 if ui
     56                     .add(user_result(&profile, self.img_cache, i, width))
     57                     .clicked()
     58                 {
     59                     selection = Some(i)
     60                 }
     61             }
     62         });
     63 
     64         MentionPickerResponse::SelectResult(selection)
     65     }
     66 
     67     pub fn show_in_rect(&mut self, rect: egui::Rect, ui: &mut egui::Ui) -> MentionPickerResponse {
     68         let widget_id = ui.id().with("mention_results");
     69         let area_resp = egui::Area::new(widget_id)
     70             .order(egui::Order::Foreground)
     71             .fixed_pos(rect.left_top())
     72             .constrain_to(rect)
     73             .show(ui.ctx(), |ui| {
     74                 let inner_margin_size = 8.0;
     75                 egui::Frame::NONE
     76                     .fill(ui.visuals().panel_fill)
     77                     .show(ui, |ui| {
     78                         let width = rect.width() - (2.0 * inner_margin_size);
     79 
     80                         ui.allocate_space(vec2(ui.available_width(), inner_margin_size));
     81                         let close_button_resp = {
     82                             let close_button_size = 16.0;
     83                             let (close_section_rect, _) = ui.allocate_exact_size(
     84                                 vec2(width, close_button_size),
     85                                 egui::Sense::hover(),
     86                             );
     87                             let (_, button_rect) = close_section_rect.split_left_right_at_x(
     88                                 close_section_rect.right() - close_button_size,
     89                             );
     90                             let button_resp = ui.allocate_rect(button_rect, egui::Sense::click());
     91                             ui.allocate_new_ui(
     92                                 UiBuilder::new()
     93                                     .max_rect(close_section_rect)
     94                                     .layout(Layout::right_to_left(egui::Align::Center)),
     95                                 |ui| ui.add(x_button(button_resp.rect)).clicked(),
     96                             )
     97                             .inner
     98                         };
     99 
    100                         ui.allocate_space(vec2(ui.available_width(), inner_margin_size));
    101 
    102                         let scroll_resp = ScrollArea::vertical()
    103                             .max_width(rect.width())
    104                             .auto_shrink(Vec2b::FALSE)
    105                             .show(ui, |ui| self.show(ui, width));
    106                         ui.advance_cursor_after_rect(rect);
    107 
    108                         if close_button_resp {
    109                             MentionPickerResponse::DeleteMention
    110                         } else {
    111                             scroll_resp.inner
    112                         }
    113                     })
    114                     .inner
    115             });
    116 
    117         area_resp.inner
    118     }
    119 }
    120 
    121 fn user_result<'a>(
    122     profile: &'a ProfileRecord<'_>,
    123     cache: &'a mut Images,
    124     index: usize,
    125     width: f32,
    126 ) -> impl egui::Widget + 'a {
    127     move |ui: &mut egui::Ui| -> egui::Response {
    128         let min_img_size = 48.0;
    129         let max_image = min_img_size * ICON_EXPANSION_MULTIPLE;
    130         let spacing = 8.0;
    131         let body_font_size = get_font_size(ui.ctx(), &NotedeckTextStyle::Body);
    132 
    133         let animation_rect = {
    134             let max_width = ui.available_width();
    135             let extra_width = (max_width - width) / 2.0;
    136             let left = ui.cursor().left();
    137             let (rect, _) =
    138                 ui.allocate_exact_size(vec2(width + extra_width, max_image), egui::Sense::click());
    139 
    140             let (_, right) = rect.split_left_right_at_x(left + extra_width);
    141             right
    142         };
    143 
    144         let helper = AnimationHelper::new_from_rect(ui, ("user_result", index), animation_rect);
    145 
    146         let icon_rect = {
    147             let r = helper.get_animation_rect();
    148             let mut center = r.center();
    149             center.x = r.left() + (max_image / 2.0);
    150             let size = helper.scale_1d_pos(min_img_size);
    151             Rect::from_center_size(center, vec2(size, size))
    152         };
    153 
    154         let pfp_resp = ui.put(
    155             icon_rect,
    156             &mut ProfilePic::new(cache, get_profile_url(Some(profile)))
    157                 .size(helper.scale_1d_pos(min_img_size)),
    158         );
    159 
    160         let name_font = FontId::new(
    161             helper.scale_1d_pos(body_font_size),
    162             NotedeckTextStyle::Body.font_family(),
    163         );
    164         let painter = ui.painter_at(helper.get_animation_rect());
    165         let name_galley = painter.layout(
    166             get_display_name(Some(profile)).name().to_owned(),
    167             name_font,
    168             ui.visuals().text_color(),
    169             width,
    170         );
    171 
    172         let galley_pos = {
    173             let right_top = pfp_resp.rect.right_top();
    174             let galley_pos_y = pfp_resp.rect.center().y - (name_galley.rect.height() / 2.0);
    175             Pos2::new(right_top.x + spacing, galley_pos_y)
    176         };
    177 
    178         painter.galley(galley_pos, name_galley, ui.visuals().text_color());
    179         ui.advance_cursor_after_rect(helper.get_animation_rect());
    180 
    181         pfp_resp.union(helper.take_animation_response())
    182     }
    183 }