#![feature(min_specialization)]
pub mod cartesian;
pub mod grid;
pub mod plain_box;
pub mod primitive;
pub mod theme;
mod widget_size;
pub mod data;
pub mod legend;
pub mod prelude;
#[cfg(feature = "gg")]
pub mod gg;
#[cfg(feature = "graph")]
pub mod graph;
use std::path::Path;
use cartesian::cartesian2::Cartesian2;
use cassowary::strength::REQUIRED;
use cassowary::Solver;
use cassowary::WeightedRelation::EQ;
use grid::Grid;
use piet::kurbo::Affine;
use piet::RenderContext;
use plain_box::PlainBox;
use widget_size::WidgetSizeVars;
use crate::theme::MAKIE;
pub trait Renderable {
fn layout<C: RenderContext>(&self, ctx: &mut C, solver: &mut Solver);
fn size(&self) -> &WidgetSizeVars;
fn add_zero_protrusion_constraints(&self, solver: &mut Solver) {
let size = self.size();
solver
.add_constraints(&[
size.top | EQ(REQUIRED) | 0.0,
size.bottom | EQ(REQUIRED) | 0.0,
size.left | EQ(REQUIRED) | 0.0,
size.right | EQ(REQUIRED) | 0.0,
])
.unwrap();
}
fn render<C: RenderContext>(&self, ctx: &mut C, layout: &Solver);
fn draw_to_ctx<C: RenderContext>(&self, ctx: &mut C, (width, height): (usize, usize)) {
let mut solver = Solver::new();
let size = self.size();
solver
.add_constraints(&[
size.width() + 2.0 * MAKIE.root_padding | EQ(REQUIRED) | width as f64,
size.height() + 2.0 * MAKIE.root_padding | EQ(REQUIRED) | height as f64,
])
.unwrap();
self.layout(ctx, &mut solver);
let size = self.size().read(&solver);
ctx.clear(None, piet::Color::WHITE);
ctx.with_save(|ctx: &mut C| {
ctx.transform(Affine::translate((
size.left + MAKIE.root_padding,
size.top + MAKIE.root_padding,
)));
self.render(ctx, &solver);
Ok(())
})
.unwrap();
}
#[cfg(feature = "png")]
fn draw_to_png<P: AsRef<Path>>(
&self,
path: P,
size: (usize, usize), ) -> Result<(), piet_common::Error> {
self.draw_to_png_scaled(path, size, 1.0)
}
#[cfg(feature = "png")]
fn draw_to_png_scaled<P: AsRef<Path>>(
&self,
path: P,
size: (usize, usize), scale: f64, ) -> Result<(), piet_common::Error> {
let mut device = piet_common::Device::new()?;
let mut bitmap: piet_common::BitmapTarget = device.bitmap_target(size.0, size.1, scale)?;
let mut rc = bitmap.render_context();
self.draw_to_ctx(
&mut rc,
(
(size.0 as f64 / scale) as usize,
(size.1 as f64 / scale) as usize,
),
);
rc.finish()?;
drop(rc);
bitmap.save_to_file(path)?;
Ok(())
}
#[cfg(feature = "svg")]
fn draw_to_svg<P: AsRef<Path>>(
&self,
path: P,
(width, height): (usize, usize), ) -> std::io::Result<()> {
let file = std::fs::File::create(path)?;
self.draw_to_svg_buffer(file, (width, height))
}
#[cfg(feature = "svg")]
fn draw_to_svg_buffer(
&self,
writer: impl std::io::Write,
(width, height): (usize, usize), ) -> std::io::Result<()> {
let mut rc = piet_svg::RenderContext::new();
self.draw_to_ctx(&mut rc, (width, height));
rc.write(writer)?;
Ok(())
}
fn draw_to_file<P: AsRef<Path>>(
&self,
path: P,
(width, height): (usize, usize),
) -> anyhow::Result<()> {
let ext = path
.as_ref()
.extension()
.and_then(std::ffi::OsStr::to_str)
.map(str::to_lowercase);
match ext.as_deref() {
Some("svg") => {
#[cfg(feature = "svg")]
{
Ok(self.draw_to_svg(path, (width, height))?)
}
#[cfg(not(feature = "svg"))]
Err(std::io::Error::new(
std::io::ErrorKind::Unsupported,
"Please enable ezel's svg feature.",
))
}
Some("png") => {
#[cfg(feature = "png")]
{
Ok(self
.draw_to_png(path, (width, height))
.map_err(|e| anyhow::anyhow!(e.to_string()))?)
}
#[cfg(not(feature = "png"))]
Err(std::io::Error::new(
std::io::ErrorKind::Unsupported,
"Please enable ezel's svg feature.",
))
}
Some("jpg" | "jpeg") => {
todo!()
}
_ => Err(std::io::Error::new(
std::io::ErrorKind::Unsupported,
"Saving to this file extension is not supported.",
)
.into()),
}
}
}
pub enum Widget {
Cartesian2(Cartesian2),
Grid(Grid),
PlainBox(PlainBox),
}
impl From<Cartesian2> for Widget {
fn from(x: Cartesian2) -> Self {
Self::Cartesian2(x)
}
}
impl From<Grid> for Widget {
fn from(x: Grid) -> Self {
Self::Grid(x)
}
}
impl From<PlainBox> for Widget {
fn from(x: PlainBox) -> Self {
Self::PlainBox(x)
}
}
impl Renderable for Widget {
fn layout<C: RenderContext>(&self, ctx: &mut C, solver: &mut Solver) {
match self {
Self::Cartesian2(x) => x.layout(ctx, solver),
Self::Grid(x) => x.layout(ctx, solver),
Self::PlainBox(x) => x.layout(ctx, solver),
}
}
fn render<C: RenderContext>(&self, ctx: &mut C, layout: &Solver) {
match self {
Self::Cartesian2(x) => x.render(ctx, layout),
Self::Grid(x) => x.render(ctx, layout),
Self::PlainBox(x) => x.render(ctx, layout),
}
}
fn size(&self) -> &WidgetSizeVars {
match self {
Self::Cartesian2(x) => x.size(),
Self::Grid(x) => x.size(),
Self::PlainBox(x) => x.size(),
}
}
}