1use crate::area::Area;
2use crate::aspect_ratio::AspectRatio;
3use crate::axis::{Axis, SizeForAxis};
4use crate::dividing::VerticalDividingHelper;
5use crate::rotate::QuarterRotation;
6use num_traits::{Float, Num, NumAssignOps, NumOps};
7#[derive(Debug, PartialEq, Clone, Copy)]
10pub struct Rectangle<T>
11where
12 T: Copy + Num + NumAssignOps + NumOps,
13{
14 width: T,
15 height: T,
16}
17
18impl<T> Rectangle<T>
19where
20 T: Copy + Num + NumAssignOps + NumOps + Float,
21{
22 pub fn round(&self) -> Self {
23 Self {
24 width: self.width.round(),
25 height: self.height.round(),
26 }
27 }
28}
29
30impl<T> SizeForAxis<T> for Rectangle<T>
31where
32 T: Copy + Num + NumAssignOps + NumOps,
33{
34 fn size_for_axis(&self, axis: Axis) -> T {
35 match axis {
36 Axis::Vertical => self.width,
37 Axis::Horizontal => self.height,
38 }
39 }
40}
41
42pub trait RectangleSize<T>
43where
44 T: Copy + Num + NumAssignOps + NumOps,
45{
46 fn width(&self) -> T;
47 fn height(&self) -> T;
48}
49
50impl<T> RectangleSize<T> for Rectangle<T>
52where
53 T: Copy + Num + NumAssignOps + NumOps,
54{
55 fn width(&self) -> T {
56 self.width
57 }
58
59 fn height(&self) -> T {
60 self.height
61 }
62}
63
64impl<T> Area<T> for Rectangle<T>
65where
66 T: Copy + Num + NumAssignOps + NumOps,
67{
68 fn area(&self) -> T {
69 self.width * self.height
70 }
71}
72
73impl<T> Rectangle<T>
75where
76 T: Copy + Num + NumAssignOps + NumOps,
77{
78 pub fn new(width: T, height: T) -> Self {
79 Self { width, height }
80 }
81}
82
83impl<T> QuarterRotation for Rectangle<T>
85where
86 T: Copy + Num + NumAssignOps + NumOps,
87{
88 fn rotate_clockwise(&self) -> Self {
89 Self {
90 width: self.height,
91 height: self.width,
92 }
93 }
94}
95
96impl<T> VerticalDividingHelper<T> for Rectangle<T>
97where
98 T: Copy + Num + NumAssignOps + NumOps,
99{
100 fn divide_vertical_helper(&self, x: T) -> (Rectangle<T>, Rectangle<T>) {
102 (
103 Self::new(x, self.height),
104 Self::new(self.width - x, self.height),
105 )
106 }
107}
108
109impl<T> AspectRatio<T> for Rectangle<T>
110where
111 T: Copy + Num + NumAssignOps + NumOps,
112{
113 fn aspect_ratio(&self) -> T {
114 self.width / self.height
115 }
116}
117
118#[cfg(test)]
119mod tests {
120 use crate::axis::Axis;
121 use crate::dividing::Dividing;
122
123 use super::*;
124
125 #[test]
126 fn test_new() {
127 let result = Rectangle::new(2, 3);
128 assert_eq!(result.width, 2);
129 assert_eq!(result.height, 3);
130 }
131
132 #[test]
133 fn test_identity() {
134 let rect = Rectangle::new(2, 3);
136 assert_rect_eq(&rect, &rect);
137 }
138
139 #[test]
140 fn test_rotate() {
141 assert_rotate_twice_is_same_as_original(&Rectangle::new(2, 3));
142 }
143
144 #[test]
145 fn test_area() {
146 let result = Rectangle::new(2, 3).area();
147 assert_eq!(result, 6);
148 }
149
150 #[test]
151 fn test_aspect_ratio() {
152 let result = Rectangle::new(16.0, 9.0).aspect_ratio();
153 assert_eq!(result, 1.7777777777777777);
154 let result = Rectangle::new(1920.0, 1080.0).aspect_ratio();
155 assert_eq!(result, 1.7777777777777777);
156 }
157
158 #[test]
159 fn test_divide_vertical() {
160 let (rect_a, rect_b) = Rectangle::new(4, 2).divide_vertical(1);
161 assert_rect_eq(&rect_a, &Rectangle::new(1, 2));
162 assert_rect_eq(&rect_b, &Rectangle::new(3, 2));
163 }
164
165 #[test]
166 fn test_divide_horizontal() {
167 let (rect_a, rect_b) = Rectangle::new(2, 4).divide_horizontal(1);
168 assert_rect_eq(&rect_a, &Rectangle::new(2, 1));
169 assert_rect_eq(&rect_b, &Rectangle::new(2, 3));
170 }
171
172 #[test]
173 fn test_divide() {
174 let (rect_a, rect_b) = Rectangle::new(4, 2).divide(1, Axis::Vertical);
175 assert_rect_eq(&rect_a, &Rectangle::new(1, 2));
176 assert_rect_eq(&rect_b, &Rectangle::new(3, 2));
177
178 let (rect_a, rect_b) = Rectangle::new(2, 4).divide(1, Axis::Horizontal);
179 assert_rect_eq(&rect_a, &Rectangle::new(2, 1));
180 assert_rect_eq(&rect_b, &Rectangle::new(2, 3));
181 }
182
183 #[test]
184 fn test_divide_nth() {
185 let rect = Rectangle::new(6, 2);
186 let divided = rect.divide_by_values_and_axis(&vec![1, 2], Axis::Vertical);
187 assert_rect_eq(÷d[0], &Rectangle::new(1, 2));
188 assert_rect_eq(÷d[1], &Rectangle::new(2, 2));
189 assert_rect_eq(÷d[2], &Rectangle::new(3, 2));
190
191 let rect = Rectangle::new(2, 6);
192 let divided = rect.divide_by_values_and_axis(&vec![3, 2], Axis::Horizontal);
193 assert_rect_eq(÷d[0], &Rectangle::new(2, 3));
194 assert_rect_eq(÷d[1], &Rectangle::new(2, 2));
195 assert_rect_eq(÷d[2], &Rectangle::new(2, 1));
196 }
197
198 #[test]
199 fn test_divide_by_weights() {
200 let rect = Rectangle::new(6.0, 2.0);
201 let divided1 = rect.divide_by_values_and_axis(&vec![1.0, 2.0], Axis::Vertical);
203
204 let rect = Rectangle::new(6.0, 2.0);
205 let divided2 = rect.divide_by_weights_and_axis(&vec![2.0, 4.0, 6.0], Axis::Vertical);
206 assert_eq!(divided1, divided2);
207 }
208
209 fn assert_rect_eq(rect1: &Rectangle<i32>, rect2: &Rectangle<i32>) {
211 assert_rect_has_same_component_is_equal(rect1, rect2);
212 }
213
214 fn assert_rect_has_same_component_is_equal<T>(rect1: &Rectangle<T>, rect2: &Rectangle<T>)
216 where
217 T: Copy + PartialEq + std::fmt::Debug + Num + NumAssignOps,
218 {
219 assert_eq!(rect1.width, rect2.width);
220 assert_eq!(rect1.height, rect2.height);
221 assert_eq!(rect1, rect2);
222 }
223
224 fn assert_rotate_twice_is_same_as_original<T>(rect: &Rectangle<T>)
226 where
227 T: Copy + PartialEq + std::fmt::Debug + Num + NumAssignOps,
228 {
229 let rotated_twice = rect.rotate_clockwise().rotate_clockwise();
230 assert_rect_has_same_component_is_equal(rect, &rotated_twice);
231 assert_eq!(rotated_twice, *rect);
232 }
233}