diff --git a/Makefile b/Makefile index 5e1b2b8..4069b4f 100644 --- a/Makefile +++ b/Makefile @@ -26,7 +26,7 @@ tracy_build: meson compile -j 10 -C builddir test: build - ./builddir/runtests + ./builddir/fuc2it run: build test ifeq '$(OS)' 'Windows_NT' @@ -49,7 +49,7 @@ clean: meson compile --clean -C builddir debug_test: build - gdb --nx -x .gdbinit --ex run --args builddir/runtests -e + gdb --nx -x .gdbinit --ex run --args builddir/fuc2it -e win_installer: powershell 'start "C:\Program Files (x86)\solicus\InstallForge\bin\ifbuilderenvx86.exe" scripts\win_installer.ifp' diff --git a/demos/calc.cpp b/demos/calc.cpp index c96ffea..8f38517 100644 --- a/demos/calc.cpp +++ b/demos/calc.cpp @@ -169,8 +169,8 @@ struct CalculatorUI { $gui.set(id, { label }); wchar_t op = label[0]; $gui.set(id, { - [&, op](auto, auto) { handle_button(op); } - }); + [&, op](auto) { handle_button(op); } + }); } } @@ -182,8 +182,8 @@ struct CalculatorUI { // $gui.debug_layout(window); } - void mouse(float x, float y, bool hover) { - $gui.mouse(x, y, hover); + void mouse(float x, float y, guecs::Modifiers mods) { + $gui.mouse(x, y, mods); } void handle_button(wchar_t op) { diff --git a/demos/clicker_game.cpp b/demos/clicker_game.cpp index 17ffbb1..c5e68b6 100644 --- a/demos/clicker_game.cpp +++ b/demos/clicker_game.cpp @@ -92,9 +92,9 @@ struct ClickerUI { auto id = $gui.entity(name); if(name != "clicker") { $gui.set(id, {}); - $gui.set(id, { "clicker_treat_bone" }); + $gui.set(id, { "clicker_treat_bone" }); $gui.set(id, { - [&](auto, auto) { handle_button(Event::A_BUTTON); } + [&](auto) { handle_button(Event::A_BUTTON); } }); } } @@ -103,7 +103,7 @@ struct ClickerUI { $gui.set($clicker, {"clicker_the_dog"}); $gui.set($clicker, {"clicker_bark"}); $gui.set($clicker, { - [&](auto, auto) { handle_button(Event::CLICKER); } + [&](auto) { handle_button(Event::CLICKER); } }); // custom components need to be initialized manually @@ -126,8 +126,8 @@ struct ClickerUI { // $gui.debug_layout(window); } - void mouse(float x, float y, bool hover) { - $gui.mouse(x, y, hover); + void mouse(float x, float y, guecs::Modifiers mods) { + $gui.mouse(x, y, mods); } void handle_button(Event ev) { diff --git a/demos/meters.cpp b/demos/meters.cpp new file mode 100644 index 0000000..10195ff --- /dev/null +++ b/demos/meters.cpp @@ -0,0 +1,124 @@ +#include "guecs/sfml/backend.hpp" +#include "guecs/sfml/components.hpp" +#include "guecs/uistack.hpp" +#include "guecs/ui.hpp" +#include +#include +#include +#include +#include + +using namespace std::chrono_literals; + +struct TestMeters { + guecs::UI $gui{}; + + void init() { + $gui.position(0, 0, 1280, 720); + $gui.layout( + "[*%(300,100)meter1|_|_|*%(100,400)vert1|*%(100,400)vert2|*%(100,400)vert3]" + "[*%(300,100)meter2|_|_|_|_|_]" + "[*%(300,100)meter3|_|_|_|_|_]" + "[*%(300,100)meter4|_|_|_|_|_]"); + + for(auto& [name, cell] : $gui.cells()) { + auto gui_id = $gui.entity(name); + + $gui.set(gui_id, {guecs::THEME.PADDING, {120, 25, 25, 255}}); + + if(name.starts_with("meter")) { + $gui.set(gui_id, {1.0f, guecs::THEME.DARK_LIGHT, guecs::THEME.PADDING}); + } else { + $gui.set(gui_id, {1.0f, guecs::THEME.DARK_LIGHT, guecs::THEME.PADDING, true}); + } + + $gui.set(gui_id, {}); + $gui.set(gui_id, {guecs::to_wstring(name)}); + $gui.set(gui_id, { + [&, gui_id, name](auto) { + auto& meter = $gui.get(gui_id); + auto& bg = $gui.get(gui_id); + meter.percent = 1.0f; + if(meter.color.r == 255) { + meter.color = guecs::THEME.DARK_LIGHT; + bg.color = {120, 25, 25, 255}; + } else { + meter.color = {255, 0, 0, 255}; + bg.color = {0,255,0,255}; + } + } + }); + } + + $gui.init(); + } + + void render(sf::RenderTarget& target) { + $gui.render(target); + } + + bool mouse(float x, float y, guecs::Modifiers mods) { + return $gui.mouse(x, y, mods); + } + + void update() { + $gui.update(); + } +}; + +int main() { + try { + sfml::Backend backend; + guecs::init(&backend); + + sf::RenderWindow window(sf::VideoMode({1280, 720}), "Meter Tester"); + window.setVerticalSyncEnabled(true); + window.setFramerateLimit(60); + window.setPosition({0,0}); + TestMeters gui; + gui.init(); + + while(window.isOpen()) { + while (const auto event = window.pollEvent()) { + if(event->is()) { + window.close(); + } + + if(const auto* mouse = event->getIf()) { + if(mouse->button == sf::Mouse::Button::Left) { + sf::Vector2f pos = window.mapPixelToCoords(mouse->position); + gui.mouse(pos.x, pos.y, false); + } + } else if(const auto* key = event->getIf()) { + using KEY = sf::Keyboard::Scan; + + switch(key->scancode) { + case KEY::Q: + return 0; + default: + break; + } + } + } + + gui.$gui.query([](auto ent, auto& meter) { + if(meter.percent <= 0.0f) { + meter.percent = 1.0f; + } else { + meter.percent -= 0.01f * float(ent); + } + }); + + gui.update(); + gui.render(window); + window.display(); + + std::this_thread::sleep_for(100ms); + } + } catch(const std::system_error& e) { + std::cout << "WARNING: On OSX you'll get this error on shutdown.\n"; + std::cout << "Caught system_error with code " + "[" << e.code() << "] meaning " + "[" << e.what() << "]\n"; + } +} diff --git a/demos/multiscreen.cpp b/demos/multiscreen.cpp new file mode 100644 index 0000000..8234d1c --- /dev/null +++ b/demos/multiscreen.cpp @@ -0,0 +1,123 @@ +#include "guecs/sfml/backend.hpp" +#include "guecs/sfml/components.hpp" +#include "guecs/uistack.hpp" +#include "guecs/ui.hpp" +#include +#include +#include +#include +#include + +using namespace std::chrono_literals; + +struct TestScreen { + guecs::UI $gui{}; + + TestScreen(const std::string &layout) { + init(layout); + } + + void init(const std::string& layout) { + $gui.position(0, 0, 1280, 720); + $gui.layout(layout); + + for(auto& [name, cell] : $gui.cells()) { + auto gui_id = $gui.entity(name); + $gui.set(gui_id, {}); + $gui.set(gui_id, {}); + $gui.set(gui_id, {guecs::to_wstring(name)}); + $gui.set(gui_id, { + [&, gui_id, name](auto) { fmt::println("click! {}={}", name, gui_id); } + }); + } + + $gui.init(); + } + + void render(sf::RenderTarget& target) { + $gui.render(target); + } + + bool mouse(float x, float y, guecs::Modifiers mods) { + return $gui.mouse(x, y, mods); + } + + void update() { + // empty + } +}; + + +int main() { + try { + sfml::Backend backend; + guecs::init(&backend); + + sf::RenderWindow window(sf::VideoMode({1280, 720}), "Multi-Screen Tester"); + window.setVerticalSyncEnabled(true); + window.setFramerateLimit(60); + window.setPosition({0,0}); + + gui::UIStack stack; + auto test2 = std::make_shared("[test2|sonice]"); + auto test3 = std::make_shared("[test3|pointer]"); + + { + stack.add("test1", std::make_shared("[test1|hello]")); + stack.add("test2", test2); + stack.add("test3", test3); + } + + stack.set_active("test1"); + + while(window.isOpen()) { + while (const auto event = window.pollEvent()) { + if(event->is()) { + window.close(); + } + + if(const auto* mouse = event->getIf()) { + if(mouse->button == sf::Mouse::Button::Left) { + sf::Vector2f pos = window.mapPixelToCoords(mouse->position); + stack.mouse(pos.x, pos.y, false); + } + } else if(const auto* key = event->getIf()) { + using KEY = sf::Keyboard::Scan; + + switch(key->scancode) { + case KEY::Q: + return 0; + case KEY::N: { + bool good = stack.next(); + fmt::println("showing {} is {}", stack.active_name(), good); + } break; + case KEY::P: { + bool good = stack.prev(); + fmt::println("showing {} is {}", stack.active_name(), good); + } break; + case KEY::F: + stack.first(); + break; + case KEY::L: + stack.last(); + break; + default: + break; + } + } + } + + stack.update(); + stack.render(window); + window.display(); + + std::this_thread::sleep_for(100ms); + } + + } catch(const std::system_error& e) { + std::cout << "WARNING: On OSX you'll get this error on shutdown.\n"; + std::cout << "Caught system_error with code " + "[" << e.code() << "] meaning " + "[" << e.what() << "]\n"; + } +} diff --git a/include/guecs/sfml/backend.hpp b/include/guecs/sfml/backend.hpp index fc36f35..e4c2e9f 100644 --- a/include/guecs/sfml/backend.hpp +++ b/include/guecs/sfml/backend.hpp @@ -1,3 +1,4 @@ +#pragma once #include "guecs/ui.hpp" namespace sfml { diff --git a/include/guecs/sfml/components.hpp b/include/guecs/sfml/components.hpp index dc82033..d681ebc 100644 --- a/include/guecs/sfml/components.hpp +++ b/include/guecs/sfml/components.hpp @@ -21,7 +21,7 @@ namespace guecs { void init(lel::Cell &cell, shared_ptr font_ptr); void update(const std::wstring& new_content); - void render(sf::RenderWindow& window, sf::Shader *shader_ptr); + void render(sf::RenderTarget& window, sf::Shader *shader_ptr); }; struct Sprite { @@ -30,13 +30,29 @@ namespace guecs { bool stretch = true; bool is_icon = false; std::shared_ptr sprite = nullptr; + std::shared_ptr texture = nullptr; void init(lel::Cell &cell); void update(const string& new_name); - void render(sf::RenderWindow& window, sf::Shader *shader_ptr); + void render(sf::RenderTarget& window, sf::Shader *shader_ptr); + }; + + struct Icon : public Sprite { + template + Icon(Args... args) : Sprite(args...) + { + stretch = false; + is_icon = true; + } + + Icon() { + stretch = false; + is_icon = true; + }; }; struct Rectangle { + // BUG: confirm that padding is being used correctly here int padding = THEME.PADDING; sf::Color color = THEME.FILL_COLOR; sf::Color border_color = THEME.BORDER_COLOR; @@ -44,17 +60,29 @@ namespace guecs { shared_ptr shape = nullptr; void init(lel::Cell& cell); - void render(sf::RenderWindow& window, sf::Shader *shader_ptr); + void render(sf::RenderTarget& window, sf::Shader *shader_ptr); + void update(); }; + // BUG: meter needs a backing rectangle of one color for the full bar, then + // another rectangle for the current setting. + // BUG: the meter will be past the end, probably a floating point error struct Meter { float percent = 1.0f; sf::Color color = THEME.BG_COLOR_DARK; - Rectangle bar; + int padding = THEME.PADDING; + bool vertical = false; + Rectangle bar{padding, color}; + + // this is set automatically if a Rectangle is configured for the cell + size_t $cell_x = 0; + size_t $cell_y = 0; + size_t $cell_w = 0; + size_t $cell_h = 0; void init(lel::Cell& cell); - void init(lel::Cell& cell, Rectangle& bg); - void render(lel::Cell& cell, sf::RenderWindow& window, sf::Shader *shader_ptr); + void render(sf::RenderTarget& window, sf::Shader *shader_ptr); + void update(); }; struct Effect { @@ -80,6 +108,7 @@ namespace guecs { void stop(bool hover); }; + // BUG: it should be _way_ easier to set the background and change it struct Background { float x = 0.0f; float y = 0.0f; @@ -88,6 +117,7 @@ namespace guecs { sf::Color color=THEME.BG_COLOR; shared_ptr shape = nullptr; shared_ptr sprite = nullptr; + shared_ptr texture = nullptr; Background(lel::Parser& parser, sf::Color bg_color=THEME.BG_COLOR) : x(parser.grid_x), @@ -100,7 +130,7 @@ namespace guecs { Background() {} void init(); - void render(sf::RenderWindow& window); + void render(sf::RenderTarget& window); void set_color(sf::Color c); void set_sprite(const std::string& name, bool stretch=false); diff --git a/include/guecs/ui.hpp b/include/guecs/ui.hpp index d66b063..b797bc1 100644 --- a/include/guecs/ui.hpp +++ b/include/guecs/ui.hpp @@ -10,6 +10,7 @@ #include "guecs/theme.hpp" #include "guecs/sfml/components.hpp" #include +#include namespace guecs { using std::shared_ptr, std::wstring, std::string; @@ -24,22 +25,30 @@ namespace guecs { std::queue free_indices; }; + namespace ModBit { + enum { + NONE=0, + hover=1, + left=2, + right=3 + }; + } + + using Modifiers = std::bitset<16>; + + constexpr const Modifiers NO_MODS{0}; + struct Clickable { /* This is actually called by UI::mouse and passed the entity ID of the * button pressed so you can interact with it in the event handler. */ - std::function action; - }; - - struct ActionData { - std::any data; + std::function action; }; struct CellName { string name; }; - class UI { public: Entity MAIN = 0; @@ -51,6 +60,9 @@ namespace guecs { shared_ptr $font = nullptr; lel::Parser $parser; string $grid = ""; + // BUG: check for this to prevent bugs, or find a way to shape the API so you + // simply can't get it wrong + bool initialized = false; UI(); @@ -59,6 +71,7 @@ namespace guecs { sf::Vector2f get_size(); void layout(const string& grid); Entity init_entity(const string& name); + bool contains(const string& name); Entity entity(const string& name); Entity entity(const string& name, int id); const std::string& name_for(Entity gui_id); @@ -68,11 +81,12 @@ namespace guecs { } void init(); - void render(sf::RenderWindow& window); - bool mouse(float x, float y, bool hover); - void click_on(const string& name, bool required=false); - void click_on(Entity slot_id); - void debug_layout(sf::RenderWindow& window); + void render(sf::RenderTarget& window); + void update(); + bool mouse(float x, float y, Modifiers mods); + void click_on(const std::string& name, Modifiers mods=NO_MODS); + void click_on(Entity slot_id, Modifiers mods=NO_MODS); + void debug_layout(sf::RenderTarget& window); Entity entity() { return ++entity_count; } @@ -211,15 +225,15 @@ namespace guecs { } template - void render_helper(sf::RenderWindow& window, T& target, sf::Shader *shader_ptr) { + void render_helper(sf::RenderTarget& window, T& target, sf::Shader *shader_ptr) { window.draw(*target, shader_ptr); } sf::Shader* find_shader(Entity ent, bool is_shape); void show_sprite(const string& region, const string& sprite_name); + void show_icon(const string& region, const string& sprite_name); void show_text(const string& region, const wstring& content); - void show_label(const string& region, const wstring& content); }; diff --git a/include/guecs/uistack.hpp b/include/guecs/uistack.hpp new file mode 100644 index 0000000..d451ea0 --- /dev/null +++ b/include/guecs/uistack.hpp @@ -0,0 +1,97 @@ +#include +#include +#include + +namespace gui { + template + struct UIStack { + using UIStackMap = std::flat_map>; + + UIStackMap screens{}; + bool visible = false; + + UIStackMap::iterator $current{screens.begin()}; + SCREEN_TYPE* $active = nullptr; + + void add(const std::string& name, std::shared_ptr screen) { + screens.insert_or_assign(name, screen); + update_active(screens.begin()); + } + + void update_active(const UIStackMap::iterator& itr) { + $current = itr; + $active = (*$current).second.get(); + } + + void set_active(const std::string& name) { + assert(screens.contains(name) && "no screen with that name"); + update_active(screens.find(name)); + } + + void set_visible(bool new_value) { + visible = new_value; + } + + bool is_visible() { + return visible; + } + + void render(sf::RenderTarget& target) { + assert($active != nullptr && "you didn't set active"); + $active->render(target); + } + + void update() { + assert($active != nullptr && "you didn't set active"); + $active->update(); + } + + bool mouse(float x, float y, guecs::Modifiers mods = guecs::NO_MODS) { + assert($active != nullptr && "you didn't set active"); + return $active->mouse(x, y, mods); + } + + bool move(const UIStackMap::iterator& itr, const UIStackMap::iterator& avoid, const UIStackMap::iterator& result) { + if(itr != avoid) { + update_active(result); + return true; + } else { + return false; + } + } + + bool next() { + return move($current + 1, screens.end(), $current + 1); + } + + bool prev() { + return move($current, screens.begin(), $current - 1); + } + + void first() { + update_active(screens.begin()); + } + + void last() { + update_active(screens.end() - 1); + } + + const std::string& active_name() { + assert($active != nullptr && "you didn't set active"); + return (*$current).first; + } + + std::shared_ptr active_ui() { + assert($active != nullptr && "you didn't set active"); + return (*$current).second; + } + + std::shared_ptr at(const std::string& name) { + return screens.at(name); + } + + const UIStackMap::key_container_type& names() { + return screens.keys(); + } + }; +} diff --git a/lel-guecs.wrap b/lel-guecs.wrap new file mode 100644 index 0000000..5ce8218 --- /dev/null +++ b/lel-guecs.wrap @@ -0,0 +1,9 @@ +[wrap-git] +directory=lel-guecs-0.7.3 +url=https://git.learnjsthehardway.com/learn-code-the-hard-way/lel-guecs.git +revision=HEAD +depth=1 +method=meson + +[provide] +lel_guecs = lel_guecs_dep diff --git a/meson.build b/meson.build index a9f4b00..02f9c18 100644 --- a/meson.build +++ b/meson.build @@ -3,14 +3,21 @@ # HEY BUG: when you have a . spec in a LEL it doesn't work on text project('lel-guecs', 'cpp', - version: '0.5.0', + version: '0.7.4', default_options: [ - 'cpp_std=c++20', + 'cpp_std=c++23', 'cpp_args=-D_GLIBCXX_DEBUG=1 -D_GLIBCXX_DEBUG_PEDANTIC=1', ]) # use this for common options only for our executables -cpp_args=[] +cpp_args=[ + '-I../subprojects/utf8cpp-4.0.9/source', + '-Wno-unused-parameter', + '-Wno-unused-function', + '-Wno-unused-variable', + '-Wno-unused-but-set-variable', + '-Wno-deprecated-declarations', + ] link_args=[] # these are passed as override_defaults exe_defaults = [ 'warning_level=2' ] @@ -30,10 +37,6 @@ if build_machine.system() == 'windows' exe_defaults += ['werror=false'] elif build_machine.system() == 'darwin' - add_global_link_arguments( - language: 'cpp', - ) - opengl = dependency('OpenGL') corefoundation = dependency('CoreFoundation') carbon = dependency('Carbon') @@ -48,10 +51,11 @@ elif build_machine.system() == 'darwin' ] endif -catch2 = subproject('catch2').get_variable('catch2_with_main_dep') +fuc2 = subproject('fuc2').get_variable('fuc2_dep') fmt = subproject('fmt').get_variable('fmt_dep') json = subproject('nlohmann_json').get_variable('nlohmann_json_dep') freetype2 = subproject('freetype2').get_variable('freetype_dep') +utf8cpp = subproject('utf8cpp').get_variable('utf8cpp_dep') flac = subproject('flac').get_variable('flac_dep') ogg = subproject('ogg').get_variable('libogg_dep') @@ -66,7 +70,7 @@ sfml_window = subproject('sfml').get_variable('sfml_window_dep') lib_depends = [ fmt, sfml_graphics, sfml_system, sfml_window ] lib_sfml_depends = [ - fmt, json, freetype2, flac, ogg, + fmt, json, freetype2, flac, ogg, utf8cpp, vorbis, vorbisfile, vorbisenc, sfml_audio, ] @@ -113,19 +117,20 @@ lel_guecs_sfml_dep = declare_dependency( link_with: lel_guecs_sfml_lib, include_directories: lel_guecs_inc) -executable('runtests', [ +executable('fuc2it', [ + 'tests/guecs.cpp', 'tests/lel.cpp', + 'tests/main.cpp', 'tests/shaders.cpp', 'tests/sound.cpp', 'tests/textures.cpp', - 'tests/guecs.cpp', ], cpp_args: cpp_args, link_args: link_args, override_options: exe_defaults, include_directories: lel_guecs_inc, link_with: [lel_guecs_lib, lel_guecs_sfml_lib], - dependencies: dependencies + [catch2]) + dependencies: dependencies + [fuc2]) executable('clicker_game', [ 'demos/clicker_game.cpp', @@ -146,3 +151,23 @@ executable('calc', [ include_directories: lel_guecs_inc, link_with: [lel_guecs_lib, lel_guecs_sfml_lib], dependencies: dependencies) + +executable('multiscreen', [ + 'demos/multiscreen.cpp', + ], + cpp_args: cpp_args, + link_args: link_args, + override_options: exe_defaults, + include_directories: lel_guecs_inc, + link_with: [lel_guecs_lib, lel_guecs_sfml_lib], + dependencies: dependencies) + +executable('meters', [ + 'demos/meters.cpp', + ], + cpp_args: cpp_args, + link_args: link_args, + override_options: exe_defaults, + include_directories: lel_guecs_inc, + link_with: [lel_guecs_lib, lel_guecs_sfml_lib], + dependencies: dependencies) diff --git a/scripts/reset_build.sh b/scripts/reset_build.sh old mode 100644 new mode 100755 diff --git a/src/guecs/sfml/components.cpp b/src/guecs/sfml/components.cpp index 6dbb221..2144a96 100644 --- a/src/guecs/sfml/components.cpp +++ b/src/guecs/sfml/components.cpp @@ -7,19 +7,19 @@ namespace guecs { using std::make_shared; - template - void sfml_center_helper(T& obj, lel::Cell& cell, int padding) { - sf::Vector2f position{float(cell.x + padding), float(cell.y + padding)}; + // BUG: this seems to center things wrong in lel layouts + void sfml_center_helper(auto& obj, lel::Cell& cell, int padding) { + sf::Vector2f position{float(cell.x + padding), float(cell.y + padding)}; - if(cell.center) { - auto bounds = obj->getLocalBounds(); - position = {float(cell.mid_x), float(cell.mid_y)}; - obj->setOrigin({bounds.size.x/2, bounds.size.y/2}); - } - - obj->setPosition(position); + if(cell.center) { + auto bounds = obj->getLocalBounds(); + position = {float(cell.mid_x), float(cell.mid_y)}; + obj->setOrigin({bounds.size.x/2, bounds.size.y/2}); } + obj->setPosition(position); + } + inline SpriteTexture load_texture(const string& name, bool is_icon) { if(is_icon) { return BACKEND->get_icon(name); @@ -46,7 +46,7 @@ namespace guecs { text->setCharacterSize(size); } - void Text::render(sf::RenderWindow& window, sf::Shader *shader_ptr) { + void Text::render(sf::RenderTarget& window, sf::Shader *shader_ptr) { window.draw(*text, shader_ptr); } @@ -59,7 +59,8 @@ namespace guecs { if(new_name != name) { name = new_name; auto sprite_texture = BACKEND->get_sprite(name); - sprite->setTexture(*sprite_texture.texture); + texture = sprite_texture.texture; + sprite->setTexture(*texture); sprite->setTextureRect({{0,0},sprite_texture.frame_size}); } } @@ -71,7 +72,8 @@ namespace guecs { auto bounds = sprite_texture.frame_size; sf::IntRect rect{{0,0}, bounds}; - sprite = make_shared(*sprite_texture.texture, rect); + texture = sprite_texture.texture; + sprite = make_shared(*texture, rect); if(stretch) { sprite->setScale({ @@ -87,36 +89,54 @@ namespace guecs { sfml_center_helper(sprite, cell, padding); } - void Sprite::render(sf::RenderWindow& window, sf::Shader *shader_ptr) { + void Sprite::render(sf::RenderTarget& window, sf::Shader *shader_ptr) { window.draw(*sprite, shader_ptr); } void Rectangle::init(lel::Cell& cell) { sf::Vector2f size{float(cell.w) - padding * 2, float(cell.h) - padding * 2}; if(shape == nullptr) shape = make_shared(size); + update(); + sfml_center_helper(shape, cell, padding); + } + + void Rectangle::render(sf::RenderTarget& window, sf::Shader *shader_ptr) { + window.draw(*shape, shader_ptr); + } + + void Rectangle::update() { shape->setFillColor(color); shape->setOutlineColor(border_color); shape->setOutlineThickness(border_px); } - void Rectangle::render(sf::RenderWindow& window, sf::Shader *shader_ptr) { - window.draw(*shape, shader_ptr); - } - - void Meter::init(lel::Cell& cell, Rectangle& bg) { - bg.shape->setFillColor(color); - init(cell); - } - void Meter::init(lel::Cell& cell) { + $cell_x = cell.x; + $cell_y = cell.y; + $cell_w = cell.w; + $cell_h = cell.h; bar.init(cell); + update(); } - void Meter::render(lel::Cell& cell, sf::RenderWindow& window, sf::Shader *shader_ptr) { - float level = std::clamp(percent, 0.0f, 1.0f) * float(cell.w); - // ZED: this 6 is a border width, make it a thing - bar.shape->setSize({std::max(level, 0.0f), float(cell.h - 6)}); - window.draw(*bar.shape, shader_ptr); + void Meter::render(sf::RenderTarget& window, sf::Shader *shader_ptr) { + bar.render(window, shader_ptr); + } + + void Meter::update() { + bar.color = color; + bar.padding = padding; + bar.update(); + + if(vertical) { + float level = std::clamp(percent, 0.0f, 1.0f) * float($cell_h); + sf::Vector2f size{float($cell_w) - padding * 2, std::max(level, 0.0f) - padding}; + bar.shape->setSize(size); + bar.shape->setPosition({float($cell_x + padding), float($cell_y) + (float($cell_h) - size.y)}); + } else { + float level = std::clamp(percent, 0.0f, 1.0f) * float($cell_w); + bar.shape->setSize({std::max(level, 0.0f) - padding * 2, float($cell_h) - padding * 2}); + } } void Sound::play(bool hover) { @@ -135,7 +155,6 @@ namespace guecs { assert(w > 0.0f && "Background.w must be > 0.0f. Forgot $gui.$parser?"); assert(h > 0.0f && "Background.h must be > 0.0f. Forgot $gui.$parser?"); - std::cout << "x=" << x << " y=" << y << " w=" << w << " h=" << h << std::endl; sf::Vector2f size{float(w), float(h)}; if(shape == nullptr) { shape = make_shared(size); @@ -155,7 +174,8 @@ namespace guecs { auto sprite_texture = BACKEND->get_sprite(name); sf::IntRect rect{{0,0},sprite_texture.frame_size}; - sprite = make_shared(*sprite_texture.texture, rect); + texture = sprite_texture.texture; + sprite = make_shared(*texture, rect); sprite->setPosition({float(x), float(y)}); @@ -168,7 +188,7 @@ namespace guecs { } } - void Background::render(sf::RenderWindow& window) { + void Background::render(sf::RenderTarget& window) { if(shape != nullptr) { window.draw(*shape); } diff --git a/src/guecs/ui.cpp b/src/guecs/ui.cpp index 5e8d498..87fa4c7 100644 --- a/src/guecs/ui.cpp +++ b/src/guecs/ui.cpp @@ -3,6 +3,7 @@ #include #include #include +#include namespace guecs { using std::make_shared; @@ -51,6 +52,10 @@ namespace guecs { return $ents_name.at(gui_id); } + bool UI::contains(const string& name) { + return $name_ents.contains(name); + } + Entity UI::entity(const string& name) { assert($name_ents.contains(name) && "GUECS entity does not exist. Mispelled cell name?"); @@ -75,11 +80,7 @@ namespace guecs { }); query([this](auto ent, auto &cell, auto& meter) { - if(auto bg = get_if(ent)) { - meter.init(cell, *bg); - } else { - meter.init(cell); - } + meter.init(cell); }); query([this](auto, auto& cell, auto& text) { @@ -89,9 +90,13 @@ namespace guecs { query([&](auto, auto &cell, auto &sprite) { sprite.init(cell); }); + + query([&](auto, auto &cell, auto &icon) { + icon.init(cell); + }); } - void UI::debug_layout(sf::RenderWindow& window) { + void UI::debug_layout(sf::RenderTarget& window) { query([&](const auto, auto &cell) { sf::RectangleShape rect{{float(cell.w), float(cell.h)}}; rect.setPosition({float(cell.x), float(cell.y)}); @@ -110,7 +115,19 @@ namespace guecs { } } - void UI::render(sf::RenderWindow& window) { + void UI::update() { + query([&](auto ent, auto& rect) { + rect.update(); + }); + + query([&](auto ent, auto &meter) { + meter.update(); + }); + } + + // BUG: either render detects that the things are initialized or there's + // another validator function I can call in debug modes to confirm they are + void UI::render(sf::RenderTarget& window) { if(auto bg = get_if(MAIN)) { bg->render(window); } @@ -124,9 +141,9 @@ namespace guecs { rect.render(window, shader_ptr); }); - query([&](auto ent, auto& cell, auto &meter) { + query([&](auto ent, auto &meter) { auto shader_ptr = find_shader(ent, true); - meter.render(cell, window, shader_ptr); + meter.render(window, shader_ptr); }); query([&](auto ent, auto& sprite) { @@ -134,15 +151,20 @@ namespace guecs { sprite.render(window, shader_ptr); }); + query([&](auto ent, auto& icon) { + auto shader_ptr = find_shader(ent, false); + icon.render(window, shader_ptr); + }); + query([&](auto ent, auto& text) { auto shader_ptr = find_shader(ent, false); text.render(window, shader_ptr); }); } - bool UI::mouse(float x, float y, bool hover) { + bool UI::mouse(float x, float y, Modifiers mods) { int action_count = 0; - // BUG: Is Parser::hit useful? + bool hover = mods.test(size_t(ModBit::hover)); query([&](auto ent, auto& cell) { if((x >= cell.x && x <= cell.x + cell.w) && @@ -160,11 +182,11 @@ namespace guecs { if(hover) return; - click_on(ent); + click_on(ent, mods); action_count++; } else { - do_if(ent, [hover](auto& effect) { + do_if(ent, [](auto& effect) { effect.stop(); }); @@ -188,6 +210,16 @@ namespace guecs { } } + void UI::show_icon(const string& region, const string& sprite_name) { + auto ent = entity(region); + + if(auto sprite = get_if(ent)) { + sprite->update(sprite_name); + } else { + set_init(ent, {sprite_name}); + } + } + void UI::show_text(const string& region, const wstring& content) { auto ent = entity(region); @@ -201,29 +233,25 @@ namespace guecs { } } - void UI::click_on(const string& name, bool required) { - auto ent = entity(name); - - if(required) { - assert(has(ent) && - "click_on required '{}' to exist but it doesn't"); - } - - click_on(ent); + void UI::click_on(const std::string& name, Modifiers mods) { + click_on(entity(name), mods); } - void UI::click_on(Entity ent) { - if(auto clicked = get_if(ent)) { - if(auto action_data = get_if(ent)) { - clicked->action(ent, action_data->data); - } else { - clicked->action(ent, {}); - } - } + void UI::click_on(Entity gui_id, Modifiers mods) { + if(auto to_click = get_if(gui_id)) { + to_click->action(mods); + }; } wstring to_wstring(const string& str) { - std::wstring_convert> $converter; - return $converter.from_bytes(str); + std::wstring result = L""; + + try { + utf8::utf8to32(str.begin(), str.end(), std::back_inserter(result)); + } catch(const utf8::exception& e) { + std::cerr << e.what() << std::endl; + } + + return result; } } diff --git a/tests/guecs.cpp b/tests/guecs.cpp index 33d5765..c4d3fb9 100644 --- a/tests/guecs.cpp +++ b/tests/guecs.cpp @@ -1,10 +1,20 @@ -#include #include #include "guecs/ui.hpp" #include +#include using namespace guecs; +using namespace fuc2; -TEST_CASE("prototype one gui", "[ecs-gui]") { - // accidentally gutted, will recover later +namespace guecs_tests { + void test_prototype() { + // accidentally gutted, will recover later + } + + fuc2::Set TESTS{ + .name="guecs", + .tests={ + TEST(test_prototype), + } + }; } diff --git a/tests/lel.cpp b/tests/lel.cpp index 244e751..273d988 100644 --- a/tests/lel.cpp +++ b/tests/lel.cpp @@ -1,61 +1,72 @@ #include "guecs/lel.hpp" -#include #include #include +#include -TEST_CASE("test basic ops", "[lel]") { - lel::Parser parser(0, 0, 500, 500); +using namespace fuc2; - bool good = parser.parse( - "[ label_1 | label3 | test1]" - "[ *(300,300)text1 | %(150)people | ^test2]" - "[ >label2 | _ | .test3]" - "[ =message | buttons | test4]"); +namespace lel_tests { + void test_basic_ops() { + lel::Parser parser(0, 0, 500, 500); - REQUIRE(good); + bool good = parser.parse( + "[ label_1 | label3 | test1]" + "[ *(300,300)text1 | %(150)people | ^test2]" + "[ >label2 | _ | .test3]" + "[ =message | buttons | test4]"); - for(size_t rowcount = 0; rowcount < parser.grid.size(); rowcount++) { - auto& row = parser.grid[rowcount]; + CHECK(good); - for(size_t colcount = 0; colcount < row.size(); colcount++) { - auto &name = row[colcount]; - if(name == "_") { - REQUIRE(!parser.cells.contains(name)); - } else if(name == "_MAIN") { - // it's the main cell which covers the whole area - auto &cell = parser.cells.at(name); - REQUIRE(cell.row == 0); - REQUIRE(cell.col == 0); - REQUIRE(cell.x == parser.grid_x); - REQUIRE(cell.y == parser.grid_y); - REQUIRE(cell.w == parser.grid_w); - REQUIRE(cell.h == parser.grid_h); - } else { - auto &cell = parser.cells.at(name); - REQUIRE(cell.row == int(rowcount)); - REQUIRE(cell.col == int(colcount)); + for(size_t rowcount = 0; rowcount < parser.grid.size(); rowcount++) { + auto& row = parser.grid[rowcount]; + + for(size_t colcount = 0; colcount < row.size(); colcount++) { + auto &name = row[colcount]; + if(name == "_") { + CHECK(!parser.cells.contains(name)); + } else if(name == "_MAIN") { + // it's the main cell which covers the whole area + auto &cell = parser.cells.at(name); + CHECK(cell.row == 0); + CHECK(cell.col == 0); + CHECK(cell.x == parser.grid_x); + CHECK(cell.y == parser.grid_y); + CHECK(cell.w == parser.grid_w); + CHECK(cell.h == parser.grid_h); + } else { + auto &cell = parser.cells.at(name); + CHECK(cell.row == int(rowcount)); + CHECK(cell.col == int(colcount)); + } } } + + CHECK(parser.cells.size() == 11); + CHECK(parser.cells.at("label2").right == true); + CHECK(parser.cells.at("text1").expand == true); + CHECK(parser.cells.at("text1").w == 300); + CHECK(parser.cells.at("text1").h == 300); + CHECK(parser.cells.at("people").expand == false); + CHECK(parser.cells.at("message").expand == false); + CHECK(parser.cells.at("message").center == true); + + for(auto& [name, cell] : parser.cells) { + CHECK(cell.w > 0); + CHECK(cell.h > 0); + } + + auto hit = parser.hit(10, 10); + CHECK(*hit == "label_1"); + + auto nohit = parser.hit(1000, 1000); + CHECK(!nohit); + CHECK(nohit == std::nullopt); } - REQUIRE(parser.cells.size() == 11); - REQUIRE(parser.cells.at("label2").right == true); - REQUIRE(parser.cells.at("text1").expand == true); - REQUIRE(parser.cells.at("text1").w == 300); - REQUIRE(parser.cells.at("text1").h == 300); - REQUIRE(parser.cells.at("people").expand == false); - REQUIRE(parser.cells.at("message").expand == false); - REQUIRE(parser.cells.at("message").center == true); - - for(auto& [name, cell] : parser.cells) { - REQUIRE(cell.w > 0); - REQUIRE(cell.h > 0); - } - - auto hit = parser.hit(10, 10); - REQUIRE(*hit == "label_1"); - - auto nohit = parser.hit(1000, 1000); - REQUIRE(!nohit); - REQUIRE(nohit == std::nullopt); + fuc2::Set TESTS{ + .name="lel", + .tests={ + TEST(test_basic_ops), + } + }; } diff --git a/tests/main.cpp b/tests/main.cpp new file mode 100644 index 0000000..1207005 --- /dev/null +++ b/tests/main.cpp @@ -0,0 +1,21 @@ +#include + +using namespace fuc2; + +TEST_SET(lel_tests); +TEST_SET(guecs_tests); +TEST_SET(shader_tests); +TEST_SET(sound_tests); +TEST_SET(texture_tests); + +int main(int argc, char* argv[]) { + std::vector all_tests{ + lel_tests::TESTS, + guecs_tests::TESTS, + shader_tests::TESTS, + sound_tests::TESTS, + texture_tests::TESTS, + }; + + run_tests(all_tests, argc, argv); +} diff --git a/tests/shaders.cpp b/tests/shaders.cpp index 2b39e1f..e2560d0 100644 --- a/tests/shaders.cpp +++ b/tests/shaders.cpp @@ -1,26 +1,36 @@ -#include #include #include #include "guecs/sfml/shaders.hpp" +#include +using namespace fuc2; using namespace fmt; -TEST_CASE("shader loading/init works", "[shaders]") { - shaders::init(); - int version = shaders::version(); +namespace shader_tests { + void test_shader_loading() { + shaders::init(); + int version = shaders::version(); - std::shared_ptr ui_shader = shaders::get("ui_shader"); - auto other_test = shaders::get("ui_shader"); + std::shared_ptr ui_shader = shaders::get("ui_shader"); + auto other_test = shaders::get("ui_shader"); - REQUIRE(ui_shader != nullptr); - REQUIRE(ui_shader == other_test); - REQUIRE(shaders::updated(version) == false); + CHECK(ui_shader != nullptr); + CHECK(ui_shader == other_test); + CHECK(shaders::updated(version) == false); - int new_version = shaders::reload(); - REQUIRE(version != shaders::version()); - REQUIRE(version != new_version); - REQUIRE(shaders::version() == new_version); - REQUIRE(shaders::updated(version) == true); - version = new_version; + int new_version = shaders::reload(); + CHECK(version != shaders::version()); + CHECK(version != new_version); + CHECK(shaders::version() == new_version); + CHECK(shaders::updated(version) == true); + version = new_version; + } + + fuc2::Set TESTS{ + .name="shader", + .tests={ + TEST(test_shader_loading), + } + }; } diff --git a/tests/sound.cpp b/tests/sound.cpp index 32dab05..c6d0514 100644 --- a/tests/sound.cpp +++ b/tests/sound.cpp @@ -1,14 +1,23 @@ -#include #include #include #include "guecs/sfml/sound.hpp" +#include -using namespace fmt; +using namespace fuc2; -TEST_CASE("test sound manager", "[sound]") { - sound::init(); +namespace sound_tests { + void test_sound_manager() { + sound::init(); - sound::play("blank"); + sound::play("blank"); - sound::play_at("blank", 0.1, 0.1, 0.1); + sound::play_at("blank", 0.1, 0.1, 0.1); + } + + fuc2::Set TESTS{ + .name="sound", + .tests={ + TEST(test_sound_manager), + } + }; } diff --git a/tests/textures.cpp b/tests/textures.cpp index 68ccee0..65da69d 100644 --- a/tests/textures.cpp +++ b/tests/textures.cpp @@ -1,15 +1,24 @@ -#include #include #include #include "guecs/sfml/textures.hpp" +#include -using namespace fmt; +using namespace fuc2; -TEST_CASE("test texture management", "[textures]") { - textures::init(); - auto spider = textures::get("textures_test"); - REQUIRE(spider.sprite != nullptr); - REQUIRE(spider.texture != nullptr); +namespace texture_tests { + void test_texture_management() { + textures::init(); + auto spider = textures::get("textures_test"); + CHECK(spider.sprite != nullptr); + CHECK(spider.texture != nullptr); - auto image = textures::load_image("assets/textures_test.png"); + auto image = textures::load_image("assets/textures_test.png"); + } + + fuc2::Set TESTS{ + .name="texture", + .tests={ + TEST(test_texture_management), + } + }; } diff --git a/wraps/catch2.wrap b/wraps/catch2.wrap deleted file mode 100644 index f9bf436..0000000 --- a/wraps/catch2.wrap +++ /dev/null @@ -1,11 +0,0 @@ -[wrap-file] -directory = Catch2-3.7.1 -source_url = https://github.com/catchorg/Catch2/archive/v3.7.1.tar.gz -source_filename = Catch2-3.7.1.tar.gz -source_hash = c991b247a1a0d7bb9c39aa35faf0fe9e19764213f28ffba3109388e62ee0269c -source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/catch2_3.7.1-1/Catch2-3.7.1.tar.gz -wrapdb_version = 3.7.1-1 - -[provide] -catch2 = catch2_dep -catch2-with-main = catch2_with_main_dep diff --git a/wraps/fuc2.wrap b/wraps/fuc2.wrap new file mode 100644 index 0000000..50fe0df --- /dev/null +++ b/wraps/fuc2.wrap @@ -0,0 +1,9 @@ +[wrap-git] +directory=fuc2-0.1.0 +url=https://lcthw.dev/cpp/fuc2.git +revision=HEAD +depth=1 +method=meson + +[provide] +fuc2 = fuc2_dep diff --git a/wraps/utf8cpp.wrap b/wraps/utf8cpp.wrap new file mode 100644 index 0000000..c1ae900 --- /dev/null +++ b/wraps/utf8cpp.wrap @@ -0,0 +1,9 @@ +[wrap-git] +directory=utf8cpp-4.0.9 +url=https://github.com/nemtrif/utfcpp.git +revision=v4.0.9 +depth=1 +method=cmake + +[provide] +utf8cpp = utf8cpp_dep