From 0574eb79ef9d0dd3dcc5beaa1ef3933e31f44172 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Tue, 1 Oct 2024 05:26:21 +0300 Subject: [PATCH] fold single-use components into TransportToolbar --- crates/tek_core/src/engine.rs | 36 +- crates/tek_sequencer/src/sequencer.rs | 457 +++++++++----------------- 2 files changed, 180 insertions(+), 313 deletions(-) diff --git a/crates/tek_core/src/engine.rs b/crates/tek_core/src/engine.rs index fda5b15e..7db9dc01 100644 --- a/crates/tek_core/src/engine.rs +++ b/crates/tek_core/src/engine.rs @@ -118,24 +118,6 @@ impl> Widget for Option { self.as_ref().map(|widget|widget.render(to)).unwrap_or(Ok(())) } } -/// A [Widget] that contains other [Widget]s -pub trait Content: Send + Sync { - type Engine: Engine; - fn content (&self) -> impl Widget::Engine>; -} -/// Every struct that has [Content] is a renderable [Widget]. -impl> Widget for W { - type Engine = E; - fn layout (&self, to: E::Size) -> Perhaps { - self.content().layout(to) - } - fn render (&self, to: &mut E::Output) -> Usually<()> { - match self.layout(to.area().wh().into())? { - Some(wh) => to.render_in(to.area().clip(wh).into(), &self.content()), - None => Ok(()) - } - } -} /// A custom [Widget] defined by passing layout and render closures in place. pub struct CustomWidget< E: Engine, @@ -164,6 +146,24 @@ impl< self.1(to) } } +/// A [Widget] that contains other [Widget]s +pub trait Content: Send + Sync { + type Engine: Engine; + fn content (&self) -> impl Widget::Engine>; +} +/// Every struct that has [Content] is a renderable [Widget]. +impl> Widget for W { + type Engine = E; + fn layout (&self, to: E::Size) -> Perhaps { + self.content().layout(to) + } + fn render (&self, to: &mut E::Output) -> Usually<()> { + match self.layout(to.area().wh().into())? { + Some(wh) => to.render_in(to.area().clip(wh).into(), &self.content()), + None => Ok(()) + } + } +} /// Handle input pub trait Handle: Send + Sync { fn handle (&mut self, context: &E::Input) -> Perhaps; diff --git a/crates/tek_sequencer/src/sequencer.rs b/crates/tek_sequencer/src/sequencer.rs index 05be3a22..3afe181b 100644 --- a/crates/tek_sequencer/src/sequencer.rs +++ b/crates/tek_sequencer/src/sequencer.rs @@ -1447,12 +1447,15 @@ impl Content for Sequencer { type Engine = Tui; fn content (&self) -> impl Widget { let toolbar = col!( - col! { "Name", self.name.read().unwrap().as_str(), }.min_xy(10, 4), - col! { "Start: ", " 1.1.1", "End: ", " 2.1.1", }.min_xy(10, 6), - col! { "Loop [ ]", "From: ", " 1.1.1", "Length: ", " 1.0.0", }.min_xy(10, 7), + col! { "Name", self.name.read().unwrap().as_str(), }, + "", + col! { "Start: ", " 1.1.1", "End: ", " 2.1.1", }, + "", + col! { "Loop [ ]", "From: ", " 1.1.1", "Length: ", " 1.0.0", }, + "", col! { "Notes: ", "C#0-C#9 ", "[ /2 ]", "[ x2 ]" - , "[ Rev ]", "[ Inv ]", "[ Dup ]" }.min_xy(10, 9), - ); + , "[ Rev ]", "[ Inv ]", "[ Dup ]" }, + ).min_x(10); let content = lay!( // keys CustomWidget::new(|_|Ok(Some([32,4])), |to: &mut TuiOutput|{ @@ -1815,6 +1818,8 @@ impl Phrase { /// Stores and displays time-related state. pub struct TransportToolbar { + _engine: PhantomData, + /// Enable metronome? pub metronome: bool, /// Current sample rate, tempo, and PPQ. @@ -1828,23 +1833,24 @@ pub struct TransportToolbar { pub started: Option<(usize, usize)>, pub focused: bool, - pub focus: usize, - pub playing: TransportPlayPauseButton, - pub bpm: TransportBPM, - pub quant: TransportQuantize, - pub sync: TransportSync, - pub clock: TransportClock, + pub focus: TransportToolbarFocus, + pub playing: Option, + pub bpm: f64, + pub quant: usize, + pub sync: usize, + pub frame: usize, + pub pulse: usize, + pub ppq: usize, + pub usecs: usize, } impl TransportToolbar { pub fn standalone () -> Usually>> where Self: 'static { - let mut transport = Self::new(None); - transport.focused = true; let jack = JackClient::Inactive( Client::new("tek_transport", ClientOptions::NO_START_SERVER)?.0 ); - transport.transport = Some(jack.transport()); - transport.playing.transport = Some(jack.transport()); + let mut transport = Self::new(Some(jack.transport())); + transport.focused = true; let transport = Arc::new(RwLock::new(transport)); transport.write().unwrap().jack = Some( jack.activate( @@ -1859,39 +1865,17 @@ impl TransportToolbar { pub fn new (transport: Option) -> Self { let timebase = Arc::new(Timebase::default()); Self { + _engine: Default::default(), focused: false, - focus: 0, - - playing: TransportPlayPauseButton { - _engine: Default::default(), - transport: None, - value: Some(TransportState::Stopped), - focused: true - }, - bpm: TransportBPM { - _engine: Default::default(), - value: timebase.bpm(), - focused: false - }, - quant: TransportQuantize { - _engine: Default::default(), - value: 24, - focused: false - }, - sync: TransportSync { - _engine: Default::default(), - value: timebase.ppq() as usize * 4, - focused: false - }, - clock: TransportClock { - _engine: Default::default(), - frame: 0, - pulse: 0, - ppq: 0, - usecs: 0, - focused: false - }, - + focus: TransportToolbarFocus::PlayPause, + playing: Some(TransportState::Stopped), + bpm: timebase.bpm(), + quant: 24, + sync: timebase.ppq() as usize * 4, + frame: 0, + pulse: 0, + ppq: 0, + usecs: 0, transport, timebase, metronome: false, @@ -1900,7 +1884,18 @@ impl TransportToolbar { } } pub fn toggle_play (&mut self) -> Usually<()> { - self.playing.toggle()?; + let transport = self.transport.as_ref().unwrap(); + self.playing = match self.playing.expect("1st frame has not been processed yet") { + TransportState::Stopped => { + transport.start()?; + Some(TransportState::Starting) + }, + _ => { + transport.stop()?; + transport.locate(0)?; + Some(TransportState::Stopped) + }, + }; Ok(()) } pub fn update (&mut self, scope: &ProcessScope) -> (bool, usize, usize, usize, usize, f64) { @@ -1912,9 +1907,9 @@ impl TransportToolbar { } = scope.cycle_times().unwrap(); let chunk_size = scope.n_frames() as usize; let transport = self.transport.as_ref().unwrap().query().unwrap(); - self.clock.frame = transport.pos.frame() as usize; + self.frame = transport.pos.frame() as usize; let mut reset = false; - if self.playing.value != Some(transport.state) { + if self.playing != Some(transport.state) { match transport.state { TransportState::Rolling => { self.started = Some(( @@ -1929,7 +1924,7 @@ impl TransportToolbar { _ => {} } } - self.playing.value = Some(transport.state); + self.playing = Some(transport.state); ( reset, current_frames as usize, @@ -1942,20 +1937,56 @@ impl TransportToolbar { pub fn bpm (&self) -> usize { self.timebase.bpm() as usize } + fn handle_bpm (&mut self, from: &TuiInput) -> Perhaps { + match from.event() { + key!(KeyCode::Char(',')) => { self.bpm -= 1.0; }, + key!(KeyCode::Char('.')) => { self.bpm += 1.0; }, + key!(KeyCode::Char('<')) => { self.bpm -= 0.001; }, + key!(KeyCode::Char('>')) => { self.bpm += 0.001; }, + _ => return Ok(None) + } + Ok(Some(true)) + } pub fn ppq (&self) -> usize { self.timebase.ppq() as usize } pub fn pulse (&self) -> usize { - self.timebase.frame_to_pulse(self.clock.frame as f64) as usize + self.timebase.frame_to_pulse(self.frame as f64) as usize } pub fn usecs (&self) -> usize { - self.timebase.frame_to_usec(self.clock.frame as f64) as usize + self.timebase.frame_to_usec(self.frame as f64) as usize } pub fn quant (&self) -> usize { - self.quant.value + self.quant + } + fn handle_quant (&mut self, from: &TuiInput) -> Perhaps { + match from.event() { + key!(KeyCode::Char(',')) => { + self.quant = prev_note_length(self.quant); + Ok(Some(true)) + }, + key!(KeyCode::Char('.')) => { + self.quant = next_note_length(self.quant); + Ok(Some(true)) + }, + _ => Ok(None) + } } pub fn sync (&self) -> usize { - self.sync.value + self.sync + } + fn handle_sync (&mut self, from: &TuiInput) -> Perhaps { + match from.event() { + key!(KeyCode::Char(',')) => { + self.sync = prev_note_length(self.sync); + Ok(Some(true)) + }, + key!(KeyCode::Char('.')) => { + self.sync = next_note_length(self.sync); + Ok(Some(true)) + }, + _ => Ok(None) + } } } @@ -1969,9 +2000,15 @@ impl Audio for TransportToolbar { impl Handle for TransportToolbar { fn handle (&mut self, from: &TuiInput) -> Perhaps { match from.event() { - key!(KeyCode::Left) => { self.focus_prev(); }, - key!(KeyCode::Right) => { self.focus_next(); }, - _ => return self.focused_mut().handle(from) + key!(KeyCode::Left) => { self.focus.prev(); }, + key!(KeyCode::Right) => { self.focus.next(); }, + _ => match self.focus { + TransportToolbarFocus::PlayPause => self.toggle_play().map(|_|())?, + TransportToolbarFocus::Bpm => self.handle_bpm(from).map(|_|())?, + TransportToolbarFocus::Quant => self.handle_quant(from).map(|_|())?, + TransportToolbarFocus::Sync => self.handle_sync(from).map(|_|())?, + TransportToolbarFocus::Clock => {/*todo*/}, + } } Ok(Some(true)) } @@ -1980,32 +2017,49 @@ impl Handle for TransportToolbar { impl Content for TransportToolbar { type Engine = Tui; fn content (&self) -> impl Widget { - let focus_wrap = |focused, component|Layers::new(move |add|{ - if focused { add(&CORNERS)?; add(&Background(Color::Rgb(60, 70, 50)))?; } - add(component) - }); row! { - focus_wrap(self.focused && self.playing.focused, &self.playing), - focus_wrap(self.focused && self.bpm.focused, &self.bpm), - focus_wrap(self.focused && self.quant.focused, &self.quant), - focus_wrap(self.focused && self.sync.focused, &self.sync), - focus_wrap(self.focused && self.clock.focused, &self.clock), - }.fill_x().bg(Color::Rgb(40, 50, 30)) - } -} -impl Focus<5, Tui> for TransportToolbar { - fn focus (&self) -> usize { - self.focus - } - fn focus_mut (&mut self) -> &mut usize { - &mut self.focus - } - fn focusable (&self) -> [&dyn Focusable;5] { - focusables!(self.playing, self.bpm, self.quant, self.sync, self.clock) - } - fn focusable_mut (&mut self) -> [&mut dyn Focusable;5] { - focusables_mut!(self.playing, self.bpm, self.quant, self.sync, self.clock) + self.focus.wrap(self.focused, TransportToolbarFocus::PlayPause, &Styled( + match self.playing { + Some(TransportState::Stopped) => Some(GRAY_DIM.bold()), + Some(TransportState::Starting) => Some(GRAY_NOT_DIM_BOLD), + Some(TransportState::Rolling) => Some(WHITE_NOT_DIM_BOLD), + _ => unreachable!(), + }, + match self.playing { + Some(TransportState::Rolling) => "▶ PLAYING", + Some(TransportState::Starting) => "READY ...", + Some(TransportState::Stopped) => "⏹ STOPPED", + _ => unreachable!(), + } + ).min_xy(11, 2).push_x(1)), + + self.focus.wrap(self.focused, TransportToolbarFocus::Bpm, &Outset::X(1u16, col! { + "BPM", + format!("{}.{:03}", self.bpm as usize, (self.bpm * 1000.0) % 1000.0).as_str() + })), + + self.focus.wrap(self.focused, TransportToolbarFocus::Quant, &Outset::X(1u16, col! { + "QUANT", ppq_to_name(self.quant as usize) + })), + + self.focus.wrap(self.focused, TransportToolbarFocus::Sync, &Outset::X(1u16, col! { + "SYNC", ppq_to_name(self.sync as usize) + })), + + self.focus.wrap(self.focused, TransportToolbarFocus::Clock, &{ + let Self { frame: _frame, pulse, ppq, usecs, .. } = self; + let (beats, pulses) = if *ppq > 0 { (pulse / ppq, pulse % ppq) } else { (0, 0) }; + let (bars, beats) = ((beats / 4) + 1, (beats % 4) + 1); + let (seconds, msecs) = (usecs / 1000000, usecs / 1000 % 1000); + let (minutes, seconds) = (seconds / 60, seconds % 60); + Outset::X(1u16, col! { + format!("{bars}.{beats}.{pulses:02}").as_str(), + format!("{minutes}:{seconds:02}:{msecs:03}").as_str(), + }) + }), + + }.fill_x().bg(Color::Rgb(40, 50, 30)) } } @@ -2018,223 +2072,36 @@ impl Focusable for TransportToolbar { } } -/////////////////////////////////////////////////////////////////////////////////////////////////// - -pub struct TransportPlayPauseButton { - pub _engine: PhantomData, - pub transport: Option, - pub value: Option, - pub focused: bool -} -impl TransportPlayPauseButton { - fn toggle (&mut self) -> Usually<()> { - let transport = self.transport.as_ref().unwrap(); - self.value = match self.value.expect("1st frame has not been processed yet") { - TransportState::Stopped => { - transport.start()?; - Some(TransportState::Starting) - }, - _ => { - transport.stop()?; - transport.locate(0)?; - Some(TransportState::Stopped) - }, - }; - Ok(()) - } -} -impl Focusable for TransportPlayPauseButton { - fn is_focused (&self) -> bool { - self.focused - } - fn set_focused (&mut self, focused: bool) { - self.focused = focused - } -} -impl Handle for TransportPlayPauseButton { - fn handle (&mut self, from: &TuiInput) -> Perhaps { - match from.event() { - key!(KeyCode::Enter) => self.toggle().map(|_|Some(true)), - _ => Ok(None) - } - } -} -impl Content for TransportPlayPauseButton { - type Engine = Tui; - fn content (&self) -> impl Widget { - Layers::new(|add|{ - add(&Styled(match self.value { - Some(TransportState::Stopped) => Some(GRAY_DIM.bold()), - Some(TransportState::Starting) => Some(GRAY_NOT_DIM_BOLD), - Some(TransportState::Rolling) => Some(WHITE_NOT_DIM_BOLD), - _ => unreachable!(), - }, match self.value { - Some(TransportState::Rolling) => "▶ PLAYING", - Some(TransportState::Starting) => "READY ...", - Some(TransportState::Stopped) => "⏹ STOPPED", - _ => unreachable!(), - }).min_xy(11, 2).push_x(1))?; - Ok(()) +#[derive(Clone, Copy, PartialEq)] +enum TransportToolbarFocus { PlayPause, Bpm, Quant, Sync, Clock, } +impl TransportToolbarFocus { + pub fn wrap <'a, W: Widget> ( + self, parent_focus: bool, focus: Self, widget: &'a W + ) -> impl Widget + 'a { + Layers::new(move |add|{ + if parent_focus && focus == self { + add(&CORNERS)?; + add(&Background(Color::Rgb(60, 70, 50)))?; + } + add(widget) }) } -} - -/////////////////////////////////////////////////////////////////////////////////////////////////// - -pub struct TransportBPM { - pub _engine: PhantomData, - pub value: f64, - pub focused: bool -} -impl Focusable for TransportBPM { - fn is_focused (&self) -> bool { - self.focused - } - fn set_focused (&mut self, focused: bool) { - self.focused = focused - } -} -impl Handle for TransportBPM { - fn handle (&mut self, from: &TuiInput) -> Perhaps { - match from.event() { - key!(KeyCode::Char(',')) => { self.value -= 1.0; }, - key!(KeyCode::Char('.')) => { self.value += 1.0; }, - key!(KeyCode::Char('<')) => { self.value -= 0.001; }, - key!(KeyCode::Char('>')) => { self.value += 0.001; }, - _ => return Ok(None) + fn next (&mut self) { + *self = match self { + Self::PlayPause => Self::Bpm, + Self::Bpm => Self::Quant, + Self::Quant => Self::Sync, + Self::Sync => Self::Clock, + Self::Clock => Self::PlayPause, } - Ok(Some(true)) } -} -impl Content for TransportBPM { - type Engine = Tui; - fn content (&self) -> impl Widget { - let Self { value, .. } = self; - Outset::X(1u16, Stack::down(move |add|{ - add(&"BPM")?; - add(&format!("{}.{:03}", *value as usize, (value * 1000.0) % 1000.0).as_str()) - })) - } -} - -/////////////////////////////////////////////////////////////////////////////////////////////////// - -pub struct TransportQuantize { - pub _engine: PhantomData, - pub value: usize, - pub focused: bool -} -impl Focusable for TransportQuantize { - fn is_focused (&self) -> bool { - self.focused - } - fn set_focused (&mut self, focused: bool) { - self.focused = focused - } -} -impl Handle for TransportQuantize { - fn handle (&mut self, from: &TuiInput) -> Perhaps { - match from.event() { - key!(KeyCode::Char(',')) => { - self.value = prev_note_length(self.value); - Ok(Some(true)) - }, - key!(KeyCode::Char('.')) => { - self.value = next_note_length(self.value); - Ok(Some(true)) - }, - _ => Ok(None) + fn prev (&mut self) { + *self = match self { + Self::PlayPause => Self::Clock, + Self::Bpm => Self::PlayPause, + Self::Quant => Self::Bpm, + Self::Sync => Self::Quant, + Self::Clock => Self::Sync, } } } -impl Content for TransportQuantize { - type Engine = Tui; - fn content (&self) -> impl Widget { - let Self { value, .. } = self; - Outset::X(1u16, Stack::down(|add|{ - add(&"QUANT")?; - add(&ppq_to_name(*value as usize)) - })) - } -} - -/////////////////////////////////////////////////////////////////////////////////////////////////// - -pub struct TransportSync { - pub _engine: PhantomData, - pub value: usize, - pub focused: bool -} -impl Focusable for TransportSync { - fn is_focused (&self) -> bool { - self.focused - } - fn set_focused (&mut self, focused: bool) { - self.focused = focused - } -} -impl Handle for TransportSync { - fn handle (&mut self, from: &TuiInput) -> Perhaps { - match from.event() { - key!(KeyCode::Char(',')) => { - self.value = prev_note_length(self.value); - Ok(Some(true)) - }, - key!(KeyCode::Char('.')) => { - self.value = next_note_length(self.value); - Ok(Some(true)) - }, - _ => Ok(None) - } - } -} -impl Content for TransportSync { - type Engine = Tui; - fn content (&self) -> impl Widget { - let Self { value, .. } = self; - Outset::X(1u16, Stack::down(|add|{ - add(&"SYNC")?; - add(&ppq_to_name(*value as usize)) - })) - } -} - -/////////////////////////////////////////////////////////////////////////////////////////////////// - -pub struct TransportClock { - pub _engine: PhantomData, - pub frame: usize, - pub pulse: usize, - pub ppq: usize, - pub usecs: usize, - pub focused: bool, -} -impl Focusable for TransportClock { - fn is_focused (&self) -> bool { - self.focused - } - fn set_focused (&mut self, focused: bool) { - self.focused = focused - } -} -impl Handle for TransportClock { - fn handle (&mut self, _: &TuiInput) -> Perhaps { - Ok(None) - } -} -impl Content for TransportClock { - type Engine = Tui; - fn content (&self) -> impl Widget { - let Self { frame: _frame, pulse, ppq, usecs, .. } = self; - Layers::new(move|add|{ - let (beats, pulses) = if *ppq > 0 { (pulse / ppq, pulse % ppq) } else { (0, 0) }; - let (bars, beats) = ((beats / 4) + 1, (beats % 4) + 1); - let (seconds, msecs) = (usecs / 1000000, usecs / 1000 % 1000); - let (minutes, seconds) = (seconds / 60, seconds % 60); - add(&Outset::X(1u16, Stack::down(|add|{ - add(&format!("{bars}.{beats}.{pulses:02}").as_str())?; - add(&format!("{minutes}:{seconds:02}:{msecs:03}").as_str()) - }))) - }) - } -}