diff --git a/crates/tek_core/src/focus.rs b/crates/tek_core/src/focus.rs index e65e3293..b32a65ba 100644 --- a/crates/tek_core/src/focus.rs +++ b/crates/tek_core/src/focus.rs @@ -1,5 +1,47 @@ use crate::*; +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum FocusState { + Exited(T), + Focused(T), + Entered(T), +} + +impl FocusState { + pub fn inner (&self) -> T { + match self { + Self::Exited(inner) => *inner, + Self::Focused(inner) => *inner, + Self::Entered(inner) => *inner, + } + } + pub fn set_inner (&mut self, inner: T) { + *self = match self { + Self::Exited(_) => Self::Exited(inner), + Self::Focused(_) => Self::Focused(inner), + Self::Entered(_) => Self::Entered(inner), + } + } + pub fn is_exited (&self) -> bool { + if let Self::Exited(_) = self { true } else { false } + } + pub fn is_focused (&self) -> bool { + if let Self::Focused(_) = self { true } else { false } + } + pub fn is_entered (&self) -> bool { + if let Self::Entered(_) = self { true } else { false } + } + pub fn to_exited (&mut self) { + *self = Self::Exited(self.inner()) + } + pub fn to_focused (&mut self) { + *self = Self::Focused(self.inner()) + } + pub fn to_entered (&mut self) { + *self = Self::Entered(self.inner()) + } +} + #[derive(Copy, Clone, PartialEq, Debug)] pub enum FocusCommand { Next, diff --git a/crates/tek_tui/src/tui_content.rs b/crates/tek_tui/src/tui_content.rs index 0a0c3239..b5d05be8 100644 --- a/crates/tek_tui/src/tui_content.rs +++ b/crates/tek_tui/src/tui_content.rs @@ -170,9 +170,9 @@ impl<'a, T: PhrasesViewState> Content for PhrasesView<'a, T> { impl<'a, T: PhraseViewState> Content for PhraseView<'a, T> { type Engine = Tui; fn content (&self) -> impl Widget { - let phrase = self.0.phrase(); + let phrase = self.0.phrase_editing(); let size = self.0.size(); - let focused = self.0.phrase_focused(); + let focused = self.0.phrase_editor_focused(); let entered = self.0.phrase_editor_entered(); let keys = self.0.keys(); let buffer = self.0.buffer(); diff --git a/crates/tek_tui/src/tui_control.rs b/crates/tek_tui/src/tui_control.rs index 8d58dea1..7f67f37d 100644 --- a/crates/tek_tui/src/tui_control.rs +++ b/crates/tek_tui/src/tui_control.rs @@ -6,19 +6,19 @@ pub trait TransportControl: ClockApi { impl TransportControl for TransportTui { fn transport_focused (&self) -> TransportFocus { - self.state.focus + self.state.focus.inner() } } impl TransportControl for SequencerTui { fn transport_focused (&self) -> TransportFocus { - self.transport.focus + self.transport.focus.inner() } } impl TransportControl for ArrangerTui { fn transport_focused (&self) -> TransportFocus { - self.transport.focus + self.transport.focus.inner() } } diff --git a/crates/tek_tui/src/tui_debug.rs b/crates/tek_tui/src/tui_debug.rs index ced0afcd..75ebf5a1 100644 --- a/crates/tek_tui/src/tui_debug.rs +++ b/crates/tek_tui/src/tui_debug.rs @@ -1,8 +1,13 @@ +// Not all fields are included here. Add as needed. + use crate::*; -impl std::fmt::Debug for TransportModel { - fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { - f.debug_struct("TransportModel") +use std::fmt::{Debug, Formatter, Error}; +type DebugResult = std::result::Result<(), Error>; + +impl Debug for ClockModel { + fn fmt (&self, f: &mut Formatter<'_>) -> DebugResult { + f.debug_struct("editor") .field("playing", &self.playing) .field("started", &self.started) .field("current", &self.current) @@ -12,8 +17,17 @@ impl std::fmt::Debug for TransportModel { } } -impl std::fmt::Debug for TransportTui { - fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { +impl Debug for TransportModel { + fn fmt (&self, f: &mut Formatter<'_>) -> DebugResult { + f.debug_struct("TransportModel") + .field("clock", &self.clock) + .field("focus", &self.focus) + .finish() + } +} + +impl Debug for TransportTui { + fn fmt (&self, f: &mut Formatter<'_>) -> DebugResult { f.debug_struct("Measure") .field("jack", &self.jack) .field("state", &self.state) @@ -23,11 +37,21 @@ impl std::fmt::Debug for TransportTui { } } -impl std::fmt::Debug for PhraseEditorModel { - fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { +impl Debug for PhraseEditorModel { + fn fmt (&self, f: &mut Formatter<'_>) -> DebugResult { f.debug_struct("editor") .field("note_axis", &self.time_axis) .field("time_axis", &self.note_axis) .finish() } } + +impl Debug for PhrasePlayerModel { + fn fmt (&self, f: &mut Formatter<'_>) -> DebugResult { + f.debug_struct("editor") + .field("clock", &self.clock) + .field("play_phrase", &self.play_phrase) + .field("next_phrase", &self.next_phrase) + .finish() + } +} diff --git a/crates/tek_tui/src/tui_impls.rs b/crates/tek_tui/src/tui_impls.rs index d4c5c16a..221450f0 100644 --- a/crates/tek_tui/src/tui_impls.rs +++ b/crates/tek_tui/src/tui_impls.rs @@ -174,18 +174,23 @@ macro_rules! impl_phrase_editor_control { impl_jack_api!(TransportTui::jack); impl_jack_api!(SequencerTui::jack); impl_jack_api!(ArrangerTui::jack); -impl_clock_api!(TransportTui::state); -impl_clock_api!(SequencerTui::transport); -impl_clock_api!(ArrangerTui::transport); -impl_clock_api!(PhrasePlayerModel); -impl_clock_api!(ArrangerTrack::player); + +impl_clock_api!(TransportTui::state::clock); +impl_clock_api!(SequencerTui::transport::clock); +impl_clock_api!(ArrangerTui::transport::clock); +impl_clock_api!(PhrasePlayerModel::clock); +impl_clock_api!(ArrangerTrack::player::clock); + impl_has_phrases!(PhrasesModel); impl_has_phrases!(SequencerTui::phrases); impl_has_phrases!(ArrangerTui::phrases); + impl_midi_player!(SequencerTui::player); impl_midi_player!(ArrangerTrack::player); impl_midi_player!(PhrasePlayerModel); + impl_phrases_control!(SequencerTui); impl_phrases_control!(ArrangerTui); + impl_phrase_editor_control!(SequencerTui [SequencerFocus::PhraseEditor]); impl_phrase_editor_control!(ArrangerTui [ArrangerFocus::PhraseEditor]); diff --git a/crates/tek_tui/src/tui_init.rs b/crates/tek_tui/src/tui_init.rs index 2c5b226e..6ca779de 100644 --- a/crates/tek_tui/src/tui_init.rs +++ b/crates/tek_tui/src/tui_init.rs @@ -6,7 +6,7 @@ impl TryFrom<&Arc>> for TransportTui { fn try_from (jack: &Arc>) -> Usually { Ok(Self { jack: jack.clone(), - state: TransportModel::from(jack.read().unwrap().transport()), + state: TransportModel::from(&Arc::new(jack.read().unwrap().transport())), size: Measure::new(), cursor: (0, 0), }) @@ -16,11 +16,11 @@ impl TryFrom<&Arc>> for TransportTui { impl TryFrom<&Arc>> for SequencerTui { type Error = Box; fn try_from (jack: &Arc>) -> Usually { + let transport = TransportModel::from(&Arc::new(jack.read().unwrap().transport())); Ok(Self { jack: jack.clone(), - transport: TransportModel::from(jack.read().unwrap().transport()), phrases: PhrasesModel::default(), - player: PhrasePlayerModel::default(), + player: PhrasePlayerModel::from(&transport.clock), editor: PhraseEditorModel::default(), size: Measure::new(), cursor: (0, 0), @@ -28,6 +28,7 @@ impl TryFrom<&Arc>> for SequencerTui { split: 20, midi_buf: vec![], note_buf: vec![], + transport, }) } } @@ -37,7 +38,7 @@ impl TryFrom<&Arc>> for ArrangerTui { fn try_from (jack: &Arc>) -> Usually { Ok(Self { jack: jack.clone(), - transport: TransportModel::from(jack.read().unwrap().transport()), + transport: TransportModel::from(&Arc::new(jack.read().unwrap().transport())), phrases: PhrasesModel::default(), editor: PhraseEditorModel::default(), selected: ArrangerSelection::Clip(0, 0), @@ -58,4 +59,3 @@ impl TryFrom<&Arc>> for ArrangerTui { }) } } - diff --git a/crates/tek_tui/src/tui_input.rs b/crates/tek_tui/src/tui_input.rs index 41dab1ad..0dea5f84 100644 --- a/crates/tek_tui/src/tui_input.rs +++ b/crates/tek_tui/src/tui_input.rs @@ -112,7 +112,7 @@ impl InputToCommand for ArrangerCommand { PhraseCommand::input_to_command(state, input)? ), ArrangerFocus::Phrases => match input.event() { - key!(KeyCode::Char('e')) => EditPhrase(state.phrase().clone()), + key!(KeyCode::Char('e')) => EditPhrase(state.phrase_editing().clone()), _ => Phrases(PhrasesCommand::input_to_command(state, input)?) }, ArrangerFocus::Arranger => { @@ -121,7 +121,7 @@ impl InputToCommand for ArrangerCommand { use ArrangerClipCommand as Clip; use ArrangerSceneCommand as Scene; match input.event() { - key!(KeyCode::Char('e')) => EditPhrase(state.phrase().clone()), + key!(KeyCode::Char('e')) => EditPhrase(state.phrase_editing().clone()), _ => match input.event() { // FIXME: boundary conditions diff --git a/crates/tek_tui/src/tui_model.rs b/crates/tek_tui/src/tui_model.rs index 65b49ec9..702bcf89 100644 --- a/crates/tek_tui/src/tui_model.rs +++ b/crates/tek_tui/src/tui_model.rs @@ -1,56 +1,57 @@ use crate::*; -pub struct TransportModel { +#[derive(Clone)] +pub struct ClockModel { /// JACK transport handle. - pub(crate) transport: Arc, + pub(crate) transport: Arc, /// Playback state - pub(crate) playing: Arc>>, + pub(crate) playing: Arc>>, /// Global sample and usec at which playback started - pub(crate) started: Arc>>, + pub(crate) started: Arc>>, /// Current moment in time - pub(crate) current: Arc, + pub(crate) current: Arc, /// Note quantization factor - pub(crate) quant: Arc, + pub(crate) quant: Arc, /// Launch quantization factor - pub(crate) sync: Arc, - /// Enable metronome? - pub(crate) metronome: bool, - /// Selected transport component - pub(crate) focus: TransportFocus, - /// Whether the transport is focused - pub(crate) is_focused: bool, + pub(crate) sync: Arc, + /// TODO: Enable metronome? + pub(crate) metronome: bool, } -impl From for TransportModel { - fn from (transport: Transport) -> Self { +impl From<&Arc> for ClockModel { + fn from (transport: &Arc) -> Self { Self { - current: Instant::default().into(), - focus: TransportFocus::PlayPause, - is_focused: true, - metronome: false, - playing: RwLock::new(None).into(), - quant: Quantize::default().into(), - started: RwLock::new(None).into(), - sync: LaunchSync::default().into(), - transport: transport.into(), + current: Instant::default().into(), + playing: RwLock::new(None).into(), + quant: Quantize::default().into(), + started: RwLock::new(None).into(), + sync: LaunchSync::default().into(), + transport: transport.clone(), + metronome: false, + } + } +} + +pub struct TransportModel { + /// State of clock and playhead + pub(crate) clock: ClockModel, + /// Whether the transport is focused and which part of it is selected + pub(crate) focus: FocusState, +} + +impl From<&Arc> for TransportModel { + fn from (transport: &Arc) -> Self { + Self { + clock: ClockModel::from(transport), + focus: FocusState::Exited(TransportFocus::PlayPause), } } } /// Contains state for playing a phrase -#[derive(Debug)] pub struct PhrasePlayerModel { - /// Playback state - pub(crate) playing: Arc>>, - /// Global sample and usec at which playback started - pub(crate) started: Arc>>, - /// Current moment in time - pub(crate) current: Arc, - /// Note quantization factor - pub(crate) quant: Arc, - /// Launch quantization factor - pub(crate) sync: Arc, - + /// State of clock and playhead + pub(crate) clock: ClockModel, /// Start time and phrase being played pub(crate) play_phrase: Option<(Instant, Option>>)>, /// Start time and next phrase @@ -73,14 +74,10 @@ pub struct PhrasePlayerModel { pub(crate) notes_out: Arc>, } -impl Default for PhrasePlayerModel { - fn default () -> Self { +impl From<&ClockModel> for PhrasePlayerModel { + fn from (clock: &ClockModel) -> Self { Self { - playing: RwLock::new(None).into(), - started: RwLock::new(None).into(), - current: Instant::default().into(), - quant: Quantize::default().into(), - sync: LaunchSync::default().into(), + clock: clock.clone(), midi_ins: vec![], midi_outs: vec![], reset: true, @@ -95,19 +92,6 @@ impl Default for PhrasePlayerModel { } } -impl From<&TransportModel> for PhrasePlayerModel { - fn from (transport: &TransportModel) -> Self { - Self { - playing: transport.playing.clone(), - started: transport.started.clone(), - current: transport.current.clone(), - quant: transport.quant.clone(), - sync: transport.sync.clone(), - ..Default::default() - } - } -} - /// Contains state for viewing and editing a phrase pub struct PhraseEditorModel { /// Phrase being played @@ -123,9 +107,7 @@ pub struct PhraseEditorModel { /// Cursor/scroll/zoom in time axis pub(crate) time_axis: RwLock>, /// Whether this widget is focused - pub(crate) focused: bool, - /// Whether note enter mode is enabled - pub(crate) entered: bool, + pub(crate) focus: FocusState<()>, /// Display mode pub(crate) mode: bool, /// Notes currently held at input @@ -145,8 +127,7 @@ impl Default for PhraseEditorModel { note_len: 24, keys: keys_vert(), buffer: Default::default(), - focused: false, - entered: false, + focus: FocusState::Exited(()), mode: false, notes_in: RwLock::new([false;128]).into(), notes_out: RwLock::new([false;128]).into(), @@ -178,9 +159,7 @@ pub struct PhrasesModel { /// Mode switch pub(crate) mode: Option, /// Whether this widget is focused - pub(crate) focused: bool, - /// Whether this widget is entered - pub(crate) entered: bool, + pub(crate) focus: FocusState<()>, } impl Default for PhrasesModel { @@ -190,8 +169,7 @@ impl Default for PhrasesModel { phrase: 0.into(), scroll: 0, mode: None, - focused: false, - entered: false, + focus: FocusState::Exited(()), } } } @@ -264,7 +242,7 @@ impl ArrangerTracksApi for ArrangerTui { width: name.len() + 2, name: Arc::new(name.into()), color: color.unwrap_or_else(||ItemColor::random()), - player: PhrasePlayerModel::default(), + player: PhrasePlayerModel::from(&self.transport.clock), }; self.tracks_mut().push(track); let index = self.tracks().len() - 1; @@ -353,16 +331,3 @@ impl PhraseLength { format!("{:>02}", self.ticks()) } } - -impl PhrasesModel { - pub fn new (phrases: Vec>>) -> Self { - Self { - scroll: 0, - phrase: 0.into(), - mode: None, - focused: false, - entered: false, - phrases, - } - } -} diff --git a/crates/tek_tui/src/tui_view.rs b/crates/tek_tui/src/tui_view.rs index 1adb2294..9bf7fbcc 100644 --- a/crates/tek_tui/src/tui_view.rs +++ b/crates/tek_tui/src/tui_view.rs @@ -22,10 +22,30 @@ pub trait TransportViewState: ClockApi + Send + Sync { self.current().usec.format_msu() } } +macro_rules! impl_transport_view_state { + ($Struct:ident :: $field:ident) => { + impl TransportViewState for $Struct { + fn transport_selected (&self) -> TransportFocus { + self.$field.focus.inner() + } + fn transport_focused (&self) -> bool { + true + } + } + } +} +impl_transport_view_state!(TransportTui::state); +impl_transport_view_state!(SequencerTui::transport); +impl_transport_view_state!(ArrangerTui::transport); pub trait ArrangerViewState { fn arranger_focused (&self) -> bool; } +impl ArrangerViewState for ArrangerTui { + fn arranger_focused (&self) -> bool { + self.focused() == ArrangerFocus::Arranger + } +} pub trait PhrasesViewState: Send + Sync { fn phrases_focused (&self) -> bool; @@ -34,10 +54,34 @@ pub trait PhrasesViewState: Send + Sync { fn phrase_index (&self) -> usize; fn phrase_mode (&self) -> &Option; } +macro_rules! impl_phrases_view_state { + ($Struct:ident $(:: $field:ident)*) => { + impl PhrasesViewState for $Struct { + fn phrases_focused (&self) -> bool { + todo!() + } + fn phrases_entered (&self) -> bool { + todo!() + } + fn phrases (&self) -> Vec>> { + todo!() + } + fn phrase_index (&self) -> usize { + todo!() + } + fn phrase_mode (&self) -> &Option { + &self$(.$field)*.mode + } + } + } +} +impl_phrases_view_state!(PhrasesModel); +impl_phrases_view_state!(SequencerTui::phrases); +impl_phrases_view_state!(ArrangerTui::phrases); pub trait PhraseViewState: Send + Sync { - fn phrase (&self) -> &Option>>; - fn phrase_focused (&self) -> bool; + fn phrase_editing (&self) -> &Option>>; + fn phrase_editor_focused (&self) -> bool; fn phrase_editor_size (&self) -> &Measure; fn phrase_editor_entered (&self) -> bool; fn keys (&self) -> &Buffer; @@ -48,201 +92,48 @@ pub trait PhraseViewState: Send + Sync { fn now (&self) -> &Arc; fn size (&self) -> &Measure; } - -impl TransportViewState for TransportTui { - fn transport_selected (&self) -> TransportFocus { - self.state.focus - } - fn transport_focused (&self) -> bool { - true - } -} - -impl TransportViewState for SequencerTui { - fn transport_selected (&self) -> TransportFocus { - self.transport.focus - } - fn transport_focused (&self) -> bool { - self.focused() == SequencerFocus::Transport - } -} - -impl TransportViewState for ArrangerTui { - fn transport_selected (&self) -> TransportFocus { - self.transport.focus - } - fn transport_focused (&self) -> bool { - self.focused() == ArrangerFocus::Transport - } -} - -impl ArrangerViewState for ArrangerTui { - fn arranger_focused (&self) -> bool { - self.focused() == ArrangerFocus::Arranger - } -} - -impl PhrasesViewState for PhrasesModel { - fn phrases_focused (&self) -> bool { - todo!() - } - fn phrases_entered (&self) -> bool { - todo!() - } - fn phrases (&self) -> Vec>> { - todo!() - } - fn phrase_index (&self) -> usize { - todo!() - } - fn phrase_mode (&self) -> &Option { - &self.mode - } -} - -impl PhrasesViewState for SequencerTui { - fn phrases_focused (&self) -> bool { - todo!() - } - fn phrases_entered (&self) -> bool { - todo!() - } - fn phrases (&self) -> Vec>> { - todo!() - } - fn phrase_index (&self) -> usize { - todo!() - } - fn phrase_mode (&self) -> &Option { - &self.phrases.mode - } -} - -impl PhrasesViewState for ArrangerTui { - fn phrases_focused (&self) -> bool { - todo!() - } - fn phrases_entered (&self) -> bool { - todo!() - } - fn phrases (&self) -> Vec>> { - todo!() - } - fn phrase_index (&self) -> usize { - todo!() - } - fn phrase_mode (&self) -> &Option { - &self.phrases.mode - } -} - -impl PhraseViewState for PhraseEditorModel { - fn phrase (&self) -> &Option>> { - &self.phrase - } - fn phrase_focused (&self) -> bool { - self.focused - } - fn phrase_editor_size (&self) -> &Measure { - todo!() - } - fn phrase_editor_entered (&self) -> bool { - self.entered - } - fn keys (&self) -> &Buffer { - &self.keys - } - fn buffer (&self) -> &BigBuffer { - &self.buffer - } - fn note_len (&self) -> usize { - self.note_len - } - fn note_axis (&self) -> &RwLock> { - &self.note_axis - } - fn time_axis (&self) -> &RwLock> { - &self.time_axis - } - fn now (&self) -> &Arc { - &self.now - } - fn size (&self) -> &Measure { - &self.size - } -} - -impl PhraseViewState for SequencerTui { - fn phrase (&self) -> &Option>> { - todo!() - } - fn phrase_focused (&self) -> bool { - todo!() - } - fn phrase_editor_size (&self) -> &Measure { - todo!() - } - fn phrase_editor_entered (&self) -> bool { - todo!() - } - fn keys (&self) -> &Buffer { - todo!() - } - fn buffer (&self) -> &BigBuffer { - todo!() - } - fn note_len (&self) -> usize { - todo!() - } - fn note_axis (&self) -> &RwLock> { - todo!() - } - fn time_axis (&self) -> &RwLock> { - todo!() - } - fn now (&self) -> &Arc { - todo!() - } - fn size (&self) -> &Measure { - &self.size - } -} - -impl PhraseViewState for ArrangerTui { - fn phrase (&self) -> &Option>> { - todo!() - } - fn phrase_focused (&self) -> bool { - todo!() - } - fn phrase_editor_size (&self) -> &Measure { - todo!() - } - fn phrase_editor_entered (&self) -> bool { - todo!() - } - fn keys (&self) -> &Buffer { - todo!() - } - fn buffer (&self) -> &BigBuffer { - todo!() - } - fn note_len (&self) -> usize { - todo!() - } - fn note_axis (&self) -> &RwLock> { - todo!() - } - fn time_axis (&self) -> &RwLock> { - todo!() - } - fn now (&self) -> &Arc { - todo!() - } - fn size (&self) -> &Measure { - &self.size +macro_rules! impl_phrase_view_state { + ($Struct:ident $(:: $field:ident)*) => { + impl PhraseViewState for $Struct { + fn phrase_editing (&self) -> &Option>> { + &self$(.$field)*.phrase + } + fn phrase_editor_focused (&self) -> bool { + self$(.$field)*.focus.is_focused() + } + fn phrase_editor_size (&self) -> &Measure { + todo!() + } + fn phrase_editor_entered (&self) -> bool { + self$(.$field)*.focus.is_entered() + } + fn keys (&self) -> &Buffer { + &self$(.$field)*.keys + } + fn buffer (&self) -> &BigBuffer { + &self$(.$field)*.buffer + } + fn note_len (&self) -> usize { + self$(.$field)*.note_len + } + fn note_axis (&self) -> &RwLock> { + &self$(.$field)*.note_axis + } + fn time_axis (&self) -> &RwLock> { + &self$(.$field)*.time_axis + } + fn now (&self) -> &Arc { + &self$(.$field)*.now + } + fn size (&self) -> &Measure { + &self$(.$field)*.size + } + } } } +impl_phrase_view_state!(PhraseEditorModel); +impl_phrase_view_state!(SequencerTui::editor); +impl_phrase_view_state!(ArrangerTui::editor); fn track_widths (tracks: &[ArrangerTrack]) -> Vec<(usize, usize)> { let mut widths = vec![];