@@ -1,10 +1,15 @@
|
||||
use egui::InnerResponse;
|
||||
use egui_virtual_list::VirtualList;
|
||||
use enostr::KeypairUnowned;
|
||||
use nostrdb::Transaction;
|
||||
use nostrdb::{Note, Transaction};
|
||||
use notedeck::note::root_note_id_from_selected_id;
|
||||
use notedeck::{MuteFun, NoteAction, NoteContext, RootNoteId, UnknownIds};
|
||||
use notedeck_ui::jobs::JobsCache;
|
||||
use notedeck_ui::NoteOptions;
|
||||
use notedeck_ui::note::NoteResponse;
|
||||
use notedeck_ui::{NoteOptions, NoteView};
|
||||
use tracing::error;
|
||||
|
||||
use crate::timeline::thread::NoteSeenFlags;
|
||||
use crate::timeline::{ThreadSelection, TimelineCache, TimelineKind};
|
||||
use crate::ui::timeline::TimelineTabView;
|
||||
|
||||
@@ -60,7 +65,9 @@ impl<'a, 'd> ThreadView<'a, 'd> {
|
||||
.auto_shrink([false, false])
|
||||
.scroll_bar_visibility(egui::scroll_area::ScrollBarVisibility::AlwaysVisible);
|
||||
|
||||
let offset_id = self.id_source.with("scroll_offset");
|
||||
let offset_id = self
|
||||
.id_source
|
||||
.with(("scroll_offset", self.selected_note_id));
|
||||
|
||||
if let Some(offset) = ui.data(|i| i.get_temp::<f32>(offset_id)) {
|
||||
scroll_area = scroll_area.vertical_scroll_offset(offset);
|
||||
@@ -123,3 +130,258 @@ impl<'a, 'd> ThreadView<'a, 'd> {
|
||||
output.inner
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn show_notes(
|
||||
ui: &mut egui::Ui,
|
||||
list: &mut VirtualList,
|
||||
thread_notes: &ThreadNotes,
|
||||
note_context: &mut NoteContext<'_>,
|
||||
zapping_acc: Option<&KeypairUnowned<'_>>,
|
||||
flags: NoteOptions,
|
||||
jobs: &mut JobsCache,
|
||||
txn: &Transaction,
|
||||
is_muted: &MuteFun,
|
||||
) -> Option<NoteAction> {
|
||||
let mut action = None;
|
||||
|
||||
ui.spacing_mut().item_spacing.y = 0.0;
|
||||
ui.spacing_mut().item_spacing.x = 4.0;
|
||||
|
||||
let selected_note_index = thread_notes.selected_index;
|
||||
let notes = &thread_notes.notes;
|
||||
|
||||
list.ui_custom_layout(ui, notes.len(), |ui, cur_index| 's: {
|
||||
let note = ¬es[cur_index];
|
||||
|
||||
// should we mute the thread? we might not have it!
|
||||
let muted = root_note_id_from_selected_id(
|
||||
note_context.ndb,
|
||||
note_context.note_cache,
|
||||
txn,
|
||||
note.note.id(),
|
||||
)
|
||||
.ok()
|
||||
.is_some_and(|root_id| is_muted(¬e.note, root_id.bytes()));
|
||||
|
||||
if muted {
|
||||
break 's 0;
|
||||
}
|
||||
|
||||
let resp = note.show(note_context, zapping_acc, flags, jobs, ui);
|
||||
|
||||
action = if cur_index == selected_note_index {
|
||||
resp.action.and_then(strip_note_action)
|
||||
} else {
|
||||
resp.action
|
||||
}
|
||||
.or(action.take());
|
||||
|
||||
1
|
||||
});
|
||||
|
||||
action
|
||||
}
|
||||
|
||||
fn strip_note_action(action: NoteAction) -> Option<NoteAction> {
|
||||
if matches!(action, NoteAction::Note(_)) {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(action)
|
||||
}
|
||||
|
||||
struct ThreadNoteBuilder<'a> {
|
||||
chain: Vec<Note<'a>>,
|
||||
selected: Note<'a>,
|
||||
replies: Vec<Note<'a>>,
|
||||
}
|
||||
|
||||
impl<'a> ThreadNoteBuilder<'a> {
|
||||
pub fn new(selected: Note<'a>) -> Self {
|
||||
Self {
|
||||
chain: Vec::new(),
|
||||
selected,
|
||||
replies: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_chain(&mut self, note: Note<'a>) {
|
||||
self.chain.push(note);
|
||||
}
|
||||
|
||||
pub fn add_reply(&mut self, note: Note<'a>) {
|
||||
self.replies.push(note);
|
||||
}
|
||||
|
||||
pub fn into_notes(mut self, seen_flags: &mut NoteSeenFlags) -> ThreadNotes<'a> {
|
||||
let mut notes = Vec::new();
|
||||
|
||||
let selected_is_root = self.chain.is_empty();
|
||||
let mut cur_is_root = true;
|
||||
while let Some(note) = self.chain.pop() {
|
||||
notes.push(ThreadNote {
|
||||
unread_and_have_replies: *seen_flags.get(note.id()).unwrap_or(&false),
|
||||
note,
|
||||
note_type: ThreadNoteType::Chain { root: cur_is_root },
|
||||
});
|
||||
cur_is_root = false;
|
||||
}
|
||||
|
||||
let selected_index = notes.len();
|
||||
notes.push(ThreadNote {
|
||||
note: self.selected,
|
||||
note_type: ThreadNoteType::Selected {
|
||||
root: selected_is_root,
|
||||
},
|
||||
unread_and_have_replies: false,
|
||||
});
|
||||
|
||||
for reply in self.replies {
|
||||
notes.push(ThreadNote {
|
||||
unread_and_have_replies: *seen_flags.get(reply.id()).unwrap_or(&false),
|
||||
note: reply,
|
||||
note_type: ThreadNoteType::Reply,
|
||||
});
|
||||
}
|
||||
|
||||
ThreadNotes {
|
||||
notes,
|
||||
selected_index,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum ThreadNoteType {
|
||||
Chain { root: bool },
|
||||
Selected { root: bool },
|
||||
Reply,
|
||||
}
|
||||
|
||||
struct ThreadNotes<'a> {
|
||||
notes: Vec<ThreadNote<'a>>,
|
||||
selected_index: usize,
|
||||
}
|
||||
|
||||
struct ThreadNote<'a> {
|
||||
pub note: Note<'a>,
|
||||
note_type: ThreadNoteType,
|
||||
pub unread_and_have_replies: bool,
|
||||
}
|
||||
|
||||
impl<'a> ThreadNote<'a> {
|
||||
fn options(&self, mut cur_options: NoteOptions) -> NoteOptions {
|
||||
match self.note_type {
|
||||
ThreadNoteType::Chain { root: _ } => cur_options,
|
||||
ThreadNoteType::Selected { root: _ } => {
|
||||
cur_options.set_wide(true);
|
||||
cur_options
|
||||
}
|
||||
ThreadNoteType::Reply => cur_options,
|
||||
}
|
||||
}
|
||||
|
||||
fn show(
|
||||
&self,
|
||||
note_context: &'a mut NoteContext<'_>,
|
||||
zapping_acc: Option<&'a KeypairUnowned<'a>>,
|
||||
flags: NoteOptions,
|
||||
jobs: &'a mut JobsCache,
|
||||
ui: &mut egui::Ui,
|
||||
) -> NoteResponse {
|
||||
let inner = notedeck_ui::padding(8.0, ui, |ui| {
|
||||
NoteView::new(
|
||||
note_context,
|
||||
zapping_acc,
|
||||
&self.note,
|
||||
self.options(flags),
|
||||
jobs,
|
||||
)
|
||||
.unread_indicator(self.unread_and_have_replies)
|
||||
.show(ui)
|
||||
});
|
||||
|
||||
match self.note_type {
|
||||
ThreadNoteType::Chain { root } => add_chain_adornment(ui, &inner, root),
|
||||
ThreadNoteType::Selected { root } => add_selected_adornment(ui, &inner, root),
|
||||
ThreadNoteType::Reply => notedeck_ui::hline(ui),
|
||||
}
|
||||
|
||||
inner.inner
|
||||
}
|
||||
}
|
||||
|
||||
fn add_chain_adornment(ui: &mut egui::Ui, note_resp: &InnerResponse<NoteResponse>, root: bool) {
|
||||
let Some(pfp_rect) = note_resp.inner.pfp_rect else {
|
||||
return;
|
||||
};
|
||||
|
||||
let note_rect = note_resp.response.rect;
|
||||
|
||||
let painter = ui.painter_at(note_rect);
|
||||
|
||||
if !root {
|
||||
paint_line_above_pfp(ui, &painter, &pfp_rect, ¬e_rect);
|
||||
}
|
||||
|
||||
// painting line below pfp:
|
||||
let top_pt = {
|
||||
let mut top = pfp_rect.center();
|
||||
top.y = pfp_rect.bottom();
|
||||
top
|
||||
};
|
||||
|
||||
let bottom_pt = {
|
||||
let mut bottom = top_pt;
|
||||
bottom.y = note_rect.bottom();
|
||||
bottom
|
||||
};
|
||||
|
||||
painter.line_segment([top_pt, bottom_pt], LINE_STROKE(ui));
|
||||
|
||||
let hline_min_x = top_pt.x + 6.0;
|
||||
notedeck_ui::hline_with_width(
|
||||
ui,
|
||||
egui::Rangef::new(hline_min_x, ui.available_rect_before_wrap().right()),
|
||||
);
|
||||
}
|
||||
|
||||
fn add_selected_adornment(ui: &mut egui::Ui, note_resp: &InnerResponse<NoteResponse>, root: bool) {
|
||||
let Some(pfp_rect) = note_resp.inner.pfp_rect else {
|
||||
return;
|
||||
};
|
||||
let note_rect = note_resp.response.rect;
|
||||
let painter = ui.painter_at(note_rect);
|
||||
|
||||
if !root {
|
||||
paint_line_above_pfp(ui, &painter, &pfp_rect, ¬e_rect);
|
||||
}
|
||||
notedeck_ui::hline(ui);
|
||||
}
|
||||
|
||||
fn paint_line_above_pfp(
|
||||
ui: &egui::Ui,
|
||||
painter: &egui::Painter,
|
||||
pfp_rect: &egui::Rect,
|
||||
note_rect: &egui::Rect,
|
||||
) {
|
||||
let bottom_pt = {
|
||||
let mut center = pfp_rect.center();
|
||||
center.y = pfp_rect.top();
|
||||
center
|
||||
};
|
||||
|
||||
let top_pt = {
|
||||
let mut top = bottom_pt;
|
||||
top.y = note_rect.top();
|
||||
top
|
||||
};
|
||||
|
||||
painter.line_segment([bottom_pt, top_pt], LINE_STROKE(ui));
|
||||
}
|
||||
|
||||
const LINE_STROKE: fn(&egui::Ui) -> egui::Stroke = |ui: &egui::Ui| {
|
||||
let mut stroke = ui.style().visuals.widgets.noninteractive.bg_stroke;
|
||||
stroke.width = 2.0;
|
||||
stroke
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user