commit 438dbb23977784ae78dafd15b68e2c36e01f6b5b parent 7a83483758f148d472cf2c769ff577429c8e97c2 Author: William Casarin <jb55@jb55.com> Date: Thu, 24 Jul 2025 15:25:53 -0700 Merge drag to nav back on all views by kernel #1035 HE FUCKING DID IT LADS Made a small tweak on the merge commit to update url to damus-io/egui-nav upstream William Casarin (2): Merge drag to nav back on all views by kernel #1035 kernelkind (9): TMP: update egui-nav refactor scrolling for post, reply & quote views enforce scroll_id for `ThreadView` add `scroll_id` for all views with vertical scroll add `DragSwitch` use `DragSwitch` in `Column` get scroll id for `Route` add `route_uses_frame` use `DragSwitch` to allow dragging anywhere in navigation Diffstat:
19 files changed, 402 insertions(+), 136 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock @@ -1526,7 +1526,7 @@ dependencies = [ [[package]] name = "egui_nav" version = "0.2.0" -source = "git+https://github.com/damus-io/egui-nav?rev=111de8ac40b5d18df53e9691eb18a50d49cb31d8#111de8ac40b5d18df53e9691eb18a50d49cb31d8" +source = "git+https://github.com/kernelkind/egui-nav?rev=3c67eb6298edbff36d46546897cfac33df4f04db#3c67eb6298edbff36d46546897cfac33df4f04db" dependencies = [ "egui", "egui_extras", diff --git a/Cargo.toml b/Cargo.toml @@ -24,7 +24,7 @@ egui = { version = "0.31.1", features = ["serde"] } egui-wgpu = "0.31.1" egui_extras = { version = "0.31.1", features = ["all_loaders"] } egui-winit = { version = "0.31.1", features = ["android-game-activity", "clipboard"] } -egui_nav = { git = "https://github.com/damus-io/egui-nav", rev = "111de8ac40b5d18df53e9691eb18a50d49cb31d8" } +egui_nav = { git = "https://github.com/damus-io/egui-nav", rev = "3c67eb6298edbff36d46546897cfac33df4f04db" } egui_tabs = { git = "https://github.com/damus-io/egui-tabs", rev = "6eb91740577b374a8a6658c09c9a4181299734d0" } #egui_virtual_list = "0.6.0" egui_virtual_list = { git = "https://github.com/jb55/hello_egui", rev = "a66b6794f5e707a2f4109633770e02b02fb722e1" } diff --git a/crates/notedeck_columns/src/column.rs b/crates/notedeck_columns/src/column.rs @@ -1,5 +1,6 @@ use crate::{ actionbar::TimelineOpenResult, + drag::DragSwitch, route::{Route, Router, SingletonRouter}, timeline::{Timeline, TimelineCache, TimelineKind}, }; @@ -13,6 +14,7 @@ use tracing::warn; pub struct Column { pub router: Router<Route>, pub sheet_router: SingletonRouter<Route>, + pub drag: DragSwitch, } impl Column { @@ -21,6 +23,7 @@ impl Column { Column { router, sheet_router: SingletonRouter::default(), + drag: DragSwitch::default(), } } diff --git a/crates/notedeck_columns/src/drag.rs b/crates/notedeck_columns/src/drag.rs @@ -0,0 +1,103 @@ +#[derive(Default, Clone, Debug)] +pub struct DragSwitch { + state: Option<DragState>, +} + +#[derive(Clone, Debug)] +struct DragState { + start_pos: egui::Pos2, + cur_direction: DragDirection, +} + +impl DragSwitch { + /// should call BEFORE both drag directions get rendered + pub fn update(&mut self, horizontal: egui::Id, vertical: egui::Id, ctx: &egui::Context) { + let horiz_being_dragged = ctx.is_being_dragged(horizontal); + let vert_being_dragged = ctx.is_being_dragged(vertical); + + if !horiz_being_dragged && !vert_being_dragged { + self.state = None; + return; + } + + let Some(state) = &mut self.state else { + return; + }; + + let Some(cur_pos) = ctx.pointer_interact_pos() else { + return; + }; + + let dx = (state.start_pos.x - cur_pos.x).abs(); + let dy = (state.start_pos.y - cur_pos.y).abs(); + + let new_direction = if dx > dy { + DragDirection::Horizontal + } else { + DragDirection::Vertical + }; + + if new_direction == DragDirection::Horizontal + && state.cur_direction == DragDirection::Vertical + { + // drag is occuring mostly in the horizontal direction + ctx.set_dragged_id(horizontal); + let new_dir = DragDirection::Horizontal; + state.cur_direction = new_dir; + } else if new_direction == DragDirection::Vertical + && state.cur_direction == DragDirection::Horizontal + { + // drag is occuring mostly in the vertical direction + let new_dir = DragDirection::Vertical; + state.cur_direction = new_dir; + ctx.set_dragged_id(vertical); + } + } + + /// should call AFTER both drag directions rendered + pub fn check_for_drag_start( + &mut self, + ctx: &egui::Context, + horizontal: egui::Id, + vertical: egui::Id, + ) { + let Some(drag_id) = ctx.drag_started_id() else { + return; + }; + + let cur_direction = if drag_id == horizontal { + DragDirection::Horizontal + } else if drag_id == vertical { + DragDirection::Vertical + } else { + return; + }; + + let Some(cur_pos) = ctx.pointer_interact_pos() else { + return; + }; + + self.state = Some(DragState { + start_pos: cur_pos, + cur_direction, + }); + } +} + +#[derive(Debug, PartialEq, Clone)] +enum DragDirection { + Horizontal, + Vertical, +} + +pub fn get_drag_id(ui: &egui::Ui, scroll_id: egui::Id) -> egui::Id { + ui.id().with(egui::Id::new(scroll_id)).with("area") +} + +// unfortunately a Frame makes a new id for the Ui +pub fn get_drag_id_through_frame(ui: &egui::Ui, scroll_id: egui::Id) -> egui::Id { + ui.id() + .with(egui::Id::new("child")) + .with(egui::Id::new(scroll_id)) + .with("area") +} diff --git a/crates/notedeck_columns/src/lib.rs b/crates/notedeck_columns/src/lib.rs @@ -12,6 +12,7 @@ pub mod column; mod deck_state; mod decks; mod draft; +mod drag; mod key_parsing; pub mod login_manager; mod media_upload; diff --git a/crates/notedeck_columns/src/nav.rs b/crates/notedeck_columns/src/nav.rs @@ -4,26 +4,28 @@ use crate::{ column::ColumnsAction, deck_state::DeckState, decks::{Deck, DecksAction, DecksCache}, + drag::{get_drag_id, get_drag_id_through_frame}, options::AppOptions, profile::{ProfileAction, SaveProfileChanges}, route::{Route, Router, SingletonRouter}, timeline::{ route::{render_thread_route, render_timeline_route}, - TimelineCache, + TimelineCache, TimelineKind, }, ui::{ self, - add_column::render_add_column_routes, + add_column::{render_add_column_routes, AddColumnView}, column::NavTitle, configure_deck::ConfigureDeckView, edit_deck::{EditDeckResponse, EditDeckView}, - note::{custom_zap::CustomZapView, NewPostAction, PostAction, PostType}, + note::{custom_zap::CustomZapView, NewPostAction, PostAction, PostType, QuoteRepostView}, profile::EditProfileView, search::{FocusState, SearchView}, settings::{SettingsAction, ShowNoteClientOptions}, support::SupportView, wallet::{get_default_zap_state, WalletAction, WalletState, WalletView}, - RelayView, SettingsView, + AccountsView, PostReplyView, PostView, ProfileView, RelayView, SettingsView, ThreadView, + TimelineView, }, Damus, }; @@ -631,27 +633,22 @@ fn render_nav_body( return None; }; - let id = egui::Id::new(("post", col, note.key().unwrap())); let poster = ctx.accounts.selected_filled()?; let action = { let draft = app.drafts.reply_mut(note.id()); - let response = egui::ScrollArea::vertical() - .show(ui, |ui| { - ui::PostReplyView::new( - &mut note_context, - poster, - draft, - ¬e, - inner_rect, - app.note_options, - &mut app.jobs, - ) - .id_source(id) - .show(ui) - }) - .inner; + let response = ui::PostReplyView::new( + &mut note_context, + poster, + draft, + ¬e, + inner_rect, + app.note_options, + &mut app.jobs, + col, + ) + .show(ui); response.action }; @@ -672,26 +669,20 @@ fn render_nav_body( return None; }; - let id = egui::Id::new(("post", col, note.key().unwrap())); - let poster = ctx.accounts.selected_filled()?; let draft = app.drafts.quote_mut(note.id()); - let response = egui::ScrollArea::vertical() - .show(ui, |ui| { - crate::ui::note::QuoteRepostView::new( - &mut note_context, - poster, - draft, - ¬e, - inner_rect, - app.note_options, - &mut app.jobs, - ) - .id_source(id) - .show(ui) - }) - .inner; + let response = crate::ui::note::QuoteRepostView::new( + &mut note_context, + poster, + draft, + ¬e, + inner_rect, + app.note_options, + &mut app.jobs, + col, + ) + .show(ui); response.action.map(Into::into) } @@ -964,47 +955,165 @@ pub fn render_nav( } }; - let nav_response = Nav::new( - &app.columns(ctx.accounts) - .column(col) - .router() - .routes() - .clone(), - ) - .navigating( - app.columns_mut(ctx.i18n, ctx.accounts) - .column_mut(col) - .router_mut() - .navigating, - ) - .returning( - app.columns_mut(ctx.i18n, ctx.accounts) - .column_mut(col) - .router_mut() - .returning, - ) - .id_source(egui::Id::new(("nav", col))) - .show_mut(ui, |ui, render_type, nav| match render_type { - NavUiType::Title => NavTitle::new( - ctx.ndb, - ctx.img_cache, - get_active_columns_mut(ctx.i18n, ctx.accounts, &mut app.decks_cache), - nav.routes(), + let routes = app + .columns(ctx.accounts) + .column(col) + .router() + .routes() + .clone(); + let nav = Nav::new(&routes).id_source(egui::Id::new(("nav", col))); + + let drag_ids = 's: { + let Some(top_route) = &routes.last().cloned() else { + break 's None; + }; + + let Some(scroll_id) = get_scroll_id( + top_route, + app.columns(ctx.accounts) + .column(col) + .router() + .routes() + .len(), + &app.timeline_cache, col, - ctx.i18n, + ) else { + break 's None; + }; + + let vertical_drag_id = if route_uses_frame(top_route) { + get_drag_id_through_frame(ui, scroll_id) + } else { + get_drag_id(ui, scroll_id) + }; + + let horizontal_drag_id = nav.drag_id(ui); + + let drag = &mut get_active_columns_mut(ctx.i18n, ctx.accounts, &mut app.decks_cache) + .column_mut(col) + .drag; + + drag.update(horizontal_drag_id, vertical_drag_id, ui.ctx()); + + Some((horizontal_drag_id, vertical_drag_id)) + }; + + let nav_response = nav + .navigating( + app.columns_mut(ctx.i18n, ctx.accounts) + .column_mut(col) + .router_mut() + .navigating, + ) + .returning( + app.columns_mut(ctx.i18n, ctx.accounts) + .column_mut(col) + .router_mut() + .returning, ) - .show_move_button(!narrow) - .show_delete_button(!narrow) - .show(ui), + .show_mut(ui, |ui, render_type, nav| match render_type { + NavUiType::Title => NavTitle::new( + ctx.ndb, + ctx.img_cache, + get_active_columns_mut(ctx.i18n, ctx.accounts, &mut app.decks_cache), + nav.routes(), + col, + ctx.i18n, + ) + .show_move_button(!narrow) + .show_delete_button(!narrow) + .show(ui), - NavUiType::Body => { - if let Some(top) = nav.routes().last() { - render_nav_body(ui, app, ctx, top, nav.routes().len(), col, inner_rect) - } else { - None + NavUiType::Body => { + if let Some(top) = nav.routes().last() { + render_nav_body(ui, app, ctx, top, nav.routes().len(), col, inner_rect) + } else { + None + } } - } - }); + }); + + if let Some((horizontal_drag_id, vertical_drag_id)) = drag_ids { + let drag = &mut get_active_columns_mut(ctx.i18n, ctx.accounts, &mut app.decks_cache) + .column_mut(col) + .drag; + drag.check_for_drag_start(ui.ctx(), horizontal_drag_id, vertical_drag_id); + } RenderNavResponse::new(col, NotedeckNavResponse::Nav(Box::new(nav_response))) } + +fn get_scroll_id( + top: &Route, + depth: usize, + timeline_cache: &TimelineCache, + col: usize, +) -> Option<egui::Id> { + match top { + Route::Timeline(timeline_kind) => match timeline_kind { + TimelineKind::List(_) + | TimelineKind::Search(_) + | TimelineKind::Algo(_) + | TimelineKind::Notifications(_) + | TimelineKind::Universe + | TimelineKind::Hashtag(_) + | TimelineKind::Generic(_) => { + TimelineView::scroll_id(timeline_cache, timeline_kind, col) + } + TimelineKind::Profile(pubkey) => { + if depth > 1 { + Some(ProfileView::scroll_id(col, pubkey)) + } else { + TimelineView::scroll_id(timeline_cache, timeline_kind, col) + } + } + }, + Route::Thread(thread_selection) => Some(ThreadView::scroll_id( + thread_selection.selected_or_root(), + col, + )), + Route::Accounts(accounts_route) => match accounts_route { + crate::accounts::AccountsRoute::Accounts => Some(AccountsView::scroll_id()), + crate::accounts::AccountsRoute::AddAccount => None, + }, + Route::Reply(note_id) => Some(PostReplyView::scroll_id(col, note_id.bytes())), + Route::Quote(note_id) => Some(QuoteRepostView::scroll_id(col, note_id.bytes())), + Route::Relays => Some(RelayView::scroll_id()), + Route::ComposeNote => Some(PostView::scroll_id()), + Route::AddColumn(add_column_route) => Some(AddColumnView::scroll_id(add_column_route)), + Route::EditProfile(_) => Some(EditProfileView::scroll_id()), + Route::Support => None, + Route::NewDeck => Some(ConfigureDeckView::scroll_id()), + Route::Search => Some(SearchView::scroll_id()), + Route::EditDeck(_) => None, + Route::Wallet(_) => None, + Route::CustomizeZapAmount(_) => None, + Route::Settings => None, + } +} + +/// Does the corresponding View for the route use a egui::Frame to wrap the ScrollArea? +/// TODO(kernelkind): this is quite hacky... +fn route_uses_frame(route: &Route) -> bool { + match route { + Route::Accounts(accounts_route) => match accounts_route { + crate::accounts::AccountsRoute::Accounts => true, + crate::accounts::AccountsRoute::AddAccount => false, + }, + Route::Relays => true, + Route::Timeline(_) => false, + Route::Thread(_) => false, + Route::Reply(_) => false, + Route::Quote(_) => false, + Route::Settings => false, + Route::ComposeNote => false, + Route::AddColumn(_) => false, + Route::EditProfile(_) => false, + Route::Support => false, + Route::NewDeck => false, + Route::Search => false, + Route::EditDeck(_) => false, + Route::Wallet(_) => false, + Route::CustomizeZapAmount(_) => false, + } +} diff --git a/crates/notedeck_columns/src/timeline/route.rs b/crates/notedeck_columns/src/timeline/route.rs @@ -87,8 +87,8 @@ pub fn render_thread_route( note_options, note_context, jobs, + col, ) - .id_source(col) .ui(ui) .map(Into::into) } diff --git a/crates/notedeck_columns/src/ui/accounts.rs b/crates/notedeck_columns/src/ui/accounts.rs @@ -52,6 +52,7 @@ impl<'a> AccountsView<'a> { ui.add_space(8.0); scroll_area() + .id_salt(AccountsView::scroll_id()) .show(ui, |ui| { Self::show_accounts(ui, self.accounts, self.ndb, self.img_cache, self.i18n) }) @@ -59,6 +60,10 @@ impl<'a> AccountsView<'a> { }) } + pub fn scroll_id() -> egui::Id { + egui::Id::new("accounts") + } + fn show_accounts( ui: &mut Ui, accounts: &Accounts, diff --git a/crates/notedeck_columns/src/ui/add_column.rs b/crates/notedeck_columns/src/ui/add_column.rs @@ -64,14 +64,14 @@ enum AddColumnOption { Individual(PubkeySource), } -#[derive(Clone, Copy, Eq, PartialEq, Debug, Default)] +#[derive(Clone, Copy, Eq, PartialEq, Debug, Default, Hash)] pub enum AddAlgoRoute { #[default] Base, LastPerPubkey, } -#[derive(Clone, Copy, Eq, PartialEq, Debug)] +#[derive(Clone, Copy, Eq, PartialEq, Debug, Hash)] pub enum AddColumnRoute { Base, UndecidedNotification, @@ -187,8 +187,13 @@ impl<'a> AddColumnView<'a> { } } + pub fn scroll_id(route: &AddColumnRoute) -> egui::Id { + egui::Id::new(("add_column", route)) + } + pub fn ui(&mut self, ui: &mut Ui) -> Option<AddColumnResponse> { ScrollArea::vertical() + .id_salt(AddColumnView::scroll_id(&AddColumnRoute::Base)) .show(ui, |ui| { let mut selected_option: Option<AddColumnResponse> = None; for column_option_data in self.get_base_options(ui) { diff --git a/crates/notedeck_columns/src/ui/configure_deck.rs b/crates/notedeck_columns/src/ui/configure_deck.rs @@ -33,6 +33,10 @@ impl<'a> ConfigureDeckView<'a> { self } + pub fn scroll_id() -> egui::Id { + egui::Id::new("configure-deck") + } + pub fn ui(&mut self, ui: &mut Ui) -> Option<ConfigureDeckResponse> { let title_font = egui::FontId::new( notedeck::fonts::get_font_size(ui.ctx(), &NotedeckTextStyle::Heading4), @@ -261,6 +265,7 @@ fn glyph_options_ui( ) -> Option<char> { let mut selected_glyph = None; egui::ScrollArea::vertical() + .id_salt(ConfigureDeckView::scroll_id()) .max_height(max_height) .show(ui, |ui| { let max_width = ui.available_width(); diff --git a/crates/notedeck_columns/src/ui/note/post.rs b/crates/notedeck_columns/src/ui/note/post.rs @@ -35,7 +35,6 @@ pub struct PostView<'a, 'd> { draft: &'a mut Draft, post_type: PostType, poster: FilledKeypair<'a>, - id_source: Option<egui::Id>, inner_rect: egui::Rect, note_options: NoteOptions, jobs: &'a mut JobsCache, @@ -112,12 +111,10 @@ impl<'a, 'd> PostView<'a, 'd> { note_options: NoteOptions, jobs: &'a mut JobsCache, ) -> Self { - let id_source: Option<egui::Id> = None; PostView { note_context, draft, poster, - id_source, post_type, inner_rect, note_options, @@ -125,9 +122,12 @@ impl<'a, 'd> PostView<'a, 'd> { } } - pub fn id_source(mut self, id_source: impl std::hash::Hash) -> Self { - self.id_source = Some(egui::Id::new(id_source)); - self + fn id() -> egui::Id { + egui::Id::new("post") + } + + pub fn scroll_id() -> egui::Id { + PostView::id().with("scroll") } fn editbox(&mut self, txn: &nostrdb::Transaction, ui: &mut egui::Ui) -> egui::Response { @@ -213,7 +213,8 @@ impl<'a, 'd> PostView<'a, 'd> { let focused = out.response.has_focus(); - ui.ctx().data_mut(|d| d.insert_temp(self.id(), focused)); + ui.ctx() + .data_mut(|d| d.insert_temp(PostView::id(), focused)); out.response } @@ -305,11 +306,7 @@ impl<'a, 'd> PostView<'a, 'd> { fn focused(&self, ui: &egui::Ui) -> bool { ui.ctx() - .data(|d| d.get_temp::<bool>(self.id()).unwrap_or(false)) - } - - fn id(&self) -> egui::Id { - self.id_source.unwrap_or_else(|| egui::Id::new("post")) + .data(|d| d.get_temp::<bool>(PostView::id()).unwrap_or(false)) } pub fn outer_margin() -> i8 { @@ -321,6 +318,13 @@ impl<'a, 'd> PostView<'a, 'd> { } pub fn ui(&mut self, txn: &Transaction, ui: &mut egui::Ui) -> PostResponse { + ScrollArea::vertical() + .id_salt(PostView::scroll_id()) + .show(ui, |ui| self.ui_no_scroll(txn, ui)) + .inner + } + + pub fn ui_no_scroll(&mut self, txn: &Transaction, ui: &mut egui::Ui) -> PostResponse { let focused = self.focused(ui); let stroke = if focused { ui.visuals().selection.stroke diff --git a/crates/notedeck_columns/src/ui/note/quote_repost.rs b/crates/notedeck_columns/src/ui/note/quote_repost.rs @@ -4,6 +4,7 @@ use crate::{ ui::{self}, }; +use egui::ScrollArea; use enostr::{FilledKeypair, NoteId}; use notedeck::NoteContext; use notedeck_ui::{jobs::JobsCache, NoteOptions}; @@ -13,7 +14,7 @@ pub struct QuoteRepostView<'a, 'd> { poster: FilledKeypair<'a>, draft: &'a mut Draft, quoting_note: &'a nostrdb::Note<'a>, - id_source: Option<egui::Id>, + scroll_id: egui::Id, inner_rect: egui::Rect, note_options: NoteOptions, jobs: &'a mut JobsCache, @@ -29,22 +30,36 @@ impl<'a, 'd> QuoteRepostView<'a, 'd> { inner_rect: egui::Rect, note_options: NoteOptions, jobs: &'a mut JobsCache, + col: usize, ) -> Self { - let id_source: Option<egui::Id> = None; QuoteRepostView { note_context, poster, draft, quoting_note, - id_source, + scroll_id: QuoteRepostView::scroll_id(col, quoting_note.id()), inner_rect, note_options, jobs, } } + fn id(col: usize, note_id: &[u8; 32]) -> egui::Id { + egui::Id::new(("quote_repost", col, note_id)) + } + + pub fn scroll_id(col: usize, note_id: &[u8; 32]) -> egui::Id { + QuoteRepostView::id(col, note_id).with("scroll") + } + pub fn show(&mut self, ui: &mut egui::Ui) -> PostResponse { - let id = self.id(); + ScrollArea::vertical() + .id_salt(self.scroll_id) + .show(ui, |ui| self.show_internal(ui)) + .inner + } + + fn show_internal(&mut self, ui: &mut egui::Ui) -> PostResponse { let quoting_note_id = self.quoting_note.id(); let post_resp = ui::PostView::new( @@ -56,18 +71,7 @@ impl<'a, 'd> QuoteRepostView<'a, 'd> { self.note_options, self.jobs, ) - .id_source(id) - .ui(self.quoting_note.txn().unwrap(), ui); + .ui_no_scroll(self.quoting_note.txn().unwrap(), ui); post_resp } - - 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("quote-repost-view")) - } } diff --git a/crates/notedeck_columns/src/ui/note/reply.rs b/crates/notedeck_columns/src/ui/note/reply.rs @@ -4,7 +4,7 @@ use crate::ui::{ note::{PostAction, PostResponse, PostType}, }; -use egui::{Rect, Response, Ui}; +use egui::{Rect, Response, ScrollArea, Ui}; use enostr::{FilledKeypair, NoteId}; use notedeck::NoteContext; use notedeck_ui::jobs::JobsCache; @@ -15,7 +15,7 @@ pub struct PostReplyView<'a, 'd> { poster: FilledKeypair<'a>, draft: &'a mut Draft, note: &'a nostrdb::Note<'a>, - id_source: Option<egui::Id>, + scroll_id: egui::Id, inner_rect: egui::Rect, note_options: NoteOptions, jobs: &'a mut JobsCache, @@ -31,31 +31,37 @@ impl<'a, 'd> PostReplyView<'a, 'd> { inner_rect: egui::Rect, note_options: NoteOptions, jobs: &'a mut JobsCache, + col: usize, ) -> Self { - let id_source: Option<egui::Id> = None; PostReplyView { note_context, poster, draft, note, - id_source, + scroll_id: PostReplyView::scroll_id(col, note.id()), inner_rect, note_options, jobs, } } - pub fn id_source(mut self, id: egui::Id) -> Self { - self.id_source = Some(id); - self + fn id(col: usize, note_id: &[u8; 32]) -> egui::Id { + egui::Id::new(("reply_view", col, note_id)) } - pub fn id(&self) -> egui::Id { - self.id_source - .unwrap_or_else(|| egui::Id::new("post-reply-view")) + pub fn scroll_id(col: usize, note_id: &[u8; 32]) -> egui::Id { + PostReplyView::id(col, note_id).with("scroll") } pub fn show(&mut self, ui: &mut egui::Ui) -> PostResponse { + ScrollArea::vertical() + .id_salt(self.scroll_id) + .show(ui, |ui| self.show_internal(ui)) + .inner + } + + // no scroll + fn show_internal(&mut self, ui: &mut egui::Ui) -> PostResponse { ui.vertical(|ui| { let avail_rect = ui.available_rect_before_wrap(); @@ -81,7 +87,6 @@ impl<'a, 'd> PostReplyView<'a, 'd> { }) .inner; - let id = self.id(); let replying_to = self.note.id(); let rect_before_post = ui.min_rect(); @@ -95,8 +100,7 @@ impl<'a, 'd> PostReplyView<'a, 'd> { self.note_options, self.jobs, ) - .id_source(id) - .ui(self.note.txn().unwrap(), ui) + .ui_no_scroll(self.note.txn().unwrap(), ui) }; post_response.action = post_response diff --git a/crates/notedeck_columns/src/ui/profile/edit.rs b/crates/notedeck_columns/src/ui/profile/edit.rs @@ -24,9 +24,14 @@ impl<'a> EditProfileView<'a> { } } + pub fn scroll_id() -> egui::Id { + egui::Id::new("edit_profile") + } + // return true to save pub fn ui(&mut self, ui: &mut egui::Ui) -> bool { ScrollArea::vertical() + .id_salt(EditProfileView::scroll_id()) .show(ui, |ui| { banner(ui, self.state.banner(), 188.0); diff --git a/crates/notedeck_columns/src/ui/profile/mod.rs b/crates/notedeck_columns/src/ui/profile/mod.rs @@ -59,8 +59,12 @@ impl<'a, 'd> ProfileView<'a, 'd> { } } + pub fn scroll_id(col_id: usize, profile_pubkey: &Pubkey) -> egui::Id { + egui::Id::new(("profile_scroll", col_id, profile_pubkey)) + } + 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 scroll_id = ProfileView::scroll_id(self.col_id, self.pubkey); let offset_id = scroll_id.with("scroll_offset"); let mut scroll_area = ScrollArea::vertical().id_salt(scroll_id); diff --git a/crates/notedeck_columns/src/ui/relay.rs b/crates/notedeck_columns/src/ui/relay.rs @@ -36,6 +36,7 @@ impl RelayView<'_> { ui.add_space(8.0); egui::ScrollArea::vertical() + .id_salt(RelayView::scroll_id()) .scroll_bar_visibility(egui::scroll_area::ScrollBarVisibility::AlwaysHidden) .auto_shrink([false; 2]) .show(ui, |ui| { @@ -51,6 +52,10 @@ impl RelayView<'_> { action } + + pub fn scroll_id() -> egui::Id { + egui::Id::new("relay_scroll") + } } impl<'a> RelayView<'a> { diff --git a/crates/notedeck_columns/src/ui/search/mod.rs b/crates/notedeck_columns/src/ui/search/mod.rs @@ -151,6 +151,7 @@ impl<'a, 'd> SearchView<'a, 'd> { fn show_search_results(&mut self, ui: &mut egui::Ui) -> Option<NoteAction> { egui::ScrollArea::vertical() + .id_salt(SearchView::scroll_id()) .show(ui, |ui| { let reversed = false; TimelineTabView::new( @@ -165,6 +166,10 @@ impl<'a, 'd> SearchView<'a, 'd> { }) .inner } + + pub fn scroll_id() -> egui::Id { + egui::Id::new("search_results") + } } fn execute_search( diff --git a/crates/notedeck_columns/src/ui/thread.rs b/crates/notedeck_columns/src/ui/thread.rs @@ -14,7 +14,6 @@ pub struct ThreadView<'a, 'd> { selected_note_id: &'a [u8; 32], note_options: NoteOptions, col: usize, - id_source: egui::Id, note_context: &'a mut NoteContext<'d>, jobs: &'a mut JobsCache, } @@ -27,37 +26,33 @@ impl<'a, 'd> ThreadView<'a, 'd> { note_options: NoteOptions, note_context: &'a mut NoteContext<'d>, jobs: &'a mut JobsCache, + col: usize, ) -> Self { - let id_source = egui::Id::new("threadscroll_threadview"); ThreadView { threads, selected_note_id, note_options, - id_source, note_context, jobs, - col: 0, + col, } } - pub fn id_source(mut self, col: usize) -> Self { - self.col = col; - self.id_source = egui::Id::new(("threadscroll", col)); - self + pub fn scroll_id(selected_note_id: &[u8; 32], col: usize) -> egui::Id { + egui::Id::new(("threadscroll", selected_note_id, col)) } pub fn ui(&mut self, ui: &mut egui::Ui) -> Option<NoteAction> { let txn = Transaction::new(self.note_context.ndb).expect("txn"); + let scroll_id = ThreadView::scroll_id(self.selected_note_id, self.col); let mut scroll_area = egui::ScrollArea::vertical() - .id_salt(self.id_source) + .id_salt(scroll_id) .animated(false) .auto_shrink([false, false]) .scroll_bar_visibility(egui::scroll_area::ScrollBarVisibility::AlwaysVisible); - let offset_id = self - .id_source - .with(("scroll_offset", self.selected_note_id)); + let offset_id = scroll_id.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); diff --git a/crates/notedeck_columns/src/ui/timeline.rs b/crates/notedeck_columns/src/ui/timeline.rs @@ -74,6 +74,15 @@ impl<'a, 'd> TimelineView<'a, 'd> { self.reverse = true; self } + + pub fn scroll_id( + timeline_cache: &TimelineCache, + timeline_id: &TimelineKind, + col: usize, + ) -> Option<egui::Id> { + let timeline = timeline_cache.get(timeline_id)?; + Some(egui::Id::new(("tlscroll", timeline.view_id(col)))) + } } #[allow(clippy::too_many_arguments)] @@ -95,7 +104,9 @@ fn timeline_ui( */ - let scroll_id = { + let scroll_id = TimelineView::scroll_id(timeline_cache, timeline_id, col)?; + + { let timeline = if let Some(timeline) = timeline_cache.get_mut(timeline_id) { timeline } else { @@ -114,8 +125,6 @@ fn timeline_ui( // need this for some reason?? ui.add_space(3.0); - - egui::Id::new(("tlscroll", timeline.view_id(col))) }; let show_top_button_id = ui.id().with((scroll_id, "at_top"));