mirror of
https://github.com/FliegendeWurst/cursive.git
synced 2024-11-09 19:00:46 +00:00
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:
parent
23aa9e6da7
commit
969650ab1a
@ -44,3 +44,7 @@ path = "examples/edit.rs"
|
||||
[[example]]
|
||||
name = "key_codes"
|
||||
path = "examples/key_codes.rs"
|
||||
|
||||
[[example]]
|
||||
name = "list"
|
||||
path = "examples/list.rs"
|
||||
|
25
examples/list.rs
Normal file
25
examples/list.rs
Normal 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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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<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();
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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| {
|
||||
|
@ -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);
|
||||
|
@ -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 {
|
||||
|
85
src/view/list_view.rs
Normal file
85
src/view/list_view.rs
Normal 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
|
||||
}
|
||||
}
|
@ -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.
|
||||
|
@ -31,7 +31,7 @@ impl <T: View> ViewWrapper for ShadowView<T> {
|
||||
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 <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;
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
});
|
||||
|
@ -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 <T: ViewWrapper> 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 {
|
||||
|
Loading…
Reference in New Issue
Block a user