disable advanced sequencer focus, pt.2

This commit is contained in:
🪞👃🪞 2024-12-11 21:14:08 +01:00
parent be924d447e
commit d492dbb637
4 changed files with 209 additions and 217 deletions

View file

@ -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)?),

View file

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

View file

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

View file

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