From e30e71516e7cae544361d540b1f51df773b972ae Mon Sep 17 00:00:00 2001 From: Sofia Date: Tue, 2 Jun 2026 12:10:25 +0300 Subject: [PATCH] Implement simple menu system --- src/state.rs | 79 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/states.rs | 62 ++++++++++++++++++++++++++++++++++++---- 2 files changed, 136 insertions(+), 5 deletions(-) diff --git a/src/state.rs b/src/state.rs index a91a3c2..d4094a1 100644 --- a/src/state.rs +++ b/src/state.rs @@ -157,3 +157,82 @@ where } } } + +const MENU_HEIGHT: usize = 240 / 30; + +pub trait MenuItem { + fn as_text(&self) -> String; +} + +#[derive(Default, Debug, Clone)] +pub struct Menu { + items: Vec, + idx: usize, + scroll: usize, +} + +impl Menu { + pub fn with_item(mut self, item: T) -> Menu { + self.items.push(item); + self + } + + pub fn poll<'s>(&'s mut self, io: &mut AsyncIO) -> Option<&'s T> { + use crate::async_io::KeypadButton; + + for _ in 0..io.keypad.get_presses(KeypadButton::KeypadC) { + self.previous(); + } + for _ in 0..io.keypad.get_presses(KeypadButton::KeypadD) { + self.next(); + } + + if io.keypad.get_presses(KeypadButton::KeypadA) > 0 { + Some(&self.items[self.idx]) + } else { + None + } + } + + pub fn previous(&mut self) { + if self.idx == 0 { + // Wrap index around + self.idx = self.items.len() - 1; + if self.idx >= (self.scroll + MENU_HEIGHT) { + self.scroll = (self.idx - MENU_HEIGHT) + 1; + } + } else { + self.idx -= 1; + if self.idx < self.scroll { + self.scroll = self.idx; + } + } + } + + pub fn next(&mut self) { + self.idx = (self.idx + 1) % self.items.len(); + if self.idx == 0 { + self.scroll = 0; + } else if self.idx >= (self.scroll + MENU_HEIGHT) { + self.scroll = (self.idx - MENU_HEIGHT) + 1; + } + } + + pub fn draw(&self, data: &mut StateData) { + for (i, (idx, item)) in self + .items + .iter() + .enumerate() + .skip(self.scroll) + .take(MENU_HEIGHT) + .enumerate() + { + let mut settings = TextSettings::default(); + if idx == self.idx { + settings.bg = Rgb565::white(); + settings.fg = Rgb565::black(); + } + data.draw_text(item.as_text(), Position::new(0, (i * 30) as i16), settings); + } + } +} diff --git a/src/states.rs b/src/states.rs index 0c23d39..a17123d 100644 --- a/src/states.rs +++ b/src/states.rs @@ -17,7 +17,7 @@ use crate::{ }, display::{Position, Rgb565}, font::{HorizontalAlignment, VerticalAlignment}, - state::{ATCommandHelper, State, StateData, TextSettings}, + state::{ATCommandHelper, Menu, MenuItem, State, StateData, TextSettings}, }; #[derive(Debug, Clone)] @@ -232,10 +232,7 @@ impl State for EnterPinState { match helper.poll(&mut data.io) { Some(response) => match response.unwrap() { EnterPinResult::Ok => { - return Some(Box::new(TextState { - text: "SIM entered\nsuccessfully".to_owned(), - after: PhoneNumberState::default(), - })); + return Some(Box::new(MainMenuState::default())); } EnterPinResult::Error => { return Some(Box::new(TextState { @@ -275,6 +272,61 @@ impl State for EnterPinState { } } +#[derive(Debug, Default)] +enum MainMenuItem { + #[default] + Item1, + Item2, + Item3, + Item4, + Item5, + Item6, +} +impl MenuItem for MainMenuItem { + fn as_text(&self) -> String { + match self { + MainMenuItem::Item1 => "item1", + MainMenuItem::Item2 => "item2", + MainMenuItem::Item3 => "item3", + MainMenuItem::Item4 => "item4", + MainMenuItem::Item5 => "item5", + MainMenuItem::Item6 => "item6", + } + .to_string() + } +} + +#[derive(Debug)] +pub struct MainMenuState { + menu: Menu, +} + +impl Default for MainMenuState { + fn default() -> Self { + Self { + menu: Menu::default() + .with_item(MainMenuItem::Item1) + .with_item(MainMenuItem::Item2) + .with_item(MainMenuItem::Item3) + .with_item(MainMenuItem::Item4) + .with_item(MainMenuItem::Item5) + .with_item(MainMenuItem::Item6), + } + } +} + +impl State for MainMenuState { + fn update(&mut self, data: &mut StateData) -> Option> { + self.menu.poll(&mut data.io); + None + } + + fn draw(&self, data: &mut StateData) { + data.clear_screen(Rgb565::black().as_color()); + self.menu.draw(data); + } +} + #[derive(Clone, Default)] pub struct PhoneNumberState { input: NumberInput,