Switch from Qt6 to Slint

This commit is contained in:
Vyn 2024-08-16 21:35:12 +02:00
parent f8be14bcf8
commit 63bf267a22
107 changed files with 27532 additions and 2896 deletions

55
ui/Backend.slint Normal file
View file

@ -0,0 +1,55 @@
import { Date, Time } from "std-widgets.slint";
export struct TaskData {
sourceId: int,
id: int,
title: string,
checked: bool,
tags: [string],
}
export struct Event {
sourceId: int,
id: int,
title: string,
startsAt: Time,
endsAt: Time,
tasks: [TaskData],
}
export struct Day {
sourceId: int,
id: int,
date: Date,
events: [Event],
tasks: [TaskData],
isLate: bool,
isToday: bool
}
struct OpenNewTaskFormParams {
eventSourceId: int,
eventId: int,
}
export global Backend {
in-out property<[string]> resources;
in-out property<[string]> tags;
in-out property<[Day]> visible_tasks;
callback task_clicked(int, int);
callback source_clicked(int);
callback tag_clicked(int);
callback open_new_task_form(OpenNewTaskFormParams);
callback open_edit_task_form(int, int);
callback open_new_event_form();
callback open_edit_event_form(int, int);
callback toggle_show_completed_tasks();
callback delete_task_clicked(int, int);
callback delete_event_clicked(int, int);
pure callback formatDate(Date) -> string;
}

90
ui/EventGroup.slint Normal file
View file

@ -0,0 +1,90 @@
import { Backend, TaskData, Event } from "Backend.slint";
import { ScrollView } from "std-widgets.slint";
import { SideBar } from "SideBar.slint";
import { VCheckBox, VButton, VTag, VPopupIconMenu, VText, Palette } from "@vynui";
import { TaskLine } from "TaskLine.slint";
import { Utils } from "Utils.slint";
export component EventGroup {
in property<Event> event;
eventPopup := VPopupIconMenu {
VButton {
icon-source: @image-url("./images/add.png");
icon-colorize: Colors.greenyellow;
icon-size: 1.5rem;
border-radius: 0;
clicked => { Backend.open_new_task_form({
eventSourceId: event.sourceId,
eventId: event.id,
})}
}
VButton {
icon-source: @image-url("./images/edit.png");
icon-colorize: Colors.grey;
icon-size: 1.5rem;
border-radius: 0;
clicked => { Backend.open_edit_event_form(event.sourceId, event.id) }
}
VButton {
icon-source: @image-url("./images/delete.png");
icon-colorize: Colors.pink;
icon-size: 1.5rem;
border-radius: 0;
clicked => { Backend.delete_event_clicked(event.sourceId, event.id) }
}
}
ta := TouchArea {
pointer-event(e) => {
if (e.button == PointerEventButton.right && e.kind == PointerEventKind.up) {
eventPopup.show(ta.mouse-x, ta.mouse-y);
}
}
}
HorizontalLayout {
VerticalLayout {
spacing: 4px;
VText {
text: Utils.timeToString(event.startsAt);
color: Palette.accent;
}
HorizontalLayout {
alignment: center;
Rectangle {
width: 4px;
background: Palette.accent;
border-radius: 8px;
}
}
VText {
text: Utils.timeToString(event.endsAt);
color: Palette.accent;
}
}
VerticalLayout {
horizontal-stretch: 1;
padding: 16px;
padding-top: 32px;
padding-bottom: 32px;
padding-right: 0px;
VText {
text: event.title;
font-size: 1.1rem;
}
for task[taskIndex] in event.tasks: VerticalLayout {
padding-top: taskIndex == 0 ? 16px : 0px;
padding-bottom: 8px;
TaskLine {
task: task;
source-index: task.sourceId;
task-index: task.id;
}
}
}
}
}

109
ui/MainView.slint Normal file
View file

@ -0,0 +1,109 @@
import { Backend, TaskData } from "Backend.slint";
import { Button, VerticalBox, CheckBox, ScrollView } from "std-widgets.slint";
import { SideBar } from "SideBar.slint";
import { TaskLine } from "TaskLine.slint";
import { EventGroup } from "EventGroup.slint";
import { VPopupIconMenu, VCheckBox, VButton, VTag, VText, Palette } from "@vynui";
export component MainView inherits Rectangle {
background: Palette.background;
private property<image> icon-visible: @image-url("./images/visible.png");
private property<image> icon-not-visible: @image-url("./images/not-visible.png");
private property<bool> completed-tasks-visible: false;
pure function formatZeroPadding(number: int) -> string {
if (number < 10) {
return "0\{number}";
}
return number;
}
VerticalLayout {
horizontal-stretch: 1;
padding: 16px;
spacing: 16px;
alignment: start;
HorizontalLayout {
horizontal-stretch: 1;
alignment: start;
spacing: 8px;
VButton {
text: "New task";
clicked => { Backend.open_new_task_form({
eventSourceId: -1,
eventId: -1,
})}
icon-source: @image-url("./images/add.png");
icon-colorize: Colors.greenyellow;
}
VButton {
text: "New event";
clicked => { Backend.open_new_event_form() }
icon-source: @image-url("./images/add.png");
icon-colorize: Colors.greenyellow;
}
VButton {
text: "Show/Hide completed tasks";
clicked => {
Backend.toggle_show_completed_tasks();
completed-tasks-visible = !completed-tasks-visible;
}
icon-source: completed-tasks-visible ? icon-visible : icon-not-visible;
icon-colorize: Palette.control-foreground;
}
}
Rectangle {
horizontal-stretch: 1;
background: Palette.background.brighter(0.2);
height: 1px;
}
Flickable {
horizontal-stretch: 1;
VerticalLayout {
alignment: start;
spacing: 16px;
for day[dayIndex] in Backend.visible_tasks: VerticalLayout {
Rectangle {
background: Palette.card-background;
border-radius: 8px;
VerticalLayout {
padding: 16px;
HorizontalLayout {
alignment: start;
VText {
text: Backend.formatDate(day.date);
color: day.isLate ? Palette.orange : Palette.foreground;
font-size: 1.2rem;
}
if day.isToday : VerticalLayout {
alignment: center;
VText {
text: " - Today";
color: Palette.foreground-hint;
font-size: 1rem;
}
}
}
for event[eventIndex] in day.events: VerticalLayout {
padding-top: 16px;
EventGroup {
event: event;
}
}
for task[taskIndex] in day.tasks: VerticalLayout {
padding-top: taskIndex == 0 ? 16px : 0px;
padding-bottom: 8px;
TaskLine {
task: task;
source-index: task.sourceId;
task-index: task.id;
}
}
}
}
}
}
}
}
}

35
ui/SideBar.slint Normal file
View file

@ -0,0 +1,35 @@
import { Backend } from "Backend.slint";
import { ToggleButton, VText, Palette } from "@vynui";
export component SideBar inherits Rectangle {
background: Palette.pane;
VerticalLayout {
alignment: start;
padding: 16px;
spacing: 16px;
VText {
text: "Sources";
font-size: 1.5rem;
}
VerticalLayout {
spacing: 4px;
for item[index] in Backend.resources: ToggleButton {
text: item;
text-alignment: left;
clicked => { Backend.source_clicked(index) }
}
}
/*VText {
text: "Tags";
font-size: 1.5rem;
}
VerticalLayout {
spacing: 4px;
for item[index] in Backend.tags: ToggleButton {
text: item;
text-alignment: left;
clicked => { Backend.tag_clicked(index) }
}
}*/
}
}

87
ui/TaskLine.slint Normal file
View file

@ -0,0 +1,87 @@
import { Backend, TaskData } from "Backend.slint";
import { ToggleButton } from "@vynui";
import { VPopupIconMenu, VTag, VButton, VCheckBox, Palette } from "@vynui";
export component TaskLine {
in property<TaskData> task;
in property<int> source-index: -1;
in property<int> task-index: -1;
private property<length> popup-x: 200px;
private property<length> popup-y: 200px;
init => {
if (task-index == -1) {
debug("Error: missing task-index")
}
}
popup := VPopupIconMenu {
VButton {
icon-source: @image-url("./images/edit.png");
icon-colorize: Colors.grey;
icon-size: 1.5rem;
border-radius: 0;
clicked => { Backend.open_edit_task_form(source-index, task-index) }
}
VButton {
icon-source: @image-url("./images/delete.png");
icon-colorize: Colors.pink;
icon-size: 1.5rem;
border-radius: 0;
clicked => { Backend.delete_task_clicked(source-index, task-index) }
}
}
ta := TouchArea {
clicked => {
checkbox.checked = !checkbox.checked;
Backend.task_clicked(source-index, task-index);
}
pointer-event(e) => {
if (e.button == PointerEventButton.right && e.kind == PointerEventKind.up) {
popup.show(ta.mouse-x, ta.mouse-y);
}
}
z: 10;
}
HorizontalLayout {
alignment: space-between;
HorizontalLayout {
alignment: start;
spacing: 8px;
checkbox := VCheckBox {
text: task.title;
checked: task.checked;
toggled => {
Backend.task_clicked(source-index, task-index)
}
}
for tag[tag-index] in task.tags: VTag {
text: tag;
size: 0.8rem;
}
}
HorizontalLayout {
alignment: end;
spacing: 8px;
// Not needed anymore, to remove later
/*VButton {
icon-source: @image-url("./images/edit.png");
icon-colorize: Colors.grey;
clicked => { Backend.open_edit_task_form(source-index, task-index) }
}
VButton {
icon-source: @image-url("./images/delete.png");
icon-colorize: Colors.pink;
clicked => { Backend.delete_task_clicked(source-index, task-index) }
}*/
}
}
}

14
ui/Utils.slint Normal file
View file

@ -0,0 +1,14 @@
import { Time } from "std-widgets.slint";
export global Utils {
pure function formatZeroPadding(number: int) -> string {
if (number < 10) {
return "0\{number}";
}
return number;
}
public pure function timeToString(time: Time) -> string {
return "\{formatZeroPadding(time.hour)}h\{formatZeroPadding(time.minute)}";
}
}

26
ui/appwindow.slint Normal file
View file

@ -0,0 +1,26 @@
import { Backend } from "Backend.slint";
import { Button, VerticalBox, CheckBox, Palette } from "std-widgets.slint";
import { SideBar } from "SideBar.slint";
import { MainView } from "MainView.slint";
import { TaskEdit } from "windows/TaskEdit.slint";
import { EventWindow } from "windows/EventWindow.slint";
export component AppWindow inherits Window {
title: "Mirai";
min-height: 100px;
max-height: 4000px; // needed, otherwise the window wants to fit the content (on Swaywm)
default-font-size: 16px;
in-out property<string> test;
HorizontalLayout {
SideBar {}
MainView {
horizontal-stretch: 1;
}
}
}
export { Backend, TaskEdit, EventWindow } // Export to make it visible to the C++ backend

BIN
ui/images/add.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

BIN
ui/images/calendar.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

BIN
ui/images/delete.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

BIN
ui/images/edit.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
ui/images/not-visible.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

BIN
ui/images/settings.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

BIN
ui/images/task.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
ui/images/visible.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

View file

@ -0,0 +1,101 @@
import { Date, Time, ComboBox } from "std-widgets.slint";
import { VTimePicker, VDatePicker, VButton, VTextInput, VText, Palette } from "@vynui";
import { Backend } from "../Backend.slint";
export struct NewEventParams {
sourceId: int,
title: string,
date: Date,
startsAt: Time,
endsAt: Time
}
export struct SaveEventParams {
sourceId: int,
id: int,
title: string,
date: Date,
startsAt: Time,
endsAt: Time
}
export component EventWindow inherits Window {
title: "Mirai - " + (eventId == -1 ? "New event" : "Edit event");
min-width: 100px;
max-width: 1920px;
preferred-width: 512px;
min-height: 100px;
max-height: 4000px;
default-font-size: 16px;
background: Palette.background;
in-out property<int> sourceId <=> resourceInput.current-index;
in-out property<int> eventId: -1;
in-out property<Date> taskDate <=> taskDateInput.date;
in-out property<string> taskTitle <=> taskTitleInput.text;
in-out property<Time> startsAt <=> eventStartTimeInput.time;
in-out property<Time> endsAt <=> eventEndTimeInput.time;
callback create(NewEventParams);
callback save(SaveEventParams);
VerticalLayout {
padding: 16px;
spacing: 8px;
VText {
text: eventId == -1 ? "New event" : "Edit event";
}
resourceInput := ComboBox {
model: Backend.resources;
enabled: eventId == -1;
}
taskTitleInput := VTextInput {
label: "Title";
wrap: word-wrap;
}
taskDateInput := VDatePicker {
label: "Date";
}
HorizontalLayout {
spacing: 4px;
eventStartTimeInput := VTimePicker {
label: "Starts at";
}
eventEndTimeInput := VTimePicker {
label: "Ends at";
}
}
VButton {
text: eventId == -1 ? "Create" : "Save";
clicked => {
if (eventId == -1) {
create({
sourceId: sourceId,
title: taskTitle,
date: taskDate,
startsAt: startsAt,
endsAt: endsAt,
});
} else {
save({
sourceId: sourceId,
id: eventId,
title: taskTitle,
date: taskDate,
startsAt: startsAt,
endsAt: endsAt,
});
}
}
}
}
}
export { Backend }

85
ui/windows/TaskEdit.slint Normal file
View file

@ -0,0 +1,85 @@
import { Date, ComboBox } from "std-widgets.slint";
import { VTextInput, VButton, VDatePicker, VText, Palette } from "@vynui";
import { Backend } from "../Backend.slint";
import { Palette } from "../../lib/slint-vynui/Palette.slint";
export struct NewTaskData {
sourceId: int,
eventId: int,
title: string,
date: Date
}
export struct SaveTaskData {
sourceId: int,
id: int,
title: string,
date: Date,
}
export component TaskEdit inherits Window {
title: "Mirai - " + (taskId == -1 ? "New task" : "Edit task");
min-width: 100px;
max-width: 1920px;
preferred-width: 512px;
min-height: 100px;
max-height: 4000px;
default-font-size: 16px;
background: Palette.background;
in-out property<int> taskId: -1;
in-out property<int> eventId: -1;
in-out property<Date> taskDate <=> taskDateInput.date;
in-out property<string> taskTitle <=> taskTitleInput.text;
in-out property<int> taskResourceIndex <=> resourceInput.current-index;
callback create(NewTaskData);
callback save(SaveTaskData);
VerticalLayout {
padding: 16px;
spacing: 8px;
VText {
text: taskId == -1 ? "New task" : "Edit task";
}
resourceInput := ComboBox {
model: Backend.resources;
enabled: taskId == -1 && eventId == -1;
}
taskTitleInput := VTextInput {
label: "Content";
wrap: word-wrap;
accepted => { button.clicked() }
}
taskDateInput := VDatePicker {
label: "Date";
enabled: eventId == -1;
}
button := VButton {
text: taskId == -1 ? "Create" : "Save";
clicked => {
if (taskId == -1) {
create({
sourceId: taskResourceIndex,
eventId: eventId,
title: taskTitle,
date: taskDate,
});
} else {
save({
sourceId: taskResourceIndex,
id: taskId,
title: taskTitle,
date: taskDate,
});
}
}
}
}
}
export { Backend }