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 }