notedeck

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

column.rs (7759B)


      1 use crate::{
      2     actionbar::TimelineOpenResult,
      3     route::{ColumnsRouter, Route, SingletonRouter},
      4     timeline::{Timeline, TimelineCache, TimelineKind},
      5 };
      6 use enostr::Pubkey;
      7 use nostrdb::{Ndb, Transaction};
      8 use notedeck::{NoteCache, ScopedSubApi};
      9 use std::iter::Iterator;
     10 use tracing::warn;
     11 
     12 #[derive(Clone, Debug)]
     13 pub struct Column {
     14     pub router: ColumnsRouter<Route>,
     15     pub sheet_router: SingletonRouter<Route>,
     16 }
     17 
     18 impl Column {
     19     pub fn new(routes: Vec<Route>) -> Self {
     20         let router = ColumnsRouter::new(routes);
     21         Column {
     22             router,
     23             sheet_router: SingletonRouter::default(),
     24         }
     25     }
     26 
     27     pub fn router(&self) -> &ColumnsRouter<Route> {
     28         &self.router
     29     }
     30 
     31     pub fn router_mut(&mut self) -> &mut ColumnsRouter<Route> {
     32         &mut self.router
     33     }
     34 }
     35 
     36 #[derive(Default, Debug)]
     37 pub struct Columns {
     38     /// Columns are simply routers into settings, timelines, etc
     39     columns: Vec<Column>,
     40 
     41     /// The selected column for key navigation
     42     pub selected: i32,
     43 }
     44 
     45 /// When selecting columns, return what happened
     46 pub enum SelectionResult {
     47     /// We're already selecting that
     48     AlreadySelected(usize),
     49 
     50     /// New selection success!
     51     NewSelection(usize),
     52 
     53     /// Failed to make a selection
     54     Failed,
     55 }
     56 
     57 impl Columns {
     58     pub fn new() -> Self {
     59         Columns::default()
     60     }
     61 
     62     /// Choose which column is selected. If in narrow mode, this
     63     /// decides which column to render in the main view
     64     pub fn select_column(&mut self, index: i32) {
     65         let len = self.columns.len();
     66 
     67         if index < (len as i32) {
     68             self.selected = index;
     69         }
     70     }
     71 
     72     /// Select the column based on the timeline kind.
     73     ///
     74     /// TODO: add timeline if missing?
     75     pub fn select_by_route(&mut self, desired_route: Route) -> SelectionResult {
     76         for (i, col) in self.columns.iter().enumerate() {
     77             for route in col.router().routes() {
     78                 if *route == desired_route {
     79                     if self.selected as usize == i {
     80                         return SelectionResult::AlreadySelected(i);
     81                     } else {
     82                         self.select_column(i as i32);
     83                         return SelectionResult::NewSelection(i);
     84                     }
     85                 }
     86             }
     87         }
     88 
     89         if matches!(&desired_route, Route::Timeline(_))
     90             || matches!(&desired_route, Route::Thread(_))
     91         {
     92             // these require additional handling to add state
     93             tracing::error!("failed to select {desired_route:?} column");
     94             return SelectionResult::Failed;
     95         }
     96 
     97         self.add_column(Column::new(vec![desired_route]));
     98 
     99         let selected_index = self.columns.len() - 1;
    100         self.select_column(selected_index as i32);
    101         SelectionResult::NewSelection(selected_index)
    102     }
    103 
    104     #[allow(clippy::too_many_arguments)]
    105     pub fn add_new_timeline_column(
    106         &mut self,
    107         timeline_cache: &mut TimelineCache,
    108         txn: &Transaction,
    109         ndb: &Ndb,
    110         note_cache: &mut NoteCache,
    111         scoped_subs: &mut ScopedSubApi<'_, '_>,
    112         kind: &TimelineKind,
    113         account_pk: Pubkey,
    114     ) -> Option<TimelineOpenResult> {
    115         self.columns
    116             .push(Column::new(vec![Route::timeline(kind.to_owned())]));
    117         timeline_cache.open(ndb, note_cache, txn, scoped_subs, kind, account_pk, false)
    118     }
    119 
    120     pub fn new_column_picker(&mut self) {
    121         self.add_column(Column::new(vec![Route::AddColumn(
    122             crate::ui::add_column::AddColumnRoute::Base,
    123         )]));
    124     }
    125 
    126     pub fn insert_intermediary_routes(
    127         &mut self,
    128         timeline_cache: &mut TimelineCache,
    129         account_pk: Pubkey,
    130         intermediary_routes: Vec<IntermediaryRoute>,
    131     ) {
    132         let routes = intermediary_routes
    133             .into_iter()
    134             .map(|r| match r {
    135                 IntermediaryRoute::Timeline(timeline) => {
    136                     let route = Route::timeline(timeline.kind.clone());
    137                     timeline_cache.insert(timeline.kind.clone(), account_pk, *timeline);
    138                     route
    139                 }
    140                 IntermediaryRoute::Route(route) => route,
    141             })
    142             .collect();
    143 
    144         self.columns.push(Column::new(routes));
    145     }
    146 
    147     #[inline]
    148     pub fn add_column_at(&mut self, column: Column, index: u32) {
    149         self.columns.insert(index as usize, column);
    150     }
    151 
    152     #[inline]
    153     pub fn add_column(&mut self, column: Column) {
    154         self.columns.push(column);
    155     }
    156 
    157     #[inline]
    158     pub fn columns_mut(&mut self) -> &mut Vec<Column> {
    159         &mut self.columns
    160     }
    161 
    162     #[inline]
    163     pub fn num_columns(&self) -> usize {
    164         self.columns.len()
    165     }
    166 
    167     // Get the first router in the columns if there are columns present.
    168     // Otherwise, create a new column picker and return the router
    169     pub fn get_selected_router(&mut self) -> &mut ColumnsRouter<Route> {
    170         self.ensure_column();
    171         self.selected_mut().router_mut()
    172     }
    173 
    174     #[inline]
    175     pub fn column(&self, ind: usize) -> &Column {
    176         &self.columns[ind]
    177     }
    178 
    179     #[inline]
    180     pub fn columns(&self) -> &[Column] {
    181         &self.columns
    182     }
    183 
    184     #[inline]
    185     pub fn selected(&self) -> Option<&Column> {
    186         if self.columns.is_empty() {
    187             return None;
    188         }
    189         Some(&self.columns[self.selected as usize])
    190     }
    191 
    192     // TODO(jb55): switch to non-empty container for columns?
    193     fn ensure_column(&mut self) {
    194         if self.columns.is_empty() {
    195             self.new_column_picker();
    196         }
    197     }
    198 
    199     /// Get the selected column. If you're looking to route something
    200     /// and you're not sure which one to choose, use this one
    201     #[inline]
    202     pub fn selected_mut(&mut self) -> &mut Column {
    203         self.ensure_column();
    204         assert!(self.selected < self.columns.len() as i32);
    205         &mut self.columns[self.selected as usize]
    206     }
    207 
    208     #[inline]
    209     pub fn column_mut(&mut self, ind: usize) -> &mut Column {
    210         self.ensure_column();
    211         &mut self.columns[ind]
    212     }
    213 
    214     pub fn select_down(&mut self) {
    215         warn!("todo: implement select_down");
    216     }
    217 
    218     pub fn select_up(&mut self) {
    219         warn!("todo: implement select_up");
    220     }
    221 
    222     pub fn select_left(&mut self) {
    223         if self.selected - 1 < 0 {
    224             return;
    225         }
    226         self.selected -= 1;
    227     }
    228 
    229     pub fn select_right(&mut self) {
    230         if self.selected + 1 >= self.columns.len() as i32 {
    231             return;
    232         }
    233         self.selected += 1;
    234     }
    235 
    236     #[must_use = "you must call timeline_cache.pop() for each returned value"]
    237     pub fn delete_column(&mut self, index: usize) -> Vec<TimelineKind> {
    238         let mut kinds_to_pop: Vec<TimelineKind> = vec![];
    239         for route in self.columns[index].router().routes() {
    240             if let Route::Timeline(kind) = route {
    241                 kinds_to_pop.push(kind.clone());
    242             }
    243         }
    244 
    245         self.columns.remove(index);
    246 
    247         // if we've removed the selected column, reduce the index by 1
    248         if self.selected == (index as i32) && self.selected != 0 {
    249             self.selected -= 1;
    250         }
    251 
    252         if self.columns.is_empty() {
    253             self.new_column_picker();
    254         }
    255 
    256         kinds_to_pop
    257     }
    258 
    259     pub fn move_col(&mut self, from_index: usize, to_index: usize) {
    260         if from_index == to_index
    261             || from_index >= self.columns.len()
    262             || to_index >= self.columns.len()
    263         {
    264             return;
    265         }
    266 
    267         self.columns.swap(from_index, to_index);
    268     }
    269 }
    270 
    271 pub enum IntermediaryRoute {
    272     Timeline(Box<Timeline>),
    273     Route(Route),
    274 }
    275 
    276 pub enum ColumnsAction {
    277     Switch(usize, usize), // from Switch.0 to Switch.1,
    278     Remove(usize),
    279 }