diff --git a/.github/workflows/appimage-wayland.yml b/.github/workflows/appimage-wayland.yml new file mode 100644 index 00000000..f59902f7 --- /dev/null +++ b/.github/workflows/appimage-wayland.yml @@ -0,0 +1,42 @@ +name: C/C++ AppImage (wayland) + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +jobs: + build-appimage: + + strategy: + matrix: + include: + - os: ubuntu-latest + + runs-on: ${{ matrix.os }} + + steps: + - uses: actions/checkout@v2 + with: + submodules: 'true' + - name: install dependencies + run: | + sudo apt-get update + sudo apt-get install -y build-essential libglfw3-wayland libglfw3-dev libglew-dev libglm-dev libpng-dev libopenal-dev libluajit-5.1-dev cmake squashfs-tools + sudo ln -s /usr/lib/x86_64-linux-gnu/libluajit-5.1.a /usr/lib/x86_64-linux-gnu/liblua5.1.a + sudo ln -s /usr/include/luajit-2.1 /usr/include/lua + - name: configure + run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DVOXELENGINE_BUILD_APPDIR=1 + - name: build + run: cmake --build build -t install + - name: Build AppImage + uses: AppImageCrafters/build-appimage-action@fe2205a4d6056be47051f7b1b3811106e9814910 + env: + UPDATE_INFO: gh-releases-zsync|MihailRis|VoxelEngine-Cpp|latest|*x86_64.AppImage.zsync + with: + recipe: dev/AppImageBuilder.yml + - uses: actions/upload-artifact@v2 + with: + name: AppImage + path: './*.AppImage*' diff --git a/.github/workflows/appimage.yml b/.github/workflows/appimage.yml index 62f20563..f799077e 100644 --- a/.github/workflows/appimage.yml +++ b/.github/workflows/appimage.yml @@ -9,7 +9,12 @@ on: jobs: build-appimage: - runs-on: ubuntu-20.04 + strategy: + matrix: + include: + - os: ubuntu-latest + + runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v2 diff --git a/CMakeLists.txt b/CMakeLists.txt index 7ec5691b..b772fb72 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -45,7 +45,7 @@ if(MSVC) endif() set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /source-charset:UTF-8") else() - target_compile_options(${PROJECT_NAME} PRIVATE -Wall -Wextra -lstdc++fs + target_compile_options(${PROJECT_NAME} PRIVATE -Wall -Wextra # additional warnings -Wformat-nonliteral -Wcast-align -Wpointer-arith -Wundef @@ -114,7 +114,7 @@ if(UNIX) endif() if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -no-pie") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -no-pie -lstdc++fs") endif() include_directories(${LUA_INCLUDE_DIR}) diff --git a/res/content/base/package.json b/res/content/base/package.json index bd8c1c2a..249dd4ee 100644 --- a/res/content/base/package.json +++ b/res/content/base/package.json @@ -1,6 +1,6 @@ { "id": "base", "title": "Base", - "version": "0.18", + "version": "0.19", "description": "basic content package" } diff --git a/res/content/base/textures/blocks/dirt.png b/res/content/base/textures/blocks/dirt.png index 6239e67c..9fc51ad8 100644 Binary files a/res/content/base/textures/blocks/dirt.png and b/res/content/base/textures/blocks/dirt.png differ diff --git a/res/content/base/textures/blocks/flower.png b/res/content/base/textures/blocks/flower.png index 38f070eb..a69626de 100644 Binary files a/res/content/base/textures/blocks/flower.png and b/res/content/base/textures/blocks/flower.png differ diff --git a/res/content/base/textures/blocks/grass_side.png b/res/content/base/textures/blocks/grass_side.png index 802c1a87..553bd8b4 100644 Binary files a/res/content/base/textures/blocks/grass_side.png and b/res/content/base/textures/blocks/grass_side.png differ diff --git a/res/content/base/textures/blocks/leaves.png b/res/content/base/textures/blocks/leaves.png index 861fbcb7..3beaf7ab 100644 Binary files a/res/content/base/textures/blocks/leaves.png and b/res/content/base/textures/blocks/leaves.png differ diff --git a/res/content/base/textures/blocks/stone.png b/res/content/base/textures/blocks/stone.png index 5214e3e1..53620aa1 100644 Binary files a/res/content/base/textures/blocks/stone.png and b/res/content/base/textures/blocks/stone.png differ diff --git a/res/layouts/inventory.xml b/res/layouts/inventory.xml new file mode 100644 index 00000000..6d2fc761 --- /dev/null +++ b/res/layouts/inventory.xml @@ -0,0 +1,4 @@ + + + + diff --git a/res/layouts/inventory.xml.lua b/res/layouts/inventory.xml.lua new file mode 100644 index 00000000..bc3fc5d2 --- /dev/null +++ b/res/layouts/inventory.xml.lua @@ -0,0 +1,3 @@ +function inventory_share_func(invid, slotid) + inventory.set(invid, slotid, 0, 0) +end diff --git a/res/modules/document.lua b/res/modules/document.lua new file mode 100644 index 00000000..e69de29b diff --git a/res/modules/toml.lua b/res/modules/toml.lua new file mode 100644 index 00000000..fb082e75 --- /dev/null +++ b/res/modules/toml.lua @@ -0,0 +1,65 @@ +-- TOML serialization module +local toml = {} + +-- Convert table to TOML +function toml.serialize(tb, isinner) + local text = "" + for k, v in pairs(tb) do + local tp = type(v) + if tp ~= "table" then + text = text..k.." = " + if tp == "string" then + text = text..string.format("%q", v) + else + text = text..tostring(v) + end + text = text.."\n" + end + end + for k, v in pairs(tb) do + local tp = type(v) + if tp == "table" then + if isinner then + error("only one level of subtables supported") + end + text = text.."["..k.."]\n"..toml.serialize(v).."\n" + end + end + return text +end + +-- Parse TOML to new table +function toml.deserialize(s) + local output = {} + local current = output + local lines = {} + for line in string.gmatch(s, "[^\r\n]+") do + line = string.gsub(line, "%s+", "") + table.insert(lines, line) + end + for i = 1,#lines do + local s = lines[i] + if string.sub(s, 1, 1) == "[" then + local section = s.sub(s, 2, #s-1) + current = {} + output[section] = current + else + for k, v in string.gmatch(s, "(%w+)=(.+)" ) do + v = string.gsub(v, "%s+", "") + if v.sub(v, 1, 1) == "\"" then + current[k] = v.sub(v, 2, #v-1) + elseif v == "true" or v == "false" then + current[k] = v == "true" + end + + local num = tonumber(v) + if num ~= nil then + current[k] = num + end + end + end + end + return output +end + +return toml diff --git a/res/scripts/stdlib.lua b/res/scripts/stdlib.lua index ace88536..370d4eef 100644 --- a/res/scripts/stdlib.lua +++ b/res/scripts/stdlib.lua @@ -37,10 +37,14 @@ function load_script(path, nocache) if not nocache and __cached_scripts[fullpath] ~= nil then return __cached_results[fullpath] end - local script = loadfile(fullpath) - if script == nil then + if not file.isfile(path) then error("script '"..filename.."' not found in '"..packname.."'") end + + local script, err = loadfile(fullpath) + if script == nil then + error(err) + end local result = script() if not nocache then __cached_scripts[fullpath] = script @@ -49,6 +53,16 @@ function load_script(path, nocache) return result end +function require(path) + local prefix, file = parse_path(path) + return load_script(prefix..":modules/"..file..".lua") +end + +function __reset_scripts_cache() + __cached_scripts = {} + __cached_results = {} +end + function sleep(timesec) local start = time.uptime() while time.uptime() - start < timesec do @@ -73,3 +87,50 @@ function dofile(path) end return _dofile(path) end + +function pack.is_installed(packid) + return file.isfile(packid..":package.json") +end + +vec2_mt = {} +function vec2_mt.__tostring(self) + return "vec2("..self[1]..", "..self[2]..")" +end + +vec3_mt = {} +function vec3_mt.__tostring(self) + return "vec3("..self[1]..", "..self[2]..", "..self[3]..")" +end + +vec4_mt = {} +function vec4_mt.__tostring(self) + return "vec4("..self[1]..", "..self[2]..", "..self[3]..", "..self[4]..")" +end + +color_mt = {} +function color_mt.__tostring(self) + return "rgba("..self[1]..", "..self[2]..", "..self[3]..", "..self[4]..")" +end + +-- class designed for simple UI-nodes access via properties syntax +local Element = {} +function Element.new(docname, name) + return setmetatable({docname=docname, name=name}, { + __index=function(self, k) + return gui.getattr(self.docname, self.name, k) + end, + __newindex=function(self, k, v) + gui.setattr(self.docname, self.name, k, v) + end + }) +end + +-- the engine automatically creates an instance for every ui document (layout) +Document = {} +function Document.new(docname) + return setmetatable({name=docname}, { + __index=function(self, k) + return Element.new(self.name, k) + end + }) +end diff --git a/res/scripts/world.lua b/res/scripts/world.lua index e69de29b..964100e7 100644 --- a/res/scripts/world.lua +++ b/res/scripts/world.lua @@ -0,0 +1,3 @@ +-- use for engine development tests +-- must be empty in release +-- must not be modified by content-packs diff --git a/res/shaders/main.glslv b/res/shaders/main.glslv index f368328d..e59839a0 100644 --- a/res/shaders/main.glslv +++ b/res/shaders/main.glslv @@ -26,6 +26,7 @@ uniform float u_torchlightDistance; void main(){ vec3 pos3d = (u_model * vec4(v_position, 1.0)).xyz-u_cameraPos.xyz; vec4 modelpos = u_model * vec4(v_position, 1.0); + modelpos.y -= pow(length(pos3d.xz)*0.002, 3.0); vec4 viewmodelpos = u_view * modelpos; vec4 decomp_light = decompress_light(v_light); vec3 light = decomp_light.rgb; diff --git a/res/texts/be_BY.txt b/res/texts/be_BY.txt index 5ca3dcc1..0e574fd4 100644 --- a/res/texts/be_BY.txt +++ b/res/texts/be_BY.txt @@ -34,6 +34,7 @@ settings.Load Speed=Хуткасць Загрузкі settings.Fog Curve=Крывая Туману settings.Backlight=Падсветка settings.V-Sync=Вертыкальная Сінхранізацыя +settings.Camera Shaking=Труска Камеры settings.FOV=Поле Зроку settings.Mouse Sensitivity=Адчувальнасць Мышы diff --git a/res/texts/fi_FI.txt b/res/texts/fi_FI.txt index 4da41a1c..124a2956 100644 --- a/res/texts/fi_FI.txt +++ b/res/texts/fi_FI.txt @@ -13,8 +13,8 @@ menu.New World = Uusi Maailma menu.Quit=Poistu menu.Continue=Jatka menu.Save and Quit to Menu=Tallenna ja poistu valikkoon -menu.missing-content=Puuttuu jotkut lisäosat! -menu.Content Error=Sisältövirhe! +menu.missing-content=Puuttuu lisäosia! +menu.Content Error=Lisäosa virhe! menu.Controls=Ohjaus menu.Back to Main Menu=Takaisin Valikoon menu.Settings=Asetukset diff --git a/res/texts/ru_RU.txt b/res/texts/ru_RU.txt index 65085d46..71fabbb7 100644 --- a/res/texts/ru_RU.txt +++ b/res/texts/ru_RU.txt @@ -34,6 +34,7 @@ settings.Load Speed=Скорость Загрузки settings.Fog Curve=Кривая Тумана settings.Backlight=Подсветка settings.V-Sync=Вертикальная Синхронизация +settings.Camera Shaking=Тряска Камеры settings.FOV=Поле Зрения settings.Mouse Sensitivity=Чувствительность Мыши diff --git a/res/textures/gui/crosshair.png b/res/textures/gui/crosshair.png new file mode 100644 index 00000000..7010266b Binary files /dev/null and b/res/textures/gui/crosshair.png differ diff --git a/res/textures/gui/error.png b/res/textures/gui/error.png new file mode 100644 index 00000000..5651ac6c Binary files /dev/null and b/res/textures/gui/error.png differ diff --git a/res/textures/gui/warning.png b/res/textures/gui/warning.png new file mode 100644 index 00000000..7dbbd087 Binary files /dev/null and b/res/textures/gui/warning.png differ diff --git a/src/assets/Assets.cpp b/src/assets/Assets.cpp index c13f3da0..5183cca2 100644 --- a/src/assets/Assets.cpp +++ b/src/assets/Assets.cpp @@ -4,6 +4,8 @@ #include "../graphics/Shader.h" #include "../graphics/Atlas.h" #include "../graphics/Font.h" +#include "../frontend/UiDocument.h" +#include "../logic/scripting/scripting.h" Assets::~Assets() { } @@ -62,6 +64,17 @@ void Assets::store(const TextureAnimation& animation) { animations.emplace_back(animation); } +UiDocument* Assets::getLayout(std::string name) const { + auto found = layouts.find(name); + if (found == layouts.end()) + return nullptr; + return found->second.get(); +} + +void Assets::store(UiDocument* layout, std::string name) { + layouts[name].reset(layout); +} + void Assets::extend(const Assets& assets) { for (auto entry : assets.textures) { textures[entry.first] = entry.second; @@ -75,6 +88,9 @@ void Assets::extend(const Assets& assets) { for (auto entry : assets.atlases) { atlases[entry.first] = entry.second; } + for (auto entry : assets.layouts) { + layouts[entry.first] = entry.second; + } animations.clear(); for (auto entry : assets.animations) { animations.emplace_back(entry); diff --git a/src/assets/Assets.h b/src/assets/Assets.h index 6df72ac4..87e94380 100644 --- a/src/assets/Assets.h +++ b/src/assets/Assets.h @@ -12,12 +12,20 @@ class Texture; class Shader; class Font; class Atlas; +class UiDocument; + +struct LayoutCfg { + int env; + + LayoutCfg(int env) : env(env) {} +}; class Assets { std::unordered_map> textures; std::unordered_map> shaders; std::unordered_map> fonts; std::unordered_map> atlases; + std::unordered_map> layouts; std::vector animations; public: ~Assets(); @@ -36,6 +44,9 @@ public: const std::vector& getAnimations(); void store(const TextureAnimation& animation); + UiDocument* getLayout(std::string name) const; + void store(UiDocument* layout, std::string name); + void extend(const Assets& assets); }; diff --git a/src/assets/AssetsLoader.cpp b/src/assets/AssetsLoader.cpp index 9ef4696d..2298904a 100644 --- a/src/assets/AssetsLoader.cpp +++ b/src/assets/AssetsLoader.cpp @@ -8,20 +8,24 @@ #include "../constants.h" #include "../files/engine_paths.h" - -using std::filesystem::path; -using std::unique_ptr; +#include "../content/Content.h" +#include "../logic/scripting/scripting.h" AssetsLoader::AssetsLoader(Assets* assets, const ResPaths* paths) : assets(assets), paths(paths) { + addLoader(ASSET_SHADER, assetload::shader); + addLoader(ASSET_TEXTURE, assetload::texture); + addLoader(ASSET_FONT, assetload::font); + addLoader(ASSET_ATLAS, assetload::atlas); + addLoader(ASSET_LAYOUT, assetload::layout); } void AssetsLoader::addLoader(int tag, aloader_func func) { loaders[tag] = func; } -void AssetsLoader::add(int tag, const std::string filename, const std::string alias) { - entries.push(aloader_entry{ tag, filename, alias }); +void AssetsLoader::add(int tag, const std::string filename, const std::string alias, std::shared_ptr settings) { + entries.push(aloader_entry{ tag, filename, alias, settings}); } bool AssetsLoader::hasNext() const { @@ -38,32 +42,48 @@ bool AssetsLoader::loadNext() { return false; } aloader_func loader = found->second; - bool status = loader(assets, paths, entry.filename, entry.alias); + bool status = loader(*this, assets, paths, entry.filename, entry.alias, entry.config); entries.pop(); return status; } -void AssetsLoader::createDefaults(AssetsLoader& loader) { - loader.addLoader(ASSET_SHADER, assetload::shader); - loader.addLoader(ASSET_TEXTURE, assetload::texture); - loader.addLoader(ASSET_FONT, assetload::font); - loader.addLoader(ASSET_ATLAS, assetload::atlas); +void addLayouts(int env, const std::string& prefix, const fs::path& folder, AssetsLoader& loader) { + if (!fs::is_directory(folder)) { + return; + } + for (auto& entry : fs::directory_iterator(folder)) { + const fs::path file = entry.path(); + if (file.extension().u8string() != ".xml") + continue; + std::string name = prefix+":"+file.stem().u8string(); + loader.add(ASSET_LAYOUT, file.u8string(), name, std::make_shared(env)); + } } -void AssetsLoader::addDefaults(AssetsLoader& loader, bool allAssets) { - if (allAssets) { - loader.add(ASSET_SHADER, SHADERS_FOLDER"/main", "main"); - loader.add(ASSET_SHADER, SHADERS_FOLDER"/lines", "lines"); - loader.add(ASSET_SHADER, SHADERS_FOLDER"/ui", "ui"); +void AssetsLoader::addDefaults(AssetsLoader& loader, const Content* content) { + loader.add(ASSET_FONT, FONTS_FOLDER"/font", "normal"); + loader.add(ASSET_SHADER, SHADERS_FOLDER"/ui", "ui"); + loader.add(ASSET_SHADER, SHADERS_FOLDER"/main", "main"); + loader.add(ASSET_SHADER, SHADERS_FOLDER"/lines", "lines"); + loader.add(ASSET_TEXTURE, TEXTURES_FOLDER"/gui/menubg.png", "gui/menubg"); + loader.add(ASSET_TEXTURE, TEXTURES_FOLDER"/gui/delete_icon.png", "gui/delete_icon"); + loader.add(ASSET_TEXTURE, TEXTURES_FOLDER"/gui/no_icon.png", "gui/no_icon"); + loader.add(ASSET_TEXTURE, TEXTURES_FOLDER"/gui/warning.png", "gui/warning"); + loader.add(ASSET_TEXTURE, TEXTURES_FOLDER"/gui/error.png", "gui/error"); + if (content) { loader.add(ASSET_SHADER, SHADERS_FOLDER"/ui3d", "ui3d"); loader.add(ASSET_SHADER, SHADERS_FOLDER"/background", "background"); loader.add(ASSET_SHADER, SHADERS_FOLDER"/skybox_gen", "skybox_gen"); - loader.add(ASSET_TEXTURE, TEXTURES_FOLDER"/gui/menubg.png", "gui/menubg"); - loader.add(ASSET_TEXTURE, TEXTURES_FOLDER"/gui/delete_icon.png", "gui/delete_icon"); - loader.add(ASSET_TEXTURE, TEXTURES_FOLDER"/gui/no_icon.png", "gui/no_icon"); loader.add(ASSET_TEXTURE, TEXTURES_FOLDER"/misc/moon.png", "misc/moon"); loader.add(ASSET_TEXTURE, TEXTURES_FOLDER"/misc/sun.png", "misc/sun"); - loader.add(ASSET_FONT, FONTS_FOLDER"/font", "normal"); + loader.add(ASSET_TEXTURE, TEXTURES_FOLDER"/gui/crosshair.png", "gui/crosshair"); + + addLayouts(0, "core", loader.getPaths()->getMainRoot()/fs::path("layouts"), loader); + for (auto& pack : content->getPacks()) { + auto& info = pack->getInfo(); + fs::path folder = info.folder / fs::path("layouts"); + addLayouts(pack->getEnvironment()->getId(), info.id, folder, loader); + } } loader.add(ASSET_ATLAS, TEXTURES_FOLDER"/blocks", "blocks"); loader.add(ASSET_ATLAS, TEXTURES_FOLDER"/items", "items"); @@ -71,4 +91,4 @@ void AssetsLoader::addDefaults(AssetsLoader& loader, bool allAssets) { const ResPaths* AssetsLoader::getPaths() const { return paths; -} \ No newline at end of file +} diff --git a/src/assets/AssetsLoader.h b/src/assets/AssetsLoader.h index 13ea12fa..de401846 100644 --- a/src/assets/AssetsLoader.h +++ b/src/assets/AssetsLoader.h @@ -2,6 +2,7 @@ #define ASSETS_ASSETS_LOADER_H #include +#include #include #include #include @@ -10,16 +11,20 @@ const short ASSET_TEXTURE = 1; const short ASSET_SHADER = 2; const short ASSET_FONT = 3; const short ASSET_ATLAS = 4; +const short ASSET_LAYOUT = 5; class ResPaths; class Assets; +class AssetsLoader; +class Content; -typedef std::function aloader_func; +using aloader_func = std::function)>; struct aloader_entry { int tag; const std::string filename; const std::string alias; + std::shared_ptr config; }; class AssetsLoader { @@ -30,13 +35,18 @@ class AssetsLoader { public: AssetsLoader(Assets* assets, const ResPaths* paths); void addLoader(int tag, aloader_func func); - void add(int tag, const std::string filename, const std::string alias); + void add( + int tag, + const std::string filename, + const std::string alias, + std::shared_ptr settings=nullptr + ); + bool hasNext() const; bool loadNext(); - static void createDefaults(AssetsLoader& loader); - static void addDefaults(AssetsLoader& loader, bool allAssets); + static void addDefaults(AssetsLoader& loader, const Content* content); const ResPaths* getPaths() const; }; diff --git a/src/assets/assetload_funcs.cpp b/src/assets/assetload_funcs.cpp index 6683d7f6..eeff84e0 100644 --- a/src/assets/assetload_funcs.cpp +++ b/src/assets/assetload_funcs.cpp @@ -3,6 +3,7 @@ #include #include #include "Assets.h" +#include "AssetsLoader.h" #include "../files/files.h" #include "../files/engine_paths.h" #include "../coders/png.h" @@ -13,193 +14,233 @@ #include "../graphics/Atlas.h" #include "../graphics/Font.h" #include "../graphics/TextureAnimation.h" +#include "../frontend/UiDocument.h" +#include "../logic/scripting/scripting.h" namespace fs = std::filesystem; -bool assetload::texture(Assets* assets, - const ResPaths* paths, - const std::string filename, - const std::string name) { - Texture* texture = png::load_texture(paths->find(filename).string()); - if (texture == nullptr) { - std::cerr << "failed to load texture '" << name << "'" << std::endl; - return false; - } - assets->store(texture, name); - return true; +bool assetload::texture( + AssetsLoader&, + Assets* assets, + const ResPaths* paths, + const std::string filename, + const std::string name, + std::shared_ptr +) { + std::unique_ptr texture( + png::load_texture(paths->find(filename).u8string()) + ); + if (texture == nullptr) { + std::cerr << "failed to load texture '" << name << "'" << std::endl; + return false; + } + assets->store(texture.release(), name); + return true; } -bool assetload::shader(Assets* assets, - const ResPaths* paths, - const std::string filename, - const std::string name) { - fs::path vertexFile = paths->find(filename+".glslv"); - fs::path fragmentFile = paths->find(filename+".glslf"); +bool assetload::shader( + AssetsLoader&, + Assets* assets, + const ResPaths* paths, + const std::string filename, + const std::string name, + std::shared_ptr +) { + fs::path vertexFile = paths->find(filename+".glslv"); + fs::path fragmentFile = paths->find(filename+".glslf"); - std::string vertexSource = files::read_string(vertexFile); - std::string fragmentSource = files::read_string(fragmentFile); + std::string vertexSource = files::read_string(vertexFile); + std::string fragmentSource = files::read_string(fragmentFile); - Shader* shader = Shader::loadShader( - vertexFile.string(), - fragmentFile.string(), - vertexSource, fragmentSource); + Shader* shader = Shader::loadShader( + vertexFile.string(), + fragmentFile.string(), + vertexSource, fragmentSource); - if (shader == nullptr) { - std::cerr << "failed to load shader '" << name << "'" << std::endl; - return false; - } - assets->store(shader, name); - return true; + if (shader == nullptr) { + std::cerr << "failed to load shader '" << name << "'" << std::endl; + return false; + } + assets->store(shader, name); + return true; } static bool appendAtlas(AtlasBuilder& atlas, const fs::path& file) { - // png is only supported format - if (file.extension() != ".png") - return false; - std::string name = file.stem().string(); - // skip duplicates - if (atlas.has(name)) { - return false; - } - std::unique_ptr image(png::load_image(file.string())); - if (image == nullptr) { - std::cerr << "could not to load " << file.string() << std::endl; - return false; - } - image->fixAlphaColor(); - atlas.add(name, image.release()); + // png is only supported format + if (file.extension() != ".png") + return false; + std::string name = file.stem().string(); + // skip duplicates + if (atlas.has(name)) { + return false; + } + std::unique_ptr image(png::load_image(file.string())); + if (image == nullptr) { + std::cerr << "could not to load " << file.string() << std::endl; + return false; + } + image->fixAlphaColor(); + atlas.add(name, image.release()); - return true; + return true; } -bool assetload::atlas(Assets* assets, - const ResPaths* paths, - const std::string directory, - const std::string name) { - AtlasBuilder builder; - for (const auto& file : paths->listdir(directory)) { - if (!appendAtlas(builder, file)) continue; - } - Atlas* atlas = builder.build(2); - assets->store(atlas, name); - for (const auto& file : builder.getNames()) { - assetload::animation(assets, paths, "textures", file, atlas); - } - return true; +bool assetload::atlas( + AssetsLoader&, + Assets* assets, + const ResPaths* paths, + const std::string directory, + const std::string name, + std::shared_ptr +) { + AtlasBuilder builder; + for (const auto& file : paths->listdir(directory)) { + if (!appendAtlas(builder, file)) continue; + } + Atlas* atlas = builder.build(2); + assets->store(atlas, name); + for (const auto& file : builder.getNames()) { + assetload::animation(assets, paths, "textures", file, atlas); + } + return true; } -bool assetload::font(Assets* assets, - const ResPaths* paths, - const std::string filename, - const std::string name) { - std::vector pages; - for (size_t i = 0; i <= 4; i++) { +bool assetload::font( + AssetsLoader&, + Assets* assets, + const ResPaths* paths, + const std::string filename, + const std::string name, + std::shared_ptr +) { + std::vector> pages; + for (size_t i = 0; i <= 4; i++) { std::string name = filename + "_" + std::to_string(i) + ".png"; name = paths->find(name).string(); - Texture* texture = png::load_texture(name); - if (texture == nullptr) { - std::cerr << "failed to load bitmap font '" << name; + std::unique_ptr texture (png::load_texture(name)); + if (texture == nullptr) { + std::cerr << "failed to load bitmap font '" << name; std::cerr << "' (missing page " << std::to_string(i) << ")"; std::cerr << std::endl; - return false; - } - pages.push_back(texture); - } - Font* font = new Font(pages, pages[0]->height / 16); - assets->store(font, name); - return true; + return false; + } + pages.push_back(std::move(texture)); + } + int res = pages[0]->height / 16; + assets->store(new Font(std::move(pages), res, 4), name); + return true; +} + +bool assetload::layout( + AssetsLoader& loader, + Assets* assets, + const ResPaths* paths, + const std::string file, + const std::string name, + std::shared_ptr config +) { + try { + LayoutCfg* cfg = reinterpret_cast(config.get()); + auto document = UiDocument::read(loader, cfg->env, name, file); + assets->store(document.release(), name); + return true; + } catch (const parsing_error& err) { + std::cerr << "failed to parse layout XML '" << file << "'" << std::endl; + std::cerr << err.errorLog() << std::endl; + return false; + } } bool assetload::animation(Assets* assets, - const ResPaths* paths, - const std::string directory, - const std::string name, - Atlas* dstAtlas) { - std::string animsDir = directory + "/animations"; - std::string blocksDir = directory + "/blocks"; + const ResPaths* paths, + const std::string directory, + const std::string name, + Atlas* dstAtlas) { + std::string animsDir = directory + "/animations"; + std::string blocksDir = directory + "/blocks"; - for (const auto& folder : paths->listdir(animsDir)) { - if (!fs::is_directory(folder)) continue; - if (folder.filename().string() != name) continue; - if (fs::is_empty(folder)) continue; - - AtlasBuilder builder; + for (const auto& folder : paths->listdir(animsDir)) { + if (!fs::is_directory(folder)) continue; + if (folder.filename().string() != name) continue; + if (fs::is_empty(folder)) continue; + + AtlasBuilder builder; appendAtlas(builder, paths->find(blocksDir + "/" + name + ".png")); - std::string animFile = folder.string() + "/animation.json"; + std::string animFile = folder.string() + "/animation.json"; - std::vector> frameList; + std::vector> frameList; - if (fs::exists(animFile)) { - auto root = files::read_json(animFile); + if (fs::exists(animFile)) { + auto root = files::read_json(animFile); - auto frameArr = root->list("frames"); + auto frameArr = root->list("frames"); - Frame temp; - float frameDuration = DEFAULT_FRAME_DURATION; - std::string frameName; + float frameDuration = DEFAULT_FRAME_DURATION; + std::string frameName; - if (frameArr) { - for (size_t i = 0; i < frameArr->size(); i++) { - auto currentFrame = frameArr->list(i); + if (frameArr) { + for (size_t i = 0; i < frameArr->size(); i++) { + auto currentFrame = frameArr->list(i); - frameName = currentFrame->str(0); - if (currentFrame->size() > 1) frameDuration = static_cast(currentFrame->integer(1)) / 1000; + frameName = currentFrame->str(0); + if (currentFrame->size() > 1) + frameDuration = static_cast(currentFrame->integer(1)) / 1000; - frameList.emplace_back(frameName, frameDuration); - } - } - } - for (const auto& file : paths->listdir(animsDir + "/" + name)) { - if (!frameList.empty()) { - bool contains = false; - for (const auto& elem : frameList) { - if (file.stem() == elem.first) { - contains = true; - break; - } - } - if (!contains) continue; - } - if (!appendAtlas(builder, file)) continue; - } + frameList.emplace_back(frameName, frameDuration); + } + } + } + for (const auto& file : paths->listdir(animsDir + "/" + name)) { + if (!frameList.empty()) { + bool contains = false; + for (const auto& elem : frameList) { + if (file.stem() == elem.first) { + contains = true; + break; + } + } + if (!contains) continue; + } + if (!appendAtlas(builder, file)) continue; + } - Atlas* srcAtlas = builder.build(2); + std::unique_ptr srcAtlas (builder.build(2)); - Texture* srcTex = srcAtlas->getTexture(); - Texture* dstTex = dstAtlas->getTexture(); + Texture* srcTex = srcAtlas->getTexture(); + Texture* dstTex = dstAtlas->getTexture(); - TextureAnimation animation(srcTex, dstTex); - Frame frame; - UVRegion region = dstAtlas->get(name); + TextureAnimation animation(srcTex, dstTex); + Frame frame; + UVRegion region = dstAtlas->get(name); - frame.dstPos = glm::ivec2(region.u1 * dstTex->width, region.v1 * dstTex->height); - frame.size = glm::ivec2(region.u2 * dstTex->width, region.v2 * dstTex->height) - frame.dstPos; + frame.dstPos = glm::ivec2(region.u1 * dstTex->width, region.v1 * dstTex->height); + frame.size = glm::ivec2(region.u2 * dstTex->width, region.v2 * dstTex->height) - frame.dstPos; - if (frameList.empty()) { - for (const auto& elem : builder.getNames()) { - region = srcAtlas->get(elem); - frame.srcPos = glm::ivec2(region.u1 * srcTex->width, srcTex->height - region.v2 * srcTex->height); - animation.addFrame(frame); - } - } - else { - for (const auto& elem : frameList) { - if (!srcAtlas->has(elem.first)) { - std::cerr << "Unknown frame name: " << elem.first << std::endl; - continue; - } - region = srcAtlas->get(elem.first); - frame.duration = elem.second; - frame.srcPos = glm::ivec2(region.u1 * srcTex->width, srcTex->height - region.v2 * srcTex->height); - animation.addFrame(frame); - } - } + if (frameList.empty()) { + for (const auto& elem : builder.getNames()) { + region = srcAtlas->get(elem); + frame.srcPos = glm::ivec2(region.u1 * srcTex->width, srcTex->height - region.v2 * srcTex->height); + animation.addFrame(frame); + } + } + else { + for (const auto& elem : frameList) { + if (!srcAtlas->has(elem.first)) { + std::cerr << "Unknown frame name: " << elem.first << std::endl; + continue; + } + region = srcAtlas->get(elem.first); + frame.duration = elem.second; + frame.srcPos = glm::ivec2(region.u1 * srcTex->width, srcTex->height - region.v2 * srcTex->height); + animation.addFrame(frame); + } + } - assets->store(srcAtlas, name + "_animation"); - assets->store(animation); + assets->store(srcAtlas.release(), name + "_animation"); + assets->store(animation); - return true; - } - return true; + return true; + } + return true; } diff --git a/src/assets/assetload_funcs.h b/src/assets/assetload_funcs.h index 67da528c..a9ef5d26 100644 --- a/src/assets/assetload_funcs.h +++ b/src/assets/assetload_funcs.h @@ -2,33 +2,62 @@ #define ASSETS_ASSET_LOADERS_H_ #include +#include class ResPaths; class Assets; +class AssetsLoader; class Atlas; namespace assetload { - bool texture(Assets* assets, - const ResPaths* paths, - const std::string filename, - const std::string name); - bool shader(Assets* assets, - const ResPaths* paths, - const std::string filename, - const std::string name); - bool atlas(Assets* assets, - const ResPaths* paths, - const std::string directory, - const std::string name); - bool font(Assets* assets, - const ResPaths* paths, - const std::string filename, - const std::string name); - bool animation(Assets* assets, - const ResPaths* paths, - const std::string directory, - const std::string name, - Atlas* dstAtlas); + bool texture( + AssetsLoader&, + Assets*, + const ResPaths* paths, + const std::string filename, + const std::string name, + std::shared_ptr settings + ); + bool shader( + AssetsLoader&, + Assets*, + const ResPaths* paths, + const std::string filename, + const std::string name, + std::shared_ptr settings + ); + bool atlas( + AssetsLoader&, + Assets*, + const ResPaths* paths, + const std::string directory, + const std::string name, + std::shared_ptr settings + ); + bool font( + AssetsLoader&, + Assets*, + const ResPaths* paths, + const std::string filename, + const std::string name, + std::shared_ptr settings + ); + bool layout( + AssetsLoader&, + Assets*, + const ResPaths* paths, + const std::string file, + const std::string name, + std::shared_ptr settings + ); + + bool animation( + Assets*, + const ResPaths* paths, + const std::string directory, + const std::string name, + Atlas* dstAtlas + ); } #endif // ASSETS_ASSET_LOADERS_H_ \ No newline at end of file diff --git a/src/audio/audioutil.cpp b/src/audio/audioutil.cpp index fcb6b773..17e3d99c 100644 --- a/src/audio/audioutil.cpp +++ b/src/audio/audioutil.cpp @@ -15,21 +15,23 @@ #endif bool is_big_endian(void){ - union { - uint32_t i; - char c[4]; - } bint = {0x01020304}; + uint32_t ui32_v = 0x01020304; + char bytes[sizeof(uint32_t)]; + std::memcpy(bytes, &ui32_v, sizeof(uint32_t)); - return bint.c[0] == 1; + return bytes[0] == 1; } std::int32_t convert_to_int(char* buffer, std::size_t len){ std::int32_t a = 0; - if(!is_big_endian()) + if (!is_big_endian()) { std::memcpy(&a, buffer, len); - else - for(std::size_t i = 0; i < len; ++i) + } + else { + for (std::size_t i = 0; i < len; ++i) { reinterpret_cast(&a)[3 - i] = buffer[i]; + } + } return a; } diff --git a/src/coders/binary_json.cpp b/src/coders/binary_json.cpp index c296dc16..04d91dfa 100644 --- a/src/coders/binary_json.cpp +++ b/src/coders/binary_json.cpp @@ -56,7 +56,11 @@ static void to_binary(ByteBuilder& builder, const Value* value) { static List* array_from_binary(ByteReader& reader); static Map* object_from_binary(ByteReader& reader); -std::vector json::to_binary(const Map* obj) { +std::vector json::to_binary(const Map* obj, bool compress) { + if (compress) { + auto bytes = to_binary(obj, false); + return gzip::compress(bytes.data(), bytes.size()); + } ByteBuilder builder; // type byte builder.put(BJSON_TYPE_DOCUMENT); diff --git a/src/coders/binary_json.h b/src/coders/binary_json.h index 018f6573..913e71ec 100644 --- a/src/coders/binary_json.h +++ b/src/coders/binary_json.h @@ -21,7 +21,7 @@ namespace json { const int BJSON_TYPE_NULL = 0xC; const int BJSON_TYPE_CDOCUMENT = 0x1F; - extern std::vector to_binary(const dynamic::Map* obj); + extern std::vector to_binary(const dynamic::Map* obj, bool compress=false); extern std::unique_ptr from_binary(const ubyte* src, size_t size); } diff --git a/src/coders/byte_utils.cpp b/src/coders/byte_utils.cpp index eaead0dc..ce52a156 100644 --- a/src/coders/byte_utils.cpp +++ b/src/coders/byte_utils.cpp @@ -121,21 +121,21 @@ void ByteReader::checkMagic(const char* data, size_t size) { ubyte ByteReader::get() { if (pos == size) { - throw std::underflow_error("buffer underflow"); + throw std::runtime_error("buffer underflow"); } return data[pos++]; } ubyte ByteReader::peek() { if (pos == size) { - throw std::underflow_error("buffer underflow"); + throw std::runtime_error("buffer underflow"); } return data[pos]; } int16_t ByteReader::getInt16() { if (pos+2 > size) { - throw std::underflow_error("unexpected end"); + throw std::runtime_error("buffer underflow"); } pos += 2; return (static_cast(data[pos - 1]) << 8) | @@ -144,7 +144,7 @@ int16_t ByteReader::getInt16() { int32_t ByteReader::getInt32() { if (pos+4 > size) { - throw std::underflow_error("unexpected end"); + throw std::runtime_error("buffer underflow"); } pos += 4; return (static_cast(data[pos - 1]) << 24) | @@ -155,7 +155,7 @@ int32_t ByteReader::getInt32() { int64_t ByteReader::getInt64() { if (pos+8 > size) { - throw std::underflow_error("unexpected end"); + throw std::runtime_error("buffer underflow"); } pos += 8; return (static_cast(data[pos - 1]) << 56) | @@ -191,7 +191,7 @@ const char* ByteReader::getCString() { std::string ByteReader::getString() { uint32_t length = (uint32_t)getInt32(); if (pos+length > size) { - throw std::underflow_error("unexpected end"); + throw std::runtime_error("buffer underflow"); } pos += length; return std::string(reinterpret_cast(data+pos-length), length); @@ -200,3 +200,11 @@ std::string ByteReader::getString() { bool ByteReader::hasNext() const { return pos < size; } + +const ubyte* ByteReader::pointer() const { + return data + pos; +} + +void ByteReader::skip(size_t n) { + pos += n; +} diff --git a/src/coders/byte_utils.h b/src/coders/byte_utils.h index 7bd2846d..4d651892 100644 --- a/src/coders/byte_utils.h +++ b/src/coders/byte_utils.h @@ -71,6 +71,9 @@ public: const char* getCString(); std::string getString(); bool hasNext() const; + + const ubyte* pointer() const; + void skip(size_t n); }; #endif // CODERS_BYTE_UTILS_H_ diff --git a/src/coders/commons.cpp b/src/coders/commons.cpp index 82033cd5..ad4a8677 100644 --- a/src/coders/commons.cpp +++ b/src/coders/commons.cpp @@ -4,19 +4,6 @@ #include #include -inline int char2int(int c) { - if (c >= '0' && c <= '9') { - return c - '0'; - } - if (c >= 'a' && c <= 'f') { - return 10 + c - 'a'; - } - if (c >= 'A' && c <= 'F') { - return 10 + c - 'A'; - } - return -1; -} - inline double power(double base, int64_t power) { double result = 1.0; for (int64_t i = 0; i < power; i++) { @@ -98,6 +85,18 @@ void BasicParser::skipWhitespace() { } } +void BasicParser::skip(size_t n) { + n = std::min(n, source.length()-pos); + + for (size_t i = 0; i < n; i++) { + char next = source[pos++]; + if (next == '\n') { + line++; + linestart = pos; + } + } +} + void BasicParser::skipLine() { while (hasNext()) { if (source[pos] == '\n') { @@ -110,10 +109,28 @@ void BasicParser::skipLine() { } } +bool BasicParser::skipTo(const std::string& substring) { + size_t idx = source.find(substring, pos); + if (idx == std::string::npos) { + skip(source.length()-pos); + return false; + } else { + skip(idx-pos); + return true; + } +} + bool BasicParser::hasNext() { return pos < source.length(); } +bool BasicParser::isNext(const std::string& substring) { + if (source.length() - pos < substring.length()) { + return false; + } + return source.substr(pos, substring.length()) == substring; +} + char BasicParser::nextChar() { if (!hasNext()) { throw error("unexpected end"); @@ -129,6 +146,17 @@ void BasicParser::expect(char expected) { pos++; } +void BasicParser::expect(const std::string& substring) { + if (substring.empty()) + return; + for (uint i = 0; i < substring.length(); i++) { + if (source.length() <= pos + i || source[pos+i] != substring[i]) { + throw error(escape_string(substring)+" expected"); + } + } + pos += substring.length(); +} + void BasicParser::expectNewLine() { while (hasNext()) { char next = source[pos]; @@ -145,6 +173,10 @@ void BasicParser::expectNewLine() { } } +void BasicParser::goBack() { + if (pos) pos--; +} + char BasicParser::peek() { skipWhitespace(); if (pos >= source.length()) { @@ -171,7 +203,7 @@ std::string BasicParser::parseName() { int64_t BasicParser::parseSimpleInt(int base) { char c = peek(); - int index = char2int(c); + int index = hexchar2int(c); if (index == -1 || index >= base) { throw error("invalid number literal"); } @@ -182,7 +214,7 @@ int64_t BasicParser::parseSimpleInt(int base) { while (c == '_') { c = source[++pos]; } - index = char2int(c); + index = hexchar2int(c); if (index == -1 || index >= base) { return value; } diff --git a/src/coders/commons.h b/src/coders/commons.h index 6c225309..509ff0d6 100644 --- a/src/coders/commons.h +++ b/src/coders/commons.h @@ -41,6 +41,19 @@ inline bool is_identifier_part(int c) { return is_identifier_start(c) || is_digit(c); } +inline int hexchar2int(int c) { + if (c >= '0' && c <= '9') { + return c - '0'; + } + if (c >= 'a' && c <= 'f') { + return 10 + c - 'a'; + } + if (c >= 'A' && c <= 'F') { + return 10 + c - 'A'; + } + return -1; +} + extern std::string escape_string(std::string s); class parsing_error : public std::runtime_error { @@ -70,12 +83,17 @@ protected: uint linestart = 0; virtual void skipWhitespace(); + void skip(size_t n); void skipLine(); + bool skipTo(const std::string& substring); void expect(char expected); + void expect(const std::string& substring); char peek(); char nextChar(); bool hasNext(); + bool isNext(const std::string& substring); void expectNewLine(); + void goBack(); std::string parseName(); int64_t parseSimpleInt(int base); diff --git a/src/coders/gzip.cpp b/src/coders/gzip.cpp index b30140c8..ee1afd62 100644 --- a/src/coders/gzip.cpp +++ b/src/coders/gzip.cpp @@ -34,7 +34,7 @@ std::vector gzip::compress(const ubyte* src, size_t size) { std::vector gzip::decompress(const ubyte* src, size_t size) { // getting uncompressed data length from gzip footer - size_t decompressed_size = *(uint32_t*)(src+size-4); + size_t decompressed_size = *reinterpret_cast(src+size-4); std::vector buffer; buffer.resize(decompressed_size); diff --git a/src/coders/xml.cpp b/src/coders/xml.cpp new file mode 100644 index 00000000..03b4b3cd --- /dev/null +++ b/src/coders/xml.cpp @@ -0,0 +1,425 @@ +#include "xml.h" + +#include +#include +#include +#include "../util/stringutil.h" + +using namespace xml; + +Attribute::Attribute(std::string name, std::string text) + : name(name), + text(text) { +} + +const std::string& Attribute::getName() const { + return name; +} + +const std::string& Attribute::getText() const { + return text; +} + +int64_t Attribute::asInt() const { + return std::stoll(text); +} + +double Attribute::asFloat() const { + return util::parse_double(text); +} + +bool Attribute::asBool() const { + return text == "true" || text == "1"; +} + +/* Read 2d vector formatted `x,y`*/ +glm::vec2 Attribute::asVec2() const { + size_t pos = text.find(','); + if (pos == std::string::npos) { + throw std::runtime_error("invalid vec2 value "+escape_string(text)); + } + return glm::vec2( + util::parse_double(text, 0, pos), + util::parse_double(text, pos+1, text.length()-pos-1) + ); +} + +/* Read 3d vector formatted `x,y,z`*/ +glm::vec3 Attribute::asVec3() const { + size_t pos1 = text.find(','); + if (pos1 == std::string::npos) { + throw std::runtime_error("invalid vec3 value "+escape_string(text)); + } + size_t pos2 = text.find(',', pos1+1); + if (pos2 == std::string::npos) { + throw std::runtime_error("invalid vec3 value "+escape_string(text)); + } + return glm::vec3( + util::parse_double(text, 0, pos1), + util::parse_double(text, pos1+1, pos2), + util::parse_double(text, pos2+1, text.length()-pos2-1) + ); +} + +/* Read 4d vector formatted `x,y,z,w`*/ +glm::vec4 Attribute::asVec4() const { + size_t pos1 = text.find(','); + if (pos1 == std::string::npos) { + throw std::runtime_error("invalid vec4 value "+escape_string(text)); + } + size_t pos2 = text.find(',', pos1+1); + if (pos2 == std::string::npos) { + throw std::runtime_error("invalid vec4 value "+escape_string(text)); + } + size_t pos3 = text.find(',', pos2+1); + if (pos3 == std::string::npos) { + throw std::runtime_error("invalid vec4 value "+escape_string(text)); + } + return glm::vec4( + util::parse_double(text, 0, pos1), + util::parse_double(text, pos1+1, pos2-pos1-1), + util::parse_double(text, pos2+1, pos3-pos2-1), + util::parse_double(text, pos3+1, text.length()-pos3-1) + ); +} + +/* Read RGBA color. Supported formats: + - "#RRGGBB" or "#RRGGBBAA" hex color */ +glm::vec4 Attribute::asColor() const { + if (text[0] == '#') { + if (text.length() != 7 && text.length() != 9) { + throw std::runtime_error("#RRGGBB or #RRGGBBAA required"); + } + int a = 255; + int r = (hexchar2int(text[1]) << 4) | hexchar2int(text[2]); + int g = (hexchar2int(text[3]) << 4) | hexchar2int(text[4]); + int b = (hexchar2int(text[5]) << 4) | hexchar2int(text[6]); + if (text.length() == 9) { + a = (hexchar2int(text[7]) << 4) | hexchar2int(text[8]); + } + return glm::vec4(r / 255.f, g / 255.f, b / 255.f, a / 255.f); + } else { + throw std::runtime_error("hex colors are only supported"); + } +} + +Node::Node(std::string tag) : tag(tag) { +} + +void Node::add(xmlelement element) { + elements.push_back(element); +} + +void Node::set(std::string name, std::string text) { + attrs[name] = Attribute(name, text); +} + +const std::string& Node::getTag() const { + return tag; +} + +const xmlattribute Node::attr(const std::string& name) const { + auto found = attrs.find(name); + if (found == attrs.end()) { + throw std::runtime_error("element <"+tag+" ...> missing attribute "+name); + } + return found->second; +} + +const xmlattribute Node::attr(const std::string& name, const std::string& def) const { + auto found = attrs.find(name); + if (found == attrs.end()) { + return Attribute(name, def); + } + return found->second; +} + +bool Node::has(const std::string& name) const { + auto found = attrs.find(name); + return found != attrs.end(); +} + +xmlelement Node::sub(size_t index) { + return elements.at(index); +} + +size_t Node::size() const { + return elements.size(); +} + +const std::vector& Node::getElements() const { + return elements; +} + +const xmlelements_map& Node::getAttributes() const { + return attrs; +} + +Document::Document(std::string version, std::string encoding) + : version(version), + encoding(encoding) { +} + +void Document::setRoot(xmlelement element) { + this->root = element; +} + +xmlelement Document::getRoot() const { + return root; +} + +const std::string& Document::getVersion() const { + return version; +} + +const std::string& Document::getEncoding() const { + return encoding; +} + +Parser::Parser(std::string filename, std::string source) + : BasicParser(filename, source) { +} + +xmlelement Parser::parseOpenTag() { + std::string tag = parseXMLName(); + auto node = std::make_shared(tag); + + char c; + while (true) { + skipWhitespace(); + c = peek(); + if (c == '/' || c == '>' || c == '?') + break; + std::string attrname = parseXMLName(); + std::string attrtext = ""; + skipWhitespace(); + if (peek() == '=') { + nextChar(); + skipWhitespace(); + expect('"'); + attrtext = parseString('"'); + } + node->set(attrname, attrtext); + } + return node; +} + +void Parser::parseDeclaration() { + std::string version = "1.0"; + std::string encoding = "UTF-8"; + expect('<'); + if (peek() == '?') { + nextChar(); + xmlelement node = parseOpenTag(); + expect("?>"); + if (node->getTag() != "xml") { + throw error("invalid declaration"); + } + version = node->attr("version", version).getText(); + encoding = node->attr("encoding", encoding).getText(); + if (encoding != "utf-8" && encoding != "UTF-8") { + throw error("UTF-8 encoding is only supported"); + } + } else { + goBack(); + } + document = std::make_shared(version, encoding); +} + +void Parser::parseComment() { + expect("!--"); + if (skipTo("-->")) { + skip(3); + } else { + throw error("comment close missing"); + } +} + +std::string Parser::parseText() { + size_t start = pos; + while (hasNext()) { + char c = peek(); + if (c == '<') { + break; + } + nextChar(); + } + return source.substr(start, pos-start); +} + +inline bool is_xml_identifier_start(char c) { + return is_identifier_start(c) || c == ':'; +} + +inline bool is_xml_identifier_part(char c) { + return is_identifier_part(c) || c == '-' || c == '.' || c == ':'; +} + +std::string Parser::parseXMLName() { + char c = peek(); + if (!is_xml_identifier_start(c)) { + throw error("identifier expected"); + } + int start = pos; + while (hasNext() && is_xml_identifier_part(source[pos])) { + pos++; + } + return source.substr(start, pos-start); +} + +xmlelement Parser::parseElement() { + // text element + if (peek() != '<') { + auto element = std::make_shared("#"); + auto text = parseText(); + util::replaceAll(text, """, "\""); + util::replaceAll(text, "'", "'"); + util::replaceAll(text, "<", "<"); + util::replaceAll(text, ">", ">"); + util::replaceAll(text, "&", "&"); + element->set("#", text); + return element; + } + nextChar(); + + // + if (peek() == '!') { + if (isNext("!DOCTYPE ")) { + throw error("XML DTD is not supported yet"); + } + parseComment(); + return nullptr; + } + + auto element = parseOpenTag(); + char c = nextChar(); + + // + if (c == '/') { + expect('>'); + } + // ... + else if (c == '>') { + skipWhitespace(); + while (!isNext("add(sub); + } + skipWhitespace(); + } + skip(2); + expect(element->getTag()); + expect('>'); + } + // + else { + throw error("invalid syntax"); + } + return element; +} + +xmldocument Parser::parse() { + parseDeclaration(); + + xmlelement root = nullptr; + while (root == nullptr) { + root = parseElement(); + } + document->setRoot(root); + return document; +} + +xmldocument xml::parse(std::string filename, std::string source) { + Parser parser(filename, source); + return parser.parse(); +} + +inline void newline( + std::stringstream& ss, + bool nice, + const std::string& indentStr, + int indent +) { + if (!nice) + return; + ss << '\n'; + for (int i = 0; i < indent; i++) { + ss << indentStr; + } +} + +static void stringifyElement( + std::stringstream& ss, + const xmlelement element, + bool nice, + const std::string& indentStr, + int indent +) { + if (element->isText()) { + std::string text = element->attr("#").getText(); + util::replaceAll(text, "&", "&"); + util::replaceAll(text, "\"","""); + util::replaceAll(text, "'", "'"); + util::replaceAll(text, "<", "<"); + util::replaceAll(text, ">", ">"); + ss << text; + return; + } + const std::string& tag = element->getTag(); + + ss << '<' << tag; + auto& attrs = element->getAttributes(); + if (!attrs.empty()) { + ss << ' '; + int count = 0; + for (auto& entry : attrs) { + auto attr = entry.second; + ss << attr.getName(); + if (!attr.getText().empty()) { + ss << "=" << escape_string(attr.getText()); + } + if (count + 1 < int(attrs.size())) { + ss << " "; + } + count++; + } + } + auto& elements = element->getElements(); + if (elements.size() == 1 && elements[0]->isText()) { + ss << ">"; + stringifyElement(ss, elements[0], nice, indentStr, indent+1); + ss << ""; + return; + } + if (!elements.empty()) { + ss << '>'; + for (auto& sub : elements) { + newline(ss, nice, indentStr, indent+1); + stringifyElement(ss, sub, nice, indentStr, indent+1); + } + newline(ss, nice, indentStr, indent); + ss << ""; + + } else { + ss << "/>"; + } + +} + +std::string xml::stringify( + const xmldocument document, + bool nice, + const std::string& indentStr +) { + std::stringstream ss; + + // XML declaration + ss << "getVersion(); + ss << "\" encoding=\"UTF-8\" ?>"; + newline(ss, nice, indentStr, 0); + + stringifyElement(ss, document->getRoot(), nice, indentStr, 0); + + return ss.str(); +} diff --git a/src/coders/xml.h b/src/coders/xml.h new file mode 100644 index 00000000..824b8209 --- /dev/null +++ b/src/coders/xml.h @@ -0,0 +1,139 @@ +#ifndef CODERS_XML_H_ +#define CODERS_XML_H_ + +#include +#include +#include +#include +#include + +#include "commons.h" + +namespace xml { + class Node; + class Attribute; + class Document; + + typedef Attribute xmlattribute; + typedef std::shared_ptr xmlelement; + typedef std::shared_ptr xmldocument; + typedef std::unordered_map xmlelements_map; + + class Attribute { + std::string name; + std::string text; + public: + Attribute() {}; + Attribute(std::string name, std::string text); + + const std::string& getName() const; + const std::string& getText() const; + int64_t asInt() const; + double asFloat() const; + bool asBool() const; + glm::vec2 asVec2() const; + glm::vec3 asVec3() const; + glm::vec4 asVec4() const; + glm::vec4 asColor() const; + }; + + /* XML element class. Text element has tag 'text' and attribute 'text' */ + class Node { + std::string tag; + std::unordered_map attrs; + std::vector elements; + public: + Node(std::string tag); + + /* Add sub-element */ + void add(xmlelement element); + + /* Set attribute value. Creates attribute if does not exists */ + void set(std::string name, std::string text); + + /* Get element tag */ + const std::string& getTag() const; + + inline bool isText() const { + return getTag() == "#"; + } + + inline const std::string& text() const { + return attr("#").getText(); + } + + /* Get attribute by name + @param name attribute name + @throws std::runtime_error if element has no attribute + @return xmlattribute - {name, value} */ + const xmlattribute attr(const std::string& name) const; + /* Get attribute by name + @param name name + @param def default value will be returned wrapped in xmlattribute + if element has no attribute + @return xmlattribute - {name, value} or {name, def} if not found*/ + const xmlattribute attr(const std::string& name, const std::string& def) const; + + /* Check if element has attribute + @param name attribute name */ + bool has(const std::string& name) const; + + /* Get sub-element by index + @throws std::out_of_range if an invalid index given */ + xmlelement sub(size_t index); + + /* Get number of sub-elements */ + size_t size() const; + + const std::vector& getElements() const; + const xmlelements_map& getAttributes() const; + }; + + class Document { + xmlelement root = nullptr; + std::string version; + std::string encoding; + public: + Document(std::string version, std::string encoding); + + void setRoot(xmlelement element); + xmlelement getRoot() const; + + const std::string& getVersion() const; + const std::string& getEncoding() const; + }; + + class Parser : public BasicParser { + xmldocument document; + + xmlelement parseOpenTag(); + xmlelement parseElement(); + void parseDeclaration(); + void parseComment(); + std::string parseText(); + std::string parseXMLName(); + public: + Parser(std::string filename, std::string source); + + xmldocument parse(); + }; + + /* Serialize XML Document to string + @param document serializing document + @param nice use human readable format + (with indents and line-separators) + @param indentStr indentation characters sequence + (default - 4 spaces)*/ + extern std::string stringify( + const xmldocument document, + bool nice=true, + const std::string& indentStr=" " + ); + + /* Read XML Document from string + @param filename file name will be shown in error messages + @param source xml source code string */ + extern xmldocument parse(std::string filename, std::string source); +} + +#endif // CODERS_XML_H_ diff --git a/src/constants.h b/src/constants.h index c67a5f7c..56f54c7f 100644 --- a/src/constants.h +++ b/src/constants.h @@ -5,7 +5,7 @@ #include "typedefs.h" const int ENGINE_VERSION_MAJOR = 0; -const int ENGINE_VERSION_MINOR = 18; +const int ENGINE_VERSION_MINOR = 19; const int BLOCK_AIR = 0; const int ITEM_EMPTY = 0; @@ -28,7 +28,7 @@ const itemid_t ITEM_VOID = std::numeric_limits::max(); const blockid_t MAX_BLOCKS = BLOCK_VOID; -inline uint vox_index(int x, int y, int z, int w=CHUNK_W, int d=CHUNK_D) { +constexpr uint vox_index(uint x, uint y, uint z, uint w=CHUNK_W, uint d=CHUNK_D) { return (y * d + z) * w + x; } @@ -36,5 +36,6 @@ inline uint vox_index(int x, int y, int z, int w=CHUNK_W, int d=CHUNK_D) { #define SHADERS_FOLDER "shaders" #define TEXTURES_FOLDER "textures" #define FONTS_FOLDER "fonts" +#define LAYOUTS_FOLDER "layouts" #endif // SRC_CONSTANTS_H_ diff --git a/src/content/Content.cpp b/src/content/Content.cpp index aa98de9a..d208af2b 100644 --- a/src/content/Content.cpp +++ b/src/content/Content.cpp @@ -7,6 +7,9 @@ #include "../voxels/Block.h" #include "../items/ItemDef.h" +#include "ContentPack.h" +#include "../logic/scripting/scripting.h" + ContentBuilder::~ContentBuilder() { } @@ -22,7 +25,11 @@ void ContentBuilder::add(ItemDef* def) { itemIds.push_back(def->name); } -Block* ContentBuilder::createBlock(std::string id) { +void ContentBuilder::add(ContentPackRuntime* pack) { + packs.push_back(std::unique_ptr(pack)); +} + +Block& ContentBuilder::createBlock(std::string id) { auto found = blockDefs.find(id); if (found != blockDefs.end()) { //return found->second; @@ -30,20 +37,20 @@ Block* ContentBuilder::createBlock(std::string id) { } Block* block = new Block(id); add(block); - return block; + return *block; } -ItemDef* ContentBuilder::createItem(std::string id) { +ItemDef& ContentBuilder::createItem(std::string id) { auto found = itemDefs.find(id); if (found != itemDefs.end()) { if (found->second->generated) { - return found->second; + return *found->second; } throw namereuse_error("name "+id+" is already used", contenttype::item); } ItemDef* item = new ItemDef(id); add(item); - return item; + return *item; } void ContentBuilder::checkIdentifier(std::string id) { @@ -65,13 +72,13 @@ contenttype ContentBuilder::checkContentType(std::string id) { Content* ContentBuilder::build() { std::vector blockDefsIndices; - DrawGroups* groups = new DrawGroups; + auto groups = std::make_unique(); for (const std::string& name : blockIds) { Block* def = blockDefs[name]; // Generating runtime info def->rt.id = blockDefsIndices.size(); - def->rt.emissive = *((uint32_t*)def->emission); + def->rt.emissive = *reinterpret_cast(def->emission); def->rt.solid = def->model == BlockModel::block; if (def->rotatable) { @@ -95,20 +102,23 @@ Content* ContentBuilder::build() { // Generating runtime info def->rt.id = itemDefsIndices.size(); - def->rt.emissive = *((uint32_t*)def->emission); + def->rt.emissive = *reinterpret_cast(def->emission); itemDefsIndices.push_back(def); } auto indices = new ContentIndices(blockDefsIndices, itemDefsIndices); - std::unique_ptr content (new Content(indices, groups, blockDefs, itemDefs)); - // Now, it's time to solve foreign keys + auto content = std::make_unique( + indices, std::move(groups), blockDefs, itemDefs, std::move(packs) + ); + + // Now, it's time to resolve foreign keys for (Block* def : blockDefsIndices) { - def->rt.pickingItem = content->requireItem(def->pickingItem)->rt.id; + def->rt.pickingItem = content->requireItem(def->pickingItem).rt.id; } for (ItemDef* def : itemDefsIndices) { - def->rt.placingBlock = content->requireBlock(def->placingBlock)->rt.id; + def->rt.placingBlock = content->requireBlock(def->placingBlock).rt.id; } return content.release(); @@ -121,17 +131,19 @@ ContentIndices::ContentIndices( itemDefs(itemDefs) { } -Content::Content(ContentIndices* indices, DrawGroups* drawGroups, +Content::Content(ContentIndices* indices, + std::unique_ptr drawGroups, std::unordered_map blockDefs, - std::unordered_map itemDefs) + std::unordered_map itemDefs, + std::vector> packs) : blockDefs(blockDefs), itemDefs(itemDefs), indices(indices), - drawGroups(drawGroups) { + packs(std::move(packs)), + drawGroups(std::move(drawGroups)) { } Content::~Content() { - delete drawGroups; } Block* Content::findBlock(std::string id) const { @@ -142,12 +154,12 @@ Block* Content::findBlock(std::string id) const { return found->second; } -Block* Content::requireBlock(std::string id) const { +Block& Content::requireBlock(std::string id) const { auto found = blockDefs.find(id); if (found == blockDefs.end()) { throw std::runtime_error("missing block "+id); } - return found->second; + return *found->second; } ItemDef* Content::findItem(std::string id) const { @@ -158,10 +170,14 @@ ItemDef* Content::findItem(std::string id) const { return found->second; } -ItemDef* Content::requireItem(std::string id) const { +ItemDef& Content::requireItem(std::string id) const { auto found = itemDefs.find(id); if (found == itemDefs.end()) { throw std::runtime_error("missing item "+id); } - return found->second; + return *found->second; +} + +const std::vector>& Content::getPacks() const { + return packs; } diff --git a/src/content/Content.h b/src/content/Content.h index dc865ff2..fa0e9c53 100644 --- a/src/content/Content.h +++ b/src/content/Content.h @@ -9,11 +9,12 @@ #include #include "../typedefs.h" -typedef std::set DrawGroups; +using DrawGroups = std::set; class Block; class ItemDef; class Content; +class ContentPackRuntime; enum class contenttype { none, block, item @@ -46,14 +47,17 @@ class ContentBuilder { std::unordered_map itemDefs; std::vector itemIds; + + std::vector> packs; public: ~ContentBuilder(); void add(Block* def); void add(ItemDef* def); + void add(ContentPackRuntime* pack); - Block* createBlock(std::string id); - ItemDef* createItem(std::string id); + Block& createBlock(std::string id); + ItemDef& createItem(std::string id); void checkIdentifier(std::string id); contenttype checkContentType(std::string id); @@ -104,12 +108,15 @@ class Content { std::unordered_map blockDefs; std::unordered_map itemDefs; std::unique_ptr indices; + std::vector> packs; public: - DrawGroups* const drawGroups; + std::unique_ptr const drawGroups; - Content(ContentIndices* indices, DrawGroups* drawGroups, + Content(ContentIndices* indices, + std::unique_ptr drawGroups, std::unordered_map blockDefs, - std::unordered_map itemDefs); + std::unordered_map itemDefs, + std::vector> packs); ~Content(); inline ContentIndices* getIndices() const { @@ -117,10 +124,12 @@ public: } Block* findBlock(std::string id) const; - Block* requireBlock(std::string id) const; + Block& requireBlock(std::string id) const; ItemDef* findItem(std::string id) const; - ItemDef* requireItem(std::string id) const; + ItemDef& requireItem(std::string id) const; + + const std::vector>& getPacks() const; }; #endif // CONTENT_CONTENT_H_ \ No newline at end of file diff --git a/src/content/ContentLoader.cpp b/src/content/ContentLoader.cpp index 75af2fcd..00a325d8 100644 --- a/src/content/ContentLoader.cpp +++ b/src/content/ContentLoader.cpp @@ -74,7 +74,7 @@ void ContentLoader::fixPackIndices() { std::unique_ptr root; if (fs::is_regular_file(indexFile)) { - root = std::move(files::read_json(indexFile)); + root = files::read_json(indexFile); } else { root.reset(new dynamic::Map()); } @@ -92,7 +92,7 @@ void ContentLoader::fixPackIndices() { } // TODO: add basic validation and logging -void ContentLoader::loadBlock(Block* def, std::string name, fs::path file) { +void ContentLoader::loadBlock(Block& def, std::string name, fs::path file) { auto root = files::read_json(file); // block texturing @@ -100,22 +100,22 @@ void ContentLoader::loadBlock(Block* def, std::string name, fs::path file) { std::string texture; root->str("texture", texture); for (uint i = 0; i < 6; i++) { - def->textureFaces[i] = texture; + def.textureFaces[i] = texture; } } else if (root->has("texture-faces")) { auto texarr = root->list("texture-faces"); for (uint i = 0; i < 6; i++) { - def->textureFaces[i] = texarr->str(i); + def.textureFaces[i] = texarr->str(i); } } // block model std::string model = "block"; root->str("model", model); - if (model == "block") def->model = BlockModel::block; - else if (model == "aabb") def->model = BlockModel::aabb; + if (model == "block") def.model = BlockModel::block; + else if (model == "aabb") def.model = BlockModel::aabb; else if (model == "custom") { - def->model = BlockModel::custom; + def.model = BlockModel::custom; if (root->has("model-primitives")) { loadCustomBlockModel(def, root->map("model-primitives")); } @@ -124,30 +124,30 @@ void ContentLoader::loadBlock(Block* def, std::string name, fs::path file) { << name << " parsed: no \"model-primitives\" found" << std::endl; } } - else if (model == "X") def->model = BlockModel::xsprite; - else if (model == "none") def->model = BlockModel::none; + else if (model == "X") def.model = BlockModel::xsprite; + else if (model == "none") def.model = BlockModel::none; else { std::cerr << "unknown model " << model << std::endl; - def->model = BlockModel::none; + def.model = BlockModel::none; } // rotation profile std::string profile = "none"; root->str("rotation", profile); - def->rotatable = profile != "none"; + def.rotatable = profile != "none"; if (profile == "pipe") { - def->rotations = BlockRotProfile::PIPE; + def.rotations = BlockRotProfile::PIPE; } else if (profile == "pane") { - def->rotations = BlockRotProfile::PANE; + def.rotations = BlockRotProfile::PANE; } else if (profile != "none") { std::cerr << "unknown rotation profile " << profile << std::endl; - def->rotatable = false; + def.rotatable = false; } // block hitbox AABB [x, y, z, width, height, depth] auto boxarr = root->list("hitbox"); if (boxarr) { - AABB& aabb = def->hitbox; + AABB& aabb = def.hitbox; aabb.a = glm::vec3(boxarr->num(0), boxarr->num(1), boxarr->num(2)); aabb.b = glm::vec3(boxarr->num(3), boxarr->num(4), boxarr->num(5)); aabb.b += aabb.a; @@ -156,26 +156,28 @@ void ContentLoader::loadBlock(Block* def, std::string name, fs::path file) { // block light emission [r, g, b] where r,g,b in range [0..15] auto emissionarr = root->list("emission"); if (emissionarr) { - def->emission[0] = emissionarr->num(0); - def->emission[1] = emissionarr->num(1); - def->emission[2] = emissionarr->num(2); + def.emission[0] = emissionarr->num(0); + def.emission[1] = emissionarr->num(1); + def.emission[2] = emissionarr->num(2); } // primitive properties - root->flag("obstacle", def->obstacle); - root->flag("replaceable", def->replaceable); - root->flag("light-passing", def->lightPassing); - root->flag("breakable", def->breakable); - root->flag("selectable", def->selectable); - root->flag("grounded", def->grounded); - root->flag("hidden", def->hidden); - root->flag("sky-light-passing", def->skyLightPassing); - root->num("draw-group", def->drawGroup); - root->str("picking-item", def->pickingItem); - root->str("script-name", def->scriptName); + root->flag("obstacle", def.obstacle); + root->flag("replaceable", def.replaceable); + root->flag("light-passing", def.lightPassing); + root->flag("breakable", def.breakable); + root->flag("selectable", def.selectable); + root->flag("grounded", def.grounded); + root->flag("hidden", def.hidden); + root->flag("sky-light-passing", def.skyLightPassing); + root->num("draw-group", def.drawGroup); + root->str("picking-item", def.pickingItem); + root->str("script-name", def.scriptName); + root->str("ui-layout", def.uiLayout); + root->num("inventory-size", def.inventorySize); } -void ContentLoader::loadCustomBlockModel(Block* def, dynamic::Map* primitives) { +void ContentLoader::loadCustomBlockModel(Block& def, dynamic::Map* primitives) { if (primitives->has("aabbs")) { auto modelboxes = primitives->list("aabbs"); for (uint i = 0; i < modelboxes->size(); i++ ) { @@ -185,19 +187,19 @@ void ContentLoader::loadCustomBlockModel(Block* def, dynamic::Map* primitives) { modelbox.a = glm::vec3(boxarr->num(0), boxarr->num(1), boxarr->num(2)); modelbox.b = glm::vec3(boxarr->num(3), boxarr->num(4), boxarr->num(5)); modelbox.b += modelbox.a; - def->modelBoxes.push_back(modelbox); + def.modelBoxes.push_back(modelbox); if (boxarr->size() == 7) for (uint i = 6; i < 12; i++) { - def->modelTextures.push_back(boxarr->str(6)); + def.modelTextures.push_back(boxarr->str(6)); } else if (boxarr->size() == 12) for (uint i = 6; i < 12; i++) { - def->modelTextures.push_back(boxarr->str(i)); + def.modelTextures.push_back(boxarr->str(i)); } else for (uint i = 6; i < 12; i++) { - def->modelTextures.push_back("notfound"); + def.modelTextures.push_back("notfound"); } } } @@ -209,77 +211,81 @@ void ContentLoader::loadCustomBlockModel(Block* def, dynamic::Map* primitives) { glm::vec3 p1(tgonobj->num(0), tgonobj->num(1), tgonobj->num(2)), xw(tgonobj->num(3), tgonobj->num(4), tgonobj->num(5)), yh(tgonobj->num(6), tgonobj->num(7), tgonobj->num(8)); - def->modelExtraPoints.push_back(p1); - def->modelExtraPoints.push_back(p1+xw); - def->modelExtraPoints.push_back(p1+xw+yh); - def->modelExtraPoints.push_back(p1+yh); + def.modelExtraPoints.push_back(p1); + def.modelExtraPoints.push_back(p1+xw); + def.modelExtraPoints.push_back(p1+xw+yh); + def.modelExtraPoints.push_back(p1+yh); - def->modelTextures.push_back(tgonobj->str(9)); + def.modelTextures.push_back(tgonobj->str(9)); } } } -void ContentLoader::loadItem(ItemDef* def, std::string name, fs::path file) { +void ContentLoader::loadItem(ItemDef& def, std::string name, fs::path file) { auto root = files::read_json(file); std::string iconTypeStr = ""; root->str("icon-type", iconTypeStr); if (iconTypeStr == "none") { - def->iconType = item_icon_type::none; + def.iconType = item_icon_type::none; } else if (iconTypeStr == "block") { - def->iconType = item_icon_type::block; + def.iconType = item_icon_type::block; } else if (iconTypeStr == "sprite") { - def->iconType = item_icon_type::sprite; + def.iconType = item_icon_type::sprite; } else if (iconTypeStr.length()){ std::cerr << "unknown icon type" << iconTypeStr << std::endl; } - root->str("icon", def->icon); - root->str("placing-block", def->placingBlock); - root->str("script-name", def->scriptName); - root->num("stack-size", def->stackSize); + root->str("icon", def.icon); + root->str("placing-block", def.placingBlock); + root->str("script-name", def.scriptName); + root->num("stack-size", def.stackSize); // item light emission [r, g, b] where r,g,b in range [0..15] auto emissionarr = root->list("emission"); if (emissionarr) { - def->emission[0] = emissionarr->num(0); - def->emission[1] = emissionarr->num(1); - def->emission[2] = emissionarr->num(2); + def.emission[0] = emissionarr->num(0); + def.emission[1] = emissionarr->num(1); + def.emission[2] = emissionarr->num(2); } } -void ContentLoader::loadBlock(Block* def, std::string full, std::string name) { +void ContentLoader::loadBlock(Block& def, std::string full, std::string name) { auto folder = pack->folder; fs::path configFile = folder/fs::path("blocks/"+name+".json"); loadBlock(def, full, configFile); - fs::path scriptfile = folder/fs::path("scripts/"+def->scriptName+".lua"); + fs::path scriptfile = folder/fs::path("scripts/"+def.scriptName+".lua"); if (fs::is_regular_file(scriptfile)) { - scripting::load_block_script(full, scriptfile, &def->rt.funcsset); + scripting::load_block_script(env, full, scriptfile, def.rt.funcsset); } } -void ContentLoader::loadItem(ItemDef* def, std::string full, std::string name) { +void ContentLoader::loadItem(ItemDef& def, std::string full, std::string name) { auto folder = pack->folder; fs::path configFile = folder/fs::path("items/"+name+".json"); loadItem(def, full, configFile); - fs::path scriptfile = folder/fs::path("scripts/"+def->scriptName+".lua"); + fs::path scriptfile = folder/fs::path("scripts/"+def.scriptName+".lua"); if (fs::is_regular_file(scriptfile)) { - scripting::load_item_script(full, scriptfile, &def->rt.funcsset); + scripting::load_item_script(env, full, scriptfile, def.rt.funcsset); } } -void ContentLoader::load(ContentBuilder* builder) { +void ContentLoader::load(ContentBuilder& builder) { std::cout << "-- loading pack [" << pack->id << "]" << std::endl; + auto runtime = new ContentPackRuntime(*pack, scripting::create_pack_environment(*pack)); + builder.add(runtime); + env = runtime->getEnvironment()->getId(); + fixPackIndices(); auto folder = pack->folder; fs::path scriptFile = folder/fs::path("scripts/world.lua"); if (fs::is_regular_file(scriptFile)) { - scripting::load_world_script(pack->id, scriptFile); + scripting::load_world_script(env, pack->id, scriptFile); } if (!fs::is_regular_file(pack->getContentFile())) @@ -290,17 +296,17 @@ void ContentLoader::load(ContentBuilder* builder) { for (uint i = 0; i < blocksarr->size(); i++) { std::string name = blocksarr->str(i); std::string full = pack->id+":"+name; - auto def = builder->createBlock(full); + auto& def = builder.createBlock(full); loadBlock(def, full, name); - if (!def->hidden) { - auto item = builder->createItem(full+BLOCK_ITEM_SUFFIX); - item->generated = true; - item->iconType = item_icon_type::block; - item->icon = full; - item->placingBlock = full; + if (!def.hidden) { + auto& item = builder.createItem(full+BLOCK_ITEM_SUFFIX); + item.generated = true; + item.iconType = item_icon_type::block; + item.icon = full; + item.placingBlock = full; for (uint j = 0; j < 4; j++) { - item->emission[j] = def->emission[j]; + item.emission[j] = def.emission[j]; } } } @@ -311,7 +317,7 @@ void ContentLoader::load(ContentBuilder* builder) { for (uint i = 0; i < itemsarr->size(); i++) { std::string name = itemsarr->str(i); std::string full = pack->id+":"+name; - loadItem(builder->createItem(full), full, name); + loadItem(builder.createItem(full), full, name); } } } diff --git a/src/content/ContentLoader.h b/src/content/ContentLoader.h index e8517d53..2606e71d 100644 --- a/src/content/ContentLoader.h +++ b/src/content/ContentLoader.h @@ -8,7 +8,7 @@ namespace fs = std::filesystem; class Block; class ItemDef; -class ContentPack; +struct ContentPack; class ContentBuilder; namespace dynamic { @@ -17,10 +17,11 @@ namespace dynamic { class ContentLoader { const ContentPack* pack; + int env = 0; - void loadBlock(Block* def, std::string full, std::string name); - void loadCustomBlockModel(Block* def, dynamic::Map* primitives); - void loadItem(ItemDef* def, std::string full, std::string name); + void loadBlock(Block& def, std::string full, std::string name); + void loadCustomBlockModel(Block& def, dynamic::Map* primitives); + void loadItem(ItemDef& def, std::string full, std::string name); public: ContentLoader(ContentPack* pack); @@ -28,9 +29,9 @@ public: dynamic::Map* indicesRoot, std::string contentSection); void fixPackIndices(); - void loadBlock(Block* def, std::string name, fs::path file); - void loadItem(ItemDef* def, std::string name, fs::path file); - void load(ContentBuilder* builder); + void loadBlock(Block& def, std::string name, fs::path file); + void loadItem(ItemDef& def, std::string name, fs::path file); + void load(ContentBuilder& builder); }; #endif // CONTENT_CONTENT_LOADER_H_ diff --git a/src/content/ContentPack.cpp b/src/content/ContentPack.cpp index 087eb6cb..4266f867 100644 --- a/src/content/ContentPack.cpp +++ b/src/content/ContentPack.cpp @@ -7,6 +7,7 @@ #include "../files/files.h" #include "../files/engine_paths.h" #include "../data/dynamic.h" +#include "../logic/scripting/scripting.h" namespace fs = std::filesystem; @@ -86,25 +87,43 @@ ContentPack ContentPack::read(fs::path folder) { return pack; } -void ContentPack::scan(fs::path rootfolder, - std::vector& packs) { - if (!fs::is_directory(rootfolder)) { +void ContentPack::scanFolder( + fs::path folder, + std::vector& packs +) { + if (!fs::is_directory(folder)) { return; } - for (auto entry : fs::directory_iterator(rootfolder)) { + for (auto entry : fs::directory_iterator(folder)) { fs::path folder = entry.path(); if (!fs::is_directory(folder)) continue; if (!is_pack(folder)) continue; - packs.push_back(read(folder)); + try { + packs.push_back(read(folder)); + } catch (const contentpack_error& err) { + std::cerr << "package.json error at " << err.getFolder().u8string(); + std::cerr << ": " << err.what() << std::endl; + } catch (const std::runtime_error& err) { + std::cerr << err.what() << std::endl; + } } } +void ContentPack::scan( + fs::path rootfolder, + EnginePaths* paths, + std::vector& packs +) { + scanFolder(paths->getResources()/fs::path("content"), packs); + scanFolder(paths->getUserfiles()/fs::path("content"), packs); + scanFolder(rootfolder, packs); +} + void ContentPack::scan(EnginePaths* paths, std::vector& packs) { - scan(paths->getResources()/fs::path("content"), packs); - scan(paths->getWorldFolder()/fs::path("content"), packs); + scan(paths->getWorldFolder()/fs::path("content"), paths, packs); } std::vector ContentPack::worldPacksList(fs::path folder) { @@ -122,6 +141,10 @@ fs::path ContentPack::findPack(const EnginePaths* paths, fs::path worldDir, std: if (fs::is_directory(folder)) { return folder; } + folder = paths->getUserfiles() / fs::path("content") / fs::path(name); + if (fs::is_directory(folder)) { + return folder; + } folder = paths->getResources() / fs::path("content") / fs::path(name); if (fs::is_directory(folder)) { return folder; @@ -142,3 +165,10 @@ void ContentPack::readPacks(const EnginePaths* paths, packs.push_back(ContentPack::read(packfolder)); } } + +ContentPackRuntime::ContentPackRuntime( + ContentPack info, + std::unique_ptr env +) : info(info), env(std::move(env)) +{ +} diff --git a/src/content/ContentPack.h b/src/content/ContentPack.h index 6f82519a..952dc65b 100644 --- a/src/content/ContentPack.h +++ b/src/content/ContentPack.h @@ -8,16 +8,22 @@ class EnginePaths; +namespace fs = std::filesystem; + +namespace scripting { + class Environment; +} + class contentpack_error : public std::runtime_error { std::string packId; - std::filesystem::path folder; + fs::path folder; public: contentpack_error(std::string packId, - std::filesystem::path folder, + fs::path folder, std::string message); std::string getPackId() const; - std::filesystem::path getFolder() const; + fs::path getFolder() const; }; struct ContentPack { @@ -26,35 +32,71 @@ struct ContentPack { std::string version = "0.0"; std::string creator = ""; std::string description = "no description"; - std::filesystem::path folder; + fs::path folder; std::vector dependencies; - std::filesystem::path getContentFile() const; + fs::path getContentFile() const; static const std::string PACKAGE_FILENAME; static const std::string CONTENT_FILENAME; - static const std::filesystem::path BLOCKS_FOLDER; - static const std::filesystem::path ITEMS_FOLDER; + static const fs::path BLOCKS_FOLDER; + static const fs::path ITEMS_FOLDER; static const std::vector RESERVED_NAMES; - static bool is_pack(std::filesystem::path folder); - static ContentPack read(std::filesystem::path folder); + static bool is_pack(fs::path folder); + static ContentPack read(fs::path folder); - static void scan(std::filesystem::path folder, - std::vector& packs); - static void scan(EnginePaths* paths, - std::vector& packs); + static void scanFolder( + fs::path folder, + std::vector& packs + ); + + static void scan( + fs::path folder, + EnginePaths* paths, + std::vector& packs + ); + static void scan( + EnginePaths* paths, + std::vector& packs + ); - static std::vector worldPacksList(std::filesystem::path folder); + static std::vector worldPacksList(fs::path folder); - static std::filesystem::path findPack( + static fs::path findPack( const EnginePaths* paths, - std::filesystem::path worldDir, - std::string name); - static void readPacks(const EnginePaths* paths, - std::vector& packs, - const std::vector& names, - std::filesystem::path worldDir); + fs::path worldDir, + std::string name + ); + + static void readPacks( + const EnginePaths* paths, + std::vector& packs, + const std::vector& names, + fs::path worldDir + ); +}; + +class ContentPackRuntime { + ContentPack info; + std::unique_ptr env; +public: + ContentPackRuntime( + ContentPack info, + std::unique_ptr env + ); + + inline const std::string& getId() { + return info.id; + } + + inline const ContentPack& getInfo() const { + return info; + } + + inline scripting::Environment* getEnvironment() const { + return env.get(); + } }; #endif // CONTENT_CONTENT_PACK_H_ diff --git a/src/definitions.cpp b/src/core_defs.cpp similarity index 69% rename from src/definitions.cpp rename to src/core_defs.cpp index a9243ee4..adf3c938 100644 --- a/src/definitions.cpp +++ b/src/core_defs.cpp @@ -1,48 +1,43 @@ -#include "definitions.h" - -#include - -#include "items/ItemDef.h" -#include "content/Content.h" -#include "window/Window.h" -#include "window/Events.h" -#include "window/input.h" -#include "voxels/Block.h" - -using glm::vec3; - -// All in-game definitions (blocks, items, etc..) -void setup_definitions(ContentBuilder* builder) { // Strange function, need to REDO ? - Block* block = new Block("core:air", "air"); - block->replaceable = true; - block->drawGroup = 1; - block->lightPassing = true; - block->skyLightPassing = true; - block->obstacle = false; - block->selectable = false; - block->model = BlockModel::none; - block->pickingItem = "core:empty"; - builder->add(block); - - ItemDef* item = builder->createItem("core:empty"); - item->iconType = item_icon_type::none; -} - -void setup_bindings() { - Events::bind(BIND_MOVE_FORWARD, inputtype::keyboard, keycode::W); - Events::bind(BIND_MOVE_BACK, inputtype::keyboard, keycode::S); - Events::bind(BIND_MOVE_RIGHT, inputtype::keyboard, keycode::D); - Events::bind(BIND_MOVE_LEFT, inputtype::keyboard, keycode::A); - Events::bind(BIND_MOVE_JUMP, inputtype::keyboard, keycode::SPACE); - Events::bind(BIND_MOVE_SPRINT, inputtype::keyboard, keycode::LEFT_CONTROL); - Events::bind(BIND_MOVE_CROUCH, inputtype::keyboard, keycode::LEFT_SHIFT); - Events::bind(BIND_MOVE_CHEAT, inputtype::keyboard, keycode::R); - Events::bind(BIND_CAM_ZOOM, inputtype::keyboard, keycode::C); - Events::bind(BIND_CAM_MODE, inputtype::keyboard, keycode::F4); - Events::bind(BIND_PLAYER_NOCLIP, inputtype::keyboard, keycode::N); - Events::bind(BIND_PLAYER_FLIGHT, inputtype::keyboard, keycode::F); - Events::bind(BIND_PLAYER_ATTACK, inputtype::mouse, mousecode::BUTTON_1); - Events::bind(BIND_PLAYER_BUILD, inputtype::mouse, mousecode::BUTTON_2); - Events::bind(BIND_PLAYER_PICK, inputtype::mouse, mousecode::BUTTON_3); - Events::bind(BIND_HUD_INVENTORY, inputtype::keyboard, keycode::TAB); +#include "core_defs.h" + +#include "items/ItemDef.h" +#include "content/Content.h" +#include "window/Window.h" +#include "window/Events.h" +#include "window/input.h" +#include "voxels/Block.h" + +// All in-game definitions (blocks, items, etc..) +void corecontent::setup(ContentBuilder* builder) { + Block& block = builder->createBlock("core:air"); + block.replaceable = true; + block.drawGroup = 1; + block.lightPassing = true; + block.skyLightPassing = true; + block.obstacle = false; + block.selectable = false; + block.model = BlockModel::none; + block.pickingItem = "core:empty"; + + ItemDef& item = builder->createItem("core:empty"); + item.iconType = item_icon_type::none; +} + +void corecontent::setup_bindings() { + Events::bind(BIND_MOVE_FORWARD, inputtype::keyboard, keycode::W); + Events::bind(BIND_MOVE_BACK, inputtype::keyboard, keycode::S); + Events::bind(BIND_MOVE_RIGHT, inputtype::keyboard, keycode::D); + Events::bind(BIND_MOVE_LEFT, inputtype::keyboard, keycode::A); + Events::bind(BIND_MOVE_JUMP, inputtype::keyboard, keycode::SPACE); + Events::bind(BIND_MOVE_SPRINT, inputtype::keyboard, keycode::LEFT_CONTROL); + Events::bind(BIND_MOVE_CROUCH, inputtype::keyboard, keycode::LEFT_SHIFT); + Events::bind(BIND_MOVE_CHEAT, inputtype::keyboard, keycode::R); + Events::bind(BIND_CAM_ZOOM, inputtype::keyboard, keycode::C); + Events::bind(BIND_CAM_MODE, inputtype::keyboard, keycode::F4); + Events::bind(BIND_PLAYER_NOCLIP, inputtype::keyboard, keycode::N); + Events::bind(BIND_PLAYER_FLIGHT, inputtype::keyboard, keycode::F); + Events::bind(BIND_PLAYER_ATTACK, inputtype::mouse, mousecode::BUTTON_1); + Events::bind(BIND_PLAYER_BUILD, inputtype::mouse, mousecode::BUTTON_2); + Events::bind(BIND_PLAYER_PICK, inputtype::mouse, mousecode::BUTTON_3); + Events::bind(BIND_HUD_INVENTORY, inputtype::keyboard, keycode::TAB); } \ No newline at end of file diff --git a/src/core_defs.h b/src/core_defs.h index 5cdfbf2b..a813c133 100644 --- a/src/core_defs.h +++ b/src/core_defs.h @@ -3,7 +3,6 @@ #include - const std::string TEXTURE_NOTFOUND = "notfound"; /* bindings used in engine code */ @@ -24,4 +23,11 @@ const std::string BIND_PLAYER_BUILD = "player.build"; const std::string BIND_PLAYER_PICK = "player.pick"; const std::string BIND_HUD_INVENTORY = "hud.inventory"; +class ContentBuilder; + +namespace corecontent { + extern void setup_bindings(); + extern void setup(ContentBuilder* builder); +} + #endif // SRC_CORE_DEFS_H_ \ No newline at end of file diff --git a/src/definitions.h b/src/definitions.h deleted file mode 100644 index 31dc982c..00000000 --- a/src/definitions.h +++ /dev/null @@ -1,13 +0,0 @@ -#ifndef DECLARATIONS_H -#define DECLARATIONS_H - -#include -#include "core_defs.h" - -class ContentBuilder; - -extern void setup_bindings(); -extern void setup_definitions(ContentBuilder* builder); - -#endif // DECLARATIONS_H - diff --git a/src/delegates.h b/src/delegates.h new file mode 100644 index 00000000..debb7f59 --- /dev/null +++ b/src/delegates.h @@ -0,0 +1,23 @@ +#ifndef DELEGATES_H_ +#define DELEGATES_H_ + +#include +#include +#include + +using runnable = std::function; + +// data sources +using wstringsupplier = std::function; +using doublesupplier = std::function; +using boolsupplier = std::function; +using vec2supplier = std::function; + +using stringconsumer = std::function; +using wstringconsumer = std::function; +using doubleconsumer = std::function; +using boolconsumer = std::function; +using int_array_consumer = std::function; +using wstringchecker = std::function; + +#endif // DELEGATES_H_ diff --git a/src/engine.cpp b/src/engine.cpp index ac6958c6..80d9114a 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -17,6 +17,7 @@ #include "window/Camera.h" #include "window/input.h" #include "graphics/Batch2D.h" +#include "graphics/GfxContext.h" #include "graphics/Shader.h" #include "graphics/ImageData.h" #include "frontend/gui/GUI.h" @@ -36,12 +37,13 @@ #include "frontend/locale/langs.h" #include "logic/scripting/scripting.h" -#include "definitions.h" +#include "core_defs.h" namespace fs = std::filesystem; Engine::Engine(EngineSettings& settings, EnginePaths* paths) - : settings(settings), paths(paths) { + : settings(settings), paths(paths) +{ if (Window::initialize(settings.display)){ throw initialize_error("could not initialize window"); } @@ -51,18 +53,21 @@ Engine::Engine(EngineSettings& settings, EnginePaths* paths) std::cout << "-- loading assets" << std::endl; std::vector roots {resdir}; - resPaths.reset(new ResPaths(resdir, roots)); - assets.reset(new Assets()); + + resPaths = std::make_unique(resdir, roots); + assets = std::make_unique(); + + AssetsLoader loader(assets.get(), resPaths.get()); - AssetsLoader::createDefaults(loader); - AssetsLoader::addDefaults(loader, true); + AssetsLoader::addDefaults(loader, nullptr); Shader::preprocessor->setPaths(resPaths.get()); while (loader.hasNext()) { if (!loader.loadNext()) { assets.reset(); + scripting::close(); Window::terminate(); - throw initialize_error("could not to initialize assets"); + throw initialize_error("could not to load assets"); } } @@ -72,7 +77,6 @@ Engine::Engine(EngineSettings& settings, EnginePaths* paths) settings.ui.language = langs::locale_by_envlocale(platform::detect_locale(), paths->getResources()); } setLanguage(settings.ui.language); - std::cout << "-- initializing finished" << std::endl; } void Engine::updateTimers() { @@ -97,12 +101,11 @@ void Engine::updateHotkeys() { void Engine::mainloop() { setScreen(std::make_shared(this)); - - std::cout << "-- preparing systems" << std::endl; Batch2D batch(1024); lastTime = Window::time(); + std::cout << "-- initialized" << std::endl; while (!Window::isShouldClose()){ assert(screen != nullptr); updateTimers(); @@ -113,7 +116,11 @@ void Engine::mainloop() { if (!Window::isIconified()) { screen->draw(delta); - gui->draw(&batch, assets.get()); + + Viewport viewport(Window::width, Window::height); + GfxContext ctx(nullptr, viewport, &batch); + gui->draw(&ctx, assets.get()); + Window::swapInterval(settings.display.swapInterval); } else { Window::swapInterval(1); @@ -124,13 +131,13 @@ void Engine::mainloop() { } Engine::~Engine() { - screen = nullptr; - scripting::close(); + std::cout << "-- shutting down" << std::endl; + screen.reset(); + content.reset(); Audio::finalize(); - - std::cout << "-- shutting down" << std::endl; assets.reset(); + scripting::close(); Window::terminate(); std::cout << "-- engine finished" << std::endl; } @@ -143,7 +150,7 @@ inline const std::string checkPacks(const std::unordered_set& packs void Engine::loadContent() { auto resdir = paths->getResources(); ContentBuilder contentBuilder; - setup_definitions(&contentBuilder); + corecontent::setup(&contentBuilder); paths->setContentPacks(&contentPacks); std::vector resRoots; @@ -158,13 +165,14 @@ void Engine::loadContent() { for (auto& pack : srcPacks) { if(loadedPacks.find(pack.id) != loadedPacks.end()) continue; missingDependency = checkPacks(existingPacks, pack.dependencies); - if(!missingDependency.empty()) throw contentpack_error(pack.id, pack.folder, "missing dependency '"+missingDependency+"'"); + if(!missingDependency.empty()) + throw contentpack_error(pack.id, pack.folder, "missing dependency '"+missingDependency+"'"); if(pack.dependencies.empty() || checkPacks(loadedPacks, pack.dependencies).empty()) { loadedPacks.insert(pack.id); resRoots.push_back(pack.folder); contentPacks.push_back(pack); ContentLoader loader(&pack); - loader.load(&contentBuilder); + loader.load(contentBuilder); } } } @@ -177,8 +185,7 @@ void Engine::loadContent() { std::unique_ptr new_assets(new Assets()); std::cout << "-- loading assets" << std::endl; AssetsLoader loader(new_assets.get(), resPaths.get()); - AssetsLoader::createDefaults(loader); - AssetsLoader::addDefaults(loader, false); + AssetsLoader::addDefaults(loader, content.get()); while (loader.hasNext()) { if (!loader.loadNext()) { new_assets.reset(); @@ -191,7 +198,6 @@ void Engine::loadContent() { void Engine::loadWorldContent(const fs::path& folder) { contentPacks.clear(); auto packNames = ContentPack::worldPacksList(folder); - std::cout << folder << " " << packNames.size() << std::endl; ContentPack::readPacks(paths, contentPacks, packNames, folder); loadContent(); } @@ -202,6 +208,10 @@ void Engine::loadAllPacks() { ContentPack::scan(paths, contentPacks); } +double Engine::getDelta() const { + return delta; +} + void Engine::setScreen(std::shared_ptr screen) { this->screen = screen; } @@ -209,7 +219,7 @@ void Engine::setScreen(std::shared_ptr screen) { void Engine::setLanguage(std::string locale) { settings.ui.language = locale; langs::setup(paths->getResources(), locale, contentPacks); - menus::create_menus(this, gui->getMenu()); + menus::create_menus(this); } gui::GUI* Engine::getGUI() { @@ -238,4 +248,4 @@ EnginePaths* Engine::getPaths() { std::shared_ptr Engine::getScreen() { return screen; -} \ No newline at end of file +} diff --git a/src/engine.h b/src/engine.h index 595b80da..6e986e85 100644 --- a/src/engine.h +++ b/src/engine.h @@ -44,27 +44,76 @@ class Engine { double delta = 0.0; std::unique_ptr gui; + + void updateTimers(); + void updateHotkeys(); public: Engine(EngineSettings& settings, EnginePaths* paths); ~Engine(); - void updateTimers(); - void updateHotkeys(); + /** + * Start main engine input/update/render loop + * Automatically sets MenuScreen + */ void mainloop(); - Assets* getAssets(); - gui::GUI* getGUI(); - EngineSettings& getSettings(); - void setScreen(std::shared_ptr screen); - EnginePaths* getPaths(); - const Content* getContent() const; - std::vector& getContentPacks(); + /** + * Set screen (scene). + * nullptr may be used to delete previous screen before creating new one + * example: + * + * engine->setScreen(nullptr); + * engine->setScreen(std::make_shared<...>(...)); + * + * not-null value must be set before next frame + */ + void setScreen(std::shared_ptr screen); + + /** + * Change locale to specified + * @param locale isolanguage_ISOCOUNTRY (example: en_US) + */ void setLanguage(std::string locale); + + /** + * Load all selected content-packs and reload assets + */ void loadContent(); + /** + * Collect world content-packs and load content + * @see loadContent + * @param folder world folder + */ void loadWorldContent(const fs::path& folder); + + /** + * Collect all available content-packs from res/content + */ void loadAllPacks(); + /** Get current frame delta-time */ + double getDelta() const; + + /** Get active assets storage instance */ + Assets* getAssets(); + + /** Get main UI controller */ + gui::GUI* getGUI(); + + /** Get writeable engine settings structure instance */ + EngineSettings& getSettings(); + + /** Get engine filesystem paths source */ + EnginePaths* getPaths(); + + /** Get current Content instance */ + const Content* getContent() const; + + /** Get selected content packs */ + std::vector& getContentPacks(); + + /** Get current screen */ std::shared_ptr getScreen(); }; -#endif // SRC_ENGINE_H_ \ No newline at end of file +#endif // SRC_ENGINE_H_ diff --git a/src/files/WorldFiles.cpp b/src/files/WorldFiles.cpp index 6616d54d..62b79a41 100644 --- a/src/files/WorldFiles.cpp +++ b/src/files/WorldFiles.cpp @@ -13,6 +13,7 @@ #include "../world/World.h" #include "../lighting/Lightmap.h" +#include "../coders/byte_utils.h" #include "../util/data_io.h" #include "../coders/json.h" #include "../constants.h" @@ -28,6 +29,8 @@ #include #include +const size_t BUFFER_SIZE_UNKNOWN = -1; + regfile::regfile(fs::path filename) : file(filename) { if (file.length() < REGION_HEADER_SIZE) throw std::runtime_error("incomplete region file header"); @@ -189,7 +192,7 @@ void WorldFiles::put(Chunk* chunk){ int localX = chunk->x - (regionX * REGION_SIZE); int localZ = chunk->z - (regionZ * REGION_SIZE); - /* Writing Voxels */ { + /* Writing voxels */ { size_t compressedSize; std::unique_ptr chunk_data (chunk->encode()); ubyte* data = compress(chunk_data.get(), CHUNK_DATA_LEN, compressedSize); @@ -198,15 +201,39 @@ void WorldFiles::put(Chunk* chunk){ region->setUnsaved(true); region->put(localX, localZ, data, compressedSize); } + /* Writing lights cache */ if (doWriteLights && chunk->isLighted()) { size_t compressedSize; - std::unique_ptr light_data (chunk->lightmap->encode()); + std::unique_ptr light_data (chunk->lightmap.encode()); ubyte* data = compress(light_data.get(), LIGHTMAP_DATA_LEN, compressedSize); WorldRegion* region = getOrCreateRegion(lights, regionX, regionZ); region->setUnsaved(true); region->put(localX, localZ, data, compressedSize); } + /* Writing block inventories */ + if (!chunk->inventories.empty()){ + auto& inventories = chunk->inventories; + ByteBuilder builder; + builder.putInt32(inventories.size()); + for (auto& entry : inventories) { + builder.putInt32(entry.first); + auto map = entry.second->serialize(); + auto bytes = json::to_binary(map.get(), true); + builder.putInt32(bytes.size()); + builder.put(bytes.data(), bytes.size()); + } + WorldRegion* region = getOrCreateRegion(storages, regionX, regionZ); + region->setUnsaved(true); + + auto datavec = builder.data(); + uint datasize = builder.size(); + auto data = std::make_unique(datasize); + for (uint i = 0; i < datasize; i++) { + data[i] = datavec[i]; + } + region->put(localX, localZ, data.release(), datasize); + } } fs::path WorldFiles::getRegionsFolder() const { @@ -217,6 +244,10 @@ fs::path WorldFiles::getLightsFolder() const { return directory/fs::path("lights"); } +fs::path WorldFiles::getInventoriesFolder() const { + return directory/fs::path("inventories"); +} + fs::path WorldFiles::getRegionFilename(int x, int z) const { return fs::path(std::to_string(x) + "_" + std::to_string(z) + ".bin"); } @@ -260,20 +291,39 @@ fs::path WorldFiles::getPacksFile() const { } ubyte* WorldFiles::getChunk(int x, int z){ - return getData(regions, getRegionsFolder(), x, z, REGION_LAYER_VOXELS); + return getData(regions, getRegionsFolder(), x, z, REGION_LAYER_VOXELS, true); } /* Get cached lights for chunk at x,z * @return lights data or nullptr */ light_t* WorldFiles::getLights(int x, int z) { - std::unique_ptr data (getData(lights, getLightsFolder(), x, z, REGION_LAYER_LIGHTS)); + std::unique_ptr data (getData(lights, getLightsFolder(), x, z, REGION_LAYER_LIGHTS, true)); if (data == nullptr) return nullptr; return Lightmap::decode(data.get()); } +chunk_inventories_map WorldFiles::fetchInventories(int x, int z) { + chunk_inventories_map inventories; + const ubyte* data = getData(storages, getInventoriesFolder(), x, z, REGION_LAYER_INVENTORIES, false); + if (data == nullptr) + return inventories; + ByteReader reader(data, BUFFER_SIZE_UNKNOWN); + int count = reader.getInt32(); + for (int i = 0; i < count; i++) { + uint index = reader.getInt32(); + uint size = reader.getInt32(); + auto map = json::from_binary(reader.pointer(), size); + reader.skip(size); + auto inv = std::make_shared(0, 0); + inv->deserialize(map.get()); + inventories[index] = inv; + } + return inventories; +} + ubyte* WorldFiles::getData(regionsmap& regions, const fs::path& folder, - int x, int z, int layer) { + int x, int z, int layer, bool compression) { int regionX = floordiv(x, REGION_SIZE); int regionZ = floordiv(z, REGION_SIZE); @@ -291,7 +341,10 @@ ubyte* WorldFiles::getData(regionsmap& regions, const fs::path& folder, } if (data != nullptr) { size_t size = region->getChunkDataSize(localX, localZ); - return decompress(data, size, CHUNK_DATA_LEN); + if (compression) { + return decompress(data, size, CHUNK_DATA_LEN); + } + return data; } return nullptr; } @@ -343,17 +396,16 @@ ubyte* WorldFiles::readChunkData(int x, file.seekg(table_offset + chunkIndex * 4); file.read((char*)(&offset), 4); offset = dataio::read_int32_big((const ubyte*)(&offset), 0); + if (offset == 0){ return nullptr; } + file.seekg(offset); file.read((char*)(&offset), 4); length = dataio::read_int32_big((const ubyte*)(&offset), 0); - ubyte* data = new ubyte[length]; + ubyte* data = new ubyte[length]{}; file.read((char*)data, length); - if (data == nullptr) { - std::cerr << "ERROR: failed to read data of chunk x("<< x <<"), z("<< z <<")" << std::endl; - } return data; } @@ -436,20 +488,24 @@ void WorldFiles::writeRegions(regionsmap& regions, const fs::path& folder, int l void WorldFiles::write(const World* world, const Content* content) { fs::path regionsFolder = getRegionsFolder(); fs::path lightsFolder = getLightsFolder(); + fs::path inventoriesFolder = getInventoriesFolder(); fs::create_directories(regionsFolder); + fs::create_directories(inventoriesFolder); fs::create_directories(lightsFolder); if (world) { writeWorldInfo(world); writePacks(world); } - if (generatorTestMode) + if (generatorTestMode) { return; + } writeIndices(content->getIndices()); writeRegions(regions, regionsFolder, REGION_LAYER_VOXELS); writeRegions(lights, lightsFolder, REGION_LAYER_LIGHTS); + writeRegions(storages, inventoriesFolder, REGION_LAYER_INVENTORIES); } void WorldFiles::writePacks(const World* world) { @@ -488,21 +544,7 @@ void WorldFiles::writeIndices(const ContentIndices* indices) { } void WorldFiles::writeWorldInfo(const World* world) { - dynamic::Map root; - - auto& versionobj = root.putMap("version"); - versionobj.put("major", ENGINE_VERSION_MAJOR); - versionobj.put("minor", ENGINE_VERSION_MINOR); - - root.put("name", world->getName()); - root.put("seed", world->getSeed()); - - auto& timeobj = root.putMap("time"); - timeobj.put("day-time", world->daytime); - timeobj.put("day-time-speed", world->daytimeSpeed); - timeobj.put("total-time", world->totalTime); - - files::write_json(getWorldFile(), &root); + files::write_json(getWorldFile(), world->serialize().get()); } bool WorldFiles::readWorldInfo(World* world) { @@ -513,25 +555,7 @@ bool WorldFiles::readWorldInfo(World* world) { } auto root = files::read_json(file); - - world->setName(root->getStr("name", world->getName())); - world->setSeed(root->getInt("seed", world->getSeed())); - - auto verobj = root->map("version"); - if (verobj) { - int major=0, minor=-1; - verobj->num("major", major); - verobj->num("minor", minor); - std::cout << "world version: " << major << "." << minor << std::endl; - } - - auto timeobj = root->map("time"); - if (timeobj) { - timeobj->num("day-time", world->daytime); - timeobj->num("day-time-speed", world->daytimeSpeed); - timeobj->num("total-time", world->totalTime); - } - + world->deserialize(root.get()); return true; } @@ -550,8 +574,15 @@ bool WorldFiles::readPlayer(Player* player) { return true; } -void WorldFiles::addPack(const std::string& id) { - auto packs = files::read_list(getPacksFile()); +void WorldFiles::addPack(const World* world, const std::string& id) { + fs::path file = getPacksFile(); + if (!fs::is_regular_file(file)) { + if (!fs::is_directory(directory)) { + fs::create_directories(directory); + } + writePacks(world); + } + auto packs = files::read_list(file); packs.push_back(id); std::stringstream ss; @@ -559,5 +590,5 @@ void WorldFiles::addPack(const std::string& id) { for (const auto& pack : packs) { ss << pack << "\n"; } - files::write_string(getPacksFile(), ss.str()); + files::write_string(file, ss.str()); } diff --git a/src/files/WorldFiles.h b/src/files/WorldFiles.h index 291604dc..34203b97 100644 --- a/src/files/WorldFiles.h +++ b/src/files/WorldFiles.h @@ -15,9 +15,14 @@ #include "../typedefs.h" #include "../settings.h" +#include "../voxels/Chunk.h" + const uint REGION_HEADER_SIZE = 10; + const uint REGION_LAYER_VOXELS = 0; const uint REGION_LAYER_LIGHTS = 1; +const uint REGION_LAYER_INVENTORIES = 2; + const uint REGION_SIZE_BIT = 5; const uint REGION_SIZE = (1 << (REGION_SIZE_BIT)); const uint REGION_CHUNKS_COUNT = ((REGION_SIZE) * (REGION_SIZE)); @@ -29,7 +34,6 @@ const uint MAX_OPEN_REGION_FILES = 16; #define WORLD_FORMAT_MAGIC ".VOXWLD" class Player; -class Chunk; class Content; class ContentIndices; class World; @@ -74,8 +78,7 @@ class WorldFiles { std::unordered_map> openRegFiles; void writeWorldInfo(const World* world); - fs::path getLightsFolder() const; - fs::path getRegionFilename(int x, int y) const; + fs::path getRegionFilename(int x, int y) const; fs::path getWorldFile() const; fs::path getIndicesFile() const; fs::path getPacksFile() const; @@ -107,15 +110,19 @@ class WorldFiles { ubyte* getData(regionsmap& regions, const fs::path& folder, - int x, int z, int layer); + int x, int z, int layer, bool compression); regfile* getRegFile(glm::ivec3 coord, const fs::path& folder); + + fs::path getLightsFolder() const; + fs::path getInventoriesFolder() const; public: static bool parseRegionFilename(const std::string& name, int& x, int& y); fs::path getRegionsFolder() const; fs::path getPlayerFile() const; regionsmap regions; + regionsmap storages; regionsmap lights; fs::path directory; std::unique_ptr compressionBuffer; @@ -133,6 +140,7 @@ public: ubyte* getChunk(int x, int z); light_t* getLights(int x, int z); + chunk_inventories_map fetchInventories(int x, int z); bool readWorldInfo(World* world); bool readPlayer(Player* player); @@ -147,7 +155,7 @@ public: void writePacks(const World* world); void writeIndices(const ContentIndices* indices); /* Append pack to packs.list without duplicate check */ - void addPack(const std::string& id); + void addPack(const World* world, const std::string& id); static const char* WORLD_FILE; }; diff --git a/src/files/engine_paths.cpp b/src/files/engine_paths.cpp index a657a3ca..2f716f97 100644 --- a/src/files/engine_paths.cpp +++ b/src/files/engine_paths.cpp @@ -1,12 +1,13 @@ #include "engine_paths.h" -#include +#include #include +#include #include "../typedefs.h" #include "WorldFiles.h" -#define SCREENSHOTS_FOLDER "screenshots" +const fs::path SCREENSHOTS_FOLDER {"screenshots"}; fs::path EnginePaths::getUserfiles() const { return userfiles; @@ -30,10 +31,10 @@ fs::path EnginePaths::getScreenshotFile(std::string ext) { ss << std::put_time(&tm, format); std::string datetimestr = ss.str(); - fs::path filename = folder/fs::path("screenshot-"+datetimestr+"."+ext); + fs::path filename = folder/fs::u8path("screenshot-"+datetimestr+"."+ext); uint index = 0; while (fs::exists(filename)) { - filename = folder/fs::path("screenshot-"+datetimestr+"-"+std::to_string(index)+"."+ext); + filename = folder/fs::u8path("screenshot-"+datetimestr+"-"+std::to_string(index)+"."+ext); index++; } return filename; @@ -59,12 +60,17 @@ std::vector EnginePaths::scanForWorlds() { continue; } fs::path worldFolder = entry.path(); - fs::path worldFile = worldFolder/fs::path(WorldFiles::WORLD_FILE); + fs::path worldFile = worldFolder/fs::u8path(WorldFiles::WORLD_FILE); if (!fs::is_regular_file(worldFile)) { continue; } folders.push_back(worldFolder); } + std::sort(folders.begin(), folders.end(), [](fs::path a, fs::path b) { + a = a/fs::u8path(WorldFiles::WORLD_FILE); + b = b/fs::u8path(WorldFiles::WORLD_FILE); + return fs::last_write_time(a) > fs::last_write_time(b); + }); return folders; } @@ -88,34 +94,58 @@ void EnginePaths::setContentPacks(std::vector* contentPacks) { this->contentPacks = contentPacks; } +static fs::path toCanonic(fs::path path) { + std::stack parts; + path = path.lexically_normal(); + while (true) { + parts.push(path.filename().u8string()); + path = path.parent_path(); + if (path.empty()) + break; + } + path = fs::u8path(""); + while (!parts.empty()) { + const std::string part = parts.top(); + parts.pop(); + if (part == ".") { + continue; + } + if (part == "..") { + throw files_access_error("entry point reached"); + } + + path = path / fs::path(part); + } + return path; +} + fs::path EnginePaths::resolve(std::string path) { size_t separator = path.find(':'); if (separator == std::string::npos) { - return fs::path(path); + throw files_access_error("no entry point specified"); } std::string prefix = path.substr(0, separator); std::string filename = path.substr(separator+1); + filename = toCanonic(fs::u8path(filename)).u8string(); if (prefix == "res" || prefix == "core") { - return resources/fs::path(filename); + return resources/fs::u8path(filename); } - if (prefix == "user") { - return userfiles/fs::path(filename); + return userfiles/fs::u8path(filename); } - if (prefix == "world") { - return worldFolder/fs::path(filename); + return worldFolder/fs::u8path(filename); } if (contentPacks) { for (auto& pack : *contentPacks) { if (pack.id == prefix) { - return pack.folder/fs::path(filename); + return pack.folder/fs::u8path(filename); } } } - return fs::path("./"+filename); + throw files_access_error("unknown entry point '"+prefix+"'"); } ResPaths::ResPaths(fs::path mainRoot, std::vector roots) @@ -125,19 +155,19 @@ ResPaths::ResPaths(fs::path mainRoot, std::vector roots) fs::path ResPaths::find(const std::string& filename) const { for (int i = roots.size()-1; i >= 0; i--) { auto& root = roots[i]; - fs::path file = root / fs::path(filename); + fs::path file = root / fs::u8path(filename); if (fs::exists(file)) { return file; } } - return mainRoot / fs::path(filename); + return mainRoot / fs::u8path(filename); } std::vector ResPaths::listdir(const std::string& folderName) const { std::vector entries; for (int i = roots.size()-1; i >= 0; i--) { auto& root = roots[i]; - fs::path folder = root / fs::path(folderName); + fs::path folder = root / fs::u8path(folderName); if (!fs::is_directory(folder)) continue; for (const auto& entry : fs::directory_iterator(folder)) { @@ -145,7 +175,7 @@ std::vector ResPaths::listdir(const std::string& folderName) const { } } { - fs::path folder = mainRoot / fs::path(folderName); + fs::path folder = mainRoot / fs::u8path(folderName); if (!fs::is_directory(folder)) return entries; for (const auto& entry : fs::directory_iterator(folder)) { @@ -154,3 +184,7 @@ std::vector ResPaths::listdir(const std::string& folderName) const { } return entries; } + +const fs::path& ResPaths::getMainRoot() const { + return mainRoot; +} diff --git a/src/files/engine_paths.h b/src/files/engine_paths.h index 760a1ba5..cc9ee863 100644 --- a/src/files/engine_paths.h +++ b/src/files/engine_paths.h @@ -3,12 +3,18 @@ #include #include +#include #include #include "../content/ContentPack.h" namespace fs = std::filesystem; +class files_access_error : public std::runtime_error { +public: + files_access_error(const std::string& msg) : std::runtime_error(msg) {} +}; + class EnginePaths { fs::path userfiles {"."}; fs::path resources {"res"}; @@ -42,6 +48,8 @@ public: fs::path find(const std::string& filename) const; std::vector listdir(const std::string& folder) const; + + const fs::path& getMainRoot() const; }; -#endif // FILES_ENGINE_PATHS_H_ \ No newline at end of file +#endif // FILES_ENGINE_PATHS_H_ diff --git a/src/files/files.cpp b/src/files/files.cpp index 55ca345f..59fa44b1 100644 --- a/src/files/files.cpp +++ b/src/files/files.cpp @@ -99,10 +99,7 @@ bool files::write_json(fs::path filename, const dynamic::Map* obj, bool nice) { } bool files::write_binary_json(fs::path filename, const dynamic::Map* obj, bool compression) { - auto bytes = json::to_binary(obj); - if (compression) { - bytes = gzip::compress(bytes.data(), bytes.size()); - } + auto bytes = json::to_binary(obj, compression); return files::write_bytes(filename, bytes.data(), bytes.size()); } diff --git a/src/files/settings_io.cpp b/src/files/settings_io.cpp index 037282d0..2a67fb34 100644 --- a/src/files/settings_io.cpp +++ b/src/files/settings_io.cpp @@ -32,6 +32,7 @@ toml::Wrapper* create_wrapper(EngineSettings& settings) { camera.add("sensitivity", &settings.camera.sensitivity); toml::Section& graphics = wrapper->add("graphics"); + graphics.add("gamma", &settings.graphics.gamma); graphics.add("fog-curve", &settings.graphics.fogCurve); graphics.add("backlight", &settings.graphics.backlight); graphics.add("frustum-culling", &settings.graphics.frustumCulling); diff --git a/src/frontend/ContentGfxCache.cpp b/src/frontend/ContentGfxCache.cpp index cb9a9ae0..f8e7bd40 100644 --- a/src/frontend/ContentGfxCache.cpp +++ b/src/frontend/ContentGfxCache.cpp @@ -4,38 +4,49 @@ #include "../assets/Assets.h" #include "../content/Content.h" +#include "../content/ContentPack.h" #include "../graphics/Atlas.h" #include "../voxels/Block.h" +#include "../core_defs.h" +#include "UiDocument.h" -ContentGfxCache::ContentGfxCache(const Content* content, Assets* assets) { +ContentGfxCache::ContentGfxCache(const Content* content, Assets* assets) : content(content) { auto indices = content->getIndices(); - sideregions = new UVRegion[indices->countBlockDefs() * 6]; - Atlas* atlas = assets->getAtlas("blocks"); - - for (uint i = 0; i < indices->countBlockDefs(); i++) { - Block* def = indices->getBlockDef(i); - for (uint side = 0; side < 6; side++) { - std::string tex = def->textureFaces[side]; - if (atlas->has(tex)) { - sideregions[i * 6 + side] = atlas->get(tex); - } else { - if (atlas->has("notfound")) - sideregions[i * 6 + side] = atlas->get("notfound"); - } - } - for (uint side = 0; side < def->modelTextures.size(); side++) - { - std::string tex = def->modelTextures[side]; - if (atlas->has(tex)) { - def->modelUVs.push_back(atlas->get(tex)); - } else { - if (atlas->has("notfound")) - def->modelUVs.push_back(atlas->get("notfound")); - } - } + sideregions = std::make_unique(indices->countBlockDefs() * 6); + Atlas* atlas = assets->getAtlas("blocks"); + + for (uint i = 0; i < indices->countBlockDefs(); i++) { + Block* def = indices->getBlockDef(i); + for (uint side = 0; side < 6; side++) { + const std::string& tex = def->textureFaces[side]; + if (atlas->has(tex)) { + sideregions[i * 6 + side] = atlas->get(tex); + } else if (atlas->has(TEXTURE_NOTFOUND)) { + sideregions[i * 6 + side] = atlas->get(TEXTURE_NOTFOUND); + } + } + for (uint side = 0; side < def->modelTextures.size(); side++) { + const std::string& tex = def->modelTextures[side]; + if (atlas->has(tex)) { + def->modelUVs.push_back(atlas->get(tex)); + } else if (atlas->has(TEXTURE_NOTFOUND)) { + def->modelUVs.push_back(atlas->get(TEXTURE_NOTFOUND)); + } + } } } ContentGfxCache::~ContentGfxCache() { - delete[] sideregions; +} + +std::shared_ptr ContentGfxCache::getLayout(const std::string& id) { + auto found = layouts.find(id); + if (found == layouts.end()) { + return nullptr; + } + return found->second; +} + +const Content* ContentGfxCache::getContent() const { + return content; } diff --git a/src/frontend/ContentGfxCache.h b/src/frontend/ContentGfxCache.h index 1f2b3773..b561742b 100644 --- a/src/frontend/ContentGfxCache.h +++ b/src/frontend/ContentGfxCache.h @@ -1,15 +1,24 @@ #ifndef FRONTEND_BLOCKS_GFX_CACHE_H_ #define FRONTEND_BLOCKS_GFX_CACHE_H_ +#include +#include +#include #include "../graphics/UVRegion.h" #include "../typedefs.h" class Content; class Assets; +class UiDocument; + +using uidocuments_map = std::unordered_map>; class ContentGfxCache { + const Content* content; // array of block sides uv regions (6 per block) - UVRegion* sideregions; + std::unique_ptr sideregions; + // all loaded layouts + uidocuments_map layouts; public: ContentGfxCache(const Content* content, Assets* assets); ~ContentGfxCache(); @@ -17,6 +26,10 @@ public: inline const UVRegion& getRegion(blockid_t id, int side) const { return sideregions[id * 6 + side]; } + + std::shared_ptr getLayout(const std::string& id); + + const Content* getContent() const; }; #endif // FRONTEND_BLOCKS_GFX_CACHE_H_ diff --git a/src/frontend/InventoryView.cpp b/src/frontend/InventoryView.cpp index a09d64c1..d3a16549 100644 --- a/src/frontend/InventoryView.cpp +++ b/src/frontend/InventoryView.cpp @@ -19,66 +19,40 @@ #include "../maths/voxmaths.h" #include "../objects/Player.h" #include "../voxels/Block.h" +#include "../frontend/gui/containers.h" #include "../frontend/gui/controls.h" #include "../util/stringutil.h" - -InventoryLayout::InventoryLayout(glm::vec2 size) : size(size) {} - -void InventoryLayout::add(SlotLayout slot) { - slots.push_back(slot); -} - -void InventoryLayout::add(InventoryPanel panel) { - panels.push_back(panel); -} - -void InventoryLayout::setSize(glm::vec2 size) { - this->size = size; -} - -void InventoryLayout::setOrigin(glm::vec2 origin) { - this->origin = origin; -} - -glm::vec2 InventoryLayout::getSize() const { - return size; -} - -glm::vec2 InventoryLayout::getOrigin() const { - return origin; -} - -std::vector& InventoryLayout::getSlots() { - return slots; -} - -std::vector& InventoryLayout::getPanels() { - return panels; -} +#include "../world/Level.h" +#include "../logic/scripting/scripting.h" SlotLayout::SlotLayout( + int index, glm::vec2 position, bool background, bool itemSource, - itemsharefunc shareFunc, + slotcallback shareFunc, slotcallback rightClick ) - : position(position), + : index(index), + position(position), background(background), itemSource(itemSource), shareFunc(shareFunc), rightClick(rightClick) {} -InventoryPanel::InventoryPanel( - glm::vec2 position, - glm::vec2 size, - glm::vec4 color) - : position(position), size(size), color(color) {} - -InventoryBuilder::InventoryBuilder() - : layout(std::make_unique(glm::vec2())) -{} +InventoryBuilder::InventoryBuilder() { + view = std::make_shared(); +} +/** Add slots grid to inventory view + * @param cols grid columns + * @param count total number of grid slots + * @param coord position of the first slot of the grid + * @param padding additional space around the grid + * @param addpanel automatically create panel behind the grid + * with size including padding + * @param slotLayout slot settings (index and position are ignored) + */ void InventoryBuilder::addGrid( int cols, int count, glm::vec2 coord, @@ -94,14 +68,20 @@ void InventoryBuilder::addGrid( uint width = cols * (slotSize + interval) - interval + padding*2; uint height = rows * (slotSize + interval) - interval + padding*2; - auto lsize = layout->getSize(); - if (coord.x + width > lsize.x) { - lsize.x = coord.x + width; + glm::vec2 vsize = view->getSize(); + if (coord.x + width > vsize.x) { + vsize.x = coord.x + width; } - if (coord.y + height > lsize.y) { - lsize.y = coord.y + height; + if (coord.y + height > vsize.y) { + vsize.y = coord.y + height; + } + view->setSize(vsize); + + if (addpanel) { + auto panel = std::make_shared(coord, glm::vec2(width, height)); + view->setColor(glm::vec4(0.122f, 0.122f, 0.122f, 0.878f)); + view->add(panel); } - layout->setSize(lsize); for (int row = 0; row < rows; row++) { for (int col = 0; col < cols; col++) { @@ -112,70 +92,47 @@ void InventoryBuilder::addGrid( row * (slotSize + interval) + padding ); auto builtSlot = slotLayout; + builtSlot.index = row * cols + col; builtSlot.position = position; - layout->add(builtSlot); + add(builtSlot); } } - - if (addpanel) { - add(InventoryPanel( - coord, - glm::vec2(width, height), - glm::vec4(0, 0, 0, 0.5f))); - } } -void InventoryBuilder::add(SlotLayout slotLayout) { - uint width = InventoryView::SLOT_SIZE; - uint height = InventoryView::SLOT_SIZE; - - auto coord = slotLayout.position; - auto lsize = layout->getSize(); - if (coord.x + width > lsize.x) { - lsize.x = coord.x + width; - } - if (coord.y + height > lsize.y) { - lsize.y = coord.y + height; - } - layout->add(slotLayout); +void InventoryBuilder::add(SlotLayout layout) { + view->add(view->addSlot(layout), layout.position); } -void InventoryBuilder::add(InventoryPanel panel) { - layout->add(panel); -} - -std::unique_ptr InventoryBuilder::build() { - return std::unique_ptr(layout.release()); +std::shared_ptr InventoryBuilder::build() { + return view; } SlotView::SlotView( - ItemStack& stack, - LevelFrontend* frontend, - InventoryInteraction* interaction, - const Content* content, - SlotLayout layout) - : UINode(glm::vec2(), glm::vec2(InventoryView::SLOT_SIZE)), - frontend(frontend), - interaction(interaction), - content(content), - stack(stack), - layout(layout) { - color(glm::vec4(0, 0, 0, 0.2f)); + SlotLayout layout +) : UINode(glm::vec2(), glm::vec2(InventoryView::SLOT_SIZE)), + layout(layout) +{ + setColor(glm::vec4(0, 0, 0, 0.2f)); } -// performance disaster -void SlotView::draw(Batch2D* batch, Assets* assets) { +void SlotView::draw(const GfxContext* pctx, Assets* assets) { + if (bound == nullptr) + return; + + ItemStack& stack = *bound; + glm::vec2 coord = calcCoord(); int slotSize = InventoryView::SLOT_SIZE; glm::vec4 tint(1.0f); - glm::vec4 color = color_; - if (hover_ || highlighted) { + glm::vec4 color = getColor(); + if (hover || highlighted) { tint *= 1.333f; color = glm::vec4(1, 1, 1, 0.2f); } + auto batch = pctx->getBatch2D(); batch->color = color; if (color.a > 0.0) { batch->texture(nullptr); @@ -196,10 +153,10 @@ void SlotView::draw(Batch2D* batch, Assets* assets) { case item_icon_type::none: break; case item_icon_type::block: { - Block* cblock = content->requireBlock(item->icon); + const Block& cblock = content->requireBlock(item->icon); batch->texture(previews->getTexture()); - UVRegion region = previews->get(cblock->name); + UVRegion region = previews->get(cblock.name); batch->rect( coord.x, coord.y, slotSize, slotSize, 0, 0, 0, region, false, true, tint); @@ -249,11 +206,16 @@ bool SlotView::isHighlighted() const { } void SlotView::clicked(gui::GUI* gui, int button) { + if (bound == nullptr) + return; + ItemStack& grabbed = interaction->getGrabbedItem(); + ItemStack& stack = *bound; + if (button == mousecode::BUTTON_1) { if (Events::pressed(keycode::LEFT_SHIFT)) { if (layout.shareFunc) { - layout.shareFunc(stack); + layout.shareFunc(layout.index, stack); } return; } @@ -272,7 +234,7 @@ void SlotView::clicked(gui::GUI* gui, int button) { } } else if (button == mousecode::BUTTON_2) { if (layout.rightClick) { - layout.rightClick(stack, grabbed); + layout.rightClick(inventoryid, stack); return; } if (layout.itemSource) @@ -288,50 +250,90 @@ void SlotView::clicked(gui::GUI* gui, int button) { if (stack.isEmpty()) { stack.set(grabbed); stack.setCount(1); - } else { + grabbed.setCount(grabbed.getCount()-1); + } else if (stack.accepts(grabbed)){ stack.setCount(stack.getCount()+1); + grabbed.setCount(grabbed.getCount()-1); } - grabbed.setCount(grabbed.getCount()-1); } } } -InventoryView::InventoryView( - const Content* content, - LevelFrontend* frontend, - InventoryInteraction* interaction, - std::shared_ptr inventory, - std::unique_ptr layout) - : Container(glm::vec2(), glm::vec2()), - content(content), - indices(content->getIndices()), - inventory(inventory), - layout(std::move(layout)), - frontend(frontend), - interaction(interaction) { - size(this->layout->getSize()); - color(glm::vec4(0, 0, 0, 0.0f)); +void SlotView::focus(gui::GUI* gui) { + clicked(gui, 0); +} + +void SlotView::bind( + int64_t inventoryid, + ItemStack& stack, + LevelFrontend* frontend, + InventoryInteraction* interaction +) { + this->inventoryid = inventoryid; + bound = &stack; + content = frontend->getLevel()->content; + this->frontend = frontend; + this->interaction = interaction; +} + +const SlotLayout& SlotView::getLayout() const { + return layout; +} + +InventoryView::InventoryView() : Container(glm::vec2(), glm::vec2()) { + setColor(glm::vec4(0, 0, 0, 0.0f)); } InventoryView::~InventoryView() {} -void InventoryView::build() { - size_t index = 0; - for (auto& slot : layout->getSlots()) { - if (index >= inventory->size()) - break; - ItemStack& item = inventory->getSlot(index); +std::shared_ptr InventoryView::addSlot(SlotLayout layout) { + uint width = InventoryView::SLOT_SIZE + layout.padding; + uint height = InventoryView::SLOT_SIZE + layout.padding; - auto view = std::make_shared( - item, frontend, interaction, content, slot + auto coord = layout.position; + auto vsize = getSize(); + if (coord.x + width > vsize.x) { + vsize.x = coord.x + width; + } + if (coord.y + height > vsize.y) { + vsize.y = coord.y + height; + } + setSize(vsize); + + auto slot = std::make_shared(layout); + if (!layout.background) { + slot->setColor(glm::vec4()); + } + slots.push_back(slot.get()); + return slot; +} + +std::shared_ptr InventoryView::getInventory() const { + return inventory; +} + + +size_t InventoryView::getSlotsCount() const { + return slots.size(); +} + +void InventoryView::bind( + std::shared_ptr inventory, + LevelFrontend* frontend, + InventoryInteraction* interaction +) { + this->frontend = frontend; + this->interaction = interaction; + this->inventory = inventory; + content = frontend->getLevel()->content; + indices = content->getIndices(); + for (auto slot : slots) { + slot->bind( + inventory->getId(), + inventory->getSlot(slot->getLayout().index), + frontend, interaction ); - if (!slot.background) { - view->color(glm::vec4()); - } - slots.push_back(view.get()); - add(view, slot.position); - index++; } } @@ -343,26 +345,119 @@ void InventoryView::setSelected(int index) { } void InventoryView::setCoord(glm::vec2 coord) { - Container::setCoord(coord - layout->getOrigin()); + Container::setCoord(coord - origin); +} + +void InventoryView::setOrigin(glm::vec2 origin) { + this->origin = origin; +} + +glm::vec2 InventoryView::getOrigin() const { + return origin; } void InventoryView::setInventory(std::shared_ptr inventory) { this->inventory = inventory; } -InventoryLayout* InventoryView::getLayout() const { - return layout.get(); +#include "../coders/xml.h" +#include "gui/gui_xml.h" + +static slotcallback readSlotFunc(InventoryView* view, gui::UiXmlReader& reader, xml::xmlelement& element, const std::string& attr) { + auto consumer = scripting::create_int_array_consumer( + reader.getEnvironment().getId(), + element->attr(attr).getText() + ); + return [=](uint slot, ItemStack& stack) { + int args[] {int(view->getInventory()->getId()), int(slot)}; + consumer(args, 2); + }; } -void InventoryView::drawBackground(Batch2D* batch, Assets* assets) { - glm::vec2 coord = calcCoord(); +static void readSlot(InventoryView* view, gui::UiXmlReader& reader, xml::xmlelement element) { + int index = element->attr("index", "0").asInt(); + bool itemSource = element->attr("item-source", "false").asBool(); + SlotLayout layout(index, glm::vec2(), true, itemSource, nullptr, nullptr); + if (element->has("coord")) { + layout.position = element->attr("coord").asVec2(); + } + if (element->has("sharefunc")) { + layout.shareFunc = readSlotFunc(view, reader, element, "sharefunc"); + } + if (element->has("onrightclick")) { + layout.rightClick = readSlotFunc(view, reader, element, "onrightclick"); + } + auto slot = view->addSlot(layout); + reader.readUINode(reader, element, *slot); + view->add(slot); +} - batch->texture(nullptr); +static void readSlotsGrid(InventoryView* view, gui::UiXmlReader& reader, xml::xmlelement element) { + int startIndex = element->attr("start-index", "0").asInt(); + int rows = element->attr("rows", "0").asInt(); + int cols = element->attr("cols", "0").asInt(); + int count = element->attr("count", "0").asInt(); + const int slotSize = InventoryView::SLOT_SIZE; + int interval = element->attr("interval", "-1").asInt(); + if (interval < 0) { + interval = InventoryView::SLOT_INTERVAL; + } + int padding = element->attr("padding", "-1").asInt(); + if (padding < 0) { + padding = interval; + } + if (rows == 0) { + rows = ceildiv(count, cols); + } else if (cols == 0) { + cols = ceildiv(count, rows); + } else if (count == 0) { + count = rows * cols; + } + bool itemSource = element->attr("item-source", "false").asBool(); + SlotLayout layout(-1, glm::vec2(), true, itemSource, nullptr, nullptr); + if (element->has("pos")) { + layout.position = element->attr("pos").asVec2(); + } + if (element->has("sharefunc")) { + layout.shareFunc = readSlotFunc(view, reader, element, "sharefunc"); + } + if (element->has("onrightclick")) { + layout.rightClick = readSlotFunc(view, reader, element, "onrightclick"); + } + layout.padding = padding; - for (auto& panel : layout->getPanels()) { - glm::vec2 size = panel.size; - glm::vec2 pos = coord + panel.position; - batch->color = panel.color; - batch->rect(pos.x-1, pos.y-1, size.x+2, size.y+2); + int idx = 0; + for (int row = 0; row < rows; row++) { + for (int col = 0; col < cols; col++, idx++) { + if (idx >= count) { + return; + } + SlotLayout slotLayout = layout; + slotLayout.index = startIndex + idx; + slotLayout.position += glm::vec2( + padding + col * (slotSize + interval), + padding + (rows-row-1) * (slotSize + interval) + ); + auto slot = view->addSlot(slotLayout); + view->add(slot, slotLayout.position); + } } } + +void InventoryView::createReaders(gui::UiXmlReader& reader) { + reader.add("inventory", [=](gui::UiXmlReader& reader, xml::xmlelement element) { + auto view = std::make_shared(); + view->setColor(glm::vec4(0.122f, 0.122f, 0.122f, 0.878f)); // todo: fixme + reader.addIgnore("slots-grid"); + reader.readUINode(reader, element, *view); + + for (auto& sub : element->getElements()) { + if (sub->getTag() == "slot") { + readSlot(view.get(), reader, sub); + } else if (sub->getTag() == "slots-grid") { + readSlotsGrid(view.get(), reader, sub); + } + } + return view; + }); +} diff --git a/src/frontend/InventoryView.h b/src/frontend/InventoryView.h index 6191d8c8..eb42f1c7 100644 --- a/src/frontend/InventoryView.h +++ b/src/frontend/InventoryView.h @@ -6,12 +6,11 @@ #include #include "../frontend/gui/UINode.h" -#include "../frontend/gui/panels.h" +#include "../frontend/gui/containers.h" #include "../frontend/gui/controls.h" #include "../items/ItemStack.h" #include "../typedefs.h" -class Batch2D; class Assets; class GfxContext; class Content; @@ -19,134 +18,15 @@ class ContentIndices; class LevelFrontend; class Inventory; -typedef std::function itemsharefunc; -typedef std::function slotcallback; +namespace gui { + class UiXmlReader; +} -class InventoryInteraction; +namespace scripting { + class Environment; +} -struct SlotLayout { - glm::vec2 position; - bool background; - bool itemSource; - itemsharefunc shareFunc; - slotcallback rightClick; - - SlotLayout(glm::vec2 position, - bool background, - bool itemSource, - itemsharefunc shareFunc, - slotcallback rightClick); -}; - -// temporary unused -struct InventoryPanel { - glm::vec2 position; - glm::vec2 size; - glm::vec4 color; - - InventoryPanel(glm::vec2 position, - glm::vec2 size, - glm::vec4 color); -}; - -class InventoryLayout { - glm::vec2 size {}; - glm::vec2 origin {}; - std::vector slots; - std::vector panels; -public: - InventoryLayout(glm::vec2 size); - - void add(SlotLayout slot); - void add(InventoryPanel panel); - void setSize(glm::vec2 size); - void setOrigin(glm::vec2 origin); - - glm::vec2 getSize() const; - glm::vec2 getOrigin() const; - - std::vector& getSlots(); - std::vector& getPanels(); -}; - -class InventoryBuilder { - std::unique_ptr layout; -public: - InventoryBuilder(); - - void addGrid( - int cols, int count, - glm::vec2 coord, - int padding, - bool addpanel, - SlotLayout slotLayout); - - void add(SlotLayout slotLayout); - void add(InventoryPanel panel); - - std::unique_ptr build(); -}; - -class SlotView : public gui::UINode { - LevelFrontend* frontend; - InventoryInteraction* interaction; - const Content* const content; - ItemStack& stack; - bool highlighted = false; - - SlotLayout layout; -public: - SlotView(ItemStack& stack, - LevelFrontend* frontend, - InventoryInteraction* interaction, - const Content* content, - SlotLayout layout); - - virtual void draw(Batch2D* batch, Assets* assets) override; - - void setHighlighted(bool flag); - bool isHighlighted() const; - - virtual void clicked(gui::GUI*, int) override; -}; - -class InventoryView : public gui::Container { - const Content* content; - const ContentIndices* indices; - - std::shared_ptr inventory; - std::unique_ptr layout; - LevelFrontend* frontend; - InventoryInteraction* interaction; - - std::vector slots; - - int scroll = 0; -public: - InventoryView( - const Content* content, - LevelFrontend* frontend, - InventoryInteraction* interaction, - std::shared_ptr inventory, - std::unique_ptr layout); - - virtual ~InventoryView(); - - void build(); - - virtual void drawBackground(Batch2D* batch, Assets* assets) override; - - void setInventory(std::shared_ptr inventory); - - virtual void setCoord(glm::vec2 coord) override; - - InventoryLayout* getLayout() const; - - void setSelected(int index); - - static const int SLOT_INTERVAL = 4; - static const int SLOT_SIZE = ITEM_ICON_SIZE; -}; +using slotcallback = std::function; class InventoryInteraction { ItemStack grabbedItem; @@ -158,4 +38,109 @@ public: } }; +struct SlotLayout { + int index; + glm::vec2 position; + bool background; + bool itemSource; + slotcallback shareFunc; + slotcallback rightClick; + int padding = 0; + + SlotLayout(int index, + glm::vec2 position, + bool background, + bool itemSource, + slotcallback shareFunc, + slotcallback rightClick); +}; + +class SlotView : public gui::UINode { + LevelFrontend* frontend = nullptr; + InventoryInteraction* interaction = nullptr; + const Content* content; + SlotLayout layout; + bool highlighted = false; + + int64_t inventoryid = 0; + ItemStack* bound = nullptr; +public: + SlotView(SlotLayout layout); + + virtual void draw(const GfxContext* pctx, Assets* assets) override; + + void setHighlighted(bool flag); + bool isHighlighted() const; + + virtual void clicked(gui::GUI*, int) override; + virtual void focus(gui::GUI*) override; + + void bind( + int64_t inventoryid, + ItemStack& stack, + LevelFrontend* frontend, + InventoryInteraction* interaction + ); + + const SlotLayout& getLayout() const; +}; + +class InventoryView : public gui::Container { + const Content* content; + const ContentIndices* indices; + + std::shared_ptr inventory; + LevelFrontend* frontend = nullptr; + InventoryInteraction* interaction = nullptr; + + std::vector slots; + glm::vec2 origin {}; +public: + InventoryView(); + virtual ~InventoryView(); + + void setInventory(std::shared_ptr inventory); + + virtual void setCoord(glm::vec2 coord) override; + + void setOrigin(glm::vec2 origin); + glm::vec2 getOrigin() const; + + void setSelected(int index); + + void bind( + std::shared_ptr inventory, + LevelFrontend* frontend, + InventoryInteraction* interaction + ); + + std::shared_ptr addSlot(SlotLayout layout); + + std::shared_ptr getInventory() const; + + size_t getSlotsCount() const; + + static void createReaders(gui::UiXmlReader& reader); + + static const int SLOT_INTERVAL = 4; + static const int SLOT_SIZE = ITEM_ICON_SIZE; +}; + +class InventoryBuilder { + std::shared_ptr view; +public: + InventoryBuilder(); + + void addGrid( + int cols, int count, + glm::vec2 coord, + int padding, + bool addpanel, + SlotLayout slotLayout + ); + + void add(SlotLayout slotLayout); + std::shared_ptr build(); +}; + #endif // FRONTEND_INVENTORY_VIEW_H_ diff --git a/src/frontend/LevelFrontend.h b/src/frontend/LevelFrontend.h index 98a8a111..772c5309 100644 --- a/src/frontend/LevelFrontend.h +++ b/src/frontend/LevelFrontend.h @@ -24,5 +24,4 @@ public: Atlas* getBlocksAtlas() const; }; - #endif // FRONTEND_LEVEL_FRONTEND_H_ diff --git a/src/frontend/UiDocument.cpp b/src/frontend/UiDocument.cpp new file mode 100644 index 00000000..0690eaa9 --- /dev/null +++ b/src/frontend/UiDocument.cpp @@ -0,0 +1,79 @@ +#include "UiDocument.h" + +#include +#include "gui/UINode.h" +#include "gui/containers.h" +#include "InventoryView.h" +#include "../logic/scripting/scripting.h" +#include "../files/files.h" +#include "../frontend/gui/gui_xml.h" + +UiDocument::UiDocument( + std::string id, + uidocscript script, + std::shared_ptr root, + std::unique_ptr env +) : id(id), script(script), root(root), env(std::move(env)) { + collect(map, root); +} + + +const uinodes_map& UiDocument::getMap() const { + return map; +} + +const std::string& UiDocument::getId() const { + return id; +} + +const std::shared_ptr UiDocument::getRoot() const { + return root; +} + +const std::shared_ptr UiDocument::get(const std::string& id) const { + auto found = map.find(id); + if (found == map.end()) { + return nullptr; + } + return found->second; +} + +const uidocscript& UiDocument::getScript() const { + return script; +} + +int UiDocument::getEnvironment() const { + return env->getId(); +} + +void UiDocument::collect(uinodes_map& map, std::shared_ptr node) { + const std::string& id = node->getId(); + if (!id.empty()) { + map[id] = node; + } + auto container = dynamic_cast(node.get()); + if (container) { + for (auto subnode : container->getNodes()) { + collect(map, subnode); + } + } +} + +std::unique_ptr UiDocument::read(AssetsLoader& loader, int penv, std::string namesp, fs::path file) { + const std::string text = files::read_string(file); + auto xmldoc = xml::parse(file.u8string(), text); + + auto env = scripting::create_doc_environment(penv, namesp); + gui::UiXmlReader reader(*env, loader); + InventoryView::createReaders(reader); + auto view = reader.readXML( + file.u8string(), xmldoc->getRoot() + ); + view->setId("root"); + uidocscript script {}; + auto scriptFile = fs::path(file.u8string()+".lua"); + if (fs::is_regular_file(scriptFile)) { + scripting::load_layout_script(env->getId(), namesp, scriptFile, script); + } + return std::make_unique(namesp, script, view, std::move(env)); +} diff --git a/src/frontend/UiDocument.h b/src/frontend/UiDocument.h new file mode 100644 index 00000000..8dad5cb0 --- /dev/null +++ b/src/frontend/UiDocument.h @@ -0,0 +1,55 @@ +#ifndef FRONTEND_UI_DOCUMENT_H_ +#define FRONTEND_UI_DOCUMENT_H_ + +#include +#include +#include +#include + +namespace fs = std::filesystem; + +namespace gui { + class UINode; +} + +namespace scripting { + class Environment; +} + +struct uidocscript { + int environment; + bool onopen : 1; + bool onclose : 1; +}; + +using uinodes_map = std::unordered_map>; + +class AssetsLoader; + +class UiDocument { + std::string id; + uidocscript script; + uinodes_map map; + std::shared_ptr root; + std::unique_ptr env; +public: + UiDocument( + std::string id, + uidocscript script, + std::shared_ptr root, + std::unique_ptr env + ); + + const std::string& getId() const; + const uinodes_map& getMap() const; + const std::shared_ptr getRoot() const; + const std::shared_ptr get(const std::string& id) const; + const uidocscript& getScript() const; + int getEnvironment() const; + /* Collect map of all uinodes having identifiers */ + static void collect(uinodes_map& map, std::shared_ptr node); + + static std::unique_ptr read(AssetsLoader& loader, int env, std::string namesp, fs::path file); +}; + +#endif // FRONTEND_UI_DOCUMENT_H_ diff --git a/src/frontend/WorldRenderer.cpp b/src/frontend/WorldRenderer.cpp index 0ee73c32..2e25d6f4 100644 --- a/src/frontend/WorldRenderer.cpp +++ b/src/frontend/WorldRenderer.cpp @@ -106,7 +106,7 @@ void WorldRenderer::drawChunks(Chunks* chunks, } float px = camera->position.x / (float)CHUNK_W; float pz = camera->position.z / (float)CHUNK_D; - std::sort(indices.begin(), indices.end(), [this, chunks, px, pz](size_t i, size_t j) { + std::sort(indices.begin(), indices.end(), [chunks, px, pz](size_t i, size_t j) { auto a = chunks->chunks[i]; auto b = chunks->chunks[j]; return ((a->x + 0.5f - px)*(a->x + 0.5f - px) + @@ -157,7 +157,7 @@ void WorldRenderer::draw(const GfxContext& pctx, Camera* camera, bool hudVisible shader->use(); shader->uniformMatrix("u_proj", camera->getProjection()); shader->uniformMatrix("u_view", camera->getView()); - shader->uniform1f("u_gamma", 1.0f); + shader->uniform1f("u_gamma", settings.graphics.gamma); shader->uniform1f("u_fogFactor", fogFactor); shader->uniform1f("u_fogCurve", settings.graphics.fogCurve); shader->uniform3f("u_cameraPos", camera->position); diff --git a/src/frontend/graphics/Skybox.cpp b/src/frontend/graphics/Skybox.cpp index bd41d1c4..cb481990 100644 --- a/src/frontend/graphics/Skybox.cpp +++ b/src/frontend/graphics/Skybox.cpp @@ -39,7 +39,7 @@ Skybox::Skybox(uint size, Shader* shader) -1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f, -1.0f }; - vattr attrs[] {2, 0}; + vattr attrs[] {{2}, {0}}; mesh = std::make_unique(vertices, 6, attrs); sprites.push_back(skysprite { diff --git a/src/frontend/gui/GUI.cpp b/src/frontend/gui/GUI.cpp index 5dd189c9..9dfa758d 100644 --- a/src/frontend/gui/GUI.cpp +++ b/src/frontend/gui/GUI.cpp @@ -1,6 +1,6 @@ #include "GUI.h" #include "UINode.h" -#include "panels.h" +#include "containers.h" #include #include @@ -8,44 +8,41 @@ #include "../../assets/Assets.h" #include "../../graphics/Batch2D.h" #include "../../graphics/Shader.h" +#include "../../graphics/GfxContext.h" #include "../../window/Events.h" #include "../../window/input.h" #include "../../window/Camera.h" -using glm::vec2; -using glm::vec3; -using std::string; -using std::shared_ptr; using namespace gui; GUI::GUI() { - container = new Container(vec2(0, 0), vec2(1000)); - - uicamera = new Camera(vec3(), Window::height); + container = std::make_shared(glm::vec2(0, 0), glm::vec2(1000)); + uicamera = std::make_unique(glm::vec3(), Window::height); uicamera->perspective = false; uicamera->flipped = true; - menu = new PagesControl(); + menu = std::make_shared(); container->add(menu); - container->scrollable(false); + container->setScrollable(false); } GUI::~GUI() { - delete uicamera; - delete container; } -PagesControl* GUI::getMenu() { +std::shared_ptr GUI::getMenu() { return menu; } +/** Mouse related input and logic handling + * @param delta delta time +*/ void GUI::actMouse(float delta) { auto hover = container->getAt(Events::cursor, nullptr); if (this->hover && this->hover != hover) { - this->hover->hover(false); + this->hover->setHover(false); } if (hover) { - hover->hover(true); + hover->setHover(true); if (Events::scroll) { hover->scrolled(Events::scroll); } @@ -83,8 +80,11 @@ void GUI::actMouse(float delta) { } } +/** Processing user input and UI logic + * @param delta delta time +*/ void GUI::act(float delta) { - container->size(vec2(Window::width, Window::height)); + container->setSize(glm::vec2(Window::width, Window::height)); container->act(delta); auto prevfocus = focus; @@ -111,48 +111,47 @@ void GUI::act(float delta) { } } } - if (focus && !focus->isfocused()) { + if (focus && !focus->isFocused()) { focus = nullptr; } } -void GUI::draw(Batch2D* batch, Assets* assets) { - menu->setCoord((Window::size() - menu->size()) / 2.0f); - uicamera->setFov(Window::height); +void GUI::draw(const GfxContext* pctx, Assets* assets) { + auto& viewport = pctx->getViewport(); + glm::vec2 wsize = viewport.size(); + + menu->setCoord((wsize - menu->getSize()) / 2.0f); + uicamera->setFov(wsize.y); Shader* uishader = assets->getShader("ui"); uishader->use(); uishader->uniformMatrix("u_projview", uicamera->getProjection()*uicamera->getView()); - batch->begin(); - container->draw(batch, assets); + pctx->getBatch2D()->begin(); + container->draw(pctx, assets); } -shared_ptr GUI::getFocused() const { +std::shared_ptr GUI::getFocused() const { return focus; } bool GUI::isFocusCaught() const { - return focus && focus->isfocuskeeper(); + return focus && focus->isFocuskeeper(); } -void GUI::addBack(std::shared_ptr panel) { - container->addBack(panel); +void GUI::add(std::shared_ptr node) { + container->add(node); } -void GUI::add(shared_ptr panel) { - container->add(panel); +void GUI::remove(std::shared_ptr node) noexcept { + container->remove(node); } -void GUI::remove(shared_ptr panel) { - container->remove(panel); -} - -void GUI::store(string name, shared_ptr node) { +void GUI::store(std::string name, std::shared_ptr node) { storage[name] = node; } -shared_ptr GUI::get(string name) { +std::shared_ptr GUI::get(std::string name) noexcept { auto found = storage.find(name); if (found == storage.end()) { return nullptr; @@ -160,11 +159,11 @@ shared_ptr GUI::get(string name) { return found->second; } -void GUI::remove(string name) { +void GUI::remove(std::string name) noexcept { storage.erase(name); } -void GUI::setFocus(shared_ptr node) { +void GUI::setFocus(std::shared_ptr node) { if (focus) { focus->defocus(); } @@ -173,3 +172,7 @@ void GUI::setFocus(shared_ptr node) { focus->focus(this); } } + +std::shared_ptr GUI::getContainer() const { + return container; +} diff --git a/src/frontend/gui/GUI.h b/src/frontend/gui/GUI.h index 3e159f4a..fa9437ff 100644 --- a/src/frontend/gui/GUI.h +++ b/src/frontend/gui/GUI.h @@ -8,7 +8,7 @@ #include #include -class Batch2D; +class GfxContext; class Assets; class Camera; @@ -44,42 +44,76 @@ class Camera; */ namespace gui { - typedef std::function runnable; - typedef std::function stringconsumer; - class UINode; class Container; class PagesControl; + /** The main UI controller */ class GUI { - Container* container; + std::shared_ptr container; std::shared_ptr hover = nullptr; std::shared_ptr pressed = nullptr; std::shared_ptr focus = nullptr; std::unordered_map> storage; - Camera* uicamera; - PagesControl* menu; + std::unique_ptr uicamera; + std::shared_ptr menu; void actMouse(float delta); public: GUI(); ~GUI(); - PagesControl* getMenu(); + /** Get the main menu (PagesControl) node */ + std::shared_ptr getMenu(); + /** Get current focused node + * @return focused node or nullptr */ std::shared_ptr getFocused() const; + + /** Check if all user input is caught by some element like TextBox */ bool isFocusCaught() const; + /** Main input handling and logic update method + * @param delta delta time */ void act(float delta); - void draw(Batch2D* batch, Assets* assets); - void addBack(std::shared_ptr panel); - void add(std::shared_ptr panel); - void remove(std::shared_ptr panel); + + /** Draw all visible elements on main container + * @param pctx parent graphics context + * @param assets active assets storage */ + void draw(const GfxContext* pctx, Assets* assets); + + /** Add node to the main container */ + void add(std::shared_ptr node); + + /** Remove node from the main container */ + void remove(std::shared_ptr node) noexcept; + + /** Store node in the GUI nodes dictionary + * (does not add node to the main container) + * @param name node key + * @param node target node + */ void store(std::string name, std::shared_ptr node); - std::shared_ptr get(std::string name); - void remove(std::string name); + + /** Get node from the GUI nodes dictionary + * @param name node key + * @return stored node or nullptr + */ + std::shared_ptr get(std::string name) noexcept; + + /** Remove node from the GUI nodes dictionary + * @param name node key + */ + void remove(std::string name) noexcept; + + /** Set node as focused + * @param node new focused node or nullptr to remove focus + */ void setFocus(std::shared_ptr node); + + /** Get the main container */ + std::shared_ptr getContainer() const; }; } -#endif // FRONTEND_GUI_GUI_H_ \ No newline at end of file +#endif // FRONTEND_GUI_GUI_H_ diff --git a/src/frontend/gui/UINode.cpp b/src/frontend/gui/UINode.cpp index 239da047..9ca0c150 100644 --- a/src/frontend/gui/UINode.cpp +++ b/src/frontend/gui/UINode.cpp @@ -2,42 +2,37 @@ #include "../../graphics/Batch2D.h" -using std::shared_ptr; - using gui::UINode; using gui::Align; -using glm::vec2; -using glm::vec4; - -UINode::UINode(vec2 coord, vec2 size) : coord(coord), size_(size) { +UINode::UINode(glm::vec2 coord, glm::vec2 size) : coord(coord), size(size) { } UINode::~UINode() { } -bool UINode::visible() const { - return isvisible; +bool UINode::isVisible() const { + return visible; } -void UINode::visible(bool flag) { - isvisible = flag; +void UINode::setVisible(bool flag) { + visible = flag; } -Align UINode::align() const { - return align_; +Align UINode::getAlign() const { + return align; } -void UINode::align(Align align) { - align_ = align; +void UINode::setAlign(Align align) { + this->align = align; } -void UINode::hover(bool flag) { - hover_ = flag; +void UINode::setHover(bool flag) { + hover = flag; } -bool UINode::hover() const { - return hover_; +bool UINode::isHover() const { + return hover; } void UINode::setParent(UINode* node) { @@ -49,33 +44,33 @@ UINode* UINode::getParent() const { } void UINode::click(GUI*, int x, int y) { - pressed_ = true; + pressed = true; } void UINode::mouseRelease(GUI*, int x, int y) { - pressed_ = false; + pressed = false; } -bool UINode::ispressed() const { - return pressed_; +bool UINode::isPressed() const { + return pressed; } void UINode::defocus() { - focused_ = false; + focused = false; } -bool UINode::isfocused() const { - return focused_; +bool UINode::isFocused() const { + return focused; } bool UINode::isInside(glm::vec2 pos) { - vec2 coord = calcCoord(); - vec2 size = this->size(); + glm::vec2 coord = calcCoord(); + glm::vec2 size = getSize(); return (pos.x >= coord.x && pos.y >= coord.y && pos.x < coord.x + size.x && pos.y < coord.y + size.y); } -shared_ptr UINode::getAt(vec2 pos, shared_ptr self) { +std::shared_ptr UINode::getAt(glm::vec2 pos, std::shared_ptr self) { if (!interactive) { return nullptr; } @@ -83,14 +78,22 @@ shared_ptr UINode::getAt(vec2 pos, shared_ptr self) { } bool UINode::isInteractive() const { - return interactive && visible(); + return interactive && isVisible(); } void UINode::setInteractive(bool flag) { interactive = flag; } -vec2 UINode::calcCoord() const { +void UINode::setResizing(bool flag) { + resizing = flag; +} + +bool UINode::isResizing() const { + return resizing; +} + +glm::vec2 UINode::calcCoord() const { if (parent) { return coord + parent->calcCoord() + parent->contentOffset(); } @@ -103,41 +106,87 @@ void UINode::scrolled(int value) { } } -void UINode::setCoord(vec2 coord) { +void UINode::setCoord(glm::vec2 coord) { this->coord = coord; } -vec2 UINode::size() const { - return size_; +glm::vec2 UINode::getCoord() const { + return coord; } -void UINode::size(vec2 size) { - if (sizelock) - return; - this->size_ = size; +glm::vec2 UINode::getSize() const { + return size; } -void UINode::_size(vec2 size) { - if (sizelock) - return; - this->size_ = size; +void UINode::setSize(glm::vec2 size) { + this->size = glm::vec2( + glm::max(minSize.x, size.x), glm::max(minSize.y, size.y) + ); } -void UINode::color(vec4 color) { - this->color_ = color; +glm::vec2 UINode::getMinSize() const { + return minSize; } -vec4 UINode::color() const { - return color_; +void UINode::setMinSize(glm::vec2 minSize) { + this->minSize = minSize; + setSize(getSize()); } -void UINode::margin(vec4 margin) { - this->margin_ = margin; +void UINode::setColor(glm::vec4 color) { + this->color = color; + this->hoverColor = color; } -vec4 UINode::margin() const { - return margin_; +void UINode::setHoverColor(glm::vec4 newColor) { + this->hoverColor = newColor; +} + +glm::vec4 UINode::getHoverColor() const { + return hoverColor; +} + +glm::vec4 UINode::getColor() const { + return color; +} + +void UINode::setMargin(glm::vec4 margin) { + this->margin = margin; +} + +glm::vec4 UINode::getMargin() const { + return margin; +} + +void UINode::setZIndex(int zindex) { + this->zindex = zindex; +} + +int UINode::getZIndex() const { + return zindex; } void UINode::lock() { -} \ No newline at end of file +} + +vec2supplier UINode::getPositionFunc() const { + return positionfunc; +} + +void UINode::setPositionFunc(vec2supplier func) { + positionfunc = func; +} + +void UINode::setId(const std::string& id) { + this->id = id; +} + +const std::string& UINode::getId() const { + return id; +} + +void UINode::reposition() { + if (positionfunc) { + setCoord(positionfunc()); + } +} diff --git a/src/frontend/gui/UINode.h b/src/frontend/gui/UINode.h index 6f909ba5..cdc380d9 100644 --- a/src/frontend/gui/UINode.h +++ b/src/frontend/gui/UINode.h @@ -4,88 +4,142 @@ #include #include #include +#include #include +#include "../../delegates.h" -class Batch2D; +class GfxContext; class Assets; namespace gui { class UINode; class GUI; - typedef std::function onaction; - typedef std::function onnumberchange; + using onaction = std::function; + using onnumberchange = std::function; enum class Align { left, center, right }; + class UINode { + /** + * element identifier used for direct access in UiDocument + */ + std::string id = ""; protected: glm::vec2 coord; - glm::vec2 size_; - glm::vec4 color_ {1.0f}; - glm::vec4 margin_ {1.0f}; - bool isvisible = true; - bool sizelock = false; - bool hover_ = false; - bool pressed_ = false; - bool focused_ = false; + glm::vec2 size; + glm::vec2 minSize {1.0f}; + glm::vec4 color {1.0f}; + glm::vec4 hoverColor {1.0f}; + glm::vec4 margin {1.0f}; + bool visible = true; + bool hover = false; + bool pressed = false; + bool focused = false; bool interactive = true; - Align align_ = Align::left; + bool resizing = true; + int zindex = 0; + Align align = Align::left; UINode* parent = nullptr; + vec2supplier positionfunc = nullptr; UINode(glm::vec2 coord, glm::vec2 size); public: virtual ~UINode(); + /** Called every frame for all visible elements + * @param delta delta time + */ virtual void act(float delta) {}; - virtual void draw(Batch2D* batch, Assets* assets) = 0; + virtual void draw(const GfxContext* pctx, Assets* assets) = 0; - virtual void visible(bool flag); - bool visible() const; + virtual void setVisible(bool flag); + bool isVisible() const; - virtual void align(Align align); - Align align() const; + virtual void setAlign(Align align); + Align getAlign() const; - virtual void hover(bool flag); - bool hover() const; + virtual void setHover(bool flag); + bool isHover() const; virtual void setParent(UINode* node); UINode* getParent() const; - virtual void color(glm::vec4 newColor); - glm::vec4 color() const; + /** Set element color (doesn't affect inner elements). + Also replaces hover color to avoid adding extra properties. */ + virtual void setColor(glm::vec4 newColor); - virtual void margin(glm::vec4 margin); - glm::vec4 margin() const; + /** Get element color (float R,G,B,A in range [0.0, 1.0])*/ + glm::vec4 getColor() const; - virtual void focus(GUI*) {focused_ = true;} + virtual void setHoverColor(glm::vec4 newColor); + glm::vec4 getHoverColor() const; + + virtual void setMargin(glm::vec4 margin); + glm::vec4 getMargin() const; + + /** Influences container elements sort order + Doesn't work in Panel */ + virtual void setZIndex(int idx); + int getZIndex() const; + + virtual void focus(GUI*) {focused = true;} virtual void click(GUI*, int x, int y); virtual void clicked(GUI*, int button) {} virtual void mouseMove(GUI*, int x, int y) {}; virtual void mouseRelease(GUI*, int x, int y); virtual void scrolled(int value); - bool ispressed() const; + bool isPressed() const; void defocus(); - bool isfocused() const; - virtual bool isfocuskeeper() const {return false;} + bool isFocused() const; + + /** Check if element catches all user input when focused */ + virtual bool isFocuskeeper() const {return false;} virtual void typed(unsigned int codepoint) {}; virtual void keyPressed(int key) {}; + /** Check if screen position is inside of the element + * @param pos screen position */ virtual bool isInside(glm::vec2 pos); + + /** Get element under the cursor. + * @param pos cursor screen position + * @param self shared pointer to element + * @return self, sub-element or nullptr if element is not interractive */ virtual std::shared_ptr getAt(glm::vec2 pos, std::shared_ptr self); + /* Check if element is opaque for cursor */ virtual bool isInteractive() const; + /* Make the element opaque (true) or transparent (false) for cursor */ virtual void setInteractive(bool flag); + virtual void setResizing(bool flag); + virtual bool isResizing() const; + + /* Get inner content offset. Used for scroll */ virtual glm::vec2 contentOffset() {return glm::vec2(0.0f);}; - glm::vec2 calcCoord() const; + /* Calculate screen position of the element */ + virtual glm::vec2 calcCoord() const; virtual void setCoord(glm::vec2 coord); - virtual glm::vec2 size() const; - virtual void size(glm::vec2 size); - void _size(glm::vec2 size); + virtual glm::vec2 getCoord() const; + virtual glm::vec2 getSize() const; + virtual void setSize(glm::vec2 size); + virtual glm::vec2 getMinSize() const; + virtual void setMinSize(glm::vec2 size); + /* Called in containers when new element added */ virtual void refresh() {}; virtual void lock(); + + virtual vec2supplier getPositionFunc() const; + virtual void setPositionFunc(vec2supplier); + + void setId(const std::string& id); + const std::string& getId() const; + + /* Fetch coord from positionfunc if assigned */ + void reposition(); }; } diff --git a/src/frontend/gui/containers.cpp b/src/frontend/gui/containers.cpp new file mode 100644 index 00000000..bea33bec --- /dev/null +++ b/src/frontend/gui/containers.cpp @@ -0,0 +1,304 @@ +#include "containers.h" + +#include +#include + +#include "../../window/Window.h" +#include "../../assets/Assets.h" +#include "../../graphics/Batch2D.h" +#include "../../graphics/GfxContext.h" + +using namespace gui; + +Container::Container(glm::vec2 coord, glm::vec2 size) : UINode(coord, size) { + actualLength = size.y; + setColor(glm::vec4()); +} + +std::shared_ptr Container::getAt(glm::vec2 pos, std::shared_ptr self) { + if (!interactive) { + return nullptr; + } + if (!isInside(pos)) return nullptr; + + for (int i = nodes.size()-1; i >= 0; i--) { + auto& node = nodes[i]; + if (!node->isVisible()) + continue; + auto hover = node->getAt(pos, node); + if (hover != nullptr) { + return hover; + } + } + return UINode::getAt(pos, self); +} + +void Container::act(float delta) { + for (IntervalEvent& event : intervalEvents) { + event.timer += delta; + if (event.timer > event.interval) { + event.callback(); + event.timer = fmod(event.timer, event.interval); + if (event.repeat > 0) { + event.repeat--; + } + } + } + intervalEvents.erase(std::remove_if( + intervalEvents.begin(), intervalEvents.end(), + [](const IntervalEvent& event) { + return event.repeat == 0; + } + ), intervalEvents.end()); + + for (auto node : nodes) { + if (node->isVisible()) { + node->act(delta); + } + } +} + +void Container::scrolled(int value) { + int diff = (actualLength-getSize().y); + if (diff > 0 && scrollable) { + scroll += value * 40; + if (scroll > 0) + scroll = 0; + if (-scroll > diff) { + scroll = -diff; + } + } else if (parent) { + parent->scrolled(value); + } +} + +void Container::setScrollable(bool flag) { + scrollable = flag; +} + +void Container::draw(const GfxContext* pctx, Assets* assets) { + glm::vec2 coord = calcCoord(); + glm::vec2 size = getSize(); + drawBackground(pctx, assets); + + auto batch = pctx->getBatch2D(); + batch->texture(nullptr); + batch->render(); + { + GfxContext ctx = pctx->sub(); + ctx.scissors(glm::vec4(coord.x, coord.y, size.x, size.y)); + for (auto node : nodes) { + if (node->isVisible()) + node->draw(pctx, assets); + } + batch->render(); + } +} + +void Container::drawBackground(const GfxContext* pctx, Assets* assets) { + if (color.a <= 0.0f) + return; + glm::vec2 coord = calcCoord(); + + auto batch = pctx->getBatch2D(); + batch->texture(nullptr); + batch->color = color; + batch->rect(coord.x, coord.y, size.x, size.y); +} + +void Container::addBack(std::shared_ptr node) { + nodes.insert(nodes.begin(), node); + node->setParent(this); + refresh(); +} + +void Container::add(std::shared_ptr node) { + nodes.push_back(node); + node->setParent(this); + node->reposition(); + refresh(); +} + +void Container::add(std::shared_ptr node, glm::vec2 coord) { + node->setCoord(coord); + add(node); +} + +void Container::remove(std::shared_ptr selected) { + selected->setParent(nullptr); + nodes.erase(std::remove_if(nodes.begin(), nodes.end(), + [selected](const std::shared_ptr node) { + return node == selected; + } + ), nodes.end()); + refresh(); +} + +void Container::listenInterval(float interval, ontimeout callback, int repeat) { + intervalEvents.push_back({callback, interval, 0.0f, repeat}); +} + +void Container::setSize(glm::vec2 size) { + if (size == getSize()) { + refresh(); + return; + } + UINode::setSize(size); + refresh(); + for (auto& node : nodes) { + node->reposition(); + } +} + +void Container::refresh() { + std::stable_sort(nodes.begin(), nodes.end(), [](const auto& a, const auto& b) { + return a->getZIndex() < b->getZIndex(); + }); +} + +const std::vector>& Container::getNodes() const { + return nodes; +} + +Panel::Panel(glm::vec2 size, glm::vec4 padding, float interval) + : Container(glm::vec2(), size), + padding(padding), + interval(interval) { + setColor(glm::vec4(0.0f, 0.0f, 0.0f, 0.75f)); +} + +Panel::~Panel() { +} + +void Panel::setMaxLength(int value) { + maxLength = value; +} + +int Panel::getMaxLength() const { + return maxLength; +} + +void Panel::setPadding(glm::vec4 padding) { + this->padding = padding; + refresh(); +} + +glm::vec4 Panel::getPadding() const { + return padding; +} + +void Panel::cropToContent() { + if (maxLength > 0.0f) { + setSize(glm::vec2(getSize().x, glm::min(maxLength, actualLength))); + } else { + setSize(glm::vec2(getSize().x, actualLength)); + } +} + +void Panel::add(std::shared_ptr node) { + Container::add(node); + refresh(); + cropToContent(); +} + +void Panel::refresh() { + UINode::refresh(); + float x = padding.x; + float y = padding.y; + glm::vec2 size = getSize(); + if (orientation == Orientation::vertical) { + float maxw = size.x; + for (auto& node : nodes) { + glm::vec2 nodesize = node->getSize(); + const glm::vec4 margin = node->getMargin(); + y += margin.y; + + float ex = x + margin.x; + node->setCoord(glm::vec2(ex, y)); + y += nodesize.y + margin.w + interval; + + float width = size.x - padding.x - padding.z - margin.x - margin.z; + if (node->isResizing()) { + node->setSize(glm::vec2(width, nodesize.y)); + } + node->refresh(); + maxw = fmax(maxw, ex+node->getSize().x+margin.z+padding.z); + } + actualLength = y + padding.w; + } else { + float maxh = size.y; + for (auto& node : nodes) { + glm::vec2 nodesize = node->getSize(); + const glm::vec4 margin = node->getMargin(); + x += margin.x; + node->setCoord(glm::vec2(x, y+margin.y)); + x += nodesize.x + margin.z + interval; + + node->refresh(); + maxh = fmax(maxh, y+margin.y+node->getSize().y+margin.w+padding.w); + } + actualLength = size.y; + } +} + +void Panel::setOrientation(Orientation orientation) { + this->orientation = orientation; +} + +Orientation Panel::getOrientation() const { + return orientation; +} + +PagesControl::PagesControl() : Container(glm::vec2(), glm::vec2(1)){ +} + +bool PagesControl::has(std::string name) { + return pages.find(name) != pages.end(); +} + +void PagesControl::addPage(std::string name, std::shared_ptr panel) { + pages[name] = Page{panel}; +} + +void PagesControl::setPage(std::string name, bool history) { + auto found = pages.find(name); + if (found == pages.end()) { + throw std::runtime_error("no page found"); + } + if (current.panel) { + Container::remove(current.panel); + } + if (history) { + pageStack.push(curname); + } + curname = name; + current = found->second; + Container::add(current.panel); + setSize(current.panel->getSize()); +} + +void PagesControl::back() { + if (pageStack.empty()) + return; + std::string name = pageStack.top(); + pageStack.pop(); + setPage(name, false); +} + +Page& PagesControl::getCurrent() { + return current; +} + +void PagesControl::clearHistory() { + pageStack = std::stack(); +} + +void PagesControl::reset() { + clearHistory(); + if (current.panel) { + curname = ""; + Container::remove(current.panel); + current = Page{nullptr}; + } +} + diff --git a/src/frontend/gui/panels.h b/src/frontend/gui/containers.h similarity index 56% rename from src/frontend/gui/panels.h rename to src/frontend/gui/containers.h index 6bc1018c..6ebe21b4 100644 --- a/src/frontend/gui/panels.h +++ b/src/frontend/gui/containers.h @@ -1,5 +1,5 @@ -#ifndef FRONTEND_GUI_PANELS_H_ -#define FRONTEND_GUI_PANELS_H_ +#ifndef FRONTEND_GUI_CONTAINERS_H_ +#define FRONTEND_GUI_CONTAINERS_H_ #include #include @@ -12,7 +12,8 @@ class Batch2D; class Assets; namespace gui { - typedef std::function ontimeout; + using ontimeout = std::function; + struct IntervalEvent { ontimeout callback; float interval; @@ -29,46 +30,56 @@ namespace gui { std::vector intervalEvents; int scroll = 0; int actualLength = 0; - bool scrollable_ = true; + bool scrollable = true; public: Container(glm::vec2 coord, glm::vec2 size); virtual void act(float delta) override; - virtual void drawBackground(Batch2D* batch, Assets* assets) {}; - virtual void draw(Batch2D* batch, Assets* assets) override; + virtual void drawBackground(const GfxContext* pctx, Assets* assets); + virtual void draw(const GfxContext* pctx, Assets* assets) override; virtual std::shared_ptr getAt(glm::vec2 pos, std::shared_ptr self) override; virtual void addBack(std::shared_ptr node); virtual void add(std::shared_ptr node); - virtual void add(UINode* node); virtual void add(std::shared_ptr node, glm::vec2 coord); virtual void remove(std::shared_ptr node); virtual void scrolled(int value) override; - virtual void scrollable(bool flag); + virtual void setScrollable(bool flag); void listenInterval(float interval, ontimeout callback, int repeat=-1); virtual glm::vec2 contentOffset() override {return glm::vec2(0.0f, scroll);}; + virtual void setSize(glm::vec2 size) override; + virtual void refresh() override; + + const std::vector>& getNodes() const; }; class Panel : public Container { protected: - Orientation orientation_ = Orientation::vertical; + Orientation orientation = Orientation::vertical; glm::vec4 padding {2.0f}; float interval = 2.0f; - bool resizing_; - int maxLength_ = 0; + int maxLength = 0; public: - Panel(glm::vec2 size, glm::vec4 padding=glm::vec4(2.0f), float interval=2.0f, bool resizing=true); + Panel( + glm::vec2 size, + glm::vec4 padding=glm::vec4(2.0f), + float interval=2.0f + ); virtual ~Panel(); - virtual void drawBackground(Batch2D* batch, Assets* assets) override; + virtual void cropToContent(); - virtual void orientation(Orientation orientation); - Orientation orientation() const; + virtual void setOrientation(Orientation orientation); + Orientation getOrientation() const; + + virtual void add(std::shared_ptr node) override; virtual void refresh() override; - virtual void lock() override; - virtual void maxLength(int value); - int maxLength() const; + virtual void setMaxLength(int value); + int getMaxLength() const; + + virtual void setPadding(glm::vec4 padding); + glm::vec4 getPadding() const; }; struct Page { @@ -83,20 +94,19 @@ namespace gui { protected: std::unordered_map pages; std::stack pageStack; - Page current_; - std::string curname_ = ""; + Page current; + std::string curname = ""; public: PagesControl(); bool has(std::string name); - void set(std::string name, bool history=true); - void add(std::string name, std::shared_ptr panel); - void add(std::string name, UINode* panel); + void setPage(std::string name, bool history=true); + void addPage(std::string name, std::shared_ptr panel); void back(); void clearHistory(); void reset(); - Page& current(); + Page& getCurrent(); }; } -#endif // FRONTEND_GUI_PANELS_H_ \ No newline at end of file +#endif // FRONTEND_GUI_CONTAINERS_H_ diff --git a/src/frontend/gui/controls.cpp b/src/frontend/gui/controls.cpp index 54188b43..9979b6f1 100644 --- a/src/frontend/gui/controls.cpp +++ b/src/frontend/gui/controls.cpp @@ -6,140 +6,175 @@ #include "../../assets/Assets.h" #include "../../graphics/Batch2D.h" #include "../../graphics/Font.h" +#include "../../graphics/Texture.h" +#include "../../graphics/GfxContext.h" #include "../../util/stringutil.h" #include "GUI.h" -using std::string; -using std::wstring; -using std::shared_ptr; -using glm::vec2; -using glm::vec3; -using glm::vec4; - -const uint KEY_ESCAPE = 256; -const uint KEY_ENTER = 257; -const uint KEY_BACKSPACE = 259; - using namespace gui; -Label::Label(string text, string fontName) - : UINode(vec2(), vec2(text.length() * 8, 15)), - text_(util::str2wstr_utf8(text)), - fontName_(fontName) { -} - - -Label::Label(wstring text, string fontName) - : UINode(vec2(), vec2(text.length() * 8, 15)), - text_(text), - fontName_(fontName) { -} - -Label& Label::text(wstring text) { - this->text_ = text; - return *this; -} - -wstring Label::text() const { - return text_; -} - -void Label::draw(Batch2D* batch, Assets* assets) { - if (supplier) { - text(supplier()); - } - batch->color = color_; - Font* font = assets->getFont(fontName_); - vec2 size = UINode::size(); - vec2 newsize = vec2(font->calcWidth(text_), font->lineHeight()); - if (newsize.x > size.x) { - this->size(newsize); - size = newsize; - } - vec2 coord = calcCoord(); - font->draw(batch, text_, coord.x, coord.y); -} - -Label* Label::textSupplier(wstringsupplier supplier) { - this->supplier = supplier; - return this; -} - -void Label::size(vec2 sizenew) { - UINode::size(vec2(UINode::size().x, sizenew.y)); -} - -// ================================= Image ==================================== -Image::Image(string texture, vec2 size) : UINode(vec2(), size), texture(texture) { +Label::Label(std::string text, std::string fontName) + : UINode(glm::vec2(), glm::vec2(text.length() * 8, 15)), + text(util::str2wstr_utf8(text)), + fontName(fontName) { setInteractive(false); } -void Image::draw(Batch2D* batch, Assets* assets) { - vec2 coord = calcCoord(); - batch->texture(assets->getTexture(texture)); - batch->color = color_; - batch->rect(coord.x, coord.y, size_.x, size_.y, 0, 0, 0, UVRegion(), false, true, color_); + +Label::Label(std::wstring text, std::string fontName) + : UINode(glm::vec2(), glm::vec2(text.length() * 8, 15)), + text(text), + fontName(fontName) { + setInteractive(false); +} + +void Label::setText(std::wstring text) { + this->text = text; +} + +std::wstring Label::getText() const { + return text; +} + +void Label::draw(const GfxContext* pctx, Assets* assets) { + if (supplier) { + setText(supplier()); + } + + auto batch = pctx->getBatch2D(); + batch->color = getColor(); + Font* font = assets->getFont(fontName); + glm::vec2 size = getSize(); + glm::vec2 newsize ( + font->calcWidth(text), + font->getLineHeight()+font->getYOffset() + ); + + glm::vec2 coord = calcCoord(); + switch (align) { + case Align::left: + break; + case Align::center: + coord.x += (size.x-newsize.x)*0.5f; + break; + case Align::right: + coord.x += size.x-newsize.x; + break; + } + coord.y += (size.y-newsize.y)*0.5f; + font->draw(batch, text, coord.x, coord.y); +} + +void Label::textSupplier(wstringsupplier supplier) { + this->supplier = supplier; +} + +// ================================= Image ==================================== +Image::Image(std::string texture, glm::vec2 size) : UINode(glm::vec2(), size), texture(texture) { + setInteractive(false); +} + +void Image::draw(const GfxContext* pctx, Assets* assets) { + glm::vec2 coord = calcCoord(); + glm::vec4 color = getColor(); + auto batch = pctx->getBatch2D(); + + auto texture = assets->getTexture(this->texture); + if (texture && autoresize) { + setSize(glm::vec2(texture->width, texture->height)); + } + batch->texture(texture); + batch->color = color; + batch->rect(coord.x, coord.y, size.x, size.y, + 0, 0, 0, UVRegion(), false, true, color); +} + +void Image::setAutoResize(bool flag) { + autoresize = flag; +} +bool Image::isAutoResize() const { + return autoresize; } // ================================= Button =================================== -Button::Button(shared_ptr content, glm::vec4 padding) - : Panel(content->size()+vec2(padding[0]+padding[2]+content->margin()[0]+content->margin()[2], - padding[1]+padding[3]+content->margin()[1]+content->margin()[3]), padding, 0) { +Button::Button(std::shared_ptr content, glm::vec4 padding) + : Panel(glm::vec2(), padding, 0) { + glm::vec4 margin = getMargin(); + setSize(content->getSize()+ + glm::vec2(padding[0]+padding[2]+margin[0]+margin[2], + padding[1]+padding[3]+margin[1]+margin[3])); add(content); - scrollable(false); + setScrollable(false); + setHoverColor(glm::vec4(0.05f, 0.1f, 0.15f, 0.75f)); + content->setInteractive(false); } -Button::Button(wstring text, glm::vec4 padding, glm::vec4 margin) - : Panel(vec2(32,32), padding, 0) { - this->margin(margin); - Label* label = new Label(text); - label->align(Align::center); - this->label = shared_ptr(label); - add(this->label); - scrollable(false); +Button::Button( + std::wstring text, + glm::vec4 padding, + onaction action, + glm::vec2 size +) : Panel(size, padding, 0) +{ + if (size.y < 0.0f) { + size = glm::vec2( + glm::max(padding.x + padding.z + text.length()*8, size.x), + glm::max(padding.y + padding.w + 16, size.y) + ); + } + setSize(size); + + if (action) { + listenAction(action); + } + setScrollable(false); + + label = std::make_shared