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",
|
||||
"symphonia",
|
||||
"tengri",
|
||||
"tengri_proc",
|
||||
"toml",
|
||||
"uuid",
|
||||
"wavers",
|
||||
|
|
@ -2458,17 +2457,6 @@ dependencies = [
|
|||
"tengri_dsl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tengri_proc"
|
||||
version = "0.14.0"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"tengri_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tengri_tui"
|
||||
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 RUST_BACKTRACE := "1"
|
||||
|
||||
debug := "reset && cargo run --"
|
||||
release := "reset && cargo run --release --"
|
||||
name := "-n tek"
|
||||
bpm := "-b 174"
|
||||
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*'"
|
||||
default:
|
||||
just -l
|
||||
|
||||
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
|
||||
|
||||
bacon:
|
||||
bacon -s
|
||||
|
||||
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'"
|
||||
grcov-binary := "--binary-path ./target/coverage/deps/"
|
||||
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:
|
||||
{{covfig}} time cargo test -j4 --workspace --exclude jack --profile coverage
|
||||
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 --doc
|
||||
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:
|
||||
git commit --amend
|
||||
push:
|
||||
|
|
@ -62,11 +63,18 @@ fpush:
|
|||
ftpush:
|
||||
git push --tags -fu codeberg && git push --tags -fu origin
|
||||
|
||||
name := "-n tek"
|
||||
bpm := "-b 174"
|
||||
clock:
|
||||
{{debug}} {{name}} {{bpm}} clock
|
||||
clock-release:
|
||||
{{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:
|
||||
{{debug}} {{name}} {{bpm}} arranger
|
||||
arranger-ext:
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
# https://dystroy.org/bacon/config/
|
||||
default_job = "check"
|
||||
default_job = "test"
|
||||
env.CARGO_TERM_COLOR = "always"
|
||||
[keybindings]
|
||||
c = "job:check"
|
||||
|
|
@ -24,7 +24,7 @@ command = ["cargo", "clippy", "--all-targets"]
|
|||
need_stdout = false
|
||||
watch = ["tek", "deps"]
|
||||
[jobs.test]
|
||||
command = ["cargo", "test"]
|
||||
command = ["cargo", "test", "--workspace", "--exclude", "jack"]
|
||||
need_stdout = true
|
||||
watch = ["tek", "deps"]
|
||||
[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"]
|
||||
|
||||
[dependencies]
|
||||
tengri = { workspace = true }
|
||||
tengri_proc = { workspace = true }
|
||||
|
||||
atomic_float = { workspace = true }
|
||||
backtrace = { workspace = true }
|
||||
clap = { workspace = true, optional = true }
|
||||
|
|
@ -27,6 +24,7 @@ midly = { workspace = true }
|
|||
palette = { workspace = true }
|
||||
rand = { workspace = true }
|
||||
symphonia = { workspace = true, optional = true }
|
||||
tengri = { workspace = true }
|
||||
toml = { workspace = true }
|
||||
uuid = { 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()));
|
||||
//take!(ClipCommand |state: Arrangement, iter|state.selected_clip().as_ref()
|
||||
//.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_out (&self) -> Option<MidiOutput> { todo!() }
|
||||
fn selected_device (&self) -> Option<Device> { todo!() }
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
use crate::*;
|
||||
|
||||
#[tengri_proc::expose]
|
||||
impl MidiClip {
|
||||
fn _todo_opt_bool_stub_ (&self) -> Option<bool> { todo!() }
|
||||
fn _todo_bool_stub_ (&self) -> bool { todo!() }
|
||||
|
|
|
|||
|
|
@ -56,7 +56,6 @@ pub trait AddScene: HasScenes + HasTracks {
|
|||
}
|
||||
}
|
||||
|
||||
#[tengri_proc::expose]
|
||||
impl Scene {
|
||||
fn _todo_opt_bool_stub_ (&self) -> Option<bool> { todo!() }
|
||||
fn _todo_usize_stub_ (&self) -> usize { todo!() }
|
||||
|
|
|
|||
|
|
@ -121,7 +121,6 @@ impl<T: MaybeHas<Track>> HasTrack for T {
|
|||
}
|
||||
}
|
||||
|
||||
#[tengri_proc::expose]
|
||||
impl Track {
|
||||
fn _todo_opt_bool_stub_ (&self) -> Option<bool> { 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> + '_ {
|
||||
Bsp::s(self.view_inputs_header(), Thunk::new(|to: &mut TuiOut|{
|
||||
for port in self.midi_ins().iter() {
|
||||
to.place(&self.view_inputs_row(port))
|
||||
for (index, port) in self.midi_ins().iter().enumerate() {
|
||||
to.place(&Push::x(index as u16 * 10, self.view_inputs_row(port)))
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
|
@ -25,12 +25,12 @@ impl Arrangement {
|
|||
Fixed::x(20, Align::w(
|
||||
button_3("i", "nput ", format!("{}", self.midi_ins.len()), 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() {
|
||||
to.place(&Tui::bg(track.color.dark.rgb, Align::w(Fixed::x(track.width as u16, row!(
|
||||
Thunk::new(|to: &mut TuiOut|for (_index, track, x1, _x2) in self.tracks_with_sizes() {
|
||||
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.recording, Tui::fg(Red, "rec "), "rec "),
|
||||
Either::new(track.sequencer.overdub, Tui::fg(Yellow, "dub "), "dub "),
|
||||
)))))
|
||||
))))))
|
||||
}))))
|
||||
}
|
||||
|
||||
|
|
@ -180,8 +180,8 @@ pub trait TracksView:
|
|||
button_2("S", "+", false));
|
||||
view_track_row_section(theme, button, button_2, Tui::bg(theme.darker.rgb,
|
||||
Fixed::y(2, Thunk::new(|to: &mut TuiOut|{
|
||||
for (index, track, _x1, _x2) in self.tracks_with_sizes() {
|
||||
to.place(&Fixed::x(self.track_width(index, track),
|
||||
for (index, track, x1, _x2) in self.tracks_with_sizes() {
|
||||
to.place(&Push::x(x1 as u16, Fixed::x(self.track_width(index, track),
|
||||
Tui::bg(if selected.track() == Some(index) {
|
||||
track.color.light.rgb
|
||||
} else {
|
||||
|
|
@ -189,7 +189,7 @@ pub trait TracksView:
|
|||
}, Bsp::s(Fill::x(Align::nw(Bsp::e(
|
||||
format!("·t{index:02} "),
|
||||
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> {
|
||||
view_track_row_section(theme,
|
||||
|
|
@ -288,7 +288,7 @@ pub trait ScenesView:
|
|||
};
|
||||
let a = Fill::x(Align::w(Bsp::e(format!("·s{index:02} "),
|
||||
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(
|
||||
self.editor().as_ref().map(|e|e.clip_status()),
|
||||
self.editor().as_ref().map(|e|e.edit_status())))));
|
||||
|
|
@ -363,7 +363,7 @@ pub trait ClipsView:
|
|||
Tui::fg_bg(outline, bg, Fill::xy("")),
|
||||
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.is_editing(), self.editor())))))));
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
use crate::*;
|
||||
|
||||
#[tengri_proc::expose]
|
||||
impl Browse {
|
||||
fn _todo_stub_path_buf (&self) -> PathBuf {
|
||||
todo!()
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
use crate::*;
|
||||
|
||||
#[tengri_proc::expose]
|
||||
impl Clock {
|
||||
fn _todo_provide_u32 (&self) -> u32 {
|
||||
todo!()
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ def_command!(MidiEditCommand: |editor: MidiEditor| {
|
|||
// 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>>> {
|
||||
todo!()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
use crate::*;
|
||||
|
||||
#[tengri_proc::expose]
|
||||
impl Pool {
|
||||
fn _todo_usize_ (&self) -> usize { 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")))))
|
||||
});
|
||||
|
||||
#[tengri_proc::expose]
|
||||
impl Sampler {
|
||||
fn sample_selected (&self) -> usize {
|
||||
(self.get_note_pos() as u8).into()
|
||||
|
|
|
|||
|
|
@ -3,10 +3,10 @@ use crate::*;
|
|||
impl Sampler {
|
||||
|
||||
pub fn view_grid (&self) -> impl Draw<TuiOut> + Layout<TuiOut> + use<'_> {
|
||||
let cells_x = 8u16;
|
||||
let cells_y = 8u16;
|
||||
let cell_width = 10u16;
|
||||
let cell_height = 2u16;
|
||||
//let cells_x = 8u16;
|
||||
//let cells_y = 8u16;
|
||||
//let cell_width = 10u16;
|
||||
//let cell_height = 2u16;
|
||||
//let width = cells_x * cell_width;
|
||||
//let height = cells_y * cell_height;
|
||||
//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<'_> {
|
||||
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 theme = sample.color;
|
||||
to.place(&row!(
|
||||
|
|
|
|||
40
tek/tek.edn
40
tek/tek.edn
|
|
@ -34,7 +34,10 @@
|
|||
(@enter confirm))
|
||||
(view :menu (bg (g 0) (bsp/s
|
||||
: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
|
||||
(fill/x (align/w (text L-AUDIO-OUT)))
|
||||
(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)))
|
||||
(bsp/a (text MIDI-IN) (fill/x (align/e (text AUDIO-IN-R))))))))
|
||||
(view :browse (bsp/s
|
||||
(padding/xy 3 1 :browse-title)
|
||||
(padding 3 1 :browse-title)
|
||||
(enclose (fg (g 96)) browser)))
|
||||
(keys :help
|
||||
(@f1 dialog :help))
|
||||
|
|
@ -84,86 +87,113 @@
|
|||
(mode :length (keys :rename)) (mode :clip (keys :clip)) (mode :track (keys :track))
|
||||
(mode :scene (keys :scene)) (mode :mix (keys :mix))
|
||||
(keys :clock :arranger :global) :arranger)
|
||||
|
||||
(view :arranger (bsp/n
|
||||
:status
|
||||
(bsp/w :meters/output (bsp/e :meters/input :arrangement))))
|
||||
|
||||
(view :arrangement (bsp/n
|
||||
:tracks/inputs
|
||||
(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)
|
||||
(@tab project/edit) (@enter project/edit)
|
||||
(@shift/I project/input/add) (@shift/O project/output/add)
|
||||
(@shift/S project/scene/add) (@shift/T project/track/add)
|
||||
(@shift/D dialog/show :dialog/device))
|
||||
|
||||
(keys :tracks
|
||||
(@t select :select/track)
|
||||
(@left select :select/track/dec)
|
||||
(@right select :select/track/inc))
|
||||
|
||||
(keys :scenes
|
||||
(@s select :select/scene)
|
||||
(@up select :select/scene/dec)
|
||||
(@down select :select/scene/inc))
|
||||
|
||||
(keys :track (see :color :launch :axis/z :axis/z2 :delete)
|
||||
(@r toggle :rec)
|
||||
(@m toggle :mon)
|
||||
(@p toggle :play)
|
||||
(@P toggle :solo))
|
||||
(keys :scene (see :color :launch :axis/z :axis/z2 :delete))
|
||||
|
||||
(keys :clip (see :color :launch :axis/z :axis/z2 :delete)
|
||||
(@l toggle :loop))
|
||||
|
||||
(mode :groovebox (name Groovebox) (info A sequencer with built-in sampler.)
|
||||
(mode browse (keys :browse))
|
||||
(mode rename (keys :pool-rename))
|
||||
(mode length (keys :pool-length))
|
||||
(keys :clock :editor :sampler :global) (view :groovebox))
|
||||
|
||||
(view :groovebox (bsp/w
|
||||
:meters/output
|
||||
(bsp/e :meters/input (bsp/w :groove/meta :groove/editor))))
|
||||
|
||||
(view :groove/meta (fill/y (align/n (stack/s
|
||||
:midi-ins/status :midi-outs/status :audio-ins/status :audio-outs/status :pool))))
|
||||
|
||||
(view :groove/editor (bsp/n
|
||||
:groove/sample
|
||||
:groove/sequence))
|
||||
|
||||
(view :groove/sample (fixed/y :h-sample-detail (bsp/e
|
||||
(fill/y (fixed/x 20 (align/nw :sample-status)))
|
||||
:sample-viewer)))
|
||||
|
||||
(view :groove/sequence (bsp/e
|
||||
(fill/y (align/n (bsp/s :status/v :editor-status)))
|
||||
(bsp/e :samples/keys :editor)))
|
||||
|
||||
(mode :sampler (name Sampler) (info A sampling soundboard.)
|
||||
(keys :sampler :global) (view :sampler))
|
||||
|
||||
(view :sampler (bsp/s
|
||||
(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/record
|
||||
(@r sampler/record/toggle :sample/selected) (@shift/R sampler/record/back))
|
||||
|
||||
(keys :sampler/play
|
||||
(@p sampler/play/sample :sample/selected) (@P sampler/stop/sample :sample/selected))
|
||||
|
||||
(keys :sampler/import-export
|
||||
(@shift/f6 dialog :dialog/export/sample) (@shift/f9 dialog :dialog/import/sample))
|
||||
|
||||
(keys :sampler/directions
|
||||
(@up sampler/select :sample/above)
|
||||
(@down sampler/select :sample/below)
|
||||
(@left sampler/select :sample/to/left)
|
||||
(@right sampler/select :sample/to/right))
|
||||
|
||||
(mode :sequencer (name Sequencer) (info A MIDI sequencer.)
|
||||
(mode browse (keys :browse)) (mode rename (keys :pool/rename)) (mode length (keys :pool/length))
|
||||
(keys :editor :clock :global) (view :sequencer))
|
||||
|
||||
(view :sequencer (bsp/s
|
||||
(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/view (see :axis/x :axis/x2 :axis/z :axis/z2)
|
||||
(@z toggle :lock))
|
||||
|
||||
(keys :editor/note (see :axis/i :axis/i2 :axis/y :page)
|
||||
(@a editor/append :true)
|
||||
(@enter editor/append :false)
|
||||
(@del editor/delete/note)
|
||||
(@shift/del editor/delete/note))
|
||||
|
||||
(keys :pool (see :axis-y :axis-w :axis/z2 :color :delete)
|
||||
(@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))
|
||||
|
||||
(keys :sequencer (see :color :launch)
|
||||
(@shift/I input/add) (@shift/O output/add))
|
||||
|
|
|
|||
767
tek/tek.rs
767
tek/tek.rs
|
|
@ -13,15 +13,11 @@
|
|||
clippy::unit_arg
|
||||
)]
|
||||
|
||||
mod deps; pub use self::deps::*;
|
||||
|
||||
mod deps; pub use self::deps::*;
|
||||
mod config; pub use self::config::*;
|
||||
mod device; pub use self::device::*;
|
||||
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;
|
||||
|
||||
/// Total state
|
||||
|
|
@ -76,21 +72,19 @@ audio!(
|
|||
|self, event|{
|
||||
use JackEvent::*;
|
||||
match event {
|
||||
SampleRate(sr) => {
|
||||
self.clock().timebase.sr.set(sr as f64);
|
||||
},
|
||||
PortRegistration(id, true) => {
|
||||
SampleRate(sr) => { self.clock().timebase.sr.set(sr as f64); },
|
||||
PortRegistration(_id, true) => {
|
||||
//let port = self.jack().port_by_id(id);
|
||||
//println!("\rport add: {id} {port:?}");
|
||||
//println!("\rport add: {id}");
|
||||
},
|
||||
PortRegistration(id, false) => {
|
||||
PortRegistration(_id, false) => {
|
||||
/*println!("\rport del: {id}")*/
|
||||
},
|
||||
PortsConnected(a, b, true) => { /*println!("\rport conn: {a} {b}")*/ },
|
||||
PortsConnected(a, b, false) => { /*println!("\rport disc: {a} {b}")*/ },
|
||||
ClientRegistration(id, true) => {},
|
||||
ClientRegistration(id, false) => {},
|
||||
PortsConnected(_a, _b, true) => { /*println!("\rport conn: {a} {b}")*/ },
|
||||
PortsConnected(_a, _b, false) => { /*println!("\rport disc: {a} {b}")*/ },
|
||||
ClientRegistration(_id, true) => {},
|
||||
ClientRegistration(_id, false) => {},
|
||||
ThreadInit => {},
|
||||
XRun => {},
|
||||
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> {
|
||||
literal = |dsl|Ok(dsl.src()?.map(|x|x.into()));
|
||||
});
|
||||
|
||||
dsl_ns!(App: ItemTheme {});
|
||||
|
||||
// Provide boolean values.
|
||||
dsl_ns!(App: bool {
|
||||
// TODO literal = ...
|
||||
word = |app| {
|
||||
":mode/editor" => app.project.editor.is_some(),
|
||||
":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 {
|
||||
word = |app| {
|
||||
":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!(Pool: |self: App|self.pool);
|
||||
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) };
|
||||
{ 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 {
|
||||
fn w_side (&self) -> u16 { 20 }
|
||||
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 w_side (&self) -> u16 { 20 }
|
||||
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) }
|
||||
}
|
||||
|
||||
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 {
|
||||
|
||||
|
|
@ -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 {
|
||||
//|to|to.place(&Fill::xy("·"))
|
||||
//}
|
||||
|
|
|
|||
230
tek/tek_bind.rs
230
tek/tek_bind.rs
|
|
@ -1,231 +1 @@
|
|||
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::*;
|
||||
|
||||
#[cfg(test)] #[test] fn test_model () -> Usually<()> {
|
||||
#[cfg(test)] #[test] fn test_app () -> Usually<()> {
|
||||
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_focus()?;
|
||||
let scene = app.scene_del(0);
|
||||
let _ = app.update_clock();
|
||||
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.pulses();
|
||||
let _ = scene.is_playing(&[]);
|
||||
let _ = app.view_transport();
|
||||
let _ = app.view_status();
|
||||
let _ = app.update_clock();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
@ -27,8 +25,8 @@ use crate::*;
|
|||
let _ = button_2("", "", false);
|
||||
let _ = button_3("", "", "", true);
|
||||
let _ = button_3("", "", "", false);
|
||||
let _ = heading("", "", 0, "", true);
|
||||
let _ = heading("", "", 0, "", false);
|
||||
//let _ = heading("", "", 0, "", true);
|
||||
//let _ = heading("", "", 0, "", false);
|
||||
let _ = wrap(Reset, Reset, "");
|
||||
}
|
||||
|
||||
|
|
@ -56,29 +54,29 @@ use crate::*;
|
|||
|
||||
#[cfg(test)] #[test] fn test_view_iter () {
|
||||
let mut app = App::default();
|
||||
app.editor = Some(Default::default());
|
||||
let _: Vec<_> = app.inputs_with_sizes().collect();
|
||||
let _: Vec<_> = app.outputs_with_sizes().collect();
|
||||
let _: Vec<_> = app.tracks_with_sizes().collect();
|
||||
let _: Vec<_> = app.scenes_with_sizes(true, 10, 10).collect();
|
||||
app.project.editor = Some(Default::default());
|
||||
//let _: Vec<_> = app.project.inputs_with_sizes().collect();
|
||||
//let _: Vec<_> = app.project.outputs_with_sizes().collect();
|
||||
let _: Vec<_> = app.project.tracks_with_sizes().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_track_colors(true, 10, 10).collect();
|
||||
}
|
||||
|
||||
#[cfg(test)] #[test] fn test_view_sizes () {
|
||||
let app = App::default();
|
||||
let _ = app.w();
|
||||
let _ = app.w_sidebar();
|
||||
let _ = app.w_tracks_area();
|
||||
let _ = app.h();
|
||||
let _ = app.h_tracks_area();
|
||||
let _ = app.h_inputs();
|
||||
let _ = app.h_outputs();
|
||||
let _ = app.h_scenes();
|
||||
let _ = app.project.w();
|
||||
//let _ = app.project.w_sidebar();
|
||||
//let _ = app.project.w_tracks_area();
|
||||
let _ = app.project.h();
|
||||
//let _ = app.project.h_tracks_area();
|
||||
//let _ = app.project.h_inputs();
|
||||
//let _ = app.project.h_outputs();
|
||||
let _ = app.project.h_scenes();
|
||||
}
|
||||
|
||||
#[cfg(test)] #[test] fn test_midi_edit () {
|
||||
let editor = MidiEditor::default();
|
||||
let _editor = MidiEditor::default();
|
||||
let mut editor = MidiEditor {
|
||||
mode: PianoHorizontal::new(Some(&Arc::new(RwLock::new(MidiClip::stop_all())))),
|
||||
size: Default::default(),
|
||||
|
|
@ -89,12 +87,13 @@ use crate::*;
|
|||
let _ = editor.clip_status();
|
||||
let _ = editor.edit_status();
|
||||
struct TestEditorHost(Option<MidiEditor>);
|
||||
has_editor!(|self: TestEditorHost|{
|
||||
editor = self.0;
|
||||
editor_w = 0;
|
||||
editor_h = 0;
|
||||
is_editing = false;
|
||||
});
|
||||
has!(Option<MidiEditor>: |self: TestEditorHost|self.0);
|
||||
//has_editor!(|self: TestEditorHost|{
|
||||
//editor = self.0;
|
||||
//editor_w = 0;
|
||||
//editor_h = 0;
|
||||
//is_editing = false;
|
||||
//});
|
||||
let mut host = TestEditorHost(Some(editor));
|
||||
let _ = host.editor();
|
||||
let _ = host.editor_mut();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue