2021-08-23 14:06:00 +02:00
|
|
|
use std::sync::mpsc;
|
|
|
|
use std::{io, thread};
|
|
|
|
|
2021-08-25 12:23:15 +02:00
|
|
|
use termion::event::Key;
|
2021-08-23 14:06:00 +02:00
|
|
|
use termion::input::TermRead;
|
|
|
|
use termion::raw::IntoRawMode;
|
|
|
|
use tui::backend::TermionBackend;
|
2021-08-25 12:23:15 +02:00
|
|
|
use tui::layout::{Constraint, Direction, Layout};
|
2021-08-23 14:06:00 +02:00
|
|
|
use tui::Terminal;
|
|
|
|
|
2021-08-25 12:23:15 +02:00
|
|
|
use crate::widget::repo_entry;
|
2021-08-30 00:28:12 +02:00
|
|
|
use crate::widget::service_switcher;
|
2021-08-25 12:23:15 +02:00
|
|
|
use crate::widget::tag_list;
|
2021-08-23 14:06:00 +02:00
|
|
|
|
|
|
|
pub struct Ui {
|
|
|
|
state: State,
|
|
|
|
repo: crate::widget::repo_entry::RepoEntry,
|
|
|
|
tags: crate::widget::tag_list::TagList,
|
2021-08-30 00:28:12 +02:00
|
|
|
services: crate::widget::service_switcher::ServiceSwitcher,
|
2021-08-23 14:06:00 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(PartialEq, Clone)]
|
|
|
|
pub enum State {
|
|
|
|
EditRepo,
|
|
|
|
SelectTag,
|
2021-08-30 00:28:12 +02:00
|
|
|
SelectService,
|
2021-08-23 14:06:00 +02:00
|
|
|
}
|
|
|
|
|
2021-08-31 01:40:29 +02:00
|
|
|
impl State {
|
|
|
|
fn next(&self) -> State {
|
|
|
|
match self {
|
|
|
|
State::EditRepo => State::SelectTag,
|
|
|
|
State::SelectTag => State::SelectService,
|
|
|
|
State::SelectService => State::EditRepo,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-23 14:06:00 +02:00
|
|
|
impl Ui {
|
2021-08-29 00:23:40 +02:00
|
|
|
pub fn run(repo_id: &str) {
|
2021-08-23 14:06:00 +02:00
|
|
|
let mut ui = Ui {
|
2021-08-30 00:28:12 +02:00
|
|
|
state: State::SelectService,
|
2021-08-29 00:23:40 +02:00
|
|
|
repo: repo_entry::RepoEntry::new(repo_id),
|
2021-09-01 20:03:49 +02:00
|
|
|
tags: tag_list::TagList::with_status("Fetching Tags"),
|
2021-08-30 00:28:12 +02:00
|
|
|
services: service_switcher::ServiceSwitcher::new(),
|
2021-08-23 14:06:00 +02:00
|
|
|
};
|
2021-09-01 20:03:49 +02:00
|
|
|
ui.tags = tag_list::TagList::with_repo(ui.repo.get());
|
2021-08-23 14:06:00 +02:00
|
|
|
|
|
|
|
//setup tui
|
2021-08-25 12:23:15 +02:00
|
|
|
let stdout = io::stdout().into_raw_mode().unwrap();
|
2021-08-23 14:06:00 +02:00
|
|
|
let backend = TermionBackend::new(stdout);
|
2021-08-25 12:23:15 +02:00
|
|
|
let mut terminal = Terminal::new(backend).unwrap();
|
2021-08-23 14:06:00 +02:00
|
|
|
|
|
|
|
//setup input thread
|
|
|
|
let receiver = ui.spawn_stdin_channel();
|
|
|
|
|
|
|
|
//core interaction loop
|
|
|
|
'core: loop {
|
|
|
|
//draw
|
2021-08-25 12:23:15 +02:00
|
|
|
terminal
|
|
|
|
.draw(|rect| {
|
|
|
|
let chunks = Layout::default()
|
|
|
|
.direction(Direction::Vertical)
|
2021-08-30 00:28:12 +02:00
|
|
|
.constraints(
|
|
|
|
[
|
|
|
|
Constraint::Min(3),
|
|
|
|
Constraint::Length(3),
|
|
|
|
Constraint::Max(7),
|
|
|
|
]
|
|
|
|
.as_ref(),
|
|
|
|
)
|
2021-08-25 12:23:15 +02:00
|
|
|
.split(rect.size());
|
2021-08-23 14:06:00 +02:00
|
|
|
|
2021-08-30 00:28:12 +02:00
|
|
|
let (list, state) = ui.services.render(&ui.state);
|
|
|
|
rect.render_stateful_widget(list, chunks[0], state);
|
|
|
|
rect.render_widget(ui.repo.render(&ui.state), chunks[1]);
|
2021-08-28 17:37:55 +02:00
|
|
|
let (list, state) = ui.tags.render(&ui.state);
|
2021-08-30 00:28:12 +02:00
|
|
|
rect.render_stateful_widget(list, chunks[2], state);
|
2021-08-25 12:23:15 +02:00
|
|
|
})
|
|
|
|
.unwrap();
|
2021-08-23 14:06:00 +02:00
|
|
|
|
|
|
|
//handle input
|
|
|
|
match receiver.try_recv() {
|
|
|
|
Ok(Key::Ctrl('q')) => break 'core, //quit program without saving
|
2021-08-31 01:40:29 +02:00
|
|
|
Ok(Key::Char('\t')) => ui.state = ui.state.next(),
|
2021-08-31 16:37:04 +02:00
|
|
|
Ok(Key::Ctrl('s')) => {
|
|
|
|
match ui.services.save() {
|
|
|
|
Err(_) => (), //TODO proper error handling
|
|
|
|
Ok(_) => (),
|
|
|
|
}
|
|
|
|
}
|
2021-08-31 01:40:29 +02:00
|
|
|
Ok(Key::Char('\n')) => match ui.state {
|
|
|
|
State::EditRepo => {
|
2021-08-23 14:06:00 +02:00
|
|
|
ui.repo.confirm();
|
2021-09-01 20:03:49 +02:00
|
|
|
ui.tags = tag_list::TagList::with_repo(ui.repo.get());
|
2021-08-23 14:06:00 +02:00
|
|
|
}
|
2021-08-31 01:40:29 +02:00
|
|
|
State::SelectTag => {
|
|
|
|
let mut repo = ui.services.extract_repo().unwrap();
|
2021-09-01 20:03:49 +02:00
|
|
|
let tag = match ui.tags.get_selected() {
|
|
|
|
Err(_) => continue,
|
|
|
|
Ok(tag) => tag,
|
|
|
|
};
|
2021-09-01 18:28:17 +02:00
|
|
|
repo.push_str(":");
|
2021-08-31 01:40:29 +02:00
|
|
|
repo.push_str(&tag);
|
|
|
|
ui.services.change_current_line(repo);
|
|
|
|
}
|
|
|
|
_ => (),
|
|
|
|
},
|
2021-08-28 17:37:55 +02:00
|
|
|
Ok(Key::Char(key)) => {
|
|
|
|
if ui.state == State::EditRepo {
|
2021-09-01 20:03:49 +02:00
|
|
|
ui.tags = tag_list::TagList::with_status("Editing Repository");
|
2021-08-23 14:06:00 +02:00
|
|
|
}
|
2021-08-28 17:37:55 +02:00
|
|
|
ui.repo.handle_input(&ui.state, Key::Char(key));
|
|
|
|
ui.tags.handle_input(&ui.state, Key::Char(key));
|
2021-08-23 14:06:00 +02:00
|
|
|
}
|
|
|
|
Ok(Key::Backspace) => {
|
2021-08-28 17:37:55 +02:00
|
|
|
if ui.state == State::EditRepo {
|
2021-09-01 20:03:49 +02:00
|
|
|
ui.tags = tag_list::TagList::with_status("Editing Repository");
|
2021-08-28 17:37:55 +02:00
|
|
|
}
|
|
|
|
ui.repo.handle_input(&ui.state, Key::Backspace);
|
|
|
|
ui.tags.handle_input(&ui.state, Key::Backspace);
|
2021-08-23 14:06:00 +02:00
|
|
|
}
|
2021-08-30 00:28:12 +02:00
|
|
|
Ok(Key::Up) => {
|
|
|
|
if ui.state == State::SelectService && ui.services.find_previous_match() {
|
|
|
|
match ui.services.extract_repo() {
|
2021-09-01 20:03:49 +02:00
|
|
|
Err(_) => ui.tags = tag_list::TagList::with_status("no image found"),
|
2021-08-30 00:28:12 +02:00
|
|
|
Ok(s) => ui.repo.set(s),
|
|
|
|
}
|
|
|
|
}
|
2021-09-01 18:28:17 +02:00
|
|
|
ui.tags.handle_input(&ui.state, Key::Up);
|
2021-08-30 00:28:12 +02:00
|
|
|
ui.repo.handle_input(&ui.state, Key::Up);
|
|
|
|
}
|
2021-09-01 18:28:17 +02:00
|
|
|
Ok(Key::Down) => match ui.state {
|
|
|
|
State::SelectService if ui.services.find_next_match() => {
|
2021-08-30 00:28:12 +02:00
|
|
|
match ui.services.extract_repo() {
|
2021-09-01 20:03:49 +02:00
|
|
|
Err(_) => ui.tags = tag_list::TagList::with_status("no image found"),
|
2021-09-01 18:28:17 +02:00
|
|
|
Ok(s) => {
|
|
|
|
ui.repo.set(s);
|
2021-09-01 20:03:49 +02:00
|
|
|
ui.tags = tag_list::TagList::with_repo(ui.repo.get());
|
2021-09-01 18:28:17 +02:00
|
|
|
}
|
2021-08-30 00:28:12 +02:00
|
|
|
}
|
|
|
|
}
|
2021-09-01 18:28:17 +02:00
|
|
|
_ => {
|
|
|
|
ui.tags.handle_input(&ui.state, Key::Down);
|
|
|
|
ui.repo.handle_input(&ui.state, Key::Down);
|
|
|
|
}
|
|
|
|
},
|
2021-08-23 14:06:00 +02:00
|
|
|
Ok(key) => {
|
2021-09-01 18:28:17 +02:00
|
|
|
ui.repo.handle_input(&ui.state, Key::Down);
|
2021-08-28 17:37:55 +02:00
|
|
|
ui.tags.handle_input(&ui.state, key);
|
2021-08-23 14:06:00 +02:00
|
|
|
}
|
|
|
|
_ => (),
|
|
|
|
}
|
|
|
|
|
2021-08-30 00:28:12 +02:00
|
|
|
//sleep for 32ms (30 fps)
|
2021-08-25 10:34:42 +02:00
|
|
|
thread::sleep(std::time::Duration::from_millis(32));
|
2021-08-23 14:06:00 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn spawn_stdin_channel(&self) -> mpsc::Receiver<termion::event::Key> {
|
|
|
|
let (tx, rx) = mpsc::channel::<termion::event::Key>();
|
|
|
|
|
|
|
|
thread::spawn(move || loop {
|
|
|
|
let stdin = io::stdin();
|
|
|
|
for c in stdin.keys() {
|
|
|
|
tx.send(c.unwrap()).unwrap();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
thread::sleep(std::time::Duration::from_millis(64));
|
|
|
|
rx
|
|
|
|
}
|
|
|
|
}
|