Files
qbz/crates/qbz-qt/qml/views/local/LocalFoldersTab.qml
T
vicrodh a1c771c39e fix(qbz-qt): row buttons, cover triggers, light-theme contrast, flyouts, genre filter
**Track-row buttons did nothing.** Declaration order is z-order in QML, and the
row-wide MouseArea added for right-click was declared last — so it covered the
heart, cloud and menu buttons and swallowed their presses.
propagateComposedEvents cannot rescue that: a press is not a composed event,
which is why only the row's own double-click still worked.

**Covers appeared only after a scroll.** Three compounding defects, not one. The
window report fired on contentY and on model change, both of which happen while
the tab body is still hidden behind its loading flag, so the report that
mattered hit the not-visible early return and was discarded with nothing to
re-fire it. The debounce reset on every change, so a burst of mount-time reports
kept pushing its trailing edge out. And a single pending slot meant that when
two surfaces are mounted at once — the artist rail and that artist's grid — the
second overwrote the first, so one pane was never requested at all. Eviction had
the same shape: each report deleted everything outside its own array, so the two
panes deleted each other's covers.

**Latest release had no cover** because the artist page's collector skipped it,
along with Appears On and the playlists strip.

**Light themes.** The mechanism is Slint's, from 2.0.2 and issue #638: the
on-surface arm on circle actions, header-light deciding the header foreground
only when a dark backdrop is actually painted, and the circular backing behind
the track-row play glyph. Ported as specified rather than approximated. Two
icons rendered in their SVG's own colour because they pointed at the raw asset
instead of a baked tint — a sweep found the rest of that class.

**Navigation flyouts** exist, one component serving both the sidebar and the
header nav modes.

**Filter by genre and the Discover sort now work**, everywhere the control is
drawn. The genre ids were already a parameter on the discover fetch that the
port always passed empty.

cc
2026-07-28 19:55:33 -06:00

168 lines
5.7 KiB
QML

// Folders tab body (LocalLibraryView.slint:1531) — three mutually exclusive
// arms: the ephemeral pane (an ad-hoc folder is open), FLAT mode (the album
// collection grouped by directory) and TREE mode (the two-pane filesystem
// browser).
//
// The tree divider is also the drag handle that resizes the rail: min 136px
// (half the 272px default), max half the content area — the Slint's own
// clamp at :1907.
import QtQuick
import com.blitzfc.qbz
import "../../controls"
import "../../theme"
Item {
id: root
property var view: null
QbzTheme { id: theme }
// ---- Ephemeral ----
LocalEphemeralPane {
anchors.fill: parent
visible: root.view.ephemeralActive
view: root.view
}
// Loading = the shape of the arm that is coming. FLAT mode is the same
// collection the Albums tab renders; TREE mode is the rail + detail pair
// (the rail rows are 34px folder rows with a leading glyph, so the
// placeholder uses the small square art cell).
QbzSkeleton {
visible: QbzLocal.localFoldersLoading && !root.view.ephemeralActive
&& root.view.foldersMode === "flat"
variant: root.view.foldersGridView === "grid" ? "cardGrid" : "rowList"
anchors.fill: parent
anchors.leftMargin: 32
anchors.rightMargin: 32
anchors.topMargin: 16
cellW: 220
cellH: 266
rowH: 56
rowGap: 0
rowArtSize: 40
phase: root.view.skelPhase
}
Row {
visible: QbzLocal.localFoldersLoading && !root.view.ephemeralActive
&& root.view.foldersMode !== "flat"
anchors.fill: parent
anchors.topMargin: 12
spacing: 0
Item { width: 12; height: 1 }
QbzSkeleton {
variant: "rowList"
width: root.view.treeRailWidth - 24
height: parent.height
// TreeRow is 26px with a small leading glyph (TreeRow.qml:34).
rowH: 26
rowGap: 0
rowArtSize: 14
phase: root.view.skelPhase
}
Item { width: 19; height: 1 }
QbzSkeleton {
variant: "cardGrid"
width: parent.width - root.view.treeRailWidth - 7
height: parent.height
cellW: 220
cellH: 266
phase: root.view.skelPhase
}
}
LocalNote {
visible: !QbzLocal.localFoldersLoading && !root.view.ephemeralActive
&& root.view.folders.length === 0 && root.view.foldersSearch === ""
text: QbzSession.tr("No folders in your local library yet.", QbzSession.trRev)
}
// ---------------------------- FLAT MODE ------------------------------
Item {
anchors.fill: parent
visible: !QbzLocal.localFoldersLoading && !root.view.ephemeralActive
&& root.view.folders.length > 0 && root.view.foldersMode === "flat"
LocalNote {
visible: root.view.foldersVisible.length === 0
text: QbzSession.tr("No folders match your search.", QbzSession.trRev)
}
LocalAlbumCollection {
anchors.fill: parent
anchors.leftMargin: 32
anchors.rightMargin: 32
anchors.topMargin: 16
visible: root.view.foldersVisible.length > 0
view: root.view
surface: "folders-flat"
rows: root.view.foldersVisible
groups: root.view.foldersGrouped
grouped: root.view.foldersGroup !== "off"
viewMode: root.view.foldersGridView
// Slint mounts the flat folders collection with show-source
// false but show-source-badge true (:1606) — the badge on the
// card, no source COLUMN in the list arm.
showSource: true
onOpenRequested: function (id) { root.view.openAlbum(id) }
onPlayRequested: function (id) { QbzLocal.playAlbum(id, false) }
onEnqueueRequested: function (id, m) { QbzLocal.enqueue("album", id, m) }
}
}
// ---------------------------- TREE MODE ------------------------------
Item {
anchors.fill: parent
visible: !QbzLocal.localFoldersLoading && !root.view.ephemeralActive
&& root.view.folders.length > 0 && root.view.foldersMode === "tree"
LocalTreeRail {
id: rail
x: 0
width: root.view.treeRailWidth
height: parent.height
view: root.view
}
// Divider + drag handle (7px grab area, 1px visible line).
Item {
id: handle
x: rail.width
width: 7
height: parent.height
Rectangle {
x: 3
width: 1
height: parent.height
color: dragArea.containsMouse || dragArea.pressed
? theme.accent : theme.borderSubtle
}
MouseArea {
id: dragArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.SizeHorCursor
property real startW: 272
property real startX: 0
onPressed: function (mouse) {
startW = root.view.treeRailWidth
startX = mapToItem(root, mouse.x, mouse.y).x
}
onPositionChanged: function (mouse) {
if (!pressed) return
var dx = mapToItem(root, mouse.x, mouse.y).x - startX
root.view.treeRailWidth = Math.max(136,
Math.min(root.width / 2, startW + dx))
}
}
}
LocalFolderDetail {
x: rail.width + 7
width: parent.width - rail.width - 7
height: parent.height
view: root.view
}
}
}