app trait impls

This commit is contained in:
🪞👃🪞 2025-01-13 20:23:10 +01:00
parent af2e237b94
commit 93fa3c26b4
4 changed files with 156 additions and 137 deletions

View file

@ -1,6 +1,10 @@
use crate::*; use crate::*;
pub trait HasEditor { pub trait HasEditor {
fn editor (&self) -> &MidiEditor; fn editor (&self) -> &Option<MidiEditor>;
fn editor_mut (&mut self) -> &Option<MidiEditor>;
fn is_editing (&self) -> bool { true }
fn editor_w (&self) -> usize { 0 }
fn editor_h (&self) -> usize { 0 }
} }
#[macro_export] macro_rules! has_editor { #[macro_export] macro_rules! has_editor {
(|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => { (|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => {

View file

@ -0,0 +1,22 @@
use crate::*;
pub trait HasSampler {
fn sampler (&self) -> &Option<Sampler>;
fn sampler_mut (&mut self) -> &mut Option<Sampler>;
fn sample_index (&self) -> usize;
fn view_sample <'a> (&'a self, compact: bool) -> impl Content<TuiOut> + 'a {
self.sampler().as_ref().map(|sampler|Max::y(
if compact { 0u16 } else { 5 }.into(),
Fill::x(sampler.viewer(self.sample_index()))
))
}
fn view_sampler <'a> (&'a self, compact: bool, editor: &Option<MidiEditor>) -> impl Content<TuiOut> + 'a {
self.sampler().as_ref().map(|sampler|Fixed::x(
if compact { 4u16 } else { 40 }.into(),
Push::y(
if compact { 1u16 } else { 0 }.into(),
editor.as_ref().map(|e|Fill::y(sampler.list(compact, e)))
)
))
}
}

View file

@ -1,6 +1,7 @@
mod sampler; pub use self::sampler::*; mod sampler; pub use self::sampler::*;
mod sampler_tui; pub use self::sampler_tui::*; mod sampler_tui; pub use self::sampler_tui::*;
mod sampler_cmd; pub use self::sampler_cmd::*; mod sampler_cmd; pub use self::sampler_cmd::*;
mod has_sampler; pub use self::has_sampler::*;
pub(crate) use ::tek_jack::{*, jack::*}; pub(crate) use ::tek_jack::{*, jack::*};
pub(crate) use ::tek_midi::{*, midly::{*, live::*, num::*}}; pub(crate) use ::tek_midi::{*, midly::{*, live::*, num::*}};

View file

