Skip to content

Commit da67483

Browse files
committed
TUI refactor
1 parent a7c1f6c commit da67483

File tree

3 files changed

+97
-104
lines changed

3 files changed

+97
-104
lines changed

src/textui/mod.rs

Lines changed: 86 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -12,88 +12,15 @@
1212
// see the license for the specific language governing permissions and
1313
// limitations under the license.
1414

15-
use crossterm::event::KeyCode;
16-
use anyhow::Result;
17-
use tui::widgets::{ListState, TableState};
1815
use crate::{filehost, serial};
16+
use anyhow::Result;
17+
use crossterm::event::KeyCode;
1918
use serialport::SerialPort;
19+
use tui::widgets::{ListState, TableState};
2020

2121
pub mod terminal;
2222
mod ui;
2323

24-
pub struct FilesApp {
25-
pub filetable: StatefulTable<filehost::Record>,
26-
pub port: Box<dyn SerialPort>,
27-
toggle_sort: bool,
28-
/// Selected CBM disk
29-
pub cbm_disk: Option<Box<dyn cbm::disk::Disk>>,
30-
/// Browser for files CBM disk images (d81 etc)
31-
pub cbm_browser: StatefulList<String>,
32-
}
33-
34-
impl FilesApp {
35-
pub fn new(port: &mut Box<dyn SerialPort>, filehost_items: &[filehost::Record]) -> FilesApp {
36-
FilesApp {
37-
filetable: StatefulTable::with_items(filehost_items.to_vec()),
38-
port: port.try_clone().unwrap(),
39-
toggle_sort: false,
40-
cbm_disk: None,
41-
cbm_browser: StatefulList::with_items(Vec::<String>::new()),
42-
}
43-
}
44-
45-
pub fn keypress(&mut self, key: crossterm::event::KeyCode) -> Result<()> {
46-
match key {
47-
KeyCode::Down => self.filetable.next(),
48-
KeyCode::Up => self.filetable.previous(),
49-
KeyCode::Char('s') => self.sort_filehost(),
50-
_ => {}
51-
}
52-
Ok(())
53-
}
54-
55-
/// Toggles filehost file sorting by date or title
56-
fn sort_filehost(&mut self) {
57-
if self.toggle_sort {
58-
self.filetable.items.sort_by_key(|i| i.published.clone());
59-
self.filetable.items.reverse();
60-
} else {
61-
self.filetable.items.sort_by_key(|i| i.title.clone());
62-
}
63-
self.toggle_sort = !self.toggle_sort;
64-
}
65-
66-
pub fn selected_url(&self) -> String {
67-
let sel = self.filetable.state.selected().unwrap_or(0);
68-
let item = &self.filetable.items[sel];
69-
format!("https://files.mega65.org/{}", &item.location)
70-
}
71-
72-
/// Transfer and run selected file
73-
pub fn run(&mut self, reset_before_run: bool) -> Result<()> {
74-
let url = self.selected_url();
75-
if url.ends_with(".prg") {
76-
serial::handle_prg(&mut self.port, &url, reset_before_run, true)?;
77-
} else if url.ends_with(".d81") & self.cbm_disk.is_some() & self.cbm_browser.is_selected() {
78-
let selected_file = self.cbm_browser.state.selected().unwrap();
79-
let (load_address, bytes) =
80-
crate::io::cbm_load_file(self.cbm_disk.as_ref().unwrap().as_ref(), selected_file)?;
81-
serial::handle_prg_from_bytes(
82-
&mut self.port,
83-
&bytes,
84-
load_address,
85-
reset_before_run,
86-
true,
87-
)?;
88-
self.cbm_browser.unselect();
89-
self.cbm_disk = None;
90-
} else {
91-
return Err(anyhow::Error::msg("Cannot run selection"));
92-
}
93-
Ok(())
94-
}
95-
}
96-
9724
/// Specified the currently active widget of the TUI
9825
#[derive(PartialEq, Eq)]
9926
pub enum AppWidgets {
@@ -104,8 +31,6 @@ pub enum AppWidgets {
10431
}
10532

10633
pub struct App {
107-
/// FileHost file browser
108-
files: FilesApp,
10934
/// Status messages presented in the UI
11035
messages: Vec<String>,
11136
/// Holds the active widget
@@ -114,12 +39,21 @@ pub struct App {
11439
file_action: StatefulList<String>,
11540
/// Set to true when UI is unresponsive
11641
busy: bool,
42+
/// FileHost file browser
43+
pub filetable: StatefulTable<filehost::Record>,
44+
/// Serial port to communicate on
45+
pub port: Box<dyn SerialPort>,
46+
/// Determines how to sort the filehost table
47+
toggle_sort: bool,
48+
/// Selected CBM disk
49+
pub cbm_disk: Option<Box<dyn cbm::disk::Disk>>,
50+
/// Browser for files CBM disk images (d81 etc)
51+
pub cbm_browser: StatefulList<String>,
11752
}
11853

11954
impl App {
12055
fn new(port: &mut Box<dyn SerialPort>, filehost_items: &[filehost::Record]) -> App {
12156
App {
122-
files: FilesApp::new(port, filehost_items),
12357
messages: vec![
12458
"Matrix65 welcomes you to the FileHost!".to_string(),
12559
"Press 'h' for help".to_string(),
@@ -132,6 +66,11 @@ impl App {
13266
"Cancel".to_string(),
13367
]),
13468
busy: false,
69+
filetable: StatefulTable::with_items(filehost_items.to_vec()),
70+
port: port.try_clone().unwrap(),
71+
toggle_sort: false,
72+
cbm_disk: None,
73+
cbm_browser: StatefulList::with_items(Vec::<String>::new()),
13574
}
13675
}
13776

@@ -143,15 +82,15 @@ impl App {
14382
fn activate_cbm_browser(&mut self) -> Result<()> {
14483
self.busy = false;
14584
self.set_current_widget(AppWidgets::CBMBrowser);
146-
let url = self.files.selected_url();
147-
self.files.cbm_disk = Some(crate::io::cbm_open(&url)?);
148-
if self.files.cbm_disk.is_some() {
149-
let dir = self.files.cbm_disk.as_ref().unwrap().directory()?;
85+
let url = self.selected_url();
86+
self.cbm_disk = Some(crate::io::cbm_open(&url)?);
87+
if self.cbm_disk.is_some() {
88+
let dir = self.cbm_disk.as_ref().unwrap().directory()?;
15089
let files: Vec<String> = dir
15190
.iter()
15291
.map(|i| format!("{}.{}", i.filename.to_string(), i.file_attributes.file_type))
15392
.collect();
154-
self.files.cbm_browser.items = files;
93+
self.cbm_browser.items = files;
15594
}
15695
Ok(())
15796
}
@@ -185,17 +124,17 @@ impl App {
185124
AppWidgets::FileAction => {
186125
self.set_current_widget(AppWidgets::FileSelector);
187126
match self.file_action.state.selected() {
188-
Some(0) => self.files.run(false)?, // run
189-
Some(1) => self.files.run(true)?, // reset, then run
127+
Some(0) => self.run(false)?, // run
128+
Some(1) => self.run(true)?, // reset, then run
190129
Some(2) => self.activate_cbm_browser()?,
191130
_ => {}
192131
};
193132
self.file_action.unselect();
194133
}
195134
AppWidgets::CBMBrowser => {
196-
match self.files.cbm_browser.state.selected() {
135+
match self.cbm_browser.state.selected() {
197136
_ => {
198-
self.files.run(false)?;
137+
self.run(false)?;
199138
self.busy = false;
200139
self.active_widget = AppWidgets::FileSelector;
201140
}
@@ -208,9 +147,25 @@ impl App {
208147
_ => {}
209148
}
210149
match self.active_widget {
211-
AppWidgets::CBMBrowser => self.files.cbm_browser.keypress(key),
150+
AppWidgets::CBMBrowser => self.cbm_browser.keypress(key),
212151
AppWidgets::FileAction => self.file_action.keypress(key),
213-
AppWidgets::FileSelector => self.files.keypress(key),
152+
AppWidgets::FileSelector => {
153+
match key {
154+
KeyCode::Down => {
155+
self.filetable.next();
156+
Ok(())
157+
},
158+
KeyCode::Up => {
159+
self.filetable.previous();
160+
Ok(())
161+
},
162+
KeyCode::Char('s') => {
163+
self.sort_filehost();
164+
Ok(())
165+
},
166+
_ => {Ok(())}
167+
}
168+
}
214169
_ => Ok(()),
215170
}
216171
}
@@ -231,6 +186,47 @@ impl App {
231186
pub fn clear_status_line(&mut self) {
232187
//self.messages.clear();
233188
}
189+
190+
/// Toggles filehost file sorting by date or title
191+
fn sort_filehost(&mut self) {
192+
if self.toggle_sort {
193+
self.filetable.items.sort_by_key(|i| i.published.clone());
194+
self.filetable.items.reverse();
195+
} else {
196+
self.filetable.items.sort_by_key(|i| i.title.clone());
197+
}
198+
self.toggle_sort = !self.toggle_sort;
199+
}
200+
201+
pub fn selected_url(&self) -> String {
202+
let sel = self.filetable.state.selected().unwrap_or(0);
203+
let item = &self.filetable.items[sel];
204+
format!("https://files.mega65.org/{}", &item.location)
205+
}
206+
207+
/// Transfer and run selected file
208+
pub fn run(&mut self, reset_before_run: bool) -> Result<()> {
209+
let url = self.selected_url();
210+
if url.ends_with(".prg") {
211+
serial::handle_prg(&mut self.port, &url, reset_before_run, true)?;
212+
} else if url.ends_with(".d81") & self.cbm_disk.is_some() & self.cbm_browser.is_selected() {
213+
let selected_file = self.cbm_browser.state.selected().unwrap();
214+
let (load_address, bytes) =
215+
crate::io::cbm_load_file(self.cbm_disk.as_ref().unwrap().as_ref(), selected_file)?;
216+
serial::handle_prg_from_bytes(
217+
&mut self.port,
218+
&bytes,
219+
load_address,
220+
reset_before_run,
221+
true,
222+
)?;
223+
self.cbm_browser.unselect();
224+
self.cbm_disk = None;
225+
} else {
226+
return Err(anyhow::Error::msg("Cannot run selection"));
227+
}
228+
Ok(())
229+
}
234230
}
235231

236232
pub struct StatefulList<T> {
@@ -342,5 +338,4 @@ impl<T> StatefulTable<T> {
342338
pub fn unselect(&mut self) {
343339
self.state.select(None);
344340
}
345-
346341
}

src/textui/terminal.rs

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,19 +18,17 @@ use crossterm::{
1818
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
1919
};
2020

21-
use crate::textui::ui;
22-
use crate::textui::{App, AppWidgets};
23-
21+
use crate::filehost;
22+
use serialport::SerialPort;
23+
use crate::textui::{ui, App, AppWidgets};
2424
use anyhow::Result;
2525
use std::io;
2626
use tui::{
2727
backend::{Backend, CrosstermBackend},
2828
Terminal,
2929
};
3030

31-
use crate::filehost;
32-
use serialport::SerialPort;
33-
31+
/// This is the first entry for the TUI
3432
pub fn start_tui(
3533
port: &mut Box<dyn SerialPort>,
3634
filehost_items: &[filehost::Record],
@@ -69,11 +67,11 @@ pub fn run_app<B: Backend>(terminal: &mut Terminal<B>, mut app: App) -> Result<(
6967
match key.code {
7068
KeyCode::Char('q') => return Ok(()),
7169
KeyCode::Char('R') => {
72-
crate::serial::reset(&mut app.files.port)?;
70+
crate::serial::reset(&mut app.port)?;
7371
app.add_message("Reset MEGA65");
7472
}
7573
KeyCode::Enter => {
76-
if app.files.cbm_browser.is_selected() {
74+
if app.cbm_browser.is_selected() {
7775
app.busy = true;
7876
terminal.draw(|f| ui::ui(f, &mut app))?;
7977
} else {
@@ -86,7 +84,7 @@ pub fn run_app<B: Backend>(terminal: &mut Terminal<B>, mut app: App) -> Result<(
8684
Ok(()) => {}
8785
Err(error) => {
8886
app.add_message(error.to_string().as_str());
89-
app.files.cbm_browser.unselect();
87+
app.cbm_browser.unselect();
9088
app.active_widget = AppWidgets::FileSelector;
9189
}
9290
}

src/textui/ui.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,15 +33,15 @@ pub fn ui<B: Backend>(f: &mut Frame<B>, app: &mut App) {
3333
.constraints([Constraint::Min(4), Constraint::Length(8)].as_ref())
3434
.split(f.size());
3535

36-
let files_widget = make_files_widget(&app.files.filetable.items);
37-
f.render_stateful_widget(files_widget, chunks[0], &mut app.files.filetable.state);
36+
let files_widget = make_files_widget(&app.filetable.items);
37+
f.render_stateful_widget(files_widget, chunks[0], &mut app.filetable.state);
3838

3939
let chunks = Layout::default()
4040
.direction(Direction::Horizontal)
4141
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
4242
.split(chunks[1]);
4343

44-
let fileinfo_widget = make_fileinfo_widget(&app.files.filetable);
44+
let fileinfo_widget = make_fileinfo_widget(&app.filetable);
4545
f.render_widget(fileinfo_widget, chunks[0]);
4646

4747
let messages_widget = make_messages_widget(&app.messages);
@@ -56,7 +56,7 @@ pub fn ui<B: Backend>(f: &mut Frame<B>, app: &mut App) {
5656
}
5757

5858
if app.active_widget == AppWidgets::CBMBrowser {
59-
render_cbm_selector_widget(f, &mut app.files.cbm_browser, app.busy);
59+
render_cbm_selector_widget(f, &mut app.cbm_browser, app.busy);
6060
}
6161
}
6262

0 commit comments

Comments
 (0)