#![feature(decl_macro)]
use std::{fs, io};
use std::path::Path;
use std::sync::LazyLock;
use bmp;
use log;
use easycurses;
use pancurses;
use png;
use easycurses::EasyCurses;
use mat::Mat;
pub use pancurses::COLOR_PAIR as color_pair;
type PancursesResult = i32;
pub static INIT_COLORS_DEFAULT : LazyLock <[[u8; 3]; 8]> = LazyLock::new (||{
use easycurses::Color;
let mut colors = [[0,0,0]; 8];
colors[Color::Black as usize ] = [ 25, 25, 25 ];
colors[Color::Red as usize ] = [ 238, 0, 0 ];
colors[Color::Green as usize ] = [ 0, 163, 0 ];
colors[Color::Blue as usize ] = [ 67, 67, 255 ];
colors[Color::Yellow as usize ] = [ 137, 137, 0 ];
colors[Color::Magenta as usize ] = [ 181, 0, 181 ];
colors[Color::Cyan as usize ] = [ 0, 147, 147 ];
colors[Color::White as usize ] = [ 230, 230, 230 ];
colors
});
pub macro pancurses_ok {
($e:expr) => {{
let result = $e;
if result != $crate::pancurses::OK {
$crate::log::error!("{}:l{}: pancurses error: {:?}", file!(), line!(),
result);
panic!()
}
}}
}
pub macro pancurses_warn_ok {
($e:expr) => {{
let result = $e;
if result != $crate::pancurses::OK {
$crate::log::warn!("{}:l{}: pancurses error: {:?}", file!(), line!(),
result);
}
}}
}
pub macro pancurses_err {
($e:expr) => {{
let result = $e;
if result != $crate::pancurses::ERR {
$crate::log::error!("{}:l{}: pancurses expected error, got: {:?}",
file!(), line!(), result);
panic!()
}
}}
}
pub macro pancurses_warn_err {
($e:expr) => {{
let result = $e;
if result != $crate::pancurses::ERR {
$crate::log::warn!("{}:l{}: pancurses expected error, got: {:?}",
file!(), line!(), result);
}
}}
}
#[cfg(not(target_os = "windows"))]
pub const ATTRIBUTE_MASK : pancurses::chtype = pancurses::A_ATTRIBUTES;
#[cfg(target_os = "windows")]
pub const ATTRIBUTE_MASK : pancurses::chtype =
!0x0 << pancurses::PDC_CHARTEXT_BITS;
#[derive(Debug)]
pub struct Curses {
pub easycurses : EasyCurses
}
#[inline]
pub const fn color_pair_id (fg : easycurses::Color, bg : easycurses::Color) -> u8 {
1 + 8 * fg as u8 + bg as u8
}
#[inline]
pub fn color_pair_attr (fg : easycurses::Color, bg : easycurses::Color)
-> pancurses::chtype
{
color_pair (color_pair_id (fg, bg) as pancurses::chtype)
}
#[inline]
pub const fn chtype_character (ch : pancurses::chtype) -> char {
ch.to_le_bytes()[0] as char
}
#[inline]
pub const fn chtype_color_pair (ch : pancurses::chtype) -> i16 {
ch.to_le_bytes()[1] as i16
}
#[inline]
#[expect(clippy::missing_panics_doc)]
pub const fn color_pair_colors (color_pair : i16)
-> Option <(easycurses::Color, easycurses::Color)>
{
if color_pair == 0 {
Some ((easycurses::Color::White, easycurses::Color::Black))
} else if color_pair <= 64 {
let bg = (color_pair-1) & 0b0000_0111;
let fg = ((color_pair-1) & 0b0011_1000) >> 3;
Some ((
color_from_primitive (fg).unwrap(),
color_from_primitive (bg).unwrap()
))
} else {
None
}
}
#[inline]
pub const fn chtype_attrs (ch : pancurses::chtype) -> pancurses::chtype {
ATTRIBUTE_MASK & ch
}
pub const fn color_from_primitive (color : i16) -> Option <easycurses::Color> {
use easycurses::Color;
let color = match color {
0 => Color::Black,
1 => Color::Red,
2 => Color::Green,
3 => Color::Yellow,
4 => Color::Blue,
5 => Color::Magenta,
6 => Color::Cyan,
7 => Color::White,
_ => return None
};
Some (color)
}
pub const fn color_complement (color : easycurses::Color) -> easycurses::Color {
use easycurses::Color;
match color {
Color::Black => Color::White,
Color::White => Color::Black,
Color::Red => Color::Cyan,
Color::Green => Color::Magenta,
Color::Blue => Color::Yellow,
Color::Cyan => Color::Red,
Color::Magenta => Color::Green,
Color::Yellow => Color::Blue
}
}
pub const fn rgba_to_color (rgba : [u8; 4]) -> Option <easycurses::Color> {
use easycurses::Color;
let color = match rgba {
[ _, _, _, 0] => return None,
[ 0, 0, 0, _] => Color::Black,
[255, 255, 255, _] => Color::White,
[255, 0, 0, _] => Color::Red,
[ 0, 255, 0, _] => Color::Green,
[ 0, 0, 255, _] => Color::Blue,
[ 0, 255, 255, _] => Color::Cyan,
[255, 0, 255, _] => Color::Magenta,
[255, 255, 0, _] => Color::Yellow,
_ => return None
};
Some (color)
}
#[inline]
pub fn rgba_to_color_pair (rgba : [u8; 4]) -> Option <pancurses::ColorPair> {
#[expect(trivial_numeric_casts)]
#[expect(clippy::unnecessary_cast)]
rgba_to_color (rgba).map (
|bg| pancurses::ColorPair (color_pair_id (color_complement (bg), bg) as u8))
}
#[inline]
pub fn rgba_to_color_halftone (rgba : [u8; 4]) -> Option <pancurses::chtype> {
use easycurses::Color;
let fg = Color::Black;
let (bg, ch) = if let Some (bg) = rgba_to_color (rgba) {
(bg, b' ' as pancurses::chtype)
} else {
let bg = match rgba {
[ _, _, _, 0] => return None,
[ 0, 0, 0, _] => Color::Black,
[128, 128, 128, _] => Color::White,
[128, 0, 0, _] => Color::Red,
[ 0, 128, 0, _] => Color::Green,
[ 0, 0, 128, _] => Color::Blue,
[ 0, 128, 128, _] => Color::Cyan,
[128, 0, 128, _] => Color::Magenta,
[128, 128, 0, _] => Color::Yellow,
_ => return None
};
(bg, pancurses::ACS_CKBOARD())
};
Some (ch | color_pair_attr (fg, bg))
}
#[deprecated]
#[expect(clippy::missing_panics_doc)]
pub fn image_text_load <P : AsRef <Path>> (path : P) -> Mat <char> {
use io::Read;
let mut buf = String::new();
let _ = fs::File::open (path).unwrap().read_to_string (&mut buf).unwrap();
buf.pop(); #[expect(deprecated)]
image_text_load_str (&buf)
}
#[deprecated]
#[expect(clippy::missing_panics_doc)]
pub fn image_text_load_str (chars : &str) -> Mat <char> {
let lines = chars.lines();
let rows = lines.clone().count();
let cols = lines.clone().map (str::len).max().unwrap();
let mut vec = Vec::new();
for line in lines {
let len = line.chars().count();
vec.extend (line.chars());
vec.extend (std::iter::repeat_n (' ', cols - len));
}
Mat::from_vec ((rows, cols).into(), vec).unwrap()
}
#[expect(clippy::missing_panics_doc)]
pub fn image_ascii_load <P : AsRef <Path>> (path : P) -> Mat <u8> {
use io::Read;
let mut buf = Vec::new();
let _ = fs::File::open (path).unwrap().read_to_end (&mut buf).unwrap();
buf.pop(); image_ascii_load_bytes (buf.as_slice())
}
#[expect(clippy::missing_panics_doc)]
pub fn image_ascii_load_bytes (chars : &[u8]) -> Mat <u8> {
let split = chars.split (|ch| ch == &b'\n');
let rows = split.clone().count();
let cols = split.clone().map (<[u8]>::len).max().unwrap();
let mut vec = Vec::new();
for line in split {
let len = line.len();
vec.extend (line);
vec.extend (std::iter::repeat_n (b' ', cols - len));
}
Mat::from_vec ((rows, cols).into(), vec).unwrap()
}
#[inline]
#[expect(clippy::missing_panics_doc)]
pub fn image_color_load_bmp24 <P : AsRef <Path>> (path : P) -> Mat <easycurses::Color> {
let image = bmp::open (path).unwrap();
image_color_load_bmp24_helper (image)
}
#[inline]
#[expect(clippy::missing_panics_doc)]
pub fn image_color_load_bmp24_bytes (mut bytes : &[u8]) -> Mat <easycurses::Color> {
let image = bmp::from_reader (&mut bytes).unwrap();
image_color_load_bmp24_helper (image)
}
#[inline]
#[expect(clippy::missing_panics_doc)]
pub fn image_color_load_png <P : AsRef <Path>> (path : P)
-> Mat <Option <easycurses::Color>>
{
let file = io::BufReader::new (fs::File::open (path).unwrap());
let decoder = png::Decoder::new (file);
image_color_load_png_helper (decoder)
}
#[inline]
pub fn image_color_load_png_bytes (bytes : &[u8]) -> Mat <Option <easycurses::Color>> {
let decoder = png::Decoder::new (io::Cursor::new (bytes));
image_color_load_png_helper (decoder)
}
#[inline]
#[expect(clippy::missing_panics_doc)]
pub fn image_color_bg_load_bmp24 <P : AsRef <Path>> (path : P)
-> Mat <pancurses::ColorPair>
{
let image = bmp::open (path).unwrap();
image_color_bg_load_bmp24_helper (image)
}
#[inline]
#[expect(clippy::missing_panics_doc)]
pub fn image_color_bg_load_bmp24_bytes (mut bytes : &[u8])
-> Mat <pancurses::ColorPair>
{
let image = bmp::from_reader (&mut bytes).unwrap();
image_color_bg_load_bmp24_helper (image)
}
#[expect(clippy::missing_panics_doc)]
pub fn image_color_pair_load_bmp24 <P : AsRef <Path>> (fg : P, bg : P)
-> Mat <pancurses::ColorPair>
{
let fg = bmp::open (fg).unwrap();
let bg = bmp::open (bg).unwrap();
image_color_pair_load_bmp24_helper (fg, bg)
}
#[expect(clippy::missing_panics_doc)]
pub fn image_color_pair_load_bmp24_bytes (mut fg : &[u8], mut bg : &[u8])
-> Mat <pancurses::ColorPair>
{
let fg = bmp::from_reader (&mut fg).unwrap();
let bg = bmp::from_reader (&mut bg).unwrap();
image_color_pair_load_bmp24_helper (fg, bg)
}
#[expect(clippy::missing_panics_doc)]
pub fn image_color_halftone_load_bmp24 <P : AsRef <Path>> (path : P)
-> Mat <pancurses::chtype>
{
let image = bmp::open (path).unwrap();
image_color_halftone_load_bmp24_helper (image)
}
#[expect(clippy::missing_panics_doc)]
pub fn image_color_halftone_load_bmp24_bytes (mut bytes : &[u8])
-> Mat <pancurses::chtype>
{
let image = bmp::from_reader (&mut bytes).unwrap();
image_color_halftone_load_bmp24_helper (image)
}
#[expect(clippy::missing_panics_doc)]
pub fn image_chtype (colors : &Mat <pancurses::ColorPair>, chars : &Mat <u8>)
-> Mat <pancurses::chtype>
{
let dimensions = colors.dimensions();
assert_eq!(dimensions, chars.dimensions());
let vec = colors.elements().zip (chars.elements()).map (|(color, ch)|
color_pair (color.0 as pancurses::chtype) |
*ch as pancurses::chtype
).collect::<Vec <pancurses::chtype>>();
Mat::from_vec (dimensions, vec).unwrap()
}
#[expect(clippy::missing_panics_doc)]
pub fn image_chtype_text (colors : &Mat <pancurses::ColorPair>, chars : &Mat <char>)
-> Mat <pancurses::chtype>
{
let dimensions = colors.dimensions();
assert_eq!(dimensions, chars.dimensions());
let vec = colors.elements().zip (chars.elements()).map (|(color, ch)|
color_pair (color.0 as pancurses::chtype) |
*ch as pancurses::chtype
).collect::<Vec <pancurses::chtype>>();
Mat::from_vec (dimensions, vec).unwrap()
}
fn image_color_load_bmp24_helper (image : bmp::Image) -> Mat <easycurses::Color> {
let cols = image.get_width() as usize;
let rows = image.get_height() as usize;
let vec = image.coordinates().map (|(x, y)|{
let pixel = image.get_pixel (x, y);
rgba_to_color ([pixel.r, pixel.g, pixel.b, 255]).unwrap()
}).collect::<Vec <easycurses::Color>>();
Mat::from_vec ((rows, cols).into(), vec).unwrap()
}
fn image_color_bg_load_bmp24_helper (image : bmp::Image) -> Mat <pancurses::ColorPair> {
let cols = image.get_width() as usize;
let rows = image.get_height() as usize;
let vec = image.coordinates().map (|(x, y)|{
let pixel = image.get_pixel (x, y);
rgba_to_color_pair ([pixel.r, pixel.g, pixel.b, 255]).unwrap()
}).collect::<Vec <pancurses::ColorPair>>();
Mat::from_vec ((rows, cols).into(), vec).unwrap()
}
fn image_color_pair_load_bmp24_helper (fg : bmp::Image, bg : bmp::Image)
-> Mat <pancurses::ColorPair>
{
let rows = fg.get_height() as usize;
let cols = fg.get_width() as usize;
assert_eq!(rows, bg.get_height() as usize);
assert_eq!(cols, bg.get_width() as usize);
let vec = fg.coordinates().map (|(x, y)|{
let fg = {
let pixel = fg.get_pixel (x, y);
rgba_to_color ([pixel.r, pixel.g, pixel.b, 255]).unwrap()
};
let bg = {
let pixel = bg.get_pixel (x, y);
rgba_to_color ([pixel.r, pixel.g, pixel.b, 255]).unwrap()
};
#[expect(trivial_numeric_casts)]
#[expect(clippy::unnecessary_cast)]
pancurses::ColorPair (color_pair_id (fg, bg) as u8)
}).collect::<Vec <pancurses::ColorPair>>();
Mat::from_vec ((rows, cols).into(), vec).unwrap()
}
fn image_color_halftone_load_bmp24_helper (image : bmp::Image)
-> Mat <pancurses::chtype>
{
let cols = image.get_width() as usize;
let rows = image.get_height() as usize;
let vec = image.coordinates().map (|(x, y)|{
let pixel = image.get_pixel (x, y);
rgba_to_color_halftone ([pixel.r, pixel.g, pixel.b, 255]).unwrap()
}).collect::<Vec <pancurses::chtype>>();
Mat::from_vec ((rows, cols).into(), vec).unwrap()
}
fn image_color_load_png_helper <R> (decoder : png::Decoder <R>)
-> Mat <Option <easycurses::Color>>
where R : io::BufRead + io::Seek {
let mut reader = decoder.read_info().unwrap();
let info = reader.info();
match info.color_type {
png::ColorType::Rgba => {}
_ => {
log::error!("invalid color type: {:?}", info.color_type);
panic!()
}
}
match info.bit_depth {
png::BitDepth::Eight => {}
_ => {
log::error!("invalid bit depth: {:?}", info.bit_depth);
panic!()
}
}
let mut bytes = vec![0; reader.output_buffer_size().unwrap()];
let rows = info.height as usize;
let cols = info.width as usize;
reader.next_frame (&mut bytes).unwrap();
let mut vec = Vec::with_capacity (rows * cols);
for i in 0..rows {
for j in 0..cols {
let offset = i * 4 * cols + j * 4;
let pixel = &bytes[offset..offset+4];
debug_assert_eq!(pixel.len(), 4);
let color = rgba_to_color ([pixel[0], pixel[1], pixel[2], pixel[3]]);
vec.push (color);
}
}
Mat::from_vec ((rows, cols).into(), vec).unwrap()
}
impl Curses {
#[expect(clippy::missing_panics_doc)]
pub fn new (color_init : Option <&[[u8; 3]; 8]>) -> Self {
log::trace!("new...");
use easycurses::{CursorVisibility, InputMode};
let easycurses = EasyCurses::initialize_system().unwrap();
easycurses.win.keypad (true); let mut curses = Curses { easycurses };
curses.set_cursor_visibility (CursorVisibility::Invisible); curses.set_echo (false); curses.set_input_mode (InputMode::Character); if let Some (colors) = color_init {
if !pancurses::can_change_color() {
log::warn!("curses color init: pancurses backend reports terminal can \
not change color");
} else {
let init_color = |color_name| {
#[expect(clippy::cast_possible_truncation)]
const fn byte_to_milli_color (byte : u8) -> i16 {
((byte as f64 / 255.0) * 1000.0) as i16
}
let color = colors[color_name as usize];
pancurses_ok!(pancurses::init_color (
color_name as i16,
byte_to_milli_color (color[0]),
byte_to_milli_color (color[1]),
byte_to_milli_color (color[2])
));
};
init_color (easycurses::Color::Black);
init_color (easycurses::Color::Red);
init_color (easycurses::Color::Green);
init_color (easycurses::Color::Blue);
init_color (easycurses::Color::Yellow);
init_color (easycurses::Color::Magenta);
init_color (easycurses::Color::Cyan);
init_color (easycurses::Color::White);
}
}
log::trace!("...new");
curses
}
#[inline]
pub const fn win (&self) -> &pancurses::Window {
&self.easycurses.win
}
#[inline]
pub fn getch_wait (&mut self) -> Option <easycurses::Input> {
use easycurses::{InputMode, TimeoutMode};
self.set_input_mode (InputMode::Character);
self.set_input_timeout (TimeoutMode::Never);
self.get_input()
}
#[inline]
pub fn getch_timeout (&mut self, timeout : u32) -> Option <easycurses::Input> {
use easycurses::{InputMode, TimeoutMode};
self.set_input_mode (InputMode::Character);
debug_assert!(timeout <= i32::MAX as u32);
#[expect(clippy::cast_possible_wrap)]
self.set_input_timeout (TimeoutMode::WaitUpTo (timeout as i32));
self.get_input()
}
#[inline]
pub fn getch_nowait (&mut self) -> Option <easycurses::Input> {
use easycurses::{InputMode, TimeoutMode};
self.set_input_mode (InputMode::Character);
self.set_input_timeout (TimeoutMode::Immediate);
self.get_input()
}
#[inline]
pub fn getline_wait (&mut self) -> Option <easycurses::Input> {
use easycurses::{InputMode, TimeoutMode};
self.set_input_mode (InputMode::Cooked);
self.set_input_timeout (TimeoutMode::Never);
self.get_input()
}
#[inline]
pub fn getline_nowait (&mut self) -> Option <easycurses::Input> {
use easycurses::{InputMode, TimeoutMode};
self.set_input_mode (InputMode::Cooked);
self.set_input_timeout (TimeoutMode::Immediate);
self.get_input()
}
#[inline]
pub fn rows (&self) -> i32 {
self.win().get_max_y()
}
#[inline]
pub fn columns (&self) -> i32 {
self.win().get_max_x()
}
#[inline]
pub fn dimensions_rc (&self) -> (i32, i32) {
self.easycurses.get_row_col_count()
}
#[inline]
pub fn center_col (&self) -> i32 {
let (_, cols) = self.get_row_col_count();
cols / 2
}
#[inline]
pub fn center_row (&self) -> i32 {
let (rows, _) = self.get_row_col_count();
rows / 2
}
#[inline]
pub fn center_rc (&self) -> (i32, i32) {
let (rows, cols) = self.get_row_col_count();
(rows / 2, cols / 2)
}
pub fn print_centered (&mut self,
string : &str,
row_offset : Option <i32>,
col_offset : Option <i32>
) -> (i32, i32) {
let (longest, count) = string.lines().fold ((0,0),
|(longest, count), next| {
let next_len = next.len();
(usize::max (longest, next_len), count+1)
}
);
#[expect(clippy::cast_possible_truncation)]
#[expect(clippy::cast_possible_wrap)]
let half_longest = longest as i32 / 2;
let half_count = count / 2;
let (center_row, center_col) = {
let (row, col) = self.center_rc();
(row + row_offset.unwrap_or (0), col + col_offset.unwrap_or (0))
};
for (i, line) in string.lines().enumerate() {
#[expect(clippy::cast_possible_truncation)]
#[expect(clippy::cast_possible_wrap)]
let at_row = center_row - half_count + i as i32;
let at_col = center_col - half_longest;
let _ = self.move_rc (at_row, at_col);
let _ = self.print (line);
}
(center_row - half_count, center_col - half_longest)
}
#[inline]
#[must_use]
pub fn draw_border_default (&mut self) -> PancursesResult {
self.win().draw_box (pancurses::ACS_VLINE(), pancurses::ACS_HLINE())
}
#[must_use]
pub fn draw_box (&mut self,
ch : pancurses::chtype,
rc_min : (i32, i32),
rc_max : (i32, i32)
) -> PancursesResult {
let mut out = pancurses::OK;
let mut result_ok = |result| if out == pancurses::OK {
out = result;
};
let (min_row, min_col) =
(i32::min (rc_min.0, rc_max.0), i32::min (rc_min.1, rc_max.1));
let (max_row, max_col) =
(i32::max (rc_min.0, rc_max.0), i32::max (rc_min.1, rc_max.1));
let rows = max_row - min_row + 1;
let cols = max_col - min_col + 1;
let _ = self.move_rc (min_row, min_col);
result_ok (self.win().hline (ch, cols));
let _ = self.move_rc (min_row, min_col);
result_ok (self.win().vline (ch, rows));
let _ = self.move_rc (max_row, min_col);
result_ok (self.win().hline (ch, cols));
let _ = self.move_rc (min_row, max_col);
result_ok (self.win().vline (ch, rows));
out
}
#[must_use]
#[expect(clippy::too_many_arguments)]
pub fn draw_border (&mut self,
left : pancurses::chtype,
right : pancurses::chtype,
top : pancurses::chtype,
bottom : pancurses::chtype,
top_left : pancurses::chtype,
top_right : pancurses::chtype,
bottom_left : pancurses::chtype,
bottom_right : pancurses::chtype,
rc_min : (i32, i32),
rc_max : (i32, i32),
thickness_top : u32,
thickness_bottom : u32,
thickness_left : u32,
thickness_right : u32
) -> PancursesResult {
let mut out = pancurses::OK;
let mut result_ok = |result| if out == pancurses::OK {
out = result;
};
#[expect(clippy::cast_possible_wrap)]
let (thickness_top, thickness_bottom, thickness_left, thickness_right) = (
thickness_top as i32,
thickness_bottom as i32,
thickness_left as i32,
thickness_right as i32);
let (min_row, min_col) =
(i32::min (rc_min.0, rc_max.0), i32::min (rc_min.1, rc_max.1));
let (max_row, max_col) =
(i32::max (rc_min.0, rc_max.0), i32::max (rc_min.1, rc_max.1));
let rows = max_row - min_row + 1;
let cols = max_col - min_col + 1;
let total_width = thickness_left + thickness_right;
let total_height = thickness_top + thickness_bottom;
for i in 0..thickness_top {
let _ = self.move_rc (min_row + i, min_col);
result_ok (self.win().hline (top_left, thickness_left));
let _ = self.move_rc (min_row + i, min_col + thickness_left);
result_ok (self.win().hline (top, cols - total_width));
let _ = self.move_rc (min_row + i, min_col + cols - thickness_right);
result_ok (self.win().hline (top_right, thickness_right));
}
for i in 0..thickness_bottom {
let _ = self.move_rc (max_row - i, min_col);
result_ok (self.win().hline (bottom_left, thickness_left));
let _ = self.move_rc (max_row - i, min_col + thickness_left);
result_ok (self.win().hline (bottom, cols - total_width));
let _ = self.move_rc (max_row - i, min_col + cols - thickness_right);
result_ok (self.win().hline (bottom_right, thickness_right));
}
for i in 0..thickness_left {
let _ = self.move_rc (min_row + thickness_top, min_col + i);
result_ok (self.win().vline (left, rows - total_height));
}
for i in 0..thickness_right {
let _ = self.move_rc (min_row + thickness_top, max_col - i);
result_ok (self.win().vline (right, rows - total_height));
}
out
}
pub fn draw_rect (&mut self,
border : pancurses::chtype,
fill : pancurses::chtype,
rc_min : (i32, i32),
rc_max : (i32, i32),
thickness_h : u32, thickness_v : u32 ) {
let mut out = pancurses::OK;
let mut result_ok = |result| if out == pancurses::OK {
out = result;
};
let (min_row, min_col) =
(i32::min (rc_min.0, rc_max.0), i32::min (rc_min.1, rc_max.1));
let (max_row, max_col) =
(i32::max (rc_min.0, rc_max.0), i32::max (rc_min.1, rc_max.1));
let rows = max_row - min_row + 1;
let cols = max_col - min_col + 1;
for i in 0..rows {
let _ = self.move_rc (min_row + i, min_col);
result_ok (self.win().hline (fill, cols));
}
#[expect(clippy::cast_possible_wrap)]
for i in 0..thickness_h as i32 {
let _ = self.move_rc (min_row + i, min_col);
result_ok (self.win().hline (border, cols));
let _ = self.move_rc (max_row - i, min_col);
result_ok (self.win().hline (border, cols));
}
#[expect(clippy::cast_possible_wrap)]
for i in 0..thickness_v as i32 {
let _ = self.move_rc (min_row, min_col + i);
result_ok (self.win().vline (border, rows));
let _ = self.move_rc (min_row, max_col - i);
result_ok (self.win().vline (border, rows));
}
}
#[expect(clippy::missing_panics_doc)]
pub fn draw_image_color_pair (&mut self,
at_rc : (i32, i32), image : &Mat <pancurses::ColorPair>
) {
let mut row = 0;
while row < image.height() {
let mut col = 0;
loop {
let color = *image.get ((row, col).into()).unwrap();
#[expect(clippy::cast_possible_truncation)]
#[expect(clippy::cast_possible_wrap)]
let start = col as i32;
col += 1;
while col < image.width()-1 {
if color == *image.get ((row, col).into()).unwrap() {
col += 1;
} else {
break
}
}
#[expect(clippy::cast_possible_truncation)]
#[expect(clippy::cast_possible_wrap)]
let _ = self.win().mvchgat (
at_rc.0 + row as i32,
at_rc.1 + start,
col as i32 - start,
0x0, color.0 as i16);
if col == image.width() {
break
}
}
row += 1;
}
}
#[expect(clippy::missing_panics_doc)]
pub fn draw_image_chtype (&mut self,
at_rc : (i32, i32), image : &Mat <pancurses::chtype>
) {
let dimensions = image.dimensions();
for row in 0..dimensions.rows {
#[expect(clippy::cast_possible_truncation)]
#[expect(clippy::cast_possible_wrap)]
for col in 0..dimensions.columns {
let ch = *image.get ((row, col).into()).unwrap();
let at_row = at_rc.0 + row as i32;
let at_col = at_rc.1 + col as i32;
let _ = self.win().mvaddch (at_row, at_col, ch);
}
}
}
pub fn log_info (&self) {
log::info!("log info...");
log::info!(" cursor(row,col): {:?}", self.get_cursor_rc());
log::info!(" dimensions(row,col): {:?}", self.dimensions_rc());
log::info!(" is color terminal: {}", self.is_color_terminal());
log::info!(" can change color: {}", pancurses::can_change_color());
log::info!(" maximum color pairs: {}", pancurses::COLOR_PAIRS());
log::info!(" maximum colors: {}", pancurses::COLORS());
log::info!(" colors:");
#[expect(clippy::cast_possible_truncation)]
for i in 0..pancurses::COLORS() as i16 {
let color_string = if let Some (color) = color_from_primitive (i) {
format!("{color:?}")
} else {
i.to_string()
};
let rgb = pancurses::color_content (i);
log::info!(" {color_string}: {rgb:?}");
}
log::info!("...log info");
}
}
impl Default for Curses {
fn default() -> Self {
Curses::new (None)
}
}
impl std::ops::Deref for Curses {
type Target = EasyCurses;
fn deref (&self) -> &EasyCurses {
&self.easycurses
}
}
impl std::ops::DerefMut for Curses {
fn deref_mut (&mut self) -> &mut EasyCurses {
&mut self.easycurses
}
}
#[cfg(test)]
mod tests {
#[test]
fn color_from_primitive() {
use super::color_from_primitive;
for i in 0..8 {
assert_eq!(i, color_from_primitive (i).unwrap() as i16);
}
}
}