« get me outta code hell

Rework input system, support number pad - mtui - Music Text User Interface - user-friendly command line music player
about summary refs log tree commit diff
diff options
context:
space:
mode:
authorFlorrie <towerofnix@gmail.com>2019-04-17 10:40:56 -0300
committerFlorrie <towerofnix@gmail.com>2019-04-17 10:40:56 -0300
commit8feefa78118cdfe9a98198786a0315053dc098a1 (patch)
treed99586568cb1f385814e7131d4f8d10ab2c6a1fd
parent68fce2031ce323b57a20998d718f8f81026f0e4b (diff)
Rework input system, support number pad
Also changed focus main/queue listings to [ and ], so that we can use
the 2 key for the down arrow on a number pad.
-rw-r--r--README.md15
-rw-r--r--ui.js128
2 files changed, 112 insertions, 31 deletions
diff --git a/README.md b/README.md
index d66a65b..1d444b0 100644
--- a/README.md
+++ b/README.md
@@ -27,8 +27,8 @@ You're also welcome to share any ideas, suggestions, and questions through there
 * <kbd>Up</kbd> and <kbd>Down</kbd> - select previous or next item in a listing
 * <kbd>Tab</kbd> and <kbd><kbd>Shift</kbd>+<kbd>Tab</kbd></kbd> - switch between UI elements
 * <kbd>g</kbd> and <kbd>G</kbd> (shift+G) - move to the first and last item in a listing
-* <kbd>1</kbd> - focus the main track/group listing
-* <kbd>2</kbd> - focus the queue listing
+* <kbd>[</kbd> - focus the main track/group listing
+* <kbd>]</kbd> - focus the queue listing
 * <kbd>Enter</kbd> - play the selected track
 * <kbd><kbd>Shift</kbd>+<kbd>Up</kbd></kbd> or <kbd>p</kbd> - play previous track
 * <kbd><kbd>Shift</kbd>+<kbd>Down</kbd></kbd> or <kbd>n</kbd> - play next track
@@ -37,6 +37,7 @@ You're also welcome to share any ideas, suggestions, and questions through there
 * <kbd>l</kbd> - toggle track loop
 * <kbd>Right</kbd> - seek ahead
 * <kbd>Left</kbd> - seek back
+* <kbd>m</kbd> or <kbd>f</kbd> - open a menu of actions related to the selected track or group
 * <kbd>v</kbd> and <kbd>V</kbd> - increase and decrease playback volume
 * <kbd><kbd>Ctrl</kbd>+<kbd>F</kbd></kbd> or <kbd>/</kbd> - jump to a track or group by entering (part of) its name
 * <kbd><kbd>Ctrl</kbd>+<kbd>O</kbd></kbd> - open a playlist from a source (like a /path/to/a/folder or a YouTube playlist URL) (you can also just pass this source to `mtui`)
@@ -46,7 +47,6 @@ You're also welcome to share any ideas, suggestions, and questions through there
 * **In the main listing:**
   * <kbd>Enter</kbd> - if the selected item is a group, enter it; if it's a track, play it
   * <kbd>Backspace</kbd> - leave the current group (if in one)
