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 midi_buf: Vec<Vec<Vec<u8>>>,
pub cursor: (usize, usize), pub cursor: (usize, usize),
pub menu_bar: Option<MenuBar<Tui, Self, ArrangerCommand>>, pub menu_bar: Option<MenuBar<Tui, Self, ArrangerCommand>>,
pub status_bar: Option<S>, pub status_bar: Option<ArrangerStatus>,
pub history: Vec<C>, pub history: Vec<ArrangerCommand>,
} }
impl TryFrom<&Arc<RwLock<JackClient>>> for ArrangerTui { impl TryFrom<&Arc<RwLock<JackClient>>> for ArrangerTui {

View file

@ -20,13 +20,19 @@ pub enum ArrangerCommand {
Clip(ArrangerClipCommand), Clip(ArrangerClipCommand),
Select(ArrangerSelection), Select(ArrangerSelection),
Zoom(usize), Zoom(usize),
Phrases(PhrasePoolViewCommand), Phrases(PhrasePoolCommand),
Editor(PhraseEditorCommand), Editor(PhraseEditorCommand),
EditPhrase(Option<Arc<RwLock<Phrase>>>), EditPhrase(Option<Arc<RwLock<Phrase>>>),
} }
impl InputToCommand<Tui, ArrangerTui> for ArrangerCommand { pub trait ArrangerControl {
fn input_to_command (view: &ArrangerTui, input: &TuiInput) -> Option<Self> { }
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 FocusCommand::*;
use ArrangerCommand::*; use ArrangerCommand::*;
Some(match input.event() { Some(match input.event() {
@ -44,7 +50,7 @@ impl InputToCommand<Tui, ArrangerTui> for ArrangerCommand {
Self::App(Playhead(PlayheadCommand::Play(None))) Self::App(Playhead(PlayheadCommand::Play(None)))
}, },
_ => Self::App(match view.focused() { _ => Self::App(match view.focused() {
Content(ArrangerFocus::Transport) => { ArrangerFocus::Transport => {
use TransportCommand::{Clock, Playhead}; use TransportCommand::{Clock, Playhead};
match TransportCommand::input_to_command(view, input)? { match TransportCommand::input_to_command(view, input)? {
Clock(command) => { Clock(command) => {
@ -55,18 +61,18 @@ impl InputToCommand<Tui, ArrangerTui> for ArrangerCommand {
}, },
} }
}, },
Content(ArrangerFocus::PhraseEditor) => Editor( ArrangerFocus::PhraseEditor => Editor(
PhraseEditorCommand::input_to_command(&view.editor, input)? PhraseCommand::input_to_command(&view.editor, input)?
), ),
Content(ArrangerFocus::PhrasePool) => match input.event() { ArrangerFocus::PhrasePool => match input.event() {
key!(KeyCode::Char('e')) => EditPhrase( key!(KeyCode::Char('e')) => EditPhrase(
Some(view.phrase().clone()) Some(view.phrase().clone())
), ),
_ => Phrases( _ => Phrases(
PhrasePoolViewCommand::input_to_command(view, input)? PhrasePoolCommand::input_to_command(view, input)?
) )
}, },
Content(ArrangerFocus::Arranger) => { ArrangerFocus::Arranger => {
use ArrangerSelection as Select; use ArrangerSelection as Select;
use ArrangerTrackCommand as Track; use ArrangerTrackCommand as Track;
use ArrangerClipCommand as Clip; use ArrangerClipCommand as Clip;
@ -183,23 +189,11 @@ impl InputToCommand<Tui, ArrangerTui> for ArrangerCommand {
} }
} }
impl Command<ArrangerTui> for ArrangerCommand { impl<T: ArrangerControl> Command<T> for ArrangerCommand {
fn execute (self, state: &mut ArrangerTui) -> Perhaps<Self> { fn execute (self, state: &mut T) -> 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> {
use ArrangerCommand::*; use ArrangerCommand::*;
match self { match self {
Focus(cmd) => { delegate(cmd, Focus, state) },
Scene(cmd) => { delegate(cmd, Scene, &mut state) }, Scene(cmd) => { delegate(cmd, Scene, &mut state) },
Track(cmd) => { delegate(cmd, Track, &mut state) }, Track(cmd) => { delegate(cmd, Track, &mut state) },
Clip(cmd) => { delegate(cmd, Clip, &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 /// Sections in the arranger app that may be focused
#[derive(Copy, Clone, PartialEq, Eq, Debug)] #[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum ArrangerFocus { pub enum ArrangerFocus {
/// The menu bar is focused
Menu,
/// The transport (toolbar) is focused /// The transport (toolbar) is focused
Transport, Transport,
/// The arrangement (grid) is focused /// The arrangement (grid) is focused
@ -13,16 +15,15 @@ pub enum ArrangerFocus {
PhraseEditor, PhraseEditor,
} }
impl FocusEnter for ArrangerApp<Tui> { impl FocusEnter for ArrangerTui {
type Item = AppViewFocus<ArrangerFocus>; type Item = ArrangerFocus;
fn focus_enter (&mut self) { fn focus_enter (&mut self) {
use AppViewFocus::*;
use ArrangerFocus::*; use ArrangerFocus::*;
let focused = self.focused(); let focused = self.focused();
if !self.entered { if !self.entered {
self.entered = focused == Content(Arranger); self.entered = focused == Arranger;
self.app.editor.entered = focused == Content(PhraseEditor); self.app.editor.entered = focused == PhraseEditor;
self.app.phrases.entered = focused == Content(PhrasePool); self.app.phrases.entered = focused == PhrasePool;
} }
} }
fn focus_exit (&mut self) { fn focus_exit (&mut self) {
@ -42,8 +43,8 @@ impl FocusEnter for ArrangerApp<Tui> {
} }
/// Focus layout of arranger app /// Focus layout of arranger app
impl FocusGrid for ArrangerApp<Tui> { impl FocusGrid for ArrangerTui {
type Item = AppViewFocus<ArrangerFocus>; type Item = ArrangerFocus;
fn focus_cursor (&self) -> (usize, usize) { fn focus_cursor (&self) -> (usize, usize) {
self.cursor self.cursor
} }
@ -51,24 +52,22 @@ impl FocusGrid for ArrangerApp<Tui> {
&mut self.cursor &mut self.cursor
} }
fn focus_layout (&self) -> &[&[Self::Item]] { fn focus_layout (&self) -> &[&[Self::Item]] {
use AppViewFocus::*;
use ArrangerFocus::*; use ArrangerFocus::*;
&[ &[
&[Menu, Menu ], &[Menu, Menu ],
&[Content(Transport), Content(Transport) ], &[Transport, Transport ],
&[Content(Arranger), Content(Arranger) ], &[Arranger, Arranger ],
&[Content(PhrasePool), Content(PhraseEditor)], &[PhrasePool, PhraseEditor],
] ]
} }
fn focus_update (&mut self) { fn focus_update (&mut self) {
use AppViewFocus::*;
use ArrangerFocus::*; use ArrangerFocus::*;
let focused = self.focused(); let focused = self.focused();
if let Some(mut status_bar) = self.status_bar { if let Some(mut status_bar) = self.status_bar {
status_bar.update(&( status_bar.update(&(
self.focused(), self.focused(),
self.app.selected, self.app.selected,
focused == Content(PhraseEditor) && self.entered focused == PhraseEditor && self.entered
)) ))
} }
} }

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -2,12 +2,12 @@ use crate::*;
impl Handle<Tui> for PhrasePoolView<Tui> { impl Handle<Tui> for PhrasePoolView<Tui> {
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> { fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
PhrasePoolViewCommand::execute_with_state(self, from) PhrasesCommand::execute_with_state(self, from)
} }
} }
#[derive(Clone, PartialEq, Debug)] #[derive(Clone, PartialEq, Debug)]
pub enum PhrasePoolViewCommand { pub enum PhrasesCommand {
Select(usize), Select(usize),
Edit(PhrasePoolCommand), Edit(PhrasePoolCommand),
Rename(PhraseRenameCommand), Rename(PhraseRenameCommand),
@ -33,9 +33,9 @@ pub enum PhraseLengthCommand {
Cancel, 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> { fn input_to_command (state: &PhrasePoolView<Tui>, input: &TuiInput) -> Option<Self> {
use PhrasePoolViewCommand as Cmd; use PhrasesCommand as Cmd;
use PhrasePoolCommand as Edit; use PhrasePoolCommand as Edit;
use PhraseRenameCommand as Rename; use PhraseRenameCommand as Rename;
use PhraseLengthCommand as Length; use PhraseLengthCommand as Length;
@ -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> { fn execute (self, view: &mut PhrasePoolView<E>) -> Perhaps<Self> {
use PhraseRenameCommand as Rename; use PhraseRenameCommand as Rename;
use PhraseLengthCommand as Length; use PhraseLengthCommand as Length;

View file

@ -1,46 +1,78 @@
use crate::*; use crate::*;
impl Widget for TransportTui { impl Widget for PhrasesTui {
type Engine = Tui; type Engine = Tui;
fn layout (&self, to: [u16;2]) -> Perhaps<[u16;2]> { 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<()> { 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 // 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; type Engine = Tui;
fn content (&self) -> impl Widget<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!( let content = col!(
(i, phrase) in phrases.iter().enumerate() => Layers::new(|add|{ (i, phrase) in phrases.iter().enumerate() => Layers::new(|add|{
let Phrase { ref name, color, length, .. } = *phrase.read().unwrap(); let Phrase { ref name, color, length, .. } = *phrase.read().unwrap();
let mut length = PhraseLength::new(length, None); let mut length = PhraseLength::new(length, None);
if let Some(PhrasePoolMode::Length(phrase, new_length, focus)) = mode { if let Some(PhrasesMode::Length(phrase, new_length, focus)) = mode {
if *focused && i == *phrase { if *focused && i == phrase {
length.pulses = *new_length; length.pulses = new_length;
length.focus = Some(*focus); length.focus = Some(focus);
} }
} }
let length = length.align_e().fill_x(); let length = length.align_e().fill_x();
let row1 = lay!(format!(" {i}").align_w().fill_x(), length).fill_x(); let row1 = lay!(format!(" {i}").align_w().fill_x(), length).fill_x();
let mut row2 = format!(" {name}"); let mut row2 = format!(" {name}");
if let Some(PhrasePoolMode::Rename(phrase, _)) = mode { if let Some(PhrasesMode::Rename(phrase, _)) = mode {
if *focused && i == *phrase { row2 = format!("{row2}"); } if *focused && i == phrase { row2 = format!("{row2}"); }
}; };
let row2 = TuiStyle::bold(row2, true); let row2 = TuiStyle::bold(row2, true);
add(&col!(row1, row2).fill_x().bg(color.base.rgb))?; 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_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 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 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 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()); let upper_right = format!("({})", phrases.len());
lay!( lay!(
content, content,

View file

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

View file

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

View file

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

View file

@ -3,7 +3,7 @@ use crate::*;
/// Handle input. /// Handle input.
impl Handle<Tui> for TransportTui { impl Handle<Tui> for TransportTui {
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> { 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), 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 { impl<T: TransportControl> InputToCommand<Tui, T> for TransportCommand {
fn input_to_command (state: &T, input: &TuiInput) -> Option<Self> { fn input_to_command (state: &T, input: &TuiInput) -> Option<Self> {
use KeyCode::Char; use KeyCode::Char;
use AppViewFocus::Content;
use ClockCommand::{SetBpm, SetQuant, SetSync}; use ClockCommand::{SetBpm, SetQuant, SetSync};
use TransportViewFocus as Focus; use TransportFocus as Focused;
use TransportCommand::{Clock, Playhead}; use TransportCommand::{Focus, Clock, Playhead};
let focused = state.focused(); let focused = state.focused();
Some(match input.event() { Some(match input.event() {
key!(Left) => Focus(FocusCommand::Prev),
key!(Right) => Focus(FocusCommand::Next),
key!(Char('.')) => match focused { key!(Char('.')) => match focused {
Content(Focus::Bpm) => Clock(SetBpm(state.bpm().get() + 1.0)), Focused::Bpm => Clock(SetBpm(state.bpm().get() + 1.0)),
Content(Focus::Quant) => Clock(SetQuant(state.next_quant())), Focused::Quant => Clock(SetQuant(state.next_quant())),
Content(Focus::Sync) => Clock(SetSync(state.next_sync())), Focused::Sync => Clock(SetSync(state.next_sync())),
Content(Focus::PlayPause) => Playhead(todo!()), Focused::PlayPause => Playhead(todo!()),
Content(Focus::Clock) => Playhead(todo!()), Focused::Clock => Playhead(todo!()),
_ => {todo!()} _ => {todo!()}
}, },
key!(KeyCode::Char(',')) => match focused { key!(KeyCode::Char(',')) => match focused {
Content(Focus::Bpm) => Clock(SetBpm(state.bpm().get() - 1.0)), Focused::Bpm => Clock(SetBpm(state.bpm().get() - 1.0)),
Content(Focus::Quant) => Clock(SetQuant(state.prev_quant())), Focused::Quant => Clock(SetQuant(state.prev_quant())),
Content(Focus::Sync) => Clock(SetSync(state.prev_sync())), Focused::Sync => Clock(SetSync(state.prev_sync())),
Content(Focus::PlayPause) => Playhead(todo!()), Focused::PlayPause => Playhead(todo!()),
Content(Focus::Clock) => Playhead(todo!()), Focused::Clock => Playhead(todo!()),
_ => {todo!()} _ => {todo!()}
}, },
key!(KeyCode::Char('>')) => match focused { key!(KeyCode::Char('>')) => match focused {
Content(Focus::Bpm) => Clock(SetBpm(state.bpm().get() + 0.001)), Focused::Bpm => Clock(SetBpm(state.bpm().get() + 0.001)),
Content(Focus::Quant) => Clock(SetQuant(state.next_quant())), Focused::Quant => Clock(SetQuant(state.next_quant())),
Content(Focus::Sync) => Clock(SetSync(state.next_sync())), Focused::Sync => Clock(SetSync(state.next_sync())),
Content(Focus::PlayPause) => Playhead(todo!()), Focused::PlayPause => Playhead(todo!()),
Content(Focus::Clock) => Playhead(todo!()), Focused::Clock => Playhead(todo!()),
_ => {todo!()} _ => {todo!()}
}, },
key!(KeyCode::Char('<')) => match focused { key!(KeyCode::Char('<')) => match focused {
Content(Focus::Bpm) => Clock(SetBpm(state.bpm().get() - 0.001)), Focused::Bpm => Clock(SetBpm(state.bpm().get() - 0.001)),
Content(Focus::Quant) => Clock(SetQuant(state.prev_quant())), Focused::Quant => Clock(SetQuant(state.prev_quant())),
Content(Focus::Sync) => Clock(SetSync(state.prev_sync())), Focused::Sync => Clock(SetSync(state.prev_sync())),
Content(Focus::PlayPause) => Playhead(todo!()), Focused::PlayPause => Playhead(todo!()),
Content(Focus::Clock) => Playhead(todo!()), Focused::Clock => Playhead(todo!()),
_ => {todo!()} _ => {todo!()}
}, },
_ => return None _ => 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 quant (&self) -> &Quantize;
fn bpm (&self) -> &BeatsPerMinute; fn bpm (&self) -> &BeatsPerMinute;
fn next_quant (&self) -> f64 { 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 { impl<T: TransportControl> Command<T> for TransportCommand {
fn execute (self, state: &mut T) -> Perhaps<Self> { fn execute (self, state: &mut T) -> Perhaps<Self> {
use TransportCommand::{Clock, Playhead}; use TransportCommand::{Focus, Clock, Playhead};
use ClockCommand::{SetBpm, SetQuant, SetSync}; use ClockCommand::{SetBpm, SetQuant, SetSync};
Ok(Some(match self { Ok(Some(match self {
Focus(Next) => { todo!() }
Focus(Prev) => { todo!() },
Focus(_) => { unimplemented!() },
Clock(SetBpm(bpm)) => Clock(SetBpm(state.bpm().set(bpm))), Clock(SetBpm(bpm)) => Clock(SetBpm(state.bpm().set(bpm))),
Clock(SetQuant(quant)) => Clock(SetQuant(state.quant().set(quant))), Clock(SetQuant(quant)) => Clock(SetQuant(state.quant().set(quant))),
Clock(SetSync(sync)) => Clock(SetSync(state.sync().set(sync))), 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 /// Which item of the transport toolbar is focused
#[derive(Clone, Copy, Debug, PartialEq)] #[derive(Clone, Copy, Debug, PartialEq)]
pub enum TransportViewFocus { pub enum TransportFocus {
Menu,
Bpm, Bpm,
Sync, Sync,
PlayPause, PlayPause,
@ -10,7 +11,7 @@ pub enum TransportViewFocus {
Quant, Quant,
} }
impl TransportViewFocus { impl TransportFocus {
pub fn next (&mut self) { pub fn next (&mut self) {
*self = match self { *self = match self {
Self::PlayPause => Self::Bpm, Self::PlayPause => Self::Bpm,
@ -40,7 +41,7 @@ impl TransportViewFocus {
} }
impl HasFocus for TransportTui { impl HasFocus for TransportTui {
type Item = AppViewFocus<TransportViewFocus>; type Item = TransportFocus;
} }
impl FocusEnter for TransportTui { impl FocusEnter for TransportTui {
@ -60,7 +61,7 @@ impl FocusEnter for TransportTui {
} }
impl FocusGrid for TransportTui { impl FocusGrid for TransportTui {
type Item = AppViewFocus<TransportViewFocus>; type Item = TransportFocus;
fn focus_cursor (&self) -> (usize, usize) { fn focus_cursor (&self) -> (usize, usize) {
self.cursor self.cursor
} }
@ -68,17 +69,10 @@ impl FocusGrid for TransportTui {
&mut self.cursor &mut self.cursor
} }
fn focus_layout (&self) -> &[&[Self::Item]] { fn focus_layout (&self) -> &[&[Self::Item]] {
use AppViewFocus::*; use TransportFocus::*;
use TransportViewFocus::*;
&[ &[
&[Menu], &[Menu],
&[ &[Bpm, Sync, PlayPause, Clock, Quant],
Content(Bpm),
Content(Sync),
Content(PlayPause),
Content(Clock),
Content(Quant),
],
] ]
} }
fn focus_update (&mut self) { 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 struct TransportView<'a, T: TransportViewState>(pub &'a T);
pub trait TransportViewState: Send + Sync { pub trait TransportViewState: Send + Sync {
fn focus (&self) -> TransportViewFocus; fn focus (&self) -> TransportFocus;
fn focused (&self) -> bool; fn focused (&self) -> bool;
fn transport_state (&self) -> Option<TransportState>; fn transport_state (&self) -> Option<TransportState>;
fn bpm_value (&self) -> f64; fn bpm_value (&self) -> f64;
@ -23,7 +23,7 @@ pub trait TransportViewState: Send + Sync {
} }
impl TransportViewState for TransportTui { impl TransportViewState for TransportTui {
fn focus (&self) -> TransportViewFocus { fn focus (&self) -> TransportFocus {
self.focus self.focus
} }
fn focused (&self) -> bool { fn focused (&self) -> bool {
@ -51,7 +51,7 @@ impl<'a, T: TransportViewState> Content for TransportView<'a, T> {
fn content (&self) -> impl Widget<Engine = Tui> { fn content (&self) -> impl Widget<Engine = Tui> {
let state = self.0; let state = self.0;
lay!( lay!(
state.focus().wrap(state.focused(), TransportViewFocus::PlayPause, &Styled( state.focus().wrap(state.focused(), TransportFocus::PlayPause, &Styled(
None, None,
match state.transport_state() { match state.transport_state() {
Some(TransportState::Rolling) => "▶ PLAYING", 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(), ).min_xy(11, 2).push_x(1)).align_x().fill_x(),
row!( 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(); let bpm = state.bpm_value();
row! { "BPM ", format!("{}.{:03}", bpm as usize, (bpm * 1000.0) % 1000.0) } 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) //"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) "SYNC ", pulses_to_name(state.sync_value() as usize)
})) }))
).align_w().fill_x(), ).align_w().fill_x(),
state.focus().wrap(state.focused(), TransportViewFocus::Clock, &{ state.focus().wrap(state.focused(), TransportFocus::Clock, &{
let time1 = state.format_beat(); let time1 = state.format_beat();
let time2 = state.format_msu(); let time2 = state.format_msu();
row!("B" ,time1.as_str(), " T", time2.as_str()).outset_x(1) row!("B" ,time1.as_str(), " T", time2.as_str()).outset_x(1)