tek/crates/tek_tui/src/lib.rs

308 lines
9.3 KiB
Rust

pub(crate) use tek_core::crossterm::event::{KeyCode, KeyModifiers};
pub(crate) use tek_core::midly::{num::u7, live::LiveEvent, MidiMessage};
pub(crate) use tek_core::{*, jack::*};
pub(crate) use tek_api::*;
pub(crate) use std::collections::BTreeMap;
pub(crate) use std::sync::{Arc, Mutex, RwLock};
pub(crate) use std::path::PathBuf;
pub(crate) use std::ffi::OsString;
pub(crate) use std::fs::read_dir;
submod! {
tui_app_arranger
tui_app_sequencer
tui_app_transport
tui_jack_transport
tui_jack_sequencer
tui_jack_arranger
tui_control_arranger
tui_control_file_browser
tui_control_phrase_editor
tui_control_phrase_length
tui_control_phrase_list
tui_control_phrase_rename
tui_control_sequencer
tui_control_transport
tui_model_arranger
tui_model_clock
tui_model_file_browser
tui_model_phrase_editor
tui_model_phrase_length
tui_model_phrase_list
tui_model_phrase_player
tui_view_arranger
tui_view_file_browser
tui_view_phrase_editor
tui_view_phrase_length
tui_view_phrase_list
tui_view_phrase_selector
tui_view_sequencer
tui_view_transport
}
pub fn to_focus_command (input: &TuiInput) -> Option<FocusCommand> {
use KeyCode::{Tab, BackTab, Up, Down, Left, Right, Enter, Esc};
Some(match input.event() {
key!(Tab) => FocusCommand::Next,
key!(Shift-Tab) => FocusCommand::Prev,
key!(BackTab) => FocusCommand::Prev,
key!(Shift-BackTab) => FocusCommand::Prev,
key!(Up) => FocusCommand::Up,
key!(Down) => FocusCommand::Down,
key!(Left) => FocusCommand::Left,
key!(Right) => FocusCommand::Right,
key!(Enter) => FocusCommand::Enter,
key!(Esc) => FocusCommand::Exit,
_ => return None
})
}
pub struct TuiTheme;
impl TuiTheme {
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 {
Color::Rgb(0, 0, 0)
}
pub const fn hotkey_fg () -> Color {
Color::Rgb(255, 255, 0)
}
pub fn mode_bg () -> Color {
Color::Rgb(150, 160, 90)
}
pub fn mode_fg () -> Color {
Color::Rgb(255, 255, 255)
}
pub fn status_bar_bg () -> Color {
Color::Rgb(28, 35, 25)
}
}
macro_rules! impl_midi_player {
($Struct:ident $(:: $field:ident)*) => {
impl HasPhrase for $Struct {
fn reset (&self) -> bool {
self$(.$field)*.reset
}
fn reset_mut (&mut self) -> &mut bool {
&mut self$(.$field)*.reset
}
fn play_phrase (&self) -> &Option<(Instant, Option<Arc<RwLock<Phrase>>>)> {
&self$(.$field)*.play_phrase
}
fn play_phrase_mut (&mut self) -> &mut Option<(Instant, Option<Arc<RwLock<Phrase>>>)> {
&mut self$(.$field)*.play_phrase
}
fn next_phrase (&self) -> &Option<(Instant, Option<Arc<RwLock<Phrase>>>)> {
&self$(.$field)*.next_phrase
}
fn next_phrase_mut (&mut self) -> &mut Option<(Instant, Option<Arc<RwLock<Phrase>>>)> {
&mut self$(.$field)*.next_phrase
}
}
impl MidiInputApi for $Struct {
fn midi_ins (&self) -> &Vec<Port<jack::MidiIn>> {
&self$(.$field)*.midi_ins
}
fn midi_ins_mut (&mut self) -> &mut Vec<Port<jack::MidiIn>> {
&mut self$(.$field)*.midi_ins
}
fn recording (&self) -> bool {
self$(.$field)*.recording
}
fn recording_mut (&mut self) -> &mut bool {
&mut self$(.$field)*.recording
}
fn monitoring (&self) -> bool {
self$(.$field)*.monitoring
}
fn monitoring_mut (&mut self) -> &mut bool {
&mut self$(.$field)*.monitoring
}
fn overdub (&self) -> bool {
self$(.$field)*.overdub
}
fn overdub_mut (&mut self) -> &mut bool {
&mut self$(.$field)*.overdub
}
fn notes_in (&self) -> &Arc<RwLock<[bool; 128]>> {
&self$(.$field)*.notes_in
}
}
impl MidiOutputApi for $Struct {
fn midi_outs (&self) -> &Vec<Port<jack::MidiOut>> {
&self$(.$field)*.midi_outs
}
fn midi_outs_mut (&mut self) -> &mut Vec<Port<jack::MidiOut>> {
&mut self$(.$field)*.midi_outs
}
fn midi_note (&mut self) -> &mut Vec<u8> {
&mut self$(.$field)*.note_buf
}
fn notes_out (&self) -> &Arc<RwLock<[bool; 128]>> {
&self$(.$field)*.notes_in
}
}
impl MidiPlayerApi for $Struct {}
}
}
impl_midi_player!(SequencerTui::player);
impl_midi_player!(ArrangerTrack::player);
impl_midi_player!(PhrasePlayerModel);
use std::fmt::{Debug, Formatter, Error};
type DebugResult = std::result::Result<(), Error>;
impl Debug for TransportTui {
fn fmt (&self, f: &mut Formatter<'_>) -> DebugResult {
f.debug_struct("Measure")
.field("jack", &self.jack)
.field("size", &self.size)
.field("cursor", &self.cursor)
.finish()
}
}
impl Debug for PhraseEditorModel {
fn fmt (&self, f: &mut Formatter<'_>) -> DebugResult {
f.debug_struct("editor")
.field("note_axis", &self.time_axis)
.field("time_axis", &self.note_axis)
.finish()
}
}
impl Debug for PhrasePlayerModel {
fn fmt (&self, f: &mut Formatter<'_>) -> DebugResult {
f.debug_struct("editor")
.field("clock", &self.clock)
.field("play_phrase", &self.play_phrase)
.field("next_phrase", &self.next_phrase)
.finish()
}
}
pub trait FocusWrap<T> {
fn wrap <'a, W: Widget<Engine = Tui>> (self, focus: T, content: &'a W)
-> impl Widget<Engine = Tui> + 'a;
}
#[macro_export] macro_rules! impl_focus {
($Struct:ident $Focus:ident $Grid:expr) => {
impl HasFocus for $Struct {
type Item = $Focus;
/// Get the currently focused item.
fn focused (&self) -> Self::Item {
self.focus.inner()
}
/// Get the currently focused item.
fn set_focused (&mut self, to: Self::Item) {
self.focus.set_inner(to)
}
}
impl HasEnter for $Struct {
/// Get the currently focused item.
fn entered (&self) -> bool {
self.focus.is_entered()
}
/// Get the currently focused item.
fn set_entered (&mut self, entered: bool) {
if entered {
self.focus.to_entered()
} else {
self.focus.to_focused()
}
}
}
impl FocusGrid for $Struct {
fn focus_cursor (&self) -> (usize, usize) {
self.cursor
}
fn focus_cursor_mut (&mut self) -> &mut (usize, usize) {
&mut self.cursor
}
fn focus_layout (&self) -> &[&[$Focus]] {
use $Focus::*;
&$Grid
}
}
}
}
pub trait StatusBar: Widget<Engine = Tui> {
type State;
fn hotkey_fg () -> Color where Self: Sized;
fn update (&mut self, state: &Self::State) where Self: Sized;
fn command (commands: &[[impl Widget<Engine = Tui>;3]])
-> impl Widget<Engine = Tui> + '_
where
Self: Sized
{
let hotkey_fg = Self::hotkey_fg();
Stack::right(move |add|{
Ok(for [a, b, c] in commands.iter() {
add(&row!(
" ",
widget(a),
widget(b).bold(true).fg(hotkey_fg),
widget(c),
))?;
})
})
}
fn with <'a> (state: &'a Self::State, content: impl Widget<Engine=Tui>) -> impl Widget<Engine=Tui>
where Self: Sized, &'a Self::State: Into<Self>
{
Split::up(1, state.into(), content)
}
}
fn content_with_menu_and_status <'a, A, S, C> (
content: &'a A,
menu_bar: &'a Option<MenuBar<Tui, S, C>>,
status_bar: &'a Option<impl StatusBar>
) -> impl Widget<Engine = Tui> + 'a
where
A: Widget<Engine = Tui>,
S: Send + Sync + 'a,
C: Command<S>
{
let menus = menu_bar.as_ref().map_or_else(
||&[] as &[Menu<_, _, _>],
|m|m.menus.as_slice()
);
Either(
menu_bar.is_none(),
Either(
status_bar.is_none(),
widget(content),
Split::up(
1,
widget(status_bar.as_ref().unwrap()),
widget(content)
),
),
Split::down(
1,
row!(menu in menus.iter() => {
row!(" ", menu.title.as_str(), " ")
}),
widget(content)
)
)
}