1use std::io;
4
5use crate::{Position, TerminalCapabilities, TerminalSize};
6
7#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
11pub struct TerminalBackground {
12 red: u16,
13 green: u16,
14 blue: u16,
15}
16
17impl TerminalBackground {
18 pub const fn new(red: u16, green: u16, blue: u16) -> Self {
19 Self { red, green, blue }
20 }
21
22 pub const fn red(self) -> u16 {
23 self.red
24 }
25
26 pub const fn green(self) -> u16 {
27 self.green
28 }
29
30 pub const fn blue(self) -> u16 {
31 self.blue
32 }
33}
34
35#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
36pub struct PixelSize {
37 width: usize,
38 height: usize,
39}
40
41impl PixelSize {
42 pub const fn new(width: usize, height: usize) -> Self {
43 Self { width, height }
44 }
45
46 pub const fn width(self) -> usize {
47 self.width
48 }
49
50 pub const fn height(self) -> usize {
51 self.height
52 }
53}
54
55#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
57pub struct WindowSize {
58 cells: TerminalSize,
59 pixels: Option<PixelSize>,
60}
61
62impl WindowSize {
63 pub const fn new(cells: TerminalSize, pixels: Option<PixelSize>) -> Self {
64 Self { cells, pixels }
65 }
66
67 pub const fn cells(self) -> TerminalSize {
68 self.cells
69 }
70
71 pub const fn pixels(self) -> Option<PixelSize> {
73 self.pixels
74 }
75
76 pub const fn cell_pixels(self) -> Option<PixelSize> {
79 let Some(pixels) = self.pixels else {
80 return None;
81 };
82 if self.cells.columns() == 0
83 || self.cells.rows() == 0
84 || pixels.width() % self.cells.columns() != 0
85 || pixels.height() % self.cells.rows() != 0
86 {
87 return None;
88 }
89 let width = pixels.width() / self.cells.columns();
90 let height = pixels.height() / self.cells.rows();
91 if width == 0 || height == 0 {
92 None
93 } else {
94 Some(PixelSize::new(width, height))
95 }
96 }
97}
98
99pub trait TerminalQuery {
105 fn terminal_size(&mut self) -> io::Result<TerminalSize>;
107
108 fn cursor_position(&mut self) -> io::Result<Position>;
110
111 fn window_size(&mut self) -> io::Result<WindowSize>;
113
114 fn raw_mode_enabled(&mut self) -> io::Result<bool>;
116
117 fn terminal_capabilities(&mut self) -> io::Result<TerminalCapabilities> {
123 Ok(TerminalCapabilities::none())
124 }
125
126 fn terminal_background(&mut self) -> io::Result<Option<TerminalBackground>> {
132 Ok(None)
133 }
134}
135
136impl<T: TerminalQuery + ?Sized> TerminalQuery for &mut T {
137 fn terminal_size(&mut self) -> io::Result<TerminalSize> {
138 T::terminal_size(self)
139 }
140
141 fn cursor_position(&mut self) -> io::Result<Position> {
142 T::cursor_position(self)
143 }
144
145 fn window_size(&mut self) -> io::Result<WindowSize> {
146 T::window_size(self)
147 }
148
149 fn raw_mode_enabled(&mut self) -> io::Result<bool> {
150 T::raw_mode_enabled(self)
151 }
152
153 fn terminal_capabilities(&mut self) -> io::Result<TerminalCapabilities> {
154 T::terminal_capabilities(self)
155 }
156
157 fn terminal_background(&mut self) -> io::Result<Option<TerminalBackground>> {
158 T::terminal_background(self)
159 }
160}
161
162pub trait KeyboardEnhancementQuery {
168 fn supports_keyboard_enhancement(&mut self) -> io::Result<bool>;
169}
170
171impl<T: KeyboardEnhancementQuery + ?Sized> KeyboardEnhancementQuery for &mut T {
172 fn supports_keyboard_enhancement(&mut self) -> io::Result<bool> {
173 T::supports_keyboard_enhancement(self)
174 }
175}
176
177#[cfg(test)]
178mod tests {
179 use super::*;
180
181 struct UnsupportedQuery;
182
183 impl TerminalQuery for UnsupportedQuery {
184 fn terminal_size(&mut self) -> io::Result<TerminalSize> {
185 Ok(TerminalSize::new(80, 24))
186 }
187
188 fn cursor_position(&mut self) -> io::Result<Position> {
189 Ok(Position::new(0, 0))
190 }
191
192 fn window_size(&mut self) -> io::Result<WindowSize> {
193 Ok(WindowSize::new(TerminalSize::new(80, 24), None))
194 }
195
196 fn raw_mode_enabled(&mut self) -> io::Result<bool> {
197 Ok(false)
198 }
199 }
200
201 #[test]
202 fn cell_pixels_require_exact_uniform_geometry() {
203 assert_eq!(
204 WindowSize::new(TerminalSize::new(80, 24), Some(PixelSize::new(800, 480)),)
205 .cell_pixels(),
206 Some(PixelSize::new(10, 20))
207 );
208 assert_eq!(
209 WindowSize::new(TerminalSize::new(80, 24), Some(PixelSize::new(801, 480)),)
210 .cell_pixels(),
211 None
212 );
213 assert_eq!(WindowSize::default().cell_pixels(), None);
214 }
215
216 #[test]
217 fn unsupported_background_queries_return_none() {
218 let mut query = UnsupportedQuery;
219 assert_eq!(query.terminal_background().unwrap(), None);
220 let mut borrowed = &mut query;
221 assert_eq!(
222 TerminalQuery::terminal_background(&mut borrowed).unwrap(),
223 None
224 );
225 }
226}