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 c8f69ab..8f38517 100644 --- a/demos/calc.cpp +++ b/demos/calc.cpp @@ -141,12 +141,12 @@ struct CalculatorUI { $gui.layout( "[*%(400)stack |_|_|_]" "[*%(400)readout|_|_|_]" - "[push|pop|clear|eq]" - "[add |sub|mul |div]" - "[btn7|btn8|btn9]" - "[btn4|btn5|btn6]" - "[btn1|btn2|btn3]" - "[neg |btn0|del]"); + "[=push|=pop|=clear|=eq]" + "[=add |=sub|=mul |=div]" + "[=btn7|=btn8|=btn9]" + "[=btn4|=btn5|=btn6]" + "[=btn1|=btn2|=btn3]" + "[=neg |=btn0|=del]"); } void init() { @@ -161,16 +161,16 @@ struct CalculatorUI { $gui.set(id, {}); if(name == "readout") { - $gui.set(id, {L"", 40}); + $gui.set(id, {L"", 40}); } else if(name == "stack") { - $gui.set(id, {L"", 20}); + $gui.set(id, {L"", 20}); } else { $gui.set(id, {}); - $gui.set(id, { label }); + $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 bb6cda7..c5e68b6 100644 --- a/demos/clicker_game.cpp +++ b/demos/clicker_game.cpp @@ -73,7 +73,7 @@ struct ClickerUI { ClickerUI() { $gui.position(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT); $gui.layout( - "[_|*%(300,400)clicker|_|_|_]" + "[_|=*%(300,400)clicker|_|_|_]" "[_|_ |_|_|_]" "[_|_ |_|_|_]" "[_|_ |_|_|_]" @@ -91,11 +91,10 @@ struct ClickerUI { for(auto& [name, cell] : $gui.cells()) { auto id = $gui.entity(name); if(name != "clicker") { - $gui.set(id, {}); $gui.set(id, {}); $gui.set(id, { "clicker_treat_bone" }); $gui.set(id, { - [&](auto, auto) { handle_button(Event::A_BUTTON); } + [&](auto) { handle_button(Event::A_BUTTON); } }); } } @@ -104,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 @@ -127,23 +126,22 @@ 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) { using enum Event; switch(ev) { case CLICKER: { - auto& shaker = $gui.get($clicker); - auto& sprite = $gui.get($clicker); - shaker.play(sprite); - fmt::println("CLICKER LOVES YOU!"); - } break; + auto& shaker = $gui.get($clicker); + auto& sprite = $gui.get($clicker); + shaker.play(sprite); + fmt::println("CLICKER LOVES YOU!"); + } break; case A_BUTTON: - fmt::println("a button clicked"); - break; - + fmt::println("a button clicked"); + break; default: assert(false && "invalid event"); } 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 a323371..d681ebc 100644 --- a/include/guecs/sfml/components.hpp +++ b/include/guecs/sfml/components.hpp @@ -10,7 +10,7 @@ namespace guecs { using std::shared_ptr, std::wstring, std::string; - struct Textual { + struct Text { std::wstring content; unsigned int size = THEME.TEXT_SIZE; sf::Color color = THEME.TEXT_COLOR; @@ -21,43 +21,38 @@ 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); - }; - - struct Label : public Textual { - template - Label(Args... args) : Textual(args...) - { - centered = true; - size = THEME.LABEL_SIZE; - } - - Label() { - centered = true; - }; + void render(sf::RenderTarget& window, sf::Shader *shader_ptr); }; struct Sprite { string name; int padding = THEME.PADDING; + 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 { - string name; - int padding = THEME.PADDING; - std::shared_ptr sprite = nullptr; + struct Icon : public Sprite { + template + Icon(Args... args) : Sprite(args...) + { + stretch = false; + is_icon = true; + } - void init(lel::Cell &cell); - void update(const string& new_name); - void render(sf::RenderWindow& window, sf::Shader *shader_ptr); + 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; @@ -65,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 { @@ -101,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; @@ -109,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), @@ -121,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 830a626..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.4.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/lel.cpp b/src/guecs/lel.cpp index b363293..c8efccc 100644 --- a/src/guecs/lel.cpp +++ b/src/guecs/lel.cpp @@ -50,6 +50,7 @@ namespace lel { int cell_height = grid_h / rows; for(auto& row : grid) { + // BUG: see issue #16 size_t columns = row.size(); int cell_width = grid_w / columns; assert(cell_width > 0 && "invalid cell width calc"); diff --git a/src/guecs/sfml/components.cpp b/src/guecs/sfml/components.cpp index 064cc3a..2144a96 100644 --- a/src/guecs/sfml/components.cpp +++ b/src/guecs/sfml/components.cpp @@ -7,13 +7,34 @@ namespace guecs { using std::make_shared; - void Textual::init(lel::Cell &cell, shared_ptr font_ptr) { + // 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); + } + + inline SpriteTexture load_texture(const string& name, bool is_icon) { + if(is_icon) { + return BACKEND->get_icon(name); + } else { + return BACKEND->get_sprite(name); + } + } + + void Text::init(lel::Cell &cell, shared_ptr font_ptr) { assert(font_ptr != nullptr && "you failed to initialize this WideText"); if(font == nullptr) font = font_ptr; if(text == nullptr) text = make_shared(*font, content, size); text->setFillColor(color); - if(centered) { + if(centered || cell.center) { auto bounds = text->getLocalBounds(); auto text_cell = lel::center(bounds.size.x, bounds.size.y, cell); // this stupid / 2 is because SFML renders from baseline rather than from the claimed bounding box @@ -25,11 +46,11 @@ namespace guecs { text->setCharacterSize(size); } - void Textual::render(sf::RenderWindow& window, sf::Shader *shader_ptr) { + void Text::render(sf::RenderTarget& window, sf::Shader *shader_ptr) { window.draw(*text, shader_ptr); } - void Textual::update(const wstring& new_content) { + void Text::update(const wstring& new_content) { content = new_content; text->setString(content); } @@ -38,85 +59,84 @@ 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}); } } void Sprite::init(lel::Cell &cell) { - auto sprite_texture = BACKEND->get_sprite(name); + if(cell.center) stretch = false; - sf::IntRect rect{{0,0},sprite_texture.frame_size}; - sprite = make_shared(*sprite_texture.texture, rect); + auto sprite_texture = load_texture(name, is_icon); + auto bounds = sprite_texture.frame_size; - sprite->setPosition({ - float(cell.x + padding), - float(cell.y + padding)}); + sf::IntRect rect{{0,0}, bounds}; + texture = sprite_texture.texture; + sprite = make_shared(*texture, rect); - auto bounds = sprite->getLocalBounds(); - - sprite->setScale({ - float(cell.w - padding * 2) / bounds.size.x, - float(cell.h - padding * 2) / bounds.size.y}); - } - - void Sprite::render(sf::RenderWindow& window, sf::Shader *shader_ptr) { - window.draw(*sprite, shader_ptr); - } - - void Icon::update(const string& new_name) { - if(new_name != name) { - name = new_name; - auto sprite_texture = BACKEND->get_icon(name); - sprite->setTexture(*sprite_texture.texture); - sprite->setTextureRect({{0,0},sprite_texture.frame_size}); + if(stretch) { + sprite->setScale({ + float(cell.w - padding * 2) / float(bounds.x), + float(cell.h - padding * 2) / float(bounds.y)}); + } else { + float box_width = float(cell.w - padding * 2); + float box_height = float(cell.h - padding * 2); + float scale = std::min(box_width / float(bounds.x), box_height / float(bounds.y)); + sprite->setScale({scale, scale}); } + + sfml_center_helper(sprite, cell, padding); } - void Icon::init(lel::Cell &cell) { - auto sprite_texture = BACKEND->get_icon(name); - - sf::IntRect rect{{0,0},sprite_texture.frame_size}; - fmt::println("ICON SIZE: {},{}; {},{}", - rect.position.x, rect.position.y, - rect.size.x, rect.size.y); - sprite = make_shared(*sprite_texture.texture, rect); - - sprite->setPosition({ float(cell.x + padding), float(cell.y + padding)}); - } - - void Icon::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); - shape->setPosition({float(cell.x + padding), float(cell.y + padding)}); + 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 b1824c6..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,18 +80,10 @@ 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) { - text.init(cell, $font); - }); - - query([this](auto, auto& cell, auto& text) { + query([this](auto, auto& cell, auto& text) { text.init(cell, $font); }); @@ -99,7 +96,7 @@ namespace guecs { }); } - 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)}); @@ -118,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); } @@ -132,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) { @@ -147,20 +156,15 @@ namespace guecs { icon.render(window, shader_ptr); }); - query