mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-08 20:56:43 +01:00
refactor(transport): make widgets focusable
This commit is contained in:
parent
2106a7c044
commit
b8ac83b019
9 changed files with 204 additions and 132 deletions
17
Cargo.lock
generated
17
Cargo.lock
generated
|
|
@ -1302,12 +1302,6 @@ dependencies = [
|
||||||
"autocfg",
|
"autocfg",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "microxdg"
|
|
||||||
version = "0.1.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "fdcd3dc4ca0d1a9b1b80576639e93911a3e1db25a31ab6e6ba33027429adde5e"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "midly"
|
name = "midly"
|
||||||
version = "0.5.3"
|
version = "0.5.3"
|
||||||
|
|
@ -2505,17 +2499,6 @@ version = "0.12.15"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4873307b7c257eddcb50c9bedf158eb669578359fb28428bef438fec8e6ba7c2"
|
checksum = "4873307b7c257eddcb50c9bedf158eb669578359fb28428bef438fec8e6ba7c2"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tek"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"clojure-reader",
|
|
||||||
"microxdg",
|
|
||||||
"tek_core",
|
|
||||||
"tek_mixer",
|
|
||||||
"tek_sequencer",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tek_core"
|
name = "tek_core"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,7 @@
|
||||||
[workspace]
|
[workspace]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
members = [ "crates/*" ]
|
members = [
|
||||||
|
"crates/tek_core",
|
||||||
|
"crates/tek_mixer",
|
||||||
|
"crates/tek_sequencer",
|
||||||
|
]
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
|
/// A component that may contain [Focusable] components.
|
||||||
pub trait Focus <const N: usize>: Render + Handle {
|
pub trait Focus <const N: usize>: Render + Handle {
|
||||||
fn focus (&self) -> usize;
|
fn focus (&self) -> usize;
|
||||||
fn focus_mut (&mut self) -> &mut 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 {
|
pub trait Focusable: Render + Handle {
|
||||||
fn is_focused (&self) -> bool;
|
fn is_focused (&self) -> bool;
|
||||||
fn set_focused (&mut self, focused: 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 {
|
#[macro_export] macro_rules! focus {
|
||||||
($struct:ident ($focus:ident) : $count:expr => [
|
($struct:ident ($focus:ident) : $count:expr => [
|
||||||
$($focusable:ident),*
|
$($focusable:ident),*
|
||||||
|
|
@ -47,7 +64,7 @@ pub trait Focusable: Render + Handle {
|
||||||
$(&self.$focusable as &dyn Focusable,)*
|
$(&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,)*
|
$(&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 {
|
#[macro_export] macro_rules! focusable {
|
||||||
|
($struct:ident) => {
|
||||||
|
focusable!($struct (focused));
|
||||||
|
};
|
||||||
($struct:ident ($focused:ident)) => {
|
($struct:ident ($focused:ident)) => {
|
||||||
impl Focusable for $struct {
|
impl Focusable for $struct {
|
||||||
fn is_focused (&self) -> bool {
|
fn is_focused (&self) -> bool {
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,12 @@ pub struct Arranger {
|
||||||
pub mode: ArrangerViewMode,
|
pub mode: ArrangerViewMode,
|
||||||
/// Slot for modal dialog displayed on top of app.
|
/// Slot for modal dialog displayed on top of app.
|
||||||
pub modal: Option<Box<dyn ExitableComponent>>,
|
pub modal: Option<Box<dyn ExitableComponent>>,
|
||||||
|
/// Whether the arranger is currently focused
|
||||||
|
pub focused: bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
focusable!(Arranger (focused));
|
||||||
|
|
||||||
impl Arranger {
|
impl Arranger {
|
||||||
pub fn new (name: &str) -> Self {
|
pub fn new (name: &str) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
|
@ -25,7 +29,8 @@ impl Arranger {
|
||||||
selected: ArrangerFocus::Clip(0, 0),
|
selected: ArrangerFocus::Clip(0, 0),
|
||||||
scenes: vec![],
|
scenes: vec![],
|
||||||
tracks: vec![],
|
tracks: vec![],
|
||||||
modal: None
|
modal: None,
|
||||||
|
focused: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn activate (&mut self) {
|
pub fn activate (&mut self) {
|
||||||
|
|
|
||||||
|
|
@ -95,11 +95,25 @@ impl Render for ArrangerStandalone {
|
||||||
impl Handle for ArrangerStandalone {
|
impl Handle for ArrangerStandalone {
|
||||||
fn handle (&mut self, e: &AppEvent) -> Usually<bool> {
|
fn handle (&mut self, e: &AppEvent) -> Usually<bool> {
|
||||||
match e {
|
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();
|
self.focus_next();
|
||||||
Ok(true)
|
Ok(true)
|
||||||
},
|
},
|
||||||
AppEvent::Input(Event::Key(KeyEvent { code: KeyCode::BackTab, .. })) => {
|
AppEvent::Input(Event::Key(KeyEvent {
|
||||||
|
code: KeyCode::BackTab, ..
|
||||||
|
})) => {
|
||||||
self.focus_prev();
|
self.focus_prev();
|
||||||
Ok(true)
|
Ok(true)
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,6 @@ use crate::*;
|
||||||
pub struct TransportToolbar {
|
pub struct TransportToolbar {
|
||||||
/// Enable metronome?
|
/// Enable metronome?
|
||||||
pub metronome: bool,
|
pub metronome: bool,
|
||||||
pub focused: bool,
|
|
||||||
pub entered: bool,
|
|
||||||
pub selected: TransportFocus,
|
|
||||||
/// Current sample rate, tempo, and PPQ.
|
/// Current sample rate, tempo, and PPQ.
|
||||||
pub timebase: Arc<Timebase>,
|
pub timebase: Arc<Timebase>,
|
||||||
/// JACK client handle (needs to not be dropped for standalone mode to work).
|
/// JACK client handle (needs to not be dropped for standalone mode to work).
|
||||||
|
|
@ -14,28 +11,66 @@ pub struct TransportToolbar {
|
||||||
/// JACK transport handle.
|
/// JACK transport handle.
|
||||||
pub transport: Option<Transport>,
|
pub transport: Option<Transport>,
|
||||||
/// Quantization factor
|
/// 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
|
/// Global frame and usec at which playback started
|
||||||
pub started: Option<(usize, usize)>,
|
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| {
|
process!(TransportToolbar |self, _client, scope| {
|
||||||
self.update(&scope);
|
self.update(&scope);
|
||||||
Control::Continue
|
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 {
|
impl TransportToolbar {
|
||||||
|
|
||||||
pub fn standalone () -> Usually<Arc<RwLock<Self>>> {
|
pub fn standalone () -> Usually<Arc<RwLock<Self>>> {
|
||||||
let mut transport = Self::new(None);
|
let mut transport = Self::new(None);
|
||||||
transport.focused = true;
|
transport.focused = true;
|
||||||
transport.entered = true;
|
|
||||||
let jack = JackClient::Inactive(
|
let jack = JackClient::Inactive(
|
||||||
Client::new("tek_transport", ClientOptions::NO_START_SERVER)?.0
|
Client::new("tek_transport", ClientOptions::NO_START_SERVER)?.0
|
||||||
);
|
);
|
||||||
|
|
@ -55,24 +90,44 @@ impl TransportToolbar {
|
||||||
pub fn new (transport: Option<Transport>) -> Self {
|
pub fn new (transport: Option<Transport>) -> Self {
|
||||||
let timebase = Arc::new(Timebase::default());
|
let timebase = Arc::new(Timebase::default());
|
||||||
Self {
|
Self {
|
||||||
selected: TransportFocus::BPM,
|
|
||||||
metronome: false,
|
metronome: false,
|
||||||
focused: false,
|
focused: false,
|
||||||
entered: false,
|
focus: 0,
|
||||||
playhead: 0,
|
|
||||||
playing: Some(TransportState::Stopped),
|
|
||||||
started: None,
|
started: None,
|
||||||
quant: 24,
|
|
||||||
sync: timebase.ppq() as usize * 4,
|
|
||||||
jack: None,
|
jack: None,
|
||||||
transport,
|
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,
|
timebase,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn toggle_play (&mut self) -> Usually<()> {
|
pub fn toggle_play (&mut self) -> Usually<()> {
|
||||||
let transport = self.transport.as_ref().unwrap();
|
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 => {
|
TransportState::Stopped => {
|
||||||
transport.start()?;
|
transport.start()?;
|
||||||
Some(TransportState::Starting)
|
Some(TransportState::Starting)
|
||||||
|
|
@ -95,9 +150,9 @@ impl TransportToolbar {
|
||||||
} = scope.cycle_times().unwrap();
|
} = scope.cycle_times().unwrap();
|
||||||
let chunk_size = scope.n_frames() as usize;
|
let chunk_size = scope.n_frames() as usize;
|
||||||
let transport = self.transport.as_ref().unwrap().query().unwrap();
|
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;
|
let mut reset = false;
|
||||||
if self.playing != Some(transport.state) {
|
if self.playing.value != Some(transport.state) {
|
||||||
match transport.state {
|
match transport.state {
|
||||||
TransportState::Rolling => {
|
TransportState::Rolling => {
|
||||||
self.started = Some((
|
self.started = Some((
|
||||||
|
|
@ -112,7 +167,7 @@ impl TransportToolbar {
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.playing = Some(transport.state);
|
self.playing.value = Some(transport.state);
|
||||||
(
|
(
|
||||||
reset,
|
reset,
|
||||||
current_frames as usize,
|
current_frames as usize,
|
||||||
|
|
@ -132,11 +187,11 @@ impl TransportToolbar {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pulse (&self) -> usize {
|
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 {
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,4 +20,3 @@ impl TransportFocus {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,51 +1,55 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
handle!{
|
handle!(TransportToolbar |self, e| {
|
||||||
TransportToolbar |self, e| {
|
match e {
|
||||||
handle_keymap(self, e, KEYMAP_TRANSPORT)
|
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.
|
handle!(TransportPlayPauseButton |self, _e| Ok(false));
|
||||||
pub const KEYMAP_TRANSPORT: &'static [KeyBinding<TransportToolbar>] = keymap!(TransportToolbar {
|
|
||||||
[Left, NONE, "transport_prev", "select previous control", |transport: &mut TransportToolbar| Ok({
|
handle!(TransportBPM |self, _e| {
|
||||||
transport.selected.prev();
|
//TransportFocus::BPM => {
|
||||||
true
|
//transport.timebase.bpm.fetch_add(1.0, Ordering::Relaxed);
|
||||||
})],
|
//},
|
||||||
[Right, NONE, "transport_next", "select next control", |transport: &mut TransportToolbar| Ok({
|
//TransportFocus::BPM => {
|
||||||
transport.selected.next();
|
//transport.timebase.bpm.fetch_sub(1.0, Ordering::Relaxed);
|
||||||
true
|
//},
|
||||||
})],
|
Ok(false)
|
||||||
[Char('.'), NONE, "transport_increment", "increment value at cursor", |transport: &mut TransportToolbar| {
|
});
|
||||||
match transport.selected {
|
|
||||||
TransportFocus::BPM => {
|
handle!(TransportQuantize |self, _e| {
|
||||||
transport.timebase.bpm.fetch_add(1.0, Ordering::Relaxed);
|
//TransportFocus::Quant => {
|
||||||
},
|
//transport.quant.value = next_note_length(transport.quant)
|
||||||
TransportFocus::Quant => {
|
//},
|
||||||
transport.quant = next_note_length(transport.quant)
|
//TransportFocus::Quant => {
|
||||||
},
|
//transport.quant.value = prev_note_length(transport.quant);
|
||||||
TransportFocus::Sync => {
|
//},
|
||||||
transport.sync = next_note_length(transport.sync)
|
Ok(false)
|
||||||
},
|
});
|
||||||
};
|
|
||||||
Ok(true)
|
handle!(TransportSync |self, _e| {
|
||||||
}],
|
//TransportFocus::Sync => {
|
||||||
[Char(','), NONE, "transport_decrement", "decrement value at cursor", |transport: &mut TransportToolbar| {
|
//transport.sync.value = next_note_length(transport.sync)
|
||||||
match transport.selected {
|
//},
|
||||||
TransportFocus::BPM => {
|
//TransportFocus::Sync => {
|
||||||
transport.timebase.bpm.fetch_sub(1.0, Ordering::Relaxed);
|
//transport.sync.value = prev_note_length(transport.sync);
|
||||||
},
|
//},
|
||||||
TransportFocus::Quant => {
|
Ok(false)
|
||||||
transport.quant = prev_note_length(transport.quant);
|
});
|
||||||
},
|
|
||||||
TransportFocus::Sync => {
|
handle!(TransportClock |self, _e| {
|
||||||
transport.sync = prev_note_length(transport.sync);
|
Ok(false)
|
||||||
},
|
|
||||||
};
|
|
||||||
Ok(true)
|
|
||||||
}],
|
|
||||||
[Char(' '), NONE, "transport_play_toggle", "play or pause", |transport: &mut TransportToolbar| {
|
|
||||||
transport.toggle_play()?;
|
|
||||||
Ok(true)
|
|
||||||
}],
|
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -5,92 +5,79 @@ const CORNERS: Corners = Corners(NOT_DIM_GREEN);
|
||||||
render!(TransportToolbar |self, buf, area| {
|
render!(TransportToolbar |self, buf, area| {
|
||||||
let mut area = area;
|
let mut area = area;
|
||||||
area.height = 2;
|
area.height = 2;
|
||||||
let Self { quant, sync, focused, entered, .. } = self;
|
fill_bg(buf, area, Nord::BG0);
|
||||||
fill_bg(buf, area, Nord::bg_lo(*focused, *entered));
|
|
||||||
let active = self.focused && self.entered;
|
|
||||||
Split::right()
|
Split::right()
|
||||||
.add(TransportPlayPauseButton(self.playing))
|
.add_ref(&self.playing)
|
||||||
.add(TransportBPM(self.bpm(), active && self.selected == TransportFocus::BPM))
|
.add_ref(&self.bpm)
|
||||||
.add(TransportQuantize(*quant, active && self.selected == TransportFocus::Quant))
|
.add_ref(&self.quant)
|
||||||
.add(TransportSync(*sync, active && self.selected == TransportFocus::Sync))
|
.add_ref(&self.sync)
|
||||||
.add(TransportClock(self.pulse(), self.ppq(), self.usecs()))
|
.add_ref(&self.clock)
|
||||||
.render(buf, area)
|
.render(buf, area)
|
||||||
});
|
});
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
|
||||||
struct TransportPlayPauseButton(Option<TransportState>);
|
|
||||||
|
|
||||||
render!(TransportPlayPauseButton |self, buf, area| {
|
render!(TransportPlayPauseButton |self, buf, area| {
|
||||||
let Rect { x, y, .. } = 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::Stopped) => GRAY_DIM.bold(),
|
||||||
Some(TransportState::Starting) => GRAY_NOT_DIM_BOLD,
|
Some(TransportState::Starting) => GRAY_NOT_DIM_BOLD,
|
||||||
Some(TransportState::Rolling) => WHITE_NOT_DIM_BOLD,
|
Some(TransportState::Rolling) => WHITE_NOT_DIM_BOLD,
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
});
|
});
|
||||||
let label = match self.0 {
|
let label = match value {
|
||||||
Some(TransportState::Rolling) => "▶ PLAYING",
|
Some(TransportState::Rolling) => "▶ PLAYING",
|
||||||
Some(TransportState::Starting) => "READY ...",
|
Some(TransportState::Starting) => "READY ...",
|
||||||
Some(TransportState::Stopped) => "⏹ STOPPED",
|
Some(TransportState::Stopped) => "⏹ STOPPED",
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
let mut result = label.blit(buf, x + 1, y, style)?;
|
let mut area = label.blit(buf, x + 1, y, style)?;
|
||||||
result.width = result.width + 1;
|
area.width = area.width + 1;
|
||||||
Ok(result)
|
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| {
|
render!(TransportBPM |self, buf, area| {
|
||||||
let Rect { x, y, .. } = area;
|
let Rect { x, y, .. } = area;
|
||||||
let Self(bpm, highlight) = self;
|
let Self { value, focused } = self;
|
||||||
"BPM".blit(buf, x, y, Some(NOT_DIM))?;
|
"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 };
|
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 })?;
|
CORNERS.draw(buf, Rect { x: area.x - 1, ..area })?;
|
||||||
}
|
}
|
||||||
Ok(area)
|
Ok(area)
|
||||||
});
|
});
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
|
||||||
struct TransportQuantize(usize, bool);
|
|
||||||
|
|
||||||
render!(TransportQuantize |self, buf, area| {
|
render!(TransportQuantize |self, buf, area| {
|
||||||
let Rect { x, y, .. } = area;
|
let Rect { x, y, .. } = area;
|
||||||
let Self(quant, highlight) = self;
|
let Self { value, focused } = self;
|
||||||
"QUANT".blit(buf, x, y, Some(NOT_DIM))?;
|
"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 };
|
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 })?;
|
CORNERS.draw(buf, Rect { x: area.x - 1, ..area })?;
|
||||||
}
|
}
|
||||||
Ok(area)
|
Ok(area)
|
||||||
});
|
});
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
|
||||||
struct TransportSync(usize, bool);
|
|
||||||
|
|
||||||
render!(TransportSync |self, buf, area| {
|
render!(TransportSync |self, buf, area| {
|
||||||
let Rect { x, y, .. } = area;
|
let Rect { x, y, .. } = area;
|
||||||
let Self(sync, highlight) = self;
|
let Self { value, focused } = self;
|
||||||
"SYNC".blit(buf, x, y, Some(NOT_DIM))?;
|
"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 };
|
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 })?;
|
CORNERS.draw(buf, Rect { x: area.x - 1, ..area })?;
|
||||||
}
|
}
|
||||||
Ok(area)
|
Ok(area)
|
||||||
});
|
});
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
|
||||||
struct TransportClock(usize, usize, usize);
|
|
||||||
|
|
||||||
render!(TransportClock |self, buf, area| {
|
render!(TransportClock |self, buf, area| {
|
||||||
let Rect { x, y, width, .. } = area;
|
let Rect { x, y, width, .. } = area;
|
||||||
let Self(pulse, ppq, usecs) = self;
|
let Self { frame, pulse, ppq, usecs, focused } = self;
|
||||||
let (beats, pulses) = (pulse / ppq, pulse % ppq);
|
let (beats, pulses) = if *ppq > 0 { (pulse / ppq, pulse % ppq) } else { (0, 0) };
|
||||||
let (bars, beats) = ((beats / 4) + 1, (beats % 4) + 1);
|
let (bars, beats) = ((beats / 4) + 1, (beats % 4) + 1);
|
||||||
let (seconds, msecs) = (usecs / 1000000, usecs / 1000 % 1000);
|
let (seconds, msecs) = (usecs / 1000000, usecs / 1000 % 1000);
|
||||||
let (minutes, seconds) = (seconds / 60, seconds % 60);
|
let (minutes, seconds) = (seconds / 60, seconds % 60);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue