« get me outta code hell

mtui - Music Text User Interface - user-friendly command line music player
about summary refs log tree commit diff
path: root/ui.js
diff options
context:
space:
mode:
Diffstat (limited to 'ui.js')
-rw-r--r--ui.js136
1 files changed, 136 insertions, 0 deletions
diff --git a/ui.js b/ui.js
index eb6b8f9..1bb74fc 100644
--- a/ui.js
+++ b/ui.js
@@ -235,6 +235,7 @@ class AppElement extends FocusElement {
           {divider: true},
           this.playingTrack && {element: this.playingControl},
           {element: this.loopingControl},
+          {element: this.volumeSlider},
           (next || previous) && {divider: true},
           previous && {label: `Previous (${previous.name})`, action: () => this.playPreviousTrack(this.playingTrack)},
           next && {label: `Next (${next.name})`, action: () => this.playNextTrack(this.playingTrack)},
@@ -263,6 +264,11 @@ class AppElement extends FocusElement {
       setValue: val => this.setLoop(val),
       getValue: () => this.player.isLooping
     })
+
+    this.volumeSlider = new SliderElement('Volume', {
+      setValue: val => this.setVolume(val),
+      getValue: () => this.player.volume
+    })
   }
 
   selected() {
@@ -720,6 +726,10 @@ class AppElement extends FocusElement {
     this.player.volDown(amount)
   }
 
+  setVolume(value) {
+    this.player.setVolume(value)
+  }
+
   stopPlaying() {
     // We emit this so playTrack doesn't immediately start a new track.
     // We aren't *actually* about to play a new track.
@@ -1822,6 +1832,132 @@ class InlineListPickerElement extends FocusElement {
   }
 }
 
+class SliderElement extends FocusElement {
+  // Same general principle and usage as InlineListPickerElement, but for
+  // changing a numeric value.
+
+  constructor(labelText, {setValue, getValue, maxValue = 100, percent = true}) {
+    super()
+    this.labelText = labelText
+    this.setValue = setValue
+    this.getValue = getValue
+    this.maxValue = maxValue
+    this.percent = percent
+    this.keyboardIdentifier = this.labelText
+  }
+
+  fixLayout() {
+    const idealWidth = ansi.measureColumns(
+      this.labelText +
+      '  ' + this.getValueString(this.maxValue) +
+      ' ' + this.getNumString(this.maxValue) +
+      '  '
+    )
+
+    this.w = Math.max(this.parent.contentW, idealWidth)
+    this.h = 1
+  }
+
+  drawTo(writable) {
+    if (this.isSelected) {
+      writable.write(ansi.invert())
+    }
+
+    let drawX = 0
+    writable.write(ansi.moveCursor(this.absTop, this.absLeft))
+
+    writable.write(this.labelText + ' ')
+    drawX += ansi.measureColumns(this.labelText) + 1
+
+    writable.write(ansi.setAttributes([ansi.A_BRIGHT, ansi.C_BLUE]))
+    writable.write(' ')
+    drawX += 1
+
+    const valueString = this.getValueString(this.getValue())
+    writable.write(valueString)
+    drawX += valueString.length
+
+    const numString = this.getNumString(this.getValue())
+    writable.write(' ' + numString + ' ')
+    drawX += numString.length + 2
+
+    writable.write(ansi.setForeground(ansi.C_RESET))
+    writable.write(' '.repeat(Math.max(0, this.w - drawX)))
+
+    writable.write(ansi.resetAttributes())
+  }
+
+  getValueString(value) {
+    const maxLength = 10
+
+    let length = Math.round(value / this.maxValue * maxLength)
+
+    if (value < this.maxValue && length === maxLength) {
+      length--
+    }
+
+    if (value > 0 && length === 0) {
+      length++
+    }
+
+    return (
+      '[' +
+      '-'.repeat(length) +
+      ' '.repeat(maxLength - length) +
+      ']'
+    )
+  }
+
+  getNumString(value) {
+    const maxValueString = Math.round(this.maxValue).toString()
+    const valueString = Math.round(value).toString()
+    const paddedString = valueString.padStart(maxValueString.length)
+
+    return paddedString + (this.percent ? '%' : '')
+  }
+
+  keyPressed(keyBuf) {
+    if (telc.isRight(keyBuf)) {
+      this.increment()
+    } else if (telc.isLeft(keyBuf)) {
+      this.decrement()
+    } else {
+      return true
+    }
+    return false
+  }
+
+  clicked(button) {
+    if (button === 'left') {
+      if (this.isSelected) {
+        if (this.getValue() === this.maxValue) {
+          this.setValue(0)
+        } else {
+          this.increment()
+        }
+      } else {
+        this.root.select(this)
+      }
+    } else if (button === 'scroll-up') {
+      this.increment()
+    } else if (button === 'scroll-down') {
+      this.decrement()
+    }
+  }
+
+  increment() {
+    this.setValue(this.getValue() + this.step)
+  }
+
+  decrement() {
+    this.setValue(this.getValue() - this.step)
+  }
+
+  get step() {
+    return this.maxValue / 10
+  }
+}
+
 class ToggleControl extends FocusElement {
   constructor(labelText, {setValue, getValue}) {
     super()