From 969650ab1a89633c3e718eebddbbd9198fc2a9fa Mon Sep 17 00:00:00 2001 From: Alexandre Bury Date: Sat, 30 May 2015 21:05:34 -0700 Subject: [PATCH] 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 --- Cargo.toml | 4 ++ examples/list.rs | 25 ++++++++++++ src/lib.rs | 7 +--- src/printer.rs | 6 ++- src/view/button.rs | 4 +- src/view/dialog.rs | 18 +++++++-- src/view/edit_view.rs | 4 +- src/view/list_view.rs | 85 ++++++++++++++++++++++++++++++++++++++++ src/view/mod.rs | 4 +- src/view/shadow_view.rs | 4 +- src/view/stack_view.rs | 5 ++- src/view/text_view.rs | 4 +- src/view/view_wrapper.rs | 8 ++-- 13 files changed, 154 insertions(+), 24 deletions(-) create mode 100644 examples/list.rs create mode 100644 src/view/list_view.rs diff --git a/Cargo.toml b/Cargo.toml index 7850000..192c27d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,3 +44,7 @@ path = "examples/edit.rs" [[example]] name = "key_codes" path = "examples/key_codes.rs" + +[[example]] +name = "list" +path = "examples/list.rs" diff --git a/examples/list.rs b/examples/list.rs new file mode 100644 index 0000000..f1ff9d7 --- /dev/null +++ b/examples/list.rs @@ -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::(&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(); +} + diff --git a/src/lib.rs b/src/lib.rs index 72e8378..5f5e257 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -192,11 +192,8 @@ impl Cursive { } fn draw(&mut self) { - let printer = Printer { - offset: Vec2::new(0,0), - size: self.screen_size(), - }; - self.screen_mut().draw(&printer, true); + let printer = Printer::new(self.screen_size()); + self.screen_mut().draw(&printer); ncurses::refresh(); } diff --git a/src/printer.rs b/src/printer.rs index 0851f76..fc8a7c9 100644 --- a/src/printer.rs +++ b/src/printer.rs @@ -13,6 +13,8 @@ pub struct Printer { pub offset: Vec2, /// Size of the area we are allowed to draw on. pub size: Vec2, + /// Whether the view to draw is currently focused or not. + pub focused: bool, } impl Printer { @@ -21,6 +23,7 @@ impl Printer { Printer { offset: Vec2::zero(), size: size.to_vec2(), + focused: true, } } @@ -118,12 +121,13 @@ impl Printer { } /// Returns a printer on a subset of this one's area. - pub fn sub_printer(&self, offset: S, size: S) -> Printer { + pub fn sub_printer(&self, offset: S, size: S, focused: bool) -> Printer { let offset_v = offset.to_vec2(); Printer { offset: self.offset + offset_v, // We can't be larger than what remains size: Vec2::min(self.size - offset_v, size.to_vec2()), + focused: self.focused && focused, } } } diff --git a/src/view/button.rs b/src/view/button.rs index 8e85b11..8589960 100644 --- a/src/view/button.rs +++ b/src/view/button.rs @@ -28,8 +28,8 @@ impl Button { impl View for Button { - fn draw(&mut self, printer: &Printer, focused: bool) { - let style = if !focused { color::PRIMARY } else { color::HIGHLIGHT }; + fn draw(&mut self, printer: &Printer) { + let style = if !printer.focused { color::PRIMARY } else { color::HIGHLIGHT }; let x = printer.size.x - 1; printer.with_color(style, |printer| { diff --git a/src/view/dialog.rs b/src/view/dialog.rs index c99f280..ca6a12a 100644 --- a/src/view/dialog.rs +++ b/src/view/dialog.rs @@ -80,7 +80,7 @@ impl 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. let mut height = 0; @@ -90,7 +90,7 @@ impl View for Dialog { let size = button.size; 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 - 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 x += size.x + 1; // Also keep 1 blank above the buttons @@ -103,7 +103,7 @@ impl View for Dialog { - self.borders.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); @@ -174,6 +174,10 @@ impl View for Dialog { self.focus = Focus::Button(0); EventResult::Consumed(None) }, + Event::KeyEvent(Key::Tab) | Event::KeyEvent(Key::ShiftTab) => { + self.focus = Focus::Button(0); + EventResult::Consumed(None) + } _ => EventResult::Ignored, }, res => res, @@ -190,6 +194,14 @@ impl View for Dialog { 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 Event::KeyEvent(Key::Right) if i+1 < self.buttons.len() => { self.focus = Focus::Button(i+1); diff --git a/src/view/edit_view.rs b/src/view/edit_view.rs index 6ba3851..fb4bc42 100644 --- a/src/view/edit_view.rs +++ b/src/view/edit_view.rs @@ -57,7 +57,7 @@ fn remove_char(s: &mut String, cursor: usize) { } 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 len = self.content.chars().count(); printer.with_color(color::SECONDARY, |printer| { @@ -67,7 +67,7 @@ impl View for EditView { }); // Now print cursor - if focused { + if printer.focused { let c = if self.cursor == len { '_' } else { diff --git a/src/view/list_view.rs b/src/view/list_view.rs new file mode 100644 index 0000000..2c3d12e --- /dev/null +++ b/src/view/list_view.rs @@ -0,0 +1,85 @@ +use color; +use view::{View,IdView,SizeRequest}; +use event::{Event,EventResult,Key}; +use vec::Vec2; +use printer::Printer; + +struct Item { + label: String, + value: T, +} + +impl Item { + fn new(label: &str, value: T) -> Self { + Item { + label: label.to_string(), + value: value, + } + } +} + +pub struct ListView { + items: Vec>, + focus: usize, +} + +impl ListView { + 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 { + IdView::new(label, self) + } +} + +impl ListView { + pub fn item_str(self, label: &str) -> Self { + self.item(label, label.to_string()) + } +} + +impl View for ListView { + 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 + } +} diff --git a/src/view/mod.rs b/src/view/mod.rs index 49897db..782285f 100644 --- a/src/view/mod.rs +++ b/src/view/mod.rs @@ -13,6 +13,7 @@ mod full_view; mod id_view; mod shadow_view; mod edit_view; +mod list_view; use std::any::Any; @@ -29,6 +30,7 @@ pub use self::full_view::FullView; pub use self::id_view::IdView; pub use self::shadow_view::ShadowView; pub use self::edit_view::EditView; +pub use self::list_view::ListView; use event::{Event,EventResult}; use vec::{Vec2,ToVec2}; @@ -47,7 +49,7 @@ pub trait View { fn layout(&mut self, Vec2) { } /// 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. /// Returns None if the path doesn't lead to a view. diff --git a/src/view/shadow_view.rs b/src/view/shadow_view.rs index 2599602..25f9bef 100644 --- a/src/view/shadow_view.rs +++ b/src/view/shadow_view.rs @@ -31,7 +31,7 @@ impl ViewWrapper for ShadowView { 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| { // Draw the view background @@ -40,7 +40,7 @@ impl ViewWrapper for ShadowView { } }); - 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; diff --git a/src/view/stack_view.rs b/src/view/stack_view.rs index 6dfae8b..4525adc 100644 --- a/src/view/stack_view.rs +++ b/src/view/stack_view.rs @@ -41,12 +41,13 @@ impl 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() { // Center the view let size = v.size; 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)); } } diff --git a/src/view/text_view.rs b/src/view/text_view.rs index 3372042..05818c6 100644 --- a/src/view/text_view.rs +++ b/src/view/text_view.rs @@ -182,7 +182,7 @@ impl <'a> Iterator for LinesIterator<'a> { } 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 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); @@ -194,7 +194,7 @@ impl View for TextView { 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(); printer.with_color( - if focused { color::HIGHLIGHT } else { color::HIGHLIGHT_INACTIVE }, + if printer.focused { color::HIGHLIGHT } else { color::HIGHLIGHT_INACTIVE }, |printer| { printer.print_vline((printer.size.x-1, start), end-start, ' ' as u64); }); diff --git a/src/view/view_wrapper.rs b/src/view/view_wrapper.rs index 1e767f4..814f084 100644 --- a/src/view/view_wrapper.rs +++ b/src/view/view_wrapper.rs @@ -16,8 +16,8 @@ pub trait ViewWrapper { fn get_view_mut(&mut self) -> &mut View; /// Wraps the draw method. - fn wrap_draw(&mut self, printer: &Printer, focused: bool) { - self.get_view_mut().draw(printer, focused); + fn wrap_draw(&mut self, printer: &Printer) { + self.get_view_mut().draw(printer); } /// Wraps the get_min_size method. @@ -46,8 +46,8 @@ pub trait ViewWrapper { } impl View for T { - fn draw(&mut self, printer: &Printer, focused: bool) { - self.wrap_draw(printer, focused); + fn draw(&mut self, printer: &Printer) { + self.wrap_draw(printer); } fn get_min_size(&self, req: SizeRequest) -> Vec2 {