use crate::bounds::BoundingBox;
use crate::style::{FillStyle, FontStyle, MarkerShape, StrokeStyle};
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum MarkKey {
Index(u64),
Name(String),
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum Interpolation {
#[default]
Linear,
StepBefore,
StepAfter,
Monotone,
}
#[derive(Clone, Debug)]
pub struct LineMark {
pub points: Vec<[f32; 2]>,
pub stroke: StrokeStyle,
pub interpolation: Interpolation,
}
#[derive(Clone, Debug)]
pub struct RectMark {
pub bounds: BoundingBox,
pub fill: FillStyle,
pub stroke: StrokeStyle,
pub corner_radius: f32,
}
#[derive(Clone, Debug)]
pub struct PointMark {
pub center: [f32; 2],
pub size: f32,
pub shape: MarkerShape,
pub fill: FillStyle,
pub stroke: StrokeStyle,
}
#[derive(Clone, Debug)]
pub struct AreaMark {
pub upper: Vec<[f32; 2]>,
pub lower: Vec<[f32; 2]>,
pub fill: FillStyle,
pub stroke: StrokeStyle,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum TextAnchor {
#[default]
Start,
Middle,
End,
}
#[derive(Clone, Debug)]
pub struct TextMark {
pub position: [f32; 2],
pub text: String,
pub font: FontStyle,
pub fill: FillStyle,
pub angle: f32,
pub anchor: TextAnchor,
}
#[derive(Clone, Debug)]
pub struct ArcMark {
pub center: [f32; 2],
pub inner_radius: f32,
pub outer_radius: f32,
pub start_angle: f32,
pub end_angle: f32,
pub fill: FillStyle,
pub stroke: StrokeStyle,
}
#[derive(Clone, Debug)]
pub struct RuleMark {
pub segments: Vec<([f32; 2], [f32; 2])>,
pub stroke: StrokeStyle,
}
#[derive(Clone, Debug)]
pub struct PathMark {
pub commands: Vec<PathCommand>,
pub fill: FillStyle,
pub stroke: StrokeStyle,
}
#[derive(Clone, Copy, Debug)]
pub enum PathCommand {
MoveTo([f32; 2]),
LineTo([f32; 2]),
CubicTo([f32; 2], [f32; 2], [f32; 2]),
QuadTo([f32; 2], [f32; 2]),
Close,
}
#[derive(Clone, Debug)]
pub struct ImageMark {
pub bounds: BoundingBox,
pub data: Vec<u8>,
pub width: u32,
pub height: u32,
}
pub enum Mark {
Line(LineMark),
Rect(RectMark),
Point(PointMark),
Area(AreaMark),
Text(TextMark),
Arc(ArcMark),
Rule(RuleMark),
Path(PathMark),
Image(ImageMark),
}
#[derive(Clone, Debug)]
pub enum BatchAttr<T> {
Uniform(T),
Varying(Vec<T>),
}
impl<T: Clone> BatchAttr<T> {
pub fn get(&self, index: usize) -> &T {
match self {
Self::Uniform(v) => v,
Self::Varying(v) => {
debug_assert!(
index < v.len(),
"BatchAttr index {index} out of bounds (len {})",
v.len()
);
&v[index]
}
}
}
pub fn len(&self) -> usize {
match self {
Self::Uniform(_) => 1,
Self::Varying(v) => v.len(),
}
}
pub fn is_empty(&self) -> bool {
match self {
Self::Uniform(_) => false,
Self::Varying(v) => v.is_empty(),
}
}
}
pub enum MarkBatch {
Points {
positions: Vec<[f32; 2]>,
sizes: BatchAttr<f32>,
fills: BatchAttr<FillStyle>,
shape: MarkerShape,
strokes: BatchAttr<StrokeStyle>,
},
Rules {
segments: Vec<([f32; 2], [f32; 2])>,
stroke: StrokeStyle,
},
Rects {
rects: Vec<BoundingBox>,
fills: BatchAttr<FillStyle>,
strokes: BatchAttr<StrokeStyle>,
corner_radius: f32,
},
}
impl MarkBatch {
pub fn points(
positions: Vec<[f32; 2]>,
sizes: BatchAttr<f32>,
fills: BatchAttr<FillStyle>,
shape: MarkerShape,
strokes: BatchAttr<StrokeStyle>,
) -> Result<Self, String> {
let batch = Self::Points {
positions,
sizes,
fills,
shape,
strokes,
};
batch.validate()?;
Ok(batch)
}
pub fn rects(
rects: Vec<BoundingBox>,
fills: BatchAttr<FillStyle>,
strokes: BatchAttr<StrokeStyle>,
corner_radius: f32,
) -> Result<Self, String> {
let batch = Self::Rects {
rects,
fills,
strokes,
corner_radius,
};
batch.validate()?;
Ok(batch)
}
pub fn validate(&self) -> Result<(), String> {
match self {
Self::Points {
positions,
sizes,
fills,
strokes,
..
} => {
let n = positions.len();
check_varying_len("sizes", sizes, n)?;
check_varying_len("fills", fills, n)?;
check_varying_len("strokes", strokes, n)?;
}
Self::Rects {
rects,
fills,
strokes,
..
} => {
let n = rects.len();
check_varying_len("fills", fills, n)?;
check_varying_len("strokes", strokes, n)?;
}
Self::Rules { .. } => {}
}
Ok(())
}
}
fn check_varying_len<T: Clone>(
name: &str,
attr: &BatchAttr<T>,
expected: usize,
) -> Result<(), String> {
if let BatchAttr::Varying(v) = attr {
if v.len() != expected {
return Err(format!(
"BatchAttr '{name}' has {} elements, expected {expected}",
v.len()
));
}
}
Ok(())
}