Add scrolling to SelectView

Scrolling functionalities are stored in ScrollBase. Both TextView and
SelectView use it.
Also add letter search to SelectView (currently case sensitive).
And made the cities example bigger with a long list of capitals.
This commit is contained in:
Alexandre Bury 2015-05-31 16:38:53 -07:00
parent 95ca27a1d0
commit 323805b52f
6 changed files with 443 additions and 53 deletions

254
assets/cities.txt Normal file
View File

@ -0,0 +1,254 @@
Abidjan
Abu Dhabi
Abuja
Accra
Adamstown
Addis Ababa
Algiers
Alofi
Amman
Amsterdam
Andorra la Vella
Ankara
Antananarivo
Apia
Arbil
Ashgabat
Asmara
Astana
Asunción
Athens
Avarua
Baghdad
Baku
Bamako
Bandar Seri Begawan
Bangkok
Bangui
Banjul
Basseterre
Beijing
Beirut
Belgrade
Belmopan
Berlin
Bern
Bishkek
Bissau
Bogotá
Brasília
Bratislava
Brazzaville
Bridgetown
Brussels
Bucharest
Budapest
Buenos Aires
Bujumbura
Cairo
Canberra
Caracas
Cardiff
Castries
Cayenne
Charlotte Amalie
Chișinău
Cockburn Town
Colombo
Conakry
Copenhagen
Dakar
Damascus
Dhaka
Dili
Djibouti
Dodoma
Doha
Douglas
Dublin
Dushanbe
Edinburgh
Fort-de-France
Freetown
Funafuti
Funchal
Gaborone
Garoowe
Gaza
Georgetown
Georgetown
George Town
Gibraltar
Grozny
Guatemala City
Hagatna
Hamilton
Hanoi
Harare
Hargeisa
Havana
Helsinki
Honiara
Islamabad
Jakarta
Jamestown
Jerusalem
Jerusalem
Kabul
Kampala
Kathmandu
Khartoum
Kiev
Kigali
Kilinochchi
Kingston
Kingston
Kingstown
Kinshasa
Kuala Lumpur
Kuwait City
La Paz
Las Palmas
Libreville
Lilongwe
Lima
Lisbon
Ljubljana
Lomé
London
Luanda
Lusaka
Luxembourg
Madrid
Majuro
Malabo
Malé
Mamoudzou
Managua
Manama
Manila
Maputo
Maseru
Mata-Utu
Mbabane
Melekeok
Mexico City
Minsk
Mogadishu
Monaco
Monrovia
Montevideo
Moroni
Moscow
Muscat
Nairobi
Nassau
Naypyidaw
N'Djamena
New Delhi
Niamey
Nicosia
none
Nouakchott
Nouméa
Nuku'alofa
Nuuk
Oranjestad
Oslo
Ottawa
Ouagadougou
Pago Pago
Palikir
Panama City
Papeete
Paramaribo
Paris
Phnom Penh
Plymouth
Podgorica
Ponta Delgada
Port-au-Prince
Port Louis
Port Moresby
Port of Spain
Porto-Novo
Port Vila
Prague
Praia
Pretoria
Priština
Putrajaya
Pyongyang
Quito
Rabat
Ramallah
Reykjavík
Riga
Riyadh
Road Town
Rome
Roseau
Saint-Denis
Saint Helier
Saint-Pierre
Saipan
Sanaa
San Juan
San Marino
San Salvador
Santiago
Sant José
Santo Domingo
São Tomé
Sarajevo
Seoul
Singapore
Skopje
Sofia
Stanley
Stepanakert
St. George's
St. John's
Stockholm
St Peter Port
Sucre
Sukhumi
Suva
Taipei
Tallinn
Tarawa
Tashkent
Tbilisi
Tegucigalpa
Tehran
The Settlement
The Valley
Thimphu
Tirana
Tiraspol
Tokyo
Tórshavn
Tripoli
Tskhinvali
Tunis
Ulaanbaatar
Vaduz
Valletta
Valparaíso­so
Vatican City
Victoria
Vienna
Vientiane
Vilnius
Vitoria-Gasteiz
Warsaw
Washington, D.C.
Wellington
West Island
Willemstad
Windhoek
Yamoussoukro
Yaoundé
Yaren
Yerevan
Zagreb

View File

