wip: fixing Map, centering

This commit is contained in:
🪞👃🪞 2025-01-01 17:00:28 +01:00
parent 059ff2ca79
commit d17d20e7db
10 changed files with 104 additions and 78 deletions

View file

@ -90,6 +90,9 @@ pub trait Area<N: Coordinate> {
Ok(self) Ok(self)
} }
} }
#[inline] fn xy (&self) -> [N;2] {
[self.x(), self.y()]
}
#[inline] fn wh (&self) -> [N;2] { #[inline] fn wh (&self) -> [N;2] {
[self.w(), self.h()] [self.w(), self.h()]
} }
@ -121,7 +124,10 @@ pub trait Area<N: Coordinate> {
[self.x(), self.x2(), self.y(), self.y2()] [self.x(), self.x2(), self.y(), self.y2()]
} }
#[inline] fn center (&self) -> [N;2] { #[inline] fn center (&self) -> [N;2] {
[self.x() + self.w() / 2.into(), self.y() + self.h() / 2.into()] [self.x() + self.w()/2.into(), self.y() + self.h()/2.into()]
}
#[inline] fn centered (&self) -> [N;2] {
[self.x().minus(self.w()/2.into()), self.y().minus(self.h()/2.into())]
} }
fn zero () -> [N;4] { fn zero () -> [N;4] {
[N::zero(), N::zero(), N::zero(), N::zero()] [N::zero(), N::zero(), N::zero(), N::zero()]

View file

@ -30,16 +30,13 @@ pub trait Content<E: Engine>: Send + Sync {
} }
} }
/// The platonic ideal item of content: emptiness at dead center. /// The platonic ideal unit of [Content]: total emptiness at dead center.
impl<E: Engine> Content<E> for () { impl<E: Engine> Content<E> for () {
fn layout (&self, area: E::Area) -> E::Area { fn layout (&self, area: E::Area) -> E::Area {
let [x, y, w, h] = area.xywh(); let [x, y] = area.center();
let x = x + w / 2.into();
let y = y + h / 2.into();
[x, y, 0.into(), 0.into()].into() [x, y, 0.into(), 0.into()].into()
} }
fn render (&self, _: &mut E::Output) { fn render (&self, _: &mut E::Output) {}
}
} }
impl<E: Engine, T: Content<E>> Content<E> for &T { impl<E: Engine, T: Content<E>> Content<E> for &T {

View file

@ -29,13 +29,13 @@ impl<E: Engine, T: Content<E>> Content<E> for Align<E, T> {
} }
pub fn align_areas<N: Coordinate>(alignment: Alignment, on: [N;4], it: [N;4]) -> [N;4] { pub fn align_areas<N: Coordinate>(alignment: Alignment, on: [N;4], it: [N;4]) -> [N;4] {
let [cfx, cfy] = on.center(); let [cfx, cfy, ..] = on.center();
let [cmx, cmy] = it.center(); let [cmx, cmy, ..] = it.center();
let center = |cf, cm, m: N|if cf >= cm { m + (cf - cm) } else { m.minus(cm - cf) }; let center = |cf, cm, m: N|if cf >= cm { m + (cf - cm) } else { m.minus(cm - cf) };
let center_x = center(cfx, cmx, it.x()); let center_x = center(cfx, cmx, it.x());
let center_y = center(cfy, cmy, it.y()); let center_y = center(cfy, cmy, it.y());
let east_x = on.x() + on.w().minus(it.w()); let east_x = on.x() + on.w().minus(it.w());
let south_y = on.y() + on.h().minus(it.h()); let south_y = on.y() + on.h().minus(it.h());
let [x, y] = match alignment { let [x, y] = match alignment {
Alignment::Center => [center_x, center_y,], Alignment::Center => [center_x, center_y,],

View file

@ -13,11 +13,13 @@ pub(crate) use ::tek_engine::*;
pub(crate) use std::marker::PhantomData; pub(crate) use std::marker::PhantomData;
#[cfg(test)] #[test] fn test_layout () -> Usually<()> { #[cfg(test)] #[test] fn test_layout () -> Usually<()> {
use crate::tui::Tui;
let area: [u16;4] = [10, 10, 20, 20]; let area: [u16;4] = [10, 10, 20, 20];
let unit = (); let unit = ();
//assert_eq!(().layout(area), [15, 15, 0, 0]); // should be independent over E, isn't // should be independent over E, isn't
assert_eq!(Fill::x(()).layout(area), [10, 15, 20, 0]); assert_eq!(Content::<Tui>::layout(&unit, area), [15, 15, 0, 0]);
assert_eq!(Fill::y(()).layout(area), [15, 10, 0, 20]); assert_eq!(Fill::<Tui, _>::x(unit).layout(area), [10, 15, 20, 0]);
assert_eq!(Fill::xy(()).layout(area), area); assert_eq!(Fill::<Tui, _>::y(unit).layout(area), [15, 10, 0, 20]);
assert_eq!(Fill::<Tui, _>::xy(unit).layout(area), area);
Ok(()) Ok(())
} }

View file

@ -21,13 +21,14 @@ pub trait Layout<E: Engine> {
{ {
Opt(option, cb, Default::default()) Opt(option, cb, Default::default())
} }
fn map <T, I, R, F>(iterator: I, callback: F) -> Map<E, T, I, R, F> where fn map <T, I, J, R, F>(iterator: J, callback: F) -> Map<E, T, I, J, R, F> where
E: Engine, E: Engine,
I: Iterator<Item = T> + Send + Sync, I: Iterator<Item = T> + Send + Sync,
J: Fn() -> I + Send + Sync,
R: Content<E>, R: Content<E>,
F: Fn(T, usize)->R + Send + Sync F: Fn(T, usize)->R + Send + Sync
{ {
Map(Default::default(), RwLock::new(iterator), callback) Map(Default::default(), iterator, callback)
} }
//pub fn reduce <E, T, I, R, F>(iterator: I, callback: F) -> Reduce<E, T, I, R, F> where //pub fn reduce <E, T, I, R, F>(iterator: I, callback: F) -> Reduce<E, T, I, R, F> where
//E: Engine, //E: Engine,
@ -77,21 +78,39 @@ impl<E: Engine, A: Content<E>, B: Content<E>> Content<E> for Either<E, A, B> {
} }
} }
pub struct Map<E, T, I, R, F>(PhantomData<E>, RwLock<I>, F) where pub struct Map<E, T, I, J, R, F>(PhantomData<E>, J, F) where
E: Engine, E: Engine,
I: Iterator<Item = T> + Send + Sync, I: Iterator<Item = T> + Send + Sync,
J: Fn()->I + Send + Sync,
R: Content<E>, R: Content<E>,
F: Fn(T, usize)->R + Send + Sync; F: Fn(T, usize)->R + Send + Sync;
impl<E, T, I, R, F> Content<E> for Map<E, T, I, R, F> where impl<E, T, I, J, R, F> Content<E> for Map<E, T, I, J, R, F> where
E: Engine, E: Engine,
I: Iterator<Item = T> + Send + Sync, I: Iterator<Item = T> + Send + Sync,
J: Fn()->I + Send + Sync,
R: Content<E>, R: Content<E>,
F: Fn(T, usize)->R + Send + Sync F: Fn(T, usize)->R + Send + Sync
{ {
fn layout (&self, area: E::Area) -> E::Area {
let mut index = 0;
let mut max_w = 0;
let mut max_h = 0;
for item in (self.1)() {
let [x, y, w, h] = (self.2)(item, index).layout(area).xywh();
max_w = max_w.max((x + w).into());
max_h = max_h.max((y + h).into());
index += 1;
}
align_areas(
Alignment::Center,
area.xywh(),
[0.into(), 0.into(), max_w.into(), max_h.into()]
).into()
}
fn render (&self, to: &mut E::Output) { fn render (&self, to: &mut E::Output) {
let mut index = 0; let mut index = 0;
for item in &mut*self.1.write().unwrap() { for item in (self.1)() {
(self.2)(item, index).render(to); (self.2)(item, index).render(to);
index += 1; index += 1;
} }

View file

@ -19,7 +19,7 @@ impl<'a> ArrangerVClips<'a> {
} }
impl<'a> Content<Tui> for ArrangerVClips<'a> { impl<'a> Content<Tui> for ArrangerVClips<'a> {
fn content (&self) -> impl Content<Tui> { fn content (&self) -> impl Content<Tui> {
let iter = self.scenes.iter().zip(self.rows.iter().map(|row|row.0)); let iter = ||self.scenes.iter().zip(self.rows.iter().map(|row|row.0));
let col = Tui::map(iter, |(scene, pulses), i|Self::format_scene(self.tracks, scene, pulses)); let col = Tui::map(iter, |(scene, pulses), i|Self::format_scene(self.tracks, scene, pulses));
Fill::xy(col) Fill::xy(col)
} }
@ -37,7 +37,7 @@ impl<'a> ArrangerVClips<'a> {
let name = Tui::fg_bg(scene.color.lightest.rgb, scene.color.base.rgb, let name = Tui::fg_bg(scene.color.lightest.rgb, scene.color.base.rgb,
Expand::x(1, Tui::bold(true, scene.name.read().unwrap().clone())) Expand::x(1, Tui::bold(true, scene.name.read().unwrap().clone()))
); );
let clips = Tui::map(ArrangerTrack::with_widths(tracks), move|(index, track, x1, x2), _| let clips = Tui::map(||ArrangerTrack::with_widths(tracks), move|(index, track, x1, x2), _|
Push::x((x2 - x1) as u16, Self::format_clip(scene, index, track, (x2 - x1) as u16, height)) Push::x((x2 - x1) as u16, Self::format_clip(scene, index, track, (x2 - x1) as u16, height))
); );
Fixed::y(height, row!(icon, name, clips)) Fixed::y(height, row!(icon, name, clips))

View file

@ -20,7 +20,7 @@ render!(Tui: (self: ArrangerVHead<'a>) => {
row!(Tui::fg(color.light.rgb, ""), Tui::fg(color.lightest.rgb, field)) row!(Tui::fg(color.light.rgb, ""), Tui::fg(color.lightest.rgb, field))
} }
Some(Push::x(self.scenes_w, Some(Push::x(self.scenes_w,
Tui::map(ArrangerTrack::with_widths(self.tracks), |(_, track, x1, x2), i| { Tui::map(||ArrangerTrack::with_widths(self.tracks), |(_, track, x1, x2), i| {
let (w, h) = (ArrangerTrack::MIN_WIDTH.max(x2 - x1), HEADER_H); let (w, h) = (ArrangerTrack::MIN_WIDTH.max(x2 - x1), HEADER_H);
let color = track.color(); let color = track.color();
let input = Self::format_input(track); let input = Self::format_input(track);

View file

@ -128,7 +128,7 @@ render!(Tui: (self: Groovebox) => {
PhraseSelector::play_phrase(&self.player), PhraseSelector::play_phrase(&self.player),
PhraseSelector::next_phrase(&self.player), PhraseSelector::next_phrase(&self.player),
))); )));
"tabula rasa" PoolView(&self.pool)
//let pool = PoolView(&self.pool); //let pool = PoolView(&self.pool);
//let with_pool = move|x|Bsp::w(Fixed::x(pool_w, Pull::y(1, Fill::y(Align::e(pool)))), x); //let with_pool = move|x|Bsp::w(Fixed::x(pool_w, Pull::y(1, Fill::y(Align::e(pool)))), x);
//with_pool(col!(transport, selector)) //with_pool(col!(transport, selector))
@ -182,7 +182,8 @@ render!(Tui: (self: GrooveboxSamples<'a>) => {
let note_lo = self.0.editor.note_lo().load(Relaxed); let note_lo = self.0.editor.note_lo().load(Relaxed);
let note_pt = self.0.editor.note_point(); let note_pt = self.0.editor.note_point();
let note_hi = self.0.editor.note_hi(); let note_hi = self.0.editor.note_hi();
Fill::xy(Tui::map((note_lo..=note_hi).rev(), move|note, i| { let range = move||(note_lo..=note_hi).rev();
Fill::xy(Tui::map(range, move|note, i| {
let mut bg = if note == note_pt { TuiTheme::g(64) } else { Color::Reset }; let mut bg = if note == note_pt { TuiTheme::g(64) } else { Color::Reset };
let mut fg = TuiTheme::g(160); let mut fg = TuiTheme::g(160);
if let Some((index, _)) = self.0.sampler.recording { if let Some((index, _)) = self.0.sampler.recording {

View file

@ -209,49 +209,54 @@ pub struct PoolView<'a>(pub(crate) &'a PoolModel);
// TODO: Display phrases always in order of appearance // TODO: Display phrases always in order of appearance
render!(Tui: (self: PoolView<'a>) => { render!(Tui: (self: PoolView<'a>) => {
let PoolModel { phrases, mode, .. } = self.0; let PoolModel { phrases, mode, .. } = self.0;
let bg = TuiTheme::g(32); let bg = TuiTheme::g(32);
let title_color = TuiTheme::ti1(); let title_color = TuiTheme::ti1();
let upper_left = "Pool:"; let upper_left = "Pool:";
let upper_right = format!("({})", phrases.len()); let upper_right = format!("({})", phrases.len());
let color = self.0.phrase().read().unwrap().color; let color = self.0.phrase().read().unwrap().color;
let border = Fill::xy(Outer(Style::default().fg(color.base.rgb).bg(bg))); let with_files = |x|Tui::either(self.0.file_picker().is_some(),
let enclose = |x|lay!(border, Padding::xy(0, 1, Tui::bg(bg, x)));
let content = Tui::either(
self.0.file_picker().is_some(),
Thunk::new(||self.0.file_picker().unwrap()), Thunk::new(||self.0.file_picker().unwrap()),
Thunk::new(||Tui::map(phrases.iter(), |phrase, i|{ Thunk::new(x));
let MidiClip { ref name, color, length, .. } = *phrase.read().unwrap(); let content = with_files(||Tui::map(||phrases.iter(), |clip, i|{
let mut length = PhraseLength::new(length, None); let MidiClip { ref name, color, length, .. } = *clip.read().unwrap();
if let Some(PoolMode::Length(phrase, new_length, focus)) = self.0.mode { //let mut length = PhraseLength::new(length, None);
if i == phrase { //if let Some(PoolMode::Length(clip, new_length, focus)) = self.0.mode {
length.pulses = new_length; //if i == clip {
length.focus = Some(focus); //length.pulses = new_length;
} //length.focus = Some(focus);
} //}
let clip = Tui::bg(color.base.rgb, Fill::x(col!( //}
Fill::x(lay!( Push::y(1 + i as u16 * 2, Fill::x(Fixed::y(2, Tui::bg(color.base.rgb,
Fill::x(Align::w(format!(" {i}"))), format!(" {i} {name} {length} ")))))/*,
Fill::x(Align::e(Pull::x(1, length.clone()))), name.clone()))))Bsp::s(
)), Fill::x(Bsp::a(
Tui::bold(true, { Align::w(format!(" {i}")),
let mut row2 = format!(" {name}"); Align::e(Pull::x(1, length)),
if let Some(PoolMode::Rename(phrase, _)) = self.0.mode { )),
if i == phrase { Tui::bold(true, {
row2 = format!("{row2}"); let mut row2 = format!(" {name}");
} if let Some(PoolMode::Rename(clip, _)) = self.0.mode {
}; if i == clip {
row2 row2 = format!("{row2}");
}), }
))); };
Push::y(i as u16 * 2, lay!(clip, Tui::when(i == self.0.phrase_index(), CORNERS))) row2
}))); }),
enclose(lay!( ))))//lay!(clip, Tui::when(i == self.0.clip_index(), CORNERS)))*/
//add(&Lozenge(Style::default().bg(border_bg).fg(border_color)))?; }));
content, content
Fill::x(Align::nw(Push::x(1, Tui::fg(title_color, upper_left.to_string())))), //let border = Outer(Style::default().fg(color.base.rgb).bg(color.dark.rgb));
Fill::x(Align::ne(Pull::x(1, Tui::fg(title_color, upper_right.to_string())))), //let enclose = |x|lay!(
self.0.size.clone() //Fill::xy(border),
)) //Padding::xy(0, 1, Tui::bg(bg, x))
//);
//enclose(lay!(
////add(&Lozenge(Style::default().bg(border_bg).fg(border_color)))?;
//Fill::xy(content),
//Fill::x(Align::nw(Push::x(1, Tui::fg(title_color, upper_left.to_string())))),
//Fill::x(Align::ne(Pull::x(1, Tui::fg(title_color, upper_right.to_string())))),
//self.0.size.clone()
//))
}); });
command!(|self: FileBrowserCommand, state: PoolModel|{ command!(|self: FileBrowserCommand, state: PoolModel|{
use PoolMode::*; use PoolMode::*;

View file

@ -23,10 +23,11 @@ from_jack!(|jack|TransportTui Self {
has_clock!(|self: TransportTui|&self.clock); has_clock!(|self: TransportTui|&self.clock);
audio!(|self: TransportTui, client, scope|ClockAudio(self).process(client, scope)); audio!(|self: TransportTui, client, scope|ClockAudio(self).process(client, scope));
handle!(<Tui>|self: TransportTui, from|TransportCommand::execute_with_state(self, from)); handle!(<Tui>|self: TransportTui, from|TransportCommand::execute_with_state(self, from));
render!(Tui: (self: TransportTui) => Align::x(Fixed::y(3, row!( render!(Tui: (self: TransportTui) => PlayPause(false));
/*Align::x(Fixed::y(3, row!(
Fixed::x(5, Fixed::y(3, PlayPause(false))), Fixed::x(5, Fixed::y(3, PlayPause(false))),
TransportView::new(self, Some(self.color), true), TransportView::new(self, Some(self.color), true),
)))); ))));*/
impl std::fmt::Debug for TransportTui { impl std::fmt::Debug for TransportTui {
fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
f.debug_struct("TransportTui") f.debug_struct("TransportTui")
@ -111,14 +112,9 @@ render!(Tui: (self: TransportView) => {
pub struct PlayPause(pub bool); pub struct PlayPause(pub bool);
render!(Tui: (self: PlayPause) => Tui::bg( render!(Tui: (self: PlayPause) => Tui::bg(
if self.0{Color::Rgb(0,128,0)}else{Color::Rgb(128,64,0)}, if self.0{Color::Rgb(0,128,0)}else{Color::Rgb(128,64,0)},
Fixed::x(5, Tui::either(self.0, Tui::fg(Color::Rgb(0, 255, 0), col!( Fixed::x(5, Tui::either(self.0,
" 🭍🭑🬽 ", Tui::fg(Color::Rgb(0, 255, 0), Bsp::s(" 🭍🭑🬽 ", " 🭞🭜🭘 ",)),
" 🭞🭜🭘 ", Tui::fg(Color::Rgb(255, 128, 0), Bsp::s(" ▗▄▖ ", " ▝▀▘ ",))))));
)), Tui::fg(Color::Rgb(255, 128, 0), col!(
" ▗▄▖ ",
" ▝▀▘ ",
))))
));
impl HasFocus for TransportTui { impl HasFocus for TransportTui {
type Item = TransportFocus; type Item = TransportFocus;
fn focused (&self) -> Self::Item { fn focused (&self) -> Self::Item {