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