diff --git a/color.hpp b/color.hpp index 4443c95..4026ba5 100644 --- a/color.hpp +++ b/color.hpp @@ -1,4 +1,5 @@ #pragma once +#include namespace ColorValue { const sf::Color BLACK{0, 0, 0}; diff --git a/combat_ui.cpp b/combat_ui.cpp index 721fd9f..d795aea 100644 --- a/combat_ui.cpp +++ b/combat_ui.cpp @@ -4,62 +4,30 @@ namespace gui { CombatUI::CombatUI(GameLevel level) : - $layout(RAY_VIEW_X, RAY_VIEW_HEIGHT, - RAY_VIEW_WIDTH, SCREEN_HEIGHT - RAY_VIEW_HEIGHT), - $level(level), - $font{FONT_FILE_NAME} + $level(level) { - bool good = $layout.parse($grid); - dbc::check(good, "failed to parse combat layout"); + $gui.position(RAY_VIEW_X, RAY_VIEW_HEIGHT, RAY_VIEW_WIDTH, SCREEN_HEIGHT - RAY_VIEW_HEIGHT); + $gui.layout( + "[*%(100,150)button_attack1 | *%(100,150)button_attack2 | *%(100,150)button_attack3 | *%(100,150)button_heal]" + "[ >.%(100,50)label_hp | *%.(200,50)bar_hp | _ ]"); render(); } void CombatUI::render() { - $background.setPosition({float($layout.grid_x), float($layout.grid_y)}); - $background.setSize({float($layout.grid_w), float($layout.grid_h)}); - $background.setFillColor({0, 0, 0}); - - for(auto& [name, cell] : $layout.cells) { - if(name == "bar_hp") { - $meters.try_emplace(name, cell); - } else if(name == "label_hp") { - gui::Label label(cell, "hp:", $font); - $labels.emplace_back(cell, "hp:", $font); - } else if(name.starts_with("button_")) { - $buttons.try_emplace(name, cell, name, $font); - } - } - } - - void CombatUI::draw(sf::RenderWindow& window) { - window.draw($background); - auto &player = $level.world->get_the(); - auto &combat = $level.world->get(player.entity); - - float hp_now = float(combat.hp) / float(combat.max_hp); - auto& bar_hp = $meters.at("bar_hp"); - bar_hp.set_percent(hp_now); - - for(auto& [name, button] : $buttons) { - button.draw(window); - } - - for(auto& [name, meter] : $meters) { - meter.draw(window); - } - - for(auto& label : $labels) { - label.draw(window); + auto& world = $gui.world(); + + for(auto& [name, cell] : $gui.cells()) { + auto button = $gui.entity(name); + world.set(button, cell); + world.set(button, {}); + world.set(button, {100}); + world.set(button, {name}); } + $gui.init(); } - void CombatUI::click(int x, int y) { - if(auto name = $layout.hit(x, y)) { - if((*name).starts_with("button_")) { - auto& button = $buttons.at(*name); - button.shape.setFillColor({100, 0, 0}); - } - } + void CombatUI::draw(sf::RenderWindow& window) { + $gui.render(window); } } diff --git a/combat_ui.hpp b/combat_ui.hpp index 961d809..59b70af 100644 --- a/combat_ui.hpp +++ b/combat_ui.hpp @@ -2,25 +2,15 @@ #include "panel.hpp" #include "levelmanager.hpp" #include -#include #include -#include -#include "lel.hpp" -#include "gui_gadgets.hpp" +#include "ecs_gui.hpp" namespace gui { class CombatUI { public: - std::string $grid = - "[*%(100,150)button_attack1 | *%(100,150)button_attack2 | *%(100,150)button_attack3 | *%(100,150)button_heal]" - "[ >.%(100,50)label_hp | *%.(200,50)bar_hp | _ ]"; - lel::Parser $layout; + GUECS $gui; GameLevel $level; - sf::Font $font; - sf::RectangleShape $background; - std::unordered_map $buttons; - std::vector $labels; - std::unordered_map $meters; + CombatUI(GameLevel level); void render(); diff --git a/ecs_gui.cpp b/ecs_gui.cpp new file mode 100644 index 0000000..5986cd5 --- /dev/null +++ b/ecs_gui.cpp @@ -0,0 +1,65 @@ +#include "ecs_gui.hpp" +#include "constants.hpp" + +GUECS::GUECS() { + $font = make_shared(FONT_FILE_NAME); +} + +void GUECS::position(int x, int y, int width, int height) { + $parser.position(x, y, width, height); +} + +void GUECS::layout(std::string grid) { + $grid = grid; + $parser.parse($grid); +} + +DinkyECS::Entity GUECS::entity(std::string name) { + auto entity = $world.entity(); + $world.set(entity, {name}); + return entity; +} + +void GUECS::init() { + $world.query([](const auto &, auto& cell, auto& rect) { + rect.init(cell); + }); + + $world.query([this](const auto &, auto& cell, auto& text) { + text.init(cell, $font); + }); +} + +void GUECS::render(sf::RenderWindow& window) { + $world.query([&](const auto &ent, const auto& cell, const auto &meter) { + if($world.has(ent)) { + float level = meter.percent * float(cell.w); + auto& target = $world.get(ent); + // ZED: this 6 is a border width, make it a thing + target.shape->setSize({std::max(level, 0.0f), float(cell.h - 6)}); + } + }); + + $world.query([&](const auto &, const auto& rect) { + window.draw(*rect.shape); + }); + + $world.query([&](const auto &, const auto& text) { + window.draw(*text.text); + }); +} + +void GUECS::mouse(sf::RenderWindow &window) { + if(sf::Mouse::isButtonPressed(sf::Mouse::Button::Left)) { + sf::Vector2f pos = window.mapPixelToCoords(sf::Mouse::getPosition(window)); + $world.query([&](const auto &ent, auto& cell, auto &clicked) { + if((pos.x >= cell.x && pos.x <= cell.x + cell.w) && + (pos.y >= cell.y && pos.y <= cell.y + cell.h)) + { + auto& cn = $world.get(ent); + fmt::println("clicked on entity {} with name {} and event {}", + ent, cn.name, clicked.event); + } + }); + } +} diff --git a/ecs_gui.hpp b/ecs_gui.hpp new file mode 100644 index 0000000..daf572c --- /dev/null +++ b/ecs_gui.hpp @@ -0,0 +1,77 @@ +#pragma once +#include "color.hpp" +#include "dinkyecs.hpp" +#include "lel.hpp" +#include +#include +#include + +using std::shared_ptr, std::make_shared; + +struct Textual { + std::string label; + shared_ptr font = nullptr; + shared_ptr text = nullptr; + + void init(lel::Cell &cell, shared_ptr font_ptr) { + font = font_ptr; + text = make_shared(*font, label); + + 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 + text->setPosition({float(text_cell.x), float(text_cell.y) - text_cell.h / 2}); + } +}; + +struct Clickable { + int event = 0; +}; + +struct Rectangle { + shared_ptr shape = nullptr; + + void init(lel::Cell& cell) { + sf::Vector2f size{(float)cell.w, (float)cell.h}; + if(shape == nullptr) shape = make_shared(size); + shape->setPosition({float(cell.x + 3), float(cell.y + 3)}); + shape->setSize({float(cell.w - 6), float(cell.h - 6)}); + shape->setFillColor(ColorValue::DARK_MID); + shape->setOutlineColor(ColorValue::MID); + shape->setOutlineThickness(1); + } +}; + +struct Meter { + float percent = 100.0; +}; + +struct CellName { + std::string name; +}; + +class GUECS { + public: + DinkyECS::World $world; + shared_ptr $font = nullptr; + lel::Parser $parser; + std::string $grid = ""; + + GUECS(); + + void position(int x, int y, int width, int height); + void layout(std::string grid); + DinkyECS::Entity entity(std::string name); + + inline lel::CellMap& cells() { + return $parser.cells; + } + + inline DinkyECS::World& world() { + return $world; + } + + void init(); + void render(sf::RenderWindow& window); + void mouse(sf::RenderWindow &window); +}; diff --git a/gui.cpp b/gui.cpp index e47014e..6e8ceea 100644 --- a/gui.cpp +++ b/gui.cpp @@ -361,10 +361,8 @@ namespace gui { } void FSM::mouse() { - if(sf::Mouse::isButtonPressed(sf::Mouse::Button::Left)) { - sf::Vector2f pos = $window.mapPixelToCoords(sf::Mouse::getPosition($window)); - $combat_view.click(pos.x, pos.y); - } + // need to sort out how this will be easier with multiple UIs + $combat_view.$gui.mouse($window); } void FSM::generate_map() { @@ -411,6 +409,8 @@ namespace gui { } else { $status_view.log("You MISSED the enemy."); } + + // this is where we tell the combat ui about the damage } break; case eGUI::COMBAT_START: diff --git a/gui_gadgets.hpp b/gui_gadgets.hpp deleted file mode 100644 index 39e28be..0000000 --- a/gui_gadgets.hpp +++ /dev/null @@ -1,75 +0,0 @@ -#pragma once -#include -#include -#include -#include "lel.hpp" - -namespace gui { - struct Label { - sf::Font &font; - sf::Text text; - lel::Cell& cell; - - Label(lel::Cell& cell, std::string label_text, sf::Font &font) : - font(font), - text(font, label_text), - cell(cell) - { - 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 - text.setPosition({float(text_cell.x), float(text_cell.y) - text_cell.h / 2}); - } - - void draw(sf::RenderWindow& window) { - window.draw(text); - } - }; - - struct Button { - Label label; - lel::Cell& cell; - sf::RectangleShape shape; - - Button(lel::Cell& cell, std::string label_text, sf::Font &font) : - label(cell, label_text, font), - cell(cell) - { - shape.setPosition({float(cell.x + 3), float(cell.y + 3)}); - shape.setSize({float(cell.w - 6), float(cell.h - 6)}); - shape.setFillColor(ColorValue::DARK_MID); - shape.setOutlineColor(ColorValue::MID); - shape.setOutlineThickness(1); - } - - void draw(sf::RenderWindow& window) { - window.draw(shape); - label.draw(window); - } - }; - - struct Meter { - sf::RectangleShape shape; - lel::Cell& cell; - - Meter(lel::Cell& cell) : - cell(cell) - { - shape.setPosition({float(cell.x + 3), float(cell.y + 3)}); - shape.setSize({float(cell.w - 6), float(cell.h - 6)}); - shape.setFillColor(ColorValue::DARK_MID); - shape.setOutlineColor(ColorValue::MID); - shape.setOutlineThickness(1); - } - - void draw(sf::RenderWindow& window) { - window.draw(shape); - } - - void set_percent(float percent) { - float level = percent * float(cell.w); - // ZED: this 6 is a border to make it a thing - shape.setSize({std::max(level, 0.0f), float(cell.h - 6)}); - } - }; -} diff --git a/lel.cpp b/lel.cpp index 52cc236..d95061b 100644 --- a/lel.cpp +++ b/lel.cpp @@ -16,6 +16,15 @@ namespace lel { { } + Parser::Parser() : cur(0, 0) { } + + void Parser::position(int x, int y, int width, int height) { + grid_x = x; + grid_y = y; + grid_w = width; + grid_h = height; + } + void Parser::id(std::string name) { if(name != "_") { dbc::check(!cells.contains(name), diff --git a/lel.hpp b/lel.hpp index bbdb857..74ee59f 100644 --- a/lel.hpp +++ b/lel.hpp @@ -6,7 +6,6 @@ #include namespace lel { - using Row = std::vector; struct Cell { int x = 0; @@ -26,8 +25,12 @@ namespace lel { bool percent = false; Cell(int col, int row) : col(col), row(row) {} + Cell() {} }; + using Row = std::vector; + using CellMap = std::unordered_map; + struct Parser { int grid_x = 0; int grid_y = 0; @@ -35,9 +38,12 @@ namespace lel { int grid_h = 0; Cell cur; std::vector grid; - std::unordered_map cells; + CellMap cells; Parser(int x, int y, int width, int height); + Parser(); + + void position(int x, int y, int width, int height); void id(std::string name); void reset(); bool parse(std::string input); diff --git a/meson.build b/meson.build index 32a85de..f95aaca 100644 --- a/meson.build +++ b/meson.build @@ -8,7 +8,7 @@ project('raycaster', 'cpp', # use this for common options only for our executables cpp_args=[] # these are passed as override_defaults -exe_defaults = ['warning_level=2', 'werror=false'] +exe_defaults = ['warning_level=2', 'werror=true'] cc = meson.get_compiler('cpp') @@ -61,6 +61,7 @@ sources = [ 'config.cpp', 'dbc.cpp', 'devices.cpp', + 'ecs_gui.cpp', 'gui.cpp', 'inventory.cpp', 'lel.cpp', @@ -91,6 +92,7 @@ executable('runtests', sources + [ 'tests/base.cpp', 'tests/dbc.cpp', 'tests/dinkyecs.cpp', + 'tests/ecs_gui.cpp', 'tests/fsm.cpp', 'tests/inventory.cpp', 'tests/lel.cpp', diff --git a/tests/ecs_gui.cpp b/tests/ecs_gui.cpp new file mode 100644 index 0000000..5bdb877 --- /dev/null +++ b/tests/ecs_gui.cpp @@ -0,0 +1,28 @@ +#include +#include +#include "constants.hpp" +#include "ecs_gui.hpp" + + +TEST_CASE("prototype one gui", "[ecs-gui]") { + GUECS gui; + gui.position(0, 0, 1000, 500); + gui.layout("[test1|test2|test3][test4|_|test5]"); + + for(auto& [name, cell] : gui.cells()) { + auto& world = gui.world(); + auto button = gui.entity(name); + world.set(button, cell); + world.set(button, {}); + world.set(button, {}); + world.set(button, {name}); + } + + gui.init(); + + // at this point it's mostly ready but I'd need to render it to a window real quick + sf::RenderWindow window; + window.setSize({SCREEN_WIDTH, SCREEN_HEIGHT}); + gui.render(window); + window.display(); +} diff --git a/tests/lel.cpp b/tests/lel.cpp index 2bd3088..8898a23 100644 --- a/tests/lel.cpp +++ b/tests/lel.cpp @@ -2,10 +2,6 @@ #include #include #include -#include "ansi_parser.hpp" -#include -#include -#include TEST_CASE("test basic ops", "[lel]") { lel::Parser parser(0, 0, 500, 500);