From 5751a293e5f0230574d3ea66841b56e92d33ea66 Mon Sep 17 00:00:00 2001 From: Alexandre Bury Date: Wed, 29 Jun 2016 17:36:20 -0700 Subject: [PATCH] Push most ncurses-specific code into separate trait Prepare backend change. Still not isolated is the color setup in `theme`. --- src/backend.rs | 221 ++++++++++++++++++++++++++++++++++++++ src/event.rs | 124 --------------------- src/lib.rs | 60 ++++------- src/menu.rs | 3 +- src/orientation.rs | 8 +- src/printer.rs | 40 +++---- src/theme.rs | 15 +-- src/view/button.rs | 4 +- src/view/dialog.rs | 20 ++-- src/view/edit_view.rs | 13 +-- src/view/linear_layout.rs | 43 +++----- src/view/request.rs | 4 +- src/view/scroll.rs | 20 ++-- src/view/select_view.rs | 15 +-- src/view/shadow_view.rs | 14 +-- src/view/stack_view.rs | 17 +-- src/view/text_view.rs | 19 +--- 17 files changed, 326 insertions(+), 314 deletions(-) create mode 100644 src/backend.rs diff --git a/src/backend.rs b/src/backend.rs new file mode 100644 index 0000000..258e46e --- /dev/null +++ b/src/backend.rs @@ -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(color: theme::ColorPair, f: F); + fn with_effect(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(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(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), + } +} diff --git a/src/event.rs b/src/event.rs index d554fc1..4b40e54 100644 --- a/src/event.rs +++ b/src/event.rs @@ -3,8 +3,6 @@ use std::fmt; use std::rc::Rc; -use ncurses; - use Cursive; /// Callback is a function that can be triggered by an event. @@ -130,128 +128,6 @@ pub enum Key { 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 { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { diff --git a/src/lib.rs b/src/lib.rs index 20682cc..97470bc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -39,6 +39,10 @@ mod menubar; mod div; mod utf8; +mod backend; + +use backend::{Backend, NcursesBackend}; + use std::any::Any; use std::rc::Rc; use std::collections::HashMap; @@ -49,7 +53,7 @@ use printer::Printer; use view::View; use view::{Selector, StackView}; -use event::{Callback, Event, EventResult, Key, ToEvent}; +use event::{Callback, Event, EventResult, ToEvent}; /// Identifies a screen in the cursive ROOT. 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 { /// Creates a new Cursive root, and initialize ncurses. pub fn new() -> Self { // Default delay is way too long. 25 is imperceptible yet works fine. std::env::set_var("ESCDELAY", "25"); - 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); + B::init(); + let theme = theme::load_default(); // 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 { theme: theme, @@ -148,11 +149,7 @@ impl Cursive { /// /// Call with fps=0 to disable (default value). pub fn set_fps(&self, fps: u32) { - if fps == 0 { - ncurses::timeout(-1); - } else { - ncurses::timeout(1000 / fps as i32); - } + B::set_refresh_rate(fps) } /// Returns a mutable reference to the currently active screen. @@ -235,9 +232,7 @@ impl Cursive { /// Returns the size of the screen, in characters. pub fn screen_size(&self) -> Vec2 { - let mut x: i32 = 0; - let mut y: i32 = 0; - ncurses::getmaxyx(ncurses::stdscr, &mut y, &mut x); + let (x, y) = B::screen_size(); Vec2 { x: x as usize, @@ -258,35 +253,20 @@ impl Cursive { // Draw the currently active screen // If the menubar is active, nothing else can be. let offset = if self.menu.autohide { - 1 - } else { 0 + } else { + 1 }; let selected = self.menu.selected; self.screen_mut() - .draw(&printer.sub_printer(Vec2::new(0, offset), - printer.size, - !selected)); + .draw(&printer.sub_printer(Vec2::new(0, offset), printer.size, !selected)); // Draw the menubar? if self.menu.selected || !self.menu.autohide { self.menu.draw(&printer); } - ncurses::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)) - } + B::refresh(); } /// Runs the event loop. @@ -300,7 +280,7 @@ impl Cursive { // Do we need to redraw everytime? // Probably, actually. // TODO: Do we actually need to clear everytime? - ncurses::clear(); + B::clear(); // TODO: Do we need to re-layout everytime? self.layout(); // TODO: Do we need to redraw every view every time? @@ -309,7 +289,7 @@ impl Cursive { // Wait for next event. // (If set_fps was called, this returns -1 now and then) - let event = Cursive::poll_event(); + let event = B::poll_event(); // Event dispatch order: // * Focused element: @@ -339,6 +319,6 @@ impl Cursive { impl Drop for Cursive { fn drop(&mut self) { - ncurses::endwin(); + B::finish(); } } diff --git a/src/menu.rs b/src/menu.rs index 4550cf3..9963a0a 100644 --- a/src/menu.rs +++ b/src/menu.rs @@ -35,8 +35,7 @@ impl MenuTree { self } - pub fn leaf(&mut self, title: &str, cb: F) - -> &mut Self { + pub fn leaf(&mut self, title: &str, cb: F) -> &mut Self { self.children .push(MenuItem::Leaf(title.to_string(), Rc::new(Box::new(cb)))); self diff --git a/src/orientation.rs b/src/orientation.rs index 64a13cb..e37fe5a 100644 --- a/src/orientation.rs +++ b/src/orientation.rs @@ -44,12 +44,8 @@ impl Orientation { /// For a vertical view, returns (Max(x),Sum(y)). pub fn stack<'a, T: Iterator>(&self, iter: T) -> Vec2 { match *self { - Orientation::Horizontal => { - iter.fold(Vec2::zero(), |a, b| a.stack_horizontal(b)) - } - Orientation::Vertical => { - iter.fold(Vec2::zero(), |a, b| a.stack_vertical(b)) - } + Orientation::Horizontal => iter.fold(Vec2::zero(), |a, b| a.stack_horizontal(b)), + Orientation::Vertical => iter.fold(Vec2::zero(), |a, b| a.stack_vertical(b)), } } } diff --git a/src/printer.rs b/src/printer.rs index 2401bed..2cd7e43 100644 --- a/src/printer.rs +++ b/src/printer.rs @@ -2,9 +2,11 @@ use std::cmp::min; -use ncurses; +use backend::Backend; -use theme::{ColorPair, Theme}; +use B; + +use theme::{ColorPair, Theme, Effect}; use vec::{ToVec2, Vec2}; /// Convenient interface to draw on a subset of the screen. @@ -49,11 +51,9 @@ impl Printer { let p = p + self.offset; if text.contains('%') { - ncurses::mvprintw(p.y as i32, - p.x as i32, - &text.replace("%", "%%")); + B::print_at((p.x, p.y), &text.replace("%", "%%")); } 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; 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; 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(&self, c: ColorPair, f: F) - where F: Fn(&Printer) + where F: FnOnce(&Printer) { - self.with_style(ncurses::COLOR_PAIR(c.ncurses_id()), f); - ncurses::attron(ncurses::COLOR_PAIR(ColorPair::Primary.ncurses_id())); + B::with_color(c, || f(self)); } /// Same as `with_color`, but apply a ncurses style instead, /// like `ncurses::A_BOLD()` or `ncurses::A_REVERSE()`. /// /// Will probably use a cursive enum some day. - pub fn with_style(&self, style: ncurses::attr_t, f: F) - where F: Fn(&Printer) + pub fn with_effect(&self, effect: Effect, f: F) + where F: FnOnce(&Printer) { - ncurses::attron(style); - f(self); - ncurses::attroff(style); + B::with_effect(effect, || f(self)); } /// Prints a rectangular box. @@ -138,12 +135,8 @@ impl Printer { self.print_hline(start_v + (1, 0), size_v.x - 1, "─"); self.print_vline(start_v + (0, 1), size_v.y - 1, "│"); - self.print_hline(start_v + (1, 0) + size_v.keep_y(), - size_v.x - 1, - "─"); - self.print_vline(start_v + (0, 1) + size_v.keep_x(), - size_v.y - 1, - "│"); + self.print_hline(start_v + (1, 0) + size_v.keep_y(), size_v.x - 1, "─"); + self.print_vline(start_v + (0, 1) + size_v.keep_x(), size_v.y - 1, "│"); } pub fn print_hdelim(&self, start: T, len: usize) { @@ -154,8 +147,7 @@ impl Printer { } /// Returns a printer on a subset of this one's area. - pub fn sub_printer(&self, offset: S, size: S, focused: bool) - -> Printer { + pub fn sub_printer(&self, offset: S, size: S, focused: bool) -> Printer { let offset_v = offset.to_vec2(); Printer { offset: self.offset + offset_v, diff --git a/src/theme.rs b/src/theme.rs index 068989a..7325250 100644 --- a/src/theme.rs +++ b/src/theme.rs @@ -8,6 +8,11 @@ use std::path::Path; use ncurses; use toml; +pub enum Effect { + Simple, + Reverse, +} + /// Represents the color of a character and its background. #[derive(Clone,Copy)] pub enum ColorPair { @@ -97,14 +102,10 @@ impl Theme { fn apply(&self) { Theme::apply_color(ColorPair::Background, - &self.colors.background, + &self.colors.view, &self.colors.background); - 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::Shadow, &self.colors.shadow, &self.colors.shadow); + Theme::apply_color(ColorPair::Primary, &self.colors.primary, &self.colors.view); Theme::apply_color(ColorPair::Secondary, &self.colors.secondary, &self.colors.view); diff --git a/src/view/button.rs b/src/view/button.rs index 0e81be5..c4d5728 100644 --- a/src/view/button.rs +++ b/src/view/button.rs @@ -50,9 +50,7 @@ impl View for Button { fn on_event(&mut self, event: Event) -> EventResult { match event { // 10 is the ascii code for '\n', that is the return key - Event::Key(Key::Enter) => { - EventResult::Consumed(Some(self.callback.clone())) - } + Event::Key(Key::Enter) => EventResult::Consumed(Some(self.callback.clone())), _ => EventResult::Ignored, } } diff --git a/src/view/dialog.rs b/src/view/dialog.rs index 20ae890..5e77d4f 100644 --- a/src/view/dialog.rs +++ b/src/view/dialog.rs @@ -121,9 +121,7 @@ impl View for Dialog { }; let overhead = self.padding + self.borders; let mut offset = overhead.left + - self.align.h.get_offset(width, - printer.size.x - - overhead.horizontal()); + self.align.h.get_offset(width, printer.size.x - overhead.horizontal()); let y = printer.size.y - self.padding.bottom - self.borders.bottom - 1; for (i, button) in self.buttons.iter_mut().enumerate() { @@ -139,12 +137,10 @@ impl View for Dialog { } // What do we have left? - let inner_size = printer.size - Vec2::new(0, height) - - self.borders.combined() - + let inner_size = printer.size - Vec2::new(0, height) - self.borders.combined() - self.padding.combined(); - self.content.draw(&printer.sub_printer(self.borders.top_left() + - self.padding.top_left(), + self.content.draw(&printer.sub_printer(self.borders.top_left() + self.padding.top_left(), inner_size, self.focus == Focus::Content)); @@ -156,16 +152,14 @@ impl View for Dialog { printer.print((x - 2, 0), "┤ "); printer.print((x + len, 0), " ├"); - printer.with_color(ColorPair::TitlePrimary, - |p| p.print((x, 0), &self.title)); + printer.with_color(ColorPair::TitlePrimary, |p| p.print((x, 0), &self.title)); } } fn get_min_size(&self, req: SizeRequest) -> Vec2 { // Padding and borders are not available for kids. - let content_req = req.reduced(self.padding.combined() + - self.borders.combined()); + let content_req = req.reduced(self.padding.combined() + self.borders.combined()); let content_size = self.content.get_min_size(content_req); let mut buttons_size = Vec2::new(0, 0); @@ -251,8 +245,8 @@ impl View for Dialog { } // Left and Right move to other buttons Event::Key(Key::Right) if i + 1 < - self.buttons - .len() => { + self.buttons + .len() => { self.focus = Focus::Button(i + 1); EventResult::Consumed(None) } diff --git a/src/view/edit_view.rs b/src/view/edit_view.rs index e055a94..b65d42c 100644 --- a/src/view/edit_view.rs +++ b/src/view/edit_view.rs @@ -1,9 +1,8 @@ -use ncurses; use unicode_segmentation::UnicodeSegmentation; use std::cmp::min; -use theme::ColorPair; +use theme::{ColorPair, Effect}; use vec::Vec2; use view::{IdView, SizeRequest, View}; use event::*; @@ -87,13 +86,12 @@ impl View for EditView { // let style = if focused { color::HIGHLIGHT } else { color::HIGHLIGHT_INACTIVE }; let len = self.content.chars().count(); printer.with_color(ColorPair::Secondary, |printer| { - printer.with_style(ncurses::A_REVERSE(), |printer| { + printer.with_effect(Effect::Reverse, |printer| { if len < self.last_length { printer.print((0, 0), &self.content); printer.print_hline((len, 0), printer.size.x - len, "_"); } 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); let content = &self.content[self.offset..visible_end]; printer.print((0, 0), content); @@ -151,10 +149,7 @@ impl View for EditView { Key::Home => self.cursor = 0, Key::End => self.cursor = self.content.chars().count(), 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); diff --git a/src/view/linear_layout.rs b/src/view/linear_layout.rs index 42bc6c1..b8fce25 100644 --- a/src/view/linear_layout.rs +++ b/src/view/linear_layout.rs @@ -113,9 +113,7 @@ impl View for LinearLayout { // Use pre-computed sizes let mut offset = Vec2::zero(); for (i, child) in self.children.iter_mut().enumerate() { - child.view.draw(&printer.sub_printer(offset, - child.size, - i == self.focus)); + child.view.draw(&printer.sub_printer(offset, child.size, i == self.focus)); *self.orientation.get_ref(&mut offset) += self.orientation .get(&child.size); @@ -130,9 +128,7 @@ impl View for LinearLayout { }; let min_sizes: Vec = self.children .iter() - .map(|child| { - child.view.get_min_size(req) - }) + .map(|child| child.view.get_min_size(req)) .collect(); let min_size = self.orientation.stack(min_sizes.iter()); @@ -153,14 +149,12 @@ impl View for LinearLayout { }; - 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; *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); child.size = child_size; child.view.layout(child_size); } @@ -194,34 +188,27 @@ impl View for LinearLayout { self.focus -= 1; EventResult::Consumed(None) } - Event::Key(Key::ShiftTab) if self.focus + 1 < - self.children.len() => { + Event::Key(Key::ShiftTab) if self.focus + 1 < self.children.len() => { self.focus += 1; EventResult::Consumed(None) } - Event::Key(Key::Left) if self.orientation == - Orientation::Horizontal && - self.focus > 0 => { + Event::Key(Key::Left) if self.orientation == Orientation::Horizontal && + self.focus > 0 => { self.focus -= 1; EventResult::Consumed(None) } - Event::Key(Key::Up) if self.orientation == - Orientation::Vertical && - self.focus > 0 => { + Event::Key(Key::Up) if self.orientation == Orientation::Vertical && + self.focus > 0 => { self.focus -= 1; EventResult::Consumed(None) } - Event::Key(Key::Right) if self.orientation == - Orientation::Horizontal && - self.focus + 1 < - self.children.len() => { + Event::Key(Key::Right) if self.orientation == Orientation::Horizontal && + self.focus + 1 < self.children.len() => { self.focus += 1; EventResult::Consumed(None) } - Event::Key(Key::Down) if self.orientation == - Orientation::Vertical && - self.focus + 1 < - self.children.len() => { + Event::Key(Key::Down) if self.orientation == Orientation::Vertical && + self.focus + 1 < self.children.len() => { self.focus += 1; EventResult::Consumed(None) } diff --git a/src/view/request.rs b/src/view/request.rs index d708781..c30a74d 100644 --- a/src/view/request.rs +++ b/src/view/request.rs @@ -16,9 +16,7 @@ impl DimensionRequest { pub fn reduced(self, offset: usize) -> Self { match self { DimensionRequest::Fixed(w) => DimensionRequest::Fixed(w - offset), - DimensionRequest::AtMost(w) => { - DimensionRequest::AtMost(w - offset) - } + DimensionRequest::AtMost(w) => DimensionRequest::AtMost(w - offset), DimensionRequest::Unknown => DimensionRequest::Unknown, } } diff --git a/src/view/scroll.rs b/src/view/scroll.rs index 5e3afeb..dc2759f 100644 --- a/src/view/scroll.rs +++ b/src/view/scroll.rs @@ -27,8 +27,7 @@ impl ScrollBase { self.content_height = content_height; if self.scrollable() { - self.start_line = min(self.start_line, - self.content_height - self.view_height); + self.start_line = min(self.start_line, self.content_height - self.view_height); } else { 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. pub fn scroll_down(&mut self, n: usize) { - self.start_line = min(self.start_line + n, - self.content_height - self.view_height); + self.start_line = min(self.start_line + n, self.content_height - self.view_height); } /// 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) { // 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 { @@ -114,9 +111,7 @@ impl ScrollBase { for y in 0..max_y { // Y is the actual coordinate of the line. // The item ID is then Y + self.start_line - line_drawer(&printer.sub_printer(Vec2::new(0, y), - Vec2::new(w, 1), - true), + line_drawer(&printer.sub_printer(Vec2::new(0, y), Vec2::new(w, 1), true), 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). // (ratio) * max_height // Where ratio is ({start or end} / content.height) - let height = max(1, - self.view_height * self.view_height / - self.content_height); + let height = max(1, self.view_height * self.view_height / self.content_height); // Number of different possible positions let steps = self.view_height - height + 1; // Now - let start = steps * self.start_line / - (1 + self.content_height - self.view_height); + let start = steps * self.start_line / (1 + self.content_height - self.view_height); let color = if printer.focused { ColorPair::Highlight diff --git a/src/view/select_view.rs b/src/view/select_view.rs index 592cbff..5bd5ca8 100644 --- a/src/view/select_view.rs +++ b/src/view/select_view.rs @@ -128,9 +128,7 @@ impl View for SelectView { let h = self.items.len(); let offset = self.align.v.get_offset(h, printer.size.y); - let printer = &printer.sub_printer(Vec2::new(0, offset), - printer.size, - true); + let printer = &printer.sub_printer(Vec2::new(0, offset), printer.size, true); self.scrollbase.draw(printer, |printer, i| { let style = if i == self.focus { @@ -181,12 +179,9 @@ impl View for SelectView { fn on_event(&mut self, event: Event) -> EventResult { match event { Event::Key(Key::Up) if self.focus > 0 => self.focus -= 1, - Event::Key(Key::Down) if self.focus + 1 < - self.items.len() => self.focus += 1, + Event::Key(Key::Down) if self.focus + 1 < self.items.len() => self.focus += 1, Event::Key(Key::PageUp) => self.focus -= min(self.focus, 10), - Event::Key(Key::PageDown) => { - self.focus = min(self.focus + 10, self.items.len() - 1) - } + Event::Key(Key::PageDown) => self.focus = min(self.focus + 10, self.items.len() - 1), Event::Key(Key::Home) => self.focus = 0, Event::Key(Key::End) => self.focus = self.items.len() - 1, Event::Key(Key::Enter) if self.select_cb.is_some() => { @@ -205,9 +200,7 @@ impl View for SelectView { let iter = self.items.iter().chain(self.items.iter()); if let Some((i, _)) = iter.enumerate() .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 self.focus = i % self.items.len(); diff --git a/src/view/shadow_view.rs b/src/view/shadow_view.rs index 58d0d7d..3ad93db 100644 --- a/src/view/shadow_view.rs +++ b/src/view/shadow_view.rs @@ -30,16 +30,12 @@ impl ViewWrapper for ShadowView { fn wrap_draw(&mut self, printer: &Printer) { - printer.with_color(ColorPair::Primary, |printer| { - // Draw the view background - for y in 1..printer.size.y - 1 { - printer.print_hline((1, y), printer.size.x - 2, " "); - } - }); + // Draw the view background + for y in 1..printer.size.y - 1 { + printer.print_hline((1, y), printer.size.x - 2, " "); + } - self.view.draw(&printer.sub_printer(Vec2::new(1, 1), - printer.size - (2, 2), - true)); + self.view.draw(&printer.sub_printer(Vec2::new(1, 1), printer.size - (2, 2), true)); let h = printer.size.y - 1; let w = printer.size.x - 1; diff --git a/src/view/stack_view.rs b/src/view/stack_view.rs index cae40e1..9b2d9cf 100644 --- a/src/view/stack_view.rs +++ b/src/view/stack_view.rs @@ -4,6 +4,7 @@ use vec::Vec2; use view::{DimensionRequest, Selector, ShadowView, SizeRequest, View}; use event::{Event, EventResult}; use printer::Printer; +use theme::ColorPair; /// Simple stack of views. /// Only the top-most view is active and can receive input. @@ -48,13 +49,15 @@ impl StackView { impl View for StackView { fn draw(&mut self, printer: &Printer) { let last = self.layers.len(); - for (i, v) in self.layers.iter_mut().enumerate() { - // Center the view - let size = v.size; - let offset = (printer.size - size) / 2; - // TODO: only draw focus for the top view - v.view.draw(&printer.sub_printer(offset, size, i + 1 == last)); - } + printer.with_color(ColorPair::Primary, |printer| { + for (i, v) in self.layers.iter_mut().enumerate() { + // Center the view + let size = v.size; + let offset = (printer.size - size) / 2; + // TODO: only draw focus for the top view + v.view.draw(&printer.sub_printer(offset, size, i + 1 == last)); + } + }); } fn on_event(&mut self, event: Event) -> EventResult { diff --git a/src/view/text_view.rs b/src/view/text_view.rs index 4bc9693..ce10190 100644 --- a/src/view/text_view.rs +++ b/src/view/text_view.rs @@ -214,9 +214,7 @@ impl View for TextView { let h = self.rows.len(); let offset = self.align.v.get_offset(h, printer.size.y); - let printer = &printer.sub_printer(Vec2::new(0, offset), - printer.size, - true); + let printer = &printer.sub_printer(Vec2::new(0, offset), printer.size, true); self.scrollbase.draw(printer, |printer, i| { let row = &self.rows[i]; @@ -235,13 +233,9 @@ impl View for TextView { match event { Event::Key(Key::Home) => self.scrollbase.scroll_top(), Event::Key(Key::End) => self.scrollbase.scroll_bottom(), - Event::Key(Key::Up) if self.scrollbase.can_scroll_up() => { - self.scrollbase.scroll_up(1) - } + Event::Key(Key::Up) if self.scrollbase.can_scroll_up() => self.scrollbase.scroll_up(1), Event::Key(Key::Down) if self.scrollbase - .can_scroll_down() => { - self.scrollbase.scroll_down(1) - } + .can_scroll_down() => self.scrollbase.scroll_down(1), Event::Key(Key::PageDown) => self.scrollbase.scroll_down(10), Event::Key(Key::PageUp) => self.scrollbase.scroll_up(10), _ => return EventResult::Ignored, @@ -254,9 +248,7 @@ impl View for TextView { match (size.w, size.h) { // If we have no directive, ask for a single big line. // TODO: what if the text has newlines?? - (DimensionRequest::Unknown, DimensionRequest::Unknown) => { - self.get_ideal_size() - } + (DimensionRequest::Unknown, DimensionRequest::Unknown) => self.get_ideal_size(), (DimensionRequest::Fixed(w), _) => { // In a BoxView or something. let h = self.get_num_lines(w); @@ -291,8 +283,7 @@ impl View for TextView { // Compute the text rows. self.rows = LinesIterator::new(&self.content, size.x).collect(); if self.rows.len() > size.y { - self.rows = LinesIterator::new(&self.content, size.x - 2) - .collect(); + self.rows = LinesIterator::new(&self.content, size.x - 2).collect(); } self.scrollbase.set_heights(size.y, self.rows.len()); }