implement sequencer focus; auto update_focus

This commit is contained in:
🪞👃🪞 2024-10-09 22:50:23 +03:00
parent 9f15f8fff9
commit 3bf475d15e
8 changed files with 119 additions and 160 deletions

View file

@ -4,6 +4,7 @@ pub trait FocusGrid<T: Copy + PartialEq> {
fn layout (&self) -> &[&[T]];
fn cursor (&self) -> (usize, usize);
fn cursor_mut (&mut self) -> &mut (usize, usize);
fn update_focus (&mut self) {}
fn focused (&self) -> &T {
let (x, y) = self.cursor();
&self.layout()[y][x]
@ -16,6 +17,7 @@ pub trait FocusGrid<T: Copy + PartialEq> {
((x as f32 / layout[y].len() as f32) * layout[next_y].len() as f32) as usize
};
*self.cursor_mut() = (next_x, next_y);
self.update_focus();
}
fn focus_down (&mut self) {
let layout = self.layout();
@ -25,18 +27,21 @@ pub trait FocusGrid<T: Copy + PartialEq> {
((x as f32 / layout[y].len() as f32) * layout[next_y].len() as f32) as usize
};
*self.cursor_mut() = (next_x, next_y);
self.update_focus();
}
fn focus_left (&mut self) {
let layout = self.layout();
let (x, y) = self.cursor();
let next_x = if x == 0 { layout[y].len().saturating_sub(1) } else { x - 1 };
*self.cursor_mut() = (next_x, y);
self.update_focus();
}
fn focus_right (&mut self) {
let layout = self.layout();
let (x, y) = self.cursor();
let next_x = if x >= layout[y].len().saturating_sub(1) { 0 } else { x + 1 };
*self.cursor_mut() = (next_x, y);
self.update_focus();
}
fn focus_next (&mut self) {
let current = *self.focused();
@ -50,6 +55,7 @@ pub trait FocusGrid<T: Copy + PartialEq> {
if *self.focused() == current { // FIXME: prevent infinite loop
self.focus_next()
}
self.update_focus();
}
fn focus_prev (&mut self) {
let current = *self.focused();
@ -65,6 +71,7 @@ pub trait FocusGrid<T: Copy + PartialEq> {
if *self.focused() == current { // FIXME: prevent infinite loop
self.focus_prev()
}
self.update_focus();
}
}

View file

@ -2,6 +2,8 @@ use crate::*;
/// Root level object for standalone `tek_arranger`
pub struct Arranger<E: Engine> {
/// Which view is focused
pub focus_cursor: (usize, usize),
/// Controls the JACK transport.
pub transport: Option<Arc<RwLock<TransportToolbar<E>>>>,
/// Contains all the sequencers.
@ -14,24 +16,10 @@ pub struct Arranger<E: Engine> {
pub show_sequencer: Option<tek_core::Direction>,
/// Slot for modal dialog displayed on top of app.
pub modal: Option<Box<dyn ContentComponent<E>>>,
/// Focus cursor
pub focus_cursor: (usize, usize)
}
/// Sections in the arranger that may be focused
/// Sections in the arranger app that may be focused
#[derive(Copy, Clone, PartialEq, Eq)]
pub enum ArrangerFocus { Transport, Arrangement, PhrasePool, PhraseEditor }
/// Focus layout of arranger.
impl<E: Engine> FocusGrid<ArrangerFocus> for Arranger<E> {
fn cursor (&self) -> (usize, usize) { self.focus_cursor }
fn cursor_mut (&mut self) -> &mut (usize, usize) { &mut self.focus_cursor }
fn layout (&self) -> &[&[ArrangerFocus]] {
&[
&[ArrangerFocus::Transport],
&[ArrangerFocus::Arrangement, ArrangerFocus::Arrangement],
&[ArrangerFocus::PhrasePool, ArrangerFocus::PhraseEditor],
]
}
}
/// Represents the tracks and scenes of the composition.
pub struct Arrangement<E: Engine> {
/// Name of arranger
@ -104,6 +92,29 @@ pub struct ArrangerRenameModal<E: Engine> {
pub result: Arc<RwLock<String>>,
pub cursor: usize
}
/// Focus layout of arranger app
impl<E: Engine> FocusGrid<ArrangerFocus> for Arranger<E> {
fn cursor (&self) -> (usize, usize) { self.focus_cursor }
fn cursor_mut (&mut self) -> &mut (usize, usize) { &mut self.focus_cursor }
fn layout (&self) -> &[&[ArrangerFocus]] { &[
&[ArrangerFocus::Transport],
&[ArrangerFocus::Arrangement, ArrangerFocus::Arrangement],
&[ArrangerFocus::PhrasePool, ArrangerFocus::PhraseEditor],
] }
fn update_focus (&mut self) {
let focused = *self.focused();
if let Some(transport) = self.transport.as_ref() {
transport.write().unwrap().focused =
focused == ArrangerFocus::Transport
}
self.arrangement.focused =
focused == ArrangerFocus::Arrangement;
self.phrases.write().unwrap().focused =
focused == ArrangerFocus::PhrasePool;
self.editor.focused =
focused == ArrangerFocus::PhraseEditor;
}
}
/// General methods for arrangement
impl<E: Engine> Arrangement<E> {
pub fn new (name: &str, phrases: &Arc<RwLock<PhrasePool<E>>>) -> Self {
@ -182,7 +193,7 @@ impl<E: Engine> Arrangement<E> {
for track_index in 0..self.tracks.len() {
if let Some(phrase) = scene.clip(track_index) {
let len = phrase.read().unwrap().name.read().unwrap().len();
lens[track_index] = lens[track_index].max(len + 16);
lens[track_index] = lens[track_index].max(len);
}
}
}

View file

@ -7,29 +7,21 @@ pub fn main () -> Usually<()> { ArrangerCli::parse().run() }
#[command(version, about, long_about = None)]
pub struct ArrangerCli {
/// Name of JACK client
#[arg(short, long)]
name: Option<String>,
#[arg(short, long)] name: Option<String>,
/// Whether to include a transport toolbar (default: true)
#[arg(short, long, default_value_t = true)]
transport: bool,
#[arg(short, long, default_value_t = true)] transport: bool,
/// Number of tracks
#[arg(short = 'x', long, default_value_t = 8)]
tracks: usize,
#[arg(short = 'x', long, default_value_t = 8)] tracks: usize,
/// Number of scenes
#[arg(short, long, default_value_t = 8)]
scenes: usize,
#[arg(short, long, default_value_t = 8)] scenes: usize,
}
impl ArrangerCli {
/// Run the arranger TUI from CLI arguments.
fn run (&self) -> Usually<()> {
let jack = JackClient::Inactive(
Client::new("tek_arranger", ClientOptions::NO_START_SERVER)?.0
);
let jack_transport = jack.transport();
let mut transport = TransportToolbar::new(Some(jack_transport));
transport.set_focused(true);
let transport = Arc::new(RwLock::new(transport));
let jack = Client::new("tek_arranger", ClientOptions::NO_START_SERVER)?.0;
let jack = JackClient::Inactive(jack);
let transport = Arc::new(RwLock::new(TransportToolbar::new(Some(jack.transport()))));
let phrases = Arc::new(RwLock::new(PhrasePool::new()));
let mut arrangement = Arrangement::new("", &phrases);
if let Some(name) = self.name.as_ref() {
@ -57,7 +49,7 @@ impl ArrangerCli {
}
)?
);
Tui::run(Arc::new(RwLock::new(Arranger {
let mut app = Arranger {
focus_cursor: (0, 1),
transport: self.transport.then_some(transport),
show_sequencer: Some(tek_core::Direction::Down),
@ -65,7 +57,9 @@ impl ArrangerCli {
editor: PhraseEditor::new(),
arrangement,
phrases,
})))?;
};
app.update_focus();
Tui::run(Arc::new(RwLock::new(app)))?;
Ok(())
}
}

View file

@ -28,15 +28,6 @@ impl Content for Arranger<Tui> {
/// Handle top-level events in standalone arranger.
impl Handle<Tui> for Arranger<Tui> {
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
let update_focus = |arranger: &mut Self| {
let focused = *arranger.focused();
arranger.transport.as_ref().map(|transport|{
transport.write().unwrap().focused = focused == ArrangerFocus::Transport
});
arranger.arrangement.focused = focused == ArrangerFocus::Arrangement;
arranger.phrases.write().unwrap().focused = focused == ArrangerFocus::PhrasePool;
arranger.editor.focused = focused == ArrangerFocus::PhraseEditor;
};
if !match self.focused() {
ArrangerFocus::Transport => self.transport.handle(from)?,
ArrangerFocus::Arrangement => self.arrangement.handle(from)?,
@ -45,15 +36,15 @@ impl Handle<Tui> for Arranger<Tui> {
}.unwrap_or(false) {
match from.event() {
// Tab navigation
key!(KeyCode::Tab) => { self.focus_next(); update_focus(self); },
key!(Shift-KeyCode::Tab) => { self.focus_prev(); update_focus(self); },
key!(KeyCode::BackTab) => { self.focus_prev(); update_focus(self); },
key!(Shift-KeyCode::BackTab) => { self.focus_prev(); update_focus(self); },
key!(KeyCode::Tab) => { self.focus_next(); },
key!(Shift-KeyCode::Tab) => { self.focus_prev(); },
key!(KeyCode::BackTab) => { self.focus_prev(); },
key!(Shift-KeyCode::BackTab) => { self.focus_prev(); },
// Directional navigation
key!(KeyCode::Up) => { self.focus_up(); update_focus(self); },
key!(KeyCode::Down) => { self.focus_down(); update_focus(self); },
key!(KeyCode::Left) => { self.focus_left(); update_focus(self); },
key!(KeyCode::Right) => { self.focus_right(); update_focus(self); },
key!(KeyCode::Up) => { self.focus_up(); },
key!(KeyCode::Down) => { self.focus_down(); },
key!(KeyCode::Left) => { self.focus_left(); },
key!(KeyCode::Right) => { self.focus_right(); },
// Global play/pause binding
key!(KeyCode::Char(' ')) => match self.transport {
Some(ref mut transport) => { transport.write().unwrap().toggle_play()?; },
@ -86,14 +77,6 @@ impl Arranger<Tui> {
};
}
}
impl Focusable<Tui> for Arrangement<Tui> {
fn is_focused (&self) -> bool {
self.focused
}
fn set_focused (&mut self, focused: bool) {
self.focused = focused
}
}
impl Handle<Tui> for Arrangement<Tui> {
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
match from.event() {

View file

@ -7,15 +7,18 @@ pub type PhraseMessage = Vec<u8>;
pub type PhraseChunk = [Vec<PhraseMessage>];
/// Root level object for standalone `tek_sequencer`
pub struct Sequencer<E: Engine> {
/// Which view is focused
pub focus_cursor: (usize, usize),
/// Controls the JACK transport.
pub transport: Option<Arc<RwLock<TransportToolbar<E>>>>,
/// Pool of all phrases available to the sequencer
pub phrases: Arc<RwLock<PhrasePool<E>>>,
/// Phrase editor view
pub editor: PhraseEditor<E>,
/// Which view is focused
pub focus: usize
}
/// Sections in the sequencer app that may be focused
#[derive(Copy, Clone, PartialEq, Eq)]
pub enum SequencerFocus { Transport, PhrasePool, PhraseEditor }
/// Contains all phrases in a project
pub struct PhrasePool<E: Engine> {
_engine: PhantomData<E>,
@ -93,6 +96,26 @@ pub struct PhrasePlayer<E: Engine> {
/// Send all notes off
pub reset: bool, // TODO?: after Some(nframes)
}
/// Focus layout of sequencer app
impl<E: Engine> FocusGrid<SequencerFocus> for Sequencer<E> {
fn cursor (&self) -> (usize, usize) { self.focus_cursor }
fn cursor_mut (&mut self) -> &mut (usize, usize) { &mut self.focus_cursor }
fn layout (&self) -> &[&[SequencerFocus]] { &[
&[SequencerFocus::Transport],
&[SequencerFocus::PhrasePool, SequencerFocus::PhraseEditor],
] }
fn update_focus (&mut self) {
let focused = *self.focused();
if let Some(transport) = self.transport.as_ref() {
transport.write().unwrap().focused =
focused == SequencerFocus::Transport
}
self.phrases.write().unwrap().focused =
focused == SequencerFocus::PhrasePool;
self.editor.focused =
focused == SequencerFocus::PhraseEditor;
}
}
impl<E: Engine> PhrasePool<E> {
pub fn new () -> Self {
Self {

View file

@ -13,18 +13,14 @@ pub struct SequencerCli {
/// Default phrase duration (in pulses; default: 4 * PPQ = 1 bar)
#[arg(short, long)] length: Option<usize>,
/// Whether to include a transport toolbar (default: true)
#[arg(short, long)] transport: Option<bool>
#[arg(short, long, default_value_t = true)] transport: bool
}
impl SequencerCli {
fn run (&self) -> Usually<()> {
let jack = JackClient::Inactive(
Client::new("tek_arranger", ClientOptions::NO_START_SERVER)?.0
);
let jack_transport = jack.transport();
let mut transport = TransportToolbar::new(Some(jack_transport));
transport.set_focused(true);
let transport = Arc::new(RwLock::new(transport));
let jack = Client::new("tek_arranger", ClientOptions::NO_START_SERVER)?.0;
let jack = JackClient::Inactive(jack);
let transport = Arc::new(RwLock::new(TransportToolbar::new(Some(jack.transport()))));
transport.write().unwrap().jack = Some(
jack.activate(
&transport.clone(),
@ -34,10 +30,10 @@ impl SequencerCli {
)?
);
let seq = Sequencer {
focus: 0,
focus_cursor: (1, 1),
transport: self.transport.then_some(transport),
editor: PhraseEditor::new(),
phrases: Arc::new(RwLock::new(PhrasePool::new())),
transport: Some(transport),
};
if let Some(name) = self.name.as_ref() {
// TODO

View file

@ -10,59 +10,33 @@ impl Content for Sequencer<Tui> {
})
}
}
/// Focusable items in standalone arranger.
impl Focus<3, Tui> for Sequencer<Tui> {
fn focus (&self) -> usize {
self.focus
}
fn focus_mut (&mut self) -> &mut usize {
&mut self.focus
}
fn focusable (&self) -> [&dyn Focusable<Tui>;3] {
focusables!(self.transport, self.phrases, self.editor)
}
fn focusable_mut (&mut self) -> [&mut dyn Focusable<Tui>;3] {
focusables_mut!(self.transport, self.phrases, self.editor)
}
}
/// Handle top-level events in standalone arranger.
impl Handle<Tui> for Sequencer<Tui> {
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
let focus = self.focus;
let is_first_row = false;
let is_last_row = false;
if !match self.focused() {
SequencerFocus::Transport => self.transport.handle(from)?,
SequencerFocus::PhrasePool => self.phrases.handle(from)?,
SequencerFocus::PhraseEditor => self.editor.handle(from)?
}.unwrap_or(false) {
match from.event() {
key!(KeyCode::Char(' ')) => {
if let Some(ref mut transport) = self.transport {
transport.write().unwrap().toggle_play()?;
} else {
return Ok(None)
}
},
key!(KeyCode::Tab) => {
self.focus_next();
},
key!(KeyCode::BackTab) => {
self.focus_prev();
},
key!(KeyCode::Down) => {
if focus == 0 {
self.focus_next();
} else if focus == 1 && is_last_row {
self.focus_next();
} else {
return self.focused_mut().handle(from)
}
},
key!(KeyCode::Up) => {
if focus == 1 && is_first_row {
self.focus_prev();
} else {
return self.focused_mut().handle(from)
}
},
_ => return self.focused_mut().handle(from)
// Tab navigation
key!(KeyCode::Tab) => { self.focus_next(); },
key!(Shift-KeyCode::Tab) => { self.focus_prev(); },
key!(KeyCode::BackTab) => { self.focus_prev(); },
key!(Shift-KeyCode::BackTab) => { self.focus_prev(); },
// Directional navigation
key!(KeyCode::Up) => { self.focus_up(); },
key!(KeyCode::Down) => { self.focus_down(); },
key!(KeyCode::Left) => { self.focus_left(); },
key!(KeyCode::Right) => { self.focus_right(); },
// Global play/pause binding
key!(KeyCode::Char(' ')) => match self.transport {
Some(ref mut transport) => { transport.write().unwrap().toggle_play()?; },
None => { return Ok(None) }
},
_ => {}
}
};
Ok(Some(true))
}
}
@ -81,14 +55,6 @@ impl Content for PhrasePool<Tui> {
.fg(Color::Rgb(70, 80, 50))))
}
}
impl Focusable<Tui> for PhrasePool<Tui> {
fn is_focused (&self) -> bool {
self.focused
}
fn set_focused (&mut self, focused: bool) {
self.focused = focused
}
}
impl Handle<Tui> for PhrasePool<Tui> {
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
match from.event() {
@ -225,14 +191,6 @@ impl Handle<Tui> for PhraseEditor<Tui> {
return Ok(Some(true))
}
}
impl Focusable<Tui> for PhraseEditor<Tui> {
fn is_focused (&self) -> bool {
self.focused
}
fn set_focused (&mut self, focused: bool) {
self.focused = focused
}
}
impl PhraseEditor<Tui> {
const H_KEYS_OFFSET: usize = 5;
/// Select which pattern to display. This pre-renders it to the buffer at full resolution.

View file

@ -1,5 +1,4 @@
use crate::*;
impl TransportToolbar<Tui> {
fn handle_bpm (&mut self, from: &TuiInput) -> Perhaps<bool> {
match from.event() {
@ -28,7 +27,6 @@ impl TransportToolbar<Tui> {
return Ok(Some(true))
}
}
impl Handle<Tui> for TransportToolbar<Tui> {
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
match from.event() {
@ -48,7 +46,6 @@ impl Handle<Tui> for TransportToolbar<Tui> {
Ok(Some(true))
}
}
impl Content for TransportToolbar<Tui> {
type Engine = Tui;
fn content (&self) -> impl Widget<Engine = Tui> {
@ -89,16 +86,6 @@ impl Content for TransportToolbar<Tui> {
).fill_x().bg(Color::Rgb(40, 50, 30))
}
}
impl Focusable<Tui> for TransportToolbar<Tui> {
fn is_focused (&self) -> bool {
self.focused
}
fn set_focused (&mut self, focused: bool) {
self.focused = focused
}
}
impl TransportToolbarFocus {
pub fn wrap <'a, W: Widget<Engine = Tui>> (
self, parent_focus: bool, focus: Self, widget: &'a W