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 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
26/// Default foreground color, used at console creation time and when requesting the default color
27/// via the `COLOR` command.
28const DEFAULT_FG_COLOR: u8 = AnsiColor::White as u8;
29
30/// Default background color, used at console creation time and when requesting the default color
31/// via the `COLOR` command.
32const DEFAULT_BG_COLOR: u8 = AnsiColor::Black as u8;
33
34/// Conversion between types with silent value clamping.
35pub trait ClampedInto<T> {
36    /// Converts self into `T` capping values at `T`'s maximum or minimum boundaries.
37    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
94/// Multiplication of values into a narrower type with silent value clamping.
95pub trait ClampedMul<T, O> {
96    /// Multiplies self by `rhs` and clamps the result to fit in `O`.
97    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
152/// Given two points, calculates the origin and size of the rectangle they define.
153fn 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
179/// Container for configuration information of the backing surface.
180pub struct RasterInfo {
181    /// Size of the console in pixels.
182    pub size_pixels: SizeInPixels,
183
184    /// Size of each character.
185    pub glyph_size: SizeInPixels,
186
187    /// Size of the console in characters.  This is derived from `size_pixels` and `glyph_size`.
188    pub size_chars: CharsXY,
189}
190
191/// Primitive graphical console raster operations.
192pub trait RasterOps {
193    /// Type of the image data (raw pixels).
194    type ID;
195
196    /// Queries information about the backend.
197    fn get_info(&self) -> RasterInfo;
198
199    /// Sets the drawing color for subsequent operations.
200    fn set_draw_color(&mut self, color: RGB);
201
202    /// Clears the whole console with the given color.
203    fn clear(&mut self) -> io::Result<()>;
204
205    /// Sets whether automatic presentation of the canvas is enabled or not.
206    ///
207    /// Raster backends might need this when the device they talk to is very slow and they want to
208    /// buffer data in main memory first.
209    ///
210    /// Does *NOT* present the canvas.
211    fn set_sync(&mut self, _enabled: bool) {}
212
213    /// Displays any buffered changes to the console.
214    ///
215    /// Should ignore any sync values that the backend might have cached via `set_sync`.
216    fn present_canvas(&mut self) -> io::Result<()>;
217
218    /// Reads the raw pixel data for the rectangular region specified by `xy` and `size`.
219    fn read_pixels(&mut self, xy: PixelsXY, size: SizeInPixels) -> io::Result<Self::ID>;
220
221    /// Restores the rectangular region stored in `data` at the `xy` coordinates.
222    fn put_pixels(&mut self, xy: PixelsXY, data: &Self::ID) -> io::Result<()>;
223
224    /// Moves the rectangular region specified by `x1y1` and `size` to `x2y2`.  The original region
225    /// is erased with the current drawing color.
226    fn move_pixels(&mut self, x1y1: PixelsXY, x2y2: PixelsXY, size: SizeInPixels)
227        -> io::Result<()>;
228
229    /// Writes `text` starting at `xy` with the current drawing color.
230    fn write_text(&mut self, xy: PixelsXY, text: &str) -> io::Result<()>;
231
232    /// Draws the outline of a circle at `center` with `radius` using the current drawing color.
233    fn draw_circle(&mut self, center: PixelsXY, radius: u16) -> io::Result<()>;
234
235    /// Draws a filled circle at `center` with `radius` using the current drawing color.
236    fn draw_circle_filled(&mut self, center: PixelsXY, radius: u16) -> io::Result<()>;
237
238    /// Draws a line from `x1y1` to `x2y2` using the current drawing color.
239    fn draw_line(&mut self, x1y1: PixelsXY, x2y2: PixelsXY) -> io::Result<()>;
240
241    /// Draws a single pixel at `xy` using the current drawing color.
242    fn draw_pixel(&mut self, xy: PixelsXY) -> io::Result<()>;
243
244    /// Draws the outline of a rectangle from `x1y1` to `x2y2` using the current drawing color.
245    fn draw_rect(&mut self, xy: PixelsXY, size: SizeInPixels) -> io::Result<()>;
246
247    /// Draws a filled rectangle from `x1y1` to `x2y2` using the current drawing color.
248    fn draw_rect_filled(&mut self, xy: PixelsXY, size: SizeInPixels) -> io::Result<()>;
249}
250
251/// Primitive graphical console input operations.
252#[async_trait(?Send)]
253pub trait InputOps {
254    /// Returns the next key press if any is available.
255    async fn poll_key(&mut self) -> io::Result<Option<Key>>;
256
257    /// Waits for and returns the next key press.
258    async fn read_key(&mut self) -> io::Result<Key>;
259}
260
261/// Implementation of a console that renders to a backing surface.
262pub struct GraphicsConsole<IO, RO>
263where
264    RO: RasterOps,
265{
266    input_ops: IO,
267
268    /// Operations to render to the console.
269    raster_ops: RO,
270
271    /// Size of the console in pixels.
272    size_pixels: SizeInPixels,
273
274    /// Size of each character.
275    glyph_size: SizeInPixels,
276
277    /// Size of the console in characters.  This is derived from `size_pixels` and `glyph_size`.
278    size_chars: CharsXY,
279
280    /// Location of the cursor.
281    cursor_pos: CharsXY,
282
283    /// Whether the cursor is visible or not.
284    cursor_visible: bool,
285
286    /// Raw pixels at the cursor position before the cursor was drawn.  Used to restore the previous
287    /// contents when the cursor moves.
288    cursor_backup: Option<RO::ID>,
289
290    /// Current foreground color as exposed via `color` and `set_color`.
291    ansi_fg_color: Option<u8>,
292
293    /// Current background color as exposed via `color` and `set_color`.
294    ansi_bg_color: Option<u8>,
295
296    /// Current foreground color.  Used for text and graphical rendering.
297    fg_color: RGB,
298
299    /// Current background color.  Used to clear text.
300    bg_color: RGB,
301
302    /// State of the console right before entering the "alternate" console.
303    alt_backup: Option<(RO::ID, CharsXY, RGB, RGB)>,
304
305    /// Whether video syncing is enabled or not.
306    sync_enabled: bool,
307}
308
309impl<IO, RO> GraphicsConsole<IO, RO>
310where
311    IO: InputOps,
312    RO: RasterOps,
313{
314    /// Initializes a new graphical console.
315    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    /// Renders any buffered changes to the backing surface.
342    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    /// Draws the cursor at the current position and saves the previous contents of the screen so
351    /// that `clear_cursor` can restore them.
352    ///
353    /// Does not present the canvas.
354    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        // TODO(jmmv): It would be nice to draw the cursor with alpha blending so that the letters
365        // under it are visible.  This was done before in the HTML canvas but was lost when I added
366        // the GraphicsConsole abstraction.  Maybe all RGB colors should switch to RGBA.  Or maybe
367        // we should special-case the cursor drawing.
368        self.raster_ops.set_draw_color(self.fg_color);
369        self.raster_ops.draw_rect_filled(x1y1, self.glyph_size)
370    }
371
372    /// Clears the cursor at the current position by restoring the contents of the screen saved by
373    /// an earlier call to `draw_cursor`.
374    ///
375    /// Does not present the canvas.
376    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    /// Moves the cursor to beginning of the next line, scrolling the console if necessary.
389    ///
390    /// Does not clear nor draw the cursor.
391    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    /// Renders the given text at the current cursor position, with wrapping and
413    /// scrolling if necessary.
414    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}