notedeck

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

search_results.rs (7277B)


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