mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-07 12:16:42 +01:00
global zoom in sequencer
This commit is contained in:
parent
c4453a85fb
commit
6705585f91
7 changed files with 106 additions and 71 deletions
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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!()
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)]
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue