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
This commit is contained in:
William Casarin
2025-07-24 15:25:53 -07:00
19 changed files with 404 additions and 138 deletions

2
Cargo.lock generated
View File

@@ -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",

View File

@@ -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" }

View File

@@ -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(),
}
}

View File

@@ -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")
}

View File

@@ -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;

View File

@@ -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,
&note,
inner_rect,
app.note_options,
&mut app.jobs,
)
.id_source(id)
.show(ui)
})
.inner;
let response = ui::PostReplyView::new(
&mut note_context,
poster,
draft,
&note,
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,
&note,
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,
&note,
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(),
col,
ctx.i18n,
)
.show_move_button(!narrow)
.show_delete_button(!narrow)
.show(ui),
let routes = app
.columns(ctx.accounts)
.column(col)
.router()
.routes()
.clone();
let nav = Nav::new(&routes).id_source(egui::Id::new(("nav", col)));
NavUiType::Body => {
if let Some(top) = nav.routes().last() {
render_nav_body(ui, app, ctx, top, nav.routes().len(), col, inner_rect)
} else {
None
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,
) 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_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
}
}
}
});
});
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,
}
}

View File

@@ -87,8 +87,8 @@ pub fn render_thread_route(
note_options,
note_context,
jobs,
col,
)
.id_source(col)
.ui(ui)
.map(Into::into)
}

View File

@@ -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,

View File

@@ -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) {

View File

@@ -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();

View File

@@ -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

View File

@@ -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"))
}
}

View File

@@ -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

View File

@@ -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);

View File

@@ -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);

View File

@@ -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> {

View File

@@ -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(

View File

@@ -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);

View File

@@ -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"));