fancier tabs

This commit is contained in:
end-4 2025-04-18 12:43:39 +02:00
parent d8d812cf47
commit 02151a93f6
6 changed files with 225 additions and 23 deletions

View file

@ -0,0 +1,67 @@
import "root:/modules/common"
import "root:/modules/common/widgets"
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell.Io
import Quickshell.Widgets
TabButton {
id: button
property string buttonText
property string buttonIcon
property bool selected: false
property int tabContentWidth: contentItem.children[0].implicitWidth
height: buttonBackground.height
PointingHandInteraction {}
background: Rectangle {
id: buttonBackground
radius: Appearance.rounding.small
implicitHeight: 50
color: (button.down ? Appearance.colors.colLayer1Active : button.hovered ? Appearance.colors.colLayer1Hover : Appearance.transparentize(Appearance.colors.colLayer1Hover, 1))
Behavior on color {
ColorAnimation {
duration: Appearance.animation.elementDecel.duration
easing.type: Appearance.animation.elementDecel.type
}
}
}
contentItem: Item {
anchors.centerIn: buttonBackground
ColumnLayout {
anchors.centerIn: parent
spacing: 0
MaterialSymbol {
visible: buttonIcon?.length > 0
Layout.alignment: Qt.AlignHCenter
horizontalAlignment: Text.AlignHCenter
text: buttonIcon
font.pixelSize: 24
color: selected ? Appearance.m3colors.m3primary : Appearance.colors.colOnLayer1
Behavior on color {
ColorAnimation {
duration: Appearance.animation.elementDecel.duration
easing.type: Appearance.animation.elementDecel.type
}
}
}
StyledText {
id: buttonTextWidget
Layout.alignment: Qt.AlignHCenter
horizontalAlignment: Text.AlignHCenter
font.pixelSize: Appearance.font.pixelSize.small
color: selected ? Appearance.m3colors.m3primary : Appearance.colors.colOnLayer1
text: buttonText
Behavior on color {
ColorAnimation {
duration: Appearance.animation.elementDecel.duration
easing.type: Appearance.animation.elementDecel.type
}
}
}
}
}
}

View file

