mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 11:46:41 +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 {
|
||||
($(move)?|$add:ident|$expr:expr) => {
|
||||
Stack::up($(move)?|$add|$expr)
|
||||
([$($expr: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) => {
|
||||
Stack::up(move |add|{
|
||||
for $pat in $collection { add(&$item)?; }
|
||||
Ok(())
|
||||
})
|
||||
};
|
||||
($($expr:expr),* $(,)?) => {
|
||||
Stack::up(move|add|{ $(add(&$expr)?;)* Ok(()) })
|
||||
Stack::up(move |add|{ for $pat in $collection { add(&$item)?; } Ok(()) })
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,42 +3,37 @@ use crate::*;
|
|||
// Layout for standalone arranger app.
|
||||
render!(|self: ArrangerTui|{
|
||||
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),
|
||||
Tui::to_south(
|
||||
self.splits[0],
|
||||
Tui::to_south(
|
||||
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(arranger_focused)))),
|
||||
widget(&self.size),
|
||||
widget(&format!("[{}] Arranger", if self.entered {
|
||||
col!([
|
||||
Tui::fixed_y(self.splits[0], lay!([
|
||||
border.wrap(Tui::grow_y(1, 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)
|
||||
}))),
|
||||
self.size,
|
||||
Tui::push_x(1, Tui::fg(
|
||||
TuiTheme::title_fg(arranger_focused),
|
||||
format!("[{}] Arranger", if self.entered {
|
||||
"■"
|
||||
} else {
|
||||
" "
|
||||
}))
|
||||
.fg(TuiTheme::title_fg(arranger_focused))
|
||||
.push_x(1),
|
||||
]),
|
||||
Split::right(
|
||||
self.splits[1],
|
||||
PhraseListView::from(self),
|
||||
PhraseView::from(self),
|
||||
)
|
||||
})
|
||||
))
|
||||
])),
|
||||
Split::right(
|
||||
self.splits[1],
|
||||
PhraseListView::from(self),
|
||||
PhraseView::from(self),
|
||||
)
|
||||
)
|
||||
)
|
||||
])
|
||||
])
|
||||
});
|
||||
|
||||
/// Display mode of arranger
|
||||
|
|
@ -97,114 +92,19 @@ pub fn arranger_content_vertical (
|
|||
view: &ArrangerTui,
|
||||
factor: usize
|
||||
) -> 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!([
|
||||
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),
|
||||
ArrangerVerticalRowSeparator::from((view, factor)),
|
||||
col!(![header, content]),
|
||||
ArrangerCursor::from((view, factor)),
|
||||
col!(![
|
||||
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)>,
|
||||
rows: Vec<(usize, usize)>,
|
||||
focused: bool,
|
||||
|
|
@ -267,7 +167,7 @@ struct ArrangerCursor {
|
|||
scenes_w: u16,
|
||||
header_h: u16,
|
||||
}
|
||||
impl From<(&ArrangerTui, usize)> for ArrangerCursor {
|
||||
impl From<(&ArrangerTui, usize)> for ArrangerVerticalCursor {
|
||||
fn from ((state, factor): (&ArrangerTui, usize)) -> Self {
|
||||
Self {
|
||||
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 focused = self.focused;
|
||||
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 (
|
||||
view: &ArrangerTui,
|
||||
) -> impl Render<Tui> + use<'_> {
|
||||
todo!()
|
||||
}
|
||||
//let focused = view.arranger_focused();
|
||||
//let _tracks = view.tracks();
|
||||
//lay!(
|
||||
|
|
@ -526,4 +558,4 @@ pub fn arranger_content_horizontal (
|
|||
//}),
|
||||
//)
|
||||
//)
|
||||
}
|
||||
//}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue