Add a Calendar view on the right side

This commit is contained in:
Vyn 2024-10-29 15:02:46 +01:00
parent a80515ff90
commit f1ac8a42d1
18 changed files with 406 additions and 130 deletions

View file

@ -36,6 +36,8 @@ class MarkdownDataProvider : public DataProvider
std::string toMarkdown();
MarkdownData parseMarkdown(const std::string &content);
std::string path() const;
void save() override;
void load() override;

View file

@ -53,6 +53,8 @@ class Source
void load();
std::string name() const;
std::string type() const;
DataProvider *dataProvider();
void createTask(const createTaskParams &task);
void removeTask(const Task &task);

View file

@ -18,6 +18,11 @@
namespace mirai
{
std::string MarkdownDataProvider::path() const
{
return filePath_;
}
std::string MarkdownDataProvider::name() const
{
return data.name;

View file

@ -82,6 +82,8 @@ void Source::createEvent(const createEventParams &eventToCreate)
.id = generateUniqueId(),
.dayId = day.value().id,
.title = eventToCreate.title,
.startsAt = eventToCreate.startsAt,
.endsAt = eventToCreate.endsAt,
});
};
@ -144,4 +146,15 @@ std::string Source::name() const
{
return data->name();
}
std::string Source::type() const
{
// There is only 1 type for now
return "MarkdownFile";
}
DataProvider *Source::dataProvider()
{
return data;
}
} // namespace mirai

View file

@ -43,6 +43,9 @@ std::vector<Task> View::getTasksForDate(const Date &date)
std::vector<Event> View::getEventsForDate(const Date &date)
{
if (!dates.contains(date)) {
return {};
}
return dates.at(date).events;
}

2
external/selenite vendored

@ -1 +1 @@
Subproject commit cbab9dabe784bd3c799d23ead5d2b4d942ec4081
Subproject commit 33d6a9dee8437979b5a9bf5a716a4053f3ebf2fa

View file

@ -5,10 +5,13 @@
*/
#include "AppWindowBackend.h"
#include "AppWindow.h"
#include "SeleniteSetup.h"
#include "Utils.h"
#include "appwindow.h"
#include "mirai-core/DataProvider.h"
#include "mirai-core/DateTime.h"
#include "mirai-core/Day.h"
#include "mirai-core/MarkdownDataProvider.h"
#include "mirai-core/Mirai.h"
#include "slint.h"
#include "slint_string.h"
@ -34,6 +37,7 @@ AppWindowBackend::AppWindowBackend(mirai::Mirai *miraiInstance)
{
sources_ = std::make_shared<slint::VectorModel<ui::Source>>();
days_ = std::make_shared<slint::VectorModel<ui::Day>>();
calendar_ = std::make_shared<slint::VectorModel<ui::CalendarDay>>();
unscheduledTasks_ = std::make_shared<slint::VectorModel<ui::TaskData>>();
tags_ = std::make_shared<slint::VectorModel<slint::SharedString>>();
auto sourcesNames = std::make_shared<slint::MapModel<ui::Source, slint::SharedString>>(
@ -43,14 +47,23 @@ AppWindowBackend::AppWindowBackend(mirai::Mirai *miraiInstance)
}
);
const auto palette =
selenite::parseJson(std::string(getenv("HOME")) + "/.config/evalyte/theme.json");
const auto palettePath = std::string(getenv("HOME")) + "/.config/evalyte/theme.json";
const auto palette = selenite::parseJson(palettePath);
if (palette.has_value()) {
std::cerr << "Warning, no evalyte/theme.json found" << std::endl;
setSelenitePalette(mainWindow_, palette.value());
std::cerr << "Warning, no " << palettePath << " found" << std::endl;
setSelenitePalette(mainWindow_->global<ui::Palette>(), palette.value());
setSelenitePalette(settingsWindow_->global<ui::Palette>(), palette.value());
}
mainWindow_->global<ui::Backend>().set_sources(sourcesNames);
settingsWindow_->global<ui::Backend>().set_sources(sourcesNames);
mainWindow_->global<ui::Backend>().set_sources_selected(sources_);
settingsWindow_->global<ui::Backend>().set_sources_selected(sources_);
mainWindow_->global<ui::Backend>().set_tags(tags_);
mainWindow_->global<ui::Backend>().set_days(days_);
mainWindow_->global<ui::Backend>().set_calendar(calendar_);
view_.setAllSources();
view_.update();
@ -92,10 +105,17 @@ void AppWindowBackend::setupUtilsCallbacks()
};
return std::format("{:%B %d}", chronoDate);
});
mainWindow_->global<ui::Backend>().on_format_date_relative([&](const ui::Date &date) {
return formatDateRelative(date);
});
}
void AppWindowBackend::setupCallbacks()
{
mainWindow_->global<ui::Backend>().on_settings_clicked([&]() {
settingsWindow_->show();
});
mainWindow_->global<ui::Backend>().on_task_clicked([&](int sourceId, int taskId) {
auto source = miraiInstance_->getSourceById(sourceId);
assert(source);
@ -142,10 +162,6 @@ void AppWindowBackend::setupCallbacks()
reloadTasks();
});
mainWindow_->global<ui::Backend>().set_sources_selected(sources_);
mainWindow_->global<ui::Backend>().set_tags(tags_);
mainWindow_->global<ui::Backend>().set_days(days_);
mainWindow_->global<ui::Backend>().on_save_task([&](ui::SaveTaskData newTaskData) {
auto source = miraiInstance_->getSourceById(newTaskData.sourceId);
assert(source);
@ -326,6 +342,42 @@ void AppWindowBackend::reloadTasks()
});
}
mainWindow_->global<ui::Backend>().set_unscheduled_tasks(unscheduledTasks_);
calendar_->clear();
for (int dayIndex = 0; dayIndex < 7; ++dayIndex) {
std::chrono::year_month_day nextDate =
std::chrono::floor<std::chrono::days>(std::chrono::system_clock::now()) +
std::chrono::days{dayIndex};
auto currentDate = mirai::Date{nextDate};
auto events = view_.getEventsForDate(currentDate);
auto slintEvents = std::make_shared<slint::VectorModel<ui::CalendarDayEvent>>();
auto relativeDaysDiff = std::chrono::duration_cast<std::chrono::days>(
std::chrono::sys_days(currentDate.toStdChrono()) -
std::chrono::sys_days(todayDate.toStdChrono())
)
.count();
if (relativeDaysDiff < 0 || relativeDaysDiff >= 3) {
continue;
}
const std::vector<mirai::Event> eventsForDate = view_.getEventsForDate(currentDate);
for (int eventIndex = 0; eventIndex < eventsForDate.size(); ++eventIndex) {
auto &currentEvent = eventsForDate.at(eventIndex);
slintEvents->push_back(ui::CalendarDayEvent{
.title = slint::SharedString(currentEvent.title()),
.startsAt = MiraiTimeToSlintTime(currentEvent.startsAt()),
.endsAt = MiraiTimeToSlintTime(currentEvent.endsAt()),
});
}
auto calendarDay = ui::CalendarDay{
.events = slintEvents,
.date = MiraiDateToSlintDate(currentDate),
.header =
slint::SharedString(capitalize(formatDateRelative(MiraiDateToSlintDate(currentDate))
))
};
calendar_->push_back(calendarDay);
}
}
void AppWindowBackend::reloadSources()
@ -334,9 +386,12 @@ void AppWindowBackend::reloadSources()
bool noSourceSelected = miraiInstance_->getSources().size() == view_.activeSourceCount();
for (const auto &source : miraiInstance_->getSources()) {
bool isSourceSelected = view_.isSourceSelected(*source);
mirai::MarkdownDataProvider *sourceProvider =
dynamic_cast<mirai::MarkdownDataProvider *>(source->dataProvider());
sources_->push_back(
{.name = slint::SharedString(source->name()),
.selected = isSourceSelected && !noSourceSelected}
.selected = isSourceSelected && !noSourceSelected,
.path = slint::SharedString(sourceProvider->path())}
);
}
mainWindow_->global<ui::Backend>().set_no_source_selected(noSourceSelected);

