Add manual input elements to the app an ui #216

Merged
wojtek merged 17 commits from 188---add-option-for-manual-input-during-fetch into main 2024-09-15 15:20:11 +02:00
6 changed files with 141 additions and 79 deletions
Showing only changes of commit 664950d5a3 - Show all commits

130
Cargo.lock generated
View File

@ -65,7 +65,7 @@ version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
"hermit-abi",
"hermit-abi 0.1.19",
"libc",
"winapi",
]
@ -129,9 +129,9 @@ checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53"
[[package]]
name = "castaway"
version = "0.2.2"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a17ed5635fc8536268e5d4de1e22e81ac34419e5f052d4d51f4e01dcc263fcc"
checksum = "0abae9be0aaf9ea96a3b1b8b1b55c602ca751eba1b1500220cea4ecbafe7c0d5"
dependencies = [
"rustversion",
]
@ -168,13 +168,14 @@ dependencies = [
[[package]]
name = "compact_str"
version = "0.7.1"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f86b9c4c00838774a6d902ef931eff7470720c51d90c2e32cfe15dc304737b3f"
checksum = "6050c3a16ddab2e412160b31f2c871015704239bca62f72f6e5f0be631d3f644"
dependencies = [
"castaway",
"cfg-if",
"itoa",
"rustversion",
"ryu",
"static_assertions",
]
@ -197,15 +198,15 @@ checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
[[package]]
name = "crossterm"
version = "0.27.0"
version = "0.28.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df"
checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6"
dependencies = [
"bitflags 2.4.2",
"crossterm_winapi",
"libc",
"mio",
"mio 1.0.2",
"parking_lot",
"rustix",
"signal-hook",
"signal-hook-mio",
"winapi",
@ -393,9 +394,9 @@ dependencies = [
[[package]]
name = "heck"
version = "0.4.1"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "hermit-abi"
@ -406,6 +407,12 @@ dependencies = [
"libc",
]
[[package]]
name = "hermit-abi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
[[package]]
name = "http"
version = "0.2.12"
@ -498,10 +505,14 @@ dependencies = [
]
[[package]]
name = "indoc"
version = "2.0.4"
name = "instability"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e186cfbae8084e513daff4240b4797e342f988cecda4fb6c939150f96315fd8"
checksum = "b23a0c8dfe501baac4adf6ebbfa6eddf8f0c07f56b058cc1288017e32397846c"
dependencies = [
"quote",
"syn 2.0.48",
]
[[package]]
name = "ipnet"
@ -511,9 +522,9 @@ checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3"
[[package]]
name = "itertools"
version = "0.12.1"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
dependencies = [
"either",
]
@ -541,15 +552,15 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.153"
version = "0.2.158"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439"
[[package]]
name = "linux-raw-sys"
version = "0.4.13"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c"
checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
[[package]]
name = "lock_api"
@ -604,11 +615,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09"
dependencies = [
"libc",
"log",
"wasi",
"windows-sys 0.48.0",
]
[[package]]
name = "mio"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec"
dependencies = [
"hermit-abi 0.3.9",
"libc",
"log",
"wasi",
"windows-sys 0.52.0",
]
[[package]]
name = "mockall"
version = "0.12.1"
@ -653,6 +676,7 @@ dependencies = [
"structopt",
"tempfile",
"tokio",
"tui-input",
"url",
"uuid",
"version_check",
@ -911,21 +935,22 @@ dependencies = [
[[package]]
name = "ratatui"
version = "0.26.0"
version = "0.28.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "154b85ef15a5d1719bcaa193c3c81fe645cd120c156874cd660fe49fd21d1373"
checksum = "fdef7f9be5c0122f890d58bdf4d964349ba6a6161f705907526d891efabba57d"
dependencies = [
"bitflags 2.4.2",
"cassowary",
"compact_str",
"crossterm",
"indoc",
"instability",
"itertools",
"lru",
"paste",
"stability",
"strum",
"strum_macros",
"unicode-segmentation",
"unicode-truncate",
"unicode-width",
]
@ -986,9 +1011,9 @@ checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
[[package]]
name = "rustix"
version = "0.38.31"
version = "0.38.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949"
checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811"
dependencies = [
"bitflags 2.4.2",
"errno",
@ -1127,12 +1152,12 @@ dependencies = [
[[package]]
name = "signal-hook-mio"
version = "0.2.3"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af"
checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd"
dependencies = [
"libc",
"mio",
"mio 1.0.2",
"signal-hook",
]
@ -1189,16 +1214,6 @@ dependencies = [
"serde",
]
[[package]]
name = "stability"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebd1b177894da2a2d9120208c3386066af06a488255caabc5de8ddca22dbc3ce"
dependencies = [
"quote",
"syn 1.0.109",
]
[[package]]
name = "static_assertions"
version = "1.1.0"
@ -1246,11 +1261,11 @@ dependencies = [
[[package]]
name = "strum_macros"
version = "0.26.1"
version = "0.26.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a3417fc93d76740d974a01654a09777cb500428cc874ca9f45edfe0c4d4cd18"
checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be"
dependencies = [
"heck 0.4.1",
"heck 0.5.0",
"proc-macro2",
"quote",
"rustversion",
@ -1377,7 +1392,7 @@ dependencies = [
"backtrace",
"bytes",
"libc",
"mio",
"mio 0.8.10",
"pin-project-lite",
"signal-hook-registry",
"socket2",
@ -1470,6 +1485,16 @@ version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
[[package]]
name = "tui-input"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd137780d743c103a391e06fe952487f914b299a4fe2c3626677f6a6339a7c6b"
dependencies = [
"ratatui",
"unicode-width",
]
[[package]]
name = "typed-builder"
version = "0.18.1"
@ -1518,10 +1543,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202"
[[package]]
name = "unicode-width"
version = "0.1.11"
name = "unicode-truncate"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf"
dependencies = [
"itertools",
"unicode-segmentation",
"unicode-width",
]
[[package]]
name = "unicode-width"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d"
[[package]]
name = "url"

View File

@ -7,16 +7,18 @@ edition = "2021"
[dependencies]
aho-corasick = { version = "1.1.2", optional = true }
crossterm = { version = "0.27.0", optional = true}
crossterm = { version = "0.28.1", optional = true}
once_cell = { version = "1.19.0", optional = true}
openssh = { version = "0.10.3", features = ["native-mux"], default-features = false, optional = true}
paste = { version = "1.0.15", optional = true }
ratatui = { version = "0.26.0", optional = true}
ratatui = { version = "0.28.1", optional = true}
reqwest = { version = "0.11.25", features = ["blocking", "json"], optional = true }
serde = { version = "1.0.196", features = ["derive"], optional = true }
serde_json = { version = "1.0.113", optional = true}
structopt = { version = "0.3.26", optional = true}
tokio = { version = "1.36.0", features = ["rt"], optional = true}
# ratatui/crossterm dependency version must match with musichhoard's ratatui/crossterm
tui-input = { version = "0.10.1", optional = true }
url = { version = "2.5.0" }
uuid = { version = "1.7.0" }
@ -35,7 +37,7 @@ database-json = ["serde", "serde_json"]
library-beets = []
library-beets-ssh = ["openssh", "tokio"]
musicbrainz = ["paste", "reqwest", "serde", "serde_json"]
tui = ["aho-corasick", "crossterm", "once_cell", "ratatui"]
tui = ["aho-corasick", "crossterm", "once_cell", "ratatui", "tui-input"]
[[bin]]
name = "musichoard"

View File

@ -1,12 +1,14 @@
use tui_input::{backend::crossterm::EventHandler, Input};
use crate::tui::app::{
machine::{App, AppInner, AppMachine},
AppPublic, AppState, IAppInteractInput,
AppPublic, AppState, IAppInteractInput, InputEvent,
};
use super::match_state::MatchState;
pub struct InputState {
string: String,
input: Input,
client: InputClient,
}
@ -19,7 +21,7 @@ impl AppMachine<InputState> {
AppMachine {
inner,
state: InputState {
string: String::new(),
input: Input::default(),
client,
},
}
@ -36,7 +38,7 @@ impl<'a> From<&'a mut AppMachine<InputState>> for AppPublic<'a> {
fn from(machine: &'a mut AppMachine<InputState>) -> Self {
AppPublic {
inner: (&mut machine.inner).into(),
state: AppState::Input(&machine.state.string),
state: AppState::Input(&machine.state.input),
}
}
}
@ -44,13 +46,8 @@ impl<'a> From<&'a mut AppMachine<InputState>> for AppPublic<'a> {
impl IAppInteractInput for AppMachine<InputState> {
type APP = App;
fn append_character(mut self, ch: char) -> Self::APP {
self.state.string.push(ch);
self.into()
}
fn delete_character(mut self) -> Self::APP {
self.state.string.pop();
fn input(mut self, input: InputEvent) -> Self::APP {
self.state.input.handle_event(&crossterm::event::Event::Key(input));
self.into()
}

View File

@ -1,10 +1,12 @@
mod machine;
mod selection;
use crossterm::event::KeyEvent;
pub use machine::App;
pub use selection::{Category, Delta, Selection, WidgetState};
use musichoard::collection::{album::AlbumMeta, artist::ArtistMeta, Collection};
use tui_input::Input;
use crate::tui::lib::interface::musicbrainz::Match;
@ -133,11 +135,11 @@ pub trait IAppInteractMatch {
fn abort(self) -> Self::APP;
}
type InputEvent = KeyEvent;
pub trait IAppInteractInput {
type APP: IApp;
fn append_character(self, ch: char) -> Self::APP;
fn delete_character(self) -> Self::APP;
fn input(self, input: InputEvent) -> Self::APP;
fn confirm(self) -> Self::APP;
fn cancel(self) -> Self::APP;
}
@ -214,8 +216,19 @@ pub struct MatchStatePublic<'app> {
pub state: &'app mut WidgetState,
}
pub type AppPublicState<'app> =
AppState<(), (), (), &'app str, (), MatchStatePublic<'app>, &'app str, &'app str, &'app str>;
pub type InputStatePublic<'app> = &'app Input;
pub type AppPublicState<'app> = AppState<
(),
(),
(),
&'app str,
(),
MatchStatePublic<'app>,
InputStatePublic<'app>,
&'app str,
&'app str,
>;
impl<
BrowseState,

View File

@ -201,20 +201,17 @@ impl<APP: IApp> IEventHandlerPrivate<APP> for EventHandler {
fn handle_input_key_event(app: <APP as IApp>::InputState, key_event: KeyEvent) -> APP {
if key_event.modifiers == KeyModifiers::CONTROL {
return match key_event.code {
KeyCode::Char('g') | KeyCode::Char('G') => app.cancel(),
_ => app.no_op(),
match key_event.code {
KeyCode::Char('g') | KeyCode::Char('G') => return app.cancel(),
_ => {},
};
}
match key_event.code {
// Add/remove character.
KeyCode::Char(ch) => app.append_character(ch),
KeyCode::Backspace => app.delete_character(),
// Return.
KeyCode::Esc | KeyCode::Enter => app.confirm(),
// Othey keys.
_ => app.no_op(),
_ => app.input(key_event),
}
}

View File

@ -32,6 +32,8 @@ use crate::tui::{
},
};
use super::app::InputStatePublic;
pub trait IUi {
fn render<APP: IAppAccess>(app: &mut APP, frame: &mut Frame);
}
@ -68,7 +70,7 @@ impl Ui {
frame: &mut Frame,
) {
let active = selection.category();
let areas = FrameArea::new(frame.size());
let areas = FrameArea::new(frame.area());
let artist_state = ArtistState::new(
active == Category::Artist,
@ -106,7 +108,7 @@ impl Ui {
}
fn render_info_overlay(artists: &Collection, selection: &mut Selection, frame: &mut Frame) {
let area = OverlayBuilder::default().build(frame.size());
let area = OverlayBuilder::default().build(frame.area());
if selection.category() == Category::Artist {
let overlay = ArtistOverlay::new(artists, &selection.widget_state_artist().list);
@ -126,13 +128,13 @@ impl Ui {
let area = OverlayBuilder::default()
.with_width(OverlaySize::Value(39))
.with_height(OverlaySize::Value(4))
.build(frame.size());
.build(frame.area());
let reload_text = ReloadOverlay::paragraph();
UiWidget::render_overlay_widget("Reload", reload_text, area, false, frame);
}
fn render_fetch_overlay(frame: &mut Frame) {
let area = OverlayBuilder::default().build(frame.size());
let area = OverlayBuilder::default().build(frame.area());
let fetch_text = FetchOverlay::paragraph();
UiWidget::render_overlay_widget("Fetching", fetch_text, area, false, frame)
}
@ -142,15 +144,29 @@ impl Ui {
state: &mut WidgetState,
frame: &mut Frame,
) {
let area = OverlayBuilder::default().build(frame.size());
let area = OverlayBuilder::default().build(frame.area());
let st = MatchOverlay::new(info, state);
UiWidget::render_overlay_list_widget(&st.matching, st.list, st.state, true, area, frame)
}
fn render_input_overlay(input: InputStatePublic, frame: &mut Frame) {
let area = OverlayBuilder::default()
.with_height(OverlaySize::Value(3))
.build(frame.area());
UiWidget::render_overlay_widget("Input", Paragraph::new(input.value()), area, false, frame);
let width = area.width.max(3) - 3; // keep 2 for borders and 1 for cursor
let scroll = input.visual_scroll(width as usize);
frame.set_cursor_position((
area.x + ((input.visual_cursor()).max(scroll) - scroll) as u16 + 1,
area.y + 1,
))
}
fn render_error_overlay<S: AsRef<str>>(title: S, msg: S, frame: &mut Frame) {
let area = OverlayBuilder::default()
.with_height(OverlaySize::Value(4))
.build(frame.size());
.build(frame.area());
let error_text = ErrorOverlay::paragraph(msg.as_ref());
UiWidget::render_overlay_widget(title.as_ref(), error_text, area, true, frame);
}
@ -170,6 +186,7 @@ impl IUi for Ui {
AppState::Reload(()) => Self::render_reload_overlay(frame),
AppState::Fetch(()) => Self::render_fetch_overlay(frame),
AppState::Match(public) => Self::render_match_overlay(public.info, public.state, frame),
AppState::Input(input) => Self::render_input_overlay(input, frame),
AppState::Error(msg) => Self::render_error_overlay("Error", msg, frame),
AppState::Critical(msg) => Self::render_error_overlay("Critical Error", msg, frame),
_ => {}
@ -211,7 +228,7 @@ mod tests {
info: m.info,
state: m.state,
}),
AppState::Input(s) => AppState::Input(s),
AppState::Input(ref i) => AppState::Input(i),
AppState::Error(s) => AppState::Error(s),
AppState::Critical(s) => AppState::Critical(s),
},