1#![feature(min_specialization)]
2
3pub mod cartesian;
4
5pub mod grid;
6pub mod plain_box;
7pub mod primitive;
8pub mod theme;
9mod widget_size;
10
11pub mod data;
12pub mod legend;
13pub mod prelude;
14
15#[cfg(feature = "gg")]
16pub mod gg;
17#[cfg(feature = "graph")]
18pub mod graph;
19
20use std::path::Path;
21
22use cartesian::cartesian2::Cartesian2;
23use cassowary::strength::REQUIRED;
24use cassowary::Solver;
25use cassowary::WeightedRelation::EQ;
26use grid::Grid;
27use piet::kurbo::Affine;
28use piet::RenderContext;
29use plain_box::PlainBox;
30use widget_size::WidgetSizeVars;
31
32use crate::theme::MAKIE;
33
34pub trait Renderable {
35 fn layout<C: RenderContext>(&self, ctx: &mut C, solver: &mut Solver);
43 fn size(&self) -> &WidgetSizeVars;
44 fn add_zero_protrusion_constraints(&self, solver: &mut Solver) {
45 let size = self.size();
46 solver
47 .add_constraints(&[
48 size.top | EQ(REQUIRED) | 0.0,
49 size.bottom | EQ(REQUIRED) | 0.0,
50 size.left | EQ(REQUIRED) | 0.0,
51 size.right | EQ(REQUIRED) | 0.0,
52 ])
53 .unwrap();
54 }
55
56 fn render<C: RenderContext>(&self, ctx: &mut C, layout: &Solver);
76
77 fn draw_to_ctx<C: RenderContext>(&self, ctx: &mut C, (width, height): (usize, usize)) {
79 let mut solver = Solver::new();
80 let size = self.size();
81
82 solver
84 .add_constraints(&[
85 size.width() + 2.0 * MAKIE.root_padding | EQ(REQUIRED) | width as f64,
86 size.height() + 2.0 * MAKIE.root_padding | EQ(REQUIRED) | height as f64,
87 ])
88 .unwrap();
89
90 self.layout(ctx, &mut solver);
91
92 let size = self.size().read(&solver);
93
94 ctx.clear(None, piet::Color::WHITE);
95 ctx.with_save(|ctx: &mut C| {
96 ctx.transform(Affine::translate((
97 size.left + MAKIE.root_padding,
98 size.top + MAKIE.root_padding,
99 )));
100 self.render(ctx, &solver);
107 Ok(())
108 })
109 .unwrap();
110 }
111
112 #[cfg(feature = "png")]
113 fn draw_to_png<P: AsRef<Path>>(
114 &self,
115 path: P,
116 size: (usize, usize), ) -> Result<(), piet_common::Error> {
118 self.draw_to_png_scaled(path, size, 1.0)
119 }
120
121 #[cfg(feature = "png")]
122 fn draw_to_png_scaled<P: AsRef<Path>>(
123 &self,
124 path: P,
125 size: (usize, usize), scale: f64, ) -> Result<(), piet_common::Error> {
128 let mut device = piet_common::Device::new()?;
129 let mut bitmap: piet_common::BitmapTarget = device.bitmap_target(size.0, size.1, scale)?;
130 let mut rc = bitmap.render_context();
131
132 self.draw_to_ctx(
133 &mut rc,
134 (
135 (size.0 as f64 / scale) as usize,
136 (size.1 as f64 / scale) as usize,
137 ),
138 );
139 rc.finish()?;
140 drop(rc);
141 bitmap.save_to_file(path)?;
142 Ok(())
143 }
144
145 #[cfg(feature = "svg")]
148 fn draw_to_svg<P: AsRef<Path>>(
149 &self,
150 path: P,
151 (width, height): (usize, usize), ) -> std::io::Result<()> {
153 let file = std::fs::File::create(path)?;
154 self.draw_to_svg_buffer(file, (width, height))
155 }
156
157 #[cfg(feature = "svg")]
158 fn draw_to_svg_buffer(
159 &self,
160 writer: impl std::io::Write,
161 (width, height): (usize, usize), ) -> std::io::Result<()> {
163 let mut rc = piet_svg::RenderContext::new();
171
172 self.draw_to_ctx(&mut rc, (width, height));
173 rc.write(writer)?;
174 Ok(())
175 }
176
177 fn draw_to_file<P: AsRef<Path>>(
178 &self,
179 path: P,
180 (width, height): (usize, usize),
181 ) -> anyhow::Result<()> {
182 let ext = path
183 .as_ref()
184 .extension()
185 .and_then(std::ffi::OsStr::to_str)
186 .map(str::to_lowercase);
187
188 match ext.as_deref() {
189 Some("svg") => {
190 #[cfg(feature = "svg")]
191 {
192 Ok(self.draw_to_svg(path, (width, height))?)
193 }
194
195 #[cfg(not(feature = "svg"))]
196 Err(std::io::Error::new(
197 std::io::ErrorKind::Unsupported,
198 "Please enable ezel's svg feature.",
199 ))
200 }
201 Some("png") => {
202 #[cfg(feature = "png")]
203 {
204 Ok(self
205 .draw_to_png(path, (width, height))
206 .map_err(|e| anyhow::anyhow!(e.to_string()))?)
207 }
208
209 #[cfg(not(feature = "png"))]
210 Err(std::io::Error::new(
211 std::io::ErrorKind::Unsupported,
212 "Please enable ezel's svg feature.",
213 ))
214 }
215 Some("jpg" | "jpeg") => {
216 todo!()
217 }
218 _ => Err(std::io::Error::new(
219 std::io::ErrorKind::Unsupported,
220 "Saving to this file extension is not supported.",
221 )
222 .into()),
223 }
224 }
225}
226
227pub enum Widget {
229 Cartesian2(Cartesian2),
230 Grid(Grid),
231 PlainBox(PlainBox),
232}
233
234impl From<Cartesian2> for Widget {
235 fn from(x: Cartesian2) -> Self {
236 Self::Cartesian2(x)
237 }
238}
239impl From<Grid> for Widget {
240 fn from(x: Grid) -> Self {
241 Self::Grid(x)
242 }
243}
244impl From<PlainBox> for Widget {
245 fn from(x: PlainBox) -> Self {
246 Self::PlainBox(x)
247 }
248}
249
250impl Renderable for Widget {
251 fn layout<C: RenderContext>(&self, ctx: &mut C, solver: &mut Solver) {
252 match self {
253 Self::Cartesian2(x) => x.layout(ctx, solver),
254 Self::Grid(x) => x.layout(ctx, solver),
255 Self::PlainBox(x) => x.layout(ctx, solver),
256 }
257 }
258
259 fn render<C: RenderContext>(&self, ctx: &mut C, layout: &Solver) {
260 match self {
261 Self::Cartesian2(x) => x.render(ctx, layout),
262 Self::Grid(x) => x.render(ctx, layout),
263 Self::PlainBox(x) => x.render(ctx, layout),
264 }
265 }
266
267 fn size(&self) -> &WidgetSizeVars {
268 match self {
269 Self::Cartesian2(x) => x.size(),
270 Self::Grid(x) => x.size(),
271 Self::PlainBox(x) => x.size(),
272 }
273 }
274}