Split into multi-menu

This commit is contained in:
2026-08-14 23:31:36 +02:00
parent 6c683e415f
commit 0fb87f2913
4 changed files with 105 additions and 57 deletions
+33 -1
View File
@@ -1,3 +1,5 @@
use core::future;
use crate::{display::primary_lcd::PrimaryLcdDisplay, navigation::inputs::Inputs}; use crate::{display::primary_lcd::PrimaryLcdDisplay, navigation::inputs::Inputs};
use alloc::format; use alloc::format;
@@ -10,13 +12,16 @@ use embedded_graphics::text::Text;
use embedded_graphics::{Drawable, geometry::Size, primitives::Rectangle}; use embedded_graphics::{Drawable, geometry::Size, primitives::Rectangle};
use embedded_graphics_core::draw_target::DrawTarget; use embedded_graphics_core::draw_target::DrawTarget;
use super::state::Menu;
pub const MAX_SELECTED: i8 = 3; pub const MAX_SELECTED: i8 = 3;
#[derive(Debug, Clone)]
pub struct MainMenu { pub struct MainMenu {
pub selected: i8, pub selected: i8,
} }
impl super::navigation::Displayable for MainMenu { impl super::navigation::Navigable for MainMenu {
fn display( fn display(
&self, &self,
display: &mut PrimaryLcdDisplay<'static>, display: &mut PrimaryLcdDisplay<'static>,
@@ -62,4 +67,31 @@ impl super::navigation::Displayable for MainMenu {
core::future::ready(()) core::future::ready(())
} }
fn handle_input(
&self,
input: super::navigation::Action,
) -> impl core::future::Future<Output = Menu> + Send {
let new_menu = match input {
super::navigation::Action::Up => {
let new_selected = if (self.selected - 1) < 0 {
MAX_SELECTED - 1
} else {
self.selected - 1
};
Menu::Main(MainMenu {
selected: new_selected,
})
}
super::navigation::Action::Down => {
let new_selected = self.selected.wrapping_add(1) % MAX_SELECTED;
Menu::Main(MainMenu {
selected: new_selected,
})
}
_ => Menu::Main(self.clone()),
};
future::ready(new_menu)
}
} }
+24 -38
View File
@@ -1,4 +1,4 @@
use crate::navigation::main_menu::{MAX_SELECTED, MainMenu}; use crate::navigation::state::{Menu, NavigationState};
use crate::{display::primary_lcd::PrimaryLcdDisplay, navigation::inputs::Inputs}; use crate::{display::primary_lcd::PrimaryLcdDisplay, navigation::inputs::Inputs};
use embassy_futures::select::Either6; use embassy_futures::select::Either6;
@@ -7,21 +7,30 @@ use embassy_time::{Duration, Timer};
const DEBOUNCE_DURATION_MILLIS: u64 = 100; const DEBOUNCE_DURATION_MILLIS: u64 = 100;
pub trait Displayable { #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Action {
Up,
Down,
Left,
Right,
Ok,
Back,
}
pub trait Navigable {
fn display( fn display(
&self, &self,
display: &mut PrimaryLcdDisplay<'static>, display: &mut PrimaryLcdDisplay<'static>,
) -> impl core::future::Future<Output = ()> + Send; ) -> impl core::future::Future<Output = ()> + Send;
fn handle_input(&self, input: Action) -> impl core::future::Future<Output = Menu> + Send;
} }
#[embassy_executor::task] #[embassy_executor::task]
pub async fn run(mut inputs: Inputs, display: &'static mut PrimaryLcdDisplay<'static>) { pub async fn run(mut inputs: Inputs, display: &'static mut PrimaryLcdDisplay<'static>) {
let mut state = MainMenu { selected: 0 }; let mut state = NavigationState::default();
state.display(display).await;
log::info!("starting navigation"); log::info!("starting navigation");
loop { loop {
log::info!("await button press");
let selection = select6( let selection = select6(
inputs.up.wait_for_low(), inputs.up.wait_for_low(),
inputs.down.wait_for_low(), inputs.down.wait_for_low(),
@@ -32,41 +41,18 @@ pub async fn run(mut inputs: Inputs, display: &'static mut PrimaryLcdDisplay<'st
) )
.await; .await;
log::info!("button pressed"); let action = match selection {
let value = match selection { Either6::First(_) => Action::Up,
Either6::First(_) => { Either6::Second(_) => Action::Down,
log::info!("up pressed"); Either6::Third(_) => Action::Left,
if (state.selected - 1) < 0 { Either6::Fourth(_) => Action::Right,
MAX_SELECTED - 1 Either6::Fifth(_) => Action::Ok,
} else { Either6::Sixth(_) => Action::Back,
state.selected - 1
}
}
Either6::Second(_) => {
log::info!("down pressed");
(state.selected.wrapping_add(1)) % MAX_SELECTED
}
Either6::Third(_) => {
log::info!("left pressed");
continue;
}
Either6::Fourth(_) => {
log::info!("right pressed");
continue;
}
Either6::Fifth(_) => {
log::info!("ok pressed");
continue;
}
Either6::Sixth(_) => {
log::info!("back pressed");
continue;
}
}; };
state.selected = value; log::info!("Action: {:?}", action);
state.current_screen = state.current_screen.handle_input(action).await;
state.display(display).await; state.current_screen.display(display).await;
Timer::after(Duration::from_millis(DEBOUNCE_DURATION_MILLIS)).await; Timer::after(Duration::from_millis(DEBOUNCE_DURATION_MILLIS)).await;
} }
+11 -1
View File
@@ -19,11 +19,14 @@ use embedded_graphics::text::Text;
use embedded_graphics::text::TextStyleBuilder; use embedded_graphics::text::TextStyleBuilder;
use embedded_graphics_core::draw_target::DrawTarget; use embedded_graphics_core::draw_target::DrawTarget;
use super::state::Menu;
#[derive(Debug, Clone)]
pub struct ScanMenu { pub struct ScanMenu {
progress: u8, // 0..255 progress: u8, // 0..255
} }
impl super::navigation::Displayable for ScanMenu { impl super::navigation::Navigable for ScanMenu {
fn display( fn display(
&self, &self,
display: &mut PrimaryLcdDisplay<'static>, display: &mut PrimaryLcdDisplay<'static>,
@@ -69,4 +72,11 @@ impl super::navigation::Displayable for ScanMenu {
core::future::ready(()) core::future::ready(())
} }
fn handle_input(
&self,
_input: super::navigation::Action,
) -> impl core::future::Future<Output = Menu> + Send {
core::future::ready(Menu::Scan(self.clone()))
}
} }
+37 -17
View File
@@ -1,23 +1,49 @@
#[derive(Debug, Clone, Copy, PartialEq, Eq)] use crate::navigation::{main_menu::MainMenu, navigation::Navigable, scan_menu::ScanMenu};
pub enum ScreenMode {
CardDetail, #[derive(Debug, Clone)]
DexGrid, pub enum Menu {
Main(MainMenu),
Scan(ScanMenu),
Settings, Settings,
} }
#[derive(Debug, Clone, Copy)] impl Navigable for Menu {
fn display(
&self,
display: &mut crate::display::primary_lcd::PrimaryLcdDisplay<'static>,
) -> impl core::future::Future<Output = ()> + Send {
async move {
match self {
Menu::Main(main_menu) => main_menu.display(display).await,
Menu::Scan(scan_menu) => scan_menu.display(display).await,
Menu::Settings => {}
}
}
}
fn handle_input(
&self,
input: crate::navigation::navigation::Action,
) -> impl core::future::Future<Output = Self> + Send {
async move {
match self {
Menu::Main(main_menu) => main_menu.handle_input(input).await,
Menu::Scan(scan_menu) => scan_menu.handle_input(input).await,
Menu::Settings => Self::Settings,
}
}
}
}
#[derive(Debug, Clone)]
pub struct NavigationState { pub struct NavigationState {
pub current_screen: ScreenMode, pub current_screen: Menu,
pub card_index: usize,
pub use_first_payload: bool,
} }
impl Default for NavigationState { impl Default for NavigationState {
fn default() -> Self { fn default() -> Self {
Self { Self {
current_screen: ScreenMode::CardDetail, current_screen: Menu::Main(MainMenu { selected: 0 }),
card_index: 0,
use_first_payload: true,
} }
} }
} }
@@ -26,10 +52,4 @@ impl NavigationState {
pub fn new() -> Self { pub fn new() -> Self {
Self::default() Self::default()
} }
pub fn toggle_payload(&mut self) -> bool {
let current = self.use_first_payload;
self.use_first_payload = !self.use_first_payload;
current
}
} }