use std::sync::Arc;
use super::{Areas, Edges, Faces, Label, Lines, Vertices};
use crate::expr::compile::{Compiler, Store};
use crate::gpu::shaders as wgsl;
use crate::{Error, Expr, Name, Result, Size, Space};
#[derive(Clone, PartialEq, Debug, serde::Deserialize, schemars::JsonSchema)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
#[non_exhaustive]
pub enum Mark {
Vertices(Vertices),
Edges(Edges),
Faces(Faces),
Lines(Lines),
Areas(Areas),
}
impl From<Vertices> for Mark {
fn from(mark: Vertices) -> Mark {
Mark::Vertices(mark)
}
}
impl From<Edges> for Mark {
fn from(mark: Edges) -> Mark {
Mark::Edges(mark)
}
}
impl From<Faces> for Mark {
fn from(mark: Faces) -> Mark {
Mark::Faces(mark)
}
}
impl From<Lines> for Mark {
fn from(mark: Lines) -> Mark {
Mark::Lines(mark)
}
}
impl From<Areas> for Mark {
fn from(mark: Areas) -> Mark {
Mark::Areas(mark)
}
}
pub(crate) const GEOMETRY: &str = "geometry";
#[derive(Clone, PartialEq, Debug)]
pub(crate) struct Style {
pub(crate) data: Name,
pub(crate) table: Name,
pub(crate) color: Option<Expr>,
pub(crate) visible: Expr,
pub(crate) priority: Option<Expr>,
pub(crate) name: Option<Name>,
pub(crate) interaction: Interaction,
pub(crate) label: Option<Label>,
pub(crate) shadow: bool,
pub(crate) legend: Option<Arc<str>>,
}
#[derive(Clone, PartialEq, Debug)]
pub(crate) struct Stroke {
pub(crate) width: Option<Size>,
pub(crate) dash: Expr,
pub(crate) flow: Expr,
}
impl Default for Stroke {
fn default() -> Stroke {
Stroke { width: None, dash: false.into(), flow: 0.0.into() }
}
}
pub(crate) struct StrokeWords {
pub(crate) width: u32,
pub(crate) flow: u32,
pub(crate) flags: u32,
pub(crate) theme_width: u32,
}
impl Stroke {
pub(crate) fn px(&self) -> [f32; 2] {
self.width.as_ref().map_or(crate::Size::ANY, |size| size.px)
}
pub(crate) fn compile(&self, c: &mut Compiler<'_>, words: &StrokeWords, diagram: bool) -> Result<()> {
c.channel("dash", &self.dash, Store::Flag { word: words.flags, bit: wgsl::DASH })?;
c.channel("flow", &self.flow, Store::Number { word: words.flow, fallback: None })?;
let width =
|units| Store::Size { word: words.width, units: units || diagram, fallback: Some(words.theme_width) };
match &self.width {
Some(size) => c.channel("width", &size.expr, width(size.units)),
None => c.theme("width", words.theme_width, width(false)),
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub(crate) struct Interaction {
pub(crate) pick: Option<bool>,
pub(crate) select: bool,
pub(crate) drag: bool,
}
impl Default for Interaction {
fn default() -> Interaction {
Interaction { pick: None, select: true, drag: false }
}
}
impl Style {
pub(crate) fn new(data: Name, table: Name) -> Style {
Style {
data,
table,
color: None,
visible: true.into(),
priority: None,
name: None,
interaction: Interaction::default(),
label: None,
shadow: false,
legend: None,
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub(crate) enum MarkKind {
Vertices,
Edges,
Faces,
Lines,
Areas,
}
impl MarkKind {
pub(crate) fn primitive(self) -> Primitive {
match self {
MarkKind::Vertices => Primitive::Points,
MarkKind::Edges | MarkKind::Lines => Primitive::Strokes,
MarkKind::Faces | MarkKind::Areas => Primitive::Triangles,
}
}
pub(crate) fn name(self) -> &'static str {
match self {
MarkKind::Vertices => "vertices",
MarkKind::Edges => "edges",
MarkKind::Faces => "faces",
MarkKind::Lines => "lines",
MarkKind::Areas => "areas",
}
}
}
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
pub(crate) struct Key {
pub(crate) kind: MarkKind,
pub(crate) data: Name,
pub(crate) table: Name,
pub(crate) name: Option<Name>,
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub(crate) enum Primitive {
Points,
Strokes,
Triangles,
}
impl Primitive {
pub(crate) fn code(self) -> u32 {
match self {
Primitive::Points => wgsl::POINTS,
Primitive::Strokes => wgsl::STROKES,
Primitive::Triangles => wgsl::TRIANGLES,
}
}
}
#[derive(Clone, PartialEq, Debug, Default)]
pub(crate) struct Markers {
pub(crate) marker: u32,
pub(crate) shape: u32,
pub(crate) corner: f32,
pub(crate) hole: f32,
pub(crate) inputs: u32,
pub(crate) input: u32,
pub(crate) palette: Vec<u32>,
pub(crate) icons: Vec<crate::Image>,
pub(crate) custom: Option<std::sync::Arc<str>>,
}
#[derive(Clone, PartialEq, Debug)]
pub(crate) struct Labeling {
pub(crate) extra: u32,
pub(crate) column: u32,
pub(crate) size: f32,
pub(crate) max_width: Option<f32>,
pub(crate) font: std::sync::Arc<str>,
pub(crate) max: Option<u32>,
}
#[derive(Clone, Copy)]
pub(crate) struct Context<'a> {
pub(crate) theme: &'a super::Theme,
pub(crate) space: &'a Space,
pub(crate) layout: &'a super::Layout,
}
impl Context<'_> {
pub(crate) fn diagram(&self) -> bool {
matches!(self.space, Space::Diagram(_))
}
pub(crate) fn placed(&self) -> bool {
matches!(self.space, Space::Plane(_) | Space::Earth(_)) && !matches!(self.layout, super::Layout::None)
}
}
#[derive(Clone, PartialEq, Debug)]
pub(crate) struct Drawing {
pub(crate) kind: MarkKind,
pub(crate) primitive: Primitive,
pub(crate) path: u32,
pub(crate) stride: u32,
pub(crate) extras: u32,
pub(crate) flags: u32,
pub(crate) markers: Markers,
pub(crate) trace: u32,
pub(crate) geometry: u32,
pub(crate) lane: u32,
pub(crate) label: Option<Labeling>,
pub(crate) ports: Vec<(Name, super::Port, u32)>,
pub(crate) port_label_size: f32,
pub(crate) size_px: [f32; 2],
}
impl Drawing {
pub(crate) fn new(kind: MarkKind, stride: u32) -> Drawing {
let path = match kind {
MarkKind::Edges | MarkKind::Faces => wgsl::PATH_ENDS,
MarkKind::Lines => wgsl::PATH_TRACE,
MarkKind::Areas => wgsl::PATH_GEOMETRY,
MarkKind::Vertices => wgsl::NONE,
};
Drawing {
kind,
primitive: kind.primitive(),
path,
stride,
extras: 0,
flags: 0,
markers: Markers::default(),
trace: wgsl::NONE,
geometry: wgsl::NONE,
lane: wgsl::NONE,
label: None,
ports: Vec::new(),
port_label_size: 0.0,
size_px: crate::Size::ANY,
}
}
}
impl Mark {
pub(crate) fn style(&self) -> &Style {
match self {
Mark::Vertices(m) => &m.style,
Mark::Edges(m) => &m.style,
Mark::Faces(m) => &m.style,
Mark::Lines(m) => &m.style,
Mark::Areas(m) => &m.style,
}
}
fn style_mut(&mut self) -> &mut Style {
match self {
Mark::Vertices(m) => &mut m.style,
Mark::Edges(m) => &mut m.style,
Mark::Faces(m) => &mut m.style,
Mark::Lines(m) => &mut m.style,
Mark::Areas(m) => &mut m.style,
}
}
pub(crate) fn kind(&self) -> MarkKind {
match self {
Mark::Vertices(_) => MarkKind::Vertices,
Mark::Edges(_) => MarkKind::Edges,
Mark::Faces(_) => MarkKind::Faces,
Mark::Lines(_) => MarkKind::Lines,
Mark::Areas(_) => MarkKind::Areas,
}
}
pub(crate) fn picks(&self) -> bool {
self.style().interaction.pick.unwrap_or(match self {
Mark::Vertices(_) | Mark::Edges(_) | Mark::Faces(_) => true,
Mark::Lines(lines) => lines.trace.is_some(),
Mark::Areas(_) => false,
})
}
pub(crate) fn within(&self, i: usize, error: Error) -> Error {
error.at(self.kind().name()).at(i).at("marks")
}
pub(crate) fn absent_from(&self, data: &crate::Data) -> Option<Error> {
let style = self.style();
data.table(&style.table)
.is_none()
.then(|| Error::invalid(format!("dataset '{}' has no table '{}'", style.data, style.table)).at("of"))
}
pub(crate) fn flows(&self) -> bool {
let stroke = match self {
Mark::Edges(m) => &m.stroke,
Mark::Lines(m) => &m.stroke,
Mark::Vertices(_) | Mark::Faces(_) | Mark::Areas(_) => return false,
};
stroke.flow != Stroke::default().flow
}
pub(crate) fn path(&self, space: &Space) -> Option<u32> {
match self {
Mark::Edges(e) if e.poles => Some(wgsl::PATH_POLES),
Mark::Edges(e) if e.joins.is_some() => Some(wgsl::PATH_GEOMETRY),
Mark::Edges(_) if matches!(space, Space::Diagram(_)) => Some(wgsl::PATH_GEOMETRY),
Mark::Edges(_) => Some(wgsl::PATH_ENDS),
Mark::Lines(_) if matches!(space, Space::Plot(_)) => Some(wgsl::PATH_TRACE),
Mark::Lines(_) | Mark::Areas(_) => Some(wgsl::PATH_GEOMETRY),
Mark::Vertices(_) | Mark::Faces(_) => None,
}
}
pub(crate) fn misplaced(&self, diagram: bool) -> Option<(&'static str, &'static str)> {
const ONLY: &str = "only a diagram has ports, groups, nets, and orthogonal routes";
let style = self.style();
if let Mark::Areas(_) = self
&& style.shadow
{
return Some(("shadow", "an area lies on the ground: it casts no shadow"));
}
if diagram {
if style.shadow {
return Some(("shadow", "a diagram is flat: nothing casts shadows"));
}
if let Mark::Edges(e) = self
&& e.poles
{
return Some(("poles", "a diagram is flat: poles stand on planes and the earth"));
}
if let Mark::Edges(e) = self
&& e.joins.is_some()
{
return Some(("joins", "a diagram's nets join vertex ports"));
}
if let Mark::Vertices(v) = self
&& v.elevation != Expr::from(0.0)
{
return Some(("elevation", "a diagram is flat: vertices have no elevation"));
}
return None;
}
match self {
Mark::Vertices(v) if !v.ports.is_empty() => Some(("port", ONLY)),
Mark::Vertices(v) if v.contains.is_some() => Some(("contains", ONLY)),
Mark::Edges(e) if e.ends.is_none() && !e.poles && e.joins.is_none() => Some(("ends", ONLY)),
Mark::Edges(e) if e.route == Some(super::Route::Orthogonal) => Some(("route", ONLY)),
_ => None,
}
}
pub(crate) fn key(&self) -> Key {
let style = self.style();
let name = match self {
Mark::Lines(lines) => style.name.clone().or_else(|| Some(lines.traced().clone())),
_ => style.name.clone(),
};
Key { kind: self.kind(), data: style.data.clone(), table: style.table.clone(), name }
}
pub(crate) fn draws_like(&self, other: &Mark) -> bool {
let mut a = self.clone();
a.style_mut().interaction = other.style().interaction;
a == *other
}
pub(crate) fn compile(&self, compiler: &mut Compiler<'_>, context: Context<'_>) -> Result<Drawing> {
let mut drawing = match self {
Mark::Vertices(m) => m.compile(compiler, context),
Mark::Edges(m) => m.compile(compiler, context),
Mark::Faces(m) => m.compile(compiler),
Mark::Lines(m) => m.compile(compiler, context),
Mark::Areas(m) => m.compile(compiler, context),
}?;
let style = self.style();
if let Some(priority) = &style.priority {
compiler.check_data("priority", priority)?;
}
if let Some(label) = &style.label {
let priority = label.priority.as_ref().or(style.priority.as_ref());
compile_label(label, priority, compiler, context.theme, &mut drawing).map_err(|e| e.at("label"))?;
}
if style.shadow {
drawing.flags |= wgsl::CASTS;
}
Ok(drawing)
}
}
fn compile_label(
label: &Label,
priority: Option<&Expr>,
c: &mut Compiler<'_>,
theme: &super::Theme,
drawing: &mut Drawing,
) -> Result<()> {
let extra = drawing.extras;
let word = |k: u32| drawing.stride + extra + k;
let column = c.text("text", &label.text, word(wgsl::LABEL_CODE))?;
let priority: Expr = priority.cloned().unwrap_or_else(|| 0.0.into()).scale(0.0..=255.0).into();
c.channel("priority", &priority, Store::Number { word: word(wgsl::LABEL_PRIORITY), fallback: None })?;
let color = Store::Color { word: word(wgsl::LABEL_COLOR), fallback: wgsl::THEME_TEXT };
match &label.color {
Some(expr) => c.channel("color", expr, color)?,
None => c.theme("color", wgsl::THEME_TEXT, color)?,
}
drawing.extras += wgsl::LABEL_WORDS;
drawing.flags |= wgsl::LABELED;
drawing.label = Some(Labeling {
extra,
column,
size: label.size.unwrap_or(theme.font_size),
max_width: label.max_width,
font: label.font.clone().unwrap_or_else(|| theme.font.clone()),
max: label.max,
});
Ok(())
}
pub(crate) fn described(lines: &[&str]) -> String {
let prose =
lines.iter().map(|line| line.strip_prefix(' ').unwrap_or(line)).take_while(|line| !line.starts_with("```"));
let paragraphs: Vec<String> =
prose.collect::<Vec<_>>().split(|line| line.is_empty()).map(|p| p.join(" ")).collect();
paragraphs.into_iter().filter(|p| !p.is_empty()).collect::<Vec<_>>().join(
"
",
)
}
macro_rules! mark {
($(#[doc = $line:literal])* $name:ident $(+ $stroke:ident)? {
$($(#[$doc:meta])* $field:ident: $ty:ty $(as $ts:literal)? $(= $default:literal)?),* $(,)?
}) => {
$(#[doc = $line])*
#[derive(Clone, PartialEq, Debug)]
pub struct $name {
pub(crate) style: $crate::scene::mark::Style,
$(pub(crate) $stroke: $crate::scene::mark::Stroke,)?
$($(#[$doc])* pub(crate) $field: $ty,)*
}
$crate::scene::mark::mark!(@form $name $(+ $stroke)? { $($(#[$doc])* $field: $ty $(as $ts)? $(= $default)?),* });
impl<'de> serde::Deserialize<'de> for $name {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<$name, D::Error> {
let form = <Form as serde::Deserialize>::deserialize(deserializer)?;
Ok($name {
style: form.style(),
$($stroke: form.stroke(),)?
$($field: form.$field,)*
})
}
}
impl schemars::JsonSchema for $name {
fn schema_name() -> std::borrow::Cow<'static, str> {
stringify!($name).into()
}
fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
let mut schema = <Form as schemars::JsonSchema>::json_schema(generator);
schema.insert("description".into(), $crate::scene::mark::described(&[$($line),*]).into());
schema
}
}
};
(@form $name:ident { $($(#[$doc:meta])* $field:ident: $ty:ty $(as $ts:literal)? $(= $default:literal)?),* }) => {
$crate::scene::mark::mark!(@struct { $($(#[$doc])* $field: $ty $(as $ts)? $(= $default)?),* } {});
};
(@form $name:ident + stroke { $($(#[$doc:meta])* $field:ident: $ty:ty $(as $ts:literal)? $(= $default:literal)?),* }) => {
$crate::scene::mark::mark!(@struct { $($(#[$doc])* $field: $ty $(as $ts)? $(= $default)?),* } {
width: Option<$crate::Size>,
#[serde(default = "crate::scene::mark::no")]
dash: $crate::Expr,
#[serde(default = "crate::scene::mark::still")]
flow: $crate::Expr,
});
impl Form {
fn stroke(&self) -> $crate::scene::mark::Stroke {
$crate::scene::mark::Stroke { width: self.width.clone(), dash: self.dash.clone(), flow: self.flow.clone() }
}
}
};
(@struct { $($(#[$doc:meta])* $field:ident: $ty:ty $(as $ts:literal)? $(= $default:literal)?),* } { $($stroke:tt)* }) => {
#[derive(serde::Deserialize, schemars::JsonSchema)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
struct Form {
#[schemars(extend("tsType" = "[data: string, table: string]"))]
of: ($crate::Name, $crate::Name),
$($(#[$doc])* $(#[schemars(extend("tsType" = $ts))])? #[serde(default $(= $default)?)] $field: $ty,)*
$($stroke)*
color: Option<$crate::Expr>,
#[serde(default = "crate::scene::mark::yes")]
visible: $crate::Expr,
priority: Option<$crate::Expr>,
name: Option<$crate::Name>,
pick: Option<bool>,
#[serde(default = "crate::scene::mark::on")]
select: bool,
#[serde(default)]
drag: bool,
label: Option<$crate::Label>,
#[serde(default)]
shadow: bool,
legend: Option<std::sync::Arc<str>>,
}
impl Form {
fn style(&self) -> $crate::scene::mark::Style {
$crate::scene::mark::Style {
data: self.of.0.clone(),
table: self.of.1.clone(),
color: self.color.clone(),
visible: self.visible.clone(),
priority: self.priority.clone(),
name: self.name.clone(),
interaction: $crate::scene::mark::Interaction { pick: self.pick, select: self.select, drag: self.drag },
label: self.label.clone(),
shadow: self.shadow,
legend: self.legend.clone(),
}
}
}
};
}
pub(crate) use mark;
pub(crate) fn yes() -> Expr {
true.into()
}
pub(crate) fn no() -> Expr {
false.into()
}
pub(crate) fn still() -> Expr {
0.0.into()
}
pub(crate) fn on() -> bool {
true
}
pub(crate) fn zero() -> Expr {
0.0.into()
}
macro_rules! style_methods {
($mark:ty) => {
impl $mark {
#[must_use]
pub fn color(mut self, color: impl Into<$crate::Expr>) -> Self {
self.style.color = Some(color.into());
self
}
#[must_use]
pub fn visible(mut self, visible: impl Into<$crate::Expr>) -> Self {
self.style.visible = visible.into();
self
}
#[must_use]
pub fn priority(mut self, priority: impl Into<$crate::Expr>) -> Self {
self.style.priority = Some(priority.into());
self
}
#[must_use]
pub fn named(mut self, name: impl Into<$crate::Name>) -> Self {
self.style.name = Some(name.into());
self
}
#[must_use]
pub fn pick(mut self, on: bool) -> Self {
self.style.interaction.pick = Some(on);
self
}
#[must_use]
pub fn select(mut self, on: bool) -> Self {
self.style.interaction.select = on;
self
}
#[must_use]
pub fn drag(mut self, on: bool) -> Self {
self.style.interaction.drag = on;
self
}
#[must_use]
pub fn label(mut self, label: $crate::Label) -> Self {
self.style.label = Some(label);
self
}
#[must_use]
pub fn shadow(mut self, on: bool) -> Self {
self.style.shadow = on;
self
}
#[must_use]
pub fn legend(mut self, title: impl Into<std::sync::Arc<str>>) -> Self {
self.style.legend = Some(title.into());
self
}
}
};
}
macro_rules! stroke_methods {
($mark:ty) => {
impl $mark {
#[must_use]
pub fn width(mut self, width: impl Into<$crate::Size>) -> Self {
self.stroke.width = Some(width.into());
self
}
#[must_use]
pub fn dash(mut self, dash: impl Into<$crate::Expr>) -> Self {
self.stroke.dash = dash.into();
self
}
#[must_use]
pub fn flow(mut self, speed: impl Into<$crate::Expr>) -> Self {
self.stroke.flow = speed.into();
self
}
}
};
}
pub(crate) use {stroke_methods, style_methods};