disable advanced focus in tek_sequencer

This commit is contained in:
🪞👃🪞 2024-12-11 20:49:13 +01:00
parent 32e547194a
commit be924d447e
9 changed files with 311 additions and 341 deletions

View file

@ -34,7 +34,7 @@ impl<T: Copy + Debug + PartialEq> FocusState<T> {
} }
#[derive(Copy, Clone, PartialEq, Debug)] #[derive(Copy, Clone, PartialEq, Debug)]
pub enum FocusCommand { pub enum FocusCommand<T: Send + Sync> {
Up, Up,
Down, Down,
Left, Left,
@ -43,10 +43,11 @@ pub enum FocusCommand {
Prev, Prev,
Enter, Enter,
Exit, Exit,
Set(T)
} }
impl<F: HasFocus + HasEnter + FocusGrid + FocusOrder> Command<F> for FocusCommand { impl<F: HasFocus + HasEnter + FocusGrid + FocusOrder> Command<F> for FocusCommand<F::Item> {
fn execute (self, state: &mut F) -> Perhaps<FocusCommand> { fn execute (self, state: &mut F) -> Perhaps<FocusCommand<F::Item>> {
use FocusCommand::*; use FocusCommand::*;
match self { match self {
Next => { state.focus_next(); }, Next => { state.focus_next(); },
@ -57,6 +58,7 @@ impl<F: HasFocus + HasEnter + FocusGrid + FocusOrder> Command<F> for FocusComman
Right => { state.focus_right(); }, Right => { state.focus_right(); },
Enter => { state.focus_enter(); }, Enter => { state.focus_enter(); },
Exit => { state.focus_exit(); }, Exit => { state.focus_exit(); },
Set(to) => { state.set_focused(to); },
} }
Ok(None) Ok(None)
} }
@ -64,7 +66,7 @@ impl<F: HasFocus + HasEnter + FocusGrid + FocusOrder> Command<F> for FocusComman
/// Trait for things that have focusable subparts. /// Trait for things that have focusable subparts.
pub trait HasFocus { pub trait HasFocus {
type Item: Copy + PartialEq + Debug; type Item: Copy + PartialEq + Debug + Send + Sync;
/// Get the currently focused item. /// Get the currently focused item.
fn focused (&self) -> Self::Item; fn focused (&self) -> Self::Item;
/// Get the currently focused item. /// Get the currently focused item.
@ -249,55 +251,67 @@ impl<T: FocusGrid + HasEnter> FocusOrder for T {
} }
} }
#[cfg(test)] pub trait FocusWrap<T> {
mod test { fn wrap <'a, W: Render<Tui>> (self, focus: T, content: &'a W)
use super::*; -> impl Render<Tui> + 'a;
}
#[test] pub fn to_focus_command <T: Send + Sync> (input: &TuiInput) -> Option<FocusCommand<T>> {
fn test_focus () { use KeyCode::{Tab, BackTab, Up, Down, Left, Right, Enter, Esc};
Some(match input.event() {
key!(Tab) => FocusCommand::Next,
key!(Shift-Tab) => FocusCommand::Prev,
key!(BackTab) => FocusCommand::Prev,
key!(Shift-BackTab) => FocusCommand::Prev,
key!(Up) => FocusCommand::Up,
key!(Down) => FocusCommand::Down,
key!(Left) => FocusCommand::Left,
key!(Right) => FocusCommand::Right,
key!(Enter) => FocusCommand::Enter,
key!(Esc) => FocusCommand::Exit,
_ => return None
})
}
struct FocusTest { #[macro_export] macro_rules! impl_focus {
focused: char, ($Struct:ident $Focus:ident $Grid:expr $(=> [$self:ident : $update_focus:expr])?) => {
cursor: (usize, usize) impl HasFocus for $Struct {
} type Item = $Focus;
/// Get the currently focused item.
impl HasFocus for FocusTest {
type Item = char;
fn focused (&self) -> Self::Item { fn focused (&self) -> Self::Item {
self.focused self.focus.inner()
} }
/// Get the currently focused item.
fn set_focused (&mut self, to: Self::Item) { fn set_focused (&mut self, to: Self::Item) {
self.focused = to self.focus.set_inner(to)
}
$(fn focus_updated (&mut $self) { $update_focus })?
}
impl HasEnter for $Struct {
/// Get the currently focused item.
fn entered (&self) -> bool {
self.focus.is_entered()
}
/// Get the currently focused item.
fn set_entered (&mut self, entered: bool) {
if entered {
self.focus.to_entered()
} else {
self.focus.to_focused()
}
} }
} }
impl FocusGrid for $Struct {
impl FocusGrid for FocusTest {
fn focus_cursor (&self) -> (usize, usize) { fn focus_cursor (&self) -> (usize, usize) {
self.cursor self.cursor
} }
fn focus_cursor_mut (&mut self) -> &mut (usize, usize) { fn focus_cursor_mut (&mut self) -> &mut (usize, usize) {
&mut self.cursor &mut self.cursor
} }
fn focus_layout (&self) -> &[&[Self::Item]] { fn focus_layout (&self) -> &[&[$Focus]] {
&[ use $Focus::*;
&['a', 'a', 'a', 'b', 'b', 'd'], &$Grid
&['a', 'a', 'a', 'b', 'b', 'd'],
&['a', 'a', 'a', 'c', 'c', 'd'],
&['a', 'a', 'a', 'c', 'c', 'd'],
&['e', 'e', 'e', 'e', 'e', 'e'],
]
} }
} }
let mut tester = FocusTest { focused: 'a', cursor: (0, 0) };
tester.focus_right();
assert_eq!(tester.cursor.0, 3);
assert_eq!(tester.focused, 'b');
tester.focus_down();
assert_eq!(tester.cursor.1, 2);
assert_eq!(tester.focused, 'c');
} }
} }

