From 5823d0b746acbcc59dd15156439da647c19c234f Mon Sep 17 00:00:00 2001 From: unspeaker Date: Mon, 11 Nov 2024 00:39:45 +0100 Subject: [PATCH] wip: refactor pt.15: 107 errors --- crates/tek_api/src/arrange.rs | 4 +- crates/tek_api/src/arrange_cmd.rs | 6 +- crates/tek_tui/src/tui_arrangement.rs | 4 +- crates/tek_tui/src/tui_arrangement_cmd.rs | 8 +- crates/tek_tui/src/tui_arranger.rs | 32 +- crates/tek_tui/src/tui_arranger_cmd.rs | 78 ++--- crates/tek_tui/src/tui_arranger_hor.rs | 369 +++++++++++---------- crates/tek_tui/src/tui_arranger_ver.rs | 377 +++++++++++----------- 8 files changed, 423 insertions(+), 455 deletions(-) diff --git a/crates/tek_api/src/arrange.rs b/crates/tek_api/src/arrange.rs index fec23742..98eca32c 100644 --- a/crates/tek_api/src/arrange.rs +++ b/crates/tek_api/src/arrange.rs @@ -113,12 +113,12 @@ impl ArrangementScene { /// Returns true if all phrases in the scene are /// currently playing on the given collection of tracks. - pub fn is_playing (&self, tracks: &[MIDIPlayer]) -> bool { + pub fn is_playing (&self, tracks: &[ArrangementTrack]) -> bool { self.clips.iter().any(|clip|clip.is_some()) && self.clips.iter().enumerate() .all(|(track_index, clip)|match clip { Some(clip) => tracks .get(track_index) - .map(|track|if let Some((_, Some(phrase))) = &track.phrase { + .map(|track|if let Some((_, Some(phrase))) = &track.player.phrase { *phrase.read().unwrap() == *clip.read().unwrap() } else { false diff --git a/crates/tek_api/src/arrange_cmd.rs b/crates/tek_api/src/arrange_cmd.rs index 189d18e9..6380838a 100644 --- a/crates/tek_api/src/arrange_cmd.rs +++ b/crates/tek_api/src/arrange_cmd.rs @@ -14,9 +14,9 @@ pub enum ArrangementCommand { #[derive(Clone)] pub enum ArrangementSceneCommand { Add, - Delete, + Delete(usize), RandomColor, - Play, + Play(usize), Swap(usize), SetSize(usize), SetZoom(usize), @@ -25,7 +25,7 @@ pub enum ArrangementSceneCommand { #[derive(Clone)] pub enum ArrangementTrackCommand { Add, - Delete, + Delete(usize), RandomColor, Stop, Swap(usize), diff --git a/crates/tek_tui/src/tui_arrangement.rs b/crates/tek_tui/src/tui_arrangement.rs index 918bb75a..5e81843c 100644 --- a/crates/tek_tui/src/tui_arrangement.rs +++ b/crates/tek_tui/src/tui_arrangement.rs @@ -45,9 +45,9 @@ impl Content for ArrangementEditor { Layers::new(move |add|{ match self.mode { ArrangementEditorMode::Horizontal => - add(&HorizontalArranger(&self))?, + add(&arranger_content_horizontal(self))?, ArrangementEditorMode::Vertical(factor) => - add(&VerticalArranger(&self, factor))? + add(&arranger_content_vertical(self, factor))? }; add(&self.size) }) diff --git a/crates/tek_tui/src/tui_arrangement_cmd.rs b/crates/tek_tui/src/tui_arrangement_cmd.rs index b0be3d83..b6acd34c 100644 --- a/crates/tek_tui/src/tui_arrangement_cmd.rs +++ b/crates/tek_tui/src/tui_arrangement_cmd.rs @@ -94,7 +94,7 @@ impl InputToCommand> for ArrangementEditorCommand { key!(KeyCode::Enter) => match state.selected { ArrangementEditorFocus::Mix => return None, ArrangementEditorFocus::Track(t) => return None, - ArrangementEditorFocus::Scene(s) => return None, + ArrangementEditorFocus::Scene(s) => Cmd::Edit(Edit::Scene(Scene::Play(s))), ArrangementEditorFocus::Clip(t, s) => return None, }, @@ -129,16 +129,16 @@ impl InputToCommand> for ArrangementEditorCommand { } impl Command> for ArrangementEditorCommand { - fn execute (self, state: &mut ArrangementEditor) -> Perhaps { + fn execute (self, view: &mut ArrangementEditor) -> Perhaps { match self { Self::Zoom(zoom) => { todo!(); }, Self::Select(selected) => { - state.selected = selected; + view.selected = selected; }, Self::Edit(command) => { - return command.execute(state.state).map(Self::Edit) + return Ok(command.execute(&mut view.model)?.map(Self::Edit)) }, } Ok(None) diff --git a/crates/tek_tui/src/tui_arranger.rs b/crates/tek_tui/src/tui_arranger.rs index de26ca87..545fbcb3 100644 --- a/crates/tek_tui/src/tui_arranger.rs +++ b/crates/tek_tui/src/tui_arranger.rs @@ -88,12 +88,8 @@ impl ArrangerView { } /// Toggle global play/pause - pub fn toggle_play (&mut self) -> Perhaps { - match self.transport { - Some(ref mut transport) => { transport.write().unwrap().toggle_play()?; }, - None => { return Ok(None) } - } - Ok(Some(true)) + pub fn toggle_play (&mut self) -> Usually<()> { + self.sequencer.transport.model.toggle_play() } pub fn next_color (&self) -> ItemColor { if let ArrangementEditorFocus::Clip(track, scene) = self.arrangement.selected { @@ -225,30 +221,6 @@ impl ArrangerView { phrase.write().unwrap().toggle_loop() } } - pub fn go_up (&mut self) { - match self.mode { - ArrangementEditorMode::Horizontal => self.track_prev(), - _ => self.scene_prev(), - }; - } - pub fn go_down (&mut self) { - match self.mode { - ArrangementEditorMode::Horizontal => self.track_next(), - _ => self.scene_next(), - }; - } - pub fn go_left (&mut self) { - match self.mode { - ArrangementEditorMode::Horizontal => self.scene_prev(), - _ => self.track_prev(), - }; - } - pub fn go_right (&mut self) { - match self.mode { - ArrangementEditorMode::Horizontal => self.scene_next(), - _ => self.track_next(), - }; - } pub fn move_back (&mut self) { match self.selected { ArrangementEditorFocus::Scene(s) => { diff --git a/crates/tek_tui/src/tui_arranger_cmd.rs b/crates/tek_tui/src/tui_arranger_cmd.rs index d0b8464f..236adaf8 100644 --- a/crates/tek_tui/src/tui_arranger_cmd.rs +++ b/crates/tek_tui/src/tui_arranger_cmd.rs @@ -4,8 +4,8 @@ use crate::*; pub enum ArrangerViewCommand { Focus(FocusCommand), Arrangement(ArrangementEditorCommand), - Transport(TransportCommand), - Phrases(PhrasePoolCommand), + Transport(TransportViewCommand), + Phrases(PhrasePoolViewCommand), Editor(PhraseEditorCommand), EditPhrase(Option>>), } @@ -18,44 +18,48 @@ impl Handle for ArrangerView { } impl InputToCommand> for ArrangerViewCommand { - fn input_to_command (state: &ArrangerView, input: &TuiInput) -> Option { + fn input_to_command (view: &ArrangerView, input: &TuiInput) -> Option { use FocusCommand::*; use ArrangerViewCommand::*; - match input.event() { - key!(KeyCode::Tab) => Some(Focus(Next)), - key!(Shift-KeyCode::Tab) => Some(Focus(Prev)), - key!(KeyCode::BackTab) => Some(Focus(Prev)), - key!(Shift-KeyCode::BackTab) => Some(Focus(Prev)), - key!(KeyCode::Up) => Some(Focus(Up)), - key!(KeyCode::Down) => Some(Focus(Down)), - key!(KeyCode::Left) => Some(Focus(Left)), - key!(KeyCode::Right) => Some(Focus(Right)), - key!(KeyCode::Enter) => Some(Focus(Enter)), - key!(KeyCode::Esc) => Some(Focus(Exit)), - //key!(KeyCode::Char(' ')) => Some(Transport(TransportCommand::PlayToggle)), - _ => match state.focused() { - ArrangerViewFocus::Transport => state.transport.as_ref() - .map(|t|TransportCommand::input_to_command(&*t.read().unwrap(), input) - .map(Transport)) - .flatten(), - ArrangerViewFocus::PhrasePool => { - let phrases = state.phrases.read().unwrap(); - match input.event() { - key!(KeyCode::Char('e')) => Some(EditPhrase(Some(phrases.phrase().clone()))), - _ => PhrasePoolCommand::input_to_command(&*phrases, input) - .map(Phrases) - } + Some(match input.event() { + key!(KeyCode::Tab) => Focus(Next), + key!(Shift-KeyCode::Tab) => Focus(Prev), + key!(KeyCode::BackTab) => Focus(Prev), + key!(Shift-KeyCode::BackTab) => Focus(Prev), + key!(KeyCode::Up) => Focus(Up), + key!(KeyCode::Down) => Focus(Down), + key!(KeyCode::Left) => Focus(Left), + key!(KeyCode::Right) => Focus(Right), + key!(KeyCode::Enter) => Focus(Enter), + key!(KeyCode::Esc) => Focus(Exit), + key!(KeyCode::Char(' ')) => { + Transport(TransportViewCommand::Transport(TransportCommand::Play(None))) + }, + _ => match view.focused() { + ArrangerViewFocus::Transport => Transport( + TransportViewCommand::input_to_command(&view.sequencer.transport, input)? + ), + ArrangerViewFocus::PhraseEditor => Editor( + PhraseEditorCommand::input_to_command(&view.sequencer.editor, input)? + ), + ArrangerViewFocus::PhrasePool => match input.event() { + key!(KeyCode::Char('e')) => EditPhrase( + Some(view.sequencer.phrases.phrase().clone()) + ), + _ => Phrases( + PhrasePoolViewCommand::input_to_command(&view.sequencer.phrases, input)? + ) }, - ArrangerViewFocus::PhraseEditor => - PhraseEditorCommand::input_to_command(&state.editor, input) - .map(Editor), ArrangerViewFocus::Arrangement => match input.event() { - key!(KeyCode::Char('e')) => Some(EditPhrase(state.arrangement.phrase())), - _ => ArrangementCommand::input_to_command(&state.arrangement, &input) - .map(Arrangement) + key!(KeyCode::Char('e')) => EditPhrase( + view.arrangement.phrase() + ), + _ => Arrangement( + ArrangementEditorCommand::input_to_command(&view.arrangement, &input)? + ) } } - } + }) } } @@ -69,7 +73,7 @@ impl Command> for ArrangerViewCommand { Self::Editor(cmd) => delegate(cmd, Self::Editor, &mut view.sequencer.editor), Self::Transport(cmd) => - delegate(cmd, Self::Transport, &mut view.sequencer.transport.state), + delegate(cmd, Self::Transport, &mut view.sequencer.transport), Self::Arrangement(cmd) => delegate(cmd, Self::Arrangement, &mut view.arrangement), Self::EditPhrase(phrase) => { @@ -79,8 +83,8 @@ impl Command> for ArrangerViewCommand { Ok(None) } }?; - view.sequencer.show_phrase(); - view.sequencer.update_status(); + view.show_phrase(); + view.update_status(); return Ok(undo); } } diff --git a/crates/tek_tui/src/tui_arranger_hor.rs b/crates/tek_tui/src/tui_arranger_hor.rs index c528073f..f37327f9 100644 --- a/crates/tek_tui/src/tui_arranger_hor.rs +++ b/crates/tek_tui/src/tui_arranger_hor.rs @@ -1,200 +1,197 @@ use crate::*; -/// Arrangement, rendered horizontally (arrangement/track mode). -pub struct HorizontalArranger<'a, E: Engine>(pub &'a Arrangement); - -impl<'a> Content for HorizontalArranger<'a, Tui> { - type Engine = Tui; - fn content (&self) -> impl Widget { - let Arrangement { tracks, focused, .. } = self.0; - let _tracks = tracks.as_slice(); - lay!( - focused.then_some(Background(Arranger::::border_bg())), - row!( - // name - CustomWidget::new(|_|{todo!()}, |_: &mut TuiOutput|{ - 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)?; - //} +pub fn arranger_content_horizontal ( + state: &ArrangementEditor, +) -> impl Widget + use<'_> { + let focused = state.focused; + let _tracks = state.model.tracks.as_slice(); + lay!( + focused.then_some(Background(TuiTheme::border_bg())), + row!( + // name + CustomWidget::new(|_|{todo!()}, |_: &mut TuiOutput|{ + 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)) - }), - // monitor - CustomWidget::new(|_|{todo!()}, |_: &mut TuiOutput|{ - 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)?; + //} + //Ok(Some(area)) + }), + // monitor + CustomWidget::new(|_|{todo!()}, |_: &mut TuiOutput|{ + 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)) + }), + // record + CustomWidget::new(|_|{todo!()}, |_: &mut TuiOutput|{ + 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)) + }), + // overdub + CustomWidget::new(|_|{todo!()}, |_: &mut TuiOutput|{ + 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 { - //area.height = y; - //break - //} + //off + //})?; + //} else { + //area.height = y; + //break //} //} - //area.width = 4; - //Ok(Some(area)) - }), - // record - CustomWidget::new(|_|{todo!()}, |_: &mut TuiOutput|{ - 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)) + }), + // erase + CustomWidget::new(|_|{todo!()}, |_: &mut TuiOutput|{ + 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)) - }), - // overdub - CustomWidget::new(|_|{todo!()}, |_: &mut TuiOutput|{ - 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)) + }), + // gain + CustomWidget::new(|_|{todo!()}, |_: &mut TuiOutput|{ + 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 = 4; - //Ok(Some(area)) - }), - // erase - CustomWidget::new(|_|{todo!()}, |_: &mut TuiOutput|{ - 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)) - }), - // gain - CustomWidget::new(|_|{todo!()}, |_: &mut TuiOutput|{ - 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)) - }), - // scenes - CustomWidget::new(|_|{todo!()}, |to: &mut TuiOutput|{ - let Arrangement { scenes, selected, .. } = self.0; - let area = to.area(); - let mut x2 = 0; - let [x, y, _, height] = area; - Ok(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); + //} + //area.width = 7; + //Ok(Some(area)) + }), + // scenes + CustomWidget::new(|_|{todo!()}, |to: &mut TuiOutput|{ + let selected = &state.selected; + let scenes = &state.model.scenes; + let area = to.area(); + let mut x2 = 0; + let [x, y, _, height] = area; + Ok(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 = format!("{}", clip.read().unwrap().name); + 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) } - 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 = format!("{}", clip.read().unwrap().name); - 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; - }) - }), - ) + } + x2 = x2 + x3 + 1; + }) + }), ) - } + ) } diff --git a/crates/tek_tui/src/tui_arranger_ver.rs b/crates/tek_tui/src/tui_arranger_ver.rs index 455ea80c..ae0648b3 100644 --- a/crates/tek_tui/src/tui_arranger_ver.rs +++ b/crates/tek_tui/src/tui_arranger_ver.rs @@ -1,197 +1,192 @@ use crate::*; -/// Arrangement, rendered vertically (session/grid mode). -pub struct VerticalArranger<'a, E: Engine>(pub &'a Arrangement, pub usize); - -impl<'a> Content for VerticalArranger<'a, Tui> { - type Engine = Tui; - fn content (&self) -> impl Widget { - let Self(state, factor) = self; - let tracks = state.tracks.as_ref() as &[ArrangementTrack]; - let scenes = state.scenes.as_ref(); - let cols = state.track_widths(); - let rows = ArrangementScene::ppqs(scenes, *factor); - let bg = state.color; - let clip_bg = TuiTheme::border_bg(); - let sep_fg = TuiTheme::separator_fg(false); - let header_h = 3u16;//5u16; - let scenes_w = 3 + ArrangementScene::longest_name(scenes) as u16; // x of 1st track - let clock = &self.0.clock; - let arrangement = Layers::new(move |add|{ - let rows: &[(usize, usize)] = rows.as_ref(); - let cols: &[(usize, usize)] = cols.as_ref(); - let any_size = |_|Ok(Some([0,0])); - // column separators - add(&CustomWidget::new(any_size, move|to: &mut TuiOutput|{ - let style = Some(Style::default().fg(sep_fg)); - Ok(for x in cols.iter().map(|col|col.1) { - let x = scenes_w + to.area().x() + x as u16; - for y in to.area().y()..to.area().y2() { to.blit(&"▎", x, y, style); } - }) - }))?; - // row separators - add(&CustomWidget::new(any_size, move|to: &mut TuiOutput|{ - Ok(for y in 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 = sep_fg; - } +pub fn arranger_content_vertical ( + view: &ArrangerView, + factor: usize +) -> impl Widget + use<'_> { + let tracks = view.arrangement.model.tracks.as_ref() as &[ArrangementTrack]; + let scenes = view.arrangement.model.scenes.as_ref(); + let cols = view.arrangement.track_widths(); + let rows = ArrangementScene::ppqs(scenes, factor); + let bg = view.arrangement.color; + let clip_bg = TuiTheme::border_bg(); + let sep_fg = TuiTheme::separator_fg(false); + let header_h = 3u16;//5u16; + let scenes_w = 3 + ArrangementScene::longest_name(scenes) as u16; // x of 1st track + let clock = &view.sequencer.transport.model.clock; + let arrangement = Layers::new(move |add|{ + let rows: &[(usize, usize)] = rows.as_ref(); + let cols: &[(usize, usize)] = cols.as_ref(); + let any_size = |_|Ok(Some([0,0])); + // column separators + add(&CustomWidget::new(any_size, move|to: &mut TuiOutput|{ + let style = Some(Style::default().fg(sep_fg)); + Ok(for x in cols.iter().map(|col|col.1) { + let x = scenes_w + to.area().x() + x as u16; + for y in to.area().y()..to.area().y2() { to.blit(&"▎", x, y, style); } + }) + }))?; + // row separators + add(&CustomWidget::new(any_size, move|to: &mut TuiOutput|{ + Ok(for y in 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 = sep_fg; } - }) - }))?; - // 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 player = &track.player; - let max_w = w.saturating_sub(1).min(name.len()).max(2); - let name = format!("▎{}", &name[0..max_w]); - let name = TuiStyle::bold(name, true); - // beats elapsed - let elapsed = if let Some((_, Some(phrase))) = player.phrase.as_ref() { - let length = phrase.read().unwrap().length; - let elapsed = player.pulses_since_start().unwrap(); - let elapsed = clock.timebase().format_beats_1_short( - (elapsed as usize % length) as f64 - ); - format!("▎+{elapsed:>}") + } + }) + }))?; + // 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 player = &track.player; + let max_w = w.saturating_sub(1).min(name.len()).max(2); + let name = format!("▎{}", &name[0..max_w]); + let name = TuiStyle::bold(name, true); + // beats elapsed + let elapsed = if let Some((_, Some(phrase))) = player.phrase.as_ref() { + let length = phrase.read().unwrap().length; + let elapsed = player.pulses_since_start().unwrap(); + let elapsed = clock.timebase().format_beats_1_short( + (elapsed as usize % length) as f64 + ); + format!("▎+{elapsed:>}") + } else { + String::from("▎") + }; + // beats until switchover + let until_next = player.next_phrase.as_ref().map(|(t, _)|{ + let target = t.pulse.get(); + let current = clock.current.pulse.get(); + if target > current { + let remaining = target - current; + format!("▎-{:>}", clock.timebase().format_beats_0_short(remaining)) } else { - String::from("▎") - }; - // beats until switchover - let until_next = player.next_phrase.as_ref().map(|(t, _)|{ - let target = t.pulse.get(); - let current = clock.current.pulse.get(); - if target > current { - let remaining = target - current; - format!("▎-{:>}", clock.timebase().format_beats_0_short(remaining)) - } else { - String::new() - } - }).unwrap_or(String::from("▎")); - // name of active MIDI input - let input = format!("▎>{}", track.player.midi_inputs.get(0) - .map(|port|port.short_name()) - .transpose()? - .unwrap_or("(none)".into())); - // name of active MIDI output - let output = format!("▎<{}", track.player.midi_outputs.get(0) - .map(|port|port.short_name()) - .transpose()? - .unwrap_or("(none)".into())); - col!(name, /*input, output,*/ until_next, elapsed) - .min_xy(w as u16, header_h) - .bg(track.color.rgb) - .push_x(scenes_w) - }); - // scene titles - let scene_name = |scene: &ArrangementScene, playing: bool, height|row!( - if playing { "▶ " } else { " " }, - TuiStyle::bold(scene.name.read().unwrap().as_str(), true), - ).fixed_xy(scenes_w, height); - // scene clips - let scene_clip = |scene: &ArrangementScene, track: usize, w: u16, h: u16|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>).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; - add(&name.as_str()[0..max_w].push_x(1).fixed_x(w))?; - bg = color.dark.rgb; - if let Some((_, Some(ref playing))) = track.player.phrase { - if *playing.read().unwrap() == *phrase.read().unwrap() { - bg = color.light.rgb - } - }; - }, - _ => {} - }; - add(&Background(bg)) - }).fixed_xy(w, h); - // tracks and scenes - let content = col!( - // scenes: - (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); - Stack::right(move |add| { - // scene title: - add(&scene_name(scene, playing, height).bg(scene.color.rgb))?; - // clip per track: - Ok(for (track, w) in cols.iter().map(|col|col.0).enumerate() { - add(&scene_clip(scene, track, w as u16, height))?; - }) - }).fixed_y(height) + String::new() } - ).fixed_y((self.0.size.h() as u16).saturating_sub(header_h)); - // full grid with header and footer - add(&col!(header, content))?; - // cursor - add(&CustomWidget::new(any_size, move|to: &mut TuiOutput|{ - let area = to.area(); - let focused = state.focused; - let selected = state.selected; - let get_track_area = |t: usize| [ - scenes_w + area.x() + cols[t].1 as u16, area.y(), - cols[t].0 as u16, area.h(), - ]; - let get_scene_area = |s: usize| [ - area.x(), header_h + area.y() + (rows[s].1 / PPQ) as u16, - area.w(), (rows[s].0 / PPQ) as u16 - ]; - let get_clip_area = |t: usize, s: usize| [ - scenes_w + area.x() + cols[t].1 as u16, - header_h + area.y() + (rows[s].1/PPQ) as u16, - cols[t].0 as u16, - (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 { - ArrangementEditorFocus::Mix => area, - ArrangementEditorFocus::Track(t) => { - track_area = Some(get_track_area(t)); - area - }, - ArrangementEditorFocus::Scene(s) => { - scene_area = Some(get_scene_area(s)); - area - }, - ArrangementEditorFocus::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(header_h) } - else if let Some(scene_area) = scene_area { scene_area.clip_w(scenes_w) } - else { area.clip_w(scenes_w).clip_h(header_h) }, &CORNERS)? - }) - })) - }).bg(bg.rgb); - let color = TuiTheme::title_fg(self.0.focused); - let size = format!("{}x{}", self.0.size.w(), self.0.size.h()); - let lower_right = TuiStyle::fg(size, color).pull_x(1).align_se().fill_xy(); - lay!(arrangement, lower_right) - } + }).unwrap_or(String::from("▎")); + // name of active MIDI input + let input = format!("▎>{}", track.player.midi_inputs.get(0) + .map(|port|port.short_name()) + .transpose()? + .unwrap_or("(none)".into())); + // name of active MIDI output + let output = format!("▎<{}", track.player.midi_outputs.get(0) + .map(|port|port.short_name()) + .transpose()? + .unwrap_or("(none)".into())); + col!(name, /*input, output,*/ until_next, elapsed) + .min_xy(w as u16, header_h) + .bg(track.color.rgb) + .push_x(scenes_w) + }); + // scene titles + let scene_name = |scene: &ArrangementScene, playing: bool, height|row!( + if playing { "▶ " } else { " " }, + TuiStyle::bold(scene.name.read().unwrap().as_str(), true), + ).fixed_xy(scenes_w, height); + // scene clips + let scene_clip = |scene: &ArrangementScene, track: usize, w: u16, h: u16|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>).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; + add(&name.as_str()[0..max_w].push_x(1).fixed_x(w))?; + bg = color.dark.rgb; + if let Some((_, Some(ref playing))) = track.player.phrase { + if *playing.read().unwrap() == *phrase.read().unwrap() { + bg = color.light.rgb + } + }; + }, + _ => {} + }; + add(&Background(bg)) + }).fixed_xy(w, h); + // tracks and scenes + let content = col!( + // scenes: + (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); + Stack::right(move |add| { + // scene title: + add(&scene_name(scene, playing, height).bg(scene.color.rgb))?; + // clip per track: + Ok(for (track, w) in cols.iter().map(|col|col.0).enumerate() { + add(&scene_clip(scene, track, w as u16, height))?; + }) + }).fixed_y(height) + } + ).fixed_y((view.size.h() as u16).saturating_sub(header_h)); + // full grid with header and footer + add(&col!(header, content))?; + // cursor + add(&CustomWidget::new(any_size, move|to: &mut TuiOutput|{ + let area = to.area(); + let focused = view.arrangement.focused; + let selected = view.arrangement.selected; + let get_track_area = |t: usize| [ + scenes_w + area.x() + cols[t].1 as u16, area.y(), + cols[t].0 as u16, area.h(), + ]; + let get_scene_area = |s: usize| [ + area.x(), header_h + area.y() + (rows[s].1 / PPQ) as u16, + area.w(), (rows[s].0 / PPQ) as u16 + ]; + let get_clip_area = |t: usize, s: usize| [ + scenes_w + area.x() + cols[t].1 as u16, + header_h + area.y() + (rows[s].1/PPQ) as u16, + cols[t].0 as u16, + (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 { + ArrangementEditorFocus::Mix => area, + ArrangementEditorFocus::Track(t) => { + track_area = Some(get_track_area(t)); + area + }, + ArrangementEditorFocus::Scene(s) => { + scene_area = Some(get_scene_area(s)); + area + }, + ArrangementEditorFocus::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(header_h) } + else if let Some(scene_area) = scene_area { scene_area.clip_w(scenes_w) } + else { area.clip_w(scenes_w).clip_h(header_h) }, &CORNERS)? + }) + })) + }).bg(bg.rgb); + let color = TuiTheme::title_fg(view.arrangement.focused); + let size = format!("{}x{}", view.size.w(), view.size.h()); + let lower_right = TuiStyle::fg(size, color).pull_x(1).align_se().fill_xy(); + lay!(arrangement, lower_right) } -