Switch from Qt6 to Slint
55
ui/Backend.slint
Normal 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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
After Width: | Height: | Size: 5.7 KiB |
BIN
ui/images/calendar.png
Normal file
After Width: | Height: | Size: 4.3 KiB |
BIN
ui/images/delete.png
Normal file
After Width: | Height: | Size: 2 KiB |
BIN
ui/images/edit.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
ui/images/not-visible.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
ui/images/settings.png
Normal file
After Width: | Height: | Size: 3.7 KiB |
BIN
ui/images/task.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
ui/images/visible.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
101
ui/windows/EventWindow.slint
Normal 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
|
@ -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 }
|