Browse Source

buttons, button and text input merged into a global window

Kajetan Johannes Hammerle 2 years ago
parent
commit
8841e762ab
11 changed files with 460 additions and 510 deletions
  1. 17 16
      Main.cpp
  2. 1 1
      images/ImageReader.cpp
  3. 0 36
      input/Button.cpp
  4. 0 27
      input/Button.h
  5. 0 121
      input/Buttons.cpp
  6. 0 60
      input/Buttons.h
  7. 0 99
      input/TextInput.cpp
  8. 0 32
      input/TextInput.h
  9. 0 3
      meson.build
  10. 373 72
      rendering/Window.cpp
  11. 69 43
      rendering/Window.h

+ 17 - 16
Main.cpp

@@ -29,6 +29,19 @@
 #include "utils/Logger.h"
 #include "wrapper/GL.h"
 
+static int ticks = 40;
+
+static bool isRunning() {
+    return !Window::shouldClose() && ticks > 0;
+}
+
+static void tick() {
+    ticks -= ticks > 0;
+}
+
+static void render(float) {
+}
+
 int main(int argAmount, char** args) {
     if(argAmount < 2) {
         LOG_ERROR("missing path to images");
@@ -60,27 +73,15 @@ int main(int argAmount, char** args) {
     NetworkTests::test();
     ComponentsTests::test();
 
-    struct Game {
-        bool isRunning() {
-            return true;
-        }
-
-        void tick() {
-        }
-
-        void render(float) {
-        }
-    };
-
     WindowOptions options(4, 3, {800, 480}, false, "Test");
-    Window w;
-    Error error = w.open(options);
+    Error error = Window::open(options);
     if(error.has()) {
         LOG_ERROR(error.message);
         return 0;
     }
-    Game game;
-    w.run(game, 10'000'000);
+    Window::show();
+    Window::run<isRunning, tick, render>(50'000'000);
+    Window::close();
     GL::printError("WUSI");
     return 0;
 }

+ 1 - 1
images/ImageReader.cpp

@@ -1,4 +1,4 @@
-#include <lodepng/lodepng.h>
+#include "lodepng/lodepng.h"
 
 #include "images/ImageReader.h"
 #include "utils/Cleaner.h"

+ 0 - 36
input/Button.cpp

@@ -1,36 +0,0 @@
-#include "input/Button.h"
-
-Button::Button(int key, const char* name)
-    : key(key), downTime(0), keyboardUp(0), keyboardDown(0),
-      controllerDown(false), released(false), name(name) {
-}
-
-void Button::tick() {
-    bool down = (keyboardDown > 0) || controllerDown;
-    bool up = (keyboardUp == keyboardDown) && !controllerDown;
-
-    if(released) {
-        downTime = 0;
-    }
-    downTime += down;
-    released = down && up;
-
-    keyboardDown -= keyboardUp;
-    keyboardUp = 0;
-}
-
-bool Button::isDown() const {
-    return downTime > 0;
-}
-
-int Button::getDownTime() const {
-    return downTime;
-}
-
-bool Button::wasReleased() const {
-    return released;
-}
-
-const char* Button::getName() const {
-    return name;
-}

+ 0 - 27
input/Button.h

@@ -1,27 +0,0 @@
-#ifndef BUTTON_H
-#define BUTTON_H
-
-class Button final {
-    friend class Buttons;
-
-    int key;
-    int downTime;
-    int keyboardUp;
-    int keyboardDown;
-    bool controllerDown;
-    bool released;
-    const char* name;
-
-public:
-    Button(int key, const char* name);
-
-    bool isDown() const;
-    int getDownTime() const;
-    bool wasReleased() const;
-    const char* getName() const;
-
-private:
-    void tick();
-};
-
-#endif

+ 0 - 121
input/Buttons.cpp

@@ -1,121 +0,0 @@
-#include "input/Buttons.h"
-
-Buttons::Axis::Axis()
-    : less(0.0f), greater(0.0f), lessButton(nullptr), greaterButton(nullptr) {
-}
-
-Buttons::Buttons()
-    : lastMouseX(0), lastMouseY(0), mouseX(0), mouseY(0), activeController(-1),
-      gamepadToButton(nullptr) {
-}
-
-void Buttons::add(Button& button) {
-    buttons.add(button.key, &button);
-}
-
-void Buttons::addMouse(Button& button) {
-    mouseButtons.add(button.key, &button);
-}
-
-void Buttons::mapGamepadButton(Button& button, int mapping) {
-    gamepadToButton[mapping] = &button;
-}
-
-void Buttons::mapGamepadAxis(Button& button, float value, int index) {
-    if(value > 0.0f) {
-        gamepadAxisToButton[index].greater = value;
-        gamepadAxisToButton[index].greaterButton = &button;
-    } else {
-        gamepadAxisToButton[index].less = value;
-        gamepadAxisToButton[index].lessButton = &button;
-    }
-}
-
-void Buttons::tick() {
-    if(searchForGamepad()) {
-        checkGamepad();
-    }
-    for(Button* b : buttons.values()) {
-        b->tick();
-    }
-    for(Button* b : mouseButtons.values()) {
-        b->tick();
-    }
-    lastMouseX = mouseX;
-    lastMouseY = mouseY;
-}
-
-double Buttons::getLastMouseX() const {
-    return lastMouseX;
-}
-
-double Buttons::getLastMouseY() const {
-    return lastMouseY;
-}
-
-double Buttons::getMouseX() const {
-    return mouseX;
-}
-
-double Buttons::getMouseY() const {
-    return mouseY;
-}
-
-void Buttons::onButton(HashMap<int, Button*>& map, int key, int action) {
-    Button** b = map.search(key);
-    if(b != nullptr) {
-        if(action == GLFW_RELEASE) {
-            (*b)->keyboardUp++;
-        } else if(action == GLFW_PRESS) {
-            (*b)->keyboardDown++;
-        }
-    }
-}
-
-void Buttons::onKey(int key, int, int action, int) {
-    onButton(buttons, key, action);
-}
-
-void Buttons::onMouse(int button, int action, int) {
-    onButton(mouseButtons, button, action);
-}
-
-void Buttons::onMouseMove(double x, double y) {
-    mouseX = x;
-    mouseY = y;
-}
-
-bool Buttons::searchForGamepad() {
-    if(activeController != -1) {
-        return true;
-    }
-    for(int i = GLFW_JOYSTICK_1; i <= GLFW_JOYSTICK_LAST; i++) {
-        if(glfwJoystickIsGamepad(i)) {
-            activeController = i;
-            return true;
-        }
-    }
-    return false;
-}
-
-void Buttons::checkGamepad() {
-    GLFWgamepadstate state;
-    if(!glfwGetGamepadState(activeController, &state)) {
-        return;
-    }
-    for(int i = 0; i <= GLFW_GAMEPAD_BUTTON_LAST; i++) {
-        if(gamepadToButton[i] != nullptr) {
-            gamepadToButton[i]->controllerDown = state.buttons[i];
-        }
-    }
-    for(int i = 0; i <= GLFW_GAMEPAD_AXIS_LAST; i++) {
-        if(gamepadAxisToButton[i].greaterButton != nullptr) {
-            gamepadAxisToButton[i].greaterButton->controllerDown =
-                (state.axes[i] > gamepadAxisToButton[i].greater);
-        }
-        if(gamepadAxisToButton[i].lessButton != nullptr) {
-            gamepadAxisToButton[i].lessButton->controllerDown =
-                (state.axes[i] < gamepadAxisToButton[i].less);
-        }
-    }
-}

+ 0 - 60
input/Buttons.h

@@ -1,60 +0,0 @@
-#ifndef BUTTONS_H
-#define BUTTONS_H
-
-#include "GL/glew.h"
-
-#include "GLFW/glfw3.h"
-
-#include "input/Button.h"
-#include "utils/Array.h"
-#include "utils/ArrayList.h"
-#include "utils/HashMap.h"
-
-class Buttons final {
-    friend class Window;
-
-    HashMap<int, Button*> buttons;
-    HashMap<int, Button*> mouseButtons;
-    double lastMouseX;
-    double lastMouseY;
-    double mouseX;
-    double mouseY;
-
-    int activeController;
-    struct Axis {
-        float less;
-        float greater;
-        Button* lessButton;
-        Button* greaterButton;
-
-        Axis();
-    };
-    Array<Axis, 1 + GLFW_GAMEPAD_AXIS_LAST> gamepadAxisToButton;
-    Array<Button*, 1 + GLFW_GAMEPAD_BUTTON_LAST> gamepadToButton;
-
-public:
-    Buttons();
-
-    void add(Button& button);
-    void addMouse(Button& button);
-    void mapGamepadButton(Button& button, int mapping);
-    void mapGamepadAxis(Button& button, float value, int index);
-
-    void tick();
-
-    double getLastMouseX() const;
-    double getLastMouseY() const;
-    double getMouseX() const;
-    double getMouseY() const;
-
-private:
-    void onButton(HashMap<int, Button*>& map, int key, int action);
-    void onKey(int key, int scancode, int action, int mods);
-    void onMouse(int button, int action, int mods);
-    void onMouseMove(double x, double y);
-
-    bool searchForGamepad();
-    void checkGamepad();
-};
-
-#endif

+ 0 - 99
input/TextInput.cpp

@@ -1,99 +0,0 @@
-#include <utility>
-
-#include <GLFW/glfw3.h>
-
-#include "input/TextInput.h"
-
-TextInput::TextInput() : limit(256), active(false) {
-}
-
-void TextInput::setLimit(int limit) {
-    TextInput::limit = limit;
-    while(input.getLength() > limit) {
-        input.removeBySwap(limit);
-    }
-}
-
-void TextInput::reset() {
-    input.clear();
-    cursor = 0;
-}
-
-void TextInput::setActive(bool b) {
-    active = b;
-}
-
-void TextInput::onKeyEvent(int key, int scancode, int action, int mods) {
-    if(!active || action == GLFW_RELEASE) {
-        return;
-    }
-    (void)scancode;
-    (void)mods;
-    switch(key) {
-        case GLFW_KEY_BACKSPACE:
-            if(input.getLength() > cursor - 1 && cursor > 0) {
-                input.remove(cursor - 1);
-                cursor--;
-            }
-            break;
-        case GLFW_KEY_LEFT:
-            if(cursor > 0) {
-                cursor--;
-            }
-            break;
-        case GLFW_KEY_RIGHT:
-            if(cursor < input.getLength()) {
-                cursor++;
-            }
-            break;
-    }
-}
-
-void TextInput::onCharEvent(uint32 codepoint) {
-    if(!active || input.getLength() >= limit) {
-        return;
-    }
-    input.add(codepoint);
-    for(int i = input.getLength() - 1; i > cursor; i--) {
-        std::swap(input[i], input[i - 1]);
-    }
-    cursor++;
-}
-
-static uint32 read(int& index, const char* s) {
-    if(s[index] == '\0') {
-        return '\0';
-    }
-    return s[index++];
-}
-
-static uint32 readUnicode(int& index, const char* s) {
-    uint32 c = read(index, s);
-    if((c & 0xE0) == 0xC0) {
-        c = ((c & 0x1F) << 6) | (read(index, s) & 0x3F);
-    } else if((c & 0xF0) == 0xE0) {
-        c = ((c & 0xF) << 12) | ((read(index, s) & 0x3F) << 6);
-        c |= read(index, s) & 0x3F;
-    } else if((c & 0xF8) == 0xF0) {
-        c = ((c & 0x7) << 18) | ((read(index, s) & 0x3F) << 12);
-        c |= (read(index, s) & 0x3F) << 6;
-        c |= read(index, s) & 0x3F;
-    }
-    return c;
-}
-
-void TextInput::fill(const char* s) {
-    int index = 0;
-    reset();
-    while(true) {
-        uint32 c = readUnicode(index, s);
-        if(c == '\0') {
-            break;
-        }
-        onCharEvent(c);
-    }
-}
-
-int TextInput::getCursor() const {
-    return cursor;
-}

+ 0 - 32
input/TextInput.h

@@ -1,32 +0,0 @@
-#ifndef TEXT_INPUT_H
-#define TEXT_INPUT_H
-
-#include "utils/List.h"
-
-class TextInput final {
-    List<uint32> input;
-    int cursor;
-    int limit;
-    bool active;
-
-public:
-    TextInput();
-
-    void setLimit(int limit);
-    void reset();
-    void setActive(bool b);
-    void onKeyEvent(int key, int scancode, int action, int mods);
-    void onCharEvent(uint32);
-    void fill(const char* s);
-
-    template<int N>
-    void toString(StringBuffer<N>& s) const {
-        for(unsigned int c : input) {
-            s.appendUnicode(c);
-        }
-    }
-
-    int getCursor() const;
-};
-
-#endif

+ 0 - 3
meson.build

@@ -2,9 +2,6 @@ project('gamingcore', 'cpp', default_options : ['default_library=static'])
 
 src = [
     'images/ImageReader.cpp',
-    'input/Button.cpp',
-    'input/Buttons.cpp',
-    'input/TextInput.cpp',
     'math/Frustum.cpp',
     'math/Matrix.cpp',
     'math/Plane.cpp',

+ 373 - 72
rendering/Window.cpp

@@ -1,52 +1,181 @@
+#include <utility>
+
+#include "GL/glew.h"
+#include "GLFW/glfw3.h"
+
 #include "rendering/Window.h"
+#include "utils/Array.h"
+#include "utils/HashMap.h"
+
+static GLFWwindow* window = nullptr;
+static Clock fps;
+static Clock tps;
+static Size size{0, 0};
+static bool sizeChanged = false;
+
+static List<uint32> input;
+static int inputCursor = 0;
+static int inputLimit = 256;
+static bool inputActive = false;
+
+struct Button final {
+    Window::Controls::ButtonName name;
+    int key;
+    int downTime;
+    int upEvents;
+    int downEvents;
+    int controllerUp;
+    int controllerDown;
+    bool released;
+
+    Button(const Window::Controls::ButtonName& name)
+        : name(name), key(0), downTime(0), upEvents(0), downEvents(0),
+          controllerUp(0), controllerDown(0), released(false) {
+    }
+
+    void tick() {
+        bool down = (downEvents > 0) || (controllerDown > 0);
+        bool up = (upEvents == downEvents) && (controllerUp == controllerDown);
 
-static int glfwCounter = 0;
-static int glewCounter = 0;
+        if(released) {
+            downTime = 0;
+        }
+        downTime += down;
+        released = down && up;
+
+        downEvents -= upEvents;
+        upEvents = 0;
+
+        controllerDown -= controllerUp;
+        controllerUp = 0;
+    }
+};
+static Button fallbackButton{"unknown"};
+static List<Button> buttons;
+static HashMap<int, Window::Controls::ButtonId> keyToButtonId;
+static HashMap<int, Window::Controls::ButtonId> gamepadToButtonId;
+static HashMap<int, Window::Controls::ButtonId> mouseToButtonId;
+static Vector2 lastMousePosition;
+static Vector2 mousePosition;
+static int activeController = -1;
+static GLFWgamepadstate lastControllerState;
+
+static void onButton(HashMap<int, Window::Controls::ButtonId>& map, int key,
+                     int action) {
+    Window::Controls::ButtonId* b = map.search(key);
+    if(b == nullptr) {
+        return;
+    }
+    Window::Controls::ButtonId id = *b;
+    if(id < 0 || id >= buttons.getLength()) {
+        return;
+    }
+    if(action == GLFW_RELEASE) {
+        buttons[id].upEvents++;
+    } else if(action == GLFW_PRESS) {
+        buttons[id].downEvents++;
+    }
+}
 
-Window::Window() : window(nullptr), size({0, 0}), textInput(nullptr) {
-    if(glfwCounter == 0 && !glfwInit()) {
+static void addError(Error& e) {
+    const char* description = nullptr;
+    int errorCode = glfwGetError(&description);
+    if(errorCode == GLFW_NO_ERROR) {
         return;
     }
-    glfwCounter++;
+    e.message.append(": ").append(description);
 }
 
-Window::~Window() {
-    if(window != nullptr) {
-        glfwDestroyWindow(window);
+static void handleInputKey(int key, int action) {
+    if(action == GLFW_RELEASE) {
+        return;
     }
-    if(glfwCounter > 0 && --glfwCounter == 0) {
-        glfwTerminate();
+    switch(key) {
+        case GLFW_KEY_BACKSPACE:
+            if(input.getLength() > inputCursor - 1 && inputCursor > 0) {
+                input.remove(inputCursor - 1);
+                inputCursor--;
+            }
+            break;
+        case GLFW_KEY_LEFT: inputCursor -= inputCursor > 0; break;
+        case GLFW_KEY_RIGHT:
+            inputCursor += inputCursor < input.getLength();
+            break;
     }
 }
 
-Error Window::open(const WindowOptions& options) {
-    if(glfwCounter <= 0) {
-        return {"could not initialize GLFW"};
+static void onKey(GLFWwindow*, int key, int scancode, int action, int mods) {
+    (void)scancode;
+    (void)mods;
+    if(inputActive) {
+        handleInputKey(key, action);
+    } else {
+        onButton(keyToButtonId, key, action);
+    }
+}
+
+static void addUnicode(uint32 codepoint) {
+    if(input.getLength() >= inputLimit) {
+        return;
+    }
+    input.add(codepoint);
+    for(int i = input.getLength() - 1; i > inputCursor; i--) {
+        std::swap(input[i], input[i - 1]);
+    }
+    inputCursor++;
+}
+
+static void onChar(GLFWwindow*, uint32 codepoint) {
+    if(inputActive) {
+        addUnicode(codepoint);
+    }
+}
+
+static void onResize(GLFWwindow*, int width, int height) {
+    sizeChanged = true;
+    size.width = width;
+    size.height = height;
+}
+
+static void onMouse(GLFWwindow*, int button, int action, int mods) {
+    (void)mods;
+    onButton(mouseToButtonId, button, action);
+}
+
+static void onMouseMove(GLFWwindow*, double x, double y) {
+    mousePosition = Vector2(x, y);
+}
+
+Error Window::open(const WindowOptions& o) {
+    if(!glfwInit()) {
+        Error e{"could not initialize GLFW"};
+        addError(e);
+        return e;
     }
 
     glfwDefaultWindowHints();
     glfwWindowHint(GLFW_VISIBLE, false);
     glfwWindowHint(GLFW_RESIZABLE, true);
-    glfwWindowHint(GLFW_DECORATED, !options.fullscreen);
+    glfwWindowHint(GLFW_DECORATED, !o.fullscreen);
     glfwWindowHint(GLFW_DOUBLEBUFFER, true);
 
-    if(options.es) {
+    if(o.es) {
         glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API);
     } else {
         glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
     }
-    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, options.majorVersion);
-    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, options.minorVersion);
+    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, o.majorVersion);
+    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, o.minorVersion);
 
-    GLFWmonitor* monitor =
-        options.fullscreen ? glfwGetPrimaryMonitor() : nullptr;
-    window = glfwCreateWindow(options.size.width, options.size.height,
-                              options.name, monitor, nullptr);
+    GLFWmonitor* m = o.fullscreen ? glfwGetPrimaryMonitor() : nullptr;
+    window = glfwCreateWindow(o.size.width, o.size.height, o.name, m, nullptr);
     if(window == nullptr) {
-        return {"could not create window"};
+        Error e{"could not create window"};
+        addError(e);
+        close();
+        return e;
     }
-    size = options.size;
-    glfwSetWindowUserPointer(window, this);
+    size = o.size;
     glfwSetKeyCallback(window, onKey);
     glfwSetCharCallback(window, onChar);
     glfwSetFramebufferSizeCallback(window, onResize);
@@ -54,79 +183,251 @@ Error Window::open(const WindowOptions& options) {
     glfwSetCursorPosCallback(window, onMouseMove);
 
     glfwMakeContextCurrent(window);
-    glfwSwapInterval(options.vsync);
-
-    if(glewCounter == 0) {
-        GLenum err = glewInit();
-        if(err != GLEW_OK) {
-            Error error;
-            error.message.clear()
-                .append("could not initialize GLEW: ")
-                .append(glewGetErrorString(err));
-            return error;
-        }
+    glfwSwapInterval(o.vsync);
+
+    GLenum err = glewInit();
+    if(err != GLEW_OK) {
+        Error e{"could not initialize GLEW: "};
+        e.message.append(glewGetErrorString(err));
+        close();
+        return e;
     }
-    glewCounter++;
     return {};
 }
 
-const Clock& Window::getFrameClock() const {
-    return fps;
+void Window::close() {
+    if(window != nullptr) {
+        glfwDestroyWindow(window);
+        window = nullptr;
+    }
+    glfwTerminate();
 }
 
-const Clock& Window::getTickClock() const {
-    return tps;
+float Window::getTicksPerSecond() {
+    return tps.getUpdatesPerSecond();
 }
 
-const Size& Window::getSize() const {
+float Window::getFramesPerSecond() {
+    return fps.getUpdatesPerSecond();
+}
+
+const Size& Window::getSize() {
     return size;
 }
 
-void Window::trapCursor(bool trap) {
-    glfwSetInputMode(window, GLFW_CURSOR,
-                     trap ? GLFW_CURSOR_DISABLED : GLFW_CURSOR_NORMAL);
+bool Window::hasSizeChanged() {
+    return sizeChanged;
+}
+
+void Window::trapCursor() {
+    glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
+}
+
+void Window::freeCursor() {
+    glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
+}
+
+void Window::show() {
+    glfwShowWindow(window);
+}
+
+bool Window::shouldClose() {
+    return glfwWindowShouldClose(window);
 }
 
-void Window::onKey(GLFWwindow* w, int key, int scancode, int action, int mods) {
-    void* p = glfwGetWindowUserPointer(w);
-    if(p != nullptr) {
-        Window* rw = static_cast<Window*>(p);
-        TextInput* input = rw->textInput;
-        if(input != nullptr) {
-            input->onKeyEvent(key, scancode, action, mods);
+Clock::Nanos Window::startFrame() {
+    return fps.update();
+}
+
+static bool searchForGamepad() {
+    if(activeController != -1) {
+        return true;
+    }
+    for(int i = GLFW_JOYSTICK_1; i <= GLFW_JOYSTICK_LAST; i++) {
+        if(glfwJoystickIsGamepad(i)) {
+            activeController = i;
+            return true;
         }
-        rw->buttons.onKey(key, scancode, action, mods);
     }
+    return false;
 }
-void Window::onChar(GLFWwindow* w, unsigned int codepoint) {
-    void* p = glfwGetWindowUserPointer(w);
-    if(p != nullptr) {
-        TextInput* input = static_cast<Window*>(p)->textInput;
-        if(input != nullptr) {
-            input->onCharEvent(codepoint);
+
+static void checkGamepad() {
+    GLFWgamepadstate state;
+    if(!glfwGetGamepadState(activeController, &state)) {
+        activeController = -1;
+        return;
+    }
+    for(int i = 0; i <= GLFW_GAMEPAD_BUTTON_LAST; i++) {
+        Window::Controls::ButtonId* idp = gamepadToButtonId.search(i);
+        if(idp == nullptr) {
+            continue;
+        }
+        Window::Controls::ButtonId id = *idp;
+        if(id < 0 || id >= buttons.getLength()) {
+            continue;
+        }
+        if(!lastControllerState.buttons[i] && state.buttons[i]) {
+            buttons[id].controllerDown++;
+        } else if(lastControllerState.buttons[i] && !state.buttons[i]) {
+            buttons[id].controllerUp++;
         }
     }
+    lastControllerState = state;
+}
+
+void Window::endFrame() {
+    glfwSwapBuffers(window);
+    sizeChanged = false;
+    glfwPollEvents();
+    if(searchForGamepad()) {
+        checkGamepad();
+    }
+}
+
+void Window::tick() {
+    tps.update();
+    lastMousePosition = mousePosition;
+    for(Button& b : buttons) {
+        b.tick();
+    }
+}
+
+void Window::Input::setLimit(int l) {
+    inputLimit = l;
+    while(input.getLength() > inputLimit) {
+        input.removeBySwap(inputLimit);
+    }
+}
+
+void Window::Input::reset() {
+    input.clear();
+    inputCursor = 0;
+}
+
+void Window::Input::enable() {
+    inputActive = true;
+}
+
+void Window::Input::disable() {
+    inputActive = false;
+}
+
+static uint32 read(int& index, const char* s) {
+    if(s[index] == '\0') {
+        return '\0';
+    }
+    return s[index++];
 }
 
-void Window::onResize(GLFWwindow* w, int width, int height) {
-    void* p = glfwGetWindowUserPointer(w);
-    if(p != nullptr) {
-        Window* rw = static_cast<Window*>(p);
-        rw->size.width = width;
-        rw->size.height = height;
+static uint32 readUnicode(int& index, const char* s) {
+    uint32 c = read(index, s);
+    if((c & 0xE0) == 0xC0) {
+        c = ((c & 0x1F) << 6) | (read(index, s) & 0x3F);
+    } else if((c & 0xF0) == 0xE0) {
+        c = ((c & 0xF) << 12) | ((read(index, s) & 0x3F) << 6);
+        c |= read(index, s) & 0x3F;
+    } else if((c & 0xF8) == 0xF0) {
+        c = ((c & 0x7) << 18) | ((read(index, s) & 0x3F) << 12);
+        c |= (read(index, s) & 0x3F) << 6;
+        c |= read(index, s) & 0x3F;
     }
+    return c;
 }
 
-void Window::onMouse(GLFWwindow* w, int button, int action, int mods) {
-    void* p = glfwGetWindowUserPointer(w);
-    if(p != nullptr) {
-        static_cast<Window*>(p)->buttons.onMouse(button, action, mods);
+void Window::Input::fill(const char* s) {
+    int index = 0;
+    reset();
+    while(true) {
+        uint32 c = readUnicode(index, s);
+        if(c == '\0') {
+            break;
+        }
+        addUnicode(c);
     }
 }
 
-void Window::onMouseMove(GLFWwindow* w, double x, double y) {
-    void* p = glfwGetWindowUserPointer(w);
-    if(p != nullptr) {
-        static_cast<Window*>(p)->buttons.onMouseMove(x, y);
+int Window::Input::getCursor() {
+    return inputCursor;
+}
+
+void Window::Input::setCursor(int index) {
+    if(index < 0) {
+        inputCursor = 0;
+    } else if(index > input.getLength()) {
+        inputCursor = input.getLength();
+    } else {
+        inputCursor = index;
     }
+}
+
+const List<uint32>& Window::Input::getUnicode() {
+    return input;
+}
+
+Window::Controls::ButtonId Window::Controls::add(const ButtonName& name) {
+    ButtonId id = buttons.getLength();
+    buttons.add(name);
+    return id;
+}
+
+void Window::Controls::bindKey(ButtonId id, int key) {
+    keyToButtonId.add(key, id);
+}
+
+void Window::Controls::bindGamepad(ButtonId id, int gamepadButton) {
+    gamepadToButtonId.add(gamepadButton, id);
+}
+
+void Window::Controls::bindMouse(ButtonId id, int mouseButton) {
+    mouseToButtonId.add(mouseButton, id);
+}
+
+Vector2 Window::Controls::getLastMousePosition() {
+    return lastMousePosition;
+}
+
+Vector2 Window::Controls::getMousePosition() {
+    return mousePosition;
+}
+
+Vector2 Window::Controls::getLeftGamepadAxis() {
+    return Vector2(lastControllerState.axes[GLFW_GAMEPAD_AXIS_LEFT_X],
+                   lastControllerState.axes[GLFW_GAMEPAD_AXIS_LEFT_Y]);
+}
+
+Vector2 Window::Controls::getRightGamepadAxis() {
+    return Vector2(lastControllerState.axes[GLFW_GAMEPAD_AXIS_RIGHT_X],
+                   lastControllerState.axes[GLFW_GAMEPAD_AXIS_RIGHT_Y]);
+}
+
+float Window::Controls::getLeftGamepadTrigger() {
+    return lastControllerState.axes[GLFW_GAMEPAD_AXIS_LEFT_TRIGGER];
+}
+
+float Window::Controls::getRightGamepadTrigger() {
+    return lastControllerState.axes[GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER];
+}
+
+static const Button& getButton(Window::Controls::ButtonId id) {
+    if(id < 0 || id >= buttons.getLength()) {
+        return fallbackButton;
+    }
+    return buttons[id];
+}
+
+bool Window::Controls::isDown(ButtonId id) {
+    return getButton(id).downTime > 0;
+}
+
+int Window::Controls::getDownTime(ButtonId id) {
+    return getButton(id).downTime;
+}
+
+bool Window::Controls::wasReleased(ButtonId id) {
+    return getButton(id).released;
+}
+
+const Window::Controls::ButtonName& Window::Controls::getName(ButtonId id) {
+    return getButton(id).name;
 }

+ 69 - 43
rendering/Window.h

@@ -1,65 +1,91 @@
 #ifndef WINDOW_H
 #define WINDOW_H
 
-#include "GL/glew.h"
-#include "GLFW/glfw3.h"
-
-#include "input/Buttons.h"
-#include "input/TextInput.h"
+#include "math/Vector.h"
 #include "rendering/WindowOptions.h"
 #include "utils/Clock.h"
 #include "utils/Error.h"
+#include "utils/List.h"
+#include "utils/StringBuffer.h"
+
+namespace Window {
+    typedef bool (*ShouldRun)();
+    typedef void (*Tick)();
+    typedef void (*Render)(float);
 
-class Window final {
-    GLFWwindow* window;
-    Clock fps;
-    Clock tps;
-    Size size;
+    Error open(const WindowOptions& options);
+    void close();
 
-public:
-    TextInput* textInput;
-    Buttons buttons;
+    float getTicksPerSecond();
+    float getFramesPerSecond();
 
-    Window();
-    ~Window();
-    Window(const Window&) = delete;
-    Window& operator=(const Window&) = delete;
-    Window(Window&&) = delete;
-    Window& operator=(Window&&) = delete;
+    void trapCursor();
+    void freeCursor();
 
-    Error open(const WindowOptions& options);
+    const Size& getSize();
+    bool hasSizeChanged();
 
-    const Clock& getFrameClock() const;
-    const Clock& getTickClock() const;
-    const Size& getSize() const;
+    void show();
+    bool shouldClose();
 
-    void trapCursor(bool trap);
+    Clock::Nanos startFrame();
+    void endFrame();
+    void tick();
 
-    template<typename T>
-    void run(T& game, Clock::Nanos nanosPerTick) {
-        glfwShowWindow(window);
+    template<ShouldRun SR, Tick T, Render R>
+    void run(Clock::Nanos nanosPerTick) {
         Clock::Nanos lag = 0;
-        while(!glfwWindowShouldClose(window) && game.isRunning()) {
-            lag += fps.update();
+        while(SR()) {
+            lag += startFrame();
             while(lag >= nanosPerTick) {
                 lag -= nanosPerTick;
-                tps.update();
-                buttons.tick();
-                game.tick();
+                tick();
+                T();
             }
-            game.render(static_cast<float>(lag) / nanosPerTick);
-            glfwSwapBuffers(window);
-            glfwPollEvents();
+            R(static_cast<float>(lag) / nanosPerTick);
+            endFrame();
         }
     }
 
-private:
-    static void onKey(GLFWwindow* w, int key, int scancode, int action,
-                      int mods);
-    static void onChar(GLFWwindow* w, unsigned int codepoint);
-    static void onResize(GLFWwindow* w, int width, int height);
-    static void onMouse(GLFWwindow* w, int button, int action, int mods);
-    static void onMouseMove(GLFWwindow* w, double x, double y);
-};
+    namespace Input {
+        void setLimit(int limit);
+        void reset();
+        void enable();
+        void disable();
+        void fill(const char* s);
+        int getCursor();
+        void setCursor(int index);
+        const List<uint32>& getUnicode();
+
+        template<int N>
+        void toString(StringBuffer<N>& s) {
+            for(uint32 c : getUnicode()) {
+                s.appendUnicode(c);
+            }
+        }
+    }
+
+    namespace Controls {
+        typedef StringBuffer<32> ButtonName;
+        typedef int ButtonId;
+
+        ButtonId add(const ButtonName& name);
+        void bindKey(ButtonId id, int key);
+        void bindGamepad(ButtonId id, int gamepadButton);
+        void bindMouse(ButtonId id, int mouseButton);
+
+        Vector2 getLastMousePosition();
+        Vector2 getMousePosition();
+        Vector2 getLeftGamepadAxis();
+        Vector2 getRightGamepadAxis();
+        float getLeftGamepadTrigger();
+        float getRightGamepadTrigger();
+
+        bool isDown(ButtonId id);
+        int getDownTime(ButtonId id);
+        bool wasReleased(ButtonId id);
+        const ButtonName& getName(ButtonId id);
+    }
+}
 
 #endif