diff --git a/CMakeLists.txt b/CMakeLists.txt index 083ba41..29f1d42 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,8 +30,12 @@ add_subdirectory(external/evalyte-cpp-common) add_executable(mirai src/main.cpp - src/AppWindowBackend.cpp - src/Utils.cpp + src/windows/AppWindow/AppWindow.cpp + src/windows/AddSourceWindow/AddSourceWindow.cpp + src/windows/SettingsWindow/SettingsWindow.cpp + src/windows/EditSourceWindow/EditSourceWindow.cpp + src/SeleniteSetup.cpp + src/shared/Utils.cpp ) target_include_directories(mirai PRIVATE "external/mirai-core/include") @@ -48,7 +52,7 @@ target_include_directories(mirai PRIVATE "external") target_link_libraries(mirai PRIVATE mirai-core) slint_target_sources( - mirai ui/AppWindow.slint + mirai src/ui.slint NAMESPACE ui LIBRARY_PATHS selenite=${CMAKE_CURRENT_SOURCE_DIR}/external/selenite/components/index.slint ) diff --git a/external/mirai-core/include/mirai-core/EventEmitter.h b/external/mirai-core/include/mirai-core/EventEmitter.h index 41ca3ae..590a90c 100644 --- a/external/mirai-core/include/mirai-core/EventEmitter.h +++ b/external/mirai-core/include/mirai-core/EventEmitter.h @@ -4,6 +4,8 @@ * The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt */ +#pragma once + #include #include @@ -11,7 +13,7 @@ template class EventEmitter { public: - void registerCallback(std::function func) + void registerCallback(std::function func) { callbacks.push_back(func); } @@ -24,5 +26,5 @@ template class EventEmitter } private: - std::vector> callbacks; + std::vector> callbacks; }; diff --git a/external/mirai-core/include/mirai-core/Mirai.h b/external/mirai-core/include/mirai-core/Mirai.h index 8edd526..ce750e1 100644 --- a/external/mirai-core/include/mirai-core/Mirai.h +++ b/external/mirai-core/include/mirai-core/Mirai.h @@ -6,6 +6,7 @@ #pragma once +#include "EventEmitter.h" #include "Source.h" #include #include @@ -35,10 +36,18 @@ class Mirai // Returns a non owning pointer to the requested resource or nullptr if not found. Source *getSourceById(int id); + void onSourceAdded(std::function f); + void onSourceEdited(std::function f); + void onSourceDeleted(std::function f); + private: void loadConfig(const std::string &path); void saveConfig(); std::vector> sources_; std::string configPath_; + + EventEmitter sourceAdded; + EventEmitter sourceEdited; + EventEmitter sourceDeleted; }; } // namespace mirai diff --git a/external/mirai-core/src/Mirai.cpp b/external/mirai-core/src/Mirai.cpp index bf7830b..d6d0f71 100644 --- a/external/mirai-core/src/Mirai.cpp +++ b/external/mirai-core/src/Mirai.cpp @@ -88,6 +88,7 @@ void Mirai::addSource( SourceConstructor{.name = name, .sourceDataProvider = sourceDataProvider} )); saveConfig(); + sourceAdded.emit(nullptr); }; void Mirai::editSource(int id, const std::string &name, const std::string &path) @@ -97,6 +98,7 @@ void Mirai::editSource(int id, const std::string &name, const std::string &path) DataProvider *sourceDataProvider = source->dataProvider(); saveConfig(); + sourceEdited.emit(nullptr); } void Mirai::deleteSource(int id) @@ -113,6 +115,7 @@ void Mirai::deleteSource(int id) ); saveConfig(); + sourceDeleted.emit(id); } void Mirai::unloadAllSources() @@ -146,4 +149,19 @@ Source *Mirai::getSourceById(int id) return source->get(); } +void Mirai::onSourceAdded(std::function f) +{ + sourceAdded.registerCallback(f); +} + +void Mirai::onSourceEdited(std::function f) +{ + sourceEdited.registerCallback(f); +} + +void Mirai::onSourceDeleted(std::function f) +{ + sourceDeleted.registerCallback(f); +} + } // namespace mirai diff --git a/src/SeleniteSetup.cpp b/src/SeleniteSetup.cpp new file mode 100644 index 0000000..f56f865 --- /dev/null +++ b/src/SeleniteSetup.cpp @@ -0,0 +1,28 @@ +/* + * 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 + */ + +#include "SeleniteSetup.h" +#include "selenite/palette.h" + +slint::Color seleniteColorToSlint(const selenite::Color &color) +{ + return slint::Color::from_rgb_uint8(color.r, color.g, color.b); +} + +void setSelenitePalette(const ui::Palette &uiPalette, const selenite::Palette &palette) +{ + uiPalette.set_accent(seleniteColorToSlint(palette.primary)); + uiPalette.set_background(seleniteColorToSlint(palette.background)); + uiPalette.set_pane(seleniteColorToSlint(palette.pane)); + uiPalette.set_foreground(seleniteColorToSlint(palette.foreground)); + uiPalette.set_foreground_hint(seleniteColorToSlint(palette.foregroundHint)); + uiPalette.set_control_background(seleniteColorToSlint(palette.background3)); + uiPalette.set_control_foreground(seleniteColorToSlint(palette.foreground)); + uiPalette.set_card_background(seleniteColorToSlint(palette.background2)); + uiPalette.set_green(seleniteColorToSlint(palette.green)); + uiPalette.set_orange(seleniteColorToSlint(palette.orange)); + uiPalette.set_red(seleniteColorToSlint(palette.red)); +} diff --git a/src/SeleniteSetup.h b/src/SeleniteSetup.h index c263e3c..fbb085d 100644 --- a/src/SeleniteSetup.h +++ b/src/SeleniteSetup.h @@ -6,26 +6,8 @@ #pragma once -#include "AppWindow.h" #include "selenite/palette.h" -#include "slint_color.h" +#include "ui.h" -slint::Color seleniteColorToSlint(const selenite::Color &color) -{ - return slint::Color::from_rgb_uint8(color.r, color.g, color.b); -} - -void setSelenitePalette(const ui::Palette &uiPalette, const selenite::Palette &palette) -{ - uiPalette.set_accent(seleniteColorToSlint(palette.primary)); - uiPalette.set_background(seleniteColorToSlint(palette.background)); - uiPalette.set_pane(seleniteColorToSlint(palette.pane)); - uiPalette.set_foreground(seleniteColorToSlint(palette.foreground)); - uiPalette.set_foreground_hint(seleniteColorToSlint(palette.foregroundHint)); - uiPalette.set_control_background(seleniteColorToSlint(palette.background3)); - uiPalette.set_control_foreground(seleniteColorToSlint(palette.foreground)); - uiPalette.set_card_background(seleniteColorToSlint(palette.background2)); - uiPalette.set_green(seleniteColorToSlint(palette.green)); - uiPalette.set_orange(seleniteColorToSlint(palette.orange)); - uiPalette.set_red(seleniteColorToSlint(palette.red)); -} +slint::Color seleniteColorToSlint(const selenite::Color &color); +void setSelenitePalette(const ui::Palette &uiPalette, const selenite::Palette &palette); diff --git a/ui/components/Calendar.slint b/src/components/Calendar.slint similarity index 98% rename from ui/components/Calendar.slint rename to src/components/Calendar.slint index 0d2e12f..10a2368 100644 --- a/ui/components/Calendar.slint +++ b/src/components/Calendar.slint @@ -1,6 +1,6 @@ import { ScrollView, Date, Time } from "std-widgets.slint"; import { VCheckBox, VButton, VActionButton, Svg, VTag, VPopupIconMenu, VText, Palette } from "@selenite"; -import { Utils } from "../Utils.slint"; +import { Utils } from "../shared/Utils.slint"; export struct CalendarDayEvent { title: string, diff --git a/ui/components/CreateTaskOrEvent.slint b/src/components/CreateTaskOrEvent.slint similarity index 72% rename from ui/components/CreateTaskOrEvent.slint rename to src/components/CreateTaskOrEvent.slint index 1436fad..58ae47f 100644 --- a/ui/components/CreateTaskOrEvent.slint +++ b/src/components/CreateTaskOrEvent.slint @@ -1,28 +1,44 @@ -import { Backend, TaskData } from "../Backend.slint"; -import { Button, VerticalBox, CheckBox, ScrollView, ComboBox } from "std-widgets.slint"; +import { AppWindowModels } from "../windows/AppWindow/Models.slint"; +import { Date, Time, Button, VerticalBox, CheckBox, ScrollView, ComboBox } from "std-widgets.slint"; import { VPopupIconMenu, VDatePicker, VTimePicker, VCheckBox, VButton, VActionButton, VTag, VText, Svg, VTextInput, Palette } from "@selenite"; -import { NewTaskData, SaveTaskData } from "../Backend.slint"; + +export struct CreateTaskData { + sourceId: int, + title: string, + date: Date +} + +export struct CreateEventData { + sourceId: int, + title: string, + date: Date, + startsAt: Time, + endsAt: Time +} export component CreateTaskOrEvent inherits Rectangle { + in property <[string]> sources; private property task-or-event: 1; - function create-task() { + + callback create-task(CreateTaskData); + callback create-event(CreateEventData); + + function accepted() { if (task-or-event == 1) { - Backend.create-task({ - sourceId: sourceInput.current-index, - eventId: -1, + root.create-task({ + sourceId: AppWindowModels.get-source-id-from-name(sourceInput.current-value), title: newTaskTitleInput.text, - scheduled: taskDateInput.date.year != 0, date: taskDateInput.date }) } else { - Backend.create-event({ - sourceId: sourceInput.current-index, + root.create-event({ + sourceId: AppWindowModels.get-source-id-from-name(sourceInput.current-value), title: newTaskTitleInput.text, date: taskDateInput.date, startsAt: eventStartTimeInput.time, - endsAt: eventEndTimeInput.time, - }); + endsAt: eventEndTimeInput.time + }) } newTaskTitleInput.edit-text(""); } @@ -43,9 +59,9 @@ export component CreateTaskOrEvent inherits Rectangle { newTaskTitleInput := VTextInput { placeholder: "Add new Task / Event"; started-writting() => { - sourceInput.current-index = Backend.default-source-index; + sourceInput.current-index = AppWindowModels.default-source-index; } - accepted => { create-task() } + accepted => { accepted() } } Rectangle { min-height: 0px; @@ -70,7 +86,7 @@ export component CreateTaskOrEvent inherits Rectangle { VerticalLayout { alignment: end; sourceInput := ComboBox { - model: Backend.sources; + model: root.sources; } } @@ -98,7 +114,7 @@ export component CreateTaskOrEvent inherits Rectangle { text: "Create"; icon-svg: Svg.correct; icon-colorize: greenyellow; - clicked => { create-task() } + clicked => { accepted() } } } } diff --git a/ui/components/EventGroup.slint b/src/components/EventGroup.slint similarity index 74% rename from ui/components/EventGroup.slint rename to src/components/EventGroup.slint index 64cc4fb..e19e9c0 100644 --- a/ui/components/EventGroup.slint +++ b/src/components/EventGroup.slint @@ -1,14 +1,25 @@ -import { Backend, TaskData, Event } from "../Backend.slint"; +import { Event } from "../windows/AppWindow/Models.slint"; import { ScrollView } from "std-widgets.slint"; import { VCheckBox, VTextInput, VButton, VActionButton, Svg, VTag, VPopupIconMenu, VText, Palette } from "@selenite"; import { TaskLine } from "./TaskLine.slint"; -import { Utils } from "../Utils.slint"; +import { Utils } from "../shared/Utils.slint"; + +export struct EventGroupAddTask { + title: string +} export component EventGroup { in property event; private property show-add-task: false; private property edit-name: false; + callback add-task(EventGroupAddTask); + callback edit-task(int, EventGroupAddTask); + callback delete-task(int); + callback delete(); + callback edit(EventGroupAddTask); + callback toggle-check-task(int); + eventPopup := VPopupIconMenu { VActionButton { icon-svg: Svg.plus; @@ -32,7 +43,7 @@ export component EventGroup { icon-colorize: Colors.pink; icon-size: 1.5rem; border-radius: 0; - clicked => { Backend.delete-event-clicked(event.sourceId, event.id) } + clicked => { root.delete() } } } @@ -79,13 +90,8 @@ export component EventGroup { if edit-name : title := VTextInput { text: event.title; accepted => { - Backend.save-event({ - sourceId: event.sourceId, - id: event.id, - title: title.text, - //date: event.date, - startsAt: event.startsAt, - endsAt: event.endsAt, + root.edit({ + title: title.text }); root.edit-name = false; } @@ -94,22 +100,28 @@ export component EventGroup { padding-top: taskIndex == 0 ? 16px : 0px; padding-bottom: 8px; TaskLine { - task: task; - source-index: task.sourceId; - task-index: task.id; + title: task.title; + delete => { + root.delete-task(task.id) + } + toggle-check => { + root.toggle-check-task(task.id) + } + edited(data) => { + root.edit-task(task.id, { + title: data.title + }); + } } } if show-add-task : taskInput := VTextInput { accepted => { - Backend.create-task({ - sourceId: event.sourceId, - eventId: event.id, - title: taskInput.text, + root.add-task({ + title: taskInput.text }); root.show-add-task = false; } } } } - } diff --git a/ui/components/TaskEdit.slint b/src/components/TaskEdit.slint similarity index 75% rename from ui/components/TaskEdit.slint rename to src/components/TaskEdit.slint index 1a2117d..fde8c80 100644 --- a/ui/components/TaskEdit.slint +++ b/src/components/TaskEdit.slint @@ -1,32 +1,36 @@ -import { Backend, TaskData } from "../Backend.slint"; import { Button, VerticalBox, CheckBox, Date, ScrollView, ComboBox } from "std-widgets.slint"; import { VPopupIconMenu, VDatePicker, VTimePicker, VCheckBox, VButton, VTag, VText, VTextInput, Svg, Palette } from "@selenite"; -import { NewTaskData, SaveTaskData } from "../Backend.slint"; + +export struct TaskEditData { + title: string, + date: Date, +} export component TaskEdit inherits VerticalLayout { - in-out property task; out property should-show; + in property allow-edit-date; + in-out property task; + + callback accepted(TaskEditData); private property newDate: task.date; - public function show(task: TaskData) { + public function show(task: TaskEditData) { root.task = task; should-show = true; } public function close() { should-show = false; } - callback accepted(SaveTaskData); + + // Render if !should-show : Rectangle {} if should-show : Rectangle { function modify() { root.accepted({ - id: task.id, - sourceId: task.sourceId, title: newTaskTitleInput.text, - scheduled: newDate.year != 0, date: newDate }); } @@ -43,7 +47,7 @@ export component TaskEdit inherits VerticalLayout { HorizontalLayout { alignment: start; spacing: 8px; - if root.task.eventId == -1 : taskDateInput := VDatePicker { + if root.allow-edit-date : taskDateInput := VDatePicker { date: task.date; enabled: true; edited(date) => { newDate = date; } diff --git a/ui/components/TaskLine.slint b/src/components/TaskLine.slint similarity index 62% rename from ui/components/TaskLine.slint rename to src/components/TaskLine.slint index 0e6f7ad..9216e15 100644 --- a/ui/components/TaskLine.slint +++ b/src/components/TaskLine.slint @@ -1,30 +1,34 @@ -import { Backend, TaskData } from "../Backend.slint"; import { ToggleButton } from "@selenite"; import { VPopupIconMenu, VTag, VButton, VActionButton, VCheckBox, Svg, Palette } from "@selenite"; import { TaskEdit } from "./TaskEdit.slint"; import { Date } from "std-widgets.slint"; +export struct TaskLineEditData { + title: string, + scheduled: bool, + date: Date, + checked: bool +} + export component TaskLine inherits VerticalLayout { - in property task; + in property title; + in property scheduled; in property date; - in property event-index: -1; - in property source-index: -1; - in property task-index: -1; + in property checked; + in property allow-edit-date; + + callback delete(); + callback edit(); + callback toggle-check(); + callback edited(TaskLineEditData); private property popup-x: 200px; private property popup-y: 200px; - init => { - if (task-index == -1) { - debug("Error: missing task-index") - } - } - taskEdit := TaskEdit { + allow-edit-date: root.allow-edit-date; accepted(task) => { - Backend.save-task({ - id: task.id, - sourceId: task.sourceId, + root.edited({ title: task.title, scheduled: task.date.year != 0, date: task.date @@ -42,13 +46,8 @@ export component TaskLine inherits VerticalLayout { border-radius: 0; clicked => { taskEdit.show({ - sourceId: task.sourceId, - id: task.id, - eventId: task.eventId, - title: task.title, - scheduled: root.date.year != 0, - checked: task.checked, - date: date, + title: title, + date: date }); } } @@ -59,7 +58,7 @@ export component TaskLine inherits VerticalLayout { icon-size: 1.5rem; border-radius: 0; clicked => { - Backend.delete-task-clicked(source-index, task-index) + root.delete(); } } } @@ -67,7 +66,7 @@ export component TaskLine inherits VerticalLayout { ta := TouchArea { clicked => { checkbox.checked = !checkbox.checked; - Backend.task-clicked(source-index, task-index); + root.toggle-check(); } pointer-event(e) => { if (e.button == PointerEventButton.right && e.kind == PointerEventKind.up) { @@ -83,20 +82,13 @@ export component TaskLine inherits VerticalLayout { alignment: start; spacing: 8px; checkbox := VCheckBox { - text: task.title; - checked: task.checked; + text: root.title; + checked: root.checked; toggled => { - Backend.task-clicked(source-index, task-index) + root.toggle-check() } } - /*for tag[tag-index] in task.tags: VTag { - text: tag; - size: 0.8rem; - }*/ } - } - } - } diff --git a/src/main.cpp b/src/main.cpp index dfee9a5..3925e36 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -4,9 +4,10 @@ * The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt */ -#include "AppWindowBackend.h" #include "evalyte-cpp-common/evalyte.h" #include "mirai-core/Mirai.h" +#include "ui.h" +#include "windows/AppWindow/AppWindow.h" #include #include @@ -15,7 +16,7 @@ int main(int argc, char **argv) evalyte::createRequiredDirectories("mirai"); const auto configFilePath = evalyte::configDirectoryPath("mirai") + "/config.json"; mirai::Mirai mirai{configFilePath}; - AppWindowBackend appWindow{&mirai}; + AppWindow appWindow{&mirai}; appWindow.run(); return 0; } diff --git a/src/Utils.cpp b/src/shared/Utils.cpp similarity index 86% rename from src/Utils.cpp rename to src/shared/Utils.cpp index 169ed93..8b6c1c3 100644 --- a/src/Utils.cpp +++ b/src/shared/Utils.cpp @@ -9,6 +9,18 @@ #include #include +void bindSlintUtils(const ui::Utils &utils) +{ + utils.on_format_date([&](const ui::Date &date) { + std::chrono::year_month_day chronoDate{ + std::chrono::year(date.year), + std::chrono::month(date.month), + std::chrono::day(date.day), + }; + return std::format("{:%B %d}", chronoDate); + }); +} + std::string formatZeroPadding(const int number) { if (number < 10) { diff --git a/src/Utils.h b/src/shared/Utils.h similarity index 92% rename from src/Utils.h rename to src/shared/Utils.h index 4af3230..0d3cf65 100644 --- a/src/Utils.h +++ b/src/shared/Utils.h @@ -6,10 +6,12 @@ #pragma once -#include "AppWindow.h" #include "mirai-core/DateTime.h" +#include "ui.h" #include +void bindSlintUtils(const ui::Utils &utils); + std::string formatZeroPadding(const int number); std::string formatDateRelative(const ui::Date &date); std::string capitalize(std::string str); diff --git a/ui/Utils.slint b/src/shared/Utils.slint similarity index 78% rename from ui/Utils.slint rename to src/shared/Utils.slint index 5b9c1ab..c38a6b8 100644 --- a/ui/Utils.slint +++ b/src/shared/Utils.slint @@ -1,4 +1,4 @@ -import { Time } from "std-widgets.slint"; +import { Date, Time } from "std-widgets.slint"; export global Utils { pure function format-zero-padding(number: int) -> string { @@ -14,4 +14,6 @@ export global Utils { } return "\{time.hour}:\{format-zero-padding(time.minute)}"; } + + pure callback format-date(Date) -> string; } diff --git a/src/ui.slint b/src/ui.slint new file mode 100644 index 0000000..9d162cb --- /dev/null +++ b/src/ui.slint @@ -0,0 +1,10 @@ +import { AppWindowModels } from "windows/AppWindow/Models.slint"; +import { AppWindowActions } from "windows/AppWindow/Actions.slint"; +import { AppWindow } from "windows/AppWindow/AppWindow.slint"; +import { SettingsWindow } from "windows/SettingsWindow/SettingsWindow.slint"; +import { AddSourceWindow } from "windows/AddSourceWindow//AddSourceWindow.slint"; +import { EditSourceWindow } from "windows/EditSourceWindow/EditSourceWindow.slint"; +import { Utils } from "shared/Utils.slint"; +import { Palette } from "@selenite"; + +export { Utils, Palette, AppWindow, AppWindowModels, AppWindowActions, SettingsWindow, AddSourceWindow, EditSourceWindow } diff --git a/src/windows/AddSourceWindow/AddSourceWindow.cpp b/src/windows/AddSourceWindow/AddSourceWindow.cpp new file mode 100644 index 0000000..ecb958b --- /dev/null +++ b/src/windows/AddSourceWindow/AddSourceWindow.cpp @@ -0,0 +1,59 @@ +/* + * 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 + */ + +#include "AddSourceWindow.h" +#include "../../SeleniteSetup.h" +#include "evalyte-cpp-common/evalyte.h" +#include "mirai-core/MarkdownDataProvider.h" +#include "mirai-core/Mirai.h" +#include "slint.h" +#include "slint_string.h" +#include "ui.h" +#include +#include +#include +#include +#include +#include +#include +#include + +AddSourceWindow::AddSourceWindow(mirai::Mirai *miraiInstance) : miraiInstance_(miraiInstance) +{ + window_->set_default_source_path(slint::SharedString(evalyte::dataDirectoryPath("mirai") + "/") + ); + + const auto palettePath = std::string(getenv("HOME")) + "/.config/evalyte/theme.json"; + const auto palette = selenite::parseJson(palettePath); + + if (palette.has_value()) { + setSelenitePalette(window_->global(), palette.value()); + } + + setupCallbacks(); +} + +void AddSourceWindow::setupCallbacks() +{ + window_->on_add_source([&](ui::AddSourceParam params) { + std::unique_ptr file = + std::make_unique(std::string(params.path)); + miraiInstance_->addSource( + std::string(params.name), std::string(params.type), std::move(file) + ); + window_->hide(); + }); +} + +void AddSourceWindow::open() +{ + window_->show(); +} + +void AddSourceWindow::close() +{ + window_->hide(); +} diff --git a/src/windows/AddSourceWindow/AddSourceWindow.h b/src/windows/AddSourceWindow/AddSourceWindow.h new file mode 100644 index 0000000..cfbcd4e --- /dev/null +++ b/src/windows/AddSourceWindow/AddSourceWindow.h @@ -0,0 +1,28 @@ +/* + * 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 + */ + +#pragma once + +#include "mirai-core/Mirai.h" +#include "slint.h" +#include "ui.h" + +class AddSourceWindow +{ + + public: + AddSourceWindow(mirai::Mirai *mirai); + + void open(); + void close(); + + private: + void setupCallbacks(); + + std::shared_ptr> sources_; + slint::ComponentHandle window_ = ui::AddSourceWindow::create(); + mirai::Mirai *miraiInstance_; +}; diff --git a/ui/windows/AddSourceWindow.slint b/src/windows/AddSourceWindow/AddSourceWindow.slint similarity index 80% rename from ui/windows/AddSourceWindow.slint rename to src/windows/AddSourceWindow/AddSourceWindow.slint index 7dfad1d..6572f44 100644 --- a/ui/windows/AddSourceWindow.slint +++ b/src/windows/AddSourceWindow/AddSourceWindow.slint @@ -1,8 +1,12 @@ -import { Backend } from "../Backend.slint"; import { VerticalBox, CheckBox } from "std-widgets.slint"; -import { SideBar } from "../components/SideBar.slint"; import { VButton, VText, VTextInput, Palette } from "@selenite"; +export struct AddSourceParam { + name: string, + type: string, + path: string +} + export component AddSourceWindow inherits Window { title: "Mirai - Add source"; @@ -13,6 +17,7 @@ export component AddSourceWindow inherits Window { background: Palette.background; in-out property default-source-path; + callback add-source(AddSourceParam); VerticalLayout { padding: 16px; @@ -28,7 +33,7 @@ export component AddSourceWindow inherits Window { VButton { text: "Create"; clicked => { - Backend.add-source({ + root.add-source({ name: nameInput.text, type: "FileSystemMarkdown", path: pathInput.text @@ -38,4 +43,4 @@ export component AddSourceWindow inherits Window { } } -export { Backend, Palette } // Export to make it visible to the C++ backend +export { Palette } // Export to make it visible to the C++ backend diff --git a/src/windows/AppWindow/Actions.slint b/src/windows/AppWindow/Actions.slint new file mode 100644 index 0000000..3a8fda8 --- /dev/null +++ b/src/windows/AppWindow/Actions.slint @@ -0,0 +1,61 @@ +import { Date, Time } from "std-widgets.slint"; +import { CalendarDay } from "../../components/Calendar.slint"; + +export struct NewTaskData { + sourceId: int, + eventId: int, + title: string, + scheduled: bool, + date: Date +} + +export struct SaveTaskData { + sourceId: int, + id: int, + title: string, + scheduled: bool, + date: Date, +} + +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 +} + +struct OpenNewTaskFormParams { + eventSourceId: int, + eventId: int, +} + +export global AppWindowActions { + callback task-clicked(int, int); + callback source-clicked(int); + callback open-settings-window(); + callback open-add-source-window(); + callback open-edit-source-window(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); + + callback create-task(NewTaskData); + callback save-task(SaveTaskData); + callback create-event(NewEventParams); + callback save-event(SaveEventParams); +} diff --git a/src/AppWindowBackend.cpp b/src/windows/AppWindow/AppWindow.cpp similarity index 71% rename from src/AppWindowBackend.cpp rename to src/windows/AppWindow/AppWindow.cpp index 9f3ece4..615e44e 100644 --- a/src/AppWindowBackend.cpp +++ b/src/windows/AppWindow/AppWindow.cpp @@ -4,18 +4,17 @@ * The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt */ -#include "AppWindowBackend.h" #include "AppWindow.h" -#include "SeleniteSetup.h" -#include "Utils.h" -#include "evalyte-cpp-common/evalyte.h" -#include "mirai-core/DataProvider.h" +#include "../../SeleniteSetup.h" +#include "../../shared/Utils.h" #include "mirai-core/DateTime.h" #include "mirai-core/Day.h" #include "mirai-core/MarkdownDataProvider.h" #include "mirai-core/Mirai.h" +#include "selenite/palette.h" #include "slint.h" #include "slint_string.h" +#include "ui.h" #include #include #include @@ -23,7 +22,6 @@ #include #include #include -#include #include #include #include @@ -34,14 +32,14 @@ #include #include -AppWindowBackend::AppWindowBackend(mirai::Mirai *miraiInstance) - : miraiInstance_(miraiInstance), view_(miraiInstance) +AppWindow::AppWindow(mirai::Mirai *miraiInstance) + : miraiInstance_(miraiInstance), addSourceWindow_(miraiInstance), + editSourceWindow_(miraiInstance), settingsWindow_(miraiInstance), view_(miraiInstance) { sources_ = std::make_shared>(); days_ = std::make_shared>(); calendar_ = std::make_shared>(); unscheduledTasks_ = std::make_shared>(); - tags_ = std::make_shared>(); auto sourcesNames = std::make_shared>( sources_, [&](const ui::Source &a) { @@ -49,29 +47,19 @@ AppWindowBackend::AppWindowBackend(mirai::Mirai *miraiInstance) } ); - addSourceWindow_->set_default_source_path( - slint::SharedString(evalyte::dataDirectoryPath("mirai") + "/") - ); - const auto palettePath = std::string(getenv("HOME")) + "/.config/evalyte/theme.json"; const auto palette = selenite::parseJson(palettePath); if (palette.has_value()) { std::println(std::cerr, "Warning, no {} found", palettePath); setSelenitePalette(mainWindow_->global(), palette.value()); - setSelenitePalette(settingsWindow_->global(), palette.value()); - setSelenitePalette(addSourceWindow_->global(), palette.value()); - setSelenitePalette(editSourceWindow_->global(), palette.value()); } - mainWindow_->global().set_sources(sourcesNames); - settingsWindow_->global().set_sources(sourcesNames); + bindSlintUtils(mainWindow_->global()); - mainWindow_->global().set_sources_selected(sources_); - settingsWindow_->global().set_sources_selected(sources_); - - mainWindow_->global().set_tags(tags_); - mainWindow_->global().set_days(days_); - mainWindow_->global().set_calendar(calendar_); + models().set_sources(sourcesNames); + models().set_sources_selected(sources_); + models().set_days(days_); + models().set_calendar(calendar_); view_.setAllSources(); view_.update(); @@ -103,64 +91,30 @@ std::optional stringToDate(const std::string &dateStr) return ui::Date{.year = year, .month = month, .day = day}; } -void AppWindowBackend::setupUtilsCallbacks() +void AppWindow::setupCallbacks() { - mainWindow_->global().on_format_date([&](const ui::Date &date) { - std::chrono::year_month_day chronoDate{ - std::chrono::year(date.year), - std::chrono::month(date.month), - std::chrono::day(date.day), - }; - return std::format("{:%B %d}", chronoDate); + miraiInstance_->onSourceAdded([&](mirai::Source *source) { + refreshModels(); }); - - mainWindow_->global().on_format_date_relative([&](const ui::Date &date) { - return formatDateRelative(date); + miraiInstance_->onSourceEdited([&](mirai::Source *source) { + refreshModels(); }); -} - -void AppWindowBackend::setupCallbacks() -{ - mainWindow_->global().on_settings_clicked([&]() { - settingsWindow_->show(); + miraiInstance_->onSourceDeleted([&](int id) { + refreshModels(); }); - mainWindow_->global().on_add_source_clicked([&]() { - addSourceWindow_->show(); + actions().on_open_settings_window([&]() { + settingsWindow_.open(); }); - mainWindow_->global().on_edit_source_clicked([&](int sourceId) { + actions().on_open_add_source_window([&]() { + addSourceWindow_.open(); + }); + actions().on_open_edit_source_window([&](int sourceId) { auto source = miraiInstance_->getSourceById(sourceId); assert(source); - auto markdownSource = dynamic_cast(source->dataProvider()); - editSourceWindow_->set_id(source->id); - editSourceWindow_->set_name(slint::SharedString(source->name())); - editSourceWindow_->set_path(slint::SharedString(markdownSource->path())); - editSourceWindow_->show(); + editSourceWindow_.open(source); }); - addSourceWindow_->global().on_add_source([&](ui::AddSourceParam params) { - std::unique_ptr file = - std::make_unique(std::string(params.path)); - miraiInstance_->addSource( - std::string(params.name), std::string(params.type), std::move(file) - ); - addSourceWindow_->hide(); - view_.setAllSources(); - view_.update(); - reloadSources(); - reloadTasks(); - }); - editSourceWindow_->global().on_modify_source([&](ui::ModifySourceParam params) { - miraiInstance_->editSource(params.id, std::string(params.name), std::string(params.path)); - editSourceWindow_->hide(); - reloadSources(); - reloadTasks(); - }); - editSourceWindow_->global().on_delete_source([&](int sourceId) { - miraiInstance_->deleteSource(sourceId); - editSourceWindow_->hide(); - reloadSources(); - reloadTasks(); - }); - mainWindow_->global().on_task_clicked([&](int sourceId, int taskId) { + + actions().on_task_clicked([&](int sourceId, int taskId) { auto source = miraiInstance_->getSourceById(sourceId); assert(source); auto task = source->getTaskById(taskId); @@ -174,7 +128,7 @@ void AppWindowBackend::setupCallbacks() reloadTasks(); }); - mainWindow_->global().on_source_clicked([&](int index) { + actions().on_source_clicked([&](int index) { // index with value -1 is equal to no selection, aka "All" if (index == -1) { view_.setAllSources(); @@ -183,13 +137,13 @@ void AppWindowBackend::setupCallbacks() const mirai::Source *source = miraiInstance_->getSourceById(index); view_.addSource(*source); } - mainWindow_->global().set_default_source_index(index == -1 ? 0 : index); + models().set_default_source_index(index == -1 ? 0 : index); view_.update(); reloadSources(); reloadTasks(); }); - mainWindow_->global().on_delete_task_clicked([&](int sourceId, int taskId) { + actions().on_delete_task_clicked([&](int sourceId, int taskId) { auto source = miraiInstance_->getSourceById(sourceId); assert(source); auto task = source->getTaskById(taskId); @@ -200,13 +154,13 @@ void AppWindowBackend::setupCallbacks() reloadTasks(); }); - mainWindow_->global().on_toggle_show_completed_tasks([&] { + actions().on_toggle_show_completed_tasks([&] { view_.hideCompletedTasks(!view_.shouldHideCompletedTasks()); view_.update(); reloadTasks(); }); - mainWindow_->global().on_save_task([&](ui::SaveTaskData newTaskData) { + actions().on_save_task([&](ui::SaveTaskData newTaskData) { auto source = miraiInstance_->getSourceById(newTaskData.sourceId); assert(source); auto task = source->getTaskById(newTaskData.id); @@ -228,7 +182,7 @@ void AppWindowBackend::setupCallbacks() reloadTasks(); }); - mainWindow_->global().on_create_task([&](ui::NewTaskData newTaskData) { + actions().on_create_task([&](ui::NewTaskData newTaskData) { std::optional date = std::nullopt; if (newTaskData.date.year != 0) { date = SlintDateToMiraiDate(newTaskData.date); @@ -251,7 +205,7 @@ void AppWindowBackend::setupCallbacks() reloadTasks(); }); - mainWindow_->global().on_delete_event_clicked([&](int sourceId, int eventId) { + actions().on_delete_event_clicked([&](int sourceId, int eventId) { auto source = miraiInstance_->getSourceById(sourceId); assert(source); auto event = source->getEventById(eventId); @@ -262,7 +216,7 @@ void AppWindowBackend::setupCallbacks() reloadTasks(); }); - mainWindow_->global().on_create_event([&](ui::NewEventParams newEventParams) { + actions().on_create_event([&](ui::NewEventParams newEventParams) { const ui::Date &date = newEventParams.date; const std::string dateStr = SlintDateToStdString(date); auto source = miraiInstance_->getSourceById(newEventParams.sourceId); @@ -278,7 +232,7 @@ void AppWindowBackend::setupCallbacks() reloadTasks(); }); - mainWindow_->global().on_save_event([&](ui::SaveEventParams newEventParams) { + actions().on_save_event([&](ui::SaveEventParams newEventParams) { const ui::Date &date = newEventParams.date; const std::string dateStr = SlintDateToStdString(date); auto source = miraiInstance_->getSourceById(newEventParams.sourceId); @@ -295,7 +249,6 @@ void AppWindowBackend::setupCallbacks() view_.update(); reloadTasks(); }); - setupUtilsCallbacks(); } std::shared_ptr> @@ -308,7 +261,7 @@ stdToSlintStringVector(const std::vector &stdVector) return slintVector; } -void AppWindowBackend::reloadTasks() +void AppWindow::reloadTasks() { days_->clear(); if (miraiInstance_->getSources().size() == 0) { @@ -374,7 +327,7 @@ void AppWindowBackend::reloadTasks() } } days_ = slintDays; - mainWindow_->global().set_days(days_); + models().set_days(days_); auto unscheduledTasksView = view_.getUnscheduledTasks(); unscheduledTasks_->clear(); @@ -388,7 +341,7 @@ void AppWindowBackend::reloadTasks() .checked = task.checked(), }); } - mainWindow_->global().set_unscheduled_tasks(unscheduledTasks_); + models().set_unscheduled_tasks(unscheduledTasks_); calendar_->clear(); for (int dayIndex = 0; dayIndex < 7; ++dayIndex) { @@ -427,7 +380,7 @@ void AppWindowBackend::reloadTasks() } } -void AppWindowBackend::reloadSources() +void AppWindow::reloadSources() { sources_->clear(); bool noSourceSelected = miraiInstance_->getSources().size() == view_.activeSourceCount(); @@ -442,10 +395,28 @@ void AppWindowBackend::reloadSources() .path = slint::SharedString(sourceProvider->path())} ); } - mainWindow_->global().set_no_source_selected(noSourceSelected); + models().set_no_source_selected(noSourceSelected); } -void AppWindowBackend::run() +void AppWindow::refreshModels() +{ + view_.setAllSources(); + view_.update(); + reloadSources(); + reloadTasks(); +} + +void AppWindow::run() { mainWindow_->run(); } + +const ui::AppWindowModels &AppWindow::models() +{ + return mainWindow_->global(); +} + +const ui::AppWindowActions &AppWindow::actions() +{ + return mainWindow_->global(); +} diff --git a/src/AppWindowBackend.h b/src/windows/AppWindow/AppWindow.h similarity index 63% rename from src/AppWindowBackend.h rename to src/windows/AppWindow/AppWindow.h index ddcdeb8..1222809 100644 --- a/src/AppWindowBackend.h +++ b/src/windows/AppWindow/AppWindow.h @@ -6,36 +6,40 @@ #pragma once -#include "AppWindow.h" +#include "../AddSourceWindow/AddSourceWindow.h" +#include "../EditSourceWindow/EditSourceWindow.h" +#include "../SettingsWindow/SettingsWindow.h" #include "mirai-core/Mirai.h" #include "mirai-core/View.h" #include "slint.h" +#include "ui.h" -class AppWindowBackend +class AppWindow { public: - AppWindowBackend(mirai::Mirai *mirai); + AppWindow(mirai::Mirai *mirai); void run(); void reloadSources(); void reloadTasks(); + void refreshModels(); private: void setupCallbacks(); - void setupUtilsCallbacks(); + const ui::AppWindowModels &models(); + const ui::AppWindowActions &actions(); std::shared_ptr> sources_; - std::shared_ptr> tags_; std::shared_ptr> days_; std::shared_ptr> calendar_; std::shared_ptr> unscheduledTasks_; slint::ComponentHandle mainWindow_ = ui::AppWindow::create(); - slint::ComponentHandle settingsWindow_ = ui::SettingsWindow::create(); - slint::ComponentHandle addSourceWindow_ = ui::AddSourceWindow::create(); - slint::ComponentHandle editSourceWindow_ = ui::EditSourceWindow::create(); + SettingsWindow settingsWindow_; + AddSourceWindow addSourceWindow_; + EditSourceWindow editSourceWindow_; mirai::Mirai *miraiInstance_; mirai::View view_; diff --git a/src/windows/AppWindow/AppWindow.slint b/src/windows/AppWindow/AppWindow.slint new file mode 100644 index 0000000..b8a0feb --- /dev/null +++ b/src/windows/AppWindow/AppWindow.slint @@ -0,0 +1,26 @@ +import { AppWindowModels } from "Models.slint"; +import { Button, VerticalBox, CheckBox } from "std-widgets.slint"; +import { SideBar } from "views/SideBar.slint"; +import { MainView } from "views/TasksView.slint"; +import { SettingsWindow } from "../SettingsWindow/SettingsWindow.slint"; +import { AddSourceWindow } from "../AddSourceWindow//AddSourceWindow.slint"; +import { EditSourceWindow } from "../EditSourceWindow/EditSourceWindow.slint"; +import { Palette } from "@selenite"; + +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; + + HorizontalLayout { + SideBar {} + MainView { + horizontal-stretch: 1; + } + } + +} + +export { AppWindowModels, Palette, SettingsWindow, AddSourceWindow, EditSourceWindow } // Export to make it visible to the C++ backend diff --git a/src/windows/AppWindow/Models.slint b/src/windows/AppWindow/Models.slint new file mode 100644 index 0000000..9d6a79d --- /dev/null +++ b/src/windows/AppWindow/Models.slint @@ -0,0 +1,50 @@ +import { Date, Time } from "std-widgets.slint"; +import { CalendarDay } from "../../components/Calendar.slint"; + +export struct Source { + id: int, + name: string, + selected: bool, + path: string +} + +export struct TaskData { + sourceId: int, + eventId: int, + id: int, + title: string, + date: Date, + checked: bool, +} + +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, + relativeDaysDiff: int +} + +export global AppWindowModels { + in-out property<[Source]> sources-selected; + in-out property default-source-index; + in-out property<[string]> sources; + in-out property no-source-selected; + in-out property<[Day]> days; + in-out property<[CalendarDay]> calendar; + in-out property<[TaskData]> unscheduled-tasks; + + callback get-source-id-from-name(string) -> int; +} diff --git a/ui/components/SideBar.slint b/src/windows/AppWindow/views/SideBar.slint similarity index 65% rename from ui/components/SideBar.slint rename to src/windows/AppWindow/views/SideBar.slint index f698bcd..e7e17e2 100644 --- a/ui/components/SideBar.slint +++ b/src/windows/AppWindow/views/SideBar.slint @@ -1,4 +1,5 @@ -import { Backend } from "../Backend.slint"; +import { AppWindowModels, TaskData } from "../Models.slint"; +import { AppWindowActions } from "../Actions.slint"; import { VButton, ToggleButton, VActionButton, VText, Svg, Palette } from "@selenite"; export component SideBar inherits Rectangle { @@ -20,7 +21,7 @@ export component SideBar inherits Rectangle { icon-svg: Svg.plus; icon-colorize: Palette.green; background: transparent; - clicked => { Backend.add-source-clicked() } + clicked => { AppWindowActions.open-add-source-window() } } } VerticalLayout { @@ -32,21 +33,21 @@ export component SideBar inherits Rectangle { ToggleButton { text: "All"; text-alignment: left; - active: Backend.no-source-selected; - clicked => { Backend.source-clicked(-1) } + active: AppWindowModels.no-source-selected; + clicked => { AppWindowActions.source-clicked(-1) } } - for item[index] in Backend.sources-selected: ToggleButton { + for item[index] in AppWindowModels.sources-selected: ToggleButton { text: item.name; text-alignment: left; active: item.selected; - clicked => { Backend.source-clicked(item.id) } + clicked => { AppWindowActions.source-clicked(item.id) } VActionButton { visible: parent.active; icon-svg: Svg.cog; background: transparent; - clicked => { Backend.edit-source-clicked(item.id) } + clicked => { AppWindowActions.open-edit-source-window(item.id) } } } } @@ -56,7 +57,7 @@ export component SideBar inherits Rectangle { icon-svg: Svg.cog; text: "Settings"; background: transparent; - clicked => { Backend.settings-clicked() } + clicked => { AppWindowModels.open-settings-window() } }*/ } } diff --git a/src/windows/AppWindow/views/TasksView.slint b/src/windows/AppWindow/views/TasksView.slint new file mode 100644 index 0000000..e45b456 --- /dev/null +++ b/src/windows/AppWindow/views/TasksView.slint @@ -0,0 +1,221 @@ +import { AppWindowModels, TaskData } from "../Models.slint"; +import { AppWindowActions, NewTaskData, SaveTaskData } from "../Actions.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 { CreateTaskOrEvent } from "../../../components/CreateTaskOrEvent.slint"; +import { Utils } from "../../../shared/Utils.slint"; + +export component MainView inherits Rectangle { + + background: Palette.background; + private property icon-visible: Svg.visible; + private property icon-not-visible: Svg.not-visible; + private property completed-tasks-visible: false; + + HorizontalLayout { + + VerticalLayout { + horizontal-stretch: 1; + padding: 16px; + spacing: 16px; + HorizontalLayout { + horizontal-stretch: 1; + alignment: start; + spacing: 8px; + VButton { + text: "Show/Hide completed tasks"; + clicked => { + AppWindowActions.toggle-show-completed-tasks(); + completed-tasks-visible = !completed-tasks-visible; + } + icon-svg: 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; + } + + CreateTaskOrEvent { + sources: AppWindowModels.sources; + create-task(data) => { + AppWindowActions.create-task({ + sourceId: data.sourceId, + eventId: -1, + title: data.title, + scheduled: data.date.year != 0, + date: data.date + }) + } + + create-event(data) => { + AppWindowActions.create-event({ + sourceId: data.sourceId, + title: data.title, + date: data.date, + startsAt: data.startsAt, + endsAt: data.endsAt, + }); + } + } + + Flickable { + horizontal-stretch: 1; + VerticalLayout { + alignment: start; + spacing: 16px; + if AppWindowModels.days.length == 0 && AppWindowModels.unscheduled-tasks.length == 0 : VText { + text: "There is no task to show"; + horizontal-alignment: center; + vertical-alignment: center; + } + for day[dayIndex] in AppWindowModels.days: VerticalLayout { + Rectangle { + background: Palette.card-background; + border-radius: 8px; + VerticalLayout { + padding: 16px; + HorizontalLayout { + alignment: start; + VText { + text: Utils.format-date(day.date); + color: day.isLate ? Palette.orange : Palette.foreground; + font-size: 1.2rem; + } + VerticalLayout { + alignment: center; + VText { + text: day.relativeDaysDiff == 0 ? " - today" : + day.relativeDaysDiff == 1 ? " - tomorrow" : + day.relativeDaysDiff == -1 ? " - yesterday" : + day.relativeDaysDiff > 0 ? " - in \{day.relativeDaysDiff} days" : + " - \{-day.relativeDaysDiff} days ago"; + color: Palette.foreground-hint; + font-size: 1rem; + } + } + } + for event[eventIndex] in day.events: VerticalLayout { + padding-top: 16px; + EventGroup { + event: event; + add-task(data) => { + AppWindowActions.create-task({ + sourceId: event.sourceId, + eventId: event.id, + title: data.title, + }); + } + edit-task(taskId, data) => { + AppWindowActions.save-task({ + id: taskId, + sourceId: event.sourceId, + title: data.title, + }); + } + delete-task(taskId) => { + AppWindowActions.delete-task-clicked(event.sourceId, taskId) + } + toggle-check-task(taskId) => { + AppWindowActions.task-clicked(event.sourceId, taskId); + } + delete => { + AppWindowActions.delete-event-clicked(event.sourceId, event.id) + } + edit(data) => { + AppWindowActions.save-event({ + sourceId: event.sourceId, + id: event.id, + title: data.title, + //date: event.date, + startsAt: event.startsAt, + endsAt: event.endsAt, + }); + } + } + } + for task[taskIndex] in day.tasks: VerticalLayout { + padding-top: taskIndex == 0 ? 16px : 0px; + padding-bottom: 8px; + TaskLine { + title: task.title; + scheduled: task.date.year != 0; + date: day.date; + checked: task.checked; + allow-edit-date: true; + delete => { + AppWindowActions.delete-task-clicked(task.sourceId, task.id) + } + toggle-check => { + AppWindowActions.task-clicked(task.sourceId, task.id); + } + edited(data) => { + AppWindowActions.save-task({ + id: task.id, + sourceId: task.sourceId, + title: data.title, + scheduled: data.scheduled, + date: data.date + }) + } + } + } + } + } + } + if AppWindowModels.unscheduled-tasks.length > 0 : VerticalLayout { + Rectangle { + background: Palette.card-background; + border-radius: 8px; + VerticalLayout { + padding: 16px; + HorizontalLayout { + alignment: start; + VText { + text: "Unscheduled"; + color: Palette.foreground; + font-size: 1.2rem; + } + } + for task[taskIndex] in AppWindowModels.unscheduled-tasks: VerticalLayout { + padding-top: taskIndex == 0 ? 16px : 0px; + padding-bottom: 8px; + TaskLine { + title: task.title; + checked: task.checked; + allow-edit-date: true; + delete => { + AppWindowActions.delete-task-clicked(task.sourceId, task.id) + } + toggle-check => { + AppWindowActions.task-clicked(task.sourceId, task.id); + } + edited(data) => { + AppWindowActions.save-task({ + id: task.id, + sourceId: task.sourceId, + title: data.title, + scheduled: data.scheduled, + date: data.date + }) + } + } + } + } + } + } + } + } + } + Calendar { + init => { debug("cal len", AppWindowModels.calendar.length) } + days: AppWindowModels.calendar; + } + } + +} diff --git a/src/windows/EditSourceWindow/EditSourceWindow.cpp b/src/windows/EditSourceWindow/EditSourceWindow.cpp new file mode 100644 index 0000000..8408429 --- /dev/null +++ b/src/windows/EditSourceWindow/EditSourceWindow.cpp @@ -0,0 +1,61 @@ +/* + * 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 + */ + +#include "EditSourceWindow.h" +#include "../../SeleniteSetup.h" +#include "mirai-core/MarkdownDataProvider.h" +#include "mirai-core/Mirai.h" +#include "slint.h" +#include "slint_string.h" +#include "ui.h" +#include +#include +#include +#include +#include +#include +#include +#include + +EditSourceWindow::EditSourceWindow(mirai::Mirai *miraiInstance) : miraiInstance_(miraiInstance) +{ + + const auto palettePath = std::string(getenv("HOME")) + "/.config/evalyte/theme.json"; + const auto palette = selenite::parseJson(palettePath); + + if (palette.has_value()) { + setSelenitePalette(window_->global(), palette.value()); + } + + setupCallbacks(); +} + +void EditSourceWindow::setupCallbacks() +{ + window_->on_modify_source([&](ui::ModifySourceParam params) { + miraiInstance_->editSource(params.id, std::string(params.name), std::string(params.path)); + close(); + }); + window_->on_delete_source([&](int sourceId) { + miraiInstance_->deleteSource(sourceId); + close(); + }); +} + +void EditSourceWindow::open(mirai::Source *source) +{ + assert(source != nullptr); + auto markdownSource = dynamic_cast(source->dataProvider()); + window_->set_id(source->id); + window_->set_name(slint::SharedString(source->name())); + window_->set_path(slint::SharedString(markdownSource->path())); + window_->show(); +} + +void EditSourceWindow::close() +{ + window_->hide(); +} diff --git a/src/windows/EditSourceWindow/EditSourceWindow.h b/src/windows/EditSourceWindow/EditSourceWindow.h new file mode 100644 index 0000000..59e5164 --- /dev/null +++ b/src/windows/EditSourceWindow/EditSourceWindow.h @@ -0,0 +1,29 @@ +/* + * 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 + */ + +#pragma once + +#include "mirai-core/Mirai.h" +#include "mirai-core/Source.h" +#include "slint.h" +#include "ui.h" + +class EditSourceWindow +{ + + public: + EditSourceWindow(mirai::Mirai *mirai); + + void open(mirai::Source *source); + void close(); + + private: + void setupCallbacks(); + + std::shared_ptr> sources_; + slint::ComponentHandle window_ = ui::EditSourceWindow::create(); + mirai::Mirai *miraiInstance_; +}; diff --git a/ui/windows/EditSourceWindow.slint b/src/windows/EditSourceWindow/EditSourceWindow.slint similarity index 76% rename from ui/windows/EditSourceWindow.slint rename to src/windows/EditSourceWindow/EditSourceWindow.slint index 9adb42c..f040ae2 100644 --- a/ui/windows/EditSourceWindow.slint +++ b/src/windows/EditSourceWindow/EditSourceWindow.slint @@ -1,8 +1,12 @@ -import { Backend } from "../Backend.slint"; import { VerticalBox, CheckBox } from "std-widgets.slint"; -import { SideBar } from "../components/SideBar.slint"; import { VButton, VText, VTextInput, Palette } from "@selenite"; +export struct ModifySourceParam { + id: int, + name: string, + path: string +} + export component EditSourceWindow inherits Window { in-out property id; in-out property name <=> nameInput.text; @@ -15,6 +19,9 @@ export component EditSourceWindow inherits Window { default-font-size: 16px; background: Palette.background; + callback modify-source(ModifySourceParam); + callback delete-source(int); + VerticalLayout { padding: 16px; spacing: 8px; @@ -27,7 +34,7 @@ export component EditSourceWindow inherits Window { VButton { text: "Save"; clicked => { - Backend.modify-source({ + root.modify-source({ id: root.id, name: nameInput.text, path: pathInput.text @@ -38,10 +45,10 @@ export component EditSourceWindow inherits Window { text: "Delete"; background-color: Palette.red; double-clicked => { - Backend.delete-source(root.id) + root.delete-source(root.id) } } } } -export { Backend, Palette } // Export to make it visible to the C++ backend +export { Palette } // Export to make it visible to the C++ backend diff --git a/src/windows/SettingsWindow/SettingsWindow.cpp b/src/windows/SettingsWindow/SettingsWindow.cpp new file mode 100644 index 0000000..1996ca8 --- /dev/null +++ b/src/windows/SettingsWindow/SettingsWindow.cpp @@ -0,0 +1,50 @@ +/* + * 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 + */ + +#include "SettingsWindow.h" +#include "../../SeleniteSetup.h" +#include "evalyte-cpp-common/evalyte.h" +#include "mirai-core/DataProvider.h" +#include "mirai-core/MarkdownDataProvider.h" +#include "mirai-core/Mirai.h" +#include "slint.h" +#include "slint_string.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +SettingsWindow::SettingsWindow(mirai::Mirai *miraiInstance) : miraiInstance_(miraiInstance) +{ + + const auto palettePath = std::string(getenv("HOME")) + "/.config/evalyte/theme.json"; + const auto palette = selenite::parseJson(palettePath); + + if (palette.has_value()) { + setSelenitePalette(window_->global(), palette.value()); + } + + setupCallbacks(); +} + +void SettingsWindow::setupCallbacks() +{ +} + +void SettingsWindow::open() +{ + window_->show(); +} + +void SettingsWindow::close() +{ + window_->hide(); +} diff --git a/src/windows/SettingsWindow/SettingsWindow.h b/src/windows/SettingsWindow/SettingsWindow.h new file mode 100644 index 0000000..83d5797 --- /dev/null +++ b/src/windows/SettingsWindow/SettingsWindow.h @@ -0,0 +1,28 @@ +/* + * 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 + */ + +#pragma once + +#include "mirai-core/Mirai.h" +#include "slint.h" +#include "ui.h" + +class SettingsWindow +{ + + public: + SettingsWindow(mirai::Mirai *mirai); + + void open(); + void close(); + + private: + void setupCallbacks(); + + std::shared_ptr> sources_; + slint::ComponentHandle window_ = ui::SettingsWindow::create(); + mirai::Mirai *miraiInstance_; +}; diff --git a/ui/SettingsWindow.slint b/src/windows/SettingsWindow/SettingsWindow.slint similarity index 64% rename from ui/SettingsWindow.slint rename to src/windows/SettingsWindow/SettingsWindow.slint index ec9da4f..c20638f 100644 --- a/ui/SettingsWindow.slint +++ b/src/windows/SettingsWindow/SettingsWindow.slint @@ -1,7 +1,5 @@ -import { Backend } from "Backend.slint"; +import { AppWindowModels } from "../AppWindow/Models.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 { @@ -15,7 +13,7 @@ export component SettingsWindow inherits Window { VerticalLayout { padding: 16px; spacing: 8px; - for source[source-index] in Backend.sources-selected: VerticalLayout { + for source[source-index] in AppWindowModels.sources-selected: VerticalLayout { VText { text: source.name; } @@ -26,4 +24,4 @@ export component SettingsWindow inherits Window { } } -export { Backend, Palette } // Export to make it visible to the C++ backend +export { AppWindowModels, Palette } // Export to make it visible to the C++ backend diff --git a/ui/AppWindow.slint b/ui/AppWindow.slint deleted file mode 100644 index 7833eef..0000000 --- a/ui/AppWindow.slint +++ /dev/null @@ -1,26 +0,0 @@ -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 { AddSourceWindow } from "windows/AddSourceWindow.slint"; -import { EditSourceWindow } from "windows/EditSourceWindow.slint"; -import { Palette } from "@selenite"; - -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; - - HorizontalLayout { - SideBar {} - MainView { - horizontal-stretch: 1; - } - } - -} - -export { Backend, Palette, SettingsWindow, AddSourceWindow, EditSourceWindow } // Export to make it visible to the C++ backend diff --git a/ui/Backend.slint b/ui/Backend.slint deleted file mode 100644 index c3b9923..0000000 --- a/ui/Backend.slint +++ /dev/null @@ -1,128 +0,0 @@ -import { Date, Time } from "std-widgets.slint"; -import { CalendarDay } from "components/Calendar.slint"; - -export struct NewTaskData { - sourceId: int, - eventId: int, - title: string, - scheduled: bool, - date: Date -} - -export struct SaveTaskData { - sourceId: int, - id: int, - title: string, - scheduled: bool, - date: Date, -} - -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 struct AddSourceParam { - name: string, - type: string, - path: string -} - -export struct ModifySourceParam { - id: int, - name: string, - path: string -} - -export struct Source { - id: int, - name: string, - selected: bool, - path: string -} - -export struct TaskData { - sourceId: int, - eventId: int, - id: int, - title: string, - date: Date, - checked: bool, -} - -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, - relativeDaysDiff: int -} - -struct OpenNewTaskFormParams { - eventSourceId: int, - eventId: int, -} - -export global Backend { - in-out property<[Source]> sources-selected; - in-out property default-source-index; - in-out property<[string]> sources; - in-out property 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 add-source-clicked(); - callback edit-source-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); - - callback create-task(NewTaskData); - callback save-task(SaveTaskData); - callback create-event(NewEventParams); - callback save-event(SaveEventParams); - - callback add-source(AddSourceParam); - callback modify-source(ModifySourceParam); - callback delete-source(int); - - // Utils - pure callback format-date(Date) -> string; - pure callback format-date-relative(Date) -> string; - pure callback capitalize-string(string) -> string; -} diff --git a/ui/MainView.slint b/ui/MainView.slint deleted file mode 100644 index 0adffc9..0000000 --- a/ui/MainView.slint +++ /dev/null @@ -1,137 +0,0 @@ -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"; - -export component MainView inherits Rectangle { - - background: Palette.background; - private property icon-visible: Svg.visible; - private property icon-not-visible: Svg.not-visible; - private property completed-tasks-visible: false; - - HorizontalLayout { - - VerticalLayout { - horizontal-stretch: 1; - padding: 16px; - spacing: 16px; - HorizontalLayout { - horizontal-stretch: 1; - alignment: start; - spacing: 8px; - VButton { - text: "Show/Hide completed tasks"; - clicked => { - Backend.toggle-show-completed-tasks(); - completed-tasks-visible = !completed-tasks-visible; - } - icon-svg: 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; - } - - CreateTaskOrEvent { - - } - - Flickable { - horizontal-stretch: 1; - VerticalLayout { - alignment: start; - spacing: 16px; - if Backend.days.length == 0 && Backend.unscheduled-tasks.length == 0 : VText { - text: "There is no task to show"; - horizontal-alignment: center; - vertical-alignment: center; - } - for day[dayIndex] in Backend.days: VerticalLayout { - Rectangle { - background: Palette.card-background; - border-radius: 8px; - VerticalLayout { - padding: 16px; - HorizontalLayout { - alignment: start; - VText { - text: Backend.format-date(day.date); - color: day.isLate ? Palette.orange : Palette.foreground; - font-size: 1.2rem; - } - VerticalLayout { - alignment: center; - VText { - text: day.relativeDaysDiff == 0 ? " - today" : - day.relativeDaysDiff == 1 ? " - tomorrow" : - day.relativeDaysDiff == -1 ? " - yesterday" : - day.relativeDaysDiff > 0 ? " - in \{day.relativeDaysDiff} days" : - " - \{-day.relativeDaysDiff} days ago"; - 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; - date: day.date; - source-index: task.sourceId; - task-index: task.id; - } - } - } - } - } - if Backend.unscheduled-tasks.length > 0 : VerticalLayout { - Rectangle { - background: Palette.card-background; - border-radius: 8px; - VerticalLayout { - padding: 16px; - HorizontalLayout { - alignment: start; - VText { - text: "Unscheduled"; - color: Palette.foreground; - font-size: 1.2rem; - } - } - for task[taskIndex] in Backend.unscheduled-tasks: VerticalLayout { - padding-top: taskIndex == 0 ? 16px : 0px; - padding-bottom: 8px; - TaskLine { - task: task; - source-index: task.sourceId; - task-index: task.id; - } - } - } - } - } - } - } - } - Calendar { - init => { debug("cal len", Backend.calendar.length) } - days: Backend.calendar; - } - } - -}