use super::{ColorV, LineV, MarkerV, TuiW, TuiWidget};
use anyhow::{bail, Context, Result};
use arcstr::ArcStr;
use async_trait::async_trait;
use crossterm::event::Event;
use futures::future::try_join_all;
use graphix_compiler::expr::ExprId;
use graphix_rt::{GXExt, GXHandle, Ref, TRef};
use netidx::publisher::{FromValue, Value};
use ratatui::{
layout::Rect,
style::Color,
widgets::canvas::{
Canvas, Circle, Context as CanvasContext, Line, Points, Rectangle,
},
Frame,
};
use smallvec::SmallVec;
use tokio::try_join;
#[derive(Clone, Copy)]
struct BoundsV([f64; 2]);
impl FromValue for BoundsV {
fn from_value(v: Value) -> Result<Self> {
let [(_, max), (_, min)] = v.cast_to::<[(ArcStr, f64); 2]>()?;
Ok(Self([min, max]))
}
}
#[derive(Clone)]
struct CanvasLineV(Line);
impl FromValue for CanvasLineV {
fn from_value(v: Value) -> Result<Self> {
let [(_, color), (_, x1), (_, x2), (_, y1), (_, y2)] =
v.cast_to::<[(ArcStr, Value); 5]>()?;
let color = color.cast_to::<ColorV>()?.0;
Ok(Self(Line {
x1: x1.cast_to()?,
y1: y1.cast_to()?,
x2: x2.cast_to()?,
y2: y2.cast_to()?,
color,
}))
}
}
#[derive(Clone)]
struct CanvasCircleV(Circle);
impl FromValue for CanvasCircleV {
fn from_value(v: Value) -> Result<Self> {
let [(_, color), (_, radius), (_, x), (_, y)] =
v.cast_to::<[(ArcStr, Value); 4]>()?;
let color = color.cast_to::<ColorV>()?.0;
Ok(Self(Circle {
x: x.cast_to()?,
y: y.cast_to()?,
radius: radius.cast_to()?,
color,
}))
}
}
#[derive(Clone)]
struct CanvasRectangleV(Rectangle);
impl FromValue for CanvasRectangleV {
fn from_value(v: Value) -> Result<Self> {
let [(_, color), (_, height), (_, width), (_, x), (_, y)] =
v.cast_to::<[(ArcStr, Value); 5]>()?;
let color = color.cast_to::<ColorV>()?.0;
Ok(Self(Rectangle {
x: x.cast_to()?,
y: y.cast_to()?,
width: width.cast_to()?,
height: height.cast_to()?,
color,
}))
}
}
#[derive(Clone)]
struct CanvasPointsV {
color: Color,
coords: Vec<(f64, f64)>,
}
impl FromValue for CanvasPointsV {
fn from_value(v: Value) -> Result<Self> {
let [(_, color), (_, coords)] = v.cast_to::<[(ArcStr, Value); 2]>()?;
let color = color.cast_to::<ColorV>()?.0;
let coords = match coords {
Value::Array(a) => {
let mut c = Vec::with_capacity(a.len());
for v in a {
c.push(v.cast_to::<(f64, f64)>()?);
}
c
}
Value::Null => Vec::new(),
v => bail!("invalid points coords {v}"),
};
Ok(Self { color, coords })
}
}
#[derive(Clone)]
struct CanvasLabelV {
line: LineV,
x: f64,
y: f64,
}
impl FromValue for CanvasLabelV {
fn from_value(v: Value) -> Result<Self> {
let [(_, line), (_, x), (_, y)] = v.cast_to::<[(ArcStr, Value); 3]>()?;
Ok(Self { line: line.cast_to()?, x: x.cast_to()?, y: y.cast_to()? })
}
}
#[derive(Clone)]
enum ShapeV {
Line(CanvasLineV),
Circle(CanvasCircleV),
Rectangle(CanvasRectangleV),
Points(CanvasPointsV),
Label(CanvasLabelV),
}
impl FromValue for ShapeV {
fn from_value(v: Value) -> Result<Self> {
match v.cast_to::<(ArcStr, Value)>()? {
(s, v) if &s == "Line" => Ok(ShapeV::Line(v.cast_to()?)),
(s, v) if &s == "Circle" => Ok(ShapeV::Circle(v.cast_to()?)),
(s, v) if &s == "Rectangle" => Ok(ShapeV::Rectangle(v.cast_to()?)),
(s, v) if &s == "Points" => Ok(ShapeV::Points(v.cast_to()?)),
(s, v) if &s == "Label" => Ok(ShapeV::Label(v.cast_to()?)),
(s, v) => bail!("invalid shape {s}({v})"),
}
}
}
impl ShapeV {
fn draw(&self, ctx: &mut CanvasContext) {
match self {
ShapeV::Line(s) => {
ctx.draw(&s.0);
}
ShapeV::Circle(s) => {
ctx.draw(&s.0);
}
ShapeV::Rectangle(s) => {
ctx.draw(&s.0);
}
ShapeV::Points(s) => {
let points = Points { coords: &s.coords, color: s.color };
ctx.draw(&points);
}
ShapeV::Label(s) => {
ctx.print(s.x, s.y, s.line.0.clone());
}
}
}
}
struct ShapeRef<X: GXExt> {
r: Ref<X>,
shape: Option<ShapeV>,
}
impl<X: GXExt> ShapeRef<X> {
fn update(&mut self, id: ExprId, v: &Value) -> Result<()> {
if self.r.id == id {
self.shape = Some(v.clone().cast_to::<ShapeV>()?);
}
Ok(())
}
}
pub(super) struct CanvasW<X: GXExt> {
gx: GXHandle<X>,
shapes_ref: Ref<X>,
shapes: Vec<ShapeRef<X>>,
background_color: TRef<X, Option<ColorV>>,
marker: TRef<X, Option<MarkerV>>,
x_bounds: TRef<X, BoundsV>,
y_bounds: TRef<X, BoundsV>,
}
impl<X: GXExt> CanvasW<X> {
pub(super) async fn compile(gx: GXHandle<X>, v: Value) -> Result<TuiW> {
let [(_, background_color), (_, marker), (_, shapes), (_, x_bounds), (_, y_bounds)] =
v.cast_to::<[(ArcStr, u64); 5]>()?;
let (background_color, marker, shapes_ref, x_bounds, y_bounds) = try_join! {
gx.compile_ref(background_color),
gx.compile_ref(marker),
gx.compile_ref(shapes),
gx.compile_ref(x_bounds),
gx.compile_ref(y_bounds)
}?;
let mut t = Self {
gx: gx.clone(),
shapes_ref,
shapes: Vec::new(),
background_color: TRef::new(background_color)?,
marker: TRef::new(marker)?,
x_bounds: TRef::new(x_bounds)?,
y_bounds: TRef::new(y_bounds)?,
};
if let Some(v) = t.shapes_ref.last.take() {
t.set_shapes(v).await?;
}
Ok(Box::new(t))
}
async fn set_shapes(&mut self, v: Value) -> Result<()> {
let ids = v.cast_to::<SmallVec<[u64; 8]>>()?;
let refs = try_join_all(ids.into_iter().map(|id| {
let gx = self.gx.clone();
async move {
let mut r = gx.compile_ref(id).await?;
let shape = match r.last.take() {
Some(v) => Some(v.cast_to::<ShapeV>()?),
None => None,
};
Ok::<ShapeRef<X>, anyhow::Error>(ShapeRef { r, shape })
}
}))
.await?;
self.shapes = refs;
Ok(())
}
}
#[async_trait]
impl<X: GXExt> TuiWidget for CanvasW<X> {
async fn handle_event(&mut self, _e: Event, _v: Value) -> Result<()> {
Ok(())
}
async fn handle_update(&mut self, id: ExprId, v: Value) -> Result<()> {
self.background_color.update(id, &v).context("canvas background update")?;
self.marker.update(id, &v).context("canvas marker update")?;
self.x_bounds.update(id, &v).context("canvas x_bounds update")?;
self.y_bounds.update(id, &v).context("canvas y_bounds update")?;
if self.shapes_ref.id == id {
self.set_shapes(v.clone()).await?;
}
for s in &mut self.shapes {
s.update(id, &v)?;
}
Ok(())
}
fn draw(&mut self, frame: &mut Frame, rect: Rect) -> Result<()> {
let x_bounds = self.x_bounds.t.unwrap_or(BoundsV([0.0, 0.0])).0;
let y_bounds = self.y_bounds.t.unwrap_or(BoundsV([0.0, 0.0])).0;
let bg = self.background_color.t.as_ref().and_then(|c| c.as_ref()).cloned();
let marker = self.marker.t.as_ref().and_then(|m| m.as_ref()).cloned();
let shapes = &self.shapes;
let mut canvas = Canvas::default().x_bounds(x_bounds).y_bounds(y_bounds);
if let Some(c) = bg {
canvas = canvas.background_color(c.0);
}
if let Some(m) = marker {
canvas = canvas.marker(m.0);
}
canvas = canvas.paint(|ctx| {
for s in shapes {
if let Some(shape) = &s.shape {
shape.draw(ctx);
}
}
});
frame.render_widget(canvas, rect);
Ok(())
}
}