feat(qt): header nav goes compact only when the text tabs no longer fit, and compact items honour the click rule
The compact icon form is now a fit decision, agnostic of the sidebar state: text tabs while every label at the 11 px floor plus the tab chrome and whatever trails the tabs (the playlists button a closed sidebar adds) fits left of the search box, icons only otherwise — or always with "Compact header navigation" on. A closed sidebar used to force the compact form by itself; it now only adds one button to the measure, so a window with room keeps its text tabs. The room is measured from the third sacred button, not from the hidden row (a Row leaves a hidden child's x where it was). The downloads ring and the separator + playlists flyout button move out of the compact row into a trailing group shared by both forms, so a closed sidebar with room keeps its playlists entry; the group hides outright when it has nothing to show. Compact section buttons now call NavFlyout.sectionClicked like the text tabs and the sidebar rows do, so under the click-navigates opt-in a click on an icon lands on the section's first entry instead of only opening the flyout — Purchases was the one compact item that navigated. Settings > Navigation describes the new rule in all eight catalogs (en never carried the old entry). The Purchases navigation gate models headerNavOn and the fit bit: 128 x 3 combinations, still exactly one reachable Purchases entry. cc
This commit is contained in:
11 files changed
+106
-38
No files matched your search
@@ -26,7 +26,7 @@ Column {
|
||||
// ADR-010: only mounted when navigation is NOT in the sidebar.
|
||||
visible: root.doc.navInSidebar !== true
|
||||
label: QbzSession.tr("Compact header navigation", QbzSession.trRev)
|
||||
description: QbzSession.tr("Use the icon-only section navigation in the header even while the sidebar is open.", QbzSession.trRev)
|
||||
description: QbzSession.tr("Always use the icon-only section navigation in the header. When off, the text tabs turn into icons only when they no longer fit next to the search box.", QbzSession.trRev)
|
||||
QbzToggle { kioskHost: root.kioskHost;
|
||||
checked: root.doc.navHeaderCompact === true
|
||||
onToggled: function (v) { QbzBridge.settingsBool("nav-header-compact", v) }
|
||||
|
||||
@@ -5,13 +5,15 @@
|
||||
// (HeaderBar.slint:858 / :974 — the two blocks are mutually exclusive):
|
||||
//
|
||||
// navInSidebar ON -> the sections live in Sidebar.qml. The header shows
|
||||
// the COMPACT icon nav only while the sidebar is fully
|
||||
// the section nav only while the sidebar is fully
|
||||
// closed (state 2), plus the separator + playlists
|
||||
// flyout button, so nothing is unreachable.
|
||||
// navInSidebar OFF -> the sections live HERE. Full text tabs while the
|
||||
// sidebar is not fully closed and navHeaderCompact is
|
||||
// OFF; the compact icon form when navHeaderCompact is
|
||||
// ON, or whenever the sidebar is fully closed.
|
||||
// navInSidebar OFF -> the sections live HERE.
|
||||
// The FORM (2026-09-13) is a fit decision, agnostic of the sidebar state:
|
||||
// full text tabs while they fit beside the search box, the compact icon
|
||||
// form when they do not — or always when navHeaderCompact is ON. A closed
|
||||
// sidebar no longer forces the compact form; it only adds the playlists
|
||||
// button to the row, and that button is part of the measure.
|
||||
//
|
||||
// The search field gives up 60px whenever the nav is in the header, and
|
||||
// re-centers with a 220ms animation (HeaderBar.slint:569).
|
||||
@@ -228,20 +230,49 @@ Rectangle {
|
||||
// 6 px gap above the 1140 px breakpoint, and the Row's 2 px spacing.
|
||||
readonly property real navTabsChrome:
|
||||
root.navTabCount * ((root.width >= 1140 ? 9 + 14 + 6 : 11) + 11 + 2)
|
||||
TextMetrics {
|
||||
id: navMetrics11
|
||||
font.pixelSize: 11
|
||||
font.weight: theme.weightSemibold
|
||||
text: root.navLabelsJoined
|
||||
}
|
||||
// Where the section nav starts inside leftControls: right after the
|
||||
// third sacred button. Read off that button, not off the tab row itself,
|
||||
// because a Row leaves a hidden child's x wherever it last was — a room
|
||||
// measured from the hidden form would feed the form decision below.
|
||||
readonly property real navOriginX: fwdBtn.x + fwdBtn.width + leftControls.spacing
|
||||
// Room between the tabs' left edge and the (centred) search box.
|
||||
readonly property real navTabsRoom: searchBox.x - (leftControls.x + fullTabs.x) - 16
|
||||
readonly property real navTabsRoom: searchBox.x - (leftControls.x + root.navOriginX) - 16
|
||||
// What trails the tabs in either form (the downloads ring, the playlists
|
||||
// button of a closed sidebar) plus the Row gap before it — measured off
|
||||
// the mounted group, which does not depend on the form.
|
||||
readonly property real navTrailingWidth:
|
||||
navTrailing.visible ? leftControls.spacing + navTrailing.width : 0
|
||||
// The text tabs fit when every label at the 11 px floor, the per-tab
|
||||
// chrome and the trailing group all sit left of the search box.
|
||||
readonly property bool navTabsFit:
|
||||
navMetrics11.advanceWidth + root.navTabsChrome + root.navTrailingWidth <= root.navTabsRoom
|
||||
readonly property int navLabelPx:
|
||||
!root.headerTabsOn ? 11
|
||||
: navMetrics13.advanceWidth + root.navTabsChrome <= root.navTabsRoom ? 13
|
||||
: navMetrics12.advanceWidth + root.navTabsChrome <= root.navTabsRoom ? 12
|
||||
: 11
|
||||
|
||||
// Which of the two header forms is mounted (mutually exclusive, and both
|
||||
// off while the nav lives in the sidebar and the sidebar is not closed).
|
||||
readonly property bool headerTabsOn: !QbzShell.navInSidebar
|
||||
&& QbzShell.sidebarState !== 2 && !QbzShell.navHeaderCompact
|
||||
readonly property bool headerCompactOn: QbzShell.sidebarState === 2
|
||||
|| (!QbzShell.navInSidebar && QbzShell.navHeaderCompact)
|
||||
// Whether the section nav is up here at all: the nav lives in the header,
|
||||
// or it lives in the sidebar and the sidebar is fully closed (so the
|
||||
// sections stay reachable).
|
||||
readonly property bool headerNavOn: !QbzShell.navInSidebar || QbzShell.sidebarState === 2
|
||||
|
||||
// Which of the two header forms is mounted (mutually exclusive). The form
|
||||
// is a FIT decision (2026-09-13), agnostic of the sidebar state: text tabs
|
||||
// while they fit beside the search box, icon-only glyphs when they do not
|
||||
// — or always when "Compact header navigation" is on. A closed sidebar
|
||||
// used to force the compact form by itself; it now only adds the
|
||||
// playlists button to the measure, so a window with room keeps its tabs.
|
||||
readonly property bool headerTabsOn: root.headerNavOn
|
||||
&& !QbzShell.navHeaderCompact && root.navTabsFit
|
||||
|
||||
readonly property bool headerCompactOn: root.headerNavOn && !root.headerTabsOn
|
||||
|
||||
// Purchases downloading now (see Sidebar.qml's twin block).
|
||||
readonly property var activeDownloads: {
|
||||
@@ -426,7 +457,14 @@ Rectangle {
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
// Icon-only buttons do NOT name their section, so their dropdown
|
||||
// is headed by the section name + hairline (HeaderBar.slint:223).
|
||||
onClicked: navFlyout.openUnder(cnb, cnb.section, true)
|
||||
// The click also lands on the section's first entry under the
|
||||
// same opt-in the text tabs and the sidebar rows honour
|
||||
// (NavFlyout.sectionClicked) — the compact form used to be the
|
||||
// one host that ignored it.
|
||||
onClicked: {
|
||||
navFlyout.openUnder(cnb, cnb.section, true)
|
||||
navFlyout.sectionClicked(cnb.section)
|
||||
}
|
||||
onContainsMouseChanged: {
|
||||
if (containsMouse) {
|
||||
navFlyout.triggerHovered = true
|
||||
@@ -460,14 +498,15 @@ Rectangle {
|
||||
onClicked: QbzShell.navigateBack()
|
||||
}
|
||||
QbzNavButton {
|
||||
id: fwdBtn
|
||||
name: "chevron-right"
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
btnEnabled: QbzShell.canForward
|
||||
onClicked: QbzShell.navigateForward()
|
||||
}
|
||||
|
||||
// Full section nav (text tabs) — nav in the header, sidebar not fully
|
||||
// closed, compact form OFF. Sits AFTER the three sacred buttons so it
|
||||
// Full section nav (text tabs) — the section nav is up here and the
|
||||
// tabs fit (headerTabsOn). Sits AFTER the three sacred buttons so it
|
||||
// can never overlap them (HeaderBar.slint:853).
|
||||
Row {
|
||||
id: fullTabs
|
||||
@@ -499,9 +538,9 @@ Rectangle {
|
||||
}
|
||||
}
|
||||
|
||||
// Compact section nav — while the sidebar is fully closed (so the
|
||||
// sections stay reachable), or always when the nav is in the header
|
||||
// and "Compact header navigation" is ON (HeaderBar.slint:974).
|
||||
// Compact section nav — the icon-only form: when the text tabs do not
|
||||
// fit beside the search box, or always when "Compact header
|
||||
// navigation" is ON (HeaderBar.slint:974).
|
||||
Row {
|
||||
visible: root.headerCompactOn
|
||||
height: parent.height
|
||||
@@ -524,6 +563,20 @@ Rectangle {
|
||||
compact: true
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
// What trails the section nav in EITHER form (2026-09-13): the
|
||||
// downloads ring and, while the sidebar is really closed, the
|
||||
// separator + playlists flyout button. One group, mounted once, so a
|
||||
// closed sidebar with room for text tabs keeps its playlists entry.
|
||||
// Hidden outright when it has nothing to show, so the Row gap before
|
||||
// it (and the fit measure above) only counts when something is there.
|
||||
Row {
|
||||
id: navTrailing
|
||||
visible: root.headerNavOn
|
||||
&& (root.activeDownloads.length > 0 || QbzShell.sidebarState === 2)
|
||||
height: parent.height
|
||||
spacing: 2
|
||||
// Purchases downloading now, as one ring (the sidebar is closed
|
||||
// or the nav lives up here, so there is no row to list them in);
|
||||
// the per-album detail is the hover bubble. Absent while idle.
|
||||
|
||||
Reference in new issue
Block a user