mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-07 04:06:45 +01:00
308 lines
9.3 KiB
Rust
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)
|
|
)
|
|
)
|
|
}
|