diff --git a/app/src/view.rs b/app/src/view.rs index 732b5fe5..6d7986db 100644 --- a/app/src/view.rs +++ b/app/src/view.rs @@ -3,7 +3,6 @@ mod view_clock; pub use self::view_clock::*; mod view_color; pub use self::view_color::*; mod view_memo; pub use self::view_memo::*; mod view_meter; pub use self::view_meter::*; -mod view_sizes; pub use self::view_sizes::*; mod view_track; pub use self::view_track::*; mod view_ports; pub use self::view_ports::*; mod view_layout; pub use self::view_layout::*; @@ -134,6 +133,55 @@ impl<'a> ArrangerView<'a> { } impl Tek { + /// Spacing between tracks. + pub(crate) const TRACK_SPACING: usize = 0; + /// Default scene height. + pub(crate) const H_SCENE: usize = 2; + /// Default editor height. + pub(crate) const H_EDITOR: usize = 15; + + /// Width of display + pub(crate) fn w (&self) -> u16 { + self.size.w() as u16 + } + pub(crate) fn w_sidebar (&self) -> u16 { + self.w() / if self.is_editing() { 16 } else { 8 } as u16 + } + /// Width taken by all tracks. + pub(crate) fn w_tracks (&self) -> u16 { + self.tracks_with_sizes().last().map(|(_, _, _, x)|x as u16).unwrap_or(0) + } + /// Width available to display tracks. + pub(crate) fn w_tracks_area (&self) -> u16 { + self.w().saturating_sub(2 * self.w_sidebar()) + } + /// Height of display + pub(crate) fn h (&self) -> u16 { + self.size.h() as u16 + } + /// Height available to display track headers. + pub(crate) fn h_tracks_area (&self) -> u16 { + 5 + //self.h().saturating_sub(self.h_inputs() + self.h_outputs()) + } + /// Height available to display tracks. + pub(crate) fn h_scenes_area (&self) -> u16 { + //15 + self.h().saturating_sub(self.h_inputs() + self.h_outputs() + 11) + } + /// Height taken by all inputs. + pub(crate) fn h_inputs (&self) -> u16 { + 1 + self.inputs_with_sizes().last().map(|(_, _, _, _, y)|y as u16).unwrap_or(0) + } + /// Height taken by all outputs. + pub(crate) fn h_outputs (&self) -> u16 { + 1 + self.outputs_with_sizes().last().map(|(_, _, _, _, y)|y as u16).unwrap_or(0) + } + /// Height taken by all scenes. + pub(crate) fn h_scenes (&self) -> u16 { + self.scenes_with_sizes(self.is_editing(), Self::H_SCENE, Self::H_EDITOR).last() + .map(|(_, _, _, y)|y as u16).unwrap_or(0) + } pub(crate) fn inputs_with_sizes (&self) -> impl PortsSizes<'_> @@ -199,6 +247,18 @@ impl Tek { } +/// Define a type alias for iterators of sized items (columns). +macro_rules! def_sizes_iter { + ($Type:ident => $($Item:ty),+) => { + pub(crate) trait $Type<'a> = + Iterator + Send + Sync + 'a;}} + +def_sizes_iter!(ScenesSizes => Scene); +def_sizes_iter!(TracksSizes => Track); +def_sizes_iter!(InputsSizes => JackMidiIn); +def_sizes_iter!(OutputsSizes => JackMidiOut); +def_sizes_iter!(PortsSizes => Arc, [PortConnect]); + #[cfg(test)] #[test] fn test_view_iter () { let mut tek = Tek::default(); tek.editor = Some(Default::default()); @@ -209,3 +269,14 @@ impl Tek { //let _: Vec<_> = tek.scenes_with_colors(true, 10).collect(); //let _: Vec<_> = tek.scenes_with_track_colors(true, 10, 10).collect(); } +#[cfg(test)] #[test] fn test_view_sizes () { + let app = Tek::default(); + let _ = app.w(); + let _ = app.w_sidebar(); + let _ = app.w_tracks_area(); + let _ = app.h(); + let _ = app.h_tracks_area(); + let _ = app.h_inputs(); + let _ = app.h_outputs(); + let _ = app.h_scenes(); +} diff --git a/app/src/view/view_clock.rs b/app/src/view/view_clock.rs index 585c07fc..b785b090 100644 --- a/app/src/view/view_clock.rs +++ b/app/src/view/view_clock.rs @@ -1,64 +1,64 @@ use crate::*; impl Tek { fn update_clock (&self) { - let compact = self.size.w() > 80; - let clock = self.clock(); - let rate = clock.timebase.sr.get(); - let chunk = clock.chunk.load(Relaxed) as f64; - let lat = chunk / rate * 1000.; - let delta = |start: &Moment|clock.global.usec.get() - start.usec.get(); - let mut view_cache = self.view_cache.write().unwrap(); - view_cache.buf.update(Some(chunk), rewrite!(buf, "{chunk}")); - view_cache.lat.update(Some(lat), rewrite!(buf, "{lat:.1}ms")); - view_cache.sr.update(Some((compact, rate)), |buf,_,_|if compact { - buf.clear(); write!(buf, "{:.1}kHz", rate / 1000.) - } else { - buf.clear(); write!(buf, "{:.0}Hz", rate) - }); - if let Some(now) = clock.started.read().unwrap().as_ref().map(delta) { - let pulse = clock.timebase.usecs_to_pulse(now); - let time = now/1000000.; - let bpm = clock.timebase.bpm.get(); - view_cache.beat.update(Some(pulse), - |buf, _, _|{buf.clear();clock.timebase.format_beats_1_to(buf, pulse)}); - view_cache.time.update(Some(time), rewrite!(buf, "{:.3}s", time)); - view_cache.bpm.update(Some(bpm), rewrite!(buf, "{:.3}", bpm)); - } else { - view_cache.beat.update(None, rewrite!(buf, "{}", ViewCache::BEAT_EMPTY)); - view_cache.time.update(None, rewrite!(buf, "{}", ViewCache::TIME_EMPTY)); - view_cache.bpm.update(None, rewrite!(buf, "{}", ViewCache::BPM_EMPTY)); - } + ViewCache::update_clock(&self.view_cache, self.clock(), self.size.w() > 80) } pub(crate) fn view_transport (&self) -> impl Content + use<'_> { self.update_clock(); - let theme = ItemPalette::G[96]; - let view_cache = self.view_cache.read().unwrap(); - Fixed::y(1, Tui::bg(Black, row!(Bsp::a( - Fill::xy(Align::w(button_play_pause(self.clock.is_rolling()))), - Fill::xy(Align::e(row!( - FieldH(theme, "BPM", view_cache.bpm.view.clone()), - FieldH(theme, "Beat", view_cache.beat.view.clone()), - FieldH(theme, "Time", view_cache.time.view.clone()) - ))) - )))) + let cache = self.view_cache.read().unwrap(); + view_transport( + self.clock.is_rolling(), + cache.bpm.view.clone(), + cache.beat.view.clone(), + cache.time.view.clone(), + ) } pub(crate) fn view_status (&self) -> impl Content + use<'_> { self.update_clock(); - let theme = ItemPalette::G[96]; - let view_cache = self.view_cache.read().unwrap(); - Tui::bg(Black, row!(Bsp::a( - Fill::xy(Align::w( - FieldH(theme, "Selected", self.selected.describe(&self.tracks, &self.scenes)) - )), - Fill::xy(Align::e(row!( - FieldH(theme, "SR", view_cache.sr.view.clone()), - FieldH(theme, "Buf", view_cache.buf.view.clone()), - FieldH(theme, "Lat", view_cache.lat.view.clone()), - ))) - ))) + let cache = self.view_cache.read().unwrap(); + view_status( + self.selected.describe(&self.tracks, &self.scenes), + cache.sr.view.clone(), + cache.buf.view.clone(), + cache.lat.view.clone(), + ) } } +fn view_transport ( + play: bool, + bpm: Arc>, + beat: Arc>, + time: Arc>, +) -> impl Content { + let theme = ItemPalette::G[96]; + Tui::bg(Black, row!(Bsp::a( + Fill::xy(Align::w(button_play_pause(play))), + Fill::xy(Align::e(row!( + FieldH(theme, "BPM", bpm), + FieldH(theme, "Beat", beat), + FieldH(theme, "Time", time), + ))) + ))) +} + +fn view_status ( + sel: Arc, + sr: Arc>, + buf: Arc>, + lat: Arc>, +) -> impl Content { + let theme = ItemPalette::G[96]; + Tui::bg(Black, row!(Bsp::a( + Fill::xy(Align::w(FieldH(theme, "Selected", sel))), + Fill::xy(Align::e(row!( + FieldH(theme, "SR", sr), + FieldH(theme, "Buf", buf), + FieldH(theme, "Lat", lat), + ))) + ))) +} + fn button_play_pause (playing: bool) -> impl Content { let compact = true;//self.is_editing(); Tui::bg( @@ -66,10 +66,14 @@ fn button_play_pause (playing: bool) -> impl Content { Either::new(compact, Thunk::new(move||Fixed::x(9, Either::new(playing, Tui::fg(Rgb(0, 255, 0), " PLAYING "), - Tui::fg(Rgb(255, 128, 0), " STOPPED ")))), + Tui::fg(Rgb(255, 128, 0), " STOPPED "))) + ), Thunk::new(move||Fixed::x(5, Either::new(playing, Tui::fg(Rgb(0, 255, 0), Bsp::s(" 🭍🭑🬽 ", " 🭞🭜🭘 ",)), - Tui::fg(Rgb(255, 128, 0), Bsp::s(" ▗▄▖ ", " ▝▀▘ ",))))))) + Tui::fg(Rgb(255, 128, 0), Bsp::s(" ▗▄▖ ", " ▝▀▘ ",)))) + ) + ) + ) } #[cfg(test)] mod test { diff --git a/app/src/view/view_color.rs b/app/src/view/view_color.rs index 76db469f..8edcc48a 100644 --- a/app/src/view/view_color.rs +++ b/app/src/view/view_color.rs @@ -14,9 +14,19 @@ impl Tek { [fg, bg, hi, lo] } pub(crate) fn color_hi (prev: Option, neighbor: bool) -> Color { - prev.map(|prev|if neighbor { prev.light.rgb } else { prev.base.rgb }).unwrap_or(Reset) + prev.map(|prev|if neighbor { + prev.light.rgb + } else { + prev.base.rgb + }).unwrap_or(Reset) } pub(crate) fn color_lo (theme: &ItemPalette, is_last: bool, selected: bool) -> Color { - if is_last { Reset } else if selected { theme.light.rgb } else { theme.base.rgb } + if is_last { + Reset + } else if selected { + theme.light.rgb + } else { + theme.base.rgb + } } } diff --git a/app/src/view/view_memo.rs b/app/src/view/view_memo.rs index 9deccf29..03c3eb23 100644 --- a/app/src/view/view_memo.rs +++ b/app/src/view/view_memo.rs @@ -2,12 +2,7 @@ use crate::*; /// Clear a pre-allocated buffer, then write into it. #[macro_export] macro_rules! rewrite { - ($buf:ident, $($rest:tt)*) => { - |$buf,_,_|{ - $buf.clear(); - write!($buf, $($rest)*) - } - } + ($buf:ident, $($rest:tt)*) => { |$buf,_,_|{ $buf.clear(); write!($buf, $($rest)*) } } } #[derive(Debug, Default)] pub(crate) struct ViewMemo { @@ -33,7 +28,7 @@ impl ViewMemo { } } -#[derive(Debug)] pub(crate) struct ViewCache { +#[derive(Debug)] pub struct ViewCache { pub(crate) sr: ViewMemo, String>, pub(crate) buf: ViewMemo, String>, pub(crate) lat: ViewMemo, String>, @@ -46,12 +41,6 @@ impl ViewMemo { pub(crate) edit: Arc, } -impl ViewCache { - pub const BEAT_EMPTY: &'static str = "-.-.--"; - pub const TIME_EMPTY: &'static str = "-.---s"; - pub const BPM_EMPTY: &'static str = "---.---"; -} - impl Default for ViewCache { fn default () -> Self { let mut beat = String::with_capacity(16); @@ -74,3 +63,58 @@ impl Default for ViewCache { } } } + +impl ViewCache { + pub const BEAT_EMPTY: &'static str = "-.-.--"; + pub const TIME_EMPTY: &'static str = "-.---s"; + pub const BPM_EMPTY: &'static str = "---.---"; + + pub(crate) fn track_counter (cache: &Arc>, track: usize, tracks: usize) + -> Arc> + { + let data = (track, tracks); + cache.write().unwrap().trks.update(Some(data), rewrite!(buf, "{}/{}", data.0, data.1)); + cache.read().unwrap().trks.view.clone() + } + + pub(crate) fn scene_add (cache: &Arc>, scene: usize, scenes: usize, is_editing: bool) + -> impl Content + { + let data = (scene, scenes); + cache.write().unwrap().scns.update(Some(data), rewrite!(buf, "({}/{})", data.0, data.1)); + button_3("S", "add scene", cache.read().unwrap().scns.view.clone(), is_editing) + } + + pub(crate) fn update_clock (cache: &Arc>, clock: &Clock, compact: bool) { + let rate = clock.timebase.sr.get(); + let chunk = clock.chunk.load(Relaxed) as f64; + let lat = chunk / rate * 1000.; + let delta = |start: &Moment|clock.global.usec.get() - start.usec.get(); + let mut cache = cache.write().unwrap(); + cache.buf.update(Some(chunk), rewrite!(buf, "{chunk}")); + cache.lat.update(Some(lat), rewrite!(buf, "{lat:.1}ms")); + cache.sr.update(Some((compact, rate)), |buf,_,_|{ + buf.clear(); + if compact { + write!(buf, "{:.1}kHz", rate / 1000.) + } else { + write!(buf, "{:.0}Hz", rate) + } + }); + if let Some(now) = clock.started.read().unwrap().as_ref().map(delta) { + let pulse = clock.timebase.usecs_to_pulse(now); + let time = now/1000000.; + let bpm = clock.timebase.bpm.get(); + cache.beat.update(Some(pulse), |buf, _, _|{ + buf.clear(); + clock.timebase.format_beats_1_to(buf, pulse) + }); + cache.time.update(Some(time), rewrite!(buf, "{:.3}s", time)); + cache.bpm.update(Some(bpm), rewrite!(buf, "{:.3}", bpm)); + } else { + cache.beat.update(None, rewrite!(buf, "{}", ViewCache::BEAT_EMPTY)); + cache.time.update(None, rewrite!(buf, "{}", ViewCache::TIME_EMPTY)); + cache.bpm.update(None, rewrite!(buf, "{}", ViewCache::BPM_EMPTY)); + } + } +} diff --git a/app/src/view/view_sizes.rs b/app/src/view/view_sizes.rs deleted file mode 100644 index 696f8663..00000000 --- a/app/src/view/view_sizes.rs +++ /dev/null @@ -1,76 +0,0 @@ -use crate::*; - -/// Define a type alias for iterators of sized items (columns). -macro_rules! def_sizes_iter { - ($Type:ident => $($Item:ty),+) => { - pub(crate) trait $Type<'a> = - Iterator + Send + Sync + 'a;}} - -def_sizes_iter!(ScenesSizes => Scene); -def_sizes_iter!(TracksSizes => Track); -def_sizes_iter!(InputsSizes => JackMidiIn); -def_sizes_iter!(OutputsSizes => JackMidiOut); -def_sizes_iter!(PortsSizes => Arc, [PortConnect]); - -impl Tek { - /// Spacing between tracks. - pub(crate) const TRACK_SPACING: usize = 0; - /// Default scene height. - pub(crate) const H_SCENE: usize = 2; - /// Default editor height. - pub(crate) const H_EDITOR: usize = 15; - - /// Width of display - pub(crate) fn w (&self) -> u16 { - self.size.w() as u16 - } - pub(crate) fn w_sidebar (&self) -> u16 { - self.w() / if self.is_editing() { 16 } else { 8 } as u16 - } - /// Width taken by all tracks. - pub(crate) fn w_tracks (&self) -> u16 { - self.tracks_with_sizes().last().map(|(_, _, _, x)|x as u16).unwrap_or(0) - } - /// Width available to display tracks. - pub(crate) fn w_tracks_area (&self) -> u16 { - self.w().saturating_sub(2 * self.w_sidebar()) - } - /// Height of display - pub(crate) fn h (&self) -> u16 { - self.size.h() as u16 - } - /// Height available to display track headers. - pub(crate) fn h_tracks_area (&self) -> u16 { - 5 - //self.h().saturating_sub(self.h_inputs() + self.h_outputs()) - } - /// Height available to display tracks. - pub(crate) fn h_scenes_area (&self) -> u16 { - //15 - self.h().saturating_sub(self.h_inputs() + self.h_outputs() + 11) - } - /// Height taken by all inputs. - pub(crate) fn h_inputs (&self) -> u16 { - 1 + self.inputs_with_sizes().last().map(|(_, _, _, _, y)|y as u16).unwrap_or(0) - } - /// Height taken by all outputs. - pub(crate) fn h_outputs (&self) -> u16 { - 1 + self.outputs_with_sizes().last().map(|(_, _, _, _, y)|y as u16).unwrap_or(0) - } - /// Height taken by all scenes. - pub(crate) fn h_scenes (&self) -> u16 { - self.scenes_with_sizes(self.is_editing(), Self::H_SCENE, Self::H_EDITOR).last() - .map(|(_, _, _, y)|y as u16).unwrap_or(0) - } -} -#[cfg(test)] #[test] fn test_view_sizes () { - let app = Tek::default(); - let _ = app.w(); - let _ = app.w_sidebar(); - let _ = app.w_tracks_area(); - let _ = app.h(); - let _ = app.h_tracks_area(); - let _ = app.h_inputs(); - let _ = app.h_outputs(); - let _ = app.h_scenes(); -} diff --git a/app/src/view/view_track.rs b/app/src/view/view_track.rs index 95ad9331..9520f179 100644 --- a/app/src/view/view_track.rs +++ b/app/src/view/view_track.rs @@ -18,7 +18,7 @@ impl<'a> ArrangerView<'a> { let Self { width, width_side, width_mid, scenes_height, scene_last, scene_selected, - track_selected, is_editing, .. + track_selected, is_editing, app: Tek { editor, .. }, .. } = self; Tryptich::center(*scenes_height) .left(*width_side, Map::new(||self.scenes_with_scene_colors(), @@ -47,28 +47,25 @@ impl<'a> ArrangerView<'a> { *track_selected == Some(track_index), *scene_selected, *scene_last == scene_index, - &self.app.editor + editor )))) } fn scene_add (&'a self) -> impl Content + 'a { - let Self { - scene_selected, scene_count, is_editing, app: Tek { view_cache, .. }, .. - } = self; - let data = (scene_selected.unwrap_or(0), *scene_count); - view_cache.write().unwrap() - .scns.update(Some(data), rewrite!(buf, "({}/{})", data.0, data.1)); - button_3("S", "add scene", view_cache.read().unwrap().scns.view.clone(), *is_editing) + ViewCache::scene_add( + &self.app.view_cache, + self.scene_selected.unwrap_or(0), + self.scene_count, + self.is_editing, + ) } fn track_counter (&'a self) -> Arc> { - let Self { - track_selected, track_count, app: Tek { view_cache, .. }, .. - } = self; - let data = (track_selected.unwrap_or(0), *track_count); - view_cache.write().unwrap() - .trks.update(Some(data), rewrite!(buf, "{}/{}", data.0, data.1)); - view_cache.read().unwrap().trks.view.clone() + ViewCache::track_counter( + &self.app.view_cache, + self.track_selected.unwrap_or(0), + self.track_count, + ) } } @@ -93,15 +90,7 @@ pub(crate) fn view_scene_name ( select: Option, ) -> impl Content { Fill::x(map_south(offset, height, Fixed::y(height, view_scene_cell( - " ⯈ ", - Some(scene.name.clone()), - last, - select, - true, - index, - &scene.color, - prev, - scene.color.lightest.rgb + " ⯈ ", Some(scene.name.clone()), &scene.color, prev, last, select, true, index, )))) } @@ -110,7 +99,7 @@ pub(crate) fn view_scene_clip <'a> ( height: u16, offset: u16, scene: &'a Scene, - prev: Option, + prev_bg: Option, scene_index: usize, track_index: usize, editing: bool, @@ -119,37 +108,29 @@ pub(crate) fn view_scene_clip <'a> ( scene_is_last: bool, editor: &'a Option, ) -> impl Content + use<'a> { - let (name, fg, bg) = if let Some(clip) = &scene.clips[track_index] { + let (name, _fg, bg) = if let Some(clip) = &scene.clips[track_index] { let clip = clip.read().unwrap(); (Some(clip.name.clone()), clip.color.lightest.rgb, clip.color) } else { (None, Tui::g(96), ItemPalette::G[32]) }; let active = editing && same_track && scene_selected == Some(scene_index); - let edit = |x|Bsp::b(x, When(active, editor)); - map_south(offset, height, edit(Fixed::y(height, view_scene_cell( - " ⏹ ", - name, - scene_is_last, - scene_selected, - same_track, - scene_index, - &bg, - prev, - fg + let with_editor = |x|Bsp::b(x, When(active, editor)); + map_south(offset, height, with_editor(Fixed::y(height, view_scene_cell( + " ⏹ ", name, &bg, prev_bg, + scene_is_last, scene_selected, same_track, scene_index, )))) } pub(crate) fn view_scene_cell <'a> ( icon: &'a str, name: Option>, + color: &ItemPalette, + prev_color: Option, is_last: bool, selected: Option, same_track: bool, scene: usize, - color: &ItemPalette, - prev_color: Option, - fg: Color, ) -> impl Content + use<'a> { Phat { width: 0, diff --git a/midi/src/midi_pool.rs b/midi/src/midi_pool.rs index f660f475..7fabb7fa 100644 --- a/midi/src/midi_pool.rs +++ b/midi/src/midi_pool.rs @@ -395,30 +395,27 @@ atom_command!(ClipRenameCommand: |state: MidiPool| { ("confirm" [] Some(Self::Confirm)) ("set" [n: Arc] Some(Self::Set(n.expect("no name")))) }); -command!(|self: ClipRenameCommand, state: MidiPool|{ - use ClipRenameCommand::*; - if let Some( - PoolMode::Rename(clip, ref mut old_name) - ) = state.mode_mut().clone() { - match self { - Set(s) => { - state.clips()[clip].write().unwrap().name = s; - return Ok(Some(Self::Set(old_name.clone().into()))) - }, - Confirm => { - let old_name = old_name.clone(); - *state.mode_mut() = None; - return Ok(Some(Self::Set(old_name))) - }, - Cancel => { - state.clips()[clip].write().unwrap().name = old_name.clone().into(); - return Ok(None) - }, - _ => unreachable!() - } - } else { - unreachable!() +command!(|self: ClipRenameCommand, state: MidiPool|if let Some( + PoolMode::Rename(clip, ref mut old_name) +) = state.mode_mut().clone() { + match self { + Self::Set(s) => { + state.clips()[clip].write().unwrap().name = s; + return Ok(Some(Self::Set(old_name.clone().into()))) + }, + Self::Confirm => { + let old_name = old_name.clone(); + *state.mode_mut() = None; + return Ok(Some(Self::Set(old_name))) + }, + Self::Cancel => { + state.clips()[clip].write().unwrap().name = old_name.clone().into(); + return Ok(None) + }, + _ => unreachable!() } +} else { + unreachable!() }); #[derive(Copy, Clone, Debug, PartialEq)] pub enum ClipLengthCommand { Begin, diff --git a/tengri b/tengri index 9809c464..1daca5ea 160000 --- a/tengri +++ b/tengri @@ -1 +1 @@ -Subproject commit 9809c4642883b4dba896bdc92709f6d0b1513f8b +Subproject commit 1daca5ea7b286d95ffb1c9c14b513e0cbe641965