Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Note context menu refactoring #328

Merged
merged 9 commits into from
Sep 26, 2024
4 changes: 4 additions & 0 deletions enostr/src/pubkey.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ impl Pubkey {
Ok(Pubkey(data.1.try_into().unwrap()))
}
}

pub fn to_bech(&self) -> Option<String> {
nostr::bech32::encode::<nostr::bech32::Bech32>(HRP_NPUB, &self.0).ok()
}
}

impl fmt::Display for Pubkey {
Expand Down
8 changes: 1 addition & 7 deletions src/notecache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ impl NoteCache {
pub struct CachedNote {
reltime: TimeCached<String>,
pub reply: NoteReplyBuf,
pub bar_open: bool,
}

impl CachedNote {
Expand All @@ -46,12 +45,7 @@ impl CachedNote {
Box::new(move || time_ago_since(created_at)),
);
let reply = NoteReply::new(note.tags()).to_owned();
let bar_open = false;
CachedNote {
reltime,
reply,
bar_open,
}
CachedNote { reltime, reply }
}

pub fn reltime_str_mut(&mut self) -> &str {
Expand Down
7 changes: 6 additions & 1 deletion src/ui/note/contents.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,12 +103,17 @@ pub fn render_note_preview(
ui.visuals().noninteractive().bg_stroke.color,
))
.show(ui, |ui| {
ui::NoteView::new(ndb, note_cache, img_cache, &note)
let resp = ui::NoteView::new(ndb, note_cache, img_cache, &note)
.actionbar(false)
.small_pfp(true)
.wide(true)
.note_previews(false)
.options_button(true)
.show(ui);

if let Some(context) = resp.context_selection {
context.process(ui, &note);
}
})
.response
}
Expand Down
177 changes: 177 additions & 0 deletions src/ui/note/context.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
use crate::colors;
use egui::{Rect, 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 {
put_at: Option<Rect>,
note_key: NoteKey,
}

impl egui::Widget for NoteContextButton {
fn ui(self, ui: &mut egui::Ui) -> egui::Response {
let r = if let Some(r) = self.put_at {
r
} else {
let mut place = ui.available_rect_before_wrap();
let size = Self::max_width();
place.set_width(size);
place.set_height(size);
place
};

Self::show(ui, self.note_key, r)
}
}

impl NoteContextButton {
pub fn new(note_key: NoteKey) -> Self {
let put_at: Option<Rect> = None;
NoteContextButton { note_key, put_at }
}

pub fn place_at(mut self, rect: Rect) -> Self {
self.put_at = Some(rect);
self
}

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, put_at: Rect) -> egui::Response {
let id = ui.id().with(("more_options_anim", note_key));

let min_radius = Self::min_radius();
let anim_speed = 0.05;
let response = ui.interact(put_at, id, egui::Sense::click());

let hovered = response.hovered();
let animation_progress = ui.ctx().animate_bool_with_time(id, hovered, anim_speed);

if hovered {
ui.ctx().set_cursor_icon(egui::CursorIcon::PointingHand);
}

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 = put_at.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(put_at);
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<NoteContextSelection> {
let mut context_selection: Option<NoteContextSelection> = 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<R>(
ui: &mut egui::Ui,
button_response: egui::Response,
add_contents: impl FnOnce(&mut egui::Ui) -> R,
) -> egui::InnerResponse<Option<R>> {
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)
}
Loading
Loading