View file

@ -0,0 +1,49 @@
#[cfg(test)] mod test_focus {
use super::focus::*;
#[test] fn test_focus () {
struct FocusTest {
focused: char,
cursor: (usize, usize)
}
impl HasFocus for FocusTest {
type Item = char;
fn focused (&self) -> Self::Item {
self.focused
}
fn set_focused (&mut self, to: Self::Item) {
self.focused = to
}
}
impl FocusGrid for FocusTest {
fn focus_cursor (&self) -> (usize, usize) {
self.cursor
}
fn focus_cursor_mut (&mut self) -> &mut (usize, usize) {
&mut self.cursor
}
fn focus_layout (&self) -> &[&[Self::Item]] {
&[
&['a', 'a', 'a', 'b', 'b', 'd'],
&['a', 'a', 'a', 'b', 'b', 'd'],
&['a', 'a', 'a', 'c', 'c', 'd'],
&['a', 'a', 'a', 'c', 'c', 'd'],
&['e', 'e', 'e', 'e', 'e', 'e'],
]
}
}
let mut tester = FocusTest { focused: 'a', cursor: (0, 0) };
tester.focus_right();
assert_eq!(tester.cursor.0, 3);
assert_eq!(tester.focused, 'b');
tester.focus_down();
assert_eq!(tester.cursor.1, 2);
assert_eq!(tester.focused, 'c');
}
}

View file

@ -1,6 +1,5 @@
use crate::*; use crate::*;
mod engine_focus; pub(crate) use engine_focus::*;
mod engine_input; pub(crate) use engine_input::*; mod engine_input; pub(crate) use engine_input::*;
mod engine_style; pub(crate) use engine_style::*; mod engine_style; pub(crate) use engine_style::*;
mod engine_theme; pub(crate) use engine_theme::*; mod engine_theme; pub(crate) use engine_theme::*;

View file

@ -7,7 +7,6 @@ use crate::{
} }
}; };
impl TryFrom<&Arc<RwLock<JackClient>>> for ArrangerTui { impl TryFrom<&Arc<RwLock<JackClient>>> for ArrangerTui {
type Error = Box<dyn std::error::Error>; type Error = Box<dyn std::error::Error>;
fn try_from (jack: &Arc<RwLock<JackClient>>) -> Usually<Self> { fn try_from (jack: &Arc<RwLock<JackClient>>) -> Usually<Self> {
@ -200,6 +199,16 @@ pub enum ArrangerFocus {
PhraseEditor, PhraseEditor,
} }
impl Into<Option<TransportFocus>> for ArrangerFocus {
fn into (self) -> Option<TransportFocus> {
if let Self::Transport(transport) = self {
Some(transport)
} else {
None
}
}
}
impl From<&ArrangerTui> for Option<TransportFocus> { impl From<&ArrangerTui> for Option<TransportFocus> {
fn from (state: &ArrangerTui) -> Self { fn from (state: &ArrangerTui) -> Self {
match state.focus.inner() { match state.focus.inner() {
@ -1075,7 +1084,7 @@ impl Handle<Tui> for ArrangerTui {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum ArrangerCommand { pub enum ArrangerCommand {
Focus(FocusCommand), Focus(FocusCommand<ArrangerFocus>),
Undo, Undo,
Redo, Redo,
Clear, Clear,
@ -1132,7 +1141,7 @@ impl Command<ArrangerTui> for ArrangerClipCommand {
} }
} }
pub trait ArrangerControl: TransportControl { pub trait ArrangerControl: TransportControl<ArrangerFocus> {
fn selected (&self) -> ArrangerSelection; fn selected (&self) -> ArrangerSelection;
fn selected_mut (&mut self) -> &mut ArrangerSelection; fn selected_mut (&mut self) -> &mut ArrangerSelection;
fn activate (&mut self) -> Usually<()>; fn activate (&mut self) -> Usually<()>;
@ -1212,7 +1221,7 @@ fn to_arranger_command (state: &ArrangerTui, input: &TuiInput) -> Option<Arrange
))), ))),
_ => match state.focused() { _ => match state.focused() {
ArrangerFocus::Transport(_) => { ArrangerFocus::Transport(_) => {
match TransportCommand::input_to_command(state, input)? { match to_transport_command(state, input)? {
TransportCommand::Clock(command) => Cmd::Clock(command), TransportCommand::Clock(command) => Cmd::Clock(command),
_ => return None, _ => return None,
} }
@ -1323,3 +1332,12 @@ fn to_arranger_clip_command (input: &TuiInput, t: usize, s: usize) -> Option<Arr
_ => return None _ => return None
}) })
} }
impl TransportControl<ArrangerFocus> for ArrangerTui {
fn transport_focused (&self) -> Option<TransportFocus> {
match self.focus.inner() {
ArrangerFocus::Transport(focus) => Some(focus),
_ => None
}
}
}

View file

@ -1,9 +1,8 @@
use crate::{*, api::ClockCommand::{Play, Pause}}; use crate::{*, api::ClockCommand::{Play, Pause}};
use super::phrase_editor::PhraseCommand::Show; use KeyCode::{Tab, BackTab, Char, Enter};
use super::app_transport::TransportCommand;
use KeyCode::{Char, Enter};
use SequencerCommand::*; use SequencerCommand::*;
use SequencerFocus::*; use SequencerFocus::*;
use PhraseCommand::*;
/// Create app state from JACK handle. /// Create app state from JACK handle.
impl TryFrom<&Arc<RwLock<JackClient>>> for SequencerTui { impl TryFrom<&Arc<RwLock<JackClient>>> for SequencerTui {
@ -41,7 +40,7 @@ impl TryFrom<&Arc<RwLock<JackClient>>> for SequencerTui {
midi_buf: vec![vec![];65536], midi_buf: vec![vec![];65536],
note_buf: vec![], note_buf: vec![],
perf: PerfModel::default(), perf: PerfModel::default(),
focus: FocusState::Entered(SequencerFocus::PhraseEditor) focus: SequencerFocus::PhraseEditor
}) })
} }
@ -60,7 +59,7 @@ pub struct SequencerTui {
pub entered: bool, pub entered: bool,
pub note_buf: Vec<u8>, pub note_buf: Vec<u8>,
pub midi_buf: Vec<Vec<Vec<u8>>>, pub midi_buf: Vec<Vec<Vec<u8>>>,
pub focus: FocusState<SequencerFocus>, pub focus: SequencerFocus,
pub perf: PerfModel, pub perf: PerfModel,
} }
@ -109,32 +108,26 @@ impl Audio for SequencerTui {
render!(|self: SequencerTui|lay!([self.size, Tui::split_up(false, 1, render!(|self: SequencerTui|lay!([self.size, Tui::split_up(false, 1,
Tui::fill_xy(SequencerStatusBar::from(self)), Tui::fill_xy(SequencerStatusBar::from(self)),
Tui::split_down(false, 2, Tui::split_left(false, 20,
TransportView::from(( Tui::fixed_x(20, Tui::split_down(false, 4, col!([
self, PhraseSelector::play_phrase(&self.player, false, true),
self.player.play_phrase().as_ref().map(|(_,p)|p.as_ref().map(|p|p.read().unwrap().color)).flatten(), PhraseSelector::next_phrase(&self.player, false, true),
if let SequencerFocus::Transport(_) = self.focus.inner() { ]), Tui::split_up(false, 2,
true PhraseSelector::edit_phrase(&self.editor.phrase, self.focused() == SequencerFocus::PhraseEditor, true),
} else { PhraseListView::from(self),
false ))),
} col!([
)), Tui::fixed_y(2, TransportView::from((
Tui::split_left(false, 20, self,
Tui::fixed_x(20, Tui::split_down(false, 4, col!([ self.player.play_phrase().as_ref().map(|(_,p)|p.as_ref().map(|p|p.read().unwrap().color)).flatten(),
PhraseSelector::play_phrase( if let SequencerFocus::Transport(_) = self.focus {
&self.player, self.focused() == SequencerFocus::PhrasePlay, self.entered() true
), } else {
PhraseSelector::next_phrase( false
&self.player, self.focused() == SequencerFocus::PhraseNext, self.entered() }
),
]), Tui::split_up(false, 2,
PhraseSelector::edit_phrase(
&self.editor.phrase, self.focused() == SequencerFocus::PhraseEditor, self.entered()
),
PhraseListView::from(self),
))), ))),
PhraseView::from(self) PhraseView::from(self)
) ]),
) )
)])); )]));
@ -161,7 +154,19 @@ impl HasEditor for SequencerTui {
self.focused() == SequencerFocus::PhraseEditor self.focused() == SequencerFocus::PhraseEditor
} }
fn editor_entered (&self) -> bool { fn editor_entered (&self) -> bool {
self.entered() && self.editor_focused() true
}
}
impl HasFocus for SequencerTui {
type Item = SequencerFocus;
/// Get the currently focused item.
fn focused (&self) -> Self::Item {
self.focus
}
/// Get the currently focused item.
fn set_focused (&mut self, to: Self::Item) {
self.focus = to
} }
} }
@ -174,64 +179,27 @@ pub enum SequencerFocus {
PhraseList, PhraseList,
/// The phrase editor (sequencer) is focused /// The phrase editor (sequencer) is focused
PhraseEditor, PhraseEditor,
}
PhrasePlay, impl Into<Option<TransportFocus>> for SequencerFocus {
PhraseNext, fn into (self) -> Option<TransportFocus> {
if let Self::Transport(transport) = self {
Some(transport)
} else {
None
}
}
} }
impl From<&SequencerTui> for Option<TransportFocus> { impl From<&SequencerTui> for Option<TransportFocus> {
fn from (state: &SequencerTui) -> Self { fn from (state: &SequencerTui) -> Self {
match state.focus.inner() { match state.focus {
Transport(focus) => Some(focus), Transport(focus) => Some(focus),
_ => None _ => None
} }
} }
} }
impl_focus!(SequencerTui SequencerFocus [
//&[
//Menu,
//Menu,
//Menu,
//Menu,
//Menu,
//],
&[
Transport(TransportFocus::PlayPause),
Transport(TransportFocus::Bpm),
Transport(TransportFocus::Sync),
Transport(TransportFocus::Quant),
Transport(TransportFocus::Clock),
],
//&[
//PhrasePlay,
//PhrasePlay,
//PhraseEditor,
//PhraseEditor,
//PhraseEditor,
//],
//&[
//PhraseNext,
//PhraseNext,
//PhraseEditor,
//PhraseEditor,
//PhraseEditor,
//],
&[
PhraseList,
PhraseList,
PhraseEditor,
PhraseEditor,
PhraseEditor,
],
] => [self: {
if self.focus.is_entered() && self.focus.inner() == PhraseEditor {
self.editor.edit_mode = PhraseEditMode::Note
} else {
self.editor.edit_mode = PhraseEditMode::Scroll
}
}]);
/// Status bar for sequencer app /// Status bar for sequencer app
#[derive(Clone)] #[derive(Clone)]
pub struct SequencerStatusBar { pub struct SequencerStatusBar {
@ -272,8 +240,8 @@ impl From<&SequencerTui> for SequencerStatusBar {
Transport(Sync) => " LAUNCH SYNC ", Transport(Sync) => " LAUNCH SYNC ",
Transport(Quant) => " REC QUANT ", Transport(Quant) => " REC QUANT ",
Transport(Clock) => " SEEK ", Transport(Clock) => " SEEK ",
PhrasePlay => " TO PLAY ", //PhrasePlay => " TO PLAY ",
PhraseNext => " UP NEXT ", //PhraseNext => " UP NEXT ",
PhraseList => " PHRASES ", PhraseList => " PHRASES ",
PhraseEditor => match state.editor.edit_mode { PhraseEditor => match state.editor.edit_mode {
PhraseEditMode::Note => " EDIT MIDI ", PhraseEditMode::Note => " EDIT MIDI ",
@ -299,16 +267,12 @@ impl From<&SequencerTui> for SequencerStatusBar {
("", ".,", " by beat"), ("", ".,", " by beat"),
("", "<>", " by time"), ("", "<>", " by time"),
], ],
PhraseList => if state.entered() { PhraseList => &[
&[ ("", "", " pick"),
("", "", " pick"), ("", ".,", " move"),
("", ".,", " move"), ("", "", " play"),
("", "", " play"), ("", "e", " edit"),
("", "e", " edit"), ],
]
} else {
default_help
},
PhraseEditor => match state.editor.edit_mode { PhraseEditor => match state.editor.edit_mode {
PhraseEditMode::Note => &[ PhraseEditMode::Note => &[
("", "", " cursor"), ("", "", " cursor"),
@ -316,14 +280,8 @@ impl From<&SequencerTui> for SequencerStatusBar {
PhraseEditMode::Scroll => &[ PhraseEditMode::Scroll => &[
("", "", " scroll"), ("", "", " scroll"),
], ],
} },
_ => if state.entered() { _ => default_help,
&[
("", "Esc", " exit")
]
} else {
default_help
}
} }
} }
} }
@ -331,15 +289,15 @@ impl From<&SequencerTui> for SequencerStatusBar {
render!(|self: SequencerStatusBar|{ render!(|self: SequencerStatusBar|{
lay!(|add|if self.width > 60 { lay!(|add|if self.width > 60 {
add(&row!(![ add(&Tui::fill_x(Tui::fixed_y(1, lay!([
SequencerMode::from(self), Tui::fill_x(Tui::at_w(SequencerMode::from(self))),
SequencerStats::from(self), Tui::fill_x(Tui::at_e(SequencerStats::from(self))),
])) ]))))
} else { } else {
add(&col!(![ add(&Tui::fill_x(col!(![
SequencerMode::from(self), Tui::fill_x(Tui::center_x(SequencerMode::from(self))),
SequencerStats::from(self), Tui::fill_x(Tui::center_x(SequencerStats::from(self))),
])) ])))
}) })
}); });
@ -404,14 +362,11 @@ impl Handle<Tui> for SequencerTui {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum SequencerCommand { pub enum SequencerCommand {
Focus(FocusCommand), Focus(FocusCommand<SequencerFocus>),
Clock(ClockCommand), Clock(ClockCommand),
Phrases(PhrasesCommand), Phrases(PhrasesCommand),
Editor(PhraseCommand), Editor(PhraseCommand),
Enqueue(Option<Arc<RwLock<Phrase>>>), Enqueue(Option<Arc<RwLock<Phrase>>>),
//Clear,
//Undo,
//Redo,
} }
impl Command<SequencerTui> for SequencerCommand { impl Command<SequencerTui> for SequencerCommand {
@ -425,76 +380,74 @@ impl Command<SequencerTui> for SequencerCommand {
state.player.enqueue_next(phrase.as_ref()); state.player.enqueue_next(phrase.as_ref());
None None
}, },
//Self::Undo => { todo!() },
//Self::Redo => { todo!() },
//Self::Clear => { todo!() },
}) })
} }
} }
impl Command<SequencerTui> for FocusCommand<SequencerFocus> {
fn execute (self, state: &mut SequencerTui) -> Perhaps<FocusCommand<SequencerFocus>> {
if let FocusCommand::Set(to) = self {
state.set_focused(to);
}
Ok(None)
}
}
impl InputToCommand<Tui, SequencerTui> for SequencerCommand { impl InputToCommand<Tui, SequencerTui> for SequencerCommand {
fn input_to_command (state: &SequencerTui, input: &TuiInput) -> Option<Self> { fn input_to_command (state: &SequencerTui, input: &TuiInput) -> Option<Self> {
if state.entered() { to_sequencer_command(state, input)
to_sequencer_command(state, input) .or_else(||to_focus_command(input).map(Focus))
.or_else(||to_focus_command(input).map(SequencerCommand::Focus))
} 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)),
key!(Char('c')) => SequencerCommand::Phrases(
PhrasesCommand::input_to_command(&state.phrases, input)?,
),
_ => return None
}
}))
} }
} }
pub fn to_sequencer_command (state: &SequencerTui, input: &TuiInput) -> Option<SequencerCommand> { pub fn to_sequencer_command (state: &SequencerTui, input: &TuiInput) -> Option<SequencerCommand> {
let stopped = state.clock().is_stopped(); use super::app_transport::TransportCommand;
Some(match input.event() { Some(match input.event() {
// Play/pause
key!(Char(' ')) => Clock(if stopped { Play(None) } else { Pause(None) }), // Transport: Play/pause
// Play from start/rewind to start key!(Char(' ')) =>
key!(Shift-Char(' ')) => Clock(if stopped { Play(Some(0)) } else { Pause(Some(0)) }), Clock(if state.clock().is_stopped() { Play(None) } else { Pause(None) }),
// Edit phrase
key!(Char('e')) => match state.focused() { // Transport: Play from start or rewind to start
PhrasePlay => Editor(Show( key!(Shift-Char(' ')) =>
state.player.play_phrase().as_ref().map(|x|x.1.as_ref()).flatten().map(|x|x.clone()) Clock(if state.clock().is_stopped() { Play(Some(0)) } else { Pause(Some(0)) }),
)),
PhraseNext => Editor(Show( // Editor: zoom
state.player.next_phrase().as_ref().map(|x|x.1.as_ref()).flatten().map(|x|x.clone()) key!(Char('z')) | key!(Char('-')) | key!(Char('_'))| key!(Char('=')) | key!(Char('+')) =>
)), Editor(PhraseCommand::input_to_command(&state.editor, input)?),
PhraseList => Editor(Show(
Some(state.phrases.phrases[state.phrases.phrase.load(Ordering::Relaxed)].clone()) // List: select phrase to edit, change color
)), key!(Char('[')) | key!(Char(']')) | key!(Char('c')) =>
_ => return None, Phrases(PhrasesCommand::input_to_command(&state.phrases, input)?),
},
_ => match state.focused() { // Enqueue currently edited phrase
Transport(_) => match TransportCommand::input_to_command(state, input)? { key!(Enter) =>
Enqueue(Some(state.phrases.phrases[state.phrases.phrase.load(Ordering::Relaxed)].clone())),
// Switch between editor and list
key!(Tab) | key!(BackTab) | key!(Shift-Tab) | key!(Shift-BackTab) => match state.focus {
PhraseEditor => SequencerCommand::Focus(FocusCommand::Set(PhraseList)),
_ => SequencerCommand::Focus(FocusCommand::Set(PhraseList)),
}
// Delegate to focused control:
_ => match state.focus {
PhraseEditor => Editor(PhraseCommand::input_to_command(&state.editor, input)?),
PhraseList => Phrases(PhrasesCommand::input_to_command(&state.phrases, input)?),
Transport(_) => match to_transport_command(state, input)? {
TransportCommand::Clock(command) => Clock(command), TransportCommand::Clock(command) => Clock(command),
_ => return None, _ => return None,
}, },
PhraseEditor => Editor(
PhraseCommand::input_to_command(&state.editor, input)?
),
PhraseList => match input.event() {
key!(Enter) => Enqueue(Some(
state.phrases.phrases[state.phrases.phrase.load(Ordering::Relaxed)].clone()
)),
_ => Phrases(
PhrasesCommand::input_to_command(&state.phrases, input)?
),
}
_ => return None
} }
}) })
} }
impl TransportControl<SequencerFocus> for SequencerTui {
fn transport_focused (&self) -> Option<TransportFocus> {
match self.focus {
SequencerFocus::Transport(focus) => Some(focus),
_ => None
}
}
}

