From 4db72d941e02fefe51a216f80c4e9b83c0aa1be8 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 17 Apr 2025 12:31:51 +0200 Subject: [PATCH] todo list --- .config/quickshell/modules/bar/Media.qml | 2 +- .../quickshell/modules/common/Appearance.qml | 5 + .../common/widgets/StyledTabButton.qml | 37 ++++-- .../modules/common/widgets/StyledToolTip.qml | 2 +- .../sidebarRight/BottomWidgetGroup.qml | 72 +++++------ .../sidebarRight/calendar/CalendarWidget.qml | 4 +- .../modules/sidebarRight/todo/TaskList.qml | 115 ++++++++++++++++-- .../todo/TodoItemActionButton.qml | 46 +++++++ .../modules/sidebarRight/todo/TodoWidget.qml | 10 +- .config/quickshell/services/Todo.qml | 13 ++ .config/quickshell/shell.qml | 17 +-- 11 files changed, 249 insertions(+), 74 deletions(-) create mode 100644 .config/quickshell/modules/sidebarRight/todo/TodoItemActionButton.qml diff --git a/.config/quickshell/modules/bar/Media.qml b/.config/quickshell/modules/bar/Media.qml index 4200a77c5..434a8a24d 100644 --- a/.config/quickshell/modules/bar/Media.qml +++ b/.config/quickshell/modules/bar/Media.qml @@ -70,7 +70,7 @@ Item { } StyledText { - width: rowLayout.width - (CircularProgress.size + rowLayout.spacing * 2) // TODO ADJUST THIS + width: rowLayout.width - (CircularProgress.size + rowLayout.spacing * 2) Layout.alignment: Qt.AlignVCenter Layout.fillWidth: true // Ensures the text takes up available space Layout.rightMargin: rowLayout.spacing diff --git a/.config/quickshell/modules/common/Appearance.qml b/.config/quickshell/modules/common/Appearance.qml index b43e29545..8929e348f 100644 --- a/.config/quickshell/modules/common/Appearance.qml +++ b/.config/quickshell/modules/common/Appearance.qml @@ -166,6 +166,11 @@ Singleton { property int type: Easing.OutCirc property int velocity: 650 } + property QtObject elementDecelFast: QtObject { + property int duration: 140 + property int type: Easing.OutCirc + property int velocity: 750 + } property QtObject menuDecel: QtObject { property int duration: 350 property int type: Easing.OutExpo diff --git a/.config/quickshell/modules/common/widgets/StyledTabButton.qml b/.config/quickshell/modules/common/widgets/StyledTabButton.qml index f1dc611e7..47d447b78 100644 --- a/.config/quickshell/modules/common/widgets/StyledTabButton.qml +++ b/.config/quickshell/modules/common/widgets/StyledTabButton.qml @@ -4,6 +4,7 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts import Quickshell.Io +import Quickshell.Widgets TabButton { id: button @@ -25,16 +26,34 @@ TabButton { } } } - contentItem: StyledText { - id: buttonTextWidget + contentItem: Item { 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 + RowLayout { + anchors.centerIn: parent + spacing: 0 + MaterialSymbol { + Layout.rightMargin: 5 + text: buttonIcon + font.pixelSize: Appearance.font.pixelSize.larger + color: selected ? Appearance.m3colors.m3primary : Appearance.colors.colOnLayer1 + Behavior on color { + ColorAnimation { + duration: Appearance.animation.elementDecel.duration + easing.type: Appearance.animation.elementDecel.type + } + } + } + StyledText { + id: buttonTextWidget + 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 + } + } } } } diff --git a/.config/quickshell/modules/common/widgets/StyledToolTip.qml b/.config/quickshell/modules/common/widgets/StyledToolTip.qml index 98945ceec..388a4bad7 100644 --- a/.config/quickshell/modules/common/widgets/StyledToolTip.qml +++ b/.config/quickshell/modules/common/widgets/StyledToolTip.qml @@ -27,6 +27,6 @@ ToolTip { id: tooltipTextObject text: content color: Appearance.colors.colOnTooltip - wrapMode: Text.WordWrap + wrapMode: Text.Wrap } } \ No newline at end of file diff --git a/.config/quickshell/modules/sidebarRight/BottomWidgetGroup.qml b/.config/quickshell/modules/sidebarRight/BottomWidgetGroup.qml index 9fdd9b580..162077f53 100644 --- a/.config/quickshell/modules/sidebarRight/BottomWidgetGroup.qml +++ b/.config/quickshell/modules/sidebarRight/BottomWidgetGroup.qml @@ -9,20 +9,41 @@ import QtQuick.Layouts import Quickshell Rectangle { + id: root 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 + property int selectedTab: 0 + property var tabs: [ + {"type": "calendar", "name": "Calendar", "icon": "calendar_month", "widget": calendarWidget}, + {"type": "todo", "name": "To Do", "icon": "done_outline", "widget": todoWidget} + ] + // Calendar + Component { + id: calendarWidget + + CalendarWidget { + anchors.centerIn: parent + } + } + + // To Do + Component { + id: todoWidget + TodoWidget { + anchors.fill: parent + anchors.margins: 5 + } + } RowLayout { id: bottomWidgetGroupRow anchors.fill: parent height: tabStack.height spacing: 10 - property int selectedTab: 0 // Navigation rail ColumnLayout { @@ -32,16 +53,13 @@ Rectangle { Layout.leftMargin: 15 spacing: 15 Repeater { - model: [ - {"name": "Calendar", "icon": "calendar_month"}, - {"name": "To Do", "icon": "done_outline"} - ] + model: root.tabs NavRailButton { - toggled: bottomWidgetGroupRow.selectedTab == index + toggled: root.selectedTab == index buttonText: modelData.name buttonIcon: modelData.icon onClicked: { - bottomWidgetGroupRow.selectedTab = index + root.selectedTab = index } } } @@ -51,34 +69,17 @@ Rectangle { StackLayout { id: tabStack Layout.fillWidth: true - height: 358 // ???? wtf + height: tabStack.children[0]?.tabLoader?.implicitHeight // TODO: make this less stupid 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 - } - } - + // Switch the tab on halfway of the anim duration Connections { - target: bottomWidgetGroupRow + target: root function onSelectedTabChanged() { delayedStackSwitch.start() - tabStack.realIndex = bottomWidgetGroupRow.selectedTab + tabStack.realIndex = root.selectedTab } } Timer { @@ -86,27 +87,28 @@ Rectangle { interval: tabStack.animationDuration / 2 repeat: false onTriggered: { - tabStack.currentIndex = bottomWidgetGroupRow.selectedTab + tabStack.currentIndex = root.selectedTab } } Repeater { - model: [ - { type: "calendar" }, - { type: "todo" } - ] + 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: (tabType === "calendar") ? calendarWidget : todoWidget + sourceComponent: modelData.widget } } } diff --git a/.config/quickshell/modules/sidebarRight/calendar/CalendarWidget.qml b/.config/quickshell/modules/sidebarRight/calendar/CalendarWidget.qml index 0000e2577..8da0e1e07 100644 --- a/.config/quickshell/modules/sidebarRight/calendar/CalendarWidget.qml +++ b/.config/quickshell/modules/sidebarRight/calendar/CalendarWidget.qml @@ -6,11 +6,13 @@ import QtQuick.Controls import QtQuick.Layouts Item { + // Layout.topMargin: 10 + anchors.topMargin: 10 property int monthShift: 0 property var viewingDate: CalendarLayout.getDateInXMonthsTime(monthShift) property var calendarLayout: CalendarLayout.getCalendarLayout(viewingDate, monthShift === 0) width: calendarColumn.width - height: calendarColumn.height + implicitHeight: calendarColumn.height + 10 * 2 MouseArea { anchors.fill: parent diff --git a/.config/quickshell/modules/sidebarRight/todo/TaskList.qml b/.config/quickshell/modules/sidebarRight/todo/TaskList.qml index 8248a14ca..0edab9613 100644 --- a/.config/quickshell/modules/sidebarRight/todo/TaskList.qml +++ b/.config/quickshell/modules/sidebarRight/todo/TaskList.qml @@ -6,29 +6,120 @@ import QtQuick.Controls import QtQuick.Layouts Item { + id: root required property var taskList; + property int todoListItemSpacing: 5 + property int todoListItemPadding: 8 - Flickable { + Flickable { // Scrolled window anchors.fill: parent - contentHeight: column.height + contentHeight: columnLayout.height clip: true ColumnLayout { - id: column + id: columnLayout width: parent.width + spacing: 0 Repeater { model: taskList - delegate: Rectangle { + delegate: Item { + id: todoItem + property bool pendingDoneToggle: false + property bool pendingDelete: false + Layout.fillWidth: true - width: parent.width - height: 40 - color: Appearance.colors.colLayer2 - radius: Appearance.rounding.small - Text { - text: modelData.content - anchors.verticalCenter: parent.verticalCenter + implicitHeight: todoItemRectangle.implicitHeight + todoListItemSpacing + height: implicitHeight + clip: true + + // Behavior on implicitHeight { + // NumberAnimation { + // duration: Appearance.animation.elementDecel.duration + // easing.type: Appearance.animation.elementDecel.type + // } + // } + + function startAction() { + todoItem.implicitHeight = 0 + actionTimer.start() + } + + Timer { + id: actionTimer + interval: Appearance.animation.elementDecelFast.duration + ConfigOptions.hacks.arbitraryRaceConditionDelay + 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.leftMargin: 8 + anchors.right: parent.right + anchors.bottom: parent.bottom + implicitHeight: todoContentRowLayout.implicitHeight + todoListItemPadding * 2 + 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 + Item { + Layout.fillWidth: true + } + // layoutDirection: Qt.RightToLeft + TodoItemActionButton { + Layout.fillWidth: false + onClicked: { + todoItem.pendingDoneToggle = true + todoItem.startAction() + // if (!modelData.done) Todo.markDone(modelData.originalIndex) + // else Todo.markUnfinished(modelData.originalIndex) + } + contentItem: MaterialSymbol { + anchors.centerIn: parent + horizontalAlignment: Text.AlignHCenter + text: modelData.done ? "remove_done" : "check" + font.pixelSize: Appearance.font.pixelSize.larger + color: Appearance.colors.colOnLayer1 + } + } + TodoItemActionButton { + Layout.fillWidth: false + onClicked: { + todoItem.pendingDelete = true + todoItem.startAction() + // Todo.deleteItem(modelData.originalIndex) + } + contentItem: MaterialSymbol { + anchors.centerIn: parent + horizontalAlignment: Text.AlignHCenter + text: "delete_forever" + font.pixelSize: Appearance.font.pixelSize.larger + color: Appearance.colors.colOnLayer1 + } + } + } + } } } } diff --git a/.config/quickshell/modules/sidebarRight/todo/TodoItemActionButton.qml b/.config/quickshell/modules/sidebarRight/todo/TodoItemActionButton.qml new file mode 100644 index 000000000..4bfdf61fe --- /dev/null +++ b/.config/quickshell/modules/sidebarRight/todo/TodoItemActionButton.qml @@ -0,0 +1,46 @@ +import "root:/modules/common" +import "root:/modules/common/widgets" +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +Button { + id: button + property string buttonText: "" + property string tooltipText: "" + + implicitHeight: 30 + implicitWidth: implicitHeight + + Behavior on implicitWidth { + SmoothedAnimation { + velocity: Appearance.animation.elementDecel.velocity + } + } + + background: Rectangle { + anchors.fill: parent + radius: Appearance.rounding.full + color: (button.down) ? Appearance.colors.colLayer2Active : (button.hovered ? Appearance.colors.colLayer2Hover : Appearance.transparentize(Appearance.colors.colLayer2, 1)) + + Behavior on color { + ColorAnimation { + duration: Appearance.animation.elementDecel.duration + easing.type: Appearance.animation.elementDecel.type + } + + } + + } + contentItem: StyledText { + text: buttonText + horizontalAlignment: Text.AlignHCenter + font.pixelSize: Appearance.font.pixelSize.larger + color: Appearance.colors.colOnLayer1 + } + + StyledToolTip { + content: tooltipText + extraVisibleCondition: tooltipText.length > 0 + } +} \ No newline at end of file diff --git a/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml b/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml index b7923d45a..f7ec48ee1 100644 --- a/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml +++ b/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml @@ -36,7 +36,7 @@ Item { delegate: StyledTabButton { selected: (index == currentTab) buttonText: modelData.name - // buttonIcon: modelData.icon + buttonIcon: modelData.icon } } } @@ -71,10 +71,14 @@ Item { // To Do tab TaskList { - taskList: Todo.list.filter(item => !item.done) + taskList: Todo.list + .map(function(item, i) { return Object.assign({}, item, {originalIndex: i}); }) + .filter(function(item) { return !item.done; }) } TaskList { - taskList: Todo.list.filter(item => item.done) + taskList: Todo.list + .map(function(item, i) { return Object.assign({}, item, {originalIndex: i}); }) + .filter(function(item) { return item.done; }) } } diff --git a/.config/quickshell/services/Todo.qml b/.config/quickshell/services/Todo.qml index 7bd82e76d..96b90de95 100644 --- a/.config/quickshell/services/Todo.qml +++ b/.config/quickshell/services/Todo.qml @@ -19,6 +19,17 @@ Singleton { function markDone(index) { if (index >= 0 && index < list.length) { list[index].done = true + // Reassign to trigger onListChanged + root.list = list.slice(0) + todoFileView.setText(JSON.stringify(root.list)) + } + } + + function markUnfinished(index) { + if (index >= 0 && index < list.length) { + list[index].done = false + // Reassign to trigger onListChanged + root.list = list.slice(0) todoFileView.setText(JSON.stringify(root.list)) } } @@ -26,6 +37,8 @@ Singleton { function deleteItem(index) { if (index >= 0 && index < list.length) { list.splice(index, 1) + // Reassign to trigger onListChanged + root.list = list.slice(0) todoFileView.setText(JSON.stringify(root.list)) } } diff --git a/.config/quickshell/shell.qml b/.config/quickshell/shell.qml index d253053d6..cda323564 100644 --- a/.config/quickshell/shell.qml +++ b/.config/quickshell/shell.qml @@ -10,16 +10,9 @@ import QtQuick.Window import Quickshell ShellRoot { - Bar { - } - - SidebarRight { - } - - ScreenCorners { - } - - ReloadPopup { - } - + Bar {} + SidebarRight {} + ScreenCorners {} + ReloadPopup {} } +