diff --git a/Cargo.lock b/Cargo.lock index f6961f4..82dc96f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -206,6 +206,7 @@ dependencies = [ "log", "num_enum", "pn532", + "profont", "static_assertions", ] @@ -1595,6 +1596,15 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "profont" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "016681370a9dd6e7ddb4c1a959922fd59dc45e5ebaa5ff5b13090267898ced34" +dependencies = [ + "embedded-graphics", +] + [[package]] name = "quote" version = "1.0.47" diff --git a/Cargo.toml b/Cargo.toml index 150a220..44ac928 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,6 +58,7 @@ esp-backtrace = { version = "0.19.0", features = [ "println", ] } esp-radio = { version = "0.18.0", features = ["esp32s3", "wifi"] } +profont = "0.7.0" [build-dependencies] log = "0.4.27" diff --git a/src/drivers/secondary_oled.rs b/src/drivers/secondary_oled.rs index 86b6101..4973893 100644 --- a/src/drivers/secondary_oled.rs +++ b/src/drivers/secondary_oled.rs @@ -1,5 +1,9 @@ use alloc::format; use ch1115::{Ch1115, Size128x64}; +use core::convert::Infallible; +use core::error::Error; +use core::fmt::Debug; +use core::fmt::Display; use display_interface_spi::SPIInterface; use embedded_hal_bus::spi::AtomicDevice; use embedded_hal_bus::util::AtomicCell; @@ -8,6 +12,29 @@ use esp_hal::gpio::Output; use crate::drivers::spi_bus::{DisplaySpiBus, SharedOutput, SharedReset}; +#[derive(Debug)] +pub struct SecondaryDisplayError(ch1115::Error); + +impl Display for SecondaryDisplayError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + self.0.fmt(f) + } +} + +impl Error for SecondaryDisplayError {} + +impl From> for SecondaryDisplayError { + fn from(value: ch1115::Error) -> Self { + Self(value) + } +} + +impl From for SecondaryDisplayError { + fn from(value: Infallible) -> Self { + Self(ch1115::Error::Pin(value)) + } +} + pub type GenericSecondaryDisplay<'a, BUS, RST, DC> = Ch1115, Delay>, DC>, RST, Size128x64>; pub type SecondaryDisplay<'a> = diff --git a/src/drivers/tertiary_lcd.rs b/src/drivers/tertiary_lcd.rs index 39fb117..d5fe0c7 100644 --- a/src/drivers/tertiary_lcd.rs +++ b/src/drivers/tertiary_lcd.rs @@ -1,3 +1,4 @@ +use core::error::Error; use embedded_hal_bus::i2c::AtomicDevice; use esp_hal::delay::Delay; use i2c_character_display::{CharacterDisplayPCF8574T, LcdDisplayType}; @@ -48,6 +49,8 @@ pub enum TertiaryDisplayWriteError<'a> { Format(LinesError), } +impl<'a> Error for TertiaryDisplayWriteError<'a> {} + impl<'a> From> for TertiaryDisplayWriteError<'a> { fn from(value: TertiaryDisplayError<'a>) -> Self { Self::Display(value) diff --git a/src/navigation/outputs.rs b/src/navigation/outputs.rs index 03aba5a..b014266 100644 --- a/src/navigation/outputs.rs +++ b/src/navigation/outputs.rs @@ -1,10 +1,14 @@ +use alloc::boxed::Box; +use core::error; use embedded_graphics::{ draw_target::DrawTarget as _, pixelcolor::{Rgb565, RgbColor as _}, }; use crate::drivers::{ - primary_lcd::PrimaryDisplay, secondary_oled::SecondaryDisplay, tertiary_lcd::TertiaryDisplay, + primary_lcd::{PrimaryDisplay, PrimaryDisplayError}, + secondary_oled::{SecondaryDisplay, SecondaryDisplayError}, + tertiary_lcd::TertiaryDisplay, }; pub struct Outputs { @@ -17,11 +21,20 @@ pub struct Outputs { } impl Outputs { - pub fn clear_screens(&mut self) { - self.primary_display.clear(Rgb565::BLACK).unwrap(); - self.secondary_display_1.clear().unwrap(); - // self.secondary_display_2.clear().unwrap(); - // self.secondary_display_3.clear().unwrap(); + pub fn clear_screens(&mut self) -> Result<(), Box> { + self.primary_display + .clear(Rgb565::BLACK) + .map_err(PrimaryDisplayError::from)?; + self.secondary_display_1 + .clear() + .map_err(SecondaryDisplayError::from)?; + self.secondary_display_2 + .clear() + .map_err(SecondaryDisplayError::from)?; + self.secondary_display_3 + .clear() + .map_err(SecondaryDisplayError::from)?; self.tertiary_display.clear().unwrap(); + Ok(()) } } diff --git a/src/views/card_view.rs b/src/views/card_view.rs index 9eb71b9..6c556a4 100644 --- a/src/views/card_view.rs +++ b/src/views/card_view.rs @@ -1,5 +1,7 @@ use crate::display::sprite::render_sprite_onto_ili9341; -use crate::drivers::primary_lcd::PrimaryDisplayError; +use crate::drivers::primary_lcd::{PrimaryDisplay, PrimaryDisplayError}; +use crate::drivers::secondary_oled::{SecondaryDisplay, SecondaryDisplayError}; +use crate::drivers::tertiary_lcd::{TertiaryDisplay, TertiaryDisplayWriteError}; use crate::navigation::navigation::NewState; use crate::navigation::outputs::Outputs; use crate::peripherals::Peripherals; @@ -8,14 +10,23 @@ use alloc::format; use core::error; use embedded_graphics::mono_font::MonoTextStyle; use embedded_graphics::mono_font::ascii::FONT_6X10; +use embedded_graphics::pixelcolor::BinaryColor; use embedded_graphics::pixelcolor::Rgb565; use embedded_graphics::prelude::Point; use embedded_graphics::prelude::RgbColor; use embedded_graphics::text::Text; use embedded_graphics::{Drawable, geometry::Size, primitives::Rectangle}; use embedded_graphics_core::draw_target::DrawTarget; +use profont::PROFONT_7_POINT; +use profont::PROFONT_18_POINT; use core::future; +use embedded_graphics::geometry::Dimensions; +use embedded_text::{ + TextBox, + alignment::{HorizontalAlignment, VerticalAlignment}, + style::{HeightMode, TextBoxStyleBuilder}, +}; use crate::{ card::model::{Card, Palette}, @@ -27,7 +38,118 @@ use crate::{ pub struct CardView { pub card: Card, } +impl CardView { + fn display_card( + &self, + primary_display: &mut PrimaryDisplay, + ) -> Result<(), Box> { + let background = Rectangle::new(Point::new(0, 0), Size::new(240, 320)); + let bg_color = self.card.cardtype.clone().into(); + let _ = primary_display.fill_solid(&background, bg_color); + // name as header + let text_style_name = MonoTextStyle::new(&PROFONT_18_POINT, Rgb565::WHITE); + let textbox_style = TextBoxStyleBuilder::new() + .height_mode(HeightMode::FitToText) + .alignment(HorizontalAlignment::Center) + .build(); + + let display_area = primary_display.bounding_box(); + let bounds = Rectangle::new(Point::new(0, 10), Size::new(display_area.size.width, 50)); + + TextBox::with_textbox_style(&self.card.name, bounds, text_style_name, textbox_style) + .draw(primary_display) + .map_err(PrimaryDisplayError::from)?; + + // event lower left corner + let text_style_event = MonoTextStyle::new(&PROFONT_7_POINT, Rgb565::WHITE); + + let textbox_style = TextBoxStyleBuilder::new() + .height_mode(HeightMode::FitToText) + .alignment(HorizontalAlignment::Right) + .build(); + + let display_area = primary_display.bounding_box(); + let bounds = Rectangle::new(Point::new(0, 310), Size::new(display_area.size.width, 50)); + + TextBox::with_textbox_style( + &format!("{}", self.card.event), + bounds, + text_style_event, + textbox_style, + ) + .draw(primary_display) + .map_err(PrimaryDisplayError::from)?; + + // card sprite + let palette: Palette = self.card.cardtype.clone().into(); + + render_sprite_onto_ili9341( + primary_display, + self.card.sprite.data.as_slice(), + &palette, + Point::new(0, 50), + 240, + ); + + Ok(()) + } + + fn display_trait( + &self, + secondary_display: &mut SecondaryDisplay, + trait_str: &str, + ) -> Result<(), Box> { + let text_style_secondary = MonoTextStyle::new(&PROFONT_18_POINT, BinaryColor::On); + + let textbox_style = TextBoxStyleBuilder::new() + .height_mode(HeightMode::FitToText) + .vertical_alignment(VerticalAlignment::Middle) + .alignment(HorizontalAlignment::Justified) + .build(); + + let display_area = secondary_display.bounding_box(); + let bounds = Rectangle::new(Point::new(0, 0), display_area.size); + + TextBox::with_textbox_style( + &format!("{}", trait_str), + bounds, + text_style_secondary, + textbox_style, + ) + .draw(secondary_display) + .map_err(SecondaryDisplayError::from)?; + + secondary_display + .flush() + .map_err(SecondaryDisplayError::from)?; + + Ok(()) + } + + fn display_traits( + &self, + secondary_display_1: &mut SecondaryDisplay, + secondary_display_2: &mut SecondaryDisplay, + secondary_display_3: &mut SecondaryDisplay, + ) -> Result<(), Box> { + self.display_trait(secondary_display_1, self.card.trait1.as_str())?; + self.display_trait(secondary_display_2, self.card.trait2.as_str())?; + self.display_trait(secondary_display_3, self.card.trait3.as_str())?; + + Ok(()) + } + + fn display_secret<'a>( + &self, + tertiary_display: &mut TertiaryDisplay<'a>, + ) -> Result<(), Box> { + tertiary_display + .write(&alloc::format!("{}", self.card.secret)) + .map_err(TertiaryDisplayWriteError::from)?; + Ok(()) + } +} impl Navigable for CardView { fn display( &self, @@ -35,82 +157,21 @@ impl Navigable for CardView { _: &Peripherals, ) -> impl core::future::Future>> + Send { async move { - let display = &mut outputs.primary_display; - display - .clear(Rgb565::BLACK) - .map_err(PrimaryDisplayError::from)?; - - let background = Rectangle::new(Point::new(0, 0), Size::new(240, 320)); - let bg_color = self.card.cardtype.clone().into(); - - let _ = display.fill_solid(&background, bg_color); - - let style = MonoTextStyle::new(&FONT_6X10, Rgb565::WHITE); - Text::new( - &format!("Name: {}", self.card.name), - Point::new(20, 240), - style, - ) - .draw(display) - .map_err(PrimaryDisplayError::from)?; - Text::new( - &format!("Type: {}", self.card.cardtype), - Point::new(20, 250), - style, - ) - .draw(display) - .map_err(PrimaryDisplayError::from)?; - Text::new( - &format!("Event: {}", self.card.event), - Point::new(20, 260), - style, - ) - .draw(display) - .map_err(PrimaryDisplayError::from)?; - Text::new( - &format!("UUID: {}", self.card.uuid), - Point::new(20, 270), - style, - ) - .draw(display) - .map_err(PrimaryDisplayError::from)?; - Text::new( - &format!("Trait1: {}", self.card.trait1), - Point::new(20, 280), - style, - ) - .draw(display) - .map_err(PrimaryDisplayError::from)?; - Text::new( - &format!("Trait2: {}", self.card.trait2), - Point::new(20, 290), - style, - ) - .draw(display) - .map_err(PrimaryDisplayError::from)?; - Text::new( - &format!("Trait3: {}", self.card.trait3), - Point::new(20, 300), - style, - ) - .draw(display) - .map_err(PrimaryDisplayError::from)?; - Text::new( + outputs.clear_screens()?; + /*Text::new( &format!("Secret: {}", self.card.secret), Point::new(20, 310), style, ) .draw(display) - .map_err(PrimaryDisplayError::from)?; - - let palette: Palette = self.card.cardtype.clone().into(); - render_sprite_onto_ili9341( - display, - self.card.sprite.data.as_slice(), - &palette, - Point::new(13, 10), - 215, - ); + .map_err(PrimaryDisplayError::from)?;*/ + self.display_traits( + &mut outputs.secondary_display_1, + &mut outputs.secondary_display_2, + &mut outputs.secondary_display_3, + )?; + self.display_secret(&mut outputs.tertiary_display)?; + self.display_card(&mut outputs.primary_display)?; Ok(()) } }