notedeck

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

column.rs (7652B)


      1 use crate::{
      2     actionbar::TimelineOpenResult,
      3     route::{ColumnsRouter, Route, SingletonRouter},
      4     timeline::{Timeline, TimelineCache, TimelineKind},
      5 };
      6 use enostr::RelayPool;
      7 use nostrdb::{Ndb, Transaction};
      8 use notedeck::NoteCache;
      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     pub fn add_new_timeline_column(
    105         &mut self,
    106         timeline_cache: &mut TimelineCache,
    107         txn: &Transaction,
    108         ndb: &Ndb,
    109         note_cache: &mut NoteCache,
    110         pool: &mut RelayPool,
    111         kind: &TimelineKind,
    112     ) -> Option<TimelineOpenResult> {
    113         self.columns
    114             .push(Column::new(vec![Route::timeline(kind.to_owned())]));
    115         timeline_cache.open(ndb, note_cache, txn, pool, kind)
    116     }
    117 
    118     pub fn new_column_picker(&mut self) {
    119         self.add_column(Column::new(vec![Route::AddColumn(
    120             crate::ui::add_column::AddColumnRoute::Base,
    121         )]));
    122     }
    123 
    124     pub fn insert_intermediary_routes(
    125         &mut self,
    126         timeline_cache: &mut TimelineCache,
    127         intermediary_routes: Vec<IntermediaryRoute>,
    128     ) {
    129         let routes = intermediary_routes
    130             .into_iter()
    131             .map(|r| match r {
    132                 IntermediaryRoute::Timeline(mut timeline) => {
    133                     let route = Route::timeline(timeline.kind.clone());
    134                     timeline.subscription.increment();
    135                     timeline_cache.insert(timeline.kind.clone(), *timeline);
    136                     route
    137                 }
    138                 IntermediaryRoute::Route(route) => route,
    139             })
    140             .collect();
    141 
    142         self.columns.push(Column::new(routes));
    143     }
    144 
    145     #[inline]
    146     pub fn add_column_at(&mut self, column: Column, index: u32) {
    147         self.columns.insert(index as usize, column);
    148     }
    149 
    150     #[inline]
    151     pub fn add_column(&mut self, column: Column) {
    152         self.columns.push(column);
    153     }
    154 
    155     #[inline]
    156     pub fn columns_mut(&mut self) -> &mut Vec<Column> {
    157         &mut self.columns
    158     }
    159 
    160     #[inline]
    161     pub fn num_columns(&self) -> usize {
    162         self.columns.len()
    163     }
    164 
    165     // Get the first router in the columns if there are columns present.
    166     // Otherwise, create a new column picker and return the router
    167     pub fn get_selected_router(&mut self) -> &mut ColumnsRouter<Route> {
    168         self.ensure_column();
    169         self.selected_mut().router_mut()
    170     }
    171 
    172     #[inline]
    173     pub fn column(&self, ind: usize) -> &Column {
    174         &self.columns[ind]
    175     }
    176 
    177     #[inline]
    178     pub fn columns(&self) -> &[Column] {
    179         &self.columns
    180     }
    181 
    182     #[inline]
    183     pub fn selected(&self) -> Option<&Column> {
    184         if self.columns.is_empty() {
    185             return None;
    186         }
    187         Some(&self.columns[self.selected as usize])
    188     }
    189 
    190     // TODO(jb55): switch to non-empty container for columns?
    191     fn ensure_column(&mut self) {
    192         if self.columns.is_empty() {
    193             self.new_column_picker();
    194         }
    195     }
    196 
    197     /// Get the selected column. If you're looking to route something
    198     /// and you're not sure which one to choose, use this one
    199     #[inline]
    200     pub fn selected_mut(&mut self) -> &mut Column {
    201         self.ensure_column();
    202         assert!(self.selected < self.columns.len() as i32);
    203         &mut self.columns[self.selected as usize]
    204     }
    205 
    206     #[inline]
    207     pub fn column_mut(&mut self, ind: usize) -> &mut Column {
    208         self.ensure_column();
    209         &mut self.columns[ind]
    210     }
    211 
    212     pub fn select_down(&mut self) {
    213         warn!("todo: implement select_down");
    214     }
    215 
    216     pub fn select_up(&mut self) {
    217         warn!("todo: implement select_up");
    218     }
    219 
    220     pub fn select_left(&mut self) {
    221         if self.selected - 1 < 0 {
    222             return;
    223         }
    224         self.selected -= 1;
    225     }
    226 
    227     pub fn select_right(&mut self) {
    228         if self.selected + 1 >= self.columns.len() as i32 {
    229             return;
    230         }
    231         self.selected += 1;
    232     }
    233 
    234     #[must_use = "you must call timeline_cache.pop() for each returned value"]
    235     pub fn delete_column(&mut self, index: usize) -> Vec<TimelineKind> {
    236         let mut kinds_to_pop: Vec<TimelineKind> = vec![];
    237         for route in self.columns[index].router().routes() {
    238             if let Route::Timeline(kind) = route {
    239                 kinds_to_pop.push(kind.clone());
    240             }
    241         }
    242 
    243         self.columns.remove(index);
    244 
    245         // if we've removed the selected column, reduce the index by 1
    246         if self.selected == (index as i32) && self.selected != 0 {
    247             self.selected -= 1;
    248         }
    249 
    250         if self.columns.is_empty() {
    251             self.new_column_picker();
    252         }
    253 
    254         kinds_to_pop
    255     }
    256 
    257     pub fn move_col(&mut self, from_index: usize, to_index: usize) {
    258         if from_index == to_index
    259             || from_index >= self.columns.len()
    260             || to_index >= self.columns.len()
    261         {
    262             return;
    263         }
    264 
    265         self.columns.swap(from_index, to_index);
    266     }
    267 }
    268 
    269 pub enum IntermediaryRoute {
    270     Timeline(Box<Timeline>),
    271     Route(Route),
    272 }
    273 
    274 pub enum ColumnsAction {
    275     Switch(usize, usize), // from Switch.0 to Switch.1,
    276     Remove(usize),
    277 }