notedeck

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

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 }