#include "rendering/Window.h" #include <utility> #include "GL/glew.h" #include "GLFW/glfw3.h" #include "data/Array.h" #include "data/HashMap.h" static GLFWwindow* window = nullptr; static Clock fps; static Clock tps; static IntVector2 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); 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; Window::Options::Options(int majorVersion_, int minorVersion_, const IntVector2& size_, bool es_, const char* name_) : majorVersion(majorVersion_), minorVersion(minorVersion_), size(size_), fullscreen(false), es(es_), vsync(true), name(name_) { } 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++; } } static void addError(Error& e) { const char* description = nullptr; int errorCode = glfwGetError(&description); if(errorCode == GLFW_NO_ERROR) { return; } e.message.append(": ").append(description); } static void handleInputKey(int key, int action) { if(action == GLFW_RELEASE) { return; } 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; } } static void onKey(GLFWwindow*, int key, int scancode, int action, int mods) { (void)scancode; (void)mods; if(inputActive) { handleInputKey(key, action); } 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[0] = width; size[1] = 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 = Vector<2, double>(x, y).toFloat(); } Error Window::open(const Options& 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, !o.fullscreen); glfwWindowHint(GLFW_DOUBLEBUFFER, true); 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, o.majorVersion); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, o.minorVersion); GLFWmonitor* m = o.fullscreen ? glfwGetPrimaryMonitor() : nullptr; window = glfwCreateWindow(o.size[0], o.size[1], o.name, m, nullptr); if(window == nullptr) { Error e{"could not create window"}; addError(e); close(); return e; } size = o.size; glfwSetKeyCallback(window, onKey); glfwSetCharCallback(window, onChar); glfwSetFramebufferSizeCallback(window, onResize); glfwSetMouseButtonCallback(window, onMouse); glfwSetCursorPosCallback(window, onMouseMove); glfwMakeContextCurrent(window); glfwSwapInterval(o.vsync); GLenum err = glewInit(); if(err != GLEW_OK) { Error e{"could not initialize GLEW: "}; e.message.append(glewGetErrorString(err)); close(); return e; } return {}; } void Window::close() { if(window != nullptr) { glfwDestroyWindow(window); window = nullptr; } glfwTerminate(); } float Window::getTicksPerSecond() { return tps.getUpdatesPerSecond(); } float Window::getFramesPerSecond() { return fps.getUpdatesPerSecond(); } const IntVector2& Window::getSize() { return size; } 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); } bool Window::isCursorTrapped() { return glfwGetInputMode(window, GLFW_CURSOR) == GLFW_CURSOR_DISABLED; } void Window::show() { glfwShowWindow(window); } bool Window::shouldClose() { return glfwWindowShouldClose(window); } 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; } } return false; } 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(); for(Button& b : buttons) { b.tick(); } } void Window::postTick() { lastMousePosition = mousePosition; } 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; } bool Window::Input::isEnabled() { return inputActive; } static uint32 read(int& index, const char* s) { if(s[index] == '\0') { return '\0'; } return static_cast<uint32>(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 Window::Input::fill(const char* s) { int index = 0; reset(); while(true) { uint32 c = readUnicode(index, s); if(c == '\0') { break; } addUnicode(c); } } 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; }