prop drag id through responses instead of manual wiring

Signed-off-by: kernelkind <kernelkind@gmail.com>
This commit is contained in:
kernelkind
2025-09-25 13:33:52 -04:00
parent a6d91c43e4
commit d81243f055
16 changed files with 394 additions and 201 deletions

View File

@@ -33,6 +33,7 @@ use crate::{
Damus,
};
use egui::scroll_area::ScrollAreaOutput;
use egui_nav::{Nav, NavAction, NavResponse, NavUiType, Percent, PopupResponse, PopupSheet};
use enostr::ProfileState;
use nostrdb::{Filter, Ndb, Transaction};
@@ -532,7 +533,7 @@ fn render_nav_body(
depth: usize,
col: usize,
inner_rect: egui::Rect,
) -> Option<RenderNavAction> {
) -> BodyResponse<RenderNavAction> {
let mut note_context = NoteContext {
ndb: ctx.ndb,
accounts: ctx.accounts,
@@ -555,7 +556,7 @@ fn render_nav_body(
.is_some_and(|ind| ind == col)
&& app.options.contains(AppOptions::ScrollToTop);
let nav_action = render_timeline_route(
let resp = render_timeline_route(
&mut app.timeline_cache,
kind,
col,
@@ -574,7 +575,7 @@ fn render_nav_body(
app.options.remove(AppOptions::ScrollToTop);
}
nav_action
resp
}
Route::Thread(selection) => render_thread_route(
&mut app.threads,
@@ -585,8 +586,8 @@ fn render_nav_body(
&mut note_context,
&mut app.jobs,
),
Route::Accounts(amr) => 's: {
let Some(action) = render_accounts_route(
Route::Accounts(amr) => {
let resp = render_accounts_route(
ui,
ctx,
&mut app.jobs,
@@ -594,11 +595,9 @@ fn render_nav_body(
&mut app.onboarding,
&mut app.view_state.follow_packs,
*amr,
) else {
break 's None;
};
);
match action {
resp.map_output_maybe(|action| match action {
AccountsResponse::ViewProfile(pubkey) => {
Some(RenderNavAction::NoteAction(NoteAction::Profile(pubkey)))
}
@@ -611,11 +610,11 @@ fn render_nav_body(
.accounts_action
.map(|f| RenderNavAction::SwitchingAction(SwitchingAction::Accounts(f)))
}
}
})
}
Route::Relays => RelayView::new(ctx.pool, &mut app.view_state.id_string_map, ctx.i18n)
.ui(ui)
.map(RenderNavAction::RelayAction),
.map_output(RenderNavAction::RelayAction),
Route::Settings => SettingsView::new(
ctx.settings.get_settings_mut(),
@@ -624,7 +623,7 @@ fn render_nav_body(
&mut app.jobs,
)
.ui(ui)
.map(RenderNavAction::SettingsAction),
.map_output(RenderNavAction::SettingsAction),
Route::Reply(id) => {
let txn = if let Ok(txn) = Transaction::new(ctx.ndb) {
@@ -635,7 +634,7 @@ fn render_nav_body(
"Reply to unknown note",
"Error message when reply note cannot be found"
));
return None;
return BodyResponse::none();
};
let note = if let Ok(note) = ctx.ndb.get_note_by_id(&txn, id.bytes()) {
@@ -646,18 +645,20 @@ fn render_nav_body(
"Reply to unknown note",
"Error message when reply note cannot be found"
));
return None;
return BodyResponse::none();
};
let poster = ctx.accounts.selected_filled()?;
let Some(poster) = ctx.accounts.selected_filled() else {
return BodyResponse::none();
};
let action = {
let resp = {
let draft = app.drafts.reply_mut(note.id());
let mut options = app.note_options;
options.set(NoteOptions::Wide, false);
let response = ui::PostReplyView::new(
ui::PostReplyView::new(
&mut note_context,
poster,
draft,
@@ -667,12 +668,10 @@ fn render_nav_body(
&mut app.jobs,
col,
)
.show(ui);
response.action
.show(ui)
};
action.map(Into::into)
resp.map_output_maybe(|o| Some(o.action?.into()))
}
Route::Quote(id) => {
let txn = Transaction::new(ctx.ndb).expect("txn");
@@ -685,10 +684,13 @@ fn render_nav_body(
"Quote of unknown note",
"Error message when quote note cannot be found"
));
return None;
return BodyResponse::none();
};
let Some(poster) = ctx.accounts.selected_filled() else {
return BodyResponse::none();
};
let poster = ctx.accounts.selected_filled()?;
let draft = app.drafts.quote_mut(note.id());
let response = crate::ui::note::QuoteRepostView::new(
@@ -703,10 +705,12 @@ fn render_nav_body(
)
.show(ui);
response.action.map(Into::into)
response.map_output_maybe(|o| Some(o.action?.into()))
}
Route::ComposeNote => {
let kp = ctx.accounts.get_selected_account().key.to_full()?;
let Some(kp) = ctx.accounts.get_selected_account().key.to_full() else {
return BodyResponse::none();
};
let draft = app.drafts.compose_mut();
let txn = Transaction::new(ctx.ndb).expect("txn");
@@ -721,16 +725,16 @@ fn render_nav_body(
)
.ui(&txn, ui);
post_response.action.map(Into::into)
post_response.map_output_maybe(|o| Some(o.action?.into()))
}
Route::AddColumn(route) => {
render_add_column_routes(ui, app, ctx, col, route);
None
BodyResponse::none()
}
Route::Support => {
SupportView::new(&mut app.support, ctx.i18n).show(ui);
None
BodyResponse::none()
}
Route::Search => {
let id = ui.id().with(("search", depth, col));
@@ -759,7 +763,7 @@ fn render_nav_body(
&mut app.jobs,
)
.show(ui)
.map(RenderNavAction::NoteAction)
.map_output(RenderNavAction::NoteAction)
}
Route::NewDeck => {
let id = ui.id().with("new-deck");
@@ -784,7 +788,8 @@ fn render_nav_body(
.get_selected_router()
.go_back();
}
resp
BodyResponse::output(resp)
}
Route::EditDeck(index) => {
let mut action = None;
@@ -816,31 +821,37 @@ fn render_nav_body(
.go_back();
}
action
BodyResponse::output(action)
}
Route::EditProfile(pubkey) => {
let mut action = None;
let Some(kp) = ctx.accounts.get_full(pubkey) else {
error!("Pubkey in EditProfile route did not have an nsec attached in Accounts");
return None;
return BodyResponse::none();
};
let Some(state) = app.view_state.pubkey_to_profile_state.get_mut(kp.pubkey) else {
tracing::error!(
"No profile state when navigating to EditProfile... was handle_navigating_edit_profile not called?"
);
return action;
return BodyResponse::none();
};
if EditProfileView::new(ctx.i18n, state, ctx.img_cache, ctx.clipboard).ui(ui) {
if let Some(state) = app.view_state.pubkey_to_profile_state.get(kp.pubkey) {
action = Some(RenderNavAction::ProfileAction(ProfileAction::SaveChanges(
SaveProfileChanges::new(kp.to_full(), state.clone()),
)))
}
}
action
EditProfileView::new(ctx.i18n, state, ctx.img_cache, ctx.clipboard)
.ui(ui)
.map_output_maybe(|save| {
if save {
app.view_state
.pubkey_to_profile_state
.get(kp.pubkey)
.map(|state| {
RenderNavAction::ProfileAction(ProfileAction::SaveChanges(
SaveProfileChanges::new(kp.to_full(), state.clone()),
))
})
} else {
None
}
})
}
Route::Wallet(wallet_type) => {
let state = match wallet_type {
@@ -887,23 +898,24 @@ fn render_nav_body(
}
};
WalletView::new(state, ctx.i18n, ctx.clipboard)
.ui(ui)
.map(RenderNavAction::WalletAction)
BodyResponse::output(WalletView::new(state, ctx.i18n, ctx.clipboard).ui(ui))
.map_output(RenderNavAction::WalletAction)
}
Route::CustomizeZapAmount(target) => {
let txn = Transaction::new(ctx.ndb).expect("txn");
let default_msats = get_current_default_msats(ctx.accounts, ctx.global_wallet);
CustomZapView::new(
ctx.i18n,
ctx.img_cache,
ctx.ndb,
&txn,
&target.zap_recipient,
default_msats,
BodyResponse::output(
CustomZapView::new(
ctx.i18n,
ctx.img_cache,
ctx.ndb,
&txn,
&target.zap_recipient,
default_msats,
)
.ui(ui),
)
.ui(ui)
.map(|msats| {
.map_output(|msats| {
get_active_columns_mut(ctx.i18n, ctx.accounts, &mut app.decks_cache)
.column_mut(col)
.router_mut()
@@ -919,6 +931,87 @@ fn render_nav_body(
}
}
pub struct BodyResponse<R> {
pub drag_id: Option<egui::Id>, // the id which was used for dragging.
pub output: Option<R>,
}
impl<R> BodyResponse<R> {
pub fn none() -> Self {
Self {
drag_id: None,
output: None,
}
}
pub fn scroll(output: ScrollAreaOutput<Option<R>>) -> Self {
Self {
drag_id: Some(Self::scroll_output_to_drag_id(output.id)),
output: output.inner,
}
}
pub fn set_scroll_id(&mut self, output: &ScrollAreaOutput<Option<R>>) {
self.drag_id = Some(Self::scroll_output_to_drag_id(output.id));
}
pub fn output(output: Option<R>) -> Self {
Self {
drag_id: None,
output,
}
}
pub fn set_output(&mut self, output: R) {
self.output = Some(output);
}
/// The id of an `egui::ScrollAreaOutput`
/// Should use `Self::scroll` when possible
pub fn scroll_raw(mut self, id: egui::Id) -> Self {
self.drag_id = Some(Self::scroll_output_to_drag_id(id));
self
}
/// The id which is directly used for dragging
pub fn set_drag_id_raw(&mut self, id: egui::Id) {
self.drag_id = Some(id);
}
fn scroll_output_to_drag_id(id: egui::Id) -> egui::Id {
id.with("area")
}
pub fn map_output<S>(self, f: impl FnOnce(R) -> S) -> BodyResponse<S> {
BodyResponse {
drag_id: self.drag_id,
output: self.output.map(f),
}
}
pub fn map_output_maybe<S>(self, f: impl FnOnce(R) -> Option<S>) -> BodyResponse<S> {
BodyResponse {
drag_id: self.drag_id,
output: self.output.and_then(f),
}
}
pub fn maybe_map_output<S>(self, f: impl FnOnce(Option<R>) -> S) -> BodyResponse<S> {
BodyResponse {
drag_id: self.drag_id,
output: Some(f(self.output)),
}
}
/// insert the contents of the new BodyResponse if they are empty in Self
pub fn insert(&mut self, body: BodyResponse<R>) {
self.drag_id = self.drag_id.or(body.drag_id);
if self.output.is_none() {
self.output = body.output;
}
}
}
#[must_use = "RenderNavResponse must be handled by calling .process_render_nav_response(..)"]
pub fn render_nav(
col: usize,
@@ -967,7 +1060,9 @@ pub fn render_nav(
.show_move_button(!narrow)
.show_delete_button(!narrow)
.show(ui),
NavUiType::Body => render_nav_body(ui, app, ctx, route, 1, col, inner_rect),
NavUiType::Body => {
render_nav_body(ui, app, ctx, route, 1, col, inner_rect).output
}
});
return RenderNavResponse::new(col, NotedeckNavResponse::Popup(Box::new(resp)));
@@ -1047,8 +1142,9 @@ pub fn render_nav(
if let Some(top) = nav.routes().last() {
render_nav_body(ui, app, ctx, top, nav.routes().len(), col, inner_rect)
} else {
None
BodyResponse::none()
}
.output
}
});