mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 19:56:42 +01:00
wip: big flat
This commit is contained in:
parent
8cbe621b07
commit
4a3de618d0
20 changed files with 305 additions and 336 deletions
|
|
@ -22,7 +22,7 @@ pub struct ArrangerTui {
|
||||||
pub size: Measure<Tui>,
|
pub size: Measure<Tui>,
|
||||||
pub note_buf: Vec<u8>,
|
pub note_buf: Vec<u8>,
|
||||||
pub midi_buf: Vec<Vec<Vec<u8>>>,
|
pub midi_buf: Vec<Vec<Vec<u8>>>,
|
||||||
pub editor: MidiEditorModel,
|
pub editor: MidiEditor,
|
||||||
pub perf: PerfModel,
|
pub perf: PerfModel,
|
||||||
}
|
}
|
||||||
impl ArrangerTui {
|
impl ArrangerTui {
|
||||||
|
|
@ -99,27 +99,29 @@ impl ArrangerTui {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
render!(<Tui>|self: ArrangerTui|{
|
impl Render<Tui> for ArrangerTui {
|
||||||
let play = PlayPause(self.clock.is_rolling());
|
fn content (&self) -> Option<impl Render<Tui>> {
|
||||||
let transport = TransportView::new(self, Some(ItemPalette::from(TuiTheme::g(96))), true);
|
let play = PlayPause(self.clock.is_rolling());
|
||||||
let with_transport = |x|col!([row!(![&play, &transport]), &x]);
|
let transport = TransportView::new(self, Some(ItemPalette::from(TuiTheme::g(96))), true);
|
||||||
let pool_size = if self.phrases.visible { self.splits[1] } else { 0 };
|
let with_transport = |x|col!([row!(![&play, &transport]), &x]);
|
||||||
let with_pool = |x|Split::w(false, pool_size, PoolView(&self.phrases), x);
|
let pool_size = if self.phrases.visible { self.splits[1] } else { 0 };
|
||||||
let status = ArrangerStatus::from(self);
|
let with_pool = |x|Split::w(false, pool_size, PoolView(&self.phrases), x);
|
||||||
let with_editbar = |x|Split::n(false, 1, MidiEditStatus(&self.editor), x);
|
let status = ArrangerStatus::from(self);
|
||||||
let with_status = |x|Split::n(false, 2, status, x);
|
let with_editbar = |x|Split::n(false, 1, MidiEditStatus(&self.editor), x);
|
||||||
let with_size = |x|lay!([&self.size, x]);
|
let with_status = |x|Split::n(false, 2, status, x);
|
||||||
let arranger = ||lay!(|add|{
|
let with_size = |x|lay!([&self.size, x]);
|
||||||
let color = self.color;
|
let arranger = ||lay!(|add|{
|
||||||
add(&Fill::wh(Tui::bg(color.darkest.rgb, ())))?;
|
let color = self.color;
|
||||||
add(&Fill::wh(Outer(Style::default().fg(color.dark.rgb).bg(color.darkest.rgb))))?;
|
add(&Fill::wh(Tui::bg(color.darkest.rgb, ())))?;
|
||||||
add(&Self::render_mode(self))
|
add(&Fill::wh(Outer(Style::default().fg(color.dark.rgb).bg(color.darkest.rgb))))?;
|
||||||
});
|
add(&Self::render_mode(self))
|
||||||
with_size(with_status(with_editbar(with_pool(with_transport(col!([
|
});
|
||||||
Fill::w(Fixed::h(20, arranger())),
|
Some(with_size(with_status(with_editbar(with_pool(with_transport(col!([
|
||||||
Fill::wh(&self.editor),
|
Fill::w(Fixed::h(20, arranger())),
|
||||||
]))))))
|
Fill::wh(&self.editor),
|
||||||
});
|
])))))))
|
||||||
|
}
|
||||||
|
}
|
||||||
audio!(|self: ArrangerTui, client, scope|{
|
audio!(|self: ArrangerTui, client, scope|{
|
||||||
// Start profiling cycle
|
// Start profiling cycle
|
||||||
let t0 = self.perf.get_t0();
|
let t0 = self.perf.get_t0();
|
||||||
|
|
|
||||||
69
src/core.rs
69
src/core.rs
|
|
@ -1,69 +0,0 @@
|
||||||
pub(crate) use std::error::Error;
|
|
||||||
|
|
||||||
pub(crate) mod color;
|
|
||||||
pub(crate) use color::*;
|
|
||||||
pub use color::*;
|
|
||||||
|
|
||||||
pub(crate) mod command; pub(crate) use command::*;
|
|
||||||
pub(crate) mod engine; pub(crate) use engine::*;
|
|
||||||
pub(crate) mod focus; pub(crate) use focus::*;
|
|
||||||
pub(crate) mod input; pub(crate) use input::*;
|
|
||||||
pub(crate) mod output; pub(crate) use output::*;
|
|
||||||
|
|
||||||
pub(crate) use std::sync::atomic::{Ordering, AtomicBool, AtomicUsize};
|
|
||||||
pub(crate) use Ordering::Relaxed;
|
|
||||||
|
|
||||||
pub use self::{
|
|
||||||
engine::Engine,
|
|
||||||
input::Handle,
|
|
||||||
output::Render
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Standard result type.
|
|
||||||
pub type Usually<T> = Result<T, Box<dyn Error>>;
|
|
||||||
|
|
||||||
/// Standard optional result type.
|
|
||||||
pub type Perhaps<T> = Result<Option<T>, Box<dyn Error>>;
|
|
||||||
|
|
||||||
/// Define test modules.
|
|
||||||
#[macro_export] macro_rules! testmod {
|
|
||||||
($($name:ident)*) => { $(#[cfg(test)] mod $name;)* };
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Prototypal case of implementor macro.
|
|
||||||
/// Saves 4loc per data pats.
|
|
||||||
#[macro_export] macro_rules! from {
|
|
||||||
($(<$($lt:lifetime),+>)?|$state:ident:$Source:ty|$Target:ty=$cb:expr) => {
|
|
||||||
impl $(<$($lt),+>)? From<$Source> for $Target {
|
|
||||||
fn from ($state:$Source) -> Self { $cb }
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait Gettable<T> {
|
|
||||||
/// Returns current value
|
|
||||||
fn get (&self) -> T;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait Mutable<T>: Gettable<T> {
|
|
||||||
/// Sets new value, returns old
|
|
||||||
fn set (&mut self, value: T) -> T;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait InteriorMutable<T>: Gettable<T> {
|
|
||||||
/// Sets new value, returns old
|
|
||||||
fn set (&self, value: T) -> T;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Gettable<bool> for AtomicBool {
|
|
||||||
fn get (&self) -> bool { self.load(Relaxed) }
|
|
||||||
}
|
|
||||||
impl InteriorMutable<bool> for AtomicBool {
|
|
||||||
fn set (&self, value: bool) -> bool { self.swap(value, Relaxed) }
|
|
||||||
}
|
|
||||||
impl Gettable<usize> for AtomicUsize {
|
|
||||||
fn get (&self) -> usize { self.load(Relaxed) }
|
|
||||||
}
|
|
||||||
impl InteriorMutable<usize> for AtomicUsize {
|
|
||||||
fn set (&self, value: usize) -> usize { self.swap(value, Relaxed) }
|
|
||||||
}
|
|
||||||
|
|
@ -1,49 +0,0 @@
|
||||||
#[cfg(test)] mod test_focus {
|
|
||||||
use super::focus::*;
|
|
||||||
#[test] fn test_focus () {
|
|
||||||
|
|
||||||
struct FocusTest {
|
|
||||||
focused: char,
|
|
||||||
cursor: (usize, usize)
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HasFocus for FocusTest {
|
|
||||||
type Item = char;
|
|
||||||
fn focused (&self) -> Self::Item {
|
|
||||||
self.focused
|
|
||||||
}
|
|
||||||
fn set_focused (&mut self, to: Self::Item) {
|
|
||||||
self.focused = to
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FocusGrid for FocusTest {
|
|
||||||
fn focus_cursor (&self) -> (usize, usize) {
|
|
||||||
self.cursor
|
|
||||||
}
|
|
||||||
fn focus_cursor_mut (&mut self) -> &mut (usize, usize) {
|
|
||||||
&mut self.cursor
|
|
||||||
}
|
|
||||||
fn focus_layout (&self) -> &[&[Self::Item]] {
|
|
||||||
&[
|
|
||||||
&['a', 'a', 'a', 'b', 'b', 'd'],
|
|
||||||
&['a', 'a', 'a', 'b', 'b', 'd'],
|
|
||||||
&['a', 'a', 'a', 'c', 'c', 'd'],
|
|
||||||
&['a', 'a', 'a', 'c', 'c', 'd'],
|
|
||||||
&['e', 'e', 'e', 'e', 'e', 'e'],
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut tester = FocusTest { focused: 'a', cursor: (0, 0) };
|
|
||||||
|
|
||||||
tester.focus_right();
|
|
||||||
assert_eq!(tester.cursor.0, 3);
|
|
||||||
assert_eq!(tester.focused, 'b');
|
|
||||||
|
|
||||||
tester.focus_down();
|
|
||||||
assert_eq!(tester.cursor.1, 2);
|
|
||||||
assert_eq!(tester.focused, 'c');
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
34
src/event.rs
Normal file
34
src/event.rs
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
pub struct EventMap<'a, const N: usize, E, T, U>(
|
||||||
|
pub [(E, &'a dyn Fn(T) -> U); N],
|
||||||
|
pub Option<&'a dyn Fn(T) -> U>,
|
||||||
|
);
|
||||||
|
|
||||||
|
impl<'a, const N: usize, E: PartialEq, T, U> EventMap<'a, N, E, T, U> {
|
||||||
|
pub fn handle (&self, context: T, event: &E) -> Option<U> {
|
||||||
|
for (binding, handler) in self.0.iter() {
|
||||||
|
if event == binding {
|
||||||
|
return Some(handler(context))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export] macro_rules! event_map {
|
||||||
|
($events:expr) => {
|
||||||
|
EventMap($events, None)
|
||||||
|
};
|
||||||
|
($events:expr, $default: expr) => {
|
||||||
|
EventMap($events, $default)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export] macro_rules! event_map_input_to_command {
|
||||||
|
($Engine:ty: $Model:ty: $Command:ty: $EventMap:expr) => {
|
||||||
|
input_to_command!($Command: <$Engine>|state: $Model, input|{
|
||||||
|
event_map!($EventMap).handle(state, input.event())?
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,7 +2,7 @@ use crate::*;
|
||||||
use super::*;
|
use super::*;
|
||||||
use KeyCode::{Char, Delete, Tab, Up, Down, Left, Right};
|
use KeyCode::{Char, Delete, Tab, Up, Down, Left, Right};
|
||||||
use ClockCommand::{Play, Pause};
|
use ClockCommand::{Play, Pause};
|
||||||
use GrooveboxCommand::*;
|
use GrooveboxCommand as Cmd;
|
||||||
use PhraseCommand::*;
|
use PhraseCommand::*;
|
||||||
use PhrasePoolCommand::*;
|
use PhrasePoolCommand::*;
|
||||||
|
|
||||||
|
|
@ -11,7 +11,7 @@ pub struct Groovebox {
|
||||||
|
|
||||||
pub player: MidiPlayer,
|
pub player: MidiPlayer,
|
||||||
pub pool: PoolModel,
|
pub pool: PoolModel,
|
||||||
pub editor: MidiEditorModel,
|
pub editor: MidiEditor,
|
||||||
pub sampler: Sampler,
|
pub sampler: Sampler,
|
||||||
|
|
||||||
pub size: Measure<Tui>,
|
pub size: Measure<Tui>,
|
||||||
|
|
@ -49,7 +49,7 @@ impl Groovebox {
|
||||||
)));
|
)));
|
||||||
player.play_phrase = Some((Moment::zero(&player.clock.timebase), Some(phrase.clone())));
|
player.play_phrase = Some((Moment::zero(&player.clock.timebase), Some(phrase.clone())));
|
||||||
let pool = crate::pool::PoolModel::from(&phrase);
|
let pool = crate::pool::PoolModel::from(&phrase);
|
||||||
let editor = crate::midi::MidiEditorModel::from(&phrase);
|
let editor = crate::midi::MidiEditor::from(&phrase);
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
_jack: jack.clone(),
|
_jack: jack.clone(),
|
||||||
player,
|
player,
|
||||||
|
|
@ -142,25 +142,29 @@ render!(<Tui>|self:Groovebox|{
|
||||||
});
|
});
|
||||||
|
|
||||||
struct EditStatus<'a, T: Render<Tui>>(&'a Sampler, &'a MidiEditor, usize, T);
|
struct EditStatus<'a, T: Render<Tui>>(&'a Sampler, &'a MidiEditor, usize, T);
|
||||||
render!(<Tui>|self: EditStatus<'a, T: Render<Tui>>|Split::n(false, 9, col!(![
|
impl<'a, T: Render<Tui>> Render<Tui> for EditStatus<'a, T> {
|
||||||
row!(|add|{
|
fn content (&self) -> impl Render<Tui> {
|
||||||
if let Some(sample) = &self.0.mapped[self.2] {
|
Split::n(false, 9, col!([
|
||||||
add(&format!("Sample {}", sample.read().unwrap().end))?;
|
row!(|add|{
|
||||||
}
|
if let Some(sample) = &self.0.mapped[self.2] {
|
||||||
add(&MidiEditStatus(&self.1))?;
|
add(&format!("Sample {}", sample.read().unwrap().end))?;
|
||||||
Ok(())
|
}
|
||||||
}),
|
add(&MidiEditStatus(&self.1))?;
|
||||||
lay!([
|
Ok(())
|
||||||
Outer(Style::default().fg(TuiTheme::g(128))),
|
}),
|
||||||
Fill::w(Fixed::h(8, if let Some((_, sample)) = &self.0.recording {
|
lay!([
|
||||||
SampleViewer(Some(sample.clone()))
|
Outer(Style::default().fg(TuiTheme::g(128))),
|
||||||
} else if let Some(sample) = &self.0.mapped[self.2] {
|
Fill::w(Fixed::h(8, if let Some((_, sample)) = &self.0.recording {
|
||||||
SampleViewer(Some(sample.clone()))
|
SampleViewer(Some(sample.clone()))
|
||||||
} else {
|
} else if let Some(sample) = &self.0.mapped[self.2] {
|
||||||
SampleViewer(None)
|
SampleViewer(Some(sample.clone()))
|
||||||
})),
|
} else {
|
||||||
]),
|
SampleViewer(None)
|
||||||
]), self.3));
|
})),
|
||||||
|
]),
|
||||||
|
]), &self.3)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct GrooveboxSamples<'a>(&'a Groovebox);
|
struct GrooveboxSamples<'a>(&'a Groovebox);
|
||||||
render!(<Tui>|self: GrooveboxSamples<'a>|{
|
render!(<Tui>|self: GrooveboxSamples<'a>|{
|
||||||
|
|
@ -204,19 +208,19 @@ input_to_command!(GrooveboxCommand: <Tui>|state: Groovebox, input|match input.ev
|
||||||
},
|
},
|
||||||
|
|
||||||
// Transport: Play from start or rewind to start
|
// Transport: Play from start or rewind to start
|
||||||
key_pat!(Char(' ')) => Clock(
|
key_pat!(Char(' ')) => Cmd::Clock(
|
||||||
if state.clock().is_stopped() { Play(Some(0)) } else { Pause(Some(0)) }
|
if state.clock().is_stopped() { Play(Some(0)) } else { Pause(Some(0)) }
|
||||||
),
|
),
|
||||||
|
|
||||||
// Tab: Toggle visibility of phrase pool column
|
// Tab: Toggle visibility of phrase pool column
|
||||||
key_pat!(Tab) => Pool(PoolCommand::Show(!state.pool.visible)),
|
key_pat!(Tab) => Cmd::Pool(PoolCommand::Show(!state.pool.visible)),
|
||||||
|
|
||||||
// q: Enqueue currently edited phrase
|
// q: Enqueue currently edited phrase
|
||||||
key_pat!(Char('q')) => Enqueue(Some(state.pool.phrase().clone())),
|
key_pat!(Char('q')) => Cmd::Enqueue(Some(state.pool.phrase().clone())),
|
||||||
// 0: Enqueue phrase 0 (stop all)
|
// 0: Enqueue phrase 0 (stop all)
|
||||||
key_pat!(Char('0')) => Enqueue(Some(state.pool.phrases()[0].clone())),
|
key_pat!(Char('0')) => Cmd::Enqueue(Some(state.pool.phrases()[0].clone())),
|
||||||
|
|
||||||
key_pat!(Shift-Char('R')) => Sampler(if state.sampler.recording.is_some() {
|
key_pat!(Shift-Char('R')) => Cmd::Sampler(if state.sampler.recording.is_some() {
|
||||||
SamplerCommand::RecordFinish
|
SamplerCommand::RecordFinish
|
||||||
} else {
|
} else {
|
||||||
SamplerCommand::RecordBegin(u7::from(state.editor.note_point() as u8))
|
SamplerCommand::RecordBegin(u7::from(state.editor.note_point() as u8))
|
||||||
|
|
@ -226,7 +230,7 @@ input_to_command!(GrooveboxCommand: <Tui>|state: Groovebox, input|match input.ev
|
||||||
key_pat!(Char('e')) => if let Some((_, Some(playing))) = state.player.play_phrase() {
|
key_pat!(Char('e')) => if let Some((_, Some(playing))) = state.player.play_phrase() {
|
||||||
let editing = state.editor.phrase().as_ref().map(|p|p.read().unwrap().clone());
|
let editing = state.editor.phrase().as_ref().map(|p|p.read().unwrap().clone());
|
||||||
let selected = state.pool.phrase().clone();
|
let selected = state.pool.phrase().clone();
|
||||||
Editor(Show(Some(if Some(selected.read().unwrap().clone()) != editing {
|
Cmd::Editor(Show(Some(if Some(selected.read().unwrap().clone()) != editing {
|
||||||
selected
|
selected
|
||||||
} else {
|
} else {
|
||||||
playing.clone()
|
playing.clone()
|
||||||
|
|
@ -238,9 +242,9 @@ input_to_command!(GrooveboxCommand: <Tui>|state: Groovebox, input|match input.ev
|
||||||
// For the rest, use the default keybindings of the components.
|
// For the rest, use the default keybindings of the components.
|
||||||
// The ones defined above supersede them.
|
// The ones defined above supersede them.
|
||||||
_ => if let Some(command) = PhraseCommand::input_to_command(&state.editor, input) {
|
_ => if let Some(command) = PhraseCommand::input_to_command(&state.editor, input) {
|
||||||
Editor(command)
|
Cmd::Editor(command)
|
||||||
} else if let Some(command) = PoolCommand::input_to_command(&state.pool, input) {
|
} else if let Some(command) = PoolCommand::input_to_command(&state.pool, input) {
|
||||||
Pool(command)
|
Cmd::Pool(command)
|
||||||
} else {
|
} else {
|
||||||
return None
|
return None
|
||||||
}
|
}
|
||||||
|
|
@ -250,7 +254,7 @@ command!(|self: GrooveboxCommand, state: Groovebox|match self {
|
||||||
Self::Pool(cmd) => {
|
Self::Pool(cmd) => {
|
||||||
let mut default = |cmd: PoolCommand|cmd
|
let mut default = |cmd: PoolCommand|cmd
|
||||||
.execute(&mut state.pool)
|
.execute(&mut state.pool)
|
||||||
.map(|x|x.map(Pool));
|
.map(|x|x.map(Self::Pool));
|
||||||
match cmd {
|
match cmd {
|
||||||
// autoselect: automatically load selected phrase in editor
|
// autoselect: automatically load selected phrase in editor
|
||||||
PoolCommand::Select(_) => {
|
PoolCommand::Select(_) => {
|
||||||
|
|
@ -268,13 +272,13 @@ command!(|self: GrooveboxCommand, state: Groovebox|match self {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Self::Editor(cmd) => {
|
Self::Editor(cmd) => {
|
||||||
let default = ||cmd.execute(&mut state.editor).map(|x|x.map(Editor));
|
let default = ||cmd.execute(&mut state.editor).map(|x|x.map(Self::Editor));
|
||||||
match cmd {
|
match cmd {
|
||||||
_ => default()?
|
_ => default()?
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Self::Clock(cmd) => cmd.execute(state)?.map(Clock),
|
Self::Clock(cmd) => cmd.execute(state)?.map(Self::Clock),
|
||||||
Self::Sampler(cmd) => cmd.execute(&mut state.sampler)?.map(Sampler),
|
Self::Sampler(cmd) => cmd.execute(&mut state.sampler)?.map(Self::Sampler),
|
||||||
Self::Enqueue(phrase) => {
|
Self::Enqueue(phrase) => {
|
||||||
state.player.enqueue_next(phrase.as_ref());
|
state.player.enqueue_next(phrase.as_ref());
|
||||||
None
|
None
|
||||||
|
|
|
||||||
155
src/lib.rs
155
src/lib.rs
|
|
@ -1,69 +1,50 @@
|
||||||
#![allow(unused)]
|
#![allow(unused)]
|
||||||
#![allow(clippy::unit_arg)]
|
#![allow(clippy::unit_arg)]
|
||||||
|
|
||||||
pub mod core; pub use self::core::*;
|
|
||||||
|
|
||||||
pub mod time; pub(crate) use self::time::*;
|
|
||||||
pub use self::time::HasClock;
|
|
||||||
|
|
||||||
pub mod space; pub(crate) use self::space::*;
|
|
||||||
pub use self::space::Measure;
|
|
||||||
|
|
||||||
pub mod tui; pub(crate) use self::tui::*;
|
|
||||||
pub use tui::*;
|
|
||||||
|
|
||||||
pub mod jack; pub(crate) use self::jack::*;
|
|
||||||
pub use self::jack::*;
|
|
||||||
|
|
||||||
pub mod midi; pub(crate) use self::midi::*;
|
|
||||||
|
|
||||||
pub mod meter; pub(crate) use self::meter::*;
|
|
||||||
|
|
||||||
pub mod piano_h; pub(crate) use self::piano_h::*;
|
|
||||||
|
|
||||||
pub mod transport; pub(crate) use self::transport::*;
|
|
||||||
pub use self::transport::TransportTui;
|
|
||||||
|
|
||||||
pub mod sequencer; pub(crate) use self::sequencer::*;
|
|
||||||
pub use self::sequencer::SequencerTui;
|
|
||||||
|
|
||||||
pub mod arranger; pub(crate) use self::arranger::*;
|
|
||||||
pub use self::arranger::ArrangerTui;
|
|
||||||
|
|
||||||
pub mod sampler; pub(crate) use self::sampler::*;
|
|
||||||
pub use self::sampler::{SamplerTui, Sampler, Sample, Voice};
|
|
||||||
|
|
||||||
pub mod mixer; pub(crate) use self::mixer::*;
|
|
||||||
pub use self::mixer::{Mixer, MixerTrack, MixerTrackDevice};
|
|
||||||
|
|
||||||
pub mod plugin; pub(crate) use self::plugin::*;
|
|
||||||
pub use self::plugin::*;
|
|
||||||
|
|
||||||
pub mod groovebox; pub(crate) use self::groovebox::*;
|
|
||||||
pub use self::groovebox::Groovebox;
|
|
||||||
|
|
||||||
pub mod pool; pub(crate) use self::pool::*;
|
|
||||||
pub use self::pool::PoolModel;
|
|
||||||
|
|
||||||
pub mod status; pub(crate) use self::status::*;
|
|
||||||
|
|
||||||
pub use ::better_panic; pub(crate) use better_panic::{Settings, Verbosity};
|
|
||||||
|
|
||||||
pub use ::atomic_float; pub(crate) use atomic_float::*;
|
|
||||||
|
|
||||||
pub(crate) use std::sync::{Arc, Mutex, RwLock};
|
|
||||||
pub(crate) use std::sync::atomic::Ordering;
|
|
||||||
pub(crate) use std::sync::atomic::{AtomicBool, AtomicUsize};
|
|
||||||
pub(crate) use std::collections::BTreeMap;
|
|
||||||
pub(crate) use std::marker::PhantomData;
|
|
||||||
pub(crate) use std::thread::{spawn, JoinHandle};
|
|
||||||
pub(crate) use std::path::PathBuf;
|
|
||||||
pub(crate) use std::ffi::OsString;
|
|
||||||
pub(crate) use std::time::Duration;
|
|
||||||
pub(crate) use std::io::{Stdout, stdout};
|
|
||||||
pub(crate) use std::ops::{Add, Sub, Mul, Div, Rem};
|
|
||||||
pub(crate) use std::cmp::{Ord, Eq, PartialEq};
|
pub(crate) use std::cmp::{Ord, Eq, PartialEq};
|
||||||
|
pub(crate) use std::collections::BTreeMap;
|
||||||
|
pub(crate) use std::error::Error;
|
||||||
|
pub(crate) use std::ffi::OsString;
|
||||||
pub(crate) use std::fmt::{Debug, Display, Formatter};
|
pub(crate) use std::fmt::{Debug, Display, Formatter};
|
||||||
|
pub(crate) use std::io::{Stdout, stdout};
|
||||||
|
pub(crate) use std::marker::PhantomData;
|
||||||
|
pub(crate) use std::ops::{Add, Sub, Mul, Div, Rem};
|
||||||
|
pub(crate) use std::path::PathBuf;
|
||||||
|
pub(crate) use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering::{self, *}};
|
||||||
|
pub(crate) use std::sync::{Arc, Mutex, RwLock};
|
||||||
|
pub(crate) use std::thread::{spawn, JoinHandle};
|
||||||
|
pub(crate) use std::time::Duration;
|
||||||
|
|
||||||
|
pub mod arranger; pub use self::arranger::*;
|
||||||
|
pub mod color; pub use self::color::*;
|
||||||
|
pub mod command; pub use self::command::*;
|
||||||
|
pub mod engine; pub use self::engine::*;
|
||||||
|
pub mod event; pub use self::event::*;
|
||||||
|
pub mod file; pub use self::file::*;
|
||||||
|
pub mod focus; pub use self::focus::*;
|
||||||
|
pub mod groovebox; pub use self::groovebox::*;
|
||||||
|
pub mod input; pub use self::input::*;
|
||||||
|
pub mod jack; pub use self::jack::*;
|
||||||
|
pub mod meter; pub use self::meter::*;
|
||||||
|
pub mod midi; pub use self::midi::*;
|
||||||
|
pub mod mixer; pub use self::mixer::*;
|
||||||
|
pub mod output; pub use self::output::*;
|
||||||
|
pub mod piano_h; pub use self::piano_h::*;
|
||||||
|
pub mod plugin; pub use self::plugin::*;
|
||||||
|
pub mod pool; pub use self::pool::*;
|
||||||
|
pub mod sampler; pub use self::sampler::*;
|
||||||
|
pub mod sequencer; pub use self::sequencer::*;
|
||||||
|
pub mod space; pub use self::space::*;
|
||||||
|
pub mod status; pub use self::status::*;
|
||||||
|
pub mod time; pub use self::time::*;
|
||||||
|
pub mod transport; pub use self::transport::*;
|
||||||
|
pub mod tui; pub use self::tui::*;
|
||||||
|
|
||||||
|
pub use ::better_panic;
|
||||||
|
pub(crate) use better_panic::{Settings, Verbosity};
|
||||||
|
|
||||||
|
pub use ::atomic_float;
|
||||||
|
pub(crate) use atomic_float::*;
|
||||||
|
|
||||||
pub use ::crossterm;
|
pub use ::crossterm;
|
||||||
pub(crate) use crossterm::{ExecutableCommand};
|
pub(crate) use crossterm::{ExecutableCommand};
|
||||||
|
|
@ -93,3 +74,55 @@ pub(crate) use ::palette::{
|
||||||
};
|
};
|
||||||
|
|
||||||
testmod! { test }
|
testmod! { test }
|
||||||
|
|
||||||
|
/// Standard result type.
|
||||||
|
pub type Usually<T> = Result<T, Box<dyn Error>>;
|
||||||
|
|
||||||
|
/// Standard optional result type.
|
||||||
|
pub type Perhaps<T> = Result<Option<T>, Box<dyn Error>>;
|
||||||
|
|
||||||
|
/// Define test modules.
|
||||||
|
#[macro_export] macro_rules! testmod {
|
||||||
|
($($name:ident)*) => { $(#[cfg(test)] mod $name;)* };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prototypal case of implementor macro.
|
||||||
|
/// Saves 4loc per data pats.
|
||||||
|
#[macro_export] macro_rules! from {
|
||||||
|
($(<$($lt:lifetime),+>)?|$state:ident:$Source:ty|$Target:ty=$cb:expr) => {
|
||||||
|
impl $(<$($lt),+>)? From<$Source> for $Target {
|
||||||
|
fn from ($state:$Source) -> Self { $cb }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Gettable<T> {
|
||||||
|
/// Returns current value
|
||||||
|
fn get (&self) -> T;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Mutable<T>: Gettable<T> {
|
||||||
|
/// Sets new value, returns old
|
||||||
|
fn set (&mut self, value: T) -> T;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait InteriorMutable<T>: Gettable<T> {
|
||||||
|
/// Sets new value, returns old
|
||||||
|
fn set (&self, value: T) -> T;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Gettable<bool> for AtomicBool {
|
||||||
|
fn get (&self) -> bool { self.load(Relaxed) }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InteriorMutable<bool> for AtomicBool {
|
||||||
|
fn set (&self, value: bool) -> bool { self.swap(value, Relaxed) }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Gettable<usize> for AtomicUsize {
|
||||||
|
fn get (&self) -> usize { self.load(Relaxed) }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InteriorMutable<usize> for AtomicUsize {
|
||||||
|
fn set (&self, value: usize) -> usize { self.swap(value, Relaxed) }
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,13 @@ use KeyCode::{Char, Up, Down, Left, Right, Enter};
|
||||||
use PhraseCommand::*;
|
use PhraseCommand::*;
|
||||||
|
|
||||||
pub trait HasEditor {
|
pub trait HasEditor {
|
||||||
fn editor (&self) -> &MidiEditorModel;
|
fn editor (&self) -> &MidiEditor;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[macro_export] macro_rules! has_editor {
|
#[macro_export] macro_rules! has_editor {
|
||||||
(|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => {
|
(|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => {
|
||||||
impl $(<$($L),*$($T $(: $U)?),*>)? HasEditor for $Struct $(<$($L),*$($T),*>)? {
|
impl $(<$($L),*$($T $(: $U)?),*>)? HasEditor for $Struct $(<$($L),*$($T),*>)? {
|
||||||
fn editor (&$self) -> &MidiEditorModel { &$cb }
|
fn editor (&$self) -> &MidiEditor { &$cb }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -29,8 +29,8 @@ pub enum PhraseCommand {
|
||||||
Show(Option<Arc<RwLock<MidiClip>>>),
|
Show(Option<Arc<RwLock<MidiClip>>>),
|
||||||
}
|
}
|
||||||
|
|
||||||
event_map_input_to_command!(Tui: MidiEditorModel: PhraseCommand: MidiEditorModel::KEYS);
|
event_map_input_to_command!(Tui: MidiEditor: PhraseCommand: MidiEditor::KEYS);
|
||||||
impl MidiEditorModel {
|
impl MidiEditor {
|
||||||
const KEYS: KeyMapping<31, Self> = [
|
const KEYS: KeyMapping<31, Self> = [
|
||||||
(kexp!(Ctrl-Alt-Up), &|s: &Self|SetNoteScroll(s.note_point() + 3)),
|
(kexp!(Ctrl-Alt-Up), &|s: &Self|SetNoteScroll(s.note_point() + 3)),
|
||||||
(kexp!(Ctrl-Alt-Down), &|s: &Self|SetNoteScroll(s.note_point().saturating_sub(3))),
|
(kexp!(Ctrl-Alt-Down), &|s: &Self|SetNoteScroll(s.note_point().saturating_sub(3))),
|
||||||
|
|
@ -71,8 +71,8 @@ impl MidiEditorModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Command<MidiEditorModel> for PhraseCommand {
|
impl Command<MidiEditor> for PhraseCommand {
|
||||||
fn execute (self, state: &mut MidiEditorModel) -> Perhaps<Self> {
|
fn execute (self, state: &mut MidiEditor) -> Perhaps<Self> {
|
||||||
use PhraseCommand::*;
|
use PhraseCommand::*;
|
||||||
match self {
|
match self {
|
||||||
Show(phrase) => { state.set_phrase(phrase.as_ref()); },
|
Show(phrase) => { state.set_phrase(phrase.as_ref()); },
|
||||||
|
|
@ -92,13 +92,13 @@ impl Command<MidiEditorModel> for PhraseCommand {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Contains state for viewing and editing a phrase
|
/// Contains state for viewing and editing a phrase
|
||||||
pub struct MidiEditorModel {
|
pub struct MidiEditor {
|
||||||
/// Renders the phrase
|
/// Renders the phrase
|
||||||
pub mode: Box<dyn PhraseViewMode>,
|
pub mode: Box<dyn PhraseViewMode>,
|
||||||
pub size: Measure<Tui>
|
pub size: Measure<Tui>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for MidiEditorModel {
|
impl Default for MidiEditor {
|
||||||
fn default () -> Self {
|
fn default () -> Self {
|
||||||
let mut mode = Box::new(PianoHorizontal::new(None));
|
let mut mode = Box::new(PianoHorizontal::new(None));
|
||||||
mode.redraw();
|
mode.redraw();
|
||||||
|
|
@ -106,13 +106,13 @@ impl Default for MidiEditorModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
has_size!(<Tui>|self:MidiEditorModel|&self.size);
|
has_size!(<Tui>|self: MidiEditor|&self.size);
|
||||||
render!(<Tui>|self: MidiEditorModel|{
|
render!(<Tui>|self: MidiEditor|{
|
||||||
self.autoscroll();
|
self.autoscroll();
|
||||||
self.autozoom();
|
self.autozoom();
|
||||||
&self.mode
|
&self.mode
|
||||||
});
|
});
|
||||||
//render!(<Tui>|self: MidiEditorModel|lay!(|add|{add(&self.size)?;add(self.mode)}));//bollocks
|
//render!(<Tui>|self: MidiEditor|lay!(|add|{add(&self.size)?;add(self.mode)}));//bollocks
|
||||||
|
|
||||||
pub trait PhraseViewMode: Render<Tui> + HasSize<Tui> + MidiRange + MidiPoint + Debug + Send + Sync {
|
pub trait PhraseViewMode: Render<Tui> + HasSize<Tui> + MidiRange + MidiPoint + Debug + Send + Sync {
|
||||||
fn buffer_size (&self, phrase: &MidiClip) -> (usize, usize);
|
fn buffer_size (&self, phrase: &MidiClip) -> (usize, usize);
|
||||||
|
|
@ -125,9 +125,9 @@ pub trait PhraseViewMode: Render<Tui> + HasSize<Tui> + MidiRange + MidiPoint + D
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MidiView<Tui> for MidiEditorModel {}
|
impl MidiView<Tui> for MidiEditor {}
|
||||||
|
|
||||||
impl TimeRange for MidiEditorModel {
|
impl TimeRange for MidiEditor {
|
||||||
fn time_len (&self) -> &AtomicUsize { self.mode.time_len() }
|
fn time_len (&self) -> &AtomicUsize { self.mode.time_len() }
|
||||||
fn time_zoom (&self) -> &AtomicUsize { self.mode.time_zoom() }
|
fn time_zoom (&self) -> &AtomicUsize { self.mode.time_zoom() }
|
||||||
fn time_lock (&self) -> &AtomicBool { self.mode.time_lock() }
|
fn time_lock (&self) -> &AtomicBool { self.mode.time_lock() }
|
||||||
|
|
@ -135,24 +135,24 @@ impl TimeRange for MidiEditorModel {
|
||||||
fn time_axis (&self) -> &AtomicUsize { self.mode.time_axis() }
|
fn time_axis (&self) -> &AtomicUsize { self.mode.time_axis() }
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NoteRange for MidiEditorModel {
|
impl NoteRange for MidiEditor {
|
||||||
fn note_lo (&self) -> &AtomicUsize { self.mode.note_lo() }
|
fn note_lo (&self) -> &AtomicUsize { self.mode.note_lo() }
|
||||||
fn note_axis (&self) -> &AtomicUsize { self.mode.note_axis() }
|
fn note_axis (&self) -> &AtomicUsize { self.mode.note_axis() }
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NotePoint for MidiEditorModel {
|
impl NotePoint for MidiEditor {
|
||||||
fn note_len (&self) -> usize { self.mode.note_len() }
|
fn note_len (&self) -> usize { self.mode.note_len() }
|
||||||
fn set_note_len (&self, x: usize) { self.mode.set_note_len(x) }
|
fn set_note_len (&self, x: usize) { self.mode.set_note_len(x) }
|
||||||
fn note_point (&self) -> usize { self.mode.note_point() }
|
fn note_point (&self) -> usize { self.mode.note_point() }
|
||||||
fn set_note_point (&self, x: usize) { self.mode.set_note_point(x) }
|
fn set_note_point (&self, x: usize) { self.mode.set_note_point(x) }
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TimePoint for MidiEditorModel {
|
impl TimePoint for MidiEditor {
|
||||||
fn time_point (&self) -> usize { self.mode.time_point() }
|
fn time_point (&self) -> usize { self.mode.time_point() }
|
||||||
fn set_time_point (&self, x: usize) { self.mode.set_time_point(x) }
|
fn set_time_point (&self, x: usize) { self.mode.set_time_point(x) }
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PhraseViewMode for MidiEditorModel {
|
impl PhraseViewMode for MidiEditor {
|
||||||
fn buffer_size (&self, phrase: &MidiClip) -> (usize, usize) {
|
fn buffer_size (&self, phrase: &MidiClip) -> (usize, usize) {
|
||||||
self.mode.buffer_size(phrase)
|
self.mode.buffer_size(phrase)
|
||||||
}
|
}
|
||||||
|
|
@ -170,7 +170,7 @@ impl PhraseViewMode for MidiEditorModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MidiEditorModel {
|
impl MidiEditor {
|
||||||
/// Put note at current position
|
/// Put note at current position
|
||||||
pub fn put_note (&mut self, advance: bool) {
|
pub fn put_note (&mut self, advance: bool) {
|
||||||
let mut redraw = false;
|
let mut redraw = false;
|
||||||
|
|
@ -203,22 +203,22 @@ impl MidiEditorModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
from!(|phrase: &Arc<RwLock<MidiClip>>|MidiEditorModel = {
|
from!(|phrase: &Arc<RwLock<MidiClip>>|MidiEditor = {
|
||||||
let mut model = Self::from(Some(phrase.clone()));
|
let mut model = Self::from(Some(phrase.clone()));
|
||||||
model.redraw();
|
model.redraw();
|
||||||
model
|
model
|
||||||
});
|
});
|
||||||
|
|
||||||
from!(|phrase: Option<Arc<RwLock<MidiClip>>>|MidiEditorModel = {
|
from!(|phrase: Option<Arc<RwLock<MidiClip>>>|MidiEditor = {
|
||||||
let mut model = Self::default();
|
let mut model = Self::default();
|
||||||
*model.phrase_mut() = phrase;
|
*model.phrase_mut() = phrase;
|
||||||
model.redraw();
|
model.redraw();
|
||||||
model
|
model
|
||||||
});
|
});
|
||||||
|
|
||||||
impl std::fmt::Debug for MidiEditorModel {
|
impl std::fmt::Debug for MidiEditor {
|
||||||
fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
|
fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
|
||||||
f.debug_struct("MidiEditorModel")
|
f.debug_struct("MidiEditor")
|
||||||
.field("mode", &self.mode)
|
.field("mode", &self.mode)
|
||||||
.finish()
|
.finish()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,24 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
|
/// Ad-hoc widget with custom rendering.
|
||||||
|
pub fn render <E, F> (render: F) -> impl Render<E>
|
||||||
|
where E: Engine, F: Fn(&mut E::Output)->Usually<()>+Send+Sync
|
||||||
|
{
|
||||||
|
Widget::new(|_|Ok(Some([0.into(),0.into()].into())), render)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Cast to dynamic pointer
|
||||||
|
pub fn widget <E, T> (w: &T) -> &dyn Render<E>
|
||||||
|
where E: Engine, T: Render<E>
|
||||||
|
{
|
||||||
|
w as &dyn Render<E>
|
||||||
|
}
|
||||||
|
|
||||||
#[macro_export] macro_rules! render {
|
#[macro_export] macro_rules! render {
|
||||||
(|$self:ident:$Struct:ident$(<$($L:lifetime),*E$(,$T:ident$(:$U:path)?)*$(,)?>)?|$cb:expr) => {
|
(|$self:ident:$Struct:ident$(<$($L:lifetime),*E$(,$T:ident$(:$U:path)?)*$(,)?>)?|$cb:expr) => {
|
||||||
impl<E: Engine, $($($L),*$($T $(: $U)?),*)?> Render<E> for $Struct $(<$($L,)* E, $($T),*>)? {
|
impl<E: Engine, $($($L),*$($T $(: $U)?),*)?> Render<E> for $Struct $(<$($L,)* E, $($T),*>)? {
|
||||||
fn min_size (&$self, to: <E as Engine>::Size) -> Perhaps<<E as Engine>::Size> {
|
fn content (&$self) -> Option<impl Render<$E>> {
|
||||||
$cb.min_size(to)
|
Some($cb)
|
||||||
}
|
|
||||||
fn render (&$self, to: &mut <E as Engine>::Output) -> Usually<()> {
|
|
||||||
$cb.render(to)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -22,11 +33,8 @@ use crate::*;
|
||||||
$($($L),+)?
|
$($($L),+)?
|
||||||
$($($T),+)?
|
$($($T),+)?
|
||||||
>)? {
|
>)? {
|
||||||
fn min_size (&$self, to: <$E as Engine>::Size) -> Perhaps<<$E as Engine>::Size> {
|
fn content (&$self) -> Option<impl Render<$E>> {
|
||||||
$cb.min_size(to)
|
Some($cb)
|
||||||
}
|
|
||||||
fn render (&$self, to: &mut <$E as Engine>::Output) -> Usually<()> {
|
|
||||||
$cb.render(to)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -42,19 +50,28 @@ pub trait Output<E: Engine> {
|
||||||
fn render_in (&mut self, area: E::Area, widget: &dyn Render<E>) -> Usually<()>;
|
fn render_in (&mut self, area: E::Area, widget: &dyn Render<E>) -> Usually<()>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Cast to dynamic pointer
|
|
||||||
pub fn widget <E: Engine, T: Render<E>> (w: &T) -> &dyn Render<E> {
|
|
||||||
w as &dyn Render<E>
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A renderable component
|
/// A renderable component
|
||||||
pub trait Render<E: Engine>: Send + Sync {
|
pub trait Render<E: Engine>: Send + Sync where (): Render<E> {
|
||||||
|
fn content (&self) -> Option<impl Render<E>> where Self: Sized {
|
||||||
|
None::<()>
|
||||||
|
}
|
||||||
/// Minimum size to use
|
/// Minimum size to use
|
||||||
fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
|
fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
|
||||||
Ok(Some(to))
|
self.content().map(|content|content.min_size(to)).unwrap_or(Ok(None))
|
||||||
}
|
}
|
||||||
/// Draw to output render target
|
/// Draw to output render target
|
||||||
fn render (&self, to: &mut E::Output) -> Usually<()>;
|
fn render (&self, to: &mut E::Output) -> Usually<()> {
|
||||||
|
self.content().map(|content|content.render(to)).unwrap_or(Ok(()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E: Engine> Render<E> for () {
|
||||||
|
fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
fn render (&self, to: &mut E::Output) -> Usually<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E: Engine, R: Render<E>> Render<E> for &R {
|
impl<E: Engine, R: Render<E>> Render<E> for &R {
|
||||||
|
|
@ -12,7 +12,7 @@ pub struct SequencerTui {
|
||||||
pub clock: ClockModel,
|
pub clock: ClockModel,
|
||||||
pub phrases: PoolModel,
|
pub phrases: PoolModel,
|
||||||
pub player: MidiPlayer,
|
pub player: MidiPlayer,
|
||||||
pub editor: MidiEditorModel,
|
pub editor: MidiEditor,
|
||||||
pub size: Measure<Tui>,
|
pub size: Measure<Tui>,
|
||||||
pub status: bool,
|
pub status: bool,
|
||||||
pub note_buf: Vec<u8>,
|
pub note_buf: Vec<u8>,
|
||||||
|
|
@ -30,7 +30,7 @@ from_jack!(|jack|SequencerTui {
|
||||||
transport: true,
|
transport: true,
|
||||||
selectors: true,
|
selectors: true,
|
||||||
phrases: PoolModel::from(&phrase),
|
phrases: PoolModel::from(&phrase),
|
||||||
editor: MidiEditorModel::from(&phrase),
|
editor: MidiEditor::from(&phrase),
|
||||||
player: MidiPlayer::from((&clock, &phrase)),
|
player: MidiPlayer::from((&clock, &phrase)),
|
||||||
size: Measure::new(),
|
size: Measure::new(),
|
||||||
midi_buf: vec![vec![];65536],
|
midi_buf: vec![vec![];65536],
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
pub struct MidiEditStatus<'a>(pub &'a MidiEditorModel);
|
pub struct MidiEditStatus<'a>(pub &'a MidiEditor);
|
||||||
render!(<Tui>|self:MidiEditStatus<'a>|{
|
render!(<Tui>|self:MidiEditStatus<'a>|{
|
||||||
|
|
||||||
let (color, name, length, looped) = if let Some(phrase) = self.0.phrase().as_ref().map(|p|p.read().unwrap()) {
|
let (color, name, length, looped) = if let Some(phrase) = self.0.phrase().as_ref().map(|p|p.read().unwrap()) {
|
||||||
|
|
|
||||||
49
src/test.rs
49
src/test.rs
|
|
@ -1,3 +1,52 @@
|
||||||
|
//#[cfg(test)] mod test_focus {
|
||||||
|
//use super::focus::*;
|
||||||
|
//#[test] fn test_focus () {
|
||||||
|
|
||||||
|
//struct FocusTest {
|
||||||
|
//focused: char,
|
||||||
|
//cursor: (usize, usize)
|
||||||
|
//}
|
||||||
|
|
||||||
|
//impl HasFocus for FocusTest {
|
||||||
|
//type Item = char;
|
||||||
|
//fn focused (&self) -> Self::Item {
|
||||||
|
//self.focused
|
||||||
|
//}
|
||||||
|
//fn set_focused (&mut self, to: Self::Item) {
|
||||||
|
//self.focused = to
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
|
||||||
|
//impl FocusGrid for FocusTest {
|
||||||
|
//fn focus_cursor (&self) -> (usize, usize) {
|
||||||
|
//self.cursor
|
||||||
|
//}
|
||||||
|
//fn focus_cursor_mut (&mut self) -> &mut (usize, usize) {
|
||||||
|
//&mut self.cursor
|
||||||
|
//}
|
||||||
|
//fn focus_layout (&self) -> &[&[Self::Item]] {
|
||||||
|
//&[
|
||||||
|
//&['a', 'a', 'a', 'b', 'b', 'd'],
|
||||||
|
//&['a', 'a', 'a', 'b', 'b', 'd'],
|
||||||
|
//&['a', 'a', 'a', 'c', 'c', 'd'],
|
||||||
|
//&['a', 'a', 'a', 'c', 'c', 'd'],
|
||||||
|
//&['e', 'e', 'e', 'e', 'e', 'e'],
|
||||||
|
//]
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
|
||||||
|
//let mut tester = FocusTest { focused: 'a', cursor: (0, 0) };
|
||||||
|
|
||||||
|
//tester.focus_right();
|
||||||
|
//assert_eq!(tester.cursor.0, 3);
|
||||||
|
//assert_eq!(tester.focused, 'b');
|
||||||
|
|
||||||
|
//tester.focus_down();
|
||||||
|
//assert_eq!(tester.cursor.1, 2);
|
||||||
|
//assert_eq!(tester.focused, 'c');
|
||||||
|
|
||||||
|
//}
|
||||||
|
//}
|
||||||
//use crate::*;
|
//use crate::*;
|
||||||
|
|
||||||
//struct TestEngine([u16;4], Vec<Vec<char>>);
|
//struct TestEngine([u16;4], Vec<Vec<char>>);
|
||||||
|
|
|
||||||
22
src/tui.rs
22
src/tui.rs
|
|
@ -1,27 +1,17 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
mod tui_input; pub(crate) use self::tui_input::*;
|
mod tui_input; pub(crate) use self::tui_input::*;
|
||||||
mod tui_output; pub(crate) use self::tui_output::*;
|
|
||||||
|
|
||||||
pub use self::tui_input::TuiInput;
|
pub use self::tui_input::TuiInput;
|
||||||
|
|
||||||
|
mod tui_output; pub(crate) use self::tui_output::*;
|
||||||
pub use self::tui_output::TuiOutput;
|
pub use self::tui_output::TuiOutput;
|
||||||
|
|
||||||
////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
mod tui_style;
|
mod tui_style;
|
||||||
|
|
||||||
mod tui_theme; pub(crate) use self::tui_theme::*;
|
mod tui_theme; pub(crate) use self::tui_theme::*;
|
||||||
|
|
||||||
mod tui_border; pub(crate) use self::tui_border::*;
|
mod tui_border; pub(crate) use self::tui_border::*;
|
||||||
|
|
||||||
///////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
pub mod file_browser; pub(crate) use self::file_browser::*;
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
pub fn render <F: Fn(&mut TuiOutput)->Usually<()>+Send+Sync> (render: F) -> impl Render<Tui> {
|
|
||||||
Widget::new(|_|Ok(Some([0u16,0u16])), render)
|
|
||||||
}
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////
|
||||||
|
|
||||||
pub struct Tui {
|
pub struct Tui {
|
||||||
|
|
@ -31,7 +21,7 @@ pub struct Tui {
|
||||||
pub area: [u16;4], // FIXME auto resize
|
pub area: [u16;4], // FIXME auto resize
|
||||||
}
|
}
|
||||||
|
|
||||||
impl crate::core::Engine for Tui {
|
impl Engine for Tui {
|
||||||
type Unit = u16;
|
type Unit = u16;
|
||||||
type Size = [Self::Unit;2];
|
type Size = [Self::Unit;2];
|
||||||
type Area = [Self::Unit;4];
|
type Area = [Self::Unit;4];
|
||||||
|
|
|
||||||
|
|
@ -59,39 +59,6 @@ impl Input<Tui> for TuiInput {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct EventMap<'a, const N: usize, E, T, U>(
|
|
||||||
pub [(E, &'a dyn Fn(T) -> U); N],
|
|
||||||
pub Option<&'a dyn Fn(T) -> U>,
|
|
||||||
);
|
|
||||||
|
|
||||||
impl<'a, const N: usize, E: PartialEq, T, U> EventMap<'a, N, E, T, U> {
|
|
||||||
pub fn handle (&self, context: T, event: &E) -> Option<U> {
|
|
||||||
for (binding, handler) in self.0.iter() {
|
|
||||||
if event == binding {
|
|
||||||
return Some(handler(context))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[macro_export] macro_rules! event_map {
|
|
||||||
($events:expr) => {
|
|
||||||
EventMap($events, None)
|
|
||||||
};
|
|
||||||
($events:expr, $default: expr) => {
|
|
||||||
EventMap($events, $default)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[macro_export] macro_rules! event_map_input_to_command {
|
|
||||||
($Engine:ty: $Model:ty: $Command:ty: $EventMap:expr) => {
|
|
||||||
input_to_command!($Command: <$Engine>|state: $Model, input|{
|
|
||||||
event_map!($EventMap).handle(state, input.event())?
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) type KeyMapping<const N: usize, T> = [(TuiEvent, &'static dyn Fn(&T)->PhraseCommand);N];
|
pub(crate) type KeyMapping<const N: usize, T> = [(TuiEvent, &'static dyn Fn(&T)->PhraseCommand);N];
|
||||||
|
|
||||||
#[macro_export] macro_rules! kexp {
|
#[macro_export] macro_rules! kexp {
|
||||||
|
|
|
||||||
|
|
@ -121,15 +121,6 @@ pub fn half_block (lower: bool, upper: bool) -> Option<char> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Render<Tui> for () {
|
|
||||||
fn min_size (&self, _: [u16;2]) -> Perhaps<[u16;2]> {
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
fn render (&self, to: &mut TuiOutput) -> Usually<()> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Render<Tui> for &str {
|
impl Render<Tui> for &str {
|
||||||
fn min_size (&self, _: [u16;2]) -> Perhaps<[u16;2]> {
|
fn min_size (&self, _: [u16;2]) -> Perhaps<[u16;2]> {
|
||||||
// TODO: line breaks
|
// TODO: line breaks
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue