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 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,147 +9,108 @@ 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
|
model: ScriptModel {
|
||||||
layer.enabled: true
|
values: root.taskList
|
||||||
layer.effect: OpacityMask {
|
|
||||||
maskSource: Rectangle {
|
|
||||||
width: flickable.width
|
|
||||||
height: flickable.height
|
|
||||||
radius: Appearance.rounding.small
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
delegate: Item {
|
||||||
|
id: todoItem
|
||||||
|
required property var modelData
|
||||||
|
property bool pendingDoneToggle: false
|
||||||
|
property bool pendingDelete: false
|
||||||
|
property bool enableHeightAnimation: false
|
||||||
|
|
||||||
ColumnLayout {
|
implicitHeight: todoItemRectangle.implicitHeight
|
||||||
id: columnLayout
|
width: ListView.view.width
|
||||||
width: parent.width
|
clip: true
|
||||||
spacing: 0
|
|
||||||
Repeater {
|
Behavior on implicitHeight {
|
||||||
model: ScriptModel {
|
enabled: enableHeightAnimation
|
||||||
values: taskList
|
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 {
|
Rectangle {
|
||||||
implicitHeight: listBottomPadding
|
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
|
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
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user