From b20ebbd7bed5f5a6f09cfe68058999bd9999e0c2 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Thu, 2 Jan 2025 23:39:11 +0100 Subject: [PATCH 001/652] update screenshot --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 710719a0..40708f51 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ for [jack](https://jackaudio.org/) and [pipewire](https://www.pipewire.org/). hmu on [**mastodon** `@unspeaker@mastodon.social`](https://mastodon.social/@unspeaker) or [**matrix** `@unspeaker:matrix.org`](https://matrix.to/#/@unspeaker:matrix.org) -![Screenshot](https://codeberg.org/unspeaker/tek/attachments/549efab7-f46b-438b-9508-cd499d044b41) +![Screenshot](https://codeberg.org/unspeaker/tek/releases/download/0.2.0-rc.7/Screenshot%20From%202025-01-02%2023-18-05.png) this codebase produces the following binaries: From 83f840a412bfd42e6c6637b73d9c043737dac64b Mon Sep 17 00:00:00 2001 From: unspeaker Date: Fri, 3 Jan 2025 00:44:00 +0100 Subject: [PATCH 002/652] improve ui legibility immensely right after release --- src/field.rs | 35 ++++++++++++--------------------- src/groovebox/groovebox_tui.rs | 36 +++++++++++++++------------------- src/midi/midi_status.rs | 20 ++++++++----------- src/pool/clip_select.rs | 2 +- src/sampler/sampler_status.rs | 4 ++-- 5 files changed, 40 insertions(+), 57 deletions(-) diff --git a/src/field.rs b/src/field.rs index 070a3cf2..1f80e02b 100644 --- a/src/field.rs +++ b/src/field.rs @@ -7,16 +7,12 @@ impl Content for Field where T: AsRef + Send + Sync, U: AsRef + Send + Sync { fn content (&self) -> impl Content { + let ItemPalette { darkest, dark, lighter, lightest, .. } = self.0; row!( - Tui::bg(self.0.darkest.rgb, Tui::fg(self.0.darker.rgb, "▐")), - - Tui::bg(self.0.darker.rgb, Tui::fg(self.0.lighter.rgb, - Tui::bold(true, format!("{}", self.1.as_ref())))), - - Tui::bg(self.0.darkest.rgb, Tui::fg(self.0.darker.rgb, "▌")), - - Tui::bg(self.0.darkest.rgb, Tui::fg(self.0.lightest.rgb, - format!("{} ", self.2.as_ref()))) + Tui::fg_bg(dark.rgb, darkest.rgb, "▐"), + Tui::fg_bg(lighter.rgb, dark.rgb, Tui::bold(true, format!("{}", self.1.as_ref()))), + Tui::fg_bg(dark.rgb, darkest.rgb, "▌"), + Tui::fg_bg(lightest.rgb, darkest.rgb, format!("{} ", self.2.as_ref())) ) } } @@ -28,18 +24,13 @@ impl Content for FieldV where T: AsRef + Send + Sync, U: AsRef + Send + Sync { fn content (&self) -> impl Content { - Bsp::e( - Bsp::s( - row!( - Tui::bg(self.0.darkest.rgb, Tui::fg(self.0.darker.rgb, "▐")), - Tui::bg(self.0.darker.rgb, Tui::fg(self.0.lighter.rgb, - Tui::bold(true, format!("{}", self.1.as_ref())))), - Tui::bg(self.0.darkest.rgb, Tui::fg(self.0.darker.rgb, "▌")), - ), - Tui::bg(self.0.darkest.rgb, Tui::fg(self.0.lightest.rgb, - format!(" {}", self.2.as_ref()))) - ), - " " - ) + let ItemPalette { darkest, dark, lighter, lightest, .. } = self.0; + let sep1 = Tui::bg(darkest.rgb, Tui::fg(dark.rgb, "▐")); + let sep2 = Tui::bg(darkest.rgb, Tui::fg(dark.rgb, "▌")); + let name = Tui::bg(dark.rgb, Tui::fg(lighter.rgb, + Tui::bold(true, format!("{}", self.1.as_ref())))); + let value = Tui::bg(darkest.rgb, Tui::fg(lightest.rgb, + format!(" {} ", self.2.as_ref()))); + Bsp::e(Bsp::s(row!(sep1, name, sep2), value), " ") } } diff --git a/src/groovebox/groovebox_tui.rs b/src/groovebox/groovebox_tui.rs index 5e635095..0bd1601d 100644 --- a/src/groovebox/groovebox_tui.rs +++ b/src/groovebox/groovebox_tui.rs @@ -3,7 +3,7 @@ use super::*; render!(Tui: (self: Groovebox) => self.size.of( Bsp::s(self.toolbar_view(), - Bsp::s(self.selector_view(), + Bsp::n(self.selector_view(), Bsp::n(self.sample_view(), Bsp::n(self.status_view(), Bsp::w(self.pool_view(), Fill::xy(Bsp::e(self.sampler_view(), &self.editor))))))))); @@ -16,38 +16,34 @@ impl Groovebox { Align::x(TransportView::new(true, &self.player.clock)), ))) } - fn status_view (&self) -> impl Content + use<'_> { - let note_pt = self.editor.note_point(); - lay!( - Align::w(Fixed::y(1, SamplerStatus(&self.sampler, note_pt))), - Align::x(Fixed::y(1, MidiEditStatus(&self.editor))), + fn selector_view (&self) -> impl Content + use<'_> { + row!( + MidiEditClip(&self.editor), + MidiEditStatus(&self.editor), + ClipSelected::play_phrase(&self.player), + ClipSelected::next_phrase(&self.player), ) } - fn sampler_view (&self) -> impl Content + use<'_> { - let sampler_w = if !self.compact { 11 } else { 4 }; - let sampler_y = if self.compact { 1 } else { 0 }; - Fixed::x(sampler_w, Push::y(sampler_y, Fill::y( - SampleList::new(self.compact, &self.sampler, &self.editor)))) - } fn sample_view (&self) -> impl Content + use<'_> { let note_pt = self.editor.note_point(); let sample_h = if self.compact { 0 } else { 5 }; Max::y(sample_h, Fill::xy( SampleViewer::from_sampler(&self.sampler, note_pt))) } + fn status_view (&self) -> impl Content + use<'_> { + let note_pt = self.editor.note_point(); + Align::w(Fixed::y(1, SamplerStatus(&self.sampler, note_pt))) + } fn pool_view (&self) -> impl Content + use<'_> { let w = self.size.w(); let pool_w = if w > 60 { 20 } else if w > 40 { 15 } else { 10 }; Fixed::x(if self.compact { 5 } else { pool_w }, PoolView(self.compact, &self.pool)) } - fn selector_view (&self) -> impl Content + use<'_> { - lay!( - Align::w(MidiEditClip(&self.editor)), - Align::e(Bsp::e( - ClipSelected::play_phrase(&self.player), - ClipSelected::next_phrase(&self.player) - )) - ) + fn sampler_view (&self) -> impl Content + use<'_> { + let sampler_w = if self.compact { 4 } else { 11 }; + let sampler_y = if self.compact { 0 } else { 1 }; + Fixed::x(sampler_w, Push::y(sampler_y, Fill::y( + SampleList::new(self.compact, &self.sampler, &self.editor)))) } } diff --git a/src/midi/midi_status.rs b/src/midi/midi_status.rs index 4cb323cc..b8fda05c 100644 --- a/src/midi/midi_status.rs +++ b/src/midi/midi_status.rs @@ -7,11 +7,11 @@ render!(Tui: (self: MidiEditClip<'a>) => { } else { (ItemPalette::from(TuiTheme::g(64)), String::new(), 0, false) }; - Fixed::y(1, row!( - Field(color, "Edit", name.to_string()), - Field(color, "Length", length.to_string()), - Field(color, "Loop", looped.to_string()) - )) + row!( + FieldV(color, "Edit", name.to_string()), + FieldV(color, "Length", length.to_string()), + FieldV(color, "Loop", looped.to_string()) + ) }); pub struct MidiEditStatus<'a>(pub &'a MidiEditor); @@ -27,15 +27,11 @@ render!(Tui: (self: MidiEditStatus<'a>) => { let time_axis = self.0.time_axis().get(); let time_zoom = self.0.time_zoom().get(); let time_lock = if self.0.time_lock().get() { "[lock]" } else { " " }; - let time_field = Field(color, "Time", format!("{length}/{time_zoom}+{time_point} {time_lock}")); + let time_field = FieldV(color, "Time", format!("{length}/{time_zoom}+{time_point} {time_lock}")); let note_point = format!("{:>3}", self.0.note_point()); let note_name = format!("{:4}", Note::pitch_to_name(self.0.note_point())); let note_len = format!("{:>4}", self.0.note_len());;;; - let note_field = Field(color, "Note", format!("{note_name} {note_point} {note_len}")); - - Tui::bg(color.darkest.rgb, Fill::x(Tui::fg(color.lightest.rgb, Bsp::e( - time_field, - note_field, - )))) + let note_field = FieldV(color, "Note", format!("{note_name} {note_point} {note_len}")); + Bsp::e(time_field, note_field,) }); diff --git a/src/pool/clip_select.rs b/src/pool/clip_select.rs index b3f240f6..5c14a25c 100644 --- a/src/pool/clip_select.rs +++ b/src/pool/clip_select.rs @@ -8,7 +8,7 @@ pub struct ClipSelected { } render!(Tui: (self: ClipSelected) => - Field(self.color, self.title, format!("{} {}", self.time, self.name))); + FieldV(self.color, self.title, format!("{} {}", self.time, self.name))); impl ClipSelected { diff --git a/src/sampler/sampler_status.rs b/src/sampler/sampler_status.rs index a948dfb7..184d8c82 100644 --- a/src/sampler/sampler_status.rs +++ b/src/sampler/sampler_status.rs @@ -2,8 +2,8 @@ use crate::*; pub struct SamplerStatus<'a>(pub &'a Sampler, pub usize); -render!(Tui: (self: SamplerStatus<'a>) => self.0.mapped[self.1].as_ref().map(|sample|format!( +render!(Tui: (self: SamplerStatus<'a>) => Tui::bold(true, Tui::fg(TuiTheme::g(224), self.0.mapped[self.1].as_ref().map(|sample|format!( "Sample {}-{}", sample.read().unwrap().start, sample.read().unwrap().end, -)).unwrap_or_else(||"No sample".to_string())); +)).unwrap_or_else(||"No sample".to_string())))); From a4e61c087acc3da058787ca0168a7a76b274c53d Mon Sep 17 00:00:00 2001 From: unspeaker Date: Fri, 3 Jan 2025 00:46:00 +0100 Subject: [PATCH 003/652] fix sampler/sequencer alignment --- src/groovebox/groovebox_tui.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/groovebox/groovebox_tui.rs b/src/groovebox/groovebox_tui.rs index 0bd1601d..54542d95 100644 --- a/src/groovebox/groovebox_tui.rs +++ b/src/groovebox/groovebox_tui.rs @@ -42,7 +42,7 @@ impl Groovebox { } fn sampler_view (&self) -> impl Content + use<'_> { let sampler_w = if self.compact { 4 } else { 11 }; - let sampler_y = if self.compact { 0 } else { 1 }; + let sampler_y = if self.compact { 1 } else { 0 }; Fixed::x(sampler_w, Push::y(sampler_y, Fill::y( SampleList::new(self.compact, &self.sampler, &self.editor)))) } From f359768ba2f9a50f9405c6915aabbb71608bbbdf Mon Sep 17 00:00:00 2001 From: unspeaker Date: Fri, 3 Jan 2025 15:45:51 +0100 Subject: [PATCH 004/652] new status bar enhancements --- src/groovebox/groovebox_tui.rs | 4 +-- src/midi/midi_editor.rs | 2 +- src/midi/midi_status.rs | 5 ++-- src/midi/midi_view.rs | 2 +- src/piano/piano_h.rs | 2 +- src/pool/clip_select.rs | 48 ++++++++++++++++++++-------------- 6 files changed, 36 insertions(+), 27 deletions(-) diff --git a/src/groovebox/groovebox_tui.rs b/src/groovebox/groovebox_tui.rs index 54542d95..03acbae1 100644 --- a/src/groovebox/groovebox_tui.rs +++ b/src/groovebox/groovebox_tui.rs @@ -18,10 +18,10 @@ impl Groovebox { } fn selector_view (&self) -> impl Content + use<'_> { row!( - MidiEditClip(&self.editor), - MidiEditStatus(&self.editor), ClipSelected::play_phrase(&self.player), ClipSelected::next_phrase(&self.player), + MidiEditClip(&self.editor), + MidiEditStatus(&self.editor), ) } fn sample_view (&self) -> impl Content + use<'_> { diff --git a/src/midi/midi_editor.rs b/src/midi/midi_editor.rs index bab9a1fb..b1439082 100644 --- a/src/midi/midi_editor.rs +++ b/src/midi/midi_editor.rs @@ -73,7 +73,7 @@ impl TimePoint for MidiEditor { fn set_time_point (&self, x: usize) { self.mode.set_time_point(x) } } -impl MidiViewMode for MidiEditor { +impl MidiViewer for MidiEditor { fn buffer_size (&self, phrase: &MidiClip) -> (usize, usize) { self.mode.buffer_size(phrase) } diff --git a/src/midi/midi_status.rs b/src/midi/midi_status.rs index b8fda05c..b294a04f 100644 --- a/src/midi/midi_status.rs +++ b/src/midi/midi_status.rs @@ -8,9 +8,8 @@ render!(Tui: (self: MidiEditClip<'a>) => { (ItemPalette::from(TuiTheme::g(64)), String::new(), 0, false) }; row!( - FieldV(color, "Edit", name.to_string()), - FieldV(color, "Length", length.to_string()), - FieldV(color, "Loop", looped.to_string()) + FieldV(color, "Edit", format!("{name} ({length})")), + FieldV(color, "Loop", looped.to_string()) ) }); diff --git a/src/midi/midi_view.rs b/src/midi/midi_view.rs index 47573902..211367a4 100644 --- a/src/midi/midi_view.rs +++ b/src/midi/midi_view.rs @@ -1,6 +1,6 @@ use crate::*; -pub trait MidiViewMode: HasSize + MidiRange + MidiPoint + Debug + Send + Sync { +pub trait MidiViewer: HasSize + MidiRange + MidiPoint + Debug + Send + Sync { fn buffer_size (&self, phrase: &MidiClip) -> (usize, usize); fn redraw (&self); fn phrase (&self) -> &Option>>; diff --git a/src/piano/piano_h.rs b/src/piano/piano_h.rs index f7b9e8d3..69e5a204 100644 --- a/src/piano/piano_h.rs +++ b/src/piano/piano_h.rs @@ -104,7 +104,7 @@ impl TimePoint for PianoHorizontal { fn time_point (&self) -> usize { self.point.time_point() } fn set_time_point (&self, x: usize) { self.point.set_time_point(x) } } -impl MidiViewMode for PianoHorizontal { +impl MidiViewer for PianoHorizontal { fn phrase (&self) -> &Option>> { &self.phrase } diff --git a/src/pool/clip_select.rs b/src/pool/clip_select.rs index 5c14a25c..44a2b3d7 100644 --- a/src/pool/clip_select.rs +++ b/src/pool/clip_select.rs @@ -12,27 +12,34 @@ render!(Tui: (self: ClipSelected) => impl ClipSelected { - // beats elapsed + /// Shows currently playing phrase with beats elapsed pub fn play_phrase (state: &T) -> Self { let (name, color) = if let Some((_, Some(phrase))) = state.play_phrase() { let MidiClip { ref name, color, .. } = *phrase.read().unwrap(); (name.clone(), color) } else { - ("".to_string(), ItemPalette::from(TuiTheme::g(64))) + ("".to_string(), TuiTheme::g(64).into()) }; - let time = if let Some(elapsed) = state.pulses_since_start_looped() { - format!("+{:>}", state.clock().timebase.format_beats_0(elapsed)) - } else { - String::from(" ") - }; - Self { title: "Now", time, name, color, } + Self { + title: "Now", + name, + color, + time: state.pulses_since_start_looped() + .map(|elapsed|format!("+{:>}", state.clock().timebase.format_beats_0(elapsed))) + .unwrap_or_else(||String::from(" ")) + } } - // beats until switchover + /// Shows next phrase with beats remaining until switchover pub fn next_phrase (state: &T) -> Self { - let (time, name, color) = if let Some((t, Some(phrase))) = state.next_phrase() { - let MidiClip { ref name, color, .. } = *phrase.read().unwrap(); - let time = { + let mut time = String::from("--.-.--"); + let mut name = String::from(""); + let mut color = ItemPalette::from(TuiTheme::g(64)); + if let Some((t, Some(phrase))) = state.next_phrase() { + let phrase = phrase.read().unwrap(); + name = phrase.name.clone(); + color = phrase.color.clone(); + time = { let target = t.pulse.get(); let current = state.clock().playhead.pulse.get(); if target > current { @@ -41,17 +48,20 @@ impl ClipSelected { } else { String::new() } - }; - (time, name.clone(), color) - } else if let Some((_, Some(phrase))) = state.play_phrase() { + } + } else if let Some((t, Some(phrase))) = state.play_phrase() { let phrase = phrase.read().unwrap(); if phrase.looped { - (" ".into(), phrase.name.clone(), phrase.color) + name = phrase.name.clone(); + color = phrase.color.clone(); + let target = t.pulse.get() + phrase.length as f64; + let current = state.clock().playhead.pulse.get(); + if target > current { + time = format!("-{:>}", state.clock().timebase.format_beats_0(target - current)) + } } else { - (" ".into(), " ".into(), TuiTheme::g(64).into()) + name = "Stop".to_string(); } - } else { - (" ".into(), " ".into(), TuiTheme::g(64).into()) }; Self { title: "Next", time, name, color, } } From 2b07e7963e7df5be0041e8089a09fc237b7cbc6f Mon Sep 17 00:00:00 2001 From: unspeaker Date: Fri, 3 Jan 2025 22:50:58 +0100 Subject: [PATCH 005/652] start implementing edn loader; remove PhantomData from some tek_layout constructs --- Cargo.lock | 1 + engine/Cargo.toml | 1 + engine/src/edn.rs | 36 ++++++++ engine/src/lib.rs | 1 + layout/src/align.rs | 72 +++------------- layout/src/direction.rs | 36 +++++--- layout/src/layout_edn.rs | 47 ++++++++++ layout/src/lib.rs | 1 + layout/src/measure.rs | 2 +- layout/src/ops.rs | 142 ++++++++++++++++--------------- src/arranger/arranger_v_clips.rs | 4 +- src/arranger/arranger_v_head.rs | 2 +- src/clock/clock_tui.rs | 10 +-- src/edn.rs | 33 ------- src/lib.rs | 1 + src/midi/midi_launch.rs | 5 +- src/pool/clip_select.rs | 6 +- src/pool/pool_tui.rs | 6 +- src/sampler/sample_list.rs | 2 +- src/sequencer.rs | 53 +++++------- 20 files changed, 239 insertions(+), 222 deletions(-) create mode 100644 engine/src/edn.rs create mode 100644 layout/src/layout_edn.rs diff --git a/Cargo.lock b/Cargo.lock index c73f9036..762b7603 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1389,6 +1389,7 @@ name = "tek_engine" version = "0.2.0" dependencies = [ "better-panic", + "clojure-reader", "crossterm", "ratatui", ] diff --git a/engine/Cargo.toml b/engine/Cargo.toml index b72a07cb..f9005149 100644 --- a/engine/Cargo.toml +++ b/engine/Cargo.toml @@ -7,3 +7,4 @@ version = "0.2.0" crossterm = "0.28.1" ratatui = { version = "0.29.0", features = [ "unstable-widget-ref", "underline-color" ] } better-panic = "0.3.0" +clojure-reader = "0.3.0" diff --git a/engine/src/edn.rs b/engine/src/edn.rs new file mode 100644 index 00000000..2a84d991 --- /dev/null +++ b/engine/src/edn.rs @@ -0,0 +1,36 @@ +use crate::*; + +use std::sync::{Arc, RwLock}; +use std::collections::BTreeMap; + +pub use clojure_reader::edn::Edn; + +/// EDN parsing helper. +#[macro_export] macro_rules! edn { + ($edn:ident { $($pat:pat => $expr:expr),* $(,)? }) => { + match $edn { $($pat => $expr),* } + }; + ($edn:ident in $args:ident { $($pat:pat => $expr:expr),* $(,)? }) => { + for $edn in $args { + edn!($edn { $($pat => $expr),* }) + } + }; +} + +pub trait FromEdn: Sized { + const ID: &'static str; + fn from_edn (context: C, expr: &[Edn<'_>]) -> Usually; +} + +/// Implements the [FromEdn] trait. +#[macro_export] macro_rules! from_edn { + ($id:expr => |$context:tt:$Context:ty, $args:ident| -> $T:ty $body:block) => { + impl FromEdn<$Context> for $T { + const ID: &'static str = $id; + fn from_edn <'e> ($context: $Context, $args: &[Edn<'e>]) -> Usually { + $body + } + } + } +} + diff --git a/engine/src/lib.rs b/engine/src/lib.rs index 9a89169b..e792e371 100644 --- a/engine/src/lib.rs +++ b/engine/src/lib.rs @@ -4,6 +4,7 @@ mod input; pub use self::input::*; mod output; pub use self::output::*; pub mod tui; +pub mod edn; pub use std::error::Error; diff --git a/layout/src/align.rs b/layout/src/align.rs index 7135643a..919443a8 100644 --- a/layout/src/align.rs +++ b/layout/src/align.rs @@ -3,23 +3,23 @@ use crate::*; #[derive(Debug, Copy, Clone, Default)] pub enum Alignment { #[default] Center, X, Y, NW, N, NE, E, SE, S, SW, W } -pub struct Align>(Alignment, T, PhantomData); +pub struct Align(Alignment, T); -impl> Align { - pub fn c (a: T) -> Self { Self(Alignment::Center, a, Default::default()) } - pub fn x (a: T) -> Self { Self(Alignment::X, a, Default::default()) } - pub fn y (a: T) -> Self { Self(Alignment::Y, a, Default::default()) } - pub fn n (a: T) -> Self { Self(Alignment::N, a, Default::default()) } - pub fn s (a: T) -> Self { Self(Alignment::S, a, Default::default()) } - pub fn e (a: T) -> Self { Self(Alignment::E, a, Default::default()) } - pub fn w (a: T) -> Self { Self(Alignment::W, a, Default::default()) } - pub fn nw (a: T) -> Self { Self(Alignment::NW, a, Default::default()) } - pub fn sw (a: T) -> Self { Self(Alignment::SW, a, Default::default()) } - pub fn ne (a: T) -> Self { Self(Alignment::NE, a, Default::default()) } - pub fn se (a: T) -> Self { Self(Alignment::SE, a, Default::default()) } +impl Align { + pub fn c (a: T) -> Self { Self(Alignment::Center, a) } + pub fn x (a: T) -> Self { Self(Alignment::X, a) } + pub fn y (a: T) -> Self { Self(Alignment::Y, a) } + pub fn n (a: T) -> Self { Self(Alignment::N, a) } + pub fn s (a: T) -> Self { Self(Alignment::S, a) } + pub fn e (a: T) -> Self { Self(Alignment::E, a) } + pub fn w (a: T) -> Self { Self(Alignment::W, a) } + pub fn nw (a: T) -> Self { Self(Alignment::NW, a) } + pub fn sw (a: T) -> Self { Self(Alignment::SW, a) } + pub fn ne (a: T) -> Self { Self(Alignment::NE, a) } + pub fn se (a: T) -> Self { Self(Alignment::SE, a) } } -impl> Content for Align { +impl> Content for Align { fn content (&self) -> impl Content { &self.1 } @@ -45,52 +45,8 @@ impl> Content for Align { Y => [it.x(), centered.y()], }; [x, y, centered.w(), centered.h()].into() - //let [cfx, cfy, ..] = on.center(); - //let [cmx, cmy, ..] = it.center(); - ////let center = |cf, cm, m|if cf >= cm { m + (cf - cm) } else { m.minus(cm - cf) }; - //let center_x = center(cfx, cmx, it.x()); - //let center_y = center(cfy, cmy, it.y()); - //let east_x = on.x() + on.w().minus(it.w()); - //let south_y = on.y() + on.h().minus(it.h()); - //let [x, y] = match self.0 { - //Alignment::X => [center_x, it.y(), ], - //Alignment::Y => [it.x(), center_y,], - - //Alignment::NW => [on.x(), on.y(), ], - //Alignment::N => [center_x, on.y(), ], - //Alignment::NE => [east_x, on.y(), ], - //Alignment::E => [east_x, center_y,], - //Alignment::SE => [east_x, south_y, ], - //Alignment::S => [center_x, south_y, ], - //Alignment::SW => [on.x(), south_y, ], - //Alignment::W => [on.x(), center_y,], - //}; - //[x, y, it.w(), it.h()].into() } fn render (&self, render: &mut E::Output) { render.place(self.layout(render.area()), &self.content()) } } - -//fn align, N: Coordinate, R: Area + From<[N;4]>> (align: &Align, outer: R, content: R) -> Option { - //if outer.w() < content.w() || outer.h() < content.h() { - //None - //} else { - //let [ox, oy, ow, oh] = outer.xywh(); - //let [ix, iy, iw, ih] = content.xywh(); - //Some(match align { - //Align::Center(_) => [ox + (ow - iw) / 2.into(), oy + (oh - ih) / 2.into(), iw, ih,].into(), - //Align::X(_) => [ox + (ow - iw) / 2.into(), iy, iw, ih,].into(), - //Align::Y(_) => [ix, oy + (oh - ih) / 2.into(), iw, ih,].into(), - //Align::NW(_) => [ox, oy, iw, ih,].into(), - //Align::N(_) => [ox + (ow - iw) / 2.into(), oy, iw, ih,].into(), - //Align::NE(_) => [ox + ow - iw, oy, iw, ih,].into(), - //Align::W(_) => [ox, oy + (oh - ih) / 2.into(), iw, ih,].into(), - //Align::E(_) => [ox + ow - iw, oy + (oh - ih) / 2.into(), iw, ih,].into(), - //Align::SW(_) => [ox, oy + oh - ih, iw, ih,].into(), - //Align::S(_) => [ox + (ow - iw) / 2.into(), oy + oh - ih, iw, ih,].into(), - //Align::SE(_) => [ox + ow - iw, oy + oh - ih, iw, ih,].into(), - //_ => unreachable!() - //}) - //} -//} diff --git a/layout/src/direction.rs b/layout/src/direction.rs index 8cab09c8..78f9f477 100644 --- a/layout/src/direction.rs +++ b/layout/src/direction.rs @@ -18,9 +18,9 @@ impl Direction { } } -pub struct Bsp, Y: Content>(Direction, X, Y, PhantomData); +pub struct Bsp(Direction, X, Y); -impl, B: Content> Content for Bsp { +impl, B: Content> Content for Bsp { fn layout (&self, outer: E::Area) -> E::Area { let [_, _, c] = self.areas(outer); c @@ -35,26 +35,31 @@ impl, B: Content> Content for Bsp { } } -impl, B: Content> Bsp { - pub fn n (a: A, b: B) -> Self { Self(North, a, b, Default::default()) } - pub fn s (a: A, b: B) -> Self { Self(South, a, b, Default::default()) } - pub fn e (a: A, b: B) -> Self { Self(East, a, b, Default::default()) } - pub fn w (a: A, b: B) -> Self { Self(West, a, b, Default::default()) } - pub fn a (a: A, b: B) -> Self { Self(Above, a, b, Default::default()) } - pub fn b (a: A, b: B) -> Self { Self(Below, a, b, Default::default()) } - pub fn contents (&self) -> (&A, &B) { (&self.1, &self.2) } - pub fn areas (&self, outer: E::Area) -> [E::Area;3] { +impl Bsp { + pub fn n (a: A, b: B) -> Self { Self(North, a, b) } + pub fn s (a: A, b: B) -> Self { Self(South, a, b) } + pub fn e (a: A, b: B) -> Self { Self(East, a, b) } + pub fn w (a: A, b: B) -> Self { Self(West, a, b) } + pub fn a (a: A, b: B) -> Self { Self(Above, a, b) } + pub fn b (a: A, b: B) -> Self { Self(Below, a, b) } +} + +pub trait BspAreas, B: Content> { + fn direction (&self) -> Direction; + fn contents (&self) -> (&A, &B); + fn areas (&self, outer: E::Area) -> [E::Area;3] { + let direction = self.direction(); let [x, y, w, h] = outer.xywh(); let (a, b) = self.contents(); let [ax, ay, aw, ah] = a.layout(outer).xywh(); - let [bx, by, bw, bh] = b.layout(match self.0 { + let [bx, by, bw, bh] = b.layout(match direction { Above | Below => outer, South => [x, y + ah, w, h.minus(ah)].into(), North => [x, y, w, h.minus(ah)].into(), East => [x + aw, y, w.minus(aw), h].into(), West => [x, y, w.minus(aw), h].into(), }).xywh(); - match self.0 { + match direction { Above | Below => { let x = ax.min(bx); let w = (ax+aw).max(bx+bw).minus(x); @@ -90,6 +95,11 @@ impl, B: Content> Bsp { } } +impl, B: Content> BspAreas for Bsp { + fn direction (&self) -> Direction { self. 0 } + fn contents (&self) -> (&A, &B) { (&self.1, &self.2) } +} + /// Renders multiple things on top of each other, #[macro_export] macro_rules! lay { ($($expr:expr),* $(,)?) => {{ let bsp = (); $(let bsp = Bsp::b(bsp, $expr);)*; bsp }} diff --git a/layout/src/layout_edn.rs b/layout/src/layout_edn.rs new file mode 100644 index 00000000..e466483e --- /dev/null +++ b/layout/src/layout_edn.rs @@ -0,0 +1,47 @@ +use crate::*; +use ::tek_engine::edn::*; + +macro_rules! edn_module { + ($name:literal = $Host:ident<$E:ident: $Engine:path> { $( + (defn $fn:ident + $(<$($G:ident: $Generic:ty),+>)? + $Struct:ident($($arg:ident : $Arg:ty),*)) + )* }) => { + pub trait $Host<$E: Engine> { + pub fn read_one <'e> (edn: &[Edn<'e>]) -> impl Content<$E> { + if let Some(Edn::Symbol(name)) = edn.get(0) { + match name { + $( + stringify!($fn) => + ),* + } + } else { + panic!("invalid edn") + } + } + $( + + )* + } + }; +} + +//edn_module! { + //(host LayoutEdn) + //(name "layout") + + //(defn when > + //When(cond: bool, item: A)) + + //(defn either , B: Content> + //Either(cond: bool, a: A, b: B)) + + //(defn map < + //A: Content, + //B: Content, + //I: Iterator + Send + Sync, + //F: Fn() -> I + Send + Sync, + //G: Fn(A, usize)->B + Send + Sync + //> + //Map(get_iterator: I, callback: G)) +//} diff --git a/layout/src/lib.rs b/layout/src/lib.rs index b41d9f0b..56f2a87d 100644 --- a/layout/src/lib.rs +++ b/layout/src/lib.rs @@ -2,6 +2,7 @@ mod align; pub use self::align::*; mod direction; pub use self::direction::*; +mod layout_edn; pub use self::edn::*; mod measure; pub use self::measure::*; mod ops; pub use self::ops::*; mod transform_xy; pub use self::transform_xy::*; diff --git a/layout/src/measure.rs b/layout/src/measure.rs index 98ccf85f..b1bdf2e1 100644 --- a/layout/src/measure.rs +++ b/layout/src/measure.rs @@ -65,7 +65,7 @@ impl Measure { y: Arc::new(0.into()), } } - pub fn of > (&self, item: T) -> Bsp, T> { + pub fn of > (&self, item: T) -> Bsp, T> { Bsp::b(Fill::xy(&self), item) } } diff --git a/layout/src/ops.rs b/layout/src/ops.rs index 117a77fc..06539e6b 100644 --- a/layout/src/ops.rs +++ b/layout/src/ops.rs @@ -1,55 +1,11 @@ use crate::*; -impl Layout for E {} +/// Show an item only when a condition is true. +pub struct When(pub bool, pub A); -pub trait Layout { - /// Content `item` when `cond` is true. - fn when (cond: bool, item: A) -> When where - A: Content - { - When(cond, item, Default::default()) - } - /// Content `item` if `cond` is true, otherwise render `other`. - fn either (cond: bool, a: A, b: B) -> Either where - A: Content, - B: Content, - { - Either(cond, a, b, Default::default()) - } - /// Maps an [Option] through a callback `F` - fn opt (option: Option, cb: F) -> Opt where - F: Fn(A) -> R, - R: Content - { - Opt(option, cb, Default::default()) - } - fn map (iterator: J, callback: F) -> Map where - E: Engine, - I: Iterator + Send + Sync, - J: Fn() -> I + Send + Sync, - R: Content, - F: Fn(T, usize)->R + Send + Sync - { - Map(Default::default(), iterator, callback) - } - //pub fn reduce (iterator: I, callback: F) -> Reduce where - //E: Engine, - //I: Iterator + Send + Sync, - //R: Content, - //F: Fn(R, T, usize) -> R + Send + Sync - //{ - //Reduce(Default::default(), iterator, callback) - //} -} - -pub struct OptR, R: Content>(Option, F, PhantomData); - -/// Contents `self.1` when `self.0` is true. -pub struct When(bool, A, PhantomData); - -impl> Content for When { +impl> Content for When { fn layout (&self, to: E::Area) -> E::Area { - let Self(cond, item, ..) = self; + let Self(cond, item) = self; let mut area = E::Area::zero(); if *cond { let item_area = item.layout(to); @@ -61,45 +17,44 @@ impl> Content for When { area.into() } fn render (&self, to: &mut E::Output) { - let Self(cond, item, ..) = self; + let Self(cond, item) = self; if *cond { item.render(to) } } } -/// Contents `self.1` when `self.0` is true, otherwise renders `self.2` -pub struct Either(bool, A, B, PhantomData); +/// Show one item if a condition is true and another if the condition is false +pub struct Either(pub bool, pub A, pub B); -impl, B: Content> Content for Either { +impl, B: Content> Content for Either { fn layout (&self, to: E::Area) -> E::Area { - let Self(cond, a, b, ..) = self; + let Self(cond, a, b) = self; if *cond { a.layout(to) } else { b.layout(to) } } fn render (&self, to: &mut E::Output) { - let Self(cond, a, b, ..) = self; + let Self(cond, a, b) = self; if *cond { a.render(to) } else { b.render(to) } } } -pub struct Map(PhantomData, J, F) where - E: Engine, - I: Iterator + Send + Sync, - J: Fn()->I + Send + Sync, - R: Content, - F: Fn(T, usize)->R + Send + Sync; +pub struct Map(pub F, pub G) where + I: Iterator + Send + Sync, + F: Fn() -> I + Send + Sync, + G: Fn(A, usize)->B + Send + Sync; -impl Content for Map where +impl Content for Map where E: Engine, - I: Iterator + Send + Sync, - J: Fn()->I + Send + Sync, - R: Content, - F: Fn(T, usize)->R + Send + Sync + B: Content, + I: Iterator + Send + Sync, + F: Fn() -> I + Send + Sync, + G: Fn(A, usize)->B + Send + Sync { fn layout (&self, area: E::Area) -> E::Area { + let Self(get_iterator, callback) = self; let mut index = 0; let [mut min_x, mut min_y] = area.center(); let [mut max_x, mut max_y] = area.center(); - for item in (self.1)() { - let area = (self.2)(item, index).layout(area).xywh(); + for item in get_iterator() { + let area = callback(item, index).layout(area).xywh(); let [x,y,w,h] = area.xywh(); min_x = min_x.min(x.into()); min_y = min_y.min(y.into()); @@ -113,10 +68,11 @@ impl Content for Map where area.center_xy([w.into(), h.into()].into()).into() } fn render (&self, to: &mut E::Output) { + let Self(get_iterator, callback) = self; let mut index = 0; //let area = self.layout(to.area()); - for item in (self.1)() { - let item = (self.2)(item, index); + for item in get_iterator() { + let item = callback(item, index); //to.place(area.into(), &item); to.place(to.area().into(), &item); index += 1; @@ -125,6 +81,15 @@ impl Content for Map where } /* + + //pub fn reduce (iterator: I, callback: F) -> Reduce where + //E: Engine, + //I: Iterator + Send + Sync, + //R: Content, + //F: Fn(R, T, usize) -> R + Send + Sync + //{ + //Reduce(Default::default(), iterator, callback) + //} pub struct Reduce(PhantomData<(E, R)>, I, F) where E: Engine, I: Iterator + Send + Sync, @@ -141,3 +106,42 @@ impl Content for Reduce where } } */ + +//macro_rules! define_ops { + //($Trait:ident<$E:ident:$Engine:path> { $( + //$(#[$attr:meta $($attr_args:tt)*])* + //( + //$fn:ident + //$(<$($G:ident$(:$Gen:path)?, )+>)? + //$Op:ident + //($($arg:ident:$Arg:ty),*) + //) + //)* }) => { + //impl<$E: $Engine> $Trait for E {} + //pub trait $Trait<$E: $Engine> { + //$( + //$(#[$attr $($attr_args)*])* + //fn $fn $(<$($G),+>)? + //($($arg:$Arg),*)-> $Op<$($(, $G)+)?> + //$(where $($G: $($Gen + Send + Sync)?),+)? + //{ $Op($($arg),*) } + //)* + //} + //} +//} + +//define_ops! { + //Layout { + //(when ,> + //When(cond: bool, item: A)) + ///// When `cond` is `true`, render `a`, otherwise render `b`. + //(either , B: Content,> + //Either(cond: bool, a: A, b: B)) + ///// If `opt` is `Some(T)` renders `cb(t)`, otherwise nothing. + //(opt B, B: Content,> + //Opt(option: Option, cb: F)) + ///// Maps items of iterator through callback. + //(map , I: Iterator, F: Fn() -> I, G: Fn(A, usize)->B,> + //Map(get_iterator: F, callback: G)) + //} +//} diff --git a/src/arranger/arranger_v_clips.rs b/src/arranger/arranger_v_clips.rs index 97520739..6dcc0d86 100644 --- a/src/arranger/arranger_v_clips.rs +++ b/src/arranger/arranger_v_clips.rs @@ -20,7 +20,7 @@ impl<'a> ArrangerVClips<'a> { impl<'a> Content for ArrangerVClips<'a> { fn content (&self) -> impl Content { 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 = Map(iter, |(scene, pulses), i|Self::format_scene(self.tracks, scene, pulses)); Fill::xy(col) } } @@ -37,7 +37,7 @@ impl<'a> ArrangerVClips<'a> { let name = Tui::fg_bg(scene.color.lightest.rgb, scene.color.base.rgb, 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 = 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)) ); Fixed::y(height, row!(icon, name, clips)) diff --git a/src/arranger/arranger_v_head.rs b/src/arranger/arranger_v_head.rs index 0a9a68a3..cfef2096 100644 --- a/src/arranger/arranger_v_head.rs +++ b/src/arranger/arranger_v_head.rs @@ -20,7 +20,7 @@ render!(Tui: (self: ArrangerVHead<'a>) => { row!(Tui::fg(color.light.rgb, "▎"), Tui::fg(color.lightest.rgb, field)) } Some(Push::x(self.scenes_w, - Tui::map(||ArrangerTrack::with_widths(self.tracks), |(_, track, x1, x2), i| { + Map(||ArrangerTrack::with_widths(self.tracks), |(_, track, x1, x2), i| { let (w, h) = (ArrangerTrack::MIN_WIDTH.max(x2 - x1), HEADER_H); let color = track.color(); let input = Self::format_input(track); diff --git a/src/clock/clock_tui.rs b/src/clock/clock_tui.rs index 5221b2bc..80d9c0c4 100644 --- a/src/clock/clock_tui.rs +++ b/src/clock/clock_tui.rs @@ -74,11 +74,11 @@ render!(Tui: (self: TransportView<'a>) => Outer( pub struct PlayPause { pub compact: bool, pub playing: bool } render!(Tui: (self: PlayPause) => Tui::bg( if self.playing{Color::Rgb(0,128,0)}else{Color::Rgb(128,64,0)}, - Tui::either(self.compact, - Thunk::new(||Fixed::x(9, Tui::either(self.playing, + Either(self.compact, + Thunk::new(||Fixed::x(9, Either(self.playing, Tui::fg(Color::Rgb(0, 255, 0), " PLAYING "), Tui::fg(Color::Rgb(255, 128, 0), " STOPPED ")))), - Thunk::new(||Fixed::x(5, Tui::either(self.playing, + Thunk::new(||Fixed::x(5, Either(self.playing, Tui::fg(Color::Rgb(0, 255, 0), Bsp::s(" 🭍🭑🬽 ", " 🭞🭜🭘 ",)), Tui::fg(Color::Rgb(255, 128, 0), Bsp::s(" ▗▄▖ ", " ▝▀▘ ",)))))))); @@ -95,7 +95,7 @@ impl BeatStats { Self { compact, bpm: format!("{:.3}", clock.timebase.bpm.get()), beat, time } } } -render!(Tui: (self: BeatStats) => Tui::either(self.compact, +render!(Tui: (self: BeatStats) => Either(self.compact, row!( FieldV(TuiTheme::g(128).into(), "BPM", &self.bpm), FieldV(TuiTheme::g(128).into(), "Beat", &self.beat), @@ -124,7 +124,7 @@ impl OutputStats { } } } -render!(Tui: (self: OutputStats) => Tui::either(self.compact, +render!(Tui: (self: OutputStats) => Either(self.compact, row!( FieldV(TuiTheme::g(128).into(), "SR", &self.sample_rate), FieldV(TuiTheme::g(128).into(), "Buf", &self.buffer_size), diff --git a/src/edn.rs b/src/edn.rs index 289d3e81..931fcdc3 100644 --- a/src/edn.rs +++ b/src/edn.rs @@ -1,37 +1,4 @@ use crate::*; -use std::sync::{Arc, RwLock}; -use std::collections::BTreeMap; -pub use clojure_reader::edn::Edn; -//pub use clojure_reader::{edn::{read, Edn}, error::Error as EdnError}; - -/// EDN parsing helper. -#[macro_export] macro_rules! edn { - ($edn:ident { $($pat:pat => $expr:expr),* $(,)? }) => { - match $edn { $($pat => $expr),* } - }; - ($edn:ident in $args:ident { $($pat:pat => $expr:expr),* $(,)? }) => { - for $edn in $args { - edn!($edn { $($pat => $expr),* }) - } - }; -} - -pub trait FromEdn: Sized { - const ID: &'static str; - fn from_edn (context: C, expr: &[Edn<'_>]) -> Usually; -} - -/// Implements the [FromEdn] trait. -#[macro_export] macro_rules! from_edn { - ($id:expr => |$context:tt:$Context:ty, $args:ident| -> $T:ty $body:block) => { - impl FromEdn<$Context> for $T { - const ID: &'static str = $id; - fn from_edn <'e> ($context: $Context, $args: &[Edn<'e>]) -> Usually { - $body - } - } - } -} from_edn!("sampler" => |jack: &Arc>, args| -> crate::Sampler { let mut name = String::new(); diff --git a/src/lib.rs b/src/lib.rs index a60d3762..84d9c50b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,6 +12,7 @@ pub(crate) use ::tek_layout::{ Output, Content, Thunk, render, Input, Handle, handle, kexp, kpat, + edn::*, tui::{ Tui, TuiIn, key, ctrl, shift, alt, diff --git a/src/midi/midi_launch.rs b/src/midi/midi_launch.rs index 49ec833e..c196b5a4 100644 --- a/src/midi/midi_launch.rs +++ b/src/midi/midi_launch.rs @@ -15,12 +15,13 @@ pub trait HasPlayPhrase: HasClock { None } } - fn pulses_since_start_looped (&self) -> Option { + fn pulses_since_start_looped (&self) -> Option<(f64, f64)> { if let Some((started, Some(phrase))) = self.play_phrase().as_ref() { let elapsed = self.clock().playhead.pulse.get() - started.pulse.get(); let length = phrase.read().unwrap().length.max(1); // prevent div0 on empty phrase + let times = (elapsed as usize / length) as f64; let elapsed = (elapsed as usize % length) as f64; - Some(elapsed) + Some((times, elapsed)) } else { None } diff --git a/src/pool/clip_select.rs b/src/pool/clip_select.rs index 44a2b3d7..5d07f341 100644 --- a/src/pool/clip_select.rs +++ b/src/pool/clip_select.rs @@ -25,7 +25,9 @@ impl ClipSelected { name, color, time: state.pulses_since_start_looped() - .map(|elapsed|format!("+{:>}", state.clock().timebase.format_beats_0(elapsed))) + .map(|(times, time)|format!("{:>3}x {:>}", + times+1.0, + state.clock().timebase.format_beats_1(time))) .unwrap_or_else(||String::from(" ")) } } @@ -44,7 +46,7 @@ impl ClipSelected { let current = state.clock().playhead.pulse.get(); if target > current { let remaining = target - current; - format!("-{:>}", state.clock().timebase.format_beats_0(remaining)) + format!("-{:>}", state.clock().timebase.format_beats_1(remaining)) } else { String::new() } diff --git a/src/pool/pool_tui.rs b/src/pool/pool_tui.rs index 614cd450..4fdb8166 100644 --- a/src/pool/pool_tui.rs +++ b/src/pool/pool_tui.rs @@ -7,7 +7,7 @@ render!(Tui: (self: PoolView<'a>) => { let color = self.1.phrase().read().unwrap().color; Outer( Style::default().fg(color.dark.rgb).bg(color.darkest.rgb) - ).enclose(Tui::map(||model.phrases().iter(), |clip, i|{ + ).enclose(Map(||model.phrases().iter(), |clip, i|{ let item_height = 1; let item_offset = i as u16 * item_height; let selected = i == model.phrase_index(); @@ -20,8 +20,8 @@ render!(Tui: (self: PoolView<'a>) => { offset(Tui::bg(if selected { color.light.rgb } else { color.base.rgb }, lay!( Align::w(Tui::fg(color.lightest.rgb, Tui::bold(selected, name))), Align::e(Tui::fg(color.lightest.rgb, Tui::bold(selected, length))), - Align::w(Tui::when(selected, Tui::bold(true, Tui::fg(TuiTheme::g(255), "▶")))), - Align::e(Tui::when(selected, Tui::bold(true, Tui::fg(TuiTheme::g(255), "◀")))), + Align::w(When(selected, Tui::bold(true, Tui::fg(TuiTheme::g(255), "▶")))), + Align::e(When(selected, Tui::bold(true, Tui::fg(TuiTheme::g(255), "◀")))), ))) })) }); diff --git a/src/sampler/sample_list.rs b/src/sampler/sample_list.rs index ad46d003..8ce01bd7 100644 --- a/src/sampler/sample_list.rs +++ b/src/sampler/sample_list.rs @@ -17,7 +17,7 @@ render!(Tui: (self: SampleList<'a>) => { let note_lo = editor.note_lo().load(Relaxed); let note_pt = editor.note_point(); let note_hi = editor.note_hi(); - Outer(Style::default().fg(TuiTheme::g(96))).enclose(Tui::map(move||(note_lo..=note_hi).rev(), move|note, i| { + Outer(Style::default().fg(TuiTheme::g(96))).enclose(Map(move||(note_lo..=note_hi).rev(), move|note, i| { let offset = |a|Push::y(i as u16, Align::n(Fixed::y(1, Fill::x(a)))); diff --git a/src/sequencer.rs b/src/sequencer.rs index e0656c6c..3e84f468 100644 --- a/src/sequencer.rs +++ b/src/sequencer.rs @@ -47,38 +47,27 @@ from_jack!(|jack|SequencerTui { clock, } }); -render!(Tui: (self: SequencerTui) => { - let w = - self.size.w(); - let phrase_w = - if w > 60 { 20 } else if w > 40 { 15 } else { 10 }; - let color = self.player.play_phrase().as_ref().map(|(_,p)| - p.as_ref().map(|p|p.read().unwrap().color) - ).flatten().clone(); - let toolbar = Tui::when(self.transport, - TransportView::new(true, &self.clock)); - let selectors = Tui::when(self.selectors, - Bsp::e(ClipSelected::play_phrase(&self.player), ClipSelected::next_phrase(&self.player))); - let pool_w = - if self.pool.visible { phrase_w } else { 0 }; - let pool = - Pull::y(1, Fill::y(Align::e(PoolView(self.pool.visible, &self.pool)))); - let edit_clip = - MidiEditClip(&self.editor); - self.size.of(Bsp::s( - toolbar, - Bsp::s( - lay!(Align::w(edit_clip), Align::e(selectors)), - Bsp::n( - Align::x(Fixed::y(1, MidiEditStatus(&self.editor))), - Bsp::w( - Fixed::x(pool_w, Align::e(Fill::y(PoolView(self.compact, &self.pool)))), - Fill::xy(&self.editor), - ), - ) - ) - )) -}); +render!(Tui: (self: SequencerTui) => self.size.of( + Bsp::s(self.toolbar_view(), + Bsp::n(self.status_view(), + Bsp::w(self.pool_view(), Fill::xy(&self.editor)))))); +impl SequencerTui { + fn toolbar_view (&self) -> impl Content + use<'_> { + self.transport.then(||TransportView::new(true, &self.clock)) + } + fn status_view (&self) -> impl Content + use<'_> { + let edit_clip = MidiEditClip(&self.editor); + let selectors = When(self.selectors, Bsp::e(ClipSelected::play_phrase(&self.player), ClipSelected::next_phrase(&self.player))); + row!(selectors, edit_clip, MidiEditStatus(&self.editor)) + } + fn pool_view (&self) -> impl Content + use<'_> { + let w = self.size.w(); + let phrase_w = if w > 60 { 20 } else if w > 40 { 15 } else { 10 }; + let pool_w = if self.pool.visible { phrase_w } else { 0 }; + let pool = Pull::y(1, Fill::y(Align::e(PoolView(self.pool.visible, &self.pool)))); + Fixed::x(pool_w, Align::e(Fill::y(PoolView(self.compact, &self.pool)))) + } +} audio!(|self:SequencerTui, client, scope|{ // Start profiling cycle let t0 = self.perf.get_t0(); From f81a04dd441c287bcf1a756f1f01e09dbc04a2a7 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Fri, 3 Jan 2025 23:00:26 +0100 Subject: [PATCH 006/652] layout: remove more superfluous PhantomData usage --- layout/src/measure.rs | 2 +- layout/src/transform_xy.rs | 17 ++++++++--------- layout/src/transform_xy_unit.rs | 24 ++++++++++++------------ 3 files changed, 21 insertions(+), 22 deletions(-) diff --git a/layout/src/measure.rs b/layout/src/measure.rs index b1bdf2e1..da8b3ae1 100644 --- a/layout/src/measure.rs +++ b/layout/src/measure.rs @@ -65,7 +65,7 @@ impl Measure { y: Arc::new(0.into()), } } - pub fn of > (&self, item: T) -> Bsp, T> { + pub fn of > (&self, item: T) -> Bsp, T> { Bsp::b(Fill::xy(&self), item) } } diff --git a/layout/src/transform_xy.rs b/layout/src/transform_xy.rs index ef082d0b..78b12918 100644 --- a/layout/src/transform_xy.rs +++ b/layout/src/transform_xy.rs @@ -7,22 +7,22 @@ use crate::*; /// using `PhantomData` to permit the double generic. macro_rules! transform_xy { ($self:ident : $Enum:ident |$to:ident|$area:expr) => { - pub enum $Enum> { _Unused(PhantomData), X(T), Y(T), XY(T) } - impl> $Enum { + pub enum $Enum { X(T), Y(T), XY(T) } + impl $Enum { pub fn x (item: T) -> Self { Self::X(item) } pub fn y (item: T) -> Self { Self::Y(item) } pub fn xy (item: T) -> Self { Self::XY(item) } } - impl> Content for $Enum { + impl> Content for $Enum { fn content (&self) -> impl Content { match self { Self::X(item) => item, Self::Y(item) => item, Self::XY(item) => item, - _ => unreachable!() } } fn layout (&$self, $to: ::Area) -> ::Area { + use $Enum::*; $area } } @@ -32,10 +32,9 @@ macro_rules! transform_xy { transform_xy!(self: Fill |to|{ let [x0, y0, wmax, hmax] = to.xywh(); let [x, y, w, h] = Content::layout(&self.content(), to).xywh(); - return match self { - Self::X(_) => [x0, y, wmax, h], - Self::Y(_) => [x, y0, w, hmax], - Self::XY(_) => [x0, y0, wmax, hmax], - _ => unreachable!() + match self { + X(_) => [x0, y, wmax, h], + Y(_) => [x, y0, w, hmax], + XY(_) => [x0, y0, wmax, hmax], }.into() }); diff --git a/layout/src/transform_xy_unit.rs b/layout/src/transform_xy_unit.rs index 1b400c5e..d43f1399 100644 --- a/layout/src/transform_xy_unit.rs +++ b/layout/src/transform_xy_unit.rs @@ -4,29 +4,29 @@ use crate::*; /// along either the X axis, the Y axis, or both. macro_rules! transform_xy_unit { (|$self:ident : $Enum:ident, $to:ident|$layout:expr) => { - pub enum $Enum> { - X(E::Unit, T), Y(E::Unit, T), XY(E::Unit, E::Unit, T), + pub enum $Enum { X(U, T), Y(U, T), XY(U, U, T), } + impl $Enum { + pub fn x (x: U, item: T) -> Self { Self::X(x, item) } + pub fn y (y: U, item: T) -> Self { Self::Y(y, item) } + pub fn xy (x: U, y: U, item: T) -> Self { Self::XY(x, y, item) } } - impl> $Enum { - pub fn x (x: E::Unit, item: T) -> Self { Self::X(x, item) } - pub fn y (y: E::Unit, item: T) -> Self { Self::Y(y, item) } - pub fn xy (x: E::Unit, y: E::Unit, item: T) -> Self { Self::XY(x, y, item) } - pub fn dx (&self) -> E::Unit { + impl $Enum { + pub fn dx (&self) -> U { match self { Self::X(x, _) => *x, - Self::Y(_, _) => E::Unit::zero(), + Self::Y(_, _) => 0.into(), Self::XY(x, _, _) => *x, } } - pub fn dy (&self) -> E::Unit { + pub fn dy (&self) -> U { match self { - Self::X(_, _) => E::Unit::zero(), - Self::Y(y, _) => *y, + Self::X(_, _) => 0.into(), + Self::Y(y, _) => *y, Self::XY(_, y, _) => *y, } } } - impl> Content for $Enum { + impl> Content for $Enum { fn content (&self) -> impl Content { Some(match self { Self::X(_, content) => content, From fc82d6ff9bcceffd8262d084063fa1e80ab05237 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Fri, 3 Jan 2025 23:08:25 +0100 Subject: [PATCH 007/652] layout docs: try something --- layout/README.md | 48 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 32 insertions(+), 16 deletions(-) diff --git a/layout/README.md b/layout/README.md index 80ebcad4..c6dde42c 100644 --- a/layout/README.md +++ b/layout/README.md @@ -6,20 +6,36 @@ and are generic over `tek_engine::Engine` and `tek_engine::Content`. chiefly, they are not dependent on rendering framework. -* `Fill` is to make the content's dimension equal to the container's. -* `Fixed` is to assign a fixed dimension to its content. -* `Shrink`/`Expand` are to change the dimension of the content -* `Min`/`Max` are to constrain the dimension of the content -* `Push`/`Pull` are to move the content along the axis -* `Margin`/`Padding` are to change the dimension proportionally -* `Align` is to pin the content along the axis of the container -* `When` is to render content conditionally -* `Either` is to alternates between contents -* `Map` is to transform each content -* `Reduce` is to transform all contents into one -* and, finally, `Bsp` is to put 2 where there was 1. +|operator|description| +|-|-| +|**`When(x, a)`**|render `a` only when `x == true`| +|**`Either(x, a, b)`**|render `a` when `x == true`, otherwise render `b`| +|**`Map(get_iterator, callback)`**|transform items in uniform way| +|**`Bsp`**|concatenative layout| +|...|...| +|**`Align`**|pin content along axis| +|...|...| +|**`Fill`**|**make content's dimension equal to container's:**| +|`Fill::x(a)`|use container's width for content| +|`Fill::y(a)`|use container's height for content| +|`Fill::xy(a)`|use container's width and height for content| +|**`Fixed`**|**assign fixed dimension to content:**| +|`Fixed::x(w, a)`|use width `w` for content| +|`Fixed::y(w, a)`|use height `w` for content| +|`Fixed::xy(w, h, a)`|use width `w` and height `h` for content| +|**`Expand`/`Shrink`**|**change dimension of content:**| +|`Expand::x(n, a)`/`Shrink::x(n, a)`|increment/decrement width of content area by `n`| +|`Expand::y(n, a)`/`Shrink::y(n, a)`|increment/decrement height of content area by `m`| +|`Expand::xy(n, m, a)`/`Shrink::xy(n, m, a)`|increment/decrement width of content area by `n`, height by `m`| +|**`Min`/`Max`**|**constrain dimension of content:**| +|`Min::x(w, a)`/`Max::x(w, a)`|enforce minimum/maximum width `w` for content| +|`Min::y(h, a)`/`Max::y(h, a)`|enforce minimum/maximum height `h` for content| +|`Min::xy(w, h, a)`/`Max::xy(w, h, a)`|enforce minimum/maximum width `w` and height `h` for content| +|**`Push`/`Pull`**|**move content along axis:**| +|`Push::x(n, a)`/`Pull::x(n, a)`|increment/decrement `x` of content area| +|`Push::y(n, a)`/`Pull::y(n, a)`|increment/decrement `y` of content area| +|`Push::xy(n, m, a)`/`Pull::xy(n, m, a)`|increment/decrement `x` and `y` of content area| -**todo.** and then you're like, -"but why are they generic over E in the first place -and not, say, over E::Unit? that might even free up -some space to implement for non-uint cosmologies... +**todo:** +* sensible `Margin`/`Padding` +* `Reduce` From 600d0b3acab8036d0366131defedf198bb316896 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sat, 4 Jan 2025 08:49:38 +0100 Subject: [PATCH 008/652] wip: try to get a simplified parser going --- Cargo.lock | 10 +- Cargo.toml | 6 +- README.md | 6 + edn/Cargo.lock | 92 ++++++++++ edn/Cargo.toml | 8 + edn/src/edn_layout.rs | 118 +++++++++++++ edn/src/edn_lib.rs | 296 +++++++++++++++++++++++++++++++++ edn/src/example.edn | 12 ++ edn/src/lib.rs | 2 + engine/Cargo.toml | 1 - engine/src/edn.rs | 35 ---- engine/src/lib.rs | 4 +- layout/src/layout_edn.rs | 47 ------ layout/src/lib.rs | 4 +- src/groovebox/groovebox.edn | 12 ++ src/groovebox/groovebox_tui.rs | 148 ++++++++++++----- src/lib.rs | 8 +- 17 files changed, 676 insertions(+), 133 deletions(-) create mode 100644 edn/Cargo.lock create mode 100644 edn/Cargo.toml create mode 100644 edn/src/edn_layout.rs create mode 100644 edn/src/edn_lib.rs create mode 100644 edn/src/example.edn create mode 100644 edn/src/lib.rs delete mode 100644 layout/src/layout_edn.rs create mode 100644 src/groovebox/groovebox.edn diff --git a/Cargo.lock b/Cargo.lock index 762b7603..50887011 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1369,7 +1369,6 @@ dependencies = [ "atomic_float", "backtrace", "clap", - "clojure-reader", "jack", "livi", "midly", @@ -1378,18 +1377,25 @@ dependencies = [ "quanta", "rand", "symphonia", + "tek_edn", "tek_layout", "toml", "uuid", "wavers", ] +[[package]] +name = "tek_edn" +version = "0.1.0" +dependencies = [ + "clojure-reader", +] + [[package]] name = "tek_engine" version = "0.2.0" dependencies = [ "better-panic", - "clojure-reader", "crossterm", "ratatui", ] diff --git a/Cargo.toml b/Cargo.toml index 3fcd44a1..73cd11b9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,11 +5,11 @@ version = "0.2.0" [dependencies] tek_layout = { path = "./layout" } +tek_edn = { optional = true, path = "./edn" } atomic_float = "1.0.0" backtrace = "0.3.72" clap = { version = "4.5.4", features = [ "derive" ] } -clojure-reader = "0.3.0" jack = { path = "./rust-jack" } livi = "0.7.4" midly = "0.5" @@ -27,6 +27,10 @@ wavers = "1.4.3" #vst3 = "0.1.0" #winit = { version = "0.30.4", features = [ "x11" ] } +[features] +default = ["edn"] +edn = ["tek_edn"] + [[bin]] name = "tek_arranger" path = "bin/cli_arranger.rs" diff --git a/README.md b/README.md index 40708f51..61cf10fe 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,12 @@ from there, use the commands in the `Justfile`, e.g.: just arranger ``` +note that `tek > 0.2.0-rc.7` will require rust nightly +for the unstable features `type_alias_impl_trait` and +`impl_trait_in_assoc_type`. make some noise for lucky +[**rust rfc2515**](https://github.com/rust-lang/rust/issues/63063) +if you want to see this buildable with stable/beta. + ## design goals ### lightweight diff --git a/edn/Cargo.lock b/edn/Cargo.lock new file mode 100644 index 00000000..c0add748 --- /dev/null +++ b/edn/Cargo.lock @@ -0,0 +1,92 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "clojure-reader" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9edf141eea627c101a97509266bc9f6ba8cd408618f5e2ac4a0cb6b64b1d4ea8" +dependencies = [ + "ordered-float", +] + +[[package]] +name = "const_panic" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53857514f72ee4a2b583de67401e3ff63a5472ca4acf289d09a9ea7636dfec17" + +[[package]] +name = "konst" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4381b9b00c55f251f2ebe9473aef7c117e96828def1a7cb3bd3f0f903c6894e9" +dependencies = [ + "const_panic", + "konst_kernel", + "konst_proc_macros", + "typewit", +] + +[[package]] +name = "konst_kernel" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4b1eb7788f3824c629b1116a7a9060d6e898c358ebff59070093d51103dcc3c" +dependencies = [ + "typewit", +] + +[[package]] +name = "konst_proc_macros" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00af7901ba50898c9e545c24d5c580c96a982298134e8037d8978b6594782c07" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "ordered-float" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bb71e1b3fa6ca1c61f383464aaf2bb0e2f8e772a1f01d486832464de363b951" +dependencies = [ + "num-traits", +] + +[[package]] +name = "tek_edn" +version = "0.1.0" +dependencies = [ + "clojure-reader", + "konst", +] + +[[package]] +name = "typewit" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb77c29baba9e4d3a6182d51fa75e3215c7fd1dab8f4ea9d107c716878e55fc0" +dependencies = [ + "typewit_proc_macros", +] + +[[package]] +name = "typewit_proc_macros" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e36a83ea2b3c704935a01b4642946aadd445cea40b10935e3f8bd8052b8193d6" diff --git a/edn/Cargo.toml b/edn/Cargo.toml new file mode 100644 index 00000000..1ee74954 --- /dev/null +++ b/edn/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "tek_edn" +edition = "2021" +version = "0.1.0" + +[dependencies] +clojure-reader = "0.3.0" +konst = "0.3.16" diff --git a/edn/src/edn_layout.rs b/edn/src/edn_layout.rs new file mode 100644 index 00000000..3773374b --- /dev/null +++ b/edn/src/edn_layout.rs @@ -0,0 +1,118 @@ +use crate::*; + +#[cfg(test)] #[test] fn test_edn_layout () -> Result<(), ParseError> { + let source = include_str!("example.edn"); + let layout = Item::read_all(source)?; + panic!("{layout:?}"); + let content = EdnLayout::from(&layout); + Ok(()) +} + +impl<'a> From<&'a [Item]> for EdnLayout<'a> { + fn from (items: &'a [Item]) -> Self { + Self(items) + } +} + +pub struct EdnLayout<'a, E>(&'a [Item]); +impl<'a, E> EdnLayout<'a, E> { + fn get_content (&self) -> impl Content { + } +} + +//pub struct EdnContent<'a, T>(T, &'a [Item]); + +#[macro_export] macro_rules! edn_ns { + ($name:literal = $Host:ident<$E:ident: $Engine:path> |$args:ident| { $( + ($fn:literal + $Struct:ident + $(<$($G:ident$(: $Generic:ty)?),+>)? + $(::$Variant:ident)? + ($($arg:expr),*)) + )* }) => { + //pub trait $Host<$E: Engine> { + //fn read_one <'e> (edn: &[Edn<'e>]) -> impl Content<$E> { + //if let Some(Edn::Symbol(name)) = edn.get(0) { + //match *name { + //$( + //$fn => $Struct$(::$Variant)?($($arg),+), + //)* + //_ => {} + //} + //} else { + //panic!("invalid edn") + //} + //} + //} + }; +} + +edn_ns! { + + "when" = When(args[0].into(), args[1].into()), + + "either" = Either(args[0].into(), args[1].into(), args[2].into()), + + "map" = Map(args[0].into(), args[1].into()), + + "fill" = edn_ns! { + "x" = Fixed::X(args[0].into(), args[1].into()), + "y" = Fixed::Y(args[0].into(), args[1].into()), + "xy" = Fixed::XY(args[0].into(), args[1].into(), args[2].into()), + }, + + "fixed" = edn_ns! { + "x" = Fixed::X(args[0].into(), args[1].into()), + "y" = Fixed::Y(args[0].into(), args[1].into()), + "xy" = Fixed::XY(args[0].into(), args[1].into(), args[2].into()), + }, + + "shrink" = edn_ns! { + "x" = Shrink::X(args[0].into(), args[1].into()), + "y" = Shrink::Y(args[0].into(), args[1].into()), + "xy" = Shrink::XY(args[0].into(), args[1].into(), args[2].into()), + }, + + "expand" = edn_ns! { + "x" = Expand::X(args[0].into(), args[1].into()), + "y" = Expand::Y(args[0].into(), args[1].into()), + "xy" = Expand::XY(args[0].into(), args[1].into(), args[2].into()), + }, + + "min" = edn_ns! { + "x" = Min::X(args[0].into(), args[1].into()), + "y" = Min::Y(args[0].into(), args[1].into()), + "xy" = Min::XY(args[0].into(), args[1].into(), args[2].into()), + }, + + "max" = edn_ns! { + "x" = Max::X(args[0].into(), args[1].into()), + "y" = Max::Y(args[0].into(), args[1].into()), + "xy" = Max::XY(args[0].into(), args[1].into(), args[2].into()), + }, + + "push" = edn_ns! { + "x" = Push::X(args[0].into(), args[1].into()), + "y" = Push::Y(args[0].into(), args[1].into()), + "xy" = Push::XY(args[0].into(), args[1].into(), args[2].into()), + }, + + "pull" = edn_ns! { + "x" = Pull::X(args[0].into(), args[1].into()), + "y" = Pull::Y(args[0].into(), args[1].into()), + "xy" = Pull::XY(args[0].into(), args[1].into(), args[2].into()), + }, + + "margin" = edn_ns! { + "x" = Margin::X(args[0].into(), args[1].into()), + "y" = Margin::Y(args[0].into(), args[1].into()), + "xy" = Margin::XY(args[0].into(), args[1].into(), args[2].into()), + }, + + "padding" = edn_ns! { + "x" = Padding::X(args[0].into(), args[1].into()), + "y" = Padding::Y(args[0].into(), args[1].into()), + "xy" = Padding::XY(args[0].into(), args[1].into(), args[2].into()), + }, + +} diff --git a/edn/src/edn_lib.rs b/edn/src/edn_lib.rs new file mode 100644 index 00000000..92f14b92 --- /dev/null +++ b/edn/src/edn_lib.rs @@ -0,0 +1,296 @@ +use std::sync::{Arc, RwLock}; +use std::collections::BTreeMap; +pub use clojure_reader::edn::Edn; + +#[cfg(test)] #[test] fn test_edn () -> Result<(), ParseError> { + use Item::*; + assert_eq!(Item::read_all("")?, + vec![]); + assert_eq!(Item::read_all(" ")?, + vec![]); + assert_eq!(Item::read_all("1234")?, + vec![Num(1234)]); + assert_eq!(Item::read_all("1234 5 67")?, + vec![Num(1234), Num(5), Num(67)]); + assert_eq!(Item::read_all("foo/bar")?, + vec![Key("foo/bar".into())]); + assert_eq!(Item::read_all(":symbol")?, + vec![Sym(":symbol".into())]); + assert_eq!(Item::read_all(" foo/bar :baz 456")?, + vec![Key("foo/bar".into()), Sym(":baz".into()), Num(456)]); + assert_eq!(Item::read_all(" (foo/bar :baz 456) ")?, + vec![Exp(vec![Key("foo/bar".into()), Sym(":baz".into()), Num(456)])]); + Ok(()) +} + +fn number (digits: &str) -> usize { + let mut value = 0; + for c in digits.chars() { + value = 10 * value + digit(c); + } + value +} + +const fn digit (c: char) -> usize { + match c { '0' => 0, '1' => 1, '2' => 2, '3' => 3, '4' => 4, + '5' => 5, '6' => 6, '7' => 7, '8' => 8, '9' => 9, _ => unreachable!() } +} + +#[derive(Debug)] +pub enum ParseError { + Empty, + Unexpected(char), + Incomplete +} + +#[derive(Debug, Clone, Default, PartialEq)] +pub enum Item { + #[default] Nil, + Num(usize), + Sym(String), + Key(String), + Exp(Vec), +} + +impl Item { + pub fn read_all <'a> (mut source: &'a str) -> Result, ParseError> { + let mut items = vec![]; + loop { + if source.len() == 0 { + break + } + let (remaining, token) = Token::chomp(source)?; + match Item::read(token)? { Item::Nil => {}, item => items.push(item) }; + source = remaining + } + Ok(items) + } + pub fn read <'a> (token: Token<'a>) -> Result { + use Token::*; + Ok(match token { + Nil => Item::Nil, + Num(chars, index, length) => + Self::Num(number(&chars[index..index+length])), + Sym(chars, index, length) => + Self::Sym(chars[index..index+length].to_string()), + Key(chars, index, length) => + Self::Key(chars[index..index+length].to_string()), + Exp(chars, index, length, 0) => + Self::Exp(Self::read_all(&chars[index+1..(index+length).saturating_sub(1)])?), + _ => panic!("unclosed delimiter") + }) + } +} + + +#[derive(Debug, Copy, Clone, Default, PartialEq)] +pub enum Token<'a> { + #[default] Nil, + Num(&'a str, usize, usize), + Sym(&'a str, usize, usize), + Key(&'a str, usize, usize), + Exp(&'a str, usize, usize, usize), +} + +impl<'a> Token<'a> { + fn chomp (source: &'a str) -> Result<(&'a str, Self), ParseError> { + use Token::*; + let mut state = Self::default(); + for (index, c) in source.char_indices() { + state = match state { + // must begin expression + Nil => match c { + ' '|'\n'|'\r'|'\t' => Nil, + '(' => Exp(source, index, 1, 1), + ':' => Sym(source, index, 1), + '0'..='9' => Num(source, index, 1), + 'a'..='z' => Key(source, index, 1), + _ => return Err(ParseError::Unexpected(c)) + }, + Num(_, _, 0) => unreachable!(), + Sym(_, _, 0) => unreachable!(), + Key(_, _, 0) => unreachable!(), + Num(source, index, length) => match c { + '0'..='9' => Num(source, index, length + 1), + ' '|'\n'|'\r'|'\t' => return Ok((&source[index+length..], Num(source, index, length))), + _ => return Err(ParseError::Unexpected(c)) + }, + Sym(source, index, length) => match c { + 'a'..='z'|'0'..='9'|'-' => Sym(source, index, length + 1), + ' '|'\n'|'\r'|'\t' => return Ok((&source[index+length..], Sym(source, index, length))), + _ => return Err(ParseError::Unexpected(c)) + }, + Key(source, index, length) => match c { + 'a'..='z'|'0'..='9'|'-'|'/' => Key(source, index, length + 1), + ' '|'\n'|'\r'|'\t' => return Ok((&source[index+length..], Key(source, index, length))), + _ => return Err(ParseError::Unexpected(c)) + }, + Exp(source, index, length, 0) => match c { + ' '|'\n'|'\r'|'\t' => return Ok((&source[index+length..], Exp(source, index, length, 0))), + _ => return Err(ParseError::Unexpected(c)) + }, + Exp(source, index, length, depth) => match c { + ')' => Exp(source, index, length + 1, depth - 1), + '(' => Exp(source, index, length + 1, depth + 1), + _ => Exp(source, index, length + 1, depth) + }, + } + } + Ok(("", state)) + } +} + +//#[derive(Debug, Copy, Clone, Default, PartialEq)] +//pub struct Items<'a>(&'a [Item<'a>]); +//impl<'a> Items<'a> { + //fn iter (&'a self) -> ItemsIterator<'a> { + //ItemsIterator(0, self.0) + //} +//} + +//pub struct ItemsIterator<'a>(usize, &'a [Item<'a>]); +//impl<'a> Iterator for ItemsIterator<'a> { + //type Item = &'a Item<'a>; + //fn next (&mut self) -> Option { + //let item = self.1.get(self.0); + //self.0 += 1; + //item + //} +//} + +/* + +nice but doesn't work without compile time slice concat +(which i guess could be implemeted using an unsafe linked list?) +never done that one before im ny life, might try + +use konst::slice_concat; + +const fn read <'a> ( + chars: impl Iterator +) -> Result, ParseError> { + use Range::*; + let mut state = Range::Nil; + let mut tokens: &[Range<'a>] = &[]; + while let Some(c) = chars.next() { + state = match state { + // must begin expression + Nil => match c { + ' ' => Nil, + '(' => Exp(&[]), + ':' => Sym(&[]), + '1'..'9' => Num(digit(c)), + 'a'..'z' => Key(&[&[c]]), + _ => return Err(ParseError::Unexpected(c)) + }, + Num(b) => match c { + ' ' => return Ok(Num(digit(c))), + '1'..'9' => Num(b*10+digit(c)), + _ => return Err(ParseError::Unexpected(c)) + } + Sym([]) => match c { + 'a'..'z' => Sym(&[c]), + _ => return Err(ParseError::Unexpected(c)) + }, + Sym([b @ ..]) => match c { + ' ' => return Ok(Sym(&b)), + 'a'..'z' | '0'..'9' | '-' => Sym(&[..b, c]), + _ => return Err(ParseError::Unexpected(c)) + } + Key([[b @ ..]]) => match c { + ' ' => return Ok(Key(&[&b])), + '/' => Key(&[&b, &[]]), + 'a'..'z' | '0'..'9' | '-' => Key(&[&[..b, c], &[]]), + _ => return Err(ParseError::Unexpected(c)) + } + Key([s @ .., []]) => match c { + 'a'..'z' => Key(&[..s, &[c]]), + _ => return Err(ParseError::Unexpected(c)) + } + Key([s @ .., [b @ ..]]) => match c { + '/' => Key([..s, &b, &[]]), + 'a'..'z' | '0'..'9' | '-' => Key(&[..s, &[..b, c]]), + _ => return Err(ParseError::Unexpected(c)) + } + // expression must begin with key or symbol + Exp([]) => match c { + ' ' => Exp(&[]), + ')' => return Err(ParseError::Empty), + ':' => Exp(&[Sym(&[':'])]), + c => Exp(&[Key(&[&[c]])]), + }, + + // expression can't begin with number + Exp([Num(num)]) => return Err(ParseError::Unexpected(c)), + + // symbol begins with : and lowercase a-z + Exp([Sym([':'])]) => match c { + 'a'..'z' => Exp(&[Sym(&[':', c])]), + _ => return Err(ParseError::Unexpected(c)), + }, + + // any other char is part of symbol until space or ) + Exp([Sym([':', b @ ..])]) => match c { + ')' => { tokens = &[..tokens, Exp(&[Sym(&[":", ..b])])]; Nil }, + ' ' => Exp(&[Sym(&[':', ..b]), Nil]), + c => Exp(&[Sym(&[':', ..b, c])]), + }, + + // key begins with lowercase a-z + Exp([Key([])]) => match c { + 'a'..'z' => Exp([Key([[c]])]), + _ => return Err(ParseError::Unexpected(c)), + }, + + // any other char is part of key until slash space or ) + Exp([Key([[b @ ..]])]) => match c { + '/' => Exp(&[Key(&[[..b], []])]), + ' ' => Exp(&[Key(&[[..b]]), Nil]), + ')' => { tokens = &[..tokens, Exp(&[Sym(&[":", ..b])])]; Nil }, + c => Exp(&[Key(&[[..b, c]])]) + } + + // slash adds new section to key + Exp([Key([b @ .., []])]) => match c { + '/' => Exp(&[Key(&[[..b], []])]), + ' ' => Exp(&[Key(&[[..b]]), Nil]), + ')' => { tokens = &[..tokens, Exp(&[Sym(&[":", ..b])])]; Nil }, + c => Exp(&[Key(&[[..b, c]])]) + } + + } + } + Ok(state) +} +*/ + + +/// EDN parsing helper. +#[macro_export] macro_rules! edn { + ($edn:ident { $($pat:pat => $expr:expr),* $(,)? }) => { + match $edn { $($pat => $expr),* } + }; + ($edn:ident in $args:ident { $($pat:pat => $expr:expr),* $(,)? }) => { + for $edn in $args { + edn!($edn { $($pat => $expr),* }) + } + }; +} + +pub trait FromEdn: Sized { + const ID: &'static str; + fn from_edn (context: C, expr: &[Edn<'_>]) -> + std::result::Result>; +} + +/// Implements the [FromEdn] trait. +#[macro_export] macro_rules! from_edn { + ($id:expr => |$context:tt:$Context:ty, $args:ident| -> $T:ty $body:block) => { + impl FromEdn<$Context> for $T { + const ID: &'static str = $id; + fn from_edn <'e> ($context: $Context, $args: &[Edn<'e>]) -> Usually { + $body + } + } + } +} diff --git a/edn/src/example.edn b/edn/src/example.edn new file mode 100644 index 00000000..afca85a5 --- /dev/null +++ b/edn/src/example.edn @@ -0,0 +1,12 @@ +(sized + (bsp/s (fill/x (fixed/y 2 (lay + (align/w :input-meter-l) + (align/e :input-meter-r) + (align/x :transport)))) + (bsp/n (row :clip-play :clip-next :clip-edit :edit-stat) + (bsp/n (max/y :sample-h (fill/xy :sample-view)) + (bsp/n (align/w (fixed/y 1 :sample-stat)) + (bsp/n (fixed/x :pool-w :pool-view) + (fill/xy (bsp/e + (fixed/x :samples-w (push/y :samples-y :samples-view)) + :midi-view)))))))) diff --git a/edn/src/lib.rs b/edn/src/lib.rs new file mode 100644 index 00000000..14841351 --- /dev/null +++ b/edn/src/lib.rs @@ -0,0 +1,2 @@ +mod edn_lib; pub use self::edn_lib::*; +mod edn_layout; pub use self::edn_layout::*; diff --git a/engine/Cargo.toml b/engine/Cargo.toml index f9005149..b72a07cb 100644 --- a/engine/Cargo.toml +++ b/engine/Cargo.toml @@ -7,4 +7,3 @@ version = "0.2.0" crossterm = "0.28.1" ratatui = { version = "0.29.0", features = [ "unstable-widget-ref", "underline-color" ] } better-panic = "0.3.0" -clojure-reader = "0.3.0" diff --git a/engine/src/edn.rs b/engine/src/edn.rs index 2a84d991..c7b7e813 100644 --- a/engine/src/edn.rs +++ b/engine/src/edn.rs @@ -1,36 +1 @@ use crate::*; - -use std::sync::{Arc, RwLock}; -use std::collections::BTreeMap; - -pub use clojure_reader::edn::Edn; - -/// EDN parsing helper. -#[macro_export] macro_rules! edn { - ($edn:ident { $($pat:pat => $expr:expr),* $(,)? }) => { - match $edn { $($pat => $expr),* } - }; - ($edn:ident in $args:ident { $($pat:pat => $expr:expr),* $(,)? }) => { - for $edn in $args { - edn!($edn { $($pat => $expr),* }) - } - }; -} - -pub trait FromEdn: Sized { - const ID: &'static str; - fn from_edn (context: C, expr: &[Edn<'_>]) -> Usually; -} - -/// Implements the [FromEdn] trait. -#[macro_export] macro_rules! from_edn { - ($id:expr => |$context:tt:$Context:ty, $args:ident| -> $T:ty $body:block) => { - impl FromEdn<$Context> for $T { - const ID: &'static str = $id; - fn from_edn <'e> ($context: $Context, $args: &[Edn<'e>]) -> Usually { - $body - } - } - } -} - diff --git a/engine/src/lib.rs b/engine/src/lib.rs index e792e371..a294d35d 100644 --- a/engine/src/lib.rs +++ b/engine/src/lib.rs @@ -1,10 +1,12 @@ +#![feature(associated_type_defaults)] + //mod component; pub use self::component::*; mod engine; pub use self::engine::*; mod input; pub use self::input::*; mod output; pub use self::output::*; pub mod tui; -pub mod edn; +#[cfg(feature = "edn")] pub mod edn; pub use std::error::Error; diff --git a/layout/src/layout_edn.rs b/layout/src/layout_edn.rs deleted file mode 100644 index e466483e..00000000 --- a/layout/src/layout_edn.rs +++ /dev/null @@ -1,47 +0,0 @@ -use crate::*; -use ::tek_engine::edn::*; - -macro_rules! edn_module { - ($name:literal = $Host:ident<$E:ident: $Engine:path> { $( - (defn $fn:ident - $(<$($G:ident: $Generic:ty),+>)? - $Struct:ident($($arg:ident : $Arg:ty),*)) - )* }) => { - pub trait $Host<$E: Engine> { - pub fn read_one <'e> (edn: &[Edn<'e>]) -> impl Content<$E> { - if let Some(Edn::Symbol(name)) = edn.get(0) { - match name { - $( - stringify!($fn) => - ),* - } - } else { - panic!("invalid edn") - } - } - $( - - )* - } - }; -} - -//edn_module! { - //(host LayoutEdn) - //(name "layout") - - //(defn when > - //When(cond: bool, item: A)) - - //(defn either , B: Content> - //Either(cond: bool, a: A, b: B)) - - //(defn map < - //A: Content, - //B: Content, - //I: Iterator + Send + Sync, - //F: Fn() -> I + Send + Sync, - //G: Fn(A, usize)->B + Send + Sync - //> - //Map(get_iterator: I, callback: G)) -//} diff --git a/layout/src/lib.rs b/layout/src/lib.rs index 56f2a87d..d87dac79 100644 --- a/layout/src/lib.rs +++ b/layout/src/lib.rs @@ -1,8 +1,8 @@ -//mod collection; pub use self::collection::*; +#![feature(type_alias_impl_trait)] +#![feature(impl_trait_in_assoc_type)] mod align; pub use self::align::*; mod direction; pub use self::direction::*; -mod layout_edn; pub use self::edn::*; mod measure; pub use self::measure::*; mod ops; pub use self::ops::*; mod transform_xy; pub use self::transform_xy::*; diff --git a/src/groovebox/groovebox.edn b/src/groovebox/groovebox.edn new file mode 100644 index 00000000..afca85a5 --- /dev/null +++ b/src/groovebox/groovebox.edn @@ -0,0 +1,12 @@ +(sized + (bsp/s (fill/x (fixed/y 2 (lay + (align/w :input-meter-l) + (align/e :input-meter-r) + (align/x :transport)))) + (bsp/n (row :clip-play :clip-next :clip-edit :edit-stat) + (bsp/n (max/y :sample-h (fill/xy :sample-view)) + (bsp/n (align/w (fixed/y 1 :sample-stat)) + (bsp/n (fixed/x :pool-w :pool-view) + (fill/xy (bsp/e + (fixed/x :samples-w (push/y :samples-y :samples-view)) + :midi-view)))))))) diff --git a/src/groovebox/groovebox_tui.rs b/src/groovebox/groovebox_tui.rs index 03acbae1..1b5125fe 100644 --- a/src/groovebox/groovebox_tui.rs +++ b/src/groovebox/groovebox_tui.rs @@ -1,49 +1,111 @@ use crate::*; use super::*; +use std::marker::ConstParamTy; -render!(Tui: (self: Groovebox) => self.size.of( - Bsp::s(self.toolbar_view(), - Bsp::n(self.selector_view(), - Bsp::n(self.sample_view(), - Bsp::n(self.status_view(), - Bsp::w(self.pool_view(), Fill::xy(Bsp::e(self.sampler_view(), &self.editor))))))))); +const GROOVEBOX_EDN: &'static str = include_str!("groovebox.edn"); -impl Groovebox { - fn toolbar_view (&self) -> impl Content + use<'_> { - Fill::x(Fixed::y(2, lay!( - Align::w(Meter("L/", self.sampler.input_meter[0])), - Align::e(Meter("R/", self.sampler.input_meter[1])), - Align::x(TransportView::new(true, &self.player.clock)), - ))) - } - fn selector_view (&self) -> impl Content + use<'_> { - row!( - ClipSelected::play_phrase(&self.player), - ClipSelected::next_phrase(&self.player), - MidiEditClip(&self.editor), - MidiEditStatus(&self.editor), - ) - } - fn sample_view (&self) -> impl Content + use<'_> { - let note_pt = self.editor.note_point(); - let sample_h = if self.compact { 0 } else { 5 }; - Max::y(sample_h, Fill::xy( - SampleViewer::from_sampler(&self.sampler, note_pt))) - } - fn status_view (&self) -> impl Content + use<'_> { - let note_pt = self.editor.note_point(); - Align::w(Fixed::y(1, SamplerStatus(&self.sampler, note_pt))) - } - fn pool_view (&self) -> impl Content + use<'_> { - let w = self.size.w(); - let pool_w = if w > 60 { 20 } else if w > 40 { 15 } else { 10 }; - Fixed::x(if self.compact { 5 } else { pool_w }, - PoolView(self.compact, &self.pool)) - } - fn sampler_view (&self) -> impl Content + use<'_> { - let sampler_w = if self.compact { 4 } else { 11 }; - let sampler_y = if self.compact { 1 } else { 0 }; - Fixed::x(sampler_w, Push::y(sampler_y, Fill::y( - SampleList::new(self.compact, &self.sampler, &self.editor)))) +impl Content for Groovebox { + fn content (&self) -> impl Content { + EdnView::parse(self.edn.as_slice()) } } +//render!(Tui: (self: Groovebox) => self.size.of( + //Bsp::s(self.toolbar_view(), + //Bsp::n(self.selector_view(), + //Bsp::n(self.sample_view(), + //Bsp::n(self.status_view(), + //Bsp::w(self.pool_view(), Fill::xy(Bsp::e(self.sampler_view(), &self.editor))))))))); + +macro_rules! edn_context { + ($Struct:ident |$l:lifetime, $state:ident| { + $($key:literal = $field:ident: $Type:ty => $expr:expr,)* + }) => { + + #[derive(Default)] + pub struct EdnView<$l> { $($field: Option<$Type>),* } + + impl<$l> EdnView<$l> { + pub fn parse <'e> (edn: &[Edn<'e>]) -> impl Fn(&$Struct) + use<'e> { + let imports = Self::imports_all(edn); + move |state| { + let mut context = EdnView::default(); + for import in imports.iter() { + context.import(state, import) + } + } + } + fn imports_all <'e> (edn: &[Edn<'e>]) -> Vec<&'e str> { + let mut imports = vec![]; + for edn in edn.iter() { + for import in Self::imports_one(edn) { + imports.push(import); + } + } + imports + } + fn imports_one <'e> (edn: &Edn<'e>) -> Vec<&'e str> { + match edn { + Edn::Symbol(import) => vec![import], + Edn::List(edn) => Self::imports_all(edn.as_slice()), + _ => vec![], + } + } + pub fn import (&mut self, $state: &$l$Struct, key: &str) { + match key { + $($key => self.$field = Some($expr),)* + _ => {} + } + } + } + } +} + +edn_context!(Groovebox |'a, state| { + ":input-meter-l" = input_meter_l: Meter<'a> => Meter("L/", state.sampler.input_meter[0]), + ":input-meter-r" = input_meter_r: Meter<'a> => Meter("R/", state.sampler.input_meter[1]), + + ":transport" = transport: TransportView<'a> => TransportView::new(true, &state.player.clock), + + ":clip-play" = clip_play: ClipSelected => ClipSelected::play_phrase(&state.player), + ":clip-next" = clip_next: ClipSelected => ClipSelected::next_phrase(&state.player), + ":clip-edit" = clip_edit: MidiEditClip<'a> => MidiEditClip(&state.editor), + + ":edit-stat" = edit_stat: MidiEditStatus<'a> => MidiEditStatus(&state.editor), + + ":sample-h" = sample_h: u16 => if state.compact { 0 } else { 5 }, + ":sample-view" = sample_view: SampleViewer => SampleViewer::from_sampler( + &state.sampler, state.editor.note_point()), + ":sample-stat" = sample_stat: SamplerStatus<'a> => SamplerStatus( + &state.sampler, state.editor.note_point()), + + ":pool-w" = pool_w: u16 => if state.compact { 5 } else { + let w = state.size.w(); + if w > 60 { 20 } else if w > 40 { 15 } else { 10 } }, + ":pool-view" = pool_view: PoolView<'a> => PoolView(state.compact, &state.pool), + + ":samples-w" = samples_w: u16 => if state.compact { 4 } else { 11 }, + ":samples-y" = samples_y: u16 => if state.compact { 1 } else { 0 }, + ":samples-view" = samples_view: SampleList<'a> => SampleList::new( + state.compact, &state.sampler, &state.editor), + + ":midi-view" = midi_view: &'a MidiEditor => &state.editor, +}); + +//impl Groovebox { + //fn status_view (&self) -> impl Content + use<'_> { + //let note_pt = self.editor.note_point(); + //Align::w(Fixed::y(1, )) + //} + //fn pool_view (&self) -> impl Content + use<'_> { + //let w = self.size.w(); + //let pool_w = if w > 60 { 20 } else if w > 40 { 15 } else { 10 }; + //Fixed::x(if self.compact { 5 } else { pool_w }, + //) + //} + //fn sampler_view (&self) -> impl Content + use<'_> { + //let sampler_w = if self.compact { 4 } else { 11 }; + //let sampler_y = if self.compact { 1 } else { 0 }; + //Fixed::x(sampler_w, Push::y(sampler_y, Fill::y( + //SampleList::new(self.compact, &self.sampler, &self.editor)))) + //} +//} diff --git a/src/lib.rs b/src/lib.rs index 84d9c50b..a88f93d0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,9 @@ #![allow(unused)] #![allow(clippy::unit_arg)] +#![feature(adt_const_params)] +#![feature(type_alias_impl_trait)] +#![feature(impl_trait_in_assoc_type)] +#![feature(associated_type_defaults)] pub use ::tek_layout; pub use ::tek_layout::tek_engine; @@ -12,7 +16,6 @@ pub(crate) use ::tek_layout::{ Output, Content, Thunk, render, Input, Handle, handle, kexp, kpat, - edn::*, tui::{ Tui, TuiIn, key, ctrl, shift, alt, @@ -33,6 +36,9 @@ pub(crate) use ::tek_layout::{ } }; +pub use ::tek_edn; +pub(crate) use ::tek_edn::*; + pub(crate) use std::cmp::{Ord, Eq, PartialEq}; pub(crate) use std::collections::BTreeMap; pub(crate) use std::error::Error; From ac3827b8f3e3d2306eb688171399e4ac73f00200 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sat, 4 Jan 2025 10:44:20 +0100 Subject: [PATCH 009/652] wip: reenable dynamic dispatch --- edn/Cargo.lock | 698 ++++++++++++++++++++++++++++++++ edn/Cargo.toml | 5 + edn/src/edn_layout.rs | 284 ++++++++----- edn/src/edn_lib.rs | 31 +- edn/src/lib.rs | 32 ++ engine/src/output.rs | 32 +- engine/src/tui/tui_output.rs | 2 +- layout/src/align.rs | 6 +- layout/src/ops.rs | 20 +- layout/src/transform_xy_unit.rs | 18 +- src/groovebox/groovebox_tui.rs | 63 +-- 11 files changed, 997 insertions(+), 194 deletions(-) diff --git a/edn/Cargo.lock b/edn/Cargo.lock index c0add748..fa4411ba 100644 --- a/edn/Cargo.lock +++ b/edn/Cargo.lock @@ -2,12 +2,85 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + [[package]] name = "autocfg" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +[[package]] +name = "backtrace" +version = "0.3.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets", +] + +[[package]] +name = "better-panic" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fa9e1d11a268684cbd90ed36370d7577afb6c62d912ddff5c15fc34343e5036" +dependencies = [ + "backtrace", + "console", +] + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "cassowary" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" + +[[package]] +name = "castaway" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0abae9be0aaf9ea96a3b1b8b1b55c602ca751eba1b1500220cea4ecbafe7c0d5" +dependencies = [ + "rustversion", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + [[package]] name = "clojure-reader" version = "0.3.0" @@ -17,12 +90,201 @@ dependencies = [ "ordered-float", ] +[[package]] +name = "compact_str" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b79c4069c6cad78e2e0cdfcbd26275770669fb39fd308a752dc110e83b9af32" +dependencies = [ + "castaway", + "cfg-if", + "itoa", + "rustversion", + "ryu", + "static_assertions", +] + +[[package]] +name = "console" +version = "0.15.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea3c6ecd8059b57859df5c69830340ed3c41d30e3da0c1cbed90a96ac853041b" +dependencies = [ + "encode_unicode", + "libc", + "once_cell", + "windows-sys 0.59.0", +] + [[package]] name = "const_panic" version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53857514f72ee4a2b583de67401e3ff63a5472ca4acf289d09a9ea7636dfec17" +[[package]] +name = "crossterm" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" +dependencies = [ + "bitflags", + "crossterm_winapi", + "mio", + "parking_lot", + "rustix", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + +[[package]] +name = "darling" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + +[[package]] +name = "encode_unicode" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" + +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "indoc" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" + +[[package]] +name = "instability" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "894813a444908c0c8c0e221b041771d107c4a21de1d317dc49bcc66e3c9e5b3f" +dependencies = [ + "darling", + "indoc", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" + [[package]] name = "konst" version = "0.3.16" @@ -50,6 +312,70 @@ version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00af7901ba50898c9e545c24d5c580c96a982298134e8037d8978b6594782c07" +[[package]] +name = "libc" +version = "0.2.169" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" + +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "lru" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" +dependencies = [ + "hashbrown", +] + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "miniz_oxide" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ffbe83022cedc1d264172192511ae958937694cd57ce297164951b8b3568394" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.52.0", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -59,6 +385,21 @@ dependencies = [ "autocfg", ] +[[package]] +name = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" + [[package]] name = "ordered-float" version = "4.6.0" @@ -68,12 +409,224 @@ dependencies = [ "num-traits", ] +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "proc-macro2" +version = "1.0.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "ratatui" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eabd94c2f37801c20583fc49dd5cd6b0ba68c716787c2dd6ed18571e1e63117b" +dependencies = [ + "bitflags", + "cassowary", + "compact_str", + "crossterm", + "indoc", + "instability", + "itertools", + "lru", + "paste", + "strum", + "unicode-segmentation", + "unicode-truncate", + "unicode-width 0.2.0", +] + +[[package]] +name = "redox_syscall" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" +dependencies = [ + "bitflags", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustix" +version = "0.38.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustversion" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "signal-hook" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" +dependencies = [ + "libc", + "mio", + "signal-hook", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + +[[package]] +name = "syn" +version = "2.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "987bc0be1cdea8b10216bd06e2ca407d40b9543468fafd3ddfb02f36e77f71f3" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "tek_edn" version = "0.1.0" dependencies = [ "clojure-reader", "konst", + "tek_layout", +] + +[[package]] +name = "tek_engine" +version = "0.2.0" +dependencies = [ + "better-panic", + "crossterm", + "ratatui", +] + +[[package]] +name = "tek_layout" +version = "0.2.0" +dependencies = [ + "tek_engine", ] [[package]] @@ -90,3 +643,148 @@ name = "typewit_proc_macros" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e36a83ea2b3c704935a01b4642946aadd445cea40b10935e3f8bd8052b8193d6" + +[[package]] +name = "unicode-ident" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-truncate" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf" +dependencies = [ + "itertools", + "unicode-segmentation", + "unicode-width 0.1.14", +] + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-width" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" diff --git a/edn/Cargo.toml b/edn/Cargo.toml index 1ee74954..d07d4640 100644 --- a/edn/Cargo.toml +++ b/edn/Cargo.toml @@ -6,3 +6,8 @@ version = "0.1.0" [dependencies] clojure-reader = "0.3.0" konst = "0.3.16" +tek_layout = { optional = true, path = "../layout" } + +[features] +default = ["layout"] +layout = [ "tek_layout" ] diff --git a/edn/src/edn_layout.rs b/edn/src/edn_layout.rs index 3773374b..1e4c7d3e 100644 --- a/edn/src/edn_layout.rs +++ b/edn/src/edn_layout.rs @@ -1,118 +1,194 @@ use crate::*; +use std::marker::PhantomData; +use ::tek_layout::{*, tek_engine::{Content, Render, Engine, Thunk}}; +use Item::*; -#[cfg(test)] #[test] fn test_edn_layout () -> Result<(), ParseError> { - let source = include_str!("example.edn"); - let layout = Item::read_all(source)?; - panic!("{layout:?}"); - let content = EdnLayout::from(&layout); - Ok(()) -} +pub struct EdnContent<'a, E, T: AsRef>(PhantomData, &'a [Item]); -impl<'a> From<&'a [Item]> for EdnLayout<'a> { - fn from (items: &'a [Item]) -> Self { - Self(items) +impl<'a, E, T: AsRef> From<&'a [Item]> for EdnContent<'a, E, T> { + fn from (items: &'a [Item]) -> Self { + Self(Default::default(), items) } } -pub struct EdnLayout<'a, E>(&'a [Item]); -impl<'a, E> EdnLayout<'a, E> { - fn get_content (&self) -> impl Content { - } -} +//impl<'a, E: Engine> Content for EdnContent<'a, E> { + /*todo*/ +//} //pub struct EdnContent<'a, T>(T, &'a [Item]); -#[macro_export] macro_rules! edn_ns { - ($name:literal = $Host:ident<$E:ident: $Engine:path> |$args:ident| { $( - ($fn:literal - $Struct:ident - $(<$($G:ident$(: $Generic:ty)?),+>)? - $(::$Variant:ident)? - ($($arg:expr),*)) - )* }) => { - //pub trait $Host<$E: Engine> { - //fn read_one <'e> (edn: &[Edn<'e>]) -> impl Content<$E> { - //if let Some(Edn::Symbol(name)) = edn.get(0) { - //match *name { - //$( - //$fn => $Struct$(::$Variant)?($($arg),+), - //)* - //_ => {} - //} - //} else { - //panic!("invalid edn") - //} - //} - //} - }; +pub trait EdnLayout { + fn get_bool (&self, item: &Item<&str>) -> bool { todo!() } + fn get_unit (&self, item: &Item<&str>) -> E::Unit { todo!() } + fn get_usize (&self, key: &str) -> usize { todo!() } + fn get_content (&self, item: &Item<&str>) -> &dyn Render { todo!() } + fn parse <'a: 'static> (&'a self, items: &[Item<&str>]) -> Box + 'a> { + match items { + [Key("when"), c, a, ..] => + Box::new(When(self.get_bool(c), self.get_content(a))), + [Key("either"), c, a, b, ..] => + Box::new(Either(self.get_bool(c), self.get_content(a), self.get_content(b))), + + [Key("fill"), a, ..] => + Box::new(Fill::xy(self.get_content(a))), + [Key("fill/x"), a, ..] => Box::new(Fill::x(self.get_content(a))), + [Key("fill/y"), a, ..] => Box::new(Fill::y(self.get_content(a))), + + [Key("fixed"), x, y, a, ..] => + Box::new(Fixed::xy(self.get_unit(x), self.get_unit(y), self.get_content(a))), + [Key("fixed/x"), x, a, ..] => Box::new(Fixed::x(self.get_unit(x), self.get_content(a))), + [Key("fixed/y"), y, a, ..] => Box::new(Fixed::y(self.get_unit(y), self.get_content(a))), + + [Key("shrink"), x, y, a, ..] => + Box::new(Shrink::xy(self.get_unit(x), self.get_unit(y), self.get_content(a))), + [Key("shrink/x"), x, a, ..] => Box::new(Shrink::x(self.get_unit(x), self.get_content(a))), + [Key("shrink/y"), y, a, ..] => Box::new(Shrink::y(self.get_unit(y), self.get_content(a))), + + [Key("expand"), x, y, a, ..] => + Box::new(Expand::xy(self.get_unit(x), self.get_unit(y), self.get_content(a))), + [Key("expand/x"), x, a, ..] => Box::new(Expand::x(self.get_unit(x), self.get_content(a))), + [Key("expand/y"), y, a, ..] => Box::new(Expand::y(self.get_unit(y), self.get_content(a))), + + [Key("push"), x, y, a, ..] => + Box::new(Push::xy(self.get_unit(x), self.get_unit(y), self.get_content(a))), + [Key("push/x"), x, a, ..] => Box::new(Push::x(self.get_unit(x), self.get_content(a))), + [Key("push/y"), y, a, ..] => Box::new(Push::y(self.get_unit(y), self.get_content(a))), + + [Key("pull"), x, y, a, ..] => + Box::new(Pull::xy(self.get_unit(x), self.get_unit(y), self.get_content(a))), + [Key("pull/x"), x, a, ..] => Box::new(Pull::x(self.get_unit(x), self.get_content(a))), + [Key("pull/y"), y, a, ..] => Box::new(Pull::y(self.get_unit(y), self.get_content(a))), + + _ => Box::new(()) + //[Key("when"), Sym(condition), Sym(template)] => When( + //self.get_bool(condition), + //self.get_content(template)), + //[Key("when"), Sym(condition), Exp(template)] => When( + //self.get_bool(condition), + //Thunk::new(||EdnLayout::parse(template))), + + } + } } -edn_ns! { +macro_rules! edn_context { + ($Struct:ident |$l:lifetime, $state:ident| { + $($key:literal = $field:ident: $Type:ty => $expr:expr,)* + }) => { - "when" = When(args[0].into(), args[1].into()), - - "either" = Either(args[0].into(), args[1].into(), args[2].into()), - - "map" = Map(args[0].into(), args[1].into()), - - "fill" = edn_ns! { - "x" = Fixed::X(args[0].into(), args[1].into()), - "y" = Fixed::Y(args[0].into(), args[1].into()), - "xy" = Fixed::XY(args[0].into(), args[1].into(), args[2].into()), - }, - - "fixed" = edn_ns! { - "x" = Fixed::X(args[0].into(), args[1].into()), - "y" = Fixed::Y(args[0].into(), args[1].into()), - "xy" = Fixed::XY(args[0].into(), args[1].into(), args[2].into()), - }, - - "shrink" = edn_ns! { - "x" = Shrink::X(args[0].into(), args[1].into()), - "y" = Shrink::Y(args[0].into(), args[1].into()), - "xy" = Shrink::XY(args[0].into(), args[1].into(), args[2].into()), - }, - - "expand" = edn_ns! { - "x" = Expand::X(args[0].into(), args[1].into()), - "y" = Expand::Y(args[0].into(), args[1].into()), - "xy" = Expand::XY(args[0].into(), args[1].into(), args[2].into()), - }, - - "min" = edn_ns! { - "x" = Min::X(args[0].into(), args[1].into()), - "y" = Min::Y(args[0].into(), args[1].into()), - "xy" = Min::XY(args[0].into(), args[1].into(), args[2].into()), - }, - - "max" = edn_ns! { - "x" = Max::X(args[0].into(), args[1].into()), - "y" = Max::Y(args[0].into(), args[1].into()), - "xy" = Max::XY(args[0].into(), args[1].into(), args[2].into()), - }, - - "push" = edn_ns! { - "x" = Push::X(args[0].into(), args[1].into()), - "y" = Push::Y(args[0].into(), args[1].into()), - "xy" = Push::XY(args[0].into(), args[1].into(), args[2].into()), - }, - - "pull" = edn_ns! { - "x" = Pull::X(args[0].into(), args[1].into()), - "y" = Pull::Y(args[0].into(), args[1].into()), - "xy" = Pull::XY(args[0].into(), args[1].into(), args[2].into()), - }, - - "margin" = edn_ns! { - "x" = Margin::X(args[0].into(), args[1].into()), - "y" = Margin::Y(args[0].into(), args[1].into()), - "xy" = Margin::XY(args[0].into(), args[1].into(), args[2].into()), - }, - - "padding" = edn_ns! { - "x" = Padding::X(args[0].into(), args[1].into()), - "y" = Padding::Y(args[0].into(), args[1].into()), - "xy" = Padding::XY(args[0].into(), args[1].into(), args[2].into()), - }, + #[derive(Default)] + pub struct EdnView<$l> { $($field: Option<$Type>),* } + impl<$l> EdnView<$l> { + pub fn parse <'e> (edn: &[Edn<'e>]) -> impl Fn(&$Struct) + use<'e> { + let imports = Self::imports_all(edn); + move |state| { + let mut context = EdnView::default(); + for import in imports.iter() { + context.import(state, import) + } + } + } + fn imports_all <'e> (edn: &[Edn<'e>]) -> Vec<&'e str> { + let mut imports = vec![]; + for edn in edn.iter() { + for import in Self::imports_one(edn) { + imports.push(import); + } + } + imports + } + fn imports_one <'e> (edn: &Edn<'e>) -> Vec<&'e str> { + match edn { + Edn::Symbol(import) => vec![import], + Edn::List(edn) => Self::imports_all(edn.as_slice()), + _ => vec![], + } + } + pub fn import (&mut self, $state: &$l$Struct, key: &str) { + match key { + $($key => self.$field = Some($expr),)* + _ => {} + } + } + } + } } + +//edn_ns! { EdnLayout |context, item| { + + //[Key("when"), Sym(condition), Sym(template)] => When( + //context.get_bool(condition), + //context.get_template(template)), + + //[Key("when"), Sym(condition), Exp(template)] => When( + //context.get_bool(condition), + //Thunk::new(||EdnLayout::parse(template))), + + //"when" => When(item.0[1].into(), item.0[2].into()) + + ////"either" = Either(args[0].into(), args[1].into(), args[2].into()), + + ////"map" = Map(args[0].into(), args[1].into()), + + ////"fill" = edn_ns! { + ////"x" = Fixed::X(args[0].into(), args[1].into()), + ////"y" = Fixed::Y(args[0].into(), args[1].into()), + ////"xy" = Fixed::XY(args[0].into(), args[1].into(), args[2].into()), + ////}, + + ////"fixed" = edn_ns! { + ////"x" = Fixed::X(args[0].into(), args[1].into()), + ////"y" = Fixed::Y(args[0].into(), args[1].into()), + ////"xy" = Fixed::XY(args[0].into(), args[1].into(), args[2].into()), + ////}, + + ////"shrink" = edn_ns! { + ////"x" = Shrink::X(args[0].into(), args[1].into()), + ////"y" = Shrink::Y(args[0].into(), args[1].into()), + ////"xy" = Shrink::XY(args[0].into(), args[1].into(), args[2].into()), + ////}, + + ////"expand" = edn_ns! { + ////"x" = Expand::X(args[0].into(), args[1].into()), + ////"y" = Expand::Y(args[0].into(), args[1].into()), + ////"xy" = Expand::XY(args[0].into(), args[1].into(), args[2].into()), + ////}, + + ////"min" = edn_ns! { + ////"x" = Min::X(args[0].into(), args[1].into()), + ////"y" = Min::Y(args[0].into(), args[1].into()), + ////"xy" = Min::XY(args[0].into(), args[1].into(), args[2].into()), + ////}, + + ////"max" = edn_ns! { + ////"x" = Max::X(args[0].into(), args[1].into()), + ////"y" = Max::Y(args[0].into(), args[1].into()), + ////"xy" = Max::XY(args[0].into(), args[1].into(), args[2].into()), + ////}, + + ////"push" = edn_ns! { + ////"x" = Push::X(args[0].into(), args[1].into()), + ////"y" = Push::Y(args[0].into(), args[1].into()), + ////"xy" = Push::XY(args[0].into(), args[1].into(), args[2].into()), + ////}, + + ////"pull" = edn_ns! { + ////"x" = Pull::X(args[0].into(), args[1].into()), + ////"y" = Pull::Y(args[0].into(), args[1].into()), + ////"xy" = Pull::XY(args[0].into(), args[1].into(), args[2].into()), + ////}, + + ////"margin" = edn_ns! { + ////"x" = Margin::X(args[0].into(), args[1].into()), + ////"y" = Margin::Y(args[0].into(), args[1].into()), + ////"xy" = Margin::XY(args[0].into(), args[1].into(), args[2].into()), + ////}, + + ////"padding" = edn_ns! { + ////"x" = Padding::X(args[0].into(), args[1].into()), + ////"y" = Padding::Y(args[0].into(), args[1].into()), + ////"xy" = Padding::XY(args[0].into(), args[1].into(), args[2].into()), + ////}, + +//} } diff --git a/edn/src/edn_lib.rs b/edn/src/edn_lib.rs index 92f14b92..d8ec815f 100644 --- a/edn/src/edn_lib.rs +++ b/edn/src/edn_lib.rs @@ -2,27 +2,6 @@ use std::sync::{Arc, RwLock}; use std::collections::BTreeMap; pub use clojure_reader::edn::Edn; -#[cfg(test)] #[test] fn test_edn () -> Result<(), ParseError> { - use Item::*; - assert_eq!(Item::read_all("")?, - vec![]); - assert_eq!(Item::read_all(" ")?, - vec![]); - assert_eq!(Item::read_all("1234")?, - vec![Num(1234)]); - assert_eq!(Item::read_all("1234 5 67")?, - vec![Num(1234), Num(5), Num(67)]); - assert_eq!(Item::read_all("foo/bar")?, - vec![Key("foo/bar".into())]); - assert_eq!(Item::read_all(":symbol")?, - vec![Sym(":symbol".into())]); - assert_eq!(Item::read_all(" foo/bar :baz 456")?, - vec![Key("foo/bar".into()), Sym(":baz".into()), Num(456)]); - assert_eq!(Item::read_all(" (foo/bar :baz 456) ")?, - vec![Exp(vec![Key("foo/bar".into()), Sym(":baz".into()), Num(456)])]); - Ok(()) -} - fn number (digits: &str) -> usize { let mut value = 0; for c in digits.chars() { @@ -44,15 +23,15 @@ pub enum ParseError { } #[derive(Debug, Clone, Default, PartialEq)] -pub enum Item { +pub enum Item> { #[default] Nil, Num(usize), - Sym(String), - Key(String), - Exp(Vec), + Sym(T), + Key(T), + Exp(Vec>), } -impl Item { +impl Item { pub fn read_all <'a> (mut source: &'a str) -> Result, ParseError> { let mut items = vec![]; loop { diff --git a/edn/src/lib.rs b/edn/src/lib.rs index 14841351..f6f2337e 100644 --- a/edn/src/lib.rs +++ b/edn/src/lib.rs @@ -1,2 +1,34 @@ +#![feature(type_alias_impl_trait)] +#![feature(impl_trait_in_fn_trait_return)] + mod edn_lib; pub use self::edn_lib::*; mod edn_layout; pub use self::edn_layout::*; + +#[cfg(test)] #[test] fn test_edn () -> Result<(), ParseError> { + use Item::*; + assert_eq!(Item::read_all("")?, + vec![]); + assert_eq!(Item::read_all(" ")?, + vec![]); + assert_eq!(Item::read_all("1234")?, + vec![Num(1234)]); + assert_eq!(Item::read_all("1234 5 67")?, + vec![Num(1234), Num(5), Num(67)]); + assert_eq!(Item::read_all("foo/bar")?, + vec![Key("foo/bar".into())]); + assert_eq!(Item::read_all(":symbol")?, + vec![Sym(":symbol".into())]); + assert_eq!(Item::read_all(" foo/bar :baz 456")?, + vec![Key("foo/bar".into()), Sym(":baz".into()), Num(456)]); + assert_eq!(Item::read_all(" (foo/bar :baz 456) ")?, + vec![Exp(vec![Key("foo/bar".into()), Sym(":baz".into()), Num(456)])]); + Ok(()) +} + +#[cfg(test)] #[test] fn test_edn_layout () -> Result<(), ParseError> { + let source = include_str!("example.edn"); + let layout = Item::read_all(source)?; + panic!("{layout:?}"); + let content = EdnLayout::from(&layout); + Ok(()) +} diff --git a/engine/src/output.rs b/engine/src/output.rs index 2433140d..1edb44f2 100644 --- a/engine/src/output.rs +++ b/engine/src/output.rs @@ -9,7 +9,7 @@ pub trait Output { /// Mutable pointer to area fn area_mut (&mut self) -> &mut E::Area; /// Render widget in area - fn place (&mut self, area: E::Area, content: &impl Content); + fn place (&mut self, area: E::Area, content: &impl Render); #[inline] fn x (&self) -> E::Unit { self.area().x() } #[inline] fn y (&self) -> E::Unit { self.area().y() } @@ -17,19 +17,25 @@ pub trait Output { #[inline] fn h (&self) -> E::Unit { self.area().h() } #[inline] fn wh (&self) -> E::Size { self.area().wh().into() } } - -pub trait Content: Send + Sync { - fn content (&self) -> impl Content { - () - } - fn layout (&self, area: E::Area) -> E::Area { - self.content().layout(area) - } - fn render (&self, output: &mut E::Output) { - output.place(self.layout(output.area()), &self.content()) - } +pub trait Render: Send + Sync { + fn layout (&self, area: E::Area) -> E::Area { area } + fn render (&self, output: &mut E::Output) {} +} +impl> Render for C { + fn layout (&self, area: E::Area) -> E::Area { Content::layout(self, area) } + fn render (&self, output: &mut E::Output) { Content::render(self, output) } +} +impl Content for Box> { + fn content (&self) -> impl Content { self } +} +impl Content for &dyn Render { + fn content (&self) -> impl Content { self } +} +pub trait Content: Send + Sync + Sized { + fn content (&self) -> impl Content { () } + fn layout (&self, area: E::Area) -> E::Area { area } + fn render (&self, output: &mut E::Output) {} } - /// The platonic ideal unit of [Content]: total emptiness at dead center. impl Content for () { fn layout (&self, area: E::Area) -> E::Area { diff --git a/engine/src/tui/tui_output.rs b/engine/src/tui/tui_output.rs index bb11a0f2..43382fb7 100644 --- a/engine/src/tui/tui_output.rs +++ b/engine/src/tui/tui_output.rs @@ -8,7 +8,7 @@ pub struct TuiOut { impl Output for TuiOut { #[inline] fn area (&self) -> [u16;4] { self.area } #[inline] fn area_mut (&mut self) -> &mut [u16;4] { &mut self.area } - #[inline] fn place (&mut self, area: [u16;4], content: &impl Content) { + #[inline] fn place (&mut self, area: [u16;4], content: &impl Render) { let last = self.area(); *self.area_mut() = area; content.render(self); diff --git a/layout/src/align.rs b/layout/src/align.rs index 919443a8..80dd44f5 100644 --- a/layout/src/align.rs +++ b/layout/src/align.rs @@ -25,7 +25,7 @@ impl> Content for Align { } fn layout (&self, on: E::Area) -> E::Area { use Alignment::*; - let it = self.content().layout(on).xywh(); + let it = Render::layout(&self.content(), on).xywh(); let centered = on.center_xy(it.wh()); let far_x = (on.x() + on.w()).minus(it.w()); let far_y = (on.y() + on.h()).minus(it.h()); @@ -47,6 +47,8 @@ impl> Content for Align { [x, y, centered.w(), centered.h()].into() } fn render (&self, render: &mut E::Output) { - render.place(self.layout(render.area()), &self.content()) + let content = &self.content(); + let it = Render::layout(content, render.area()).xywh(); + render.place(it.into(), content) } } diff --git a/layout/src/ops.rs b/layout/src/ops.rs index 06539e6b..89c53d8d 100644 --- a/layout/src/ops.rs +++ b/layout/src/ops.rs @@ -3,7 +3,7 @@ use crate::*; /// Show an item only when a condition is true. pub struct When(pub bool, pub A); -impl> Content for When { +impl> Content for When { fn layout (&self, to: E::Area) -> E::Area { let Self(cond, item) = self; let mut area = E::Area::zero(); @@ -25,7 +25,7 @@ impl> Content for When { /// Show one item if a condition is true and another if the condition is false pub struct Either(pub bool, pub A, pub B); -impl, B: Content> Content for Either { +impl, B: Render> Content for Either { fn layout (&self, to: E::Area) -> E::Area { let Self(cond, a, b) = self; if *cond { a.layout(to) } else { b.layout(to) } @@ -43,7 +43,7 @@ pub struct Map(pub F, pub G) where impl Content for Map where E: Engine, - B: Content, + B: Render, I: Iterator + Send + Sync, F: Fn() -> I + Send + Sync, G: Fn(A, usize)->B + Send + Sync @@ -85,7 +85,7 @@ impl Content for Map where //pub fn reduce (iterator: I, callback: F) -> Reduce where //E: Engine, //I: Iterator + Send + Sync, - //R: Content, + //R: Render, //F: Fn(R, T, usize) -> R + Send + Sync //{ //Reduce(Default::default(), iterator, callback) @@ -93,12 +93,12 @@ impl Content for Map where pub struct Reduce(PhantomData<(E, R)>, I, F) where E: Engine, I: Iterator + Send + Sync, - R: Content, + R: Render, F: Fn(R, T, usize) -> R + Send + Sync; impl Content for Reduce where E: Engine, I: Iterator + Send + Sync, - R: Content, + R: Render, F: Fn(R, T, usize) -> R + Send + Sync { fn render (&self, to: &mut E::Output) { @@ -132,16 +132,16 @@ impl Content for Reduce where //define_ops! { //Layout { - //(when ,> + //(when ,> //When(cond: bool, item: A)) ///// When `cond` is `true`, render `a`, otherwise render `b`. - //(either , B: Content,> + //(either , B: Render,> //Either(cond: bool, a: A, b: B)) ///// If `opt` is `Some(T)` renders `cb(t)`, otherwise nothing. - //(opt B, B: Content,> + //(opt B, B: Render,> //Opt(option: Option, cb: F)) ///// Maps items of iterator through callback. - //(map , I: Iterator, F: Fn() -> I, G: Fn(A, usize)->B,> + //(map , I: Iterator, F: Fn() -> I, G: Fn(A, usize)->B,> //Map(get_iterator: F, callback: G)) //} //} diff --git a/layout/src/transform_xy_unit.rs b/layout/src/transform_xy_unit.rs index d43f1399..3475782e 100644 --- a/layout/src/transform_xy_unit.rs +++ b/layout/src/transform_xy_unit.rs @@ -42,7 +42,7 @@ macro_rules! transform_xy_unit { } transform_xy_unit!(|self: Fixed, area|{ - let [w, h] = self.content().layout(area.center_xy(match self { + let [w, h] = Render::layout(&self.content(), area.center_xy(match self { Self::X(fw, _) => [*fw, area.h()], Self::Y(fh, _) => [area.w(), *fh], Self::XY(fw, fh, _) => [*fw, *fh], @@ -54,16 +54,16 @@ transform_xy_unit!(|self: Fixed, area|{ }) }); -transform_xy_unit!(|self: Shrink, area|self.content().layout([ +transform_xy_unit!(|self: Shrink, area|Render::layout(&self.content(), [ area.x(), area.y(), area.w().minus(self.dx()), area.h().minus(self.dy()) ].into())); -transform_xy_unit!(|self: Expand, area|self.content().layout([ +transform_xy_unit!(|self: Expand, area|Render::layout(&self.content(), [ area.x(), area.y(), area.w() + self.dx(), area.h() + self.dy() ].into())); transform_xy_unit!(|self: Min, area|{ - let area = self.content().layout(area); + let area = Render::layout(&self.content(), area); match self { Self::X(mw, _) => [area.x(), area.y(), area.w().max(*mw), area.h()], Self::Y(mh, _) => [area.x(), area.y(), area.w(), area.h().max(*mh)], @@ -71,31 +71,31 @@ transform_xy_unit!(|self: Min, area|{ }}); transform_xy_unit!(|self: Max, area|{ - let area = self.content().layout(area); + let area = Render::layout(&self.content(), area); match self { Self::X(mw, _) => [area.x(), area.y(), area.w().min(*mw), area.h()], Self::Y(mh, _) => [area.x(), area.y(), area.w(), area.h().min(*mh)], Self::XY(mw, mh, _) => [area.x(), area.y(), area.w().min(*mw), area.h().min(*mh)], }}); -transform_xy_unit!(|self: Push, area|self.content().layout([ +transform_xy_unit!(|self: Push, area|Render::layout(&self.content(), [ area.x() + self.dx(), area.y() + self.dy(), area.w(), area.h() ].into())); transform_xy_unit!(|self: Pull, area|{ - let area = self.content().layout(area); + let area = Render::layout(&self.content(), area); [area.x().minus(self.dx()), area.y().minus(self.dy()), area.w(), area.h()] }); transform_xy_unit!(|self: Margin, area|{ - let area = self.content().layout(area); + let area = Render::layout(&self.content(), area); let dx = self.dx(); let dy = self.dy(); [area.x().minus(dx), area.y().minus(dy), area.w() + dy + dy, area.h() + dy + dy] }); transform_xy_unit!(|self: Padding, area|{ - let area = self.content().layout(area); + let area = Render::layout(&self.content(), area); let dx = self.dx(); let dy = self.dy(); [area.x() + dx, area.y() + dy, area.w().minus(dy + dy), area.h().minus(dy + dy), ] diff --git a/src/groovebox/groovebox_tui.rs b/src/groovebox/groovebox_tui.rs index 1b5125fe..b8505009 100644 --- a/src/groovebox/groovebox_tui.rs +++ b/src/groovebox/groovebox_tui.rs @@ -1,6 +1,12 @@ use crate::*; use super::*; use std::marker::ConstParamTy; +render!(Tui: (self: Groovebox) => self.size.of( + Bsp::s(self.toolbar_view(), + Bsp::n(self.selector_view(), + Bsp::n(self.sample_view(), + Bsp::n(self.status_view(), + Bsp::w(self.pool_view(), Fill::xy(Bsp::e(self.sampler_view(), &self.editor))))))))); const GROOVEBOX_EDN: &'static str = include_str!("groovebox.edn"); @@ -9,12 +15,6 @@ impl Content for Groovebox { EdnView::parse(self.edn.as_slice()) } } -//render!(Tui: (self: Groovebox) => self.size.of( - //Bsp::s(self.toolbar_view(), - //Bsp::n(self.selector_view(), - //Bsp::n(self.sample_view(), - //Bsp::n(self.status_view(), - //Bsp::w(self.pool_view(), Fill::xy(Bsp::e(self.sampler_view(), &self.editor))))))))); macro_rules! edn_context { ($Struct:ident |$l:lifetime, $state:ident| { @@ -61,34 +61,39 @@ macro_rules! edn_context { } edn_context!(Groovebox |'a, state| { - ":input-meter-l" = input_meter_l: Meter<'a> => Meter("L/", state.sampler.input_meter[0]), - ":input-meter-r" = input_meter_r: Meter<'a> => Meter("R/", state.sampler.input_meter[1]), - - ":transport" = transport: TransportView<'a> => TransportView::new(true, &state.player.clock), - - ":clip-play" = clip_play: ClipSelected => ClipSelected::play_phrase(&state.player), - ":clip-next" = clip_next: ClipSelected => ClipSelected::next_phrase(&state.player), - ":clip-edit" = clip_edit: MidiEditClip<'a> => MidiEditClip(&state.editor), - - ":edit-stat" = edit_stat: MidiEditStatus<'a> => MidiEditStatus(&state.editor), - - ":sample-h" = sample_h: u16 => if state.compact { 0 } else { 5 }, - ":sample-view" = sample_view: SampleViewer => SampleViewer::from_sampler( - &state.sampler, state.editor.note_point()), - ":sample-stat" = sample_stat: SamplerStatus<'a> => SamplerStatus( - &state.sampler, state.editor.note_point()), - + ":input-meter-l" = input_meter_l: Meter<'a> => + Meter("L/", state.sampler.input_meter[0]), + ":input-meter-r" = input_meter_r: Meter<'a> => + Meter("R/", state.sampler.input_meter[1]), + ":transport" = transport: TransportView<'a> => + TransportView::new(true, &state.player.clock), + ":clip-play" = clip_play: ClipSelected => + ClipSelected::play_phrase(&state.player), + ":clip-next" = clip_next: ClipSelected => + ClipSelected::next_phrase(&state.player), + ":clip-edit" = clip_edit: MidiEditClip<'a> => + MidiEditClip(&state.editor), + ":edit-stat" = edit_stat: MidiEditStatus<'a> => + MidiEditStatus(&state.editor), + ":sample-h" = sample_h: u16 => + if state.compact { 0 } else { 5 }, + ":sample-view" = sample_view: SampleViewer => + SampleViewer::from_sampler(&state.sampler, state.editor.note_point()), + ":sample-stat" = sample_stat: SamplerStatus<'a> => + SamplerStatus(&state.sampler, state.editor.note_point()), ":pool-w" = pool_w: u16 => if state.compact { 5 } else { let w = state.size.w(); if w > 60 { 20 } else if w > 40 { 15 } else { 10 } }, - ":pool-view" = pool_view: PoolView<'a> => PoolView(state.compact, &state.pool), - - ":samples-w" = samples_w: u16 => if state.compact { 4 } else { 11 }, - ":samples-y" = samples_y: u16 => if state.compact { 1 } else { 0 }, + ":pool-view" = pool_view: PoolView<'a> => + PoolView(state.compact, &state.pool), + ":samples-w" = samples_w: u16 => + if state.compact { 4 } else { 11 }, + ":samples-y" = samples_y: u16 => + if state.compact { 1 } else { 0 }, ":samples-view" = samples_view: SampleList<'a> => SampleList::new( state.compact, &state.sampler, &state.editor), - - ":midi-view" = midi_view: &'a MidiEditor => &state.editor, + ":midi-view" = midi_view: &'a MidiEditor => + &state.editor, }); //impl Groovebox { From 98d2107e4ef20b7c83056509e12e38bb620951d3 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sat, 4 Jan 2025 11:19:37 +0100 Subject: [PATCH 010/652] wip: compiles and runs (not enabled yet) --- Cargo.lock | 50 +++++++ edn/src/edn_context.rs | 45 ++++++ edn/src/edn_item.rs | 60 ++++++++ edn/src/edn_layout.rs | 52 +------ edn/src/edn_lib.rs | 116 --------------- edn/src/edn_token.rs | 58 ++++++++ edn/src/lib.rs | 10 +- engine/src/output.rs | 45 +++--- layout/src/align.rs | 2 +- layout/src/transform_xy.rs | 4 +- layout/src/transform_xy_unit.rs | 2 +- src/clock/clock_tui.rs | 70 +++++---- src/groovebox/groovebox_tui.rs | 251 +++++++++++++++++++------------- src/lib.rs | 2 +- src/style.rs | 30 ++-- 15 files changed, 440 insertions(+), 357 deletions(-) create mode 100644 edn/src/edn_context.rs create mode 100644 edn/src/edn_item.rs create mode 100644 edn/src/edn_token.rs diff --git a/Cargo.lock b/Cargo.lock index 50887011..aa24f594 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -262,6 +262,12 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "const_panic" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53857514f72ee4a2b583de67401e3ff63a5472ca4acf289d09a9ea7636dfec17" + [[package]] name = "crossbeam-deque" version = "0.8.6" @@ -557,6 +563,33 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "konst" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4381b9b00c55f251f2ebe9473aef7c117e96828def1a7cb3bd3f0f903c6894e9" +dependencies = [ + "const_panic", + "konst_kernel", + "konst_proc_macros", + "typewit", +] + +[[package]] +name = "konst_kernel" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4b1eb7788f3824c629b1116a7a9060d6e898c358ebff59070093d51103dcc3c" +dependencies = [ + "typewit", +] + +[[package]] +name = "konst_proc_macros" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00af7901ba50898c9e545c24d5c580c96a982298134e8037d8978b6594782c07" + [[package]] name = "lazy_static" version = "1.5.0" @@ -1389,6 +1422,8 @@ name = "tek_edn" version = "0.1.0" dependencies = [ "clojure-reader", + "konst", + "tek_layout", ] [[package]] @@ -1461,6 +1496,21 @@ dependencies = [ "winnow", ] +[[package]] +name = "typewit" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb77c29baba9e4d3a6182d51fa75e3215c7fd1dab8f4ea9d107c716878e55fc0" +dependencies = [ + "typewit_proc_macros", +] + +[[package]] +name = "typewit_proc_macros" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e36a83ea2b3c704935a01b4642946aadd445cea40b10935e3f8bd8052b8193d6" + [[package]] name = "unicode-ident" version = "1.0.14" diff --git a/edn/src/edn_context.rs b/edn/src/edn_context.rs new file mode 100644 index 00000000..c051d08d --- /dev/null +++ b/edn/src/edn_context.rs @@ -0,0 +1,45 @@ +use crate::*; + +#[macro_export] macro_rules! edn_context { + ($Struct:ident |$l:lifetime, $state:ident| { + $($key:literal = $field:ident: $Type:ty => $expr:expr,)* + }) => { + + #[derive(Default)] + pub struct EdnView<$l> { $($field: Option<$Type>),* } + + impl<$l> EdnView<$l> { + pub fn parse <'e> (edn: &[Edn<'e>]) -> impl Fn(&$Struct) + use<'e> { + let imports = Self::imports_all(edn); + move |state| { + let mut context = EdnView::default(); + for import in imports.iter() { + context.import(state, import) + } + } + } + fn imports_all <'e> (edn: &[Edn<'e>]) -> Vec<&'e str> { + let mut imports = vec![]; + for edn in edn.iter() { + for import in Self::imports_one(edn) { + imports.push(import); + } + } + imports + } + fn imports_one <'e> (edn: &Edn<'e>) -> Vec<&'e str> { + match edn { + Edn::Symbol(import) => vec![import], + Edn::List(edn) => Self::imports_all(edn.as_slice()), + _ => vec![], + } + } + pub fn import (&mut self, $state: &$l$Struct, key: &str) { + match key { + $($key => self.$field = Some($expr),)* + _ => {} + } + } + } + } +} diff --git a/edn/src/edn_item.rs b/edn/src/edn_item.rs new file mode 100644 index 00000000..6423af47 --- /dev/null +++ b/edn/src/edn_item.rs @@ -0,0 +1,60 @@ +use crate::*; + +fn number (digits: &str) -> usize { + let mut value = 0; + for c in digits.chars() { + value = 10 * value + digit(c); + } + value +} + +const fn digit (c: char) -> usize { + match c { '0' => 0, '1' => 1, '2' => 2, '3' => 3, '4' => 4, + '5' => 5, '6' => 6, '7' => 7, '8' => 8, '9' => 9, _ => unreachable!() } +} + +#[derive(Debug)] +pub enum ParseError { + Empty, + Unexpected(char), + Incomplete +} + +#[derive(Debug, Clone, Default, PartialEq)] +pub enum Item> { + #[default] Nil, + Num(usize), + Sym(T), + Key(T), + Exp(Vec>), +} + +impl Item { + pub fn read_all <'a> (mut source: &'a str) -> Result, ParseError> { + let mut items = vec![]; + loop { + if source.len() == 0 { + break + } + let (remaining, token) = Token::chomp(source)?; + match Item::read(token)? { Item::Nil => {}, item => items.push(item) }; + source = remaining + } + Ok(items) + } + pub fn read <'a> (token: Token<'a>) -> Result { + use Token::*; + Ok(match token { + Nil => Item::Nil, + Num(chars, index, length) => + Self::Num(number(&chars[index..index+length])), + Sym(chars, index, length) => + Self::Sym(chars[index..index+length].to_string()), + Key(chars, index, length) => + Self::Key(chars[index..index+length].to_string()), + Exp(chars, index, length, 0) => + Self::Exp(Self::read_all(&chars[index+1..(index+length).saturating_sub(1)])?), + _ => panic!("unclosed delimiter") + }) + } +} diff --git a/edn/src/edn_layout.rs b/edn/src/edn_layout.rs index 1e4c7d3e..46deea52 100644 --- a/edn/src/edn_layout.rs +++ b/edn/src/edn_layout.rs @@ -18,10 +18,10 @@ impl<'a, E, T: AsRef> From<&'a [Item]> for EdnContent<'a, E, T> { //pub struct EdnContent<'a, T>(T, &'a [Item]); pub trait EdnLayout { - fn get_bool (&self, item: &Item<&str>) -> bool { todo!() } - fn get_unit (&self, item: &Item<&str>) -> E::Unit { todo!() } - fn get_usize (&self, key: &str) -> usize { todo!() } - fn get_content (&self, item: &Item<&str>) -> &dyn Render { todo!() } + fn get_bool (&self, item: &Item<&str>) -> bool { false } + fn get_unit (&self, item: &Item<&str>) -> E::Unit { 0.into() } + fn get_usize (&self, key: &str) -> usize { 0 } + fn get_content (&self, item: &Item<&str>) -> Box + '_> { Box::new(()) } fn parse <'a: 'static> (&'a self, items: &[Item<&str>]) -> Box + 'a> { match items { [Key("when"), c, a, ..] => @@ -71,50 +71,6 @@ pub trait EdnLayout { } } -macro_rules! edn_context { - ($Struct:ident |$l:lifetime, $state:ident| { - $($key:literal = $field:ident: $Type:ty => $expr:expr,)* - }) => { - - #[derive(Default)] - pub struct EdnView<$l> { $($field: Option<$Type>),* } - - impl<$l> EdnView<$l> { - pub fn parse <'e> (edn: &[Edn<'e>]) -> impl Fn(&$Struct) + use<'e> { - let imports = Self::imports_all(edn); - move |state| { - let mut context = EdnView::default(); - for import in imports.iter() { - context.import(state, import) - } - } - } - fn imports_all <'e> (edn: &[Edn<'e>]) -> Vec<&'e str> { - let mut imports = vec![]; - for edn in edn.iter() { - for import in Self::imports_one(edn) { - imports.push(import); - } - } - imports - } - fn imports_one <'e> (edn: &Edn<'e>) -> Vec<&'e str> { - match edn { - Edn::Symbol(import) => vec![import], - Edn::List(edn) => Self::imports_all(edn.as_slice()), - _ => vec![], - } - } - pub fn import (&mut self, $state: &$l$Struct, key: &str) { - match key { - $($key => self.$field = Some($expr),)* - _ => {} - } - } - } - } -} - //edn_ns! { EdnLayout |context, item| { //[Key("when"), Sym(condition), Sym(template)] => When( diff --git a/edn/src/edn_lib.rs b/edn/src/edn_lib.rs index d8ec815f..7233d310 100644 --- a/edn/src/edn_lib.rs +++ b/edn/src/edn_lib.rs @@ -2,122 +2,6 @@ use std::sync::{Arc, RwLock}; use std::collections::BTreeMap; pub use clojure_reader::edn::Edn; -fn number (digits: &str) -> usize { - let mut value = 0; - for c in digits.chars() { - value = 10 * value + digit(c); - } - value -} - -const fn digit (c: char) -> usize { - match c { '0' => 0, '1' => 1, '2' => 2, '3' => 3, '4' => 4, - '5' => 5, '6' => 6, '7' => 7, '8' => 8, '9' => 9, _ => unreachable!() } -} - -#[derive(Debug)] -pub enum ParseError { - Empty, - Unexpected(char), - Incomplete -} - -#[derive(Debug, Clone, Default, PartialEq)] -pub enum Item> { - #[default] Nil, - Num(usize), - Sym(T), - Key(T), - Exp(Vec>), -} - -impl Item { - pub fn read_all <'a> (mut source: &'a str) -> Result, ParseError> { - let mut items = vec![]; - loop { - if source.len() == 0 { - break - } - let (remaining, token) = Token::chomp(source)?; - match Item::read(token)? { Item::Nil => {}, item => items.push(item) }; - source = remaining - } - Ok(items) - } - pub fn read <'a> (token: Token<'a>) -> Result { - use Token::*; - Ok(match token { - Nil => Item::Nil, - Num(chars, index, length) => - Self::Num(number(&chars[index..index+length])), - Sym(chars, index, length) => - Self::Sym(chars[index..index+length].to_string()), - Key(chars, index, length) => - Self::Key(chars[index..index+length].to_string()), - Exp(chars, index, length, 0) => - Self::Exp(Self::read_all(&chars[index+1..(index+length).saturating_sub(1)])?), - _ => panic!("unclosed delimiter") - }) - } -} - - -#[derive(Debug, Copy, Clone, Default, PartialEq)] -pub enum Token<'a> { - #[default] Nil, - Num(&'a str, usize, usize), - Sym(&'a str, usize, usize), - Key(&'a str, usize, usize), - Exp(&'a str, usize, usize, usize), -} - -impl<'a> Token<'a> { - fn chomp (source: &'a str) -> Result<(&'a str, Self), ParseError> { - use Token::*; - let mut state = Self::default(); - for (index, c) in source.char_indices() { - state = match state { - // must begin expression - Nil => match c { - ' '|'\n'|'\r'|'\t' => Nil, - '(' => Exp(source, index, 1, 1), - ':' => Sym(source, index, 1), - '0'..='9' => Num(source, index, 1), - 'a'..='z' => Key(source, index, 1), - _ => return Err(ParseError::Unexpected(c)) - }, - Num(_, _, 0) => unreachable!(), - Sym(_, _, 0) => unreachable!(), - Key(_, _, 0) => unreachable!(), - Num(source, index, length) => match c { - '0'..='9' => Num(source, index, length + 1), - ' '|'\n'|'\r'|'\t' => return Ok((&source[index+length..], Num(source, index, length))), - _ => return Err(ParseError::Unexpected(c)) - }, - Sym(source, index, length) => match c { - 'a'..='z'|'0'..='9'|'-' => Sym(source, index, length + 1), - ' '|'\n'|'\r'|'\t' => return Ok((&source[index+length..], Sym(source, index, length))), - _ => return Err(ParseError::Unexpected(c)) - }, - Key(source, index, length) => match c { - 'a'..='z'|'0'..='9'|'-'|'/' => Key(source, index, length + 1), - ' '|'\n'|'\r'|'\t' => return Ok((&source[index+length..], Key(source, index, length))), - _ => return Err(ParseError::Unexpected(c)) - }, - Exp(source, index, length, 0) => match c { - ' '|'\n'|'\r'|'\t' => return Ok((&source[index+length..], Exp(source, index, length, 0))), - _ => return Err(ParseError::Unexpected(c)) - }, - Exp(source, index, length, depth) => match c { - ')' => Exp(source, index, length + 1, depth - 1), - '(' => Exp(source, index, length + 1, depth + 1), - _ => Exp(source, index, length + 1, depth) - }, - } - } - Ok(("", state)) - } -} //#[derive(Debug, Copy, Clone, Default, PartialEq)] //pub struct Items<'a>(&'a [Item<'a>]); diff --git a/edn/src/edn_token.rs b/edn/src/edn_token.rs new file mode 100644 index 00000000..791e9657 --- /dev/null +++ b/edn/src/edn_token.rs @@ -0,0 +1,58 @@ +use crate::*; + +#[derive(Debug, Copy, Clone, Default, PartialEq)] +pub enum Token<'a> { + #[default] Nil, + Num(&'a str, usize, usize), + Sym(&'a str, usize, usize), + Key(&'a str, usize, usize), + Exp(&'a str, usize, usize, usize), +} + +impl<'a> Token<'a> { + pub fn chomp (source: &'a str) -> Result<(&'a str, Self), ParseError> { + use Token::*; + let mut state = Self::default(); + for (index, c) in source.char_indices() { + state = match state { + // must begin expression + Nil => match c { + ' '|'\n'|'\r'|'\t' => Nil, + '(' => Exp(source, index, 1, 1), + ':' => Sym(source, index, 1), + '0'..='9' => Num(source, index, 1), + 'a'..='z' => Key(source, index, 1), + _ => return Err(ParseError::Unexpected(c)) + }, + Num(_, _, 0) => unreachable!(), + Sym(_, _, 0) => unreachable!(), + Key(_, _, 0) => unreachable!(), + Num(source, index, length) => match c { + '0'..='9' => Num(source, index, length + 1), + ' '|'\n'|'\r'|'\t' => return Ok((&source[index+length..], Num(source, index, length))), + _ => return Err(ParseError::Unexpected(c)) + }, + Sym(source, index, length) => match c { + 'a'..='z'|'0'..='9'|'-' => Sym(source, index, length + 1), + ' '|'\n'|'\r'|'\t' => return Ok((&source[index+length..], Sym(source, index, length))), + _ => return Err(ParseError::Unexpected(c)) + }, + Key(source, index, length) => match c { + 'a'..='z'|'0'..='9'|'-'|'/' => Key(source, index, length + 1), + ' '|'\n'|'\r'|'\t' => return Ok((&source[index+length..], Key(source, index, length))), + _ => return Err(ParseError::Unexpected(c)) + }, + Exp(source, index, length, 0) => match c { + ' '|'\n'|'\r'|'\t' => return Ok((&source[index+length..], Exp(source, index, length, 0))), + _ => return Err(ParseError::Unexpected(c)) + }, + Exp(source, index, length, depth) => match c { + ')' => Exp(source, index, length + 1, depth - 1), + '(' => Exp(source, index, length + 1, depth + 1), + _ => Exp(source, index, length + 1, depth) + }, + } + } + Ok(("", state)) + } +} diff --git a/edn/src/lib.rs b/edn/src/lib.rs index f6f2337e..5fe7808e 100644 --- a/edn/src/lib.rs +++ b/edn/src/lib.rs @@ -1,8 +1,10 @@ #![feature(type_alias_impl_trait)] #![feature(impl_trait_in_fn_trait_return)] -mod edn_lib; pub use self::edn_lib::*; -mod edn_layout; pub use self::edn_layout::*; +mod edn_context; pub use self::edn_context::*; +mod edn_item; pub use self::edn_item::*; +mod edn_layout; pub use self::edn_layout::*; +mod edn_token; pub use self::edn_token::*; #[cfg(test)] #[test] fn test_edn () -> Result<(), ParseError> { use Item::*; @@ -28,7 +30,7 @@ mod edn_layout; pub use self::edn_layout::*; #[cfg(test)] #[test] fn test_edn_layout () -> Result<(), ParseError> { let source = include_str!("example.edn"); let layout = Item::read_all(source)?; - panic!("{layout:?}"); - let content = EdnLayout::from(&layout); + //panic!("{layout:?}"); + //let content = >::from(&layout); Ok(()) } diff --git a/engine/src/output.rs b/engine/src/output.rs index 1edb44f2..1926b52a 100644 --- a/engine/src/output.rs +++ b/engine/src/output.rs @@ -1,7 +1,5 @@ use crate::*; use std::marker::PhantomData; -//use std::sync::{Arc, Mutex, RwLock}; - /// Rendering target pub trait Output { /// Current output area @@ -10,31 +8,37 @@ pub trait Output { fn area_mut (&mut self) -> &mut E::Area; /// Render widget in area fn place (&mut self, area: E::Area, content: &impl Render); - #[inline] fn x (&self) -> E::Unit { self.area().x() } #[inline] fn y (&self) -> E::Unit { self.area().y() } #[inline] fn w (&self) -> E::Unit { self.area().w() } #[inline] fn h (&self) -> E::Unit { self.area().h() } #[inline] fn wh (&self) -> E::Size { self.area().wh().into() } } +pub struct Thunk, F: Fn()->T + Send + Sync>(F, PhantomData); +impl, F: Fn()->T + Send + Sync> Thunk { + pub fn new (thunk: F) -> Self { Self(thunk, Default::default()) } +} +impl, F: Fn()->T + Send + Sync> Content for Thunk { + fn content (&self) -> impl Content { (self.0)() } +} pub trait Render: Send + Sync { - fn layout (&self, area: E::Area) -> E::Area { area } - fn render (&self, output: &mut E::Output) {} + fn layout (&self, area: E::Area) -> E::Area; + fn render (&self, output: &mut E::Output); +} +pub trait Content: Send + Sync + Sized { + fn content (&self) -> impl Render { () } + fn layout (&self, area: E::Area) -> E::Area { self.content().layout(area) } + fn render (&self, output: &mut E::Output) { self.content().render(output) } } impl> Render for C { fn layout (&self, area: E::Area) -> E::Area { Content::layout(self, area) } fn render (&self, output: &mut E::Output) { Content::render(self, output) } } impl Content for Box> { - fn content (&self) -> impl Content { self } + fn content (&self) -> impl Render { self } } impl Content for &dyn Render { - fn content (&self) -> impl Content { self } -} -pub trait Content: Send + Sync + Sized { - fn content (&self) -> impl Content { () } - fn layout (&self, area: E::Area) -> E::Area { area } - fn render (&self, output: &mut E::Output) {} + fn content (&self) -> impl Render { self } } /// The platonic ideal unit of [Content]: total emptiness at dead center. impl Content for () { @@ -46,7 +50,7 @@ impl Content for () { } impl> Content for &T { - fn content (&self) -> impl Content { + fn content (&self) -> impl Render { (*self).content() } fn layout (&self, area: E::Area) -> E::Area { @@ -58,9 +62,8 @@ impl> Content for &T { } impl> Content for Option { - fn content (&self) -> impl Content { + fn content (&self) -> impl Render { self.as_ref() - .map(|content|content.content()) } fn layout (&self, area: E::Area) -> E::Area { self.as_ref() @@ -73,18 +76,6 @@ impl> Content for Option { } } -pub struct Thunk, F: Fn()->T + Send + Sync>(F, PhantomData); - -impl, F: Fn()->T + Send + Sync> Thunk { - pub fn new (thunk: F) -> Self { - Self(thunk, Default::default()) - } -} -impl, F: Fn()->T + Send + Sync> Content for Thunk { - fn content (&self) -> impl Content { - (self.0)() - } -} #[macro_export] macro_rules! render { (($self:ident:$Struct:ty) => $content:expr) => { diff --git a/layout/src/align.rs b/layout/src/align.rs index 80dd44f5..419d5aff 100644 --- a/layout/src/align.rs +++ b/layout/src/align.rs @@ -20,7 +20,7 @@ impl Align { } impl> Content for Align { - fn content (&self) -> impl Content { + fn content (&self) -> impl Render { &self.1 } fn layout (&self, on: E::Area) -> E::Area { diff --git a/layout/src/transform_xy.rs b/layout/src/transform_xy.rs index 78b12918..f7f6b805 100644 --- a/layout/src/transform_xy.rs +++ b/layout/src/transform_xy.rs @@ -14,7 +14,7 @@ macro_rules! transform_xy { pub fn xy (item: T) -> Self { Self::XY(item) } } impl> Content for $Enum { - fn content (&self) -> impl Content { + fn content (&self) -> impl Render { match self { Self::X(item) => item, Self::Y(item) => item, @@ -31,7 +31,7 @@ macro_rules! transform_xy { transform_xy!(self: Fill |to|{ let [x0, y0, wmax, hmax] = to.xywh(); - let [x, y, w, h] = Content::layout(&self.content(), to).xywh(); + let [x, y, w, h] = self.content().layout(to).xywh(); match self { X(_) => [x0, y, wmax, h], Y(_) => [x, y0, w, hmax], diff --git a/layout/src/transform_xy_unit.rs b/layout/src/transform_xy_unit.rs index 3475782e..a43c786f 100644 --- a/layout/src/transform_xy_unit.rs +++ b/layout/src/transform_xy_unit.rs @@ -27,7 +27,7 @@ macro_rules! transform_xy_unit { } } impl> Content for $Enum { - fn content (&self) -> impl Content { + fn content (&self) -> impl Render { Some(match self { Self::X(_, content) => content, Self::Y(_, content) => content, diff --git a/src/clock/clock_tui.rs b/src/clock/clock_tui.rs index 80d9c0c4..e7078cd5 100644 --- a/src/clock/clock_tui.rs +++ b/src/clock/clock_tui.rs @@ -2,7 +2,6 @@ use crate::*; use ClockCommand::{Play, Pause, SetBpm, SetQuant, SetSync}; use FocusCommand::{Next, Prev}; use KeyCode::{Enter, Left, Right, Char}; - /// Transport clock app. pub struct TransportTui { pub jack: Arc>, @@ -10,48 +9,13 @@ pub struct TransportTui { } has_clock!(|self: TransportTui|&self.clock); audio!(|self: TransportTui, client, scope|ClockAudio(self).process(client, scope)); -handle!(|self: TransportTui, input|ClockCommand::execute_with_state(self, input.event())); -keymap!(TRANSPORT_KEYS = |state: TransportTui, input: Event| ClockCommand { - key(Char(' ')) => - if state.clock().is_stopped() { Play(None) } else { Pause(None) }, - shift(key(Char(' '))) => - if state.clock().is_stopped() { Play(Some(0)) } else { Pause(Some(0)) } -}); -// TODO: -//keymap!(TRANSPORT_BPM_KEYS = |state: Clock, input: Event| ClockCommand { - //key(Char(',')) => SetBpm(state.bpm().get() - 1.0), - //key(Char('.')) => SetBpm(state.bpm().get() + 1.0), - //key(Char('<')) => SetBpm(state.bpm().get() - 0.001), - //key(Char('>')) => SetBpm(state.bpm().get() + 0.001), -//}); -//keymap!(TRANSPORT_QUANT_KEYS = |state: Clock, input: Event| ClockCommand { - //key(Char(',')) => SetQuant(state.quant.prev()), - //key(Char('.')) => SetQuant(state.quant.next()), - //key(Char('<')) => SetQuant(state.quant.prev()), - //key(Char('>')) => SetQuant(state.quant.next()), -//}); -//keymap!(TRANSPORT_SYNC_KEYS = |sync: Clock, input: Event | ClockCommand { - //key(Char(',')) => SetSync(state.sync.prev()), - //key(Char('.')) => SetSync(state.sync.next()), - //key(Char('<')) => SetSync(state.sync.prev()), - //key(Char('>')) => SetSync(state.sync.next()), -//}); -//keymap!(TRANSPORT_SEEK_KEYS = |state: Clock, input: Event| ClockCommand { - //key(Char(',')) => todo!("transport seek bar"), - //key(Char('.')) => todo!("transport seek bar"), - //key(Char('<')) => todo!("transport seek beat"), - //key(Char('>')) => todo!("transport seek beat"), -//}); render!(Tui: (self: TransportTui) => TransportView { compact: false, clock: &self.clock }); impl TransportTui { pub fn new (jack: &Arc>) -> Usually { - Ok(Self { - jack: jack.clone(), - clock: Clock::from(jack), - }) + Ok(Self { jack: jack.clone(), clock: Clock::from(jack) }) } } @@ -136,3 +100,35 @@ render!(Tui: (self: OutputStats) => Either(self.compact, Bsp::e(Tui::fg(TuiTheme::g(255), format!("{:.3}ms", self.latency)), " latency"), ))); +handle!(|self: TransportTui, input|ClockCommand::execute_with_state(self, input.event())); +keymap!(TRANSPORT_KEYS = |state: TransportTui, input: Event| ClockCommand { + key(Char(' ')) => + if state.clock().is_stopped() { Play(None) } else { Pause(None) }, + shift(key(Char(' '))) => + if state.clock().is_stopped() { Play(Some(0)) } else { Pause(Some(0)) } +}); +// TODO: +//keymap!(TRANSPORT_BPM_KEYS = |state: Clock, input: Event| ClockCommand { + //key(Char(',')) => SetBpm(state.bpm().get() - 1.0), + //key(Char('.')) => SetBpm(state.bpm().get() + 1.0), + //key(Char('<')) => SetBpm(state.bpm().get() - 0.001), + //key(Char('>')) => SetBpm(state.bpm().get() + 0.001), +//}); +//keymap!(TRANSPORT_QUANT_KEYS = |state: Clock, input: Event| ClockCommand { + //key(Char(',')) => SetQuant(state.quant.prev()), + //key(Char('.')) => SetQuant(state.quant.next()), + //key(Char('<')) => SetQuant(state.quant.prev()), + //key(Char('>')) => SetQuant(state.quant.next()), +//}); +//keymap!(TRANSPORT_SYNC_KEYS = |sync: Clock, input: Event | ClockCommand { + //key(Char(',')) => SetSync(state.sync.prev()), + //key(Char('.')) => SetSync(state.sync.next()), + //key(Char('<')) => SetSync(state.sync.prev()), + //key(Char('>')) => SetSync(state.sync.next()), +//}); +//keymap!(TRANSPORT_SEEK_KEYS = |state: Clock, input: Event| ClockCommand { + //key(Char(',')) => todo!("transport seek bar"), + //key(Char('.')) => todo!("transport seek bar"), + //key(Char('<')) => todo!("transport seek beat"), + //key(Char('>')) => todo!("transport seek beat"), +//}); diff --git a/src/groovebox/groovebox_tui.rs b/src/groovebox/groovebox_tui.rs index b8505009..3aa1ac33 100644 --- a/src/groovebox/groovebox_tui.rs +++ b/src/groovebox/groovebox_tui.rs @@ -1,6 +1,48 @@ use crate::*; use super::*; use std::marker::ConstParamTy; +use tek_engine::Render; +use Item::*; + +impl EdnLayout for Groovebox { + fn get_bool (&self, item: &Item<&str>) -> bool { todo!() } + fn get_unit (&self, item: &Item<&str>) -> u16 { + match item { + Sym(":sample-h") => if self.compact { 0 } else { 5 }, + Sym(":samples-w") => if self.compact { 4 } else { 11 }, + Sym(":samples-y") => if self.compact { 1 } else { 0 }, + Sym(":pool-w") => if self.compact { 5 } else { + let w = self.size.w(); + if w > 60 { 20 } else if w > 40 { 15 } else { 10 } + }, + _ => 0 + } + } + fn get_content (&self, item: &Item<&str>) -> Box + '_> { + match item { + Sym(":input-meter-l") => Box::new(Meter("L/", self.sampler.input_meter[0])), + Sym(":input-meter-r") => Box::new(Meter("R/", self.sampler.input_meter[1])), + + Sym(":transport") => Box::new(TransportView::new(true, &self.player.clock)), + Sym(":clip-play") => Box::new(ClipSelected::play_phrase(&self.player)), + Sym(":clip-next") => Box::new(ClipSelected::next_phrase(&self.player)), + Sym(":clip-edit") => Box::new(MidiEditClip(&self.editor)), + Sym(":edit-stat") => Box::new(MidiEditStatus(&self.editor)), + Sym(":pool-view") => Box::new(PoolView(self.compact, &self.pool)), + Sym(":midi-view") => Box::new(&self.editor), + + Sym(":sample-view") => Box::new(SampleViewer::from_sampler( + &self.sampler, self.editor.note_point())), + Sym(":sample-stat") => Box::new(SamplerStatus( + &self.sampler, self.editor.note_point())), + Sym(":samples-view") => Box::new(SampleList::new( + self.compact, &self.sampler, &self.editor)), + + _ => Box::new(()) + } + } +} + render!(Tui: (self: Groovebox) => self.size.of( Bsp::s(self.toolbar_view(), Bsp::n(self.selector_view(), @@ -8,109 +50,120 @@ render!(Tui: (self: Groovebox) => self.size.of( Bsp::n(self.status_view(), Bsp::w(self.pool_view(), Fill::xy(Bsp::e(self.sampler_view(), &self.editor))))))))); -const GROOVEBOX_EDN: &'static str = include_str!("groovebox.edn"); - -impl Content for Groovebox { - fn content (&self) -> impl Content { - EdnView::parse(self.edn.as_slice()) +impl Groovebox { + fn toolbar_view (&self) -> impl Content + use<'_> { + Fill::x(Fixed::y(2, lay!( + Align::w(Meter("L/", self.sampler.input_meter[0])), + Align::e(Meter("R/", self.sampler.input_meter[1])), + Align::x(TransportView::new(true, &self.player.clock)), + ))) + } + fn selector_view (&self) -> impl Content + use<'_> { + row!( + ClipSelected::play_phrase(&self.player), + ClipSelected::next_phrase(&self.player), + MidiEditClip(&self.editor), + MidiEditStatus(&self.editor), + ) + } + fn sample_view (&self) -> impl Content + use<'_> { + let note_pt = self.editor.note_point(); + let sample_h = if self.compact { 0 } else { 5 }; + Max::y(sample_h, Fill::xy( + SampleViewer::from_sampler(&self.sampler, note_pt))) + } + fn status_view (&self) -> impl Content + use<'_> { + let note_pt = self.editor.note_point(); + Align::w(Fixed::y(1, SamplerStatus(&self.sampler, note_pt))) + } + fn pool_view (&self) -> impl Content + use<'_> { + let w = self.size.w(); + let pool_w = if w > 60 { 20 } else if w > 40 { 15 } else { 10 }; + Fixed::x(if self.compact { 5 } else { pool_w }, + PoolView(self.compact, &self.pool)) + } + fn sampler_view (&self) -> impl Content + use<'_> { + let sampler_w = if self.compact { 4 } else { 11 }; + let sampler_y = if self.compact { 1 } else { 0 }; + Fixed::x(sampler_w, Push::y(sampler_y, Fill::y( + SampleList::new(self.compact, &self.sampler, &self.editor)))) } } -macro_rules! edn_context { - ($Struct:ident |$l:lifetime, $state:ident| { - $($key:literal = $field:ident: $Type:ty => $expr:expr,)* - }) => { +//render!(Tui: (self: Groovebox) => self.size.of( + //Bsp::s(self.toolbar_view(), + //Bsp::n(self.selector_view(), + //Bsp::n(self.sample_view(), + //Bsp::n(self.status_view(), + //Bsp::w(self.pool_view(), Fill::xy(Bsp::e(self.sampler_view(), &self.editor))))))))); - #[derive(Default)] - pub struct EdnView<$l> { $($field: Option<$Type>),* } +//const GROOVEBOX_EDN: &'static str = include_str!("groovebox.edn"); - impl<$l> EdnView<$l> { - pub fn parse <'e> (edn: &[Edn<'e>]) -> impl Fn(&$Struct) + use<'e> { - let imports = Self::imports_all(edn); - move |state| { - let mut context = EdnView::default(); - for import in imports.iter() { - context.import(state, import) - } - } - } - fn imports_all <'e> (edn: &[Edn<'e>]) -> Vec<&'e str> { - let mut imports = vec![]; - for edn in edn.iter() { - for import in Self::imports_one(edn) { - imports.push(import); - } - } - imports - } - fn imports_one <'e> (edn: &Edn<'e>) -> Vec<&'e str> { - match edn { - Edn::Symbol(import) => vec![import], - Edn::List(edn) => Self::imports_all(edn.as_slice()), - _ => vec![], - } - } - pub fn import (&mut self, $state: &$l$Struct, key: &str) { - match key { - $($key => self.$field = Some($expr),)* - _ => {} - } - } - } - } -} - -edn_context!(Groovebox |'a, state| { - ":input-meter-l" = input_meter_l: Meter<'a> => - Meter("L/", state.sampler.input_meter[0]), - ":input-meter-r" = input_meter_r: Meter<'a> => - Meter("R/", state.sampler.input_meter[1]), - ":transport" = transport: TransportView<'a> => - TransportView::new(true, &state.player.clock), - ":clip-play" = clip_play: ClipSelected => - ClipSelected::play_phrase(&state.player), - ":clip-next" = clip_next: ClipSelected => - ClipSelected::next_phrase(&state.player), - ":clip-edit" = clip_edit: MidiEditClip<'a> => - MidiEditClip(&state.editor), - ":edit-stat" = edit_stat: MidiEditStatus<'a> => - MidiEditStatus(&state.editor), - ":sample-h" = sample_h: u16 => - if state.compact { 0 } else { 5 }, - ":sample-view" = sample_view: SampleViewer => - SampleViewer::from_sampler(&state.sampler, state.editor.note_point()), - ":sample-stat" = sample_stat: SamplerStatus<'a> => - SamplerStatus(&state.sampler, state.editor.note_point()), - ":pool-w" = pool_w: u16 => if state.compact { 5 } else { - let w = state.size.w(); - if w > 60 { 20 } else if w > 40 { 15 } else { 10 } }, - ":pool-view" = pool_view: PoolView<'a> => - PoolView(state.compact, &state.pool), - ":samples-w" = samples_w: u16 => - if state.compact { 4 } else { 11 }, - ":samples-y" = samples_y: u16 => - if state.compact { 1 } else { 0 }, - ":samples-view" = samples_view: SampleList<'a> => SampleList::new( - state.compact, &state.sampler, &state.editor), - ":midi-view" = midi_view: &'a MidiEditor => - &state.editor, -}); - -//impl Groovebox { - //fn status_view (&self) -> impl Content + use<'_> { - //let note_pt = self.editor.note_point(); - //Align::w(Fixed::y(1, )) - //} - //fn pool_view (&self) -> impl Content + use<'_> { - //let w = self.size.w(); - //let pool_w = if w > 60 { 20 } else if w > 40 { 15 } else { 10 }; - //Fixed::x(if self.compact { 5 } else { pool_w }, - //) - //} - //fn sampler_view (&self) -> impl Content + use<'_> { - //let sampler_w = if self.compact { 4 } else { 11 }; - //let sampler_y = if self.compact { 1 } else { 0 }; - //Fixed::x(sampler_w, Push::y(sampler_y, Fill::y( - //SampleList::new(self.compact, &self.sampler, &self.editor)))) +//impl Content for Groovebox { + //fn content (&self) -> impl Content { + //EdnView::parse(self.edn.as_slice()) //} //} + +//macro_rules! edn_context { + //($Struct:ident |$l:lifetime, $state:ident| { + //$($key:literal = $field:ident: $Type:ty => $expr:expr,)* + //}) => { + + //#[derive(Default)] + //pub struct EdnView<$l> { $($field: Option<$Type>),* } + + //impl<$l> EdnView<$l> { + //pub fn parse <'e> (edn: &[Edn<'e>]) -> impl Fn(&$Struct) + use<'e> { + //let imports = Self::imports_all(edn); + //move |state| { + //let mut context = EdnView::default(); + //for import in imports.iter() { + //context.import(state, import) + //} + //} + //} + //fn imports_all <'e> (edn: &[Edn<'e>]) -> Vec<&'e str> { + //let mut imports = vec![]; + //for edn in edn.iter() { + //for import in Self::imports_one(edn) { + //imports.push(import); + //} + //} + //imports + //} + //fn imports_one <'e> (edn: &Edn<'e>) -> Vec<&'e str> { + //match edn { + //Edn::Symbol(import) => vec![import], + //Edn::List(edn) => Self::imports_all(edn.as_slice()), + //_ => vec![], + //} + //} + //pub fn import (&mut self, $state: &$l$Struct, key: &str) { + //match key { + //$($key => self.$field = Some($expr),)* + //_ => {} + //} + //} + //} + //} +//} + +////impl Groovebox { + ////fn status_view (&self) -> impl Content + use<'_> { + ////let note_pt = self.editor.note_point(); + ////Align::w(Fixed::y(1, )) + ////} + ////fn pool_view (&self) -> impl Content + use<'_> { + ////let w = self.size.w(); + ////let pool_w = if w > 60 { 20 } else if w > 40 { 15 } else { 10 }; + ////Fixed::x(if self.compact { 5 } else { pool_w }, + ////) + ////} + ////fn sampler_view (&self) -> impl Content + use<'_> { + ////let sampler_w = if self.compact { 4 } else { 11 }; + ////let sampler_y = if self.compact { 1 } else { 0 }; + ////Fixed::x(sampler_w, Push::y(sampler_y, Fill::y( + ////SampleList::new(self.compact, &self.sampler, &self.editor)))) + ////} +////} diff --git a/src/lib.rs b/src/lib.rs index a88f93d0..9f109b7c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,7 +13,7 @@ pub(crate) use ::tek_layout::{ tek_engine::{ Usually, Perhaps, Engine, Size, Area, - Output, Content, Thunk, render, + Output, Content, Render, Thunk, render, Input, Handle, handle, kexp, kpat, tui::{ diff --git a/src/style.rs b/src/style.rs index 46ecaf8c..cc9252e8 100644 --- a/src/style.rs +++ b/src/style.rs @@ -21,11 +21,8 @@ pub trait TuiStyle { impl TuiStyle for Tui {} pub struct Bold>(pub bool, W); - impl> Content for Bold { - fn content (&self) -> impl Content { - Some(&self.1) - } + fn content (&self) -> impl Render { &self.1 } fn render (&self, to: &mut TuiOut) { to.fill_bold(to.area(), self.0); self.1.render(to) @@ -33,11 +30,8 @@ impl> Content for Bold { } pub struct Foreground>(pub Color, W); - impl> Content for Foreground { - fn content (&self) -> impl Content { - Some(&self.1) - } + fn content (&self) -> impl Render { &self.1 } fn render (&self, to: &mut TuiOut) { to.fill_fg(to.area(), self.0); self.1.render(to) @@ -45,11 +39,8 @@ impl> Content for Foreground { } pub struct Background>(pub Color, W); - impl> Content for Background { - fn content (&self) -> impl Content { - Some(&self.1) - } + fn content (&self) -> impl Render { &self.1 } fn render (&self, to: &mut TuiOut) { to.fill_bg(to.area(), self.0); self.1.render(to) @@ -57,11 +48,8 @@ impl> Content for Background { } pub struct Styled>(pub Option