@ -52,13 +52,38 @@ pub(crate) use std::sync::{Arc, RwLock};
pub perf: PerfModel, pub perf: PerfModel,
pub compact: bool, pub compact: bool,
} }
impl HasJack for App { fn jack (&self) -> &Arc<RwLock<JackConnection>> { &self.jack } }
has_size!(<TuiOut>|self: App|&self.size); has_size!(<TuiOut>|self: App|&self.size);
has_clock!(|self: App|&self.clock); has_clock!(|self: App|&self.clock);
has_clips!(|self: App|self.pool.as_ref().expect("no clip pool").clips); has_clips!(|self: App|self.pool.as_ref().expect("no clip pool").clips);
has_clock!(|self:Track|self.player.clock()); //has_editor!(|self: App|self.editor.as_ref().expect("no editor"));
has_player!(|self:Track|self.player); impl HasJack for App {
has_editor!(|self: App|self.editor.as_ref().expect("no editor")); fn jack (&self) -> &Arc<RwLock<JackConnection>> { &self.jack }
}
impl HasSampler for App {
fn sampler (&self) -> &Option<Sampler> { &self.sampler }
fn sampler_mut (&mut self) -> &mut Option<Sampler> { &mut self.sampler }
fn sample_index (&self) -> usize { self.editor.as_ref().map(|e|e.note_point()).unwrap_or(0) }
}
impl HasEditor for App {
fn editor (&self) -> &Option<MidiEditor> {
&self.editor
}
fn editor_mut (&mut self) -> &Option<MidiEditor> {
&mut self.editor
}
fn is_editing (&self) -> bool {
self.editing.load(Relaxed)
}
fn editor_w (&self) -> usize {
let editor = self.editor.as_ref().expect("missing editor");
(5 + (editor.time_len().get() / editor.time_zoom().get()))
.min(self.size.w().saturating_sub(20))
.max(16)
}
fn editor_h (&self) -> usize {
15
}
}
edn_provide!(u16: |self: App|{ edn_provide!(u16: |self: App|{
":sample-h" => if self.compact() { 0 } else { 5 }, ":sample-h" => if self.compact() { 0 } else { 5 },
":samples-w" => if self.compact() { 4 } else { 11 }, ":samples-w" => if self.compact() { 4 } else { 11 },
@ -68,28 +93,25 @@ edn_provide!(u16: |self: App|{
if w > 60 { 20 } else if w > 40 { 15 } else { 10 } if w > 60 { 20 } else if w > 40 { 15 } else { 10 }
} }
}); });
edn_provide!(Color: |self: App| { _ => return None }); edn_provide!(bool: |self: App| { _ => return None });
edn_provide!(usize: |self: App| { _ => return None }); edn_provide!(usize: |self: App| { _ => return None });
edn_provide!(isize: |self: App| { _ => return None }); edn_provide!(isize: |self: App| { _ => return None });
edn_provide!(bool: |self: App| { _ => return None }); edn_provide!(Color: |self: App| { _ => return None });
edn_provide!(Selection: |self: App| { _ => return None }); edn_provide!(Selection: |self: App| { _ => return None });
edn_provide!(Arc<RwLock<MidiClip>>: |self: App| { _ => return None }); edn_provide!(Arc<RwLock<MidiClip>>: |self: App| { _ => return None });
edn_provide!(Option<Arc<RwLock<MidiClip>>>: |self: App| { _ => return None }); edn_provide!(Option<Arc<RwLock<MidiClip>>>: |self: App| { _ => return None });
edn_provide!('a: Box<dyn Render<TuiOut> + 'a>: |self: App|{ edn_provide!('a: Box<dyn Render<TuiOut> + 'a>: |self: App|{
":editor" => (&self.editor).boxed(), ":editor" => (&self.editor).boxed(),
":pool" => self.pool.as_ref().map(|pool|Align::e(Fixed::x(self.sidebar_w(), PoolView(self.compact(), pool)))).boxed(), ":pool" => self.pool.as_ref().map(|pool|Align::e(Fixed::x(self.sidebar_w(), PoolView(self.compact(), pool)))).boxed(),
":sample" => self.sample().boxed(), ":sample" => self.view_sample(self.is_editing()).boxed(),
":sampler" => self.sampler().boxed(), ":sampler" => self.view_sampler(self.is_editing(), &self.editor).boxed(),
":status" => self.editor.as_ref().map(|e|Bsp::e(e.clip_status(), e.edit_status())).boxed(), ":status" => self.editor.as_ref().map(|e|Bsp::e(e.clip_status(), e.edit_status())).boxed(),
":toolbar" => Fill::x(Fixed::y(2, Align::x(ClockView::new(true, &self.clock)))).boxed(), ":toolbar" => Fill::x(Fixed::y(2, Align::x(ClockView::new(true, &self.clock)))).boxed(),
":tracks" => self.row(self.w(), 3, ":tracks" => self.row(self.w(), 3, self.track_header(), self.track_cells()).boxed(),
self.track_header(), self.track_cells()).boxed(), ":inputs" => self.row(self.w(), 3, self.input_header(), self.input_cells()).boxed(),
":inputs" => self.row(self.w(), 3, ":outputs" => self.row(self.w(), 3, self.output_header(), self.output_cells()).boxed(),
input_header(&self), input_cells(&self)).boxed(),
":outputs" => self.row(self.w(), 3,
output_header(&self), output_cells(&self)).boxed(),
":scenes" => self.row(self.w(), self.size.h().saturating_sub(9) as u16, ":scenes" => self.row(self.w(), self.size.h().saturating_sub(9) as u16,
self.scene_header(), self.scene_cells()).boxed(), self.scene_header(), self.scene_cells(self.is_editing())).boxed(),
}); });
render!(TuiOut: (self: App) => self.size.of(EdnView::from_source(self, self.edn.as_ref()))); render!(TuiOut: (self: App) => self.size.of(EdnView::from_source(self, self.edn.as_ref())));
handle!(TuiIn: |self: App, input| Ok(None)); handle!(TuiIn: |self: App, input| Ok(None));
@ -184,8 +206,10 @@ pub trait HasSelection {
/// Outputs of last device /// Outputs of last device
audio_outs: Vec<JackPort<AudioOut>>, audio_outs: Vec<JackPort<AudioOut>>,
/// Device chain /// Device chain
devices: Vec<Device>, devices: Vec<Box<dyn Device>>,
} }
has_clock!(|self: Track|self.player.clock());
has_player!(|self: Track|self.player);
impl Track { impl Track {
const MIN_WIDTH: usize = 9; const MIN_WIDTH: usize = 9;
fn longest_name (tracks: &[Self]) -> usize { fn longest_name (tracks: &[Self]) -> usize {
@ -204,14 +228,12 @@ impl HasTracks for App {
fn tracks (&self) -> &Vec<Track> { &self.tracks } fn tracks (&self) -> &Vec<Track> { &self.tracks }
fn tracks_mut (&mut self) -> &mut Vec<Track> { &mut self.tracks } fn tracks_mut (&mut self) -> &mut Vec<Track> { &mut self.tracks }
} }
pub trait HasTracks: HasSelection + HasClock + HasJack { pub trait HasTracks: HasSelection + HasClock + HasJack + HasEditor + Send + Sync {
fn tracks (&self) -> &Vec<Track>; fn tracks (&self) -> &Vec<Track>;
fn tracks_mut (&mut self) -> &mut Vec<Track>; fn tracks_mut (&mut self) -> &mut Vec<Track>;
fn tracks_sizes ( fn tracks_sizes <'a> (&'a self, editing: bool, bigger: usize)
&self, -> impl Iterator<Item=(usize, &'a Track, usize, usize)> + Send + Sync + 'a
editing: bool, {
bigger: usize
) -> impl Iterator<Item=(usize,&Track,usize,usize)> {
let mut x = 0; let mut x = 0;
let active = match self.selected() { let active = match self.selected() {
Selection::Track(t) if editing => Some(t), Selection::Track(t) if editing => Some(t),
@ -241,7 +263,7 @@ pub trait HasTracks: HasSelection + HasClock + HasJack {
)).boxed()).into() )).boxed()).into()
} }
fn track_cells <'a> (&'a self) -> BoxThunk<'a, TuiOut> { fn track_cells <'a> (&'a self) -> BoxThunk<'a, TuiOut> {
let iter = ||self.tracks_with_sizes(); let iter = ||self.tracks_sizes(self.is_editing(), self.editor_w());
(move||Align::x(Map::new(iter, move|(_, track, x1, x2), i| { (move||Align::x(Map::new(iter, move|(_, track, x1, x2), i| {
let name = Push::x(1, &track.name); let name = Push::x(1, &track.name);
let color = track.color; let color = track.color;
@ -256,7 +278,7 @@ pub trait HasTracks: HasSelection + HasClock + HasJack {
})).boxed()).into() })).boxed()).into()
} }
} }
trait Device {} trait Device: Send + Sync + std::fmt::Debug {}
impl Device for Sampler {} impl Device for Sampler {}
impl Device for Plugin {} impl Device for Plugin {}
#[derive(Debug, Default)] struct Scene { #[derive(Debug, Default)] struct Scene {
@ -321,15 +343,12 @@ impl HasScenes for App {
fn scenes (&self) -> &Vec<Scene> { &self.scenes } fn scenes (&self) -> &Vec<Scene> { &self.scenes }
fn scenes_mut (&mut self) -> &mut Vec<Scene> { &mut self.scenes } fn scenes_mut (&mut self) -> &mut Vec<Scene> { &mut self.scenes }
} }
pub trait HasScenes: HasSelection { pub trait HasScenes: HasSelection + HasEditor + Send + Sync {
fn scenes (&self) -> &Vec<Scene>; fn scenes (&self) -> &Vec<Scene>;
fn scenes_mut (&mut self) -> &mut Vec<Scene>; fn scenes_mut (&mut self) -> &mut Vec<Scene>;
fn scenes_sizes( fn scenes_sizes (&self, editing: bool, height: usize, larger: usize,)
&self, -> impl Iterator<Item = (usize, &Scene, usize, usize)> + Send + Sync
editing: bool, {
scene_height: usize,
scene_larger: usize,
) -> impl Iterator<Item = (usize, &Scene, usize, usize)> {
let mut y = 0; let mut y = 0;
let (selected_track, selected_scene) = match self.selected() { let (selected_track, selected_scene) = match self.selected() {
Selection::Clip(t, s) => (Some(t), Some(s)), Selection::Clip(t, s) => (Some(t), Some(s)),
@ -337,7 +356,7 @@ pub trait HasScenes: HasSelection {
}; };
self.scenes().iter().enumerate().map(move|(s, scene)|{ self.scenes().iter().enumerate().map(move|(s, scene)|{
let active = editing && selected_track.is_some() && selected_scene == Some(&s); let active = editing && selected_track.is_some() && selected_scene == Some(&s);
let height = if active { scene_larger } else { scene_height }; let height = if active { larger } else { height };
let data = (s, scene, y, y + height); let data = (s, scene, y, y + height);
y += height; y += height;
data data
@ -353,13 +372,14 @@ pub trait HasScenes: HasSelection {
self.selected().scene().and_then(|s|self.scenes_mut().get_mut(s)) self.selected().scene().and_then(|s|self.scenes_mut().get_mut(s))
} }
fn scene_del (&mut self, index: usize) { fn scene_del (&mut self, index: usize) {
todo!("delete scene"); self.selected().scene().and_then(|s|Some(self.scenes_mut().remove(index)));
} }
fn scene_header <'a> (&'a self) -> BoxThunk<'a, TuiOut> { fn scene_header <'a> (&'a self) -> BoxThunk<'a, TuiOut> {
(||{ (move||{
let last_color = Arc::new(RwLock::new(ItemPalette::from(Color::Rgb(0, 0, 0)))); let last_color = Arc::new(RwLock::new(ItemPalette::from(Color::Rgb(0, 0, 0))));
let selected = self.selected().scene(); let selected = self.selected().scene();
Fill::y(Align::c(Map::new(||self.scenes_with_sizes(2), move|(_, scene, y1, y2), i| { let iter = ||self.scenes_sizes(self.is_editing(), 2, 15);
Fill::y(Align::c(Map::new(iter, move|(_, scene, y1, y2), i| {
let h = (y2 - y1) as u16; let h = (y2 - y1) as u16;
let name = format!("🭬{}", &scene.name); let name = format!("🭬{}", &scene.name);
let color = scene.color; let color = scene.color;
@ -431,27 +451,8 @@ impl App {
} }
fn compact (&self) -> bool { false } fn compact (&self) -> bool { false }
fn editor (&self) -> impl Content<TuiOut> + '_ { &self.editor } fn editor (&self) -> impl Content<TuiOut> + '_ { &self.editor }
fn w (&self) -> u16 { self.tracks_with_sizes().last().map(|x|x.3 as u16).unwrap_or(0) } fn w (&self) -> u16 { self.tracks_sizes(self.is_editing(), self.editor_w()).last().map(|x|x.3 as u16).unwrap_or(0) }
fn pool (&self) -> impl Content<TuiOut> + use<'_> { self.pool.as_ref().map(|pool|Align::e(Fixed::x(self.sidebar_w(), PoolView(self.compact(), pool)))) } fn pool (&self) -> impl Content<TuiOut> + use<'_> { self.pool.as_ref().map(|pool|Align::e(Fixed::x(self.sidebar_w(), PoolView(self.compact(), pool)))) }
fn sample <'a> (&'a self) -> impl Content<TuiOut> + 'a {
let compact = self.is_editing();
if let (Some(editor), Some(sampler)) = (&self.editor, &self.sampler) {
let note_pt = editor.note_point();
let sample_h = if compact { 0 } else { 5 };
return Some(Max::y(sample_h, Fill::x(sampler.viewer(note_pt))))
}
None
}
fn sampler (&self) -> impl Content<TuiOut> + use<'_> {
let compact = self.is_editing();
if let (Some(editor), Some(sampler)) = (&self.editor, &self.sampler) {
let note_pt = editor.note_point();
let w = if compact { 4 } else { 40 };
let y = if compact { 1 } else { 0 };
return Some(Fixed::x(w, Push::y(y, Fill::y(sampler.list(compact, editor)))))
}
None
}
fn row <'a> ( fn row <'a> (
&'a self, w: u16, h: u16, a: impl Content<TuiOut> + 'a, b: impl Content<TuiOut> + 'a &'a self, w: u16, h: u16, a: impl Content<TuiOut> + 'a, b: impl Content<TuiOut> + 'a
) -> impl Content<TuiOut> + 'a { ) -> impl Content<TuiOut> + 'a {
@ -460,27 +461,69 @@ impl App {
Fill::x(Align::c(Fixed::xy(w, h, b))) Fill::x(Align::c(Fixed::xy(w, h, b)))
)) ))
} }
fn tracks_with_sizes (&self) -> impl Iterator<Item = (usize, &Track, usize, usize)> {
self.tracks_sizes(self.is_editing(), self.editor_w())
}
fn scenes_with_sizes (&self, h: usize) -> impl Iterator<Item = (usize, &Scene, usize, usize)> {
self.scenes_sizes(self.is_editing(), 2, 15)
}
fn is_editing (&self) -> bool { fn is_editing (&self) -> bool {
self.editing.load(Relaxed) self.editing.load(Relaxed)
} }
fn editor_w (&self) -> usize {
let editor = self.editor.as_ref().expect("missing editor");
(5 + (editor.time_len().get() / editor.time_zoom().get()))
.min(self.size.w().saturating_sub(20))
.max(16)
}
fn sidebar_w (&self) -> u16 { fn sidebar_w (&self) -> u16 {
let w = self.size.w(); let w = self.size.w();
let w = if w > 60 { 20 } else if w > 40 { 15 } else { 10 }; let w = if w > 60 { 20 } else if w > 40 { 15 } else { 10 };
let w = if self.is_editing() { 8 } else { w }; let w = if self.is_editing() { 8 } else { w };
w w
} }
fn input_header <'a> (&'a self) -> BoxThunk<'a, TuiOut> {
let fg = TuiTheme::g(224);
let bg = TuiTheme::g(64);
(move||Bsp::s(help_tag("midi ", "I", "ns"), self.midi_ins.get(0).map(|inp|Bsp::s(
Fill::x(Tui::bold(true, Tui::fg_bg(fg, bg, Align::w(inp.name.clone())))),
inp.connect.get(0).map(|connect|Fill::x(Align::w(Tui::bold(false,
Tui::fg_bg(fg, bg, connect.info()))))),
))).boxed()).into()
}
fn input_cells <'a> (&'a self) -> BoxThunk<'a, TuiOut> {
(move||Align::x(Map::new(||self.tracks_sizes(self.is_editing(), self.editor_w()), move|(_, track, x1, x2), i| {
let w = (x2 - x1) as u16;
let color: ItemPalette = track.color.dark.into();
map_east(x1 as u16, w, Fixed::x(w, cell(color, Bsp::n(
Self::rec_mon(color.base.rgb, false, false),
phat_hi(color.base.rgb, color.dark.rgb)
))))
})).boxed()).into()
}
fn rec_mon (bg: Color, rec: bool, mon: bool) -> impl Content<TuiOut> {
row!(
Tui::fg_bg(if rec { Color::Red } else { bg }, bg, ""),
Tui::fg_bg(if rec { Color::White } else { Color::Rgb(0,0,0) }, bg, "REC"),
Tui::fg_bg(if rec { Color::White } else { bg }, bg, ""),
Tui::fg_bg(if mon { Color::White } else { Color::Rgb(0,0,0) }, bg, "MON"),
Tui::fg_bg(if mon { Color::White } else { bg }, bg, ""),
)
}
fn output_header <'a> (&'a self) -> BoxThunk<'a, TuiOut> {
let fg = TuiTheme::g(224);
let bg = TuiTheme::g(64);
(move||Bsp::s(help_tag("midi ", "O", "uts"), self.midi_outs.get(0).map(|out|Bsp::s(
Fill::x(Tui::bold(true, Tui::fg_bg(fg, bg, Align::w(out.name.clone())))),
out.connect.get(0).map(|connect|Fill::x(Align::w(Tui::bold(false,
Tui::fg_bg(fg, bg, connect.info()))))),
))).boxed()).into()
}
fn output_cells <'a> (&'a self) -> BoxThunk<'a, TuiOut> {
(move||Align::x(Map::new(||self.tracks_sizes(self.is_editing(), self.editor_w()), move|(_, track, x1, x2), i| {
let w = (x2 - x1) as u16;
let color: ItemPalette = track.color.dark.into();
map_east(x1 as u16, w, Fixed::x(w, cell(color, Bsp::n(
Self::mute_solo(color.base.rgb, false, false),
phat_hi(color.dark.rgb, color.darker.rgb)
))))
})).boxed()).into()
}
fn mute_solo (bg: Color, mute: bool, solo: bool) -> impl Content<TuiOut> {
row!(
Tui::fg_bg(if mute { Color::White } else { Color::Rgb(0,0,0) }, bg, "MUTE"),
Tui::fg_bg(if mute { Color::White } else { bg }, bg, ""),
Tui::fg_bg(if solo { Color::White } else { Color::Rgb(0,0,0) }, bg, "SOLO"),
)
}
} }
#[derive(Clone, Debug)] pub enum AppCommand { #[derive(Clone, Debug)] pub enum AppCommand {
Clear, Clear,
@ -777,44 +820,6 @@ fn help_tag <'a> (before: &'a str, key: &'a str, after: &'a str) -> impl Content
let hi = TuiTheme::orange(); let hi = TuiTheme::orange();
Tui::bold(true, row!(Tui::fg(lo, before), Tui::fg(hi, key), Tui::fg(lo, after))) Tui::bold(true, row!(Tui::fg(lo, before), Tui::fg(hi, key), Tui::fg(lo, after)))
} }
fn input_header <'a> (state: &'a App) -> BoxThunk<'a, TuiOut> {
let fg = TuiTheme::g(224);
let bg = TuiTheme::g(64);
(move||Bsp::s(help_tag("midi ", "I", "ns"), state.midi_ins.get(0).map(|inp|Bsp::s(
Fill::x(Tui::bold(true, Tui::fg_bg(fg, bg, Align::w(inp.name.clone())))),
inp.connect.get(0).map(|connect|Fill::x(Align::w(Tui::bold(false,
Tui::fg_bg(fg, bg, connect.info()))))),
))).boxed()).into()
}
fn input_cells <'a> (state: &'a App) -> BoxThunk<'a, TuiOut> {
(move||Align::x(Map::new(||state.tracks_with_sizes(), move|(_, track, x1, x2), i| {
let w = (x2 - x1) as u16;
let color: ItemPalette = track.color.dark.into();
map_east(x1 as u16, w, Fixed::x(w, cell(color, Bsp::n(
rec_mon(color.base.rgb, false, false),
phat_hi(color.base.rgb, color.dark.rgb)
))))
})).boxed()).into()
}
fn output_header <'a> (state: &'a App) -> BoxThunk<'a, TuiOut> {
let fg = TuiTheme::g(224);
let bg = TuiTheme::g(64);
(move||Bsp::s(help_tag("midi ", "O", "uts"), state.midi_outs.get(0).map(|out|Bsp::s(
Fill::x(Tui::bold(true, Tui::fg_bg(fg, bg, Align::w(out.name.clone())))),
out.connect.get(0).map(|connect|Fill::x(Align::w(Tui::bold(false,
Tui::fg_bg(fg, bg, connect.info()))))),
))).boxed()).into()
}
fn output_cells <'a> (state: &'a App) -> BoxThunk<'a, TuiOut> {
(move||Align::x(Map::new(||state.tracks_with_sizes(), move|(_, track, x1, x2), i| {
let w = (x2 - x1) as u16;
let color: ItemPalette = track.color.dark.into();
map_east(x1 as u16, w, Fixed::x(w, cell(color, Bsp::n(
mute_solo(color.base.rgb, false, false),
phat_hi(color.dark.rgb, color.darker.rgb)
))))
})).boxed()).into()
}
fn cell_clip <'a> ( fn cell_clip <'a> (
scene: &'a Scene, index: usize, track: &'a Track, w: u16, h: u16 scene: &'a Scene, index: usize, track: &'a Track, w: u16, h: u16
) -> impl Content<TuiOut> + use<'a> { ) -> impl Content<TuiOut> + use<'a> {
@ -833,36 +838,24 @@ fn cell_clip <'a> (
Fixed::xy(w, h, &Tui::bg(bg, Push::x(1, Fixed::x(w, &name.as_str()[0..max_w])))); Fixed::xy(w, h, &Tui::bg(bg, Push::x(1, Fixed::x(w, &name.as_str()[0..max_w]))));
})) }))
} }
fn rec_mon (bg: Color, rec: bool, mon: bool) -> impl Content<TuiOut> {
row!(
Tui::fg_bg(if rec { Color::Red } else { bg }, bg, ""),
Tui::fg_bg(if rec { Color::White } else { Color::Rgb(0,0,0) }, bg, "REC"),
Tui::fg_bg(if rec { Color::White } else { bg }, bg, ""),
Tui::fg_bg(if mon { Color::White } else { Color::Rgb(0,0,0) }, bg, "MON"),
Tui::fg_bg(if mon { Color::White } else { bg }, bg, ""),
)
}
fn mute_solo (bg: Color, mute: bool, solo: bool) -> impl Content<TuiOut> {
row!(
Tui::fg_bg(if mute { Color::White } else { Color::Rgb(0,0,0) }, bg, "MUTE"),
Tui::fg_bg(if mute { Color::White } else { bg }, bg, ""),
Tui::fg_bg(if solo { Color::White } else { Color::Rgb(0,0,0) }, bg, "SOLO"),
)
}
fn cell <T: Content<TuiOut>> (color: ItemPalette, field: T) -> impl Content<TuiOut> { fn cell <T: Content<TuiOut>> (color: ItemPalette, field: T) -> impl Content<TuiOut> {
Tui::fg_bg(color.lightest.rgb, color.base.rgb, Fixed::y(1, field)) Tui::fg_bg(color.lightest.rgb, color.base.rgb, Fixed::y(1, field))
} }
impl Arrangement for App {} impl<T> Arrangement for T where T:
pub trait Arrangement: HasTracks + HasScenes + HasSelection + HasClock + HasJack { HasEditor + HasTracks + HasScenes + HasSelection + HasClock + HasJack {}
fn track_add (&mut self, name: Option<&str>, color: Option<ItemPalette>) pub trait Arrangement: HasEditor + HasTracks + HasScenes + HasSelection + HasClock + HasJack {
-> Usually<&mut Track> fn track_add (
{ &mut self,
name: Option<&str>,
color: Option<ItemPalette>
) -> Usually<&mut Track> {
let name = name.map_or_else(||self.track_next_name(), |x|x.to_string().into()); let name = name.map_or_else(||self.track_next_name(), |x|x.to_string().into());
let track = Track { let track = Track {
width: (name.len() + 2).max(9), width: (name.len() + 2).max(9),
color: color.unwrap_or_else(ItemPalette::random), color: color.unwrap_or_else(ItemPalette::random),
player: MidiPlayer::from(self.clock()), player: MidiPlayer::from(self.clock()),
name, name,
..Default::default()
}; };
self.tracks_mut().push(track); self.tracks_mut().push(track);
let len = self.tracks().len(); let len = self.tracks().len();
@ -876,8 +869,8 @@ pub trait Arrangement: HasTracks + HasScenes + HasSelection + HasClock + HasJack
} }
fn tracks_add ( fn tracks_add (
&mut self, &mut self,
count: usize, count: usize,
width: usize, width: usize,
midi_from: &[PortConnection], midi_from: &[PortConnection],
midi_to: &[PortConnection], midi_to: &[PortConnection],
) -> Usually<()> { ) -> Usually<()> {
@ -923,12 +916,11 @@ pub trait Arrangement: HasTracks + HasScenes + HasSelection + HasClock + HasJack
} }
Ok(()) Ok(())
} }
fn scene_cells <'a> (&'a self) -> BoxThunk<'a, TuiOut> { fn scene_cells <'a> (&'a self, editing: bool) -> BoxThunk<'a, TuiOut> {
let editing = self.is_editing(); let tracks = move||self.tracks_sizes(self.is_editing(), self.editor_w());
let tracks = move||self.tracks_with_sizes(); let scenes = ||self.scenes_sizes(self.is_editing(), 2, 15);
let scenes = ||self.scenes_with_sizes(2); let selected_track = self.selected().track();
let selected_track = self.selected.track(); let selected_scene = self.selected().scene();
let selected_scene = self.selected.scene();
(move||Fill::y(Align::c(Map::new(tracks, move|(_, track, x1, x2), t| { (move||Fill::y(Align::c(Map::new(tracks, move|(_, track, x1, x2), t| {
let w = (x2 - x1) as u16; let w = (x2 - x1) as u16;
let color: ItemPalette = track.color.dark.into(); let color: ItemPalette = track.color.dark.into();
@ -944,7 +936,7 @@ pub trait Arrangement: HasTracks + HasScenes + HasSelection + HasClock + HasJack
}; };
let last = last_color.read().unwrap().clone(); let last = last_color.read().unwrap().clone();
let active = editing && selected_scene == Some(s) && selected_track == Some(t); let active = editing && selected_scene == Some(s) && selected_track == Some(t);
let editor = Thunk::new(||&self.editor); let editor = Thunk::new(||self.editor());
let cell = Thunk::new(move||phat_sel_3( let cell = Thunk::new(move||phat_sel_3(
selected_track == Some(t) && selected_scene == Some(s), selected_track == Some(t) && selected_scene == Some(s),
Tui::fg(fg, Push::x(1, Tui::bold(true, name.to_string()))), Tui::fg(fg, Push::x(1, Tui::bold(true, name.to_string()))),