Push most ncurses-specific code into separate trait

Prepare backend change.

Still not isolated is the color setup in `theme`.
This commit is contained in:
Alexandre Bury 2016-06-29 17:36:20 -07:00
parent 69e58a76db
commit 5751a293e5
17 changed files with 326 additions and 314 deletions

221
src/backend.rs Normal file
View File

@ -0,0 +1,221 @@
use event;
use theme;
use utf8;
use ncurses;
pub trait Backend {
fn init();
fn finish();
fn clear();
fn refresh();
fn print_at((usize, usize), &str);
fn poll_event() -> event::Event;
fn set_refresh_rate(fps: u32);
fn screen_size() -> (usize, usize);
fn with_color<F: FnOnce()>(color: theme::ColorPair, f: F);
fn with_effect<F: FnOnce()>(effect: theme::Effect, f: F);
}
pub struct NcursesBackend;
impl Backend for NcursesBackend {
fn init() {
ncurses::setlocale(ncurses::LcCategory::all, "");
ncurses::initscr();
ncurses::keypad(ncurses::stdscr, true);
ncurses::noecho();
ncurses::cbreak();
ncurses::start_color();
ncurses::curs_set(ncurses::CURSOR_VISIBILITY::CURSOR_INVISIBLE);
ncurses::wbkgd(ncurses::stdscr,
ncurses::COLOR_PAIR(theme::ColorPair::Background.ncurses_id()));
}
fn screen_size() -> (usize, usize) {
let mut x: i32 = 0;
let mut y: i32 = 0;
ncurses::getmaxyx(ncurses::stdscr, &mut y, &mut x);
(x as usize, y as usize)
}
fn finish() {
ncurses::endwin();
}
fn with_color<F: FnOnce()>(color: theme::ColorPair, f: F) {
let mut current_style: ncurses::attr_t = 0;
let mut current_color: i16 = 0;
ncurses::attr_get(&mut current_style, &mut current_color);
let style = ncurses::COLOR_PAIR(color.ncurses_id());
ncurses::attron(style);
f();
// ncurses::attroff(style);
ncurses::attron(current_style);
}
fn with_effect<F: FnOnce()>(effect: theme::Effect, f: F) {
let style = match effect {
theme::Effect::Reverse => ncurses::A_REVERSE(),
theme::Effect::Simple => ncurses::A_NORMAL(),
};
ncurses::attron(style);
f();
ncurses::attroff(style);
}
fn clear() {
ncurses::clear();
}
fn refresh() {
ncurses::refresh();
}
fn print_at((x, y): (usize, usize), text: &str) {
println!("{} {}", x, y);
ncurses::mvaddstr(y as i32, x as i32, text);
}
fn poll_event() -> event::Event {
let ch: i32 = ncurses::getch();
// Is it a UTF-8 starting point?
if 32 <= ch && ch < 0x100 && ch != 127 {
event::Event::Char(utf8::read_char(ch as u8, || ncurses::getch() as u8).unwrap())
} else {
event::Event::Key(parse_ncurses_char(ch))
}
}
fn set_refresh_rate(fps: u32) {
if fps == 0 {
ncurses::timeout(-1);
} else {
ncurses::timeout(1000 / fps as i32);
}
}
}
/// Returns the Key enum corresponding to the given ncurses event.
fn parse_ncurses_char(ch: i32) -> event::Key {
match ch {
// Values under 256 are chars and control values
//
// Tab is '\t'
9 => event::Key::Tab,
// Treat '\n' and the numpad Enter the same
10 | ncurses::KEY_ENTER => event::Key::Enter,
// This is the escape key when pressed by itself.
// When used for control sequences, it should have been caught earlier.
27 => event::Key::Esc,
// `Backspace` sends 127, but Ctrl-H sends `Backspace`
127 | ncurses::KEY_BACKSPACE => event::Key::Backspace,
410 => event::Key::Resize,
// Values 512 and above are probably extensions
// Those keys don't seem to be documented...
519 => event::Key::AltDel,
520 => event::Key::AltShiftDel,
521 => event::Key::CtrlDel,
522 => event::Key::CtrlShiftDel,
// 523: CtrlAltDel?
//
// 524?
525 => event::Key::AltDown,
526 => event::Key::AltShiftDown,
527 => event::Key::CtrlDown,
528 => event::Key::CtrlShiftDown,
529 => event::Key::CtrlAltDown,
530 => event::Key::AltEnd,
531 => event::Key::AltShiftEnd,
532 => event::Key::CtrlEnd,
533 => event::Key::CtrlShiftEnd,
534 => event::Key::CtrlAltEnd,
535 => event::Key::AltHome,
536 => event::Key::AltShiftHome,
537 => event::Key::CtrlHome,
538 => event::Key::CtrlShiftHome,
539 => event::Key::CtrlAltHome,
540 => event::Key::AltIns,
// 541: AltShiftIns?
542 => event::Key::CtrlIns,
// 543: CtrlShiftIns?
544 => event::Key::CtrlAltIns,
545 => event::Key::AltLeft,
546 => event::Key::AltShiftLeft,
547 => event::Key::CtrlLeft,
548 => event::Key::CtrlShiftLeft,
549 => event::Key::CtrlAltLeft,
550 => event::Key::AltPageDown,
551 => event::Key::AltShiftPageDown,
552 => event::Key::CtrlPageDown,
553 => event::Key::CtrlShiftPageDown,
554 => event::Key::CtrlAltPageDown,
555 => event::Key::AltPageUp,
556 => event::Key::AltShiftPageUp,
557 => event::Key::CtrlPageUp,
558 => event::Key::CtrlShiftPageUp,
559 => event::Key::CtrlAltPageUp,
560 => event::Key::AltRight,
561 => event::Key::AltShiftRight,
562 => event::Key::CtrlRight,
563 => event::Key::CtrlShiftRight,
564 => event::Key::CtrlAltRight,
// 565?
566 => event::Key::AltUp,
567 => event::Key::AltShiftUp,
568 => event::Key::CtrlUp,
569 => event::Key::CtrlShiftUp,
570 => event::Key::CtrlAltUp,
ncurses::KEY_B2 => event::Key::NumpadCenter,
ncurses::KEY_DC => event::Key::Del,
ncurses::KEY_IC => event::Key::Ins,
ncurses::KEY_BTAB => event::Key::ShiftTab,
ncurses::KEY_SLEFT => event::Key::ShiftLeft,
ncurses::KEY_SRIGHT => event::Key::ShiftRight,
ncurses::KEY_LEFT => event::Key::Left,
ncurses::KEY_RIGHT => event::Key::Right,
ncurses::KEY_UP => event::Key::Up,
ncurses::KEY_DOWN => event::Key::Down,
ncurses::KEY_SR => event::Key::ShiftUp,
ncurses::KEY_SF => event::Key::ShiftDown,
ncurses::KEY_PPAGE => event::Key::PageUp,
ncurses::KEY_NPAGE => event::Key::PageDown,
ncurses::KEY_HOME => event::Key::Home,
ncurses::KEY_END => event::Key::End,
ncurses::KEY_SHOME => event::Key::ShiftHome,
ncurses::KEY_SEND => event::Key::ShiftEnd,
ncurses::KEY_SDC => event::Key::ShiftDel,
ncurses::KEY_SNEXT => event::Key::ShiftPageDown,
ncurses::KEY_SPREVIOUS => event::Key::ShiftPageUp,
// All Fn keys use the same enum with associated number
f @ ncurses::KEY_F1...ncurses::KEY_F12 => event::Key::F((f - ncurses::KEY_F0) as u8),
f @ 277...288 => event::Key::ShiftF((f - 277) as u8),
f @ 289...300 => event::Key::CtrlF((f - 289) as u8),
f @ 301...312 => event::Key::CtrlShiftF((f - 300) as u8),
f @ 313...324 => event::Key::AltF((f - 313) as u8),
// Shift and Ctrl F{1-4} need escape sequences...
//
// TODO: shift and ctrl Fn keys
// Avoids 8-10 (H,I,J), they are used by other commands.
c @ 1...7 | c @ 11...25 => event::Key::CtrlChar((b'a' + (c - 1) as u8) as char),
_ => event::Key::Unknown(ch),
}
}

