mod color;
mod theme;
pub use color::{Color, ColorDepth};
pub use theme::{Spacing, Theme, ThemeBuilder, ThemeColor};
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Breakpoint {
Xs,
Sm,
Md,
Lg,
Xl,
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Border {
Single,
Double,
Rounded,
Thick,
Dashed,
DashedThick,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct BorderChars {
pub tl: char,
pub tr: char,
pub bl: char,
pub br: char,
pub h: char,
pub v: char,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct BorderSides {
pub top: bool,
pub right: bool,
pub bottom: bool,
pub left: bool,
}
impl BorderSides {
pub const fn all() -> Self {
Self {
top: true,
right: true,
bottom: true,
left: true,
}
}
pub const fn none() -> Self {
Self {
top: false,
right: false,
bottom: false,
left: false,
}
}
pub const fn horizontal() -> Self {
Self {
top: true,
right: false,
bottom: true,
left: false,
}
}
pub const fn vertical() -> Self {
Self {
top: false,
right: true,
bottom: false,
left: true,
}
}
pub fn has_horizontal(&self) -> bool {
self.top || self.bottom
}
pub fn has_vertical(&self) -> bool {
self.left || self.right
}
}
impl Default for BorderSides {
fn default() -> Self {
Self::all()
}
}
impl Border {
pub const fn chars(self) -> BorderChars {
match self {
Self::Single => BorderChars {
tl: '┌',
tr: '┐',
bl: '└',
br: '┘',
h: '─',
v: '│',
},
Self::Double => BorderChars {
tl: '╔',
tr: '╗',
bl: '╚',
br: '╝',
h: '═',
v: '║',
},
Self::Rounded => BorderChars {
tl: '╭',
tr: '╮',
bl: '╰',
br: '╯',
h: '─',
v: '│',
},
Self::Thick => BorderChars {
tl: '┏',
tr: '┓',
bl: '┗',
br: '┛',
h: '━',
v: '┃',
},
Self::Dashed => BorderChars {
tl: '┌',
tr: '┐',
bl: '└',
br: '┘',
h: '┄',
v: '┆',
},
Self::DashedThick => BorderChars {
tl: '┏',
tr: '┓',
bl: '┗',
br: '┛',
h: '┅',
v: '┇',
},
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Padding {
pub top: u32,
pub right: u32,
pub bottom: u32,
pub left: u32,
}
impl Padding {
pub const fn all(v: u32) -> Self {
Self::new(v, v, v, v)
}
pub const fn xy(x: u32, y: u32) -> Self {
Self::new(y, x, y, x)
}
pub const fn new(top: u32, right: u32, bottom: u32, left: u32) -> Self {
Self {
top,
right,
bottom,
left,
}
}
pub const fn horizontal(self) -> u32 {
self.left + self.right
}
pub const fn vertical(self) -> u32 {
self.top + self.bottom
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Margin {
pub top: u32,
pub right: u32,
pub bottom: u32,
pub left: u32,
}
impl Margin {
pub const fn all(v: u32) -> Self {
Self::new(v, v, v, v)
}
pub const fn xy(x: u32, y: u32) -> Self {
Self::new(y, x, y, x)
}
pub const fn new(top: u32, right: u32, bottom: u32, left: u32) -> Self {
Self {
top,
right,
bottom,
left,
}
}
pub const fn horizontal(self) -> u32 {
self.left + self.right
}
pub const fn vertical(self) -> u32 {
self.top + self.bottom
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[non_exhaustive]
pub enum WidthSpec {
Auto,
Fixed(u32),
Pct(u8),
Ratio(u16, u16),
MinMax {
min: u32,
max: u32,
},
}
impl Default for WidthSpec {
#[inline]
fn default() -> Self {
Self::Auto
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[non_exhaustive]
pub enum HeightSpec {
Auto,
Fixed(u32),
Pct(u8),
Ratio(u16, u16),
MinMax {
min: u32,
max: u32,
},
}
impl Default for HeightSpec {
#[inline]
fn default() -> Self {
Self::Auto
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[must_use = "configure constraints using the returned value"]
pub struct Constraints {
pub width: WidthSpec,
pub height: HeightSpec,
}
const _ASSERT_CONSTRAINTS_SIZE: () = assert!(
std::mem::size_of::<Constraints>() == 24,
"Constraints must be 24 bytes"
);
impl Constraints {
pub const fn min_w(mut self, min_width: u32) -> Self {
let max = match self.width {
WidthSpec::MinMax { max, .. } => max,
WidthSpec::Fixed(v) => v,
_ => u32::MAX,
};
self.width = WidthSpec::MinMax {
min: min_width,
max,
};
self
}
pub const fn max_w(mut self, max_width: u32) -> Self {
let min = match self.width {
WidthSpec::MinMax { min, .. } => min,
WidthSpec::Fixed(v) => v,
_ => 0,
};
self.width = WidthSpec::MinMax {
min,
max: max_width,
};
self
}
pub const fn min_h(mut self, min_height: u32) -> Self {
let max = match self.height {
HeightSpec::MinMax { max, .. } => max,
HeightSpec::Fixed(v) => v,
_ => u32::MAX,
};
self.height = HeightSpec::MinMax {
min: min_height,
max,
};
self
}
pub const fn max_h(mut self, max_height: u32) -> Self {
let min = match self.height {
HeightSpec::MinMax { min, .. } => min,
HeightSpec::Fixed(v) => v,
_ => 0,
};
self.height = HeightSpec::MinMax {
min,
max: max_height,
};
self
}
pub const fn w_minmax(mut self, min: u32, max: u32) -> Self {
self.width = WidthSpec::MinMax { min, max };
self
}
pub const fn h_minmax(mut self, min: u32, max: u32) -> Self {
self.height = HeightSpec::MinMax { min, max };
self
}
pub const fn w(mut self, width: u32) -> Self {
self.width = WidthSpec::Fixed(width);
self
}
pub const fn h(mut self, height: u32) -> Self {
self.height = HeightSpec::Fixed(height);
self
}
pub const fn w_pct(mut self, pct: u8) -> Self {
self.width = WidthSpec::Pct(pct);
self
}
pub const fn h_pct(mut self, pct: u8) -> Self {
self.height = HeightSpec::Pct(pct);
self
}
pub const fn w_ratio(mut self, num: u16, den: u16) -> Self {
self.width = WidthSpec::Ratio(num, den);
self
}
pub const fn h_ratio(mut self, num: u16, den: u16) -> Self {
self.height = HeightSpec::Ratio(num, den);
self
}
pub const fn min_width(&self) -> Option<u32> {
match self.width {
WidthSpec::Fixed(v) => Some(v),
WidthSpec::MinMax { min, .. } if min > 0 => Some(min),
_ => None,
}
}
pub const fn max_width(&self) -> Option<u32> {
match self.width {
WidthSpec::Fixed(v) => Some(v),
WidthSpec::MinMax { max, .. } if max < u32::MAX => Some(max),
_ => None,
}
}
pub const fn min_height(&self) -> Option<u32> {
match self.height {
HeightSpec::Fixed(v) => Some(v),
HeightSpec::MinMax { min, .. } if min > 0 => Some(min),
_ => None,
}
}
pub const fn max_height(&self) -> Option<u32> {
match self.height {
HeightSpec::Fixed(v) => Some(v),
HeightSpec::MinMax { max, .. } if max < u32::MAX => Some(max),
_ => None,
}
}
pub const fn width_pct(&self) -> Option<u8> {
match self.width {
WidthSpec::Pct(p) => Some(p),
_ => None,
}
}
pub const fn height_pct(&self) -> Option<u8> {
match self.height {
HeightSpec::Pct(p) => Some(p),
_ => None,
}
}
pub fn set_min_width(&mut self, value: Option<u32>) {
let max = match self.width {
WidthSpec::MinMax { max, .. } => max,
WidthSpec::Fixed(v) => v,
_ => u32::MAX,
};
let min = value.unwrap_or(0);
self.width = if min == 0 && max == u32::MAX {
WidthSpec::Auto
} else {
WidthSpec::MinMax { min, max }
};
}
pub fn set_max_width(&mut self, value: Option<u32>) {
let min = match self.width {
WidthSpec::MinMax { min, .. } => min,
WidthSpec::Fixed(v) => v,
_ => 0,
};
let max = value.unwrap_or(u32::MAX);
self.width = if min == 0 && max == u32::MAX {
WidthSpec::Auto
} else {
WidthSpec::MinMax { min, max }
};
}
pub fn set_min_height(&mut self, value: Option<u32>) {
let max = match self.height {
HeightSpec::MinMax { max, .. } => max,
HeightSpec::Fixed(v) => v,
_ => u32::MAX,
};
let min = value.unwrap_or(0);
self.height = if min == 0 && max == u32::MAX {
HeightSpec::Auto
} else {
HeightSpec::MinMax { min, max }
};
}
pub fn set_max_height(&mut self, value: Option<u32>) {
let min = match self.height {
HeightSpec::MinMax { min, .. } => min,
HeightSpec::Fixed(v) => v,
_ => 0,
};
let max = value.unwrap_or(u32::MAX);
self.height = if min == 0 && max == u32::MAX {
HeightSpec::Auto
} else {
HeightSpec::MinMax { min, max }
};
}
pub fn set_width_pct(&mut self, value: Option<u8>) {
self.width = match value {
Some(p) => WidthSpec::Pct(p),
None => WidthSpec::Auto,
};
}
pub fn set_height_pct(&mut self, value: Option<u8>) {
self.height = match value {
Some(p) => HeightSpec::Pct(p),
None => HeightSpec::Auto,
};
}
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Align {
#[default]
Start,
Center,
End,
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Justify {
#[default]
Start,
Center,
End,
SpaceBetween,
SpaceAround,
SpaceEvenly,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(transparent))]
pub struct Modifiers(pub u8);
impl Modifiers {
pub const NONE: Self = Self(0);
pub const BOLD: Self = Self(1 << 0);
pub const DIM: Self = Self(1 << 1);
pub const ITALIC: Self = Self(1 << 2);
pub const UNDERLINE: Self = Self(1 << 3);
pub const REVERSED: Self = Self(1 << 4);
pub const STRIKETHROUGH: Self = Self(1 << 5);
#[inline]
pub fn contains(self, other: Self) -> bool {
(self.0 & other.0) == other.0
}
#[inline]
pub fn insert(&mut self, other: Self) {
self.0 |= other.0;
}
#[inline]
pub fn remove(&mut self, other: Self) {
self.0 &= !other.0;
}
#[inline]
pub fn is_empty(self) -> bool {
self.0 == 0
}
}
impl std::ops::BitOr for Modifiers {
type Output = Self;
#[inline]
fn bitor(self, rhs: Self) -> Self {
Self(self.0 | rhs.0)
}
}
impl std::ops::BitOrAssign for Modifiers {
#[inline]
fn bitor_assign(&mut self, rhs: Self) {
self.0 |= rhs.0;
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[must_use = "build and pass the returned Style value"]
pub struct Style {
pub fg: Option<Color>,
pub bg: Option<Color>,
pub modifiers: Modifiers,
}
impl Style {
pub const fn new() -> Self {
Self {
fg: None,
bg: None,
modifiers: Modifiers::NONE,
}
}
pub const fn fg(mut self, color: Color) -> Self {
self.fg = Some(color);
self
}
pub const fn bg(mut self, color: Color) -> Self {
self.bg = Some(color);
self
}
pub fn bold(mut self) -> Self {
self.modifiers |= Modifiers::BOLD;
self
}
pub fn dim(mut self) -> Self {
self.modifiers |= Modifiers::DIM;
self
}
pub fn italic(mut self) -> Self {
self.modifiers |= Modifiers::ITALIC;
self
}
pub fn underline(mut self) -> Self {
self.modifiers |= Modifiers::UNDERLINE;
self
}
pub fn reversed(mut self) -> Self {
self.modifiers |= Modifiers::REVERSED;
self
}
pub fn strikethrough(mut self) -> Self {
self.modifiers |= Modifiers::STRIKETHROUGH;
self
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct ContainerStyle {
pub border: Option<Border>,
pub border_sides: Option<BorderSides>,
pub border_style: Option<Style>,
pub bg: Option<Color>,
pub text_color: Option<Color>,
pub dark_bg: Option<Color>,
pub dark_border_style: Option<Style>,
pub padding: Option<Padding>,
pub margin: Option<Margin>,
pub gap: Option<u32>,
pub row_gap: Option<u32>,
pub col_gap: Option<u32>,
pub grow: Option<u16>,
pub align: Option<Align>,
pub align_self: Option<Align>,
pub justify: Option<Justify>,
pub w: Option<u32>,
pub h: Option<u32>,
pub min_w: Option<u32>,
pub max_w: Option<u32>,
pub min_h: Option<u32>,
pub max_h: Option<u32>,
pub w_pct: Option<u8>,
pub h_pct: Option<u8>,
pub theme_bg: Option<ThemeColor>,
pub theme_text_color: Option<ThemeColor>,
pub theme_border_fg: Option<ThemeColor>,
pub extends: Option<&'static ContainerStyle>,
}
impl ContainerStyle {
pub const fn new() -> Self {
Self {
border: None,
border_sides: None,
border_style: None,
bg: None,
text_color: None,
dark_bg: None,
dark_border_style: None,
padding: None,
margin: None,
gap: None,
row_gap: None,
col_gap: None,
grow: None,
align: None,
align_self: None,
justify: None,
w: None,
h: None,
min_w: None,
max_w: None,
min_h: None,
max_h: None,
w_pct: None,
h_pct: None,
theme_bg: None,
theme_text_color: None,
theme_border_fg: None,
extends: None,
}
}
pub const fn extending(base: &'static ContainerStyle) -> Self {
let mut s = Self::new();
s.extends = Some(base);
s
}
pub const fn border(mut self, border: Border) -> Self {
self.border = Some(border);
self
}
pub const fn border_sides(mut self, sides: BorderSides) -> Self {
self.border_sides = Some(sides);
self
}
pub const fn bg(mut self, color: Color) -> Self {
self.bg = Some(color);
self
}
pub const fn text_color(mut self, color: Color) -> Self {
self.text_color = Some(color);
self
}
pub const fn dark_bg(mut self, color: Color) -> Self {
self.dark_bg = Some(color);
self
}
pub const fn p(mut self, value: u32) -> Self {
self.padding = Some(Padding {
top: value,
bottom: value,
left: value,
right: value,
});
self
}
pub const fn px(mut self, value: u32) -> Self {
let p = match self.padding {
Some(p) => Padding {
left: value,
right: value,
..p
},
None => Padding {
top: 0,
bottom: 0,
left: value,
right: value,
},
};
self.padding = Some(p);
self
}
pub const fn py(mut self, value: u32) -> Self {
let p = match self.padding {
Some(p) => Padding {
top: value,
bottom: value,
..p
},
None => Padding {
top: value,
bottom: value,
left: 0,
right: 0,
},
};
self.padding = Some(p);
self
}
pub const fn m(mut self, value: u32) -> Self {
self.margin = Some(Margin {
top: value,
bottom: value,
left: value,
right: value,
});
self
}
pub const fn mx(mut self, value: u32) -> Self {
let m = match self.margin {
Some(m) => Margin {
left: value,
right: value,
..m
},
None => Margin {
top: 0,
bottom: 0,
left: value,
right: value,
},
};
self.margin = Some(m);
self
}
pub const fn my(mut self, value: u32) -> Self {
let m = match self.margin {
Some(m) => Margin {
top: value,
bottom: value,
..m
},
None => Margin {
top: value,
bottom: value,
left: 0,
right: 0,
},
};
self.margin = Some(m);
self
}
pub const fn gap(mut self, value: u32) -> Self {
self.gap = Some(value);
self
}
pub const fn row_gap(mut self, value: u32) -> Self {
self.row_gap = Some(value);
self
}
pub const fn col_gap(mut self, value: u32) -> Self {
self.col_gap = Some(value);
self
}
pub const fn grow(mut self, value: u16) -> Self {
self.grow = Some(value);
self
}
pub const fn w(mut self, value: u32) -> Self {
self.w = Some(value);
self
}
pub const fn h(mut self, value: u32) -> Self {
self.h = Some(value);
self
}
pub const fn min_w(mut self, value: u32) -> Self {
self.min_w = Some(value);
self
}
pub const fn max_w(mut self, value: u32) -> Self {
self.max_w = Some(value);
self
}
pub const fn align(mut self, value: Align) -> Self {
self.align = Some(value);
self
}
pub const fn align_self(mut self, value: Align) -> Self {
self.align_self = Some(value);
self
}
pub const fn justify(mut self, value: Justify) -> Self {
self.justify = Some(value);
self
}
pub const fn min_h(mut self, value: u32) -> Self {
self.min_h = Some(value);
self
}
pub const fn max_h(mut self, value: u32) -> Self {
self.max_h = Some(value);
self
}
pub const fn w_pct(mut self, value: u8) -> Self {
self.w_pct = Some(value);
self
}
pub const fn h_pct(mut self, value: u8) -> Self {
self.h_pct = Some(value);
self
}
pub const fn theme_bg(mut self, color: ThemeColor) -> Self {
self.theme_bg = Some(color);
self
}
pub const fn theme_text_color(mut self, color: ThemeColor) -> Self {
self.theme_text_color = Some(color);
self
}
pub const fn theme_border_fg(mut self, color: ThemeColor) -> Self {
self.theme_border_fg = Some(color);
self
}
}
#[derive(Debug, Clone, Copy, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct WidgetColors {
pub fg: Option<Color>,
pub bg: Option<Color>,
pub border: Option<Color>,
pub accent: Option<Color>,
pub theme_fg: Option<ThemeColor>,
pub theme_bg: Option<ThemeColor>,
pub theme_border: Option<ThemeColor>,
pub theme_accent: Option<ThemeColor>,
}
impl WidgetColors {
pub const fn new() -> Self {
Self {
fg: None,
bg: None,
border: None,
accent: None,
theme_fg: None,
theme_bg: None,
theme_border: None,
theme_accent: None,
}
}
pub const fn fg(mut self, color: Color) -> Self {
self.fg = Some(color);
self
}
pub const fn bg(mut self, color: Color) -> Self {
self.bg = Some(color);
self
}
pub const fn border(mut self, color: Color) -> Self {
self.border = Some(color);
self
}
pub const fn accent(mut self, color: Color) -> Self {
self.accent = Some(color);
self
}
pub const fn theme_fg(mut self, color: ThemeColor) -> Self {
self.theme_fg = Some(color);
self
}
pub const fn theme_bg(mut self, color: ThemeColor) -> Self {
self.theme_bg = Some(color);
self
}
pub const fn theme_border(mut self, color: ThemeColor) -> Self {
self.theme_border = Some(color);
self
}
pub const fn theme_accent(mut self, color: ThemeColor) -> Self {
self.theme_accent = Some(color);
self
}
pub fn resolve_fg(&self, theme: &Theme, fallback: Color) -> Color {
self.theme_fg
.map(|tc| theme.resolve(tc))
.or(self.fg)
.unwrap_or(fallback)
}
pub fn resolve_bg(&self, theme: &Theme, fallback: Color) -> Color {
self.theme_bg
.map(|tc| theme.resolve(tc))
.or(self.bg)
.unwrap_or(fallback)
}
pub fn resolve_border(&self, theme: &Theme, fallback: Color) -> Color {
self.theme_border
.map(|tc| theme.resolve(tc))
.or(self.border)
.unwrap_or(fallback)
}
pub fn resolve_accent(&self, theme: &Theme, fallback: Color) -> Color {
self.theme_accent
.map(|tc| theme.resolve(tc))
.or(self.accent)
.unwrap_or(fallback)
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct WidgetTheme {
pub button: WidgetColors,
pub table: WidgetColors,
pub list: WidgetColors,
pub tabs: WidgetColors,
pub select: WidgetColors,
pub radio: WidgetColors,
pub checkbox: WidgetColors,
pub toggle: WidgetColors,
pub text_input: WidgetColors,
}
impl WidgetTheme {
pub const fn new() -> Self {
Self {
button: WidgetColors::new(),
table: WidgetColors::new(),
list: WidgetColors::new(),
tabs: WidgetColors::new(),
select: WidgetColors::new(),
radio: WidgetColors::new(),
checkbox: WidgetColors::new(),
toggle: WidgetColors::new(),
text_input: WidgetColors::new(),
}
}
pub const fn button(mut self, colors: WidgetColors) -> Self {
self.button = colors;
self
}
pub const fn table(mut self, colors: WidgetColors) -> Self {
self.table = colors;
self
}
pub const fn list(mut self, colors: WidgetColors) -> Self {
self.list = colors;
self
}
pub const fn tabs(mut self, colors: WidgetColors) -> Self {
self.tabs = colors;
self
}
pub const fn select(mut self, colors: WidgetColors) -> Self {
self.select = colors;
self
}
pub const fn radio(mut self, colors: WidgetColors) -> Self {
self.radio = colors;
self
}
pub const fn checkbox(mut self, colors: WidgetColors) -> Self {
self.checkbox = colors;
self
}
pub const fn toggle(mut self, colors: WidgetColors) -> Self {
self.toggle = colors;
self
}
pub const fn text_input(mut self, colors: WidgetColors) -> Self {
self.text_input = colors;
self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn style_new_is_default() {
let style = Style::new();
assert_eq!(style.fg, None);
assert_eq!(style.bg, None);
assert_eq!(style.modifiers, Modifiers::NONE);
assert_eq!(style, Style::default());
}
#[test]
fn style_bold_and_fg_set_expected_fields() {
let style = Style::new().bold().fg(Color::Red);
assert_eq!(style.fg, Some(Color::Red));
assert_eq!(style.bg, None);
assert!(style.modifiers.contains(Modifiers::BOLD));
}
#[test]
fn style_multiple_modifiers_accumulate() {
let style = Style::new().italic().underline().dim();
assert!(style.modifiers.contains(Modifiers::ITALIC));
assert!(style.modifiers.contains(Modifiers::UNDERLINE));
assert!(style.modifiers.contains(Modifiers::DIM));
}
#[test]
fn style_repeated_fg_overrides_previous_color() {
let style = Style::new().fg(Color::Blue).fg(Color::Green);
assert_eq!(style.fg, Some(Color::Green));
}
#[test]
fn style_repeated_bg_overrides_previous_color() {
let style = Style::new().bg(Color::Blue).bg(Color::Green);
assert_eq!(style.bg, Some(Color::Green));
}
#[test]
fn style_override_preserves_existing_modifiers() {
let style = Style::new().bold().fg(Color::Red).fg(Color::Yellow);
assert_eq!(style.fg, Some(Color::Yellow));
assert!(style.modifiers.contains(Modifiers::BOLD));
}
#[test]
fn padding_all_sets_all_sides() {
let p = Padding::all(3);
assert_eq!(p.top, 3);
assert_eq!(p.right, 3);
assert_eq!(p.bottom, 3);
assert_eq!(p.left, 3);
}
#[test]
fn padding_xy_sets_axis_values() {
let p = Padding::xy(4, 2);
assert_eq!(p.top, 2);
assert_eq!(p.bottom, 2);
assert_eq!(p.left, 4);
assert_eq!(p.right, 4);
}
#[test]
fn padding_new_and_totals_are_correct() {
let p = Padding::new(1, 2, 3, 4);
assert_eq!(p.top, 1);
assert_eq!(p.right, 2);
assert_eq!(p.bottom, 3);
assert_eq!(p.left, 4);
assert_eq!(p.horizontal(), 6);
assert_eq!(p.vertical(), 4);
}
#[test]
fn margin_all_and_xy_are_correct() {
let all = Margin::all(5);
assert_eq!(all, Margin::new(5, 5, 5, 5));
let xy = Margin::xy(7, 1);
assert_eq!(xy.top, 1);
assert_eq!(xy.bottom, 1);
assert_eq!(xy.left, 7);
assert_eq!(xy.right, 7);
}
#[test]
fn margin_new_and_totals_are_correct() {
let m = Margin::new(2, 4, 6, 8);
assert_eq!(m.horizontal(), 12);
assert_eq!(m.vertical(), 8);
}
#[test]
fn constraints_min_max_builder_sets_values() {
let c = Constraints::default()
.min_w(10)
.max_w(40)
.min_h(5)
.max_h(20);
assert_eq!(c.min_width(), Some(10));
assert_eq!(c.max_width(), Some(40));
assert_eq!(c.min_height(), Some(5));
assert_eq!(c.max_height(), Some(20));
assert_eq!(c.width, WidthSpec::MinMax { min: 10, max: 40 });
}
#[test]
fn constraints_percentage_builder_sets_values() {
let c = Constraints::default().w_pct(50).h_pct(80);
assert_eq!(c.width_pct(), Some(50));
assert_eq!(c.height_pct(), Some(80));
assert_eq!(c.width, WidthSpec::Pct(50));
assert_eq!(c.height, HeightSpec::Pct(80));
}
#[test]
fn constraints_default_is_auto() {
let c = Constraints::default();
assert_eq!(c.width, WidthSpec::Auto);
assert_eq!(c.height, HeightSpec::Auto);
}
#[test]
fn constraints_fixed_w_h() {
let c = Constraints::default().w(20).h(10);
assert_eq!(c.width, WidthSpec::Fixed(20));
assert_eq!(c.height, HeightSpec::Fixed(10));
assert_eq!(c.min_width(), Some(20));
assert_eq!(c.max_width(), Some(20));
}
#[test]
fn constraints_size_24_bytes() {
assert_eq!(std::mem::size_of::<Constraints>(), 24);
}
#[test]
fn constraints_set_min_width_promotes_to_minmax() {
let mut c = Constraints::default();
c.set_min_width(Some(10));
assert_eq!(
c.width,
WidthSpec::MinMax {
min: 10,
max: u32::MAX,
}
);
c.set_max_width(Some(40));
assert_eq!(c.width, WidthSpec::MinMax { min: 10, max: 40 });
}
#[test]
fn constraints_w_ratio_builder() {
let c = Constraints::default().w_ratio(1, 3);
assert_eq!(c.width, WidthSpec::Ratio(1, 3));
}
#[test]
fn border_sides_all_has_both_axes() {
let sides = BorderSides::all();
assert!(sides.top && sides.right && sides.bottom && sides.left);
assert!(sides.has_horizontal());
assert!(sides.has_vertical());
}
#[test]
fn border_sides_none_has_no_axes() {
let sides = BorderSides::none();
assert!(!sides.top && !sides.right && !sides.bottom && !sides.left);
assert!(!sides.has_horizontal());
assert!(!sides.has_vertical());
}
#[test]
fn border_sides_horizontal_only() {
let sides = BorderSides::horizontal();
assert!(sides.top);
assert!(sides.bottom);
assert!(!sides.left);
assert!(!sides.right);
assert!(sides.has_horizontal());
assert!(!sides.has_vertical());
}
#[test]
fn border_sides_vertical_only() {
let sides = BorderSides::vertical();
assert!(!sides.top);
assert!(!sides.bottom);
assert!(sides.left);
assert!(sides.right);
assert!(!sides.has_horizontal());
assert!(sides.has_vertical());
}
#[test]
fn container_style_new_is_empty() {
let s = ContainerStyle::new();
assert_eq!(s.border, None);
assert_eq!(s.bg, None);
assert_eq!(s.padding, None);
assert_eq!(s.margin, None);
assert_eq!(s.gap, None);
assert_eq!(s.align, None);
assert_eq!(s.justify, None);
}
#[test]
fn container_style_const_construction_and_fields() {
const CARD: ContainerStyle = ContainerStyle::new()
.border(Border::Rounded)
.border_sides(BorderSides::horizontal())
.p(2)
.m(1)
.gap(3)
.align(Align::Center)
.justify(Justify::SpaceBetween)
.w(60)
.h(20);
assert_eq!(CARD.border, Some(Border::Rounded));
assert_eq!(CARD.border_sides, Some(BorderSides::horizontal()));
assert_eq!(CARD.padding, Some(Padding::all(2)));
assert_eq!(CARD.margin, Some(Margin::all(1)));
assert_eq!(CARD.gap, Some(3));
assert_eq!(CARD.align, Some(Align::Center));
assert_eq!(CARD.justify, Some(Justify::SpaceBetween));
assert_eq!(CARD.w, Some(60));
assert_eq!(CARD.h, Some(20));
}
#[test]
fn widget_colors_new_is_empty() {
let colors = WidgetColors::new();
assert_eq!(colors.fg, None);
assert_eq!(colors.bg, None);
assert_eq!(colors.border, None);
assert_eq!(colors.accent, None);
let defaults = WidgetColors::default();
assert_eq!(defaults.fg, None);
assert_eq!(defaults.bg, None);
assert_eq!(defaults.border, None);
assert_eq!(defaults.accent, None);
}
#[test]
fn widget_colors_builder_sets_all_fields() {
let colors = WidgetColors::new()
.fg(Color::White)
.bg(Color::Black)
.border(Color::Cyan)
.accent(Color::Yellow);
assert_eq!(colors.fg, Some(Color::White));
assert_eq!(colors.bg, Some(Color::Black));
assert_eq!(colors.border, Some(Color::Cyan));
assert_eq!(colors.accent, Some(Color::Yellow));
}
#[test]
fn align_default_is_start() {
assert_eq!(Align::default(), Align::Start);
}
#[test]
fn justify_default_is_start() {
assert_eq!(Justify::default(), Justify::Start);
}
#[test]
fn align_and_justify_variants_are_distinct() {
assert_ne!(Align::Start, Align::Center);
assert_ne!(Align::Center, Align::End);
assert_ne!(Justify::Start, Justify::Center);
assert_ne!(Justify::Center, Justify::End);
assert_ne!(Justify::SpaceBetween, Justify::SpaceAround);
assert_ne!(Justify::SpaceAround, Justify::SpaceEvenly);
}
}