context: move note context button to its own file
Signed-off-by: William Casarin <jb55@jb55.com>
This commit is contained in:
@@ -23,7 +23,6 @@ pub mod login_manager;
|
|||||||
mod macos_key_storage;
|
mod macos_key_storage;
|
||||||
mod nav;
|
mod nav;
|
||||||
mod note;
|
mod note;
|
||||||
mod note_options;
|
|
||||||
mod notecache;
|
mod notecache;
|
||||||
mod post;
|
mod post;
|
||||||
mod post_action_executor;
|
mod post_action_executor;
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -111,8 +111,8 @@ pub fn render_note_preview(
|
|||||||
.options_button(true)
|
.options_button(true)
|
||||||
.show(ui);
|
.show(ui);
|
||||||
|
|
||||||
if let Some(selection) = resp.option_selection {
|
if let Some(context) = resp.context_selection {
|
||||||
selection.process(ui, ¬e);
|
context.process(ui, ¬e);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.response
|
.response
|
||||||
|
|||||||
158
src/ui/note/context.rs
Normal file
158
src/ui/note/context.rs
Normal file
@@ -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<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)
|
||||||
|
}
|
||||||
@@ -1,10 +1,12 @@
|
|||||||
pub mod contents;
|
pub mod contents;
|
||||||
|
pub mod context;
|
||||||
pub mod options;
|
pub mod options;
|
||||||
pub mod post;
|
pub mod post;
|
||||||
pub mod quote_repost;
|
pub mod quote_repost;
|
||||||
pub mod reply;
|
pub mod reply;
|
||||||
|
|
||||||
pub use contents::NoteContents;
|
pub use contents::NoteContents;
|
||||||
|
pub use context::{NoteContextButton, NoteContextSelection};
|
||||||
pub use options::NoteOptions;
|
pub use options::NoteOptions;
|
||||||
pub use post::{PostAction, PostResponse, PostView};
|
pub use post::{PostAction, PostResponse, PostView};
|
||||||
pub use quote_repost::QuoteRepostView;
|
pub use quote_repost::QuoteRepostView;
|
||||||
@@ -15,11 +17,10 @@ use crate::{
|
|||||||
app_style::NotedeckTextStyle,
|
app_style::NotedeckTextStyle,
|
||||||
colors,
|
colors,
|
||||||
imgcache::ImageCache,
|
imgcache::ImageCache,
|
||||||
note_options::NoteOptionSelection,
|
|
||||||
notecache::{CachedNote, NoteCache},
|
notecache::{CachedNote, NoteCache},
|
||||||
ui::{self, View},
|
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 enostr::NoteId;
|
||||||
use nostrdb::{Ndb, Note, NoteKey, NoteReply, Transaction};
|
use nostrdb::{Ndb, Note, NoteKey, NoteReply, Transaction};
|
||||||
|
|
||||||
@@ -36,7 +37,7 @@ pub struct NoteView<'a> {
|
|||||||
pub struct NoteResponse {
|
pub struct NoteResponse {
|
||||||
pub response: egui::Response,
|
pub response: egui::Response,
|
||||||
pub action: Option<BarAction>,
|
pub action: Option<BarAction>,
|
||||||
pub option_selection: Option<NoteOptionSelection>,
|
pub context_selection: Option<NoteContextSelection>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NoteResponse {
|
impl NoteResponse {
|
||||||
@@ -44,7 +45,7 @@ impl NoteResponse {
|
|||||||
Self {
|
Self {
|
||||||
response,
|
response,
|
||||||
action: None,
|
action: None,
|
||||||
option_selection: None,
|
context_selection: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,9 +53,9 @@ impl NoteResponse {
|
|||||||
Self { action, ..self }
|
Self { action, ..self }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn select_option(self, option_selection: Option<NoteOptionSelection>) -> Self {
|
pub fn select_option(self, context_selection: Option<NoteContextSelection>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
option_selection,
|
context_selection,
|
||||||
..self
|
..self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -406,9 +407,9 @@ impl<'a> NoteView<'a> {
|
|||||||
render_reltime(ui, cached_note, true);
|
render_reltime(ui, cached_note, true);
|
||||||
|
|
||||||
if options.has_options_button() {
|
if options.has_options_button() {
|
||||||
ui.with_layout(Layout::right_to_left(Align::Center), |ui| {
|
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
||||||
let more_options_resp = more_options_button(ui, note_key, 8.0);
|
let resp = ui.add(NoteContextButton::new(note_key));
|
||||||
options_context_menu(ui, more_options_resp)
|
NoteContextButton::menu(ui, resp)
|
||||||
})
|
})
|
||||||
.inner
|
.inner
|
||||||
} else {
|
} else {
|
||||||
@@ -425,7 +426,7 @@ impl<'a> NoteView<'a> {
|
|||||||
let note_key = self.note.key().expect("todo: support non-db notes");
|
let note_key = self.note.key().expect("todo: support non-db notes");
|
||||||
let txn = self.note.txn().expect("todo: support non-db notes");
|
let txn = self.note.txn().expect("todo: support non-db notes");
|
||||||
let mut note_action: Option<BarAction> = None;
|
let mut note_action: Option<BarAction> = None;
|
||||||
let mut selected_option: Option<NoteOptionSelection> = None;
|
let mut selected_option: Option<NoteContextSelection> = None;
|
||||||
let profile = self.ndb.get_profile_by_pubkey(txn, self.note.pubkey());
|
let profile = self.ndb.get_profile_by_pubkey(txn, self.note.pubkey());
|
||||||
let maybe_hitbox = maybe_note_hitbox(ui, note_key);
|
let maybe_hitbox = maybe_note_hitbox(ui, note_key);
|
||||||
|
|
||||||
@@ -445,7 +446,7 @@ impl<'a> NoteView<'a> {
|
|||||||
&profile,
|
&profile,
|
||||||
self.options(),
|
self.options(),
|
||||||
)
|
)
|
||||||
.option_selection;
|
.context_selection;
|
||||||
})
|
})
|
||||||
.response
|
.response
|
||||||
});
|
});
|
||||||
@@ -492,7 +493,7 @@ impl<'a> NoteView<'a> {
|
|||||||
&profile,
|
&profile,
|
||||||
self.options(),
|
self.options(),
|
||||||
)
|
)
|
||||||
.option_selection;
|
.context_selection;
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.spacing_mut().item_spacing.x = 2.0;
|
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)
|
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<NoteOptionSelection> {
|
|
||||||
let mut selected_option: Option<NoteOptionSelection> = 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<R>(
|
|
||||||
ui: &mut egui::Ui,
|
|
||||||
button_response: egui::Response,
|
|
||||||
add_contents: impl FnOnce(&mut egui::Ui) -> R,
|
|
||||||
) -> InnerResponse<Option<R>> {
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -125,7 +125,7 @@ impl<'a> ThreadView<'a> {
|
|||||||
action = Some(bar_action);
|
action = Some(bar_action);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(selection) = note_response.option_selection {
|
if let Some(selection) = note_response.context_selection {
|
||||||
selection.process(ui, ¬e);
|
selection.process(ui, ¬e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -158,8 +158,8 @@ fn timeline_ui(
|
|||||||
debug!("clicked note");
|
debug!("clicked note");
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(selection) = resp.option_selection {
|
if let Some(context) = resp.context_selection {
|
||||||
selection.process(ui, ¬e);
|
context.process(ui, ¬e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user