global zoom in sequencer

This commit is contained in:
🪞👃🪞 2024-12-10 19:27:54 +01:00
parent c4453a85fb
commit 6705585f91
7 changed files with 106 additions and 71 deletions

View file

@ -1,12 +1,22 @@
use crate::*; use crate::*;
pub trait LayoutSplit<E: Engine>: Render<E> + Sized { impl<E: Engine> LayoutSplit<E> for E {}
fn split <W: Render<E>> (
self, direction: Direction, amount: E::Unit, other: W pub trait LayoutSplit<E: Engine> {
) -> Split<E, Self, W> { Split::new(direction, amount, self, other) } fn split <A: Render<E>, B: Render<E>> (
fn split_flip <W: Render<E>> ( direction: Direction, amount: E::Unit, a: A, b: B
self, direction: Direction, amount: E::Unit, other: W ) -> Split<E, A, B> {
) -> Split<E, W, Self> { Split::new(direction, amount, other, self) } Split::new(direction, amount, a, b)
}
fn split_up <A: Render<E>, B: Render<E>> (
amount: E::Unit, a: A, b: B
) -> Split<E, A, B> {
Split::new(Direction::Up, amount, a, b)
}
//fn split_flip <W: Render<E>> (
//self, direction: Direction, amount: E::Unit, other: W
//) -> Split<E, W, Self> { Split::new(direction, amount, other, self) }
} }
/// A binary split with fixed proportion /// A binary split with fixed proportion

View file

