wip: big flat

This commit is contained in:
🪞👃🪞 2024-12-30 15:56:56 +01:00
parent 8cbe621b07
commit 4a3de618d0
20 changed files with 305 additions and 336 deletions

View file

@ -22,7 +22,7 @@ pub struct ArrangerTui {
pub size: Measure<Tui>,
pub note_buf: Vec<u8>,
pub midi_buf: Vec<Vec<Vec<u8>>>,
pub editor: MidiEditorModel,
pub editor: MidiEditor,
pub perf: PerfModel,
}
impl ArrangerTui {
@ -99,27 +99,29 @@ impl ArrangerTui {
}
}
}
render!(<Tui>|self: ArrangerTui|{
let play = PlayPause(self.clock.is_rolling());
let transport = TransportView::new(self, Some(ItemPalette::from(TuiTheme::g(96))), true);
let with_transport = |x|col!([row!(![&play, &transport]), &x]);
let pool_size = if self.phrases.visible { self.splits[1] } else { 0 };
let with_pool = |x|Split::w(false, pool_size, PoolView(&self.phrases), x);
let status = ArrangerStatus::from(self);
let with_editbar = |x|Split::n(false, 1, MidiEditStatus(&self.editor), x);
let with_status = |x|Split::n(false, 2, status, x);
let with_size = |x|lay!([&self.size, x]);
let arranger = ||lay!(|add|{
let color = self.color;
add(&Fill::wh(Tui::bg(color.darkest.rgb, ())))?;
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())),
Fill::wh(&self.editor),
]))))))
});
impl Render<Tui> for ArrangerTui {
fn content (&self) -> Option<impl Render<Tui>> {
let play = PlayPause(self.clock.is_rolling());
let transport = TransportView::new(self, Some(ItemPalette::from(TuiTheme::g(96))), true);
let with_transport = |x|col!([row!(![&play, &transport]), &x]);
let pool_size = if self.phrases.visible { self.splits[1] } else { 0 };
let with_pool = |x|Split::w(false, pool_size, PoolView(&self.phrases), x);
let status = ArrangerStatus::from(self);
let with_editbar = |x|Split::n(false, 1, MidiEditStatus(&self.editor), x);
let with_status = |x|Split::n(false, 2, status, x);
let with_size = |x|lay!([&self.size, x]);
let arranger = ||lay!(|add|{
let color = self.color;
add(&Fill::wh(Tui::bg(color.darkest.rgb, ())))?;
add(&Fill::wh(Outer(Style::default().fg(color.dark.rgb).bg(color.darkest.rgb))))?;
add(&Self::render_mode(self))
});
Some(with_size(with_status(with_editbar(with_pool(with_transport(col!([
Fill::w(Fixed::h(20, arranger())),
Fill::wh(&self.editor),
])))))))
}
}
audio!(|self: ArrangerTui, client, scope|{
// Start profiling cycle
let t0 = self.perf.get_t0();

View file

@ -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) }
}

View file

@ -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
View 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())?
});
}
}

View file

@ -2,7 +2,7 @@ use crate::*;
use super::*;
use KeyCode::{Char, Delete, Tab, Up, Down, Left, Right};
use ClockCommand::{Play, Pause};
use GrooveboxCommand::*;
use GrooveboxCommand as Cmd;
use PhraseCommand::*;
use PhrasePoolCommand::*;
@ -11,7 +11,7 @@ pub struct Groovebox {
pub player: MidiPlayer,
pub pool: PoolModel,
pub editor: MidiEditorModel,
pub editor: MidiEditor,
pub sampler: Sampler,
pub size: Measure<Tui>,
@ -49,7 +49,7 @@ impl Groovebox {
)));
player.play_phrase = Some((Moment::zero(&player.clock.timebase), Some(phrase.clone())));
let pool = crate::pool::PoolModel::from(&phrase);
let editor = crate::midi::MidiEditorModel::from(&phrase);
let editor = crate::midi::MidiEditor::from(&phrase);
Ok(Self {
_jack: jack.clone(),
player,
@ -142,25 +142,29 @@ render!(<Tui>|self:Groovebox|{
});
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!(![
row!(|add|{
if let Some(sample) = &self.0.mapped[self.2] {
add(&format!("Sample {}", sample.read().unwrap().end))?;
}
add(&MidiEditStatus(&self.1))?;
Ok(())
}),
lay!([
Outer(Style::default().fg(TuiTheme::g(128))),
Fill::w(Fixed::h(8, if let Some((_, sample)) = &self.0.recording {
SampleViewer(Some(sample.clone()))
} else if let Some(sample) = &self.0.mapped[self.2] {
SampleViewer(Some(sample.clone()))
} else {
SampleViewer(None)
})),
]),
]), self.3));
impl<'a, T: Render<Tui>> Render<Tui> for EditStatus<'a, T> {
fn content (&self) -> impl Render<Tui> {
Split::n(false, 9, col!([
row!(|add|{
if let Some(sample) = &self.0.mapped[self.2] {
add(&format!("Sample {}", sample.read().unwrap().end))?;
}
add(&MidiEditStatus(&self.1))?;
Ok(())
}),
lay!([
Outer(Style::default().fg(TuiTheme::g(128))),
Fill::w(Fixed::h(8, if let Some((_, sample)) = &self.0.recording {
SampleViewer(Some(sample.clone()))
} else if let Some(sample) = &self.0.mapped[self.2] {
SampleViewer(Some(sample.clone()))
} else {
SampleViewer(None)
})),
]),
]), &self.3)
}
}
struct GrooveboxSamples<'a>(&'a Groovebox);
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
key_pat!(Char(' ')) => Clock(
key_pat!(Char(' ')) => Cmd::Clock(
if state.clock().is_stopped() { Play(Some(0)) } else { Pause(Some(0)) }
),
// 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
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)
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
} else {
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() {
let editing = state.editor.phrase().as_ref().map(|p|p.read().unwrap().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
} else {
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.
// The ones defined above supersede them.
_ => 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) {
Pool(command)
Cmd::Pool(command)
} else {
return None
}
@ -250,7 +254,7 @@ command!(|self: GrooveboxCommand, state: Groovebox|match self {
Self::Pool(cmd) => {
let mut default = |cmd: PoolCommand|cmd
.execute(&mut state.pool)
.map(|x|x.map(Pool));
.map(|x|x.map(Self::Pool));
match cmd {
// autoselect: automatically load selected phrase in editor
PoolCommand::Select(_) => {
@ -268,13 +272,13 @@ command!(|self: GrooveboxCommand, state: Groovebox|match self {
}
},
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 {
_ => default()?
}
},
Self::Clock(cmd) => cmd.execute(state)?.map(Clock),
Self::Sampler(cmd) => cmd.execute(&mut state.sampler)?.map(Sampler),
Self::Clock(cmd) => cmd.execute(state)?.map(Self::Clock),
Self::Sampler(cmd) => cmd.execute(&mut state.sampler)?.map(Self::Sampler),
Self::Enqueue(phrase) => {
state.player.enqueue_next(phrase.as_ref());
None

View file

@ -1,69 +1,50 @@
#![allow(unused)]
#![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::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::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(crate) use crossterm::{ExecutableCommand};
@ -93,3 +74,55 @@ pub(crate) use ::palette::{
};
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) }
}

View file

@ -3,13 +3,13 @@ use KeyCode::{Char, Up, Down, Left, Right, Enter};
use PhraseCommand::*;
pub trait HasEditor {
fn editor (&self) -> &MidiEditorModel;
fn editor (&self) -> &MidiEditor;
}
#[macro_export] macro_rules! has_editor {
(|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => {
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>>>),
}
event_map_input_to_command!(Tui: MidiEditorModel: PhraseCommand: MidiEditorModel::KEYS);
impl MidiEditorModel {
event_map_input_to_command!(Tui: MidiEditor: PhraseCommand: MidiEditor::KEYS);
impl MidiEditor {
const KEYS: KeyMapping<31, Self> = [
(kexp!(Ctrl-Alt-Up), &|s: &Self|SetNoteScroll(s.note_point() + 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 {
fn execute (self, state: &mut MidiEditorModel) -> Perhaps<Self> {
impl Command<MidiEditor> for PhraseCommand {
fn execute (self, state: &mut MidiEditor) -> Perhaps<Self> {
use PhraseCommand::*;
match self {
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
pub struct MidiEditorModel {
pub struct MidiEditor {
/// Renders the phrase
pub mode: Box<dyn PhraseViewMode>,
pub size: Measure<Tui>
}
impl Default for MidiEditorModel {
impl Default for MidiEditor {
fn default () -> Self {
let mut mode = Box::new(PianoHorizontal::new(None));
mode.redraw();
@ -106,13 +106,13 @@ impl Default for MidiEditorModel {
}
}
has_size!(<Tui>|self:MidiEditorModel|&self.size);
render!(<Tui>|self: MidiEditorModel|{
has_size!(<Tui>|self: MidiEditor|&self.size);
render!(<Tui>|self: MidiEditor|{
self.autoscroll();
self.autozoom();
&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 {
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_zoom (&self) -> &AtomicUsize { self.mode.time_zoom() }
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() }
}
impl NoteRange for MidiEditorModel {
impl NoteRange for MidiEditor {
fn note_lo (&self) -> &AtomicUsize { self.mode.note_lo() }
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 set_note_len (&self, x: usize) { self.mode.set_note_len(x) }
fn note_point (&self) -> usize { self.mode.note_point() }
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 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) {
self.mode.buffer_size(phrase)
}
@ -170,7 +170,7 @@ impl PhraseViewMode for MidiEditorModel {
}
}
impl MidiEditorModel {
impl MidiEditor {
/// Put note at current position
pub fn put_note (&mut self, advance: bool) {
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()));
model.redraw();
model
});
from!(|phrase: Option<Arc<RwLock<MidiClip>>>|MidiEditorModel = {
from!(|phrase: Option<Arc<RwLock<MidiClip>>>|MidiEditor = {
let mut model = Self::default();
*model.phrase_mut() = phrase;
model.redraw();
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> {
f.debug_struct("MidiEditorModel")
f.debug_struct("MidiEditor")
.field("mode", &self.mode)
.finish()
}

View file

@ -1,13 +1,24 @@
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 {
(|$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),*>)? {
fn min_size (&$self, to: <E as Engine>::Size) -> Perhaps<<E as Engine>::Size> {
$cb.min_size(to)
}
fn render (&$self, to: &mut <E as Engine>::Output) -> Usually<()> {
$cb.render(to)
fn content (&$self) -> Option<impl Render<$E>> {
Some($cb)
}
}
};
@ -22,11 +33,8 @@ use crate::*;
$($($L),+)?
$($($T),+)?
>)? {
fn min_size (&$self, to: <$E as Engine>::Size) -> Perhaps<<$E as Engine>::Size> {
$cb.min_size(to)
}
fn render (&$self, to: &mut <$E as Engine>::Output) -> Usually<()> {
$cb.render(to)
fn content (&$self) -> Option<impl Render<$E>> {
Some($cb)
}
}
}
@ -42,19 +50,28 @@ pub trait Output<E: Engine> {
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
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
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
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 {

View file

@ -12,7 +12,7 @@ pub struct SequencerTui {
pub clock: ClockModel,
pub phrases: PoolModel,
pub player: MidiPlayer,
pub editor: MidiEditorModel,
pub editor: MidiEditor,
pub size: Measure<Tui>,
pub status: bool,
pub note_buf: Vec<u8>,
@ -30,7 +30,7 @@ from_jack!(|jack|SequencerTui {
transport: true,
selectors: true,
phrases: PoolModel::from(&phrase),
editor: MidiEditorModel::from(&phrase),
editor: MidiEditor::from(&phrase),
player: MidiPlayer::from((&clock, &phrase)),
size: Measure::new(),
midi_buf: vec![vec![];65536],

View file

@ -1,6 +1,6 @@
use crate::*;
pub struct MidiEditStatus<'a>(pub &'a MidiEditorModel);
pub struct MidiEditStatus<'a>(pub &'a MidiEditor);
render!(<Tui>|self:MidiEditStatus<'a>|{
let (color, name, length, looped) = if let Some(phrase) = self.0.phrase().as_ref().map(|p|p.read().unwrap()) {

View file

@ -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::*;
//struct TestEngine([u16;4], Vec<Vec<char>>);

View file

@ -1,27 +1,17 @@
use crate::*;
mod tui_input; pub(crate) use self::tui_input::*;
mod tui_output; pub(crate) use self::tui_output::*;
mod tui_input; pub(crate) use self::tui_input::*;
pub use self::tui_input::TuiInput;
mod tui_output; pub(crate) use self::tui_output::*;
pub use self::tui_output::TuiOutput;
////////////////////////////////////////////////////////
mod tui_style;
mod tui_theme; pub(crate) use self::tui_theme::*;
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 {
@ -31,7 +21,7 @@ pub struct Tui {
pub area: [u16;4], // FIXME auto resize
}
impl crate::core::Engine for Tui {
impl Engine for Tui {
type Unit = u16;
type Size = [Self::Unit;2];
type Area = [Self::Unit;4];

View file

@ -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];
#[macro_export] macro_rules! kexp {

View file

@ -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 {
fn min_size (&self, _: [u16;2]) -> Perhaps<[u16;2]> {
// TODO: line breaks