From 2fee8045432e4258f0347d6e042c2a66b159a1fa Mon Sep 17 00:00:00 2001 From: Bu5hm4nn <“bu5hm4nn@users.noreply.github.com”> Date: Mon, 13 Mar 2023 16:50:35 -0600 Subject: [PATCH 01/25] Let note module own the info on how to render a note which is used by themes --- src/ui/feed/mod.rs | 1 + src/ui/feed/note/mod.rs | 19 +++++++++++++-- src/ui/theme/classic.rs | 20 ++++++++-------- src/ui/theme/default.rs | 20 ++++++++-------- src/ui/theme/mod.rs | 53 +++++++++++++++-------------------------- src/ui/theme/roundy.rs | 20 ++++++++-------- 6 files changed, 67 insertions(+), 66 deletions(-) diff --git a/src/ui/feed/mod.rs b/src/ui/feed/mod.rs index 10279fd8..2f376dc6 100644 --- a/src/ui/feed/mod.rs +++ b/src/ui/feed/mod.rs @@ -7,6 +7,7 @@ use egui::{Context, Frame, RichText, ScrollArea, SelectableLabel, Ui, Vec2}; use nostr_types::Id; mod note; +pub use note::NoteRenderData; mod post; struct FeedNoteParams { diff --git a/src/ui/feed/note/mod.rs b/src/ui/feed/note/mod.rs index 33be0d34..274056c2 100644 --- a/src/ui/feed/note/mod.rs +++ b/src/ui/feed/note/mod.rs @@ -3,7 +3,6 @@ use crate::comms::ToOverlordMessage; use crate::feed::FeedKind; use crate::globals::{Globals, GLOBALS}; use crate::people::DbPerson; -use crate::ui::theme::PostProperties; use crate::ui::widgets::CopyButton; use crate::ui::{GossipUi, Page}; use crate::AVATAR_SIZE_F32; @@ -57,6 +56,22 @@ impl NoteData { } } +pub struct NoteRenderData { + pub height: f32, + /// Post height + pub is_new: bool, + /// This message is the focus of the view (formerly called is_main_event) + pub is_focused: bool, + /// This message is part of a thread + pub is_thread: bool, + /// Is this the first post in the display? + pub is_first: bool, + /// Is this the last post in the display + pub is_last: bool, + /// Position in the thread, focused message = 0 + pub thread_position: i32, +} + pub(super) fn render_note( app: &mut GossipUi, ctx: &Context, @@ -102,7 +117,7 @@ pub(super) fn render_note( } else { &0.0 }; - let post_properties = PostProperties { + let post_properties = NoteRenderData { height: *height, is_new, is_thread: threaded, diff --git a/src/ui/theme/classic.rs b/src/ui/theme/classic.rs index a19336ac..9ec6034c 100644 --- a/src/ui/theme/classic.rs +++ b/src/ui/theme/classic.rs @@ -1,4 +1,4 @@ -use super::{FeedProperties, PostProperties, ThemeDef}; +use super::{FeedProperties, NoteRenderData, ThemeDef}; use crate::ui::HighlightType; use eframe::egui::style::{Selection, WidgetVisuals, Widgets}; use eframe::egui::{FontDefinitions, Margin, RichText, Style, TextFormat, TextStyle, Visuals}; @@ -378,15 +378,15 @@ impl ThemeDef for ClassicTheme { fn feed_scroll_stroke(_dark_mode: bool, _feed: &FeedProperties) -> Stroke { Stroke::NONE } - fn feed_post_separator_stroke(dark_mode: bool, _post: &PostProperties) -> Stroke { + fn feed_post_separator_stroke(dark_mode: bool, _post: &NoteRenderData) -> Stroke { if dark_mode { Stroke::new(1.0, Color32::from_gray(72)) } else { Stroke::new(1.0, Color32::from_gray(192)) } } - fn feed_post_outer_indent(_ui: &mut eframe::egui::Ui, _post: &PostProperties) {} - fn feed_post_inner_indent(ui: &mut eframe::egui::Ui, post: &PostProperties) { + fn feed_post_outer_indent(_ui: &mut eframe::egui::Ui, _post: &NoteRenderData) {} + fn feed_post_inner_indent(ui: &mut eframe::egui::Ui, post: &NoteRenderData) { if post.is_thread { let space = 100.0 * (10.0 - (1000.0 / (post.thread_position as f32 + 100.0))); ui.add_space(space); @@ -399,7 +399,7 @@ impl ThemeDef for ClassicTheme { } } } - fn feed_frame_inner_margin(_post: &PostProperties) -> Margin { + fn feed_frame_inner_margin(_post: &NoteRenderData) -> Margin { Margin { left: 0.0, top: 4.0, @@ -407,16 +407,16 @@ impl ThemeDef for ClassicTheme { bottom: 0.0, } } - fn feed_frame_outer_margin(_post: &PostProperties) -> Margin { + fn feed_frame_outer_margin(_post: &NoteRenderData) -> Margin { Margin::default() } - fn feed_frame_rounding(_post: &PostProperties) -> Rounding { + fn feed_frame_rounding(_post: &NoteRenderData) -> Rounding { Rounding::default() } - fn feed_frame_shadow(_dark_mode: bool, _post: &PostProperties) -> Shadow { + fn feed_frame_shadow(_dark_mode: bool, _post: &NoteRenderData) -> Shadow { Shadow::default() } - fn feed_frame_fill(dark_mode: bool, post: &PostProperties) -> Color32 { + fn feed_frame_fill(dark_mode: bool, post: &NoteRenderData) -> Color32 { if post.is_new { if dark_mode { Color32::from_rgb(60, 0, 0) @@ -431,7 +431,7 @@ impl ThemeDef for ClassicTheme { } } } - fn feed_frame_stroke(_dark_mode: bool, _post: &PostProperties) -> Stroke { + fn feed_frame_stroke(_dark_mode: bool, _post: &NoteRenderData) -> Stroke { Stroke::NONE } diff --git a/src/ui/theme/default.rs b/src/ui/theme/default.rs index 8dffd59c..86ee17c3 100644 --- a/src/ui/theme/default.rs +++ b/src/ui/theme/default.rs @@ -1,4 +1,4 @@ -use super::{FeedProperties, PostProperties, ThemeDef}; +use super::{FeedProperties, NoteRenderData, ThemeDef}; use crate::ui::HighlightType; use eframe::egui::style::{Selection, WidgetVisuals, Widgets}; use eframe::egui::{ @@ -381,11 +381,11 @@ impl ThemeDef for DefaultTheme { fn feed_scroll_stroke(_dark_mode: bool, _feed: &FeedProperties) -> Stroke { Stroke::NONE } - fn feed_post_separator_stroke(_dark_mode: bool, _post: &PostProperties) -> Stroke { + fn feed_post_separator_stroke(_dark_mode: bool, _post: &NoteRenderData) -> Stroke { Stroke::NONE } - fn feed_post_outer_indent(_ui: &mut eframe::egui::Ui, _post: &PostProperties) {} - fn feed_post_inner_indent(ui: &mut eframe::egui::Ui, post: &PostProperties) { + fn feed_post_outer_indent(_ui: &mut eframe::egui::Ui, _post: &NoteRenderData) {} + fn feed_post_inner_indent(ui: &mut eframe::egui::Ui, post: &NoteRenderData) { if post.is_thread { if post.thread_position > 0 { let space = 150.0 * (10.0 - (1000.0 / (post.thread_position as f32 + 100.0))); @@ -421,7 +421,7 @@ impl ThemeDef for DefaultTheme { } } } - fn feed_frame_inner_margin(_post: &PostProperties) -> Margin { + fn feed_frame_inner_margin(_post: &NoteRenderData) -> Margin { Margin { left: 10.0, top: 14.0, @@ -429,16 +429,16 @@ impl ThemeDef for DefaultTheme { bottom: 6.0, } } - fn feed_frame_outer_margin(_post: &PostProperties) -> Margin { + fn feed_frame_outer_margin(_post: &NoteRenderData) -> Margin { Margin::symmetric(0.0, 0.0) } - fn feed_frame_rounding(_post: &PostProperties) -> Rounding { + fn feed_frame_rounding(_post: &NoteRenderData) -> Rounding { Rounding::same(4.0) } - fn feed_frame_shadow(_dark_mode: bool, _post: &PostProperties) -> Shadow { + fn feed_frame_shadow(_dark_mode: bool, _post: &NoteRenderData) -> Shadow { Shadow::default() } - fn feed_frame_fill(dark_mode: bool, post: &PostProperties) -> Color32 { + fn feed_frame_fill(dark_mode: bool, post: &NoteRenderData) -> Color32 { if post.is_focused { if dark_mode { Color32::from_rgb(16, 23, 33) @@ -459,7 +459,7 @@ impl ThemeDef for DefaultTheme { } } } - fn feed_frame_stroke(_dark_mode: bool, _post: &PostProperties) -> Stroke { + fn feed_frame_stroke(_dark_mode: bool, _post: &NoteRenderData) -> Stroke { Stroke::NONE } diff --git a/src/ui/theme/mod.rs b/src/ui/theme/mod.rs index 1837aa65..36f3c4b6 100644 --- a/src/ui/theme/mod.rs +++ b/src/ui/theme/mod.rs @@ -1,4 +1,5 @@ use super::HighlightType; +use super::feed::NoteRenderData; use eframe::egui::{ Color32, Context, FontData, FontDefinitions, FontTweak, Margin, Rounding, Stroke, Style, TextFormat, TextStyle, Ui, @@ -42,22 +43,6 @@ pub struct FeedProperties { pub is_thread: bool, } -pub struct PostProperties { - pub height: f32, - /// Post height - pub is_new: bool, - /// This message is the focus of the view (formerly called is_main_event) - pub is_focused: bool, - /// This message is part of a thread - pub is_thread: bool, - /// Is this the first post in the display? - pub is_first: bool, - /// Is this the last post in the display - pub is_last: bool, - /// Position in the thread, focused message = 0 - pub thread_position: i32, -} - macro_rules! theme_dispatch { ($($variant:path, $class:ident, $name:literal),+) => { @@ -143,55 +128,55 @@ macro_rules! theme_dispatch { } } - pub fn feed_post_separator_stroke(&self, post: &PostProperties) -> Stroke { + pub fn feed_post_separator_stroke(&self, post: &NoteRenderData) -> Stroke { match self.variant { $( $variant => $class::feed_post_separator_stroke(self.dark_mode, post), )+ } } - pub fn feed_post_outer_indent(&self, ui: &mut Ui, post: &PostProperties) { + pub fn feed_post_outer_indent(&self, ui: &mut Ui, post: &NoteRenderData) { match self.variant { $( $variant => $class::feed_post_outer_indent(ui, post), )+ } } - pub fn feed_post_inner_indent(&self, ui: &mut Ui, post: &PostProperties) { + pub fn feed_post_inner_indent(&self, ui: &mut Ui, post: &NoteRenderData) { match self.variant { $( $variant => $class::feed_post_inner_indent(ui, post), )+ } } - pub fn feed_frame_inner_margin(&self, post: &PostProperties) -> Margin { + pub fn feed_frame_inner_margin(&self, post: &NoteRenderData) -> Margin { match self.variant { $( $variant => $class::feed_frame_inner_margin(post), )+ } } - pub fn feed_frame_outer_margin(&self, post: &PostProperties) -> Margin { + pub fn feed_frame_outer_margin(&self, post: &NoteRenderData) -> Margin { match self.variant { $( $variant => $class::feed_frame_outer_margin(post), )+ } } - pub fn feed_frame_rounding(&self, post: &PostProperties) -> Rounding { + pub fn feed_frame_rounding(&self, post: &NoteRenderData) -> Rounding { match self.variant { $( $variant => $class::feed_frame_rounding(post), )+ } } - pub fn feed_frame_shadow(&self, post: &PostProperties) -> Shadow { + pub fn feed_frame_shadow(&self, post: &NoteRenderData) -> Shadow { match self.variant { $( $variant => $class::feed_frame_shadow(self.dark_mode, post), )+ } } - pub fn feed_frame_fill(&self, post: &PostProperties) -> Color32 { + pub fn feed_frame_fill(&self, post: &NoteRenderData) -> Color32 { match self.variant { $( $variant => $class::feed_frame_fill(self.dark_mode, post), )+ } } - pub fn feed_frame_stroke(&self, post: &PostProperties) -> Stroke { + pub fn feed_frame_stroke(&self, post: &NoteRenderData) -> Stroke { match self.variant { $( $variant => $class::feed_frame_stroke(self.dark_mode, post), )+ } @@ -241,15 +226,15 @@ pub trait ThemeDef: Send + Sync { fn feed_scroll_rounding(feed: &FeedProperties) -> Rounding; fn feed_scroll_fill(dark_mode: bool, feed: &FeedProperties) -> Color32; fn feed_scroll_stroke(dark_mode: bool, feed: &FeedProperties) -> Stroke; - fn feed_post_separator_stroke(dark_mode: bool, post: &PostProperties) -> Stroke; - fn feed_post_outer_indent(ui: &mut Ui, post: &PostProperties); - fn feed_post_inner_indent(ui: &mut Ui, post: &PostProperties); - fn feed_frame_inner_margin(post: &PostProperties) -> Margin; - fn feed_frame_outer_margin(post: &PostProperties) -> Margin; - fn feed_frame_rounding(post: &PostProperties) -> Rounding; - fn feed_frame_shadow(dark_mode: bool, post: &PostProperties) -> Shadow; - fn feed_frame_fill(dark_mode: bool, post: &PostProperties) -> Color32; - fn feed_frame_stroke(dark_mode: bool, post: &PostProperties) -> Stroke; + fn feed_post_separator_stroke(dark_mode: bool, post: &NoteRenderData) -> Stroke; + fn feed_post_outer_indent(ui: &mut Ui, post: &NoteRenderData); + fn feed_post_inner_indent(ui: &mut Ui, post: &NoteRenderData); + fn feed_frame_inner_margin(post: &NoteRenderData) -> Margin; + fn feed_frame_outer_margin(post: &NoteRenderData) -> Margin; + fn feed_frame_rounding(post: &NoteRenderData) -> Rounding; + fn feed_frame_shadow(dark_mode: bool, post: &NoteRenderData) -> Shadow; + fn feed_frame_fill(dark_mode: bool, post: &NoteRenderData) -> Color32; + fn feed_frame_stroke(dark_mode: bool, post: &NoteRenderData) -> Stroke; // image rounding fn round_image() -> bool; diff --git a/src/ui/theme/roundy.rs b/src/ui/theme/roundy.rs index 343e4dd3..2a7116ea 100644 --- a/src/ui/theme/roundy.rs +++ b/src/ui/theme/roundy.rs @@ -1,4 +1,4 @@ -use super::{FeedProperties, PostProperties, ThemeDef}; +use super::{FeedProperties, NoteRenderData, ThemeDef}; use crate::ui::HighlightType; use eframe::egui::style::{Selection, WidgetVisuals, Widgets}; use eframe::egui::{FontDefinitions, Margin, Style, TextFormat, TextStyle, Visuals}; @@ -380,17 +380,17 @@ impl ThemeDef for RoundyTheme { fn feed_scroll_stroke(_dark_mode: bool, _feed: &FeedProperties) -> Stroke { Stroke::NONE } - fn feed_post_separator_stroke(_dark_mode: bool, _post: &PostProperties) -> Stroke { + fn feed_post_separator_stroke(_dark_mode: bool, _post: &NoteRenderData) -> Stroke { Stroke::new(1.0, Color32::TRANSPARENT) } - fn feed_post_outer_indent(ui: &mut eframe::egui::Ui, post: &PostProperties) { + fn feed_post_outer_indent(ui: &mut eframe::egui::Ui, post: &NoteRenderData) { if post.is_thread { let space = 100.0 * (10.0 - (1000.0 / (post.thread_position as f32 + 100.0))); ui.add_space(space); } } - fn feed_post_inner_indent(_ui: &mut eframe::egui::Ui, _post: &PostProperties) {} - fn feed_frame_inner_margin(_post: &PostProperties) -> Margin { + fn feed_post_inner_indent(_ui: &mut eframe::egui::Ui, _post: &NoteRenderData) {} + fn feed_frame_inner_margin(_post: &NoteRenderData) -> Margin { Margin { left: 10.0, right: 10.0, @@ -398,10 +398,10 @@ impl ThemeDef for RoundyTheme { bottom: 5.0, } } - fn feed_frame_outer_margin(_post: &PostProperties) -> Margin { + fn feed_frame_outer_margin(_post: &NoteRenderData) -> Margin { Margin::default() } - fn feed_frame_rounding(post: &PostProperties) -> Rounding { + fn feed_frame_rounding(post: &NoteRenderData) -> Rounding { if post.is_thread { let mut rounding = Rounding::none(); if post.is_first && post.thread_position == 0 { @@ -413,10 +413,10 @@ impl ThemeDef for RoundyTheme { Rounding::same(7.0) } } - fn feed_frame_shadow(_dark_mode: bool, _post: &PostProperties) -> Shadow { + fn feed_frame_shadow(_dark_mode: bool, _post: &NoteRenderData) -> Shadow { Shadow::NONE } - fn feed_frame_fill(dark_mode: bool, post: &PostProperties) -> Color32 { + fn feed_frame_fill(dark_mode: bool, post: &NoteRenderData) -> Color32 { if post.is_new { if dark_mode { Color32::from_rgb(45, 45, 46) @@ -431,7 +431,7 @@ impl ThemeDef for RoundyTheme { } } } - fn feed_frame_stroke(dark_mode: bool, post: &PostProperties) -> Stroke { + fn feed_frame_stroke(dark_mode: bool, post: &NoteRenderData) -> Stroke { if post.is_focused { if dark_mode { Stroke::new(1.0, Color32::from_rgb(64, 96, 64)) From 715420ff9e1fe7620d4525d1b5d18966590d87a6 Mon Sep 17 00:00:00 2001 From: Bu5hm4nn <“bu5hm4nn@users.noreply.github.com”> Date: Mon, 13 Mar 2023 17:18:44 -0600 Subject: [PATCH 02/25] Rename `as_reply_to` into `hide_footer` as that was the only effect of that variable --- src/ui/feed/mod.rs | 12 +++---- src/ui/feed/note/mod.rs | 73 ++++++++++++++++++++++++----------------- src/ui/feed/post.rs | 2 +- src/ui/theme/default.rs | 2 +- src/ui/theme/roundy.rs | 2 +- 5 files changed, 51 insertions(+), 40 deletions(-) diff --git a/src/ui/feed/mod.rs b/src/ui/feed/mod.rs index 2f376dc6..79d69b7a 100644 --- a/src/ui/feed/mod.rs +++ b/src/ui/feed/mod.rs @@ -13,7 +13,7 @@ mod post; struct FeedNoteParams { id: Id, indent: usize, - as_reply_to: bool, + hide_footer: bool, threaded: bool, is_first: bool, is_last: bool, @@ -174,7 +174,7 @@ fn render_a_feed( FeedNoteParams { id: *id, indent: 0, - as_reply_to: false, + hide_footer: false, threaded, is_first: Some(id) == first, is_last: Some(id) == last, @@ -195,7 +195,7 @@ fn render_note_maybe_fake( let FeedNoteParams { id, indent, - as_reply_to, + hide_footer: as_reply_to, threaded, is_first, is_last, @@ -234,7 +234,7 @@ fn render_note_maybe_fake( FeedNoteParams { id, indent, - as_reply_to, + hide_footer: as_reply_to, threaded, is_first, is_last, @@ -265,7 +265,7 @@ fn render_note_maybe_fake( FeedNoteParams { id: *reply_id, indent: indent + 1, - as_reply_to, + hide_footer: as_reply_to, threaded, is_first: Some(reply_id) == first, is_last: Some(reply_id) == last, @@ -282,7 +282,7 @@ fn render_note_maybe_fake( FeedNoteParams { id, indent, - as_reply_to, + hide_footer: as_reply_to, threaded, is_first, is_last, diff --git a/src/ui/feed/note/mod.rs b/src/ui/feed/note/mod.rs index 274056c2..24820638 100644 --- a/src/ui/feed/note/mod.rs +++ b/src/ui/feed/note/mod.rs @@ -60,8 +60,8 @@ pub struct NoteRenderData { pub height: f32, /// Post height pub is_new: bool, - /// This message is the focus of the view (formerly called is_main_event) - pub is_focused: bool, + /// This message is the focus of the view (formerly called is_focused) + pub is_main_event: bool, /// This message is part of a thread pub is_thread: bool, /// Is this the first post in the display? @@ -82,7 +82,7 @@ pub(super) fn render_note( let FeedNoteParams { id, indent, - as_reply_to, + hide_footer, threaded, is_first, is_last, @@ -117,13 +117,14 @@ pub(super) fn render_note( } else { &0.0 }; - let post_properties = NoteRenderData { + + let render_data = NoteRenderData { height: *height, is_new, is_thread: threaded, is_first, is_last, - is_focused: is_main_event, + is_main_event, thread_position: indent as i32, }; @@ -131,28 +132,24 @@ pub(super) fn render_note( ui.horizontal(|ui| { // Outer indents first - app.settings - .theme - .feed_post_outer_indent(ui, &post_properties); + app.settings.theme.feed_post_outer_indent(ui, &render_data); let inner_response = Frame::none() - .inner_margin(app.settings.theme.feed_frame_inner_margin(&post_properties)) - .outer_margin(app.settings.theme.feed_frame_outer_margin(&post_properties)) - .rounding(app.settings.theme.feed_frame_rounding(&post_properties)) - .shadow(app.settings.theme.feed_frame_shadow(&post_properties)) - .fill(app.settings.theme.feed_frame_fill(&post_properties)) - .stroke(app.settings.theme.feed_frame_stroke(&post_properties)) + .inner_margin(app.settings.theme.feed_frame_inner_margin(&render_data)) + .outer_margin(app.settings.theme.feed_frame_outer_margin(&render_data)) + .rounding(app.settings.theme.feed_frame_rounding(&render_data)) + .shadow(app.settings.theme.feed_frame_shadow(&render_data)) + .fill(app.settings.theme.feed_frame_fill(&render_data)) + .stroke(app.settings.theme.feed_frame_stroke(&render_data)) .show(ui, |ui| { ui.horizontal_wrapped(|ui| { - // Innter indents first - app.settings - .theme - .feed_post_inner_indent(ui, &post_properties); + // Inner indents first + app.settings.theme.feed_post_inner_indent(ui, &render_data); if note_data.author.muted > 0 { ui.label(RichText::new("MUTED POST").monospace().italics()); } else { - render_note_inner(app, ctx, ui, note_data, is_main_event, as_reply_to); + render_note_inner(app, ctx, ui, note_data, &render_data, hide_footer); } }); }); @@ -170,12 +167,10 @@ pub(super) fn render_note( thin_separator( ui, - app.settings - .theme - .feed_post_separator_stroke(&post_properties), + app.settings.theme.feed_post_separator_stroke(&render_data), ); - if threaded && !as_reply_to { + if threaded && !hide_footer { let replies = Globals::get_replies_sync(id); let iter = replies.iter(); let first = replies.first(); @@ -189,7 +184,7 @@ pub(super) fn render_note( FeedNoteParams { id: *reply_id, indent: indent + 1, - as_reply_to, + hide_footer, threaded, is_first: Some(reply_id) == first, is_last: Some(reply_id) == last, @@ -205,8 +200,8 @@ fn render_note_inner( ctx: &Context, ui: &mut Ui, note_data: NoteData, - is_main_event: bool, - as_reply_to: bool, + render_data: &NoteRenderData, + hide_footer: bool, ) { let NoteData { event, @@ -309,7 +304,8 @@ fn render_note_inner( ui.with_layout(Layout::right_to_left(Align::TOP), |ui| { ui.menu_button(RichText::new("=").size(13.0), |ui| { - if !is_main_event && event.kind != EventKind::EncryptedDirectMessage { + if !render_data.is_main_event && event.kind != EventKind::EncryptedDirectMessage + { if ui.button("View Thread").clicked() { app.set_page(Page::Feed(FeedKind::Thread { id: event.id, @@ -361,7 +357,7 @@ fn render_note_inner( ui.add_space(4.0); } - if !is_main_event && event.kind != EventKind::EncryptedDirectMessage { + if !render_data.is_main_event && event.kind != EventKind::EncryptedDirectMessage { if ui .button(RichText::new("◉").size(13.0)) .on_hover_text("View Thread") @@ -469,7 +465,7 @@ fn render_note_inner( } // Under row - if !as_reply_to { + if !hide_footer { ui.horizontal_wrapped(|ui| { if ui .add(CopyButton {}) @@ -594,13 +590,28 @@ fn thin_separator(ui: &mut Ui, stroke: Stroke) { ui.reset_style(); } -pub(super) fn render_repost(app: &mut GossipUi, ui: &mut Ui, ctx: &Context, repost_data: NoteData) { +pub(super) fn render_repost( + app: &mut GossipUi, + ui: &mut Ui, + ctx: &Context, + repost_data: NoteData, +) { + let render_data = NoteRenderData { + height: 0.0, + is_new: false, + is_main_event: false, + is_thread: false, + is_first: false, + is_last: false, + thread_position: 0, + }; + ui.vertical(|ui| { thin_repost_separator(ui); ui.add_space(4.0); ui.horizontal_wrapped(|ui| { // FIXME: don't do this recursively - render_note_inner(app, ctx, ui, repost_data, false, false); + render_note_inner(app, ctx, ui, repost_data, &render_data, false); }); thin_repost_separator(ui); }); diff --git a/src/ui/feed/post.rs b/src/ui/feed/post.rs index f83dd7bf..2b3e7416 100644 --- a/src/ui/feed/post.rs +++ b/src/ui/feed/post.rs @@ -99,7 +99,7 @@ fn real_posting_area(app: &mut GossipUi, ctx: &Context, frame: &mut eframe::Fram FeedNoteParams { id, indent: 0, - as_reply_to: true, + hide_footer: true, threaded: false, is_first: true, is_last: true, diff --git a/src/ui/theme/default.rs b/src/ui/theme/default.rs index 86ee17c3..1d2ff0b7 100644 --- a/src/ui/theme/default.rs +++ b/src/ui/theme/default.rs @@ -439,7 +439,7 @@ impl ThemeDef for DefaultTheme { Shadow::default() } fn feed_frame_fill(dark_mode: bool, post: &NoteRenderData) -> Color32 { - if post.is_focused { + if post.is_main_event { if dark_mode { Color32::from_rgb(16, 23, 33) } else { diff --git a/src/ui/theme/roundy.rs b/src/ui/theme/roundy.rs index 2a7116ea..7f5b76ec 100644 --- a/src/ui/theme/roundy.rs +++ b/src/ui/theme/roundy.rs @@ -432,7 +432,7 @@ impl ThemeDef for RoundyTheme { } } fn feed_frame_stroke(dark_mode: bool, post: &NoteRenderData) -> Stroke { - if post.is_focused { + if post.is_main_event { if dark_mode { Stroke::new(1.0, Color32::from_rgb(64, 96, 64)) } else { From 50f1034f5af24c9337ef4ebddbaeb5e507999ec5 Mon Sep 17 00:00:00 2001 From: Bu5hm4nn <“bu5hm4nn@users.noreply.github.com”> Date: Mon, 13 Mar 2023 17:36:31 -0600 Subject: [PATCH 03/25] Optimize avatar size determination --- src/ui/feed/mod.rs | 10 +++++----- src/ui/feed/note/mod.rs | 27 ++++++++++++++------------- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/src/ui/feed/mod.rs b/src/ui/feed/mod.rs index 79d69b7a..859eec85 100644 --- a/src/ui/feed/mod.rs +++ b/src/ui/feed/mod.rs @@ -195,7 +195,7 @@ fn render_note_maybe_fake( let FeedNoteParams { id, indent, - hide_footer: as_reply_to, + hide_footer, threaded, is_first, is_last, @@ -234,7 +234,7 @@ fn render_note_maybe_fake( FeedNoteParams { id, indent, - hide_footer: as_reply_to, + hide_footer, threaded, is_first, is_last, @@ -251,7 +251,7 @@ fn render_note_maybe_fake( ui.add_space(height); // Yes, and we need to fake render threads to get their approx height too. - if threaded && !as_reply_to { + if threaded && !hide_footer { let replies = Globals::get_replies_sync(event.id); let iter = replies.iter(); let first = replies.first(); @@ -265,7 +265,7 @@ fn render_note_maybe_fake( FeedNoteParams { id: *reply_id, indent: indent + 1, - hide_footer: as_reply_to, + hide_footer, threaded, is_first: Some(reply_id) == first, is_last: Some(reply_id) == last, @@ -282,7 +282,7 @@ fn render_note_maybe_fake( FeedNoteParams { id, indent, - hide_footer: as_reply_to, + hide_footer, threaded, is_first, is_last, diff --git a/src/ui/feed/note/mod.rs b/src/ui/feed/note/mod.rs index 24820638..5f43775f 100644 --- a/src/ui/feed/note/mod.rs +++ b/src/ui/feed/note/mod.rs @@ -6,13 +6,13 @@ use crate::people::DbPerson; use crate::ui::widgets::CopyButton; use crate::ui::{GossipUi, Page}; use crate::AVATAR_SIZE_F32; +pub const AVATAR_SIZE_REPOST_F32: f32 = 27.0; // points, not pixels use eframe::egui; use egui::{ Align, Context, Frame, Image, Label, Layout, RichText, Sense, Separator, Stroke, TextStyle, Ui, Vec2, }; use nostr_types::{Event, EventDelegation, EventKind, IdHex, PublicKeyHex}; -use std::sync::atomic::Ordering; mod content; @@ -57,11 +57,14 @@ impl NoteData { } pub struct NoteRenderData { + /// Available height for post pub height: f32, - /// Post height + /// Has this post been seen yet? pub is_new: bool, /// This message is the focus of the view (formerly called is_focused) pub is_main_event: bool, + /// This message is a repost of another message + pub has_repost: bool, /// This message is part of a thread pub is_thread: bool, /// Is this the first post in the display? @@ -118,8 +121,12 @@ pub(super) fn render_note( &0.0 }; + // FIXME: determine all repost scenarios here + let has_repost = note_data.event.kind == EventKind::Repost; + let render_data = NoteRenderData { height: *height, + has_repost, is_new, is_thread: threaded, is_first, @@ -224,20 +231,13 @@ fn render_note_inner( app.placeholder_avatar.clone() }; - // If it is a repost without any comment, resize the avatar to highlight the original poster's one - let resize_factor = if event.kind == EventKind::Repost - && (event.content.is_empty() || serde_json::from_str::(&event.content).is_ok()) - { - 180.0 - } else { - 100.0 + let avatar_size = match render_data.has_repost { + true => AVATAR_SIZE_REPOST_F32, + false => AVATAR_SIZE_F32, }; - let size = AVATAR_SIZE_F32 * GLOBALS.pixels_per_point_times_100.load(Ordering::Relaxed) as f32 - / resize_factor; - if ui - .add(Image::new(&avatar, Vec2 { x: size, y: size }).sense(Sense::click())) + .add(Image::new(&avatar, Vec2 { x: avatar_size, y: avatar_size }).sense(Sense::click())) .clicked() { app.set_page(Page::Person(author.pubkey.clone())); @@ -598,6 +598,7 @@ pub(super) fn render_repost( ) { let render_data = NoteRenderData { height: 0.0, + has_repost: false, // FIXME should we consider allowing some recursion? is_new: false, is_main_event: false, is_thread: false, From 90423d9a6a352d550f2f75c731df94fba3b653cb Mon Sep 17 00:00:00 2001 From: Bu5hm4nn <“bu5hm4nn@users.noreply.github.com”> Date: Mon, 13 Mar 2023 18:06:08 -0600 Subject: [PATCH 04/25] Move common fields to NoteData::new() so they get used for constructing reposts --- src/ui/feed/note/content.rs | 2 +- src/ui/feed/note/mod.rs | 36 ++++++++++++++++++++++-------------- 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/src/ui/feed/note/content.rs b/src/ui/feed/note/content.rs index 063a25ec..28e0cbd2 100644 --- a/src/ui/feed/note/content.rs +++ b/src/ui/feed/note/content.rs @@ -10,11 +10,11 @@ pub(super) fn render_content( app: &mut GossipUi, ctx: &Context, ui: &mut Ui, - tag_re: ®ex::Regex, event: &Event, as_deleted: bool, content: &str, ) { + let tag_re = app.tag_re.clone(); ui.style_mut().spacing.item_spacing.x = 0.0; for span in LinkFinder::new().kinds(&[LinkKind::Url]).spans(content) { diff --git a/src/ui/feed/note/mod.rs b/src/ui/feed/note/mod.rs index 5f43775f..b32949da 100644 --- a/src/ui/feed/note/mod.rs +++ b/src/ui/feed/note/mod.rs @@ -12,7 +12,8 @@ use egui::{ Align, Context, Frame, Image, Label, Layout, RichText, Sense, Separator, Stroke, TextStyle, Ui, Vec2, }; -use nostr_types::{Event, EventDelegation, EventKind, IdHex, PublicKeyHex}; +use gossip_relay_picker::RelayUrl; +use nostr_types::{Event, EventDelegation, EventKind, IdHex, PublicKeyHex, Id}; mod content; @@ -20,6 +21,10 @@ pub(super) struct NoteData { event: Event, delegation: EventDelegation, author: DbPerson, + deletion: Option, + replies: Option<(Id, Option)>, + reactions: Vec<(char, usize)>, + self_already_reacted: bool, } impl NoteData { @@ -36,6 +41,12 @@ impl NoteData { let delegation = event.delegation(); + let deletion = Globals::get_deletion_sync(event.id); + + let (reactions, self_already_reacted) = Globals::get_reactions_sync(event.id); + + let replies = event.replies_to(); + // If delegated, use the delegated person let author_pubkey: PublicKeyHex = if let EventDelegation::DelegatedBy(pubkey) = delegation { pubkey.into() @@ -52,6 +63,10 @@ impl NoteData { event, delegation, author, + deletion, + replies, + reactions, + self_already_reacted, }) } } @@ -214,14 +229,12 @@ fn render_note_inner( event, delegation, author, + deletion, + replies, + reactions, + self_already_reacted } = note_data; - let deletion = Globals::get_deletion_sync(event.id); - - let (reactions, self_already_reacted) = Globals::get_reactions_sync(event.id); - - let tag_re = app.tag_re.clone(); - let collapsed = app.collapsed.contains(&event.id); // Avatar first @@ -243,8 +256,6 @@ fn render_note_inner( app.set_page(Page::Person(author.pubkey.clone())); }; - let mut is_a_reply = false; - // Everything else next ui.add_space(6.0); ui.vertical(|ui| { @@ -252,8 +263,7 @@ fn render_note_inner( ui.horizontal_wrapped(|ui| { GossipUi::render_person_name_line(app, ui, &author); - if let Some((irt, _)) = event.replies_to() { - is_a_reply = true; + if let Some((irt, _)) = replies { ui.add_space(8.0); ui.style_mut().override_text_style = Some(TextStyle::Small); @@ -335,7 +345,7 @@ fn render_note_inner( matches!(feed_kind, FeedKind::Thread { .. }) }; - if is_thread_view && is_a_reply { + if is_thread_view && replies.is_some() { if collapsed { let color = app.settings.theme.warning_marker_text_color(); if ui @@ -437,7 +447,6 @@ fn render_note_inner( app, ctx, ui, - &tag_re, &event, deletion.is_some(), &content, @@ -448,7 +457,6 @@ fn render_note_inner( app, ctx, ui, - &tag_re, &event, deletion.is_some(), &content, From a45669a24741196dc413cae8268245756489dbe4 Mon Sep 17 00:00:00 2001 From: Bu5hm4nn <“bu5hm4nn@users.noreply.github.com”> Date: Mon, 13 Mar 2023 18:40:45 -0600 Subject: [PATCH 05/25] Move more common fields to NoteData::new() so they get used for constructing reposts --- src/ui/feed/note/mod.rs | 91 +++++++++++++++++++++++++---------------- 1 file changed, 55 insertions(+), 36 deletions(-) diff --git a/src/ui/feed/note/mod.rs b/src/ui/feed/note/mod.rs index b32949da..95cfaff5 100644 --- a/src/ui/feed/note/mod.rs +++ b/src/ui/feed/note/mod.rs @@ -12,19 +12,32 @@ use egui::{ Align, Context, Frame, Image, Label, Layout, RichText, Sense, Separator, Stroke, TextStyle, Ui, Vec2, }; -use gossip_relay_picker::RelayUrl; -use nostr_types::{Event, EventDelegation, EventKind, IdHex, PublicKeyHex, Id}; +use nostr_types::{Event, EventDelegation, EventKind, IdHex, PublicKeyHex}; mod content; +enum RepostType { + Kind6, + Mention +} + pub(super) struct NoteData { + /// Original Event object, as received from nostr event: Event, + /// Delegation status of this event delegation: EventDelegation, + /// Author of this note (considers delegation) author: DbPerson, + /// Deletion reason if any deletion: Option, - replies: Option<(Id, Option)>, + /// Do we consider this note as being a repost of another? + repost: Option, + /// Known reactions to this post reactions: Vec<(char, usize)>, + /// Has the current user reacted to this post? self_already_reacted: bool, + /// The content modified to our display needs + display_content: String, } impl NoteData { @@ -45,7 +58,14 @@ impl NoteData { let (reactions, self_already_reacted) = Globals::get_reactions_sync(event.id); - let replies = event.replies_to(); + // FIXME: determine all desired repost scenarios here + let repost = { + if event.kind == EventKind::Repost && (event.content.is_empty() || serde_json::from_str::(&event.content).is_ok()) { + Some(RepostType::Kind6) + } else { + None + } + }; // If delegated, use the delegated person let author_pubkey: PublicKeyHex = if let EventDelegation::DelegatedBy(pubkey) = delegation { @@ -59,14 +79,32 @@ impl NoteData { None => DbPerson::new(author_pubkey), }; + // Compute the content to our needs + let display_content = match event.kind { + EventKind::TextNote => event.content.clone(), + EventKind::Repost => { + if event.content.is_empty() { + "#[0]".to_owned() // a bit of a hack + } else { + event.content.clone() + } + } + EventKind::EncryptedDirectMessage => match GLOBALS.signer.decrypt_message(&event) { + Ok(m) => m, + Err(_) => "DECRYPTION FAILED".to_owned(), + }, + _ => "NON FEED RELATED EVENT".to_owned(), + }; + Some(NoteData { event, delegation, author, deletion, - replies, + repost, reactions, self_already_reacted, + display_content, }) } } @@ -136,12 +174,9 @@ pub(super) fn render_note( &0.0 }; - // FIXME: determine all repost scenarios here - let has_repost = note_data.event.kind == EventKind::Repost; - let render_data = NoteRenderData { height: *height, - has_repost, + has_repost: note_data.repost.is_some(), is_new, is_thread: threaded, is_first, @@ -230,9 +265,10 @@ fn render_note_inner( delegation, author, deletion, - replies, + repost, reactions, - self_already_reacted + self_already_reacted, + display_content, } = note_data; let collapsed = app.collapsed.contains(&event.id); @@ -244,7 +280,7 @@ fn render_note_inner( app.placeholder_avatar.clone() }; - let avatar_size = match render_data.has_repost { + let avatar_size = match repost.is_some() { true => AVATAR_SIZE_REPOST_F32, false => AVATAR_SIZE_F32, }; @@ -263,7 +299,7 @@ fn render_note_inner( ui.horizontal_wrapped(|ui| { GossipUi::render_person_name_line(app, ui, &author); - if let Some((irt, _)) = replies { + if let Some((irt, _)) = event.replies_to() { ui.add_space(8.0); ui.style_mut().override_text_style = Some(TextStyle::Small); @@ -345,7 +381,7 @@ fn render_note_inner( matches!(feed_kind, FeedKind::Thread { .. }) }; - if is_thread_view && replies.is_some() { + if is_thread_view && event.replies_to().is_some() { if collapsed { let color = app.settings.theme.warning_marker_text_color(); if ui @@ -397,30 +433,13 @@ fn render_note_inner( ui.add_space(2.0); - // Compute the content - let content = match event.kind { - EventKind::TextNote => event.content.clone(), - EventKind::Repost => { - if event.content.is_empty() { - "#[0]".to_owned() // a bit of a hack - } else { - event.content.clone() - } - } - EventKind::EncryptedDirectMessage => match GLOBALS.signer.decrypt_message(&event) { - Ok(m) => m, - Err(_) => "DECRYPTION FAILED".to_owned(), - }, - _ => "NON FEED RELATED EVENT".to_owned(), - }; - // MAIN CONTENT if !collapsed { ui.horizontal_wrapped(|ui| { if app.render_raw == Some(event.id) { ui.label(serde_json::to_string_pretty(&event).unwrap()); } else if app.render_qr == Some(event.id) { - app.render_qr(ui, ctx, "feedqr", content.trim()); + app.render_qr(ui, ctx, "feedqr", display_content.trim()); // FIXME should this be the unmodified content (event.content)? } else if event.content_warning().is_some() && !app.approved.contains(&event.id) { ui.label( RichText::new(format!( @@ -435,7 +454,7 @@ fn render_note_inner( app.height.remove(&event.id); // will need to be recalculated. } } else if event.kind == EventKind::Repost { - if let Ok(inner_event) = serde_json::from_str::(&content) { + if let Ok(inner_event) = serde_json::from_str::(&event.content) { if let Some(inner_note_data) = NoteData::new(inner_event) { render_repost(app, ui, ctx, inner_note_data); } else { @@ -449,7 +468,7 @@ fn render_note_inner( ui, &event, deletion.is_some(), - &content, + &display_content, ); } } else { @@ -459,7 +478,7 @@ fn render_note_inner( ui, &event, deletion.is_some(), - &content, + &display_content, ); } }); @@ -485,7 +504,7 @@ fn render_note_inner( o.copied_text = serde_json::to_string(&event).unwrap() }); } else { - ui.output_mut(|o| o.copied_text = content.clone()); + ui.output_mut(|o| o.copied_text = display_content.clone()); } } From e6068c87acc527c0a61b1b16c8845311310de604 Mon Sep 17 00:00:00 2001 From: Bu5hm4nn <“bu5hm4nn@users.noreply.github.com”> Date: Mon, 13 Mar 2023 19:05:44 -0600 Subject: [PATCH 06/25] Fix adding newline before repost mention --- src/ui/feed/note/content.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/ui/feed/note/content.rs b/src/ui/feed/note/content.rs index 28e0cbd2..ffdb3328 100644 --- a/src/ui/feed/note/content.rs +++ b/src/ui/feed/note/content.rs @@ -24,11 +24,11 @@ pub(super) fn render_content( || span.as_str().ends_with(".jpeg") || span.as_str().ends_with(".png") || span.as_str().ends_with(".gif") - { + { crate::ui::widgets::break_anywhere_hyperlink_to(ui, "[ Image ]", span.as_str()); } else if span.as_str().ends_with(".mov") || span.as_str().ends_with(".mp4") - { + { crate::ui::widgets::break_anywhere_hyperlink_to(ui, "[ Video ]", span.as_str()); } else { crate::ui::widgets::break_anywhere_hyperlink_to(ui, span.as_str(), span.as_str()); @@ -55,7 +55,8 @@ pub(super) fn render_content( }; } Tag::Event { id, .. } => { - if ui.cursor().min == ui.max_rect().min { + // insert a newline if the current line has text + if ui.cursor().min.x > ui.max_rect().min.y { ui.end_row(); } let mut render_as_link = true; From b1ad1f3ef565a6e22aeb783d6f3a7f1f12271d7f Mon Sep 17 00:00:00 2001 From: Bu5hm4nn <“bu5hm4nn@users.noreply.github.com”> Date: Mon, 13 Mar 2023 19:39:17 -0600 Subject: [PATCH 07/25] Use `Event::mentions()` to determine if we have mentions --- src/ui/feed/note/mod.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/ui/feed/note/mod.rs b/src/ui/feed/note/mod.rs index 95cfaff5..93e75896 100644 --- a/src/ui/feed/note/mod.rs +++ b/src/ui/feed/note/mod.rs @@ -62,6 +62,8 @@ impl NoteData { let repost = { if event.kind == EventKind::Repost && (event.content.is_empty() || serde_json::from_str::(&event.content).is_ok()) { Some(RepostType::Kind6) + } else if !event.mentions().is_empty() { + Some(RepostType::Mention) } else { None } @@ -625,7 +627,7 @@ pub(super) fn render_repost( ) { let render_data = NoteRenderData { height: 0.0, - has_repost: false, // FIXME should we consider allowing some recursion? + has_repost: repost_data.repost.is_some(), is_new: false, is_main_event: false, is_thread: false, From 27aee36a05eddcbe83bcf7d77956dd38837e9792 Mon Sep 17 00:00:00 2001 From: Bu5hm4nn <“bu5hm4nn@users.noreply.github.com”> Date: Mon, 13 Mar 2023 22:21:11 -0600 Subject: [PATCH 08/25] Implement new style for mentions, implements #305 --- src/ui/feed/note/content.rs | 52 ++--- src/ui/feed/note/mod.rs | 438 +++++++++++++++++++++--------------- 2 files changed, 284 insertions(+), 206 deletions(-) diff --git a/src/ui/feed/note/content.rs b/src/ui/feed/note/content.rs index ffdb3328..9230943a 100644 --- a/src/ui/feed/note/content.rs +++ b/src/ui/feed/note/content.rs @@ -1,16 +1,16 @@ -use super::{GossipUi, Page}; +use super::{GossipUi, NoteData, Page, RepostType}; use crate::feed::FeedKind; use crate::globals::GLOBALS; use eframe::egui::{self, Context}; use egui::{RichText, Ui}; use linkify::{LinkFinder, LinkKind}; -use nostr_types::{Event, IdHex, Tag}; +use nostr_types::{IdHex, Tag}; pub(super) fn render_content( app: &mut GossipUi, ctx: &Context, ui: &mut Ui, - event: &Event, + note: &NoteData, as_deleted: bool, content: &str, ) { @@ -19,28 +19,24 @@ pub(super) fn render_content( for span in LinkFinder::new().kinds(&[LinkKind::Url]).spans(content) { if span.kind().is_some() { - if span.as_str().ends_with(".jpg") || span.as_str().ends_with(".jpeg") || span.as_str().ends_with(".png") || span.as_str().ends_with(".gif") { crate::ui::widgets::break_anywhere_hyperlink_to(ui, "[ Image ]", span.as_str()); - } else if span.as_str().ends_with(".mov") - || span.as_str().ends_with(".mp4") - { + } else if span.as_str().ends_with(".mov") || span.as_str().ends_with(".mp4") { crate::ui::widgets::break_anywhere_hyperlink_to(ui, "[ Video ]", span.as_str()); } else { crate::ui::widgets::break_anywhere_hyperlink_to(ui, span.as_str(), span.as_str()); } - } else { let s = span.as_str(); let mut pos = 0; for mat in tag_re.find_iter(s) { ui.label(&s[pos..mat.start()]); let num: usize = s[mat.start() + 2..mat.end() - 1].parse::().unwrap(); - if let Some(tag) = event.tags.get(num) { + if let Some(tag) = note.event.tags.get(num) { match tag { Tag::Pubkey { pubkey, .. } => { let nam = match GLOBALS.people.get(pubkey) { @@ -59,26 +55,28 @@ pub(super) fn render_content( if ui.cursor().min.x > ui.max_rect().min.y { ui.end_row(); } - let mut render_as_link = true; - if app.settings.show_first_mention && pos == 0 { - // try to find the mentioned note in our cache - let maybe_event = GLOBALS.events.get(id); - if let Some(event) = maybe_event { - if let Some(note_data) = super::NoteData::new(event) { - super::render_repost(app, ui, ctx, note_data); - render_as_link = false; + match note.repost { + Some(RepostType::Mention) => { + if app.settings.show_first_mention && pos == 0 { + // try to find the mentioned note in our cache + let maybe_event = GLOBALS.events.get(id); + if let Some(event) = maybe_event { + if let Some(note_data) = super::NoteData::new(event) { + super::render_repost(app, ui, ctx, note_data); + } + } } } - } - if render_as_link { - let idhex: IdHex = (*id).into(); - let nam = format!("#{}", GossipUi::hex_id_short(&idhex)); - if ui.link(&nam).clicked() { - app.set_page(Page::Feed(FeedKind::Thread { - id: *id, - referenced_by: event.id, - })); - }; + _ => { + let idhex: IdHex = (*id).into(); + let nam = format!("#{}", GossipUi::hex_id_short(&idhex)); + if ui.link(&nam).clicked() { + app.set_page(Page::Feed(FeedKind::Thread { + id: *id, + referenced_by: note.event.id, + })); + }; + } } } Tag::Hashtag(s) => { diff --git a/src/ui/feed/note/mod.rs b/src/ui/feed/note/mod.rs index 93e75896..ffb98d25 100644 --- a/src/ui/feed/note/mod.rs +++ b/src/ui/feed/note/mod.rs @@ -7,7 +7,7 @@ use crate::ui::widgets::CopyButton; use crate::ui::{GossipUi, Page}; use crate::AVATAR_SIZE_F32; pub const AVATAR_SIZE_REPOST_F32: f32 = 27.0; // points, not pixels -use eframe::egui; +use eframe::egui::{self, Margin}; use egui::{ Align, Context, Frame, Image, Label, Layout, RichText, Sense, Separator, Stroke, TextStyle, Ui, Vec2, @@ -18,7 +18,7 @@ mod content; enum RepostType { Kind6, - Mention + Mention, } pub(super) struct NoteData { @@ -59,10 +59,19 @@ impl NoteData { let (reactions, self_already_reacted) = Globals::get_reactions_sync(event.id); // FIXME: determine all desired repost scenarios here + // FIXME: in the unlikely event that the order of mentions in the text isn't the same + // as in the tags, we would be checking for the wrong mention here + let first_mention = match event.mentions().first() { + Some(mention) => GLOBALS.events.get(&mention.0), + None => None, + }; let repost = { - if event.kind == EventKind::Repost && (event.content.is_empty() || serde_json::from_str::(&event.content).is_ok()) { + if event.kind == EventKind::Repost + && (event.content.is_empty() + || serde_json::from_str::(&event.content).is_ok()) + { Some(RepostType::Kind6) - } else if !event.mentions().is_empty() { + } else if first_mention.is_some() { Some(RepostType::Mention) } else { None @@ -271,35 +280,70 @@ fn render_note_inner( reactions, self_already_reacted, display_content, - } = note_data; + } = ¬e_data; let collapsed = app.collapsed.contains(&event.id); - // Avatar first + // Load avatar texture let avatar = if let Some(avatar) = app.try_get_avatar(ctx, &author.pubkey) { avatar } else { app.placeholder_avatar.clone() }; - let avatar_size = match repost.is_some() { - true => AVATAR_SIZE_REPOST_F32, - false => AVATAR_SIZE_F32, + // Determine avatar size + let avatar_size = match repost { + None => AVATAR_SIZE_F32, + Some(_) => AVATAR_SIZE_REPOST_F32, }; - if ui - .add(Image::new(&avatar, Vec2 { x: avatar_size, y: avatar_size }).sense(Sense::click())) - .clicked() - { - app.set_page(Page::Person(author.pubkey.clone())); + let inner_margin = app.settings.theme.feed_frame_inner_margin(render_data); + + let avatar_margin_left = match repost { + None => 0.0, + Some(_) => (AVATAR_SIZE_F32 - AVATAR_SIZE_REPOST_F32) / 2.0, + }; + + let content_margin_top = match repost { + None => inner_margin.top + ui.style().spacing.item_spacing.y * 2.0 - avatar_size, + Some(_) => 0.0, + }; + + let footer_margin_left = AVATAR_SIZE_F32 + inner_margin.left; + + let content_margin_left = match repost { + None => avatar_size + inner_margin.left, + Some(_) => 0.0, }; - // Everything else next - ui.add_space(6.0); ui.vertical(|ui| { // First row - ui.horizontal_wrapped(|ui| { - GossipUi::render_person_name_line(app, ui, &author); + + ui.with_layout(Layout::left_to_right(Align::TOP), |ui| { + ui.add_space(avatar_margin_left); + + // render avatar + if ui + .add( + Image::new( + &avatar, + Vec2 { + x: avatar_size, + y: avatar_size, + }, + ) + .sense(Sense::click()), + ) + .clicked() + { + app.set_page(Page::Person(author.pubkey.clone())); + }; + + ui.add_space(avatar_margin_left); + + ui.add_space(3.0); + + GossipUi::render_person_name_line(app, ui, author); if let Some((irt, _)) = event.replies_to() { ui.add_space(8.0); @@ -426,10 +470,9 @@ fn render_note_inner( }); }); - ui.add_space(4.0); - // Possible subject line if let Some(subject) = event.subject() { + ui.add_space(4.0); ui.label(RichText::new(subject).strong().underline()); } @@ -437,53 +480,71 @@ fn render_note_inner( // MAIN CONTENT if !collapsed { - ui.horizontal_wrapped(|ui| { - if app.render_raw == Some(event.id) { - ui.label(serde_json::to_string_pretty(&event).unwrap()); - } else if app.render_qr == Some(event.id) { - app.render_qr(ui, ctx, "feedqr", display_content.trim()); // FIXME should this be the unmodified content (event.content)? - } else if event.content_warning().is_some() && !app.approved.contains(&event.id) { - ui.label( - RichText::new(format!( - "Content-Warning: {}", - event.content_warning().unwrap() - )) - .monospace() - .italics(), - ); - if ui.button("Show Post").clicked() { - app.approved.insert(event.id); - app.height.remove(&event.id); // will need to be recalculated. - } - } else if event.kind == EventKind::Repost { - if let Ok(inner_event) = serde_json::from_str::(&event.content) { - if let Some(inner_note_data) = NoteData::new(inner_event) { - render_repost(app, ui, ctx, inner_note_data); + Frame::none() + .inner_margin(Margin { + left: content_margin_left, + bottom: 0.0, + right: 0.0, + top: 0.0, + }) + .outer_margin(Margin { + left: 0.0, + bottom: 0.0, + right: 0.0, + top: content_margin_top, + }) + .show(ui, |ui| { + ui.horizontal_wrapped(|ui| { + if app.render_raw == Some(event.id) { + ui.label(serde_json::to_string_pretty(&event).unwrap()); + } else if app.render_qr == Some(event.id) { + app.render_qr(ui, ctx, "feedqr", display_content.trim()); + // FIXME should this be the unmodified content (event.content)? + } else if event.content_warning().is_some() + && !app.approved.contains(&event.id) + { + ui.label( + RichText::new(format!( + "Content-Warning: {}", + event.content_warning().unwrap() + )) + .monospace() + .italics(), + ); + if ui.button("Show Post").clicked() { + app.approved.insert(event.id); + app.height.remove(&event.id); // will need to be recalculated. + } + } else if event.kind == EventKind::Repost { + if let Ok(inner_event) = serde_json::from_str::(&event.content) { + if let Some(inner_note_data) = NoteData::new(inner_event) { + render_repost(app, ui, ctx, inner_note_data); + } else { + ui.label("REPOSTED EVENT IS NOT RELEVANT"); + } + } else { + // render like a kind-1 event with a mention + content::render_content( + app, + ctx, + ui, + ¬e_data, + deletion.is_some(), + display_content, + ); + } } else { - ui.label("REPOSTED EVENT IS NOT RELEVANT"); + content::render_content( + app, + ctx, + ui, + ¬e_data, + deletion.is_some(), + display_content, + ); } - } else { - // render like a kind-1 event with a mention - content::render_content( - app, - ctx, - ui, - &event, - deletion.is_some(), - &display_content, - ); - } - } else { - content::render_content( - app, - ctx, - ui, - &event, - deletion.is_some(), - &display_content, - ); - } - }); + }); + }); ui.add_space(8.0); @@ -495,113 +556,135 @@ fn render_note_inner( // Under row if !hide_footer { - ui.horizontal_wrapped(|ui| { - if ui - .add(CopyButton {}) - .on_hover_text("Copy Contents") - .clicked() - { - if app.render_raw == Some(event.id) { - ui.output_mut(|o| { - o.copied_text = serde_json::to_string(&event).unwrap() - }); - } else { - ui.output_mut(|o| o.copied_text = display_content.clone()); - } - } - - ui.add_space(24.0); - - // Button to quote note - if ui - .add(Label::new(RichText::new("»").size(18.0)).sense(Sense::click())) - .on_hover_text("Quote") - .clicked() - { - if !app.draft.ends_with(' ') && !app.draft.is_empty() { - app.draft.push(' '); - } - app.draft - .push_str(&event.id.try_as_bech32_string().unwrap()); - } - - ui.add_space(24.0); - - // Button to reply - if event.kind != EventKind::EncryptedDirectMessage { - if ui - .add(Label::new(RichText::new("💬").size(18.0)).sense(Sense::click())) - .on_hover_text("Reply") - .clicked() - { - app.replying_to = Some(event.id); - } - - ui.add_space(24.0); - } - - // Button to render raw - if ui - .add(Label::new(RichText::new("🥩").size(13.0)).sense(Sense::click())) - .on_hover_text("Raw") - .clicked() - { - if app.render_raw != Some(event.id) { - app.render_raw = Some(event.id); - } else { - app.render_raw = None; - } - } - - ui.add_space(24.0); - - // Button to render QR code - if ui - .add(Label::new(RichText::new("⚃").size(16.0)).sense(Sense::click())) - .on_hover_text("QR Code") - .clicked() - { - if app.render_qr != Some(event.id) { - app.render_qr = Some(event.id); - app.qr_codes.remove("feedqr"); - } else { - app.render_qr = None; - app.qr_codes.remove("feedqr"); - } - } - - ui.add_space(24.0); - - // Buttons to react and reaction counts - if app.settings.reactions { - let default_reaction_icon = match self_already_reacted { - true => "♥", - false => "♡", - }; - if ui - .add( - Label::new(RichText::new(default_reaction_icon).size(20.0)) - .sense(Sense::click()), - ) - .clicked() - { - let _ = GLOBALS - .to_overlord - .send(ToOverlordMessage::Like(event.id, event.pubkey)); - } - for (ch, count) in reactions.iter() { - if *ch == '+' { - ui.label(format!("{}", count)); + Frame::none() + .inner_margin(Margin { + left: footer_margin_left, + bottom: 0.0, + right: 0.0, + top: 0.0, + }) + .outer_margin(Margin::same(0.0)) + .show(ui, |ui| { + ui.horizontal_wrapped(|ui| { + if ui + .add(CopyButton {}) + .on_hover_text("Copy Contents") + .clicked() + { + if app.render_raw == Some(event.id) { + ui.output_mut(|o| { + o.copied_text = serde_json::to_string(&event).unwrap() + }); + } else { + ui.output_mut(|o| o.copied_text = display_content.clone()); + } } - } - ui.add_space(12.0); - for (ch, count) in reactions.iter() { - if *ch != '+' { - ui.label(RichText::new(format!("{} {}", ch, count)).strong()); + + ui.add_space(24.0); + + // Button to quote note + if ui + .add( + Label::new(RichText::new("»").size(18.0)).sense(Sense::click()), + ) + .on_hover_text("Quote") + .clicked() + { + if !app.draft.ends_with(' ') && !app.draft.is_empty() { + app.draft.push(' '); + } + app.draft + .push_str(&event.id.try_as_bech32_string().unwrap()); } - } - } - }); + + ui.add_space(24.0); + + // Button to reply + if event.kind != EventKind::EncryptedDirectMessage { + if ui + .add( + Label::new(RichText::new("💬").size(18.0)) + .sense(Sense::click()), + ) + .on_hover_text("Reply") + .clicked() + { + app.replying_to = Some(event.id); + } + + ui.add_space(24.0); + } + + // Button to render raw + if ui + .add( + Label::new(RichText::new("🥩").size(13.0)) + .sense(Sense::click()), + ) + .on_hover_text("Raw") + .clicked() + { + if app.render_raw != Some(event.id) { + app.render_raw = Some(event.id); + } else { + app.render_raw = None; + } + } + + ui.add_space(24.0); + + // Button to render QR code + if ui + .add( + Label::new(RichText::new("⚃").size(16.0)).sense(Sense::click()), + ) + .on_hover_text("QR Code") + .clicked() + { + if app.render_qr != Some(event.id) { + app.render_qr = Some(event.id); + app.qr_codes.remove("feedqr"); + } else { + app.render_qr = None; + app.qr_codes.remove("feedqr"); + } + } + + ui.add_space(24.0); + + // Buttons to react and reaction counts + if app.settings.reactions { + let default_reaction_icon = match self_already_reacted { + true => "♥", + false => "♡", + }; + if ui + .add( + Label::new(RichText::new(default_reaction_icon).size(20.0)) + .sense(Sense::click()), + ) + .clicked() + { + let _ = GLOBALS + .to_overlord + .send(ToOverlordMessage::Like(event.id, event.pubkey)); + } + for (ch, count) in reactions.iter() { + if *ch == '+' { + ui.label(format!("{}", count)); + } + } + ui.add_space(12.0); + for (ch, count) in reactions.iter() { + if *ch != '+' { + ui.label( + RichText::new(format!("{} {}", ch, count)).strong(), + ); + } + } + } + }); + }); } } }); @@ -619,12 +702,7 @@ fn thin_separator(ui: &mut Ui, stroke: Stroke) { ui.reset_style(); } -pub(super) fn render_repost( - app: &mut GossipUi, - ui: &mut Ui, - ctx: &Context, - repost_data: NoteData, -) { +pub(super) fn render_repost(app: &mut GossipUi, ui: &mut Ui, ctx: &Context, repost_data: NoteData) { let render_data = NoteRenderData { height: 0.0, has_repost: repost_data.repost.is_some(), @@ -636,13 +714,15 @@ pub(super) fn render_repost( thread_position: 0, }; + let margin = app.settings.theme.feed_frame_inner_margin(&render_data); + ui.vertical(|ui| { + ui.add_space(margin.bottom); thin_repost_separator(ui); - ui.add_space(4.0); + ui.add_space(margin.top); ui.horizontal_wrapped(|ui| { // FIXME: don't do this recursively - render_note_inner(app, ctx, ui, repost_data, &render_data, false); + render_note_inner(app, ctx, ui, repost_data, &render_data, true); }); - thin_repost_separator(ui); }); } From ff83508a7f2c2bb791ecb61cffdbff2c405a9fde Mon Sep 17 00:00:00 2001 From: Bu5hm4nn <“bu5hm4nn@users.noreply.github.com”> Date: Tue, 14 Mar 2023 13:13:13 -0600 Subject: [PATCH 09/25] Improve repost type detection, case CommentMention is not yet handled (will throw warning) --- src/ui/feed/note/content.rs | 2 +- src/ui/feed/note/mod.rs | 33 +++++++++++++++++++++++---------- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/src/ui/feed/note/content.rs b/src/ui/feed/note/content.rs index 9230943a..59b528ca 100644 --- a/src/ui/feed/note/content.rs +++ b/src/ui/feed/note/content.rs @@ -56,7 +56,7 @@ pub(super) fn render_content( ui.end_row(); } match note.repost { - Some(RepostType::Mention) => { + Some(RepostType::MentionOnly) => { if app.settings.show_first_mention && pos == 0 { // try to find the mentioned note in our cache let maybe_event = GLOBALS.events.get(id); diff --git a/src/ui/feed/note/mod.rs b/src/ui/feed/note/mod.rs index ffb98d25..8c6d6a94 100644 --- a/src/ui/feed/note/mod.rs +++ b/src/ui/feed/note/mod.rs @@ -17,8 +17,15 @@ use nostr_types::{Event, EventDelegation, EventKind, IdHex, PublicKeyHex}; mod content; enum RepostType { - Kind6, - Mention, + /// Damus style, kind 6 repost where the reposted note's JSON + /// is included in the content + Kind6Embedded, + /// kind 6 repost without reposted note, but has a mention tag + Kind6Mention, + /// Post only has whitespace and a single mention tag + MentionOnly, + /// Post has a comment and at least one mention tag + CommentMention, } pub(super) struct NoteData { @@ -59,20 +66,26 @@ impl NoteData { let (reactions, self_already_reacted) = Globals::get_reactions_sync(event.id); // FIXME: determine all desired repost scenarios here - // FIXME: in the unlikely event that the order of mentions in the text isn't the same - // as in the tags, we would be checking for the wrong mention here + // FIXME: in the unlikely event that the first mentions in the text isn't index 0, + // we would be checking for the wrong mention here let first_mention = match event.mentions().first() { Some(mention) => GLOBALS.events.get(&mention.0), None => None, }; let repost = { - if event.kind == EventKind::Repost - && (event.content.is_empty() - || serde_json::from_str::(&event.content).is_ok()) + if event.kind == EventKind::Repost && serde_json::from_str::(&event.content).is_ok() { - Some(RepostType::Kind6) - } else if first_mention.is_some() { - Some(RepostType::Mention) + Some(RepostType::Kind6Embedded) + } else if event.content.trim().contains("#[0]") || event.content.trim().is_empty() { + if first_mention.is_some() { + if event.kind == EventKind::Repost { + Some(RepostType::Kind6Mention) + } else { + Some(RepostType::MentionOnly) + } + } else { + None + } } else { None } From 43b072d7644ce4886dd15775501737eb40b413aa Mon Sep 17 00:00:00 2001 From: Bu5hm4nn <“bu5hm4nn@users.noreply.github.com”> Date: Tue, 14 Mar 2023 13:27:53 -0600 Subject: [PATCH 10/25] fix detection of MentionOnly --- src/ui/feed/note/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ui/feed/note/mod.rs b/src/ui/feed/note/mod.rs index 8c6d6a94..34c2ee50 100644 --- a/src/ui/feed/note/mod.rs +++ b/src/ui/feed/note/mod.rs @@ -76,7 +76,7 @@ impl NoteData { if event.kind == EventKind::Repost && serde_json::from_str::(&event.content).is_ok() { Some(RepostType::Kind6Embedded) - } else if event.content.trim().contains("#[0]") || event.content.trim().is_empty() { + } else if event.content.trim() == "#[0]" || event.content.trim().is_empty() { if first_mention.is_some() { if event.kind == EventKind::Repost { Some(RepostType::Kind6Mention) From f292c1f3e8263336ff4cef1e759a6c2d13e6e22b Mon Sep 17 00:00:00 2001 From: Bu5hm4nn <“bu5hm4nn@users.noreply.github.com”> Date: Tue, 14 Mar 2023 13:42:58 -0600 Subject: [PATCH 11/25] Change which buttons are shown. Now show only the innermost repost buttons. --- src/ui/feed/note/mod.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/ui/feed/note/mod.rs b/src/ui/feed/note/mod.rs index 34c2ee50..e1599312 100644 --- a/src/ui/feed/note/mod.rs +++ b/src/ui/feed/note/mod.rs @@ -86,6 +86,10 @@ impl NoteData { } else { None } + } else if event.content.contains("#[0]") && first_mention.is_some() { + // Some(RepostType::CommentMention) + tracing::warn!("FIXME: Repost for comment + mention not yet implemented, Id: {}", event.id.as_hex_string() ); + None } else { None } @@ -568,7 +572,7 @@ fn render_note_inner( } // Under row - if !hide_footer { + if !hide_footer && !note_data.repost.is_some() { Frame::none() .inner_margin(Margin { left: footer_margin_left, @@ -735,7 +739,7 @@ pub(super) fn render_repost(app: &mut GossipUi, ui: &mut Ui, ctx: &Context, repo ui.add_space(margin.top); ui.horizontal_wrapped(|ui| { // FIXME: don't do this recursively - render_note_inner(app, ctx, ui, repost_data, &render_data, true); + render_note_inner(app, ctx, ui, repost_data, &render_data, false); }); }); } From 6b357654762e599649d1dee1f0e62a3d5ddf2d23 Mon Sep 17 00:00:00 2001 From: bu5hm4nn Date: Tue, 14 Mar 2023 15:30:46 -0600 Subject: [PATCH 12/25] Trim off whitespace when there is only one mention and no other text --- src/ui/feed/note/mod.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ui/feed/note/mod.rs b/src/ui/feed/note/mod.rs index e1599312..9123875f 100644 --- a/src/ui/feed/note/mod.rs +++ b/src/ui/feed/note/mod.rs @@ -113,6 +113,8 @@ impl NoteData { EventKind::Repost => { if event.content.is_empty() { "#[0]".to_owned() // a bit of a hack + } else if event.content.trim() == "#[0]" { + event.content.trim().to_string() } else { event.content.clone() } From c6cdc0437f6b50667a666d36e65f740c0b904de5 Mon Sep 17 00:00:00 2001 From: bu5hm4nn Date: Tue, 14 Mar 2023 17:26:57 -0600 Subject: [PATCH 13/25] Add support for rendering reposts of inline mentions if the mention is last tag in the content --- src/ui/feed/note/content.rs | 35 +++++++++++++----------- src/ui/feed/note/mod.rs | 53 +++++++++++++++++++++++++------------ 2 files changed, 55 insertions(+), 33 deletions(-) diff --git a/src/ui/feed/note/content.rs b/src/ui/feed/note/content.rs index 59b528ca..e9e699c5 100644 --- a/src/ui/feed/note/content.rs +++ b/src/ui/feed/note/content.rs @@ -55,28 +55,31 @@ pub(super) fn render_content( if ui.cursor().min.x > ui.max_rect().min.y { ui.end_row(); } + let mut render_link = true; match note.repost { - Some(RepostType::MentionOnly) => { - if app.settings.show_first_mention && pos == 0 { - // try to find the mentioned note in our cache - let maybe_event = GLOBALS.events.get(id); - if let Some(event) = maybe_event { - if let Some(note_data) = super::NoteData::new(event) { + Some(RepostType::MentionOnly) | Some(RepostType::CommentMention) => { + for (i, event) in note.cached_mentions.iter() { + if *i == num { + // FIXME is there a way to consume just this entry in cached_mentions so + // we can avoid the clone? + if let Some(note_data) = super::NoteData::new(event.clone(), true) { super::render_repost(app, ui, ctx, note_data); + render_link = false; } } } } - _ => { - let idhex: IdHex = (*id).into(); - let nam = format!("#{}", GossipUi::hex_id_short(&idhex)); - if ui.link(&nam).clicked() { - app.set_page(Page::Feed(FeedKind::Thread { - id: *id, - referenced_by: note.event.id, - })); - }; - } + _ => (), + } + if render_link { + let idhex: IdHex = (*id).into(); + let nam = format!("#{}", GossipUi::hex_id_short(&idhex)); + if ui.link(&nam).clicked() { + app.set_page(Page::Feed(FeedKind::Thread { + id: *id, + referenced_by: note.event.id, + })); + }; } } Tag::Hashtag(s) => { diff --git a/src/ui/feed/note/mod.rs b/src/ui/feed/note/mod.rs index 9123875f..31e5b82b 100644 --- a/src/ui/feed/note/mod.rs +++ b/src/ui/feed/note/mod.rs @@ -12,7 +12,7 @@ use egui::{ Align, Context, Frame, Image, Label, Layout, RichText, Sense, Separator, Stroke, TextStyle, Ui, Vec2, }; -use nostr_types::{Event, EventDelegation, EventKind, IdHex, PublicKeyHex}; +use nostr_types::{Event, EventDelegation, EventKind, IdHex, PublicKeyHex, Tag}; mod content; @@ -39,6 +39,8 @@ pub(super) struct NoteData { deletion: Option, /// Do we consider this note as being a repost of another? repost: Option, + /// A list of CACHED mentioned events and their index: (index, event) + cached_mentions: Vec<(usize, Event)>, /// Known reactions to this post reactions: Vec<(char, usize)>, /// Has the current user reacted to this post? @@ -48,7 +50,7 @@ pub(super) struct NoteData { } impl NoteData { - pub fn new(event: Event) -> Option { + pub fn new(event: Event, with_inline_mentions: bool) -> Option { // Only render known relevent events let enable_reposts = GLOBALS.settings.read().reposts; let direct_messages = GLOBALS.settings.read().direct_messages; @@ -65,19 +67,30 @@ impl NoteData { let (reactions, self_already_reacted) = Globals::get_reactions_sync(event.id); - // FIXME: determine all desired repost scenarios here - // FIXME: in the unlikely event that the first mentions in the text isn't index 0, - // we would be checking for the wrong mention here - let first_mention = match event.mentions().first() { - Some(mention) => GLOBALS.events.get(&mention.0), - None => None, + let cached_mentions = { + let mut cached_mentions= Vec::<(usize, Event)>::new(); + for (i, tag) in event.tags.iter().enumerate() { + match tag { + Tag::Event { id, .. } => { + if let Some(event) = GLOBALS.events.get(id) { + cached_mentions.push((i, event)); + } + }, + _ => (), + } + } + cached_mentions }; + let repost = { - if event.kind == EventKind::Repost && serde_json::from_str::(&event.content).is_ok() + let content_trim = event.content.trim(); + let content_trim_len = content_trim.chars().count(); + if event.kind == EventKind::Repost + && serde_json::from_str::(&event.content).is_ok() { Some(RepostType::Kind6Embedded) - } else if event.content.trim() == "#[0]" || event.content.trim().is_empty() { - if first_mention.is_some() { + } else if content_trim == "#[0]" || content_trim.is_empty() { + if !cached_mentions.is_empty() { if event.kind == EventKind::Repost { Some(RepostType::Kind6Mention) } else { @@ -86,10 +99,14 @@ impl NoteData { } else { None } - } else if event.content.contains("#[0]") && first_mention.is_some() { - // Some(RepostType::CommentMention) - tracing::warn!("FIXME: Repost for comment + mention not yet implemented, Id: {}", event.id.as_hex_string() ); - None + } else if with_inline_mentions + && content_trim_len > 4 + && content_trim.chars().nth(content_trim_len - 1).unwrap() == ']' + && content_trim.chars().nth(content_trim_len - 3).unwrap() == '[' + && content_trim.chars().nth(content_trim_len - 4).unwrap() == '#' + && !cached_mentions.is_empty() { + // matches content that ends with a mention, avoiding use of a regex match + Some(RepostType::CommentMention) } else { None } @@ -132,6 +149,7 @@ impl NoteData { author, deletion, repost, + cached_mentions, reactions, self_already_reacted, display_content, @@ -181,7 +199,7 @@ pub(super) fn render_note( } let event = maybe_event.unwrap(); - match NoteData::new(event) { + match NoteData::new(event, app.settings.show_first_mention) { Some(nd) => nd, None => return, } @@ -296,6 +314,7 @@ fn render_note_inner( author, deletion, repost, + cached_mentions: _, reactions, self_already_reacted, display_content, @@ -536,7 +555,7 @@ fn render_note_inner( } } else if event.kind == EventKind::Repost { if let Ok(inner_event) = serde_json::from_str::(&event.content) { - if let Some(inner_note_data) = NoteData::new(inner_event) { + if let Some(inner_note_data) = NoteData::new(inner_event, false) { render_repost(app, ui, ctx, inner_note_data); } else { ui.label("REPOSTED EVENT IS NOT RELEVANT"); From 4dc0181a830b43cf87744d6f1799999e17144b01 Mon Sep 17 00:00:00 2001 From: bu5hm4nn Date: Tue, 14 Mar 2023 17:42:53 -0600 Subject: [PATCH 14/25] Fix: Kind 6 Mention (kind 6 repost without JSON payload) && cargo fmt --- src/ui/feed/note/content.rs | 8 ++++++-- src/ui/feed/note/mod.rs | 15 ++++++++------- src/ui/theme/mod.rs | 2 +- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/ui/feed/note/content.rs b/src/ui/feed/note/content.rs index e9e699c5..41c91f15 100644 --- a/src/ui/feed/note/content.rs +++ b/src/ui/feed/note/content.rs @@ -57,12 +57,16 @@ pub(super) fn render_content( } let mut render_link = true; match note.repost { - Some(RepostType::MentionOnly) | Some(RepostType::CommentMention) => { + Some(RepostType::MentionOnly) + | Some(RepostType::CommentMention) + | Some(RepostType::Kind6Mention) => { for (i, event) in note.cached_mentions.iter() { if *i == num { // FIXME is there a way to consume just this entry in cached_mentions so // we can avoid the clone? - if let Some(note_data) = super::NoteData::new(event.clone(), true) { + if let Some(note_data) = + super::NoteData::new(event.clone(), true) + { super::render_repost(app, ui, ctx, note_data); render_link = false; } diff --git a/src/ui/feed/note/mod.rs b/src/ui/feed/note/mod.rs index 31e5b82b..8a1ef8df 100644 --- a/src/ui/feed/note/mod.rs +++ b/src/ui/feed/note/mod.rs @@ -68,14 +68,14 @@ impl NoteData { let (reactions, self_already_reacted) = Globals::get_reactions_sync(event.id); let cached_mentions = { - let mut cached_mentions= Vec::<(usize, Event)>::new(); + let mut cached_mentions = Vec::<(usize, Event)>::new(); for (i, tag) in event.tags.iter().enumerate() { match tag { Tag::Event { id, .. } => { if let Some(event) = GLOBALS.events.get(id) { cached_mentions.push((i, event)); } - }, + } _ => (), } } @@ -100,11 +100,12 @@ impl NoteData { None } } else if with_inline_mentions - && content_trim_len > 4 - && content_trim.chars().nth(content_trim_len - 1).unwrap() == ']' - && content_trim.chars().nth(content_trim_len - 3).unwrap() == '[' - && content_trim.chars().nth(content_trim_len - 4).unwrap() == '#' - && !cached_mentions.is_empty() { + && content_trim_len > 4 + && content_trim.chars().nth(content_trim_len - 1).unwrap() == ']' + && content_trim.chars().nth(content_trim_len - 3).unwrap() == '[' + && content_trim.chars().nth(content_trim_len - 4).unwrap() == '#' + && !cached_mentions.is_empty() + { // matches content that ends with a mention, avoiding use of a regex match Some(RepostType::CommentMention) } else { diff --git a/src/ui/theme/mod.rs b/src/ui/theme/mod.rs index 36f3c4b6..f88927ed 100644 --- a/src/ui/theme/mod.rs +++ b/src/ui/theme/mod.rs @@ -1,5 +1,5 @@ -use super::HighlightType; use super::feed::NoteRenderData; +use super::HighlightType; use eframe::egui::{ Color32, Context, FontData, FontDefinitions, FontTweak, Margin, Rounding, Stroke, Style, TextFormat, TextStyle, Ui, From c0fa261007db74a41b47d3b13bcd3f79b88609b0 Mon Sep 17 00:00:00 2001 From: bu5hm4nn Date: Tue, 14 Mar 2023 20:08:14 -0600 Subject: [PATCH 15/25] Correctly indent comments that come before reposts, refactor to render all reposts in the same code line (yay!) --- src/ui/feed/note/content.rs | 13 ++- src/ui/feed/note/mod.rs | 167 +++++++++++++++++------------------- 2 files changed, 89 insertions(+), 91 deletions(-) diff --git a/src/ui/feed/note/content.rs b/src/ui/feed/note/content.rs index 41c91f15..d7acf227 100644 --- a/src/ui/feed/note/content.rs +++ b/src/ui/feed/note/content.rs @@ -1,22 +1,25 @@ use super::{GossipUi, NoteData, Page, RepostType}; use crate::feed::FeedKind; use crate::globals::GLOBALS; -use eframe::egui::{self, Context}; +use eframe::egui; use egui::{RichText, Ui}; use linkify::{LinkFinder, LinkKind}; use nostr_types::{IdHex, Tag}; +/// returns None or a repost pub(super) fn render_content( app: &mut GossipUi, - ctx: &Context, ui: &mut Ui, note: &NoteData, as_deleted: bool, content: &str, -) { +) -> Option { let tag_re = app.tag_re.clone(); ui.style_mut().spacing.item_spacing.x = 0.0; + // Optional repost return + let mut append_repost: Option = None; + for span in LinkFinder::new().kinds(&[LinkKind::Url]).spans(content) { if span.kind().is_some() { if span.as_str().ends_with(".jpg") @@ -67,7 +70,7 @@ pub(super) fn render_content( if let Some(note_data) = super::NoteData::new(event.clone(), true) { - super::render_repost(app, ui, ctx, note_data); + append_repost = Some(note_data); render_link = false; } } @@ -111,4 +114,6 @@ pub(super) fn render_content( } ui.reset_style(); + + append_repost } diff --git a/src/ui/feed/note/mod.rs b/src/ui/feed/note/mod.rs index 8a1ef8df..7c10bd00 100644 --- a/src/ui/feed/note/mod.rs +++ b/src/ui/feed/note/mod.rs @@ -70,13 +70,10 @@ impl NoteData { let cached_mentions = { let mut cached_mentions = Vec::<(usize, Event)>::new(); for (i, tag) in event.tags.iter().enumerate() { - match tag { - Tag::Event { id, .. } => { - if let Some(event) = GLOBALS.events.get(id) { - cached_mentions.push((i, event)); - } + if let Tag::Event { id, .. } = tag { + if let Some(event) = GLOBALS.events.get(id) { + cached_mentions.push((i, event)); } - _ => (), } } cached_mentions @@ -343,17 +340,10 @@ fn render_note_inner( Some(_) => (AVATAR_SIZE_F32 - AVATAR_SIZE_REPOST_F32) / 2.0, }; - let content_margin_top = match repost { - None => inner_margin.top + ui.style().spacing.item_spacing.y * 2.0 - avatar_size, - Some(_) => 0.0, - }; + let content_margin_top = inner_margin.top + ui.style().spacing.item_spacing.y * 2.0 - avatar_size; - let footer_margin_left = AVATAR_SIZE_F32 + inner_margin.left; - - let content_margin_left = match repost { - None => avatar_size + inner_margin.left, - Some(_) => 0.0, - }; + let content_margin_left= AVATAR_SIZE_F32 + inner_margin.left; + let footer_margin_left = content_margin_left; ui.vertical(|ui| { // First row @@ -519,91 +509,94 @@ fn render_note_inner( // MAIN CONTENT if !collapsed { + let mut append_repost: Option = None; Frame::none() - .inner_margin(Margin { - left: content_margin_left, - bottom: 0.0, - right: 0.0, - top: 0.0, - }) - .outer_margin(Margin { - left: 0.0, - bottom: 0.0, - right: 0.0, - top: content_margin_top, - }) - .show(ui, |ui| { - ui.horizontal_wrapped(|ui| { - if app.render_raw == Some(event.id) { - ui.label(serde_json::to_string_pretty(&event).unwrap()); - } else if app.render_qr == Some(event.id) { - app.render_qr(ui, ctx, "feedqr", display_content.trim()); - // FIXME should this be the unmodified content (event.content)? - } else if event.content_warning().is_some() - && !app.approved.contains(&event.id) - { - ui.label( - RichText::new(format!( - "Content-Warning: {}", - event.content_warning().unwrap() - )) - .monospace() - .italics(), - ); - if ui.button("Show Post").clicked() { - app.approved.insert(event.id); - app.height.remove(&event.id); // will need to be recalculated. - } - } else if event.kind == EventKind::Repost { - if let Ok(inner_event) = serde_json::from_str::(&event.content) { - if let Some(inner_note_data) = NoteData::new(inner_event, false) { - render_repost(app, ui, ctx, inner_note_data); - } else { - ui.label("REPOSTED EVENT IS NOT RELEVANT"); - } + .inner_margin(Margin { + left: content_margin_left, + bottom: 0.0, + right: 0.0, + top: 0.0, + }) + .outer_margin(Margin { + left: 0.0, + bottom: 0.0, + right: 0.0, + top: content_margin_top, + }) + .show(ui, |ui| { + ui.horizontal_wrapped(|ui| { + if app.render_raw == Some(event.id) { + ui.label(serde_json::to_string_pretty(&event).unwrap()); + } else if app.render_qr == Some(event.id) { + app.render_qr(ui, ctx, "feedqr", display_content.trim()); + // FIXME should this be the unmodified content (event.content)? + } else if event.content_warning().is_some() + && !app.approved.contains(&event.id) + { + ui.label( + RichText::new(format!( + "Content-Warning: {}", + event.content_warning().unwrap() + )) + .monospace() + .italics(), + ); + if ui.button("Show Post").clicked() { + app.approved.insert(event.id); + app.height.remove(&event.id); // will need to be recalculated. + } + } else if event.kind == EventKind::Repost { + if let Ok(inner_event) = serde_json::from_str::(&event.content) { + if let Some(inner_note_data) = NoteData::new(inner_event, false) { + append_repost = Some(inner_note_data); } else { - // render like a kind-1 event with a mention - content::render_content( - app, - ctx, - ui, - ¬e_data, - deletion.is_some(), - display_content, - ); + ui.label("REPOSTED EVENT IS NOT RELEVANT"); } } else { - content::render_content( + // render like a kind-1 event with a mention + append_repost = content::render_content( app, - ctx, ui, ¬e_data, deletion.is_some(), display_content, ); } - }); + } else { + append_repost = content::render_content( + app, + ui, + ¬e_data, + deletion.is_some(), + display_content, + ); + } }); + }); - ui.add_space(8.0); - - // deleted? - if let Some(delete_reason) = &deletion { - ui.label(RichText::new(format!("Deletion Reason: {}", delete_reason)).italics()); - ui.add_space(8.0); + // render any repost without frame or indent + if let Some(repost) = append_repost { + render_repost(app, ui, ctx, repost) } // Under row - if !hide_footer && !note_data.repost.is_some() { - Frame::none() - .inner_margin(Margin { - left: footer_margin_left, - bottom: 0.0, - right: 0.0, - top: 0.0, - }) - .outer_margin(Margin::same(0.0)) - .show(ui, |ui| { + Frame::none() + .inner_margin(Margin { + left: footer_margin_left, + bottom: 0.0, + right: 0.0, + top: 8.0, + }) + .outer_margin(Margin::same(0.0)) + .show(ui, |ui| { + + // deleted? + if let Some(delete_reason) = &deletion { + ui.add_space(8.0); + ui.label(RichText::new(format!("Deletion Reason: {}", delete_reason)).italics()); + } + + if !hide_footer && note_data.repost.is_none() { ui.horizontal_wrapped(|ui| { if ui .add(CopyButton {}) @@ -723,8 +716,8 @@ fn render_note_inner( } } }); - }); - } + } + }); } }); } From 2c763c6146a6894d93e3fc986a851f5b88ae5692 Mon Sep 17 00:00:00 2001 From: bu5hm4nn Date: Wed, 15 Mar 2023 17:50:32 -0600 Subject: [PATCH 16/25] Fix whitespace and classic theme, addresses #305 --- src/ui/feed/note/content.rs | 9 +- src/ui/feed/note/mod.rs | 229 +++++++++++++++++++----------------- src/ui/theme/classic.rs | 6 +- 3 files changed, 127 insertions(+), 117 deletions(-) diff --git a/src/ui/feed/note/content.rs b/src/ui/feed/note/content.rs index d7acf227..6e7f2941 100644 --- a/src/ui/feed/note/content.rs +++ b/src/ui/feed/note/content.rs @@ -54,10 +54,6 @@ pub(super) fn render_content( }; } Tag::Event { id, .. } => { - // insert a newline if the current line has text - if ui.cursor().min.x > ui.max_rect().min.y { - ui.end_row(); - } let mut render_link = true; match note.repost { Some(RepostType::MentionOnly) @@ -79,6 +75,11 @@ pub(super) fn render_content( _ => (), } if render_link { + // insert a newline if the current line has text + if ui.cursor().min.x > ui.max_rect().min.y { + ui.end_row(); + } + let idhex: IdHex = (*id).into(); let nam = format!("#{}", GossipUi::hex_id_short(&idhex)); if ui.link(&nam).clicked() { diff --git a/src/ui/feed/note/mod.rs b/src/ui/feed/note/mod.rs index 7c10bd00..0f73effc 100644 --- a/src/ui/feed/note/mod.rs +++ b/src/ui/feed/note/mod.rs @@ -340,7 +340,7 @@ fn render_note_inner( Some(_) => (AVATAR_SIZE_F32 - AVATAR_SIZE_REPOST_F32) / 2.0, }; - let content_margin_top = inner_margin.top + ui.style().spacing.item_spacing.y * 2.0 - avatar_size; + let content_pull_top = inner_margin.top + ui.style().spacing.item_spacing.y * 4.0 - avatar_size; let content_margin_left= AVATAR_SIZE_F32 + inner_margin.left; let footer_margin_left = content_margin_left; @@ -521,7 +521,7 @@ fn render_note_inner( left: 0.0, bottom: 0.0, right: 0.0, - top: content_margin_top, + top: content_pull_top, }) .show(ui, |ui| { ui.horizontal_wrapped(|ui| { @@ -579,7 +579,19 @@ fn render_note_inner( render_repost(app, ui, ctx, repost) } - // Under row + // deleted? + if let Some(delete_reason) = &deletion { + Frame::none().inner_margin(Margin{left: footer_margin_left, + bottom: 0.0, + right: 0.0, + top: 8.0,}) + .show(ui, |ui| { + ui.label(RichText::new(format!("Deletion Reason: {}", delete_reason)).italics()); + }); + } + + // Footer + if !hide_footer && note_data.repost.is_none() { Frame::none() .inner_margin(Margin { left: footer_margin_left, @@ -587,137 +599,134 @@ fn render_note_inner( right: 0.0, top: 8.0, }) - .outer_margin(Margin::same(0.0)) + .outer_margin(Margin { + left: 0.0, + bottom: 0.0, + right: 0.0, + top: 0.0, + }) .show(ui, |ui| { - - // deleted? - if let Some(delete_reason) = &deletion { - ui.add_space(8.0); - ui.label(RichText::new(format!("Deletion Reason: {}", delete_reason)).italics()); - } - - if !hide_footer && note_data.repost.is_none() { - ui.horizontal_wrapped(|ui| { - if ui - .add(CopyButton {}) - .on_hover_text("Copy Contents") - .clicked() - { - if app.render_raw == Some(event.id) { - ui.output_mut(|o| { - o.copied_text = serde_json::to_string(&event).unwrap() - }); - } else { - ui.output_mut(|o| o.copied_text = display_content.clone()); - } + ui.horizontal_wrapped(|ui| { + if ui + .add(CopyButton {}) + .on_hover_text("Copy Contents") + .clicked() + { + if app.render_raw == Some(event.id) { + ui.output_mut(|o| { + o.copied_text = serde_json::to_string(&event).unwrap() + }); + } else { + ui.output_mut(|o| o.copied_text = display_content.clone()); } + } - ui.add_space(24.0); + ui.add_space(24.0); - // Button to quote note + // Button to quote note + if ui + .add( + Label::new(RichText::new("»").size(18.0)).sense(Sense::click()), + ) + .on_hover_text("Quote") + .clicked() + { + if !app.draft.ends_with(' ') && !app.draft.is_empty() { + app.draft.push(' '); + } + app.draft + .push_str(&event.id.try_as_bech32_string().unwrap()); + } + + ui.add_space(24.0); + + // Button to reply + if event.kind != EventKind::EncryptedDirectMessage { if ui .add( - Label::new(RichText::new("»").size(18.0)).sense(Sense::click()), - ) - .on_hover_text("Quote") - .clicked() - { - if !app.draft.ends_with(' ') && !app.draft.is_empty() { - app.draft.push(' '); - } - app.draft - .push_str(&event.id.try_as_bech32_string().unwrap()); - } - - ui.add_space(24.0); - - // Button to reply - if event.kind != EventKind::EncryptedDirectMessage { - if ui - .add( - Label::new(RichText::new("💬").size(18.0)) - .sense(Sense::click()), - ) - .on_hover_text("Reply") - .clicked() - { - app.replying_to = Some(event.id); - } - - ui.add_space(24.0); - } - - // Button to render raw - if ui - .add( - Label::new(RichText::new("🥩").size(13.0)) + Label::new(RichText::new("💬").size(18.0)) .sense(Sense::click()), ) - .on_hover_text("Raw") + .on_hover_text("Reply") .clicked() { - if app.render_raw != Some(event.id) { - app.render_raw = Some(event.id); - } else { - app.render_raw = None; - } + app.replying_to = Some(event.id); } ui.add_space(24.0); + } - // Button to render QR code + // Button to render raw + if ui + .add( + Label::new(RichText::new("🥩").size(13.0)) + .sense(Sense::click()), + ) + .on_hover_text("Raw") + .clicked() + { + if app.render_raw != Some(event.id) { + app.render_raw = Some(event.id); + } else { + app.render_raw = None; + } + } + + ui.add_space(24.0); + + // Button to render QR code + if ui + .add( + Label::new(RichText::new("⚃").size(16.0)).sense(Sense::click()), + ) + .on_hover_text("QR Code") + .clicked() + { + if app.render_qr != Some(event.id) { + app.render_qr = Some(event.id); + app.qr_codes.remove("feedqr"); + } else { + app.render_qr = None; + app.qr_codes.remove("feedqr"); + } + } + + ui.add_space(24.0); + + // Buttons to react and reaction counts + if app.settings.reactions { + let default_reaction_icon = match self_already_reacted { + true => "♥", + false => "♡", + }; if ui .add( - Label::new(RichText::new("⚃").size(16.0)).sense(Sense::click()), + Label::new(RichText::new(default_reaction_icon).size(20.0)) + .sense(Sense::click()), ) - .on_hover_text("QR Code") .clicked() { - if app.render_qr != Some(event.id) { - app.render_qr = Some(event.id); - app.qr_codes.remove("feedqr"); - } else { - app.render_qr = None; - app.qr_codes.remove("feedqr"); + let _ = GLOBALS + .to_overlord + .send(ToOverlordMessage::Like(event.id, event.pubkey)); + } + for (ch, count) in reactions.iter() { + if *ch == '+' { + ui.label(format!("{}", count)); } } - - ui.add_space(24.0); - - // Buttons to react and reaction counts - if app.settings.reactions { - let default_reaction_icon = match self_already_reacted { - true => "♥", - false => "♡", - }; - if ui - .add( - Label::new(RichText::new(default_reaction_icon).size(20.0)) - .sense(Sense::click()), - ) - .clicked() - { - let _ = GLOBALS - .to_overlord - .send(ToOverlordMessage::Like(event.id, event.pubkey)); - } - for (ch, count) in reactions.iter() { - if *ch == '+' { - ui.label(format!("{}", count)); - } - } - ui.add_space(12.0); - for (ch, count) in reactions.iter() { - if *ch != '+' { - ui.label( - RichText::new(format!("{} {}", ch, count)).strong(), - ); - } + ui.add_space(12.0); + for (ch, count) in reactions.iter() { + if *ch != '+' { + ui.label( + RichText::new(format!("{} {}", ch, count)).strong(), + ); } } - }); - } + } + }); }); + } } }); } diff --git a/src/ui/theme/classic.rs b/src/ui/theme/classic.rs index 9ec6034c..3d20d57d 100644 --- a/src/ui/theme/classic.rs +++ b/src/ui/theme/classic.rs @@ -401,10 +401,10 @@ impl ThemeDef for ClassicTheme { } fn feed_frame_inner_margin(_post: &NoteRenderData) -> Margin { Margin { - left: 0.0, + left: 10.0, top: 4.0, - right: 0.0, - bottom: 0.0, + right: 10.0, + bottom: 4.0, } } fn feed_frame_outer_margin(_post: &NoteRenderData) -> Margin { From 0bb6cc9d2d72da3bda442c814d0fb29d2174e4b1 Mon Sep 17 00:00:00 2001 From: bu5hm4nn Date: Wed, 15 Mar 2023 22:27:00 -0600 Subject: [PATCH 17/25] Always trim whitespace around notes --- src/ui/feed/note/mod.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/ui/feed/note/mod.rs b/src/ui/feed/note/mod.rs index 0f73effc..603f6ef3 100644 --- a/src/ui/feed/note/mod.rs +++ b/src/ui/feed/note/mod.rs @@ -124,14 +124,12 @@ impl NoteData { // Compute the content to our needs let display_content = match event.kind { - EventKind::TextNote => event.content.clone(), + EventKind::TextNote => event.content.trim().to_string(), EventKind::Repost => { if event.content.is_empty() { "#[0]".to_owned() // a bit of a hack - } else if event.content.trim() == "#[0]" { - event.content.trim().to_string() } else { - event.content.clone() + event.content.trim().to_string() } } EventKind::EncryptedDirectMessage => match GLOBALS.signer.decrypt_message(&event) { From c635cb129a30abb0c5747c4c2c405fd35a1ef18b Mon Sep 17 00:00:00 2001 From: bu5hm4nn Date: Wed, 15 Mar 2023 22:27:24 -0600 Subject: [PATCH 18/25] Give themes control over repost separator spacing --- src/ui/feed/note/mod.rs | 6 ++---- src/ui/theme/classic.rs | 7 +++++++ src/ui/theme/default.rs | 7 +++++++ src/ui/theme/mod.rs | 14 ++++++++++++++ src/ui/theme/roundy.rs | 7 +++++++ 5 files changed, 37 insertions(+), 4 deletions(-) diff --git a/src/ui/feed/note/mod.rs b/src/ui/feed/note/mod.rs index 603f6ef3..8d1709cb 100644 --- a/src/ui/feed/note/mod.rs +++ b/src/ui/feed/note/mod.rs @@ -753,12 +753,10 @@ pub(super) fn render_repost(app: &mut GossipUi, ui: &mut Ui, ctx: &Context, repo thread_position: 0, }; - let margin = app.settings.theme.feed_frame_inner_margin(&render_data); - ui.vertical(|ui| { - ui.add_space(margin.bottom); + ui.add_space(app.settings.theme.repost_space_above_separator(&render_data)); thin_repost_separator(ui); - ui.add_space(margin.top); + ui.add_space(app.settings.theme.repost_space_below_separator(&render_data)); ui.horizontal_wrapped(|ui| { // FIXME: don't do this recursively render_note_inner(app, ctx, ui, repost_data, &render_data, false); diff --git a/src/ui/theme/classic.rs b/src/ui/theme/classic.rs index 3d20d57d..d549199a 100644 --- a/src/ui/theme/classic.rs +++ b/src/ui/theme/classic.rs @@ -435,6 +435,13 @@ impl ThemeDef for ClassicTheme { Stroke::NONE } + fn repost_space_above_separator(_post: &NoteRenderData) -> f32 { + 4.0 + } + fn repost_space_below_separator(_post: &NoteRenderData) -> f32 { + 8.0 + } + fn round_image() -> bool { false } diff --git a/src/ui/theme/default.rs b/src/ui/theme/default.rs index 1d2ff0b7..9bfd8c93 100644 --- a/src/ui/theme/default.rs +++ b/src/ui/theme/default.rs @@ -463,6 +463,13 @@ impl ThemeDef for DefaultTheme { Stroke::NONE } + fn repost_space_above_separator(_post: &NoteRenderData) -> f32 { + 0.0 + } + fn repost_space_below_separator(_post: &NoteRenderData) -> f32 { + 10.0 + } + fn round_image() -> bool { true } diff --git a/src/ui/theme/mod.rs b/src/ui/theme/mod.rs index f88927ed..88cb4f34 100644 --- a/src/ui/theme/mod.rs +++ b/src/ui/theme/mod.rs @@ -182,6 +182,18 @@ macro_rules! theme_dispatch { } } + pub fn repost_space_above_separator(&self, post: &NoteRenderData) -> f32 { + match self.variant { + $( $variant => $class::repost_space_above_separator(post), )+ + } + } + + pub fn repost_space_below_separator(&self, post: &NoteRenderData) -> f32 { + match self.variant { + $( $variant => $class::repost_space_below_separator(post), )+ + } + } + pub fn round_image(&self) -> bool { match self.variant { $( $variant => $class::round_image(), )+ @@ -235,6 +247,8 @@ pub trait ThemeDef: Send + Sync { fn feed_frame_shadow(dark_mode: bool, post: &NoteRenderData) -> Shadow; fn feed_frame_fill(dark_mode: bool, post: &NoteRenderData) -> Color32; fn feed_frame_stroke(dark_mode: bool, post: &NoteRenderData) -> Stroke; + fn repost_space_above_separator(post: &NoteRenderData) -> f32; + fn repost_space_below_separator(post: &NoteRenderData) -> f32; // image rounding fn round_image() -> bool; diff --git a/src/ui/theme/roundy.rs b/src/ui/theme/roundy.rs index 7f5b76ec..32fed06f 100644 --- a/src/ui/theme/roundy.rs +++ b/src/ui/theme/roundy.rs @@ -447,6 +447,13 @@ impl ThemeDef for RoundyTheme { } } + fn repost_space_above_separator(_post: &NoteRenderData) -> f32 { + 0.0 + } + fn repost_space_below_separator(_post: &NoteRenderData) -> f32 { + 8.0 + } + fn round_image() -> bool { true } From 2854c36b1295b36d4ffde399cce24675b8967cd5 Mon Sep 17 00:00:00 2001 From: bu5hm4nn Date: Thu, 16 Mar 2023 15:58:16 -0600 Subject: [PATCH 19/25] Fix vertical alignment for header following username (REPOST, etc) --- src/ui/feed/note/mod.rs | 80 +++++++++++++++++++++-------------------- 1 file changed, 41 insertions(+), 39 deletions(-) diff --git a/src/ui/feed/note/mod.rs b/src/ui/feed/note/mod.rs index 8d1709cb..e0731b1d 100644 --- a/src/ui/feed/note/mod.rs +++ b/src/ui/feed/note/mod.rs @@ -372,54 +372,56 @@ fn render_note_inner( GossipUi::render_person_name_line(app, ui, author); - if let Some((irt, _)) = event.replies_to() { + ui.horizontal_wrapped(|ui| { + if let Some((irt, _)) = event.replies_to() { + ui.add_space(8.0); + + ui.style_mut().override_text_style = Some(TextStyle::Small); + let idhex: IdHex = irt.into(); + let nam = format!("▲ #{}", GossipUi::hex_id_short(&idhex)); + if ui.link(&nam).clicked() { + app.set_page(Page::Feed(FeedKind::Thread { + id: irt, + referenced_by: event.id, + })); + }; + ui.reset_style(); + } + ui.add_space(8.0); - ui.style_mut().override_text_style = Some(TextStyle::Small); - let idhex: IdHex = irt.into(); - let nam = format!("▲ #{}", GossipUi::hex_id_short(&idhex)); - if ui.link(&nam).clicked() { - app.set_page(Page::Feed(FeedKind::Thread { - id: irt, - referenced_by: event.id, - })); - }; - ui.reset_style(); - } + if event.pow() > 0 { + ui.label(format!("POW={}", event.pow())); + } - ui.add_space(8.0); + match delegation { + EventDelegation::InvalidDelegation(why) => { + let color = app.settings.theme.warning_marker_text_color(); + ui.add(Label::new(RichText::new("INVALID DELEGATION").color(color))) + .on_hover_text(why); + } + EventDelegation::DelegatedBy(_) => { + let color = app.settings.theme.notice_marker_text_color(); + ui.label(RichText::new("DELEGATED").color(color)); + } + _ => {} + } - if event.pow() > 0 { - ui.label(format!("POW={}", event.pow())); - } - - match delegation { - EventDelegation::InvalidDelegation(why) => { + if deletion.is_some() { let color = app.settings.theme.warning_marker_text_color(); - ui.add(Label::new(RichText::new("INVALID DELEGATION").color(color))) - .on_hover_text(why); + ui.label(RichText::new("DELETED").color(color)); } - EventDelegation::DelegatedBy(_) => { + + if event.kind == EventKind::Repost { let color = app.settings.theme.notice_marker_text_color(); - ui.label(RichText::new("DELEGATED").color(color)); + ui.label(RichText::new("REPOSTED").color(color)); } - _ => {} - } - if deletion.is_some() { - let color = app.settings.theme.warning_marker_text_color(); - ui.label(RichText::new("DELETED").color(color)); - } - - if event.kind == EventKind::Repost { - let color = app.settings.theme.notice_marker_text_color(); - ui.label(RichText::new("REPOSTED").color(color)); - } - - if event.kind == EventKind::EncryptedDirectMessage { - let color = app.settings.theme.notice_marker_text_color(); - ui.label(RichText::new("ENCRYPTED DM").color(color)); - } + if event.kind == EventKind::EncryptedDirectMessage { + let color = app.settings.theme.notice_marker_text_color(); + ui.label(RichText::new("ENCRYPTED DM").color(color)); + } + }); ui.with_layout(Layout::right_to_left(Align::TOP), |ui| { ui.menu_button(RichText::new("=").size(13.0), |ui| { From 5533b4a4ed6db850a636b033f2ad1ed6c7e7db68 Mon Sep 17 00:00:00 2001 From: bu5hm4nn Date: Thu, 16 Mar 2023 15:59:04 -0600 Subject: [PATCH 20/25] Add theme option to style repost stroke --- src/ui/feed/note/mod.rs | 7 +------ src/ui/theme/classic.rs | 8 ++++++++ src/ui/theme/default.rs | 8 ++++++++ src/ui/theme/mod.rs | 7 +++++++ src/ui/theme/roundy.rs | 8 ++++++++ 5 files changed, 32 insertions(+), 6 deletions(-) diff --git a/src/ui/feed/note/mod.rs b/src/ui/feed/note/mod.rs index e0731b1d..f8827d91 100644 --- a/src/ui/feed/note/mod.rs +++ b/src/ui/feed/note/mod.rs @@ -731,11 +731,6 @@ fn render_note_inner( }); } -fn thin_repost_separator(ui: &mut Ui) { - let stroke = ui.style().visuals.widgets.noninteractive.bg_stroke; - thin_separator(ui, stroke); -} - fn thin_separator(ui: &mut Ui, stroke: Stroke) { let mut style = ui.style_mut(); style.visuals.widgets.noninteractive.bg_stroke = stroke; @@ -757,7 +752,7 @@ pub(super) fn render_repost(app: &mut GossipUi, ui: &mut Ui, ctx: &Context, repo ui.vertical(|ui| { ui.add_space(app.settings.theme.repost_space_above_separator(&render_data)); - thin_repost_separator(ui); + thin_separator(ui, app.settings.theme.repost_separator_stroke(&render_data)); ui.add_space(app.settings.theme.repost_space_below_separator(&render_data)); ui.horizontal_wrapped(|ui| { // FIXME: don't do this recursively diff --git a/src/ui/theme/classic.rs b/src/ui/theme/classic.rs index d549199a..11c4c135 100644 --- a/src/ui/theme/classic.rs +++ b/src/ui/theme/classic.rs @@ -435,6 +435,14 @@ impl ThemeDef for ClassicTheme { Stroke::NONE } + fn repost_separator_stroke(dark_mode: bool, _post: &NoteRenderData) -> Stroke { + if dark_mode { + Stroke::new(1.0, Color32::from_gray(72)) + } else { + Stroke::new(1.0, Color32::from_gray(192)) + } + } + fn repost_space_above_separator(_post: &NoteRenderData) -> f32 { 4.0 } diff --git a/src/ui/theme/default.rs b/src/ui/theme/default.rs index 9bfd8c93..94a3190f 100644 --- a/src/ui/theme/default.rs +++ b/src/ui/theme/default.rs @@ -463,6 +463,14 @@ impl ThemeDef for DefaultTheme { Stroke::NONE } + fn repost_separator_stroke(dark_mode: bool, _post: &NoteRenderData) -> Stroke { + if dark_mode { + Stroke::new(1.0, Color32::from_gray(60)) + } else { + Stroke::new(1.0, Color32::from_gray(230)) + } + } + fn repost_space_above_separator(_post: &NoteRenderData) -> f32 { 0.0 } diff --git a/src/ui/theme/mod.rs b/src/ui/theme/mod.rs index 88cb4f34..1dee8d6a 100644 --- a/src/ui/theme/mod.rs +++ b/src/ui/theme/mod.rs @@ -182,6 +182,12 @@ macro_rules! theme_dispatch { } } + pub fn repost_separator_stroke(&self, post: &NoteRenderData) -> Stroke { + match self.variant { + $( $variant => $class::repost_separator_stroke(self.dark_mode, post), )+ + } + } + pub fn repost_space_above_separator(&self, post: &NoteRenderData) -> f32 { match self.variant { $( $variant => $class::repost_space_above_separator(post), )+ @@ -247,6 +253,7 @@ pub trait ThemeDef: Send + Sync { fn feed_frame_shadow(dark_mode: bool, post: &NoteRenderData) -> Shadow; fn feed_frame_fill(dark_mode: bool, post: &NoteRenderData) -> Color32; fn feed_frame_stroke(dark_mode: bool, post: &NoteRenderData) -> Stroke; + fn repost_separator_stroke(dark_mode: bool, post: &NoteRenderData) -> Stroke; fn repost_space_above_separator(post: &NoteRenderData) -> f32; fn repost_space_below_separator(post: &NoteRenderData) -> f32; diff --git a/src/ui/theme/roundy.rs b/src/ui/theme/roundy.rs index 32fed06f..4c904cd0 100644 --- a/src/ui/theme/roundy.rs +++ b/src/ui/theme/roundy.rs @@ -447,6 +447,14 @@ impl ThemeDef for RoundyTheme { } } + fn repost_separator_stroke(dark_mode: bool, _post: &NoteRenderData) -> Stroke { + if dark_mode { + Stroke::new(1.0, Color32::from_gray(72)) + } else { + Stroke::new(1.0, Color32::from_gray(192)) + } + } + fn repost_space_above_separator(_post: &NoteRenderData) -> f32 { 0.0 } From 477ac8b5bce3dc7b27282e85288c159790d3b7fd Mon Sep 17 00:00:00 2001 From: bu5hm4nn Date: Sat, 18 Mar 2023 18:00:42 -0600 Subject: [PATCH 21/25] Accept only 'e' tags marked as "mention" to comply with NIP-10 --- src/ui/feed/note/mod.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/ui/feed/note/mod.rs b/src/ui/feed/note/mod.rs index f8827d91..c3f894a8 100644 --- a/src/ui/feed/note/mod.rs +++ b/src/ui/feed/note/mod.rs @@ -67,12 +67,16 @@ impl NoteData { let (reactions, self_already_reacted) = Globals::get_reactions_sync(event.id); + // build a list of all cached mentions and their index + // only notes that are in the cache will be rendered as reposts let cached_mentions = { let mut cached_mentions = Vec::<(usize, Event)>::new(); for (i, tag) in event.tags.iter().enumerate() { - if let Tag::Event { id, .. } = tag { - if let Some(event) = GLOBALS.events.get(id) { - cached_mentions.push((i, event)); + if let Tag::Event { id, recommended_relay_url:_, marker } = tag { + if marker.is_some() && marker.as_deref().unwrap() == "mention" { + if let Some(event) = GLOBALS.events.get(id) { + cached_mentions.push((i, event)); + } } } } From 06300b8920d5b09bd689a9b8c4c5a9f1f42e95f5 Mon Sep 17 00:00:00 2001 From: bu5hm4nn Date: Mon, 20 Mar 2023 10:29:54 -0600 Subject: [PATCH 22/25] Add optional frame around repost. Also comment mentions now have reversed avatar size and show of interaction buttons. --- src/ui/feed/note/mod.rs | 75 +++++++++++++++++++++++++++++--------- src/ui/theme/classic.rs | 47 ++++++++++++++++++++++-- src/ui/theme/default.rs | 53 +++++++++++++++++++++++++-- src/ui/theme/mod.rs | 81 ++++++++++++++++++++++++++++++++++++----- src/ui/theme/roundy.rs | 66 ++++++++++++++++++++++++++++----- 5 files changed, 279 insertions(+), 43 deletions(-) diff --git a/src/ui/feed/note/mod.rs b/src/ui/feed/note/mod.rs index c3f894a8..f1eb50eb 100644 --- a/src/ui/feed/note/mod.rs +++ b/src/ui/feed/note/mod.rs @@ -254,7 +254,7 @@ pub(super) fn render_note( if note_data.author.muted > 0 { ui.label(RichText::new("MUTED POST").monospace().italics()); } else { - render_note_inner(app, ctx, ui, note_data, &render_data, hide_footer); + render_note_inner(app, ctx, ui, note_data, &render_data, hide_footer, &None); } }); }); @@ -307,6 +307,7 @@ fn render_note_inner( note_data: NoteData, render_data: &NoteRenderData, hide_footer: bool, + parent_repost: &Option, ) { let NoteData { event, @@ -330,16 +331,44 @@ fn render_note_inner( }; // Determine avatar size - let avatar_size = match repost { - None => AVATAR_SIZE_F32, - Some(_) => AVATAR_SIZE_REPOST_F32, + let avatar_size = if parent_repost.is_none() { + match repost { + None | Some(RepostType::CommentMention) => AVATAR_SIZE_F32, + Some(_) => AVATAR_SIZE_REPOST_F32, + } + } else { + match parent_repost { + None | Some(RepostType::CommentMention) => AVATAR_SIZE_REPOST_F32, + Some(_) => AVATAR_SIZE_F32, + } }; let inner_margin = app.settings.theme.feed_frame_inner_margin(render_data); - let avatar_margin_left = match repost { - None => 0.0, - Some(_) => (AVATAR_SIZE_F32 - AVATAR_SIZE_REPOST_F32) / 2.0, + let avatar_margin_left = if parent_repost.is_none() { + match repost { + None | Some(RepostType::CommentMention) => 0.0, + Some(_) => (AVATAR_SIZE_F32 - AVATAR_SIZE_REPOST_F32) / 2.0, + } + } else { + match parent_repost { + None | Some(RepostType::CommentMention) => (AVATAR_SIZE_F32 - AVATAR_SIZE_REPOST_F32) / 2.0, + Some(_) => 0.0, + } + }; + + let hide_footer = if hide_footer { + true + } else if parent_repost.is_none() { + match repost { + None | Some(RepostType::CommentMention) => false, + Some(_) => true, + } + } else { + match parent_repost { + None | Some(RepostType::CommentMention) => true, + Some(_) => false, + } }; let content_pull_top = inner_margin.top + ui.style().spacing.item_spacing.y * 4.0 - avatar_size; @@ -580,7 +609,7 @@ fn render_note_inner( // render any repost without frame or indent if let Some(repost) = append_repost { - render_repost(app, ui, ctx, repost) + render_repost(app, ui, ctx, ¬e_data, repost) } // deleted? @@ -595,7 +624,7 @@ fn render_note_inner( } // Footer - if !hide_footer && note_data.repost.is_none() { + if !hide_footer { Frame::none() .inner_margin(Margin { left: footer_margin_left, @@ -742,7 +771,7 @@ fn thin_separator(ui: &mut Ui, stroke: Stroke) { ui.reset_style(); } -pub(super) fn render_repost(app: &mut GossipUi, ui: &mut Ui, ctx: &Context, repost_data: NoteData) { +pub(super) fn render_repost(app: &mut GossipUi, ui: &mut Ui, ctx: &Context, parent_data: &NoteData, repost_data: NoteData) { let render_data = NoteRenderData { height: 0.0, has_repost: repost_data.repost.is_some(), @@ -755,12 +784,24 @@ pub(super) fn render_repost(app: &mut GossipUi, ui: &mut Ui, ctx: &Context, repo }; ui.vertical(|ui| { - ui.add_space(app.settings.theme.repost_space_above_separator(&render_data)); - thin_separator(ui, app.settings.theme.repost_separator_stroke(&render_data)); - ui.add_space(app.settings.theme.repost_space_below_separator(&render_data)); - ui.horizontal_wrapped(|ui| { - // FIXME: don't do this recursively - render_note_inner(app, ctx, ui, repost_data, &render_data, false); - }); + ui.add_space(app.settings.theme.repost_space_above_separator_before(&render_data)); + thin_separator(ui, app.settings.theme.repost_separator_before_stroke(&render_data)); + ui.add_space(app.settings.theme.repost_space_below_separator_before(&render_data)); + Frame::none() + .inner_margin(app.settings.theme.repost_inner_margin(&render_data)) + .outer_margin(app.settings.theme.repost_outer_margin(&render_data)) + .rounding(app.settings.theme.repost_rounding(&render_data)) + .shadow(app.settings.theme.repost_shadow(&render_data)) + .fill(app.settings.theme.repost_fill(&render_data)) + .stroke(app.settings.theme.repost_stroke(&render_data)) + .show(ui, |ui|{ + ui.horizontal_wrapped(|ui| { + // FIXME: don't do this recursively + render_note_inner(app, ctx, ui, repost_data, &render_data, false,&parent_data.repost); + }); + }); + ui.add_space(app.settings.theme.repost_space_above_separator_after(&render_data)); + thin_separator(ui, app.settings.theme.repost_separator_after_stroke(&render_data)); + ui.add_space(app.settings.theme.repost_space_below_separator_after(&render_data)); }); } diff --git a/src/ui/theme/classic.rs b/src/ui/theme/classic.rs index 11c4c135..708a23fa 100644 --- a/src/ui/theme/classic.rs +++ b/src/ui/theme/classic.rs @@ -435,7 +435,7 @@ impl ThemeDef for ClassicTheme { Stroke::NONE } - fn repost_separator_stroke(dark_mode: bool, _post: &NoteRenderData) -> Stroke { + fn repost_separator_before_stroke(dark_mode: bool, _post: &NoteRenderData) -> Stroke { if dark_mode { Stroke::new(1.0, Color32::from_gray(72)) } else { @@ -443,13 +443,54 @@ impl ThemeDef for ClassicTheme { } } - fn repost_space_above_separator(_post: &NoteRenderData) -> f32 { + fn repost_space_above_separator_before(_post: &NoteRenderData) -> f32 { 4.0 } - fn repost_space_below_separator(_post: &NoteRenderData) -> f32 { + fn repost_space_below_separator_before(_post: &NoteRenderData) -> f32 { 8.0 } + fn repost_separator_after_stroke(dark_mode: bool, post: &NoteRenderData) -> Stroke { + Self::repost_separator_before_stroke(dark_mode, post) + } + fn repost_space_above_separator_after(_post: &NoteRenderData) -> f32 { + 4.0 + } + fn repost_space_below_separator_after(_post: &NoteRenderData) -> f32 { + 0.0 + } + + fn repost_inner_margin(_post: &NoteRenderData) -> Margin { + // Margin { + // left: 10.0, + // top: 4.0, + // right: 10.0, + // bottom: 4.0, + // } + Margin::same(0.0) + } + fn repost_outer_margin(_post: &NoteRenderData) -> Margin { + // Margin { + // left: -10.0, + // top: -4.0, + // right: -10.0, + // bottom: -4.0, + // } + Margin::same(0.0) + } + fn repost_rounding(post: &NoteRenderData) -> Rounding { + Self::feed_frame_rounding(post) + } + fn repost_shadow(_dark_mode: bool, _post: &NoteRenderData) -> Shadow { + Shadow::NONE + } + fn repost_fill(_dark_mode: bool, _post: &NoteRenderData) -> Color32 { + Color32::TRANSPARENT + } + fn repost_stroke(_dark_mode: bool, _post: &NoteRenderData) -> Stroke { + Stroke::NONE + } + fn round_image() -> bool { false } diff --git a/src/ui/theme/default.rs b/src/ui/theme/default.rs index 94a3190f..825acc8e 100644 --- a/src/ui/theme/default.rs +++ b/src/ui/theme/default.rs @@ -463,21 +463,66 @@ impl ThemeDef for DefaultTheme { Stroke::NONE } - fn repost_separator_stroke(dark_mode: bool, _post: &NoteRenderData) -> Stroke { + fn repost_separator_before_stroke(dark_mode: bool, _post: &NoteRenderData) -> Stroke { if dark_mode { Stroke::new(1.0, Color32::from_gray(60)) } else { Stroke::new(1.0, Color32::from_gray(230)) } } - - fn repost_space_above_separator(_post: &NoteRenderData) -> f32 { + fn repost_space_above_separator_before(_post: &NoteRenderData) -> f32 { 0.0 } - fn repost_space_below_separator(_post: &NoteRenderData) -> f32 { + fn repost_space_below_separator_before(_post: &NoteRenderData) -> f32 { 10.0 } + fn repost_separator_after_stroke(_dark_mode: bool, _post: &NoteRenderData) -> Stroke { + Stroke::NONE + } + fn repost_space_above_separator_after(_post: &NoteRenderData) -> f32 { + 0.0 + } + fn repost_space_below_separator_after(_post: &NoteRenderData) -> f32 { + 0.0 + } + + fn repost_inner_margin(_post: &NoteRenderData) -> Margin { + Margin { + left: 5.0, + top: 14.0, + right: 6.0, + bottom: 6.0, + } + } + fn repost_outer_margin(_post: &NoteRenderData) -> Margin { + Margin { + left: -5.0, + top: -14.0, + right: -6.0, + bottom: -3.0, + } + } + fn repost_rounding(post: &NoteRenderData) -> Rounding { + Self::feed_frame_rounding(post) + } + fn repost_shadow(_dark_mode: bool, _post: &NoteRenderData) -> Shadow { + Shadow::NONE + } + fn repost_fill(dark_mode: bool, post: &NoteRenderData) -> Color32 { + let mut hsva: ecolor::HsvaGamma = Self::feed_frame_fill(dark_mode, post).into(); + if dark_mode { + hsva.v = (hsva.v + 0.05).min(1.0); // lighten + } else { + hsva.v = (hsva.v - 0.05).max(0.0); // darken + } + let color: Color32 = hsva.into(); + color + } + fn repost_stroke(dark_mode: bool, post: &NoteRenderData) -> Stroke { + Self::feed_frame_stroke(dark_mode, post) + } + fn round_image() -> bool { true } diff --git a/src/ui/theme/mod.rs b/src/ui/theme/mod.rs index 1dee8d6a..bb4df3c4 100644 --- a/src/ui/theme/mod.rs +++ b/src/ui/theme/mod.rs @@ -182,21 +182,75 @@ macro_rules! theme_dispatch { } } - pub fn repost_separator_stroke(&self, post: &NoteRenderData) -> Stroke { + pub fn repost_separator_before_stroke(&self, post: &NoteRenderData) -> Stroke { match self.variant { - $( $variant => $class::repost_separator_stroke(self.dark_mode, post), )+ + $( $variant => $class::repost_separator_before_stroke(self.dark_mode, post), )+ } } - pub fn repost_space_above_separator(&self, post: &NoteRenderData) -> f32 { + pub fn repost_space_above_separator_before(&self, post: &NoteRenderData) -> f32 { match self.variant { - $( $variant => $class::repost_space_above_separator(post), )+ + $( $variant => $class::repost_space_above_separator_before(post), )+ } } - pub fn repost_space_below_separator(&self, post: &NoteRenderData) -> f32 { + pub fn repost_space_below_separator_before(&self, post: &NoteRenderData) -> f32 { match self.variant { - $( $variant => $class::repost_space_below_separator(post), )+ + $( $variant => $class::repost_space_below_separator_before(post), )+ + } + } + + pub fn repost_separator_after_stroke(&self, post: &NoteRenderData) -> Stroke { + match self.variant { + $( $variant => $class::repost_separator_after_stroke(self.dark_mode, post), )+ + } + } + + pub fn repost_space_above_separator_after(&self, post: &NoteRenderData) -> f32 { + match self.variant { + $( $variant => $class::repost_space_above_separator_after(post), )+ + } + } + + pub fn repost_space_below_separator_after(&self, post: &NoteRenderData) -> f32 { + match self.variant { + $( $variant => $class::repost_space_below_separator_after(post), )+ + } + } + + pub fn repost_inner_margin(&self, post: &NoteRenderData) -> Margin { + match self.variant { + $( $variant => $class::repost_inner_margin(post), )+ + } + } + + pub fn repost_outer_margin(&self, post: &NoteRenderData) -> Margin { + match self.variant { + $( $variant => $class::repost_outer_margin(post), )+ + } + } + + pub fn repost_rounding(&self, post: &NoteRenderData) -> Rounding { + match self.variant { + $( $variant => $class::repost_rounding(post), )+ + } + } + + pub fn repost_shadow(&self, post: &NoteRenderData) -> Shadow { + match self.variant { + $( $variant => $class::repost_shadow(self.dark_mode, post), )+ + } + } + + pub fn repost_fill(&self, post: &NoteRenderData) -> Color32 { + match self.variant { + $( $variant => $class::repost_fill(self.dark_mode, post), )+ + } + } + + pub fn repost_stroke(&self, post: &NoteRenderData) -> Stroke { + match self.variant { + $( $variant => $class::repost_stroke(self.dark_mode, post), )+ } } @@ -253,9 +307,18 @@ pub trait ThemeDef: Send + Sync { fn feed_frame_shadow(dark_mode: bool, post: &NoteRenderData) -> Shadow; fn feed_frame_fill(dark_mode: bool, post: &NoteRenderData) -> Color32; fn feed_frame_stroke(dark_mode: bool, post: &NoteRenderData) -> Stroke; - fn repost_separator_stroke(dark_mode: bool, post: &NoteRenderData) -> Stroke; - fn repost_space_above_separator(post: &NoteRenderData) -> f32; - fn repost_space_below_separator(post: &NoteRenderData) -> f32; + fn repost_separator_before_stroke(dark_mode: bool, post: &NoteRenderData) -> Stroke; + fn repost_space_above_separator_before(post: &NoteRenderData) -> f32; + fn repost_space_below_separator_before(post: &NoteRenderData) -> f32; + fn repost_separator_after_stroke(dark_mode: bool, post: &NoteRenderData) -> Stroke; + fn repost_space_above_separator_after(post: &NoteRenderData) -> f32; + fn repost_space_below_separator_after(post: &NoteRenderData) -> f32; + fn repost_inner_margin(post: &NoteRenderData) -> Margin; + fn repost_outer_margin(post: &NoteRenderData) -> Margin; + fn repost_rounding(post: &NoteRenderData) -> Rounding; + fn repost_shadow(dark_mode: bool, post: &NoteRenderData) -> Shadow; + fn repost_fill(dark_mode: bool, post: &NoteRenderData) -> Color32; + fn repost_stroke(dark_mode: bool, post: &NoteRenderData) -> Stroke; // image rounding fn round_image() -> bool; diff --git a/src/ui/theme/roundy.rs b/src/ui/theme/roundy.rs index 4c904cd0..cf5303db 100644 --- a/src/ui/theme/roundy.rs +++ b/src/ui/theme/roundy.rs @@ -2,7 +2,7 @@ use super::{FeedProperties, NoteRenderData, ThemeDef}; use crate::ui::HighlightType; use eframe::egui::style::{Selection, WidgetVisuals, Widgets}; use eframe::egui::{FontDefinitions, Margin, Style, TextFormat, TextStyle, Visuals}; -use eframe::epaint::{Color32, FontFamily, FontId, Rounding, Shadow, Stroke}; +use eframe::epaint::{Color32, FontFamily, FontId, Rounding, Shadow, Stroke, ecolor}; use std::collections::BTreeMap; #[derive(Default)] @@ -447,21 +447,67 @@ impl ThemeDef for RoundyTheme { } } - fn repost_separator_stroke(dark_mode: bool, _post: &NoteRenderData) -> Stroke { - if dark_mode { - Stroke::new(1.0, Color32::from_gray(72)) - } else { - Stroke::new(1.0, Color32::from_gray(192)) - } + fn repost_separator_before_stroke(_dark_mode: bool, _post: &NoteRenderData) -> Stroke { + // if dark_mode { + // Stroke::new(1.0, Color32::from_gray(72)) + // } else { + // Stroke::new(1.0, Color32::from_gray(192)) + // } + Stroke::NONE } - - fn repost_space_above_separator(_post: &NoteRenderData) -> f32 { + fn repost_space_above_separator_before(_post: &NoteRenderData) -> f32 { 0.0 } - fn repost_space_below_separator(_post: &NoteRenderData) -> f32 { + fn repost_space_below_separator_before(_post: &NoteRenderData) -> f32 { 8.0 } + fn repost_separator_after_stroke(_dark_mode: bool, _post: &NoteRenderData) -> Stroke { + Stroke::NONE + } + fn repost_space_above_separator_after(_post: &NoteRenderData) -> f32 { + 0.0 + } + fn repost_space_below_separator_after(_post: &NoteRenderData) -> f32 { + 8.0 + } + + fn repost_inner_margin(_post: &NoteRenderData) -> Margin { + Margin { + left: 10.0, + right: 10.0, + top: 10.0, + bottom: 10.0, + } + } + fn repost_outer_margin(_post: &NoteRenderData) -> Margin { + Margin { + left: -10.0, + right: -10.0, + top: -10.0, + bottom: -6.0, + } + } + fn repost_rounding(post: &NoteRenderData) -> Rounding { + Self::feed_frame_rounding(post) + } + fn repost_shadow(_dark_mode: bool, _post: &NoteRenderData) -> Shadow { + Shadow::NONE + } + fn repost_fill(dark_mode: bool, post: &NoteRenderData) -> Color32 { + let mut hsva: ecolor::HsvaGamma = Self::feed_frame_fill(dark_mode, post).into(); + if dark_mode { + hsva.v = (hsva.v + 0.05).min(1.0); // lighten + } else { + hsva.v = (hsva.v - 0.05).max(0.0); // darken + } + let color: Color32 = hsva.into(); + color + } + fn repost_stroke(dark_mode: bool, post: &NoteRenderData) -> Stroke { + Self::feed_frame_stroke(dark_mode, post) + } + fn round_image() -> bool { true } From 5a202195cd245f68ac97628206b2ed419da31a09 Mon Sep 17 00:00:00 2001 From: bu5hm4nn Date: Mon, 20 Mar 2023 11:10:36 -0600 Subject: [PATCH 23/25] Allow theme to distinguish comment mentions --- src/ui/feed/note/mod.rs | 5 +++++ src/ui/theme/default.rs | 4 ++++ src/ui/theme/roundy.rs | 4 ++++ 3 files changed, 13 insertions(+) diff --git a/src/ui/feed/note/mod.rs b/src/ui/feed/note/mod.rs index f1eb50eb..0b1b4b12 100644 --- a/src/ui/feed/note/mod.rs +++ b/src/ui/feed/note/mod.rs @@ -16,6 +16,7 @@ use nostr_types::{Event, EventDelegation, EventKind, IdHex, PublicKeyHex, Tag}; mod content; +#[derive(PartialEq)] enum RepostType { /// Damus style, kind 6 repost where the reposted note's JSON /// is included in the content @@ -166,6 +167,8 @@ pub struct NoteRenderData { pub is_main_event: bool, /// This message is a repost of another message pub has_repost: bool, + /// Is this post being mentioned within a comment + pub is_comment_mention: bool, /// This message is part of a thread pub is_thread: bool, /// Is this the first post in the display? @@ -225,6 +228,7 @@ pub(super) fn render_note( let render_data = NoteRenderData { height: *height, has_repost: note_data.repost.is_some(), + is_comment_mention: false, is_new, is_thread: threaded, is_first, @@ -775,6 +779,7 @@ pub(super) fn render_repost(app: &mut GossipUi, ui: &mut Ui, ctx: &Context, pare let render_data = NoteRenderData { height: 0.0, has_repost: repost_data.repost.is_some(), + is_comment_mention: parent_data.repost == Some(RepostType::CommentMention), is_new: false, is_main_event: false, is_thread: false, diff --git a/src/ui/theme/default.rs b/src/ui/theme/default.rs index 825acc8e..5c05cc69 100644 --- a/src/ui/theme/default.rs +++ b/src/ui/theme/default.rs @@ -510,6 +510,10 @@ impl ThemeDef for DefaultTheme { Shadow::NONE } fn repost_fill(dark_mode: bool, post: &NoteRenderData) -> Color32 { + if !post.is_comment_mention { + return Color32::TRANSPARENT + } + let mut hsva: ecolor::HsvaGamma = Self::feed_frame_fill(dark_mode, post).into(); if dark_mode { hsva.v = (hsva.v + 0.05).min(1.0); // lighten diff --git a/src/ui/theme/roundy.rs b/src/ui/theme/roundy.rs index cf5303db..50ff208b 100644 --- a/src/ui/theme/roundy.rs +++ b/src/ui/theme/roundy.rs @@ -495,6 +495,10 @@ impl ThemeDef for RoundyTheme { Shadow::NONE } fn repost_fill(dark_mode: bool, post: &NoteRenderData) -> Color32 { + if !post.is_comment_mention { + return Color32::TRANSPARENT + } + let mut hsva: ecolor::HsvaGamma = Self::feed_frame_fill(dark_mode, post).into(); if dark_mode { hsva.v = (hsva.v + 0.05).min(1.0); // lighten From ba2df1663f7c26c855ac0112d19f1767c0d65dbd Mon Sep 17 00:00:00 2001 From: bu5hm4nn Date: Tue, 21 Mar 2023 23:38:53 -0600 Subject: [PATCH 24/25] Revert renaming "as_reply_to" in outer code where it only has that meaning. It's still called "hide_footer" in `render_note_inner()` because there, there is now more than one reason to hide the footer. --- src/ui/feed/mod.rs | 14 +++++++------- src/ui/feed/note/mod.rs | 8 ++++---- src/ui/feed/post.rs | 2 +- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/ui/feed/mod.rs b/src/ui/feed/mod.rs index 859eec85..2f376dc6 100644 --- a/src/ui/feed/mod.rs +++ b/src/ui/feed/mod.rs @@ -13,7 +13,7 @@ mod post; struct FeedNoteParams { id: Id, indent: usize, - hide_footer: bool, + as_reply_to: bool, threaded: bool, is_first: bool, is_last: bool, @@ -174,7 +174,7 @@ fn render_a_feed( FeedNoteParams { id: *id, indent: 0, - hide_footer: false, + as_reply_to: false, threaded, is_first: Some(id) == first, is_last: Some(id) == last, @@ -195,7 +195,7 @@ fn render_note_maybe_fake( let FeedNoteParams { id, indent, - hide_footer, + as_reply_to, threaded, is_first, is_last, @@ -234,7 +234,7 @@ fn render_note_maybe_fake( FeedNoteParams { id, indent, - hide_footer, + as_reply_to, threaded, is_first, is_last, @@ -251,7 +251,7 @@ fn render_note_maybe_fake( ui.add_space(height); // Yes, and we need to fake render threads to get their approx height too. - if threaded && !hide_footer { + if threaded && !as_reply_to { let replies = Globals::get_replies_sync(event.id); let iter = replies.iter(); let first = replies.first(); @@ -265,7 +265,7 @@ fn render_note_maybe_fake( FeedNoteParams { id: *reply_id, indent: indent + 1, - hide_footer, + as_reply_to, threaded, is_first: Some(reply_id) == first, is_last: Some(reply_id) == last, @@ -282,7 +282,7 @@ fn render_note_maybe_fake( FeedNoteParams { id, indent, - hide_footer, + as_reply_to, threaded, is_first, is_last, diff --git a/src/ui/feed/note/mod.rs b/src/ui/feed/note/mod.rs index 0b1b4b12..9e6cad47 100644 --- a/src/ui/feed/note/mod.rs +++ b/src/ui/feed/note/mod.rs @@ -189,7 +189,7 @@ pub(super) fn render_note( let FeedNoteParams { id, indent, - hide_footer, + as_reply_to, threaded, is_first, is_last, @@ -258,7 +258,7 @@ pub(super) fn render_note( if note_data.author.muted > 0 { ui.label(RichText::new("MUTED POST").monospace().italics()); } else { - render_note_inner(app, ctx, ui, note_data, &render_data, hide_footer, &None); + render_note_inner(app, ctx, ui, note_data, &render_data, as_reply_to, &None); } }); }); @@ -279,7 +279,7 @@ pub(super) fn render_note( app.settings.theme.feed_post_separator_stroke(&render_data), ); - if threaded && !hide_footer { + if threaded && !as_reply_to { let replies = Globals::get_replies_sync(id); let iter = replies.iter(); let first = replies.first(); @@ -293,7 +293,7 @@ pub(super) fn render_note( FeedNoteParams { id: *reply_id, indent: indent + 1, - hide_footer, + as_reply_to, threaded, is_first: Some(reply_id) == first, is_last: Some(reply_id) == last, diff --git a/src/ui/feed/post.rs b/src/ui/feed/post.rs index 2b3e7416..f83dd7bf 100644 --- a/src/ui/feed/post.rs +++ b/src/ui/feed/post.rs @@ -99,7 +99,7 @@ fn real_posting_area(app: &mut GossipUi, ctx: &Context, frame: &mut eframe::Fram FeedNoteParams { id, indent: 0, - hide_footer: true, + as_reply_to: true, threaded: false, is_first: true, is_last: true, From e3b3617c4cd63fcf7976585bbe5478a328781b35 Mon Sep 17 00:00:00 2001 From: bu5hm4nn Date: Tue, 21 Mar 2023 23:44:51 -0600 Subject: [PATCH 25/25] cargo fmt --- src/ui/feed/note/mod.rs | 428 +++++++++++++++++++++++----------------- src/ui/theme/default.rs | 2 +- src/ui/theme/roundy.rs | 4 +- 3 files changed, 247 insertions(+), 187 deletions(-) diff --git a/src/ui/feed/note/mod.rs b/src/ui/feed/note/mod.rs index 9e6cad47..6a217f10 100644 --- a/src/ui/feed/note/mod.rs +++ b/src/ui/feed/note/mod.rs @@ -73,7 +73,12 @@ impl NoteData { let cached_mentions = { let mut cached_mentions = Vec::<(usize, Event)>::new(); for (i, tag) in event.tags.iter().enumerate() { - if let Tag::Event { id, recommended_relay_url:_, marker } = tag { + if let Tag::Event { + id, + recommended_relay_url: _, + marker, + } = tag + { if marker.is_some() && marker.as_deref().unwrap() == "mention" { if let Some(event) = GLOBALS.events.get(id) { cached_mentions.push((i, event)); @@ -258,7 +263,15 @@ pub(super) fn render_note( if note_data.author.muted > 0 { ui.label(RichText::new("MUTED POST").monospace().italics()); } else { - render_note_inner(app, ctx, ui, note_data, &render_data, as_reply_to, &None); + render_note_inner( + app, + ctx, + ui, + note_data, + &render_data, + as_reply_to, + &None, + ); } }); }); @@ -356,7 +369,9 @@ fn render_note_inner( } } else { match parent_repost { - None | Some(RepostType::CommentMention) => (AVATAR_SIZE_F32 - AVATAR_SIZE_REPOST_F32) / 2.0, + None | Some(RepostType::CommentMention) => { + (AVATAR_SIZE_F32 - AVATAR_SIZE_REPOST_F32) / 2.0 + } Some(_) => 0.0, } }; @@ -377,7 +392,7 @@ fn render_note_inner( let content_pull_top = inner_margin.top + ui.style().spacing.item_spacing.y * 4.0 - avatar_size; - let content_margin_left= AVATAR_SIZE_F32 + inner_margin.left; + let content_margin_left = AVATAR_SIZE_F32 + inner_margin.left; let footer_margin_left = content_margin_left; ui.vertical(|ui| { @@ -548,49 +563,58 @@ fn render_note_inner( if !collapsed { let mut append_repost: Option = None; Frame::none() - .inner_margin(Margin { - left: content_margin_left, - bottom: 0.0, - right: 0.0, - top: 0.0, - }) - .outer_margin(Margin { - left: 0.0, - bottom: 0.0, - right: 0.0, - top: content_pull_top, - }) - .show(ui, |ui| { - ui.horizontal_wrapped(|ui| { - if app.render_raw == Some(event.id) { - ui.label(serde_json::to_string_pretty(&event).unwrap()); - } else if app.render_qr == Some(event.id) { - app.render_qr(ui, ctx, "feedqr", display_content.trim()); - // FIXME should this be the unmodified content (event.content)? - } else if event.content_warning().is_some() - && !app.approved.contains(&event.id) - { - ui.label( - RichText::new(format!( - "Content-Warning: {}", - event.content_warning().unwrap() - )) - .monospace() - .italics(), - ); - if ui.button("Show Post").clicked() { - app.approved.insert(event.id); - app.height.remove(&event.id); // will need to be recalculated. - } - } else if event.kind == EventKind::Repost { - if let Ok(inner_event) = serde_json::from_str::(&event.content) { - if let Some(inner_note_data) = NoteData::new(inner_event, false) { - append_repost = Some(inner_note_data); + .inner_margin(Margin { + left: content_margin_left, + bottom: 0.0, + right: 0.0, + top: 0.0, + }) + .outer_margin(Margin { + left: 0.0, + bottom: 0.0, + right: 0.0, + top: content_pull_top, + }) + .show(ui, |ui| { + ui.horizontal_wrapped(|ui| { + if app.render_raw == Some(event.id) { + ui.label(serde_json::to_string_pretty(&event).unwrap()); + } else if app.render_qr == Some(event.id) { + app.render_qr(ui, ctx, "feedqr", display_content.trim()); + // FIXME should this be the unmodified content (event.content)? + } else if event.content_warning().is_some() + && !app.approved.contains(&event.id) + { + ui.label( + RichText::new(format!( + "Content-Warning: {}", + event.content_warning().unwrap() + )) + .monospace() + .italics(), + ); + if ui.button("Show Post").clicked() { + app.approved.insert(event.id); + app.height.remove(&event.id); // will need to be recalculated. + } + } else if event.kind == EventKind::Repost { + if let Ok(inner_event) = serde_json::from_str::(&event.content) { + if let Some(inner_note_data) = NoteData::new(inner_event, false) { + append_repost = Some(inner_note_data); + } else { + ui.label("REPOSTED EVENT IS NOT RELEVANT"); + } } else { - ui.label("REPOSTED EVENT IS NOT RELEVANT"); + // render like a kind-1 event with a mention + append_repost = content::render_content( + app, + ui, + ¬e_data, + deletion.is_some(), + display_content, + ); } } else { - // render like a kind-1 event with a mention append_repost = content::render_content( app, ui, @@ -599,17 +623,8 @@ fn render_note_inner( display_content, ); } - } else { - append_repost = content::render_content( - app, - ui, - ¬e_data, - deletion.is_some(), - display_content, - ); - } + }); }); - }); // render any repost without frame or indent if let Some(repost) = append_repost { @@ -618,151 +633,156 @@ fn render_note_inner( // deleted? if let Some(delete_reason) = &deletion { - Frame::none().inner_margin(Margin{left: footer_margin_left, - bottom: 0.0, - right: 0.0, - top: 8.0,}) + Frame::none() + .inner_margin(Margin { + left: footer_margin_left, + bottom: 0.0, + right: 0.0, + top: 8.0, + }) .show(ui, |ui| { - ui.label(RichText::new(format!("Deletion Reason: {}", delete_reason)).italics()); + ui.label( + RichText::new(format!("Deletion Reason: {}", delete_reason)).italics(), + ); }); } // Footer if !hide_footer { - Frame::none() - .inner_margin(Margin { - left: footer_margin_left, - bottom: 0.0, - right: 0.0, - top: 8.0, - }) - .outer_margin(Margin { - left: 0.0, - bottom: 0.0, - right: 0.0, - top: 0.0, - }) - .show(ui, |ui| { - ui.horizontal_wrapped(|ui| { - if ui - .add(CopyButton {}) - .on_hover_text("Copy Contents") - .clicked() - { - if app.render_raw == Some(event.id) { - ui.output_mut(|o| { - o.copied_text = serde_json::to_string(&event).unwrap() - }); - } else { - ui.output_mut(|o| o.copied_text = display_content.clone()); - } - } - - ui.add_space(24.0); - - // Button to quote note - if ui - .add( - Label::new(RichText::new("»").size(18.0)).sense(Sense::click()), - ) - .on_hover_text("Quote") - .clicked() - { - if !app.draft.ends_with(' ') && !app.draft.is_empty() { - app.draft.push(' '); - } - app.draft - .push_str(&event.id.try_as_bech32_string().unwrap()); - } - - ui.add_space(24.0); - - // Button to reply - if event.kind != EventKind::EncryptedDirectMessage { + Frame::none() + .inner_margin(Margin { + left: footer_margin_left, + bottom: 0.0, + right: 0.0, + top: 8.0, + }) + .outer_margin(Margin { + left: 0.0, + bottom: 0.0, + right: 0.0, + top: 0.0, + }) + .show(ui, |ui| { + ui.horizontal_wrapped(|ui| { if ui - .add( - Label::new(RichText::new("💬").size(18.0)) - .sense(Sense::click()), - ) - .on_hover_text("Reply") + .add(CopyButton {}) + .on_hover_text("Copy Contents") .clicked() { - app.replying_to = Some(event.id); + if app.render_raw == Some(event.id) { + ui.output_mut(|o| { + o.copied_text = serde_json::to_string(&event).unwrap() + }); + } else { + ui.output_mut(|o| o.copied_text = display_content.clone()); + } } ui.add_space(24.0); - } - // Button to render raw - if ui - .add( - Label::new(RichText::new("🥩").size(13.0)) - .sense(Sense::click()), - ) - .on_hover_text("Raw") - .clicked() - { - if app.render_raw != Some(event.id) { - app.render_raw = Some(event.id); - } else { - app.render_raw = None; - } - } - - ui.add_space(24.0); - - // Button to render QR code - if ui - .add( - Label::new(RichText::new("⚃").size(16.0)).sense(Sense::click()), - ) - .on_hover_text("QR Code") - .clicked() - { - if app.render_qr != Some(event.id) { - app.render_qr = Some(event.id); - app.qr_codes.remove("feedqr"); - } else { - app.render_qr = None; - app.qr_codes.remove("feedqr"); - } - } - - ui.add_space(24.0); - - // Buttons to react and reaction counts - if app.settings.reactions { - let default_reaction_icon = match self_already_reacted { - true => "♥", - false => "♡", - }; + // Button to quote note if ui .add( - Label::new(RichText::new(default_reaction_icon).size(20.0)) - .sense(Sense::click()), + Label::new(RichText::new("»").size(18.0)).sense(Sense::click()), ) + .on_hover_text("Quote") .clicked() { - let _ = GLOBALS - .to_overlord - .send(ToOverlordMessage::Like(event.id, event.pubkey)); + if !app.draft.ends_with(' ') && !app.draft.is_empty() { + app.draft.push(' '); + } + app.draft + .push_str(&event.id.try_as_bech32_string().unwrap()); } - for (ch, count) in reactions.iter() { - if *ch == '+' { - ui.label(format!("{}", count)); + + ui.add_space(24.0); + + // Button to reply + if event.kind != EventKind::EncryptedDirectMessage { + if ui + .add( + Label::new(RichText::new("💬").size(18.0)) + .sense(Sense::click()), + ) + .on_hover_text("Reply") + .clicked() + { + app.replying_to = Some(event.id); + } + + ui.add_space(24.0); + } + + // Button to render raw + if ui + .add( + Label::new(RichText::new("🥩").size(13.0)) + .sense(Sense::click()), + ) + .on_hover_text("Raw") + .clicked() + { + if app.render_raw != Some(event.id) { + app.render_raw = Some(event.id); + } else { + app.render_raw = None; } } - ui.add_space(12.0); - for (ch, count) in reactions.iter() { - if *ch != '+' { - ui.label( - RichText::new(format!("{} {}", ch, count)).strong(), - ); + + ui.add_space(24.0); + + // Button to render QR code + if ui + .add( + Label::new(RichText::new("⚃").size(16.0)).sense(Sense::click()), + ) + .on_hover_text("QR Code") + .clicked() + { + if app.render_qr != Some(event.id) { + app.render_qr = Some(event.id); + app.qr_codes.remove("feedqr"); + } else { + app.render_qr = None; + app.qr_codes.remove("feedqr"); } } - } + + ui.add_space(24.0); + + // Buttons to react and reaction counts + if app.settings.reactions { + let default_reaction_icon = match self_already_reacted { + true => "♥", + false => "♡", + }; + if ui + .add( + Label::new(RichText::new(default_reaction_icon).size(20.0)) + .sense(Sense::click()), + ) + .clicked() + { + let _ = GLOBALS + .to_overlord + .send(ToOverlordMessage::Like(event.id, event.pubkey)); + } + for (ch, count) in reactions.iter() { + if *ch == '+' { + ui.label(format!("{}", count)); + } + } + ui.add_space(12.0); + for (ch, count) in reactions.iter() { + if *ch != '+' { + ui.label( + RichText::new(format!("{} {}", ch, count)).strong(), + ); + } + } + } + }); }); - }); } } }); @@ -775,7 +795,13 @@ fn thin_separator(ui: &mut Ui, stroke: Stroke) { ui.reset_style(); } -pub(super) fn render_repost(app: &mut GossipUi, ui: &mut Ui, ctx: &Context, parent_data: &NoteData, repost_data: NoteData) { +pub(super) fn render_repost( + app: &mut GossipUi, + ui: &mut Ui, + ctx: &Context, + parent_data: &NoteData, + repost_data: NoteData, +) { let render_data = NoteRenderData { height: 0.0, has_repost: repost_data.repost.is_some(), @@ -789,9 +815,22 @@ pub(super) fn render_repost(app: &mut GossipUi, ui: &mut Ui, ctx: &Context, pare }; ui.vertical(|ui| { - ui.add_space(app.settings.theme.repost_space_above_separator_before(&render_data)); - thin_separator(ui, app.settings.theme.repost_separator_before_stroke(&render_data)); - ui.add_space(app.settings.theme.repost_space_below_separator_before(&render_data)); + ui.add_space( + app.settings + .theme + .repost_space_above_separator_before(&render_data), + ); + thin_separator( + ui, + app.settings + .theme + .repost_separator_before_stroke(&render_data), + ); + ui.add_space( + app.settings + .theme + .repost_space_below_separator_before(&render_data), + ); Frame::none() .inner_margin(app.settings.theme.repost_inner_margin(&render_data)) .outer_margin(app.settings.theme.repost_outer_margin(&render_data)) @@ -799,14 +838,35 @@ pub(super) fn render_repost(app: &mut GossipUi, ui: &mut Ui, ctx: &Context, pare .shadow(app.settings.theme.repost_shadow(&render_data)) .fill(app.settings.theme.repost_fill(&render_data)) .stroke(app.settings.theme.repost_stroke(&render_data)) - .show(ui, |ui|{ + .show(ui, |ui| { ui.horizontal_wrapped(|ui| { // FIXME: don't do this recursively - render_note_inner(app, ctx, ui, repost_data, &render_data, false,&parent_data.repost); + render_note_inner( + app, + ctx, + ui, + repost_data, + &render_data, + false, + &parent_data.repost, + ); }); }); - ui.add_space(app.settings.theme.repost_space_above_separator_after(&render_data)); - thin_separator(ui, app.settings.theme.repost_separator_after_stroke(&render_data)); - ui.add_space(app.settings.theme.repost_space_below_separator_after(&render_data)); + ui.add_space( + app.settings + .theme + .repost_space_above_separator_after(&render_data), + ); + thin_separator( + ui, + app.settings + .theme + .repost_separator_after_stroke(&render_data), + ); + ui.add_space( + app.settings + .theme + .repost_space_below_separator_after(&render_data), + ); }); } diff --git a/src/ui/theme/default.rs b/src/ui/theme/default.rs index 5c05cc69..a907396e 100644 --- a/src/ui/theme/default.rs +++ b/src/ui/theme/default.rs @@ -511,7 +511,7 @@ impl ThemeDef for DefaultTheme { } fn repost_fill(dark_mode: bool, post: &NoteRenderData) -> Color32 { if !post.is_comment_mention { - return Color32::TRANSPARENT + return Color32::TRANSPARENT; } let mut hsva: ecolor::HsvaGamma = Self::feed_frame_fill(dark_mode, post).into(); diff --git a/src/ui/theme/roundy.rs b/src/ui/theme/roundy.rs index 50ff208b..6df77a01 100644 --- a/src/ui/theme/roundy.rs +++ b/src/ui/theme/roundy.rs @@ -2,7 +2,7 @@ use super::{FeedProperties, NoteRenderData, ThemeDef}; use crate::ui::HighlightType; use eframe::egui::style::{Selection, WidgetVisuals, Widgets}; use eframe::egui::{FontDefinitions, Margin, Style, TextFormat, TextStyle, Visuals}; -use eframe::epaint::{Color32, FontFamily, FontId, Rounding, Shadow, Stroke, ecolor}; +use eframe::epaint::{ecolor, Color32, FontFamily, FontId, Rounding, Shadow, Stroke}; use std::collections::BTreeMap; #[derive(Default)] @@ -496,7 +496,7 @@ impl ThemeDef for RoundyTheme { } fn repost_fill(dark_mode: bool, post: &NoteRenderData) -> Color32 { if !post.is_comment_mention { - return Color32::TRANSPARENT + return Color32::TRANSPARENT; } let mut hsva: ecolor::HsvaGamma = Self::feed_frame_fill(dark_mode, post).into();