mirror of
https://github.com/FliegendeWurst/cursive.git
synced 2024-11-23 17:35:00 +00:00
SelectView: make autojump opt-in
This commit is contained in:
parent
333392e034
commit
3d81ad92ec
@ -12,7 +12,11 @@ use cursive::Cursive;
|
|||||||
// one.
|
// one.
|
||||||
|
|
||||||
fn main() {
|
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.
|
// 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.)
|
// (We include the file at compile-time to avoid runtime read errors.)
|
||||||
|
@ -404,6 +404,13 @@ where
|
|||||||
ScrollStrategy::KeepRow => (),
|
ScrollStrategy::KeepRow => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the wrapped view.
|
||||||
|
pub fn into_inner(self) -> V {
|
||||||
|
self.inner
|
||||||
|
}
|
||||||
|
|
||||||
|
inner_getters!(self.inner: V);
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<V> View for ScrollView<V>
|
impl<V> View for ScrollView<V>
|
||||||
|
@ -47,18 +47,33 @@ use With;
|
|||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
pub struct SelectView<T = String> {
|
pub struct SelectView<T = String> {
|
||||||
|
// The core of the view: we store a list of items
|
||||||
|
// `Item` is more or less a `(String, Rc<T>)`.
|
||||||
items: Vec<Item<T>>,
|
items: Vec<Item<T>>,
|
||||||
|
|
||||||
|
// When disabled, we cannot change selection.
|
||||||
enabled: bool,
|
enabled: bool,
|
||||||
// the focus needs to be manipulable from callbacks
|
|
||||||
|
// Callbacks may need to manipulate focus, so give it some mutability.
|
||||||
focus: Rc<Cell<usize>>,
|
focus: Rc<Cell<usize>>,
|
||||||
|
|
||||||
// This is a custom callback to include a &T.
|
// 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<Rc<Fn(&mut Cursive, &T)>>,
|
on_submit: Option<Rc<Fn(&mut Cursive, &T)>>,
|
||||||
|
|
||||||
// This callback is called when the selection is changed.
|
// This callback is called when the selection is changed.
|
||||||
|
// TODO: add the previous selection? Indices?
|
||||||
on_select: Option<Rc<Fn(&mut Cursive, &T)>>,
|
on_select: Option<Rc<Fn(&mut Cursive, &T)>>,
|
||||||
|
|
||||||
|
// If `true`, when a character is pressed, jump to the next item starting
|
||||||
|
// with this character.
|
||||||
|
autojump: bool,
|
||||||
|
|
||||||
align: Align,
|
align: Align,
|
||||||
|
|
||||||
// `true` if we show a one-line view, with popup on selection.
|
// `true` if we show a one-line view, with popup on selection.
|
||||||
popup: bool,
|
popup: bool,
|
||||||
|
|
||||||
// We need the last offset to place the popup window
|
// We need the last offset to place the popup window
|
||||||
// We "cache" it during the draw, so we need interior mutability.
|
// We "cache" it during the draw, so we need interior mutability.
|
||||||
last_offset: Cell<Vec2>,
|
last_offset: Cell<Vec2>,
|
||||||
@ -82,11 +97,30 @@ impl<T: 'static> SelectView<T> {
|
|||||||
on_submit: None,
|
on_submit: None,
|
||||||
align: Align::top_left(),
|
align: Align::top_left(),
|
||||||
popup: false,
|
popup: false,
|
||||||
|
autojump: false,
|
||||||
last_offset: Cell::new(Vec2::zero()),
|
last_offset: Cell::new(Vec2::zero()),
|
||||||
last_size: 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.
|
/// Turns `self` into a popup select view.
|
||||||
///
|
///
|
||||||
/// Chainable variant.
|
/// Chainable variant.
|
||||||
@ -148,6 +182,8 @@ impl<T: 'static> SelectView<T> {
|
|||||||
|
|
||||||
/// Sets a callback to be used when `<Enter>` is pressed.
|
/// Sets a callback to be used when `<Enter>` is pressed.
|
||||||
///
|
///
|
||||||
|
/// Also happens if the user clicks an item.
|
||||||
|
///
|
||||||
/// The item currently selected will be given to the callback.
|
/// 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`.
|
/// Here, `V` can be `T` itself, or a type that can be borrowed from `T`.
|
||||||
@ -163,6 +199,8 @@ impl<T: 'static> SelectView<T> {
|
|||||||
|
|
||||||
/// Sets a callback to be used when `<Enter>` is pressed.
|
/// Sets a callback to be used when `<Enter>` is pressed.
|
||||||
///
|
///
|
||||||
|
/// Also happens if the user clicks an item.
|
||||||
|
///
|
||||||
/// The item currently selected will be given to the callback.
|
/// The item currently selected will be given to the callback.
|
||||||
///
|
///
|
||||||
/// Chainable variant.
|
/// Chainable variant.
|
||||||
@ -249,6 +287,15 @@ impl<T: 'static> SelectView<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Iterate on the items in this view.
|
||||||
|
///
|
||||||
|
/// Returns an iterator with each item and their labels.
|
||||||
|
pub fn iter(&self) -> impl Iterator<Item = (&str, &T)> {
|
||||||
|
self.items
|
||||||
|
.iter()
|
||||||
|
.map(|item| (item.label.as_str(), &*item.value))
|
||||||
|
}
|
||||||
|
|
||||||
/// Removes an item from the list.
|
/// Removes an item from the list.
|
||||||
///
|
///
|
||||||
/// Returns a callback in response to the selection change.
|
/// Returns a callback in response to the selection change.
|
||||||
@ -415,6 +462,33 @@ impl<T: 'static> SelectView<T> {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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<char> = 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 {
|
fn on_event_regular(&mut self, event: Event) -> EventResult {
|
||||||
match event {
|
match event {
|
||||||
Event::Key(Key::Up) if self.focus() > 0 => self.focus_up(1),
|
Event::Key(Key::Up) if self.focus() > 0 => self.focus_up(1),
|
||||||
@ -456,25 +530,7 @@ impl<T: 'static> SelectView<T> {
|
|||||||
Event::Key(Key::Enter) if self.on_submit.is_some() => {
|
Event::Key(Key::Enter) if self.on_submit.is_some() => {
|
||||||
return self.submit();
|
return self.submit();
|
||||||
}
|
}
|
||||||
Event::Char(c) => {
|
Event::Char(c) if self.autojump => return self.on_char_event(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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => return EventResult::Ignored,
|
_ => return EventResult::Ignored,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -713,6 +769,7 @@ impl<T: 'static> View for SelectView<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We wrap each value in a `Rc` and add a label
|
||||||
struct Item<T> {
|
struct Item<T> {
|
||||||
label: String,
|
label: String,
|
||||||
value: Rc<T>,
|
value: Rc<T>,
|
||||||
|
Loading…
Reference in New Issue
Block a user