curses_utils/
lib.rs

1#![feature(decl_macro)]
2
3use std::path::Path;
4use bmp;
5use log;
6use easycurses;
7use pancurses;
8use png;
9use easycurses::EasyCurses;
10use lazy_static::lazy_static;
11use mat::Mat;
12
13pub use pancurses::COLOR_PAIR as color_pair;
14
15type PancursesResult = i32;
16
17#[allow(unused_macros)]
18macro_rules! debug {
19  ($e:expr) => { log::debug!("{}: {:?}", stringify!($e), $e); }
20}
21
22lazy_static!{
23  /// Eight default color values, 10% lum (black), 50% lum (hues), 90% lum
24  /// (white)
25  pub static ref INIT_COLORS_DEFAULT : [[u8; 3]; 8] = {
26    use easycurses::Color;
27    let mut colors = [[0,0,0]; 8];
28    colors[Color::Black   as usize ] = [  25,  25,  25 ];
29    colors[Color::Red     as usize ] = [ 238,   0,   0 ];
30    colors[Color::Green   as usize ] = [   0, 163,   0 ];
31    colors[Color::Blue    as usize ] = [  67,  67, 255 ];
32    colors[Color::Yellow  as usize ] = [ 137, 137,   0 ];
33    colors[Color::Magenta as usize ] = [ 181,   0, 181 ];
34    colors[Color::Cyan    as usize ] = [   0, 147, 147 ];
35    colors[Color::White   as usize ] = [ 230, 230, 230 ];
36    colors
37  };
38}
39
40/// Log an error and panic if pancurses call returns `ERR`
41pub macro pancurses_ok {
42  ($e:expr) => {{
43    let result = $e;
44    if result != $crate::pancurses::OK {
45      $crate::log::error!("{}:l{}: pancurses error: {:?}", file!(), line!(),
46        result);
47      panic!()
48    }
49  }}
50}
51
52/// Log a warning but don't panic
53pub macro pancurses_warn_ok {
54  ($e:expr) => {{
55    let result = $e;
56    if result != $crate::pancurses::OK {
57      $crate::log::warn!("{}:l{}: pancurses error: {:?}", file!(), line!(),
58        result);
59    }
60  }}
61}
62
63/// Log an error and panic if pancurses call *does not* return `ERR`
64pub macro pancurses_err {
65  ($e:expr) => {{
66    let result = $e;
67    if result != $crate::pancurses::ERR {
68      $crate::log::error!("{}:l{}: pancurses expected error, got: {:?}",
69        file!(), line!(), result);
70      panic!()
71    }
72  }}
73}
74
75/// Log a warning if pancurses call *does not* return `ERR`
76pub macro pancurses_warn_err {
77  ($e:expr) => {{
78    let result = $e;
79    if result != $crate::pancurses::ERR {
80      $crate::log::warn!("{}:l{}: pancurses expected error, got: {:?}",
81        file!(), line!(), result);
82    }
83  }}
84}
85
86#[cfg(not(target_os = "windows"))]
87pub const ATTRIBUTE_MASK : pancurses::chtype = pancurses::A_ATTRIBUTES;
88#[cfg(target_os = "windows")]
89pub const ATTRIBUTE_MASK : pancurses::chtype =
90  !0x0 << pancurses::PDC_CHARTEXT_BITS;
91
92/// Wraps an EasyCurses window
93#[derive(Debug)]
94pub struct Curses {
95  pub easycurses : EasyCurses
96}
97
98/// Constructs the color pair number that `easycurses` assigned to this color
99/// combination, equal to `1 + (8 * fg + bg)`, giving a number in the range
100/// `1-64`
101#[inline]
102pub const fn color_pair_id (fg : easycurses::Color, bg : easycurses::Color)
103  -> i16
104{
105  1 + 8 * fg as i16 + bg as i16
106}
107
108/// Constructs the color pair attribute `chtype`.
109///
110/// Equivalent to `COLOR_PAIR(color_pair_id(fg,bg))`.
111#[inline]
112pub /*const*/ fn color_pair_attr (fg : easycurses::Color, bg : easycurses::Color)
113  -> pancurses::chtype
114{
115  color_pair (color_pair_id (fg, bg) as pancurses::chtype)
116}
117
118/// Get the 8-bit character data from the first byte of a `chtype`.
119#[inline]
120pub /*const*/ fn chtype_character (ch : pancurses::chtype) -> char {
121  ch.to_le_bytes()[0] as char
122}
123
124/// Get the color pair number from the second byte of a `chtype`
125#[inline]
126pub /*const*/ fn chtype_color_pair (ch : pancurses::chtype) -> i16 {
127  ch.to_le_bytes()[1] as i16
128}
129
130/// Returns the (fg, bg) color for the given color pair index.
131///
132/// Returns None if the index was >64.
133///
134/// Returns `Some (White, Black)` if the index is 0.
135#[inline]
136pub /*const*/ fn color_pair_colors (color_pair : i16)
137  -> Option <(easycurses::Color, easycurses::Color)>
138{
139  if color_pair == 0 {
140    Some ((easycurses::Color::White, easycurses::Color::Black))
141  } else if color_pair <= 64 {
142    let bg = (color_pair-1) & 0b0000_0111;
143    let fg = ((color_pair-1) & 0b0011_1000) >> 3;
144    Some ((
145      color_from_primitive (fg).unwrap(),
146      color_from_primitive (bg).unwrap()
147    ))
148  } else {
149    None
150  }
151}
152
153/// Get the attributes of a `chtype` (equivalent to `A_ATTRIBUTES & ch` in
154/// ncurses)
155#[inline]
156pub const fn chtype_attrs (ch : pancurses::chtype) -> pancurses::chtype {
157  ATTRIBUTE_MASK & ch
158}
159
160pub /*const*/ fn color_from_primitive (color : i16)
161  -> Option <easycurses::Color>
162{
163  use easycurses::Color;
164  let color = match color {
165    0 => Color::Black,
166    1 => Color::Red,
167    2 => Color::Green,
168    3 => Color::Yellow,
169    4 => Color::Blue,
170    5 => Color::Magenta,
171    6 => Color::Cyan,
172    7 => Color::White,
173    _ => return None
174  };
175  Some (color)
176}
177
178pub /*const*/ fn color_complement (color : easycurses::Color)
179  -> easycurses::Color
180{
181  use easycurses::Color;
182  match color {
183    Color::Black   => Color::White,
184    Color::White   => Color::Black,
185    Color::Red     => Color::Cyan,
186    Color::Green   => Color::Magenta,
187    Color::Blue    => Color::Yellow,
188    Color::Cyan    => Color::Red,
189    Color::Magenta => Color::Green,
190    Color::Yellow  => Color::Blue
191  }
192}
193
194/// Maps pure primary or secondary color (e.g. [255, 255, 0, 255]) with `a > 0`
195/// to easycurses Color
196///
197/// Returns `None` when `a == 0` or if any color bytes are something other than
198/// 0 or 255
199pub fn rgba_to_color (rgba : [u8; 4]) -> Option <easycurses::Color> {
200  use easycurses::Color;
201  let color = match rgba {
202    [  _,   _,   _,   0] => return None,
203    [  0,   0,   0,   _] => Color::Black,
204    [255, 255, 255,   _] => Color::White,
205    [255,   0,   0,   _] => Color::Red,
206    [  0, 255,   0,   _] => Color::Green,
207    [  0,   0, 255,   _] => Color::Blue,
208    [  0, 255, 255,   _] => Color::Cyan,
209    [255,   0, 255,   _] => Color::Magenta,
210    [255, 255,   0,   _] => Color::Yellow,
211    _ => return None
212  };
213  Some (color)
214}
215
216/// Sets the background to the given rgba color and the foreground to its
217/// complement.
218#[inline]
219pub fn rgba_to_color_pair (rgba : [u8; 4]) -> Option <pancurses::ColorPair> {
220  rgba_to_color (rgba).map (
221    |bg| pancurses::ColorPair (color_pair_id (color_complement (bg), bg) as u8))
222}
223
224/// &#9888; Note: the curses window must be created before calling the method,
225/// as `ACS_CKBOARD()` returns `0x0` before initialization.
226///
227/// Uses `pancurses::ACS_CKBOARD()` to create half-tones from pixels with 50%
228/// intensity.
229///
230/// Foreground will always be black, but will be transparent for pure chroma
231/// pixels.
232#[inline]
233pub fn rgba_to_color_halftone (rgba : [u8; 4]) -> Option <pancurses::chtype> {
234  use easycurses::Color;
235  let fg = Color::Black;
236  let (bg, ch) = if let Some (bg) = rgba_to_color (rgba) {
237    (bg, b' ' as pancurses::chtype)
238  } else {
239    let bg = match rgba {
240      [  _,   _,   _,   0] => return None,
241      [  0,   0,   0,   _] => Color::Black,
242      [128, 128, 128,   _] => Color::White,
243      [128,   0,   0,   _] => Color::Red,
244      [  0, 128,   0,   _] => Color::Green,
245      [  0,   0, 128,   _] => Color::Blue,
246      [  0, 128, 128,   _] => Color::Cyan,
247      [128,   0, 128,   _] => Color::Magenta,
248      [128, 128,   0,   _] => Color::Yellow,
249      _ => return None
250    };
251    (bg, pancurses::ACS_CKBOARD())
252  };
253  Some (ch | color_pair_attr (fg, bg))
254}
255
256/// *Deprecated*: this doesn't really work for non-ASCII unicode, use
257/// `image_ascii_load` functions instead.
258///
259/// Load a text file
260#[deprecated]
261pub fn image_text_load <P : AsRef <Path>> (path : P) -> Mat <char> {
262  use std::io::Read;
263  let mut buf = String::new();
264  let _ = std::fs::File::open (path).unwrap().read_to_string (&mut buf).unwrap();
265  buf.pop();  // read_to_end includes a final newline
266  #[allow(deprecated)]
267  image_text_load_str (&buf)
268}
269
270/// *Deprecated*: this doesn't really work for non-ASCII unicode, use
271/// `image_ascii_load` functions instead.
272///
273/// Load a text image from raw string data.
274///
275/// Newlines will be stripped out and the horizontal dimension will be
276/// determined by the longest line.
277#[deprecated]
278pub fn image_text_load_str (chars : &str) -> Mat <char> {
279  let lines   = chars.lines();
280  let rows    = lines.clone().count();
281  let cols    = lines.clone().map (|line| line.len()).max().unwrap();
282  let mut vec = Vec::new();
283  for line in lines {
284    let len = line.chars().count();
285    vec.extend (line.chars());
286    vec.extend (std::iter::repeat (' ').take (cols - len));
287  }
288  Mat::from_vec ((rows, cols).into(), vec).unwrap()
289}
290
291/// Load an ASCII text file
292pub fn image_ascii_load <P : AsRef <Path>> (path : P) -> Mat <u8> {
293  use std::io::Read;
294  let mut buf = Vec::new();
295  let _ = std::fs::File::open (path).unwrap().read_to_end (&mut buf).unwrap();
296  buf.pop();  // read_to_end includes a final newline
297  image_ascii_load_bytes (buf.as_slice())
298}
299
300/// Load a ASCII text image from raw bytes.
301///
302/// Newlines will be stripped out and the horizontal dimension will be
303/// determined by the longest line.
304pub fn image_ascii_load_bytes (chars : &[u8]) -> Mat <u8> {
305  let split   = chars.split (|ch| ch == &b'\n');
306  let rows    = split.clone().count();
307  let cols    = split.clone().map (|line| line.len()).max().unwrap();
308  let mut vec = Vec::new();
309  for line in split {
310    let len = line.len();
311    vec.extend (line);
312    vec.extend (std::iter::repeat (b' ').take (cols - len));
313  }
314  Mat::from_vec ((rows, cols).into(), vec).unwrap()
315}
316
317/// Load an image of pure chroma pixels
318#[inline]
319pub fn image_color_load_bmp24 <P : AsRef <Path>>
320  (path : P) -> Mat <easycurses::Color>
321{
322  let image = bmp::open (path).unwrap();
323  image_color_load_bmp24_helper (image)
324}
325
326/// Load an image of pure chroma pixels from memory
327#[inline]
328pub fn image_color_load_bmp24_bytes (mut bytes : &[u8])
329  -> Mat <easycurses::Color>
330{
331  let image = bmp::from_reader (&mut bytes).unwrap();
332  image_color_load_bmp24_helper (image)
333}
334
335/// Load an image of pure chroma pixels with alpha channel values of `0` as
336/// transparency
337#[inline]
338pub fn image_color_load_png <P : AsRef <Path>>
339  (path : P) -> Mat <Option <easycurses::Color>>
340{
341  let file    = std::fs::File::open (path).unwrap();
342  let decoder = png::Decoder::new (file);
343  image_color_load_png_helper (decoder)
344}
345
346/// Load an image of pure chroma pixels with alpha channel values of `0` as
347/// transparency
348#[inline]
349pub fn image_color_load_png_bytes (bytes : &[u8])
350  -> Mat <Option <easycurses::Color>>
351{
352  let decoder = png::Decoder::new (bytes);
353  image_color_load_png_helper (decoder)
354}
355
356/// Load an image of pure chroma background pixels with the color pair
357/// foreground set to the complement of the background
358#[inline]
359pub fn image_color_bg_load_bmp24 <P : AsRef <Path>>
360  (path : P) -> Mat <pancurses::ColorPair>
361{
362  let image = bmp::open (path).unwrap();
363  image_color_bg_load_bmp24_helper (image)
364}
365
366/// Load an image of pure chroma background pixels with the color pair
367/// foreground set to the complement of the background
368#[inline]
369pub fn image_color_bg_load_bmp24_bytes (mut bytes : &[u8])
370  -> Mat <pancurses::ColorPair>
371{
372  let image = bmp::from_reader (&mut bytes).unwrap();
373  image_color_bg_load_bmp24_helper (image)
374}
375
376/// Load foreground and background pair of 24bit BMP image color data.
377///
378/// &#9888; Images must have the same dimensions.
379pub fn image_color_pair_load_bmp24 <P : AsRef <Path>>
380  (fg : P, bg : P) -> Mat <pancurses::ColorPair>
381{
382  let fg = bmp::open (fg).unwrap();
383  let bg = bmp::open (bg).unwrap();
384  image_color_pair_load_bmp24_helper (fg, bg)
385}
386
387/// Load foreground and background pair of 24bit BMP image color data.
388///
389/// &#9888; Images must have the same dimensions.
390pub fn image_color_pair_load_bmp24_bytes (mut fg : &[u8], mut bg : &[u8])
391  -> Mat <pancurses::ColorPair>
392{
393  let fg = bmp::from_reader (&mut fg).unwrap();
394  let bg = bmp::from_reader (&mut bg).unwrap();
395  image_color_pair_load_bmp24_helper (fg, bg)
396}
397
398/// Load an image of pure chroma pixels
399pub fn image_color_halftone_load_bmp24 <P : AsRef <Path>>
400  (path : P) -> Mat <pancurses::chtype>
401{
402  let image = bmp::open (path).unwrap();
403  image_color_halftone_load_bmp24_helper (image)
404}
405
406/// Load an image of pure chroma pixels
407pub fn image_color_halftone_load_bmp24_bytes (mut bytes : &[u8])
408  -> Mat <pancurses::chtype>
409{
410  let image = bmp::from_reader (&mut bytes).unwrap();
411  image_color_halftone_load_bmp24_helper (image)
412}
413
414/// Load an image of ASCII characters as `pancurses::chtype`s
415pub fn image_chtype (colors : &Mat <pancurses::ColorPair>, chars : &Mat <u8>)
416  -> Mat <pancurses::chtype>
417{
418  let dimensions = colors.dimensions();
419  assert_eq!(dimensions, chars.dimensions());
420  let vec = colors.elements().zip (chars.elements()).map (|(color, ch)|
421    color_pair (color.0 as pancurses::chtype) |
422    *ch as pancurses::chtype
423  ).collect::<Vec <pancurses::chtype>>();
424  Mat::from_vec (dimensions, vec).unwrap()
425}
426
427/// Load an image of UTF8 characters as `pancurses::chtype`s.
428pub fn image_chtype_text (
429  colors : &Mat <pancurses::ColorPair>,
430  chars  : &Mat <char>
431) -> Mat <pancurses::chtype> {
432  let dimensions = colors.dimensions();
433  assert_eq!(dimensions, chars.dimensions());
434  let vec = colors.elements().zip (chars.elements()).map (|(color, ch)|
435    color_pair (color.0 as pancurses::chtype) |
436    *ch as pancurses::chtype
437  ).collect::<Vec <pancurses::chtype>>();
438  Mat::from_vec (dimensions, vec).unwrap()
439}
440
441//
442//  private
443//
444
445fn image_color_load_bmp24_helper (image : bmp::Image)
446  -> Mat <easycurses::Color>
447{
448  let cols  = image.get_width()  as usize;
449  let rows  = image.get_height() as usize;
450  let vec   = image.coordinates().map (|(x, y)|{
451    let pixel = image.get_pixel (x, y);
452    rgba_to_color ([pixel.r, pixel.g, pixel.b, 255]).unwrap()
453  }).collect::<Vec <easycurses::Color>>();
454  Mat::from_vec ((rows, cols).into(), vec).unwrap()
455}
456
457fn image_color_bg_load_bmp24_helper (image : bmp::Image)
458  -> Mat <pancurses::ColorPair>
459{
460  let cols  = image.get_width()  as usize;
461  let rows  = image.get_height() as usize;
462  let vec   = image.coordinates().map (|(x, y)|{
463    let pixel = image.get_pixel (x, y);
464    rgba_to_color_pair ([pixel.r, pixel.g, pixel.b, 255]).unwrap()
465  }).collect::<Vec <pancurses::ColorPair>>();
466  Mat::from_vec ((rows, cols).into(), vec).unwrap()
467}
468
469fn image_color_pair_load_bmp24_helper (fg : bmp::Image, bg : bmp::Image)
470  -> Mat <pancurses::ColorPair>
471{
472  let rows = fg.get_height() as usize;
473  let cols = fg.get_width()  as usize;
474  assert_eq!(rows, bg.get_height() as usize);
475  assert_eq!(cols, bg.get_width()  as usize);
476  let vec  = fg.coordinates().map (|(x, y)|{
477    let fg = {
478      let pixel = fg.get_pixel (x, y);
479      rgba_to_color ([pixel.r, pixel.g, pixel.b, 255]).unwrap()
480    };
481    let bg = {
482      let pixel = bg.get_pixel (x, y);
483      rgba_to_color ([pixel.r, pixel.g, pixel.b, 255]).unwrap()
484    };
485    pancurses::ColorPair (color_pair_id (fg, bg) as u8)
486  }).collect::<Vec <pancurses::ColorPair>>();
487  Mat::from_vec ((rows, cols).into(), vec).unwrap()
488}
489
490fn image_color_halftone_load_bmp24_helper (image : bmp::Image)
491  -> Mat <pancurses::chtype>
492{
493  let cols  = image.get_width()  as usize;
494  let rows  = image.get_height() as usize;
495  let vec   = image.coordinates().map (|(x, y)|{
496    let pixel = image.get_pixel (x, y);
497    rgba_to_color_halftone ([pixel.r, pixel.g, pixel.b, 255]).unwrap()
498  }).collect::<Vec <pancurses::chtype>>();
499  Mat::from_vec ((rows, cols).into(), vec).unwrap()
500}
501
502fn image_color_load_png_helper <R : std::io::Read> (decoder : png::Decoder <R>)
503  -> Mat <Option <easycurses::Color>>
504{
505  let mut reader = decoder.read_info().unwrap();
506  let info = reader.info();
507  match info.color_type {
508    png::ColorType::Rgba => {}
509    _ => {
510      log::error!("invalid color type: {:?}", info.color_type);
511      panic!()
512    }
513  }
514  match info.bit_depth {
515    png::BitDepth::Eight => {}
516    _ => {
517      log::error!("invalid bit depth: {:?}", info.bit_depth);
518      panic!()
519    }
520  }
521  let mut bytes = vec![0; reader.output_buffer_size()];
522  let rows  = info.height as usize;
523  let cols  = info.width  as usize;
524  reader.next_frame (&mut bytes).unwrap();
525  let mut vec = Vec::with_capacity (rows * cols);
526  for i in 0..rows {
527    for j in 0..cols {
528      let offset = i * 4 * cols + j * 4;
529      let pixel  = &bytes[offset..offset+4];
530      let color  = rgba_to_color ([pixel[0], pixel[1], pixel[2], pixel[3]]);
531      vec.push (color);
532    }
533  }
534  Mat::from_vec ((rows, cols).into(), vec).unwrap()
535}
536
537impl Curses {
538  /// Constructs an easycurses window with some default options set:
539  ///
540  /// - hidden cursor
541  /// - no input echo
542  /// - no line buffering
543  /// - modified primary colors on windows
544  ///
545  /// Colors are in the format accepted by `pancurses::init_color`
546  pub fn new (color_init : Option <&[[u8; 3]; 8]>) -> Self {
547    log::trace!("new...");
548    use easycurses::{CursorVisibility, InputMode};
549    let easycurses = EasyCurses::initialize_system().unwrap();
550    easycurses.win.keypad (true);                   // return special key codes
551    let mut curses = Curses { easycurses };
552    curses.set_cursor_visibility (CursorVisibility::Invisible); // hide cursor
553    curses.set_echo (false);                        // no input echo
554    curses.set_input_mode (InputMode::Character);   // no line buffering
555    if let Some (colors) = color_init {
556      if !pancurses::can_change_color() {
557        log::warn!("curses color init: pancurses backend reports terminal can \
558          not change color");
559      } else {
560        let init_color = |color_name| {
561          const fn byte_to_milli_color (byte : u8) -> i16 {
562            ((byte as f64 / 255.0) * 1000.0) as i16
563          }
564          let color = colors[color_name as usize];
565          pancurses_ok!(pancurses::init_color (
566            color_name as i16,
567            byte_to_milli_color (color[0]),
568            byte_to_milli_color (color[1]),
569            byte_to_milli_color (color[2])
570          ));
571        };
572        init_color (easycurses::Color::Black);
573        init_color (easycurses::Color::Red);
574        init_color (easycurses::Color::Green);
575        init_color (easycurses::Color::Blue);
576        init_color (easycurses::Color::Yellow);
577        init_color (easycurses::Color::Magenta);
578        init_color (easycurses::Color::Cyan);
579        init_color (easycurses::Color::White);
580      }
581    }
582    log::trace!("...new");
583    curses
584  }
585
586  /// Get a reference to the underlying pancurses window
587  #[inline]
588  pub fn win (&self) -> &pancurses::Window {
589    &self.easycurses.win
590  }
591
592  /// Blocking getch
593  #[inline]
594  pub fn getch_wait (&mut self) -> Option <easycurses::Input> {
595    use easycurses::{InputMode, TimeoutMode};
596    self.set_input_mode (InputMode::Character);
597    self.set_input_timeout (TimeoutMode::Never);
598    self.get_input()
599  }
600
601  /// Block until timeout getch
602  #[inline]
603  pub fn getch_timeout (&mut self, timeout : u32)
604    -> Option <easycurses::Input>
605  {
606    debug_assert!(timeout <= std::i32::MAX as u32);
607    use easycurses::{InputMode, TimeoutMode};
608    self.set_input_mode (InputMode::Character);
609    self.set_input_timeout (TimeoutMode::WaitUpTo (timeout as i32));
610    self.get_input()
611  }
612
613  /// Non-blocking getch
614  #[inline]
615  pub fn getch_nowait (&mut self) -> Option <easycurses::Input> {
616    use easycurses::{InputMode, TimeoutMode};
617    self.set_input_mode (InputMode::Character);
618    self.set_input_timeout (TimeoutMode::Immediate);
619    self.get_input()
620  }
621
622  /// Blocking getline
623  #[inline]
624  pub fn getline_wait (&mut self) -> Option <easycurses::Input> {
625    use easycurses::{InputMode, TimeoutMode};
626    self.set_input_mode (InputMode::Cooked);
627    self.set_input_timeout (TimeoutMode::Never);
628    self.get_input()
629  }
630
631  /// Non-blocking getline
632  #[inline]
633  pub fn getline_nowait (&mut self) -> Option <easycurses::Input> {
634    use easycurses::{InputMode, TimeoutMode};
635    self.set_input_mode (InputMode::Cooked);
636    self.set_input_timeout (TimeoutMode::Immediate);
637    self.get_input()
638  }
639
640  #[inline]
641  pub fn rows (&self) -> i32 {
642    self.win().get_max_y()
643  }
644
645  #[inline]
646  pub fn columns (&self) -> i32 {
647    self.win().get_max_x()
648  }
649
650  #[inline]
651  pub fn dimensions_rc (&self) -> (i32, i32) {
652    self.easycurses.get_row_col_count()
653  }
654
655  #[inline]
656  pub fn center_col (&self) -> i32 {
657    let (_, cols) = self.get_row_col_count();
658    cols / 2
659  }
660
661  #[inline]
662  pub fn center_row (&self) -> i32 {
663    let (rows, _) = self.get_row_col_count();
664    rows / 2
665  }
666
667  #[inline]
668  pub fn center_rc (&self) -> (i32, i32) {
669    let (rows, cols) = self.get_row_col_count();
670    (rows / 2, cols / 2)
671  }
672
673  /// Returns the (row, col) of the upper left corner of the inserted text
674  pub fn print_centered (&mut self,
675    string     : &str,
676    row_offset : Option <i32>,
677    col_offset : Option <i32>
678  ) -> (i32, i32) {
679    let (longest, count) = string.lines().fold ((0,0),
680      |(longest, count), next| {
681        let next_len = next.len();
682        (usize::max (longest, next_len), count+1)
683      }
684    );
685    let half_longest = longest as i32 / 2;
686    let half_count   = count   as i32 / 2;
687    let (center_row, center_col) = {
688      let (row, col) = self.center_rc();
689      (row + row_offset.unwrap_or (0), col + col_offset.unwrap_or (0))
690    };
691    for (i, line) in string.lines().enumerate() {
692      let at_row = center_row - half_count + i as i32;
693      let at_col = center_col - half_longest;
694      // NOTE: move_rc returns false if the cursor is moved outside of the
695      // window
696      let _ = self.move_rc (at_row, at_col);
697      // NOTE: print returns false if the cursor would be advanced past the end
698      // of the window
699      let _ = self.print (line);
700    }
701    (center_row - half_count, center_col - half_longest)
702  }
703
704  /// Draws a border with `pancurses::draw_bux()` using `ACS_VLINE` and
705  /// `ACS_HLINE` (corners will be `ACS_*` corners as well).
706  #[inline]
707  #[must_use]
708  pub fn draw_border_default (&mut self) -> PancursesResult {
709    self.win().draw_box (pancurses::ACS_VLINE(), pancurses::ACS_HLINE())
710  }
711
712  /// Draw a box with min (upper left) and max (lower right) coordinates.
713  ///
714  /// *Note*: this is not like the ncurses 'box' function which draws a window
715  /// border with matching top/bottom and left/right sides.
716  #[must_use]
717  pub fn draw_box (&mut self,
718    ch     : pancurses::chtype,
719    rc_min : (i32, i32),
720    rc_max : (i32, i32)
721  ) -> PancursesResult {
722    let mut out = pancurses::OK;
723    // this will return the first error and ignore all others
724    let mut result_ok = |result| if out == pancurses::OK {
725      out = result;
726    };
727    // take the min/max anyway in case they are not in the right order
728    let (min_row, min_col) =
729      (i32::min (rc_min.0, rc_max.0), i32::min (rc_min.1, rc_max.1));
730    let (max_row, max_col) =
731      (i32::max (rc_min.0, rc_max.0), i32::max (rc_min.1, rc_max.1));
732    let rows = max_row - min_row + 1;
733    let cols = max_col - min_col + 1;
734    let _ = self.move_rc (min_row, min_col);
735    result_ok (self.win().hline (ch, cols));
736    let _ = self.move_rc (min_row, min_col);
737    result_ok (self.win().vline (ch, rows));
738    let _ = self.move_rc (max_row, min_col);
739    result_ok (self.win().hline (ch, cols));
740    let _ = self.move_rc (min_row, max_col);
741    result_ok (self.win().vline (ch, rows));
742    out
743  }
744
745  /// Draws a box with explicit top/sides and corners.
746  ///
747  /// This is like the ncurses `border()` function, except around sub-area of
748  /// the window instead of the entire window.
749  #[must_use]
750  pub fn draw_border (&mut self,
751    left             : pancurses::chtype,
752    right            : pancurses::chtype,
753    top              : pancurses::chtype,
754    bottom           : pancurses::chtype,
755    top_left         : pancurses::chtype,
756    top_right        : pancurses::chtype,
757    bottom_left      : pancurses::chtype,
758    bottom_right     : pancurses::chtype,
759    rc_min           : (i32, i32),
760    rc_max           : (i32, i32),
761    thickness_top    : u32,
762    thickness_bottom : u32,
763    thickness_left   : u32,
764    thickness_right  : u32
765  ) -> PancursesResult {
766    let mut out = pancurses::OK;
767    // this will return the first error and ignore all others
768    let mut result_ok = |result| if out == pancurses::OK {
769      out = result;
770    };
771    let (thickness_top, thickness_bottom, thickness_left, thickness_right) = (
772      thickness_top    as i32,
773      thickness_bottom as i32,
774      thickness_left   as i32,
775      thickness_right  as i32);
776    // take the min/max anyway in case they are not in the right order
777    let (min_row, min_col) =
778      (i32::min (rc_min.0, rc_max.0), i32::min (rc_min.1, rc_max.1));
779    let (max_row, max_col) =
780      (i32::max (rc_min.0, rc_max.0), i32::max (rc_min.1, rc_max.1));
781    let rows = max_row - min_row + 1;
782    let cols = max_col - min_col + 1;
783    let total_width  = thickness_left + thickness_right;
784    let total_height = thickness_top  + thickness_bottom;
785    for i in 0..thickness_top {
786      let _ = self.move_rc (min_row + i, min_col);
787      result_ok (self.win().hline (top_left, thickness_left));
788      let _ = self.move_rc (min_row + i, min_col + thickness_left);
789      result_ok (self.win().hline (top, cols - total_width));
790      let _ = self.move_rc (min_row + i, min_col + cols - thickness_right);
791      result_ok (self.win().hline (top_right, thickness_right));
792    }
793    for i in 0..thickness_bottom {
794      let _ = self.move_rc (max_row - i, min_col);
795      result_ok (self.win().hline (bottom_left, thickness_left));
796      let _ = self.move_rc (max_row - i, min_col + thickness_left);
797      result_ok (self.win().hline (bottom, cols - total_width));
798      let _ = self.move_rc (max_row - i, min_col + cols - thickness_right);
799      result_ok (self.win().hline (bottom_right, thickness_right));
800    }
801    for i in 0..thickness_left {
802      let _ = self.move_rc (min_row + thickness_top, min_col + i);
803      result_ok (self.win().vline (left, rows - total_height));
804    }
805    for i in 0..thickness_right {
806      let _ = self.move_rc (min_row + thickness_top, max_col - i);
807      result_ok (self.win().vline (right, rows - total_height));
808    }
809    out
810  }
811
812  /// Draws a filled rectangular box with the given characters.
813  pub fn draw_rect (&mut self,
814    border      : pancurses::chtype,
815    fill        : pancurses::chtype,
816    rc_min      : (i32, i32),
817    rc_max      : (i32, i32),
818    thickness_h : u32,        // thickness of top and bottom border
819    thickness_v : u32         // thickness of left and right border
820  ) {
821    let mut out = pancurses::OK;
822    // this will return the first error and ignore all others
823    let mut result_ok = |result| if out == pancurses::OK {
824      out = result;
825    };
826    // take the min/max anyway in case they are not in the right order
827    let (min_row, min_col) =
828      (i32::min (rc_min.0, rc_max.0), i32::min (rc_min.1, rc_max.1));
829    let (max_row, max_col) =
830      (i32::max (rc_min.0, rc_max.0), i32::max (rc_min.1, rc_max.1));
831    let rows = max_row - min_row + 1;
832    let cols = max_col - min_col + 1;
833    for i in 0..rows {
834      let _ = self.move_rc (min_row + i, min_col);
835      result_ok (self.win().hline (fill, cols));
836    }
837    for i in 0..thickness_h as i32 {
838      let _ = self.move_rc (min_row + i, min_col);
839      result_ok (self.win().hline (border, cols));
840      let _ = self.move_rc (max_row - i, min_col);
841      result_ok (self.win().hline (border, cols));
842    }
843    for i in 0..thickness_v as i32 {
844      let _ = self.move_rc (min_row, min_col + i);
845      result_ok (self.win().vline (border, rows));
846      let _ = self.move_rc (min_row, max_col - i);
847      result_ok (self.win().vline (border, rows));
848    }
849  }
850
851  /// Draw color image data using `chgat`
852  pub fn draw_image_color_pair (&mut self,
853    at_rc : (i32, i32), image : &Mat <pancurses::ColorPair>
854  ) {
855    let mut row = 0;
856    while row < image.height() {
857      let mut col = 0;
858      loop {
859        let color = *image.get ((row, col).into()).unwrap();
860        let start = col as i32;
861        col += 1;
862        while col < image.width()-1 {
863          if color == *image.get ((row, col).into()).unwrap() {
864            col += 1;
865          } else {
866            break
867          }
868        }
869        // NOTE: returns -1 if start position is outside the window
870        let _ = self.win().mvchgat (
871          at_rc.0 + row   as i32,
872          at_rc.1 + start as i32,
873          col as i32 - start,
874          0x0, color.0 as i16);
875        if col == image.width() {
876          break
877        }
878      }
879      row += 1;
880    }
881  }
882
883  /// Draw chtype image data using `addch`
884  pub fn draw_image_chtype (&mut self,
885    at_rc : (i32, i32), image : &Mat <pancurses::chtype>
886  ) {
887    let dimensions = image.dimensions();
888    for row in 0..dimensions.rows {
889      for col in 0..dimensions.columns {
890        let ch = *image.get ((row, col).into()).unwrap();
891        let at_row = at_rc.0 + row as i32;
892        let at_col = at_rc.1 + col as i32;
893        // NOTE: function returns -1 when adding character outside the window
894        let _ = self.win().mvaddch (at_row, at_col, ch);
895      }
896    }
897  }
898
899  /// Logs some current curses (and terminal) attributes
900  pub fn log_info (&self) {
901    log::info!("log info...");
902    log::info!("  cursor(row,col):     {:?}", self.get_cursor_rc());
903    log::info!("  dimensions(row,col): {:?}", self.dimensions_rc());
904    log::info!("  is color terminal:   {}",   self.is_color_terminal());
905    log::info!("  can change color:    {}",   pancurses::can_change_color());
906    log::info!("  maximum color pairs: {}",   pancurses::COLOR_PAIRS());
907    log::info!("  maximum colors:      {}",   pancurses::COLORS());
908    log::info!("  colors:");
909    for i in 0..pancurses::COLORS() as i16 {
910      let color_string = if let Some (color) = color_from_primitive (i) {
911        format!("{:?}", color)
912      } else {
913        i.to_string()
914      };
915      let rgb = pancurses::color_content (i);
916      log::info!("    {}: {:?}", color_string, rgb);
917    }
918    log::info!("...log info");
919  }
920}
921
922impl Default for Curses {
923  /// Calls `Curses::new` with `None`
924  fn default() -> Self {
925    Curses::new (None)
926  }
927}
928
929impl std::ops::Deref for Curses {
930  type Target = EasyCurses;
931  fn deref (&self) -> &EasyCurses {
932    &self.easycurses
933  }
934}
935
936impl std::ops::DerefMut for Curses {
937  fn deref_mut (&mut self) -> &mut EasyCurses {
938    &mut self.easycurses
939  }
940}
941
942#[cfg(test)]
943mod tests {
944  use super::*;
945  #[test]
946  fn test_color_from_primitive() {
947    for i in 0..8 {
948      assert_eq!(i, color_from_primitive (i).unwrap() as i16);
949    }
950  }
951}