The strip scrolled away with the body; the reference clamps it to the top. Lifted out of the flow with a slot holding its place, using the reference's clamp verbatim -- the formula must not add the viewport y or the scroll is double-counted and the bar lifts ahead of the body mid-transition. Pinned at the pane's top edge it then showed the square-corner defect already fixed in Discover: Qt's clip is a rectangular scissor that ignores radius, and the shell hides its bezel nubs while the dynamic background shows through the corners, so a full-bleed child has to round itself. Driven off the live y, so the rounding exists only while the bar is actually at the top -- rounded corners in mid-page would be a notch with nothing behind them. cc
179 lines
7.0 KiB
QML
179 lines
7.0 KiB
QML
// QbzJumpNavBar — 1:1 port of primitives/JumpNavBar.slint.
|
|
//
|
|
// The section-jump strip that sits between a page header and its body:
|
|
// [JUMP TO] tab tab tab … [magnifier]
|
|
// The Slint mounts it on the artist page and (without the search arm) on the
|
|
// label page, which is why it lives in controls/ rather than inside a view.
|
|
//
|
|
// Numbers, read off primitives/JumpNavBar.slint:
|
|
// :59 height 44 :60 background = `bar-bg` (host passes the page fill)
|
|
// :65 1px border-subtle hairline pinned to the BOTTOM edge
|
|
// :74 padding-left/right 32 :76 spacing 18 between label / tabs / search
|
|
// :84 "JUMP TO" — legal (13px), semibold, text-muted, letter-spacing 0.5
|
|
// :106 tab spacing 14; :111 32px tall cell, width = text + 4, text x = 2
|
|
// :116 active -> text-primary + semibold, hover -> text-primary,
|
|
// else text-secondary + regular; :128 2px accent underline when active
|
|
// :157 open search field 216px wide, 200ms ease-in-out, surface-card
|
|
// :260 closed toggle 32px, radius 6, hover surface-elevated, 16px glyph
|
|
//
|
|
// The search affordance is the SHARED controls/QbzLineEdit expandable arm
|
|
// (ExpandableSearch.slint), not a second inline input: same interaction —
|
|
// closed magnifier slot, opens right-anchored growing LEFT over the tabs,
|
|
// X clears and closes, Esc closes. Its closed slot is 34px against the
|
|
// .slint's 32px; that 2px is the price of not owning a fourth search box.
|
|
//
|
|
// Presentation only: sticky positioning and what a tab scrolls to are the
|
|
// host's business (the .slint says the same at :12).
|
|
|
|
import QtQuick
|
|
import com.blitzfc.qbz
|
|
import "../theme"
|
|
|
|
Rectangle {
|
|
id: root
|
|
|
|
QbzTheme { id: theme }
|
|
|
|
/// [{ id, label }] — the sections present on the page.
|
|
property var tabs: []
|
|
property string activeTabId: ""
|
|
/// Page fill, so the bar reads as part of the page (and can go
|
|
/// translucent under the dynamic background, like the .slint's bar-bg).
|
|
property color barBg: theme.surfaceMain
|
|
/// The label page mounts the bar WITHOUT the search affordance.
|
|
property bool showSearch: true
|
|
/// Horizontal inset. 32 = the .slint's own padding for a full-bleed
|
|
/// mount; a host that already pads its column passes 0.
|
|
property real padH: 32
|
|
/// Top-corner rounding, for a host that PINS this bar to y=0 of the
|
|
/// content pane.
|
|
///
|
|
/// A full-bleed child sitting at the pane's top-left/right has to round
|
|
/// ITSELF: Qt's `clip` is a rectangular scissor that ignores `radius`, and
|
|
/// AppShell hides its bezel nubs while the dynamic background shows through
|
|
/// the corners — so the pane's own rounding cannot reach a child. Square
|
|
/// corners then poke out past the bezel, which is exactly what the owner
|
|
/// caught in Discover (`DiscoverBrowseView.qml:103`) and again here once
|
|
/// this bar became sticky.
|
|
///
|
|
/// The host must drive it from the PINNED state, not set it once: rounded
|
|
/// corners in mid-page, where the bar is surrounded by content on all
|
|
/// sides, are a notch out of the strip with nothing behind them.
|
|
property real topRadius: 0
|
|
|
|
signal tabClicked(string id)
|
|
signal searchEdited(string text)
|
|
|
|
/// Host hook: clear the filter when the field closes itself (Esc / X).
|
|
property alias searchOpen: searchBox.open
|
|
|
|
height: 44
|
|
color: root.barBg
|
|
topLeftRadius: root.topRadius
|
|
topRightRadius: root.topRadius
|
|
|
|
Rectangle {
|
|
x: 0
|
|
y: parent.height - 1
|
|
width: parent.width
|
|
height: 1
|
|
color: theme.borderSubtle
|
|
}
|
|
|
|
Text {
|
|
id: jumpLabel
|
|
x: root.padH
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
text: QbzSession.tr("JUMP TO", QbzSession.trRev)
|
|
color: theme.textMuted
|
|
font.pixelSize: theme.fontLegal
|
|
font.weight: theme.weightSemibold
|
|
font.letterSpacing: 0.5
|
|
}
|
|
|
|
// Tab strip. Clipped so a long discography's tabs never spill under the
|
|
// search slot (the .slint's `center-region { clip: true }`).
|
|
Item {
|
|
id: centerRegion
|
|
x: jumpLabel.x + jumpLabel.width + 18
|
|
y: 0
|
|
width: Math.max(0, root.width - root.padH
|
|
- (searchSlot.visible ? searchSlot.width + 18 : 0)
|
|
- x)
|
|
height: parent.height
|
|
clip: true
|
|
|
|
Row {
|
|
anchors.left: parent.left
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
spacing: 14
|
|
Repeater {
|
|
model: root.tabs
|
|
delegate: Item {
|
|
id: tabCell
|
|
required property var modelData
|
|
readonly property bool active: modelData.id === root.activeTabId
|
|
|
|
width: tabText.implicitWidth + 4
|
|
height: 32
|
|
|
|
Text {
|
|
id: tabText
|
|
x: 2
|
|
height: parent.height
|
|
verticalAlignment: Text.AlignVCenter
|
|
text: tabCell.modelData.label
|
|
color: (tabCell.active || tabArea.containsMouse)
|
|
? theme.textPrimary : theme.textSecondary
|
|
font.pixelSize: theme.fontBody
|
|
font.weight: tabCell.active ? theme.weightSemibold
|
|
: theme.weightRegular
|
|
}
|
|
Rectangle {
|
|
visible: tabCell.active
|
|
x: 0
|
|
y: parent.height - 2
|
|
width: parent.width
|
|
height: 2
|
|
color: theme.accent
|
|
}
|
|
MouseArea {
|
|
id: tabArea
|
|
anchors.fill: parent
|
|
// The .slint disables the tabs while the field is
|
|
// open (they are behind it).
|
|
enabled: !searchBox.open
|
|
hoverEnabled: true
|
|
cursorShape: Qt.PointingHandCursor
|
|
onClicked: root.tabClicked(tabCell.modelData.id)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Search slot — keeps the CLOSED footprint; the field itself grows left
|
|
// over the tabs, so nothing in the bar reflows when it opens.
|
|
Item {
|
|
id: searchSlot
|
|
visible: root.showSearch
|
|
width: searchBox.width
|
|
height: searchBox.height
|
|
anchors.right: parent.right
|
|
anchors.rightMargin: root.padH
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
|
|
QbzLineEdit {
|
|
id: searchBox
|
|
searchMode: true
|
|
expandable: true
|
|
// surface-card overlay fill (JumpNavBar.slint:164), not the
|
|
// toolbar's elevated.
|
|
elevated: false
|
|
openWidth: Math.min(216, Math.max(120, centerRegion.width))
|
|
placeholder: QbzSession.tr("Search this page…", QbzSession.trRev)
|
|
onEdited: function (v) { root.searchEdited(v) }
|
|
}
|
|
}
|
|
}
|