From 5670fc179bfdf4f9b955f79c817c8316f3e0ed36 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Thu, 12 Sep 2024 19:24:21 +0300 Subject: [PATCH] refactoring arranger into components --- crates/tek_core/src/engine.rs | 14 + crates/tek_core/src/tui.rs | 9 +- crates/tek_sequencer/src/arranger.rs | 518 ++++++++++++--------------- crates/tek_test/src/main.rs | 9 +- 4 files changed, 262 insertions(+), 288 deletions(-) diff --git a/crates/tek_core/src/engine.rs b/crates/tek_core/src/engine.rs index 9185ed9e..e28c75a7 100644 --- a/crates/tek_core/src/engine.rs +++ b/crates/tek_core/src/engine.rs @@ -316,6 +316,20 @@ impl< } } +//pub fn collect <'a, E: Engine, const N: usize> ( + //items: &'a [&'a dyn Widget;N] +//) -> impl Send + Sync + Fn(&'a mut dyn FnMut(&'a dyn Widget)->Usually<()>)->Usually<()> + '_ { + //|add: &'a mut dyn FnMut(&'a dyn Widget)->Usually<()>| { + //for item in items.iter() { + //add(item)?; + //} + //Ok(()) + //} +//} + +//`Layers<_, impl (Fn(&mut dyn FnMut(&dyn Widget) -> Result<(), Box<...>>) -> ... ) + Send + Sync>` +//`Layers) -> Result<(), Box<(dyn std::error::Error + 'static)>>) -> Result<(), Box<(dyn std::error::Error + 'static)>>) + Send + Sync + '_>` + #[derive(Copy, Clone)] pub enum Direction { Up, diff --git a/crates/tek_core/src/tui.rs b/crates/tek_core/src/tui.rs index 8b34be30..bb3c3d95 100644 --- a/crates/tek_core/src/tui.rs +++ b/crates/tek_core/src/tui.rs @@ -582,10 +582,11 @@ impl> Widget for Align { }) } fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> { - self.layout(to.area())? - .map(|area|to.render_in(area, self.inner())) - .transpose() - .map(|x|x.flatten()) + if let Some(area) = self.layout(to.area())? { + to.render_in(area, self.inner()) + } else { + Ok(None) + } } } diff --git a/crates/tek_sequencer/src/arranger.rs b/crates/tek_sequencer/src/arranger.rs index 27103ad0..fa420f7e 100644 --- a/crates/tek_sequencer/src/arranger.rs +++ b/crates/tek_sequencer/src/arranger.rs @@ -79,62 +79,44 @@ impl Arranger { _ => false } } -} -/// Display mode of arranger -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, - } + pub fn track (&self) -> Option<&Sequencer> { + self.selected.track().map(|t|self.tracks.get(t)).flatten() + } + pub fn track_mut (&mut self) -> Option<&mut Sequencer> { + 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> { + 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 Widget for Arranger { - 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|match self.mode { - 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::>(); - 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::>(); - 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, - w.saturating_sub(2), - h.saturating_sub(2), - ]))?.unwrap(); - 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, - ])) +impl Arranger { + 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 Focusable for Arranger { @@ -173,10 +155,6 @@ impl ArrangerFocus { pub fn is_clip (&self) -> bool { match self { Self::Clip(_, _) => true, _ => false } } -} - -/// Track focus methods -impl ArrangerFocus { pub fn track (&self) -> Option { match self { Self::Clip(t, _) => Some(*t), @@ -208,10 +186,6 @@ impl ArrangerFocus { } } } -} - -/// Scene focus methods -impl ArrangerFocus { pub fn scene (&self) -> Option { match self { Self::Clip(_, s) => Some(*s), @@ -244,8 +218,6 @@ impl ArrangerFocus { } } } - - impl Handle for Arranger { fn handle (&mut self, from: &Tui) -> Perhaps { if let Some(modal) = self.modal.as_mut() { @@ -341,212 +313,78 @@ impl Handle for Arranger { } } } -impl Arranger { - 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 { - _engine: std::marker::PhantomData, - done: bool, - target: ArrangerFocus, - value: String, - result: Arc>, - cursor: usize -} -impl ArrangerRenameModal { - pub fn new (target: ArrangerFocus, value: &Arc>) -> Self { - Self { - _engine: Default::default(), - done: false, - target, - value: value.read().unwrap().clone(), - cursor: value.read().unwrap().len(), - result: value.clone(), +/// 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, } } } -impl Widget for ArrangerRenameModal { +impl Content for Arranger { 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 for ArrangerRenameModal { - fn handle (&mut self, from: &Tui) -> Perhaps { - 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 Exit for ArrangerRenameModal { - fn exited (&self) -> bool { - self.done - } - fn exit (&mut self) { - self.done = true + fn content (&self) -> impl Widget { + 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::>().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::>().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(), + )), + } + }) } } -/// Track management methods -impl Arranger { - pub fn track (&self) -> Option<&Sequencer> { - self.selected.track().map(|t|self.tracks.get(t)).flatten() - } - pub fn track_mut (&mut self) -> Option<&mut Sequencer> { - 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> { - 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) - } -} +struct ArrangerViewVertical<'a, 'b, E: Engine>( + &'a Arranger, &'b [(usize, usize)], &'b [(usize, usize)] +); -pub fn track_name_max_len (tracks: &[Sequencer]) -> usize { - tracks.iter() - .map(|s|s.name.read().unwrap().len()) - .fold(0, usize::max) -} - -pub fn track_clip_name_lengths (tracks: &[Sequencer]) -> 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, 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::>(); - 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, 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::>(); - 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, 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, - 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)) +impl<'a, 'b> Content for ArrangerViewVertical<'a, 'b, Tui> { + type Engine = Tui; + fn content (&self) -> impl Widget { + let Self(state, cols, rows) = self; + //let area = to.area(); + //let area = [area.x(), area.y(), area.w(), 2 + (rows[rows.len() - 1].1 / 96) as u16]; + let tracks = state.tracks.as_ref(); + let scenes = state.scenes.as_ref(); + let offset = 3 + scene_name_max_len(scenes) as u16; + Layers::new(move |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)) + })) + }) + } } struct ColumnSeparators<'a>(u16, &'a [(usize, usize)]); @@ -760,23 +598,31 @@ impl<'a> Widget for SceneClip<'a> { } } -pub fn draw_h (state: &Arranger, 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 ArrangerViewHorizontal<'a, E: Engine>( + &'a Arranger +); + +impl<'a> Content for ArrangerViewHorizontal<'a, Tui> { + type Engine = Tui; + fn content (&self) -> impl Widget { + //let area = to.area(); + //let area = [area.x(), area.y(), area.w(), area.h().min((2 + state.tracks.len() * 2) as u16)]; + let Arranger { tracks, focused, selected, scenes, .. } = self.0; + let tracks = tracks.as_slice(); + Layers::new(|add|{ + add(&focused.then_some(FillBg(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], ArrangerFocus); @@ -1021,3 +867,117 @@ impl<'a> Widget for TrackScenesColumn<'a> { Ok(Some([x, y, x2, height])) } } +/// Appears on first run (i.e. if state dir is missing). +pub struct ArrangerRenameModal { + _engine: std::marker::PhantomData, + done: bool, + target: ArrangerFocus, + value: String, + result: Arc>, + cursor: usize +} +impl ArrangerRenameModal { + pub fn new (target: ArrangerFocus, value: &Arc>) -> 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 { + 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 for ArrangerRenameModal { + fn handle (&mut self, from: &Tui) -> Perhaps { + 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 Exit for ArrangerRenameModal { + fn exited (&self) -> bool { + self.done + } + fn exit (&mut self) { + self.done = true + } +} + +pub fn track_name_max_len (tracks: &[Sequencer]) -> usize { + tracks.iter() + .map(|s|s.name.read().unwrap().len()) + .fold(0, usize::max) +} + +pub fn track_clip_name_lengths (tracks: &[Sequencer]) -> 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 +} diff --git a/crates/tek_test/src/main.rs b/crates/tek_test/src/main.rs index dc50b987..a4005463 100644 --- a/crates/tek_test/src/main.rs +++ b/crates/tek_test/src/main.rs @@ -41,12 +41,12 @@ impl Content for Demo { add(&Split::down(|add|{ add(&Layers::new(|add|{ add(&FillBg(Color::Rgb(0,128,0)))?; - add(&Align::Center("55555"))?; + add(&Align::Center("12345"))?; add(&Align::Center("FOO")) }))?; add(&Layers::new(|add|{ add(&FillBg(Color::Rgb(0,0,128)))?; - add(&Align::Center("7777777"))?; + add(&Align::Center("1234567"))?; add(&Align::Center("BAR")) })) })) @@ -59,7 +59,6 @@ impl Handle for Demo { match from.event() { key!(KeyCode::PageUp) => { self.index = (self.index + 1) % self.items.len(); - Ok(Some(true)) }, key!(KeyCode::PageDown) => { self.index = if self.index > 1 { @@ -67,10 +66,10 @@ impl Handle for Demo { } else { self.items.len() - 1 }; - Ok(Some(true)) }, - _ => Ok(None) + _ => return Ok(None) } + Ok(Some(true)) } }