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 }