From ec0b00d62f5fe02248de71552f5cd3d76589295c Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Sun, 10 Oct 2021 10:46:23 -0300 Subject: "Loop mode" option: no loop, loop, shuffle This also reorganizes the menubar options a little. --- backend.js | 18 ++++++++++----- todo.txt | 11 +++++++++ ui.js | 75 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++------ 3 files changed, 92 insertions(+), 12 deletions(-) diff --git a/backend.js b/backend.js index e38fe2f..41107d7 100644 --- a/backend.js +++ b/backend.js @@ -66,7 +66,7 @@ class QueuePlayer extends EventEmitter { this.playingTrack = null this.queueGrouplike = {name: 'Queue', isTheQueue: true, items: []} this.pauseNextTrack = false - this.loopQueueAtEnd = false + this.queueEndMode = 'end' // end, loop, shuffle this.playedTrackToEnd = false this.timeData = null @@ -435,10 +435,18 @@ class QueuePlayer extends EventEmitter { if (playingThisTrack) { this.playedTrackToEnd = true if (!this.playNext(item)) { - if (this.loopQueueAtEnd) { - this.playFirst() - } else { - this.clearPlayingTrack() + switch (this.queueEndMode) { + case 'loop': + this.playFirst() + break + case 'shuffle': + this.clearPlayingTrack() + this.shuffleQueue() + this.playFirst() + break + case 'end': + default: + this.clearPlayingTrack() } } } diff --git a/todo.txt b/todo.txt index af31659..90ed41b 100644 --- a/todo.txt +++ b/todo.txt @@ -637,3 +637,14 @@ TODO: Timestamps which have a timestampEnd property (all of them I think?) TODO: Read timestamps as JSON when the file extension is .json. (Right now any .timestamps.json file is ignored!) + +TODO: "Remove from queue" seems to always restore the cursor to a non-timestamp + input. This might be an issue with other queue-modifying actions too! + +TODO: The "From: " text in the playback info element *does* cut + off its text in an attempt to not go outside the screen bounds... but it + goes over the info pane edges anyway, so there's probably a math issue + there. + +TODO: "Play later" has a slight chance of keeping the track in the same place, + which is accentuated when there's only a couple tracks left in the queue. diff --git a/ui.js b/ui.js index 1331344..bf67b8e 100644 --- a/ui.js +++ b/ui.js @@ -351,12 +351,13 @@ class AppElement extends FocusElement { return [ {label: playingTrack ? `("${playingTrack.name}")` : '(No track playing.)'}, {divider: true}, + {element: this.loopModeControl}, + {element: this.volumeSlider}, + {divider: true}, playingTrack && {element: this.playingControl}, - {element: this.loopingControl}, - {element: this.loopQueueControl}, - {element: this.pauseNextControl}, + playingTrack && {element: this.loopingControl}, + playingTrack && {element: this.pauseNextControl}, {element: this.autoDJControl}, - {element: this.volumeSlider}, {divider: true}, previous && {label: `Previous (${previous.name})`, action: () => this.SQP.playPrevious(playingTrack)}, next && {label: `Next (${next.name})`, action: () => this.SQP.playNext(playingTrack)}, @@ -403,6 +404,20 @@ class AppElement extends FocusElement { getEnabled: () => this.config.canControlPlayback }) + this.loopModeControl = new InlineListPickerElement('Loop mode', [ + {value: 'end', label: 'Don\'t loop'}, + {value: 'loop', label: 'Loop (same order)'}, + {value: 'shuffle', label: 'Loop (shuffle)'} + ], { + setValue: val => { + if (this.SQP) { + this.SQP.queueEndMode = val + } + }, + getValue: () => this.SQP && this.SQP.queueEndMode, + showContextMenu: this.showContextMenu + }) + this.pauseNextControl = new ToggleControl('Pause when this track ends?', { setValue: val => this.SQP.setPauseNextTrack(val), getValue: () => this.SQP.pauseNextTrack, @@ -3153,13 +3168,27 @@ class InlineListPickerElement extends FocusElement { // next or previous. (That's the point, it's inline.) This element is mainly // useful in forms or ContextMenus. - constructor(labelText, options, showContextMenu = null) { + constructor(labelText, options, optsOrShowContextMenu = null) { super() + this.labelText = labelText this.options = options - this.showContextMenu = showContextMenu - this.curIndex = 0 + + if (typeof optsOrShowContextMenu === 'function') { + this.showContextMenu = optsOrShowContextMenu + } + + if (typeof optsOrShowContextMenu === 'object') { + const opts = optsOrShowContextMenu + this.showContextMenu = opts.showContextMenu + this.getValue = opts.getValue + this.setValue = opts.setValue + } + this.keyboardIdentifier = this.labelText + + this.curIndex = 0 + this.refreshValue() } fixLayout() { @@ -3244,11 +3273,26 @@ class InlineListPickerElement extends FocusElement { return false } + + refreshValue() { + if (this.getValue) { + const value = this.getValue() + const index = this.options.findIndex(opt => opt.value === value) + if (index >= 0) { + this.curIndex = index + } + } + } + nextOption() { this.curIndex++ if (this.curIndex === this.options.length) { this.curIndex = 0 } + + if (this.setValue) { + this.setValue(this.curValue) + } } previousOption() { @@ -3256,6 +3300,10 @@ class InlineListPickerElement extends FocusElement { if (this.curIndex < 0) { this.curIndex = this.options.length - 1 } + + if (this.setValue) { + this.setValue(this.curValue) + } } get curValue() { @@ -3453,6 +3501,9 @@ class ToggleControl extends FocusElement { } + // Note: ToggleControl doesn't specify refreshValue because it doesn't have an + // internal state for the current value. It sets and draws based on the value + // getter provided externally. toggle() { this.setValue(!this.getValue()) } @@ -4716,6 +4767,16 @@ class ContextMenu extends FocusElement { return } + // Call refreshValue() on any items before they're shown, for items that + // provide it. (This is handy when reusing the same input across a menu that + // might be shown under different contexts.) + for (const item of items) { + const el = item.element + if (!el) continue + if (!el.refreshValue) continue + el.refreshValue() + } + if (!this.root.selectedElement.directAncestors.includes(this)) { this.selectedBefore = this.root.selectedElement } -- cgit 1.3.0-6-gf8a5