add Window
This commit is contained in:
parent
c9ad4e3357
commit
260ae6a1a0
14 changed files with 277 additions and 224 deletions
|
|
@ -3,7 +3,7 @@
|
|||
use clap::Parser;
|
||||
use servicepoint::{
|
||||
Bitmap, BitmapCommand, Brightness, BrightnessGrid, BrightnessGridCommand,
|
||||
DataRef, Grid, UdpSocketExt, TILE_HEIGHT, TILE_WIDTH,
|
||||
DataRef, UdpSocketExt, TILE_HEIGHT, TILE_WIDTH,
|
||||
};
|
||||
use std::net::UdpSocket;
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
use clap::Parser;
|
||||
use servicepoint::{
|
||||
Bitmap, BitmapCommand, CompressionCode, Grid, Origin, Packet, UdpSocketExt,
|
||||
Bitmap, BitmapCommand, CompressionCode, Origin, Packet, UdpSocketExt,
|
||||
FRAME_PACING, PIXEL_HEIGHT, PIXEL_WIDTH,
|
||||
};
|
||||
use std::{net::UdpSocket, thread, time::Duration};
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
use clap::Parser;
|
||||
use servicepoint::{
|
||||
Bitmap, BitmapCommand, CompressionCode, Grid, UdpSocketExt, FRAME_PACING,
|
||||
Bitmap, BitmapCommand, CompressionCode, UdpSocketExt, FRAME_PACING,
|
||||
PIXEL_HEIGHT, PIXEL_WIDTH,
|
||||
};
|
||||
use std::{net::UdpSocket, thread};
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
use clap::Parser;
|
||||
use servicepoint::{
|
||||
Bitmap, BitmapCommand, Grid, UdpSocketExt, FRAME_PACING, PIXEL_HEIGHT,
|
||||
Bitmap, BitmapCommand, UdpSocketExt, FRAME_PACING, PIXEL_HEIGHT,
|
||||
PIXEL_WIDTH,
|
||||
};
|
||||
use std::{net::UdpSocket, thread, time::Duration};
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use crate::{
|
|||
command_code::{CommandCode, InvalidCommandCodeError},
|
||||
commands::errors::{TryFromPacketError, TryIntoPacketError},
|
||||
compression::{compress, decompress, CompressionError},
|
||||
Bitmap, CompressionCode, DataRef, Grid, Header, Origin, Packet, Pixels,
|
||||
Bitmap, CompressionCode, DataRef, Header, Origin, Packet, Pixels,
|
||||
TypedCommand, TILE_SIZE,
|
||||
};
|
||||
|
||||
|
|
@ -304,7 +304,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(),
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use crate::{
|
||||
DataRef, DisplayBitVec, Grid, Payload, ValueGrid, WindowMut, PIXEL_HEIGHT,
|
||||
PIXEL_WIDTH,
|
||||
DataRef, DisplayBitVec, Grid, GridMut, Payload, ValueGrid, Window,
|
||||
PIXEL_HEIGHT, PIXEL_WIDTH,
|
||||
};
|
||||
use ::bitvec::{order::Msb0, prelude::BitSlice, slice::IterMut};
|
||||
use inherent::inherent;
|
||||
|
|
@ -180,6 +180,26 @@ impl Bitmap {
|
|||
|
||||
#[inherent]
|
||||
impl Grid<bool> for Bitmap {
|
||||
#[must_use]
|
||||
#[allow(unused, reason = "False positive because of #[inherent]")]
|
||||
pub fn get(&self, x: usize, y: usize) -> bool {
|
||||
self.assert_in_bounds(x, y);
|
||||
self.bit_vec[x + y * self.width]
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn width(&self) -> usize {
|
||||
self.width
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn height(&self) -> usize {
|
||||
self.height
|
||||
}
|
||||
}
|
||||
|
||||
#[inherent]
|
||||
impl GridMut<bool> for Bitmap {
|
||||
/// Sets the value of the specified position in the [Bitmap].
|
||||
///
|
||||
/// # Arguments
|
||||
|
|
@ -193,17 +213,11 @@ impl Grid<bool> for Bitmap {
|
|||
///
|
||||
/// 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) {
|
||||
pub 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]
|
||||
}
|
||||
|
||||
/// Sets the state of all pixels in the [Bitmap].
|
||||
///
|
||||
/// # Arguments
|
||||
|
|
@ -211,17 +225,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) {
|
||||
pub 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 {
|
||||
|
|
@ -263,10 +269,10 @@ impl TryFrom<&ValueGrid<bool>> for Bitmap {
|
|||
}
|
||||
}
|
||||
|
||||
impl<G: Grid<bool>> TryFrom<&WindowMut<'_, bool, G>> for Bitmap {
|
||||
impl<G: Grid<bool>> TryFrom<&Window<'_, bool, G>> for Bitmap {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: &WindowMut<'_, bool, G>) -> Result<Self, Self::Error> {
|
||||
fn try_from(value: &Window<'_, bool, G>) -> Result<Self, Self::Error> {
|
||||
let mut result = Self::new(value.width(), value.height()).ok_or(())?;
|
||||
for y in 0..value.height() {
|
||||
for x in 0..value.width() {
|
||||
|
|
@ -380,7 +386,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]
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use crate::{Brightness, ByteGrid, Grid, ValueGrid};
|
||||
use crate::{Brightness, ByteGrid, ValueGrid};
|
||||
|
||||
/// A grid containing brightness values.
|
||||
///
|
||||
|
|
@ -93,7 +93,7 @@ impl TryFrom<ByteGrid> for BrightnessGrid {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{Brightness, BrightnessGrid, DataRef, Grid, ValueGrid};
|
||||
use crate::{Brightness, BrightnessGrid, DataRef, ValueGrid};
|
||||
|
||||
#[test]
|
||||
fn to_u8_grid() {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use crate::{Grid, SetValueSeriesError, TryLoadValueGridError, ValueGrid};
|
||||
use crate::{SetValueSeriesError, TryLoadValueGridError, ValueGrid};
|
||||
use std::string::FromUtf8Error;
|
||||
|
||||
/// A grid containing UTF-8 characters.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use crate::{Grid, ValueGrid};
|
||||
use crate::ValueGrid;
|
||||
|
||||
/// A grid containing codepage 437 characters.
|
||||
///
|
||||
|
|
|
|||
|
|
@ -1,16 +1,5 @@
|
|||
/// A two-dimensional grid of `T`
|
||||
/// A two-dimensional readonly 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);
|
||||
|
||||
/// Get the current value at the specified position
|
||||
///
|
||||
/// # Arguments
|
||||
|
|
@ -20,6 +9,7 @@ pub trait Grid<T> {
|
|||
/// # Panics
|
||||
///
|
||||
/// When accessing `x` or `y` out of bounds.
|
||||
#[must_use]
|
||||
fn get(&self, x: usize, y: usize) -> T;
|
||||
|
||||
/// Get the current value at the specified position if the position is inside of bounds
|
||||
|
|
@ -29,6 +19,7 @@ pub trait Grid<T> {
|
|||
/// - `x` and `y`: position of the cell to read
|
||||
///
|
||||
/// returns: Value at position or None
|
||||
#[must_use]
|
||||
fn get_optional(&self, x: usize, y: usize) -> Option<T> {
|
||||
if self.is_in_bounds(x, y) {
|
||||
Some(self.get(x, y))
|
||||
|
|
@ -37,32 +28,16 @@ pub trait Grid<T> {
|
|||
}
|
||||
}
|
||||
|
||||
/// 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);
|
||||
|
||||
/// 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()
|
||||
}
|
||||
|
|
@ -79,3 +54,37 @@ pub trait Grid<T> {
|
|||
assert!(y < height, "cannot access index [{x}, {y}] because y is outside of bounds [0..{height})");
|
||||
}
|
||||
}
|
||||
|
||||
/// A two-dimensional mutable grid of `T`
|
||||
pub trait GridMut<T>: 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);
|
||||
|
||||
/// 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 {
|
||||
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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,128 +0,0 @@
|
|||
use crate::Grid;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct WindowMut<'t, TElement: Copy, TGrid: Grid<TElement>> {
|
||||
grid: &'t mut TGrid,
|
||||
x: usize,
|
||||
y: usize,
|
||||
width: usize,
|
||||
height: usize,
|
||||
phantom: PhantomData<TElement>,
|
||||
}
|
||||
|
||||
impl<'t, TElement: Copy, TGrid: Grid<TElement>> WindowMut<'t, TElement, TGrid> {
|
||||
#[must_use]
|
||||
pub fn new(
|
||||
grid: &'t mut TGrid,
|
||||
x: usize,
|
||||
y: usize,
|
||||
width: usize,
|
||||
height: usize,
|
||||
) -> Option<Self> {
|
||||
if width == 0 || height == 0 {
|
||||
return None;
|
||||
}
|
||||
if !grid.is_in_bounds(x + width - 1, y + height - 1) {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(Self {
|
||||
grid,
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height,
|
||||
phantom: PhantomData,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<TElement: Copy, TGrid: Grid<TElement>> Grid<TElement>
|
||||
for WindowMut<'_, TElement, TGrid>
|
||||
{
|
||||
fn set(&mut self, x: usize, y: usize, value: TElement) {
|
||||
self.grid.set(x + self.x, y + self.y, value)
|
||||
}
|
||||
fn get(&self, x: usize, y: usize) -> TElement {
|
||||
self.grid.get(x + self.x, y + self.y)
|
||||
}
|
||||
|
||||
fn fill(&mut self, value: TElement) {
|
||||
for y in self.y..(self.y + self.height) {
|
||||
for x in self.x..(self.x + self.width) {
|
||||
self.grid.set(x, y, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn width(&self) -> usize {
|
||||
self.width
|
||||
}
|
||||
|
||||
fn height(&self) -> usize {
|
||||
self.height
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::containers::grid_view::WindowMut;
|
||||
use crate::{Bitmap, CharGrid, DataRef, Grid};
|
||||
|
||||
#[test]
|
||||
fn test_grid_view_bitmap() {
|
||||
let mut bitmap = Bitmap::new(8, 4).unwrap();
|
||||
|
||||
// non-byte-aligned views work
|
||||
let mut view = WindowMut::new(&mut bitmap, 3, 1, 4, 2).unwrap();
|
||||
view.fill(true);
|
||||
|
||||
assert_eq!(bitmap.data_ref(), &[0, 30, 30, 0]);
|
||||
|
||||
// full size view works
|
||||
_ = WindowMut::new(&mut bitmap, 0, 0, 8, 4).unwrap();
|
||||
|
||||
// zero size view does not work
|
||||
assert!(WindowMut::new(&mut bitmap, 1, 2, 3, 0).is_none());
|
||||
assert!(WindowMut::new(&mut bitmap, 1, 2, 0, 1).is_none());
|
||||
|
||||
// oob does not work
|
||||
assert!(WindowMut::new(&mut bitmap, 30, 43, 3, 1).is_none());
|
||||
assert!(WindowMut::new(&mut bitmap, 0, 0, 9, 1).is_none());
|
||||
assert!(WindowMut::new(&mut bitmap, 0, 0, 1, 5).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_grid_view_char_grid() {
|
||||
let mut grid = CharGrid::new(3, 4);
|
||||
grid.fill(' ');
|
||||
|
||||
let mut view = WindowMut::new(&mut grid, 1, 1, 1, 2).unwrap();
|
||||
view.fill('#');
|
||||
|
||||
assert_eq!(
|
||||
grid.data_ref(),
|
||||
&[' ', ' ', ' ', ' ', '#', ' ', ' ', '#', ' ', ' ', ' ', ' ']
|
||||
);
|
||||
|
||||
// full size view works
|
||||
_ = WindowMut::new(&mut grid, 0, 0, 3, 4).unwrap();
|
||||
|
||||
// zero size view does not work
|
||||
assert!(WindowMut::new(&mut grid, 1, 2, 2, 0).is_none());
|
||||
assert!(WindowMut::new(&mut grid, 1, 2, 0, 1).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn round_trip_bitmap() {
|
||||
let mut bitmap = Bitmap::new(8, 4).unwrap();
|
||||
|
||||
let non_aligned = WindowMut::new(&mut bitmap, 3, 1, 4, 2).unwrap();
|
||||
assert_eq!(Bitmap::try_from(&non_aligned), Err(()));
|
||||
|
||||
let aligned = WindowMut::new(&mut bitmap, 0, 1, 8, 2).unwrap();
|
||||
|
||||
assert!(matches!(Bitmap::try_from(&aligned), Ok(_)));
|
||||
}
|
||||
}
|
||||
|
|
@ -6,8 +6,8 @@ mod char_grid;
|
|||
mod cp437_grid;
|
||||
mod data_ref;
|
||||
mod grid;
|
||||
mod grid_view;
|
||||
mod value_grid;
|
||||
mod window;
|
||||
|
||||
pub use bit_vec::{bitvec, DisplayBitVec};
|
||||
pub use bitmap::*;
|
||||
|
|
@ -16,8 +16,8 @@ pub use byte_grid::ByteGrid;
|
|||
pub use char_grid::CharGrid;
|
||||
pub use cp437_grid::Cp437Grid;
|
||||
pub use data_ref::DataRef;
|
||||
pub use grid::Grid;
|
||||
pub use grid_view::WindowMut;
|
||||
pub use grid::{Grid, GridMut};
|
||||
pub use value_grid::{
|
||||
IterGridRows, SetValueSeriesError, TryLoadValueGridError, Value, ValueGrid,
|
||||
};
|
||||
pub use window::{Window, WindowMut};
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use crate::{DataRef, Grid, WindowMut};
|
||||
use crate::{DataRef, Grid, GridMut, Window};
|
||||
use inherent::inherent;
|
||||
use std::fmt::Debug;
|
||||
use std::slice::{Iter, IterMut};
|
||||
|
|
@ -322,6 +322,35 @@ pub enum TryLoadValueGridError {
|
|||
|
||||
#[inherent]
|
||||
impl<T: Value> Grid<T> for ValueGrid<T> {
|
||||
/// 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.
|
||||
#[must_use]
|
||||
#[allow(unused, reason = "False positive because of #[inherent]")]
|
||||
pub fn get(&self, x: usize, y: usize) -> T {
|
||||
self.assert_in_bounds(x, y);
|
||||
self.data[x + y * self.width]
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn width(&self) -> usize {
|
||||
self.width
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn height(&self) -> usize {
|
||||
self.height
|
||||
}
|
||||
}
|
||||
|
||||
#[inherent]
|
||||
impl<T: Value> GridMut<T> for ValueGrid<T> {
|
||||
/// Sets the value of the cell at the specified position in the grid.
|
||||
///
|
||||
/// # Arguments
|
||||
|
|
@ -333,38 +362,15 @@ impl<T: Value> Grid<T> for ValueGrid<T> {
|
|||
///
|
||||
/// 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) {
|
||||
pub fn set(&mut self, x: usize, y: usize, value: T) {
|
||||
self.assert_in_bounds(x, y);
|
||||
self.data[x + y * self.width] = value;
|
||||
}
|
||||
|
||||
/// 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) {
|
||||
pub 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> {
|
||||
|
|
@ -441,8 +447,8 @@ impl<T: Value> Iterator for EnumerateGrid<'_, T> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<E: Value> From<&WindowMut<'_, E, Self>> for ValueGrid<E> {
|
||||
fn from(value: &WindowMut<'_, E, Self>) -> Self {
|
||||
impl<E: Value> From<&Window<'_, E, Self>> for ValueGrid<E> {
|
||||
fn from(value: &Window<'_, E, Self>) -> Self {
|
||||
let mut result = Self::new(value.width(), value.height());
|
||||
for y in 0..value.height() {
|
||||
for x in 0..value.width() {
|
||||
|
|
@ -552,7 +558,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]
|
||||
|
|
@ -575,8 +581,8 @@ mod tests {
|
|||
#[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!(grid.set_optional(0, 0, 5));
|
||||
assert!(!grid.set_optional(0, 8, 42));
|
||||
assert_eq!(grid.data, [5, 1, 2, 3]);
|
||||
|
||||
assert_eq!(grid.get_optional(0, 0), Some(5));
|
||||
|
|
|
|||
160
src/containers/window.rs
Normal file
160
src/containers/window.rs
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
use crate::{Grid, GridMut};
|
||||
use std::marker::PhantomData;
|
||||
|
||||
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,
|
||||
x: usize,
|
||||
y: usize,
|
||||
width: usize,
|
||||
height: usize,
|
||||
phantom: PhantomData<TElement>,
|
||||
}
|
||||
|
||||
impl<'t, TElement: Copy, TGrid: Grid<TElement>>
|
||||
$name<'t, TElement, TGrid>
|
||||
{
|
||||
/// Create a new window into `grid`.
|
||||
#[must_use]
|
||||
#[allow(unused, reason = "False positive because of #[inherent]")]
|
||||
pub fn new(
|
||||
grid: $grid,
|
||||
x: usize,
|
||||
y: usize,
|
||||
width: usize,
|
||||
height: usize,
|
||||
) -> Option<Self> {
|
||||
if width == 0 || height == 0 {
|
||||
return None;
|
||||
}
|
||||
if !grid.is_in_bounds(x + width - 1, y + height - 1) {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(Self {
|
||||
grid,
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height,
|
||||
phantom: PhantomData,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[inherent::inherent]
|
||||
impl<TElement: Copy, TGrid: Grid<TElement>> Grid<TElement>
|
||||
for $name<'_, TElement, TGrid>
|
||||
{
|
||||
#[must_use]
|
||||
#[allow(unused, reason = "False positive because of #[inherent]")]
|
||||
pub fn get(&self, x: usize, y: usize) -> TElement {
|
||||
self.grid.get(x + self.x, y + self.y)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
#[allow(unused, reason = "False positive because of #[inherent]")]
|
||||
pub fn width(&self) -> usize {
|
||||
self.width
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
#[allow(unused, reason = "False positive because of #[inherent]")]
|
||||
pub fn height(&self) -> usize {
|
||||
self.height
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
define_window!(Window, &'t TGrid);
|
||||
define_window!(WindowMut, &'t mut TGrid);
|
||||
|
||||
#[inherent::inherent]
|
||||
impl<TElement: Copy, TGrid: GridMut<TElement>> GridMut<TElement>
|
||||
for WindowMut<'_, TElement, TGrid>
|
||||
{
|
||||
#[allow(unused, reason = "False positive because of #[inherent]")]
|
||||
pub fn set(&mut self, x: usize, y: usize, value: TElement) {
|
||||
self.grid.set(x + self.x, y + self.y, value);
|
||||
}
|
||||
|
||||
#[allow(unused, reason = "False positive because of #[inherent]")]
|
||||
pub fn fill(&mut self, value: TElement) {
|
||||
for y in self.y..(self.y + self.height) {
|
||||
for x in self.x..(self.x + self.width) {
|
||||
self.grid.set(x, y, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::containers::window::{Window, WindowMut};
|
||||
use crate::{Bitmap, CharGrid, DataRef, GridMut};
|
||||
|
||||
#[test]
|
||||
fn test_grid_view_bitmap() {
|
||||
let mut bitmap = Bitmap::new(8, 4).unwrap();
|
||||
|
||||
// non-byte-aligned views work
|
||||
let mut view = WindowMut::new(&mut bitmap, 3, 1, 4, 2).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
|
||||
_ = Window::new(&mut bitmap, 0, 0, 8, 4).unwrap();
|
||||
|
||||
// zero size view does not work
|
||||
assert!(Window::new(&mut bitmap, 1, 2, 3, 0).is_none());
|
||||
assert!(WindowMut::new(&mut bitmap, 1, 2, 0, 1).is_none());
|
||||
|
||||
// oob does not work
|
||||
assert!(Window::new(&mut bitmap, 30, 43, 3, 1).is_none());
|
||||
assert!(WindowMut::new(&mut bitmap, 0, 0, 9, 1).is_none());
|
||||
assert!(Window::new(&mut bitmap, 0, 0, 1, 5).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_grid_view_char_grid() {
|
||||
let mut grid = CharGrid::new(3, 4);
|
||||
grid.fill(' ');
|
||||
|
||||
let mut view = WindowMut::new(&mut grid, 1, 1, 1, 2).unwrap();
|
||||
view.fill('#');
|
||||
|
||||
assert_eq!(
|
||||
grid.data_ref(),
|
||||
&[' ', ' ', ' ', ' ', '#', ' ', ' ', '#', ' ', ' ', ' ', ' ']
|
||||
);
|
||||
|
||||
// full size view works
|
||||
_ = Window::new(&mut grid, 0, 0, 3, 4).unwrap();
|
||||
|
||||
// zero size view does not work
|
||||
assert!(Window::new(&mut grid, 1, 2, 2, 0).is_none());
|
||||
assert!(Window::new(&mut grid, 1, 2, 0, 1).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn round_trip_bitmap() {
|
||||
let mut bitmap = Bitmap::new(8, 4).unwrap();
|
||||
|
||||
let non_aligned = Window::new(&mut bitmap, 3, 1, 4, 2).unwrap();
|
||||
assert_eq!(Bitmap::try_from(&non_aligned), Err(()));
|
||||
|
||||
let aligned = Window::new(&mut bitmap, 0, 1, 8, 2).unwrap();
|
||||
|
||||
assert!(matches!(Bitmap::try_from(&aligned), Ok(_)));
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue