pragma ComponentBehavior: Bound import qs.modules.common import qs.modules.common.widgets import qs.services import qs.modules.ii.sidebarRight.calendar import qs.modules.ii.sidebarRight.todo import qs.modules.ii.sidebarRight.pomodoro import QtQuick import QtQuick.Layouts Rectangle { id: root radius: Appearance.rounding.normal color: Appearance.colors.colLayer1 clip: true implicitHeight: collapsed ? collapsedBottomWidgetGroupRow.implicitHeight : 350 property int selectedTab: Persistent.states.sidebar.bottomGroup.tab property int previousIndex: -1 property bool collapsed: Persistent.states.sidebar.bottomGroup.collapsed property var tabs: [ { "type": "calendar", "name": Translation.tr("Calendar"), "icon": "calendar_month", "widget": "calendar/CalendarWidget.qml" }, { "type": "todo", "name": Translation.tr("To Do"), "icon": "done_outline", "widget": "todo/TodoWidget.qml" }, { "type": "timer", "name": Translation.tr("Timer"), "icon": "schedule", "widget": "pomodoro/PomodoroWidget.qml" }, ] 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 downAction: () => { 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 // implicitHeight: tabStack.implicitHeight spacing: 20 // Navigation rail Item { Layout.fillHeight: true Layout.fillWidth: false Layout.leftMargin: 10 Layout.topMargin: 10 implicitWidth: tabBar.implicitWidth // 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 { required property int index required property var modelData showToggledHighlight: false toggled: root.selectedTab == index buttonText: modelData.name buttonIcon: modelData.icon onPressed: { root.selectedTab = index; Persistent.states.sidebar.bottomGroup.tab = index; } } } } // Collapse button CalendarHeaderButton { anchors.left: parent.left anchors.top: parent.top forceCircle: true downAction: () => { root.setCollapsed(true); } contentItem: MaterialSymbol { text: "keyboard_arrow_down" iconSize: Appearance.font.pixelSize.larger horizontalAlignment: Text.AlignHCenter color: Appearance.colors.colOnLayer1 } } } // Content area Item { Layout.fillWidth: true Layout.fillHeight: true // implicitHeight: tabStack.implicitHeight Loader { id: tabStack anchors.fill: parent anchors.bottomMargin: -anchors.topMargin Component.onCompleted: { tabStack.source = root.tabs[root.selectedTab].widget; } Connections { target: root function onSelectedTabChanged() { if (root.currentTab > root.previousIndex) tabSwitchBehavior.animation.down = true; else if (root.currentTab < root.previousIndex) tabSwitchBehavior.animation.down = false; tabStack.source = root.tabs[root.selectedTab].widget; } } Behavior on source { id: tabSwitchBehavior animation: TabSwitchAnim { id: upAnim down: true } } } } } component TabSwitchAnim: SequentialAnimation { id: switchAnim property bool down: false ParallelAnimation { PropertyAnimation { target: tabStack properties: "opacity" to: 0 duration: Appearance.animation.elementMoveFast.duration easing.type: Easing.BezierSpline easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve } PropertyAnimation { target: tabStack.anchors properties: "topMargin" to: 10 * (switchAnim.down ? -1 : 1) duration: Appearance.animation.elementMoveFast.duration easing.type: Easing.BezierSpline easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve } } PropertyAction { target: tabStack property: "source" value: root.tabs[root.selectedTab].widget } // The source change happens here ParallelAnimation { PropertyAnimation { target: tabStack.anchors properties: "topMargin" from: 10 * -(switchAnim.down ? -1 : 1) to: 0 duration: Appearance.animation.elementMoveFast.duration easing.type: Easing.BezierSpline easing.bezierCurve: Appearance.animation.elementMoveEnter.bezierCurve } PropertyAnimation { target: tabStack properties: "opacity" to: 1 duration: Appearance.animation.elementMoveFast.duration easing.type: Easing.BezierSpline easing.bezierCurve: Appearance.animation.elementMoveEnter.bezierCurve } } ScriptAction { script: { root.previousIndex = root.selectedTab; } } } }