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

@@ -7,6 +7,7 @@ use notedeck_ui::nip51_set::Nip51SetUiCache;
pub use crate::accounts::route::AccountsResponse; pub use crate::accounts::route::AccountsResponse;
use crate::app::get_active_columns_mut; use crate::app::get_active_columns_mut;
use crate::decks::DecksCache; use crate::decks::DecksCache;
use crate::nav::BodyResponse;
use crate::onboarding::Onboarding; use crate::onboarding::Onboarding;
use crate::profile::send_new_contact_list; use crate::profile::send_new_contact_list;
use crate::subscriptions::Subscriptions; use crate::subscriptions::Subscriptions;
@@ -81,7 +82,7 @@ pub fn render_accounts_route(
onboarding: &mut Onboarding, onboarding: &mut Onboarding,
follow_packs_ui: &mut Nip51SetUiCache, follow_packs_ui: &mut Nip51SetUiCache,
route: AccountsRoute, route: AccountsRoute,
) -> Option<AccountsResponse> { ) -> BodyResponse<AccountsResponse> {
match route { match route {
AccountsRoute::Accounts => AccountsView::new( AccountsRoute::Accounts => AccountsView::new(
app_ctx.ndb, app_ctx.ndb,
@@ -90,15 +91,15 @@ pub fn render_accounts_route(
app_ctx.i18n, app_ctx.i18n,
) )
.ui(ui) .ui(ui)
.inner .map_output(AccountsRouteResponse::Accounts)
.map(AccountsRouteResponse::Accounts) .map_output(AccountsResponse::Account),
.map(AccountsResponse::Account),
AccountsRoute::AddAccount => { AccountsRoute::AddAccount => {
AccountLoginView::new(login_state, app_ctx.clipboard, app_ctx.i18n) let action = AccountLoginView::new(login_state, app_ctx.clipboard, app_ctx.i18n)
.ui(ui) .ui(ui)
.inner .inner
.map(AccountsRouteResponse::AddAccount) .map(AccountsRouteResponse::AddAccount)
.map(AccountsResponse::Account) .map(AccountsResponse::Account);
BodyResponse::output(action)
} }
AccountsRoute::Onboarding => FollowPackOnboardingView::new( AccountsRoute::Onboarding => FollowPackOnboardingView::new(
onboarding, onboarding,
@@ -110,7 +111,7 @@ pub fn render_accounts_route(
jobs, jobs,
) )
.ui(ui) .ui(ui)
.map(|r| match r { .map_output(|r| match r {
OnboardingResponse::FollowPacks(follow_packs_response) => { OnboardingResponse::FollowPacks(follow_packs_response) => {
AccountsResponse::Account(AccountsRouteResponse::AddAccount( AccountsResponse::Account(AccountsRouteResponse::AddAccount(
AccountLoginResponse::Onboarding(follow_packs_response), AccountLoginResponse::Onboarding(follow_packs_response),

View File

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

View File

@@ -1,5 +1,5 @@
use crate::{ use crate::{
nav::RenderNavAction, nav::{BodyResponse, RenderNavAction},
profile::ProfileAction, profile::ProfileAction,
timeline::{thread::Threads, ThreadSelection, TimelineCache, TimelineKind}, timeline::{thread::Threads, ThreadSelection, TimelineCache, TimelineKind},
ui::{self, ProfileView}, ui::{self, ProfileView},
@@ -20,7 +20,7 @@ pub fn render_timeline_route(
note_context: &mut NoteContext, note_context: &mut NoteContext,
jobs: &mut JobsCache, jobs: &mut JobsCache,
scroll_to_top: bool, scroll_to_top: bool,
) -> Option<RenderNavAction> { ) -> BodyResponse<RenderNavAction> {
match kind { match kind {
TimelineKind::List(_) TimelineKind::List(_)
| TimelineKind::Search(_) | TimelineKind::Search(_)
@@ -29,11 +29,11 @@ pub fn render_timeline_route(
| TimelineKind::Universe | TimelineKind::Universe
| TimelineKind::Hashtag(_) | TimelineKind::Hashtag(_)
| TimelineKind::Generic(_) => { | TimelineKind::Generic(_) => {
let note_action = let resp =
ui::TimelineView::new(kind, timeline_cache, note_context, note_options, jobs, col) ui::TimelineView::new(kind, timeline_cache, note_context, note_options, jobs, col)
.ui(ui); .ui(ui);
note_action.map(RenderNavAction::NoteAction) resp.map_output(RenderNavAction::NoteAction)
} }
TimelineKind::Profile(pubkey) => { TimelineKind::Profile(pubkey) => {
@@ -49,7 +49,7 @@ pub fn render_timeline_route(
) )
} else { } else {
// we render profiles like timelines if they are at the root // we render profiles like timelines if they are at the root
let note_action = ui::TimelineView::new( let resp = ui::TimelineView::new(
kind, kind,
timeline_cache, timeline_cache,
note_context, note_context,
@@ -60,7 +60,7 @@ pub fn render_timeline_route(
.scroll_to_top(scroll_to_top) .scroll_to_top(scroll_to_top)
.ui(ui); .ui(ui);
note_action.map(RenderNavAction::NoteAction) resp.map_output(RenderNavAction::NoteAction)
} }
} }
} }
@@ -75,7 +75,7 @@ pub fn render_thread_route(
ui: &mut egui::Ui, ui: &mut egui::Ui,
note_context: &mut NoteContext, note_context: &mut NoteContext,
jobs: &mut JobsCache, jobs: &mut JobsCache,
) -> Option<RenderNavAction> { ) -> BodyResponse<RenderNavAction> {
// don't truncate thread notes for now, since they are // don't truncate thread notes for now, since they are
// default truncated everywher eelse // default truncated everywher eelse
note_options.set(NoteOptions::Truncate, false); note_options.set(NoteOptions::Truncate, false);
@@ -92,7 +92,7 @@ pub fn render_thread_route(
col, col,
) )
.ui(ui) .ui(ui)
.map(Into::into) .map_output(RenderNavAction::NoteAction)
} }
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
@@ -104,7 +104,7 @@ pub fn render_profile_route(
note_options: NoteOptions, note_options: NoteOptions,
note_context: &mut NoteContext, note_context: &mut NoteContext,
jobs: &mut JobsCache, jobs: &mut JobsCache,
) -> Option<RenderNavAction> { ) -> BodyResponse<RenderNavAction> {
let profile_view = ProfileView::new( let profile_view = ProfileView::new(
pubkey, pubkey,
col, col,
@@ -115,23 +115,19 @@ pub fn render_profile_route(
) )
.ui(ui); .ui(ui);
if let Some(action) = profile_view { profile_view.map_output_maybe(|action| match action {
match action { ui::profile::ProfileViewAction::EditProfile => note_context
ui::profile::ProfileViewAction::EditProfile => note_context .accounts
.accounts .get_full(pubkey)
.get_full(pubkey) .map(|kp| RenderNavAction::ProfileAction(ProfileAction::Edit(kp.to_full()))),
.map(|kp| RenderNavAction::ProfileAction(ProfileAction::Edit(kp.to_full()))), ui::profile::ProfileViewAction::Note(note_action) => {
ui::profile::ProfileViewAction::Note(note_action) => { Some(RenderNavAction::NoteAction(note_action))
Some(RenderNavAction::NoteAction(note_action))
}
ui::profile::ProfileViewAction::Follow(target_key) => Some(
RenderNavAction::ProfileAction(ProfileAction::Follow(target_key)),
),
ui::profile::ProfileViewAction::Unfollow(target_key) => Some(
RenderNavAction::ProfileAction(ProfileAction::Unfollow(target_key)),
),
} }
} else { ui::profile::ProfileViewAction::Follow(target_key) => Some(RenderNavAction::ProfileAction(
None ProfileAction::Follow(target_key),
} )),
ui::profile::ProfileViewAction::Unfollow(target_key) => Some(
RenderNavAction::ProfileAction(ProfileAction::Unfollow(target_key)),
),
})
} }

View File

@@ -9,6 +9,8 @@ use notedeck_ui::profile::preview::SimpleProfilePreview;
use notedeck_ui::app_images; use notedeck_ui::app_images;
use crate::nav::BodyResponse;
pub struct AccountsView<'a> { pub struct AccountsView<'a> {
ndb: &'a Ndb, ndb: &'a Ndb,
accounts: &'a Accounts, accounts: &'a Accounts,
@@ -44,20 +46,26 @@ impl<'a> AccountsView<'a> {
} }
} }
pub fn ui(&mut self, ui: &mut Ui) -> InnerResponse<Option<AccountsViewResponse>> { pub fn ui(&mut self, ui: &mut Ui) -> BodyResponse<AccountsViewResponse> {
let mut out = BodyResponse::none();
Frame::new().outer_margin(12.0).show(ui, |ui| { Frame::new().outer_margin(12.0).show(ui, |ui| {
if let Some(resp) = Self::top_section_buttons_widget(ui, self.i18n).inner { if let Some(resp) = Self::top_section_buttons_widget(ui, self.i18n).inner {
return Some(resp); out.set_output(resp);
} }
ui.add_space(8.0); ui.add_space(8.0);
scroll_area() let scroll_out = scroll_area()
.id_salt(AccountsView::scroll_id()) .id_salt(AccountsView::scroll_id())
.show(ui, |ui| { .show(ui, |ui| {
Self::show_accounts(ui, self.accounts, self.ndb, self.img_cache, self.i18n) Self::show_accounts(ui, self.accounts, self.ndb, self.img_cache, self.i18n)
}) });
.inner
}) out.set_scroll_id(&scroll_out);
if let Some(scroll_output) = scroll_out.inner {
out.set_output(scroll_output);
}
});
out
} }
pub fn scroll_id() -> egui::Id { pub fn scroll_id() -> egui::Id {

View File

@@ -11,6 +11,8 @@ use notedeck_ui::{
}; };
use tracing::error; use tracing::error;
use crate::nav::BodyResponse;
/// Displays user profiles for the user to pick from. /// Displays user profiles for the user to pick from.
/// Useful for manually typing a username and selecting the profile desired /// Useful for manually typing a username and selecting the profile desired
pub struct MentionPickerView<'a> { pub struct MentionPickerView<'a> {
@@ -64,7 +66,11 @@ impl<'a> MentionPickerView<'a> {
MentionPickerResponse::SelectResult(selection) MentionPickerResponse::SelectResult(selection)
} }
pub fn show_in_rect(&mut self, rect: egui::Rect, ui: &mut egui::Ui) -> MentionPickerResponse { pub fn show_in_rect(
&mut self,
rect: egui::Rect,
ui: &mut egui::Ui,
) -> BodyResponse<MentionPickerResponse> {
let widget_id = ui.id().with("mention_results"); let widget_id = ui.id().with("mention_results");
let area_resp = egui::Area::new(widget_id) let area_resp = egui::Area::new(widget_id)
.order(egui::Order::Foreground) .order(egui::Order::Foreground)
@@ -102,14 +108,16 @@ impl<'a> MentionPickerView<'a> {
let scroll_resp = ScrollArea::vertical() let scroll_resp = ScrollArea::vertical()
.max_width(rect.width()) .max_width(rect.width())
.auto_shrink(Vec2b::FALSE) .auto_shrink(Vec2b::FALSE)
.show(ui, |ui| self.show(ui, width)); .show(ui, |ui| Some(self.show(ui, width)));
ui.advance_cursor_after_rect(rect); ui.advance_cursor_after_rect(rect);
if close_button_resp { BodyResponse::scroll(scroll_resp).map_output(|o| {
MentionPickerResponse::DeleteMention if close_button_resp {
} else { MentionPickerResponse::DeleteMention
scroll_resp.inner } else {
} o
}
})
}) })
.inner .inner
}); });

View File

@@ -1,5 +1,6 @@
use crate::draft::{Draft, Drafts, MentionHint}; use crate::draft::{Draft, Drafts, MentionHint};
use crate::media_upload::nostrbuild_nip96_upload; use crate::media_upload::nostrbuild_nip96_upload;
use crate::nav::BodyResponse;
use crate::post::{downcast_post_buffer, MentionType, NewPost}; use crate::post::{downcast_post_buffer, MentionType, NewPost};
use crate::ui::mentions_picker::MentionPickerView; use crate::ui::mentions_picker::MentionPickerView;
use crate::ui::{self, Preview, PreviewConfig}; use crate::ui::{self, Preview, PreviewConfig};
@@ -143,7 +144,7 @@ impl<'a, 'd> PostView<'a, 'd> {
self self
} }
fn editbox(&mut self, txn: &nostrdb::Transaction, ui: &mut egui::Ui) -> egui::Response { fn editbox(&mut self, txn: &nostrdb::Transaction, ui: &mut egui::Ui) -> EditBoxResponse {
ui.spacing_mut().item_spacing.x = 12.0; ui.spacing_mut().item_spacing.x = 12.0;
let pfp_size = 24.0; let pfp_size = 24.0;
@@ -221,37 +222,42 @@ impl<'a, 'd> PostView<'a, 'd> {
self.draft.buffer.selected_mention = false; self.draft.buffer.selected_mention = false;
} }
if let Some(cursor_index) = get_cursor_index(&out.state.cursor.char_range()) { let mention_hints_drag_id =
self.show_mention_hints(txn, ui, cursor_index, &out); if let Some(cursor_index) = get_cursor_index(&out.state.cursor.char_range()) {
} self.show_mention_hints(txn, ui, cursor_index, &out)
} else {
None
};
let focused = out.response.has_focus(); let focused = out.response.has_focus();
ui.ctx() ui.ctx()
.data_mut(|d| d.insert_temp(PostView::id(), focused)); .data_mut(|d| d.insert_temp(PostView::id(), focused));
out.response EditBoxResponse {
resp: out.response,
mention_hints_drag_id,
}
} }
// Displays the mention picker and handles when one is selected. // Displays the mention picker and handles when one is selected.
// returns the drag id of the mention hint widget
fn show_mention_hints( fn show_mention_hints(
&mut self, &mut self,
txn: &nostrdb::Transaction, txn: &nostrdb::Transaction,
ui: &mut egui::Ui, ui: &mut egui::Ui,
cursor_index: usize, cursor_index: usize,
textedit_output: &TextEditOutput, textedit_output: &TextEditOutput,
) { ) -> Option<egui::Id> {
let Some(mention) = self.draft.buffer.get_mention(cursor_index) else { let mention = self.draft.buffer.get_mention(cursor_index)?;
return;
};
if mention.info.mention_type != MentionType::Pending { if mention.info.mention_type != MentionType::Pending {
return; return None;
} }
if ui.ctx().input(|r| r.key_pressed(egui::Key::Escape)) { if ui.ctx().input(|r| r.key_pressed(egui::Key::Escape)) {
self.draft.buffer.delete_mention(mention.index); self.draft.buffer.delete_mention(mention.index);
return; return None;
} }
let mention_str = self.draft.buffer.get_mention_string(&mention); let mention_str = self.draft.buffer.get_mention_string(&mention);
@@ -274,10 +280,8 @@ impl<'a, 'd> PostView<'a, 'd> {
} }
let hint_rect = { let hint_rect = {
let hint = if let Some(hint) = &self.draft.cur_mention_hint { let Some(hint) = &self.draft.cur_mention_hint else {
hint return None;
} else {
return;
}; };
let mut hint_rect = self.inner_rect; let mut hint_rect = self.inner_rect;
@@ -285,9 +289,11 @@ impl<'a, 'd> PostView<'a, 'd> {
hint_rect hint_rect
}; };
let Ok(res) = self.note_context.ndb.search_profile(txn, mention_str, 10) else { let res = self
return; .note_context
}; .ndb
.search_profile(txn, mention_str, 10)
.ok()?;
let resp = MentionPickerView::new( let resp = MentionPickerView::new(
self.note_context.img_cache, self.note_context.img_cache,
@@ -298,7 +304,12 @@ impl<'a, 'd> PostView<'a, 'd> {
.show_in_rect(hint_rect, ui); .show_in_rect(hint_rect, ui);
let mut selection_made = None; let mut selection_made = None;
match resp {
let Some(out) = resp.output else {
return resp.drag_id;
};
match out {
ui::mentions_picker::MentionPickerResponse::SelectResult(selection) => { ui::mentions_picker::MentionPickerResponse::SelectResult(selection) => {
if let Some(hint_index) = selection { if let Some(hint_index) = selection {
if let Some(pk) = res.get(hint_index) { if let Some(pk) = res.get(hint_index) {
@@ -326,6 +337,8 @@ impl<'a, 'd> PostView<'a, 'd> {
if let Some(selection) = selection_made { if let Some(selection) = selection_made {
selection.process(ui.ctx(), textedit_output); selection.process(ui.ctx(), textedit_output);
} }
resp.drag_id
} }
fn focused(&self, ui: &egui::Ui) -> bool { fn focused(&self, ui: &egui::Ui) -> bool {
@@ -341,14 +354,25 @@ impl<'a, 'd> PostView<'a, 'd> {
12 12
} }
pub fn ui(&mut self, txn: &Transaction, ui: &mut egui::Ui) -> PostResponse { pub fn ui(&mut self, txn: &Transaction, ui: &mut egui::Ui) -> BodyResponse<PostResponse> {
ScrollArea::vertical() let scroll_out = ScrollArea::vertical()
.id_salt(PostView::scroll_id()) .id_salt(PostView::scroll_id())
.show(ui, |ui| self.ui_no_scroll(txn, ui)) .show(ui, |ui| Some(self.ui_no_scroll(txn, ui)));
.inner
let scroll_id = scroll_out.id;
if let Some(inner) = scroll_out.inner {
inner // should override the PostView scroll for the mention scroll
} else {
BodyResponse::none()
}
.scroll_raw(scroll_id)
} }
pub fn ui_no_scroll(&mut self, txn: &Transaction, ui: &mut egui::Ui) -> PostResponse { pub fn ui_no_scroll(
&mut self,
txn: &Transaction,
ui: &mut egui::Ui,
) -> BodyResponse<PostResponse> {
while let Some(selected_file) = get_next_selected_file() { while let Some(selected_file) = get_next_selected_file() {
match selected_file { match selected_file {
Ok(selected_media) => { Ok(selected_media) => {
@@ -393,7 +417,7 @@ impl<'a, 'd> PostView<'a, 'd> {
.inner .inner
} }
fn input_ui(&mut self, txn: &Transaction, ui: &mut egui::Ui) -> PostResponse { fn input_ui(&mut self, txn: &Transaction, ui: &mut egui::Ui) -> BodyResponse<PostResponse> {
let edit_response = ui.horizontal(|ui| self.editbox(txn, ui)).inner; let edit_response = ui.horizontal(|ui| self.editbox(txn, ui)).inner;
let note_response = if let PostType::Quote(id) = self.post_type { let note_response = if let PostType::Quote(id) = self.post_type {
@@ -445,10 +469,14 @@ impl<'a, 'd> PostView<'a, 'd> {
.and_then(|nr| nr.action.map(PostAction::QuotedNoteAction)) .and_then(|nr| nr.action.map(PostAction::QuotedNoteAction))
.or(post_action.map(PostAction::NewPostAction)); .or(post_action.map(PostAction::NewPostAction));
PostResponse { let mut resp = BodyResponse::output(action);
action, if let Some(drag_id) = edit_response.mention_hints_drag_id {
edit_response, resp.set_drag_id_raw(drag_id);
} }
resp.maybe_map_output(|action| PostResponse {
action,
edit_response: edit_response.resp,
})
} }
fn input_buttons(&mut self, ui: &mut egui::Ui) -> Option<NewPostAction> { fn input_buttons(&mut self, ui: &mut egui::Ui) -> Option<NewPostAction> {
@@ -596,6 +624,11 @@ impl<'a, 'd> PostView<'a, 'd> {
} }
} }
struct EditBoxResponse {
resp: egui::Response,
mention_hints_drag_id: Option<egui::Id>,
}
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
fn render_post_view_media( fn render_post_view_media(
ui: &mut egui::Ui, ui: &mut egui::Ui,

View File

@@ -1,6 +1,7 @@
use super::{PostResponse, PostType}; use super::{PostResponse, PostType};
use crate::{ use crate::{
draft::Draft, draft::Draft,
nav::BodyResponse,
ui::{self}, ui::{self},
}; };
@@ -52,14 +53,22 @@ impl<'a, 'd> QuoteRepostView<'a, 'd> {
QuoteRepostView::id(col, note_id).with("scroll") QuoteRepostView::id(col, note_id).with("scroll")
} }
pub fn show(&mut self, ui: &mut egui::Ui) -> PostResponse { pub fn show(&mut self, ui: &mut egui::Ui) -> BodyResponse<PostResponse> {
ScrollArea::vertical() let scroll_out = ScrollArea::vertical()
.id_salt(self.scroll_id) .id_salt(self.scroll_id)
.show(ui, |ui| self.show_internal(ui)) .show(ui, |ui| Some(self.show_internal(ui)));
.inner
let scroll_id = scroll_out.id;
if let Some(inner) = scroll_out.inner {
inner
} else {
BodyResponse::none()
}
.scroll_raw(scroll_id)
} }
fn show_internal(&mut self, ui: &mut egui::Ui) -> PostResponse { fn show_internal(&mut self, ui: &mut egui::Ui) -> BodyResponse<PostResponse> {
let quoting_note_id = self.quoting_note.id(); let quoting_note_id = self.quoting_note.id();
let post_resp = ui::PostView::new( let post_resp = ui::PostView::new(

View File

@@ -1,4 +1,5 @@
use crate::draft::Draft; use crate::draft::Draft;
use crate::nav::BodyResponse;
use crate::ui::{ use crate::ui::{
self, self,
note::{PostAction, PostResponse, PostType}, note::{PostAction, PostResponse, PostType},
@@ -52,16 +53,23 @@ impl<'a, 'd> PostReplyView<'a, 'd> {
PostReplyView::id(col, note_id).with("scroll") PostReplyView::id(col, note_id).with("scroll")
} }
pub fn show(&mut self, ui: &mut egui::Ui) -> PostResponse { pub fn show(&mut self, ui: &mut egui::Ui) -> BodyResponse<PostResponse> {
ScrollArea::vertical() let scroll_out = ScrollArea::vertical()
.id_salt(self.scroll_id) .id_salt(self.scroll_id)
.stick_to_bottom(true) .stick_to_bottom(true)
.show(ui, |ui| self.show_internal(ui)) .show(ui, |ui| Some(self.show_internal(ui)));
.inner
let scroll_id = scroll_out.id;
if let Some(inner) = scroll_out.inner {
inner
} else {
BodyResponse::none()
}
.scroll_raw(scroll_id)
} }
// no scroll // no scroll
fn show_internal(&mut self, ui: &mut egui::Ui) -> PostResponse { fn show_internal(&mut self, ui: &mut egui::Ui) -> BodyResponse<PostResponse> {
ui.vertical(|ui| { ui.vertical(|ui| {
let avail_rect = ui.available_rect_before_wrap(); let avail_rect = ui.available_rect_before_wrap();
@@ -103,17 +111,22 @@ impl<'a, 'd> PostReplyView<'a, 'd> {
.ui_no_scroll(self.note.txn().unwrap(), ui) .ui_no_scroll(self.note.txn().unwrap(), ui)
}; };
post_response.action = post_response post_response = post_response.map_output(|mut o| {
.action o.action = o
.or(quoted_note.action.map(PostAction::QuotedNoteAction)); .action
.or(quoted_note.action.map(PostAction::QuotedNoteAction));
o
});
reply_line_ui( if let Some(p) = &post_response.output {
&rect_before_post, reply_line_ui(
&post_response.edit_response, &rect_before_post,
pfp_offset as f32, &p.edit_response,
&avail_rect, pfp_offset as f32,
ui, &avail_rect,
); ui,
);
}
// //
// NOTE(jb55): We add some space so that you can scroll to // NOTE(jb55): We add some space so that you can scroll to

View File

@@ -8,7 +8,7 @@ use notedeck_ui::{
nip51_set::{Nip51SetUiCache, Nip51SetWidget, Nip51SetWidgetAction, Nip51SetWidgetFlags}, nip51_set::{Nip51SetUiCache, Nip51SetWidget, Nip51SetWidgetAction, Nip51SetWidgetFlags},
}; };
use crate::{onboarding::Onboarding, ui::widgets::styled_button}; use crate::{nav::BodyResponse, onboarding::Onboarding, ui::widgets::styled_button};
/// Display Follow Packs for the user to choose from authors trusted by the Damus team /// Display Follow Packs for the user to choose from authors trusted by the Damus team
pub struct FollowPackOnboardingView<'a> { pub struct FollowPackOnboardingView<'a> {
@@ -56,17 +56,17 @@ impl<'a> FollowPackOnboardingView<'a> {
egui::Id::new("follow_pack_onboarding") egui::Id::new("follow_pack_onboarding")
} }
pub fn ui(&mut self, ui: &mut egui::Ui) -> Option<OnboardingResponse> { pub fn ui(&mut self, ui: &mut egui::Ui) -> BodyResponse<OnboardingResponse> {
let Some(follow_pack_state) = self.onboarding.get_follow_packs() else { let Some(follow_pack_state) = self.onboarding.get_follow_packs() else {
return Some(OnboardingResponse::FollowPacks( return BodyResponse::output(Some(OnboardingResponse::FollowPacks(
FollowPacksResponse::NoFollowPacks, FollowPacksResponse::NoFollowPacks,
)); )));
}; };
let max_height = ui.available_height() - 48.0; let max_height = ui.available_height() - 48.0;
let mut action = None; let mut action = None;
ScrollArea::vertical() let scroll_out = ScrollArea::vertical()
.id_salt(Self::scroll_id()) .id_salt(Self::scroll_id())
.max_height(max_height) .max_height(max_height)
.show(ui, |ui| { .show(ui, |ui| {
@@ -114,6 +114,6 @@ impl<'a> FollowPackOnboardingView<'a> {
} }
}); });
action BodyResponse::output(action).scroll_raw(scroll_out.id)
} }
} }

View File

@@ -7,6 +7,8 @@ use notedeck::{profile::unwrap_profile_url, tr, Images, Localization, NotedeckTe
use notedeck_ui::context_menu::{input_context, PasteBehavior}; use notedeck_ui::context_menu::{input_context, PasteBehavior};
use notedeck_ui::{profile::banner, ProfilePic}; use notedeck_ui::{profile::banner, ProfilePic};
use crate::nav::BodyResponse;
pub struct EditProfileView<'a> { pub struct EditProfileView<'a> {
state: &'a mut ProfileState, state: &'a mut ProfileState,
clipboard: &'a mut Clipboard, clipboard: &'a mut Clipboard,
@@ -34,8 +36,8 @@ impl<'a> EditProfileView<'a> {
} }
// return true to save // return true to save
pub fn ui(&mut self, ui: &mut egui::Ui) -> bool { pub fn ui(&mut self, ui: &mut egui::Ui) -> BodyResponse<bool> {
ScrollArea::vertical() let scroll_out = ScrollArea::vertical()
.id_salt(EditProfileView::scroll_id()) .id_salt(EditProfileView::scroll_id())
.stick_to_bottom(true) .stick_to_bottom(true)
.show(ui, |ui| { .show(ui, |ui| {
@@ -71,9 +73,9 @@ impl<'a> EditProfileView<'a> {
}); });
}); });
save Some(save)
}) });
.inner BodyResponse::scroll(scroll_out)
} }
fn inner(&mut self, ui: &mut egui::Ui, padding: f32) { fn inner(&mut self, ui: &mut egui::Ui, padding: f32) {

View File

@@ -10,6 +10,7 @@ use robius_open::Uri;
use tracing::error; use tracing::error;
use crate::{ use crate::{
nav::BodyResponse,
timeline::{TimelineCache, TimelineKind}, timeline::{TimelineCache, TimelineKind},
ui::timeline::{tabs_ui, TimelineTabView}, ui::timeline::{tabs_ui, TimelineTabView},
}; };
@@ -68,13 +69,16 @@ impl<'a, 'd> ProfileView<'a, 'd> {
egui::Id::new(("profile_scroll", col_id, profile_pubkey)) egui::Id::new(("profile_scroll", col_id, profile_pubkey))
} }
pub fn ui(&mut self, ui: &mut egui::Ui) -> Option<ProfileViewAction> { pub fn ui(&mut self, ui: &mut egui::Ui) -> BodyResponse<ProfileViewAction> {
let scroll_id = ProfileView::scroll_id(self.col_id, self.pubkey); let scroll_id = ProfileView::scroll_id(self.col_id, self.pubkey);
let scroll_area = ScrollArea::vertical().id_salt(scroll_id).animated(false); let scroll_area = ScrollArea::vertical().id_salt(scroll_id).animated(false);
let profile_timeline = self let Some(profile_timeline) = self
.timeline_cache .timeline_cache
.get_mut(&TimelineKind::Profile(*self.pubkey))?; .get_mut(&TimelineKind::Profile(*self.pubkey))
else {
return BodyResponse::none();
};
let output = scroll_area.show(ui, |ui| { let output = scroll_area.show(ui, |ui| {
let mut action = None; let mut action = None;
@@ -132,7 +136,7 @@ impl<'a, 'd> ProfileView<'a, 'd> {
// only allow front insert when the profile body is fully obstructed // only allow front insert when the profile body is fully obstructed
profile_timeline.enable_front_insert = output.inner.body_end_pos < ui.clip_rect().top(); profile_timeline.enable_front_insert = output.inner.body_end_pos < ui.clip_rect().top();
output.inner.action BodyResponse::output(output.inner.action).scroll_raw(output.id)
} }
} }

View File

@@ -1,5 +1,6 @@
use std::collections::HashMap; use std::collections::HashMap;
use crate::nav::BodyResponse;
use crate::ui::{Preview, PreviewConfig}; use crate::ui::{Preview, PreviewConfig};
use egui::{Align, Button, CornerRadius, Frame, Id, Layout, Margin, Rgba, RichText, Ui, Vec2}; use egui::{Align, Button, CornerRadius, Frame, Id, Layout, Margin, Rgba, RichText, Ui, Vec2};
use enostr::{RelayPool, RelayStatus}; use enostr::{RelayPool, RelayStatus};
@@ -17,9 +18,8 @@ pub struct RelayView<'a> {
} }
impl RelayView<'_> { impl RelayView<'_> {
pub fn ui(&mut self, ui: &mut egui::Ui) -> Option<RelayAction> { pub fn ui(&mut self, ui: &mut egui::Ui) -> BodyResponse<RelayAction> {
let mut action = None; let scroll_out = Frame::new()
Frame::new()
.inner_margin(Margin::symmetric(10, 0)) .inner_margin(Margin::symmetric(10, 0))
.show(ui, |ui| { .show(ui, |ui| {
ui.add_space(24.0); ui.add_space(24.0);
@@ -40,6 +40,7 @@ impl RelayView<'_> {
.scroll_bar_visibility(egui::scroll_area::ScrollBarVisibility::AlwaysHidden) .scroll_bar_visibility(egui::scroll_area::ScrollBarVisibility::AlwaysHidden)
.auto_shrink([false; 2]) .auto_shrink([false; 2])
.show(ui, |ui| { .show(ui, |ui| {
let mut action = None;
if let Some(relay_to_remove) = self.show_relays(ui) { if let Some(relay_to_remove) = self.show_relays(ui) {
action = Some(RelayAction::Remove(relay_to_remove)); action = Some(RelayAction::Remove(relay_to_remove));
} }
@@ -47,10 +48,12 @@ impl RelayView<'_> {
if let Some(relay_to_add) = self.show_add_relay_ui(ui) { if let Some(relay_to_add) = self.show_add_relay_ui(ui) {
action = Some(RelayAction::Add(relay_to_add)); action = Some(RelayAction::Add(relay_to_add));
} }
}); action
}); })
})
.inner;
action BodyResponse::scroll(scroll_out)
} }
pub fn scroll_id() -> egui::Id { pub fn scroll_id() -> egui::Id {

View File

@@ -3,6 +3,7 @@ use enostr::{NoteId, Pubkey};
use state::TypingType; use state::TypingType;
use crate::{ use crate::{
nav::BodyResponse,
timeline::{TimelineTab, TimelineUnits}, timeline::{TimelineTab, TimelineUnits},
ui::timeline::TimelineTabView, ui::timeline::TimelineTabView,
}; };
@@ -49,11 +50,11 @@ impl<'a, 'd> SearchView<'a, 'd> {
} }
} }
pub fn show(&mut self, ui: &mut egui::Ui) -> Option<NoteAction> { pub fn show(&mut self, ui: &mut egui::Ui) -> BodyResponse<NoteAction> {
padding(8.0, ui, |ui| self.show_impl(ui)).inner padding(8.0, ui, |ui| self.show_impl(ui)).inner
} }
pub fn show_impl(&mut self, ui: &mut egui::Ui) -> Option<NoteAction> { pub fn show_impl(&mut self, ui: &mut egui::Ui) -> BodyResponse<NoteAction> {
ui.spacing_mut().item_spacing = egui::vec2(0.0, 12.0); ui.spacing_mut().item_spacing = egui::vec2(0.0, 12.0);
let search_resp = search_box( let search_resp = search_box(
@@ -67,7 +68,7 @@ impl<'a, 'd> SearchView<'a, 'd> {
search_resp.process(self.query); search_resp.process(self.query);
let mut search_action = None; let mut search_action = None;
let mut note_action = None; let mut body_resp = BodyResponse::none();
match &self.query.state { match &self.query.state {
SearchState::New | SearchState::Navigating => {} SearchState::New | SearchState::Navigating => {}
SearchState::Typing(TypingType::Mention(mention_name)) => 's: { SearchState::Typing(TypingType::Mention(mention_name)) => 's: {
@@ -87,7 +88,11 @@ impl<'a, 'd> SearchView<'a, 'd> {
) )
.show_in_rect(ui.available_rect_before_wrap(), ui); .show_in_rect(ui.available_rect_before_wrap(), ui);
search_action = match search_res { let Some(res) = search_res.output else {
break 's;
};
search_action = match res {
MentionPickerResponse::SelectResult(Some(index)) => { MentionPickerResponse::SelectResult(Some(index)) => {
let Some(pk_bytes) = results.get(index) else { let Some(pk_bytes) = results.get(index) else {
break 's; break 's;
@@ -120,7 +125,7 @@ impl<'a, 'd> SearchView<'a, 'd> {
&mut self.query.notes, &mut self.query.notes,
); );
search_action = Some(SearchAction::Searched); search_action = Some(SearchAction::Searched);
note_action = self.show_search_results(ui); body_resp.insert(self.show_search_results(ui));
} }
SearchState::Searched => { SearchState::Searched => {
ui.label(tr_plural!( ui.label(tr_plural!(
@@ -131,7 +136,7 @@ impl<'a, 'd> SearchView<'a, 'd> {
self.query.notes.units.len(), // count self.query.notes.units.len(), // count
query = &self.query.string query = &self.query.string
)); ));
note_action = self.show_search_results(ui); body_resp.insert(self.show_search_results(ui));
} }
SearchState::Typing(TypingType::AutoSearch) => { SearchState::Typing(TypingType::AutoSearch) => {
ui.label(tr!( ui.label(tr!(
@@ -141,7 +146,7 @@ impl<'a, 'd> SearchView<'a, 'd> {
query = &self.query.string query = &self.query.string
)); ));
note_action = self.show_search_results(ui); body_resp.insert(self.show_search_results(ui));
} }
}; };
@@ -149,11 +154,11 @@ impl<'a, 'd> SearchView<'a, 'd> {
resp.process(self.query); resp.process(self.query);
} }
note_action body_resp
} }
fn show_search_results(&mut self, ui: &mut egui::Ui) -> Option<NoteAction> { fn show_search_results(&mut self, ui: &mut egui::Ui) -> BodyResponse<NoteAction> {
egui::ScrollArea::vertical() let scroll_out = egui::ScrollArea::vertical()
.id_salt(SearchView::scroll_id()) .id_salt(SearchView::scroll_id())
.show(ui, |ui| { .show(ui, |ui| {
TimelineTabView::new( TimelineTabView::new(
@@ -164,8 +169,9 @@ impl<'a, 'd> SearchView<'a, 'd> {
self.jobs, self.jobs,
) )
.show(ui) .show(ui)
}) });
.inner
BodyResponse::scroll(scroll_out)
} }
pub fn scroll_id() -> egui::Id { pub fn scroll_id() -> egui::Id {

View File

@@ -16,7 +16,11 @@ use notedeck_ui::{
AnimationHelper, NoteOptions, NoteView, AnimationHelper, NoteOptions, NoteView,
}; };
use crate::{nav::RouterAction, ui::account_login_view::eye_button, Damus, Route}; use crate::{
nav::{BodyResponse, RouterAction},
ui::account_login_view::eye_button,
Damus, Route,
};
const PREVIEW_NOTE_ID: &str = "note1edjc8ggj07hwv77g2405uh6j2jkk5aud22gktxrvc2wnre4vdwgqzlv2gw"; const PREVIEW_NOTE_ID: &str = "note1edjc8ggj07hwv77g2405uh6j2jkk5aud22gktxrvc2wnre4vdwgqzlv2gw";
@@ -638,13 +642,12 @@ impl<'a> SettingsView<'a> {
action action
} }
pub fn ui(&mut self, ui: &mut egui::Ui) -> Option<SettingsAction> { pub fn ui(&mut self, ui: &mut egui::Ui) -> BodyResponse<SettingsAction> {
let mut action: Option<SettingsAction> = None; let scroll_out = Frame::default()
Frame::default()
.inner_margin(Margin::symmetric(10, 10)) .inner_margin(Margin::symmetric(10, 10))
.show(ui, |ui| { .show(ui, |ui| {
ScrollArea::vertical().show(ui, |ui| { ScrollArea::vertical().show(ui, |ui| {
let mut action = None;
if let Some(new_action) = self.appearance_section(ui) { if let Some(new_action) = self.appearance_section(ui) {
action = Some(new_action); action = Some(new_action);
} }
@@ -670,10 +673,12 @@ impl<'a> SettingsView<'a> {
if let Some(new_action) = self.manage_relays_section(ui) { if let Some(new_action) = self.manage_relays_section(ui) {
action = Some(new_action); action = Some(new_action);
} }
}); action
}); })
})
.inner;
action BodyResponse::scroll(scroll_out)
} }
} }

View File

@@ -7,6 +7,7 @@ use notedeck::{NoteAction, NoteContext};
use notedeck_ui::note::NoteResponse; use notedeck_ui::note::NoteResponse;
use notedeck_ui::{NoteOptions, NoteView}; use notedeck_ui::{NoteOptions, NoteView};
use crate::nav::BodyResponse;
use crate::timeline::thread::{NoteSeenFlags, ParentState, Threads}; use crate::timeline::thread::{NoteSeenFlags, ParentState, Threads};
pub struct ThreadView<'a, 'd> { pub struct ThreadView<'a, 'd> {
@@ -42,7 +43,7 @@ impl<'a, 'd> ThreadView<'a, 'd> {
egui::Id::new(("threadscroll", selected_note_id, col)) egui::Id::new(("threadscroll", selected_note_id, col))
} }
pub fn ui(&mut self, ui: &mut egui::Ui) -> Option<NoteAction> { pub fn ui(&mut self, ui: &mut egui::Ui) -> BodyResponse<NoteAction> {
let txn = Transaction::new(self.note_context.ndb).expect("txn"); let txn = Transaction::new(self.note_context.ndb).expect("txn");
let scroll_id = ThreadView::scroll_id(self.selected_note_id, self.col); let scroll_id = ThreadView::scroll_id(self.selected_note_id, self.col);
@@ -60,6 +61,7 @@ impl<'a, 'd> ThreadView<'a, 'd> {
let output = scroll_area.show(ui, |ui| self.notes(ui, &txn)); let output = scroll_area.show(ui, |ui| self.notes(ui, &txn));
let out_id = output.id;
let mut resp = output.inner; let mut resp = output.inner;
if let Some(NoteAction::Note { if let Some(NoteAction::Note {
@@ -71,7 +73,7 @@ impl<'a, 'd> ThreadView<'a, 'd> {
*scroll_offset = output.state.offset.y; *scroll_offset = output.state.offset.y;
} }
resp BodyResponse::output(resp).scroll_raw(out_id)
} }
fn notes(&mut self, ui: &mut egui::Ui, txn: &Transaction) -> Option<NoteAction> { fn notes(&mut self, ui: &mut egui::Ui, txn: &Transaction) -> Option<NoteAction> {

View File

@@ -12,6 +12,7 @@ use notedeck_ui::{ProfilePic, ProfilePreview};
use std::f32::consts::PI; use std::f32::consts::PI;
use tracing::{error, warn}; use tracing::{error, warn};
use crate::nav::BodyResponse;
use crate::timeline::{ use crate::timeline::{
CompositeType, CompositeUnit, NoteUnit, ReactionUnit, RepostUnit, TimelineCache, TimelineKind, CompositeType, CompositeUnit, NoteUnit, ReactionUnit, RepostUnit, TimelineCache, TimelineKind,
TimelineTab, ViewFilter, TimelineTab, ViewFilter,
@@ -56,7 +57,7 @@ impl<'a, 'd> TimelineView<'a, 'd> {
} }
} }
pub fn ui(&mut self, ui: &mut egui::Ui) -> Option<NoteAction> { pub fn ui(&mut self, ui: &mut egui::Ui) -> BodyResponse<NoteAction> {
timeline_ui( timeline_ui(
ui, ui,
self.timeline_id, self.timeline_id,
@@ -94,7 +95,7 @@ fn timeline_ui(
jobs: &mut JobsCache, jobs: &mut JobsCache,
col: usize, col: usize,
scroll_to_top: bool, scroll_to_top: bool,
) -> Option<NoteAction> { ) -> BodyResponse<NoteAction> {
//padding(4.0, ui, |ui| ui.heading("Notifications")); //padding(4.0, ui, |ui| ui.heading("Notifications"));
/* /*
let font_id = egui::TextStyle::Body.resolve(ui.style()); let font_id = egui::TextStyle::Body.resolve(ui.style());
@@ -102,7 +103,9 @@ fn timeline_ui(
*/ */
let scroll_id = TimelineView::scroll_id(timeline_cache, timeline_id, col)?; let Some(scroll_id) = TimelineView::scroll_id(timeline_cache, timeline_id, col) else {
return BodyResponse::none();
};
{ {
let timeline = if let Some(timeline) = timeline_cache.get_mut(timeline_id) { let timeline = if let Some(timeline) = timeline_cache.get_mut(timeline_id) {
@@ -111,7 +114,7 @@ fn timeline_ui(
error!("tried to render timeline in column, but timeline was missing"); error!("tried to render timeline in column, but timeline was missing");
// TODO (jb55): render error when timeline is missing? // TODO (jb55): render error when timeline is missing?
// this shouldn't happen... // this shouldn't happen...
return None; return BodyResponse::none();
}; };
timeline.selected_view = tabs_ui( timeline.selected_view = tabs_ui(
@@ -204,7 +207,9 @@ fn timeline_ui(
.data_mut(|d| d.insert_temp(show_top_button_id, true)); .data_mut(|d| d.insert_temp(show_top_button_id, true));
} }
scroll_output.inner.or_else(|| { let scroll_id = scroll_output.id;
let action = scroll_output.inner.or_else(|| {
// if we're scrolling, return that as a response. We need this // if we're scrolling, return that as a response. We need this
// for auto-closing the side menu // for auto-closing the side menu
@@ -215,7 +220,9 @@ fn timeline_ui(
} else { } else {
None None
} }
}) });
BodyResponse::output(action).scroll_raw(scroll_id)
} }
fn goto_top_button(center: Pos2) -> impl egui::Widget { fn goto_top_button(center: Pos2) -> impl egui::Widget {