use crate::render::{Color, LineStyle};
#[derive(Debug, Clone, PartialEq)]
pub struct GridStyle {
pub visible: bool,
pub color: Color,
pub line_width: f32,
pub alpha: f32,
pub line_style: LineStyle,
pub minor: bool,
pub minor_line_width: f32,
pub minor_alpha: f32,
}
impl Default for GridStyle {
fn default() -> Self {
Self {
visible: true,
color: Color::from_gray(176), line_width: 0.8,
alpha: 1.0,
line_style: LineStyle::Solid,
minor: false,
minor_line_width: 0.4,
minor_alpha: 0.5,
}
}
}
impl GridStyle {
pub fn new() -> Self {
Self::default()
}
pub fn hidden() -> Self {
Self {
visible: false,
..Default::default()
}
}
pub fn prominent() -> Self {
Self {
visible: true,
color: Color::from_gray(140), line_width: 1.0,
alpha: 1.0,
line_style: LineStyle::Solid,
minor: false,
minor_line_width: 0.5,
minor_alpha: 0.6,
}
}
pub fn visible(mut self, enabled: bool) -> Self {
self.visible = enabled;
self
}
pub fn color(mut self, color: Color) -> Self {
self.color = color;
self
}
pub fn line_width(mut self, width: f32) -> Self {
self.line_width = width.max(0.0);
self
}
pub fn alpha(mut self, alpha: f32) -> Self {
self.alpha = alpha.clamp(0.0, 1.0);
self
}
pub fn line_style(mut self, style: LineStyle) -> Self {
self.line_style = style;
self
}
pub fn minor(mut self, enabled: bool) -> Self {
self.minor = enabled;
self
}
pub fn minor_line_width(mut self, width: f32) -> Self {
self.minor_line_width = width.max(0.0);
self
}
pub fn minor_alpha(mut self, alpha: f32) -> Self {
self.minor_alpha = alpha.clamp(0.0, 1.0);
self
}
pub fn effective_color(&self) -> Color {
self.color.with_alpha(self.alpha)
}
pub fn effective_minor_color(&self) -> Color {
self.color.with_alpha(self.minor_alpha)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn gray_luminance(value: u8) -> f32 {
let c = value as f32 / 255.0;
if c <= 0.03928 {
c / 12.92
} else {
((c + 0.055) / 1.055).powf(2.4)
}
}
fn contrast_on_white(color: Color, alpha: f32) -> f32 {
let composited = (255.0 - alpha * (255.0 - color.r as f32)).round() as u8;
let l = gray_luminance(composited);
1.05 / (l + 0.05)
}
#[test]
fn test_default_grid_is_visible() {
let style = GridStyle::default();
assert!(style.visible);
assert_eq!(style.color.r, 176);
assert_eq!(style.color.g, 176);
assert_eq!(style.color.b, 176);
assert!((style.line_width - 0.8).abs() < 0.001);
assert!((style.alpha - 1.0).abs() < 0.001);
assert!(matches!(style.line_style, LineStyle::Solid));
assert!(!style.minor);
}
#[test]
fn test_default_grid_has_readable_contrast_on_white() {
let style = GridStyle::default();
let major = contrast_on_white(style.color, style.alpha);
assert!(
major > 1.6,
"major grid contrast on white too low: {major}:1"
);
let minor = contrast_on_white(style.color, style.minor_alpha);
assert!(
minor > 1.0,
"minor grid contrast on white too low: {minor}:1"
);
assert!(
minor < major,
"minor grid ({minor}:1) should recede behind major grid ({major}:1)"
);
}
#[test]
fn test_default_minor_grid_is_subordinate() {
let style = GridStyle::default();
assert!(style.minor_line_width < style.line_width);
assert!((style.minor_line_width - style.line_width * 0.5).abs() < 0.05);
assert!(style.minor_alpha < style.alpha);
}
#[test]
fn test_hidden() {
let style = GridStyle::hidden();
assert!(!style.visible);
assert_eq!(style.color, GridStyle::default().color);
assert!((style.alpha - GridStyle::default().alpha).abs() < 0.001);
}
#[test]
fn test_prominent() {
let style = GridStyle::prominent();
let default = GridStyle::default();
assert!(style.visible);
assert!(style.color.r < default.color.r);
assert!(style.line_width > default.line_width);
assert!(
contrast_on_white(style.color, style.alpha)
> contrast_on_white(default.color, default.alpha)
);
}
#[test]
fn test_builder_methods() {
let style = GridStyle::default()
.visible(false)
.color(Color::BLUE)
.line_width(2.0)
.alpha(0.5)
.line_style(LineStyle::Dashed)
.minor(true);
assert!(!style.visible);
assert_eq!(style.color, Color::BLUE);
assert!((style.line_width - 2.0).abs() < 0.001);
assert!((style.alpha - 0.5).abs() < 0.001);
assert!(matches!(style.line_style, LineStyle::Dashed));
assert!(style.minor);
}
#[test]
fn test_effective_color() {
let style = GridStyle::default();
let effective = style.effective_color();
assert_eq!(effective.r, 176);
assert_eq!(effective.g, 176);
assert_eq!(effective.b, 176);
assert_eq!(effective.a, 255); }
#[test]
fn test_effective_minor_color() {
let style = GridStyle::default();
let minor = style.effective_minor_color();
assert_eq!(minor.r, 176);
assert_eq!(minor.a, 127); assert!(minor.a < style.effective_color().a);
}
#[test]
fn test_clamping() {
let style = GridStyle::default()
.alpha(2.0) .line_width(-5.0);
assert!((style.alpha - 1.0).abs() < 0.001);
assert!((style.line_width - 0.0).abs() < 0.001);
}
}