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)]
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');
}
}

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::*;
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::*;

View file

@ -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
}
}
}

View file

@ -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
}
}
}

View file

@ -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),

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(),
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
},
}

View file

@ -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