notedeck

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

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 }