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::*;
pub trait LayoutSplit<E: Engine>: Render<E> + Sized {
fn split <W: Render<E>> (
self, direction: Direction, amount: E::Unit, other: W
) -> Split<E, Self, W> { Split::new(direction, amount, self, other) }
fn split_flip <W: Render<E>> (
self, direction: Direction, amount: E::Unit, other: W
) -> Split<E, W, Self> { Split::new(direction, amount, other, self) }
impl<E: Engine> LayoutSplit<E> for E {}
pub trait LayoutSplit<E: Engine> {
fn split <A: Render<E>, B: Render<E>> (
direction: Direction, amount: E::Unit, a: A, b: B
) -> Split<E, A, B> {
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

View file

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

View file

@ -91,7 +91,6 @@ impl Audio for SequencerTui {
//self.now().set(now);
//}
//}
// End profiling cycle
self.perf.update(t0, scope);
@ -108,12 +107,11 @@ render!(|self: SequencerTui|lay!([
false
})),
row!([
Tui::fixed_x(20, col!([
PhraseSelector::edit_phrase(
&self.editor.phrase,
self.focused() == SequencerFocus::PhraseEditor,
self.entered()
),
Tui::fixed_x(20, Tui::split_up(2, PhraseSelector::edit_phrase(
&self.editor.phrase,
self.focused() == SequencerFocus::PhraseEditor,
self.entered()
), col!([
PhraseSelector::play_phrase(
&self.player,
self.focused() == SequencerFocus::PhrasePlay,
@ -125,7 +123,8 @@ render!(|self: SequencerTui|lay!([
self.entered()
),
PhraseListView::from(self),
])),
]))),
PhraseView::from(self)
])
])),
@ -228,7 +227,7 @@ pub struct SequencerStatusBar {
impl StatusBar for SequencerStatusBar {
type State = SequencerTui;
fn hotkey_fg () -> Color {
TuiTheme::hotkey_fg()
TuiTheme::HOTKEY_FG
}
fn update (&mut self, _: &SequencerTui) {
todo!()
@ -422,7 +421,18 @@ impl InputToCommand<Tui, SequencerTui> for SequencerCommand {
} else {
to_focus_command(input).map(SequencerCommand::Focus)
.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,23 +63,27 @@ pub struct TransportView {
sr: String,
bpm: String,
ppq: String,
beat: String,
global_sample: String,
global_second: String,
started: bool,
current_sample: String,
current_second: String,
current_sample: f64,
current_second: f64,
}
impl<T: HasClock> From<(&T, bool)> for TransportView {
fn from ((state, focused): (&T, bool)) -> Self {
let clock = state.clock();
let sr = format!("{:.1}k", clock.timebase.sr.get() / 1000.0);
let bpm = format!("{:.3}", clock.timebase.bpm.get());
let ppq = format!("{:.0}", clock.timebase.ppq.get());
let bpm = format!("{:.3}", clock.timebase.bpm.get());
let ppq = format!("{:.0}", clock.timebase.ppq.get());
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 {
focused,
sr,
@ -88,8 +92,11 @@ impl<T: HasClock> From<(&T, bool)> for TransportView {
started: true,
global_sample: format!("{:.0}k", started.sample.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_second: format!("{:.1}s", (clock.global.usec.get() - started.usec.get())/1000000.),
current_sample,
current_second,
beat: clock.timebase.format_beats_0(
clock.timebase.usecs_to_pulse(current_usec)
),
}
} else {
Self {
@ -100,8 +107,9 @@ impl<T: HasClock> From<(&T, bool)> for TransportView {
started: false,
global_sample: format!("{:.0}k", clock.global.sample.get()/1000.),
global_second: format!("{:.1}s", clock.global.usec.get()/1000000.),
current_sample: "0".to_string(),
current_second: "0.0s".to_string(),
current_sample: 0.0,
current_second: 0.0,
beat: format!("0.0.00")
}
}
}
@ -116,16 +124,19 @@ render!(|self: TransportField<'a>|{
});
render!(|self: TransportView|{
let bg = if self.focused { TuiTheme::border_bg() } else { TuiTheme::bg() };
let border_style = Style::default()
.bg(if self.focused { TuiTheme::border_bg() } else { TuiTheme::bg() })
.bg(bg)
.fg(TuiTheme::border_fg(self.focused));
lay!(move|add|{
add(&Tui::fill_x(Lozenge(border_style)))?;
add(&Tui::outset_x(1, row!([
TransportField("Beat", "00X+0/0B+00/00P"),
" ",
TransportField("BPM ", self.bpm.as_str()),
" ",
Tui::bg(bg, lay!(move|add|{
add(&Tui::fill_x(Tui::at_w(lay!(move|add|{
add(&Lozenge(border_style))?;
add(&Tui::outset_x(1, row!([
TransportField("Beat", self.beat.as_str()), " ",
TransportField("BPM ", self.bpm.as_str()), " ",
])))
}))))?;
add(&Tui::fill_x(Tui::center_x(Tui::pull_x(2, row!([
col!(|add|{
if self.started {
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")]))
}
}),
" ",
TransportField("Second", self.current_second.as_str()),
" ",
TransportField("SR ", self.sr.as_str()),
" ",
TransportField("Sample", self.current_sample.as_str()),
])))
})
])))))?;
add(&Tui::fill_x(Tui::at_e(lay!(move|add|{
add(&Lozenge(border_style))?;
add(&Tui::outset_x(1, row!([
TransportField("Second", format!("{:.1}s", self.current_second).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
@ -198,7 +211,7 @@ pub struct TransportStatusBar;
impl StatusBar for TransportStatusBar {
type State = ();
fn hotkey_fg () -> Color {
TuiTheme::hotkey_fg()
TuiTheme::HOTKEY_FG
}
fn update (&mut self, _: &()) {
todo!()

View file

@ -1,33 +1,37 @@
use crate::*;
#[derive(Copy,Clone)]
pub struct TuiTheme;
impl TuiTheme {
pub fn bg () -> Color {
Color::Rgb(28, 35, 25)
}
pub fn border_bg () -> 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 {
impl Theme for TuiTheme {}
pub trait Theme {
const HOTKEY_FG: Color = Color::Rgb(255, 255, 0);
fn black () -> Color {
Color::Rgb(0, 0, 0)
}
pub const fn hotkey_fg () -> Color {
Color::Rgb(255, 255, 0)
fn bg () -> Color {
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)
}
pub fn mode_fg () -> Color {
fn mode_fg () -> Color {
Color::Rgb(255, 255, 255)
}
pub fn status_bar_bg () -> Color {
fn status_bar_bg () -> Color {
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
render!(|self: PhraseListView<'a>|{
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 title_color = if *focused {Color::Rgb(150, 160, 90)} else {Color::Rgb(120, 130, 100)};
let upper_left = format!("{title}");
let upper_right = format!("({})", phrases.len());
lay!(move|add|{
//if *focused {
add(&Lozenge(Style::default().bg(border_bg).fg(border_color)))?;
//}
Tui::bg(border_bg, lay!(move|add|{
add(&Lozenge(Style::default().bg(border_bg).fg(border_color)))?;
add(&Tui::inset_xy(0, 1, Tui::fill_xy(col!(move|add|match mode {
Some(PhrasesMode::Import(_, ref 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_ne(Tui::pull_x(1, Tui::fg(title_color, upper_right.to_string())))))
})
}))
});
#[derive(Clone, PartialEq, Debug)]

View file

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