diff --git a/crates/tek/src/layout/split.rs b/crates/tek/src/layout/split.rs index 476ecc10..5b78d405 100644 --- a/crates/tek/src/layout/split.rs +++ b/crates/tek/src/layout/split.rs @@ -1,12 +1,22 @@ use crate::*; -pub trait LayoutSplit: Render + Sized { - fn split > ( - self, direction: Direction, amount: E::Unit, other: W - ) -> Split { Split::new(direction, amount, self, other) } - fn split_flip > ( - self, direction: Direction, amount: E::Unit, other: W - ) -> Split { Split::new(direction, amount, other, self) } +impl LayoutSplit for E {} + +pub trait LayoutSplit { + fn split , B: Render> ( + direction: Direction, amount: E::Unit, a: A, b: B + ) -> Split { + Split::new(direction, amount, a, b) + } + fn split_up , B: Render> ( + amount: E::Unit, a: A, b: B + ) -> Split { + Split::new(Direction::Up, amount, a, b) + } + + //fn split_flip > ( + //self, direction: Direction, amount: E::Unit, other: W + //) -> Split { Split::new(direction, amount, other, self) } } /// A binary split with fixed proportion diff --git a/crates/tek/src/tui/app_arranger.rs b/crates/tek/src/tui/app_arranger.rs index 48ea51d6..f5cc1afe 100644 --- a/crates/tek/src/tui/app_arranger.rs +++ b/crates/tek/src/tui/app_arranger.rs @@ -240,7 +240,7 @@ pub enum ArrangerStatus { impl StatusBar for ArrangerStatus { type State = (ArrangerFocus, ArrangerSelection, bool); fn hotkey_fg () -> Color where Self: Sized { - TuiTheme::hotkey_fg() + TuiTheme::HOTKEY_FG } fn update (&mut self, (focused, selected, entered): &Self::State) { *self = match focused { diff --git a/crates/tek/src/tui/app_sequencer.rs b/crates/tek/src/tui/app_sequencer.rs index 9dc49c90..66e1a55b 100644 --- a/crates/tek/src/tui/app_sequencer.rs +++ b/crates/tek/src/tui/app_sequencer.rs @@ -91,7 +91,6 @@ impl Audio for SequencerTui { //self.now().set(now); //} //} - // End profiling cycle self.perf.update(t0, scope); @@ -108,12 +107,11 @@ render!(|self: SequencerTui|lay!([ false })), row!([ - Tui::fixed_x(20, col!([ - PhraseSelector::edit_phrase( - &self.editor.phrase, - self.focused() == SequencerFocus::PhraseEditor, - self.entered() - ), + Tui::fixed_x(20, Tui::split_up(2, PhraseSelector::edit_phrase( + &self.editor.phrase, + self.focused() == SequencerFocus::PhraseEditor, + self.entered() + ), col!([ PhraseSelector::play_phrase( &self.player, self.focused() == SequencerFocus::PhrasePlay, @@ -125,7 +123,8 @@ render!(|self: SequencerTui|lay!([ self.entered() ), PhraseListView::from(self), - ])), + + ]))), PhraseView::from(self) ]) ])), @@ -228,7 +227,7 @@ pub struct SequencerStatusBar { impl StatusBar for SequencerStatusBar { type State = SequencerTui; fn hotkey_fg () -> Color { - TuiTheme::hotkey_fg() + TuiTheme::HOTKEY_FG } fn update (&mut self, _: &SequencerTui) { todo!() @@ -422,7 +421,18 @@ impl InputToCommand for SequencerCommand { } else { to_focus_command(input).map(SequencerCommand::Focus) .or_else(||to_sequencer_command(state, input)) - } + }.or_else(||Some({ + let time_zoom = state.editor.view_mode.time_zoom(); + let next_zoom = next_note_length(time_zoom); + let prev_zoom = prev_note_length(time_zoom); + match input.event() { + key!(Char('-')) => SequencerCommand::Editor(PhraseCommand::SetTimeZoom(next_zoom)), + key!(Char('_')) => SequencerCommand::Editor(PhraseCommand::SetTimeZoom(next_zoom)), + key!(Char('=')) => SequencerCommand::Editor(PhraseCommand::SetTimeZoom(prev_zoom)), + key!(Char('+')) => SequencerCommand::Editor(PhraseCommand::SetTimeZoom(prev_zoom)), + _ => return None + } + })) } } diff --git a/crates/tek/src/tui/app_transport.rs b/crates/tek/src/tui/app_transport.rs index 9462e6c2..1f05267a 100644 --- a/crates/tek/src/tui/app_transport.rs +++ b/crates/tek/src/tui/app_transport.rs @@ -63,23 +63,27 @@ pub struct TransportView { sr: String, bpm: String, ppq: String, + beat: String, global_sample: String, global_second: String, started: bool, - current_sample: String, - current_second: String, + current_sample: f64, + current_second: f64, } impl From<(&T, bool)> for TransportView { fn from ((state, focused): (&T, bool)) -> Self { let clock = state.clock(); let sr = format!("{:.1}k", clock.timebase.sr.get() / 1000.0); - let bpm = format!("{:.3}", clock.timebase.bpm.get()); - let ppq = format!("{:.0}", clock.timebase.ppq.get()); + let bpm = format!("{:.3}", clock.timebase.bpm.get()); + let ppq = format!("{:.0}", clock.timebase.ppq.get()); if let Some(started) = clock.started.read().unwrap().as_ref() { + let current_sample = (clock.global.sample.get() - started.sample.get())/1000.; + let current_usec = clock.global.usec.get() - started.usec.get(); + let current_second = current_usec/1000000.; Self { focused, sr, @@ -88,8 +92,11 @@ impl From<(&T, bool)> for TransportView { started: true, global_sample: format!("{:.0}k", started.sample.get()/1000.), global_second: format!("{:.1}s", started.usec.get()/1000.), - current_sample: format!("{:.0}k", (clock.global.sample.get() - started.sample.get())/1000.), - current_second: format!("{:.1}s", (clock.global.usec.get() - started.usec.get())/1000000.), + current_sample, + current_second, + beat: clock.timebase.format_beats_0( + clock.timebase.usecs_to_pulse(current_usec) + ), } } else { Self { @@ -100,8 +107,9 @@ impl From<(&T, bool)> for TransportView { started: false, global_sample: format!("{:.0}k", clock.global.sample.get()/1000.), global_second: format!("{:.1}s", clock.global.usec.get()/1000000.), - current_sample: "0".to_string(), - current_second: "0.0s".to_string(), + current_sample: 0.0, + current_second: 0.0, + beat: format!("0.0.00") } } } @@ -116,16 +124,19 @@ render!(|self: TransportField<'a>|{ }); render!(|self: TransportView|{ + let bg = if self.focused { TuiTheme::border_bg() } else { TuiTheme::bg() }; let border_style = Style::default() - .bg(if self.focused { TuiTheme::border_bg() } else { TuiTheme::bg() }) + .bg(bg) .fg(TuiTheme::border_fg(self.focused)); - lay!(move|add|{ - add(&Tui::fill_x(Lozenge(border_style)))?; - add(&Tui::outset_x(1, row!([ - TransportField("Beat", "00X+0/0B+00/00P"), - " ", - TransportField("BPM ", self.bpm.as_str()), - " ", + Tui::bg(bg, lay!(move|add|{ + add(&Tui::fill_x(Tui::at_w(lay!(move|add|{ + add(&Lozenge(border_style))?; + add(&Tui::outset_x(1, row!([ + TransportField("Beat", self.beat.as_str()), " ", + TransportField("BPM ", self.bpm.as_str()), " ", + ]))) + }))))?; + add(&Tui::fill_x(Tui::center_x(Tui::pull_x(2, row!([ col!(|add|{ if self.started { add(&col!([Tui::fg(Color::Rgb(0, 255, 0), "▶ PLAYING"), ""])) @@ -133,14 +144,16 @@ render!(|self: TransportView|{ add(&col!(["", Tui::fg(Color::Rgb(255, 128, 0), "⏹ STOPPED")])) } }), - " ", - TransportField("Second", self.current_second.as_str()), - " ", - TransportField("SR ", self.sr.as_str()), - " ", - TransportField("Sample", self.current_sample.as_str()), - ]))) - }) + ])))))?; + add(&Tui::fill_x(Tui::at_e(lay!(move|add|{ + add(&Lozenge(border_style))?; + add(&Tui::outset_x(1, row!([ + TransportField("Second", format!("{:.1}s", self.current_second).as_str()), " ", + TransportField("Rate ", self.sr.as_str()), " ", + TransportField("Sample", format!("{:.0}k", self.current_sample).as_str()), + ]))) + })))) + })) }); /// Which item of the transport toolbar is focused @@ -198,7 +211,7 @@ pub struct TransportStatusBar; impl StatusBar for TransportStatusBar { type State = (); fn hotkey_fg () -> Color { - TuiTheme::hotkey_fg() + TuiTheme::HOTKEY_FG } fn update (&mut self, _: &()) { todo!() diff --git a/crates/tek/src/tui/engine_theme.rs b/crates/tek/src/tui/engine_theme.rs index e8e9ec43..738dd093 100644 --- a/crates/tek/src/tui/engine_theme.rs +++ b/crates/tek/src/tui/engine_theme.rs @@ -1,33 +1,37 @@ use crate::*; +#[derive(Copy,Clone)] pub struct TuiTheme; -impl TuiTheme { - pub fn bg () -> Color { - Color::Rgb(28, 35, 25) - } - pub fn border_bg () -> Color { - Color::Rgb(40, 50, 30) - } - pub fn border_fg (focused: bool) -> Color { - if focused { Color::Rgb(100, 110, 40) } else { Color::Rgb(70, 80, 50) } - } - pub fn title_fg (focused: bool) -> Color { - if focused { Color::Rgb(150, 160, 90) } else { Color::Rgb(120, 130, 100) } - } - pub fn separator_fg (_: bool) -> Color { +impl Theme for TuiTheme {} + +pub trait Theme { + const HOTKEY_FG: Color = Color::Rgb(255, 255, 0); + fn black () -> Color { Color::Rgb(0, 0, 0) } - pub const fn hotkey_fg () -> Color { - Color::Rgb(255, 255, 0) + fn bg () -> Color { + Color::Rgb(28, 35, 25) } - pub fn mode_bg () -> Color { + fn border_bg () -> Color { + Color::Rgb(40, 50, 30) + } + fn border_fg (focused: bool) -> Color { + if focused { Color::Rgb(100, 110, 40) } else { Color::Rgb(70, 80, 50) } + } + fn title_fg (focused: bool) -> Color { + if focused { Color::Rgb(150, 160, 90) } else { Color::Rgb(120, 130, 100) } + } + fn separator_fg (_: bool) -> Color { + Color::Rgb(0, 0, 0) + } + fn mode_bg () -> Color { Color::Rgb(150, 160, 90) } - pub fn mode_fg () -> Color { + fn mode_fg () -> Color { Color::Rgb(255, 255, 255) } - pub fn status_bar_bg () -> Color { + fn status_bar_bg () -> Color { Color::Rgb(28, 35, 25) } } diff --git a/crates/tek/src/tui/phrase_list.rs b/crates/tek/src/tui/phrase_list.rs index fce3a86c..4d1cd9ce 100644 --- a/crates/tek/src/tui/phrase_list.rs +++ b/crates/tek/src/tui/phrase_list.rs @@ -122,15 +122,13 @@ 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 border_bg = if *entered {Color::Rgb(40, 50, 30)} else {Color::Reset}; + let border_bg = if *focused {Color::Rgb(40, 50, 30)} else {Color::Reset}; let border_color = if *entered {Color::Rgb(100, 110, 40)} else {Color::Rgb(70, 80, 50)}; let title_color = if *focused {Color::Rgb(150, 160, 90)} else {Color::Rgb(120, 130, 100)}; let upper_left = format!("{title}"); let upper_right = format!("({})", phrases.len()); - lay!(move|add|{ - //if *focused { - add(&Lozenge(Style::default().bg(border_bg).fg(border_color)))?; - //} + Tui::bg(border_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) @@ -170,7 +168,7 @@ render!(|self: PhraseListView<'a>|{ }))))?; 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()))))) - }) + })) }); #[derive(Clone, PartialEq, Debug)] diff --git a/crates/tek/src/tui/phrase_select.rs b/crates/tek/src/tui/phrase_select.rs index 8d16cb25..b99a0861 100644 --- a/crates/tek/src/tui/phrase_select.rs +++ b/crates/tek/src/tui/phrase_select.rs @@ -21,9 +21,9 @@ render!(|self: PhraseSelector|{ Color::Rgb(120, 130, 100) }; 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 {