From e91021a6338e6980d6193ea702d88d1d7d255879 Mon Sep 17 00:00:00 2001 From: Alexandre Bury Date: Wed, 8 Feb 2017 16:17:18 -0800 Subject: [PATCH 01/23] Bump version to 0.4.1 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 85bc834..20ccbb6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ license = "MIT" name = "cursive" readme = "Readme.md" repository = "https://github.com/gyscos/Cursive" -version = "0.4.0" +version = "0.4.1" [badges] travis-ci = { repository = "gyscos/Cursive" } From 34c74628774eda52014d45e5d13d4a362ddbb72f Mon Sep 17 00:00:00 2001 From: Alexandre Bury Date: Sun, 26 Feb 2017 14:53:50 -0800 Subject: [PATCH 02/23] fix: protect scroll operations when non-scrollable Fixes #109 --- src/view/scroll.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/view/scroll.rs b/src/view/scroll.rs index 7c40d46..2e417e9 100644 --- a/src/view/scroll.rs +++ b/src/view/scroll.rs @@ -119,22 +119,28 @@ impl ScrollBase { /// Scroll to the bottom of the view. pub fn scroll_bottom(&mut self) { - self.start_line = self.content_height - self.view_height; + if self.scrollable() { + self.start_line = self.content_height - self.view_height; + } } /// Scroll down by the given number of line. /// /// Never further than the bottom of the view. pub fn scroll_down(&mut self, n: usize) { - self.start_line = min(self.start_line + n, - self.content_height - self.view_height); + if self.scrollable() { + self.start_line = min(self.start_line + n, + self.content_height - self.view_height); + } } /// Scroll up by the given number of lines. /// /// Never above the top of the view. pub fn scroll_up(&mut self, n: usize) { - self.start_line -= min(self.start_line, n); + if self.scrollable() { + self.start_line -= min(self.start_line, n); + } } /// Draws the scroll bar and the content using the given drawer. From ce3400ec0cd4abe23f1caf20bea4111680b12d88 Mon Sep 17 00:00:00 2001 From: Alexandre Bury Date: Mon, 27 Feb 2017 14:22:43 -0800 Subject: [PATCH 03/23] Add gitter badge --- Readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Readme.md b/Readme.md index cfbda31..bd96d36 100644 --- a/Readme.md +++ b/Readme.md @@ -3,6 +3,7 @@ [![crates.io](https://meritbadge.herokuapp.com/cursive)](https://crates.io/crates/cursive) [![Build Status](https://travis-ci.org/gyscos/Cursive.svg?branch=master)](https://travis-ci.org/gyscos/Cursive) [![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE) +[![Gitter chat](https://badges.gitter.im/gyscos/cursive.png)](https://gitter.im/cursive-rs/cursive) Cursive is a TUI (Text User Interface) library for rust. It is currently based on jeaye's [ncurses-rs](https://github.com/jeaye/ncurses-rs), but [other backends are available](https://github.com/gyscos/Cursive/wiki/Backends). From f6f26bf7af899f207b6e450133fefd1db8c345a3 Mon Sep 17 00:00:00 2001 From: Ilkka Halila Date: Sun, 5 Mar 2017 13:49:20 +0200 Subject: [PATCH 04/23] Increment pancurses dependency to version 0.8 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 20ccbb6..71ea401 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,7 +43,7 @@ version = "5.85.0" [dependencies.pancurses] features = ["wide"] optional = true -version = "0.7" +version = "0.8" [dependencies.termion] optional = true From ea053640fe0027203b25a3c2f78d27778e560761 Mon Sep 17 00:00:00 2001 From: Ilkka Halila Date: Sun, 5 Mar 2017 13:50:42 +0200 Subject: [PATCH 05/23] Use new Attributes and ColorPairs Pancurses now has an Attribute and ColorPair type, making for a more strongly typed way of handling them when compared to using chtypes. Also window now has an .attrget() function so pan::Concrete does not need to track the current color any longer. --- src/backend/curses/pan.rs | 27 +++++++-------------------- 1 file changed, 7 insertions(+), 20 deletions(-) diff --git a/src/backend/curses/pan.rs b/src/backend/curses/pan.rs index 25a8ba4..4ce0893 100644 --- a/src/backend/curses/pan.rs +++ b/src/backend/curses/pan.rs @@ -5,13 +5,11 @@ use backend; use event::{Event, Key}; use self::super::find_closest; -use std::cell::Cell; use theme::{Color, ColorStyle, Effect}; use utf8; pub struct Concrete { window: pancurses::Window, - current_style: Cell, } impl backend::Backend for Concrete { @@ -23,11 +21,10 @@ impl backend::Backend for Concrete { pancurses::cbreak(); pancurses::start_color(); pancurses::curs_set(0); - window.bkgd(pancurses::COLOR_PAIR(ColorStyle::Background.id() as pancurses::chtype)); + window.bkgd(pancurses::ColorPair(ColorStyle::Background.id() as u8)); Concrete { window: window, - current_style: Cell::new(ColorStyle::Background), } } @@ -52,28 +49,18 @@ impl backend::Backend for Concrete { } fn with_color(&self, color: ColorStyle, f: F) { - // TODO: pancurses doesn't have an `attr_get` equivalent - // let mut current_style: pancurses::attr_t = 0; - // let mut current_color: i16 = 0; - // pancurses::attr_get(&mut current_style, &mut current_color); - let current_style = self.current_style.get(); + let (_, current_color_pair) = self.window.attrget(); + let color_attribute = pancurses::ColorPair(color.id() as u8); - let style = pancurses::COLOR_PAIR(color.id() as pancurses::chtype); - self.window.attron(style); - - self.current_style.set(color); + self.window.attron(color_attribute); f(); - self.current_style.set(current_style); - - // self.window.attroff(style); - self.window.attron(pancurses::COLOR_PAIR(current_style.id() as pancurses::chtype)); + self.window.attron(pancurses::ColorPair(current_color_pair as u8)); } fn with_effect(&self, effect: Effect, f: F) { let style = match effect { - // A_REVERSE, taken from ncurses - Effect::Reverse => 1 << (10 + 8u32), - Effect::Simple => pancurses::A_NORMAL, + Effect::Reverse => pancurses::Attribute::Reverse, + Effect::Simple => pancurses::Attribute::Normal, }; self.window.attron(style); f(); From 7d9cb03ffbfeeeac2e02adce2b4139a62fc5d31c Mon Sep 17 00:00:00 2001 From: Alexandre Bury Date: Sun, 5 Mar 2017 11:34:45 -0800 Subject: [PATCH 06/23] More checks against small viewports Prevents panics when the terminal is resized. --- src/views/edit_view.rs | 1 + src/views/linear_layout.rs | 24 ++++++++++++-- src/views/list_view.rs | 68 ++++++++++++++++++++++---------------- src/views/select_view.rs | 3 ++ src/xy.rs | 10 +++++- 5 files changed, 75 insertions(+), 31 deletions(-) diff --git a/src/views/edit_view.rs b/src/views/edit_view.rs index 734ef28..e6626ea 100644 --- a/src/views/edit_view.rs +++ b/src/views/edit_view.rs @@ -343,6 +343,7 @@ impl View for EditView { fn layout(&mut self, size: Vec2) { self.last_length = size.x; + // println_stderr!("Promised: {}", size.x); } fn take_focus(&mut self, _: Direction) -> bool { diff --git a/src/views/linear_layout.rs b/src/views/linear_layout.rs index 9c6a750..0ceb621 100644 --- a/src/views/linear_layout.rs +++ b/src/views/linear_layout.rs @@ -26,6 +26,7 @@ struct Child { } impl Child { + // Compute and caches the required size. fn required_size(&mut self, req: Vec2) -> Vec2 { self.size = self.view.required_size(req); self.size @@ -36,6 +37,17 @@ impl Child { } } +fn cap<'a, I: Iterator>(iter: I, max: usize) { + let mut available = max; + for item in iter { + if *item > available { + *item = available; + } + + available -= *item; + } +} + impl LinearLayout { /// Creates a new layout with the given orientation. pub fn new(orientation: direction::Orientation) -> Self { @@ -197,6 +209,7 @@ impl View for LinearLayout { let o = self.orientation; for child in &mut self.children { + // Every item has the same size orthogonal to the layout child.size.set_axis_from(o.swap(), &size); child.view.layout(size.with_axis_from(o, &child.size)); } @@ -225,8 +238,8 @@ impl View for LinearLayout { return ideal; } - // Ok, so maybe it didn't. - // Budget cuts, everyone. + // Ok, so maybe it didn't. Budget cuts, everyone. + // Let's pretend we have almost no space in this direction. let budget_req = req.with_axis(self.orientation, 1); // println_stderr!("Budget req: {:?}", budget_req); @@ -240,8 +253,15 @@ impl View for LinearLayout { // println_stderr!("Desperate: {:?}", desperate); // This is the lowest we'll ever go. It better fit at least. + let orientation = self.orientation; if !desperate.fits_in(req) { // Just give up... + // TODO: hard-cut + cap(self.children + .iter_mut() + .map(|c| c.size.get_mut(orientation)), + *req.get(self.orientation)); + // TODO: print some error message or something // println_stderr!("Seriously? {:?} > {:?}???", desperate, req); // self.cache = Some(SizeCache::build(desperate, req)); diff --git a/src/views/list_view.rs b/src/views/list_view.rs index 77a84df..453ec30 100644 --- a/src/views/list_view.rs +++ b/src/views/list_view.rs @@ -6,6 +6,8 @@ use event::{Callback, Event, EventResult, Key}; use std::any::Any; use std::rc::Rc; + +use unicode_width::UnicodeWidthStr; use vec::Vec2; use view::ScrollBase; use view::Selector; @@ -132,16 +134,16 @@ impl ListView { fn move_focus(&mut self, n: usize, source: direction::Direction) -> EventResult { let i = if let Some(i) = - source.relative(direction::Orientation::Vertical) - .and_then(|rel| { - // The iterator starts at the focused element. - // We don't want that one. - self.iter_mut(true, rel) - .skip(1) - .filter_map(|p| try_focus(p, source)) - .take(n) - .last() - }) { + source.relative(direction::Orientation::Vertical) + .and_then(|rel| { + // The iterator starts at the focused element. + // We don't want that one. + self.iter_mut(true, rel) + .skip(1) + .filter_map(|p| try_focus(p, source)) + .take(n) + .last() + }) { i } else { return EventResult::Ignored; @@ -181,28 +183,30 @@ impl View for ListView { let offset = self.children .iter() .map(Child::label) - .map(str::len) + .map(UnicodeWidthStr::width) .max() .unwrap_or(0) + 1; - self.scrollbase.draw(printer, |printer, i| { - match self.children[i] { - Child::Row(ref label, ref view) => { - printer.print((0, 0), label); - view.draw(&printer.offset((offset, 0), i == self.focus)); - } - Child::Delimiter => (), + // println_stderr!("Offset: {}", offset); + + self.scrollbase.draw(printer, |printer, i| match self.children[i] { + Child::Row(ref label, ref view) => { + printer.print((0, 0), label); + view.draw(&printer.offset((offset, 0), i == self.focus)); } + Child::Delimiter => (), }); } fn required_size(&mut self, req: Vec2) -> Vec2 { - let label_size = self.children + // We'll show 2 columns: the labels, and the views. + let label_width = self.children .iter() .map(Child::label) - .map(str::len) + .map(UnicodeWidthStr::width) .max() .unwrap_or(0); + let view_size = self.children .iter_mut() .filter_map(Child::view) @@ -211,26 +215,34 @@ impl View for ListView { .unwrap_or(0); if self.children.len() > req.y { - Vec2::new(label_size + 1 + view_size + 2, req.y) + Vec2::new(label_width + 1 + view_size + 2, req.y) } else { - Vec2::new(label_size + 1 + view_size, self.children.len()) + Vec2::new(label_width + 1 + view_size, self.children.len()) } } fn layout(&mut self, size: Vec2) { self.scrollbase.set_heights(size.y, self.children.len()); - let label_size = self.children + // We'll show 2 columns: the labels, and the views. + let label_width = self.children .iter() .map(Child::label) - .map(str::len) + .map(UnicodeWidthStr::width) .max() .unwrap_or(0); - let mut available = size.x - label_size - 1; - if self.children.len() > size.y { - available -= 2; - } + let spacing = 1; + let scrollbar_width = if self.children.len() > size.y { 2 } else { 0 }; + + let available = if label_width + spacing + scrollbar_width > size.x { + // We have no space for the kids! :( + 0 + } else { + size.x - label_width - spacing - scrollbar_width + }; + + // println_stderr!("Available: {}", available); for child in self.children.iter_mut().filter_map(Child::view) { child.layout(Vec2::new(available, 1)); diff --git a/src/views/select_view.rs b/src/views/select_view.rs index d7181ee..b0301cd 100644 --- a/src/views/select_view.rs +++ b/src/views/select_view.rs @@ -340,6 +340,9 @@ impl View for SelectView { ColorStyle::Highlight }; let x = printer.size.x; + if x == 0 { + return; + } printer.with_color(style, |printer| { diff --git a/src/xy.rs b/src/xy.rs index 4f30323..3e22b46 100644 --- a/src/xy.rs +++ b/src/xy.rs @@ -45,6 +45,14 @@ impl XY { } } + /// Returns a mutable reference to the value on the given axis. + pub fn get_mut(&mut self, o: Orientation) -> &mut T { + match o { + Orientation::Horizontal => &mut self.x, + Orientation::Vertical => &mut self.y, + } + } + /// Returns a new `XY` of tuples made by zipping `self` and `other`. pub fn zip(self, other: XY) -> XY<(T, U)> { XY::new((self.x, other.x), (self.y, other.y)) @@ -56,7 +64,7 @@ impl XY { } } -impl XY { +impl XY { /// Returns a new `XY` with the axis `o` set to `value`. pub fn with_axis(&self, o: Orientation, value: T) -> Self { let mut new = self.clone(); From 6f129ac83dffd0cc4c2c3a826b05e081d817b46e Mon Sep 17 00:00:00 2001 From: Alexandre Bury Date: Sun, 5 Mar 2017 14:45:21 -0800 Subject: [PATCH 07/23] Only include chan dependancy with termion backend. We may want to use channels for all backend some day, but not today. --- Cargo.toml | 7 +++++-- src/lib.rs | 1 + 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 71ea401..e0ad628 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,13 +20,16 @@ optional = true version = "0.6" [dependencies] -chan = "0.1.18" num = "0.1" odds = "0.2" toml = "0.2" unicode-segmentation = "1.0" unicode-width = "0.1" +[dependencies.chan] +optional = true +version = "0.1.18" + [dependencies.chan-signal] optional = true version = "0.1" @@ -55,7 +58,7 @@ skeptic = "0.6" [features] default = ["ncurses"] -termion-backend = ["termion", "chan-signal"] +termion-backend = ["termion", "chan", "chan-signal"] pancurses-backend = ["pancurses"] [lib] diff --git a/src/lib.rs b/src/lib.rs index e6cefe4..2f33997 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -65,6 +65,7 @@ extern crate unicode_width; extern crate odds; extern crate num; +#[cfg(feature = "termion")] #[macro_use] extern crate chan; From e0b279d9dfc63f54cecb892f414b12074449ae01 Mon Sep 17 00:00:00 2001 From: Alexandre Bury Date: Sun, 5 Mar 2017 15:18:15 -0800 Subject: [PATCH 08/23] Fix clippy warnings --- examples/logs.rs | 6 +++--- examples/progress.rs | 4 ++-- src/views/edit_view.rs | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/logs.rs b/examples/logs.rs index 53d7b17..749a493 100644 --- a/examples/logs.rs +++ b/examples/logs.rs @@ -22,8 +22,8 @@ fn main() { let (tx, rx) = mpsc::channel(); // Generate data in a separate thread. - thread::spawn(|| { - generate_logs(tx); + thread::spawn(move || { + generate_logs(&tx); }); // And sets the view to read from the other end of the channel. @@ -34,7 +34,7 @@ fn main() { // We will only simulate log generation here. // In real life, this may come from a running task, a separate process, ... -fn generate_logs(tx: mpsc::Sender) { +fn generate_logs(tx: &mpsc::Sender) { let mut i = 1; loop { let line = format!("Interesting log line {}", i); diff --git a/examples/progress.rs b/examples/progress.rs index d22c1a8..3a83235 100644 --- a/examples/progress.rs +++ b/examples/progress.rs @@ -28,7 +28,7 @@ fn main() { } // Function to simulate a long process. -fn fake_load(n_max: usize, counter: Counter) { +fn fake_load(n_max: usize, counter: &Counter) { for _ in 0..n_max { thread::sleep(Duration::from_millis(5)); // The `counter.tick()` method increases the progress value @@ -50,7 +50,7 @@ fn phase_1(s: &mut Cursive) { .range(0, n_max) .with_task(move |counter| { // This closure will be called in a separate thread. - fake_load(n_max, counter); + fake_load(n_max, &counter); // When we're done, send a callback through the channel cb.send(Box::new(coffee_break)).unwrap(); diff --git a/src/views/edit_view.rs b/src/views/edit_view.rs index e6626ea..044bc0e 100644 --- a/src/views/edit_view.rs +++ b/src/views/edit_view.rs @@ -267,7 +267,7 @@ fn make_small_stars(length: usize) -> &'static str { impl View for EditView { fn draw(&self, printer: &Printer) { - assert!(printer.size.x == self.last_length, + assert_eq!(printer.size.x, self.last_length, "Was promised {}, received {}", self.last_length, printer.size.x); From dc7754d38f352a16e79947c288782e2bf1dc3885 Mon Sep 17 00:00:00 2001 From: Constantin Berhard Date: Mon, 6 Mar 2017 15:32:03 +0100 Subject: [PATCH 09/23] fix #113 --- src/utils/mod.rs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/utils/mod.rs b/src/utils/mod.rs index c7f4f47..f86752c 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -50,14 +50,16 @@ pub fn prefix<'a, I>(iter: I, available_width: usize, delimiter: &str) -> Prefix let mut current_width = 0; let sum = iter.take_while(|token| { - let width = token.width(); - if current_width + width > available_width { + let needed_width = if current_width == 0 { + token.width() + } else { + // We also need to insert the delimiter, if not at the beginning. + token.width() + delimiter_width + }; + if current_width + needed_width > available_width { false } else { - if current_width != 0 { - current_width += delimiter_width; - } - current_width += width; + current_width += needed_width; true } }) @@ -66,7 +68,9 @@ pub fn prefix<'a, I>(iter: I, available_width: usize, delimiter: &str) -> Prefix // We counted delimiter once too many times, // but only if the iterator was non empty. - let length = if sum == 0 { sum } else { sum - delimiter_len }; + let length = sum.saturating_sub(delimiter_len); + + debug_assert!(current_width <= available_width); Prefix { length: length, From 30cac851e7cd61de2b2df5de9b7251b00c4fe059 Mon Sep 17 00:00:00 2001 From: Alexandre Bury Date: Mon, 6 Mar 2017 10:34:58 -0800 Subject: [PATCH 10/23] Fix utils::prefix And add a few tests --- src/utils/mod.rs | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/src/utils/mod.rs b/src/utils/mod.rs index f86752c..04cec20 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -50,16 +50,12 @@ pub fn prefix<'a, I>(iter: I, available_width: usize, delimiter: &str) -> Prefix let mut current_width = 0; let sum = iter.take_while(|token| { - let needed_width = if current_width == 0 { - token.width() - } else { - // We also need to insert the delimiter, if not at the beginning. - token.width() + delimiter_width - }; - if current_width + needed_width > available_width { + let width = token.width(); + if current_width + width > available_width { false } else { - current_width += needed_width; + current_width += width; + current_width += delimiter_width; true } }) @@ -70,7 +66,7 @@ pub fn prefix<'a, I>(iter: I, available_width: usize, delimiter: &str) -> Prefix // but only if the iterator was non empty. let length = sum.saturating_sub(delimiter_len); - debug_assert!(current_width <= available_width); + debug_assert!(current_width <= available_width + delimiter_width); Prefix { length: length, @@ -98,3 +94,15 @@ pub fn suffix<'a, I>(iter: I, width: usize, delimiter: &str) -> Prefix pub fn simple_suffix(text: &str, width: usize) -> Prefix { suffix(text.graphemes(true), width, "") } + +#[cfg(test)] +mod tests { + use utils; + + #[test] + fn test_prefix() { + assert_eq!(utils::prefix(" abra ".split(' '), 5, " ").length, 5); + assert_eq!(utils::prefix("abra a".split(' '), 5, " ").length, 4); + assert_eq!(utils::prefix("a a br".split(' '), 5, " ").length, 3); + } +} From 5f5fb4e5024438f400bb647474fddb42f400d034 Mon Sep 17 00:00:00 2001 From: Alexandre Bury Date: Mon, 6 Mar 2017 10:38:18 -0800 Subject: [PATCH 11/23] Add comments --- src/utils/mod.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 04cec20..a4f9bcf 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -48,12 +48,15 @@ pub fn prefix<'a, I>(iter: I, available_width: usize, delimiter: &str) -> Prefix let delimiter_width = delimiter.width(); let delimiter_len = delimiter.len(); + // `current_width` is the width of everything + // before the next token, including any space. let mut current_width = 0; let sum = iter.take_while(|token| { let width = token.width(); if current_width + width > available_width { false } else { + // Include the delimiter after this token. current_width += width; current_width += delimiter_width; true @@ -66,6 +69,7 @@ pub fn prefix<'a, I>(iter: I, available_width: usize, delimiter: &str) -> Prefix // but only if the iterator was non empty. let length = sum.saturating_sub(delimiter_len); + // `current_width` includes a delimiter after the last token debug_assert!(current_width <= available_width + delimiter_width); Prefix { From 36d5aa016ab591f21b3f94c2a10f6123af4f9e46 Mon Sep 17 00:00:00 2001 From: Constantin Berhard Date: Mon, 6 Mar 2017 22:46:16 +0100 Subject: [PATCH 12/23] use Iterator::sum() for summing instead of fold --- src/utils/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils/mod.rs b/src/utils/mod.rs index a4f9bcf..71dc16c 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -51,7 +51,7 @@ pub fn prefix<'a, I>(iter: I, available_width: usize, delimiter: &str) -> Prefix // `current_width` is the width of everything // before the next token, including any space. let mut current_width = 0; - let sum = iter.take_while(|token| { + let sum: usize = iter.take_while(|token| { let width = token.width(); if current_width + width > available_width { false @@ -63,7 +63,7 @@ pub fn prefix<'a, I>(iter: I, available_width: usize, delimiter: &str) -> Prefix } }) .map(|token| token.len() + delimiter_len) - .fold(0, |a, b| a + b); + .sum(); // We counted delimiter once too many times, // but only if the iterator was non empty. From 87676295a0d79d57a660ebbc9490abcb74f04c4c Mon Sep 17 00:00:00 2001 From: Alexandre Bury Date: Mon, 6 Mar 2017 14:53:10 -0800 Subject: [PATCH 13/23] fix: Check for insufficient space in Panel::required_size --- src/views/panel.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/views/panel.rs b/src/views/panel.rs index 8b1b1f1..64a11dd 100644 --- a/src/views/panel.rs +++ b/src/views/panel.rs @@ -20,7 +20,13 @@ impl ViewWrapper for Panel { fn wrap_required_size(&mut self, req: Vec2) -> Vec2 { // TODO: make borders conditional? - self.view.required_size(req - (2, 2)) + (2, 2) + let req = if Vec2::new(2, 2).fits_in(req) { + req - (2, 2) + } else { + Vec2::zero() + }; + + self.view.required_size(req) + (2, 2) } fn wrap_draw(&self, printer: &Printer) { From cbe237624fc2d24628d1d8f90a7617114743861b Mon Sep 17 00:00:00 2001 From: Alexandre Bury Date: Mon, 6 Mar 2017 22:33:03 -0800 Subject: [PATCH 14/23] Update toml to 0.3 --- Cargo.toml | 2 +- src/theme.rs | 15 ++++++++++----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e0ad628..a6156a3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,7 @@ version = "0.6" [dependencies] num = "0.1" odds = "0.2" -toml = "0.2" +toml = "0.3" unicode-segmentation = "1.0" unicode-width = "0.1" diff --git a/src/theme.rs b/src/theme.rs index 36c41de..6e63f9e 100644 --- a/src/theme.rs +++ b/src/theme.rs @@ -210,7 +210,7 @@ impl Default for Theme { } impl Theme { - fn load(&mut self, table: &toml::Table) { + fn load(&mut self, table: &toml::value::Table) { if let Some(&toml::Value::Boolean(shadow)) = table.get("shadow") { self.shadow = shadow; } @@ -314,7 +314,7 @@ pub struct Palette { impl Palette { /// Fills `self` with the colors from the given `table`. - fn load(&mut self, table: &toml::Table) { + fn load(&mut self, table: &toml::value::Table) { load_color(&mut self.background, table.get("background")); load_color(&mut self.shadow, table.get("shadow")); load_color(&mut self.view, table.get("view")); @@ -413,7 +413,7 @@ pub enum Error { /// An error occured when reading the file. Io(io::Error), /// An error occured when parsing the toml content. - Parse, + Parse(toml::de::Error), } impl From for Error { @@ -422,6 +422,12 @@ impl From for Error { } } +impl From for Error { + fn from(err: toml::de::Error) -> Self { + Error::Parse(err) + } +} + impl Color { fn parse(value: &str) -> Option { Some(match value { @@ -490,8 +496,7 @@ pub fn load_theme_file>(filename: P) -> Result { /// Loads a theme string and sets it as active. pub fn load_theme(content: &str) -> Result { - let mut parser = toml::Parser::new(content); - let table = try!(parser.parse().ok_or(Error::Parse)); + let table = toml::de::from_str(content)?; let mut theme = Theme::default(); theme.load(&table); From 22258d3d84086338f27294581662dcaa14bc2696 Mon Sep 17 00:00:00 2001 From: Alexandre Bury Date: Mon, 6 Mar 2017 22:35:12 -0800 Subject: [PATCH 15/23] Update chan-signal to 0.2 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index a6156a3..a60b2c4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,7 +32,7 @@ version = "0.1.18" [dependencies.chan-signal] optional = true -version = "0.1" +version = "0.2" [dependencies.bear-lib-terminal] optional = true From 633e274216b12596e8427716780e08eeb4ab149a Mon Sep 17 00:00:00 2001 From: Alexandre Bury Date: Mon, 6 Mar 2017 22:49:03 -0800 Subject: [PATCH 16/23] Update rust-skeptic to 0.7 --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a60b2c4..6f40559 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ travis-ci = { repository = "gyscos/Cursive" } [build-dependencies.skeptic] optional = true -version = "0.6" +version = "0.7" [dependencies] num = "0.1" @@ -54,7 +54,7 @@ version = "1.1.1" [dev-dependencies] rand = "0.3" -skeptic = "0.6" +skeptic = "0.7" [features] default = ["ncurses"] From 9850e931259cd4acd59dc5e879a96db60d949b97 Mon Sep 17 00:00:00 2001 From: Alexandre Bury Date: Tue, 7 Mar 2017 13:42:32 -0800 Subject: [PATCH 17/23] Add ncurses-backend and blt-backend features Not very useful for now since those backends don't need any extra crate, but at least it improves coherence since there is termion-backend already. --- Cargo.toml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 6f40559..ea8999c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -57,9 +57,12 @@ rand = "0.3" skeptic = "0.7" [features] -default = ["ncurses"] +default = ["ncurses-backend"] + +ncurses-backend = ["ncurses"] termion-backend = ["termion", "chan", "chan-signal"] pancurses-backend = ["pancurses"] +blt-backend = ["bear-lib-terminal"] [lib] name = "cursive" From 0bbc10706e501032949b4a131e2e6f1aa562bb7a Mon Sep 17 00:00:00 2001 From: Alexandre Bury Date: Sat, 4 Feb 2017 21:03:35 -0800 Subject: [PATCH 18/23] EditVIew: Add mutable and non-mutable callback methods mutable callbacks disable recursive calls --- src/views/edit_view.rs | 125 +++++++++++++++++++++++++++++++++++------ 1 file changed, 108 insertions(+), 17 deletions(-) diff --git a/src/views/edit_view.rs b/src/views/edit_view.rs index 044bc0e..9afa540 100644 --- a/src/views/edit_view.rs +++ b/src/views/edit_view.rs @@ -3,6 +3,7 @@ use {Cursive, Printer, With}; use direction::Direction; use event::{Callback, Event, EventResult, Key}; +use std::cell::RefCell; use std::rc::Rc; use theme::{ColorStyle, Effect}; @@ -74,7 +75,7 @@ pub struct EditView { /// Callback when the content is modified. /// - /// Will be called with the current content and the cursor position + /// Will be called with the current content and the cursor position. on_edit: Option>, /// Callback when is pressed. @@ -136,25 +137,118 @@ impl EditView { self.enabled = true; } + /// Sets a mutable callback to be called whenever the content is modified. + /// + /// `callback` will be called with the view + /// content and the current cursor position. + /// + /// *Warning*: this callback cannot be called recursively. If you somehow + /// trigger this callback again in the given closure, it will be ignored. + /// + /// If you don't need a mutable closure but want the possibility of + /// recursive calls, see [`set_on_edit`](#method.set_on_edit). + pub fn set_on_edit_mut(&mut self, callback: F) + where F: FnMut(&mut Cursive, &str, usize) + 'static + { + let callback = RefCell::new(callback); + // Here's the weird trick: if we're already borrowed, + // just ignored the callback. + self.set_on_edit(move |s, text, cursor| { + if let Ok(mut f) = callback.try_borrow_mut() { + // Beeeaaah that's ugly. + // Why do we need to manually dereference here? + (&mut *f)(s, text, cursor); + } + }); + } + /// Sets a callback to be called whenever the content is modified. /// /// `callback` will be called with the view /// content and the current cursor position. - pub fn on_edit(mut self, callback: F) -> Self + /// + /// This callback can safely trigger itself recursively if needed + /// (for instance if you call `on_event` on this view from the callback). + /// + /// If you need a mutable closure and don't care about the recursive + /// aspect, see [`set_on_edit_mut`](#method.set_on_edit_mut). + pub fn set_on_edit(&mut self, callback: F) where F: Fn(&mut Cursive, &str, usize) + 'static { self.on_edit = Some(Rc::new(callback)); - self + } + + /// Sets a mutable callback to be called whenever the content is modified. + /// + /// Chainable variant. See [`set_on_edit_mut`](#method.set_on_edit_mut). + pub fn on_edit_mut(self, callback: F) -> Self + where F: FnMut(&mut Cursive, &str, usize) + 'static + { + self.with(|v| v.set_on_edit_mut(callback)) + } + + /// Sets a callback to be called whenever the content is modified. + /// + /// Chainable variant. See [`set_on_edit`](#method.set_on_edit). + pub fn on_edit(self, callback: F) -> Self + where F: Fn(&mut Cursive, &str, usize) + 'static + { + self.with(|v| v.set_on_edit(callback)) + } + + /// Sets a mutable callback to be called when `` is pressed. + /// + /// `callback` will be given the content of the view. + /// + /// *Warning*: this callback cannot be called recursively. If you somehow + /// trigger this callback again in the given closure, it will be ignored. + /// + /// If you don't need a mutable closure but want the possibility of + /// recursive calls, see [`set_on_submit`](#method.set_on_submit). + pub fn set_on_submit_mut(&mut self, callback: F) + where F: FnMut(&mut Cursive, &str) + 'static + { + // TODO: don't duplicate all those methods. + // Instead, have some generic function immutify() + // or something that wraps a FnMut closure. + let callback = RefCell::new(callback); + self.set_on_submit(move |s, text| if let Ok(mut f) = + callback.try_borrow_mut() { + (&mut *f)(s, text); + }); } /// Sets a callback to be called when `` is pressed. /// /// `callback` will be given the content of the view. - pub fn on_submit(mut self, callback: F) -> Self + /// + /// This callback can safely trigger itself recursively if needed + /// (for instance if you call `on_event` on this view from the callback). + /// + /// If you need a mutable closure and don't care about the recursive + /// aspect, see [`set_on_submit_mut`](#method.set_on_submit_mut). + pub fn set_on_submit(&mut self, callback: F) where F: Fn(&mut Cursive, &str) + 'static { self.on_submit = Some(Rc::new(callback)); - self + } + + /// Sets a mutable callback to be called when `` is pressed. + /// + /// Chainable variant. + pub fn on_submit_mut(self, callback: F) -> Self + where F: FnMut(&mut Cursive, &str) + 'static + { + self.with(|v| v.set_on_submit_mut(callback)) + } + + /// Sets a callback to be called when `` is pressed. + /// + /// Chainable variant. + pub fn on_submit(self, callback: F) -> Self + where F: Fn(&mut Cursive, &str) + 'static + { + self.with(|v| v.set_on_submit(callback)) } /// Enable or disable this view. @@ -239,9 +333,10 @@ impl EditView { // Look at the content before the cursor (we will print its tail). // From the end, count the length until we reach `available`. // Then sum the byte lengths. - let suffix_length = - simple_suffix(&self.content[self.offset..self.cursor], - available).length; + let suffix_length = simple_suffix(&self.content[self.offset.. + self.cursor], + available) + .length; self.offset = self.cursor - suffix_length; // Make sure the cursor is in view assert!(self.cursor >= self.offset); @@ -250,8 +345,8 @@ impl EditView { // If we have too much space if self.content[self.offset..].width() < self.last_length { - let suffix_length = simple_suffix(&self.content, - self.last_length - 1).length; + let suffix_length = + simple_suffix(&self.content, self.last_length - 1).length; self.offset = self.content.len() - suffix_length; } } @@ -393,9 +488,7 @@ impl View for EditView { Event::Key(Key::Enter) if self.on_submit.is_some() => { let cb = self.on_submit.clone().unwrap(); let content = self.content.clone(); - return EventResult::with_cb(move |s| { - cb(s, &content); - }); + return EventResult::with_cb(move |s| { cb(s, &content); }); } _ => return EventResult::Ignored, } @@ -404,13 +497,11 @@ impl View for EditView { let cb = self.on_edit.clone().map(|cb| { - // Get a new Rc on it + // Get a new Rc on the content let content = self.content.clone(); let cursor = self.cursor; - Callback::from_fn(move |s| { - cb(s, &content, cursor); - }) + Callback::from_fn(move |s| { cb(s, &content, cursor); }) }); EventResult::Consumed(cb) } From 77433e80eea865e6366069b1ae21265f87227ae1 Mon Sep 17 00:00:00 2001 From: Alexandre Bury Date: Wed, 15 Mar 2017 16:35:20 -0700 Subject: [PATCH 19/23] fix: add Panel::wrap_layout Fixes #119 --- src/views/panel.rs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/views/panel.rs b/src/views/panel.rs index 64a11dd..e3720d6 100644 --- a/src/views/panel.rs +++ b/src/views/panel.rs @@ -31,7 +31,16 @@ impl ViewWrapper for Panel { fn wrap_draw(&self, printer: &Printer) { printer.print_box((0, 0), printer.size, true); - self.view - .draw(&printer.sub_printer((1, 1), printer.size - (2, 2), true)); + self.view.draw(&printer.sub_printer((1, 1), + printer.size - (2, 2), + true)); + } + + fn wrap_layout(&mut self, size: Vec2) { + self.view.layout(if Vec2::new(2, 2).fits_in(size) { + size - (2, 2) + } else { + size + }); } } From 906cd557a89cb14ee9939ac2107f6dcaa08b98f8 Mon Sep 17 00:00:00 2001 From: Alexandre Bury Date: Sat, 25 Mar 2017 00:23:15 -0700 Subject: [PATCH 20/23] Readme: update images title --- Readme.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Readme.md b/Readme.md index bd96d36..6d295ed 100644 --- a/Readme.md +++ b/Readme.md @@ -51,12 +51,12 @@ fn main() { Check out the other [examples](https://github.com/gyscos/Cursive/tree/master/examples) to get these results, and more: -`edit` example -`lorem` example -`menubar` example -`select` example -`list_view` example -`theme` example +edit.rs example +lorem.rs example +menubar.rs example +select.rs example +list_view.rs example +theme.rs example _(Colors may depend on your terminal configuration.)_ From 79212d4be68b49f61d13c0bb620785343ad919af Mon Sep 17 00:00:00 2001 From: Alexandre Bury Date: Sat, 25 Mar 2017 00:34:53 -0700 Subject: [PATCH 21/23] Readme: add div around example images Otherwise it's not treated as HTML --- Readme.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Readme.md b/Readme.md index 6d295ed..53e890d 100644 --- a/Readme.md +++ b/Readme.md @@ -51,12 +51,14 @@ fn main() { Check out the other [examples](https://github.com/gyscos/Cursive/tree/master/examples) to get these results, and more: +
edit.rs example lorem.rs example menubar.rs example select.rs example list_view.rs example theme.rs example +
_(Colors may depend on your terminal configuration.)_ From 2cd2787119d036a450ae05068257067523b700c8 Mon Sep 17 00:00:00 2001 From: Alexandre Bury Date: Sat, 25 Mar 2017 11:01:25 -0700 Subject: [PATCH 22/23] Event::Unknown now carries the unknown bytes rather than a i32 And termion has been updated to use termion::Event::Unsupported --- Cargo.toml | 2 +- src/backend/curses/n.rs | 13 ++++--- src/backend/curses/pan.rs | 34 +++++++++++++------ src/backend/termion.rs | 67 +++++++++++++++++++------------------ src/event.rs | 4 +-- src/lib.rs | 2 +- src/views/dialog.rs | 4 +-- src/views/key_event_view.rs | 2 +- src/views/linear_layout.rs | 2 +- src/views/list_view.rs | 2 +- 10 files changed, 75 insertions(+), 57 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ea8999c..cf7eaed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,7 +50,7 @@ version = "0.8" [dependencies.termion] optional = true -version = "1.1.1" +version = "1.3.0" [dev-dependencies] rand = "0.3" diff --git a/src/backend/curses/n.rs b/src/backend/curses/n.rs index 8e568ce..467a6c2 100644 --- a/src/backend/curses/n.rs +++ b/src/backend/curses/n.rs @@ -1,9 +1,9 @@ extern crate ncurses; -use backend; -use event::{Event, Key}; use self::super::find_closest; +use backend; +use event::{Event, Key}; use theme::{Color, ColorStyle, Effect}; use utf8; @@ -94,7 +94,7 @@ impl backend::Backend for Concrete { if 32 <= ch && ch <= 255 && ch != 127 { Event::Char(utf8::read_char(ch as u8, || Some(ncurses::getch() as u8)) - .unwrap()) + .unwrap()) } else { parse_ncurses_char(ch) } @@ -226,6 +226,11 @@ fn parse_ncurses_char(ch: i32) -> Event { // Values 8-10 (H,I,J) are used by other commands, // so we probably won't receive them. Meh~ c @ 1...25 => Event::CtrlChar((b'a' + (c - 1) as u8) as char), - _ => Event::Unknown(ch), + other => { + // Split the i32 into 4 bytes + Event::Unknown((0..4) + .map(|i| ((other >> (8 * i)) & 0xFF) as u8) + .collect()) + } } } diff --git a/src/backend/curses/pan.rs b/src/backend/curses/pan.rs index 4ce0893..062df26 100644 --- a/src/backend/curses/pan.rs +++ b/src/backend/curses/pan.rs @@ -1,10 +1,10 @@ extern crate pancurses; -use backend; -use event::{Event, Key}; use self::super::find_closest; +use backend; +use event::{Event, Key}; use theme::{Color, ColorStyle, Effect}; use utf8; @@ -23,9 +23,7 @@ impl backend::Backend for Concrete { pancurses::curs_set(0); window.bkgd(pancurses::ColorPair(ColorStyle::Background.id() as u8)); - Concrete { - window: window, - } + Concrete { window: window } } fn screen_size(&self) -> (usize, usize) { @@ -88,25 +86,39 @@ impl backend::Backend for Concrete { // TODO: wait for a very short delay. If more keys are // pipelined, it may be an escape sequence. pancurses::Input::Character('\u{7f}') | - pancurses::Input::Character('\u{8}') => Event::Key(Key::Backspace), + pancurses::Input::Character('\u{8}') => { + Event::Key(Key::Backspace) + } pancurses::Input::Character('\u{9}') => Event::Key(Key::Tab), pancurses::Input::Character('\u{1b}') => Event::Key(Key::Esc), pancurses::Input::Character(c) if 32 <= (c as u32) && (c as u32) <= 255 => { Event::Char(utf8::read_char(c as u8, || { - self.window.getch().and_then(|i| match i { + self.window.getch().and_then(|i| match i { pancurses::Input::Character(c) => { Some(c as u8) } _ => None, }) - }) - .unwrap()) + }) + .unwrap()) + } + pancurses::Input::Character(c) => { + let mut bytes = [0u8; 4]; + Event::Unknown(c.encode_utf8(&mut bytes) + .as_bytes() + .to_vec()) } - pancurses::Input::Character(c) => Event::Unknown(c as i32), // TODO: Some key combos are not recognized by pancurses, // but are sent as Unknown. We could still parse them here. - pancurses::Input::Unknown(i) => Event::Unknown(i), + pancurses::Input::Unknown(other) => { + Event::Unknown((0..4) + .map(|i| { + ((other >> (8 * i)) & 0xFF) as + u8 + }) + .collect()) + } // TODO: I honestly have no fucking idea what KeyCodeYes is pancurses::Input::KeyCodeYes => Event::Refresh, pancurses::Input::KeyBreak => Event::Key(Key::PauseBreak), diff --git a/src/backend/termion.rs b/src/backend/termion.rs index f77c0f7..5ae7851 100644 --- a/src/backend/termion.rs +++ b/src/backend/termion.rs @@ -2,22 +2,22 @@ extern crate termion; extern crate chan_signal; -use ::backend; -use chan; -use ::event::{Event, Key}; use self::termion::color as tcolor; +use self::termion::event::Event as TEvent; use self::termion::event::Key as TKey; use self::termion::input::TermRead; use self::termion::raw::IntoRawMode; use self::termion::style as tstyle; +use backend; +use chan; +use event::{Event, Key}; use std::cell::Cell; use std::collections::BTreeMap; use std::fmt; use std::io::Write; use std::thread; -use std::time; -use ::theme; +use theme; pub struct Concrete { terminal: termion::raw::RawTerminal<::std::io::Stdout>, @@ -84,11 +84,11 @@ impl backend::Backend for Concrete { let terminal = ::std::io::stdout().into_raw_mode().unwrap(); let (sender, receiver) = chan::async(); - thread::spawn(move || for key in ::std::io::stdin().keys() { - if let Ok(key) = key { - sender.send(map_key(key)) - } - }); + thread::spawn(move || for key in ::std::io::stdin().events() { + if let Ok(key) = key { + sender.send(map_key(key)) + } + }); let backend = Concrete { terminal: terminal, @@ -185,29 +185,30 @@ impl backend::Backend for Concrete { } } -fn map_key(key: TKey) -> Event { - match key { - TKey::Esc => Event::Key(Key::Esc), - TKey::Backspace => Event::Key(Key::Backspace), - TKey::Left => Event::Key(Key::Left), - TKey::Right => Event::Key(Key::Right), - TKey::Up => Event::Key(Key::Up), - TKey::Down => Event::Key(Key::Down), - TKey::Home => Event::Key(Key::Home), - TKey::End => Event::Key(Key::End), - TKey::PageUp => Event::Key(Key::PageUp), - TKey::PageDown => Event::Key(Key::PageDown), - TKey::Delete => Event::Key(Key::Del), - TKey::Insert => Event::Key(Key::Ins), - TKey::F(i) if i < 12 => Event::Key(Key::from_f(i)), - TKey::F(j) => Event::Unknown(-(j as i32)), - TKey::Char('\n') => Event::Key(Key::Enter), - TKey::Char('\t') => Event::Key(Key::Tab), - TKey::Char(c) => Event::Char(c), - TKey::Ctrl('c') => Event::Exit, - TKey::Ctrl(c) => Event::CtrlChar(c), - TKey::Alt(c) => Event::AltChar(c), - _ => Event::Unknown(-1), +fn map_key(event: TEvent) -> Event { + match event { + TEvent::Unsupported(bytes) => Event::Unknown(bytes), + TEvent::Key(TKey::Esc) => Event::Key(Key::Esc), + TEvent::Key(TKey::Backspace) => Event::Key(Key::Backspace), + TEvent::Key(TKey::Left) => Event::Key(Key::Left), + TEvent::Key(TKey::Right) => Event::Key(Key::Right), + TEvent::Key(TKey::Up) => Event::Key(Key::Up), + TEvent::Key(TKey::Down) => Event::Key(Key::Down), + TEvent::Key(TKey::Home) => Event::Key(Key::Home), + TEvent::Key(TKey::End) => Event::Key(Key::End), + TEvent::Key(TKey::PageUp) => Event::Key(Key::PageUp), + TEvent::Key(TKey::PageDown) => Event::Key(Key::PageDown), + TEvent::Key(TKey::Delete) => Event::Key(Key::Del), + TEvent::Key(TKey::Insert) => Event::Key(Key::Ins), + TEvent::Key(TKey::F(i)) if i < 12 => Event::Key(Key::from_f(i)), + TEvent::Key(TKey::F(j)) => Event::Unknown(vec![j]), + TEvent::Key(TKey::Char('\n')) => Event::Key(Key::Enter), + TEvent::Key(TKey::Char('\t')) => Event::Key(Key::Tab), + TEvent::Key(TKey::Char(c)) => Event::Char(c), + TEvent::Key(TKey::Ctrl('c')) => Event::Exit, + TEvent::Key(TKey::Ctrl(c)) => Event::CtrlChar(c), + TEvent::Key(TKey::Alt(c)) => Event::AltChar(c), + _ => Event::Unknown(vec![]), } } diff --git a/src/event.rs b/src/event.rs index cf3c384..9e77d68 100644 --- a/src/event.rs +++ b/src/event.rs @@ -186,7 +186,7 @@ impl Key { } /// Represents an event as seen by the application. -#[derive(PartialEq,Eq,Clone,Copy,Hash,Debug)] +#[derive(PartialEq,Eq,Clone,Hash,Debug)] pub enum Event { /// Event fired when the window is resized. WindowResize, @@ -217,7 +217,7 @@ pub enum Event { CtrlAlt(Key), /// An unknown event was received. - Unknown(i32), + Unknown(Vec), #[doc(hidden)] /// The application is about to exit. diff --git a/src/lib.rs b/src/lib.rs index 2f33997..45fc31c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -594,7 +594,7 @@ impl Cursive { if self.menubar.receive_events() { self.menubar.on_event(event).process(self); } else { - match self.screen_mut().on_event(event) { + match self.screen_mut().on_event(event.clone()) { // If the event was ignored, // it is our turn to play with it. EventResult::Ignored => self.on_event(event), diff --git a/src/views/dialog.rs b/src/views/dialog.rs index 8f3a04c..0172f76 100644 --- a/src/views/dialog.rs +++ b/src/views/dialog.rs @@ -316,7 +316,7 @@ impl View for Dialog { match self.focus { // If we are on the content, we can only go down. Focus::Content => { - match self.content.on_event(event) { + match self.content.on_event(event.clone()) { EventResult::Ignored if !self.buttons.is_empty() => { match event { Event::Key(Key::Down) | @@ -334,7 +334,7 @@ impl View for Dialog { } // If we are on a button, we have more choice Focus::Button(i) => { - match self.buttons[i].on_event(event) { + match self.buttons[i].on_event(event.clone()) { EventResult::Ignored => { match event { // Up goes back to the content diff --git a/src/views/key_event_view.rs b/src/views/key_event_view.rs index cbbc1b7..8206860 100644 --- a/src/views/key_event_view.rs +++ b/src/views/key_event_view.rs @@ -46,7 +46,7 @@ impl ViewWrapper for KeyEventView { wrap_impl!(self.content: T); fn wrap_on_event(&mut self, event: Event) -> EventResult { - match self.content.on_event(event) { + match self.content.on_event(event.clone()) { EventResult::Ignored => { match self.callbacks.get(&event) { None => EventResult::Ignored, diff --git a/src/views/linear_layout.rs b/src/views/linear_layout.rs index 0ceb621..82e4286 100644 --- a/src/views/linear_layout.rs +++ b/src/views/linear_layout.rs @@ -353,7 +353,7 @@ impl View for LinearLayout { } fn on_event(&mut self, event: Event) -> EventResult { - match self.children[self.focus].view.on_event(event) { + match self.children[self.focus].view.on_event(event.clone()) { EventResult::Ignored => { match event { Event::Shift(Key::Tab) if self.focus > 0 => { diff --git a/src/views/list_view.rs b/src/views/list_view.rs index 453ec30..df56bb5 100644 --- a/src/views/list_view.rs +++ b/src/views/list_view.rs @@ -255,7 +255,7 @@ impl View for ListView { } if let Child::Row(_, ref mut view) = self.children[self.focus] { - let result = view.on_event(event); + let result = view.on_event(event.clone()); if result.is_consumed() { return result; } From 017bb217109c5b0dfc38dcac3e9d6ae456cf6fb5 Mon Sep 17 00:00:00 2001 From: Alexandre Bury Date: Sat, 25 Mar 2017 14:50:52 -0700 Subject: [PATCH 23/23] Add `View::focus_view` --- src/lib.rs | 14 ++++++++++++++ src/view/mod.rs | 9 ++++++++- src/view/view_wrapper.rs | 9 +++++++++ src/views/dialog.rs | 4 ++++ src/views/id_view.rs | 7 +++++++ src/views/linear_layout.rs | 11 +++++++++++ src/views/list_view.rs | 28 +++++++++++++++++++++++----- src/views/stack_view.rs | 10 ++++++++++ 8 files changed, 86 insertions(+), 6 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 45fc31c..a6a721c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -392,6 +392,8 @@ impl Cursive { self.screen_mut().find(sel) } + /// Tries to find the view identified by the given id. + /// /// Convenient method to use `find` with a `view::Selector::Id`. /// /// # Examples @@ -417,6 +419,18 @@ impl Cursive { self.find(&view::Selector::Id(id)) } + /// Moves the focus to the view identified by `id`. + /// + /// Convenient method to call `focus` with a `view::Selector::Id`. + pub fn focus_id(&mut self, id: &str) -> Result<(), ()> { + self.focus(&view::Selector::Id(id)) + } + + /// Moves the focus to the view identified by `sel`. + pub fn focus(&mut self, sel: &view::Selector) -> Result<(), ()> { + self.screen_mut().focus_view(sel) + } + /// Adds a global callback. /// /// Will be triggered on the given key press when no view catches it. diff --git a/src/view/mod.rs b/src/view/mod.rs index 5614c0d..29f499a 100644 --- a/src/view/mod.rs +++ b/src/view/mod.rs @@ -113,7 +113,7 @@ pub trait View { /// Draws the view with the given printer (includes bounds) and focus. fn draw(&self, printer: &Printer); - /// Finds the view pointed to by the given path. + /// Finds the view identified by the given selector. /// /// See [`Finder::find`] for a nicer interface, implemented for all views. /// @@ -126,6 +126,13 @@ pub trait View { None } + /// Moves the focus to the view identified by the given selector. + /// + /// Returns `Ok(())` if the view was found and selected. + fn focus_view(&mut self, &Selector) -> Result<(), ()> { + Err(()) + } + /// This view is offered focus. Will it take it? /// /// `source` indicates where the focus comes from. diff --git a/src/view/view_wrapper.rs b/src/view/view_wrapper.rs index b981bbe..879f887 100644 --- a/src/view/view_wrapper.rs +++ b/src/view/view_wrapper.rs @@ -55,6 +55,11 @@ pub trait ViewWrapper { self.get_view_mut().find_any(selector) } + /// Wraps the `focus_view` method. + fn wrap_focus_view(&mut self, selector: &Selector) -> Result<(), ()> { + self.get_view_mut().focus_view(selector) + } + /// Wraps the `needs_relayout` method. fn wrap_needs_relayout(&self) -> bool { self.get_view().needs_relayout() @@ -89,6 +94,10 @@ impl View for T { fn needs_relayout(&self) -> bool { self.wrap_needs_relayout() } + + fn focus_view(&mut self, selector: &Selector) -> Result<(), ()> { + self.wrap_focus_view(selector) + } } /// Convenient macro to implement the [`ViewWrapper`] trait. diff --git a/src/views/dialog.rs b/src/views/dialog.rs index 0172f76..9f79b00 100644 --- a/src/views/dialog.rs +++ b/src/views/dialog.rs @@ -402,4 +402,8 @@ impl View for Dialog { fn find_any(&mut self, selector: &Selector) -> Option<&mut Any> { self.content.find_any(selector) } + + fn focus_view(&mut self, selector: &Selector) -> Result<(), ()> { + self.content.focus_view(selector) + } } diff --git a/src/views/id_view.rs b/src/views/id_view.rs index 73ae6c2..e26baf3 100644 --- a/src/views/id_view.rs +++ b/src/views/id_view.rs @@ -27,4 +27,11 @@ impl ViewWrapper for IdView { s => self.view.find_any(s), } } + + fn wrap_focus_view(&mut self, selector: &Selector) -> Result<(), ()> { + match selector { + &Selector::Id(id) if id == self.id => Ok(()), + s => self.view.focus_view(s), + } + } } diff --git a/src/views/linear_layout.rs b/src/views/linear_layout.rs index 82e4286..8519c0c 100644 --- a/src/views/linear_layout.rs +++ b/src/views/linear_layout.rs @@ -401,4 +401,15 @@ impl View for LinearLayout { .filter_map(|c| c.view.find_any(selector)) .next() } + + fn focus_view(&mut self, selector: &Selector) -> Result<(), ()> { + for (i, child) in self.children.iter_mut().enumerate() { + if child.view.focus_view(selector).is_ok() { + self.focus = i; + return Ok(()); + } + } + + Err(()) + } } diff --git a/src/views/list_view.rs b/src/views/list_view.rs index df56bb5..ad94fb8 100644 --- a/src/views/list_view.rs +++ b/src/views/list_view.rs @@ -118,7 +118,10 @@ impl ListView { direction::Relative::Front => { let start = if from_focus { self.focus } else { 0 }; - Box::new(self.children.iter_mut().enumerate().skip(start)) + Box::new(self.children + .iter_mut() + .enumerate() + .skip(start)) } direction::Relative::Back => { let end = if from_focus { @@ -296,10 +299,11 @@ impl View for ListView { fn take_focus(&mut self, source: direction::Direction) -> bool { let rel = source.relative(direction::Orientation::Vertical); - let i = if let Some(i) = self.iter_mut(rel.is_none(), - rel.unwrap_or(direction::Relative::Front)) - .filter_map(|p| try_focus(p, source)) - .next() { + let i = if let Some(i) = + self.iter_mut(rel.is_none(), + rel.unwrap_or(direction::Relative::Front)) + .filter_map(|p| try_focus(p, source)) + .next() { i } else { // No one wants to be in focus @@ -317,4 +321,18 @@ impl View for ListView { .filter_map(|v| v.find_any(selector)) .next() } + + fn focus_view(&mut self, selector: &Selector) -> Result<(), ()> { + if let Some(i) = self.children + .iter_mut() + .enumerate() + .filter_map(|(i, v)| v.view().map(|v| (i, v))) + .filter_map(|(i, v)| v.focus_view(selector).ok().map(|_| i)) + .next() { + self.focus = i; + Ok(()) + } else { + Err(()) + } + } } diff --git a/src/views/stack_view.rs b/src/views/stack_view.rs index e48c01a..b79f121 100644 --- a/src/views/stack_view.rs +++ b/src/views/stack_view.rs @@ -186,4 +186,14 @@ impl View for StackView { .filter_map(|l| l.view.find_any(selector)) .next() } + + fn focus_view(&mut self, selector: &Selector) -> Result<(), ()> { + for layer in &mut self.layers { + if layer.view.focus_view(selector).is_ok() { + return Ok(()); + } + } + + Err(()) + } }