forked from Shinonome/dots-hyprland
right sidebar: don't load all bottom group tabs
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user