refactor(transport): make widgets focusable

This commit is contained in:
🪞👃🪞 2024-09-01 20:29:15 +03:00
parent 2106a7c044
commit b8ac83b019
9 changed files with 204 additions and 132 deletions

17
Cargo.lock generated
View file

@ -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"

View file

@ -1,3 +1,7 @@
[workspace]
resolver = "2"
members = [ "crates/*" ]
members = [
"crates/tek_core",
"crates/tek_mixer",
"crates/tek_sequencer",
]

View file

@ -1,5 +1,6 @@
use crate::*;
/// A component that may contain [Focusable] components.
pub trait Focus <const N: usize>: Render + Handle {
fn focus (&self) -> usize;
fn focus_mut (&mut self) -> &mut usize;
@ -26,11 +27,27 @@ pub trait Focus <const N: usize>: 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<T: Focusable> Focusable for Option<T> {
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 {

View file

@ -15,8 +15,12 @@ pub struct Arranger {
pub mode: ArrangerViewMode,
/// Slot for modal dialog displayed on top of app.
pub modal: Option<Box<dyn ExitableComponent>>,
/// 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) {

View file

@ -95,11 +95,25 @@ impl Render for ArrangerStandalone {
impl Handle for ArrangerStandalone {
fn handle (&mut self, e: &AppEvent) -> Usually<bool> {
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)
},

View file

@ -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<Timebase>,
/// 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<Transport>,
/// Quantization factor
pub quant: usize,
/// Global sync quant
pub sync: usize,
/// Current transport state
pub playing: Option<TransportState>,
/// 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<TransportState>,
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<Arc<RwLock<Self>>> {
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<Transport>) -> 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
}
}

View file

@ -20,4 +20,3 @@ impl TransportFocus {
}
}
}

View file

@ -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<TransportToolbar>] = 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)
});

View file

@ -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<TransportState>);
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);