Compare commits

..

2 Commits

Author SHA1 Message Date
Thomas Eppers
47752720b4 created a new struct for a layout without opening a yaml file 2021-10-20 17:50:12 +02:00
Thomas Eppers
a7f509b165 started decoupling widgets and ui 2021-10-20 15:05:56 +02:00
7 changed files with 210 additions and 75 deletions

View File

@ -26,5 +26,5 @@ pub struct Opt {
fn main() { fn main() {
//parse parameter //parse parameter
let opt = Opt::from_args(); let opt = Opt::from_args();
ui::Ui::run(&opt); ui::create_ui(&opt);
} }

View File

@ -1,8 +1,7 @@
use std::sync::mpsc;
use std::{io, thread}; use std::{io, thread};
use crate::Opt;
use termion::event::Key; use termion::event::Key;
use termion::input::TermRead;
use termion::raw::IntoRawMode; use termion::raw::IntoRawMode;
use tui::backend::TermionBackend; use tui::backend::TermionBackend;
use tui::layout::{Constraint, Direction, Layout}; use tui::layout::{Constraint, Direction, Layout};
@ -12,7 +11,6 @@ use crate::widget::info;
use crate::widget::repo_entry; use crate::widget::repo_entry;
use crate::widget::service_switcher; use crate::widget::service_switcher;
use crate::widget::tag_list; use crate::widget::tag_list;
use crate::Opt;
pub struct Ui { pub struct Ui {
state: State, state: State,
@ -56,7 +54,7 @@ impl Ui {
state: State::SelectService, state: State::SelectService,
repo: repo_entry::RepoEntry::new(repo_id), repo: repo_entry::RepoEntry::new(repo_id),
tags: tag_list::TagList::with_status("Tags are empty"), tags: tag_list::TagList::with_status("Tags are empty"),
services: service_switcher::ServiceSwitcher::new(&opt.config), services: service_switcher::ServiceSwitcher::new(&opt.config).unwrap(),
info: info::Info::new("Select image of edit Repository"), info: info::Info::new("Select image of edit Repository"),
}; };
@ -70,7 +68,7 @@ impl Ui {
let mut terminal = Terminal::new(backend).unwrap(); let mut terminal = Terminal::new(backend).unwrap();
//setup input thread //setup input thread
let receiver = ui.spawn_stdin_channel(); let receiver = super::spawn_stdin_channel();
//core interaction loop //core interaction loop
'core: loop { 'core: loop {
@ -90,10 +88,10 @@ impl Ui {
) )
.split(rect.size()); .split(rect.size());
let (list, state) = ui.services.render(&ui.state); let (list, state) = ui.services.render(ui.state == State::SelectService);
rect.render_stateful_widget(list, chunks[0], state); rect.render_stateful_widget(list, chunks[0], state);
rect.render_widget(ui.repo.render(&ui.state), chunks[1]); rect.render_widget(ui.repo.render(ui.state == State::EditRepo), chunks[1]);
let (list, state) = ui.tags.render(&ui.state); let (list, state) = ui.tags.render(ui.state == State::SelectTag);
rect.render_stateful_widget(list, chunks[2], state); rect.render_stateful_widget(list, chunks[2], state);
rect.render_widget(ui.info.render(), chunks[3]); rect.render_widget(ui.info.render(), chunks[3]);
}) })
@ -145,22 +143,24 @@ impl Ui {
} }
_ => (), _ => (),
}, },
Ok(Key::Char(key)) => { Ok(Key::Char(key)) => match ui.state {
if ui.state == State::EditRepo { State::SelectService => (),
State::EditRepo => {
ui.info.set_info("Editing Repository"); ui.info.set_info("Editing Repository");
ui.repo.handle_input(Key::Char(key));
} }
ui.repo.handle_input(&ui.state, Key::Char(key)); State::SelectTag => (),
ui.tags.handle_input(&ui.state, Key::Char(key)); },
} Ok(Key::Backspace) => match ui.state {
Ok(Key::Backspace) => { State::SelectService => (),
if ui.state == State::EditRepo { State::EditRepo => {
ui.info.set_info("Editing Repository"); ui.info.set_info("Editing Repository");
ui.repo.handle_input(Key::Backspace);
} }
ui.repo.handle_input(&ui.state, Key::Backspace); State::SelectTag => (),
ui.tags.handle_input(&ui.state, Key::Backspace); },
} Ok(Key::Up) => match ui.state {
Ok(Key::Up) => { State::SelectService if ui.services.find_previous_match() => {
if ui.state == State::SelectService && ui.services.find_previous_match() {
match ui.services.extract_repo() { match ui.services.extract_repo() {
Err(e) => ui.info.set_info(&format!("{}", e)), Err(e) => ui.info.set_info(&format!("{}", e)),
Ok(s) => { Ok(s) => {
@ -176,9 +176,10 @@ impl Ui {
} }
} }
} }
ui.tags.handle_input(&ui.state, Key::Up); State::SelectService => (),
ui.repo.handle_input(&ui.state, Key::Up); State::EditRepo => (),
} State::SelectTag => ui.tags.handle_input(Key::Up),
},
Ok(Key::Down) => match ui.state { Ok(Key::Down) => match ui.state {
State::SelectService if ui.services.find_next_match() => { State::SelectService if ui.services.find_next_match() => {
match ui.services.extract_repo() { match ui.services.extract_repo() {
@ -196,15 +197,10 @@ impl Ui {
} }
} }
} }
_ => { State::SelectService => (),
ui.tags.handle_input(&ui.state, Key::Down); State::EditRepo => (),
ui.repo.handle_input(&ui.state, Key::Down); State::SelectTag => ui.tags.handle_input(Key::Down),
}
}, },
Ok(key) => {
ui.repo.handle_input(&ui.state, Key::Down);
ui.tags.handle_input(&ui.state, key);
}
_ => (), _ => (),
} }
@ -214,18 +210,4 @@ impl Ui {
terminal.clear().unwrap(); terminal.clear().unwrap();
} }
/// create a thread for catching input and send them to core loop
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
}
} }

32
src/ui/mod.rs Normal file
View File

@ -0,0 +1,32 @@
mod default;
mod no_yaml;
use std::sync::mpsc;
use std::{io, thread};
use crate::Opt;
use termion::input::TermRead;
use crate::widget::service_switcher;
pub fn create_ui(opt: &Opt) {
let service_result = service_switcher::ServiceSwitcher::new(&opt.config);
match service_result {
None => no_yaml::NoYaml::run(opt),
Some(_) => default::Ui::run(opt),
}
}
/// create a thread for catching input and send them to core loop
pub fn spawn_stdin_channel() -> 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
}

138
src/ui/no_yaml.rs Normal file
View File

@ -0,0 +1,138 @@
use std::{io, thread};
use termion::event::Key;
use termion::raw::IntoRawMode;
use tui::backend::TermionBackend;
use tui::layout::{Constraint, Direction, Layout};
use tui::Terminal;
use crate::widget::info;
use crate::widget::repo_entry;
use crate::widget::tag_list;
use crate::Opt;
#[derive(PartialEq, Clone)]
pub enum State {
EditRepo,
SelectTag,
}
impl std::iter::Iterator for State {
type Item = Self;
fn next(&mut self) -> Option<Self::Item> {
match self {
State::EditRepo => *self = State::SelectTag,
State::SelectTag => *self = State::EditRepo,
}
Some(self.clone())
}
}
pub struct NoYaml {
state: State,
repo: repo_entry::RepoEntry,
tags: tag_list::TagList,
info: info::Info,
}
impl NoYaml {
pub fn run(opt: &Opt) {
let (repo, load_repo) = match &opt.repo {
None => (
repo_entry::RepoEntry::new(
"enter a repository or select one from docker-compose.yml",
),
false,
),
Some(repo_id) => (repo_entry::RepoEntry::new(repo_id), true),
};
let mut ui = NoYaml {
state: State::EditRepo,
repo,
tags: tag_list::TagList::with_status("Tags are empty"),
info: info::Info::new("edit the Repository widget to load tags"),
};
// load tags if a repository was given thorugh paramter
if load_repo {
ui.tags = tag_list::TagList::with_repo(ui.repo.get());
}
//setup tui
let stdout = io::stdout().into_raw_mode().unwrap();
let backend = TermionBackend::new(stdout);
let mut terminal = Terminal::new(backend).unwrap();
//setup input thread
let receiver = super::spawn_stdin_channel();
//core interaction loop
'core: loop {
//draw
terminal
.draw(|rect| {
let chunks = Layout::default()
.direction(Direction::Vertical)
.constraints(
[
Constraint::Length(3),
Constraint::Min(7),
Constraint::Length(2),
]
.as_ref(),
)
.split(rect.size());
rect.render_widget(ui.repo.render(ui.state == State::EditRepo), chunks[0]);
let (list, state) = ui.tags.render(ui.state == State::SelectTag);
rect.render_stateful_widget(list, chunks[1], state);
rect.render_widget(ui.info.render(), chunks[2]);
})
.unwrap();
//handle input
match receiver.try_recv() {
Ok(Key::Ctrl('q')) => break 'core,
Ok(Key::Char('\t')) => {
ui.state.next();
}
Ok(Key::Ctrl('r')) => {
ui.repo.confirm();
ui.tags = tag_list::TagList::with_repo(ui.repo.get());
}
Ok(Key::Ctrl('n')) => match ui.tags.next_page() {
Err(e) => ui.info.set_info(&format!("{}", e)),
Ok(_) => (),
},
Ok(Key::Ctrl('p')) => match ui.tags.prev_page() {
Err(e) => ui.info.set_info(&format!("{}", e)),
Ok(_) => (),
},
Ok(Key::Char('\n')) => match ui.state {
State::EditRepo => {
ui.repo.confirm();
ui.tags = tag_list::TagList::with_repo(ui.repo.get());
}
_ => (),
},
Ok(Key::Char(key)) => match ui.state {
State::EditRepo => {
ui.info.set_info("Editing Repository");
ui.repo.handle_input(Key::Char(key));
}
State::SelectTag => {
ui.tags.handle_input(Key::Char(key));
}
},
_ => (),
}
//sleep for 32ms (30 fps)
thread::sleep(std::time::Duration::from_millis(32));
}
terminal.clear().unwrap();
}
}

View File

@ -3,8 +3,6 @@ use tui::layout::Alignment;
use tui::style::{Color, Style}; use tui::style::{Color, Style};
use tui::widgets::{Block, Borders, Paragraph}; use tui::widgets::{Block, Borders, Paragraph};
use crate::ui::State;
pub struct RepoEntry { pub struct RepoEntry {
text: String, text: String,
old_text: String, old_text: String,
@ -29,13 +27,13 @@ impl RepoEntry {
self.old_text = entry; self.old_text = entry;
} }
pub fn render(&self, state: &crate::ui::State) -> Paragraph { pub fn render(&self, colored: bool) -> Paragraph {
let title = match self.changed { let title = match self.changed {
true => "Repository*", true => "Repository*",
false => "Repository", false => "Repository",
}; };
let border_style = if state == &crate::ui::State::EditRepo { let border_style = if colored {
Style::default().fg(Color::Green) Style::default().fg(Color::Green)
} else { } else {
Style::default().fg(Color::Gray) Style::default().fg(Color::Gray)
@ -52,11 +50,7 @@ impl RepoEntry {
.alignment(Alignment::Left) .alignment(Alignment::Left)
} }
pub fn handle_input(&mut self, state: &State, key: termion::event::Key) { pub fn handle_input(&mut self, key: termion::event::Key) {
if state != &State::EditRepo {
return;
}
match key { match key {
// Key::Char('\n') => self.confirm(), //handled in Ui // Key::Char('\n') => self.confirm(), //handled in Ui
Key::Char(c) => { Key::Char(c) => {

View File

@ -9,7 +9,6 @@ use tui::style::{Color, Style};
use tui::widgets::{Block, Borders, List, ListState}; use tui::widgets::{Block, Borders, List, ListState};
use crate::repo; use crate::repo;
use crate::ui::State;
#[derive(Debug)] #[derive(Debug)]
pub enum Error { pub enum Error {
@ -34,7 +33,7 @@ pub struct ServiceSwitcher {
} }
impl ServiceSwitcher { impl ServiceSwitcher {
pub fn new(file: &Option<PathBuf>) -> Self { pub fn new(file: &Option<PathBuf>) -> Option<Self> {
let mut file_list = vec![ let mut file_list = vec![
PathBuf::from("docker-compose.yml"), PathBuf::from("docker-compose.yml"),
PathBuf::from("docker-compose.yaml"), PathBuf::from("docker-compose.yaml"),
@ -55,25 +54,20 @@ impl ServiceSwitcher {
} }
}; };
return Self { return Some(Self {
list, list,
state: ListState::default(), state: ListState::default(),
changed: false, changed: false,
opened_file: file, opened_file: file,
}; });
} }
//could not find docker-compose file //could not find docker-compose file
Self { None
list: vec![format!("No docker-compose file found")],
state: ListState::default(),
changed: false,
opened_file: PathBuf::new(),
}
} }
pub fn render(&mut self, state: &State) -> (List, &mut ListState) { pub fn render(&mut self, colored: bool) -> (List, &mut ListState) {
let border_style = if state == &State::SelectService { let border_style = if colored {
Style::default().fg(Color::Green) Style::default().fg(Color::Green)
} else { } else {
Style::default().fg(Color::Gray) Style::default().fg(Color::Gray)

View File

@ -5,7 +5,6 @@ use tui::style::{Color, Style};
use tui::widgets::{Block, Borders, List, ListState}; use tui::widgets::{Block, Borders, List, ListState};
use crate::tags; use crate::tags;
use crate::ui::State;
#[derive(Debug)] #[derive(Debug)]
pub enum Error { pub enum Error {
@ -109,8 +108,8 @@ impl TagList {
} }
} }
pub fn render(&mut self, state: &State) -> (List, &mut ListState) { pub fn render(&mut self, colored: bool) -> (List, &mut ListState) {
let border_style = if state == &State::SelectTag { let border_style = if colored {
Style::default().fg(Color::Green) Style::default().fg(Color::Green)
} else { } else {
Style::default().fg(Color::Gray) Style::default().fg(Color::Gray)
@ -144,11 +143,7 @@ impl TagList {
(items, &mut self.state) (items, &mut self.state)
} }
pub fn handle_input(&mut self, state: &State, key: termion::event::Key) { pub fn handle_input(&mut self, key: termion::event::Key) {
if state != &State::SelectTag {
return;
}
match key { match key {
Key::Down => self.next(), Key::Down => self.next(),
Key::Up => self.previous(), Key::Up => self.previous(),