pub mod terminal;
mod ui;
use anyhow::Result;
use matrix65::filehost;
use matrix65::{io, serial};
use serialport::SerialPort;
use ui::{StatefulList, StatefulTable};
#[derive(PartialEq, Eq)]
pub enum AppWidgets {
FileSelector,
FileAction,
CBMBrowser,
Help,
}
pub struct App {
active_widget: AppWidgets,
busy: bool,
cbm_browser: StatefulList<String>,
cbm_disk: Option<Box<dyn cbm::disk::Disk>>,
file_action: StatefulList<String>,
filetable: StatefulTable<filehost::Record>,
messages: Vec<String>,
port: Box<dyn SerialPort>,
toggle_sort: bool,
}
impl App {
fn new(port: &mut Box<dyn SerialPort>, filehost_items: &[filehost::Record]) -> App {
App {
messages: vec![
"Matrix65 welcomes you to the FileHost!".to_string(),
"Press 'h' for help".to_string(),
],
active_widget: AppWidgets::FileSelector,
file_action: StatefulList::with_items(vec![
"Run".to_string(),
"Reset and Run".to_string(),
"Open CBM disk...".to_string(),
"Cancel".to_string(),
]),
busy: false,
filetable: StatefulTable::with_items(filehost_items.to_vec()),
port: port.try_clone().unwrap(),
toggle_sort: false,
cbm_disk: None,
cbm_browser: StatefulList::with_items(Vec::<String>::new()),
}
}
pub fn set_current_widget(&mut self, widget: AppWidgets) {
self.active_widget = widget;
}
fn activate_cbm_browser(&mut self) -> Result<()> {
self.busy = false;
self.set_current_widget(AppWidgets::CBMBrowser);
let url = self.selected_url();
self.cbm_disk = Some(io::cbm_open(&url)?);
if self.cbm_disk.is_some() {
let dir = self.cbm_disk.as_ref().unwrap().directory()?;
let files: Vec<String> = dir
.iter()
.map(|i| format!("{}.{}", i.filename.to_string(), i.file_attributes.file_type))
.collect();
self.cbm_browser.items = files;
}
Ok(())
}
pub fn previous_item(&mut self) {
match self.active_widget {
AppWidgets::CBMBrowser => self.cbm_browser.previous(),
AppWidgets::FileAction => self.file_action.previous(),
AppWidgets::FileSelector => self.filetable.previous(),
_ => {}
}
}
pub fn next_item(&mut self) {
match self.active_widget {
AppWidgets::CBMBrowser => self.cbm_browser.next(),
AppWidgets::FileAction => self.file_action.next(),
AppWidgets::FileSelector => self.filetable.next(),
_ => {}
}
}
fn return_to_filehost(&mut self) {
self.set_current_widget(AppWidgets::FileSelector);
self.file_action.unselect();
}
fn select_filehost_item(&mut self) -> Result<(), anyhow::Error> {
self.active_widget = AppWidgets::FileAction;
if !self.file_action.is_selected() {
self.file_action.state.select(Some(0));
};
Ok(())
}
fn select_file_action(&mut self) -> Result<(), anyhow::Error> {
self.set_current_widget(AppWidgets::FileSelector);
match self.file_action.state.selected() {
Some(0) => self.run(false)?, Some(1) => self.run(true)?, Some(2) => self.activate_cbm_browser()?,
_ => {}
};
self.file_action.unselect();
Ok(())
}
fn select_cbm_item(&mut self) -> Result<(), anyhow::Error> {
match self.cbm_browser.state.selected() {
Some(_) => {
self.run(false)?;
self.busy = false;
self.active_widget = AppWidgets::FileSelector;
self.cbm_browser.unselect();
self.file_action.unselect();
Ok(())
}
None => Err(anyhow::Error::msg("No CBM file selected")),
}
}
fn toggle_help(&mut self) {
if self.active_widget != AppWidgets::Help {
self.set_current_widget(AppWidgets::Help);
} else {
self.set_current_widget(AppWidgets::FileSelector);
}
}
fn _ok_message(&mut self) {
let ok_text = "Ready".to_string();
if *self.messages.last().unwrap() != ok_text {
self.messages.push(ok_text);
}
}
fn add_message(&mut self, message: &str) {
self.messages.push(message.to_string());
}
#[allow(dead_code)]
pub fn clear_status_line(&mut self) {
}
fn sort_filehost(&mut self) {
if self.toggle_sort {
self.filetable.items.sort_by_key(|i| i.published.clone());
self.filetable.items.reverse();
} else {
self.filetable.items.sort_by_key(|i| i.title.clone());
}
self.toggle_sort = !self.toggle_sort;
}
pub fn selected_url(&self) -> String {
let sel = self.filetable.state.selected().unwrap_or(0);
let item = &self.filetable.items[sel];
format!("https://files.mega65.org/{}", &item.location)
}
pub fn run(&mut self, reset_before_run: bool) -> Result<()> {
let url = self.selected_url();
if url.ends_with(".prg") {
serial::handle_prg(&mut self.port, &url, reset_before_run, true)?;
} else if url.ends_with(".d81") & self.cbm_disk.is_some() & self.cbm_browser.is_selected() {
let selected_file = self.cbm_browser.state.selected().unwrap();
let (load_address, bytes) =
io::cbm_load_file(self.cbm_disk.as_ref().unwrap().as_ref(), selected_file)?;
serial::handle_prg_from_bytes(
&mut self.port,
&bytes,
load_address,
reset_before_run,
true,
)?;
self.cbm_browser.unselect();
self.cbm_disk = None;
} else {
return Err(anyhow::Error::msg("Cannot run selection"));
}
Ok(())
}
pub fn reset(&mut self) -> Result<()> {
crate::serial::reset(&mut self.port)?;
self.add_message("Reset MEGA65");
Ok(())
}
pub fn unselect_all(&mut self) {
self.cbm_browser.unselect();
self.file_action.unselect();
}
}