context.rs (6302B)
1 use egui::{Rect, Vec2}; 2 use nostrdb::NoteKey; 3 use notedeck::{tr, BroadcastContext, Localization, NoteContextSelection}; 4 5 pub struct NoteContextButton { 6 put_at: Option<Rect>, 7 note_key: NoteKey, 8 } 9 10 impl egui::Widget for NoteContextButton { 11 fn ui(self, ui: &mut egui::Ui) -> egui::Response { 12 let r = if let Some(r) = self.put_at { 13 r 14 } else { 15 let mut place = ui.available_rect_before_wrap(); 16 let size = Self::max_width(); 17 place.set_width(size); 18 place.set_height(size); 19 place 20 }; 21 22 Self::show(ui, self.note_key, r) 23 } 24 } 25 26 impl NoteContextButton { 27 pub fn new(note_key: NoteKey) -> Self { 28 let put_at: Option<Rect> = None; 29 NoteContextButton { note_key, put_at } 30 } 31 32 pub fn place_at(mut self, rect: Rect) -> Self { 33 self.put_at = Some(rect); 34 self 35 } 36 37 pub fn max_width() -> f32 { 38 Self::max_radius() * 3.0 + Self::max_distance_between_circles() * 2.0 39 } 40 41 pub fn size() -> Vec2 { 42 let width = Self::max_width(); 43 egui::vec2(width, width) 44 } 45 46 fn max_radius() -> f32 { 47 4.0 48 } 49 50 fn min_radius() -> f32 { 51 2.0 52 } 53 54 fn max_distance_between_circles() -> f32 { 55 2.0 56 } 57 58 fn expansion_multiple() -> f32 { 59 2.0 60 } 61 62 fn min_distance_between_circles() -> f32 { 63 Self::max_distance_between_circles() / Self::expansion_multiple() 64 } 65 66 #[profiling::function] 67 pub fn show(ui: &mut egui::Ui, note_key: NoteKey, put_at: Rect) -> egui::Response { 68 let id = ui.id().with(("more_options_anim", note_key)); 69 70 let min_radius = Self::min_radius(); 71 let anim_speed = 0.05; 72 let response = ui.interact(put_at, id, egui::Sense::click()); 73 74 let hovered = response.hovered(); 75 let animation_progress = ui.ctx().animate_bool_with_time(id, hovered, anim_speed); 76 77 if hovered { 78 ui.ctx().set_cursor_icon(egui::CursorIcon::PointingHand); 79 } 80 81 let min_distance = Self::min_distance_between_circles(); 82 let cur_distance = min_distance 83 + (Self::max_distance_between_circles() - min_distance) * animation_progress; 84 85 let cur_radius = min_radius + (Self::max_radius() - min_radius) * animation_progress; 86 87 let center = put_at.center(); 88 let left_circle_center = center - egui::vec2(cur_distance + cur_radius, 0.0); 89 let right_circle_center = center + egui::vec2(cur_distance + cur_radius, 0.0); 90 91 let translated_radius = (cur_radius - 1.0) / 2.0; 92 93 // This works in both themes 94 let color = ui.style().visuals.noninteractive().fg_stroke.color; 95 96 // Draw circles 97 let painter = ui.painter_at(put_at); 98 painter.circle_filled(left_circle_center, translated_radius, color); 99 painter.circle_filled(center, translated_radius, color); 100 painter.circle_filled(right_circle_center, translated_radius, color); 101 102 response 103 } 104 105 #[profiling::function] 106 pub fn menu( 107 ui: &mut egui::Ui, 108 i18n: &mut Localization, 109 button_response: egui::Response, 110 ) -> Option<NoteContextSelection> { 111 let mut context_selection: Option<NoteContextSelection> = None; 112 113 stationary_arbitrary_menu_button(ui, button_response, |ui| { 114 ui.set_max_width(200.0); 115 116 // Debug: Check what the tr! macro returns 117 let copy_text = tr!( 118 i18n, 119 "Copy Text", 120 "Copy the text content of the note to clipboard" 121 ); 122 tracing::debug!("Copy Text translation: '{}'", copy_text); 123 124 if ui.button(copy_text).clicked() { 125 context_selection = Some(NoteContextSelection::CopyText); 126 ui.close_menu(); 127 } 128 if ui 129 .button(tr!( 130 i18n, 131 "Copy Pubkey", 132 "Copy the author's public key to clipboard" 133 )) 134 .clicked() 135 { 136 context_selection = Some(NoteContextSelection::CopyPubkey); 137 ui.close_menu(); 138 } 139 if ui 140 .button(tr!( 141 i18n, 142 "Copy Note ID", 143 "Copy the unique note identifier to clipboard" 144 )) 145 .clicked() 146 { 147 context_selection = Some(NoteContextSelection::CopyNoteId); 148 ui.close_menu(); 149 } 150 if ui 151 .button(tr!( 152 i18n, 153 "Copy Note JSON", 154 "Copy the raw note data in JSON format to clipboard" 155 )) 156 .clicked() 157 { 158 context_selection = Some(NoteContextSelection::CopyNoteJSON); 159 ui.close_menu(); 160 } 161 if ui 162 .button(tr!( 163 i18n, 164 "Broadcast", 165 "Broadcast the note to all connected relays" 166 )) 167 .clicked() 168 { 169 context_selection = Some(NoteContextSelection::Broadcast( 170 BroadcastContext::Everywhere, 171 )); 172 ui.close_menu(); 173 } 174 if ui 175 .button(tr!( 176 i18n, 177 "Broadcast Local", 178 "Broadcast the note only to local network relays" 179 )) 180 .clicked() 181 { 182 context_selection = Some(NoteContextSelection::Broadcast( 183 BroadcastContext::LocalNetwork, 184 )); 185 ui.close_menu(); 186 } 187 }); 188 189 context_selection 190 } 191 } 192 193 fn stationary_arbitrary_menu_button<R>( 194 ui: &mut egui::Ui, 195 button_response: egui::Response, 196 add_contents: impl FnOnce(&mut egui::Ui) -> R, 197 ) -> egui::InnerResponse<Option<R>> { 198 let bar_id = ui.id(); 199 let mut bar_state = egui::menu::BarState::load(ui.ctx(), bar_id); 200 201 let inner = bar_state.bar_menu(&button_response, add_contents); 202 203 bar_state.store(ui.ctx(), bar_id); 204 egui::InnerResponse::new(inner.map(|r| r.inner), button_response) 205 }