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)]
|
#[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');
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
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::*;
|
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::*;
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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),
|
||||||
|
|
|
||||||
|
|
@ -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(),
|
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
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue