mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-07 12:16:42 +01:00
235 lines
7.8 KiB
Rust
235 lines
7.8 KiB
Rust
use crate::*;
|
|
|
|
/// Root view for standalone `tek_arranger`
|
|
pub struct ArrangerTui {
|
|
pub jack: Arc<RwLock<JackClient>>,
|
|
pub clock: ClockModel,
|
|
pub phrases: PhraseListModel,
|
|
pub tracks: Vec<ArrangerTrack>,
|
|
pub scenes: Vec<ArrangerScene>,
|
|
pub name: Arc<RwLock<String>>,
|
|
pub splits: [u16;2],
|
|
pub selected: ArrangerSelection,
|
|
pub mode: ArrangerMode,
|
|
pub color: ItemColor,
|
|
pub entered: bool,
|
|
pub size: Measure<Tui>,
|
|
pub cursor: (usize, usize),
|
|
pub menu_bar: Option<MenuBar<Tui, Self, ArrangerCommand>>,
|
|
pub status_bar: Option<ArrangerStatus>,
|
|
pub history: Vec<ArrangerCommand>,
|
|
pub note_buf: Vec<u8>,
|
|
pub midi_buf: Vec<Vec<Vec<u8>>>,
|
|
pub editor: PhraseEditorModel,
|
|
pub focus: FocusState<ArrangerFocus>,
|
|
pub perf: PerfModel,
|
|
}
|
|
|
|
impl TryFrom<&Arc<RwLock<JackClient>>> for ArrangerTui {
|
|
type Error = Box<dyn std::error::Error>;
|
|
fn try_from (jack: &Arc<RwLock<JackClient>>) -> Usually<Self> {
|
|
Ok(Self {
|
|
jack: jack.clone(),
|
|
clock: ClockModel::from(jack),
|
|
phrases: PhraseListModel::default(),
|
|
editor: PhraseEditorModel::default(),
|
|
selected: ArrangerSelection::Clip(0, 0),
|
|
scenes: vec![],
|
|
tracks: vec![],
|
|
color: Color::Rgb(28, 35, 25).into(),
|
|
history: vec![],
|
|
mode: ArrangerMode::Vertical(2),
|
|
name: Arc::new(RwLock::new(String::new())),
|
|
size: Measure::new(),
|
|
cursor: (0, 0),
|
|
splits: [20, 20],
|
|
entered: false,
|
|
menu_bar: None,
|
|
status_bar: None,
|
|
midi_buf: vec![vec![];65536],
|
|
note_buf: vec![],
|
|
perf: PerfModel::default(),
|
|
focus: FocusState::Entered(ArrangerFocus::Transport(TransportFocus::PlayPause)),
|
|
})
|
|
}
|
|
}
|
|
|
|
impl HasClock for ArrangerTui {
|
|
fn clock (&self) -> &ClockModel {
|
|
&self.clock
|
|
}
|
|
}
|
|
impl HasClock for ArrangerTrack {
|
|
fn clock (&self) -> &ClockModel {
|
|
&self.player.clock()
|
|
}
|
|
}
|
|
impl HasPhrases for ArrangerTui {
|
|
fn phrases (&self) -> &Vec<Arc<RwLock<Phrase>>> {
|
|
&self.phrases.phrases
|
|
}
|
|
fn phrases_mut (&mut self) -> &mut Vec<Arc<RwLock<Phrase>>> {
|
|
&mut self.phrases.phrases
|
|
}
|
|
}
|
|
|
|
/// Sections in the arranger app that may be focused
|
|
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
|
pub enum ArrangerFocus {
|
|
/// The transport (toolbar) is focused
|
|
Transport(TransportFocus),
|
|
/// The arrangement (grid) is focused
|
|
Arranger,
|
|
/// The phrase list (pool) is focused
|
|
Phrases,
|
|
/// The phrase editor (sequencer) is focused
|
|
PhraseEditor,
|
|
}
|
|
|
|
impl_focus!(ArrangerTui ArrangerFocus [
|
|
//&[
|
|
//Menu,
|
|
//Menu,
|
|
//Menu,
|
|
//Menu,
|
|
//Menu,
|
|
//],
|
|
&[
|
|
Transport(TransportFocus::PlayPause),
|
|
Transport(TransportFocus::Bpm),
|
|
Transport(TransportFocus::Sync),
|
|
Transport(TransportFocus::Quant),
|
|
Transport(TransportFocus::Clock),
|
|
], &[
|
|
Arranger,
|
|
Arranger,
|
|
Arranger,
|
|
Arranger,
|
|
Arranger,
|
|
], &[
|
|
Phrases,
|
|
Phrases,
|
|
PhraseEditor,
|
|
PhraseEditor,
|
|
PhraseEditor,
|
|
],
|
|
]);
|
|
|
|
/// Status bar for arranger app
|
|
#[derive(Copy, Clone, Debug)]
|
|
pub enum ArrangerStatus {
|
|
Transport,
|
|
ArrangerMix,
|
|
ArrangerTrack,
|
|
ArrangerScene,
|
|
ArrangerClip,
|
|
PhrasePool,
|
|
PhraseView,
|
|
PhraseEdit,
|
|
}
|
|
|
|
impl StatusBar for ArrangerStatus {
|
|
type State = (ArrangerFocus, ArrangerSelection, bool);
|
|
fn hotkey_fg () -> Color where Self: Sized {
|
|
TuiTheme::hotkey_fg()
|
|
}
|
|
fn update (&mut self, (focused, selected, entered): &Self::State) {
|
|
*self = match focused {
|
|
//ArrangerFocus::Menu => { todo!() },
|
|
ArrangerFocus::Transport(_) => ArrangerStatus::Transport,
|
|
ArrangerFocus::Arranger => match selected {
|
|
ArrangerSelection::Mix => ArrangerStatus::ArrangerMix,
|
|
ArrangerSelection::Track(_) => ArrangerStatus::ArrangerTrack,
|
|
ArrangerSelection::Scene(_) => ArrangerStatus::ArrangerScene,
|
|
ArrangerSelection::Clip(_, _) => ArrangerStatus::ArrangerClip,
|
|
},
|
|
ArrangerFocus::Phrases => ArrangerStatus::PhrasePool,
|
|
ArrangerFocus::PhraseEditor => match entered {
|
|
true => ArrangerStatus::PhraseEdit,
|
|
false => ArrangerStatus::PhraseView,
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Content<Tui> for ArrangerStatus {
|
|
fn content (&self) -> impl Render<Tui> {
|
|
let label = match self {
|
|
Self::Transport => "TRANSPORT",
|
|
Self::ArrangerMix => "PROJECT",
|
|
Self::ArrangerTrack => "TRACK",
|
|
Self::ArrangerScene => "SCENE",
|
|
Self::ArrangerClip => "CLIP",
|
|
Self::PhrasePool => "SEQ LIST",
|
|
Self::PhraseView => "VIEW SEQ",
|
|
Self::PhraseEdit => "EDIT SEQ",
|
|
};
|
|
let status_bar_bg = TuiTheme::status_bar_bg();
|
|
let mode_bg = TuiTheme::mode_bg();
|
|
let mode_fg = TuiTheme::mode_fg();
|
|
let mode = TuiStyle::bold(format!(" {label} "), true).bg(mode_bg).fg(mode_fg);
|
|
let commands = match self {
|
|
Self::ArrangerMix => Self::command(&[
|
|
["", "c", "olor"],
|
|
["", "<>", "resize"],
|
|
["", "+-", "zoom"],
|
|
["", "n", "ame/number"],
|
|
["", "Enter", " stop all"],
|
|
]),
|
|
Self::ArrangerClip => Self::command(&[
|
|
["", "g", "et"],
|
|
["", "s", "et"],
|
|
["", "a", "dd"],
|
|
["", "i", "ns"],
|
|
["", "d", "up"],
|
|
["", "e", "dit"],
|
|
["", "c", "olor"],
|
|
["re", "n", "ame"],
|
|
["", ",.", "select"],
|
|
["", "Enter", " launch"],
|
|
]),
|
|
Self::ArrangerTrack => Self::command(&[
|
|
["re", "n", "ame"],
|
|
["", ",.", "resize"],
|
|
["", "<>", "move"],
|
|
["", "i", "nput"],
|
|
["", "o", "utput"],
|
|
["", "m", "ute"],
|
|
["", "s", "olo"],
|
|
["", "Del", "ete"],
|
|
["", "Enter", " stop"],
|
|
]),
|
|
Self::ArrangerScene => Self::command(&[
|
|
["re", "n", "ame"],
|
|
["", "Del", "ete"],
|
|
["", "Enter", " launch"],
|
|
]),
|
|
Self::PhrasePool => Self::command(&[
|
|
["", "a", "ppend"],
|
|
["", "i", "nsert"],
|
|
["", "d", "uplicate"],
|
|
["", "Del", "ete"],
|
|
["", "c", "olor"],
|
|
["re", "n", "ame"],
|
|
["leng", "t", "h"],
|
|
["", ",.", "move"],
|
|
["", "+-", "resize view"],
|
|
]),
|
|
Self::PhraseView => Self::command(&[
|
|
["", "enter", " edit"],
|
|
["", "arrows/pgup/pgdn", " scroll"],
|
|
["", "+=", "zoom"],
|
|
]),
|
|
Self::PhraseEdit => Self::command(&[
|
|
["", "esc", " exit"],
|
|
["", "a", "ppend"],
|
|
["", "s", "et"],
|
|
["", "][", "length"],
|
|
["", "+-", "zoom"],
|
|
]),
|
|
_ => Self::command(&[])
|
|
};
|
|
//let commands = commands.iter().reduce(String::new(), |s, (a, b, c)| format!("{s} {a}{b}{c}"));
|
|
row!(mode, commands).fill_x().bg(status_bar_bg)
|
|
}
|
|
}
|