Skip to main content

endbasic_std/console/
graphics.rs

1// EndBASIC
2// Copyright 2024 Julio Merino
3//
4// Licensed under the Apache License, Version 2.0 (the "License"); you may not
5// use this file except in compliance with the License.  You may obtain a copy
6// of the License at:
7//
8//     http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
13// License for the specific language governing permissions and limitations
14// under the License.
15
16//! Support to implement graphical consoles.
17
18use 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
28/// Default foreground color, used at console creation time and when requesting the default color
29/// via the `COLOR` command.
30const DEFAULT_FG_COLOR: u8 = AnsiColor::White as u8;
31
32/// Default background color, used at console creation time and when requesting the default color
33/// via the `COLOR` command.
34const DEFAULT_BG_COLOR: u8 = AnsiColor::Black as u8;
35
36/// Conversion between types with silent value clamping.
37pub trait ClampedInto<T> {
38    /// Converts self into `T` capping values at `T`'s maximum or minimum boundaries.
39    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
84/// Multiplication of values into a narrower type with silent value clamping.
85pub trait ClampedMul<T, O> {
86    /// Multiplies self by `rhs` and clamps the result to fit in `O`.
87    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
131/// Returns true if the polygon has at least one non-degenerate edge.
132fn 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
137/// Given two points, calculates the origin and size of the rectangle they define.
138fn 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
168/// Returns true if the three points define a non-degenerate triangle.
169fn 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
177/// Container for configuration information of the backing surface.
178pub struct RasterInfo {
179    /// Size of the console in pixels.
180    pub size_pixels: SizeInPixels,
181
182    /// Size of each character.
183    pub glyph_size: SizeInPixels,
184
185    /// Size of the console in characters.  This is derived from `size_pixels` and `glyph_size`.
186    pub size_chars: CharsXY,
187}
188
189/// Primitive graphical console raster operations.
190pub trait RasterOps {
191    /// Type of the image data (raw pixels).
192    type ID;
193
194    /// Queries information about the backend.
195    fn get_info(&self) -> RasterInfo;
196
197    /// Sets the drawing color for subsequent operations.
198    fn set_draw_color(&mut self, color: RGB);
199
200    /// Clears the whole console with the given color.
201    fn clear(&mut self) -> io::Result<()>;
202
203    /// Sets whether automatic presentation of the canvas is enabled or not.
204    ///
205    /// Raster backends might need this when the device they talk to is very slow and they want to
206    /// buffer data in main memory first.
207    ///
208    /// Does *NOT* present the canvas.
209    fn set_sync(&mut self, _enabled: bool) {}
210
211    /// Displays any buffered changes to the console.
212    ///
213    /// Should ignore any sync values that the backend might have cached via `set_sync`.
214    fn present_canvas(&mut self) -> io::Result<()>;
215
216    /// Returns the color number of the pixel at `xy` if it is in bounds and exactly mappable.
217    fn peek_pixel(&self, xy: PixelsXY) -> io::Result<Option<u8>>;
218
219    /// Reads the raw pixel data for the rectangular region specified by `xy` and `size`.
220    fn read_pixels(&mut self, xy: PixelsXY, size: SizeInPixels) -> io::Result<Self::ID>;
221
222    /// Restores the rectangular region stored in `data` at the `xy` coordinates.
223    fn put_pixels(&mut self, xy: PixelsXY, data: &Self::ID) -> io::Result<()>;
224
225    /// Moves the rectangular region specified by `x1y1` and `size` to `x2y2`.  The original region
226    /// is erased with the current drawing color.
227    fn move_pixels(&mut self, x1y1: PixelsXY, x2y2: PixelsXY, size: SizeInPixels)
228    -> io::Result<()>;
229
230    /// Writes `text` starting at `xy` with the current drawing color.
231    fn write_text(&mut self, xy: PixelsXY, text: &str) -> io::Result<()>;
232
233    /// Draws the outline of a circle at `center` with `radius` using the current drawing color.
234    fn draw_circle(&mut self, center: PixelsXY, radius: u16) -> io::Result<()>;
235
236    /// Draws a filled circle at `center` with `radius` using the current drawing color.
237    fn draw_circle_filled(&mut self, center: PixelsXY, radius: u16) -> io::Result<()>;
238
239    /// Draws a line from `x1y1` to `x2y2` using the current drawing color.
240    fn draw_line(&mut self, x1y1: PixelsXY, x2y2: PixelsXY) -> io::Result<()>;
241
242    /// Draws a single pixel at `xy` using the current drawing color.
243    fn draw_pixel(&mut self, xy: PixelsXY) -> io::Result<()>;
244
245    /// Draws the outline of a polygon using the current drawing color.
246    fn draw_poly(&mut self, points: &[PixelsXY]) -> io::Result<()>;
247
248    /// Draws a filled polygon using the current drawing color.
249    fn draw_poly_filled(&mut self, points: &[PixelsXY]) -> io::Result<()>;
250
251    /// Draws the outline of a rectangle from `x1y1` to `x2y2` using the current drawing color.
252    fn draw_rect(&mut self, xy: PixelsXY, size: SizeInPixels) -> io::Result<()>;
253
254    /// Draws a filled rectangle from `x1y1` to `x2y2` using the current drawing color.
255    fn draw_rect_filled(&mut self, xy: PixelsXY, size: SizeInPixels) -> io::Result<()>;
256
257    /// Draws the outline of a triangle using the current drawing color.
258    fn draw_tri(&mut self, x1y1: PixelsXY, x2y2: PixelsXY, x3y3: PixelsXY) -> io::Result<()>;
259
260    /// Draws a filled triangle using the current drawing color.
261    fn draw_tri_filled(&mut self, x1y1: PixelsXY, x2y2: PixelsXY, x3y3: PixelsXY)
262    -> io::Result<()>;
263}
264
265/// Primitive graphical console input operations.
266#[async_trait(?Send)]
267pub trait InputOps {
268    /// Returns the next key press if any is available.
269    async fn poll_key(&mut self) -> io::Result<Option<Key>>;
270
271    /// Waits for and returns the next key press.
272    async fn read_key(&mut self) -> io::Result<Key>;
273}
274
275/// Implementation of a console that renders to a backing surface.
276pub struct GraphicsConsole<IO, RO, AO>
277where
278    RO: RasterOps,
279{
280    input_ops: IO,
281
282    /// Operations to render to the console.
283    raster_ops: RO,
284
285    /// Operations to reproduce sound.
286    audio_ops: AO,
287
288    /// Size of the console in pixels.
289    size_pixels: SizeInPixels,
290
291    /// Size of each character.
292    glyph_size: SizeInPixels,
293
294    /// Size of the console in characters.  This is derived from `size_pixels` and `glyph_size`.
295    size_chars: CharsXY,
296
297    /// Location of the cursor.
298    cursor_pos: CharsXY,
299
300    /// Whether the cursor is visible or not.
301    cursor_visible: bool,
302
303    /// Raw pixels at the cursor position before the cursor was drawn.  Used to restore the previous
304    /// contents when the cursor moves.
305    cursor_backup: Option<RO::ID>,
306
307    /// Default foreground color to use.
308    default_fg_color: u8,
309
310    /// Default background color to use.
311    default_bg_color: u8,
312
313    /// Current foreground color as exposed via `color` and `set_color`.
314    ansi_fg_color: Option<u8>,
315
316    /// Current background color as exposed via `color` and `set_color`.
317    ansi_bg_color: Option<u8>,
318
319    /// Current foreground color.  Used for text and graphical rendering.
320    fg_color: RGB,
321
322    /// Current background color.  Used to clear text.
323    bg_color: RGB,
324
325    /// State of the console right before entering the "alternate" console.
326    #[allow(clippy::type_complexity)]
327    alt_backup: Option<(RO::ID, CharsXY, Option<u8>, Option<u8>, RGB, RGB)>,
328
329    /// Whether video syncing is enabled or not.
330    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    /// Initializes a new graphical console.
340    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    /// Renders any buffered changes to the backing surface.
379    fn present_canvas(&mut self) -> io::Result<()> {
380        if self.sync_enabled { self.raster_ops.present_canvas() } else { Ok(()) }
381    }
382
383    /// Draws the cursor at the current position and saves the previous contents of the screen so
384    /// that `clear_cursor` can restore them.
385    ///
386    /// Does not present the canvas.
387    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        // TODO(jmmv): It would be nice to draw the cursor with alpha blending so that the letters
398        // under it are visible.  This was done before in the HTML canvas but was lost when I added
399        // the GraphicsConsole abstraction.  Maybe all RGB colors should switch to RGBA.  Or maybe
400        // we should special-case the cursor drawing.
401        self.raster_ops.set_draw_color(self.fg_color);
402        self.raster_ops.draw_rect_filled(x1y1, self.glyph_size)
403    }
404
405    /// Clears the cursor at the current position by restoring the contents of the screen saved by
406    /// an earlier call to `draw_cursor`.
407    ///
408    /// Does not present the canvas.
409    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    /// Moves the cursor to beginning of the next line, scrolling the console if necessary.
422    ///
423    /// Does not clear nor draw the cursor.
424    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    /// Renders the given text at the current cursor position, with wrapping and
450    /// scrolling if necessary.
451    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}