#[allow(unused_imports)]
use crate::{Field, FieldKind, Fill, Region, RowPart};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Width {
Content,
Fixed,
Fill,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[non_exhaustive]
pub enum Priority {
Optional,
Secondary,
Essential,
}
impl Priority {
pub const RAISING: &'static [Self] = &[Self::Optional, Self::Secondary, Self::Essential];
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[non_exhaustive]
pub struct Budget {
pub columns: u32,
pub floors: u32,
}
impl Budget {
#[must_use]
pub fn width(self, ch: f32, gap: f32) -> f32 {
self.floors as f32 * ch + gap * (self.columns + 1) as f32
}
}
#[must_use]
pub fn budget(columns: &[Column<'_>], cutoff: Priority) -> Budget {
columns.iter().filter(|column| column.kept_at(cutoff)).fold(
Budget::default(),
|budget, column| Budget {
columns: budget.columns + 1,
floors: budget.floors + u32::from(column.floor()),
},
)
}
#[must_use]
pub fn cutoff_for(columns: &[Column<'_>], fits: impl Fn(Budget) -> bool) -> Priority {
Priority::RAISING
.iter()
.copied()
.find(|&cutoff| fits(budget(columns, cutoff)))
.unwrap_or(Priority::Essential)
}
#[derive(Debug, Clone, Copy, PartialEq, Default)]
#[non_exhaustive]
pub struct Sizing<'a, L> {
pub lengths: &'a [(&'a str, L)],
pub fallback: L,
}
impl<'a, L: Copy> Sizing<'a, L> {
#[must_use]
pub const fn new(lengths: &'a [(&'a str, L)], fallback: L) -> Self {
Self { lengths, fallback }
}
#[must_use]
pub fn length_for(&self, name: &str) -> L {
self.lengths
.iter()
.find(|(column, _)| *column == name)
.map_or(self.fallback, |(_, length)| *length)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Fallback {
Wrap,
Stack,
Shed,
Menu,
}
pub const MIN_CEILING: u16 = 64;
const FILL_FLOOR: u16 = 16;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub struct Column<'a> {
pub name: &'a str,
pub width: Width,
pub priority: Priority,
pub kind: ColumnKind,
pub min: Option<u16>,
pub sortable: bool,
pub sorted: Option<Sort>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[non_exhaustive]
pub enum ColumnKind {
#[default]
Text,
Identifier,
Date,
Number,
Code,
Status,
Actions,
}
impl ColumnKind {
#[must_use]
pub const fn floor(self) -> u16 {
match self {
Self::Text | Self::Code | Self::Status | Self::Actions => 8,
Self::Identifier => 12,
Self::Date => 10,
Self::Number => 6,
}
}
#[must_use]
pub const fn aligns_end(self) -> bool {
matches!(self, Self::Number | Self::Actions)
}
#[must_use]
pub const fn wraps(self) -> bool {
matches!(self, Self::Text | Self::Identifier)
}
#[must_use]
pub const fn monospace(self) -> bool {
matches!(self, Self::Identifier | Self::Code)
}
#[must_use]
pub const fn tabular(self) -> bool {
matches!(self, Self::Date | Self::Number)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Sort {
Ascending,
Descending,
}
impl Sort {
#[must_use]
pub const fn reversed(self) -> Self {
match self {
Self::Ascending => Self::Descending,
Self::Descending => Self::Ascending,
}
}
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Ascending => "ascending",
Self::Descending => "descending",
}
}
#[must_use]
pub const fn glyph(self) -> &'static str {
match self {
Self::Ascending => "\u{25B2}",
Self::Descending => "\u{25BC}",
}
}
}
impl<'a> Column<'a> {
#[must_use]
pub const fn new(name: &'a str) -> Self {
Self {
name,
width: Width::Fill,
priority: Priority::Essential,
kind: ColumnKind::Text,
min: None,
sortable: false,
sorted: None,
}
}
#[must_use]
pub const fn min(mut self, ch: u16) -> Self {
self.min = Some(ch);
self
}
#[must_use]
pub const fn width(mut self, width: Width) -> Self {
self.width = width;
self
}
#[must_use]
pub const fn priority(mut self, priority: Priority) -> Self {
self.priority = priority;
self
}
#[must_use]
pub const fn kind(mut self, kind: ColumnKind) -> Self {
self.kind = kind;
self
}
#[must_use]
pub const fn sortable(mut self, sortable: bool) -> Self {
self.sortable = sortable;
self
}
#[must_use]
pub const fn sorted(mut self, sort: Sort) -> Self {
self.sorted = Some(sort);
self
}
#[must_use]
pub const fn floor(&self) -> u16 {
let raw = match self.min {
Some(ch) => ch,
None => {
let caret = if self.sortable || self.sorted.is_some() {
2
} else {
0
};
let heading = if self.name.len() > MIN_CEILING as usize {
MIN_CEILING
} else {
self.name.len() as u16 + 2 + caret
};
let kind = self.kind.floor();
let fill = if matches!(self.width, Width::Fill) {
FILL_FLOOR
} else {
0
};
let wider = if heading > kind { heading } else { kind };
if fill > wider { fill } else { wider }
}
};
let raw = if raw > MIN_CEILING { MIN_CEILING } else { raw };
let even = raw + raw % 2;
if even < 2 {
2
} else if even > MIN_CEILING {
MIN_CEILING
} else {
even
}
}
#[must_use]
pub const fn kept_at(&self, cutoff: Priority) -> bool {
(self.priority as u8) >= (cutoff as u8)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum CellPart {
Value,
Tokens,
Actions,
Link,
}
impl CellPart {
#[must_use]
pub const fn intent(self) -> &'static str {
match self {
Self::Value => "content",
Self::Tokens => "content",
Self::Actions => "content",
Self::Link => "content",
}
}
}