@ -12,6 +12,7 @@ TabButton {
property string buttonIcon
property bool selected: false
height: buttonBackground.height
property int tabContentWidth: buttonBackground.width - buttonBackground.radius*2
PointingHandInteraction {}
@ -27,9 +28,6 @@ TabButton {
easing.type: Appearance.animation.elementDecel.type
}
}
// border.color: button.activeFocus ? Appearance.m3colors.m3secondary : Appearance.transparentize(Appearance.m3colors.m3secondary, 1)
// border.width: button.activeFocus ? 2 : 0
}
contentItem: Item {
anchors.centerIn: buttonBackground
@ -37,9 +35,11 @@ TabButton {
anchors.centerIn: parent
spacing: 0
MaterialSymbol {
visible: buttonIcon?.length > 0
Layout.rightMargin: 5
verticalAlignment: Text.AlignVCenter
text: buttonIcon
font.pixelSize: Appearance.font.pixelSize.larger
font.pixelSize: Appearance.font.pixelSize.huge
color: selected ? Appearance.m3colors.m3primary : Appearance.colors.colOnLayer1
Behavior on color {
ColorAnimation {
@ -50,9 +50,10 @@ TabButton {
}
StyledText {
id: buttonTextWidget
horizontalAlignment: Text.AlignHCenter
text: buttonText
verticalAlignment: Text.AlignVCenter
font.pixelSize: Appearance.font.pixelSize.small
color: selected ? Appearance.m3colors.m3primary : Appearance.colors.colOnLayer1
text: buttonText
Behavior on color {
ColorAnimation {
duration: Appearance.animation.elementDecel.duration

View file

@ -94,7 +94,7 @@ Rectangle {
property int remainingTasks: Todo.list.filter(task => !task.done).length;
Layout.margins: 10
Layout.leftMargin: 0
text: `${DateTime.day} ${DateTime.month} ${DateTime.year} ${remainingTasks} task${remainingTasks > 1 ? "s" : ""}`
text: `${DateTime.day} ${DateTime.month} ${DateTime.year} ${remainingTasks} task${remainingTasks > 1 ? "s" : ""}`
font.pixelSize: Appearance.font.pixelSize.large
color: Appearance.colors.colOnLayer1
}

View file

@ -0,0 +1,110 @@
import "root:/modules/common"
import "root:/modules/common/widgets"
import "root:/services"
import "./calendar"
import "./todo"
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
Rectangle {
id: root
radius: Appearance.rounding.normal
color: Appearance.colors.colLayer1
property int currentTab: 0
property var tabButtonList: [{"icon": "notifications", "name": "Notifications"}, {"icon": "volume_up", "name": "Volume mixer"}]
ColumnLayout {
anchors.margins: 5
anchors.fill: parent
spacing: 0
TabBar {
id: tabBar
Layout.fillWidth: true
currentIndex: currentTab
onCurrentIndexChanged: currentTab = currentIndex
background: Item {
WheelHandler {
onWheel: (event) => {
if (event.angleDelta.y < 0)
tabBar.currentIndex = Math.min(tabBar.currentIndex + 1, root.tabButtonList.length - 1)
else if (event.angleDelta.y > 0)
tabBar.currentIndex = Math.max(tabBar.currentIndex - 1, 0)
}
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
}
}
Repeater {
model: root.tabButtonList
delegate: PrimaryTabButton {
selected: (index == currentTab)
buttonText: modelData.name
buttonIcon: modelData.icon
}
}
}
Item { // Tab indicator
id: tabIndicator
Layout.fillWidth: true
height: 3
property bool enableIndicatorAnimation: false
Connections {
target: root
function onCurrentTabChanged() {
tabIndicator.enableIndicatorAnimation = true
}
}
Rectangle {
color: Appearance.m3colors.m3primary
radius: Appearance.rounding.full
z: 2
anchors.fill: parent
anchors.leftMargin: {
const tabCount = root.tabButtonList.length
const targetWidth = tabBar.contentItem.children[0].children[tabBar.currentIndex].tabContentWidth
const fullTabSize = tabBar.width / tabCount;
return fullTabSize * currentTab + (fullTabSize - targetWidth) / 2;
}
anchors.rightMargin: {
const tabCount = root.tabButtonList.length
const targetWidth = tabBar.contentItem.children[0].children[tabBar.currentIndex].tabContentWidth
const fullTabSize = tabBar.width / tabCount;
return fullTabSize * (tabCount - currentTab - 1) + (fullTabSize - targetWidth) / 2;
}
Behavior on anchors.leftMargin {
enabled: tabIndicator.enableIndicatorAnimation
SmoothedAnimation {
velocity: Appearance.animation.positionShift.velocity
}
}
Behavior on anchors.rightMargin {
enabled: tabIndicator.enableIndicatorAnimation
SmoothedAnimation {
velocity: Appearance.animation.positionShift.velocity
}
}
}
}
SwipeView {
id: swipeView
Layout.topMargin: 10
Layout.fillWidth: true
Layout.fillHeight: true
clip: true
currentIndex: currentTab
onCurrentIndexChanged: currentTab = currentIndex
Item{}
Item{}
}
}
}

View file

@ -140,12 +140,10 @@ Scope {
}
// Center widget group
Rectangle {
CenterWidgetGroup {
Layout.alignment: Qt.AlignHCenter
Layout.fillHeight: true
Layout.fillWidth: true
radius: Appearance.rounding.normal
color: Appearance.colors.colLayer1
}
BottomWidgetGroup {

View file

@ -50,9 +50,9 @@ Item {
WheelHandler {
onWheel: (event) => {
if (event.angleDelta.y < 0)
currentTab = Math.min(currentTab + 1, root.tabButtonList.length - 1)
tabBar.currentIndex = Math.min(tabBar.currentIndex + 1, root.tabButtonList.length - 1)
else if (event.angleDelta.y > 0)
currentTab = Math.max(currentTab - 1, 0)
tabBar.currentIndex = Math.max(tabBar.currentIndex - 1, 0)
}
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
}
@ -60,7 +60,7 @@ Item {
Repeater {
model: root.tabButtonList
delegate: StyledTabButton {
delegate: SecondaryTabButton {
selected: (index == currentTab)
buttonText: modelData.name
buttonIcon: modelData.icon
@ -68,21 +68,47 @@ Item {
}
}
Item {
Item { // Tab indicator
id: tabIndicator
Layout.fillWidth: true
height: 3
property bool enableIndicatorAnimation: false
Connections {
target: root
function onCurrentTabChanged() {
tabIndicator.enableIndicatorAnimation = true
}
}
Rectangle {
property int indicatorPadding: 15
id: indicator
color: Appearance.m3colors.m3primary
height: 3
radius: Appearance.rounding.full
width: tabBar.width / root.tabButtonList.length - indicatorPadding * 2
x: indicatorPadding + tabBar.width / root.tabButtonList.length * currentTab
z: 2
Behavior on x { SmoothedAnimation {
velocity: Appearance.animation.positionShift.velocity
} }
anchors.fill: parent
anchors.leftMargin: {
const tabCount = root.tabButtonList.length
const targetWidth = tabBar.contentItem.children[0].children[tabBar.currentIndex].tabContentWidth
const fullTabSize = tabBar.width / tabCount;
return fullTabSize * currentTab + (fullTabSize - targetWidth) / 2;
}
anchors.rightMargin: {
const tabCount = root.tabButtonList.length
const targetWidth = tabBar.contentItem.children[0].children[tabBar.currentIndex].tabContentWidth
const fullTabSize = tabBar.width / tabCount;
return fullTabSize * (tabCount - currentTab - 1) + (fullTabSize - targetWidth) / 2;
}
Behavior on anchors.leftMargin {
enabled: tabIndicator.enableIndicatorAnimation
SmoothedAnimation {
velocity: Appearance.animation.positionShift.velocity
}
}
Behavior on anchors.rightMargin {
enabled: tabIndicator.enableIndicatorAnimation
SmoothedAnimation {
velocity: Appearance.animation.positionShift.velocity
}
}
}
}