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, CornerRadius,
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_rect<S: BackendStyle>(
299 &mut self,
300 upper_left: BackendCoord,
301 bottom_right: BackendCoord,
302 style: &S,
303 fill: bool,
304 ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
305 let bounds = self.ui.max_rect();
306 let painter = self.ui.painter().with_clip_rect(bounds);
307
308 let p0 = self.point_transform(EguiBackendCoord::from(upper_left), bounds);
309 let p1 = self.point_transform(EguiBackendCoord::from(bottom_right), bounds);
310 let color: Color32 = EguiBackendColor::from(style.color()).into();
311 if fill {
312 painter.rect_filled(
313 egui::Rect {
314 min: p0.into(),
315 max: p1.into(),
316 },
317 CornerRadius::default(),
318 color,
319 );
320 } else {
321 let stroke = Stroke::new(style.stroke_width() as f32, color);
322 painter.rect(
323 egui::Rect {
324 min: p0.into(),
325 max: p1.into(),
326 },
327 CornerRadius::default(),
328 Color32::TRANSPARENT,
329 stroke,
330 egui::StrokeKind::Inside
331 );
332 }
333
334 Ok(())
335 }
336
337 fn draw_path<S: BackendStyle, I: IntoIterator<Item = BackendCoord>>(
338 &mut self,
339 path: I,
340 style: &S,
341 ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
342 let bounds = self.ui.max_rect();
343 let painter = self.ui.painter().with_clip_rect(bounds);
344
345 let points: Vec<Pos2> = path
346 .into_iter()
347 .map(|point| {
348 let point = self.point_transform(EguiBackendCoord::from(point), bounds);
349
350 point.into()
351 })
352 .collect();
353
354 let color: Color32 = EguiBackendColor::from(style.color()).into();
355
356 let stroke = Stroke::new(style.stroke_width() as f32, color);
357
358 let shape = PathShape::line(points, stroke);
359
360 painter.add(shape);
361 Ok(())
362 }
363
364 fn draw_circle<S: BackendStyle>(
365 &mut self,
366 center: BackendCoord,
367 radius: u32,
368 style: &S,
369 fill: bool,
370 ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
371 let bounds = self.ui.max_rect();
372 let painter = self.ui.painter().with_clip_rect(bounds);
373
374 let center = self.point_transform(EguiBackendCoord::from(center), bounds);
375 let color: Color32 = EguiBackendColor::from(style.color()).into();
376 if fill {
377 painter.circle_filled(center.into(), radius as _, color);
378 } else {
379 let stroke = Stroke::new(style.stroke_width() as f32, color);
380 painter.circle(center.into(), radius as _, Color32::TRANSPARENT, stroke);
381 }
382
383 Ok(())
384 }
385
386 fn fill_polygon<S: BackendStyle, I: IntoIterator<Item = BackendCoord>>(
387 &mut self,
388 vert: I,
389 style: &S,
390 ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
391 let bounds = self.ui.max_rect();
392 let painter = self.ui.painter().with_clip_rect(bounds);
393
394 let points: Vec<Pos2> = vert
395 .into_iter()
396 .map(|point| {
397 let point = self.point_transform(EguiBackendCoord::from(point), bounds);
398
399 point.into()
400 })
401 .collect();
402
403 let color: Color32 = EguiBackendColor::from(style.color()).into();
404
405 let stroke = Stroke::NONE;
406
407 let shape = PathShape::convex_polygon(points, color, stroke);
408
409 painter.add(shape);
410
411 Ok(())
412 }
413
414 fn draw_text<TStyle: BackendTextStyle>(
415 &mut self,
416 text: &str,
417 style: &TStyle,
418 pos: (i32, i32),
419 ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
420 let bounds = self.ui.max_rect();
421 let painter = self.ui.painter().with_clip_rect(bounds);
422
423 let pos = self.point_transform(EguiBackendCoord::from(pos), bounds);
424
425 let font_size = style.size() as f32;
426 let font_family = match style.family() {
427 PlottersFontFamily::Serif | PlottersFontFamily::SansSerif => {
428 EguiFontFamily::Proportional
429 }
430 PlottersFontFamily::Monospace => EguiFontFamily::Monospace,
431 PlottersFontFamily::Name(string) => EguiFontFamily::Name(string.into()),
432 };
433
434 let font = FontId {
435 size: font_size,
436 family: font_family,
437 };
438
439 let color: Color32 = EguiBackendColor::from(style.color()).into();
440
441 let rotations = style.transform() as usize;
442 let angle = rotations as f32 * FRAC_PI_2;
443
444 let Pos { h_pos, v_pos } = style.anchor();
445
446 let mut anchor = Align2([
448 match h_pos {
449 HPos::Left => Align::LEFT,
450 HPos::Right => Align::RIGHT,
451 HPos::Center => Align::Center,
452 },
453 match v_pos {
454 VPos::Top => Align::TOP,
455 VPos::Center => Align::Center,
456 VPos::Bottom => Align::BOTTOM,
457 },
458 ]);
459 fn rotate(anchor: &mut Align2) {
460 *anchor = match anchor {
461 &mut Align2::LEFT_TOP => Align2::RIGHT_TOP,
462 &mut Align2::RIGHT_TOP => Align2::RIGHT_BOTTOM,
463 &mut Align2::RIGHT_BOTTOM => Align2::LEFT_BOTTOM,
464 &mut Align2::LEFT_BOTTOM => Align2::LEFT_TOP,
465 &mut Align2::LEFT_CENTER => Align2::CENTER_TOP,
466 &mut Align2::CENTER_TOP => Align2::RIGHT_CENTER,
467 &mut Align2::RIGHT_CENTER => Align2::CENTER_BOTTOM,
468 &mut Align2::CENTER_BOTTOM => Align2::LEFT_CENTER,
469 &mut Align2::CENTER_CENTER => Align2::CENTER_CENTER,
470 }
471 }
472 for _ in 0..rotations {
473 rotate(&mut anchor)
474 }
475 let galley = painter.layout_no_wrap(text.to_string(), font, color);
476 let rect = anchor.anchor_rect(Rect::from_min_size(pos.into(), galley.size()));
477 if !galley.is_empty() {
478 painter.add(TextShape {
479 angle,
480 ..TextShape::new(rect.min, galley, Color32::PLACEHOLDER)
481 });
482 }
483
484 Ok(())
485 }
486}