View file

@ -207,16 +207,28 @@ impl FocusWrap<TransportFocus> for Option<TransportFocus> {
} }
} }
impl_focus!(TransportTui TransportFocus [ impl HasFocus for TransportTui {
//&[Menu], type Item = TransportFocus;
&[ /// Get the currently focused item.
PlayPause, fn focused (&self) -> Self::Item {
Bpm, self.focus.inner()
Sync, }
Quant, /// Get the currently focused item.
Clock, fn set_focused (&mut self, to: Self::Item) {
], self.focus.set_inner(to)
]); }
}
//impl_focus!(TransportTui TransportFocus [
////&[Menu],
//&[
//PlayPause,
//Bpm,
//Sync,
//Quant,
//Clock,
//],
//]);
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
pub struct TransportStatusBar; pub struct TransportStatusBar;
@ -239,14 +251,24 @@ impl Handle<Tui> for TransportTui {
} }
} }
pub trait TransportControl<T>: HasClock + HasFocus<Item = T> {
fn transport_focused (&self) -> Option<TransportFocus>;
}
impl TransportControl<TransportFocus> for TransportTui {
fn transport_focused (&self) -> Option<TransportFocus> {
Some(self.focus.inner())
}
}
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub enum TransportCommand { pub enum TransportCommand {
Focus(FocusCommand), Focus(FocusCommand<TransportFocus>),
Clock(ClockCommand), Clock(ClockCommand),
} }
impl<T: TransportControl> Command<T> for TransportCommand { impl Command<TransportTui> for TransportCommand {
fn execute (self, state: &mut T) -> Perhaps<Self> { fn execute (self, state: &mut TransportTui) -> Perhaps<Self> {
Ok(match self { Ok(match self {
Self::Focus(cmd) => cmd.execute(state)?.map(Self::Focus), Self::Focus(cmd) => cmd.execute(state)?.map(Self::Focus),
Self::Clock(cmd) => cmd.execute(state)?.map(Self::Clock), Self::Clock(cmd) => cmd.execute(state)?.map(Self::Clock),
@ -254,44 +276,26 @@ impl<T: TransportControl> Command<T> for TransportCommand {
} }
} }
pub trait TransportControl: HasClock + FocusGrid + HasEnter { impl Command<TransportTui> for FocusCommand<TransportFocus> {
fn transport_focused (&self) -> Option<TransportFocus>; fn execute (self, state: &mut TransportTui) -> Perhaps<FocusCommand<TransportFocus>> {
} if let FocusCommand::Set(to) = self {
state.set_focused(to);
impl TransportControl for TransportTui {
fn transport_focused (&self) -> Option<TransportFocus> {
Some(self.focus.inner())
}
}
impl TransportControl for SequencerTui {
fn transport_focused (&self) -> Option<TransportFocus> {
match self.focus.inner() {
SequencerFocus::Transport(focus) => Some(focus),
_ => None
} }
Ok(None)
} }
} }
impl TransportControl for ArrangerTui { impl InputToCommand<Tui, TransportTui> for TransportCommand {
fn transport_focused (&self) -> Option<TransportFocus> { fn input_to_command (state: &TransportTui, input: &TuiInput) -> Option<Self> {
match self.focus.inner() {
ArrangerFocus::Transport(focus) => Some(focus),
_ => None
}
}
}
impl<T: TransportControl> InputToCommand<Tui, T> for TransportCommand {
fn input_to_command (state: &T, input: &TuiInput) -> Option<Self> {
to_transport_command(state, input) to_transport_command(state, input)
.or_else(||to_focus_command(input).map(TransportCommand::Focus)) .or_else(||to_focus_command(input).map(TransportCommand::Focus))
} }
} }
pub fn to_transport_command <T> (state: &T, input: &TuiInput) -> Option<TransportCommand> pub fn to_transport_command <T, U> (state: &T, input: &TuiInput) -> Option<TransportCommand>
where where
T: TransportControl T: TransportControl<U>,
U: Into<Option<TransportFocus>>,
{ {
Some(match input.event() { Some(match input.event() {
key!(Left) => Focus(Prev), key!(Left) => Focus(Prev),

View file

@ -1,66 +0,0 @@
use crate::*;
pub trait FocusWrap<T> {
fn wrap <'a, W: Render<Tui>> (self, focus: T, content: &'a W)
-> impl Render<Tui> + 'a;
}
pub fn to_focus_command (input: &TuiInput) -> Option<FocusCommand> {
use KeyCode::{Tab, BackTab, Up, Down, Left, Right, Enter, Esc};
Some(match input.event() {
key!(Tab) => FocusCommand::Next,
key!(Shift-Tab) => FocusCommand::Prev,
key!(BackTab) => FocusCommand::Prev,
key!(Shift-BackTab) => FocusCommand::Prev,
key!(Up) => FocusCommand::Up,
key!(Down) => FocusCommand::Down,
key!(Left) => FocusCommand::Left,
key!(Right) => FocusCommand::Right,
key!(Enter) => FocusCommand::Enter,
key!(Esc) => FocusCommand::Exit,
_ => return None
})
}
#[macro_export] macro_rules! impl_focus {
($Struct:ident $Focus:ident $Grid:expr $(=> [$self:ident : $update_focus:expr])?) => {
impl HasFocus for $Struct {
type Item = $Focus;
/// Get the currently focused item.
fn focused (&self) -> Self::Item {
self.focus.inner()
}
/// Get the currently focused item.
fn set_focused (&mut self, to: Self::Item) {
self.focus.set_inner(to)
}
$(fn focus_updated (&mut $self) { $update_focus })?
}
impl HasEnter for $Struct {
/// Get the currently focused item.
fn entered (&self) -> bool {
self.focus.is_entered()
}
/// Get the currently focused item.
fn set_entered (&mut self, entered: bool) {
if entered {
self.focus.to_entered()
} else {
self.focus.to_focused()
}
}
}
impl FocusGrid for $Struct {
fn focus_cursor (&self) -> (usize, usize) {
self.cursor
}
fn focus_cursor_mut (&mut self) -> &mut (usize, usize) {
&mut self.cursor
}
fn focus_layout (&self) -> &[&[$Focus]] {
use $Focus::*;
&$Grid
}
}
}
}

View file

@ -62,7 +62,7 @@ impl Default for PhraseEditorModel {
time_point: 0.into(), time_point: 0.into(),
view_mode: Box::new(PianoHorizontal { view_mode: Box::new(PianoHorizontal {
buffer: Default::default(), buffer: Default::default(),
time_zoom: 24, time_zoom: Some(24),
note_zoom: PhraseViewNoteZoom::N(1) note_zoom: PhraseViewNoteZoom::N(1)
}), }),
} }
@ -190,7 +190,7 @@ render!(|self: PhraseViewStats<'a>|{
"Time: {}/{} {} {upper_right}", "Time: {}/{} {} {upper_right}",
self.0.time_point, self.0.time_point,
phrase.read().unwrap().length, phrase.read().unwrap().length,
pulses_to_name(self.0.view_mode.time_zoom()), pulses_to_name(self.0.view_mode.time_zoom().unwrap()),
) )
}; };
Tui::pull_x(1, Tui::bg(title_color, Tui::fg(TuiTheme::g(224), upper_right))) Tui::pull_x(1, Tui::bg(title_color, Tui::fg(TuiTheme::g(224), upper_right)))
@ -218,7 +218,6 @@ render!(|self: PhraseViewCursor<'a>|Tui::fill_xy(render(|to: &mut TuiOutput|Ok(
to, to,
self.0.time_point, self.0.time_point,
self.0.time_start, self.0.time_start,
self.0.view_mode.time_zoom(),
self.0.note_point, self.0.note_point,
self.0.note_len, self.0.note_len,
self.0.note_range.1, self.0.note_range.1,
@ -234,8 +233,8 @@ pub enum PhraseEditMode {
pub trait PhraseViewMode { pub trait PhraseViewMode {
fn show (&mut self, phrase: Option<&Phrase>, note_len: usize); fn show (&mut self, phrase: Option<&Phrase>, note_len: usize);
fn time_zoom (&self) -> usize; fn time_zoom (&self) -> Option<usize>;
fn set_time_zoom (&mut self, time_zoom: usize); fn set_time_zoom (&mut self, time_zoom: Option<usize>);
fn buffer_width (&self, phrase: &Phrase) -> usize; fn buffer_width (&self, phrase: &Phrase) -> usize;
fn buffer_height (&self, phrase: &Phrase) -> usize; fn buffer_height (&self, phrase: &Phrase) -> usize;
fn render_keys (&self, fn render_keys (&self,
@ -247,7 +246,6 @@ pub trait PhraseViewMode {
to: &mut TuiOutput, to: &mut TuiOutput,
time_point: usize, time_point: usize,
time_start: usize, time_start: usize,
time_zoom: usize,
note_point: usize, note_point: usize,
note_len: usize, note_len: usize,
note_hi: usize, note_hi: usize,
@ -256,7 +254,7 @@ pub trait PhraseViewMode {
} }
pub struct PianoHorizontal { pub struct PianoHorizontal {
time_zoom: usize, time_zoom: Option<usize>,
note_zoom: PhraseViewNoteZoom, note_zoom: PhraseViewNoteZoom,
buffer: BigBuffer, buffer: BigBuffer,
} }
@ -269,22 +267,23 @@ pub enum PhraseViewNoteZoom {
} }
impl PhraseViewMode for PianoHorizontal { impl PhraseViewMode for PianoHorizontal {
fn time_zoom (&self) -> usize { fn time_zoom (&self) -> Option<usize> {
self.time_zoom self.time_zoom
} }
fn set_time_zoom (&mut self, time_zoom: usize) { fn set_time_zoom (&mut self, time_zoom: Option<usize>) {
self.time_zoom = time_zoom self.time_zoom = time_zoom
} }
fn show (&mut self, phrase: Option<&Phrase>, note_len: usize) { fn show (&mut self, phrase: Option<&Phrase>, note_len: usize) {
if let Some(phrase) = phrase { if let Some(phrase) = phrase {
self.buffer = BigBuffer::new(self.buffer_width(phrase), self.buffer_height(phrase)); self.buffer = BigBuffer::new(self.buffer_width(phrase), self.buffer_height(phrase));
draw_piano_horizontal(&mut self.buffer, phrase, self.time_zoom, note_len); draw_piano_horizontal_bg(&mut self.buffer, phrase, self.time_zoom.unwrap(), note_len);
draw_piano_horizontal_fg(&mut self.buffer, phrase, self.time_zoom.unwrap());
} else { } else {
self.buffer = Default::default(); self.buffer = Default::default();
} }
} }
fn buffer_width (&self, phrase: &Phrase) -> usize { fn buffer_width (&self, phrase: &Phrase) -> usize {
phrase.length / self.time_zoom phrase.length / self.time_zoom.unwrap()
} }
/// Determine the required height to render the phrase. /// Determine the required height to render the phrase.
fn buffer_height (&self, phrase: &Phrase) -> usize { fn buffer_height (&self, phrase: &Phrase) -> usize {
@ -369,12 +368,12 @@ impl PhraseViewMode for PianoHorizontal {
to: &mut TuiOutput, to: &mut TuiOutput,
time_point: usize, time_point: usize,
time_start: usize, time_start: usize,
time_zoom: usize,
note_point: usize, note_point: usize,
note_len: usize, note_len: usize,
note_hi: usize, note_hi: usize,
note_lo: usize, note_lo: usize,
) { ) {
let time_zoom = self.time_zoom.unwrap();
let [x0, y0, w, _] = to.area().xywh(); let [x0, y0, w, _] = to.area().xywh();
let style = Some(Style::default().fg(Color::Rgb(0,255,0))); let style = Some(Style::default().fg(Color::Rgb(0,255,0)));
for (y, note) in (note_lo..=note_hi).rev().enumerate() { for (y, note) in (note_lo..=note_hi).rev().enumerate() {
@ -386,7 +385,7 @@ impl PhraseViewMode for PianoHorizontal {
to.blit(&"", x0 + x as u16, y0 + y as u16, style); to.blit(&"", x0 + x as u16, y0 + y as u16, style);
let tail = note_len as u16 / time_zoom as u16; let tail = note_len as u16 / time_zoom as u16;
for x_tail in (x0 + x + 1)..(x0 + x + tail) { for x_tail in (x0 + x + 1)..(x0 + x + tail) {
to.blit(&"", x_tail, y0 + y as u16, style); to.blit(&"", x_tail, y0 + y as u16, style);
} }
break break
} }
@ -417,7 +416,6 @@ fn draw_piano_horizontal_bg (
for (y, note) in (0..127).rev().enumerate() { for (y, note) in (0..127).rev().enumerate() {
for (x, time) in (0..target.width).map(|x|(x, x*time_zoom)) { for (x, time) in (0..target.width).map(|x|(x, x*time_zoom)) {
let cell = target.get_mut(x, y).unwrap(); let cell = target.get_mut(x, y).unwrap();
//cell.set_fg(Color::Rgb(48, 55, 45));
cell.set_bg(phrase.color.darkest.rgb); cell.set_bg(phrase.color.darkest.rgb);
cell.set_fg(phrase.color.darker.rgb); cell.set_fg(phrase.color.darker.rgb);
cell.set_char(if time % 384 == 0 { cell.set_char(if time % 384 == 0 {
@ -447,7 +445,7 @@ fn draw_piano_horizontal_fg (
for (y, note) in (0..127).rev().enumerate() { for (y, note) in (0..127).rev().enumerate() {
let cell = target.get_mut(x, note).unwrap(); let cell = target.get_mut(x, note).unwrap();
if notes_on[note] { if notes_on[note] {
cell.set_char(''); cell.set_char('');
cell.set_style(style); cell.set_style(style);
} }
} }
@ -484,7 +482,7 @@ pub enum PhraseCommand {
SetNoteScroll(usize), SetNoteScroll(usize),
SetTimeCursor(usize), SetTimeCursor(usize),
SetTimeScroll(usize), SetTimeScroll(usize),
SetTimeZoom(usize), SetTimeZoom(Option<usize>),
Show(Option<Arc<RwLock<Phrase>>>), Show(Option<Arc<RwLock<Phrase>>>),
SetEditMode(PhraseEditMode), SetEditMode(PhraseEditMode),
ToggleDirection, ToggleDirection,
@ -504,10 +502,11 @@ impl InputToCommand<Tui, PhraseEditorModel> for PhraseCommand {
Some(match from.event() { Some(match from.event() {
key!(Char('`')) => ToggleDirection, key!(Char('`')) => ToggleDirection,
key!(Esc) => SetEditMode(PhraseEditMode::Scroll), key!(Esc) => SetEditMode(PhraseEditMode::Scroll),
key!(Char('-')) => SetTimeZoom(next_note_length(time_zoom)), key!(Char('z')) => SetTimeZoom(None),
key!(Char('_')) => SetTimeZoom(next_note_length(time_zoom)), key!(Char('-')) => SetTimeZoom(time_zoom.map(next_note_length)),
key!(Char('=')) => SetTimeZoom(prev_note_length(time_zoom)), key!(Char('_')) => SetTimeZoom(time_zoom.map(next_note_length)),
key!(Char('+')) => SetTimeZoom(prev_note_length(time_zoom)), key!(Char('=')) => SetTimeZoom(time_zoom.map(prev_note_length)),
key!(Char('+')) => SetTimeZoom(time_zoom.map(prev_note_length)),
key!(Char('a')) => AppendNote, key!(Char('a')) => AppendNote,
key!(Char('s')) => PutNote, key!(Char('s')) => PutNote,
// TODO: no triplet/dotted // TODO: no triplet/dotted
@ -523,8 +522,8 @@ impl InputToCommand<Tui, PhraseEditorModel> for PhraseCommand {
key!(Down) => SetNoteScroll(note_lo.saturating_sub(1)), key!(Down) => SetNoteScroll(note_lo.saturating_sub(1)),
key!(PageUp) => SetNoteScroll(note_lo + 3), key!(PageUp) => SetNoteScroll(note_lo + 3),
key!(PageDown) => SetNoteScroll(note_lo.saturating_sub(3)), key!(PageDown) => SetNoteScroll(note_lo.saturating_sub(3)),
key!(Left) => SetTimeScroll(time_start.saturating_sub(time_zoom)), key!(Left) => SetTimeScroll(time_start.saturating_sub(time_zoom.unwrap())),
key!(Right) => SetTimeScroll(time_start + time_zoom), key!(Right) => SetTimeScroll(time_start + time_zoom.unwrap()),
_ => return None _ => return None
}, },
PhraseEditMode::Note => match from.event() { PhraseEditMode::Note => match from.event() {
@ -535,8 +534,8 @@ impl InputToCommand<Tui, PhraseEditorModel> for PhraseCommand {
key!(PageDown) => SetNoteCursor(note_point.saturating_sub(3)), key!(PageDown) => SetNoteCursor(note_point.saturating_sub(3)),
key!(Left) => SetTimeCursor(time_point.saturating_sub(note_len)), key!(Left) => SetTimeCursor(time_point.saturating_sub(note_len)),
key!(Right) => SetTimeCursor((time_point + note_len) % length), key!(Right) => SetTimeCursor((time_point + note_len) % length),
key!(Shift-Left) => SetTimeCursor(time_point.saturating_sub(time_zoom)), key!(Shift-Left) => SetTimeCursor(time_point.saturating_sub(time_zoom.unwrap())),
key!(Shift-Right) => SetTimeCursor((time_point + time_zoom) % length), key!(Shift-Right) => SetTimeCursor((time_point + time_zoom.unwrap()) % length),
_ => return None _ => return None
}, },
} }

View file

@ -72,7 +72,7 @@ impl HasPhraseList for SequencerTui {
self.focused() == SequencerFocus::PhraseList self.focused() == SequencerFocus::PhraseList
} }
fn phrases_entered (&self) -> bool { fn phrases_entered (&self) -> bool {
self.entered() && self.phrases_focused() true && self.phrases_focused()
} }
fn phrases_mode (&self) -> &Option<PhrasesMode> { fn phrases_mode (&self) -> &Option<PhrasesMode> {
&self.phrases.mode &self.phrases.mode