mirror of
https://github.com/FliegendeWurst/cursive.git
synced 2024-11-23 17:35:00 +00:00
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:
parent
95ca27a1d0
commit
323805b52f
254
assets/cities.txt
Normal file
254
assets/cities.txt
Normal 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ísoso
|
||||
Vatican City
|
||||
Victoria
|
||||
Vienna
|
||||
Vientiane
|
||||
Vilnius
|
||||
Vitoria-Gasteiz
|
||||
Warsaw
|
||||
Washington, D.C.
|
||||
Wellington
|
||||
West Island
|
||||
Willemstad
|
||||
Windhoek
|
||||
Yamoussoukro
|
||||
Yaoundé
|
||||
Yaren
|
||||
Yerevan
|
||||
Zagreb
|
@ -1,17 +1,25 @@
|
||||
extern crate cursive;
|
||||
|
||||
use std::fs::File;
|
||||
use std::io::{BufReader,BufRead};
|
||||
|
||||
use cursive::Cursive;
|
||||
use cursive::view::{Dialog,SelectView,TextView,Selector};
|
||||
use cursive::view::{Dialog,SelectView,TextView,Selector,BoxView};
|
||||
|
||||
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();
|
||||
|
||||
siv.add_layer(Dialog::new(SelectView::new()
|
||||
.item_str("Berlin")
|
||||
.item_str("London")
|
||||
.item_str("New York")
|
||||
.item_str("Paris")
|
||||
.with_id("city"))
|
||||
siv.add_layer(Dialog::new(BoxView::new((20,10), select.with_id("city")))
|
||||
.title("Where are you from?")
|
||||
.button("Ok", |s| {
|
||||
let city = s.find::<SelectView>(&Selector::Id("city")).unwrap().selection().to_string();
|
||||
|
@ -15,6 +15,8 @@ mod shadow_view;
|
||||
mod edit_view;
|
||||
mod select_view;
|
||||
|
||||
mod scroll;
|
||||
|
||||
use std::any::Any;
|
||||
|
||||
pub use self::view_path::ViewPath;
|
||||
|
95
src/view/scroll.rs
Normal file
95
src/view/scroll.rs
Normal 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);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -1,8 +1,11 @@
|
||||
use std::cmp::min;
|
||||
|
||||
use color;
|
||||
use view::{View,IdView,SizeRequest};
|
||||
use view::{View,IdView,SizeRequest,DimensionRequest};
|
||||
use event::{Event,EventResult,Key};
|
||||
use vec::Vec2;
|
||||
use printer::Printer;
|
||||
use super::scroll::ScrollBase;
|
||||
|
||||
struct Item<T> {
|
||||
label: String,
|
||||
@ -24,6 +27,7 @@ impl <T> Item<T> {
|
||||
pub struct SelectView<T=String> {
|
||||
items: Vec<Item<T>>,
|
||||
focus: usize,
|
||||
scrollbase: ScrollBase,
|
||||
}
|
||||
|
||||
impl <T> SelectView<T> {
|
||||
@ -32,6 +36,7 @@ impl <T> SelectView<T> {
|
||||
SelectView {
|
||||
items: Vec::new(),
|
||||
focus: 0,
|
||||
scrollbase: ScrollBase::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -41,8 +46,13 @@ impl <T> SelectView<T> {
|
||||
}
|
||||
|
||||
/// 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));
|
||||
}
|
||||
|
||||
/// Chainable variant of add_item
|
||||
pub fn item(mut self, label: &str, value: T) -> Self {
|
||||
self.add_item(label, value);
|
||||
|
||||
self
|
||||
}
|
||||
@ -58,36 +68,73 @@ impl SelectView<String> {
|
||||
pub fn item_str(self, label: &str) -> Self {
|
||||
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> {
|
||||
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));
|
||||
}
|
||||
|
||||
self.scrollbase.draw(printer, |printer,i| {
|
||||
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 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)
|
||||
}
|
||||
|
||||
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,
|
||||
Event::KeyEvent(Key::Up) if self.focus > 0 => self.focus -= 1,
|
||||
Event::KeyEvent(Key::Down) if self.focus + 1 < self.items.len() => self.focus += 1,
|
||||
Event::KeyEvent(Key::PageUp) => self.focus -= min(self.focus,10),
|
||||
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,
|
||||
}
|
||||
|
||||
self.scrollbase.scroll_to(self.focus);
|
||||
|
||||
EventResult::Consumed(None)
|
||||
}
|
||||
|
||||
fn take_focus(&mut self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn layout(&mut self, size: Vec2) {
|
||||
self.scrollbase.set_heights(size.y, self.items.len());
|
||||
}
|
||||
}
|
||||
|
@ -1,18 +1,18 @@
|
||||
use std::cmp::{max,min};
|
||||
use std::cmp::max;
|
||||
|
||||
use color;
|
||||
use vec::Vec2;
|
||||
use view::{View,DimensionRequest,SizeRequest};
|
||||
use div::*;
|
||||
use printer::Printer;
|
||||
use event::*;
|
||||
use super::scroll::ScrollBase;
|
||||
|
||||
/// A simple view showing a fixed text
|
||||
pub struct TextView {
|
||||
content: String,
|
||||
rows: Vec<Row>,
|
||||
start_line: usize,
|
||||
view_height: usize,
|
||||
|
||||
scrollbase: ScrollBase,
|
||||
}
|
||||
|
||||
// Subset of the main content representing a row on the display.
|
||||
@ -54,9 +54,8 @@ impl TextView {
|
||||
let content = strip_last_newline(content);
|
||||
TextView {
|
||||
content: content.to_string(),
|
||||
start_line: 0,
|
||||
rows: Vec::new(),
|
||||
view_height: 0,
|
||||
scrollbase: ScrollBase::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -183,36 +182,24 @@ impl <'a> Iterator for LinesIterator<'a> {
|
||||
|
||||
impl View for TextView {
|
||||
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);
|
||||
}
|
||||
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);
|
||||
self.scrollbase.draw(printer, |printer, i| {
|
||||
let row = &self.rows[i];
|
||||
printer.print((0,0), &self.content[row.start..row.end]);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn on_event(&mut self, event: Event) -> EventResult {
|
||||
if self.view_height >= self.rows.len() {
|
||||
if !self.scrollbase.scrollable() {
|
||||
return EventResult::Ignored;
|
||||
}
|
||||
|
||||
match event {
|
||||
Event::KeyEvent(Key::Home) => self.start_line = 0,
|
||||
Event::KeyEvent(Key::End) => self.start_line = self.rows.len() - self.view_height,
|
||||
Event::KeyEvent(Key::Up) if self.start_line > 0 => self.start_line -= 1,
|
||||
Event::KeyEvent(Key::Down) if self.start_line+self.view_height < self.rows.len() => self.start_line += 1,
|
||||
Event::KeyEvent(Key::PageDown) => self.start_line = min(self.start_line+10, self.rows.len()-self.view_height),
|
||||
Event::KeyEvent(Key::PageUp) => self.start_line -= min(self.start_line, 10),
|
||||
Event::KeyEvent(Key::Home) => self.scrollbase.scroll_top(),
|
||||
Event::KeyEvent(Key::End) => self.scrollbase.scroll_bottom(),
|
||||
Event::KeyEvent(Key::Up) if self.scrollbase.can_scroll_up() => self.scrollbase.scroll_up(1),
|
||||
Event::KeyEvent(Key::Down) if self.scrollbase.can_scroll_down() => self.scrollbase.scroll_down(1),
|
||||
Event::KeyEvent(Key::PageDown) => self.scrollbase.scroll_down(10),
|
||||
Event::KeyEvent(Key::PageUp) => self.scrollbase.scroll_up(10),
|
||||
_ => return EventResult::Ignored,
|
||||
}
|
||||
|
||||
@ -248,18 +235,15 @@ impl View for TextView {
|
||||
}
|
||||
|
||||
fn take_focus(&mut self) -> bool {
|
||||
self.view_height < self.rows.len()
|
||||
self.scrollbase.scrollable()
|
||||
}
|
||||
|
||||
fn layout(&mut self, size: Vec2) {
|
||||
// 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 {
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user