commit a6856867a992e3ecbffb8d1224405a0382e2cb31
parent 8ef6534981a11faff8448c348e9a3873e0f754a5
Author: William Casarin <jb55@jb55.com>
Date: Tue, 25 Jun 2024 13:20:56 -0500
Merge remote-tracking branch 'pr/107'
Diffstat:
10 files changed, 231 insertions(+), 56 deletions(-)
diff --git a/src/app.rs b/src/app.rs
@@ -930,17 +930,12 @@ fn render_nav(routes: Vec<Route>, timeline_ind: usize, app: &mut Damus, ui: &mut
return;
};
- let note_key = note.key().unwrap();
-
- let poster = app
- .account_manager
- .get_selected_account_index()
- .unwrap_or(0);
-
- let replying_to = note.pubkey();
- ui::PostView::new(&mut app, poster, replying_to)
- .id_source(("post", timeline_ind, note_key))
- .ui(&txn, ui);
+ let id = egui::Id::new(("post", timeline_ind, note.key().unwrap()));
+ egui::ScrollArea::vertical().show(ui, |ui| {
+ ui::PostReplyView::new(&mut app, ¬e)
+ .id_source(id)
+ .show(ui);
+ });
}
});
diff --git a/src/colors.rs b/src/colors.rs
@@ -8,7 +8,7 @@ pub const PINK: Color32 = Color32::from_rgb(0xE4, 0x5A, 0xC9);
pub const GRAY_SECONDARY: Color32 = Color32::from_rgb(0x8A, 0x8A, 0x8A);
const BLACK: Color32 = Color32::from_rgb(0x00, 0x00, 0x00);
const RED_700: Color32 = Color32::from_rgb(0xC7, 0x37, 0x5A);
-const ORANGE_700: Color32 = Color32::from_rgb(0xF6, 0xB1, 0x4A);
+//const ORANGE_700: Color32 = Color32::from_rgb(0xF6, 0xB1, 0x4A);
// BACKGROUNDS
const SEMI_DARKER_BG: Color32 = Color32::from_rgb(0x39, 0x39, 0x39);
@@ -29,7 +29,7 @@ pub struct ColorTheme {
pub extreme_bg_color: Color32,
pub text_color: Color32,
pub err_fg_color: Color32,
- pub warn_fg_color: Color32,
+ //pub warn_fg_color: Color32,
pub hyperlink_color: Color32,
pub selection_color: Color32,
@@ -56,7 +56,7 @@ pub fn desktop_dark_color_theme() -> ColorTheme {
extreme_bg_color: DARK_ISH_BG,
text_color: Color32::WHITE,
err_fg_color: RED_700,
- warn_fg_color: ORANGE_700,
+ //warn_fg_color: ORANGE_700,
hyperlink_color: PURPLE,
selection_color: PURPLE_ALT,
@@ -92,7 +92,7 @@ pub fn light_color_theme() -> ColorTheme {
extreme_bg_color: LIGHTER_GRAY,
text_color: BLACK,
err_fg_color: RED_700,
- warn_fg_color: ORANGE_700,
+ //warn_fg_color: ORANGE_700,
hyperlink_color: PURPLE,
selection_color: PURPLE_ALT,
diff --git a/src/timeline.rs b/src/timeline.rs
@@ -225,11 +225,7 @@ fn tabs_ui(timeline: &mut Timeline, ui: &mut egui::Ui) {
//ui.add_space(0.5);
ui::hline(ui);
- let sel = if let Some(sel) = tab_res.selected() {
- sel
- } else {
- 0
- };
+ let sel = tab_res.selected().unwrap_or_default();
// fun animation
timeline.selected_view = sel;
diff --git a/src/ui/account_login_view.rs b/src/ui/account_login_view.rs
@@ -30,7 +30,7 @@ impl<'a> View for AccountLoginView<'a> {
if self.is_mobile {
self.show_mobile(ui);
} else {
- self.show(ui);
+ self.ui(ui);
}
}
}
@@ -44,7 +44,7 @@ impl<'a> AccountLoginView<'a> {
}
}
- fn show(&mut self, ui: &mut egui::Ui) -> egui::Response {
+ fn ui(&mut self, ui: &mut egui::Ui) -> egui::Response {
let screen_width = ui.ctx().screen_rect().max.x;
let screen_height = ui.ctx().screen_rect().max.y;
diff --git a/src/ui/mod.rs b/src/ui/mod.rs
@@ -17,7 +17,7 @@ pub use account_switcher::AccountSelectionWidget;
pub use fixed_window::{FixedWindow, FixedWindowResponse};
pub use global_popup::DesktopGlobalPopup;
pub use mention::Mention;
-pub use note::{BarAction, Note, NoteResponse, PostView};
+pub use note::{BarAction, Note, NoteResponse, PostReplyView, PostView};
pub use preview::{Preview, PreviewApp, PreviewConfig};
pub use profile::{profile_preview_controller, ProfilePic, ProfilePreview};
pub use relay::RelayView;
diff --git a/src/ui/note/mod.rs b/src/ui/note/mod.rs
@@ -1,10 +1,12 @@
pub mod contents;
pub mod options;
pub mod post;
+pub mod reply;
pub use contents::NoteContents;
pub use options::NoteOptions;
pub use post::PostView;
+pub use reply::PostReplyView;
use crate::{colors, notecache::CachedNote, ui, ui::View, Damus};
use egui::{Label, RichText, Sense};
@@ -128,6 +130,11 @@ impl<'a> Note<'a> {
self
}
+ pub fn medium_pfp(mut self, enable: bool) -> Self {
+ self.options_mut().set_medium_pfp(enable);
+ self
+ }
+
pub fn note_previews(mut self, enable: bool) -> Self {
self.options_mut().set_note_previews(enable);
self
@@ -179,6 +186,10 @@ impl<'a> Note<'a> {
.response
}
+ pub fn expand_size() -> f32 {
+ 5.0
+ }
+
fn pfp(
&mut self,
note_key: NoteKey,
@@ -188,7 +199,9 @@ impl<'a> Note<'a> {
ui.spacing_mut().item_spacing.x = 16.0;
let pfp_size = if self.options().has_small_pfp() {
- 24.0
+ ui::ProfilePic::small_size()
+ } else if self.options().has_medium_pfp() {
+ ui::ProfilePic::medium_size()
} else {
ui::ProfilePic::default_size()
};
@@ -201,7 +214,6 @@ impl<'a> Note<'a> {
// these have different lifetimes and types,
// so the calls must be separate
Some(pic) => {
- let expand_size = 5.0;
let anim_speed = 0.05;
let profile_key = profile.as_ref().unwrap().record().note_key();
let note_key = note_key.as_u64();
@@ -213,7 +225,7 @@ impl<'a> Note<'a> {
ui,
egui::Id::new((profile_key, note_key)),
pfp_size,
- expand_size,
+ ui::Note::expand_size(),
anim_speed,
);
diff --git a/src/ui/note/options.rs b/src/ui/note/options.rs
@@ -8,7 +8,8 @@ bitflags! {
const actionbar = 0b00000001;
const note_previews = 0b00000010;
const small_pfp = 0b00000100;
- const wide = 0b00001000;
+ const medium_pfp = 0b00001000;
+ const wide = 0b00010000;
}
}
@@ -29,6 +30,11 @@ impl NoteOptions {
}
#[inline]
+ pub fn has_medium_pfp(self) -> bool {
+ (self & NoteOptions::medium_pfp) == NoteOptions::medium_pfp
+ }
+
+ #[inline]
pub fn has_wide(self) -> bool {
(self & NoteOptions::wide) == NoteOptions::wide
}
@@ -41,6 +47,16 @@ impl NoteOptions {
*self &= !NoteOptions::small_pfp;
}
}
+
+ #[inline]
+ pub fn set_medium_pfp(&mut self, enable: bool) {
+ if enable {
+ *self |= NoteOptions::medium_pfp;
+ } else {
+ *self &= !NoteOptions::medium_pfp;
+ }
+ }
+
#[inline]
pub fn set_note_previews(&mut self, enable: bool) {
if enable {
diff --git a/src/ui/note/post.rs b/src/ui/note/post.rs
@@ -1,9 +1,9 @@
use crate::app::Damus;
+use crate::draft::Draft;
use crate::ui;
use crate::ui::{Preview, PreviewConfig, View};
use egui::widgets::text_edit::TextEdit;
use nostrdb::Transaction;
-use tracing::info;
pub struct PostView<'app, 'p> {
app: &'app mut Damus,
@@ -13,6 +13,20 @@ pub struct PostView<'app, 'p> {
replying_to: &'p [u8; 32],
}
+pub struct NewPost {
+ pub content: String,
+ pub account: usize,
+}
+
+pub enum PostAction {
+ Post(NewPost),
+}
+
+pub struct PostResponse {
+ pub action: Option<PostAction>,
+ pub edit_response: egui::Response,
+}
+
impl<'app, 'p> PostView<'app, 'p> {
pub fn new(app: &'app mut Damus, poster: usize, replying_to: &'p [u8; 32]) -> Self {
let id_source: Option<egui::Id> = None;
@@ -29,7 +43,14 @@ impl<'app, 'p> PostView<'app, 'p> {
self
}
- fn editbox(&mut self, txn: &nostrdb::Transaction, ui: &mut egui::Ui) {
+ fn draft(&mut self) -> &mut Draft {
+ self.app
+ .drafts
+ .entry(enostr::NoteId::new(*self.replying_to))
+ .or_default()
+ }
+
+ fn editbox(&mut self, txn: &nostrdb::Transaction, ui: &mut egui::Ui) -> egui::Response {
ui.spacing_mut().item_spacing.x = 12.0;
let pfp_size = 24.0;
@@ -61,17 +82,13 @@ impl<'app, 'p> PostView<'app, 'p> {
);
}
- let draft = self
- .app
- .drafts
- .entry(enostr::NoteId::new(*self.replying_to))
- .or_default();
+ let response = ui.add(TextEdit::multiline(&mut self.draft().buffer).frame(false));
- let focused = ui
- .add(TextEdit::multiline(&mut draft.buffer).frame(false))
- .has_focus();
+ let focused = response.has_focus();
ui.ctx().data_mut(|d| d.insert_temp(self.id(), focused));
+
+ response
}
fn focused(&self, ui: &egui::Ui) -> bool {
@@ -83,7 +100,15 @@ impl<'app, 'p> PostView<'app, 'p> {
self.id_source.unwrap_or_else(|| egui::Id::new("post"))
}
- pub fn ui(&mut self, txn: &nostrdb::Transaction, ui: &mut egui::Ui) {
+ pub fn outer_margin() -> f32 {
+ 16.0
+ }
+
+ pub fn inner_margin() -> f32 {
+ 12.0
+ }
+
+ pub fn ui(&mut self, txn: &nostrdb::Transaction, ui: &mut egui::Ui) -> PostResponse {
let focused = self.focused(ui);
let stroke = if focused {
ui.visuals().selection.stroke
@@ -93,8 +118,8 @@ impl<'app, 'p> PostView<'app, 'p> {
};
let mut frame = egui::Frame::default()
- .inner_margin(egui::Margin::same(12.0))
- .outer_margin(egui::Margin::same(12.0))
+ .inner_margin(egui::Margin::same(PostView::inner_margin()))
+ .outer_margin(egui::Margin::same(PostView::outer_margin()))
.fill(ui.visuals().extreme_bg_color)
.stroke(stroke)
.rounding(12.0);
@@ -108,22 +133,35 @@ impl<'app, 'p> PostView<'app, 'p> {
});
}
- frame.show(ui, |ui| {
- ui.vertical(|ui| {
- ui.horizontal(|ui| {
- self.editbox(txn, ui);
- });
-
- ui.with_layout(egui::Layout::right_to_left(egui::Align::TOP), |ui| {
- if ui
- .add_sized([91.0, 32.0], egui::Button::new("Post now"))
- .clicked()
- {
- info!("Post clicked");
+ frame
+ .show(ui, |ui| {
+ ui.vertical(|ui| {
+ let edit_response = ui.horizontal(|ui| self.editbox(txn, ui)).inner;
+
+ let action = ui
+ .with_layout(egui::Layout::right_to_left(egui::Align::TOP), |ui| {
+ if ui
+ .add_sized([91.0, 32.0], egui::Button::new("Post now"))
+ .clicked()
+ {
+ Some(PostAction::Post(NewPost {
+ content: self.draft().buffer.clone(),
+ account: self.poster,
+ }))
+ } else {
+ None
+ }
+ })
+ .inner;
+
+ PostResponse {
+ action,
+ edit_response,
}
- });
- });
- });
+ })
+ .inner
+ })
+ .inner
}
}
diff --git a/src/ui/note/reply.rs b/src/ui/note/reply.rs
@@ -1 +1,106 @@
-struct PostReplyView {}
+use crate::{ui, Damus};
+
+pub struct PostReplyView<'a> {
+ app: &'a mut Damus,
+ id_source: Option<egui::Id>,
+ note: &'a nostrdb::Note<'a>,
+}
+
+impl<'a> PostReplyView<'a> {
+ pub fn new(app: &'a mut Damus, note: &'a nostrdb::Note<'a>) -> Self {
+ let id_source: Option<egui::Id> = None;
+ PostReplyView {
+ app,
+ id_source,
+ note,
+ }
+ }
+
+ pub fn id_source(mut self, id: egui::Id) -> Self {
+ self.id_source = Some(id);
+ self
+ }
+
+ pub fn id(&self) -> egui::Id {
+ self.id_source
+ .unwrap_or_else(|| egui::Id::new("post-reply-view"))
+ }
+
+ pub fn show(&mut self, ui: &mut egui::Ui) {
+ ui.vertical(|ui| {
+ let avail_rect = ui.available_rect_before_wrap();
+
+ // This is the offset of the post view's pfp. We use this
+ // to indent things so that the reply line is aligned
+ let pfp_offset = ui::PostView::outer_margin()
+ + ui::PostView::inner_margin()
+ + ui::ProfilePic::small_size() / 2.0;
+
+ let note_offset =
+ pfp_offset - ui::ProfilePic::medium_size() / 2.0 - ui::Note::expand_size() / 2.0;
+
+ egui::Frame::none()
+ .outer_margin(egui::Margin::same(note_offset))
+ .show(ui, |ui| {
+ ui::Note::new(self.app, self.note)
+ .actionbar(false)
+ .medium_pfp(true)
+ .show(ui);
+ });
+
+ let poster = self
+ .app
+ .account_manager
+ .get_selected_account_index()
+ .unwrap_or(0);
+
+ let replying_to = self.note.pubkey();
+ let rect_before_post = ui.min_rect();
+
+ let id = self.id();
+ let post_response = ui::PostView::new(self.app, poster, replying_to)
+ .id_source(id)
+ .ui(self.note.txn().unwrap(), ui);
+
+ //
+ // reply line
+ //
+
+ // Position and draw the reply line
+ let mut rect = ui.min_rect();
+
+ // Position the line right above the poster's profile pic in
+ // the post box. Use the PostView's margin values to
+ // determine this offset.
+ rect.min.x = avail_rect.min.x + pfp_offset;
+
+ // honestly don't know what the fuck I'm doing here. just trying
+ // to get the line under the profile picture
+ rect.min.y = avail_rect.min.y
+ + (ui::ProfilePic::medium_size() / 2.0
+ + ui::ProfilePic::medium_size()
+ + ui::Note::expand_size() * 2.0)
+ + 1.0;
+
+ // For some reason we need to nudge the reply line's height a
+ // few more pixels?
+ let nudge = if post_response.edit_response.has_focus() {
+ // we nudge by one less pixel if focused, otherwise it
+ // overlaps the focused PostView purple border color
+ 2.0
+ } else {
+ // we have to nudge by one more pixel when not focused
+ // otherwise it looks like there's a gap(?)
+ 3.0
+ };
+
+ rect.max.y = rect_before_post.max.y + ui::PostView::outer_margin() + nudge;
+
+ ui.painter().vline(
+ rect.left(),
+ rect.y_range(),
+ ui.visuals().widgets.noninteractive.bg_stroke,
+ );
+ });
+ }
+}
diff --git a/src/ui/profile/picture.rs b/src/ui/profile/picture.rs
@@ -31,14 +31,27 @@ impl<'cache, 'url> ProfilePic<'cache, 'url> {
.map(|url| ProfilePic::new(cache, url))
}
+ #[inline]
pub fn default_size() -> f32 {
38.0
}
+ #[inline]
+ pub fn medium_size() -> f32 {
+ 32.0
+ }
+
+ #[inline]
+ pub fn small_size() -> f32 {
+ 24.0
+ }
+
+ #[inline]
pub fn no_pfp_url() -> &'static str {
"https://damus.io/img/no-profile.svg"
}
+ #[inline]
pub fn size(mut self, size: f32) -> Self {
self.size = size;
self