diff --git a/crates/tek/src/tui.rs b/crates/tek/src/tui.rs index b4ee76d8..e47674e6 100644 --- a/crates/tek/src/tui.rs +++ b/crates/tek/src/tui.rs @@ -1,51 +1,42 @@ use crate::*; -mod tui_input; -pub(crate) use tui_input::*; -pub use tui_input::TuiInput; +mod tui_input; pub(crate) use self::tui_input::*; +mod tui_output; pub(crate) use self::tui_output::*; -mod tui_output; -pub(crate) use tui_output::*; -pub use tui_output::TuiOutput; +pub use self::tui_input::TuiInput; +pub use self::tui_output::TuiOutput; //////////////////////////////////////////////////////// mod tui_style; -mod tui_theme; -pub(crate) use tui_theme::*; -mod tui_border; -pub(crate) use tui_border::*; +mod tui_theme; pub(crate) use self::tui_theme::*; +mod tui_border; pub(crate) use self::tui_border::*; //////////////////////////////////////////////////////// -mod app_transport; pub(crate) use app_transport::*; -mod app_sequencer; pub(crate) use app_sequencer::*; -mod app_sampler; pub(crate) use app_sampler::*; -mod app_groovebox; pub(crate) use app_groovebox::*; -mod app_arranger; pub(crate) use app_arranger::*; +mod app_transport; pub(crate) use self::app_transport::*; +mod app_sequencer; pub(crate) use self::app_sequencer::*; +mod app_sampler; pub(crate) use self::app_sampler::*; +mod app_groovebox; pub(crate) use self::app_groovebox::*; +mod app_arranger; pub(crate) use self::app_arranger::*; /////////////////////////////////////////////////////// -mod arranger_command; pub(crate) use arranger_command::*; -mod arranger_scene; pub(crate) use arranger_scene::*; -mod arranger_select; pub(crate) use arranger_select::*; -mod arranger_track; pub(crate) use arranger_track::*; -mod arranger_mode_h; -mod arranger_mode_v; pub(crate) use arranger_mode_v::*; +mod arranger_command; pub(crate) use self::arranger_command::*; +mod arranger_scene; pub(crate) use self::arranger_scene::*; +mod arranger_select; pub(crate) use self::arranger_select::*; +mod arranger_track; pub(crate) use self::arranger_track::*; +mod arranger_mode; pub(crate) use self::arranger_mode::*; +mod arranger_v; pub(crate) use self::arranger_v::*; +mod arranger_h; //////////////////////////////////////////////////////// -mod pool; pub(crate) use pool::*; - -mod phrase_editor; pub(crate) use phrase_editor::*; - -mod status_bar; pub(crate) use status_bar::*; - -mod file_browser; pub(crate) use file_browser::*; - -mod piano_horizontal; pub(crate) use piano_horizontal::*; - -mod port_select; +mod pool; pub(crate) use self::pool::*; +mod phrase_editor; pub(crate) use self::phrase_editor::*; +mod status; pub(crate) use self::status::*; +mod file_browser; pub(crate) use self::file_browser::*; +mod piano_h; pub(crate) use self::piano_h::*; //////////////////////////////////////////////////////// diff --git a/crates/tek/src/tui/app_arranger.rs b/crates/tek/src/tui/app_arranger.rs index 6a554b17..fe3f91bd 100644 --- a/crates/tek/src/tui/app_arranger.rs +++ b/crates/tek/src/tui/app_arranger.rs @@ -151,31 +151,3 @@ has_clock!(|self: ArrangerTui|&self.clock); has_phrases!(|self: ArrangerTui|self.phrases.phrases); has_editor!(|self: ArrangerTui|self.editor); handle!(|self: ArrangerTui, input|ArrangerCommand::execute_with_state(self, input)); - -/// Display mode of arranger -#[derive(Clone, PartialEq)] -pub enum ArrangerMode { - /// Tracks are columns - V(usize), - /// Tracks are rows - H, -} -render!(|self: ArrangerMode|{}); - -/// Arranger display mode can be cycled -impl ArrangerMode { - /// Cycle arranger display mode - pub fn to_next (&mut self) { - *self = match self { - Self::H => Self::V(1), - Self::V(1) => Self::V(2), - Self::V(2) => Self::V(2), - Self::V(0) => Self::H, - Self::V(_) => Self::V(0), - } - } -} - -fn any_size (_: E::Size) -> Perhaps{ - Ok(Some([0.into(),0.into()].into())) -} diff --git a/crates/tek/src/tui/arranger_h.rs b/crates/tek/src/tui/arranger_h.rs new file mode 100644 index 00000000..70b786d1 --- /dev/null +++ b/crates/tek/src/tui/arranger_h.rs @@ -0,0 +1 @@ +// TODO diff --git a/crates/tek/src/tui/arranger_mode.rs b/crates/tek/src/tui/arranger_mode.rs new file mode 100644 index 00000000..c28d8b7f --- /dev/null +++ b/crates/tek/src/tui/arranger_mode.rs @@ -0,0 +1,29 @@ +use crate::*; + +/// Display mode of arranger +#[derive(Clone, PartialEq)] +pub enum ArrangerMode { + /// Tracks are columns + V(usize), + /// Tracks are rows + H, +} +render!(|self: ArrangerMode|{}); + +/// Arranger display mode can be cycled +impl ArrangerMode { + /// Cycle arranger display mode + pub fn to_next (&mut self) { + *self = match self { + Self::H => Self::V(1), + Self::V(1) => Self::V(2), + Self::V(2) => Self::V(2), + Self::V(0) => Self::H, + Self::V(_) => Self::V(0), + } + } +} + +fn any_size (_: E::Size) -> Perhaps{ + Ok(Some([0.into(),0.into()].into())) +} diff --git a/crates/tek/src/tui/arranger_mode_h.rs b/crates/tek/src/tui/arranger_mode_h.rs deleted file mode 100644 index e69de29b..00000000 diff --git a/crates/tek/src/tui/arranger_mode_v.rs b/crates/tek/src/tui/arranger_v.rs similarity index 100% rename from crates/tek/src/tui/arranger_mode_v.rs rename to crates/tek/src/tui/arranger_v.rs diff --git a/crates/tek/src/tui/arranger_mode_v/v_clips.rs b/crates/tek/src/tui/arranger_v/v_clips.rs similarity index 100% rename from crates/tek/src/tui/arranger_mode_v/v_clips.rs rename to crates/tek/src/tui/arranger_v/v_clips.rs diff --git a/crates/tek/src/tui/arranger_mode_v/v_cursor.rs b/crates/tek/src/tui/arranger_v/v_cursor.rs similarity index 100% rename from crates/tek/src/tui/arranger_mode_v/v_cursor.rs rename to crates/tek/src/tui/arranger_v/v_cursor.rs diff --git a/crates/tek/src/tui/arranger_mode_v/v_head.rs b/crates/tek/src/tui/arranger_v/v_head.rs similarity index 100% rename from crates/tek/src/tui/arranger_mode_v/v_head.rs rename to crates/tek/src/tui/arranger_v/v_head.rs diff --git a/crates/tek/src/tui/arranger_mode_v/v_io.rs b/crates/tek/src/tui/arranger_v/v_io.rs similarity index 100% rename from crates/tek/src/tui/arranger_mode_v/v_io.rs rename to crates/tek/src/tui/arranger_v/v_io.rs diff --git a/crates/tek/src/tui/arranger_mode_v/v_sep.rs b/crates/tek/src/tui/arranger_v/v_sep.rs similarity index 100% rename from crates/tek/src/tui/arranger_mode_v/v_sep.rs rename to crates/tek/src/tui/arranger_v/v_sep.rs diff --git a/crates/tek/src/tui/piano_horizontal.rs b/crates/tek/src/tui/piano_h.rs similarity index 55% rename from crates/tek/src/tui/piano_horizontal.rs rename to crates/tek/src/tui/piano_h.rs index 045551b3..9889fbf8 100644 --- a/crates/tek/src/tui/piano_horizontal.rs +++ b/crates/tek/src/tui/piano_h.rs @@ -1,26 +1,13 @@ use crate::*; use super::*; -fn note_y_iter (note_lo: usize, note_hi: usize, y0: u16) -> impl Iterator { - (note_lo..=note_hi).rev().enumerate().map(move|(y, n)|(y, y0 + y as u16, n)) -} +mod piano_h_cursor; use self::piano_h_cursor::*; +mod piano_h_keys; use self::piano_h_keys::*; +mod piano_h_notes; use self::piano_h_notes::*; +mod piano_h_time; use self::piano_h_time::*; -fn to_key (note: usize) -> &'static str { - match note % 12 { - 11 => "████▌", - 10 => " ", - 9 => "████▌", - 8 => " ", - 7 => "████▌", - 6 => " ", - 5 => "████▌", - 4 => "████▌", - 3 => " ", - 2 => "████▌", - 1 => " ", - 0 => "████▌", - _ => unreachable!(), - } +pub(crate) fn note_y_iter (note_lo: usize, note_hi: usize, y0: u16) -> impl Iterator { + (note_lo..=note_hi).rev().enumerate().map(move|(y, n)|(y, y0 + y as u16, n)) } /// A phrase, rendered as a horizontal piano roll. @@ -76,133 +63,6 @@ render!(|self: PianoHorizontal|{ ))) }); -pub struct PianoHorizontalTimeline<'a>(&'a PianoHorizontal); -render!(|self: PianoHorizontalTimeline<'a>|render(|to|{ - let [x, y, w, h] = to.area(); - let style = Some(Style::default().dim()); - let length = self.0.phrase.as_ref().map(|p|p.read().unwrap().length).unwrap_or(1); - for (area_x, screen_x) in (0..w).map(|d|(d, d+x)) { - let t = area_x as usize * self.0.time_zoom().get(); - if t < length { - to.blit(&"|", screen_x, y, style); - } - } - Ok(()) -})); - //Tui::fg_bg( - //self.0.color.lightest.rgb, - //self.0.color.darkest.rgb, - //format!("{}*{}", self.0.time_start(), self.0.time_zoom()).as_str() -//)); - -pub struct PianoHorizontalKeys<'a>(&'a PianoHorizontal); -render!(|self: PianoHorizontalKeys<'a>|render(|to|Ok({ - let color = self.0.color; - let note_lo = self.0.note_lo().get(); - let note_hi = self.0.note_hi(); - let note_point = self.0.note_point(); - let [x, y0, w, h] = to.area().xywh(); - let key_style = Some(Style::default().fg(Color::Rgb(192, 192, 192)).bg(Color::Rgb(0, 0, 0))); - let off_style = Some(Style::default().fg(TuiTheme::g(160))); - let on_style = Some(Style::default().fg(TuiTheme::g(255)).bg(color.light.rgb).bold()); - for (area_y, screen_y, note) in note_y_iter(note_lo, note_hi, y0) { - to.blit(&to_key(note), x, screen_y, key_style); - if note > 127 { - continue - } - if note == note_point { - to.blit(&format!("{:<5}", to_note_name(note)), x, screen_y, on_style) - } else { - to.blit(&to_note_name(note), x, screen_y, off_style) - }; - } - //let debug = false; - //if debug { - //to.blit(&format!("XYU"), x, y0, None); - //to.blit(&format!("x={x}"), x, y0+1, None); - //to.blit(&format!("y0={y0}"), x, y0+2, None); - //to.blit(&format!("w={w}"), x, y0+3, None); - //to.blit(&format!("h={h}"), x, y0+4, None); - //to.blit(&format!("note_lo={note_lo}"), x, y0+5, None); - //to.blit(&format!("note_hi={note_hi}"), x, y0+6, None); - //} -}))); - -pub struct PianoHorizontalCursor<'a>(&'a PianoHorizontal); -render!(|self: PianoHorizontalCursor<'a>|render(|to|Ok({ - let style = Some(Style::default().fg(self.0.color.lightest.rgb)); - let note_hi = self.0.note_hi(); - let note_len = self.0.note_len(); - let note_lo = self.0.note_lo().get(); - let note_point = self.0.note_point(); - let time_point = self.0.time_point(); - let time_start = self.0.time_start().get(); - let time_zoom = self.0.time_zoom().get(); - let [x0, y0, w, _] = to.area().xywh(); - for (area_y, screen_y, note) in note_y_iter(note_lo, note_hi, y0) { - if note == note_point { - for x in 0..w { - let screen_x = x0 + x as u16; - let time_1 = time_start + x as usize * time_zoom; - let time_2 = time_1 + time_zoom; - if time_1 <= time_point && time_point < time_2 { - to.blit(&"█", screen_x, screen_y, style); - let tail = note_len as u16 / time_zoom as u16; - for x_tail in (screen_x + 1)..(screen_x + tail) { - to.blit(&"▂", x_tail, screen_y, style); - } - break - } - } - break - } - } -}))); - -pub struct PianoHorizontalNotes<'a>(&'a PianoHorizontal); -render!(|self: PianoHorizontalNotes<'a>|render(|to|Ok({ - let time_start = self.0.time_start().get(); - let note_lo = self.0.note_lo().get(); - let note_hi = self.0.note_hi(); - let note_point = self.0.note_point(); - let source = &self.0.buffer; - let [x0, y0, w, h] = to.area().xywh(); - if h as usize != self.0.note_axis().get() { - panic!("area height mismatch"); - } - for (area_x, screen_x) in (x0..x0+w).enumerate() { - for (area_y, screen_y, note) in note_y_iter(note_lo, note_hi, y0) { - //if area_x % 10 == 0 { - //to.blit(&format!("{area_y} {note}"), screen_x, screen_y, None); - //} - let source_x = time_start + area_x; - let source_y = note_hi - area_y; - ////// TODO: enable loop rollover: - //////let source_x = (time_start + area_x) % source.width.max(1); - //////let source_y = (note_hi - area_y) % source.height.max(1); - let is_in_x = source_x < source.width; - let is_in_y = source_y < source.height; - if is_in_x && is_in_y { - if let Some(source_cell) = source.get(source_x, source_y) { - *to.buffer.get_mut(screen_x, screen_y) = source_cell.clone(); - } - } - } - } - //let debug = true; - //if debug { - //let x0=20+x0; - //to.blit(&format!("KYP "), x0, y0, None); - //to.blit(&format!("x0={x0} "), x0, y0+1, None); - //to.blit(&format!("y0={y0} "), x0, y0+2, None); - //to.blit(&format!("note_lo={note_lo}, {} ", to_note_name(note_lo)), x0, y0+3, None); - //to.blit(&format!("note_hi={note_hi}, {} ", to_note_name(note_hi)), x0, y0+4, None); - //to.blit(&format!("note_point={note_point}, {} ", to_note_name(note_point)), x0, y0+5, None); - //to.blit(&format!("time_start={time_start} "), x0, y0+6, None); - //return Ok(()); - //} -}))); - impl PianoHorizontal { /// Draw the piano roll foreground using full blocks on note on and half blocks on legato: █▄ █▄ █▄ fn draw_bg (buf: &mut BigBuffer, phrase: &Phrase, zoom: usize, note_len: usize) { diff --git a/crates/tek/src/tui/piano_h/piano_h_cursor.rs b/crates/tek/src/tui/piano_h/piano_h_cursor.rs new file mode 100644 index 00000000..2c5dd6b5 --- /dev/null +++ b/crates/tek/src/tui/piano_h/piano_h_cursor.rs @@ -0,0 +1,33 @@ +use crate::*; +use super::note_y_iter; + +pub struct PianoHorizontalCursor<'a>(pub(crate) &'a PianoHorizontal); +render!(|self: PianoHorizontalCursor<'a>|render(|to|Ok({ + let style = Some(Style::default().fg(self.0.color.lightest.rgb)); + let note_hi = self.0.note_hi(); + let note_len = self.0.note_len(); + let note_lo = self.0.note_lo().get(); + let note_point = self.0.note_point(); + let time_point = self.0.time_point(); + let time_start = self.0.time_start().get(); + let time_zoom = self.0.time_zoom().get(); + let [x0, y0, w, _] = to.area().xywh(); + for (area_y, screen_y, note) in note_y_iter(note_lo, note_hi, y0) { + if note == note_point { + for x in 0..w { + let screen_x = x0 + x as u16; + let time_1 = time_start + x as usize * time_zoom; + let time_2 = time_1 + time_zoom; + if time_1 <= time_point && time_point < time_2 { + to.blit(&"█", screen_x, screen_y, style); + let tail = note_len as u16 / time_zoom as u16; + for x_tail in (screen_x + 1)..(screen_x + tail) { + to.blit(&"▂", x_tail, screen_y, style); + } + break + } + } + break + } + } +}))); diff --git a/crates/tek/src/tui/piano_h/piano_h_keys.rs b/crates/tek/src/tui/piano_h/piano_h_keys.rs new file mode 100644 index 00000000..91bc29a3 --- /dev/null +++ b/crates/tek/src/tui/piano_h/piano_h_keys.rs @@ -0,0 +1,53 @@ +use crate::*; +use super::note_y_iter; + +pub struct PianoHorizontalKeys<'a>(pub(crate) &'a PianoHorizontal); +render!(|self: PianoHorizontalKeys<'a>|render(|to|Ok({ + let color = self.0.color; + let note_lo = self.0.note_lo().get(); + let note_hi = self.0.note_hi(); + let note_point = self.0.note_point(); + let [x, y0, w, h] = to.area().xywh(); + let key_style = Some(Style::default().fg(Color::Rgb(192, 192, 192)).bg(Color::Rgb(0, 0, 0))); + let off_style = Some(Style::default().fg(TuiTheme::g(160))); + let on_style = Some(Style::default().fg(TuiTheme::g(255)).bg(color.light.rgb).bold()); + for (area_y, screen_y, note) in note_y_iter(note_lo, note_hi, y0) { + to.blit(&to_key(note), x, screen_y, key_style); + if note > 127 { + continue + } + if note == note_point { + to.blit(&format!("{:<5}", to_note_name(note)), x, screen_y, on_style) + } else { + to.blit(&to_note_name(note), x, screen_y, off_style) + }; + } + //let debug = false; + //if debug { + //to.blit(&format!("XYU"), x, y0, None); + //to.blit(&format!("x={x}"), x, y0+1, None); + //to.blit(&format!("y0={y0}"), x, y0+2, None); + //to.blit(&format!("w={w}"), x, y0+3, None); + //to.blit(&format!("h={h}"), x, y0+4, None); + //to.blit(&format!("note_lo={note_lo}"), x, y0+5, None); + //to.blit(&format!("note_hi={note_hi}"), x, y0+6, None); + //} +}))); + +fn to_key (note: usize) -> &'static str { + match note % 12 { + 11 => "████▌", + 10 => " ", + 9 => "████▌", + 8 => " ", + 7 => "████▌", + 6 => " ", + 5 => "████▌", + 4 => "████▌", + 3 => " ", + 2 => "████▌", + 1 => " ", + 0 => "████▌", + _ => unreachable!(), + } +} diff --git a/crates/tek/src/tui/piano_h/piano_h_notes.rs b/crates/tek/src/tui/piano_h/piano_h_notes.rs new file mode 100644 index 00000000..4d45a07d --- /dev/null +++ b/crates/tek/src/tui/piano_h/piano_h_notes.rs @@ -0,0 +1,46 @@ +use crate::*; +use super::note_y_iter; + +pub struct PianoHorizontalNotes<'a>(pub(crate) &'a PianoHorizontal); +render!(|self: PianoHorizontalNotes<'a>|render(|to|Ok({ + let time_start = self.0.time_start().get(); + let note_lo = self.0.note_lo().get(); + let note_hi = self.0.note_hi(); + let note_point = self.0.note_point(); + let source = &self.0.buffer; + let [x0, y0, w, h] = to.area().xywh(); + if h as usize != self.0.note_axis().get() { + panic!("area height mismatch"); + } + for (area_x, screen_x) in (x0..x0+w).enumerate() { + for (area_y, screen_y, note) in note_y_iter(note_lo, note_hi, y0) { + //if area_x % 10 == 0 { + //to.blit(&format!("{area_y} {note}"), screen_x, screen_y, None); + //} + let source_x = time_start + area_x; + let source_y = note_hi - area_y; + ////// TODO: enable loop rollover: + //////let source_x = (time_start + area_x) % source.width.max(1); + //////let source_y = (note_hi - area_y) % source.height.max(1); + let is_in_x = source_x < source.width; + let is_in_y = source_y < source.height; + if is_in_x && is_in_y { + if let Some(source_cell) = source.get(source_x, source_y) { + *to.buffer.get_mut(screen_x, screen_y) = source_cell.clone(); + } + } + } + } + //let debug = true; + //if debug { + //let x0=20+x0; + //to.blit(&format!("KYP "), x0, y0, None); + //to.blit(&format!("x0={x0} "), x0, y0+1, None); + //to.blit(&format!("y0={y0} "), x0, y0+2, None); + //to.blit(&format!("note_lo={note_lo}, {} ", to_note_name(note_lo)), x0, y0+3, None); + //to.blit(&format!("note_hi={note_hi}, {} ", to_note_name(note_hi)), x0, y0+4, None); + //to.blit(&format!("note_point={note_point}, {} ", to_note_name(note_point)), x0, y0+5, None); + //to.blit(&format!("time_start={time_start} "), x0, y0+6, None); + //return Ok(()); + //} +}))); diff --git a/crates/tek/src/tui/piano_h/piano_h_time.rs b/crates/tek/src/tui/piano_h/piano_h_time.rs new file mode 100644 index 00000000..6c2d4ec3 --- /dev/null +++ b/crates/tek/src/tui/piano_h/piano_h_time.rs @@ -0,0 +1,21 @@ +use crate::*; +use super::note_y_iter; + +pub struct PianoHorizontalTimeline<'a>(pub(crate) &'a PianoHorizontal); +render!(|self: PianoHorizontalTimeline<'a>|render(|to|{ + let [x, y, w, h] = to.area(); + let style = Some(Style::default().dim()); + let length = self.0.phrase.as_ref().map(|p|p.read().unwrap().length).unwrap_or(1); + for (area_x, screen_x) in (0..w).map(|d|(d, d+x)) { + let t = area_x as usize * self.0.time_zoom().get(); + if t < length { + to.blit(&"|", screen_x, y, style); + } + } + Ok(()) +})); + //Tui::fg_bg( + //self.0.color.lightest.rgb, + //self.0.color.darkest.rgb, + //format!("{}*{}", self.0.time_start(), self.0.time_zoom()).as_str() +//)); diff --git a/crates/tek/src/tui/piano_v.rs b/crates/tek/src/tui/piano_v.rs new file mode 100644 index 00000000..70b786d1 --- /dev/null +++ b/crates/tek/src/tui/piano_v.rs @@ -0,0 +1 @@ +// TODO diff --git a/crates/tek/src/tui/port_select.rs b/crates/tek/src/tui/port_select.rs deleted file mode 100644 index 1850a253..00000000 --- a/crates/tek/src/tui/port_select.rs +++ /dev/null @@ -1,7 +0,0 @@ -use crate::*; - -pub struct PortSelector { - pub(crate) title: &'static str, -} - -render!(|self: PortSelector|{}); diff --git a/crates/tek/src/tui/status.rs b/crates/tek/src/tui/status.rs new file mode 100644 index 00000000..8359e9da --- /dev/null +++ b/crates/tek/src/tui/status.rs @@ -0,0 +1,3 @@ +mod status_arranger; pub(crate) use self::status_arranger::*; +mod status_edit; pub(crate) use self::status_edit::*; +mod status_sequencer; pub(crate) use self::status_sequencer::*; diff --git a/crates/tek/src/tui/status/status_arranger.rs b/crates/tek/src/tui/status/status_arranger.rs new file mode 100644 index 00000000..7005d562 --- /dev/null +++ b/crates/tek/src/tui/status/status_arranger.rs @@ -0,0 +1,55 @@ +use crate::*; +use super::*; + +/// Status bar for arranger app +#[derive(Clone)] +pub struct ArrangerStatus { + pub(crate) width: usize, + pub(crate) cpu: Option, + pub(crate) size: String, + pub(crate) res: String, + pub(crate) playing: bool, +} +from!(|state:&ArrangerTui|ArrangerStatus = { + let samples = state.clock.chunk.load(Relaxed); + let rate = state.clock.timebase.sr.get() as f64; + let buffer = samples as f64 / rate; + let width = state.size.w(); + Self { + width, + playing: state.clock.is_rolling(), + 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.), + } +}); +render!(|self: ArrangerStatus|Fixed::h(2, lay!([ + Self::help(), + Fill::wh(Align::se(Tui::fg_bg(TuiTheme::orange(), TuiTheme::g(25), self.stats()))), +]))); +impl ArrangerStatus { + fn help () -> impl Render { + let single = |binding, command|row!([" ", col!([ + Tui::fg(TuiTheme::yellow(), binding), + command + ])]); + let double = |(b1, c1), (b2, c2)|col!([ + row!([" ", Tui::fg(TuiTheme::yellow(), b1), " ", c1,]), + row!([" ", Tui::fg(TuiTheme::yellow(), b2), " ", c2,]), + ]); + Tui::fg_bg(TuiTheme::g(255), TuiTheme::g(50), row!([ + single("SPACE", "play/pause"), + single(" Ctrl", " scroll"), + single(" ▲▼▶◀", " cell"), + double(("p", "put"), ("g", "get")), + double(("q", "enqueue"), ("e", "edit")), + single(" wsad", " note"), + double(("a", "append"), ("s", "set"),), + double((",.", "length"), ("<>", "triplet"),), + double(("[]", "phrase"), ("{}", "order"),), + ])) + } + fn stats <'a> (&'a self) -> impl Render + use<'a> { + row!([&self.cpu, &self.res, &self.size]) + } +} diff --git a/crates/tek/src/tui/status/status_edit.rs b/crates/tek/src/tui/status/status_edit.rs new file mode 100644 index 00000000..149dd2f0 --- /dev/null +++ b/crates/tek/src/tui/status/status_edit.rs @@ -0,0 +1,31 @@ +use crate::*; +use super::*; + +pub struct PhraseEditStatus<'a>(pub &'a PhraseEditorModel); +render!(|self:PhraseEditStatus<'a>|{ + let (color, name, length, looped) = if let Some(phrase) = self.0.phrase().as_ref().map(|p|p.read().unwrap()) { + (phrase.color, phrase.name.clone(), phrase.length, phrase.looped) + } else { + (ItemPalette::from(TuiTheme::g(64)), String::new(), 0, false) + }; + let bg = color.darkest.rgb; + let fg = color.lightest.rgb; + let field = move|x, y|row!([ + Tui::fg_bg(color.lighter.rgb, color.darker.rgb, Tui::bold(true, x)), + Tui::fg_bg(color.light.rgb, color.darker.rgb, Tui::bold(true, "│")), + Fill::w(Tui::fg_bg(color.lightest.rgb, color.dark.rgb, &y)), + ]); + Fill::w(Tui::fg_bg(fg, bg, row!([ + Fixed::wh(26, 3, col!(![ + field(" Edit", format!("{name}")), + field(" Length", format!("{length}")), + field(" Loop", format!("{looped}"))])), + Fixed::wh(30, 3, col!(![ + field(" Time", format!("{}/{}-{} ({}*{}) {}", + self.0.time_point(), self.0.time_start().get(), self.0.time_end(), + self.0.time_axis().get(), self.0.time_zoom().get(), + if self.0.time_lock().get() { "[lock]" } else { " " })), + field(" Note", format!("{} ({}) {} | {}-{} ({})", + self.0.note_point(), to_note_name(self.0.note_point()), self.0.note_len(), + to_note_name(self.0.note_lo().get()), to_note_name(self.0.note_hi()), + self.0.note_axis().get()))]))])))}); diff --git a/crates/tek/src/tui/status/status_sequencer.rs b/crates/tek/src/tui/status/status_sequencer.rs new file mode 100644 index 00000000..347e2cb1 --- /dev/null +++ b/crates/tek/src/tui/status/status_sequencer.rs @@ -0,0 +1,53 @@ +use crate::*; +use super::*; + +/// Status bar for sequencer app +#[derive(Clone)] +pub struct SequencerStatus { + pub(crate) width: usize, + pub(crate) cpu: Option, + pub(crate) size: String, + pub(crate) res: String, + pub(crate) playing: bool, +} +from!(|state:&SequencerTui|SequencerStatus = { + let samples = state.clock.chunk.load(Relaxed); + let rate = state.clock.timebase.sr.get() as f64; + let buffer = samples as f64 / rate; + let width = state.size.w(); + Self { + width, + playing: state.clock.is_rolling(), + 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.), + } +}); +render!(|self: SequencerStatus|Fixed::h(2, lay!([ + Self::help(), + Fill::wh(Align::se(Tui::fg_bg(TuiTheme::orange(), TuiTheme::g(25), self.stats()))), +]))); +impl SequencerStatus { + fn help () -> impl Render { + let single = |binding, command|row!([" ", col!([ + Tui::fg(TuiTheme::yellow(), binding), + command + ])]); + let double = |(b1, c1), (b2, c2)|col!([ + row!([" ", Tui::fg(TuiTheme::yellow(), b1), " ", c1,]), + row!([" ", Tui::fg(TuiTheme::yellow(), b2), " ", c2,]), + ]); + Tui::fg_bg(TuiTheme::g(255), TuiTheme::g(50), row!([ + single("SPACE", "play/pause"), + double(("▲▼▶◀", "cursor"), ("Ctrl", "scroll"), ), + double(("a", "append"), ("s", "set note"),), + double((",.", "length"), ("<>", "triplet"), ), + double(("[]", "phrase"), ("{}", "order"), ), + double(("q", "enqueue"), ("e", "edit"), ), + double(("c", "color"), ("", ""),), + ])) + } + fn stats <'a> (&'a self) -> impl Render + use<'a> { + row!([&self.cpu, &self.res, &self.size]) + } +} diff --git a/crates/tek/src/tui/status_bar.rs b/crates/tek/src/tui/status_bar.rs deleted file mode 100644 index a0cc3bc1..00000000 --- a/crates/tek/src/tui/status_bar.rs +++ /dev/null @@ -1,134 +0,0 @@ -use crate::*; - -pub struct PhraseEditStatus<'a>(pub &'a PhraseEditorModel); -render!(|self:PhraseEditStatus<'a>|{ - let (color, name, length, looped) = if let Some(phrase) = self.0.phrase().as_ref().map(|p|p.read().unwrap()) { - (phrase.color, phrase.name.clone(), phrase.length, phrase.looped) - } else { - (ItemPalette::from(TuiTheme::g(64)), String::new(), 0, false) - }; - let bg = color.darkest.rgb; - let fg = color.lightest.rgb; - let field = move|x, y|row!([ - Tui::fg_bg(color.lighter.rgb, color.darker.rgb, Tui::bold(true, x)), - Tui::fg_bg(color.light.rgb, color.darker.rgb, Tui::bold(true, "│")), - Fill::w(Tui::fg_bg(color.lightest.rgb, color.dark.rgb, &y)), - ]); - Fill::w(Tui::fg_bg(fg, bg, row!([ - Fixed::wh(26, 3, col!(![ - field(" Edit", format!("{name}")), - field(" Length", format!("{length}")), - field(" Loop", format!("{looped}"))])), - Fixed::wh(30, 3, col!(![ - field(" Time", format!("{}/{}-{} ({}*{}) {}", - self.0.time_point(), self.0.time_start().get(), self.0.time_end(), - self.0.time_axis().get(), self.0.time_zoom().get(), - if self.0.time_lock().get() { "[lock]" } else { " " })), - field(" Note", format!("{} ({}) {} | {}-{} ({})", - self.0.note_point(), to_note_name(self.0.note_point()), self.0.note_len(), - to_note_name(self.0.note_lo().get()), to_note_name(self.0.note_hi()), - self.0.note_axis().get()))]))])))}); - -/// Status bar for sequencer app -#[derive(Clone)] -pub struct SequencerStatus { - pub(crate) width: usize, - pub(crate) cpu: Option, - pub(crate) size: String, - pub(crate) res: String, - pub(crate) playing: bool, -} -from!(|state:&SequencerTui|SequencerStatus = { - let samples = state.clock.chunk.load(Relaxed); - let rate = state.clock.timebase.sr.get() as f64; - let buffer = samples as f64 / rate; - let width = state.size.w(); - Self { - width, - playing: state.clock.is_rolling(), - 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.), - } -}); -render!(|self: SequencerStatus|Fixed::h(2, lay!([ - Self::help(), - Fill::wh(Align::se(Tui::fg_bg(TuiTheme::orange(), TuiTheme::g(25), self.stats()))), -]))); -impl SequencerStatus { - fn help () -> impl Render { - let single = |binding, command|row!([" ", col!([ - Tui::fg(TuiTheme::yellow(), binding), - command - ])]); - let double = |(b1, c1), (b2, c2)|col!([ - row!([" ", Tui::fg(TuiTheme::yellow(), b1), " ", c1,]), - row!([" ", Tui::fg(TuiTheme::yellow(), b2), " ", c2,]), - ]); - Tui::fg_bg(TuiTheme::g(255), TuiTheme::g(50), row!([ - single("SPACE", "play/pause"), - double(("▲▼▶◀", "cursor"), ("Ctrl", "scroll"), ), - double(("a", "append"), ("s", "set note"),), - double((",.", "length"), ("<>", "triplet"), ), - double(("[]", "phrase"), ("{}", "order"), ), - double(("q", "enqueue"), ("e", "edit"), ), - double(("c", "color"), ("", ""),), - ])) - } - fn stats <'a> (&'a self) -> impl Render + use<'a> { - row!([&self.cpu, &self.res, &self.size]) - } -} - -/// Status bar for arranger app -#[derive(Clone)] -pub struct ArrangerStatus { - pub(crate) width: usize, - pub(crate) cpu: Option, - pub(crate) size: String, - pub(crate) res: String, - pub(crate) playing: bool, -} -from!(|state:&ArrangerTui|ArrangerStatus = { - let samples = state.clock.chunk.load(Relaxed); - let rate = state.clock.timebase.sr.get() as f64; - let buffer = samples as f64 / rate; - let width = state.size.w(); - Self { - width, - playing: state.clock.is_rolling(), - 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.), - } -}); -render!(|self: ArrangerStatus|Fixed::h(2, lay!([ - Self::help(), - Fill::wh(Align::se(Tui::fg_bg(TuiTheme::orange(), TuiTheme::g(25), self.stats()))), -]))); -impl ArrangerStatus { - fn help () -> impl Render { - let single = |binding, command|row!([" ", col!([ - Tui::fg(TuiTheme::yellow(), binding), - command - ])]); - let double = |(b1, c1), (b2, c2)|col!([ - row!([" ", Tui::fg(TuiTheme::yellow(), b1), " ", c1,]), - row!([" ", Tui::fg(TuiTheme::yellow(), b2), " ", c2,]), - ]); - Tui::fg_bg(TuiTheme::g(255), TuiTheme::g(50), row!([ - single("SPACE", "play/pause"), - single(" Ctrl", " scroll"), - single(" ▲▼▶◀", " cell"), - double(("p", "put"), ("g", "get")), - double(("q", "enqueue"), ("e", "edit")), - single(" wsad", " note"), - double(("a", "append"), ("s", "set"),), - double((",.", "length"), ("<>", "triplet"),), - double(("[]", "phrase"), ("{}", "order"),), - ])) - } - fn stats <'a> (&'a self) -> impl Render + use<'a> { - row!([&self.cpu, &self.res, &self.size]) - } -}