forked from Shinonome/dots-hyprland
fancier tabs
This commit is contained in:
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+7
-6
@@ -12,6 +12,7 @@ TabButton {
|
|||||||
property string buttonIcon
|
property string buttonIcon
|
||||||
property bool selected: false
|
property bool selected: false
|
||||||
height: buttonBackground.height
|
height: buttonBackground.height
|
||||||
|
property int tabContentWidth: buttonBackground.width - buttonBackground.radius*2
|
||||||
|
|
||||||
PointingHandInteraction {}
|
PointingHandInteraction {}
|
||||||
|
|
||||||
@@ -27,9 +28,6 @@ TabButton {
|
|||||||
easing.type: Appearance.animation.elementDecel.type
|
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 {
|
contentItem: Item {
|
||||||
anchors.centerIn: buttonBackground
|
anchors.centerIn: buttonBackground
|
||||||
@@ -37,9 +35,11 @@ TabButton {
|
|||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
spacing: 0
|
spacing: 0
|
||||||
MaterialSymbol {
|
MaterialSymbol {
|
||||||
|
visible: buttonIcon?.length > 0
|
||||||
Layout.rightMargin: 5
|
Layout.rightMargin: 5
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
text: buttonIcon
|
text: buttonIcon
|
||||||
font.pixelSize: Appearance.font.pixelSize.larger
|
font.pixelSize: Appearance.font.pixelSize.huge
|
||||||
color: selected ? Appearance.m3colors.m3primary : Appearance.colors.colOnLayer1
|
color: selected ? Appearance.m3colors.m3primary : Appearance.colors.colOnLayer1
|
||||||
Behavior on color {
|
Behavior on color {
|
||||||
ColorAnimation {
|
ColorAnimation {
|
||||||
@@ -50,9 +50,10 @@ TabButton {
|
|||||||
}
|
}
|
||||||
StyledText {
|
StyledText {
|
||||||
id: buttonTextWidget
|
id: buttonTextWidget
|
||||||
horizontalAlignment: Text.AlignHCenter
|
verticalAlignment: Text.AlignVCenter
|
||||||
text: buttonText
|
font.pixelSize: Appearance.font.pixelSize.small
|
||||||
color: selected ? Appearance.m3colors.m3primary : Appearance.colors.colOnLayer1
|
color: selected ? Appearance.m3colors.m3primary : Appearance.colors.colOnLayer1
|
||||||
|
text: buttonText
|
||||||
Behavior on color {
|
Behavior on color {
|
||||||
ColorAnimation {
|
ColorAnimation {
|
||||||
duration: Appearance.animation.elementDecel.duration
|
duration: Appearance.animation.elementDecel.duration
|
||||||
@@ -94,7 +94,7 @@ Rectangle {
|
|||||||
property int remainingTasks: Todo.list.filter(task => !task.done).length;
|
property int remainingTasks: Todo.list.filter(task => !task.done).length;
|
||||||
Layout.margins: 10
|
Layout.margins: 10
|
||||||
Layout.leftMargin: 0
|
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
|
font.pixelSize: Appearance.font.pixelSize.large
|
||||||
color: Appearance.colors.colOnLayer1
|
color: Appearance.colors.colOnLayer1
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -140,12 +140,10 @@ Scope {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Center widget group
|
// Center widget group
|
||||||
Rectangle {
|
CenterWidgetGroup {
|
||||||
Layout.alignment: Qt.AlignHCenter
|
Layout.alignment: Qt.AlignHCenter
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
radius: Appearance.rounding.normal
|
|
||||||
color: Appearance.colors.colLayer1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
BottomWidgetGroup {
|
BottomWidgetGroup {
|
||||||
|
|||||||
@@ -50,9 +50,9 @@ Item {
|
|||||||
WheelHandler {
|
WheelHandler {
|
||||||
onWheel: (event) => {
|
onWheel: (event) => {
|
||||||
if (event.angleDelta.y < 0)
|
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)
|
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
|
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
|
||||||
}
|
}
|
||||||
@@ -60,7 +60,7 @@ Item {
|
|||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
model: root.tabButtonList
|
model: root.tabButtonList
|
||||||
delegate: StyledTabButton {
|
delegate: SecondaryTabButton {
|
||||||
selected: (index == currentTab)
|
selected: (index == currentTab)
|
||||||
buttonText: modelData.name
|
buttonText: modelData.name
|
||||||
buttonIcon: modelData.icon
|
buttonIcon: modelData.icon
|
||||||
@@ -68,21 +68,47 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
Item { // Tab indicator
|
||||||
|
id: tabIndicator
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
height: 3
|
||||||
|
property bool enableIndicatorAnimation: false
|
||||||
|
Connections {
|
||||||
|
target: root
|
||||||
|
function onCurrentTabChanged() {
|
||||||
|
tabIndicator.enableIndicatorAnimation = true
|
||||||
|
}
|
||||||
|
}
|
||||||
Rectangle {
|
Rectangle {
|
||||||
property int indicatorPadding: 15
|
|
||||||
id: indicator
|
|
||||||
color: Appearance.m3colors.m3primary
|
color: Appearance.m3colors.m3primary
|
||||||
height: 3
|
|
||||||
radius: Appearance.rounding.full
|
radius: Appearance.rounding.full
|
||||||
|
|
||||||
width: tabBar.width / root.tabButtonList.length - indicatorPadding * 2
|
|
||||||
x: indicatorPadding + tabBar.width / root.tabButtonList.length * currentTab
|
|
||||||
z: 2
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user