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 }