diff --git a/src/lib.rs b/src/lib.rs index 676d87e..22610e4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -23,7 +23,6 @@ pub mod login_manager; mod macos_key_storage; mod nav; mod note; -mod note_options; mod notecache; mod post; mod post_action_executor; diff --git a/src/note_options.rs b/src/note_options.rs deleted file mode 100644 index adbf4e1..0000000 --- a/src/note_options.rs +++ /dev/null @@ -1,36 +0,0 @@ -use enostr::{NoteId, Pubkey}; -use nostrdb::Note; - -#[derive(Clone)] -#[allow(clippy::enum_variant_names)] -pub enum NoteOptionSelection { - CopyText, - CopyPubkey, - CopyNoteId, -} - -impl NoteOptionSelection { - pub fn process(&self, ui: &mut egui::Ui, note: &Note<'_>) { - match self { - NoteOptionSelection::CopyText => { - ui.output_mut(|w| { - w.copied_text = note.content().to_string(); - }); - } - NoteOptionSelection::CopyPubkey => { - ui.output_mut(|w| { - if let Some(bech) = Pubkey::new(*note.pubkey()).to_bech() { - w.copied_text = bech; - } - }); - } - NoteOptionSelection::CopyNoteId => { - ui.output_mut(|w| { - if let Some(bech) = NoteId::new(*note.id()).to_bech() { - w.copied_text = bech; - } - }); - } - } - } -} diff --git a/src/ui/note/contents.rs b/src/ui/note/contents.rs index cb55050..084eb75 100644 --- a/src/ui/note/contents.rs +++ b/src/ui/note/contents.rs @@ -111,8 +111,8 @@ pub fn render_note_preview( .options_button(true) .show(ui); - if let Some(selection) = resp.option_selection { - selection.process(ui, ¬e); + if let Some(context) = resp.context_selection { + context.process(ui, ¬e); } }) .response diff --git a/src/ui/note/context.rs b/src/ui/note/context.rs new file mode 100644 index 0000000..ba4aed4 --- /dev/null +++ b/src/ui/note/context.rs @@ -0,0 +1,158 @@ +use crate::colors; +use egui::Vec2; +use enostr::{NoteId, Pubkey}; +use nostrdb::{Note, NoteKey}; + +#[derive(Clone)] +#[allow(clippy::enum_variant_names)] +pub enum NoteContextSelection { + CopyText, + CopyPubkey, + CopyNoteId, +} + +impl NoteContextSelection { + pub fn process(&self, ui: &mut egui::Ui, note: &Note<'_>) { + match self { + NoteContextSelection::CopyText => { + ui.output_mut(|w| { + w.copied_text = note.content().to_string(); + }); + } + NoteContextSelection::CopyPubkey => { + ui.output_mut(|w| { + if let Some(bech) = Pubkey::new(*note.pubkey()).to_bech() { + w.copied_text = bech; + } + }); + } + NoteContextSelection::CopyNoteId => { + ui.output_mut(|w| { + if let Some(bech) = NoteId::new(*note.id()).to_bech() { + w.copied_text = bech; + } + }); + } + } + } +} + +pub struct NoteContextButton { + note_key: NoteKey, +} + +impl egui::Widget for NoteContextButton { + fn ui(self, ui: &mut egui::Ui) -> egui::Response { + Self::show(ui, self.note_key) + } +} + +impl NoteContextButton { + pub fn new(note_key: NoteKey) -> Self { + NoteContextButton { note_key } + } + + pub fn max_width() -> f32 { + Self::max_radius() * 3.0 + Self::max_distance_between_circles() * 2.0 + } + + pub fn size() -> Vec2 { + let width = Self::max_width(); + egui::vec2(width, width) + } + + fn max_radius() -> f32 { + 8.0 + } + + fn min_radius() -> f32 { + Self::max_radius() / Self::expansion_multiple() + } + + fn max_distance_between_circles() -> f32 { + 2.0 + } + + fn expansion_multiple() -> f32 { + 2.0 + } + + fn min_distance_between_circles() -> f32 { + Self::max_distance_between_circles() / Self::expansion_multiple() + } + + pub fn show(ui: &mut egui::Ui, note_key: NoteKey) -> egui::Response { + let id = ui.id().with(("more_options_anim", note_key)); + + let min_radius = Self::min_radius(); + let anim_speed = 0.05; + let size = Self::size(); + let (rect, response) = ui.allocate_exact_size(size, egui::Sense::click()); + + let animation_progress = + ui.ctx() + .animate_bool_with_time(id, response.hovered(), anim_speed); + + let min_distance = Self::min_distance_between_circles(); + let cur_distance = min_distance + + (Self::max_distance_between_circles() - min_distance) * animation_progress; + + let cur_radius = min_radius + (Self::max_radius() - min_radius) * animation_progress; + + let center = rect.center(); + let left_circle_center = center - egui::vec2(cur_distance + cur_radius, 0.0); + let right_circle_center = center + egui::vec2(cur_distance + cur_radius, 0.0); + + let translated_radius = (cur_radius - 1.0) / 2.0; + + // This works in both themes + let color = colors::GRAY_SECONDARY; + + // Draw circles + let painter = ui.painter_at(rect); + painter.circle_filled(left_circle_center, translated_radius, color); + painter.circle_filled(center, translated_radius, color); + painter.circle_filled(right_circle_center, translated_radius, color); + + response + } + + pub fn menu( + ui: &mut egui::Ui, + button_response: egui::Response, + ) -> Option { + let mut context_selection: Option = None; + + stationary_arbitrary_menu_button(ui, button_response, |ui| { + ui.set_max_width(200.0); + if ui.button("Copy text").clicked() { + context_selection = Some(NoteContextSelection::CopyText); + ui.close_menu(); + } + if ui.button("Copy user public key").clicked() { + context_selection = Some(NoteContextSelection::CopyPubkey); + ui.close_menu(); + } + if ui.button("Copy note id").clicked() { + context_selection = Some(NoteContextSelection::CopyNoteId); + ui.close_menu(); + } + }); + + context_selection + } +} + +fn stationary_arbitrary_menu_button( + ui: &mut egui::Ui, + button_response: egui::Response, + add_contents: impl FnOnce(&mut egui::Ui) -> R, +) -> egui::InnerResponse> { + let bar_id = ui.id(); + let mut bar_state = egui::menu::BarState::load(ui.ctx(), bar_id); + + let inner = bar_state.bar_menu(&button_response, add_contents); + + bar_state.store(ui.ctx(), bar_id); + egui::InnerResponse::new(inner.map(|r| r.inner), button_response) +} diff --git a/src/ui/note/mod.rs b/src/ui/note/mod.rs index 87690f1..6d9a5fb 100644 --- a/src/ui/note/mod.rs +++ b/src/ui/note/mod.rs @@ -1,10 +1,12 @@ pub mod contents; +pub mod context; pub mod options; pub mod post; pub mod quote_repost; pub mod reply; pub use contents::NoteContents; +pub use context::{NoteContextButton, NoteContextSelection}; pub use options::NoteOptions; pub use post::{PostAction, PostResponse, PostView}; pub use quote_repost::QuoteRepostView; @@ -15,11 +17,10 @@ use crate::{ app_style::NotedeckTextStyle, colors, imgcache::ImageCache, - note_options::NoteOptionSelection, notecache::{CachedNote, NoteCache}, ui::{self, View}, }; -use egui::{menu::BarState, Align, Id, InnerResponse, Label, Layout, Response, RichText, Sense}; +use egui::{Id, Label, Response, RichText, Sense}; use enostr::NoteId; use nostrdb::{Ndb, Note, NoteKey, NoteReply, Transaction}; @@ -36,7 +37,7 @@ pub struct NoteView<'a> { pub struct NoteResponse { pub response: egui::Response, pub action: Option, - pub option_selection: Option, + pub context_selection: Option, } impl NoteResponse { @@ -44,7 +45,7 @@ impl NoteResponse { Self { response, action: None, - option_selection: None, + context_selection: None, } } @@ -52,9 +53,9 @@ impl NoteResponse { Self { action, ..self } } - pub fn select_option(self, option_selection: Option) -> Self { + pub fn select_option(self, context_selection: Option) -> Self { Self { - option_selection, + context_selection, ..self } } @@ -406,9 +407,9 @@ impl<'a> NoteView<'a> { render_reltime(ui, cached_note, true); if options.has_options_button() { - ui.with_layout(Layout::right_to_left(Align::Center), |ui| { - let more_options_resp = more_options_button(ui, note_key, 8.0); - options_context_menu(ui, more_options_resp) + ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { + let resp = ui.add(NoteContextButton::new(note_key)); + NoteContextButton::menu(ui, resp) }) .inner } else { @@ -425,7 +426,7 @@ impl<'a> NoteView<'a> { let note_key = self.note.key().expect("todo: support non-db notes"); let txn = self.note.txn().expect("todo: support non-db notes"); let mut note_action: Option = None; - let mut selected_option: Option = None; + let mut selected_option: Option = None; let profile = self.ndb.get_profile_by_pubkey(txn, self.note.pubkey()); let maybe_hitbox = maybe_note_hitbox(ui, note_key); @@ -445,7 +446,7 @@ impl<'a> NoteView<'a> { &profile, self.options(), ) - .option_selection; + .context_selection; }) .response }); @@ -492,7 +493,7 @@ impl<'a> NoteView<'a> { &profile, self.options(), ) - .option_selection; + .context_selection; ui.horizontal(|ui| { ui.spacing_mut().item_spacing.x = 2.0; @@ -681,81 +682,3 @@ fn quote_repost_button(ui: &mut egui::Ui, note_key: NoteKey) -> egui::Response { resp.union(put_resp) } - -fn more_options_button(ui: &mut egui::Ui, note_key: NoteKey, max_height: f32) -> egui::Response { - let id = ui.id().with(("more_options_anim", note_key)); - - let expansion_multiple = 2.0; - let max_radius = max_height; - let min_radius = max_radius / expansion_multiple; - let max_distance_between_circles = 2.0; - let min_distance_between_circles = max_distance_between_circles / expansion_multiple; - let max_width = max_radius * 3.0 + max_distance_between_circles * 2.0; - - let anim_speed = 0.05; - let expanded_size = egui::vec2(max_width, max_height); - let (rect, response) = ui.allocate_exact_size(expanded_size, egui::Sense::click()); - - let animation_progress = ui - .ctx() - .animate_bool_with_time(id, response.hovered(), anim_speed); - let cur_distance = min_distance_between_circles - + (max_distance_between_circles - min_distance_between_circles) * animation_progress; - let cur_radius = min_radius + (max_radius - min_radius) * animation_progress; - - let center = rect.center(); - let left_circle_center = center - egui::vec2(cur_distance + cur_radius, 0.0); - let right_circle_center = center + egui::vec2(cur_distance + cur_radius, 0.0); - - let translated_radius = (cur_radius - 1.0) / 2.0; - - // This works in both themes - let color = colors::GRAY_SECONDARY; - - // Draw circles - let painter = ui.painter_at(rect); - painter.circle_filled(left_circle_center, translated_radius, color); - painter.circle_filled(center, translated_radius, color); - painter.circle_filled(right_circle_center, translated_radius, color); - - response -} - -fn options_context_menu( - ui: &mut egui::Ui, - more_options_button_resp: egui::Response, -) -> Option { - let mut selected_option: Option = None; - - stationary_arbitrary_menu_button(ui, more_options_button_resp, |ui| { - ui.set_max_width(200.0); - if ui.button("Copy text").clicked() { - selected_option = Some(NoteOptionSelection::CopyText); - ui.close_menu(); - } - if ui.button("Copy user public key").clicked() { - selected_option = Some(NoteOptionSelection::CopyPubkey); - ui.close_menu(); - } - if ui.button("Copy note id").clicked() { - selected_option = Some(NoteOptionSelection::CopyNoteId); - ui.close_menu(); - } - }); - - selected_option -} - -fn stationary_arbitrary_menu_button( - ui: &mut egui::Ui, - button_response: egui::Response, - add_contents: impl FnOnce(&mut egui::Ui) -> R, -) -> InnerResponse> { - let bar_id = ui.id(); - let mut bar_state = BarState::load(ui.ctx(), bar_id); - - let inner = bar_state.bar_menu(&button_response, add_contents); - - bar_state.store(ui.ctx(), bar_id); - InnerResponse::new(inner.map(|r| r.inner), button_response) -} diff --git a/src/ui/thread.rs b/src/ui/thread.rs index 76ca581..44ff6f8 100644 --- a/src/ui/thread.rs +++ b/src/ui/thread.rs @@ -125,7 +125,7 @@ impl<'a> ThreadView<'a> { action = Some(bar_action); } - if let Some(selection) = note_response.option_selection { + if let Some(selection) = note_response.context_selection { selection.process(ui, ¬e); } }); diff --git a/src/ui/timeline.rs b/src/ui/timeline.rs index bbea0a5..f0e4ace 100644 --- a/src/ui/timeline.rs +++ b/src/ui/timeline.rs @@ -158,8 +158,8 @@ fn timeline_ui( debug!("clicked note"); } - if let Some(selection) = resp.option_selection { - selection.process(ui, ¬e); + if let Some(context) = resp.context_selection { + context.process(ui, ¬e); } });