left sidebar: toolbar-style tabs

This commit is contained in:
end-4
2025-11-03 17:00:28 +01:00
parent 7c974b7fb4
commit 087a736d1a
5 changed files with 174 additions and 31 deletions
@@ -9,7 +9,7 @@ ColumnLayout {
id: root
spacing: 0
required property var tabButtonList // Something like [{"icon": "notifications", "name": Translation.tr("Notifications")}, {"icon": "volume_up", "name": Translation.tr("Volume mixer")}]
property int currentIndex
property alias currentIndex: tabBar.currentIndex
property bool enableIndicatorAnimation: false
property color colIndicator: Appearance?.colors.colPrimary ?? "#65558F"
property color colBorder: Appearance?.m3colors.m3outlineVariant ?? "#C6C6D0"
@@ -26,17 +26,14 @@ ColumnLayout {
TabBar {
id: tabBar
Layout.fillWidth: true
Synchronizer on currentIndex {
property alias source: root.currentIndex
}
background: Item {
WheelHandler {
onWheel: (event) => {
if (event.angleDelta.y < 0)
tabBar.currentIndex = Math.min(tabBar.currentIndex + 1, root.tabButtonList.length - 1)
tabBar.incrementCurrentIndex()
else if (event.angleDelta.y > 0)
tabBar.currentIndex = Math.max(tabBar.currentIndex - 1, 0)
tabBar.decrementCurrentIndex()
}
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
}
@@ -49,7 +46,7 @@ ColumnLayout {
buttonText: modelData.name
buttonIcon: modelData.icon
minimumWidth: 160
onClicked: root.currentIndex = index
onClicked: tabBar.setCurrentIndex(index)
}
}
}
@@ -10,6 +10,7 @@ import qs.modules.common.widgets
Item {
id: root
property bool enableShadow: true
property real padding: 8
property alias colBackground: background.color
property alias spacing: toolbarLayout.spacing
@@ -18,15 +19,20 @@ Item {
implicitHeight: background.implicitHeight
property alias radius: background.radius
StyledRectangularShadow {
target: background
Loader {
active: root.enableShadow
anchors.fill: background
sourceComponent: StyledRectangularShadow {
target: background
anchors.fill: undefined
}
}
Rectangle {
id: background
anchors.fill: parent
color: Appearance.m3colors.m3surfaceContainer
implicitHeight: Math.max(toolbarLayout.implicitHeight + root.padding * 2, 56)
implicitHeight: 56
implicitWidth: toolbarLayout.implicitWidth + root.padding * 2
radius: height / 2
@@ -0,0 +1,103 @@
pragma ComponentBehavior: Bound
import qs.modules.common
import qs.modules.common.models
import qs.services
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Qt.labs.synchronizer
Item {
id: root
property alias currentIndex: tabBar.currentIndex
required property var tabButtonList
function incrementCurrentIndex() {
tabBar.incrementCurrentIndex()
}
function decrementCurrentIndex() {
tabBar.decrementCurrentIndex()
}
function setCurrentIndex(index) {
tabBar.setCurrentIndex(index)
}
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
implicitWidth: contentItem.implicitWidth
implicitHeight: 40
Row {
id: contentItem
z: 1
anchors.centerIn: parent
spacing: 4
Repeater {
model: root.tabButtonList
delegate: ToolbarTabButton {
required property int index
required property var modelData
current: index == root.currentIndex
text: modelData.name
materialSymbol: modelData.icon
onClicked: {
root.setCurrentIndex(index)
}
}
}
}
Rectangle {
id: activeIndicator
z: 0
color: Appearance.colors.colSecondaryContainer
implicitWidth: contentItem.children[root.currentIndex].implicitWidth
implicitHeight: contentItem.children[root.currentIndex].implicitHeight
radius: height / 2
// Animation
property Item targetItem: contentItem.children[root.currentIndex]
AnimatedTabIndexPair {
id: leftBound
idx1Duration: 50
idx2Duration: 200
index: activeIndicator.targetItem.x
}
AnimatedTabIndexPair {
id: rightBound
idx1Duration: 50
idx2Duration: 200
index: activeIndicator.targetItem.x + activeIndicator.targetItem.width
}
x: Math.min(leftBound.idx1, leftBound.idx2)
width: Math.max(rightBound.idx1, rightBound.idx2) - x
}
MouseArea {
anchors.fill: parent
z: 2
acceptedButtons: Qt.NoButton
cursorShape: Qt.PointingHandCursor
onWheel: (event) => {
if (event.angleDelta.y < 0) {
root.incrementCurrentIndex();
}
else {
root.decrementCurrentIndex();
}
}
}
// TabBar doesn't allow tabs to be of different sizes. Literally unusable.
// We use it only for the logic and draw stuff manually
TabBar {
id: tabBar
z: -1
background: null
Repeater { // This is to fool the TabBar that it has tabs so it does the indices properly
model: root.tabButtonList.length
delegate: TabButton {
background: null
}
}
}
}
@@ -0,0 +1,40 @@
import qs.modules.common
import qs.modules.common.widgets
import qs.modules.common.functions
import Qt5Compat.GraphicalEffects
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
RippleButton {
id: root
required property string materialSymbol
required property bool current
horizontalPadding: 10
implicitHeight: 40
implicitWidth: implicitContentWidth + horizontalPadding * 2
buttonRadius: height / 2
colBackground: ColorUtils.transparentize(Appearance.colors.colSurfaceContainer)
colBackgroundHover: ColorUtils.transparentize(Appearance.colors.colOnSurface, current ? 1 : 0.95)
colRipple: ColorUtils.transparentize(Appearance.colors.colOnSurface, 0.95)
contentItem: Row {
id: contentRow
anchors.centerIn: parent
spacing: 6
MaterialSymbol {
id: icon
anchors.verticalCenter: parent.verticalCenter
iconSize: 22
text: root.materialSymbol
}
StyledText {
id: label
anchors.verticalCenter: parent.verticalCenter
text: root.text
}
}
}