notedeck

One damus client to rule them all
git clone git://jb55.com/notedeck
Log | Files | Refs | README | LICENSE

commit c5fdff8f30c42dfd40bd5021d9c807c0bcc49bd2
parent 47ce825108cf53ba88d690b80cb000bb33c5685f
Author: kernelkind <kernelkind@gmail.com>
Date:   Tue,  9 Dec 2025 16:24:28 -0600

refactor(route): extract Router to notedeck core

Signed-off-by: kernelkind <kernelkind@gmail.com>

Diffstat:
Mcrates/notedeck/src/lib.rs | 2+-
Mcrates/notedeck/src/route.rs | 71+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcrates/notedeck_columns/src/column.rs | 12++++++------
Mcrates/notedeck_columns/src/nav.rs | 16++++++++--------
Mcrates/notedeck_columns/src/route.rs | 97++++++++++++++++++++++++++++++++++++++++---------------------------------------
5 files changed, 135 insertions(+), 63 deletions(-)

diff --git a/crates/notedeck/src/lib.rs b/crates/notedeck/src/lib.rs @@ -79,7 +79,7 @@ pub use profile::*; pub use relay_debug::RelayDebugView; pub use relayspec::RelaySpec; pub use result::Result; -pub use route::DrawerRouter; +pub use route::{DrawerRouter, Router}; pub use storage::{AccountStorage, DataPath, DataPathType, Directory}; pub use style::NotedeckTextStyle; pub use theme::ColorTheme; diff --git a/crates/notedeck/src/route.rs b/crates/notedeck/src/route.rs @@ -29,3 +29,74 @@ impl DrawerRouter { self.drawer_focused = true; } } + +#[derive(Clone, Debug)] +pub struct Router<R: Clone> { + pub routes: Vec<R>, + pub returning: bool, + pub navigating: bool, +} + +impl<R: Clone> Router<R> { + pub fn new(routes: Vec<R>) -> Self { + if routes.is_empty() { + panic!("routes can't be empty") + } + let returning = false; + let navigating = false; + + Self { + routes, + returning, + navigating, + } + } + + pub fn route_to(&mut self, route: R) { + self.navigating = true; + self.routes.push(route); + } + + /// Go back, start the returning process + pub fn go_back(&mut self) -> Option<R> { + if self.returning || self.routes.len() == 1 { + return None; + } + self.returning = true; + + if self.routes.len() == 1 { + return None; + } + + self.prev().cloned() + } + + pub fn pop(&mut self) -> Option<R> { + if self.routes.len() == 1 { + return None; + } + + self.returning = false; + self.routes.pop() + } + + pub fn top(&self) -> &R { + self.routes.last().expect("routes can't be empty") + } + + pub fn prev(&self) -> Option<&R> { + self.routes.get(self.routes.len() - 2) + } + + pub fn routes(&self) -> &Vec<R> { + &self.routes + } + + pub fn len(&self) -> usize { + self.routes.len() + } + + pub fn is_empty(&self) -> bool { + self.routes.is_empty() + } +} diff --git a/crates/notedeck_columns/src/column.rs b/crates/notedeck_columns/src/column.rs @@ -1,6 +1,6 @@ use crate::{ actionbar::TimelineOpenResult, - route::{Route, Router, SingletonRouter}, + route::{ColumnsRouter, Route, SingletonRouter}, timeline::{Timeline, TimelineCache, TimelineKind}, }; use enostr::RelayPool; @@ -11,24 +11,24 @@ use tracing::warn; #[derive(Clone, Debug)] pub struct Column { - pub router: Router<Route>, + pub router: ColumnsRouter<Route>, pub sheet_router: SingletonRouter<Route>, } impl Column { pub fn new(routes: Vec<Route>) -> Self { - let router = Router::new(routes); + let router = ColumnsRouter::new(routes); Column { router, sheet_router: SingletonRouter::default(), } } - pub fn router(&self) -> &Router<Route> { + pub fn router(&self) -> &ColumnsRouter<Route> { &self.router } - pub fn router_mut(&mut self) -> &mut Router<Route> { + pub fn router_mut(&mut self) -> &mut ColumnsRouter<Route> { &mut self.router } } @@ -164,7 +164,7 @@ impl Columns { // Get the first router in the columns if there are columns present. // Otherwise, create a new column picker and return the router - pub fn get_selected_router(&mut self) -> &mut Router<Route> { + pub fn get_selected_router(&mut self) -> &mut ColumnsRouter<Route> { self.ensure_column(); self.selected_mut().router_mut() } diff --git a/crates/notedeck_columns/src/nav.rs b/crates/notedeck_columns/src/nav.rs @@ -7,7 +7,7 @@ use crate::{ options::AppOptions, profile::{ProfileAction, SaveProfileChanges}, repost::RepostAction, - route::{Route, Router, SingletonRouter}, + route::{ColumnsRouter, Route, SingletonRouter}, subscriptions::Subscriptions, timeline::{ kind::ListKind, @@ -316,7 +316,7 @@ fn process_nav_resp( .columns_mut(ctx.i18n, ctx.accounts) .column_mut(col) .router_mut(); - cur_router.navigating = false; + cur_router.navigating_mut(false); if cur_router.is_replacing() { cur_router.remove_previous_routes(); } @@ -431,7 +431,7 @@ pub enum RouterType { Stack, } -fn go_back(stack: &mut Router<Route>, sheet: &mut SingletonRouter<Route>) { +fn go_back(stack: &mut ColumnsRouter<Route>, sheet: &mut SingletonRouter<Route>) { if sheet.route().is_some() { sheet.go_back(); } else { @@ -442,7 +442,7 @@ fn go_back(stack: &mut Router<Route>, sheet: &mut SingletonRouter<Route>) { impl RouterAction { pub fn process_router_action( self, - stack_router: &mut Router<Route>, + stack_router: &mut ColumnsRouter<Route>, sheet_router: &mut SingletonRouter<Route>, ) -> Option<ProcessNavResult> { match self { @@ -802,7 +802,7 @@ fn render_nav_body( get_active_columns_mut(note_context.i18n, ctx.accounts, &mut app.decks_cache) .column(col) .router() - .navigating; + .navigating(); let draft = app.drafts.compose_mut(); if navigating { @@ -839,7 +839,7 @@ fn render_nav_body( get_active_columns_mut(note_context.i18n, ctx.accounts, &mut app.decks_cache) .column(col) .router() - .navigating; + .navigating(); let search_buffer = app.view_state.searches.entry(id).or_default(); let txn = Transaction::new(ctx.ndb).expect("txn"); @@ -1246,13 +1246,13 @@ pub fn render_nav( app.columns_mut(ctx.i18n, ctx.accounts) .column_mut(col) .router_mut() - .navigating, + .navigating(), ) .returning( app.columns_mut(ctx.i18n, ctx.accounts) .column_mut(col) .router_mut() - .returning, + .returning(), ) .animate_transitions(ctx.settings.get_settings_mut().animate_nav_transitions) .show_mut(ui, |ui, render_type, nav| match render_type { diff --git a/crates/notedeck_columns/src/route.rs b/crates/notedeck_columns/src/route.rs @@ -1,6 +1,6 @@ use egui_nav::Percent; use enostr::{NoteId, Pubkey}; -use notedeck::{tr, Localization, NoteZapTargetOwned, RootNoteIdBuf, WalletType}; +use notedeck::{tr, Localization, NoteZapTargetOwned, RootNoteIdBuf, Router, WalletType}; use std::ops::Range; use crate::{ @@ -418,10 +418,8 @@ impl Route { // TODO: add this to egui-nav so we don't have to deal with returning // and navigating headaches #[derive(Clone, Debug)] -pub struct Router<R: Clone> { - routes: Vec<R>, - pub returning: bool, - pub navigating: bool, +pub struct ColumnsRouter<R: Clone> { + router_internal: Router<R>, replacing: bool, forward_stack: Vec<R>, @@ -429,18 +427,15 @@ pub struct Router<R: Clone> { overlay_ranges: Vec<Range<usize>>, } -impl<R: Clone> Router<R> { +impl<R: Clone> ColumnsRouter<R> { pub fn new(routes: Vec<R>) -> Self { if routes.is_empty() { panic!("routes can't be empty") } - let returning = false; - let navigating = false; let replacing = false; - Router { - routes, - returning, - navigating, + let router_internal = Router::new(routes); + ColumnsRouter { + router_internal, replacing, forward_stack: Vec::new(), overlay_ranges: Vec::new(), @@ -448,9 +443,8 @@ impl<R: Clone> Router<R> { } pub fn route_to(&mut self, route: R) { - self.navigating = true; + self.router_internal.route_to(route); self.forward_stack.clear(); - self.routes.push(route); } pub fn route_to_overlaid(&mut self, route: R) { @@ -465,17 +459,15 @@ impl<R: Clone> Router<R> { // Route to R. Then when it is successfully placed, should call `remove_previous_routes` to remove all previous routes pub fn route_to_replaced(&mut self, route: R) { - self.navigating = true; self.replacing = true; - self.routes.push(route); + self.router_internal.route_to(route); } /// Go back, start the returning process pub fn go_back(&mut self) -> Option<R> { - if self.returning || self.routes.len() == 1 { + if self.router_internal.returning || self.router_internal.len() == 1 { return None; } - self.returning = true; if let Some(range) = self.overlay_ranges.pop() { tracing::debug!("Going back, found overlay: {:?}", range); @@ -484,17 +476,12 @@ impl<R: Clone> Router<R> { tracing::debug!("Going back, no overlay"); } - if self.routes.len() == 1 { - return None; - } - - self.prev().cloned() + self.router_internal.go_back() } pub fn go_forward(&mut self) -> bool { if let Some(route) = self.forward_stack.pop() { - self.navigating = true; - self.routes.push(route); + self.router_internal.route_to(route); true } else { false @@ -503,7 +490,7 @@ impl<R: Clone> Router<R> { /// Pop a route, should only be called on a NavRespose::Returned reseponse pub fn pop(&mut self) -> Option<R> { - if self.routes.len() == 1 { + if self.router_internal.len() == 1 { return None; } @@ -512,7 +499,7 @@ impl<R: Clone> Router<R> { break 's false; }; - if last_range.end != self.routes.len() { + if last_range.end != self.router_internal.len() { break 's false; } @@ -525,30 +512,27 @@ impl<R: Clone> Router<R> { true }; - self.returning = false; - let popped = self.routes.pop(); + let popped = self.router_internal.pop()?; if !is_overlay { - if let Some(ref route) = popped { - self.forward_stack.push(route.clone()); - } + self.forward_stack.push(popped.clone()); } - popped + Some(popped) } pub fn remove_previous_routes(&mut self) { - let num_routes = self.routes.len(); + let num_routes = self.router_internal.len(); if num_routes <= 1 { return; } - self.returning = false; + self.router_internal.returning = false; self.replacing = false; - self.routes.drain(..num_routes - 1); + self.router_internal.routes.drain(..num_routes - 1); } /// Removes all routes in the overlay besides the last fn remove_overlay(&mut self, overlay_range: Range<usize>) { - let num_routes = self.routes.len(); + let num_routes = self.router_internal.routes.len(); if num_routes <= 1 { return; } @@ -557,7 +541,8 @@ impl<R: Clone> Router<R> { return; } - self.routes + self.router_internal + .routes .drain(overlay_range.start..overlay_range.end - 1); } @@ -569,34 +554,50 @@ impl<R: Clone> Router<R> { let mut overlaying_active = None; let mut binding = self.overlay_ranges.last_mut(); if let Some(range) = &mut binding { - if range.end == self.routes.len() - 1 { + if range.end == self.router_internal.len() - 1 { overlaying_active = Some(range); } }; if let Some(range) = overlaying_active { - range.end = self.routes.len(); + range.end = self.router_internal.len(); } else { - let new_range = self.routes.len() - 1..self.routes.len(); + let new_range = self.router_internal.len() - 1..self.router_internal.len(); self.overlay_ranges.push(new_range); } } fn new_overlay(&mut self) { - let new_range = self.routes.len() - 1..self.routes.len(); + let new_range = self.router_internal.len() - 1..self.router_internal.len(); self.overlay_ranges.push(new_range); } - pub fn top(&self) -> &R { - self.routes.last().expect("routes can't be empty") + pub fn routes(&self) -> &Vec<R> { + self.router_internal.routes() } - pub fn prev(&self) -> Option<&R> { - self.routes.get(self.routes.len() - 2) + pub fn navigating(&self) -> bool { + self.router_internal.navigating } - pub fn routes(&self) -> &Vec<R> { - &self.routes + pub fn navigating_mut(&mut self, new: bool) { + self.router_internal.navigating = new; + } + + pub fn returning(&self) -> bool { + self.router_internal.returning + } + + pub fn returning_mut(&mut self, new: bool) { + self.router_internal.returning = new; + } + + pub fn top(&self) -> &R { + self.router_internal.top() + } + + pub fn prev(&self) -> Option<&R> { + self.router_internal.prev() } }