@ -1,17 +1,25 @@
extern crate cursive; extern crate cursive;
use std::fs::File;
use std::io::{BufReader,BufRead};
use cursive::Cursive; use cursive::Cursive;
use cursive::view::{Dialog,SelectView,TextView,Selector}; use cursive::view::{Dialog,SelectView,TextView,Selector,BoxView};
fn main() { fn main() {
let mut select = SelectView::new();
// Read the list of cities from separate file, and fill the view with it.
let file = File::open("assets/cities.txt").unwrap();
let reader = BufReader::new(file);
for line in reader.lines() {
select.add_item_str(&line.unwrap());
}
let mut siv = Cursive::new(); let mut siv = Cursive::new();
siv.add_layer(Dialog::new(SelectView::new() siv.add_layer(Dialog::new(BoxView::new((20,10), select.with_id("city")))
.item_str("Berlin")
.item_str("London")
.item_str("New York")
.item_str("Paris")
.with_id("city"))
.title("Where are you from?") .title("Where are you from?")
.button("Ok", |s| { .button("Ok", |s| {
let city = s.find::<SelectView>(&Selector::Id("city")).unwrap().selection().to_string(); let city = s.find::<SelectView>(&Selector::Id("city")).unwrap().selection().to_string();

View File

@ -15,6 +15,8 @@ mod shadow_view;
mod edit_view; mod edit_view;
mod select_view; mod select_view;
mod scroll;
use std::any::Any; use std::any::Any;
pub use self::view_path::ViewPath; pub use self::view_path::ViewPath;

95
src/view/scroll.rs Normal file
View File

@ -0,0 +1,95 @@
use std::cmp::{min,max};
use color;
use vec::Vec2;
use printer::Printer;
pub struct ScrollBase {
pub start_line: usize,
pub content_height: usize,
pub view_height: usize,
}
impl ScrollBase {
pub fn new() -> Self {
ScrollBase {
start_line: 0,
content_height: 0,
view_height: 0,
}
}
pub fn set_heights(&mut self, view_height: usize, content_height: usize) {
self.view_height = view_height;
self.content_height = content_height;
if self.scrollable() {
self.start_line = min(self.start_line, self.content_height - self.view_height);
} else {
self.start_line = 0;
}
}
pub fn scrollable(&self) -> bool {
self.view_height < self.content_height
}
pub fn can_scroll_up(&self) -> bool {
self.start_line > 0
}
pub fn can_scroll_down(&self) -> bool {
self.start_line + self.view_height < self.content_height
}
pub fn scroll_top(&mut self) {
self.start_line = 0;
}
pub fn scroll_to(&mut self, y: usize) {
if y >= self.start_line + self.view_height {
self.start_line = 1 + y - self.view_height;
} else if y < self.start_line {
self.start_line = y;
}
}
pub fn scroll_bottom(&mut self) {
self.start_line = self.content_height - self.view_height;
}
pub fn scroll_down(&mut self, n: usize) {
self.start_line = min(self.start_line+n, self.content_height - self.view_height);
}
pub fn scroll_up(&mut self, n: usize) {
self.start_line -= min(self.start_line, n);
}
pub fn draw<F>(&self, printer: &Printer, line_drawer: F)
where F: Fn(&Printer,usize)
{
for y in 0..self.view_height {
line_drawer(&printer.sub_printer(Vec2::new(0,y),printer.size,true), y+self.start_line);
}
if self.view_height < self.content_height {
// We directly compute the size of the scrollbar (this allow use to avoid using floats).
// (ratio) * max_height
// Where ratio is ({start or end} / content.height)
let height = max(1,self.view_height * self.view_height / self.content_height);
// Number of different possible positions
let steps = self.view_height - height + 1;
// Now
let start = steps * self.start_line / (1 + self.content_height - self.view_height);
printer.with_color(
if printer.focused { color::HIGHLIGHT } else { color::HIGHLIGHT_INACTIVE },
|printer| {
printer.print_vline((printer.size.x-1, start), height, ' ' as u64);
});
}
}
}

View File

@ -1,8 +1,11 @@
use std::cmp::min;
use color; use color;
use view::{View,IdView,SizeRequest}; use view::{View,IdView,SizeRequest,DimensionRequest};
use event::{Event,EventResult,Key}; use event::{Event,EventResult,Key};
use vec::Vec2; use vec::Vec2;
use printer::Printer; use printer::Printer;
use super::scroll::ScrollBase;
struct Item<T> { struct Item<T> {
label: String, label: String,
@ -24,6 +27,7 @@ impl <T> Item<T> {
pub struct SelectView<T=String> { pub struct SelectView<T=String> {
items: Vec<Item<T>>, items: Vec<Item<T>>,
focus: usize, focus: usize,
scrollbase: ScrollBase,
} }
impl <T> SelectView<T> { impl <T> SelectView<T> {
@ -32,6 +36,7 @@ impl <T> SelectView<T> {
SelectView { SelectView {
items: Vec::new(), items: Vec::new(),
focus: 0, focus: 0,
scrollbase: ScrollBase::new(),
} }
} }
@ -41,8 +46,13 @@ impl <T> SelectView<T> {
} }
/// Adds a item to the list, with given label and value. /// Adds a item to the list, with given label and value.
pub fn item(mut self, label: &str, value: T) -> Self { pub fn add_item(&mut self, label: &str, value: T) {
self.items.push(Item::new(label,value)); self.items.push(Item::new(label,value));
}
/// Chainable variant of add_item
pub fn item(mut self, label: &str, value: T) -> Self {
self.add_item(label, value);
self self
} }
@ -58,36 +68,73 @@ impl SelectView<String> {
pub fn item_str(self, label: &str) -> Self { pub fn item_str(self, label: &str) -> Self {
self.item(label, label.to_string()) self.item(label, label.to_string())
} }
pub fn add_item_str(&mut self, label: &str) {
self.add_item(label, label.to_string());
}
} }
impl <T> View for SelectView<T> { impl <T> View for SelectView<T> {
fn draw(&mut self, printer: &Printer) { 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 }; self.scrollbase.draw(printer, |printer,i| {
printer.with_color(style, |printer| printer.print((0,i), &item.label)); 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,0), &self.items[i].label));
});
} }
fn get_min_size(&self, _: SizeRequest) -> Vec2 { fn get_min_size(&self, req: SizeRequest) -> Vec2 {
let w = self.items.iter().map(|item| item.label.len()).max().unwrap_or(1); let w = self.items.iter().map(|item| item.label.len()).max().unwrap_or(1);
let h = self.items.len(); let h = self.items.len();
let scrolling = if let DimensionRequest::Fixed(r_h) = req.h {
r_h < h
} else if let DimensionRequest::AtMost(r_h) = req.h {
r_h < h
} else {
false
};
let w = if scrolling { w + 1 } else { w };
Vec2::new(w,h) Vec2::new(w,h)
} }
fn on_event(&mut self, event: Event) -> EventResult { fn on_event(&mut self, event: Event) -> EventResult {
match event { match event {
Event::KeyEvent(key) => match key { Event::KeyEvent(Key::Up) if self.focus > 0 => self.focus -= 1,
Key::Up if self.focus > 0 => self.focus -= 1, Event::KeyEvent(Key::Down) if self.focus + 1 < self.items.len() => self.focus += 1,
Key::Down if self.focus + 1 < self.items.len() => self.focus += 1, Event::KeyEvent(Key::PageUp) => self.focus -= min(self.focus,10),
_ => return EventResult::Ignored, Event::KeyEvent(Key::PageDown) => self.focus = min(self.focus+10,self.items.len()-1),
Event::KeyEvent(Key::Home) => self.focus = 0,
Event::KeyEvent(Key::End) => self.focus = self.items.len()-1,
Event::CharEvent(c) => {
// Starting from the current focus, find the first item that match the char.
if let Some((i,_)) = self.items.iter().enumerate()
.skip(self.focus+1)
.find(|&(_,item)| item.label.starts_with(c))
{
self.focus = i;
}
}, },
_ => return EventResult::Ignored, _ => return EventResult::Ignored,
} }
self.scrollbase.scroll_to(self.focus);
EventResult::Consumed(None) EventResult::Consumed(None)
} }
fn take_focus(&mut self) -> bool { fn take_focus(&mut self) -> bool {
true true
} }
fn layout(&mut self, size: Vec2) {
self.scrollbase.set_heights(size.y, self.items.len());
}
} }

View File

@ -1,18 +1,18 @@
use std::cmp::{max,min}; use std::cmp::max;
use color;
use vec::Vec2; use vec::Vec2;
use view::{View,DimensionRequest,SizeRequest}; use view::{View,DimensionRequest,SizeRequest};
use div::*; use div::*;
use printer::Printer; use printer::Printer;
use event::*; use event::*;
use super::scroll::ScrollBase;
/// A simple view showing a fixed text /// A simple view showing a fixed text
pub struct TextView { pub struct TextView {
content: String, content: String,
rows: Vec<Row>, rows: Vec<Row>,
start_line: usize,
view_height: usize, scrollbase: ScrollBase,
} }
// Subset of the main content representing a row on the display. // Subset of the main content representing a row on the display.
@ -54,9 +54,8 @@ impl TextView {
let content = strip_last_newline(content); let content = strip_last_newline(content);
TextView { TextView {
content: content.to_string(), content: content.to_string(),
start_line: 0,
rows: Vec::new(), rows: Vec::new(),
view_height: 0, scrollbase: ScrollBase::new(),
} }
} }
@ -183,36 +182,24 @@ impl <'a> Iterator for LinesIterator<'a> {
impl View for TextView { impl View for TextView {
fn draw(&mut self, printer: &Printer) { fn draw(&mut self, printer: &Printer) {
// We don't have a focused view self.scrollbase.draw(printer, |printer, i| {
for (i,line) in self.rows.iter().skip(self.start_line).map(|row| &self.content[row.start..row.end]).enumerate() { let row = &self.rows[i];
printer.print((0,i), line); printer.print((0,0), &self.content[row.start..row.end]);
} });
if self.view_height < self.rows.len() {
// We directly compute the size of the scrollbar (this allow use to avoid using floats).
// (ratio) * max_height
// Where ratio is ({start or end} / content.height)
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 printer.focused { color::HIGHLIGHT } else { color::HIGHLIGHT_INACTIVE },
|printer| {
printer.print_vline((printer.size.x-1, start), end-start, ' ' as u64);
});
}
} }
fn on_event(&mut self, event: Event) -> EventResult { fn on_event(&mut self, event: Event) -> EventResult {
if self.view_height >= self.rows.len() { if !self.scrollbase.scrollable() {
return EventResult::Ignored; return EventResult::Ignored;
} }
match event { match event {
Event::KeyEvent(Key::Home) => self.start_line = 0, Event::KeyEvent(Key::Home) => self.scrollbase.scroll_top(),
Event::KeyEvent(Key::End) => self.start_line = self.rows.len() - self.view_height, Event::KeyEvent(Key::End) => self.scrollbase.scroll_bottom(),
Event::KeyEvent(Key::Up) if self.start_line > 0 => self.start_line -= 1, Event::KeyEvent(Key::Up) if self.scrollbase.can_scroll_up() => self.scrollbase.scroll_up(1),
Event::KeyEvent(Key::Down) if self.start_line+self.view_height < self.rows.len() => self.start_line += 1, Event::KeyEvent(Key::Down) if self.scrollbase.can_scroll_down() => self.scrollbase.scroll_down(1),
Event::KeyEvent(Key::PageDown) => self.start_line = min(self.start_line+10, self.rows.len()-self.view_height), Event::KeyEvent(Key::PageDown) => self.scrollbase.scroll_down(10),
Event::KeyEvent(Key::PageUp) => self.start_line -= min(self.start_line, 10), Event::KeyEvent(Key::PageUp) => self.scrollbase.scroll_up(10),
_ => return EventResult::Ignored, _ => return EventResult::Ignored,
} }
@ -248,18 +235,15 @@ impl View for TextView {
} }
fn take_focus(&mut self) -> bool { fn take_focus(&mut self) -> bool {
self.view_height < self.rows.len() self.scrollbase.scrollable()
} }
fn layout(&mut self, size: Vec2) { fn layout(&mut self, size: Vec2) {
// Compute the text rows. // Compute the text rows.
self.view_height = size.y; self.rows = LinesIterator::new(&self.content, size.x).collect();
self.rows = LinesIterator::new(&self.content, size.x ).collect();
if self.rows.len() > size.y { if self.rows.len() > size.y {
self.rows = LinesIterator::new(&self.content, size.x - 1).collect(); self.rows = LinesIterator::new(&self.content, size.x - 1).collect();
self.start_line = min(self.start_line, self.rows.len() - size.y);
} else {
self.start_line = 0;
} }
self.scrollbase.set_heights(size.y, self.rows.len());
} }
} }