mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-07 12:16:42 +01:00
wip10 (6e)
This commit is contained in:
parent
f9d1456897
commit
06dab6d0d7
2 changed files with 180 additions and 148 deletions
|
|
@ -16,17 +16,17 @@ use crate::*;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[macro_export] macro_rules! col_up {
|
#[macro_export] macro_rules! col_up {
|
||||||
($(move)?|$add:ident|$expr:expr) => {
|
([$($expr:expr),* $(,)?]) => {
|
||||||
Stack::up($(move)?|$add|$expr)
|
Stack::up(move|add|{ $(add(&$expr)?;)* Ok(()) })
|
||||||
|
};
|
||||||
|
(![$($expr:expr),* $(,)?]) => {
|
||||||
|
Stack::up(|add|{ $(add(&$expr)?;)* Ok(()) })
|
||||||
|
};
|
||||||
|
($expr:expr) => {
|
||||||
|
Stack::up(expr)
|
||||||
};
|
};
|
||||||
($pat:pat in $collection:expr => $item:expr) => {
|
($pat:pat in $collection:expr => $item:expr) => {
|
||||||
Stack::up(move |add|{
|
Stack::up(move |add|{ for $pat in $collection { add(&$item)?; } Ok(()) })
|
||||||
for $pat in $collection { add(&$item)?; }
|
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
};
|
|
||||||
($($expr:expr),* $(,)?) => {
|
|
||||||
Stack::up(move|add|{ $(add(&$expr)?;)* Ok(()) })
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,12 @@ use crate::*;
|
||||||
// Layout for standalone arranger app.
|
// Layout for standalone arranger app.
|
||||||
render!(|self: ArrangerTui|{
|
render!(|self: ArrangerTui|{
|
||||||
let arranger_focused = self.arranger_focused();
|
let arranger_focused = self.arranger_focused();
|
||||||
Tui::to_south(
|
let border = Lozenge(Style::default().bg(TuiTheme::border_bg()).fg(TuiTheme::border_fg(arranger_focused)));
|
||||||
|
col_up!([
|
||||||
TransportView::from(self),
|
TransportView::from(self),
|
||||||
Tui::to_south(
|
col!([
|
||||||
self.splits[0],
|
Tui::fixed_y(self.splits[0], lay!([
|
||||||
Tui::to_south(
|
border.wrap(Tui::grow_y(1, Layers::new(move |add|{
|
||||||
lay!([
|
|
||||||
Layers::new(move |add|{
|
|
||||||
match self.mode {
|
match self.mode {
|
||||||
ArrangerMode::Horizontal =>
|
ArrangerMode::Horizontal =>
|
||||||
add(&arranger_content_horizontal(self))?,
|
add(&arranger_content_horizontal(self))?,
|
||||||
|
|
@ -17,28 +16,24 @@ render!(|self: ArrangerTui|{
|
||||||
add(&arranger_content_vertical(self, factor))?
|
add(&arranger_content_vertical(self, factor))?
|
||||||
};
|
};
|
||||||
add(&self.size)
|
add(&self.size)
|
||||||
})
|
}))),
|
||||||
.grow_y(1)
|
self.size,
|
||||||
.border(Lozenge(Style::default()
|
Tui::push_x(1, Tui::fg(
|
||||||
.bg(TuiTheme::border_bg())
|
TuiTheme::title_fg(arranger_focused),
|
||||||
.fg(TuiTheme::border_fg(arranger_focused)))),
|
format!("[{}] Arranger", if self.entered {
|
||||||
widget(&self.size),
|
|
||||||
widget(&format!("[{}] Arranger", if self.entered {
|
|
||||||
"■"
|
"■"
|
||||||
} else {
|
} else {
|
||||||
" "
|
" "
|
||||||
}))
|
})
|
||||||
.fg(TuiTheme::title_fg(arranger_focused))
|
))
|
||||||
.push_x(1),
|
])),
|
||||||
]),
|
|
||||||
Split::right(
|
Split::right(
|
||||||
self.splits[1],
|
self.splits[1],
|
||||||
PhraseListView::from(self),
|
PhraseListView::from(self),
|
||||||
PhraseView::from(self),
|
PhraseView::from(self),
|
||||||
)
|
)
|
||||||
)
|
])
|
||||||
)
|
])
|
||||||
)
|
|
||||||
});
|
});
|
||||||
|
|
||||||
/// Display mode of arranger
|
/// Display mode of arranger
|
||||||
|
|
@ -97,114 +92,19 @@ pub fn arranger_content_vertical (
|
||||||
view: &ArrangerTui,
|
view: &ArrangerTui,
|
||||||
factor: usize
|
factor: usize
|
||||||
) -> impl Render<Tui> + use<'_> {
|
) -> impl Render<Tui> + use<'_> {
|
||||||
let timebase = view.clock().timebase();
|
|
||||||
let current = &view.clock().playhead;
|
|
||||||
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 rows: &[(usize, usize)] = rows_.as_ref();
|
|
||||||
//let cols: &[(usize, usize)] = cols_.as_ref();
|
|
||||||
|
|
||||||
// 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 = Tui::bold(true, name);
|
|
||||||
// beats elapsed
|
|
||||||
let elapsed = if let Some((_, Some(phrase))) = track.player.play_phrase().as_ref() {
|
|
||||||
let length = phrase.read().unwrap().length;
|
|
||||||
let elapsed = track.player.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.player.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("▎"));
|
|
||||||
let timer = Tui::to_south(until_next, elapsed);
|
|
||||||
// name of active MIDI input
|
|
||||||
let _input = format!("▎>{}", track.player.midi_ins().get(0)
|
|
||||||
.map(|port|port.short_name())
|
|
||||||
.transpose()?
|
|
||||||
.unwrap_or("(none)".into()));
|
|
||||||
// name of active MIDI output
|
|
||||||
let _output = format!("▎<{}", track.player.midi_outs().get(0)
|
|
||||||
.map(|port|port.short_name())
|
|
||||||
.transpose()?
|
|
||||||
.unwrap_or("(none)".into()));
|
|
||||||
Tui::push_x(scenes_w,
|
|
||||||
Tui::bg(track.color().rgb,
|
|
||||||
Tui::min_xy(w as u16, header_h,
|
|
||||||
Tui::to_south(name, timer))))
|
|
||||||
});
|
|
||||||
|
|
||||||
let content = Tui::fixed_y(
|
|
||||||
(view.size.h() as u16).saturating_sub(header_h),
|
|
||||||
col!((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);
|
|
||||||
Tui::fixed_y(
|
|
||||||
height,
|
|
||||||
Tui::to_east(
|
|
||||||
Tui::to_east(
|
|
||||||
if playing { "▶ " } else { " " },
|
|
||||||
Tui::bold(true, scene.name.read().unwrap().as_str())
|
|
||||||
),
|
|
||||||
row!((track, w) in cols.iter().map(|col|col.0).enumerate() => {
|
|
||||||
Tui::fixed_xy(w as u16, height, 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;
|
|
||||||
bg = color.dark.rgb;
|
|
||||||
if let Some((_, Some(ref playing))) = track.player.play_phrase() {
|
|
||||||
if *playing.read().unwrap() == *phrase.read().unwrap() {
|
|
||||||
bg = color.light.rgb
|
|
||||||
}
|
|
||||||
};
|
|
||||||
add(&Tui::fixed_x(w as u16, Tui::push_x(1, &name.as_str()[0..max_w])))?;
|
|
||||||
},
|
|
||||||
_ => {}
|
|
||||||
};
|
|
||||||
//add(&Background(bg))
|
|
||||||
Ok(())
|
|
||||||
}))
|
|
||||||
})
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}));
|
|
||||||
|
|
||||||
let color = TuiTheme::title_fg(view.arranger_focused());
|
|
||||||
lay!([
|
lay!([
|
||||||
Tui::bg(bg.rgb, lay!(![
|
Tui::at_se(Tui::fill_xy(Tui::pull_x(1, Tui::fg(TuiTheme::title_fg(view.arranger_focused()),
|
||||||
|
format!("{}x{}", view.size.w(), view.size.h()))
|
||||||
|
))),
|
||||||
|
Tui::bg(view.color.rgb, lay!(![
|
||||||
ArrangerVerticalColumnSeparator::from(view),
|
ArrangerVerticalColumnSeparator::from(view),
|
||||||
ArrangerVerticalRowSeparator::from((view, factor)),
|
ArrangerVerticalRowSeparator::from((view, factor)),
|
||||||
col!(![header, content]),
|
col!(![
|
||||||
ArrangerCursor::from((view, factor)),
|
ArrangerVerticalHeader::from(view),
|
||||||
|
ArrangerVerticalContent::from((view, factor)),
|
||||||
|
]),
|
||||||
|
ArrangerVerticalCursor::from((view, factor)),
|
||||||
])),
|
])),
|
||||||
Tui::at_se(Tui::fill_xy(Tui::pull_x(1, Tui::fg(color, format!("{}x{}", view.size.w(), view.size.h()))))),
|
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -259,7 +159,7 @@ render!(|self: ArrangerVerticalRowSeparator|custom_render(move|to: &mut TuiOutpu
|
||||||
})
|
})
|
||||||
}));
|
}));
|
||||||
|
|
||||||
struct ArrangerCursor {
|
struct ArrangerVerticalCursor {
|
||||||
cols: Vec<(usize, usize)>,
|
cols: Vec<(usize, usize)>,
|
||||||
rows: Vec<(usize, usize)>,
|
rows: Vec<(usize, usize)>,
|
||||||
focused: bool,
|
focused: bool,
|
||||||
|
|
@ -267,7 +167,7 @@ struct ArrangerCursor {
|
||||||
scenes_w: u16,
|
scenes_w: u16,
|
||||||
header_h: u16,
|
header_h: u16,
|
||||||
}
|
}
|
||||||
impl From<(&ArrangerTui, usize)> for ArrangerCursor {
|
impl From<(&ArrangerTui, usize)> for ArrangerVerticalCursor {
|
||||||
fn from ((state, factor): (&ArrangerTui, usize)) -> Self {
|
fn from ((state, factor): (&ArrangerTui, usize)) -> Self {
|
||||||
Self {
|
Self {
|
||||||
cols: track_widths(state.tracks()),
|
cols: track_widths(state.tracks()),
|
||||||
|
|
@ -279,7 +179,7 @@ impl From<(&ArrangerTui, usize)> for ArrangerCursor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
render!(|self: ArrangerCursor|custom_render(move|to: &mut TuiOutput|{
|
render!(|self: ArrangerVerticalCursor|custom_render(move|to: &mut TuiOutput|{
|
||||||
let area = to.area();
|
let area = to.area();
|
||||||
let focused = self.focused;
|
let focused = self.focused;
|
||||||
let selected = self.selected;
|
let selected = self.selected;
|
||||||
|
|
@ -334,10 +234,142 @@ render!(|self: ArrangerCursor|custom_render(move|to: &mut TuiOutput|{
|
||||||
})
|
})
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
struct ArrangerVerticalHeader<'a> {
|
||||||
|
tracks: &'a Vec<ArrangerTrack>,
|
||||||
|
cols: Vec<(usize, usize)>,
|
||||||
|
focused: bool,
|
||||||
|
selected: ArrangerSelection,
|
||||||
|
scenes_w: u16,
|
||||||
|
header_h: u16,
|
||||||
|
timebase: &'a Arc<Timebase>,
|
||||||
|
current: &'a Arc<Moment>,
|
||||||
|
}
|
||||||
|
impl<'a> From<&'a ArrangerTui> for ArrangerVerticalHeader<'a> {
|
||||||
|
fn from (state: &'a ArrangerTui) -> Self {
|
||||||
|
Self {
|
||||||
|
tracks: &state.tracks,
|
||||||
|
cols: track_widths(state.tracks()),
|
||||||
|
focused: state.arranger_focused(),
|
||||||
|
selected: state.selected,
|
||||||
|
scenes_w: 3 + ArrangerScene::longest_name(state.scenes()) as u16,
|
||||||
|
header_h: 3,
|
||||||
|
timebase: state.clock().timebase(),
|
||||||
|
current: &state.clock().playhead,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
render!(|self: ArrangerVerticalHeader<'a>|row!(
|
||||||
|
(track, w) in self.tracks.iter().zip(self.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 = Tui::bold(true, name);
|
||||||
|
// beats elapsed
|
||||||
|
let elapsed = if let Some((_, Some(phrase))) = track.player.play_phrase().as_ref() {
|
||||||
|
let length = phrase.read().unwrap().length;
|
||||||
|
let elapsed = track.player.pulses_since_start().unwrap();
|
||||||
|
let elapsed = self.timebase.format_beats_1_short(
|
||||||
|
(elapsed as usize % length) as f64
|
||||||
|
);
|
||||||
|
format!("▎+{elapsed:>}")
|
||||||
|
} else {
|
||||||
|
String::from("▎")
|
||||||
|
};
|
||||||
|
// beats until switchover
|
||||||
|
let until_next = track.player.next_phrase().as_ref().map(|(t, _)|{
|
||||||
|
let target = t.pulse.get();
|
||||||
|
let current = self.current.pulse.get();
|
||||||
|
if target > current {
|
||||||
|
let remaining = target - current;
|
||||||
|
format!("▎-{:>}", self.timebase.format_beats_0_short(remaining))
|
||||||
|
} else {
|
||||||
|
String::new()
|
||||||
|
}
|
||||||
|
}).unwrap_or(String::from("▎"));
|
||||||
|
let timer = Tui::to_south(until_next, elapsed);
|
||||||
|
// name of active MIDI input
|
||||||
|
let _input = format!("▎>{}", track.player.midi_ins().get(0)
|
||||||
|
.map(|port|port.short_name())
|
||||||
|
.transpose()?
|
||||||
|
.unwrap_or("(none)".into()));
|
||||||
|
// name of active MIDI output
|
||||||
|
let _output = format!("▎<{}", track.player.midi_outs().get(0)
|
||||||
|
.map(|port|port.short_name())
|
||||||
|
.transpose()?
|
||||||
|
.unwrap_or("(none)".into()));
|
||||||
|
Tui::push_x(self.scenes_w,
|
||||||
|
Tui::bg(track.color().rgb,
|
||||||
|
Tui::min_xy(w as u16, self.header_h,
|
||||||
|
Tui::to_south(name, timer))))
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
struct ArrangerVerticalContent<'a> {
|
||||||
|
size: &'a Measure<Tui>,
|
||||||
|
scenes: &'a Vec<ArrangerScene>,
|
||||||
|
tracks: &'a Vec<ArrangerTrack>,
|
||||||
|
rows: Vec<(usize, usize)>,
|
||||||
|
cols: Vec<(usize, usize)>,
|
||||||
|
header_h: u16,
|
||||||
|
}
|
||||||
|
impl<'a> From<(&'a ArrangerTui, usize)> for ArrangerVerticalContent<'a> {
|
||||||
|
fn from ((state, factor): (&'a ArrangerTui, usize)) -> Self {
|
||||||
|
Self {
|
||||||
|
size: &state.size,
|
||||||
|
scenes: &state.scenes,
|
||||||
|
tracks: &state.tracks,
|
||||||
|
rows: ArrangerScene::ppqs(state.scenes(), factor),
|
||||||
|
cols: track_widths(state.tracks()),
|
||||||
|
header_h: 3,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
render!(|self: ArrangerVerticalContent<'a>|Tui::fixed_y(
|
||||||
|
(self.size.h() as u16).saturating_sub(self.header_h),
|
||||||
|
col!((scene, pulses) in self.scenes.iter().zip(self.rows.iter().map(|row|row.0)) => {
|
||||||
|
let height = 1.max((pulses / PPQ) as u16);
|
||||||
|
let playing = scene.is_playing(self.tracks);
|
||||||
|
Tui::fixed_y(
|
||||||
|
height,
|
||||||
|
Tui::to_east(
|
||||||
|
Tui::to_east(
|
||||||
|
if playing { "▶ " } else { " " },
|
||||||
|
Tui::bold(true, scene.name.read().unwrap().as_str())
|
||||||
|
),
|
||||||
|
row!((track, w) in self.cols.iter().map(|col|col.0).enumerate() => {
|
||||||
|
Tui::fixed_xy(w as u16, height, Layers::new(move |add|{
|
||||||
|
let mut bg = TuiTheme::border_bg();
|
||||||
|
match (self.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;
|
||||||
|
bg = color.dark.rgb;
|
||||||
|
if let Some((_, Some(ref playing))) = track.player.play_phrase() {
|
||||||
|
if *playing.read().unwrap() == *phrase.read().unwrap() {
|
||||||
|
bg = color.light.rgb
|
||||||
|
}
|
||||||
|
};
|
||||||
|
add(&Tui::fixed_x(w as u16, Tui::push_x(1, &name.as_str()[0..max_w])))?;
|
||||||
|
},
|
||||||
|
_ => {}
|
||||||
|
};
|
||||||
|
//add(&Background(bg))
|
||||||
|
Ok(())
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
));
|
||||||
|
|
||||||
pub fn arranger_content_horizontal (
|
pub fn arranger_content_horizontal (
|
||||||
view: &ArrangerTui,
|
view: &ArrangerTui,
|
||||||
) -> impl Render<Tui> + use<'_> {
|
) -> impl Render<Tui> + use<'_> {
|
||||||
todo!()
|
todo!()
|
||||||
|
}
|
||||||
//let focused = view.arranger_focused();
|
//let focused = view.arranger_focused();
|
||||||
//let _tracks = view.tracks();
|
//let _tracks = view.tracks();
|
||||||
//lay!(
|
//lay!(
|
||||||
|
|
@ -526,4 +558,4 @@ pub fn arranger_content_horizontal (
|
||||||
//}),
|
//}),
|
||||||
//)
|
//)
|
||||||
//)
|
//)
|
||||||
}
|
//}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue