WIP
This commit is contained in:
parent
b26022b993
commit
79eef9f043
60
Cargo.lock
generated
60
Cargo.lock
generated
@ -32,6 +32,12 @@ version = "1.0.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b700ce4376041dcd0a327fd0097c41095743c4c8af8887265942faf1100bd040"
|
checksum = "b700ce4376041dcd0a327fd0097c41095743c4c8af8887265942faf1100bd040"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cassowary"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.0.69"
|
version = "1.0.69"
|
||||||
@ -437,6 +443,12 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "numtoa"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.8.0"
|
version = "1.8.0"
|
||||||
@ -523,6 +535,8 @@ dependencies = [
|
|||||||
"reqwest",
|
"reqwest",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"termion",
|
||||||
|
"tui",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -583,6 +597,15 @@ dependencies = [
|
|||||||
"bitflags",
|
"bitflags",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "redox_termios"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8440d8acb4fd3d277125b4bd01a6f38aee8d814b3b5fc09b3f2b825d37d3fe8f"
|
||||||
|
dependencies = [
|
||||||
|
"redox_syscall",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "remove_dir_all"
|
name = "remove_dir_all"
|
||||||
version = "0.5.3"
|
version = "0.5.3"
|
||||||
@ -750,6 +773,18 @@ dependencies = [
|
|||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "termion"
|
||||||
|
version = "1.5.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "077185e2eac69c3f8379a4298e1e07cd36beb962290d4a51199acf0fdc10607e"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"numtoa",
|
||||||
|
"redox_syscall",
|
||||||
|
"redox_termios",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "time"
|
name = "time"
|
||||||
version = "0.1.44"
|
version = "0.1.44"
|
||||||
@ -848,6 +883,19 @@ version = "0.2.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642"
|
checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tui"
|
||||||
|
version = "0.16.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "39c8ce4e27049eed97cfa363a5048b09d995e209994634a0efc26a14ab6c0c23"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"cassowary",
|
||||||
|
"termion",
|
||||||
|
"unicode-segmentation",
|
||||||
|
"unicode-width",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-bidi"
|
name = "unicode-bidi"
|
||||||
version = "0.3.6"
|
version = "0.3.6"
|
||||||
@ -863,6 +911,18 @@ dependencies = [
|
|||||||
"tinyvec",
|
"tinyvec",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-segmentation"
|
||||||
|
version = "1.8.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-width"
|
||||||
|
version = "0.1.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-xid"
|
name = "unicode-xid"
|
||||||
version = "0.2.2"
|
version = "0.2.2"
|
||||||
|
@ -10,5 +10,5 @@ serde = { version = "1.0.127", features = ["derive"] }
|
|||||||
serde_json = "1.0.66"
|
serde_json = "1.0.66"
|
||||||
reqwest = { version = "0.11.4", features = ["blocking", "json"] }
|
reqwest = { version = "0.11.4", features = ["blocking", "json"] }
|
||||||
chrono = "0.4.19"
|
chrono = "0.4.19"
|
||||||
# crossterm = "0.17"
|
tui = "0.16"
|
||||||
# tui = { version = "0.12", default-features = false, features = ['crossterm'] }
|
termion = "1.5"
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
use chrono::DateTime;
|
use chrono::DateTime;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
mod ui;
|
||||||
|
mod widget;
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct Image {
|
struct Image {
|
||||||
architecture: String,
|
architecture: String,
|
||||||
@ -52,6 +55,8 @@ fn main() {
|
|||||||
let dif = now - rfc3339.with_timezone(&chrono::Utc);
|
let dif = now - rfc3339.with_timezone(&chrono::Utc);
|
||||||
println!("{} vor {}", result.tag_name, format_time_nice(dif));
|
println!("{} vor {}", result.tag_name, format_time_nice(dif));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ui::Ui::new();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn format_time_nice(time: chrono::Duration) -> String {
|
fn format_time_nice(time: chrono::Duration) -> String {
|
||||||
|
1
src/states.rs
Normal file
1
src/states.rs
Normal file
@ -0,0 +1 @@
|
|||||||
|
pub trait Screen {}
|
121
src/ui.rs
Normal file
121
src/ui.rs
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
use core::time::Duration;
|
||||||
|
use std::sync::mpsc;
|
||||||
|
use std::{io, thread};
|
||||||
|
|
||||||
|
use termion::event::{Event, Key};
|
||||||
|
use termion::input::TermRead;
|
||||||
|
use termion::raw::IntoRawMode;
|
||||||
|
use tui::backend::TermionBackend;
|
||||||
|
use tui::layout::{Alignment, Constraint, Direction, Layout};
|
||||||
|
use tui::style::{Color, Style};
|
||||||
|
use tui::widgets::{Block, Borders, Paragraph};
|
||||||
|
use tui::Terminal;
|
||||||
|
|
||||||
|
use crate::widget::Widget;
|
||||||
|
|
||||||
|
pub struct Ui {
|
||||||
|
state: State,
|
||||||
|
repo: crate::widget::repo_entry::RepoEntry,
|
||||||
|
tags: crate::widget::tag_list::TagList,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Clone)]
|
||||||
|
pub enum State {
|
||||||
|
EditRepo,
|
||||||
|
SelectTag,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ui {
|
||||||
|
pub fn new() -> Result<Self, io::Error> {
|
||||||
|
let mut ui = Ui {
|
||||||
|
state: State::EditRepo,
|
||||||
|
repo: crate::widget::repo_entry::RepoEntry::new("This is a text"),
|
||||||
|
tags: crate::widget::tag_list::TagList::new(vec![
|
||||||
|
String::from("first"),
|
||||||
|
String::from("second"),
|
||||||
|
String::from("third"),
|
||||||
|
String::from("sdfs"),
|
||||||
|
]),
|
||||||
|
};
|
||||||
|
|
||||||
|
//setup tui
|
||||||
|
let stdout = io::stdout().into_raw_mode()?;
|
||||||
|
let backend = TermionBackend::new(stdout);
|
||||||
|
let mut terminal = Terminal::new(backend)?;
|
||||||
|
|
||||||
|
//setup input thread
|
||||||
|
let receiver = ui.spawn_stdin_channel();
|
||||||
|
|
||||||
|
//core interaction loop
|
||||||
|
'core: loop {
|
||||||
|
//draw
|
||||||
|
terminal.draw(|rect| {
|
||||||
|
let chunks = Layout::default()
|
||||||
|
.direction(Direction::Vertical)
|
||||||
|
// .margin(1)
|
||||||
|
.constraints([Constraint::Length(3), Constraint::Min(1)].as_ref())
|
||||||
|
.split(rect.size());
|
||||||
|
|
||||||
|
rect.render_widget(ui.repo.render(), chunks[0]);
|
||||||
|
let (list, state) = ui.tags.render();
|
||||||
|
rect.render_stateful_widget(list, chunks[1], state);
|
||||||
|
})?;
|
||||||
|
|
||||||
|
//handle input
|
||||||
|
match receiver.try_recv() {
|
||||||
|
Ok(Key::Ctrl('q')) => break 'core, //quit program without saving
|
||||||
|
Ok(Key::Ctrl('s')) => {
|
||||||
|
if ui.state == State::SelectTag {
|
||||||
|
//TODO save currently selected tag
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(Key::Char('\n')) => {
|
||||||
|
if ui.state == State::EditRepo {
|
||||||
|
ui.state = State::SelectTag;
|
||||||
|
ui.repo.confirm();
|
||||||
|
//TODO query tags and show them switch
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(Key::Down) => {
|
||||||
|
if ui.state == State::SelectTag {
|
||||||
|
//TODO select tag
|
||||||
|
ui.tags.next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(Key::Up) => {
|
||||||
|
if ui.state == State::SelectTag {
|
||||||
|
//TODO select tag
|
||||||
|
ui.tags.previous();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(Key::Backspace) => {
|
||||||
|
ui.state = State::EditRepo;
|
||||||
|
ui.repo.input(Key::Backspace);
|
||||||
|
}
|
||||||
|
Ok(key) => {
|
||||||
|
ui.state = State::EditRepo;
|
||||||
|
ui.repo.input(key);
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
|
||||||
|
//sleep for 64ms (15 fps)
|
||||||
|
thread::sleep(std::time::Duration::from_millis(64));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(ui)
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
61
src/widget/mod.rs
Normal file
61
src/widget/mod.rs
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
use tui::widgets::ListState;
|
||||||
|
|
||||||
|
pub mod repo_entry;
|
||||||
|
pub mod tag_list;
|
||||||
|
|
||||||
|
pub trait Widget {
|
||||||
|
fn input(&mut self, event: termion::event::Key);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct StatefulList<T> {
|
||||||
|
pub state: ListState,
|
||||||
|
pub items: Vec<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> StatefulList<T> {
|
||||||
|
pub fn new() -> StatefulList<T> {
|
||||||
|
StatefulList {
|
||||||
|
state: ListState::default(),
|
||||||
|
items: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_items(items: Vec<T>) -> StatefulList<T> {
|
||||||
|
StatefulList {
|
||||||
|
state: ListState::default(),
|
||||||
|
items,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next(&mut self) {
|
||||||
|
let i = match self.state.selected() {
|
||||||
|
Some(i) => {
|
||||||
|
if i >= self.items.len() - 1 {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
i + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => 0,
|
||||||
|
};
|
||||||
|
self.state.select(Some(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn previous(&mut self) {
|
||||||
|
let i = match self.state.selected() {
|
||||||
|
Some(i) => {
|
||||||
|
if i == 0 {
|
||||||
|
self.items.len() - 1
|
||||||
|
} else {
|
||||||
|
i - 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => 0,
|
||||||
|
};
|
||||||
|
self.state.select(Some(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unselect(&mut self) {
|
||||||
|
self.state.select(None);
|
||||||
|
}
|
||||||
|
}
|
56
src/widget/repo_entry.rs
Normal file
56
src/widget/repo_entry.rs
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
use termion::event::Key;
|
||||||
|
use tui::layout::Alignment;
|
||||||
|
use tui::style::{Color, Style};
|
||||||
|
use tui::widgets::{Block, Borders, Paragraph};
|
||||||
|
|
||||||
|
pub struct RepoEntry {
|
||||||
|
text: String,
|
||||||
|
old_text: String,
|
||||||
|
changed: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RepoEntry {
|
||||||
|
pub fn new(text: &str) -> Self {
|
||||||
|
Self {
|
||||||
|
text: String::from(text),
|
||||||
|
old_text: String::from(text),
|
||||||
|
changed: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render(&self) -> Paragraph {
|
||||||
|
let title = match self.changed {
|
||||||
|
true => "Repository*",
|
||||||
|
false => "Repository",
|
||||||
|
};
|
||||||
|
Paragraph::new(self.text.clone())
|
||||||
|
.block(Block::default().title(title).borders(Borders::ALL))
|
||||||
|
.style(Style::default().fg(Color::White).bg(Color::Black))
|
||||||
|
.alignment(Alignment::Left)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn confirm(&mut self) {
|
||||||
|
self.old_text = self.text.clone();
|
||||||
|
self.changed = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl super::Widget for RepoEntry {
|
||||||
|
fn input(&mut self, key: termion::event::Key) {
|
||||||
|
match key {
|
||||||
|
Key::Esc => {
|
||||||
|
self.text = self.old_text.clone(); //TODO return to other structure
|
||||||
|
self.changed = false;
|
||||||
|
}
|
||||||
|
Key::Backspace => {
|
||||||
|
self.text.pop();
|
||||||
|
self.changed = true;
|
||||||
|
}
|
||||||
|
Key::Char(c) => {
|
||||||
|
self.text.push(c);
|
||||||
|
self.changed = true;
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
56
src/widget/tag_list.rs
Normal file
56
src/widget/tag_list.rs
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
use termion::event::Key;
|
||||||
|
use tui::layout::Alignment;
|
||||||
|
use tui::style::{Color, Style};
|
||||||
|
use tui::widgets::{Block, Borders, List, ListState};
|
||||||
|
|
||||||
|
pub struct TagList {
|
||||||
|
list: super::StatefulList<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TagList {
|
||||||
|
pub fn new(items: Vec<String>) -> Self {
|
||||||
|
Self {
|
||||||
|
list: super::StatefulList::with_items(items),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render(&mut self) -> (List, &mut ListState) {
|
||||||
|
let items: Vec<tui::widgets::ListItem> = self
|
||||||
|
.list
|
||||||
|
.items
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|i| {
|
||||||
|
let mut lines = vec![tui::text::Spans::from(i.1.as_ref())];
|
||||||
|
for _ in 0..i.0 {
|
||||||
|
lines.push(tui::text::Spans::from(tui::text::Span::styled(
|
||||||
|
"Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
|
||||||
|
Style::default().add_modifier(tui::style::Modifier::ITALIC),
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
tui::widgets::ListItem::new(lines)
|
||||||
|
.style(Style::default().fg(Color::Black).bg(Color::White))
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// Create a List from all list items and highlight the currently selected one
|
||||||
|
let items = List::new(items)
|
||||||
|
.block(Block::default().borders(Borders::ALL).title("List"))
|
||||||
|
.highlight_style(
|
||||||
|
Style::default()
|
||||||
|
.bg(Color::LightGreen)
|
||||||
|
.add_modifier(tui::style::Modifier::BOLD),
|
||||||
|
)
|
||||||
|
.highlight_symbol(">> ");
|
||||||
|
|
||||||
|
(items, &mut self.list.state)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next(&mut self) {
|
||||||
|
self.list.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn previous(&mut self) {
|
||||||
|
self.list.previous();
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user