Files
Panama/config/dot/quickshell/modules/settings/SettingsTabs.qml
T

149 lines
4.7 KiB
QML

// Page-level tabs, for a page that is genuinely several subjects.
//
// SettingsTabs {
// tabs: [{ value: "theme", label: "Theme" }, …]
// current: root.tab
// onSelected: value => root.tab = value
// }
//
// Only for pages long enough that scrolling hides the control someone came for.
// Appearance was six cards deep, so light and dark -- the thing reached most --
// sat below a wallpaper grid and an entire lock screen. Tabs are not a way to
// make a short page look organized; they are for when the page is long enough
// that the order stops being a suggestion and starts being a burial.
//
// The strip scrolls horizontally when it overflows: the Settings window tiles
// down to 900px, and the System category carries nine tabs. The active tab is
// always brought into view, and edge fades say there is more to the side.
import QtQuick
import qs.config
Item {
id: root
// [{ value, label }]
property var tabs: []
property string current: ""
signal selected(string value)
width: parent ? parent.width : 620
implicitHeight: 40
Flickable {
id: flick
anchors.fill: parent
contentWidth: strip.implicitWidth
contentHeight: height
flickableDirection: Flickable.HorizontalFlick
boundsBehavior: Flickable.StopAtBounds
interactive: contentWidth > width
clip: true
function reveal(item: Item): void {
if (contentWidth <= width)
return;
const target = item.x - (flick.width - item.width) / 2;
contentX = Math.max(0, Math.min(contentWidth - width, target));
}
Row {
id: strip
anchors.bottom: parent.bottom
spacing: 2
Repeater {
model: root.tabs
delegate: Item {
id: tab
required property var modelData
readonly property bool active: String(tab.modelData.value) === root.current
implicitWidth: caption.implicitWidth + 30
implicitHeight: 38
onActiveChanged: if (active) flick.reveal(tab)
Component.onCompleted: if (active) flick.reveal(tab)
Text {
id: caption
anchors.centerIn: parent
text: String(tab.modelData.label ?? "")
color: tab.active ? Theme.fg : (hover.hovered ? Theme.fgDim : Theme.fgMuted)
font.family: Theme.fontFamily
font.pixelSize: Theme.fontSize
font.weight: tab.active ? Font.DemiBold : Font.Medium
}
Rectangle {
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
height: 2
radius: 1
visible: tab.active
color: Theme.accent
}
HoverHandler {
id: hover
cursorShape: Qt.PointingHandCursor
}
TapHandler {
onTapped: root.selected(String(tab.modelData.value))
}
}
}
}
WheelHandler {
target: null
onWheel: event => {
const delta = event.angleDelta.x !== 0 ? event.angleDelta.x : event.angleDelta.y;
flick.contentX = Math.max(0, Math.min(flick.contentWidth - flick.width, flick.contentX - delta));
}
}
}
Rectangle {
anchors.left: parent.left
anchors.bottom: parent.bottom
width: 18
height: parent.height
visible: flick.contentX > 1
gradient: Gradient {
orientation: Gradient.Horizontal
GradientStop { position: 0; color: Theme.bg }
GradientStop { position: 1; color: Theme.alpha(Theme.bg, 0) }
}
}
Rectangle {
anchors.right: parent.right
anchors.bottom: parent.bottom
width: 18
height: parent.height
visible: flick.contentWidth > flick.width && flick.contentX < flick.contentWidth - flick.width - 1
gradient: Gradient {
orientation: Gradient.Horizontal
GradientStop { position: 0; color: Theme.alpha(Theme.bg, 0) }
GradientStop { position: 1; color: Theme.bg }
}
}
Rectangle {
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
height: 1
color: Theme.alpha(Theme.fg, 0.08)
z: -1
}
}