notedeck

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

search_results.rs (7182B)


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