windows are range based

This commit is contained in:
Vinzenz Schroeter 2025-07-07 23:39:54 +02:00
parent ae0f10b48c
commit 487dd03713
4 changed files with 95 additions and 51 deletions

View file

@ -1,10 +1,10 @@
use crate::{
DataRef, DisplayBitVec, Grid, GridMut, Payload, ValueGrid, Window,
WindowMut, 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::{Index, Range};
use std::ops::RangeBounds;
/// A fixed-size 2D grid of booleans.
///
@ -184,9 +184,11 @@ impl Bitmap {
#[must_use]
pub fn window(
&self,
xs: Range<usize>,
ys: Range<usize>,
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)
}
@ -195,9 +197,11 @@ impl Bitmap {
/// Returns None in case the window does not fit.
pub fn window_mut(
&mut self,
xs: Range<usize>,
ys: Range<usize>,
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)
}
}

View file

@ -22,3 +22,43 @@ pub use value_grid::{
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)
}

View file

@ -1,8 +1,11 @@
use crate::{DataRef, Grid, GridMut, Window, WindowMut};
use crate::{
containers::absolute_bounds_to_abs_range, DataRef, Grid, GridMut, Window,
WindowMut,
};
use inherent::inherent;
use std::{
fmt::Debug,
ops::Range,
ops::RangeBounds,
slice::{Iter, IterMut},
};
@ -320,9 +323,11 @@ impl<T: Value> ValueGrid<T> {
/// Returns None in case the window does not fit.
pub fn window(
&self,
xs: Range<usize>,
ys: Range<usize>,
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)
}
@ -331,9 +336,11 @@ impl<T: Value> ValueGrid<T> {
/// Returns None in case the window does not fit.
pub fn window_mut(
&mut self,
xs: Range<usize>,
ys: Range<usize>,
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)
}
}

View file

@ -1,8 +1,15 @@
use crate::{
containers::char_grid::{CharGridExt, CharGridMutExt},
containers::{
absolute_bounds_to_abs_range,
char_grid::{CharGridExt, CharGridMutExt},
relative_bounds_to_abs_range,
},
Grid, GridMut,
};
use std::{marker::PhantomData, ops::Range};
use std::{
marker::PhantomData,
ops::{Range, RangeBounds},
};
macro_rules! define_window {
($name:ident, $grid:ty) => {
@ -26,13 +33,11 @@ macro_rules! define_window {
#[allow(unused, reason = "False positive because of #[inherent]")]
pub fn new(
grid: $grid,
xs: Range<usize>,
ys: Range<usize>,
xs: impl RangeBounds<usize>,
ys: impl RangeBounds<usize>,
) -> Option<Self> {
if !grid.is_in_bounds(xs.end - 1, ys.end - 1) {
return None;
}
let xs = absolute_bounds_to_abs_range(xs, grid.width())?;
let ys = absolute_bounds_to_abs_range(ys, grid.height())?;
Some(Self {
grid,
xs,
@ -47,18 +52,12 @@ macro_rules! define_window {
/// Returns None in case the window does not fit.
pub fn window(
&self,
xs: Range<usize>,
ys: Range<usize>,
xs: impl RangeBounds<usize>,
ys: impl RangeBounds<usize>,
) -> Option<Window<TElement, TGrid>> {
if !self.is_in_bounds(xs.end - 1, ys.end - 1) {
return None;
}
Window::new(
self.grid,
// TODO off by one
(self.xs.start + xs.start)..(self.xs.end + xs.end),
(self.ys.start + ys.start)..(self.ys.end + ys.end),
)
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)
}
#[must_use]
@ -159,18 +158,12 @@ impl<TElement: Copy, TGrid: GridMut<TElement>> WindowMut<'_, TElement, TGrid> {
/// Returns None in case the window does not fit.
pub fn window_mut(
&mut self,
xs: Range<usize>,
ys: Range<usize>,
xs: impl RangeBounds<usize>,
ys: impl RangeBounds<usize>,
) -> Option<WindowMut<TElement, TGrid>> {
if self.is_in_bounds(xs.end - 1, ys.end - 1) {
return None;
}
WindowMut::new(
self.grid,
(self.xs.start + xs.start)..(self.xs.end + xs.end),
(self.ys.start + ys.start)..(self.ys.end + ys.end),
)
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)
}
pub fn deref_assign<O: Grid<TElement>>(&mut self, other: &O) {
@ -296,7 +289,7 @@ mod tests {
#[test]
fn split_horizontal() {
let grid = ByteGrid::new(4, 5);
let window = grid.window(0..grid.width(), 0..grid.height()).unwrap();
let window = grid.window(.., ..).unwrap();
let (top, bottom) = window.split_horizontal(3).unwrap();
assert_eq!(3, top.width());
@ -311,23 +304,23 @@ mod tests {
grid.fill(1);
let mut w1 = grid
.window_mut(1..grid.width() - 2, 1..grid.height() - 2)
.window_mut(1..grid.width() - 1, 1..grid.height() - 1)
.unwrap();
w1.fill(2);
let w1_1 = w1.window(0..w1.width(), 0..w1.height()).unwrap();
let w1_1 = w1.window(.., ..).unwrap();
assert_eq!(w1_1.get(0, 0), 2);
assert!(matches!(w1.window(0..w1.width(), 1..w1.height()), None));
assert!(matches!(w1.window(.., 0..=w1.height()), None));
let mut w2 = w1
.window_mut(1..w1.width() - 2, 1..w1.height() - 2)
.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() - 2, 1..w2.height() - 2)
.window_mut(1..w2.width() - 1, 1..w2.height() - 1)
.unwrap();
w3.fill(4);
@ -349,10 +342,10 @@ mod tests {
#[test]
fn width_height() {
let grid = ByteGrid::new(4, 4);
let w1 = grid.window(0..grid.width(), 0..grid.height()).unwrap();
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.width(), 0..w1.height()).unwrap();
let w2 = w1.window(.., 0..w1.height()).unwrap();
assert_eq!(grid.width(), w2.width());
assert_eq!(grid.height(), w2.height());
}