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