From 06dab6d0d735188bcfc09317505c46c856b23b84 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Mon, 9 Dec 2024 20:59:19 +0100 Subject: [PATCH] wip10 (6e) --- crates/tek/src/layout/stack.rs | 18 +- crates/tek/src/tui/view_arranger.rs | 310 +++++++++++++++------------- 2 files changed, 180 insertions(+), 148 deletions(-) diff --git a/crates/tek/src/layout/stack.rs b/crates/tek/src/layout/stack.rs index be309c77..24daf4be 100644 --- a/crates/tek/src/layout/stack.rs +++ b/crates/tek/src/layout/stack.rs @@ -16,17 +16,17 @@ use crate::*; } #[macro_export] macro_rules! col_up { - ($(move)?|$add:ident|$expr:expr) => { - Stack::up($(move)?|$add|$expr) + ([$($expr:expr),* $(,)?]) => { + Stack::up(move|add|{ $(add(&$expr)?;)* Ok(()) }) + }; + (![$($expr:expr),* $(,)?]) => { + Stack::up(|add|{ $(add(&$expr)?;)* Ok(()) }) + }; + ($expr:expr) => { + Stack::up(expr) }; ($pat:pat in $collection:expr => $item:expr) => { - Stack::up(move |add|{ - for $pat in $collection { add(&$item)?; } - Ok(()) - }) - }; - ($($expr:expr),* $(,)?) => { - Stack::up(move|add|{ $(add(&$expr)?;)* Ok(()) }) + Stack::up(move |add|{ for $pat in $collection { add(&$item)?; } Ok(()) }) }; } diff --git a/crates/tek/src/tui/view_arranger.rs b/crates/tek/src/tui/view_arranger.rs index 89b8d25b..84614847 100644 --- a/crates/tek/src/tui/view_arranger.rs +++ b/crates/tek/src/tui/view_arranger.rs @@ -3,42 +3,37 @@ use crate::*; // Layout for standalone arranger app. render!(|self: ArrangerTui|{ let arranger_focused = self.arranger_focused(); - Tui::to_south( + let border = Lozenge(Style::default().bg(TuiTheme::border_bg()).fg(TuiTheme::border_fg(arranger_focused))); + col_up!([ TransportView::from(self), - Tui::to_south( - self.splits[0], - Tui::to_south( - lay!([ - Layers::new(move |add|{ - match self.mode { - ArrangerMode::Horizontal => - add(&arranger_content_horizontal(self))?, - ArrangerMode::Vertical(factor) => - add(&arranger_content_vertical(self, factor))? - }; - add(&self.size) - }) - .grow_y(1) - .border(Lozenge(Style::default() - .bg(TuiTheme::border_bg()) - .fg(TuiTheme::border_fg(arranger_focused)))), - widget(&self.size), - widget(&format!("[{}] Arranger", if self.entered { + col!([ + Tui::fixed_y(self.splits[0], lay!([ + border.wrap(Tui::grow_y(1, Layers::new(move |add|{ + match self.mode { + ArrangerMode::Horizontal => + add(&arranger_content_horizontal(self))?, + ArrangerMode::Vertical(factor) => + add(&arranger_content_vertical(self, factor))? + }; + add(&self.size) + }))), + self.size, + Tui::push_x(1, Tui::fg( + TuiTheme::title_fg(arranger_focused), + format!("[{}] Arranger", if self.entered { "■" } else { " " - })) - .fg(TuiTheme::title_fg(arranger_focused)) - .push_x(1), - ]), - Split::right( - self.splits[1], - PhraseListView::from(self), - PhraseView::from(self), - ) + }) + )) + ])), + Split::right( + self.splits[1], + PhraseListView::from(self), + PhraseView::from(self), ) - ) - ) + ]) + ]) }); /// Display mode of arranger @@ -97,114 +92,19 @@ pub fn arranger_content_vertical ( view: &ArrangerTui, factor: usize ) -> impl Render + use<'_> { - let timebase = view.clock().timebase(); - let current = &view.clock().playhead; - let tracks = view.tracks(); - let scenes = view.scenes(); - let cols = track_widths(tracks); - let rows = ArrangerScene::ppqs(scenes, factor); - let bg = view.color; - let clip_bg = TuiTheme::border_bg(); - let sep_fg = TuiTheme::separator_fg(false); - let header_h = 3u16;//5u16; - let scenes_w = 3 + ArrangerScene::longest_name(scenes) as u16; // x of 1st track - //let rows: &[(usize, usize)] = rows_.as_ref(); - //let cols: &[(usize, usize)] = cols_.as_ref(); - - // 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 max_w = w.saturating_sub(1).min(name.len()).max(2); - let name = format!("▎{}", &name[0..max_w]); - let name = Tui::bold(true, name); - // beats elapsed - let elapsed = if let Some((_, Some(phrase))) = track.player.play_phrase().as_ref() { - let length = phrase.read().unwrap().length; - let elapsed = track.player.pulses_since_start().unwrap(); - let elapsed = timebase.format_beats_1_short( - (elapsed as usize % length) as f64 - ); - format!("▎+{elapsed:>}") - } else { - String::from("▎") - }; - // beats until switchover - let until_next = track.player.next_phrase().as_ref().map(|(t, _)|{ - let target = t.pulse.get(); - let current = current.pulse.get(); - if target > current { - let remaining = target - current; - format!("▎-{:>}", timebase.format_beats_0_short(remaining)) - } else { - String::new() - } - }).unwrap_or(String::from("▎")); - let timer = Tui::to_south(until_next, elapsed); - // name of active MIDI input - let _input = format!("▎>{}", track.player.midi_ins().get(0) - .map(|port|port.short_name()) - .transpose()? - .unwrap_or("(none)".into())); - // name of active MIDI output - let _output = format!("▎<{}", track.player.midi_outs().get(0) - .map(|port|port.short_name()) - .transpose()? - .unwrap_or("(none)".into())); - Tui::push_x(scenes_w, - Tui::bg(track.color().rgb, - Tui::min_xy(w as u16, header_h, - Tui::to_south(name, timer)))) - }); - - let content = Tui::fixed_y( - (view.size.h() as u16).saturating_sub(header_h), - col!((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); - Tui::fixed_y( - height, - Tui::to_east( - Tui::to_east( - if playing { "▶ " } else { " " }, - Tui::bold(true, scene.name.read().unwrap().as_str()) - ), - row!((track, w) in cols.iter().map(|col|col.0).enumerate() => { - Tui::fixed_xy(w as u16, height, 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; - bg = color.dark.rgb; - if let Some((_, Some(ref playing))) = track.player.play_phrase() { - if *playing.read().unwrap() == *phrase.read().unwrap() { - bg = color.light.rgb - } - }; - add(&Tui::fixed_x(w as u16, Tui::push_x(1, &name.as_str()[0..max_w])))?; - }, - _ => {} - }; - //add(&Background(bg)) - Ok(()) - })) - }) - ) - ) - })); - - let color = TuiTheme::title_fg(view.arranger_focused()); lay!([ - Tui::bg(bg.rgb, lay!(![ + Tui::at_se(Tui::fill_xy(Tui::pull_x(1, Tui::fg(TuiTheme::title_fg(view.arranger_focused()), + format!("{}x{}", view.size.w(), view.size.h())) + ))), + Tui::bg(view.color.rgb, lay!(![ ArrangerVerticalColumnSeparator::from(view), ArrangerVerticalRowSeparator::from((view, factor)), - col!(![header, content]), - ArrangerCursor::from((view, factor)), + col!(![ + ArrangerVerticalHeader::from(view), + ArrangerVerticalContent::from((view, factor)), + ]), + ArrangerVerticalCursor::from((view, factor)), ])), - Tui::at_se(Tui::fill_xy(Tui::pull_x(1, Tui::fg(color, format!("{}x{}", view.size.w(), view.size.h()))))), ]) } @@ -259,7 +159,7 @@ render!(|self: ArrangerVerticalRowSeparator|custom_render(move|to: &mut TuiOutpu }) })); -struct ArrangerCursor { +struct ArrangerVerticalCursor { cols: Vec<(usize, usize)>, rows: Vec<(usize, usize)>, focused: bool, @@ -267,7 +167,7 @@ struct ArrangerCursor { scenes_w: u16, header_h: u16, } -impl From<(&ArrangerTui, usize)> for ArrangerCursor { +impl From<(&ArrangerTui, usize)> for ArrangerVerticalCursor { fn from ((state, factor): (&ArrangerTui, usize)) -> Self { Self { cols: track_widths(state.tracks()), @@ -279,7 +179,7 @@ impl From<(&ArrangerTui, usize)> for ArrangerCursor { } } } -render!(|self: ArrangerCursor|custom_render(move|to: &mut TuiOutput|{ +render!(|self: ArrangerVerticalCursor|custom_render(move|to: &mut TuiOutput|{ let area = to.area(); let focused = self.focused; let selected = self.selected; @@ -334,10 +234,142 @@ render!(|self: ArrangerCursor|custom_render(move|to: &mut TuiOutput|{ }) })); +struct ArrangerVerticalHeader<'a> { + tracks: &'a Vec, + cols: Vec<(usize, usize)>, + focused: bool, + selected: ArrangerSelection, + scenes_w: u16, + header_h: u16, + timebase: &'a Arc, + current: &'a Arc, +} +impl<'a> From<&'a ArrangerTui> for ArrangerVerticalHeader<'a> { + fn from (state: &'a ArrangerTui) -> Self { + Self { + tracks: &state.tracks, + cols: track_widths(state.tracks()), + focused: state.arranger_focused(), + selected: state.selected, + scenes_w: 3 + ArrangerScene::longest_name(state.scenes()) as u16, + header_h: 3, + timebase: state.clock().timebase(), + current: &state.clock().playhead, + } + } +} +render!(|self: ArrangerVerticalHeader<'a>|row!( + (track, w) in self.tracks.iter().zip(self.cols.iter().map(|col|col.0)) => { + // name and width of track + let name = track.name().read().unwrap(); + let max_w = w.saturating_sub(1).min(name.len()).max(2); + let name = format!("▎{}", &name[0..max_w]); + let name = Tui::bold(true, name); + // beats elapsed + let elapsed = if let Some((_, Some(phrase))) = track.player.play_phrase().as_ref() { + let length = phrase.read().unwrap().length; + let elapsed = track.player.pulses_since_start().unwrap(); + let elapsed = self.timebase.format_beats_1_short( + (elapsed as usize % length) as f64 + ); + format!("▎+{elapsed:>}") + } else { + String::from("▎") + }; + // beats until switchover + let until_next = track.player.next_phrase().as_ref().map(|(t, _)|{ + let target = t.pulse.get(); + let current = self.current.pulse.get(); + if target > current { + let remaining = target - current; + format!("▎-{:>}", self.timebase.format_beats_0_short(remaining)) + } else { + String::new() + } + }).unwrap_or(String::from("▎")); + let timer = Tui::to_south(until_next, elapsed); + // name of active MIDI input + let _input = format!("▎>{}", track.player.midi_ins().get(0) + .map(|port|port.short_name()) + .transpose()? + .unwrap_or("(none)".into())); + // name of active MIDI output + let _output = format!("▎<{}", track.player.midi_outs().get(0) + .map(|port|port.short_name()) + .transpose()? + .unwrap_or("(none)".into())); + Tui::push_x(self.scenes_w, + Tui::bg(track.color().rgb, + Tui::min_xy(w as u16, self.header_h, + Tui::to_south(name, timer)))) + } +)); + +struct ArrangerVerticalContent<'a> { + size: &'a Measure, + scenes: &'a Vec, + tracks: &'a Vec, + rows: Vec<(usize, usize)>, + cols: Vec<(usize, usize)>, + header_h: u16, +} +impl<'a> From<(&'a ArrangerTui, usize)> for ArrangerVerticalContent<'a> { + fn from ((state, factor): (&'a ArrangerTui, usize)) -> Self { + Self { + size: &state.size, + scenes: &state.scenes, + tracks: &state.tracks, + rows: ArrangerScene::ppqs(state.scenes(), factor), + cols: track_widths(state.tracks()), + header_h: 3, + } + } +} +render!(|self: ArrangerVerticalContent<'a>|Tui::fixed_y( + (self.size.h() as u16).saturating_sub(self.header_h), + col!((scene, pulses) in self.scenes.iter().zip(self.rows.iter().map(|row|row.0)) => { + let height = 1.max((pulses / PPQ) as u16); + let playing = scene.is_playing(self.tracks); + Tui::fixed_y( + height, + Tui::to_east( + Tui::to_east( + if playing { "▶ " } else { " " }, + Tui::bold(true, scene.name.read().unwrap().as_str()) + ), + row!((track, w) in self.cols.iter().map(|col|col.0).enumerate() => { + Tui::fixed_xy(w as u16, height, Layers::new(move |add|{ + let mut bg = TuiTheme::border_bg(); + match (self.tracks.get(track), scene.clips.get(track)) { + (Some(track), Some(Some(phrase))) => { + let name = &(phrase as &Arc>).read().unwrap().name; + let name = format!("{}", name); + let max_w = name.len().min((w as usize).saturating_sub(2)); + let color = phrase.read().unwrap().color; + bg = color.dark.rgb; + if let Some((_, Some(ref playing))) = track.player.play_phrase() { + if *playing.read().unwrap() == *phrase.read().unwrap() { + bg = color.light.rgb + } + }; + add(&Tui::fixed_x(w as u16, Tui::push_x(1, &name.as_str()[0..max_w])))?; + }, + _ => {} + }; + //add(&Background(bg)) + Ok(()) + })) + }) + ) + ) + }) +)); + pub fn arranger_content_horizontal ( view: &ArrangerTui, ) -> impl Render + use<'_> { todo!() +} //let focused = view.arranger_focused(); //let _tracks = view.tracks(); //lay!( @@ -526,4 +558,4 @@ pub fn arranger_content_horizontal ( //}), //) //) -} +//}