mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 19:56:42 +01:00
wip: p.43, e=22, almost 10kloc!
This commit is contained in:
parent
dbf6e353b7
commit
a7998860b1
9 changed files with 1177 additions and 1166 deletions
|
|
@ -13,8 +13,14 @@ use std::fmt::Debug;
|
||||||
|
|
||||||
submod! {
|
submod! {
|
||||||
tui_arranger
|
tui_arranger
|
||||||
tui_arranger_track
|
tui_arranger_cmd
|
||||||
|
tui_arranger_focus
|
||||||
tui_arranger_scene
|
tui_arranger_scene
|
||||||
|
tui_arranger_select
|
||||||
|
tui_arranger_status
|
||||||
|
tui_arranger_track
|
||||||
|
tui_arranger_view
|
||||||
|
|
||||||
//tui_mixer // TODO
|
//tui_mixer // TODO
|
||||||
tui_phrase
|
tui_phrase
|
||||||
//tui_plugin // TODO
|
//tui_plugin // TODO
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
221
crates/tek_tui/src/tui_arranger_cmd.rs
Normal file
221
crates/tek_tui/src/tui_arranger_cmd.rs
Normal file
|
|
@ -0,0 +1,221 @@
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
/// Handle top-level events in standalone arranger.
|
||||||
|
impl Handle<Tui> for ArrangerApp<Tui> {
|
||||||
|
fn handle (&mut self, i: &TuiInput) -> Perhaps<bool> {
|
||||||
|
ArrangerAppCommand::execute_with_state(self, i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type ArrangerAppCommand = AppViewCommand<ArrangerViewCommand>;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum ArrangerViewCommand {
|
||||||
|
Clear,
|
||||||
|
Scene(ArrangerSceneCommand),
|
||||||
|
Track(ArrangerTrackCommand),
|
||||||
|
Clip(ArrangerClipCommand),
|
||||||
|
Select(ArrangerSelection),
|
||||||
|
Zoom(usize),
|
||||||
|
Clock(ClockCommand),
|
||||||
|
Playhead(PlayheadCommand),
|
||||||
|
Phrases(PhrasePoolViewCommand),
|
||||||
|
Editor(PhraseEditorCommand),
|
||||||
|
EditPhrase(Option<Arc<RwLock<Phrase>>>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InputToCommand<Tui, ArrangerApp<Tui>> for ArrangerAppCommand {
|
||||||
|
fn input_to_command (view: &ArrangerApp<Tui>, input: &TuiInput) -> Option<Self> {
|
||||||
|
use AppViewFocus::*;
|
||||||
|
use FocusCommand::*;
|
||||||
|
use ArrangerViewCommand::*;
|
||||||
|
Some(match input.event() {
|
||||||
|
key!(KeyCode::Tab) => Self::Focus(Next),
|
||||||
|
key!(Shift-KeyCode::Tab) => Self::Focus(Prev),
|
||||||
|
key!(KeyCode::BackTab) => Self::Focus(Prev),
|
||||||
|
key!(Shift-KeyCode::BackTab) => Self::Focus(Prev),
|
||||||
|
key!(KeyCode::Up) => Self::Focus(Up),
|
||||||
|
key!(KeyCode::Down) => Self::Focus(Down),
|
||||||
|
key!(KeyCode::Left) => Self::Focus(Left),
|
||||||
|
key!(KeyCode::Right) => Self::Focus(Right),
|
||||||
|
key!(KeyCode::Enter) => Self::Focus(Enter),
|
||||||
|
key!(KeyCode::Esc) => Self::Focus(Exit),
|
||||||
|
key!(KeyCode::Char(' ')) => {
|
||||||
|
Self::App(Playhead(PlayheadCommand::Play(None)))
|
||||||
|
},
|
||||||
|
_ => Self::App(match view.focused() {
|
||||||
|
Content(ArrangerFocus::Transport) => {
|
||||||
|
use TransportCommand::{Clock, Playhead};
|
||||||
|
match TransportCommand::input_to_command(view, input)? {
|
||||||
|
Clock(command) => {
|
||||||
|
todo!()
|
||||||
|
},
|
||||||
|
Playhead(command) => {
|
||||||
|
todo!()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Content(ArrangerFocus::PhraseEditor) => Editor(
|
||||||
|
PhraseEditorCommand::input_to_command(&view.app.editor, input)?
|
||||||
|
),
|
||||||
|
Content(ArrangerFocus::PhrasePool) => match input.event() {
|
||||||
|
key!(KeyCode::Char('e')) => EditPhrase(
|
||||||
|
Some(view.app.phrase().clone())
|
||||||
|
),
|
||||||
|
_ => Phrases(
|
||||||
|
PhrasePoolViewCommand::input_to_command(view, input)?
|
||||||
|
)
|
||||||
|
},
|
||||||
|
Content(ArrangerFocus::Arranger) => {
|
||||||
|
use ArrangerSelection as Select;
|
||||||
|
use ArrangerTrackCommand as Track;
|
||||||
|
use ArrangerClipCommand as Clip;
|
||||||
|
use ArrangerSceneCommand as Scene;
|
||||||
|
match input.event() {
|
||||||
|
key!(KeyCode::Char('e')) => EditPhrase(view.phrase()),
|
||||||
|
_ => match input.event() {
|
||||||
|
// FIXME: boundary conditions
|
||||||
|
|
||||||
|
key!(KeyCode::Up) => match view.app.selected {
|
||||||
|
Select::Mix => return None,
|
||||||
|
Select::Track(t) => return None,
|
||||||
|
Select::Scene(s) => Select(Select::Scene(s - 1)),
|
||||||
|
Select::Clip(t, s) => Select(Select::Clip(t, s - 1)),
|
||||||
|
},
|
||||||
|
|
||||||
|
key!(KeyCode::Down) => match view.app.selected {
|
||||||
|
Select::Mix => Select(Select::Scene(0)),
|
||||||
|
Select::Track(t) => Select(Select::Clip(t, 0)),
|
||||||
|
Select::Scene(s) => Select(Select::Scene(s + 1)),
|
||||||
|
Select::Clip(t, s) => Select(Select::Clip(t, s + 1)),
|
||||||
|
},
|
||||||
|
|
||||||
|
key!(KeyCode::Left) => match view.app.selected {
|
||||||
|
Select::Mix => return None,
|
||||||
|
Select::Track(t) => Select(Select::Track(t - 1)),
|
||||||
|
Select::Scene(s) => return None,
|
||||||
|
Select::Clip(t, s) => Select(Select::Clip(t - 1, s)),
|
||||||
|
},
|
||||||
|
|
||||||
|
key!(KeyCode::Right) => match view.app.selected {
|
||||||
|
Select::Mix => return None,
|
||||||
|
Select::Track(t) => Select(Select::Track(t + 1)),
|
||||||
|
Select::Scene(s) => Select(Select::Clip(0, s)),
|
||||||
|
Select::Clip(t, s) => Select(Select::Clip(t, s - 1)),
|
||||||
|
},
|
||||||
|
|
||||||
|
key!(KeyCode::Char('+')) => Zoom(0),
|
||||||
|
|
||||||
|
key!(KeyCode::Char('=')) => Zoom(0),
|
||||||
|
|
||||||
|
key!(KeyCode::Char('_')) => Zoom(0),
|
||||||
|
|
||||||
|
key!(KeyCode::Char('-')) => Zoom(0),
|
||||||
|
|
||||||
|
key!(KeyCode::Char('`')) => { todo!("toggle view mode") },
|
||||||
|
|
||||||
|
key!(KeyCode::Char(',')) => match view.app.selected {
|
||||||
|
Select::Mix => Zoom(0),
|
||||||
|
Select::Track(t) => Track(Track::Swap(t, t - 1)),
|
||||||
|
Select::Scene(s) => Scene(Scene::Swap(s, s - 1)),
|
||||||
|
Select::Clip(t, s) => Clip(Clip::Set(t, s, None)),
|
||||||
|
},
|
||||||
|
|
||||||
|
key!(KeyCode::Char('.')) => match view.app.selected {
|
||||||
|
Select::Mix => Zoom(0),
|
||||||
|
Select::Track(t) => Track(Track::Swap(t, t + 1)),
|
||||||
|
Select::Scene(s) => Scene(Scene::Swap(s, s + 1)),
|
||||||
|
Select::Clip(t, s) => Clip(Clip::Set(t, s, None)),
|
||||||
|
},
|
||||||
|
|
||||||
|
key!(KeyCode::Char('<')) => match view.app.selected {
|
||||||
|
Select::Mix => Zoom(0),
|
||||||
|
Select::Track(t) => Track(Track::Swap(t, t - 1)),
|
||||||
|
Select::Scene(s) => Scene(Scene::Swap(s, s - 1)),
|
||||||
|
Select::Clip(t, s) => Clip(Clip::Set(t, s, None)),
|
||||||
|
},
|
||||||
|
|
||||||
|
key!(KeyCode::Char('>')) => match view.app.selected {
|
||||||
|
Select::Mix => Zoom(0),
|
||||||
|
Select::Track(t) => Track(Track::Swap(t, t + 1)),
|
||||||
|
Select::Scene(s) => Scene(Scene::Swap(s, s + 1)),
|
||||||
|
Select::Clip(t, s) => Clip(Clip::Set(t, s, None)),
|
||||||
|
},
|
||||||
|
|
||||||
|
key!(KeyCode::Enter) => match view.app.selected {
|
||||||
|
Select::Mix => return None,
|
||||||
|
Select::Track(t) => return None,
|
||||||
|
Select::Scene(s) => Scene(Scene::Play(s)),
|
||||||
|
Select::Clip(t, s) => return None,
|
||||||
|
},
|
||||||
|
|
||||||
|
key!(KeyCode::Delete) => match view.app.selected {
|
||||||
|
Select::Mix => Clear,
|
||||||
|
Select::Track(t) => Track(Track::Delete(t)),
|
||||||
|
Select::Scene(s) => Scene(Scene::Delete(s)),
|
||||||
|
Select::Clip(t, s) => Clip(Clip::Set(t, s, None)),
|
||||||
|
},
|
||||||
|
|
||||||
|
key!(KeyCode::Char('c')) => Clip(Clip::RandomColor),
|
||||||
|
|
||||||
|
key!(KeyCode::Char('s')) => match view.app.selected {
|
||||||
|
Select::Clip(t, s) => Clip(Clip::Set(t, s, None)),
|
||||||
|
_ => return None,
|
||||||
|
},
|
||||||
|
|
||||||
|
key!(KeyCode::Char('g')) => match view.app.selected {
|
||||||
|
Select::Clip(t, s) => Clip(Clip::Get(t, s)),
|
||||||
|
_ => return None,
|
||||||
|
},
|
||||||
|
|
||||||
|
key!(Ctrl-KeyCode::Char('a')) => Scene(Scene::Add),
|
||||||
|
|
||||||
|
key!(Ctrl-KeyCode::Char('t')) => Track(Track::Add),
|
||||||
|
|
||||||
|
key!(KeyCode::Char('l')) => Clip(Clip::SetLoop(false)),
|
||||||
|
|
||||||
|
_ => return None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Command<ArrangerApp<Tui>> for ArrangerAppCommand {
|
||||||
|
fn execute (self, state: &mut ArrangerApp<Tui>) -> Perhaps<Self> {
|
||||||
|
use AppViewCommand::*;
|
||||||
|
let undo = match self {
|
||||||
|
Focus(cmd) => { delegate(cmd, Focus, state) },
|
||||||
|
App(cmd) => { delegate(cmd, App, state) }
|
||||||
|
_ => {todo!()}
|
||||||
|
}?;
|
||||||
|
state.show_phrase();
|
||||||
|
state.update_status();
|
||||||
|
return Ok(undo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Command<ArrangerApp<Tui>> for ArrangerViewCommand {
|
||||||
|
fn execute (self, state: &mut ArrangerApp<Tui>) -> Perhaps<Self> {
|
||||||
|
use ArrangerViewCommand::*;
|
||||||
|
match self {
|
||||||
|
Scene(cmd) => { delegate(cmd, Scene, &mut state.app) },
|
||||||
|
Track(cmd) => { delegate(cmd, Track, &mut state.app) },
|
||||||
|
Clip(cmd) => { delegate(cmd, Clip, &mut state.app) },
|
||||||
|
Phrases(cmd) => { delegate(cmd, Phrases, &mut state.app) },
|
||||||
|
Editor(cmd) => { delegate(cmd, Editor, &mut state.app) },
|
||||||
|
Clock(cmd) => { delegate(cmd, Clock, &mut state.app) },
|
||||||
|
Playhead(cmd) => { delegate(cmd, Playhead, &mut state.app) },
|
||||||
|
Zoom(zoom) => { todo!(); },
|
||||||
|
Select(selected) => { state.selected = selected; Ok(None) },
|
||||||
|
EditPhrase(phrase) => {
|
||||||
|
state.editor.phrase = phrase.clone();
|
||||||
|
state.focus(ArrangerFocus::PhraseEditor);
|
||||||
|
state.focus_enter();
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
74
crates/tek_tui/src/tui_arranger_focus.rs
Normal file
74
crates/tek_tui/src/tui_arranger_focus.rs
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
/// Sections in the arranger app that may be focused
|
||||||
|
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||||
|
pub enum ArrangerFocus {
|
||||||
|
/// The transport (toolbar) is focused
|
||||||
|
Transport,
|
||||||
|
/// The arrangement (grid) is focused
|
||||||
|
Arranger,
|
||||||
|
/// The phrase list (pool) is focused
|
||||||
|
PhrasePool,
|
||||||
|
/// The phrase editor (sequencer) is focused
|
||||||
|
PhraseEditor,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FocusEnter for ArrangerApp<Tui> {
|
||||||
|
fn focus_enter (&mut self) {
|
||||||
|
use AppViewFocus::*;
|
||||||
|
use ArrangerFocus::*;
|
||||||
|
let focused = self.focused();
|
||||||
|
if !self.entered {
|
||||||
|
self.entered = focused == Content(Arranger);
|
||||||
|
self.app.editor.entered = focused == Content(PhraseEditor);
|
||||||
|
self.app.phrases.entered = focused == Content(PhrasePool);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn focus_exit (&mut self) {
|
||||||
|
if self.entered {
|
||||||
|
self.entered = false;
|
||||||
|
self.app.editor.entered = false;
|
||||||
|
self.app.phrases.entered = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn focus_entered (&self) -> Option<Self::Item> {
|
||||||
|
if self.entered {
|
||||||
|
Some(self.focused())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Focus layout of arranger app
|
||||||
|
impl FocusGrid for ArrangerApp<Tui> {
|
||||||
|
type Item = AppViewFocus<ArrangerFocus>;
|
||||||
|
fn focus_cursor (&self) -> (usize, usize) {
|
||||||
|
self.cursor
|
||||||
|
}
|
||||||
|
fn focus_cursor_mut (&mut self) -> &mut (usize, usize) {
|
||||||
|
&mut self.cursor
|
||||||
|
}
|
||||||
|
fn focus_layout (&self) -> &[&[Self::Item]] {
|
||||||
|
use AppViewFocus::*;
|
||||||
|
use ArrangerFocus::*;
|
||||||
|
&[
|
||||||
|
&[Menu, Menu ],
|
||||||
|
&[Content(Transport), Content(Transport) ],
|
||||||
|
&[Content(Arranger), Content(Arranger) ],
|
||||||
|
&[Content(PhrasePool), Content(PhraseEditor)],
|
||||||
|
]
|
||||||
|
}
|
||||||
|
fn focus_update (&mut self) {
|
||||||
|
use AppViewFocus::*;
|
||||||
|
use ArrangerFocus::*;
|
||||||
|
let focused = self.focused();
|
||||||
|
if let Some(mut status_bar) = self.status_bar {
|
||||||
|
status_bar.update(&(
|
||||||
|
self.focused(),
|
||||||
|
self.app.selected,
|
||||||
|
focused == Content(PhraseEditor) && self.entered
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,27 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
|
impl HasScenes<ArrangerScene> for ArrangerView<Tui> {
|
||||||
|
fn scenes (&self) -> &Vec<ArrangerScene> {
|
||||||
|
&self.scenes
|
||||||
|
}
|
||||||
|
fn scenes_mut (&mut self) -> &mut Vec<ArrangerScene> {
|
||||||
|
&mut self.scenes
|
||||||
|
}
|
||||||
|
fn scene_add (&mut self, name: Option<&str>, color: Option<ItemColor>)
|
||||||
|
-> Usually<&mut ArrangerScene>
|
||||||
|
{
|
||||||
|
let name = name.map_or_else(||self.scene_default_name(), |x|x.to_string());
|
||||||
|
let scene = ArrangerScene {
|
||||||
|
name: Arc::new(name.into()),
|
||||||
|
clips: vec![None;self.tracks().len()],
|
||||||
|
color: color.unwrap_or_else(||ItemColor::random()),
|
||||||
|
};
|
||||||
|
self.scenes_mut().push(scene);
|
||||||
|
let index = self.scenes().len() - 1;
|
||||||
|
Ok(&mut self.scenes_mut()[index])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug, Clone)]
|
#[derive(Default, Debug, Clone)]
|
||||||
pub struct ArrangerScene {
|
pub struct ArrangerScene {
|
||||||
/// Name of scene
|
/// Name of scene
|
||||||
|
|
|
||||||
70
crates/tek_tui/src/tui_arranger_select.rs
Normal file
70
crates/tek_tui/src/tui_arranger_select.rs
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
#[derive(PartialEq, Clone, Copy, Debug)]
|
||||||
|
/// Represents the current user selection in the arranger
|
||||||
|
pub enum ArrangerSelection {
|
||||||
|
/// The whole mix is selected
|
||||||
|
Mix,
|
||||||
|
/// A track is selected.
|
||||||
|
Track(usize),
|
||||||
|
/// A scene is selected.
|
||||||
|
Scene(usize),
|
||||||
|
/// A clip (track × scene) is selected.
|
||||||
|
Clip(usize, usize),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Focus identification methods
|
||||||
|
impl ArrangerSelection {
|
||||||
|
pub fn description <E: Engine> (
|
||||||
|
&self,
|
||||||
|
tracks: &Vec<ArrangerTrack>,
|
||||||
|
scenes: &Vec<ArrangerScene>,
|
||||||
|
) -> String {
|
||||||
|
format!("Selected: {}", match self {
|
||||||
|
Self::Mix => format!("Everything"),
|
||||||
|
Self::Track(t) => match tracks.get(*t) {
|
||||||
|
Some(track) => format!("T{t}: {}", &track.name.read().unwrap()),
|
||||||
|
None => format!("T??"),
|
||||||
|
},
|
||||||
|
Self::Scene(s) => match scenes.get(*s) {
|
||||||
|
Some(scene) => format!("S{s}: {}", &scene.name.read().unwrap()),
|
||||||
|
None => format!("S??"),
|
||||||
|
},
|
||||||
|
Self::Clip(t, s) => match (tracks.get(*t), scenes.get(*s)) {
|
||||||
|
(Some(_), Some(scene)) => match scene.clip(*t) {
|
||||||
|
Some(clip) => format!("T{t} S{s} C{}", &clip.read().unwrap().name),
|
||||||
|
None => format!("T{t} S{s}: Empty")
|
||||||
|
},
|
||||||
|
_ => format!("T{t} S{s}: Empty"),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
pub fn is_mix (&self) -> bool {
|
||||||
|
match self { Self::Mix => true, _ => false }
|
||||||
|
}
|
||||||
|
pub fn is_track (&self) -> bool {
|
||||||
|
match self { Self::Track(_) => true, _ => false }
|
||||||
|
}
|
||||||
|
pub fn is_scene (&self) -> bool {
|
||||||
|
match self { Self::Scene(_) => true, _ => false }
|
||||||
|
}
|
||||||
|
pub fn is_clip (&self) -> bool {
|
||||||
|
match self { Self::Clip(_, _) => true, _ => false }
|
||||||
|
}
|
||||||
|
pub fn track (&self) -> Option<usize> {
|
||||||
|
use ArrangerSelection::*;
|
||||||
|
match self {
|
||||||
|
Clip(t, _) => Some(*t),
|
||||||
|
Track(t) => Some(*t),
|
||||||
|
_ => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn scene (&self) -> Option<usize> {
|
||||||
|
use ArrangerSelection::*;
|
||||||
|
match self {
|
||||||
|
Clip(_, s) => Some(*s),
|
||||||
|
Scene(s) => Some(*s),
|
||||||
|
_ => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
125
crates/tek_tui/src/tui_arranger_status.rs
Normal file
125
crates/tek_tui/src/tui_arranger_status.rs
Normal file
|
|
@ -0,0 +1,125 @@
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
/// Status bar for arranger app
|
||||||
|
#[derive(Copy, Clone, Debug)]
|
||||||
|
pub enum ArrangerStatusBar {
|
||||||
|
Transport,
|
||||||
|
ArrangerMix,
|
||||||
|
ArrangerTrack,
|
||||||
|
ArrangerScene,
|
||||||
|
ArrangerClip,
|
||||||
|
PhrasePool,
|
||||||
|
PhraseView,
|
||||||
|
PhraseEdit,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StatusBar for ArrangerStatusBar {
|
||||||
|
|
||||||
|
type State = (AppViewFocus<ArrangerFocus>, ArrangerSelection, bool);
|
||||||
|
|
||||||
|
fn hotkey_fg () -> Color where Self: Sized {
|
||||||
|
TuiTheme::hotkey_fg()
|
||||||
|
}
|
||||||
|
fn update (&mut self, (focused, selected, entered): &Self::State) {
|
||||||
|
use AppViewFocus::*;
|
||||||
|
if let Content(focused) = focused {
|
||||||
|
*self = match focused {
|
||||||
|
ArrangerFocus::Transport => ArrangerStatusBar::Transport,
|
||||||
|
ArrangerFocus::Arranger => match selected {
|
||||||
|
ArrangerSelection::Mix => ArrangerStatusBar::ArrangerMix,
|
||||||
|
ArrangerSelection::Track(_) => ArrangerStatusBar::ArrangerTrack,
|
||||||
|
ArrangerSelection::Scene(_) => ArrangerStatusBar::ArrangerScene,
|
||||||
|
ArrangerSelection::Clip(_, _) => ArrangerStatusBar::ArrangerClip,
|
||||||
|
},
|
||||||
|
ArrangerFocus::PhrasePool => ArrangerStatusBar::PhrasePool,
|
||||||
|
ArrangerFocus::PhraseEditor => match entered {
|
||||||
|
true => ArrangerStatusBar::PhraseEdit,
|
||||||
|
false => ArrangerStatusBar::PhraseView,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Content for ArrangerStatusBar {
|
||||||
|
type Engine = Tui;
|
||||||
|
fn content (&self) -> impl Widget<Engine = 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,46 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
|
impl HasTracks<ArrangerTrack> for ArrangerView<Tui> {
|
||||||
|
fn tracks (&self) -> &Vec<ArrangerTrack> {
|
||||||
|
&self.tracks
|
||||||
|
}
|
||||||
|
fn tracks_mut (&mut self) -> &mut Vec<ArrangerTrack> {
|
||||||
|
&mut self.tracks
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ArrangerTracksApi<ArrangerTrack> for ArrangerView<Tui> {
|
||||||
|
fn track_add (&mut self, name: Option<&str>, color: Option<ItemColor>)
|
||||||
|
-> Usually<&mut ArrangerTrack>
|
||||||
|
{
|
||||||
|
let name = name.map_or_else(||self.track_default_name(), |x|x.to_string());
|
||||||
|
let track = ArrangerTrack {
|
||||||
|
width: name.len() + 2,
|
||||||
|
name: Arc::new(name.into()),
|
||||||
|
color: color.unwrap_or_else(||ItemColor::random()),
|
||||||
|
midi_ins: vec![],
|
||||||
|
midi_outs: vec![],
|
||||||
|
reset: true,
|
||||||
|
recording: false,
|
||||||
|
monitoring: false,
|
||||||
|
overdub: false,
|
||||||
|
play_phrase: None,
|
||||||
|
next_phrase: None,
|
||||||
|
notes_in: RwLock::new([false;128]).into(),
|
||||||
|
notes_out: RwLock::new([false;128]).into(),
|
||||||
|
};
|
||||||
|
self.tracks_mut().push(track);
|
||||||
|
let index = self.tracks().len() - 1;
|
||||||
|
Ok(&mut self.tracks_mut()[index])
|
||||||
|
}
|
||||||
|
fn track_del (&mut self, index: usize) {
|
||||||
|
self.tracks_mut().remove(index);
|
||||||
|
for scene in self.scenes_mut().iter_mut() {
|
||||||
|
scene.clips.remove(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct ArrangerTrack {
|
pub struct ArrangerTrack {
|
||||||
/// Name of track
|
/// Name of track
|
||||||
|
|
|
||||||
482
crates/tek_tui/src/tui_arranger_view.rs
Normal file
482
crates/tek_tui/src/tui_arranger_view.rs
Normal file
|
|
@ -0,0 +1,482 @@
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
/// Display mode of arranger
|
||||||
|
#[derive(Clone, PartialEq)]
|
||||||
|
pub enum ArrangerMode {
|
||||||
|
/// Tracks are rows
|
||||||
|
Horizontal,
|
||||||
|
/// Tracks are columns
|
||||||
|
Vertical(usize),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Arranger display mode can be cycled
|
||||||
|
impl ArrangerMode {
|
||||||
|
/// Cycle arranger display mode
|
||||||
|
pub fn to_next (&mut self) {
|
||||||
|
*self = match self {
|
||||||
|
Self::Horizontal => Self::Vertical(1),
|
||||||
|
Self::Vertical(1) => Self::Vertical(2),
|
||||||
|
Self::Vertical(2) => Self::Vertical(2),
|
||||||
|
Self::Vertical(0) => Self::Horizontal,
|
||||||
|
Self::Vertical(_) => Self::Vertical(0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Layout for standalone arranger app.
|
||||||
|
impl Content for ArrangerView<Tui> {
|
||||||
|
type Engine = Tui;
|
||||||
|
fn content (&self) -> impl Widget<Engine = Tui> {
|
||||||
|
Split::up(
|
||||||
|
1,
|
||||||
|
widget(&TransportRef(self)),
|
||||||
|
Split::down(
|
||||||
|
self.splits[0],
|
||||||
|
lay!(
|
||||||
|
Layers::new(move |add|{
|
||||||
|
match self.mode {
|
||||||
|
ArrangerMode::Horizontal =>
|
||||||
|
add(&arranger_content_horizontal(self))?,
|
||||||
|
ArrangerMode::Vertical(factor) =>
|
||||||
|
add(&arranger_content_vertical(self, factor))?
|
||||||
|
};
|
||||||
|
add(&self.size)
|
||||||
|
})
|
||||||
|
.grow_y(1)
|
||||||
|
.border(Lozenge(Style::default()
|
||||||
|
.bg(TuiTheme::border_bg())
|
||||||
|
.fg(TuiTheme::border_fg(self.focused)))),
|
||||||
|
widget(&self.size),
|
||||||
|
widget(&format!("[{}] Arranger", if self.entered {
|
||||||
|
"■"
|
||||||
|
} else {
|
||||||
|
" "
|
||||||
|
}))
|
||||||
|
.fg(TuiTheme::title_fg(self.focused))
|
||||||
|
.push_x(1),
|
||||||
|
),
|
||||||
|
Split::right(
|
||||||
|
self.splits[1],
|
||||||
|
widget(&self.phrases),
|
||||||
|
widget(&PhraseEditorRef(self)),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TransportViewState for ArrangerView<Tui> {
|
||||||
|
fn focus (&self) -> TransportViewFocus {
|
||||||
|
self.focus
|
||||||
|
}
|
||||||
|
fn focused (&self) -> bool {
|
||||||
|
self.focused
|
||||||
|
}
|
||||||
|
fn transport_state (&self) -> Option<TransportState> {
|
||||||
|
*self.playing().read().unwrap()
|
||||||
|
}
|
||||||
|
fn bpm_value (&self) -> f64 {
|
||||||
|
self.bpm().get()
|
||||||
|
}
|
||||||
|
fn sync_value (&self) -> f64 {
|
||||||
|
self.sync().get()
|
||||||
|
}
|
||||||
|
fn format_beat (&self) -> String {
|
||||||
|
self.current().format_beat()
|
||||||
|
}
|
||||||
|
fn format_msu (&self) -> String {
|
||||||
|
self.current().usec.format_msu()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn track_widths (tracks: &[ArrangerTrack]) -> Vec<(usize, usize)> {
|
||||||
|
let mut widths = vec![];
|
||||||
|
let mut total = 0;
|
||||||
|
for track in tracks.iter() {
|
||||||
|
let width = track.width;
|
||||||
|
widths.push((width, total));
|
||||||
|
total += width;
|
||||||
|
}
|
||||||
|
widths.push((0, total));
|
||||||
|
widths
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn arranger_content_vertical (
|
||||||
|
view: &ArrangerView<Tui>,
|
||||||
|
factor: usize
|
||||||
|
) -> impl Widget<Engine = Tui> + use<'_> {
|
||||||
|
let timebase = view.timebase();
|
||||||
|
let current = view.current();
|
||||||
|
let tracks = view.tracks();
|
||||||
|
let scenes = view.scenes();
|
||||||
|
let cols = track_widths(tracks);
|
||||||
|
let rows = ArrangerScene::ppqs(scenes, factor);
|
||||||
|
let bg = view.color;
|
||||||
|
let clip_bg = TuiTheme::border_bg();
|
||||||
|
let sep_fg = TuiTheme::separator_fg(false);
|
||||||
|
let header_h = 3u16;//5u16;
|
||||||
|
let scenes_w = 3 + ArrangerScene::longest_name(scenes) as u16; // x of 1st track
|
||||||
|
let arrangement = Layers::new(move |add|{
|
||||||
|
let rows: &[(usize, usize)] = rows.as_ref();
|
||||||
|
let cols: &[(usize, usize)] = cols.as_ref();
|
||||||
|
let any_size = |_|Ok(Some([0,0]));
|
||||||
|
// column separators
|
||||||
|
add(&CustomWidget::new(any_size, move|to: &mut TuiOutput|{
|
||||||
|
let style = Some(Style::default().fg(sep_fg));
|
||||||
|
Ok(for x in cols.iter().map(|col|col.1) {
|
||||||
|
let x = scenes_w + to.area().x() + x as u16;
|
||||||
|
for y in to.area().y()..to.area().y2() { to.blit(&"▎", x, y, style); }
|
||||||
|
})
|
||||||
|
}))?;
|
||||||
|
// row separators
|
||||||
|
add(&CustomWidget::new(any_size, move|to: &mut TuiOutput|{
|
||||||
|
Ok(for y in rows.iter().map(|row|row.1) {
|
||||||
|
let y = to.area().y() + (y / PPQ) as u16 + 1;
|
||||||
|
if y >= to.buffer.area.height { break }
|
||||||
|
for x in to.area().x()..to.area().x2().saturating_sub(2) {
|
||||||
|
if x < to.buffer.area.x && y < to.buffer.area.y {
|
||||||
|
let cell = to.buffer.get_mut(x, y);
|
||||||
|
cell.modifier = Modifier::UNDERLINED;
|
||||||
|
cell.underline_color = sep_fg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}))?;
|
||||||
|
// track titles
|
||||||
|
let header = row!((track, w) in tracks.iter().zip(cols.iter().map(|col|col.0))=>{
|
||||||
|
// name and width of track
|
||||||
|
let name = track.name.read().unwrap();
|
||||||
|
let max_w = w.saturating_sub(1).min(name.len()).max(2);
|
||||||
|
let name = format!("▎{}", &name[0..max_w]);
|
||||||
|
let name = TuiStyle::bold(name, true);
|
||||||
|
// beats elapsed
|
||||||
|
let elapsed = if let Some((_, Some(phrase))) = track.phrase().as_ref() {
|
||||||
|
let length = phrase.read().unwrap().length;
|
||||||
|
let elapsed = track.pulses_since_start().unwrap();
|
||||||
|
let elapsed = timebase.format_beats_1_short(
|
||||||
|
(elapsed as usize % length) as f64
|
||||||
|
);
|
||||||
|
format!("▎+{elapsed:>}")
|
||||||
|
} else {
|
||||||
|
String::from("▎")
|
||||||
|
};
|
||||||
|
// beats until switchover
|
||||||
|
let until_next = track.next_phrase().as_ref().map(|(t, _)|{
|
||||||
|
let target = t.pulse.get();
|
||||||
|
let current = current.pulse.get();
|
||||||
|
if target > current {
|
||||||
|
let remaining = target - current;
|
||||||
|
format!("▎-{:>}", timebase.format_beats_0_short(remaining))
|
||||||
|
} else {
|
||||||
|
String::new()
|
||||||
|
}
|
||||||
|
}).unwrap_or(String::from("▎"));
|
||||||
|
// name of active MIDI input
|
||||||
|
let input = format!("▎>{}", track.midi_ins().get(0)
|
||||||
|
.map(|port|port.short_name())
|
||||||
|
.transpose()?
|
||||||
|
.unwrap_or("(none)".into()));
|
||||||
|
// name of active MIDI output
|
||||||
|
let output = format!("▎<{}", track.midi_outs().get(0)
|
||||||
|
.map(|port|port.short_name())
|
||||||
|
.transpose()?
|
||||||
|
.unwrap_or("(none)".into()));
|
||||||
|
col!(name, /*input, output,*/ until_next, elapsed)
|
||||||
|
.min_xy(w as u16, header_h)
|
||||||
|
.bg(track.color.rgb)
|
||||||
|
.push_x(scenes_w)
|
||||||
|
});
|
||||||
|
// tracks and scenes
|
||||||
|
let content = col!(
|
||||||
|
// scenes:
|
||||||
|
(scene, pulses) in scenes.iter().zip(rows.iter().map(|row|row.0)) => {
|
||||||
|
let height = 1.max((pulses / PPQ) as u16);
|
||||||
|
let playing = scene.is_playing(tracks);
|
||||||
|
Stack::right(move |add| {
|
||||||
|
// scene title:
|
||||||
|
add(&row!(
|
||||||
|
if playing { "▶ " } else { " " },
|
||||||
|
TuiStyle::bold(scene.name.read().unwrap().as_str(), true),
|
||||||
|
).fixed_xy(scenes_w, height).bg(scene.color.rgb))?;
|
||||||
|
// clip per track:
|
||||||
|
Ok(for (track, w) in cols.iter().map(|col|col.0).enumerate() {
|
||||||
|
add(&Layers::new(move |add|{
|
||||||
|
let mut bg = clip_bg;
|
||||||
|
match (tracks.get(track), scene.clips.get(track)) {
|
||||||
|
(Some(track), Some(Some(phrase))) => {
|
||||||
|
let name = &(phrase as &Arc<RwLock<Phrase>>).read().unwrap().name;
|
||||||
|
let name = format!("{}", name);
|
||||||
|
let max_w = name.len().min((w as usize).saturating_sub(2));
|
||||||
|
let color = phrase.read().unwrap().color;
|
||||||
|
add(&name.as_str()[0..max_w].push_x(1).fixed_x(w as u16))?;
|
||||||
|
bg = color.dark.rgb;
|
||||||
|
if let Some((_, Some(ref playing))) = track.phrase() {
|
||||||
|
if *playing.read().unwrap() == *phrase.read().unwrap() {
|
||||||
|
bg = color.light.rgb
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
_ => {}
|
||||||
|
};
|
||||||
|
add(&Background(bg))
|
||||||
|
}).fixed_xy(w as u16, height))?;
|
||||||
|
})
|
||||||
|
}).fixed_y(height)
|
||||||
|
}
|
||||||
|
).fixed_y((view.size.h() as u16).saturating_sub(header_h));
|
||||||
|
// full grid with header and footer
|
||||||
|
add(&col!(header, content))?;
|
||||||
|
// cursor
|
||||||
|
add(&CustomWidget::new(any_size, move|to: &mut TuiOutput|{
|
||||||
|
let area = to.area();
|
||||||
|
let focused = view.focused;
|
||||||
|
let selected = view.selected;
|
||||||
|
let get_track_area = |t: usize| [
|
||||||
|
scenes_w + area.x() + cols[t].1 as u16, area.y(),
|
||||||
|
cols[t].0 as u16, area.h(),
|
||||||
|
];
|
||||||
|
let get_scene_area = |s: usize| [
|
||||||
|
area.x(), header_h + area.y() + (rows[s].1 / PPQ) as u16,
|
||||||
|
area.w(), (rows[s].0 / PPQ) as u16
|
||||||
|
];
|
||||||
|
let get_clip_area = |t: usize, s: usize| [
|
||||||
|
scenes_w + area.x() + cols[t].1 as u16,
|
||||||
|
header_h + area.y() + (rows[s].1/PPQ) as u16,
|
||||||
|
cols[t].0 as u16,
|
||||||
|
(rows[s].0 / PPQ) as u16
|
||||||
|
];
|
||||||
|
let mut track_area: Option<[u16;4]> = None;
|
||||||
|
let mut scene_area: Option<[u16;4]> = None;
|
||||||
|
let mut clip_area: Option<[u16;4]> = None;
|
||||||
|
let area = match selected {
|
||||||
|
ArrangerSelection::Mix => area,
|
||||||
|
ArrangerSelection::Track(t) => {
|
||||||
|
track_area = Some(get_track_area(t));
|
||||||
|
area
|
||||||
|
},
|
||||||
|
ArrangerSelection::Scene(s) => {
|
||||||
|
scene_area = Some(get_scene_area(s));
|
||||||
|
area
|
||||||
|
},
|
||||||
|
ArrangerSelection::Clip(t, s) => {
|
||||||
|
track_area = Some(get_track_area(t));
|
||||||
|
scene_area = Some(get_scene_area(s));
|
||||||
|
clip_area = Some(get_clip_area(t, s));
|
||||||
|
area
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let bg = TuiTheme::border_bg();
|
||||||
|
if let Some([x, y, width, height]) = track_area {
|
||||||
|
to.fill_fg([x, y, 1, height], bg);
|
||||||
|
to.fill_fg([x + width, y, 1, height], bg);
|
||||||
|
}
|
||||||
|
if let Some([_, y, _, height]) = scene_area {
|
||||||
|
to.fill_ul([area.x(), y - 1, area.w(), 1], bg);
|
||||||
|
to.fill_ul([area.x(), y + height - 1, area.w(), 1], bg);
|
||||||
|
}
|
||||||
|
Ok(if focused {
|
||||||
|
to.render_in(if let Some(clip_area) = clip_area { clip_area }
|
||||||
|
else if let Some(track_area) = track_area { track_area.clip_h(header_h) }
|
||||||
|
else if let Some(scene_area) = scene_area { scene_area.clip_w(scenes_w) }
|
||||||
|
else { area.clip_w(scenes_w).clip_h(header_h) }, &CORNERS)?
|
||||||
|
})
|
||||||
|
}))
|
||||||
|
}).bg(bg.rgb);
|
||||||
|
let color = TuiTheme::title_fg(view.focused);
|
||||||
|
let size = format!("{}x{}", view.size.w(), view.size.h());
|
||||||
|
let lower_right = TuiStyle::fg(size, color).pull_x(1).align_se().fill_xy();
|
||||||
|
lay!(arrangement, lower_right)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn arranger_content_horizontal (
|
||||||
|
view: &ArrangerView<Tui>,
|
||||||
|
) -> impl Widget<Engine = Tui> + use<'_> {
|
||||||
|
let focused = view.focused;
|
||||||
|
let _tracks = view.tracks();
|
||||||
|
lay!(
|
||||||
|
focused.then_some(Background(TuiTheme::border_bg())),
|
||||||
|
row!(
|
||||||
|
// name
|
||||||
|
CustomWidget::new(|_|{todo!()}, |_: &mut TuiOutput|{
|
||||||
|
todo!()
|
||||||
|
//let Self(tracks, selected) = self;
|
||||||
|
//let yellow = Some(Style::default().yellow().bold().not_dim());
|
||||||
|
//let white = Some(Style::default().white().bold().not_dim());
|
||||||
|
//let area = to.area();
|
||||||
|
//let area = [area.x(), area.y(), 3 + 5.max(track_name_max_len(tracks)) as u16, area.h()];
|
||||||
|
//let offset = 0; // track scroll offset
|
||||||
|
//for y in 0..area.h() {
|
||||||
|
//if y == 0 {
|
||||||
|
//to.blit(&"Mixer", area.x() + 1, area.y() + y, Some(DIM))?;
|
||||||
|
//} else if y % 2 == 0 {
|
||||||
|
//let index = (y as usize - 2) / 2 + offset;
|
||||||
|
//if let Some(track) = tracks.get(index) {
|
||||||
|
//let selected = selected.track() == Some(index);
|
||||||
|
//let style = if selected { yellow } else { white };
|
||||||
|
//to.blit(&format!(" {index:>02} "), area.x(), area.y() + y, style)?;
|
||||||
|
//to.blit(&*track.name.read().unwrap(), area.x() + 4, area.y() + y, style)?;
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
//Ok(Some(area))
|
||||||
|
}),
|
||||||
|
// monitor
|
||||||
|
CustomWidget::new(|_|{todo!()}, |_: &mut TuiOutput|{
|
||||||
|
todo!()
|
||||||
|
//let Self(tracks) = self;
|
||||||
|
//let mut area = to.area();
|
||||||
|
//let on = Some(Style::default().not_dim().green().bold());
|
||||||
|
//let off = Some(DIM);
|
||||||
|
//area.x += 1;
|
||||||
|
//for y in 0..area.h() {
|
||||||
|
//if y == 0 {
|
||||||
|
////" MON ".blit(to.buffer, area.x, area.y + y, style2)?;
|
||||||
|
//} else if y % 2 == 0 {
|
||||||
|
//let index = (y as usize - 2) / 2;
|
||||||
|
//if let Some(track) = tracks.get(index) {
|
||||||
|
//let style = if track.monitoring { on } else { off };
|
||||||
|
//to.blit(&" MON ", area.x(), area.y() + y, style)?;
|
||||||
|
//} else {
|
||||||
|
//area.height = y;
|
||||||
|
//break
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
//area.width = 4;
|
||||||
|
//Ok(Some(area))
|
||||||
|
}),
|
||||||
|
// record
|
||||||
|
CustomWidget::new(|_|{todo!()}, |_: &mut TuiOutput|{
|
||||||
|
todo!()
|
||||||
|
//let Self(tracks) = self;
|
||||||
|
//let mut area = to.area();
|
||||||
|
//let on = Some(Style::default().not_dim().red().bold());
|
||||||
|
//let off = Some(Style::default().dim());
|
||||||
|
//area.x += 1;
|
||||||
|
//for y in 0..area.h() {
|
||||||
|
//if y == 0 {
|
||||||
|
////" REC ".blit(to.buffer, area.x, area.y + y, style2)?;
|
||||||
|
//} else if y % 2 == 0 {
|
||||||
|
//let index = (y as usize - 2) / 2;
|
||||||
|
//if let Some(track) = tracks.get(index) {
|
||||||
|
//let style = if track.recording { on } else { off };
|
||||||
|
//to.blit(&" REC ", area.x(), area.y() + y, style)?;
|
||||||
|
//} else {
|
||||||
|
//area.height = y;
|
||||||
|
//break
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
//area.width = 4;
|
||||||
|
//Ok(Some(area))
|
||||||
|
}),
|
||||||
|
// overdub
|
||||||
|
CustomWidget::new(|_|{todo!()}, |_: &mut TuiOutput|{
|
||||||
|
todo!()
|
||||||
|
//let Self(tracks) = self;
|
||||||
|
//let mut area = to.area();
|
||||||
|
//let on = Some(Style::default().not_dim().yellow().bold());
|
||||||
|
//let off = Some(Style::default().dim());
|
||||||
|
//area.x = area.x + 1;
|
||||||
|
//for y in 0..area.h() {
|
||||||
|
//if y == 0 {
|
||||||
|
////" OVR ".blit(to.buffer, area.x, area.y + y, style2)?;
|
||||||
|
//} else if y % 2 == 0 {
|
||||||
|
//let index = (y as usize - 2) / 2;
|
||||||
|
//if let Some(track) = tracks.get(index) {
|
||||||
|
//to.blit(&" OVR ", area.x(), area.y() + y, if track.overdub {
|
||||||
|
//on
|
||||||
|
//} else {
|
||||||
|
//off
|
||||||
|
//})?;
|
||||||
|
//} else {
|
||||||
|
//area.height = y;
|
||||||
|
//break
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
//area.width = 4;
|
||||||
|
//Ok(Some(area))
|
||||||
|
}),
|
||||||
|
// erase
|
||||||
|
CustomWidget::new(|_|{todo!()}, |_: &mut TuiOutput|{
|
||||||
|
todo!()
|
||||||
|
//let Self(tracks) = self;
|
||||||
|
//let mut area = to.area();
|
||||||
|
//let off = Some(Style::default().dim());
|
||||||
|
//area.x = area.x + 1;
|
||||||
|
//for y in 0..area.h() {
|
||||||
|
//if y == 0 {
|
||||||
|
////" DEL ".blit(to.buffer, area.x, area.y + y, style2)?;
|
||||||
|
//} else if y % 2 == 0 {
|
||||||
|
//let index = (y as usize - 2) / 2;
|
||||||
|
//if let Some(_) = tracks.get(index) {
|
||||||
|
//to.blit(&" DEL ", area.x(), area.y() + y, off)?;
|
||||||
|
//} else {
|
||||||
|
//area.height = y;
|
||||||
|
//break
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
//area.width = 4;
|
||||||
|
//Ok(Some(area))
|
||||||
|
}),
|
||||||
|
// gain
|
||||||
|
CustomWidget::new(|_|{todo!()}, |_: &mut TuiOutput|{
|
||||||
|
todo!()
|
||||||
|
//let Self(tracks) = self;
|
||||||
|
//let mut area = to.area();
|
||||||
|
//let off = Some(Style::default().dim());
|
||||||
|
//area.x = area.x() + 1;
|
||||||
|
//for y in 0..area.h() {
|
||||||
|
//if y == 0 {
|
||||||
|
////" GAIN ".blit(to.buffer, area.x, area.y + y, style2)?;
|
||||||
|
//} else if y % 2 == 0 {
|
||||||
|
//let index = (y as usize - 2) / 2;
|
||||||
|
//if let Some(_) = tracks.get(index) {
|
||||||
|
//to.blit(&" +0.0 ", area.x(), area.y() + y, off)?;
|
||||||
|
//} else {
|
||||||
|
//area.height = y;
|
||||||
|
//break
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
//area.width = 7;
|
||||||
|
//Ok(Some(area))
|
||||||
|
}),
|
||||||
|
// scenes
|
||||||
|
CustomWidget::new(|_|{todo!()}, |to: &mut TuiOutput|{
|
||||||
|
let [x, y, _, height] = to.area();
|
||||||
|
let mut x2 = 0;
|
||||||
|
Ok(for (scene_index, scene) in view.scenes().iter().enumerate() {
|
||||||
|
let active_scene = view.selected.scene() == Some(scene_index);
|
||||||
|
let sep = Some(if active_scene {
|
||||||
|
Style::default().yellow().not_dim()
|
||||||
|
} else {
|
||||||
|
Style::default().dim()
|
||||||
|
});
|
||||||
|
for y in y+1..y+height {
|
||||||
|
to.blit(&"│", x + x2, y, sep);
|
||||||
|
}
|
||||||
|
let name = scene.name.read().unwrap();
|
||||||
|
let mut x3 = name.len() as u16;
|
||||||
|
to.blit(&*name, x + x2, y, sep);
|
||||||
|
for (i, clip) in scene.clips.iter().enumerate() {
|
||||||
|
let active_track = view.selected.track() == Some(i);
|
||||||
|
if let Some(clip) = clip {
|
||||||
|
let y2 = y + 2 + i as u16 * 2;
|
||||||
|
let label = format!("{}", clip.read().unwrap().name);
|
||||||
|
to.blit(&label, x + x2, y2, Some(if active_track && active_scene {
|
||||||
|
Style::default().not_dim().yellow().bold()
|
||||||
|
} else {
|
||||||
|
Style::default().not_dim()
|
||||||
|
}));
|
||||||
|
x3 = x3.max(label.len() as u16)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
x2 = x2 + x3 + 1;
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue