Buttons that rest on surfaceElevated used surfaceHover for their hover fill: a 6% white wash meant for rows that rest transparent. Over an elevated button it lands DARKER than the rest fill — on a modal's dark ground the Cancel/Save/footer buttons went near-black under the pointer. QbzTheme gains surfaceElevatedHover (a lift of surfaceElevated, derived so every palette has it), its ambient half-alpha twin, and the elevatedFill / elevatedHoverFill pair an ambient-aware control paints. Every site with the old pairing (65 across modals, toolbars, selects, circle actions, chips and cards) now hovers to the lift. cc
662 lines
29 KiB
QML
662 lines
29 KiB
QML
// LibraryToolbar — two rows: navigation/actions/selects above, an always-open
|
|
// search and independent opt-in filter switches below. Narrow panes scroll
|
|
// each control row horizontally instead of covering neighbouring controls.
|
|
//
|
|
// EXTRACTED from views/LibraryView.qml (track rule 2 — the file was 1,881
|
|
// lines). Everything it reads and writes goes through `view`, the LibraryView
|
|
// instance, exactly as views/local/LocalToolbar.qml does for LocalLibraryView.
|
|
// Toolbar choices are written through `view.setPref(...)` rather than straight
|
|
// onto the property, because they PERSIST (library_prefs.rs -> the same
|
|
// favorites_ui.json the Slint build writes).
|
|
|
|
import QtQuick
|
|
import com.blitzfc.qbz
|
|
import "../../controls"
|
|
import "../../theme"
|
|
import "../../assets/release-sort.js" as ReleaseSort
|
|
|
|
Item {
|
|
id: root
|
|
|
|
readonly property var sortOptions: {
|
|
var tab = view.activeTab
|
|
var options = []
|
|
function add(value, label) { options.push({value: value, label: label}) }
|
|
// Every tab now leads with an explicit Default (the resting order),
|
|
// so there is always a clear way back to the un-sorted state.
|
|
add("default", QbzSession.tr("Default", QbzSession.trRev))
|
|
add("date", QbzSession.tr("Date added", QbzSession.trRev))
|
|
if (tab === "playlists") add("updated", QbzSession.tr("Date updated", QbzSession.trRev))
|
|
add("title", QbzSession.tr("Alphabetical", QbzSession.trRev))
|
|
if (tab === "tracks") add("release", QbzSession.tr("Release", QbzSession.trRev))
|
|
if (["all", "tracks", "albums"].indexOf(tab) >= 0)
|
|
add("artist", QbzSession.tr("Artist", QbzSession.trRev))
|
|
if (tab === "tracks" || tab === "albums") {
|
|
add("label", QbzSession.tr("Label", QbzSession.trRev))
|
|
add("genre", QbzSession.tr("Genre", QbzSession.trRev))
|
|
add("release-date", QbzSession.tr("Release date", QbzSession.trRev))
|
|
}
|
|
if (tab === "playlists") add("track-count", QbzSession.tr("Track count", QbzSession.trRev))
|
|
if (["tracks", "albums", "playlists"].indexOf(tab) >= 0)
|
|
add("duration", QbzSession.tr("Duration", QbzSession.trRev))
|
|
return options
|
|
}
|
|
|
|
/// The LibraryView root.
|
|
property var view: null
|
|
|
|
QbzTheme { id: theme }
|
|
|
|
// The active sort differs from the tab's resting default → the sort select
|
|
// highlights its outline and names the sort in a tooltip. "all" rests at
|
|
// date-added descending; every other tab rests at its "default" option.
|
|
readonly property bool sortIsNonDefault: {
|
|
if (view.activeTab === "all")
|
|
return !(view.sortBy === "date" && !view.sortAsc)
|
|
return ReleaseSort.fieldOf(view.activeSort) !== "default"
|
|
}
|
|
readonly property string sortActiveTooltip: {
|
|
if (!root.sortIsNonDefault) return ""
|
|
var field = ReleaseSort.fieldOf(view.activeSort)
|
|
var lbl = ""
|
|
for (var i = 0; i < root.sortOptions.length; i++)
|
|
if (root.sortOptions[i].value === field) { lbl = root.sortOptions[i].label; break }
|
|
if (lbl === "") return ""
|
|
return lbl + (ReleaseSort.ascending(view.activeSort) ? " ↑" : " ↓")
|
|
}
|
|
|
|
readonly property bool singleRow: view.activeTab === "labels" || view.activeTab === "playlists"
|
|
|| view.activeTab === "artists"
|
|
readonly property real rowGap: 8
|
|
readonly property real secondRowY: 25 + Math.max(tabRow.height, 30) / 2 + rowGap
|
|
readonly property real fullSearchWidth: Math.min(240, Math.max(140, root.width * 0.22))
|
|
height: singleRow ? 50 : secondRowY + searchBox.height + rowGap
|
|
// Compact only the count badges; genre and sort retain their labels.
|
|
readonly property bool compactChrome: width < 1040
|
|
|
|
// Small toolbar toggle button (ToggleButton sm): 30px, active = accent.
|
|
component ToolToggle: Rectangle {
|
|
property string name: ""
|
|
property bool active: false
|
|
signal clicked()
|
|
width: 30
|
|
height: 30
|
|
radius: 6
|
|
color: active ? (theme.ambientOn ? theme.surfaceElevatedA50 : theme.surfaceElevated)
|
|
: ttArea.containsMouse ? theme.surfaceHover : "transparent"
|
|
QbzIcon {
|
|
name: parent.name
|
|
width: 16
|
|
height: 16
|
|
anchors.centerIn: parent
|
|
tintName: parent.active ? "accent"
|
|
: ttArea.containsMouse ? "textPrimary" : "secondary"
|
|
}
|
|
MouseArea {
|
|
id: ttArea
|
|
anchors.fill: parent
|
|
hoverEnabled: true
|
|
cursorShape: Qt.PointingHandCursor
|
|
onClicked: parent.clicked()
|
|
}
|
|
}
|
|
|
|
// Filter-by-genre trigger (FavoritesView.slint's FavGenreButton, sm):
|
|
// accent fill + "N genres" while the "library-all" selection is active.
|
|
component GenreToolButton: Rectangle {
|
|
id: gtb
|
|
readonly property bool active: root.view.genreCount > 0
|
|
width: gtbRow.implicitWidth
|
|
height: 30
|
|
radius: 6
|
|
// Homologated with the selects (QbzSelect): the same ambient-aware
|
|
// rest fill and, when a genre is applied, the SAME accent OUTLINE —
|
|
// not an accent fill — so the three toolbar controls read identically
|
|
// in their modified state.
|
|
color: gtbArea.containsMouse ? theme.elevatedHoverFill
|
|
: theme.elevatedFill
|
|
border.width: gtb.active ? 1 : 0
|
|
border.color: gtb.active ? theme.accent : "transparent"
|
|
Row {
|
|
id: gtbRow
|
|
anchors.centerIn: parent
|
|
height: parent.height
|
|
leftPadding: 10
|
|
rightPadding: 12
|
|
spacing: 7
|
|
QbzIcon {
|
|
name: "list-filter"
|
|
width: 13
|
|
height: 13
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
// Same pair-of-halves fix as controls/BrowseGenreButton.qml:
|
|
// the glyph said "primary" (legacy alias of a literal
|
|
// #ffffff) next to an accent-text label, i.e. a white glyph
|
|
// beside a black label on the pale-accent themes.
|
|
// favorites/FavoritesView.slint:301 + :309 are consistently
|
|
// #ffffff, but that white is 1.70:1 on high-contrast, 1.74
|
|
// on ikari, 1.82 on wcag-dark and under 2.6:1 on 16 of the
|
|
// 35 palettes — deliberate divergence from both lines.
|
|
// theme/QbzTheme.qml, "ON AN ACCENT FILL".
|
|
tintName: gtb.active ? "accent" : "secondary"
|
|
}
|
|
Text {
|
|
visible: true
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
text: root.view.genreCount === 0
|
|
? QbzSession.tr("Filter by genre", QbzSession.trRev)
|
|
: root.view.genreCount === 1
|
|
? QbzSession.tr("1 genre", QbzSession.trRev)
|
|
: QbzSession.tr("{} genres", QbzSession.trRev)
|
|
.replace("{}", root.view.genreCount)
|
|
// The colour twin of the glyph's tint above — accent-text on
|
|
// 34 of the 35 palettes.
|
|
color: gtb.active ? theme.accent : theme.textSecondary
|
|
font.pixelSize: 12
|
|
}
|
|
}
|
|
MouseArea {
|
|
id: gtbArea
|
|
anchors.fill: parent
|
|
hoverEnabled: true
|
|
cursorShape: Qt.PointingHandCursor
|
|
onClicked: {
|
|
libFilterTip.exit()
|
|
root.view.toggleGenrePopup()
|
|
}
|
|
onEntered: libFilterTip.enter()
|
|
onExited: libFilterTip.exit()
|
|
}
|
|
|
|
// The genre chip is the toolbar's filter affordance, so it carries the
|
|
// whole bar's summary: the genres it owns, plus the source switches
|
|
// that live next to it and are just as invisible once set.
|
|
QbzFilterTip {
|
|
id: libFilterTip
|
|
ownerKey: "library-all-filter"
|
|
anchor: gtb
|
|
groups: {
|
|
var out = []
|
|
var names = (root.view.genreDoc.names || {})[root.view.genreContext] || []
|
|
if (names.length > 0)
|
|
out.push({ group: QbzSession.tr("Genre", QbzSession.trRev),
|
|
values: names })
|
|
var ex = root.view.filterSummaryGroups || []
|
|
for (var i = 0; i < ex.length; i++)
|
|
out.push(ex[i])
|
|
return out
|
|
}
|
|
}
|
|
}
|
|
|
|
// --- Group-by selects (FavoritesView.slint:604-670, 760-775).
|
|
// One control, three tabs: each carries its own option set and writes its
|
|
// own pref. The reference draws a QbzSelect per tab; this is the same menu
|
|
// shape the sort popups beside it already use, so the toolbar stays
|
|
// visually of a piece.
|
|
// Now a QbzSelect (sm) like the sort control beside it: one select style,
|
|
// ambient-aware, with the same popup. `options` are {value,label} — the
|
|
// primitive reads `.label`; the value maps back through the picked index.
|
|
// The outline highlights once a real grouping (not the resting "off") is on.
|
|
component GroupSelect: QbzSelect {
|
|
id: gsRoot
|
|
property string current: "off"
|
|
signal picked(string value)
|
|
visible: false
|
|
sm: true
|
|
menuWidth: 180
|
|
popupWidth: 180
|
|
currentIndex: {
|
|
for (var i = 0; i < gsRoot.options.length; i++)
|
|
if (gsRoot.options[i].value === gsRoot.current) return i
|
|
return 0
|
|
}
|
|
outlineActive: gsRoot.current !== "off"
|
|
onSelected: function (index) {
|
|
if (index >= 0 && index < gsRoot.options.length)
|
|
gsRoot.picked(gsRoot.options[index].value)
|
|
}
|
|
}
|
|
|
|
// Navigation and labelled selects share the first row.
|
|
Flickable {
|
|
id: topChrome
|
|
width: parent.width
|
|
height: 52
|
|
contentWidth: Math.max(width, topControls.x + topControls.width + 32)
|
|
contentHeight: height
|
|
flickableDirection: Flickable.HorizontalFlick
|
|
boundsBehavior: Flickable.StopAtBounds
|
|
clip: true
|
|
|
|
Rectangle {
|
|
x: 32
|
|
y: 25 - height / 2
|
|
width: tabRow.width
|
|
height: tabRow.height
|
|
// Ambient-aware like the tab bar it hosts (the strip read opaque
|
|
// under the ambient field while Discover's and Local's had alpha).
|
|
color: (theme.ambientOn ? theme.surfaceElevatedA50 : theme.surfaceElevated)
|
|
radius: 6
|
|
Row {
|
|
id: tabRow
|
|
padding: 3
|
|
spacing: 4
|
|
QbzTabBar {
|
|
counts: !root.compactChrome
|
|
underline: true
|
|
activeId: root.view.activeTab
|
|
tabs: [
|
|
{ "id": "all", "label": QbzSession.tr("All", QbzSession.trRev), "count": root.view.counts.all || 0 },
|
|
{ "id": "tracks", "label": QbzSession.tr("Tracks", QbzSession.trRev), "count": root.view.tabTotals.tracks || 0 },
|
|
{ "id": "albums", "label": QbzSession.tr("Releases", QbzSession.trRev), "count": root.view.tabTotals.albums || 0 },
|
|
{ "id": "artists", "label": QbzSession.tr("Artists", QbzSession.trRev), "count": root.view.counts.artists || 0 },
|
|
{ "id": "playlists", "label": QbzSession.tr("Playlists", QbzSession.trRev), "count": root.view.tabTotals.playlists || 0 },
|
|
{ "id": "labels", "label": QbzSession.tr("Labels", QbzSession.trRev), "count": root.view.counts.labels || 0 },
|
|
]
|
|
onSelected: function (id) { root.view.activeTab = id }
|
|
}
|
|
}
|
|
}
|
|
|
|
// Per-tab controls.
|
|
Row {
|
|
id: topControls
|
|
x: Math.max(32 + tabRow.width + 16, topChrome.width - width - 32)
|
|
y: 25 - height / 2
|
|
height: 30
|
|
spacing: 8
|
|
|
|
Item {
|
|
id: firstRowSearchSlot
|
|
visible: root.singleRow
|
|
width: searchBox.width
|
|
height: 30
|
|
}
|
|
|
|
GenreToolButton {
|
|
visible: ["all", "tracks", "albums"].indexOf(root.view.activeTab) >= 0
|
|
&& root.view.tabHasItems
|
|
}
|
|
GroupSelect {
|
|
visible: root.view.activeTab === "tracks" && root.view.tabHasItems
|
|
current: root.view.tracksGroup
|
|
options: [
|
|
{ "value": "off", "label": QbzSession.tr("Group: Off", QbzSession.trRev) },
|
|
{ "value": "album", "label": QbzSession.tr("Group: Album", QbzSession.trRev) },
|
|
{ "value": "artist", "label": QbzSession.tr("Group: Artist", QbzSession.trRev) },
|
|
{ "value": "name", "label": QbzSession.tr("Group: Name", QbzSession.trRev) },
|
|
]
|
|
onPicked: function (v) { root.view.setPref("tracksGroup", v) }
|
|
}
|
|
GroupSelect {
|
|
visible: root.view.activeTab === "albums" && root.view.tabHasItems
|
|
current: root.view.albumsGroup
|
|
options: [
|
|
{ "value": "off", "label": QbzSession.tr("Group: Off", QbzSession.trRev) },
|
|
{ "value": "alpha", "label": QbzSession.tr("Group: A-Z", QbzSession.trRev) },
|
|
{ "value": "artist", "label": QbzSession.tr("Group: Artist", QbzSession.trRev) },
|
|
{ "value": "year", "label": QbzSession.tr("Group: Year", QbzSession.trRev) },
|
|
{ "value": "decade", "label": QbzSession.tr("Group: Decade", QbzSession.trRev) },
|
|
]
|
|
onPicked: function (v) { root.view.setPref("albumsGroup", v) }
|
|
}
|
|
// Grid grouping is independent of the chosen sort.
|
|
GroupSelect {
|
|
visible: root.view.activeTab === "artists" && root.view.tabHasItems
|
|
&& root.view.artistsView === "grid"
|
|
current: root.view.artistsGroup
|
|
options: [
|
|
{ "value": "off", "label": QbzSession.tr("Group: Off", QbzSession.trRev) },
|
|
{ "value": "alpha", "label": QbzSession.tr("Group: A-Z", QbzSession.trRev) },
|
|
]
|
|
onPicked: function (v) { root.view.setPref("artistsGroup", v) }
|
|
}
|
|
|
|
QbzSelect {
|
|
id: sortSelect
|
|
sm: true
|
|
// Collapsed control is a compact "Sort" pill; the list stays wide.
|
|
menuWidth: 140
|
|
popupWidth: 200
|
|
leadingIcon: "arrow-down-up"
|
|
placeholderText: QbzSession.tr("Sort", QbzSession.trRev)
|
|
outlineActive: root.sortIsNonDefault
|
|
tooltipHost: libTips
|
|
tooltipText: root.sortActiveTooltip
|
|
tooltipKey: "library-sort"
|
|
options: root.sortOptions
|
|
searchable: options.length > 8
|
|
currentIndex: {
|
|
// On "all" the resting order is date-desc; map that to the
|
|
// explicit Default row so the list highlights it (and the
|
|
// outline stays off) at the default.
|
|
if (root.view.activeTab === "all"
|
|
&& root.view.sortBy === "date" && !root.view.sortAsc) {
|
|
for (var j = 0; j < options.length; ++j)
|
|
if (options[j].value === "default") return j
|
|
}
|
|
var field = ReleaseSort.fieldOf(root.view.activeSort)
|
|
for (var i = 0; i < options.length; ++i)
|
|
if (options[i].value === field) return i
|
|
return 0
|
|
}
|
|
sortDirection: ReleaseSort.ascending(root.view.activeSort) ? "asc" : "desc"
|
|
onSelected: function(index) { root.view.pickSort(options[index].value) }
|
|
}
|
|
QbzSelect {
|
|
visible: root.view.activeTab === "playlists"
|
|
sm: true
|
|
menuWidth: 180
|
|
outlineActive: root.view.playlistsSubTab !== "all"
|
|
options: [QbzSession.tr("All", QbzSession.trRev),
|
|
QbzSession.tr("By you", QbzSession.trRev),
|
|
QbzSession.tr("By Qobuz", QbzSession.trRev),
|
|
QbzSession.tr("By others", QbzSession.trRev)]
|
|
currentIndex: Math.max(0, ["all", "you", "qobuz", "others"].indexOf(root.view.playlistsSubTab))
|
|
onSelected: function (index) {
|
|
root.view.playlistsSubTab = ["all", "you", "qobuz", "others"][index]
|
|
}
|
|
}
|
|
Rectangle {
|
|
visible: rightButtons.width > 0
|
|
width: 1
|
|
height: 20
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
color: theme.borderSubtle
|
|
}
|
|
Row {
|
|
id: rightButtons
|
|
height: 30
|
|
spacing: 8
|
|
Row {
|
|
id: actionRow
|
|
height: 30
|
|
spacing: 8
|
|
|
|
function randomVisibleId(kind) {
|
|
var pool = []
|
|
var rows = root.view.visibleRows
|
|
for (var i = 0; i < rows.length; i++)
|
|
if (rows[i].kind === kind) pool.push(rows[i])
|
|
if (pool.length === 0) return null
|
|
return pool[Math.floor(Math.random() * pool.length)]
|
|
}
|
|
|
|
// Tracks — play all / shuffle all / select multiple.
|
|
QbzIconButton {
|
|
visible: root.view.activeTab === "tracks" && root.view.tabHasItems
|
|
btnSize: 30
|
|
name: "play-fill"
|
|
onClicked: QbzLibrary.libraryPlayAll(
|
|
JSON.stringify(root.view.visibleTrackIds()), false)
|
|
}
|
|
QbzIconButton {
|
|
visible: root.view.activeTab === "tracks" && root.view.tabHasItems
|
|
btnSize: 30
|
|
name: "shuffle"
|
|
onClicked: QbzLibrary.libraryPlayAll(
|
|
JSON.stringify(root.view.visibleTrackIds()), true)
|
|
}
|
|
ToolToggle {
|
|
visible: root.view.activeTab === "tracks" && root.view.tabHasItems
|
|
name: "square-check-big"
|
|
active: root.view.tracksMultiSelect
|
|
onClicked: root.view.setTracksMultiSelect(!root.view.tracksMultiSelect)
|
|
}
|
|
// Albums — play a RANDOM album (the reference's `albums_shuffle`,
|
|
// which picks one visible album, not a shuffled play of every album).
|
|
QbzIconButton {
|
|
visible: root.view.activeTab === "albums" && root.view.tabHasItems
|
|
btnSize: 30
|
|
name: "shuffle"
|
|
onClicked: {
|
|
var pick = actionRow.randomVisibleId("album")
|
|
if (pick) QbzPlayer.playAlbum(pick.id)
|
|
}
|
|
}
|
|
// Albums multi-select — LIST mode ONLY (owner 2026-07-24,
|
|
// FavoritesView.slint:512-523): the grid card has no checkbox slot.
|
|
ToolToggle {
|
|
visible: root.view.activeTab === "albums" && root.view.tabHasItems
|
|
&& root.view.albumsView === "list"
|
|
name: "square-check-big"
|
|
active: root.view.albumsMultiSelect
|
|
onClicked: root.view.setAlbumsMultiSelect(!root.view.albumsMultiSelect)
|
|
}
|
|
// Artists — open a random artist.
|
|
QbzIconButton {
|
|
visible: root.view.activeTab === "artists" && root.view.tabHasItems
|
|
btnSize: 30
|
|
name: "shuffle"
|
|
onClicked: {
|
|
var pick = actionRow.randomVisibleId("artist")
|
|
if (pick) QbzArtist.openArtist(pick.id)
|
|
}
|
|
}
|
|
// Playlists — play a random playlist.
|
|
QbzIconButton {
|
|
visible: root.view.activeTab === "playlists" && root.view.tabHasItems
|
|
btnSize: 30
|
|
name: "shuffle"
|
|
onClicked: {
|
|
var pick = actionRow.randomVisibleId("playlist")
|
|
if (pick) QbzBridge.openPlaylist(pick.id)
|
|
}
|
|
}
|
|
// Labels — open a random label's landing.
|
|
QbzIconButton {
|
|
visible: root.view.activeTab === "labels" && root.view.tabHasItems
|
|
btnSize: 30
|
|
name: "shuffle"
|
|
onClicked: {
|
|
var pick = actionRow.randomVisibleId("label")
|
|
if (pick) QbzHome.openLabel(pick.id)
|
|
}
|
|
}
|
|
}
|
|
|
|
ToolToggle {
|
|
visible: root.view.activeTab === "all"
|
|
name: root.view.viewMode === "list" ? "layout-grid" : "list"
|
|
active: false
|
|
onClicked: root.view.viewMode = root.view.viewMode === "list" ? "grid" : "list"
|
|
}
|
|
// Albums grid / list toggle (FavoritesView.slint:697-705 ViewToggle).
|
|
ToolToggle {
|
|
visible: root.view.activeTab === "albums" && root.view.tabHasItems
|
|
name: root.view.albumsView === "list" ? "layout-grid" : "list"
|
|
active: false
|
|
onClicked: root.view.setPref("albumsView",
|
|
root.view.albumsView === "list" ? "grid" : "list")
|
|
}
|
|
// Playlists grid / list toggle (FavoritesView.slint:737-745).
|
|
ToolToggle {
|
|
visible: root.view.activeTab === "playlists" && root.view.tabHasItems
|
|
name: root.view.playlistsView === "list" ? "layout-grid" : "list"
|
|
active: false
|
|
onClicked: root.view.setPref("playlistsView",
|
|
root.view.playlistsView === "list" ? "grid" : "list")
|
|
}
|
|
// Artists grid / sidepanel toggle (FavoritesView.slint:780-793 —
|
|
// `active: false` there too, "keeps the two icon states visually
|
|
// identical").
|
|
ToolToggle {
|
|
visible: root.view.activeTab === "artists" && root.view.tabHasItems
|
|
name: root.view.artistsView === "sidepanel" ? "layout-grid" : "list"
|
|
active: false
|
|
onClicked: root.view.setPref("artistsView",
|
|
root.view.artistsView === "sidepanel" ? "grid" : "sidepanel")
|
|
}
|
|
}
|
|
}
|
|
|
|
} // topChrome
|
|
|
|
// Search remains visible, including when a restriction produces no rows.
|
|
QbzLineEdit {
|
|
id: searchBox
|
|
objectName: "librarySearch"
|
|
parent: root.singleRow ? firstRowSearchSlot : root
|
|
x: root.singleRow ? 0 : 34
|
|
y: root.singleRow ? 0 : root.secondRowY
|
|
width: root.fullSearchWidth * (root.singleRow ? 0.6 : 1)
|
|
searchMode: true
|
|
expandable: false
|
|
sm: true
|
|
text: root.view.activeTab === "all" ? root.view.search : root.view.tabSearch
|
|
placeholder: root.view.activeTab === "tracks" ? QbzSession.tr("Search your tracks", QbzSession.trRev)
|
|
: root.view.activeTab === "albums" ? QbzSession.tr("Search your releases", QbzSession.trRev)
|
|
: root.view.activeTab === "playlists" ? QbzSession.tr("Search your playlists", QbzSession.trRev)
|
|
: root.view.activeTab === "artists" ? QbzSession.tr("Search your artists", QbzSession.trRev)
|
|
: root.view.activeTab === "labels" ? QbzSession.tr("Search your labels", QbzSession.trRev)
|
|
: QbzSession.tr("Search your library", QbzSession.trRev)
|
|
onEdited: function (value) {
|
|
if (root.view.activeTab === "all") root.view.setAllSearch(value)
|
|
else root.view.tabSearch = value
|
|
}
|
|
}
|
|
|
|
// Compact Library-only switch: glyph travels inside the thumb.
|
|
component SourceSwitch: Rectangle {
|
|
id: sourceSwitch
|
|
property string label: ""
|
|
property string explanation: ""
|
|
property string glyph: ""
|
|
property bool checked: false
|
|
signal toggled(bool value)
|
|
width: 46
|
|
height: 26
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
radius: 13
|
|
color: checked ? theme.accent : theme.surfaceHover
|
|
activeFocusOnTab: true
|
|
border.width: activeFocus ? 2 : 0
|
|
border.color: theme.accent
|
|
Accessible.role: Accessible.CheckBox
|
|
Accessible.name: label
|
|
Accessible.description: explanation
|
|
Accessible.checked: checked
|
|
Accessible.onToggleAction: sourceSwitch.toggled(!checked)
|
|
Keys.onPressed: function (event) {
|
|
if (event.key === Qt.Key_Space || event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
|
|
if (!event.isAutoRepeat) sourceSwitch.toggled(!checked)
|
|
event.accepted = true
|
|
}
|
|
}
|
|
Rectangle {
|
|
width: 20
|
|
height: 20
|
|
radius: 10
|
|
x: sourceSwitch.checked ? sourceSwitch.width - width - 3 : 3
|
|
y: 3
|
|
color: theme.textPrimary
|
|
QbzIcon {
|
|
anchors.centerIn: parent
|
|
width: 15
|
|
height: 15
|
|
visible: sourceSwitch.glyph !== ""
|
|
name: sourceSwitch.glyph
|
|
tintName: theme.isDark ? "black" : "white"
|
|
}
|
|
Text {
|
|
anchors.centerIn: parent
|
|
visible: sourceSwitch.glyph === ""
|
|
text: "Hi-Res"
|
|
font.pixelSize: 7
|
|
font.weight: Font.DemiBold
|
|
color: theme.surfaceMain
|
|
}
|
|
}
|
|
MouseArea {
|
|
anchors.fill: parent
|
|
cursorShape: Qt.PointingHandCursor
|
|
onPressed: sourceSwitch.forceActiveFocus()
|
|
onClicked: sourceSwitch.toggled(!sourceSwitch.checked)
|
|
}
|
|
HoverHandler { onHoveredChanged: hovered ? switchTip.enter() : switchTip.exit() }
|
|
QbzFilterTip {
|
|
id: switchTip
|
|
ownerKey: "library-switch-" + sourceSwitch.label
|
|
anchor: sourceSwitch
|
|
groups: [{group: sourceSwitch.label, values: [sourceSwitch.explanation]}]
|
|
}
|
|
}
|
|
|
|
Flickable {
|
|
id: filterChrome
|
|
x: 34 + searchBox.width + 20
|
|
width: Math.max(0, root.width - x - 32)
|
|
y: root.secondRowY
|
|
visible: !root.singleRow
|
|
height: 30
|
|
clip: true
|
|
contentWidth: Math.max(width, switches.width)
|
|
contentHeight: height
|
|
flickableDirection: Flickable.HorizontalFlick
|
|
boundsBehavior: Flickable.StopAtBounds
|
|
Row {
|
|
id: switches
|
|
x: Math.max(0, filterChrome.width - width)
|
|
height: 30
|
|
spacing: 12
|
|
readonly property bool sourceTab: root.view.activeTab === "all"
|
|
|| root.view.activeTab === "tracks" || root.view.activeTab === "albums"
|
|
SourceSwitch {
|
|
visible: switches.sourceTab
|
|
glyph: "shopping-bag"
|
|
label: QbzSession.tr("Purchases only", QbzSession.trRev)
|
|
explanation: QbzSession.tr("Show only purchased items. Combines with the other enabled filters.", QbzSession.trRev)
|
|
checked: root.view.showPurchases
|
|
onToggled: function (value) { root.view.setShowPurchases(value) }
|
|
}
|
|
SourceSwitch {
|
|
visible: switches.sourceTab
|
|
glyph: "heart"
|
|
label: QbzSession.tr("Favorites only", QbzSession.trRev)
|
|
explanation: QbzSession.tr("Show only favorites. Combines with the other enabled filters.", QbzSession.trRev)
|
|
checked: root.view.showFavorites
|
|
onToggled: function (value) { root.view.setShowFavorites(value) }
|
|
}
|
|
SourceSwitch {
|
|
visible: root.view.activeTab === "all"
|
|
glyph: "user-plus"
|
|
label: QbzSession.tr("Following only", QbzSession.trRev)
|
|
explanation: QbzSession.tr("Show only followed items. Combines with the other enabled filters.", QbzSession.trRev)
|
|
checked: root.view.showFollowing
|
|
onToggled: function (value) { root.view.setShowFollowing(value) }
|
|
}
|
|
SourceSwitch {
|
|
visible: root.view.activeTab === "tracks" || root.view.activeTab === "albums"
|
|
label: QbzSession.tr("Hi-Res only", QbzSession.trRev)
|
|
explanation: QbzSession.tr("Show only Hi-Res items. Combines with the other enabled filters.", QbzSession.trRev)
|
|
checked: root.view.hiresOnly
|
|
onToggled: function (value) { root.view.setHiresOnly(value) }
|
|
}
|
|
SourceSwitch {
|
|
visible: root.view.activeTab === "all"
|
|
glyph: "hard-drive"
|
|
label: QbzSession.tr("Hide local albums from library", QbzSession.trRev)
|
|
explanation: QbzSession.tr("Hide local albums here. They remain available in Local Library.", QbzSession.trRev)
|
|
checked: !root.view.showLocal
|
|
onToggled: function (value) { root.view.setShowLocal(!value) }
|
|
}
|
|
}
|
|
}
|
|
|
|
function closeTabSearch() {
|
|
searchBox.text = Qt.binding(function () {
|
|
return root.view.activeTab === "all" ? root.view.search : root.view.tabSearch
|
|
})
|
|
}
|
|
|
|
// Shared hover-tooltip overlay for this toolbar (the sort control names its
|
|
// active sort here when it differs from the tab default). Mounted last so
|
|
// it paints above the controls; QueueView uses the same pattern.
|
|
QbzTooltip {
|
|
id: libTips
|
|
anchors.fill: parent
|
|
z: 4000
|
|
}
|
|
}
|