@@ -18,216 +18,9 @@ use crate::{
};
use itertools::intersperse_with;
-pub mod table_row {
- //! A newtype for a table row that enforces a fixed column count at runtime.
- //!
- //! This type ensures that all rows in a table have the same width, preventing accidental creation or mutation of rows with inconsistent lengths.
- //! It is especially useful for CSV or tabular data where rectangular invariants must be maintained, but the number of columns is only known at runtime.
- //! By using `TableRow`, we gain stronger guarantees and safer APIs compared to a bare `Vec<T>`, without requiring const generics.
-
- use std::{
- any::type_name,
- ops::{
- Index, IndexMut, Range, RangeFrom, RangeFull, RangeInclusive, RangeTo, RangeToInclusive,
- },
- };
-
- #[derive(Clone, Debug, PartialEq, Eq)]
- pub struct TableRow<T>(Vec<T>);
-
- impl<T> TableRow<T> {
- pub fn from_element(element: T, length: usize) -> Self
- where
- T: Clone,
- {
- Self::from_vec(vec![element; length], length)
- }
-
- /// Constructs a `TableRow` from a `Vec<T>`, panicking if the length does not match `expected_length`.
- ///
- /// Use this when you want to ensure at construction time that the row has the correct number of columns.
- /// This enforces the rectangular invariant for table data, preventing accidental creation of malformed rows.
- ///
- /// # Panics
- /// Panics if `data.len() != expected_length`.
- pub fn from_vec(data: Vec<T>, expected_length: usize) -> Self {
- Self::try_from_vec(data, expected_length).unwrap_or_else(|e| {
- let name = type_name::<Vec<T>>();
- panic!("Expected {name} to be created successfully: {e}");
- })
- }
-
- /// Attempts to construct a `TableRow` from a `Vec<T>`, returning an error if the length does not match `expected_len`.
- ///
- /// This is a fallible alternative to `from_vec`, allowing you to handle inconsistent row lengths gracefully.
- /// Returns `Ok(TableRow)` if the length matches, or an `Err` with a descriptive message otherwise.
- pub fn try_from_vec(data: Vec<T>, expected_len: usize) -> Result<Self, String> {
- if data.len() != expected_len {
- Err(format!(
- "Row length {} does not match expected {}",
- data.len(),
- expected_len
- ))
- } else {
- Ok(Self(data))
- }
- }
-
- /// Returns reference to element by column index.
- ///
- /// # Panics
- /// Panics if `col` is out of bounds (i.e., `col >= self.cols()`).
- pub fn expect_get(&self, col: impl Into<usize>) -> &T {
- let col = col.into();
- self.0.get(col).unwrap_or_else(|| {
- panic!(
- "Expected table row of `{}` to have {col:?}",
- type_name::<T>()
- )
- })
- }
-
- pub fn get(&self, col: impl Into<usize>) -> Option<&T> {
- self.0.get(col.into())
- }
-
- pub fn as_slice(&self) -> &[T] {
- &self.0
- }
-
- pub fn into_vec(self) -> Vec<T> {
- self.0
- }
-
- /// Like [`map`], but borrows the row and clones each element before mapping.
- ///
- /// This is useful when you want to map over a borrowed row without consuming it,
- /// but your mapping function requires ownership of each element.
- ///
- /// # Difference
- /// - `map_cloned` takes `&self`, clones each element, and applies `f(T) -> U`.
- /// - [`map`] takes `self` by value and applies `f(T) -> U` directly, consuming the row.
- /// - [`map_ref`] takes `&self` and applies `f(&T) -> U` to references of each element.
- pub fn map_cloned<F, U>(&self, f: F) -> TableRow<U>
- where
- F: FnMut(T) -> U,
- T: Clone,
- {
- self.clone().map(f)
- }
-
- /// Consumes the row and transforms all elements within it in a length-safe way.
- ///
- /// # Difference
- /// - `map` takes ownership of the row (`self`) and applies `f(T) -> U` to each element.
- /// - Use this when you want to transform and consume the row in one step.
- /// - See also [`map_cloned`] (for mapping over a borrowed row with cloning) and [`map_ref`] (for mapping over references).
- pub fn map<F, U>(self, f: F) -> TableRow<U>
- where
- F: FnMut(T) -> U,
- {
- TableRow(self.0.into_iter().map(f).collect())
- }
-
- /// Borrows the row and transforms all elements by reference in a length-safe way.
- ///
- /// # Difference
- /// - `map_ref` takes `&self` and applies `f(&T) -> U` to each element by reference.
- /// - Use this when you want to map over a borrowed row without cloning or consuming it.
- /// - See also [`map`] (for consuming the row) and [`map_cloned`] (for mapping with cloning).
- pub fn map_ref<F, U>(&self, f: F) -> TableRow<U>
- where
- F: FnMut(&T) -> U,
- {
- TableRow(self.0.iter().map(f).collect())
- }
-
- /// Number of columns (alias to `len()` with more semantic meaning)
- pub fn cols(&self) -> usize {
- self.0.len()
- }
- }
-
- ///// Convenience traits /////
- pub trait IntoTableRow<T> {
- fn into_table_row(self, expected_length: usize) -> TableRow<T>;
- }
- impl<T> IntoTableRow<T> for Vec<T> {
- fn into_table_row(self, expected_length: usize) -> TableRow<T> {
- TableRow::from_vec(self, expected_length)
- }
- }
-
- // Index implementations for convenient access
- impl<T> Index<usize> for TableRow<T> {
- type Output = T;
-
- fn index(&self, index: usize) -> &Self::Output {
- &self.0[index]
- }
- }
-
- impl<T> IndexMut<usize> for TableRow<T> {
- fn index_mut(&mut self, index: usize) -> &mut Self::Output {
- &mut self.0[index]
- }
- }
-
- // Range indexing implementations for slice operations
- impl<T> Index<Range<usize>> for TableRow<T> {
- type Output = [T];
-
- fn index(&self, index: Range<usize>) -> &Self::Output {
- <Vec<T> as Index<Range<usize>>>::index(&self.0, index)
- }
- }
-
- impl<T> Index<RangeFrom<usize>> for TableRow<T> {
- type Output = [T];
-
- fn index(&self, index: RangeFrom<usize>) -> &Self::Output {
- <Vec<T> as Index<RangeFrom<usize>>>::index(&self.0, index)
- }
- }
-
- impl<T> Index<RangeTo<usize>> for TableRow<T> {
- type Output = [T];
-
- fn index(&self, index: RangeTo<usize>) -> &Self::Output {
- <Vec<T> as Index<RangeTo<usize>>>::index(&self.0, index)
- }
- }
-
- impl<T> Index<RangeToInclusive<usize>> for TableRow<T> {
- type Output = [T];
-
- fn index(&self, index: RangeToInclusive<usize>) -> &Self::Output {
- <Vec<T> as Index<RangeToInclusive<usize>>>::index(&self.0, index)
- }
- }
-
- impl<T> Index<RangeFull> for TableRow<T> {
- type Output = [T];
-
- fn index(&self, index: RangeFull) -> &Self::Output {
- <Vec<T> as Index<RangeFull>>::index(&self.0, index)
- }
- }
-
- impl<T> Index<RangeInclusive<usize>> for TableRow<T> {
- type Output = [T];
-
- fn index(&self, index: RangeInclusive<usize>) -> &Self::Output {
- <Vec<T> as Index<RangeInclusive<usize>>>::index(&self.0, index)
- }
- }
-
- impl<T> IndexMut<RangeInclusive<usize>> for TableRow<T> {
- fn index_mut(&mut self, index: RangeInclusive<usize>) -> &mut Self::Output {
- <Vec<T> as IndexMut<RangeInclusive<usize>>>::index_mut(&mut self.0, index)
- }
- }
-}
+pub mod table_row;
+#[cfg(test)]
+mod tests;
const RESIZE_COLUMN_WIDTH: f32 = 8.0;
@@ -1445,330 +1238,3 @@ impl Component for Table {
)
}
}
-
-#[cfg(test)]
-mod test {
- use super::*;
-
- fn is_almost_eq(a: &[f32], b: &[f32]) -> bool {
- a.len() == b.len() && a.iter().zip(b).all(|(x, y)| (x - y).abs() < 1e-6)
- }
-
- fn cols_to_str(cols: &[f32], total_size: f32) -> String {
- cols.iter()
- .map(|f| "*".repeat(f32::round(f * total_size) as usize))
- .collect::<Vec<String>>()
- .join("|")
- }
-
- fn parse_resize_behavior(
- input: &str,
- total_size: f32,
- expected_cols: usize,
- ) -> Vec<TableResizeBehavior> {
- let mut resize_behavior = Vec::with_capacity(expected_cols);
- for col in input.split('|') {
- if col.starts_with('X') || col.is_empty() {
- resize_behavior.push(TableResizeBehavior::None);
- } else if col.starts_with('*') {
- resize_behavior.push(TableResizeBehavior::MinSize(col.len() as f32 / total_size));
- } else {
- panic!("invalid test input: unrecognized resize behavior: {}", col);
- }
- }
-
- if resize_behavior.len() != expected_cols {
- panic!(
- "invalid test input: expected {} columns, got {}",
- expected_cols,
- resize_behavior.len()
- );
- }
- resize_behavior
- }
-
- mod reset_column_size {
- use super::*;
-
- fn parse(input: &str) -> (Vec<f32>, f32, Option<usize>) {
- let mut widths = Vec::new();
- let mut column_index = None;
- for (index, col) in input.split('|').enumerate() {
- widths.push(col.len() as f32);
- if col.starts_with('X') {
- column_index = Some(index);
- }
- }
-
- for w in &widths {
- assert!(w.is_finite(), "incorrect number of columns");
- }
- let total = widths.iter().sum::<f32>();
- for width in &mut widths {
- *width /= total;
- }
- (widths, total, column_index)
- }
-
- #[track_caller]
- fn check_reset_size(
- initial_sizes: &str,
- widths: &str,
- expected: &str,
- resize_behavior: &str,
- ) {
- let (initial_sizes, total_1, None) = parse(initial_sizes) else {
- panic!("invalid test input: initial sizes should not be marked");
- };
- let (widths, total_2, Some(column_index)) = parse(widths) else {
- panic!("invalid test input: widths should be marked");
- };
- assert_eq!(
- total_1, total_2,
- "invalid test input: total width not the same {total_1}, {total_2}"
- );
- let (expected, total_3, None) = parse(expected) else {
- panic!("invalid test input: expected should not be marked: {expected:?}");
- };
- assert_eq!(
- total_2, total_3,
- "invalid test input: total width not the same"
- );
- let cols = initial_sizes.len();
- let resize_behavior_vec = parse_resize_behavior(resize_behavior, total_1, cols);
- let resize_behavior = TableRow::from_vec(resize_behavior_vec, cols);
- let result = TableColumnWidths::reset_to_initial_size(
- column_index,
- TableRow::from_vec(widths, cols),
- TableRow::from_vec(initial_sizes, cols),
- &resize_behavior,
- );
- let result_slice = result.as_slice();
- let is_eq = is_almost_eq(result_slice, &expected);
- if !is_eq {
- let result_str = cols_to_str(result_slice, total_1);
- let expected_str = cols_to_str(&expected, total_1);
- panic!(
- "resize failed\ncomputed: {result_str}\nexpected: {expected_str}\n\ncomputed values: {result_slice:?}\nexpected values: {expected:?}\n:minimum widths: {resize_behavior:?}"
- );
- }
- }
-
- macro_rules! check_reset_size {
- (columns: $cols:expr, starting: $initial:expr, snapshot: $current:expr, expected: $expected:expr, resizing: $resizing:expr $(,)?) => {
- check_reset_size($initial, $current, $expected, $resizing);
- };
- ($name:ident, columns: $cols:expr, starting: $initial:expr, snapshot: $current:expr, expected: $expected:expr, minimums: $resizing:expr $(,)?) => {
- #[test]
- fn $name() {
- check_reset_size($initial, $current, $expected, $resizing);
- }
- };
- }
-
- check_reset_size!(
- basic_right,
- columns: 5,
- starting: "**|**|**|**|**",
- snapshot: "**|**|X|***|**",
- expected: "**|**|**|**|**",
- minimums: "X|*|*|*|*",
- );
-
- check_reset_size!(
- basic_left,
- columns: 5,
- starting: "**|**|**|**|**",
- snapshot: "**|**|***|X|**",
- expected: "**|**|**|**|**",
- minimums: "X|*|*|*|**",
- );
-
- check_reset_size!(
- squashed_left_reset_col2,
- columns: 6,
- starting: "*|***|**|**|****|*",
- snapshot: "*|*|X|*|*|********",
- expected: "*|*|**|*|*|*******",
- minimums: "X|*|*|*|*|*",
- );
-
- check_reset_size!(
- grow_cascading_right,
- columns: 6,
- starting: "*|***|****|**|***|*",
- snapshot: "*|***|X|**|**|*****",
- expected: "*|***|****|*|*|****",
- minimums: "X|*|*|*|*|*",
- );
-
- check_reset_size!(
- squashed_right_reset_col4,
- columns: 6,
- starting: "*|***|**|**|****|*",
- snapshot: "*|********|*|*|X|*",
- expected: "*|*****|*|*|****|*",
- minimums: "X|*|*|*|*|*",
- );
-
- check_reset_size!(
- reset_col6_right,
- columns: 6,
- starting: "*|***|**|***|***|**",
- snapshot: "*|***|**|***|**|XXX",
- expected: "*|***|**|***|***|**",
- minimums: "X|*|*|*|*|*",
- );
-
- check_reset_size!(
- reset_col6_left,
- columns: 6,
- starting: "*|***|**|***|***|**",
- snapshot: "*|***|**|***|****|X",
- expected: "*|***|**|***|***|**",
- minimums: "X|*|*|*|*|*",
- );
-
- check_reset_size!(
- last_column_grow_cascading,
- columns: 6,
- starting: "*|***|**|**|**|***",
- snapshot: "*|*******|*|**|*|X",
- expected: "*|******|*|*|*|***",
- minimums: "X|*|*|*|*|*",
- );
-
- check_reset_size!(
- goes_left_when_left_has_extreme_diff,
- columns: 6,
- starting: "*|***|****|**|**|***",
- snapshot: "*|********|X|*|**|**",
- expected: "*|*****|****|*|**|**",
- minimums: "X|*|*|*|*|*",
- );
-
- check_reset_size!(
- basic_shrink_right,
- columns: 6,
- starting: "**|**|**|**|**|**",
- snapshot: "**|**|XXX|*|**|**",
- expected: "**|**|**|**|**|**",
- minimums: "X|*|*|*|*|*",
- );
-
- check_reset_size!(
- shrink_should_go_left,
- columns: 6,
- starting: "*|***|**|*|*|*",
- snapshot: "*|*|XXX|**|*|*",
- expected: "*|**|**|**|*|*",
- minimums: "X|*|*|*|*|*",
- );
-
- check_reset_size!(
- shrink_should_go_right,
- columns: 6,
- starting: "*|***|**|**|**|*",
- snapshot: "*|****|XXX|*|*|*",
- expected: "*|****|**|**|*|*",
- minimums: "X|*|*|*|*|*",
- );
- }
-
- mod drag_handle {
- use super::*;
-
- fn parse(input: &str) -> (Vec<f32>, f32, Option<usize>) {
- let mut widths = Vec::new();
- let column_index = input.replace("*", "").find("I");
- for col in input.replace("I", "|").split('|') {
- widths.push(col.len() as f32);
- }
-
- for w in &widths {
- assert!(w.is_finite(), "incorrect number of columns");
- }
- let total = widths.iter().sum::<f32>();
- for width in &mut widths {
- *width /= total;
- }
- (widths, total, column_index)
- }
-
- #[track_caller]
- fn check(distance: i32, widths: &str, expected: &str, resize_behavior: &str) {
- let (widths, total_1, Some(column_index)) = parse(widths) else {
- panic!("invalid test input: widths should be marked");
- };
- let (expected, total_2, None) = parse(expected) else {
- panic!("invalid test input: expected should not be marked: {expected:?}");
- };
- assert_eq!(
- total_1, total_2,
- "invalid test input: total width not the same"
- );
- let cols = widths.len();
- let resize_behavior_vec = parse_resize_behavior(resize_behavior, total_1, cols);
- let resize_behavior = TableRow::from_vec(resize_behavior_vec, cols);
-
- let distance = distance as f32 / total_1;
-
- let mut widths_table_row = TableRow::from_vec(widths, cols);
- TableColumnWidths::drag_column_handle(
- distance,
- column_index,
- &mut widths_table_row,
- &resize_behavior,
- );
-
- let result_widths = widths_table_row.as_slice();
- let is_eq = is_almost_eq(result_widths, &expected);
- if !is_eq {
- let result_str = cols_to_str(result_widths, total_1);
- let expected_str = cols_to_str(&expected, total_1);
- panic!(
- "resize failed\ncomputed: {result_str}\nexpected: {expected_str}\n\ncomputed values: {result_widths:?}\nexpected values: {expected:?}\n:minimum widths: {resize_behavior:?}"
- );
- }
- }
-
- macro_rules! check {
- (columns: $cols:expr, distance: $dist:expr, snapshot: $current:expr, expected: $expected:expr, resizing: $resizing:expr $(,)?) => {
- check($dist, $current, $expected, $resizing);
- };
- ($name:ident, columns: $cols:expr, distance: $dist:expr, snapshot: $current:expr, expected: $expected:expr, minimums: $resizing:expr $(,)?) => {
- #[test]
- fn $name() {
- check($dist, $current, $expected, $resizing);
- }
- };
- }
-
- check!(
- basic_right_drag,
- columns: 3,
- distance: 1,
- snapshot: "**|**I**",
- expected: "**|***|*",
- minimums: "X|*|*",
- );
-
- check!(
- drag_left_against_mins,
- columns: 5,
- distance: -1,
- snapshot: "*|*|*|*I*******",
- expected: "*|*|*|*|*******",
- minimums: "X|*|*|*|*",
- );
-
- check!(
- drag_left,
- columns: 5,
- distance: -2,
- snapshot: "*|*|*|*****I***",
- expected: "*|*|*|***|*****",
- minimums: "X|*|*|*|*",
- );
- }
-}
@@ -0,0 +1,208 @@
+//! A newtype for a table row that enforces a fixed column count at runtime.
+//!
+//! This type ensures that all rows in a table have the same width, preventing accidental creation or mutation of rows with inconsistent lengths.
+//! It is especially useful for CSV or tabular data where rectangular invariants must be maintained, but the number of columns is only known at runtime.
+//! By using `TableRow`, we gain stronger guarantees and safer APIs compared to a bare `Vec<T>`, without requiring const generics.
+
+use std::{
+ any::type_name,
+ ops::{
+ Index, IndexMut, Range, RangeFrom, RangeFull, RangeInclusive, RangeTo, RangeToInclusive,
+ },
+};
+
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub struct TableRow<T>(Vec<T>);
+
+impl<T> TableRow<T> {
+ pub fn from_element(element: T, length: usize) -> Self
+ where
+ T: Clone,
+ {
+ Self::from_vec(vec![element; length], length)
+ }
+
+ /// Constructs a `TableRow` from a `Vec<T>`, panicking if the length does not match `expected_length`.
+ ///
+ /// Use this when you want to ensure at construction time that the row has the correct number of columns.
+ /// This enforces the rectangular invariant for table data, preventing accidental creation of malformed rows.
+ ///
+ /// # Panics
+ /// Panics if `data.len() != expected_length`.
+ pub fn from_vec(data: Vec<T>, expected_length: usize) -> Self {
+ Self::try_from_vec(data, expected_length).unwrap_or_else(|e| {
+ let name = type_name::<Vec<T>>();
+ panic!("Expected {name} to be created successfully: {e}");
+ })
+ }
+
+ /// Attempts to construct a `TableRow` from a `Vec<T>`, returning an error if the length does not match `expected_len`.
+ ///
+ /// This is a fallible alternative to `from_vec`, allowing you to handle inconsistent row lengths gracefully.
+ /// Returns `Ok(TableRow)` if the length matches, or an `Err` with a descriptive message otherwise.
+ pub fn try_from_vec(data: Vec<T>, expected_len: usize) -> Result<Self, String> {
+ if data.len() != expected_len {
+ Err(format!(
+ "Row length {} does not match expected {}",
+ data.len(),
+ expected_len
+ ))
+ } else {
+ Ok(Self(data))
+ }
+ }
+
+ /// Returns reference to element by column index.
+ ///
+ /// # Panics
+ /// Panics if `col` is out of bounds (i.e., `col >= self.cols()`).
+ pub fn expect_get(&self, col: impl Into<usize>) -> &T {
+ let col = col.into();
+ self.0.get(col).unwrap_or_else(|| {
+ panic!(
+ "Expected table row of `{}` to have {col:?}",
+ type_name::<T>()
+ )
+ })
+ }
+
+ pub fn get(&self, col: impl Into<usize>) -> Option<&T> {
+ self.0.get(col.into())
+ }
+
+ pub fn as_slice(&self) -> &[T] {
+ &self.0
+ }
+
+ pub fn into_vec(self) -> Vec<T> {
+ self.0
+ }
+
+ /// Like [`map`], but borrows the row and clones each element before mapping.
+ ///
+ /// This is useful when you want to map over a borrowed row without consuming it,
+ /// but your mapping function requires ownership of each element.
+ ///
+ /// # Difference
+ /// - `map_cloned` takes `&self`, clones each element, and applies `f(T) -> U`.
+ /// - [`map`] takes `self` by value and applies `f(T) -> U` directly, consuming the row.
+ /// - [`map_ref`] takes `&self` and applies `f(&T) -> U` to references of each element.
+ pub fn map_cloned<F, U>(&self, f: F) -> TableRow<U>
+ where
+ F: FnMut(T) -> U,
+ T: Clone,
+ {
+ self.clone().map(f)
+ }
+
+ /// Consumes the row and transforms all elements within it in a length-safe way.
+ ///
+ /// # Difference
+ /// - `map` takes ownership of the row (`self`) and applies `f(T) -> U` to each element.
+ /// - Use this when you want to transform and consume the row in one step.
+ /// - See also [`map_cloned`] (for mapping over a borrowed row with cloning) and [`map_ref`] (for mapping over references).
+ pub fn map<F, U>(self, f: F) -> TableRow<U>
+ where
+ F: FnMut(T) -> U,
+ {
+ TableRow(self.0.into_iter().map(f).collect())
+ }
+
+ /// Borrows the row and transforms all elements by reference in a length-safe way.
+ ///
+ /// # Difference
+ /// - `map_ref` takes `&self` and applies `f(&T) -> U` to each element by reference.
+ /// - Use this when you want to map over a borrowed row without cloning or consuming it.
+ /// - See also [`map`] (for consuming the row) and [`map_cloned`] (for mapping with cloning).
+ pub fn map_ref<F, U>(&self, f: F) -> TableRow<U>
+ where
+ F: FnMut(&T) -> U,
+ {
+ TableRow(self.0.iter().map(f).collect())
+ }
+
+ /// Number of columns (alias to `len()` with more semantic meaning)
+ pub fn cols(&self) -> usize {
+ self.0.len()
+ }
+}
+
+///// Convenience traits /////
+pub trait IntoTableRow<T> {
+ fn into_table_row(self, expected_length: usize) -> TableRow<T>;
+}
+impl<T> IntoTableRow<T> for Vec<T> {
+ fn into_table_row(self, expected_length: usize) -> TableRow<T> {
+ TableRow::from_vec(self, expected_length)
+ }
+}
+
+// Index implementations for convenient access
+impl<T> Index<usize> for TableRow<T> {
+ type Output = T;
+
+ fn index(&self, index: usize) -> &Self::Output {
+ &self.0[index]
+ }
+}
+
+impl<T> IndexMut<usize> for TableRow<T> {
+ fn index_mut(&mut self, index: usize) -> &mut Self::Output {
+ &mut self.0[index]
+ }
+}
+
+// Range indexing implementations for slice operations
+impl<T> Index<Range<usize>> for TableRow<T> {
+ type Output = [T];
+
+ fn index(&self, index: Range<usize>) -> &Self::Output {
+ <Vec<T> as Index<Range<usize>>>::index(&self.0, index)
+ }
+}
+
+impl<T> Index<RangeFrom<usize>> for TableRow<T> {
+ type Output = [T];
+
+ fn index(&self, index: RangeFrom<usize>) -> &Self::Output {
+ <Vec<T> as Index<RangeFrom<usize>>>::index(&self.0, index)
+ }
+}
+
+impl<T> Index<RangeTo<usize>> for TableRow<T> {
+ type Output = [T];
+
+ fn index(&self, index: RangeTo<usize>) -> &Self::Output {
+ <Vec<T> as Index<RangeTo<usize>>>::index(&self.0, index)
+ }
+}
+
+impl<T> Index<RangeToInclusive<usize>> for TableRow<T> {
+ type Output = [T];
+
+ fn index(&self, index: RangeToInclusive<usize>) -> &Self::Output {
+ <Vec<T> as Index<RangeToInclusive<usize>>>::index(&self.0, index)
+ }
+}
+
+impl<T> Index<RangeFull> for TableRow<T> {
+ type Output = [T];
+
+ fn index(&self, index: RangeFull) -> &Self::Output {
+ <Vec<T> as Index<RangeFull>>::index(&self.0, index)
+ }
+}
+
+impl<T> Index<RangeInclusive<usize>> for TableRow<T> {
+ type Output = [T];
+
+ fn index(&self, index: RangeInclusive<usize>) -> &Self::Output {
+ <Vec<T> as Index<RangeInclusive<usize>>>::index(&self.0, index)
+ }
+}
+
+impl<T> IndexMut<RangeInclusive<usize>> for TableRow<T> {
+ fn index_mut(&mut self, index: RangeInclusive<usize>) -> &mut Self::Output {
+ <Vec<T> as IndexMut<RangeInclusive<usize>>>::index_mut(&mut self.0, index)
+ }
+}
@@ -0,0 +1,318 @@
+use super::*;
+
+fn is_almost_eq(a: &[f32], b: &[f32]) -> bool {
+ a.len() == b.len() && a.iter().zip(b).all(|(x, y)| (x - y).abs() < 1e-6)
+}
+
+fn cols_to_str(cols: &[f32], total_size: f32) -> String {
+ cols.iter()
+ .map(|f| "*".repeat(f32::round(f * total_size) as usize))
+ .collect::<Vec<String>>()
+ .join("|")
+}
+
+fn parse_resize_behavior(
+ input: &str,
+ total_size: f32,
+ expected_cols: usize,
+) -> Vec<TableResizeBehavior> {
+ let mut resize_behavior = Vec::with_capacity(expected_cols);
+ for col in input.split('|') {
+ if col.starts_with('X') || col.is_empty() {
+ resize_behavior.push(TableResizeBehavior::None);
+ } else if col.starts_with('*') {
+ resize_behavior.push(TableResizeBehavior::MinSize(col.len() as f32 / total_size));
+ } else {
+ panic!("invalid test input: unrecognized resize behavior: {}", col);
+ }
+ }
+
+ if resize_behavior.len() != expected_cols {
+ panic!(
+ "invalid test input: expected {} columns, got {}",
+ expected_cols,
+ resize_behavior.len()
+ );
+ }
+ resize_behavior
+}
+
+mod reset_column_size {
+ use super::*;
+
+ fn parse(input: &str) -> (Vec<f32>, f32, Option<usize>) {
+ let mut widths = Vec::new();
+ let mut column_index = None;
+ for (index, col) in input.split('|').enumerate() {
+ widths.push(col.len() as f32);
+ if col.starts_with('X') {
+ column_index = Some(index);
+ }
+ }
+
+ for w in &widths {
+ assert!(w.is_finite(), "incorrect number of columns");
+ }
+ let total = widths.iter().sum::<f32>();
+ for width in &mut widths {
+ *width /= total;
+ }
+ (widths, total, column_index)
+ }
+
+ #[track_caller]
+ fn check_reset_size(initial_sizes: &str, widths: &str, expected: &str, resize_behavior: &str) {
+ let (initial_sizes, total_1, None) = parse(initial_sizes) else {
+ panic!("invalid test input: initial sizes should not be marked");
+ };
+ let (widths, total_2, Some(column_index)) = parse(widths) else {
+ panic!("invalid test input: widths should be marked");
+ };
+ assert_eq!(
+ total_1, total_2,
+ "invalid test input: total width not the same {total_1}, {total_2}"
+ );
+ let (expected, total_3, None) = parse(expected) else {
+ panic!("invalid test input: expected should not be marked: {expected:?}");
+ };
+ assert_eq!(
+ total_2, total_3,
+ "invalid test input: total width not the same"
+ );
+ let cols = initial_sizes.len();
+ let resize_behavior_vec = parse_resize_behavior(resize_behavior, total_1, cols);
+ let resize_behavior = TableRow::from_vec(resize_behavior_vec, cols);
+ let result = TableColumnWidths::reset_to_initial_size(
+ column_index,
+ TableRow::from_vec(widths, cols),
+ TableRow::from_vec(initial_sizes, cols),
+ &resize_behavior,
+ );
+ let result_slice = result.as_slice();
+ let is_eq = is_almost_eq(result_slice, &expected);
+ if !is_eq {
+ let result_str = cols_to_str(result_slice, total_1);
+ let expected_str = cols_to_str(&expected, total_1);
+ panic!(
+ "resize failed\ncomputed: {result_str}\nexpected: {expected_str}\n\ncomputed values: {result_slice:?}\nexpected values: {expected:?}\n:minimum widths: {resize_behavior:?}"
+ );
+ }
+ }
+
+ macro_rules! check_reset_size {
+ (columns: $cols:expr, starting: $initial:expr, snapshot: $current:expr, expected: $expected:expr, resizing: $resizing:expr $(,)?) => {
+ check_reset_size($initial, $current, $expected, $resizing);
+ };
+ ($name:ident, columns: $cols:expr, starting: $initial:expr, snapshot: $current:expr, expected: $expected:expr, minimums: $resizing:expr $(,)?) => {
+ #[test]
+ fn $name() {
+ check_reset_size($initial, $current, $expected, $resizing);
+ }
+ };
+ }
+
+ check_reset_size!(
+ basic_right,
+ columns: 5,
+ starting: "**|**|**|**|**",
+ snapshot: "**|**|X|***|**",
+ expected: "**|**|**|**|**",
+ minimums: "X|*|*|*|*",
+ );
+
+ check_reset_size!(
+ basic_left,
+ columns: 5,
+ starting: "**|**|**|**|**",
+ snapshot: "**|**|***|X|**",
+ expected: "**|**|**|**|**",
+ minimums: "X|*|*|*|**",
+ );
+
+ check_reset_size!(
+ squashed_left_reset_col2,
+ columns: 6,
+ starting: "*|***|**|**|****|*",
+ snapshot: "*|*|X|*|*|********",
+ expected: "*|*|**|*|*|*******",
+ minimums: "X|*|*|*|*|*",
+ );
+
+ check_reset_size!(
+ grow_cascading_right,
+ columns: 6,
+ starting: "*|***|****|**|***|*",
+ snapshot: "*|***|X|**|**|*****",
+ expected: "*|***|****|*|*|****",
+ minimums: "X|*|*|*|*|*",
+ );
+
+ check_reset_size!(
+ squashed_right_reset_col4,
+ columns: 6,
+ starting: "*|***|**|**|****|*",
+ snapshot: "*|********|*|*|X|*",
+ expected: "*|*****|*|*|****|*",
+ minimums: "X|*|*|*|*|*",
+ );
+
+ check_reset_size!(
+ reset_col6_right,
+ columns: 6,
+ starting: "*|***|**|***|***|**",
+ snapshot: "*|***|**|***|**|XXX",
+ expected: "*|***|**|***|***|**",
+ minimums: "X|*|*|*|*|*",
+ );
+
+ check_reset_size!(
+ reset_col6_left,
+ columns: 6,
+ starting: "*|***|**|***|***|**",
+ snapshot: "*|***|**|***|****|X",
+ expected: "*|***|**|***|***|**",
+ minimums: "X|*|*|*|*|*",
+ );
+
+ check_reset_size!(
+ last_column_grow_cascading,
+ columns: 6,
+ starting: "*|***|**|**|**|***",
+ snapshot: "*|*******|*|**|*|X",
+ expected: "*|******|*|*|*|***",
+ minimums: "X|*|*|*|*|*",
+ );
+
+ check_reset_size!(
+ goes_left_when_left_has_extreme_diff,
+ columns: 6,
+ starting: "*|***|****|**|**|***",
+ snapshot: "*|********|X|*|**|**",
+ expected: "*|*****|****|*|**|**",
+ minimums: "X|*|*|*|*|*",
+ );
+
+ check_reset_size!(
+ basic_shrink_right,
+ columns: 6,
+ starting: "**|**|**|**|**|**",
+ snapshot: "**|**|XXX|*|**|**",
+ expected: "**|**|**|**|**|**",
+ minimums: "X|*|*|*|*|*",
+ );
+
+ check_reset_size!(
+ shrink_should_go_left,
+ columns: 6,
+ starting: "*|***|**|*|*|*",
+ snapshot: "*|*|XXX|**|*|*",
+ expected: "*|**|**|**|*|*",
+ minimums: "X|*|*|*|*|*",
+ );
+
+ check_reset_size!(
+ shrink_should_go_right,
+ columns: 6,
+ starting: "*|***|**|**|**|*",
+ snapshot: "*|****|XXX|*|*|*",
+ expected: "*|****|**|**|*|*",
+ minimums: "X|*|*|*|*|*",
+ );
+}
+
+mod drag_handle {
+ use super::*;
+
+ fn parse(input: &str) -> (Vec<f32>, f32, Option<usize>) {
+ let mut widths = Vec::new();
+ let column_index = input.replace("*", "").find("I");
+ for col in input.replace("I", "|").split('|') {
+ widths.push(col.len() as f32);
+ }
+
+ for w in &widths {
+ assert!(w.is_finite(), "incorrect number of columns");
+ }
+ let total = widths.iter().sum::<f32>();
+ for width in &mut widths {
+ *width /= total;
+ }
+ (widths, total, column_index)
+ }
+
+ #[track_caller]
+ fn check(distance: i32, widths: &str, expected: &str, resize_behavior: &str) {
+ let (widths, total_1, Some(column_index)) = parse(widths) else {
+ panic!("invalid test input: widths should be marked");
+ };
+ let (expected, total_2, None) = parse(expected) else {
+ panic!("invalid test input: expected should not be marked: {expected:?}");
+ };
+ assert_eq!(
+ total_1, total_2,
+ "invalid test input: total width not the same"
+ );
+ let cols = widths.len();
+ let resize_behavior_vec = parse_resize_behavior(resize_behavior, total_1, cols);
+ let resize_behavior = TableRow::from_vec(resize_behavior_vec, cols);
+
+ let distance = distance as f32 / total_1;
+
+ let mut widths_table_row = TableRow::from_vec(widths, cols);
+ TableColumnWidths::drag_column_handle(
+ distance,
+ column_index,
+ &mut widths_table_row,
+ &resize_behavior,
+ );
+
+ let result_widths = widths_table_row.as_slice();
+ let is_eq = is_almost_eq(result_widths, &expected);
+ if !is_eq {
+ let result_str = cols_to_str(result_widths, total_1);
+ let expected_str = cols_to_str(&expected, total_1);
+ panic!(
+ "resize failed\ncomputed: {result_str}\nexpected: {expected_str}\n\ncomputed values: {result_widths:?}\nexpected values: {expected:?}\n:minimum widths: {resize_behavior:?}"
+ );
+ }
+ }
+
+ macro_rules! check {
+ (columns: $cols:expr, distance: $dist:expr, snapshot: $current:expr, expected: $expected:expr, resizing: $resizing:expr $(,)?) => {
+ check($dist, $current, $expected, $resizing);
+ };
+ ($name:ident, columns: $cols:expr, distance: $dist:expr, snapshot: $current:expr, expected: $expected:expr, minimums: $resizing:expr $(,)?) => {
+ #[test]
+ fn $name() {
+ check($dist, $current, $expected, $resizing);
+ }
+ };
+ }
+
+ check!(
+ basic_right_drag,
+ columns: 3,
+ distance: 1,
+ snapshot: "**|**I**",
+ expected: "**|***|*",
+ minimums: "X|*|*",
+ );
+
+ check!(
+ drag_left_against_mins,
+ columns: 5,
+ distance: -1,
+ snapshot: "*|*|*|*I*******",
+ expected: "*|*|*|*|*******",
+ minimums: "X|*|*|*|*",
+ );
+
+ check!(
+ drag_left,
+ columns: 5,
+ distance: -2,
+ snapshot: "*|*|*|*****I***",
+ expected: "*|*|*|***|*****",
+ minimums: "X|*|*|*|*",
+ );
+}