right sidebar: don't load all bottom group tabs

This commit is contained in:
end-4
2025-11-25 10:44:29 +01:00
parent 9053927480
commit 08739043f6
3 changed files with 137 additions and 201 deletions
@@ -16,9 +16,9 @@ Rectangle {
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},
{"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 {
@@ -170,83 +170,56 @@ Rectangle {
}
// Content area
StackLayout {
Loader {
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
Layout.fillHeight: true
source: root.tabs[root.selectedTab].widget
// 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
}
}
Behavior on source {
id: switchBehavior
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
SequentialAnimation {
id: switchAnim
ParallelAnimation {
PropertyAnimation {
target: tabStack.item
properties: "opacity"
to: 0
duration: Appearance.animation.elementMoveFast.duration
easing.type: Easing.BezierSpline
easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve
}
PropertyAnimation {
target: tabStack.item
properties: "y"
from: 0
to: 20
duration: Appearance.animation.elementMoveFast.duration
easing.type: Easing.InExpo
}
}
PropertyAction {} // The source change happens here
ParallelAnimation {
PropertyAnimation {
target: tabStack.item
properties: "opacity"
to: 1
duration: Appearance.animation.elementMoveFast.duration
easing.type: Easing.BezierSpline
easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve
}
PropertyAnimation {
target: tabStack.item
properties: "y"
from: 20
to: 0
duration: Appearance.animation.elementMoveFast.duration
easing.type: Easing.OutExpo
}
}
}
}
}
}
// Calendar component
Component {
id: calendarWidget
CalendarWidget {
anchors.fill: parent
anchors.margins: 5
}
}
// 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
}
}
}
@@ -9,147 +9,108 @@ import Quickshell
Item {
id: root
required property var taskList;
required property var taskList
property string emptyPlaceholderIcon
property string emptyPlaceholderText
property int todoListItemSpacing: 5
property int todoListItemPadding: 8
property int listBottomPadding: 80
StyledFlickable {
id: flickable
StyledListView {
id: listView
anchors.fill: parent
contentHeight: columnLayout.height
clip: true
layer.enabled: true
layer.effect: OpacityMask {
maskSource: Rectangle {
width: flickable.width
height: flickable.height
radius: Appearance.rounding.small
}
spacing: root.todoListItemSpacing
animateAppearance: false
model: ScriptModel {
values: root.taskList
}
delegate: Item {
id: todoItem
required property var modelData
property bool pendingDoneToggle: false
property bool pendingDelete: false
property bool enableHeightAnimation: false
ColumnLayout {
id: columnLayout
width: parent.width
spacing: 0
Repeater {
model: ScriptModel {
values: taskList
implicitHeight: todoItemRectangle.implicitHeight
width: ListView.view.width
clip: true
Behavior on implicitHeight {
enabled: enableHeightAnimation
NumberAnimation {
duration: Appearance.animation.elementMoveFast.duration
easing.type: Appearance.animation.elementMoveFast.type
easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve
}
delegate: Item {
id: todoItem
property bool pendingDoneToggle: false
property bool pendingDelete: false
property bool enableHeightAnimation: false
Layout.fillWidth: true
implicitHeight: todoItemRectangle.implicitHeight + todoListItemSpacing
height: implicitHeight
clip: true
Behavior on implicitHeight {
enabled: enableHeightAnimation
NumberAnimation {
duration: Appearance.animation.elementMoveFast.duration
easing.type: Appearance.animation.elementMoveFast.type
easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve
}
}
function startAction() {
enableHeightAnimation = true
todoItem.implicitHeight = 0
actionTimer.start()
}
Timer {
id: actionTimer
interval: Appearance.animation.elementMoveFast.duration
repeat: false
onTriggered: {
if (todoItem.pendingDelete) {
Todo.deleteItem(modelData.originalIndex)
} else if (todoItem.pendingDoneToggle) {
if (!modelData.done) Todo.markDone(modelData.originalIndex)
else Todo.markUnfinished(modelData.originalIndex)
}
}
}
Rectangle {
id: todoItemRectangle
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
implicitHeight: todoContentRowLayout.implicitHeight
color: Appearance.colors.colLayer2
radius: Appearance.rounding.small
ColumnLayout {
id: todoContentRowLayout
anchors.left: parent.left
anchors.right: parent.right
StyledText {
Layout.fillWidth: true // Needed for wrapping
Layout.leftMargin: 10
Layout.rightMargin: 10
Layout.topMargin: todoListItemPadding
id: todoContentText
text: modelData.content
wrapMode: Text.Wrap
}
RowLayout {
Layout.leftMargin: 10
Layout.rightMargin: 10
Layout.bottomMargin: todoListItemPadding
Item {
Layout.fillWidth: true
}
TodoItemActionButton {
Layout.fillWidth: false
onClicked: {
todoItem.pendingDoneToggle = true
todoItem.startAction()
}
contentItem: MaterialSymbol {
anchors.centerIn: parent
horizontalAlignment: Text.AlignHCenter
text: modelData.done ? "remove_done" : "check"
iconSize: Appearance.font.pixelSize.larger
color: Appearance.colors.colOnLayer1
}
}
TodoItemActionButton {
Layout.fillWidth: false
onClicked: {
todoItem.pendingDelete = true
todoItem.startAction()
}
contentItem: MaterialSymbol {
anchors.centerIn: parent
horizontalAlignment: Text.AlignHCenter
text: "delete_forever"
iconSize: Appearance.font.pixelSize.larger
color: Appearance.colors.colOnLayer1
}
}
}
}
}
}
}
// Bottom padding
Item {
implicitHeight: listBottomPadding
Rectangle {
id: todoItemRectangle
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
implicitHeight: todoContentRowLayout.implicitHeight
color: Appearance.colors.colLayer2
radius: Appearance.rounding.small
ColumnLayout {
id: todoContentRowLayout
anchors.left: parent.left
anchors.right: parent.right
StyledText {
id: todoContentText
Layout.fillWidth: true // Needed for wrapping
Layout.leftMargin: 10
Layout.rightMargin: 10
Layout.topMargin: todoListItemPadding
text: todoItem.modelData.content
wrapMode: Text.Wrap
}
RowLayout {
Layout.leftMargin: 10
Layout.rightMargin: 10
Layout.bottomMargin: todoListItemPadding
Item {
Layout.fillWidth: true
}
TodoItemActionButton {
Layout.fillWidth: false
onClicked: {
if (!todoItem.modelData.done)
Todo.markDone(todoItem.modelData.originalIndex);
else
Todo.markUnfinished(todoItem.modelData.originalIndex);
}
contentItem: MaterialSymbol {
anchors.centerIn: parent
horizontalAlignment: Text.AlignHCenter
text: todoItem.modelData.done ? "remove_done" : "check"
iconSize: Appearance.font.pixelSize.larger
color: Appearance.colors.colOnLayer1
}
}
TodoItemActionButton {
Layout.fillWidth: false
onClicked: {
Todo.deleteItem(todoItem.modelData.originalIndex);
}
contentItem: MaterialSymbol {
anchors.centerIn: parent
horizontalAlignment: Text.AlignHCenter
text: "delete_forever"
iconSize: Appearance.font.pixelSize.larger
color: Appearance.colors.colOnLayer1
}
}
}
}
}
}
}
Item { // Placeholder when list is empty
Item {
// Placeholder when list is empty
visible: opacity > 0
opacity: taskList.length === 0 ? 1 : 0
anchors.fill: parent
@@ -177,4 +138,4 @@ Item {
}
}
}
}
}
@@ -9,6 +9,8 @@ import qs.modules.common
import qs.modules.common.functions
import qs.modules.waffle.looks
// TODO: The overlaps are crazy, but the positioning approach works.
// This could work well if we do it week by week instead of month by month.
BodyRectangle {
id: root