From d6914a4ea2b04d757c84b8ba7e81d4035f2428ce Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 17 Apr 2025 01:32:35 +0200 Subject: [PATCH] sidebar todo --- .config/quickshell/modules/bar/Battery.qml | 2 - .../quickshell/modules/common/Appearance.qml | 5 +- .../modules/common/widgets/NavRailButton.qml | 1 - .../common/widgets/StyledTabButton.qml | 41 ++++ .../sidebarRight/BottomWidgetGroup.qml | 115 +++++++++ .../modules/sidebarRight/SidebarCalendar.qml | 223 ------------------ .../modules/sidebarRight/SidebarRight.qml | 2 +- .../sidebarRight/calendar/CalendarWidget.qml | 107 +++++++++ .../modules/sidebarRight/todo/TaskList.qml | 37 +++ .../modules/sidebarRight/todo/TodoWidget.qml | 82 +++++++ .config/quickshell/services/DateTime.qml | 1 + .config/quickshell/services/ResourceUsage.qml | 1 + .config/quickshell/services/Todo.qml | 59 +++++ 13 files changed, 447 insertions(+), 229 deletions(-) create mode 100644 .config/quickshell/modules/common/widgets/StyledTabButton.qml create mode 100644 .config/quickshell/modules/sidebarRight/BottomWidgetGroup.qml delete mode 100644 .config/quickshell/modules/sidebarRight/SidebarCalendar.qml create mode 100644 .config/quickshell/modules/sidebarRight/calendar/CalendarWidget.qml create mode 100644 .config/quickshell/modules/sidebarRight/todo/TaskList.qml create mode 100644 .config/quickshell/modules/sidebarRight/todo/TodoWidget.qml create mode 100644 .config/quickshell/services/Todo.qml diff --git a/.config/quickshell/modules/bar/Battery.qml b/.config/quickshell/modules/bar/Battery.qml index 6dc6a92dc..5e5e731b3 100644 --- a/.config/quickshell/modules/bar/Battery.qml +++ b/.config/quickshell/modules/bar/Battery.qml @@ -34,9 +34,7 @@ Rectangle { duration: Appearance.animation.elementDecel.duration easing.type: Appearance.animation.elementDecel.type } - } - } StyledText { diff --git a/.config/quickshell/modules/common/Appearance.qml b/.config/quickshell/modules/common/Appearance.qml index 4fb519793..b43e29545 100644 --- a/.config/quickshell/modules/common/Appearance.qml +++ b/.config/quickshell/modules/common/Appearance.qml @@ -162,7 +162,7 @@ Singleton { animation: QtObject { property QtObject elementDecel: QtObject { - property int duration: 180 + property int duration: 200 property int type: Easing.OutCirc property int velocity: 650 } @@ -171,8 +171,9 @@ Singleton { property int type: Easing.OutExpo } property QtObject positionShift: QtObject { - property int duration: 160 + property int duration: 300 property int type: Easing.InOutExpo + property int velocity: 650 } } diff --git a/.config/quickshell/modules/common/widgets/NavRailButton.qml b/.config/quickshell/modules/common/widgets/NavRailButton.qml index cc40503fc..935d98063 100644 --- a/.config/quickshell/modules/common/widgets/NavRailButton.qml +++ b/.config/quickshell/modules/common/widgets/NavRailButton.qml @@ -36,7 +36,6 @@ Button { duration: Appearance.animation.elementDecel.duration easing.type: Appearance.animation.elementDecel.type } - } MaterialSymbol { id: navRailButtonIcon diff --git a/.config/quickshell/modules/common/widgets/StyledTabButton.qml b/.config/quickshell/modules/common/widgets/StyledTabButton.qml new file mode 100644 index 000000000..f1dc611e7 --- /dev/null +++ b/.config/quickshell/modules/common/widgets/StyledTabButton.qml @@ -0,0 +1,41 @@ +import "root:/modules/common" +import "root:/modules/common/widgets" +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell.Io + +TabButton { + id: button + property string buttonText + property string buttonIcon + property bool selected: false + height: buttonBackground.height + + background: Rectangle { + id: buttonBackground + radius: Appearance.rounding.small + implicitHeight: 37 + color: (button.down ? Appearance.colors.colLayer1Active : button.hovered ? Appearance.colors.colLayer1Hover : Appearance.transparentize(Appearance.colors.colLayer1Hover, 1)) + + Behavior on color { + ColorAnimation { + duration: Appearance.animation.elementDecel.duration + easing.type: Appearance.animation.elementDecel.type + } + } + } + contentItem: StyledText { + id: buttonTextWidget + anchors.centerIn: buttonBackground + horizontalAlignment: Text.AlignHCenter + text: buttonText + color: selected ? Appearance.m3colors.m3primary : Appearance.colors.colOnLayer1 + Behavior on color { + ColorAnimation { + duration: Appearance.animation.elementDecel.duration + easing.type: Appearance.animation.elementDecel.type + } + } + } +} \ No newline at end of file diff --git a/.config/quickshell/modules/sidebarRight/BottomWidgetGroup.qml b/.config/quickshell/modules/sidebarRight/BottomWidgetGroup.qml new file mode 100644 index 000000000..9fdd9b580 --- /dev/null +++ b/.config/quickshell/modules/sidebarRight/BottomWidgetGroup.qml @@ -0,0 +1,115 @@ +import "root:/modules/common" +import "root:/modules/common/widgets" +import "root:/services" +import "./calendar" +import "./todo" +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell + +Rectangle { + Layout.alignment: Qt.AlignHCenter + Layout.fillHeight: false + Layout.fillWidth: true + radius: Appearance.rounding.normal + color: Appearance.colors.colLayer1 + // implicitHeight: 343 // TODO NO HARD CODE + height: bottomWidgetGroupRow.height + + RowLayout { + id: bottomWidgetGroupRow + anchors.fill: parent + height: tabStack.height + spacing: 10 + property int selectedTab: 0 + + // Navigation rail + ColumnLayout { + id: tabBar + Layout.fillHeight: true + Layout.fillWidth: false + Layout.leftMargin: 15 + spacing: 15 + Repeater { + model: [ + {"name": "Calendar", "icon": "calendar_month"}, + {"name": "To Do", "icon": "done_outline"} + ] + NavRailButton { + toggled: bottomWidgetGroupRow.selectedTab == index + buttonText: modelData.name + buttonIcon: modelData.icon + onClicked: { + bottomWidgetGroupRow.selectedTab = index + } + } + } + } + + // Content area + StackLayout { + id: tabStack + Layout.fillWidth: true + height: 358 // ???? wtf + Layout.alignment: Qt.AlignVCenter + property int realIndex: 0 + property int animationDuration: Appearance.animation.elementDecel.duration * 1.5 + + // Calendar + Component { + id: calendarWidget + + CalendarWidget { + anchors.centerIn: parent + } + } + + // To Do + Component { + id: todoWidget + TodoWidget { + anchors.fill: parent + anchors.margins: 5 + } + } + + Connections { + target: bottomWidgetGroupRow + function onSelectedTabChanged() { + delayedStackSwitch.start() + tabStack.realIndex = bottomWidgetGroupRow.selectedTab + } + } + Timer { + id: delayedStackSwitch + interval: tabStack.animationDuration / 2 + repeat: false + onTriggered: { + tabStack.currentIndex = bottomWidgetGroupRow.selectedTab + } + } + + Repeater { + model: [ + { type: "calendar" }, + { type: "todo" } + ] + 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 + opacity: (tabStack.currentIndex === tabItem.tabIndex && tabStack.realIndex === tabItem.tabIndex) ? 1 : 0 + 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 { + anchors.fill: parent + sourceComponent: (tabType === "calendar") ? calendarWidget : todoWidget + } + } + } + } + } +} \ No newline at end of file diff --git a/.config/quickshell/modules/sidebarRight/SidebarCalendar.qml b/.config/quickshell/modules/sidebarRight/SidebarCalendar.qml deleted file mode 100644 index bf749f0c2..000000000 --- a/.config/quickshell/modules/sidebarRight/SidebarCalendar.qml +++ /dev/null @@ -1,223 +0,0 @@ -import "root:/modules/common" -import "root:/modules/common/widgets" -import "./calendar" -import "./calendar/calendar_layout.js" as CalendarLayout -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import Quickshell - -Rectangle { - Layout.alignment: Qt.AlignHCenter - Layout.fillHeight: false - Layout.fillWidth: true - radius: Appearance.rounding.normal - color: Appearance.colors.colLayer1 - // implicitHeight: 343 // TODO NO HARD CODE - height: calendarWidgetRow.height - - RowLayout { - id: calendarWidgetRow - anchors.fill: parent - height: tabStack.height - spacing: 10 - property int selectedTab: 0 - - // Navigation rail - ColumnLayout { - id: tabBar - Layout.fillHeight: true - Layout.fillWidth: false - Layout.leftMargin: 15 - spacing: 15 - Repeater { - model: [ - {"name": "Calendar", "icon": "calendar_month"}, - {"name": "To Do", "icon": "done_outline"} - ] - NavRailButton { - toggled: calendarWidgetRow.selectedTab == index - buttonText: modelData.name - buttonIcon: modelData.icon - onClicked: { - calendarWidgetRow.selectedTab = index - } - } - } - } - - // Content area - StackLayout { - id: tabStack - Layout.fillWidth: true - height: 358 // ???? wtf - Layout.alignment: Qt.AlignVCenter - property int realIndex: 0 - property int animationDuration: Appearance.animation.elementDecel.duration * 1.5 - - // Calendar - Component { - id: calendarWidget - - Item { - anchors.centerIn: parent - width: calendarColumn.width - height: calendarColumn.height - property int monthShift: 0 - property var viewingDate: CalendarLayout.getDateInXMonthsTime(monthShift) - property var calendarLayout: CalendarLayout.getCalendarLayout(viewingDate, monthShift === 0) - - MouseArea { - anchors.fill: parent - onWheel: { - if (wheel.angleDelta.y > 0) { - monthShift--; - } else if (wheel.angleDelta.y < 0) { - monthShift++; - } - } - } - ColumnLayout { - id: calendarColumn - anchors.centerIn: parent - spacing: 5 - - // Calendar header - RowLayout { - Layout.fillWidth: true - spacing: 5 - CalendarHeaderButton { - buttonText: `${monthShift != 0 ? "• " : ""}${viewingDate.toLocaleDateString(Qt.locale(), "MMMM yyyy")}` - tooltipText: (monthShift === 0) ? "" : "Jump to current month" - onClicked: { - monthShift = 0; - } - } - Item { - Layout.fillWidth: true - Layout.fillHeight: false - } - CalendarHeaderButton { - forceCircle: true - onClicked: { - monthShift--; - } - contentItem: MaterialSymbol { - text: "chevron_left" - font.pixelSize: Appearance.font.pixelSize.larger - horizontalAlignment: Text.AlignHCenter - color: Appearance.colors.colOnLayer1 - } - } - CalendarHeaderButton { - forceCircle: true - onClicked: { - monthShift++; - } - contentItem: MaterialSymbol { - text: "chevron_right" - font.pixelSize: Appearance.font.pixelSize.larger - horizontalAlignment: Text.AlignHCenter - color: Appearance.colors.colOnLayer1 - } - } - } - - // Week days row - RowLayout { - id: weekDaysRow - Layout.alignment: Qt.AlignHCenter - Layout.fillHeight: false - spacing: 5 - Repeater { - model: CalendarLayout.weekDays - delegate: CalendarDayButton { - day: modelData.day - isToday: modelData.today - bold: true - interactable: false - } - } - } - - // Real week rows - Repeater { - id: calendarRows - // model: calendarLayout - model: 6 - delegate: RowLayout { - Layout.alignment: Qt.AlignHCenter - Layout.fillHeight: false - spacing: 5 - Repeater { - model: Array(7).fill(modelData) - delegate: CalendarDayButton { - day: calendarLayout[modelData][index].day - isToday: calendarLayout[modelData][index].today - } - } - } - } - } - } - } - - // To Do - Component { - id: todoWidget - Item { - anchors.fill: parent - // color: "lavender" - // radius: Appearance.rounding.small - width: 30; height: 30; - StyledText { - anchors.margins: 10 - anchors.left: parent.left - anchors.right: parent.right - anchors.top: parent.top - text: "## To Do\n- Lorem ipsum\n- Dolor shit amet\n\nSigma Ohayo rc1 Pro+ Premium Hippuland hi ask vaxry for pleas fix 123 Billions must lorem ipsum ipsum yesterdays tears are tomorrows coom awawawa" - wrapMode: Text.WordWrap - textFormat: Text.MarkdownText - } - } - } - - Connections { - target: calendarWidgetRow - function onSelectedTabChanged() { - delayedStackSwitch.start() - tabStack.realIndex = calendarWidgetRow.selectedTab - } - } - Timer { - id: delayedStackSwitch - interval: tabStack.animationDuration / 2 - repeat: false - onTriggered: { - tabStack.currentIndex = calendarWidgetRow.selectedTab - } - } - - Repeater { - model: [ - { type: "calendar" }, - { type: "todo" } - ] - 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 - opacity: (tabStack.currentIndex === tabItem.tabIndex && tabStack.realIndex === tabItem.tabIndex) ? 1 : 0 - 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 { - anchors.fill: parent - sourceComponent: (tabType === "calendar") ? calendarWidget : todoWidget - } - } - } - } - } -} \ No newline at end of file diff --git a/.config/quickshell/modules/sidebarRight/SidebarRight.qml b/.config/quickshell/modules/sidebarRight/SidebarRight.qml index 381cf3977..92ebf0447 100644 --- a/.config/quickshell/modules/sidebarRight/SidebarRight.qml +++ b/.config/quickshell/modules/sidebarRight/SidebarRight.qml @@ -148,7 +148,7 @@ Scope { } // Calendar - SidebarCalendar {} + BottomWidgetGroup {} } } diff --git a/.config/quickshell/modules/sidebarRight/calendar/CalendarWidget.qml b/.config/quickshell/modules/sidebarRight/calendar/CalendarWidget.qml new file mode 100644 index 000000000..0000e2577 --- /dev/null +++ b/.config/quickshell/modules/sidebarRight/calendar/CalendarWidget.qml @@ -0,0 +1,107 @@ +import "root:/modules/common" +import "root:/modules/common/widgets" +import "./calendar_layout.js" as CalendarLayout +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +Item { + property int monthShift: 0 + property var viewingDate: CalendarLayout.getDateInXMonthsTime(monthShift) + property var calendarLayout: CalendarLayout.getCalendarLayout(viewingDate, monthShift === 0) + width: calendarColumn.width + height: calendarColumn.height + + MouseArea { + anchors.fill: parent + onWheel: { + if (wheel.angleDelta.y > 0) { + monthShift--; + } else if (wheel.angleDelta.y < 0) { + monthShift++; + } + } + } + ColumnLayout { + id: calendarColumn + anchors.centerIn: parent + spacing: 5 + + // Calendar header + RowLayout { + Layout.fillWidth: true + spacing: 5 + CalendarHeaderButton { + buttonText: `${monthShift != 0 ? "• " : ""}${viewingDate.toLocaleDateString(Qt.locale(), "MMMM yyyy")}` + tooltipText: (monthShift === 0) ? "" : "Jump to current month" + onClicked: { + monthShift = 0; + } + } + Item { + Layout.fillWidth: true + Layout.fillHeight: false + } + CalendarHeaderButton { + forceCircle: true + onClicked: { + monthShift--; + } + contentItem: MaterialSymbol { + text: "chevron_left" + font.pixelSize: Appearance.font.pixelSize.larger + horizontalAlignment: Text.AlignHCenter + color: Appearance.colors.colOnLayer1 + } + } + CalendarHeaderButton { + forceCircle: true + onClicked: { + monthShift++; + } + contentItem: MaterialSymbol { + text: "chevron_right" + font.pixelSize: Appearance.font.pixelSize.larger + horizontalAlignment: Text.AlignHCenter + color: Appearance.colors.colOnLayer1 + } + } + } + + // Week days row + RowLayout { + id: weekDaysRow + Layout.alignment: Qt.AlignHCenter + Layout.fillHeight: false + spacing: 5 + Repeater { + model: CalendarLayout.weekDays + delegate: CalendarDayButton { + day: modelData.day + isToday: modelData.today + bold: true + interactable: false + } + } + } + + // Real week rows + Repeater { + id: calendarRows + // model: calendarLayout + model: 6 + delegate: RowLayout { + Layout.alignment: Qt.AlignHCenter + Layout.fillHeight: false + spacing: 5 + Repeater { + model: Array(7).fill(modelData) + delegate: CalendarDayButton { + day: calendarLayout[modelData][index].day + isToday: calendarLayout[modelData][index].today + } + } + } + } + } +} \ No newline at end of file diff --git a/.config/quickshell/modules/sidebarRight/todo/TaskList.qml b/.config/quickshell/modules/sidebarRight/todo/TaskList.qml new file mode 100644 index 000000000..8248a14ca --- /dev/null +++ b/.config/quickshell/modules/sidebarRight/todo/TaskList.qml @@ -0,0 +1,37 @@ +import "root:/modules/common" +import "root:/modules/common/widgets" +import "root:/services" +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +Item { + required property var taskList; + + Flickable { + anchors.fill: parent + contentHeight: column.height + clip: true + + ColumnLayout { + id: column + width: parent.width + Repeater { + model: taskList + delegate: Rectangle { + Layout.fillWidth: true + width: parent.width + height: 40 + color: Appearance.colors.colLayer2 + radius: Appearance.rounding.small + Text { + text: modelData.content + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + anchors.leftMargin: 8 + } + } + } + } + } +} \ No newline at end of file diff --git a/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml b/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml new file mode 100644 index 000000000..b7923d45a --- /dev/null +++ b/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml @@ -0,0 +1,82 @@ +import "root:/modules/common" +import "root:/modules/common/widgets" +import "root:/services" +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +Item { + property int currentTab: 0 + + ColumnLayout { + anchors.fill: parent + spacing: 0 + + TabBar { + id: tabBar + property var tabButtonList: [{"icon": "checklist", "name": "Unfinished"}, {"name": "Done", "icon": "check_circle"}] + Layout.fillWidth: true + currentIndex: currentTab + onCurrentIndexChanged: currentTab = currentIndex + + background: Item { + WheelHandler { + onWheel: (event) => { + if (event.angleDelta.y < 0) + currentTab = Math.min(currentTab + 1, tabBar.tabButtonList.length - 1) + else if (event.angleDelta.y > 0) + currentTab = Math.max(currentTab - 1, 0) + } + acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad + } + } + + Repeater { + model: tabBar.tabButtonList + delegate: StyledTabButton { + selected: (index == currentTab) + buttonText: modelData.name + // buttonIcon: modelData.icon + } + } + } + + Item { + Layout.fillWidth: true + Rectangle { + property int indicatorPadding: 15 + id: indicator + color: Appearance.m3colors.m3primary + height: 3 + radius: Appearance.rounding.full + + width: tabBar.width / tabBar.tabButtonList.length - indicatorPadding * 2 + x: indicatorPadding + tabBar.width / tabBar.tabButtonList.length * currentTab + z: 2 + Behavior on x { SmoothedAnimation { + velocity: Appearance.animation.positionShift.velocity + } } + + } + } + + SwipeView { + id: swipeView + Layout.topMargin: 10 + Layout.fillWidth: true + Layout.fillHeight: true + clip: true + currentIndex: currentTab + onCurrentIndexChanged: currentTab = currentIndex + + // To Do tab + TaskList { + taskList: Todo.list.filter(item => !item.done) + } + TaskList { + taskList: Todo.list.filter(item => item.done) + } + + } + } +} \ No newline at end of file diff --git a/.config/quickshell/services/DateTime.qml b/.config/quickshell/services/DateTime.qml index 3e09be16c..fc1c501be 100644 --- a/.config/quickshell/services/DateTime.qml +++ b/.config/quickshell/services/DateTime.qml @@ -1,3 +1,4 @@ +import "root:/modules/common" import QtQuick import Quickshell import Quickshell.Io diff --git a/.config/quickshell/services/ResourceUsage.qml b/.config/quickshell/services/ResourceUsage.qml index 6326baceb..239217a6f 100644 --- a/.config/quickshell/services/ResourceUsage.qml +++ b/.config/quickshell/services/ResourceUsage.qml @@ -1,3 +1,4 @@ +import "root:/modules/common" pragma Singleton import QtQuick import Quickshell diff --git a/.config/quickshell/services/Todo.qml b/.config/quickshell/services/Todo.qml new file mode 100644 index 000000000..7bd82e76d --- /dev/null +++ b/.config/quickshell/services/Todo.qml @@ -0,0 +1,59 @@ +pragma Singleton + +import Quickshell; +import Quickshell.Io; +import Quickshell.Services.Pipewire; +import Qt.labs.platform +import QtQuick; + +Singleton { + id: root + property var filePath: `${StandardPaths.standardLocations(StandardPaths.StateLocation)[0]}/user/todo.json` + property var list: [] + + function addItem(item) { + list.push(item) + todoFileView.setText(JSON.stringify(root.list)) + } + + function markDone(index) { + if (index >= 0 && index < list.length) { + list[index].done = true + todoFileView.setText(JSON.stringify(root.list)) + } + } + + function deleteItem(index) { + if (index >= 0 && index < list.length) { + list.splice(index, 1) + todoFileView.setText(JSON.stringify(root.list)) + } + } + + function refresh() { + todoFileView.reload() + } + + Component.onCompleted: { + refresh() + } + + FileView { + id: todoFileView + path: filePath + onLoaded: { + const fileContents = todoFileView.text() + root.list = JSON.parse(fileContents) + } + onLoadFailed: (error) => { + if(error == FileViewError.FileNotFound) { + console.log("[To Do] File not found, creating new file.") + root.list = [] + todoFileView.setText(JSON.stringify(root.list)) + } else { + console.log("[To Do] Error loading file: " + error) + } + } + } +} +