mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-07 12:16:42 +01:00
remodularize 3
This commit is contained in:
parent
d38dc14e84
commit
113e7b0bad
24 changed files with 262 additions and 370 deletions
109
Cargo.lock
generated
109
Cargo.lock
generated
|
|
@ -23,55 +23,6 @@ version = "0.2.21"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
|
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "anstream"
|
|
||||||
version = "0.6.18"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b"
|
|
||||||
dependencies = [
|
|
||||||
"anstyle",
|
|
||||||
"anstyle-parse",
|
|
||||||
"anstyle-query",
|
|
||||||
"anstyle-wincon",
|
|
||||||
"colorchoice",
|
|
||||||
"is_terminal_polyfill",
|
|
||||||
"utf8parse",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "anstyle"
|
|
||||||
version = "1.0.10"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "anstyle-parse"
|
|
||||||
version = "0.2.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9"
|
|
||||||
dependencies = [
|
|
||||||
"utf8parse",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "anstyle-query"
|
|
||||||
version = "1.1.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c"
|
|
||||||
dependencies = [
|
|
||||||
"windows-sys 0.59.0",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "anstyle-wincon"
|
|
||||||
version = "3.0.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125"
|
|
||||||
dependencies = [
|
|
||||||
"anstyle",
|
|
||||||
"windows-sys 0.59.0",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "approx"
|
name = "approx"
|
||||||
version = "0.5.1"
|
version = "0.5.1"
|
||||||
|
|
@ -181,46 +132,6 @@ version = "1.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "clap"
|
|
||||||
version = "4.5.23"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84"
|
|
||||||
dependencies = [
|
|
||||||
"clap_builder",
|
|
||||||
"clap_derive",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "clap_builder"
|
|
||||||
version = "4.5.23"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838"
|
|
||||||
dependencies = [
|
|
||||||
"anstream",
|
|
||||||
"anstyle",
|
|
||||||
"clap_lex",
|
|
||||||
"strsim",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "clap_derive"
|
|
||||||
version = "4.5.18"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab"
|
|
||||||
dependencies = [
|
|
||||||
"heck",
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "clap_lex"
|
|
||||||
version = "0.7.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clojure-reader"
|
name = "clojure-reader"
|
||||||
version = "0.3.0"
|
version = "0.3.0"
|
||||||
|
|
@ -230,12 +141,6 @@ dependencies = [
|
||||||
"ordered-float",
|
"ordered-float",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "colorchoice"
|
|
||||||
version = "1.0.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "compact_str"
|
name = "compact_str"
|
||||||
version = "0.8.1"
|
version = "0.8.1"
|
||||||
|
|
@ -509,12 +414,6 @@ dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "is_terminal_polyfill"
|
|
||||||
version = "1.70.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itertools"
|
name = "itertools"
|
||||||
version = "0.13.0"
|
version = "0.13.0"
|
||||||
|
|
@ -1409,9 +1308,7 @@ name = "tek"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"backtrace",
|
"backtrace",
|
||||||
"clap",
|
|
||||||
"livi",
|
"livi",
|
||||||
"once_cell",
|
|
||||||
"palette",
|
"palette",
|
||||||
"rand",
|
"rand",
|
||||||
"symphonia",
|
"symphonia",
|
||||||
|
|
@ -1590,12 +1487,6 @@ version = "0.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd"
|
checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "utf8parse"
|
|
||||||
version = "0.2.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uuid"
|
name = "uuid"
|
||||||
version = "1.11.0"
|
version = "1.11.0"
|
||||||
|
|
|
||||||
42
Cargo.toml
42
Cargo.toml
|
|
@ -10,14 +10,13 @@ tek_time = { path = "./time" }
|
||||||
tek_midi = { path = "./midi" }
|
tek_midi = { path = "./midi" }
|
||||||
|
|
||||||
backtrace = "0.3.72"
|
backtrace = "0.3.72"
|
||||||
clap = { version = "4.5.4", features = [ "derive" ] }
|
|
||||||
livi = "0.7.4"
|
livi = "0.7.4"
|
||||||
once_cell = "1.19.0"
|
|
||||||
palette = { version = "0.7.6", features = [ "random" ] }
|
palette = { version = "0.7.6", features = [ "random" ] }
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
symphonia = { version = "0.5.4", features = [ "all" ] }
|
symphonia = { version = "0.5.4", features = [ "all" ] }
|
||||||
toml = "0.8.12"
|
toml = "0.8.12"
|
||||||
wavers = "1.4.3"
|
wavers = "1.4.3"
|
||||||
|
#once_cell = "1.19.0"
|
||||||
#no_deadlocks = "1.3.2"
|
#no_deadlocks = "1.3.2"
|
||||||
#suil-rs = { path = "../suil" }
|
#suil-rs = { path = "../suil" }
|
||||||
#vst = "0.4.0"
|
#vst = "0.4.0"
|
||||||
|
|
@ -27,44 +26,5 @@ wavers = "1.4.3"
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = "tek"
|
|
||||||
path = "bin/tek.rs"
|
|
||||||
|
|
||||||
#[[bin]]
|
|
||||||
#name = "tek_arranger"
|
|
||||||
#path = "bin/cli_arranger.rs"
|
|
||||||
|
|
||||||
#[[bin]]
|
|
||||||
#name = "tek_sequencer"
|
|
||||||
#path = "bin/cli_sequencer.rs"
|
|
||||||
|
|
||||||
#[[bin]]
|
|
||||||
#name = "tek_groovebox"
|
|
||||||
#path = "bin/cli_groovebox.rs"
|
|
||||||
|
|
||||||
#[[bin]]
|
|
||||||
#name = "tek_clock"
|
|
||||||
#path = "bin/cli_clock.rs"
|
|
||||||
|
|
||||||
#[[bin]]
|
|
||||||
#name = "tek_sampler"
|
|
||||||
#path = "bin/cli_sampler.rs"
|
|
||||||
|
|
||||||
#[[bin]]
|
|
||||||
#name = "tek_mixer"
|
|
||||||
#path = "src/cli_mixer.rs"
|
|
||||||
|
|
||||||
#[[bin]]
|
|
||||||
#name = "tek_track"
|
|
||||||
#path = "src/cli_track.rs"
|
|
||||||
|
|
||||||
#[[bin]]
|
|
||||||
#name = "tek_plugin"
|
|
||||||
#path = "src/cli_plugin.rs"
|
|
||||||
|
|
||||||
[lib]
|
|
||||||
path = "src/lib.rs"
|
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
lto = true
|
lto = true
|
||||||
|
|
|
||||||
2
Justfile
2
Justfile
|
|
@ -132,4 +132,4 @@ test:
|
||||||
cargo test
|
cargo test
|
||||||
|
|
||||||
cloc:
|
cloc:
|
||||||
for src in {edn,input,jack,midi,output,time,tui,.}; do echo $src; cloc $src/src; done
|
for src in {cli/src,edn/src,examples,input/src,jack/src,midi/src,output/src,time/src,tui/src,src}; do echo $src; cloc $src; done
|
||||||
|
|
|
||||||
8
cli/Cargo.toml
Normal file
8
cli/Cargo.toml
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
[package]
|
||||||
|
name = "tek_cli"
|
||||||
|
edition = "2021"
|
||||||
|
version = "0.2.0"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
tek = { path = ".." }
|
||||||
|
clap = { version = "4.5.4", features = [ "derive" ] }
|
||||||
|
|
@ -13,6 +13,6 @@ impl HasPhrases for ExamplePhrases {
|
||||||
|
|
||||||
fn main () -> Usually<()> {
|
fn main () -> Usually<()> {
|
||||||
let mut phrases = ExamplePhrases(vec![]);
|
let mut phrases = ExamplePhrases(vec![]);
|
||||||
PhrasePoolCommand::Import(0, String::from("./example.mid")).execute(&mut phrases)?;
|
MidiPoolCommand::Import(0, String::from("./example.mid")).execute(&mut phrases)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,3 +11,35 @@ tek_time = { path = "../time" }
|
||||||
jack = { path = "../rust-jack" }
|
jack = { path = "../rust-jack" }
|
||||||
midly = "0.5"
|
midly = "0.5"
|
||||||
uuid = { version = "1.10.0", features = [ "v4" ] }
|
uuid = { version = "1.10.0", features = [ "v4" ] }
|
||||||
|
|
||||||
|
#[[bin]]
|
||||||
|
#name = "tek_arranger"
|
||||||
|
#path = "bin/cli_arranger.rs"
|
||||||
|
|
||||||
|
#[[bin]]
|
||||||
|
#name = "tek_sequencer"
|
||||||
|
#path = "bin/cli_sequencer.rs"
|
||||||
|
|
||||||
|
#[[bin]]
|
||||||
|
#name = "tek_groovebox"
|
||||||
|
#path = "bin/cli_groovebox.rs"
|
||||||
|
|
||||||
|
#[[bin]]
|
||||||
|
#name = "tek_clock"
|
||||||
|
#path = "bin/cli_clock.rs"
|
||||||
|
|
||||||
|
#[[bin]]
|
||||||
|
#name = "tek_sampler"
|
||||||
|
#path = "bin/cli_sampler.rs"
|
||||||
|
|
||||||
|
#[[bin]]
|
||||||
|
#name = "tek_mixer"
|
||||||
|
#path = "src/cli_mixer.rs"
|
||||||
|
|
||||||
|
#[[bin]]
|
||||||
|
#name = "tek_track"
|
||||||
|
#path = "src/cli_track.rs"
|
||||||
|
|
||||||
|
#[[bin]]
|
||||||
|
#name = "tek_plugin"
|
||||||
|
#path = "src/cli_plugin.rs"
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,22 @@
|
||||||
mod midi_pool; pub(crate) use midi_pool::*;
|
mod midi_pool; pub use midi_pool::*;
|
||||||
mod midi_clip; pub(crate) use midi_clip::*;
|
mod midi_clip; pub use midi_clip::*;
|
||||||
mod midi_launch; pub(crate) use midi_launch::*;
|
mod midi_launch; pub use midi_launch::*;
|
||||||
mod midi_player; pub(crate) use midi_player::*;
|
mod midi_player; pub use midi_player::*;
|
||||||
mod midi_in; pub(crate) use midi_in::*;
|
mod midi_in; pub use midi_in::*;
|
||||||
mod midi_out; pub(crate) use midi_out::*;
|
mod midi_out; pub use midi_out::*;
|
||||||
|
|
||||||
mod midi_pitch; pub(crate) use midi_pitch::*;
|
mod midi_pitch; pub use midi_pitch::*;
|
||||||
mod midi_range; pub(crate) use midi_range::*;
|
mod midi_range; pub use midi_range::*;
|
||||||
mod midi_point; pub(crate) use midi_point::*;
|
mod midi_point; pub use midi_point::*;
|
||||||
mod midi_view; pub(crate) use midi_view::*;
|
mod midi_view; pub use midi_view::*;
|
||||||
mod midi_editor; pub(crate) use midi_editor::*;
|
mod midi_editor; pub use midi_editor::*;
|
||||||
mod midi_select; pub(crate) use midi_select::*;
|
mod midi_select; pub use midi_select::*;
|
||||||
|
|
||||||
mod piano_h; pub(crate) use self::piano_h::*;
|
mod piano_h; pub use self::piano_h::*;
|
||||||
mod piano_h_cursor; pub(crate) use self::piano_h_cursor::*;
|
mod piano_h_cursor; pub use self::piano_h_cursor::*;
|
||||||
mod piano_h_keys; pub(crate) use self::piano_h_keys::*;
|
mod piano_h_keys; pub use self::piano_h_keys::*;
|
||||||
mod piano_h_notes; pub(crate) use self::piano_h_notes::*;
|
mod piano_h_notes; pub use self::piano_h_notes::*;
|
||||||
mod piano_h_time; pub(crate) use self::piano_h_time::*;
|
mod piano_h_time; pub use self::piano_h_time::*;
|
||||||
|
|
||||||
pub(crate) use ::tek_time::*;
|
pub(crate) use ::tek_time::*;
|
||||||
pub(crate) use ::tek_jack::{*, jack::*};
|
pub(crate) use ::tek_jack::{*, jack::*};
|
||||||
|
|
|
||||||
|
|
@ -21,29 +21,29 @@ pub trait HasPlayer {
|
||||||
/// Contains state for playing a phrase
|
/// Contains state for playing a phrase
|
||||||
pub struct MidiPlayer {
|
pub struct MidiPlayer {
|
||||||
/// State of clock and playhead
|
/// State of clock and playhead
|
||||||
pub(crate) clock: Clock,
|
pub clock: Clock,
|
||||||
/// Start time and phrase being played
|
/// Start time and phrase being played
|
||||||
pub(crate) play_phrase: Option<(Moment, Option<Arc<RwLock<MidiClip>>>)>,
|
pub play_phrase: Option<(Moment, Option<Arc<RwLock<MidiClip>>>)>,
|
||||||
/// Start time and next phrase
|
/// Start time and next phrase
|
||||||
pub(crate) next_phrase: Option<(Moment, Option<Arc<RwLock<MidiClip>>>)>,
|
pub next_phrase: Option<(Moment, Option<Arc<RwLock<MidiClip>>>)>,
|
||||||
/// Play input through output.
|
/// Play input through output.
|
||||||
pub(crate) monitoring: bool,
|
pub monitoring: bool,
|
||||||
/// Write input to sequence.
|
/// Write input to sequence.
|
||||||
pub(crate) recording: bool,
|
pub recording: bool,
|
||||||
/// Overdub input to sequence.
|
/// Overdub input to sequence.
|
||||||
pub(crate) overdub: bool,
|
pub overdub: bool,
|
||||||
/// Send all notes off
|
/// Send all notes off
|
||||||
pub(crate) reset: bool, // TODO?: after Some(nframes)
|
pub reset: bool, // TODO?: after Some(nframes)
|
||||||
/// Record from MIDI ports to current sequence.
|
/// Record from MIDI ports to current sequence.
|
||||||
pub midi_ins: Vec<Port<MidiIn>>,
|
pub midi_ins: Vec<Port<MidiIn>>,
|
||||||
/// Play from current sequence to MIDI ports
|
/// Play from current sequence to MIDI ports
|
||||||
pub midi_outs: Vec<Port<MidiOut>>,
|
pub midi_outs: Vec<Port<MidiOut>>,
|
||||||
/// Notes currently held at input
|
/// Notes currently held at input
|
||||||
pub(crate) notes_in: Arc<RwLock<[bool; 128]>>,
|
pub notes_in: Arc<RwLock<[bool; 128]>>,
|
||||||
/// Notes currently held at output
|
/// Notes currently held at output
|
||||||
pub(crate) notes_out: Arc<RwLock<[bool; 128]>>,
|
pub notes_out: Arc<RwLock<[bool; 128]>>,
|
||||||
/// MIDI output buffer
|
/// MIDI output buffer
|
||||||
pub note_buf: Vec<u8>,
|
pub note_buf: Vec<u8>,
|
||||||
}
|
}
|
||||||
impl MidiPlayer {
|
impl MidiPlayer {
|
||||||
pub fn new (
|
pub fn new (
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ pub trait HasPhrases {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub enum PhrasePoolCommand {
|
pub enum MidiPoolCommand {
|
||||||
Add(usize, MidiClip),
|
Add(usize, MidiClip),
|
||||||
Delete(usize),
|
Delete(usize),
|
||||||
Swap(usize, usize),
|
Swap(usize, usize),
|
||||||
|
|
@ -26,9 +26,9 @@ pub enum PhrasePoolCommand {
|
||||||
SetColor(usize, ItemColor),
|
SetColor(usize, ItemColor),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: HasPhrases> Command<T> for PhrasePoolCommand {
|
impl<T: HasPhrases> Command<T> for MidiPoolCommand {
|
||||||
fn execute (self, model: &mut T) -> Perhaps<Self> {
|
fn execute (self, model: &mut T) -> Perhaps<Self> {
|
||||||
use PhrasePoolCommand::*;
|
use MidiPoolCommand::*;
|
||||||
Ok(match self {
|
Ok(match self {
|
||||||
Add(mut index, phrase) => {
|
Add(mut index, phrase) => {
|
||||||
let phrase = Arc::new(RwLock::new(phrase));
|
let phrase = Arc::new(RwLock::new(phrase));
|
||||||
|
|
|
||||||
|
|
@ -185,7 +185,7 @@ command!(|self: ArrangerCommand, state: Arranger|match self {
|
||||||
undo
|
undo
|
||||||
},
|
},
|
||||||
// reload phrase in editor to update color
|
// reload phrase in editor to update color
|
||||||
PoolCommand::Phrase(PhrasePoolCommand::SetColor(index, _)) => {
|
PoolCommand::Phrase(MidiPoolCommand::SetColor(index, _)) => {
|
||||||
let undo = cmd.delegate(&mut state.pool, Self::Phrases)?;
|
let undo = cmd.delegate(&mut state.pool, Self::Phrases)?;
|
||||||
state.editor.set_phrase(Some(state.pool.phrase()));
|
state.editor.set_phrase(Some(state.pool.phrase()));
|
||||||
undo
|
undo
|
||||||
|
|
|
||||||
151
src/file.rs
151
src/file.rs
|
|
@ -1,151 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
/// Browses for phrase to import/export
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct FileBrowser {
|
|
||||||
pub cwd: PathBuf,
|
|
||||||
pub dirs: Vec<(OsString, String)>,
|
|
||||||
pub files: Vec<(OsString, String)>,
|
|
||||||
pub filter: String,
|
|
||||||
pub index: usize,
|
|
||||||
pub scroll: usize,
|
|
||||||
pub size: Measure<TuiOut>
|
|
||||||
}
|
|
||||||
/// Commands supported by [FileBrowser]
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
|
||||||
pub enum FileBrowserCommand {
|
|
||||||
Begin,
|
|
||||||
Cancel,
|
|
||||||
Confirm,
|
|
||||||
Select(usize),
|
|
||||||
Chdir(PathBuf),
|
|
||||||
Filter(Arc<str>),
|
|
||||||
}
|
|
||||||
render!(TuiOut: (self: FileBrowser) => /*Stack::down(|add|{
|
|
||||||
let mut i = 0;
|
|
||||||
for (_, name) in self.dirs.iter() {
|
|
||||||
if i >= self.scroll {
|
|
||||||
add(&Tui::bold(i == self.index, name.as_str()))?;
|
|
||||||
}
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
for (_, name) in self.files.iter() {
|
|
||||||
if i >= self.scroll {
|
|
||||||
add(&Tui::bold(i == self.index, name.as_str()))?;
|
|
||||||
}
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
add(&format!("{}/{i}", self.index))?;
|
|
||||||
Ok(())
|
|
||||||
})*/"todo");
|
|
||||||
impl FileBrowser {
|
|
||||||
pub fn new (cwd: Option<PathBuf>) -> Usually<Self> {
|
|
||||||
let cwd = if let Some(cwd) = cwd { cwd } else { std::env::current_dir()? };
|
|
||||||
let mut dirs = vec![];
|
|
||||||
let mut files = vec![];
|
|
||||||
for entry in std::fs::read_dir(&cwd)? {
|
|
||||||
let entry = entry?;
|
|
||||||
let name = entry.file_name();
|
|
||||||
let decoded = name.clone().into_string().unwrap_or_else(|_|"<unreadable>".to_string());
|
|
||||||
let meta = entry.metadata()?;
|
|
||||||
if meta.is_dir() {
|
|
||||||
dirs.push((name, format!("📁 {decoded}")));
|
|
||||||
} else if meta.is_file() {
|
|
||||||
files.push((name, format!("📄 {decoded}")));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(Self {
|
|
||||||
cwd,
|
|
||||||
dirs,
|
|
||||||
files,
|
|
||||||
filter: "".to_string(),
|
|
||||||
index: 0,
|
|
||||||
scroll: 0,
|
|
||||||
size: Measure::new(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
pub fn len (&self) -> usize {
|
|
||||||
self.dirs.len() + self.files.len()
|
|
||||||
}
|
|
||||||
pub fn is_dir (&self) -> bool {
|
|
||||||
self.index < self.dirs.len()
|
|
||||||
}
|
|
||||||
pub fn is_file (&self) -> bool {
|
|
||||||
self.index >= self.dirs.len()
|
|
||||||
}
|
|
||||||
pub fn path (&self) -> PathBuf {
|
|
||||||
self.cwd.join(if self.is_dir() {
|
|
||||||
&self.dirs[self.index].0
|
|
||||||
} else if self.is_file() {
|
|
||||||
&self.files[self.index - self.dirs.len()].0
|
|
||||||
} else {
|
|
||||||
unreachable!()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
pub fn chdir (&self) -> Usually<Self> {
|
|
||||||
Self::new(Some(self.path()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
command!(|self: FileBrowserCommand, state: PoolModel|{
|
|
||||||
use PoolMode::*;
|
|
||||||
use FileBrowserCommand::*;
|
|
||||||
let mode = &mut state.mode;
|
|
||||||
match mode {
|
|
||||||
Some(Import(index, ref mut browser)) => match self {
|
|
||||||
Cancel => { *mode = None; },
|
|
||||||
Chdir(cwd) => { *mode = Some(Import(*index, FileBrowser::new(Some(cwd))?)); },
|
|
||||||
Select(index) => { browser.index = index; },
|
|
||||||
Confirm => if browser.is_file() {
|
|
||||||
let index = *index;
|
|
||||||
let path = browser.path();
|
|
||||||
*mode = None;
|
|
||||||
PhrasePoolCommand::Import(index, path).execute(state)?;
|
|
||||||
} else if browser.is_dir() {
|
|
||||||
*mode = Some(Import(*index, browser.chdir()?));
|
|
||||||
},
|
|
||||||
_ => todo!(),
|
|
||||||
},
|
|
||||||
Some(Export(index, ref mut browser)) => match self {
|
|
||||||
Cancel => { *mode = None; },
|
|
||||||
Chdir(cwd) => { *mode = Some(Export(*index, FileBrowser::new(Some(cwd))?)); },
|
|
||||||
Select(index) => { browser.index = index; },
|
|
||||||
_ => unreachable!()
|
|
||||||
},
|
|
||||||
_ => unreachable!(),
|
|
||||||
};
|
|
||||||
None
|
|
||||||
});
|
|
||||||
input_to_command!(FileBrowserCommand: |state: PoolModel, input: Event|{
|
|
||||||
use FileBrowserCommand::*;
|
|
||||||
use KeyCode::{Up, Down, Left, Right, Enter, Esc, Backspace, Char};
|
|
||||||
if let Some(PoolMode::Import(_index, browser)) = &state.mode {
|
|
||||||
match input {
|
|
||||||
kpat!(Up) => Select(browser.index.overflowing_sub(1).0
|
|
||||||
.min(browser.len().saturating_sub(1))),
|
|
||||||
kpat!(Down) => Select(browser.index.saturating_add(1)
|
|
||||||
% browser.len()),
|
|
||||||
kpat!(Right) => Chdir(browser.cwd.clone()),
|
|
||||||
kpat!(Left) => Chdir(browser.cwd.clone()),
|
|
||||||
kpat!(Enter) => Confirm,
|
|
||||||
kpat!(Char(_)) => { todo!() },
|
|
||||||
kpat!(Backspace) => { todo!() },
|
|
||||||
kpat!(Esc) => Cancel,
|
|
||||||
_ => return None
|
|
||||||
}
|
|
||||||
} else if let Some(PoolMode::Export(_index, browser)) = &state.mode {
|
|
||||||
match input {
|
|
||||||
kpat!(Up) => Select(browser.index.overflowing_sub(1).0
|
|
||||||
.min(browser.len())),
|
|
||||||
kpat!(Down) => Select(browser.index.saturating_add(1)
|
|
||||||
% browser.len()),
|
|
||||||
kpat!(Right) => Chdir(browser.cwd.clone()),
|
|
||||||
kpat!(Left) => Chdir(browser.cwd.clone()),
|
|
||||||
kpat!(Enter) => Confirm,
|
|
||||||
kpat!(Char(_)) => { todo!() },
|
|
||||||
kpat!(Backspace) => { todo!() },
|
|
||||||
kpat!(Esc) => Cancel,
|
|
||||||
_ => return None
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
unreachable!()
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
@ -7,7 +7,7 @@ use KeyCode::{Char, Delete, Tab, Up, Down, Left, Right};
|
||||||
use ClockCommand::{Play, Pause};
|
use ClockCommand::{Play, Pause};
|
||||||
use GrooveboxCommand as Cmd;
|
use GrooveboxCommand as Cmd;
|
||||||
use MidiEditCommand::*;
|
use MidiEditCommand::*;
|
||||||
use PhrasePoolCommand::*;
|
use MidiPoolCommand::*;
|
||||||
|
|
||||||
pub struct Groovebox {
|
pub struct Groovebox {
|
||||||
_jack: Arc<RwLock<JackConnection>>,
|
_jack: Arc<RwLock<JackConnection>>,
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,6 @@
|
||||||
#![feature(associated_type_defaults)]
|
#![feature(associated_type_defaults)]
|
||||||
|
|
||||||
pub mod arranger; pub use self::arranger::*;
|
pub mod arranger; pub use self::arranger::*;
|
||||||
pub mod file; pub use self::file::*;
|
|
||||||
pub mod focus; pub use self::focus::*;
|
|
||||||
pub mod groovebox; pub use self::groovebox::*;
|
pub mod groovebox; pub use self::groovebox::*;
|
||||||
pub mod meter; pub use self::meter::*;
|
pub mod meter; pub use self::meter::*;
|
||||||
pub mod mixer; pub use self::mixer::*;
|
pub mod mixer; pub use self::mixer::*;
|
||||||
|
|
@ -42,16 +40,16 @@ pub(crate) use ::tek_tui::{
|
||||||
pub(crate) use std::cmp::{Ord, Eq, PartialEq};
|
pub(crate) use std::cmp::{Ord, Eq, PartialEq};
|
||||||
pub(crate) use std::collections::BTreeMap;
|
pub(crate) use std::collections::BTreeMap;
|
||||||
pub(crate) use std::error::Error;
|
pub(crate) use std::error::Error;
|
||||||
pub(crate) use std::ffi::OsString;
|
|
||||||
pub(crate) use std::fmt::{Debug, Display, Formatter};
|
pub(crate) use std::fmt::{Debug, Display, Formatter};
|
||||||
pub(crate) use std::io::{Stdout, stdout};
|
pub(crate) use std::io::{Stdout, stdout};
|
||||||
pub(crate) use std::marker::PhantomData;
|
pub(crate) use std::marker::PhantomData;
|
||||||
pub(crate) use std::ops::{Add, Sub, Mul, Div, Rem};
|
pub(crate) use std::ops::{Add, Sub, Mul, Div, Rem};
|
||||||
pub(crate) use std::path::PathBuf;
|
|
||||||
pub(crate) use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering::{self, *}};
|
pub(crate) use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering::{self, *}};
|
||||||
pub(crate) use std::sync::{Arc, Mutex, RwLock};
|
pub(crate) use std::sync::{Arc, Mutex, RwLock};
|
||||||
pub(crate) use std::thread::{spawn, JoinHandle};
|
pub(crate) use std::thread::{spawn, JoinHandle};
|
||||||
pub(crate) use std::time::Duration;
|
pub(crate) use std::time::Duration;
|
||||||
|
pub(crate) use std::path::PathBuf;
|
||||||
|
pub(crate) use std::ffi::OsString;
|
||||||
|
|
||||||
//#[cfg(test)] mod test_focus {
|
//#[cfg(test)] mod test_focus {
|
||||||
//use super::focus::*;
|
//use super::focus::*;
|
||||||
|
|
|
||||||
80
src/pool.rs
80
src/pool.rs
|
|
@ -37,7 +37,7 @@ pub enum PoolMode {
|
||||||
pub enum PoolCommand {
|
pub enum PoolCommand {
|
||||||
Show(bool),
|
Show(bool),
|
||||||
/// Update the contents of the phrase pool
|
/// Update the contents of the phrase pool
|
||||||
Phrase(PhrasePoolCommand),
|
Phrase(MidiPoolCommand),
|
||||||
/// Select a phrase from the phrase pool
|
/// Select a phrase from the phrase pool
|
||||||
Select(usize),
|
Select(usize),
|
||||||
/// Rename a phrase
|
/// Rename a phrase
|
||||||
|
|
@ -121,7 +121,7 @@ fn to_phrases_command (state: &PoolModel, input: &Event) -> Option<PoolCommand>
|
||||||
kpat!(Char('t')) => Cmd::Length(PhraseLengthCommand::Begin),
|
kpat!(Char('t')) => Cmd::Length(PhraseLengthCommand::Begin),
|
||||||
kpat!(Char('m')) => Cmd::Import(FileBrowserCommand::Begin),
|
kpat!(Char('m')) => Cmd::Import(FileBrowserCommand::Begin),
|
||||||
kpat!(Char('x')) => Cmd::Export(FileBrowserCommand::Begin),
|
kpat!(Char('x')) => Cmd::Export(FileBrowserCommand::Begin),
|
||||||
kpat!(Char('c')) => Cmd::Phrase(PhrasePoolCommand::SetColor(index, ItemColor::random())),
|
kpat!(Char('c')) => Cmd::Phrase(MidiPoolCommand::SetColor(index, ItemColor::random())),
|
||||||
kpat!(Char('[')) | kpat!(Up) => Cmd::Select(
|
kpat!(Char('[')) | kpat!(Up) => Cmd::Select(
|
||||||
index.overflowing_sub(1).0.min(state.phrases().len() - 1)
|
index.overflowing_sub(1).0.min(state.phrases().len() - 1)
|
||||||
),
|
),
|
||||||
|
|
@ -130,32 +130,32 @@ fn to_phrases_command (state: &PoolModel, input: &Event) -> Option<PoolCommand>
|
||||||
),
|
),
|
||||||
kpat!(Char('<')) => if index > 1 {
|
kpat!(Char('<')) => if index > 1 {
|
||||||
state.set_phrase_index(state.phrase_index().saturating_sub(1));
|
state.set_phrase_index(state.phrase_index().saturating_sub(1));
|
||||||
Cmd::Phrase(PhrasePoolCommand::Swap(index - 1, index))
|
Cmd::Phrase(MidiPoolCommand::Swap(index - 1, index))
|
||||||
} else {
|
} else {
|
||||||
return None
|
return None
|
||||||
},
|
},
|
||||||
kpat!(Char('>')) => if index < count.saturating_sub(1) {
|
kpat!(Char('>')) => if index < count.saturating_sub(1) {
|
||||||
state.set_phrase_index(state.phrase_index() + 1);
|
state.set_phrase_index(state.phrase_index() + 1);
|
||||||
Cmd::Phrase(PhrasePoolCommand::Swap(index + 1, index))
|
Cmd::Phrase(MidiPoolCommand::Swap(index + 1, index))
|
||||||
} else {
|
} else {
|
||||||
return None
|
return None
|
||||||
},
|
},
|
||||||
kpat!(Delete) => if index > 0 {
|
kpat!(Delete) => if index > 0 {
|
||||||
state.set_phrase_index(index.min(count.saturating_sub(1)));
|
state.set_phrase_index(index.min(count.saturating_sub(1)));
|
||||||
Cmd::Phrase(PhrasePoolCommand::Delete(index))
|
Cmd::Phrase(MidiPoolCommand::Delete(index))
|
||||||
} else {
|
} else {
|
||||||
return None
|
return None
|
||||||
},
|
},
|
||||||
kpat!(Char('a')) | kpat!(Shift-Char('A')) => Cmd::Phrase(PhrasePoolCommand::Add(count, MidiClip::new(
|
kpat!(Char('a')) | kpat!(Shift-Char('A')) => Cmd::Phrase(MidiPoolCommand::Add(count, MidiClip::new(
|
||||||
"Clip", true, 4 * PPQ, None, Some(ItemPalette::random())
|
"Clip", true, 4 * PPQ, None, Some(ItemPalette::random())
|
||||||
))),
|
))),
|
||||||
kpat!(Char('i')) => Cmd::Phrase(PhrasePoolCommand::Add(index + 1, MidiClip::new(
|
kpat!(Char('i')) => Cmd::Phrase(MidiPoolCommand::Add(index + 1, MidiClip::new(
|
||||||
"Clip", true, 4 * PPQ, None, Some(ItemPalette::random())
|
"Clip", true, 4 * PPQ, None, Some(ItemPalette::random())
|
||||||
))),
|
))),
|
||||||
kpat!(Char('d')) | kpat!(Shift-Char('D')) => {
|
kpat!(Char('d')) | kpat!(Shift-Char('D')) => {
|
||||||
let mut phrase = state.phrases()[index].read().unwrap().duplicate();
|
let mut phrase = state.phrases()[index].read().unwrap().duplicate();
|
||||||
phrase.color = ItemPalette::random_near(phrase.color, 0.25);
|
phrase.color = ItemPalette::random_near(phrase.color, 0.25);
|
||||||
Cmd::Phrase(PhrasePoolCommand::Add(index + 1, phrase))
|
Cmd::Phrase(MidiPoolCommand::Add(index + 1, phrase))
|
||||||
},
|
},
|
||||||
_ => return None
|
_ => return None
|
||||||
})
|
})
|
||||||
|
|
@ -201,3 +201,67 @@ impl PoolModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
command!(|self: FileBrowserCommand, state: PoolModel|{
|
||||||
|
use PoolMode::*;
|
||||||
|
use FileBrowserCommand::*;
|
||||||
|
let mode = &mut state.mode;
|
||||||
|
match mode {
|
||||||
|
Some(Import(index, ref mut browser)) => match self {
|
||||||
|
Cancel => { *mode = None; },
|
||||||
|
Chdir(cwd) => { *mode = Some(Import(*index, FileBrowser::new(Some(cwd))?)); },
|
||||||
|
Select(index) => { browser.index = index; },
|
||||||
|
Confirm => if browser.is_file() {
|
||||||
|
let index = *index;
|
||||||
|
let path = browser.path();
|
||||||
|
*mode = None;
|
||||||
|
MidiPoolCommand::Import(index, path).execute(state)?;
|
||||||
|
} else if browser.is_dir() {
|
||||||
|
*mode = Some(Import(*index, browser.chdir()?));
|
||||||
|
},
|
||||||
|
_ => todo!(),
|
||||||
|
},
|
||||||
|
Some(Export(index, ref mut browser)) => match self {
|
||||||
|
Cancel => { *mode = None; },
|
||||||
|
Chdir(cwd) => { *mode = Some(Export(*index, FileBrowser::new(Some(cwd))?)); },
|
||||||
|
Select(index) => { browser.index = index; },
|
||||||
|
_ => unreachable!()
|
||||||
|
},
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
None
|
||||||
|
});
|
||||||
|
input_to_command!(FileBrowserCommand: |state: PoolModel, input: Event|{
|
||||||
|
use FileBrowserCommand::*;
|
||||||
|
use KeyCode::{Up, Down, Left, Right, Enter, Esc, Backspace, Char};
|
||||||
|
if let Some(PoolMode::Import(_index, browser)) = &state.mode {
|
||||||
|
match input {
|
||||||
|
kpat!(Up) => Select(browser.index.overflowing_sub(1).0
|
||||||
|
.min(browser.len().saturating_sub(1))),
|
||||||
|
kpat!(Down) => Select(browser.index.saturating_add(1)
|
||||||
|
% browser.len()),
|
||||||
|
kpat!(Right) => Chdir(browser.cwd.clone()),
|
||||||
|
kpat!(Left) => Chdir(browser.cwd.clone()),
|
||||||
|
kpat!(Enter) => Confirm,
|
||||||
|
kpat!(Char(_)) => { todo!() },
|
||||||
|
kpat!(Backspace) => { todo!() },
|
||||||
|
kpat!(Esc) => Cancel,
|
||||||
|
_ => return None
|
||||||
|
}
|
||||||
|
} else if let Some(PoolMode::Export(_index, browser)) = &state.mode {
|
||||||
|
match input {
|
||||||
|
kpat!(Up) => Select(browser.index.overflowing_sub(1).0
|
||||||
|
.min(browser.len())),
|
||||||
|
kpat!(Down) => Select(browser.index.saturating_add(1)
|
||||||
|
% browser.len()),
|
||||||
|
kpat!(Right) => Chdir(browser.cwd.clone()),
|
||||||
|
kpat!(Left) => Chdir(browser.cwd.clone()),
|
||||||
|
kpat!(Enter) => Confirm,
|
||||||
|
kpat!(Char(_)) => { todo!() },
|
||||||
|
kpat!(Backspace) => { todo!() },
|
||||||
|
kpat!(Esc) => Cancel,
|
||||||
|
_ => return None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ use ClockCommand::{Play, Pause};
|
||||||
use KeyCode::{Tab, Char};
|
use KeyCode::{Tab, Char};
|
||||||
use SequencerCommand as Cmd;
|
use SequencerCommand as Cmd;
|
||||||
use MidiEditCommand::*;
|
use MidiEditCommand::*;
|
||||||
use PhrasePoolCommand::*;
|
use MidiPoolCommand::*;
|
||||||
/// Root view for standalone `tek_sequencer`.
|
/// Root view for standalone `tek_sequencer`.
|
||||||
pub struct Sequencer {
|
pub struct Sequencer {
|
||||||
pub _jack: Arc<RwLock<JackConnection>>,
|
pub _jack: Arc<RwLock<JackConnection>>,
|
||||||
|
|
@ -57,6 +57,25 @@ impl Sequencer {
|
||||||
let pool = Pull::y(1, Fill::y(Align::e(PoolView(self.pool.visible, &self.pool))));
|
let pool = Pull::y(1, Fill::y(Align::e(PoolView(self.pool.visible, &self.pool))));
|
||||||
Fixed::x(pool_w, Align::e(Fill::y(PoolView(self.compact, &self.pool))))
|
Fixed::x(pool_w, Align::e(Fill::y(PoolView(self.compact, &self.pool))))
|
||||||
}
|
}
|
||||||
|
fn help () -> impl Content<TuiOut> {
|
||||||
|
let single = |binding, command|row!(" ", col!(
|
||||||
|
Tui::fg(TuiTheme::yellow(), binding),
|
||||||
|
command
|
||||||
|
));
|
||||||
|
let double = |(b1, c1), (b2, c2)|col!(
|
||||||
|
row!(" ", Tui::fg(TuiTheme::yellow(), b1), " ", c1,),
|
||||||
|
row!(" ", Tui::fg(TuiTheme::yellow(), b2), " ", c2,),
|
||||||
|
);
|
||||||
|
Tui::fg_bg(TuiTheme::g(255), TuiTheme::g(50), row!(
|
||||||
|
single("SPACE", "play/pause"),
|
||||||
|
double(("▲▼▶◀", "cursor"), ("Ctrl", "scroll"), ),
|
||||||
|
double(("a", "append"), ("s", "set note"),),
|
||||||
|
double((",.", "length"), ("<>", "triplet"), ),
|
||||||
|
double(("[]", "phrase"), ("{}", "order"), ),
|
||||||
|
double(("q", "enqueue"), ("e", "edit"), ),
|
||||||
|
double(("c", "color"), ("", ""),),
|
||||||
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
audio!(|self:Sequencer, client, scope|{
|
audio!(|self:Sequencer, client, scope|{
|
||||||
// Start profiling cycle
|
// Start profiling cycle
|
||||||
|
|
@ -178,29 +197,10 @@ from!(|state:&Sequencer|SequencerStatus = {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
render!(TuiOut: (self: SequencerStatus) => Fixed::y(2, lay!(
|
render!(TuiOut: (self: SequencerStatus) => Fixed::y(2, lay!(
|
||||||
Self::help(),
|
Sequencer::help(),
|
||||||
Fill::xy(Align::se(Tui::fg_bg(TuiTheme::orange(), TuiTheme::g(25), self.stats()))),
|
Fill::xy(Align::se(Tui::fg_bg(TuiTheme::orange(), TuiTheme::g(25), self.stats()))),
|
||||||
)));
|
)));
|
||||||
impl SequencerStatus {
|
impl SequencerStatus {
|
||||||
fn help () -> impl Content<TuiOut> {
|
|
||||||
let single = |binding, command|row!(" ", col!(
|
|
||||||
Tui::fg(TuiTheme::yellow(), binding),
|
|
||||||
command
|
|
||||||
));
|
|
||||||
let double = |(b1, c1), (b2, c2)|col!(
|
|
||||||
row!(" ", Tui::fg(TuiTheme::yellow(), b1), " ", c1,),
|
|
||||||
row!(" ", Tui::fg(TuiTheme::yellow(), b2), " ", c2,),
|
|
||||||
);
|
|
||||||
Tui::fg_bg(TuiTheme::g(255), TuiTheme::g(50), row!(
|
|
||||||
single("SPACE", "play/pause"),
|
|
||||||
double(("▲▼▶◀", "cursor"), ("Ctrl", "scroll"), ),
|
|
||||||
double(("a", "append"), ("s", "set note"),),
|
|
||||||
double((",.", "length"), ("<>", "triplet"), ),
|
|
||||||
double(("[]", "phrase"), ("{}", "order"), ),
|
|
||||||
double(("q", "enqueue"), ("e", "edit"), ),
|
|
||||||
double(("c", "color"), ("", ""),),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
fn stats (&self) -> impl Content<TuiOut> + use<'_> {
|
fn stats (&self) -> impl Content<TuiOut> + use<'_> {
|
||||||
row!(&self.cpu, &self.size)
|
row!(&self.cpu, &self.size)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,10 +19,13 @@ mod tui_theme; pub use self::tui_theme::*;
|
||||||
mod tui_border; pub use self::tui_border::*;
|
mod tui_border; pub use self::tui_border::*;
|
||||||
mod tui_field; pub use self::tui_field::*;
|
mod tui_field; pub use self::tui_field::*;
|
||||||
mod tui_buffer; pub use self::tui_buffer::*;
|
mod tui_buffer; pub use self::tui_buffer::*;
|
||||||
|
mod tui_file; pub use self::tui_file::*;
|
||||||
|
|
||||||
pub(crate) use std::sync::{Arc, RwLock, atomic::{AtomicUsize, AtomicBool, Ordering::*}};
|
pub(crate) use std::sync::{Arc, RwLock, atomic::{AtomicUsize, AtomicBool, Ordering::*}};
|
||||||
pub(crate) use std::io::{stdout, Stdout};
|
pub(crate) use std::io::{stdout, Stdout};
|
||||||
pub(crate) use std::error::Error;
|
pub(crate) use std::error::Error;
|
||||||
|
pub(crate) use std::path::PathBuf;
|
||||||
|
pub(crate) use std::ffi::OsString;
|
||||||
|
|
||||||
/// Standard result type.
|
/// Standard result type.
|
||||||
pub type Usually<T> = Result<T, Box<dyn Error>>;
|
pub type Usually<T> = Result<T, Box<dyn Error>>;
|
||||||
|
|
|
||||||
87
tui/src/tui_file.rs
Normal file
87
tui/src/tui_file.rs
Normal file
|
|
@ -0,0 +1,87 @@
|
||||||
|
use crate::*;
|
||||||
|
/// Browses for phrase to import/export
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct FileBrowser {
|
||||||
|
pub cwd: PathBuf,
|
||||||
|
pub dirs: Vec<(OsString, String)>,
|
||||||
|
pub files: Vec<(OsString, String)>,
|
||||||
|
pub filter: String,
|
||||||
|
pub index: usize,
|
||||||
|
pub scroll: usize,
|
||||||
|
pub size: Measure<TuiOut>
|
||||||
|
}
|
||||||
|
/// Commands supported by [FileBrowser]
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub enum FileBrowserCommand {
|
||||||
|
Begin,
|
||||||
|
Cancel,
|
||||||
|
Confirm,
|
||||||
|
Select(usize),
|
||||||
|
Chdir(PathBuf),
|
||||||
|
Filter(Arc<str>),
|
||||||
|
}
|
||||||
|
render!(TuiOut: (self: FileBrowser) => /*Stack::down(|add|{
|
||||||
|
let mut i = 0;
|
||||||
|
for (_, name) in self.dirs.iter() {
|
||||||
|
if i >= self.scroll {
|
||||||
|
add(&Tui::bold(i == self.index, name.as_str()))?;
|
||||||
|
}
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
for (_, name) in self.files.iter() {
|
||||||
|
if i >= self.scroll {
|
||||||
|
add(&Tui::bold(i == self.index, name.as_str()))?;
|
||||||
|
}
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
add(&format!("{}/{i}", self.index))?;
|
||||||
|
Ok(())
|
||||||
|
})*/"todo");
|
||||||
|
impl FileBrowser {
|
||||||
|
pub fn new (cwd: Option<PathBuf>) -> Usually<Self> {
|
||||||
|
let cwd = if let Some(cwd) = cwd { cwd } else { std::env::current_dir()? };
|
||||||
|
let mut dirs = vec![];
|
||||||
|
let mut files = vec![];
|
||||||
|
for entry in std::fs::read_dir(&cwd)? {
|
||||||
|
let entry = entry?;
|
||||||
|
let name = entry.file_name();
|
||||||
|
let decoded = name.clone().into_string().unwrap_or_else(|_|"<unreadable>".to_string());
|
||||||
|
let meta = entry.metadata()?;
|
||||||
|
if meta.is_dir() {
|
||||||
|
dirs.push((name, format!("📁 {decoded}")));
|
||||||
|
} else if meta.is_file() {
|
||||||
|
files.push((name, format!("📄 {decoded}")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(Self {
|
||||||
|
cwd,
|
||||||
|
dirs,
|
||||||
|
files,
|
||||||
|
filter: "".to_string(),
|
||||||
|
index: 0,
|
||||||
|
scroll: 0,
|
||||||
|
size: Measure::new(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
pub fn len (&self) -> usize {
|
||||||
|
self.dirs.len() + self.files.len()
|
||||||
|
}
|
||||||
|
pub fn is_dir (&self) -> bool {
|
||||||
|
self.index < self.dirs.len()
|
||||||
|
}
|
||||||
|
pub fn is_file (&self) -> bool {
|
||||||
|
self.index >= self.dirs.len()
|
||||||
|
}
|
||||||
|
pub fn path (&self) -> PathBuf {
|
||||||
|
self.cwd.join(if self.is_dir() {
|
||||||
|
&self.dirs[self.index].0
|
||||||
|
} else if self.is_file() {
|
||||||
|
&self.files[self.index - self.dirs.len()].0
|
||||||
|
} else {
|
||||||
|
unreachable!()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
pub fn chdir (&self) -> Usually<Self> {
|
||||||
|
Self::new(Some(self.path()))
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue