diff --git a/dots/.config/quickshell/ii/modules/ii/sidebarRight/BottomWidgetGroup.qml b/dots/.config/quickshell/ii/modules/ii/sidebarRight/BottomWidgetGroup.qml index f81e9db13..2adb5f503 100644 --- a/dots/.config/quickshell/ii/modules/ii/sidebarRight/BottomWidgetGroup.qml +++ b/dots/.config/quickshell/ii/modules/ii/sidebarRight/BottomWidgetGroup.qml @@ -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 - } - } } diff --git a/dots/.config/quickshell/ii/modules/ii/sidebarRight/todo/TaskList.qml b/dots/.config/quickshell/ii/modules/ii/sidebarRight/todo/TaskList.qml index 4f6a4304d..b48352f31 100644 --- a/dots/.config/quickshell/ii/modules/ii/sidebarRight/todo/TaskList.qml +++ b/dots/.config/quickshell/ii/modules/ii/sidebarRight/todo/TaskList.qml @@ -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 { } } } -} \ No newline at end of file +} diff --git a/dots/.config/quickshell/ii/modules/waffle/notificationCenter/CalendarView.qml b/dots/.config/quickshell/ii/modules/waffle/notificationCenter/CalendarView.qml index 07228590c..b0ff57684 100644 --- a/dots/.config/quickshell/ii/modules/waffle/notificationCenter/CalendarView.qml +++ b/dots/.config/quickshell/ii/modules/waffle/notificationCenter/CalendarView.qml @@ -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