1#[cfg(not(feature = "std"))]
5use crate::float::Float;
6
7#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
11pub struct ConsoleSpec {
12 pub cols: u16,
14 pub rows: u16,
16 pub cell_w: u16,
18 pub cell_h: u16,
20}
21
22impl ConsoleSpec {
23 pub const fn text(cols: u16, rows: u16) -> Self {
26 Self {
27 cols,
28 rows,
29 cell_w: 8,
30 cell_h: 16,
31 }
32 }
33
34 #[must_use]
36 pub const fn sized(self, cols: u16, rows: u16) -> Self {
37 Self { cols, rows, ..self }
38 }
39
40 pub fn cell_px(self) -> (i32, i32) {
42 (i32::from(self.cell_w), i32::from(self.cell_h))
43 }
44
45 pub fn px(self) -> (i32, i32) {
47 (
48 i32::from(self.cols) * i32::from(self.cell_w),
49 i32::from(self.rows) * i32::from(self.cell_h),
50 )
51 }
52}
53
54#[derive(Copy, Clone, PartialEq, Debug)]
57pub struct Layout {
58 pub scale: u32,
60 pub offset_x: f32,
62 pub offset_y: f32,
64}
65
66pub fn layout(spec: ConsoleSpec, screen_w: f32, screen_h: f32) -> Layout {
70 let console_w = f32::from(spec.cols) * f32::from(spec.cell_w);
71 let console_h = f32::from(spec.rows) * f32::from(spec.cell_h);
72 let finite = |v: f32| if v.is_finite() { v } else { 0.0 };
73 let (screen_w, screen_h) = (finite(screen_w), finite(screen_h));
74 let fit = (screen_w / console_w).min(screen_h / console_h).floor();
75 #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
77 let scale = (fit.max(1.0) as u32).max(1);
78 #[allow(clippy::cast_precision_loss)] let s = scale as f32;
80 Layout {
81 scale,
82 offset_x: ((screen_w - console_w * s) / 2.0).floor(),
83 offset_y: ((screen_h - console_h * s) / 2.0).floor(),
84 }
85}
86
87#[cfg(test)]
88mod tests {
89 use super::*;
90
91 const WIDE: ConsoleSpec = ConsoleSpec::text(100, 32);
93
94 fn l(scale: u32, offset_x: f32, offset_y: f32) -> Layout {
95 Layout {
96 scale,
97 offset_x,
98 offset_y,
99 }
100 }
101
102 #[test]
103 fn exact_fit() {
104 assert_eq!(layout(WIDE, 800.0, 512.0), l(1, 0.0, 0.0));
105 assert_eq!(layout(WIDE, 1600.0, 1024.0), l(2, 0.0, 0.0));
106 assert_eq!(layout(WIDE, 2400.0, 1536.0), l(3, 0.0, 0.0));
107 }
108
109 #[test]
110 fn oversize_window_letterboxes() {
111 assert_eq!(layout(WIDE, 1920.0, 1080.0), l(2, 160.0, 28.0));
113 assert_eq!(layout(WIDE, 3000.0, 1100.0), l(2, 700.0, 38.0));
115 assert_eq!(layout(WIDE, 1700.0, 5000.0), l(2, 50.0, 1988.0));
117 assert_eq!(layout(WIDE, 801.0, 513.0), l(1, 0.0, 0.0));
119 assert_eq!(layout(WIDE, 803.0, 515.0), l(1, 1.0, 1.0));
120 }
121
122 #[test]
123 fn undersize_window_keeps_scale_1_and_centres() {
124 assert_eq!(layout(WIDE, 600.0, 412.0), l(1, -100.0, -50.0));
125 assert_eq!(layout(WIDE, 799.0, 1024.0), l(1, -1.0, 256.0));
126 assert_eq!(layout(WIDE, 0.0, 0.0), l(1, -400.0, -256.0));
127 assert_eq!(layout(WIDE, -5.0, -5.0).scale, 1);
128 }
129
130 #[test]
131 fn a_console_of_any_size_fits_its_own_size() {
132 let small = ConsoleSpec::text(50, 15);
133 assert_eq!(small.px(), (400, 240));
134 assert_eq!(WIDE.px(), (800, 512));
135 assert_eq!(small.cell_px(), (8, 16));
136 assert_eq!(
137 small.sized(3, 2),
138 ConsoleSpec {
139 cols: 3,
140 rows: 2,
141 ..small
142 }
143 );
144 assert_eq!(layout(small, 400.0, 240.0), l(1, 0.0, 0.0));
145 assert_eq!(layout(small, 1920.0, 1080.0), l(4, 160.0, 60.0));
147 let square = ConsoleSpec {
149 cell_h: 8,
150 ..ConsoleSpec::text(40, 20)
151 };
152 assert_eq!(square.px(), (320, 160));
153 assert_eq!(square.cell_px(), (8, 8));
154 assert_eq!(layout(square, 1920.0, 1080.0), l(6, 0.0, 60.0));
155 }
156
157 #[test]
158 fn non_finite_sizes_act_as_zero() {
159 assert_eq!(layout(WIDE, f32::NAN, f32::INFINITY), l(1, -400.0, -256.0));
160 assert_eq!(layout(WIDE, f32::NEG_INFINITY, 512.0), l(1, -400.0, 0.0));
161 }
162}