1use super::{
19 AnsiColor, CharsXY, ClearType, Console, Key, LineBuffer, PixelsXY, RGB, SizeInPixels,
20 ansi_color_to_rgb, remove_control_chars,
21};
22use async_trait::async_trait;
23use std::convert::TryFrom;
24use std::io;
25
26const DEFAULT_FG_COLOR: u8 = AnsiColor::White as u8;
29
30const DEFAULT_BG_COLOR: u8 = AnsiColor::Black as u8;
33
34pub trait ClampedInto<T> {
36 fn clamped_into(self) -> T;
38}
39
40impl ClampedInto<usize> for i16 {
41 fn clamped_into(self) -> usize {
42 if self < 0 { 0 } else { self as usize }
43 }
44}
45
46impl ClampedInto<i16> for u16 {
47 fn clamped_into(self) -> i16 {
48 if self > u16::try_from(i16::MAX).unwrap() { i16::MAX } else { self as i16 }
49 }
50}
51
52impl ClampedInto<i16> for i32 {
53 fn clamped_into(self) -> i16 {
54 if self > i32::from(i16::MAX) {
55 i16::MAX
56 } else if self < i32::from(i16::MIN) {
57 i16::MIN
58 } else {
59 self as i16
60 }
61 }
62}
63
64impl ClampedInto<u16> for i32 {
65 fn clamped_into(self) -> u16 {
66 if self > i32::from(u16::MAX) {
67 u16::MAX
68 } else if self < 0 {
69 0
70 } else {
71 self as u16
72 }
73 }
74}
75
76impl ClampedInto<u16> for u32 {
77 fn clamped_into(self) -> u16 {
78 if self > u32::from(u16::MAX) { u16::MAX } else { self as u16 }
79 }
80}
81
82pub trait ClampedMul<T, O> {
84 fn clamped_mul(self, rhs: T) -> O;
86}
87
88impl ClampedMul<u16, i16> for u16 {
89 fn clamped_mul(self, rhs: u16) -> i16 {
90 let product = u32::from(self) * u32::from(rhs);
91 if product > i16::MAX as u32 { i16::MAX } else { product as i16 }
92 }
93}
94
95impl ClampedMul<u16, u16> for u16 {
96 fn clamped_mul(self, rhs: u16) -> u16 {
97 let product = u32::from(self) * u32::from(rhs);
98 if product > u16::MAX as u32 { u16::MAX } else { product as u16 }
99 }
100}
101
102impl ClampedMul<u16, i32> for u16 {
103 fn clamped_mul(self, rhs: u16) -> i32 {
104 i32::from(self).checked_mul(i32::from(rhs)).unwrap_or(i32::MAX)
105 }
106}
107
108impl ClampedMul<u16, u32> for u16 {
109 fn clamped_mul(self, rhs: u16) -> u32 {
110 u32::from(self).checked_mul(u32::from(rhs)).expect("Result must have fit")
111 }
112}
113
114impl ClampedMul<usize, usize> for usize {
115 fn clamped_mul(self, rhs: usize) -> usize {
116 match self.checked_mul(rhs) {
117 Some(v) => v,
118 None => usize::MAX,
119 }
120 }
121}
122
123impl ClampedMul<SizeInPixels, PixelsXY> for CharsXY {
124 fn clamped_mul(self, rhs: SizeInPixels) -> PixelsXY {
125 PixelsXY { x: self.x.clamped_mul(rhs.width), y: self.y.clamped_mul(rhs.height) }
126 }
127}
128
129fn rect_points(x1y1: PixelsXY, x2y2: PixelsXY) -> Option<(PixelsXY, SizeInPixels)> {
131 let (x1, x2) = if x1y1.x < x2y2.x { (x1y1.x, x2y2.x) } else { (x2y2.x, x1y1.x) };
132 let (y1, y2) = if x1y1.y < x2y2.y { (x1y1.y, x2y2.y) } else { (x2y2.y, x1y1.y) };
133
134 let width = {
135 let width = i32::from(x2) - i32::from(x1);
136 if cfg!(debug_assertions) {
137 u32::try_from(width).expect("Width must have been non-negative")
138 } else {
139 width as u32
140 }
141 }
142 .clamped_into();
143 let height = {
144 let height = i32::from(y2) - i32::from(y1);
145 if cfg!(debug_assertions) {
146 u32::try_from(height).expect("Height must have been non-negative")
147 } else {
148 height as u32
149 }
150 }
151 .clamped_into();
152
153 if width == 0 || height == 0 {
154 None
155 } else {
156 Some((PixelsXY::new(x1, y1), SizeInPixels::new(width, height)))
157 }
158}
159
160pub struct RasterInfo {
162 pub size_pixels: SizeInPixels,
164
165 pub glyph_size: SizeInPixels,
167
168 pub size_chars: CharsXY,
170}
171
172pub trait RasterOps {
174 type ID;
176
177 fn get_info(&self) -> RasterInfo;
179
180 fn set_draw_color(&mut self, color: RGB);
182
183 fn clear(&mut self) -> io::Result<()>;
185
186 fn set_sync(&mut self, _enabled: bool) {}
193
194 fn present_canvas(&mut self) -> io::Result<()>;
198
199 fn read_pixels(&mut self, xy: PixelsXY, size: SizeInPixels) -> io::Result<Self::ID>;
201
202 fn put_pixels(&mut self, xy: PixelsXY, data: &Self::ID) -> io::Result<()>;
204
205 fn move_pixels(&mut self, x1y1: PixelsXY, x2y2: PixelsXY, size: SizeInPixels)
208 -> io::Result<()>;
209
210 fn write_text(&mut self, xy: PixelsXY, text: &str) -> io::Result<()>;
212
213 fn draw_circle(&mut self, center: PixelsXY, radius: u16) -> io::Result<()>;
215
216 fn draw_circle_filled(&mut self, center: PixelsXY, radius: u16) -> io::Result<()>;
218
219 fn draw_line(&mut self, x1y1: PixelsXY, x2y2: PixelsXY) -> io::Result<()>;
221
222 fn draw_pixel(&mut self, xy: PixelsXY) -> io::Result<()>;
224
225 fn draw_rect(&mut self, xy: PixelsXY, size: SizeInPixels) -> io::Result<()>;
227
228 fn draw_rect_filled(&mut self, xy: PixelsXY, size: SizeInPixels) -> io::Result<()>;
230}
231
232#[async_trait(?Send)]
234pub trait InputOps {
235 async fn poll_key(&mut self) -> io::Result<Option<Key>>;
237
238 async fn read_key(&mut self) -> io::Result<Key>;
240}
241
242pub struct GraphicsConsole<IO, RO>
244where
245 RO: RasterOps,
246{
247 input_ops: IO,
248
249 raster_ops: RO,
251
252 size_pixels: SizeInPixels,
254
255 glyph_size: SizeInPixels,
257
258 size_chars: CharsXY,
260
261 cursor_pos: CharsXY,
263
264 cursor_visible: bool,
266
267 cursor_backup: Option<RO::ID>,
270
271 default_fg_color: u8,
273
274 default_bg_color: u8,
276
277 ansi_fg_color: Option<u8>,
279
280 ansi_bg_color: Option<u8>,
282
283 fg_color: RGB,
285
286 bg_color: RGB,
288
289 #[allow(clippy::type_complexity)]
291 alt_backup: Option<(RO::ID, CharsXY, Option<u8>, Option<u8>, RGB, RGB)>,
292
293 sync_enabled: bool,
295}
296
297impl<IO, RO> GraphicsConsole<IO, RO>
298where
299 IO: InputOps,
300 RO: RasterOps,
301{
302 pub fn new(
304 input_ops: IO,
305 raster_ops: RO,
306 default_fg_color: Option<u8>,
307 default_bg_color: Option<u8>,
308 ) -> io::Result<Self> {
309 let info = raster_ops.get_info();
310
311 let default_fg_color = default_fg_color.unwrap_or(DEFAULT_FG_COLOR);
312 let default_bg_color = default_bg_color.unwrap_or(DEFAULT_BG_COLOR);
313
314 let mut console = Self {
315 input_ops,
316 raster_ops,
317 size_pixels: info.size_pixels,
318 glyph_size: info.glyph_size,
319 size_chars: info.size_chars,
320 cursor_pos: CharsXY::default(),
321 cursor_visible: true,
322 cursor_backup: None,
323 default_fg_color,
324 default_bg_color,
325 ansi_bg_color: None,
326 ansi_fg_color: None,
327 bg_color: ansi_color_to_rgb(default_bg_color),
328 fg_color: ansi_color_to_rgb(default_fg_color),
329 alt_backup: None,
330 sync_enabled: true,
331 };
332
333 console.set_color(console.ansi_fg_color, console.ansi_bg_color)?;
334 console.clear(ClearType::All)?;
335
336 Ok(console)
337 }
338
339 fn present_canvas(&mut self) -> io::Result<()> {
341 if self.sync_enabled { self.raster_ops.present_canvas() } else { Ok(()) }
342 }
343
344 fn draw_cursor(&mut self) -> io::Result<()> {
349 if !self.cursor_visible {
350 return Ok(());
351 }
352
353 let x1y1 = self.cursor_pos.clamped_mul(self.glyph_size);
354
355 assert!(self.cursor_backup.is_none());
356 self.cursor_backup = Some(self.raster_ops.read_pixels(x1y1, self.glyph_size)?);
357
358 self.raster_ops.set_draw_color(self.fg_color);
363 self.raster_ops.draw_rect_filled(x1y1, self.glyph_size)
364 }
365
366 fn clear_cursor(&mut self) -> io::Result<()> {
371 if !self.cursor_visible || self.cursor_backup.is_none() {
372 return Ok(());
373 }
374
375 let x1y1 = self.cursor_pos.clamped_mul(self.glyph_size);
376
377 self.raster_ops.put_pixels(x1y1, self.cursor_backup.as_ref().unwrap())?;
378 self.cursor_backup = None;
379 Ok(())
380 }
381
382 fn open_line(&mut self) -> io::Result<()> {
386 if self.cursor_pos.y < self.size_chars.y - 1 {
387 self.cursor_pos.x = 0;
388 self.cursor_pos.y += 1;
389 return Ok(());
390 }
391
392 let x1y1 = PixelsXY::new(0, self.glyph_size.height.clamped_into());
393 let x2y2 = PixelsXY::new(0, 0);
394 let size = SizeInPixels::new(
395 self.size_pixels.width,
396 self.size_pixels.height - self.glyph_size.height,
397 );
398
399 self.raster_ops.set_draw_color(self.bg_color);
400 self.raster_ops.move_pixels(x1y1, x2y2, size)?;
401
402 self.cursor_pos.x = 0;
403 Ok(())
404 }
405
406 fn raw_write_wrapped(&mut self, text: String) -> io::Result<()> {
409 let mut line_buffer = LineBuffer::from(text);
410
411 loop {
412 let fit_chars = self.size_chars.x - self.cursor_pos.x;
413
414 let remaining = line_buffer.split_off(usize::from(fit_chars));
415 let len = match u16::try_from(line_buffer.len()) {
416 Ok(len) => len,
417 Err(_) => return Err(io::Error::new(io::ErrorKind::InvalidInput, "Text too long")),
418 };
419
420 if len > 0 {
421 let xy = self.cursor_pos.clamped_mul(self.glyph_size);
422 let size = SizeInPixels::new(
423 len.clamped_mul(self.glyph_size.width),
424 self.glyph_size.height,
425 );
426
427 self.raster_ops.set_draw_color(self.bg_color);
428 self.raster_ops.draw_rect_filled(xy, size)?;
429
430 self.raster_ops.set_draw_color(self.fg_color);
431 self.raster_ops.write_text(xy, &line_buffer.into_inner())?;
432 self.cursor_pos.x += len;
433 }
434
435 line_buffer = remaining;
436 if line_buffer.is_empty() {
437 break;
438 } else {
439 self.open_line()?;
440 }
441 }
442
443 Ok(())
444 }
445}
446
447#[async_trait(?Send)]
448impl<IO, RO> Console for GraphicsConsole<IO, RO>
449where
450 IO: InputOps,
451 RO: RasterOps,
452{
453 fn clear(&mut self, how: ClearType) -> io::Result<()> {
454 match how {
455 ClearType::All => {
456 self.raster_ops.set_draw_color(self.bg_color);
457 self.raster_ops.clear()?;
458 self.cursor_pos.y = 0;
459 self.cursor_pos.x = 0;
460 self.cursor_backup = None;
461 }
462 ClearType::CurrentLine => {
463 self.clear_cursor()?;
464 let xy = PixelsXY::new(0, self.cursor_pos.y.clamped_mul(self.glyph_size.height));
465 let size = SizeInPixels::new(self.size_pixels.width, self.glyph_size.height);
466 self.raster_ops.set_draw_color(self.bg_color);
467 self.raster_ops.draw_rect_filled(xy, size)?;
468 self.cursor_pos.x = 0;
469 }
470 ClearType::PreviousChar => {
471 if self.cursor_pos.x > 0 {
472 self.clear_cursor()?;
473 let previous_pos = CharsXY::new(self.cursor_pos.x - 1, self.cursor_pos.y);
474 let origin = previous_pos.clamped_mul(self.glyph_size);
475 self.raster_ops.set_draw_color(self.bg_color);
476 self.raster_ops.draw_rect_filled(origin, self.glyph_size)?;
477 self.cursor_pos = previous_pos;
478 }
479 }
480 ClearType::UntilNewLine => {
481 self.clear_cursor()?;
482 let pos = self.cursor_pos.clamped_mul(self.glyph_size);
483 debug_assert!(pos.x >= 0, "Inputs to pos are unsigned");
484 debug_assert!(pos.y >= 0, "Inputs to pos are unsigned");
485 let size = SizeInPixels::new(
486 (i32::from(self.size_pixels.width) - i32::from(pos.x)).clamped_into(),
487 self.glyph_size.height,
488 );
489 self.raster_ops.set_draw_color(self.bg_color);
490 self.raster_ops.draw_rect_filled(pos, size)?;
491 }
492 }
493 self.draw_cursor()?;
494 self.present_canvas()
495 }
496
497 fn color(&self) -> (Option<u8>, Option<u8>) {
498 (self.ansi_fg_color, self.ansi_bg_color)
499 }
500
501 fn set_color(&mut self, fg: Option<u8>, bg: Option<u8>) -> io::Result<()> {
502 self.ansi_fg_color = fg;
503 self.fg_color = ansi_color_to_rgb(fg.unwrap_or(self.default_fg_color));
504 self.ansi_bg_color = bg;
505 self.bg_color = ansi_color_to_rgb(bg.unwrap_or(self.default_bg_color));
506 Ok(())
507 }
508
509 fn enter_alt(&mut self) -> io::Result<()> {
510 if self.alt_backup.is_some() {
511 return Err(io::Error::new(
512 io::ErrorKind::InvalidInput,
513 "Cannot nest alternate screens",
514 ));
515 }
516
517 let pixels = self.raster_ops.read_pixels(PixelsXY::new(0, 0), self.size_pixels)?;
518 self.alt_backup = Some((
519 pixels,
520 self.cursor_pos,
521 self.ansi_fg_color,
522 self.ansi_bg_color,
523 self.fg_color,
524 self.bg_color,
525 ));
526
527 self.clear(ClearType::All)
528 }
529
530 fn hide_cursor(&mut self) -> io::Result<()> {
531 self.clear_cursor()?;
532 self.cursor_visible = false;
533 self.present_canvas()
534 }
535
536 fn is_interactive(&self) -> bool {
537 true
538 }
539
540 fn leave_alt(&mut self) -> io::Result<()> {
541 let (pixels, cursor_pos, ansi_fg_color, ansi_bg_color, fg_color, bg_color) =
542 match self.alt_backup.take() {
543 Some(t) => t,
544 None => {
545 return Err(io::Error::new(
546 io::ErrorKind::InvalidInput,
547 "Cannot leave alternate screen; not entered",
548 ));
549 }
550 };
551
552 self.clear_cursor()?;
553
554 self.raster_ops.put_pixels(PixelsXY::new(0, 0), &pixels)?;
555
556 self.cursor_pos = cursor_pos;
557 self.ansi_fg_color = ansi_fg_color;
558 self.ansi_bg_color = ansi_bg_color;
559 self.fg_color = fg_color;
560 self.bg_color = bg_color;
561 self.draw_cursor()?;
562 self.present_canvas()?;
563
564 debug_assert!(self.alt_backup.is_none());
565 Ok(())
566 }
567
568 fn locate(&mut self, pos: CharsXY) -> io::Result<()> {
569 debug_assert!(pos.x < self.size_chars.x);
570 debug_assert!(pos.y < self.size_chars.y);
571
572 let previous = self.set_sync(false)?;
573 self.clear_cursor()?;
574 self.cursor_pos = pos;
575 self.draw_cursor()?;
576 self.set_sync(previous)?;
577 Ok(())
578 }
579
580 fn move_within_line(&mut self, off: i16) -> io::Result<()> {
581 let previous = self.set_sync(false)?;
582 self.clear_cursor()?;
583 if off < 0 {
584 self.cursor_pos.x -= -off as u16;
585 } else {
586 self.cursor_pos.x += off as u16;
587 }
588 self.draw_cursor()?;
589 self.set_sync(previous)?;
590 Ok(())
591 }
592
593 fn print(&mut self, text: &str) -> io::Result<()> {
594 let text = remove_control_chars(text);
595
596 let previous = self.set_sync(false)?;
597 self.clear_cursor()?;
598 self.raw_write_wrapped(text)?;
599 self.open_line()?;
600 self.draw_cursor()?;
601 self.set_sync(previous)?;
602 Ok(())
603 }
604
605 async fn poll_key(&mut self) -> io::Result<Option<Key>> {
606 self.input_ops.poll_key().await
607 }
608
609 async fn read_key(&mut self) -> io::Result<Key> {
610 self.input_ops.read_key().await
611 }
612
613 fn show_cursor(&mut self) -> io::Result<()> {
614 if !self.cursor_visible {
615 self.cursor_visible = true;
616 if let Err(e) = self.draw_cursor() {
617 self.cursor_visible = false;
618 return Err(e);
619 }
620 }
621 self.present_canvas()
622 }
623
624 fn size_chars(&self) -> io::Result<CharsXY> {
625 Ok(self.size_chars)
626 }
627
628 fn size_pixels(&self) -> io::Result<SizeInPixels> {
629 Ok(self.size_pixels)
630 }
631
632 fn write(&mut self, text: &str) -> io::Result<()> {
633 let text = remove_control_chars(text);
634
635 let previous = self.set_sync(false)?;
636 self.clear_cursor()?;
637 self.raw_write_wrapped(text)?;
638 self.draw_cursor()?;
639 self.set_sync(previous)?;
640 Ok(())
641 }
642
643 fn draw_circle(&mut self, center: PixelsXY, radius: u16) -> io::Result<()> {
644 self.raster_ops.set_draw_color(self.fg_color);
645 self.raster_ops.draw_circle(center, radius)?;
646 self.present_canvas()
647 }
648
649 fn draw_circle_filled(&mut self, center: PixelsXY, radius: u16) -> io::Result<()> {
650 self.raster_ops.set_draw_color(self.fg_color);
651 self.raster_ops.draw_circle_filled(center, radius)?;
652 self.present_canvas()
653 }
654
655 fn draw_line(&mut self, x1y1: PixelsXY, x2y2: PixelsXY) -> io::Result<()> {
656 self.raster_ops.set_draw_color(self.fg_color);
657 self.raster_ops.draw_line(x1y1, x2y2)?;
658 self.present_canvas()
659 }
660
661 fn draw_pixel(&mut self, xy: PixelsXY) -> io::Result<()> {
662 self.raster_ops.set_draw_color(self.fg_color);
663 self.raster_ops.draw_pixel(xy)?;
664 self.present_canvas()
665 }
666
667 fn draw_rect(&mut self, x1y1: PixelsXY, x2y2: PixelsXY) -> io::Result<()> {
668 self.raster_ops.set_draw_color(self.fg_color);
669 match rect_points(x1y1, x2y2) {
670 Some((xy, size)) => self.raster_ops.draw_rect(xy, size)?,
671 None => self.raster_ops.draw_line(x1y1, x2y2)?,
672 }
673 self.present_canvas()
674 }
675
676 fn draw_rect_filled(&mut self, x1y1: PixelsXY, x2y2: PixelsXY) -> io::Result<()> {
677 self.raster_ops.set_draw_color(self.fg_color);
678 match rect_points(x1y1, x2y2) {
679 Some((xy, size)) => self.raster_ops.draw_rect_filled(xy, size)?,
680 None => self.raster_ops.draw_line(x1y1, x2y2)?,
681 }
682 self.present_canvas()
683 }
684
685 fn sync_now(&mut self) -> io::Result<()> {
686 if self.sync_enabled { Ok(()) } else { self.raster_ops.present_canvas() }
687 }
688
689 fn set_sync(&mut self, enabled: bool) -> io::Result<bool> {
690 if !self.sync_enabled && enabled {
691 self.raster_ops.present_canvas()?;
692 }
693 let previous = self.sync_enabled;
694 self.sync_enabled = enabled;
695 self.raster_ops.set_sync(enabled);
696 Ok(previous)
697 }
698}
699
700#[cfg(test)]
701mod tests {
702 use super::*;
703
704 #[test]
705 fn test_clamped_into_u16_i16() {
706 assert_eq!(0i16, 0u16.clamped_into());
707 assert_eq!(10i16, 10u16.clamped_into());
708 assert_eq!(i16::MAX - 1, u16::try_from(i16::MAX - 1).unwrap().clamped_into());
709 assert_eq!(i16::MAX, u16::try_from(i16::MAX).unwrap().clamped_into());
710 assert_eq!(i16::MAX, u16::MAX.clamped_into());
711 }
712
713 #[test]
714 fn test_clamped_into_u16_i32() {
715 assert_eq!(0i16, 0i32.clamped_into());
716 assert_eq!(10i16, 10i32.clamped_into());
717 assert_eq!(i16::MIN + 1, i32::from(i16::MIN + 1).clamped_into());
718 assert_eq!(i16::MIN, i32::from(i16::MIN).clamped_into());
719 assert_eq!(i16::MIN, i32::MIN.clamped_into());
720 assert_eq!(i16::MAX - 1, i32::from(i16::MAX - 1).clamped_into());
721 assert_eq!(i16::MAX, i32::from(i16::MAX).clamped_into());
722 assert_eq!(i16::MAX, i32::MAX.clamped_into());
723 }
724
725 #[test]
726 fn test_clamped_into_i32_u16() {
727 assert_eq!(0u16, 0i32.clamped_into());
728 assert_eq!(10u16, 10i32.clamped_into());
729 assert_eq!(0u16, (-10i32).clamped_into());
730 assert_eq!(u16::MAX - 1, i32::from(u16::MAX - 1).clamped_into());
731 assert_eq!(u16::MAX, i32::from(u16::MAX).clamped_into());
732 assert_eq!(u16::MAX, i32::MAX.clamped_into());
733 }
734
735 #[test]
736 fn test_clamped_into_u32_u16() {
737 assert_eq!(0u16, 0u32.clamped_into());
738 assert_eq!(10u16, 10u32.clamped_into());
739 assert_eq!(u16::MAX - 1, u32::from(u16::MAX - 1).clamped_into());
740 assert_eq!(u16::MAX, u32::from(u16::MAX).clamped_into());
741 assert_eq!(u16::MAX, u32::MAX.clamped_into());
742 }
743
744 #[test]
745 fn test_clamped_mul_u16_u16_i16() {
746 assert_eq!(0i16, ClampedMul::<u16, i16>::clamped_mul(0u16, 0u16));
747 assert_eq!(55i16, ClampedMul::<u16, i16>::clamped_mul(11u16, 5u16));
748 assert_eq!(i16::MAX, ClampedMul::<u16, i16>::clamped_mul(u16::MAX, u16::MAX));
749 }
750
751 #[test]
752 fn test_clamped_mul_u16_u16_u16() {
753 assert_eq!(0u16, ClampedMul::<u16, u16>::clamped_mul(0u16, 0u16));
754 assert_eq!(55u16, ClampedMul::<u16, u16>::clamped_mul(11u16, 5u16));
755 assert_eq!(u16::MAX, ClampedMul::<u16, u16>::clamped_mul(u16::MAX, u16::MAX));
756 }
757
758 #[test]
759 fn test_clamped_mul_u16_u16_i32() {
760 assert_eq!(0i32, ClampedMul::<u16, i32>::clamped_mul(0u16, 0u16));
761 assert_eq!(55i32, ClampedMul::<u16, i32>::clamped_mul(11u16, 5u16));
762 assert_eq!(i32::MAX, ClampedMul::<u16, i32>::clamped_mul(u16::MAX, u16::MAX));
763 }
764
765 #[test]
766 fn test_clamped_mul_u16_u16_u32() {
767 assert_eq!(0u32, ClampedMul::<u16, u32>::clamped_mul(0u16, 0u16));
768 assert_eq!(55u32, ClampedMul::<u16, u32>::clamped_mul(11u16, 5u16));
769 assert_eq!(4294836225u32, ClampedMul::<u16, u32>::clamped_mul(u16::MAX, u16::MAX));
770 }
771
772 #[test]
773 fn test_clamped_mul_usize_usize_usize() {
774 assert_eq!(0, ClampedMul::<usize, usize>::clamped_mul(0, 0));
775 assert_eq!(55, ClampedMul::<usize, usize>::clamped_mul(11, 5));
776 assert_eq!(usize::MAX, ClampedMul::<usize, usize>::clamped_mul(usize::MAX, usize::MAX));
777 }
778
779 #[test]
780 fn test_clamped_mul_charsxy_sizeinpixels_pixelsxy() {
781 assert_eq!(
782 PixelsXY { x: 0, y: 0 },
783 CharsXY { x: 0, y: 0 }.clamped_mul(SizeInPixels::new(1, 1))
784 );
785 assert_eq!(
786 PixelsXY { x: 50, y: 120 },
787 CharsXY { x: 10, y: 20 }.clamped_mul(SizeInPixels::new(5, 6))
788 );
789 assert_eq!(
790 PixelsXY { x: i16::MAX, y: 120 },
791 CharsXY { x: 10, y: 20 }.clamped_mul(SizeInPixels::new(50000, 6))
792 );
793 assert_eq!(
794 PixelsXY { x: 50, y: i16::MAX },
795 CharsXY { x: 10, y: 20 }.clamped_mul(SizeInPixels::new(5, 60000))
796 );
797 assert_eq!(
798 PixelsXY { x: i16::MAX, y: i16::MAX },
799 CharsXY { x: 10, y: 20 }.clamped_mul(SizeInPixels::new(50000, 60000))
800 );
801 }
802
803 #[test]
804 fn test_rect_points_ok() {
805 assert_eq!(
806 Some((PixelsXY { x: 10, y: 20 }, SizeInPixels::new(100, 200))),
807 rect_points(PixelsXY { x: 10, y: 20 }, PixelsXY { x: 110, y: 220 })
808 );
809 assert_eq!(
810 Some((PixelsXY { x: 10, y: 20 }, SizeInPixels::new(100, 200))),
811 rect_points(PixelsXY { x: 110, y: 20 }, PixelsXY { x: 10, y: 220 })
812 );
813 assert_eq!(
814 Some((PixelsXY { x: 10, y: 20 }, SizeInPixels::new(100, 200))),
815 rect_points(PixelsXY { x: 10, y: 220 }, PixelsXY { x: 110, y: 20 })
816 );
817 assert_eq!(
818 Some((PixelsXY { x: 10, y: 20 }, SizeInPixels::new(100, 200))),
819 rect_points(PixelsXY { x: 110, y: 220 }, PixelsXY { x: 10, y: 20 })
820 );
821
822 assert_eq!(
823 Some((PixelsXY { x: -31000, y: -32000 }, SizeInPixels::new(31005, 32010))),
824 rect_points(PixelsXY { x: 5, y: -32000 }, PixelsXY { x: -31000, y: 10 })
825 );
826 assert_eq!(
827 Some((PixelsXY { x: 10, y: 5 }, SizeInPixels::new(30990, 31995))),
828 rect_points(PixelsXY { x: 31000, y: 5 }, PixelsXY { x: 10, y: 32000 })
829 );
830
831 assert_eq!(
832 Some((PixelsXY { x: -31000, y: -32000 }, SizeInPixels::new(62000, 64000))),
833 rect_points(PixelsXY { x: -31000, y: -32000 }, PixelsXY { x: 31000, y: 32000 })
834 );
835 assert_eq!(
836 Some((PixelsXY { x: -31000, y: -32000 }, SizeInPixels::new(62000, 64000))),
837 rect_points(PixelsXY { x: 31000, y: 32000 }, PixelsXY { x: -31000, y: -32000 })
838 );
839 }
840
841 #[test]
842 fn test_rect_points_zeroes() {
843 assert_eq!(None, rect_points(PixelsXY { x: 10, y: 10 }, PixelsXY { x: 10, y: 10 }));
844 assert_eq!(None, rect_points(PixelsXY { x: 10, y: 10 }, PixelsXY { x: 10, y: 20 }));
845 assert_eq!(None, rect_points(PixelsXY { x: 10, y: 10 }, PixelsXY { x: 20, y: 10 }));
846 }
847}