wip: p.45, e=33, lotta todo!

This commit is contained in:
🪞👃🪞 2024-11-17 04:03:06 +01:00
parent 260736f31d
commit 627c7d8820
17 changed files with 198 additions and 226 deletions

View file

@ -25,8 +25,8 @@ pub struct ArrangerTui {
pub midi_buf: Vec<Vec<Vec<u8>>>,
pub cursor: (usize, usize),
pub menu_bar: Option<MenuBar<Tui, Self, ArrangerCommand>>,
pub status_bar: Option<S>,
pub history: Vec<C>,
pub status_bar: Option<ArrangerStatus>,
pub history: Vec<ArrangerCommand>,
}
impl TryFrom<&Arc<RwLock<JackClient>>> for ArrangerTui {

View file

@ -20,13 +20,19 @@ pub enum ArrangerCommand {
Clip(ArrangerClipCommand),
Select(ArrangerSelection),
Zoom(usize),
Phrases(PhrasePoolViewCommand),
Phrases(PhrasePoolCommand),
Editor(PhraseEditorCommand),
EditPhrase(Option<Arc<RwLock<Phrase>>>),
}
impl InputToCommand<Tui, ArrangerTui> for ArrangerCommand {
fn input_to_command (view: &ArrangerTui, input: &TuiInput) -> Option<Self> {
pub trait ArrangerControl {
}
impl ArrangerControl for ArrangerTui {
}
impl<T: ArrangerControl> InputToCommand<Tui, T> for ArrangerCommand {
fn input_to_command (view: &T, input: &TuiInput) -> Option<Self> {
use FocusCommand::*;
use ArrangerCommand::*;
Some(match input.event() {
@ -44,7 +50,7 @@ impl InputToCommand<Tui, ArrangerTui> for ArrangerCommand {
Self::App(Playhead(PlayheadCommand::Play(None)))
},
_ => Self::App(match view.focused() {
Content(ArrangerFocus::Transport) => {
ArrangerFocus::Transport => {
use TransportCommand::{Clock, Playhead};
match TransportCommand::input_to_command(view, input)? {
Clock(command) => {
@ -55,18 +61,18 @@ impl InputToCommand<Tui, ArrangerTui> for ArrangerCommand {
},
}
},
Content(ArrangerFocus::PhraseEditor) => Editor(
PhraseEditorCommand::input_to_command(&view.editor, input)?
ArrangerFocus::PhraseEditor => Editor(
PhraseCommand::input_to_command(&view.editor, input)?
),
Content(ArrangerFocus::PhrasePool) => match input.event() {
ArrangerFocus::PhrasePool => match input.event() {
key!(KeyCode::Char('e')) => EditPhrase(
Some(view.phrase().clone())
),
_ => Phrases(
PhrasePoolViewCommand::input_to_command(view, input)?
PhrasePoolCommand::input_to_command(view, input)?
)
},
Content(ArrangerFocus::Arranger) => {
ArrangerFocus::Arranger => {
use ArrangerSelection as Select;
use ArrangerTrackCommand as Track;
use ArrangerClipCommand as Clip;
@ -183,23 +189,11 @@ impl InputToCommand<Tui, ArrangerTui> for ArrangerCommand {
}
}
impl Command<ArrangerTui> for ArrangerCommand {
fn execute (self, state: &mut ArrangerTui) -> Perhaps<Self> {
let undo = match self {
Focus(cmd) => { delegate(cmd, Focus, state) },
App(cmd) => { delegate(cmd, App, state) }
_ => {todo!()}
}?;
state.show_phrase();
state.update_status();
return Ok(undo);
}
}
impl Command<ArrangerTui> for ArrangerCommand {
fn execute (self, state: &mut ArrangerTui) -> Perhaps<Self> {
impl<T: ArrangerControl> Command<T> for ArrangerCommand {
fn execute (self, state: &mut T) -> Perhaps<Self> {
use ArrangerCommand::*;
match self {
Focus(cmd) => { delegate(cmd, Focus, state) },
Scene(cmd) => { delegate(cmd, Scene, &mut state) },
Track(cmd) => { delegate(cmd, Track, &mut state) },
Clip(cmd) => { delegate(cmd, Clip, &mut state) },

View file

@ -3,6 +3,8 @@ use crate::*;
/// Sections in the arranger app that may be focused
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum ArrangerFocus {
/// The menu bar is focused
Menu,
/// The transport (toolbar) is focused
Transport,
/// The arrangement (grid) is focused
@ -13,16 +15,15 @@ pub enum ArrangerFocus {
PhraseEditor,
}
impl FocusEnter for ArrangerApp<Tui> {
type Item = AppViewFocus<ArrangerFocus>;
impl FocusEnter for ArrangerTui {
type Item = ArrangerFocus;
fn focus_enter (&mut self) {
use AppViewFocus::*;
use ArrangerFocus::*;
let focused = self.focused();
if !self.entered {
self.entered = focused == Content(Arranger);
self.app.editor.entered = focused == Content(PhraseEditor);
self.app.phrases.entered = focused == Content(PhrasePool);
self.entered = focused == Arranger;
self.app.editor.entered = focused == PhraseEditor;
self.app.phrases.entered = focused == PhrasePool;
}
}
fn focus_exit (&mut self) {
@ -42,8 +43,8 @@ impl FocusEnter for ArrangerApp<Tui> {
}
/// Focus layout of arranger app
impl FocusGrid for ArrangerApp<Tui> {
type Item = AppViewFocus<ArrangerFocus>;
impl FocusGrid for ArrangerTui {
type Item = ArrangerFocus;
fn focus_cursor (&self) -> (usize, usize) {
self.cursor
}
@ -51,24 +52,22 @@ impl FocusGrid for ArrangerApp<Tui> {
&mut self.cursor
}
fn focus_layout (&self) -> &[&[Self::Item]] {
use AppViewFocus::*;
use ArrangerFocus::*;
&[
&[Menu, Menu ],
&[Content(Transport), Content(Transport) ],
&[Content(Arranger), Content(Arranger) ],
&[Content(PhrasePool), Content(PhraseEditor)],
&[Menu, Menu ],
&[Transport, Transport ],
&[Arranger, Arranger ],
&[PhrasePool, PhraseEditor],
]
}
fn focus_update (&mut self) {
use AppViewFocus::*;
use ArrangerFocus::*;
let focused = self.focused();
if let Some(mut status_bar) = self.status_bar {
status_bar.update(&(
self.focused(),
self.app.selected,
focused == Content(PhraseEditor) && self.entered
focused == PhraseEditor && self.entered
))
}
}

View file

@ -1,6 +1,6 @@
use crate::*;
impl HasScenes<ArrangerScene> for ArrangerView<Tui> {
impl HasScenes<ArrangerScene> for ArrangerTui {
fn scenes (&self) -> &Vec<ArrangerScene> {
&self.scenes
}

View file

@ -2,7 +2,7 @@ use crate::*;
/// Status bar for arranger app
#[derive(Copy, Clone, Debug)]
pub enum ArrangerStatusBar {
pub enum ArrangerStatus {
Transport,
ArrangerMix,
ArrangerTrack,
@ -13,35 +13,30 @@ pub enum ArrangerStatusBar {
PhraseEdit,
}
impl StatusBar for ArrangerStatusBar {
type State = (AppViewFocus<ArrangerFocus>, ArrangerSelection, bool);
impl StatusBar for ArrangerStatus {
type State = (ArrangerFocus, ArrangerSelection, bool);
fn hotkey_fg () -> Color where Self: Sized {
TuiTheme::hotkey_fg()
}
fn update (&mut self, (focused, selected, entered): &Self::State) {
use AppViewFocus::*;
if let Content(focused) = focused {
*self = match focused {
ArrangerFocus::Transport => ArrangerStatusBar::Transport,
ArrangerFocus::Arranger => match selected {
ArrangerSelection::Mix => ArrangerStatusBar::ArrangerMix,
ArrangerSelection::Track(_) => ArrangerStatusBar::ArrangerTrack,
ArrangerSelection::Scene(_) => ArrangerStatusBar::ArrangerScene,
ArrangerSelection::Clip(_, _) => ArrangerStatusBar::ArrangerClip,
},
ArrangerFocus::PhrasePool => ArrangerStatusBar::PhrasePool,
ArrangerFocus::PhraseEditor => match entered {
true => ArrangerStatusBar::PhraseEdit,
false => ArrangerStatusBar::PhraseView,
},
}
*self = match focused {
ArrangerFocus::Transport => ArrangerStatus::Transport,
ArrangerFocus::Arranger => match selected {
ArrangerSelection::Mix => ArrangerStatus::ArrangerMix,
ArrangerSelection::Track(_) => ArrangerStatus::ArrangerTrack,
ArrangerSelection::Scene(_) => ArrangerStatus::ArrangerScene,
ArrangerSelection::Clip(_, _) => ArrangerStatus::ArrangerClip,
},
ArrangerFocus::PhrasePool => ArrangerStatus::PhrasePool,
ArrangerFocus::PhraseEditor => match entered {
true => ArrangerStatus::PhraseEdit,
false => ArrangerStatus::PhraseView,
},
}
}
}
impl Content for ArrangerStatusBar {
impl Content for ArrangerStatus {
type Engine = Tui;
fn content (&self) -> impl Widget<Engine = Tui> {
let label = match self {

View file

@ -1,6 +1,6 @@
use crate::*;
impl HasTracks<ArrangerTrack> for ArrangerView<Tui> {
impl HasTracks<ArrangerTrack> for ArrangerTui {
fn tracks (&self) -> &Vec<ArrangerTrack> {
&self.tracks
}
@ -9,7 +9,7 @@ impl HasTracks<ArrangerTrack> for ArrangerView<Tui> {
}
}
impl ArrangerTracksApi<ArrangerTrack> for ArrangerView<Tui> {
impl ArrangerTracksApi<ArrangerTrack> for ArrangerTui {
fn track_add (&mut self, name: Option<&str>, color: Option<ItemColor>)
-> Usually<&mut ArrangerTrack>
{

View file

@ -24,7 +24,7 @@ impl ArrangerMode {
}
/// Layout for standalone arranger app.
impl Content for ArrangerView<Tui> {
impl Content for ArrangerTui {
type Engine = Tui;
fn content (&self) -> impl Widget<Engine = Tui> {
Split::up(
@ -65,8 +65,8 @@ impl Content for ArrangerView<Tui> {
}
}
impl TransportViewState for ArrangerView<Tui> {
fn focus (&self) -> TransportViewFocus {
impl TransportViewState for ArrangerTui {
fn focus (&self) -> TransportFocus {
self.focus
}
fn focused (&self) -> bool {
@ -102,7 +102,7 @@ fn track_widths (tracks: &[ArrangerTrack]) -> Vec<(usize, usize)> {
}
pub fn arranger_content_vertical (
view: &ArrangerView<Tui>,
view: &ArrangerTui,
factor: usize
) -> impl Widget<Engine = Tui> + use<'_> {
let timebase = view.timebase();
@ -289,7 +289,7 @@ pub fn arranger_content_vertical (
}
pub fn arranger_content_horizontal (
view: &ArrangerView<Tui>,
view: &ArrangerTui,
) -> impl Widget<Engine = Tui> + use<'_> {
let focused = view.focused;
let _tracks = view.tracks();

View file

@ -1,7 +1,7 @@
use crate::*;
/// Contains state for viewing and editing a phrase
pub struct PhraseEditor<E: Engine> {
pub struct PhraseTui<E: Engine> {
_engine: PhantomData<E>,
/// Phrase being played
pub phrase: Option<Arc<RwLock<Phrase>>>,
@ -31,7 +31,7 @@ pub struct PhraseEditor<E: Engine> {
pub size: Measure<E>
}
impl Widget for PhraseEditor<Tui> {
impl Widget for PhraseTui<Tui> {
type Engine = Tui;
fn layout (&self, to: [u16;2]) -> Perhaps<[u16;2]> {
PhraseView(&self, Default::default()).layout(to)
@ -41,9 +41,9 @@ impl Widget for PhraseEditor<Tui> {
}
}
pub struct PhraseView<'a, T: PhraseEditorViewState>(pub &'a T);
pub struct PhraseView<'a, T: PhraseTuiViewState>(pub &'a T);
pub trait PhraseEditorViewState: Send + Sync {
pub trait PhraseTuiViewState: Send + Sync {
fn focused (&self) -> bool;
fn entered (&self) -> bool;
fn keys (&self) -> &Buffer;
@ -56,7 +56,7 @@ pub trait PhraseEditorViewState: Send + Sync {
fn now (&self) -> &Arc<Pulse>;
}
impl PhraseEditorViewState for PhraseEditor<Tui> {
impl PhraseTuiViewState for PhraseTui<Tui> {
fn focused (&self) -> bool {
&self.focused
}
@ -89,7 +89,7 @@ impl PhraseEditorViewState for PhraseEditor<Tui> {
}
}
impl<'a, T: PhraseEditorViewState> Content for PhraseView<'a, T> {
impl<'a, T: PhraseTuiViewState> Content for PhraseView<'a, T> {
type Engine = Tui;
fn content (&self) -> impl Widget<Engine = Tui> {
let phrase = self.0.phrase();
@ -223,7 +223,7 @@ impl<'a, T: PhraseEditorViewState> Content for PhraseView<'a, T> {
}
}
impl<E: Engine> PhraseEditor<E> {
impl<E: Engine> PhraseTui<E> {
pub fn new () -> Self {
Self {
_engine: Default::default(),
@ -410,7 +410,7 @@ const NTH_OCTAVE: [&'static str; 11] = [
];
#[derive(Clone, PartialEq, Debug)]
pub enum PhraseEditorCommand {
pub enum PhraseCommand {
// TODO: 1-9 seek markers that by default start every 8th of the phrase
ToggleDirection,
EnterEditMode,
@ -425,15 +425,15 @@ pub enum PhraseEditorCommand {
TimeZoomSet(usize),
}
impl Handle<Tui> for PhraseEditor<Tui> {
impl Handle<Tui> for PhraseTui<Tui> {
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
PhraseEditorCommand::execute_with_state(self, from)
PhraseCommand::execute_with_state(self, from)
}
}
impl InputToCommand<Tui, PhraseEditor<Tui>> for PhraseEditorCommand {
fn input_to_command (state: &PhraseEditor<Tui>, from: &TuiInput) -> Option<Self> {
use PhraseEditorCommand::*;
impl InputToCommand<Tui, PhraseTui<Tui>> for PhraseCommand {
fn input_to_command (state: &PhraseTui<Tui>, from: &TuiInput) -> Option<Self> {
use PhraseCommand::*;
Some(match from.event() {
key!(KeyCode::Char('`')) => ToggleDirection,
key!(KeyCode::Enter) => EnterEditMode,
@ -469,9 +469,9 @@ impl InputToCommand<Tui, PhraseEditor<Tui>> for PhraseEditorCommand {
}
}
impl<E: Engine> Command<PhraseEditor<E>> for PhraseEditorCommand {
//fn translate (self, state: &PhraseEditor<E>) -> Self {
//use PhraseEditorCommand::*;
impl<E: Engine> Command<PhraseTui<E>> for PhraseCommand {
//fn translate (self, state: &PhraseTui<E>) -> Self {
//use PhraseCommand::*;
//match self {
//GoUp => match state.entered { true => NoteCursorInc, false => NoteScrollInc, },
//GoDown => match state.entered { true => NoteCursorDec, false => NoteScrollDec, },
@ -480,8 +480,8 @@ impl<E: Engine> Command<PhraseEditor<E>> for PhraseEditorCommand {
//_ => self
//}
//}
fn execute (self, state: &mut PhraseEditor<E>) -> Perhaps<Self> {
use PhraseEditorCommand::*;
fn execute (self, state: &mut PhraseTui<E>) -> Perhaps<Self> {
use PhraseCommand::*;
match self.translate(state) {
ToggleDirection => {
state.mode = !state.mode;

View file

@ -8,7 +8,7 @@ pub struct PhrasesTui {
/// Scroll offset
pub scroll: usize,
/// Mode switch
pub mode: Option<PhrasePoolMode>,
pub mode: Option<PhrasesMode>,
/// Whether this widget is focused
pub focused: bool,
/// Whether this widget is entered
@ -16,7 +16,7 @@ pub struct PhrasesTui {
}
/// Modes for phrase pool
pub enum PhrasePoolMode {
pub enum PhrasesMode {
/// Renaming a pattern
Rename(usize, String),
/// Editing the length of a pattern
@ -103,7 +103,7 @@ pub struct PhraseLength {
pub focus: Option<PhraseLengthFocus>,
}
impl<E: Engine> PhraseLength<E> {
impl PhraseLength {
pub fn new (pulses: usize, focus: Option<PhraseLengthFocus>) -> Self {
Self { _engine: Default::default(), ppq: PPQ, bpb: 4, pulses, focus }
}

View file

@ -2,12 +2,12 @@ use crate::*;
impl Handle<Tui> for PhrasePoolView<Tui> {
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
PhrasePoolViewCommand::execute_with_state(self, from)
PhrasesCommand::execute_with_state(self, from)
}
}
#[derive(Clone, PartialEq, Debug)]
pub enum PhrasePoolViewCommand {
pub enum PhrasesCommand {
Select(usize),
Edit(PhrasePoolCommand),
Rename(PhraseRenameCommand),
@ -33,12 +33,12 @@ pub enum PhraseLengthCommand {
Cancel,
}
impl InputToCommand<Tui, PhrasePoolView<Tui>> for PhrasePoolViewCommand {
impl InputToCommand<Tui, PhrasePoolView<Tui>> for PhrasesCommand {
fn input_to_command (state: &PhrasePoolView<Tui>, input: &TuiInput) -> Option<Self> {
use PhrasePoolViewCommand as Cmd;
use PhrasePoolCommand as Edit;
use PhraseRenameCommand as Rename;
use PhraseLengthCommand as Length;
use PhrasesCommand as Cmd;
use PhrasePoolCommand as Edit;
use PhraseRenameCommand as Rename;
use PhraseLengthCommand as Length;
match input.event() {
key!(KeyCode::Up) => Some(Cmd::Select(0)),
key!(KeyCode::Down) => Some(Cmd::Select(0)),
@ -64,7 +64,7 @@ impl InputToCommand<Tui, PhrasePoolView<Tui>> for PhrasePoolViewCommand {
}
}
impl<E: Engine> Command<PhrasePoolView<E>> for PhrasePoolViewCommand {
impl<E: Engine> Command<PhrasePoolView<E>> for PhrasesCommand {
fn execute (self, view: &mut PhrasePoolView<E>) -> Perhaps<Self> {
use PhraseRenameCommand as Rename;
use PhraseLengthCommand as Length;

View file

@ -1,46 +1,78 @@
use crate::*;
impl Widget for TransportTui {
impl Widget for PhrasesTui {
type Engine = Tui;
fn layout (&self, to: [u16;2]) -> Perhaps<[u16;2]> {
TransportView(&self, Default::default()).layout(to)
PhrasesView(&self).layout(to)
}
fn render (&self, to: &mut TuiOutput) -> Usually<()> {
TransportView(&self, Default::default()).render(to)
PhrasesView(&self).render(to)
}
}
pub trait PhrasesViewState {
fn focused (&self) -> bool;
fn entered (&self) -> bool;
fn phrases (&self) -> Vec<()>;
fn phrase (&self) -> ();
fn mode (&self) -> PhrasesMode;
}
impl PhrasesViewState for PhrasesTui {
fn focused (&self) -> bool {
todo!()
}
fn entered (&self) -> bool {
todo!()
}
fn phrases (&self) -> Vec<()> {
todo!()
}
fn phrase (&self) -> () {
todo!()
}
fn mode (&self) -> PhrasesMode {
todo!()
}
}
pub struct PhrasesView<'a, T: PhrasesViewState>(&'a T);
// TODO: Display phrases always in order of appearance
impl Content for PhrasePoolView<Tui> {
impl<'a, T: PhrasesViewState> Content for PhrasesView<'a, T> {
type Engine = Tui;
fn content (&self) -> impl Widget<Engine = Tui> {
let Self { focused, phrases, mode, .. } = self;
let focused = self.focused();
let entered = self.entered();
let phrases = self.phrases();
let phrase = self.phrase();
let mode = self.mode();
let content = col!(
(i, phrase) in phrases.iter().enumerate() => Layers::new(|add|{
let Phrase { ref name, color, length, .. } = *phrase.read().unwrap();
let mut length = PhraseLength::new(length, None);
if let Some(PhrasePoolMode::Length(phrase, new_length, focus)) = mode {
if *focused && i == *phrase {
length.pulses = *new_length;
length.focus = Some(*focus);
if let Some(PhrasesMode::Length(phrase, new_length, focus)) = mode {
if *focused && i == phrase {
length.pulses = new_length;
length.focus = Some(focus);
}
}
let length = length.align_e().fill_x();
let row1 = lay!(format!(" {i}").align_w().fill_x(), length).fill_x();
let mut row2 = format!(" {name}");
if let Some(PhrasePoolMode::Rename(phrase, _)) = mode {
if *focused && i == *phrase { row2 = format!("{row2}"); }
if let Some(PhrasesMode::Rename(phrase, _)) = mode {
if *focused && i == phrase { row2 = format!("{row2}"); }
};
let row2 = TuiStyle::bold(row2, true);
add(&col!(row1, row2).fill_x().bg(color.base.rgb))?;
Ok(if *focused && i == self.phrase { add(&CORNERS)?; })
Ok(if *focused && i == phrase { add(&CORNERS)?; })
})
);
let border_color = if *focused {Color::Rgb(100, 110, 40)} else {Color::Rgb(70, 80, 50)};
let border = Lozenge(Style::default().bg(Color::Rgb(40, 50, 30)).fg(border_color));
let content = content.fill_xy().bg(Color::Rgb(28, 35, 25)).border(border);
let title_color = if *focused {Color::Rgb(150, 160, 90)} else {Color::Rgb(120, 130, 100)};
let upper_left = format!("[{}] Phrases", if self.entered {""} else {" "});
let upper_left = format!("[{}] Phrases", if entered {""} else {" "});
let upper_right = format!("({})", phrases.len());
lay!(
content,

View file

@ -3,14 +3,13 @@ use crate::*;
impl TryFrom<&Arc<RwLock<JackClient>>> for SequencerTui {
type Error = Box<dyn std::error::Error>;
fn try_from (jack: &Arc<RwLock<JackClient>>) -> Usually<Self> {
let clock = Arc::new(Clock::from(Instant::default()));
Ok(Self::new(SequencerView {
Ok(Self::new(SequencerTui {
phrases: vec![],
metronome: false,
transport: jack.read().unwrap().transport(),
jack: jack.clone(),
focused: false,
focus: TransportViewFocus::PlayPause,
focus: TransportFocus::PlayPause,
size: Measure::new(),
phrases: vec![],
editor: PhraseEditor::new(),
@ -60,6 +59,8 @@ pub struct SequencerTui {
/// Sections in the sequencer app that may be focused
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum SequencerFocus {
/// The menu bar is focused
Menu,
/// The transport (toolbar) is focused
Transport,
/// The phrase list (pool) is focused
@ -114,7 +115,7 @@ impl Content for SequencerStatusBar {
}
impl HasFocus for SequencerTui {
type Item = AppViewFocus<SequencerFocus>;
type Item = SequencerFocus;
}
impl FocusEnter for SequencerTui {
@ -141,7 +142,7 @@ impl FocusEnter for SequencerTui {
}
impl FocusGrid for SequencerTui {
type Item = AppViewFocus<SequencerFocus>;
type Item = SequencerFocus;
fn focus_cursor (&self) -> (usize, usize) {
self.cursor
}
@ -149,12 +150,11 @@ impl FocusGrid for SequencerTui {
&mut self.cursor
}
fn focus_layout (&self) -> &[&[Self::Item]] {
use AppViewFocus::*;
use SequencerFocus::*;
&[
&[Menu, Menu ],
&[Content(Transport), Content(Transport) ],
&[Content(PhrasePool), Content(PhraseEditor)],
&[Menu, Menu ],
&[Transport, Transport ],
&[PhrasePool, PhraseEditor],
]
}
fn focus_update (&mut self) {
@ -273,7 +273,7 @@ impl PlayheadApi for SequencerTui {
impl PlayerApi for SequencerTui {}
impl TransportViewState for SequencerTui {
fn focus (&self) -> TransportViewFocus {
fn focus (&self) -> TransportFocus {
self.focus
}
fn focused (&self) -> bool {

View file

@ -14,13 +14,12 @@ pub enum SequencerCommand {
Clear,
Clock(ClockCommand),
Playhead(PlayheadCommand),
Phrases(PhrasePoolViewCommand),
Editor(PhraseEditorCommand),
Phrases(PhrasesCommand),
Editor(PhraseCommand),
}
impl InputToCommand<Tui, SequencerTui> for SequencerCommand {
fn input_to_command (state: &SequencerTui, input: &TuiInput) -> Option<Self> {
use AppViewFocus::*;
use FocusCommand::*;
use SequencerCommand::*;
match input.event() {
@ -33,35 +32,23 @@ impl InputToCommand<Tui, SequencerTui> for SequencerCommand {
key!(KeyCode::Left) => Some(Self::Focus(Left)),
key!(KeyCode::Right) => Some(Self::Focus(Right)),
_ => Some(Self::App(match state.focused() {
Content(SequencerFocus::Transport) =>
TransportCommand::input_to_command(&state, input)
.map(Transport),
Content(SequencerFocus::PhrasePool) =>
PhrasePoolViewCommand::input_to_command(&state.phrases, input)
.map(Phrases),
Content(SequencerFocus::PhraseEditor) =>
PhraseEditorCommand::input_to_command(&state.editor, input)
.map(Editor),
SequencerFocus::Transport =>
TransportCommand::input_to_command(&state, input).map(Transport),
SequencerFocus::Phrases =>
PhrasesCommand::input_to_command(&state.phrases, input).map(Phrases),
SequencerFocus::PhraseEditor =>
PhraseCommand::input_to_command(&state.editor, input).map(Editor),
_ => return None,
}))
}
}
}
impl Command<SequencerTui> for SequencerCommand {
fn execute (self, state: &mut SequencerTui) -> Perhaps<Self> {
use AppViewCommand::*;
match self {
Focus(cmd) => delegate(cmd, Focus, state),
App(cmd) => delegate(cmd, App, state),
}
}
}
impl Command<SequencerTui> for SequencerCommand {
fn execute (self, state: &mut SequencerTui) -> Perhaps<Self> {
use SequencerCommand::*;
match self {
Focus(cmd) => delegate(cmd, Focus, state),
Phrases(cmd) => delegate(cmd, Phrases, &mut state.phrases),
Editor(cmd) => delegate(cmd, Editor, &mut state.editor),
Transport(cmd) => delegate(cmd, Transport, &mut state.transport)

View file

@ -17,7 +17,7 @@ pub struct TransportTui {
transport: jack::Transport,
/// Enable metronome?
metronome: bool,
focus: TransportViewFocus,
focus: TransportFocus,
focused: bool,
size: Measure<E>,
}
@ -31,7 +31,7 @@ impl TryFrom<&Arc<RwLock<JackClient>>> for TransportTui {
transport: jack.read().unwrap().transport(),
jack: jack.clone(),
focused: false,
focus: TransportViewFocus::PlayPause,
focus: TransportFocus::PlayPause,
size: Measure::new(),
})
}

View file

@ -3,7 +3,7 @@ use crate::*;
/// Handle input.
impl Handle<Tui> for TransportTui {
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
AppViewCommand::<TransportCommand>::execute_with_state(self, from)
TransportCommand::execute_with_state(self, from)
}
}
@ -14,58 +14,46 @@ pub enum TransportCommand {
Playhead(PlayheadCommand),
}
impl InputToCommand<Tui, TransportTui> for AppViewCommand<TransportCommand> {
fn input_to_command (app: &TransportTui, input: &TuiInput) -> Option<Self> {
use KeyCode::{Left, Right};
use FocusCommand::{Prev, Next};
use TransportCommand::{Focus, Clock, Playhead};
Some(match input.event() {
key!(Left) => Focus(Prev),
key!(Right) => Focus(Next),
_ => TransportCommand::input_to_command(&app, input).map(App)?
})
}
}
impl<T: TransportControl> InputToCommand<Tui, T> for TransportCommand {
fn input_to_command (state: &T, input: &TuiInput) -> Option<Self> {
use KeyCode::Char;
use AppViewFocus::Content;
use ClockCommand::{SetBpm, SetQuant, SetSync};
use TransportViewFocus as Focus;
use TransportCommand::{Clock, Playhead};
use TransportFocus as Focused;
use TransportCommand::{Focus, Clock, Playhead};
let focused = state.focused();
Some(match input.event() {
key!(Left) => Focus(FocusCommand::Prev),
key!(Right) => Focus(FocusCommand::Next),
key!(Char('.')) => match focused {
Content(Focus::Bpm) => Clock(SetBpm(state.bpm().get() + 1.0)),
Content(Focus::Quant) => Clock(SetQuant(state.next_quant())),
Content(Focus::Sync) => Clock(SetSync(state.next_sync())),
Content(Focus::PlayPause) => Playhead(todo!()),
Content(Focus::Clock) => Playhead(todo!()),
Focused::Bpm => Clock(SetBpm(state.bpm().get() + 1.0)),
Focused::Quant => Clock(SetQuant(state.next_quant())),
Focused::Sync => Clock(SetSync(state.next_sync())),
Focused::PlayPause => Playhead(todo!()),
Focused::Clock => Playhead(todo!()),
_ => {todo!()}
},
key!(KeyCode::Char(',')) => match focused {
Content(Focus::Bpm) => Clock(SetBpm(state.bpm().get() - 1.0)),
Content(Focus::Quant) => Clock(SetQuant(state.prev_quant())),
Content(Focus::Sync) => Clock(SetSync(state.prev_sync())),
Content(Focus::PlayPause) => Playhead(todo!()),
Content(Focus::Clock) => Playhead(todo!()),
Focused::Bpm => Clock(SetBpm(state.bpm().get() - 1.0)),
Focused::Quant => Clock(SetQuant(state.prev_quant())),
Focused::Sync => Clock(SetSync(state.prev_sync())),
Focused::PlayPause => Playhead(todo!()),
Focused::Clock => Playhead(todo!()),
_ => {todo!()}
},
key!(KeyCode::Char('>')) => match focused {
Content(Focus::Bpm) => Clock(SetBpm(state.bpm().get() + 0.001)),
Content(Focus::Quant) => Clock(SetQuant(state.next_quant())),
Content(Focus::Sync) => Clock(SetSync(state.next_sync())),
Content(Focus::PlayPause) => Playhead(todo!()),
Content(Focus::Clock) => Playhead(todo!()),
Focused::Bpm => Clock(SetBpm(state.bpm().get() + 0.001)),
Focused::Quant => Clock(SetQuant(state.next_quant())),
Focused::Sync => Clock(SetSync(state.next_sync())),
Focused::PlayPause => Playhead(todo!()),
Focused::Clock => Playhead(todo!()),
_ => {todo!()}
},
key!(KeyCode::Char('<')) => match focused {
Content(Focus::Bpm) => Clock(SetBpm(state.bpm().get() - 0.001)),
Content(Focus::Quant) => Clock(SetQuant(state.prev_quant())),
Content(Focus::Sync) => Clock(SetSync(state.prev_sync())),
Content(Focus::PlayPause) => Playhead(todo!()),
Content(Focus::Clock) => Playhead(todo!()),
Focused::Bpm => Clock(SetBpm(state.bpm().get() - 0.001)),
Focused::Quant => Clock(SetQuant(state.prev_quant())),
Focused::Sync => Clock(SetSync(state.prev_sync())),
Focused::PlayPause => Playhead(todo!()),
Focused::Clock => Playhead(todo!()),
_ => {todo!()}
},
_ => return None
@ -73,7 +61,7 @@ impl<T: TransportControl> InputToCommand<Tui, T> for TransportCommand {
}
}
pub trait TransportControl: FocusGrid<Item = AppViewFocus<TransportViewFocus>> {
pub trait TransportControl: FocusGrid<Item = TransportFocus> {
fn quant (&self) -> &Quantize;
fn bpm (&self) -> &BeatsPerMinute;
fn next_quant (&self) -> f64 {
@ -103,31 +91,14 @@ impl TransportControl for TransportTui {
}
}
impl Command<TransportTui> for AppViewCommand<TransportCommand> {
fn execute (self, state: &mut TransportTui) -> Perhaps<Self> {
use AppViewCommand::{Focus, App};
use FocusCommand::{Next, Prev};
Ok(Some(match self {
App(command) => if let Some(undo) = TransportCommand::execute(command, &mut state.app)? {
App(undo)
} else {
return Ok(None)
},
Focus(command) => Focus(match command {
Next => { todo!() },
Prev => { todo!() },
_ => { todo!() }
}),
_ => todo!()
}))
}
}
impl<T: TransportControl> Command<T> for TransportCommand {
fn execute (self, state: &mut T) -> Perhaps<Self> {
use TransportCommand::{Clock, Playhead};
use TransportCommand::{Focus, Clock, Playhead};
use ClockCommand::{SetBpm, SetQuant, SetSync};
Ok(Some(match self {
Focus(Next) => { todo!() }
Focus(Prev) => { todo!() },
Focus(_) => { unimplemented!() },
Clock(SetBpm(bpm)) => Clock(SetBpm(state.bpm().set(bpm))),
Clock(SetQuant(quant)) => Clock(SetQuant(state.quant().set(quant))),
Clock(SetSync(sync)) => Clock(SetSync(state.sync().set(sync))),

View file

@ -2,7 +2,8 @@ use crate::*;
/// Which item of the transport toolbar is focused
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum TransportViewFocus {
pub enum TransportFocus {
Menu,
Bpm,
Sync,
PlayPause,
@ -10,7 +11,7 @@ pub enum TransportViewFocus {
Quant,
}
impl TransportViewFocus {
impl TransportFocus {
pub fn next (&mut self) {
*self = match self {
Self::PlayPause => Self::Bpm,
@ -40,7 +41,7 @@ impl TransportViewFocus {
}
impl HasFocus for TransportTui {
type Item = AppViewFocus<TransportViewFocus>;
type Item = TransportFocus;
}
impl FocusEnter for TransportTui {
@ -60,7 +61,7 @@ impl FocusEnter for TransportTui {
}
impl FocusGrid for TransportTui {
type Item = AppViewFocus<TransportViewFocus>;
type Item = TransportFocus;
fn focus_cursor (&self) -> (usize, usize) {
self.cursor
}
@ -68,17 +69,10 @@ impl FocusGrid for TransportTui {
&mut self.cursor
}
fn focus_layout (&self) -> &[&[Self::Item]] {
use AppViewFocus::*;
use TransportViewFocus::*;
use TransportFocus::*;
&[
&[Menu],
&[
Content(Bpm),
Content(Sync),
Content(PlayPause),
Content(Clock),
Content(Quant),
],
&[Bpm, Sync, PlayPause, Clock, Quant],
]
}
fn focus_update (&mut self) {

View file

@ -13,7 +13,7 @@ impl Widget for TransportTui {
pub struct TransportView<'a, T: TransportViewState>(pub &'a T);
pub trait TransportViewState: Send + Sync {
fn focus (&self) -> TransportViewFocus;
fn focus (&self) -> TransportFocus;
fn focused (&self) -> bool;
fn transport_state (&self) -> Option<TransportState>;
fn bpm_value (&self) -> f64;
@ -23,7 +23,7 @@ pub trait TransportViewState: Send + Sync {
}
impl TransportViewState for TransportTui {
fn focus (&self) -> TransportViewFocus {
fn focus (&self) -> TransportFocus {
self.focus
}
fn focused (&self) -> bool {
@ -51,7 +51,7 @@ impl<'a, T: TransportViewState> Content for TransportView<'a, T> {
fn content (&self) -> impl Widget<Engine = Tui> {
let state = self.0;
lay!(
state.focus().wrap(state.focused(), TransportViewFocus::PlayPause, &Styled(
state.focus().wrap(state.focused(), TransportFocus::PlayPause, &Styled(
None,
match state.transport_state() {
Some(TransportState::Rolling) => "▶ PLAYING",
@ -62,19 +62,19 @@ impl<'a, T: TransportViewState> Content for TransportView<'a, T> {
).min_xy(11, 2).push_x(1)).align_x().fill_x(),
row!(
state.focus().wrap(state.focused(), TransportViewFocus::Bpm, &Outset::X(1u16, {
state.focus().wrap(state.focused(), TransportFocus::Bpm, &Outset::X(1u16, {
let bpm = state.bpm_value();
row! { "BPM ", format!("{}.{:03}", bpm as usize, (bpm * 1000.0) % 1000.0) }
})),
//let quant = state.focus().wrap(state.focused(), TransportViewFocus::Quant, &Outset::X(1u16, row! {
//let quant = state.focus().wrap(state.focused(), TransportFocus::Quant, &Outset::X(1u16, row! {
//"QUANT ", ppq_to_name(state.0.quant as usize)
//})),
state.focus().wrap(state.focused(), TransportViewFocus::Sync, &Outset::X(1u16, row! {
state.focus().wrap(state.focused(), TransportFocus::Sync, &Outset::X(1u16, row! {
"SYNC ", pulses_to_name(state.sync_value() as usize)
}))
).align_w().fill_x(),
state.focus().wrap(state.focused(), TransportViewFocus::Clock, &{
state.focus().wrap(state.focused(), TransportFocus::Clock, &{
let time1 = state.format_beat();
let time2 = state.format_msu();
row!("B" ,time1.as_str(), " T", time2.as_str()).outset_x(1)