diff --git a/assets/cities.txt b/assets/cities.txt new file mode 100644 index 0000000..93f18a4 --- /dev/null +++ b/assets/cities.txt @@ -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 diff --git a/examples/select.rs b/examples/select.rs index 6b7bd71..05b1f27 100644 --- a/examples/select.rs +++ b/examples/select.rs @@ -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::(&Selector::Id("city")).unwrap().selection().to_string(); diff --git a/src/view/mod.rs b/src/view/mod.rs index 04e942b..cbc4995 100644 --- a/src/view/mod.rs +++ b/src/view/mod.rs @@ -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; diff --git a/src/view/scroll.rs b/src/view/scroll.rs new file mode 100644 index 0000000..d8fe434 --- /dev/null +++ b/src/view/scroll.rs @@ -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(&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); + }); + } + } +} diff --git a/src/view/select_view.rs b/src/view/select_view.rs index e9f8086..0147b80 100644 --- a/src/view/select_view.rs +++ b/src/view/select_view.rs @@ -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 { label: String, @@ -24,6 +27,7 @@ impl Item { pub struct SelectView { items: Vec>, focus: usize, + scrollbase: ScrollBase, } impl SelectView { @@ -32,6 +36,7 @@ impl SelectView { SelectView { items: Vec::new(), focus: 0, + scrollbase: ScrollBase::new(), } } @@ -41,8 +46,13 @@ impl SelectView { } /// 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 { 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 View for SelectView { 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()); + } } diff --git a/src/view/text_view.rs b/src/view/text_view.rs index 3ad7091..e9730df 100644 --- a/src/view/text_view.rs +++ b/src/view/text_view.rs @@ -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, - 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()); } }