diff --git a/src/arranger.rs b/src/arranger.rs index efb6d39f..33e74002 100644 --- a/src/arranger.rs +++ b/src/arranger.rs @@ -22,7 +22,7 @@ pub struct ArrangerTui { pub size: Measure, pub note_buf: Vec, pub midi_buf: Vec>>, - pub editor: MidiEditorModel, + pub editor: MidiEditor, pub perf: PerfModel, } impl ArrangerTui { @@ -99,27 +99,29 @@ impl ArrangerTui { } } } -render!(|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 for ArrangerTui { + fn content (&self) -> Option> { + 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(); diff --git a/src/core/color.rs b/src/color.rs similarity index 100% rename from src/core/color.rs rename to src/color.rs diff --git a/src/core/command.rs b/src/command.rs similarity index 100% rename from src/core/command.rs rename to src/command.rs diff --git a/src/core.rs b/src/core.rs index 40b42d85..e69de29b 100644 --- a/src/core.rs +++ b/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 = Result>; - -/// Standard optional result type. -pub type Perhaps = Result, Box>; - -/// 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 { - /// Returns current value - fn get (&self) -> T; -} - -pub trait Mutable: Gettable { - /// Sets new value, returns old - fn set (&mut self, value: T) -> T; -} - -pub trait InteriorMutable: Gettable { - /// Sets new value, returns old - fn set (&self, value: T) -> T; -} - -impl Gettable for AtomicBool { - fn get (&self) -> bool { self.load(Relaxed) } -} -impl InteriorMutable for AtomicBool { - fn set (&self, value: bool) -> bool { self.swap(value, Relaxed) } -} -impl Gettable for AtomicUsize { - fn get (&self) -> usize { self.load(Relaxed) } -} -impl InteriorMutable for AtomicUsize { - fn set (&self, value: usize) -> usize { self.swap(value, Relaxed) } -} diff --git a/src/core/test.rs b/src/core/test.rs deleted file mode 100644 index 88699914..00000000 --- a/src/core/test.rs +++ /dev/null @@ -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'); - - } -} diff --git a/src/core/engine.rs b/src/engine.rs similarity index 100% rename from src/core/engine.rs rename to src/engine.rs diff --git a/src/event.rs b/src/event.rs new file mode 100644 index 00000000..ea13af41 --- /dev/null +++ b/src/event.rs @@ -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 { + 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())? + }); + } +} diff --git a/src/tui/file_browser.rs b/src/file.rs similarity index 100% rename from src/tui/file_browser.rs rename to src/file.rs diff --git a/src/core/focus.rs b/src/focus.rs similarity index 100% rename from src/core/focus.rs rename to src/focus.rs diff --git a/src/groovebox.rs b/src/groovebox.rs index f8a6753a..94f63393 100644 --- a/src/groovebox.rs +++ b/src/groovebox.rs @@ -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, @@ -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!(|self:Groovebox|{ }); struct EditStatus<'a, T: Render>(&'a Sampler, &'a MidiEditor, usize, T); -render!(|self: EditStatus<'a, T: Render>|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> Render for EditStatus<'a, T> { + fn content (&self) -> impl Render { + 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!(|self: GrooveboxSamples<'a>|{ @@ -204,19 +208,19 @@ input_to_command!(GrooveboxCommand: |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: |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: |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 diff --git a/src/core/input.rs b/src/input.rs similarity index 100% rename from src/core/input.rs rename to src/input.rs diff --git a/src/lib.rs b/src/lib.rs index 01258fc5..60204b0e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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 = Result>; + +/// Standard optional result type. +pub type Perhaps = Result, Box>; + +/// 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 { + /// Returns current value + fn get (&self) -> T; +} + +pub trait Mutable: Gettable { + /// Sets new value, returns old + fn set (&mut self, value: T) -> T; +} + +pub trait InteriorMutable: Gettable { + /// Sets new value, returns old + fn set (&self, value: T) -> T; +} + +impl Gettable for AtomicBool { + fn get (&self) -> bool { self.load(Relaxed) } +} + +impl InteriorMutable for AtomicBool { + fn set (&self, value: bool) -> bool { self.swap(value, Relaxed) } +} + +impl Gettable for AtomicUsize { + fn get (&self) -> usize { self.load(Relaxed) } +} + +impl InteriorMutable for AtomicUsize { + fn set (&self, value: usize) -> usize { self.swap(value, Relaxed) } +} diff --git a/src/midi/midi_editor.rs b/src/midi/midi_editor.rs index f61779b1..72e79c59 100644 --- a/src/midi/midi_editor.rs +++ b/src/midi/midi_editor.rs @@ -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>>), } -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 for PhraseCommand { - fn execute (self, state: &mut MidiEditorModel) -> Perhaps { +impl Command for PhraseCommand { + fn execute (self, state: &mut MidiEditor) -> Perhaps { use PhraseCommand::*; match self { Show(phrase) => { state.set_phrase(phrase.as_ref()); }, @@ -92,13 +92,13 @@ impl Command for PhraseCommand { } /// Contains state for viewing and editing a phrase -pub struct MidiEditorModel { +pub struct MidiEditor { /// Renders the phrase pub mode: Box, pub size: Measure } -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!(|self:MidiEditorModel|&self.size); -render!(|self: MidiEditorModel|{ +has_size!(|self: MidiEditor|&self.size); +render!(|self: MidiEditor|{ self.autoscroll(); self.autozoom(); &self.mode }); -//render!(|self: MidiEditorModel|lay!(|add|{add(&self.size)?;add(self.mode)}));//bollocks +//render!(|self: MidiEditor|lay!(|add|{add(&self.size)?;add(self.mode)}));//bollocks pub trait PhraseViewMode: Render + HasSize + MidiRange + MidiPoint + Debug + Send + Sync { fn buffer_size (&self, phrase: &MidiClip) -> (usize, usize); @@ -125,9 +125,9 @@ pub trait PhraseViewMode: Render + HasSize + MidiRange + MidiPoint + D } } -impl MidiView for MidiEditorModel {} +impl MidiView 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>|MidiEditorModel = { +from!(|phrase: &Arc>|MidiEditor = { let mut model = Self::from(Some(phrase.clone())); model.redraw(); model }); -from!(|phrase: Option>>|MidiEditorModel = { +from!(|phrase: Option>>|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() } diff --git a/src/core/output.rs b/src/output.rs similarity index 79% rename from src/core/output.rs rename to src/output.rs index ff8748a7..5a45e89e 100644 --- a/src/core/output.rs +++ b/src/output.rs @@ -1,13 +1,24 @@ use crate::*; +/// Ad-hoc widget with custom rendering. +pub fn render (render: F) -> impl Render + 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 (w: &T) -> &dyn Render + where E: Engine, T: Render +{ + w as &dyn Render +} + #[macro_export] macro_rules! render { (|$self:ident:$Struct:ident$(<$($L:lifetime),*E$(,$T:ident$(:$U:path)?)*$(,)?>)?|$cb:expr) => { impl Render for $Struct $(<$($L,)* E, $($T),*>)? { - fn min_size (&$self, to: ::Size) -> Perhaps<::Size> { - $cb.min_size(to) - } - fn render (&$self, to: &mut ::Output) -> Usually<()> { - $cb.render(to) + fn content (&$self) -> Option> { + 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> { + Some($cb) } } } @@ -42,19 +50,28 @@ pub trait Output { fn render_in (&mut self, area: E::Area, widget: &dyn Render) -> Usually<()>; } -/// Cast to dynamic pointer -pub fn widget > (w: &T) -> &dyn Render { - w as &dyn Render -} - /// A renderable component -pub trait Render: Send + Sync { +pub trait Render: Send + Sync where (): Render { + fn content (&self) -> Option> where Self: Sized { + None::<()> + } /// Minimum size to use fn min_size (&self, to: E::Size) -> Perhaps { - 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 Render for () { + fn min_size (&self, to: E::Size) -> Perhaps { + Ok(None) + } + fn render (&self, to: &mut E::Output) -> Usually<()> { + Ok(()) + } } impl> Render for &R { diff --git a/src/sequencer.rs b/src/sequencer.rs index b9dc4287..fdfa6cc5 100644 --- a/src/sequencer.rs +++ b/src/sequencer.rs @@ -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, pub status: bool, pub note_buf: Vec, @@ -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], diff --git a/src/status/status_edit.rs b/src/status/status_edit.rs index 2bbde7be..5ea8de03 100644 --- a/src/status/status_edit.rs +++ b/src/status/status_edit.rs @@ -1,6 +1,6 @@ use crate::*; -pub struct MidiEditStatus<'a>(pub &'a MidiEditorModel); +pub struct MidiEditStatus<'a>(pub &'a MidiEditor); render!(|self:MidiEditStatus<'a>|{ let (color, name, length, looped) = if let Some(phrase) = self.0.phrase().as_ref().map(|p|p.read().unwrap()) { diff --git a/src/test.rs b/src/test.rs index 5f7dcb0e..34c6e4aa 100644 --- a/src/test.rs +++ b/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::*; //struct TestEngine([u16;4], Vec>); diff --git a/src/tui.rs b/src/tui.rs index 8cd25285..361c45b4 100644 --- a/src/tui.rs +++ b/src/tui.rs @@ -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 Usually<()>+Send+Sync> (render: F) -> impl Render { - 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]; diff --git a/src/tui/tui_input.rs b/src/tui/tui_input.rs index cabf317b..d8b20059 100644 --- a/src/tui/tui_input.rs +++ b/src/tui/tui_input.rs @@ -59,39 +59,6 @@ impl Input 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 { - 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 = [(TuiEvent, &'static dyn Fn(&T)->PhraseCommand);N]; #[macro_export] macro_rules! kexp { diff --git a/src/tui/tui_output.rs b/src/tui/tui_output.rs index b346b535..b139c490 100644 --- a/src/tui/tui_output.rs +++ b/src/tui/tui_output.rs @@ -121,15 +121,6 @@ pub fn half_block (lower: bool, upper: bool) -> Option { } } -impl Render for () { - fn min_size (&self, _: [u16;2]) -> Perhaps<[u16;2]> { - Ok(None) - } - fn render (&self, to: &mut TuiOutput) -> Usually<()> { - Ok(()) - } -} - impl Render for &str { fn min_size (&self, _: [u16;2]) -> Perhaps<[u16;2]> { // TODO: line breaks