View file

@ -6,7 +6,7 @@
#pragma once
#include "appwindow.h"
#include "AppWindow.h"
#include "mirai-core/Mirai.h"
#include "mirai-core/View.h"
#include "slint.h"
@ -29,9 +29,11 @@ class AppWindowBackend
std::shared_ptr<slint::VectorModel<ui::Source>> sources_;
std::shared_ptr<slint::VectorModel<slint::SharedString>> tags_;
std::shared_ptr<slint::VectorModel<ui::Day>> days_;
std::shared_ptr<slint::VectorModel<ui::CalendarDay>> calendar_;
std::shared_ptr<slint::VectorModel<ui::TaskData>> unscheduledTasks_;
slint::ComponentHandle<ui::AppWindow> mainWindow_ = ui::AppWindow::create();
slint::ComponentHandle<ui::SettingsWindow> settingsWindow_ = ui::SettingsWindow::create();
mirai::Mirai *miraiInstance_;
mirai::View view_;

View file

@ -6,7 +6,7 @@
#pragma once
#include "appwindow.h"
#include "AppWindow.h"
#include "selenite/palette.h"
#include "slint_color.h"
@ -15,10 +15,8 @@ slint::Color seleniteColorToSlint(const selenite::Color &color)
return slint::Color::from_rgb_uint8(color.r, color.g, color.b);
}
void setSelenitePalette(slint::ComponentHandle<ui::AppWindow> ui, const selenite::Palette &palette)
void setSelenitePalette(const ui::Palette &uiPalette, const selenite::Palette &palette)
{
auto &uiPalette = ui->global<ui::Palette>();
uiPalette.set_background(seleniteColorToSlint(palette.background));
uiPalette.set_pane(seleniteColorToSlint(palette.pane));
uiPalette.set_foreground(seleniteColorToSlint(palette.foreground));

View file

@ -5,6 +5,9 @@
*/
#include "Utils.h"
#include <cctype>
#include <format>
#include <string>
std::string formatZeroPadding(const int number)
{
@ -14,6 +17,29 @@ std::string formatZeroPadding(const int number)
return std::to_string(number);
}
std::string formatDateRelative(const ui::Date &date)
{
auto todayDate = mirai::Date(std::chrono::system_clock::now());
auto relativeDaysDiff = std::chrono::duration_cast<std::chrono::days>(
std::chrono::sys_days(SlintDateToMiraiDate(date).toStdChrono()) -
std::chrono::sys_days(todayDate.toStdChrono())
)
.count();
if (relativeDaysDiff == 0) {
return std::string("today");
} else if (relativeDaysDiff == 1) {
return std::string("tomorrow");
}
return std::format("in {} days", relativeDaysDiff);
};
std::string capitalize(std::string str)
{
str[0] = static_cast<char>(toupper(str[0]));
return str;
};
std::string SlintDateToStdString(const ui::Date &date)
{
return std::to_string(date.year) + "-" + formatZeroPadding(date.month) + "-" +

View file

@ -6,11 +6,13 @@
#pragma once
#include "appwindow.h"
#include "AppWindow.h"
#include "mirai-core/DateTime.h"
#include <string>
std::string formatZeroPadding(const int number);
std::string formatDateRelative(const ui::Date &date);
std::string capitalize(std::string str);
std::string SlintDateToStdString(const ui::Date &date);
mirai::Date SlintDateToMiraiDate(const ui::Date &date);
ui::Date MiraiDateToSlintDate(const mirai::Date &date);

View file

@ -2,6 +2,7 @@ import { Backend } from "Backend.slint";
import { Button, VerticalBox, CheckBox } from "std-widgets.slint";
import { SideBar } from "./components/SideBar.slint";
import { MainView } from "MainView.slint";
import { SettingsWindow } from "SettingsWindow.slint";
import { Palette } from "@selenite";
export component AppWindow inherits Window {
@ -11,8 +12,6 @@ export component AppWindow inherits Window {
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 {
@ -22,4 +21,4 @@ export component AppWindow inherits Window {
}
export { Backend, Palette } // Export to make it visible to the C++ backend
export { Backend, Palette, SettingsWindow } // Export to make it visible to the C++ backend

View file

@ -1,4 +1,5 @@
import { Date, Time } from "std-widgets.slint";
import { CalendarDay } from "components/Calendar.slint";
export struct NewTaskData {
sourceId: int,
@ -35,7 +36,8 @@ export struct SaveEventParams {
export struct Source {
name: string,
selected: bool
selected: bool,
path: string
}
export struct TaskData {
@ -78,11 +80,13 @@ export global Backend {
in-out property<bool> no-source-selected;
in-out property<[string]> tags;
in-out property<[Day]> days;
in-out property<[CalendarDay]> calendar;
in-out property<[TaskData]> unscheduled-tasks;
callback task-clicked(int, int);
callback source-clicked(int);
callback tag-clicked(int);
callback settings-clicked();
callback open-new-task-form(OpenNewTaskFormParams);
callback open-edit-task-form(int, int);
@ -98,4 +102,6 @@ export global Backend {
callback save-event(SaveEventParams);
pure callback format-date(Date) -> string;
pure callback format-date-relative(Date) -> string;
pure callback capitalize-string(string) -> string;
}

View file

@ -2,6 +2,7 @@ import { Backend, TaskData } from "Backend.slint";
import { Button, VerticalBox, CheckBox, ScrollView, ComboBox } from "std-widgets.slint";
import { TaskLine } from "./components/TaskLine.slint";
import { EventGroup } from "./components/EventGroup.slint";
import { Calendar } from "./components/Calendar.slint";
import { VPopupIconMenu, VDatePicker, VTimePicker, VCheckBox, VButton, VTag, VText, VTextInput, Svg, Palette } from "@selenite";
import { NewTaskData, SaveTaskData } from "Backend.slint";
import { CreateTaskOrEvent } from "components/CreateTaskOrEvent.slint";
@ -13,11 +14,12 @@ export component MainView inherits Rectangle {
private property<string> icon-not-visible: Svg.not-visible;
private property<bool> completed-tasks-visible: false;
HorizontalLayout {
VerticalLayout {
horizontal-stretch: 1;
padding: 16px;
spacing: 16px;
alignment: start;
HorizontalLayout {
horizontal-stretch: 1;
alignment: start;
@ -126,4 +128,10 @@ export component MainView inherits Rectangle {
}
}
}
Calendar {
init => { debug("cal len", Backend.calendar.length) }
days: Backend.calendar;
}
}
}

29
ui/SettingsWindow.slint Normal file
View file

@ -0,0 +1,29 @@
import { Backend } from "Backend.slint";
import { Button, VerticalBox, CheckBox } from "std-widgets.slint";
import { SideBar } from "./components/SideBar.slint";
import { MainView } from "MainView.slint";
import { VText, VTextInput, Palette } from "@selenite";
export component SettingsWindow inherits Window {
title: "Mirai - Settings";
min-height: 100px;
max-height: 4000px; // needed, otherwise the window wants to fit the content (on Swaywm)
default-font-size: 16px;
background: Palette.background;
VerticalLayout {
padding: 16px;
spacing: 8px;
for source[source-index] in Backend.sources-selected: VerticalLayout {
VText {
text: source.name;
}
VTextInput {
text: source.path;
}
}
}
}
export { Backend, Palette } // Export to make it visible to the C++ backend

View file

@ -0,0 +1,112 @@
import { ScrollView, Date, Time } from "std-widgets.slint";
import { VCheckBox, VButton, VActionButton, Svg, VTag, VPopupIconMenu, VText, Palette } from "@selenite";
import { Utils } from "../Utils.slint";
export struct CalendarDayEvent {
title: string,
startsAt: Time,
endsAt: Time
}
export struct CalendarDay {
events: [CalendarDayEvent],
date: Date,
header: string,
}
export enum CalendarDateDisplayFormat {
Relative,
Normal
}
export component Calendar inherits Rectangle {
in property<[CalendarDay]> days;
in property <CalendarDateDisplayFormat> format;
private property <length> header-height: 64px;
private property <length> available-day-space: self.height - header-height;
private property <length> day-start-y: header-height;
private property <length> hour-spacing: available-day-space / 24;
background: Palette.pane;
HorizontalLayout {
Rectangle {
//background: red;
width: 48px;
VerticalLayout {
y: 0;
height: header-height;
padding-right: 8px;
VText {
vertical-alignment: center;
horizontal-alignment: right;
text: "";
}
}
for index in 24: VerticalLayout {
y: day-start-y + index * hour-spacing - (hour-spacing / 2);
height: hour-spacing;
padding-right: 8px;
VText {
vertical-alignment: center;
horizontal-alignment: right;
text: "\{index}h";
}
}
}
Rectangle {
//background: green;
HorizontalLayout {
for day[day-index] in root.days: Rectangle {
if day-index > 0 : Rectangle {
x: 0;
y: header-height - 32px;
width: 1px;
height: parent.height;
background: Palette.card-background.transparentize(0.5);
}
VerticalLayout {
y: 0;
height: header-height;
padding-right: 8px;
VText {
vertical-alignment: center;
horizontal-alignment: center;
text: day.header;
}
}
for hour[hour-index] in 24 : Rectangle {
background: Palette.card-background.transparentize(0.5);
x: 0px;
width: parent.width;
y: day-start-y + hour-spacing * hour-index;
height: 1px;
}
for event[event-index] in day.events : Rectangle {
background: Palette.card-background;
border-radius: 4px;
x: 8px;
width: parent.width - 16px;
y: day-start-y + hour-spacing * event.startsAt.hour;
height: hour-spacing * (event.endsAt.hour - event.startsAt.hour) - 2px;
clip: true;
HorizontalLayout {
Rectangle {
width: 4px;
background: Palette.accent;
}
VerticalLayout {
padding: 16px;
VText {
text: event.title;
wrap: TextWrap.word-wrap;
}
}
}
}
}
}
}
}
}

View file

@ -62,7 +62,7 @@ export component EventGroup {
VText {
text: Utils.time-to-string(event.endsAt);
color: Palette.accent;
font-size: 0.8rem;
font-size: 0.9rem;
horizontal-alignment: center;
}
}

View file

@ -1,11 +1,11 @@
import { Backend } from "../Backend.slint";
import { ToggleButton, VText, Palette } from "@selenite";
import { VButton, ToggleButton, VText, Svg, Palette } from "@selenite";
export component SideBar inherits Rectangle {
background: Palette.pane;
VerticalLayout {
alignment: start;
height: parent.height;
padding: 16px;
spacing: 16px;
VText {
@ -13,6 +13,10 @@ export component SideBar inherits Rectangle {
font-size: 1.5rem;
}
VerticalLayout {
alignment: space-between;
vertical-stretch: 1;
VerticalLayout {
vertical-stretch: 1;
spacing: 4px;
ToggleButton {
text: "All";
@ -27,5 +31,15 @@ export component SideBar inherits Rectangle {
clicked => { Backend.source-clicked(index) }
}
}
/*VerticalLayout {
spacing: 4px;
VButton {
icon-svg: Svg.cog;
text: "Settings";
background: transparent;
clicked => { Backend.settings-clicked() }
}
}*/
}
}
}