Files
stemify/desktop/stemify/player/qml/StemPlayer/Ribbon.qml
T

291 lines
14 KiB
QML

import QtQuick
import QtQuick.Controls.Basic
// Tabs on the green strip, commands on the gray ribbon below.
Column {
id: rib
property var win: Window.window
property int tab: 1 // 0 = File (backstage), 1 Home, 2 Insert, 3 Data, 4 View
readonly property var tabs: ["File", "Home", "Insert", "Data", "View"]
function focusSearch() { tell.startEditing() }
Rectangle {
width: parent.width
height: 28
color: X.green
Row {
x: 0
height: parent.height
Repeater {
model: rib.tabs
delegate: Item {
id: tabItem
required property int index
required property string modelData
readonly property bool file: index === 0
readonly property bool active: rib.tab === index && !file
width: tl.implicitWidth + (file ? 30 : 22)
height: 28
Rectangle {
anchors.fill: parent
anchors.topMargin: tabItem.active ? 2 : 0
color: tabItem.active ? X.ribbon : tabItem.file ? (tm.containsMouse ? "#0E4B2A" : X.greenDark)
: tm.containsMouse ? X.greenHover : "transparent"
}
T {
id: tl
anchors.centerIn: parent
text: tabItem.modelData
color: tabItem.active ? X.greenText : "#FFFFFF"
}
MouseArea {
id: tm
anchors.fill: parent
hoverEnabled: true
onClicked: tabItem.file ? rib.win.openBackstage("info") : rib.tab = tabItem.index
}
}
}
// "Tell me what you want to do": here it searches the library.
Item {
id: tell
width: 260
height: 28
function startEditing() { field.visible = true; field.forceActiveFocus(); field.selectAll() }
Ico { x: 12; anchors.verticalCenter: parent.verticalCenter; name: "lightbulb"; ink: "#FFFFFF"; size: 16 }
T {
x: 34
anchors.verticalCenter: parent.verticalCenter
visible: !field.visible
color: "#FFFFFF"
text: "Tell me what you want to listen to..."
}
MouseArea { anchors.fill: parent; cursorShape: Qt.IBeamCursor; onClicked: tell.startEditing() }
XField {
id: field
visible: false
x: 32
width: 220
anchors.verticalCenter: parent.verticalCenter
placeholderText: "Song, artist or album"
onAccepted: { library.search(text); visible = false; rib.win.focusGrid() }
onActiveFocusChanged: if (!activeFocus && text === "") visible = false
Keys.onEscapePressed: { text = ""; visible = false; rib.win.focusGrid() }
}
}
}
}
Rectangle {
width: parent.width
height: 94
color: X.ribbon
Rectangle { anchors.bottom: parent.bottom; width: parent.width; height: 1; color: X.ribbonLine }
// ---- Home ----
Row {
visible: rib.tab === 1
height: parent.height - 2
RGroup {
title: "Playback"
RButton {
big: true
icon: player.status === "playing" ? "pause" : "play"
text: player.status === "playing" ? "Pause" : "Play"
tip: "Play or pause (Space)"
onClicked: player.toggle()
}
Column {
RButton { icon: "previous"; text: "Previous"; tip: "Previous song (Ctrl+Left)"; onClicked: player.previous() }
RButton { icon: "next"; text: "Next"; tip: "Next song (Ctrl+Right)"; onClicked: player.next() }
RButton { icon: "stop"; text: "Stop"; onClicked: { engine.pause(); player.seek(0) } }
}
Column {
RButton { icon: "shuffle"; text: "Shuffle"; checked: player.shuffleOn; onClicked: player.toggleShuffle() }
RButton {
icon: player.repeatMode === "one" ? "repeat_one" : "repeat"
text: player.repeatMode === "one" ? "Repeat One" : "Repeat"
checked: player.repeatMode !== "off"
onClicked: player.cycleRepeat()
}
}
}
RGroup {
title: "Karaoke"
RButton {
big: true
enabled: player.sourceKind === "stems"
icon: player.presetLabel === "Karaoke" ? "vocals_off" : "vocals"
text: player.presetLabel === "Karaoke" ? "Vocals\nOff" : "Vocals\nOn"
checked: player.presetLabel === "Karaoke"
tip: "Mute the vocals (V)"
onClicked: player.toggleVocals()
}
}
RGroup {
title: "Mix Styles"
Grid {
columns: 4
rowSpacing: 3
columnSpacing: 3
y: 4
Repeater {
model: player.presets
delegate: Rectangle {
id: sty
required property int index
required property var modelData
readonly property var s: X.styles[index]
width: 82
height: 28
opacity: modelData.usable ? 1 : 0.35
color: s.fill
border.width: modelData.active ? 2 : 1
border.color: modelData.active ? X.green : (sm.containsMouse ? "#8A8A8A" : s.border)
T {
anchors.fill: parent
anchors.leftMargin: 5
font.family: cellFont
font.pixelSize: 14
font.bold: sty.s.bold
color: sty.s.text
text: sty.modelData.label
}
MouseArea {
id: sm
anchors.fill: parent
hoverEnabled: true
enabled: sty.modelData.usable
onClicked: player.applyPreset(sty.index)
}
Tip { target: sm; text: "Style: " + sty.s.name + " — " + sty.modelData.label }
}
}
}
}
RGroup {
title: "Cells"
RButton { big: true; icon: "play_next"; text: "Play\nNext"; tip: "Play the selection after this song"; onClicked: library.playNextRows(rib.win.selTop, rib.win.selBottom) }
RButton { big: true; icon: "queue_add"; text: "Add to\nQueue"; tip: "Add the selection to the end of the queue"; onClicked: library.queueRows(rib.win.selTop, rib.win.selBottom) }
RButton {
big: true
icon: "delete_rows"
text: "Delete\nRows"
enabled: library.layout === "queue" || library.sheetTitle !== "" && rib.win.inPlaylist
tip: "Remove the selected rows from the queue or playlist (Delete)"
onClicked: library.removeRows(rib.win.selTop, rib.win.selBottom)
}
}
RGroup {
title: "Editing"
Column {
RButton { icon: "sigma"; text: "AutoShuffle"; tip: "Shuffle everything on this sheet"; onClicked: library.shuffleSheet() }
RButton { icon: "sort_az"; text: "Sort & Filter"; dropdown: true; onClicked: sortMenu.popup() }
RButton { icon: "search"; text: "Find & Select"; tip: "Search the library (Ctrl+F)"; onClicked: rib.focusSearch() }
}
}
}
// ---- Insert ----
Row {
visible: rib.tab === 2
height: parent.height - 2
RGroup {
title: "Playlists"
RButton { big: true; icon: "playlist_new"; text: "New\nPlaylist"; tip: "A Plex playlist from the selected rows"; onClicked: rib.win.newPlaylist() }
RButton { big: true; icon: "playlist_add"; text: "Add to\nPlaylist"; dropdown: true; onClicked: plMenu.popup() }
}
RGroup {
title: "Queue"
RButton { big: true; icon: "play_next"; text: "Play\nNext"; onClicked: library.playNextRows(rib.win.selTop, rib.win.selBottom) }
RButton { big: true; icon: "queue_add"; text: "Add to\nQueue"; onClicked: library.queueRows(rib.win.selTop, rib.win.selBottom) }
RButton { big: true; icon: "play"; text: "Play\nSelection"; onClicked: library.playRows(rib.win.selTop, rib.win.selBottom) }
}
RGroup {
title: "Charts"
RButton { big: true; icon: "chart"; text: "Stem\nChart"; checked: rib.win.showMixer; tip: "Show the stem mixer pane"; onClicked: rib.win.toggleMixer() }
}
}
// ---- Data ----
Row {
visible: rib.tab === 3
height: parent.height - 2
RGroup {
title: "Get External Data"
RButton { big: true; icon: "plex"; text: library.signedIn ? "Plex\nAccount" : "Connect\nto Plex"; onClicked: rib.win.openBackstage("account") }
RButton { big: true; icon: "server"; text: "Connections"; tip: "Server addresses and folders"; onClicked: rib.win.openBackstage("options") }
}
RGroup {
title: "Connections"
RButton { big: true; icon: "refresh"; text: "Refresh\nAll"; tip: "Reload the Plex library and the stems index"; onClicked: library.refresh() }
Column {
y: 4
spacing: 2
T { text: "Songs: " + library.counts.songs.toLocaleString(Qt.locale(), "f", 0); height: 20 }
T { text: "With stems: " + library.counts.stems.toLocaleString(Qt.locale(), "f", 0); height: 20 }
T {
text: "Stem server: " + (library.counts.stemOnline === true ? "Online" : library.counts.stemOnline === false ? "Offline" : "Checking…")
color: library.counts.stemOnline === true ? X.goodText : library.counts.stemOnline === false ? X.badText : X.inkSoft
height: 20
}
}
}
RGroup {
title: "Sort & Filter"
Column {
RButton { icon: "sort_az"; text: "Sort A to Z"; onClicked: rib.win.sortCurrent(false) }
RButton { icon: "sort_za"; text: "Sort Z to A"; onClicked: rib.win.sortCurrent(true) }
RButton { icon: "filter"; text: "Stems Only"; tip: "Songs that have stems"; onClicked: library.current = 4 }
}
}
}
// ---- View ----
Row {
visible: rib.tab === 4
height: parent.height - 2
RGroup {
title: "Show"
Column {
y: 2
XCheck { text: "Gridlines"; checked: rib.win.gridlines; onToggled: rib.win.setOption("gridlines", !rib.win.gridlines) }
XCheck { text: "Headings"; checked: rib.win.headings; onToggled: rib.win.setOption("headings", !rib.win.headings) }
XCheck { text: "Mixer Pane"; checked: rib.win.showMixer; onToggled: rib.win.toggleMixer() }
}
}
RGroup {
title: "Zoom"
RButton { big: true; icon: "zoom"; text: "Zoom"; tip: "Ctrl+scroll on the grid"; onClicked: rib.win.setZoom(rib.win.zoom >= 150 ? 100 : rib.win.zoom + 25) }
RButton { big: true; icon: "grid"; text: "100%"; onClicked: rib.win.setZoom(100) }
}
RGroup {
title: "Window"
RButton { big: true; icon: "freeze"; text: "Freeze\nPanes"; checked: true; tip: "The header row is always frozen" }
}
}
}
XMenu {
id: sortMenu
Action { text: "Sort A to Z"; icon.name: "sort_az"; onTriggered: rib.win.sortCurrent(false) }
Action { text: "Sort Z to A"; icon.name: "sort_za"; onTriggered: rib.win.sortCurrent(true) }
MenuSeparator {}
Action { text: "Filter: Stems Only"; icon.name: "filter"; onTriggered: library.current = 4 }
}
XMenu {
id: plMenu
Instantiator {
model: library.playlistNames
delegate: MenuItem {
required property int index
required property string modelData
text: modelData
onTriggered: rib.win.addToPlaylist(modelData)
}
onObjectAdded: (i, o) => plMenu.insertItem(i, o)
onObjectRemoved: (i, o) => plMenu.removeItem(o)
}
}
}