1use std::error::Error as ErrorTrait;
4use std::f32::consts::FRAC_PI_2;
5use std::fmt::{Display, Formatter, Result as FmtResult};
6use std::ops::{Add, AddAssign, MulAssign, Sub, SubAssign};
7
8use egui::{
9 epaint::{PathShape, TextShape},
10 Align, Align2, Color32, FontFamily as EguiFontFamily, FontId, Pos2, Rect, Stroke, Ui,
11};
12use plotters_backend::{
13 text_anchor::{HPos, Pos, VPos},
14 BackendColor, BackendCoord, BackendStyle, BackendTextStyle, DrawingBackend, DrawingErrorKind,
15 FontFamily as PlottersFontFamily,
16};
17
18#[derive(Debug, Clone, Copy)]
19pub struct EguiBackendError;
22
23impl Display for EguiBackendError {
24 #[inline]
25 fn fmt(&self, _f: &mut Formatter<'_>) -> FmtResult {
26 Ok(())
27 }
28}
29
30impl ErrorTrait for EguiBackendError {
31 #[inline]
32 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
33 None
34 }
35}
36
37#[derive(Debug, Clone, Copy)]
38struct EguiBackendCoord {
41 x: f32,
42 y: f32,
43}
44
45impl From<(i32, i32)> for EguiBackendCoord {
46 #[inline]
47 fn from(value: (i32, i32)) -> Self {
48 let (x, y) = value;
49
50 Self {
51 x: x as f32,
52 y: y as f32,
53 }
54 }
55}
56
57impl From<EguiBackendCoord> for Pos2 {
58 #[inline]
59 fn from(val: EguiBackendCoord) -> Self {
60 Pos2 { x: val.x, y: val.y }
61 }
62}
63
64impl From<EguiBackendCoord> for (u32, u32) {
65 #[inline]
66 fn from(val: EguiBackendCoord) -> Self {
67 (val.x as u32, val.y as u32)
68 }
69}
70
71impl From<Pos2> for EguiBackendCoord {
72 #[inline]
73 fn from(value: Pos2) -> Self {
74 Self {
75 x: value.x,
76 y: value.y,
77 }
78 }
79}
80
81impl Add<EguiBackendCoord> for EguiBackendCoord {
82 type Output = EguiBackendCoord;
83
84 #[inline]
85 fn add(self, rhs: EguiBackendCoord) -> Self::Output {
86 Self {
87 x: self.x + rhs.x,
88 y: self.y + rhs.y,
89 }
90 }
91}
92
93impl Sub<EguiBackendCoord> for EguiBackendCoord {
94 type Output = EguiBackendCoord;
95
96 #[inline]
97 fn sub(self, rhs: EguiBackendCoord) -> Self::Output {
98 Self {
99 x: self.x - rhs.x,
100 y: self.y - rhs.y,
101 }
102 }
103}
104
105impl Add<Pos2> for EguiBackendCoord {
106 type Output = EguiBackendCoord;
107
108 #[inline]
109 fn add(self, rhs: Pos2) -> Self::Output {
110 Self {
111 x: self.x + rhs.x,
112 y: self.y + rhs.y,
113 }
114 }
115}
116
117impl Add<f32> for EguiBackendCoord {
118 type Output = EguiBackendCoord;
119
120 #[inline]
121 fn add(self, rhs: f32) -> Self::Output {
122 Self {
123 x: self.x + rhs,
124 y: self.y + rhs,
125 }
126 }
127}
128
129impl AddAssign<EguiBackendCoord> for EguiBackendCoord {
130 fn add_assign(&mut self, rhs: EguiBackendCoord) {
131 self.x += rhs.x;
132 self.y += rhs.y;
133 }
134}
135
136impl SubAssign<EguiBackendCoord> for EguiBackendCoord {
137 fn sub_assign(&mut self, rhs: EguiBackendCoord) {
138 self.x -= rhs.x;
139 self.y -= rhs.y;
140 }
141}
142
143impl MulAssign<f32> for EguiBackendCoord {
144 fn mul_assign(&mut self, rhs: f32) {
145 self.x *= rhs;
146 self.y *= rhs;
147 }
148}
149
150#[derive(Debug, Clone, Copy)]
151struct EguiBackendColor {
153 r: u8,
154 g: u8,
155 b: u8,
156 a: u8,
157}
158
159impl From<BackendColor> for EguiBackendColor {
160 #[inline]
161 fn from(value: BackendColor) -> Self {
162 let (r, g, b) = value.rgb;
163
164 let a = (value.alpha * 255.0) as u8;
165
166 Self { r, g, b, a }
167 }
168}
169
170impl From<EguiBackendColor> for Color32 {
171 #[inline]
172 fn from(val: EguiBackendColor) -> Self {
173 Color32::from_rgba_unmultiplied(val.r, val.g, val.b, val.a)
174 }
175}
176
177pub struct EguiBackend<'a> {
180 ui: &'a Ui,
181 x: i32,
182 y: i32,
183 scale: f32,
184}
185
186impl<'a> EguiBackend<'a> {
187 #[inline]
188 pub fn new(ui: &'a Ui) -> Self {
190 Self {
191 ui,
192 x: 0,
193 y: 0,
194 scale: 1.0,
195 }
196 }
197
198 #[inline]
199 fn point_transform(&self, mut point: EguiBackendCoord, bounds: Rect) -> EguiBackendCoord {
201 let center = EguiBackendCoord::from(bounds.center()) - EguiBackendCoord::from(bounds.min);
202 point -= center;
203 point *= self.scale;
204 point += center;
205
206 point += EguiBackendCoord::from((self.x, self.y));
207 point += EguiBackendCoord::from(bounds.min);
208
209 point
210 }
211 #[inline]
212 pub fn set_offset(&mut self, offset: (i32, i32)) {
214 (self.x, self.y) = offset
215 }
216
217 #[inline]
218 pub fn offset(mut self, offset: (i32, i32)) -> Self {
220 self.set_offset(offset);
221
222 self
223 }
224
225 #[inline]
226 pub fn set_scale(&mut self, scale: f32) {
228 self.scale = scale
229 }
230
231 #[inline]
232 pub fn scale(mut self, scale: f32) -> Self {
234 self.set_scale(scale);
235
236 self
237 }
238}
239
240impl<'a> DrawingBackend for EguiBackend<'a> {
241 type ErrorType = std::io::Error;
242
243 fn get_size(&self) -> (u32, u32) {
244 let bounds = self.ui.max_rect();
245 (bounds.width() as u32, bounds.height() as u32)
246 }
247
248 fn ensure_prepared(&mut self) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
249 Ok(())
250 }
251
252 fn present(&mut self) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
253 Ok(())
254 }
255
256 fn draw_pixel(
257 &mut self,
258 point: (i32, i32),
259 color: BackendColor,
260 ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
261 let bounds = self.ui.max_rect();
262 let painter = self.ui.painter().with_clip_rect(bounds);
263
264 let p0 = self.point_transform(EguiBackendCoord::from(point), bounds);
265
266 let p1 = p0 + 1.0;
267
268 let color: Color32 = EguiBackendColor::from(color).into();
269
270 let stroke = Stroke::new(1.0, color);
271
272 painter.line_segment([p0.into(), p1.into()], stroke);
273
274 Ok(())
275 }
276
277 fn draw_line<S: BackendStyle>(
278 &mut self,
279 from: (i32, i32),
280 to: (i32, i32),
281 style: &S,
282 ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
283 let bounds = self.ui.max_rect();
284 let painter = self.ui.painter().with_clip_rect(bounds);
285
286 let p0 = self.point_transform(EguiBackendCoord::from(from), bounds);
287 let p1 = self.point_transform(EguiBackendCoord::from(to), bounds);
288
289 let color: Color32 = EguiBackendColor::from(style.color()).into();
290
291 let stroke = Stroke::new(style.stroke_width() as f32, color);
292
293 painter.line_segment([p0.into(), p1.into()], stroke);
294
295 Ok(())
296 }
297
298 fn draw_text<TStyle: BackendTextStyle>(
299 &mut self,
300 text: &str,
301 style: &TStyle,
302 pos: (i32, i32),
303 ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
304 let bounds = self.ui.max_rect();
305 let painter = self.ui.painter().with_clip_rect(bounds);
306
307 let pos = self.point_transform(EguiBackendCoord::from(pos), bounds);
308
309 let font_size = style.size() as f32;
310 let font_family = match style.family() {
311 PlottersFontFamily::Serif | PlottersFontFamily::SansSerif => {
312 EguiFontFamily::Proportional
313 }
314 PlottersFontFamily::Monospace => EguiFontFamily::Monospace,
315 PlottersFontFamily::Name(string) => EguiFontFamily::Name(string.into()),
316 };
317
318 let font = FontId {
319 size: font_size,
320 family: font_family,
321 };
322
323 let color: Color32 = EguiBackendColor::from(style.color()).into();
324
325 let rotations = style.transform() as usize;
326 let angle = rotations as f32 * FRAC_PI_2;
327
328 let Pos { h_pos, v_pos } = style.anchor();
329
330 let mut anchor = Align2([
332 match h_pos {
333 HPos::Left => Align::LEFT,
334 HPos::Right => Align::RIGHT,
335 HPos::Center => Align::Center,
336 },
337 match v_pos {
338 VPos::Top => Align::TOP,
339 VPos::Center => Align::Center,
340 VPos::Bottom => Align::BOTTOM,
341 },
342 ]);
343 fn rotate(anchor: &mut Align2) {
344 *anchor = match anchor {
345 &mut Align2::LEFT_TOP => Align2::RIGHT_TOP,
346 &mut Align2::RIGHT_TOP => Align2::RIGHT_BOTTOM,
347 &mut Align2::RIGHT_BOTTOM => Align2::LEFT_BOTTOM,
348 &mut Align2::LEFT_BOTTOM => Align2::LEFT_TOP,
349 &mut Align2::LEFT_CENTER => Align2::CENTER_TOP,
350 &mut Align2::CENTER_TOP => Align2::RIGHT_CENTER,
351 &mut Align2::RIGHT_CENTER => Align2::CENTER_BOTTOM,
352 &mut Align2::CENTER_BOTTOM => Align2::LEFT_CENTER,
353 &mut Align2::CENTER_CENTER => Align2::CENTER_CENTER,
354 }
355 }
356 for _ in 0..rotations {
357 rotate(&mut anchor)
358 }
359 let galley = painter.layout_no_wrap(text.to_string(), font, color);
360 let rect = anchor.anchor_rect(Rect::from_min_size(pos.into(), galley.size()));
361 if !galley.is_empty() {
362 painter.add(TextShape {
363 angle,
364 ..TextShape::new(rect.min, galley, Color32::PLACEHOLDER)
365 });
366 }
367
368 Ok(())
369 }
370
371 fn draw_path<S: BackendStyle, I: IntoIterator<Item = BackendCoord>>(
372 &mut self,
373 path: I,
374 style: &S,
375 ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
376 let bounds = self.ui.max_rect();
377 let painter = self.ui.painter().with_clip_rect(bounds);
378
379 let points: Vec<Pos2> = path
380 .into_iter()
381 .map(|point| {
382 let point = self.point_transform(EguiBackendCoord::from(point), bounds);
383
384 point.into()
385 })
386 .collect();
387
388 let color: Color32 = EguiBackendColor::from(style.color()).into();
389
390 let stroke = Stroke::new(style.stroke_width() as f32, color);
391
392 let shape = PathShape::line(points, stroke);
393
394 painter.add(shape);
395 Ok(())
396 }
397
398 fn fill_polygon<S: BackendStyle, I: IntoIterator<Item = BackendCoord>>(
399 &mut self,
400 vert: I,
401 style: &S,
402 ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
403 let bounds = self.ui.max_rect();
404 let painter = self.ui.painter().with_clip_rect(bounds);
405
406 let points: Vec<Pos2> = vert
407 .into_iter()
408 .map(|point| {
409 let point = self.point_transform(EguiBackendCoord::from(point), bounds);
410
411 point.into()
412 })
413 .collect();
414
415 let color: Color32 = EguiBackendColor::from(style.color()).into();
416
417 let stroke = Stroke::NONE;
418
419 let shape = PathShape::convex_polygon(points, color, stroke);
420
421 painter.add(shape);
422
423 Ok(())
424 }
425}