From db5e10656dfd8b71c21adadb5c2af7916290b2a2 Mon Sep 17 00:00:00 2001 From: kernelkind Date: Thu, 22 May 2025 20:05:11 -0400 Subject: [PATCH] set variable for scroll offset necessary to maintain scroll positions across popup & Nav Signed-off-by: kernelkind --- crates/notedeck_columns/src/ui/profile/mod.rs | 116 ++++++++++-------- crates/notedeck_columns/src/ui/thread.rs | 102 ++++++++------- crates/notedeck_columns/src/ui/timeline.rs | 10 +- 3 files changed, 127 insertions(+), 101 deletions(-) diff --git a/crates/notedeck_columns/src/ui/profile/mod.rs b/crates/notedeck_columns/src/ui/profile/mod.rs index bde2f412..49a3f8be 100644 --- 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 { 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::(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(); - action - }) - .inner + 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)); + + 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 index f706cc6e..49d6493a 100644 --- 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 { 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, + .scroll_bar_visibility(egui::scroll_area::ScrollBarVisibility::AlwaysVisible); - Err(err) => { - ui.label(format!("Error loading thread: {:?}", err)); - return None; - } - }; + let offset_id = self.id_source.with("scroll_offset"); - 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(); + if let Some(offset) = ui.data(|i| i.get_temp::(offset_id)) { + scroll_area = scroll_area.vertical_scroll_offset(offset); + } - // TODO(jb55): skip poll if ThreadResult is fresh? + 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, - 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}"); + 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 index 74f8e0b8..5930546b 100644 --- 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::(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::(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;