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 scroll_core: scroll::Core,
120 needs_relayout: bool,
121
122 column_select: bool,
123 columns: Vec<TableColumn<H>>,
124 column_indicies: HashMap<H, usize>,
125
126 focus: usize,
127 items: Vec<T>,
128 rows_to_items: Vec<usize>,
129
130 on_sort: Option<OnSortCallback<H>>,
131 on_submit: Option<IndexCallback>,
134 on_select: Option<IndexCallback>,
135}
136
137cursive::impl_scroller!(TableView < T, H > ::scroll_core);
138
139impl<T, H> Default for TableView<T, H>
140where
141 T: TableViewItem<H> + PartialEq,
142 H: Eq + Hash + Copy + Clone + Send + Sync + 'static,
143{
144 fn default() -> Self {
148 Self::new()
149 }
150}
151
152impl<T, H> TableView<T, H>
153where
154 T: TableViewItem<H> + PartialEq,
155 H: Eq + Hash + Copy + Clone + Send + Sync + 'static,
156{
157 pub fn set_items_stable(&mut self, items: Vec<T>) {
165 let new_location = self
167 .item()
168 .and_then(|old_item| {
169 let old_item = &self.items[old_item];
170 items.iter().position(|new| new == old_item)
171 })
172 .unwrap_or(0);
173
174 self.set_items_and_focus(items, new_location);
175 }
176}
177
178impl<T, H> TableView<T, H>
179where
180 T: TableViewItem<H>,
181 H: Eq + Hash + Copy + Clone + Send + Sync + 'static,
182{
183 pub fn new() -> Self {
188 Self {
189 enabled: true,
190 scroll_core: scroll::Core::new(),
191 needs_relayout: true,
192
193 column_select: false,
194 columns: Vec::new(),
195 column_indicies: HashMap::new(),
196
197 focus: 0,
198 items: Vec::new(),
199 rows_to_items: Vec::new(),
200
201 on_sort: None,
202 on_submit: None,
203 on_select: None,
204 }
205 }
206
207 pub fn column<S: Into<String>, C: FnOnce(TableColumn<H>) -> TableColumn<H>>(
213 mut self,
214 column: H,
215 title: S,
216 callback: C,
217 ) -> Self {
218 self.add_column(column, title, callback);
219 self
220 }
221
222 pub fn add_column<S: Into<String>, C: FnOnce(TableColumn<H>) -> TableColumn<H>>(
228 &mut self,
229 column: H,
230 title: S,
231 callback: C,
232 ) {
233 self.insert_column(self.columns.len(), column, title, callback);
234 }
235
236 pub fn remove_column(&mut self, i: usize) {
238 for column in &self.columns[i + 1..] {
240 *self.column_indicies.get_mut(&column.column).unwrap() -= 1;
241 }
242
243 let column = self.columns.remove(i);
244 self.column_indicies.remove(&column.column);
245 self.needs_relayout = true;
246 }
247
248 pub fn insert_column<S: Into<String>, C: FnOnce(TableColumn<H>) -> TableColumn<H>>(
254 &mut self,
255 i: usize,
256 column: H,
257 title: S,
258 callback: C,
259 ) {
260 for column in &self.columns[i..] {
262 *self.column_indicies.get_mut(&column.column).unwrap() += 1;
263 }
264
265 self.column_indicies.insert(column, i);
266 self.columns
267 .insert(i, callback(TableColumn::new(column, title.into())));
268
269 if self.columns.len() == 1 {
271 self.set_default_column(column);
272 }
273 self.needs_relayout = true;
274 }
275
276 pub fn default_column(mut self, column: H) -> Self {
278 self.set_default_column(column);
279 self
280 }
281
282 pub fn set_default_column(&mut self, column: H) {
284 if self.column_indicies.contains_key(&column) {
285 for c in &mut self.columns {
286 c.selected = c.column == column;
287 if c.selected {
288 c.order = c.default_order;
289 } else {
290 c.order = Ordering::Equal;
291 }
292 }
293 }
294 }
295
296 pub fn sort_by(&mut self, column: H, order: Ordering) {
299 if self.column_indicies.contains_key(&column) {
300 for c in &mut self.columns {
301 c.selected = c.column == column;
303 if c.selected {
304 c.order = order;
305 } else {
306 c.order = Ordering::Equal;
307 }
308 }
309 }
310
311 self.sort_items(column, order);
312 }
313
314 pub fn sort(&mut self) {
317 if let Some((column, order)) = self.order() {
318 self.sort_items(column, order);
319 }
320 }
321
322 pub fn order(&self) -> Option<(H, Ordering)> {
328 for c in &self.columns {
329 if c.order != Ordering::Equal {
330 return Some((c.column, c.order));
331 }
332 }
333 None
334 }
335
336 pub fn disable(&mut self) {
340 self.enabled = false;
341 }
342
343 pub fn enable(&mut self) {
345 self.enabled = true;
346 }
347
348 pub fn set_enabled(&mut self, enabled: bool) {
350 self.enabled = enabled;
351 }
352
353 pub fn is_enabled(&self) -> bool {
355 self.enabled
356 }
357
358 pub fn set_on_sort<F>(&mut self, cb: F)
369 where
370 F: Fn(&mut Cursive, H, Ordering) + Send + Sync + 'static,
371 {
372 self.on_sort = Some(Arc::new(move |s, h, o| cb(s, h, o)));
373 }
374
375 pub fn on_sort<F>(self, cb: F) -> Self
388 where
389 F: Fn(&mut Cursive, H, Ordering) + Send + Sync + 'static,
390 {
391 self.with(|t| t.set_on_sort(cb))
392 }
393
394 pub fn set_on_submit<F>(&mut self, cb: F)
408 where
409 F: Fn(&mut Cursive, usize, usize) + Send + Sync + 'static,
410 {
411 self.on_submit = Some(Arc::new(move |s, row, index| cb(s, row, index)));
412 }
413
414 pub fn on_submit<F>(self, cb: F) -> Self
430 where
431 F: Fn(&mut Cursive, usize, usize) + Send + Sync + 'static,
432 {
433 self.with(|t| t.set_on_submit(cb))
434 }
435
436 pub fn set_on_select<F>(&mut self, cb: F)
449 where
450 F: Fn(&mut Cursive, usize, usize) + Send + Sync + 'static,
451 {
452 self.on_select = Some(Arc::new(move |s, row, index| cb(s, row, index)));
453 }
454
455 pub fn on_select<F>(self, cb: F) -> Self
470 where
471 F: Fn(&mut Cursive, usize, usize) + Send + Sync + 'static,
472 {
473 self.with(|t| t.set_on_select(cb))
474 }
475
476 pub fn clear(&mut self) {
478 self.items.clear();
479 self.rows_to_items.clear();
480 self.focus = 0;
481 self.needs_relayout = true;
482 }
483
484 pub fn len(&self) -> usize {
486 self.items.len()
487 }
488
489 pub fn is_empty(&self) -> bool {
491 self.items.is_empty()
492 }
493
494 pub fn row(&self) -> Option<usize> {
496 if self.items.is_empty() {
497 None
498 } else {
499 Some(self.focus)
500 }
501 }
502
503 pub fn set_selected_row(&mut self, row_index: usize) {
505 self.focus = row_index;
506 self.scroll_core.scroll_to_y(row_index);
507 }
508
509 pub fn selected_row(self, row_index: usize) -> Self {
513 self.with(|t| t.set_selected_row(row_index))
514 }
515
516 pub fn set_items(&mut self, items: Vec<T>) {
521 self.set_items_and_focus(items, 0);
522 }
523
524 fn set_items_and_focus(&mut self, items: Vec<T>, new_location: usize) {
525 self.items = items;
526 self.rows_to_items = Vec::with_capacity(self.items.len());
527
528 for i in 0..self.items.len() {
529 self.rows_to_items.push(i);
530 }
531
532 if let Some((column, order)) = self.order() {
533 let selected_column = self.columns.iter().find(|c| c.selected).map(|c| c.column);
535 self.sort_by(column, order);
536 if let Some(column) = selected_column {
537 for c in &mut self.columns {
538 c.selected = c.column == column;
539 }
540 }
541 }
542
543 self.set_selected_item(new_location);
544 self.needs_relayout = true;
545 }
546
547 pub fn items(self, items: Vec<T>) -> Self {
553 self.with(|t| t.set_items(items))
554 }
555
556 pub fn borrow_item(&self, index: usize) -> Option<&T> {
559 self.items.get(index)
560 }
561
562 pub fn borrow_item_mut(&mut self, index: usize) -> Option<&mut T> {
565 self.items.get_mut(index)
566 }
567
568 pub fn borrow_items(&mut self) -> &[T] {
570 &self.items
571 }
572
573 pub fn borrow_items_mut(&mut self) -> &mut [T] {
577 self.needs_relayout = true;
578 &mut self.items
579 }
580
581 pub fn item(&self) -> Option<usize> {
584 self.rows_to_items.get(self.focus).copied()
585 }
586
587 pub fn set_selected_item(&mut self, item_index: usize) {
590 if item_index < self.items.len() {
592 for (row, item) in self.rows_to_items.iter().enumerate() {
593 if *item == item_index {
594 self.focus = row;
595 self.scroll_core.scroll_to_y(row);
596 break;
597 }
598 }
599 }
600 }
601
602 pub fn selected_item(self, item_index: usize) -> Self {
607 self.with(|t| t.set_selected_item(item_index))
608 }
609
610 pub fn insert_item(&mut self, item: T) {
617 self.insert_item_at(self.items.len(), item);
618 }
619
620 pub fn insert_item_at(&mut self, index: usize, item: T) {
631 self.items.push(item);
632
633 self.rows_to_items.insert(index, self.items.len() - 1);
635
636 if let Some((column, order)) = self.order() {
637 self.sort_by(column, order);
638 }
639 self.needs_relayout = true;
640 }
641
642 pub fn remove_item(&mut self, item_index: usize) -> Option<T> {
645 if item_index < self.items.len() {
646 if let Some(selected_index) = self.item() {
648 if selected_index == item_index {
649 self.focus_up(1);
650 }
651 }
652
653 self.rows_to_items.retain(|i| *i != item_index);
655
656 for ref_index in &mut self.rows_to_items {
658 if *ref_index > item_index {
659 *ref_index -= 1;
660 }
661 }
662 self.needs_relayout = true;
663
664 Some(self.items.remove(item_index))
666 } else {
667 None
668 }
669 }
670
671 pub fn take_items(&mut self) -> Vec<T> {
673 self.set_selected_row(0);
674 self.rows_to_items.clear();
675 self.needs_relayout = true;
676 self.items.drain(0..).collect()
677 }
678}
679
680impl<T, H> TableView<T, H>
681where
682 T: TableViewItem<H>,
683 H: Eq + Hash + Copy + Clone + Send + Sync + 'static,
684{
685 fn draw_columns<C: Fn(&Printer, &TableColumn<H>)>(
686 &self,
687 printer: &Printer,
688 sep: &str,
689 callback: C,
690 ) {
691 let mut column_offset = 0;
692 let column_count = self.columns.len();
693 for (index, column) in self.columns.iter().enumerate() {
694 let printer = &printer.offset((column_offset, 0)).focused(true);
695
696 callback(printer, column);
697
698 if 1 + index < column_count {
699 printer.print((column.width + 1, 0), sep);
700 }
701
702 column_offset += column.width + 3;
703 }
704 }
705
706 fn sort_items(&mut self, column: H, order: Ordering) {
707 if !self.is_empty() {
708 let old_item = self.item();
709
710 let mut rows_to_items = self.rows_to_items.clone();
711 rows_to_items.sort_by(|a, b| {
712 if order == Ordering::Less {
713 self.items[*a].cmp(&self.items[*b], column)
714 } else {
715 self.items[*b].cmp(&self.items[*a], column)
716 }
717 });
718 self.rows_to_items = rows_to_items;
719
720 if let Some(old_item) = old_item {
721 self.set_selected_item(old_item);
722 }
723 }
724 }
725
726 fn draw_item(&self, printer: &Printer, i: usize) {
727 self.draw_columns(printer, "┆ ", |printer, column| {
728 let value = self.items[self.rows_to_items[i]].to_column(column.column);
729 column.draw_row(printer, value.as_str());
730 });
731 }
732
733 fn on_focus_change(&self) -> EventResult {
734 let row = self.row().unwrap();
735 let index = self.item().unwrap();
736 EventResult::Consumed(
737 self.on_select
738 .clone()
739 .map(|cb| Callback::from_fn(move |s| cb(s, row, index))),
740 )
741 }
742
743 fn focus_up(&mut self, n: usize) {
744 self.focus -= cmp::min(self.focus, n);
745 }
746
747 fn focus_down(&mut self, n: usize) {
748 self.focus = cmp::min(self.focus + n, self.items.len().saturating_sub(1));
749 }
750
751 fn active_column(&self) -> usize {
752 self.columns.iter().position(|c| c.selected).unwrap_or(0)
753 }
754
755 fn column_cancel(&mut self) {
756 self.column_select = false;
757 for column in &mut self.columns {
758 column.selected = column.order != Ordering::Equal;
759 }
760 }
761
762 fn column_next(&mut self) -> bool {
763 let column = self.active_column();
764 if 1 + column < self.columns.len() {
765 self.columns[column].selected = false;
766 self.columns[column + 1].selected = true;
767 true
768 } else {
769 false
770 }
771 }
772
773 fn column_prev(&mut self) -> bool {
774 let column = self.active_column();
775 if column > 0 {
776 self.columns[column].selected = false;
777 self.columns[column - 1].selected = true;
778 true
779 } else {
780 false
781 }
782 }
783
784 fn column_select(&mut self) -> EventResult {
785 let next = self.active_column();
786 let column = self.columns[next].column;
787 let current = self
788 .columns
789 .iter()
790 .position(|c| c.order != Ordering::Equal)
791 .unwrap_or(0);
792
793 let order = if current != next {
794 self.columns[next].default_order
795 } else if self.columns[current].order == Ordering::Less {
796 Ordering::Greater
797 } else {
798 Ordering::Less
799 };
800
801 self.sort_by(column, order);
802
803 if self.on_sort.is_some() {
804 let c = &self.columns[self.active_column()];
805 let column = c.column;
806 let order = c.order;
807
808 let cb = self.on_sort.clone().unwrap();
809 EventResult::with_cb(move |s| cb(s, column, order))
810 } else {
811 EventResult::Consumed(None)
812 }
813 }
814
815 fn column_for_x(&self, mut x: usize) -> Option<usize> {
816 for (i, col) in self.columns.iter().enumerate() {
817 x = match x.checked_sub(col.width) {
818 None => return Some(i),
819 Some(x) => x.checked_sub(3)?,
820 };
821 }
822
823 None
824 }
825
826 fn draw_content(&self, printer: &Printer) {
827 for i in 0..self.rows_to_items.len() {
828 let printer = printer.offset((0, i));
829 let color = if i == self.focus && self.enabled {
830 if !self.column_select && self.enabled && printer.focused {
831 theme::ColorStyle::highlight()
832 } else {
833 theme::ColorStyle::highlight_inactive()
834 }
835 } else {
836 theme::ColorStyle::primary()
837 };
838
839 if i < self.items.len() {
840 printer.with_color(color, |printer| {
841 self.draw_item(printer, i);
842 });
843 }
844 }
845 }
846
847 fn layout_content(&mut self, size: Vec2) {
848 let column_count = self.columns.len();
849
850 let (mut sized, mut usized): (Vec<&mut TableColumn<H>>, Vec<&mut TableColumn<H>>) = self
852 .columns
853 .iter_mut()
854 .partition(|c| c.requested_width.is_some());
855
856 let available_width = size.x.saturating_sub(column_count.saturating_sub(1) * 3);
858
859 let mut remaining_width = available_width;
861 for column in &mut sized {
862 column.width = match *column.requested_width.as_ref().unwrap() {
863 TableColumnWidth::Percent(width) => cmp::min(
864 (size.x as f32 / 100.0 * width as f32).ceil() as usize,
865 remaining_width,
866 ),
867 TableColumnWidth::Absolute(width) => width,
868 };
869 remaining_width = remaining_width.saturating_sub(column.width);
870 }
871
872 let remaining_columns = usized.len();
874 for column in &mut usized {
875 column.width = (remaining_width as f32 / remaining_columns as f32).floor() as usize;
876 }
877
878 self.needs_relayout = false;
879 }
880
881 fn content_required_size(&mut self, req: Vec2) -> Vec2 {
882 Vec2::new(req.x, self.rows_to_items.len())
883 }
884
885 fn on_inner_event(&mut self, event: Event) -> EventResult {
886 let last_focus = self.focus;
887 match event {
888 Event::Key(Key::Right) => {
889 if self.column_select {
890 if !self.column_next() {
891 return EventResult::Ignored;
892 }
893 } else {
894 self.column_select = true;
895 }
896 }
897 Event::Key(Key::Left) => {
898 if self.column_select {
899 if !self.column_prev() {
900 return EventResult::Ignored;
901 }
902 } else {
903 self.column_select = true;
904 }
905 }
906 Event::Key(Key::Up) if self.focus > 0 || self.column_select => {
907 if self.column_select {
908 self.column_cancel();
909 } else {
910 self.focus_up(1);
911 }
912 }
913 Event::Key(Key::Down) if self.focus + 1 < self.items.len() || self.column_select => {
914 if self.column_select {
915 self.column_cancel();
916 } else {
917 self.focus_down(1);
918 }
919 }
920 Event::Key(Key::PageUp) => {
921 self.column_cancel();
922 self.focus_up(10);
923 }
924 Event::Key(Key::PageDown) => {
925 self.column_cancel();
926 self.focus_down(10);
927 }
928 Event::Key(Key::Home) => {
929 self.column_cancel();
930 self.focus = 0;
931 }
932 Event::Key(Key::End) => {
933 self.column_cancel();
934 self.focus = self.items.len().saturating_sub(1);
935 }
936 Event::Key(Key::Enter) => {
937 if self.column_select {
938 return self.column_select();
939 } else if !self.is_empty() && self.on_submit.is_some() {
940 return self.on_submit_event();
941 }
942 }
943 Event::Mouse {
944 position,
945 offset,
946 event: MouseEvent::Press(MouseButton::Left),
947 } if !self.is_empty()
948 && position
949 .checked_sub(offset)
950 .map_or(false, |p| p.y == self.focus) =>
951 {
952 self.column_cancel();
953 return self.on_submit_event();
954 }
955 Event::Mouse {
956 position,
957 offset,
958 event: MouseEvent::Press(_),
959 } if !self.is_empty() => match position.checked_sub(offset) {
960 Some(position) if position.y < self.rows_to_items.len() => {
961 self.column_cancel();
962 self.focus = position.y;
963 }
964 _ => return EventResult::Ignored,
965 },
966 _ => return EventResult::Ignored,
967 }
968
969 let focus = self.focus;
970
971 if self.column_select {
972 EventResult::Consumed(None)
973 } else if !self.is_empty() && last_focus != focus {
974 self.on_focus_change()
975 } else {
976 EventResult::Ignored
977 }
978 }
979
980 fn inner_important_area(&self, size: Vec2) -> Rect {
981 Rect::from_size((0, self.focus), (size.x, 1))
982 }
983
984 fn on_submit_event(&mut self) -> EventResult {
985 if let Some(ref cb) = &self.on_submit {
986 let cb = Arc::clone(cb);
987 let row = self.row().unwrap();
988 let index = self.item().unwrap();
989 return EventResult::Consumed(Some(Callback::from_fn(move |s| cb(s, row, index))));
990 }
991 EventResult::Ignored
992 }
993}
994
995impl<T, H> View for TableView<T, H>
996where
997 T: TableViewItem<H> + Send + Sync + 'static,
998 H: Eq + Hash + Copy + Clone + Send + Sync + 'static,
999{
1000 fn draw(&self, printer: &Printer) {
1001 self.draw_columns(printer, "╷ ", |printer, column| {
1002 let color = if self.enabled && (column.order != Ordering::Equal || column.selected) {
1003 if self.column_select && column.selected && self.enabled && printer.focused {
1004 theme::ColorStyle::highlight()
1005 } else {
1006 theme::ColorStyle::highlight_inactive()
1007 }
1008 } else {
1009 theme::ColorStyle::primary()
1010 };
1011
1012 printer.with_color(color, |printer| {
1013 column.draw_header(printer);
1014 });
1015 });
1016
1017 self.draw_columns(
1018 &printer.offset((0, 1)).focused(true),
1019 "┴─",
1020 |printer, column| {
1021 printer.print_hline((0, 0), column.width + 1, "─");
1022 },
1023 );
1024
1025 for y in 2..printer.size.y {
1027 self.draw_columns(&printer.offset((0, y)), "┆ ", |_, _| ());
1028 }
1029
1030 let printer = &printer.offset((0, 2)).focused(true);
1031 scroll::draw(self, printer, Self::draw_content);
1032 }
1033
1034 fn layout(&mut self, size: Vec2) {
1035 scroll::layout(
1036 self,
1037 size.saturating_sub((0, 2)),
1038 self.needs_relayout,
1039 Self::layout_content,
1040 Self::content_required_size,
1041 );
1042 }
1043
1044 fn take_focus(&mut self, _: Direction) -> Result<EventResult, CannotFocus> {
1045 self.enabled.then(EventResult::consumed).ok_or(CannotFocus)
1046 }
1047
1048 fn on_event(&mut self, event: Event) -> EventResult {
1049 if !self.enabled {
1050 return EventResult::Ignored;
1051 }
1052
1053 match event {
1054 Event::Mouse {
1055 position,
1056 offset,
1057 event: MouseEvent::Press(MouseButton::Left),
1058 } if position.checked_sub(offset).map_or(false, |p| p.y == 0) => {
1059 if let Some(position) = position.checked_sub(offset) {
1060 if let Some(col) = self.column_for_x(position.x) {
1061 if self.column_select && self.columns[col].selected {
1062 return self.column_select();
1063 } else {
1064 let active = self.active_column();
1065 self.columns[active].selected = false;
1066 self.columns[col].selected = true;
1067 self.column_select = true;
1068 }
1069 }
1070 }
1071 EventResult::Ignored
1072 }
1073 event => scroll::on_event(
1074 self,
1075 event.relativized((0, 2)),
1076 Self::on_inner_event,
1077 Self::inner_important_area,
1078 ),
1079 }
1080 }
1081
1082 fn important_area(&self, size: Vec2) -> Rect {
1083 self.inner_important_area(size.saturating_sub((0, 2))) + (0, 2)
1084 }
1085}
1086
1087pub struct TableColumn<H> {
1090 column: H,
1091 title: String,
1092 selected: bool,
1093 alignment: HAlign,
1094 order: Ordering,
1095 width: usize,
1096 default_order: Ordering,
1097 requested_width: Option<TableColumnWidth>,
1098}
1099
1100enum TableColumnWidth {
1101 Percent(usize),
1102 Absolute(usize),
1103}
1104
1105impl<H: Copy + Clone + 'static> TableColumn<H> {
1106 pub fn ordering(mut self, order: Ordering) -> Self {
1108 self.default_order = order;
1109 self
1110 }
1111
1112 pub fn align(mut self, alignment: HAlign) -> Self {
1114 self.alignment = alignment;
1115 self
1116 }
1117
1118 pub fn width(mut self, width: usize) -> Self {
1120 self.requested_width = Some(TableColumnWidth::Absolute(width));
1121 self
1122 }
1123
1124 pub fn width_percent(mut self, width: usize) -> Self {
1127 self.requested_width = Some(TableColumnWidth::Percent(width));
1128 self
1129 }
1130
1131 fn new(column: H, title: String) -> Self {
1132 Self {
1133 column,
1134 title,
1135 selected: false,
1136 alignment: HAlign::Left,
1137 order: Ordering::Equal,
1138 width: 0,
1139 default_order: Ordering::Less,
1140 requested_width: None,
1141 }
1142 }
1143
1144 fn draw_header(&self, printer: &Printer) {
1145 let order = match self.order {
1146 Ordering::Less => "^",
1147 Ordering::Greater => "v",
1148 Ordering::Equal => " ",
1149 };
1150
1151 let header = match self.alignment {
1152 HAlign::Left => format!(
1153 "{:<width$} [{}]",
1154 self.title,
1155 order,
1156 width = self.width.saturating_sub(4)
1157 ),
1158 HAlign::Right => format!(
1159 "{:>width$} [{}]",
1160 self.title,
1161 order,
1162 width = self.width.saturating_sub(4)
1163 ),
1164 HAlign::Center => format!(
1165 "{:^width$} [{}]",
1166 self.title,
1167 order,
1168 width = self.width.saturating_sub(4)
1169 ),
1170 };
1171
1172 printer.print((0, 0), header.as_str());
1173 }
1174
1175 fn draw_row(&self, printer: &Printer, value: &str) {
1176 let value = match self.alignment {
1177 HAlign::Left => format!("{:<width$} ", value, width = self.width),
1178 HAlign::Right => format!("{:>width$} ", value, width = self.width),
1179 HAlign::Center => format!("{:^width$} ", value, width = self.width),
1180 };
1181
1182 printer.print((0, 0), value.as_str());
1183 }
1184}
1185
1186#[cfg(test)]
1187mod tests {
1188 use super::*;
1189
1190 #[derive(Copy, Clone, PartialEq, Eq, Hash)]
1191 enum SimpleColumn {
1192 Name,
1193 }
1194
1195 #[allow(dead_code)]
1196 impl SimpleColumn {
1197 fn as_str(&self) -> &str {
1198 match *self {
1199 SimpleColumn::Name => "Name",
1200 }
1201 }
1202 }
1203
1204 #[derive(Clone, Debug)]
1205 struct SimpleItem {
1206 name: String,
1207 }
1208
1209 impl TableViewItem<SimpleColumn> for SimpleItem {
1210 fn to_column(&self, column: SimpleColumn) -> String {
1211 match column {
1212 SimpleColumn::Name => self.name.to_string(),
1213 }
1214 }
1215
1216 fn cmp(&self, other: &Self, column: SimpleColumn) -> Ordering
1217 where
1218 Self: Sized,
1219 {
1220 match column {
1221 SimpleColumn::Name => self.name.cmp(&other.name),
1222 }
1223 }
1224 }
1225
1226 fn setup_test_table() -> TableView<SimpleItem, SimpleColumn> {
1227 TableView::<SimpleItem, SimpleColumn>::new()
1228 .column(SimpleColumn::Name, "Name", |c| c.width_percent(20))
1229 }
1230
1231 #[test]
1232 fn should_insert_into_existing_table() {
1233 let mut simple_table = setup_test_table();
1234
1235 let mut simple_items = Vec::new();
1236
1237 for i in 1..=10 {
1238 simple_items.push(SimpleItem {
1239 name: format!("{} - Name", i),
1240 });
1241 }
1242
1243 simple_table.set_items(simple_items);
1245
1246 simple_table.insert_item(SimpleItem {
1248 name: format!("{} Name", 11),
1249 });
1250
1251 assert!(simple_table.len() == 11);
1252 }
1253
1254 #[test]
1255 fn should_insert_into_empty_table() {
1256 let mut simple_table = setup_test_table();
1257
1258 simple_table.insert_item(SimpleItem {
1260 name: format!("{} Name", 1),
1261 });
1262
1263 assert!(simple_table.len() == 1);
1264 }
1265}