diff --git a/.gitignore b/.gitignore index 11ce06f..800d22f 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ todo.md .clangd .cache .nvimrc +CMakeLists.txt.user diff --git a/CMakeLists.txt b/CMakeLists.txt index 15aca42..6442944 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,6 +39,7 @@ qt_add_qml_module(mirai #RESOURCE_PREFIX /qt/qml QML_FILES src/qml/Main.qml + src/qml/MainPanel.qml src/qml/SideMenu.qml src/qml/DatePicker.qml src/qml/DateField.qml diff --git a/libs/cpp-utils/debug.h b/libs/cpp-utils/debug.h index 2a21dfd..859a660 100644 --- a/libs/cpp-utils/debug.h +++ b/libs/cpp-utils/debug.h @@ -19,19 +19,19 @@ namespace cpputils::debug class Timer { public: - Timer() : startTime(std::chrono::high_resolution_clock::now()) + Timer() : startTime(std::chrono::steady_clock::now()) { } void start() { - startTime = std::chrono::high_resolution_clock::now(); + startTime = std::chrono::steady_clock::now(); isRunning = true; } void stop() { - const auto now = std::chrono::high_resolution_clock::now(); + const auto now = std::chrono::steady_clock::now(); duration += std::chrono::duration_cast(now - startTime).count(); isRunning = false; } @@ -46,7 +46,7 @@ class Timer { long durationToShow = duration; if (isRunning) { - const auto now = std::chrono::high_resolution_clock::now(); + const auto now = std::chrono::steady_clock::now(); durationToShow += std::chrono::duration_cast(now - startTime).count(); } @@ -55,7 +55,7 @@ class Timer } private: - std::chrono::time_point startTime; + std::chrono::time_point startTime; // Timer are always running when created. You can use .reset() after creation. bool isRunning = true; diff --git a/src/Backend.cpp b/src/Backend.cpp index 02bb410..90c45d7 100644 --- a/src/Backend.cpp +++ b/src/Backend.cpp @@ -11,6 +11,7 @@ #include "core/TodoMd.h" #include "cpp-utils/debug.h" #include +#include #include #include #include @@ -28,41 +29,42 @@ Backend::Backend() : todoView(&mirai) QFile loadFile(QDir::homePath() + "/.config/mirai/config.json"); readConfigDuration.printTimeElapsed("Read config duration"); - if (!loadFile.open(QIODevice::ReadOnly)) { - qWarning() << "Couldn't find existing config file"; - exit(1); - } - - QByteArray loadData = loadFile.readAll(); - QJsonDocument json = QJsonDocument::fromJson(loadData); - loadFile.close(); - if (!json.isObject()) { - qWarning() << "config.json is not a valid config file"; - exit(1); - } - QJsonObject jsonRootObject = json.object(); - - auto jsonFilesPath = json["files"]; - if (!jsonFilesPath.isArray()) { - qWarning() << "config.json should contains a 'files' string array"; - exit(1); - } - for (const QJsonValueRef &filePath : jsonFilesPath.toArray()) { - cpputils::debug::Timer loadingFileDuration; - mirai.loadFile(filePath.toString().toStdString()); - loadingFileDuration.printTimeElapsed( - "Loading file duration of " + filePath.toString().toStdString() - ); - } - - auto jsonTagsConfig = json["tags"]; - if (jsonTagsConfig.isObject()) { - for (auto &jsonTagConfigKey : jsonTagsConfig.toObject().keys()) { - tagsConfig[jsonTagConfigKey] = - jsonTagsConfig.toObject()[jsonTagConfigKey].toObject()["color"].toString(); + if (loadFile.open(QIODevice::ReadOnly)) { + QByteArray loadData = loadFile.readAll(); + QJsonDocument json = QJsonDocument::fromJson(loadData); + loadFile.close(); + if (!json.isObject()) { + qWarning() << "config.json is not a valid config file"; + exit(1); } + QJsonObject jsonRootObject = json.object(); + + auto jsonFilesPath = json["files"]; + if (!jsonFilesPath.isArray()) { + qWarning() << "config.json should contains a 'files' string array"; + exit(1); + } + for (const QJsonValueRef &filePath : jsonFilesPath.toArray()) { + cpputils::debug::Timer loadingFileDuration; + mirai.loadFile(filePath.toString().toStdString()); + loadingFileDuration.printTimeElapsed( + "Loading file duration of " + filePath.toString().toStdString() + ); + } + + auto jsonTagsConfig = json["tags"]; + if (jsonTagsConfig.isObject()) { + for (auto &jsonTagConfigKey : jsonTagsConfig.toObject().keys()) { + tagsConfig[jsonTagConfigKey] = + jsonTagsConfig.toObject()[jsonTagConfigKey].toObject()["color"].toString(); + } + } + } else { + qWarning() << "Couldn't find existing config file"; } + + cpputils::debug::Timer updatingViewDuration; todoView.update(); updatingViewDuration.printTimeElapsed("Updating view duration"); diff --git a/src/qml/Main.qml b/src/qml/Main.qml index 07e73ff..75c99a3 100644 --- a/src/qml/Main.qml +++ b/src/qml/Main.qml @@ -51,123 +51,65 @@ Window { taskFormPopup.open() } + Component { + id: sideMenuComponent + SideMenu { + id: sideMenu + + } + } + + Component { + id: mainPanelComponent + MainPanel { + } + } + RowLayout { + id: desktopLayout anchors.fill: parent spacing: 0 - - Rectangle { - color: MiraiColorPalette.pane - Layout.preferredWidth: childrenRect.width + 20 - Layout.fillHeight: true - SideMenu { - anchors.top: parent.top - anchors.bottom: parent.bottom - anchors.left: parent.left - anchors.margins: 10 - } - } - - Rectangle { - color: colorPalette.selected.background - Layout.fillWidth: true - Layout.fillHeight: true - ColumnLayout { - anchors.fill: parent - anchors.margins: 10 - spacing: 16 - - TabSelector { - Layout.fillWidth: true - Layout.preferredHeight: childrenRect.height - - tabs: [ - { - label: "Todo", - onClick: () => { - backend.hideCompletedTasks(true) // Little workaround for now. - root.selectedView = "list" - }, - selected: root.selectedView === "list" - }, - { - label: "Calendar", - onClick: () => { - backend.hideCompletedTasks(false) // Little workaround for now. - root.selectedView = "calendar" - }, - selected: root.selectedView === "calendar" - } - ] - - } - - AppButton { - icon.source: "qrc:/qt/qml/Mirai/src/images/add.png" - icon.color: colorPalette.selected.palette.green - text: "Add task" - onClicked: { - root.newTask() - } - } - - Component { - id: listViewComponent - ListView { - - } - } - - Component { - id: calendarViewComponent - CalendarView { - - } - } - - Component { - id: gettingStartedComponent - AppText { - text: "You currently have no files loaded, you can add them by clicking on the cog icon on the left pane" - anchors.centerIn: parent - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } - } - - Loader { - sourceComponent: backend.tasks.length === 0 ? gettingStartedComponent - : selectedView === "list" ? listViewComponent - : selectedView === "calendar" ? calendarViewComponent - : undefined - Layout.fillWidth: true - Layout.fillHeight: true - } - - - - Popup { - id: taskFormPopup - width: parent.width * 0.75 - implicitHeight: taskForm.height + padding * 2 - x: Math.round((parent.width - width) / 2) - y: Math.round((parent.height * 0.4) / 2) - padding: 8 - background: Rectangle { - border.color: colorPalette.selected.modalBorder - border.width: 2 - color: colorPalette.selected.pane - radius: 4 - } - TaskForm { - id: taskForm - width: parent.width - onConfirmed: { - taskFormPopup.close() - } - } - } - } - } + Loader { + sourceComponent: sideMenuComponent + Layout.preferredWidth: item.width + Layout.fillHeight: true + } + Loader { + sourceComponent: mainPanelComponent + Layout.fillWidth: true + Layout.fillHeight: true + } + } + + SwipeView { + id: phoneLayout + anchors.fill: parent + spacing: 0 + + Loader { + sourceComponent: sideMenuComponent + Layout.preferredWidth: item.width + Layout.fillHeight: true + } + Loader { + sourceComponent: mainPanelComponent + Layout.fillWidth: true + Layout.fillHeight: true + } + } + + function setFittingLayout() { + if (width > height) { + desktopLayout.visible = true + phoneLayout.visible = false + } else { + desktopLayout.visible = false + phoneLayout.visible = true + phoneLayout.setCurrentIndex(1) + } + } + onWidthChanged: setFittingLayout() + Component.onCompleted: setFittingLayout() } diff --git a/src/qml/MainPanel.qml b/src/qml/MainPanel.qml new file mode 100644 index 0000000..24fd896 --- /dev/null +++ b/src/qml/MainPanel.qml @@ -0,0 +1,112 @@ +/* + * Mirai. Copyright (C) 2024 Vyn + * This file is licensed under version 3 of the GNU General Public License (GPL-3.0-only) + * The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt + */ + +import QtQuick +import QtQuick.Window +import QtQuick.Controls +import QtQuick.Layouts +import Mirai + +Rectangle { + color: colorPalette.selected.background + + ColumnLayout { + anchors.fill: parent + anchors.margins: 10 + spacing: 16 + + TabSelector { + Layout.fillWidth: true + Layout.preferredHeight: childrenRect.height + + tabs: [ + { + label: "Todo", + onClick: () => { + backend.hideCompletedTasks(true) // Little workaround for now. + root.selectedView = "list" + }, + selected: root.selectedView === "list" + }, + { + label: "Calendar", + onClick: () => { + backend.hideCompletedTasks(false) // Little workaround for now. + root.selectedView = "calendar" + }, + selected: root.selectedView === "calendar" + } + ] + + } + + AppButton { + icon.source: "qrc:/qt/qml/Mirai/src/images/add.png" + icon.color: colorPalette.selected.palette.green + text: "Add task" + onClicked: { + root.newTask() + } + } + + Component { + id: listViewComponent + ListView { + + } + } + + Component { + id: calendarViewComponent + CalendarView { + + } + } + + Component { + id: gettingStartedComponent + AppText { + text: "You currently have no files loaded, you can add them by clicking on the cog icon on the left pane" + anchors.centerIn: parent + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + } + + Loader { + sourceComponent: backend.tasks.length === 0 ? gettingStartedComponent + : selectedView === "list" ? listViewComponent + : selectedView === "calendar" ? calendarViewComponent + : undefined + Layout.fillWidth: true + Layout.fillHeight: true + } + + + + Popup { + id: taskFormPopup + width: parent.width * 0.75 + implicitHeight: taskForm.height + padding * 2 + x: Math.round((parent.width - width) / 2) + y: Math.round((parent.height * 0.4) / 2) + padding: 8 + background: Rectangle { + border.color: colorPalette.selected.modalBorder + border.width: 2 + color: colorPalette.selected.pane + radius: 4 + } + TaskForm { + id: taskForm + width: parent.width + onConfirmed: { + taskFormPopup.close() + } + } + } + } +} diff --git a/src/qml/SideMenu.qml b/src/qml/SideMenu.qml index 0d7331a..75bb0e3 100644 --- a/src/qml/SideMenu.qml +++ b/src/qml/SideMenu.qml @@ -10,169 +10,178 @@ import QtQuick.Layouts import QtQuick.Controls import Mirai -ColumnLayout { +Rectangle { - RowLayout { - AppText { - text: "Files" - font.pixelSize: 32 - } + color: MiraiColorPalette.pane + implicitWidth: childrenRect.width + 20 - Item { Layout.fillWidth: true } - AppIcon { - icon.source: "qrc:/qt/qml/Mirai/src/images/settings.png" - icon.color: colorPalette.selected.textPlaceholder - onClicked: { - filesForm.reset(); - filesFormPopup.open(); - } - } - } - - Item { Layout.preferredHeight: 16 } - - Repeater { - model: backend.files - Rectangle { - Layout.preferredHeight: childrenRect.height - Layout.fillWidth: true - color: backend.activeFilesFilter.includes(modelData.name) ? MiraiColorPalette.filterSelected : mouse.hovered ? MiraiColorPalette.filterHovered : "transparent" - radius: 4 + ColumnLayout { + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.margins: 10 + RowLayout { AppText { - text: modelData.name - padding: 4 + text: "Files" + font.pixelSize: 32 } - MouseArea { - anchors.fill: parent + + Item { Layout.fillWidth: true } + AppIcon { + icon.source: "qrc:/qt/qml/Mirai/src/images/settings.png" + icon.color: colorPalette.selected.textPlaceholder onClicked: { - if (backend.activeFilesFilter.includes(modelData.name)) { - backend.removeFileFilter(modelData.name) - } else { - backend.addFileFilter(modelData.name) + filesForm.reset(); + filesFormPopup.open(); + } + } + } + + Item { Layout.preferredHeight: 16 } + + Repeater { + model: backend.files + Rectangle { + Layout.preferredHeight: childrenRect.height + Layout.fillWidth: true + color: backend.activeFilesFilter.includes(modelData.name) ? MiraiColorPalette.filterSelected : mouse.hovered ? MiraiColorPalette.filterHovered : "transparent" + radius: 4 + AppText { + text: modelData.name + padding: 4 + } + MouseArea { + anchors.fill: parent + onClicked: { + if (backend.activeFilesFilter.includes(modelData.name)) { + backend.removeFileFilter(modelData.name) + } else { + backend.addFileFilter(modelData.name) + } + } + HoverHandler { + id: mouse + acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad + cursorShape: Qt.PointingHandCursor } } - HoverHandler { - id: mouse - acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad - cursorShape: Qt.PointingHandCursor - } - } - - } - } - - Item { Layout.preferredHeight: 16 } - - RowLayout { - AppText { - text: "Tags" - font.pixelSize: 32 - } - Item { Layout.fillWidth: true } - AppIcon { - icon.source: "qrc:/qt/qml/Mirai/src/images/settings.png" - icon.color: colorPalette.selected.textPlaceholder - onClicked: { - tagsForm.reset(); - tagsFormPopup.open(); + } } - } - Item { Layout.preferredHeight: 16 } + Item { Layout.preferredHeight: 16 } - Repeater { - model: backend.tags - Rectangle { - Layout.preferredHeight: childrenRect.height - Layout.fillWidth: true - color: backend.activeTagsFilter.includes(modelData.name) ? MiraiColorPalette.filterSelected : mouse.hovered ? MiraiColorPalette.filterHovered : "transparent" - radius: 4 - QtObject { - id: internal - } + RowLayout { AppText { - text: modelData.name - color: { - return modelData.color - } - padding: 4 + text: "Tags" + font.pixelSize: 32 } - MouseArea { - anchors.fill: parent + Item { Layout.fillWidth: true } + AppIcon { + icon.source: "qrc:/qt/qml/Mirai/src/images/settings.png" + icon.color: colorPalette.selected.textPlaceholder onClicked: { - if (backend.activeTagsFilter.includes(modelData.name)) { - backend.removeTagFilter(modelData.name) - } else { - backend.addTagFilter(modelData.name) + tagsForm.reset(); + tagsFormPopup.open(); + } + } + } + + Item { Layout.preferredHeight: 16 } + + Repeater { + model: backend.tags + Rectangle { + Layout.preferredHeight: childrenRect.height + Layout.fillWidth: true + color: backend.activeTagsFilter.includes(modelData.name) ? MiraiColorPalette.filterSelected : mouse.hovered ? MiraiColorPalette.filterHovered : "transparent" + radius: 4 + QtObject { + id: internal + } + AppText { + text: modelData.name + color: { + return modelData.color + } + padding: 4 + } + MouseArea { + anchors.fill: parent + onClicked: { + if (backend.activeTagsFilter.includes(modelData.name)) { + backend.removeTagFilter(modelData.name) + } else { + backend.addTagFilter(modelData.name) + } + } + HoverHandler { + id: mouse + acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad + cursorShape: Qt.PointingHandCursor } } - HoverHandler { - id: mouse - acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad - cursorShape: Qt.PointingHandCursor + + } + } + + Item { + Layout.fillHeight: true + } + + AppButton { + text: `Hide completed tasks: ${backend.shouldHideCompletedTasks ? "ON" : "OFF"}` + onClicked: { + backend.hideCompletedTasks(!backend.shouldHideCompletedTasks) + } + } + + Popup { + parent: Overlay.overlay + id: filesFormPopup + width: parent.width * 0.75 + implicitHeight: filesForm.height + padding * 2 + x: Math.round((parent.width - width) / 2) + y: Math.round((parent.height * 0.4) / 2) + padding: 8 + background: Rectangle { + border.color: colorPalette.selected.modalBorder + border.width: 2 + color: colorPalette.selected.pane + radius: 4 + } + FilesForm { + id: filesForm + width: parent.width + onConfirmed: (filesPath) => { + filesFormPopup.close() + console.log(filesPath) + backend.saveFilesPath(filesPath) } } - } - } - Item { - Layout.fillHeight: true - } - - AppButton { - text: `Hide completed tasks: ${backend.shouldHideCompletedTasks ? "ON" : "OFF"}` - onClicked: { - backend.hideCompletedTasks(!backend.shouldHideCompletedTasks) - } - } - - Popup { - parent: Overlay.overlay - id: filesFormPopup - width: parent.width * 0.75 - implicitHeight: filesForm.height + padding * 2 - x: Math.round((parent.width - width) / 2) - y: Math.round((parent.height * 0.4) / 2) - padding: 8 - background: Rectangle { - border.color: colorPalette.selected.modalBorder - border.width: 2 - color: colorPalette.selected.pane - radius: 4 - } - FilesForm { - id: filesForm - width: parent.width - onConfirmed: (filesPath) => { - filesFormPopup.close() - console.log(filesPath) - backend.saveFilesPath(filesPath) + Popup { + parent: Overlay.overlay + id: tagsFormPopup + width: parent.width * 0.75 + implicitHeight: tagsForm.height + padding * 2 + x: Math.round((parent.width - width) / 2) + y: Math.round((parent.height * 0.4) / 2) + padding: 8 + background: Rectangle { + border.color: colorPalette.selected.modalBorder + border.width: 2 + color: colorPalette.selected.pane + radius: 4 } - } - } - - Popup { - parent: Overlay.overlay - id: tagsFormPopup - width: parent.width * 0.75 - implicitHeight: tagsForm.height + padding * 2 - x: Math.round((parent.width - width) / 2) - y: Math.round((parent.height * 0.4) / 2) - padding: 8 - background: Rectangle { - border.color: colorPalette.selected.modalBorder - border.width: 2 - color: colorPalette.selected.pane - radius: 4 - } - TagsConfigForm { - id: tagsForm - width: parent.width - onConfirmed: (tags) => { - tagsFormPopup.close() - backend.saveTagsColor(tags) + TagsConfigForm { + id: tagsForm + width: parent.width + onConfirmed: (tags) => { + tagsFormPopup.close() + backend.saveTagsColor(tags) + } } } }