1#![feature(decl_macro)]
2
3use std::{fs, io};
4use std::path::Path;
5use std::sync::LazyLock;
6use bmp;
7use log;
8use easycurses;
9use pancurses;
10use png;
11use easycurses::EasyCurses;
12use mat::Mat;
13
14pub use pancurses::COLOR_PAIR as color_pair;
15
16type PancursesResult = i32;
17
18pub static INIT_COLORS_DEFAULT : LazyLock <[[u8; 3]; 8]> = LazyLock::new (||{
20 use easycurses::Color;
21 let mut colors = [[0,0,0]; 8];
22 colors[Color::Black as usize ] = [ 25, 25, 25 ];
23 colors[Color::Red as usize ] = [ 238, 0, 0 ];
24 colors[Color::Green as usize ] = [ 0, 163, 0 ];
25 colors[Color::Blue as usize ] = [ 67, 67, 255 ];
26 colors[Color::Yellow as usize ] = [ 137, 137, 0 ];
27 colors[Color::Magenta as usize ] = [ 181, 0, 181 ];
28 colors[Color::Cyan as usize ] = [ 0, 147, 147 ];
29 colors[Color::White as usize ] = [ 230, 230, 230 ];
30 colors
31});
32
33pub macro pancurses_ok {
35 ($e:expr) => {{
36 let result = $e;
37 if result != $crate::pancurses::OK {
38 $crate::log::error!("{}:l{}: pancurses error: {:?}", file!(), line!(),
39 result);
40 panic!()
41 }
42 }}
43}
44
45pub macro pancurses_warn_ok {
47 ($e:expr) => {{
48 let result = $e;
49 if result != $crate::pancurses::OK {
50 $crate::log::warn!("{}:l{}: pancurses error: {:?}", file!(), line!(),
51 result);
52 }
53 }}
54}
55
56pub macro pancurses_err {
58 ($e:expr) => {{
59 let result = $e;
60 if result != $crate::pancurses::ERR {
61 $crate::log::error!("{}:l{}: pancurses expected error, got: {:?}",
62 file!(), line!(), result);
63 panic!()
64 }
65 }}
66}
67
68pub macro pancurses_warn_err {
70 ($e:expr) => {{
71 let result = $e;
72 if result != $crate::pancurses::ERR {
73 $crate::log::warn!("{}:l{}: pancurses expected error, got: {:?}",
74 file!(), line!(), result);
75 }
76 }}
77}
78
79#[cfg(not(target_os = "windows"))]
80pub const ATTRIBUTE_MASK : pancurses::chtype = pancurses::A_ATTRIBUTES;
81#[cfg(target_os = "windows")]
82pub const ATTRIBUTE_MASK : pancurses::chtype =
83 !0x0 << pancurses::PDC_CHARTEXT_BITS;
84
85#[derive(Debug)]
87pub struct Curses {
88 pub easycurses : EasyCurses
89}
90
91#[inline]
95pub const fn color_pair_id (fg : easycurses::Color, bg : easycurses::Color) -> u8 {
96 1 + 8 * fg as u8 + bg as u8
97}
98
99#[inline]
103pub fn color_pair_attr (fg : easycurses::Color, bg : easycurses::Color)
104 -> pancurses::chtype
105{
106 color_pair (color_pair_id (fg, bg) as pancurses::chtype)
107}
108
109#[inline]
111pub const fn chtype_character (ch : pancurses::chtype) -> char {
112 ch.to_le_bytes()[0] as char
113}
114
115#[inline]
117pub const fn chtype_color_pair (ch : pancurses::chtype) -> i16 {
118 ch.to_le_bytes()[1] as i16
119}
120
121#[inline]
127#[expect(clippy::missing_panics_doc)]
128pub const fn color_pair_colors (color_pair : i16)
129 -> Option <(easycurses::Color, easycurses::Color)>
130{
131 if color_pair == 0 {
132 Some ((easycurses::Color::White, easycurses::Color::Black))
133 } else if color_pair <= 64 {
134 let bg = (color_pair-1) & 0b0000_0111;
135 let fg = ((color_pair-1) & 0b0011_1000) >> 3;
136 Some ((
137 color_from_primitive (fg).unwrap(),
138 color_from_primitive (bg).unwrap()
139 ))
140 } else {
141 None
142 }
143}
144
145#[inline]
148pub const fn chtype_attrs (ch : pancurses::chtype) -> pancurses::chtype {
149 ATTRIBUTE_MASK & ch
150}
151
152pub const fn color_from_primitive (color : i16) -> Option <easycurses::Color> {
153 use easycurses::Color;
154 let color = match color {
155 0 => Color::Black,
156 1 => Color::Red,
157 2 => Color::Green,
158 3 => Color::Yellow,
159 4 => Color::Blue,
160 5 => Color::Magenta,
161 6 => Color::Cyan,
162 7 => Color::White,
163 _ => return None
164 };
165 Some (color)
166}
167
168pub const fn color_complement (color : easycurses::Color) -> easycurses::Color {
169 use easycurses::Color;
170 match color {
171 Color::Black => Color::White,
172 Color::White => Color::Black,
173 Color::Red => Color::Cyan,
174 Color::Green => Color::Magenta,
175 Color::Blue => Color::Yellow,
176 Color::Cyan => Color::Red,
177 Color::Magenta => Color::Green,
178 Color::Yellow => Color::Blue
179 }
180}
181
182pub const fn rgba_to_color (rgba : [u8; 4]) -> Option <easycurses::Color> {
188 use easycurses::Color;
189 let color = match rgba {
190 [ _, _, _, 0] => return None,
191 [ 0, 0, 0, _] => Color::Black,
192 [255, 255, 255, _] => Color::White,
193 [255, 0, 0, _] => Color::Red,
194 [ 0, 255, 0, _] => Color::Green,
195 [ 0, 0, 255, _] => Color::Blue,
196 [ 0, 255, 255, _] => Color::Cyan,
197 [255, 0, 255, _] => Color::Magenta,
198 [255, 255, 0, _] => Color::Yellow,
199 _ => return None
200 };
201 Some (color)
202}
203
204#[inline]
206pub fn rgba_to_color_pair (rgba : [u8; 4]) -> Option <pancurses::ColorPair> {
207 #[expect(trivial_numeric_casts)]
208 #[expect(clippy::unnecessary_cast)]
209 rgba_to_color (rgba).map (
210 |bg| pancurses::ColorPair (color_pair_id (color_complement (bg), bg) as u8))
211}
212
213#[inline]
222pub fn rgba_to_color_halftone (rgba : [u8; 4]) -> Option <pancurses::chtype> {
223 use easycurses::Color;
224 let fg = Color::Black;
225 let (bg, ch) = if let Some (bg) = rgba_to_color (rgba) {
226 (bg, b' ' as pancurses::chtype)
227 } else {
228 let bg = match rgba {
229 [ _, _, _, 0] => return None,
230 [ 0, 0, 0, _] => Color::Black,
231 [128, 128, 128, _] => Color::White,
232 [128, 0, 0, _] => Color::Red,
233 [ 0, 128, 0, _] => Color::Green,
234 [ 0, 0, 128, _] => Color::Blue,
235 [ 0, 128, 128, _] => Color::Cyan,
236 [128, 0, 128, _] => Color::Magenta,
237 [128, 128, 0, _] => Color::Yellow,
238 _ => return None
239 };
240 (bg, pancurses::ACS_CKBOARD())
241 };
242 Some (ch | color_pair_attr (fg, bg))
243}
244
245#[deprecated]
250#[expect(clippy::missing_panics_doc)]
251pub fn image_text_load <P : AsRef <Path>> (path : P) -> Mat <char> {
252 use io::Read;
253 let mut buf = String::new();
254 let _ = fs::File::open (path).unwrap().read_to_string (&mut buf).unwrap();
255 buf.pop(); #[expect(deprecated)]
257 image_text_load_str (&buf)
258}
259
260#[deprecated]
268#[expect(clippy::missing_panics_doc)]
269pub fn image_text_load_str (chars : &str) -> Mat <char> {
270 let lines = chars.lines();
271 let rows = lines.clone().count();
272 let cols = lines.clone().map (str::len).max().unwrap();
273 let mut vec = Vec::new();
274 for line in lines {
275 let len = line.chars().count();
276 vec.extend (line.chars());
277 vec.extend (std::iter::repeat_n (' ', cols - len));
278 }
279 Mat::from_vec ((rows, cols).into(), vec).unwrap()
280}
281
282#[expect(clippy::missing_panics_doc)]
284pub fn image_ascii_load <P : AsRef <Path>> (path : P) -> Mat <u8> {
285 use io::Read;
286 let mut buf = Vec::new();
287 let _ = fs::File::open (path).unwrap().read_to_end (&mut buf).unwrap();
288 buf.pop(); image_ascii_load_bytes (buf.as_slice())
290}
291
292#[expect(clippy::missing_panics_doc)]
297pub fn image_ascii_load_bytes (chars : &[u8]) -> Mat <u8> {
298 let split = chars.split (|ch| ch == &b'\n');
299 let rows = split.clone().count();
300 let cols = split.clone().map (<[u8]>::len).max().unwrap();
301 let mut vec = Vec::new();
302 for line in split {
303 let len = line.len();
304 vec.extend (line);
305 vec.extend (std::iter::repeat_n (b' ', cols - len));
306 }
307 Mat::from_vec ((rows, cols).into(), vec).unwrap()
308}
309
310#[inline]
312#[expect(clippy::missing_panics_doc)]
313pub fn image_color_load_bmp24 <P : AsRef <Path>> (path : P) -> Mat <easycurses::Color> {
314 let image = bmp::open (path).unwrap();
315 image_color_load_bmp24_helper (image)
316}
317
318#[inline]
320#[expect(clippy::missing_panics_doc)]
321pub fn image_color_load_bmp24_bytes (mut bytes : &[u8]) -> Mat <easycurses::Color> {
322 let image = bmp::from_reader (&mut bytes).unwrap();
323 image_color_load_bmp24_helper (image)
324}
325
326#[inline]
329#[expect(clippy::missing_panics_doc)]
330pub fn image_color_load_png <P : AsRef <Path>> (path : P)
331 -> Mat <Option <easycurses::Color>>
332{
333 let file = io::BufReader::new (fs::File::open (path).unwrap());
334 let decoder = png::Decoder::new (file);
335 image_color_load_png_helper (decoder)
336}
337
338#[inline]
340pub fn image_color_load_png_bytes (bytes : &[u8]) -> Mat <Option <easycurses::Color>> {
341 let decoder = png::Decoder::new (io::Cursor::new (bytes));
342 image_color_load_png_helper (decoder)
343}
344
345#[inline]
348#[expect(clippy::missing_panics_doc)]
349pub fn image_color_bg_load_bmp24 <P : AsRef <Path>> (path : P)
350 -> Mat <pancurses::ColorPair>
351{
352 let image = bmp::open (path).unwrap();
353 image_color_bg_load_bmp24_helper (image)
354}
355
356#[inline]
359#[expect(clippy::missing_panics_doc)]
360pub fn image_color_bg_load_bmp24_bytes (mut bytes : &[u8])
361 -> Mat <pancurses::ColorPair>
362{
363 let image = bmp::from_reader (&mut bytes).unwrap();
364 image_color_bg_load_bmp24_helper (image)
365}
366
367#[expect(clippy::missing_panics_doc)]
371pub fn image_color_pair_load_bmp24 <P : AsRef <Path>> (fg : P, bg : P)
372 -> Mat <pancurses::ColorPair>
373{
374 let fg = bmp::open (fg).unwrap();
375 let bg = bmp::open (bg).unwrap();
376 image_color_pair_load_bmp24_helper (fg, bg)
377}
378
379#[expect(clippy::missing_panics_doc)]
383pub fn image_color_pair_load_bmp24_bytes (mut fg : &[u8], mut bg : &[u8])
384 -> Mat <pancurses::ColorPair>
385{
386 let fg = bmp::from_reader (&mut fg).unwrap();
387 let bg = bmp::from_reader (&mut bg).unwrap();
388 image_color_pair_load_bmp24_helper (fg, bg)
389}
390
391#[expect(clippy::missing_panics_doc)]
393pub fn image_color_halftone_load_bmp24 <P : AsRef <Path>> (path : P)
394 -> Mat <pancurses::chtype>
395{
396 let image = bmp::open (path).unwrap();
397 image_color_halftone_load_bmp24_helper (image)
398}
399
400#[expect(clippy::missing_panics_doc)]
402pub fn image_color_halftone_load_bmp24_bytes (mut bytes : &[u8])
403 -> Mat <pancurses::chtype>
404{
405 let image = bmp::from_reader (&mut bytes).unwrap();
406 image_color_halftone_load_bmp24_helper (image)
407}
408
409#[expect(clippy::missing_panics_doc)]
411pub fn image_chtype (colors : &Mat <pancurses::ColorPair>, chars : &Mat <u8>)
412 -> Mat <pancurses::chtype>
413{
414 let dimensions = colors.dimensions();
415 assert_eq!(dimensions, chars.dimensions());
416 let vec = colors.elements().zip (chars.elements()).map (|(color, ch)|
417 color_pair (color.0 as pancurses::chtype) |
418 *ch as pancurses::chtype
419 ).collect::<Vec <pancurses::chtype>>();
420 Mat::from_vec (dimensions, vec).unwrap()
421}
422
423#[expect(clippy::missing_panics_doc)]
425pub fn image_chtype_text (colors : &Mat <pancurses::ColorPair>, chars : &Mat <char>)
426 -> Mat <pancurses::chtype>
427{
428 let dimensions = colors.dimensions();
429 assert_eq!(dimensions, chars.dimensions());
430 let vec = colors.elements().zip (chars.elements()).map (|(color, ch)|
431 color_pair (color.0 as pancurses::chtype) |
432 *ch as pancurses::chtype
433 ).collect::<Vec <pancurses::chtype>>();
434 Mat::from_vec (dimensions, vec).unwrap()
435}
436
437fn image_color_load_bmp24_helper (image : bmp::Image) -> Mat <easycurses::Color> {
442 let cols = image.get_width() as usize;
443 let rows = image.get_height() as usize;
444 let vec = image.coordinates().map (|(x, y)|{
445 let pixel = image.get_pixel (x, y);
446 rgba_to_color ([pixel.r, pixel.g, pixel.b, 255]).unwrap()
447 }).collect::<Vec <easycurses::Color>>();
448 Mat::from_vec ((rows, cols).into(), vec).unwrap()
449}
450
451fn image_color_bg_load_bmp24_helper (image : bmp::Image) -> Mat <pancurses::ColorPair> {
452 let cols = image.get_width() as usize;
453 let rows = image.get_height() as usize;
454 let vec = image.coordinates().map (|(x, y)|{
455 let pixel = image.get_pixel (x, y);
456 rgba_to_color_pair ([pixel.r, pixel.g, pixel.b, 255]).unwrap()
457 }).collect::<Vec <pancurses::ColorPair>>();
458 Mat::from_vec ((rows, cols).into(), vec).unwrap()
459}
460
461fn image_color_pair_load_bmp24_helper (fg : bmp::Image, bg : bmp::Image)
462 -> Mat <pancurses::ColorPair>
463{
464 let rows = fg.get_height() as usize;
465 let cols = fg.get_width() as usize;
466 assert_eq!(rows, bg.get_height() as usize);
467 assert_eq!(cols, bg.get_width() as usize);
468 let vec = fg.coordinates().map (|(x, y)|{
469 let fg = {
470 let pixel = fg.get_pixel (x, y);
471 rgba_to_color ([pixel.r, pixel.g, pixel.b, 255]).unwrap()
472 };
473 let bg = {
474 let pixel = bg.get_pixel (x, y);
475 rgba_to_color ([pixel.r, pixel.g, pixel.b, 255]).unwrap()
476 };
477 #[expect(trivial_numeric_casts)]
478 #[expect(clippy::unnecessary_cast)]
479 pancurses::ColorPair (color_pair_id (fg, bg) as u8)
480 }).collect::<Vec <pancurses::ColorPair>>();
481 Mat::from_vec ((rows, cols).into(), vec).unwrap()
482}
483
484fn image_color_halftone_load_bmp24_helper (image : bmp::Image)
485 -> Mat <pancurses::chtype>
486{
487 let cols = image.get_width() as usize;
488 let rows = image.get_height() as usize;
489 let vec = image.coordinates().map (|(x, y)|{
490 let pixel = image.get_pixel (x, y);
491 rgba_to_color_halftone ([pixel.r, pixel.g, pixel.b, 255]).unwrap()
492 }).collect::<Vec <pancurses::chtype>>();
493 Mat::from_vec ((rows, cols).into(), vec).unwrap()
494}
495
496fn image_color_load_png_helper <R> (decoder : png::Decoder <R>)
497 -> Mat <Option <easycurses::Color>>
498where R : io::BufRead + io::Seek {
499 let mut reader = decoder.read_info().unwrap();
500 let info = reader.info();
501 match info.color_type {
502 png::ColorType::Rgba => {}
503 _ => {
504 log::error!("invalid color type: {:?}", info.color_type);
505 panic!()
506 }
507 }
508 match info.bit_depth {
509 png::BitDepth::Eight => {}
510 _ => {
511 log::error!("invalid bit depth: {:?}", info.bit_depth);
512 panic!()
513 }
514 }
515 let mut bytes = vec![0; reader.output_buffer_size().unwrap()];
516 let rows = info.height as usize;
517 let cols = info.width as usize;
518 reader.next_frame (&mut bytes).unwrap();
519 let mut vec = Vec::with_capacity (rows * cols);
520 for i in 0..rows {
521 for j in 0..cols {
522 let offset = i * 4 * cols + j * 4;
523 let pixel = &bytes[offset..offset+4];
524 debug_assert_eq!(pixel.len(), 4);
525 let color = rgba_to_color ([pixel[0], pixel[1], pixel[2], pixel[3]]);
526 vec.push (color);
527 }
528 }
529 Mat::from_vec ((rows, cols).into(), vec).unwrap()
530}
531
532impl Curses {
533 #[expect(clippy::missing_panics_doc)]
542 pub fn new (color_init : Option <&[[u8; 3]; 8]>) -> Self {
543 log::trace!("new...");
544 use easycurses::{CursorVisibility, InputMode};
545 let easycurses = EasyCurses::initialize_system().unwrap();
546 easycurses.win.keypad (true); let mut curses = Curses { easycurses };
548 curses.set_cursor_visibility (CursorVisibility::Invisible); curses.set_echo (false); curses.set_input_mode (InputMode::Character); if let Some (colors) = color_init {
552 if !pancurses::can_change_color() {
553 log::warn!("curses color init: pancurses backend reports terminal can \
554 not change color");
555 } else {
556 let init_color = |color_name| {
557 #[expect(clippy::cast_possible_truncation)]
558 const fn byte_to_milli_color (byte : u8) -> i16 {
559 ((byte as f64 / 255.0) * 1000.0) as i16
560 }
561 let color = colors[color_name as usize];
562 pancurses_ok!(pancurses::init_color (
563 color_name as i16,
564 byte_to_milli_color (color[0]),
565 byte_to_milli_color (color[1]),
566 byte_to_milli_color (color[2])
567 ));
568 };
569 init_color (easycurses::Color::Black);
570 init_color (easycurses::Color::Red);
571 init_color (easycurses::Color::Green);
572 init_color (easycurses::Color::Blue);
573 init_color (easycurses::Color::Yellow);
574 init_color (easycurses::Color::Magenta);
575 init_color (easycurses::Color::Cyan);
576 init_color (easycurses::Color::White);
577 }
578 }
579 log::trace!("...new");
580 curses
581 }
582
583 #[inline]
585 pub const fn win (&self) -> &pancurses::Window {
586 &self.easycurses.win
587 }
588
589 #[inline]
591 pub fn getch_wait (&mut self) -> Option <easycurses::Input> {
592 use easycurses::{InputMode, TimeoutMode};
593 self.set_input_mode (InputMode::Character);
594 self.set_input_timeout (TimeoutMode::Never);
595 self.get_input()
596 }
597
598 #[inline]
600 pub fn getch_timeout (&mut self, timeout : u32) -> Option <easycurses::Input> {
601 use easycurses::{InputMode, TimeoutMode};
602 self.set_input_mode (InputMode::Character);
603 debug_assert!(timeout <= i32::MAX as u32);
604 #[expect(clippy::cast_possible_wrap)]
605 self.set_input_timeout (TimeoutMode::WaitUpTo (timeout as i32));
606 self.get_input()
607 }
608
609 #[inline]
611 pub fn getch_nowait (&mut self) -> Option <easycurses::Input> {
612 use easycurses::{InputMode, TimeoutMode};
613 self.set_input_mode (InputMode::Character);
614 self.set_input_timeout (TimeoutMode::Immediate);
615 self.get_input()
616 }
617
618 #[inline]
620 pub fn getline_wait (&mut self) -> Option <easycurses::Input> {
621 use easycurses::{InputMode, TimeoutMode};
622 self.set_input_mode (InputMode::Cooked);
623 self.set_input_timeout (TimeoutMode::Never);
624 self.get_input()
625 }
626
627 #[inline]
629 pub fn getline_nowait (&mut self) -> Option <easycurses::Input> {
630 use easycurses::{InputMode, TimeoutMode};
631 self.set_input_mode (InputMode::Cooked);
632 self.set_input_timeout (TimeoutMode::Immediate);
633 self.get_input()
634 }
635
636 #[inline]
637 pub fn rows (&self) -> i32 {
638 self.win().get_max_y()
639 }
640
641 #[inline]
642 pub fn columns (&self) -> i32 {
643 self.win().get_max_x()
644 }
645
646 #[inline]
647 pub fn dimensions_rc (&self) -> (i32, i32) {
648 self.easycurses.get_row_col_count()
649 }
650
651 #[inline]
652 pub fn center_col (&self) -> i32 {
653 let (_, cols) = self.get_row_col_count();
654 cols / 2
655 }
656
657 #[inline]
658 pub fn center_row (&self) -> i32 {
659 let (rows, _) = self.get_row_col_count();
660 rows / 2
661 }
662
663 #[inline]
664 pub fn center_rc (&self) -> (i32, i32) {
665 let (rows, cols) = self.get_row_col_count();
666 (rows / 2, cols / 2)
667 }
668
669 pub fn print_centered (&mut self,
671 string : &str,
672 row_offset : Option <i32>,
673 col_offset : Option <i32>
674 ) -> (i32, i32) {
675 let (longest, count) = string.lines().fold ((0,0),
676 |(longest, count), next| {
677 let next_len = next.len();
678 (usize::max (longest, next_len), count+1)
679 }
680 );
681 #[expect(clippy::cast_possible_truncation)]
682 #[expect(clippy::cast_possible_wrap)]
683 let half_longest = longest as i32 / 2;
684 let half_count = count / 2;
685 let (center_row, center_col) = {
686 let (row, col) = self.center_rc();
687 (row + row_offset.unwrap_or (0), col + col_offset.unwrap_or (0))
688 };
689 for (i, line) in string.lines().enumerate() {
690 #[expect(clippy::cast_possible_truncation)]
691 #[expect(clippy::cast_possible_wrap)]
692 let at_row = center_row - half_count + i as i32;
693 let at_col = center_col - half_longest;
694 let _ = self.move_rc (at_row, at_col);
697 let _ = self.print (line);
700 }
701 (center_row - half_count, center_col - half_longest)
702 }
703
704 #[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 #[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 let mut result_ok = |result| if out == pancurses::OK {
725 out = result;
726 };
727 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 #[must_use]
751 #[expect(clippy::too_many_arguments)]
752 pub fn draw_border (&mut self,
753 left : pancurses::chtype,
754 right : pancurses::chtype,
755 top : pancurses::chtype,
756 bottom : pancurses::chtype,
757 top_left : pancurses::chtype,
758 top_right : pancurses::chtype,
759 bottom_left : pancurses::chtype,
760 bottom_right : pancurses::chtype,
761 rc_min : (i32, i32),
762 rc_max : (i32, i32),
763 thickness_top : u32,
764 thickness_bottom : u32,
765 thickness_left : u32,
766 thickness_right : u32
767 ) -> PancursesResult {
768 let mut out = pancurses::OK;
769 let mut result_ok = |result| if out == pancurses::OK {
771 out = result;
772 };
773 #[expect(clippy::cast_possible_wrap)]
774 let (thickness_top, thickness_bottom, thickness_left, thickness_right) = (
775 thickness_top as i32,
776 thickness_bottom as i32,
777 thickness_left as i32,
778 thickness_right as i32);
779 let (min_row, min_col) =
781 (i32::min (rc_min.0, rc_max.0), i32::min (rc_min.1, rc_max.1));
782 let (max_row, max_col) =
783 (i32::max (rc_min.0, rc_max.0), i32::max (rc_min.1, rc_max.1));
784 let rows = max_row - min_row + 1;
785 let cols = max_col - min_col + 1;
786 let total_width = thickness_left + thickness_right;
787 let total_height = thickness_top + thickness_bottom;
788 for i in 0..thickness_top {
789 let _ = self.move_rc (min_row + i, min_col);
790 result_ok (self.win().hline (top_left, thickness_left));
791 let _ = self.move_rc (min_row + i, min_col + thickness_left);
792 result_ok (self.win().hline (top, cols - total_width));
793 let _ = self.move_rc (min_row + i, min_col + cols - thickness_right);
794 result_ok (self.win().hline (top_right, thickness_right));
795 }
796 for i in 0..thickness_bottom {
797 let _ = self.move_rc (max_row - i, min_col);
798 result_ok (self.win().hline (bottom_left, thickness_left));
799 let _ = self.move_rc (max_row - i, min_col + thickness_left);
800 result_ok (self.win().hline (bottom, cols - total_width));
801 let _ = self.move_rc (max_row - i, min_col + cols - thickness_right);
802 result_ok (self.win().hline (bottom_right, thickness_right));
803 }
804 for i in 0..thickness_left {
805 let _ = self.move_rc (min_row + thickness_top, min_col + i);
806 result_ok (self.win().vline (left, rows - total_height));
807 }
808 for i in 0..thickness_right {
809 let _ = self.move_rc (min_row + thickness_top, max_col - i);
810 result_ok (self.win().vline (right, rows - total_height));
811 }
812 out
813 }
814
815 pub fn draw_rect (&mut self,
817 border : pancurses::chtype,
818 fill : pancurses::chtype,
819 rc_min : (i32, i32),
820 rc_max : (i32, i32),
821 thickness_h : u32, thickness_v : u32 ) {
824 let mut out = pancurses::OK;
825 let mut result_ok = |result| if out == pancurses::OK {
827 out = result;
828 };
829 let (min_row, min_col) =
831 (i32::min (rc_min.0, rc_max.0), i32::min (rc_min.1, rc_max.1));
832 let (max_row, max_col) =
833 (i32::max (rc_min.0, rc_max.0), i32::max (rc_min.1, rc_max.1));
834 let rows = max_row - min_row + 1;
835 let cols = max_col - min_col + 1;
836 for i in 0..rows {
837 let _ = self.move_rc (min_row + i, min_col);
838 result_ok (self.win().hline (fill, cols));
839 }
840 #[expect(clippy::cast_possible_wrap)]
841 for i in 0..thickness_h as i32 {
842 let _ = self.move_rc (min_row + i, min_col);
843 result_ok (self.win().hline (border, cols));
844 let _ = self.move_rc (max_row - i, min_col);
845 result_ok (self.win().hline (border, cols));
846 }
847 #[expect(clippy::cast_possible_wrap)]
848 for i in 0..thickness_v as i32 {
849 let _ = self.move_rc (min_row, min_col + i);
850 result_ok (self.win().vline (border, rows));
851 let _ = self.move_rc (min_row, max_col - i);
852 result_ok (self.win().vline (border, rows));
853 }
854 }
855
856 #[expect(clippy::missing_panics_doc)]
858 pub fn draw_image_color_pair (&mut self,
859 at_rc : (i32, i32), image : &Mat <pancurses::ColorPair>
860 ) {
861 let mut row = 0;
862 while row < image.height() {
863 let mut col = 0;
864 loop {
865 let color = *image.get ((row, col).into()).unwrap();
866 #[expect(clippy::cast_possible_truncation)]
867 #[expect(clippy::cast_possible_wrap)]
868 let start = col as i32;
869 col += 1;
870 while col < image.width()-1 {
871 if color == *image.get ((row, col).into()).unwrap() {
872 col += 1;
873 } else {
874 break
875 }
876 }
877 #[expect(clippy::cast_possible_truncation)]
879 #[expect(clippy::cast_possible_wrap)]
880 let _ = self.win().mvchgat (
881 at_rc.0 + row as i32,
882 at_rc.1 + start,
883 col as i32 - start,
884 0x0, color.0 as i16);
885 if col == image.width() {
886 break
887 }
888 }
889 row += 1;
890 }
891 }
892
893 #[expect(clippy::missing_panics_doc)]
895 pub fn draw_image_chtype (&mut self,
896 at_rc : (i32, i32), image : &Mat <pancurses::chtype>
897 ) {
898 let dimensions = image.dimensions();
899 for row in 0..dimensions.rows {
900 #[expect(clippy::cast_possible_truncation)]
901 #[expect(clippy::cast_possible_wrap)]
902 for col in 0..dimensions.columns {
903 let ch = *image.get ((row, col).into()).unwrap();
904 let at_row = at_rc.0 + row as i32;
905 let at_col = at_rc.1 + col as i32;
906 let _ = self.win().mvaddch (at_row, at_col, ch);
908 }
909 }
910 }
911
912 pub fn log_info (&self) {
914 log::info!("log info...");
915 log::info!(" cursor(row,col): {:?}", self.get_cursor_rc());
916 log::info!(" dimensions(row,col): {:?}", self.dimensions_rc());
917 log::info!(" is color terminal: {}", self.is_color_terminal());
918 log::info!(" can change color: {}", pancurses::can_change_color());
919 log::info!(" maximum color pairs: {}", pancurses::COLOR_PAIRS());
920 log::info!(" maximum colors: {}", pancurses::COLORS());
921 log::info!(" colors:");
922 #[expect(clippy::cast_possible_truncation)]
923 for i in 0..pancurses::COLORS() as i16 {
924 let color_string = if let Some (color) = color_from_primitive (i) {
925 format!("{color:?}")
926 } else {
927 i.to_string()
928 };
929 let rgb = pancurses::color_content (i);
930 log::info!(" {color_string}: {rgb:?}");
931 }
932 log::info!("...log info");
933 }
934}
935
936impl Default for Curses {
937 fn default() -> Self {
939 Curses::new (None)
940 }
941}
942
943impl std::ops::Deref for Curses {
944 type Target = EasyCurses;
945 fn deref (&self) -> &EasyCurses {
946 &self.easycurses
947 }
948}
949
950impl std::ops::DerefMut for Curses {
951 fn deref_mut (&mut self) -> &mut EasyCurses {
952 &mut self.easycurses
953 }
954}
955
956#[cfg(test)]
957mod tests {
958 #[test]
960 fn color_from_primitive() {
961 use super::color_from_primitive;
962 for i in 0..8 {
963 assert_eq!(i, color_from_primitive (i).unwrap() as i16);
964 }
965 }
966}