use super::Port;
use super::mark::{Context, Drawing, GEOMETRY, MarkKind, Style, mark, style_methods};
use super::marker::Marker;
use crate::expr::compile::{Compiler, Store};
use crate::gpu::shaders as wgsl;
use crate::{Expr, Kind, Name, Result, Size};
mark!(
Vertices {
position: Option<[Expr; 2]>,
geometry: Option<Name>,
elevation: Expr = "crate::scene::mark::zero",
radius: Option<Size>,
size: Option<[Size; 2]>,
marker: Marker,
ring: Option<Expr>,
ports: Vec<(Name, Port)> as "[reference: string, port: Port][]",
contains: Option<(Name, Name)> as "[table: string, reference: string]",
});
impl Vertices {
pub fn of(data: impl Into<Name>, table: impl Into<Name>) -> Vertices {
Vertices {
style: Style::new(data.into(), table.into()),
position: None,
geometry: None,
elevation: 0.0.into(),
radius: None,
size: None,
marker: Marker::default(),
ring: None,
ports: Vec::new(),
contains: None,
}
}
#[must_use]
pub fn port(mut self, reference: impl Into<Name>, port: impl Into<Port>) -> Vertices {
let reference = reference.into();
self.ports.retain(|(name, _)| *name != reference);
self.ports.push((reference, port.into()));
self
}
#[must_use]
pub fn contains(mut self, table: impl Into<Name>, reference: impl Into<Name>) -> Vertices {
self.contains = Some((table.into(), reference.into()));
self
}
#[must_use]
pub fn geometry(mut self, field: impl Into<Name>) -> Vertices {
self.geometry = Some(field.into());
self
}
pub(crate) fn point_field(&self) -> &str {
self.geometry.as_deref().unwrap_or(GEOMETRY)
}
#[must_use]
pub fn position(mut self, x: impl Into<Expr>, y: impl Into<Expr>) -> Vertices {
self.position = Some([x.into(), y.into()]);
self
}
#[must_use]
pub fn elevation(mut self, z: impl Into<Expr>) -> Vertices {
self.elevation = z.into();
self
}
#[must_use]
pub fn radius(mut self, radius: impl Into<Size>) -> Vertices {
self.radius = Some(radius.into());
self
}
#[must_use]
pub fn size(mut self, width: impl Into<Size>, height: impl Into<Size>) -> Vertices {
self.size = Some([width.into(), height.into()]);
self
}
#[must_use]
pub fn marker(mut self, marker: impl Into<Marker>) -> Vertices {
self.marker = marker.into();
self
}
#[must_use]
pub fn ring(mut self, color: impl Into<Expr>) -> Vertices {
self.ring = Some(color.into());
self
}
pub(crate) fn compile(&self, c: &mut Compiler<'_>, context: Context<'_>) -> Result<Drawing> {
let group = self.contains.is_some();
let inputs = self.marker.inputs();
if inputs.len() > 8 {
return Err(crate::Error::invalid("a marker reads at most eight inputs").at("marker"));
}
let mut flags = 0;
if context.diagram() {
c.check_structure("position", self.position.iter().flatten())?;
c.check_structure("size", self.size.iter().flatten().map(|s| &s.expr))?;
c.check_structure("radius", self.radius.iter().map(|s| &s.expr))?;
c.derived_position(wgsl::VERTEX_POSITION).map_err(|e| e.at("position"))?;
for (k, field) in ["half_x", "half_y"].into_iter().enumerate() {
let store = Store::Size { word: wgsl::VERTEX_HALF + k as u32, units: true, fallback: None };
c.derived(["width", "height"][k], field, store)?;
}
if group {
c.derived("depth", "depth", Store::Number { word: wgsl::VERTEX_DEPTH, fallback: None })?;
}
flags |= wgsl::SIZED;
} else {
let at = wgsl::VERTEX_POSITION;
if context.placed() {
c.check_structure("position", self.position.iter().flatten())?;
c.derived_position(at)
} else {
match &self.position {
Some([x, y]) => c.position(x, y, at),
None if c.kind(self.point_field()) == Some(Kind::Point) => {
let field = self.point_field();
c.position(&Expr::component(field, 0), &Expr::component(field, 1), at)
}
None => c.position(&Expr::missing(), &Expr::missing(), at),
}
}
.map_err(|e| e.at("position"))?;
let half = |word: u32, size: &Size| Store::Size {
word,
units: size.units,
fallback: Some(wgsl::THEME_VERTEX_RADIUS),
};
match (&self.size, &self.radius) {
(Some([w, h]), _) => {
flags |= wgsl::SIZED;
c.channel("width", &(w.expr.clone() * 0.5), half(wgsl::VERTEX_HALF, w))?;
c.channel("height", &(h.expr.clone() * 0.5), half(wgsl::VERTEX_HALF + 1, h))?;
}
(None, Some(radius)) => c.channel("radius", &radius.expr, half(wgsl::VERTEX_HALF, radius))?,
(None, None) => {
let store = Store::Size { word: wgsl::VERTEX_HALF, units: false, fallback: None };
c.theme("radius", wgsl::THEME_VERTEX_RADIUS, store)?;
}
}
c.channel("elevation", &self.elevation, Store::Number { word: wgsl::VERTEX_ELEVATION, fallback: None })?;
}
let fallback = if group { wgsl::THEME_SURFACE } else { wgsl::THEME_VERTEX };
let color = Store::Color { word: wgsl::VERTEX_COLOR, fallback };
match &self.style.color {
Some(expr) => c.channel("color", expr, color)?,
None => c.theme("color", fallback, color)?,
}
let visible = Store::Flag { word: wgsl::VERTEX_FLAGS, bit: wgsl::VISIBLE };
c.channel("visible", &self.style.visible, visible)?;
if let Some(ring) = &self.ring {
flags |= wgsl::STATUS;
c.channel("ring", ring, Store::Color { word: wgsl::VERTEX_STATUS, fallback: wgsl::THEME_CLEAR })?;
}
for (i, input) in inputs.iter().enumerate() {
let store = Store::Number { word: wgsl::VERTEX_WORDS + i as u32, fallback: None };
c.channel(&format!("input{i}"), input, store).map_err(|e| e.at(i).at("marker"))?;
}
let mut ports = Vec::with_capacity(self.ports.len());
for (reference, port) in &self.ports {
let column = c.bits(reference).map_err(|e| e.at(reference).at("port"))?;
ports.push((reference.clone(), port.clone(), column));
}
let mut drawing = Drawing::new(MarkKind::Vertices, wgsl::VERTEX_WORDS);
drawing.size_px = match (&self.size, &self.radius) {
(Some([w, _]), _) => w.px.map(|px| px / 2.0),
(None, Some(radius)) => radius.px,
(None, None) => crate::Size::ANY,
};
drawing.markers = self.marker.markers(0);
if group {
flags |= wgsl::GROUP;
drawing.markers.marker = wgsl::MARKER_SHAPE;
drawing.markers.shape = wgsl::SHAPE_RECTANGLE;
}
drawing.flags = flags;
drawing.extras = inputs.len() as u32;
drawing.ports = ports;
drawing.port_label_size = 0.8 * context.theme.font_size;
Ok(drawing)
}
}
style_methods!(Vertices);