context.rs (5476B)
1 use crate::colors; 2 use egui::{Rect, Vec2}; 3 use enostr::{NoteId, Pubkey}; 4 use nostrdb::{Note, NoteKey}; 5 6 #[derive(Clone)] 7 #[allow(clippy::enum_variant_names)] 8 pub enum NoteContextSelection { 9 CopyText, 10 CopyPubkey, 11 CopyNoteId, 12 } 13 14 impl NoteContextSelection { 15 pub fn process(&self, ui: &mut egui::Ui, note: &Note<'_>) { 16 match self { 17 NoteContextSelection::CopyText => { 18 ui.output_mut(|w| { 19 w.copied_text = note.content().to_string(); 20 }); 21 } 22 NoteContextSelection::CopyPubkey => { 23 ui.output_mut(|w| { 24 if let Some(bech) = Pubkey::new(*note.pubkey()).to_bech() { 25 w.copied_text = bech; 26 } 27 }); 28 } 29 NoteContextSelection::CopyNoteId => { 30 ui.output_mut(|w| { 31 if let Some(bech) = NoteId::new(*note.id()).to_bech() { 32 w.copied_text = bech; 33 } 34 }); 35 } 36 } 37 } 38 } 39 40 pub struct NoteContextButton { 41 put_at: Option<Rect>, 42 note_key: NoteKey, 43 } 44 45 impl egui::Widget for NoteContextButton { 46 fn ui(self, ui: &mut egui::Ui) -> egui::Response { 47 let r = if let Some(r) = self.put_at { 48 r 49 } else { 50 let mut place = ui.available_rect_before_wrap(); 51 let size = Self::max_width(); 52 place.set_width(size); 53 place.set_height(size); 54 place 55 }; 56 57 Self::show(ui, self.note_key, r) 58 } 59 } 60 61 impl NoteContextButton { 62 pub fn new(note_key: NoteKey) -> Self { 63 let put_at: Option<Rect> = None; 64 NoteContextButton { note_key, put_at } 65 } 66 67 pub fn place_at(mut self, rect: Rect) -> Self { 68 self.put_at = Some(rect); 69 self 70 } 71 72 pub fn max_width() -> f32 { 73 Self::max_radius() * 3.0 + Self::max_distance_between_circles() * 2.0 74 } 75 76 pub fn size() -> Vec2 { 77 let width = Self::max_width(); 78 egui::vec2(width, width) 79 } 80 81 fn max_radius() -> f32 { 82 8.0 83 } 84 85 fn min_radius() -> f32 { 86 Self::max_radius() / Self::expansion_multiple() 87 } 88 89 fn max_distance_between_circles() -> f32 { 90 2.0 91 } 92 93 fn expansion_multiple() -> f32 { 94 2.0 95 } 96 97 fn min_distance_between_circles() -> f32 { 98 Self::max_distance_between_circles() / Self::expansion_multiple() 99 } 100 101 pub fn show(ui: &mut egui::Ui, note_key: NoteKey, put_at: Rect) -> egui::Response { 102 #[cfg(feature = "profiling")] 103 puffin::profile_function!(); 104 105 let id = ui.id().with(("more_options_anim", note_key)); 106 107 let min_radius = Self::min_radius(); 108 let anim_speed = 0.05; 109 let response = ui.interact(put_at, id, egui::Sense::click()); 110 111 let hovered = response.hovered(); 112 let animation_progress = ui.ctx().animate_bool_with_time(id, hovered, anim_speed); 113 114 if hovered { 115 ui.ctx().set_cursor_icon(egui::CursorIcon::PointingHand); 116 } 117 118 let min_distance = Self::min_distance_between_circles(); 119 let cur_distance = min_distance 120 + (Self::max_distance_between_circles() - min_distance) * animation_progress; 121 122 let cur_radius = min_radius + (Self::max_radius() - min_radius) * animation_progress; 123 124 let center = put_at.center(); 125 let left_circle_center = center - egui::vec2(cur_distance + cur_radius, 0.0); 126 let right_circle_center = center + egui::vec2(cur_distance + cur_radius, 0.0); 127 128 let translated_radius = (cur_radius - 1.0) / 2.0; 129 130 // This works in both themes 131 let color = colors::GRAY_SECONDARY; 132 133 // Draw circles 134 let painter = ui.painter_at(put_at); 135 painter.circle_filled(left_circle_center, translated_radius, color); 136 painter.circle_filled(center, translated_radius, color); 137 painter.circle_filled(right_circle_center, translated_radius, color); 138 139 response 140 } 141 142 pub fn menu( 143 ui: &mut egui::Ui, 144 button_response: egui::Response, 145 ) -> Option<NoteContextSelection> { 146 #[cfg(feature = "profiling")] 147 puffin::profile_function!(); 148 149 let mut context_selection: Option<NoteContextSelection> = None; 150 151 stationary_arbitrary_menu_button(ui, button_response, |ui| { 152 ui.set_max_width(200.0); 153 if ui.button("Copy text").clicked() { 154 context_selection = Some(NoteContextSelection::CopyText); 155 ui.close_menu(); 156 } 157 if ui.button("Copy user public key").clicked() { 158 context_selection = Some(NoteContextSelection::CopyPubkey); 159 ui.close_menu(); 160 } 161 if ui.button("Copy note id").clicked() { 162 context_selection = Some(NoteContextSelection::CopyNoteId); 163 ui.close_menu(); 164 } 165 }); 166 167 context_selection 168 } 169 } 170 171 fn stationary_arbitrary_menu_button<R>( 172 ui: &mut egui::Ui, 173 button_response: egui::Response, 174 add_contents: impl FnOnce(&mut egui::Ui) -> R, 175 ) -> egui::InnerResponse<Option<R>> { 176 let bar_id = ui.id(); 177 let mut bar_state = egui::menu::BarState::load(ui.ctx(), bar_id); 178 179 let inner = bar_state.bar_menu(&button_response, add_contents); 180 181 bar_state.store(ui.ctx(), bar_id); 182 egui::InnerResponse::new(inner.map(|r| r.inner), button_response) 183 }