#pragma once #include "dbc.hpp" #include "color.hpp" #include "lel.hpp" #include #include #include #include #include "events.hpp" #include #include #include #include namespace guecs { constexpr const int PADDING = 3; constexpr const int BORDER_PX = 1; constexpr const int TEXT_SIZE = 30; constexpr const int LABEL_SIZE = 20; constexpr const sf::Color FILL_COLOR = ColorValue::DARK_MID; constexpr const sf::Color TEXT_COLOR = ColorValue::LIGHT_LIGHT; constexpr const sf::Color BG_COLOR = ColorValue::MID; constexpr const sf::Color BORDER_COLOR = ColorValue::MID; constexpr const char *FONT_FILE_NAME="assets/text.otf"; using std::shared_ptr, std::wstring, std::string; using Entity = unsigned long; using EntityMap = std::unordered_map; template struct ComponentStorage { std::vector data; std::queue free_indices; }; struct Textual { std::wstring content; unsigned int size = TEXT_SIZE; sf::Color color = TEXT_COLOR; int padding = PADDING; bool centered = false; shared_ptr font = nullptr; shared_ptr text = nullptr; void init(lel::Cell &cell, shared_ptr font_ptr); void update(const std::wstring& new_content); }; struct Label : public Textual { template Label(Args... args) : Textual(args...) { centered = true; size = LABEL_SIZE; } Label() { centered = true; }; }; 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 Sprite { string name; int padding = PADDING; std::shared_ptr sprite = nullptr; void init(lel::Cell &cell); void update(const string& new_name); }; struct Rectangle { int padding = PADDING; sf::Color color = FILL_COLOR; sf::Color border_color = BORDER_COLOR; int border_px = BORDER_PX; shared_ptr shape = nullptr; void init(lel::Cell& cell); }; struct Meter { float percent = 1.0f; sf::Color color = ColorValue::BLACK; Rectangle bar; void init(lel::Cell& cell); void render(lel::Cell& cell); }; struct ActionData { std::any data; }; struct CellName { string name; }; struct Effect { float duration = 0.1f; string name{"ui_shader"}; float $u_time_end = 0.0; bool $active = false; std::shared_ptr $clock = nullptr; std::shared_ptr $shader = nullptr; int $shader_version = 0; void init(lel::Cell &cell); void run(); void stop(); void step(); shared_ptr checkout_ptr(); }; struct Sound { string on_click{"ui_click"}; void play(bool hover); void stop(bool hover); }; struct Background { float x = 0.0f; float y = 0.0f; float w = 0.0f; float h = 0.0f; sf::Color color = BG_COLOR; shared_ptr shape = nullptr; Background(lel::Parser& parser, sf::Color bg_color=BG_COLOR) : x(parser.grid_x), y(parser.grid_y), w(parser.grid_w), h(parser.grid_h), color(bg_color) {} Background() {} void init(); }; class UI { public: Entity MAIN = 0; unsigned long entity_count = 1; std::unordered_map $components; std::unordered_map $component_storages; std::unordered_map $name_ents; shared_ptr $font = nullptr; lel::Parser $parser; string $grid = ""; UI(); void position(int x, int y, int width, int height); sf::Vector2f get_position(); sf::Vector2f get_size(); void layout(const string& grid); Entity init_entity(const string& name); Entity entity(const string& name); Entity entity(const string& name, int id); inline lel::CellMap& cells() { return $parser.cells; } 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); Entity entity() { return ++entity_count; } template size_t make_component() { auto &storage = component_storage_for(); size_t index; if(!storage.free_indices.empty()) { index = storage.free_indices.front(); storage.free_indices.pop(); } else { storage.data.emplace_back(); index = storage.data.size() - 1; } return index; } template ComponentStorage &component_storage_for() { auto type_index = std::type_index(typeid(Comp)); $component_storages.try_emplace(type_index, ComponentStorage{}); return std::any_cast &>( $component_storages.at(type_index)); } template EntityMap &entity_map_for() { return $components[std::type_index(typeid(Comp))]; } template void set(Entity ent, Comp val) { EntityMap &map = entity_map_for(); if(has(ent)) { get(ent) = val; return; } map.insert_or_assign(ent, make_component()); get(ent) = val; } template Comp& get(Entity ent) { EntityMap &map = entity_map_for(); auto &storage = component_storage_for(); auto index = map.at(ent); return storage.data[index]; } template Comp* get_if(Entity entity) { EntityMap &map = entity_map_for(); auto &storage = component_storage_for(); if(map.contains(entity)) { auto index = map.at(entity); return &storage.data[index]; } else { return nullptr; } } template bool has(Entity ent) { EntityMap &map = entity_map_for(); return map.contains(ent); } template void remove(Entity ent) { EntityMap &map = entity_map_for(); if(map.contains(ent)) { size_t index = map.at(ent); component_storage_for().free_indices.push(index); } map.erase(ent); } template void query(std::function cb) { EntityMap &map = entity_map_for(); for(auto &[entity, index] : map) { cb(entity, get(entity)); } } template void query(std::function cb) { EntityMap &map_a = entity_map_for(); EntityMap &map_b = entity_map_for(); for(auto &[entity, index_a] : map_a) { if(map_b.contains(entity)) { cb(entity, get(entity), get(entity)); } } } template void set_init(Entity ent, Comp val) { dbc::check(has(ent),"WRONG! slot is missing its cell?!"); auto& cell = get(ent); val.init(cell); set(ent, val); } template void do_if(Entity ent, std::function cb) { if(has(ent)) { cb(get(ent)); } } lel::Cell& cell_for(Entity ent) { return get(ent); } lel::Cell& cell_for(const string& name) { Entity ent = entity(name); return get(ent); } // BUG: close could just be remove with overload template void close(string region) { auto ent = entity(region); if(has(ent)) { remove(ent); } } template void render_helper(sf::RenderWindow& window, Entity ent, bool is_shape, T& target) { sf::Shader *shader_ptr = nullptr; if(auto shader = get_if(ent)) { if(shader->$active && !is_shape) { auto ptr = shader->checkout_ptr(); ptr->setUniform("is_shape", is_shape); // NOTE: this is needed because SFML doesn't handle shared_ptr shader_ptr = ptr.get(); } } window.draw(*target, shader_ptr); } void show_sprite(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); }; }