Windows, light removals, move more functions to traits #15
					 21 changed files with 1067 additions and 464 deletions
				
			
		
							
								
								
									
										14
									
								
								Cargo.lock
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										14
									
								
								Cargo.lock
									
										
									
										generated
									
									
									
								
							| 
						 | 
				
			
			@ -201,17 +201,6 @@ version = "0.5.0"
 | 
			
		|||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "inherent"
 | 
			
		||||
version = "1.0.12"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "6c38228f24186d9cc68c729accb4d413be9eaed6ad07ff79e0270d9e56f3de13"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "proc-macro2",
 | 
			
		||||
 "quote",
 | 
			
		||||
 "syn",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "is_terminal_polyfill"
 | 
			
		||||
version = "1.70.1"
 | 
			
		||||
| 
						 | 
				
			
			@ -347,13 +336,12 @@ dependencies = [
 | 
			
		|||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "servicepoint"
 | 
			
		||||
version = "0.15.2"
 | 
			
		||||
version = "0.16.0"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "bitvec",
 | 
			
		||||
 "bzip2",
 | 
			
		||||
 "clap",
 | 
			
		||||
 "flate2",
 | 
			
		||||
 "inherent",
 | 
			
		||||
 "log",
 | 
			
		||||
 "once_cell",
 | 
			
		||||
 "rand",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,6 @@
 | 
			
		|||
[package]
 | 
			
		||||
name = "servicepoint"
 | 
			
		||||
version = "0.15.2"
 | 
			
		||||
version = "0.16.0"
 | 
			
		||||
publish = true
 | 
			
		||||
edition = "2021"
 | 
			
		||||
license = "GPL-3.0-or-later"
 | 
			
		||||
| 
						 | 
				
			
			@ -24,7 +24,6 @@ rust-lzma = { version = "0.6", optional = true }
 | 
			
		|||
rand = { version = "0.9", optional = true }
 | 
			
		||||
once_cell = { version = "1.20", optional = true }
 | 
			
		||||
thiserror = "2.0"
 | 
			
		||||
inherent = "1.0"
 | 
			
		||||
 | 
			
		||||
[features]
 | 
			
		||||
default = ["compression_lzma", "cp437"]
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,7 +3,7 @@
 | 
			
		|||
use clap::Parser;
 | 
			
		||||
use servicepoint::{
 | 
			
		||||
    Bitmap, BitmapCommand, Brightness, BrightnessGrid, BrightnessGridCommand,
 | 
			
		||||
    DataRef, Grid, UdpSocketExt, TILE_HEIGHT, TILE_WIDTH,
 | 
			
		||||
    DataRef, GridMut, UdpSocketExt, TILE_HEIGHT, TILE_WIDTH,
 | 
			
		||||
};
 | 
			
		||||
use std::net::UdpSocket;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,8 +2,8 @@
 | 
			
		|||
 | 
			
		||||
use clap::Parser;
 | 
			
		||||
use servicepoint::{
 | 
			
		||||
    Bitmap, BitmapCommand, CompressionCode, Grid, Origin, Packet, UdpSocketExt,
 | 
			
		||||
    FRAME_PACING, PIXEL_HEIGHT, PIXEL_WIDTH,
 | 
			
		||||
    Bitmap, BitmapCommand, CompressionCode, GridMut, Origin, Packet,
 | 
			
		||||
    UdpSocketExt, FRAME_PACING, PIXEL_HEIGHT, PIXEL_WIDTH,
 | 
			
		||||
};
 | 
			
		||||
use std::{net::UdpSocket, thread, time::Duration};
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,7 +2,9 @@
 | 
			
		|||
 | 
			
		||||
use clap::Parser;
 | 
			
		||||
use rand::Rng;
 | 
			
		||||
use servicepoint::{Bitmap, BitmapCommand, Grid, UdpSocketExt, FRAME_PACING};
 | 
			
		||||
use servicepoint::{
 | 
			
		||||
    Bitmap, BitmapCommand, Grid, GridMut, UdpSocketExt, FRAME_PACING,
 | 
			
		||||
};
 | 
			
		||||
use std::{net::UdpSocket, thread};
 | 
			
		||||
 | 
			
		||||
#[derive(Parser, Debug)]
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,8 +2,8 @@
 | 
			
		|||
 | 
			
		||||
use clap::Parser;
 | 
			
		||||
use servicepoint::{
 | 
			
		||||
    Bitmap, BitmapCommand, CompressionCode, Grid, UdpSocketExt, FRAME_PACING,
 | 
			
		||||
    PIXEL_HEIGHT, PIXEL_WIDTH,
 | 
			
		||||
    Bitmap, BitmapCommand, CompressionCode, GridMut, UdpSocketExt,
 | 
			
		||||
    FRAME_PACING, PIXEL_HEIGHT, PIXEL_WIDTH,
 | 
			
		||||
};
 | 
			
		||||
use std::{net::UdpSocket, thread};
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,7 +5,7 @@ use clap::Parser;
 | 
			
		|||
use rand::Rng;
 | 
			
		||||
use servicepoint::{
 | 
			
		||||
    Bitmap, BitmapCommand, Brightness, BrightnessGrid, BrightnessGridCommand,
 | 
			
		||||
    GlobalBrightnessCommand, Grid, Origin, UdpSocketExt, TILE_HEIGHT,
 | 
			
		||||
    GlobalBrightnessCommand, GridMut, Origin, UdpSocketExt, TILE_HEIGHT,
 | 
			
		||||
    TILE_WIDTH,
 | 
			
		||||
};
 | 
			
		||||
use std::{net::UdpSocket, time::Duration};
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,7 +2,7 @@
 | 
			
		|||
 | 
			
		||||
use clap::Parser;
 | 
			
		||||
use servicepoint::{
 | 
			
		||||
    Bitmap, BitmapCommand, Grid, UdpSocketExt, FRAME_PACING, PIXEL_HEIGHT,
 | 
			
		||||
    Bitmap, BitmapCommand, GridMut, UdpSocketExt, FRAME_PACING, PIXEL_HEIGHT,
 | 
			
		||||
    PIXEL_WIDTH,
 | 
			
		||||
};
 | 
			
		||||
use std::{net::UdpSocket, thread, time::Duration};
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -126,8 +126,7 @@
 | 
			
		|||
          default = pkgs.mkShell rec {
 | 
			
		||||
            inputsFrom = [ self.packages.${system}.servicepoint ];
 | 
			
		||||
            packages = with pkgs; [
 | 
			
		||||
              (pkgs.symlinkJoin
 | 
			
		||||
              {
 | 
			
		||||
              (pkgs.symlinkJoin {
 | 
			
		||||
                name = "rust-toolchain";
 | 
			
		||||
                paths = with pkgs; [
 | 
			
		||||
                  rustc
 | 
			
		||||
| 
						 | 
				
			
			@ -145,6 +144,8 @@
 | 
			
		|||
            ];
 | 
			
		||||
            LD_LIBRARY_PATH = "${pkgs.lib.makeLibraryPath (builtins.concatMap (d: d.buildInputs) inputsFrom)}";
 | 
			
		||||
            RUST_SRC_PATH = "${pkgs.rust.packages.stable.rustPlatform.rustLibSrc}";
 | 
			
		||||
            RUST_LOG = "all";
 | 
			
		||||
            RUST_BACKTRACE = "1";
 | 
			
		||||
          };
 | 
			
		||||
        }
 | 
			
		||||
      );
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -106,10 +106,15 @@ mod tests {
 | 
			
		|||
    #[cfg(feature = "rand")]
 | 
			
		||||
    fn test() {
 | 
			
		||||
        let mut rng = rand::rng();
 | 
			
		||||
        // two so test failure is less likely
 | 
			
		||||
        // more so random failure is less likely
 | 
			
		||||
        assert_ne!(
 | 
			
		||||
            [rng.random::<Brightness>(), rng.random()],
 | 
			
		||||
            [rng.random(), rng.random()]
 | 
			
		||||
            [
 | 
			
		||||
                rng.random::<Brightness>(),
 | 
			
		||||
                rng.random(),
 | 
			
		||||
                rng.random(),
 | 
			
		||||
                rng.random()
 | 
			
		||||
            ],
 | 
			
		||||
            [rng.random(), rng.random(), rng.random(), rng.random()]
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -183,6 +183,7 @@ mod tests {
 | 
			
		|||
    use super::*;
 | 
			
		||||
    use crate::{
 | 
			
		||||
        command_code::CommandCode, commands::tests::TestImplementsCommand,
 | 
			
		||||
        GridMut,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    impl TestImplementsCommand for BitmapCommand {}
 | 
			
		||||
| 
						 | 
				
			
			@ -304,7 +305,7 @@ mod tests {
 | 
			
		|||
            crate::commands::tests::round_trip(
 | 
			
		||||
                BitmapCommand {
 | 
			
		||||
                    origin: Origin::ZERO,
 | 
			
		||||
                    bitmap: Bitmap::max_sized(),
 | 
			
		||||
                    bitmap: Bitmap::new(8, 2).unwrap(),
 | 
			
		||||
                    compression: *compression,
 | 
			
		||||
                }
 | 
			
		||||
                .into(),
 | 
			
		||||
| 
						 | 
				
			
			@ -325,4 +326,17 @@ mod tests {
 | 
			
		|||
            );
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn invalid_y() {
 | 
			
		||||
        let command = BitmapCommand {
 | 
			
		||||
            bitmap: Bitmap::new(8, 3).unwrap(),
 | 
			
		||||
            origin: Origin::new(4, u16::MAX as usize + 1),
 | 
			
		||||
            compression: CompressionCode::Uncompressed,
 | 
			
		||||
        };
 | 
			
		||||
        assert!(matches!(
 | 
			
		||||
            Packet::try_from(command),
 | 
			
		||||
            Err(TryIntoPacketError::ConversionError(_)),
 | 
			
		||||
        ))
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -414,4 +414,18 @@ mod tests {
 | 
			
		|||
            }
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn invalid_offset() {
 | 
			
		||||
        let command = BitVecCommand {
 | 
			
		||||
            bitvec: DisplayBitVec::repeat(false, 16),
 | 
			
		||||
            offset: u16::MAX as usize + 1,
 | 
			
		||||
            operation: BinaryOperation::Or,
 | 
			
		||||
            compression: CompressionCode::Uncompressed,
 | 
			
		||||
        };
 | 
			
		||||
        assert!(matches!(
 | 
			
		||||
            Packet::try_from(command),
 | 
			
		||||
            Err(TryIntoPacketError::ConversionError(_)),
 | 
			
		||||
        ))
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,8 +1,9 @@
 | 
			
		|||
use crate::{
 | 
			
		||||
    DataRef, DisplayBitVec, Grid, Payload, ValueGrid, PIXEL_HEIGHT, PIXEL_WIDTH,
 | 
			
		||||
    containers::absolute_bounds_to_abs_range, DataRef, DisplayBitVec, Grid,
 | 
			
		||||
    GridMut, Payload, ValueGrid, Window, WindowMut, PIXEL_HEIGHT, PIXEL_WIDTH,
 | 
			
		||||
};
 | 
			
		||||
use ::bitvec::{order::Msb0, prelude::BitSlice, slice::IterMut};
 | 
			
		||||
use inherent::inherent;
 | 
			
		||||
use std::ops::RangeBounds;
 | 
			
		||||
 | 
			
		||||
/// A fixed-size 2D grid of booleans.
 | 
			
		||||
///
 | 
			
		||||
| 
						 | 
				
			
			@ -143,7 +144,7 @@ impl Bitmap {
 | 
			
		|||
    ///
 | 
			
		||||
    /// Order is equivalent to the following loop:
 | 
			
		||||
    /// ```
 | 
			
		||||
    /// # use servicepoint::{Bitmap, Grid};
 | 
			
		||||
    /// # use servicepoint::{Bitmap, Grid, GridMut};
 | 
			
		||||
    /// # let mut grid = Bitmap::new(8, 2).unwrap();
 | 
			
		||||
    /// # let value = false;
 | 
			
		||||
    /// for y in 0..grid.height() {
 | 
			
		||||
| 
						 | 
				
			
			@ -164,7 +165,7 @@ impl Bitmap {
 | 
			
		|||
    /// ```
 | 
			
		||||
    #[must_use]
 | 
			
		||||
    #[allow(clippy::iter_without_into_iter)]
 | 
			
		||||
    pub fn iter_mut(&mut self) -> IterMut<u8, Msb0> {
 | 
			
		||||
    pub fn iter_mut(&mut self) -> IterMut<'_, u8, Msb0> {
 | 
			
		||||
        self.bit_vec.iter_mut()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -175,10 +176,55 @@ impl Bitmap {
 | 
			
		|||
            row: 0,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Creates a window into the bitmap.
 | 
			
		||||
    ///
 | 
			
		||||
    /// Returns None in case the window does not fit.
 | 
			
		||||
    #[must_use]
 | 
			
		||||
    pub fn window(
 | 
			
		||||
        &self,
 | 
			
		||||
        xs: impl RangeBounds<usize>,
 | 
			
		||||
        ys: impl RangeBounds<usize>,
 | 
			
		||||
    ) -> Option<Window<'_, bool, Self>> {
 | 
			
		||||
        let xs = absolute_bounds_to_abs_range(xs, self.width)?;
 | 
			
		||||
        let ys = absolute_bounds_to_abs_range(ys, self.height)?;
 | 
			
		||||
        Window::new(self, xs, ys)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Creates a mutable window into the bitmap.
 | 
			
		||||
    ///
 | 
			
		||||
    /// Returns None in case the window does not fit.
 | 
			
		||||
    pub fn window_mut(
 | 
			
		||||
        &mut self,
 | 
			
		||||
        xs: impl RangeBounds<usize>,
 | 
			
		||||
        ys: impl RangeBounds<usize>,
 | 
			
		||||
    ) -> Option<WindowMut<'_, bool, Self>> {
 | 
			
		||||
        let xs = absolute_bounds_to_abs_range(xs, self.width)?;
 | 
			
		||||
        let ys = absolute_bounds_to_abs_range(ys, self.height)?;
 | 
			
		||||
        WindowMut::new(self, xs, ys)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[inherent]
 | 
			
		||||
impl Grid<bool> for Bitmap {
 | 
			
		||||
    fn get_optional(&self, x: usize, y: usize) -> Option<bool> {
 | 
			
		||||
        let index = x + y * self.width;
 | 
			
		||||
        if self.is_in_bounds(x, y) {
 | 
			
		||||
            Some(self.bit_vec[index])
 | 
			
		||||
        } else {
 | 
			
		||||
            None
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn width(&self) -> usize {
 | 
			
		||||
        self.width
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn height(&self) -> usize {
 | 
			
		||||
        self.height
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl GridMut<bool> for Bitmap {
 | 
			
		||||
    /// Sets the value of the specified position in the [Bitmap].
 | 
			
		||||
    ///
 | 
			
		||||
    /// # Arguments
 | 
			
		||||
| 
						 | 
				
			
			@ -191,16 +237,13 @@ impl Grid<bool> for Bitmap {
 | 
			
		|||
    /// # Panics
 | 
			
		||||
    ///
 | 
			
		||||
    /// When accessing `x` or `y` out of bounds.
 | 
			
		||||
    #[allow(unused, reason = "False positive because of #[inherent]")]
 | 
			
		||||
    fn set(&mut self, x: usize, y: usize, value: bool) {
 | 
			
		||||
        self.assert_in_bounds(x, y);
 | 
			
		||||
        self.bit_vec.set(x + y * self.width, value);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[allow(unused, reason = "False positive because of #[inherent]")]
 | 
			
		||||
    fn get(&self, x: usize, y: usize) -> bool {
 | 
			
		||||
        self.assert_in_bounds(x, y);
 | 
			
		||||
        self.bit_vec[x + y * self.width]
 | 
			
		||||
    fn set_optional(&mut self, x: usize, y: usize, value: bool) -> bool {
 | 
			
		||||
        if self.is_in_bounds(x, y) {
 | 
			
		||||
            self.bit_vec.set(x + y * self.width, value);
 | 
			
		||||
            true
 | 
			
		||||
        } else {
 | 
			
		||||
            false
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Sets the state of all pixels in the [Bitmap].
 | 
			
		||||
| 
						 | 
				
			
			@ -209,18 +252,9 @@ impl Grid<bool> for Bitmap {
 | 
			
		|||
    ///
 | 
			
		||||
    /// - `this`: instance to write to
 | 
			
		||||
    /// - `value`: the value to set all pixels to
 | 
			
		||||
    #[allow(unused, reason = "False positive because of #[inherent]")]
 | 
			
		||||
    fn fill(&mut self, value: bool) {
 | 
			
		||||
        self.bit_vec.fill(value);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn width(&self) -> usize {
 | 
			
		||||
        self.width
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn height(&self) -> usize {
 | 
			
		||||
        self.height
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl DataRef<u8> for Bitmap {
 | 
			
		||||
| 
						 | 
				
			
			@ -250,26 +284,20 @@ impl From<Bitmap> for DisplayBitVec {
 | 
			
		|||
impl TryFrom<&ValueGrid<bool>> for Bitmap {
 | 
			
		||||
    type Error = ();
 | 
			
		||||
 | 
			
		||||
    /// Converts a grid of [bool]s into a [Bitmap].
 | 
			
		||||
    ///
 | 
			
		||||
    /// Returns Err if the width of `value` is not dividable by 8
 | 
			
		||||
    fn try_from(value: &ValueGrid<bool>) -> Result<Self, Self::Error> {
 | 
			
		||||
        let mut result = Self::new(value.width(), value.height()).ok_or(())?;
 | 
			
		||||
        for (mut to, from) in result.iter_mut().zip(value.iter()) {
 | 
			
		||||
            *to = *from;
 | 
			
		||||
        }
 | 
			
		||||
        result.deref_assign(value);
 | 
			
		||||
        Ok(result)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl From<&Bitmap> for ValueGrid<bool> {
 | 
			
		||||
    /// Converts a [Bitmap] into a grid of [bool]s.
 | 
			
		||||
    fn from(value: &Bitmap) -> Self {
 | 
			
		||||
        let mut result = Self::new(value.width(), value.height());
 | 
			
		||||
        for (to, from) in result.iter_mut().zip(value.iter()) {
 | 
			
		||||
            *to = *from;
 | 
			
		||||
        }
 | 
			
		||||
        result
 | 
			
		||||
impl<T: Grid<bool>> TryFrom<&Window<'_, bool, T>> for Bitmap {
 | 
			
		||||
    type Error = ();
 | 
			
		||||
 | 
			
		||||
    fn try_from(value: &Window<bool, T>) -> Result<Self, Self::Error> {
 | 
			
		||||
        let mut result = Self::new(value.width(), value.height()).ok_or(())?;
 | 
			
		||||
        result.deref_assign(value);
 | 
			
		||||
        Ok(result)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -316,7 +344,8 @@ pub enum LoadBitmapError {
 | 
			
		|||
#[cfg(test)]
 | 
			
		||||
mod tests {
 | 
			
		||||
    use crate::{
 | 
			
		||||
        Bitmap, DataRef, DisplayBitVec, LoadBitmapError, ValueGrid,
 | 
			
		||||
        Bitmap, DataRef, DisplayBitVec, Grid, GridMut, LoadBitmapError,
 | 
			
		||||
        ValueGrid,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
| 
						 | 
				
			
			@ -367,7 +396,7 @@ mod tests {
 | 
			
		|||
    #[should_panic]
 | 
			
		||||
    fn out_of_bounds_x() {
 | 
			
		||||
        let vec = Bitmap::new(8, 2).unwrap();
 | 
			
		||||
        vec.get(8, 1);
 | 
			
		||||
        _ = vec.get(8, 1);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -93,14 +93,14 @@ impl TryFrom<ByteGrid> for BrightnessGrid {
 | 
			
		|||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod tests {
 | 
			
		||||
    use crate::{Brightness, BrightnessGrid, DataRef, Grid, ValueGrid};
 | 
			
		||||
    use crate::{Brightness, BrightnessGrid, DataRef, GridMut, ValueGrid};
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn to_u8_grid() {
 | 
			
		||||
        let mut grid = BrightnessGrid::new(2, 2);
 | 
			
		||||
        grid.set(1, 0, Brightness::MIN);
 | 
			
		||||
        grid.set(0, 1, Brightness::MAX);
 | 
			
		||||
        let actual = ValueGrid::from(&grid);
 | 
			
		||||
        let actual: ValueGrid<u8> = ValueGrid::from(&grid);
 | 
			
		||||
        assert_eq!(actual.data_ref(), &[11, 0, 11, 11]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,4 @@
 | 
			
		|||
use crate::{Grid, SetValueSeriesError, TryLoadValueGridError, ValueGrid};
 | 
			
		||||
use std::string::FromUtf8Error;
 | 
			
		||||
use crate::{CharGridMutExt, GridMut, ValueGrid};
 | 
			
		||||
 | 
			
		||||
/// A grid containing UTF-8 characters.
 | 
			
		||||
///
 | 
			
		||||
| 
						 | 
				
			
			@ -58,151 +57,6 @@ impl CharGrid {
 | 
			
		|||
        }
 | 
			
		||||
        result
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Copies a column from the grid as a String.
 | 
			
		||||
    ///
 | 
			
		||||
    /// Returns [None] if x is out of bounds.
 | 
			
		||||
    ///
 | 
			
		||||
    /// # Examples
 | 
			
		||||
    ///
 | 
			
		||||
    /// ```
 | 
			
		||||
    /// # use servicepoint::CharGrid;
 | 
			
		||||
    /// let grid = CharGrid::from("ab\ncd");
 | 
			
		||||
    /// let col = grid.get_col_str(0).unwrap(); // "ac"
 | 
			
		||||
    /// ```
 | 
			
		||||
    #[must_use]
 | 
			
		||||
    pub fn get_col_str(&self, x: usize) -> Option<String> {
 | 
			
		||||
        Some(String::from_iter(self.get_col(x)?))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Copies a row from the grid as a String.
 | 
			
		||||
    ///
 | 
			
		||||
    /// Returns [None] if y is out of bounds.
 | 
			
		||||
    ///
 | 
			
		||||
    /// # Examples
 | 
			
		||||
    ///
 | 
			
		||||
    /// ```
 | 
			
		||||
    /// # use servicepoint::CharGrid;
 | 
			
		||||
    /// let grid = CharGrid::from("ab\ncd");
 | 
			
		||||
    /// let row = grid.get_row_str(0).unwrap(); // "ab"
 | 
			
		||||
    /// ```
 | 
			
		||||
    #[must_use]
 | 
			
		||||
    pub fn get_row_str(&self, y: usize) -> Option<String> {
 | 
			
		||||
        Some(String::from_iter(self.get_row(y)?))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Overwrites a row in the grid with a str.
 | 
			
		||||
    ///
 | 
			
		||||
    /// Returns [`SetValueSeriesError`] if y is out of bounds or `row` is not of the correct size.
 | 
			
		||||
    ///
 | 
			
		||||
    /// # Examples
 | 
			
		||||
    ///
 | 
			
		||||
    /// ```
 | 
			
		||||
    /// # use servicepoint::CharGrid;
 | 
			
		||||
    /// let mut grid = CharGrid::from("ab\ncd");
 | 
			
		||||
    /// grid.set_row_str(0, "ef").unwrap();
 | 
			
		||||
    /// ```
 | 
			
		||||
    pub fn set_row_str(
 | 
			
		||||
        &mut self,
 | 
			
		||||
        y: usize,
 | 
			
		||||
        value: &str,
 | 
			
		||||
    ) -> Result<(), SetValueSeriesError> {
 | 
			
		||||
        let width = self.width();
 | 
			
		||||
 | 
			
		||||
        let len = value.len();
 | 
			
		||||
        if len > width {
 | 
			
		||||
            return Err(SetValueSeriesError::InvalidLength {
 | 
			
		||||
                actual: len,
 | 
			
		||||
                expected: width,
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let height = self.height();
 | 
			
		||||
        if y >= height {
 | 
			
		||||
            return Err(SetValueSeriesError::OutOfBounds {
 | 
			
		||||
                index: y,
 | 
			
		||||
                size: height,
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let chars = value.chars().take(width);
 | 
			
		||||
        for (x, c) in chars.enumerate() {
 | 
			
		||||
            self.set(x, y, c);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Overwrites a column in the grid with a str.
 | 
			
		||||
    ///
 | 
			
		||||
    /// Returns [`SetValueSeriesError`] if y is out of bounds or `row` is not of the correct size.
 | 
			
		||||
    ///
 | 
			
		||||
    /// # Examples
 | 
			
		||||
    ///
 | 
			
		||||
    /// ```
 | 
			
		||||
    /// # use servicepoint::CharGrid;
 | 
			
		||||
    /// let mut grid = CharGrid::from("ab\ncd");
 | 
			
		||||
    /// grid.set_col_str(0, "ef").unwrap();
 | 
			
		||||
    /// ```
 | 
			
		||||
    pub fn set_col_str(
 | 
			
		||||
        &mut self,
 | 
			
		||||
        x: usize,
 | 
			
		||||
        value: &str,
 | 
			
		||||
    ) -> Result<(), SetValueSeriesError> {
 | 
			
		||||
        let height = self.height();
 | 
			
		||||
 | 
			
		||||
        let len = value.len();
 | 
			
		||||
        if len > height {
 | 
			
		||||
            return Err(SetValueSeriesError::InvalidLength {
 | 
			
		||||
                actual: len,
 | 
			
		||||
                expected: height,
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let width = self.width();
 | 
			
		||||
        if x >= width {
 | 
			
		||||
            return Err(SetValueSeriesError::OutOfBounds {
 | 
			
		||||
                index: x,
 | 
			
		||||
                size: width,
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let chars = value.chars().take(height);
 | 
			
		||||
        for (y, c) in chars.enumerate() {
 | 
			
		||||
            self.set(x, y, c);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Loads a [`CharGrid`] with the specified dimensions from the provided UTF-8 bytes.
 | 
			
		||||
    ///
 | 
			
		||||
    /// returns: [`CharGrid`] that contains the provided data, or [`FromUtf8Error`] if the data is invalid.
 | 
			
		||||
    ///
 | 
			
		||||
    /// # Examples
 | 
			
		||||
    ///
 | 
			
		||||
    /// ```
 | 
			
		||||
    /// # use servicepoint::CharGrid;
 | 
			
		||||
    /// let grid = CharGrid::load_utf8(2, 2, [97u8, 98, 99, 100].to_vec());
 | 
			
		||||
    /// ```
 | 
			
		||||
    pub fn load_utf8(
 | 
			
		||||
        width: usize,
 | 
			
		||||
        height: usize,
 | 
			
		||||
        bytes: Vec<u8>,
 | 
			
		||||
    ) -> Result<CharGrid, LoadUtf8Error> {
 | 
			
		||||
        let s: Vec<char> = String::from_utf8(bytes)?.chars().collect();
 | 
			
		||||
        CharGrid::load(width, height, &s).ok_or(LoadUtf8Error::TryLoadError(
 | 
			
		||||
            TryLoadValueGridError::InvalidDimensions,
 | 
			
		||||
        ))
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, thiserror::Error)]
 | 
			
		||||
pub enum LoadUtf8Error {
 | 
			
		||||
    #[error(transparent)]
 | 
			
		||||
    FromUtf8Error(#[from] FromUtf8Error),
 | 
			
		||||
    #[error(transparent)]
 | 
			
		||||
    TryLoadError(#[from] TryLoadValueGridError),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl From<&str> for CharGrid {
 | 
			
		||||
| 
						 | 
				
			
			@ -274,7 +128,7 @@ impl From<&CharGrid> for Vec<u8> {
 | 
			
		|||
    /// let grid = CharGrid::from("ab\ncd");
 | 
			
		||||
    /// let height = grid.height();
 | 
			
		||||
    /// let width = grid.width();
 | 
			
		||||
    /// let grid = CharGrid::load_utf8(width, height, grid.into());
 | 
			
		||||
    /// let bytes = Vec::<u8>::from(grid);
 | 
			
		||||
    /// ```
 | 
			
		||||
    fn from(value: &CharGrid) -> Self {
 | 
			
		||||
        value.iter().collect::<String>().into_bytes()
 | 
			
		||||
| 
						 | 
				
			
			@ -291,31 +145,7 @@ impl From<CharGrid> for Vec<u8> {
 | 
			
		|||
#[cfg(test)]
 | 
			
		||||
mod test {
 | 
			
		||||
    use super::*;
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn col_str() {
 | 
			
		||||
        let mut grid = CharGrid::new(2, 3);
 | 
			
		||||
        assert_eq!(grid.get_col_str(2), None);
 | 
			
		||||
        assert_eq!(grid.get_col_str(1), Some(String::from("\0\0\0")));
 | 
			
		||||
        assert_eq!(grid.set_col_str(1, "abc"), Ok(()));
 | 
			
		||||
        assert_eq!(grid.get_col_str(1), Some(String::from("abc")));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn row_str() {
 | 
			
		||||
        let mut grid = CharGrid::new(2, 3);
 | 
			
		||||
        assert_eq!(grid.get_row_str(3), None);
 | 
			
		||||
        assert_eq!(grid.get_row_str(1), Some(String::from("\0\0")));
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            grid.set_row_str(1, "abc"),
 | 
			
		||||
            Err(SetValueSeriesError::InvalidLength {
 | 
			
		||||
                expected: 2,
 | 
			
		||||
                actual: 3
 | 
			
		||||
            })
 | 
			
		||||
        );
 | 
			
		||||
        assert_eq!(grid.set_row_str(1, "ab"), Ok(()));
 | 
			
		||||
        assert_eq!(grid.get_row_str(1), Some(String::from("ab")));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    use crate::Grid;
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn str_to_char_grid() {
 | 
			
		||||
        // conversion with .to_string() covers one more line
 | 
			
		||||
| 
						 | 
				
			
			@ -330,8 +160,15 @@ mod test {
 | 
			
		|||
    fn round_trip_bytes() {
 | 
			
		||||
        let grid = CharGrid::from("Hello\0\nWorld!\n...\0\0\0");
 | 
			
		||||
        let bytes: Vec<u8> = grid.clone().into();
 | 
			
		||||
        let copy =
 | 
			
		||||
            CharGrid::load_utf8(grid.width(), grid.height(), bytes).unwrap();
 | 
			
		||||
        let copy = CharGrid::load(
 | 
			
		||||
            grid.width(),
 | 
			
		||||
            grid.height(),
 | 
			
		||||
            &String::from_utf8(bytes)
 | 
			
		||||
                .unwrap()
 | 
			
		||||
                .chars()
 | 
			
		||||
                .collect::<Vec<_>>(),
 | 
			
		||||
        )
 | 
			
		||||
        .unwrap();
 | 
			
		||||
        assert_eq!(grid, copy);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										173
									
								
								src/containers/char_grid_ext.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										173
									
								
								src/containers/char_grid_ext.rs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,173 @@
 | 
			
		|||
use crate::{Grid, GridMut, SetValueSeriesError};
 | 
			
		||||
 | 
			
		||||
/// Extension methods for any [`Grid<char>`]
 | 
			
		||||
pub trait CharGridExt {
 | 
			
		||||
    /// Copies a column from the grid as a String.
 | 
			
		||||
    ///
 | 
			
		||||
    /// Returns [None] if x is out of bounds.
 | 
			
		||||
    ///
 | 
			
		||||
    /// # Examples
 | 
			
		||||
    ///
 | 
			
		||||
    /// ```
 | 
			
		||||
    /// # use servicepoint::{CharGrid, CharGridExt};
 | 
			
		||||
    /// let grid = CharGrid::from("ab\ncd");
 | 
			
		||||
    /// let col = grid.get_col_str(0).unwrap(); // "ac"
 | 
			
		||||
    /// ```
 | 
			
		||||
    #[must_use]
 | 
			
		||||
    fn get_col_str(&self, x: usize) -> Option<String>;
 | 
			
		||||
 | 
			
		||||
    /// Copies a row from the grid as a String.
 | 
			
		||||
    ///
 | 
			
		||||
    /// Returns [None] if y is out of bounds.
 | 
			
		||||
    ///
 | 
			
		||||
    /// # Examples
 | 
			
		||||
    ///
 | 
			
		||||
    /// ```
 | 
			
		||||
    /// # use servicepoint::{CharGrid, CharGridExt};
 | 
			
		||||
    /// let grid = CharGrid::from("ab\ncd");
 | 
			
		||||
    /// let row = grid.get_row_str(0).unwrap(); // "ab"
 | 
			
		||||
    /// ```
 | 
			
		||||
    #[must_use]
 | 
			
		||||
    fn get_row_str(&self, y: usize) -> Option<String>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Extension methods for any [`GridMut<char>`].
 | 
			
		||||
pub trait CharGridMutExt {
 | 
			
		||||
    /// Overwrites a row in the grid with a str.
 | 
			
		||||
    ///
 | 
			
		||||
    /// Returns [`SetValueSeriesError`] if y is out of bounds or `row` is not of the correct size.
 | 
			
		||||
    ///
 | 
			
		||||
    /// # Examples
 | 
			
		||||
    ///
 | 
			
		||||
    /// ```
 | 
			
		||||
    /// # use servicepoint::{CharGrid, CharGridMutExt};
 | 
			
		||||
    /// let mut grid = CharGrid::from("ab\ncd");
 | 
			
		||||
    /// grid.set_row_str(0, "ef").unwrap();
 | 
			
		||||
    /// ```
 | 
			
		||||
    fn set_row_str(
 | 
			
		||||
        &mut self,
 | 
			
		||||
        y: usize,
 | 
			
		||||
        value: &str,
 | 
			
		||||
    ) -> Result<(), SetValueSeriesError>;
 | 
			
		||||
 | 
			
		||||
    /// Overwrites a column in the grid with a str.
 | 
			
		||||
    ///
 | 
			
		||||
    /// Returns [`SetValueSeriesError`] if y is out of bounds or `row` is not of the correct size.
 | 
			
		||||
    ///
 | 
			
		||||
    /// # Examples
 | 
			
		||||
    ///
 | 
			
		||||
    /// ```
 | 
			
		||||
    /// # use servicepoint::{CharGrid, CharGridMutExt};
 | 
			
		||||
    /// let mut grid = CharGrid::from("ab\ncd");
 | 
			
		||||
    /// grid.set_col_str(0, "ef").unwrap();
 | 
			
		||||
    /// ```
 | 
			
		||||
    fn set_col_str(
 | 
			
		||||
        &mut self,
 | 
			
		||||
        x: usize,
 | 
			
		||||
        value: &str,
 | 
			
		||||
    ) -> Result<(), SetValueSeriesError>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<G: Grid<char>> CharGridExt for G {
 | 
			
		||||
    fn get_col_str(&self, x: usize) -> Option<String> {
 | 
			
		||||
        Some(String::from_iter(self.get_col(x)?))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn get_row_str(&self, y: usize) -> Option<String> {
 | 
			
		||||
        Some(String::from_iter(self.get_row(y)?))
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<G: GridMut<char>> CharGridMutExt for G {
 | 
			
		||||
    fn set_row_str(
 | 
			
		||||
        &mut self,
 | 
			
		||||
        y: usize,
 | 
			
		||||
        value: &str,
 | 
			
		||||
    ) -> Result<(), SetValueSeriesError> {
 | 
			
		||||
        let width = self.width();
 | 
			
		||||
 | 
			
		||||
        let len = value.len();
 | 
			
		||||
        if len > width {
 | 
			
		||||
            return Err(SetValueSeriesError::InvalidLength {
 | 
			
		||||
                actual: len,
 | 
			
		||||
                expected: width,
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let height = self.height();
 | 
			
		||||
        if y >= height {
 | 
			
		||||
            return Err(SetValueSeriesError::OutOfBounds {
 | 
			
		||||
                index: y,
 | 
			
		||||
                size: height,
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let chars = value.chars().take(width);
 | 
			
		||||
        for (x, c) in chars.enumerate() {
 | 
			
		||||
            self.set(x, y, c);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn set_col_str(
 | 
			
		||||
        &mut self,
 | 
			
		||||
        x: usize,
 | 
			
		||||
        value: &str,
 | 
			
		||||
    ) -> Result<(), SetValueSeriesError> {
 | 
			
		||||
        let height = self.height();
 | 
			
		||||
 | 
			
		||||
        let len = value.len();
 | 
			
		||||
        if len > height {
 | 
			
		||||
            return Err(SetValueSeriesError::InvalidLength {
 | 
			
		||||
                actual: len,
 | 
			
		||||
                expected: height,
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let width = self.width();
 | 
			
		||||
        if x >= width {
 | 
			
		||||
            return Err(SetValueSeriesError::OutOfBounds {
 | 
			
		||||
                index: x,
 | 
			
		||||
                size: width,
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let chars = value.chars().take(height);
 | 
			
		||||
        for (y, c) in chars.enumerate() {
 | 
			
		||||
            self.set(x, y, c);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod test {
 | 
			
		||||
    use crate::{CharGrid, CharGridExt, CharGridMutExt, SetValueSeriesError};
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn col_str() {
 | 
			
		||||
        let mut grid = CharGrid::new(2, 3);
 | 
			
		||||
        assert_eq!(grid.get_col_str(2), None);
 | 
			
		||||
        assert_eq!(grid.get_col_str(1), Some(String::from("\0\0\0")));
 | 
			
		||||
        assert_eq!(grid.set_col_str(1, "abc"), Ok(()));
 | 
			
		||||
        assert_eq!(grid.get_col_str(1), Some(String::from("abc")));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn row_str() {
 | 
			
		||||
        let mut grid = CharGrid::new(2, 3);
 | 
			
		||||
        assert_eq!(grid.get_row_str(3), None);
 | 
			
		||||
        assert_eq!(grid.get_row_str(1), Some(String::from("\0\0")));
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            grid.set_row_str(1, "abc"),
 | 
			
		||||
            Err(SetValueSeriesError::InvalidLength {
 | 
			
		||||
                expected: 2,
 | 
			
		||||
                actual: 3
 | 
			
		||||
            })
 | 
			
		||||
        );
 | 
			
		||||
        assert_eq!(grid.set_row_str(1, "ab"), Ok(()));
 | 
			
		||||
        assert_eq!(grid.get_row_str(1), Some(String::from("ab")));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,4 +1,4 @@
 | 
			
		|||
use crate::{Grid, ValueGrid};
 | 
			
		||||
use crate::{GridMut, ValueGrid};
 | 
			
		||||
 | 
			
		||||
/// A grid containing codepage 437 characters.
 | 
			
		||||
///
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,16 +1,7 @@
 | 
			
		|||
/// A two-dimensional grid of `T`
 | 
			
		||||
pub trait Grid<T> {
 | 
			
		||||
    /// Sets the value at the specified position
 | 
			
		||||
    ///
 | 
			
		||||
    /// # Arguments
 | 
			
		||||
    ///
 | 
			
		||||
    /// - `x` and `y`: position of the cell to read
 | 
			
		||||
    ///
 | 
			
		||||
    /// # Panics
 | 
			
		||||
    ///
 | 
			
		||||
    /// When accessing `x` or `y` out of bounds.
 | 
			
		||||
    fn set(&mut self, x: usize, y: usize, value: T);
 | 
			
		||||
use crate::SetValueSeriesError;
 | 
			
		||||
 | 
			
		||||
/// A two-dimensional readonly grid of `T`
 | 
			
		||||
pub trait Grid<T> {
 | 
			
		||||
    /// Get the current value at the specified position
 | 
			
		||||
    ///
 | 
			
		||||
    /// # Arguments
 | 
			
		||||
| 
						 | 
				
			
			@ -20,7 +11,12 @@ pub trait Grid<T> {
 | 
			
		|||
    /// # Panics
 | 
			
		||||
    ///
 | 
			
		||||
    /// When accessing `x` or `y` out of bounds.
 | 
			
		||||
    fn get(&self, x: usize, y: usize) -> T;
 | 
			
		||||
    #[must_use]
 | 
			
		||||
    fn get(&self, x: usize, y: usize) -> T {
 | 
			
		||||
        #[allow(clippy::panic, reason = "This is the version that panics - _optional does not")]
 | 
			
		||||
        self.get_optional(x, y)
 | 
			
		||||
            .unwrap_or_else(|| panic!("Cannot access index ({x}, {y}) because it is out of bounds for a grid of dimension {}x{}", self.width(), self.height()))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Get the current value at the specified position if the position is inside of bounds
 | 
			
		||||
    ///
 | 
			
		||||
| 
						 | 
				
			
			@ -29,40 +25,19 @@ pub trait Grid<T> {
 | 
			
		|||
    /// - `x` and `y`: position of the cell to read
 | 
			
		||||
    ///
 | 
			
		||||
    /// returns: Value at position or None
 | 
			
		||||
    fn get_optional(&self, x: usize, y: usize) -> Option<T> {
 | 
			
		||||
        if self.is_in_bounds(x, y) {
 | 
			
		||||
            Some(self.get(x, y))
 | 
			
		||||
        } else {
 | 
			
		||||
            None
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Sets the value at the specified position if the position is inside of bounds
 | 
			
		||||
    ///
 | 
			
		||||
    /// # Arguments
 | 
			
		||||
    ///
 | 
			
		||||
    /// - `x` and `y`: position of the cell to read
 | 
			
		||||
    ///
 | 
			
		||||
    /// returns: the old value or None
 | 
			
		||||
    fn set_optional(&mut self, x: usize, y: usize, value: T) -> bool {
 | 
			
		||||
        if self.is_in_bounds(x, y) {
 | 
			
		||||
            self.set(x, y, value);
 | 
			
		||||
            true
 | 
			
		||||
        } else {
 | 
			
		||||
            false
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Sets all cells in the grid to the specified value
 | 
			
		||||
    fn fill(&mut self, value: T);
 | 
			
		||||
    #[must_use]
 | 
			
		||||
    fn get_optional(&self, x: usize, y: usize) -> Option<T>;
 | 
			
		||||
 | 
			
		||||
    /// the size in x-direction
 | 
			
		||||
    #[must_use]
 | 
			
		||||
    fn width(&self) -> usize;
 | 
			
		||||
 | 
			
		||||
    /// the height in y-direction
 | 
			
		||||
    #[must_use]
 | 
			
		||||
    fn height(&self) -> usize;
 | 
			
		||||
 | 
			
		||||
    /// Checks whether the specified signed position is in grid bounds
 | 
			
		||||
    #[must_use]
 | 
			
		||||
    fn is_in_bounds(&self, x: usize, y: usize) -> bool {
 | 
			
		||||
        x < self.width() && y < self.height()
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -78,4 +53,219 @@ pub trait Grid<T> {
 | 
			
		|||
        let height = self.height();
 | 
			
		||||
        assert!(y < height, "cannot access index [{x}, {y}] because y is outside of bounds [0..{height})");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Copies a row from the grid.
 | 
			
		||||
    ///
 | 
			
		||||
    /// Returns [None] if y is out of bounds.
 | 
			
		||||
    #[must_use]
 | 
			
		||||
    fn get_row(&self, y: usize) -> Option<Vec<T>> {
 | 
			
		||||
        if y >= self.height() {
 | 
			
		||||
            return None;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let width = self.width();
 | 
			
		||||
        let mut row = Vec::with_capacity(width);
 | 
			
		||||
        for x in 0..width {
 | 
			
		||||
            row.push(self.get(x, y));
 | 
			
		||||
        }
 | 
			
		||||
        Some(row)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Copies a column from the grid.
 | 
			
		||||
    ///
 | 
			
		||||
    /// Returns [None] if x is out of bounds.
 | 
			
		||||
    #[must_use]
 | 
			
		||||
    fn get_col(&self, x: usize) -> Option<Vec<T>> {
 | 
			
		||||
        if x >= self.width() {
 | 
			
		||||
            return None;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let height = self.height();
 | 
			
		||||
        let mut col = Vec::with_capacity(height);
 | 
			
		||||
        for y in 0..height {
 | 
			
		||||
            col.push(self.get(x, y));
 | 
			
		||||
        }
 | 
			
		||||
        Some(col)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// A two-dimensional mutable grid of `T`
 | 
			
		||||
pub trait GridMut<T: Clone>: Grid<T> {
 | 
			
		||||
    /// Sets the value at the specified position
 | 
			
		||||
    ///
 | 
			
		||||
    /// # Arguments
 | 
			
		||||
    ///
 | 
			
		||||
    /// - `x` and `y`: position of the cell to read
 | 
			
		||||
    ///
 | 
			
		||||
    /// # Panics
 | 
			
		||||
    ///
 | 
			
		||||
    /// When accessing `x` or `y` out of bounds.
 | 
			
		||||
    fn set(&mut self, x: usize, y: usize, value: T) {
 | 
			
		||||
        #[allow(
 | 
			
		||||
            clippy::expect_used,
 | 
			
		||||
            reason = "This is the version that panics - _optional does not"
 | 
			
		||||
        )]
 | 
			
		||||
        let worked = self.set_optional(x, y, value);
 | 
			
		||||
        assert!(worked, "Cannot access index ({x}, {y}) because it is out of bounds for a grid of dimension {}x{}", self.width(), self.height());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Sets the value at the specified position if the position is inside of bounds
 | 
			
		||||
    ///
 | 
			
		||||
    /// # Arguments
 | 
			
		||||
    ///
 | 
			
		||||
    /// - `x` and `y`: position of the cell to read
 | 
			
		||||
    ///
 | 
			
		||||
    /// returns: true if the value has been set
 | 
			
		||||
    #[must_use]
 | 
			
		||||
    fn set_optional(&mut self, x: usize, y: usize, value: T) -> bool;
 | 
			
		||||
 | 
			
		||||
    /// Sets all cells in the grid to the specified value
 | 
			
		||||
    fn fill(&mut self, value: T);
 | 
			
		||||
 | 
			
		||||
    /// Fills the grid with the values from the provided grid.
 | 
			
		||||
    ///
 | 
			
		||||
    /// The grids have to match in size exactly.
 | 
			
		||||
    ///
 | 
			
		||||
    /// For 1D slices the equivalent would be `*slice = other_slice`.
 | 
			
		||||
    fn deref_assign<O: Grid<T>>(&mut self, other: &O) {
 | 
			
		||||
        let width = self.width();
 | 
			
		||||
        let height = self.height();
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            width,
 | 
			
		||||
            other.width(),
 | 
			
		||||
            "Cannot assign grid of width {} to a window of width {}",
 | 
			
		||||
            other.width(),
 | 
			
		||||
            self.width()
 | 
			
		||||
        );
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            height,
 | 
			
		||||
            other.height(),
 | 
			
		||||
            "Cannot assign grid of height {} to a height of width {}",
 | 
			
		||||
            other.height(),
 | 
			
		||||
            self.height()
 | 
			
		||||
        );
 | 
			
		||||
        for y in 0..height {
 | 
			
		||||
            for x in 0..width {
 | 
			
		||||
                self.set(x, y, other.get(x, y));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Overwrites a column in the grid.
 | 
			
		||||
    ///
 | 
			
		||||
    /// Returns [Err] if x is out of bounds or `col` is not of the correct size.
 | 
			
		||||
    fn set_col(
 | 
			
		||||
        &mut self,
 | 
			
		||||
        x: usize,
 | 
			
		||||
        col: &[T],
 | 
			
		||||
    ) -> Result<(), SetValueSeriesError> {
 | 
			
		||||
        let height = self.height();
 | 
			
		||||
        if col.len() != height {
 | 
			
		||||
            return Err(SetValueSeriesError::InvalidLength {
 | 
			
		||||
                expected: height,
 | 
			
		||||
                actual: col.len(),
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if x >= self.width() {
 | 
			
		||||
            return Err(SetValueSeriesError::OutOfBounds {
 | 
			
		||||
                size: self.width(),
 | 
			
		||||
                index: x,
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        for (y, item) in col.iter().enumerate().take(height) {
 | 
			
		||||
            self.set(x, y, item.clone());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Overwrites a row in the grid.
 | 
			
		||||
    ///
 | 
			
		||||
    /// Returns [Err] if y is out of bounds or `row` is not of the correct size.
 | 
			
		||||
    fn set_row(
 | 
			
		||||
        &mut self,
 | 
			
		||||
        y: usize,
 | 
			
		||||
        row: &[T],
 | 
			
		||||
    ) -> Result<(), SetValueSeriesError> {
 | 
			
		||||
        let width = self.width();
 | 
			
		||||
        if row.len() != width {
 | 
			
		||||
            return Err(SetValueSeriesError::InvalidLength {
 | 
			
		||||
                expected: width,
 | 
			
		||||
                actual: row.len(),
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if y >= self.height() {
 | 
			
		||||
            return Err(SetValueSeriesError::OutOfBounds {
 | 
			
		||||
                size: self.height(),
 | 
			
		||||
                index: y,
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        for (x, item) in row.iter().enumerate().take(width) {
 | 
			
		||||
            self.set(x, y, item.clone());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod test {
 | 
			
		||||
    use crate::{DataRef, Grid, GridMut, SetValueSeriesError, ValueGrid};
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn optional() {
 | 
			
		||||
        let mut grid = ValueGrid::load(2, 2, &[0, 1, 2, 3]).unwrap();
 | 
			
		||||
        assert!(grid.set_optional(0, 0, 5));
 | 
			
		||||
        assert!(!grid.set_optional(0, 8, 42));
 | 
			
		||||
        assert_eq!(grid.data_ref(), [5, 1, 2, 3]);
 | 
			
		||||
 | 
			
		||||
        assert_eq!(grid.get_optional(0, 0), Some(5));
 | 
			
		||||
        assert_eq!(grid.get_optional(0, 8), None);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn col() {
 | 
			
		||||
        let mut grid = ValueGrid::load(2, 3, &[0, 1, 2, 3, 4, 5]).unwrap();
 | 
			
		||||
        assert_eq!(grid.get_col(0), Some(vec![0, 2, 4]));
 | 
			
		||||
        assert_eq!(grid.get_col(1), Some(vec![1, 3, 5]));
 | 
			
		||||
        assert_eq!(grid.get_col(2), None);
 | 
			
		||||
        assert_eq!(grid.set_col(0, &[5, 7, 9]), Ok(()));
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            grid.set_col(2, &[5, 7, 9]),
 | 
			
		||||
            Err(SetValueSeriesError::OutOfBounds { size: 2, index: 2 })
 | 
			
		||||
        );
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            grid.set_col(0, &[5, 7]),
 | 
			
		||||
            Err(SetValueSeriesError::InvalidLength {
 | 
			
		||||
                expected: 3,
 | 
			
		||||
                actual: 2
 | 
			
		||||
            })
 | 
			
		||||
        );
 | 
			
		||||
        assert_eq!(grid.get_col(0), Some(vec![5, 7, 9]));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn row() {
 | 
			
		||||
        let mut grid = ValueGrid::load(2, 3, &[0, 1, 2, 3, 4, 5]).unwrap();
 | 
			
		||||
        assert_eq!(grid.get_row(0), Some(vec![0, 1]));
 | 
			
		||||
        assert_eq!(grid.get_row(2), Some(vec![4, 5]));
 | 
			
		||||
        assert_eq!(grid.get_row(3), None);
 | 
			
		||||
        assert_eq!(grid.set_row(0, &[5, 7]), Ok(()));
 | 
			
		||||
        assert_eq!(grid.get_row(0), Some(vec![5, 7]));
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            grid.set_row(3, &[5, 7]),
 | 
			
		||||
            Err(SetValueSeriesError::OutOfBounds { size: 3, index: 3 })
 | 
			
		||||
        );
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            grid.set_row(2, &[5, 7, 3]),
 | 
			
		||||
            Err(SetValueSeriesError::InvalidLength {
 | 
			
		||||
                expected: 2,
 | 
			
		||||
                actual: 3
 | 
			
		||||
            })
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,19 +3,63 @@ mod bitmap;
 | 
			
		|||
mod brightness_grid;
 | 
			
		||||
mod byte_grid;
 | 
			
		||||
mod char_grid;
 | 
			
		||||
mod char_grid_ext;
 | 
			
		||||
mod cp437_grid;
 | 
			
		||||
mod data_ref;
 | 
			
		||||
mod grid;
 | 
			
		||||
mod value_grid;
 | 
			
		||||
mod window;
 | 
			
		||||
 | 
			
		||||
pub use bit_vec::{bitvec, DisplayBitVec};
 | 
			
		||||
pub use bitmap::*;
 | 
			
		||||
pub use bitmap::{Bitmap, LoadBitmapError};
 | 
			
		||||
pub use brightness_grid::BrightnessGrid;
 | 
			
		||||
pub use byte_grid::ByteGrid;
 | 
			
		||||
pub use char_grid::CharGrid;
 | 
			
		||||
pub use cp437_grid::Cp437Grid;
 | 
			
		||||
pub use char_grid_ext::{CharGridExt, CharGridMutExt};
 | 
			
		||||
pub use cp437_grid::{Cp437Grid, InvalidCharError};
 | 
			
		||||
pub use data_ref::DataRef;
 | 
			
		||||
pub use grid::Grid;
 | 
			
		||||
pub use grid::{Grid, GridMut};
 | 
			
		||||
pub use value_grid::{
 | 
			
		||||
    IterGridRows, SetValueSeriesError, TryLoadValueGridError, Value, ValueGrid,
 | 
			
		||||
    SetValueSeriesError, TryLoadValueGridError, Value, ValueGrid,
 | 
			
		||||
};
 | 
			
		||||
pub use window::{Window, WindowMut};
 | 
			
		||||
 | 
			
		||||
use std::{
 | 
			
		||||
    collections::Bound,
 | 
			
		||||
    ops::{Range, RangeBounds},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
pub(crate) fn absolute_bounds_to_abs_range(
 | 
			
		||||
    bounds: impl RangeBounds<usize>,
 | 
			
		||||
    len: usize,
 | 
			
		||||
) -> Option<Range<usize>> {
 | 
			
		||||
    let start = match bounds.start_bound() {
 | 
			
		||||
        Bound::Included(start) => *start,
 | 
			
		||||
        Bound::Excluded(start) => start + 1,
 | 
			
		||||
        Bound::Unbounded => 0,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    let end = match bounds.end_bound() {
 | 
			
		||||
        Bound::Included(end) => end + 1,
 | 
			
		||||
        Bound::Excluded(end) => *end,
 | 
			
		||||
        Bound::Unbounded => len,
 | 
			
		||||
    };
 | 
			
		||||
    if end > len {
 | 
			
		||||
        return None;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Some(start..end)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub(crate) fn relative_bounds_to_abs_range(
 | 
			
		||||
    bounds: impl RangeBounds<usize>,
 | 
			
		||||
    range: Range<usize>,
 | 
			
		||||
) -> Option<Range<usize>> {
 | 
			
		||||
    let relative = absolute_bounds_to_abs_range(bounds, range.len())?;
 | 
			
		||||
    let start = range.start + relative.start;
 | 
			
		||||
    let end = range.start + relative.end;
 | 
			
		||||
    if end > range.end {
 | 
			
		||||
        return None;
 | 
			
		||||
    }
 | 
			
		||||
    Some(start..end)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,7 +1,12 @@
 | 
			
		|||
use crate::{DataRef, Grid};
 | 
			
		||||
use inherent::inherent;
 | 
			
		||||
use std::fmt::Debug;
 | 
			
		||||
use std::slice::{Iter, IterMut};
 | 
			
		||||
use crate::{
 | 
			
		||||
    containers::absolute_bounds_to_abs_range, DataRef, Grid, GridMut, Window,
 | 
			
		||||
    WindowMut,
 | 
			
		||||
};
 | 
			
		||||
use std::{
 | 
			
		||||
    fmt::Debug,
 | 
			
		||||
    ops::RangeBounds,
 | 
			
		||||
    slice::{Iter, IterMut},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/// A type that can be stored in a [`ValueGrid`], e.g. [char], [u8].
 | 
			
		||||
pub trait Value: Sized + Default + Copy + Clone + Debug {}
 | 
			
		||||
| 
						 | 
				
			
			@ -139,14 +144,14 @@ impl<T: Value> ValueGrid<T> {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    /// Iterate over all rows in [`ValueGrid`] top to bottom.
 | 
			
		||||
    pub fn iter_rows(&self) -> IterGridRows<T> {
 | 
			
		||||
    pub fn iter_rows(&self) -> impl Iterator<Item = Iter<'_, T>> {
 | 
			
		||||
        IterGridRows { grid: self, row: 0 }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Returns an iterator that allows modifying each value.
 | 
			
		||||
    ///
 | 
			
		||||
    /// The iterator yields all cells from top left to bottom right.
 | 
			
		||||
    pub fn iter_mut(&mut self) -> IterMut<T> {
 | 
			
		||||
    pub fn iter_mut(&mut self) -> IterMut<'_, T> {
 | 
			
		||||
        self.data.iter_mut()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -218,88 +223,6 @@ impl<T: Value> ValueGrid<T> {
 | 
			
		|||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Copies a row from the grid.
 | 
			
		||||
    ///
 | 
			
		||||
    /// Returns [None] if y is out of bounds.
 | 
			
		||||
    #[must_use]
 | 
			
		||||
    pub fn get_row(&self, y: usize) -> Option<Vec<T>> {
 | 
			
		||||
        self.data
 | 
			
		||||
            .chunks_exact(self.width())
 | 
			
		||||
            .nth(y)
 | 
			
		||||
            .map(<[T]>::to_vec)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Copies a column from the grid.
 | 
			
		||||
    ///
 | 
			
		||||
    /// Returns [None] if x is out of bounds.
 | 
			
		||||
    #[must_use]
 | 
			
		||||
    pub fn get_col(&self, x: usize) -> Option<Vec<T>> {
 | 
			
		||||
        self.data
 | 
			
		||||
            .chunks_exact(self.width())
 | 
			
		||||
            .map(|row| row.get(x).copied())
 | 
			
		||||
            .collect()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Overwrites a column in the grid.
 | 
			
		||||
    ///
 | 
			
		||||
    /// Returns [Err] if x is out of bounds or `col` is not of the correct size.
 | 
			
		||||
    pub fn set_col(
 | 
			
		||||
        &mut self,
 | 
			
		||||
        x: usize,
 | 
			
		||||
        col: &[T],
 | 
			
		||||
    ) -> Result<(), SetValueSeriesError> {
 | 
			
		||||
        if col.len() != self.height() {
 | 
			
		||||
            return Err(SetValueSeriesError::InvalidLength {
 | 
			
		||||
                expected: self.height(),
 | 
			
		||||
                actual: col.len(),
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
        let width = self.width();
 | 
			
		||||
        if self
 | 
			
		||||
            .data
 | 
			
		||||
            .chunks_exact_mut(width)
 | 
			
		||||
            .zip(col.iter())
 | 
			
		||||
            .map(|(row, column_value)| {
 | 
			
		||||
                row.get_mut(x).map(move |cell| *cell = *column_value)
 | 
			
		||||
            })
 | 
			
		||||
            .all(|cell| cell.is_some())
 | 
			
		||||
        {
 | 
			
		||||
            Ok(())
 | 
			
		||||
        } else {
 | 
			
		||||
            Err(SetValueSeriesError::OutOfBounds {
 | 
			
		||||
                index: x,
 | 
			
		||||
                size: width,
 | 
			
		||||
            })
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Overwrites a row in the grid.
 | 
			
		||||
    ///
 | 
			
		||||
    /// Returns [Err] if y is out of bounds or `row` is not of the correct size.
 | 
			
		||||
    pub fn set_row(
 | 
			
		||||
        &mut self,
 | 
			
		||||
        y: usize,
 | 
			
		||||
        row: &[T],
 | 
			
		||||
    ) -> Result<(), SetValueSeriesError> {
 | 
			
		||||
        let width = self.width();
 | 
			
		||||
        if row.len() != width {
 | 
			
		||||
            return Err(SetValueSeriesError::InvalidLength {
 | 
			
		||||
                expected: width,
 | 
			
		||||
                actual: row.len(),
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let Some(chunk) = self.data.chunks_exact_mut(width).nth(y) else {
 | 
			
		||||
            return Err(SetValueSeriesError::OutOfBounds {
 | 
			
		||||
                size: self.height(),
 | 
			
		||||
                index: y,
 | 
			
		||||
            });
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        chunk.copy_from_slice(row);
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Enumerates all values in the grid.
 | 
			
		||||
    pub fn enumerate(
 | 
			
		||||
        &self,
 | 
			
		||||
| 
						 | 
				
			
			@ -310,6 +233,33 @@ impl<T: Value> ValueGrid<T> {
 | 
			
		|||
            row: 0,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[must_use]
 | 
			
		||||
    /// Creates a window into the grid.
 | 
			
		||||
    ///
 | 
			
		||||
    /// Returns None in case the window does not fit.
 | 
			
		||||
    pub fn window(
 | 
			
		||||
        &self,
 | 
			
		||||
        xs: impl RangeBounds<usize>,
 | 
			
		||||
        ys: impl RangeBounds<usize>,
 | 
			
		||||
    ) -> Option<Window<'_, T, Self>> {
 | 
			
		||||
        let xs = absolute_bounds_to_abs_range(xs, self.width)?;
 | 
			
		||||
        let ys = absolute_bounds_to_abs_range(ys, self.height)?;
 | 
			
		||||
        Window::new(self, xs, ys)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Creates a mutable window into the grid.
 | 
			
		||||
    ///
 | 
			
		||||
    /// Returns None in case the window does not fit.
 | 
			
		||||
    pub fn window_mut(
 | 
			
		||||
        &mut self,
 | 
			
		||||
        xs: impl RangeBounds<usize>,
 | 
			
		||||
        ys: impl RangeBounds<usize>,
 | 
			
		||||
    ) -> Option<WindowMut<'_, T, Self>> {
 | 
			
		||||
        let xs = absolute_bounds_to_abs_range(xs, self.width)?;
 | 
			
		||||
        let ys = absolute_bounds_to_abs_range(ys, self.height)?;
 | 
			
		||||
        WindowMut::new(self, xs, ys)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Errors that can occur when loading a grid
 | 
			
		||||
| 
						 | 
				
			
			@ -320,8 +270,25 @@ pub enum TryLoadValueGridError {
 | 
			
		|||
    InvalidDimensions,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[inherent]
 | 
			
		||||
impl<T: Value> Grid<T> for ValueGrid<T> {
 | 
			
		||||
    fn get_optional(&self, x: usize, y: usize) -> Option<T> {
 | 
			
		||||
        if self.is_in_bounds(x, y) {
 | 
			
		||||
            Some(self.data[x + y * self.width])
 | 
			
		||||
        } else {
 | 
			
		||||
            None
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn width(&self) -> usize {
 | 
			
		||||
        self.width
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn height(&self) -> usize {
 | 
			
		||||
        self.height
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<T: Value> GridMut<T> for ValueGrid<T> {
 | 
			
		||||
    /// Sets the value of the cell at the specified position in the grid.
 | 
			
		||||
    ///
 | 
			
		||||
    /// # Arguments
 | 
			
		||||
| 
						 | 
				
			
			@ -332,39 +299,18 @@ impl<T: Value> Grid<T> for ValueGrid<T> {
 | 
			
		|||
    /// # Panics
 | 
			
		||||
    ///
 | 
			
		||||
    /// When accessing `x` or `y` out of bounds.
 | 
			
		||||
    #[allow(unused, reason = "False positive because of #[inherent]")]
 | 
			
		||||
    fn set(&mut self, x: usize, y: usize, value: T) {
 | 
			
		||||
        self.assert_in_bounds(x, y);
 | 
			
		||||
        self.data[x + y * self.width] = value;
 | 
			
		||||
    fn set_optional(&mut self, x: usize, y: usize, value: T) -> bool {
 | 
			
		||||
        if self.is_in_bounds(x, y) {
 | 
			
		||||
            self.data[x + y * self.width] = value;
 | 
			
		||||
            true
 | 
			
		||||
        } else {
 | 
			
		||||
            false
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Gets the current value at the specified position.
 | 
			
		||||
    ///
 | 
			
		||||
    /// # Arguments
 | 
			
		||||
    ///
 | 
			
		||||
    /// - `x` and `y`: position of the cell to read
 | 
			
		||||
    ///
 | 
			
		||||
    /// # Panics
 | 
			
		||||
    ///
 | 
			
		||||
    /// When accessing `x` or `y` out of bounds.
 | 
			
		||||
    #[allow(unused, reason = "False positive because of #[inherent]")]
 | 
			
		||||
    fn get(&self, x: usize, y: usize) -> T {
 | 
			
		||||
        self.assert_in_bounds(x, y);
 | 
			
		||||
        self.data[x + y * self.width]
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[allow(unused, reason = "False positive because of #[inherent]")]
 | 
			
		||||
    fn fill(&mut self, value: T) {
 | 
			
		||||
        self.data.fill(value);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn width(&self) -> usize {
 | 
			
		||||
        self.width
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn height(&self) -> usize {
 | 
			
		||||
        self.height
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<T: Value> DataRef<T> for ValueGrid<T> {
 | 
			
		||||
| 
						 | 
				
			
			@ -393,9 +339,19 @@ impl<T: Value> From<&ValueGrid<T>> for Vec<T> {
 | 
			
		|||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<T: Value, G: Grid<T>> From<&G> for ValueGrid<T> {
 | 
			
		||||
    fn from(grid: &G) -> Self {
 | 
			
		||||
        let width = grid.width();
 | 
			
		||||
        let height = grid.height();
 | 
			
		||||
        let mut result = Self::new(width, height);
 | 
			
		||||
        result.deref_assign(grid);
 | 
			
		||||
        result
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// An iterator iver the rows in a [`ValueGrid`]
 | 
			
		||||
#[must_use]
 | 
			
		||||
pub struct IterGridRows<'t, T: Value> {
 | 
			
		||||
struct IterGridRows<'t, T: Value> {
 | 
			
		||||
    grid: &'t ValueGrid<T>,
 | 
			
		||||
    row: usize,
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -416,7 +372,7 @@ impl<'t, T: Value> Iterator for IterGridRows<'t, T> {
 | 
			
		|||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub struct EnumerateGrid<'t, T: Value> {
 | 
			
		||||
struct EnumerateGrid<'t, T: Value> {
 | 
			
		||||
    grid: &'t ValueGrid<T>,
 | 
			
		||||
    row: usize,
 | 
			
		||||
    column: usize,
 | 
			
		||||
| 
						 | 
				
			
			@ -443,7 +399,7 @@ impl<T: Value> Iterator for EnumerateGrid<'_, T> {
 | 
			
		|||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod tests {
 | 
			
		||||
    use crate::{SetValueSeriesError, ValueGrid, *};
 | 
			
		||||
    use crate::{DataRef, Grid, GridMut, ValueGrid};
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn fill() {
 | 
			
		||||
| 
						 | 
				
			
			@ -540,7 +496,7 @@ mod tests {
 | 
			
		|||
    #[should_panic]
 | 
			
		||||
    fn out_of_bounds_y() {
 | 
			
		||||
        let vec = ValueGrid::load(2, 2, &[0, 1, 2, 3]).unwrap();
 | 
			
		||||
        vec.get(1, 2);
 | 
			
		||||
        _ = vec.get(1, 2);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
| 
						 | 
				
			
			@ -560,59 +516,6 @@ mod tests {
 | 
			
		|||
        assert_eq!(Some(&mut 8), vec.get_ref_mut_optional(2, 2));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn optional() {
 | 
			
		||||
        let mut grid = ValueGrid::load(2, 2, &[0, 1, 2, 3]).unwrap();
 | 
			
		||||
        grid.set_optional(0, 0, 5);
 | 
			
		||||
        grid.set_optional(0, 8, 42);
 | 
			
		||||
        assert_eq!(grid.data, [5, 1, 2, 3]);
 | 
			
		||||
 | 
			
		||||
        assert_eq!(grid.get_optional(0, 0), Some(5));
 | 
			
		||||
        assert_eq!(grid.get_optional(0, 8), None);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn col() {
 | 
			
		||||
        let mut grid = ValueGrid::load(2, 3, &[0, 1, 2, 3, 4, 5]).unwrap();
 | 
			
		||||
        assert_eq!(grid.get_col(0), Some(vec![0, 2, 4]));
 | 
			
		||||
        assert_eq!(grid.get_col(1), Some(vec![1, 3, 5]));
 | 
			
		||||
        assert_eq!(grid.get_col(2), None);
 | 
			
		||||
        assert_eq!(grid.set_col(0, &[5, 7, 9]), Ok(()));
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            grid.set_col(2, &[5, 7, 9]),
 | 
			
		||||
            Err(SetValueSeriesError::OutOfBounds { size: 2, index: 2 })
 | 
			
		||||
        );
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            grid.set_col(0, &[5, 7]),
 | 
			
		||||
            Err(SetValueSeriesError::InvalidLength {
 | 
			
		||||
                expected: 3,
 | 
			
		||||
                actual: 2
 | 
			
		||||
            })
 | 
			
		||||
        );
 | 
			
		||||
        assert_eq!(grid.get_col(0), Some(vec![5, 7, 9]));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn row() {
 | 
			
		||||
        let mut grid = ValueGrid::load(2, 3, &[0, 1, 2, 3, 4, 5]).unwrap();
 | 
			
		||||
        assert_eq!(grid.get_row(0), Some(vec![0, 1]));
 | 
			
		||||
        assert_eq!(grid.get_row(2), Some(vec![4, 5]));
 | 
			
		||||
        assert_eq!(grid.get_row(3), None);
 | 
			
		||||
        assert_eq!(grid.set_row(0, &[5, 7]), Ok(()));
 | 
			
		||||
        assert_eq!(grid.get_row(0), Some(vec![5, 7]));
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            grid.set_row(3, &[5, 7]),
 | 
			
		||||
            Err(SetValueSeriesError::OutOfBounds { size: 3, index: 3 })
 | 
			
		||||
        );
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            grid.set_row(2, &[5, 7, 3]),
 | 
			
		||||
            Err(SetValueSeriesError::InvalidLength {
 | 
			
		||||
                expected: 2,
 | 
			
		||||
                actual: 3
 | 
			
		||||
            })
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn wrap() {
 | 
			
		||||
        let grid = ValueGrid::from_vec(2, vec![0, 1, 2, 3, 4, 5]).unwrap();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										404
									
								
								src/containers/window.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										404
									
								
								src/containers/window.rs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,404 @@
 | 
			
		|||
use crate::{
 | 
			
		||||
    containers::{absolute_bounds_to_abs_range, relative_bounds_to_abs_range},
 | 
			
		||||
    Grid, GridMut,
 | 
			
		||||
};
 | 
			
		||||
use std::{
 | 
			
		||||
    marker::PhantomData,
 | 
			
		||||
    ops::{Range, RangeBounds},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
macro_rules! define_window {
 | 
			
		||||
    ($name:ident, $grid:ty) => {
 | 
			
		||||
        /// A window into a 2D grid.
 | 
			
		||||
        ///
 | 
			
		||||
        /// All operations are done directly on the contained reference,
 | 
			
		||||
        /// but translated to where the window is.
 | 
			
		||||
        #[derive(Debug)]
 | 
			
		||||
        pub struct $name<'t, TElement: Copy, TGrid: Grid<TElement>> {
 | 
			
		||||
            grid: $grid,
 | 
			
		||||
            xs: Range<usize>,
 | 
			
		||||
            ys: Range<usize>,
 | 
			
		||||
            phantom: PhantomData<TElement>,
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        impl<'t, TElement: Copy, TGrid: Grid<TElement>>
 | 
			
		||||
            $name<'t, TElement, TGrid>
 | 
			
		||||
        {
 | 
			
		||||
            /// Create a new window into `grid`.
 | 
			
		||||
            #[must_use]
 | 
			
		||||
            pub fn new(
 | 
			
		||||
                grid: $grid,
 | 
			
		||||
                xs: impl RangeBounds<usize>,
 | 
			
		||||
                ys: impl RangeBounds<usize>,
 | 
			
		||||
            ) -> Option<Self> {
 | 
			
		||||
                let xs = absolute_bounds_to_abs_range(xs, grid.width())?;
 | 
			
		||||
                let ys = absolute_bounds_to_abs_range(ys, grid.height())?;
 | 
			
		||||
                Some(Self {
 | 
			
		||||
                    grid,
 | 
			
		||||
                    xs,
 | 
			
		||||
                    ys,
 | 
			
		||||
                    phantom: PhantomData::default(),
 | 
			
		||||
                })
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            #[must_use]
 | 
			
		||||
            /// Creates a window into the window.
 | 
			
		||||
            ///
 | 
			
		||||
            /// Returns None in case the window does not fit.
 | 
			
		||||
            pub fn window(
 | 
			
		||||
                &self,
 | 
			
		||||
                xs: impl RangeBounds<usize>,
 | 
			
		||||
                ys: impl RangeBounds<usize>,
 | 
			
		||||
            ) -> Option<Window<'_, TElement, TGrid>> {
 | 
			
		||||
                let xs = relative_bounds_to_abs_range(xs, self.xs.clone())?;
 | 
			
		||||
                let ys = relative_bounds_to_abs_range(ys, self.ys.clone())?;
 | 
			
		||||
                Window::new(self.grid, xs, ys)
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            /// Splits the window horizontally, returning windows to the left and right parts.
 | 
			
		||||
            ///
 | 
			
		||||
            /// The right window fills the remaining width, which may be zero.
 | 
			
		||||
            ///
 | 
			
		||||
            /// Returns None for out-of-bounds.
 | 
			
		||||
            #[must_use]
 | 
			
		||||
            pub fn split_horizontal(
 | 
			
		||||
                &'t self,
 | 
			
		||||
                left_width: usize,
 | 
			
		||||
            ) -> Option<(
 | 
			
		||||
                Window<'t, TElement, TGrid>,
 | 
			
		||||
                Window<'t, TElement, TGrid>,
 | 
			
		||||
            )> {
 | 
			
		||||
                assert!(left_width <= self.width());
 | 
			
		||||
                let middle_abs = self.xs.start + left_width;
 | 
			
		||||
                let left = Window::new(
 | 
			
		||||
                    self.grid,
 | 
			
		||||
                    self.xs.start..middle_abs,
 | 
			
		||||
                    self.ys.clone(),
 | 
			
		||||
                )?;
 | 
			
		||||
                let right = Window::new(
 | 
			
		||||
                    self.grid,
 | 
			
		||||
                    middle_abs..self.xs.end,
 | 
			
		||||
                    self.ys.clone(),
 | 
			
		||||
                )?;
 | 
			
		||||
                Some((left, right))
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            /// Splits the window vertically, returning windows to the top and bottom parts.
 | 
			
		||||
            ///
 | 
			
		||||
            /// The bottom window fills the remaining height, which may be zero.
 | 
			
		||||
            ///
 | 
			
		||||
            /// Returns None for out-of-bounds.
 | 
			
		||||
            #[must_use]
 | 
			
		||||
            pub fn split_vertical(
 | 
			
		||||
                &'t self,
 | 
			
		||||
                top_height: usize,
 | 
			
		||||
            ) -> Option<(
 | 
			
		||||
                Window<'t, TElement, TGrid>,
 | 
			
		||||
                Window<'t, TElement, TGrid>,
 | 
			
		||||
            )> {
 | 
			
		||||
                assert!(top_height <= self.height());
 | 
			
		||||
                let middle_abs = self.ys.start + top_height;
 | 
			
		||||
                let top = Window::new(
 | 
			
		||||
                    self.grid,
 | 
			
		||||
                    self.xs.clone(),
 | 
			
		||||
                    self.ys.start..middle_abs,
 | 
			
		||||
                )?;
 | 
			
		||||
                let bottom = Window::new(
 | 
			
		||||
                    self.grid,
 | 
			
		||||
                    self.xs.clone(),
 | 
			
		||||
                    middle_abs..self.ys.end,
 | 
			
		||||
                )?;
 | 
			
		||||
                Some((top, bottom))
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        impl<TElement: Copy, TGrid: Grid<TElement>> Grid<TElement>
 | 
			
		||||
            for $name<'_, TElement, TGrid>
 | 
			
		||||
        {
 | 
			
		||||
            fn get_optional(&self, x: usize, y: usize) -> Option<TElement> {
 | 
			
		||||
                if self.is_in_bounds(x, y) {
 | 
			
		||||
                    Some(self.grid.get(self.xs.start + x, self.ys.start + y))
 | 
			
		||||
                } else {
 | 
			
		||||
                    None
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            fn width(&self) -> usize {
 | 
			
		||||
                self.xs.len()
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            fn height(&self) -> usize {
 | 
			
		||||
                self.ys.len()
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
define_window!(Window, &'t TGrid);
 | 
			
		||||
define_window!(WindowMut, &'t mut TGrid);
 | 
			
		||||
 | 
			
		||||
impl<TElement: Copy, TGrid: GridMut<TElement>> GridMut<TElement>
 | 
			
		||||
    for WindowMut<'_, TElement, TGrid>
 | 
			
		||||
{
 | 
			
		||||
    fn set_optional(&mut self, x: usize, y: usize, value: TElement) -> bool {
 | 
			
		||||
        if self.is_in_bounds(x, y) {
 | 
			
		||||
            self.grid.set(self.xs.start + x, self.ys.start + y, value);
 | 
			
		||||
            true
 | 
			
		||||
        } else {
 | 
			
		||||
            false
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn fill(&mut self, value: TElement) {
 | 
			
		||||
        for y in self.ys.clone() {
 | 
			
		||||
            for x in self.xs.clone() {
 | 
			
		||||
                self.grid.set(x, y, value);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<TElement: Copy, TGrid: GridMut<TElement>> WindowMut<'_, TElement, TGrid> {
 | 
			
		||||
    /// Creates a mutable window into the grid.
 | 
			
		||||
    ///
 | 
			
		||||
    /// Returns None in case the window does not fit.
 | 
			
		||||
    pub fn window_mut(
 | 
			
		||||
        &mut self,
 | 
			
		||||
        xs: impl RangeBounds<usize>,
 | 
			
		||||
        ys: impl RangeBounds<usize>,
 | 
			
		||||
    ) -> Option<WindowMut<'_, TElement, TGrid>> {
 | 
			
		||||
        let xs = relative_bounds_to_abs_range(xs, self.xs.clone())?;
 | 
			
		||||
        let ys = relative_bounds_to_abs_range(ys, self.ys.clone())?;
 | 
			
		||||
        WindowMut::new(self.grid, xs, ys)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Splits the window horizontally, returning windows to the left and right parts.
 | 
			
		||||
    ///
 | 
			
		||||
    /// The right window fills the remaining width, which may be zero.
 | 
			
		||||
    ///
 | 
			
		||||
    /// Returns None for out-of-bounds.
 | 
			
		||||
    #[must_use]
 | 
			
		||||
    pub fn split_horizontal_mut<'t>(
 | 
			
		||||
        &'t mut self,
 | 
			
		||||
        left_width: usize,
 | 
			
		||||
    ) -> Option<(
 | 
			
		||||
        WindowMut<'t, TElement, TGrid>,
 | 
			
		||||
        WindowMut<'t, TElement, TGrid>,
 | 
			
		||||
    )> {
 | 
			
		||||
        assert!(left_width <= self.width());
 | 
			
		||||
        let (grid1, grid2): (&'t mut TGrid, &'t mut TGrid) =
 | 
			
		||||
            unsafe { Self::duplicate_mutable_ref(self.grid) };
 | 
			
		||||
        let middle_abs = self.xs.start + left_width;
 | 
			
		||||
        let left =
 | 
			
		||||
            WindowMut::new(grid1, self.xs.start..middle_abs, self.ys.clone())?;
 | 
			
		||||
        let right =
 | 
			
		||||
            WindowMut::new(grid2, middle_abs..self.xs.end, self.ys.clone())?;
 | 
			
		||||
        Some((left, right))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Splits the window vertically, returning windows to the top and bottom parts.
 | 
			
		||||
    ///
 | 
			
		||||
    /// The bottom window fills the remaining height, which may be zero.
 | 
			
		||||
    ///
 | 
			
		||||
    /// Returns None for out-of-bounds.
 | 
			
		||||
    #[must_use]
 | 
			
		||||
    pub fn split_vertical_mut<'t>(
 | 
			
		||||
        &'t mut self,
 | 
			
		||||
        top_height: usize,
 | 
			
		||||
    ) -> Option<(
 | 
			
		||||
        WindowMut<'t, TElement, TGrid>,
 | 
			
		||||
        WindowMut<'t, TElement, TGrid>,
 | 
			
		||||
    )> {
 | 
			
		||||
        assert!(top_height <= self.height());
 | 
			
		||||
        let (grid1, grid2): (&'t mut TGrid, &'t mut TGrid) =
 | 
			
		||||
            unsafe { Self::duplicate_mutable_ref(self.grid) };
 | 
			
		||||
        let middle_abs = self.ys.start + top_height;
 | 
			
		||||
        let top = WindowMut::<'t>::new(
 | 
			
		||||
            grid1,
 | 
			
		||||
            self.xs.clone(),
 | 
			
		||||
            self.ys.start..middle_abs,
 | 
			
		||||
        )?;
 | 
			
		||||
        let bottom = WindowMut::<'t>::new(
 | 
			
		||||
            grid2,
 | 
			
		||||
            self.xs.clone(),
 | 
			
		||||
            middle_abs..self.ys.end,
 | 
			
		||||
        )?;
 | 
			
		||||
        Some((top, bottom))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// SAFETY: the returned windows do not alias
 | 
			
		||||
    /// Does not work if the grid uses the same memory location for multiple cells internally.
 | 
			
		||||
    /// That means for e.g. a Bitmap, middle must be byte aligned or bit refs only used with the alias flag on.
 | 
			
		||||
    unsafe fn duplicate_mutable_ref<T>(it: &mut T) -> (&mut T, &mut T) {
 | 
			
		||||
        let mut ptr = std::ptr::NonNull::from(it);
 | 
			
		||||
        unsafe { (ptr.as_mut(), ptr.as_mut()) }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod tests {
 | 
			
		||||
    use super::{Window, WindowMut};
 | 
			
		||||
    use crate::{Bitmap, ByteGrid, CharGrid, DataRef, Grid, GridMut};
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn grid_view_bitmap() {
 | 
			
		||||
        let mut bitmap = Bitmap::new(8, 4).unwrap();
 | 
			
		||||
 | 
			
		||||
        // non-byte-aligned views work
 | 
			
		||||
        let mut view = bitmap.window_mut(3..7, 1..3).unwrap();
 | 
			
		||||
        view.fill(true);
 | 
			
		||||
 | 
			
		||||
        assert_eq!(bitmap.data_ref(), &[0, 30, 30, 0]);
 | 
			
		||||
 | 
			
		||||
        assert_eq!(bitmap.set_optional(99, 99, false), false);
 | 
			
		||||
 | 
			
		||||
        // full size view works
 | 
			
		||||
        bitmap.window(0..8, 0..4).unwrap();
 | 
			
		||||
 | 
			
		||||
        // zero size view works
 | 
			
		||||
        assert!(Window::new(&mut bitmap, 1..4, 2..2).is_some());
 | 
			
		||||
        assert!(WindowMut::new(&mut bitmap, 1..1, 2..3)
 | 
			
		||||
            .is_some_and(|w| w.get_optional(0, 0).is_none()));
 | 
			
		||||
 | 
			
		||||
        // oob does not work
 | 
			
		||||
        assert!(Window::new(&mut bitmap, 30..33, 43..44).is_none());
 | 
			
		||||
        assert!(WindowMut::new(&mut bitmap, 0..9, 0..1).is_none());
 | 
			
		||||
        assert!(Window::new(&mut bitmap, 0..1, 0..5).is_none());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn grid_view_char_grid() {
 | 
			
		||||
        let mut grid = CharGrid::new(3, 4);
 | 
			
		||||
        grid.fill(' ');
 | 
			
		||||
 | 
			
		||||
        let mut view = grid.window_mut(1..2, 1..4).unwrap();
 | 
			
		||||
        view.fill('#');
 | 
			
		||||
        view.set(0, 0, '!');
 | 
			
		||||
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            grid.data_ref(),
 | 
			
		||||
            &[' ', ' ', ' ', ' ', '!', ' ', ' ', '#', ' ', ' ', '#', ' ']
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        // full size view works
 | 
			
		||||
        _ = grid.window(0..3, 0..4).unwrap();
 | 
			
		||||
 | 
			
		||||
        // zero size view works
 | 
			
		||||
        assert!(grid
 | 
			
		||||
            .window(1..3, 2..2)
 | 
			
		||||
            .is_some_and(|w| w.get_optional(0, 0).is_none()));
 | 
			
		||||
        assert!(grid.window(1..1, 2..3).is_some());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn round_trip_bitmap() {
 | 
			
		||||
        let bitmap = Bitmap::new(8, 4).unwrap();
 | 
			
		||||
 | 
			
		||||
        let non_aligned = bitmap.window(3..7, 1..3).unwrap();
 | 
			
		||||
        assert_eq!(Bitmap::try_from(&non_aligned), Err(()));
 | 
			
		||||
 | 
			
		||||
        let aligned = bitmap.window(0..8, 1..3).unwrap();
 | 
			
		||||
        assert!(matches!(Bitmap::try_from(&aligned), Ok(_)));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn split_vertical() {
 | 
			
		||||
        let grid = ByteGrid::new(5, 4);
 | 
			
		||||
        let window = grid.window(0..grid.width(), 0..grid.height()).unwrap();
 | 
			
		||||
 | 
			
		||||
        let (left, right) = window.split_vertical(3).unwrap();
 | 
			
		||||
        assert_eq!(3, left.height());
 | 
			
		||||
        assert_eq!(1, right.height());
 | 
			
		||||
        assert_eq!(5, left.width());
 | 
			
		||||
        assert_eq!(5, right.width())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn split_horizontal() {
 | 
			
		||||
        let grid = ByteGrid::new(4, 5);
 | 
			
		||||
        let window = grid.window(.., ..).unwrap();
 | 
			
		||||
 | 
			
		||||
        let (top, bottom) = window.split_horizontal(3).unwrap();
 | 
			
		||||
        assert_eq!(3, top.width());
 | 
			
		||||
        assert_eq!(1, bottom.width());
 | 
			
		||||
        assert_eq!(5, top.height());
 | 
			
		||||
        assert_eq!(5, bottom.height())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn window_in_window() {
 | 
			
		||||
        let mut grid = ByteGrid::new(6, 7);
 | 
			
		||||
        grid.fill(1);
 | 
			
		||||
 | 
			
		||||
        let mut w1 = grid
 | 
			
		||||
            .window_mut(1..grid.width() - 1, 1..grid.height() - 1)
 | 
			
		||||
            .unwrap();
 | 
			
		||||
        w1.fill(2);
 | 
			
		||||
 | 
			
		||||
        let w1_1 = w1.window(.., ..).unwrap();
 | 
			
		||||
        assert_eq!(w1_1.get(0, 0), 2);
 | 
			
		||||
 | 
			
		||||
        assert!(matches!(w1.window(.., 0..=w1.height()), None));
 | 
			
		||||
 | 
			
		||||
        let mut w2 = w1
 | 
			
		||||
            .window_mut(1..w1.width() - 1, 1..w1.height() - 1)
 | 
			
		||||
            .unwrap();
 | 
			
		||||
        w2.fill(3);
 | 
			
		||||
 | 
			
		||||
        // zero-sized
 | 
			
		||||
        let mut w3 = w2
 | 
			
		||||
            .window_mut(1..w2.width() - 1, 1..w2.height() - 1)
 | 
			
		||||
            .unwrap();
 | 
			
		||||
        w3.fill(4);
 | 
			
		||||
 | 
			
		||||
        #[rustfmt::skip]
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            grid.data_ref(),
 | 
			
		||||
            &[
 | 
			
		||||
                1, 1, 1, 1, 1, 1,
 | 
			
		||||
                1, 2, 2, 2, 2, 1,
 | 
			
		||||
                1, 2, 3, 3, 2, 1,
 | 
			
		||||
                1, 2, 3, 3, 2, 1,
 | 
			
		||||
                1, 2, 3, 3, 2, 1,
 | 
			
		||||
                1, 2, 2, 2, 2, 1,
 | 
			
		||||
                1, 1, 1, 1, 1, 1
 | 
			
		||||
            ]
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn width_height() {
 | 
			
		||||
        let grid = ByteGrid::new(4, 4);
 | 
			
		||||
        let w1 = grid.window(0.., ..grid.height()).unwrap();
 | 
			
		||||
        assert_eq!(grid.width(), w1.width());
 | 
			
		||||
        assert_eq!(grid.height(), w1.height());
 | 
			
		||||
        let w2 = w1.window(.., 0..w1.height()).unwrap();
 | 
			
		||||
        assert_eq!(grid.width(), w2.width());
 | 
			
		||||
        assert_eq!(grid.height(), w2.height());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn split_mut() {
 | 
			
		||||
        let mut grid = ByteGrid::new(5, 5);
 | 
			
		||||
        grid.fill(1);
 | 
			
		||||
 | 
			
		||||
        let mut win = grid.window_mut(.., ..).unwrap();
 | 
			
		||||
        let (mut top, mut bottom) = win.split_vertical_mut(2).unwrap();
 | 
			
		||||
        let (mut left, mut right) = bottom.split_horizontal_mut(2).unwrap();
 | 
			
		||||
 | 
			
		||||
        top.fill(2);
 | 
			
		||||
        left.fill(3);
 | 
			
		||||
        right.fill(4);
 | 
			
		||||
 | 
			
		||||
        let grid2 = ByteGrid::from(&win.window(1..4, 1..4).unwrap());
 | 
			
		||||
 | 
			
		||||
        assert_eq!(grid2.data_ref(), &[2, 2, 2, 3, 4, 4, 3, 4, 4,]);
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            grid.data_ref(),
 | 
			
		||||
            &[
 | 
			
		||||
                2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 4, 4, 4, 3, 3, 4, 4, 4, 3,
 | 
			
		||||
                3, 4, 4, 4,
 | 
			
		||||
            ]
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue