mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 19:56:42 +01:00
general unfuckeries
This commit is contained in:
parent
d0d187b5b6
commit
a7ff74e27c
11 changed files with 210 additions and 317 deletions
|
|
@ -1,9 +1,5 @@
|
|||
use crate::*;
|
||||
|
||||
pub trait JackApi {
|
||||
fn jack (&self) -> &Arc<RwLock<JackClient>>;
|
||||
}
|
||||
|
||||
pub trait HasMidiIns {
|
||||
fn midi_ins (&self) -> &Vec<Port<MidiIn>>;
|
||||
fn midi_ins_mut (&mut self) -> &mut Vec<Port<MidiIn>>;
|
||||
|
|
|
|||
|
|
@ -16,7 +16,17 @@ pub trait HasPlayPhrase: HasClock {
|
|||
fn next_phrase_mut (&mut self) -> &mut Option<(Moment, Option<Arc<RwLock<Phrase>>>)>;
|
||||
fn pulses_since_start (&self) -> Option<f64> {
|
||||
if let Some((started, Some(_))) = self.play_phrase().as_ref() {
|
||||
Some(self.clock().playhead.pulse.get() - started.pulse.get())
|
||||
let elapsed = self.clock().playhead.pulse.get() - started.pulse.get();
|
||||
Some(elapsed)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
fn pulses_since_start_looped (&self) -> Option<f64> {
|
||||
if let Some((started, Some(phrase))) = self.play_phrase().as_ref() {
|
||||
let elapsed = self.clock().playhead.pulse.get() - started.pulse.get();
|
||||
let elapsed = (elapsed as usize % phrase.read().unwrap().length) as f64;
|
||||
Some(elapsed)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use crate::*;
|
||||
use jack::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
/// Event enum for JACK events.
|
||||
pub enum JackEvent {
|
||||
ThreadInit,
|
||||
|
|
|
|||
|
|
@ -61,12 +61,6 @@ pub struct ArrangerTui {
|
|||
pub perf: PerfModel,
|
||||
}
|
||||
|
||||
impl JackApi for ArrangerTui {
|
||||
fn jack (&self) -> &Arc<RwLock<JackClient>> {
|
||||
&self.jack
|
||||
}
|
||||
}
|
||||
|
||||
impl Audio for ArrangerTui {
|
||||
#[inline] fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control {
|
||||
// Start profiling cycle
|
||||
|
|
|
|||
|
|
@ -0,0 +1,2 @@
|
|||
use crate::*;
|
||||
use super::*;
|
||||
|
|
@ -64,12 +64,6 @@ pub struct SequencerTui {
|
|||
pub perf: PerfModel,
|
||||
}
|
||||
|
||||
impl JackApi for SequencerTui {
|
||||
fn jack (&self) -> &Arc<RwLock<JackClient>> {
|
||||
&self.jack
|
||||
}
|
||||
}
|
||||
|
||||
impl Audio for SequencerTui {
|
||||
fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control {
|
||||
// Start profiling cycle
|
||||
|
|
@ -109,12 +103,12 @@ impl Audio for SequencerTui {
|
|||
|
||||
render!(|self: SequencerTui|lay!([self.size, Tui::split_up(false, 1,
|
||||
Tui::fill_xy(SequencerStatusBar::from(self)),
|
||||
Tui::split_left(false, 20,
|
||||
Tui::split_right(false, 20,
|
||||
Tui::fixed_x(20, Tui::split_down(false, 4, col!([
|
||||
PhraseSelector::play_phrase(&self.player, false, true),
|
||||
PhraseSelector::next_phrase(&self.player, false, true),
|
||||
PhraseSelector::play_phrase(&self.player),
|
||||
PhraseSelector::next_phrase(&self.player),
|
||||
]), Tui::split_up(false, 2,
|
||||
PhraseSelector::edit_phrase(&self.editor.phrase, self.focused() == SequencerFocus::PhraseEditor, true),
|
||||
PhraseSelector::edit_phrase(&self.editor.phrase),
|
||||
PhraseListView::from(self),
|
||||
))),
|
||||
col!([
|
||||
|
|
|
|||
|
|
@ -43,12 +43,6 @@ impl HasClock for TransportTui {
|
|||
}
|
||||
}
|
||||
|
||||
impl JackApi for TransportTui {
|
||||
fn jack (&self) -> &Arc<RwLock<JackClient>> {
|
||||
&self.jack
|
||||
}
|
||||
}
|
||||
|
||||
impl Audio for TransportTui {
|
||||
fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control {
|
||||
ClockAudio(self).process(client, scope)
|
||||
|
|
@ -120,55 +114,53 @@ impl<T: HasClock> From<(&T, Option<ItemPalette>, bool)> for TransportView {
|
|||
}
|
||||
}
|
||||
|
||||
struct TransportField<'a>(&'a str, &'a str);
|
||||
render!(|self: TransportField<'a>|{
|
||||
col!([
|
||||
Tui::fg(Color::Rgb(200, 200, 200), self.0),
|
||||
Tui::bold(true, Tui::fg(Color::Rgb(220, 220, 220), self.1)),
|
||||
])
|
||||
});
|
||||
|
||||
render!(|self: TransportView|{
|
||||
let bg = self.bg;
|
||||
let border_style = Style::default()
|
||||
.bg(bg)
|
||||
.fg(TuiTheme::border_fg(self.focused));
|
||||
let play_bg = if self.started{Color::Rgb(0,128,0)}else{Color::Rgb(128,64,0)};
|
||||
Tui::bg(bg, lay!(move|add|{
|
||||
add(&Tui::fill_x(Tui::at_w(lay!([
|
||||
//Lozenge(border_style),
|
||||
Tui::outset_x(0, row!([
|
||||
Tui::bg(play_bg, Tui::outset_x(1, Tui::fixed_x(9, col!(|add|{
|
||||
if self.started {
|
||||
add(&col!([Tui::fg(Color::Rgb(0, 255, 0), "▶ PLAYING"), ""]))
|
||||
} else {
|
||||
add(&col!(["", Tui::fg(Color::Rgb(255, 128, 0), "⏹ STOPPED")]))
|
||||
}
|
||||
})))), " ",
|
||||
Tui::fixed_x(10, TransportField("Beat",
|
||||
self.beat.as_str())), " ",
|
||||
Tui::fixed_x(8, TransportField("BPM ",
|
||||
self.bpm.as_str())), " ",
|
||||
Tui::fixed_x(8, TransportField("Second",
|
||||
format!("{:.1}s", self.current_second).as_str())), " ",
|
||||
Tui::fixed_x(8, TransportField("Rate ",
|
||||
self.sr.as_str())), " ",
|
||||
Tui::fixed_x(8, TransportField("Sample",
|
||||
format!("{:.0}k", self.current_sample).as_str())),
|
||||
]))
|
||||
]))))?;
|
||||
//add(&Tui::fill_x(Tui::center_x(Tui::pull_x(2, row!([
|
||||
//<center>
|
||||
//])))))?;
|
||||
//add(&Tui::fill_x(Tui::at_e(lay!(move|add|{
|
||||
////add(&Lozenge(border_style))?;
|
||||
//add(&Tui::outset_x(1, row!([
|
||||
//])))
|
||||
//}))))
|
||||
Ok(())
|
||||
}))
|
||||
|
||||
struct Field<'a>(&'a str, &'a str);
|
||||
render!(|self: Field<'a>|row!([
|
||||
Tui::fg(Color::Rgb(200, 200, 200), self.0),
|
||||
" ",
|
||||
Tui::bold(true, Tui::fg(Color::Rgb(220, 220, 220), self.1)),
|
||||
]));
|
||||
|
||||
struct PlayPause(bool);
|
||||
render!(|self: PlayPause|Tui::bg(
|
||||
if self.0{Color::Rgb(0,128,0)}else{Color::Rgb(128,64,0)},
|
||||
Tui::outset_x(1, Tui::fixed_x(9, col!(|add|{
|
||||
if self.0 {
|
||||
add(&col!([Tui::fg(Color::Rgb(0, 255, 0), "▶ PLAYING"), ""]))
|
||||
} else {
|
||||
add(&col!(["", Tui::fg(Color::Rgb(255, 128, 0), "⏹ STOPPED")]))
|
||||
}
|
||||
})))
|
||||
));
|
||||
|
||||
Tui::bg(self.bg, Tui::fill_x(row!([
|
||||
PlayPause(self.started), " ",
|
||||
col!([
|
||||
Field("Beat", self.beat.as_str()),
|
||||
Field("BPM ", self.bpm.as_str()),
|
||||
]),
|
||||
" ",
|
||||
col!([
|
||||
Field("Time ", format!("{:.1}s", self.current_second).as_str()),
|
||||
Field("Sample", format!("{:.0}k", self.current_sample).as_str()),
|
||||
]),
|
||||
])))
|
||||
|
||||
});
|
||||
|
||||
impl HasFocus for TransportTui {
|
||||
type Item = TransportFocus;
|
||||
fn focused (&self) -> Self::Item {
|
||||
self.focus.inner()
|
||||
}
|
||||
fn set_focused (&mut self, to: Self::Item) {
|
||||
self.focus.set_inner(to)
|
||||
}
|
||||
}
|
||||
|
||||
/// Which item of the transport toolbar is focused
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum TransportFocus {
|
||||
|
|
@ -207,29 +199,6 @@ impl FocusWrap<TransportFocus> for Option<TransportFocus> {
|
|||
}
|
||||
}
|
||||
|
||||
impl HasFocus for TransportTui {
|
||||
type Item = TransportFocus;
|
||||
/// Get the currently focused item.
|
||||
fn focused (&self) -> Self::Item {
|
||||
self.focus.inner()
|
||||
}
|
||||
/// Get the currently focused item.
|
||||
fn set_focused (&mut self, to: Self::Item) {
|
||||
self.focus.set_inner(to)
|
||||
}
|
||||
}
|
||||
|
||||
//impl_focus!(TransportTui TransportFocus [
|
||||
////&[Menu],
|
||||
//&[
|
||||
//PlayPause,
|
||||
//Bpm,
|
||||
//Sync,
|
||||
//Quant,
|
||||
//Clock,
|
||||
//],
|
||||
//]);
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct TransportStatusBar;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,56 +1,62 @@
|
|||
use crate::*;
|
||||
use Ordering::Relaxed;
|
||||
use ::crossterm::event::*;
|
||||
|
||||
pub struct TuiInput {
|
||||
pub(crate) exited: Arc<AtomicBool>,
|
||||
pub(crate) event: TuiEvent,
|
||||
pub(crate) event: TuiEvent,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum TuiEvent {
|
||||
/// Terminal input
|
||||
Input(::crossterm::event::Event),
|
||||
/// Update values but not the whole form.
|
||||
Update,
|
||||
/// Update the whole form.
|
||||
Redraw,
|
||||
/// Device gains focus
|
||||
Focus,
|
||||
/// Device loses focus
|
||||
Blur,
|
||||
// /// JACK notification
|
||||
// Jack(JackEvent)
|
||||
Input(Event),
|
||||
/// JACK notification
|
||||
_Jack(JackEvent)
|
||||
}
|
||||
|
||||
impl Input<Tui> for TuiInput {
|
||||
type Event = TuiEvent;
|
||||
fn event (&self) -> &TuiEvent { &self.event }
|
||||
fn is_done (&self) -> bool { self.exited.fetch_and(true, Ordering::Relaxed) }
|
||||
fn done (&self) { self.exited.store(true, Ordering::Relaxed); }
|
||||
}
|
||||
|
||||
impl TuiInput {
|
||||
// TODO remove
|
||||
pub fn handle_keymap <T> (&self, state: &mut T, keymap: &KeyMap<T>) -> Usually<bool> {
|
||||
match self.event() {
|
||||
TuiEvent::Input(crossterm::event::Event::Key(event)) => {
|
||||
for (code, modifiers, _, _, command) in keymap.iter() {
|
||||
if *code == event.code && modifiers.bits() == event.modifiers.bits() {
|
||||
return command(state)
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => {}
|
||||
};
|
||||
Ok(false)
|
||||
fn event (&self) -> &TuiEvent {
|
||||
&self.event
|
||||
}
|
||||
fn is_done (&self) -> bool {
|
||||
self.exited.fetch_and(true, Relaxed)
|
||||
}
|
||||
fn done (&self) {
|
||||
self.exited.store(true, Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
pub type KeyHandler<T> = &'static dyn Fn(&mut T)->Usually<bool>;
|
||||
|
||||
pub type KeyBinding<T> = (KeyCode, KeyModifiers, &'static str, &'static str, KeyHandler<T>);
|
||||
|
||||
pub type KeyMap<T> = [KeyBinding<T>];
|
||||
/// Define key pattern in key match statement
|
||||
#[macro_export] macro_rules! key {
|
||||
(Ctrl-$code:pat) => { TuiEvent::Input(crossterm::event::Event::Key(KeyEvent { code: $code,
|
||||
modifiers: KeyModifiers::CONTROL, kind: KeyEventKind::Press, state: KeyEventState::NONE
|
||||
})) };
|
||||
(Ctrl-$code:expr) => { TuiEvent::Input(crossterm::event::Event::Key(KeyEvent { code: $code,
|
||||
modifiers: KeyModifiers::CONTROL, kind: KeyEventKind::Press, state: KeyEventState::NONE
|
||||
})) };
|
||||
(Alt-$code:pat) => { TuiEvent::Input(crossterm::event::Event::Key(KeyEvent { code: $code,
|
||||
modifiers: KeyModifiers::ALT, kind: KeyEventKind::Press, state: KeyEventState::NONE
|
||||
})) };
|
||||
(Alt-$code:expr) => { TuiEvent::Input(crossterm::event::Event::Key(KeyEvent { code: $code,
|
||||
modifiers: KeyModifiers::ALT, kind: KeyEventKind::Press, state: KeyEventState::NONE
|
||||
})) };
|
||||
(Shift-$code:pat) => { TuiEvent::Input(crossterm::event::Event::Key(KeyEvent { code: $code,
|
||||
modifiers: KeyModifiers::SHIFT, kind: KeyEventKind::Press, state: KeyEventState::NONE
|
||||
})) };
|
||||
(Shift-$code:expr) => { TuiEvent::Input(crossterm::event::Event::Key(KeyEvent { code: $code,
|
||||
modifiers: KeyModifiers::SHIFT, kind: KeyEventKind::Press, state: KeyEventState::NONE
|
||||
})) };
|
||||
($code:pat) => { TuiEvent::Input(crossterm::event::Event::Key(KeyEvent { code: $code,
|
||||
modifiers: KeyModifiers::NONE, kind: KeyEventKind::Press, state: KeyEventState::NONE
|
||||
})) };
|
||||
($code:expr) => { TuiEvent::Input(crossterm::event::Event::Key(KeyEvent { code: $code,
|
||||
modifiers: KeyModifiers::NONE, kind: KeyEventKind::Press, state: KeyEventState::NONE
|
||||
})) };
|
||||
}
|
||||
|
||||
/*
|
||||
/// Define a key
|
||||
pub const fn key (code: KeyCode) -> KeyEvent {
|
||||
let modifiers = KeyModifiers::NONE;
|
||||
|
|
@ -83,99 +89,31 @@ pub const fn shift (key: KeyEvent) -> KeyEvent {
|
|||
}
|
||||
}
|
||||
|
||||
/// Define a key in a keymap
|
||||
#[macro_export] macro_rules! map_key {
|
||||
($k:ident $(($char:literal))?, $m:ident, $n: literal, $d: literal, $f: expr) => {
|
||||
(KeyCode::$k $(($char))?, KeyModifiers::$m, $n, $d, &$f as &dyn Fn()->Usually<bool>)
|
||||
*/
|
||||
|
||||
/*
|
||||
|
||||
impl TuiInput {
|
||||
// TODO remove
|
||||
pub fn handle_keymap <T> (&self, state: &mut T, keymap: &KeyMap<T>) -> Usually<bool> {
|
||||
match self.event() {
|
||||
TuiEvent::Input(Key(event)) => {
|
||||
for (code, modifiers, _, _, command) in keymap.iter() {
|
||||
if *code == event.code && modifiers.bits() == event.modifiers.bits() {
|
||||
return command(state)
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => {}
|
||||
};
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
/// Shorthand for key match statement
|
||||
#[macro_export] macro_rules! match_key {
|
||||
($event:expr, {
|
||||
$($key:pat=>$block:expr),* $(,)?
|
||||
}) => {
|
||||
match $event {
|
||||
$(crossterm::event::Event::Key(crossterm::event::KeyEvent {
|
||||
code: $key,
|
||||
modifiers: crossterm::event::KeyModifiers::NONE,
|
||||
kind: crossterm::event::KeyEventKind::Press,
|
||||
state: crossterm::event::KeyEventState::NONE
|
||||
}) => {
|
||||
$block
|
||||
})*
|
||||
_ => Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
pub type KeyHandler<T> = &'static dyn Fn(&mut T)->Usually<bool>;
|
||||
|
||||
/// Define key pattern in key match statement
|
||||
#[macro_export] macro_rules! key {
|
||||
($code:pat) => {
|
||||
TuiEvent::Input(crossterm::event::Event::Key(crossterm::event::KeyEvent {
|
||||
code: $code,
|
||||
modifiers: crossterm::event::KeyModifiers::NONE,
|
||||
kind: crossterm::event::KeyEventKind::Press,
|
||||
state: crossterm::event::KeyEventState::NONE
|
||||
}))
|
||||
};
|
||||
(Ctrl-$code:pat) => {
|
||||
TuiEvent::Input(crossterm::event::Event::Key(crossterm::event::KeyEvent {
|
||||
code: $code,
|
||||
modifiers: crossterm::event::KeyModifiers::CONTROL,
|
||||
kind: crossterm::event::KeyEventKind::Press,
|
||||
state: crossterm::event::KeyEventState::NONE
|
||||
}))
|
||||
};
|
||||
(Alt-$code:pat) => {
|
||||
TuiEvent::Input(crossterm::event::Event::Key(crossterm::event::KeyEvent {
|
||||
code: $code,
|
||||
modifiers: crossterm::event::KeyModifiers::ALT,
|
||||
kind: crossterm::event::KeyEventKind::Press,
|
||||
state: crossterm::event::KeyEventState::NONE
|
||||
}))
|
||||
};
|
||||
(Shift-$code:pat) => {
|
||||
TuiEvent::Input(crossterm::event::Event::Key(crossterm::event::KeyEvent {
|
||||
code: $code,
|
||||
modifiers: crossterm::event::KeyModifiers::SHIFT,
|
||||
kind: crossterm::event::KeyEventKind::Press,
|
||||
state: crossterm::event::KeyEventState::NONE
|
||||
}))
|
||||
}
|
||||
}
|
||||
pub type KeyBinding<T> = (KeyCode, KeyModifiers, &'static str, &'static str, KeyHandler<T>);
|
||||
|
||||
#[macro_export] macro_rules! key_lit {
|
||||
($code:expr) => {
|
||||
TuiEvent::Input(crossterm::event::Event::Key(crossterm::event::KeyEvent {
|
||||
code: $code,
|
||||
modifiers: crossterm::event::KeyModifiers::NONE,
|
||||
kind: crossterm::event::KeyEventKind::Press,
|
||||
state: crossterm::event::KeyEventState::NONE
|
||||
}))
|
||||
};
|
||||
(Ctrl-$code:expr) => {
|
||||
TuiEvent::Input(crossterm::event::Event::Key(crossterm::event::KeyEvent {
|
||||
code: $code,
|
||||
modifiers: crossterm::event::KeyModifiers::CONTROL,
|
||||
kind: crossterm::event::KeyEventKind::Press,
|
||||
state: crossterm::event::KeyEventState::NONE
|
||||
}))
|
||||
};
|
||||
(Alt-$code:expr) => {
|
||||
TuiEvent::Input(crossterm::event::Event::Key(crossterm::event::KeyEvent {
|
||||
code: $code,
|
||||
modifiers: crossterm::event::KeyModifiers::ALT,
|
||||
kind: crossterm::event::KeyEventKind::Press,
|
||||
state: crossterm::event::KeyEventState::NONE
|
||||
}))
|
||||
};
|
||||
(Shift-$code:expr) => {
|
||||
TuiEvent::Input(crossterm::event::Event::Key(crossterm::event::KeyEvent {
|
||||
code: $code,
|
||||
modifiers: crossterm::event::KeyModifiers::SHIFT,
|
||||
kind: crossterm::event::KeyEventKind::Press,
|
||||
state: crossterm::event::KeyEventState::NONE
|
||||
}))
|
||||
}
|
||||
}
|
||||
pub type KeyMap<T> = [KeyBinding<T>];
|
||||
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ use PhraseLengthFocus::*;
|
|||
use PhraseLengthCommand::*;
|
||||
|
||||
/// Displays and edits phrase length.
|
||||
#[derive(Clone)]
|
||||
pub struct PhraseLength {
|
||||
/// Pulses per beat (quaver)
|
||||
pub ppq: usize,
|
||||
|
|
|
|||
|
|
@ -110,50 +110,46 @@ impl<'a, T: HasPhraseList> From<&'a T> for PhraseListView<'a> {
|
|||
// TODO: Display phrases always in order of appearance
|
||||
render!(|self: PhraseListView<'a>|{
|
||||
let Self { title, focused, entered, phrases, index, mode } = self;
|
||||
let bg = if *focused {TuiTheme::g(32)} else {TuiTheme::null()};
|
||||
let border_bg = if *focused {TuiTheme::bg()} else {TuiTheme::null()};
|
||||
let border_color = if *entered {TuiTheme::bo1()} else {TuiTheme::bo2()};
|
||||
let title_color = if *focused {TuiTheme::ti1()} else {TuiTheme::ti2()};
|
||||
let bg = TuiTheme::g(32);
|
||||
let title_color = TuiTheme::ti1();
|
||||
let upper_left = format!("{title}");
|
||||
let upper_right = format!("({})", phrases.len());
|
||||
Tui::bg(bg, lay!(move|add|{
|
||||
//add(&Lozenge(Style::default().bg(border_bg).fg(border_color)))?;
|
||||
add(&Tui::inset_xy(0, 1, Tui::fill_xy(col!(move|add|match mode {
|
||||
Some(PhrasesMode::Import(_, ref browser)) => {
|
||||
add(browser)
|
||||
},
|
||||
Some(PhrasesMode::Export(_, ref browser)) => {
|
||||
add(browser)
|
||||
},
|
||||
_ => {
|
||||
for (i, phrase) in phrases.iter().enumerate() {
|
||||
add(&lay!(|add|{
|
||||
let Phrase { ref name, color, length, .. } = *phrase.read().unwrap();
|
||||
let mut length = PhraseLength::new(length, None);
|
||||
if let Some(PhrasesMode::Length(phrase, new_length, focus)) = mode {
|
||||
if *focused && i == *phrase {
|
||||
length.pulses = *new_length;
|
||||
length.focus = Some(*focus);
|
||||
}
|
||||
Some(PhrasesMode::Import(_, ref file_picker)) => add(file_picker),
|
||||
Some(PhrasesMode::Export(_, ref file_picker)) => add(file_picker),
|
||||
_ => Ok(for (i, phrase) in phrases.iter().enumerate() {
|
||||
add(&lay!(|add|{
|
||||
let Phrase { ref name, color, length, .. } = *phrase.read().unwrap();
|
||||
let mut length = PhraseLength::new(length, None);
|
||||
if let Some(PhrasesMode::Length(phrase, new_length, focus)) = mode {
|
||||
if *focused && i == *phrase {
|
||||
length.pulses = *new_length;
|
||||
length.focus = Some(*focus);
|
||||
}
|
||||
let length = Tui::fill_x(Tui::at_e(length));
|
||||
let row1 = Tui::fill_x(lay!([Tui::fill_x(Tui::at_w(format!(" {i}"))), length]));
|
||||
let mut row2 = format!(" {name}");
|
||||
if let Some(PhrasesMode::Rename(phrase, _)) = mode {
|
||||
if *focused && i == *phrase {
|
||||
row2 = format!("{row2}▄");
|
||||
}
|
||||
};
|
||||
let row2 = Tui::bold(true, row2);
|
||||
add(&Tui::bg(color.base.rgb, Tui::fill_x(col!([row1, row2]))))?;
|
||||
if *entered && i == *index {
|
||||
add(&CORNERS)?;
|
||||
}
|
||||
Ok(())
|
||||
}))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
add(&Tui::bg(color.base.rgb, Tui::fill_x(col!([
|
||||
Tui::fill_x(lay!(|add|{
|
||||
add(&Tui::fill_x(Tui::at_w(format!(" {i}"))))?;
|
||||
add(&Tui::fill_x(Tui::at_e(Tui::pull_x(1, length.clone()))))
|
||||
})),
|
||||
Tui::bold(true, {
|
||||
let mut row2 = format!(" {name}");
|
||||
if let Some(PhrasesMode::Rename(phrase, _)) = mode {
|
||||
if *focused && i == *phrase {
|
||||
row2 = format!("{row2}▄");
|
||||
}
|
||||
};
|
||||
row2
|
||||
}),
|
||||
]))))?;
|
||||
if *entered && i == *index {
|
||||
add(&CORNERS)?;
|
||||
}
|
||||
Ok(())
|
||||
}))?;
|
||||
})
|
||||
}))))?;
|
||||
add(&Tui::fill_xy(Tui::at_nw(Tui::push_x(1, Tui::fg(title_color, upper_left.to_string())))))?;
|
||||
add(&Tui::fill_xy(Tui::at_ne(Tui::pull_x(1, Tui::fg(title_color, upper_right.to_string())))))
|
||||
|
|
|
|||
|
|
@ -3,79 +3,72 @@ use crate::*;
|
|||
pub struct PhraseSelector {
|
||||
pub(crate) title: &'static str,
|
||||
pub(crate) phrase: Option<(Moment, Option<Arc<RwLock<Phrase>>>)>,
|
||||
pub(crate) focused: bool,
|
||||
pub(crate) entered: bool,
|
||||
pub(crate) time: String,
|
||||
}
|
||||
|
||||
// TODO: Display phrases always in order of appearance
|
||||
render!(|self: PhraseSelector|{
|
||||
let Self { title, phrase, focused, entered } = self;
|
||||
let border_bg = if *focused {TuiTheme::border_bg()} else {TuiTheme::null()};
|
||||
let border_color = if *focused {TuiTheme::bo1()} else {TuiTheme::bo2()};
|
||||
let Self { title, phrase, time } = self;
|
||||
let border_bg = TuiTheme::border_bg();
|
||||
let border_color = TuiTheme::bo1();
|
||||
let border = Lozenge(Style::default().bg(border_bg).fg(border_color));
|
||||
let title_color = if phrase.is_some() {
|
||||
TuiTheme::g(200)
|
||||
} else if *focused {
|
||||
TuiTheme::ti1()
|
||||
} else {
|
||||
TuiTheme::ti2()
|
||||
};
|
||||
let title_color = TuiTheme::g(200);
|
||||
Tui::fixed_y(2, lay!(move|add|{
|
||||
if phrase.is_none() {
|
||||
add(&Tui::fill_x(border))?;
|
||||
}
|
||||
//if phrase.is_none() {
|
||||
//add(&Tui::fill_x(border))?;
|
||||
//}
|
||||
add(&Tui::push_x(1, Tui::fg(title_color, *title)))?;
|
||||
add(&Tui::push_y(0, Tui::fill_xy(Layers::new(move|add|{
|
||||
if let Some((instant, Some(phrase))) = phrase {
|
||||
let Phrase { ref name, color, length, .. } = *phrase.read().unwrap();
|
||||
add(&Tui::pull_y(0, Tui::inset_x(0, Tui::bg(color.base.rgb, Tui::fill_x(col!([
|
||||
Tui::fill_x(lay!([
|
||||
Tui::fill_x(Tui::at_w(Tui::fg(TuiTheme::g(255), format!(" ")))),
|
||||
Tui::fill_x(Tui::at_e(Tui::fg(TuiTheme::g(255), PhraseLength::new(length, None))))
|
||||
])),
|
||||
Tui::bold(true, Tui::fg(TuiTheme::g(255), format!(" {name}")))
|
||||
]))))))?;
|
||||
}
|
||||
Ok(())
|
||||
}))))
|
||||
if let Some((_started, Some(phrase))) = phrase {
|
||||
let Phrase { ref name, color, .. } = *phrase.read().unwrap();
|
||||
add(&Tui::bg(color.base.rgb, Tui::fill_x(col!([
|
||||
Tui::fill_x(lay!([
|
||||
Tui::fill_x(Tui::at_w(Tui::fg(TuiTheme::g(255), format!(" ")))),
|
||||
Tui::inset_x(1, Tui::fill_x(Tui::at_e(Tui::fg(TuiTheme::g(255), time))))
|
||||
])),
|
||||
Tui::bold(true, Tui::fg(TuiTheme::g(255), format!(" {name}")))
|
||||
]))))?;
|
||||
}
|
||||
Ok(())
|
||||
}))
|
||||
});
|
||||
|
||||
impl PhraseSelector {
|
||||
pub fn play_phrase <T: HasPlayPhrase> (
|
||||
state: &T,
|
||||
focused: bool,
|
||||
entered: bool,
|
||||
) -> Self {
|
||||
// beats elapsed
|
||||
pub fn play_phrase <T: HasPlayPhrase + HasClock> (state: &T) -> Self {
|
||||
let phrase = state.play_phrase().clone();
|
||||
Self {
|
||||
focused,
|
||||
entered: focused && entered,
|
||||
phrase: state.play_phrase().clone(),
|
||||
title: "Now:",
|
||||
title: "Now:",
|
||||
time: if let Some(elapsed) = state.pulses_since_start_looped() {
|
||||
format!("+{:>}", state.clock().timebase.format_beats_0(elapsed))
|
||||
} else {
|
||||
String::from("")
|
||||
},
|
||||
phrase,
|
||||
}
|
||||
}
|
||||
pub fn next_phrase <T: HasPlayPhrase> (
|
||||
state: &T,
|
||||
focused: bool,
|
||||
entered: bool,
|
||||
) -> Self {
|
||||
// beats until switchover
|
||||
pub fn next_phrase <T: HasPlayPhrase> (state: &T) -> Self {
|
||||
let phrase = state.next_phrase().clone();
|
||||
Self {
|
||||
focused,
|
||||
entered: focused && entered,
|
||||
phrase: state.next_phrase().clone(),
|
||||
title: "Next:",
|
||||
title: "Next:",
|
||||
time: phrase.as_ref().map(|(t, _)|{
|
||||
let target = t.pulse.get();
|
||||
let current = state.clock().playhead.pulse.get();
|
||||
if target > current {
|
||||
let remaining = target - current;
|
||||
format!("-{:>}", state.clock().timebase.format_beats_0(remaining))
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
}).unwrap_or(String::from("")),
|
||||
phrase,
|
||||
}
|
||||
}
|
||||
pub fn edit_phrase (
|
||||
phrase: &Option<Arc<RwLock<Phrase>>>,
|
||||
focused: bool,
|
||||
entered: bool,
|
||||
) -> Self {
|
||||
pub fn edit_phrase (phrase: &Option<Arc<RwLock<Phrase>>>) -> Self {
|
||||
let phrase = phrase.clone();
|
||||
Self {
|
||||
focused,
|
||||
entered: focused && entered,
|
||||
phrase: Some((Moment::default(), phrase.clone())),
|
||||
title: "Edit:",
|
||||
title: "Edit:",
|
||||
time: phrase.as_ref().map(|p|format!("{}", p.read().unwrap().length)).unwrap_or(String::new()),
|
||||
phrase: Some((Moment::default(), phrase)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue