diff --git a/demos/calc.cpp b/demos/calc.cpp index d8b7aa0..7e5b369 100644 --- a/demos/calc.cpp +++ b/demos/calc.cpp @@ -4,17 +4,31 @@ #include "sfml/textures.hpp" #include "guecs.hpp" #include "constants.hpp" +#include constexpr const int WINDOW_WIDTH=300; constexpr const int WINDOW_HEIGHT=400; +using std::string, std::wstring; + +const std::unordered_map LABELS { + {"readout", L""}, {"clear", L"CLR"}, {"btn0", L"0"}, {"btn1", L"1"}, + {"btn2", L"2"}, {"btn3", L"3"}, {"btn4", L"4"}, + {"btn5", L"5"}, {"btn6", L"6"}, {"btn7", L"7"}, + {"btn8", L"8"}, {"btn9", L"9"}, {"mult", L"*"}, + {"minus", L"-"}, {"plus", L"+"}, {"neg", L"!"}, + {"dot", L"."}, {"eq", L"="} + }; + struct Calculator { guecs::UI $gui; + wstring $input; + double $value = 0.0; Calculator() { $gui.position(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT); $gui.layout( - "[readout]" + "[*%(400)readout|_|_|_|clear]" "[btn7|btn8|btn9|mult]" "[btn4|btn5|btn6|minus]" "[btn1|btn2|btn3|plus]" @@ -26,12 +40,19 @@ struct Calculator { for(auto& [name, cell] : $gui.cells()) { auto id = $gui.entity(name); + auto& label = LABELS.at(name); + $gui.set(id, {}); $gui.set(id, {}); - $gui.set(id, {guecs::to_wstring(name)}); - $gui.set(id, { - [=](auto, auto) { fmt::println("clicked {}", name); } - }); + + if(name == "readout") { + $gui.set(id, {L"", 40}); + } else { + $gui.set(id, { label }); + $gui.set(id, { + [&, name](auto, auto) { handle_button(label[0]); } + }); + } } $gui.init(); @@ -45,9 +66,57 @@ struct Calculator { void mouse(float x, float y, bool hover) { $gui.mouse(x, y, hover); } + + void handle_button(wchar_t op) { + switch(op) { + case L'0': + case L'1': + case L'2': + case L'3': + case L'4': + case L'5': + case L'6': + case L'7': + case L'8': + case L'9': + case L'.': + if($input.size() <= 10) { + $input += op; + } + break; + case L'*': + $value = $value * std::stof($input); + $input = L""; + break; + case L'-': + $value = $value - std::stof($input); + $input = L""; + break; + case L'+': + $value = $value + std::stof($input); + $input = L""; + break; + case L'!': + $value = $value * -1.0; + $input = L""; + break; + case L'=': + $input = fmt::format(L"{}", $value); + $value = 0.0; + break; + case L'C': + $input = L""; + break; + } + + auto readout = $gui.entity("readout"); + auto& label = $gui.get(readout); + label.update($input); + } }; + int main() { sound::init(); shaders::init(); diff --git a/sfml/components.cpp b/sfml/components.cpp new file mode 100644 index 0000000..3a72919 --- /dev/null +++ b/sfml/components.cpp @@ -0,0 +1,136 @@ +#include "guecs.hpp" +#include "sfml/shaders.hpp" +#include "sfml/sound.hpp" +#include "sfml/textures.hpp" + +namespace guecs { + using std::make_shared; + + void Textual::init(lel::Cell &cell, shared_ptr font_ptr) { + dbc::check(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) { + 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}); + } else { + text->setPosition({float(cell.x + padding * 2), float(cell.y + padding * 2)}); + } + + text->setCharacterSize(size); + } + + void Textual::update(const wstring& new_content) { + content = new_content; + text->setString(content); + } + + void Sprite::update(const string& new_name) { + if(new_name != name) { + name = new_name; + auto sprite_texture = textures::get(name); + sprite->setTexture(*sprite_texture.texture); + sprite->setTextureRect(sprite_texture.sprite->getTextureRect()); + } + } + + void Sprite::init(lel::Cell &cell) { + auto sprite_texture = textures::get(name); + + sprite = make_shared( + *sprite_texture.texture, + sprite_texture.sprite->getTextureRect()); + + sprite->setPosition({ + float(cell.x + padding), + float(cell.y + padding)}); + + auto bounds = sprite->getLocalBounds(); + + sprite->setScale({ + float(cell.w - padding * 2) / bounds.size.x, + float(cell.h - padding * 2) / bounds.size.y}); + } + + 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)}); + shape->setFillColor(color); + shape->setOutlineColor(border_color); + shape->setOutlineThickness(border_px); + } + + + void Meter::init(lel::Cell& cell) { + bar.init(cell); + } + + void Meter::render(lel::Cell& cell) { + 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)}); + } + + void Sound::play(bool hover) { + if(!hover) { + sound::play(on_click); + } + } + + void Sound::stop(bool hover) { + if(!hover) { + sound::stop(on_click); + } + } + + void Background::init() { + sf::Vector2f size{float(w), float(h)}; + if(shape == nullptr) shape = make_shared(size); + shape->setPosition({float(x), float(y)}); + shape->setFillColor(color); + } + + void Effect::init(lel::Cell &cell) { + $shader_version = shaders::version(); + $shader = shaders::get(name); + $shader->setUniform("u_resolution", sf::Vector2f({float(cell.w), float(cell.h)})); + $clock = std::make_shared(); + } + + void Effect::step() { + sf::Time cur_time = $clock->getElapsedTime(); + float u_time = cur_time.asSeconds(); + + if(u_time < $u_time_end) { + $shader->setUniform("u_duration", duration); + $shader->setUniform("u_time_end", $u_time_end); + $shader->setUniform("u_time", u_time); + } else { + $active = false; + } + } + + void Effect::run() { + $active = true; + sf::Time u_time = $clock->getElapsedTime(); + $u_time_end = u_time.asSeconds() + duration; + } + + shared_ptr Effect::checkout_ptr() { + if(shaders::updated($shader_version)) { + $shader = shaders::get(name); + $shader_version = shaders::version(); + } + + return $shader; + } + + void Effect::stop() { + $active = false; + } +} diff --git a/sfml/components.hpp b/sfml/components.hpp new file mode 100644 index 0000000..fe890f8 --- /dev/null +++ b/sfml/components.hpp @@ -0,0 +1,120 @@ +#pragma once +#include "dbc.hpp" +#include "sfml/color.hpp" +#include "lel.hpp" +#include +#include +#include +#include +#include + +namespace guecs { + using std::shared_ptr, std::wstring, std::string; + + 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"; + + 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 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 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(); + }; +}