From 0b86edc57dba5199af47a830136cf3261e4acb2b Mon Sep 17 00:00:00 2001 From: CDevv Date: Thu, 23 Apr 2026 19:10:29 +0300 Subject: [PATCH 01/71] Use nlohmann::json for UI Element properties --- include/colorRect.hpp | 5 ++--- include/game.hpp | 2 +- include/gamedata.hpp | 4 ---- include/imageRect.hpp | 10 ++++++---- include/property.hpp | 24 ++++++++++++++++++++++++ include/textArea.hpp | 10 ++++++---- include/uiElement.hpp | 14 +++++++++++++- src/colorRect.cpp | 26 +++++++++++++++++++++++--- src/game.cpp | 13 ++++++++++--- src/game/main.cpp | 2 +- src/imageRect.cpp | 8 ++++++++ src/textArea.cpp | 13 ++++++++++++- src/uiElement.cpp | 13 ++++++++++++- 13 files changed, 118 insertions(+), 26 deletions(-) create mode 100644 include/property.hpp diff --git a/include/colorRect.hpp b/include/colorRect.hpp index adab37c2..8749c0dc 100644 --- a/include/colorRect.hpp +++ b/include/colorRect.hpp @@ -6,14 +6,13 @@ #include "uiElement.hpp" class ColorRect : public UIElement { -private: +public: Rectangle rect; Color color; -public: ColorRect(); - explicit ColorRect(Rectangle rect); + void setColor(Color newColor); void update() override; void draw() override; diff --git a/include/game.hpp b/include/game.hpp index 680e86c2..86f2e268 100644 --- a/include/game.hpp +++ b/include/game.hpp @@ -42,7 +42,7 @@ class Game { static SoundService &getSounds(); static ScriptService &getScripts(); - static void init(); + static void init(bool useBin = false); static void update(); static void draw(); diff --git a/include/gamedata.hpp b/include/gamedata.hpp index 8913f704..d772120c 100644 --- a/include/gamedata.hpp +++ b/include/gamedata.hpp @@ -134,10 +134,6 @@ struct RoomBin { std::string musicSource; }; -struct GameBinSettings { - std::string playerActor; -}; - struct ProjectProgramSettings { std::string projectTitle; std::string projectVersion; diff --git a/include/imageRect.hpp b/include/imageRect.hpp index 3acff511..bbdab2af 100644 --- a/include/imageRect.hpp +++ b/include/imageRect.hpp @@ -9,17 +9,19 @@ class ImageRect : public UIElement { private: - Rectangle rect; - std::string source; Texture2D texture; public: + Rectangle rect; + std::string source; + ImageRect(); ImageRect(Rectangle rect); + void setSource(const std::string &source); void setTexture(Texture2D texture); - void update(); - void draw(); + void update() override; + void draw() override; }; #endif diff --git a/include/property.hpp b/include/property.hpp new file mode 100644 index 00000000..a7735923 --- /dev/null +++ b/include/property.hpp @@ -0,0 +1,24 @@ +#ifndef _RPGPP_PROPERTY_H +#define _RPGPP_PROPERTY_H + +#include + +class IProperty { + virtual const std::string &getName() const; +}; + +template +class Property : public IProperty { + std::string name; + T Owner::*member; + +public: + Property(const std::string &name, T Owner::*member) { + this->name = name; + this->member = member; + } + + const std::string &getName() const override { return name; } +}; + +#endif \ No newline at end of file diff --git a/include/textArea.hpp b/include/textArea.hpp index dfce7781..37f1299c 100644 --- a/include/textArea.hpp +++ b/include/textArea.hpp @@ -9,16 +9,18 @@ class TextArea : public UIElement { private: - Rectangle rect; - std::string content; void putChar(int i, Vector2 *charPos, Vector2 *charMeasure) const; public: + Rectangle rect; + std::string content; + TextArea(); TextArea(Rectangle rect); + void setText(const std::string &text); - void update(); - void draw(); + void update() override; + void draw() override; }; #endif diff --git a/include/uiElement.hpp b/include/uiElement.hpp index 4467ce56..96f9abdc 100644 --- a/include/uiElement.hpp +++ b/include/uiElement.hpp @@ -1,13 +1,25 @@ #ifndef _RPGPP_UIELEMENT_H #define _RPGPP_UIELEMENT_H +#include +#include +#include +#include +#include + +#include "nlohmann/json.hpp" +#include "nlohmann/json_fwd.hpp" + class UIElement { public: - virtual ~UIElement() = default; + std::unique_ptr props; UIElement(); + virtual ~UIElement() = default; virtual void update(); virtual void draw(); + virtual nlohmann::json &getProperties(); + void setProperties(const nlohmann::json &newProps); }; #endif diff --git a/src/colorRect.cpp b/src/colorRect.cpp index b6c4c505..07c2e66e 100644 --- a/src/colorRect.cpp +++ b/src/colorRect.cpp @@ -2,17 +2,37 @@ #include -ColorRect::ColorRect() : rect(Rectangle{1, 1, 1, 1}), color() {} +ColorRect::ColorRect() : ColorRect({1, 1, 1, 1}) {} ColorRect::ColorRect(const Rectangle rect) { this->rect = rect; this->color = RAYWHITE; + + props->push_back({"rect_x", rect.x}); + props->push_back({"rect_y", rect.y}); + props->push_back({"rect_width", rect.width}); + props->push_back({"rect_height", rect.height}); + + props->at("color_r") = color.r; + props->at("color_g") = color.g; + props->at("color_b") = color.b; + props->at("color_a") = color.a; } -void ColorRect::setColor(const Color newColor) { this->color = newColor; } +void ColorRect::setColor(const Color newColor) { + this->color = newColor; + props->at("color_r") = newColor.r; + props->at("color_g") = newColor.g; + props->at("color_b") = newColor.b; + props->at("color_a") = newColor.a; +} void ColorRect::update() { // TODO } -void ColorRect::draw() { DrawRectangleRec(rect, color); } +void ColorRect::draw() { + Rectangle rect = {props->at("rect_x"), props->at("rect_y"), props->at("rect_width"), props->at("rect_height")}; + Color color = {props->at("color_r"), props->at("color_g"), props->at("color_b")}; + DrawRectangleRec(rect, color); +} diff --git a/src/game.cpp b/src/game.cpp index 2846715e..713b84fd 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -37,9 +37,16 @@ Game &Game::instance() { return *instance_; } -void Game::init() { +void Game::init(bool usesBin) { gameData = std::make_unique(); - usesBin = false; + Game::usesBin = false; + + if (usesBin) { + std::string filePath = "game.bin"; + gameData = std::make_unique(deserializeFile(filePath)); + Game::usesBin = true; + } + resources = std::make_unique(); state = std::make_unique(); world = std::make_unique(); @@ -55,7 +62,7 @@ void Game::useBin(const std::string &filePath) { usesBin = true; // resources - resources->init(); + // resources->init(); /// Setup program SetWindowTitle(gameData->title.c_str()); diff --git a/src/game/main.cpp b/src/game/main.cpp index b1513d88..40a36ac9 100644 --- a/src/game/main.cpp +++ b/src/game/main.cpp @@ -10,7 +10,7 @@ int main() { InitAudioDevice(); Game game; - game.init(); + game.init(true); game.useBin("game.bin"); diff --git a/src/imageRect.cpp b/src/imageRect.cpp index 840ec749..9dedbcaa 100644 --- a/src/imageRect.cpp +++ b/src/imageRect.cpp @@ -9,6 +9,13 @@ ImageRect::ImageRect() : rect(Rectangle{}), texture() {} ImageRect::ImageRect(Rectangle rect) : texture() { this->rect = rect; this->source = ""; + + props->push_back({"rect_x", rect.x}); + props->push_back({"rect_y", rect.y}); + props->push_back({"rect_width", rect.width}); + props->push_back({"rect_height", rect.height}); + + props->push_back({"source", source}); } void ImageRect::setSource(const std::string &source) { this->source = source; } @@ -18,6 +25,7 @@ void ImageRect::setTexture(Texture2D texture) { this->texture = texture; } void ImageRect::update() {} void ImageRect::draw() { + Rectangle rect = {props->at("rect_x"), props->at("rect_y"), props->at("rect_width"), props->at("rect_height")}; DrawTexturePro(texture, Rectangle{0, 0, static_cast(texture.width), static_cast(texture.height)}, rect, Vector2{0, 0}, 0.0f, WHITE); } diff --git a/src/textArea.cpp b/src/textArea.cpp index ab455d58..4fe3067d 100644 --- a/src/textArea.cpp +++ b/src/textArea.cpp @@ -9,10 +9,18 @@ TextArea::TextArea() : rect(Rectangle{}) {} TextArea::TextArea(Rectangle rect) { this->rect = rect; this->content = ""; + + props->push_back({"rect_x", rect.x}); + props->push_back({"rect_y", rect.y}); + props->push_back({"rect_width", rect.width}); + props->push_back({"rect_height", rect.height}); + + props->push_back({"content", content}); } void TextArea::update() { // TODO + content = props->at("content"); } void TextArea::draw() { @@ -32,4 +40,7 @@ void TextArea::putChar(int i, Vector2 *charPos, Vector2 *charMeasure) const { *charMeasure = MeasureTextEx(Game::getUi().getFont(), TextSubtext(content.c_str(), i, 1), 13 * 3, 1.0f); } -void TextArea::setText(const std::string &text) { this->content = text; } +void TextArea::setText(const std::string &text) { + this->content = text; + props->at("content") = text; +} diff --git a/src/uiElement.cpp b/src/uiElement.cpp index 739c52ef..d398833e 100644 --- a/src/uiElement.cpp +++ b/src/uiElement.cpp @@ -1,6 +1,17 @@ #include "uiElement.hpp" -UIElement::UIElement() = default; +#include + +#include "nlohmann/json_fwd.hpp" + +UIElement::UIElement() { props = std::make_unique(nlohmann::json::object()); }; + +void UIElement::setProperties(const nlohmann::json &newProps) { + props.reset(); + props = std::make_unique(newProps); +} + +nlohmann::json &UIElement::getProperties() { return *props; } void UIElement::draw() {} void UIElement::update() {} From e273f9e2b3599ab4341c12a7051eb6cbc897b5f4 Mon Sep 17 00:00:00 2001 From: CDevv Date: Fri, 24 Apr 2026 19:17:52 +0300 Subject: [PATCH 02/71] Refactor UIElement + Introduce NinePatchImageRect --- include/any_ptr/any_ptr.h | 254 +++++++++++++++++++++++++++++++++ include/colorRect.hpp | 7 + include/game.hpp | 1 + include/gamedata.hpp | 7 + include/imageRect.hpp | 9 ++ include/interfaceView.hpp | 1 + include/jsonConversions.hpp | 19 +++ include/ninePatchImageRect.hpp | 37 +++++ include/resourceService.hpp | 2 + include/textArea.hpp | 6 + include/uiElement.hpp | 16 ++- src/colorRect.cpp | 49 ++++--- src/game.cpp | 2 + src/gamedata.cpp | 6 + src/imageRect.cpp | 67 ++++++++- src/interfaceService.cpp | 12 ++ src/interfaceView.cpp | 3 + src/jsonConversions.cpp | 32 +++++ src/ninePatchImageRect.cpp | 93 ++++++++++++ src/resourceService.cpp | 6 +- src/textArea.cpp | 39 +++-- src/uiElement.cpp | 11 +- 22 files changed, 630 insertions(+), 49 deletions(-) create mode 100644 include/any_ptr/any_ptr.h create mode 100644 include/jsonConversions.hpp create mode 100644 include/ninePatchImageRect.hpp create mode 100644 src/jsonConversions.cpp create mode 100644 src/ninePatchImageRect.cpp diff --git a/include/any_ptr/any_ptr.h b/include/any_ptr/any_ptr.h new file mode 100644 index 00000000..e39764da --- /dev/null +++ b/include/any_ptr/any_ptr.h @@ -0,0 +1,254 @@ +// MIT License +// +// Copyright (c) 2017 Scott Slack-Smith +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +#pragma once +#include +#include +#include +#ifdef _MSC_VER + #if _HAS_CXX17 != 0 + #include + #define ANY_PTR_HAS_LIB_OPTIONAL + #endif +#else + #if __has_include() // requires GCC 5 or greater + #include + #else + #include + namespace std { + using std::experimental::optional; + } // namespace std + #endif +#endif + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable: 26461 26493 26496) // disable various GSL warnings emitted the code analyzer +#endif + +namespace xxx { + + class bad_any_ptr_cast : public std::bad_cast + { + public: + virtual const char* what() const noexcept override { return "bad any_ptr cast"; } + }; + + inline namespace v1 { + + /** + The class any_ptr is type-safe container for pointers to any type + that, unlike std::any, supports cv-qualifier promotion and dynamic up-cast. + + For example: + + struct Base {}; + struct Derived : public Base {}; + + auto ptr = std::make_unique(); + Derived * p = ptr.get(); + + std::any a1{ p }; + std::any_cast( a1 ); // cast succeeds + std::any_cast( a1 ); // cast fails as there's no implicit up cast + + xxx::any_ptr a2{ p }; + xxx::any_ptr_cast( a2 ); // implicit up cast succeeds + */ + class any_ptr + { + public: + //----------------------------------------------------- + // Canonical "Rule of 5" ctors, dtor and copy operators + + ~any_ptr() = default; + + any_ptr(any_ptr const & other) noexcept = default; + + any_ptr(any_ptr && other) noexcept = default; + + any_ptr& operator=(any_ptr const& other) noexcept = default; + + any_ptr& operator=(any_ptr && other) noexcept = default; + + //----------------------------------------------------- + // Constructors + + // set to empty state + any_ptr() noexcept = default; + + template + any_ptr(T* ptr) noexcept + : my_type_info{ &typeid(HeldType) } + , my_ptr{ const_cast> (ptr) } + , my_throw_func{ &any_ptr::throw_function> } + { + } + + //----------------------------------------------------- + // Modifiers + + // Reset to empty state + void reset() noexcept + { + // Use default ctor to reset to empty state + ::new (this) any_ptr(); + } + + // Swaps two any objects + void swap(any_ptr & other) noexcept + { + other = std::exchange(*this, std::move(other)); + } + + //----------------------------------------------------- + // Observers + + // Returns true if instance is non-empty. + bool has_value() const noexcept { return my_throw_func != nullptr; } + + // Returns the typeid(T*) of the contained pointer T* if instance is non-empty, + // otherwise typeid(void). + const std::type_info& type() const noexcept { return *my_type_info; } + + private: + + template + using HeldBaseType = T; + + template + using HeldType = std::add_pointer_t; + + using Throw_func = void(void *); + + // The typeid(T*) of the held pointer, + // otherwise set to typeid(void) to indicate an empty state. + const std::type_info * my_type_info{ & typeid(void) }; + // The held pointer + void* my_ptr{ nullptr }; + // The throw function that implements a dynamic up cast + Throw_func * my_throw_func{ nullptr }; + + // Attempt a dynamic up cast to T to replicate an implicit up cast. + // If the cast is successful then return { ptr , true } where ptr is the casted pointer + // otherwise return { nullptr , false }. + template + std::pair dynamic_up_cast() const noexcept + { + std::pair result{ nullptr, false }; + if (type() == typeid(HeldType)) { // cast succeeded + result.first = static_cast(my_ptr); + result.second = true; + } + else if (has_value()) { // attempt a dynamic up cast by throwing an exception + try { + my_throw_func(const_cast(my_ptr)); + } + catch (T* const ptr) { // up cast succeeded + result.first = ptr; + result.second = true; + } + catch (...) { // up cast failed + } + } + // else dynamic up cast failed + return result; + } + + template + static void throw_function(void * const ptr) + { + throw static_cast(ptr); + } + +#ifdef ANY_PTR_HAS_LIB_OPTIONAL + + template + friend std::optional any_ptr_cast(any_ptr const * any_ptr_) noexcept; + +#else // replace std::optional with std::pair + + template + std::pair any_ptr_cast(const any_ptr * any_ptr_) noexcept; + +#endif + + template + friend T* any_ptr_cast(any_ptr const & any_ptr_); + }; + + //----------------------------------------------------------------------------------------------------- + + +#ifdef ANY_PTR_HAS_LIB_OPTIONAL + + template + std::optional any_ptr_cast(const any_ptr * any_ptr_) noexcept + { + std::optional result; + const std::pair cast_result = any_ptr_->template dynamic_up_cast(); + if (cast_result.second) { + result = cast_result.first; + } + return result; + } + +#else // replace std::optional with std::pair + + template + std::pair any_ptr_cast(const any_ptr * any_ptr_) noexcept + { + return any_ptr_->template dynamic_up_cast(); + } + +#endif + + template + T* any_ptr_cast(any_ptr const & any_ptr_) + { + const std::pair result = any_ptr_.template dynamic_up_cast(); + if (result.second) { + return result.first; + } + throw bad_any_ptr_cast(); + } + + } // namespace v2 + +} // namespace xxx + + +namespace std { + + inline void swap(xxx::any_ptr & lhs, xxx::any_ptr & rhs) + { + lhs.swap(rhs); + } + +} // namespace std + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#ifdef ANY_PTR_HAS_LIB_OPTIONAL +#undef ANY_PTR_HAS_LIB_OPTIONAL +#endif diff --git a/include/colorRect.hpp b/include/colorRect.hpp index 8749c0dc..a29bfbec 100644 --- a/include/colorRect.hpp +++ b/include/colorRect.hpp @@ -3,6 +3,7 @@ #include +#include "gamedata.hpp" #include "uiElement.hpp" class ColorRect : public UIElement { @@ -13,6 +14,12 @@ class ColorRect : public UIElement { ColorRect(); explicit ColorRect(Rectangle rect); + void fromJson(const nlohmann::json &json) override; + nlohmann::json dumpJson() override; + void fromBin(UIElementBin &bin) override; + UIElementBin dumpBin() override; + std::map getProps() override; + void setColor(Color newColor); void update() override; void draw() override; diff --git a/include/game.hpp b/include/game.hpp index 86f2e268..308c63a5 100644 --- a/include/game.hpp +++ b/include/game.hpp @@ -32,6 +32,7 @@ class Game { public: Game(); static Game &instance(); + static Game *instancePtr(); static bool isUsingBin(); static void useBin(const std::string &filePath); static GameData &getBin(); diff --git a/include/gamedata.hpp b/include/gamedata.hpp index d772120c..ee233d0f 100644 --- a/include/gamedata.hpp +++ b/include/gamedata.hpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include "dialogueBalloon.hpp" @@ -39,6 +40,12 @@ struct IRect { int height; }; +typedef std::variant PropVariant; + +struct UIElementBin { + std::map props; +}; + struct ActorBin { std::string name; std::string tileSetName; diff --git a/include/imageRect.hpp b/include/imageRect.hpp index bbdab2af..00b62ee1 100644 --- a/include/imageRect.hpp +++ b/include/imageRect.hpp @@ -10,16 +10,25 @@ class ImageRect : public UIElement { private: Texture2D texture; + void loadTexture(); public: Rectangle rect; std::string source; + int scale; ImageRect(); ImageRect(Rectangle rect); + void fromJson(const nlohmann::json &json) override; + nlohmann::json dumpJson() override; + void fromBin(UIElementBin &bin) override; + UIElementBin dumpBin() override; + std::map getProps() override; + void setSource(const std::string &source); void setTexture(Texture2D texture); + void update() override; void draw() override; }; diff --git a/include/interfaceView.hpp b/include/interfaceView.hpp index ac8c8672..2a607971 100644 --- a/include/interfaceView.hpp +++ b/include/interfaceView.hpp @@ -17,6 +17,7 @@ class InterfaceView { InterfaceView(); explicit InterfaceView(Rectangle rect); void addElement(UIElement *element); + void addElement(std::unique_ptr element); void update() const; void draw() const; }; diff --git a/include/jsonConversions.hpp b/include/jsonConversions.hpp new file mode 100644 index 00000000..dd925130 --- /dev/null +++ b/include/jsonConversions.hpp @@ -0,0 +1,19 @@ +#ifndef _RPGPP_JSONCONVERSIONS_H +#define _RPGPP_JSONCONVERSIONS_H + +#include + +#include "raylib.h" + +using json = nlohmann::json; + +void to_json(json &j, const Rectangle &rect); +void from_json(const json &j, Rectangle &rect); + +void to_json(json &j, const Color &color); +void from_json(const json &j, Color &color); + +void to_json(json &j, const NPatchInfo &info); +void from_json(const json &j, NPatchInfo &info); + +#endif \ No newline at end of file diff --git a/include/ninePatchImageRect.hpp b/include/ninePatchImageRect.hpp new file mode 100644 index 00000000..21305377 --- /dev/null +++ b/include/ninePatchImageRect.hpp @@ -0,0 +1,37 @@ +#ifndef _RPGPP_NINEPATCHIMAGERECT_H +#define _RPGPP_NINEPATCHIMAGERECT_H + +#include + +#include + +#include "uiElement.hpp" + +class NinePatchImageRect : public UIElement { +private: + Texture2D texture; + void loadTexture(); + +public: + Rectangle rect; + std::string source; + int scale; + NPatchInfo npatchInfo; + + NinePatchImageRect(); + NinePatchImageRect(Rectangle rect); + + void fromJson(const nlohmann::json &json) override; + nlohmann::json dumpJson() override; + void fromBin(UIElementBin &bin) override; + UIElementBin dumpBin() override; + std::map getProps() override; + + void setSource(const std::string &source); + void setTexture(Texture2D texture); + + void update() override; + void draw() override; +}; + +#endif \ No newline at end of file diff --git a/include/resourceService.hpp b/include/resourceService.hpp index 947f3584..11273a40 100644 --- a/include/resourceService.hpp +++ b/include/resourceService.hpp @@ -18,9 +18,11 @@ class ResourceService { void addTextureFromFile(const std::string &filePath); void addTexture(const std::string &id, Texture2D texture); Texture2D getTexture(const std::string &id); + bool textureExists(const std::string &id); void addFont(const std::string &id, Font font); void addFontFromFile(const std::string &filePath, int fontSize); Font getFont(const std::string &id); + bool fontExists(const std::string &id); void unload() const; }; diff --git a/include/textArea.hpp b/include/textArea.hpp index 37f1299c..26922d24 100644 --- a/include/textArea.hpp +++ b/include/textArea.hpp @@ -18,6 +18,12 @@ class TextArea : public UIElement { TextArea(); TextArea(Rectangle rect); + void fromJson(const nlohmann::json &json) override; + nlohmann::json dumpJson() override; + void fromBin(UIElementBin &bin) override; + UIElementBin dumpBin() override; + std::map getProps() override; + void setText(const std::string &text); void update() override; void draw() override; diff --git a/include/uiElement.hpp b/include/uiElement.hpp index 96f9abdc..f135e572 100644 --- a/include/uiElement.hpp +++ b/include/uiElement.hpp @@ -7,19 +7,25 @@ #include #include +#include "any_ptr/any_ptr.h" +#include "gamedata.hpp" +#include "jsonConversions.hpp" #include "nlohmann/json.hpp" #include "nlohmann/json_fwd.hpp" +#include "saveable.hpp" -class UIElement { +class UIElement : public ISaveable { public: - std::unique_ptr props; - UIElement(); virtual ~UIElement() = default; virtual void update(); virtual void draw(); - virtual nlohmann::json &getProperties(); - void setProperties(const nlohmann::json &newProps); + + // virtual nlohmann::json dumpJson(); + virtual void fromJson(const nlohmann::json &json) = 0; + virtual void fromBin(UIElementBin &bin) = 0; + virtual UIElementBin dumpBin() = 0; + virtual std::map getProps() = 0; }; #endif diff --git a/src/colorRect.cpp b/src/colorRect.cpp index 07c2e66e..0b691d15 100644 --- a/src/colorRect.cpp +++ b/src/colorRect.cpp @@ -2,37 +2,48 @@ #include +#include + +#include "gamedata.hpp" +#include "nlohmann/json_fwd.hpp" + ColorRect::ColorRect() : ColorRect({1, 1, 1, 1}) {} ColorRect::ColorRect(const Rectangle rect) { this->rect = rect; this->color = RAYWHITE; +} - props->push_back({"rect_x", rect.x}); - props->push_back({"rect_y", rect.y}); - props->push_back({"rect_width", rect.width}); - props->push_back({"rect_height", rect.height}); +std::map ColorRect::getProps() { return {{"rect", &rect}, {"color", &color}}; } + +void ColorRect::fromJson(const nlohmann::json &json) { + this->rect = json.at("rect"); + this->color = json.at("color"); +} - props->at("color_r") = color.r; - props->at("color_g") = color.g; - props->at("color_b") = color.b; - props->at("color_a") = color.a; +nlohmann::json ColorRect::dumpJson() { + auto j = nlohmann::json::object(); + j["rect"] = rect; + j["color"] = color; + return j; } -void ColorRect::setColor(const Color newColor) { - this->color = newColor; - props->at("color_r") = newColor.r; - props->at("color_g") = newColor.g; - props->at("color_b") = newColor.b; - props->at("color_a") = newColor.a; +void ColorRect::fromBin(UIElementBin &bin) { + this->rect = std::get(bin.props["rect"]); + this->color = std::get(bin.props["color"]); } +UIElementBin ColorRect::dumpBin() { + UIElementBin bin; + bin.props["rect"] = rect; + bin.props["color"] = color; + return bin; +} + +void ColorRect::setColor(const Color newColor) { this->color = newColor; } + void ColorRect::update() { // TODO } -void ColorRect::draw() { - Rectangle rect = {props->at("rect_x"), props->at("rect_y"), props->at("rect_width"), props->at("rect_height")}; - Color color = {props->at("color_r"), props->at("color_g"), props->at("color_b")}; - DrawRectangleRec(rect, color); -} +void ColorRect::draw() { DrawRectangleRec(rect, color); } diff --git a/src/game.cpp b/src/game.cpp index 713b84fd..efc87feb 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -37,6 +37,8 @@ Game &Game::instance() { return *instance_; } +Game *Game::instancePtr() { return instance_; } + void Game::init(bool usesBin) { gameData = std::make_unique(); Game::usesBin = false; diff --git a/src/gamedata.cpp b/src/gamedata.cpp index 2d5d7e95..13fab192 100644 --- a/src/gamedata.cpp +++ b/src/gamedata.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include "dialogueBalloon.hpp" #include "interactable.hpp" @@ -21,6 +22,11 @@ void serialize(Archive &a, IVector &b) { a(b.x, b.y); } +template +void serialize(Archive &a, Rectangle &b) { + a(b.x, b.y, b.width, b.height); +} + template void serialize(Archive &a, Color &b) { a(b.a, b.r, b.g, b.b); diff --git a/src/imageRect.cpp b/src/imageRect.cpp index 9dedbcaa..31f7e086 100644 --- a/src/imageRect.cpp +++ b/src/imageRect.cpp @@ -4,28 +4,81 @@ #include +#include "gamedata.hpp" +#include "nlohmann/json_fwd.hpp" + ImageRect::ImageRect() : rect(Rectangle{}), texture() {} ImageRect::ImageRect(Rectangle rect) : texture() { this->rect = rect; this->source = ""; + this->scale = 1; +} + +void ImageRect::loadTexture() { + if (Game::instancePtr() != nullptr) { + std::string query = TextFormat("%s-%i", source.c_str(), scale); + if (Game::getResources().textureExists(query) || scale > 1) { + texture = Game::getResources().getTexture(query); + } else { + texture = Game::getResources().getTexture(source); + } + } else { + if (scale > 1) { + Image image = LoadImage(source.c_str()); + ImageResize(&image, image.width * scale, image.height * scale); + texture = LoadTextureFromImage(image); + UnloadImage(image); + } else { + texture = LoadTexture(source.c_str()); + } + } +} + +void ImageRect::fromJson(const nlohmann::json &json) { + rect = json.at("rect"); + source = json.at("source"); + scale = json.at("scale"); + loadTexture(); +} - props->push_back({"rect_x", rect.x}); - props->push_back({"rect_y", rect.y}); - props->push_back({"rect_width", rect.width}); - props->push_back({"rect_height", rect.height}); +nlohmann::json ImageRect::dumpJson() { + auto j = nlohmann::json::object(); + j["rect"] = rect; + j["source"] = source; + j["scale"] = scale; + return j; +} + +void ImageRect::fromBin(UIElementBin &bin) { + rect = std::get(bin.props["rect"]); + source = std::get(bin.props["source"]); + scale = std::get(bin.props["scale"]); + loadTexture(); +} - props->push_back({"source", source}); +UIElementBin ImageRect::dumpBin() { + UIElementBin bin; + bin.props["rect"] = rect; + bin.props["source"] = source; + bin.props["scale"] = scale; + return bin; } -void ImageRect::setSource(const std::string &source) { this->source = source; } +std::map ImageRect::getProps() { + return {{"rect", &rect}, {"source", &source}, {"scale", &scale}}; +} + +void ImageRect::setSource(const std::string &source) { + this->source = source; + this->loadTexture(); +} void ImageRect::setTexture(Texture2D texture) { this->texture = texture; } void ImageRect::update() {} void ImageRect::draw() { - Rectangle rect = {props->at("rect_x"), props->at("rect_y"), props->at("rect_width"), props->at("rect_height")}; DrawTexturePro(texture, Rectangle{0, 0, static_cast(texture.width), static_cast(texture.height)}, rect, Vector2{0, 0}, 0.0f, WHITE); } diff --git a/src/interfaceService.cpp b/src/interfaceService.cpp index c33ce297..2ec8013e 100644 --- a/src/interfaceService.cpp +++ b/src/interfaceService.cpp @@ -2,13 +2,16 @@ #include +#include #include #include "colorRect.hpp" #include "game.hpp" #include "imageRect.hpp" #include "interfaceView.hpp" +#include "ninePatchImageRect.hpp" #include "textArea.hpp" +#include "uiElement.hpp" InterfaceService::InterfaceService() { fpsVisible = false; @@ -43,6 +46,15 @@ InterfaceService::InterfaceService() { imageRect->setTexture(LoadTexture("logo-sq.png")); view.addElement(imageRect); + NinePatchImageRect *npatch = new NinePatchImageRect(Rectangle{0, 200, 150, 100}); + npatch->setSource("ui-npatch.png"); + npatch->npatchInfo.top = 3; + npatch->npatchInfo.left = 3; + npatch->npatchInfo.bottom = 3; + npatch->npatchInfo.right = 3; + npatch->npatchInfo.layout = NPATCH_NINE_PATCH; + view.addElement(std::move(npatch)); + views->emplace("test", std::move(view)); } diff --git a/src/interfaceView.cpp b/src/interfaceView.cpp index a2080400..ec6dcdb9 100644 --- a/src/interfaceView.cpp +++ b/src/interfaceView.cpp @@ -1,6 +1,7 @@ #include "interfaceView.hpp" #include +#include #include "uiElement.hpp" @@ -17,6 +18,8 @@ void InterfaceView::addElement(UIElement *element) { this->elements.push_back(std::move(ptr)); } +void InterfaceView::addElement(std::unique_ptr element) { this->elements.push_back(std::move(element)); } + void InterfaceView::update() const { for (auto &&i : elements) { i->update(); diff --git a/src/jsonConversions.cpp b/src/jsonConversions.cpp new file mode 100644 index 00000000..7638194a --- /dev/null +++ b/src/jsonConversions.cpp @@ -0,0 +1,32 @@ +#include "jsonConversions.hpp" + +void to_json(json &j, const Rectangle &rect) { j = json{rect.x, rect.y, rect.width, rect.height}; } + +void from_json(const json &j, Rectangle &rect) { + j.at(0).get_to(rect.x); + j.at(1).get_to(rect.y); + j.at(2).get_to(rect.width); + j.at(3).get_to(rect.height); +} + +void to_json(json &j, const Color &color) { j = json{color.r, color.g, color.b, color.a}; } + +void from_json(const json &j, Color &color) { + j.at(0).get_to(color.r); + j.at(1).get_to(color.g); + j.at(2).get_to(color.b); + j.at(3).get_to(color.a); +} + +void to_json(json &j, const NPatchInfo &info) { + j = json{info.source, info.left, info.top, info.right, info.bottom, info.layout}; +} + +void from_json(const json &j, NPatchInfo &info) { + j.at(0).get_to(info.source); + j.at(1).get_to(info.left); + j.at(2).get_to(info.top); + j.at(3).get_to(info.right); + j.at(4).get_to(info.bottom); + j.at(5).get_to(info.layout); +} \ No newline at end of file diff --git a/src/ninePatchImageRect.cpp b/src/ninePatchImageRect.cpp new file mode 100644 index 00000000..dcef68e5 --- /dev/null +++ b/src/ninePatchImageRect.cpp @@ -0,0 +1,93 @@ +#include "ninePatchImageRect.hpp" + +#include "game.hpp" +#include "gamedata.hpp" +#include "nlohmann/json_fwd.hpp" +#include "raylib.h" + +NinePatchImageRect::NinePatchImageRect() : NinePatchImageRect({1, 1, 1, 1}) {} + +NinePatchImageRect::NinePatchImageRect(Rectangle rect) { + this->rect = rect; + this->scale = 1; + this->source = ""; + this->npatchInfo = {{0, 0, 20, 20}, 0, 0, 0, 0, 0}; +} + +void NinePatchImageRect::loadTexture() { + if (Game::instancePtr() != nullptr) { + std::string query = TextFormat("%s-%i", source.c_str(), scale); + if (Game::getResources().textureExists(query) || scale > 1) { + texture = Game::getResources().getTexture(query); + } else { + texture = Game::getResources().getTexture(source); + } + } else { + if (scale > 1) { + Image image = LoadImage(source.c_str()); + ImageResize(&image, image.width * scale, image.height * scale); + texture = LoadTextureFromImage(image); + UnloadImage(image); + } else { + texture = LoadTexture(source.c_str()); + } + } +} + +void NinePatchImageRect::fromJson(const nlohmann::json &json) { + this->rect = json.at("rect"); + this->scale = json.at("scale"); + this->source = json.at("source"); + this->npatchInfo = json.at("npatchinfo"); +} + +nlohmann::json NinePatchImageRect::dumpJson() { + auto j = nlohmann::json::object(); + j["rect"] = rect; + j["scale"] = scale; + j["source"] = source; + j["npatchinfo"] = npatchInfo; + return j; +} + +void NinePatchImageRect::fromBin(UIElementBin &bin) { + rect = std::get(bin.props["rect"]); + scale = std::get(bin.props["scale"]); + source = std::get(bin.props["source"]); + npatchInfo = std::get(bin.props["npatchinfo"]); +} + +UIElementBin NinePatchImageRect::dumpBin() { + UIElementBin bin; + bin.props["rect"] = rect; + bin.props["scale"] = scale; + bin.props["source"] = source; + bin.props["npatchinfo"] = npatchInfo; + return bin; +} + +void NinePatchImageRect::setSource(const std::string &source) { + this->source = source; + this->loadTexture(); + this->npatchInfo.source = {0, 0, static_cast(texture.width), static_cast(texture.height)}; +} + +void NinePatchImageRect::setTexture(Texture2D texture) { this->texture = texture; } + +std::map NinePatchImageRect::getProps() { + return {{"rect", &rect}, {"scale", &scale}, {"source", &source}, {"npatchinfo", &npatchInfo}}; +} + +void NinePatchImageRect::update() {} + +void NinePatchImageRect::draw() { + Vector2 origin = {0.0f, 0.0f}; + + NPatchInfo info = npatchInfo; + info.left *= scale; + info.top *= scale; + info.right *= scale; + info.bottom *= scale; + + DrawTextureNPatch(texture, info, rect, origin, 0.0f, WHITE); +} \ No newline at end of file diff --git a/src/resourceService.cpp b/src/resourceService.cpp index 9655640f..700509e7 100644 --- a/src/resourceService.cpp +++ b/src/resourceService.cpp @@ -64,7 +64,9 @@ void ResourceService::addTextureFromFile(const std::string &filePath) { addTexture(newId, text); } -Texture2D ResourceService::getTexture(const std::string &id) { return textures[id]; } +Texture2D ResourceService::getTexture(const std::string &id) { return textures.at(id); } + +bool ResourceService::textureExists(const std::string &id) { return textures.count(id) > 0; } void ResourceService::addFont(const std::string &id, Font font) { fonts[id] = font; } @@ -75,6 +77,8 @@ void ResourceService::addFontFromFile(const std::string &filePath, int fontSize) Font ResourceService::getFont(const std::string &id) { return fonts.at(id); } +bool ResourceService::fontExists(const std::string &id) { return fonts.count(id) > 0; } + void ResourceService::unload() const { for (const auto &[name, texture] : textures) { if (IsTextureValid(texture)) { diff --git a/src/textArea.cpp b/src/textArea.cpp index 4fe3067d..f526d134 100644 --- a/src/textArea.cpp +++ b/src/textArea.cpp @@ -3,24 +3,46 @@ #include #include "game.hpp" +#include "gamedata.hpp" +#include "nlohmann/json_fwd.hpp" TextArea::TextArea() : rect(Rectangle{}) {} TextArea::TextArea(Rectangle rect) { this->rect = rect; this->content = ""; +} + +void TextArea::fromJson(const nlohmann::json &json) { + rect = json.at("rect"); + content = json.at("content"); +} + +nlohmann::json TextArea::dumpJson() { + auto j = nlohmann::json::object(); + j["rect"] = rect; + j["content"] = content; + return j; +} - props->push_back({"rect_x", rect.x}); - props->push_back({"rect_y", rect.y}); - props->push_back({"rect_width", rect.width}); - props->push_back({"rect_height", rect.height}); +void TextArea::fromBin(UIElementBin &bin) { + rect = std::get(bin.props["rect"]); + content = std::get(bin.props["content"]); +} - props->push_back({"content", content}); +UIElementBin TextArea::dumpBin() { + UIElementBin bin; + bin.props["rect"] = rect; + bin.props["content"] = content; + return bin; } +std::map TextArea::getProps() { return {{"rect", &rect}, {"content", &content}}; } + +void TextArea::setText(const std::string &text) { this->content = text; } + void TextArea::update() { // TODO - content = props->at("content"); } void TextArea::draw() { @@ -39,8 +61,3 @@ void TextArea::putChar(int i, Vector2 *charPos, Vector2 *charMeasure) const { *charMeasure = MeasureTextEx(Game::getUi().getFont(), TextSubtext(content.c_str(), i, 1), 13 * 3, 1.0f); } - -void TextArea::setText(const std::string &text) { - this->content = text; - props->at("content") = text; -} diff --git a/src/uiElement.cpp b/src/uiElement.cpp index d398833e..69434080 100644 --- a/src/uiElement.cpp +++ b/src/uiElement.cpp @@ -4,14 +4,13 @@ #include "nlohmann/json_fwd.hpp" -UIElement::UIElement() { props = std::make_unique(nlohmann::json::object()); }; +UIElement::UIElement() = default; -void UIElement::setProperties(const nlohmann::json &newProps) { - props.reset(); - props = std::make_unique(newProps); -} +void UIElement::fromJson(const nlohmann::json &json) {} -nlohmann::json &UIElement::getProperties() { return *props; } +void fromBin(UIElementBin &bin) {} +UIElementBin dumpBin() {} +std::map getProps() {} void UIElement::draw() {} void UIElement::update() {} From 07d8cb7bdade971ff5c4be72e5fe7a535695560a Mon Sep 17 00:00:00 2001 From: CDevv Date: Sat, 25 Apr 2026 19:08:54 +0300 Subject: [PATCH 03/71] Button and Label elements, simple Event system and callbacks. --- include/button.hpp | 33 +++++++++++ include/colorRect.hpp | 2 + include/gamedata.hpp | 32 +++++++++- include/interfaceElementFactory.hpp | 12 ++++ include/interfaceService.hpp | 6 +- include/interfaceView.hpp | 24 ++++++-- include/jsonConversions.hpp | 6 ++ include/label.hpp | 35 +++++++++++ include/uiElement.hpp | 44 ++++++++++++-- src/button.cpp | 81 +++++++++++++++++++++++++ src/colorRect.cpp | 26 ++++++-- src/gamedata.cpp | 5 ++ src/imageRect.cpp | 5 +- src/interfaceElementFactory.cpp | 19 ++++++ src/interfaceService.cpp | 66 +++++++++++++++++---- src/interfaceView.cpp | 84 ++++++++++++++++++++++++-- src/jsonConversions.cpp | 11 +++- src/label.cpp | 92 +++++++++++++++++++++++++++++ src/ninePatchImageRect.cpp | 5 +- src/textArea.cpp | 5 +- src/uiElement.cpp | 91 ++++++++++++++++++++++++++-- 21 files changed, 638 insertions(+), 46 deletions(-) create mode 100644 include/button.hpp create mode 100644 include/interfaceElementFactory.hpp create mode 100644 include/label.hpp create mode 100644 src/button.cpp create mode 100644 src/interfaceElementFactory.cpp create mode 100644 src/label.cpp diff --git a/include/button.hpp b/include/button.hpp new file mode 100644 index 00000000..939e6731 --- /dev/null +++ b/include/button.hpp @@ -0,0 +1,33 @@ +#ifndef _RPGPP_BUTTON_H +#define _RPGPP_BUTTON_H + +#include "colorRect.hpp" +#include "gamedata.hpp" +#include "label.hpp" +#include "raylib.h" +#include "uiElement.hpp" + +class Button : public UIElement { +public: + Label label; + ColorRect colorRect; + Color normalTextColor; + Color focusedTextColor; + Color shownTextColor; + + Button(); + + void fromJson(const nlohmann::json &json) override; + nlohmann::json dumpJson() override; + void fromBin(UIElementBin &bin) override; + UIElementBin dumpBin() override; + std::map getProps() override; + + void setRect(const Rectangle &rect); + void setText(const std::string &text); + + void update() override; + void draw() override; +}; + +#endif \ No newline at end of file diff --git a/include/colorRect.hpp b/include/colorRect.hpp index a29bfbec..ff7b016d 100644 --- a/include/colorRect.hpp +++ b/include/colorRect.hpp @@ -10,6 +10,8 @@ class ColorRect : public UIElement { public: Rectangle rect; Color color; + Color borderColor; + int borderWidth; ColorRect(); explicit ColorRect(Rectangle rect); diff --git a/include/gamedata.hpp b/include/gamedata.hpp index ee233d0f..2b891fe9 100644 --- a/include/gamedata.hpp +++ b/include/gamedata.hpp @@ -40,9 +40,35 @@ struct IRect { int height; }; -typedef std::variant PropVariant; +struct UIElementRef { + std::string title = ""; +}; + +enum TextAlignment { + TEXT_ALIGN_LEFT = 0, + TEXT_ALIGN_TOP = 0, + TEXT_ALIGN_CENTRE = 1, + TEXT_ALIGN_MIDDLE = 1, + TEXT_ALIGN_RIGHT = 2, + TEXT_ALIGN_BOTTOM = 2 +}; + +enum InterfaceElementType { + INTERFACE_NULL, + INTERFACE_TEXTAREA, + INTERFACE_LABEL, + INTERFACE_COLORRECT, + INTERFACE_IMAGERECT, + INTERFACE_NINEPATCHIMAGERECT, + INTERFACE_BUTTON +}; + +#define RPGPP_INTERFACE_ELEMENT_MAX (7) + +typedef std::variant PropVariant; struct UIElementBin { + InterfaceElementType type; std::map props; }; @@ -141,6 +167,10 @@ struct RoomBin { std::string musicSource; }; +struct InterfaceViewBin { + std::map elements; +}; + struct ProjectProgramSettings { std::string projectTitle; std::string projectVersion; diff --git a/include/interfaceElementFactory.hpp b/include/interfaceElementFactory.hpp new file mode 100644 index 00000000..c88b97ba --- /dev/null +++ b/include/interfaceElementFactory.hpp @@ -0,0 +1,12 @@ +#ifndef _RPGPP_INTERFACEELEMENTFACTORY_H +#define _RPGPP_INTERFACEELEMENTFACTORY_H + +#include +#include +#include + +#include "uiElement.hpp" + +std::unique_ptr constructElement(InterfaceElementType type); + +#endif \ No newline at end of file diff --git a/include/interfaceService.hpp b/include/interfaceService.hpp index 97de8ba8..7be706f7 100644 --- a/include/interfaceService.hpp +++ b/include/interfaceService.hpp @@ -21,7 +21,9 @@ class InterfaceService { /** Component for in-game dialogue. */ DialogueBalloon dialogue; /** Available UI Views. */ - std::unique_ptr> views; + std::map> views; + /** Current active view. */ + std::string currentViewName; public: /** Empty constructor. */ @@ -35,6 +37,8 @@ class InterfaceService { void showDialogue(const std::string &id); /** Open the dialogue with a Dialogue structure */ void showDialogue(const DialogueBin &dialogue); + /** Get the current active view. */ + InterfaceView *getCurrentView(); /** Update routine. */ void update(); /** Draw routine. */ diff --git a/include/interfaceView.hpp b/include/interfaceView.hpp index 2a607971..66e98476 100644 --- a/include/interfaceView.hpp +++ b/include/interfaceView.hpp @@ -4,20 +4,36 @@ #include #include +#include #include +#include "gamedata.hpp" +#include "saveable.hpp" #include "uiElement.hpp" -class InterfaceView { +class InterfaceView : public ISaveable { private: Rectangle rect; - std::vector> elements; + std::map> elements; + UIElement *focused = nullptr; public: InterfaceView(); explicit InterfaceView(Rectangle rect); - void addElement(UIElement *element); - void addElement(std::unique_ptr element); + + InterfaceView(const std::string &filePath); + nlohmann::json dumpJson(); + + InterfaceView(InterfaceViewBin &bin); + + void onNotify(Event event); + bool elementExists(const std::string &title); + void addElement(const std::string &title, UIElement *element); + void addElement(const std::string &title, std::unique_ptr element); + void removeElement(const std::string &title); + UIElement *getElement(const std::string &title); + void renameElement(const std::string &title, const std::string &newTitle); + void changeFocusedElement(const std::string &title); void update() const; void draw() const; }; diff --git a/include/jsonConversions.hpp b/include/jsonConversions.hpp index dd925130..d57799fa 100644 --- a/include/jsonConversions.hpp +++ b/include/jsonConversions.hpp @@ -3,8 +3,11 @@ #include +#include "gamedata.hpp" #include "raylib.h" +class UIElement; + using json = nlohmann::json; void to_json(json &j, const Rectangle &rect); @@ -16,4 +19,7 @@ void from_json(const json &j, Color &color); void to_json(json &j, const NPatchInfo &info); void from_json(const json &j, NPatchInfo &info); +void to_json(json &j, const UIElementRef &info); +void from_json(const json &j, UIElementRef &info); + #endif \ No newline at end of file diff --git a/include/label.hpp b/include/label.hpp new file mode 100644 index 00000000..b2bdea53 --- /dev/null +++ b/include/label.hpp @@ -0,0 +1,35 @@ +#ifndef _RPGPP_LABEL_H +#define _RPGPP_LABEL_H + +#include "gamedata.hpp" +#include "raylib.h" +#include "uiElement.hpp" + +class Label : public UIElement { +public: + Font font; + + Rectangle rect; + std::string text; + Color textColor; + TextAlignment horizontalAlignment; + TextAlignment verticalAlignment; + std::string fontName; + int fontSize; + + Label(); + Label(Rectangle rect); + + void fromJson(const nlohmann::json &json) override; + nlohmann::json dumpJson() override; + void fromBin(UIElementBin &bin) override; + UIElementBin dumpBin() override; + std::map getProps() override; + + void setText(const std::string &text); + + void update() override; + void draw() override; +}; + +#endif \ No newline at end of file diff --git a/include/uiElement.hpp b/include/uiElement.hpp index f135e572..cc7de07f 100644 --- a/include/uiElement.hpp +++ b/include/uiElement.hpp @@ -2,6 +2,7 @@ #define _RPGPP_UIELEMENT_H #include +#include #include #include #include @@ -12,20 +13,53 @@ #include "jsonConversions.hpp" #include "nlohmann/json.hpp" #include "nlohmann/json_fwd.hpp" +#include "raylib.h" #include "saveable.hpp" +struct Event { + KeyboardKey key; +}; + +enum CallbackType { CALLBACK_TRIGGER, CALLBACK_FOCUSED, CALLBACK_UNFOCUSED }; + +#define RPGPP_CALLBACK_TYPE_MAX (3) + class UIElement : public ISaveable { +protected: + bool focusable = false; + public: + InterfaceElementType elementType; + + UIElementRef upButton; + UIElementRef downButton; + UIElementRef leftButton; + UIElementRef rightButton; + + std::map> callbacks; + UIElement(); + UIElement(InterfaceElementType elementType); virtual ~UIElement() = default; + InterfaceElementType getType(); + virtual void onNotify(Event event); + bool isFocusable(); + void invokeCallback(CallbackType type); + void setCallback(CallbackType type, std::function callback); + + void setUpElement(const std::string &title); + void setDownElement(const std::string &title); + void setLeftElement(const std::string &title); + void setRightElement(const std::string &title); + virtual void update(); virtual void draw(); - // virtual nlohmann::json dumpJson(); - virtual void fromJson(const nlohmann::json &json) = 0; - virtual void fromBin(UIElementBin &bin) = 0; - virtual UIElementBin dumpBin() = 0; - virtual std::map getProps() = 0; + virtual nlohmann::json dumpJson(); + virtual void fromJson(const nlohmann::json &json); + virtual UIElementBin dumpBin(); + virtual void fromBin(UIElementBin &bin); + virtual std::map getProps(); }; #endif diff --git a/src/button.cpp b/src/button.cpp new file mode 100644 index 00000000..24fdbe52 --- /dev/null +++ b/src/button.cpp @@ -0,0 +1,81 @@ +#include "button.hpp" + +#include + +#include "gamedata.hpp" +#include "nlohmann/json_fwd.hpp" +#include "raylib.h" +#include "uiElement.hpp" + +Button::Button() : UIElement(INTERFACE_BUTTON) { + focusable = true; + normalTextColor = label.textColor; + focusedTextColor = DARKGRAY; + shownTextColor = label.textColor; + callbacks[CALLBACK_TRIGGER] = [] { printf("button trigger.. \n"); }; + callbacks[CALLBACK_UNFOCUSED] = [this] { shownTextColor = normalTextColor; }; + callbacks[CALLBACK_FOCUSED] = [this] { shownTextColor = focusedTextColor; }; +} + +void Button::fromJson(const nlohmann::json &json) { + UIElement::fromJson(json); + colorRect.fromJson(json); + label.fromJson(json); + normalTextColor = label.textColor; + focusedTextColor = json["focusedTextColor"]; +} + +nlohmann::json Button::dumpJson() { + auto j = UIElement::dumpJson(); + auto colorRectDump = colorRect.dumpJson(); + for (auto &i : colorRectDump.items()) { + j[i.key()] = i.value(); + } + auto labelDump = label.dumpJson(); + for (auto &i : labelDump.items()) { + j[i.key()] = i.value(); + } + j["focusedTextColor"] = focusedTextColor; + return j; +} + +void Button::fromBin(UIElementBin &bin) { + UIElement::fromBin(bin); + colorRect.fromBin(bin); + label.fromBin(bin); + focusedTextColor = std::get(bin.props["focusedTextColor"]); +} + +UIElementBin Button::dumpBin() { + auto bin = UIElement::dumpBin(); + bin.props.merge(colorRect.dumpBin().props); + bin.props.merge(label.dumpBin().props); + bin.props["focusedTextColor"] = focusedTextColor; + return bin; +} + +std::map Button::getProps() { + auto map = UIElement::getProps(); + map.merge(colorRect.getProps()); + map.merge(label.getProps()); + map["focusedTextColor"] = &focusedTextColor; + return map; +} + +void Button::setRect(const Rectangle &rect) { + this->label.rect = rect; + this->colorRect.rect = rect; +} + +void Button::setText(const std::string &text) { this->label.setText(text); } + +void Button::update() { + label.textColor = shownTextColor; + colorRect.update(); + label.update(); +} + +void Button::draw() { + colorRect.draw(); + label.draw(); +} diff --git a/src/colorRect.cpp b/src/colorRect.cpp index 0b691d15..891b119e 100644 --- a/src/colorRect.cpp +++ b/src/colorRect.cpp @@ -6,37 +6,50 @@ #include "gamedata.hpp" #include "nlohmann/json_fwd.hpp" +#include "uiElement.hpp" ColorRect::ColorRect() : ColorRect({1, 1, 1, 1}) {} -ColorRect::ColorRect(const Rectangle rect) { +ColorRect::ColorRect(const Rectangle rect) : UIElement(INTERFACE_COLORRECT) { this->rect = rect; this->color = RAYWHITE; + this->borderColor = BLACK; + this->borderWidth = 0; } -std::map ColorRect::getProps() { return {{"rect", &rect}, {"color", &color}}; } +std::map ColorRect::getProps() { + return {{"rect", &rect}, {"color", &color}, {"borderColor", &borderColor}, {"borderWidth", &borderWidth}}; +} void ColorRect::fromJson(const nlohmann::json &json) { this->rect = json.at("rect"); this->color = json.at("color"); + this->borderColor = json.at("borderColor"); + this->borderWidth = json.at("borderWidth"); } nlohmann::json ColorRect::dumpJson() { auto j = nlohmann::json::object(); j["rect"] = rect; j["color"] = color; + j["borderColor"] = borderColor; + j["borderWidth"] = borderWidth; return j; } void ColorRect::fromBin(UIElementBin &bin) { this->rect = std::get(bin.props["rect"]); this->color = std::get(bin.props["color"]); + this->borderColor = std::get(bin.props["borderColor"]); + this->borderWidth = std::get(bin.props["borderWidth"]); } UIElementBin ColorRect::dumpBin() { - UIElementBin bin; + UIElementBin bin = UIElement::dumpBin(); bin.props["rect"] = rect; bin.props["color"] = color; + bin.props["borderColor"] = borderColor; + bin.props["borderWidth"] = borderWidth; return bin; } @@ -46,4 +59,9 @@ void ColorRect::update() { // TODO } -void ColorRect::draw() { DrawRectangleRec(rect, color); } +void ColorRect::draw() { + DrawRectangleRec(rect, color); + if (borderWidth > 0) { + DrawRectangleLinesEx(rect, static_cast(borderWidth), borderColor); + } +} diff --git a/src/gamedata.cpp b/src/gamedata.cpp index 13fab192..52d76269 100644 --- a/src/gamedata.cpp +++ b/src/gamedata.cpp @@ -32,6 +32,11 @@ void serialize(Archive &a, Color &b) { a(b.a, b.r, b.g, b.b); } +template +void serialize(Archive &a, UIElementRef &b) { + a(b.title); +} + template void serialize(Archive &a, ActorBin &b) { a(b.name, b.tileSetName, b.collision, b.animations); diff --git a/src/imageRect.cpp b/src/imageRect.cpp index 31f7e086..d168f2f4 100644 --- a/src/imageRect.cpp +++ b/src/imageRect.cpp @@ -6,10 +6,11 @@ #include "gamedata.hpp" #include "nlohmann/json_fwd.hpp" +#include "uiElement.hpp" ImageRect::ImageRect() : rect(Rectangle{}), texture() {} -ImageRect::ImageRect(Rectangle rect) : texture() { +ImageRect::ImageRect(Rectangle rect) : UIElement(INTERFACE_IMAGERECT) { this->rect = rect; this->source = ""; this->scale = 1; @@ -58,7 +59,7 @@ void ImageRect::fromBin(UIElementBin &bin) { } UIElementBin ImageRect::dumpBin() { - UIElementBin bin; + UIElementBin bin = UIElement::dumpBin(); bin.props["rect"] = rect; bin.props["source"] = source; bin.props["scale"] = scale; diff --git a/src/interfaceElementFactory.cpp b/src/interfaceElementFactory.cpp new file mode 100644 index 00000000..0980e287 --- /dev/null +++ b/src/interfaceElementFactory.cpp @@ -0,0 +1,19 @@ +#include "interfaceElementFactory.hpp" + +#include + +#include "button.hpp" +#include "colorRect.hpp" +#include "imageRect.hpp" +#include "label.hpp" +#include "ninePatchImageRect.hpp" +#include "textArea.hpp" +#include "uiElement.hpp" + +static std::array()>, RPGPP_INTERFACE_ELEMENT_MAX> interfaceFactory = { + [] { return std::make_unique(); }, [] { return std::make_unique