use core::{fmt::Write, ops::Range};
use heapless::String;
use embedded_graphics::{
prelude::*,
primitives::{Line, PrimitiveStyle},
text::Text,
text::TextStyle,
};
use crate::range_conv::Scalable;
use embedded_graphics::mono_font::ascii::FONT_5X8;
use embedded_graphics::mono_font::MonoTextStyle;
use embedded_graphics::text::{Alignment, Baseline, TextStyleBuilder};
pub enum Placement {
X { x1: i32, x2: i32, y: i32 },
Y { y1: i32, y2: i32, x: i32 },
}
#[derive(Clone, Copy)]
pub enum Scale {
Fixed(usize),
RangeFraction(usize),
}
impl Default for Scale {
fn default() -> Self {
Scale::RangeFraction(5)
}
}
pub struct Axis<'a> {
range: Range<i32>,
title: Option<&'a str>,
scale: Option<Scale>,
}
impl<'a> Axis<'a> {
pub fn new(range: Range<i32>) -> Axis<'a> {
Axis {
range,
title: None,
scale: None,
}
}
pub fn set_scale(mut self, scale: Scale) -> Axis<'a> {
self.scale = Some(scale);
self
}
pub fn set_title(mut self, title: &'a str) -> Axis<'a> {
self.title = Some(title);
self
}
pub fn into_drawable_axis<C>(self, placement: Placement) -> DrawableAxis<'a, C>
where
C: PixelColor + Default,
TextStyle: Clone + Default,
{
DrawableAxis {
axis: self,
placement,
color: None,
text_style: None,
tick_size: None,
thickness: None,
}
}
}
pub struct DrawableAxis<'a, C>
where
C: PixelColor,
TextStyle: Clone + Default,
{
axis: Axis<'a>,
placement: Placement,
color: Option<C>,
text_style: Option<MonoTextStyle<'a, C>>,
tick_size: Option<usize>,
thickness: Option<usize>,
}
impl<'a, C> DrawableAxis<'a, C>
where
C: PixelColor + Default,
TextStyle: Clone + Default,
{
pub fn set_color(mut self, val: C) -> DrawableAxis<'a, C> {
self.color = Some(val);
self
}
pub fn set_text_style(mut self, val: MonoTextStyle<'a, C>) -> DrawableAxis<'a, C> {
self.text_style = Some(val);
self
}
pub fn set_tick_size(mut self, val: usize) -> DrawableAxis<'a, C> {
self.tick_size = Some(val);
self
}
pub fn set_thickness(mut self, val: usize) -> DrawableAxis<'a, C> {
self.thickness = Some(val);
self
}
}
impl<'a, C> Drawable for DrawableAxis<'a, C>
where
C: PixelColor + Default,
TextStyle: Clone + Default,
{
type Color = C;
type Output = ();
fn draw<D: DrawTarget<Color = C>>(&self, display: &mut D) -> Result<(), D::Error> {
let color = self.color.unwrap_or_default();
let thickness = self.thickness.unwrap_or(1);
let tick_size = self.tick_size.unwrap_or(2);
let character_style = MonoTextStyle::new(&FONT_5X8, color);
let scale_marks = match self.axis.scale.unwrap_or_default() {
Scale::Fixed(interval) => self.axis.range.clone().into_iter().step_by(interval),
Scale::RangeFraction(fraction) => {
let len = self.axis.range.len();
self.axis.range.clone().into_iter().step_by(len / fraction)
}
};
match self.placement {
Placement::X { x1, x2, y } => {
let title_text_style = TextStyleBuilder::new()
.alignment(Alignment::Center)
.baseline(Baseline::Top)
.build();
let tick_text_style = TextStyleBuilder::new()
.alignment(Alignment::Left)
.baseline(Baseline::Top)
.build();
Line {
start: Point { x: x1, y },
end: Point { x: x2, y },
}
.into_styled(PrimitiveStyle::with_stroke(color, thickness as u32))
.draw(display)?;
if let Some(title) = self.axis.title {
Text::with_text_style(
title,
Point {
x: x1 + (x2 - x1) / 2,
y: y + 10,
},
character_style,
title_text_style,
)
.draw(display)?;
}
for mark in scale_marks {
let x = mark.scale_between_ranges(&self.axis.range, &(x1..x2));
Line {
start: Point {
x,
y: y - tick_size as i32,
},
end: Point {
x,
y: y + tick_size as i32,
},
}
.into_styled(PrimitiveStyle::with_stroke(color, thickness as u32))
.draw(display)?;
let mut buf: String<8> = String::new();
write!(buf, "{}", mark).unwrap();
Text::with_text_style(
&buf,
Point { x: x + 2, y: y + 2 },
character_style,
tick_text_style,
)
.draw(display)?;
}
}
Placement::Y { y1, y2, x } => {
let title_text_style = TextStyleBuilder::new()
.alignment(Alignment::Right)
.baseline(Baseline::Middle)
.build();
let tick_text_style = TextStyleBuilder::new()
.alignment(Alignment::Right)
.baseline(Baseline::Top)
.build();
Line {
start: Point { x, y: y1 },
end: Point { x, y: y2 },
}
.into_styled(PrimitiveStyle::with_stroke(color, thickness as u32))
.draw(display)?;
let mut tick_text_left_pos_bound = i32::MAX;
for mark in scale_marks {
let y = mark.scale_between_ranges(&self.axis.range, &(y2..y1));
Line {
start: Point {
x: x - tick_size as i32,
y,
},
end: Point {
x: x + tick_size as i32,
y,
},
}
.into_styled(PrimitiveStyle::with_stroke(color, thickness as u32))
.draw(display)?;
let mut buf: String<8> = String::new();
write!(buf, "{}", mark).unwrap();
let tick_val = Text::with_text_style(
&buf,
Point { x, y },
character_style,
tick_text_style,
);
if tick_val.bounding_box().top_left.x < tick_text_left_pos_bound {
tick_text_left_pos_bound = tick_val.bounding_box().top_left.x
};
tick_val.draw(display)?;
}
if let Some(title) = self.axis.title {
Text::with_text_style(
title,
Point {
x: tick_text_left_pos_bound - 1,
y: y1 + (y2 - y1) / 2,
},
character_style,
title_text_style,
)
.draw(display)?;
}
}
}
Ok(())
}
}