1use crate::arc::Arc;
7use crate::basics::{
8 is_stop, VertexSource, PATH_CMD_END_POLY, PATH_CMD_LINE_TO, PATH_CMD_STOP, PATH_FLAGS_CCW,
9 PATH_FLAGS_CLOSE, PI,
10};
11
12pub struct RoundedRect {
20 x1: f64,
21 y1: f64,
22 x2: f64,
23 y2: f64,
24 rx1: f64,
25 ry1: f64,
26 rx2: f64,
27 ry2: f64,
28 rx3: f64,
29 ry3: f64,
30 rx4: f64,
31 ry4: f64,
32 status: u32,
33 arc: Arc,
34}
35
36impl RoundedRect {
37 pub fn new(x1: f64, y1: f64, x2: f64, y2: f64, r: f64) -> Self {
39 let (x1, x2) = if x1 > x2 { (x2, x1) } else { (x1, x2) };
40 let (y1, y2) = if y1 > y2 { (y2, y1) } else { (y1, y2) };
41 Self {
42 x1,
43 y1,
44 x2,
45 y2,
46 rx1: r,
47 ry1: r,
48 rx2: r,
49 ry2: r,
50 rx3: r,
51 ry3: r,
52 rx4: r,
53 ry4: r,
54 status: 0,
55 arc: Arc::default_new(),
56 }
57 }
58
59 pub fn default_new() -> Self {
61 Self {
62 x1: 0.0,
63 y1: 0.0,
64 x2: 0.0,
65 y2: 0.0,
66 rx1: 0.0,
67 ry1: 0.0,
68 rx2: 0.0,
69 ry2: 0.0,
70 rx3: 0.0,
71 ry3: 0.0,
72 rx4: 0.0,
73 ry4: 0.0,
74 status: 0,
75 arc: Arc::default_new(),
76 }
77 }
78
79 pub fn rect(&mut self, x1: f64, y1: f64, x2: f64, y2: f64) {
81 self.x1 = x1;
82 self.y1 = y1;
83 self.x2 = x2;
84 self.y2 = y2;
85 if x1 > x2 {
86 self.x1 = x2;
87 self.x2 = x1;
88 }
89 if y1 > y2 {
90 self.y1 = y2;
91 self.y2 = y1;
92 }
93 }
94
95 pub fn radius(&mut self, r: f64) {
97 self.rx1 = r;
98 self.ry1 = r;
99 self.rx2 = r;
100 self.ry2 = r;
101 self.rx3 = r;
102 self.ry3 = r;
103 self.rx4 = r;
104 self.ry4 = r;
105 }
106
107 pub fn radius_xy(&mut self, rx: f64, ry: f64) {
109 self.rx1 = rx;
110 self.rx2 = rx;
111 self.rx3 = rx;
112 self.rx4 = rx;
113 self.ry1 = ry;
114 self.ry2 = ry;
115 self.ry3 = ry;
116 self.ry4 = ry;
117 }
118
119 pub fn radius_bottom_top(&mut self, rx_bottom: f64, ry_bottom: f64, rx_top: f64, ry_top: f64) {
121 self.rx1 = rx_bottom;
122 self.rx2 = rx_bottom;
123 self.rx3 = rx_top;
124 self.rx4 = rx_top;
125 self.ry1 = ry_bottom;
126 self.ry2 = ry_bottom;
127 self.ry3 = ry_top;
128 self.ry4 = ry_top;
129 }
130
131 #[allow(clippy::too_many_arguments)]
136 pub fn radius_all(
137 &mut self,
138 rx1: f64,
139 ry1: f64,
140 rx2: f64,
141 ry2: f64,
142 rx3: f64,
143 ry3: f64,
144 rx4: f64,
145 ry4: f64,
146 ) {
147 self.rx1 = rx1;
148 self.ry1 = ry1;
149 self.rx2 = rx2;
150 self.ry2 = ry2;
151 self.rx3 = rx3;
152 self.ry3 = ry3;
153 self.rx4 = rx4;
154 self.ry4 = ry4;
155 }
156
157 pub fn normalize_radius(&mut self) {
162 let dx = (self.y2 - self.y1).abs();
163 let dy = (self.x2 - self.x1).abs();
164
165 let mut k = 1.0_f64;
166 let t = dx / (self.rx1 + self.rx2);
167 if t < k {
168 k = t;
169 }
170 let t = dx / (self.rx3 + self.rx4);
171 if t < k {
172 k = t;
173 }
174 let t = dy / (self.ry1 + self.ry2);
175 if t < k {
176 k = t;
177 }
178 let t = dy / (self.ry3 + self.ry4);
179 if t < k {
180 k = t;
181 }
182
183 if k < 1.0 {
184 self.rx1 *= k;
185 self.ry1 *= k;
186 self.rx2 *= k;
187 self.ry2 *= k;
188 self.rx3 *= k;
189 self.ry3 *= k;
190 self.rx4 *= k;
191 self.ry4 *= k;
192 }
193 }
194
195 pub fn set_approximation_scale(&mut self, s: f64) {
197 self.arc.set_approximation_scale(s);
198 }
199
200 pub fn approximation_scale(&self) -> f64 {
202 self.arc.approximation_scale()
203 }
204}
205
206impl VertexSource for RoundedRect {
207 fn rewind(&mut self, _path_id: u32) {
208 self.status = 0;
209 }
210
211 fn vertex(&mut self, x: &mut f64, y: &mut f64) -> u32 {
212 let mut cmd;
213 loop {
214 match self.status {
215 0 => {
216 self.arc.init(
218 self.x1 + self.rx1,
219 self.y1 + self.ry1,
220 self.rx1,
221 self.ry1,
222 PI,
223 PI + PI * 0.5,
224 true,
225 );
226 self.arc.rewind(0);
227 self.status += 1;
228 }
229 1 => {
230 cmd = self.arc.vertex(x, y);
231 if is_stop(cmd) {
232 self.status += 1;
233 } else {
234 return cmd;
235 }
236 }
237 2 => {
238 self.arc.init(
240 self.x2 - self.rx2,
241 self.y1 + self.ry2,
242 self.rx2,
243 self.ry2,
244 PI + PI * 0.5,
245 0.0,
246 true,
247 );
248 self.arc.rewind(0);
249 self.status += 1;
250 }
251 3 => {
252 cmd = self.arc.vertex(x, y);
253 if is_stop(cmd) {
254 self.status += 1;
255 } else {
256 return PATH_CMD_LINE_TO;
257 }
258 }
259 4 => {
260 self.arc.init(
262 self.x2 - self.rx3,
263 self.y2 - self.ry3,
264 self.rx3,
265 self.ry3,
266 0.0,
267 PI * 0.5,
268 true,
269 );
270 self.arc.rewind(0);
271 self.status += 1;
272 }
273 5 => {
274 cmd = self.arc.vertex(x, y);
275 if is_stop(cmd) {
276 self.status += 1;
277 } else {
278 return PATH_CMD_LINE_TO;
279 }
280 }
281 6 => {
282 self.arc.init(
284 self.x1 + self.rx4,
285 self.y2 - self.ry4,
286 self.rx4,
287 self.ry4,
288 PI * 0.5,
289 PI,
290 true,
291 );
292 self.arc.rewind(0);
293 self.status += 1;
294 }
295 7 => {
296 cmd = self.arc.vertex(x, y);
297 if is_stop(cmd) {
298 self.status += 1;
299 } else {
300 return PATH_CMD_LINE_TO;
301 }
302 }
303 8 => {
304 cmd = PATH_CMD_END_POLY | PATH_FLAGS_CLOSE | PATH_FLAGS_CCW;
305 self.status += 1;
306 return cmd;
307 }
308 _ => {
309 return PATH_CMD_STOP;
310 }
311 }
312 }
313 }
314}
315
316#[cfg(test)]
317mod tests {
318 use super::*;
319 use crate::basics::{is_close, is_end_poly, is_move_to, is_vertex, PATH_CMD_MOVE_TO};
320
321 #[test]
322 fn test_new_normalizes_coords() {
323 let rr = RoundedRect::new(100.0, 200.0, 50.0, 30.0, 5.0);
324 assert_eq!(rr.x1, 50.0);
325 assert_eq!(rr.y1, 30.0);
326 assert_eq!(rr.x2, 100.0);
327 assert_eq!(rr.y2, 200.0);
328 }
329
330 #[test]
331 fn test_uniform_radius() {
332 let mut rr = RoundedRect::default_new();
333 rr.radius(10.0);
334 assert_eq!(rr.rx1, 10.0);
335 assert_eq!(rr.ry1, 10.0);
336 assert_eq!(rr.rx2, 10.0);
337 assert_eq!(rr.ry2, 10.0);
338 assert_eq!(rr.rx3, 10.0);
339 assert_eq!(rr.ry3, 10.0);
340 assert_eq!(rr.rx4, 10.0);
341 assert_eq!(rr.ry4, 10.0);
342 }
343
344 #[test]
345 fn test_radius_xy() {
346 let mut rr = RoundedRect::default_new();
347 rr.radius_xy(10.0, 5.0);
348 assert_eq!(rr.rx1, 10.0);
349 assert_eq!(rr.ry1, 5.0);
350 assert_eq!(rr.rx2, 10.0);
351 assert_eq!(rr.ry2, 5.0);
352 }
353
354 #[test]
355 fn test_radius_bottom_top() {
356 let mut rr = RoundedRect::default_new();
357 rr.radius_bottom_top(3.0, 4.0, 5.0, 6.0);
358 assert_eq!(rr.rx1, 3.0);
359 assert_eq!(rr.ry1, 4.0);
360 assert_eq!(rr.rx2, 3.0);
361 assert_eq!(rr.ry2, 4.0);
362 assert_eq!(rr.rx3, 5.0);
363 assert_eq!(rr.ry3, 6.0);
364 assert_eq!(rr.rx4, 5.0);
365 assert_eq!(rr.ry4, 6.0);
366 }
367
368 #[test]
369 fn test_radius_all() {
370 let mut rr = RoundedRect::default_new();
371 rr.radius_all(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0);
372 assert_eq!(rr.rx1, 1.0);
373 assert_eq!(rr.ry1, 2.0);
374 assert_eq!(rr.rx2, 3.0);
375 assert_eq!(rr.ry2, 4.0);
376 assert_eq!(rr.rx3, 5.0);
377 assert_eq!(rr.ry3, 6.0);
378 assert_eq!(rr.rx4, 7.0);
379 assert_eq!(rr.ry4, 8.0);
380 }
381
382 #[test]
383 fn test_normalize_radius_no_change() {
384 let mut rr = RoundedRect::new(0.0, 0.0, 100.0, 100.0, 5.0);
386 rr.normalize_radius();
387 assert!((rr.rx1 - 5.0).abs() < 1e-10);
388 }
389
390 #[test]
391 fn test_normalize_radius_scales_down() {
392 let mut rr = RoundedRect::new(0.0, 0.0, 20.0, 20.0, 15.0);
394 rr.normalize_radius();
395 let expected = 15.0 * (20.0 / 30.0);
398 assert!((rr.rx1 - expected).abs() < 1e-10);
399 }
400
401 #[test]
402 fn test_vertex_generation_produces_closed_shape() {
403 let mut rr = RoundedRect::new(10.0, 10.0, 90.0, 90.0, 10.0);
404 rr.rewind(0);
405
406 let mut x = 0.0;
407 let mut y = 0.0;
408 let mut vertex_count = 0;
409 let mut has_move_to = false;
410 let mut has_end_poly = false;
411
412 loop {
413 let cmd = rr.vertex(&mut x, &mut y);
414 if is_stop(cmd) {
415 break;
416 }
417 if is_move_to(cmd) {
418 has_move_to = true;
419 }
420 if is_end_poly(cmd) {
421 has_end_poly = true;
422 }
423 if is_vertex(cmd) {
424 vertex_count += 1;
425 }
426 }
427
428 assert!(has_move_to, "Should start with move_to");
429 assert!(has_end_poly, "Should end with end_poly");
430 assert!(vertex_count > 4, "Should have more than 4 vertices (arcs)");
431 }
432
433 #[test]
434 fn test_end_poly_has_close_and_ccw_flags() {
435 let mut rr = RoundedRect::new(0.0, 0.0, 100.0, 100.0, 10.0);
436 rr.rewind(0);
437
438 let mut x = 0.0;
439 let mut y = 0.0;
440
441 loop {
442 let cmd = rr.vertex(&mut x, &mut y);
443 if is_stop(cmd) {
444 panic!("Should have end_poly before stop");
445 }
446 if is_end_poly(cmd) {
447 assert!(is_close(cmd), "end_poly should have close flag");
448 assert!((cmd & PATH_FLAGS_CCW) != 0, "Should have CCW flag");
449 break;
450 }
451 }
452 }
453
454 #[test]
455 fn test_first_vertex_is_on_bottom_left_arc() {
456 let mut rr = RoundedRect::new(10.0, 20.0, 90.0, 80.0, 10.0);
457 rr.rewind(0);
458
459 let mut x = 0.0;
460 let mut y = 0.0;
461 let cmd = rr.vertex(&mut x, &mut y);
462 assert_eq!(cmd, PATH_CMD_MOVE_TO);
463
464 assert!((x - 10.0).abs() < 0.5, "x={x}, expected near 10");
468 assert!((y - 30.0).abs() < 0.5, "y={y}, expected near 30");
469 }
470
471 #[test]
472 fn test_approximation_scale() {
473 let mut rr = RoundedRect::new(0.0, 0.0, 100.0, 100.0, 10.0);
474 rr.set_approximation_scale(2.0);
475 assert!((rr.approximation_scale() - 2.0).abs() < 1e-10);
476 }
477
478 #[test]
479 fn test_rect_method() {
480 let mut rr = RoundedRect::default_new();
481 rr.rect(100.0, 200.0, 50.0, 30.0);
482 assert_eq!(rr.x1, 50.0);
483 assert_eq!(rr.y1, 30.0);
484 assert_eq!(rr.x2, 100.0);
485 assert_eq!(rr.y2, 200.0);
486 }
487
488 #[test]
489 fn test_zero_radius_produces_rectangle() {
490 let mut rr = RoundedRect::new(0.0, 0.0, 100.0, 100.0, 0.0);
491 rr.rewind(0);
492
493 let mut x = 0.0;
494 let mut y = 0.0;
495 let mut vertex_count = 0;
496
497 loop {
498 let cmd = rr.vertex(&mut x, &mut y);
499 if is_stop(cmd) {
500 break;
501 }
502 if is_vertex(cmd) {
503 vertex_count += 1;
504 }
505 }
506
507 assert!(vertex_count >= 4);
509 }
510}