From dd73db2e9da46162130afc52d1e9856de623c824 Mon Sep 17 00:00:00 2001 From: Jonas Kulla Date: Sat, 25 Jan 2014 09:24:55 +0100 Subject: [PATCH] Introduce F1 menu to reconfigure key bindings at runtime --- CMakeLists.txt | 4 + mkxp.conf.sample | 13 + mkxp.pro | 4 + src/config.cpp | 18 + src/config.h | 7 + src/eventthread.cpp | 45 +- src/eventthread.h | 57 ++- src/font.cpp | 7 + src/font.h | 2 + src/input.cpp | 225 ++++----- src/input.h | 3 +- src/keybindings.cpp | 311 ++++++++++++ src/keybindings.h | 108 ++++ src/main.cpp | 10 +- src/settingsmenu.cpp | 1112 ++++++++++++++++++++++++++++++++++++++++++ src/settingsmenu.h | 46 ++ src/sharedstate.cpp | 1 + 17 files changed, 1837 insertions(+), 136 deletions(-) create mode 100644 src/keybindings.cpp create mode 100644 src/keybindings.h create mode 100644 src/settingsmenu.cpp create mode 100644 src/settingsmenu.h diff --git a/CMakeLists.txt b/CMakeLists.txt index aa274df..5f3457d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -135,6 +135,8 @@ set(MAIN_HEADERS src/gl-util.h src/util.h src/config.h + src/settingsmenu.h + src/keybindings.h src/tileatlas.h src/sharedstate.h src/al-util.h @@ -180,6 +182,8 @@ set(MAIN_SOURCE src/debuglogger.cpp src/etc.cpp src/config.cpp + src/settingsmenu.cpp + src/keybindings.cpp src/tileatlas.cpp src/sharedstate.cpp src/gl-fun.cpp diff --git a/mkxp.conf.sample b/mkxp.conf.sample index 2f119f4..2122fab 100644 --- a/mkxp.conf.sample +++ b/mkxp.conf.sample @@ -111,6 +111,19 @@ # allowSymlinks=false +# Organisation / company and application / game +# name to build the directory path where mkxp +# will store game specific data (eg. key bindings). +# If not specified, mkxp will save to a common +# directory shared by all games. Note that these +# are TWO individual config entries, and both need +# to be defined for this to take effect. +# (default: none) +# +# dataPathOrg=mycompany +# dataPathApp=mygame + + # Set the game window icon to 'path/to/icon.png' # (default: none) # diff --git a/mkxp.pro b/mkxp.pro index 8e522c9..5d17087 100644 --- a/mkxp.pro +++ b/mkxp.pro @@ -114,6 +114,8 @@ HEADERS += \ src/gl-util.h \ src/util.h \ src/config.h \ + src/settingsmenu.h \ + src/keybindings.h \ src/tileatlas.h \ src/sharedstate.h \ src/al-util.h \ @@ -158,6 +160,8 @@ SOURCES += \ src/debuglogger.cpp \ src/etc.cpp \ src/config.cpp \ + src/settingsmenu.cpp \ + src/keybindings.cpp \ src/tileatlas.cpp \ src/sharedstate.cpp \ src/gl-fun.cpp \ diff --git a/src/config.cpp b/src/config.cpp index 62d0676..160e66b 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -25,6 +25,8 @@ #include #include +#include + #include #include @@ -118,6 +120,15 @@ static bool validUtf8(const char *string) return true; } +static std::string prefPath(const char *org, const char *app) +{ + char *path = SDL_GetPrefPath(org, app); + std::string str(path); + SDL_free(path); + + return str; +} + typedef std::vector StringVec; namespace po = boost::program_options; @@ -167,6 +178,8 @@ void Config::read(int argc, char *argv[]) PO_DESC(anyAltToggleFS, bool) \ PO_DESC(enableReset, bool) \ PO_DESC(allowSymlinks, bool) \ + PO_DESC(dataPathOrg, std::string) \ + PO_DESC(dataPathApp, std::string) \ PO_DESC(iconPath, std::string) \ PO_DESC(titleLanguage, std::string) \ PO_DESC(midi.soundFont, std::string) \ @@ -243,6 +256,11 @@ void Config::read(int argc, char *argv[]) rgssVersion = clamp(rgssVersion, 0, 3); SE.sourceCount = clamp(SE.sourceCount, 1, 64); + + if (!dataPathOrg.empty() && !dataPathApp.empty()) + customDataPath = prefPath(dataPathOrg.c_str(), dataPathApp.c_str()); + + commonDataPath = prefPath(".", "mkxp"); } static std::string baseName(const std::string &path) diff --git a/src/config.h b/src/config.h index 78d75f7..e7eb357 100644 --- a/src/config.h +++ b/src/config.h @@ -51,6 +51,9 @@ struct Config bool allowSymlinks; bool pathCache; + std::string dataPathOrg; + std::string dataPathApp; + std::string iconPath; std::string titleLanguage; @@ -82,6 +85,10 @@ struct Config std::string title; } game; + /* Internal */ + std::string customDataPath; + std::string commonDataPath; + Config(); void read(int argc, char *argv[]); diff --git a/src/eventthread.cpp b/src/eventthread.cpp index fec1e5b..6678772 100644 --- a/src/eventthread.cpp +++ b/src/eventthread.cpp @@ -29,6 +29,7 @@ #include "sharedstate.h" #include "graphics.h" +#include "settingsmenu.h" #include "debugwriter.h" #include @@ -37,7 +38,7 @@ bool EventThread::keyStates[] = { false }; EventThread::JoyState EventThread::joyState = { - 0, 0, { false } + { 0 }, { false } }; EventThread::MouseState EventThread::mouseState = @@ -108,6 +109,8 @@ void EventThread::process(RGSSThreadData &rtData) bool resetting = false; + SettingsMenu *sMenu = 0; + while (true) { if (!SDL_WaitEvent(&event)) @@ -116,6 +119,19 @@ void EventThread::process(RGSSThreadData &rtData) break; } + if (sMenu && sMenu->onEvent(event)) + { + if (sMenu->destroyReq()) + { + delete sMenu; + sMenu = 0; + + updateCursorState(cursorInWindow && windowFocused); + } + + continue; + } + switch (event.type) { case SDL_WINDOWEVENT : @@ -129,14 +145,14 @@ void EventThread::process(RGSSThreadData &rtData) case SDL_WINDOWEVENT_ENTER : cursorInWindow = true; mouseState.inWindow = true; - updateCursorState(cursorInWindow && windowFocused); + updateCursorState(cursorInWindow && windowFocused && !sMenu); break; case SDL_WINDOWEVENT_LEAVE : cursorInWindow = false; mouseState.inWindow = false; - updateCursorState(cursorInWindow && windowFocused); + updateCursorState(cursorInWindow && windowFocused && !sMenu); break; @@ -147,13 +163,13 @@ void EventThread::process(RGSSThreadData &rtData) case SDL_WINDOWEVENT_FOCUS_GAINED : windowFocused = true; - updateCursorState(cursorInWindow && windowFocused); + updateCursorState(cursorInWindow && windowFocused && !sMenu); break; case SDL_WINDOWEVENT_FOCUS_LOST : windowFocused = false; - updateCursorState(cursorInWindow && windowFocused); + updateCursorState(cursorInWindow && windowFocused && !sMenu); resetInputStates(); break; @@ -181,6 +197,17 @@ void EventThread::process(RGSSThreadData &rtData) break; } + if (event.key.keysym.scancode == SDL_SCANCODE_F1) + { + if (!sMenu) + { + sMenu = new SettingsMenu(rtData); + updateCursorState(false); + } + + sMenu->raise(); + } + if (event.key.keysym.scancode == SDL_SCANCODE_F2) { if (!fps.displaying) @@ -248,11 +275,7 @@ void EventThread::process(RGSSThreadData &rtData) break; case SDL_JOYAXISMOTION : - if (event.jaxis.axis == 0) - joyState.xAxis = event.jaxis.value; - else - joyState.yAxis = event.jaxis.value; - + joyState.axis[event.jaxis.axis] = event.jaxis.value; break; case SDL_JOYDEVICEADDED : @@ -333,6 +356,8 @@ void EventThread::process(RGSSThreadData &rtData) if (SDL_JoystickGetAttached(js)) SDL_JoystickClose(js); + + delete sMenu; } void EventThread::cleanup() diff --git a/src/eventthread.h b/src/eventthread.h index 013f60b..ec5e8bb 100644 --- a/src/eventthread.h +++ b/src/eventthread.h @@ -25,6 +25,7 @@ #include "config.h" #include "etc-internal.h" #include "sdl-util.h" +#include "keybindings.h" #include #include @@ -45,10 +46,8 @@ public: struct JoyState { - int xAxis; - int yAxis; - - bool buttons[16]; + int axis[256]; + bool buttons[256]; }; static JoyState joyState; @@ -155,6 +154,55 @@ struct WindowSizeNotify } }; +struct BindingNotify +{ + BindingNotify() + { + mut = SDL_CreateMutex(); + } + ~BindingNotify() + { + SDL_DestroyMutex(mut); + } + + bool poll(BDescVec &out) const + { + if (!changed) + return false; + + SDL_LockMutex(mut); + + out = data; + changed.clear(); + + SDL_UnlockMutex(mut); + + return true; + } + + void get(BDescVec &out) const + { + SDL_LockMutex(mut); + out = data; + SDL_UnlockMutex(mut); + } + + void post(const BDescVec &d) + { + SDL_LockMutex(mut); + + changed.set(); + data = d; + + SDL_UnlockMutex(mut); + } + +private: + SDL_mutex *mut; + BDescVec data; + mutable AtomicFlag changed; +}; + struct RGSSThreadData { /* Main thread sets this to request RGSS thread to terminate */ @@ -171,6 +219,7 @@ struct RGSSThreadData EventThread *ethread; WindowSizeNotify windowSizeMsg; + BindingNotify bindingUpdateMsg; const char *argv0; diff --git a/src/font.cpp b/src/font.cpp index 6a04316..ea8c736 100644 --- a/src/font.cpp +++ b/src/font.cpp @@ -195,6 +195,13 @@ bool SharedFontState::fontPresent(std::string family) return !(set.regular.empty() && set.other.empty()); } +_TTF_Font *SharedFontState::openBundled(int size) +{ + SDL_RWops *ops = openBundledFont(); + + return TTF_OpenFontRW(ops, 1, size); +} + struct FontPrivate { diff --git a/src/font.h b/src/font.h index 9320600..a37c935 100644 --- a/src/font.h +++ b/src/font.h @@ -49,6 +49,8 @@ public: bool fontPresent(std::string family); + static _TTF_Font *openBundled(int size); + private: SharedFontStatePrivate *p; }; diff --git a/src/input.cpp b/src/input.cpp index fd53698..1cae58a 100644 --- a/src/input.cpp +++ b/src/input.cpp @@ -22,6 +22,7 @@ #include "input.h" #include "sharedstate.h" #include "eventthread.h" +#include "keybindings.h" #include "exception.h" #include "util.h" @@ -30,6 +31,7 @@ #include #include +#include #define BUTTON_CODE_COUNT 24 @@ -52,12 +54,6 @@ struct KbBindingData Input::ButtonCode target; }; -struct JsBindingData -{ - int source; - Input::ButtonCode target; -}; - struct Binding { Binding(Input::ButtonCode target = Input::None) @@ -82,6 +78,15 @@ struct KbBinding : public Binding 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]; } @@ -100,11 +105,6 @@ struct JsButtonBinding : public Binding { JsButtonBinding() {} - JsButtonBinding(const JsBindingData &data) - : Binding(data.target), - source(data.source) - {} - bool sourceActive() const { return EventThread::joyState.buttons[source]; @@ -115,7 +115,7 @@ struct JsButtonBinding : public Binding return true; } - int source; + uint8_t source; }; /* Joystick axis binding */ @@ -123,17 +123,22 @@ struct JsAxisBinding : public Binding { JsAxisBinding() {} - JsAxisBinding(int *source, - int compareValue, + JsAxisBinding(uint8_t source, + AxisDir dir, Input::ButtonCode target) : Binding(target), source(source), - compareValue(compareValue) + dir(dir) {} bool sourceActive() const { - return (*source == compareValue); + int val = EventThread::joyState.axis[source]; + + if (dir == Negative) + return val < -JAXIS_THRESHOLD; + else /* dir == Positive */ + return val > JAXIS_THRESHOLD; } bool sourceRepeatable() const @@ -141,8 +146,8 @@ struct JsAxisBinding : public Binding return true; } - int *source; - int compareValue; + uint8_t source; + AxisDir dir; }; /* Mouse button binding */ @@ -172,10 +177,6 @@ struct MsBinding : public Binding /* Not rebindable */ static const KbBindingData staticKbBindings[] = { - { SDL_SCANCODE_LEFT, Input::Left }, - { SDL_SCANCODE_RIGHT, Input::Right }, - { SDL_SCANCODE_UP, Input::Up }, - { SDL_SCANCODE_DOWN, Input::Down }, { SDL_SCANCODE_LSHIFT, Input::Shift }, { SDL_SCANCODE_RSHIFT, Input::Shift }, { SDL_SCANCODE_LCTRL, Input::Ctrl }, @@ -191,61 +192,6 @@ static const KbBindingData staticKbBindings[] = static elementsN(staticKbBindings); -/* Rebindable */ -static const KbBindingData defaultKbBindings[] = -{ - { SDL_SCANCODE_SPACE, Input::C }, - { SDL_SCANCODE_RETURN, Input::C }, - { SDL_SCANCODE_KP_ENTER, Input::C }, /* Treated as alias of RETURN */ - { SDL_SCANCODE_ESCAPE, Input::B }, - { SDL_SCANCODE_KP_0, Input::B }, - { SDL_SCANCODE_LSHIFT, Input::A }, - { SDL_SCANCODE_RSHIFT, Input::A }, - { SDL_SCANCODE_X, Input::B }, - { SDL_SCANCODE_B, Input::None }, - { SDL_SCANCODE_D, Input::Z }, - { SDL_SCANCODE_Q, Input::L }, - { SDL_SCANCODE_W, Input::R }, - { SDL_SCANCODE_V, Input::None }, - { SDL_SCANCODE_A, Input::X }, - { SDL_SCANCODE_S, Input::Y } -}; - -/* RGSS1 */ -static const KbBindingData defaultKbBindings1[] = -{ - { SDL_SCANCODE_Z, Input::A }, - { SDL_SCANCODE_C, Input::C }, -}; - -/* RGSS2 and higher */ -static const KbBindingData defaultKbBindings2[] = -{ - { SDL_SCANCODE_Z, Input::C }, - { SDL_SCANCODE_C, Input::None }, -}; - -static elementsN(defaultKbBindings); -static elementsN(defaultKbBindings1); -static elementsN(defaultKbBindings2); - -/* Rebindable */ -static const JsBindingData defaultJsBindings[] = -{ - { 0, Input::A }, - { 1, Input::B }, - { 2, Input::C }, - { 3, Input::X }, - { 4, Input::Y }, - { 5, Input::Z }, - { 6, Input::L }, - { 7, Input::R }, - { 8, Input::None }, - { 9, Input::None } -}; - -static elementsN(defaultJsBindings); - /* Maps ButtonCode enum values to indices * in the button state array */ static const int mapToIndex[] = @@ -292,6 +238,7 @@ static const Input::ButtonCode otherDirs[4][3] = struct InputPrivate { + std::vector kbStatBindings; std::vector kbBindings; std::vector jsABindings; std::vector jsBBindings; @@ -320,12 +267,14 @@ struct InputPrivate } dir8Data; - InputPrivate() + InputPrivate(const RGSSThreadData &rtData) { - initKbBindings(); - initJsBindings(); + initStaticKbBindings(); initMsBindings(); + /* Main thread should have these posted by now */ + checkBindingChange(rtData); + states = stateArray; statesOld = stateArray + BUTTON_CODE_COUNT; @@ -378,51 +327,90 @@ struct InputPrivate memset(states, 0, size); } - void initKbBindings() + void checkBindingChange(const RGSSThreadData &rtData) { - kbBindings.clear(); + BDescVec d; - for (size_t i = 0; i < staticKbBindingsN; ++i) - kbBindings.push_back(KbBinding(staticKbBindings[i])); + if (!rtData.bindingUpdateMsg.poll(d)) + return; - for (size_t i = 0; i < defaultKbBindingsN; ++i) - kbBindings.push_back(KbBinding(defaultKbBindings[i])); - - if (rgssVer == 1) - for (size_t i = 0; i < defaultKbBindings1N; ++i) - kbBindings.push_back(KbBinding(defaultKbBindings1[i])); - else - for (size_t i = 0; i < defaultKbBindings2N; ++i) - kbBindings.push_back(KbBinding(defaultKbBindings2[i])); - - /* Add to binging array */ - for (size_t i = 0; i < kbBindings.size(); ++i) - bindings.push_back(&kbBindings[i]); + applyBindingDesc(d); } - void initJsBindings() + template + void appendBindings(std::vector &bind) { - /* Create axis bindings */ - jsABindings.resize(4); + for (size_t i = 0; i < bind.size(); ++i) + bindings.push_back(&bind[i]); + } - size_t i = 0; - jsABindings[i++] = JsAxisBinding(&EventThread::joyState.xAxis, 0x7FFF, Input::Right); - jsABindings[i++] = JsAxisBinding(&EventThread::joyState.xAxis, -0x8000, Input::Left); - jsABindings[i++] = JsAxisBinding(&EventThread::joyState.yAxis, 0x7FFF, Input::Down); - jsABindings[i++] = JsAxisBinding(&EventThread::joyState.yAxis, -0x8000, Input::Up); + void applyBindingDesc(const BDescVec &d) + { + kbBindings.clear(); + jsABindings.clear(); + jsBBindings.clear(); - /* Create button bindings */ - jsBBindings.resize(defaultJsBindingsN); + for (size_t i = 0; i < d.size(); ++i) + { + const BindingDesc &desc = d[i]; + const SourceDesc &src = desc.src; - for (size_t i = 0; i < defaultJsBindingsN; ++i) - jsBBindings[i] = JsButtonBinding(defaultJsBindings[i]); + if (desc.target == Input::None) + continue; - /* Add to binging array */ - for (size_t i = 0; i < jsABindings.size(); ++i) - bindings.push_back(&jsABindings[i]); + switch (desc.src.type) + { + case Invalid : + break; + case Key : + { + KbBinding bind; + bind.source = src.d.scan; + bind.target = desc.target; + kbBindings.push_back(bind); - for (size_t i = 0; i < jsBBindings.size(); ++i) - bindings.push_back(&jsBBindings[i]); + 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 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(jsABindings); + appendBindings(jsBBindings); + } + + void initStaticKbBindings() + { + kbStatBindings.clear(); + + for (size_t i = 0; i < staticKbBindingsN; ++i) + kbStatBindings.push_back(KbBinding(staticKbBindings[i])); } void initMsBindings() @@ -433,10 +421,6 @@ struct InputPrivate msBindings[i++] = MsBinding(SDL_BUTTON_LEFT, Input::MouseLeft); msBindings[i++] = MsBinding(SDL_BUTTON_MIDDLE, Input::MouseMiddle); msBindings[i++] = MsBinding(SDL_BUTTON_RIGHT, Input::MouseRight); - - /* Add to binding array */ - for (size_t i = 0; i < msBindings.size(); ++i) - bindings.push_back(&msBindings[i]); } void pollBindings(Input::ButtonCode &repeatCand) @@ -564,14 +548,15 @@ struct InputPrivate }; -Input::Input() +Input::Input(const RGSSThreadData &rtData) { - p = new InputPrivate; + p = new InputPrivate(rtData); } void Input::update() { shState->checkShutdown(); + p->checkBindingChange(shState->rtData()); p->swapBuffers(); p->clearBuffer(); diff --git a/src/input.h b/src/input.h index 2a4e501..5448338 100644 --- a/src/input.h +++ b/src/input.h @@ -23,6 +23,7 @@ #define INPUT_H struct InputPrivate; +struct RGSSThreadData; class Input { @@ -59,7 +60,7 @@ public: int mouseY(); private: - Input(); + Input(const RGSSThreadData &rtData); ~Input(); friend struct SharedStatePrivate; diff --git a/src/keybindings.cpp b/src/keybindings.cpp new file mode 100644 index 0000000..a867b79 --- /dev/null +++ b/src/keybindings.cpp @@ -0,0 +1,311 @@ +/* +** keybindings.cpp +** +** This file is part of mkxp. +** +** Copyright (C) 2014 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 "keybindings.h" + +#include "config.h" +#include "util.h" + +#include + +struct KbBindingData +{ + SDL_Scancode source; + Input::ButtonCode target; + + void add(BDescVec &d) const + { + SourceDesc src; + src.type = Key; + src.d.scan = source; + + BindingDesc desc; + desc.src = src; + desc.target = target; + + d.push_back(desc); + } +}; + +struct JsBindingData +{ + int source; + Input::ButtonCode target; + + void add(BDescVec &d) const + { + SourceDesc src; + src.type = JButton; + src.d.jb = source; + + BindingDesc desc; + desc.src = src; + desc.target = target; + + d.push_back(desc); + } +}; + +/* Common */ +static const KbBindingData defaultKbBindings[] = +{ + { SDL_SCANCODE_LEFT, Input::Left }, + { SDL_SCANCODE_RIGHT, Input::Right }, + { SDL_SCANCODE_UP, Input::Up }, + { SDL_SCANCODE_DOWN, Input::Down }, + { SDL_SCANCODE_SPACE, Input::C }, + { SDL_SCANCODE_RETURN, Input::C }, + { SDL_SCANCODE_ESCAPE, Input::B }, + { SDL_SCANCODE_KP_0, Input::B }, + { SDL_SCANCODE_LSHIFT, Input::A }, + { SDL_SCANCODE_X, Input::B }, + { SDL_SCANCODE_D, Input::Z }, + { SDL_SCANCODE_Q, Input::L }, + { SDL_SCANCODE_W, Input::R }, + { SDL_SCANCODE_A, Input::X }, + { SDL_SCANCODE_S, Input::Y } +}; + +/* RGSS1 */ +static const KbBindingData defaultKbBindings1[] = +{ + { SDL_SCANCODE_Z, Input::A }, + { SDL_SCANCODE_C, Input::C }, +}; + +/* RGSS2 and higher */ +static const KbBindingData defaultKbBindings2[] = +{ + { SDL_SCANCODE_Z, Input::C } +}; + +static elementsN(defaultKbBindings); +static elementsN(defaultKbBindings1); +static elementsN(defaultKbBindings2); + +static const JsBindingData defaultJsBindings[] = +{ + { 0, Input::A }, + { 1, Input::B }, + { 2, Input::C }, + { 3, Input::X }, + { 4, Input::Y }, + { 5, Input::Z }, + { 6, Input::L }, + { 7, Input::R } +}; + +static elementsN(defaultJsBindings); + +static void addAxisBinding(BDescVec &d, uint8_t axis, AxisDir dir, Input::ButtonCode target) +{ + SourceDesc src; + src.type = JAxis; + src.d.ja.axis = axis; + src.d.ja.dir = dir; + + BindingDesc desc; + desc.src = src; + desc.target = target; + + d.push_back(desc); +} + +BDescVec genDefaultBindings(const Config &conf) +{ + BDescVec d; + + for (size_t i = 0; i < defaultKbBindingsN; ++i) + defaultKbBindings[i].add(d); + + if (conf.rgssVersion == 1) + for (size_t i = 0; i < defaultKbBindings1N; ++i) + defaultKbBindings1[i].add(d); + else + for (size_t i = 0; i < defaultKbBindings2N; ++i) + defaultKbBindings2[i].add(d); + + for (size_t i = 0; i < defaultJsBindingsN; ++i) + defaultJsBindings[i].add(d); + + addAxisBinding(d, 0, Negative, Input::Left ); + addAxisBinding(d, 0, Positive, Input::Right); + addAxisBinding(d, 1, Negative, Input::Up ); + addAxisBinding(d, 1, Positive, Input::Down ); + + return d; +} + +#define FORMAT_VER 1 + +struct Header +{ + uint32_t formVer; + uint32_t rgssVer; + uint32_t count; +}; + +static void buildPath(const std::string &dir, uint32_t rgssVersion, + char *out, size_t outSize) +{ + snprintf(out, outSize, "%s/keybindings.mkxp%u", dir.c_str(), rgssVersion); +} + +static bool writeBindings(const BDescVec &d, const std::string &dir, + uint32_t rgssVersion) +{ + if (dir.empty()) + return false; + + char path[1024]; + buildPath(dir, rgssVersion, path, sizeof(path)); + + FILE *f = fopen(path, "w"); + + if (!f) + return false; + + Header hd; + hd.formVer = FORMAT_VER; + hd.rgssVer = rgssVersion; + hd.count = d.size(); + + if (fwrite(&hd, sizeof(hd), 1, f) < 1) + { + fclose(f); + return false; + } + + if (fwrite(&d[0], sizeof(d[0]), hd.count, f) < hd.count) + { + fclose(f); + return false; + } + + fclose(f); + return true; +} + +void storeBindings(const BDescVec &d, const Config &conf) +{ + if (writeBindings(d, conf.customDataPath, conf.rgssVersion)) + return; + + writeBindings(d, conf.commonDataPath, conf.rgssVersion); +} + +#define READ(ptr, size, n, f) if (fread(ptr, size, n, f) < n) return false + +static bool verifyDesc(const BindingDesc &desc) +{ + const Input::ButtonCode codes[] = + { + Input::None, + Input::Down, Input::Left, Input::Right, Input::Up, + Input::A, Input::B, Input::C, + Input::X, Input::Y, Input::Z, + Input::L, Input::R, + Input::Shift, Input::Ctrl, Input::Alt, + Input::F5, Input::F6, Input::F7, Input::F8, Input::F9 + }; + + elementsN(codes); + size_t i; + + for (i = 0; i < codesN; ++i) + if (desc.target == codes[i]) + break; + + if (i == codesN) + return false; + + const SourceDesc &src = desc.src; + + switch (src.type) + { + case Invalid: + return true; + case Key: + return src.d.scan < SDL_NUM_SCANCODES; + case JButton: + return true; + case JAxis: + return src.d.ja.dir == Negative || src.d.ja.dir == Positive; + default: + return false; + } +} + +static bool readBindings(BDescVec &out, const std::string &dir, + uint32_t rgssVersion) +{ + if (dir.empty()) + return false; + + char path[1024]; + buildPath(dir, rgssVersion, path, sizeof(path)); + + FILE *f = fopen(path, "r"); + + if (!f) + return false; + + Header hd; + if (fread(&hd, sizeof(hd), 1, f) < 1) + { + fclose(f); + return false; + } + + if (hd.formVer != FORMAT_VER) + return false; + if (hd.rgssVer != rgssVersion) + return false; + /* Arbitrary max value */ + if (hd.count > 1024) + return false; + + out.resize(hd.count); + if (fread(&out[0], sizeof(out[0]), hd.count, f) < hd.count) + { + fclose(f); + return false; + } + + for (size_t i = 0; i < hd.count; ++i) + if (!verifyDesc(out[i])) + return false; + + return true; +} + +BDescVec loadBindings(const Config &conf) +{ + BDescVec d; + + if (readBindings(d, conf.customDataPath, conf.rgssVersion)) + return d; + + if (readBindings(d, conf.commonDataPath, conf.rgssVersion)) + return d; + + return genDefaultBindings(conf); +} diff --git a/src/keybindings.h b/src/keybindings.h new file mode 100644 index 0000000..9bd6c43 --- /dev/null +++ b/src/keybindings.h @@ -0,0 +1,108 @@ +/* +** keybindings.h +** +** This file is part of mkxp. +** +** Copyright (C) 2014 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 . +*/ + +#ifndef KEYBINDINGS_H +#define KEYBINDINGS_H + +#include "input.h" + +#include +#include +#include +#include + +enum AxisDir +{ + Negative, + Positive +}; + +enum SourceType +{ + Invalid, + Key, + JButton, + JAxis +}; + +struct SourceDesc +{ + SourceType type; + + union Data + { + /* Keyboard scancode */ + SDL_Scancode scan; + /* Joystick button index */ + uint8_t jb; + struct + { + /* Joystick axis index */ + uint8_t axis; + /* Joystick axis direction */ + AxisDir dir; + } ja; + } d; + + bool operator==(const SourceDesc &o) const + { + if (type != o.type) + return false; + + switch (type) + { + case Invalid: + return true; + case Key: + return d.scan == o.d.scan; + case JButton: + return d.jb == o.d.jb; + case JAxis: + return (d.ja.axis == o.d.ja.axis) && (d.ja.dir == o.d.ja.dir); + default: + assert(!"unreachable"); + return false; + } + } + + bool operator!=(const SourceDesc &o) const + { + return !(*this == o); + } +}; + +#define JAXIS_THRESHOLD 0x4000 + +struct BindingDesc +{ + SourceDesc src; + Input::ButtonCode target; +}; + +typedef std::vector BDescVec; +struct Config; + +BDescVec genDefaultBindings(const Config &conf); + +void storeBindings(const BDescVec &d, const Config &conf); +BDescVec loadBindings(const Config &conf); + +#endif // KEYBINDINGS_H diff --git a/src/main.cpp b/src/main.cpp index ae3def8..8c840ec 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -238,7 +238,7 @@ int main(int argc, char *argv[]) SDL_SetHint("SDL_VIDEO_MINIMIZE_ON_FOCUS_LOSS", "0"); SDL_Window *win; - Uint32 winFlags = SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN; + Uint32 winFlags = SDL_WINDOW_OPENGL | SDL_WINDOW_INPUT_FOCUS; if (conf.winResizable) winFlags |= SDL_WINDOW_RESIZABLE; @@ -268,6 +268,9 @@ int main(int argc, char *argv[]) EventThread eventThread; RGSSThreadData rtData(&eventThread, argv[0], win, conf); + /* Load and post key bindings */ + rtData.bindingUpdateMsg.post(loadBindings(conf)); + /* Start RGSS thread */ SDL_Thread *rgssThread = SDL_CreateThread(rgssThreadFun, "rgss", &rtData); @@ -310,6 +313,11 @@ int main(int argc, char *argv[]) /* Clean up any remainin events */ eventThread.cleanup(); + /* Store key bindings */ + BDescVec keyBinds; + rtData.bindingUpdateMsg.get(keyBinds); + storeBindings(keyBinds, rtData.config); + Debug() << "Shutting down."; SDL_DestroyWindow(win); diff --git a/src/settingsmenu.cpp b/src/settingsmenu.cpp new file mode 100644 index 0000000..99bfe51 --- /dev/null +++ b/src/settingsmenu.cpp @@ -0,0 +1,1112 @@ +/* +** settingsmenu.cpp +** +** This file is part of mkxp. +** +** Copyright (C) 2014 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 "settingsmenu.h" + +#include +#include +#include +#include +#include + +#include "keybindings.h" +#include "eventthread.h" +#include "font.h" +#include "input.h" +#include "etc-internal.h" +#include "util.h" + +#include +#include + +const Vec2i winSize(540, 356); + +const uint8_t cBgNorm = 50; +const uint8_t cBgDark = 20; +const uint8_t cLine = 0; +const uint8_t cText = 255; + +const uint8_t frameWidth = 4; +const uint8_t fontSize = 15; + +static bool pointInRect(const SDL_Rect &r, int x, int y) +{ + return (x >= r.x && x <= r.x+r.w && y >= r.y && y <= r.y+r.h); +} + +typedef SettingsMenuPrivate SMP; + +#define BTN_STRING(btn) { Input:: btn, #btn } +struct VButton +{ + Input::ButtonCode code; + const char *str; +} static const vButtons[] = +{ + BTN_STRING(Up), + BTN_STRING(Down), + BTN_STRING(L), + BTN_STRING(Left), + BTN_STRING(Right), + BTN_STRING(R), + BTN_STRING(A), + BTN_STRING(B), + BTN_STRING(C), + BTN_STRING(X), + BTN_STRING(Y), + BTN_STRING(Z) +}; + +static elementsN(vButtons); + +/* Human readable string representation */ +std::string sourceDescString(const SourceDesc &src) +{ + char buf[128]; + + switch (src.type) + { + case Invalid: + return std::string(); + + case Key: + { + if (src.d.scan == SDL_SCANCODE_LSHIFT) + return "Shift"; + + SDL_Keycode key = SDL_GetKeyFromScancode(src.d.scan); + const char *str = SDL_GetKeyName(key); + + if (*str == '\0') + return "Unknown key"; + else + return str; + } + case JButton: + snprintf(buf, sizeof(buf), "JS %d", src.d.jb); + return buf; + + case JAxis: + snprintf(buf, sizeof(buf), "Axis %d%c", + src.d.ja.axis, src.d.ja.dir == Negative ? '-' : '+'); + return buf; + } + + assert(!"unreachable"); + return ""; +} + +struct Widget +{ + /* Widgets have a static size and position, + * defined at creation */ + Widget(SMP *p, const IntRect &rect); + + /* Public methods take coordinates in global + * window coordinates */ + bool hit(int x, int y); + void draw(SDL_Surface *surf); + void motion(int x, int y); + void leave(); + void click(int x, int y, uint8_t button); + +protected: + SMP *p; + IntRect rect; + + /* Protected abstract methods are called with + * widget-local coordinates */ + virtual void drawHandler(SDL_Surface *surf) = 0; + virtual void motionHandler(int x, int y) = 0; + virtual void leaveHandler() = 0; + virtual void clickHandler(int x, int y, uint8_t button) = 0; +}; + +struct BindingWidget : Widget +{ + VButton vb; + /* Source slots */ + SourceDesc src[4]; + /* Flag indicating whether a slot source is used + * for multiple button targets (red indicator) */ + bool dupFlag[4]; + + BindingWidget(int vbIndex, SMP *p, const IntRect &rect) + : Widget(p, rect), + vb(vButtons[vbIndex]), + hoveredCell(-1) + {} + + void appendBindings(BDescVec &d) const; + +protected: + int hoveredCell; + void setHoveredCell(int cell); + /* Get the slot cell index that contains (x,y), + * or -1 if none */ + int cellIndex(int x, int y) const; + + void drawHandler(SDL_Surface *surf); + void motionHandler(int x, int y); + void leaveHandler(); + void clickHandler(int x, int y, uint8_t button); +}; + +struct Button : Widget +{ + typedef void (SMP::*Callback)(); + + const char *str; + Callback cb; + + Button(SMP *p, const IntRect &rect, + const char *str, Callback cb) + : Widget(p, rect), + str(str), cb(cb), hovered(false) + {} + +protected: + bool hovered; + + void setHovered(bool val); + + void drawHandler(SDL_Surface *surf); + void motionHandler(int, int); + void leaveHandler(); + void clickHandler(int, int, uint8_t button); +}; + +struct Label : Widget +{ + const char *str; + SDL_Color c; + + Label() : Widget(0, IntRect()) {} + + Label(SMP *p, const IntRect &rect, + const char *str, uint8_t r, uint8_t g, uint8_t b) + : Widget(p, rect), + str(str) + { + c.r = r; + c.g = g; + c.b = b; + c.a = 255; + } + + void setVisible(bool val); + +protected: + bool visible; + + void drawHandler(SDL_Surface *surf); + void motionHandler(int, int) {} + void leaveHandler() {} + void clickHandler(int, int, uint8_t) {} +}; + +enum State +{ + Idle, + AwaitingInput +}; + +enum Justification +{ + Left, + Center +}; + +struct SettingsMenuPrivate +{ + State state; + + /* Necessary to decide which window gets to + * process joystick events */ + bool hasFocus; + + /* Tell the outer EventThread to destroy us */ + bool destroyReq; + + /* Offset added for all draw calls */ + Vec2i drawOff; + + SDL_Window *window; + SDL_Surface *winSurf; + uint32_t winID; + + TTF_Font *font; + SDL_PixelFormat *rgb; + + RGSSThreadData &rtData; + + std::vector bWidgets; + std::vector