1 use crate::display::color::Color; 2 use crate::display::color_mode::ColorMode; 3 use crate::display::utils::detect_color_mode; 4 use pancurses::{ 5 chtype, 6 Input, 7 COLOR_BLACK, 8 COLOR_BLUE, 9 COLOR_CYAN, 10 COLOR_GREEN, 11 COLOR_MAGENTA, 12 COLOR_RED, 13 COLOR_WHITE, 14 COLOR_YELLOW, 15 }; 16 use std::collections::HashMap; 17 18 pub(crate) struct Curses { 19 color_index: i16, 20 color_lookup: HashMap<(i16, i16, i16), i16>, 21 color_mode: ColorMode, 22 color_pair_index: i16, 23 window: pancurses::Window, 24 } 25 26 impl Curses { new() -> Self27 pub(crate) fn new() -> Self { 28 let window = pancurses::initscr(); 29 window.keypad(true); 30 31 pancurses::curs_set(0); 32 pancurses::noecho(); 33 34 let has_colors = pancurses::has_colors(); 35 if has_colors { 36 pancurses::start_color(); 37 pancurses::use_default_colors(); 38 39 // pair zero should always be default 40 pancurses::init_pair(0, -1, -1); 41 } 42 43 let color_mode = if has_colors { 44 detect_color_mode(pancurses::COLORS() as i16) 45 } 46 else { 47 ColorMode::TwoTone 48 }; 49 50 Self { 51 color_index: 16, // we only create new colors in true color mode 52 window, 53 color_pair_index: 16, // skip the default color pairs 54 color_lookup: HashMap::new(), 55 color_mode, 56 } 57 } 58 init_color(&mut self, red: i16, green: i16, blue: i16) -> i1659 fn init_color(&mut self, red: i16, green: i16, blue: i16) -> i16 { 60 match self.color_lookup.get(&(red, green, blue)) { 61 Some(index) => *index, 62 None => { 63 let index = self.color_index; 64 self.color_index += 1; 65 pancurses::init_color( 66 index, 67 // convert from 0-255 range to 0 - 1000 68 ((f64::from(red) / 255.0) * 1000.0) as i16, 69 ((f64::from(green) / 255.0) * 1000.0) as i16, 70 ((f64::from(blue) / 255.0) * 1000.0) as i16, 71 ); 72 self.color_lookup.insert((red, green, blue), index); 73 index 74 }, 75 } 76 } 77 78 // Modified version from gyscos/cursive (https://github.com/gyscos/cursive) 79 // Copyright (c) 2015 Alexandre Bury - MIT License find_color(&mut self, color: Color) -> i1680 fn find_color(&mut self, color: Color) -> i16 { 81 match color { 82 Color::Default => -1, 83 Color::LightBlack => COLOR_BLACK, 84 Color::LightBlue => COLOR_BLUE, 85 Color::LightCyan => COLOR_CYAN, 86 Color::LightGreen => COLOR_GREEN, 87 Color::LightMagenta => COLOR_MAGENTA, 88 Color::LightRed => COLOR_RED, 89 Color::LightYellow => COLOR_YELLOW, 90 Color::LightWhite => COLOR_WHITE, 91 // for dark colors, just the light color when there isn't deep enough color support 92 Color::DarkBlack => { 93 if self.color_mode.has_minimum_four_bit_color() { 94 COLOR_BLACK + 8 95 } 96 else { 97 COLOR_BLACK 98 } 99 }, 100 Color::DarkBlue => { 101 if self.color_mode.has_minimum_four_bit_color() { 102 COLOR_BLUE + 8 103 } 104 else { 105 COLOR_BLUE 106 } 107 }, 108 Color::DarkCyan => { 109 if self.color_mode.has_minimum_four_bit_color() { 110 COLOR_CYAN + 8 111 } 112 else { 113 COLOR_CYAN 114 } 115 }, 116 Color::DarkGreen => { 117 if self.color_mode.has_minimum_four_bit_color() { 118 COLOR_GREEN + 8 119 } 120 else { 121 COLOR_GREEN 122 } 123 }, 124 Color::DarkMagenta => { 125 if self.color_mode.has_minimum_four_bit_color() { 126 COLOR_MAGENTA + 8 127 } 128 else { 129 COLOR_MAGENTA 130 } 131 }, 132 Color::DarkRed => { 133 if self.color_mode.has_minimum_four_bit_color() { 134 COLOR_RED + 8 135 } 136 else { 137 COLOR_RED 138 } 139 }, 140 Color::DarkYellow => { 141 if self.color_mode.has_minimum_four_bit_color() { 142 COLOR_YELLOW + 8 143 } 144 else { 145 COLOR_YELLOW 146 } 147 }, 148 Color::DarkWhite => { 149 if self.color_mode.has_minimum_four_bit_color() { 150 COLOR_WHITE + 8 151 } 152 else { 153 COLOR_WHITE 154 } 155 }, 156 // for indexed colored we assume 8bit color 157 Color::Index(i) => i, 158 Color::RGB { red, green, blue } if self.color_mode.has_true_color() => self.init_color(red, green, blue), 159 Color::RGB { red, green, blue } if self.color_mode.has_minimum_four_bit_color() => { 160 // If red, green and blue are equal then we assume a grey scale color 161 // shades less than 8 should go to pure black, while shades greater than 247 should go to pure white 162 if red == green && green == blue && red >= 8 && red < 247 { 163 // The grayscale palette says the colors 232 + n are: (red = green = blue) = 8 + 10 * n 164 // With 0 <= n <= 23. This gives: (red - 8) / 10 = n 165 let n = (red - 8) / 10; 166 232 + n 167 } 168 else { 169 // Generic RGB 170 let r = 6 * red / 256; 171 let g = 6 * green / 256; 172 let b = 6 * blue / 256; 173 16 + 36 * r + 6 * g + b 174 } 175 }, 176 Color::RGB { red, green, blue } => { 177 // Have to hack it down to 8 colors. 178 let r = if red > 127 { 1 } else { 0 }; 179 let g = if green > 127 { 1 } else { 0 }; 180 let b = if blue > 127 { 1 } else { 0 }; 181 (r + 2 * g + 4 * b) as i16 182 }, 183 } 184 } 185 init_color_pair(&mut self, foreground: Color, background: Color) -> chtype186 fn init_color_pair(&mut self, foreground: Color, background: Color) -> chtype { 187 let index = self.color_pair_index; 188 self.color_pair_index += 1; 189 pancurses::init_pair(index, self.find_color(foreground), self.find_color(background)); 190 // curses seems to init a pair for i16 but read with u64 191 pancurses::COLOR_PAIR(index as chtype) 192 } 193 register_selectable_color_pairs( &mut self, foreground: Color, background: Color, selected_background: Color, ) -> (chtype, chtype)194 pub(super) fn register_selectable_color_pairs( 195 &mut self, 196 foreground: Color, 197 background: Color, 198 selected_background: Color, 199 ) -> (chtype, chtype) 200 { 201 let standard_pair = self.init_color_pair(foreground, background); 202 if self.color_mode.has_minimum_four_bit_color() { 203 return (standard_pair, self.init_color_pair(foreground, selected_background)); 204 } 205 // when there is not enough color pairs to support selected 206 (standard_pair, standard_pair) 207 } 208 erase(&self)209 pub(super) fn erase(&self) { 210 self.window.erase(); 211 } 212 refresh(&self)213 pub(super) fn refresh(&self) { 214 self.window.refresh(); 215 } 216 addstr(&self, s: &str)217 pub(super) fn addstr(&self, s: &str) { 218 self.window.addstr(s); 219 } 220 attrset<T: Into<chtype>>(&self, attributes: T)221 pub(super) fn attrset<T: Into<chtype>>(&self, attributes: T) { 222 self.window.attrset(attributes); 223 } 224 attron<T: Into<chtype>>(&self, attributes: T)225 pub(super) fn attron<T: Into<chtype>>(&self, attributes: T) { 226 self.window.attron(attributes); 227 } 228 attroff<T: Into<chtype>>(&self, attributes: T)229 pub(super) fn attroff<T: Into<chtype>>(&self, attributes: T) { 230 self.window.attroff(attributes); 231 } 232 getch(&self) -> Option<Input>233 pub(super) fn getch(&self) -> Option<Input> { 234 self.window.getch() 235 } 236 get_max_y(&self) -> i32237 pub(super) fn get_max_y(&self) -> i32 { 238 self.window.get_max_y() 239 } 240 get_max_x(&self) -> i32241 pub(super) fn get_max_x(&self) -> i32 { 242 self.window.get_max_x() 243 } 244 def_prog_mode(&self)245 pub(super) fn def_prog_mode(&self) { 246 pancurses::def_prog_mode(); 247 } 248 reset_prog_mode(&self)249 pub(super) fn reset_prog_mode(&self) { 250 pancurses::reset_prog_mode(); 251 } 252 endwin(&self)253 pub(super) fn endwin(&self) { 254 pancurses::endwin(); 255 } 256 } 257