Update for new ncurses version

This commit is contained in:
Alexandre Bury 2016-03-15 15:37:57 -07:00
parent 0e37a7f2e4
commit 07c3c99e54
29 changed files with 408 additions and 322 deletions

View File

@ -10,7 +10,7 @@ version = "0.1.0"
[dependencies] [dependencies]
ncurses = "5.80.0" ncurses = "5.80.0"
toml = "0.1.25" toml = "0.1"
[[example]] [[example]]
name = "hello_world" name = "hello_world"

View File

@ -2,6 +2,7 @@ extern crate cursive;
use std::sync::mpsc; use std::sync::mpsc;
use std::thread; use std::thread;
use std::time::Duration;
use cursive::Cursive; use cursive::Cursive;
use cursive::printer::Printer; use cursive::printer::Printer;
@ -42,7 +43,7 @@ fn generate_logs(tx: mpsc::Sender<String>) {
Err(_) => return, Err(_) => return,
Ok(_) => (), Ok(_) => (),
} }
thread::sleep_ms(30); thread::sleep(Duration::from_millis(30));
} }
} }

View File

@ -9,10 +9,7 @@ pub struct Align {
impl Align { impl Align {
/// Creates a new Align object from the given horizontal and vertical alignments. /// Creates a new Align object from the given horizontal and vertical alignments.
pub fn new(h: HAlign, v: VAlign) -> Self { pub fn new(h: HAlign, v: VAlign) -> Self {
Align { Align { h: h, v: v }
h: h,
v: v,
}
} }
/// Creates a top-left alignment. /// Creates a top-left alignment.

View File

@ -5,6 +5,9 @@ pub fn div_up_usize(p: usize, q: usize) -> usize {
/// Integer division that rounds up. /// Integer division that rounds up.
pub fn div_up(p: u32, q: u32) -> u32 { pub fn div_up(p: u32, q: u32) -> u32 {
if p % q == 0 { p/q } if p % q == 0 {
else { 1 + p/q } p / q
} else {
1 + p / q
}
} }

View File

@ -5,7 +5,7 @@ use std::rc::Rc;
use ncurses; use ncurses;
use ::Cursive; use Cursive;
/// Callback is a function that can be triggered by an event. /// Callback is a function that can be triggered by an event.
/// It has a mutable access to the cursive root. /// It has a mutable access to the cursive root.
@ -136,7 +136,7 @@ impl Key {
f @ 293 ... 303 => Key::CtrlF((f - 293 + 5) as u8), f @ 293 ... 303 => Key::CtrlF((f - 293 + 5) as u8),
f @ 305 ... 315 => Key::CtrlShiftF((f - 305 + 5) as u8), f @ 305 ... 315 => Key::CtrlShiftF((f - 305 + 5) as u8),
// Shift and Ctrl F{1-4} need escape sequences... // Shift and Ctrl F{1-4} need escape sequences...
//
// TODO: shift and ctrl Fn keys // TODO: shift and ctrl Fn keys
// Avoids 8-10 (H,I,J), they are used by other commands. // Avoids 8-10 (H,I,J), they are used by other commands.
c @ 1 ... 7 | c @ 11 ... 25 => Key::CtrlChar(('a' as u8 + (c - 1) as u8) as char), c @ 1 ... 7 | c @ 11 ... 25 => Key::CtrlChar(('a' as u8 + (c - 1) as u8) as char),
@ -154,7 +154,9 @@ impl fmt::Display for Key {
Key::ShiftF(n) => write!(f, "Shift-F{}", n), Key::ShiftF(n) => write!(f, "Shift-F{}", n),
Key::CtrlF(n) => write!(f, "Ctrl-F{}", n), Key::CtrlF(n) => write!(f, "Ctrl-F{}", n),
Key::CtrlShiftF(n) => write!(f, "Ctrl-Shift-F{}", n), Key::CtrlShiftF(n) => write!(f, "Ctrl-Shift-F{}", n),
key => write!(f, "{}", match key { key => write!(f,
"{}",
match key {
Key::NumpadCenter => "Numpad center", Key::NumpadCenter => "Numpad center",
Key::Left => "Left", Key::Left => "Left",
Key::Right => "Right", Key::Right => "Right",

View File

@ -83,7 +83,8 @@ impl Cursive {
let theme = theme::load_default(); let theme = theme::load_default();
// let theme = theme::load_theme("assets/style.toml").unwrap(); // let theme = theme::load_theme("assets/style.toml").unwrap();
ncurses::wbkgd(ncurses::stdscr, ncurses::COLOR_PAIR(theme::ColorPair::Background.ncurses_id())); ncurses::wbkgd(ncurses::stdscr,
ncurses::COLOR_PAIR(theme::ColorPair::Background.ncurses_id()));
let mut res = Cursive { let mut res = Cursive {
screens: Vec::new(), screens: Vec::new(),
@ -148,7 +149,9 @@ impl Cursive {
/// Sets the active screen. Panics if no such screen exist. /// Sets the active screen. Panics if no such screen exist.
pub fn set_screen(&mut self, screen_id: ScreenId) { pub fn set_screen(&mut self, screen_id: ScreenId) {
if screen_id >= self.screens.len() { if screen_id >= self.screens.len() {
panic!("Tried to set an invalid screen ID: {}, but only {} screens present.", screen_id, self.screens.len()); panic!("Tried to set an invalid screen ID: {}, but only {} screens present.",
screen_id,
self.screens.len());
} }
self.active_screen = screen_id; self.active_screen = screen_id;
} }
@ -275,4 +278,3 @@ impl Drop for Cursive {
ncurses::endwin(); ncurses::endwin();
} }
} }

View File

@ -3,6 +3,7 @@
use std::cmp::min; use std::cmp::min;
use ncurses; use ncurses;
use ncurses::chtype;
use theme::{ColorPair, Theme}; use theme::{ColorPair, Theme};
use vec::{Vec2, ToVec2}; use vec::{Vec2, ToVec2};
@ -33,7 +34,9 @@ impl Printer {
/// Prints some text at the given position relative to the window. /// Prints some text at the given position relative to the window.
pub fn print<S: ToVec2>(&self, pos: S, text: &str) { pub fn print<S: ToVec2>(&self, pos: S, text: &str) {
let p = pos.to_vec2(); let p = pos.to_vec2();
if p.y >= self.size.y || p.x >= self.size.x { return; } if p.y >= self.size.y || p.x >= self.size.x {
return;
}
// Do we have enough room for the entire line? // Do we have enough room for the entire line?
let room = self.size.x - p.x; let room = self.size.x - p.x;
// We want the number of CHARACTERS, not bytes. // We want the number of CHARACTERS, not bytes.
@ -51,9 +54,11 @@ impl Printer {
} }
/// Prints a vertical line using the given character. /// Prints a vertical line using the given character.
pub fn print_vline<T: ToVec2>(&self, start: T, len: usize, c: u64) { pub fn print_vline<T: ToVec2>(&self, start: T, len: usize, c: chtype) {
let p = start.to_vec2(); let p = start.to_vec2();
if p.y > self.size.y || p.x > self.size.x { return; } if p.y > self.size.y || p.x > self.size.x {
return;
}
let len = min(len, self.size.y - p.y); let len = min(len, self.size.y - p.y);
let p = p + self.offset; let p = p + self.offset;
@ -61,9 +66,11 @@ impl Printer {
} }
/// Prints a horizontal line using the given character. /// Prints a horizontal line using the given character.
pub fn print_hline<T: ToVec2>(&self, start: T, len: usize, c: u64) { pub fn print_hline<T: ToVec2>(&self, start: T, len: usize, c: chtype) {
let p = start.to_vec2(); let p = start.to_vec2();
if p.y > self.size.y || p.x > self.size.x { return; } if p.y > self.size.y || p.x > self.size.x {
return;
}
let len = min(len, self.size.x - p.x); let len = min(len, self.size.x - p.x);
let p = p + self.offset; let p = p + self.offset;
@ -123,8 +130,12 @@ impl Printer {
self.print_hline(start_v + (1, 0), size_v.x - 1, ncurses::ACS_HLINE()); self.print_hline(start_v + (1, 0), size_v.x - 1, ncurses::ACS_HLINE());
self.print_vline(start_v + (0, 1), size_v.y - 1, ncurses::ACS_VLINE()); self.print_vline(start_v + (0, 1), size_v.y - 1, ncurses::ACS_VLINE());
self.print_hline(start_v + (1,0) + size_v.keep_y(), size_v.x - 1, ncurses::ACS_HLINE()); self.print_hline(start_v + (1, 0) + size_v.keep_y(),
self.print_vline(start_v + (0,1) + size_v.keep_x(), size_v.y - 1, ncurses::ACS_VLINE()); size_v.x - 1,
ncurses::ACS_HLINE());
self.print_vline(start_v + (0, 1) + size_v.keep_x(),
size_v.y - 1,
ncurses::ACS_VLINE());
} }
/// Returns a printer on a subset of this one's area. /// Returns a printer on a subset of this one's area.

View File

@ -100,15 +100,29 @@ impl Theme {
} }
fn apply(&self) { fn apply(&self) {
Theme::apply_color(ColorPair::Background, &self.colors.background, &self.colors.background); Theme::apply_color(ColorPair::Background,
&self.colors.background,
&self.colors.background);
Theme::apply_color(ColorPair::Shadow, &self.colors.shadow, &self.colors.shadow); Theme::apply_color(ColorPair::Shadow, &self.colors.shadow, &self.colors.shadow);
Theme::apply_color(ColorPair::Primary, &self.colors.primary, &self.colors.view); Theme::apply_color(ColorPair::Primary, &self.colors.primary, &self.colors.view);
Theme::apply_color(ColorPair::Secondary, &self.colors.secondary, &self.colors.view); Theme::apply_color(ColorPair::Secondary,
Theme::apply_color(ColorPair::Tertiary, &self.colors.tertiary, &self.colors.view); &self.colors.secondary,
Theme::apply_color(ColorPair::TitlePrimary, &self.colors.title_primary, &self.colors.view); &self.colors.view);
Theme::apply_color(ColorPair::TitleSecondary, &self.colors.title_secondary, &self.colors.view); Theme::apply_color(ColorPair::Tertiary,
Theme::apply_color(ColorPair::Highlight, &self.colors.view, &self.colors.highlight); &self.colors.tertiary,
Theme::apply_color(ColorPair::HighlightInactive, &self.colors.view, &self.colors.highlight_inactive); &self.colors.view);
Theme::apply_color(ColorPair::TitlePrimary,
&self.colors.title_primary,
&self.colors.view);
Theme::apply_color(ColorPair::TitleSecondary,
&self.colors.title_secondary,
&self.colors.view);
Theme::apply_color(ColorPair::Highlight,
&self.colors.view,
&self.colors.highlight);
Theme::apply_color(ColorPair::HighlightInactive,
&self.colors.view,
&self.colors.highlight_inactive);
} }
fn apply_color(pair: ColorPair, front: &Color, back: &Color) { fn apply_color(pair: ColorPair, front: &Color, back: &Color) {
@ -244,10 +258,14 @@ impl Color {
fn load(&mut self, table: &toml::Table, key: &str, new_id: &mut i16) { fn load(&mut self, table: &toml::Table, key: &str, new_id: &mut i16) {
match table.get(key) { match table.get(key) {
Some(&toml::Value::String(ref value)) => { self.load_value(value, new_id); }, Some(&toml::Value::String(ref value)) => {
self.load_value(value, new_id);
}
Some(&toml::Value::Array(ref array)) => for color in array.iter() { Some(&toml::Value::Array(ref array)) => for color in array.iter() {
match color { match color {
&toml::Value::String(ref color) => if self.load_value(color, new_id) { return; }, &toml::Value::String(ref color) => if self.load_value(color, new_id) {
return;
},
_ => (), _ => (),
} }
}, },

View File

@ -7,7 +7,7 @@ pub fn read_char<F>(first: u8, next: F) -> Result<char,String>
where F: Fn() -> u8 where F: Fn() -> u8
{ {
if first < 0x80 { if first < 0x80 {
return Ok(first as char) return Ok(first as char);
} }
// Number of leading 1s determines the number of bytes we'll have to read // Number of leading 1s determines the number of bytes we'll have to read

View File

@ -14,20 +14,22 @@ pub struct Vec2 {
impl PartialOrd for Vec2 { impl PartialOrd for Vec2 {
fn partial_cmp(&self, other: &Vec2) -> Option<Ordering> { fn partial_cmp(&self, other: &Vec2) -> Option<Ordering> {
if self == other { Some(Ordering::Equal) } if self == other {
else if self.x < other.x && self.y < other.y { Some(Ordering::Less) } Some(Ordering::Equal)
else if self.x > other.x && self.y > other.y { Some(Ordering::Greater) } } else if self.x < other.x && self.y < other.y {
else { None } Some(Ordering::Less)
} else if self.x > other.x && self.y > other.y {
Some(Ordering::Greater)
} else {
None
}
} }
} }
impl Vec2 { impl Vec2 {
/// Creates a new Vec2 from coordinates. /// Creates a new Vec2 from coordinates.
pub fn new(x: usize, y: usize) -> Self { pub fn new(x: usize, y: usize) -> Self {
Vec2 { Vec2 { x: x, y: y }
x: x,
y: y,
}
} }
/// Returns a new Vec2 that is a maximum per coordinate. /// Returns a new Vec2 that is a maximum per coordinate.
@ -199,7 +201,9 @@ pub trait ToVec4 {
} }
impl ToVec4 for Vec4 { impl ToVec4 for Vec4 {
fn to_vec4(self) -> Vec4 { self } fn to_vec4(self) -> Vec4 {
self
}
} }
impl ToVec4 for (usize,usize,usize,usize) { impl ToVec4 for (usize,usize,usize,usize) {
@ -210,7 +214,10 @@ impl ToVec4 for (usize,usize,usize,usize) {
impl ToVec4 for (i32,i32,i32,i32) { impl ToVec4 for (i32,i32,i32,i32) {
fn to_vec4(self) -> Vec4 { fn to_vec4(self) -> Vec4 {
Vec4::new(self.0 as usize, self.1 as usize, self.2 as usize, self.3 as usize) Vec4::new(self.0 as usize,
self.1 as usize,
self.2 as usize,
self.3 as usize)
} }
} }
@ -270,4 +277,3 @@ impl Mul<usize> for Vec4 {
} }
} }
} }

View File

@ -30,13 +30,21 @@ impl <T: View> ViewWrapper for BoxView<T> {
wrap_impl!(&self.view); wrap_impl!(&self.view);
fn wrap_get_min_size(&self, mut req: SizeRequest) -> Vec2 { fn wrap_get_min_size(&self, mut req: SizeRequest) -> Vec2 {
if self.size.x > 0 { req.w = DimensionRequest::AtMost(self.size.x); } if self.size.x > 0 {
if self.size.y > 0 { req.h = DimensionRequest::AtMost(self.size.y); } req.w = DimensionRequest::AtMost(self.size.x);
}
if self.size.y > 0 {
req.h = DimensionRequest::AtMost(self.size.y);
}
let mut size = self.view.get_min_size(req); let mut size = self.view.get_min_size(req);
if self.size.x > 0 { size.x = self.size.x; } if self.size.x > 0 {
if self.size.y > 0 { size.y = self.size.y; } size.x = self.size.x;
}
if self.size.y > 0 {
size.y = self.size.y;
}
size size
} }

View File

@ -1,7 +1,7 @@
use std::rc::Rc; use std::rc::Rc;
use theme::ColorPair; use theme::ColorPair;
use ::Cursive; use Cursive;
use vec::Vec2; use vec::Vec2;
use view::{View, SizeRequest}; use view::{View, SizeRequest};
use event::*; use event::*;
@ -29,7 +29,11 @@ impl Button {
impl View for Button { impl View for Button {
fn draw(&mut self, printer: &Printer) { fn draw(&mut self, printer: &Printer) {
let style = if !printer.focused { ColorPair::Primary } else { ColorPair::Highlight }; let style = if !printer.focused {
ColorPair::Primary
} else {
ColorPair::Highlight
};
let x = printer.size.x - 1; let x = printer.size.x - 1;
printer.with_color(style, |printer| { printer.with_color(style, |printer| {

View File

@ -1,7 +1,7 @@
use std::cmp::max; use std::cmp::max;
use std::any::Any; use std::any::Any;
use ::{Cursive}; use Cursive;
use align::*; use align::*;
use event::*; use event::*;
use theme::ColorPair; use theme::ColorPair;
@ -111,21 +111,22 @@ impl View for Dialog {
let width = if self.buttons.is_empty() { let width = if self.buttons.is_empty() {
0 0
} else { } else {
self.buttons.iter() self.buttons
.iter()
.map(|button| button.size.x) .map(|button| button.size.x)
.fold(0, |a,b| a+b) + .fold(0, |a, b| a + b) + self.buttons.len() - 1
self.buttons.len() - 1
}; };
let overhead = self.padding + self.borders; let overhead = self.padding + self.borders;
let mut offset = overhead.left + let mut offset = overhead.left +
self.align.h.get_offset(width, printer.size.x - self.align.h.get_offset(width, printer.size.x - overhead.horizontal());
overhead.horizontal());
let y = printer.size.y - self.padding.bottom - self.borders.bottom - 1; let y = printer.size.y - self.padding.bottom - self.borders.bottom - 1;
for (i, button) in self.buttons.iter_mut().enumerate() { for (i, button) in self.buttons.iter_mut().enumerate() {
let size = button.size; let size = button.size;
// Add some special effect to the focused button // Add some special effect to the focused button
button.draw(&printer.sub_printer(Vec2::new(offset, y), size, self.focus == Focus::Button(i))); button.draw(&printer.sub_printer(Vec2::new(offset, y),
size,
self.focus == Focus::Button(i)));
// Keep 1 blank between two buttons // Keep 1 blank between two buttons
offset += size.x + 1; offset += size.x + 1;
// Also keep 1 blank above the buttons // Also keep 1 blank above the buttons
@ -133,13 +134,10 @@ impl View for Dialog {
} }
// What do we have left? // What do we have left?
let inner_size = printer.size let inner_size = printer.size - Vec2::new(0, height) - self.borders.combined() -
- Vec2::new(0, height) self.padding.combined();
- self.borders.combined()
- self.padding.combined();
self.content.draw(&printer.sub_printer(self.borders.top_left() + self.content.draw(&printer.sub_printer(self.borders.top_left() + self.padding.top_left(),
self.padding.top_left(),
inner_size, inner_size,
self.focus == Focus::Content)); self.focus == Focus::Content));
@ -151,16 +149,14 @@ impl View for Dialog {
printer.print((x - 2, 0), ""); printer.print((x - 2, 0), "");
printer.print((x + len, 0), ""); printer.print((x + len, 0), "");
printer.with_color(ColorPair::TitlePrimary, printer.with_color(ColorPair::TitlePrimary, |p| p.print((x, 0), &self.title));
|p| p.print((x,0), &self.title));
} }
} }
fn get_min_size(&self, req: SizeRequest) -> Vec2 { fn get_min_size(&self, req: SizeRequest) -> Vec2 {
// Padding and borders are not available for kids. // Padding and borders are not available for kids.
let content_req = req.reduced(self.padding.combined() + let content_req = req.reduced(self.padding.combined() + self.borders.combined());
self.borders.combined());
let content_size = self.content.get_min_size(content_req); let content_size = self.content.get_min_size(content_req);
let mut buttons_size = Vec2::new(0, 0); let mut buttons_size = Vec2::new(0, 0);
@ -176,8 +172,9 @@ impl View for Dialog {
// On the Y axis, we add buttons and content. // On the Y axis, we add buttons and content.
// On the X axis, we take the max. // On the X axis, we take the max.
let mut inner_size = Vec2::new(max(content_size.x, buttons_size.x), let mut inner_size = Vec2::new(max(content_size.x, buttons_size.x),
content_size.y + buttons_size.y) content_size.y + buttons_size.y) +
+ self.padding.combined() + self.borders.combined(); self.padding.combined() +
self.borders.combined();
if self.title.len() > 0 { if self.title.len() > 0 {
// If we have a title, we have to fit it too! // If we have a title, we have to fit it too!
@ -216,7 +213,7 @@ impl View for Dialog {
// Default to leftmost button when going down. // Default to leftmost button when going down.
self.focus = Focus::Button(0); self.focus = Focus::Button(0);
EventResult::Consumed(None) EventResult::Consumed(None)
}, }
Event::KeyEvent(Key::Tab) | Event::KeyEvent(Key::ShiftTab) => { Event::KeyEvent(Key::Tab) | Event::KeyEvent(Key::ShiftTab) => {
self.focus = Focus::Button(0); self.focus = Focus::Button(0);
EventResult::Consumed(None) EventResult::Consumed(None)
@ -236,7 +233,7 @@ impl View for Dialog {
} else { } else {
EventResult::Ignored EventResult::Ignored
} }
}, }
Event::KeyEvent(Key::Tab) | Event::KeyEvent(Key::ShiftTab) => { Event::KeyEvent(Key::Tab) | Event::KeyEvent(Key::ShiftTab) => {
if self.content.take_focus() { if self.content.take_focus() {
self.focus = Focus::Content; self.focus = Focus::Content;
@ -244,16 +241,16 @@ impl View for Dialog {
} else { } else {
EventResult::Ignored EventResult::Ignored
} }
}, }
// Left and Right move to other buttons // Left and Right move to other buttons
Event::KeyEvent(Key::Right) if i + 1 < self.buttons.len() => { Event::KeyEvent(Key::Right) if i + 1 < self.buttons.len() => {
self.focus = Focus::Button(i + 1); self.focus = Focus::Button(i + 1);
EventResult::Consumed(None) EventResult::Consumed(None)
}, }
Event::KeyEvent(Key::Left) if i > 0 => { Event::KeyEvent(Key::Left) if i > 0 => {
self.focus = Focus::Button(i - 1); self.focus = Focus::Button(i - 1);
EventResult::Consumed(None) EventResult::Consumed(None)
}, }
_ => EventResult::Ignored, _ => EventResult::Ignored,
}, },
res => res, res => res,

View File

@ -1,4 +1,4 @@
use ncurses; use ncurses::{self, chtype};
use std::cmp::min; use std::cmp::min;
@ -20,9 +20,8 @@ pub struct EditView {
/// When the content is too long for the display, offset it /// When the content is too long for the display, offset it
offset: usize, offset: usize,
/// Last display length, to know the possible offset range /// Last display length, to know the possible offset range
last_length: usize, last_length: usize, /* scrollable: bool,
// scrollable: bool, * TODO: add a max text length? */
// TODO: add a max text length?
} }
impl EditView { impl EditView {
@ -33,8 +32,7 @@ impl EditView {
cursor: 0, cursor: 0,
offset: 0, offset: 0,
min_length: 1, min_length: 1,
last_length: 0, last_length: 0, /* scrollable: false, */
// scrollable: false,
} }
} }
@ -85,7 +83,7 @@ impl View for EditView {
printer.with_style(ncurses::A_REVERSE(), |printer| { printer.with_style(ncurses::A_REVERSE(), |printer| {
if len < self.last_length { if len < self.last_length {
printer.print((0, 0), &self.content); printer.print((0, 0), &self.content);
printer.print_hline((len,0), printer.size.x-len, '_' as u64); printer.print_hline((len, 0), printer.size.x - len, '_' as chtype);
} else { } else {
let visible_end = min(self.content.len(), self.offset + self.last_length); let visible_end = min(self.content.len(), self.offset + self.last_length);
@ -103,9 +101,14 @@ impl View for EditView {
'_' '_'
} else { } else {
// Get the char from the string... Is it so hard? // Get the char from the string... Is it so hard?
self.content.chars().nth(self.cursor).expect(&format!("Found no char at cursor {} in {}", self.cursor, self.content)) self.content
.chars()
.nth(self.cursor)
.expect(&format!("Found no char at cursor {} in {}",
self.cursor,
self.content))
}; };
printer.print_hline((self.cursor-self.offset, 0), 1, c as u64); printer.print_hline((self.cursor - self.offset, 0), 1, c as chtype);
} }
}); });
} }
@ -134,14 +137,19 @@ impl View for EditView {
} }
// TODO: handle wide (CJK) chars // TODO: handle wide (CJK) chars
self.cursor += 1; self.cursor += 1;
}, }
Event::KeyEvent(key) => match key { Event::KeyEvent(key) => match key {
Key::Home => self.cursor = 0, Key::Home => self.cursor = 0,
Key::End => self.cursor = self.content.chars().count(), Key::End => self.cursor = self.content.chars().count(),
Key::Left if self.cursor > 0 => self.cursor -= 1, Key::Left if self.cursor > 0 => self.cursor -= 1,
Key::Right if self.cursor < self.content.chars().count() => self.cursor += 1, Key::Right if self.cursor < self.content.chars().count() => self.cursor += 1,
Key::Backspace if self.cursor > 0 => { self.cursor -= 1; remove_char(&mut self.content, self.cursor); }, Key::Backspace if self.cursor > 0 => {
Key::Del if self.cursor < self.content.chars().count() => { remove_char(&mut self.content, self.cursor); }, self.cursor -= 1;
remove_char(&mut self.content, self.cursor);
}
Key::Del if self.cursor < self.content.chars().count() => {
remove_char(&mut self.content, self.cursor);
}
_ => return EventResult::Ignored, _ => return EventResult::Ignored,
}, },
} }

View File

@ -9,9 +9,7 @@ pub struct FullView<T: View> {
impl <T: View> FullView<T> { impl <T: View> FullView<T> {
/// Wraps the given view into a new FullView. /// Wraps the given view into a new FullView.
pub fn new(view: T) -> Self { pub fn new(view: T) -> Self {
FullView { FullView { view: view }
view: view,
}
} }
} }

View File

@ -1,7 +1,7 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::rc::Rc; use std::rc::Rc;
use ::Cursive; use Cursive;
use event::{Event, EventResult, ToEvent, Callback}; use event::{Event, EventResult, ToEvent, Callback};
use super::{View, ViewWrapper}; use super::{View, ViewWrapper};

View File

@ -78,7 +78,9 @@ fn find_max(list: &Vec<usize>) -> usize {
fn share(total: usize, weights: Vec<usize>) -> Vec<usize> { fn share(total: usize, weights: Vec<usize>) -> Vec<usize> {
// It first give a base value to everyone, which is their truncated share. // It first give a base value to everyone, which is their truncated share.
// Then, it gives the rest to the most deserving. // Then, it gives the rest to the most deserving.
if weights.len() == 0 { return Vec::new(); } if weights.len() == 0 {
return Vec::new();
}
let sum_weight = weights.iter().fold(0, |a, b| a + b); let sum_weight = weights.iter().fold(0, |a, b| a + b);
if sum_weight == 0 { if sum_weight == 0 {
@ -123,14 +125,17 @@ impl View for LinearLayout {
w: DimensionRequest::AtMost(size.x), w: DimensionRequest::AtMost(size.x),
h: DimensionRequest::AtMost(size.y), h: DimensionRequest::AtMost(size.y),
}; };
let min_sizes: Vec<Vec2> = self.children.iter().map(|child| child.view.get_min_size(req)).collect(); let min_sizes: Vec<Vec2> = self.children
.iter()
.map(|child| child.view.get_min_size(req))
.collect();
let min_size = self.orientation.stack(min_sizes.iter()); let min_size = self.orientation.stack(min_sizes.iter());
// Emulate 'non-strict inequality' on integers // Emulate 'non-strict inequality' on integers
// (default comparison on Vec2 is strict) // (default comparison on Vec2 is strict)
if !(min_size < size + (1, 1)) { if !(min_size < size + (1, 1)) {
// Error! Not enough space! Emergency procedures! // Error! Not enough space! Emergency procedures!
return return;
} }
// Now share this extra space among everyone // Now share this extra space among everyone
@ -138,11 +143,14 @@ impl View for LinearLayout {
let extras = { let extras = {
let extra = size - min_size; let extra = size - min_size;
let space = self.orientation.get(&extra); let space = self.orientation.get(&extra);
share(space, self.children.iter().map(|child| child.weight).collect()) share(space,
self.children.iter().map(|child| child.weight).collect())
}; };
for (child,(child_size,extra)) in self.children.iter_mut().zip(min_sizes.iter().zip(extras.iter())) { for (child, (child_size, extra)) in self.children
.iter_mut()
.zip(min_sizes.iter().zip(extras.iter())) {
let mut child_size = *child_size; let mut child_size = *child_size;
*self.orientation.get_ref(&mut child_size) += *extra; *self.orientation.get_ref(&mut child_size) += *extra;
*self.orientation.swap().get_ref(&mut child_size) = self.orientation.swap().get(&size); *self.orientation.swap().get_ref(&mut child_size) = self.orientation.swap().get(&size);
@ -153,7 +161,10 @@ impl View for LinearLayout {
fn get_min_size(&self, req: SizeRequest) -> Vec2 { fn get_min_size(&self, req: SizeRequest) -> Vec2 {
// First, make a naive scenario: everything will work fine. // First, make a naive scenario: everything will work fine.
let sizes: Vec<Vec2> = self.children.iter().map(|view| view.view.get_min_size(req)).collect(); let sizes: Vec<Vec2> = self.children
.iter()
.map(|view| view.view.get_min_size(req))
.collect();
self.orientation.stack(sizes.iter()) self.orientation.stack(sizes.iter())
@ -173,27 +184,31 @@ impl View for LinearLayout {
Event::KeyEvent(Key::Tab) if self.focus > 0 => { Event::KeyEvent(Key::Tab) if self.focus > 0 => {
self.focus -= 1; self.focus -= 1;
EventResult::Consumed(None) EventResult::Consumed(None)
}, }
Event::KeyEvent(Key::ShiftTab) if self.focus + 1 < self.children.len() => { Event::KeyEvent(Key::ShiftTab) if self.focus + 1 < self.children.len() => {
self.focus += 1; self.focus += 1;
EventResult::Consumed(None) EventResult::Consumed(None)
} }
Event::KeyEvent(Key::Left) if self.orientation == Orientation::Horizontal && self.focus > 0 => { Event::KeyEvent(Key::Left) if self.orientation == Orientation::Horizontal &&
self.focus > 0 => {
self.focus -= 1; self.focus -= 1;
EventResult::Consumed(None) EventResult::Consumed(None)
}, }
Event::KeyEvent(Key::Up) if self.orientation == Orientation::Vertical && self.focus > 0 => { Event::KeyEvent(Key::Up) if self.orientation == Orientation::Vertical &&
self.focus > 0 => {
self.focus -= 1; self.focus -= 1;
EventResult::Consumed(None) EventResult::Consumed(None)
}, }
Event::KeyEvent(Key::Right) if self.orientation == Orientation::Horizontal && self.focus+1 < self.children.len() => { Event::KeyEvent(Key::Right) if self.orientation == Orientation::Horizontal &&
self.focus + 1 < self.children.len() => {
self.focus += 1; self.focus += 1;
EventResult::Consumed(None) EventResult::Consumed(None)
}, }
Event::KeyEvent(Key::Down) if self.orientation == Orientation::Vertical && self.focus+1 < self.children.len() => { Event::KeyEvent(Key::Down) if self.orientation == Orientation::Vertical &&
self.focus + 1 < self.children.len() => {
self.focus += 1; self.focus += 1;
EventResult::Consumed(None) EventResult::Consumed(None)
}, }
_ => EventResult::Ignored, _ => EventResult::Ignored,
}, },
res => res, res => res,

View File

@ -49,24 +49,33 @@ pub use self::view_wrapper::ViewWrapper;
/// Main trait defining a view behaviour. /// Main trait defining a view behaviour.
pub trait View { pub trait View {
/// Called when a key was pressed. Default implementation just ignores it. /// Called when a key was pressed. Default implementation just ignores it.
fn on_event(&mut self, Event) -> EventResult { EventResult::Ignored } fn on_event(&mut self, Event) -> EventResult {
EventResult::Ignored
}
/// Returns the minimum size the view requires under the given restrictions. /// Returns the minimum size the view requires under the given restrictions.
fn get_min_size(&self, SizeRequest) -> Vec2 { Vec2::new(1,1) } fn get_min_size(&self, SizeRequest) -> Vec2 {
Vec2::new(1, 1)
}
/// Called once the size for this view has been decided, so it can /// Called once the size for this view has been decided, so it can
/// propagate the information to its children. /// propagate the information to its children.
fn layout(&mut self, Vec2) { } fn layout(&mut self, Vec2) {
}
/// Draws the view with the given printer (includes bounds) and focus. /// Draws the view with the given printer (includes bounds) and focus.
fn draw(&mut self, printer: &Printer); fn draw(&mut self, printer: &Printer);
/// Finds the view pointed to by the given path. /// Finds the view pointed to by the given path.
/// Returns None if the path doesn't lead to a view. /// Returns None if the path doesn't lead to a view.
fn find(&mut self, &Selector) -> Option<&mut Any> { None } fn find(&mut self, &Selector) -> Option<&mut Any> {
None
}
/// This view is offered focus. Will it take it? /// This view is offered focus. Will it take it?
fn take_focus(&mut self) -> bool { false } fn take_focus(&mut self) -> bool {
false
}
} }
/// Selects a single view (if any) in the tree. /// Selects a single view (if any) in the tree.
@ -76,4 +85,3 @@ pub enum Selector<'a> {
/// Selects a view from its path /// Selects a view from its path
Path(&'a ViewPath), Path(&'a ViewPath),
} }

View File

@ -49,5 +49,3 @@ impl SizeRequest {
} }
} }
} }

View File

@ -1,4 +1,5 @@
use std::cmp::{min, max}; use std::cmp::{min, max};
use ncurses::chtype;
use theme::ColorPair; use theme::ColorPair;
use vec::Vec2; use vec::Vec2;
@ -102,11 +103,16 @@ impl ScrollBase {
{ {
// Print the content in a sub_printer // Print the content in a sub_printer
let max_y = min(self.view_height, self.content_height - self.start_line); let max_y = min(self.view_height, self.content_height - self.start_line);
let w = if self.scrollable() { printer.size.x - 2 } else { printer.size.x }; let w = if self.scrollable() {
printer.size.x - 2
} else {
printer.size.x
};
for y in 0..max_y { for y in 0..max_y {
// Y is the actual coordinate of the line. // Y is the actual coordinate of the line.
// The item ID is then Y + self.start_line // The item ID is then Y + self.start_line
line_drawer(&printer.sub_printer(Vec2::new(0,y),Vec2::new(w,1),true), y+self.start_line); line_drawer(&printer.sub_printer(Vec2::new(0, y), Vec2::new(w, 1), true),
y + self.start_line);
} }
@ -122,11 +128,15 @@ impl ScrollBase {
// Now // Now
let start = steps * self.start_line / (1 + self.content_height - self.view_height); let start = steps * self.start_line / (1 + self.content_height - self.view_height);
let color = if printer.focused { ColorPair::Highlight } else { ColorPair::HighlightInactive }; let color = if printer.focused {
ColorPair::Highlight
} else {
ColorPair::HighlightInactive
};
printer.print_vline((printer.size.x-1,0), printer.size.y, '|' as u64); printer.print_vline((printer.size.x - 1, 0), printer.size.y, '|' as chtype);
printer.with_color(color, |printer| { printer.with_color(color, |printer| {
printer.print_vline((printer.size.x-1, start), height, ' ' as u64); printer.print_vline((printer.size.x - 1, start), height, ' ' as chtype);
}); });
} }
} }

View File

@ -1,8 +1,9 @@
use std::cmp::min; use std::cmp::min;
use std::rc::Rc; use std::rc::Rc;
use ncurses::chtype;
use theme::ColorPair; use theme::ColorPair;
use ::Cursive; use Cursive;
use align::*; use align::*;
use view::{View, IdView, SizeRequest, DimensionRequest}; use view::{View, IdView, SizeRequest, DimensionRequest};
use event::{Event, EventResult, Key}; use event::{Event, EventResult, Key};
@ -129,17 +130,20 @@ impl <T: 'static> View for SelectView<T> {
self.scrollbase.draw(printer, |printer, i| { self.scrollbase.draw(printer, |printer, i| {
let style = if i == self.focus { let style = if i == self.focus {
if printer.focused { ColorPair::Highlight } if printer.focused {
else { ColorPair::HighlightInactive } ColorPair::Highlight
} else {
ColorPair::HighlightInactive
}
} else { } else {
ColorPair::Primary ColorPair::Primary
}; };
printer.with_color(style, |printer| { printer.with_color(style, |printer| {
let l = self.items[i].label.chars().count(); let l = self.items[i].label.chars().count();
let x = self.align.h.get_offset(l, printer.size.x); let x = self.align.h.get_offset(l, printer.size.x);
printer.print_hline((0,0), x, ' ' as u64); printer.print_hline((0, 0), x, ' ' as chtype);
printer.print((x, 0), &self.items[i].label); printer.print((x, 0), &self.items[i].label);
printer.print_hline((x+l,0), printer.size.x-l-x, ' ' as u64); printer.print_hline((x + l, 0), printer.size.x - l - x, ' ' as chtype);
}); });
}); });
} }
@ -157,7 +161,11 @@ impl <T: 'static> View for SelectView<T> {
}; };
// Add 2 spaces for the scrollbar if we need // Add 2 spaces for the scrollbar if we need
let w = if scrolling { w + 2 } else { w }; let w = if scrolling {
w + 2
} else {
w
};
Vec2::new(w, h) Vec2::new(w, h)
} }
@ -167,7 +175,8 @@ impl <T: 'static> View for SelectView<T> {
Event::KeyEvent(Key::Up) if self.focus > 0 => self.focus -= 1, 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::Down) if self.focus + 1 < self.items.len() => self.focus += 1,
Event::KeyEvent(Key::PageUp) => self.focus -= min(self.focus, 10), 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::PageDown) =>
self.focus = min(self.focus + 10, self.items.len() - 1),
Event::KeyEvent(Key::Home) => self.focus = 0, Event::KeyEvent(Key::Home) => self.focus = 0,
Event::KeyEvent(Key::End) => self.focus = self.items.len() - 1, Event::KeyEvent(Key::End) => self.focus = self.items.len() - 1,
Event::KeyEvent(Key::Enter) if self.select_cb.is_some() => { Event::KeyEvent(Key::Enter) if self.select_cb.is_some() => {
@ -176,7 +185,7 @@ impl <T: 'static> View for SelectView<T> {
let v = self.selection(); let v = self.selection();
return EventResult::Consumed(Some(Rc::new(Box::new(move |s| cb(s, &*v))))); return EventResult::Consumed(Some(Rc::new(Box::new(move |s| cb(s, &*v)))));
} }
}, }
Event::CharEvent(c) => { Event::CharEvent(c) => {
// Starting from the current focus, find the first item that match the char. // Starting from the current focus, find the first item that match the char.
// Cycle back to the beginning of the list when we reach the end. // Cycle back to the beginning of the list when we reach the end.
@ -184,12 +193,11 @@ impl <T: 'static> View for SelectView<T> {
let iter = self.items.iter().chain(self.items.iter()); let iter = self.items.iter().chain(self.items.iter());
if let Some((i, _)) = iter.enumerate() if let Some((i, _)) = iter.enumerate()
.skip(self.focus + 1) .skip(self.focus + 1)
.find(|&(_,item)| item.label.starts_with(c)) .find(|&(_, item)| item.label.starts_with(c)) {
{
// Apply modulo in case we have a hit from the chained iterator // Apply modulo in case we have a hit from the chained iterator
self.focus = i % self.items.len(); self.focus = i % self.items.len();
} }
}, }
_ => return EventResult::Ignored, _ => return EventResult::Ignored,
} }

View File

@ -1,3 +1,4 @@
use ncurses::chtype;
use view::{View, ViewWrapper, SizeRequest}; use view::{View, ViewWrapper, SizeRequest};
use printer::Printer; use printer::Printer;
use vec::Vec2; use vec::Vec2;
@ -13,9 +14,7 @@ pub struct ShadowView<T: View> {
impl <T: View> ShadowView<T> { impl <T: View> ShadowView<T> {
/// Wraps the given view. /// Wraps the given view.
pub fn new(view: T) -> Self { pub fn new(view: T) -> Self {
ShadowView { ShadowView { view: view }
view: view,
}
} }
} }
@ -36,20 +35,18 @@ impl <T: View> ViewWrapper for ShadowView<T> {
printer.with_color(ColorPair::Primary, |printer| { printer.with_color(ColorPair::Primary, |printer| {
// Draw the view background // Draw the view background
for y in 1..printer.size.y - 1 { for y in 1..printer.size.y - 1 {
printer.print_hline((1,y), printer.size.x-2, ' ' as u64); printer.print_hline((1, y), printer.size.x - 2, ' ' as chtype);
} }
}); });
self.view.draw(&printer.sub_printer(Vec2::new(1,1), self.view.draw(&printer.sub_printer(Vec2::new(1, 1), printer.size - (2, 2), true));
printer.size - (2,2),
true));
let h = printer.size.y - 1; let h = printer.size.y - 1;
let w = printer.size.x - 1; let w = printer.size.x - 1;
printer.with_color(ColorPair::Shadow, |printer| { printer.with_color(ColorPair::Shadow, |printer| {
printer.print_hline((2,h), w-1, ' ' as u64); printer.print_hline((2, h), w - 1, ' ' as chtype);
printer.print_vline((w,2), h-1, ' ' as u64); printer.print_vline((w, 2), h - 1, ' ' as chtype);
}); });
} }
} }

View File

@ -1,4 +1,3 @@
use std::cmp::max;
use std::any::Any; use std::any::Any;
use vec::Vec2; use vec::Vec2;
@ -22,9 +21,7 @@ struct Layer {
impl StackView { impl StackView {
/// Creates a new empty StackView /// Creates a new empty StackView
pub fn new() -> Self { pub fn new() -> Self {
StackView { StackView { layers: Vec::new() }
layers: Vec::new(),
}
} }
/// Add new view on top of the stack. /// Add new view on top of the stack.
@ -91,7 +88,7 @@ impl View for StackView {
fn take_focus(&mut self) -> bool { fn take_focus(&mut self) -> bool {
match self.layers.last_mut() { match self.layers.last_mut() {
None => false, None => false,
Some(mut v) => v.view.take_focus() Some(mut v) => v.view.take_focus(),
} }
} }

View File

@ -98,7 +98,8 @@ impl TextView {
/// Returns the number of lines required to display the content /// Returns the number of lines required to display the content
/// with the given width. /// with the given width.
fn get_num_lines(&self, max_width: usize) -> usize { fn get_num_lines(&self, max_width: usize) -> usize {
self.content.split("\n") self.content
.split("\n")
.map(|line| get_line_span(line, max_width)) .map(|line| get_line_span(line, max_width))
.fold(0, |sum, x| sum + x) .fold(0, |sum, x| sum + x)
} }
@ -229,8 +230,10 @@ impl View for TextView {
match event { match event {
Event::KeyEvent(Key::Home) => self.scrollbase.scroll_top(), Event::KeyEvent(Key::Home) => self.scrollbase.scroll_top(),
Event::KeyEvent(Key::End) => self.scrollbase.scroll_bottom(), 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::Up) if self.scrollbase.can_scroll_up() =>
Event::KeyEvent(Key::Down) if self.scrollbase.can_scroll_down() => self.scrollbase.scroll_down(1), 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::PageDown) => self.scrollbase.scroll_down(10),
Event::KeyEvent(Key::PageUp) => self.scrollbase.scroll_up(10), Event::KeyEvent(Key::PageUp) => self.scrollbase.scroll_up(10),
_ => return EventResult::Ignored, _ => return EventResult::Ignored,
@ -247,11 +250,11 @@ impl View for TextView {
(DimensionRequest::Fixed(w), _) => { (DimensionRequest::Fixed(w), _) => {
let h = self.get_num_lines(w); let h = self.get_num_lines(w);
Vec2::new(w, h) Vec2::new(w, h)
}, }
(_, DimensionRequest::Fixed(h)) => { (_, DimensionRequest::Fixed(h)) => {
let w = self.get_num_cols(h); let w = self.get_num_cols(h);
Vec2::new(w, h) Vec2::new(w, h)
}, }
(DimensionRequest::AtMost(w), _) => { (DimensionRequest::AtMost(w), _) => {
// Don't _force_ the max width, but take it if we have to. // Don't _force_ the max width, but take it if we have to.
let ideal = self.get_ideal_size(); let ideal = self.get_ideal_size();
@ -262,7 +265,7 @@ impl View for TextView {
let h = self.get_num_lines(w); let h = self.get_num_lines(w);
Vec2::new(w, h) Vec2::new(w, h)
} }
}, }
_ => unreachable!(), _ => unreachable!(),
} }
} }

View File

@ -8,9 +8,7 @@ pub struct ViewPath {
impl ViewPath { impl ViewPath {
/// Creates a new empty path. /// Creates a new empty path.
pub fn new() -> Self { pub fn new() -> Self {
ViewPath { ViewPath { path: Vec::new() }
path: Vec::new(),
}
} }
/// Creates a path from the given item. /// Creates a path from the given item.
@ -27,8 +25,6 @@ pub trait ToPath {
impl <'a> ToPath for &'a [usize] { impl <'a> ToPath for &'a [usize] {
fn to_path(self) -> ViewPath { fn to_path(self) -> ViewPath {
ViewPath { ViewPath { path: self.to_owned() }
path: self.to_owned(),
}
} }
} }

View File

@ -127,4 +127,3 @@ macro_rules! wrap_impl {
} }
}; };
} }