put all of arranger/sequencer/transport in 1 file

This commit is contained in:
🪞👃🪞 2024-09-19 00:05:31 +03:00
parent 3042e9e3a8
commit 6f988e5072
12 changed files with 1877 additions and 1847 deletions

View file

@ -1,921 +0,0 @@
//! Clip launcher and arrangement editor.
use crate::*;
/// Represents the tracks and scenes of the composition.
pub struct Arranger<E: Engine> {
/// Name of arranger
pub name: Arc<RwLock<String>>,
/// Collection of tracks.
pub tracks: Vec<Sequencer<E>>,
/// Collection of scenes.
pub scenes: Vec<Scene>,
/// Currently selected element.
pub selected: ArrangerFocus,
/// Display mode of arranger
pub mode: ArrangerViewMode,
/// Slot for modal dialog displayed on top of app.
pub modal: Option<Box<dyn ExitableComponent<E>>>,
/// Whether the arranger is currently focused
pub focused: bool
}
impl<E: Engine> Arranger<E> {
pub fn new (name: &str) -> Self {
Self {
name: Arc::new(RwLock::new(name.into())),
mode: ArrangerViewMode::VerticalCompact2,
selected: ArrangerFocus::Clip(0, 0),
scenes: vec![],
tracks: vec![],
modal: None,
focused: false
}
}
pub fn activate (&mut self) {
match self.selected {
ArrangerFocus::Scene(s) => {
for (track_index, track) in self.tracks.iter_mut().enumerate() {
track.sequence = self.scenes[s].clips[track_index];
track.reset = true;
}
},
ArrangerFocus::Clip(t, s) => {
self.tracks[t].sequence = self.scenes[s].clips[t];
self.tracks[t].reset = true;
},
_ => {}
}
}
pub fn sequencer (&self) -> Option<&Sequencer<E>> {
self.selected.track()
.map(|track|self.tracks.get(track))
.flatten()
}
pub fn sequencer_mut (&mut self) -> Option<&mut Sequencer<E>> {
self.selected.track()
.map(|track|self.tracks.get_mut(track))
.flatten()
}
pub fn show_phrase (&mut self) -> Usually<()> {
//unimplemented!()
//let phrase = self.phrase();
//self.sequencer.show(phrase)
Ok(())
}
pub fn is_first_row (&self) -> bool {
let selected = self.selected;
selected.is_mix() || selected.is_track() || match selected {
ArrangerFocus::Clip(_, s) =>
s == 0,
_ => false
}
}
pub fn is_last_row (&self) -> bool {
let selected = self.selected;
match selected {
ArrangerFocus::Scene(s) =>
s == self.scenes.len() - 1,
ArrangerFocus::Clip(_, s) =>
s == self.scenes.len() - 1,
_ => false
}
}
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)
}
}
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(),
}
)));
}
}
impl Handle<Tui> for Arranger<Tui> {
fn handle (&mut self, from: &TuiInput) -> 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()
},
// cursor_up: move cursor up
key!(KeyCode::Up) => {
match self.mode {
ArrangerViewMode::Horizontal => self.track_prev(),
_ => self.scene_prev(),
};
self.show_phrase()?;
},
// cursor_down
key!(KeyCode::Down) => {
match self.mode {
ArrangerViewMode::Horizontal => self.track_next(),
_ => self.scene_next(),
};
self.show_phrase()?;
},
// cursor left
key!(KeyCode::Left) => {
match self.mode {
ArrangerViewMode::Horizontal => self.scene_prev(),
_ => self.track_prev(),
};
self.show_phrase()?;
},
// cursor right
key!(KeyCode::Right) => {
match self.mode {
ArrangerViewMode::Horizontal => self.scene_next(),
_ => self.track_next(),
};
self.show_phrase()?;
},
// increment: use next clip here
key!(KeyCode::Char('.')) => {
self.phrase_next();
},
// decrement: use previous next clip here
key!(KeyCode::Char(',')) => {
self.phrase_prev();
},
// decrement: use previous clip here
key!(KeyCode::Enter) => {
self.activate();
},
// scene_add: add a new scene
key!(Ctrl-KeyCode::Char('a')) => {
self.scene_add(None)?;
},
// track_add: add a new scene
key!(Ctrl-KeyCode::Char('t')) => {
self.track_add(None)?;
},
// rename: add a new scene
key!(KeyCode::Char('n')) => {
self.rename_selected();
},
// length: add a new scene
key!(KeyCode::Char('l')) => {
todo!();
},
// color: set color of item at cursor
key!(KeyCode::Char('c')) => {
todo!();
},
_ => return Ok(None)
}
Ok(Some(true))
}
}
impl Content for Arranger<Tui> {
type Engine = Tui;
fn content (&self) -> impl Widget<Engine = Tui> {
Layers::new(move |add|{
//Lozenge(Style::default().fg(Nord::BG2))
//.draw(&mut to.alter_area(|[x, y, w, h]|[
//x.saturating_sub(1),
//y.saturating_sub(1),
//w + 2,
//h + 2,
//]))
match self.mode {
ArrangerViewMode::Horizontal => add(&ArrangerViewHorizontal(&self)),
ArrangerViewMode::VerticalCompact1 => add(&ArrangerViewVertical(
&self,
track_clip_name_lengths(self.tracks.as_slice()).as_slice(),
(0..=self.scenes.len()).map(|i|(96, 96*i)).collect::<Vec<_>>().as_slice(),
)),
ArrangerViewMode::VerticalCompact2 => add(&ArrangerViewVertical(
&self,
track_clip_name_lengths(self.tracks.as_slice()).as_slice(),
(0..=self.scenes.len()).map(|i|(192, 192*i)).collect::<Vec<_>>().as_slice()
)),
ArrangerViewMode::VerticalExpanded => add(&ArrangerViewVertical(
&self,
track_clip_name_lengths(self.tracks.as_slice()).as_slice(),
scene_ppqs(self.tracks.as_slice(), self.scenes.as_slice()).as_slice(),
)),
}
})
}
}
impl Focusable<Tui> for Arranger<Tui> {
fn is_focused (&self) -> bool {
self.focused
}
fn set_focused (&mut self, focused: bool) {
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 }
}
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)
}
}
}
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))
}
}
}
}
/// Display mode of arranger
#[derive(PartialEq)]
pub enum ArrangerViewMode { VerticalExpanded, VerticalCompact1, VerticalCompact2, Horizontal }
/// Arranger display mode can be cycled
impl ArrangerViewMode {
/// Cycle arranger display mode
pub fn to_next (&mut self) {
*self = match self {
Self::VerticalExpanded => Self::VerticalCompact1,
Self::VerticalCompact1 => Self::VerticalCompact2,
Self::VerticalCompact2 => Self::Horizontal,
Self::Horizontal => Self::VerticalExpanded,
}
}
}
struct ArrangerViewVertical<'a, 'b, E: Engine>(
&'a Arranger<E>, &'b [(usize, usize)], &'b [(usize, usize)]
);
impl<'a, 'b> Content for ArrangerViewVertical<'a, 'b, Tui> {
type Engine = Tui;
fn content (&self) -> impl Widget<Engine = Tui> {
let Self(state, cols, rows) = self;
let tracks: &[Sequencer<Tui>] = state.tracks.as_ref();
let scenes = state.scenes.as_ref();
let offset = 4 + scene_name_max_len(scenes) as u16;
Layers::new(move |add|{
add(&ColumnSeparators(offset, cols))?;
add(&RowSeparators(rows))?;
add(&CursorFocus(state.focused, state.selected, offset, cols, rows))?;
add(&Split::down(|add|{
add(&Plus::X(offset, Split::right(move |add|{
for (track, (w, _)) in tracks.iter().zip(*cols) {
add(&Min::XY(*w as u16, 2, Layers::new(|add|{
add(&Background(COLOR_BG1))?;
add(&track.name.read().unwrap().as_str())
})))?;
}
Ok(())
})))?;
add(&Split::down(move |add| {
for (scene, (pulses, _)) in scenes.iter().zip(*rows) {
let height = 1.max((pulses / 96) as u16);
let playing = scene.is_playing(tracks);
add(&Fixed::Y(height, Split::right(move |add| {
add(&Fixed::XY(offset.saturating_sub(1), height, Split::right(|add|{
add(&if playing { "" } else { " " })?;
add(&scene.name.read().unwrap().as_str())
})))?;
for (track, (w, _x)) in cols.iter().enumerate() {
add(&Fixed::XY(*w as u16, height, Layers::new(move |add|{
let mut color = COLOR_BG0;
if let (Some(track), Some(Some(clip))) = (
tracks.get(track),
scene.clips.get(track),
) {
if let Some(phrase) = track.phrases.get(*clip) {
add(&Plus::X(1, format!(
"{clip:02} {}",
phrase.read().unwrap().name.read().unwrap()
).as_str()))?;
color = if track.sequence == Some(*clip) {
Nord::PLAYING
} else {
COLOR_BG1
};
}
}
add(&Background(color))
})))?;
}
Ok(())
})))?;
}
Ok(())
}))
}))
})
}
}
struct ColumnSeparators<'a>(u16, &'a [(usize, usize)]);
impl<'a> Widget for ColumnSeparators<'a> {
type Engine = Tui;
fn render (&self, to: &mut TuiOutput) -> Usually<()> {
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(())
}
}
struct RowSeparators<'a>(&'a [(usize, usize)]);
impl<'a> Widget for RowSeparators<'a> {
type Engine = Tui;
fn render (&self, to: &mut TuiOutput) -> Usually<()> {
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) {
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 = Nord::SEPARATOR;
}
}
}
Ok(())
}
}
struct CursorFocus<'a>(
bool, ArrangerFocus, u16, &'a [(usize, usize)], &'a [(usize, usize)]
);
impl<'a> Widget for CursorFocus<'a> {
type Engine = Tui;
fn render (&self, to: &mut TuiOutput) -> Usually<()> {
let area = to.area();
let Self(focused, 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 => {
if focused {
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 focused {
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))
Ok(())
}
}
struct ArrangerViewHorizontal<'a, E: Engine>(
&'a Arranger<E>
);
impl<'a> Content for ArrangerViewHorizontal<'a, Tui> {
type Engine = Tui;
fn content (&self) -> impl Widget<Engine = Tui> {
let Arranger { tracks, focused, selected, scenes, .. } = self.0;
let tracks = tracks.as_slice();
Layers::new(|add|{
add(&focused.then_some(Background(COLOR_BG0)))?;
add(&Split::right(|add|{
add(&TrackNameColumn(tracks, *selected))?;
add(&TrackMonitorColumn(tracks))?;
add(&TrackRecordColumn(tracks))?;
add(&TrackOverdubColumn(tracks))?;
add(&TrackEraseColumn(tracks))?;
add(&TrackGainColumn(tracks))?;
add(&TrackScenesColumn(tracks, scenes.as_slice(), *selected))?;
Ok(())
}))
})
}
}
struct TrackNameColumn<'a>(&'a [Sequencer<Tui>], ArrangerFocus);
impl<'a> Widget for TrackNameColumn<'a> {
type Engine = Tui;
fn layout (&self, area: [u16;2]) -> Perhaps<[u16;2]> {
todo!()
}
fn render (&self, to: &mut TuiOutput) -> Usually<()> {
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;2]) -> Perhaps<[u16;2]> {
todo!()
}
fn render (&self, to: &mut TuiOutput) -> Usually<()> {
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;2]) -> Perhaps<[u16;2]> {
todo!()
}
fn render (&self, to: &mut TuiOutput) -> Usually<()> {
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;2]) -> Perhaps<[u16;2]> {
todo!()
}
fn render (&self, to: &mut TuiOutput) -> Usually<()> {
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;2]) -> Perhaps<[u16;2]> {
todo!()
}
fn render (&self, to: &mut TuiOutput) -> Usually<()> {
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;2]) -> Perhaps<[u16;2]> {
todo!()
}
fn render (&self, to: &mut TuiOutput) -> Usually<()> {
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;2]) -> Perhaps<[u16;2]> {
todo!()
}
fn render (&self, to: &mut TuiOutput) -> Usually<()> {
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]))
Ok(())
}
}
/// 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;2]) -> Perhaps<[u16;2]> {
todo!()
}
fn render (&self, to: &mut TuiOutput) -> Usually<()> {
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))
Ok(())
}
}
impl Handle<Tui> for ArrangerRenameModal<Tui> {
fn handle (&mut self, from: &TuiInput) -> 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
}
}
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
}