Add basic ListView

Show a selection among a list of items.
Maybe should be called SelectView?
TODO:
* Scrolling
* Error handling with empty list
* Action on Enter on an item
This commit is contained in:
Alexandre Bury 2015-05-30 21:05:34 -07:00
parent 23aa9e6da7
commit 969650ab1a
13 changed files with 154 additions and 24 deletions

View File

@ -44,3 +44,7 @@ path = "examples/edit.rs"
[[example]] [[example]]
name = "key_codes" name = "key_codes"
path = "examples/key_codes.rs" path = "examples/key_codes.rs"
[[example]]
name = "list"
path = "examples/list.rs"

25
examples/list.rs Normal file
View File

@ -0,0 +1,25 @@
extern crate cursive;
use cursive::Cursive;
use cursive::view::{Dialog,ListView,TextView,Selector};
fn main() {
let mut siv = Cursive::new();
siv.add_layer(Dialog::new(ListView::new()
.item_str("Berlin")
.item_str("London")
.item_str("New York")
.item_str("Paris")
.with_id("city"))
.title("Where are you from?")
.button("Ok", |s| {
let city = s.find::<ListView>(&Selector::Id("city")).unwrap().selection().to_string();
s.pop_layer();
s.add_layer(Dialog::new(TextView::new(&format!("{} is a great city!", city)))
.button("Quit", |s| s.quit()));
}));
siv.run();
}

View File

@ -192,11 +192,8 @@ impl Cursive {
} }
fn draw(&mut self) { fn draw(&mut self) {
let printer = Printer { let printer = Printer::new(self.screen_size());
offset: Vec2::new(0,0), self.screen_mut().draw(&printer);
size: self.screen_size(),
};
self.screen_mut().draw(&printer, true);
ncurses::refresh(); ncurses::refresh();
} }

View File

@ -13,6 +13,8 @@ pub struct Printer {
pub offset: Vec2, pub offset: Vec2,
/// Size of the area we are allowed to draw on. /// Size of the area we are allowed to draw on.
pub size: Vec2, pub size: Vec2,
/// Whether the view to draw is currently focused or not.
pub focused: bool,
} }
impl Printer { impl Printer {
@ -21,6 +23,7 @@ impl Printer {
Printer { Printer {
offset: Vec2::zero(), offset: Vec2::zero(),
size: size.to_vec2(), size: size.to_vec2(),
focused: true,
} }
} }
@ -118,12 +121,13 @@ impl Printer {
} }
/// Returns a printer on a subset of this one's area. /// Returns a printer on a subset of this one's area.
pub fn sub_printer<S: ToVec2>(&self, offset: S, size: S) -> Printer { pub fn sub_printer<S: ToVec2>(&self, offset: S, size: S, focused: bool) -> Printer {
let offset_v = offset.to_vec2(); let offset_v = offset.to_vec2();
Printer { Printer {
offset: self.offset + offset_v, offset: self.offset + offset_v,
// We can't be larger than what remains // We can't be larger than what remains
size: Vec2::min(self.size - offset_v, size.to_vec2()), size: Vec2::min(self.size - offset_v, size.to_vec2()),
focused: self.focused && focused,
} }
} }
} }

View File

@ -28,8 +28,8 @@ impl Button {
impl View for Button { impl View for Button {
fn draw(&mut self, printer: &Printer, focused: bool) { fn draw(&mut self, printer: &Printer) {
let style = if !focused { color::PRIMARY } else { color::HIGHLIGHT }; let style = if !printer.focused { color::PRIMARY } else { color::HIGHLIGHT };
let x = printer.size.x - 1; let x = printer.size.x - 1;
printer.with_color(style, |printer| { printer.with_color(style, |printer| {

View File

@ -80,7 +80,7 @@ impl Dialog {
} }
impl View for Dialog { impl View for Dialog {
fn draw(&mut self, printer: &Printer, focused: bool) { fn draw(&mut self, printer: &Printer) {
// This will be the height used by the buttons. // This will be the height used by the buttons.
let mut height = 0; let mut height = 0;
@ -90,7 +90,7 @@ impl View for Dialog {
let size = button.size; let size = button.size;
let offset = printer.size - self.borders.bot_right() - self.padding.bot_right() - size - Vec2::new(x, 0); let offset = printer.size - self.borders.bot_right() - self.padding.bot_right() - size - Vec2::new(x, 0);
// Add some special effect to the focused button // Add some special effect to the focused button
button.draw(&printer.sub_printer(offset, size), focused && (self.focus == Focus::Button(i))); button.draw(&printer.sub_printer(offset, size, self.focus == Focus::Button(i)));
// Keep 1 blank between two buttons // Keep 1 blank between two buttons
x += size.x + 1; x += size.x + 1;
// Also keep 1 blank above the buttons // Also keep 1 blank above the buttons
@ -103,7 +103,7 @@ impl View for Dialog {
- self.borders.combined() - self.borders.combined()
- self.padding.combined(); - self.padding.combined();
self.content.draw(&printer.sub_printer(self.borders.top_left() + self.padding.top_left(), inner_size), focused && self.focus == Focus::Content); self.content.draw(&printer.sub_printer(self.borders.top_left() + self.padding.top_left(), inner_size, self.focus == Focus::Content));
printer.print_box(Vec2::new(0,0), printer.size); printer.print_box(Vec2::new(0,0), printer.size);
@ -174,6 +174,10 @@ impl View for Dialog {
self.focus = Focus::Button(0); self.focus = Focus::Button(0);
EventResult::Consumed(None) EventResult::Consumed(None)
}, },
Event::KeyEvent(Key::Tab) | Event::KeyEvent(Key::ShiftTab) => {
self.focus = Focus::Button(0);
EventResult::Consumed(None)
}
_ => EventResult::Ignored, _ => EventResult::Ignored,
}, },
res => res, res => res,
@ -190,6 +194,14 @@ impl View for Dialog {
EventResult::Ignored EventResult::Ignored
} }
}, },
Event::KeyEvent(Key::Tab) | Event::KeyEvent(Key::ShiftTab) => {
if self.content.take_focus() {
self.focus = Focus::Content;
EventResult::Consumed(None)
} else {
EventResult::Ignored
}
},
// Left and Right move to other buttons // Left and Right move to other buttons
Event::KeyEvent(Key::Right) if i+1 < self.buttons.len() => { Event::KeyEvent(Key::Right) if i+1 < self.buttons.len() => {
self.focus = Focus::Button(i+1); self.focus = Focus::Button(i+1);

View File

@ -57,7 +57,7 @@ fn remove_char(s: &mut String, cursor: usize) {
} }
impl View for EditView { impl View for EditView {
fn draw(&mut self, printer: &Printer, focused: bool) { fn draw(&mut self, printer: &Printer) {
// let style = if focused { color::HIGHLIGHT } else { color::HIGHLIGHT_INACTIVE }; // let style = if focused { color::HIGHLIGHT } else { color::HIGHLIGHT_INACTIVE };
let len = self.content.chars().count(); let len = self.content.chars().count();
printer.with_color(color::SECONDARY, |printer| { printer.with_color(color::SECONDARY, |printer| {
@ -67,7 +67,7 @@ impl View for EditView {
}); });
// Now print cursor // Now print cursor
if focused { if printer.focused {
let c = if self.cursor == len { let c = if self.cursor == len {
'_' '_'
} else { } else {

85
src/view/list_view.rs Normal file
View File

@ -0,0 +1,85 @@
use color;
use view::{View,IdView,SizeRequest};
use event::{Event,EventResult,Key};
use vec::Vec2;
use printer::Printer;
struct Item<T> {
label: String,
value: T,
}
impl <T> Item<T> {
fn new(label: &str, value: T) -> Self {
Item {
label: label.to_string(),
value: value,
}
}
}
pub struct ListView<T=String> {
items: Vec<Item<T>>,
focus: usize,
}
impl <T> ListView<T> {
pub fn new() -> Self {
ListView {
items: Vec::new(),
focus: 0,
}
}
pub fn selection(&self) -> &T {
&self.items[self.focus].value
}
pub fn item(mut self, label: &str, value: T) -> Self {
self.items.push(Item::new(label,value));
self
}
pub fn with_id(self, label: &str) -> IdView<Self> {
IdView::new(label, self)
}
}
impl ListView<String> {
pub fn item_str(self, label: &str) -> Self {
self.item(label, label.to_string())
}
}
impl <T> View for ListView<T> {
fn draw(&mut self, printer: &Printer) {
for (i,item) in self.items.iter().enumerate() {
let style = if i == self.focus { if printer.focused { color::HIGHLIGHT } else { color::HIGHLIGHT_INACTIVE } } else { color::PRIMARY };
printer.with_color(style, |printer| printer.print((0,i), &item.label));
}
}
fn get_min_size(&self, _: SizeRequest) -> Vec2 {
let w = self.items.iter().map(|item| item.label.len()).max().unwrap_or(1);
let h = self.items.len();
Vec2::new(w,h)
}
fn on_event(&mut self, event: Event) -> EventResult {
match event {
Event::KeyEvent(key) => match key {
Key::Up if self.focus > 0 => self.focus -= 1,
Key::Down if self.focus + 1 < self.items.len() => self.focus += 1,
_ => return EventResult::Ignored,
},
_ => return EventResult::Ignored,
}
EventResult::Consumed(None)
}
fn take_focus(&mut self) -> bool {
true
}
}

View File

@ -13,6 +13,7 @@ mod full_view;
mod id_view; mod id_view;
mod shadow_view; mod shadow_view;
mod edit_view; mod edit_view;
mod list_view;
use std::any::Any; use std::any::Any;
@ -29,6 +30,7 @@ pub use self::full_view::FullView;
pub use self::id_view::IdView; pub use self::id_view::IdView;
pub use self::shadow_view::ShadowView; pub use self::shadow_view::ShadowView;
pub use self::edit_view::EditView; pub use self::edit_view::EditView;
pub use self::list_view::ListView;
use event::{Event,EventResult}; use event::{Event,EventResult};
use vec::{Vec2,ToVec2}; use vec::{Vec2,ToVec2};
@ -47,7 +49,7 @@ pub trait View {
fn layout(&mut self, Vec2) { } fn layout(&mut self, Vec2) { }
/// Draws the view with the given printer (includes bounds) and focus. /// Draws the view with the given printer (includes bounds) and focus.
fn draw(&mut self, printer: &Printer, focused: bool); fn draw(&mut self, printer: &Printer);
/// Finds the view pointed to by the given path. /// Finds the view pointed to by the given path.
/// Returns None if the path doesn't lead to a view. /// Returns None if the path doesn't lead to a view.

View File

@ -31,7 +31,7 @@ impl <T: View> ViewWrapper for ShadowView<T> {
self.view.layout(size - (2,2)); self.view.layout(size - (2,2));
} }
fn wrap_draw(&mut self, printer: &Printer, focused: bool) { fn wrap_draw(&mut self, printer: &Printer) {
printer.with_color(color::PRIMARY, |printer| { printer.with_color(color::PRIMARY, |printer| {
// Draw the view background // Draw the view background
@ -40,7 +40,7 @@ impl <T: View> ViewWrapper for ShadowView<T> {
} }
}); });
self.view.draw(&printer.sub_printer(Vec2::new(1,1), printer.size - (2,2)), focused); self.view.draw(&printer.sub_printer(Vec2::new(1,1), printer.size - (2,2), true));
let h = printer.size.y-1; let h = printer.size.y-1;

View File

@ -41,12 +41,13 @@ impl StackView {
} }
impl View for StackView { impl View for StackView {
fn draw(&mut self, printer: &Printer, focused: bool) { fn draw(&mut self, printer: &Printer) {
for v in self.layers.iter_mut() { for v in self.layers.iter_mut() {
// Center the view // Center the view
let size = v.size; let size = v.size;
let offset = (printer.size - size) / 2; let offset = (printer.size - size) / 2;
v.view.draw(&printer.sub_printer(offset, size), focused); // TODO: only draw focus for the top view
v.view.draw(&printer.sub_printer(offset, size, true));
} }
} }

View File

@ -182,7 +182,7 @@ impl <'a> Iterator for LinesIterator<'a> {
} }
impl View for TextView { impl View for TextView {
fn draw(&mut self, printer: &Printer, focused: bool) { fn draw(&mut self, printer: &Printer) {
// We don't have a focused view // We don't have a focused view
for (i,line) in self.rows.iter().skip(self.start_line).map(|row| &self.content[row.start..row.end]).enumerate() { for (i,line) in self.rows.iter().skip(self.start_line).map(|row| &self.content[row.start..row.end]).enumerate() {
printer.print((0,i), line); printer.print((0,i), line);
@ -194,7 +194,7 @@ impl View for TextView {
let start = self.view_height * self.start_line / self.rows.len(); let start = self.view_height * self.start_line / self.rows.len();
let end = self.view_height * (self.start_line + self.view_height) / self.rows.len(); let end = self.view_height * (self.start_line + self.view_height) / self.rows.len();
printer.with_color( printer.with_color(
if focused { color::HIGHLIGHT } else { color::HIGHLIGHT_INACTIVE }, if printer.focused { color::HIGHLIGHT } else { color::HIGHLIGHT_INACTIVE },
|printer| { |printer| {
printer.print_vline((printer.size.x-1, start), end-start, ' ' as u64); printer.print_vline((printer.size.x-1, start), end-start, ' ' as u64);
}); });

View File

@ -16,8 +16,8 @@ pub trait ViewWrapper {
fn get_view_mut(&mut self) -> &mut View; fn get_view_mut(&mut self) -> &mut View;
/// Wraps the draw method. /// Wraps the draw method.
fn wrap_draw(&mut self, printer: &Printer, focused: bool) { fn wrap_draw(&mut self, printer: &Printer) {
self.get_view_mut().draw(printer, focused); self.get_view_mut().draw(printer);
} }
/// Wraps the get_min_size method. /// Wraps the get_min_size method.
@ -46,8 +46,8 @@ pub trait ViewWrapper {
} }
impl <T: ViewWrapper> View for T { impl <T: ViewWrapper> View for T {
fn draw(&mut self, printer: &Printer, focused: bool) { fn draw(&mut self, printer: &Printer) {
self.wrap_draw(printer, focused); self.wrap_draw(printer);
} }
fn get_min_size(&self, req: SizeRequest) -> Vec2 { fn get_min_size(&self, req: SizeRequest) -> Vec2 {