extern crate gag;
extern crate num_traits;
extern crate termbox_sys as termbox;
#[macro_use] extern crate bitflags;
pub use self::style::{Style, RB_BOLD, RB_UNDERLINE, RB_REVERSE, RB_NORMAL};
use std::error::Error;
use std::fmt;
use std::io;
use std::char;
use std::default::Default;
use std::ops::FnOnce;
use std::sync::Mutex;
use num_traits::FromPrimitive;
use termbox::RawEvent;
use std::os::raw::c_int;
use gag::Hold;
use std::time::Duration;
pub mod keyboard;
pub mod mouse;
pub use self::running::running;
pub use keyboard::Key;
pub use mouse::Mouse;
#[derive(Clone, Copy, Debug)]
pub enum Event {
KeyEventRaw(u8, u16, u32),
KeyEvent(Key),
ResizeEvent(i32, i32),
MouseEvent(Mouse, i32, i32),
NoEvent
}
#[derive(Clone, Copy, Debug)]
pub enum InputMode {
Current = 0x00,
Esc = 0x01,
Alt = 0x02,
EscMouse = 0x05,
AltMouse = 0x06
}
#[derive(Clone, Copy, Debug)]
pub enum OutputMode {
Current = 0,
Normal = 1,
EightBit = 2, WebSafe = 3, Grayscale = 4,
}
#[derive(Clone, Copy, PartialEq, Debug)]
pub enum Color {
Black,
Red,
Green,
Yellow,
Blue,
Magenta,
Cyan,
White,
Byte(u16),
Default,
}
impl Color {
pub fn as_256color(&self) -> u16 {
match *self {
Color::Black => 0x00,
Color::Red => 0x01,
Color::Green => 0x02,
Color::Yellow => 0x03,
Color::Blue => 0x04,
Color::Magenta => 0x05,
Color::Cyan => 0x06,
Color::White => 0x07,
Color::Byte(b) => b,
Color::Default => panic!("Attempted to cast default color to byte"),
}
}
pub fn as_16color(&self) -> u16 {
match *self {
Color::Default => 0x00,
Color::Black => 0x01,
Color::Red => 0x02,
Color::Green => 0x03,
Color::Yellow => 0x04,
Color::Blue => 0x05,
Color::Magenta => 0x06,
Color::Cyan => 0x07,
Color::White => 0x08,
Color::Byte(b) => panic!("Attempted to cast color byte {} to 16 color mode", b),
}
}
}
impl Default for Color {
fn default() -> Color {
Color::Black
}
}
mod style {
bitflags! {
#[repr(C)]
flags Style: u16 {
const TB_NORMAL_COLOR = 0x000F,
const RB_BOLD = 0x0100,
const RB_UNDERLINE = 0x0200,
const RB_REVERSE = 0x0400,
const RB_NORMAL = 0x0000,
const TB_ATTRIB = RB_BOLD.bits | RB_UNDERLINE.bits | RB_REVERSE.bits,
}
}
impl Style {
pub fn from_color(color: super::Color) -> Style {
Style { bits: color.as_16color() & TB_NORMAL_COLOR.bits }
}
pub fn from_256color(color: super::Color) -> Style {
Style { bits: color.as_256color() }
}
}
}
const NIL_RAW_EVENT: RawEvent = RawEvent { etype: 0, emod: 0, key: 0, ch: 0, w: 0, h: 0, x: 0, y: 0 };
#[derive(Debug)]
pub enum EventError {
TermboxError,
Unknown(isize),
}
impl fmt::Display for EventError {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
write!(fmt, "{}", self.description())
}
}
impl Error for EventError {
fn description(&self) -> &str {
match *self {
EventError::TermboxError => "Error in Termbox",
EventError::Unknown(_) => "Unknown error in Termbox",
}
}
}
impl FromPrimitive for EventError {
fn from_i64(n: i64) -> Option<EventError> {
match n {
-1 => Some(EventError::TermboxError),
n => Some(EventError::Unknown(n as isize)),
}
}
fn from_u64(n: u64) -> Option<EventError> {
Some(EventError::Unknown(n as isize))
}
}
pub type EventResult = Result<Event, EventError>;
fn unpack_event(ev_type: c_int, ev: &RawEvent, raw: bool) -> EventResult {
match ev_type {
0 => Ok(Event::NoEvent),
1 => Ok(
if raw {
Event::KeyEventRaw(ev.emod, ev.key, ev.ch)
} else {
let k = match ev.key {
0 => char::from_u32(ev.ch).map(|c| Key::Char(c)),
a => Key::from_code(a),
};
if let Some(key) = k {
Event::KeyEvent(key)
}
else {
Event::KeyEvent(Key::Unknown(ev.key))
}
}),
2 => Ok(Event::ResizeEvent(ev.w, ev.h)),
3 => {
let mouse = Mouse::from_code(ev.key).unwrap_or(Mouse::Left);
Ok(Event::MouseEvent(mouse, ev.x, ev.y))
},
n => Err(FromPrimitive::from_isize(n as isize).unwrap()),
}
}
#[derive(Debug)]
pub enum InitError {
BufferStderrFailed(io::Error),
AlreadyOpen,
UnsupportedTerminal,
FailedToOpenTTy,
PipeTrapError,
Unknown(isize),
}
impl fmt::Display for InitError {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
write!(fmt, "{}", self.description())
}
}
impl Error for InitError {
fn description(&self) -> &str {
match *self {
InitError::BufferStderrFailed(_) => "Could not redirect stderr",
InitError::AlreadyOpen => "RustBox is already open",
InitError::UnsupportedTerminal => "Unsupported terminal",
InitError::FailedToOpenTTy => "Failed to open TTY",
InitError::PipeTrapError => "Pipe trap error",
InitError::Unknown(_) => "Unknown error from Termbox",
}
}
fn cause(&self) -> Option<&Error> {
match *self {
InitError::BufferStderrFailed(ref e) => Some(e),
_ => None
}
}
}
impl FromPrimitive for InitError {
fn from_i64(n: i64) -> Option<InitError> {
match n {
-1 => Some(InitError::UnsupportedTerminal),
-2 => Some(InitError::FailedToOpenTTy),
-3 => Some(InitError::PipeTrapError),
n => Some(InitError::Unknown(n as isize)),
}
}
fn from_u64(n: u64) -> Option<InitError> {
Some(InitError::Unknown(n as isize))
}
}
#[allow(missing_copy_implementations)]
pub struct RustBox {
_stderr: Option<Hold>,
_running: running::RunningGuard,
output_mode: OutputMode,
input_lock: Mutex<()>,
output_lock: Mutex<()>,
}
#[derive(Clone, Copy,Debug)]
pub struct InitOptions {
pub input_mode: InputMode,
pub output_mode: OutputMode,
pub buffer_stderr: bool,
}
impl Default for InitOptions {
fn default() -> Self {
InitOptions {
input_mode: InputMode::Current,
output_mode: OutputMode::Current,
buffer_stderr: false,
}
}
}
mod running {
use std::sync::atomic::{self, AtomicBool};
static RUSTBOX_RUNNING: AtomicBool = atomic::ATOMIC_BOOL_INIT;
pub fn running() -> bool {
RUSTBOX_RUNNING.load(atomic::Ordering::SeqCst)
}
#[allow(missing_copy_implementations)]
pub struct RunningGuard(());
pub fn run() -> Option<RunningGuard> {
if RUSTBOX_RUNNING.swap(true, atomic::Ordering::SeqCst) {
None
} else {
Some(RunningGuard(()))
}
}
impl Drop for RunningGuard {
fn drop(&mut self) {
RUSTBOX_RUNNING.store(false, atomic::Ordering::SeqCst);
}
}
}
impl RustBox {
pub fn init(opts: InitOptions) -> Result<RustBox, InitError> {
let running = match running::run() {
Some(r) => r,
None => return Err(InitError::AlreadyOpen),
};
let stderr = if opts.buffer_stderr {
Some(try!(Hold::stderr().map_err(|e| InitError::BufferStderrFailed(e))))
} else {
None
};
let mut rb = unsafe { match termbox::tb_init() {
0 => RustBox {
_stderr: stderr,
_running: running,
output_mode: OutputMode::Current,
input_lock: Mutex::new(()),
output_lock: Mutex::new(()),
},
res => {
return Err(FromPrimitive::from_isize(res as isize).unwrap())
}
}};
match opts.input_mode {
InputMode::Current => (),
_ => rb.set_input_mode(opts.input_mode),
}
match opts.output_mode {
OutputMode::Current => (),
_ => rb.set_output_mode(opts.output_mode),
}
Ok(rb)
}
pub fn width(&self) -> usize {
let _lock = self.output_lock.lock();
unsafe { termbox::tb_width() as usize }
}
pub fn height(&self) -> usize {
let _lock = self.output_lock.lock();
unsafe { termbox::tb_height() as usize }
}
pub fn clear(&self) {
let _lock = self.output_lock.lock();
unsafe { termbox::tb_clear() }
}
pub fn present(&self) {
let _lock = self.output_lock.lock();
unsafe { termbox::tb_present() }
}
pub fn set_cursor(&self, x: isize, y: isize) {
let _lock = self.output_lock.lock();
unsafe { termbox::tb_set_cursor(x as c_int, y as c_int) }
}
pub unsafe fn change_cell(&self, x: usize, y: usize, ch: u32, fg: u16, bg: u16) {
termbox::tb_change_cell(x as c_int, y as c_int, ch, fg, bg)
}
pub fn print(&self, x: usize, y: usize, sty: Style, fg: Color, bg: Color, s: &str) {
let _lock = self.output_lock.lock();
let fg_int;
let bg_int;
match self.output_mode {
OutputMode::EightBit => {
fg_int = Style::from_256color(fg) | (sty & style::TB_ATTRIB);
bg_int = Style::from_256color(bg);
},
_ => {
fg_int = Style::from_color(fg) | (sty & style::TB_ATTRIB);
bg_int = Style::from_color(bg);
}
}
for (i, ch) in s.chars().enumerate() {
unsafe {
self.change_cell(x+i, y, ch as u32, fg_int.bits(), bg_int.bits());
}
}
}
pub fn print_char(&self, x: usize, y: usize, sty: Style, fg: Color, bg: Color, ch: char) {
let _lock = self.output_lock.lock();
let fg_int;
let bg_int;
match self.output_mode {
OutputMode::EightBit => {
fg_int = Style::from_256color(fg) | (sty & style::TB_ATTRIB);
bg_int = Style::from_256color(bg);
},
_ => {
fg_int = Style::from_color(fg) | (sty & style::TB_ATTRIB);
bg_int = Style::from_color(bg);
}
}
unsafe {
self.change_cell(x, y, ch as u32, fg_int.bits(), bg_int.bits());
}
}
pub fn poll_event(&self, raw: bool) -> EventResult {
let _lock = self.input_lock.lock();
let mut ev = NIL_RAW_EVENT;
let rc = unsafe {
termbox::tb_poll_event(&mut ev)
};
unpack_event(rc, &ev, raw)
}
pub fn peek_event(&self, timeout: Duration, raw: bool) -> EventResult {
let _lock = self.input_lock.lock();
let mut ev = NIL_RAW_EVENT;
let rc = unsafe {
termbox::tb_peek_event(&mut ev, (timeout.as_secs() * 1000 + timeout.subsec_nanos() as u64 / 1000000) as c_int)
};
unpack_event(rc, &ev, raw)
}
pub fn set_input_mode(&self, mode: InputMode) {
let _lock = self.output_lock.lock();
unsafe {
termbox::tb_select_input_mode(mode as c_int);
}
}
pub fn set_output_mode(&mut self, mode: OutputMode) {
let _lock = self.output_lock.lock();
self.output_mode = mode;
unsafe {
termbox::tb_select_output_mode(mode as c_int);
}
}
pub fn suspend<F>(&self, func: F)
where F: FnOnce() -> ()
{
let _input_lock = self.input_lock.lock();
let _output_lock = self.output_lock.lock();
unsafe {
termbox::tb_shutdown();
}
func();
unsafe {
termbox::tb_init();
}
}
}
impl Drop for RustBox {
fn drop(&mut self) {
unsafe {
termbox::tb_shutdown();
}
}
}