refactor bsp, rebalance color, BIG PLAY BUTTON

This commit is contained in:
🪞👃🪞 2024-12-16 04:18:40 +01:00
parent dcd6bc24a7
commit d401870b2d
9 changed files with 215 additions and 152 deletions

View file

@ -86,7 +86,7 @@ impl Default for MidiPointModel {
fn default () -> Self {
Self {
time_point: Arc::new(0.into()),
note_point: Arc::new(0.into()),
note_point: Arc::new(36.into()),
note_len: Arc::new(24.into()),
}
}

View file

@ -61,11 +61,11 @@ impl From<Color> for ItemPalette {
impl From<ItemColor> for ItemPalette {
fn from (base: ItemColor) -> Self {
let mut light = base.okhsl.clone();
light.lightness = (light.lightness * 1.33).min(Okhsl::<f32>::max_lightness());
light.lightness = (light.lightness * 4. / 3.).min(Okhsl::<f32>::max_lightness());
let mut lighter = light.clone();
lighter.lightness = (lighter.lightness * 1.33).min(Okhsl::<f32>::max_lightness());
lighter.lightness = (lighter.lightness * 5. / 3.).min(Okhsl::<f32>::max_lightness());
let mut lightest = lighter.clone();
lightest.lightness = (lightest.lightness * 1.33).min(Okhsl::<f32>::max_lightness());
lightest.lightness = (lightest.lightness * 4. / 3.).min(Okhsl::<f32>::max_lightness());
let mut dark = base.okhsl.clone();
dark.lightness = (dark.lightness * 0.75).max(Okhsl::<f32>::min_lightness());

View file

@ -1,105 +1,150 @@
use crate::*;
impl<E: Engine> LayoutBspStatic<E> for E {}
pub enum Bsp<E: Engine, X: Render<E>, Y: Render<E>> {
/// X is north of Y
N(Option<X>, Option<Y>),
/// X is south of Y
S(Option<X>, Option<Y>),
/// X is east of Y
E(Option<X>, Option<Y>),
/// X is west of Y
W(Option<X>, Option<Y>),
/// X is above Y
A(Option<X>, Option<Y>),
/// X is below Y
B(Option<X>, Option<Y>),
/// Should be avoided.
Null(PhantomData<E>),
}
pub trait LayoutBspStatic<E: Engine>: {
fn over <A: Render<E>, B: Render<E>> (a: A, b: B) -> Over<E, A, B> {
Over(Default::default(), a, b)
}
fn under <A: Render<E>, B: Render<E>> (a: A, b: B) -> Under<E, A, B> {
Under(Default::default(), a, b)
}
fn to_north <A: Render<E>, B: Render<E>> (a: A, b: B) -> ToNorth<E, A, B> {
ToNorth(None, a, b)
}
fn to_south <A: Render<E>, B: Render<E>> (a: A, b: B) -> ToSouth<E, A, B> {
ToSouth(None, a, b)
}
fn to_east <A: Render<E>, B: Render<E>> (a: A, b: B) -> ToEast<E, A, B> {
ToEast(None, a, b)
}
fn to_west <A: Render<E>, B: Render<E>> (a: A, b: B) -> ToWest<E, A, B> {
ToWest(None, a, b)
render!(|self:Bsp<E: Engine, X: Render<E>, Y: Render<E>>|match self {
Bsp::Null(_) => { () },
Bsp::N(a, b) => { todo!("") },
Bsp::S(a, b) => { todo!("") },
Bsp::E(a, b) => { todo!("") },
Bsp::W(a, b) => { todo!("") },
Bsp::A(a, b) => { todo!("") },
Bsp::B(a, b) => { todo!("") },
});
impl<E: Engine, X: Render<E>, Y: Render<E>> Bsp<E, X, Y> {
pub fn new (x: X) -> Self { Self::A(Some(x), None) }
pub fn n (x: X, y: Y) -> Self { Self::N(Some(x), Some(y)) }
pub fn s (x: X, y: Y) -> Self { Self::S(Some(x), Some(y)) }
pub fn e (x: X, y: Y) -> Self { Self::E(Some(x), Some(y)) }
pub fn w (x: X, y: Y) -> Self { Self::W(Some(x), Some(y)) }
pub fn a (x: X, y: Y) -> Self { Self::A(Some(x), Some(y)) }
pub fn b (x: X, y: Y) -> Self { Self::B(Some(x), Some(y)) }
}
impl<E: Engine, X: Render<E>, Y: Render<E>> Default for Bsp<E, X, Y> {
fn default () -> Self {
Self::Null(Default::default())
}
}
pub trait LayoutBspFixedStatic<E: Engine>: {
fn to_north <A: Render<E>, B: Render<E>> (n: E::Unit, a: A, b: B) -> ToNorth<E, A, B> {
ToNorth(Some(n), a, b)
}
fn to_south <A: Render<E>, B: Render<E>> (n: E::Unit, a: A, b: B) -> ToSouth<E, A, B> {
ToSouth(Some(n), a, b)
}
fn to_east <A: Render<E>, B: Render<E>> (n: E::Unit, a: A, b: B) -> ToEast<E, A, B> {
ToEast(Some(n), a, b)
}
fn to_west <A: Render<E>, B: Render<E>> (n: E::Unit, a: A, b: B) -> ToWest<E, A, B> {
ToWest(Some(n), a, b)
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
pub struct Over<E: Engine, A: Render<E>, B: Render<E>>(PhantomData<E>, A, B);
//impl<E: Engine> LayoutBspStatic<E> for E {}
pub struct Under<E: Engine, A: Render<E>, B: Render<E>>(PhantomData<E>, A, B);
//pub trait LayoutBspStatic<E: Engine>: {
//fn n <A: Render<E>, B: Render<E>> (a: A, b: B) -> ToNorth<E, A, B> {
//ToNorth(None, a, b)
//}
//fn s <A: Render<E>, B: Render<E>> (a: A, b: B) -> ToSouth<E, A, B> {
//ToSouth(None, a, b)
//}
//fn e <A: Render<E>, B: Render<E>> (a: A, b: B) -> ToEast<E, A, B> {
//ToEast(None, a, b)
//}
//fn w <A: Render<E>, B: Render<E>> (a: A, b: B) -> ToWest<E, A, B> {
//ToWest(None, a, b)
//}
//fn i <A: Render<E>, B: Render<E>> (a: A, b: B) -> Over<E, A, B> {
//Over(Default::default(), a, b)
//}
//fn o <A: Render<E>, B: Render<E>> (a: A, b: B) -> Under<E, A, B> {
//Under(Default::default(), a, b)
//}
//}
pub struct ToNorth<E: Engine, A: Render<E>, B: Render<E>>(Option<E::Unit>, A, B);
//pub trait LayoutBspFixedStatic<E: Engine>: {
//fn to_north <A: Render<E>, B: Render<E>> (n: E::Unit, a: A, b: B) -> ToNorth<E, A, B> {
//ToNorth(Some(n), a, b)
//}
//fn to_south <A: Render<E>, B: Render<E>> (n: E::Unit, a: A, b: B) -> ToSouth<E, A, B> {
//ToSouth(Some(n), a, b)
//}
//fn to_east <A: Render<E>, B: Render<E>> (n: E::Unit, a: A, b: B) -> ToEast<E, A, B> {
//ToEast(Some(n), a, b)
//}
//fn to_west <A: Render<E>, B: Render<E>> (n: E::Unit, a: A, b: B) -> ToWest<E, A, B> {
//ToWest(Some(n), a, b)
//}
//}
pub struct ToSouth<E: Engine, A: Render<E>, B: Render<E>>(Option<E::Unit>, A, B);
//pub struct Over<E: Engine, A: Render<E>, B: Render<E>>(PhantomData<E>, A, B);
pub struct ToEast<E: Engine, A, B>(Option<E::Unit>, A, B);
//pub struct Under<E: Engine, A: Render<E>, B: Render<E>>(PhantomData<E>, A, B);
pub struct ToWest<E: Engine, A: Render<E>, B: Render<E>>(Option<E::Unit>, A, B);
//pub struct ToNorth<E: Engine, A: Render<E>, B: Render<E>>(Option<E::Unit>, A, B);
impl<E: Engine, A: Render<E>, B: Render<E>> Render<E> for Over<E, A, B> {
fn min_size (&self, _: E::Size) -> Perhaps<E::Size> {
todo!();
}
fn render (&self, _: &mut E::Output) -> Usually<()> {
Ok(())
}
}
//pub struct ToSouth<E: Engine, A: Render<E>, B: Render<E>>(Option<E::Unit>, A, B);
impl<E: Engine, A: Render<E>, B: Render<E>> Render<E> for Under<E, A, B> {
fn min_size (&self, _: E::Size) -> Perhaps<E::Size> {
todo!();
}
fn render (&self, _: &mut E::Output) -> Usually<()> {
Ok(())
}
}
//pub struct ToEast<E: Engine, A, B>(Option<E::Unit>, A, B);
impl<E: Engine, A: Render<E>, B: Render<E>> Render<E> for ToNorth<E, A, B> {
fn min_size (&self, _: E::Size) -> Perhaps<E::Size> {
todo!();
}
fn render (&self, _: &mut E::Output) -> Usually<()> {
Ok(())
}
}
//pub struct ToWest<E: Engine, A: Render<E>, B: Render<E>>(Option<E::Unit>, A, B);
impl<E: Engine, A: Render<E>, B: Render<E>> Render<E> for ToSouth<E, A, B> {
fn min_size (&self, _: E::Size) -> Perhaps<E::Size> {
todo!();
}
fn render (&self, _: &mut E::Output) -> Usually<()> {
Ok(())
}
}
//impl<E: Engine, A: Render<E>, B: Render<E>> Render<E> for Over<E, A, B> {
//fn min_size (&self, _: E::Size) -> Perhaps<E::Size> {
//todo!();
//}
//fn render (&self, _: &mut E::Output) -> Usually<()> {
//Ok(())
//}
//}
impl<E: Engine, A: Render<E>, B: Render<E>> Render<E> for ToWest<E, A, B> {
fn min_size (&self, _: E::Size) -> Perhaps<E::Size> {
todo!();
}
fn render (&self, _: &mut E::Output) -> Usually<()> {
Ok(())
}
}
//impl<E: Engine, A: Render<E>, B: Render<E>> Render<E> for Under<E, A, B> {
//fn min_size (&self, _: E::Size) -> Perhaps<E::Size> {
//todo!();
//}
//fn render (&self, _: &mut E::Output) -> Usually<()> {
//Ok(())
//}
//}
impl<E: Engine, A: Render<E>, B: Render<E>> Render<E> for ToEast<E, A, B> {
fn min_size (&self, _: E::Size) -> Perhaps<E::Size> {
todo!();
}
fn render (&self, _: &mut E::Output) -> Usually<()> {
Ok(())
}
}
//impl<E: Engine, A: Render<E>, B: Render<E>> Render<E> for ToNorth<E, A, B> {
//fn min_size (&self, _: E::Size) -> Perhaps<E::Size> {
//todo!();
//}
//fn render (&self, _: &mut E::Output) -> Usually<()> {
//Ok(())
//}
//}
//impl<E: Engine, A: Render<E>, B: Render<E>> Render<E> for ToSouth<E, A, B> {
//fn min_size (&self, _: E::Size) -> Perhaps<E::Size> {
//todo!();
//}
//fn render (&self, _: &mut E::Output) -> Usually<()> {
//Ok(())
//}
//}
//impl<E: Engine, A: Render<E>, B: Render<E>> Render<E> for ToWest<E, A, B> {
//fn min_size (&self, _: E::Size) -> Perhaps<E::Size> {
//todo!();
//}
//fn render (&self, _: &mut E::Output) -> Usually<()> {
//Ok(())
//}
//}
//impl<E: Engine, A: Render<E>, B: Render<E>> Render<E> for ToEast<E, A, B> {
//fn min_size (&self, _: E::Size) -> Perhaps<E::Size> {
//todo!();
//}
//fn render (&self, _: &mut E::Output) -> Usually<()> {
//Ok(())
//}
//}

View file

@ -173,14 +173,16 @@ impl Tui {
}
}
struct Field(&'static str, String);
///////////////////////////////////////////////////////////////////////////////////////////////////
render!(|self: Field|{
Tui::to_east("", Tui::to_east(
Tui::bold(true, self.0),
Tui::bg(Color::Rgb(0, 0, 0), self.1.as_str()),
))
});
//struct Field(&'static str, String);
//render!(|self: Field|{
//Tui::to_east("│", Tui::to_east(
//Tui::bold(true, self.0),
//Tui::bg(Color::Rgb(0, 0, 0), self.1.as_str()),
//))
//});
//pub struct TransportView {
//pub(crate) state: Option<TransportState>,

View file

@ -85,6 +85,10 @@ impl Command<SequencerTui> for SequencerCommand {
impl InputToCommand<Tui, SequencerTui> for SequencerCommand {
fn input_to_command (state: &SequencerTui, input: &TuiInput) -> Option<Self> {
Some(match input.event() {
key_pat!(Char('u')) => { todo!("undo") },
key_pat!(Char('U')) => { todo!("redo") },
key_pat!(Ctrl-Char('k')) => { todo!("keyboard") },
// Toggle visibility of phrase pool column
key_pat!(Tab) => ShowPool(!state.show_pool),
// Enqueue currently edited phrase
@ -156,14 +160,16 @@ render!(|self: SequencerTui|{
let with_size = |x|lay!([self.size, x]);
let editor = with_bar(with_pool(Tui::fill_xy(&self.editor)));
let color = self.player.play_phrase().as_ref().map(|(_,p)|p.as_ref().map(|p|p.read().unwrap().color)).flatten().clone();
let play = Tui::fixed_xy(11, 2, PlayPause(self.clock.is_rolling()));
let playing = Tui::fixed_xy(14, 2, PhraseSelector::play_phrase(&self.player));
let next = Tui::fixed_xy(14, 2, PhraseSelector::next_phrase(&self.player));
let play = Tui::fixed_xy(5, 2, PlayPause(self.clock.is_rolling()));
let transport = Tui::fixed_y(2, TransportView::from((self, color, true)));
let toolbar = row!([play, playing, next, transport]);
let toolbar = row!([play, col!([
PhraseSelector::play_phrase(&self.player),
PhraseSelector::next_phrase(&self.player),
]), transport]);
with_size(with_status(col!([ toolbar, editor, ])))
});
has_size!(<Tui>|self:SequencerTui|&self.size);
has_clock!(|self:SequencerTui|&self.clock);
has_phrases!(|self:SequencerTui|self.phrases.phrases);
has_editor!(|self:SequencerTui|self.editor);
@ -176,16 +182,12 @@ pub struct PhraseSelector {
}
// TODO: Display phrases always in order of appearance
render!(|self: PhraseSelector|Tui::fixed_y(2, col!([
lay!(move|add|{
add(&Tui::push_x(1, Tui::fg(TuiTheme::g(240), self.title)))?;
add(&Tui::bg(self.color.base.rgb, Tui::fill_x(Tui::inset_x(1, Tui::fill_x(Tui::at_e(
Tui::fg(self.color.lightest.rgb, &self.time)))))))?;
Ok(())
}),
Tui::bg(self.color.base.rgb,
Tui::fg(self.color.lightest.rgb,
Tui::bold(true, self.name.clone()))),
render!(|self: PhraseSelector|Tui::fixed_xy(24, 1, row!([
Tui::fg(self.color.lighter.rgb, Tui::bold(true, &self.title)),
Tui::bg(self.color.base.rgb, Tui::fg(self.color.lighter.rgb, row!([
format!("{:8}", &self.name[0..8.min(self.name.len())]),
Tui::bg(self.color.dark.rgb, &self.time),
]))),
])));
impl PhraseSelector {
@ -200,9 +202,9 @@ impl PhraseSelector {
let time = if let Some(elapsed) = state.pulses_since_start_looped() {
format!("+{:>}", state.clock().timebase.format_beats_0(elapsed))
} else {
String::from("")
String::from(" ")
};
Self { title: "Now:", time, name, color, }
Self { title: " Now|", time, name, color, }
}
// beats until switchover
pub fn next_phrase <T: HasPlayPhrase> (state: &T) -> Self {
@ -219,17 +221,24 @@ impl PhraseSelector {
}
};
(time, name.clone(), color)
} else if let Some((_, Some(phrase))) = state.play_phrase() {
let phrase = phrase.read().unwrap();
if phrase.loop_on {
(" ".into(), phrase.name.clone(), phrase.color.clone())
} else {
(" ".into(), " ".into(), TuiTheme::g(64).into())
}
} else {
("".into(), "".into(), TuiTheme::g(64).into())
(" ".into(), " ".into(), TuiTheme::g(64).into())
};
Self { title: "Next:", time, name, color, }
Self { title: " Next|", time, name, color, }
}
pub fn edit_phrase (phrase: &Option<Arc<RwLock<Phrase>>>) -> Self {
let (time, name, color) = if let Some(phrase) = phrase {
let phrase = phrase.read().unwrap();
(format!("{}", phrase.length), phrase.name.clone(), phrase.color)
} else {
("".to_string(), "".to_string(), ItemPalette::from(TuiTheme::g(64)))
("".to_string(), " ".to_string(), ItemPalette::from(TuiTheme::g(64)))
};
Self { title: "Editing:", time, name, color }
}

View file

@ -42,18 +42,18 @@ audio!(|self:TransportTui,client,scope|ClockAudio(self).process(client, scope));
render!(|self: TransportTui|TransportView::from((self, None, true)));
pub struct TransportView {
bg: Color,
focused: bool,
color: ItemPalette,
focused: bool,
sr: String,
bpm: String,
ppq: String,
beat: String,
sr: String,
bpm: String,
ppq: String,
beat: String,
global_sample: String,
global_second: String,
global_sample: String,
global_second: String,
started: bool,
started: bool,
current_sample: f64,
current_second: f64,
@ -66,13 +66,12 @@ impl<T: HasClock> From<(&T, Option<ItemPalette>, bool)> for TransportView {
let bpm = format!("{:.3}", clock.timebase.bpm.get());
let ppq = format!("{:.0}", clock.timebase.ppq.get());
let color = color.unwrap_or(ItemPalette::from(TuiTheme::g(32)));
let bg = color.dark.rgb;
if let Some(started) = clock.started.read().unwrap().as_ref() {
let current_sample = (clock.global.sample.get() - started.sample.get())/1000.;
let current_usec = clock.global.usec.get() - started.usec.get();
let current_second = current_usec/1000000.;
Self {
bg, focused, sr, bpm, ppq,
color, focused, sr, bpm, ppq,
started: true,
global_sample: format!("{:.0}k", started.sample.get()/1000.),
global_second: format!("{:.1}s", started.usec.get()/1000.),
@ -84,7 +83,7 @@ impl<T: HasClock> From<(&T, Option<ItemPalette>, bool)> for TransportView {
}
} else {
Self {
bg, focused, sr, bpm, ppq,
color, focused, sr, bpm, ppq,
started: false,
global_sample: format!("{:.0}k", clock.global.sample.get()/1000.),
global_second: format!("{:.1}s", clock.global.usec.get()/1000000.),
@ -99,23 +98,25 @@ impl<T: HasClock> From<(&T, Option<ItemPalette>, bool)> for TransportView {
render!(|self: TransportView|{
struct Field<'a>(&'a str, &'a str);
let color = self.color;
struct Field<'a>(&'a str, &'a str, &'a ItemPalette);
render!(|self: Field<'a>|row!([
Tui::fg(Color::Rgb(200, 200, 200), self.0),
" ",
Tui::bold(true, Tui::fg(Color::Rgb(220, 220, 220), self.1)),
Tui::bg(Color::Reset, Tui::bold(true,
Tui::fg(self.2.lighter.rgb, self.0))),
Tui::fg(self.2.lighter.rgb, format!("{:>10}", self.1)),
]));
Tui::bg(self.bg, Tui::fill_x(row!([
Tui::bg(color.base.rgb, Tui::fill_x(row!([
//PlayPause(self.started), " ",
col!([
Field(" Beat", self.beat.as_str()),
Field(" BPM ", self.bpm.as_str()),
Field(" Beat|", self.beat.as_str(), &color),
Field(" BPM|", self.bpm.as_str(), &color),
]),
" ",
col!([
Field("Time ", format!("{:.1}s", self.current_second).as_str()),
Field("Sample", format!("{:.0}k", self.current_sample).as_str()),
Field(" Time|", format!("{:.1}s", self.current_second).as_str(), &color),
Field(" Smpl|", format!("{:.1}k", self.current_sample).as_str(), &color),
]),
])))
@ -124,11 +125,17 @@ render!(|self: TransportView|{
pub struct PlayPause(pub bool);
render!(|self: PlayPause|Tui::bg(
if self.0{Color::Rgb(0,128,0)}else{Color::Rgb(128,64,0)},
Tui::outset_x(1, Tui::fixed_x(9, col!(|add|if self.0 {
add(&Tui::fg(Color::Rgb(0, 255, 0), col!(["▶ PLAYING", "▒ ▒ ▒ ▒ ▒"])))
Tui::fixed_x(5, col!(|add|if self.0 {
add(&Tui::fg(Color::Rgb(0, 255, 0), col!([
" 🭍🭑🬽 ",
" 🭞🭜🭘 "
])))
} else {
add(&Tui::fg(Color::Rgb(255, 128, 0), col!(["▒ ▒ ▒ ▒ ▒", "⏹ STOPPED"])))
})))
add(&Tui::fg(Color::Rgb(255, 128, 0), col!([
" ▗▄▖ ",
" ▝▀▘ "
])))
}))
));
impl HasFocus for TransportTui {

View file

@ -107,15 +107,19 @@ impl Command<PhraseEditorModel> for PhraseCommand {
pub struct PhraseEditorModel {
/// Renders the phrase
pub mode: Box<dyn PhraseViewMode>,
pub size: Measure<Tui>
}
impl Default for PhraseEditorModel {
fn default () -> Self {
Self { mode: Box::new(PianoHorizontal::new(None)) }
Self { mode: Box::new(PianoHorizontal::new(None)), size: Measure::new() }
}
}
render!(|self: PhraseEditorModel|self.mode);
has_size!(<Tui>|self:PhraseEditorModel|&self.size);
render!(|self: PhraseEditorModel|&self.mode);
//render!(|self: PhraseEditorModel|lay!(|add|{add(&self.size)?;add(self.mode)}));//bollocks
pub trait PhraseViewMode: Render<Tui> + HasSize<Tui> + MidiRange + MidiPoint + Debug + Send + Sync {
fn buffer_size (&self, phrase: &Phrase) -> (usize, usize);
@ -130,8 +134,6 @@ pub trait PhraseViewMode: Render<Tui> + HasSize<Tui> + MidiRange + MidiPoint + D
impl MidiViewport<Tui> for PhraseEditorModel {}
has_size!(<Tui>|self:PhraseEditorModel|self.mode.size());
impl MidiRange for PhraseEditorModel {
fn time_zoom (&self) -> usize { self.mode.time_zoom() }
fn set_time_zoom (&self, x: usize) { self.mode.set_time_zoom(x); }

View file

@ -13,16 +13,11 @@ pub trait StatusBar: Render<Tui> {
row!([a, b, c] in commands.iter() => {
row!([a, Tui::fg(hotkey_fg, Tui::bold(true, b)), c])
})
//Tui::reduce(commands.iter(), |prev, [a, b, c]|
//Tui::to_east(prev,
//Tui::to_east(a,
//Tui::to_east(Tui::fg(hotkey_fg, Tui::bold(true, b)),
//c))))
}
fn with <'a> (state: &'a Self::State, content: impl Render<Tui>) -> impl Render<Tui>
where Self: Sized, &'a Self::State: Into<Self>
{
Tui::to_north(state.into(), content)
Bsp::n(state.into(), content)
}
}

View file

@ -7,6 +7,9 @@ impl Tui {
pub(crate) fn bg <W: Render<Tui>> (color: Color, w: W) -> Background<W> {
Background(color, w)
}
pub(crate) fn fg_bg <W: Render<Tui>> (fg: Color, bg: Color, w: W) -> Background<Foreground<W>> {
Background(bg, Foreground(fg, w))
}
pub(crate) fn bold <W: Render<Tui>> (on: bool, w: W) -> Bold<W> {
Bold(on, w)
}