mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 11:46:41 +01:00
disable advanced focus in tek_sequencer
This commit is contained in:
parent
32e547194a
commit
be924d447e
9 changed files with 311 additions and 341 deletions
|
|
@ -34,7 +34,7 @@ impl<T: Copy + Debug + PartialEq> FocusState<T> {
|
|||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Debug)]
|
||||
pub enum FocusCommand {
|
||||
pub enum FocusCommand<T: Send + Sync> {
|
||||
Up,
|
||||
Down,
|
||||
Left,
|
||||
|
|
@ -43,10 +43,11 @@ pub enum FocusCommand {
|
|||
Prev,
|
||||
Enter,
|
||||
Exit,
|
||||
Set(T)
|
||||
}
|
||||
|
||||
impl<F: HasFocus + HasEnter + FocusGrid + FocusOrder> Command<F> for FocusCommand {
|
||||
fn execute (self, state: &mut F) -> Perhaps<FocusCommand> {
|
||||
impl<F: HasFocus + HasEnter + FocusGrid + FocusOrder> Command<F> for FocusCommand<F::Item> {
|
||||
fn execute (self, state: &mut F) -> Perhaps<FocusCommand<F::Item>> {
|
||||
use FocusCommand::*;
|
||||
match self {
|
||||
Next => { state.focus_next(); },
|
||||
|
|
@ -57,6 +58,7 @@ impl<F: HasFocus + HasEnter + FocusGrid + FocusOrder> Command<F> for FocusComman
|
|||
Right => { state.focus_right(); },
|
||||
Enter => { state.focus_enter(); },
|
||||
Exit => { state.focus_exit(); },
|
||||
Set(to) => { state.set_focused(to); },
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
|
@ -64,7 +66,7 @@ impl<F: HasFocus + HasEnter + FocusGrid + FocusOrder> Command<F> for FocusComman
|
|||
|
||||
/// Trait for things that have focusable subparts.
|
||||
pub trait HasFocus {
|
||||
type Item: Copy + PartialEq + Debug;
|
||||
type Item: Copy + PartialEq + Debug + Send + Sync;
|
||||
/// Get the currently focused item.
|
||||
fn focused (&self) -> Self::Item;
|
||||
/// Get the currently focused item.
|
||||
|
|
@ -249,55 +251,67 @@ impl<T: FocusGrid + HasEnter> FocusOrder for T {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
pub trait FocusWrap<T> {
|
||||
fn wrap <'a, W: Render<Tui>> (self, focus: T, content: &'a W)
|
||||
-> impl Render<Tui> + 'a;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_focus () {
|
||||
pub fn to_focus_command <T: Send + Sync> (input: &TuiInput) -> Option<FocusCommand<T>> {
|
||||
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 {
|
||||
focused: char,
|
||||
cursor: (usize, usize)
|
||||
}
|
||||
|
||||
impl HasFocus for FocusTest {
|
||||
type Item = char;
|
||||
#[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.focused
|
||||
self.focus.inner()
|
||||
}
|
||||
/// Get the currently focused 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 FocusTest {
|
||||
}
|
||||
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) -> &[&[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'],
|
||||
]
|
||||
fn focus_layout (&self) -> &[&[$Focus]] {
|
||||
use $Focus::*;
|
||||
&$Grid
|
||||
}
|
||||
}
|
||||
|
||||
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');
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
49
crates/tek/src/core/test.rs
Normal file
49
crates/tek/src/core/test.rs
Normal 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');
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
use crate::*;
|
||||
|
||||
mod engine_focus; pub(crate) use engine_focus::*;
|
||||
mod engine_input; pub(crate) use engine_input::*;
|
||||
mod engine_style; pub(crate) use engine_style::*;
|
||||
mod engine_theme; pub(crate) use engine_theme::*;
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ use crate::{
|
|||
}
|
||||
};
|
||||
|
||||
|
||||
impl TryFrom<&Arc<RwLock<JackClient>>> for ArrangerTui {
|
||||
type Error = Box<dyn std::error::Error>;
|
||||
fn try_from (jack: &Arc<RwLock<JackClient>>) -> Usually<Self> {
|
||||
|
|
@ -200,6 +199,16 @@ pub enum ArrangerFocus {
|
|||
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> {
|
||||
fn from (state: &ArrangerTui) -> Self {
|
||||
match state.focus.inner() {
|
||||
|
|
@ -1075,7 +1084,7 @@ impl Handle<Tui> for ArrangerTui {
|
|||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum ArrangerCommand {
|
||||
Focus(FocusCommand),
|
||||
Focus(FocusCommand<ArrangerFocus>),
|
||||
Undo,
|
||||
Redo,
|
||||
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_mut (&mut self) -> &mut ArrangerSelection;
|
||||
fn activate (&mut self) -> Usually<()>;
|
||||
|
|
@ -1212,7 +1221,7 @@ fn to_arranger_command (state: &ArrangerTui, input: &TuiInput) -> Option<Arrange
|
|||
))),
|
||||
_ => match state.focused() {
|
||||
ArrangerFocus::Transport(_) => {
|
||||
match TransportCommand::input_to_command(state, input)? {
|
||||
match to_transport_command(state, input)? {
|
||||
TransportCommand::Clock(command) => Cmd::Clock(command),
|
||||
_ => return None,
|
||||
}
|
||||
|
|
@ -1323,3 +1332,12 @@ fn to_arranger_clip_command (input: &TuiInput, t: usize, s: usize) -> Option<Arr
|
|||
_ => return None
|
||||
})
|
||||
}
|
||||
|
||||
impl TransportControl<ArrangerFocus> for ArrangerTui {
|
||||
fn transport_focused (&self) -> Option<TransportFocus> {
|
||||
match self.focus.inner() {
|
||||
ArrangerFocus::Transport(focus) => Some(focus),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
use crate::{*, api::ClockCommand::{Play, Pause}};
|
||||
use super::phrase_editor::PhraseCommand::Show;
|
||||
use super::app_transport::TransportCommand;
|
||||
use KeyCode::{Char, Enter};
|
||||
use KeyCode::{Tab, BackTab, Char, Enter};
|
||||
use SequencerCommand::*;
|
||||
use SequencerFocus::*;
|
||||
use PhraseCommand::*;
|
||||
|
||||
/// Create app state from JACK handle.
|
||||
impl TryFrom<&Arc<RwLock<JackClient>>> for SequencerTui {
|
||||
|
|
@ -41,7 +40,7 @@ impl TryFrom<&Arc<RwLock<JackClient>>> for SequencerTui {
|
|||
midi_buf: vec![vec![];65536],
|
||||
note_buf: vec![],
|
||||
perf: PerfModel::default(),
|
||||
focus: FocusState::Entered(SequencerFocus::PhraseEditor)
|
||||
focus: SequencerFocus::PhraseEditor
|
||||
})
|
||||
|
||||
}
|
||||
|
|
@ -60,7 +59,7 @@ pub struct SequencerTui {
|
|||
pub entered: bool,
|
||||
pub note_buf: Vec<u8>,
|
||||
pub midi_buf: Vec<Vec<Vec<u8>>>,
|
||||
pub focus: FocusState<SequencerFocus>,
|
||||
pub focus: SequencerFocus,
|
||||
pub perf: PerfModel,
|
||||
}
|
||||
|
||||
|
|
@ -109,32 +108,26 @@ impl Audio for SequencerTui {
|
|||
|
||||
render!(|self: SequencerTui|lay!([self.size, Tui::split_up(false, 1,
|
||||
Tui::fill_xy(SequencerStatusBar::from(self)),
|
||||
Tui::split_down(false, 2,
|
||||
TransportView::from((
|
||||
Tui::split_left(false, 20,
|
||||
Tui::fixed_x(20, Tui::split_down(false, 4, col!([
|
||||
PhraseSelector::play_phrase(&self.player, false, true),
|
||||
PhraseSelector::next_phrase(&self.player, false, true),
|
||||
]), Tui::split_up(false, 2,
|
||||
PhraseSelector::edit_phrase(&self.editor.phrase, self.focused() == SequencerFocus::PhraseEditor, true),
|
||||
PhraseListView::from(self),
|
||||
))),
|
||||
col!([
|
||||
Tui::fixed_y(2, TransportView::from((
|
||||
self,
|
||||
self.player.play_phrase().as_ref().map(|(_,p)|p.as_ref().map(|p|p.read().unwrap().color)).flatten(),
|
||||
if let SequencerFocus::Transport(_) = self.focus.inner() {
|
||||
if let SequencerFocus::Transport(_) = self.focus {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
)),
|
||||
Tui::split_left(false, 20,
|
||||
Tui::fixed_x(20, Tui::split_down(false, 4, col!([
|
||||
PhraseSelector::play_phrase(
|
||||
&self.player, self.focused() == SequencerFocus::PhrasePlay, self.entered()
|
||||
),
|
||||
PhraseSelector::next_phrase(
|
||||
&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)
|
||||
)
|
||||
]),
|
||||
)
|
||||
)]));
|
||||
|
||||
|
|
@ -161,7 +154,19 @@ impl HasEditor for SequencerTui {
|
|||
self.focused() == SequencerFocus::PhraseEditor
|
||||
}
|
||||
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,
|
||||
/// The phrase editor (sequencer) is focused
|
||||
PhraseEditor,
|
||||
}
|
||||
|
||||
PhrasePlay,
|
||||
PhraseNext,
|
||||
impl Into<Option<TransportFocus>> for SequencerFocus {
|
||||
fn into (self) -> Option<TransportFocus> {
|
||||
if let Self::Transport(transport) = self {
|
||||
Some(transport)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&SequencerTui> for Option<TransportFocus> {
|
||||
fn from (state: &SequencerTui) -> Self {
|
||||
match state.focus.inner() {
|
||||
match state.focus {
|
||||
Transport(focus) => Some(focus),
|
||||
_ => 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
|
||||
#[derive(Clone)]
|
||||
pub struct SequencerStatusBar {
|
||||
|
|
@ -272,8 +240,8 @@ impl From<&SequencerTui> for SequencerStatusBar {
|
|||
Transport(Sync) => " LAUNCH SYNC ",
|
||||
Transport(Quant) => " REC QUANT ",
|
||||
Transport(Clock) => " SEEK ",
|
||||
PhrasePlay => " TO PLAY ",
|
||||
PhraseNext => " UP NEXT ",
|
||||
//PhrasePlay => " TO PLAY ",
|
||||
//PhraseNext => " UP NEXT ",
|
||||
PhraseList => " PHRASES ",
|
||||
PhraseEditor => match state.editor.edit_mode {
|
||||
PhraseEditMode::Note => " EDIT MIDI ",
|
||||
|
|
@ -299,16 +267,12 @@ impl From<&SequencerTui> for SequencerStatusBar {
|
|||
("", ".,", " by beat"),
|
||||
("", "<>", " by time"),
|
||||
],
|
||||
PhraseList => if state.entered() {
|
||||
&[
|
||||
PhraseList => &[
|
||||
("", "↕", " pick"),
|
||||
("", ".,", " move"),
|
||||
("", "⏎", " play"),
|
||||
("", "e", " edit"),
|
||||
]
|
||||
} else {
|
||||
default_help
|
||||
},
|
||||
],
|
||||
PhraseEditor => match state.editor.edit_mode {
|
||||
PhraseEditMode::Note => &[
|
||||
("", "✣", " cursor"),
|
||||
|
|
@ -316,14 +280,8 @@ impl From<&SequencerTui> for SequencerStatusBar {
|
|||
PhraseEditMode::Scroll => &[
|
||||
("", "✣", " scroll"),
|
||||
],
|
||||
}
|
||||
_ => if state.entered() {
|
||||
&[
|
||||
("", "Esc", " exit")
|
||||
]
|
||||
} else {
|
||||
default_help
|
||||
}
|
||||
},
|
||||
_ => default_help,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -331,15 +289,15 @@ impl From<&SequencerTui> for SequencerStatusBar {
|
|||
|
||||
render!(|self: SequencerStatusBar|{
|
||||
lay!(|add|if self.width > 60 {
|
||||
add(&row!(![
|
||||
SequencerMode::from(self),
|
||||
SequencerStats::from(self),
|
||||
]))
|
||||
add(&Tui::fill_x(Tui::fixed_y(1, lay!([
|
||||
Tui::fill_x(Tui::at_w(SequencerMode::from(self))),
|
||||
Tui::fill_x(Tui::at_e(SequencerStats::from(self))),
|
||||
]))))
|
||||
} else {
|
||||
add(&col!(![
|
||||
SequencerMode::from(self),
|
||||
SequencerStats::from(self),
|
||||
]))
|
||||
add(&Tui::fill_x(col!(![
|
||||
Tui::fill_x(Tui::center_x(SequencerMode::from(self))),
|
||||
Tui::fill_x(Tui::center_x(SequencerStats::from(self))),
|
||||
])))
|
||||
})
|
||||
});
|
||||
|
||||
|
|
@ -404,14 +362,11 @@ impl Handle<Tui> for SequencerTui {
|
|||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum SequencerCommand {
|
||||
Focus(FocusCommand),
|
||||
Focus(FocusCommand<SequencerFocus>),
|
||||
Clock(ClockCommand),
|
||||
Phrases(PhrasesCommand),
|
||||
Editor(PhraseCommand),
|
||||
Enqueue(Option<Arc<RwLock<Phrase>>>),
|
||||
//Clear,
|
||||
//Undo,
|
||||
//Redo,
|
||||
}
|
||||
|
||||
impl Command<SequencerTui> for SequencerCommand {
|
||||
|
|
@ -425,76 +380,74 @@ impl Command<SequencerTui> for SequencerCommand {
|
|||
state.player.enqueue_next(phrase.as_ref());
|
||||
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 {
|
||||
fn input_to_command (state: &SequencerTui, input: &TuiInput) -> Option<Self> {
|
||||
if state.entered() {
|
||||
to_sequencer_command(state, input)
|
||||
.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
|
||||
}
|
||||
}))
|
||||
.or_else(||to_focus_command(input).map(Focus))
|
||||
}
|
||||
}
|
||||
|
||||
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() {
|
||||
// Play/pause
|
||||
key!(Char(' ')) => Clock(if stopped { Play(None) } else { Pause(None) }),
|
||||
// Play from start/rewind to start
|
||||
key!(Shift-Char(' ')) => Clock(if stopped { Play(Some(0)) } else { Pause(Some(0)) }),
|
||||
// Edit phrase
|
||||
key!(Char('e')) => match state.focused() {
|
||||
PhrasePlay => Editor(Show(
|
||||
state.player.play_phrase().as_ref().map(|x|x.1.as_ref()).flatten().map(|x|x.clone())
|
||||
)),
|
||||
PhraseNext => Editor(Show(
|
||||
state.player.next_phrase().as_ref().map(|x|x.1.as_ref()).flatten().map(|x|x.clone())
|
||||
)),
|
||||
PhraseList => Editor(Show(
|
||||
Some(state.phrases.phrases[state.phrases.phrase.load(Ordering::Relaxed)].clone())
|
||||
)),
|
||||
_ => return None,
|
||||
},
|
||||
_ => match state.focused() {
|
||||
Transport(_) => match TransportCommand::input_to_command(state, input)? {
|
||||
|
||||
// Transport: Play/pause
|
||||
key!(Char(' ')) =>
|
||||
Clock(if state.clock().is_stopped() { Play(None) } else { Pause(None) }),
|
||||
|
||||
// Transport: Play from start or rewind to start
|
||||
key!(Shift-Char(' ')) =>
|
||||
Clock(if state.clock().is_stopped() { Play(Some(0)) } else { Pause(Some(0)) }),
|
||||
|
||||
// Editor: zoom
|
||||
key!(Char('z')) | key!(Char('-')) | key!(Char('_'))| key!(Char('=')) | key!(Char('+')) =>
|
||||
Editor(PhraseCommand::input_to_command(&state.editor, input)?),
|
||||
|
||||
// List: select phrase to edit, change color
|
||||
key!(Char('[')) | key!(Char(']')) | key!(Char('c')) =>
|
||||
Phrases(PhrasesCommand::input_to_command(&state.phrases, input)?),
|
||||
|
||||
// Enqueue currently edited phrase
|
||||
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),
|
||||
_ => 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -207,16 +207,28 @@ impl FocusWrap<TransportFocus> for Option<TransportFocus> {
|
|||
}
|
||||
}
|
||||
|
||||
impl_focus!(TransportTui TransportFocus [
|
||||
//&[Menu],
|
||||
&[
|
||||
PlayPause,
|
||||
Bpm,
|
||||
Sync,
|
||||
Quant,
|
||||
Clock,
|
||||
],
|
||||
]);
|
||||
impl HasFocus for TransportTui {
|
||||
type Item = TransportFocus;
|
||||
/// 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)
|
||||
}
|
||||
}
|
||||
|
||||
//impl_focus!(TransportTui TransportFocus [
|
||||
////&[Menu],
|
||||
//&[
|
||||
//PlayPause,
|
||||
//Bpm,
|
||||
//Sync,
|
||||
//Quant,
|
||||
//Clock,
|
||||
//],
|
||||
//]);
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
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)]
|
||||
pub enum TransportCommand {
|
||||
Focus(FocusCommand),
|
||||
Focus(FocusCommand<TransportFocus>),
|
||||
Clock(ClockCommand),
|
||||
}
|
||||
|
||||
impl<T: TransportControl> Command<T> for TransportCommand {
|
||||
fn execute (self, state: &mut T) -> Perhaps<Self> {
|
||||
impl Command<TransportTui> for TransportCommand {
|
||||
fn execute (self, state: &mut TransportTui) -> Perhaps<Self> {
|
||||
Ok(match self {
|
||||
Self::Focus(cmd) => cmd.execute(state)?.map(Self::Focus),
|
||||
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 {
|
||||
fn transport_focused (&self) -> Option<TransportFocus>;
|
||||
}
|
||||
|
||||
impl TransportControl for TransportTui {
|
||||
fn transport_focused (&self) -> Option<TransportFocus> {
|
||||
Some(self.focus.inner())
|
||||
impl Command<TransportTui> for FocusCommand<TransportFocus> {
|
||||
fn execute (self, state: &mut TransportTui) -> Perhaps<FocusCommand<TransportFocus>> {
|
||||
if let FocusCommand::Set(to) = self {
|
||||
state.set_focused(to);
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
impl TransportControl for SequencerTui {
|
||||
fn transport_focused (&self) -> Option<TransportFocus> {
|
||||
match self.focus.inner() {
|
||||
SequencerFocus::Transport(focus) => Some(focus),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TransportControl for ArrangerTui {
|
||||
fn transport_focused (&self) -> Option<TransportFocus> {
|
||||
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> {
|
||||
impl InputToCommand<Tui, TransportTui> for TransportCommand {
|
||||
fn input_to_command (state: &TransportTui, input: &TuiInput) -> Option<Self> {
|
||||
to_transport_command(state, input)
|
||||
.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
|
||||
T: TransportControl
|
||||
T: TransportControl<U>,
|
||||
U: Into<Option<TransportFocus>>,
|
||||
{
|
||||
Some(match input.event() {
|
||||
key!(Left) => Focus(Prev),
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -62,7 +62,7 @@ impl Default for PhraseEditorModel {
|
|||
time_point: 0.into(),
|
||||
view_mode: Box::new(PianoHorizontal {
|
||||
buffer: Default::default(),
|
||||
time_zoom: 24,
|
||||
time_zoom: Some(24),
|
||||
note_zoom: PhraseViewNoteZoom::N(1)
|
||||
}),
|
||||
}
|
||||
|
|
@ -190,7 +190,7 @@ render!(|self: PhraseViewStats<'a>|{
|
|||
"Time: {}/{} {} {upper_right}",
|
||||
self.0.time_point,
|
||||
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)))
|
||||
|
|
@ -218,7 +218,6 @@ render!(|self: PhraseViewCursor<'a>|Tui::fill_xy(render(|to: &mut TuiOutput|Ok(
|
|||
to,
|
||||
self.0.time_point,
|
||||
self.0.time_start,
|
||||
self.0.view_mode.time_zoom(),
|
||||
self.0.note_point,
|
||||
self.0.note_len,
|
||||
self.0.note_range.1,
|
||||
|
|
@ -234,8 +233,8 @@ pub enum PhraseEditMode {
|
|||
|
||||
pub trait PhraseViewMode {
|
||||
fn show (&mut self, phrase: Option<&Phrase>, note_len: usize);
|
||||
fn time_zoom (&self) -> usize;
|
||||
fn set_time_zoom (&mut self, time_zoom: usize);
|
||||
fn time_zoom (&self) -> Option<usize>;
|
||||
fn set_time_zoom (&mut self, time_zoom: Option<usize>);
|
||||
fn buffer_width (&self, phrase: &Phrase) -> usize;
|
||||
fn buffer_height (&self, phrase: &Phrase) -> usize;
|
||||
fn render_keys (&self,
|
||||
|
|
@ -247,7 +246,6 @@ pub trait PhraseViewMode {
|
|||
to: &mut TuiOutput,
|
||||
time_point: usize,
|
||||
time_start: usize,
|
||||
time_zoom: usize,
|
||||
note_point: usize,
|
||||
note_len: usize,
|
||||
note_hi: usize,
|
||||
|
|
@ -256,7 +254,7 @@ pub trait PhraseViewMode {
|
|||
}
|
||||
|
||||
pub struct PianoHorizontal {
|
||||
time_zoom: usize,
|
||||
time_zoom: Option<usize>,
|
||||
note_zoom: PhraseViewNoteZoom,
|
||||
buffer: BigBuffer,
|
||||
}
|
||||
|
|
@ -269,22 +267,23 @@ pub enum PhraseViewNoteZoom {
|
|||
}
|
||||
|
||||
impl PhraseViewMode for PianoHorizontal {
|
||||
fn time_zoom (&self) -> usize {
|
||||
fn time_zoom (&self) -> Option<usize> {
|
||||
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
|
||||
}
|
||||
fn show (&mut self, phrase: Option<&Phrase>, note_len: usize) {
|
||||
if let Some(phrase) = 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 {
|
||||
self.buffer = Default::default();
|
||||
}
|
||||
}
|
||||
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.
|
||||
fn buffer_height (&self, phrase: &Phrase) -> usize {
|
||||
|
|
@ -369,12 +368,12 @@ impl PhraseViewMode for PianoHorizontal {
|
|||
to: &mut TuiOutput,
|
||||
time_point: usize,
|
||||
time_start: usize,
|
||||
time_zoom: usize,
|
||||
note_point: usize,
|
||||
note_len: usize,
|
||||
note_hi: usize,
|
||||
note_lo: usize,
|
||||
) {
|
||||
let time_zoom = self.time_zoom.unwrap();
|
||||
let [x0, y0, w, _] = to.area().xywh();
|
||||
let style = Some(Style::default().fg(Color::Rgb(0,255,0)));
|
||||
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);
|
||||
let tail = note_len as u16 / time_zoom as u16;
|
||||
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
|
||||
}
|
||||
|
|
@ -417,7 +416,6 @@ fn draw_piano_horizontal_bg (
|
|||
for (y, note) in (0..127).rev().enumerate() {
|
||||
for (x, time) in (0..target.width).map(|x|(x, x*time_zoom)) {
|
||||
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_fg(phrase.color.darker.rgb);
|
||||
cell.set_char(if time % 384 == 0 {
|
||||
|
|
@ -447,7 +445,7 @@ fn draw_piano_horizontal_fg (
|
|||
for (y, note) in (0..127).rev().enumerate() {
|
||||
let cell = target.get_mut(x, note).unwrap();
|
||||
if notes_on[note] {
|
||||
cell.set_char('▄');
|
||||
cell.set_char('▂');
|
||||
cell.set_style(style);
|
||||
}
|
||||
}
|
||||
|
|
@ -484,7 +482,7 @@ pub enum PhraseCommand {
|
|||
SetNoteScroll(usize),
|
||||
SetTimeCursor(usize),
|
||||
SetTimeScroll(usize),
|
||||
SetTimeZoom(usize),
|
||||
SetTimeZoom(Option<usize>),
|
||||
Show(Option<Arc<RwLock<Phrase>>>),
|
||||
SetEditMode(PhraseEditMode),
|
||||
ToggleDirection,
|
||||
|
|
@ -504,10 +502,11 @@ impl InputToCommand<Tui, PhraseEditorModel> for PhraseCommand {
|
|||
Some(match from.event() {
|
||||
key!(Char('`')) => ToggleDirection,
|
||||
key!(Esc) => SetEditMode(PhraseEditMode::Scroll),
|
||||
key!(Char('-')) => SetTimeZoom(next_note_length(time_zoom)),
|
||||
key!(Char('_')) => SetTimeZoom(next_note_length(time_zoom)),
|
||||
key!(Char('=')) => SetTimeZoom(prev_note_length(time_zoom)),
|
||||
key!(Char('+')) => SetTimeZoom(prev_note_length(time_zoom)),
|
||||
key!(Char('z')) => SetTimeZoom(None),
|
||||
key!(Char('-')) => SetTimeZoom(time_zoom.map(next_note_length)),
|
||||
key!(Char('_')) => SetTimeZoom(time_zoom.map(next_note_length)),
|
||||
key!(Char('=')) => SetTimeZoom(time_zoom.map(prev_note_length)),
|
||||
key!(Char('+')) => SetTimeZoom(time_zoom.map(prev_note_length)),
|
||||
key!(Char('a')) => AppendNote,
|
||||
key!(Char('s')) => PutNote,
|
||||
// TODO: no triplet/dotted
|
||||
|
|
@ -523,8 +522,8 @@ impl InputToCommand<Tui, PhraseEditorModel> for PhraseCommand {
|
|||
key!(Down) => SetNoteScroll(note_lo.saturating_sub(1)),
|
||||
key!(PageUp) => SetNoteScroll(note_lo + 3),
|
||||
key!(PageDown) => SetNoteScroll(note_lo.saturating_sub(3)),
|
||||
key!(Left) => SetTimeScroll(time_start.saturating_sub(time_zoom)),
|
||||
key!(Right) => SetTimeScroll(time_start + time_zoom),
|
||||
key!(Left) => SetTimeScroll(time_start.saturating_sub(time_zoom.unwrap())),
|
||||
key!(Right) => SetTimeScroll(time_start + time_zoom.unwrap()),
|
||||
_ => return None
|
||||
},
|
||||
PhraseEditMode::Note => match from.event() {
|
||||
|
|
@ -535,8 +534,8 @@ impl InputToCommand<Tui, PhraseEditorModel> for PhraseCommand {
|
|||
key!(PageDown) => SetNoteCursor(note_point.saturating_sub(3)),
|
||||
key!(Left) => SetTimeCursor(time_point.saturating_sub(note_len)),
|
||||
key!(Right) => SetTimeCursor((time_point + note_len) % length),
|
||||
key!(Shift-Left) => SetTimeCursor(time_point.saturating_sub(time_zoom)),
|
||||
key!(Shift-Right) => SetTimeCursor((time_point + time_zoom) % length),
|
||||
key!(Shift-Left) => SetTimeCursor(time_point.saturating_sub(time_zoom.unwrap())),
|
||||
key!(Shift-Right) => SetTimeCursor((time_point + time_zoom.unwrap()) % length),
|
||||
_ => return None
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ impl HasPhraseList for SequencerTui {
|
|||
self.focused() == SequencerFocus::PhraseList
|
||||
}
|
||||
fn phrases_entered (&self) -> bool {
|
||||
self.entered() && self.phrases_focused()
|
||||
true && self.phrases_focused()
|
||||
}
|
||||
fn phrases_mode (&self) -> &Option<PhrasesMode> {
|
||||
&self.phrases.mode
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue