From 11fe8ca54d125d62966be4c236bde54cbc5d9ab4 Mon Sep 17 00:00:00 2001 From: chris bell Date: Wed, 5 Aug 2026 22:48:44 -0500 Subject: [PATCH] Refactored stuff into new files, added a test modal for adding critters --- CMakeLists.txt | 3 + assets/local.db | Bin 40960 -> 40960 bytes assets/main.rml | 39 ++++++++- assets/styles/main.rcss | 49 +++++++++++ src/database.cpp | 133 ++++++++++++++++++++++++++++ src/database.hpp | 109 ++--------------------- src/main.cpp | 190 +++++++++++++--------------------------- src/ui.cpp | 88 +++++++++++++++++++ src/ui.hpp | 35 ++++++++ 9 files changed, 414 insertions(+), 232 deletions(-) create mode 100644 src/database.cpp create mode 100644 src/ui.cpp create mode 100644 src/ui.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 9353726..4e52319 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,6 +26,9 @@ find_package(RmlUi CONFIG REQUIRED) add_executable(${PROJECT_NAME} src/main.cpp + src/database.cpp + src/ui.cpp + vendor/rmlui_backend/RmlUi_Backend_GLFW_GL3.cpp vendor/rmlui_backend/RmlUi_Platform_GLFW.cpp vendor/rmlui_backend/RmlUi_Renderer_GL3.cpp diff --git a/assets/local.db b/assets/local.db index 0eadb6bd81da4d8ec290b90a74d86bf0ea152741..453c24b82d7b8992ca274557afc2fb3e3efd8f9e 100644 GIT binary patch delta 104 zcmZoTz|?SnX@WE(|3n#QR(=M(si!xlEb!-I=3m6Xe~W)N|Dw%;0#o=U#hE!6g!vgc zBt?_*liZj*ixraclhX4OOD6A*_vB*c|G>cihyNS@hs}ZlkNDYGnHd?GnKysSf633t Ky!k=Bf&u_ZdLN1a delta 71 zcmZoTz|?SnX@WE(-$WT_Rz3#3-k%#&7Wi{8@$Y8fzs0|Mv!K9w{>i)JJvo^8zcKLt d;s3T-P~a8+

{{page_title}}

- -

Critter Roster

+ + + +

#{{entry.key}} - {{entry.value.name}}

Species: {{entry.value.species}}

Gender: {{entry.value.gender}}

-

Notes: {{entry.value.notes}}

+
+
+ + + diff --git a/assets/styles/main.rcss b/assets/styles/main.rcss index 322b0fd..1b10473 100644 --- a/assets/styles/main.rcss +++ b/assets/styles/main.rcss @@ -266,3 +266,52 @@ progress-value { display: none; } + + +/* =================================== + Specific app stuff + =================================== */ + +/* Critter cards */ + +/* Modals */ +.modal-overlay { + position: absolute; + top: 0px; + left: 0px; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.6); + display: flex; + align-items: center; + justify-content: center; +} + +.modal-content { + background-color: #2b2b2b; + color: #ffffff; + padding: 20px; + border-radius: 8px; + width: 350px; +} + +.modal-content label { + display: block; + margin-top: 8px; +} + +.modal-content input { + width: 100%; + margin-bottom: 8px; +} + +.error { + color: #ff5555; + font-weight: bold; +} + +.modal-buttons { + margin-top: 15px; + display: flex; + justify-content: space-between; +} \ No newline at end of file diff --git a/src/database.cpp b/src/database.cpp new file mode 100644 index 0000000..751ecfd --- /dev/null +++ b/src/database.cpp @@ -0,0 +1,133 @@ +#include "database.hpp" +#include +#include + +bool db_get_all_critters(std::map &critters) { + try { + SQLite::Database db("./assets/local.db", SQLite::OPEN_READWRITE | SQLite::OPEN_CREATE); + SQLite::Statement q(db, "SELECT * FROM critters"); + + critters.clear(); + + while (q.executeStep()) { + int id = q.getColumn(0); + + critters.try_emplace( + id, + id, + q.getColumn(1).getText(), + q.getColumn(2).getText(), + q.getColumn(3).getInt(), + q.getColumn(4).getInt(), + q.getColumn(5).getText(), + q.getColumn(6).getText() + ); + } + } + catch (const std::exception &e) { + std::cout << "[ERROR] DB exception: " << e.what() << std::endl; + return false; + } + + return true; +} + +bool db_add_critter(const Critter &c, int &out_id) { + try { + SQLite::Database db("./assets/local.db", SQLite::OPEN_READWRITE); + SQLite::Statement q(db, "INSERT INTO critters (name, gender, mother_id, father_id, notes, species) VALUES (?, ?, ?, ?, ?, ?)"); + + q.bind(1, c.name); + q.bind(2, c.gender); + q.bind(3, c.mother_id); + q.bind(4, c.father_id); + q.bind(5, c.notes); + q.bind(6, c.species); + + q.exec(); + out_id = static_cast(db.getLastInsertRowid()); + return true; + } + catch (const std::exception &e) { + std::cout << "[ERROR] DB add exception: " << e.what() << std::endl; + return false; + } +} + +bool db_delete_critter(int id) { + try { + SQLite::Database db("./assets/local.db", SQLite::OPEN_READWRITE); + SQLite::Statement q(db, "DELETE FROM critters WHERE id = ?"); + q.bind(1, id); + q.exec(); + return true; + } + catch (const std::exception &e) { + std::cout << "[ERROR] DB delete exception: " << e.what() << std::endl; + return false; + } +} + +void db_print_all_critters(const std::map &critters) { + std::cout << "Current loaded critters:"; + for (const auto &[key, c] : critters) { + std::cout << "\nCritter " << key << "\n"; + std::cout << "ID: " << c.id << std::endl; + std::cout << "Name: " << c.name << std::endl; + std::cout << "Gender: " << c.gender << std::endl; + std::cout << "Mother ID: " << c.mother_id << std::endl; + std::cout << "Father ID: " << c.father_id << std::endl; + std::cout << "Notes: " << c.notes << std::endl; + std::cout << "Species: " << c.species << std::endl; + } + std::cout << "\n"; +} + +bool validate_critter(const Critter &c, const std::map &all_critters, std::string &out_error) { + if (c.mother_id != 0 && c.father_id != 0 && c.mother_id == c.father_id) { + out_error = "Mother ID and Father ID cannot be the same."; + return false; + } + + if (c.mother_id != 0) { + auto it = all_critters.find(c.mother_id); + if (it == all_critters.end()) { + out_error = "Mother ID " + std::to_string(c.mother_id) + " does not exist."; + return false; + } + + const Critter &mother = it->second; + + if (mother.gender != "F" && mother.gender != "Female") { + out_error = "Mother must have female gender ('F' or 'Female')."; + return false; + } + + if (mother.species != c.species) { + out_error = "Mother species ('" + mother.species + "') must match critter species ('" + c.species + "')."; + return false; + } + } + + if (c.father_id != 0) { + auto it = all_critters.find(c.father_id); + if (it == all_critters.end()) { + out_error = "Father ID " + std::to_string(c.father_id) + " does not exist."; + return false; + } + + const Critter &father = it->second; + + if (father.gender != "M" && father.gender != "Male") { + out_error = "Father must have male gender ('M' or 'Male')."; + return false; + } + + if (father.species != c.species) { + out_error = "Father species ('" + father.species + "') must match critter species ('" + c.species + "')."; + return false; + } + } + + return true; +} \ No newline at end of file diff --git a/src/database.hpp b/src/database.hpp index bc22b5e..34d0895 100644 --- a/src/database.hpp +++ b/src/database.hpp @@ -1,104 +1,11 @@ +#pragma once + #include "critter.hpp" -#include -#include #include +#include -inline static bool db_get_all_critters(std::map &critters) { - try { - SQLite::Database db("./assets/local.db", SQLite::OPEN_READWRITE | SQLite::OPEN_CREATE); - SQLite::Statement q(db, "SELECT * FROM critters"); - - critters.clear(); - - while (q.executeStep()) { - int id = q.getColumn(0); - - critters.try_emplace( - id, - id, - q.getColumn(1).getText(), - q.getColumn(2).getText(), - q.getColumn(3).getInt(), - q.getColumn(4).getInt(), - q.getColumn(5).getText(), - q.getColumn(6).getText() - ); - } - } - catch (const std::exception &e) { - std::cout << "[ERROR] DB exception: " << e.what() << std::endl; - return false; - } - - return true; -} - -inline static void db_print_all_critters(std::map &critters) { - std::cout << "Current loaded critters:"; - for (auto c : critters) { - std::cout << "\nCritter " << c.first << "\n"; - std::cout << "ID: " << c.second.id << std::endl; - std::cout << "Name:" << c.second.name << std::endl; - std::cout << "Gender: " << c.second.gender << std::endl; - std::cout << "Mother ID: " << c.second.mother_id << std::endl; - std::cout << "Father ID: " << c.second.father_id << std::endl; - std::cout << "Notes: " << c.second.notes << std::endl; - std::cout << "Species: " << c.second.species << std::endl; - } - std::cout << "\n"; -} - -inline static bool validate_critter(const Critter &c, std::map &all_critters, std::string &out_error) { - if (c.mother_id != 0 && c.father_id != 0 && c.mother_id == c.father_id) { - out_error = "Mother ID and Father ID cannot be the same."; - return false; - } - - // Mother Validation - if (c.mother_id != 0) { - auto it = all_critters.find(c.mother_id); - if (it == all_critters.end()) { - out_error = "Mother ID " + std::to_string(c.mother_id) + " does not exist."; - return false; - } - - const Critter& mother = it->second; - - if (mother.gender != "F" && mother.gender != "Female") { - out_error = "Mother must have female gender ('F' or 'Female')."; - return false; - } - - if (mother.species != c.species) { - out_error = "Mother species ('" + mother.species + "') must match critter species ('" + c.species + "')."; - return false; - } - } - - // Father Validation - if (c.father_id != 0) { - auto it = all_critters.find(c.father_id); - if (it == all_critters.end()) { - out_error = "Father ID " + std::to_string(c.father_id) + " does not exist."; - return false; - } - - const Critter& father = it->second; - - if (father.gender != "M" && father.gender != "Male") { - out_error = "Father must have male gender ('M' or 'Male')."; - return false; - } - - if (father.species != c.species) { - out_error = "Father species ('" + father.species + "') must match critter species ('" + c.species + "')."; - return false; - } - } - - return true; -} - -inline static bool db_try_add_critter() { - -} \ No newline at end of file +bool db_get_all_critters(std::map &critters); +bool db_add_critter(const Critter &c, int &out_id); +bool db_delete_critter(int id); +void db_print_all_critters(const std::map &critters); +bool validate_critter(const Critter &c, const std::map &all_critters, std::string &out_error); \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index b8da5f3..b5b3bfb 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,162 +1,96 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include "critter.hpp" -#include +#include "database.hpp" +#include "ui.hpp" + +#include +#include +#include +#include + +#include #include #include -#include "database.hpp" static bool is_initalized = false; static bool is_running = false; -static Rml::Context *ctx; -static std::map critters = {}; -static std::vector critter_list = {}; +static Rml::Context *ctx = nullptr; +static std::map critters; +static std::vector critter_list; +static AppData app_data; -struct AppData { - Rml::String page_title = "Main"; -} app_data; - -void SyncMapToList() { - critter_list.clear(); - for (const auto &[id, critter] : critters) { - critter_list.push_back({id, critter}); - } +void shutdown() { + Rml::Shutdown(); + Backend::Shutdown(); } -void Shutdown() { - Rml::Shutdown(); - Backend::Shutdown(); -} +bool initialize() { + if (!Backend::Initialize("CritterFolio", 800, 600, true)) { + std::cout << "[ERROR] Failed to initialize backend.\n"; + return false; + } + std::cout << "[INFO] RmlUI Backend initialized.\n"; -bool SetupDataBinding(Rml::Context *context, Rml::DataModelHandle &model_handle) { - Rml::DataModelConstructor dmc = ctx->CreateDataModel("app_data"); - if (!dmc) { - std::cout << "[ERROR] could not create 'app_data' data binding."; - return false; - } + Rml::SetSystemInterface(Backend::GetSystemInterface()); + Rml::SetRenderInterface(Backend::GetRenderInterface()); + Rml::Initialise(); + Rml::Lua::Initialise(); - if (auto handle = dmc.RegisterStruct()) { - handle.RegisterMember("id", &Critter::id); - handle.RegisterMember("name", &Critter::name); - handle.RegisterMember("gender", &Critter::gender); - handle.RegisterMember("species", &Critter::species); - handle.RegisterMember("notes", &Critter::notes); - handle.RegisterMember("mother_id", &Critter::mother_id); - handle.RegisterMember("father_id", &Critter::father_id); - } + std::cout << "[INFO] Loading local DB...\n"; + if (!db_get_all_critters(critters)) { + std::cout << "[ERROR] Local DB failed to initialize.\n"; + return false; + } + std::cout << "[INFO] Local DB initialized.\n"; - if (auto handle = dmc.RegisterStruct()) { - handle.RegisterMember("key", &CritterEntry::key); - handle.RegisterMember("value", &CritterEntry::value); - } + sync_map_to_list(critters, critter_list); - dmc.RegisterArray>(); - dmc.Bind("critters", &critter_list); + ctx = Rml::CreateContext("main", Rml::Vector2i(800, 600)); + if (!ctx) { + std::cout << "[ERROR] Failed to create context.\n"; + return false; + } + std::cout << "[INFO] Main context initialized.\n"; - dmc.Bind("page_title", &app_data.page_title); - - model_handle = dmc.GetModelHandle(); - return true; -} - -bool Initalize() { - if (!Backend::Initialize("CritterFolio", 800, 600, true)) { - std::cout << "[ERROR] Failed to initalize the backend."; - return false; - } - std::cout << "[INFO] RmlUI Backend initalized.\n"; - - Rml::SetSystemInterface(Backend::GetSystemInterface()); - Rml::SetRenderInterface(Backend::GetRenderInterface()); - Rml::Initialise(); - Rml::Lua::Initialise(); - - // TODO: Add more error handling for initalization - - std::cout << "[INFO] Loading local DB...\n"; - bool db_result = db_get_all_critters(critters); - if (!db_result) { - std::cout << "[ERROR] Local DB failed to initalize.\n"; - return false; - } - std::cout << "[INFO] Local DB initalized.\n"; - - SyncMapToList(); - - // ------- DB TESTING - // db_print_all_critters(critters); - // ------- END DB TESTING - - ctx = Rml::CreateContext("main", Rml::Vector2i(800, 600)); - if (!ctx) { - std::cout << "[ERROR] Failed to create context."; - return false; - } - std::cout << "[INFO] Main context initalized.\n"; - - Rml::Debugger::Initialise(ctx); - - // lua_State *L = Rml::Lua::Interpreter::GetLuaState(); - - return true; -} - -void Update(Rml::DataModelHandle model_handle) { - + Rml::Debugger::Initialise(ctx); + return true; } int main() { - std::cout << "[INFO] Starting critterfolio...\n"; + std::cout << "[INFO] Starting CritterFolio...\n"; - is_initalized = Initalize(); + is_initalized = initialize(); if (!is_initalized) { - std::cout << "[ERROR] Failed to initalize, shutting down."; - Shutdown(); - return 1; + std::cout << "[ERROR] Failed to initialize, shutting down.\n"; + shutdown(); + return 1; } - std::cout << "[INFO] Application initalized, all systems running.\n"; is_running = true; Rml::DataModelHandle app_data_handle; + if (!setup_data_binding(ctx, app_data_handle, app_data, critter_list, critters)) { + shutdown(); + return 1; + } - SetupDataBinding(ctx, app_data_handle); - - Rml::ElementDocument *doc; - doc = ctx->LoadDocument("./assets/main.rml"); + Rml::ElementDocument *doc = ctx->LoadDocument("./assets/main.rml"); if (!doc) { - std::cout << "[ERROR] Could no load assets/main.rml document, shutting down."; - Shutdown(); - return 1; + std::cout << "[ERROR] Could not load assets/main.rml document, shutting down.\n"; + shutdown(); + return 1; } std::cout << "[INFO] Main page loaded.\n"; doc->Show(); - while(is_running) { - is_running = Backend::ProcessEvents(ctx); - - Update(app_data_handle); - - ctx->Update(); - Backend::BeginFrame(); - ctx->Render(); - Backend::PresentFrame(); + while (is_running) { + is_running = Backend::ProcessEvents(ctx); + ctx->Update(); + Backend::BeginFrame(); + ctx->Render(); + Backend::PresentFrame(); } - Shutdown(); + shutdown(); return 0; -} +} \ No newline at end of file diff --git a/src/ui.cpp b/src/ui.cpp new file mode 100644 index 0000000..69e6c31 --- /dev/null +++ b/src/ui.cpp @@ -0,0 +1,88 @@ +#include "ui.hpp" +#include "database.hpp" +#include + +void sync_map_to_list(const std::map &critters, std::vector &critter_list) { + critter_list.clear(); + for (const auto &[id, critter] : critters) { + critter_list.push_back({id, critter}); + } +} + +bool setup_data_binding(Rml::Context *context, Rml::DataModelHandle &model_handle, AppData &app_data, std::vector &critter_list, std::map &critters) { + Rml::DataModelConstructor dmc = context->CreateDataModel("app_data"); + if (!dmc) { + std::cout << "[ERROR] Could not create 'app_data' data binding.\n"; + return false; + } + + if (auto handle = dmc.RegisterStruct()) { + handle.RegisterMember("id", &Critter::id); + handle.RegisterMember("name", &Critter::name); + handle.RegisterMember("gender", &Critter::gender); + handle.RegisterMember("species", &Critter::species); + handle.RegisterMember("notes", &Critter::notes); + handle.RegisterMember("mother_id", &Critter::mother_id); + handle.RegisterMember("father_id", &Critter::father_id); + } + + if (auto handle = dmc.RegisterStruct()) { + handle.RegisterMember("key", &CritterEntry::key); + handle.RegisterMember("value", &CritterEntry::value); + } + + if (auto handle = dmc.RegisterStruct()) { + handle.RegisterMember("name", &NewCritterForm::name); + handle.RegisterMember("gender", &NewCritterForm::gender); + handle.RegisterMember("mother_id", &NewCritterForm::mother_id); + handle.RegisterMember("father_id", &NewCritterForm::father_id); + handle.RegisterMember("notes", &NewCritterForm::notes); + handle.RegisterMember("species", &NewCritterForm::species); + } + + dmc.RegisterArray>(); + + dmc.Bind("critters", &critter_list); + dmc.Bind("page_title", &app_data.page_title); + dmc.Bind("is_dialog_open", &app_data.is_dialog_open); + dmc.Bind("error_message", &app_data.error_message); + dmc.Bind("form", &app_data.form); + + dmc.BindEventCallback("submit_add_critter", [&app_data, &critters, &critter_list](Rml::DataModelHandle handle, Rml::Event &, const Rml::VariantList &) { + Critter temp; + temp.name = app_data.form.name; + temp.gender = app_data.form.gender; + temp.mother_id = app_data.form.mother_id; + temp.father_id = app_data.form.father_id; + temp.notes = app_data.form.notes; + temp.species = app_data.form.species; + + std::string err; + if (!validate_critter(temp, critters, err)) { + app_data.error_message = err; + handle.DirtyVariable("error_message"); + return; + } + + int new_id = 0; + if (db_add_critter(temp, new_id)) { + db_get_all_critters(critters); + sync_map_to_list(critters, critter_list); + + app_data.form.Clear(); + app_data.error_message.clear(); + app_data.is_dialog_open = false; + + handle.DirtyVariable("critters"); + handle.DirtyVariable("form"); + handle.DirtyVariable("error_message"); + handle.DirtyVariable("is_dialog_open"); + } else { + app_data.error_message = "Failed to write to database."; + handle.DirtyVariable("error_message"); + } + }); + + model_handle = dmc.GetModelHandle(); + return true; +} \ No newline at end of file diff --git a/src/ui.hpp b/src/ui.hpp new file mode 100644 index 0000000..7cc4cc5 --- /dev/null +++ b/src/ui.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include "critter.hpp" +#include +#include +#include +#include + +struct NewCritterForm { + Rml::String name; + Rml::String gender = "F"; + int mother_id = 0; + int father_id = 0; + Rml::String notes; + Rml::String species; + + void Clear() { + name.clear(); + gender = "F"; + mother_id = 0; + father_id = 0; + notes.clear(); + species.clear(); + } +}; + +struct AppData { + Rml::String page_title = "Main"; + bool is_dialog_open = false; + Rml::String error_message; + NewCritterForm form; +}; + +void sync_map_to_list(const std::map &critters, std::vector &critter_list); +bool setup_data_binding(Rml::Context *context, Rml::DataModelHandle &model_handle, AppData &app_data, std::vector &critter_list, std::map &critters); \ No newline at end of file