#include "sfml/components.hpp" #include "sfml/sound.hpp" #include "sfml/shaders.hpp" #include "sfml/textures.hpp" #include "guecs.hpp" #include "constants.hpp" #include #define FSM_DEBUG 1 #include "fsm.hpp" 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"±"}, {"div", L"/"}, {"eq", L"="} }; enum class Event { NUMBER, ADD, SUB, MUL, DIV, CLR, NOT, EQ, }; enum class State { START, CALCULATING, CLEARED, DISPLAYED }; struct Calculator : DeadSimpleFSM { wstring input; double value = 0.0; void event(Event ev, wchar_t op) { switch($state) { FSM_STATE(State, START, ev, op); FSM_STATE(State, CALCULATING, ev, op); FSM_STATE(State, CLEARED, ev, op); FSM_STATE(State, DISPLAYED, ev, op); } } void START(Event ev, wchar_t op) { if(ev == Event::NUMBER) { input += op; state(State::CALCULATING); } } void CALCULATING(Event ev, wchar_t op) { using enum Event; switch(ev) { case NUMBER: input += op; break; case ADD: value += std::stof(input); input = L""; break; case SUB: value -= std::stof(input); input = L""; break; case MUL: value *= std::stof(input); input = L""; break; case DIV: value /= std::stof(input); input = L""; break; case NOT: value = std::stof(input) * -1; input = L""; break; case EQ: value = std::stof(input); break; case CLR: input = L""; state(State::CLEARED); break; } } void CLEARED(Event ev, wchar_t op) { if(ev == Event::NUMBER) { input += op; state(State::CALCULATING); } } void DISPLAYED(Event ev, wchar_t op) { fmt::println(L"ev={}, op={}", (int)ev, op); state(State::CALCULATING); } }; struct CalculatorUI { guecs::UI $gui; Calculator $fsm; CalculatorUI() { $gui.position(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT); $gui.layout( "[*%(400)readout|_|_|_|clear]" "[btn7|btn8|btn9|mult]" "[btn4|btn5|btn6|minus]" "[btn1|btn2|btn3|plus]" "[neg|btn0|eq|div]"); } void init() { $gui.set($gui.MAIN, {}); for(auto& [name, cell] : $gui.cells()) { auto id = $gui.entity(name); auto& label = LABELS.at(name); $gui.set(id, {}); $gui.set(id, {}); 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(); } void render(sf::RenderWindow& window) { $gui.render(window); // $gui.debug_layout(window); } void mouse(float x, float y, bool hover) { $gui.mouse(x, y, hover); } void handle_button(wchar_t op) { using enum Event; 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': $fsm.event(NUMBER, op); break; case L'*': $fsm.event(MUL, op); break; case L'-': $fsm.event(SUB, op); break; case L'+': $fsm.event(ADD, op); break; case L'/': $fsm.event(DIV, op); break; case L'±': $fsm.event(NOT, op); break; case L'=': $fsm.event(EQ, op); break; case L'C': $fsm.event(CLR, op); break; } auto readout = $gui.entity("readout"); auto& label = $gui.get(readout); label.update($fsm.input); } }; int main() { sound::init(); shaders::init(); textures::init(); sf::RenderWindow window(sf::VideoMode({WINDOW_WIDTH, WINDOW_HEIGHT}), "LEL-GUECS Calculator"); window.setFramerateLimit(FRAME_LIMIT); window.setVerticalSyncEnabled(VSYNC); CalculatorUI calc; calc.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); calc.mouse(pos.x, pos.y, false); } } } calc.render(window); window.display(); } }