mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-07 12:16:42 +01:00
255 lines
9.3 KiB
Rust
255 lines
9.3 KiB
Rust
use crate::*;
|
|
pub struct ArrangerVColSep {
|
|
cols: Vec<(usize, usize)>,
|
|
scenes_w: u16,
|
|
sep_fg: Color,
|
|
}
|
|
from!(|state:&ArrangerTui|ArrangerVColSep = Self {
|
|
cols: track_widths(state.tracks()),
|
|
scenes_w: 3 + ArrangerScene::longest_name(state.scenes()) as u16,
|
|
sep_fg: TuiTheme::separator_fg(false),
|
|
});
|
|
render!(<Tui>|self: ArrangerVColSep|render(move|to: &mut TuiOutput|{
|
|
let style = Some(Style::default().fg(self.sep_fg));
|
|
Ok(for x in self.cols.iter().map(|col|col.1) {
|
|
let x = self.scenes_w + to.area().x() + x as u16;
|
|
for y in to.area().y()..to.area().y2() {
|
|
to.blit(&"▎", x, y, style);
|
|
}
|
|
})
|
|
}));
|
|
pub struct ArrangerVRowSep {
|
|
rows: Vec<(usize, usize)>,
|
|
sep_fg: Color,
|
|
}
|
|
from!(|args:(&ArrangerTui, usize)|ArrangerVRowSep = Self {
|
|
rows: ArrangerScene::ppqs(args.0.scenes(), args.1),
|
|
sep_fg: TuiTheme::separator_fg(false),
|
|
});
|
|
render!(<Tui>|self: ArrangerVRowSep|render(move|to: &mut TuiOutput|{
|
|
Ok(for y in self.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 = self.sep_fg;
|
|
}
|
|
}
|
|
})
|
|
}));
|
|
pub struct ArrangerVCursor {
|
|
cols: Vec<(usize, usize)>,
|
|
rows: Vec<(usize, usize)>,
|
|
focused: bool,
|
|
selected: ArrangerSelection,
|
|
scenes_w: u16,
|
|
header_h: u16,
|
|
}
|
|
from!(|args:(&ArrangerTui, usize)|ArrangerVCursor = Self {
|
|
cols: track_widths(state.tracks()),
|
|
rows: ArrangerScene::ppqs(args.0.scenes(), args.1),
|
|
focused: true,
|
|
selected: state.selected,
|
|
scenes_w: 3 + ArrangerScene::longest_name(state.scenes()) as u16,
|
|
header_h: 3,
|
|
});
|
|
render!(<Tui>|self: ArrangerVCursor|render(move|to: &mut TuiOutput|{
|
|
let area = to.area();
|
|
let focused = self.focused;
|
|
let selected = self.selected;
|
|
let get_track_area = |t: usize| [
|
|
self.scenes_w + area.x() + self.cols[t].1 as u16,
|
|
area.y(),
|
|
self.cols[t].0 as u16,
|
|
area.h(),
|
|
];
|
|
let get_scene_area = |s: usize| [
|
|
area.x(),
|
|
self.header_h + area.y() + (self.rows[s].1 / PPQ) as u16,
|
|
area.w(),
|
|
(self.rows[s].0 / PPQ) as u16
|
|
];
|
|
let get_clip_area = |t: usize, s: usize| [
|
|
self.scenes_w + area.x() + self.cols[t].1 as u16,
|
|
self.header_h + area.y() + (self.rows[s].1/PPQ) as u16,
|
|
self.cols[t].0 as u16,
|
|
(self.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(self.header_h) }
|
|
else if let Some(scene_area) = scene_area { scene_area.clip_w(self.scenes_w) }
|
|
else { area.clip_w(self.scenes_w).clip_h(self.header_h) }, &CORNERS)?
|
|
})
|
|
}));
|
|
|
|
pub struct ArrangerVHead<'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>,
|
|
}
|
|
from!(<'a>|state: &'a ArrangerTui|ArrangerVHead<'a> = Self { // A
|
|
tracks: &state.tracks,
|
|
cols: track_widths(state.tracks()),
|
|
focused: true,
|
|
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!(<Tui>|self: ArrangerVHead<'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 = Tui::bold(true, Tui::fg(track.color.lightest.rgb, format!("▎{}", &name[0..max_w])));
|
|
|
|
// name of active MIDI input
|
|
let input = format!(">{}", track.player.midi_ins().get(0)
|
|
.map(|port|port.short_name())
|
|
.transpose()?
|
|
.unwrap_or("(none)".into()));
|
|
|
|
// 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::new()
|
|
};
|
|
|
|
// 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()
|
|
}
|
|
});
|
|
|
|
// 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().base.rgb,
|
|
Tui::min_xy(w as u16, self.header_h, row!([
|
|
col!(!["▎", "▎", "▎", "▎", "▎", "▎",]),
|
|
col!(![name, input, output, elapsed, until_next, output])]))))
|
|
}
|
|
));
|
|
|
|
pub struct ArrangerVBody<'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 ArrangerVBody<'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!(<Tui>|self: ArrangerVBody<'a>|Fixed::h(
|
|
(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);
|
|
Fixed::h(height, row!([
|
|
if playing { "▶ " } else { " " },
|
|
Tui::bold(true, scene.name.read().unwrap().as_str()),
|
|
row!((track, w) in self.cols.iter().map(|col|col.0).enumerate() => {
|
|
Fixed::wh(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(&Fixed::w(w as u16, Tui::push_x(1, &name.as_str()[0..max_w])))?;
|
|
},
|
|
_ => {}
|
|
};
|
|
//add(&Background(bg))
|
|
Ok(())
|
|
}))
|
|
})])
|
|
)
|
|
})
|
|
));
|
|
|
|
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
|
|
}
|