Add basic responsive layout for Phone

This commit is contained in:
Vyn 2024-05-04 17:38:23 +02:00
parent 1416b5db58
commit 375a1bfb2d
7 changed files with 360 additions and 293 deletions

1
.gitignore vendored
View file

@ -5,3 +5,4 @@ todo.md
.clangd
.cache
.nvimrc
CMakeLists.txt.user

View file

@ -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

View file

@ -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<std::chrono::microseconds>(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<std::chrono::microseconds>(now - startTime).count();
}
@ -55,7 +55,7 @@ class Timer
}
private:
std::chrono::time_point<std::chrono::system_clock> startTime;
std::chrono::time_point<std::chrono::steady_clock, std::chrono::nanoseconds> startTime;
// Timer are always running when created. You can use .reset() after creation.
bool isRunning = true;

View file

@ -11,6 +11,7 @@
#include "core/TodoMd.h"
#include "cpp-utils/debug.h"
#include <exception>
#include <sstream>
#include <iostream>
#include <ostream>
#include <qjsonarray.h>
@ -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");

View file

@ -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()
}

112
src/qml/MainPanel.qml Normal file
View file

@ -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()
}
}
}
}
}

View file

@ -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)
}
}
}
}