From b8ac83b0191326e43eed39f6113a0dba58872a3d Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sun, 1 Sep 2024 20:29:15 +0300 Subject: [PATCH] refactor(transport): make widgets focusable --- Cargo.lock | 17 ---- Cargo.toml | 6 +- crates/tek_core/src/focus.rs | 23 ++++- crates/tek_sequencer/src/arranger.rs | 7 +- crates/tek_sequencer/src/arranger_main.rs | 18 +++- crates/tek_sequencer/src/transport.rs | 101 ++++++++++++++----- crates/tek_sequencer/src/transport_focus.rs | 1 - crates/tek_sequencer/src/transport_handle.rs | 98 +++++++++--------- crates/tek_sequencer/src/transport_render.rs | 65 +++++------- 9 files changed, 204 insertions(+), 132 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8d725cf3..43013a91 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1302,12 +1302,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "microxdg" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdcd3dc4ca0d1a9b1b80576639e93911a3e1db25a31ab6e6ba33027429adde5e" - [[package]] name = "midly" version = "0.5.3" @@ -2505,17 +2499,6 @@ version = "0.12.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4873307b7c257eddcb50c9bedf158eb669578359fb28428bef438fec8e6ba7c2" -[[package]] -name = "tek" -version = "0.1.0" -dependencies = [ - "clojure-reader", - "microxdg", - "tek_core", - "tek_mixer", - "tek_sequencer", -] - [[package]] name = "tek_core" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 21567f23..51f234d1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,3 +1,7 @@ [workspace] resolver = "2" -members = [ "crates/*" ] +members = [ + "crates/tek_core", + "crates/tek_mixer", + "crates/tek_sequencer", +] diff --git a/crates/tek_core/src/focus.rs b/crates/tek_core/src/focus.rs index 7b8e6492..3cbc7935 100644 --- a/crates/tek_core/src/focus.rs +++ b/crates/tek_core/src/focus.rs @@ -1,5 +1,6 @@ use crate::*; +/// A component that may contain [Focusable] components. pub trait Focus : Render + Handle { fn focus (&self) -> usize; fn focus_mut (&mut self) -> &mut usize; @@ -26,11 +27,27 @@ pub trait Focus : Render + Handle { } } +/// A component that may be focused. pub trait Focusable: Render + Handle { fn is_focused (&self) -> bool; fn set_focused (&mut self, focused: bool); } +impl Focusable for Option { + fn is_focused (&self) -> bool { + match self { + Some(focusable) => focusable.is_focused(), + None => false + } + } + fn set_focused (&mut self, focused: bool) { + if let Some(focusable) = self { + focusable.set_focused(focused) + } + } +} + +/// Implement the [Focus] trait for a component. #[macro_export] macro_rules! focus { ($struct:ident ($focus:ident) : $count:expr => [ $($focusable:ident),* @@ -47,7 +64,7 @@ pub trait Focusable: Render + Handle { $(&self.$focusable as &dyn Focusable,)* ] } - fn focusable_mut (&mut self) -> [&mut dyn Focusable;2] { + fn focusable_mut (&mut self) -> [&mut dyn Focusable;$count] { [ $(&mut self.$focusable as &mut dyn Focusable,)* ] @@ -56,7 +73,11 @@ pub trait Focusable: Render + Handle { } } +/// Implement the [Focusable] trait for a component. #[macro_export] macro_rules! focusable { + ($struct:ident) => { + focusable!($struct (focused)); + }; ($struct:ident ($focused:ident)) => { impl Focusable for $struct { fn is_focused (&self) -> bool { diff --git a/crates/tek_sequencer/src/arranger.rs b/crates/tek_sequencer/src/arranger.rs index 7244b7d8..1b4aba51 100644 --- a/crates/tek_sequencer/src/arranger.rs +++ b/crates/tek_sequencer/src/arranger.rs @@ -15,8 +15,12 @@ pub struct Arranger { pub mode: ArrangerViewMode, /// Slot for modal dialog displayed on top of app. pub modal: Option>, + /// Whether the arranger is currently focused + pub focused: bool } +focusable!(Arranger (focused)); + impl Arranger { pub fn new (name: &str) -> Self { Self { @@ -25,7 +29,8 @@ impl Arranger { selected: ArrangerFocus::Clip(0, 0), scenes: vec![], tracks: vec![], - modal: None + modal: None, + focused: false } } pub fn activate (&mut self) { diff --git a/crates/tek_sequencer/src/arranger_main.rs b/crates/tek_sequencer/src/arranger_main.rs index 6ba98373..2b4e5403 100644 --- a/crates/tek_sequencer/src/arranger_main.rs +++ b/crates/tek_sequencer/src/arranger_main.rs @@ -95,11 +95,25 @@ impl Render for ArrangerStandalone { impl Handle for ArrangerStandalone { fn handle (&mut self, e: &AppEvent) -> Usually { match e { - AppEvent::Input(Event::Key(KeyEvent { code: KeyCode::Tab, .. })) => { + AppEvent::Input(Event::Key(KeyEvent { + code: KeyCode::Char(' '), .. + })) => { + if let Some(ref mut transport) = self.transport { + transport.toggle_play()?; + Ok(true) + } else { + Ok(false) + } + }, + AppEvent::Input(Event::Key(KeyEvent { + code: KeyCode::Tab, .. + })) => { self.focus_next(); Ok(true) }, - AppEvent::Input(Event::Key(KeyEvent { code: KeyCode::BackTab, .. })) => { + AppEvent::Input(Event::Key(KeyEvent { + code: KeyCode::BackTab, .. + })) => { self.focus_prev(); Ok(true) }, diff --git a/crates/tek_sequencer/src/transport.rs b/crates/tek_sequencer/src/transport.rs index 78913bb5..badfdbf9 100644 --- a/crates/tek_sequencer/src/transport.rs +++ b/crates/tek_sequencer/src/transport.rs @@ -4,9 +4,6 @@ use crate::*; pub struct TransportToolbar { /// Enable metronome? pub metronome: bool, - pub focused: bool, - pub entered: bool, - pub selected: TransportFocus, /// Current sample rate, tempo, and PPQ. pub timebase: Arc, /// JACK client handle (needs to not be dropped for standalone mode to work). @@ -14,28 +11,66 @@ pub struct TransportToolbar { /// JACK transport handle. pub transport: Option, /// Quantization factor - pub quant: usize, - /// Global sync quant - pub sync: usize, - /// Current transport state - pub playing: Option, - /// Current position according to transport - pub playhead: usize, /// Global frame and usec at which playback started 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, } +focusable!(TransportToolbar); + +focus!(TransportToolbar (focus) : 5 => [ + playing, bpm, quant, sync, clock +]); process!(TransportToolbar |self, _client, scope| { self.update(&scope); Control::Continue }); +pub struct TransportPlayPauseButton { + pub value: Option, + pub focused: bool +} +focusable!(TransportPlayPauseButton); + +pub struct TransportBPM { + pub value: f64, + pub focused: bool +} +focusable!(TransportBPM); + +pub struct TransportQuantize { + pub value: usize, + pub focused: bool +} +focusable!(TransportQuantize); + +pub struct TransportSync { + pub value: usize, + pub focused: bool +} +focusable!(TransportSync); + +pub struct TransportClock { + pub frame: usize, + pub pulse: usize, + pub ppq: usize, + pub usecs: usize, + pub focused: bool, +} +focusable!(TransportClock); + impl TransportToolbar { pub fn standalone () -> Usually>> { let mut transport = Self::new(None); transport.focused = true; - transport.entered = true; let jack = JackClient::Inactive( Client::new("tek_transport", ClientOptions::NO_START_SERVER)?.0 ); @@ -55,24 +90,44 @@ impl TransportToolbar { pub fn new (transport: Option) -> Self { let timebase = Arc::new(Timebase::default()); Self { - selected: TransportFocus::BPM, metronome: false, focused: false, - entered: false, - playhead: 0, - playing: Some(TransportState::Stopped), + focus: 0, started: None, - quant: 24, - sync: timebase.ppq() as usize * 4, jack: None, transport, + + playing: TransportPlayPauseButton { + value: Some(TransportState::Stopped), + focused: true + }, + bpm: TransportBPM { + value: timebase.bpm(), + focused: false + }, + quant: TransportQuantize { + value: 24, + focused: false + }, + sync: TransportSync { + value: timebase.ppq() as usize * 4, + focused: false + }, + clock: TransportClock { + frame: 0, + pulse: 0, + ppq: 0, + usecs: 0, + focused: false + }, + timebase, } } pub fn toggle_play (&mut self) -> Usually<()> { let transport = self.transport.as_ref().unwrap(); - self.playing = match self.playing.expect("1st frame has not been processed yet") { + self.playing.value = match self.playing.value.expect("1st frame has not been processed yet") { TransportState::Stopped => { transport.start()?; Some(TransportState::Starting) @@ -95,9 +150,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.playhead = transport.pos.frame() as usize; + self.clock.frame = transport.pos.frame() as usize; let mut reset = false; - if self.playing != Some(transport.state) { + if self.playing.value != Some(transport.state) { match transport.state { TransportState::Rolling => { self.started = Some(( @@ -112,7 +167,7 @@ impl TransportToolbar { _ => {} } } - self.playing = Some(transport.state); + self.playing.value = Some(transport.state); ( reset, current_frames as usize, @@ -132,11 +187,11 @@ impl TransportToolbar { } pub fn pulse (&self) -> usize { - self.timebase.frame_to_pulse(self.playhead as f64) as usize + self.timebase.frame_to_pulse(self.clock.frame as f64) as usize } pub fn usecs (&self) -> usize { - self.timebase.frame_to_usec(self.playhead as f64) as usize + self.timebase.frame_to_usec(self.clock.frame as f64) as usize } } diff --git a/crates/tek_sequencer/src/transport_focus.rs b/crates/tek_sequencer/src/transport_focus.rs index c1f46355..3254eeb9 100644 --- a/crates/tek_sequencer/src/transport_focus.rs +++ b/crates/tek_sequencer/src/transport_focus.rs @@ -20,4 +20,3 @@ impl TransportFocus { } } } - diff --git a/crates/tek_sequencer/src/transport_handle.rs b/crates/tek_sequencer/src/transport_handle.rs index eab5f021..a7066720 100644 --- a/crates/tek_sequencer/src/transport_handle.rs +++ b/crates/tek_sequencer/src/transport_handle.rs @@ -1,51 +1,55 @@ use crate::*; -handle!{ - TransportToolbar |self, e| { - handle_keymap(self, e, KEYMAP_TRANSPORT) +handle!(TransportToolbar |self, e| { + match e { + AppEvent::Input(Event::Key(KeyEvent { code: KeyCode::Right, .. })) => { + self.focus_next(); + Ok(true) + }, + AppEvent::Input(Event::Key(KeyEvent { code: KeyCode::Left, .. })) => { + self.focus_prev(); + Ok(true) + }, + AppEvent::Input(Event::Key(KeyEvent { code: KeyCode::Char(' '), .. })) => { + self.toggle_play(); + Ok(true) + }, + _ => self.focused_mut().handle(e) } -} - -/// Key bindings for transport toolbar. -pub const KEYMAP_TRANSPORT: &'static [KeyBinding] = keymap!(TransportToolbar { - [Left, NONE, "transport_prev", "select previous control", |transport: &mut TransportToolbar| Ok({ - transport.selected.prev(); - true - })], - [Right, NONE, "transport_next", "select next control", |transport: &mut TransportToolbar| Ok({ - transport.selected.next(); - true - })], - [Char('.'), NONE, "transport_increment", "increment value at cursor", |transport: &mut TransportToolbar| { - match transport.selected { - TransportFocus::BPM => { - transport.timebase.bpm.fetch_add(1.0, Ordering::Relaxed); - }, - TransportFocus::Quant => { - transport.quant = next_note_length(transport.quant) - }, - TransportFocus::Sync => { - transport.sync = next_note_length(transport.sync) - }, - }; - Ok(true) - }], - [Char(','), NONE, "transport_decrement", "decrement value at cursor", |transport: &mut TransportToolbar| { - match transport.selected { - TransportFocus::BPM => { - transport.timebase.bpm.fetch_sub(1.0, Ordering::Relaxed); - }, - TransportFocus::Quant => { - transport.quant = prev_note_length(transport.quant); - }, - TransportFocus::Sync => { - transport.sync = prev_note_length(transport.sync); - }, - }; - Ok(true) - }], - [Char(' '), NONE, "transport_play_toggle", "play or pause", |transport: &mut TransportToolbar| { - transport.toggle_play()?; - Ok(true) - }], +}); + +handle!(TransportPlayPauseButton |self, _e| Ok(false)); + +handle!(TransportBPM |self, _e| { + //TransportFocus::BPM => { + //transport.timebase.bpm.fetch_add(1.0, Ordering::Relaxed); + //}, + //TransportFocus::BPM => { + //transport.timebase.bpm.fetch_sub(1.0, Ordering::Relaxed); + //}, + Ok(false) +}); + +handle!(TransportQuantize |self, _e| { + //TransportFocus::Quant => { + //transport.quant.value = next_note_length(transport.quant) + //}, + //TransportFocus::Quant => { + //transport.quant.value = prev_note_length(transport.quant); + //}, + Ok(false) +}); + +handle!(TransportSync |self, _e| { + //TransportFocus::Sync => { + //transport.sync.value = next_note_length(transport.sync) + //}, + //TransportFocus::Sync => { + //transport.sync.value = prev_note_length(transport.sync); + //}, + Ok(false) +}); + +handle!(TransportClock |self, _e| { + Ok(false) }); diff --git a/crates/tek_sequencer/src/transport_render.rs b/crates/tek_sequencer/src/transport_render.rs index ce9a025e..cbd247a0 100644 --- a/crates/tek_sequencer/src/transport_render.rs +++ b/crates/tek_sequencer/src/transport_render.rs @@ -5,92 +5,79 @@ const CORNERS: Corners = Corners(NOT_DIM_GREEN); render!(TransportToolbar |self, buf, area| { let mut area = area; area.height = 2; - let Self { quant, sync, focused, entered, .. } = self; - fill_bg(buf, area, Nord::bg_lo(*focused, *entered)); - let active = self.focused && self.entered; + fill_bg(buf, area, Nord::BG0); Split::right() - .add(TransportPlayPauseButton(self.playing)) - .add(TransportBPM(self.bpm(), active && self.selected == TransportFocus::BPM)) - .add(TransportQuantize(*quant, active && self.selected == TransportFocus::Quant)) - .add(TransportSync(*sync, active && self.selected == TransportFocus::Sync)) - .add(TransportClock(self.pulse(), self.ppq(), self.usecs())) + .add_ref(&self.playing) + .add_ref(&self.bpm) + .add_ref(&self.quant) + .add_ref(&self.sync) + .add_ref(&self.clock) .render(buf, area) }); -#[derive(Copy, Clone)] -struct TransportPlayPauseButton(Option); - render!(TransportPlayPauseButton |self, buf, area| { let Rect { x, y, .. } = area; - let style = Some(match self.0 { + let Self { value, focused } = self; + let style = Some(match value { Some(TransportState::Stopped) => GRAY_DIM.bold(), Some(TransportState::Starting) => GRAY_NOT_DIM_BOLD, Some(TransportState::Rolling) => WHITE_NOT_DIM_BOLD, _ => unreachable!(), }); - let label = match self.0 { + let label = match value { Some(TransportState::Rolling) => "▶ PLAYING", Some(TransportState::Starting) => "READY ...", Some(TransportState::Stopped) => "⏹ STOPPED", _ => unreachable!(), }; - let mut result = label.blit(buf, x + 1, y, style)?; - result.width = result.width + 1; - Ok(result) + let mut area = label.blit(buf, x + 1, y, style)?; + area.width = area.width + 1; + if *focused { + CORNERS.draw(buf, Rect { x: area.x - 1, ..area })?; + } + Ok(area) }); -#[derive(Copy, Clone)] -struct TransportBPM(usize, bool); - render!(TransportBPM |self, buf, area| { let Rect { x, y, .. } = area; - let Self(bpm, highlight) = self; + let Self { value, focused } = self; "BPM".blit(buf, x, y, Some(NOT_DIM))?; - let width = format!("{}.{:03}", bpm, bpm * 1000 % 1000).blit(buf, x, y + 1, Some(NOT_DIM_BOLD))?.width; + let width = format!("{}.{:03}", value, (value * 1000.0) % 1000.0).blit(buf, x, y + 1, Some(NOT_DIM_BOLD))?.width; let area = Rect { x, y, width: (width + 2).max(10), height: 2 }; - if *highlight { + if *focused { CORNERS.draw(buf, Rect { x: area.x - 1, ..area })?; } Ok(area) }); -#[derive(Copy, Clone)] -struct TransportQuantize(usize, bool); - render!(TransportQuantize |self, buf, area| { let Rect { x, y, .. } = area; - let Self(quant, highlight) = self; + let Self { value, focused } = self; "QUANT".blit(buf, x, y, Some(NOT_DIM))?; - let width = ppq_to_name(*quant as usize).blit(buf, x, y + 1, Some(NOT_DIM_BOLD))?.width; + let width = ppq_to_name(*value as usize).blit(buf, x, y + 1, Some(NOT_DIM_BOLD))?.width; let area = Rect { x, y, width: (width + 2).max(10), height: 2 }; - if *highlight { + if *focused { CORNERS.draw(buf, Rect { x: area.x - 1, ..area })?; } Ok(area) }); -#[derive(Copy, Clone)] -struct TransportSync(usize, bool); - render!(TransportSync |self, buf, area| { let Rect { x, y, .. } = area; - let Self(sync, highlight) = self; + let Self { value, focused } = self; "SYNC".blit(buf, x, y, Some(NOT_DIM))?; - let width = ppq_to_name(*sync as usize).blit(buf, x, y + 1, Some(NOT_DIM_BOLD))?.width; + let width = ppq_to_name(*value as usize).blit(buf, x, y + 1, Some(NOT_DIM_BOLD))?.width; let area = Rect { x, y, width: (width + 2).max(10), height: 2 }; - if *highlight { + if *focused { CORNERS.draw(buf, Rect { x: area.x - 1, ..area })?; } Ok(area) }); -#[derive(Copy, Clone)] -struct TransportClock(usize, usize, usize); - render!(TransportClock |self, buf, area| { let Rect { x, y, width, .. } = area; - let Self(pulse, ppq, usecs) = self; - let (beats, pulses) = (pulse / ppq, pulse % ppq); + let Self { frame, pulse, ppq, usecs, focused } = 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);