columns: enable toolbar scroll to top
Fixes: https://github.com/damus-io/notedeck/issues/969 Signed-off-by: William Casarin <jb55@jb55.com>
This commit is contained in:
@@ -6,7 +6,9 @@ use egui::{vec2, Button, Color32, Label, Layout, Rect, RichText, ThemePreference
|
|||||||
use egui_extras::{Size, StripBuilder};
|
use egui_extras::{Size, StripBuilder};
|
||||||
use nostrdb::{ProfileRecord, Transaction};
|
use nostrdb::{ProfileRecord, Transaction};
|
||||||
use notedeck::{App, AppAction, AppContext, NotedeckTextStyle, UserAccount, WalletType};
|
use notedeck::{App, AppAction, AppContext, NotedeckTextStyle, UserAccount, WalletType};
|
||||||
use notedeck_columns::{timeline::kind::ListKind, timeline::TimelineKind, Damus};
|
use notedeck_columns::{
|
||||||
|
column::SelectionResult, timeline::kind::ListKind, timeline::TimelineKind, Damus,
|
||||||
|
};
|
||||||
|
|
||||||
use notedeck_dave::{Dave, DaveAvatar};
|
use notedeck_dave::{Dave, DaveAvatar};
|
||||||
use notedeck_ui::{app_images, AnimationHelper, ProfilePic};
|
use notedeck_ui::{app_images, AnimationHelper, ProfilePic};
|
||||||
@@ -61,11 +63,26 @@ impl ChromePanelAction {
|
|||||||
fn columns_switch(ctx: &AppContext, chrome: &mut Chrome, kind: &TimelineKind) {
|
fn columns_switch(ctx: &AppContext, chrome: &mut Chrome, kind: &TimelineKind) {
|
||||||
chrome.switch_to_columns();
|
chrome.switch_to_columns();
|
||||||
|
|
||||||
if let Some(active_columns) = chrome
|
let Some(columns_app) = chrome.get_columns_app() else {
|
||||||
.get_columns()
|
return;
|
||||||
.and_then(|cols| cols.decks_cache.active_columns_mut(ctx.accounts))
|
};
|
||||||
{
|
|
||||||
active_columns.select_by_kind(kind)
|
if let Some(active_columns) = columns_app.decks_cache.active_columns_mut(ctx.accounts) {
|
||||||
|
match active_columns.select_by_kind(kind) {
|
||||||
|
SelectionResult::NewSelection(_index) => {
|
||||||
|
// great! no need to go to top yet
|
||||||
|
}
|
||||||
|
|
||||||
|
SelectionResult::AlreadySelected(_n) => {
|
||||||
|
// we already selected this, so scroll to top
|
||||||
|
columns_app.scroll_to_top();
|
||||||
|
}
|
||||||
|
|
||||||
|
SelectionResult::Failed => {
|
||||||
|
// oh no, something went wrong
|
||||||
|
// TODO(jb55): handle tab selection failure
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,7 +90,7 @@ impl ChromePanelAction {
|
|||||||
chrome.switch_to_columns();
|
chrome.switch_to_columns();
|
||||||
|
|
||||||
if let Some(c) = chrome
|
if let Some(c) = chrome
|
||||||
.get_columns()
|
.get_columns_app()
|
||||||
.and_then(|columns| columns.decks_cache.selected_column_mut(ctx.accounts))
|
.and_then(|columns| columns.decks_cache.selected_column_mut(ctx.accounts))
|
||||||
{
|
{
|
||||||
if c.router().routes().iter().any(|r| r == &route) {
|
if c.router().routes().iter().any(|r| r == &route) {
|
||||||
@@ -155,7 +172,7 @@ impl Chrome {
|
|||||||
self.apps.push(app);
|
self.apps.push(app);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_columns(&mut self) -> Option<&mut Damus> {
|
fn get_columns_app(&mut self) -> Option<&mut Damus> {
|
||||||
for app in &mut self.apps {
|
for app in &mut self.apps {
|
||||||
if let NotedeckApp::Columns(cols) = app {
|
if let NotedeckApp::Columns(cols) = app {
|
||||||
return Some(cols);
|
return Some(cols);
|
||||||
@@ -632,7 +649,7 @@ fn chrome_handle_app_action(
|
|||||||
|
|
||||||
AppAction::Note(note_action) => {
|
AppAction::Note(note_action) => {
|
||||||
chrome.switch_to_columns();
|
chrome.switch_to_columns();
|
||||||
let Some(columns) = chrome.get_columns() else {
|
let Some(columns) = chrome.get_columns_app() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -42,6 +42,18 @@ pub struct Columns {
|
|||||||
pub selected: i32,
|
pub selected: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// When selecting columns, return what happened
|
||||||
|
pub enum SelectionResult {
|
||||||
|
/// We're already selecting that
|
||||||
|
AlreadySelected(usize),
|
||||||
|
|
||||||
|
/// New selection success!
|
||||||
|
NewSelection(usize),
|
||||||
|
|
||||||
|
/// Failed to make a selection
|
||||||
|
Failed,
|
||||||
|
}
|
||||||
|
|
||||||
impl Columns {
|
impl Columns {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Columns::default()
|
Columns::default()
|
||||||
@@ -60,20 +72,25 @@ impl Columns {
|
|||||||
/// Select the column based on the timeline kind.
|
/// Select the column based on the timeline kind.
|
||||||
///
|
///
|
||||||
/// TODO: add timeline if missing?
|
/// TODO: add timeline if missing?
|
||||||
pub fn select_by_kind(&mut self, kind: &TimelineKind) {
|
pub fn select_by_kind(&mut self, kind: &TimelineKind) -> SelectionResult {
|
||||||
for (i, col) in self.columns.iter().enumerate() {
|
for (i, col) in self.columns.iter().enumerate() {
|
||||||
for route in col.router().routes() {
|
for route in col.router().routes() {
|
||||||
if let Some(timeline) = route.timeline_id() {
|
if let Some(timeline) = route.timeline_id() {
|
||||||
if timeline == kind {
|
if timeline == kind {
|
||||||
tracing::info!("selecting {kind:?} column");
|
tracing::info!("selecting {kind:?} column");
|
||||||
self.select_column(i as i32);
|
if self.selected as usize == i {
|
||||||
return;
|
return SelectionResult::AlreadySelected(i);
|
||||||
|
} else {
|
||||||
|
self.select_column(i as i32);
|
||||||
|
return SelectionResult::NewSelection(i);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tracing::error!("failed to select {kind:?} column");
|
tracing::error!("failed to select {kind:?} column");
|
||||||
|
SelectionResult::Failed
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_new_timeline_column(
|
pub fn add_new_timeline_column(
|
||||||
|
|||||||
@@ -40,6 +40,10 @@ impl DecksCache {
|
|||||||
self.active_columns(accounts).and_then(|ad| ad.selected())
|
self.active_columns(accounts).and_then(|ad| ad.selected())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn selected_column_index(&self, accounts: ¬edeck::Accounts) -> Option<usize> {
|
||||||
|
self.active_columns(accounts).map(|ad| ad.selected as usize)
|
||||||
|
}
|
||||||
|
|
||||||
/// Gets a mutable reference to the active columns
|
/// Gets a mutable reference to the active columns
|
||||||
pub fn active_columns_mut(&mut self, accounts: ¬edeck::Accounts) -> Option<&mut Columns> {
|
pub fn active_columns_mut(&mut self, accounts: ¬edeck::Accounts) -> Option<&mut Columns> {
|
||||||
let account = accounts.get_selected_account();
|
let account = accounts.get_selected_account();
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ pub mod actionbar;
|
|||||||
pub mod app_creation;
|
pub mod app_creation;
|
||||||
mod app_style;
|
mod app_style;
|
||||||
mod args;
|
mod args;
|
||||||
mod column;
|
pub mod column;
|
||||||
mod deck_state;
|
mod deck_state;
|
||||||
mod decks;
|
mod decks;
|
||||||
mod draft;
|
mod draft;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ use crate::{
|
|||||||
column::ColumnsAction,
|
column::ColumnsAction,
|
||||||
deck_state::DeckState,
|
deck_state::DeckState,
|
||||||
decks::{Deck, DecksAction, DecksCache},
|
decks::{Deck, DecksAction, DecksCache},
|
||||||
|
options::AppOptions,
|
||||||
profile::{ProfileAction, SaveProfileChanges},
|
profile::{ProfileAction, SaveProfileChanges},
|
||||||
route::{Route, Router, SingletonRouter},
|
route::{Route, Router, SingletonRouter},
|
||||||
timeline::{
|
timeline::{
|
||||||
@@ -496,17 +497,34 @@ fn render_nav_body(
|
|||||||
current_account_has_wallet: get_current_wallet(ctx.accounts, ctx.global_wallet).is_some(),
|
current_account_has_wallet: get_current_wallet(ctx.accounts, ctx.global_wallet).is_some(),
|
||||||
};
|
};
|
||||||
match top {
|
match top {
|
||||||
Route::Timeline(kind) => render_timeline_route(
|
Route::Timeline(kind) => {
|
||||||
&mut app.timeline_cache,
|
// did something request scroll to top for the selection column?
|
||||||
ctx.accounts,
|
let scroll_to_top = app
|
||||||
kind,
|
.decks_cache
|
||||||
col,
|
.selected_column_index(ctx.accounts)
|
||||||
app.note_options,
|
.is_some_and(|ind| ind == col)
|
||||||
depth,
|
&& app.options.contains(AppOptions::ScrollToTop);
|
||||||
ui,
|
|
||||||
&mut note_context,
|
let nav_action = render_timeline_route(
|
||||||
&mut app.jobs,
|
&mut app.timeline_cache,
|
||||||
),
|
ctx.accounts,
|
||||||
|
kind,
|
||||||
|
col,
|
||||||
|
app.note_options,
|
||||||
|
depth,
|
||||||
|
ui,
|
||||||
|
&mut note_context,
|
||||||
|
&mut app.jobs,
|
||||||
|
scroll_to_top,
|
||||||
|
);
|
||||||
|
|
||||||
|
// always clear the scroll_to_top request
|
||||||
|
if scroll_to_top {
|
||||||
|
app.options.remove(AppOptions::ScrollToTop);
|
||||||
|
}
|
||||||
|
|
||||||
|
nav_action
|
||||||
|
}
|
||||||
Route::Thread(selection) => render_thread_route(
|
Route::Thread(selection) => render_thread_route(
|
||||||
&mut app.threads,
|
&mut app.threads,
|
||||||
ctx.accounts,
|
ctx.accounts,
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ pub fn render_timeline_route(
|
|||||||
ui: &mut egui::Ui,
|
ui: &mut egui::Ui,
|
||||||
note_context: &mut NoteContext,
|
note_context: &mut NoteContext,
|
||||||
jobs: &mut JobsCache,
|
jobs: &mut JobsCache,
|
||||||
|
scroll_to_top: bool,
|
||||||
) -> Option<RenderNavAction> {
|
) -> Option<RenderNavAction> {
|
||||||
match kind {
|
match kind {
|
||||||
TimelineKind::List(_)
|
TimelineKind::List(_)
|
||||||
@@ -39,6 +40,7 @@ pub fn render_timeline_route(
|
|||||||
jobs,
|
jobs,
|
||||||
col,
|
col,
|
||||||
)
|
)
|
||||||
|
.scroll_to_top(scroll_to_top)
|
||||||
.ui(ui);
|
.ui(ui);
|
||||||
|
|
||||||
note_action.map(RenderNavAction::NoteAction)
|
note_action.map(RenderNavAction::NoteAction)
|
||||||
@@ -69,6 +71,7 @@ pub fn render_timeline_route(
|
|||||||
jobs,
|
jobs,
|
||||||
col,
|
col,
|
||||||
)
|
)
|
||||||
|
.scroll_to_top(scroll_to_top)
|
||||||
.ui(ui);
|
.ui(ui);
|
||||||
|
|
||||||
note_action.map(RenderNavAction::NoteAction)
|
note_action.map(RenderNavAction::NoteAction)
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ pub struct TimelineView<'a, 'd> {
|
|||||||
cur_acc: &'a KeypairUnowned<'a>,
|
cur_acc: &'a KeypairUnowned<'a>,
|
||||||
jobs: &'a mut JobsCache,
|
jobs: &'a mut JobsCache,
|
||||||
col: usize,
|
col: usize,
|
||||||
|
scroll_to_top: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'd> TimelineView<'a, 'd> {
|
impl<'a, 'd> TimelineView<'a, 'd> {
|
||||||
@@ -40,6 +41,7 @@ impl<'a, 'd> TimelineView<'a, 'd> {
|
|||||||
col: usize,
|
col: usize,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let reverse = false;
|
let reverse = false;
|
||||||
|
let scroll_to_top = false;
|
||||||
TimelineView {
|
TimelineView {
|
||||||
timeline_id,
|
timeline_id,
|
||||||
timeline_cache,
|
timeline_cache,
|
||||||
@@ -50,6 +52,7 @@ impl<'a, 'd> TimelineView<'a, 'd> {
|
|||||||
cur_acc,
|
cur_acc,
|
||||||
jobs,
|
jobs,
|
||||||
col,
|
col,
|
||||||
|
scroll_to_top,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,9 +68,15 @@ impl<'a, 'd> TimelineView<'a, 'd> {
|
|||||||
self.cur_acc,
|
self.cur_acc,
|
||||||
self.jobs,
|
self.jobs,
|
||||||
self.col,
|
self.col,
|
||||||
|
self.scroll_to_top,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn scroll_to_top(mut self, enable: bool) -> Self {
|
||||||
|
self.scroll_to_top = enable;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub fn reversed(mut self) -> Self {
|
pub fn reversed(mut self) -> Self {
|
||||||
self.reverse = true;
|
self.reverse = true;
|
||||||
self
|
self
|
||||||
@@ -86,6 +95,7 @@ fn timeline_ui(
|
|||||||
cur_acc: &KeypairUnowned,
|
cur_acc: &KeypairUnowned,
|
||||||
jobs: &mut JobsCache,
|
jobs: &mut JobsCache,
|
||||||
col: usize,
|
col: usize,
|
||||||
|
scroll_to_top: bool,
|
||||||
) -> Option<NoteAction> {
|
) -> Option<NoteAction> {
|
||||||
//padding(4.0, ui, |ui| ui.heading("Notifications"));
|
//padding(4.0, ui, |ui| ui.heading("Notifications"));
|
||||||
/*
|
/*
|
||||||
@@ -152,6 +162,11 @@ fn timeline_ui(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// chrome can ask to scroll to top as well via an app option
|
||||||
|
if scroll_to_top {
|
||||||
|
scroll_area = scroll_area.vertical_scroll_offset(0.0);
|
||||||
|
}
|
||||||
|
|
||||||
let scroll_output = scroll_area.show(ui, |ui| {
|
let scroll_output = scroll_area.show(ui, |ui| {
|
||||||
let timeline = if let Some(timeline) = timeline_cache.timelines.get(timeline_id) {
|
let timeline = if let Some(timeline) = timeline_cache.timelines.get(timeline_id) {
|
||||||
timeline
|
timeline
|
||||||
|
|||||||
Reference in New Issue
Block a user