mirror of
https://github.com/FliegendeWurst/cursive.git
synced 2024-11-23 17:35:00 +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]]
|
[[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
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) {
|
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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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| {
|
||||||
|
@ -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);
|
||||||
|
@ -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
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 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.
|
||||||
|
@ -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;
|
||||||
|
@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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);
|
||||||
});
|
});
|
||||||
|
@ -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 {
|
||||||
|
Loading…
Reference in New Issue
Block a user