use crate::battery::{ChargerState, StateOfCharge};
use core::marker::PhantomData;
use embedded_graphics::prelude::*;
use embedded_graphics::primitives::{Polyline, PrimitiveStyleBuilder, Rectangle, Triangle};
#[derive(Copy, Clone)]
pub enum ChargerAlignment {
Left,
Right,
}
impl ChargerAlignment {
fn battery_offset(&self) -> Point {
match self {
ChargerAlignment::Left => Point::new(15, 0),
ChargerAlignment::Right => Point::new(0, 0),
}
}
fn charger_offset(&self) -> Point {
match self {
ChargerAlignment::Left => Point::new(0, 0),
ChargerAlignment::Right => Point::new(15, 0),
}
}
}
#[derive(Copy, Clone)]
pub struct BatteryIcon<C> {
position: Point,
state_of_charge: Option<StateOfCharge>,
charger: Option<ChargerState>,
charger_alignment: ChargerAlignment,
_phantom_data: PhantomData<C>,
}
impl<C> Drawable for BatteryIcon<C>
where
C: RgbColor,
{
type Color = C;
type Output = ();
fn draw<D>(&self, display: &mut D) -> Result<(), D::Error>
where
D: DrawTarget<Color = C>,
{
if let Some(state_of_charge) = self.state_of_charge {
let mut display =
display.translated(self.position + self.charger_alignment.battery_offset());
let border_color = if state_of_charge > StateOfCharge::from_percentage(10) {
C::WHITE
} else {
C::RED
};
let border_style = PrimitiveStyleBuilder::new()
.stroke_width(2)
.stroke_color(border_color)
.build();
Polyline::new(&[
Point::new(1, 24),
Point::new(1, 6),
Point::new(4, 6),
Point::new(4, 1),
Point::new(10, 1),
Point::new(10, 6),
Point::new(13, 6),
Point::new(13, 23),
Point::new(2, 23),
])
.into_styled(border_style)
.draw(&mut display)?;
if state_of_charge > StateOfCharge::from_percentage(10) {
let color = if state_of_charge > StateOfCharge::from_percentage(20) {
C::WHITE
} else {
C::RED
};
let fill_style = PrimitiveStyleBuilder::new().fill_color(color).build();
Rectangle::new(Point::new(3, 18), Size::new(9, 4))
.into_styled(fill_style)
.draw(&mut display)?;
}
if state_of_charge > StateOfCharge::from_percentage(35) {
let white_fill_style = PrimitiveStyleBuilder::new().fill_color(C::WHITE).build();
Rectangle::new(Point::new(3, 13), Size::new(9, 4))
.into_styled(white_fill_style)
.draw(&mut display)?;
}
if state_of_charge > StateOfCharge::from_percentage(65) {
let white_fill_style = PrimitiveStyleBuilder::new().fill_color(C::WHITE).build();
Rectangle::new(Point::new(3, 8), Size::new(9, 4))
.into_styled(white_fill_style)
.draw(&mut display)?;
}
if state_of_charge > StateOfCharge::from_percentage(90) {
let white_fill_style = PrimitiveStyleBuilder::new().fill_color(C::WHITE).build();
Rectangle::new(Point::new(6, 3), Size::new(3, 4))
.into_styled(white_fill_style)
.draw(&mut display)?;
}
}
if let Some(charger) = self.charger {
if charger == ChargerState::Charging || charger == ChargerState::Full {
let offset = self.position + self.charger_alignment.charger_offset();
let color = if charger == ChargerState::Charging {
C::RED
} else {
C::GREEN
};
let yellow_fill_style = PrimitiveStyleBuilder::new().fill_color(color).build();
Triangle::new(
Point::new(8, 1) + offset,
Point::new(8, 14) + offset,
Point::new(2, 14) + offset,
)
.into_styled(yellow_fill_style)
.draw(display)?;
let yellow_fill_style = PrimitiveStyleBuilder::new().fill_color(color).build();
Triangle::new(
Point::new(7, 10) + offset,
Point::new(13, 10) + offset,
Point::new(7, 23) + offset,
)
.into_styled(yellow_fill_style)
.draw(display)?;
}
}
Ok(())
}
}
impl<C> Transform for BatteryIcon<C> {
fn translate(&self, by: Point) -> Self {
Self {
position: self.position + by,
..*self
}
}
fn translate_mut(&mut self, by: Point) -> &mut Self {
self.position += by;
self
}
}
impl<C> Dimensions for BatteryIcon<C> {
fn bounding_box(&self) -> Rectangle {
Rectangle {
top_left: self.position,
size: Size::new(30, 25),
}
}
}
pub struct BatteryIconBuilder<C> {
battery_icon: BatteryIcon<C>,
}
impl<C> BatteryIconBuilder<C> {
pub fn new(position: Point) -> Self {
Self {
battery_icon: BatteryIcon {
position,
state_of_charge: None,
charger: None,
charger_alignment: ChargerAlignment::Right,
_phantom_data: PhantomData,
},
}
}
pub fn with_state_of_charge(mut self, state_of_charge: StateOfCharge) -> Self {
self.battery_icon.state_of_charge = Some(state_of_charge);
self
}
pub fn with_charger(mut self, charger: ChargerState) -> Self {
self.battery_icon.charger = Some(charger);
self
}
pub fn with_charger_alignment(mut self, charger_alignment: ChargerAlignment) -> Self {
self.battery_icon.charger_alignment = charger_alignment;
self
}
pub fn build(self) -> BatteryIcon<C> {
self.battery_icon
}
}
#[cfg(test)]
mod tests {
use super::*;
use embedded_graphics::mock_display::MockDisplay;
use embedded_graphics::pixelcolor::Rgb888;
#[test]
fn battery_zero_percent() {
let mut display: MockDisplay<Rgb888> = MockDisplay::new();
BatteryIconBuilder::new(Point::new(0, 0))
.with_state_of_charge(StateOfCharge::from_percentage(0))
.build()
.draw(&mut display)
.unwrap();
display.assert_pattern(&[
" RRRRRRRRR ",
" RRRRRRRRR ",
" RR RR ",
" RR RR ",
" RR RR ",
"RRRRR RRRRR",
"RRRRR RRRRR",
"RR RR",
"RR RR",
"RR RR",
"RR RR",
"RR RR",
"RR RR",
"RR RR",
"RR RR",
"RR RR",
"RR RR",
"RR RR",
"RR RR",
"RR RR",
"RR RR",
"RR RR",
"RR RR",
"RRRRRRRRRRRRRRR",
"RRRRRRRRRRRRRRR",
]);
}
#[test]
fn battery_hundred_percent_with_offset() {
let mut display: MockDisplay<Rgb888> = MockDisplay::new();
BatteryIconBuilder::new(Point::new(1, 1))
.with_state_of_charge(StateOfCharge::from_percentage(100))
.build()
.draw(&mut display)
.unwrap();
display.assert_pattern(&[
" ",
" WWWWWWWWW ",
" WWWWWWWWW ",
" WW WW ",
" WW WWW WW ",
" WW WWW WW ",
" WWWWW WWW WWWWW",
" WWWWW WWW WWWWW",
" WW WW",
" WW WWWWWWWWW WW",
" WW WWWWWWWWW WW",
" WW WWWWWWWWW WW",
" WW WWWWWWWWW WW",
" WW WW",
" WW WWWWWWWWW WW",
" WW WWWWWWWWW WW",
" WW WWWWWWWWW WW",
" WW WWWWWWWWW WW",
" WW WW",
" WW WWWWWWWWW WW",
" WW WWWWWWWWW WW",
" WW WWWWWWWWW WW",
" WW WWWWWWWWW WW",
" WW WW",
" WWWWWWWWWWWWWWW",
" WWWWWWWWWWWWWWW",
]);
}
#[test]
fn charger() {
let mut display: MockDisplay<Rgb888> = MockDisplay::new();
display.set_allow_overdraw(true);
BatteryIconBuilder::new(Point::new(0, 0))
.with_charger(ChargerState::Full)
.with_charger_alignment(ChargerAlignment::Left)
.build()
.draw(&mut display)
.unwrap();
display.assert_pattern(&[
" ",
" G ",
" G ",
" GG ",
" GG ",
" GGG ",
" GGG ",
" GGGG ",
" GGGG ",
" GGGGG ",
" GGGGGGGGGG ",
" GGGGGGGGGGG ",
" GGGGGGGGGG ",
" GGGGGGGGGGG ",
" GGGGGGGGGG ",
" GGGGG ",
" GGGG ",
" GGGG ",
" GGG ",
" GGG ",
" GG ",
" GG ",
" G ",
" G ",
" ",
]);
}
}