-  * <kbd>m</kbd> or <kbd>f</kbd> - open a menu of actions related to the selected track or group
   * <kbd>q</kbd> - queue the selected track or group to play after any other items in the queue (usually after the current track)
   * <kbd>Q</kbd> (shift+Q) - queue the selected track or group to play immediately after what's currently playing
   * <kbd>d</kbd> - download the selected track (but don't play it)
@@ -62,3 +62,12 @@ You're also welcome to share any ideas, suggestions, and questions through there
   * All the usual controls (up, down, tab, space, enter) work
   * <kbd>Backspace</kbd> or <kbd>Escape</kbd> - close the menu without taking any action
   * Type the beginning of the name of an option, like its first letter, to quickly jump to it
+* **With a number pad:**
+  * Should be in NumLock!
+  * <kbd>2</kbd>, <kbd>4</kbd>, <kbd>6</kbd>, <kbd>8</kbd> - cursor keys (down/left/right/up)
+  * <kbd>5</kbd> - pause (and any action that would take the space bar)
+  * <kbd>7</kbd>, <kbd>9</kbd> - focus main track/group, queue listings
+  * <kbd>/<kbd>, <kbd>\*</kbd> - decrease, increase playback volume
+  * <kbd>.</kbd> - back (and any action that would take backspace)
+  * <kbd>+</kbd> - open a context menu
+  * <kbd>1</kbd>, <kbd>3</kbd> - skip to previous, next track
diff --git a/ui.js b/ui.js
index e9128a3..049930b 100644
--- a/ui.js
+++ b/ui.js
@@ -35,18 +35,90 @@ const { promisify } = require('util')
 const readFile = promisify(fs.readFile)
 const writeFile = promisify(fs.writeFile)
 
-// Sneaky hack :)
-const addKey = (prop, key) => {
-  const oldFunc = telc[prop]
-  telc[prop] = input => input.toString().toLowerCase() === key || oldFunc(input)
+const input = {}
+
+const keyBindings = [
+  ['isUp', telc.isUp],
+  ['isDown', telc.isDown],
+  ['isLeft', telc.isLeft],
+  ['isRight', telc.isRight],
+  ['isSelect', telc.isSelect],
+  ['isBackspace', telc.isBackspace],
+  ['isMenu', 'm'],
+  ['isMenu', 'f'],
+  ['isTogglePause', telc.isSpace],
+  ['isToggleLoop', 'l'],
+  ['isStop', telc.isEscape],
+  ['isVolumeUp', 'v', {caseless: false}],
+  ['isVolumeDown', 'V', {caseless: false}],
+  ['isSkipBack', telc.isShiftUp],
+  ['isSkipAhead', telc.isShiftDown],
+  ['isSkipBack', 'p'],
+  ['isSkipAhead', 'n'],
+  ['isFocusTabber', '['],
+  ['isFocusQueue', ']'],
+  ['isNextTab', 't', {caseless: false}],
+  ['isPreviousTab', 'T', {caseless: false}],
+  ['isDownload', 'd'],
+  ['isRemove', 'x'],
+  ['isQueueAtEnd', 'q', {caseless: false}],
+  ['isQueueAtStart', 'Q', {caseless: false}],
+  ['isShuffleQueue', 's'],
+  ['isClearQueue', 'c'],
+
+  // Number pad
+  ['isUp', '8'],
+  ['isDown', '2'],
+  ['isLeft', '4'],
+  ['isRight', '6'],
+  ['isSpace', '5'],
+  ['isTogglePause', '5'],
+  ['isBackspace', '.'],
+  ['isMenu', '+'],
+  ['isSkipBack', '1'],
+  ['isSkipAhead', '3'],
+  ['isVolumeDown', '/'],
+  ['isVolumeUp', '*'],
+  ['isFocusTabber', '7'],
+  ['isFocusQueue', '9'],
+
+  // HJKL
+  ['isDown', 'j'],
+  ['isUp', 'k'],
+  // Don't use these for now... currently L is used for toggling loop.
+  // May want to look into changing that (so we can re-enable these).
+  // ['isLeft', 'h'],
+  // ['isRight', 'l'],
+]
+
+const addKey = (prop, keyOrFunc, {caseless = true} = {}) => {
+  const oldFunc = input[prop] || (() => false)
+  let newFunc
+  if (typeof keyOrFunc === 'function') {
+    newFunc = keyOrFunc
+  } else if (typeof keyOrFunc === 'string') {
+    const key = keyOrFunc
+    if (caseless) {
+      newFunc = input => input.toString().toLowerCase() === key.toLowerCase()
+    } else {
+      newFunc = input => input.toString() === key
+    }
+  }
+  input[prop] = keyBuf => newFunc(keyBuf) || oldFunc(keyBuf)
 }
 
-addKey('isDown', 'j')
-addKey('isUp', 'k')
-// Don't use these for now... currently L is used for toggling loop.
-// May want to look into changing that (so we can re-enable these).
-// addKey('isLeft', 'h')
-// addKey('isRight', 'l')
+for (const entry of keyBindings) {
+  addKey(...entry)
+}
+
+// Some things just need to be overridden in order for the rest of tui-lib to
+// recognize our new keys.
+telc.isUp = input.isUp
+telc.isDown = input.isDown
+telc.isLeft = input.isLeft
+telc.isRight = input.isRight
+telc.isSelect = input.isSelect
+telc.isBackspace = input.isBackspace
 
 class AppElement extends FocusElement {
   constructor() {
@@ -434,27 +506,27 @@ class AppElement extends FocusElement {
       return
     }
 
-    if (telc.isRight(keyBuf)) {
+    if (input.isRight(keyBuf)) {
       this.seekAhead(10)
-    } else if (telc.isLeft(keyBuf)) {
+    } else if (input.isLeft(keyBuf)) {
       this.seekBack(10)
-    } else if (telc.isSpace(keyBuf)) {
+    } else if (input.isTogglePause(keyBuf)) {
       this.togglePause()
-    } else if (telc.isCaselessLetter(keyBuf, 'l')) {
+    } else if (input.isToggleLoop(keyBuf)) {
       this.toggleLoop()
-    } else if (telc.isCharacter(keyBuf, 'v')) {
+    } else if (input.isVolumeUp(keyBuf)) {
       this.volUp()
-    } else if (telc.isCharacter(keyBuf, 'V')) {
+    } else if (input.isVolumeDown(keyBuf)) {
       this.volDown()
-    } else if (telc.isEscape(keyBuf)) {
+    } else if (input.isStop(keyBuf)) {
       this.clearPlayingTrack()
-    } else if (telc.isShiftUp(keyBuf) || telc.isCaselessLetter(keyBuf, 'p')) {
+    } else if (input.isSkipBack(keyBuf)) {
       this.playPreviousTrack(this.playingTrack, true)
-    } else if (telc.isShiftDown(keyBuf) || telc.isCaselessLetter(keyBuf, 'n')) {
+    } else if (input.isSkipAhead(keyBuf)) {
       this.playNextTrack(this.playingTrack, true)
-    } else if (telc.isCharacter(keyBuf, '1') && this.tabber.selectable) {
+    } else if (input.isFocusTabber(keyBuf) && this.tabber.selectable) {
       this.root.select(this.tabber)
-    } else if (telc.isCharacter(keyBuf, '2') && this.queueListingElement.selectable) {
+    } else if (input.isFocusQueue(keyBuf) && this.queueListingElement.selectable) {
       this.root.select(this.queueListingElement)
     } else if (keyBuf.equals(Buffer.from([5]))) { // Ctrl-E
       this.editMode = !this.editMode
@@ -1597,11 +1669,11 @@ class InteractiveGrouplikeItemElement extends BasicGrouplikeItemElement {
   }
 
   keyPressed(keyBuf) {
-    if (telc.isCaselessLetter(keyBuf, 'd')) {
+    if (input.isDownload(keyBuf)) {
       this.emit('download')
-    } else if (telc.isCharacter(keyBuf, 'q')) {
+    } else if (input.isQueueAtEnd(keyBuf)) {
       this.emit('queue', {where: 'end'})
-    } else if (telc.isCharacter(keyBuf, 'Q')) {
+    } else if (input.isQueueAtStart(keyBuf)) {
       this.emit('queue', {where: 'next'})
     } else if (telc.isEnter(keyBuf)) {
       if (isGroup(this.item)) {
@@ -1609,9 +1681,9 @@ class InteractiveGrouplikeItemElement extends BasicGrouplikeItemElement {
       } else {
         this.emit('queue', {where: 'next', play: true})
       }
-    } else if (telc.isCaselessLetter(keyBuf, 'x')) {
+    } else if (input.isRemove(keyBuf)) {
       this.emit('remove')
-    } else if (telc.isCaselessLetter(keyBuf, 'm') || telc.isCaselessLetter(keyBuf, 'f')) {
+    } else if (input.isMenu(keyBuf)) {
       this.emit('menu', this)
     }
   }
@@ -1800,9 +1872,9 @@ class QueueListingElement extends GrouplikeListingElement {
   }
 
   keyPressed(keyBuf) {
-    if (telc.isCaselessLetter(keyBuf, 's')) {
+    if (input.isShuffleQueue(keyBuf)) {
       this.emit('shuffle')
-    } else if (telc.isCaselessLetter(keyBuf, 'c')) {
+    } else if (input.isClearQueue(keyBuf)) {
       this.emit('clear')
     } else {
       return super.keyPressed(keyBuf)