mirror of
https://github.com/danbulant/dots-hyprland
synced 2026-05-24 12:22:09 +00:00
252 lines
No EOL
9 KiB
QML
252 lines
No EOL
9 KiB
QML
import qs.modules.common
|
|
import qs.modules.common.widgets
|
|
import qs
|
|
import qs.services
|
|
import "./calendar"
|
|
import "./todo"
|
|
import "./pomodoro"
|
|
import QtQuick
|
|
import QtQuick.Layouts
|
|
|
|
Rectangle {
|
|
id: root
|
|
radius: Appearance.rounding.normal
|
|
color: Appearance.colors.colLayer1
|
|
clip: true
|
|
implicitHeight: collapsed ? collapsedBottomWidgetGroupRow.implicitHeight : bottomWidgetGroupRow.implicitHeight
|
|
property int selectedTab: Persistent.states.sidebar.bottomGroup.tab
|
|
property bool collapsed: Persistent.states.sidebar.bottomGroup.collapsed
|
|
property var tabs: [
|
|
{"type": "calendar", "name": Translation.tr("Calendar"), "icon": "calendar_month", "widget": calendarWidget},
|
|
{"type": "todo", "name": Translation.tr("To Do"), "icon": "done_outline", "widget": todoWidget},
|
|
{"type": "timer", "name": Translation.tr("Timer"), "icon": "schedule", "widget": pomodoroWidget},
|
|
]
|
|
|
|
Behavior on implicitHeight {
|
|
NumberAnimation {
|
|
duration: Appearance.animation.elementMove.duration
|
|
easing.type: Appearance.animation.elementMove.type
|
|
easing.bezierCurve: Appearance.animation.elementMove.bezierCurve
|
|
}
|
|
}
|
|
|
|
function setCollapsed(state) {
|
|
Persistent.states.sidebar.bottomGroup.collapsed = state
|
|
if (collapsed) {
|
|
bottomWidgetGroupRow.opacity = 0
|
|
}
|
|
else {
|
|
collapsedBottomWidgetGroupRow.opacity = 0
|
|
}
|
|
collapseCleanFadeTimer.start()
|
|
}
|
|
|
|
Timer {
|
|
id: collapseCleanFadeTimer
|
|
interval: Appearance.animation.elementMove.duration / 2
|
|
repeat: false
|
|
onTriggered: {
|
|
if(collapsed) collapsedBottomWidgetGroupRow.opacity = 1
|
|
else bottomWidgetGroupRow.opacity = 1
|
|
}
|
|
}
|
|
|
|
Keys.onPressed: (event) => {
|
|
if ((event.key === Qt.Key_PageDown || event.key === Qt.Key_PageUp)
|
|
&& event.modifiers === Qt.ControlModifier) {
|
|
if (event.key === Qt.Key_PageDown) {
|
|
root.selectedTab = Math.min(root.selectedTab + 1, root.tabs.length - 1)
|
|
} else if (event.key === Qt.Key_PageUp) {
|
|
root.selectedTab = Math.max(root.selectedTab - 1, 0)
|
|
}
|
|
event.accepted = true;
|
|
}
|
|
}
|
|
|
|
// The thing when collapsed
|
|
RowLayout {
|
|
id: collapsedBottomWidgetGroupRow
|
|
opacity: collapsed ? 1 : 0
|
|
visible: opacity > 0
|
|
Behavior on opacity {
|
|
NumberAnimation {
|
|
id: collapsedBottomWidgetGroupRowFade
|
|
duration: Appearance.animation.elementMove.duration / 2
|
|
easing.type: Appearance.animation.elementMove.type
|
|
easing.bezierCurve: Appearance.animation.elementMove.bezierCurve
|
|
}
|
|
}
|
|
|
|
spacing: 15
|
|
|
|
CalendarHeaderButton {
|
|
Layout.margins: 10
|
|
Layout.rightMargin: 0
|
|
forceCircle: true
|
|
onClicked: {
|
|
root.setCollapsed(false)
|
|
}
|
|
contentItem: MaterialSymbol {
|
|
text: "keyboard_arrow_up"
|
|
iconSize: Appearance.font.pixelSize.larger
|
|
horizontalAlignment: Text.AlignHCenter
|
|
color: Appearance.colors.colOnLayer1
|
|
}
|
|
}
|
|
|
|
StyledText {
|
|
property int remainingTasks: Todo.list.filter(task => !task.done).length;
|
|
Layout.margins: 10
|
|
Layout.leftMargin: 0
|
|
// text: `${DateTime.collapsedCalendarFormat} • ${remainingTasks} task${remainingTasks > 1 ? "s" : ""}`
|
|
text: Translation.tr("%1 • %2 tasks").arg(DateTime.collapsedCalendarFormat).arg(remainingTasks)
|
|
font.pixelSize: Appearance.font.pixelSize.large
|
|
color: Appearance.colors.colOnLayer1
|
|
}
|
|
}
|
|
|
|
// The thing when expanded
|
|
RowLayout {
|
|
id: bottomWidgetGroupRow
|
|
|
|
opacity: collapsed ? 0 : 1
|
|
visible: opacity > 0
|
|
Behavior on opacity {
|
|
NumberAnimation {
|
|
id: bottomWidgetGroupRowFade
|
|
duration: Appearance.animation.elementMove.duration / 2
|
|
easing.type: Appearance.animation.elementMove.type
|
|
easing.bezierCurve: Appearance.animation.elementMove.bezierCurve
|
|
}
|
|
}
|
|
|
|
anchors.fill: parent
|
|
height: tabStack.height
|
|
spacing: 10
|
|
|
|
// Navigation rail
|
|
Item {
|
|
Layout.fillHeight: true
|
|
Layout.fillWidth: false
|
|
Layout.leftMargin: 10
|
|
Layout.topMargin: 10
|
|
width: tabBar.width
|
|
// Navigation rail buttons
|
|
NavigationRailTabArray {
|
|
id: tabBar
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
anchors.left: parent.left
|
|
anchors.leftMargin: 5
|
|
currentIndex: root.selectedTab
|
|
expanded: false
|
|
Repeater {
|
|
model: root.tabs
|
|
NavigationRailButton {
|
|
showToggledHighlight: false
|
|
toggled: root.selectedTab == index
|
|
buttonText: modelData.name
|
|
buttonIcon: modelData.icon
|
|
onClicked: {
|
|
root.selectedTab = index
|
|
Persistent.states.sidebar.bottomGroup.tab = index
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Collapse button
|
|
CalendarHeaderButton {
|
|
anchors.left: parent.left
|
|
anchors.top: parent.top
|
|
forceCircle: true
|
|
onClicked: {
|
|
root.setCollapsed(true)
|
|
}
|
|
contentItem: MaterialSymbol {
|
|
text: "keyboard_arrow_down"
|
|
iconSize: Appearance.font.pixelSize.larger
|
|
horizontalAlignment: Text.AlignHCenter
|
|
color: Appearance.colors.colOnLayer1
|
|
}
|
|
}
|
|
}
|
|
|
|
// Content area
|
|
StackLayout {
|
|
id: tabStack
|
|
Layout.fillWidth: true
|
|
// Take the highest one, because the TODO list has no implicit height. This way the heigth of the calendar is used when it's initially loaded with the TODO list
|
|
height: Math.max(...tabStack.children.map(child => child.tabLoader?.implicitHeight || 0)) // TODO: make this less stupid
|
|
Layout.alignment: Qt.AlignVCenter
|
|
property int realIndex: root.selectedTab
|
|
property int animationDuration: Appearance.animation.elementMoveFast.duration * 1.5
|
|
currentIndex: root.selectedTab
|
|
|
|
// Switch the tab on halfway of the anim duration
|
|
Connections {
|
|
target: root
|
|
function onSelectedTabChanged() {
|
|
delayedStackSwitch.start()
|
|
tabStack.realIndex = root.selectedTab
|
|
}
|
|
}
|
|
Timer {
|
|
id: delayedStackSwitch
|
|
interval: tabStack.animationDuration / 2
|
|
repeat: false
|
|
onTriggered: {
|
|
tabStack.currentIndex = root.selectedTab
|
|
}
|
|
}
|
|
|
|
Repeater {
|
|
model: tabs
|
|
Item { // TODO: make behavior on y also act for the item that's switched to
|
|
id: tabItem
|
|
property int tabIndex: index
|
|
property string tabType: modelData.type
|
|
property int animDistance: 5
|
|
property var tabLoader: tabLoader
|
|
// Opacity: show up only when being animated to
|
|
opacity: (tabStack.currentIndex === tabItem.tabIndex && tabStack.realIndex === tabItem.tabIndex) ? 1 : 0
|
|
// Y: starts animating when user selects a different tab
|
|
y: (tabStack.realIndex === tabItem.tabIndex) ? 0 : (tabStack.realIndex < tabItem.tabIndex) ? animDistance : -animDistance
|
|
Behavior on opacity { NumberAnimation { duration: tabStack.animationDuration / 2; easing.type: Easing.OutCubic } }
|
|
Behavior on y { NumberAnimation { duration: tabStack.animationDuration; easing.type: Easing.OutExpo } }
|
|
Loader {
|
|
id: tabLoader
|
|
anchors.fill: parent
|
|
sourceComponent: modelData.widget
|
|
focus: root.selectedTab === tabItem.tabIndex
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Calendar component
|
|
Component {
|
|
id: calendarWidget
|
|
|
|
CalendarWidget {
|
|
anchors.centerIn: parent
|
|
}
|
|
}
|
|
|
|
// To Do component
|
|
Component {
|
|
id: todoWidget
|
|
TodoWidget {
|
|
anchors.fill: parent
|
|
anchors.margins: 5
|
|
}
|
|
}
|
|
|
|
// Pomodoro component
|
|
Component {
|
|
id: pomodoroWidget
|
|
PomodoroWidget {
|
|
anchors.fill: parent
|
|
anchors.margins: 5
|
|
}
|
|
}
|
|
} |