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 int selectedTab: Persistent.states.sidebar.bottomGroup.tab
property bool collapsed: Persistent.states.sidebar.bottomGroup.collapsed property bool collapsed: Persistent.states.sidebar.bottomGroup.collapsed
property var tabs: [ property var tabs: [
{"type": "calendar", "name": Translation.tr("Calendar"), "icon": "calendar_month", "widget": calendarWidget}, {"type": "calendar", "name": Translation.tr("Calendar"), "icon": "calendar_month", "widget": "calendar/CalendarWidget.qml"},
{"type": "todo", "name": Translation.tr("To Do"), "icon": "done_outline", "widget": todoWidget}, {"type": "todo", "name": Translation.tr("To Do"), "icon": "done_outline", "widget": "todo/TodoWidget.qml"},
{"type": "timer", "name": Translation.tr("Timer"), "icon": "schedule", "widget": pomodoroWidget}, {"type": "timer", "name": Translation.tr("Timer"), "icon": "schedule", "widget": "pomodoro/PomodoroWidget.qml"},
] ]
Behavior on implicitHeight { Behavior on implicitHeight {
@@ -170,83 +170,56 @@ Rectangle {
} }
// Content area // Content area
StackLayout { Loader {
id: tabStack id: tabStack
Layout.fillWidth: true 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 Layout.fillHeight: true
height: Math.max(...tabStack.children.map(child => child.tabLoader?.implicitHeight || 0)) // TODO: make this less stupid source: root.tabs[root.selectedTab].widget
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 Behavior on source {
Connections { id: switchBehavior
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 { SequentialAnimation {
model: tabs id: switchAnim
Item { // TODO: make behavior on y also act for the item that's switched to ParallelAnimation {
id: tabItem PropertyAnimation {
property int tabIndex: index target: tabStack.item
property string tabType: modelData.type properties: "opacity"
property int animDistance: 5 to: 0
property var tabLoader: tabLoader duration: Appearance.animation.elementMoveFast.duration
// Opacity: show up only when being animated to easing.type: Easing.BezierSpline
opacity: (tabStack.currentIndex === tabItem.tabIndex && tabStack.realIndex === tabItem.tabIndex) ? 1 : 0 easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve
// Y: starts animating when user selects a different tab }
y: (tabStack.realIndex === tabItem.tabIndex) ? 0 : (tabStack.realIndex < tabItem.tabIndex) ? animDistance : -animDistance PropertyAnimation {
Behavior on opacity { NumberAnimation { duration: tabStack.animationDuration / 2; easing.type: Easing.OutCubic } } target: tabStack.item
Behavior on y { NumberAnimation { duration: tabStack.animationDuration; easing.type: Easing.OutExpo } } properties: "y"
Loader { from: 0
id: tabLoader to: 20
anchors.fill: parent duration: Appearance.animation.elementMoveFast.duration
sourceComponent: modelData.widget easing.type: Easing.InExpo
focus: root.selectedTab === tabItem.tabIndex }
}
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,45 +9,30 @@ import Quickshell
Item { Item {
id: root id: root
required property var taskList; required property var taskList
property string emptyPlaceholderIcon property string emptyPlaceholderIcon
property string emptyPlaceholderText property string emptyPlaceholderText
property int todoListItemSpacing: 5 property int todoListItemSpacing: 5
property int todoListItemPadding: 8 property int todoListItemPadding: 8
property int listBottomPadding: 80 property int listBottomPadding: 80
StyledFlickable { StyledListView {
id: flickable id: listView
anchors.fill: parent anchors.fill: parent
contentHeight: columnLayout.height spacing: root.todoListItemSpacing
animateAppearance: false
clip: true
layer.enabled: true
layer.effect: OpacityMask {
maskSource: Rectangle {
width: flickable.width
height: flickable.height
radius: Appearance.rounding.small
}
}
ColumnLayout {
id: columnLayout
width: parent.width
spacing: 0
Repeater {
model: ScriptModel { model: ScriptModel {
values: taskList values: root.taskList
} }
delegate: Item { delegate: Item {
id: todoItem id: todoItem
required property var modelData
property bool pendingDoneToggle: false property bool pendingDoneToggle: false
property bool pendingDelete: false property bool pendingDelete: false
property bool enableHeightAnimation: false property bool enableHeightAnimation: false
Layout.fillWidth: true implicitHeight: todoItemRectangle.implicitHeight
implicitHeight: todoItemRectangle.implicitHeight + todoListItemSpacing width: ListView.view.width
height: implicitHeight
clip: true clip: true
Behavior on implicitHeight { Behavior on implicitHeight {
@@ -59,26 +44,6 @@ Item {
} }
} }
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 { Rectangle {
id: todoItemRectangle id: todoItemRectangle
anchors.left: parent.left anchors.left: parent.left
@@ -87,18 +52,19 @@ Item {
implicitHeight: todoContentRowLayout.implicitHeight implicitHeight: todoContentRowLayout.implicitHeight
color: Appearance.colors.colLayer2 color: Appearance.colors.colLayer2
radius: Appearance.rounding.small radius: Appearance.rounding.small
ColumnLayout { ColumnLayout {
id: todoContentRowLayout id: todoContentRowLayout
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
StyledText { StyledText {
id: todoContentText
Layout.fillWidth: true // Needed for wrapping Layout.fillWidth: true // Needed for wrapping
Layout.leftMargin: 10 Layout.leftMargin: 10
Layout.rightMargin: 10 Layout.rightMargin: 10
Layout.topMargin: todoListItemPadding Layout.topMargin: todoListItemPadding
id: todoContentText text: todoItem.modelData.content
text: modelData.content
wrapMode: Text.Wrap wrapMode: Text.Wrap
} }
RowLayout { RowLayout {
@@ -111,13 +77,15 @@ Item {
TodoItemActionButton { TodoItemActionButton {
Layout.fillWidth: false Layout.fillWidth: false
onClicked: { onClicked: {
todoItem.pendingDoneToggle = true if (!todoItem.modelData.done)
todoItem.startAction() Todo.markDone(todoItem.modelData.originalIndex);
else
Todo.markUnfinished(todoItem.modelData.originalIndex);
} }
contentItem: MaterialSymbol { contentItem: MaterialSymbol {
anchors.centerIn: parent anchors.centerIn: parent
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
text: modelData.done ? "remove_done" : "check" text: todoItem.modelData.done ? "remove_done" : "check"
iconSize: Appearance.font.pixelSize.larger iconSize: Appearance.font.pixelSize.larger
color: Appearance.colors.colOnLayer1 color: Appearance.colors.colOnLayer1
} }
@@ -125,8 +93,7 @@ Item {
TodoItemActionButton { TodoItemActionButton {
Layout.fillWidth: false Layout.fillWidth: false
onClicked: { onClicked: {
todoItem.pendingDelete = true Todo.deleteItem(todoItem.modelData.originalIndex);
todoItem.startAction()
} }
contentItem: MaterialSymbol { contentItem: MaterialSymbol {
anchors.centerIn: parent anchors.centerIn: parent
@@ -140,16 +107,10 @@ Item {
} }
} }
} }
} }
// Bottom padding
Item { Item {
implicitHeight: listBottomPadding // Placeholder when list is empty
}
}
}
Item { // Placeholder when list is empty
visible: opacity > 0 visible: opacity > 0
opacity: taskList.length === 0 ? 1 : 0 opacity: taskList.length === 0 ? 1 : 0
anchors.fill: parent anchors.fill: parent
@@ -9,6 +9,8 @@ import qs.modules.common
import qs.modules.common.functions import qs.modules.common.functions
import qs.modules.waffle.looks 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 { BodyRectangle {
id: root id: root