1#![deny(
3 missing_docs,
4 missing_copy_implementations,
5 trivial_casts,
6 trivial_numeric_casts,
7 unsafe_code,
8 unused_import_braces,
9 unused_qualifications
10)]
11
12extern crate cursive_core as cursive;
14
15use std::cmp::{self, Ordering};
17use std::collections::HashMap;
18use std::hash::Hash;
19use std::sync::Arc;
20
21use cursive::{
23 align::HAlign,
24 direction::Direction,
25 event::{Callback, Event, EventResult, Key, MouseButton, MouseEvent},
26 theme,
27 vec::Vec2,
28 view::{scroll, CannotFocus, View},
29 Cursive, Printer, Rect, With,
30};
31
32pub trait TableViewItem<H>: Clone + Sized
35where
36 H: Eq + Hash + Copy + Clone + Send + Sync + 'static,
37{
38 fn to_column(&self, column: H) -> String;
41
42 fn cmp(&self, other: &Self, column: H) -> Ordering
44 where
45 Self: Sized;
46}
47
48pub trait ArrayViewItem<H>: Clone + Sized
51where
52 H: Eq + Hash + Copy + Clone + Send + Sync + 'static,
53{
54 fn to_column(&self, column: H) -> String;
57
58 fn to_row(&self) -> String;
61}
62
63type OnSortCallback<H> = Arc<dyn Fn(&mut Cursive, H, Ordering) + Send + Sync>;
69
70type IndexCallback = Arc<dyn Fn(&mut Cursive, usize, usize) + Send + Sync>;
74
75type CellCallback<H> = Arc<dyn Fn(&mut Cursive, usize, H) + Send + Sync>;
79
80pub struct TableView<T, H> {
138 enabled: bool,
139 sortable: bool,
140 scroll_core: scroll::Core,
141 needs_relayout: bool,
142
143 column_select: bool,
144 columns: Vec<TableColumn<H>>,
145 column_indices: HashMap<H, usize>,
147
148 focus: usize,
149 items: Vec<T>,
150 rows_to_items: Vec<usize>,
152
153 on_sort: Option<OnSortCallback<H>>,
154 on_submit: Option<IndexCallback>,
157 on_select: Option<IndexCallback>,
158}
159
160cursive::impl_scroller!(TableView < T, H > ::scroll_core);
161
162impl<T, H> Default for TableView<T, H>
163where
164 T: TableViewItem<H> + PartialEq,
165 H: Eq + Hash + Copy + Clone + Send + Sync + 'static,
166{
167 fn default() -> Self {
171 Self::new()
172 }
173}
174
175impl<T, H> TableView<T, H>
176where
177 T: TableViewItem<H> + PartialEq,
178 H: Eq + Hash + Copy + Clone + Send + Sync + 'static,
179{
180 pub fn set_items_stable(&mut self, items: Vec<T>) {
188 let new_location = self
190 .item()
191 .and_then(|old_item| {
192 let old_item = &self.items[old_item];
193 items.iter().position(|new| new == old_item)
194 })
195 .unwrap_or(0);
196
197 self.set_items_and_focus(items, new_location);
198 }
199}
200
201impl<T, H> TableView<T, H>
202where
203 T: TableViewItem<H>,
204 H: Eq + Hash + Copy + Clone + Send + Sync + 'static,
205{
206 pub fn new() -> Self {
211 Self {
212 enabled: true,
213 sortable: true,
214 scroll_core: scroll::Core::new(),
215 needs_relayout: true,
216
217 column_select: false,
218 columns: Vec::new(),
219 column_indices: HashMap::new(),
220
221 focus: 0,
222 items: Vec::new(),
223 rows_to_items: Vec::new(),
224
225 on_sort: None,
226 on_submit: None,
227 on_select: None,
228 }
229 }
230
231 pub fn column<S: Into<String>, C: FnOnce(TableColumn<H>) -> TableColumn<H>>(
237 mut self,
238 column: H,
239 title: S,
240 callback: C,
241 ) -> Self {
242 self.add_column(column, title, callback);
243 self
244 }
245
246 pub fn add_column<S: Into<String>, C: FnOnce(TableColumn<H>) -> TableColumn<H>>(
252 &mut self,
253 column: H,
254 title: S,
255 callback: C,
256 ) {
257 self.insert_column(self.columns.len(), column, title, callback);
258 }
259
260 pub fn remove_column(&mut self, i: usize) {
262 for column in &self.columns[i + 1..] {
264 *self.column_indices.get_mut(&column.column).unwrap() -= 1;
265 }
266
267 let column = self.columns.remove(i);
268 self.column_indices.remove(&column.column);
269 self.needs_relayout = true;
270 }
271
272 pub fn insert_column<S: Into<String>, C: FnOnce(TableColumn<H>) -> TableColumn<H>>(
278 &mut self,
279 i: usize,
280 column: H,
281 title: S,
282 callback: C,
283 ) {
284 for column in &self.columns[i..] {
286 *self.column_indices.get_mut(&column.column).unwrap() += 1;
287 }
288
289 self.column_indices.insert(column, i);
290 self.columns
291 .insert(i, callback(TableColumn::new(column, title.into())));
292
293 if self.columns.len() == 1 {
295 self.set_default_column(column);
296 }
297 self.needs_relayout = true;
298 }
299
300 pub fn default_column(mut self, column: H) -> Self {
302 self.set_default_column(column);
303 self
304 }
305
306 pub fn set_default_column(&mut self, column: H) {
308 if self.column_indices.contains_key(&column) {
309 for c in &mut self.columns {
310 c.selected = c.column == column;
311 if c.selected {
312 c.order = c.default_order;
313 } else {
314 c.order = Ordering::Equal;
315 }
316 }
317 }
318 }
319
320 pub fn sort_by(&mut self, column: H, order: Ordering) {
323 if !self.sortable {
324 return;
325 }
326
327 if self.column_indices.contains_key(&column) {
328 for c in &mut self.columns {
329 let selected = c.column == column;
331 c.selected = selected;
332 c.order = if selected { order } else { Ordering::Equal };
333 }
334 }
335
336 self.sort_items(column, order);
337 }
338
339 pub fn sort(&mut self) {
342 if !self.sortable || self.items.len() <= 1 {
343 return;
344 }
345
346 if let Some((column, order)) = self.order() {
347 self.sort_items(column, order);
348 }
349 }
350
351 pub fn order(&self) -> Option<(H, Ordering)> {
357 for c in &self.columns {
358 if c.order != Ordering::Equal {
359 return Some((c.column, c.order));
360 }
361 }
362 None
363 }
364
365 pub fn disable(&mut self) {
369 self.enabled = false;
370 }
371
372 pub fn enable(&mut self) {
374 self.enabled = true;
375 }
376
377 pub fn set_enabled(&mut self, enabled: bool) {
379 self.enabled = enabled;
380 }
381
382 pub fn set_sortable(&mut self, sortable: bool) {
384 self.sortable = sortable;
385 self.column_select &= sortable;
386 }
387
388 pub fn sortable(self, sortable: bool) -> Self {
392 self.with(|t| t.set_sortable(sortable))
393 }
394
395 pub fn is_sortable(&self) -> bool {
397 self.sortable
398 }
399
400 pub fn is_enabled(&self) -> bool {
402 self.enabled
403 }
404
405 pub fn set_on_sort<F>(&mut self, cb: F)
432 where
433 F: Fn(&mut Cursive, H, Ordering) + Send + Sync + 'static,
434 {
435 self.on_sort = Some(Arc::new(move |s, h, o| cb(s, h, o)));
436 }
437
438 pub fn on_sort<F>(self, cb: F) -> Self
467 where
468 F: Fn(&mut Cursive, H, Ordering) + Send + Sync + 'static,
469 {
470 self.with(|t| t.set_on_sort(cb))
471 }
472
473 pub fn set_on_submit<F>(&mut self, cb: F)
503 where
504 F: Fn(&mut Cursive, usize, usize) + Send + Sync + 'static,
505 {
506 self.on_submit = Some(Arc::new(move |s, row, index| cb(s, row, index)));
507 }
508
509 pub fn on_submit<F>(self, cb: F) -> Self
540 where
541 F: Fn(&mut Cursive, usize, usize) + Send + Sync + 'static,
542 {
543 self.with(|t| t.set_on_submit(cb))
544 }
545
546 pub fn set_on_select<F>(&mut self, cb: F)
575 where
576 F: Fn(&mut Cursive, usize, usize) + Send + Sync + 'static,
577 {
578 self.on_select = Some(Arc::new(move |s, row, index| cb(s, row, index)));
579 }
580
581 pub fn on_select<F>(self, cb: F) -> Self
611 where
612 F: Fn(&mut Cursive, usize, usize) + Send + Sync + 'static,
613 {
614 self.with(|t| t.set_on_select(cb))
615 }
616
617 pub fn clear(&mut self) {
619 self.items.clear();
620 self.rows_to_items.clear();
621 self.focus = 0;
622 self.needs_relayout = true;
623 }
624
625 pub fn len(&self) -> usize {
627 self.items.len()
628 }
629
630 pub fn is_empty(&self) -> bool {
632 self.items.is_empty()
633 }
634
635 pub fn row(&self) -> Option<usize> {
637 if self.items.is_empty() {
638 None
639 } else {
640 Some(self.focus)
641 }
642 }
643
644 pub fn set_selected_row(&mut self, row_index: usize) {
646 self.focus = row_index;
647 self.scroll_core.scroll_to_y(row_index);
648 }
649
650 pub fn selected_row(self, row_index: usize) -> Self {
654 self.with(|t| t.set_selected_row(row_index))
655 }
656
657 pub fn set_items(&mut self, items: Vec<T>) {
662 self.set_items_and_focus(items, 0);
663 }
664
665 fn set_items_and_focus(&mut self, items: Vec<T>, new_location: usize) {
666 self.items = items;
667 self.rows_to_items = Vec::with_capacity(self.items.len());
668
669 for i in 0..self.items.len() {
670 self.rows_to_items.push(i);
671 }
672
673 if let Some((column, order)) = self.order() {
674 let selected_column = self.columns.iter().find(|c| c.selected).map(|c| c.column);
676 self.sort_by(column, order);
677 if let Some(column) = selected_column {
678 for c in &mut self.columns {
679 c.selected = c.column == column;
680 }
681 }
682 }
683
684 self.set_selected_item(new_location);
685 self.needs_relayout = true;
686 }
687
688 pub fn items(self, items: Vec<T>) -> Self {
694 self.with(|t| t.set_items(items))
695 }
696
697 pub fn borrow_item(&self, index: usize) -> Option<&T> {
700 self.items.get(index)
701 }
702
703 pub fn borrow_item_mut(&mut self, index: usize) -> Option<&mut T> {
706 self.items.get_mut(index)
707 }
708
709 pub fn borrow_items(&self) -> &[T] {
711 &self.items
712 }
713
714 pub fn borrow_items_mut(&mut self) -> &mut [T] {
718 self.needs_relayout = true;
719 &mut self.items
720 }
721
722 pub fn item(&self) -> Option<usize> {
725 self.rows_to_items.get(self.focus).copied()
726 }
727
728 pub fn set_selected_item(&mut self, item_index: usize) {
731 if item_index >= self.items.len() {
732 return;
733 }
734
735 if let Some(row) = self
736 .rows_to_items
737 .iter()
738 .position(|&item| item == item_index)
739 {
740 self.focus = row;
741 self.scroll_core.scroll_to_y(row);
742 }
743 }
744
745 pub fn selected_item(self, item_index: usize) -> Self {
750 self.with(|t| t.set_selected_item(item_index))
751 }
752
753 pub fn insert_item(&mut self, item: T) {
760 self.insert_item_at(self.items.len(), item);
761 }
762
763 pub fn insert_item_at(&mut self, index: usize, item: T) {
774 self.items.push(item);
775
776 self.rows_to_items.insert(index, self.items.len() - 1);
778
779 if let Some((column, order)) = self.order() {
780 self.sort_by(column, order);
781 }
782 self.needs_relayout = true;
783 }
784
785 pub fn remove_item(&mut self, item_index: usize) -> Option<T> {
788 if item_index < self.items.len() {
789 if let Some(selected_index) = self.item() {
791 if selected_index == item_index {
792 self.focus_up(1);
793 }
794 }
795
796 self.rows_to_items.retain(|i| *i != item_index);
798
799 for ref_index in &mut self.rows_to_items {
801 if *ref_index > item_index {
802 *ref_index -= 1;
803 }
804 }
805 self.needs_relayout = true;
806
807 Some(self.items.remove(item_index))
809 } else {
810 None
811 }
812 }
813
814 pub fn take_items(&mut self) -> Vec<T> {
816 self.set_selected_row(0);
817 self.rows_to_items.clear();
818 self.needs_relayout = true;
819 self.items.drain(0..).collect()
820 }
821}
822
823impl<T, H> TableView<T, H>
824where
825 T: TableViewItem<H>,
826 H: Eq + Hash + Copy + Clone + Send + Sync + 'static,
827{
828 fn draw_columns<C: Fn(&Printer, &TableColumn<H>)>(
829 &self,
830 printer: &Printer,
831 sep: &str,
832 callback: C,
833 ) {
834 let mut column_offset = 0;
835 let column_count = self.columns.len();
836 for (index, column) in self.columns.iter().enumerate() {
837 let printer = &printer.offset((column_offset, 0)).focused(true);
838
839 callback(printer, column);
840
841 if 1 + index < column_count {
842 printer.print((column.width + 1, 0), sep);
843 }
844
845 column_offset += column.width + 3;
846 }
847 }
848
849 fn sort_items(&mut self, column: H, order: Ordering) {
850 if !self.is_empty() {
851 let old_item = self.item();
852
853 let items = &self.items;
854 let rows_to_items = &mut self.rows_to_items;
855 let ascending = order == Ordering::Less;
856 rows_to_items.sort_by(|a, b| {
857 if ascending {
858 items[*a].cmp(&items[*b], column)
859 } else {
860 items[*b].cmp(&items[*a], column)
861 }
862 });
863
864 if let Some(old_item) = old_item {
865 self.set_selected_item(old_item);
866 }
867 }
868 }
869
870 fn draw_item(&self, printer: &Printer, i: usize) {
871 self.draw_columns(printer, "┆ ", |printer, column| {
872 let value = self.items[self.rows_to_items[i]].to_column(column.column);
873 column.draw_row(printer, value.as_str());
874 });
875 }
876
877 fn on_focus_change(&self) -> EventResult {
878 let row = self.row().unwrap();
879 let index = self.item().unwrap();
880 EventResult::Consumed(
881 self.on_select
882 .clone()
883 .map(|cb| Callback::from_fn(move |s| cb(s, row, index))),
884 )
885 }
886
887 fn focus_up(&mut self, n: usize) {
888 self.focus -= cmp::min(self.focus, n);
889 }
890
891 fn focus_down(&mut self, n: usize) {
892 self.focus = cmp::min(self.focus + n, self.items.len().saturating_sub(1));
893 }
894
895 fn active_column(&self) -> usize {
896 self.columns.iter().position(|c| c.selected).unwrap_or(0)
897 }
898
899 fn column_cancel(&mut self) {
900 self.column_select = false;
901 for column in &mut self.columns {
902 column.selected = column.order != Ordering::Equal;
903 }
904 }
905
906 fn column_next(&mut self) -> bool {
907 let column = self.active_column();
908 if 1 + column < self.columns.len() {
909 self.columns[column].selected = false;
910 self.columns[column + 1].selected = true;
911 true
912 } else {
913 false
914 }
915 }
916
917 fn column_prev(&mut self) -> bool {
918 let column = self.active_column();
919 if column > 0 {
920 self.columns[column].selected = false;
921 self.columns[column - 1].selected = true;
922 true
923 } else {
924 false
925 }
926 }
927
928 fn column_select(&mut self) -> EventResult {
929 if !self.sortable {
930 self.column_cancel();
931 return EventResult::Ignored;
932 }
933
934 let next = self.active_column();
935 let column = self.columns[next].column;
936 let current = self
937 .columns
938 .iter()
939 .position(|c| c.order != Ordering::Equal)
940 .unwrap_or(0);
941
942 let order = if current != next {
943 self.columns[next].default_order
944 } else if self.columns[current].order == Ordering::Less {
945 Ordering::Greater
946 } else {
947 Ordering::Less
948 };
949
950 self.sort_by(column, order);
951
952 if self.on_sort.is_some() {
953 let c = &self.columns[self.active_column()];
954 let column = c.column;
955 let order = c.order;
956
957 let cb = self.on_sort.clone().unwrap();
958 EventResult::with_cb(move |s| cb(s, column, order))
959 } else {
960 EventResult::Consumed(None)
961 }
962 }
963
964 fn column_for_x(&self, mut x: usize) -> Option<usize> {
965 for (i, col) in self.columns.iter().enumerate() {
966 x = match x.checked_sub(col.width) {
967 None => return Some(i),
968 Some(x) => x.checked_sub(3)?,
969 };
970 }
971
972 None
973 }
974
975 fn draw_content(&self, printer: &Printer) {
976 for i in 0..self.rows_to_items.len() {
977 let printer = printer.offset((0, i));
978 let color = if i == self.focus && self.enabled {
979 if !self.column_select && self.enabled && printer.focused {
980 theme::ColorStyle::highlight()
981 } else {
982 theme::ColorStyle::highlight_inactive()
983 }
984 } else {
985 theme::ColorStyle::primary()
986 };
987
988 if i < self.items.len() {
989 printer.with_color(color, |printer| {
990 self.draw_item(printer, i);
991 });
992 }
993 }
994 }
995
996 fn layout_content(&mut self, size: Vec2) {
997 let column_count = self.columns.len();
998
999 let (mut sized, mut flexible): (Vec<&mut TableColumn<H>>, Vec<&mut TableColumn<H>>) = self
1001 .columns
1002 .iter_mut()
1003 .partition(|c| c.requested_width.is_some());
1004
1005 let available_width = size.x.saturating_sub(column_count.saturating_sub(1) * 3);
1007
1008 let mut remaining_width = available_width;
1010 for column in &mut sized {
1011 column.width = match *column.requested_width.as_ref().unwrap() {
1012 TableColumnWidth::Percent(width) => cmp::min(
1013 (size.x as f32 / 100.0 * width as f32).ceil() as usize,
1014 remaining_width,
1015 ),
1016 TableColumnWidth::Absolute(width) => width,
1017 };
1018 remaining_width = remaining_width.saturating_sub(column.width);
1019 }
1020
1021 let remaining_columns = flexible.len();
1023 let base_width = if remaining_columns > 0 {
1024 remaining_width / remaining_columns
1025 } else {
1026 0
1027 };
1028 for column in &mut flexible {
1029 column.width = base_width;
1030 }
1031
1032 self.needs_relayout = false;
1033 }
1034
1035 fn content_required_size(&mut self, req: Vec2) -> Vec2 {
1036 Vec2::new(req.x, self.rows_to_items.len())
1037 }
1038
1039 fn on_inner_event(&mut self, event: Event) -> EventResult {
1040 let last_focus = self.focus;
1041 match event {
1042 Event::Key(Key::Right) => {
1043 if !self.sortable {
1044 return EventResult::Ignored;
1045 }
1046
1047 if self.column_select {
1048 if !self.column_next() {
1049 return EventResult::Ignored;
1050 }
1051 } else {
1052 self.column_select = true;
1053 }
1054 }
1055 Event::Key(Key::Left) => {
1056 if !self.sortable {
1057 return EventResult::Ignored;
1058 }
1059
1060 if self.column_select {
1061 if !self.column_prev() {
1062 return EventResult::Ignored;
1063 }
1064 } else {
1065 self.column_select = true;
1066 }
1067 }
1068 Event::Key(Key::Up) if self.focus > 0 || self.column_select => {
1069 if self.column_select {
1070 self.column_cancel();
1071 } else {
1072 self.focus_up(1);
1073 }
1074 }
1075 Event::Key(Key::Down) if self.focus + 1 < self.items.len() || self.column_select => {
1076 if self.column_select {
1077 self.column_cancel();
1078 } else {
1079 self.focus_down(1);
1080 }
1081 }
1082 Event::Key(Key::PageUp) => {
1083 self.column_cancel();
1084 self.focus_up(10);
1085 }
1086 Event::Key(Key::PageDown) => {
1087 self.column_cancel();
1088 self.focus_down(10);
1089 }
1090 Event::Key(Key::Home) => {
1091 self.column_cancel();
1092 self.focus = 0;
1093 }
1094 Event::Key(Key::End) => {
1095 self.column_cancel();
1096 self.focus = self.items.len().saturating_sub(1);
1097 }
1098 Event::Key(Key::Enter) => {
1099 if self.column_select && self.sortable {
1100 return self.column_select();
1101 } else if !self.is_empty() && self.on_submit.is_some() {
1102 return self.on_submit_event();
1103 }
1104 }
1105 Event::Mouse {
1106 position,
1107 offset,
1108 event: MouseEvent::Press(MouseButton::Left),
1109 } if !self.is_empty()
1110 && position
1111 .checked_sub(offset)
1112 .map_or(false, |p| p.y == self.focus) =>
1113 {
1114 self.column_cancel();
1115 return self.on_submit_event();
1116 }
1117 Event::Mouse {
1118 position,
1119 offset,
1120 event: MouseEvent::Press(_),
1121 } if !self.is_empty() => match position.checked_sub(offset) {
1122 Some(position) if position.y < self.rows_to_items.len() => {
1123 self.column_cancel();
1124 self.focus = position.y;
1125 }
1126 _ => return EventResult::Ignored,
1127 },
1128 _ => return EventResult::Ignored,
1129 }
1130
1131 let focus = self.focus;
1132
1133 if self.column_select {
1134 EventResult::Consumed(None)
1135 } else if !self.is_empty() && last_focus != focus {
1136 self.on_focus_change()
1137 } else {
1138 EventResult::Ignored
1139 }
1140 }
1141
1142 fn inner_important_area(&self, size: Vec2) -> Rect {
1143 Rect::from_size((0, self.focus), (size.x, 1))
1144 }
1145
1146 fn on_submit_event(&mut self) -> EventResult {
1147 if let Some(cb) = &self.on_submit {
1148 let cb = Arc::clone(cb);
1149 let row = self.row().unwrap();
1150 let index = self.item().unwrap();
1151 return EventResult::Consumed(Some(Callback::from_fn(move |s| cb(s, row, index))));
1152 }
1153 EventResult::Ignored
1154 }
1155}
1156
1157impl<T, H> View for TableView<T, H>
1158where
1159 T: TableViewItem<H> + Send + Sync + 'static,
1160 H: Eq + Hash + Copy + Clone + Send + Sync + 'static,
1161{
1162 fn draw(&self, printer: &Printer) {
1163 self.draw_columns(printer, "╷ ", |printer, column| {
1164 let color = if self.enabled
1165 && self.sortable
1166 && (column.order != Ordering::Equal || column.selected)
1167 {
1168 if self.column_select && column.selected && printer.focused {
1169 theme::ColorStyle::highlight()
1170 } else {
1171 theme::ColorStyle::highlight_inactive()
1172 }
1173 } else {
1174 theme::ColorStyle::primary()
1175 };
1176
1177 printer.with_color(color, |printer| {
1178 column.draw_header(printer, self.sortable);
1179 });
1180 });
1181
1182 self.draw_columns(
1183 &printer.offset((0, 1)).focused(true),
1184 "┴─",
1185 |printer, column| {
1186 printer.print_hline((0, 0), column.width + 1, "─");
1187 },
1188 );
1189
1190 let available_height = printer.size.y.saturating_sub(2);
1192 let filled_rows = self.rows_to_items.len().min(available_height);
1193 for y in 2 + filled_rows..printer.size.y {
1194 self.draw_columns(&printer.offset((0, y)), "┆ ", |_, _| ());
1195 }
1196
1197 let printer = &printer.offset((0, 2)).focused(true);
1198 scroll::draw(self, printer, Self::draw_content);
1199 }
1200
1201 fn layout(&mut self, size: Vec2) {
1202 scroll::layout(
1203 self,
1204 size.saturating_sub((0, 2)),
1205 self.needs_relayout,
1206 Self::layout_content,
1207 Self::content_required_size,
1208 );
1209 }
1210
1211 fn take_focus(&mut self, _: Direction) -> Result<EventResult, CannotFocus> {
1212 self.enabled.then(EventResult::consumed).ok_or(CannotFocus)
1213 }
1214
1215 fn on_event(&mut self, event: Event) -> EventResult {
1216 if !self.enabled {
1217 return EventResult::Ignored;
1218 }
1219
1220 match event {
1221 Event::Mouse {
1222 position,
1223 offset,
1224 event: MouseEvent::Press(MouseButton::Left),
1225 } if position.checked_sub(offset).map_or(false, |p| p.y == 0) => {
1226 if !self.sortable {
1227 return EventResult::Ignored;
1228 }
1229
1230 if let Some(position) = position.checked_sub(offset) {
1231 if let Some(col) = self.column_for_x(position.x) {
1232 if self.column_select && self.columns[col].selected {
1233 return self.column_select();
1234 } else {
1235 let active = self.active_column();
1236 self.columns[active].selected = false;
1237 self.columns[col].selected = true;
1238 self.column_select = true;
1239 }
1240 }
1241 }
1242 EventResult::Ignored
1243 }
1244 event => scroll::on_event(
1245 self,
1246 event.relativized((0, 2)),
1247 Self::on_inner_event,
1248 Self::inner_important_area,
1249 ),
1250 }
1251 }
1252
1253 fn important_area(&self, size: Vec2) -> Rect {
1254 self.inner_important_area(size.saturating_sub((0, 2))) + (0, 2)
1255 }
1256}
1257
1258pub struct ArrayView<T, H> {
1260 enabled: bool,
1261 scroll_core: scroll::Core,
1262 needs_relayout: bool,
1263
1264 columns: Vec<TableColumn<H>>,
1265 row_header: ArrayRowHeader,
1266 array_name: String,
1267
1268 focus: usize,
1269 focus_col: usize,
1270 items: Vec<T>,
1271
1272 on_submit: Option<CellCallback<H>>,
1273 on_select: Option<CellCallback<H>>,
1274}
1275
1276cursive::impl_scroller!(ArrayView < T, H > ::scroll_core);
1277
1278impl<T, H> Default for ArrayView<T, H>
1279where
1280 T: ArrayViewItem<H>,
1281 H: Eq + Hash + Copy + Clone + Send + Sync + 'static,
1282{
1283 fn default() -> Self {
1287 Self::new()
1288 }
1289}
1290
1291impl<T, H> ArrayView<T, H>
1292where
1293 T: ArrayViewItem<H> + PartialEq,
1294 H: Eq + Hash + Copy + Clone + Send + Sync + 'static,
1295{
1296 pub fn set_items_stable(&mut self, items: Vec<T>) {
1301 let new_location = self
1303 .item()
1304 .and_then(|old_item| {
1305 let old_item = &self.items[old_item];
1306 items.iter().position(|new| new == old_item)
1307 })
1308 .unwrap_or(0);
1309
1310 self.set_items_and_focus(items, new_location);
1311 }
1312}
1313
1314impl<T, H> ArrayView<T, H>
1315where
1316 T: ArrayViewItem<H>,
1317 H: Eq + Hash + Copy + Clone + Send + Sync + 'static,
1318{
1319 pub fn new() -> Self {
1324 Self {
1325 enabled: true,
1326 scroll_core: scroll::Core::new(),
1327 needs_relayout: true,
1328
1329 columns: Vec::new(),
1330 row_header: ArrayRowHeader::new(),
1331 array_name: String::new(),
1332
1333 focus: 0,
1334 focus_col: 0,
1335 items: Vec::new(),
1336
1337 on_submit: None,
1338 on_select: None,
1339 }
1340 }
1341
1342 pub fn row_header<C: FnOnce(ArrayRowHeader) -> ArrayRowHeader>(mut self, callback: C) -> Self {
1349 self.set_row_header(callback);
1350 self
1351 }
1352
1353 pub fn array_name<S: Into<String>>(mut self, name: S) -> Self {
1357 self.set_array_name(name);
1358 self
1359 }
1360
1361 pub fn set_array_name<S: Into<String>>(&mut self, name: S) {
1363 self.array_name = name.into();
1364 }
1365
1366 pub fn set_row_header<C: FnOnce(ArrayRowHeader) -> ArrayRowHeader>(
1371 &mut self,
1372 callback: C,
1373 ) {
1374 self.row_header = callback(ArrayRowHeader::new());
1375 self.needs_relayout = true;
1376 }
1377
1378 pub fn column<S: Into<String>, C: FnOnce(TableColumn<H>) -> TableColumn<H>>(
1384 mut self,
1385 column: H,
1386 title: S,
1387 callback: C,
1388 ) -> Self {
1389 self.add_column(column, title, callback);
1390 self
1391 }
1392
1393 pub fn add_column<S: Into<String>, C: FnOnce(TableColumn<H>) -> TableColumn<H>>(
1399 &mut self,
1400 column: H,
1401 title: S,
1402 callback: C,
1403 ) {
1404 self.insert_column(self.columns.len(), column, title, callback);
1405 }
1406
1407 pub fn remove_column(&mut self, i: usize) {
1409 self.columns.remove(i);
1410 if self.focus_col >= self.columns.len() {
1411 self.focus_col = self.columns.len().saturating_sub(1);
1412 }
1413 self.needs_relayout = true;
1414 }
1415
1416 pub fn insert_column<S: Into<String>, C: FnOnce(TableColumn<H>) -> TableColumn<H>>(
1422 &mut self,
1423 i: usize,
1424 column: H,
1425 title: S,
1426 callback: C,
1427 ) {
1428 self.columns
1429 .insert(i, callback(TableColumn::new(column, title.into())));
1430 if self.focus_col >= self.columns.len() {
1431 self.focus_col = self.columns.len().saturating_sub(1);
1432 }
1433 self.needs_relayout = true;
1434 }
1435
1436 pub fn disable(&mut self) {
1440 self.enabled = false;
1441 }
1442
1443 pub fn enable(&mut self) {
1445 self.enabled = true;
1446 }
1447
1448 pub fn set_enabled(&mut self, enabled: bool) {
1450 self.enabled = enabled;
1451 }
1452
1453 pub fn is_enabled(&self) -> bool {
1455 self.enabled
1456 }
1457
1458 pub fn set_on_submit<F>(&mut self, cb: F)
1487 where
1488 F: Fn(&mut Cursive, usize, H) + Send + Sync + 'static,
1489 {
1490 self.on_submit = Some(Arc::new(move |s, row, column| cb(s, row, column)));
1491 }
1492
1493 pub fn on_submit<F>(self, cb: F) -> Self
1521 where
1522 F: Fn(&mut Cursive, usize, H) + Send + Sync + 'static,
1523 {
1524 self.with(|t| t.set_on_submit(cb))
1525 }
1526
1527 pub fn set_on_select<F>(&mut self, cb: F)
1555 where
1556 F: Fn(&mut Cursive, usize, H) + Send + Sync + 'static,
1557 {
1558 self.on_select = Some(Arc::new(move |s, row, column| cb(s, row, column)));
1559 }
1560
1561 pub fn on_select<F>(self, cb: F) -> Self
1588 where
1589 F: Fn(&mut Cursive, usize, H) + Send + Sync + 'static,
1590 {
1591 self.with(|t| t.set_on_select(cb))
1592 }
1593
1594 pub fn clear(&mut self) {
1596 self.items.clear();
1597 self.focus = 0;
1598 self.needs_relayout = true;
1599 }
1600
1601 pub fn len(&self) -> usize {
1603 self.items.len()
1604 }
1605
1606 pub fn is_empty(&self) -> bool {
1608 self.items.is_empty()
1609 }
1610
1611 pub fn row(&self) -> Option<usize> {
1613 if self.items.is_empty() {
1614 None
1615 } else {
1616 Some(self.focus)
1617 }
1618 }
1619
1620 pub fn set_selected_row(&mut self, row_index: usize) {
1622 self.focus = row_index;
1623 self.scroll_core.scroll_to_y(self.row_y(row_index));
1624 }
1625
1626 pub fn selected_row(self, row_index: usize) -> Self {
1630 self.with(|t| t.set_selected_row(row_index))
1631 }
1632
1633 pub fn set_items(&mut self, items: Vec<T>) {
1635 self.set_items_and_focus(items, 0);
1636 }
1637
1638 fn set_items_and_focus(&mut self, items: Vec<T>, new_location: usize) {
1639 self.items = items;
1640 self.set_selected_item(new_location);
1641 self.needs_relayout = true;
1642 }
1643
1644 pub fn items(self, items: Vec<T>) -> Self {
1648 self.with(|t| t.set_items(items))
1649 }
1650
1651 pub fn borrow_item(&self, index: usize) -> Option<&T> {
1654 self.items.get(index)
1655 }
1656
1657 pub fn borrow_item_mut(&mut self, index: usize) -> Option<&mut T> {
1660 self.items.get_mut(index)
1661 }
1662
1663 pub fn borrow_items(&self) -> &[T] {
1665 &self.items
1666 }
1667
1668 pub fn borrow_items_mut(&mut self) -> &mut [T] {
1672 self.needs_relayout = true;
1673 &mut self.items
1674 }
1675
1676 pub fn item(&self) -> Option<usize> {
1679 if self.items.is_empty() {
1680 None
1681 } else {
1682 Some(self.focus)
1683 }
1684 }
1685
1686 pub fn set_selected_item(&mut self, item_index: usize) {
1689 if item_index < self.items.len() {
1690 self.focus = item_index;
1691 self.scroll_core.scroll_to_y(self.row_y(item_index));
1692 }
1693 }
1694
1695 pub fn selected_item(self, item_index: usize) -> Self {
1700 self.with(|t| t.set_selected_item(item_index))
1701 }
1702
1703 pub fn insert_item(&mut self, item: T) {
1707 self.insert_item_at(self.items.len(), item);
1708 }
1709
1710 pub fn insert_item_at(&mut self, index: usize, item: T) {
1718 self.items.insert(index, item);
1719 self.needs_relayout = true;
1720 }
1721
1722 pub fn remove_item(&mut self, item_index: usize) -> Option<T> {
1725 if item_index < self.items.len() {
1726 if let Some(selected_index) = self.item() {
1727 if selected_index == item_index {
1728 self.focus_up(1);
1729 }
1730 }
1731
1732 self.needs_relayout = true;
1733 Some(self.items.remove(item_index))
1734 } else {
1735 None
1736 }
1737 }
1738
1739 pub fn take_items(&mut self) -> Vec<T> {
1741 self.set_selected_row(0);
1742 self.needs_relayout = true;
1743 self.items.drain(0..).collect()
1744 }
1745}
1746
1747impl<T, H> ArrayView<T, H>
1748where
1749 T: ArrayViewItem<H>,
1750 H: Eq + Hash + Copy + Clone + Send + Sync + 'static,
1751{
1752 fn row_y(&self, row_index: usize) -> usize {
1753 row_index.saturating_mul(2)
1754 }
1755
1756 fn row_for_y(&self, y: usize) -> Option<usize> {
1757 if y % 2 == 0 {
1758 Some(y / 2)
1759 } else {
1760 None
1761 }
1762 }
1763
1764 fn column_for_x(&self, mut x: usize) -> Option<usize> {
1765 x = match x.checked_sub(self.row_header.width) {
1766 None => return None,
1767 Some(x) => x,
1768 };
1769 x = x.checked_sub(3)?;
1770
1771 for (i, col) in self.columns.iter().enumerate() {
1772 x = match x.checked_sub(col.width) {
1773 None => return Some(i),
1774 Some(x) => x.checked_sub(3)?,
1775 };
1776 }
1777
1778 None
1779 }
1780
1781 fn draw_columns<C, R>(&self, printer: &Printer, sep: &str, row_cb: R, mut col_cb: C)
1782 where
1783 C: FnMut(&Printer, &TableColumn<H>, usize),
1784 R: Fn(&Printer, &ArrayRowHeader),
1785 {
1786 let row_header = &self.row_header;
1787 let printer = &printer.offset((0, 0)).focused(true);
1788 row_cb(printer, row_header);
1789
1790 if !self.columns.is_empty() {
1791 printer.print((row_header.width + 1, 0), sep);
1792 }
1793
1794 let mut column_offset = row_header.width + 3;
1795
1796 let column_count = self.columns.len();
1797 for (index, column) in self.columns.iter().enumerate() {
1798 let printer = &printer.offset((column_offset, 0)).focused(true);
1799
1800 col_cb(printer, column, index);
1801
1802 if 1 + index < column_count {
1803 printer.print((column.width + 1, 0), sep);
1804 }
1805
1806 column_offset += column.width + 3;
1807 }
1808 }
1809
1810 fn draw_item(&self, printer: &Printer, i: usize) {
1811 let item = &self.items[i];
1812 let focused_row = i == self.focus && self.enabled;
1813 let focused_col = self.focus_col;
1814 self.draw_columns(
1815 printer,
1816 "┆ ",
1817 |printer, row_header| {
1818 let value = item.to_row();
1819 row_header.draw_row(printer, value.as_str());
1820 },
1821 |printer, column, index| {
1822 let color = if focused_row && index == focused_col {
1823 if printer.focused {
1824 theme::ColorStyle::highlight()
1825 } else {
1826 theme::ColorStyle::highlight_inactive()
1827 }
1828 } else {
1829 theme::ColorStyle::primary()
1830 };
1831 let value = item.to_column(column.column);
1832 printer.with_color(color, |printer| {
1833 column.draw_row(printer, value.as_str());
1834 });
1835 },
1836 );
1837 }
1838
1839 fn on_focus_change(&self) -> EventResult {
1840 let row = self.row().unwrap();
1841 let column = match self.columns.get(self.focus_col) {
1842 Some(column) => column.column,
1843 None => return EventResult::Ignored,
1844 };
1845 EventResult::Consumed(
1846 self.on_select
1847 .clone()
1848 .map(|cb| Callback::from_fn(move |s| cb(s, row, column))),
1849 )
1850 }
1851
1852 fn focus_up(&mut self, n: usize) {
1853 self.focus -= cmp::min(self.focus, n);
1854 }
1855
1856 fn focus_down(&mut self, n: usize) {
1857 self.focus = cmp::min(self.focus + n, self.items.len().saturating_sub(1));
1858 }
1859
1860 fn draw_content(&self, printer: &Printer) {
1861 let row_count = self.items.len();
1862 for i in 0..row_count {
1863 let row_y = self.row_y(i);
1864 let printer = printer.offset((0, row_y));
1865 self.draw_item(&printer, i);
1866
1867 self.draw_columns(
1868 &printer.offset((0, 1)).focused(true),
1869 "┼─",
1870 |printer, row_header| {
1871 printer.print_hline((0, 0), row_header.width + 1, "─");
1872 },
1873 |printer, column, _| {
1874 printer.print_hline((0, 0), column.width + 1, "─");
1875 },
1876 );
1877 }
1878 }
1879
1880 fn layout_content(&mut self, size: Vec2) {
1881 let column_count = self.columns.len() + 1;
1882
1883 let (mut sized, mut flexible): (Vec<&mut TableColumn<H>>, Vec<&mut TableColumn<H>>) = self
1885 .columns
1886 .iter_mut()
1887 .partition(|c| c.requested_width.is_some());
1888
1889 let available_width = size.x.saturating_sub(column_count.saturating_sub(1) * 3);
1891
1892 let mut remaining_width = available_width;
1894 if let Some(requested_width) = self.row_header.requested_width.as_ref() {
1895 self.row_header.width = match *requested_width {
1896 TableColumnWidth::Percent(width) => cmp::min(
1897 (size.x as f32 / 100.0 * width as f32).ceil() as usize,
1898 remaining_width,
1899 ),
1900 TableColumnWidth::Absolute(width) => width,
1901 };
1902 remaining_width = remaining_width.saturating_sub(self.row_header.width);
1903 }
1904
1905 for column in &mut sized {
1906 column.width = match *column.requested_width.as_ref().unwrap() {
1907 TableColumnWidth::Percent(width) => cmp::min(
1908 (size.x as f32 / 100.0 * width as f32).ceil() as usize,
1909 remaining_width,
1910 ),
1911 TableColumnWidth::Absolute(width) => width,
1912 };
1913 remaining_width = remaining_width.saturating_sub(column.width);
1914 }
1915
1916 let mut remaining_columns = flexible.len();
1918 if self.row_header.requested_width.is_none() {
1919 remaining_columns += 1;
1920 }
1921
1922 let base_width = if remaining_columns > 0 {
1923 remaining_width / remaining_columns
1924 } else {
1925 0
1926 };
1927
1928 if self.row_header.requested_width.is_none() {
1929 self.row_header.width = base_width;
1930 }
1931
1932 for column in &mut flexible {
1933 column.width = base_width;
1934 }
1935
1936 self.needs_relayout = false;
1937 }
1938
1939 fn content_required_size(&mut self, req: Vec2) -> Vec2 {
1940 let rows = self.items.len();
1941 let height = rows.saturating_mul(2);
1942 Vec2::new(req.x, height)
1943 }
1944
1945 fn on_inner_event(&mut self, event: Event) -> EventResult {
1946 let last_focus = (self.focus, self.focus_col);
1947 match event {
1948 Event::Key(Key::Up) if self.focus > 0 => {
1949 self.focus_up(1);
1950 }
1951 Event::Key(Key::Down) if self.focus + 1 < self.items.len() => {
1952 self.focus_down(1);
1953 }
1954 Event::Key(Key::Left) if self.focus_col > 0 => {
1955 self.focus_col -= 1;
1956 }
1957 Event::Key(Key::Right) if self.focus_col + 1 < self.columns.len() => {
1958 self.focus_col += 1;
1959 }
1960 Event::Key(Key::PageUp) => {
1961 self.focus_up(10);
1962 }
1963 Event::Key(Key::PageDown) => {
1964 self.focus_down(10);
1965 }
1966 Event::Key(Key::Home) => {
1967 self.focus = 0;
1968 }
1969 Event::Key(Key::End) => {
1970 self.focus = self.items.len().saturating_sub(1);
1971 }
1972 Event::Key(Key::Enter) => {
1973 if !self.is_empty() && self.on_submit.is_some() {
1974 return self.on_submit_event();
1975 }
1976 }
1977 Event::Mouse {
1978 position,
1979 offset,
1980 event: MouseEvent::Press(MouseButton::Left),
1981 } if !self.is_empty() => {
1982 if let Some(position) = position.checked_sub(offset) {
1983 let row = self.row_for_y(position.y);
1984 let column = self.column_for_x(position.x);
1985 if let (Some(row), Some(column)) = (row, column) {
1986 if row == self.focus && column == self.focus_col {
1987 return self.on_submit_event();
1988 }
1989 if row < self.items.len() {
1990 self.focus = row;
1991 self.focus_col = column;
1992 }
1993 } else {
1994 return EventResult::Ignored;
1995 }
1996 } else {
1997 return EventResult::Ignored;
1998 }
1999 }
2000 Event::Mouse {
2001 position,
2002 offset,
2003 event: MouseEvent::Press(_),
2004 } if !self.is_empty() => {
2005 if let Some(position) = position.checked_sub(offset) {
2006 let row = self.row_for_y(position.y);
2007 let column = self.column_for_x(position.x);
2008 if let (Some(row), Some(column)) = (row, column) {
2009 if row < self.items.len() {
2010 self.focus = row;
2011 self.focus_col = column;
2012 }
2013 } else {
2014 return EventResult::Ignored;
2015 }
2016 } else {
2017 return EventResult::Ignored;
2018 }
2019 }
2020 _ => return EventResult::Ignored,
2021 }
2022
2023 let focus = (self.focus, self.focus_col);
2024
2025 if !self.is_empty() && last_focus != focus {
2026 self.on_focus_change()
2027 } else {
2028 EventResult::Ignored
2029 }
2030 }
2031
2032 fn inner_important_area(&self, size: Vec2) -> Rect {
2033 Rect::from_size((0, self.row_y(self.focus)), (size.x, 1))
2034 }
2035
2036 fn on_submit_event(&mut self) -> EventResult {
2037 if let Some(cb) = &self.on_submit {
2038 let cb = Arc::clone(cb);
2039 let row = self.row().unwrap();
2040 let column = match self.columns.get(self.focus_col) {
2041 Some(column) => column.column,
2042 None => return EventResult::Ignored,
2043 };
2044 return EventResult::Consumed(Some(Callback::from_fn(move |s| cb(s, row, column))));
2045 }
2046 EventResult::Ignored
2047 }
2048}
2049
2050impl<T, H> View for ArrayView<T, H>
2051where
2052 T: ArrayViewItem<H> + Send + Sync + 'static,
2053 H: Eq + Hash + Copy + Clone + Send + Sync + 'static,
2054{
2055 fn draw(&self, printer: &Printer) {
2056 self.draw_columns(
2057 printer,
2058 "╷ ",
2059 |printer, row_header| {
2060 let title = self.array_name.as_str();
2061 printer.with_color(theme::ColorStyle::primary(), |printer| {
2062 row_header.draw_header(printer, title);
2063 });
2064 },
2065 |printer, column, _| {
2066 printer.with_color(theme::ColorStyle::primary(), |printer| {
2067 column.draw_header(printer, false);
2068 });
2069 },
2070 );
2071
2072 self.draw_columns(
2073 &printer.offset((0, 1)).focused(true),
2074 "┴─",
2075 |printer, row_header| {
2076 printer.print_hline((0, 0), row_header.width + 1, "─");
2077 },
2078 |printer, column, _| {
2079 printer.print_hline((0, 0), column.width + 1, "─");
2080 },
2081 );
2082
2083 let available_height = printer.size.y.saturating_sub(2);
2085 let filled_rows = self.items.len().saturating_mul(2).min(available_height);
2086 for y in 2 + filled_rows..printer.size.y {
2087 self.draw_columns(
2088 &printer.offset((0, y)),
2089 "┆ ",
2090 |_, _| (),
2091 |_, _, _| (),
2092 );
2093 }
2094
2095 let printer = &printer.offset((0, 2)).focused(true);
2096 scroll::draw(self, printer, Self::draw_content);
2097 }
2098
2099 fn layout(&mut self, size: Vec2) {
2100 scroll::layout(
2101 self,
2102 size.saturating_sub((0, 2)),
2103 self.needs_relayout,
2104 Self::layout_content,
2105 Self::content_required_size,
2106 );
2107 }
2108
2109 fn take_focus(&mut self, _: Direction) -> Result<EventResult, CannotFocus> {
2110 self.enabled.then(EventResult::consumed).ok_or(CannotFocus)
2111 }
2112
2113 fn on_event(&mut self, event: Event) -> EventResult {
2114 if !self.enabled {
2115 return EventResult::Ignored;
2116 }
2117
2118 match event {
2119 Event::Mouse {
2120 position,
2121 offset,
2122 event: MouseEvent::Press(_),
2123 } if position.checked_sub(offset).map_or(false, |p| p.y < 2) => EventResult::Ignored,
2124 event => scroll::on_event(
2125 self,
2126 event.relativized((0, 2)),
2127 Self::on_inner_event,
2128 Self::inner_important_area,
2129 ),
2130 }
2131 }
2132
2133 fn important_area(&self, size: Vec2) -> Rect {
2134 self.inner_important_area(size.saturating_sub((0, 2))) + (0, 2)
2135 }
2136}
2137
2138pub struct ArrayRowHeader {
2141 alignment: HAlign,
2142 width: usize,
2143 requested_width: Option<TableColumnWidth>,
2144}
2145
2146impl ArrayRowHeader {
2147 pub fn align(mut self, alignment: HAlign) -> Self {
2149 self.alignment = alignment;
2150 self
2151 }
2152
2153 pub fn width(mut self, width: usize) -> Self {
2155 self.requested_width = Some(TableColumnWidth::Absolute(width));
2156 self
2157 }
2158
2159 pub fn width_percent(mut self, width: usize) -> Self {
2162 self.requested_width = Some(TableColumnWidth::Percent(width));
2163 self
2164 }
2165
2166 fn new() -> Self {
2167 Self {
2168 alignment: HAlign::Left,
2169 width: 0,
2170 requested_width: None,
2171 }
2172 }
2173
2174 fn draw_header(&self, printer: &Printer, title: &str) {
2175 let header = match self.alignment {
2176 HAlign::Left => format!("{:<width$}", title, width = self.width),
2177 HAlign::Right => format!("{:>width$}", title, width = self.width),
2178 HAlign::Center => format!("{:^width$}", title, width = self.width),
2179 };
2180
2181 printer.print((0, 0), header.as_str());
2182 }
2183
2184 fn draw_row(&self, printer: &Printer, value: &str) {
2185 let value = match self.alignment {
2186 HAlign::Left => format!("{:<width$} ", value, width = self.width),
2187 HAlign::Right => format!("{:>width$} ", value, width = self.width),
2188 HAlign::Center => format!("{:^width$} ", value, width = self.width),
2189 };
2190
2191 printer.print((0, 0), value.as_str());
2192 }
2193}
2194
2195pub struct TableColumn<H> {
2198 column: H,
2199 title: String,
2200 selected: bool,
2201 alignment: HAlign,
2202 order: Ordering,
2203 width: usize,
2204 default_order: Ordering,
2205 requested_width: Option<TableColumnWidth>,
2206}
2207
2208enum TableColumnWidth {
2209 Percent(usize),
2210 Absolute(usize),
2211}
2212
2213impl<H: Copy + Clone + 'static> TableColumn<H> {
2214 pub fn ordering(mut self, order: Ordering) -> Self {
2216 self.default_order = order;
2217 self
2218 }
2219
2220 pub fn align(mut self, alignment: HAlign) -> Self {
2222 self.alignment = alignment;
2223 self
2224 }
2225
2226 pub fn width(mut self, width: usize) -> Self {
2228 self.requested_width = Some(TableColumnWidth::Absolute(width));
2229 self
2230 }
2231
2232 pub fn width_percent(mut self, width: usize) -> Self {
2235 self.requested_width = Some(TableColumnWidth::Percent(width));
2236 self
2237 }
2238
2239 fn new(column: H, title: String) -> Self {
2240 Self {
2241 column,
2242 title,
2243 selected: false,
2244 alignment: HAlign::Left,
2245 order: Ordering::Equal,
2246 width: 0,
2247 default_order: Ordering::Less,
2248 requested_width: None,
2249 }
2250 }
2251
2252 fn draw_header(&self, printer: &Printer, sortable: bool) {
2253 let title_width = if sortable {
2254 self.width.saturating_sub(4)
2255 } else {
2256 self.width
2257 };
2258 let title = self.title.as_str();
2259
2260 let mut header = match self.alignment {
2261 HAlign::Left => format!("{:<width$}", title, width = title_width),
2262 HAlign::Right => format!("{:>width$}", title, width = title_width),
2263 HAlign::Center => format!("{:^width$}", title, width = title_width),
2264 };
2265
2266 if sortable {
2267 let order = match self.order {
2268 Ordering::Less => "^",
2269 Ordering::Greater => "v",
2270 Ordering::Equal => " ",
2271 };
2272 header.push_str(" [");
2273 header.push_str(order);
2274 header.push(']');
2275 }
2276
2277 printer.print((0, 0), header.as_str());
2278 }
2279
2280 fn draw_row(&self, printer: &Printer, value: &str) {
2281 let value = match self.alignment {
2282 HAlign::Left => format!("{:<width$} ", value, width = self.width),
2283 HAlign::Right => format!("{:>width$} ", value, width = self.width),
2284 HAlign::Center => format!("{:^width$} ", value, width = self.width),
2285 };
2286
2287 printer.print((0, 0), value.as_str());
2288 }
2289}
2290
2291#[cfg(test)]
2292mod tests {
2293 use super::*;
2294
2295 #[derive(Copy, Clone, PartialEq, Eq, Hash)]
2296 enum SimpleColumn {
2297 Name,
2298 }
2299
2300 #[allow(dead_code)]
2301 impl SimpleColumn {
2302 fn as_str(&self) -> &str {
2303 match *self {
2304 SimpleColumn::Name => "Name",
2305 }
2306 }
2307 }
2308
2309 #[derive(Clone, Debug)]
2310 struct SimpleItem {
2311 name: String,
2312 }
2313
2314 impl TableViewItem<SimpleColumn> for SimpleItem {
2315 fn to_column(&self, column: SimpleColumn) -> String {
2316 match column {
2317 SimpleColumn::Name => self.name.to_string(),
2318 }
2319 }
2320
2321 fn cmp(&self, other: &Self, column: SimpleColumn) -> Ordering
2322 where
2323 Self: Sized,
2324 {
2325 match column {
2326 SimpleColumn::Name => self.name.cmp(&other.name),
2327 }
2328 }
2329 }
2330
2331 fn setup_test_table() -> TableView<SimpleItem, SimpleColumn> {
2332 TableView::<SimpleItem, SimpleColumn>::new()
2333 .column(SimpleColumn::Name, "Name", |c| c.width_percent(20))
2334 }
2335
2336 #[test]
2337 fn should_insert_into_existing_table() {
2338 let mut simple_table = setup_test_table();
2339
2340 let mut simple_items = Vec::new();
2341
2342 for i in 1..=10 {
2343 simple_items.push(SimpleItem {
2344 name: format!("{} - Name", i),
2345 });
2346 }
2347
2348 simple_table.set_items(simple_items);
2350
2351 simple_table.insert_item(SimpleItem {
2353 name: format!("{} Name", 11),
2354 });
2355
2356 assert!(simple_table.len() == 11);
2357 }
2358
2359 #[test]
2360 fn should_insert_into_empty_table() {
2361 let mut simple_table = setup_test_table();
2362
2363 simple_table.insert_item(SimpleItem {
2365 name: format!("{} Name", 1),
2366 });
2367
2368 assert!(simple_table.len() == 1);
2369 }
2370}