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 0eadb6b..453c24b 100644
Binary files a/assets/local.db and b/assets/local.db differ
diff --git a/assets/main.rml b/assets/main.rml
index 26742e9..11728f6 100644
--- a/assets/main.rml
+++ b/assets/main.rml
@@ -6,14 +6,47 @@
{{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