1use crate::sound::{AudioOps, Tone};
19
20use super::{
21 AnsiColor, CharsXY, ClearType, Console, Key, LineBuffer, PixelsXY, RGB, SizeInPixels,
22 ansi_color_to_rgb, drawing, remove_control_chars,
23};
24use async_trait::async_trait;
25use std::convert::TryFrom;
26use std::io;
27
28const DEFAULT_FG_COLOR: u8 = AnsiColor::White as u8;
31
32const DEFAULT_BG_COLOR: u8 = AnsiColor::Black as u8;
35
36pub trait ClampedInto<T> {
38 fn clamped_into(self) -> T;
40}
41
42impl ClampedInto<usize> for i16 {
43 fn clamped_into(self) -> usize {
44 if self < 0 { 0 } else { self as usize }
45 }
46}
47
48impl ClampedInto<i16> for u16 {
49 fn clamped_into(self) -> i16 {
50 if self > u16::try_from(i16::MAX).unwrap() { i16::MAX } else { self as i16 }
51 }
52}
53
54impl ClampedInto<i16> for i32 {
55 fn clamped_into(self) -> i16 {
56 if self > i32::from(i16::MAX) {
57 i16::MAX
58 } else if self < i32::from(i16::MIN) {
59 i16::MIN
60 } else {
61 self as i16
62 }
63 }
64}
65
66impl ClampedInto<u16> for i32 {
67 fn clamped_into(self) -> u16 {
68 if self > i32::from(u16::MAX) {
69 u16::MAX
70 } else if self < 0 {
71 0
72 } else {
73 self as u16
74 }
75 }
76}
77
78impl ClampedInto<u16> for u32 {
79 fn clamped_into(self) -> u16 {
80 if self > u32::from(u16::MAX) { u16::MAX } else { self as u16 }
81 }
82}
83
84pub trait ClampedMul<T, O> {
86 fn clamped_mul(self, rhs: T) -> O;
88}
89
90impl ClampedMul<u16, i16> for u16 {
91 fn clamped_mul(self, rhs: u16) -> i16 {
92 let product = u32::from(self) * u32::from(rhs);
93 if product > i16::MAX as u32 { i16::MAX } else { product as i16 }
94 }
95}
96
97impl ClampedMul<u16, u16> for u16 {
98 fn clamped_mul(self, rhs: u16) -> u16 {
99 let product = u32::from(self) * u32::from(rhs);
100 if product > u16::MAX as u32 { u16::MAX } else { product as u16 }
101 }
102}
103
104impl ClampedMul<u16, i32> for u16 {
105 fn clamped_mul(self, rhs: u16) -> i32 {
106 i32::from(self).checked_mul(i32::from(rhs)).unwrap_or(i32::MAX)
107 }
108}
109
110impl ClampedMul<u16, u32> for u16 {
111 fn clamped_mul(self, rhs: u16) -> u32 {
112 u32::from(self).checked_mul(u32::from(rhs)).expect("Result must have fit")
113 }
114}
115
116impl ClampedMul<usize, usize> for usize {
117 fn clamped_mul(self, rhs: usize) -> usize {
118 match self.checked_mul(rhs) {
119 Some(v) => v,
120 None => usize::MAX,
121 }
122 }
123}
124
125impl ClampedMul<SizeInPixels, PixelsXY> for CharsXY {
126 fn clamped_mul(self, rhs: SizeInPixels) -> PixelsXY {
127 PixelsXY { x: self.x.clamped_mul(rhs.width), y: self.y.clamped_mul(rhs.height) }
128 }
129}
130
131fn poly_points(points: &[PixelsXY]) -> bool {
133 points.windows(2).any(|ps| ps[0] != ps[1])
134 || (points.len() > 2 && points.first() != points.last())
135}
136
137fn rect_points(x1y1: PixelsXY, x2y2: PixelsXY) -> Option<(PixelsXY, SizeInPixels)> {
139 let (x1, x2) = if x1y1.x < x2y2.x { (x1y1.x, x2y2.x) } else { (x2y2.x, x1y1.x) };
140 let (y1, y2) = if x1y1.y < x2y2.y { (x1y1.y, x2y2.y) } else { (x2y2.y, x1y1.y) };
141
142 let width = {
143 let width = i32::from(x2) - i32::from(x1);
144 if cfg!(debug_assertions) {
145 u32::try_from(width).expect("Width must have been non-negative")
146 } else {
147 width as u32
148 }
149 }
150 .clamped_into();
151 let height = {
152 let height = i32::from(y2) - i32::from(y1);
153 if cfg!(debug_assertions) {
154 u32::try_from(height).expect("Height must have been non-negative")
155 } else {
156 height as u32
157 }
158 }
159 .clamped_into();
160
161 if width == 0 || height == 0 {
162 None
163 } else {
164 Some((PixelsXY::new(x1, y1), SizeInPixels::new(width, height)))
165 }
166}
167
168fn tri_points(x1y1: PixelsXY, x2y2: PixelsXY, x3y3: PixelsXY) -> bool {
170 let dx21 = i32::from(x2y2.x) - i32::from(x1y1.x);
171 let dy21 = i32::from(x2y2.y) - i32::from(x1y1.y);
172 let dx31 = i32::from(x3y3.x) - i32::from(x1y1.x);
173 let dy31 = i32::from(x3y3.y) - i32::from(x1y1.y);
174 i64::from(dx21) * i64::from(dy31) != i64::from(dy21) * i64::from(dx31)
175}
176
177pub struct RasterInfo {
179 pub size_pixels: SizeInPixels,
181
182 pub glyph_size: SizeInPixels,
184
185 pub size_chars: CharsXY,
187}
188
189pub trait RasterOps {
191 type ID;
193
194 fn get_info(&self) -> RasterInfo;
196
197 fn set_draw_color(&mut self, color: RGB);
199
200 fn clear(&mut self) -> io::Result<()>;
202
203 fn set_sync(&mut self, _enabled: bool) {}
210
211 fn present_canvas(&mut self) -> io::Result<()>;
215
216 fn peek_pixel(&self, xy: PixelsXY) -> io::Result<Option<u8>>;
218
219 fn read_pixels(&mut self, xy: PixelsXY, size: SizeInPixels) -> io::Result<Self::ID>;
221
222 fn put_pixels(&mut self, xy: PixelsXY, data: &Self::ID) -> io::Result<()>;
224
225 fn move_pixels(&mut self, x1y1: PixelsXY, x2y2: PixelsXY, size: SizeInPixels)
228 -> io::Result<()>;
229
230 fn write_text(&mut self, xy: PixelsXY, text: &str) -> io::Result<()>;
232
233 fn draw_circle(&mut self, center: PixelsXY, radius: u16) -> io::Result<()>;
235
236 fn draw_circle_filled(&mut self, center: PixelsXY, radius: u16) -> io::Result<()>;
238
239 fn draw_line(&mut self, x1y1: PixelsXY, x2y2: PixelsXY) -> io::Result<()>;
241
242 fn draw_pixel(&mut self, xy: PixelsXY) -> io::Result<()>;
244
245 fn draw_poly(&mut self, points: &[PixelsXY]) -> io::Result<()>;
247
248 fn draw_poly_filled(&mut self, points: &[PixelsXY]) -> io::Result<()>;
250
251 fn draw_rect(&mut self, xy: PixelsXY, size: SizeInPixels) -> io::Result<()>;
253
254 fn draw_rect_filled(&mut self, xy: PixelsXY, size: SizeInPixels) -> io::Result<()>;
256
257 fn draw_tri(&mut self, x1y1: PixelsXY, x2y2: PixelsXY, x3y3: PixelsXY) -> io::Result<()>;
259
260 fn draw_tri_filled(&mut self, x1y1: PixelsXY, x2y2: PixelsXY, x3y3: PixelsXY)
262 -> io::Result<()>;
263}
264
265#[async_trait(?Send)]
267pub trait InputOps {
268 async fn poll_key(&mut self) -> io::Result<Option<Key>>;
270
271 async fn read_key(&mut self) -> io::Result<Key>;
273}
274
275pub struct GraphicsConsole<IO, RO, AO>
277where
278 RO: RasterOps,
279{
280 input_ops: IO,
281
282 raster_ops: RO,
284
285 audio_ops: AO,
287
288 size_pixels: SizeInPixels,
290
291 glyph_size: SizeInPixels,
293
294 size_chars: CharsXY,
296
297 cursor_pos: CharsXY,
299
300 cursor_visible: bool,
302
303 cursor_backup: Option<RO::ID>,
306
307 default_fg_color: u8,
309
310 default_bg_color: u8,
312
313 ansi_fg_color: Option<u8>,
315
316 ansi_bg_color: Option<u8>,
318
319 fg_color: RGB,
321
322 bg_color: RGB,
324
325 #[allow(clippy::type_complexity)]
327 alt_backup: Option<(RO::ID, CharsXY, Option<u8>, Option<u8>, RGB, RGB)>,
328
329 sync_enabled: bool,
331}
332
333impl<IO, RO, AO> GraphicsConsole<IO, RO, AO>
334where
335 IO: InputOps,
336 RO: RasterOps,
337 AO: AudioOps,
338{
339 pub fn new(
341 input_ops: IO,
342 raster_ops: RO,
343 audio_ops: AO,
344 default_fg_color: Option<u8>,
345 default_bg_color: Option<u8>,
346 ) -> io::Result<Self> {
347 let info = raster_ops.get_info();
348
349 let default_fg_color = default_fg_color.unwrap_or(DEFAULT_FG_COLOR);
350 let default_bg_color = default_bg_color.unwrap_or(DEFAULT_BG_COLOR);
351
352 let mut console = Self {
353 input_ops,
354 raster_ops,
355 audio_ops,
356 size_pixels: info.size_pixels,
357 glyph_size: info.glyph_size,
358 size_chars: info.size_chars,
359 cursor_pos: CharsXY::default(),
360 cursor_visible: true,
361 cursor_backup: None,
362 default_fg_color,
363 default_bg_color,
364 ansi_bg_color: None,
365 ansi_fg_color: None,
366 bg_color: ansi_color_to_rgb(default_bg_color),
367 fg_color: ansi_color_to_rgb(default_fg_color),
368 alt_backup: None,
369 sync_enabled: true,
370 };
371
372 console.set_color(console.ansi_fg_color, console.ansi_bg_color)?;
373 console.clear(ClearType::All)?;
374
375 Ok(console)
376 }
377
378 fn present_canvas(&mut self) -> io::Result<()> {
380 if self.sync_enabled { self.raster_ops.present_canvas() } else { Ok(()) }
381 }
382
383 fn draw_cursor(&mut self) -> io::Result<()> {
388 if !self.cursor_visible {
389 return Ok(());
390 }
391
392 let x1y1 = self.cursor_pos.clamped_mul(self.glyph_size);
393
394 assert!(self.cursor_backup.is_none());
395 self.cursor_backup = Some(self.raster_ops.read_pixels(x1y1, self.glyph_size)?);
396
397 self.raster_ops.set_draw_color(self.fg_color);
402 self.raster_ops.draw_rect_filled(x1y1, self.glyph_size)
403 }
404
405 fn clear_cursor(&mut self) -> io::Result<()> {
410 if !self.cursor_visible || self.cursor_backup.is_none() {
411 return Ok(());
412 }
413
414 let x1y1 = self.cursor_pos.clamped_mul(self.glyph_size);
415
416 self.raster_ops.put_pixels(x1y1, self.cursor_backup.as_ref().unwrap())?;
417 self.cursor_backup = None;
418 Ok(())
419 }
420
421 fn open_line(&mut self) -> io::Result<()> {
425 if self.cursor_pos.y < self.size_chars.y - 1 {
426 self.cursor_pos.x = 0;
427 self.cursor_pos.y += 1;
428 return Ok(());
429 }
430
431 let text_height: u16 = self.size_chars.y.clamped_mul(self.glyph_size.height);
432 let x1y1 = PixelsXY::new(0, self.glyph_size.height.clamped_into());
433 let x2y2 = PixelsXY::new(0, 0);
434 let size = SizeInPixels::new(self.size_pixels.width, text_height - self.glyph_size.height);
435
436 self.raster_ops.set_draw_color(self.bg_color);
437 self.raster_ops.move_pixels(x1y1, x2y2, size)?;
438 if text_height < self.size_pixels.height {
439 let xy = PixelsXY::new(0, text_height.clamped_into());
440 let size =
441 SizeInPixels::new(self.size_pixels.width, self.size_pixels.height - text_height);
442 self.raster_ops.draw_rect_filled(xy, size)?;
443 }
444
445 self.cursor_pos.x = 0;
446 Ok(())
447 }
448
449 fn raw_write_wrapped(&mut self, text: String) -> io::Result<()> {
452 let mut line_buffer = LineBuffer::from(text);
453
454 loop {
455 let fit_chars = self.size_chars.x - self.cursor_pos.x;
456
457 let remaining = line_buffer.split_off(usize::from(fit_chars));
458 let len = match u16::try_from(line_buffer.len()) {
459 Ok(len) => len,
460 Err(_) => return Err(io::Error::new(io::ErrorKind::InvalidInput, "Text too long")),
461 };
462
463 if len > 0 {
464 let xy = self.cursor_pos.clamped_mul(self.glyph_size);
465 let size = SizeInPixels::new(
466 len.clamped_mul(self.glyph_size.width),
467 self.glyph_size.height,
468 );
469
470 self.raster_ops.set_draw_color(self.bg_color);
471 self.raster_ops.draw_rect_filled(xy, size)?;
472
473 self.raster_ops.set_draw_color(self.fg_color);
474 self.raster_ops.write_text(xy, &line_buffer.into_inner())?;
475 self.cursor_pos.x += len;
476 }
477
478 line_buffer = remaining;
479 if line_buffer.is_empty() {
480 break;
481 } else {
482 self.open_line()?;
483 }
484 }
485
486 Ok(())
487 }
488}
489
490#[async_trait(?Send)]
491impl<IO, RO, AO> Console for GraphicsConsole<IO, RO, AO>
492where
493 IO: InputOps,
494 RO: RasterOps,
495 AO: AudioOps,
496{
497 fn clear(&mut self, how: ClearType) -> io::Result<()> {
498 match how {
499 ClearType::All => {
500 self.raster_ops.set_draw_color(self.bg_color);
501 self.raster_ops.clear()?;
502 self.cursor_pos.y = 0;
503 self.cursor_pos.x = 0;
504 self.cursor_backup = None;
505 }
506 ClearType::CurrentLine => {
507 self.clear_cursor()?;
508 let xy = PixelsXY::new(0, self.cursor_pos.y.clamped_mul(self.glyph_size.height));
509 let size = SizeInPixels::new(self.size_pixels.width, self.glyph_size.height);
510 self.raster_ops.set_draw_color(self.bg_color);
511 self.raster_ops.draw_rect_filled(xy, size)?;
512 self.cursor_pos.x = 0;
513 }
514 ClearType::PreviousChar => {
515 if self.cursor_pos.x > 0 {
516 self.clear_cursor()?;
517 let previous_pos = CharsXY::new(self.cursor_pos.x - 1, self.cursor_pos.y);
518 let origin = previous_pos.clamped_mul(self.glyph_size);
519 self.raster_ops.set_draw_color(self.bg_color);
520 self.raster_ops.draw_rect_filled(origin, self.glyph_size)?;
521 self.cursor_pos = previous_pos;
522 }
523 }
524 ClearType::UntilNewLine => {
525 self.clear_cursor()?;
526 let pos = self.cursor_pos.clamped_mul(self.glyph_size);
527 debug_assert!(pos.x >= 0, "Inputs to pos are unsigned");
528 debug_assert!(pos.y >= 0, "Inputs to pos are unsigned");
529 let size = SizeInPixels::new(
530 (i32::from(self.size_pixels.width) - i32::from(pos.x)).clamped_into(),
531 self.glyph_size.height,
532 );
533 self.raster_ops.set_draw_color(self.bg_color);
534 self.raster_ops.draw_rect_filled(pos, size)?;
535 }
536 }
537 self.draw_cursor()?;
538 self.present_canvas()
539 }
540
541 fn color(&self) -> (Option<u8>, Option<u8>) {
542 (self.ansi_fg_color, self.ansi_bg_color)
543 }
544
545 fn set_color(&mut self, fg: Option<u8>, bg: Option<u8>) -> io::Result<()> {
546 self.ansi_fg_color = fg;
547 self.fg_color = ansi_color_to_rgb(fg.unwrap_or(self.default_fg_color));
548 self.ansi_bg_color = bg;
549 self.bg_color = ansi_color_to_rgb(bg.unwrap_or(self.default_bg_color));
550 Ok(())
551 }
552
553 fn enter_alt(&mut self) -> io::Result<()> {
554 if self.alt_backup.is_some() {
555 return Err(io::Error::new(
556 io::ErrorKind::InvalidInput,
557 "Cannot nest alternate screens",
558 ));
559 }
560
561 let pixels = self.raster_ops.read_pixels(PixelsXY::new(0, 0), self.size_pixels)?;
562 self.alt_backup = Some((
563 pixels,
564 self.cursor_pos,
565 self.ansi_fg_color,
566 self.ansi_bg_color,
567 self.fg_color,
568 self.bg_color,
569 ));
570
571 self.clear(ClearType::All)
572 }
573
574 fn hide_cursor(&mut self) -> io::Result<()> {
575 self.clear_cursor()?;
576 self.cursor_visible = false;
577 self.present_canvas()
578 }
579
580 fn is_interactive(&self) -> bool {
581 true
582 }
583
584 fn leave_alt(&mut self) -> io::Result<()> {
585 let (pixels, cursor_pos, ansi_fg_color, ansi_bg_color, fg_color, bg_color) =
586 match self.alt_backup.take() {
587 Some(t) => t,
588 None => {
589 return Err(io::Error::new(
590 io::ErrorKind::InvalidInput,
591 "Cannot leave alternate screen; not entered",
592 ));
593 }
594 };
595
596 self.clear_cursor()?;
597
598 self.raster_ops.put_pixels(PixelsXY::new(0, 0), &pixels)?;
599
600 self.cursor_pos = cursor_pos;
601 self.ansi_fg_color = ansi_fg_color;
602 self.ansi_bg_color = ansi_bg_color;
603 self.fg_color = fg_color;
604 self.bg_color = bg_color;
605 self.draw_cursor()?;
606 self.present_canvas()?;
607
608 debug_assert!(self.alt_backup.is_none());
609 Ok(())
610 }
611
612 fn locate(&mut self, pos: CharsXY) -> io::Result<()> {
613 debug_assert!(pos.x < self.size_chars.x);
614 debug_assert!(pos.y < self.size_chars.y);
615
616 let previous = self.set_sync(false)?;
617 self.clear_cursor()?;
618 self.cursor_pos = pos;
619 self.draw_cursor()?;
620 self.set_sync(previous)?;
621 Ok(())
622 }
623
624 fn move_within_line(&mut self, off: i16) -> io::Result<()> {
625 let previous = self.set_sync(false)?;
626 self.clear_cursor()?;
627 if off < 0 {
628 self.cursor_pos.x -= -off as u16;
629 } else {
630 self.cursor_pos.x += off as u16;
631 }
632 self.draw_cursor()?;
633 self.set_sync(previous)?;
634 Ok(())
635 }
636
637 fn print(&mut self, text: &str) -> io::Result<()> {
638 let text = remove_control_chars(text);
639
640 let previous = self.set_sync(false)?;
641 self.clear_cursor()?;
642 self.raw_write_wrapped(text)?;
643 self.open_line()?;
644 self.draw_cursor()?;
645 self.set_sync(previous)?;
646 Ok(())
647 }
648
649 async fn poll_key(&mut self) -> io::Result<Option<Key>> {
650 self.input_ops.poll_key().await
651 }
652
653 async fn read_key(&mut self) -> io::Result<Key> {
654 self.input_ops.read_key().await
655 }
656
657 async fn play_tone(&mut self, tone: Tone) -> io::Result<()> {
658 self.audio_ops.play_tone(tone).await
659 }
660
661 fn show_cursor(&mut self) -> io::Result<()> {
662 if !self.cursor_visible {
663 self.cursor_visible = true;
664 if let Err(e) = self.draw_cursor() {
665 self.cursor_visible = false;
666 return Err(e);
667 }
668 }
669 self.present_canvas()
670 }
671
672 fn size_chars(&self) -> io::Result<CharsXY> {
673 Ok(self.size_chars)
674 }
675
676 fn size_pixels(&self) -> io::Result<SizeInPixels> {
677 Ok(self.size_pixels)
678 }
679
680 fn glyph_size(&self) -> io::Result<SizeInPixels> {
681 Ok(self.glyph_size)
682 }
683
684 fn write(&mut self, text: &str) -> io::Result<()> {
685 let text = remove_control_chars(text);
686
687 let previous = self.set_sync(false)?;
688 self.clear_cursor()?;
689 self.raw_write_wrapped(text)?;
690 self.draw_cursor()?;
691 self.set_sync(previous)?;
692 Ok(())
693 }
694
695 fn bucket_fill(&mut self, xy: PixelsXY) -> io::Result<()> {
696 let fill_color = self.ansi_fg_color.unwrap_or(self.default_fg_color);
697 self.raster_ops.set_draw_color(self.fg_color);
698 drawing::bucket_fill(&mut self.raster_ops, xy, fill_color)?;
699 self.present_canvas()
700 }
701
702 fn draw_circle(&mut self, center: PixelsXY, radius: u16) -> io::Result<()> {
703 self.raster_ops.set_draw_color(self.fg_color);
704 self.raster_ops.draw_circle(center, radius)?;
705 self.present_canvas()
706 }
707
708 fn draw_circle_filled(&mut self, center: PixelsXY, radius: u16) -> io::Result<()> {
709 self.raster_ops.set_draw_color(self.fg_color);
710 self.raster_ops.draw_circle_filled(center, radius)?;
711 self.present_canvas()
712 }
713
714 fn draw_line(&mut self, x1y1: PixelsXY, x2y2: PixelsXY) -> io::Result<()> {
715 self.raster_ops.set_draw_color(self.fg_color);
716 self.raster_ops.draw_line(x1y1, x2y2)?;
717 self.present_canvas()
718 }
719
720 fn draw_pixel(&mut self, xy: PixelsXY) -> io::Result<()> {
721 self.raster_ops.set_draw_color(self.fg_color);
722 self.raster_ops.draw_pixel(xy)?;
723 self.present_canvas()
724 }
725
726 fn draw_poly(&mut self, points: &[PixelsXY]) -> io::Result<()> {
727 match points.len() {
728 0 => Ok(()),
729 1 => self.draw_pixel(points[0]),
730 2 => self.draw_line(points[0], points[1]),
731 3 => self.draw_tri(points[0], points[1], points[2]),
732 _ => {
733 if !poly_points(points) {
734 return Ok(());
735 }
736
737 self.raster_ops.set_draw_color(self.fg_color);
738 self.raster_ops.draw_poly(points)?;
739 self.present_canvas()
740 }
741 }
742 }
743
744 fn draw_poly_filled(&mut self, points: &[PixelsXY]) -> io::Result<()> {
745 match points.len() {
746 0 => Ok(()),
747 1 => self.draw_pixel(points[0]),
748 2 => self.draw_line(points[0], points[1]),
749 3 => self.draw_tri_filled(points[0], points[1], points[2]),
750 _ => {
751 if !poly_points(points) {
752 return Ok(());
753 }
754
755 self.raster_ops.set_draw_color(self.fg_color);
756 self.raster_ops.draw_poly_filled(points)?;
757 self.present_canvas()
758 }
759 }
760 }
761
762 fn draw_rect(&mut self, x1y1: PixelsXY, x2y2: PixelsXY) -> io::Result<()> {
763 self.raster_ops.set_draw_color(self.fg_color);
764 match rect_points(x1y1, x2y2) {
765 Some((xy, size)) => self.raster_ops.draw_rect(xy, size)?,
766 None => self.raster_ops.draw_line(x1y1, x2y2)?,
767 }
768 self.present_canvas()
769 }
770
771 fn draw_rect_filled(&mut self, x1y1: PixelsXY, x2y2: PixelsXY) -> io::Result<()> {
772 self.raster_ops.set_draw_color(self.fg_color);
773 match rect_points(x1y1, x2y2) {
774 Some((xy, size)) => self.raster_ops.draw_rect_filled(xy, size)?,
775 None => self.raster_ops.draw_line(x1y1, x2y2)?,
776 }
777 self.present_canvas()
778 }
779
780 fn draw_tri(&mut self, x1y1: PixelsXY, x2y2: PixelsXY, x3y3: PixelsXY) -> io::Result<()> {
781 if !tri_points(x1y1, x2y2, x3y3) {
782 return Ok(());
783 }
784
785 self.raster_ops.set_draw_color(self.fg_color);
786 self.raster_ops.draw_tri(x1y1, x2y2, x3y3)?;
787 self.present_canvas()
788 }
789
790 fn draw_tri_filled(
791 &mut self,
792 x1y1: PixelsXY,
793 x2y2: PixelsXY,
794 x3y3: PixelsXY,
795 ) -> io::Result<()> {
796 if !tri_points(x1y1, x2y2, x3y3) {
797 return Ok(());
798 }
799
800 self.raster_ops.set_draw_color(self.fg_color);
801 self.raster_ops.draw_tri_filled(x1y1, x2y2, x3y3)?;
802 self.present_canvas()
803 }
804
805 fn peek_pixel(&self, xy: PixelsXY) -> io::Result<Option<u8>> {
806 self.raster_ops.peek_pixel(xy)
807 }
808
809 fn sync_now(&mut self) -> io::Result<()> {
810 if self.sync_enabled { Ok(()) } else { self.raster_ops.present_canvas() }
811 }
812
813 fn set_sync(&mut self, enabled: bool) -> io::Result<bool> {
814 if !self.sync_enabled && enabled {
815 self.raster_ops.present_canvas()?;
816 }
817 let previous = self.sync_enabled;
818 self.sync_enabled = enabled;
819 self.raster_ops.set_sync(enabled);
820 Ok(previous)
821 }
822}
823
824#[cfg(test)]
825mod tests {
826 use super::*;
827
828 #[test]
829 fn test_clamped_into_u16_i16() {
830 assert_eq!(0i16, 0u16.clamped_into());
831 assert_eq!(10i16, 10u16.clamped_into());
832 assert_eq!(i16::MAX - 1, u16::try_from(i16::MAX - 1).unwrap().clamped_into());
833 assert_eq!(i16::MAX, u16::try_from(i16::MAX).unwrap().clamped_into());
834 assert_eq!(i16::MAX, u16::MAX.clamped_into());
835 }
836
837 #[test]
838 fn test_clamped_into_u16_i32() {
839 assert_eq!(0i16, 0i32.clamped_into());
840 assert_eq!(10i16, 10i32.clamped_into());
841 assert_eq!(i16::MIN + 1, i32::from(i16::MIN + 1).clamped_into());
842 assert_eq!(i16::MIN, i32::from(i16::MIN).clamped_into());
843 assert_eq!(i16::MIN, i32::MIN.clamped_into());
844 assert_eq!(i16::MAX - 1, i32::from(i16::MAX - 1).clamped_into());
845 assert_eq!(i16::MAX, i32::from(i16::MAX).clamped_into());
846 assert_eq!(i16::MAX, i32::MAX.clamped_into());
847 }
848
849 #[test]
850 fn test_clamped_into_i32_u16() {
851 assert_eq!(0u16, 0i32.clamped_into());
852 assert_eq!(10u16, 10i32.clamped_into());
853 assert_eq!(0u16, (-10i32).clamped_into());
854 assert_eq!(u16::MAX - 1, i32::from(u16::MAX - 1).clamped_into());
855 assert_eq!(u16::MAX, i32::from(u16::MAX).clamped_into());
856 assert_eq!(u16::MAX, i32::MAX.clamped_into());
857 }
858
859 #[test]
860 fn test_clamped_into_u32_u16() {
861 assert_eq!(0u16, 0u32.clamped_into());
862 assert_eq!(10u16, 10u32.clamped_into());
863 assert_eq!(u16::MAX - 1, u32::from(u16::MAX - 1).clamped_into());
864 assert_eq!(u16::MAX, u32::from(u16::MAX).clamped_into());
865 assert_eq!(u16::MAX, u32::MAX.clamped_into());
866 }
867
868 #[test]
869 fn test_clamped_mul_u16_u16_i16() {
870 assert_eq!(0i16, ClampedMul::<u16, i16>::clamped_mul(0u16, 0u16));
871 assert_eq!(55i16, ClampedMul::<u16, i16>::clamped_mul(11u16, 5u16));
872 assert_eq!(i16::MAX, ClampedMul::<u16, i16>::clamped_mul(u16::MAX, u16::MAX));
873 }
874
875 #[test]
876 fn test_clamped_mul_u16_u16_u16() {
877 assert_eq!(0u16, ClampedMul::<u16, u16>::clamped_mul(0u16, 0u16));
878 assert_eq!(55u16, ClampedMul::<u16, u16>::clamped_mul(11u16, 5u16));
879 assert_eq!(u16::MAX, ClampedMul::<u16, u16>::clamped_mul(u16::MAX, u16::MAX));
880 }
881
882 #[test]
883 fn test_clamped_mul_u16_u16_i32() {
884 assert_eq!(0i32, ClampedMul::<u16, i32>::clamped_mul(0u16, 0u16));
885 assert_eq!(55i32, ClampedMul::<u16, i32>::clamped_mul(11u16, 5u16));
886 assert_eq!(i32::MAX, ClampedMul::<u16, i32>::clamped_mul(u16::MAX, u16::MAX));
887 }
888
889 #[test]
890 fn test_clamped_mul_u16_u16_u32() {
891 assert_eq!(0u32, ClampedMul::<u16, u32>::clamped_mul(0u16, 0u16));
892 assert_eq!(55u32, ClampedMul::<u16, u32>::clamped_mul(11u16, 5u16));
893 assert_eq!(4294836225u32, ClampedMul::<u16, u32>::clamped_mul(u16::MAX, u16::MAX));
894 }
895
896 #[test]
897 fn test_clamped_mul_usize_usize_usize() {
898 assert_eq!(0, ClampedMul::<usize, usize>::clamped_mul(0, 0));
899 assert_eq!(55, ClampedMul::<usize, usize>::clamped_mul(11, 5));
900 assert_eq!(usize::MAX, ClampedMul::<usize, usize>::clamped_mul(usize::MAX, usize::MAX));
901 }
902
903 #[test]
904 fn test_clamped_mul_charsxy_sizeinpixels_pixelsxy() {
905 assert_eq!(
906 PixelsXY { x: 0, y: 0 },
907 CharsXY { x: 0, y: 0 }.clamped_mul(SizeInPixels::new(1, 1))
908 );
909 assert_eq!(
910 PixelsXY { x: 50, y: 120 },
911 CharsXY { x: 10, y: 20 }.clamped_mul(SizeInPixels::new(5, 6))
912 );
913 assert_eq!(
914 PixelsXY { x: i16::MAX, y: 120 },
915 CharsXY { x: 10, y: 20 }.clamped_mul(SizeInPixels::new(50000, 6))
916 );
917 assert_eq!(
918 PixelsXY { x: 50, y: i16::MAX },
919 CharsXY { x: 10, y: 20 }.clamped_mul(SizeInPixels::new(5, 60000))
920 );
921 assert_eq!(
922 PixelsXY { x: i16::MAX, y: i16::MAX },
923 CharsXY { x: 10, y: 20 }.clamped_mul(SizeInPixels::new(50000, 60000))
924 );
925 }
926
927 #[test]
928 fn test_poly_points_ok() {
929 assert!(poly_points(&[
930 PixelsXY { x: 10, y: 10 },
931 PixelsXY { x: 20, y: 10 },
932 PixelsXY { x: 15, y: 20 },
933 PixelsXY { x: 10, y: 10 },
934 ]));
935 }
936
937 #[test]
938 fn test_poly_points_zeroes() {
939 assert!(!poly_points(&[]));
940 assert!(!poly_points(&[
941 PixelsXY { x: 10, y: 10 },
942 PixelsXY { x: 10, y: 10 },
943 PixelsXY { x: 10, y: 10 },
944 PixelsXY { x: 10, y: 10 },
945 ]));
946 }
947
948 #[test]
949 fn test_rect_points_ok() {
950 assert_eq!(
951 Some((PixelsXY { x: 10, y: 20 }, SizeInPixels::new(100, 200))),
952 rect_points(PixelsXY { x: 10, y: 20 }, PixelsXY { x: 110, y: 220 })
953 );
954 assert_eq!(
955 Some((PixelsXY { x: 10, y: 20 }, SizeInPixels::new(100, 200))),
956 rect_points(PixelsXY { x: 110, y: 20 }, PixelsXY { x: 10, y: 220 })
957 );
958 assert_eq!(
959 Some((PixelsXY { x: 10, y: 20 }, SizeInPixels::new(100, 200))),
960 rect_points(PixelsXY { x: 10, y: 220 }, PixelsXY { x: 110, y: 20 })
961 );
962 assert_eq!(
963 Some((PixelsXY { x: 10, y: 20 }, SizeInPixels::new(100, 200))),
964 rect_points(PixelsXY { x: 110, y: 220 }, PixelsXY { x: 10, y: 20 })
965 );
966
967 assert_eq!(
968 Some((PixelsXY { x: -31000, y: -32000 }, SizeInPixels::new(31005, 32010))),
969 rect_points(PixelsXY { x: 5, y: -32000 }, PixelsXY { x: -31000, y: 10 })
970 );
971 assert_eq!(
972 Some((PixelsXY { x: 10, y: 5 }, SizeInPixels::new(30990, 31995))),
973 rect_points(PixelsXY { x: 31000, y: 5 }, PixelsXY { x: 10, y: 32000 })
974 );
975
976 assert_eq!(
977 Some((PixelsXY { x: -31000, y: -32000 }, SizeInPixels::new(62000, 64000))),
978 rect_points(PixelsXY { x: -31000, y: -32000 }, PixelsXY { x: 31000, y: 32000 })
979 );
980 assert_eq!(
981 Some((PixelsXY { x: -31000, y: -32000 }, SizeInPixels::new(62000, 64000))),
982 rect_points(PixelsXY { x: 31000, y: 32000 }, PixelsXY { x: -31000, y: -32000 })
983 );
984 }
985
986 #[test]
987 fn test_rect_points_zeroes() {
988 assert_eq!(None, rect_points(PixelsXY { x: 10, y: 10 }, PixelsXY { x: 10, y: 10 }));
989 assert_eq!(None, rect_points(PixelsXY { x: 10, y: 10 }, PixelsXY { x: 10, y: 20 }));
990 assert_eq!(None, rect_points(PixelsXY { x: 10, y: 10 }, PixelsXY { x: 20, y: 10 }));
991 }
992
993 #[test]
994 fn test_tri_points_ok() {
995 assert!(tri_points(
996 PixelsXY { x: 10, y: 10 },
997 PixelsXY { x: 20, y: 10 },
998 PixelsXY { x: 15, y: 20 }
999 ));
1000 assert!(tri_points(
1001 PixelsXY { x: -31000, y: -32000 },
1002 PixelsXY { x: 31000, y: -32000 },
1003 PixelsXY { x: 0, y: 32000 }
1004 ));
1005 }
1006
1007 #[test]
1008 fn test_tri_points_zeroes() {
1009 assert!(!tri_points(
1010 PixelsXY { x: 10, y: 10 },
1011 PixelsXY { x: 10, y: 10 },
1012 PixelsXY { x: 10, y: 10 }
1013 ));
1014 assert!(!tri_points(
1015 PixelsXY { x: 10, y: 10 },
1016 PixelsXY { x: 20, y: 20 },
1017 PixelsXY { x: 30, y: 30 }
1018 ));
1019 assert!(!tri_points(
1020 PixelsXY { x: 10, y: 10 },
1021 PixelsXY { x: 10, y: 10 },
1022 PixelsXY { x: 20, y: 20 }
1023 ));
1024 }
1025}