View File

@ -3,8 +3,6 @@
use std::fmt; use std::fmt;
use std::rc::Rc; use std::rc::Rc;
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.
@ -130,128 +128,6 @@ pub enum Key {
Unknown(i32), Unknown(i32),
} }
impl Key {
/// Returns the Key enum corresponding to the given ncurses event.
pub fn from_ncurses(ch: i32) -> Self {
match ch {
// Values under 256 are chars and control values
//
// Tab is '\t'
9 => Key::Tab,
// Treat '\n' and the numpad Enter the same
10 | ncurses::KEY_ENTER => Key::Enter,
// This is the escape key when pressed by itself.
// When used for control sequences, it should have been caught earlier.
27 => Key::Esc,
// `Backspace` sends 127, but Ctrl-H sends `Backspace`
127 | ncurses::KEY_BACKSPACE => Key::Backspace,
410 => Key::Resize,
// Values 512 and above are probably extensions
// Those keys don't seem to be documented...
519 => Key::AltDel,
520 => Key::AltShiftDel,
521 => Key::CtrlDel,
522 => Key::CtrlShiftDel,
// 523: CtrlAltDel?
//
// 524?
525 => Key::AltDown,
526 => Key::AltShiftDown,
527 => Key::CtrlDown,
528 => Key::CtrlShiftDown,
529 => Key::CtrlAltDown,
530 => Key::AltEnd,
531 => Key::AltShiftEnd,
532 => Key::CtrlEnd,
533 => Key::CtrlShiftEnd,
534 => Key::CtrlAltEnd,
535 => Key::AltHome,
536 => Key::AltShiftHome,
537 => Key::CtrlHome,
538 => Key::CtrlShiftHome,
539 => Key::CtrlAltHome,
540 => Key::AltIns,
// 541: AltShiftIns?
542 => Key::CtrlIns,
// 543: CtrlShiftIns?
544 => Key::CtrlAltIns,
545 => Key::AltLeft,
546 => Key::AltShiftLeft,
547 => Key::CtrlLeft,
548 => Key::CtrlShiftLeft,
549 => Key::CtrlAltLeft,
550 => Key::AltPageDown,
551 => Key::AltShiftPageDown,
552 => Key::CtrlPageDown,
553 => Key::CtrlShiftPageDown,
554 => Key::CtrlAltPageDown,
555 => Key::AltPageUp,
556 => Key::AltShiftPageUp,
557 => Key::CtrlPageUp,
558 => Key::CtrlShiftPageUp,
559 => Key::CtrlAltPageUp,
560 => Key::AltRight,
561 => Key::AltShiftRight,
562 => Key::CtrlRight,
563 => Key::CtrlShiftRight,
564 => Key::CtrlAltRight,
// 565?
566 => Key::AltUp,
567 => Key::AltShiftUp,
568 => Key::CtrlUp,
569 => Key::CtrlShiftUp,
570 => Key::CtrlAltUp,
ncurses::KEY_B2 => Key::NumpadCenter,
ncurses::KEY_DC => Key::Del,
ncurses::KEY_IC => Key::Ins,
ncurses::KEY_BTAB => Key::ShiftTab,
ncurses::KEY_SLEFT => Key::ShiftLeft,
ncurses::KEY_SRIGHT => Key::ShiftRight,
ncurses::KEY_LEFT => Key::Left,
ncurses::KEY_RIGHT => Key::Right,
ncurses::KEY_UP => Key::Up,
ncurses::KEY_DOWN => Key::Down,
ncurses::KEY_SR => Key::ShiftUp,
ncurses::KEY_SF => Key::ShiftDown,
ncurses::KEY_PPAGE => Key::PageUp,
ncurses::KEY_NPAGE => Key::PageDown,
ncurses::KEY_HOME => Key::Home,
ncurses::KEY_END => Key::End,
ncurses::KEY_SHOME => Key::ShiftHome,
ncurses::KEY_SEND => Key::ShiftEnd,
ncurses::KEY_SDC => Key::ShiftDel,
ncurses::KEY_SNEXT => Key::ShiftPageDown,
ncurses::KEY_SPREVIOUS => Key::ShiftPageUp,
// All Fn keys use the same enum with associated number
f @ ncurses::KEY_F1...ncurses::KEY_F12 => {
Key::F((f - ncurses::KEY_F0) as u8)
}
f @ 277...288 => Key::ShiftF((f - 277) as u8),
f @ 289...300 => Key::CtrlF((f - 289) as u8),
f @ 301...312 => Key::CtrlShiftF((f - 300) as u8),
f @ 313...324 => Key::AltF((f - 313) as u8),
// Shift and Ctrl F{1-4} need escape sequences...
//
// TODO: shift and ctrl Fn keys
// Avoids 8-10 (H,I,J), they are used by other commands.
c @ 1...7 | c @ 11...25 => {
Key::CtrlChar((b'a' + (c - 1) as u8) as char)
}
_ => Key::Unknown(ch),
}
}
}
impl fmt::Display for Key { impl fmt::Display for Key {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self { match *self {

View File

@ -39,6 +39,10 @@ mod menubar;
mod div; mod div;
mod utf8; mod utf8;
mod backend;
use backend::{Backend, NcursesBackend};
use std::any::Any; use std::any::Any;
use std::rc::Rc; use std::rc::Rc;
use std::collections::HashMap; use std::collections::HashMap;
@ -49,7 +53,7 @@ use printer::Printer;
use view::View; use view::View;
use view::{Selector, StackView}; use view::{Selector, StackView};
use event::{Callback, Event, EventResult, Key, ToEvent}; use event::{Callback, Event, EventResult, ToEvent};
/// Identifies a screen in the cursive ROOT. /// Identifies a screen in the cursive ROOT.
pub type ScreenId = usize; pub type ScreenId = usize;
@ -78,23 +82,20 @@ impl Default for Cursive {
} }
} }
// Use the Ncurses backend.
// TODO: make this feature-driven
type B = NcursesBackend;
impl Cursive { impl Cursive {
/// Creates a new Cursive root, and initialize ncurses. /// Creates a new Cursive root, and initialize ncurses.
pub fn new() -> Self { pub fn new() -> Self {
// Default delay is way too long. 25 is imperceptible yet works fine. // Default delay is way too long. 25 is imperceptible yet works fine.
std::env::set_var("ESCDELAY", "25"); std::env::set_var("ESCDELAY", "25");
ncurses::setlocale(ncurses::LcCategory::all, ""); B::init();
ncurses::initscr();
ncurses::keypad(ncurses::stdscr, true);
ncurses::noecho();
ncurses::cbreak();
ncurses::start_color();
ncurses::curs_set(ncurses::CURSOR_VISIBILITY::CURSOR_INVISIBLE);
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()));
let mut res = Cursive { let mut res = Cursive {
theme: theme, theme: theme,
@ -148,11 +149,7 @@ impl Cursive {
/// ///
/// Call with fps=0 to disable (default value). /// Call with fps=0 to disable (default value).
pub fn set_fps(&self, fps: u32) { pub fn set_fps(&self, fps: u32) {
if fps == 0 { B::set_refresh_rate(fps)
ncurses::timeout(-1);
} else {
ncurses::timeout(1000 / fps as i32);
}
} }
/// Returns a mutable reference to the currently active screen. /// Returns a mutable reference to the currently active screen.
@ -235,9 +232,7 @@ impl Cursive {
/// Returns the size of the screen, in characters. /// Returns the size of the screen, in characters.
pub fn screen_size(&self) -> Vec2 { pub fn screen_size(&self) -> Vec2 {
let mut x: i32 = 0; let (x, y) = B::screen_size();
let mut y: i32 = 0;
ncurses::getmaxyx(ncurses::stdscr, &mut y, &mut x);
Vec2 { Vec2 {
x: x as usize, x: x as usize,
@ -258,35 +253,20 @@ impl Cursive {
// Draw the currently active screen // Draw the currently active screen
// If the menubar is active, nothing else can be. // If the menubar is active, nothing else can be.
let offset = if self.menu.autohide { let offset = if self.menu.autohide {
1
} else {
0 0
} else {
1
}; };
let selected = self.menu.selected; let selected = self.menu.selected;
self.screen_mut() self.screen_mut()
.draw(&printer.sub_printer(Vec2::new(0, offset), .draw(&printer.sub_printer(Vec2::new(0, offset), printer.size, !selected));
printer.size,
!selected));
// Draw the menubar? // Draw the menubar?
if self.menu.selected || !self.menu.autohide { if self.menu.selected || !self.menu.autohide {
self.menu.draw(&printer); self.menu.draw(&printer);
} }
ncurses::refresh(); B::refresh();
}
fn poll_event() -> Event {
let ch: i32 = ncurses::getch();
// Is it a UTF-8 starting point?
if 32 <= ch && ch < 0x100 && ch != 127 {
Event::Char(utf8::read_char(ch as u8,
|| ncurses::getch() as u8)
.unwrap())
} else {
Event::Key(Key::from_ncurses(ch))
}
} }
/// Runs the event loop. /// Runs the event loop.
@ -300,7 +280,7 @@ impl Cursive {
// Do we need to redraw everytime? // Do we need to redraw everytime?
// Probably, actually. // Probably, actually.
// TODO: Do we actually need to clear everytime? // TODO: Do we actually need to clear everytime?
ncurses::clear(); B::clear();
// TODO: Do we need to re-layout everytime? // TODO: Do we need to re-layout everytime?
self.layout(); self.layout();
// TODO: Do we need to redraw every view every time? // TODO: Do we need to redraw every view every time?
@ -309,7 +289,7 @@ impl Cursive {
// Wait for next event. // Wait for next event.
// (If set_fps was called, this returns -1 now and then) // (If set_fps was called, this returns -1 now and then)
let event = Cursive::poll_event(); let event = B::poll_event();
// Event dispatch order: // Event dispatch order:
// * Focused element: // * Focused element:
@ -339,6 +319,6 @@ impl Cursive {
impl Drop for Cursive { impl Drop for Cursive {
fn drop(&mut self) { fn drop(&mut self) {
ncurses::endwin(); B::finish();
} }
} }

View File

@ -35,8 +35,7 @@ impl MenuTree {
self self
} }
pub fn leaf<F: 'static + Fn(&mut Cursive)>(&mut self, title: &str, cb: F) pub fn leaf<F: 'static + Fn(&mut Cursive)>(&mut self, title: &str, cb: F) -> &mut Self {
-> &mut Self {
self.children self.children
.push(MenuItem::Leaf(title.to_string(), Rc::new(Box::new(cb)))); .push(MenuItem::Leaf(title.to_string(), Rc::new(Box::new(cb))));
self self

View File

@ -44,12 +44,8 @@ impl Orientation {
/// For a vertical view, returns (Max(x),Sum(y)). /// For a vertical view, returns (Max(x),Sum(y)).
pub fn stack<'a, T: Iterator<Item = &'a Vec2>>(&self, iter: T) -> Vec2 { pub fn stack<'a, T: Iterator<Item = &'a Vec2>>(&self, iter: T) -> Vec2 {
match *self { match *self {
Orientation::Horizontal => { Orientation::Horizontal => iter.fold(Vec2::zero(), |a, b| a.stack_horizontal(b)),
iter.fold(Vec2::zero(), |a, b| a.stack_horizontal(b)) Orientation::Vertical => iter.fold(Vec2::zero(), |a, b| a.stack_vertical(b)),
}
Orientation::Vertical => {
iter.fold(Vec2::zero(), |a, b| a.stack_vertical(b))
}
} }
} }
} }

View File

@ -2,9 +2,11 @@
use std::cmp::min; use std::cmp::min;
use ncurses; use backend::Backend;
use theme::{ColorPair, Theme}; use B;
use theme::{ColorPair, Theme, Effect};
use vec::{ToVec2, Vec2}; use vec::{ToVec2, Vec2};
/// Convenient interface to draw on a subset of the screen. /// Convenient interface to draw on a subset of the screen.
@ -49,11 +51,9 @@ impl Printer {
let p = p + self.offset; let p = p + self.offset;
if text.contains('%') { if text.contains('%') {
ncurses::mvprintw(p.y as i32, B::print_at((p.x, p.y), &text.replace("%", "%%"));
p.x as i32,
&text.replace("%", "%%"));
} else { } else {
ncurses::mvprintw(p.y as i32, p.x as i32, text); B::print_at((p.x, p.y), text);
} }
} }
@ -67,7 +67,7 @@ impl Printer {
let p = p + self.offset; let p = p + self.offset;
for y in 0..len { for y in 0..len {
ncurses::mvaddstr((p.y + y) as i32, p.x as i32, c); B::print_at((p.x, (p.y + y)), c);
} }
} }
@ -81,7 +81,7 @@ impl Printer {
let p = p + self.offset; let p = p + self.offset;
for x in 0..len { for x in 0..len {
ncurses::mvaddstr(p.y as i32, (p.x + x) as i32, c); B::print_at((p.x + x, p.y), c);
} }
} }
@ -99,22 +99,19 @@ impl Printer {
/// }); /// });
/// ``` /// ```
pub fn with_color<F>(&self, c: ColorPair, f: F) pub fn with_color<F>(&self, c: ColorPair, f: F)
where F: Fn(&Printer) where F: FnOnce(&Printer)
{ {
self.with_style(ncurses::COLOR_PAIR(c.ncurses_id()), f); B::with_color(c, || f(self));
ncurses::attron(ncurses::COLOR_PAIR(ColorPair::Primary.ncurses_id()));
} }
/// Same as `with_color`, but apply a ncurses style instead, /// Same as `with_color`, but apply a ncurses style instead,
/// like `ncurses::A_BOLD()` or `ncurses::A_REVERSE()`. /// like `ncurses::A_BOLD()` or `ncurses::A_REVERSE()`.
/// ///
/// Will probably use a cursive enum some day. /// Will probably use a cursive enum some day.
pub fn with_style<F>(&self, style: ncurses::attr_t, f: F) pub fn with_effect<F>(&self, effect: Effect, f: F)
where F: Fn(&Printer) where F: FnOnce(&Printer)
{ {
ncurses::attron(style); B::with_effect(effect, || f(self));
f(self);
ncurses::attroff(style);
} }
/// Prints a rectangular box. /// Prints a rectangular box.
@ -138,12 +135,8 @@ impl Printer {
self.print_hline(start_v + (1, 0), size_v.x - 1, ""); self.print_hline(start_v + (1, 0), size_v.x - 1, "");
self.print_vline(start_v + (0, 1), size_v.y - 1, ""); self.print_vline(start_v + (0, 1), size_v.y - 1, "");
self.print_hline(start_v + (1, 0) + size_v.keep_y(), self.print_hline(start_v + (1, 0) + size_v.keep_y(), size_v.x - 1, "");
size_v.x - 1, self.print_vline(start_v + (0, 1) + size_v.keep_x(), size_v.y - 1, "");
"");
self.print_vline(start_v + (0, 1) + size_v.keep_x(),
size_v.y - 1,
"");
} }
pub fn print_hdelim<T: ToVec2>(&self, start: T, len: usize) { pub fn print_hdelim<T: ToVec2>(&self, start: T, len: usize) {
@ -154,8 +147,7 @@ impl Printer {
} }
/// Returns a printer on a subset of this one's area. /// Returns a printer on a subset of this one's area.
pub fn sub_printer<S: ToVec2>(&self, offset: S, size: S, focused: bool) pub fn sub_printer<S: ToVec2>(&self, offset: S, size: S, focused: bool) -> Printer {
-> Printer {
let offset_v = offset.to_vec2(); let offset_v = offset.to_vec2();
Printer { Printer {
offset: self.offset + offset_v, offset: self.offset + offset_v,

View File

@ -8,6 +8,11 @@ use std::path::Path;
use ncurses; use ncurses;
use toml; use toml;
pub enum Effect {
Simple,
Reverse,
}
/// Represents the color of a character and its background. /// Represents the color of a character and its background.
#[derive(Clone,Copy)] #[derive(Clone,Copy)]
pub enum ColorPair { pub enum ColorPair {
@ -97,14 +102,10 @@ impl Theme {
fn apply(&self) { fn apply(&self) {
Theme::apply_color(ColorPair::Background, Theme::apply_color(ColorPair::Background,
&self.colors.background, &self.colors.view,
&self.colors.background); &self.colors.background);
Theme::apply_color(ColorPair::Shadow, Theme::apply_color(ColorPair::Shadow, &self.colors.shadow, &self.colors.shadow);
&self.colors.shadow, Theme::apply_color(ColorPair::Primary, &self.colors.primary, &self.colors.view);
&self.colors.shadow);
Theme::apply_color(ColorPair::Primary,
&self.colors.primary,
&self.colors.view);
Theme::apply_color(ColorPair::Secondary, Theme::apply_color(ColorPair::Secondary,
&self.colors.secondary, &self.colors.secondary,
&self.colors.view); &self.colors.view);

View File

@ -50,9 +50,7 @@ impl View for Button {
fn on_event(&mut self, event: Event) -> EventResult { fn on_event(&mut self, event: Event) -> EventResult {
match event { match event {
// 10 is the ascii code for '\n', that is the return key // 10 is the ascii code for '\n', that is the return key
Event::Key(Key::Enter) => { Event::Key(Key::Enter) => EventResult::Consumed(Some(self.callback.clone())),
EventResult::Consumed(Some(self.callback.clone()))
}
_ => EventResult::Ignored, _ => EventResult::Ignored,
} }
} }

View File

@ -121,9 +121,7 @@ impl View for Dialog {
}; };
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, self.align.h.get_offset(width, printer.size.x - overhead.horizontal());
printer.size.x -
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() {
@ -139,12 +137,10 @@ impl View for Dialog {
} }
// What do we have left? // What do we have left?
let inner_size = printer.size - Vec2::new(0, height) - let inner_size = printer.size - Vec2::new(0, height) - self.borders.combined() -
self.borders.combined() -
self.padding.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));
@ -156,16 +152,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);

View File

@ -1,9 +1,8 @@
use ncurses;
use unicode_segmentation::UnicodeSegmentation; use unicode_segmentation::UnicodeSegmentation;
use std::cmp::min; use std::cmp::min;
use theme::ColorPair; use theme::{ColorPair, Effect};
use vec::Vec2; use vec::Vec2;
use view::{IdView, SizeRequest, View}; use view::{IdView, SizeRequest, View};
use event::*; use event::*;
@ -87,13 +86,12 @@ impl View for EditView {
// let style = if focused { color::HIGHLIGHT } else { color::HIGHLIGHT_INACTIVE }; // let style = if focused { color::HIGHLIGHT } else { color::HIGHLIGHT_INACTIVE };
let len = self.content.chars().count(); let len = self.content.chars().count();
printer.with_color(ColorPair::Secondary, |printer| { printer.with_color(ColorPair::Secondary, |printer| {
printer.with_style(ncurses::A_REVERSE(), |printer| { printer.with_effect(Effect::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, "_"); printer.print_hline((len, 0), printer.size.x - len, "_");
} else { } else {
let visible_end = min(self.content.len(), let visible_end = min(self.content.len(), self.offset + self.last_length);
self.offset + self.last_length);
let content = &self.content[self.offset..visible_end]; let content = &self.content[self.offset..visible_end];
printer.print((0, 0), content); printer.print((0, 0), content);
@ -151,10 +149,7 @@ impl View for EditView {
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 < Key::Right if self.cursor < self.content.chars().count() => self.cursor += 1,
self.content.chars().count() => {
self.cursor += 1
}
Key::Backspace if self.cursor > 0 => { Key::Backspace if self.cursor > 0 => {
self.cursor -= 1; self.cursor -= 1;
remove_char(&mut self.content, self.cursor); remove_char(&mut self.content, self.cursor);

View File

@ -113,9 +113,7 @@ impl View for LinearLayout {
// Use pre-computed sizes // Use pre-computed sizes
let mut offset = Vec2::zero(); let mut offset = Vec2::zero();
for (i, child) in self.children.iter_mut().enumerate() { for (i, child) in self.children.iter_mut().enumerate() {
child.view.draw(&printer.sub_printer(offset, child.view.draw(&printer.sub_printer(offset, child.size, i == self.focus));
child.size,
i == self.focus));
*self.orientation.get_ref(&mut offset) += self.orientation *self.orientation.get_ref(&mut offset) += self.orientation
.get(&child.size); .get(&child.size);
@ -130,9 +128,7 @@ impl View for LinearLayout {
}; };
let min_sizes: Vec<Vec2> = self.children let min_sizes: Vec<Vec2> = self.children
.iter() .iter()
.map(|child| { .map(|child| child.view.get_min_size(req))
child.view.get_min_size(req)
})
.collect(); .collect();
let min_size = self.orientation.stack(min_sizes.iter()); let min_size = self.orientation.stack(min_sizes.iter());
@ -153,14 +149,12 @@ impl View for LinearLayout {
}; };
for (child, (child_size, extra)) in for (child, (child_size, extra)) in self.children
self.children
.iter_mut() .iter_mut()
.zip(min_sizes.iter().zip(extras.iter())) { .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_ref(&mut child_size) = self.orientation.swap().get(&size);
self.orientation.swap().get(&size);
child.size = child_size; child.size = child_size;
child.view.layout(child_size); child.view.layout(child_size);
} }
@ -194,34 +188,27 @@ impl View for LinearLayout {
self.focus -= 1; self.focus -= 1;
EventResult::Consumed(None) EventResult::Consumed(None)
} }
Event::Key(Key::ShiftTab) if self.focus + 1 < Event::Key(Key::ShiftTab) if self.focus + 1 < self.children.len() => {
self.children.len() => {
self.focus += 1; self.focus += 1;
EventResult::Consumed(None) EventResult::Consumed(None)
} }
Event::Key(Key::Left) if self.orientation == Event::Key(Key::Left) if self.orientation == Orientation::Horizontal &&
Orientation::Horizontal &&
self.focus > 0 => { self.focus > 0 => {
self.focus -= 1; self.focus -= 1;
EventResult::Consumed(None) EventResult::Consumed(None)
} }
Event::Key(Key::Up) if self.orientation == Event::Key(Key::Up) if self.orientation == Orientation::Vertical &&
Orientation::Vertical &&
self.focus > 0 => { self.focus > 0 => {
self.focus -= 1; self.focus -= 1;
EventResult::Consumed(None) EventResult::Consumed(None)
} }
Event::Key(Key::Right) if self.orientation == Event::Key(Key::Right) if self.orientation == Orientation::Horizontal &&
Orientation::Horizontal && self.focus + 1 < self.children.len() => {
self.focus + 1 <
self.children.len() => {
self.focus += 1; self.focus += 1;
EventResult::Consumed(None) EventResult::Consumed(None)
} }
Event::Key(Key::Down) if self.orientation == Event::Key(Key::Down) if self.orientation == Orientation::Vertical &&
Orientation::Vertical && self.focus + 1 < self.children.len() => {
self.focus + 1 <
self.children.len() => {
self.focus += 1; self.focus += 1;
EventResult::Consumed(None) EventResult::Consumed(None)
} }

View File

@ -16,9 +16,7 @@ impl DimensionRequest {
pub fn reduced(self, offset: usize) -> Self { pub fn reduced(self, offset: usize) -> Self {
match self { match self {
DimensionRequest::Fixed(w) => DimensionRequest::Fixed(w - offset), DimensionRequest::Fixed(w) => DimensionRequest::Fixed(w - offset),
DimensionRequest::AtMost(w) => { DimensionRequest::AtMost(w) => DimensionRequest::AtMost(w - offset),
DimensionRequest::AtMost(w - offset)
}
DimensionRequest::Unknown => DimensionRequest::Unknown, DimensionRequest::Unknown => DimensionRequest::Unknown,
} }
} }

View File

@ -27,8 +27,7 @@ impl ScrollBase {
self.content_height = content_height; self.content_height = content_height;
if self.scrollable() { if self.scrollable() {
self.start_line = min(self.start_line, self.start_line = min(self.start_line, self.content_height - self.view_height);
self.content_height - self.view_height);
} else { } else {
self.start_line = 0; self.start_line = 0;
} }
@ -70,8 +69,7 @@ impl ScrollBase {
/// Scroll down by the given number of line, never going further than the bottom of the view. /// Scroll down by the given number of line, never going further than the bottom of the view.
pub fn scroll_down(&mut self, n: usize) { pub fn scroll_down(&mut self, n: usize) {
self.start_line = min(self.start_line + n, self.start_line = min(self.start_line + n, self.content_height - self.view_height);
self.content_height - self.view_height);
} }
/// Scroll up by the given number of lines, never going above the top of the view. /// Scroll up by the given number of lines, never going above the top of the view.
@ -104,8 +102,7 @@ impl ScrollBase {
where F: Fn(&Printer, usize) where F: Fn(&Printer, usize)
{ {
// Print the content in a sub_printer // Print the content in a sub_printer
let max_y = min(self.view_height, let max_y = min(self.view_height, self.content_height - self.start_line);
self.content_height - self.start_line);
let w = if self.scrollable() { let w = if self.scrollable() {
printer.size.x - 2 printer.size.x - 2
} else { } else {
@ -114,9 +111,7 @@ impl ScrollBase {
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), line_drawer(&printer.sub_printer(Vec2::new(0, y), Vec2::new(w, 1), true),
Vec2::new(w, 1),
true),
y + self.start_line); y + self.start_line);
} }
@ -126,15 +121,12 @@ impl ScrollBase {
// We directly compute the size of the scrollbar (this allow use to avoid using floats). // We directly compute the size of the scrollbar (this allow use to avoid using floats).
// (ratio) * max_height // (ratio) * max_height
// Where ratio is ({start or end} / content.height) // Where ratio is ({start or end} / content.height)
let height = max(1, let height = max(1, self.view_height * self.view_height / self.content_height);
self.view_height * self.view_height /
self.content_height);
// Number of different possible positions // Number of different possible positions
let steps = self.view_height - height + 1; let steps = self.view_height - height + 1;
// Now // Now
let start = steps * self.start_line / let start = steps * self.start_line / (1 + self.content_height - self.view_height);
(1 + self.content_height - self.view_height);
let color = if printer.focused { let color = if printer.focused {
ColorPair::Highlight ColorPair::Highlight

View File

@ -128,9 +128,7 @@ impl<T: 'static> View for SelectView<T> {
let h = self.items.len(); let h = self.items.len();
let offset = self.align.v.get_offset(h, printer.size.y); let offset = self.align.v.get_offset(h, printer.size.y);
let printer = &printer.sub_printer(Vec2::new(0, offset), let printer = &printer.sub_printer(Vec2::new(0, offset), printer.size, true);
printer.size,
true);
self.scrollbase.draw(printer, |printer, i| { self.scrollbase.draw(printer, |printer, i| {
let style = if i == self.focus { let style = if i == self.focus {
@ -181,12 +179,9 @@ impl<T: 'static> View for SelectView<T> {
fn on_event(&mut self, event: Event) -> EventResult { fn on_event(&mut self, event: Event) -> EventResult {
match event { match event {
Event::Key(Key::Up) if self.focus > 0 => self.focus -= 1, Event::Key(Key::Up) if self.focus > 0 => self.focus -= 1,
Event::Key(Key::Down) if self.focus + 1 < Event::Key(Key::Down) if self.focus + 1 < self.items.len() => self.focus += 1,
self.items.len() => self.focus += 1,
Event::Key(Key::PageUp) => self.focus -= min(self.focus, 10), Event::Key(Key::PageUp) => self.focus -= min(self.focus, 10),
Event::Key(Key::PageDown) => { Event::Key(Key::PageDown) => self.focus = min(self.focus + 10, self.items.len() - 1),
self.focus = min(self.focus + 10, self.items.len() - 1)
}
Event::Key(Key::Home) => self.focus = 0, Event::Key(Key::Home) => self.focus = 0,
Event::Key(Key::End) => self.focus = self.items.len() - 1, Event::Key(Key::End) => self.focus = self.items.len() - 1,
Event::Key(Key::Enter) if self.select_cb.is_some() => { Event::Key(Key::Enter) if self.select_cb.is_some() => {
@ -205,9 +200,7 @@ 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)| { .find(|&(_, item)| item.label.starts_with(c)) {
item.label.starts_with(c)
}) {
// Apply modulo in case we have a hit // Apply modulo in case we have a hit
// from the chained iterator // from the chained iterator
self.focus = i % self.items.len(); self.focus = i % self.items.len();

View File

@ -30,16 +30,12 @@ impl<T: View> ViewWrapper for ShadowView<T> {
fn wrap_draw(&mut self, printer: &Printer) { fn wrap_draw(&mut self, printer: &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, " "); printer.print_hline((1, y), printer.size.x - 2, " ");
} }
});
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;

View File

@ -4,6 +4,7 @@ use vec::Vec2;
use view::{DimensionRequest, Selector, ShadowView, SizeRequest, View}; use view::{DimensionRequest, Selector, ShadowView, SizeRequest, View};
use event::{Event, EventResult}; use event::{Event, EventResult};
use printer::Printer; use printer::Printer;
use theme::ColorPair;
/// Simple stack of views. /// Simple stack of views.
/// Only the top-most view is active and can receive input. /// Only the top-most view is active and can receive input.
@ -48,6 +49,7 @@ impl StackView {
impl View for StackView { impl View for StackView {
fn draw(&mut self, printer: &Printer) { fn draw(&mut self, printer: &Printer) {
let last = self.layers.len(); let last = self.layers.len();
printer.with_color(ColorPair::Primary, |printer| {
for (i, v) in self.layers.iter_mut().enumerate() { for (i, v) in self.layers.iter_mut().enumerate() {
// Center the view // Center the view
let size = v.size; let size = v.size;
@ -55,6 +57,7 @@ impl View for StackView {
// TODO: only draw focus for the top view // TODO: only draw focus for the top view
v.view.draw(&printer.sub_printer(offset, size, i + 1 == last)); v.view.draw(&printer.sub_printer(offset, size, i + 1 == last));
} }
});
} }
fn on_event(&mut self, event: Event) -> EventResult { fn on_event(&mut self, event: Event) -> EventResult {

View File

@ -214,9 +214,7 @@ impl View for TextView {
let h = self.rows.len(); let h = self.rows.len();
let offset = self.align.v.get_offset(h, printer.size.y); let offset = self.align.v.get_offset(h, printer.size.y);
let printer = &printer.sub_printer(Vec2::new(0, offset), let printer = &printer.sub_printer(Vec2::new(0, offset), printer.size, true);
printer.size,
true);
self.scrollbase.draw(printer, |printer, i| { self.scrollbase.draw(printer, |printer, i| {
let row = &self.rows[i]; let row = &self.rows[i];
@ -235,13 +233,9 @@ impl View for TextView {
match event { match event {
Event::Key(Key::Home) => self.scrollbase.scroll_top(), Event::Key(Key::Home) => self.scrollbase.scroll_top(),
Event::Key(Key::End) => self.scrollbase.scroll_bottom(), Event::Key(Key::End) => self.scrollbase.scroll_bottom(),
Event::Key(Key::Up) if self.scrollbase.can_scroll_up() => { Event::Key(Key::Up) if self.scrollbase.can_scroll_up() => self.scrollbase.scroll_up(1),
self.scrollbase.scroll_up(1)
}
Event::Key(Key::Down) if self.scrollbase Event::Key(Key::Down) if self.scrollbase
.can_scroll_down() => { .can_scroll_down() => self.scrollbase.scroll_down(1),
self.scrollbase.scroll_down(1)
}
Event::Key(Key::PageDown) => self.scrollbase.scroll_down(10), Event::Key(Key::PageDown) => self.scrollbase.scroll_down(10),
Event::Key(Key::PageUp) => self.scrollbase.scroll_up(10), Event::Key(Key::PageUp) => self.scrollbase.scroll_up(10),
_ => return EventResult::Ignored, _ => return EventResult::Ignored,
@ -254,9 +248,7 @@ impl View for TextView {
match (size.w, size.h) { match (size.w, size.h) {
// If we have no directive, ask for a single big line. // If we have no directive, ask for a single big line.
// TODO: what if the text has newlines?? // TODO: what if the text has newlines??
(DimensionRequest::Unknown, DimensionRequest::Unknown) => { (DimensionRequest::Unknown, DimensionRequest::Unknown) => self.get_ideal_size(),
self.get_ideal_size()
}
(DimensionRequest::Fixed(w), _) => { (DimensionRequest::Fixed(w), _) => {
// In a BoxView or something. // In a BoxView or something.
let h = self.get_num_lines(w); let h = self.get_num_lines(w);
@ -291,8 +283,7 @@ impl View for TextView {
// Compute the text rows. // Compute the text rows.
self.rows = LinesIterator::new(&self.content, size.x).collect(); self.rows = LinesIterator::new(&self.content, size.x).collect();
if self.rows.len() > size.y { if self.rows.len() > size.y {
self.rows = LinesIterator::new(&self.content, size.x - 2) self.rows = LinesIterator::new(&self.content, size.x - 2).collect();
.collect();
} }
self.scrollbase.set_heights(size.y, self.rows.len()); self.scrollbase.set_heights(size.y, self.rows.len());
} }