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
48type OnSortCallback<H> = Arc<dyn Fn(&mut Cursive, H, Ordering) + Send + Sync>;
54
55type IndexCallback = Arc<dyn Fn(&mut Cursive, usize, usize) + Send + Sync>;
59
60pub struct TableView<T, H> {
118 enabled: bool,
119 sortable: bool,
120 scroll_core: scroll::Core,
121 needs_relayout: bool,
122
123 column_select: bool,
124 columns: Vec<TableColumn<H>>,
125 column_indicies: HashMap<H, usize>,
126
127 focus: usize,
128 items: Vec<T>,
129 rows_to_items: Vec<usize>,
130
131 on_sort: Option<OnSortCallback<H>>,
132 on_submit: Option<IndexCallback>,
135 on_select: Option<IndexCallback>,
136}
137
138cursive::impl_scroller!(TableView < T, H > ::scroll_core);
139
140impl<T, H> Default for TableView<T, H>
141where
142 T: TableViewItem<H> + PartialEq,
143 H: Eq + Hash + Copy + Clone + Send + Sync + 'static,
144{
145 fn default() -> Self {
149 Self::new()
150 }
151}
152
153impl<T, H> TableView<T, H>
154where
155 T: TableViewItem<H> + PartialEq,
156 H: Eq + Hash + Copy + Clone + Send + Sync + 'static,
157{
158 pub fn set_items_stable(&mut self, items: Vec<T>) {
166 let new_location = self
168 .item()
169 .and_then(|old_item| {
170 let old_item = &self.items[old_item];
171 items.iter().position(|new| new == old_item)
172 })
173 .unwrap_or(0);
174
175 self.set_items_and_focus(items, new_location);
176 }
177}
178
179impl<T, H> TableView<T, H>
180where
181 T: TableViewItem<H>,
182 H: Eq + Hash + Copy + Clone + Send + Sync + 'static,
183{
184 pub fn new() -> Self {
189 Self {
190 enabled: true,
191 sortable: true,
192 scroll_core: scroll::Core::new(),
193 needs_relayout: true,
194
195 column_select: false,
196 columns: Vec::new(),
197 column_indicies: HashMap::new(),
198
199 focus: 0,
200 items: Vec::new(),
201 rows_to_items: Vec::new(),
202
203 on_sort: None,
204 on_submit: None,
205 on_select: None,
206 }
207 }
208
209 pub fn column<S: Into<String>, C: FnOnce(TableColumn<H>) -> TableColumn<H>>(
215 mut self,
216 column: H,
217 title: S,
218 callback: C,
219 ) -> Self {
220 self.add_column(column, title, callback);
221 self
222 }
223
224 pub fn add_column<S: Into<String>, C: FnOnce(TableColumn<H>) -> TableColumn<H>>(
230 &mut self,
231 column: H,
232 title: S,
233 callback: C,
234 ) {
235 self.insert_column(self.columns.len(), column, title, callback);
236 }
237
238 pub fn remove_column(&mut self, i: usize) {
240 for column in &self.columns[i + 1..] {
242 *self.column_indicies.get_mut(&column.column).unwrap() -= 1;
243 }
244
245 let column = self.columns.remove(i);
246 self.column_indicies.remove(&column.column);
247 self.needs_relayout = true;
248 }
249
250 pub fn insert_column<S: Into<String>, C: FnOnce(TableColumn<H>) -> TableColumn<H>>(
256 &mut self,
257 i: usize,
258 column: H,
259 title: S,
260 callback: C,
261 ) {
262 for column in &self.columns[i..] {
264 *self.column_indicies.get_mut(&column.column).unwrap() += 1;
265 }
266
267 self.column_indicies.insert(column, i);
268 self.columns
269 .insert(i, callback(TableColumn::new(column, title.into())));
270
271 if self.columns.len() == 1 {
273 self.set_default_column(column);
274 }
275 self.needs_relayout = true;
276 }
277
278 pub fn default_column(mut self, column: H) -> Self {
280 self.set_default_column(column);
281 self
282 }
283
284 pub fn set_default_column(&mut self, column: H) {
286 if self.column_indicies.contains_key(&column) {
287 for c in &mut self.columns {
288 c.selected = c.column == column;
289 if c.selected {
290 c.order = c.default_order;
291 } else {
292 c.order = Ordering::Equal;
293 }
294 }
295 }
296 }
297
298 pub fn sort_by(&mut self, column: H, order: Ordering) {
301 if !self.sortable {
302 return;
303 }
304
305 if self.column_indicies.contains_key(&column) {
306 for c in &mut self.columns {
307 let selected = c.column == column;
309 c.selected = selected;
310 c.order = if selected { order } else { Ordering::Equal };
311 }
312 }
313
314 self.sort_items(column, order);
315 }
316
317 pub fn sort(&mut self) {
320 if !self.sortable || self.items.len() <= 1 {
321 return;
322 }
323
324 if let Some((column, order)) = self.order() {
325 self.sort_items(column, order);
326 }
327 }
328
329 pub fn order(&self) -> Option<(H, Ordering)> {
335 for c in &self.columns {
336 if c.order != Ordering::Equal {
337 return Some((c.column, c.order));
338 }
339 }
340 None
341 }
342
343 pub fn disable(&mut self) {
347 self.enabled = false;
348 }
349
350 pub fn enable(&mut self) {
352 self.enabled = true;
353 }
354
355 pub fn set_enabled(&mut self, enabled: bool) {
357 self.enabled = enabled;
358 }
359
360 pub fn set_sortable(&mut self, sortable: bool) {
362 self.sortable = sortable;
363 self.column_select &= sortable;
364 }
365
366 pub fn sortable(self, sortable: bool) -> Self {
370 self.with(|t| t.set_sortable(sortable))
371 }
372
373 pub fn is_sortable(&self) -> bool {
375 self.sortable
376 }
377
378 pub fn is_enabled(&self) -> bool {
380 self.enabled
381 }
382
383 pub fn set_on_sort<F>(&mut self, cb: F)
394 where
395 F: Fn(&mut Cursive, H, Ordering) + Send + Sync + 'static,
396 {
397 self.on_sort = Some(Arc::new(move |s, h, o| cb(s, h, o)));
398 }
399
400 pub fn on_sort<F>(self, cb: F) -> Self
413 where
414 F: Fn(&mut Cursive, H, Ordering) + Send + Sync + 'static,
415 {
416 self.with(|t| t.set_on_sort(cb))
417 }
418
419 pub fn set_on_submit<F>(&mut self, cb: F)
433 where
434 F: Fn(&mut Cursive, usize, usize) + Send + Sync + 'static,
435 {
436 self.on_submit = Some(Arc::new(move |s, row, index| cb(s, row, index)));
437 }
438
439 pub fn on_submit<F>(self, cb: F) -> Self
455 where
456 F: Fn(&mut Cursive, usize, usize) + Send + Sync + 'static,
457 {
458 self.with(|t| t.set_on_submit(cb))
459 }
460
461 pub fn set_on_select<F>(&mut self, cb: F)
474 where
475 F: Fn(&mut Cursive, usize, usize) + Send + Sync + 'static,
476 {
477 self.on_select = Some(Arc::new(move |s, row, index| cb(s, row, index)));
478 }
479
480 pub fn on_select<F>(self, cb: F) -> Self
495 where
496 F: Fn(&mut Cursive, usize, usize) + Send + Sync + 'static,
497 {
498 self.with(|t| t.set_on_select(cb))
499 }
500
501 pub fn clear(&mut self) {
503 self.items.clear();
504 self.rows_to_items.clear();
505 self.focus = 0;
506 self.needs_relayout = true;
507 }
508
509 pub fn len(&self) -> usize {
511 self.items.len()
512 }
513
514 pub fn is_empty(&self) -> bool {
516 self.items.is_empty()
517 }
518
519 pub fn row(&self) -> Option<usize> {
521 if self.items.is_empty() {
522 None
523 } else {
524 Some(self.focus)
525 }
526 }
527
528 pub fn set_selected_row(&mut self, row_index: usize) {
530 self.focus = row_index;
531 self.scroll_core.scroll_to_y(row_index);
532 }
533
534 pub fn selected_row(self, row_index: usize) -> Self {
538 self.with(|t| t.set_selected_row(row_index))
539 }
540
541 pub fn set_items(&mut self, items: Vec<T>) {
546 self.set_items_and_focus(items, 0);
547 }
548
549 fn set_items_and_focus(&mut self, items: Vec<T>, new_location: usize) {
550 self.items = items;
551 self.rows_to_items = Vec::with_capacity(self.items.len());
552
553 for i in 0..self.items.len() {
554 self.rows_to_items.push(i);
555 }
556
557 if let Some((column, order)) = self.order() {
558 let selected_column = self.columns.iter().find(|c| c.selected).map(|c| c.column);
560 self.sort_by(column, order);
561 if let Some(column) = selected_column {
562 for c in &mut self.columns {
563 c.selected = c.column == column;
564 }
565 }
566 }
567
568 self.set_selected_item(new_location);
569 self.needs_relayout = true;
570 }
571
572 pub fn items(self, items: Vec<T>) -> Self {
578 self.with(|t| t.set_items(items))
579 }
580
581 pub fn borrow_item(&self, index: usize) -> Option<&T> {
584 self.items.get(index)
585 }
586
587 pub fn borrow_item_mut(&mut self, index: usize) -> Option<&mut T> {
590 self.items.get_mut(index)
591 }
592
593 pub fn borrow_items(&mut self) -> &[T] {
595 &self.items
596 }
597
598 pub fn borrow_items_mut(&mut self) -> &mut [T] {
602 self.needs_relayout = true;
603 &mut self.items
604 }
605
606 pub fn item(&self) -> Option<usize> {
609 self.rows_to_items.get(self.focus).copied()
610 }
611
612 pub fn set_selected_item(&mut self, item_index: usize) {
615 if item_index < self.items.len() {
617 for (row, item) in self.rows_to_items.iter().enumerate() {
618 if *item == item_index {
619 self.focus = row;
620 self.scroll_core.scroll_to_y(row);
621 break;
622 }
623 }
624 }
625 }
626
627 pub fn selected_item(self, item_index: usize) -> Self {
632 self.with(|t| t.set_selected_item(item_index))
633 }
634
635 pub fn insert_item(&mut self, item: T) {
642 self.insert_item_at(self.items.len(), item);
643 }
644
645 pub fn insert_item_at(&mut self, index: usize, item: T) {
656 self.items.push(item);
657
658 self.rows_to_items.insert(index, self.items.len() - 1);
660
661 if let Some((column, order)) = self.order() {
662 self.sort_by(column, order);
663 }
664 self.needs_relayout = true;
665 }
666
667 pub fn remove_item(&mut self, item_index: usize) -> Option<T> {
670 if item_index < self.items.len() {
671 if let Some(selected_index) = self.item() {
673 if selected_index == item_index {
674 self.focus_up(1);
675 }
676 }
677
678 self.rows_to_items.retain(|i| *i != item_index);
680
681 for ref_index in &mut self.rows_to_items {
683 if *ref_index > item_index {
684 *ref_index -= 1;
685 }
686 }
687 self.needs_relayout = true;
688
689 Some(self.items.remove(item_index))
691 } else {
692 None
693 }
694 }
695
696 pub fn take_items(&mut self) -> Vec<T> {
698 self.set_selected_row(0);
699 self.rows_to_items.clear();
700 self.needs_relayout = true;
701 self.items.drain(0..).collect()
702 }
703}
704
705impl<T, H> TableView<T, H>
706where
707 T: TableViewItem<H>,
708 H: Eq + Hash + Copy + Clone + Send + Sync + 'static,
709{
710 fn draw_columns<C: Fn(&Printer, &TableColumn<H>)>(
711 &self,
712 printer: &Printer,
713 sep: &str,
714 callback: C,
715 ) {
716 let mut column_offset = 0;
717 let column_count = self.columns.len();
718 for (index, column) in self.columns.iter().enumerate() {
719 let printer = &printer.offset((column_offset, 0)).focused(true);
720
721 callback(printer, column);
722
723 if 1 + index < column_count {
724 printer.print((column.width + 1, 0), sep);
725 }
726
727 column_offset += column.width + 3;
728 }
729 }
730
731 fn sort_items(&mut self, column: H, order: Ordering) {
732 if !self.is_empty() {
733 let old_item = self.item();
734
735 let mut rows_to_items = self.rows_to_items.clone();
736 rows_to_items.sort_by(|a, b| {
737 if order == Ordering::Less {
738 self.items[*a].cmp(&self.items[*b], column)
739 } else {
740 self.items[*b].cmp(&self.items[*a], column)
741 }
742 });
743 self.rows_to_items = rows_to_items;
744
745 if let Some(old_item) = old_item {
746 self.set_selected_item(old_item);
747 }
748 }
749 }
750
751 fn draw_item(&self, printer: &Printer, i: usize) {
752 self.draw_columns(printer, "┆ ", |printer, column| {
753 let value = self.items[self.rows_to_items[i]].to_column(column.column);
754 column.draw_row(printer, value.as_str());
755 });
756 }
757
758 fn on_focus_change(&self) -> EventResult {
759 let row = self.row().unwrap();
760 let index = self.item().unwrap();
761 EventResult::Consumed(
762 self.on_select
763 .clone()
764 .map(|cb| Callback::from_fn(move |s| cb(s, row, index))),
765 )
766 }
767
768 fn focus_up(&mut self, n: usize) {
769 self.focus -= cmp::min(self.focus, n);
770 }
771
772 fn focus_down(&mut self, n: usize) {
773 self.focus = cmp::min(self.focus + n, self.items.len().saturating_sub(1));
774 }
775
776 fn active_column(&self) -> usize {
777 self.columns.iter().position(|c| c.selected).unwrap_or(0)
778 }
779
780 fn column_cancel(&mut self) {
781 self.column_select = false;
782 for column in &mut self.columns {
783 column.selected = column.order != Ordering::Equal;
784 }
785 }
786
787 fn column_next(&mut self) -> bool {
788 let column = self.active_column();
789 if 1 + column < self.columns.len() {
790 self.columns[column].selected = false;
791 self.columns[column + 1].selected = true;
792 true
793 } else {
794 false
795 }
796 }
797
798 fn column_prev(&mut self) -> bool {
799 let column = self.active_column();
800 if column > 0 {
801 self.columns[column].selected = false;
802 self.columns[column - 1].selected = true;
803 true
804 } else {
805 false
806 }
807 }
808
809 fn column_select(&mut self) -> EventResult {
810 if !self.sortable {
811 self.column_cancel();
812 return EventResult::Ignored;
813 }
814
815 let next = self.active_column();
816 let column = self.columns[next].column;
817 let current = self
818 .columns
819 .iter()
820 .position(|c| c.order != Ordering::Equal)
821 .unwrap_or(0);
822
823 let order = if current != next {
824 self.columns[next].default_order
825 } else if self.columns[current].order == Ordering::Less {
826 Ordering::Greater
827 } else {
828 Ordering::Less
829 };
830
831 self.sort_by(column, order);
832
833 if self.on_sort.is_some() {
834 let c = &self.columns[self.active_column()];
835 let column = c.column;
836 let order = c.order;
837
838 let cb = self.on_sort.clone().unwrap();
839 EventResult::with_cb(move |s| cb(s, column, order))
840 } else {
841 EventResult::Consumed(None)
842 }
843 }
844
845 fn column_for_x(&self, mut x: usize) -> Option<usize> {
846 for (i, col) in self.columns.iter().enumerate() {
847 x = match x.checked_sub(col.width) {
848 None => return Some(i),
849 Some(x) => x.checked_sub(3)?,
850 };
851 }
852
853 None
854 }
855
856 fn draw_content(&self, printer: &Printer) {
857 for i in 0..self.rows_to_items.len() {
858 let printer = printer.offset((0, i));
859 let color = if i == self.focus && self.enabled {
860 if !self.column_select && self.enabled && printer.focused {
861 theme::ColorStyle::highlight()
862 } else {
863 theme::ColorStyle::highlight_inactive()
864 }
865 } else {
866 theme::ColorStyle::primary()
867 };
868
869 if i < self.items.len() {
870 printer.with_color(color, |printer| {
871 self.draw_item(printer, i);
872 });
873 }
874 }
875 }
876
877 fn layout_content(&mut self, size: Vec2) {
878 let column_count = self.columns.len();
879
880 let (mut sized, mut usized): (Vec<&mut TableColumn<H>>, Vec<&mut TableColumn<H>>) = self
882 .columns
883 .iter_mut()
884 .partition(|c| c.requested_width.is_some());
885
886 let available_width = size.x.saturating_sub(column_count.saturating_sub(1) * 3);
888
889 let mut remaining_width = available_width;
891 for column in &mut sized {
892 column.width = match *column.requested_width.as_ref().unwrap() {
893 TableColumnWidth::Percent(width) => cmp::min(
894 (size.x as f32 / 100.0 * width as f32).ceil() as usize,
895 remaining_width,
896 ),
897 TableColumnWidth::Absolute(width) => width,
898 };
899 remaining_width = remaining_width.saturating_sub(column.width);
900 }
901
902 let remaining_columns = usized.len();
904 for column in &mut usized {
905 column.width = (remaining_width as f32 / remaining_columns as f32).floor() as usize;
906 }
907
908 self.needs_relayout = false;
909 }
910
911 fn content_required_size(&mut self, req: Vec2) -> Vec2 {
912 Vec2::new(req.x, self.rows_to_items.len())
913 }
914
915 fn on_inner_event(&mut self, event: Event) -> EventResult {
916 let last_focus = self.focus;
917 match event {
918 Event::Key(Key::Right) => {
919 if !self.sortable {
920 return EventResult::Ignored;
921 }
922
923 if self.column_select {
924 if !self.column_next() {
925 return EventResult::Ignored;
926 }
927 } else {
928 self.column_select = true;
929 }
930 }
931 Event::Key(Key::Left) => {
932 if !self.sortable {
933 return EventResult::Ignored;
934 }
935
936 if self.column_select {
937 if !self.column_prev() {
938 return EventResult::Ignored;
939 }
940 } else {
941 self.column_select = true;
942 }
943 }
944 Event::Key(Key::Up) if self.focus > 0 || self.column_select => {
945 if self.column_select {
946 self.column_cancel();
947 } else {
948 self.focus_up(1);
949 }
950 }
951 Event::Key(Key::Down) if self.focus + 1 < self.items.len() || self.column_select => {
952 if self.column_select {
953 self.column_cancel();
954 } else {
955 self.focus_down(1);
956 }
957 }
958 Event::Key(Key::PageUp) => {
959 self.column_cancel();
960 self.focus_up(10);
961 }
962 Event::Key(Key::PageDown) => {
963 self.column_cancel();
964 self.focus_down(10);
965 }
966 Event::Key(Key::Home) => {
967 self.column_cancel();
968 self.focus = 0;
969 }
970 Event::Key(Key::End) => {
971 self.column_cancel();
972 self.focus = self.items.len().saturating_sub(1);
973 }
974 Event::Key(Key::Enter) => {
975 if self.column_select && self.sortable {
976 return self.column_select();
977 } else if !self.is_empty() && self.on_submit.is_some() {
978 return self.on_submit_event();
979 }
980 }
981 Event::Mouse {
982 position,
983 offset,
984 event: MouseEvent::Press(MouseButton::Left),
985 } if !self.is_empty()
986 && position
987 .checked_sub(offset)
988 .map_or(false, |p| p.y == self.focus) =>
989 {
990 self.column_cancel();
991 return self.on_submit_event();
992 }
993 Event::Mouse {
994 position,
995 offset,
996 event: MouseEvent::Press(_),
997 } if !self.is_empty() => match position.checked_sub(offset) {
998 Some(position) if position.y < self.rows_to_items.len() => {
999 self.column_cancel();
1000 self.focus = position.y;
1001 }
1002 _ => return EventResult::Ignored,
1003 },
1004 _ => return EventResult::Ignored,
1005 }
1006
1007 let focus = self.focus;
1008
1009 if self.column_select {
1010 EventResult::Consumed(None)
1011 } else if !self.is_empty() && last_focus != focus {
1012 self.on_focus_change()
1013 } else {
1014 EventResult::Ignored
1015 }
1016 }
1017
1018 fn inner_important_area(&self, size: Vec2) -> Rect {
1019 Rect::from_size((0, self.focus), (size.x, 1))
1020 }
1021
1022 fn on_submit_event(&mut self) -> EventResult {
1023 if let Some(cb) = &self.on_submit {
1024 let cb = Arc::clone(cb);
1025 let row = self.row().unwrap();
1026 let index = self.item().unwrap();
1027 return EventResult::Consumed(Some(Callback::from_fn(move |s| cb(s, row, index))));
1028 }
1029 EventResult::Ignored
1030 }
1031}
1032
1033impl<T, H> View for TableView<T, H>
1034where
1035 T: TableViewItem<H> + Send + Sync + 'static,
1036 H: Eq + Hash + Copy + Clone + Send + Sync + 'static,
1037{
1038 fn draw(&self, printer: &Printer) {
1039 self.draw_columns(printer, "╷ ", |printer, column| {
1040 let color = if self.enabled
1041 && self.sortable
1042 && (column.order != Ordering::Equal || column.selected)
1043 {
1044 if self.column_select && column.selected && printer.focused {
1045 theme::ColorStyle::highlight()
1046 } else {
1047 theme::ColorStyle::highlight_inactive()
1048 }
1049 } else {
1050 theme::ColorStyle::primary()
1051 };
1052
1053 printer.with_color(color, |printer| {
1054 column.draw_header(printer, self.sortable);
1055 });
1056 });
1057
1058 self.draw_columns(
1059 &printer.offset((0, 1)).focused(true),
1060 "┴─",
1061 |printer, column| {
1062 printer.print_hline((0, 0), column.width + 1, "─");
1063 },
1064 );
1065
1066 let available_height = printer.size.y.saturating_sub(2);
1068 let filled_rows = self.rows_to_items.len().min(available_height);
1069 for y in 2 + filled_rows..printer.size.y {
1070 self.draw_columns(&printer.offset((0, y)), "┆ ", |_, _| ());
1071 }
1072
1073 let printer = &printer.offset((0, 2)).focused(true);
1074 scroll::draw(self, printer, Self::draw_content);
1075 }
1076
1077 fn layout(&mut self, size: Vec2) {
1078 scroll::layout(
1079 self,
1080 size.saturating_sub((0, 2)),
1081 self.needs_relayout,
1082 Self::layout_content,
1083 Self::content_required_size,
1084 );
1085 }
1086
1087 fn take_focus(&mut self, _: Direction) -> Result<EventResult, CannotFocus> {
1088 self.enabled.then(EventResult::consumed).ok_or(CannotFocus)
1089 }
1090
1091 fn on_event(&mut self, event: Event) -> EventResult {
1092 if !self.enabled {
1093 return EventResult::Ignored;
1094 }
1095
1096 match event {
1097 Event::Mouse {
1098 position,
1099 offset,
1100 event: MouseEvent::Press(MouseButton::Left),
1101 } if position.checked_sub(offset).map_or(false, |p| p.y == 0) => {
1102 if !self.sortable {
1103 return EventResult::Ignored;
1104 }
1105
1106 if let Some(position) = position.checked_sub(offset) {
1107 if let Some(col) = self.column_for_x(position.x) {
1108 if self.column_select && self.columns[col].selected {
1109 return self.column_select();
1110 } else {
1111 let active = self.active_column();
1112 self.columns[active].selected = false;
1113 self.columns[col].selected = true;
1114 self.column_select = true;
1115 }
1116 }
1117 }
1118 EventResult::Ignored
1119 }
1120 event => scroll::on_event(
1121 self,
1122 event.relativized((0, 2)),
1123 Self::on_inner_event,
1124 Self::inner_important_area,
1125 ),
1126 }
1127 }
1128
1129 fn important_area(&self, size: Vec2) -> Rect {
1130 self.inner_important_area(size.saturating_sub((0, 2))) + (0, 2)
1131 }
1132}
1133
1134pub struct TableColumn<H> {
1137 column: H,
1138 title: String,
1139 selected: bool,
1140 alignment: HAlign,
1141 order: Ordering,
1142 width: usize,
1143 default_order: Ordering,
1144 requested_width: Option<TableColumnWidth>,
1145}
1146
1147enum TableColumnWidth {
1148 Percent(usize),
1149 Absolute(usize),
1150}
1151
1152impl<H: Copy + Clone + 'static> TableColumn<H> {
1153 pub fn ordering(mut self, order: Ordering) -> Self {
1155 self.default_order = order;
1156 self
1157 }
1158
1159 pub fn align(mut self, alignment: HAlign) -> Self {
1161 self.alignment = alignment;
1162 self
1163 }
1164
1165 pub fn width(mut self, width: usize) -> Self {
1167 self.requested_width = Some(TableColumnWidth::Absolute(width));
1168 self
1169 }
1170
1171 pub fn width_percent(mut self, width: usize) -> Self {
1174 self.requested_width = Some(TableColumnWidth::Percent(width));
1175 self
1176 }
1177
1178 fn new(column: H, title: String) -> Self {
1179 Self {
1180 column,
1181 title,
1182 selected: false,
1183 alignment: HAlign::Left,
1184 order: Ordering::Equal,
1185 width: 0,
1186 default_order: Ordering::Less,
1187 requested_width: None,
1188 }
1189 }
1190
1191 fn draw_header(&self, printer: &Printer, sortable: bool) {
1192 let title_width = if sortable {
1193 self.width.saturating_sub(4)
1194 } else {
1195 self.width
1196 };
1197 let title = self.title.as_str();
1198
1199 let mut header = match self.alignment {
1200 HAlign::Left => format!("{:<width$}", title, width = title_width),
1201 HAlign::Right => format!("{:>width$}", title, width = title_width),
1202 HAlign::Center => format!("{:^width$}", title, width = title_width),
1203 };
1204
1205 if sortable {
1206 let order = match self.order {
1207 Ordering::Less => "^",
1208 Ordering::Greater => "v",
1209 Ordering::Equal => " ",
1210 };
1211 header.push_str(" [");
1212 header.push_str(order);
1213 header.push(']');
1214 }
1215
1216 printer.print((0, 0), header.as_str());
1217 }
1218
1219 fn draw_row(&self, printer: &Printer, value: &str) {
1220 let value = match self.alignment {
1221 HAlign::Left => format!("{:<width$} ", value, width = self.width),
1222 HAlign::Right => format!("{:>width$} ", value, width = self.width),
1223 HAlign::Center => format!("{:^width$} ", value, width = self.width),
1224 };
1225
1226 printer.print((0, 0), value.as_str());
1227 }
1228}
1229
1230#[cfg(test)]
1231mod tests {
1232 use super::*;
1233
1234 #[derive(Copy, Clone, PartialEq, Eq, Hash)]
1235 enum SimpleColumn {
1236 Name,
1237 }
1238
1239 #[allow(dead_code)]
1240 impl SimpleColumn {
1241 fn as_str(&self) -> &str {
1242 match *self {
1243 SimpleColumn::Name => "Name",
1244 }
1245 }
1246 }
1247
1248 #[derive(Clone, Debug)]
1249 struct SimpleItem {
1250 name: String,
1251 }
1252
1253 impl TableViewItem<SimpleColumn> for SimpleItem {
1254 fn to_column(&self, column: SimpleColumn) -> String {
1255 match column {
1256 SimpleColumn::Name => self.name.to_string(),
1257 }
1258 }
1259
1260 fn cmp(&self, other: &Self, column: SimpleColumn) -> Ordering
1261 where
1262 Self: Sized,
1263 {
1264 match column {
1265 SimpleColumn::Name => self.name.cmp(&other.name),
1266 }
1267 }
1268 }
1269
1270 fn setup_test_table() -> TableView<SimpleItem, SimpleColumn> {
1271 TableView::<SimpleItem, SimpleColumn>::new()
1272 .column(SimpleColumn::Name, "Name", |c| c.width_percent(20))
1273 }
1274
1275 #[test]
1276 fn should_insert_into_existing_table() {
1277 let mut simple_table = setup_test_table();
1278
1279 let mut simple_items = Vec::new();
1280
1281 for i in 1..=10 {
1282 simple_items.push(SimpleItem {
1283 name: format!("{} - Name", i),
1284 });
1285 }
1286
1287 simple_table.set_items(simple_items);
1289
1290 simple_table.insert_item(SimpleItem {
1292 name: format!("{} Name", 11),
1293 });
1294
1295 assert!(simple_table.len() == 11);
1296 }
1297
1298 #[test]
1299 fn should_insert_into_empty_table() {
1300 let mut simple_table = setup_test_table();
1301
1302 simple_table.insert_item(SimpleItem {
1304 name: format!("{} Name", 1),
1305 });
1306
1307 assert!(simple_table.len() == 1);
1308 }
1309}