mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 03:36:41 +01:00
with meagre coverage
This commit is contained in:
parent
86941305a4
commit
2c3bfe4ebb
22 changed files with 611 additions and 728 deletions
12
Cargo.lock
generated
12
Cargo.lock
generated
|
|
@ -2396,7 +2396,6 @@ dependencies = [
|
||||||
"rand 0.8.5",
|
"rand 0.8.5",
|
||||||
"symphonia",
|
"symphonia",
|
||||||
"tengri",
|
"tengri",
|
||||||
"tengri_proc",
|
|
||||||
"toml",
|
"toml",
|
||||||
"uuid",
|
"uuid",
|
||||||
"wavers",
|
"wavers",
|
||||||
|
|
@ -2458,17 +2457,6 @@ dependencies = [
|
||||||
"tengri_dsl",
|
"tengri_dsl",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tengri_proc"
|
|
||||||
version = "0.14.0"
|
|
||||||
dependencies = [
|
|
||||||
"heck",
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
"tengri_core",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tengri_tui"
|
name = "tengri_tui"
|
||||||
version = "0.14.0"
|
version = "0.14.0"
|
||||||
|
|
|
||||||
80
Justfile
80
Justfile
|
|
@ -1,45 +1,24 @@
|
||||||
export RUSTFLAGS := "--cfg procmacro2_semver_exempt -Zmacro-backtrace -Clink-arg=-fuse-ld=mold"
|
export RUSTFLAGS := "--cfg procmacro2_semver_exempt -Zmacro-backtrace -Clink-arg=-fuse-ld=mold"
|
||||||
export RUST_BACKTRACE := "1"
|
export RUST_BACKTRACE := "1"
|
||||||
|
|
||||||
debug := "reset && cargo run --"
|
default:
|
||||||
release := "reset && cargo run --release --"
|
just -l
|
||||||
name := "-n tek"
|
|
||||||
bpm := "-b 174"
|
cloc:
|
||||||
midi-in := "-i 'Midi-Bridge:.*nanoKEY.*:.*capture.*'"
|
for src in {cli,edn/src,input/src,jack/src,midi/src,output/src,plugin/src,sampler/src,tek/src,time/src,tui/src}; do echo; echo $src; cloc --quiet $src; done
|
||||||
midi-out := "-o 'Midi-Bridge:.*playback.*'"
|
|
||||||
audio-in := "-l 'Komplete Audio 6 Pro:capture_AUX1' -r 'Komplete Audio 6 Pro:capture_AUX1'"
|
bacon:
|
||||||
audio-out := "-L 'Komplete Audio 6 Pro:playback_AUX1' -R 'Komplete Audio 6 Pro:playback_AUX1'"
|
bacon -s
|
||||||
firefox-in := "-l 'Firefox:output_FL*' -r 'Firefox:output_FR*'"
|
|
||||||
|
check:
|
||||||
|
reset && cargo check
|
||||||
|
|
||||||
|
test:
|
||||||
|
cargo test --workspace --exclude jack
|
||||||
|
|
||||||
covfig := "CARGO_INCREMENTAL=0 RUSTFLAGS='-Cinstrument-coverage' RUSTDOCFLAGS='-Cinstrument-coverage' LLVM_PROFILE_FILE='cov/cargo-test-%p-%m.profraw'"
|
covfig := "CARGO_INCREMENTAL=0 RUSTFLAGS='-Cinstrument-coverage' RUSTDOCFLAGS='-Cinstrument-coverage' LLVM_PROFILE_FILE='cov/cargo-test-%p-%m.profraw'"
|
||||||
grcov-binary := "--binary-path ./target/coverage/deps/"
|
grcov-binary := "--binary-path ./target/coverage/deps/"
|
||||||
grcov-ignore := "--ignore-not-existing --ignore '../*' --ignore \"/*\" --ignore 'target/*'"
|
grcov-ignore := "--ignore-not-existing --ignore '../*' --ignore \"/*\" --ignore 'target/*'"
|
||||||
|
|
||||||
default:
|
|
||||||
just -l
|
|
||||||
bacon:
|
|
||||||
bacon -s
|
|
||||||
check:
|
|
||||||
reset && cargo check
|
|
||||||
build:
|
|
||||||
reset && cargo build
|
|
||||||
run:
|
|
||||||
{{debug}}
|
|
||||||
run-init:
|
|
||||||
rm -rf ~/.config/tek && {{debug}}
|
|
||||||
release:
|
|
||||||
{{release}}
|
|
||||||
build-release:
|
|
||||||
time cargo build -j4 --release
|
|
||||||
tui:
|
|
||||||
cargo run --example tui
|
|
||||||
cloc:
|
|
||||||
for src in {cli,edn/src,input/src,jack/src,midi/src,output/src,plugin/src,sampler/src,tek/src,time/src,tui/src}; do echo; echo $src; cloc --quiet $src; done
|
|
||||||
test:
|
|
||||||
cargo test --workspace --exclude jack
|
|
||||||
prof:
|
|
||||||
CARGO_PROFILE_RELEASE_DEBUG=true cargo flamegraph --
|
|
||||||
doc:
|
|
||||||
cargo doc -j4 --workspace --document-private-items
|
|
||||||
cov:
|
cov:
|
||||||
{{covfig}} time cargo test -j4 --workspace --exclude jack --profile coverage
|
{{covfig}} time cargo test -j4 --workspace --exclude jack --profile coverage
|
||||||
rm -rf target/coverage/html || true
|
rm -rf target/coverage/html || true
|
||||||
|
|
@ -51,6 +30,28 @@ llcov:
|
||||||
time cargo llvm-cov --workspace --exclude jack --profile coverage --no-report
|
time cargo llvm-cov --workspace --exclude jack --profile coverage --no-report
|
||||||
time cargo llvm-cov --workspace --exclude jack --profile coverage --no-report --doc
|
time cargo llvm-cov --workspace --exclude jack --profile coverage --no-report --doc
|
||||||
time cargo llvm-cov report --doctests --html #--output-path target/coverage/html
|
time cargo llvm-cov report --doctests --html #--output-path target/coverage/html
|
||||||
|
|
||||||
|
build:
|
||||||
|
reset && cargo build
|
||||||
|
|
||||||
|
debug := "reset && cargo run --"
|
||||||
|
run:
|
||||||
|
{{debug}}
|
||||||
|
run-init:
|
||||||
|
rm -rf ~/.config/tek && {{debug}}
|
||||||
|
|
||||||
|
prof:
|
||||||
|
CARGO_PROFILE_RELEASE_DEBUG=true cargo flamegraph --
|
||||||
|
|
||||||
|
doc:
|
||||||
|
cargo doc -j4 --workspace --document-private-items
|
||||||
|
|
||||||
|
release := "reset && cargo run --release --"
|
||||||
|
release:
|
||||||
|
{{release}}
|
||||||
|
build-release:
|
||||||
|
time cargo build -j4 --release
|
||||||
|
|
||||||
amend:
|
amend:
|
||||||
git commit --amend
|
git commit --amend
|
||||||
push:
|
push:
|
||||||
|
|
@ -62,11 +63,18 @@ fpush:
|
||||||
ftpush:
|
ftpush:
|
||||||
git push --tags -fu codeberg && git push --tags -fu origin
|
git push --tags -fu codeberg && git push --tags -fu origin
|
||||||
|
|
||||||
|
name := "-n tek"
|
||||||
|
bpm := "-b 174"
|
||||||
clock:
|
clock:
|
||||||
{{debug}} {{name}} {{bpm}} clock
|
{{debug}} {{name}} {{bpm}} clock
|
||||||
clock-release:
|
clock-release:
|
||||||
{{release}} {{name}} {{bpm}} clock
|
{{release}} {{name}} {{bpm}} clock
|
||||||
|
|
||||||
|
midi-in := "-i 'Midi-Bridge:.*nanoKEY.*:.*capture.*'"
|
||||||
|
midi-out := "-o 'Midi-Bridge:.*playback.*'"
|
||||||
|
audio-in := "-l 'Komplete Audio 6 Pro:capture_AUX1' -r 'Komplete Audio 6 Pro:capture_AUX1'"
|
||||||
|
audio-out := "-L 'Komplete Audio 6 Pro:playback_AUX1' -R 'Komplete Audio 6 Pro:playback_AUX1'"
|
||||||
|
firefox-in := "-l 'Firefox:output_FL*' -r 'Firefox:output_FR*'"
|
||||||
arranger:
|
arranger:
|
||||||
{{debug}} {{name}} {{bpm}} arranger
|
{{debug}} {{name}} {{bpm}} arranger
|
||||||
arranger-ext:
|
arranger-ext:
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
# https://dystroy.org/bacon/config/
|
# https://dystroy.org/bacon/config/
|
||||||
default_job = "check"
|
default_job = "test"
|
||||||
env.CARGO_TERM_COLOR = "always"
|
env.CARGO_TERM_COLOR = "always"
|
||||||
[keybindings]
|
[keybindings]
|
||||||
c = "job:check"
|
c = "job:check"
|
||||||
|
|
@ -24,7 +24,7 @@ command = ["cargo", "clippy", "--all-targets"]
|
||||||
need_stdout = false
|
need_stdout = false
|
||||||
watch = ["tek", "deps"]
|
watch = ["tek", "deps"]
|
||||||
[jobs.test]
|
[jobs.test]
|
||||||
command = ["cargo", "test"]
|
command = ["cargo", "test", "--workspace", "--exclude", "jack"]
|
||||||
need_stdout = true
|
need_stdout = true
|
||||||
watch = ["tek", "deps"]
|
watch = ["tek", "deps"]
|
||||||
[jobs.nextest]
|
[jobs.nextest]
|
||||||
|
|
|
||||||
2
deps/rust-jack
vendored
2
deps/rust-jack
vendored
|
|
@ -1 +1 @@
|
||||||
Subproject commit 4cbf155d8ed222c140c11770474832ddfa52bcd7
|
Subproject commit 764a38a880ab4749ea60aa7e53cd814b858e606c
|
||||||
2
deps/tengri
vendored
2
deps/tengri
vendored
|
|
@ -1 +1 @@
|
||||||
Subproject commit a4dbf88220f75ccaf9d14cc2e4fb7c00479e3940
|
Subproject commit ca862b9802524ee1713c0088c7a525ad07f370a0
|
||||||
|
|
@ -14,9 +14,6 @@ path = "tek_cli.rs"
|
||||||
rustflags = ["-C", "link-arg=-fuse-ld=mold"]
|
rustflags = ["-C", "link-arg=-fuse-ld=mold"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tengri = { workspace = true }
|
|
||||||
tengri_proc = { workspace = true }
|
|
||||||
|
|
||||||
atomic_float = { workspace = true }
|
atomic_float = { workspace = true }
|
||||||
backtrace = { workspace = true }
|
backtrace = { workspace = true }
|
||||||
clap = { workspace = true, optional = true }
|
clap = { workspace = true, optional = true }
|
||||||
|
|
@ -27,6 +24,7 @@ midly = { workspace = true }
|
||||||
palette = { workspace = true }
|
palette = { workspace = true }
|
||||||
rand = { workspace = true }
|
rand = { workspace = true }
|
||||||
symphonia = { workspace = true, optional = true }
|
symphonia = { workspace = true, optional = true }
|
||||||
|
tengri = { workspace = true }
|
||||||
toml = { workspace = true }
|
toml = { workspace = true }
|
||||||
uuid = { workspace = true, optional = true }
|
uuid = { workspace = true, optional = true }
|
||||||
wavers = { workspace = true, optional = true }
|
wavers = { workspace = true, optional = true }
|
||||||
|
|
|
||||||
|
|
@ -84,7 +84,7 @@ maybe_has!(Scene: |self: Arrangement|
|
||||||
//.map(|t|Take::take(t, iter)).transpose().map(|x|x.flatten()));
|
//.map(|t|Take::take(t, iter)).transpose().map(|x|x.flatten()));
|
||||||
//take!(ClipCommand |state: Arrangement, iter|state.selected_clip().as_ref()
|
//take!(ClipCommand |state: Arrangement, iter|state.selected_clip().as_ref()
|
||||||
//.map(|t|Take::take(t, iter)).transpose().map(|x|x.flatten()));
|
//.map(|t|Take::take(t, iter)).transpose().map(|x|x.flatten()));
|
||||||
#[tengri_proc::expose] impl Arrangement {
|
impl Arrangement {
|
||||||
fn selected_midi_in (&self) -> Option<MidiInput> { todo!() }
|
fn selected_midi_in (&self) -> Option<MidiInput> { todo!() }
|
||||||
fn selected_midi_out (&self) -> Option<MidiOutput> { todo!() }
|
fn selected_midi_out (&self) -> Option<MidiOutput> { todo!() }
|
||||||
fn selected_device (&self) -> Option<Device> { todo!() }
|
fn selected_device (&self) -> Option<Device> { todo!() }
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
#[tengri_proc::expose]
|
|
||||||
impl MidiClip {
|
impl MidiClip {
|
||||||
fn _todo_opt_bool_stub_ (&self) -> Option<bool> { todo!() }
|
fn _todo_opt_bool_stub_ (&self) -> Option<bool> { todo!() }
|
||||||
fn _todo_bool_stub_ (&self) -> bool { todo!() }
|
fn _todo_bool_stub_ (&self) -> bool { todo!() }
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,6 @@ pub trait AddScene: HasScenes + HasTracks {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tengri_proc::expose]
|
|
||||||
impl Scene {
|
impl Scene {
|
||||||
fn _todo_opt_bool_stub_ (&self) -> Option<bool> { todo!() }
|
fn _todo_opt_bool_stub_ (&self) -> Option<bool> { todo!() }
|
||||||
fn _todo_usize_stub_ (&self) -> usize { todo!() }
|
fn _todo_usize_stub_ (&self) -> usize { todo!() }
|
||||||
|
|
|
||||||
|
|
@ -121,7 +121,6 @@ impl<T: MaybeHas<Track>> HasTrack for T {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tengri_proc::expose]
|
|
||||||
impl Track {
|
impl Track {
|
||||||
fn _todo_opt_bool_stub_ (&self) -> Option<bool> { todo!() }
|
fn _todo_opt_bool_stub_ (&self) -> Option<bool> { todo!() }
|
||||||
fn _todo_usize_stub_ (&self) -> usize { todo!() }
|
fn _todo_usize_stub_ (&self) -> usize { todo!() }
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,8 @@ impl Arrangement {
|
||||||
|
|
||||||
pub fn view_inputs (&self, _theme: ItemTheme) -> impl Draw<TuiOut> + Layout<TuiOut> + '_ {
|
pub fn view_inputs (&self, _theme: ItemTheme) -> impl Draw<TuiOut> + Layout<TuiOut> + '_ {
|
||||||
Bsp::s(self.view_inputs_header(), Thunk::new(|to: &mut TuiOut|{
|
Bsp::s(self.view_inputs_header(), Thunk::new(|to: &mut TuiOut|{
|
||||||
for port in self.midi_ins().iter() {
|
for (index, port) in self.midi_ins().iter().enumerate() {
|
||||||
to.place(&self.view_inputs_row(port))
|
to.place(&Push::x(index as u16 * 10, self.view_inputs_row(port)))
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
@ -25,12 +25,12 @@ impl Arrangement {
|
||||||
Fixed::x(20, Align::w(
|
Fixed::x(20, Align::w(
|
||||||
button_3("i", "nput ", format!("{}", self.midi_ins.len()), false))),
|
button_3("i", "nput ", format!("{}", self.midi_ins.len()), false))),
|
||||||
Bsp::w(Fixed::x(4, button_2("I", "+", false)),
|
Bsp::w(Fixed::x(4, button_2("I", "+", false)),
|
||||||
Thunk::new(|to: &mut TuiOut|for (_index, track, _x1, _x2) in self.tracks_with_sizes() {
|
Thunk::new(|to: &mut TuiOut|for (_index, track, x1, _x2) in self.tracks_with_sizes() {
|
||||||
to.place(&Tui::bg(track.color.dark.rgb, Align::w(Fixed::x(track.width as u16, row!(
|
to.place(&Push::x(x1 as u16, Tui::bg(track.color.dark.rgb, Align::w(Fixed::x(track.width as u16, row!(
|
||||||
Either::new(track.sequencer.monitoring, Tui::fg(Green, "mon "), "mon "),
|
Either::new(track.sequencer.monitoring, Tui::fg(Green, "mon "), "mon "),
|
||||||
Either::new(track.sequencer.recording, Tui::fg(Red, "rec "), "rec "),
|
Either::new(track.sequencer.recording, Tui::fg(Red, "rec "), "rec "),
|
||||||
Either::new(track.sequencer.overdub, Tui::fg(Yellow, "dub "), "dub "),
|
Either::new(track.sequencer.overdub, Tui::fg(Yellow, "dub "), "dub "),
|
||||||
)))))
|
))))))
|
||||||
}))))
|
}))))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -180,8 +180,8 @@ pub trait TracksView:
|
||||||
button_2("S", "+", false));
|
button_2("S", "+", false));
|
||||||
view_track_row_section(theme, button, button_2, Tui::bg(theme.darker.rgb,
|
view_track_row_section(theme, button, button_2, Tui::bg(theme.darker.rgb,
|
||||||
Fixed::y(2, Thunk::new(|to: &mut TuiOut|{
|
Fixed::y(2, Thunk::new(|to: &mut TuiOut|{
|
||||||
for (index, track, _x1, _x2) in self.tracks_with_sizes() {
|
for (index, track, x1, _x2) in self.tracks_with_sizes() {
|
||||||
to.place(&Fixed::x(self.track_width(index, track),
|
to.place(&Push::x(x1 as u16, Fixed::x(self.track_width(index, track),
|
||||||
Tui::bg(if selected.track() == Some(index) {
|
Tui::bg(if selected.track() == Some(index) {
|
||||||
track.color.light.rgb
|
track.color.light.rgb
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -189,7 +189,7 @@ pub trait TracksView:
|
||||||
}, Bsp::s(Fill::x(Align::nw(Bsp::e(
|
}, Bsp::s(Fill::x(Align::nw(Bsp::e(
|
||||||
format!("·t{index:02} "),
|
format!("·t{index:02} "),
|
||||||
Tui::fg(Rgb(255, 255, 255), Tui::bold(true, &track.name))
|
Tui::fg(Rgb(255, 255, 255), Tui::bold(true, &track.name))
|
||||||
))), ""))) );}}))))
|
))), ""))) ));}}))))
|
||||||
}
|
}
|
||||||
fn view_track_outputs <'a> (&'a self, theme: ItemTheme, _h: u16) -> impl Draw<TuiOut> + Layout<TuiOut> {
|
fn view_track_outputs <'a> (&'a self, theme: ItemTheme, _h: u16) -> impl Draw<TuiOut> + Layout<TuiOut> {
|
||||||
view_track_row_section(theme,
|
view_track_row_section(theme,
|
||||||
|
|
@ -288,7 +288,7 @@ pub trait ScenesView:
|
||||||
};
|
};
|
||||||
let a = Fill::x(Align::w(Bsp::e(format!("·s{index:02} "),
|
let a = Fill::x(Align::w(Bsp::e(format!("·s{index:02} "),
|
||||||
Tui::fg(Tui::g(255), Tui::bold(true, &scene.name)))));
|
Tui::fg(Tui::g(255), Tui::bold(true, &scene.name)))));
|
||||||
let b = When(self.selection().scene() == Some(index) && self.is_editing(),
|
let b = When::new(self.selection().scene() == Some(index) && self.is_editing(),
|
||||||
Fill::xy(Align::nw(Bsp::s(
|
Fill::xy(Align::nw(Bsp::s(
|
||||||
self.editor().as_ref().map(|e|e.clip_status()),
|
self.editor().as_ref().map(|e|e.clip_status()),
|
||||||
self.editor().as_ref().map(|e|e.edit_status())))));
|
self.editor().as_ref().map(|e|e.edit_status())))));
|
||||||
|
|
@ -363,7 +363,7 @@ pub trait ClipsView:
|
||||||
Tui::fg_bg(outline, bg, Fill::xy("")),
|
Tui::fg_bg(outline, bg, Fill::xy("")),
|
||||||
Fill::xy(Align::nw(Tui::fg_bg(fg, bg, Tui::bold(true, name)))),
|
Fill::xy(Align::nw(Tui::fg_bg(fg, bg, Tui::bold(true, name)))),
|
||||||
),
|
),
|
||||||
Fill::xy(When(self.selection().track() == Some(track_index)
|
Fill::xy(When::new(self.selection().track() == Some(track_index)
|
||||||
&& self.selection().scene() == Some(scene_index)
|
&& self.selection().scene() == Some(scene_index)
|
||||||
&& self.is_editing(), self.editor())))))));
|
&& self.is_editing(), self.editor())))))));
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
#[tengri_proc::expose]
|
|
||||||
impl Browse {
|
impl Browse {
|
||||||
fn _todo_stub_path_buf (&self) -> PathBuf {
|
fn _todo_stub_path_buf (&self) -> PathBuf {
|
||||||
todo!()
|
todo!()
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
#[tengri_proc::expose]
|
|
||||||
impl Clock {
|
impl Clock {
|
||||||
fn _todo_provide_u32 (&self) -> u32 {
|
fn _todo_provide_u32 (&self) -> u32 {
|
||||||
todo!()
|
todo!()
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ def_command!(MidiEditCommand: |editor: MidiEditor| {
|
||||||
// TODO: 1-9 seek markers that by default start every 8th of the clip
|
// TODO: 1-9 seek markers that by default start every 8th of the clip
|
||||||
});
|
});
|
||||||
|
|
||||||
#[tengri_proc::expose] impl MidiEditor {
|
impl MidiEditor {
|
||||||
fn _todo_opt_clip_stub (&self) -> Option<Arc<RwLock<MidiClip>>> {
|
fn _todo_opt_clip_stub (&self) -> Option<Arc<RwLock<MidiClip>>> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
#[tengri_proc::expose]
|
|
||||||
impl Pool {
|
impl Pool {
|
||||||
fn _todo_usize_ (&self) -> usize { todo!() }
|
fn _todo_usize_ (&self) -> usize { todo!() }
|
||||||
fn _todo_bool_ (&self) -> bool { todo!() }
|
fn _todo_bool_ (&self) -> bool { todo!() }
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,6 @@ def_command!(FileBrowserCommand: |sampler: Sampler|{
|
||||||
//("filter" [f: Arc<str>] Some(Self::Filter(f.expect("no filter")))))
|
//("filter" [f: Arc<str>] Some(Self::Filter(f.expect("no filter")))))
|
||||||
});
|
});
|
||||||
|
|
||||||
#[tengri_proc::expose]
|
|
||||||
impl Sampler {
|
impl Sampler {
|
||||||
fn sample_selected (&self) -> usize {
|
fn sample_selected (&self) -> usize {
|
||||||
(self.get_note_pos() as u8).into()
|
(self.get_note_pos() as u8).into()
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,10 @@ use crate::*;
|
||||||
impl Sampler {
|
impl Sampler {
|
||||||
|
|
||||||
pub fn view_grid (&self) -> impl Draw<TuiOut> + Layout<TuiOut> + use<'_> {
|
pub fn view_grid (&self) -> impl Draw<TuiOut> + Layout<TuiOut> + use<'_> {
|
||||||
let cells_x = 8u16;
|
//let cells_x = 8u16;
|
||||||
let cells_y = 8u16;
|
//let cells_y = 8u16;
|
||||||
let cell_width = 10u16;
|
//let cell_width = 10u16;
|
||||||
let cell_height = 2u16;
|
//let cell_height = 2u16;
|
||||||
//let width = cells_x * cell_width;
|
//let width = cells_x * cell_width;
|
||||||
//let height = cells_y * cell_height;
|
//let height = cells_y * cell_height;
|
||||||
//let cols = Map::east(
|
//let cols = Map::east(
|
||||||
|
|
@ -222,7 +222,7 @@ fn draw_viewer (sample: Option<&Arc<RwLock<Sample>>>) -> impl Draw<TuiOut> + Lay
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw_info (sample: Option<&Arc<RwLock<Sample>>>) -> impl Draw<TuiOut> + Layout<TuiOut> + use<'_> {
|
fn draw_info (sample: Option<&Arc<RwLock<Sample>>>) -> impl Draw<TuiOut> + Layout<TuiOut> + use<'_> {
|
||||||
When(sample.is_some(), Thunk::new(move|to: &mut TuiOut|{
|
When::new(sample.is_some(), Thunk::new(move|to: &mut TuiOut|{
|
||||||
let sample = sample.unwrap().read().unwrap();
|
let sample = sample.unwrap().read().unwrap();
|
||||||
let theme = sample.color;
|
let theme = sample.color;
|
||||||
to.place(&row!(
|
to.place(&row!(
|
||||||
|
|
|
||||||
40
tek/tek.edn
40
tek/tek.edn
|
|
@ -34,7 +34,10 @@
|
||||||
(@enter confirm))
|
(@enter confirm))
|
||||||
(view :menu (bg (g 0) (bsp/s
|
(view :menu (bg (g 0) (bsp/s
|
||||||
:ports/out
|
:ports/out
|
||||||
(bsp/n :ports/in (bg (g 30) (bsp/s (fixed/y 7 :logo) (fill/xy :dialog/menu)))))))
|
(bsp/n :ports/in (bg (g 30) (bsp/s (fixed/y 7 :logo) (fill :dialog/menu)))))))
|
||||||
|
(view :menu (bsp/s
|
||||||
|
(push/y 4 (fixed/xy 20 2 (bg (g 0) :debug)))
|
||||||
|
(fixed 20 2 (bg (g 20) (push/x 2 :debug)))))
|
||||||
(view :ports/out (fill/x (fixed/y 3 (bsp/a
|
(view :ports/out (fill/x (fixed/y 3 (bsp/a
|
||||||
(fill/x (align/w (text L-AUDIO-OUT)))
|
(fill/x (align/w (text L-AUDIO-OUT)))
|
||||||
(bsp/a (text MIDI-OUT) (fill/x (align/e (text AUDIO-OUT-R))))))))
|
(bsp/a (text MIDI-OUT) (fill/x (align/e (text AUDIO-OUT-R))))))))
|
||||||
|
|
@ -42,7 +45,7 @@
|
||||||
(fill/x (align/w (text L-AUDIO-IN)))
|
(fill/x (align/w (text L-AUDIO-IN)))
|
||||||
(bsp/a (text MIDI-IN) (fill/x (align/e (text AUDIO-IN-R))))))))
|
(bsp/a (text MIDI-IN) (fill/x (align/e (text AUDIO-IN-R))))))))
|
||||||
(view :browse (bsp/s
|
(view :browse (bsp/s
|
||||||
(padding/xy 3 1 :browse-title)
|
(padding 3 1 :browse-title)
|
||||||
(enclose (fg (g 96)) browser)))
|
(enclose (fg (g 96)) browser)))
|
||||||
(keys :help
|
(keys :help
|
||||||
(@f1 dialog :help))
|
(@f1 dialog :help))
|
||||||
|
|
@ -84,86 +87,113 @@
|
||||||
(mode :length (keys :rename)) (mode :clip (keys :clip)) (mode :track (keys :track))
|
(mode :length (keys :rename)) (mode :clip (keys :clip)) (mode :track (keys :track))
|
||||||
(mode :scene (keys :scene)) (mode :mix (keys :mix))
|
(mode :scene (keys :scene)) (mode :mix (keys :mix))
|
||||||
(keys :clock :arranger :global) :arranger)
|
(keys :clock :arranger :global) :arranger)
|
||||||
|
|
||||||
(view :arranger (bsp/n
|
(view :arranger (bsp/n
|
||||||
:status
|
:status
|
||||||
(bsp/w :meters/output (bsp/e :meters/input :arrangement))))
|
(bsp/w :meters/output (bsp/e :meters/input :arrangement))))
|
||||||
|
|
||||||
(view :arrangement (bsp/n
|
(view :arrangement (bsp/n
|
||||||
:tracks/inputs
|
:tracks/inputs
|
||||||
(bsp/s :tracks/outputs (bsp/s :tracks/names (bsp/s :tracks/devices
|
(bsp/s :tracks/outputs (bsp/s :tracks/names (bsp/s :tracks/devices
|
||||||
(fill/xy (either :mode/editor (bsp/e :scenes/names :editor) :scenes)))))))
|
(fill (either :mode/editor (bsp/e :scenes/names :editor) :scenes)))))))
|
||||||
|
|
||||||
(keys :arranger (see :color :launch :scenes :tracks)
|
(keys :arranger (see :color :launch :scenes :tracks)
|
||||||
(@tab project/edit) (@enter project/edit)
|
(@tab project/edit) (@enter project/edit)
|
||||||
(@shift/I project/input/add) (@shift/O project/output/add)
|
(@shift/I project/input/add) (@shift/O project/output/add)
|
||||||
(@shift/S project/scene/add) (@shift/T project/track/add)
|
(@shift/S project/scene/add) (@shift/T project/track/add)
|
||||||
(@shift/D dialog/show :dialog/device))
|
(@shift/D dialog/show :dialog/device))
|
||||||
|
|
||||||
(keys :tracks
|
(keys :tracks
|
||||||
(@t select :select/track)
|
(@t select :select/track)
|
||||||
(@left select :select/track/dec)
|
(@left select :select/track/dec)
|
||||||
(@right select :select/track/inc))
|
(@right select :select/track/inc))
|
||||||
|
|
||||||
(keys :scenes
|
(keys :scenes
|
||||||
(@s select :select/scene)
|
(@s select :select/scene)
|
||||||
(@up select :select/scene/dec)
|
(@up select :select/scene/dec)
|
||||||
(@down select :select/scene/inc))
|
(@down select :select/scene/inc))
|
||||||
|
|
||||||
(keys :track (see :color :launch :axis/z :axis/z2 :delete)
|
(keys :track (see :color :launch :axis/z :axis/z2 :delete)
|
||||||
(@r toggle :rec)
|
(@r toggle :rec)
|
||||||
(@m toggle :mon)
|
(@m toggle :mon)
|
||||||
(@p toggle :play)
|
(@p toggle :play)
|
||||||
(@P toggle :solo))
|
(@P toggle :solo))
|
||||||
(keys :scene (see :color :launch :axis/z :axis/z2 :delete))
|
(keys :scene (see :color :launch :axis/z :axis/z2 :delete))
|
||||||
|
|
||||||
(keys :clip (see :color :launch :axis/z :axis/z2 :delete)
|
(keys :clip (see :color :launch :axis/z :axis/z2 :delete)
|
||||||
(@l toggle :loop))
|
(@l toggle :loop))
|
||||||
|
|
||||||
(mode :groovebox (name Groovebox) (info A sequencer with built-in sampler.)
|
(mode :groovebox (name Groovebox) (info A sequencer with built-in sampler.)
|
||||||
(mode browse (keys :browse))
|
(mode browse (keys :browse))
|
||||||
(mode rename (keys :pool-rename))
|
(mode rename (keys :pool-rename))
|
||||||
(mode length (keys :pool-length))
|
(mode length (keys :pool-length))
|
||||||
(keys :clock :editor :sampler :global) (view :groovebox))
|
(keys :clock :editor :sampler :global) (view :groovebox))
|
||||||
|
|
||||||
(view :groovebox (bsp/w
|
(view :groovebox (bsp/w
|
||||||
:meters/output
|
:meters/output
|
||||||
(bsp/e :meters/input (bsp/w :groove/meta :groove/editor))))
|
(bsp/e :meters/input (bsp/w :groove/meta :groove/editor))))
|
||||||
|
|
||||||
(view :groove/meta (fill/y (align/n (stack/s
|
(view :groove/meta (fill/y (align/n (stack/s
|
||||||
:midi-ins/status :midi-outs/status :audio-ins/status :audio-outs/status :pool))))
|
:midi-ins/status :midi-outs/status :audio-ins/status :audio-outs/status :pool))))
|
||||||
|
|
||||||
(view :groove/editor (bsp/n
|
(view :groove/editor (bsp/n
|
||||||
:groove/sample
|
:groove/sample
|
||||||
:groove/sequence))
|
:groove/sequence))
|
||||||
|
|
||||||
(view :groove/sample (fixed/y :h-sample-detail (bsp/e
|
(view :groove/sample (fixed/y :h-sample-detail (bsp/e
|
||||||
(fill/y (fixed/x 20 (align/nw :sample-status)))
|
(fill/y (fixed/x 20 (align/nw :sample-status)))
|
||||||
:sample-viewer)))
|
:sample-viewer)))
|
||||||
|
|
||||||
(view :groove/sequence (bsp/e
|
(view :groove/sequence (bsp/e
|
||||||
(fill/y (align/n (bsp/s :status/v :editor-status)))
|
(fill/y (align/n (bsp/s :status/v :editor-status)))
|
||||||
(bsp/e :samples/keys :editor)))
|
(bsp/e :samples/keys :editor)))
|
||||||
|
|
||||||
(mode :sampler (name Sampler) (info A sampling soundboard.)
|
(mode :sampler (name Sampler) (info A sampling soundboard.)
|
||||||
(keys :sampler :global) (view :sampler))
|
(keys :sampler :global) (view :sampler))
|
||||||
|
|
||||||
(view :sampler (bsp/s
|
(view :sampler (bsp/s
|
||||||
(fixed/y 1 :transport)
|
(fixed/y 1 :transport)
|
||||||
(bsp/n (fixed/y 1 :status) (fill/xy :samples/grid))))
|
(bsp/n (fixed/y 1 :status) (fill :samples/grid))))
|
||||||
|
|
||||||
(keys :sampler (see :sampler/directions :sampler/record :sampler/play))
|
(keys :sampler (see :sampler/directions :sampler/record :sampler/play))
|
||||||
|
|
||||||
(keys :sampler/record
|
(keys :sampler/record
|
||||||
(@r sampler/record/toggle :sample/selected) (@shift/R sampler/record/back))
|
(@r sampler/record/toggle :sample/selected) (@shift/R sampler/record/back))
|
||||||
|
|
||||||
(keys :sampler/play
|
(keys :sampler/play
|
||||||
(@p sampler/play/sample :sample/selected) (@P sampler/stop/sample :sample/selected))
|
(@p sampler/play/sample :sample/selected) (@P sampler/stop/sample :sample/selected))
|
||||||
|
|
||||||
(keys :sampler/import-export
|
(keys :sampler/import-export
|
||||||
(@shift/f6 dialog :dialog/export/sample) (@shift/f9 dialog :dialog/import/sample))
|
(@shift/f6 dialog :dialog/export/sample) (@shift/f9 dialog :dialog/import/sample))
|
||||||
|
|
||||||
(keys :sampler/directions
|
(keys :sampler/directions
|
||||||
(@up sampler/select :sample/above)
|
(@up sampler/select :sample/above)
|
||||||
(@down sampler/select :sample/below)
|
(@down sampler/select :sample/below)
|
||||||
(@left sampler/select :sample/to/left)
|
(@left sampler/select :sample/to/left)
|
||||||
(@right sampler/select :sample/to/right))
|
(@right sampler/select :sample/to/right))
|
||||||
|
|
||||||
(mode :sequencer (name Sequencer) (info A MIDI sequencer.)
|
(mode :sequencer (name Sequencer) (info A MIDI sequencer.)
|
||||||
(mode browse (keys :browse)) (mode rename (keys :pool/rename)) (mode length (keys :pool/length))
|
(mode browse (keys :browse)) (mode rename (keys :pool/rename)) (mode length (keys :pool/length))
|
||||||
(keys :editor :clock :global) (view :sequencer))
|
(keys :editor :clock :global) (view :sequencer))
|
||||||
|
|
||||||
(view :sequencer (bsp/s
|
(view :sequencer (bsp/s
|
||||||
(fixed/y 1 :transport)
|
(fixed/y 1 :transport)
|
||||||
(bsp/n (fixed/y 1 :status) (fill/xy (bsp/a (fill/xy (align/e :pool)) :editor)))))
|
(bsp/n (fixed/y 1 :status) (fill (bsp/a (fill/xy (align/e :pool)) :editor)))))
|
||||||
|
|
||||||
(keys :editor (see :editor/view :editor/note))
|
(keys :editor (see :editor/view :editor/note))
|
||||||
|
|
||||||
(keys :editor/view (see :axis/x :axis/x2 :axis/z :axis/z2)
|
(keys :editor/view (see :axis/x :axis/x2 :axis/z :axis/z2)
|
||||||
(@z toggle :lock))
|
(@z toggle :lock))
|
||||||
|
|
||||||
(keys :editor/note (see :axis/i :axis/i2 :axis/y :page)
|
(keys :editor/note (see :axis/i :axis/i2 :axis/y :page)
|
||||||
(@a editor/append :true)
|
(@a editor/append :true)
|
||||||
(@enter editor/append :false)
|
(@enter editor/append :false)
|
||||||
(@del editor/delete/note)
|
(@del editor/delete/note)
|
||||||
(@shift/del editor/delete/note))
|
(@shift/del editor/delete/note))
|
||||||
|
|
||||||
(keys :pool (see :axis-y :axis-w :axis/z2 :color :delete)
|
(keys :pool (see :axis-y :axis-w :axis/z2 :color :delete)
|
||||||
(@n rename/begin) (@t length/begin) (@m import/begin) (@x export/begin)
|
(@n rename/begin) (@t length/begin) (@m import/begin) (@x export/begin)
|
||||||
(@shift/A clip/add :after :new/clip) (@shift/D clip/add :after :cloned/clip))
|
(@shift/A clip/add :after :new/clip) (@shift/D clip/add :after :cloned/clip))
|
||||||
|
|
||||||
(keys :sequencer (see :color :launch)
|
(keys :sequencer (see :color :launch)
|
||||||
(@shift/I input/add) (@shift/O output/add))
|
(@shift/I input/add) (@shift/O output/add))
|
||||||
|
|
|
||||||
767
tek/tek.rs
767
tek/tek.rs
|
|
@ -13,15 +13,11 @@
|
||||||
clippy::unit_arg
|
clippy::unit_arg
|
||||||
)]
|
)]
|
||||||
|
|
||||||
mod deps; pub use self::deps::*;
|
mod deps; pub use self::deps::*;
|
||||||
|
|
||||||
mod config; pub use self::config::*;
|
mod config; pub use self::config::*;
|
||||||
mod device; pub use self::device::*;
|
mod device; pub use self::device::*;
|
||||||
mod engine; pub use self::engine::*;
|
mod engine; pub use self::engine::*;
|
||||||
|
|
||||||
mod tek_bind; pub use self::tek_bind::*;
|
|
||||||
mod tek_menu; pub use self::tek_menu::*;
|
|
||||||
|
|
||||||
#[cfg(test)] mod test;
|
#[cfg(test)] mod test;
|
||||||
|
|
||||||
/// Total state
|
/// Total state
|
||||||
|
|
@ -76,21 +72,19 @@ audio!(
|
||||||
|self, event|{
|
|self, event|{
|
||||||
use JackEvent::*;
|
use JackEvent::*;
|
||||||
match event {
|
match event {
|
||||||
SampleRate(sr) => {
|
SampleRate(sr) => { self.clock().timebase.sr.set(sr as f64); },
|
||||||
self.clock().timebase.sr.set(sr as f64);
|
PortRegistration(_id, true) => {
|
||||||
},
|
|
||||||
PortRegistration(id, true) => {
|
|
||||||
//let port = self.jack().port_by_id(id);
|
//let port = self.jack().port_by_id(id);
|
||||||
//println!("\rport add: {id} {port:?}");
|
//println!("\rport add: {id} {port:?}");
|
||||||
//println!("\rport add: {id}");
|
//println!("\rport add: {id}");
|
||||||
},
|
},
|
||||||
PortRegistration(id, false) => {
|
PortRegistration(_id, false) => {
|
||||||
/*println!("\rport del: {id}")*/
|
/*println!("\rport del: {id}")*/
|
||||||
},
|
},
|
||||||
PortsConnected(a, b, true) => { /*println!("\rport conn: {a} {b}")*/ },
|
PortsConnected(_a, _b, true) => { /*println!("\rport conn: {a} {b}")*/ },
|
||||||
PortsConnected(a, b, false) => { /*println!("\rport disc: {a} {b}")*/ },
|
PortsConnected(_a, _b, false) => { /*println!("\rport disc: {a} {b}")*/ },
|
||||||
ClientRegistration(id, true) => {},
|
ClientRegistration(_id, true) => {},
|
||||||
ClientRegistration(id, false) => {},
|
ClientRegistration(_id, false) => {},
|
||||||
ThreadInit => {},
|
ThreadInit => {},
|
||||||
XRun => {},
|
XRun => {},
|
||||||
GraphReorder => {},
|
GraphReorder => {},
|
||||||
|
|
@ -99,13 +93,144 @@ audio!(
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
tui_draw!(|self: App, to|to.place(&self.content()));
|
||||||
|
content!(TuiOut: |self: App|Thunk::new(|to: &mut TuiOut|{
|
||||||
|
for (index, dsl) in self.mode.view.iter().enumerate() {
|
||||||
|
self.view(to, dsl).unwrap_or_else(|e|panic!("render #{index} failed ({e}): {dsl}"));
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
impl View<TuiOut, ()> for App {
|
||||||
|
fn view_expr <'a> (&'a self, to: &mut TuiOut, expr: &'a impl DslExpr) -> Usually<()> {
|
||||||
|
if evaluate_output_expression(self, to, expr)?
|
||||||
|
|| evaluate_output_expression_tui(self, to, expr)? {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(format!("App::view_expr: unexpected: {expr:?}").into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn view_word <'a> (&'a self, to: &mut TuiOut, dsl: &'a impl DslExpr) -> Usually<()> {
|
||||||
|
let mut frags = dsl.src()?.unwrap().split("/");
|
||||||
|
match frags.next() {
|
||||||
|
Some(":logo") => to.place(&view_logo()),
|
||||||
|
Some(":status") => to.place(&Fixed::y(1, "TODO: Status Bar")),
|
||||||
|
Some(":meters") => match frags.next() {
|
||||||
|
Some("input") => to.place(&Tui::bg(Rgb(30, 30, 30), Fill::y(Align::s("Input Meters")))),
|
||||||
|
Some("output") => to.place(&Tui::bg(Rgb(30, 30, 30), Fill::y(Align::s("Output Meters")))),
|
||||||
|
_ => panic!()
|
||||||
|
},
|
||||||
|
Some(":tracks") => match frags.next() {
|
||||||
|
None => to.place(&"TODO tracks"),
|
||||||
|
Some("names") => to.place(&self.project.view_track_names(self.color.clone())),//Tui::bg(Rgb(40, 40, 40), Fill::x(Align::w("Track Names")))),
|
||||||
|
Some("inputs") => to.place(&Tui::bg(Rgb(40, 40, 40), Fill::x(Align::w("Track Inputs")))),
|
||||||
|
Some("devices") => to.place(&Tui::bg(Rgb(40, 40, 40), Fill::x(Align::w("Track Devices")))),
|
||||||
|
Some("outputs") => to.place(&Tui::bg(Rgb(40, 40, 40), Fill::x(Align::w("Track Outputs")))),
|
||||||
|
_ => panic!()
|
||||||
|
},
|
||||||
|
Some(":scenes") => match frags.next() {
|
||||||
|
None => to.place(&"TODO scenes"),
|
||||||
|
Some(":scenes/names") => to.place(&"TODO Scene Names"),
|
||||||
|
_ => panic!()
|
||||||
|
},
|
||||||
|
Some(":editor") => to.place(&"TODO Editor"),
|
||||||
|
Some(":dialog") => match frags.next() {
|
||||||
|
Some("menu") => to.place(&if let Dialog::Menu(selected, items) = &self.dialog {
|
||||||
|
let items = items.clone();
|
||||||
|
let selected = selected;
|
||||||
|
Some(Fill::xy(Thunk::new(move|to: &mut TuiOut|{
|
||||||
|
for (index, MenuItem(item, _)) in items.0.iter().enumerate() {
|
||||||
|
to.place(&Push::y((2 * index) as u16,
|
||||||
|
Tui::fg_bg(
|
||||||
|
if *selected == index { Rgb(240,200,180) } else { Rgb(200, 200, 200) },
|
||||||
|
if *selected == index { Rgb(80, 80, 50) } else { Rgb(30, 30, 30) },
|
||||||
|
Fixed::y(2, Align::n(Fill::x(item)))
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
})))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}),
|
||||||
|
_ => unimplemented!("App::view_word: {dsl:?} ({frags:?})"),
|
||||||
|
},
|
||||||
|
Some(":templates") => to.place(&{
|
||||||
|
let modes = self.config.modes.clone();
|
||||||
|
let height = (modes.read().unwrap().len() * 2) as u16;
|
||||||
|
Fixed::y(height, Min::x(30, Thunk::new(move |to: &mut TuiOut|{
|
||||||
|
for (index, (id, profile)) in modes.read().unwrap().iter().enumerate() {
|
||||||
|
let bg = if index == 0 { Rgb(70,70,70) } else { Rgb(50,50,50) };
|
||||||
|
let name = profile.name.get(0).map(|x|x.as_ref()).unwrap_or("<no name>");
|
||||||
|
let info = profile.info.get(0).map(|x|x.as_ref()).unwrap_or("<no info>");
|
||||||
|
let fg1 = Rgb(224, 192, 128);
|
||||||
|
let fg2 = Rgb(224, 128, 32);
|
||||||
|
let field_name = Fill::x(Align::w(Tui::fg(fg1, name)));
|
||||||
|
let field_id = Fill::x(Align::e(Tui::fg(fg2, id)));
|
||||||
|
let field_info = Fill::x(Align::w(info));
|
||||||
|
to.place(&Push::y((2 * index) as u16,
|
||||||
|
Fixed::y(2, Fill::x(Tui::bg(bg, Bsp::s(
|
||||||
|
Bsp::a(field_name, field_id), field_info))))));
|
||||||
|
}
|
||||||
|
})))
|
||||||
|
}),
|
||||||
|
Some(":sessions") => to.place(&Fixed::y(6, Min::x(30, Thunk::new(|to: &mut TuiOut|{
|
||||||
|
let fg = Rgb(224, 192, 128);
|
||||||
|
for (index, name) in ["session1", "session2", "session3"].iter().enumerate() {
|
||||||
|
let bg = if index == 0 { Rgb(50,50,50) } else { Rgb(40,40,40) };
|
||||||
|
to.place(&Push::y((2 * index) as u16,
|
||||||
|
&Fixed::y(2, Fill::x(Tui::bg(bg, Align::w(Tui::fg(fg, name)))))));
|
||||||
|
}
|
||||||
|
})))),
|
||||||
|
Some(":browse/title") => to.place(&Fill::x(Align::w(FieldV(Default::default(),
|
||||||
|
match self.dialog.browser_target().unwrap() {
|
||||||
|
BrowseTarget::SaveProject => "Save project:",
|
||||||
|
BrowseTarget::LoadProject => "Load project:",
|
||||||
|
BrowseTarget::ImportSample(_) => "Import sample:",
|
||||||
|
BrowseTarget::ExportSample(_) => "Export sample:",
|
||||||
|
BrowseTarget::ImportClip(_) => "Import clip:",
|
||||||
|
BrowseTarget::ExportClip(_) => "Export clip:",
|
||||||
|
}, Shrink::x(3, Fixed::y(1, Tui::fg(Tui::g(96), RepeatH("🭻")))))))),
|
||||||
|
Some(":device") => {
|
||||||
|
let selected = self.dialog.device_kind().unwrap();
|
||||||
|
to.place(&Bsp::s(Tui::bold(true, "Add device"), Map::south(1,
|
||||||
|
move||device_kinds().iter(),
|
||||||
|
move|_label: &&'static str, i|{
|
||||||
|
let bg = if i == selected { Rgb(64,128,32) } else { Rgb(0,0,0) };
|
||||||
|
let lb = if i == selected { "[ " } else { " " };
|
||||||
|
let rb = if i == selected { " ]" } else { " " };
|
||||||
|
Fill::x(Tui::bg(bg, Bsp::e(lb, Bsp::w(rb, "FIXME device name")))) })))
|
||||||
|
},
|
||||||
|
Some(":debug") => to.place(&Fixed::y(1, format!("[{:?}]", to.area()))),
|
||||||
|
Some(_) => {
|
||||||
|
let views = self.config.views.read().unwrap();
|
||||||
|
if let Some(dsl) = views.get(dsl.src()?.unwrap()) {
|
||||||
|
let dsl = dsl.clone();
|
||||||
|
std::mem::drop(views);
|
||||||
|
self.view(to, &dsl)?
|
||||||
|
} else {
|
||||||
|
unimplemented!("{dsl:?}");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => unreachable!()
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn view_logo () -> impl Draw<TuiOut> + Layout<TuiOut> {
|
||||||
|
Fixed::xy(32, 7, Tui::bold(true, Tui::fg(Rgb(240,200,180), col!{
|
||||||
|
Fixed::y(1, ""),
|
||||||
|
Fixed::y(1, ""),
|
||||||
|
Fixed::y(1, "~~ ╓─╥─╖ ╓──╖ ╥ ╖ ~~~~~~~~~~~~"),
|
||||||
|
Fixed::y(1, Bsp::e("~~~~ ║ ~ ╟─╌ ~╟─< ~~ ", Bsp::e(Tui::fg(Rgb(230,100,40), "v0.3.0"), " ~~"))),
|
||||||
|
Fixed::y(1, "~~~~ ╨ ~ ╙──╜ ╨ ╜ ~~~~~~~~~~~~"),
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow source to be read as Literal string
|
||||||
dsl_ns!(App: Arc<str> {
|
dsl_ns!(App: Arc<str> {
|
||||||
literal = |dsl|Ok(dsl.src()?.map(|x|x.into()));
|
literal = |dsl|Ok(dsl.src()?.map(|x|x.into()));
|
||||||
});
|
});
|
||||||
|
|
||||||
dsl_ns!(App: ItemTheme {});
|
// Provide boolean values.
|
||||||
|
|
||||||
dsl_ns!(App: bool {
|
dsl_ns!(App: bool {
|
||||||
|
// TODO literal = ...
|
||||||
word = |app| {
|
word = |app| {
|
||||||
":mode/editor" => app.project.editor.is_some(),
|
":mode/editor" => app.project.editor.is_some(),
|
||||||
":focused/dialog" => !matches!(app.dialog, Dialog::None),
|
":focused/dialog" => !matches!(app.dialog, Dialog::None),
|
||||||
|
|
@ -127,6 +252,9 @@ dsl_ns!(App: bool {
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// TODO: provide colors here
|
||||||
|
dsl_ns!(App: ItemTheme {});
|
||||||
|
|
||||||
dsl_ns!(App: Dialog {
|
dsl_ns!(App: Dialog {
|
||||||
word = |app| {
|
word = |app| {
|
||||||
":dialog/none" => Dialog::None,
|
":dialog/none" => Dialog::None,
|
||||||
|
|
@ -239,10 +367,6 @@ dsl_ns!(App: isize {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
impl HasClipsSize for App { fn clips_size (&self) -> &Measure<TuiOut> { &self.project.inner_size } }
|
|
||||||
impl HasTrackScroll for App { fn track_scroll (&self) -> usize { self.project.track_scroll() } }
|
|
||||||
impl HasSceneScroll for App { fn scene_scroll (&self) -> usize { self.project.scene_scroll() } }
|
|
||||||
impl HasJack<'static> for App { fn jack (&self) -> &Jack<'static> { &self.jack } }
|
|
||||||
has!(Jack<'static>: |self: App|self.jack);
|
has!(Jack<'static>: |self: App|self.jack);
|
||||||
has!(Pool: |self: App|self.pool);
|
has!(Pool: |self: App|self.pool);
|
||||||
has!(Dialog: |self: App|self.dialog);
|
has!(Dialog: |self: App|self.dialog);
|
||||||
|
|
@ -260,280 +384,24 @@ maybe_has!(Track: |self: App| { MaybeHas::<Track>::get(&self.project) };
|
||||||
maybe_has!(Scene: |self: App| { MaybeHas::<Scene>::get(&self.project) };
|
maybe_has!(Scene: |self: App| { MaybeHas::<Scene>::get(&self.project) };
|
||||||
{ MaybeHas::<Scene>::get_mut(&mut self.project) });
|
{ MaybeHas::<Scene>::get_mut(&mut self.project) });
|
||||||
|
|
||||||
|
impl HasClipsSize for App {
|
||||||
|
fn clips_size (&self) -> &Measure<TuiOut> { &self.project.inner_size }
|
||||||
|
}
|
||||||
|
impl HasTrackScroll for App {
|
||||||
|
fn track_scroll (&self) -> usize { self.project.track_scroll() }
|
||||||
|
}
|
||||||
|
impl HasSceneScroll for App {
|
||||||
|
fn scene_scroll (&self) -> usize { self.project.scene_scroll() }
|
||||||
|
}
|
||||||
|
impl HasJack<'static> for App {
|
||||||
|
fn jack (&self) -> &Jack<'static> { &self.jack }
|
||||||
|
}
|
||||||
impl ScenesView for App {
|
impl ScenesView for App {
|
||||||
fn w_side (&self) -> u16 { 20 }
|
fn w_side (&self) -> u16 { 20 }
|
||||||
fn w_mid (&self) -> u16 { (self.width() as u16).saturating_sub(self.w_side()) }
|
fn w_mid (&self) -> u16 { (self.width() as u16).saturating_sub(self.w_side()) }
|
||||||
fn h_scenes (&self) -> u16 { (self.height() as u16).saturating_sub(20) }
|
fn h_scenes (&self) -> u16 { (self.height() as u16).saturating_sub(20) }
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Draw<TuiOut> for App {
|
|
||||||
fn draw (&self, to: &mut TuiOut) {
|
|
||||||
for (index, dsl) in self.mode.view.iter().enumerate() {
|
|
||||||
to.place(&Align::nw(Push::y(1 + index as u16 * 2, dsl.src().unwrap())));
|
|
||||||
//let _ = self.view(to, dsl).expect("render failed");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl App {
|
|
||||||
|
|
||||||
fn view <'a> (&'a self, to: &mut TuiOut, dsl: impl Dsl + 'a) -> Usually<()> {
|
|
||||||
if let Ok(Some(expr)) = dsl.expr() {
|
|
||||||
self.view_expr(to, expr)
|
|
||||||
} else if let Ok(Some(word)) = dsl.word() {
|
|
||||||
self.view_word(to, word)
|
|
||||||
} else {
|
|
||||||
panic!("{dsl:?}: invalid")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn view_expr <'a> (&'a self, to: &mut TuiOut, expr: impl DslExpr + 'a) -> Usually<()> {
|
|
||||||
let head = expr.head()?; let args = expr.tail();
|
|
||||||
let mut frags = head.src()?.unwrap_or_default().split("/");
|
|
||||||
let arg0 = args.head(); let tail0 = args.tail();
|
|
||||||
let arg1 = tail0.head(); let tail1 = tail0.tail();
|
|
||||||
let arg2 = tail1.head(); let tail2 = tail1.tail();
|
|
||||||
let arg3 = tail2.head(); let tail3 = tail2.tail();
|
|
||||||
match frags.next() {
|
|
||||||
|
|
||||||
Some("text") => to.place(&frags.next()),
|
|
||||||
|
|
||||||
Some("when") => to.place(&When::new(
|
|
||||||
self.from(arg0?)?.unwrap(),
|
|
||||||
Thunk::new(move|to: &mut TuiOut|self.view(to, arg1).unwrap())
|
|
||||||
)),
|
|
||||||
|
|
||||||
Some("either") => to.place(&Either::new(
|
|
||||||
self.from(arg0?)?.unwrap(),
|
|
||||||
Thunk::new(move|to: &mut TuiOut|self.view(to, arg1).unwrap()),
|
|
||||||
Thunk::new(move|to: &mut TuiOut|self.view(to, arg2).unwrap())
|
|
||||||
)),
|
|
||||||
|
|
||||||
Some("fg") => {
|
|
||||||
let arg0 = arg0?.expect("fg: expected arg 0 (color)");
|
|
||||||
to.place(&Tui::fg(
|
|
||||||
DslNs::<Color>::from(self, arg0)?.unwrap_or_else(||panic!("fg: {arg0:?}: not a color")),
|
|
||||||
Thunk::new(move|to: &mut TuiOut|self.view(to, arg1).unwrap()),
|
|
||||||
))
|
|
||||||
},
|
|
||||||
|
|
||||||
Some("bg") => {
|
|
||||||
let arg0 = arg0?.expect("bg: expected arg 0 (color)");
|
|
||||||
to.place(&Tui::bg(
|
|
||||||
DslNs::<Color>::from(self, arg0)?.unwrap_or_else(||panic!("bg: {arg0:?}: not a color")),
|
|
||||||
Thunk::new(move|to: &mut TuiOut|self.view(to, arg1).unwrap()),
|
|
||||||
))
|
|
||||||
},
|
|
||||||
|
|
||||||
Some("bsp") => to.place(&{
|
|
||||||
let a = Thunk::new(move|to: &mut TuiOut|self.view(to, arg0).unwrap());
|
|
||||||
let b = Thunk::new(move|to: &mut TuiOut|self.view(to, arg1).unwrap());
|
|
||||||
match frags.next() {
|
|
||||||
Some("n") => Bsp::n(a, b),
|
|
||||||
Some("s") => Bsp::s(a, b),
|
|
||||||
Some("e") => Bsp::e(a, b),
|
|
||||||
Some("w") => Bsp::w(a, b),
|
|
||||||
Some("a") => Bsp::a(a, b),
|
|
||||||
Some("b") => Bsp::b(a, b),
|
|
||||||
frag => unimplemented!("bsp/{frag:?}")
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
|
|
||||||
Some("align") => to.place(&{
|
|
||||||
let a = Thunk::new(move|to: &mut TuiOut|self.view(to, arg0).unwrap());
|
|
||||||
match frags.next() {
|
|
||||||
Some("n") => Align::n(a),
|
|
||||||
Some("s") => Align::s(a),
|
|
||||||
Some("e") => Align::e(a),
|
|
||||||
Some("w") => Align::w(a),
|
|
||||||
Some("x") => Align::x(a),
|
|
||||||
Some("y") => Align::y(a),
|
|
||||||
Some("c") => Align::c(a),
|
|
||||||
frag => unimplemented!("align/{frag:?}")
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
|
|
||||||
Some("fill") => to.place(&{
|
|
||||||
let a = Thunk::new(move|to: &mut TuiOut|self.view(to, arg0).unwrap());
|
|
||||||
match frags.next() {
|
|
||||||
Some("x") => Fill::X(a),
|
|
||||||
Some("y") => Fill::Y(a),
|
|
||||||
Some("xy") => Fill::XY(a),
|
|
||||||
frag => unimplemented!("fill/{frag:?}")
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
|
|
||||||
Some("fixed") => to.place(&{
|
|
||||||
let axis = frags.next();
|
|
||||||
let arg = match axis { Some("x") | Some("y") => arg1, Some("xy") => arg2, _ => panic!() };
|
|
||||||
let cb = Thunk::new(move|to: &mut TuiOut|self.view(to, arg).unwrap());
|
|
||||||
match axis {
|
|
||||||
Some("x") => Fixed::X(self.from(arg0?)?.unwrap(), cb),
|
|
||||||
Some("y") => Fixed::Y(self.from(arg0?)?.unwrap(), cb),
|
|
||||||
Some("xy") => Fixed::XY(self.from(arg0?)?.unwrap(), self.from(arg1?)?.unwrap(), cb),
|
|
||||||
frag => unimplemented!("fixed/{frag:?} ({expr:?}) ({head:?}) ({:?})",
|
|
||||||
head.src()?.unwrap_or_default().split("/").next())
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
|
|
||||||
Some("min") => to.place(&{
|
|
||||||
let c = match frags.next() {
|
|
||||||
Some("x") | Some("y") => arg1, Some("xy") => arg2, _ => panic!()
|
|
||||||
};
|
|
||||||
let cb = Thunk::new(move|to: &mut TuiOut|self.view(to, c).unwrap());
|
|
||||||
match frags.next() {
|
|
||||||
Some("x") => Min::X(self.from(arg0?)?.unwrap(), cb),
|
|
||||||
Some("y") => Min::Y(self.from(arg0?)?.unwrap(), cb),
|
|
||||||
Some("xy") => Min::XY(self.from(arg0?)?.unwrap(), self.from(arg1?)?.unwrap(), cb),
|
|
||||||
frag => unimplemented!("min/{frag:?}")
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
|
|
||||||
Some("max") => to.place(&{
|
|
||||||
let c = match frags.next() {
|
|
||||||
Some("x") | Some("y") => arg1, Some("xy") => arg2, _ => panic!()
|
|
||||||
};
|
|
||||||
let cb = Thunk::new(move|to: &mut TuiOut|self.view(to, c).unwrap());
|
|
||||||
match frags.next() {
|
|
||||||
Some("x") => Max::X(self.from(arg0?)?.unwrap(), cb),
|
|
||||||
Some("y") => Max::Y(self.from(arg0?)?.unwrap(), cb),
|
|
||||||
Some("xy") => Max::XY(self.from(arg0?)?.unwrap(), self.from(arg1?)?.unwrap(), cb),
|
|
||||||
frag => unimplemented!("max/{frag:?}")
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
|
|
||||||
_ => panic!("unexpected: {expr:?}")
|
|
||||||
|
|
||||||
};
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn view_word <'a> (&'a self, to: &mut TuiOut, dsl: impl DslExpr + 'a) -> Usually<()> {
|
|
||||||
let mut frags = dsl.src()?.unwrap().split("/");
|
|
||||||
match frags.next() {
|
|
||||||
|
|
||||||
Some(":logo") => to.place(&Fixed::xy(32, 7, Tui::bold(true, Tui::fg(Rgb(240,200,180), col!{
|
|
||||||
Fixed::y(1, ""),
|
|
||||||
Fixed::y(1, ""),
|
|
||||||
Fixed::y(1, "~~ ╓─╥─╖ ╓──╖ ╥ ╖ ~~~~~~~~~~~~"),
|
|
||||||
Fixed::y(1, Bsp::e("~~~~ ║ ~ ╟─╌ ~╟─< ~~ ", Bsp::e(Tui::fg(Rgb(230,100,40), "v0.3.0"), " ~~"))),
|
|
||||||
Fixed::y(1, "~~~~ ╨ ~ ╙──╜ ╨ ╜ ~~~~~~~~~~~~"),
|
|
||||||
})))),
|
|
||||||
|
|
||||||
Some(":status") => to.place(&"TODO: Status Bar"),
|
|
||||||
|
|
||||||
Some(":meters") => match frags.next() {
|
|
||||||
Some("input") => to.place(&Tui::bg(Rgb(30, 30, 30), Fill::y(Align::s("Input Meters")))),
|
|
||||||
Some("output") => to.place(&Tui::bg(Rgb(30, 30, 30), Fill::y(Align::s("Output Meters")))),
|
|
||||||
_ => panic!()
|
|
||||||
},
|
|
||||||
|
|
||||||
Some(":tracks") => match frags.next() {
|
|
||||||
None => to.place(&"TODO tracks"),
|
|
||||||
Some("names") => to.place(&self.project.view_track_names(self.color.clone())),//Tui::bg(Rgb(40, 40, 40), Fill::x(Align::w("Track Names")))),
|
|
||||||
Some("names") => to.place(&self.project.view_track_names(self.color.clone())),//Tui::bg(Rgb(40, 40, 40), Fill::x(Align::w("Track Names")))),
|
|
||||||
Some("inputs") => to.place(&Tui::bg(Rgb(40, 40, 40), Fill::x(Align::w("Track Inputs")))),
|
|
||||||
Some("devices") => to.place(&Tui::bg(Rgb(40, 40, 40), Fill::x(Align::w("Track Devices")))),
|
|
||||||
Some("outputs") => to.place(&Tui::bg(Rgb(40, 40, 40), Fill::x(Align::w("Track Outputs")))),
|
|
||||||
_ => panic!()
|
|
||||||
},
|
|
||||||
|
|
||||||
Some(":scenes") => match frags.next() {
|
|
||||||
None => to.place(&"TODO scenes"),
|
|
||||||
Some(":scenes/names") => to.place(&"TODO Scene Names"),
|
|
||||||
_ => panic!()
|
|
||||||
},
|
|
||||||
|
|
||||||
Some(":editor") => to.place(&"TODO Editor"),
|
|
||||||
|
|
||||||
Some(":dialog") => match frags.next() {
|
|
||||||
Some("menu") => to.place(&if let Dialog::Menu(selected, items) = &self.dialog {
|
|
||||||
let items = items.clone();
|
|
||||||
let selected = selected;
|
|
||||||
Some(Fill::xy(Thunk::new(move|to: &mut TuiOut|{
|
|
||||||
for (index, MenuItem(item, _)) in items.0.iter().enumerate() {
|
|
||||||
to.place(&Push::y((2 * index) as u16,
|
|
||||||
Tui::fg_bg(
|
|
||||||
if *selected == index { Rgb(240,200,180) } else { Rgb(200, 200, 200) },
|
|
||||||
if *selected == index { Rgb(80, 80, 50) } else { Rgb(30, 30, 30) },
|
|
||||||
Fixed::y(2, Align::n(Fill::x(item)))
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
})))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}),
|
|
||||||
_ => unimplemented!("App::view_word: {dsl:?} ({frags:?})"),
|
|
||||||
},
|
|
||||||
|
|
||||||
Some(":templates") => to.place(&{
|
|
||||||
let modes = self.config.modes.clone();
|
|
||||||
let height = (modes.read().unwrap().len() * 2) as u16;
|
|
||||||
Fixed::y(height, Min::x(30, Thunk::new(move |to: &mut TuiOut|{
|
|
||||||
for (index, (id, profile)) in modes.read().unwrap().iter().enumerate() {
|
|
||||||
let bg = if index == 0 { Rgb(70,70,70) } else { Rgb(50,50,50) };
|
|
||||||
let name = profile.name.get(0).map(|x|x.as_ref()).unwrap_or("<no name>");
|
|
||||||
let info = profile.info.get(0).map(|x|x.as_ref()).unwrap_or("<no info>");
|
|
||||||
let fg1 = Rgb(224, 192, 128);
|
|
||||||
let fg2 = Rgb(224, 128, 32);
|
|
||||||
let field_name = Fill::x(Align::w(Tui::fg(fg1, name)));
|
|
||||||
let field_id = Fill::x(Align::e(Tui::fg(fg2, id)));
|
|
||||||
let field_info = Fill::x(Align::w(info));
|
|
||||||
to.place(&Push::y((2 * index) as u16,
|
|
||||||
Fixed::y(2, Fill::x(Tui::bg(bg, Bsp::s(
|
|
||||||
Bsp::a(field_name, field_id), field_info))))));
|
|
||||||
}
|
|
||||||
})))
|
|
||||||
}),
|
|
||||||
|
|
||||||
Some(":sessions") => to.place(&Fixed::y(6, Min::x(30, Thunk::new(|to: &mut TuiOut|{
|
|
||||||
let fg = Rgb(224, 192, 128);
|
|
||||||
for (index, name) in ["session1", "session2", "session3"].iter().enumerate() {
|
|
||||||
let bg = if index == 0 { Rgb(50,50,50) } else { Rgb(40,40,40) };
|
|
||||||
to.place(&Push::y((2 * index) as u16,
|
|
||||||
&Fixed::y(2, Fill::x(Tui::bg(bg, Align::w(Tui::fg(fg, name)))))));
|
|
||||||
}
|
|
||||||
})))),
|
|
||||||
|
|
||||||
Some(":browse/title") => to.place(&Fill::x(Align::w(FieldV(Default::default(),
|
|
||||||
match self.dialog.browser_target().unwrap() {
|
|
||||||
BrowseTarget::SaveProject => "Save project:",
|
|
||||||
BrowseTarget::LoadProject => "Load project:",
|
|
||||||
BrowseTarget::ImportSample(_) => "Import sample:",
|
|
||||||
BrowseTarget::ExportSample(_) => "Export sample:",
|
|
||||||
BrowseTarget::ImportClip(_) => "Import clip:",
|
|
||||||
BrowseTarget::ExportClip(_) => "Export clip:",
|
|
||||||
}, Shrink::x(3, Fixed::y(1, Tui::fg(Tui::g(96), RepeatH("🭻")))))))),
|
|
||||||
|
|
||||||
Some(":device") => {
|
|
||||||
let selected = self.dialog.device_kind().unwrap();
|
|
||||||
to.place(&Bsp::s(Tui::bold(true, "Add device"), Map::south(1,
|
|
||||||
move||device_kinds().iter(),
|
|
||||||
move|label: &&'static str, i|{
|
|
||||||
let bg = if i == selected { Rgb(64,128,32) } else { Rgb(0,0,0) };
|
|
||||||
let lb = if i == selected { "[ " } else { " " };
|
|
||||||
let rb = if i == selected { " ]" } else { " " };
|
|
||||||
Fill::x(Tui::bg(bg, Bsp::e(lb, Bsp::w(rb, "FIXME device name")))) })))
|
|
||||||
},
|
|
||||||
|
|
||||||
Some(_) => {
|
|
||||||
let views = self.config.views.read().unwrap();
|
|
||||||
if let Some(dsl) = views.get(dsl.src()?.unwrap()) {
|
|
||||||
let dsl = dsl.clone();
|
|
||||||
std::mem::drop(views);
|
|
||||||
self.view(to, dsl)?
|
|
||||||
} else {
|
|
||||||
unimplemented!("{dsl:?}");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
_ => unreachable!()
|
|
||||||
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
|
|
||||||
|
|
@ -627,6 +495,323 @@ impl App {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Various possible dialog modes.
|
||||||
|
#[derive(Debug, Clone, Default, PartialEq)]
|
||||||
|
pub enum Dialog {
|
||||||
|
#[default] None,
|
||||||
|
Help(usize),
|
||||||
|
Menu(usize, MenuItems),
|
||||||
|
Device(usize),
|
||||||
|
Message(Arc<str>),
|
||||||
|
Browse(BrowseTarget, Arc<Browse>),
|
||||||
|
Options,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Default, PartialEq)]
|
||||||
|
pub struct MenuItems(pub Arc<[MenuItem]>);
|
||||||
|
|
||||||
|
impl AsRef<Arc<[MenuItem]>> for MenuItems {
|
||||||
|
fn as_ref (&self) -> &Arc<[MenuItem]> {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct MenuItem(
|
||||||
|
/// Label
|
||||||
|
pub Arc<str>,
|
||||||
|
/// Callback
|
||||||
|
pub Arc<Box<dyn Fn(&mut App)->Usually<()> + Send + Sync>>
|
||||||
|
);
|
||||||
|
|
||||||
|
impl Default for MenuItem {
|
||||||
|
fn default () -> Self { Self("".into(), Arc::new(Box::new(|_|Ok(())))) }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_debug!(MenuItem |self, w| { write!(w, "{}", &self.0) });
|
||||||
|
|
||||||
|
impl PartialEq for MenuItem {
|
||||||
|
fn eq (&self, other: &Self) -> bool {
|
||||||
|
self.0 == other.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Dialog {
|
||||||
|
pub fn welcome () -> Self {
|
||||||
|
Self::Menu(1, MenuItems([
|
||||||
|
MenuItem("Resume session".into(), Arc::new(Box::new(|_|Ok(())))),
|
||||||
|
MenuItem("Create new session".into(), Arc::new(Box::new(|app|Ok({
|
||||||
|
app.dialog = Dialog::None;
|
||||||
|
app.mode = app.config.modes.clone().read().unwrap().get(":arranger").cloned().unwrap();
|
||||||
|
})))),
|
||||||
|
MenuItem("Load old session".into(), Arc::new(Box::new(|_|Ok(())))),
|
||||||
|
].into()))
|
||||||
|
}
|
||||||
|
pub fn menu_next (&self) -> Self {
|
||||||
|
match self {
|
||||||
|
Self::Menu(index, items) => Self::Menu(wrap_inc(*index, items.0.len()), items.clone()),
|
||||||
|
_ => Self::None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn menu_prev (&self) -> Self {
|
||||||
|
match self {
|
||||||
|
Self::Menu(index, items) => Self::Menu(wrap_dec(*index, items.0.len()), items.clone()),
|
||||||
|
_ => Self::None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn menu_selected (&self) -> Option<usize> {
|
||||||
|
if let Self::Menu(selected, _) = self { Some(*selected) } else { None }
|
||||||
|
}
|
||||||
|
pub fn device_kind (&self) -> Option<usize> {
|
||||||
|
if let Self::Device(index) = self { Some(*index) } else { None }
|
||||||
|
}
|
||||||
|
pub fn device_kind_next (&self) -> Option<usize> {
|
||||||
|
self.device_kind().map(|index|(index + 1) % device_kinds().len())
|
||||||
|
}
|
||||||
|
pub fn device_kind_prev (&self) -> Option<usize> {
|
||||||
|
self.device_kind().map(|index|index.overflowing_sub(1).0.min(device_kinds().len().saturating_sub(1)))
|
||||||
|
}
|
||||||
|
pub fn message (&self) -> Option<&str> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
pub fn browser (&self) -> Option<&Arc<Browse>> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
pub fn browser_target (&self) -> Option<&BrowseTarget> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handle!(TuiIn:|self: App, input|{
|
||||||
|
let mut commands = vec![];
|
||||||
|
for id in self.mode.keys.iter() {
|
||||||
|
if let Some(event_map) = self.config.binds.clone().read().unwrap().get(id.as_ref()) {
|
||||||
|
if let Some(bindings) = event_map.query(input.event()) {
|
||||||
|
for binding in bindings {
|
||||||
|
for command in binding.commands.iter() {
|
||||||
|
if let Some(command) = self.from(command)? as Option<AppCommand> {
|
||||||
|
commands.push(command)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for command in commands.into_iter() {
|
||||||
|
let result = command.execute(self);
|
||||||
|
match result {
|
||||||
|
Ok(undo) => {
|
||||||
|
self.history.push((command, undo));
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
self.history.push((command, None));
|
||||||
|
return Err(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(None)
|
||||||
|
});
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
pub enum Axis { X, Y, Z, I }
|
||||||
|
|
||||||
|
impl<'a> DslNs<'a, AppCommand> for App {}
|
||||||
|
impl<'a> DslNsExprs<'a, AppCommand> for App {}
|
||||||
|
impl<'a> DslNsWords<'a, AppCommand> for App {
|
||||||
|
dsl_words!('a |app| -> AppCommand {
|
||||||
|
"x/inc" => AppCommand::Inc { axis: Axis::X },
|
||||||
|
"x/dec" => AppCommand::Dec { axis: Axis::X },
|
||||||
|
"y/inc" => AppCommand::Inc { axis: Axis::Y },
|
||||||
|
"y/dec" => AppCommand::Dec { axis: Axis::Y },
|
||||||
|
"confirm" => AppCommand::Confirm,
|
||||||
|
"cancel" => AppCommand::Cancel,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for AppCommand { fn default () -> Self { Self::Nop } }
|
||||||
|
|
||||||
|
def_command!(AppCommand: |app: App| {
|
||||||
|
Nop => Ok(None),
|
||||||
|
Confirm => Ok(match &app.dialog {
|
||||||
|
Dialog::Menu(index, items) => {
|
||||||
|
let callback = items.0[*index].1.clone();
|
||||||
|
callback(app)?;
|
||||||
|
None
|
||||||
|
},
|
||||||
|
_ => todo!(),
|
||||||
|
}),
|
||||||
|
Cancel => todo!(), // TODO delegate:
|
||||||
|
Inc { axis: Axis } => Ok(match (&app.dialog, axis) {
|
||||||
|
(Dialog::None, _) => todo!(),
|
||||||
|
(Dialog::Menu(_, _), Axis::Y) => AppCommand::SetDialog { dialog: app.dialog.menu_next() }
|
||||||
|
.execute(app)?,
|
||||||
|
_ => todo!()
|
||||||
|
}),
|
||||||
|
Dec { axis: Axis } => Ok(match (&app.dialog, axis) {
|
||||||
|
(Dialog::None, _) => None,
|
||||||
|
(Dialog::Menu(_, _), Axis::Y) => AppCommand::SetDialog { dialog: app.dialog.menu_prev() }
|
||||||
|
.execute(app)?,
|
||||||
|
_ => todo!()
|
||||||
|
}),
|
||||||
|
SetDialog { dialog: Dialog } => {
|
||||||
|
swap_value(&mut app.dialog, dialog, |dialog|Self::SetDialog { dialog })
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
pub fn wrap_inc (index: usize, count: usize) -> usize {
|
||||||
|
if count > 0 { (index + 1) % count } else { 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn wrap_dec (index: usize, count: usize) -> usize {
|
||||||
|
if count > 0 { index.overflowing_sub(1).0.min(count.saturating_sub(1)) } else { 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Dialog {
|
||||||
|
}
|
||||||
|
|
||||||
|
//AppCommand => {
|
||||||
|
//("x/inc" /
|
||||||
|
//("stop-all") => todo!(),//app.project.stop_all(),
|
||||||
|
//("enqueue", clip: Option<Arc<RwLock<MidiClip>>>) => todo!(),
|
||||||
|
//("history", delta: isize) => todo!(),
|
||||||
|
//("zoom", zoom: usize) => todo!(),
|
||||||
|
//("select", selection: Selection) => todo!(),
|
||||||
|
//("dialog" / command: DialogCommand) => todo!(),
|
||||||
|
//("project" / command: ArrangementCommand) => todo!(),
|
||||||
|
//("clock" / command: ClockCommand) => todo!(),
|
||||||
|
//("sampler" / command: SamplerCommand) => todo!(),
|
||||||
|
//("pool" / command: PoolCommand) => todo!(),
|
||||||
|
//("edit" / editor: MidiEditCommand) => todo!(),
|
||||||
|
//};
|
||||||
|
|
||||||
|
//DialogCommand;
|
||||||
|
|
||||||
|
//ArrangementCommand;
|
||||||
|
|
||||||
|
//ClockCommand;
|
||||||
|
|
||||||
|
//SamplerCommand;
|
||||||
|
|
||||||
|
//PoolCommand;
|
||||||
|
|
||||||
|
//MidiEditCommand;
|
||||||
|
|
||||||
|
|
||||||
|
//take!(DialogCommand |state: App, iter|Take::take(&state.dialog, iter));
|
||||||
|
//#[derive(Clone, Debug)]
|
||||||
|
//pub enum DialogCommand {
|
||||||
|
//Open { dialog: Dialog },
|
||||||
|
//Close
|
||||||
|
//}
|
||||||
|
|
||||||
|
//impl Command<Option<Dialog>> for DialogCommand {
|
||||||
|
//fn execute (self, state: &mut Option<Dialog>) -> Perhaps<Self> {
|
||||||
|
//match self {
|
||||||
|
//Self::Open { dialog } => {
|
||||||
|
//*state = Some(dialog);
|
||||||
|
//},
|
||||||
|
//Self::Close => {
|
||||||
|
//*state = None;
|
||||||
|
//}
|
||||||
|
//};
|
||||||
|
//Ok(None)
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
|
||||||
|
//dsl!(DialogCommand: |self: Dialog, iter|todo!());
|
||||||
|
//Dsl::take(&mut self.dialog, iter));
|
||||||
|
|
||||||
|
//#[tengri_proc::command(Option<Dialog>)]//Nope.
|
||||||
|
//impl DialogCommand {
|
||||||
|
//fn open (dialog: &mut Option<Dialog>, new: Dialog) -> Perhaps<Self> {
|
||||||
|
//*dialog = Some(new);
|
||||||
|
//Ok(None)
|
||||||
|
//}
|
||||||
|
//fn close (dialog: &mut Option<Dialog>) -> Perhaps<Self> {
|
||||||
|
//*dialog = None;
|
||||||
|
//Ok(None)
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//dsl_bind!(AppCommand: App {
|
||||||
|
//enqueue = |app, clip: Option<Arc<RwLock<MidiClip>>>| { todo!() };
|
||||||
|
//history = |app, delta: isize| { todo!() };
|
||||||
|
//zoom = |app, zoom: usize| { todo!() };
|
||||||
|
//stop_all = |app| { app.tracks_stop_all(); Ok(None) };
|
||||||
|
////dialog = |app, command: DialogCommand|
|
||||||
|
////Ok(command.delegate(&mut app.dialog, |c|Self::Dialog{command: c})?);
|
||||||
|
//project = |app, command: ArrangementCommand|
|
||||||
|
//Ok(command.delegate(&mut app.project, |c|Self::Project{command: c})?);
|
||||||
|
//clock = |app, command: ClockCommand|
|
||||||
|
//Ok(command.execute(app.clock_mut())?.map(|c|Self::Clock{command: c}));
|
||||||
|
//sampler = |app, command: SamplerCommand|
|
||||||
|
//Ok(app.project.sampler_mut().map(|s|command.delegate(s, |command|Self::Sampler{command}))
|
||||||
|
//.transpose()?.flatten());
|
||||||
|
//pool = |app, command: PoolCommand| {
|
||||||
|
//let undo = command.clone().delegate(&mut app.pool, |command|AppCommand::Pool{command})?;
|
||||||
|
//// update linked editor after pool action
|
||||||
|
//match command {
|
||||||
|
//// autoselect: automatically load selected clip in editor
|
||||||
|
//PoolCommand::Select { .. } |
|
||||||
|
//// autocolor: update color in all places simultaneously
|
||||||
|
//PoolCommand::Clip { command: PoolClipCommand::SetColor { .. } } => {
|
||||||
|
//let clip = app.pool.clip().clone();
|
||||||
|
//app.editor_mut().map(|editor|editor.set_clip(clip.as_ref()))
|
||||||
|
//},
|
||||||
|
//_ => None
|
||||||
|
//};
|
||||||
|
//Ok(undo)
|
||||||
|
//};
|
||||||
|
//select = |app, selection: Selection| {
|
||||||
|
//*app.project.selection_mut() = selection;
|
||||||
|
////todo!
|
||||||
|
////if let Some(ref mut editor) = app.editor_mut() {
|
||||||
|
////editor.set_clip(match selection {
|
||||||
|
////Selection::TrackClip { track, scene } if let Some(Some(Some(clip))) = app
|
||||||
|
////.project
|
||||||
|
////.scenes.get(scene)
|
||||||
|
////.map(|s|s.clips.get(track))
|
||||||
|
////=>
|
||||||
|
////Some(clip),
|
||||||
|
////_ =>
|
||||||
|
////None
|
||||||
|
////});
|
||||||
|
////}
|
||||||
|
//Ok(None)
|
||||||
|
////("select" [t: usize, s: usize] Some(match (t.expect("no track"), s.expect("no scene")) {
|
||||||
|
////(0, 0) => Self::Select(Selection::Mix),
|
||||||
|
////(t, 0) => Self::Select(Selection::Track(t)),
|
||||||
|
////(0, s) => Self::Select(Selection::Scene(s)),
|
||||||
|
////(t, s) => Self::Select(Selection::TrackClip { track: t, scene: s }) })))
|
||||||
|
//// autoedit: load focused clip in editor.
|
||||||
|
//};
|
||||||
|
////fn color (app: &mut App, theme: ItemTheme) -> Perhaps<Self> {
|
||||||
|
////Ok(app.set_color(Some(theme)).map(|theme|Self::Color{theme}))
|
||||||
|
////}
|
||||||
|
////fn launch (app: &mut App) -> Perhaps<Self> {
|
||||||
|
////app.project.launch();
|
||||||
|
////Ok(None)
|
||||||
|
////}
|
||||||
|
//toggle_editor = |app, value: bool|{ app.toggle_editor(Some(value)); Ok(None) };
|
||||||
|
//editor = |app, command: MidiEditCommand| Ok(if let Some(editor) = app.editor_mut() {
|
||||||
|
//let undo = command.clone().delegate(editor, |command|AppCommand::Editor{command})?;
|
||||||
|
//// update linked sampler after editor action
|
||||||
|
//app.project.sampler_mut().map(|sampler|match command {
|
||||||
|
//// autoselect: automatically select sample in sampler
|
||||||
|
//MidiEditCommand::SetNotePos { pos } => { sampler.set_note_pos(pos); },
|
||||||
|
//_ => {}
|
||||||
|
//});
|
||||||
|
//undo
|
||||||
|
//} else {
|
||||||
|
//None
|
||||||
|
//});
|
||||||
|
//});
|
||||||
|
//take!(ClockCommand |state: App, iter|Take::take(state.clock(), iter));
|
||||||
|
//take!(MidiEditCommand |state: App, iter|Ok(state.editor().map(|x|Take::take(x, iter)).transpose()?.flatten()));
|
||||||
|
//take!(PoolCommand |state: App, iter|Take::take(&state.pool, iter));
|
||||||
|
//take!(SamplerCommand |state: App, iter|Ok(state.project.sampler().map(|x|Take::take(x, iter)).transpose()?.flatten()));
|
||||||
|
//take!(ArrangementCommand |state: App, iter|Take::take(&state.project, iter));
|
||||||
|
|
||||||
//pub fn view_nil (_: &App) -> TuiCb {
|
//pub fn view_nil (_: &App) -> TuiCb {
|
||||||
//|to|to.place(&Fill::xy("·"))
|
//|to|to.place(&Fill::xy("·"))
|
||||||
//}
|
//}
|
||||||
|
|
|
||||||
230
tek/tek_bind.rs
230
tek/tek_bind.rs
|
|
@ -1,231 +1 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
handle!(TuiIn:|self: App, input|{
|
|
||||||
let mut commands = vec![];
|
|
||||||
for id in self.mode.keys.iter() {
|
|
||||||
if let Some(event_map) = self.config.binds.clone().read().unwrap().get(id.as_ref()) {
|
|
||||||
if let Some(bindings) = event_map.query(input.event()) {
|
|
||||||
for binding in bindings {
|
|
||||||
for command in binding.commands.iter() {
|
|
||||||
if let Some(command) = self.from(command)? as Option<AppCommand> {
|
|
||||||
commands.push(command)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for command in commands.into_iter() {
|
|
||||||
let result = command.execute(self);
|
|
||||||
match result {
|
|
||||||
Ok(undo) => {
|
|
||||||
self.history.push((command, undo));
|
|
||||||
},
|
|
||||||
Err(e) => {
|
|
||||||
self.history.push((command, None));
|
|
||||||
return Err(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(None)
|
|
||||||
});
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
|
||||||
pub enum Axis { X, Y, Z, I }
|
|
||||||
|
|
||||||
impl<'a> DslNs<'a, AppCommand> for App {}
|
|
||||||
impl<'a> DslNsExprs<'a, AppCommand> for App {}
|
|
||||||
impl<'a> DslNsWords<'a, AppCommand> for App {
|
|
||||||
dsl_words!('a |app| -> AppCommand {
|
|
||||||
"x/inc" => AppCommand::Inc { axis: Axis::X },
|
|
||||||
"x/dec" => AppCommand::Dec { axis: Axis::X },
|
|
||||||
"y/inc" => AppCommand::Inc { axis: Axis::Y },
|
|
||||||
"y/dec" => AppCommand::Dec { axis: Axis::Y },
|
|
||||||
"confirm" => AppCommand::Confirm,
|
|
||||||
"cancel" => AppCommand::Cancel,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for AppCommand { fn default () -> Self { Self::Nop } }
|
|
||||||
|
|
||||||
def_command!(AppCommand: |app: App| {
|
|
||||||
Nop => Ok(None),
|
|
||||||
Confirm => Ok(match &app.dialog {
|
|
||||||
Dialog::Menu(index, items) => {
|
|
||||||
let callback = items.0[*index].1.clone();
|
|
||||||
callback(app)?;
|
|
||||||
None
|
|
||||||
},
|
|
||||||
_ => todo!(),
|
|
||||||
}),
|
|
||||||
Cancel => todo!(), // TODO delegate:
|
|
||||||
Inc { axis: Axis } => Ok(match (&app.dialog, axis) {
|
|
||||||
(Dialog::None, _) => todo!(),
|
|
||||||
(Dialog::Menu(_, _), Axis::Y) => AppCommand::SetDialog { dialog: app.dialog.menu_next() }
|
|
||||||
.execute(app)?,
|
|
||||||
_ => todo!()
|
|
||||||
}),
|
|
||||||
Dec { axis: Axis } => Ok(match (&app.dialog, axis) {
|
|
||||||
(Dialog::None, _) => None,
|
|
||||||
(Dialog::Menu(_, _), Axis::Y) => AppCommand::SetDialog { dialog: app.dialog.menu_prev() }
|
|
||||||
.execute(app)?,
|
|
||||||
_ => todo!()
|
|
||||||
}),
|
|
||||||
SetDialog { dialog: Dialog } => {
|
|
||||||
swap_value(&mut app.dialog, dialog, |dialog|Self::SetDialog { dialog })
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
pub fn wrap_inc (index: usize, count: usize) -> usize {
|
|
||||||
if count > 0 { (index + 1) % count } else { 0 }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn wrap_dec (index: usize, count: usize) -> usize {
|
|
||||||
if count > 0 { index.overflowing_sub(1).0.min(count.saturating_sub(1)) } else { 0 }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Dialog {
|
|
||||||
}
|
|
||||||
|
|
||||||
//AppCommand => {
|
|
||||||
//("x/inc" /
|
|
||||||
//("stop-all") => todo!(),//app.project.stop_all(),
|
|
||||||
//("enqueue", clip: Option<Arc<RwLock<MidiClip>>>) => todo!(),
|
|
||||||
//("history", delta: isize) => todo!(),
|
|
||||||
//("zoom", zoom: usize) => todo!(),
|
|
||||||
//("select", selection: Selection) => todo!(),
|
|
||||||
//("dialog" / command: DialogCommand) => todo!(),
|
|
||||||
//("project" / command: ArrangementCommand) => todo!(),
|
|
||||||
//("clock" / command: ClockCommand) => todo!(),
|
|
||||||
//("sampler" / command: SamplerCommand) => todo!(),
|
|
||||||
//("pool" / command: PoolCommand) => todo!(),
|
|
||||||
//("edit" / editor: MidiEditCommand) => todo!(),
|
|
||||||
//};
|
|
||||||
|
|
||||||
//DialogCommand;
|
|
||||||
|
|
||||||
//ArrangementCommand;
|
|
||||||
|
|
||||||
//ClockCommand;
|
|
||||||
|
|
||||||
//SamplerCommand;
|
|
||||||
|
|
||||||
//PoolCommand;
|
|
||||||
|
|
||||||
//MidiEditCommand;
|
|
||||||
|
|
||||||
|
|
||||||
//take!(DialogCommand |state: App, iter|Take::take(&state.dialog, iter));
|
|
||||||
//#[derive(Clone, Debug)]
|
|
||||||
//pub enum DialogCommand {
|
|
||||||
//Open { dialog: Dialog },
|
|
||||||
//Close
|
|
||||||
//}
|
|
||||||
|
|
||||||
//impl Command<Option<Dialog>> for DialogCommand {
|
|
||||||
//fn execute (self, state: &mut Option<Dialog>) -> Perhaps<Self> {
|
|
||||||
//match self {
|
|
||||||
//Self::Open { dialog } => {
|
|
||||||
//*state = Some(dialog);
|
|
||||||
//},
|
|
||||||
//Self::Close => {
|
|
||||||
//*state = None;
|
|
||||||
//}
|
|
||||||
//};
|
|
||||||
//Ok(None)
|
|
||||||
//}
|
|
||||||
//}
|
|
||||||
|
|
||||||
//dsl!(DialogCommand: |self: Dialog, iter|todo!());
|
|
||||||
//Dsl::take(&mut self.dialog, iter));
|
|
||||||
|
|
||||||
//#[tengri_proc::command(Option<Dialog>)]//Nope.
|
|
||||||
//impl DialogCommand {
|
|
||||||
//fn open (dialog: &mut Option<Dialog>, new: Dialog) -> Perhaps<Self> {
|
|
||||||
//*dialog = Some(new);
|
|
||||||
//Ok(None)
|
|
||||||
//}
|
|
||||||
//fn close (dialog: &mut Option<Dialog>) -> Perhaps<Self> {
|
|
||||||
//*dialog = None;
|
|
||||||
//Ok(None)
|
|
||||||
//}
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//dsl_bind!(AppCommand: App {
|
|
||||||
//enqueue = |app, clip: Option<Arc<RwLock<MidiClip>>>| { todo!() };
|
|
||||||
//history = |app, delta: isize| { todo!() };
|
|
||||||
//zoom = |app, zoom: usize| { todo!() };
|
|
||||||
//stop_all = |app| { app.tracks_stop_all(); Ok(None) };
|
|
||||||
////dialog = |app, command: DialogCommand|
|
|
||||||
////Ok(command.delegate(&mut app.dialog, |c|Self::Dialog{command: c})?);
|
|
||||||
//project = |app, command: ArrangementCommand|
|
|
||||||
//Ok(command.delegate(&mut app.project, |c|Self::Project{command: c})?);
|
|
||||||
//clock = |app, command: ClockCommand|
|
|
||||||
//Ok(command.execute(app.clock_mut())?.map(|c|Self::Clock{command: c}));
|
|
||||||
//sampler = |app, command: SamplerCommand|
|
|
||||||
//Ok(app.project.sampler_mut().map(|s|command.delegate(s, |command|Self::Sampler{command}))
|
|
||||||
//.transpose()?.flatten());
|
|
||||||
//pool = |app, command: PoolCommand| {
|
|
||||||
//let undo = command.clone().delegate(&mut app.pool, |command|AppCommand::Pool{command})?;
|
|
||||||
//// update linked editor after pool action
|
|
||||||
//match command {
|
|
||||||
//// autoselect: automatically load selected clip in editor
|
|
||||||
//PoolCommand::Select { .. } |
|
|
||||||
//// autocolor: update color in all places simultaneously
|
|
||||||
//PoolCommand::Clip { command: PoolClipCommand::SetColor { .. } } => {
|
|
||||||
//let clip = app.pool.clip().clone();
|
|
||||||
//app.editor_mut().map(|editor|editor.set_clip(clip.as_ref()))
|
|
||||||
//},
|
|
||||||
//_ => None
|
|
||||||
//};
|
|
||||||
//Ok(undo)
|
|
||||||
//};
|
|
||||||
//select = |app, selection: Selection| {
|
|
||||||
//*app.project.selection_mut() = selection;
|
|
||||||
////todo!
|
|
||||||
////if let Some(ref mut editor) = app.editor_mut() {
|
|
||||||
////editor.set_clip(match selection {
|
|
||||||
////Selection::TrackClip { track, scene } if let Some(Some(Some(clip))) = app
|
|
||||||
////.project
|
|
||||||
////.scenes.get(scene)
|
|
||||||
////.map(|s|s.clips.get(track))
|
|
||||||
////=>
|
|
||||||
////Some(clip),
|
|
||||||
////_ =>
|
|
||||||
////None
|
|
||||||
////});
|
|
||||||
////}
|
|
||||||
//Ok(None)
|
|
||||||
////("select" [t: usize, s: usize] Some(match (t.expect("no track"), s.expect("no scene")) {
|
|
||||||
////(0, 0) => Self::Select(Selection::Mix),
|
|
||||||
////(t, 0) => Self::Select(Selection::Track(t)),
|
|
||||||
////(0, s) => Self::Select(Selection::Scene(s)),
|
|
||||||
////(t, s) => Self::Select(Selection::TrackClip { track: t, scene: s }) })))
|
|
||||||
//// autoedit: load focused clip in editor.
|
|
||||||
//};
|
|
||||||
////fn color (app: &mut App, theme: ItemTheme) -> Perhaps<Self> {
|
|
||||||
////Ok(app.set_color(Some(theme)).map(|theme|Self::Color{theme}))
|
|
||||||
////}
|
|
||||||
////fn launch (app: &mut App) -> Perhaps<Self> {
|
|
||||||
////app.project.launch();
|
|
||||||
////Ok(None)
|
|
||||||
////}
|
|
||||||
//toggle_editor = |app, value: bool|{ app.toggle_editor(Some(value)); Ok(None) };
|
|
||||||
//editor = |app, command: MidiEditCommand| Ok(if let Some(editor) = app.editor_mut() {
|
|
||||||
//let undo = command.clone().delegate(editor, |command|AppCommand::Editor{command})?;
|
|
||||||
//// update linked sampler after editor action
|
|
||||||
//app.project.sampler_mut().map(|sampler|match command {
|
|
||||||
//// autoselect: automatically select sample in sampler
|
|
||||||
//MidiEditCommand::SetNotePos { pos } => { sampler.set_note_pos(pos); },
|
|
||||||
//_ => {}
|
|
||||||
//});
|
|
||||||
//undo
|
|
||||||
//} else {
|
|
||||||
//None
|
|
||||||
//});
|
|
||||||
//});
|
|
||||||
//take!(ClockCommand |state: App, iter|Take::take(state.clock(), iter));
|
|
||||||
//take!(MidiEditCommand |state: App, iter|Ok(state.editor().map(|x|Take::take(x, iter)).transpose()?.flatten()));
|
|
||||||
//take!(PoolCommand |state: App, iter|Take::take(&state.pool, iter));
|
|
||||||
//take!(SamplerCommand |state: App, iter|Ok(state.project.sampler().map(|x|Take::take(x, iter)).transpose()?.flatten()));
|
|
||||||
//take!(ArrangementCommand |state: App, iter|Take::take(&state.project, iter));
|
|
||||||
|
|
|
||||||
|
|
@ -1,88 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
/// Various possible dialog modes.
|
|
||||||
#[derive(Debug, Clone, Default, PartialEq)]
|
|
||||||
pub enum Dialog {
|
|
||||||
#[default] None,
|
|
||||||
Help(usize),
|
|
||||||
Menu(usize, MenuItems),
|
|
||||||
Device(usize),
|
|
||||||
Message(Arc<str>),
|
|
||||||
Browse(BrowseTarget, Arc<Browse>),
|
|
||||||
Options,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default, PartialEq)]
|
|
||||||
pub struct MenuItems(pub Arc<[MenuItem]>);
|
|
||||||
|
|
||||||
impl AsRef<Arc<[MenuItem]>> for MenuItems {
|
|
||||||
fn as_ref (&self) -> &Arc<[MenuItem]> {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct MenuItem(
|
|
||||||
/// Label
|
|
||||||
pub Arc<str>,
|
|
||||||
/// Callback
|
|
||||||
pub Arc<Box<dyn Fn(&mut App)->Usually<()> + Send + Sync>>
|
|
||||||
);
|
|
||||||
|
|
||||||
impl Default for MenuItem {
|
|
||||||
fn default () -> Self { Self("".into(), Arc::new(Box::new(|_|Ok(())))) }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl_debug!(MenuItem |self, w| { write!(w, "{}", &self.0) });
|
|
||||||
|
|
||||||
impl PartialEq for MenuItem {
|
|
||||||
fn eq (&self, other: &Self) -> bool {
|
|
||||||
self.0 == other.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Dialog {
|
|
||||||
pub fn welcome () -> Self {
|
|
||||||
Self::Menu(1, MenuItems([
|
|
||||||
MenuItem("Resume session".into(), Arc::new(Box::new(|_|Ok(())))),
|
|
||||||
MenuItem("Create new session".into(), Arc::new(Box::new(|app|Ok({
|
|
||||||
app.dialog = Dialog::None;
|
|
||||||
app.mode = app.config.modes.clone().read().unwrap().get(":arranger").cloned().unwrap();
|
|
||||||
})))),
|
|
||||||
MenuItem("Load old session".into(), Arc::new(Box::new(|_|Ok(())))),
|
|
||||||
].into()))
|
|
||||||
}
|
|
||||||
pub fn menu_next (&self) -> Self {
|
|
||||||
match self {
|
|
||||||
Self::Menu(index, items) => Self::Menu(wrap_inc(*index, items.0.len()), items.clone()),
|
|
||||||
_ => Self::None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn menu_prev (&self) -> Self {
|
|
||||||
match self {
|
|
||||||
Self::Menu(index, items) => Self::Menu(wrap_dec(*index, items.0.len()), items.clone()),
|
|
||||||
_ => Self::None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn menu_selected (&self) -> Option<usize> {
|
|
||||||
if let Self::Menu(selected, _) = self { Some(*selected) } else { None }
|
|
||||||
}
|
|
||||||
pub fn device_kind (&self) -> Option<usize> {
|
|
||||||
if let Self::Device(index) = self { Some(*index) } else { None }
|
|
||||||
}
|
|
||||||
pub fn device_kind_next (&self) -> Option<usize> {
|
|
||||||
self.device_kind().map(|index|(index + 1) % device_kinds().len())
|
|
||||||
}
|
|
||||||
pub fn device_kind_prev (&self) -> Option<usize> {
|
|
||||||
self.device_kind().map(|index|index.overflowing_sub(1).0.min(device_kinds().len().saturating_sub(1)))
|
|
||||||
}
|
|
||||||
pub fn message (&self) -> Option<&str> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
pub fn browser (&self) -> Option<&Arc<Browse>> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
pub fn browser_target (&self) -> Option<&BrowseTarget> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
69
tek/test.rs
69
tek/test.rs
|
|
@ -1,23 +1,21 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
#[cfg(test)] #[test] fn test_model () -> Usually<()> {
|
#[cfg(test)] #[test] fn test_app () -> Usually<()> {
|
||||||
let mut app = App::default();
|
let mut app = App::default();
|
||||||
let _ = app.clip();
|
|
||||||
let _ = app.toggle_loop();
|
|
||||||
//let _ = app.tracks_add(8, None, &[], &[])?;
|
|
||||||
//let _ = app.track_add_focus()?;
|
|
||||||
let _ = app.scene_longest();
|
|
||||||
let _ = app.scene();
|
|
||||||
let _ = app.scene_mut();
|
|
||||||
let _ = app.scene_add(None, None)?;
|
let _ = app.scene_add(None, None)?;
|
||||||
let _ = app.scene_add_focus()?;
|
let _ = app.update_clock();
|
||||||
let scene = app.scene_del(0);
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)] #[test] fn test_track () -> Usually<()> {
|
||||||
|
let track = Track::default();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)] #[test] fn test_scene () -> Usually<()> {
|
||||||
let scene = Scene::default();
|
let scene = Scene::default();
|
||||||
let _ = scene.pulses();
|
let _ = scene.pulses();
|
||||||
let _ = scene.is_playing(&[]);
|
let _ = scene.is_playing(&[]);
|
||||||
let _ = app.view_transport();
|
|
||||||
let _ = app.view_status();
|
|
||||||
let _ = app.update_clock();
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -27,8 +25,8 @@ use crate::*;
|
||||||
let _ = button_2("", "", false);
|
let _ = button_2("", "", false);
|
||||||
let _ = button_3("", "", "", true);
|
let _ = button_3("", "", "", true);
|
||||||
let _ = button_3("", "", "", false);
|
let _ = button_3("", "", "", false);
|
||||||
let _ = heading("", "", 0, "", true);
|
//let _ = heading("", "", 0, "", true);
|
||||||
let _ = heading("", "", 0, "", false);
|
//let _ = heading("", "", 0, "", false);
|
||||||
let _ = wrap(Reset, Reset, "");
|
let _ = wrap(Reset, Reset, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -56,29 +54,29 @@ use crate::*;
|
||||||
|
|
||||||
#[cfg(test)] #[test] fn test_view_iter () {
|
#[cfg(test)] #[test] fn test_view_iter () {
|
||||||
let mut app = App::default();
|
let mut app = App::default();
|
||||||
app.editor = Some(Default::default());
|
app.project.editor = Some(Default::default());
|
||||||
let _: Vec<_> = app.inputs_with_sizes().collect();
|
//let _: Vec<_> = app.project.inputs_with_sizes().collect();
|
||||||
let _: Vec<_> = app.outputs_with_sizes().collect();
|
//let _: Vec<_> = app.project.outputs_with_sizes().collect();
|
||||||
let _: Vec<_> = app.tracks_with_sizes().collect();
|
let _: Vec<_> = app.project.tracks_with_sizes().collect();
|
||||||
let _: Vec<_> = app.scenes_with_sizes(true, 10, 10).collect();
|
//let _: Vec<_> = app.project.scenes_with_sizes(true, 10, 10).collect();
|
||||||
//let _: Vec<_> = app.scenes_with_colors(true, 10).collect();
|
//let _: Vec<_> = app.scenes_with_colors(true, 10).collect();
|
||||||
//let _: Vec<_> = app.scenes_with_track_colors(true, 10, 10).collect();
|
//let _: Vec<_> = app.scenes_with_track_colors(true, 10, 10).collect();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)] #[test] fn test_view_sizes () {
|
#[cfg(test)] #[test] fn test_view_sizes () {
|
||||||
let app = App::default();
|
let app = App::default();
|
||||||
let _ = app.w();
|
let _ = app.project.w();
|
||||||
let _ = app.w_sidebar();
|
//let _ = app.project.w_sidebar();
|
||||||
let _ = app.w_tracks_area();
|
//let _ = app.project.w_tracks_area();
|
||||||
let _ = app.h();
|
let _ = app.project.h();
|
||||||
let _ = app.h_tracks_area();
|
//let _ = app.project.h_tracks_area();
|
||||||
let _ = app.h_inputs();
|
//let _ = app.project.h_inputs();
|
||||||
let _ = app.h_outputs();
|
//let _ = app.project.h_outputs();
|
||||||
let _ = app.h_scenes();
|
let _ = app.project.h_scenes();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)] #[test] fn test_midi_edit () {
|
#[cfg(test)] #[test] fn test_midi_edit () {
|
||||||
let editor = MidiEditor::default();
|
let _editor = MidiEditor::default();
|
||||||
let mut editor = MidiEditor {
|
let mut editor = MidiEditor {
|
||||||
mode: PianoHorizontal::new(Some(&Arc::new(RwLock::new(MidiClip::stop_all())))),
|
mode: PianoHorizontal::new(Some(&Arc::new(RwLock::new(MidiClip::stop_all())))),
|
||||||
size: Default::default(),
|
size: Default::default(),
|
||||||
|
|
@ -89,12 +87,13 @@ use crate::*;
|
||||||
let _ = editor.clip_status();
|
let _ = editor.clip_status();
|
||||||
let _ = editor.edit_status();
|
let _ = editor.edit_status();
|
||||||
struct TestEditorHost(Option<MidiEditor>);
|
struct TestEditorHost(Option<MidiEditor>);
|
||||||
has_editor!(|self: TestEditorHost|{
|
has!(Option<MidiEditor>: |self: TestEditorHost|self.0);
|
||||||
editor = self.0;
|
//has_editor!(|self: TestEditorHost|{
|
||||||
editor_w = 0;
|
//editor = self.0;
|
||||||
editor_h = 0;
|
//editor_w = 0;
|
||||||
is_editing = false;
|
//editor_h = 0;
|
||||||
});
|
//is_editing = false;
|
||||||
|
//});
|
||||||
let mut host = TestEditorHost(Some(editor));
|
let mut host = TestEditorHost(Some(editor));
|
||||||
let _ = host.editor();
|
let _ = host.editor();
|
||||||
let _ = host.editor_mut();
|
let _ = host.editor_mut();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue