1use std::error::Error;
2use std::ffi::c_int;
3use std::fmt::{Display, Formatter};
4use std::sync::{Mutex, MutexGuard};
5
6#[cfg(feature = "custom_panic_hook")]
7use std::sync::Once;
8
9mod bindings {
10 use std::ffi::{c_char, c_int};
11
12 unsafe extern "C" {
13 pub fn clrscr();
14
15 pub fn initConsole();
16 pub fn reset();
17
18 pub fn getConsoleSize(columns_ret: *mut c_int, rows_ret: *mut c_int);
19
20 pub fn hasInput() -> c_int;
21 pub fn getKey() -> c_int;
22
23 pub fn getMousePosClicked(column: *mut c_int, row: *mut c_int);
24
25 pub fn drawText(text: *const c_char);
26
27 pub fn setColor(fg: c_int, bg: c_int);
28 pub fn resetColor();
29
30 pub fn setUnderline(underline: c_int);
31
32 pub fn setCursorPos(x: c_int, y: c_int);
33 }
34}
35
36pub struct Console<'a> {
39 _lock: MutexGuard<'a, ()>
40}
41
42static CONSOLE_MUTEX: Mutex<()> = Mutex::new(());
43
44#[cfg(feature = "custom_panic_hook")]
45static CONSOLE_PANIC_HOOK: Once = Once::new();
46
47impl Console<'_> {
48 pub fn new() -> Result<Self, Box<ConsoleError>> {
74 let lock = match CONSOLE_MUTEX.try_lock() {
75 Ok(lock) => lock,
76 Err(_) => {
77 return Err(Box::new(ConsoleError::new("Only one instance of Console can exist at once!")));
78 },
79 };
80
81 unsafe { bindings::initConsole() };
82
83 #[cfg(feature = "custom_panic_hook")]
84 {
85 CONSOLE_PANIC_HOOK.call_once(|| {
86 let default_panic_hook = std::panic::take_hook();
87 std::panic::set_hook(Box::new(move |panic_info| {
88 if CONSOLE_MUTEX.try_lock().is_err() {
90 unsafe { bindings::reset() };
91 }
92
93 default_panic_hook(panic_info);
94 }));
95 });
96 }
97
98 Ok(Self { _lock: lock })
99 }
100
101 pub fn repaint(&self) {
103 unsafe { bindings::clrscr() }
104 }
105
106 pub fn get_console_size(&self) -> (usize, usize) {
112 let mut columns_int: c_int = -1;
113 let mut rows_int: c_int = -1;
114
115 unsafe { bindings::getConsoleSize(&mut columns_int, &mut rows_int) }
116
117 (columns_int as usize, rows_int as usize)
118 }
119
120 pub fn has_input(&self) -> bool {
122 unsafe { bindings::hasInput() != 0 }
123 }
124
125 pub fn get_key(&self) -> Option<Key> {
127 let key = unsafe { bindings::getKey() as i32 };
128
129 if key < 0 {
130 None
131 }else {
132 Some(Key(key as u16))
133 }
134 }
135
136 pub fn get_mouse_pos_clicked(&self) -> Option<(usize, usize)> {
142 let mut column_int: c_int = -1;
143 let mut row_int: c_int = -1;
144
145 unsafe { bindings::getMousePosClicked(&mut column_int, &mut row_int) }
146
147 if column_int < 0 || row_int < 0 {
148 None
149 }else {
150 Some((column_int as usize, row_int as usize))
151 }
152 }
153
154 pub fn draw_text(&self, text: impl Into<String>) {
160 let text = std::ffi::CString::new(text.into()).unwrap();
161
162 unsafe { bindings::drawText(text.as_ptr()) }
163 }
164
165 pub fn set_color(&self, fg: Color, bg: Color) {
167 unsafe { bindings::setColor(fg as c_int, bg as c_int) }
168 }
169
170 pub fn set_color_invertible(&self, fg: Color, bg: Color, inverted: bool) {
174 if inverted {
175 self.set_color(bg, fg);
176 }else {
177 self.set_color(fg, bg);
178 }
179 }
180
181 pub fn reset_color(&self) {
183 unsafe { bindings::resetColor() }
184 }
185
186 pub fn set_underline(&self, underline: bool) {
187 unsafe { bindings::setUnderline(underline as c_int) }
188 }
189
190 pub fn set_cursor_pos(&self, x: usize, y: usize) {
192 let x = x as c_int;
193 let y = y as c_int;
194
195 if x < 0 || y < 0 {
196 return;
197 }
198
199 unsafe { bindings::setCursorPos(x, y) }
200 }
201}
202
203impl Drop for Console<'_> {
204 fn drop(&mut self) {
205 #[cfg(feature = "custom_panic_hook")]
206 if std::thread::panicking() {
207 return;
209 }
210
211 unsafe { bindings::reset() };
212 }
213}
214
215#[derive(Debug, Clone, Copy, Ord, PartialOrd, Eq, PartialEq, Hash)]
221pub struct Key(u16);
222
223impl Key {
224 pub const SPACE: Key = Key(b' ' as u16);
226 pub const EXCLAMATION_MARK: Key = Key(b'!' as u16);
227 pub const QUOTATION_MARK: Key = Key(b'"' as u16);
228 pub const NUMBER_SIGN: Key = Key(b'#' as u16);
229 pub const DOLLAR: Key = Key(b'$' as u16);
230 pub const PERCENT_SIGN: Key = Key(b'%' as u16);
231 pub const AMPERSAND: Key = Key(b'&' as u16);
232 pub const APOSTROPHE: Key = Key(b'\'' as u16);
233 pub const LEFT_PARENTHESIS: Key = Key(b'(' as u16);
234 pub const RIGHT_PARENTHESIS: Key = Key(b')' as u16);
235 pub const ASTERISK: Key = Key(b'*' as u16);
236 pub const PLUS: Key = Key(b'+' as u16);
237 pub const COMMA: Key = Key(b',' as u16);
238 pub const MINUS: Key = Key(b'-' as u16);
239 pub const DOT: Key = Key(b'.' as u16);
240 pub const SLASH: Key = Key(b'/' as u16);
241
242 pub const COLON: Key = Key(b':' as u16);
243 pub const SEMICOLON: Key = Key(b';' as u16);
244 pub const LESS_THAN_SIGN: Key = Key(b'<' as u16);
245 pub const EQUALS_SIGN: Key = Key(b'=' as u16);
246 pub const GREATER_THAN_SIGN: Key = Key(b'>' as u16);
247 pub const QUESTION_MARK: Key = Key(b'?' as u16);
248 pub const AT_SIGN: Key = Key(b'@' as u16);
249
250 pub const LEFT_BRACKET: Key = Key(b'[' as u16);
251 pub const BACKSLASH: Key = Key(b'\\' as u16);
252 pub const RIGHT_BRACKET: Key = Key(b']' as u16);
253 pub const CARET: Key = Key(b'^' as u16);
254 pub const UNDERSCORE: Key = Key(b'_' as u16);
255 pub const BACKTICK: Key = Key(b'`' as u16);
256
257 pub const LEFT_CURLY_BRACKET: Key = Key(b'{' as u16);
258 pub const VERTICAL_BAR: Key = Key(b'|' as u16);
259 pub const RIGHT_CURLY_BRACKET: Key = Key(b'}' as u16);
260 pub const TILDE: Key = Key(b'~' as u16);
261
262 pub const DIGIT_0: Key = Key(b'0' as u16);
263 pub const DIGIT_1: Key = Key(b'1' as u16);
264 pub const DIGIT_2: Key = Key(b'2' as u16);
265 pub const DIGIT_3: Key = Key(b'3' as u16);
266 pub const DIGIT_4: Key = Key(b'4' as u16);
267 pub const DIGIT_5: Key = Key(b'5' as u16);
268 pub const DIGIT_6: Key = Key(b'6' as u16);
269 pub const DIGIT_7: Key = Key(b'7' as u16);
270 pub const DIGIT_8: Key = Key(b'8' as u16);
271 pub const DIGIT_9: Key = Key(b'9' as u16);
272
273 pub const A: Key = Key(b'a' as u16);
274 pub const B: Key = Key(b'b' as u16);
275 pub const C: Key = Key(b'c' as u16);
276 pub const D: Key = Key(b'd' as u16);
277 pub const E: Key = Key(b'e' as u16);
278 pub const F: Key = Key(b'f' as u16);
279 pub const G: Key = Key(b'g' as u16);
280 pub const H: Key = Key(b'h' as u16);
281 pub const I: Key = Key(b'i' as u16);
282 pub const J: Key = Key(b'j' as u16);
283 pub const K: Key = Key(b'k' as u16);
284 pub const L: Key = Key(b'l' as u16);
285 pub const M: Key = Key(b'm' as u16);
286 pub const N: Key = Key(b'n' as u16);
287 pub const O: Key = Key(b'o' as u16);
288 pub const P: Key = Key(b'p' as u16);
289 pub const Q: Key = Key(b'q' as u16);
290 pub const R: Key = Key(b'r' as u16);
291 pub const S: Key = Key(b's' as u16);
292 pub const T: Key = Key(b't' as u16);
293 pub const U: Key = Key(b'u' as u16);
294 pub const V: Key = Key(b'v' as u16);
295 pub const W: Key = Key(b'w' as u16);
296 pub const X: Key = Key(b'x' as u16);
297 pub const Y: Key = Key(b'y' as u16);
298 pub const Z: Key = Key(b'z' as u16);
299
300 pub const LEFT: Key = Key(5000);
302 pub const UP: Key = Key(5001);
303 pub const RIGHT: Key = Key(5002);
304 pub const DOWN: Key = Key(5003);
305
306 pub const F1: Key = Key(5004);
308 pub const F2: Key = Key(5005);
309 pub const F3: Key = Key(5006);
310 pub const F4: Key = Key(5007);
311 pub const F5: Key = Key(5008);
312 pub const F6: Key = Key(5009);
313 pub const F7: Key = Key(5010);
314 pub const F8: Key = Key(5011);
315 pub const F9: Key = Key(5012);
316 pub const F10: Key = Key(5013);
317 pub const F11: Key = Key(5014);
318 pub const F12: Key = Key(5015);
319
320 pub const ESC: Key = Key(5016);
322 pub const DELETE: Key = Key(5017);
323 pub const ENTER: Key = Key(5018);
324 pub const TAB: Key = Key(5019);
325}
326
327impl Key {
328 pub fn is_arrow_key(&self) -> bool {
329 (Key::LEFT..=Key::DOWN).contains(self)
330 }
331
332 pub fn to_ascii(&self) -> Option<u8> {
334 self.is_ascii().then_some(self.0 as u8)
335 }
336
337 pub fn is_ascii(&self) -> bool {
338 (0..=127).contains(&self.0)
339 }
340
341 pub fn is_numeric(&self) -> bool {
343 self.is_ascii() && (self.0 as u8 as char).is_numeric()
344 }
345
346 pub fn is_alphanumeric(&self) -> bool {
348 self.is_ascii() && (self.0 as u8 as char).is_alphanumeric()
349 }
350}
351
352#[repr(i8)]
354#[derive(Debug, Clone, Copy, Ord, PartialOrd, Eq, PartialEq, Hash)]
355pub enum Color {
356 Black,
357 Blue,
358 Green,
359 Cyan,
360 Red,
361 Pink,
362 Yellow,
363 White,
364 LightBlack,
365 LightBlue,
366 LightGreen,
367 LightCyan,
368 LightRed,
369 LightPink,
370 LightYellow,
371 LightWhite,
372
373 Default = -1
375}
376
377#[derive(Debug)]
379pub struct ConsoleError {
380 message: String
381}
382
383impl ConsoleError {
384 fn new(message: impl Into<String>) -> Self {
385 Self { message: message.into() }
386 }
387}
388
389impl Display for ConsoleError {
390 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
391 f.write_str(&self.message)
392 }
393}
394
395impl Error for ConsoleError {}