use embedded_graphics::{pixelcolor::Rgb565, prelude::DrawTarget}; use heapless::{String, Vec}; use crate::{ gui::{ chat::Chat, chat_list::ChatList, contact_view::ContactView, selector::Selector, status::StatusBar, Element, }, keyboard::KeyCode, storage::{ContactIter, Storage, MAX_CONTACTS, MAX_CONTACT_NAME_LENGTH}, voltage::VoltMeter, }; pub enum View { MainMenu(Selector<&'static str, [&'static str; 3]>), Contacts( Selector< String<MAX_CONTACT_NAME_LENGTH>, Vec<String<MAX_CONTACT_NAME_LENGTH>, MAX_CONTACTS>, >, ), ContactOptions(Selector<&'static str, [&'static str; 3]>, usize), #[allow(clippy::enum_variant_names)] ContactView(ContactView, usize, usize), ChatList(ChatList), Chat(Chat, usize), } impl View { pub fn new_main_menu(id: usize) -> Self { let menu = Selector::new(["Contacts", "Messages", "Settings"], id); Self::MainMenu(menu) } pub fn new_contacts(id: usize, contacts: &ContactIter) -> Self { let mut contact_names: Vec<_, MAX_CONTACTS> = contacts.map(|c| c.name.clone()).collect(); // If we run out of space, we automatically hide the "Add" button let _ = contact_names.push(String::try_from("Add New").unwrap()); Self::Contacts(Selector::new(contact_names, id)) } pub fn new_contact_options(contact_id: usize, selected_id: usize) -> Self { let opts = Selector::new(["View", "Edit", "Message"], selected_id); Self::ContactOptions(opts, contact_id) } pub fn from_main_menu_id(id: usize, contacts: &ContactIter, storage: &Storage) -> Option<Self> { match id { 0 => Some(Self::new_contacts(0, contacts)), 1 => Some(Self::ChatList(ChatList::new(storage, 0))), _ => None, } } pub fn to_main_menu_id(&self) -> usize { match self { View::Contacts(_) => 0, View::ChatList(_) => 1, _ => unreachable!(), } } pub fn from_contact_options_id( id: usize, contact_id: usize, storage: &Storage, ) -> Option<Self> { let mut contacts = storage.contacts(); match id { 0 => { let contact = contacts .nth(contact_id) .unwrap_or_else(|| storage.new_contact()); Some(View::ContactView( ContactView::new(contact, false), contact_id, id, )) } 1 => { let contact = contacts .nth(contact_id) .unwrap_or_else(|| storage.new_contact()); Some(View::ContactView( ContactView::new(contact, true), contact_id, id, )) } 2 => { let contact = contacts .nth(contact_id) .unwrap_or_else(|| storage.new_contact()); Some(View::Chat(Chat::new(contact.callsign, contact.ssid), 0)) } _ => unreachable!(), } } fn render<E, DT: DrawTarget<Color = Rgb565, Error = E>>( &mut self, dt: &mut DT, storage: &Storage, ) -> Result<(), E> { match self { View::MainMenu(m) => m.render(dt), View::Contacts(c) => c.render(dt), View::ContactOptions(c, _) => c.render(dt), View::ContactView(c, _, _) => c.render(dt), View::ChatList(c) => c.render(dt, storage), View::Chat(c, _) => c.render(dt, storage), } } fn key_push(&mut self, k: KeyCode, storage: &mut Storage) { match self { View::MainMenu(m) => m.key_push(k), View::Contacts(c) => c.key_push(k), View::ContactOptions(c, _) => c.key_push(k), View::ContactView(c, _, _) => { c.key_push(k); } View::ChatList(c) => c.key_push(k), View::Chat(c, _) => c.key_push(k, storage), } } fn touchpad_scroll(&mut self, x: i8, y: i8) { match self { View::MainMenu(m) => m.touchpad_scroll(x, y), View::Contacts(c) => c.touchpad_scroll(x, y), View::ContactOptions(c, _) => c.touchpad_scroll(x, y), View::ContactView(c, _, _) => c.touchpad_scroll(x, y), View::ChatList(c) => c.touchpad_scroll(x, y), View::Chat(_c, _) => {} // c.touchpad_scroll(x, y), } } } impl Default for View { fn default() -> Self { Self::new_main_menu(0) } } pub struct Controller { status: StatusBar, cur_view: View, storage: Storage, } impl Controller { pub fn new(volt_meter: VoltMeter) -> Self { Self { status: StatusBar::new(volt_meter), cur_view: View::default(), storage: Storage::new(), } } } impl Element for Controller { type KeyPushReturn = (); fn render<E, DT: DrawTarget<Color = Rgb565, Error = E>>( &mut self, dt: &mut DT, ) -> Result<(), E> { dt.clear(Rgb565::new(0, 32, 16))?; self.status.render(dt)?; self.cur_view.render(dt, &self.storage)?; Ok(()) } fn key_push(&mut self, k: KeyCode) { match (&mut self.cur_view, k) { (View::MainMenu(m), KeyCode::Touchpad) => { if let Some(new_view) = View::from_main_menu_id(m.selected(), &self.storage.contacts(), &self.storage) { self.cur_view = new_view; } } (View::Contacts(c), KeyCode::Touchpad) => { if c.selected() == self.storage.contacts_len() { // Go direct to edit page self.cur_view = View::from_contact_options_id(1, c.selected(), &self.storage).unwrap(); } else { self.cur_view = View::new_contact_options(c.selected(), 0); } } (View::ContactOptions(c, contact_id), KeyCode::Touchpad) => { if let Some(new_view) = View::from_contact_options_id(c.selected(), *contact_id, &self.storage) { self.cur_view = new_view; } } (View::ChatList(cl), KeyCode::Touchpad) => { let (callsign, ssid) = self .storage .message_addressees() .nth(cl.selected()) .unwrap(); self.cur_view = View::Chat(Chat::new(callsign, ssid), cl.selected()); } (View::MainMenu(_), KeyCode::Back) => {} (View::ContactOptions(_, id), KeyCode::Back) => { self.cur_view = View::new_contacts(*id, &self.storage.contacts()); } (View::ContactView(cv, contact_id, selected_id), KeyCode::Back) => { // TODO only need to call this if we actually edited self.storage.save_contact(cv.contact()); self.cur_view = View::new_contact_options(*contact_id, *selected_id); } (View::Chat(_, selected_id), KeyCode::Back) => { self.cur_view = View::ChatList(ChatList::new(&self.storage, *selected_id)); } (_, KeyCode::Back) => { let id = self.cur_view.to_main_menu_id(); self.cur_view = View::new_main_menu(id); } (View::ContactView(cv, contact_id, _), k) => { if cv.key_push(k) { self.storage.delete_contact(*contact_id); self.cur_view = View::new_contacts(*contact_id, &self.storage.contacts()); } } (_, k) => self.cur_view.key_push(k, &mut self.storage), } } fn touchpad_scroll(&mut self, x: i8, y: i8) { self.cur_view.touchpad_scroll(x, y); } }