From 3d81ad92ec9434aa095835ba03b1fa2fa9c7ead5 Mon Sep 17 00:00:00 2001 From: Alexandre Bury Date: Thu, 27 Sep 2018 16:01:37 -0700 Subject: [PATCH] SelectView: make autojump opt-in --- examples/select.rs | 6 ++- src/views/scroll_view.rs | 7 +++ src/views/select_view.rs | 99 +++++++++++++++++++++++++++++++--------- 3 files changed, 90 insertions(+), 22 deletions(-) diff --git a/examples/select.rs b/examples/select.rs index f7b1a9a..5a0dde9 100644 --- a/examples/select.rs +++ b/examples/select.rs @@ -12,7 +12,11 @@ use cursive::Cursive; // one. fn main() { - let mut select = SelectView::new().h_align(HAlign::Center); + let mut select = SelectView::new() + // Center the text horizontally + .h_align(HAlign::Center) + // Use keyboard to jump to the pressed letters + .autojump(); // Read the list of cities from separate file, and fill the view with it. // (We include the file at compile-time to avoid runtime read errors.) diff --git a/src/views/scroll_view.rs b/src/views/scroll_view.rs index 04909c5..13d7920 100644 --- a/src/views/scroll_view.rs +++ b/src/views/scroll_view.rs @@ -404,6 +404,13 @@ where ScrollStrategy::KeepRow => (), } } + + /// Returns the wrapped view. + pub fn into_inner(self) -> V { + self.inner + } + + inner_getters!(self.inner: V); } impl View for ScrollView diff --git a/src/views/select_view.rs b/src/views/select_view.rs index 19e13d6..8569526 100644 --- a/src/views/select_view.rs +++ b/src/views/select_view.rs @@ -47,18 +47,33 @@ use With; /// /// ``` pub struct SelectView { + // The core of the view: we store a list of items + // `Item` is more or less a `(String, Rc)`. items: Vec>, + + // When disabled, we cannot change selection. enabled: bool, - // the focus needs to be manipulable from callbacks + + // Callbacks may need to manipulate focus, so give it some mutability. focus: Rc>, + // This is a custom callback to include a &T. - // It will be called whenever "Enter" is pressed. + // It will be called whenever "Enter" is pressed or when an item is clicked. on_submit: Option>, + // This callback is called when the selection is changed. + // TODO: add the previous selection? Indices? on_select: Option>, + + // If `true`, when a character is pressed, jump to the next item starting + // with this character. + autojump: bool, + align: Align, + // `true` if we show a one-line view, with popup on selection. popup: bool, + // We need the last offset to place the popup window // We "cache" it during the draw, so we need interior mutability. last_offset: Cell, @@ -82,11 +97,30 @@ impl SelectView { on_submit: None, align: Align::top_left(), popup: false, + autojump: false, last_offset: Cell::new(Vec2::zero()), last_size: Vec2::zero(), } } + /// Sets the "auto-jump" property for this view. + /// + /// If enabled, when a key is pressed, the selection will jump to the next + /// item beginning with the pressed letter. + pub fn set_autojump(&mut self, autojump: bool) { + self.autojump = autojump; + } + + /// Sets the "auto-jump" property for this view. + /// + /// If enabled, when a key is pressed, the selection will jump to the next + /// item beginning with the pressed letter. + /// + /// Chainable variant. + pub fn autojump(self) -> Self { + self.with(|s| s.set_autojump(true)) + } + /// Turns `self` into a popup select view. /// /// Chainable variant. @@ -148,6 +182,8 @@ impl SelectView { /// Sets a callback to be used when `` is pressed. /// + /// Also happens if the user clicks an item. + /// /// The item currently selected will be given to the callback. /// /// Here, `V` can be `T` itself, or a type that can be borrowed from `T`. @@ -163,6 +199,8 @@ impl SelectView { /// Sets a callback to be used when `` is pressed. /// + /// Also happens if the user clicks an item. + /// /// The item currently selected will be given to the callback. /// /// Chainable variant. @@ -249,6 +287,15 @@ impl SelectView { } } + /// Iterate on the items in this view. + /// + /// Returns an iterator with each item and their labels. + pub fn iter(&self) -> impl Iterator { + self.items + .iter() + .map(|item| (item.label.as_str(), &*item.value)) + } + /// Removes an item from the list. /// /// Returns a callback in response to the selection change. @@ -415,6 +462,33 @@ impl SelectView { ) } + fn on_char_event(&mut self, c: char) -> EventResult { + let i = { + // * Starting from the current focus, find the first item that + // match the char. + // * Cycle back to the beginning of the list when we reach the end. + // * This is achieved by chaining twice the iterator. + let iter = self.iter().chain(self.iter()); + + // We'll do a lowercase check. + let lower_c: Vec = c.to_lowercase().collect(); + let lower_c: &[char] = &lower_c; + + if let Some((i, _)) = iter.enumerate().skip(self.focus() + 1).find( + |&(_, (label, _))| label.to_lowercase().starts_with(lower_c), + ) { + i % self.len() + } else { + return EventResult::Ignored; + } + }; + + self.focus.set(i); + // Apply modulo in case we have a hit from the chained iterator + let cb = self.set_selection(i); + EventResult::Consumed(Some(cb)) + } + fn on_event_regular(&mut self, event: Event) -> EventResult { match event { Event::Key(Key::Up) if self.focus() > 0 => self.focus_up(1), @@ -456,25 +530,7 @@ impl SelectView { Event::Key(Key::Enter) if self.on_submit.is_some() => { return self.submit(); } - Event::Char(c) => { - // Starting from the current focus, - // find the first item that match the char. - // Cycle back to the beginning of - // the list when we reach the end. - // This is achieved by chaining twice the iterator - let iter = self.items.iter().chain(self.items.iter()); - if let Some((i, _)) = iter - .enumerate() - .skip(self.focus() + 1) - .find(|&(_, item)| item.label.starts_with(c)) - { - // Apply modulo in case we have a hit - // from the chained iterator - self.focus.set(i % self.items.len()); - } else { - return EventResult::Ignored; - } - } + Event::Char(c) if self.autojump => return self.on_char_event(c), _ => return EventResult::Ignored, } @@ -713,6 +769,7 @@ impl View for SelectView { } } +// We wrap each value in a `Rc` and add a label struct Item { label: String, value: Rc,