#![warn(missing_docs)]
#![allow(dead_code)]
#![deny(missing_debug_implementations)]
extern crate pancurses;
pub use pancurses::Input;
use std::panic::*;
use std::iter::Iterator;
use std::sync::atomic::{AtomicBool, Ordering, ATOMIC_BOOL_INIT};
#[allow(non_upper_case_globals)]
static curses_is_on: AtomicBool = ATOMIC_BOOL_INIT;
#[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord, Hash)]
pub enum CursorVisibility {
Invisible,
Visible,
HighlyVisible,
}
impl Default for CursorVisibility {
fn default() -> Self {
CursorVisibility::Visible
}
}
#[allow(missing_docs)]
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
pub enum Color {
Black,
Red,
Green,
Yellow,
Blue,
Magenta,
Cyan,
White,
}
type ColorIter = std::iter::Cloned<std::slice::Iter<'static, Color>>;
impl Color {
pub fn color_iterator() -> ColorIter {
use Color::*;
#[allow(non_upper_case_globals)]
static colors: &[Color] = &[Black, Red, Green, Yellow, Blue, Magenta, Cyan, White];
colors.iter().cloned()
}
}
fn color_to_i16(color: Color) -> i16 {
use Color::*;
match color {
Black => 0,
Red => 1,
Green => 2,
Yellow => 3,
Blue => 4,
Magenta => 5,
Cyan => 6,
White => 7,
}
}
fn i16_to_color(val: i16) -> Option<Color> {
use Color::*;
match val {
0 => Some(Black),
1 => Some(Red),
2 => Some(Green),
3 => Some(Yellow),
4 => Some(Blue),
5 => Some(Magenta),
6 => Some(Cyan),
7 => Some(White),
_ => None,
}
}
#[cfg(test)]
mod color_tests {
use super::*;
#[test]
fn test_color_i32_conversion_identity() {
use Color::*;
let colors = [Black, Red, Green, Yellow, Blue, Magenta, Cyan, White];
for &color in colors.iter() {
if i16_to_color(color_to_i16(color)).unwrap() != color {
panic!(color);
}
}
}
#[test]
fn test_color_i32_matches_color_constants() {
use Color::*;
assert!(color_to_i16(Black) == pancurses::COLOR_BLACK);
assert!(color_to_i16(Red) == pancurses::COLOR_RED);
assert!(color_to_i16(Green) == pancurses::COLOR_GREEN);
assert!(color_to_i16(Yellow) == pancurses::COLOR_YELLOW);
assert!(color_to_i16(Blue) == pancurses::COLOR_BLUE);
assert!(color_to_i16(Magenta) == pancurses::COLOR_MAGENTA);
assert!(color_to_i16(Cyan) == pancurses::COLOR_CYAN);
assert!(color_to_i16(White) == pancurses::COLOR_WHITE);
}
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
pub struct ColorPair(i16);
impl ColorPair {
pub fn new(fg: Color, bg: Color) -> Self {
let fgi = color_to_i16(fg);
let bgi = color_to_i16(bg);
ColorPair(fgbg_pairid(fgi, bgi))
}
}
impl Default for ColorPair {
fn default() -> Self {
Self::new(Color::White, Color::Black)
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum InputMode {
Blocking,
NonBlocking,
TimeLimit(i32),
}
impl Default for InputMode {
fn default() -> Self {
InputMode::Blocking
}
}
fn fgbg_pairid(fg: i16, bg: i16) -> i16 {
1 + (8 * fg + bg)
}
fn to_bool(curses_bool: i32) -> bool {
curses_bool == pancurses::OK
}
#[derive(Debug)]
pub struct EasyCurses {
pub win: pancurses::Window,
color_support: bool,
}
impl Drop for EasyCurses {
fn drop(&mut self) {
pancurses::endwin();
curses_is_on.store(false, Ordering::SeqCst);
}
}
impl EasyCurses {
pub fn initialize_system() -> Option<Self> {
if !curses_is_on.compare_and_swap(false, true, Ordering::SeqCst) {
let w = pancurses::initscr();
let color_support = if pancurses::has_colors() {
to_bool(pancurses::start_color())
} else {
false
};
if color_support {
let color_count = pancurses::COLORS();
let pair_count = pancurses::COLOR_PAIRS();
for fg in Color::color_iterator() {
for bg in Color::color_iterator() {
let fgi = color_to_i16(fg);
let bgi = color_to_i16(bg);
let pair_id = fgbg_pairid(fgi, bgi);
assert!(fgi <= color_count as i16);
assert!(bgi <= color_count as i16);
assert!(pair_id <= pair_count as i16);
pancurses::init_pair(pair_id, fgi, bgi);
}
}
}
Some(EasyCurses {
win: w,
color_support: color_support,
})
} else {
None
}
}
pub fn set_title_win32(&mut self, title: &str) {
pancurses::set_title(title);
}
pub fn set_cursor_visibility(&mut self, vis: CursorVisibility) -> Option<CursorVisibility> {
use CursorVisibility::*;
let result = pancurses::curs_set(match vis {
Invisible => 0,
Visible => 1,
HighlyVisible => 2,
});
match result {
0 => Some(Invisible),
1 => Some(Visible),
2 => Some(HighlyVisible),
_ => None,
}
}
pub fn set_character_break(&mut self, cbreak: bool) -> bool {
if cbreak {
to_bool(pancurses::cbreak())
} else {
to_bool(pancurses::nocbreak())
}
}
pub fn set_keypad_enabled(&mut self, use_keypad: bool) -> bool {
to_bool(self.win.keypad(use_keypad))
}
pub fn set_echo(&mut self, echoing: bool) -> bool {
to_bool(if echoing {
pancurses::echo()
} else {
pancurses::noecho()
})
}
pub fn is_color_terminal(&self) -> bool {
self.color_support
}
pub fn set_color_pair(&mut self, pair: ColorPair) {
if self.color_support {
self.win.color_set(pair.0);
}
}
pub fn set_bold(&mut self, bold_on: bool) -> bool {
to_bool(if bold_on {
self.win.attron(pancurses::Attribute::Bold)
} else {
self.win.attroff(pancurses::Attribute::Bold)
})
}
pub fn set_underline(&mut self, underline_on: bool) -> bool {
to_bool(if underline_on {
self.win.attron(pancurses::Attribute::Underline)
} else {
self.win.attroff(pancurses::Attribute::Underline)
})
}
pub fn get_row_col_count(&mut self) -> (i32, i32) {
self.win.get_max_yx()
}
pub fn move_rc(&mut self, row: i32, col: i32) -> bool {
to_bool(self.win.mv(row, col))
}
pub fn get_cursor_rc(&self) -> (i32, i32) {
self.win.get_cur_yx()
}
pub fn move_xy(&mut self, x: i32, y: i32) -> bool {
let row_count = self.win.get_max_y();
to_bool(self.win.mv(row_count - (y + 1), x))
}
pub fn get_cursor_xy(&self) -> (i32, i32) {
let row_count = self.win.get_max_y();
let (row, col) = self.win.get_cur_yx();
(col, row_count - (row + 1))
}
pub fn set_scrolling(&mut self, scrolling: bool) -> bool {
to_bool(self.win.scrollok(scrolling))
}
pub fn set_scroll_region(&mut self, top: i32, bottom: i32) -> bool {
to_bool(self.win.setscrreg(top, bottom))
}
pub fn print<S: AsRef<str>>(&mut self, asref: S) -> bool {
asref.as_ref().chars().all(|c| self.print_char(c))
}
pub fn print_char(&mut self, character: char) -> bool {
to_bool(self.win.addch(character))
}
pub fn insert_char(&mut self, character: char) -> bool {
to_bool(self.win.insch(character))
}
pub fn delete_char(&mut self) -> bool {
to_bool(self.win.delch())
}
pub fn delete_line(&mut self) -> bool {
to_bool(self.win.deleteln())
}
pub fn clear(&mut self) -> bool {
to_bool(self.win.clear())
}
pub fn refresh(&mut self) -> bool {
to_bool(self.win.refresh())
}
pub fn beep(&mut self) {
pancurses::beep();
}
pub fn flash(&mut self) {
pancurses::flash();
}
pub fn set_input_mode(&mut self, mode: InputMode) {
use InputMode::*;
use std::cmp::max;
match mode {
Blocking => self.win.timeout(-1),
NonBlocking => self.win.timeout(0),
TimeLimit(time) => self.win.timeout(max(time, 1)),
};
}
pub fn get_input(&mut self) -> Option<pancurses::Input> {
self.win.getch()
}
pub fn flush_input(&mut self) {
pancurses::flushinp();
}
pub fn un_get_input(&mut self, input: &pancurses::Input) -> bool {
to_bool(self.win.ungetch(input))
}
}
pub fn preserve_panic_message<F: FnOnce(&mut EasyCurses) -> R + UnwindSafe, R>(
user_function: F,
) -> Result<R, Option<String>> {
let result = catch_unwind(|| {
let mut easy = EasyCurses::initialize_system().expect("Curses double-initialization.");
user_function(&mut easy)
});
result.map_err(|e| match e.downcast_ref::<&str>() {
Some(andstr) => Some(andstr.to_string()),
None => match e.downcast_ref::<String>() {
Some(string) => Some(string.to_string()),
None => None,
},
})
}