From 70dd6c20b59a1fb582d1baa06a051126a07295e4 Mon Sep 17 00:00:00 2001 From: Azat Khuzhin Date: Wed, 1 Nov 2023 08:02:15 +0100 Subject: [PATCH] Add ability not to follow selected item in the table It is not always useful to always select some item (i.e. first by sorting order, by the way it is not even how it works natively since sort_by() applied after, and you need to call set_selected_item(0) for this). Consider csysdig, by default when you did not press anything it stick to the first item by sort order, and only if you select something (Up/Down) then it will stick to this item in the table. And this behaviour is better, since if you did not focus any item you want to look at the items sorted by the sort order. And I also made the Home button to cancel this selection. --- examples/basic.rs | 12 +++++--- src/lib.rs | 76 +++++++++++++++++++++++++---------------------- 2 files changed, 49 insertions(+), 39 deletions(-) diff --git a/examples/basic.rs b/examples/basic.rs index 0c2c802..45238d2 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -99,19 +99,23 @@ fn main() { ); }); - table.set_on_submit(|siv: &mut Cursive, row: usize, index: usize| { + table.set_on_submit(|siv: &mut Cursive, row: Option, index: Option| { + if !index.is_some() { + return; + } + let value = siv .call_on_name("table", move |table: &mut TableView| { - format!("{:?}", table.borrow_item(index).unwrap()) + format!("{:?}", table.borrow_item(index.unwrap()).unwrap()) }) .unwrap(); siv.add_layer( Dialog::around(TextView::new(value)) - .title(format!("Removing row # {}", row)) + .title(format!("Removing row # {}", row.unwrap())) .button("Close", move |s| { s.call_on_name("table", |table: &mut TableView| { - table.remove_item(index); + table.remove_item(index.unwrap()); }); s.pop_layer(); }), diff --git a/src/lib.rs b/src/lib.rs index 20edb91..8031df2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -55,7 +55,7 @@ type OnSortCallback = Rc; /// Callback taking as argument the row and the index of an element. /// /// This is a private type to help readability. -type IndexCallback = Rc; +type IndexCallback = Rc, Option)>; /// View to select an item among a list, supporting multiple columns for sorting. /// @@ -123,7 +123,7 @@ pub struct TableView { columns: Vec>, column_indicies: HashMap, - focus: usize, + focus: Option, items: Vec, rows_to_items: Vec, @@ -168,8 +168,7 @@ where .and_then(|old_item| { let old_item = &self.items[old_item]; items.iter().position(|new| new == old_item) - }) - .unwrap_or(0); + }); self.set_items_and_focus(items, new_location); } @@ -194,7 +193,7 @@ where columns: Vec::new(), column_indicies: HashMap::new(), - focus: 0, + focus: None, items: Vec::new(), rows_to_items: Vec::new(), @@ -400,13 +399,13 @@ where /// # Example /// /// ```ignore - /// table.set_on_submit(|siv: &mut Cursive, row: usize, index: usize| { + /// table.set_on_submit(|siv: &mut Cursive, row: Option, index: Option| { /// /// }); /// ``` pub fn set_on_submit(&mut self, cb: F) where - F: Fn(&mut Cursive, usize, usize) + 'static, + F: Fn(&mut Cursive, Option, Option) + 'static, { self.on_submit = Some(Rc::new(move |s, row, index| cb(s, row, index))); } @@ -422,13 +421,13 @@ where /// # Example /// /// ```ignore - /// table.on_submit(|siv: &mut Cursive, row: usize, index: usize| { + /// table.on_submit(|siv: &mut Cursive, row: Option, index: Option| { /// /// }); /// ``` pub fn on_submit(self, cb: F) -> Self where - F: Fn(&mut Cursive, usize, usize) + 'static, + F: Fn(&mut Cursive, Option, Option) + 'static, { self.with(|t| t.set_on_submit(cb)) } @@ -441,13 +440,13 @@ where /// # Example /// /// ```ignore - /// table.set_on_select(|siv: &mut Cursive, row: usize, index: usize| { + /// table.set_on_select(|siv: &mut Cursive, row: Option, index: Option| { /// /// }); /// ``` pub fn set_on_select(&mut self, cb: F) where - F: Fn(&mut Cursive, usize, usize) + 'static, + F: Fn(&mut Cursive, Option, Option) + 'static, { self.on_select = Some(Rc::new(move |s, row, index| cb(s, row, index))); } @@ -462,13 +461,13 @@ where /// # Example /// /// ```ignore - /// table.on_select(|siv: &mut Cursive, row: usize, index: usize| { + /// table.on_select(|siv: &mut Cursive, row: Option, index: Option| { /// /// }); /// ``` pub fn on_select(self, cb: F) -> Self where - F: Fn(&mut Cursive, usize, usize) + 'static, + F: Fn(&mut Cursive, Option, Option) + 'static, { self.with(|t| t.set_on_select(cb)) } @@ -477,7 +476,7 @@ where pub fn clear(&mut self) { self.items.clear(); self.rows_to_items.clear(); - self.focus = 0; + self.focus = None; self.needs_relayout = true; } @@ -496,13 +495,13 @@ where if self.items.is_empty() { None } else { - Some(self.focus) + self.focus } } /// Selects the row at the specified index. pub fn set_selected_row(&mut self, row_index: usize) { - self.focus = row_index; + self.focus = Some(row_index); self.scroll_core.scroll_to_y(row_index); } @@ -518,10 +517,10 @@ where /// The currently active sort order is preserved and will be applied to all /// items. pub fn set_items(&mut self, items: Vec) { - self.set_items_and_focus(items, 0); + self.set_items_and_focus(items, None); } - fn set_items_and_focus(&mut self, items: Vec, new_location: usize) { + fn set_items_and_focus(&mut self, items: Vec, new_location: Option) { self.items = items; self.rows_to_items = Vec::with_capacity(self.items.len()); @@ -540,7 +539,9 @@ where } } - self.set_selected_item(new_location); + if let Some(new_location) = new_location { + self.set_selected_item(new_location); + } self.needs_relayout = true; } @@ -581,7 +582,11 @@ where /// Returns the index of the currently selected item within the underlying /// storage vector. pub fn item(&self) -> Option { - self.rows_to_items.get(self.focus).copied() + if let Some(focus) = self.focus { + self.rows_to_items.get(focus).copied() + } else { + None + } } /// Selects the item at the specified index within the underlying storage @@ -591,7 +596,7 @@ where if item_index < self.items.len() { for (row, item) in self.rows_to_items.iter().enumerate() { if *item == item_index { - self.focus = row; + self.focus = Some(row); self.scroll_core.scroll_to_y(row); break; } @@ -731,8 +736,8 @@ where } fn on_focus_change(&self) -> EventResult { - let row = self.row().unwrap(); - let index = self.item().unwrap(); + let row = self.row(); + let index = self.item(); EventResult::Consumed( self.on_select .clone() @@ -741,11 +746,12 @@ where } fn focus_up(&mut self, n: usize) { - self.focus -= cmp::min(self.focus, n); + self.focus = Some(self.focus.map_or(0, |x| x - cmp::min(x, n))); } fn focus_down(&mut self, n: usize) { - self.focus = cmp::min(self.focus + n, self.items.len().saturating_sub(1)); + let items = self.items.len().saturating_sub(1); + self.focus = Some(self.focus.map_or(0, |x| cmp::min(x + n, items))); } fn active_column(&self) -> usize { @@ -826,7 +832,7 @@ where fn draw_content(&self, printer: &Printer) { for i in 0..self.rows_to_items.len() { let printer = printer.offset((0, i)); - let color = if i == self.focus && self.enabled { + let color = if Some(i) == self.focus && self.enabled { if !self.column_select && self.enabled && printer.focused { theme::ColorStyle::highlight() } else { @@ -903,14 +909,14 @@ where self.column_select = true; } } - Event::Key(Key::Up) if self.focus > 0 || self.column_select => { + Event::Key(Key::Up) => { if self.column_select { self.column_cancel(); } else { self.focus_up(1); } } - Event::Key(Key::Down) if self.focus + 1 < self.items.len() || self.column_select => { + Event::Key(Key::Down) => { if self.column_select { self.column_cancel(); } else { @@ -927,11 +933,11 @@ where } Event::Key(Key::Home) => { self.column_cancel(); - self.focus = 0; + self.focus = None; } Event::Key(Key::End) => { self.column_cancel(); - self.focus = self.items.len().saturating_sub(1); + self.focus = Some(self.items.len().saturating_sub(1)); } Event::Key(Key::Enter) => { if self.column_select { @@ -947,7 +953,7 @@ where } if !self.is_empty() && position .checked_sub(offset) - .map_or(false, |p| p.y == self.focus) => + .map_or(false, |p| Some(p.y) == self.focus) => { self.column_cancel(); return self.on_submit_event(); @@ -959,7 +965,7 @@ where } if !self.is_empty() => match position.checked_sub(offset) { Some(position) if position.y < self.rows_to_items.len() => { self.column_cancel(); - self.focus = position.y; + self.focus = Some(position.y); } _ => return EventResult::Ignored, }, @@ -978,14 +984,14 @@ where } fn inner_important_area(&self, size: Vec2) -> Rect { - Rect::from_size((0, self.focus), (size.x, 1)) + Rect::from_size((0, self.focus.unwrap_or_default()), (size.x, 1)) } fn on_submit_event(&mut self) -> EventResult { if let Some(ref cb) = &self.on_submit { let cb = Rc::clone(cb); - let row = self.row().unwrap(); - let index = self.item().unwrap(); + let row = self.row(); + let index = self.item(); return EventResult::Consumed(Some(Callback::from_fn(move |s| cb(s, row, index)))); } EventResult::Ignored