diff --git a/extensions/olcPGEX_QuickGUI.h b/extensions/olcPGEX_QuickGUI.h index 66a2156e..6b0c4ef8 100644 --- a/extensions/olcPGEX_QuickGUI.h +++ b/extensions/olcPGEX_QuickGUI.h @@ -361,6 +361,32 @@ namespace olc::QuickGUI void Draw(olc::PixelGameEngine* pge) override; void DrawDecal(olc::PixelGameEngine* pge) override; }; + + + class ModalDialog : public olc::PGEX + { + public: + ModalDialog(); + + public: + void ShowFileOpen(const std::string& sPath); + + protected: + virtual bool OnBeforeUserUpdate(float& fElapsedTime) override; + + private: + bool m_bShowDialog = false; + + Manager m_manFileSelect; + ListBox* m_listVolumes = nullptr; + ListBox* m_listDirectory = nullptr; + ListBox* m_listFiles = nullptr; + + std::vector m_vVolumes; + std::vector m_vDirectory; + std::vector m_vFiles; + std::filesystem::path m_path; + }; } @@ -1050,6 +1076,95 @@ namespace olc::QuickGUI #pragma endregion + +#pragma region Modal + ModalDialog::ModalDialog() : olc::PGEX(true) + { + + // Create File Open Dialog + olc::vi2d vScreenSize = pge->GetScreenSize(); + + m_listDirectory = new ListBox(m_manFileSelect, m_vDirectory, olc::vf2d(20, 20), olc::vf2d(300, 500)); + m_listFiles = new ListBox(m_manFileSelect, m_vFiles, olc::vf2d(330, 20), olc::vf2d(300, 500)); + + m_path = "/"; + for (auto const& dir_entry : std::filesystem::directory_iterator{ m_path }) + { + if(dir_entry.is_directory()) + m_vDirectory.push_back(dir_entry.path().filename().string()); + else + m_vFiles.push_back(dir_entry.path().filename().string()); + } + } + + void ModalDialog::ShowFileOpen(const std::string& sPath) + { + m_bShowDialog = true; + } + + bool ModalDialog::OnBeforeUserUpdate(float& fElapsedTime) + { + if(!m_bShowDialog) return false; + + m_manFileSelect.Update(this->pge); + + if (pge->GetKey(olc::Key::BACK).bPressed) + { + m_path = m_path.parent_path().string() + "/"; + //m_listDirectory->bSelectionChanged = true; + //m_listDirectory->nSelectedItem = 0; + } + + if (m_listDirectory->bSelectionChanged) + { + std::string sDirectory = m_vDirectory[m_listDirectory->nSelectedItem]; + /*if (sDirectory == "..") + m_path = m_path.parent_path().string() + "/"; + else + m_path += sDirectory+ "/";*/ + + + m_path += sDirectory + "/"; + // Reconstruct Lists + m_vDirectory.clear(); + + m_vFiles.clear(); + + + for (auto const& dir_entry : std::filesystem::directory_iterator{ m_path }) + { + if (dir_entry.is_directory() || dir_entry.is_other()) + m_vDirectory.push_back(dir_entry.path().filename().string()); + else + m_vFiles.push_back(dir_entry.path().filename().string()); + } + + //m_vDirectory.push_back(".."); + + //m_listFiles->nSelectedItem = 0; + //m_listDirectory->nSelectedItem = 0; + + } + + pge->DrawStringDecal({ 0,0 }, m_path.string()); + + + + + m_manFileSelect.DrawDecal(this->pge); + + + + if (pge->GetKey(olc::Key::ESCAPE).bPressed) + { + m_bShowDialog = false; + return false; + } + + return true; + } +#pragma endregion + } #endif // OLC_PGEX_QUICKGUI #endif // OLC_PGEX_QUICKGUI_H \ No newline at end of file diff --git a/olcPixelGameEngine.h b/olcPixelGameEngine.h index 4ab5be97..5480a153 100644 --- a/olcPixelGameEngine.h +++ b/olcPixelGameEngine.h @@ -440,8 +440,16 @@ int main() // | PLATFORM SELECTION CODE, Thanks slavka! | // O------------------------------------------------------------------------------O +#if defined(OLC_PGE_HEADLESS) + #define OLC_PLATFORM_HEADLESS + #define OLC_GFX_HEADLESS + #if !defined(OLC_IMAGE_STB) && !defined(OLC_IMAGE_GDI) && !defined(OLC_IMAGE_LIBPNG) + #define OLC_IMAGE_HEADLESS + #endif +#endif + // Platform -#if !defined(OLC_PLATFORM_WINAPI) && !defined(OLC_PLATFORM_X11) && !defined(OLC_PLATFORM_GLUT) && !defined(OLC_PLATFORM_EMSCRIPTEN) +#if !defined(OLC_PLATFORM_WINAPI) && !defined(OLC_PLATFORM_X11) && !defined(OLC_PLATFORM_GLUT) && !defined(OLC_PLATFORM_EMSCRIPTEN) && !defined(OLC_PLATFORM_HEADLESS) #if !defined(OLC_PLATFORM_CUSTOM_EX) #if defined(_WIN32) #define OLC_PLATFORM_WINAPI @@ -464,8 +472,10 @@ int main() #define PGE_USE_CUSTOM_START #endif + + // Renderer -#if !defined(OLC_GFX_OPENGL10) && !defined(OLC_GFX_OPENGL33) && !defined(OLC_GFX_DIRECTX10) +#if !defined(OLC_GFX_OPENGL10) && !defined(OLC_GFX_OPENGL33) && !defined(OLC_GFX_DIRECTX10) && !defined(OLC_GFX_HEADLESS) #if !defined(OLC_GFX_CUSTOM_EX) #if defined(OLC_PLATFORM_EMSCRIPTEN) #define OLC_GFX_OPENGL33 @@ -476,7 +486,7 @@ int main() #endif // Image loader -#if !defined(OLC_IMAGE_STB) && !defined(OLC_IMAGE_GDI) && !defined(OLC_IMAGE_LIBPNG) +#if !defined(OLC_IMAGE_STB) && !defined(OLC_IMAGE_GDI) && !defined(OLC_IMAGE_LIBPNG) && !defined(OLC_IMAGE_HEADLESS) #if !defined(OLC_IMAGE_CUSTOM_EX) #if defined(_WIN32) #define OLC_IMAGE_GDI @@ -534,6 +544,15 @@ int main() #endif #endif #endif + +#if defined(OLC_PGE_HEADLESS) +#if defined max +#undef max +#endif +#if defined min +#undef min +#endif +#endif #pragma endregion // O------------------------------------------------------------------------------O @@ -664,7 +683,7 @@ namespace olc v2d_generic cart() { return { std::cos(y) * x, std::sin(y) * x }; } v2d_generic polar() { return { mag(), std::atan2(y, x) }; } v2d_generic clamp(const v2d_generic& v1, const v2d_generic& v2) const { return this->max(v1)->min(v2); } - v2d_generic lerp(const v2d_generic& v1, const double t) { return this->operator*(T(1) - t) + (v1 * t); } + v2d_generic lerp(const v2d_generic& v1, const double t) { return this->operator*(T(1.0 - t)) + (v1 * T(t)); } T dot(const v2d_generic& rhs) const { return this->x * rhs.x + this->y * rhs.y; } T cross(const v2d_generic& rhs) const { return this->x * rhs.y - this->y * rhs.x; } v2d_generic operator + (const v2d_generic& rhs) const { return v2d_generic(this->x + rhs.x, this->y + rhs.y); } @@ -4028,10 +4047,118 @@ namespace olc }; #pragma endregion + +#pragma region platform_headless +namespace olc +{ +#if defined(OLC_GFX_HEADLESS) + class Renderer_Headless : public olc::Renderer + { + public: + virtual void PrepareDevice() {}; + virtual olc::rcode CreateDevice(std::vector params, bool bFullScreen, bool bVSYNC) { return olc::rcode::OK; } + virtual olc::rcode DestroyDevice() { return olc::rcode::OK; } + virtual void DisplayFrame() {} + virtual void PrepareDrawing() {} + virtual void SetDecalMode(const olc::DecalMode& mode) {} + virtual void DrawLayerQuad(const olc::vf2d& offset, const olc::vf2d& scale, const olc::Pixel tint) {} + virtual void DrawDecal(const olc::DecalInstance& decal) {} + virtual uint32_t CreateTexture(const uint32_t width, const uint32_t height, const bool filtered = false, const bool clamp = true) {return 1;}; + virtual void UpdateTexture(uint32_t id, olc::Sprite* spr) {} + virtual void ReadTexture(uint32_t id, olc::Sprite* spr) {} + virtual uint32_t DeleteTexture(const uint32_t id) {return 1;} + virtual void ApplyTexture(uint32_t id) {} + virtual void UpdateViewport(const olc::vi2d& pos, const olc::vi2d& size) {} + virtual void ClearBuffer(olc::Pixel p, bool bDepth) {} + }; +#endif +#if defined(OLC_PLATFORM_HEADLESS) + class Platform_Headless : public olc::Platform + { + public: + virtual olc::rcode ApplicationStartUp() { return olc::rcode::OK; } + virtual olc::rcode ApplicationCleanUp() { return olc::rcode::OK; } + virtual olc::rcode ThreadStartUp() { return olc::rcode::OK; } + virtual olc::rcode ThreadCleanUp() { return olc::rcode::OK; } + virtual olc::rcode CreateGraphics(bool bFullScreen, bool bEnableVSYNC, const olc::vi2d& vViewPos, const olc::vi2d& vViewSize) { return olc::rcode::OK; } + virtual olc::rcode CreateWindowPane(const olc::vi2d& vWindowPos, olc::vi2d& vWindowSize, bool bFullScreen) { return olc::rcode::OK; } + virtual olc::rcode SetWindowTitle(const std::string& s) { return olc::rcode::OK; } + virtual olc::rcode StartSystemEventLoop() { return olc::rcode::OK; } + virtual olc::rcode HandleSystemEvent() { return olc::rcode::OK; } + }; +#endif +} +#pragma endregion + // O------------------------------------------------------------------------------O // | olcPixelGameEngine Renderers - the draw-y bits | // O------------------------------------------------------------------------------O +#pragma region image_stb +// O------------------------------------------------------------------------------O +// | START IMAGE LOADER: stb_image.h, all systems, very fast | +// O------------------------------------------------------------------------------O +// Thanks to Sean Barrett - https://github.com/nothings/stb/blob/master/stb_image.h +// MIT License - Copyright(c) 2017 Sean Barrett + +// Note you need to download the above file into your project folder, and +// #define OLC_IMAGE_STB +// #define OLC_PGE_APPLICATION +// #include "olcPixelGameEngine.h" + +#if defined(OLC_IMAGE_STB) +#define STB_IMAGE_IMPLEMENTATION +#include "stb_image.h" +namespace olc +{ + class ImageLoader_STB : public olc::ImageLoader + { + public: + ImageLoader_STB() : ImageLoader() + {} + + olc::rcode LoadImageResource(olc::Sprite* spr, const std::string& sImageFile, olc::ResourcePack* pack) override + { + UNUSED(pack); + // clear out existing sprite + spr->pColData.clear(); + // Open file + stbi_uc* bytes = nullptr; + int w = 0, h = 0, cmp = 0; + if (pack != nullptr) + { + ResourceBuffer rb = pack->GetFileBuffer(sImageFile); + bytes = stbi_load_from_memory((unsigned char*)rb.vMemory.data(), rb.vMemory.size(), &w, &h, &cmp, 4); + } + else + { + // Check file exists + if (!_gfs::exists(sImageFile)) return olc::rcode::NO_FILE; + bytes = stbi_load(sImageFile.c_str(), &w, &h, &cmp, 4); + } + + if (!bytes) return olc::rcode::FAIL; + spr->width = w; spr->height = h; + spr->pColData.resize(spr->width * spr->height); + std::memcpy(spr->pColData.data(), bytes, spr->width * spr->height * 4); + delete[] bytes; + return olc::rcode::OK; + } + + olc::rcode SaveImageResource(olc::Sprite* spr, const std::string& sImageFile) override + { + return olc::rcode::OK; + } + }; +} +#endif +// O------------------------------------------------------------------------------O +// | START IMAGE LOADER: stb_image.h | +// O------------------------------------------------------------------------------O +#pragma endregion + + + #if !defined(OLC_PGE_HEADLESS) #pragma region renderer_ogl10 @@ -5214,68 +5341,6 @@ namespace olc // O------------------------------------------------------------------------------O #pragma endregion -#pragma region image_stb -// O------------------------------------------------------------------------------O -// | START IMAGE LOADER: stb_image.h, all systems, very fast | -// O------------------------------------------------------------------------------O -// Thanks to Sean Barrett - https://github.com/nothings/stb/blob/master/stb_image.h -// MIT License - Copyright(c) 2017 Sean Barrett - -// Note you need to download the above file into your project folder, and -// #define OLC_IMAGE_STB -// #define OLC_PGE_APPLICATION -// #include "olcPixelGameEngine.h" - -#if defined(OLC_IMAGE_STB) -#define STB_IMAGE_IMPLEMENTATION -#include "stb_image.h" -namespace olc -{ - class ImageLoader_STB : public olc::ImageLoader - { - public: - ImageLoader_STB() : ImageLoader() - {} - - olc::rcode LoadImageResource(olc::Sprite* spr, const std::string& sImageFile, olc::ResourcePack* pack) override - { - UNUSED(pack); - // clear out existing sprite - spr->pColData.clear(); - // Open file - stbi_uc* bytes = nullptr; - int w = 0, h = 0, cmp = 0; - if (pack != nullptr) - { - ResourceBuffer rb = pack->GetFileBuffer(sImageFile); - bytes = stbi_load_from_memory((unsigned char*)rb.vMemory.data(), rb.vMemory.size(), &w, &h, &cmp, 4); - } - else - { - // Check file exists - if (!_gfs::exists(sImageFile)) return olc::rcode::NO_FILE; - bytes = stbi_load(sImageFile.c_str(), &w, &h, &cmp, 4); - } - - if (!bytes) return olc::rcode::FAIL; - spr->width = w; spr->height = h; - spr->pColData.resize(spr->width * spr->height); - std::memcpy(spr->pColData.data(), bytes, spr->width * spr->height * 4); - delete[] bytes; - return olc::rcode::OK; - } - - olc::rcode SaveImageResource(olc::Sprite* spr, const std::string& sImageFile) override - { - return olc::rcode::OK; - } - }; -} -#endif -// O------------------------------------------------------------------------------O -// | START IMAGE LOADER: stb_image.h | -// O------------------------------------------------------------------------------O -#pragma endregion // O------------------------------------------------------------------------------O // | olcPixelGameEngine Platforms | @@ -6091,6 +6156,9 @@ namespace olc { #pragma endregion + + + #pragma region platform_emscripten // O------------------------------------------------------------------------------O // | START PLATFORM: Emscripten - Totally Game Changing... | @@ -6530,7 +6598,9 @@ namespace olc void PixelGameEngine::olc_ConfigureSystem() { -#if !defined(OLC_PGE_HEADLESS) +//#if !defined(OLC_PGE_HEADLESS) + + olc::Sprite::loader = nullptr; #if defined(OLC_IMAGE_GDI) olc::Sprite::loader = std::make_unique(); @@ -6549,7 +6619,9 @@ namespace olc #endif - +#if defined(OLC_PLATFORM_HEADLESS) + platform = std::make_unique(); +#endif #if defined(OLC_PLATFORM_WINAPI) platform = std::make_unique(); @@ -6571,7 +6643,9 @@ namespace olc platform = std::make_unique(); #endif - +#if defined(OLC_GFX_HEADLESS) + renderer = std::make_unique(); +#endif #if defined(OLC_GFX_OPENGL10) renderer = std::make_unique(); @@ -6600,11 +6674,11 @@ namespace olc // Associate components with PGE instance platform->ptrPGE = this; renderer->ptrPGE = this; -#else - olc::Sprite::loader = nullptr; - platform = nullptr; - renderer = nullptr; -#endif +//#else +// olc::Sprite::loader = nullptr; +// platform = nullptr; +// renderer = nullptr; +//#endif } } diff --git a/utilities/olcUTIL_Container.h b/utilities/olcUTIL_Container.h new file mode 100644 index 00000000..3e866933 --- /dev/null +++ b/utilities/olcUTIL_Container.h @@ -0,0 +1,158 @@ +/* + OneLoneCoder - Container v1.00 + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Assortment of std::container like objects with access specific mechanisms + + + License (OLC-3) + ~~~~~~~~~~~~~~~ + + Copyright 2018 - 2022 OneLoneCoder.com + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions or derivations of source code must retain the above + copyright notice, this list of conditions and the following disclaimer. + + 2. Redistributions or derivative works in binary form must reproduce + the above copyright notice. This list of conditions and the following + disclaimer must be reproduced in the documentation and/or other + materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Links + ~~~~~ + YouTube: https://www.youtube.com/javidx9 + Discord: https://discord.gg/WhwHUMV + Twitter: https://www.twitter.com/javidx9 + Twitch: https://www.twitch.tv/javidx9 + GitHub: https://www.github.com/onelonecoder + Homepage: https://www.onelonecoder.com + + Author + ~~~~~~ + David Barr, aka javidx9, ŠOneLoneCoder 2019, 2020, 2021, 2022 + +*/ + +/* + SingleSelection + ~~~~~~~~~~~~~~~ + This container behaves like a std::vector in all circumstances but features + additional methods that allow it to surve as a list of items, one of which + can be selected, and the items can be manipulated. + + An example of this would be the image layers in Photoshop. + + For convenience, all container operations operate on an item index rather + than an iterator +*/ + +#pragma once + +#include +#include + +namespace olc::utils::Container +{ + template + class SingleSelection : public std::vector + { + public: + + SingleSelection() : std::vector() + {} + + + SingleSelection(std::initializer_list list) : std::vector(list) + {} + + // Returns selected item in container + size_t selection() const + { + return m_nSelectedItem; + } + + // Return the item actually selected + const T& selected() const + { + return this->operator[](m_nSelectedItem); + } + + // Return the item actually selected + T& selected() + { + return this->operator[](m_nSelectedItem); + } + + // Select item in container + void select(const size_t item) + { + m_nSelectedItem = std::clamp(item, size_t(0), this->size() - size_t(1)); + } + + // Move selected item positively + void move_up() + { + if(move_up(m_nSelectedItem)) + m_nSelectedItem++; + } + + // Move selected item negatively + void move_down() + { + if(move_down(m_nSelectedItem)) + m_nSelectedItem--; + } + + // Move specified item negatively + bool move_down(const size_t item) + { + // Are there at least two items and not first one selected? + if (this->size() >= 2 && item > 0) + { + std::swap(this->operator[](item - 1), this->operator[](item)); + return true; + } + return false; + } + + // Move specified item positively + bool move_up(const size_t item) + { + // Are there at least two items and not last one selected? + if (this->size() >= 2 && item < this->size() - 1) + { + std::swap(this->operator[](item + 1), this->operator[](item)); + return true; + } + return false; + } + + void insert_after(const size_t idx, const T& value) + { + this->insert(idx, value); + } + + + protected: + size_t m_nSelectedItem = 0; + }; +} \ No newline at end of file diff --git a/utilities/olcUTIL_DataFile.h b/utilities/olcUTIL_DataFile.h new file mode 100644 index 00000000..ebd6e8ff --- /dev/null +++ b/utilities/olcUTIL_DataFile.h @@ -0,0 +1,435 @@ +/* + OneLoneCoder - DataFile v1.00 + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + An "easy to use" serialisation/deserialisation class that yields + human readable hierachical files. + + License (OLC-3) + ~~~~~~~~~~~~~~~ + + Copyright 2018 - 2022 OneLoneCoder.com + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions or derivations of source code must retain the above + copyright notice, this list of conditions and the following disclaimer. + + 2. Redistributions or derivative works in binary form must reproduce + the above copyright notice. This list of conditions and the following + disclaimer must be reproduced in the documentation and/or other + materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Links + ~~~~~ + YouTube: https://www.youtube.com/javidx9 + Discord: https://discord.gg/WhwHUMV + Twitter: https://www.twitter.com/javidx9 + Twitch: https://www.twitch.tv/javidx9 + GitHub: https://www.github.com/onelonecoder + Homepage: https://www.onelonecoder.com + + Author + ~~~~~~ + David Barr, aka javidx9, ŠOneLoneCoder 2019, 2020, 2021, 2022 + +*/ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace olc::utils +{ + class datafile + { + public: + inline datafile() = default; + + public: + // Sets the String Value of a Property (for a given index) + inline void SetString(const std::string& sString, const size_t nItem = 0) + { + if (nItem >= m_vContent.size()) + m_vContent.resize(nItem + 1); + + m_vContent[nItem] = sString; + } + + // Retrieves the String Value of a Property (for a given index) or "" + inline const std::string GetString(const size_t nItem = 0) const + { + if (nItem >= m_vContent.size()) + return ""; + else + return m_vContent[nItem]; + } + + // Retrieves the Real Value of a Property (for a given index) or 0.0 + inline const double GetReal(const size_t nItem = 0) const + { + return std::atof(GetString(nItem).c_str()); + } + + // Sets the Real Value of a Property (for a given index) + inline void SetReal(const double d, const size_t nItem = 0) + { + SetString(std::to_string(d), nItem); + } + + // Retrieves the Integer Value of a Property (for a given index) or 0 + inline const int32_t GetInt(const size_t nItem = 0) const + { + return std::atoi(GetString(nItem).c_str()); + } + + // Sets the Integer Value of a Property (for a given index) + inline void SetInt(const int32_t n, const size_t nItem = 0) + { + SetString(std::to_string(n), nItem); + } + + // Returns the number of Values a property consists of + inline size_t GetValueCount() const + { + return m_vContent.size(); + } + + // Checks if a property exists - useful to avoid creating properties + // via reading them, though non-essential + inline bool HasProperty(const std::string& sName) const + { + return m_mapObjects.count(sName) > 0; + } + + // Access a datafile via a convenient name - "root.node.something.property" + inline datafile& GetProperty(const std::string& name) + { + size_t x = name.find_first_of('.'); + if (x != std::string::npos) + { + std::string sProperty = name.substr(0, x); + if (HasProperty(sProperty)) + return operator[](sProperty).GetProperty(name.substr(x + 1, name.size())); + else + return operator[](sProperty); + } + else + { + return operator[](name); + } + } + + // Access a numbered element - "node[23]", or "root[56].node" + inline datafile& GetIndexedProperty(const std::string& name, const size_t nIndex) + { + return GetProperty(name + "[" + std::to_string(nIndex) + "]"); + } + + public: + // Writes a "datafile" node (and all of its child nodes and properties) recursively + // to a file. + inline static bool Write(const datafile& n, const std::string& sFileName, const std::string& sIndent = "\t", const char sListSep = ',') + { + // Cache indentation level + size_t nIndentCount = 0; + // Cache sperator string for convenience + std::string sSeperator = std::string(1, sListSep) + " "; + + // Fully specified lambda, because this lambda is recursive! + std::function write = [&](const datafile& n, std::ofstream& file) + { + // Lambda creates string given indentation preferences + auto indent = [&](const std::string& sString, const size_t nCount) + { + std::string sOut; + for (size_t n = 0; n < nCount; n++) sOut += sString; + return sOut; + }; + + // Iterate through each property of this node + for (auto const& property : n.m_vecObjects) + { + // Does property contain any sub objects? + if (property.second.m_vecObjects.empty()) + { + // No, so it's an assigned field and should just be written. If the property + // is flagged as comment, it has no assignment potential. First write the + // property name + file << indent(sIndent, nIndentCount) << property.first << (property.second.m_bIsComment ? "" : " = "); + + // Second, write the property value (or values, seperated by provided + // separation charater + size_t nItems = property.second.GetValueCount(); + for (size_t i = 0; i < property.second.GetValueCount(); i++) + { + // If the Value being written, in string form, contains the separation + // character, then the value must be written inside quotation marks. Note, + // that if the Value is the last of a list of Values for a property, it is + // not suffixed with the separator + size_t x = property.second.GetString(i).find_first_of(sListSep); + if (x != std::string::npos) + { + // Value contains separator, so wrap in quotes + file << "\"" << property.second.GetString(i) << "\"" << ((nItems > 1) ? sSeperator : ""); + } + else + { + // Value does not contain separator, so just write out + file << property.second.GetString(i) << ((nItems > 1) ? sSeperator : ""); + } + nItems--; + } + + // Property written, move to next line + file << "\n"; + } + else + { + // Yes, property has properties of its own, so it's a node + // Force a new line and write out the node's name + file << "\n" << indent(sIndent, nIndentCount) << property.first << "\n"; + // Open braces, and update indentation + file << indent(sIndent, nIndentCount) << "{\n"; + nIndentCount++; + // Recursively write that node + write(property.second, file); + // Node written, so close braces + file << indent(sIndent, nIndentCount) << "}\n\n"; + } + } + + // We've finished writing out a node, regardless of state, our indentation + // must decrease, unless we're top level + if (nIndentCount > 0) nIndentCount--; + }; + + // Start Here! Open the file for writing + std::ofstream file(sFileName); + if (file.is_open()) + { + // Write the file starting form the supplied node + write(n, file); + return true; + } + return false; + } + + inline static bool Read(datafile& n, const std::string& sFileName, const char sListSep = ',') + { + // Open the file! + std::ifstream file(sFileName); + if (file.is_open()) + { + // These variables are outside of the read loop, as we will + // need to refer to previous iteration values in certain conditions + std::string sPropName = ""; + std::string sPropValue = ""; + + // The file is fundamentally structured as a stack, so we will read it + // in a such, but note the data structure in memory is not explicitly + // stored in a stack, but one is constructed implicitly via the nodes + // owning other nodes (aka a tree) + + // I dont want to accidentally create copies all over the place, nor do + // I want to use pointer syntax, so being a bit different and stupidly + // using std::reference_wrapper, so I can store references to datafile + // nodes in a std::container. + std::stack> stkPath; + stkPath.push(n); + + + // Read file line by line and process + while (!file.eof()) + { + // Read line + std::string line; + std::getline(file, line); + + // This little lambda removes whitespace from + // beginning and end of supplied string + auto trim = [](std::string& s) + { + s.erase(0, s.find_first_not_of(" \t\n\r\f\v")); + s.erase(s.find_last_not_of(" \t\n\r\f\v") + 1); + }; + + trim(line); + + // If line has content + if (!line.empty()) + { + // Test if its a comment... + if (line[0] == '#') + { + // ...it is a comment, so ignore + datafile comment; + comment.m_bIsComment = true; + stkPath.top().get().m_vecObjects.push_back({ line, comment }); + } + else + { + // ...it is content, so parse. Firstly, find if the line + // contains an assignment. If it does then it's a property... + size_t x = line.find_first_of('='); + if (x != std::string::npos) + { + // ...so split up the property into a name, and its values! + + // Extract the property name, which is all characters up to + // first assignment, trim any whitespace from ends + sPropName = line.substr(0, x); + trim(sPropName); + + // Extract the property value, which is all characters after + // the first assignment operator, trim any whitespace from ends + sPropValue = line.substr(x + 1, line.size()); + trim(sPropValue); + + // The value may be in list form: a, b, c, d, e, f etc and some of those + // elements may exist in quotes a, b, c, "d, e", f. So we need to iterate + // character by character and break up the value + bool bInQuotes = false; + std::string sToken; + size_t nTokenCount = 0; + for (const auto c : sPropValue) + { + // Is character a quote... + if (c == '\"') + { + // ...yes, so toggle quote state + bInQuotes = !bInQuotes; + } + else + { + // ...no, so proceed creating token. If we are in quote state + // then just append characters until we exit quote state. + if (bInQuotes) + { + sToken.append(1, c); + } + else + { + // Is the character our seperator? If it is + if (c == sListSep) + { + // Clean up the token + trim(sToken); + // Add it to the vector of values for this property + stkPath.top().get()[sPropName].SetString(sToken, nTokenCount); + // Reset our token state + sToken.clear(); + nTokenCount++; + } + else + { + // It isnt, so just append to token + sToken.append(1, c); + } + } + } + } + + // Any residual characters at this point just make up the final token, + // so clean it up and add it to the vector of values + if (!sToken.empty()) + { + trim(sToken); + stkPath.top().get()[sPropName].SetString(sToken, nTokenCount); + } + } + else + { + // ...but if it doesnt, then it's something structural + if (line[0] == '{') + { + // Open brace, so push this node to stack, subsequent properties + // will belong to the new node + stkPath.push(stkPath.top().get()[sPropName]); + } + else + { + if (line[0] == '}') + { + // Close brace, so this node has been defined, pop it from the + // stack + stkPath.pop(); + } + else + { + // Line is a property with no assignment. Who knows whether this is useful, + // but we can simply add it as a valueless property... + sPropName = line; + // ...actually it is useful, as valuless properties are typically + // going to be the names of new datafile nodes on the next iteration + } + } + } + } + } + } + + // Close and exit! + file.close(); + return true; + } + + // File not found, so fail + return false; + } + + public: + inline datafile& operator[](const std::string& name) + { + // Check if this "node"'s map already contains an object with this name... + if (m_mapObjects.count(name) == 0) + { + // ...it did not! So create this object in the map. First get a vector id + // and link it with the name in the unordered_map + m_mapObjects[name] = m_vecObjects.size(); + // then creating the new, blank object in the vector of objects + m_vecObjects.push_back({ name, datafile() }); + } + + // ...it exists! so return the object, by getting its index from the map, and using that + // index to look up a vector element. + return m_vecObjects[m_mapObjects[name]].second; + } + + private: + // The "list of strings" that make up a property value + std::vector m_vContent; + + // Linkage to create "ordered" unordered_map. We have a vector of + // "properties", and the index to a specific element is mapped. + std::vector> m_vecObjects; + std::unordered_map m_mapObjects; + + protected: + // Used to identify if a property is a comment or not, not user facing + bool m_bIsComment = false; + }; +}