notedeck

One damus client to rule them all
git clone git://jb55.com/notedeck
Log | Files | Refs | README | LICENSE

commit db5e10656dfd8b71c21adadb5c2af7916290b2a2
parent 3cb2dd88b654ad0f064c7828897f4cf1394eab03
Author: kernelkind <kernelkind@gmail.com>
Date:   Thu, 22 May 2025 20:05:11 -0400

set variable for scroll offset

necessary to maintain scroll positions across popup & Nav

Signed-off-by: kernelkind <kernelkind@gmail.com>

Diffstat:
Mcrates/notedeck_columns/src/ui/profile/mod.rs | 116++++++++++++++++++++++++++++++++++++++++++-------------------------------------
Mcrates/notedeck_columns/src/ui/thread.rs | 110+++++++++++++++++++++++++++++++++++++++++++------------------------------------
Mcrates/notedeck_columns/src/ui/timeline.rs | 10+++++++++-
3 files changed, 131 insertions(+), 105 deletions(-)

diff --git a/crates/notedeck_columns/src/ui/profile/mod.rs b/crates/notedeck_columns/src/ui/profile/mod.rs @@ -65,67 +65,75 @@ impl<'a, 'd> ProfileView<'a, 'd> { pub fn ui(&mut self, ui: &mut egui::Ui) -> Option<ProfileViewAction> { let scroll_id = egui::Id::new(("profile_scroll", self.col_id, self.pubkey)); + let offset_id = scroll_id.with("scroll_offset"); - ScrollArea::vertical() - .id_salt(scroll_id) - .show(ui, |ui| { - let mut action = None; - let txn = Transaction::new(self.note_context.ndb).expect("txn"); - if let Ok(profile) = self - .note_context - .ndb - .get_profile_by_pubkey(&txn, self.pubkey.bytes()) - { - if self.profile_body(ui, profile) { - action = Some(ProfileViewAction::EditProfile); - } + let mut scroll_area = ScrollArea::vertical().id_salt(scroll_id); + + if let Some(offset) = ui.data(|i| i.get_temp::<f32>(offset_id)) { + scroll_area = scroll_area.vertical_scroll_offset(offset); + } + + let output = scroll_area.show(ui, |ui| { + let mut action = None; + let txn = Transaction::new(self.note_context.ndb).expect("txn"); + if let Ok(profile) = self + .note_context + .ndb + .get_profile_by_pubkey(&txn, self.pubkey.bytes()) + { + if self.profile_body(ui, profile) { + action = Some(ProfileViewAction::EditProfile); } - let profile_timeline = self - .timeline_cache - .notes( - self.note_context.ndb, - self.note_context.note_cache, - &txn, - &TimelineKind::Profile(*self.pubkey), - ) - .get_ptr(); - - profile_timeline.selected_view = - tabs_ui(ui, profile_timeline.selected_view, &profile_timeline.views); - - let reversed = false; - // poll for new notes and insert them into our existing notes - if let Err(e) = profile_timeline.poll_notes_into_view( + } + let profile_timeline = self + .timeline_cache + .notes( self.note_context.ndb, - &txn, - self.unknown_ids, self.note_context.note_cache, - reversed, - ) { - error!("Profile::poll_notes_into_view: {e}"); - } - - if let Some(note_action) = TimelineTabView::new( - profile_timeline.current_view(), - reversed, - self.note_options, &txn, - self.is_muted, - self.note_context, - &self - .accounts - .get_selected_account() - .map(|a| (&a.key).into()), - self.jobs, + &TimelineKind::Profile(*self.pubkey), ) - .show(ui) - { - action = Some(ProfileViewAction::Note(note_action)); - } + .get_ptr(); + + profile_timeline.selected_view = + tabs_ui(ui, profile_timeline.selected_view, &profile_timeline.views); + + let reversed = false; + // poll for new notes and insert them into our existing notes + if let Err(e) = profile_timeline.poll_notes_into_view( + self.note_context.ndb, + &txn, + self.unknown_ids, + self.note_context.note_cache, + reversed, + ) { + error!("Profile::poll_notes_into_view: {e}"); + } + + if let Some(note_action) = TimelineTabView::new( + profile_timeline.current_view(), + reversed, + self.note_options, + &txn, + self.is_muted, + self.note_context, + &self + .accounts + .get_selected_account() + .map(|a| (&a.key).into()), + self.jobs, + ) + .show(ui) + { + action = Some(ProfileViewAction::Note(note_action)); + } + + action + }); + + ui.data_mut(|d| d.insert_temp(offset_id, output.state.offset.y)); - action - }) - .inner + output.inner } fn profile_body(&mut self, ui: &mut egui::Ui, profile: ProfileRecord<'_>) -> bool { diff --git a/crates/notedeck_columns/src/ui/thread.rs b/crates/notedeck_columns/src/ui/thread.rs @@ -54,62 +54,72 @@ impl<'a, 'd> ThreadView<'a, 'd> { pub fn ui(&mut self, ui: &mut egui::Ui) -> Option<NoteAction> { let txn = Transaction::new(self.note_context.ndb).expect("txn"); - egui::ScrollArea::vertical() + let mut scroll_area = egui::ScrollArea::vertical() .id_salt(self.id_source) .animated(false) .auto_shrink([false, false]) - .scroll_bar_visibility(egui::scroll_area::ScrollBarVisibility::AlwaysVisible) - .show(ui, |ui| { - let root_id = match RootNoteId::new( - self.note_context.ndb, - self.note_context.note_cache, - &txn, - self.selected_note_id, - ) { - Ok(root_id) => root_id, - - Err(err) => { - ui.label(format!("Error loading thread: {:?}", err)); - return None; - } - }; - - let thread_timeline = self - .timeline_cache - .notes( - self.note_context.ndb, - self.note_context.note_cache, - &txn, - &TimelineKind::Thread(ThreadSelection::from_root_id(root_id.to_owned())), - ) - .get_ptr(); - - // TODO(jb55): skip poll if ThreadResult is fresh? - - let reversed = true; - // poll for new notes and insert them into our existing notes - if let Err(err) = thread_timeline.poll_notes_into_view( - self.note_context.ndb, - &txn, - self.unknown_ids, - self.note_context.note_cache, - reversed, - ) { - error!("error polling notes into thread timeline: {err}"); + .scroll_bar_visibility(egui::scroll_area::ScrollBarVisibility::AlwaysVisible); + + let offset_id = self.id_source.with("scroll_offset"); + + if let Some(offset) = ui.data(|i| i.get_temp::<f32>(offset_id)) { + scroll_area = scroll_area.vertical_scroll_offset(offset); + } + + let output = scroll_area.show(ui, |ui| { + let root_id = match RootNoteId::new( + self.note_context.ndb, + self.note_context.note_cache, + &txn, + self.selected_note_id, + ) { + Ok(root_id) => root_id, + + Err(err) => { + ui.label(format!("Error loading thread: {:?}", err)); + return None; } + }; - TimelineTabView::new( - thread_timeline.current_view(), - true, - self.note_options, + let thread_timeline = self + .timeline_cache + .notes( + self.note_context.ndb, + self.note_context.note_cache, &txn, - self.is_muted, - self.note_context, - self.cur_acc, - self.jobs, + &TimelineKind::Thread(ThreadSelection::from_root_id(root_id.to_owned())), ) - .show(ui) - }) - .inner + .get_ptr(); + + // TODO(jb55): skip poll if ThreadResult is fresh? + + let reversed = true; + // poll for new notes and insert them into our existing notes + if let Err(err) = thread_timeline.poll_notes_into_view( + self.note_context.ndb, + &txn, + self.unknown_ids, + self.note_context.note_cache, + reversed, + ) { + error!("error polling notes into thread timeline: {err}"); + } + + TimelineTabView::new( + thread_timeline.current_view(), + true, + self.note_options, + &txn, + self.is_muted, + self.note_context, + self.cur_acc, + self.jobs, + ) + .show(ui) + }); + + ui.data_mut(|d| d.insert_temp(offset_id, output.state.offset.y)); + + output.inner } } diff --git a/crates/notedeck_columns/src/ui/timeline.rs b/crates/notedeck_columns/src/ui/timeline.rs @@ -130,6 +130,12 @@ fn timeline_ui( .auto_shrink([false, false]) .scroll_bar_visibility(ScrollBarVisibility::AlwaysVisible); + let offset_id = scroll_id.with("timeline_scroll_offset"); + + if let Some(offset) = ui.data(|i| i.get_temp::<f32>(offset_id)) { + scroll_area = scroll_area.vertical_scroll_offset(offset); + } + if let Some(goto_top_resp) = goto_top_resp { if goto_top_resp.clicked() { scroll_area = scroll_area.vertical_scroll_offset(0.0); @@ -163,6 +169,8 @@ fn timeline_ui( .show(ui) }); + ui.data_mut(|d| d.insert_temp(offset_id, scroll_output.state.offset.y)); + let at_top_after_scroll = scroll_output.state.offset.y == 0.0; let cur_show_top_button = ui.ctx().data(|d| d.get_temp::<bool>(show_top_button_id)); @@ -362,9 +370,9 @@ impl<'a, 'd> TimelineTabView<'a, 'd> { let len = self.tab.notes.len(); let is_muted = self.is_muted; + self.tab .list - .clone() .borrow_mut() .ui_custom_layout(ui, len, |ui, start_index| { ui.spacing_mut().item_spacing.y = 0.0;