mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 11:46:41 +01:00
disable advanced sequencer focus, pt.2
This commit is contained in:
parent
be924d447e
commit
d492dbb637
4 changed files with 209 additions and 217 deletions
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::{*, api::ClockCommand::{Play, Pause}};
|
use crate::{*, api::ClockCommand::{Play, Pause}};
|
||||||
use KeyCode::{Tab, BackTab, Char, Enter};
|
use KeyCode::{Tab, BackTab, Char, Enter, Esc};
|
||||||
use SequencerCommand::*;
|
use SequencerCommand::*;
|
||||||
use SequencerFocus::*;
|
use SequencerFocus::*;
|
||||||
use PhraseCommand::*;
|
use PhraseCommand::*;
|
||||||
|
|
@ -146,12 +146,27 @@ impl HasPhrases for SequencerTui {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl HasPhraseList for SequencerTui {
|
||||||
|
fn phrases_focused (&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
fn phrases_entered (&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
fn phrases_mode (&self) -> &Option<PhrasesMode> {
|
||||||
|
&self.phrases.mode
|
||||||
|
}
|
||||||
|
fn phrase_index (&self) -> usize {
|
||||||
|
self.phrases.phrase.load(Ordering::Relaxed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl HasEditor for SequencerTui {
|
impl HasEditor for SequencerTui {
|
||||||
fn editor (&self) -> &PhraseEditorModel {
|
fn editor (&self) -> &PhraseEditorModel {
|
||||||
&self.editor
|
&self.editor
|
||||||
}
|
}
|
||||||
fn editor_focused (&self) -> bool {
|
fn editor_focused (&self) -> bool {
|
||||||
self.focused() == SequencerFocus::PhraseEditor
|
false
|
||||||
}
|
}
|
||||||
fn editor_entered (&self) -> bool {
|
fn editor_entered (&self) -> bool {
|
||||||
true
|
true
|
||||||
|
|
@ -200,160 +215,6 @@ impl From<&SequencerTui> for Option<TransportFocus> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Status bar for sequencer app
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct SequencerStatusBar {
|
|
||||||
pub(crate) width: usize,
|
|
||||||
pub(crate) cpu: Option<String>,
|
|
||||||
pub(crate) size: String,
|
|
||||||
pub(crate) res: String,
|
|
||||||
pub(crate) mode: &'static str,
|
|
||||||
pub(crate) help: &'static [(&'static str, &'static str, &'static str)]
|
|
||||||
}
|
|
||||||
|
|
||||||
impl StatusBar for SequencerStatusBar {
|
|
||||||
type State = SequencerTui;
|
|
||||||
fn hotkey_fg () -> Color {
|
|
||||||
TuiTheme::HOTKEY_FG
|
|
||||||
}
|
|
||||||
fn update (&mut self, _: &SequencerTui) {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&SequencerTui> for SequencerStatusBar {
|
|
||||||
fn from (state: &SequencerTui) -> Self {
|
|
||||||
use super::app_transport::TransportFocus::*;
|
|
||||||
let samples = state.clock.chunk.load(Ordering::Relaxed);
|
|
||||||
let rate = state.clock.timebase.sr.get() as f64;
|
|
||||||
let buffer = samples as f64 / rate;
|
|
||||||
let width = state.size.w();
|
|
||||||
let default_help = &[("", "⏎", " enter"), ("", "✣", " navigate")];
|
|
||||||
Self {
|
|
||||||
width,
|
|
||||||
cpu: state.perf.percentage().map(|cpu|format!("│{cpu:.01}%")),
|
|
||||||
size: format!("{}x{}│", width, state.size.h()),
|
|
||||||
res: format!("│{}s│{:.1}kHz│{:.1}ms│", samples, rate / 1000., buffer * 1000.),
|
|
||||||
mode: match state.focused() {
|
|
||||||
Transport(PlayPause) => " PLAY/PAUSE ",
|
|
||||||
Transport(Bpm) => " TEMPO ",
|
|
||||||
Transport(Sync) => " LAUNCH SYNC ",
|
|
||||||
Transport(Quant) => " REC QUANT ",
|
|
||||||
Transport(Clock) => " SEEK ",
|
|
||||||
//PhrasePlay => " TO PLAY ",
|
|
||||||
//PhraseNext => " UP NEXT ",
|
|
||||||
PhraseList => " PHRASES ",
|
|
||||||
PhraseEditor => match state.editor.edit_mode {
|
|
||||||
PhraseEditMode::Note => " EDIT MIDI ",
|
|
||||||
PhraseEditMode::Scroll => " VIEW MIDI ",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
help: match state.focused() {
|
|
||||||
Transport(PlayPause) => &[
|
|
||||||
("", "⏎", " play/pause"),
|
|
||||||
("", "✣", " navigate"),
|
|
||||||
],
|
|
||||||
Transport(Bpm) => &[
|
|
||||||
("", ".,", " inc/dec"),
|
|
||||||
("", "><", " fine"),
|
|
||||||
],
|
|
||||||
Transport(Sync) => &[
|
|
||||||
("", ".,", " inc/dec"),
|
|
||||||
],
|
|
||||||
Transport(Quant) => &[
|
|
||||||
("", ".,", " inc/dec"),
|
|
||||||
],
|
|
||||||
Transport(Clock) => &[
|
|
||||||
("", ".,", " by beat"),
|
|
||||||
("", "<>", " by time"),
|
|
||||||
],
|
|
||||||
PhraseList => &[
|
|
||||||
("", "↕", " pick"),
|
|
||||||
("", ".,", " move"),
|
|
||||||
("", "⏎", " play"),
|
|
||||||
("", "e", " edit"),
|
|
||||||
],
|
|
||||||
PhraseEditor => match state.editor.edit_mode {
|
|
||||||
PhraseEditMode::Note => &[
|
|
||||||
("", "✣", " cursor"),
|
|
||||||
],
|
|
||||||
PhraseEditMode::Scroll => &[
|
|
||||||
("", "✣", " scroll"),
|
|
||||||
],
|
|
||||||
},
|
|
||||||
_ => default_help,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render!(|self: SequencerStatusBar|{
|
|
||||||
lay!(|add|if self.width > 60 {
|
|
||||||
add(&Tui::fill_x(Tui::fixed_y(1, lay!([
|
|
||||||
Tui::fill_x(Tui::at_w(SequencerMode::from(self))),
|
|
||||||
Tui::fill_x(Tui::at_e(SequencerStats::from(self))),
|
|
||||||
]))))
|
|
||||||
} else {
|
|
||||||
add(&Tui::fill_x(col!(![
|
|
||||||
Tui::fill_x(Tui::center_x(SequencerMode::from(self))),
|
|
||||||
Tui::fill_x(Tui::center_x(SequencerStats::from(self))),
|
|
||||||
])))
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
struct SequencerMode {
|
|
||||||
mode: &'static str,
|
|
||||||
help: &'static [(&'static str, &'static str, &'static str)]
|
|
||||||
}
|
|
||||||
impl From<&SequencerStatusBar> for SequencerMode {
|
|
||||||
fn from (state: &SequencerStatusBar) -> Self {
|
|
||||||
Self {
|
|
||||||
mode: state.mode,
|
|
||||||
help: state.help,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
render!(|self: SequencerMode|{
|
|
||||||
let black = TuiTheme::g(0);
|
|
||||||
let light = TuiTheme::g(50);
|
|
||||||
let white = TuiTheme::g(255);
|
|
||||||
let orange = TuiTheme::orange();
|
|
||||||
let yellow = TuiTheme::yellow();
|
|
||||||
row!([
|
|
||||||
Tui::bg(orange, Tui::fg(black, Tui::bold(true, self.mode))),
|
|
||||||
Tui::bg(light, Tui::fg(white, row!((prefix, hotkey, suffix) in self.help.iter() => {
|
|
||||||
row!([" ", prefix, Tui::fg(yellow, *hotkey), suffix])
|
|
||||||
})))
|
|
||||||
])
|
|
||||||
});
|
|
||||||
|
|
||||||
struct SequencerStats<'a> {
|
|
||||||
cpu: &'a Option<String>,
|
|
||||||
size: &'a String,
|
|
||||||
res: &'a String,
|
|
||||||
}
|
|
||||||
impl<'a> From<&'a SequencerStatusBar> for SequencerStats<'a> {
|
|
||||||
fn from (state: &'a SequencerStatusBar) -> Self {
|
|
||||||
Self {
|
|
||||||
cpu: &state.cpu,
|
|
||||||
size: &state.size,
|
|
||||||
res: &state.res,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
render!(|self:SequencerStats<'a>|{
|
|
||||||
let orange = TuiTheme::orange();
|
|
||||||
let dark = TuiTheme::g(25);
|
|
||||||
let cpu = &self.cpu;
|
|
||||||
let res = &self.res;
|
|
||||||
let size = &self.size;
|
|
||||||
Tui::bg(dark, row!([
|
|
||||||
Tui::fg(orange, cpu),
|
|
||||||
Tui::fg(orange, res),
|
|
||||||
Tui::fg(orange, size),
|
|
||||||
]))
|
|
||||||
});
|
|
||||||
|
|
||||||
impl Handle<Tui> for SequencerTui {
|
impl Handle<Tui> for SequencerTui {
|
||||||
fn handle (&mut self, i: &TuiInput) -> Perhaps<bool> {
|
fn handle (&mut self, i: &TuiInput) -> Perhaps<bool> {
|
||||||
SequencerCommand::execute_with_state(self, i)
|
SequencerCommand::execute_with_state(self, i)
|
||||||
|
|
@ -404,6 +265,22 @@ pub fn to_sequencer_command (state: &SequencerTui, input: &TuiInput) -> Option<S
|
||||||
use super::app_transport::TransportCommand;
|
use super::app_transport::TransportCommand;
|
||||||
Some(match input.event() {
|
Some(match input.event() {
|
||||||
|
|
||||||
|
// Switch between editor and list
|
||||||
|
key!(Tab) | key!(BackTab) | key!(Shift-Tab) | key!(Shift-BackTab) => match state.focus {
|
||||||
|
PhraseEditor => SequencerCommand::Focus(FocusCommand::Set(PhraseList)),
|
||||||
|
_ => SequencerCommand::Focus(FocusCommand::Set(PhraseEditor)),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Esc: toggle between scrolling and editing
|
||||||
|
key!(Esc) =>
|
||||||
|
Editor(SetEditMode(match state.editor.edit_mode {
|
||||||
|
PhraseEditMode::Scroll => PhraseEditMode::Note,
|
||||||
|
PhraseEditMode::Note => PhraseEditMode::Scroll,
|
||||||
|
})),
|
||||||
|
|
||||||
|
// E: Toggle between editing currently playing or other phrase
|
||||||
|
//key!(Char('e')) => {}
|
||||||
|
|
||||||
// Transport: Play/pause
|
// Transport: Play/pause
|
||||||
key!(Char(' ')) =>
|
key!(Char(' ')) =>
|
||||||
Clock(if state.clock().is_stopped() { Play(None) } else { Pause(None) }),
|
Clock(if state.clock().is_stopped() { Play(None) } else { Pause(None) }),
|
||||||
|
|
@ -424,12 +301,6 @@ pub fn to_sequencer_command (state: &SequencerTui, input: &TuiInput) -> Option<S
|
||||||
key!(Enter) =>
|
key!(Enter) =>
|
||||||
Enqueue(Some(state.phrases.phrases[state.phrases.phrase.load(Ordering::Relaxed)].clone())),
|
Enqueue(Some(state.phrases.phrases[state.phrases.phrase.load(Ordering::Relaxed)].clone())),
|
||||||
|
|
||||||
// Switch between editor and list
|
|
||||||
key!(Tab) | key!(BackTab) | key!(Shift-Tab) | key!(Shift-BackTab) => match state.focus {
|
|
||||||
PhraseEditor => SequencerCommand::Focus(FocusCommand::Set(PhraseList)),
|
|
||||||
_ => SequencerCommand::Focus(FocusCommand::Set(PhraseList)),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delegate to focused control:
|
// Delegate to focused control:
|
||||||
_ => match state.focus {
|
_ => match state.focus {
|
||||||
PhraseEditor => Editor(PhraseCommand::input_to_command(&state.editor, input)?),
|
PhraseEditor => Editor(PhraseCommand::input_to_command(&state.editor, input)?),
|
||||||
|
|
|
||||||
|
|
@ -123,14 +123,10 @@ render!(|self: PhraseView<'a>|{
|
||||||
let fg = self.phrase.as_ref()
|
let fg = self.phrase.as_ref()
|
||||||
.map(|p|p.read().unwrap().color.clone())
|
.map(|p|p.read().unwrap().color.clone())
|
||||||
.unwrap_or(ItemPalette::from(ItemColor::from(TuiTheme::g(64))));
|
.unwrap_or(ItemPalette::from(ItemColor::from(TuiTheme::g(64))));
|
||||||
Tui::bg(bg, lay!([
|
Tui::bg(bg, Tui::split_up(false, 2,
|
||||||
PhraseTimeline(&self, fg),
|
Tui::bg(fg.dark.rgb, lay!([PhraseTimeline(&self, fg), PhraseViewStats(&self, fg),])),
|
||||||
PhraseViewNotes(&self, fg),
|
lay!([PhraseNotes(&self, fg), PhraseCursor(&self), PhraseKeys(&self, fg)]),
|
||||||
PhraseViewCursor(&self),
|
))
|
||||||
PhraseViewKeys(&self, fg),
|
|
||||||
PhraseViewStats(&self, fg),
|
|
||||||
//Measure::debug(),
|
|
||||||
]))
|
|
||||||
});
|
});
|
||||||
impl<'a, T: HasEditor> From<&'a T> for PhraseView<'a> {
|
impl<'a, T: HasEditor> From<&'a T> for PhraseView<'a> {
|
||||||
fn from (state: &'a T) -> Self {
|
fn from (state: &'a T) -> Self {
|
||||||
|
|
@ -138,10 +134,6 @@ impl<'a, T: HasEditor> From<&'a T> for PhraseView<'a> {
|
||||||
let height = editor.size.h();
|
let height = editor.size.h();
|
||||||
let note_point = editor.note_point.load(Ordering::Relaxed);
|
let note_point = editor.note_point.load(Ordering::Relaxed);
|
||||||
let mut note_lo = editor.note_lo.load(Ordering::Relaxed);
|
let mut note_lo = editor.note_lo.load(Ordering::Relaxed);
|
||||||
//if note_point < note_lo {
|
|
||||||
//note_lo = note_point;
|
|
||||||
//editor.note_lo.store(note_lo, Ordering::Relaxed);
|
|
||||||
//}
|
|
||||||
let mut note_hi = 127.min((note_lo + height).saturating_sub(2));
|
let mut note_hi = 127.min((note_lo + height).saturating_sub(2));
|
||||||
if note_point > note_hi {
|
if note_point > note_hi {
|
||||||
note_lo += note_point - note_hi;
|
note_lo += note_point - note_hi;
|
||||||
|
|
@ -169,51 +161,46 @@ render!(|self: PhraseTimeline<'a>|Tui::fg(TuiTheme::g(224), Tui::push_x(5, forma
|
||||||
|
|
||||||
pub struct PhraseViewStats<'a>(&'a PhraseView<'a>, ItemPalette);
|
pub struct PhraseViewStats<'a>(&'a PhraseView<'a>, ItemPalette);
|
||||||
render!(|self: PhraseViewStats<'a>|{
|
render!(|self: PhraseViewStats<'a>|{
|
||||||
let title_color = if self.0.focused{self.1.light.rgb}else{self.1.dark.rgb};
|
let color = self.1.dark.rgb;//if self.0.focused{self.1.light.rgb}else{self.1.dark.rgb};
|
||||||
lay!([
|
lay!([
|
||||||
Tui::at_sw({
|
Tui::at_sw(Tui::bg(color, Tui::fg(TuiTheme::g(224), format!(
|
||||||
let mut lower_right = format!(" {} ", self.0.size.format());
|
" {} | Note: {} ({}) | {} ",
|
||||||
if self.0.focused && self.0.entered {
|
self.0.size.format(),
|
||||||
lower_right = format!(
|
self.0.note_point,
|
||||||
"Note: {} ({}) {} {lower_right}",
|
to_note_name(self.0.note_point),
|
||||||
self.0.note_point,
|
pulses_to_name(self.0.note_len),
|
||||||
to_note_name(self.0.note_point),
|
)))),
|
||||||
pulses_to_name(self.0.note_len),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Tui::bg(title_color, Tui::fg(TuiTheme::g(224), lower_right))
|
|
||||||
}),
|
|
||||||
Tui::fill_xy(Tui::at_se({
|
Tui::fill_xy(Tui::at_se({
|
||||||
let mut upper_right = format!("[{}]", if self.0.entered {"■"} else {" "});
|
let mut upper_right = format!("[{}]", if self.0.entered {"■"} else {" "});
|
||||||
if let Some(phrase) = self.0.phrase {
|
if let Some(phrase) = self.0.phrase {
|
||||||
upper_right = format!(
|
upper_right = format!(
|
||||||
"Time: {}/{} {} {upper_right}",
|
" Time: {}/{} {} {upper_right} ",
|
||||||
self.0.time_point,
|
self.0.time_point,
|
||||||
phrase.read().unwrap().length,
|
phrase.read().unwrap().length,
|
||||||
pulses_to_name(self.0.view_mode.time_zoom().unwrap()),
|
pulses_to_name(self.0.view_mode.time_zoom().unwrap()),
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
Tui::pull_x(1, Tui::bg(title_color, Tui::fg(TuiTheme::g(224), upper_right)))
|
Tui::bg(color, Tui::fg(TuiTheme::g(224), upper_right))
|
||||||
})),
|
}))
|
||||||
])
|
])
|
||||||
});
|
});
|
||||||
|
|
||||||
struct PhraseViewKeys<'a>(&'a PhraseView<'a>, ItemPalette);
|
struct PhraseKeys<'a>(&'a PhraseView<'a>, ItemPalette);
|
||||||
render!(|self: PhraseViewKeys<'a>|{
|
render!(|self: PhraseKeys<'a>|{
|
||||||
let layout = |to:[u16;2]|Ok(Some(to.clip_w(5)));
|
let layout = |to:[u16;2]|Ok(Some(to.clip_w(5)));
|
||||||
Tui::fill_xy(Widget::new(layout, |to: &mut TuiOutput|Ok(
|
Tui::fill_xy(Widget::new(layout, |to: &mut TuiOutput|Ok(
|
||||||
self.0.view_mode.render_keys(to, self.1.light.rgb, Some(self.0.note_point), self.0.note_range)
|
self.0.view_mode.render_keys(to, self.1.light.rgb, Some(self.0.note_point), self.0.note_range)
|
||||||
)))
|
)))
|
||||||
});
|
});
|
||||||
|
|
||||||
struct PhraseViewNotes<'a>(&'a PhraseView<'a>, ItemPalette);
|
struct PhraseNotes<'a>(&'a PhraseView<'a>, ItemPalette);
|
||||||
render!(|self: PhraseViewNotes<'a>|Tui::fill_xy(render(|to: &mut TuiOutput|{
|
render!(|self: PhraseNotes<'a>|Tui::fill_xy(render(|to: &mut TuiOutput|{
|
||||||
self.0.size.set_wh(to.area.w(), to.area.h() as usize);
|
self.0.size.set_wh(to.area.w(), to.area.h() as usize);
|
||||||
Ok(self.0.view_mode.render_notes(to, self.0.time_start, self.0.note_range.1))
|
Ok(self.0.view_mode.render_notes(to, self.0.time_start, self.0.note_range.1))
|
||||||
})));
|
})));
|
||||||
|
|
||||||
struct PhraseViewCursor<'a>(&'a PhraseView<'a>);
|
struct PhraseCursor<'a>(&'a PhraseView<'a>);
|
||||||
render!(|self: PhraseViewCursor<'a>|Tui::fill_xy(render(|to: &mut TuiOutput|Ok(
|
render!(|self: PhraseCursor<'a>|Tui::fill_xy(render(|to: &mut TuiOutput|Ok(
|
||||||
self.0.view_mode.render_cursor(
|
self.0.view_mode.render_cursor(
|
||||||
to,
|
to,
|
||||||
self.0.time_point,
|
self.0.time_point,
|
||||||
|
|
@ -303,16 +290,12 @@ impl PhraseViewMode for PianoHorizontal {
|
||||||
let source = &self.buffer;
|
let source = &self.buffer;
|
||||||
let target = &mut target.buffer;
|
let target = &mut target.buffer;
|
||||||
for (x, target_x) in (x0..x0+w).enumerate() {
|
for (x, target_x) in (x0..x0+w).enumerate() {
|
||||||
|
|
||||||
for (y, target_y) in (y0..y0+h).enumerate() {
|
for (y, target_y) in (y0..y0+h).enumerate() {
|
||||||
|
|
||||||
if y > note_hi {
|
if y > note_hi {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
let source_x = time_start + x;
|
let source_x = time_start + x;
|
||||||
let source_y = note_hi - y;
|
let source_y = note_hi - y;
|
||||||
|
|
||||||
// TODO: enable loop rollover:
|
// TODO: enable loop rollover:
|
||||||
//let source_x = (time_start + x) % source.width.max(1);
|
//let source_x = (time_start + x) % source.width.max(1);
|
||||||
//let source_y = (note_hi - y) % source.height.max(1);
|
//let source_y = (note_hi - y) % source.height.max(1);
|
||||||
|
|
@ -322,9 +305,7 @@ impl PhraseViewMode for PianoHorizontal {
|
||||||
*target_cell = source_cell.clone();
|
*target_cell = source_cell.clone();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn render_keys (
|
fn render_keys (
|
||||||
|
|
|
||||||
|
|
@ -67,21 +67,6 @@ pub trait HasPhraseList: HasPhrases {
|
||||||
fn phrase_index (&self) -> usize;
|
fn phrase_index (&self) -> usize;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HasPhraseList for SequencerTui {
|
|
||||||
fn phrases_focused (&self) -> bool {
|
|
||||||
self.focused() == SequencerFocus::PhraseList
|
|
||||||
}
|
|
||||||
fn phrases_entered (&self) -> bool {
|
|
||||||
true && self.phrases_focused()
|
|
||||||
}
|
|
||||||
fn phrases_mode (&self) -> &Option<PhrasesMode> {
|
|
||||||
&self.phrases.mode
|
|
||||||
}
|
|
||||||
fn phrase_index (&self) -> usize {
|
|
||||||
self.phrases.phrase.load(Ordering::Relaxed)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HasPhraseList for ArrangerTui {
|
impl HasPhraseList for ArrangerTui {
|
||||||
fn phrases_focused (&self) -> bool {
|
fn phrases_focused (&self) -> bool {
|
||||||
self.focused() == ArrangerFocus::Phrases
|
self.focused() == ArrangerFocus::Phrases
|
||||||
|
|
|
||||||
|
|
@ -26,3 +26,158 @@ pub trait StatusBar: Render<Tui> {
|
||||||
Tui::to_north(state.into(), content)
|
Tui::to_north(state.into(), content)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Status bar for sequencer app
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct SequencerStatusBar {
|
||||||
|
pub(crate) width: usize,
|
||||||
|
pub(crate) cpu: Option<String>,
|
||||||
|
pub(crate) size: String,
|
||||||
|
pub(crate) res: String,
|
||||||
|
pub(crate) mode: &'static str,
|
||||||
|
pub(crate) help: &'static [(&'static str, &'static str, &'static str)]
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StatusBar for SequencerStatusBar {
|
||||||
|
type State = SequencerTui;
|
||||||
|
fn hotkey_fg () -> Color {
|
||||||
|
TuiTheme::HOTKEY_FG
|
||||||
|
}
|
||||||
|
fn update (&mut self, _: &SequencerTui) {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&SequencerTui> for SequencerStatusBar {
|
||||||
|
fn from (state: &SequencerTui) -> Self {
|
||||||
|
use super::app_transport::TransportFocus::*;
|
||||||
|
use super::app_sequencer::SequencerFocus::*;
|
||||||
|
let samples = state.clock.chunk.load(Ordering::Relaxed);
|
||||||
|
let rate = state.clock.timebase.sr.get() as f64;
|
||||||
|
let buffer = samples as f64 / rate;
|
||||||
|
let width = state.size.w();
|
||||||
|
let default_help = &[("", "⏎", " enter"), ("", "✣", " navigate")];
|
||||||
|
Self {
|
||||||
|
width,
|
||||||
|
cpu: state.perf.percentage().map(|cpu|format!("│{cpu:.01}%")),
|
||||||
|
size: format!("{}x{}│", width, state.size.h()),
|
||||||
|
res: format!("│{}s│{:.1}kHz│{:.1}ms│", samples, rate / 1000., buffer * 1000.),
|
||||||
|
mode: match state.focused() {
|
||||||
|
Transport(PlayPause) => " PLAY/PAUSE ",
|
||||||
|
Transport(Bpm) => " TEMPO ",
|
||||||
|
Transport(Sync) => " LAUNCH SYNC ",
|
||||||
|
Transport(Quant) => " REC QUANT ",
|
||||||
|
Transport(Clock) => " SEEK ",
|
||||||
|
//PhrasePlay => " TO PLAY ",
|
||||||
|
//PhraseNext => " UP NEXT ",
|
||||||
|
PhraseList => " PHRASES ",
|
||||||
|
PhraseEditor => match state.editor.edit_mode {
|
||||||
|
PhraseEditMode::Note => " EDIT MIDI ",
|
||||||
|
PhraseEditMode::Scroll => " VIEW MIDI ",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
help: match state.focused() {
|
||||||
|
Transport(PlayPause) => &[
|
||||||
|
("", "⏎", " play/pause"),
|
||||||
|
("", "✣", " navigate"),
|
||||||
|
],
|
||||||
|
Transport(Bpm) => &[
|
||||||
|
("", ".,", " inc/dec"),
|
||||||
|
("", "><", " fine"),
|
||||||
|
],
|
||||||
|
Transport(Sync) => &[
|
||||||
|
("", ".,", " inc/dec"),
|
||||||
|
],
|
||||||
|
Transport(Quant) => &[
|
||||||
|
("", ".,", " inc/dec"),
|
||||||
|
],
|
||||||
|
Transport(Clock) => &[
|
||||||
|
("", ".,", " by beat"),
|
||||||
|
("", "<>", " by time"),
|
||||||
|
],
|
||||||
|
PhraseList => &[
|
||||||
|
("", "↕", " pick"),
|
||||||
|
("", ".,", " move"),
|
||||||
|
("", "⏎", " play"),
|
||||||
|
("", "e", " edit"),
|
||||||
|
],
|
||||||
|
PhraseEditor => match state.editor.edit_mode {
|
||||||
|
PhraseEditMode::Note => &[
|
||||||
|
("", "✣", " cursor"),
|
||||||
|
],
|
||||||
|
PhraseEditMode::Scroll => &[
|
||||||
|
("", "✣", " scroll"),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
_ => default_help,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render!(|self: SequencerStatusBar|{
|
||||||
|
lay!(|add|if self.width > 60 {
|
||||||
|
add(&Tui::fill_x(Tui::fixed_y(1, lay!([
|
||||||
|
Tui::fill_x(Tui::at_w(SequencerMode::from(self))),
|
||||||
|
Tui::fill_x(Tui::at_e(SequencerStats::from(self))),
|
||||||
|
]))))
|
||||||
|
} else {
|
||||||
|
add(&Tui::fill_x(col!(![
|
||||||
|
Tui::fill_x(Tui::center_x(SequencerMode::from(self))),
|
||||||
|
Tui::fill_x(Tui::center_x(SequencerStats::from(self))),
|
||||||
|
])))
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
struct SequencerMode {
|
||||||
|
mode: &'static str,
|
||||||
|
help: &'static [(&'static str, &'static str, &'static str)]
|
||||||
|
}
|
||||||
|
impl From<&SequencerStatusBar> for SequencerMode {
|
||||||
|
fn from (state: &SequencerStatusBar) -> Self {
|
||||||
|
Self {
|
||||||
|
mode: state.mode,
|
||||||
|
help: state.help,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
render!(|self: SequencerMode|{
|
||||||
|
let black = TuiTheme::g(0);
|
||||||
|
let light = TuiTheme::g(50);
|
||||||
|
let white = TuiTheme::g(255);
|
||||||
|
let orange = TuiTheme::orange();
|
||||||
|
let yellow = TuiTheme::yellow();
|
||||||
|
row!([
|
||||||
|
Tui::bg(orange, Tui::fg(black, Tui::bold(true, self.mode))),
|
||||||
|
Tui::bg(light, Tui::fg(white, row!((prefix, hotkey, suffix) in self.help.iter() => {
|
||||||
|
row!([" ", prefix, Tui::fg(yellow, *hotkey), suffix])
|
||||||
|
})))
|
||||||
|
])
|
||||||
|
});
|
||||||
|
|
||||||
|
struct SequencerStats<'a> {
|
||||||
|
cpu: &'a Option<String>,
|
||||||
|
size: &'a String,
|
||||||
|
res: &'a String,
|
||||||
|
}
|
||||||
|
impl<'a> From<&'a SequencerStatusBar> for SequencerStats<'a> {
|
||||||
|
fn from (state: &'a SequencerStatusBar) -> Self {
|
||||||
|
Self {
|
||||||
|
cpu: &state.cpu,
|
||||||
|
size: &state.size,
|
||||||
|
res: &state.res,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
render!(|self:SequencerStats<'a>|{
|
||||||
|
let orange = TuiTheme::orange();
|
||||||
|
let dark = TuiTheme::g(25);
|
||||||
|
let cpu = &self.cpu;
|
||||||
|
let res = &self.res;
|
||||||
|
let size = &self.size;
|
||||||
|
Tui::bg(dark, row!([
|
||||||
|
Tui::fg(orange, cpu),
|
||||||
|
Tui::fg(orange, res),
|
||||||
|
Tui::fg(orange, size),
|
||||||
|
]))
|
||||||
|
});
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue