Merge remote-tracking branch 'upstream/main'

This commit is contained in:
DanielProl1xy 2024-02-19 10:23:29 +03:00
commit 0e38698dae
154 changed files with 6818 additions and 2760 deletions

42
.github/workflows/appimage-wayland.yml vendored Normal file
View File

@ -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*'

View File

@ -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

View File

@ -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})

View File

@ -1,6 +1,6 @@
{
"id": "base",
"title": "Base",
"version": "0.18",
"version": "0.19",
"description": "basic content package"
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

After

Width:  |  Height:  |  Size: 779 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

After

Width:  |  Height:  |  Size: 357 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.2 KiB

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

View File

@ -0,0 +1,4 @@
<inventory color="#1F1F1FE0">
<slots-grid pos="0, 164" rows="1" count="10" sharefunc="inventory_share_func"/>
<slots-grid rows="3" start-index="10" count="30" sharefunc="inventory_share_func"/>
</inventory>

View File

@ -0,0 +1,3 @@
function inventory_share_func(invid, slotid)
inventory.set(invid, slotid, 0, 0)
end

0
res/modules/document.lua Normal file
View File

65
res/modules/toml.lua Normal file
View File

@ -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

View File

@ -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

View File

@ -0,0 +1,3 @@
-- use for engine development tests
-- must be empty in release
-- must not be modified by content-packs

View File

@ -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;

View File

@ -34,6 +34,7 @@ settings.Load Speed=Хуткасць Загрузкі
settings.Fog Curve=Крывая Туману
settings.Backlight=Падсветка
settings.V-Sync=Вертыкальная Сінхранізацыя
settings.Camera Shaking=Труска Камеры
settings.FOV=Поле Зроку
settings.Mouse Sensitivity=Адчувальнасць Мышы

View File

@ -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

View File

@ -34,6 +34,7 @@ settings.Load Speed=Скорость Загрузки
settings.Fog Curve=Кривая Тумана
settings.Backlight=Подсветка
settings.V-Sync=Вертикальная Синхронизация
settings.Camera Shaking=Тряска Камеры
settings.FOV=Поле Зрения
settings.Mouse Sensitivity=Чувствительность Мыши

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

BIN
res/textures/gui/error.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 702 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 658 B

View File

@ -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);

View File

@ -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<std::string, std::shared_ptr<Texture>> textures;
std::unordered_map<std::string, std::shared_ptr<Shader>> shaders;
std::unordered_map<std::string, std::shared_ptr<Font>> fonts;
std::unordered_map<std::string, std::shared_ptr<Atlas>> atlases;
std::unordered_map<std::string, std::shared_ptr<UiDocument>> layouts;
std::vector<TextureAnimation> animations;
public:
~Assets();
@ -36,6 +44,9 @@ public:
const std::vector<TextureAnimation>& getAnimations();
void store(const TextureAnimation& animation);
UiDocument* getLayout(std::string name) const;
void store(UiDocument* layout, std::string name);
void extend(const Assets& assets);
};

View File

@ -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<void> 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<LayoutCfg>(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;
}
}

View File

@ -2,6 +2,7 @@
#define ASSETS_ASSETS_LOADER_H
#include <string>
#include <memory>
#include <functional>
#include <map>
#include <queue>
@ -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<bool(Assets*, const ResPaths*, const std::string&, const std::string&)> aloader_func;
using aloader_func = std::function<bool(AssetsLoader&, Assets*, const ResPaths*, const std::string&, const std::string&, std::shared_ptr<void>)>;
struct aloader_entry {
int tag;
const std::string filename;
const std::string alias;
std::shared_ptr<void> 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<void> 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;
};

View File

@ -3,6 +3,7 @@
#include <iostream>
#include <filesystem>
#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<void>
) {
std::unique_ptr<Texture> 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<void>
) {
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<ImageData> 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<ImageData> 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<void>
) {
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<Texture*> 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<void>
) {
std::vector<std::unique_ptr<Texture>> 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> 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<void> config
) {
try {
LayoutCfg* cfg = reinterpret_cast<LayoutCfg*>(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<std::pair<std::string, float>> frameList;
std::vector<std::pair<std::string, float>> 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<float>(currentFrame->integer(1)) / 1000;
frameName = currentFrame->str(0);
if (currentFrame->size() > 1)
frameDuration = static_cast<float>(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<Atlas> 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;
}

View File

@ -2,33 +2,62 @@
#define ASSETS_ASSET_LOADERS_H_
#include <string>
#include <memory>
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<void> settings
);
bool shader(
AssetsLoader&,
Assets*,
const ResPaths* paths,
const std::string filename,
const std::string name,
std::shared_ptr<void> settings
);
bool atlas(
AssetsLoader&,
Assets*,
const ResPaths* paths,
const std::string directory,
const std::string name,
std::shared_ptr<void> settings
);
bool font(
AssetsLoader&,
Assets*,
const ResPaths* paths,
const std::string filename,
const std::string name,
std::shared_ptr<void> settings
);
bool layout(
AssetsLoader&,
Assets*,
const ResPaths* paths,
const std::string file,
const std::string name,
std::shared_ptr<void> settings
);
bool animation(
Assets*,
const ResPaths* paths,
const std::string directory,
const std::string name,
Atlas* dstAtlas
);
}
#endif // ASSETS_ASSET_LOADERS_H_

View File

@ -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<char*>(&a)[3 - i] = buffer[i];
}
}
return a;
}

View File

@ -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<ubyte> json::to_binary(const Map* obj) {
std::vector<ubyte> 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);

View File

@ -21,7 +21,7 @@ namespace json {
const int BJSON_TYPE_NULL = 0xC;
const int BJSON_TYPE_CDOCUMENT = 0x1F;
extern std::vector<ubyte> to_binary(const dynamic::Map* obj);
extern std::vector<ubyte> to_binary(const dynamic::Map* obj, bool compress=false);
extern std::unique_ptr<dynamic::Map> from_binary(const ubyte* src, size_t size);
}

View File

@ -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<int16_t>(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<int32_t>(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<int64_t>(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<const char*>(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;
}

View File

@ -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_

View File

@ -4,19 +4,6 @@
#include <stdexcept>
#include <math.h>
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;
}

View File

@ -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);

View File

@ -34,7 +34,7 @@ std::vector<ubyte> gzip::compress(const ubyte* src, size_t size) {
std::vector<ubyte> 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<const uint32_t*>(src+size-4);
std::vector<ubyte> buffer;
buffer.resize(decompressed_size);

425
src/coders/xml.cpp Normal file
View File

@ -0,0 +1,425 @@
#include "xml.h"
#include <charconv>
#include <stdexcept>
#include <sstream>
#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<xmlelement>& 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<Node>(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<Document>(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<Node>("#");
auto text = parseText();
util::replaceAll(text, "&quot;", "\"");
util::replaceAll(text, "&apos;", "'");
util::replaceAll(text, "&lt;", "<");
util::replaceAll(text, "&gt;", ">");
util::replaceAll(text, "&amp;", "&");
element->set("#", text);
return element;
}
nextChar();
// <!--element-->
if (peek() == '!') {
if (isNext("!DOCTYPE ")) {
throw error("XML DTD is not supported yet");
}
parseComment();
return nullptr;
}
auto element = parseOpenTag();
char c = nextChar();
// <element/>
if (c == '/') {
expect('>');
}
// <element>...</element>
else if (c == '>') {
skipWhitespace();
while (!isNext("</")) {
auto sub = parseElement();
if (sub) {
element->add(sub);
}
skipWhitespace();
}
skip(2);
expect(element->getTag());
expect('>');
}
// <element?>
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, "&", "&amp;");
util::replaceAll(text, "\"","&quot;");
util::replaceAll(text, "'", "&apos;");
util::replaceAll(text, "<", "&lt;");
util::replaceAll(text, ">", "&gt;");
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 << "</" << tag << ">";
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 << "</" << tag << ">";
} else {
ss << "/>";
}
}
std::string xml::stringify(
const xmldocument document,
bool nice,
const std::string& indentStr
) {
std::stringstream ss;
// XML declaration
ss << "<?xml version=\"" << document->getVersion();
ss << "\" encoding=\"UTF-8\" ?>";
newline(ss, nice, indentStr, 0);
stringifyElement(ss, document->getRoot(), nice, indentStr, 0);
return ss.str();
}

139
src/coders/xml.h Normal file
View File

@ -0,0 +1,139 @@
#ifndef CODERS_XML_H_
#define CODERS_XML_H_
#include <string>
#include <memory>
#include <vector>
#include <glm/glm.hpp>
#include <unordered_map>
#include "commons.h"
namespace xml {
class Node;
class Attribute;
class Document;
typedef Attribute xmlattribute;
typedef std::shared_ptr<Node> xmlelement;
typedef std::shared_ptr<Document> xmldocument;
typedef std::unordered_map<std::string, xmlattribute> 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<std::string, xmlattribute> attrs;
std::vector<xmlelement> 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<xmlelement>& 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_

View File

@ -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<itemid_t>::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_

View File

@ -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<ContentPackRuntime>(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<Block*> blockDefsIndices;
DrawGroups* groups = new DrawGroups;
auto groups = std::make_unique<DrawGroups>();
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<uint32_t*>(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<uint32_t*>(def->emission);
itemDefsIndices.push_back(def);
}
auto indices = new ContentIndices(blockDefsIndices, itemDefsIndices);
std::unique_ptr<Content> content (new Content(indices, groups, blockDefs, itemDefs));
// Now, it's time to solve foreign keys
auto content = std::make_unique<Content>(
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> drawGroups,
std::unordered_map<std::string, Block*> blockDefs,
std::unordered_map<std::string, ItemDef*> itemDefs)
std::unordered_map<std::string, ItemDef*> itemDefs,
std::vector<std::unique_ptr<ContentPackRuntime>> 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<std::unique_ptr<ContentPackRuntime>>& Content::getPacks() const {
return packs;
}

View File

@ -9,11 +9,12 @@
#include <set>
#include "../typedefs.h"
typedef std::set<unsigned char> DrawGroups;
using DrawGroups = std::set<unsigned char>;
class Block;
class ItemDef;
class Content;
class ContentPackRuntime;
enum class contenttype {
none, block, item
@ -46,14 +47,17 @@ class ContentBuilder {
std::unordered_map<std::string, ItemDef*> itemDefs;
std::vector<std::string> itemIds;
std::vector<std::unique_ptr<ContentPackRuntime>> 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<std::string, Block*> blockDefs;
std::unordered_map<std::string, ItemDef*> itemDefs;
std::unique_ptr<ContentIndices> indices;
std::vector<std::unique_ptr<ContentPackRuntime>> packs;
public:
DrawGroups* const drawGroups;
std::unique_ptr<DrawGroups> const drawGroups;
Content(ContentIndices* indices, DrawGroups* drawGroups,
Content(ContentIndices* indices,
std::unique_ptr<DrawGroups> drawGroups,
std::unordered_map<std::string, Block*> blockDefs,
std::unordered_map<std::string, ItemDef*> itemDefs);
std::unordered_map<std::string, ItemDef*> itemDefs,
std::vector<std::unique_ptr<ContentPackRuntime>> 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<std::unique_ptr<ContentPackRuntime>>& getPacks() const;
};
#endif // CONTENT_CONTENT_H_

View File

@ -74,7 +74,7 @@ void ContentLoader::fixPackIndices() {
std::unique_ptr<dynamic::Map> 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);
}
}
}

View File

@ -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_

View File

@ -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<ContentPack>& packs) {
if (!fs::is_directory(rootfolder)) {
void ContentPack::scanFolder(
fs::path folder,
std::vector<ContentPack>& 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<ContentPack>& 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<ContentPack>& 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<std::string> 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<scripting::Environment> env
) : info(info), env(std::move(env))
{
}

View File

@ -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<std::string> 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<std::string> 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<ContentPack>& packs);
static void scan(EnginePaths* paths,
std::vector<ContentPack>& packs);
static void scanFolder(
fs::path folder,
std::vector<ContentPack>& packs
);
static void scan(
fs::path folder,
EnginePaths* paths,
std::vector<ContentPack>& packs
);
static void scan(
EnginePaths* paths,
std::vector<ContentPack>& packs
);
static std::vector<std::string> worldPacksList(std::filesystem::path folder);
static std::vector<std::string> 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<ContentPack>& packs,
const std::vector<std::string>& names,
std::filesystem::path worldDir);
fs::path worldDir,
std::string name
);
static void readPacks(
const EnginePaths* paths,
std::vector<ContentPack>& packs,
const std::vector<std::string>& names,
fs::path worldDir
);
};
class ContentPackRuntime {
ContentPack info;
std::unique_ptr<scripting::Environment> env;
public:
ContentPackRuntime(
ContentPack info,
std::unique_ptr<scripting::Environment> 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_

View File

@ -1,48 +1,43 @@
#include "definitions.h"
#include <glm/glm.hpp>
#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);
}

View File

@ -3,7 +3,6 @@
#include <string>
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_

View File

@ -1,13 +0,0 @@
#ifndef DECLARATIONS_H
#define DECLARATIONS_H
#include <iostream>
#include "core_defs.h"
class ContentBuilder;
extern void setup_bindings();
extern void setup_definitions(ContentBuilder* builder);
#endif // DECLARATIONS_H

23
src/delegates.h Normal file
View File

@ -0,0 +1,23 @@
#ifndef DELEGATES_H_
#define DELEGATES_H_
#include <glm/glm.hpp>
#include <functional>
#include <string>
using runnable = std::function<void()>;
// data sources
using wstringsupplier = std::function<std::wstring()>;
using doublesupplier = std::function<double()>;
using boolsupplier = std::function<bool()>;
using vec2supplier = std::function<glm::vec2()>;
using stringconsumer = std::function<void(const std::string&)>;
using wstringconsumer = std::function<void(const std::wstring&)>;
using doubleconsumer = std::function<void(double)>;
using boolconsumer = std::function<void(bool)>;
using int_array_consumer = std::function<void(const int[], size_t)>;
using wstringchecker = std::function<bool(const std::wstring&)>;
#endif // DELEGATES_H_

View File

@ -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<fs::path> roots {resdir};
resPaths.reset(new ResPaths(resdir, roots));
assets.reset(new Assets());
resPaths = std::make_unique<ResPaths>(resdir, roots);
assets = std::make_unique<Assets>();
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<MenuScreen>(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<std::string>& packs
void Engine::loadContent() {
auto resdir = paths->getResources();
ContentBuilder contentBuilder;
setup_definitions(&contentBuilder);
corecontent::setup(&contentBuilder);
paths->setContentPacks(&contentPacks);
std::vector<fs::path> 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<Assets> 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> screen) {
this->screen = screen;
}
@ -209,7 +219,7 @@ void Engine::setScreen(std::shared_ptr<Screen> 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<Screen> Engine::getScreen() {
return screen;
}
}

View File

@ -44,27 +44,76 @@ class Engine {
double delta = 0.0;
std::unique_ptr<gui::GUI> 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> screen);
EnginePaths* getPaths();
const Content* getContent() const;
std::vector<ContentPack>& 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> 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<ContentPack>& getContentPacks();
/** Get current screen */
std::shared_ptr<Screen> getScreen();
};
#endif // SRC_ENGINE_H_
#endif // SRC_ENGINE_H_

View File

@ -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 <sstream>
#include <cstring>
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<ubyte[]> 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<ubyte[]> light_data (chunk->lightmap->encode());
std::unique_ptr<ubyte[]> 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<ubyte[]>(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<ubyte> data (getData(lights, getLightsFolder(), x, z, REGION_LAYER_LIGHTS));
std::unique_ptr<ubyte[]> 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<Inventory>(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());
}

View File

@ -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<glm::ivec3, std::unique_ptr<regfile>> 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<ubyte[]> 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;
};

View File

@ -1,12 +1,13 @@
#include "engine_paths.h"
#include <filesystem>
#include <stack>
#include <sstream>
#include <filesystem>
#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<fs::path> 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<ContentPack>* contentPacks) {
this->contentPacks = contentPacks;
}
static fs::path toCanonic(fs::path path) {
std::stack<std::string> 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<fs::path> roots)
@ -125,19 +155,19 @@ ResPaths::ResPaths(fs::path mainRoot, std::vector<fs::path> 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<fs::path> ResPaths::listdir(const std::string& folderName) const {
std::vector<fs::path> 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<fs::path> 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<fs::path> ResPaths::listdir(const std::string& folderName) const {
}
return entries;
}
const fs::path& ResPaths::getMainRoot() const {
return mainRoot;
}

View File

@ -3,12 +3,18 @@
#include <string>
#include <vector>
#include <stdexcept>
#include <filesystem>
#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<fs::path> listdir(const std::string& folder) const;
const fs::path& getMainRoot() const;
};
#endif // FILES_ENGINE_PATHS_H_
#endif // FILES_ENGINE_PATHS_H_

View File

@ -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());
}

View File

@ -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);

View File

@ -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<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++) {
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<UiDocument> 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;
}

View File

@ -1,15 +1,24 @@
#ifndef FRONTEND_BLOCKS_GFX_CACHE_H_
#define FRONTEND_BLOCKS_GFX_CACHE_H_
#include <memory>
#include <string>
#include <unordered_map>
#include "../graphics/UVRegion.h"
#include "../typedefs.h"
class Content;
class Assets;
class UiDocument;
using uidocuments_map = std::unordered_map<std::string, std::shared_ptr<UiDocument>>;
class ContentGfxCache {
const Content* content;
// array of block sides uv regions (6 per block)
UVRegion* sideregions;
std::unique_ptr<UVRegion[]> 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<UiDocument> getLayout(const std::string& id);
const Content* getContent() const;
};
#endif // FRONTEND_BLOCKS_GFX_CACHE_H_

View File

@ -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<SlotLayout>& InventoryLayout::getSlots() {
return slots;
}
std::vector<InventoryPanel>& 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<InventoryLayout>(glm::vec2()))
{}
InventoryBuilder::InventoryBuilder() {
view = std::make_shared<InventoryView>();
}
/** 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<gui::Container>(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<InventoryLayout> InventoryBuilder::build() {
return std::unique_ptr<InventoryLayout>(layout.release());
std::shared_ptr<InventoryView> 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> inventory,
std::unique_ptr<InventoryLayout> 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<SlotView> InventoryView::addSlot(SlotLayout layout) {
uint width = InventoryView::SLOT_SIZE + layout.padding;
uint height = InventoryView::SLOT_SIZE + layout.padding;
auto view = std::make_shared<SlotView>(
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<SlotView>(layout);
if (!layout.background) {
slot->setColor(glm::vec4());
}
slots.push_back(slot.get());
return slot;
}
std::shared_ptr<Inventory> InventoryView::getInventory() const {
return inventory;
}
size_t InventoryView::getSlotsCount() const {
return slots.size();
}
void InventoryView::bind(
std::shared_ptr<Inventory> 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> 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<InventoryView>();
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;
});
}

View File

@ -6,12 +6,11 @@
#include <glm/glm.hpp>
#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<void(ItemStack&)> itemsharefunc;
typedef std::function<void(ItemStack&, ItemStack&)> 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<SlotLayout> slots;
std::vector<InventoryPanel> 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<SlotLayout>& getSlots();
std::vector<InventoryPanel>& getPanels();
};
class InventoryBuilder {
std::unique_ptr<InventoryLayout> 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<InventoryLayout> 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> inventory;
std::unique_ptr<InventoryLayout> layout;
LevelFrontend* frontend;
InventoryInteraction* interaction;
std::vector<SlotView*> slots;
int scroll = 0;
public:
InventoryView(
const Content* content,
LevelFrontend* frontend,
InventoryInteraction* interaction,
std::shared_ptr<Inventory> inventory,
std::unique_ptr<InventoryLayout> layout);
virtual ~InventoryView();
void build();
virtual void drawBackground(Batch2D* batch, Assets* assets) override;
void setInventory(std::shared_ptr<Inventory> 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<void(uint, ItemStack&)>;
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> inventory;
LevelFrontend* frontend = nullptr;
InventoryInteraction* interaction = nullptr;
std::vector<SlotView*> slots;
glm::vec2 origin {};
public:
InventoryView();
virtual ~InventoryView();
void setInventory(std::shared_ptr<Inventory> 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> inventory,
LevelFrontend* frontend,
InventoryInteraction* interaction
);
std::shared_ptr<SlotView> addSlot(SlotLayout layout);
std::shared_ptr<Inventory> 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<InventoryView> 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<InventoryView> build();
};
#endif // FRONTEND_INVENTORY_VIEW_H_

View File

@ -24,5 +24,4 @@ public:
Atlas* getBlocksAtlas() const;
};
#endif // FRONTEND_LEVEL_FRONTEND_H_

View File

@ -0,0 +1,79 @@
#include "UiDocument.h"
#include <iostream>
#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<gui::UINode> root,
std::unique_ptr<scripting::Environment> 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<gui::UINode> UiDocument::getRoot() const {
return root;
}
const std::shared_ptr<gui::UINode> 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<gui::UINode> node) {
const std::string& id = node->getId();
if (!id.empty()) {
map[id] = node;
}
auto container = dynamic_cast<gui::Container*>(node.get());
if (container) {
for (auto subnode : container->getNodes()) {
collect(map, subnode);
}
}
}
std::unique_ptr<UiDocument> 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<UiDocument>(namesp, script, view, std::move(env));
}

55
src/frontend/UiDocument.h Normal file
View File

@ -0,0 +1,55 @@
#ifndef FRONTEND_UI_DOCUMENT_H_
#define FRONTEND_UI_DOCUMENT_H_
#include <string>
#include <memory>
#include <filesystem>
#include <unordered_map>
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<std::string, std::shared_ptr<gui::UINode>>;
class AssetsLoader;
class UiDocument {
std::string id;
uidocscript script;
uinodes_map map;
std::shared_ptr<gui::UINode> root;
std::unique_ptr<scripting::Environment> env;
public:
UiDocument(
std::string id,
uidocscript script,
std::shared_ptr<gui::UINode> root,
std::unique_ptr<scripting::Environment> env
);
const std::string& getId() const;
const uinodes_map& getMap() const;
const std::shared_ptr<gui::UINode> getRoot() const;
const std::shared_ptr<gui::UINode> 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<gui::UINode> node);
static std::unique_ptr<UiDocument> read(AssetsLoader& loader, int env, std::string namesp, fs::path file);
};
#endif // FRONTEND_UI_DOCUMENT_H_

View File

@ -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);

View File

@ -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<Mesh>(vertices, 6, attrs);
sprites.push_back(skysprite {

View File

@ -1,6 +1,6 @@
#include "GUI.h"
#include "UINode.h"
#include "panels.h"
#include "containers.h"
#include <iostream>
#include <algorithm>
@ -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<Container>(glm::vec2(0, 0), glm::vec2(1000));
uicamera = std::make_unique<Camera>(glm::vec3(), Window::height);
uicamera->perspective = false;
uicamera->flipped = true;
menu = new PagesControl();
menu = std::make_shared<PagesControl>();
container->add(menu);
container->scrollable(false);
container->setScrollable(false);
}
GUI::~GUI() {
delete uicamera;
delete container;
}
PagesControl* GUI::getMenu() {
std::shared_ptr<PagesControl> 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<UINode> GUI::getFocused() const {
std::shared_ptr<UINode> GUI::getFocused() const {
return focus;
}
bool GUI::isFocusCaught() const {
return focus && focus->isfocuskeeper();
return focus && focus->isFocuskeeper();
}
void GUI::addBack(std::shared_ptr<UINode> panel) {
container->addBack(panel);
void GUI::add(std::shared_ptr<UINode> node) {
container->add(node);
}
void GUI::add(shared_ptr<UINode> panel) {
container->add(panel);
void GUI::remove(std::shared_ptr<UINode> node) noexcept {
container->remove(node);
}
void GUI::remove(shared_ptr<UINode> panel) {
container->remove(panel);
}
void GUI::store(string name, shared_ptr<UINode> node) {
void GUI::store(std::string name, std::shared_ptr<UINode> node) {
storage[name] = node;
}
shared_ptr<UINode> GUI::get(string name) {
std::shared_ptr<UINode> GUI::get(std::string name) noexcept {
auto found = storage.find(name);
if (found == storage.end()) {
return nullptr;
@ -160,11 +159,11 @@ shared_ptr<UINode> 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<UINode> node) {
void GUI::setFocus(std::shared_ptr<UINode> node) {
if (focus) {
focus->defocus();
}
@ -173,3 +172,7 @@ void GUI::setFocus(shared_ptr<UINode> node) {
focus->focus(this);
}
}
std::shared_ptr<Container> GUI::getContainer() const {
return container;
}

View File

@ -8,7 +8,7 @@
#include <functional>
#include <unordered_map>
class Batch2D;
class GfxContext;
class Assets;
class Camera;
@ -44,42 +44,76 @@ class Camera;
*/
namespace gui {
typedef std::function<void()> runnable;
typedef std::function<void(const std::string&)> stringconsumer;
class UINode;
class Container;
class PagesControl;
/** The main UI controller */
class GUI {
Container* container;
std::shared_ptr<Container> container;
std::shared_ptr<UINode> hover = nullptr;
std::shared_ptr<UINode> pressed = nullptr;
std::shared_ptr<UINode> focus = nullptr;
std::unordered_map<std::string, std::shared_ptr<UINode>> storage;
Camera* uicamera;
PagesControl* menu;
std::unique_ptr<Camera> uicamera;
std::shared_ptr<PagesControl> menu;
void actMouse(float delta);
public:
GUI();
~GUI();
PagesControl* getMenu();
/** Get the main menu (PagesControl) node */
std::shared_ptr<PagesControl> getMenu();
/** Get current focused node
* @return focused node or nullptr */
std::shared_ptr<UINode> 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<UINode> panel);
void add(std::shared_ptr<UINode> panel);
void remove(std::shared_ptr<UINode> 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<UINode> node);
/** Remove node from the main container */
void remove(std::shared_ptr<UINode> 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<UINode> node);
std::shared_ptr<UINode> 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<UINode> 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<UINode> node);
/** Get the main container */
std::shared_ptr<Container> getContainer() const;
};
}
#endif // FRONTEND_GUI_GUI_H_
#endif // FRONTEND_GUI_GUI_H_

View File

@ -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> UINode::getAt(vec2 pos, shared_ptr<UINode> self) {
std::shared_ptr<UINode> UINode::getAt(glm::vec2 pos, std::shared_ptr<UINode> self) {
if (!interactive) {
return nullptr;
}
@ -83,14 +78,22 @@ shared_ptr<UINode> UINode::getAt(vec2 pos, shared_ptr<UINode> 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() {
}
}
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());
}
}

View File

@ -4,88 +4,142 @@
#include <glm/glm.hpp>
#include <vector>
#include <memory>
#include <string>
#include <functional>
#include "../../delegates.h"
class Batch2D;
class GfxContext;
class Assets;
namespace gui {
class UINode;
class GUI;
typedef std::function<void(GUI*)> onaction;
typedef std::function<void(GUI*, double)> onnumberchange;
using onaction = std::function<void(GUI*)>;
using onnumberchange = std::function<void(GUI*, double)>;
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<UINode> getAt(glm::vec2 pos, std::shared_ptr<UINode> 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();
};
}

View File

@ -0,0 +1,304 @@
#include "containers.h"
#include <stdexcept>
#include <algorithm>
#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<UINode> Container::getAt(glm::vec2 pos, std::shared_ptr<UINode> 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<UINode> node) {
nodes.insert(nodes.begin(), node);
node->setParent(this);
refresh();
}
void Container::add(std::shared_ptr<UINode> node) {
nodes.push_back(node);
node->setParent(this);
node->reposition();
refresh();
}
void Container::add(std::shared_ptr<UINode> node, glm::vec2 coord) {
node->setCoord(coord);
add(node);
}
void Container::remove(std::shared_ptr<UINode> selected) {
selected->setParent(nullptr);
nodes.erase(std::remove_if(nodes.begin(), nodes.end(),
[selected](const std::shared_ptr<UINode> 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<std::shared_ptr<UINode>>& 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<UINode> 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<UINode> 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<std::string>();
}
void PagesControl::reset() {
clearHistory();
if (current.panel) {
curname = "";
Container::remove(current.panel);
current = Page{nullptr};
}
}

View File

@ -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 <glm/glm.hpp>
#include <vector>
@ -12,7 +12,8 @@ class Batch2D;
class Assets;
namespace gui {
typedef std::function<void()> ontimeout;
using ontimeout = std::function<void()>;
struct IntervalEvent {
ontimeout callback;
float interval;
@ -29,46 +30,56 @@ namespace gui {
std::vector<IntervalEvent> 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<UINode> getAt(glm::vec2 pos, std::shared_ptr<UINode> self) override;
virtual void addBack(std::shared_ptr<UINode> node);
virtual void add(std::shared_ptr<UINode> node);
virtual void add(UINode* node);
virtual void add(std::shared_ptr<UINode> node, glm::vec2 coord);
virtual void remove(std::shared_ptr<UINode> 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<std::shared_ptr<UINode>>& 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<UINode> 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<std::string, Page> pages;
std::stack<std::string> 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<UINode> panel);
void add(std::string name, UINode* panel);
void setPage(std::string name, bool history=true);
void addPage(std::string name, std::shared_ptr<UINode> panel);
void back();
void clearHistory();
void reset();
Page& current();
Page& getCurrent();
};
}
#endif // FRONTEND_GUI_PANELS_H_
#endif // FRONTEND_GUI_CONTAINERS_H_

View File

@ -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<UINode> 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<UINode> 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<UINode>(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<Label>(text);
label->setAlign(Align::center);
label->setSize(size-glm::vec2(padding.z+padding.x, padding.w+padding.y));
label->setInteractive(false);
add(label);
setHoverColor(glm::vec4(0.05f, 0.1f, 0.15f, 0.75f));
}
void Button::text(std::wstring text) {
void Button::setText(std::wstring text) {
if (label) {
Label* label = (Label*)(this->label.get());
label->text(text);
label->setText(text);
}
}
wstring Button::text() const {
std::wstring Button::getText() const {
if (label) {
Label* label = (Label*)(this->label.get());
return label->text();
return label->getText();
}
return L"";
}
Button* Button::textSupplier(wstringsupplier supplier) {
if (label) {
Label* label = (Label*)(this->label.get());
label->textSupplier(supplier);
}
return this;
}
void Button::setHoverColor(glm::vec4 color) {
hoverColor = color;
void Button::refresh() {
Panel::refresh();
if (label) {
label->setSize(size-glm::vec2(padding.z+padding.x, padding.w+padding.y));
}
}
void Button::drawBackground(Batch2D* batch, Assets* assets) {
vec2 coord = calcCoord();
void Button::drawBackground(const GfxContext* pctx, Assets* assets) {
glm::vec2 coord = calcCoord();
auto batch = pctx->getBatch2D();
batch->texture(nullptr);
batch->color = (ispressed() ? pressedColor : (hover_ ? hoverColor : color_));
batch->rect(coord.x, coord.y, size_.x, size_.y);
}
shared_ptr<UINode> Button::getAt(vec2 pos, shared_ptr<UINode> self) {
return UINode::getAt(pos, self);
batch->color = (isPressed() ? pressedColor : (hover ? hoverColor : color));
batch->rect(coord.x, coord.y, size.x, size.y);
}
void Button::mouseRelease(GUI* gui, int x, int y) {
UINode::mouseRelease(gui, x, y);
if (isInside(vec2(x, y))) {
if (isInside(glm::vec2(x, y))) {
for (auto callback : actions) {
callback(gui);
}
@ -151,21 +186,28 @@ Button* Button::listenAction(onaction action) {
return this;
}
void Button::textAlign(Align align) {
void Button::setTextAlign(Align align) {
if (label) {
Label* label = (Label*)(this->label.get());
label->align(align);
label->setAlign(align);
refresh();
}
}
Align Button::getTextAlign() const {
if (label) {
return label->getAlign();
}
return Align::left;
}
// ============================== RichButton ==================================
RichButton::RichButton(vec2 size) : Container(vec2(), size) {
RichButton::RichButton(glm::vec2 size) : Container(glm::vec2(), size) {
setHoverColor(glm::vec4(0.05f, 0.1f, 0.15f, 0.75f));
}
void RichButton::mouseRelease(GUI* gui, int x, int y) {
UINode::mouseRelease(gui, x, y);
if (isInside(vec2(x, y))) {
if (isInside(glm::vec2(x, y))) {
for (auto callback : actions) {
callback(gui);
}
@ -177,65 +219,66 @@ RichButton* RichButton::listenAction(onaction action) {
return this;
}
void RichButton::setHoverColor(glm::vec4 color) {
hoverColor = color;
}
void RichButton::drawBackground(Batch2D* batch, Assets* assets) {
vec2 coord = calcCoord();
void RichButton::drawBackground(const GfxContext* pctx, Assets* assets) {
glm::vec2 coord = calcCoord();
auto batch = pctx->getBatch2D();
batch->texture(nullptr);
batch->color = (ispressed() ? pressedColor : (hover_ ? hoverColor : color_));
batch->rect(coord.x, coord.y, size_.x, size_.y);
batch->color = (isPressed() ? pressedColor : (hover ? hoverColor : color));
batch->rect(coord.x, coord.y, size.x, size.y);
}
// ================================ TextBox ===================================
TextBox::TextBox(wstring placeholder, vec4 padding)
: Panel(vec2(200,32), padding, 0, false),
TextBox::TextBox(std::wstring placeholder, glm::vec4 padding)
: Panel(glm::vec2(200,32), padding, 0),
input(L""),
placeholder(placeholder) {
label = new Label(L"");
add(shared_ptr<UINode>(label));
label = std::make_shared<Label>(L"");
label->setSize(size-glm::vec2(padding.z+padding.x, padding.w+padding.y));
add(label);
setHoverColor(glm::vec4(0.05f, 0.1f, 0.2f, 0.75f));
}
void TextBox::drawBackground(Batch2D* batch, Assets* assets) {
vec2 coord = calcCoord();
void TextBox::drawBackground(const GfxContext* pctx, Assets* assets) {
glm::vec2 coord = calcCoord();
auto batch = pctx->getBatch2D();
batch->texture(nullptr);
if (valid) {
if (isfocused()) {
if (isFocused()) {
batch->color = focusedColor;
} else if (hover_) {
} else if (hover) {
batch->color = hoverColor;
} else {
batch->color = color_;
batch->color = color;
}
} else {
batch->color = invalidColor;
}
batch->rect(coord.x, coord.y, size_.x, size_.y);
if (!focused_ && supplier) {
batch->rect(coord.x, coord.y, size.x, size.y);
if (!isFocused() && supplier) {
input = supplier();
}
if (input.empty()) {
label->color(vec4(0.5f));
label->text(placeholder);
label->setColor(glm::vec4(0.5f));
label->setText(placeholder);
} else {
label->color(vec4(1.0f));
label->text(input);
label->setColor(glm::vec4(1.0f));
label->setText(input);
}
scrollable(false);
setScrollable(false);
}
void TextBox::typed(unsigned int codepoint) {
input += wstring({(wchar_t)codepoint});
input += std::wstring({(wchar_t)codepoint});
validate();
}
bool TextBox::validate() {
if (validator) {
valid = validator(input);
valid = validator(getText());
} else {
valid = true;
}
@ -250,7 +293,7 @@ bool TextBox::isValid() const {
return valid;
}
void TextBox::setOnEditStart(gui::runnable oneditstart) {
void TextBox::setOnEditStart(runnable oneditstart) {
onEditStart = oneditstart;
}
@ -261,20 +304,22 @@ void TextBox::focus(GUI* gui) {
}
}
void TextBox::refresh() {
Panel::refresh();
label->setSize(size-glm::vec2(padding.z+padding.x, padding.w+padding.y));
}
void TextBox::keyPressed(int key) {
switch (key) {
case KEY_BACKSPACE:
if (!input.empty()){
input = input.substr(0, input.length()-1);
validate();
}
break;
case KEY_ENTER:
if (validate() && consumer) {
consumer(label->text());
}
defocus();
break;
if (key == keycode::BACKSPACE) {
if (!input.empty()){
input = input.substr(0, input.length()-1);
validate();
}
} else if (key == keycode::ENTER) {
if (validate() && consumer) {
consumer(label->getText());
}
defocus();
}
// Pasting text from clipboard
if (key == keycode::V && Events::pressed(keycode::LEFT_CONTROL)) {
@ -286,51 +331,48 @@ void TextBox::keyPressed(int key) {
}
}
shared_ptr<UINode> TextBox::getAt(vec2 pos, shared_ptr<UINode> self) {
std::shared_ptr<UINode> TextBox::getAt(glm::vec2 pos, std::shared_ptr<UINode> self) {
return UINode::getAt(pos, self);
}
void TextBox::textSupplier(wstringsupplier supplier) {
void TextBox::setTextSupplier(wstringsupplier supplier) {
this->supplier = supplier;
}
void TextBox::textConsumer(wstringconsumer consumer) {
void TextBox::setTextConsumer(wstringconsumer consumer) {
this->consumer = consumer;
}
void TextBox::textValidator(wstringchecker validator) {
void TextBox::setTextValidator(wstringchecker validator) {
this->validator = validator;
}
wstring TextBox::text() const {
std::wstring TextBox::getText() const {
if (input.empty())
return placeholder;
return input;
}
void TextBox::text(std::wstring value) {
void TextBox::setText(std::wstring value) {
this->input = value;
}
// ============================== InputBindBox ================================
InputBindBox::InputBindBox(Binding& binding, vec4 padding)
: Panel(vec2(100,32), padding, 0, false),
InputBindBox::InputBindBox(Binding& binding, glm::vec4 padding)
: Panel(glm::vec2(100,32), padding, 0),
binding(binding) {
label = new Label(L"");
label = std::make_shared<Label>(L"");
add(label);
scrollable(false);
setScrollable(false);
}
shared_ptr<UINode> InputBindBox::getAt(vec2 pos, shared_ptr<UINode> self) {
return UINode::getAt(pos, self);
}
void InputBindBox::drawBackground(Batch2D* batch, Assets* assets) {
vec2 coord = calcCoord();
void InputBindBox::drawBackground(const GfxContext* pctx, Assets* assets) {
glm::vec2 coord = calcCoord();
auto batch = pctx->getBatch2D();
batch->texture(nullptr);
batch->color = (isfocused() ? focusedColor : (hover_ ? hoverColor : color_));
batch->rect(coord.x, coord.y, size_.x, size_.y);
label->text(util::str2wstr_utf8(binding.text()));
batch->color = (isFocused() ? focusedColor : (hover ? hoverColor : color));
batch->rect(coord.x, coord.y, size.x, size.y);
label->setText(util::str2wstr_utf8(binding.text()));
}
void InputBindBox::clicked(GUI*, int button) {
@ -353,98 +395,100 @@ TrackBar::TrackBar(double min,
double value,
double step,
int trackWidth)
: UINode(vec2(), vec2(26)),
: UINode(glm::vec2(), glm::vec2(26)),
min(min),
max(max),
value(value),
step(step),
trackWidth(trackWidth) {
color(vec4(0.f, 0.f, 0.f, 0.4f));
setColor(glm::vec4(0.f, 0.f, 0.f, 0.4f));
}
void TrackBar::draw(Batch2D* batch, Assets* assets) {
if (supplier_) {
value = supplier_();
void TrackBar::draw(const GfxContext* pctx, Assets* assets) {
if (supplier) {
value = supplier();
}
vec2 coord = calcCoord();
glm::vec2 coord = calcCoord();
auto batch = pctx->getBatch2D();
batch->texture(nullptr);
batch->color = (hover_ ? hoverColor : color_);
batch->rect(coord.x, coord.y, size_.x, size_.y);
batch->color = (hover ? hoverColor : color);
batch->rect(coord.x, coord.y, size.x, size.y);
float width = size_.x;
float width = size.x;
float t = (value - min) / (max-min+trackWidth*step);
batch->color = trackColor;
int actualWidth = size_.x * (trackWidth / (max-min+trackWidth*step) * step);
batch->rect(coord.x + width * t, coord.y, actualWidth, size_.y);
int actualWidth = size.x * (trackWidth / (max-min+trackWidth*step) * step);
batch->rect(coord.x + width * t, coord.y, actualWidth, size.y);
}
void TrackBar::supplier(doublesupplier supplier) {
this->supplier_ = supplier;
void TrackBar::setSupplier(doublesupplier supplier) {
this->supplier = supplier;
}
void TrackBar::consumer(doubleconsumer consumer) {
this->consumer_ = consumer;
void TrackBar::setConsumer(doubleconsumer consumer) {
this->consumer = consumer;
}
void TrackBar::mouseMove(GUI*, int x, int y) {
vec2 coord = calcCoord();
glm::vec2 coord = calcCoord();
value = x;
value -= coord.x;
value = (value)/size_.x * (max-min+trackWidth*step);
value = (value)/size.x * (max-min+trackWidth*step);
value += min;
value = (value > max) ? max : value;
value = (value < min) ? min : value;
value = (int)(value / step) * step;
if (consumer_) {
consumer_(value);
if (consumer) {
consumer(value);
}
}
// ================================ CheckBox ==================================
CheckBox::CheckBox(bool checked) : UINode(vec2(), vec2(32.0f)), checked_(checked) {
color(vec4(0.0f, 0.0f, 0.0f, 0.5f));
CheckBox::CheckBox(bool checked) : UINode(glm::vec2(), glm::vec2(32.0f)), checked(checked) {
setColor(glm::vec4(0.0f, 0.0f, 0.0f, 0.5f));
}
void CheckBox::draw(Batch2D* batch, Assets* assets) {
if (supplier_) {
checked_ = supplier_();
void CheckBox::draw(const GfxContext* pctx, Assets* assets) {
if (supplier) {
checked = supplier();
}
vec2 coord = calcCoord();
glm::vec2 coord = calcCoord();
auto batch = pctx->getBatch2D();
batch->texture(nullptr);
batch->color = checked_ ? checkColor : (hover_ ? hoverColor : color_);
batch->rect(coord.x, coord.y, size_.x, size_.y);
batch->color = checked ? checkColor : (hover ? hoverColor : color);
batch->rect(coord.x, coord.y, size.x, size.y);
}
void CheckBox::mouseRelease(GUI*, int x, int y) {
checked_ = !checked_;
if (consumer_) {
consumer_(checked_);
checked = !checked;
if (consumer) {
consumer(checked);
}
}
void CheckBox::supplier(boolsupplier supplier) {
supplier_ = supplier;
void CheckBox::setSupplier(boolsupplier supplier) {
this->supplier = supplier;
}
void CheckBox::consumer(boolconsumer consumer) {
consumer_ = consumer;
void CheckBox::setConsumer(boolconsumer consumer) {
this->consumer = consumer;
}
CheckBox* CheckBox::checked(bool flag) {
checked_ = flag;
CheckBox* CheckBox::setChecked(bool flag) {
checked = flag;
return this;
}
FullCheckBox::FullCheckBox(std::wstring text, glm::vec2 size, bool checked)
: Panel(size),
checkbox(std::make_shared<CheckBox>(checked)){
color(vec4(0.0f));
orientation(Orientation::horizontal);
setColor(glm::vec4(0.0f));
setOrientation(Orientation::horizontal);
add(checkbox);
auto label = std::make_shared<Label>(text);
label->margin(vec4(5.0f, 5.0f, 0.0f, 0.0f));
label->setMargin(glm::vec4(5.f, 5.f, 0.f, 0.f));
add(label);
}

View File

@ -6,107 +6,95 @@
#include <vector>
#include <functional>
#include <glm/glm.hpp>
#include "GUI.h"
#include "UINode.h"
#include "panels.h"
#include "containers.h"
#include "../../window/input.h"
#include "../../delegates.h"
class Batch2D;
class Assets;
namespace gui {
typedef std::function<std::wstring()> wstringsupplier;
typedef std::function<void(std::wstring)> wstringconsumer;
typedef std::function<double()> doublesupplier;
typedef std::function<void(double)> doubleconsumer;
typedef std::function<bool()> boolsupplier;
typedef std::function<void(bool)> boolconsumer;
typedef std::function<bool(const std::wstring&)> wstringchecker;
class Label : public UINode {
protected:
std::wstring text_;
std::string fontName_;
std::wstring text;
std::string fontName;
wstringsupplier supplier = nullptr;
public:
Label(std::string text, std::string fontName="normal");
Label(std::wstring text, std::string fontName="normal");
virtual Label& text(std::wstring text);
std::wstring text() const;
virtual void setText(std::wstring text);
std::wstring getText() const;
virtual void draw(Batch2D* batch, Assets* assets) override;
virtual void draw(const GfxContext* pctx, Assets* assets) override;
virtual Label* textSupplier(wstringsupplier supplier);
virtual glm::vec2 size() const override {
return UINode::size();
}
virtual void size(glm::vec2 size) override;
virtual void textSupplier(wstringsupplier supplier);
};
class Image : public UINode {
protected:
std::string texture;
bool autoresize = false;
public:
Image(std::string texture, glm::vec2 size);
Image(std::string texture, glm::vec2 size=glm::vec2(32,32));
virtual void draw(Batch2D* batch, Assets* assets) override;
virtual void draw(const GfxContext* pctx, Assets* assets) override;
virtual void setAutoResize(bool flag);
virtual bool isAutoResize() const;
};
class Button : public Panel {
protected:
glm::vec4 hoverColor {0.05f, 0.1f, 0.15f, 0.75f};
glm::vec4 pressedColor {0.0f, 0.0f, 0.0f, 0.95f};
std::vector<onaction> actions;
std::shared_ptr<UINode> label = nullptr;
std::shared_ptr<Label> label = nullptr;
public:
Button(std::shared_ptr<UINode> content, glm::vec4 padding=glm::vec4(2.0f));
Button(std::shared_ptr<UINode> content,
glm::vec4 padding=glm::vec4(2.0f));
Button(std::wstring text,
glm::vec4 padding=glm::vec4(2.0f),
glm::vec4 margin=glm::vec4(1.0f));
glm::vec4 padding,
onaction action,
glm::vec2 size=glm::vec2(-1));
virtual void drawBackground(Batch2D* batch, Assets* assets) override;
virtual std::shared_ptr<UINode> getAt(glm::vec2 pos, std::shared_ptr<UINode> self) override;
virtual void drawBackground(const GfxContext* pctx, Assets* assets) override;
virtual void mouseRelease(GUI*, int x, int y) override;
virtual Button* listenAction(onaction action);
virtual void textAlign(Align align);
virtual Align getTextAlign() const;
virtual void setTextAlign(Align align);
virtual void text(std::wstring text);
virtual std::wstring text() const;
virtual void setText(std::wstring text);
virtual std::wstring getText() const;
virtual Button* textSupplier(wstringsupplier supplier);
virtual void setHoverColor(glm::vec4 color);
virtual void refresh() override;
};
class RichButton : public Container {
protected:
glm::vec4 hoverColor {0.05f, 0.1f, 0.15f, 0.75f};
glm::vec4 pressedColor {0.0f, 0.0f, 0.0f, 0.95f};
std::vector<onaction> actions;
public:
RichButton(glm::vec2 size);
virtual void drawBackground(Batch2D* batch, Assets* assets) override;
virtual void drawBackground(const GfxContext* pctx, Assets* assets) override;
virtual void mouseRelease(GUI*, int x, int y) override;
virtual RichButton* listenAction(onaction action);
virtual void setHoverColor(glm::vec4 color);
};
class TextBox : public Panel {
protected:
glm::vec4 hoverColor {0.05f, 0.1f, 0.2f, 0.75f};
glm::vec4 focusedColor {0.0f, 0.0f, 0.0f, 1.0f};
glm::vec4 invalidColor {0.1f, 0.05f, 0.03f, 1.0f};
Label* label;
std::shared_ptr<Label> label;
std::wstring input;
std::wstring placeholder;
wstringsupplier supplier = nullptr;
@ -116,48 +104,50 @@ namespace gui {
bool valid = true;
public:
TextBox(std::wstring placeholder,
glm::vec4 padding=glm::vec4(2.0f));
glm::vec4 padding=glm::vec4(4.0f));
virtual std::shared_ptr<UINode> getAt(glm::vec2 pos, std::shared_ptr<UINode> self) override;
virtual void drawBackground(Batch2D* batch, Assets* assets) override;
virtual void drawBackground(const GfxContext* pctx, Assets* assets) override;
virtual void typed(unsigned int codepoint) override;
virtual void keyPressed(int key) override;
virtual void textSupplier(wstringsupplier supplier);
virtual void textConsumer(wstringconsumer consumer);
virtual void textValidator(wstringchecker validator);
virtual bool isfocuskeeper() const override {return true;}
virtual std::wstring text() const;
virtual void text(std::wstring value);
virtual void setTextSupplier(wstringsupplier supplier);
virtual void setTextConsumer(wstringconsumer consumer);
virtual void setTextValidator(wstringchecker validator);
virtual bool isFocuskeeper() const override {return true;}
/* Get TextBox content text or placeholder if empty */
virtual std::wstring getText() const;
/* Set TextBox content text */
virtual void setText(std::wstring value);
virtual bool validate();
virtual void setValid(bool valid);
virtual bool isValid() const;
virtual void setOnEditStart(runnable oneditstart);
virtual void focus(GUI*) override;
virtual void refresh() override;
};
class InputBindBox : public Panel {
protected:
glm::vec4 hoverColor {0.05f, 0.1f, 0.2f, 0.75f};
glm::vec4 focusedColor {0.0f, 0.0f, 0.0f, 1.0f};
Label* label;
std::shared_ptr<Label> label;
Binding& binding;
public:
InputBindBox(Binding& binding, glm::vec4 padding=glm::vec4(6.0f));
virtual void drawBackground(Batch2D* batch, Assets* assets) override;
virtual std::shared_ptr<UINode> getAt(glm::vec2 pos, std::shared_ptr<UINode> self) override;
virtual void drawBackground(const GfxContext* pctx, Assets* assets) override;
virtual void clicked(GUI*, int button) override;
virtual void keyPressed(int key) override;
virtual bool isfocuskeeper() const override {return true;}
virtual bool isFocuskeeper() const override {return true;}
};
class TrackBar : public UINode {
protected:
glm::vec4 hoverColor {0.01f, 0.02f, 0.03f, 0.5f};
glm::vec4 trackColor {1.0f, 1.0f, 1.0f, 0.4f};
doublesupplier supplier_ = nullptr;
doubleconsumer consumer_ = nullptr;
doublesupplier supplier = nullptr;
doubleconsumer consumer = nullptr;
double min;
double max;
double value;
@ -169,10 +159,10 @@ namespace gui {
double value,
double step=1.0,
int trackWidth=1);
virtual void draw(Batch2D* batch, Assets* assets) override;
virtual void draw(const GfxContext* pctx, Assets* assets) override;
virtual void supplier(doublesupplier supplier);
virtual void consumer(doubleconsumer consumer);
virtual void setSupplier(doublesupplier supplier);
virtual void setConsumer(doubleconsumer consumer);
virtual void mouseMove(GUI*, int x, int y) override;
};
@ -181,25 +171,25 @@ namespace gui {
protected:
glm::vec4 hoverColor {0.05f, 0.1f, 0.2f, 0.75f};
glm::vec4 checkColor {1.0f, 1.0f, 1.0f, 0.4f};
boolsupplier supplier_ = nullptr;
boolconsumer consumer_ = nullptr;
bool checked_ = false;
boolsupplier supplier = nullptr;
boolconsumer consumer = nullptr;
bool checked = false;
public:
CheckBox(bool checked=false);
virtual void draw(Batch2D* batch, Assets* assets) override;
virtual void draw(const GfxContext* pctx, Assets* assets) override;
virtual void mouseRelease(GUI*, int x, int y) override;
virtual void supplier(boolsupplier supplier);
virtual void consumer(boolconsumer consumer);
virtual void setSupplier(boolsupplier supplier);
virtual void setConsumer(boolconsumer consumer);
virtual CheckBox* checked(bool flag);
virtual CheckBox* setChecked(bool flag);
virtual bool checked() const {
if (supplier_)
return supplier_();
return checked_;
virtual bool isChecked() const {
if (supplier)
return supplier();
return checked;
}
};
@ -209,22 +199,22 @@ namespace gui {
public:
FullCheckBox(std::wstring text, glm::vec2 size, bool checked=false);
virtual void supplier(boolsupplier supplier) {
checkbox->supplier(supplier);
virtual void setSupplier(boolsupplier supplier) {
checkbox->setSupplier(supplier);
}
virtual void consumer(boolconsumer consumer) {
checkbox->consumer(consumer);
virtual void setConsumer(boolconsumer consumer) {
checkbox->setConsumer(consumer);
}
virtual void checked(bool flag) {
checkbox->checked(flag);
virtual void setChecked(bool flag) {
checkbox->setChecked(flag);
}
virtual bool checked() const {
return checkbox->checked();
virtual bool isChecked() const {
return checkbox->isChecked();
}
};
}
#endif // FRONTEND_GUI_CONTROLS_H_
#endif // FRONTEND_GUI_CONTROLS_H_

View File

@ -1,35 +1,39 @@
#include "gui_util.h"
#include "controls.h"
#include "panels.h"
#include "containers.h"
#include <glm/glm.hpp>
#include "../locale/langs.h"
#include "../../delegates.h"
using namespace gui;
using glm::vec2;
using glm::vec4;
Button* guiutil::backButton(PagesControl* menu) {
return (new Button(langs::get(L"Back"), vec4(10.f)))->listenAction([=](GUI* gui) {
menu->back();
});
std::shared_ptr<Button> guiutil::backButton(std::shared_ptr<PagesControl> menu) {
return std::make_shared<Button>(
langs::get(L"Back"), vec4(10.f), [=](GUI*) {
menu->back();
}
);
}
Button* guiutil::gotoButton(
std::wstring text,
const std::string& page,
PagesControl* menu) {
std::shared_ptr<Button> guiutil::gotoButton(
std::wstring text,
const std::string& page,
std::shared_ptr<PagesControl> menu
) {
text = langs::get(text, L"menu");
return (new Button(text, vec4(10.f)))->listenAction([=](GUI* gui) {
menu->set(page);
return std::make_shared<Button>(text, vec4(10.f), [=](GUI* gui) {
menu->setPage(page);
});
}
void guiutil::alert(GUI* gui, const std::wstring& text, gui::runnable on_hidden) {
PagesControl* menu = gui->getMenu();
Panel* panel = new Panel(vec2(500, 200), vec4(8.0f), 8.0f);
panel->color(vec4(0.0f, 0.0f, 0.0f, 0.5f));
void guiutil::alert(GUI* gui, const std::wstring& text, runnable on_hidden) {
auto menu = gui->getMenu();
auto panel = std::make_shared<Panel>(vec2(500, 200), vec4(8.0f), 8.0f);
panel->setColor(vec4(0.0f, 0.0f, 0.0f, 0.5f));
// TODO: implement built-in text wrapping
const int wrap_length = 60;
@ -43,48 +47,55 @@ void guiutil::alert(GUI* gui, const std::wstring& text, gui::runnable on_hidden)
}
extra = std::min(extra, wrap_length);
std::wstring part = text.substr(offset, extra);
panel->add(new Label(part));
panel->add(std::make_shared<Label>(part));
offset += extra;
}
} else {
panel->add(new Label(text));
panel->add(std::make_shared<Label>(text));
}
panel->add((new Button(langs::get(L"Ok"), vec4(10.f)))->listenAction([=](GUI* gui) {
if (on_hidden)
on_hidden();
menu->back();
}));
panel->add(std::make_shared<Button>(
langs::get(L"Ok"), vec4(10.f),
[=](GUI* gui) {
if (on_hidden) {
on_hidden();
}
menu->back();
}
));
panel->refresh();
menu->add("<alert>", panel);
menu->set("<alert>");
menu->addPage("<alert>", panel);
menu->setPage("<alert>");
}
void guiutil::confirm(
GUI* gui,
const std::wstring& text,
gui::runnable on_confirm,
runnable on_confirm,
std::wstring yestext,
std::wstring notext) {
if (yestext.empty()) yestext = langs::get(L"Yes");
if (notext.empty()) notext = langs::get(L"No");
PagesControl* menu = gui->getMenu();
Panel* panel = new Panel(vec2(600, 200), vec4(8.0f), 8.0f);
panel->color(vec4(0.0f, 0.0f, 0.0f, 0.5f));
panel->add(new Label(text));
Panel* subpanel = new Panel(vec2(600, 53));
subpanel->color(vec4(0));
subpanel->add((new Button(yestext, vec4(8.0f)))->listenAction([=](GUI*){
auto menu = gui->getMenu();
auto panel = std::make_shared<Panel>(vec2(600, 200), vec4(8.0f), 8.0f);
panel->setColor(vec4(0.0f, 0.0f, 0.0f, 0.5f));
panel->add(std::make_shared<Label>(text));
auto subpanel = std::make_shared<Panel>(vec2(600, 53));
subpanel->setColor(vec4(0));
subpanel->add(std::make_shared<Button>(yestext, vec4(8.f), [=](GUI*){
if (on_confirm)
on_confirm();
menu->back();
}));
subpanel->add((new Button(notext, vec4(8.0f)))->listenAction([=](GUI*){
subpanel->add(std::make_shared<Button>(notext, vec4(8.f), [=](GUI*){
menu->back();
}));
panel->add(subpanel);
panel->refresh();
menu->add("<confirm>", panel);
menu->set("<confirm>");
menu->addPage("<confirm>", panel);
menu->setPage("<confirm>");
}

View File

@ -1,19 +1,38 @@
#ifndef FRONTEND_GUI_GUI_UTIL_H_
#define FRONTEND_GUI_GUI_UTIL_H_
#include <memory>
#include <string>
#include "GUI.h"
#include "../../delegates.h"
namespace gui {
class Button;
}
namespace guiutil {
gui::Button* backButton(gui::PagesControl* menu);
gui::Button* gotoButton(std::wstring text, const std::string& page, gui::PagesControl* menu);
void alert(gui::GUI* gui, const std::wstring& text, gui::runnable on_hidden=nullptr);
void confirm(gui::GUI* gui, const std::wstring& text, gui::runnable on_confirm=nullptr,
std::wstring yestext=L"", std::wstring notext=L"");
std::shared_ptr<gui::Button> backButton(
std::shared_ptr<gui::PagesControl> menu
);
std::shared_ptr<gui::Button> gotoButton(
std::wstring text,
const std::string& page,
std::shared_ptr<gui::PagesControl> menu
);
void alert(
gui::GUI* gui,
const std::wstring& text,
runnable on_hidden=nullptr
);
void confirm(
gui::GUI* gui,
const std::wstring& text,
runnable on_confirm=nullptr,
std::wstring yestext=L"",
std::wstring notext=L"");
}
#endif // FRONTEND_GUI_GUI_UTIL_H_

View File

@ -0,0 +1,287 @@
#include "gui_xml.h"
#include <charconv>
#include <stdexcept>
#include "containers.h"
#include "controls.h"
#include "../../assets/AssetsLoader.h"
#include "../locale/langs.h"
#include "../../logic/scripting/scripting.h"
#include "../../util/stringutil.h"
using namespace gui;
static Align align_from_string(const std::string& str, Align def) {
if (str == "left") return Align::left;
if (str == "center") return Align::center;
if (str == "right") return Align::right;
return def;
}
/* Read basic UINode properties */
static void _readUINode(UiXmlReader& reader, xml::xmlelement element, UINode& node) {
if (element->has("id")) {
node.setId(element->attr("id").getText());
}
if (element->has("pos")) {
node.setCoord(element->attr("pos").asVec2());
}
if (element->has("size")) {
node.setSize(element->attr("size").asVec2());
}
if (element->has("color")) {
node.setColor(element->attr("color").asColor());
}
if (element->has("margin")) {
node.setMargin(element->attr("margin").asVec4());
}
if (element->has("z-index")) {
node.setZIndex(element->attr("z-index").asInt());
}
if (element->has("interactive")) {
node.setInteractive(element->attr("interactive").asBool());
}
if (element->has("visible")) {
node.setVisible(element->attr("visible").asBool());
}
if (element->has("position-func")) {
auto supplier = scripting::create_vec2_supplier(
reader.getEnvironment().getId(),
element->attr("position-func").getText(),
reader.getFilename()+".lua"
);
node.setPositionFunc(supplier);
}
std::string alignName = element->attr("align", "").getText();
node.setAlign(align_from_string(alignName, node.getAlign()));
}
static void _readContainer(UiXmlReader& reader, xml::xmlelement element, Container& container) {
_readUINode(reader, element, container);
if (element->has("scrollable")) {
container.setScrollable(element->attr("scrollable").asBool());
}
for (auto& sub : element->getElements()) {
if (sub->isText())
continue;
auto subnode = reader.readUINode(sub);
if (subnode) {
container.add(subnode);
}
}
}
void UiXmlReader::readUINode(UiXmlReader& reader, xml::xmlelement element, Container& container) {
_readContainer(reader, element, container);
}
void UiXmlReader::readUINode(UiXmlReader& reader, xml::xmlelement element, UINode& node) {
_readUINode(reader, element, node);
}
static void _readPanel(UiXmlReader& reader, xml::xmlelement element, Panel& panel) {
_readUINode(reader, element, panel);
if (element->has("padding")) {
glm::vec4 padding = element->attr("padding").asVec4();
panel.setPadding(padding);
glm::vec2 size = panel.getSize();
panel.setSize(glm::vec2(
size.x + padding.x + padding.z,
size.y + padding.y + padding.w
));
}
if (element->has("size")) {
panel.setResizing(false);
}
if (element->has("max-length")) {
panel.setMaxLength(element->attr("max-length").asInt());
}
for (auto& sub : element->getElements()) {
if (sub->isText())
continue;
auto subnode = reader.readUINode(sub);
if (subnode) {
panel.add(subnode);
}
}
}
static std::wstring readAndProcessInnerText(xml::xmlelement element) {
std::wstring text = L"";
if (element->size() == 1) {
std::string source = element->sub(0)->attr("#").getText();
util::trim(source);
text = util::str2wstr_utf8(source);
if (text[0] == '@') {
text = langs::get(text.substr(1));
}
}
return text;
}
static std::shared_ptr<UINode> readLabel(UiXmlReader& reader, xml::xmlelement element) {
std::wstring text = readAndProcessInnerText(element);
auto label = std::make_shared<Label>(text);
_readUINode(reader, element, *label);
return label;
}
static std::shared_ptr<UINode> readContainer(UiXmlReader& reader, xml::xmlelement element) {
auto container = std::make_shared<Container>(glm::vec2(), glm::vec2());
_readContainer(reader, element, *container);
return container;
}
static std::shared_ptr<UINode> readPanel(UiXmlReader& reader, xml::xmlelement element) {
float interval = element->attr("interval", "2").asFloat();
auto panel = std::make_shared<Panel>(glm::vec2(), glm::vec4(), interval);
_readPanel(reader, element, *panel);
return panel;
}
static std::shared_ptr<UINode> readButton(UiXmlReader& reader, xml::xmlelement element) {
std::wstring text = readAndProcessInnerText(element);
auto button = std::make_shared<Button>(text, glm::vec4(0.0f), nullptr);
_readPanel(reader, element, *button);
if (element->has("onclick")) {
auto callback = scripting::create_runnable(
reader.getEnvironment().getId(),
element->attr("onclick").getText(),
reader.getFilename()+".lua"
);
button->listenAction([callback](GUI*) {
callback();
});
}
if (element->has("text-align")) {
button->setTextAlign(align_from_string(element->attr("text-align").getText(), button->getTextAlign()));
}
return button;
}
static std::shared_ptr<UINode> readTextBox(UiXmlReader& reader, xml::xmlelement element) {
auto placeholder = util::str2wstr_utf8(element->attr("placeholder", "").getText());
auto text = readAndProcessInnerText(element);
auto textbox = std::make_shared<TextBox>(placeholder, glm::vec4(0.0f));
_readPanel(reader, element, *textbox);
textbox->setText(text);
if (element->has("consumer")) {
auto consumer = scripting::create_wstring_consumer(
reader.getEnvironment().getId(),
element->attr("consumer").getText(),
reader.getFilename()+".lua"
);
textbox->setTextConsumer(consumer);
}
return textbox;
}
static std::shared_ptr<UINode> readImage(UiXmlReader& reader, xml::xmlelement element) {
std::string src = element->attr("src", "").getText();
auto image = std::make_shared<Image>(src);
_readUINode(reader, element, *image);
reader.getAssetsLoader().add(ASSET_TEXTURE, "textures/"+src+".png", src, nullptr);
return image;
}
static std::shared_ptr<UINode> readTrackBar(UiXmlReader& reader, xml::xmlelement element) {
float min = element->attr("min", "0.0").asFloat();
float max = element->attr("max", "1.0").asFloat();
float def = element->attr("value", "0.0").asFloat();
float step = element->attr("step", "1.0").asFloat();
int trackWidth = element->attr("track-width", "1.0").asInt();
auto bar = std::make_shared<TrackBar>(min, max, def, step, trackWidth);
_readUINode(reader, element, *bar);
if (element->has("consumer")) {
auto consumer = scripting::create_number_consumer(
reader.getEnvironment().getId(),
element->attr("consumer").getText(),
reader.getFilename()+".lua"
);
bar->setConsumer(consumer);
}
if (element->has("supplier")) {
auto supplier = scripting::create_number_supplier(
reader.getEnvironment().getId(),
element->attr("supplier").getText(),
reader.getFilename()+".lua"
);
bar->setSupplier(supplier);
}
return bar;
}
UiXmlReader::UiXmlReader(const scripting::Environment& env, AssetsLoader& assetsLoader)
: env(env), assetsLoader(assetsLoader)
{
add("image", readImage);
add("label", readLabel);
add("panel", readPanel);
add("button", readButton);
add("textbox", readTextBox);
add("trackbar", readTrackBar);
add("container", readContainer);
}
void UiXmlReader::add(const std::string& tag, uinode_reader reader) {
readers[tag] = reader;
}
bool UiXmlReader::hasReader(const std::string& tag) const {
return readers.find(tag) != readers.end();
}
void UiXmlReader::addIgnore(const std::string& tag) {
ignored.insert(tag);
}
std::shared_ptr<UINode> UiXmlReader::readUINode(xml::xmlelement element) {
const std::string& tag = element->getTag();
auto found = readers.find(tag);
if (found == readers.end()) {
if (ignored.find(tag) != ignored.end()) {
return nullptr;
}
throw std::runtime_error("unsupported element '"+tag+"'");
}
return found->second(*this, element);
}
std::shared_ptr<UINode> UiXmlReader::readXML(
const std::string& filename,
const std::string& source
) {
this->filename = filename;
auto document = xml::parse(filename, source);
auto root = document->getRoot();
return readUINode(root);
}
std::shared_ptr<UINode> UiXmlReader::readXML(
const std::string& filename,
xml::xmlelement root
) {
this->filename = filename;
return readUINode(root);
}
const std::string& UiXmlReader::getFilename() const {
return filename;
}
const scripting::Environment& UiXmlReader::getEnvironment() const {
return env;
}
AssetsLoader& UiXmlReader::getAssetsLoader() {
return assetsLoader;
}

View File

@ -0,0 +1,67 @@
#ifndef FRONTEND_GUI_GUI_XML_H_
#define FRONTEND_GUI_GUI_XML_H_
#include <memory>
#include <unordered_set>
#include <unordered_map>
#include "GUI.h"
#include "../../coders/xml.h"
namespace scripting {
class Environment;
}
class AssetsLoader;
namespace gui {
class UiXmlReader;
using uinode_reader = std::function<std::shared_ptr<UINode>(UiXmlReader&, xml::xmlelement)>;
class UiXmlReader {
std::unordered_map<std::string, uinode_reader> readers;
std::unordered_set<std::string> ignored;
std::string filename;
const scripting::Environment& env;
AssetsLoader& assetsLoader;
public:
UiXmlReader(const scripting::Environment& env, AssetsLoader& assetsLoader);
void add(const std::string& tag, uinode_reader reader);
bool hasReader(const std::string& tag) const;
void addIgnore(const std::string& tag);
std::shared_ptr<UINode> readUINode(xml::xmlelement element);
void readUINode(
UiXmlReader& reader,
xml::xmlelement element,
UINode& node
);
void readUINode(
UiXmlReader& reader,
xml::xmlelement element,
Container& container
);
std::shared_ptr<UINode> readXML(
const std::string& filename,
const std::string& source
);
std::shared_ptr<UINode> readXML(
const std::string& filename,
xml::xmlelement root
);
const scripting::Environment& getEnvironment() const;
const std::string& getFilename() const;
AssetsLoader& getAssetsLoader();
};
}
#endif // FRONTEND_GUI_GUI_XML_H_

View File

@ -1,289 +0,0 @@
#include "panels.h"
#include <stdexcept>
#include "../../window/Window.h"
#include "../../assets/Assets.h"
#include "../../graphics/Batch2D.h"
using std::shared_ptr;
using namespace gui;
using glm::vec2;
using glm::vec4;
Container::Container(vec2 coord, vec2 size) : UINode(coord, size) {
actualLength = size.y;
}
shared_ptr<UINode> Container::getAt(vec2 pos, shared_ptr<UINode> 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->visible())
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->visible()) {
node->act(delta);
}
}
}
void Container::scrolled(int value) {
int diff = (actualLength-size().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::scrollable(bool flag) {
scrollable_ = flag;
}
void Container::draw(Batch2D* batch, Assets* assets) {
vec2 coord = calcCoord();
vec2 size = this->size();
drawBackground(batch, assets);
batch->texture(nullptr);
batch->render();
Window::pushScissor(vec4(coord.x, coord.y, size.x, size.y));
for (auto node : nodes) {
if (node->visible())
node->draw(batch, assets);
}
batch->render();
Window::popScissor();
}
void Container::addBack(shared_ptr<UINode> node) {
nodes.insert(nodes.begin(), node);
node->setParent(this);
refresh();
}
void Container::add(shared_ptr<UINode> node) {
nodes.push_back(node);
node->setParent(this);
refresh();
}
void Container::add(UINode* node) {
add(shared_ptr<UINode>(node));
}
void Container::add(shared_ptr<UINode> node, glm::vec2 coord) {
node->setCoord(coord);
add(node);
}
void Container::remove(shared_ptr<UINode> selected) {
selected->setParent(nullptr);
nodes.erase(std::remove_if(nodes.begin(), nodes.end(),
[selected](const shared_ptr<UINode> node) {
return node == selected;
}
), nodes.end());
refresh();
}
void Container::listenInterval(float interval, ontimeout callback, int repeat) {
intervalEvents.push_back({callback, interval, 0.0f, repeat});
}
Panel::Panel(vec2 size, glm::vec4 padding, float interval, bool resizing)
: Container(vec2(), size),
padding(padding),
interval(interval),
resizing_(resizing) {
color_ = vec4(0.0f, 0.0f, 0.0f, 0.75f);
}
Panel::~Panel() {
}
void Panel::drawBackground(Batch2D* batch, Assets* assets) {
vec2 coord = calcCoord();
batch->texture(nullptr);
batch->color = color_;
batch->rect(coord.x, coord.y, size_.x, size_.y);
}
void Panel::maxLength(int value) {
maxLength_ = value;
}
int Panel::maxLength() const {
return maxLength_;
}
void Panel::refresh() {
float x = padding.x;
float y = padding.y;
vec2 size = this->size();
if (orientation_ == Orientation::vertical) {
float maxw = size.x;
for (auto& node : nodes) {
vec2 nodesize = node->size();
const vec4 margin = node->margin();
y += margin.y;
float ex;
float spacex = size.x - margin.z - padding.z;
switch (node->align()) {
case Align::center:
ex = x + fmax(0.0f, spacex - node->size().x) / 2.0f;
break;
case Align::right:
ex = x + spacex - node->size().x;
break;
default:
ex = x + margin.x;
}
node->setCoord(vec2(ex, y));
y += nodesize.y + margin.w + interval;
float width = size.x - padding.x - padding.z - margin.x - margin.z;
node->size(vec2(width, nodesize.y));;
node->refresh();
maxw = fmax(maxw, ex+node->size().x+margin.z+padding.z);
}
if (resizing_) {
if (maxLength_)
this->size(vec2(size.x, glm::min(maxLength_, (int)(y+padding.w))));
else
this->size(vec2(size.x, y+padding.w));
}
actualLength = y + padding.w;
} else {
float maxh = size.y;
for (auto& node : nodes) {
vec2 nodesize = node->size();
const vec4 margin = node->margin();
x += margin.x;
node->setCoord(vec2(x, y+margin.y));
x += nodesize.x + margin.z + interval;
float height = size.y - padding.y - padding.w - margin.y - margin.w;
node->size(vec2(nodesize.x, height));
node->refresh();
maxh = fmax(maxh, y+margin.y+node->size().y+margin.w+padding.w);
}
if (resizing_) {
if (maxLength_)
this->size(vec2(glm::min(maxLength_, (int)(x+padding.z)), size.y));
else
this->size(vec2(x+padding.z, size.y));
}
actualLength = size.y;
}
}
void Panel::orientation(Orientation orientation) {
this->orientation_ = orientation;
}
Orientation Panel::orientation() const {
return orientation_;
}
void Panel::lock(){
for (auto node : nodes) {
node->lock();
}
resizing_ = false;
}
PagesControl::PagesControl() : Container(vec2(), vec2(1)){
}
bool PagesControl::has(std::string name) {
return pages.find(name) != pages.end();
}
void PagesControl::add(std::string name, std::shared_ptr<UINode> panel) {
pages[name] = Page{panel};
}
void PagesControl::add(std::string name, UINode* panel) {
add(name, shared_ptr<UINode>(panel));
}
void PagesControl::set(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);
size(current_.panel->size());
}
void PagesControl::back() {
if (pageStack.empty())
return;
std::string name = pageStack.top();
pageStack.pop();
set(name, false);
}
Page& PagesControl::current() {
return current_;
}
void PagesControl::clearHistory() {
pageStack = std::stack<std::string>();
}
void PagesControl::reset() {
clearHistory();
if (current_.panel) {
curname_ = "";
Container::remove(current_.panel);
current_ = Page{nullptr};
}
}

View File

@ -20,6 +20,7 @@
#include "../graphics/Font.h"
#include "../graphics/Atlas.h"
#include "../graphics/Mesh.h"
#include "../graphics/Texture.h"
#include "../window/Camera.h"
#include "../window/Window.h"
#include "../window/Events.h"
@ -32,7 +33,7 @@
#include "../physics/Hitbox.h"
#include "../maths/voxmaths.h"
#include "gui/controls.h"
#include "gui/panels.h"
#include "gui/containers.h"
#include "gui/UINode.h"
#include "gui/GUI.h"
#include "ContentGfxCache.h"
@ -41,35 +42,71 @@
#include "BlocksPreview.h"
#include "InventoryView.h"
#include "LevelFrontend.h"
#include "UiDocument.h"
#include "../engine.h"
#include "../delegates.h"
#include "../core_defs.h"
#include "../items/ItemDef.h"
#include "../items/Inventory.h"
#include "../items/Inventories.h"
#include "../logic/scripting/scripting.h"
using glm::vec2;
using glm::vec3;
using glm::vec4;
using namespace gui;
inline std::shared_ptr<Label> create_label(gui::wstringsupplier supplier) {
static std::shared_ptr<Label> create_label(wstringsupplier supplier) {
auto label = std::make_shared<Label>(L"-");
label->textSupplier(supplier);
return label;
}
void HudRenderer::createDebugPanel(Engine* engine) {
HudElement::HudElement(
hud_element_mode mode,
UiDocument* document,
std::shared_ptr<gui::UINode> node,
bool debug
) : mode(mode), document(document), node(node), debug(debug) {
}
void HudElement::update(bool pause, bool inventoryOpen, bool debugMode) {
if (debug && !debugMode) {
node->setVisible(false);
}
switch (mode) {
case hud_element_mode::permanent:
node->setVisible(true);
break;
case hud_element_mode::ingame:
node->setVisible(!pause && !inventoryOpen);
break;
case hud_element_mode::inventory_any:
node->setVisible(inventoryOpen);
break;
case hud_element_mode::inventory_bound:
removed = !inventoryOpen;
break;
}
}
UiDocument* HudElement::getDocument() const {
return document;
}
std::shared_ptr<gui::UINode> HudElement::getNode() const {
return node;
}
std::shared_ptr<UINode> Hud::createDebugPanel(Engine* engine) {
auto level = frontend->getLevel();
Panel* panel = new Panel(vec2(250, 200), vec4(5.0f), 1.0f);
debugPanel = std::shared_ptr<UINode>(panel);
panel->listenInterval(1.0f, [this]() {
auto panel = std::make_shared<Panel>(glm::vec2(250, 200), glm::vec4(5.0f), 2.0f);
panel->listenInterval(0.5f, [this]() {
fpsString = std::to_wstring(fpsMax)+L" / "+std::to_wstring(fpsMin);
fpsMin = fps;
fpsMax = fps;
});
panel->setCoord(vec2(10, 10));
panel->setCoord(glm::vec2(10, 10));
panel->add(create_label([this](){ return L"fps: "+this->fpsString;}));
panel->add(create_label([this](){
panel->add(create_label([](){
return L"meshes: " + std::to_wstring(Mesh::meshesCount);
}));
panel->add(create_label([=](){
@ -97,26 +134,26 @@ void HudRenderer::createDebugPanel(Engine* engine) {
return L"seed: "+std::to_wstring(level->world->getSeed());
}));
for (int ax = 0; ax < 3; ax++){
Panel* sub = new Panel(vec2(10, 27), vec4(0.0f));
sub->orientation(Orientation::horizontal);
for (int ax = 0; ax < 3; ax++) {
auto sub = std::make_shared<Container>(glm::vec2(), glm::vec2(250, 27));
std::wstring str = L"x: ";
str[0] += ax;
Label* label = new Label(str);
label->margin(vec4(2, 3, 2, 3));
auto label = std::make_shared<Label>(str);
label->setMargin(glm::vec4(2, 3, 2, 3));
label->setSize(glm::vec2(20, 27));
sub->add(label);
sub->color(vec4(0.0f));
sub->setColor(glm::vec4(0.0f));
// Coord input
TextBox* box = new TextBox(L"");
box->textSupplier([=]() {
auto box = std::make_shared<TextBox>(L"");
box->setTextSupplier([=]() {
Hitbox* hitbox = level->player->hitbox.get();
return util::to_wstring(hitbox->position[ax], 2);
});
box->textConsumer([=](std::wstring text) {
box->setTextConsumer([=](std::wstring text) {
try {
vec3 position = level->player->hitbox->position;
glm::vec3 position = level->player->hitbox->position;
position[ax] = std::stoi(text);
level->player->teleport(position);
} catch (std::invalid_argument& _){
@ -124,10 +161,11 @@ void HudRenderer::createDebugPanel(Engine* engine) {
});
box->setOnEditStart([=](){
Hitbox* hitbox = level->player->hitbox.get();
box->text(std::to_wstring(int(hitbox->position[ax])));
box->setText(std::to_wstring(int(hitbox->position[ax])));
});
box->setSize(glm::vec2(230, 27));
sub->add(box);
sub->add(box, glm::vec2(20, 0));
panel->add(sub);
}
panel->add(create_label([=](){
@ -140,117 +178,80 @@ void HudRenderer::createDebugPanel(Engine* engine) {
return L"time: "+timeString;
}));
{
TrackBar* bar = new TrackBar(0.0f, 1.0f, 1.0f, 0.005f, 8);
bar->supplier([=]() {return level->world->daytime;});
bar->consumer([=](double val) {level->world->daytime = val;});
auto bar = std::make_shared<TrackBar>(0.0f, 1.0f, 1.0f, 0.005f, 8);
bar->setSupplier([=]() {return level->world->daytime;});
bar->setConsumer([=](double val) {level->world->daytime = val;});
panel->add(bar);
}
{
TrackBar* bar = new TrackBar(0.0f, 1.0f, 0.0f, 0.005f, 8);
bar->supplier([=]() {return WorldRenderer::fog;});
bar->consumer([=](double val) {WorldRenderer::fog = val;});
auto bar = std::make_shared<TrackBar>(0.0f, 1.0f, 0.0f, 0.005f, 8);
bar->setSupplier([=]() {return WorldRenderer::fog;});
bar->setConsumer([=](double val) {WorldRenderer::fog = val;});
panel->add(bar);
}
{
auto checkbox = new FullCheckBox(L"Show Chunk Borders", vec2(400, 32));
checkbox->supplier([=]() {
auto checkbox = std::make_shared<FullCheckBox>(
L"Show Chunk Borders", glm::vec2(400, 24)
);
checkbox->setSupplier([=]() {
return engine->getSettings().debug.showChunkBorders;
});
checkbox->consumer([=](bool checked) {
checkbox->setConsumer([=](bool checked) {
engine->getSettings().debug.showChunkBorders = checked;
});
panel->add(checkbox);
}
panel->refresh();
return panel;
}
std::shared_ptr<InventoryView> HudRenderer::createContentAccess() {
std::shared_ptr<InventoryView> Hud::createContentAccess() {
auto level = frontend->getLevel();
auto content = level->content;
auto indices = content->getIndices();
auto player = level->player;
auto inventory = player->getInventory();
int itemsCount = indices->countItemDefs();
auto accessInventory = std::make_shared<Inventory>(itemsCount);
auto accessInventory = std::make_shared<Inventory>(0, itemsCount);
for (int id = 1; id < itemsCount; id++) {
accessInventory->getSlot(id-1).set(ItemStack(id, 1));
}
SlotLayout slotLayout(glm::vec2(), false, true,
[=](ItemStack& item) {
SlotLayout slotLayout(-1, glm::vec2(), false, true,
[=](uint, ItemStack& item) {
auto copy = ItemStack(item);
inventory->move(copy, indices);
},
[=](ItemStack& item, ItemStack& grabbed) {
[=](uint, ItemStack& item) {
inventory->getSlot(player->getChosenSlot()).set(item);
});
InventoryBuilder builder;
builder.addGrid(8, itemsCount-1, glm::vec2(), 8, true, slotLayout);
auto layout = builder.build();
auto contentAccess = std::make_shared<InventoryView>(
content,
frontend,
interaction.get(),
accessInventory,
std::move(layout)
);
contentAccess->build();
return contentAccess;
auto view = builder.build();
view->bind(accessInventory, frontend, interaction.get());
view->setMargin(glm::vec4());
return view;
}
std::shared_ptr<InventoryView> HudRenderer::createHotbar() {
std::shared_ptr<InventoryView> Hud::createHotbar() {
auto level = frontend->getLevel();
auto player = level->player;
auto inventory = player->getInventory();
auto content = level->content;
SlotLayout slotLayout(glm::vec2(), false, false, nullptr, nullptr);
SlotLayout slotLayout(-1, glm::vec2(), false, false, nullptr, nullptr);
InventoryBuilder builder;
builder.addGrid(10, 10, glm::vec2(), 4, true, slotLayout);
auto layout = builder.build();
auto view = builder.build();
layout->setOrigin(glm::vec2(layout->getSize().x/2, 0));
auto view = std::make_shared<InventoryView>(
content,
frontend,
interaction.get(),
inventory,
std::move(layout)
);
view->build();
view->setOrigin(glm::vec2(view->getSize().x/2, 0));
view->bind(inventory, frontend, interaction.get());
view->setInteractive(false);
return view;
}
std::shared_ptr<InventoryView> HudRenderer::createInventory() {
auto level = frontend->getLevel();
auto player = level->player;
auto inventory = player->getInventory();
auto content = level->content;
SlotLayout slotLayout(glm::vec2(), true, false, [=](ItemStack& stack) {
stack.clear();
}, nullptr);
InventoryBuilder builder;
builder.addGrid(10, inventory->size(), glm::vec2(), 4, true, slotLayout);
auto layout = builder.build();
auto view = std::make_shared<InventoryView>(
content,
frontend,
interaction.get(),
inventory,
std::move(layout)
);
view->build();
return view;
}
HudRenderer::HudRenderer(Engine* engine, LevelFrontend* frontend)
Hud::Hud(Engine* engine, LevelFrontend* frontend)
: assets(engine->getAssets()),
gui(engine->getGUI()),
frontend(frontend)
@ -259,83 +260,96 @@ HudRenderer::HudRenderer(Engine* engine, LevelFrontend* frontend)
interaction = std::make_unique<InventoryInteraction>();
grabbedItemView = std::make_shared<SlotView>(
interaction->getGrabbedItem(),
frontend,
interaction.get(),
frontend->getLevel()->content,
SlotLayout(glm::vec2(), false, false, nullptr, nullptr)
SlotLayout(-1, glm::vec2(), false, false, nullptr, nullptr)
);
grabbedItemView->color(glm::vec4());
grabbedItemView->bind(
0,
interaction->getGrabbedItem(),
frontend,
interaction.get()
);
grabbedItemView->setColor(glm::vec4());
grabbedItemView->setInteractive(false);
grabbedItemView->setZIndex(1);
contentAccess = createContentAccess();
contentAccessPanel = std::make_shared<Panel>(
contentAccess->size(), vec4(0.0f), 0.0f
contentAccess->getSize(), glm::vec4(0.0f), 0.0f
);
contentAccessPanel->color(glm::vec4());
contentAccessPanel->setColor(glm::vec4());
contentAccessPanel->add(contentAccess);
contentAccessPanel->scrollable(true);
contentAccessPanel->setScrollable(true);
hotbarView = createHotbar();
inventoryView = createInventory();
darkOverlay = std::make_unique<Panel>(glm::vec2(4000.0f));
darkOverlay->color(glm::vec4(0, 0, 0, 0.5f));
darkOverlay->setColor(glm::vec4(0, 0, 0, 0.5f));
darkOverlay->setZIndex(-1);
darkOverlay->setVisible(false);
uicamera = new Camera(vec3(), 1);
uicamera = std::make_unique<Camera>(glm::vec3(), 1);
uicamera->perspective = false;
uicamera->flipped = true;
createDebugPanel(engine);
debugPanel = createDebugPanel(engine);
menu->reset();
debugPanel->setZIndex(2);
gui->addBack(darkOverlay);
gui->addBack(hotbarView);
gui->add(darkOverlay);
gui->add(hotbarView);
gui->add(debugPanel);
gui->add(contentAccessPanel);
gui->add(inventoryView);
gui->add(grabbedItemView);
}
HudRenderer::~HudRenderer() {
Hud::~Hud() {
// removing all controlled ui
gui->remove(grabbedItemView);
gui->remove(inventoryView);
for (auto& element : elements) {
remove(element);
}
gui->remove(hotbarView);
gui->remove(darkOverlay);
gui->remove(contentAccessPanel);
gui->remove(debugPanel);
delete uicamera;
}
void HudRenderer::drawDebug(int fps){
void Hud::drawDebug(int fps){
this->fps = fps;
fpsMin = min(fps, fpsMin);
fpsMax = max(fps, fpsMax);
}
void HudRenderer::update(bool visible) {
/**
* Remove all elements marked as removed
*/
void Hud::cleanup() {
auto it = std::remove_if(elements.begin(), elements.end(), [](const HudElement& e) {
return e.isRemoved();
});
elements.erase(it, elements.end());
}
void Hud::update(bool visible) {
auto level = frontend->getLevel();
auto player = level->player;
auto menu = gui->getMenu();
debugPanel->visible(player->debug && visible);
menu->visible(pause);
debugPanel->setVisible(player->debug && visible);
if (!visible && inventoryOpen) {
closeInventory();
}
if (pause && menu->current().panel == nullptr) {
pause = false;
if (pause && menu->getCurrent().panel == nullptr) {
setPause(false);
}
if (Events::jpressed(keycode::ESCAPE) && !gui->isFocusCaught()) {
if (pause) {
pause = false;
menu->reset();
setPause(false);
} else if (inventoryOpen) {
closeInventory();
} else {
pause = true;
menu->set("pause");
setPause(true);
}
}
if (visible && Events::jactive(BIND_HUD_INVENTORY)) {
@ -343,7 +357,7 @@ void HudRenderer::update(bool visible) {
if (inventoryOpen) {
closeInventory();
} else {
inventoryOpen = true;
openInventory();
}
}
}
@ -351,11 +365,11 @@ void HudRenderer::update(bool visible) {
Events::toggleCursor();
}
glm::vec2 invSize = contentAccessPanel->size();
inventoryView->visible(inventoryOpen);
contentAccessPanel->visible(inventoryOpen);
contentAccessPanel->size(glm::vec2(invSize.x, Window::height));
hotbarView->visible(visible);
glm::vec2 invSize = contentAccessPanel->getSize();
contentAccessPanel->setVisible(inventoryOpen);
contentAccessPanel->setSize(glm::vec2(invSize.x, Window::height));
contentAccess->setMinSize(glm::vec2(1, Window::height));
// hotbarView->setVisible(visible && !inventoryOpen);
for (int i = keycode::NUM_1; i <= keycode::NUM_9; i++) {
if (Events::jpressed(i)) {
@ -374,24 +388,151 @@ void HudRenderer::update(bool visible) {
player->setChosenSlot(slot);
}
darkOverlay->visible(pause);
for (auto& element : elements) {
element.update(pause, inventoryOpen, player->debug);
if (element.isRemoved()) {
remove(element);
}
}
cleanup();
}
void HudRenderer::closeInventory() {
/**
* Show inventory on the screen and turn on inventory mode blocking movement
*/
void Hud::openInventory() {
auto level = frontend->getLevel();
auto player = level->player;
auto inventory = player->getInventory();
inventoryOpen = true;
auto inventoryDocument = assets->getLayout("core:inventory");
inventoryView = std::dynamic_pointer_cast<InventoryView>(inventoryDocument->getRoot());
inventoryView->bind(inventory, frontend, interaction.get());
add(HudElement(hud_element_mode::inventory_bound, inventoryDocument, inventoryView, false));
}
/**
* Show player inventory + block UI
* @param block world position of the open block
* @param doc block UI document (root element must be an InventoryView)
* @param blockinv block inventory.
* In case of nullptr a new virtual inventory will be created
*/
void Hud::openInventory(glm::ivec3 block, UiDocument* doc, std::shared_ptr<Inventory> blockinv) {
if (isInventoryOpen()) {
closeInventory();
}
auto level = frontend->getLevel();
blockUI = std::dynamic_pointer_cast<InventoryView>(doc->getRoot());
if (blockUI == nullptr) {
throw std::runtime_error("block UI root element must be 'inventory'");
}
openInventory();
if (blockinv == nullptr) {
Events::toggleCursor();
abort();
blockinv = level->inventories->createVirtual(blockUI->getSlotsCount());
}
blockUI->bind(blockinv, frontend, interaction.get());
currentblock = block;
add(HudElement(hud_element_mode::inventory_bound, doc, blockUI, false));
}
/**
* Add element as permanent overlay
* @param doc element layout document
*/
void Hud::openPermanent(UiDocument* doc) {
auto root = doc->getRoot();
remove(root);
auto invview = std::dynamic_pointer_cast<InventoryView>(root);
if (invview) {
auto level = frontend->getLevel();
auto player = level->player;
auto inventory = player->getInventory();
invview->bind(inventory, frontend, interaction.get());
}
add(HudElement(hud_element_mode::permanent, doc, doc->getRoot(), false));
}
/**
* Hide inventory and turn off inventory mode
*/
void Hud::closeInventory() {
auto level = frontend->getLevel();
inventoryOpen = false;
ItemStack& grabbed = interaction->getGrabbedItem();
grabbed.clear();
inventoryView = nullptr;
if (blockUI) {
auto blockinv = blockUI->getInventory();
// todo: do it automatically
if (blockinv->isVirtual()) {
level->inventories->remove(blockinv->getId());
}
blockUI = nullptr;
}
}
void HudRenderer::draw(const GfxContext& ctx){
void Hud::add(HudElement element) {
gui->add(element.getNode());
auto invview = std::dynamic_pointer_cast<InventoryView>(element.getNode());
auto document = element.getDocument();
if (document) {
if (invview) {
auto inventory = invview->getInventory();
scripting::on_ui_open(
element.getDocument(),
inventory.get(),
currentblock
);
} else {
scripting::on_ui_open(
element.getDocument(),
nullptr,
currentblock
);
}
}
elements.push_back(element);
}
void Hud::remove(HudElement& element) {
auto document = element.getDocument();
if (document) {
Inventory* inventory = nullptr;
auto invview = std::dynamic_pointer_cast<InventoryView>(element.getNode());
if (invview) {
inventory = invview->getInventory().get();
}
scripting::on_ui_close(document, inventory);
}
gui->remove(element.getNode());
}
// todo: refactor this garbage
void Hud::remove(std::shared_ptr<UINode> node) {
for (auto& element : elements) {
if (element.getNode() == node) {
element.setRemoved();
remove(element);
}
}
cleanup();
}
void Hud::draw(const GfxContext& ctx){
auto level = frontend->getLevel();
auto player = level->player;
const Viewport& viewport = ctx.getViewport();
const uint width = viewport.getWidth();
const uint height = viewport.getHeight();
Player* player = level->player;
uicamera->setFov(height);
auto batch = ctx.getBatch2D();
@ -401,42 +542,93 @@ void HudRenderer::draw(const GfxContext& ctx){
uishader->use();
uishader->uniformMatrix("u_projview", uicamera->getProjView());
// Draw selected item preview
hotbarView->setCoord(glm::vec2(width/2, height-65));
hotbarView->setSelected(player->getChosenSlot());
// Crosshair
batch->begin();
if (!pause && Events::_cursor_locked && !level->player->debug) {
batch->lineWidth(2);
batch->line(width/2, height/2-6, width/2, height/2+6, 0.2f, 0.2f, 0.2f, 1.0f);
batch->line(width/2+6, height/2, width/2-6, height/2, 0.2f, 0.2f, 0.2f, 1.0f);
batch->line(width/2-5, height/2-5, width/2+5, height/2+5, 0.9f, 0.9f, 0.9f, 1.0f);
batch->line(width/2+5, height/2-5, width/2-5, height/2+5, 0.9f, 0.9f, 0.9f, 1.0f);
GfxContext chctx = ctx.sub();
chctx.blendMode(blendmode::inversion);
auto texture = assets->getTexture("gui/crosshair");
batch->texture(texture);
int chsizex = texture != nullptr ? texture->width : 16;
int chsizey = texture != nullptr ? texture->height : 16;
batch->rect(
(width-chsizex)/2, (height-chsizey)/2,
chsizex, chsizey, 0,0, 1,1, 1,1,1,1
);
batch->render();
}
// Delta-time visualizer
if (level->player->debug) {
batch->texture(nullptr);
const int dmwidth = 256;
const float dmscale = 4000.0f;
static float deltameter[dmwidth]{};
static int index=0;
index = index + 1 % dmwidth;
deltameter[index%dmwidth] = glm::min(0.2f, 1.f/fps)*dmscale;
batch->lineWidth(1);
for (int i = index+1; i < index+dmwidth; i++) {
int j = i % dmwidth;
batch->line(width-dmwidth+i-index, height-deltameter[j],
width-dmwidth+i-index, height, 1.0f, 1.0f, 1.0f, 0.2f);
}
}
if (inventoryOpen) {
auto caLayout = contentAccess->getLayout();
auto invLayout = inventoryView->getLayout();
float caWidth = caLayout->getSize().x;
glm::vec2 invSize = invLayout->getSize();
float width = viewport.getWidth();
inventoryView->setCoord(glm::vec2(
glm::min(width/2-invSize.x/2, width-caWidth-10-invSize.x),
height/2-invSize.y/2
));
float caWidth = contentAccess->getSize().x;
contentAccessPanel->setCoord(glm::vec2(width-caWidth, 0));
glm::vec2 invSize = inventoryView->getSize();
if (blockUI == nullptr) {
inventoryView->setCoord(glm::vec2(
glm::min(width/2-invSize.x/2, width-caWidth-10-invSize.x),
height/2-invSize.y/2
));
} else {
glm::vec2 blockInvSize = blockUI->getSize();
int interval = 5;
float totalHeight = invSize.y + blockInvSize.y + interval;
inventoryView->setCoord(glm::vec2(
glm::min(width/2-invSize.x/2, width-caWidth-10-invSize.x),
height/2+totalHeight/2-invSize.y
));
blockUI->setCoord(glm::vec2(
glm::min(width/2-invSize.x/2, width-caWidth-10-invSize.x),
height/2-totalHeight/2
));
}
}
grabbedItemView->setCoord(glm::vec2(Events::cursor));
batch->render();
}
bool HudRenderer::isInventoryOpen() const {
bool Hud::isInventoryOpen() const {
return inventoryOpen;
}
bool HudRenderer::isPause() const {
bool Hud::isPause() const {
return pause;
}
void Hud::setPause(bool pause) {
if (this->pause == pause) {
return;
}
this->pause = pause;
auto menu = gui->getMenu();
if (pause) {
menu->setPage("pause");
} else {
menu->reset();
}
darkOverlay->setVisible(pause);
menu->setVisible(pause);
}
Player* Hud::getPlayer() const {
return frontend->getLevel()->player;
}

View File

@ -16,19 +16,57 @@ class Player;
class Level;
class Engine;
class SlotView;
class Inventory;
class InventoryView;
class LevelFrontend;
class UiDocument;
class InventoryInteraction;
namespace gui {
class GUI;
class UINode;
class Panel;
class Container;
}
class HudRenderer {
enum class hud_element_mode {
// element is hidden if menu or inventory open
ingame,
// element is visible if hud is visible
permanent,
// element is visible in inventory mode
inventory_any,
// element will be removed on inventory close
inventory_bound
};
class HudElement {
hud_element_mode mode;
UiDocument* document;
std::shared_ptr<gui::UINode> node;
bool debug;
bool removed = false;
public:
HudElement(hud_element_mode mode, UiDocument* document, std::shared_ptr<gui::UINode> node, bool debug);
void update(bool pause, bool inventoryOpen, bool debug);
UiDocument* getDocument() const;
std::shared_ptr<gui::UINode> getNode() const;
void setRemoved() {
removed = true;
}
bool isRemoved() const {
return removed;
}
};
class Hud {
Assets* assets;
Camera* uicamera;
std::unique_ptr<Camera> uicamera;
int fps = 60;
int fpsMin = 60;
@ -37,10 +75,9 @@ class HudRenderer {
bool inventoryOpen = false;
bool pause = false;
std::shared_ptr<gui::Panel> contentAccessPanel;
std::shared_ptr<gui::Container> contentAccessPanel;
std::shared_ptr<InventoryView> contentAccess;
std::shared_ptr<InventoryView> hotbarView;
std::shared_ptr<InventoryView> inventoryView;
std::shared_ptr<gui::UINode> debugPanel;
std::shared_ptr<gui::Panel> darkOverlay;
std::unique_ptr<InventoryInteraction> interaction;
@ -48,14 +85,20 @@ class HudRenderer {
gui::GUI* gui;
LevelFrontend* frontend;
void createDebugPanel(Engine* engine);
std::vector<HudElement> elements;
std::shared_ptr<InventoryView> inventoryView = nullptr;
std::shared_ptr<InventoryView> blockUI = nullptr;
glm::ivec3 currentblock {};
std::shared_ptr<gui::UINode> createDebugPanel(Engine* engine);
std::shared_ptr<InventoryView> createContentAccess();
std::shared_ptr<InventoryView> createHotbar();
std::shared_ptr<InventoryView> createInventory();
void cleanup();
public:
HudRenderer(Engine* engine, LevelFrontend* frontend);
~HudRenderer();
Hud(Engine* engine, LevelFrontend* frontend);
~Hud();
void update(bool hudVisible);
void draw(const GfxContext& context);
@ -63,8 +106,18 @@ public:
bool isInventoryOpen() const;
bool isPause() const;
void setPause(bool pause);
void openInventory();
void openInventory(glm::ivec3 block, UiDocument* doc, std::shared_ptr<Inventory> blockInv);
void closeInventory();
void openPermanent(UiDocument* doc);
void add(HudElement element);
void remove(HudElement& element);
void remove(std::shared_ptr<gui::UINode> node);
Player* getPlayer() const;
};
#endif /* SRC_HUD_H_ */

View File

@ -9,7 +9,7 @@
#include <glm/glm.hpp>
#include "gui/GUI.h"
#include "gui/panels.h"
#include "gui/containers.h"
#include "gui/controls.h"
#include "screens.h"
@ -24,6 +24,7 @@
#include "../window/Window.h"
#include "../engine.h"
#include "../settings.h"
#include "../delegates.h"
#include "../content/Content.h"
#include "../content/ContentLUT.h"
#include "../content/ContentPack.h"
@ -46,74 +47,89 @@ inline uint64_t randU64() {
((uint64_t)rand() << 56);
}
std::shared_ptr<Panel> create_page(
Engine* engine,
std::string name,
int width,
float opacity,
int interval) {
PagesControl* menu = engine->getGUI()->getMenu();
Panel* panel = new Panel(vec2(width, 200), vec4(8.0f), interval);
panel->color(vec4(0.0f, 0.0f, 0.0f, opacity));
std::shared_ptr<Panel> ptr (panel);
menu->add(name, ptr);
return ptr;
static std::shared_ptr<Label> create_label(wstringsupplier supplier) {
auto label = std::make_shared<Label>(L"-");
label->textSupplier(supplier);
return label;
}
Button* create_button(std::wstring text,
glm::vec4 padding,
glm::vec4 margin,
gui::onaction action) {
static std::shared_ptr<Panel> create_page(
Engine* engine,
std::string name,
int width,
float opacity,
int interval
) {
auto menu = engine->getGUI()->getMenu();
auto panel = std::make_shared<Panel>(
vec2(width, 200), vec4(8.0f), interval
);
panel->setColor(vec4(0.0f, 0.0f, 0.0f, opacity));
menu->addPage(name, panel);
return panel;
}
auto btn = new Button(langs::get(text, L"menu"),
padding, margin);
btn->listenAction(action);
static std::shared_ptr<Button> create_button(
std::wstring text,
vec4 padding,
vec4 margin,
gui::onaction action
) {
auto btn = std::make_shared<Button>(
langs::get(text, L"menu"), padding, action
);
btn->setMargin(margin);
return btn;
}
void show_content_missing(Engine* engine, const Content* content,
std::shared_ptr<ContentLUT> lut) {
static void show_content_missing(
Engine* engine,
const Content* content,
std::shared_ptr<ContentLUT> lut
) {
auto* gui = engine->getGUI();
auto* menu = gui->getMenu();
auto menu = gui->getMenu();
auto panel = create_page(engine, "missing-content", 500, 0.5f, 8);
panel->add(new Label(langs::get(L"menu.missing-content")));
panel->add(std::make_shared<Label>(langs::get(L"menu.missing-content")));
Panel* subpanel = new Panel(vec2(500, 100));
subpanel->color(vec4(0.0f, 0.0f, 0.0f, 0.5f));
auto subpanel = std::make_shared<Panel>(vec2(500, 100));
subpanel->setColor(vec4(0.0f, 0.0f, 0.0f, 0.5f));
subpanel->setScrollable(true);
subpanel->setMaxLength(400);
panel->add(subpanel);
for (auto& entry : lut->getMissingContent()) {
Panel* hpanel = new Panel(vec2(500, 30));
hpanel->color(vec4(0.0f));
hpanel->orientation(Orientation::horizontal);
auto hpanel = std::make_shared<Panel>(vec2(500, 30));
hpanel->setColor(vec4(0.0f));
hpanel->setOrientation(Orientation::horizontal);
Label* namelabel = new Label(util::str2wstr_utf8(entry.name));
namelabel->color(vec4(1.0f, 0.2f, 0.2f, 0.5f));
auto namelabel = std::make_shared<Label>(util::str2wstr_utf8(entry.name));
namelabel->setColor(vec4(1.0f, 0.2f, 0.2f, 0.5f));
auto contentname = util::str2wstr_utf8(contenttype_name(entry.type));
Label* typelabel = new Label(L"["+contentname+L"]");
typelabel->color(vec4(0.5f));
auto typelabel = std::make_shared<Label>(L"["+contentname+L"]");
typelabel->setColor(vec4(0.5f));
hpanel->add(typelabel);
hpanel->add(namelabel);
subpanel->add(hpanel);
}
subpanel->maxLength(400);
panel->add(subpanel);
panel->add((new Button(langs::get(L"Back to Main Menu", L"menu"),
vec4(8.0f)))
->listenAction([=](GUI*){
menu->back();
}));
menu->set("missing-content");
panel->add(std::make_shared<Button>(
langs::get(L"Back to Main Menu", L"menu"), vec4(8.0f), [=](GUI*){
menu->back();
}
));
menu->setPage("missing-content");
}
void show_convert_request(
Engine* engine,
const Content* content,
std::shared_ptr<ContentLUT> lut,
fs::path folder) {
fs::path folder
) {
guiutil::confirm(engine->getGUI(), langs::get(L"world.convert-request"),
[=]() {
// TODO: add multithreading here
@ -125,9 +141,10 @@ void show_convert_request(
}, L"", langs::get(L"Cancel"));
}
void create_languages_panel(Engine* engine, PagesControl* menu) {
void create_languages_panel(Engine* engine) {
auto menu = engine->getGUI()->getMenu();
auto panel = create_page(engine, "languages", 400, 0.5f, 1);
panel->scrollable(true);
panel->setScrollable(true);
std::vector<std::string> locales;
for (auto& entry : langs::locales_info) {
@ -138,11 +155,14 @@ void create_languages_panel(Engine* engine, PagesControl* menu) {
auto& locale = langs::locales_info.at(name);
std::string& fullName = locale.name;
Button* button = new Button(util::str2wstr_utf8(fullName), vec4(10.f));
button->listenAction([=](GUI*) {
engine->setLanguage(name);
menu->back();
});
auto button = std::make_shared<Button>(
util::str2wstr_utf8(fullName),
vec4(10.f),
[=](GUI*) {
engine->setLanguage(name);
menu->back();
}
);
panel->add(button);
}
panel->add(guiutil::backButton(menu));
@ -191,10 +211,10 @@ void open_world(std::string name, Engine* engine) {
}
}
Panel* create_worlds_panel(Engine* engine) {
auto panel = new Panel(vec2(390, 200), vec4(5.0f));
panel->color(vec4(1.0f, 1.0f, 1.0f, 0.07f));
panel->maxLength(400);
std::shared_ptr<Panel> create_worlds_panel(Engine* engine) {
auto panel = std::make_shared<Panel>(vec2(390, 0), vec4(5.0f));
panel->setColor(vec4(1.0f, 1.0f, 1.0f, 0.07f));
panel->setMaxLength(400);
auto paths = engine->getPaths();
@ -203,72 +223,77 @@ Panel* create_worlds_panel(Engine* engine) {
auto namews = util::str2wstr_utf8(name);
auto btn = std::make_shared<RichButton>(vec2(390, 46));
btn->color(vec4(1.0f, 1.0f, 1.0f, 0.1f));
btn->setHoverColor(vec4(1.0f, 1.0f, 1.0f, 0.17f));
auto label = std::make_shared<Label>(namews);
label->setInteractive(false);
btn->add(label, vec2(8, 8));
btn->setColor(vec4(0.06f, 0.12f, 0.18f, 0.7f));
btn->setHoverColor(vec4(0.09f, 0.17f, 0.2f, 0.6f));
btn->listenAction([=](GUI*) {
open_world(name, engine);
});
btn->add(std::make_shared<Label>(namews), vec2(8, 8));
auto image = std::make_shared<Image>("gui/delete_icon", vec2(32, 32));
image->color(vec4(1, 1, 1, 0.5f));
image->setColor(vec4(1, 1, 1, 0.5f));
auto delbtn = std::make_shared<Button>(image, vec4(2));
delbtn->color(vec4(0.0f));
delbtn->setColor(vec4(0.0f));
delbtn->setHoverColor(vec4(1.0f, 1.0f, 1.0f, 0.17f));
btn->add(delbtn, vec2(330, 3));
delbtn->listenAction([=](GUI* gui) {
guiutil::confirm(gui, langs::get(L"delete-confirm", L"world")+
L" ("+util::str2wstr_utf8(folder.u8string())+L")", [=]()
{
std::cout << "deleting " << folder.u8string() << std::endl;
fs::remove_all(folder);
menus::refresh_menus(engine, gui->getMenu());
menus::refresh_menus(engine);
});
});
btn->add(delbtn, vec2(330, 3));
panel->add(btn);
}
return panel;
}
void create_main_menu_panel(Engine* engine, PagesControl* menu) {
void create_main_menu_panel(Engine* engine) {
auto menu = engine->getGUI()->getMenu();
auto panel = create_page(engine, "main", 400, 0.0f, 1);
panel->add(guiutil::gotoButton(L"New World", "new-world", menu));
panel->add(create_worlds_panel(engine));
panel->add(guiutil::gotoButton(L"Settings", "settings", menu));
panel->add((new Button(langs::get(L"Quit", L"menu"), vec4(10.f)))
->listenAction([](GUI* gui) {
Window::setShouldClose(true);
}));
panel->add(std::make_shared<Button>(
langs::get(L"Quit", L"menu"), vec4(10.f), [](GUI*) {
Window::setShouldClose(true);
}
));
}
typedef std::function<void(const ContentPack& pack)> packconsumer;
const int PACKS_PANEL_WIDTH = 550;
std::shared_ptr<Panel> create_packs_panel(const std::vector<ContentPack>& packs, Engine* engine, bool backbutton, packconsumer callback) {
std::shared_ptr<Panel> create_packs_panel(
const std::vector<ContentPack>& packs,
Engine* engine,
bool backbutton,
packconsumer callback
){
auto assets = engine->getAssets();
auto panel = std::make_shared<Panel>(vec2(PACKS_PANEL_WIDTH, 200), vec4(5.0f));
panel->color(vec4(1.0f, 1.0f, 1.0f, 0.07f));
panel->maxLength(400);
panel->scrollable(true);
auto panel = std::make_shared<Panel>(vec2(550, 200), vec4(5.0f));
panel->setColor(vec4(1.0f, 1.0f, 1.0f, 0.07f));
panel->setMaxLength(400);
panel->setScrollable(true);
for (auto& pack : packs) {
auto packpanel = std::make_shared<RichButton>(vec2(390, 80));
auto packpanel = std::make_shared<RichButton>(vec2(540, 80));
packpanel->setColor(vec4(0.06f, 0.12f, 0.18f, 0.7f));
if (callback) {
packpanel->listenAction([=](GUI*) {
callback(pack);
});
}
auto idlabel = std::make_shared<Label>("["+pack.id+"]");
idlabel->color(vec4(1, 1, 1, 0.5f));
packpanel->add(idlabel, vec2(PACKS_PANEL_WIDTH-40-idlabel->size().x, 2));
idlabel->setColor(vec4(1, 1, 1, 0.5f));
idlabel->setSize(vec2(300, 25));
idlabel->setAlign(Align::right);
packpanel->add(idlabel, vec2(215, 2));
auto titlelabel = std::make_shared<Label>(pack.title);
packpanel->add(titlelabel, vec2(78, 6));
@ -285,28 +310,30 @@ std::shared_ptr<Panel> create_packs_panel(const std::vector<ContentPack>& packs,
if (!pack.creator.empty()) {
auto creatorlabel = std::make_shared<Label>("@"+pack.creator);
creatorlabel->color(vec4(0.8f, 1.0f, 0.9f, 0.7f));
packpanel->add(creatorlabel, vec2(PACKS_PANEL_WIDTH-40-creatorlabel->size().x, 60));
creatorlabel->setColor(vec4(0.8f, 1.0f, 0.9f, 0.7f));
creatorlabel->setSize(vec2(300, 20));
creatorlabel->setAlign(Align::right);
packpanel->add(creatorlabel, vec2(215, 60));
}
auto descriptionlabel = std::make_shared<Label>(pack.description);
descriptionlabel->color(vec4(1, 1, 1, 0.7f));
descriptionlabel->setColor(vec4(1, 1, 1, 0.7f));
packpanel->add(descriptionlabel, vec2(80, 28));
packpanel->add(std::make_shared<Image>(icon, glm::vec2(64)), vec2(8));
packpanel->color(vec4(0.06f, 0.12f, 0.18f, 0.7f));
packpanel->add(std::make_shared<Image>(icon, vec2(64)), vec2(8));
panel->add(packpanel);
}
if (backbutton)
if (backbutton) {
panel->add(guiutil::backButton(engine->getGUI()->getMenu()));
}
return panel;
}
// TODO: refactor
void create_content_panel(Engine* engine, PagesControl* menu) {
void create_content_panel(Engine* engine) {
auto menu = engine->getGUI()->getMenu();
auto paths = engine->getPaths();
auto mainPanel = create_page(engine, "content", PACKS_PANEL_WIDTH, 0.0f, 5);
auto mainPanel = create_page(engine, "content", 550, 0.0f, 5);
std::vector<ContentPack> scanned;
ContentPack::scan(engine->getPaths(), scanned);
@ -338,19 +365,19 @@ void create_content_panel(Engine* engine, PagesControl* menu) {
return;
}
if (!world->hasPack(dependency)) {
world->wfile->addPack(dependency);
world->wfile->addPack(world, dependency);
}
}
world->wfile->addPack(pack.id);
world->wfile->addPack(world, pack.id);
std::string wname = world->getName();
engine->setScreen(nullptr);
engine->setScreen(std::make_shared<MenuScreen>(engine));
open_world(wname, engine);
});
menu->add("content-packs", panel);
menu->set("content-packs");
menu->addPage("content-packs", panel);
menu->setPage("content-packs");
}));
mainPanel->add(guiutil::backButton(menu));
}
@ -369,12 +396,12 @@ inline uint64_t str2seed(std::wstring seedstr) {
}
}
void create_new_world_panel(Engine* engine, PagesControl* menu) {
void create_new_world_panel(Engine* engine) {
auto panel = create_page(engine, "new-world", 400, 0.0f, 1);
panel->add(std::make_shared<Label>(langs::get(L"Name", L"world")));
auto nameInput = std::make_shared<TextBox>(L"New World", vec4(6.0f));
nameInput->textValidator([=](const std::wstring& text) {
nameInput->setTextValidator([=](const std::wstring& text) {
EnginePaths* paths = engine->getPaths();
std::string textutf8 = util::wstr2str_utf8(text);
return util::is_valid_filename(text) &&
@ -392,77 +419,81 @@ void create_new_world_panel(Engine* engine, PagesControl* menu) {
if (!nameInput->validate())
return;
std::wstring name = nameInput->text();
std::string nameutf8 = util::wstr2str_utf8(name);
EnginePaths* paths = engine->getPaths();
std::wstring seedstr = seedInput->text();
uint64_t seed = str2seed(seedstr);
std::string name = util::wstr2str_utf8(nameInput->getText());
uint64_t seed = str2seed(seedInput->getText());
std::cout << "world seed: " << seed << std::endl;
auto folder = paths->getWorldsFolder()/fs::u8path(nameutf8);
EnginePaths* paths = engine->getPaths();
auto folder = paths->getWorldsFolder()/fs::u8path(name);
try {
engine->loadAllPacks();
engine->loadContent();
paths->setWorldFolder(folder);
} catch (const contentpack_error& error) {
guiutil::alert(engine->getGUI(),
langs::get(L"Content Error", L"menu")+
L":\n"+util::str2wstr_utf8(std::string(error.what())+
"\npack '"+error.getPackId()+"' from "+
error.getFolder().u8string()));
guiutil::alert(
engine->getGUI(),
langs::get(L"Content Error", L"menu")+L":\n"+
util::str2wstr_utf8(
std::string(error.what())+
"\npack '"+error.getPackId()+"' from "+
error.getFolder().u8string()
)
);
return;
} catch (const std::runtime_error& error) {
guiutil::alert(engine->getGUI(),
langs::get(L"Content Error", L"menu")+
L": "+util::str2wstr_utf8(error.what()));
guiutil::alert(
engine->getGUI(),
langs::get(L"Content Error", L"menu")+
L": "+util::str2wstr_utf8(error.what())
);
return;
}
Level* level = World::create(
nameutf8, folder, seed,
name, folder, seed,
engine->getSettings(),
engine->getContent(),
engine->getContentPacks());
engine->getContentPacks()
);
engine->setScreen(std::make_shared<LevelScreen>(engine, level));
}));
panel->add(guiutil::backButton(menu));
panel->add(guiutil::backButton(engine->getGUI()->getMenu()));
}
void create_controls_panel(Engine* engine, PagesControl* menu) {
void create_controls_panel(Engine* engine) {
auto menu = engine->getGUI()->getMenu();
auto panel = create_page(engine, "controls", 400, 0.0f, 1);
/* Camera sensitivity setting track bar */{
panel->add((new Label(L""))->textSupplier([=]() {
panel->add(create_label([=]() {
float s = engine->getSettings().camera.sensitivity;
return langs::get(L"Mouse Sensitivity", L"settings")+L": "+
util::to_wstring(s, 1);
}));
TrackBar* trackbar = new TrackBar(0.1, 10.0, 2.0, 0.1, 4);
trackbar->supplier([=]() {
auto trackbar = std::make_shared<TrackBar>(0.1, 10.0, 2.0, 0.1, 4);
trackbar->setSupplier([=]() {
return engine->getSettings().camera.sensitivity;
});
trackbar->consumer([=](double value) {
trackbar->setConsumer([=](double value) {
engine->getSettings().camera.sensitivity = value;
});
panel->add(trackbar);
}
Panel* scrollPanel = new Panel(vec2(400, 200), vec4(2.0f), 1.0f);
scrollPanel->color(vec4(0.0f, 0.0f, 0.0f, 0.3f));
scrollPanel->maxLength(400);
auto scrollPanel = std::make_shared<Panel>(vec2(400, 200), vec4(2.0f), 1.0f);
scrollPanel->setColor(vec4(0.0f, 0.0f, 0.0f, 0.3f));
scrollPanel->setMaxLength(400);
for (auto& entry : Events::bindings){
std::string bindname = entry.first;
Panel* subpanel = new Panel(vec2(400, 40), vec4(5.0f), 1.0f);
subpanel->color(vec4(0.0f));
subpanel->orientation(Orientation::horizontal);
auto subpanel = std::make_shared<Panel>(vec2(400, 40), vec4(5.0f), 1.0f);
subpanel->setColor(vec4(0.0f));
subpanel->setOrientation(Orientation::horizontal);
subpanel->add(std::make_shared<InputBindBox>(entry.second));
InputBindBox* bindbox = new InputBindBox(entry.second);
subpanel->add(bindbox);
Label* label = new Label(langs::get(util::str2wstr_utf8(bindname)));
label->margin(vec4(6.0f));
auto label = std::make_shared<Label>(langs::get(util::str2wstr_utf8(bindname)));
label->setMargin(vec4(6.0f));
subpanel->add(label);
scrollPanel->add(subpanel);
}
@ -470,96 +501,114 @@ void create_controls_panel(Engine* engine, PagesControl* menu) {
panel->add(guiutil::backButton(menu));
}
void create_settings_panel(Engine* engine, PagesControl* menu) {
void create_settings_panel(Engine* engine) {
auto menu = engine->getGUI()->getMenu();
auto panel = create_page(engine, "settings", 400, 0.0f, 1);
/* Load Distance setting track bar */{
panel->add((new Label(L""))->textSupplier([=]() {
panel->add(create_label([=]() {
return langs::get(L"Load Distance", L"settings")+L": " +
std::to_wstring(engine->getSettings().chunks.loadDistance);
}));
TrackBar* trackbar = new TrackBar(3, 66, 10, 1, 3);
trackbar->supplier([=]() {
auto trackbar = std::make_shared<TrackBar>(3, 66, 10, 1, 3);
trackbar->setSupplier([=]() {
return engine->getSettings().chunks.loadDistance;
});
trackbar->consumer([=](double value) {
trackbar->setConsumer([=](double value) {
engine->getSettings().chunks.loadDistance = static_cast<uint>(value);
});
panel->add(trackbar);
}
/* Load Speed setting track bar */{
panel->add((new Label(L""))->textSupplier([=]() {
panel->add(create_label([=]() {
return langs::get(L"Load Speed", L"settings")+L": " +
std::to_wstring(engine->getSettings().chunks.loadSpeed);
}));
TrackBar* trackbar = new TrackBar(1, 32, 10, 1, 1);
trackbar->supplier([=]() {
auto trackbar = std::make_shared<TrackBar>(1, 32, 10, 1, 1);
trackbar->setSupplier([=]() {
return engine->getSettings().chunks.loadSpeed;
});
trackbar->consumer([=](double value) {
trackbar->setConsumer([=](double value) {
engine->getSettings().chunks.loadSpeed = static_cast<uint>(value);
});
panel->add(trackbar);
}
/* Fog Curve setting track bar */{
panel->add((new Label(L""))->textSupplier([=]() {
panel->add(create_label([=]() {
float value = engine->getSettings().graphics.fogCurve;
return langs::get(L"Fog Curve", L"settings")+L": " +
util::to_wstring(value, 1);
}));
TrackBar* trackbar = new TrackBar(1.0, 6.0, 1.0, 0.1, 2);
trackbar->supplier([=]() {
auto trackbar = std::make_shared<TrackBar>(1.0, 6.0, 1.0, 0.1, 2);
trackbar->setSupplier([=]() {
return engine->getSettings().graphics.fogCurve;
});
trackbar->consumer([=](double value) {
trackbar->setConsumer([=](double value) {
engine->getSettings().graphics.fogCurve = value;
});
panel->add(trackbar);
}
/* Fov setting track bar */{
panel->add((new Label(L""))->textSupplier([=]() {
panel->add(create_label([=]() {
int fov = (int)engine->getSettings().camera.fov;
return langs::get(L"FOV", L"settings")+L": "+std::to_wstring(fov)+L"°";
}));
TrackBar* trackbar = new TrackBar(30.0, 120.0, 90, 1, 4);
trackbar->supplier([=]() {
auto trackbar = std::make_shared<TrackBar>(30.0, 120.0, 90, 1, 4);
trackbar->setSupplier([=]() {
return engine->getSettings().camera.fov;
});
trackbar->consumer([=](double value) {
trackbar->setConsumer([=](double value) {
engine->getSettings().camera.fov = value;
});
panel->add(trackbar);
}
/* V-Sync checkbox */{
auto checkbox = new FullCheckBox(langs::get(L"V-Sync", L"settings"), vec2(400, 32));
checkbox->supplier([=]() {
auto checkbox = std::make_shared<FullCheckBox>(
langs::get(L"V-Sync", L"settings"), vec2(400, 32)
);
checkbox->setSupplier([=]() {
return engine->getSettings().display.swapInterval != 0;
});
checkbox->consumer([=](bool checked) {
checkbox->setConsumer([=](bool checked) {
engine->getSettings().display.swapInterval = checked;
});
panel->add(checkbox);
}
/* Backlight checkbox */{
auto checkbox = new FullCheckBox(langs::get(L"Backlight", L"settings"), vec2(400, 32));
checkbox->supplier([=]() {
return engine->getSettings().graphics.backlight != 0;
auto checkbox = std::make_shared<FullCheckBox>(
langs::get(L"Backlight", L"settings"), vec2(400, 32)
);
checkbox->setSupplier([=]() {
return engine->getSettings().graphics.backlight;
});
checkbox->consumer([=](bool checked) {
checkbox->setConsumer([=](bool checked) {
engine->getSettings().graphics.backlight = checked;
});
panel->add(checkbox);
}
/* Camera shaking checkbox */ {
auto checkbox = std::make_shared<FullCheckBox>(
langs::get(L"Camera Shaking", L"settings"), vec2(400, 32)
);
checkbox->setSupplier([=]() {
return engine->getSettings().camera.shaking;
});
checkbox->setConsumer([=](bool checked) {
engine->getSettings().camera.shaking = checked;
});
panel->add(checkbox);
}
std::string langName = langs::locales_info.at(langs::current->getId()).name;
panel->add(guiutil::gotoButton(
langs::get(L"Language", L"settings")+L": "+
@ -570,15 +619,16 @@ void create_settings_panel(Engine* engine, PagesControl* menu) {
panel->add(guiutil::backButton(menu));
}
void create_pause_panel(Engine* engine, PagesControl* menu) {
void create_pause_panel(Engine* engine) {
auto menu = engine->getGUI()->getMenu();
auto panel = create_page(engine, "pause", 400, 0.0f, 1);
panel->add(create_button(L"Continue", vec4(10.0f), vec4(1), [=](GUI*){
menu->reset();
}));
panel->add(create_button(L"Content", vec4(10.0f), vec4(1), [=](GUI*) {
create_content_panel(engine, menu);
menu->set("content");
create_content_panel(engine);
menu->setPage("content");
}));
panel->add(guiutil::gotoButton(L"Settings", "settings", menu));
@ -590,16 +640,16 @@ void create_pause_panel(Engine* engine, PagesControl* menu) {
}));
}
void menus::create_menus(Engine* engine, PagesControl* menu) {
create_new_world_panel(engine, menu);
create_settings_panel(engine, menu);
create_controls_panel(engine, menu);
create_pause_panel(engine, menu);
create_languages_panel(engine, menu);
create_main_menu_panel(engine, menu);
void menus::create_menus(Engine* engine) {
create_new_world_panel(engine);
create_settings_panel(engine);
create_controls_panel(engine);
create_pause_panel(engine);
create_languages_panel(engine);
create_main_menu_panel(engine);
}
void menus::refresh_menus(Engine* engine, PagesControl* menu) {
create_main_menu_panel(engine, menu);
create_new_world_panel(engine, menu);
void menus::refresh_menus(Engine* engine) {
create_main_menu_panel(engine);
create_new_world_panel(engine);
}

View File

@ -3,13 +3,9 @@
class Engine;
namespace gui {
class PagesControl;
}
namespace menus {
void create_menus(Engine* engine, gui::PagesControl* menu);
void refresh_menus(Engine* engine, gui::PagesControl* menu);
void create_menus(Engine* engine);
void refresh_menus(Engine* engine);
}
#endif // FRONTEND_MENU_H_

View File

@ -21,6 +21,8 @@
#include "../objects/Player.h"
#include "../logic/ChunksController.h"
#include "../logic/LevelController.h"
#include "../logic/scripting/scripting.h"
#include "../logic/scripting/scripting_frontend.h"
#include "../voxels/Chunks.h"
#include "../voxels/Chunk.h"
#include "../engine.h"
@ -31,7 +33,7 @@
#include "ContentGfxCache.h"
#include "LevelFrontend.h"
#include "gui/GUI.h"
#include "gui/panels.h"
#include "gui/containers.h"
#include "menu.h"
#include "../content/Content.h"
@ -45,9 +47,9 @@ Screen::~Screen() {
MenuScreen::MenuScreen(Engine* engine_) : Screen(engine_) {
auto menu = engine->getGUI()->getMenu();
menus::refresh_menus(engine, menu);
menus::refresh_menus(engine);
menu->reset();
menu->set("main");
menu->setPage("main");
uicamera.reset(new Camera(glm::vec3(), Window::height));
uicamera->perspective = false;
@ -87,7 +89,7 @@ LevelScreen::LevelScreen(Engine* engine, Level* level)
: Screen(engine),
level(level),
frontend(std::make_unique<LevelFrontend>(level, engine->getAssets())),
hud(std::make_unique<HudRenderer>(engine, frontend.get())),
hud(std::make_unique<Hud>(engine, frontend.get())),
worldRenderer(std::make_unique<WorldRenderer>(engine, frontend.get())),
controller(std::make_unique<LevelController>(engine->getSettings(), level)) {
@ -96,10 +98,21 @@ LevelScreen::LevelScreen(Engine* engine, Level* level)
animator.reset(new TextureAnimator());
animator->addAnimations(engine->getAssets()->getAnimations());
auto content = level->content;
for (auto& pack : content->getPacks()) {
const ContentPack& info = pack->getInfo();
fs::path scriptFile = info.folder/fs::path("scripts/hud.lua");
if (fs::is_regular_file(scriptFile)) {
scripting::load_hud_script(pack->getEnvironment()->getId(), info.id, scriptFile);
}
}
scripting::on_frontend_init(hud.get());
}
LevelScreen::~LevelScreen() {
std::cout << "-- writing world" << std::endl;
scripting::on_frontend_close();
controller->onWorldSave();
auto world = level->getWorld();
world->write(level.get());

View File

@ -7,7 +7,7 @@
class Assets;
class Level;
class WorldRenderer;
class HudRenderer;
class Hud;
class Engine;
class Camera;
class Batch2D;
@ -40,7 +40,7 @@ public:
class LevelScreen : public Screen {
std::unique_ptr<Level> level;
std::unique_ptr<LevelFrontend> frontend;
std::unique_ptr<HudRenderer> hud;
std::unique_ptr<Hud> hud;
std::unique_ptr<WorldRenderer> worldRenderer;
std::unique_ptr<LevelController> controller;
std::unique_ptr<TextureAnimator> animator;

View File

@ -11,17 +11,17 @@ using glm::vec2;
using glm::vec3;
using glm::vec4;
Batch2D::Batch2D(size_t capacity) : capacity(capacity), offset(0), color(1.0f, 1.0f, 1.0f, 1.0f){
Batch2D::Batch2D(size_t capacity) : capacity(capacity), color(1.0f){
const vattr attrs[] = {
{2}, {2}, {4}, {0}
};
buffer = new float[capacity * B2D_VERTEX_SIZE];
mesh = new Mesh(buffer, 0, attrs);
mesh = std::make_unique<Mesh>(buffer, 0, attrs);
index = 0;
unsigned char pixels[] = {
255, 255, 255, 255,
ubyte pixels[] = {
0xFF, 0xFF, 0xFF, 0xFF
};
blank = new Texture(pixels, 1, 1, GL_RGBA);
_texture = nullptr;
@ -30,7 +30,6 @@ Batch2D::Batch2D(size_t capacity) : capacity(capacity), offset(0), color(1.0f, 1
Batch2D::~Batch2D(){
delete blank;
delete[] buffer;
delete mesh;
}
void Batch2D::begin(){

View File

@ -1,6 +1,7 @@
#ifndef SRC_GRAPHICS_BATCH2D_H_
#define SRC_GRAPHICS_BATCH2D_H_
#include <memory>
#include <stdlib.h>
#include <glm/glm.hpp>
@ -13,8 +14,7 @@ class Sprite;
class Batch2D {
float* buffer;
size_t capacity;
size_t offset;
Mesh* mesh;
std::unique_ptr<Mesh> mesh;
size_t index;
Texture* blank;

View File

@ -13,8 +13,7 @@ using glm::vec3;
using glm::vec4;
Batch3D::Batch3D(size_t capacity)
: capacity(capacity),
offset(0) {
: capacity(capacity) {
const vattr attrs[] = {
{3}, {2}, {4}, {0}
};

View File

@ -12,7 +12,6 @@ class Texture;
class Batch3D {
float* buffer;
size_t capacity;
size_t offset;
Mesh* mesh;
size_t index;

View File

@ -4,16 +4,19 @@
using glm::vec4;
Font::Font(std::vector<Texture*> pages, int lineHeight) : lineHeight_(lineHeight), pages(pages) {
Font::Font(std::vector<std::unique_ptr<Texture>> pages, int lineHeight, int yoffset)
: lineHeight(lineHeight), yoffset(yoffset), pages(std::move(pages)) {
}
Font::~Font(){
for (Texture* texture : pages)
delete texture;
}
int Font::lineHeight() const {
return lineHeight_;
int Font::getYOffset() const {
return yoffset;
}
int Font::getLineHeight() const {
return lineHeight;
}
bool Font::isPrintableChar(int c) {
@ -48,11 +51,11 @@ void Font::draw(Batch2D* batch, std::wstring text, int x, int y, int style) {
if (isPrintableChar(c)){
int charpage = c >> 8;
if (charpage == page){
Texture* texture = pages[charpage];
Texture* texture = pages[charpage].get();
if (texture == nullptr){
texture = pages[0];
texture = pages[0].get();
}
batch->texture(pages[charpage]);
batch->texture(texture);
switch (style){
case STYLE_SHADOW:

View File

@ -1,6 +1,7 @@
#ifndef GRAPHICS_FONT_H_
#define GRAPHICS_FONT_H_
#include <memory>
#include <string>
#include <vector>
#include "../typedefs.h"
@ -13,13 +14,15 @@ const uint STYLE_SHADOW = 1;
const uint STYLE_OUTLINE = 2;
class Font {
int lineHeight_;
int lineHeight;
int yoffset;
public:
std::vector<Texture*> pages;
Font(std::vector<Texture*> pages, int lineHeight);
std::vector<std::unique_ptr<Texture>> pages;
Font(std::vector<std::unique_ptr<Texture>> pages, int lineHeight, int yoffset);
~Font();
int lineHeight() const;
int getLineHeight() const;
int getYOffset() const;
int calcWidth(std::wstring text);
// int getGlyphWidth(char c);
bool isPrintableChar(int c);

View File

@ -9,8 +9,13 @@ GfxContext::GfxContext(const GfxContext* parent, Viewport& viewport, Batch2D* g2
}
GfxContext::~GfxContext() {
while (scissorsCount--) {
Window::popScissor();
}
if (parent == nullptr)
return;
if (depthMask_ != parent->depthMask_) {
glDepthMask(parent->depthMask_);
}
@ -77,3 +82,8 @@ void GfxContext::blendMode(blendmode mode) {
blendMode_ = mode;
Window::setBlendMode(mode);
}
void GfxContext::scissors(glm::vec4 area) {
Window::pushScissor(area);
scissorsCount++;
}

View File

@ -15,6 +15,7 @@ class GfxContext {
bool depthTest_ = false;
bool cullFace_ = false;
blendmode blendMode_ = blendmode::normal;
int scissorsCount = 0;
public:
GfxContext(const GfxContext* parent, Viewport& viewport, Batch2D* g2d);
~GfxContext();
@ -27,6 +28,7 @@ public:
void depthTest(bool flag);
void cullFace(bool flag);
void blendMode(blendmode mode);
void scissors(glm::vec4 area);
};
#endif // GRAPHICS_GFX_CONTEXT_H_

View File

@ -8,17 +8,23 @@ const uint LB_VERTEX_SIZE = (3+4);
LineBatch::LineBatch(size_t capacity) : capacity(capacity) {
const vattr attrs[] = { {3},{4}, {0} };
buffer = new float[capacity * LB_VERTEX_SIZE * 2];
mesh = new Mesh(buffer, 0, attrs);
mesh = std::make_unique<Mesh>(buffer, 0, attrs);
index = 0;
}
LineBatch::~LineBatch(){
delete[] buffer;
delete mesh;
}
void LineBatch::line(float x1, float y1, float z1, float x2, float y2, float z2,
float r, float g, float b, float a) {
void LineBatch::line(
float x1, float y1,
float z1, float x2,
float y2, float z2,
float r, float g, float b, float a
) {
if (index + LB_VERTEX_SIZE * 2 >= capacity) {
render();
}
buffer[index] = x1;
buffer[index+1] = y1;
buffer[index+2] = z1;

View File

@ -1,13 +1,14 @@
#ifndef GRAPHICS_LINEBATCH_H_
#define GRAPHICS_LINEBATCH_H_
#include <memory>
#include <stdlib.h>
#include <glm/glm.hpp>
class Mesh;
class LineBatch {
Mesh* mesh;
std::unique_ptr<Mesh> mesh;
float* buffer;
size_t index;
size_t capacity;

View File

@ -19,7 +19,7 @@ Texture::Texture(ubyte* data, int width, int height, uint format)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glGenerateMipmap(GL_TEXTURE_2D);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 2);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1);
glBindTexture(GL_TEXTURE_2D, 0);
}

View File

@ -49,9 +49,30 @@ void TextureAnimator::update(float delta) {
float srcPosY = elem.srcTexture->height - frame.size.y - frame.srcPos.y; // vertical flip
glBlitFramebuffer(frame.srcPos.x, srcPosY, frame.srcPos.x + frame.size.x, srcPosY + frame.size.y,
frame.dstPos.x, frame.dstPos.y, frame.dstPos.x + frame.size.x, frame.dstPos.y + frame.size.y,
GL_COLOR_BUFFER_BIT, GL_NEAREST);
// Extensions
const int ext = 2;
for (int y = -1; y <= 1; y++) {
for (int x = -1; x <= 1; x++) {
if (x == 0 && y == 0)
continue;
glBlitFramebuffer(
frame.srcPos.x, srcPosY, frame.srcPos.x + frame.size.x, srcPosY + frame.size.y,
frame.dstPos.x+x*ext, frame.dstPos.y+y*ext,
frame.dstPos.x + frame.size.x+x*ext, frame.dstPos.y + frame.size.y+y*ext,
GL_COLOR_BUFFER_BIT, GL_NEAREST
);
}
}
glBlitFramebuffer(
frame.srcPos.x, srcPosY,
frame.srcPos.x + frame.size.x,
srcPosY + frame.size.y,
frame.dstPos.x, frame.dstPos.y,
frame.dstPos.x + frame.size.x,
frame.dstPos.y + frame.size.y,
GL_COLOR_BUFFER_BIT, GL_NEAREST
);
}
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);

59
src/items/Inventories.cpp Normal file
View File

@ -0,0 +1,59 @@
#include "Inventories.h"
#include <algorithm>
#include "../world/Level.h"
#include "../world/World.h"
Inventories::Inventories(Level& level) : level(level) {
}
Inventories::~Inventories() {
}
std::shared_ptr<Inventory> Inventories::create(size_t size) {
int64_t id = level.getWorld()->getNextInventoryId();
auto inv = std::make_shared<Inventory>(id, size);
store(inv);
return inv;
}
std::shared_ptr<Inventory> Inventories::createVirtual(size_t size) {
int64_t id;
do {
id = -std::max<int64_t>(1LL, std::llabs(random.rand64()));
} while (map.find(id) != map.end());
auto inv = std::make_shared<Inventory>(id, size);
store(inv);
return inv;
}
void Inventories::store(std::shared_ptr<Inventory> inv) {
map[inv->getId()] = inv;
}
void Inventories::remove(int64_t id) {
map.erase(id);
}
std::shared_ptr<Inventory> Inventories::get(int64_t id) {
auto found = map.find(id);
if (found == map.end())
return nullptr;
return found->second;
}
std::shared_ptr<Inventory> Inventories::clone(int64_t id) {
auto original = get(id);
if (original == nullptr)
return nullptr;
auto origptr = reinterpret_cast<const Inventory*>(original.get());
auto clone = std::make_shared<Inventory>(*origptr);
clone->setId(level.getWorld()->getNextInventoryId());
return clone;
}
const inventories_map& Inventories::getMap() const {
return map;
}

44
src/items/Inventories.h Normal file
View File

@ -0,0 +1,44 @@
#ifndef ITEMS_INVENTORIES_H_
#define ITEMS_INVENTORIES_H_
#include <string>
#include <memory>
#include <unordered_map>
#include "Inventory.h"
#include "../maths/util.h"
class Level;
using inventories_map = std::unordered_map<int64_t, std::shared_ptr<Inventory>>;
/* Inventories runtime storage */
class Inventories {
Level& level;
inventories_map map;
PseudoRandom random;
public:
Inventories(Level& level);
~Inventories();
/* Create new inventory with new id */
std::shared_ptr<Inventory> create(size_t size);
/* Create runtime-only inventory (has negative id) */
std::shared_ptr<Inventory> createVirtual(size_t size);
/* Store inventory */
void store(std::shared_ptr<Inventory> inv);
/* Remove inventory from map */
void remove(int64_t id);
/* Get inventory by id (works with both real and virtual)*/
std::shared_ptr<Inventory> get(int64_t id);
std::shared_ptr<Inventory> clone(int64_t id);
const inventories_map& getMap() const;
};
#endif // ITEMS_INVENTORIES_H_

View File

@ -2,7 +2,7 @@
#include "../content/ContentLUT.h"
Inventory::Inventory(size_t size) : slots(size) {
Inventory::Inventory(int64_t id, size_t size) : id(id), slots(size) {
}
ItemStack& Inventory::getSlot(size_t index) {
@ -45,8 +45,12 @@ void Inventory::move(
}
void Inventory::deserialize(dynamic::Map* src) {
id = src->getNum("id", 1);
auto slotsarr = src->list("slots");
size_t slotscount = std::min(slotsarr->size(), slots.size());
size_t slotscount = slotsarr->size();
while (slots.size() < slotscount) {
slots.push_back(ItemStack());
}
for (size_t i = 0; i < slotscount; i++) {
auto item = slotsarr->map(i);
itemid_t id = item->getInt("id", ITEM_EMPTY);
@ -58,6 +62,8 @@ void Inventory::deserialize(dynamic::Map* src) {
std::unique_ptr<dynamic::Map> Inventory::serialize() const {
auto map = std::make_unique<dynamic::Map>();
map->put("id", id);
auto& slotsarr = map->putList("slots");
for (size_t i = 0; i < slots.size(); i++) {
auto& item = slots[i];

Some files were not shown because too many files have changed in this diff Show More