mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-07 12:16:42 +01:00
unify arranger, sequencer, transport
This commit is contained in:
parent
cd2555ffc7
commit
77519dbb5c
18 changed files with 1618 additions and 1639 deletions
|
|
@ -106,14 +106,22 @@ impl Widget for Arranger<Tui> {
|
|||
}
|
||||
fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> {
|
||||
let area = (|to|match self.mode {
|
||||
ArrangerViewMode::Horizontal =>
|
||||
super::arranger_view_h::draw(self, to),
|
||||
ArrangerViewMode::VerticalCompact1 =>
|
||||
super::arranger_view_v::draw_compact_1(self, to),
|
||||
ArrangerViewMode::VerticalCompact2 =>
|
||||
super::arranger_view_v::draw_compact_2(self, to),
|
||||
ArrangerViewMode::VerticalExpanded =>
|
||||
super::arranger_view_v::draw_expanded(self, to),
|
||||
ArrangerViewMode::Horizontal => draw_h(self, to),
|
||||
ArrangerViewMode::VerticalCompact1 => {
|
||||
let track_cols = track_clip_name_lengths(self.tracks.as_slice());
|
||||
let scene_rows = (0..=self.scenes.len()).map(|i|(96, 96*i)).collect::<Vec<_>>();
|
||||
draw_v(self, to, track_cols.as_slice(), scene_rows.as_slice())
|
||||
},
|
||||
ArrangerViewMode::VerticalCompact2 => {
|
||||
let track_cols = track_clip_name_lengths(self.tracks.as_slice());
|
||||
let scene_rows = (0..=self.scenes.len()).map(|i|(192, 192*i)).collect::<Vec<_>>();
|
||||
draw_v(self, to, track_cols.as_slice(), scene_rows.as_slice())
|
||||
},
|
||||
ArrangerViewMode::VerticalExpanded => {
|
||||
let track_cols = track_clip_name_lengths(self.tracks.as_slice());
|
||||
let scene_rows = scene_ppqs(self.tracks.as_slice(), self.scenes.as_slice());
|
||||
draw_v(self, to, track_cols.as_slice(), scene_rows.as_slice())
|
||||
},
|
||||
})(&mut to.alter_area(|[x, y, w, h]|[
|
||||
x + 1,
|
||||
y + 1,
|
||||
|
|
@ -137,3 +145,879 @@ impl Focusable<Tui> for Arranger<Tui> {
|
|||
self.focused = focused
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Copy)]
|
||||
/// Represents the current user selection in the arranger
|
||||
pub enum ArrangerFocus {
|
||||
/** 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 ArrangerFocus {
|
||||
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 }
|
||||
}
|
||||
}
|
||||
|
||||
/// Track focus methods
|
||||
impl ArrangerFocus {
|
||||
pub fn track (&self) -> Option<usize> {
|
||||
match self {
|
||||
Self::Clip(t, _) => Some(*t),
|
||||
Self::Track(t) => Some(*t),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
pub fn track_next (&mut self, last_track: usize) {
|
||||
*self = match self {
|
||||
Self::Mix => Self::Track(0),
|
||||
Self::Track(t) => Self::Track(last_track.min(*t + 1)),
|
||||
Self::Scene(s) => Self::Clip(0, *s),
|
||||
Self::Clip(t, s) => Self::Clip(last_track.min(*t + 1), *s),
|
||||
}
|
||||
}
|
||||
pub fn track_prev (&mut self) {
|
||||
*self = match self {
|
||||
Self::Mix => Self::Mix,
|
||||
Self::Scene(s) => Self::Scene(*s),
|
||||
Self::Track(t) => if *t == 0 {
|
||||
Self::Mix
|
||||
} else {
|
||||
Self::Track(*t - 1)
|
||||
},
|
||||
Self::Clip(t, s) => if *t == 0 {
|
||||
Self::Scene(*s)
|
||||
} else {
|
||||
Self::Clip(t.saturating_sub(1), *s)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Scene focus methods
|
||||
impl ArrangerFocus {
|
||||
pub fn scene (&self) -> Option<usize> {
|
||||
match self {
|
||||
Self::Clip(_, s) => Some(*s),
|
||||
Self::Scene(s) => Some(*s),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
pub fn scene_next (&mut self, last_scene: usize) {
|
||||
*self = match self {
|
||||
Self::Mix => Self::Scene(0),
|
||||
Self::Track(t) => Self::Clip(*t, 0),
|
||||
Self::Scene(s) => Self::Scene(last_scene.min(*s + 1)),
|
||||
Self::Clip(t, s) => Self::Clip(*t, last_scene.min(*s + 1)),
|
||||
}
|
||||
}
|
||||
pub fn scene_prev (&mut self) {
|
||||
*self = match self {
|
||||
Self::Mix => Self::Mix,
|
||||
Self::Track(t) => Self::Track(*t),
|
||||
Self::Scene(s) => if *s == 0 {
|
||||
Self::Mix
|
||||
} else {
|
||||
Self::Scene(*s - 1)
|
||||
},
|
||||
Self::Clip(t, s) => if *s == 0 {
|
||||
Self::Track(*t)
|
||||
} else {
|
||||
Self::Clip(*t, s.saturating_sub(1))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl Handle<Tui> for Arranger<Tui> {
|
||||
fn handle (&mut self, from: &Tui) -> Perhaps<bool> {
|
||||
if let Some(modal) = self.modal.as_mut() {
|
||||
let result = modal.handle(from)?;
|
||||
if modal.exited() {
|
||||
self.modal = None;
|
||||
}
|
||||
return Ok(result)
|
||||
}
|
||||
match from.event() {
|
||||
// mode_switch: switch the display mode
|
||||
key!(KeyCode::Char('`')) => {
|
||||
self.mode.to_next();
|
||||
Ok(Some(true))
|
||||
},
|
||||
// cursor_up: move cursor up
|
||||
key!(KeyCode::Up) => {
|
||||
match self.mode {
|
||||
ArrangerViewMode::Horizontal => self.track_prev(),
|
||||
_ => self.scene_prev(),
|
||||
};
|
||||
self.show_phrase()?;
|
||||
Ok(Some(true))
|
||||
},
|
||||
// cursor_down
|
||||
key!(KeyCode::Down) => {
|
||||
match self.mode {
|
||||
ArrangerViewMode::Horizontal => self.track_next(),
|
||||
_ => self.scene_next(),
|
||||
};
|
||||
self.show_phrase()?;
|
||||
Ok(Some(true))
|
||||
},
|
||||
// cursor left
|
||||
key!(KeyCode::Left) => {
|
||||
match self.mode {
|
||||
ArrangerViewMode::Horizontal => self.scene_prev(),
|
||||
_ => self.track_prev(),
|
||||
};
|
||||
self.show_phrase()?;
|
||||
Ok(Some(true))
|
||||
},
|
||||
// cursor right
|
||||
key!(KeyCode::Right) => {
|
||||
match self.mode {
|
||||
ArrangerViewMode::Horizontal => self.scene_next(),
|
||||
_ => self.track_next(),
|
||||
};
|
||||
self.show_phrase()?;
|
||||
Ok(Some(true))
|
||||
},
|
||||
// increment: use next clip here
|
||||
key!(KeyCode::Char('.')) => {
|
||||
self.phrase_next();
|
||||
Ok(Some(true))
|
||||
},
|
||||
// decrement: use previous next clip here
|
||||
key!(KeyCode::Char(',')) => {
|
||||
self.phrase_prev();
|
||||
Ok(Some(true))
|
||||
},
|
||||
// decrement: use previous clip here
|
||||
key!(KeyCode::Enter) => {
|
||||
self.activate();
|
||||
Ok(Some(true))
|
||||
},
|
||||
// scene_add: add a new scene
|
||||
key!(Ctrl-KeyCode::Char('a')) => {
|
||||
self.scene_add(None)?;
|
||||
Ok(Some(true))
|
||||
},
|
||||
// track_add: add a new scene
|
||||
key!(Ctrl-KeyCode::Char('t')) => {
|
||||
self.track_add(None)?;
|
||||
Ok(Some(true))
|
||||
},
|
||||
// rename: add a new scene
|
||||
key!(KeyCode::Char('n')) => {
|
||||
self.rename_selected();
|
||||
Ok(Some(true))
|
||||
},
|
||||
// length: add a new scene
|
||||
key!(KeyCode::Char('l')) => {
|
||||
todo!();
|
||||
Ok(Some(true))
|
||||
},
|
||||
// color: set color of item at cursor
|
||||
key!(KeyCode::Char('c')) => {
|
||||
todo!();
|
||||
Ok(Some(true))
|
||||
},
|
||||
_ => Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Arranger<Tui> {
|
||||
pub fn rename_selected (&mut self) {
|
||||
self.modal = Some(Box::new(ArrangerRenameModal::new(
|
||||
self.selected,
|
||||
&match self.selected {
|
||||
ArrangerFocus::Mix => self.name.clone(),
|
||||
ArrangerFocus::Track(t) => self.tracks[t].name.clone(),
|
||||
ArrangerFocus::Scene(s) => self.scenes[s].name.clone(),
|
||||
ArrangerFocus::Clip(t, s) => self.tracks[t].phrases[s].read().unwrap().name.clone(),
|
||||
}
|
||||
)));
|
||||
}
|
||||
}
|
||||
/// Appears on first run (i.e. if state dir is missing).
|
||||
pub struct ArrangerRenameModal<E: Engine> {
|
||||
_engine: std::marker::PhantomData<E>,
|
||||
done: bool,
|
||||
target: ArrangerFocus,
|
||||
value: String,
|
||||
result: Arc<RwLock<String>>,
|
||||
cursor: usize
|
||||
}
|
||||
impl<E: Engine> ArrangerRenameModal<E> {
|
||||
pub fn new (target: ArrangerFocus, value: &Arc<RwLock<String>>) -> Self {
|
||||
Self {
|
||||
_engine: Default::default(),
|
||||
done: false,
|
||||
target,
|
||||
value: value.read().unwrap().clone(),
|
||||
cursor: value.read().unwrap().len(),
|
||||
result: value.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Widget for ArrangerRenameModal<Tui> {
|
||||
type Engine = Tui;
|
||||
fn layout (&self, area: [u16;4]) -> Perhaps<[u16;4]> {
|
||||
todo!()
|
||||
}
|
||||
fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> {
|
||||
let area = to.area();
|
||||
let y = area.y() + area.h() / 2;
|
||||
let bg_area = [1, y - 1, area.w() - 2, 3];
|
||||
to.fill_bg(bg_area, Nord::BG0);
|
||||
Lozenge(Style::default().bold().white().dim()).draw(to.with_rect(bg_area));
|
||||
let label = match self.target {
|
||||
ArrangerFocus::Mix => "Rename project:",
|
||||
ArrangerFocus::Track(_) => "Rename track:",
|
||||
ArrangerFocus::Scene(_) => "Rename scene:",
|
||||
ArrangerFocus::Clip(_, _) => "Rename clip:",
|
||||
};
|
||||
let style = Some(Style::default().not_bold().white().not_dim());
|
||||
to.blit(&label, area.x() + 3, y, style)?;
|
||||
let style = Some(Style::default().bold().white().not_dim());
|
||||
to.blit(&self.value, area.x() + 3 + label.len() as u16 + 1, y, style)?;
|
||||
let style = Some(Style::default().bold().white().not_dim().reversed());
|
||||
to.blit(&"▂", area.x() + 3 + label.len() as u16 + 1 + self.cursor as u16, y, style)?;
|
||||
Ok(Some(area))
|
||||
}
|
||||
}
|
||||
impl Handle<Tui> for ArrangerRenameModal<Tui> {
|
||||
fn handle (&mut self, from: &Tui) -> Perhaps<bool> {
|
||||
match from.event() {
|
||||
TuiEvent::Input(Event::Key(k)) => {
|
||||
match k.code {
|
||||
KeyCode::Esc => {
|
||||
self.exit();
|
||||
},
|
||||
KeyCode::Enter => {
|
||||
*self.result.write().unwrap() = self.value.clone();
|
||||
self.exit();
|
||||
},
|
||||
KeyCode::Left => {
|
||||
self.cursor = self.cursor.saturating_sub(1);
|
||||
},
|
||||
KeyCode::Right => {
|
||||
self.cursor = self.value.len().min(self.cursor + 1)
|
||||
},
|
||||
KeyCode::Backspace => {
|
||||
let last = self.value.len().saturating_sub(1);
|
||||
self.value = format!("{}{}",
|
||||
&self.value[0..self.cursor.min(last)],
|
||||
&self.value[self.cursor.min(last)..last]
|
||||
);
|
||||
self.cursor = self.cursor.saturating_sub(1)
|
||||
}
|
||||
KeyCode::Char(c) => {
|
||||
self.value.insert(self.cursor, c);
|
||||
self.cursor = self.value.len().min(self.cursor + 1)
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
Ok(Some(true))
|
||||
},
|
||||
_ => Ok(None),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<E: Engine + Send> Exit for ArrangerRenameModal<E> {
|
||||
fn exited (&self) -> bool {
|
||||
self.done
|
||||
}
|
||||
fn exit (&mut self) {
|
||||
self.done = true
|
||||
}
|
||||
}
|
||||
|
||||
/// Track management methods
|
||||
impl<E: Engine> Arranger<E> {
|
||||
pub fn track (&self) -> Option<&Sequencer<E>> {
|
||||
self.selected.track().map(|t|self.tracks.get(t)).flatten()
|
||||
}
|
||||
pub fn track_mut (&mut self) -> Option<&mut Sequencer<E>> {
|
||||
self.selected.track().map(|t|self.tracks.get_mut(t)).flatten()
|
||||
}
|
||||
pub fn track_next (&mut self) {
|
||||
self.selected.track_next(self.tracks.len() - 1)
|
||||
}
|
||||
pub fn track_prev (&mut self) {
|
||||
self.selected.track_prev()
|
||||
}
|
||||
pub fn track_add (&mut self, name: Option<&str>) -> Usually<&mut Sequencer<E>> {
|
||||
self.tracks.push(name.map_or_else(
|
||||
|| Sequencer::new(&self.track_default_name()),
|
||||
|name| Sequencer::new(name),
|
||||
));
|
||||
let index = self.tracks.len() - 1;
|
||||
Ok(&mut self.tracks[index])
|
||||
}
|
||||
pub fn track_del (&mut self) {
|
||||
unimplemented!("Arranger::track_del");
|
||||
}
|
||||
pub fn track_default_name (&self) -> String {
|
||||
format!("Track {}", self.tracks.len() + 1)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn track_name_max_len <E: Engine> (tracks: &[Sequencer<E>]) -> usize {
|
||||
tracks.iter()
|
||||
.map(|s|s.name.read().unwrap().len())
|
||||
.fold(0, usize::max)
|
||||
}
|
||||
|
||||
pub fn track_clip_name_lengths <E: Engine> (tracks: &[Sequencer<E>]) -> Vec<(usize, usize)> {
|
||||
let mut total = 0;
|
||||
let mut lengths: Vec<(usize, usize)> = tracks.iter().map(|track|{
|
||||
let len = 4 + track.phrases
|
||||
.iter()
|
||||
.fold(track.name.read().unwrap().len(), |len, phrase|{
|
||||
len.max(phrase.read().unwrap().name.read().unwrap().len())
|
||||
});
|
||||
total = total + len;
|
||||
(len, total - len)
|
||||
}).collect();
|
||||
lengths.push((0, total));
|
||||
lengths
|
||||
}
|
||||
|
||||
/// Draw arranger with 1 row per scene.
|
||||
pub fn draw_v_compact_1 <'a> (
|
||||
state: &Arranger<Tui>, to: &mut Tui
|
||||
) -> Perhaps<[u16;4]> {
|
||||
let track_cols = track_clip_name_lengths(state.tracks.as_slice());
|
||||
let scene_rows = (0..=state.scenes.len()).map(|i|(96, 96*i)).collect::<Vec<_>>();
|
||||
draw_v(state, to, track_cols.as_slice(), scene_rows.as_slice())
|
||||
}
|
||||
|
||||
/// Draw arranger with 2 rows per scene.
|
||||
pub fn draw_v_compact_2 <'a> (
|
||||
state: &Arranger<Tui>, to: &mut Tui
|
||||
) -> Perhaps<[u16;4]> {
|
||||
let track_cols = track_clip_name_lengths(state.tracks.as_slice());
|
||||
let scene_rows = (0..=state.scenes.len()).map(|i|(192, 192*i)).collect::<Vec<_>>();
|
||||
draw_v(state, to, track_cols.as_slice(), scene_rows.as_slice())
|
||||
}
|
||||
|
||||
/// Draw arranger with number of rows per scene proportional to duration of scene.
|
||||
pub fn draw_v_expanded <'a> (
|
||||
state: &Arranger<Tui>, to: &mut Tui
|
||||
) -> Perhaps<[u16;4]> {
|
||||
let track_cols = track_clip_name_lengths(state.tracks.as_slice());
|
||||
let scene_rows = scene_ppqs(state.tracks.as_slice(), state.scenes.as_slice());
|
||||
draw_v(state, to, track_cols.as_slice(), scene_rows.as_slice())
|
||||
}
|
||||
|
||||
pub fn draw_v <'a, 'b> (
|
||||
state: &Arranger<Tui>,
|
||||
to: &mut Tui,
|
||||
cols: &'b [(usize, usize)],
|
||||
rows: &'b [(usize, usize)],
|
||||
) -> Perhaps<[u16;4]> {
|
||||
let area = to.area();
|
||||
let area = [area.x(), area.y(), area.w(), 2 + (rows[rows.len() - 1].1 / 96) as u16];
|
||||
let offset = 3 + scene_name_max_len(state.scenes.as_ref()) as u16;
|
||||
let tracks = state.tracks.as_ref();
|
||||
let scenes = state.scenes.as_ref();
|
||||
Layers::new(|add|{
|
||||
//.add_ref(&FillBg(Color::Rgb(30, 33, 36)))//COLOR_BG1))//bg_lo(state.focused, state.entered)))
|
||||
add(&ColumnSeparators(offset, cols))?;
|
||||
add(&RowSeparators(rows))?;
|
||||
add(&CursorFocus(state.selected, offset, cols, rows))?;
|
||||
add(&Split::right(|add|{
|
||||
add(&TracksHeader(offset, cols, tracks))?;
|
||||
add(&SceneRows(offset, cols, rows, tracks, scenes))
|
||||
}))
|
||||
}).render(to.with_rect(area))
|
||||
}
|
||||
|
||||
struct ColumnSeparators<'a>(u16, &'a [(usize, usize)]);
|
||||
|
||||
impl<'a> Widget for ColumnSeparators<'a> {
|
||||
type Engine = Tui;
|
||||
fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> {
|
||||
let area = to.area();
|
||||
let Self(offset, cols) = self;
|
||||
let style = Some(Style::default().fg(Nord::SEPARATOR));
|
||||
for (_, x) in cols.iter() {
|
||||
let x = offset + area.x() + *x as u16 - 1;
|
||||
for y in area.y()..area.y2() {
|
||||
to.blit(&"▎", x, y, style)?;
|
||||
}
|
||||
}
|
||||
Ok(Some(area))
|
||||
}
|
||||
}
|
||||
|
||||
struct RowSeparators<'a>(&'a [(usize, usize)]);
|
||||
|
||||
impl<'a> Widget for RowSeparators<'a> {
|
||||
type Engine = Tui;
|
||||
fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> {
|
||||
let area = to.area();
|
||||
let Self(rows) = self;
|
||||
for (_, y) in rows.iter() {
|
||||
let y = area.y() + (*y / 96) as u16 + 1;
|
||||
if y >= to.buffer().area.height {
|
||||
break
|
||||
}
|
||||
for x in area.x()..area.x2().saturating_sub(2) {
|
||||
let cell = to.buffer().get_mut(x, y);
|
||||
cell.modifier = Modifier::UNDERLINED;
|
||||
cell.underline_color = Nord::SEPARATOR;
|
||||
}
|
||||
}
|
||||
Ok(Some(area))
|
||||
}
|
||||
}
|
||||
|
||||
struct CursorFocus<'a>(
|
||||
ArrangerFocus, u16, &'a [(usize, usize)], &'a [(usize, usize)]
|
||||
);
|
||||
|
||||
impl<'a> Widget for CursorFocus<'a> {
|
||||
type Engine = Tui;
|
||||
fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> {
|
||||
let area = to.area();
|
||||
let Self(selected, offset, cols, rows) = *self;
|
||||
let get_track_area = |t: usize| [
|
||||
offset + area.x() + cols[t].1 as u16 - 1,
|
||||
area.y(),
|
||||
cols[t].0 as u16,
|
||||
area.h()
|
||||
];
|
||||
let get_scene_area = |s: usize| [
|
||||
area.x(),
|
||||
2 + area.y() + (rows[s].1 / 96) as u16,
|
||||
area.w(),
|
||||
(rows[s].0 / 96) as u16
|
||||
];
|
||||
let get_clip_area = |t: usize, s: usize| [
|
||||
offset + area.x() + cols[t].1 as u16 - 1,
|
||||
2 + area.y() + (rows[s].1 / 96) as u16,
|
||||
cols[t].0 as u16,
|
||||
(rows[s].0 / 96) 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 {
|
||||
ArrangerFocus::Mix => {
|
||||
to.fill_bg(area, COLOR_BG0);
|
||||
area
|
||||
},
|
||||
ArrangerFocus::Track(t) => {
|
||||
track_area = Some(get_track_area(t));
|
||||
area
|
||||
},
|
||||
ArrangerFocus::Scene(s) => {
|
||||
scene_area = Some(get_scene_area(s));
|
||||
area
|
||||
},
|
||||
ArrangerFocus::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
|
||||
},
|
||||
};
|
||||
if let Some([x, y, width, height]) = track_area {
|
||||
to.fill_fg([x, y, 1, height], COLOR_BG5);
|
||||
to.fill_fg([x + width, y, 1, height], COLOR_BG5);
|
||||
}
|
||||
if let Some([_, y, _, height]) = scene_area {
|
||||
to.fill_ul([area.x(), y - 1, area.w(), 1], COLOR_BG5);
|
||||
to.fill_ul([area.x(), y + height - 1, area.w(), 1], COLOR_BG5);
|
||||
}
|
||||
if let Some(clip_area) = clip_area {
|
||||
to.fill_bg(clip_area, COLOR_BG0);
|
||||
} else if let Some(track_area) = track_area {
|
||||
to.fill_bg(track_area, COLOR_BG0);
|
||||
} else if let Some(scene_area) = scene_area {
|
||||
to.fill_bg(scene_area, COLOR_BG0);
|
||||
}
|
||||
Ok(Some(area))
|
||||
}
|
||||
}
|
||||
|
||||
struct TracksHeader<'a>(u16, &'a[(usize, usize)], &'a [Sequencer<Tui>]);
|
||||
|
||||
impl<'a> Widget for TracksHeader<'a> {
|
||||
type Engine = Tui;
|
||||
fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> {
|
||||
let area = to.area();
|
||||
let Self(offset, track_cols, tracks) = *self;
|
||||
let [x, y, width, _] = area;
|
||||
for (track, (w, x)) in tracks.iter().zip(track_cols) {
|
||||
let x = *x as u16;
|
||||
if x > width {
|
||||
break
|
||||
}
|
||||
let name = track.name.read().unwrap();
|
||||
to.fill_bg([offset + x, y, *w as u16, 2], COLOR_BG1);
|
||||
to.blit(&*name, offset + x + 1, y, Some(Style::default().white()))?;
|
||||
}
|
||||
Ok(Some([x, y, width, 2]))
|
||||
}
|
||||
}
|
||||
|
||||
struct SceneRows<'a>(u16, &'a[(usize, usize)], &'a[(usize, usize)], &'a[Sequencer<Tui>], &'a[Scene]);
|
||||
|
||||
impl<'a> Widget for SceneRows<'a> {
|
||||
type Engine = Tui;
|
||||
fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> {
|
||||
let area = to.area();
|
||||
let Self(offset, track_cols, scene_rows, tracks, scenes) = *self;
|
||||
let black = Some(Style::default().fg(Nord::SEPARATOR));
|
||||
let [_, mut y, _, _height] = area;
|
||||
for (_, x) in track_cols.iter() {
|
||||
let x = *x as u16;
|
||||
if x > 0 {
|
||||
for y in area.y()-2..y-2 {
|
||||
to.blit(&"▎", x - 1, y, black)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (scene, (pulses, _)) in scenes.iter().zip(scene_rows) {
|
||||
//if y > height {
|
||||
//break
|
||||
//}
|
||||
let h = 1.max((pulses / 96) as u16);
|
||||
SceneRow(tracks, scene, track_cols, offset)
|
||||
.render(to.with_area(area.x(), y, area.w(), h))?;
|
||||
y = y + h
|
||||
}
|
||||
Ok(Some(area))
|
||||
}
|
||||
}
|
||||
|
||||
struct SceneRow<'a>(&'a[Sequencer<Tui>], &'a Scene, &'a[(usize, usize)], u16);
|
||||
|
||||
impl<'a> Widget for SceneRow<'a> {
|
||||
type Engine = Tui;
|
||||
fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> {
|
||||
let area = to.area();
|
||||
let Self(tracks, scene, track_cols, offset) = self;
|
||||
let [x, y, width, _] = area;
|
||||
let playing = scene.is_playing(tracks);
|
||||
to.blit(&if playing { "▶" } else { " " }, x, y, None)?;
|
||||
to.blit(&*scene.name.read().unwrap(), x + 1, y, Some(Style::default().white()))?;
|
||||
to.fill_bg([x, y, offset.saturating_sub(1), area.h()], COLOR_BG1);
|
||||
for (track, (w, x)) in track_cols.iter().enumerate() {
|
||||
let x = *x as u16 + offset;
|
||||
if x > width {
|
||||
break
|
||||
}
|
||||
if let (Some(track), Some(Some(clip))) = (
|
||||
tracks.get(track), scene.clips.get(track)
|
||||
) {
|
||||
SceneClip(track, *clip).render(to.with_area(x, y, *w as u16, area.h()))?;
|
||||
}
|
||||
}
|
||||
Ok(Some(area))
|
||||
}
|
||||
}
|
||||
|
||||
struct SceneClip<'a>(&'a Sequencer<Tui>, usize);
|
||||
|
||||
impl<'a> Widget for SceneClip<'a> {
|
||||
type Engine = Tui;
|
||||
fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> {
|
||||
let area = to.area();
|
||||
let Self(track, clip) = self;
|
||||
let style = Some(Style::default().white());
|
||||
if let Some(phrase) = track.phrases.get(*clip) {
|
||||
let phrase = phrase.read().unwrap();
|
||||
let name = phrase.name.read().unwrap();
|
||||
to.blit(&format!("{clip:02} {name}"), area.x() + 1, area.y(), style)?;
|
||||
to.fill_bg(area, if track.sequence == Some(*clip) {
|
||||
Nord::PLAYING
|
||||
} else {
|
||||
COLOR_BG1
|
||||
});
|
||||
} else {
|
||||
to.fill_bg(area, COLOR_BG0)
|
||||
}
|
||||
Ok(Some(area))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn draw_h (state: &Arranger<Tui>, to: &mut Tui) -> Perhaps<[u16;4]> {
|
||||
let area = to.area();
|
||||
let area = [area.x(), area.y(), area.w(), area.h().min((2 + state.tracks.len() * 2) as u16)];
|
||||
let tracks = state.tracks.as_slice();
|
||||
Layers::new(|add|{
|
||||
add(&state.focused.then_some(FillBg(COLOR_BG0)))?;
|
||||
add(&Split::right(|add|{
|
||||
add(&TrackNameColumn(tracks, state.selected))?;
|
||||
add(&TrackMonitorColumn(tracks))?;
|
||||
add(&TrackRecordColumn(tracks))?;
|
||||
add(&TrackOverdubColumn(tracks))?;
|
||||
add(&TrackEraseColumn(tracks))?;
|
||||
add(&TrackGainColumn(tracks))?;
|
||||
add(&TrackScenesColumn(tracks, state.scenes.as_slice(), state.selected))?;
|
||||
Ok(())
|
||||
}))
|
||||
}).render(to.with_rect(area))
|
||||
}
|
||||
|
||||
struct TrackNameColumn<'a>(&'a [Sequencer<Tui>], ArrangerFocus);
|
||||
|
||||
impl<'a> Widget for TrackNameColumn<'a> {
|
||||
type Engine = Tui;
|
||||
fn layout (&self, area: [u16;4]) -> Perhaps<[u16;4]> {
|
||||
todo!()
|
||||
}
|
||||
fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> {
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
struct TrackMonitorColumn<'a>(&'a [Sequencer<Tui>]);
|
||||
|
||||
impl<'a> Widget for TrackMonitorColumn<'a> {
|
||||
type Engine = Tui;
|
||||
fn layout (&self, area: [u16;4]) -> Perhaps<[u16;4]> {
|
||||
todo!()
|
||||
}
|
||||
fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> {
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
struct TrackRecordColumn<'a>(&'a [Sequencer<Tui>]);
|
||||
|
||||
impl<'a> Widget for TrackRecordColumn<'a> {
|
||||
type Engine = Tui;
|
||||
fn layout (&self, area: [u16;4]) -> Perhaps<[u16;4]> {
|
||||
todo!()
|
||||
}
|
||||
fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> {
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
struct TrackOverdubColumn<'a>(&'a [Sequencer<Tui>]);
|
||||
|
||||
impl<'a> Widget for TrackOverdubColumn<'a> {
|
||||
type Engine = Tui;
|
||||
fn layout (&self, area: [u16;4]) -> Perhaps<[u16;4]> {
|
||||
todo!()
|
||||
}
|
||||
fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> {
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
struct TrackEraseColumn<'a>(&'a [Sequencer<Tui>]);
|
||||
|
||||
impl<'a> Widget for TrackEraseColumn<'a> {
|
||||
type Engine = Tui;
|
||||
fn layout (&self, area: [u16;4]) -> Perhaps<[u16;4]> {
|
||||
todo!()
|
||||
}
|
||||
fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> {
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
struct TrackGainColumn<'a>(&'a [Sequencer<Tui>]);
|
||||
|
||||
impl<'a> Widget for TrackGainColumn<'a> {
|
||||
type Engine = Tui;
|
||||
fn layout (&self, area: [u16;4]) -> Perhaps<[u16;4]> {
|
||||
todo!()
|
||||
}
|
||||
fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> {
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
struct TrackScenesColumn<'a>(&'a [Sequencer<Tui>], &'a [Scene], ArrangerFocus);
|
||||
|
||||
impl<'a> Widget for TrackScenesColumn<'a> {
|
||||
type Engine = Tui;
|
||||
fn layout (&self, area: [u16;4]) -> Perhaps<[u16;4]> {
|
||||
todo!()
|
||||
}
|
||||
fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> {
|
||||
let Self(tracks, scenes, selected) = self;
|
||||
let area = to.area();
|
||||
let mut x2 = 0;
|
||||
let [x, y, _, height] = area;
|
||||
for (scene_index, scene) in scenes.iter().enumerate() {
|
||||
let active_scene = 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 = selected.track() == Some(i);
|
||||
if let Some(clip) = clip {
|
||||
let y2 = y + 2 + i as u16 * 2;
|
||||
let label = match tracks[i].phrases.get(*clip) {
|
||||
Some(phrase) => &format!("{}", phrase.read().unwrap().name.read().unwrap()),
|
||||
None => "...."
|
||||
};
|
||||
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;
|
||||
}
|
||||
Ok(Some([x, y, x2, height]))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue