From d492dbb637d8c0b9c1514cd3ee117878cf19a578 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Wed, 11 Dec 2024 21:14:08 +0100 Subject: [PATCH] disable advanced sequencer focus, pt.2 --- crates/tek/src/tui/app_sequencer.rs | 195 +++++----------------------- crates/tek/src/tui/phrase_editor.rs | 61 +++------ crates/tek/src/tui/phrase_list.rs | 15 --- crates/tek/src/tui/status_bar.rs | 155 ++++++++++++++++++++++ 4 files changed, 209 insertions(+), 217 deletions(-) diff --git a/crates/tek/src/tui/app_sequencer.rs b/crates/tek/src/tui/app_sequencer.rs index beece12f..63ab3c8b 100644 --- a/crates/tek/src/tui/app_sequencer.rs +++ b/crates/tek/src/tui/app_sequencer.rs @@ -1,5 +1,5 @@ use crate::{*, api::ClockCommand::{Play, Pause}}; -use KeyCode::{Tab, BackTab, Char, Enter}; +use KeyCode::{Tab, BackTab, Char, Enter, Esc}; use SequencerCommand::*; use SequencerFocus::*; 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 { + &self.phrases.mode + } + fn phrase_index (&self) -> usize { + self.phrases.phrase.load(Ordering::Relaxed) + } +} + impl HasEditor for SequencerTui { fn editor (&self) -> &PhraseEditorModel { &self.editor } fn editor_focused (&self) -> bool { - self.focused() == SequencerFocus::PhraseEditor + false } fn editor_entered (&self) -> bool { true @@ -200,160 +215,6 @@ impl From<&SequencerTui> for Option { } } -/// Status bar for sequencer app -#[derive(Clone)] -pub struct SequencerStatusBar { - pub(crate) width: usize, - pub(crate) cpu: Option, - 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, - 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 for SequencerTui { fn handle (&mut self, i: &TuiInput) -> Perhaps { SequencerCommand::execute_with_state(self, i) @@ -404,6 +265,22 @@ pub fn to_sequencer_command (state: &SequencerTui, input: &TuiInput) -> Option 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 key!(Char(' ')) => 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 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: _ => match state.focus { PhraseEditor => Editor(PhraseCommand::input_to_command(&state.editor, input)?), diff --git a/crates/tek/src/tui/phrase_editor.rs b/crates/tek/src/tui/phrase_editor.rs index 5ae39a71..04d863fd 100644 --- a/crates/tek/src/tui/phrase_editor.rs +++ b/crates/tek/src/tui/phrase_editor.rs @@ -123,14 +123,10 @@ render!(|self: PhraseView<'a>|{ let fg = self.phrase.as_ref() .map(|p|p.read().unwrap().color.clone()) .unwrap_or(ItemPalette::from(ItemColor::from(TuiTheme::g(64)))); - Tui::bg(bg, lay!([ - PhraseTimeline(&self, fg), - PhraseViewNotes(&self, fg), - PhraseViewCursor(&self), - PhraseViewKeys(&self, fg), - PhraseViewStats(&self, fg), - //Measure::debug(), - ])) + Tui::bg(bg, Tui::split_up(false, 2, + Tui::bg(fg.dark.rgb, lay!([PhraseTimeline(&self, fg), PhraseViewStats(&self, fg),])), + lay!([PhraseNotes(&self, fg), PhraseCursor(&self), PhraseKeys(&self, fg)]), + )) }); impl<'a, T: HasEditor> From<&'a T> for PhraseView<'a> { 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 note_point = editor.note_point.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)); if 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); 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!([ - Tui::at_sw({ - let mut lower_right = format!(" {} ", self.0.size.format()); - if self.0.focused && self.0.entered { - lower_right = format!( - "Note: {} ({}) {} {lower_right}", - self.0.note_point, - 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::at_sw(Tui::bg(color, Tui::fg(TuiTheme::g(224), format!( + " {} | Note: {} ({}) | {} ", + self.0.size.format(), + self.0.note_point, + to_note_name(self.0.note_point), + pulses_to_name(self.0.note_len), + )))), Tui::fill_xy(Tui::at_se({ let mut upper_right = format!("[{}]", if self.0.entered {"■"} else {" "}); if let Some(phrase) = self.0.phrase { upper_right = format!( - "Time: {}/{} {} {upper_right}", + " Time: {}/{} {} {upper_right} ", self.0.time_point, phrase.read().unwrap().length, 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); -render!(|self: PhraseViewKeys<'a>|{ +struct PhraseKeys<'a>(&'a PhraseView<'a>, ItemPalette); +render!(|self: PhraseKeys<'a>|{ let layout = |to:[u16;2]|Ok(Some(to.clip_w(5))); 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) ))) }); -struct PhraseViewNotes<'a>(&'a PhraseView<'a>, ItemPalette); -render!(|self: PhraseViewNotes<'a>|Tui::fill_xy(render(|to: &mut TuiOutput|{ +struct PhraseNotes<'a>(&'a PhraseView<'a>, ItemPalette); +render!(|self: PhraseNotes<'a>|Tui::fill_xy(render(|to: &mut TuiOutput|{ 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)) }))); -struct PhraseViewCursor<'a>(&'a PhraseView<'a>); -render!(|self: PhraseViewCursor<'a>|Tui::fill_xy(render(|to: &mut TuiOutput|Ok( +struct PhraseCursor<'a>(&'a PhraseView<'a>); +render!(|self: PhraseCursor<'a>|Tui::fill_xy(render(|to: &mut TuiOutput|Ok( self.0.view_mode.render_cursor( to, self.0.time_point, @@ -303,16 +290,12 @@ impl PhraseViewMode for PianoHorizontal { let source = &self.buffer; let target = &mut target.buffer; for (x, target_x) in (x0..x0+w).enumerate() { - for (y, target_y) in (y0..y0+h).enumerate() { - if y > note_hi { break } - let source_x = time_start + x; let source_y = note_hi - y; - // TODO: enable loop rollover: //let source_x = (time_start + x) % source.width.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(); } } - } - } } fn render_keys ( diff --git a/crates/tek/src/tui/phrase_list.rs b/crates/tek/src/tui/phrase_list.rs index eed3fbe0..27dcafdb 100644 --- a/crates/tek/src/tui/phrase_list.rs +++ b/crates/tek/src/tui/phrase_list.rs @@ -67,21 +67,6 @@ pub trait HasPhraseList: HasPhrases { 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 { - &self.phrases.mode - } - fn phrase_index (&self) -> usize { - self.phrases.phrase.load(Ordering::Relaxed) - } -} - impl HasPhraseList for ArrangerTui { fn phrases_focused (&self) -> bool { self.focused() == ArrangerFocus::Phrases diff --git a/crates/tek/src/tui/status_bar.rs b/crates/tek/src/tui/status_bar.rs index b3af3c5b..70cfa678 100644 --- a/crates/tek/src/tui/status_bar.rs +++ b/crates/tek/src/tui/status_bar.rs @@ -26,3 +26,158 @@ pub trait StatusBar: Render { Tui::to_north(state.into(), content) } } + +/// Status bar for sequencer app +#[derive(Clone)] +pub struct SequencerStatusBar { + pub(crate) width: usize, + pub(crate) cpu: Option, + 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, + 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), + ])) +});