/* ** input.cpp ** ** This file is part of mkxp. ** ** Copyright (C) 2013 Jonas Kulla ** ** mkxp is free software: you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation, either version 2 of the License, or ** (at your option) any later version. ** ** mkxp is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with mkxp. If not, see . */ #include "input.h" #include "sharedstate.h" #include "eventthread.h" #include "keybindings.h" #include "exception.h" #include "util.h" #include #include #include #include #define BUTTON_CODE_COUNT 24 struct ButtonState{ bool pressed; bool triggered; bool repeated; ButtonState() : pressed(false), triggered(false), repeated(false) {} }; struct KbBindingData{ SDL_Scancode source; Input::ButtonCode target; }; struct Binding{ Binding(Input::ButtonCode target = Input::None) : target(target) {} virtual bool sourceActive() const = 0; virtual bool sourceRepeatable() const = 0; Input::ButtonCode target; }; /* Keyboard binding */ struct KbBinding : public Binding{ KbBinding() {} KbBinding(const KbBindingData &data) : Binding(data.target), source(data.source) {} bool sourceActive() const{ /* Special case aliases */ if (source == SDL_SCANCODE_LSHIFT) return EventThread::keyStates[source] || EventThread::keyStates[SDL_SCANCODE_RSHIFT]; if (source == SDL_SCANCODE_RETURN) return EventThread::keyStates[source] || EventThread::keyStates[SDL_SCANCODE_KP_ENTER]; return EventThread::keyStates[source]; } bool sourceRepeatable() const{ return (source >= SDL_SCANCODE_A && source <= SDL_SCANCODE_0) || (source >= SDL_SCANCODE_RIGHT && source <= SDL_SCANCODE_UP) || (source >= SDL_SCANCODE_F1 && source <= SDL_SCANCODE_F12); } SDL_Scancode source; }; /* Controller button binding */ struct GcButtonBinding : public Binding{ GcButtonBinding() {} bool sourceActive() const{ return EventThread::gcState.buttons[source]; } bool sourceRepeatable() const{ return true; } uint8_t source; }; /* Controller axis binding */ struct GcAxisBinding : public Binding{ GcAxisBinding() {} GcAxisBinding(uint8_t source, AxisDir dir, Input::ButtonCode target) : Binding(target), source(source), dir(dir) {} bool sourceActive() const{ int val = EventThread::gcState.axes[source]; if (dir == Negative) return val < -JAXIS_THRESHOLD; else /* dir == Positive */ return val > JAXIS_THRESHOLD; } bool sourceRepeatable() const{ return true; } uint8_t source; AxisDir dir; }; /* Joystick button binding */ struct JsButtonBinding : public Binding{ JsButtonBinding() {} bool sourceActive() const{ return EventThread::joyState.buttons[source]; } bool sourceRepeatable() const{ return true; } uint8_t source; }; /* Joystick axis binding */ struct JsAxisBinding : public Binding{ JsAxisBinding() {} JsAxisBinding(uint8_t source, AxisDir dir, Input::ButtonCode target) : Binding(target), source(source), dir(dir) {} bool sourceActive() const{ int val = EventThread::joyState.axes[source]; if (dir == Negative) return val < -JAXIS_THRESHOLD; else /* dir == Positive */ return val > JAXIS_THRESHOLD; } bool sourceRepeatable() const{ return true; } uint8_t source; AxisDir dir; }; /* Joystick hat binding */ struct JsHatBinding : public Binding{ JsHatBinding() {} JsHatBinding(uint8_t source, uint8_t pos, Input::ButtonCode target) : Binding(target), source(source), pos(pos) {} bool sourceActive() const{ /* For a diagonal input accept it as an input for both the axes */ return (pos & EventThread::joyState.hats[source]) != 0; } bool sourceRepeatable() const{ return true; } uint8_t source; uint8_t pos; }; /* Mouse button binding */ struct MsBinding : public Binding{ MsBinding() {} MsBinding(int buttonIndex, Input::ButtonCode target) : Binding(target), index(buttonIndex) {} bool sourceActive() const{ return EventThread::mouseState.buttons[index]; } bool sourceRepeatable() const{ return false; } int index; }; /* Not rebindable */ static const KbBindingData staticKbBindings[] ={ { SDL_SCANCODE_F5, Input::F5 }, { SDL_SCANCODE_F6, Input::F6 }, { SDL_SCANCODE_F7, Input::F7 }, { SDL_SCANCODE_F8, Input::F8 }, { SDL_SCANCODE_F9, Input::F9 } }; static elementsN(staticKbBindings); /* Maps ButtonCode enum values to indices * in the button state array */ static const int mapToIndex[] = { 0, 0, 1, 0, 2, 0, 3, 0, 4, 0, 0, 5, 6, 7, 8, 9, 10, 11, 12, 0, 0, 13, 14, 15, 0, 16, 17, 18, 19, 20, 0, 0, 0, 0, 0, 0, 0, 0, 21, 22, 23 }; static elementsN(mapToIndex); static const Input::ButtonCode dirs[] = { Input::Down, Input::Left, Input::Right, Input::Up }; static const int dirFlags[] = { 1 << Input::Down, 1 << Input::Left, 1 << Input::Right, 1 << Input::Up }; /* Dir4 is always zero on these combinations */ static const int deadDirFlags[] = { dirFlags[0] | dirFlags[3], dirFlags[1] | dirFlags[2] }; static const Input::ButtonCode otherDirs[4][3] = { { Input::Left, Input::Right, Input::Up }, /* Down */ { Input::Down, Input::Up, Input::Right }, /* Left */ { Input::Down, Input::Up, Input::Left }, /* Right */ { Input::Left, Input::Right, Input::Up } /* Up */ }; // storing wheel as buffer cause yes. struct MouseFrameState { float wheelX, wheelY; bool wheelFlipped; }; struct InputPrivate { std::vector kbStatBindings; std::vector kbBindings; std::vector gcABindings; std::vector gcBBindings; std::vector jsABindings; std::vector jsHBindings; std::vector jsBBindings; std::vector msBindings; /* Collective binding array */ std::vector bindings; ButtonState stateArray[BUTTON_CODE_COUNT*2]; ButtonState *states; ButtonState *statesOld; Input::ButtonCode repeating; unsigned int repeatCount; bool triedExit; struct{ int active; Input::ButtonCode previous; } dir4Data; struct{ int active; } dir8Data; MouseFrameState mouseWheelState; InputPrivate(const RGSSThreadData &rtData){ initStaticKbBindings(); initMsBindings(); /* Main thread should have these posted by now */ checkBindingChange(rtData); states = stateArray; statesOld = stateArray + BUTTON_CODE_COUNT; /* Clear buffers */ clearBuffer(); swapBuffers(); clearBuffer(); repeating = Input::None; repeatCount = 0; dir4Data.active = 0; dir4Data.previous = Input::None; dir8Data.active = 0; triedExit = false; mouseWheelState = { 0, 0 }; } inline ButtonState &getStateCheck(int code){ int index; if (code < 0 || (size_t) code > mapToIndexN-1) index = 0; else index = mapToIndex[code]; return states[index]; } inline ButtonState &getState(Input::ButtonCode code){ return states[mapToIndex[code]]; } inline ButtonState &getOldState(Input::ButtonCode code){ return statesOld[mapToIndex[code]]; } void swapBuffers(){ ButtonState *tmp = states; states = statesOld; statesOld = tmp; } void clearBuffer(){ const size_t size = sizeof(ButtonState) * BUTTON_CODE_COUNT; SDL_memset(states, 0, size); } void checkBindingChange(const RGSSThreadData &rtData){ BDescVec d; if (!rtData.bindingUpdateMsg.poll(d)) return; applyBindingDesc(d); } template void appendBindings(std::vector &bind){ for (size_t i = 0; i < bind.size(); ++i) bindings.push_back(&bind[i]); } void setBindingDescs(const std::vector &descs, const Input::ButtonCode &target){ // cleaning kbBindings.erase( std::remove_if( kbBindings.begin(), kbBindings.end(), [target](const KbBinding& b) { return b.target == target; } ), kbBindings.end() ); gcABindings.erase( std::remove_if( gcABindings.begin(), gcABindings.end(), [target](const GcAxisBinding& b) { return b.target == target; } ), gcABindings.end() ); gcBBindings.erase( std::remove_if( gcBBindings.begin(), gcBBindings.end(), [target](const GcButtonBinding& b) { return b.target == target; } ), gcBBindings.end() ); jsABindings.erase( std::remove_if( jsABindings.begin(), jsABindings.end(), [target](const JsAxisBinding& b) { return b.target == target; } ), jsABindings.end() ); jsHBindings.erase( std::remove_if( jsHBindings.begin(), jsHBindings.end(), [target](const JsHatBinding& b) { return b.target == target; } ), jsHBindings.end() ); jsBBindings.erase( std::remove_if( jsBBindings.begin(), jsBBindings.end(), [target](const JsButtonBinding& b) { return b.target == target; } ), jsBBindings.end() ); for (size_t i = 0; i < descs.size(); ++i){ const SourceDesc &src = descs[i]; if (target == Input::None) continue; switch (src.type){ case Invalid : break; case Key : { KbBinding bind; bind.source = src.d.scan; bind.target = target; kbBindings.push_back(bind); break; } case CAxis : { GcAxisBinding bind; bind.source = src.d.ja.axis; bind.dir = src.d.ja.dir; bind.target = target; gcABindings.push_back(bind); break; } case CButton : { GcButtonBinding bind; bind.source = src.d.jb; bind.target = target; gcBBindings.push_back(bind); break; } case JAxis : { JsAxisBinding bind; bind.source = src.d.ja.axis; bind.dir = src.d.ja.dir; bind.target = target; jsABindings.push_back(bind); break; } case JHat : { JsHatBinding bind; bind.source = src.d.jh.hat; bind.pos = src.d.jh.pos; bind.target = target; jsHBindings.push_back(bind); break; } case JButton : { JsButtonBinding bind; bind.source = src.d.jb; bind.target = target; jsBBindings.push_back(bind); break; } default : assert(!"unreachable"); } } bindings.clear(); appendBindings(kbStatBindings); appendBindings(msBindings); appendBindings(kbBindings); appendBindings(gcABindings); appendBindings(gcBBindings); appendBindings(jsABindings); appendBindings(jsHBindings); appendBindings(jsBBindings); } void applyBindingDesc(const BDescVec &d){ kbBindings.clear(); gcABindings.clear(); gcBBindings.clear(); jsABindings.clear(); jsHBindings.clear(); jsBBindings.clear(); for (size_t i = 0; i < d.size(); ++i){ const BindingDesc &desc = d[i]; const SourceDesc &src = desc.src; if (desc.target == Input::None) continue; switch (desc.src.type){ case Invalid : break; case Key : { KbBinding bind; bind.source = src.d.scan; bind.target = desc.target; kbBindings.push_back(bind); break; } case CAxis : { GcAxisBinding bind; bind.source = src.d.ja.axis; bind.dir = src.d.ja.dir; bind.target = desc.target; gcABindings.push_back(bind); break; } case CButton : { GcButtonBinding bind; bind.source = src.d.jb; bind.target = desc.target; gcBBindings.push_back(bind); break; } case JAxis : { JsAxisBinding bind; bind.source = src.d.ja.axis; bind.dir = src.d.ja.dir; bind.target = desc.target; jsABindings.push_back(bind); break; } case JHat : { JsHatBinding bind; bind.source = src.d.jh.hat; bind.pos = src.d.jh.pos; bind.target = desc.target; jsHBindings.push_back(bind); break; } case JButton : { JsButtonBinding bind; bind.source = src.d.jb; bind.target = desc.target; jsBBindings.push_back(bind); break; } default : assert(!"unreachable"); } } bindings.clear(); appendBindings(kbStatBindings); appendBindings(msBindings); appendBindings(kbBindings); appendBindings(gcABindings); appendBindings(gcBBindings); appendBindings(jsABindings); appendBindings(jsHBindings); appendBindings(jsBBindings); } void initStaticKbBindings(){ kbStatBindings.clear(); for (size_t i = 0; i < staticKbBindingsN; ++i) kbStatBindings.push_back(KbBinding(staticKbBindings[i])); } void initMsBindings(){ msBindings.resize(3); size_t i = 0; msBindings[i++] = MsBinding(SDL_BUTTON_LEFT, Input::MouseLeft); msBindings[i++] = MsBinding(SDL_BUTTON_MIDDLE, Input::MouseMiddle); msBindings[i++] = MsBinding(SDL_BUTTON_RIGHT, Input::MouseRight); } void pollBindings(Input::ButtonCode &repeatCand){ for (size_t i = 0; i < bindings.size(); ++i) pollBindingPriv(*bindings[i], repeatCand); updateDir4(); updateDir8(); } void pollBindingPriv(const Binding &b, Input::ButtonCode &repeatCand){ if (!b.sourceActive()) return; if (b.target == Input::None) return; ButtonState &state = getState(b.target); ButtonState &oldState = getOldState(b.target); state.pressed = true; /* Must have been released before to trigger */ if (!oldState.pressed) state.triggered = true; /* Unbound keys don't create/break repeat */ if (repeatCand != Input::None) return; if (repeating != b.target && !oldState.pressed) { if (b.sourceRepeatable()) repeatCand = b.target; else /* Unrepeatable keys still break current repeat */ repeating = Input::None; } } void updateDir4(){ int dirFlag = 0; for (size_t i = 0; i < 4; ++i) dirFlag |= (getState(dirs[i]).pressed ? dirFlags[i] : 0); if (dirFlag == deadDirFlags[0] || dirFlag == deadDirFlags[1]){ dir4Data.active = Input::None; return; } if (dir4Data.previous != Input::None){ /* Check if prev still pressed */ if (getState(dir4Data.previous).pressed){ for (size_t i = 0; i < 3; ++i){ Input::ButtonCode other = otherDirs[(dir4Data.previous/2)-1][i]; if (!getState(other).pressed) continue; dir4Data.active = other; return; } } } for (size_t i = 0; i < 4; ++i){ if (!getState(dirs[i]).pressed) continue; dir4Data.active = dirs[i]; dir4Data.previous = dirs[i]; return; } dir4Data.active = Input::None; dir4Data.previous = Input::None; } void updateDir8(){ static const int combos[4][4] = { { 2, 1, 3, 0 }, { 1, 4, 0, 7 }, { 3, 0, 6, 9 }, { 0, 7, 9, 8 } }; dir8Data.active = 0; for (size_t i = 0; i < 4; ++i) { Input::ButtonCode one = dirs[i]; if (!getState(one).pressed) continue; for (int j = 0; j < 3; ++j){ Input::ButtonCode other = otherDirs[i][j]; if (!getState(other).pressed) continue; dir8Data.active = combos[(one/2)-1][(other/2)-1]; return; } dir8Data.active = one; return; } } }; Input::Input(const RGSSThreadData &rtData){ p = new InputPrivate(rtData); } void Input::update(){ shState->checkShutdown(); p->checkBindingChange(shState->rtData()); p->swapBuffers(); p->clearBuffer(); // updating mouse wheel state { p->mouseWheelState.wheelFlipped = EventThread::mouseState.wheelFlipped; p->mouseWheelState.wheelX = EventThread::mouseState.wheelX; p->mouseWheelState.wheelY = EventThread::mouseState.wheelY; EventThread::mouseState.wheelFlipped = false; EventThread::mouseState.wheelX = 0; EventThread::mouseState.wheelY = 0; } ButtonCode repeatCand = None; /* Poll all bindings */ p->pollBindings(repeatCand); /* Check for new repeating key */ if (repeatCand != None && repeatCand != p->repeating){ p->repeating = repeatCand; p->repeatCount = 0; p->getState(repeatCand).repeated = true; return; } /* Check if repeating key is still pressed */ if (p->getState(p->repeating).pressed){ p->repeatCount++; bool repeated; repeated = p->repeatCount >= 15 && ((p->repeatCount+1) % 4) == 0; p->getState(p->repeating).repeated |= repeated; return; } p->repeating = None; RGSSThreadData &rtData = shState->rtData(); p->triedExit = rtData.triedExit; rtData.triedExit.clear(); } bool Input::isPressed(int button){ return p->getStateCheck(button).pressed; } bool Input::isTriggered(int button){ return p->getStateCheck(button).triggered; } bool Input::isRepeated(int button){ return p->getStateCheck(button).repeated; } int Input::dir4Value(){ return p->dir4Data.active; } int Input::dir8Value(){ return p->dir8Data.active; } int Input::mouseX(){ RGSSThreadData &rtData = shState->rtData(); return (EventThread::mouseState.x - rtData.screenOffset.x) * rtData.sizeResoRatio.x; } int Input::mouseY(){ RGSSThreadData &rtData = shState->rtData(); return (EventThread::mouseState.y - rtData.screenOffset.y) * rtData.sizeResoRatio.y; } void Input::setBinding(std::vector descs, Input::ButtonCode target){ p->setBindingDescs(descs, target); } // wheel support :3 float Input::wheelX() { return p->mouseWheelState.wheelX; } float Input::wheelY() { return p->mouseWheelState.wheelY; } bool Input::wheelFlipped() { return p->mouseWheelState.wheelFlipped; } bool Input::hasQuit(){ return p->triedExit; } Input::~Input(){ delete p; }