@ -240,7 +240,7 @@ pub enum ArrangerStatus {
impl StatusBar for ArrangerStatus { impl StatusBar for ArrangerStatus {
type State = (ArrangerFocus, ArrangerSelection, bool); type State = (ArrangerFocus, ArrangerSelection, bool);
fn hotkey_fg () -> Color where Self: Sized { fn hotkey_fg () -> Color where Self: Sized {
TuiTheme::hotkey_fg() TuiTheme::HOTKEY_FG
} }
fn update (&mut self, (focused, selected, entered): &Self::State) { fn update (&mut self, (focused, selected, entered): &Self::State) {
*self = match focused { *self = match focused {

View file

@ -91,7 +91,6 @@ impl Audio for SequencerTui {
//self.now().set(now); //self.now().set(now);
//} //}
//} //}
// End profiling cycle // End profiling cycle
self.perf.update(t0, scope); self.perf.update(t0, scope);
@ -108,12 +107,11 @@ render!(|self: SequencerTui|lay!([
false false
})), })),
row!([ row!([
Tui::fixed_x(20, col!([ Tui::fixed_x(20, Tui::split_up(2, PhraseSelector::edit_phrase(
PhraseSelector::edit_phrase(
&self.editor.phrase, &self.editor.phrase,
self.focused() == SequencerFocus::PhraseEditor, self.focused() == SequencerFocus::PhraseEditor,
self.entered() self.entered()
), ), col!([
PhraseSelector::play_phrase( PhraseSelector::play_phrase(
&self.player, &self.player,
self.focused() == SequencerFocus::PhrasePlay, self.focused() == SequencerFocus::PhrasePlay,
@ -125,7 +123,8 @@ render!(|self: SequencerTui|lay!([
self.entered() self.entered()
), ),
PhraseListView::from(self), PhraseListView::from(self),
])),
]))),
PhraseView::from(self) PhraseView::from(self)
]) ])
])), ])),
@ -228,7 +227,7 @@ pub struct SequencerStatusBar {
impl StatusBar for SequencerStatusBar { impl StatusBar for SequencerStatusBar {
type State = SequencerTui; type State = SequencerTui;
fn hotkey_fg () -> Color { fn hotkey_fg () -> Color {
TuiTheme::hotkey_fg() TuiTheme::HOTKEY_FG
} }
fn update (&mut self, _: &SequencerTui) { fn update (&mut self, _: &SequencerTui) {
todo!() todo!()
@ -422,7 +421,18 @@ impl InputToCommand<Tui, SequencerTui> for SequencerCommand {
} else { } else {
to_focus_command(input).map(SequencerCommand::Focus) to_focus_command(input).map(SequencerCommand::Focus)
.or_else(||to_sequencer_command(state, input)) .or_else(||to_sequencer_command(state, input))
}.or_else(||Some({
let time_zoom = state.editor.view_mode.time_zoom();
let next_zoom = next_note_length(time_zoom);
let prev_zoom = prev_note_length(time_zoom);
match input.event() {
key!(Char('-')) => SequencerCommand::Editor(PhraseCommand::SetTimeZoom(next_zoom)),
key!(Char('_')) => SequencerCommand::Editor(PhraseCommand::SetTimeZoom(next_zoom)),
key!(Char('=')) => SequencerCommand::Editor(PhraseCommand::SetTimeZoom(prev_zoom)),
key!(Char('+')) => SequencerCommand::Editor(PhraseCommand::SetTimeZoom(prev_zoom)),
_ => return None
} }
}))
} }
} }

View file

@ -63,14 +63,15 @@ pub struct TransportView {
sr: String, sr: String,
bpm: String, bpm: String,
ppq: String, ppq: String,
beat: String,
global_sample: String, global_sample: String,
global_second: String, global_second: String,
started: bool, started: bool,
current_sample: String, current_sample: f64,
current_second: String, current_second: f64,
} }
impl<T: HasClock> From<(&T, bool)> for TransportView { impl<T: HasClock> From<(&T, bool)> for TransportView {
@ -80,6 +81,9 @@ impl<T: HasClock> From<(&T, bool)> for TransportView {
let bpm = format!("{:.3}", clock.timebase.bpm.get()); let bpm = format!("{:.3}", clock.timebase.bpm.get());
let ppq = format!("{:.0}", clock.timebase.ppq.get()); let ppq = format!("{:.0}", clock.timebase.ppq.get());
if let Some(started) = clock.started.read().unwrap().as_ref() { 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 { Self {
focused, focused,
sr, sr,
@ -88,8 +92,11 @@ impl<T: HasClock> From<(&T, bool)> for TransportView {
started: true, started: true,
global_sample: format!("{:.0}k", started.sample.get()/1000.), global_sample: format!("{:.0}k", started.sample.get()/1000.),
global_second: format!("{:.1}s", started.usec.get()/1000.), global_second: format!("{:.1}s", started.usec.get()/1000.),
current_sample: format!("{:.0}k", (clock.global.sample.get() - started.sample.get())/1000.), current_sample,
current_second: format!("{:.1}s", (clock.global.usec.get() - started.usec.get())/1000000.), current_second,
beat: clock.timebase.format_beats_0(
clock.timebase.usecs_to_pulse(current_usec)
),
} }
} else { } else {
Self { Self {
@ -100,8 +107,9 @@ impl<T: HasClock> From<(&T, bool)> for TransportView {
started: false, started: false,
global_sample: format!("{:.0}k", clock.global.sample.get()/1000.), global_sample: format!("{:.0}k", clock.global.sample.get()/1000.),
global_second: format!("{:.1}s", clock.global.usec.get()/1000000.), global_second: format!("{:.1}s", clock.global.usec.get()/1000000.),
current_sample: "0".to_string(), current_sample: 0.0,
current_second: "0.0s".to_string(), current_second: 0.0,
beat: format!("0.0.00")
} }
} }
} }
@ -116,16 +124,19 @@ render!(|self: TransportField<'a>|{
}); });
render!(|self: TransportView|{ render!(|self: TransportView|{
let bg = if self.focused { TuiTheme::border_bg() } else { TuiTheme::bg() };
let border_style = Style::default() let border_style = Style::default()
.bg(if self.focused { TuiTheme::border_bg() } else { TuiTheme::bg() }) .bg(bg)
.fg(TuiTheme::border_fg(self.focused)); .fg(TuiTheme::border_fg(self.focused));
lay!(move|add|{ Tui::bg(bg, lay!(move|add|{
add(&Tui::fill_x(Lozenge(border_style)))?; add(&Tui::fill_x(Tui::at_w(lay!(move|add|{
add(&Lozenge(border_style))?;
add(&Tui::outset_x(1, row!([ add(&Tui::outset_x(1, row!([
TransportField("Beat", "00X+0/0B+00/00P"), TransportField("Beat", self.beat.as_str()), " ",
" ", TransportField("BPM ", self.bpm.as_str()), " ",
TransportField("BPM ", self.bpm.as_str()), ])))
" ", }))))?;
add(&Tui::fill_x(Tui::center_x(Tui::pull_x(2, row!([
col!(|add|{ col!(|add|{
if self.started { if self.started {
add(&col!([Tui::fg(Color::Rgb(0, 255, 0), "▶ PLAYING"), ""])) add(&col!([Tui::fg(Color::Rgb(0, 255, 0), "▶ PLAYING"), ""]))
@ -133,14 +144,16 @@ render!(|self: TransportView|{
add(&col!(["", Tui::fg(Color::Rgb(255, 128, 0), "⏹ STOPPED")])) add(&col!(["", Tui::fg(Color::Rgb(255, 128, 0), "⏹ STOPPED")]))
} }
}), }),
" ", ])))))?;
TransportField("Second", self.current_second.as_str()), add(&Tui::fill_x(Tui::at_e(lay!(move|add|{
" ", add(&Lozenge(border_style))?;
TransportField("SR ", self.sr.as_str()), add(&Tui::outset_x(1, row!([
" ", TransportField("Second", format!("{:.1}s", self.current_second).as_str()), " ",
TransportField("Sample", self.current_sample.as_str()), TransportField("Rate ", self.sr.as_str()), " ",
TransportField("Sample", format!("{:.0}k", self.current_sample).as_str()),
]))) ])))
}) }))))
}))
}); });
/// Which item of the transport toolbar is focused /// Which item of the transport toolbar is focused
@ -198,7 +211,7 @@ pub struct TransportStatusBar;
impl StatusBar for TransportStatusBar { impl StatusBar for TransportStatusBar {
type State = (); type State = ();
fn hotkey_fg () -> Color { fn hotkey_fg () -> Color {
TuiTheme::hotkey_fg() TuiTheme::HOTKEY_FG
} }
fn update (&mut self, _: &()) { fn update (&mut self, _: &()) {
todo!() todo!()

View file

@ -1,33 +1,37 @@
use crate::*; use crate::*;
#[derive(Copy,Clone)]
pub struct TuiTheme; pub struct TuiTheme;
impl TuiTheme { impl Theme for TuiTheme {}
pub fn bg () -> Color {
Color::Rgb(28, 35, 25) pub trait Theme {
} const HOTKEY_FG: Color = Color::Rgb(255, 255, 0);
pub fn border_bg () -> Color { fn black () -> Color {
Color::Rgb(40, 50, 30)
}
pub fn border_fg (focused: bool) -> Color {
if focused { Color::Rgb(100, 110, 40) } else { Color::Rgb(70, 80, 50) }
}
pub fn title_fg (focused: bool) -> Color {
if focused { Color::Rgb(150, 160, 90) } else { Color::Rgb(120, 130, 100) }
}
pub fn separator_fg (_: bool) -> Color {
Color::Rgb(0, 0, 0) Color::Rgb(0, 0, 0)
} }
pub const fn hotkey_fg () -> Color { fn bg () -> Color {
Color::Rgb(255, 255, 0) Color::Rgb(28, 35, 25)
} }
pub fn mode_bg () -> Color { fn border_bg () -> Color {
Color::Rgb(40, 50, 30)
}
fn border_fg (focused: bool) -> Color {
if focused { Color::Rgb(100, 110, 40) } else { Color::Rgb(70, 80, 50) }
}
fn title_fg (focused: bool) -> Color {
if focused { Color::Rgb(150, 160, 90) } else { Color::Rgb(120, 130, 100) }
}
fn separator_fg (_: bool) -> Color {
Color::Rgb(0, 0, 0)
}
fn mode_bg () -> Color {
Color::Rgb(150, 160, 90) Color::Rgb(150, 160, 90)
} }
pub fn mode_fg () -> Color { fn mode_fg () -> Color {
Color::Rgb(255, 255, 255) Color::Rgb(255, 255, 255)
} }
pub fn status_bar_bg () -> Color { fn status_bar_bg () -> Color {
Color::Rgb(28, 35, 25) Color::Rgb(28, 35, 25)
} }
} }

View file

@ -122,15 +122,13 @@ impl<'a, T: HasPhraseList> From<&'a T> for PhraseListView<'a> {
// TODO: Display phrases always in order of appearance // TODO: Display phrases always in order of appearance
render!(|self: PhraseListView<'a>|{ render!(|self: PhraseListView<'a>|{
let Self { title, focused, entered, phrases, index, mode } = self; let Self { title, focused, entered, phrases, index, mode } = self;
let border_bg = if *entered {Color::Rgb(40, 50, 30)} else {Color::Reset}; let border_bg = if *focused {Color::Rgb(40, 50, 30)} else {Color::Reset};
let border_color = if *entered {Color::Rgb(100, 110, 40)} else {Color::Rgb(70, 80, 50)}; let border_color = if *entered {Color::Rgb(100, 110, 40)} else {Color::Rgb(70, 80, 50)};
let title_color = if *focused {Color::Rgb(150, 160, 90)} else {Color::Rgb(120, 130, 100)}; let title_color = if *focused {Color::Rgb(150, 160, 90)} else {Color::Rgb(120, 130, 100)};
let upper_left = format!("{title}"); let upper_left = format!("{title}");
let upper_right = format!("({})", phrases.len()); let upper_right = format!("({})", phrases.len());
lay!(move|add|{ Tui::bg(border_bg, lay!(move|add|{
//if *focused {
add(&Lozenge(Style::default().bg(border_bg).fg(border_color)))?; add(&Lozenge(Style::default().bg(border_bg).fg(border_color)))?;
//}
add(&Tui::inset_xy(0, 1, Tui::fill_xy(col!(move|add|match mode { add(&Tui::inset_xy(0, 1, Tui::fill_xy(col!(move|add|match mode {
Some(PhrasesMode::Import(_, ref browser)) => { Some(PhrasesMode::Import(_, ref browser)) => {
add(browser) add(browser)
@ -170,7 +168,7 @@ render!(|self: PhraseListView<'a>|{
}))))?; }))))?;
add(&Tui::fill_xy(Tui::at_nw(Tui::push_x(1, Tui::fg(title_color, upper_left.to_string())))))?; add(&Tui::fill_xy(Tui::at_nw(Tui::push_x(1, Tui::fg(title_color, upper_left.to_string())))))?;
add(&Tui::fill_xy(Tui::at_ne(Tui::pull_x(1, Tui::fg(title_color, upper_right.to_string()))))) add(&Tui::fill_xy(Tui::at_ne(Tui::pull_x(1, Tui::fg(title_color, upper_right.to_string())))))
}) }))
}); });
#[derive(Clone, PartialEq, Debug)] #[derive(Clone, PartialEq, Debug)]

View file

@ -21,9 +21,9 @@ render!(|self: PhraseSelector|{
Color::Rgb(120, 130, 100) Color::Rgb(120, 130, 100)
}; };
Tui::fixed_y(2, lay!(move|add|{ Tui::fixed_y(2, lay!(move|add|{
//if phrase.is_none() { if phrase.is_none() {
add(&Tui::fill_x(border))?; add(&Tui::fill_x(border))?;
//} }
add(&Tui::push_x(1, Tui::fg(title_color, *title)))?; add(&Tui::push_x(1, Tui::fg(title_color, *title)))?;
add(&Tui::push_y(0, Tui::fill_xy(Layers::new(move|add|{ add(&Tui::push_y(0, Tui::fill_xy(Layers::new(move|add|{
if let Some((instant, Some(phrase))) = phrase { if let Some((instant, Some(phrase))) = phrase {