feat!: add an impl of DoubleEndedIterator for Columns and Rows (#1358)

BREAKING-CHANGE: The `pub` modifier has been removed from fields on the
`layout::rect::Columns` and `layout::rect::Rows` iterators. These fields
were not intended to be public and should not have been accessed
directly.

Fixes: #1357
This commit is contained in:
FujiApple 2024-10-15 05:39:08 +07:00 committed by Josh McKinney
parent 3df685e114
commit 7bdccce3d5
No known key found for this signature in database
GPG key ID: 722287396A903BC5
2 changed files with 138 additions and 21 deletions

View file

@ -10,12 +10,13 @@ GitHub with a [breaking change] label.
This is a quick summary of the sections below:
- [v0.29.0](#v0290)
- [v0.29.0](#unreleased)
- Removed public fields from `Rect` iterators
- `Line` now implements `From<Cow<str>`
- `Table::highlight_style` is now `Table::row_highlight_style`
- `Tabs::select` now accepts `Into<Option<usize>>`
- [v0.28.0](#v0280)
`Backend::size` returns `Size` instead of `Rect`
- `Backend::size` returns `Size` instead of `Rect`
- `Backend` trait migrates to `get/set_cursor_position`
- Ratatui now requires Crossterm 0.28.0
- `Axis::labels` now accepts `IntoIterator<Into<Line>>`
@ -69,7 +70,15 @@ This is a quick summary of the sections below:
- MSRV is now 1.63.0
- `List` no longer ignores empty strings
## v0.29.0 (Unreleased)
## Unreleased
### Removed public fields from `Rect` iterators ([#1358])
[#1358]: https://github.com/ratatui/ratatui/pull/1358
The `pub` modifier has been removed from fields on the `layout::rect::Columns` and
`layout::rect::Rows`. These fields were not intended to be public and should not have been accessed
directly.
### `Rect::area()` now returns u32 instead of u16 ([#1378])
@ -117,8 +126,9 @@ The `Table::highlight_style` is now deprecated in favor of `Table::row_highlight
Also, the serialized output of the `TableState` will now include the "selected_column" field.
Software that manually parse the serialized the output (with anything other than the `Serialize`
implementation on `TableState`) may have to be refactored if the "selected_column" field is not accounted for.
This does not affect users who rely on the `Deserialize`, or `Serialize` implementation on the state.
implementation on `TableState`) may have to be refactored if the "selected_column" field is not
accounted for. This does not affect users who rely on the `Deserialize`, or `Serialize`
implementation on the state.
## v0.28.0

View file

@ -3,9 +3,11 @@ use crate::layout::{Position, Rect};
/// An iterator over rows within a `Rect`.
pub struct Rows {
/// The `Rect` associated with the rows.
pub rect: Rect,
/// The y coordinate of the row within the `Rect`.
pub current_row: u16,
rect: Rect,
/// The y coordinate of the row within the `Rect` when iterating forwards.
current_row_fwd: u16,
/// The y coordinate of the row within the `Rect` when iterating backwards.
current_row_back: u16,
}
impl Rows {
@ -13,7 +15,8 @@ impl Rows {
pub const fn new(rect: Rect) -> Self {
Self {
rect,
current_row: rect.y,
current_row_fwd: rect.y,
current_row_back: rect.bottom(),
}
}
}
@ -25,11 +28,25 @@ impl Iterator for Rows {
///
/// Returns `None` when there are no more rows to iterate through.
fn next(&mut self) -> Option<Self::Item> {
if self.current_row >= self.rect.bottom() {
if self.current_row_fwd >= self.current_row_back {
return None;
}
let row = Rect::new(self.rect.x, self.current_row, self.rect.width, 1);
self.current_row += 1;
let row = Rect::new(self.rect.x, self.current_row_fwd, self.rect.width, 1);
self.current_row_fwd += 1;
Some(row)
}
}
impl DoubleEndedIterator for Rows {
/// Retrieves the previous row within the `Rect`.
///
/// Returns `None` when there are no more rows to iterate through.
fn next_back(&mut self) -> Option<Self::Item> {
if self.current_row_back <= self.current_row_fwd {
return None;
}
self.current_row_back -= 1;
let row = Rect::new(self.rect.x, self.current_row_back, self.rect.width, 1);
Some(row)
}
}
@ -37,9 +54,11 @@ impl Iterator for Rows {
/// An iterator over columns within a `Rect`.
pub struct Columns {
/// The `Rect` associated with the columns.
pub rect: Rect,
/// The x coordinate of the column within the `Rect`.
pub current_column: u16,
rect: Rect,
/// The x coordinate of the column within the `Rect` when iterating forwards.
current_column_fwd: u16,
/// The x coordinate of the column within the `Rect` when iterating backwards.
current_column_back: u16,
}
impl Columns {
@ -47,7 +66,8 @@ impl Columns {
pub const fn new(rect: Rect) -> Self {
Self {
rect,
current_column: rect.x,
current_column_fwd: rect.x,
current_column_back: rect.right(),
}
}
}
@ -59,11 +79,25 @@ impl Iterator for Columns {
///
/// Returns `None` when there are no more columns to iterate through.
fn next(&mut self) -> Option<Self::Item> {
if self.current_column >= self.rect.right() {
if self.current_column_fwd >= self.current_column_back {
return None;
}
let column = Rect::new(self.current_column, self.rect.y, 1, self.rect.height);
self.current_column += 1;
let column = Rect::new(self.current_column_fwd, self.rect.y, 1, self.rect.height);
self.current_column_fwd += 1;
Some(column)
}
}
impl DoubleEndedIterator for Columns {
/// Retrieves the previous column within the `Rect`.
///
/// Returns `None` when there are no more columns to iterate through.
fn next_back(&mut self) -> Option<Self::Item> {
if self.current_column_back <= self.current_column_fwd {
return None;
}
self.current_column_back -= 1;
let column = Rect::new(self.current_column_back, self.rect.y, 1, self.rect.height);
Some(column)
}
}
@ -114,19 +148,92 @@ mod tests {
#[test]
fn rows() {
let rect = Rect::new(0, 0, 2, 2);
let rect = Rect::new(0, 0, 2, 3);
let mut rows = Rows::new(rect);
assert_eq!(rows.next(), Some(Rect::new(0, 0, 2, 1)));
assert_eq!(rows.next(), Some(Rect::new(0, 1, 2, 1)));
assert_eq!(rows.next(), Some(Rect::new(0, 2, 2, 1)));
assert_eq!(rows.next(), None);
assert_eq!(rows.next_back(), None);
}
#[test]
fn rows_back() {
let rect = Rect::new(0, 0, 2, 3);
let mut rows = Rows::new(rect);
assert_eq!(rows.next_back(), Some(Rect::new(0, 2, 2, 1)));
assert_eq!(rows.next_back(), Some(Rect::new(0, 1, 2, 1)));
assert_eq!(rows.next_back(), Some(Rect::new(0, 0, 2, 1)));
assert_eq!(rows.next_back(), None);
assert_eq!(rows.next(), None);
}
#[test]
fn rows_meet_in_the_middle() {
let rect = Rect::new(0, 0, 2, 4);
let mut rows = Rows::new(rect);
assert_eq!(rows.next(), Some(Rect::new(0, 0, 2, 1)));
assert_eq!(rows.next_back(), Some(Rect::new(0, 3, 2, 1)));
assert_eq!(rows.next(), Some(Rect::new(0, 1, 2, 1)));
assert_eq!(rows.next_back(), Some(Rect::new(0, 2, 2, 1)));
assert_eq!(rows.next(), None);
assert_eq!(rows.next_back(), None);
}
#[test]
fn columns() {
let rect = Rect::new(0, 0, 2, 2);
let rect = Rect::new(0, 0, 3, 2);
let mut columns = Columns::new(rect);
assert_eq!(columns.next(), Some(Rect::new(0, 0, 1, 2)));
assert_eq!(columns.next(), Some(Rect::new(1, 0, 1, 2)));
assert_eq!(columns.next(), Some(Rect::new(2, 0, 1, 2)));
assert_eq!(columns.next(), None);
assert_eq!(columns.next_back(), None);
}
#[test]
fn columns_back() {
let rect = Rect::new(0, 0, 3, 2);
let mut columns = Columns::new(rect);
assert_eq!(columns.next_back(), Some(Rect::new(2, 0, 1, 2)));
assert_eq!(columns.next_back(), Some(Rect::new(1, 0, 1, 2)));
assert_eq!(columns.next_back(), Some(Rect::new(0, 0, 1, 2)));
assert_eq!(columns.next_back(), None);
assert_eq!(columns.next(), None);
}
#[test]
fn columns_meet_in_the_middle() {
let rect = Rect::new(0, 0, 4, 2);
let mut columns = Columns::new(rect);
assert_eq!(columns.next(), Some(Rect::new(0, 0, 1, 2)));
assert_eq!(columns.next_back(), Some(Rect::new(3, 0, 1, 2)));
assert_eq!(columns.next(), Some(Rect::new(1, 0, 1, 2)));
assert_eq!(columns.next_back(), Some(Rect::new(2, 0, 1, 2)));
assert_eq!(columns.next(), None);
assert_eq!(columns.next_back(), None);
}
/// We allow a total of `65536` columns in the range `(0..=65535)`. In this test we iterate
/// forward and skip the first `65534` columns, and expect the next column to be `65535` and
/// the subsequent columns to be `None`.
#[test]
fn columns_max() {
let rect = Rect::new(0, 0, u16::MAX, 1);
let mut columns = Columns::new(rect).skip(usize::from(u16::MAX - 1));
assert_eq!(columns.next(), Some(Rect::new(u16::MAX - 1, 0, 1, 1)));
assert_eq!(columns.next(), None);
}
/// We allow a total of `65536` columns in the range `(0..=65535)`. In this test we iterate
/// backward and skip the last `65534` columns, and expect the next column to be `0` and the
/// subsequent columns to be `None`.
#[test]
fn columns_min() {
let rect = Rect::new(0, 0, u16::MAX, 1);
let mut columns = Columns::new(rect).rev().skip(usize::from(u16::MAX - 1));
assert_eq!(columns.next(), Some(Rect::new(0, 0, 1, 1)));
assert_eq!(columns.next(), None);
assert_eq!(columns.next(), None);
}