implement error view which displays all error on the display

This commit was merged in pull request #48.
This commit is contained in:
2026-08-29 20:19:14 +00:00
parent a31c818b37
commit ef10eec216
17 changed files with 316 additions and 92 deletions
+4 -1
View File
@@ -2,7 +2,9 @@ use crate::display::sprite::render_sprite_onto_ili9341;
use crate::navigation::navigation::NewState;
use crate::navigation::outputs::Outputs;
use crate::peripherals::Peripherals;
use alloc::boxed::Box;
use alloc::format;
use core::error;
use embedded_graphics::mono_font::MonoTextStyle;
use embedded_graphics::mono_font::ascii::FONT_6X10;
use embedded_graphics::pixelcolor::Rgb565;
@@ -30,7 +32,7 @@ impl Navigable for CardView {
&self,
outputs: &mut Outputs,
_: &Peripherals,
) -> impl core::future::Future<Output = ()> + Send {
) -> impl core::future::Future<Output = Result<(), Box<dyn error::Error>>> + Send {
async move {
let display = &mut outputs.primary_display;
display.clear(Rgb565::BLACK).unwrap();
@@ -100,6 +102,7 @@ impl Navigable for CardView {
let palette: Palette = self.card.cardtype.clone().into();
render_sprite_onto_ili9341(display, self.card.sprite.data.as_slice(), &palette);
Ok(())
}
}
+94
View File
@@ -0,0 +1,94 @@
use crate::navigation::navigation::Action;
use crate::navigation::navigation::Navigable;
use crate::navigation::navigation::NewState;
use crate::navigation::outputs::Outputs;
use crate::peripherals::Peripherals;
use crate::views::view::View;
use alloc::boxed::Box;
use alloc::format;
use alloc::string::String;
use core::error;
use embedded_graphics::{
Drawable,
mono_font::{MonoTextStyle, ascii::FONT_10X20},
pixelcolor::Rgb565,
prelude::*,
primitives::Rectangle,
text::Text,
};
use embedded_text::{
TextBox,
alignment::HorizontalAlignment,
style::{HeightMode, TextBoxStyleBuilder},
};
#[derive(Debug, Clone)]
pub struct ErrorView {
pub error: String,
}
impl Navigable for ErrorView {
fn display(
&self,
outputs: &mut Outputs,
_: &Peripherals,
) -> impl core::future::Future<Output = Result<(), Box<dyn error::Error>>> + Send {
async move {
let style = MonoTextStyle::new(&FONT_10X20, Rgb565::RED);
let display_area = outputs.primary_display.bounding_box();
Text::new(
"Error",
Point::new((display_area.size.width / 2 - 25) as i32, 30),
style,
)
.draw(&mut outputs.primary_display)
.unwrap_or_else(|error| {
panic!(
"Error: {error:?}\nDraw error while rendering error: {}",
self.error
)
});
let style = MonoTextStyle::new(&FONT_10X20, Rgb565::WHITE);
let textbox_style = TextBoxStyleBuilder::new()
.height_mode(HeightMode::FitToText)
.alignment(HorizontalAlignment::Center)
.build();
let bounds = Rectangle::new(Point::new(0, 30), display_area.size);
TextBox::with_textbox_style(&format!("{}", self.error), bounds, style, textbox_style)
.draw(&mut outputs.primary_display)
.unwrap_or_else(|error| {
panic!(
"Error: {error:?}\nDraw error while rendering error: {}",
self.error
)
});
Ok(())
}
}
fn handle_input(&self, input: Action) -> impl core::future::Future<Output = NewState> + Send {
async move {
let Action::Button(input) = input else {
// Skip timer inputs
return NewState {
view: View::Error(self.clone()),
replace_view: true,
redraw: false,
};
};
let new_menu = match input {
_ => View::Error(self.clone()),
};
NewState {
replace_view: matches!(new_menu, View::FlashInfo(_)),
view: new_menu,
redraw: true,
}
}
}
}
+17 -6
View File
@@ -1,10 +1,12 @@
use crate::navigation::inputs::ButtonAction;
use crate::navigation::navigation::Action;
use crate::navigation::navigation::Navigable;
use crate::navigation::navigation::NewState;
use crate::navigation::outputs::Outputs;
use crate::peripherals::Peripherals;
use crate::peripherals::storage::MAGIC_REGION;
use crate::views::view::View;
use alloc::boxed::Box;
use core::error;
use embedded_graphics::mono_font::MonoTextStyle;
use embedded_graphics::mono_font::ascii::FONT_10X20;
use embedded_graphics::pixelcolor::Rgb565;
@@ -22,11 +24,20 @@ impl Navigable for FlashInfoView {
&self,
outputs: &mut Outputs,
peripherals: &Peripherals,
) -> impl core::future::Future<Output = ()> + Send {
let style = MonoTextStyle::new(&FONT_10X20, Rgb565::WHITE);
Text::new("Flash Initialized: {:?}", Point::new(40, 40), style);
core::future::ready(())
) -> impl core::future::Future<Output = Result<(), Box<dyn error::Error>>> + Send {
async move {
let style = MonoTextStyle::new(&FONT_10X20, Rgb565::WHITE);
let mut magic_bytes: [u8; MAGIC_REGION.size] = [0; MAGIC_REGION.size];
peripherals
.store
.lock()
.await
.flash_store
.read(MAGIC_REGION.offset, &mut magic_bytes)
.await?;
Text::new("Flash Initialized: {:?}", Point::new(40, 40), style);
Ok(())
}
}
fn handle_input(&self, input: Action) -> impl core::future::Future<Output = NewState> + Send {
+4 -1
View File
@@ -1,3 +1,5 @@
use alloc::boxed::Box;
use core::error;
use crate::peripherals::Peripherals;
use core::future;
@@ -27,9 +29,10 @@ impl Navigable for JournalView {
&self,
outputs: &mut Outputs,
_: &Peripherals,
) -> impl core::future::Future<Output = ()> + Send {
) -> impl core::future::Future<Output = Result<(), Box<dyn error::Error>>> + Send {
async move {
outputs.primary_display.clear(Rgb565::BLACK).unwrap();
Ok(())
}
}
+14 -9
View File
@@ -1,5 +1,7 @@
use crate::peripherals::Peripherals;
use alloc::boxed::Box;
use alloc::vec;
use core::error;
use crate::card::model::Card;
use crate::card::{decoder::split_nfc_hex, mock::*};
@@ -15,6 +17,7 @@ use embedded_graphics::pixelcolor::Rgb565;
use embedded_graphics::prelude::RgbColor;
use embedded_graphics_core::draw_target::DrawTarget;
use crate::peripherals::storage::cardstore::CardStoreError;
pub const MAX_SELECTED: i32 = 4;
#[derive(Debug, Clone)]
@@ -26,17 +29,19 @@ impl Navigable for MainMenu {
fn display(
&self,
outputs: &mut Outputs,
_: &Peripherals
) -> impl core::future::Future<Output = ()> + Send {
let display = &mut outputs.primary_display;
display.clear(Rgb565::BLACK).unwrap();
_: &Peripherals,
) -> impl core::future::Future<Output = Result<(), Box<dyn error::Error>>> + Send {
async move {
let display = &mut outputs.primary_display;
display.clear(Rgb565::BLACK).unwrap();
menu_item::show(display, "Scan card", 0, self.selected);
menu_item::show(display, "Last card", 1, self.selected);
menu_item::show(display, "Journal", 2, self.selected);
menu_item::show(display, "Settings", 3, self.selected);
menu_item::show(display, "Scan card", 0, self.selected);
menu_item::show(display, "Last card", 1, self.selected);
menu_item::show(display, "Journal", 2, self.selected);
menu_item::show(display, "Settings", 3, self.selected);
core::future::ready(())
Ok(())
}
}
fn handle_input(&self, input: Action) -> impl core::future::Future<Output = NewState> + Send {
+2
View File
@@ -1,3 +1,5 @@
use alloc::boxed::Box;
use core::error;
use crate::peripherals::Peripherals;
use embedded_graphics::Drawable;
use embedded_graphics::draw_target::DrawTarget;
+47 -44
View File
@@ -3,6 +3,8 @@ use crate::navigation::navigation::CARD_DATA;
use crate::navigation::navigation::NewState;
use crate::navigation::outputs::Outputs;
use crate::peripherals::Peripherals;
use alloc::boxed::Box;
use core::error;
use alloc::format;
use embedded_graphics::Drawable;
@@ -37,55 +39,56 @@ impl Navigable for ScanMenu {
fn display(
&self,
outputs: &mut Outputs,
_: &Peripherals
) -> impl core::future::Future<Output = ()> + Send {
let display = &mut outputs.primary_display;
let style = MonoTextStyle::new(&FONT_10X20, Rgb565::WHITE);
display.clear(Rgb565::BLACK).unwrap();
_: &Peripherals,
) -> impl core::future::Future<Output = Result<(), Box<dyn error::Error>>> + Send {
async move {
let display = &mut outputs.primary_display;
let style = MonoTextStyle::new(&FONT_10X20, Rgb565::WHITE);
display.clear(Rgb565::BLACK).unwrap();
let display_area = display.bounding_box();
let display_area = display.bounding_box();
Text::new("Scanning...", Point::new(20, 30), style)
Text::new("Scanning...", Point::new(20, 30), style)
.draw(display)
.unwrap();
// Create styles used by the drawing operations.
let arc_stroke = PrimitiveStyleBuilder::new()
.stroke_color(Rgb565::WHITE)
.stroke_width(5)
.stroke_alignment(StrokeAlignment::Inside)
.build();
let character_style = MonoTextStyle::new(&FONT_10X20, Rgb565::WHITE);
let text_style = TextStyleBuilder::new()
.baseline(Baseline::Middle)
.alignment(Alignment::Center)
.build();
let sweep = self.progress as f32 * 360.0 / 255.0;
let width = display_area.size.width;
let bounding_box =
Rectangle::with_center(display_area.center(), Size::new_equal(4 * width / 5));
// log::info!("bounding_box: {:?}", bounding_box);
// log::info!("display_area: {:?}", display_area);
// Draw an arc with a 5px wide stroke.
Arc::new(
bounding_box.top_left,
bounding_box.size.width,
90.0.deg(),
sweep.deg(),
)
.into_styled(arc_stroke)
.draw(display)
.unwrap();
// Create styles used by the drawing operations.
let arc_stroke = PrimitiveStyleBuilder::new()
.stroke_color(Rgb565::WHITE)
.stroke_width(5)
.stroke_alignment(StrokeAlignment::Inside)
.build();
let character_style = MonoTextStyle::new(&FONT_10X20, Rgb565::WHITE);
let text_style = TextStyleBuilder::new()
.baseline(Baseline::Middle)
.alignment(Alignment::Center)
.build();
let sweep = self.progress as f32 * 360.0 / 255.0;
let width = display_area.size.width;
let bounding_box =
Rectangle::with_center(display_area.center(), Size::new_equal(4 * width / 5));
// log::info!("bounding_box: {:?}", bounding_box);
// log::info!("display_area: {:?}", display_area);
// Draw an arc with a 5px wide stroke.
Arc::new(
bounding_box.top_left,
bounding_box.size.width,
90.0.deg(),
sweep.deg(),
)
.into_styled(arc_stroke)
.draw(display)
.unwrap();
// Draw centered text.
let text = format!("{:.2}%", 100. * (self.progress as f32) / 255.);
Text::with_text_style(&text, display_area.center(), character_style, text_style)
.draw(display)
.unwrap();
core::future::ready(())
// Draw centered text.
let text = format!("{:.2}%", 100. * (self.progress as f32) / 255.);
Text::with_text_style(&text, display_area.center(), character_style, text_style)
.draw(display)
.unwrap();
Ok(())
}
}
fn handle_input(&self, input: Action) -> impl core::future::Future<Output = NewState> + Send {
+12 -9
View File
@@ -2,6 +2,8 @@ use crate::navigation::navigation::{Action, Navigable, NewState};
use crate::navigation::outputs::Outputs;
use crate::peripherals::Peripherals;
use crate::views::{flash_info_view::FlashInfoView, menu_item, view::View};
use alloc::boxed::Box;
use core::error;
use crate::navigation::inputs::ButtonAction;
@@ -20,15 +22,16 @@ impl Navigable for SettingsMenu {
fn display(
&self,
outputs: &mut Outputs,
_: &Peripherals
) -> impl core::future::Future<Output = ()> + Send {
let display = &mut outputs.primary_display;
display.clear(Rgb565::BLACK).unwrap();
menu_item::show(display, "System Inforation", 0, self.selected);
menu_item::show(display, "Wifi Information", 1, self.selected);
menu_item::show(display, "Flash Information", 2, self.selected);
core::future::ready(())
_: &Peripherals,
) -> impl core::future::Future<Output = Result<(), Box<dyn error::Error>>> + Send {
async move {
let display = &mut outputs.primary_display;
display.clear(Rgb565::BLACK).unwrap();
menu_item::show(display, "System Inforation", 0, self.selected);
menu_item::show(display, "Wifi Information", 1, self.selected);
menu_item::show(display, "Flash Information", 2, self.selected);
Ok(())
}
}
fn handle_input(&self, input: Action) -> impl core::future::Future<Output = NewState> + Send {
+2
View File
@@ -1 +1,3 @@
use alloc::boxed::Box;
use core::error;
// Status bar view module
+9 -4
View File
@@ -2,14 +2,17 @@ use crate::navigation::outputs::Outputs;
use crate::peripherals::Peripherals;
use crate::views::card_view::CardView;
use crate::views::{
flash_info_view::FlashInfoView, journal_view::JournalView, main_menu::MainMenu,
scan_menu::ScanMenu, settings_menu::SettingsMenu,
error_view::ErrorView, flash_info_view::FlashInfoView, journal_view::JournalView,
main_menu::MainMenu, scan_menu::ScanMenu, settings_menu::SettingsMenu,
};
use alloc::boxed::Box;
use core::error;
use crate::navigation::navigation::{Navigable, NewState};
#[derive(Debug, Clone)]
pub enum View {
Error(ErrorView),
Main(MainMenu),
Scan(ScanMenu),
Settings(SettingsMenu),
@@ -22,10 +25,11 @@ impl Navigable for View {
fn display(
&self,
outputs: &mut Outputs,
peripherals: &Peripherals
) -> impl core::future::Future<Output = ()> + Send {
peripherals: &Peripherals,
) -> impl core::future::Future<Output = Result<(), Box<dyn error::Error>>> + Send {
async move {
match self {
View::Error(error_view) => error_view.display(outputs, peripherals).await,
View::Main(main_menu) => main_menu.display(outputs, peripherals).await,
View::Scan(scan_menu) => scan_menu.display(outputs, peripherals).await,
View::Settings(settings_menu) => settings_menu.display(outputs, peripherals).await,
@@ -44,6 +48,7 @@ impl Navigable for View {
) -> impl core::future::Future<Output = NewState> + Send {
async move {
match self {
View::Error(error_view) => error_view.handle_input(input).await,
View::Main(main_menu) => main_menu.handle_input(input).await,
View::Scan(scan_menu) => scan_menu.handle_input(input).await,
View::Settings(settings_menu) => settings_menu.handle_input(input).await,