931 lines
21 KiB
C++
931 lines
21 KiB
C++
/*
|
|
** settingsmenu.cpp
|
|
**
|
|
** This file is part of mkxp.
|
|
**
|
|
** Copyright (C) 2014 Jonas Kulla <Nyocurio@gmail.com>
|
|
**
|
|
** 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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "settingsmenu.h"
|
|
|
|
#include <SDL.h>
|
|
#include <SDL_video.h>
|
|
#include <SDL_keyboard.h>
|
|
|
|
#include "keybindings.h"
|
|
#include "eventthread.h"
|
|
#include "font.h"
|
|
#include "input.h"
|
|
#include "etc-internal.h"
|
|
#include "util.h"
|
|
#include "gl-fun.h"
|
|
#include "bundledfont.h"
|
|
#include "eventthread.h"
|
|
|
|
#include "imgui/imgui.h"
|
|
#include "imgui/imgui_impl_sdl.h"
|
|
|
|
#include <algorithm>
|
|
#include <assert.h>
|
|
|
|
const Vec2i winSize(740, 400);
|
|
|
|
const float fontSize = 16.0f;
|
|
|
|
const ImVec4 colButton = ImColor(96,96,96);
|
|
const ImVec4 colButtonHover = ImColor(51,51,51);
|
|
const ImVec4 colBackground = ImColor(128,128,128);
|
|
|
|
const uint8_t numCols = 3;
|
|
const uint8_t numRows = 4;
|
|
|
|
typedef SettingsMenuPrivate SMP;
|
|
|
|
#define BTN_STRING(btn,desc) { Input:: btn, #desc }
|
|
struct VButton
|
|
{
|
|
Input::ButtonCode code;
|
|
const char *str;
|
|
} static const vButtons[] =
|
|
{
|
|
BTN_STRING(Up,Up),
|
|
BTN_STRING(Down,Down),
|
|
BTN_STRING(L,L),
|
|
BTN_STRING(Left,Left),
|
|
BTN_STRING(Right,Right),
|
|
BTN_STRING(R,W-Atk),
|
|
BTN_STRING(A,Dismount),
|
|
BTN_STRING(B,Cancel),
|
|
BTN_STRING(C,Confirm),
|
|
BTN_STRING(X,A-Atk),
|
|
BTN_STRING(Y,S-Atk),
|
|
BTN_STRING(Z,D-Atk)
|
|
};
|
|
|
|
static elementsN(vButtons);
|
|
|
|
/* Macros to read/write central config and check for changed values */
|
|
#define STORE_CONFIG(key) rtData.config. key = tempConfig. key; rtData.config.store(#key, rtData.config. key )
|
|
#define VALUE_CHANGED(key) (rtData.config. key != tempConfig. key)
|
|
|
|
/* Holds configurables that can be modified in the settings menu
|
|
/* until they get all written out the config file and applied */
|
|
struct Configurables
|
|
{
|
|
bool fullscreen;
|
|
bool fixedAspectRatio;
|
|
bool smoothScaling;
|
|
bool vsync;
|
|
int defScreenW;
|
|
int defScreenH;
|
|
bool frameSkip;
|
|
bool solidFonts;
|
|
|
|
Configurables()
|
|
{
|
|
}
|
|
|
|
Configurables(Config &c)
|
|
{
|
|
fullscreen = c.fullscreen;
|
|
fixedAspectRatio = c.fixedAspectRatio;
|
|
smoothScaling = c.smoothScaling;
|
|
vsync = c.vsync;
|
|
defScreenW = c.defScreenW;
|
|
defScreenH = c.defScreenH;
|
|
frameSkip = c.frameSkip;
|
|
solidFonts = c.solidFonts;
|
|
}
|
|
|
|
} static tempConfig;
|
|
|
|
/* Human readable string representation */
|
|
std::string sourceDescString(const SourceDesc &src)
|
|
{
|
|
char buf[128];
|
|
char pos;
|
|
|
|
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 JHat:
|
|
switch(src.d.jh.pos)
|
|
{
|
|
case SDL_HAT_UP:
|
|
pos = 'U';
|
|
break;
|
|
|
|
case SDL_HAT_DOWN:
|
|
pos = 'D';
|
|
break;
|
|
|
|
case SDL_HAT_LEFT:
|
|
pos = 'L';
|
|
break;
|
|
|
|
case SDL_HAT_RIGHT:
|
|
pos = 'R';
|
|
break;
|
|
|
|
default:
|
|
pos = '-';
|
|
}
|
|
snprintf(buf, sizeof(buf), "Hat %d:%c",
|
|
src.d.jh.hat, pos);
|
|
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 BindingWidget
|
|
{
|
|
SMP *p;
|
|
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)
|
|
: vb(vButtons[vbIndex]), p(p)
|
|
{}
|
|
|
|
void appendBindings(BDescVec &d) const;
|
|
void click(SourceDesc& desc);
|
|
void displayWidget(uint32_t width, uint32_t height);
|
|
};
|
|
|
|
enum State
|
|
{
|
|
Idle,
|
|
AwaitingInput
|
|
};
|
|
|
|
static bool resCheckbox[3];
|
|
|
|
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;
|
|
|
|
/* Set to true if there are any duplicate bindings */
|
|
bool dupWarn;
|
|
|
|
SDL_Window *window;
|
|
SDL_GLContext glContext;
|
|
uint32_t winID;
|
|
|
|
enum tabs
|
|
{
|
|
CONTROLS,
|
|
GRAPHICS
|
|
};
|
|
|
|
enum tabs currentTab;
|
|
|
|
RGSSThreadData &rtData;
|
|
|
|
std::vector<BindingWidget> bWidgets;
|
|
|
|
SourceDesc *captureDesc;
|
|
const char *captureName;
|
|
|
|
SettingsMenuPrivate(RGSSThreadData &rtData)
|
|
: rtData(rtData)
|
|
{
|
|
}
|
|
|
|
void setupBindingData(const BDescVec &d)
|
|
{
|
|
size_t slotI[vButtonsN] = { 0 };
|
|
|
|
for (size_t i = 0; i < bWidgets.size(); ++i)
|
|
for (size_t j = 0; j < 4; ++j)
|
|
bWidgets[i].src[j].type = Invalid;
|
|
|
|
for (size_t i = 0; i < d.size(); ++i)
|
|
{
|
|
const BindingDesc &desc = d[i];
|
|
const Input::ButtonCode trg = desc.target;
|
|
|
|
size_t j;
|
|
for (j = 0; j < vButtonsN; ++j)
|
|
if (bWidgets[j].vb.code == trg)
|
|
break;
|
|
|
|
assert(j < vButtonsN);
|
|
|
|
size_t &slot = slotI[j];
|
|
BindingWidget &w = bWidgets[j];
|
|
|
|
if (slot == 4)
|
|
continue;
|
|
|
|
w.src[slot++] = desc.src;
|
|
}
|
|
}
|
|
|
|
void updateDuplicateStatus()
|
|
{
|
|
for (size_t i = 0; i < bWidgets.size(); ++i)
|
|
for (size_t j = 0; j < 4; ++j)
|
|
bWidgets[i].dupFlag[j] = false;
|
|
|
|
dupWarn = false;
|
|
|
|
for (size_t i = 0; i < bWidgets.size(); ++i)
|
|
{
|
|
for (size_t j = 0; j < 4; ++j)
|
|
{
|
|
const SourceDesc &src = bWidgets[i].src[j];
|
|
|
|
if (src.type == Invalid)
|
|
continue;
|
|
|
|
for (size_t k = 0; k < bWidgets.size(); ++k)
|
|
{
|
|
if (k == i)
|
|
continue;
|
|
|
|
for (size_t l = 0; l < 4; ++l)
|
|
{
|
|
if (bWidgets[k].src[l] != src)
|
|
continue;
|
|
|
|
bWidgets[i].dupFlag[j] = true;
|
|
bWidgets[k].dupFlag[l] = true;
|
|
dupWarn = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void redraw()
|
|
{
|
|
ImGui_ImplSdl_NewFrame(window);
|
|
{
|
|
ImGui::SetNextWindowSize(ImVec2((float)winSize.x,(float)winSize.y));
|
|
ImGui::SetNextWindowPos(ImVec2(0, 0));
|
|
|
|
ImGuiWindowFlags WindowFlags = 0;
|
|
WindowFlags |= ImGuiWindowFlags_NoTitleBar;
|
|
WindowFlags |= ImGuiWindowFlags_NoResize;
|
|
WindowFlags |= ImGuiWindowFlags_NoMove;
|
|
WindowFlags |= ImGuiWindowFlags_NoScrollbar;
|
|
WindowFlags |= ImGuiWindowFlags_NoCollapse;
|
|
WindowFlags |= ImGuiWindowFlags_NoScrollWithMouse;
|
|
WindowFlags |= ImGuiWindowFlags_NoSavedSettings;
|
|
|
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0);
|
|
|
|
ImGui::PushStyleColor(ImGuiCol_Button, colButton);
|
|
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, colButtonHover);
|
|
ImGui::PushStyleColor(ImGuiCol_ButtonActive, colButtonHover);
|
|
ImGui::PushStyleColor(ImGuiCol_WindowBg, colBackground);
|
|
|
|
bool bTrue = true;
|
|
ImGui::Begin("Container", &bTrue, WindowFlags);
|
|
|
|
tabSelector("Controls", CONTROLS);
|
|
ImGui::SameLine();
|
|
ImGui::Text("|");
|
|
ImGui::SameLine();
|
|
if(tabSelector("Graphics", GRAPHICS))
|
|
{
|
|
tempConfig = Configurables(rtData.config);
|
|
}
|
|
|
|
ImGui::Separator();
|
|
|
|
switch(currentTab)
|
|
{
|
|
case CONTROLS:
|
|
displayControllerTab();
|
|
break;
|
|
|
|
case GRAPHICS:
|
|
displayGraphicsTab();
|
|
break;
|
|
}
|
|
ImGui::End();
|
|
ImGui::PopStyleColor(4);
|
|
ImGui::PopStyleVar();
|
|
}
|
|
ImGui::Render();
|
|
SDL_GL_SwapWindow(window);
|
|
}
|
|
|
|
bool tabSelector(const char * tabName, tabs tabId)
|
|
{
|
|
if (ImGui::Selectable(tabName, currentTab == tabId, 0, ImGui::CalcTextSize(tabName)) && (state == Idle))
|
|
{
|
|
currentTab = tabId;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void displayControllerTab()
|
|
{
|
|
ImVec4 red = ImColor(255, 0, 0);
|
|
if(state == AwaitingInput)
|
|
{
|
|
ImGui::Dummy(ImVec2(0, ImGui::GetWindowContentRegionMax().y/2 - fontSize/2));
|
|
ImGui::Text("Press key or joystick button for \"%s\"", captureName);
|
|
}
|
|
else
|
|
{
|
|
/* Header Text */
|
|
ImGui::Text("Use left click to bind a slot, right click to clear its binding");
|
|
if(dupWarn)
|
|
{
|
|
ImGui::TextColored(red, "Warning: Same physical key bound to multiple slots");
|
|
}
|
|
|
|
/* Button Assignment Widgets */
|
|
uint32_t widgetWidth = (ImGui::GetWindowContentRegionMax().x-ImGui::GetStyle().WindowPadding.x) / numCols;
|
|
uint32_t widgetHeight = 64;
|
|
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(2, 2));
|
|
ImGui::PushStyleColor(ImGuiCol_ChildWindowBg, ImColor(0, 0, 0));
|
|
ImGui::PushStyleColor(ImGuiCol_Button, colBackground);
|
|
ImGui::BeginChild("Table", ImVec2(numCols*widgetWidth+8, numRows*widgetHeight+2));
|
|
ImGui::Spacing();
|
|
|
|
int i = 0;
|
|
for(int y = 0; y < numRows; y++)
|
|
{
|
|
ImGui::Dummy(ImVec2(0, 0));
|
|
for(int x = 0; x < numCols; x++)
|
|
{
|
|
ImGui::SameLine();
|
|
bWidgets[i].displayWidget(widgetWidth, widgetHeight);
|
|
i++;
|
|
}
|
|
}
|
|
|
|
ImGui::EndChild();
|
|
ImGui::PopStyleColor(2);
|
|
ImGui::PopStyleVar();
|
|
|
|
ImGui::Spacing();
|
|
ImGui::Separator();
|
|
ImGui::Spacing();
|
|
|
|
/* Bottom Buttons */
|
|
ImVec2 btnDim = ImVec2(100, 24);
|
|
if(ImGui::Button("Reset Default", btnDim))
|
|
{
|
|
onResetToDefault();
|
|
}
|
|
ImGui::SameLine();
|
|
ImGui::Dummy(ImVec2(ImGui::GetWindowContentRegionMax().x - ImGui::GetStyle().WindowPadding.x - 3*btnDim.x - 2*ImGui::GetStyle().ItemSpacing.x, btnDim.y));
|
|
ImGui::SameLine();
|
|
|
|
if(ImGui::Button("Cancel", btnDim))
|
|
{
|
|
onCancel();
|
|
}
|
|
ImGui::SameLine();
|
|
|
|
if(ImGui::Button("Store", btnDim))
|
|
{
|
|
onAccept();
|
|
}
|
|
}
|
|
}
|
|
|
|
static inline bool resolutionEqualsN(int* x, int* y, int n)
|
|
{
|
|
return ((x[0] == n*y[0]) && (x[1] == n*y[1]));
|
|
}
|
|
|
|
static inline bool TextCheckbox(const char* str_id, bool active, bool &hovered, const ImVec2 &size)
|
|
{
|
|
bool result;
|
|
ImVec2 innerPadding = ImGui::GetStyle().FramePadding;
|
|
ImVec2 innerSize = ImVec2(size.x-2*innerPadding.x, size.y-2*innerPadding.y);
|
|
ImGuiID id = ImGui::GetID(str_id);
|
|
|
|
/* Set button colors to match checkbox */
|
|
if(active)
|
|
ImGui::PushStyleColor(ImGuiCol_Button, ImGui::GetStyle().Colors[ImGuiCol_CheckMark]);
|
|
else
|
|
ImGui::PushStyleColor(ImGuiCol_Button, ImColor(0, 0, 0, 0));
|
|
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImGui::GetStyle().Colors[ImGuiCol_Button]);
|
|
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImGui::GetStyle().Colors[ImGuiCol_CheckMark]);
|
|
|
|
/* Was item hovered in the previous frame? */
|
|
if(hovered)
|
|
ImGui::PushStyleColor(ImGuiCol_FrameBg, ImGui::GetStyle().Colors[ImGuiCol_FrameBgHovered]);
|
|
ImGui::BeginChildFrame(id, size);
|
|
if(hovered)
|
|
ImGui::PopStyleColor();
|
|
hovered = ImGui::IsWindowHovered();
|
|
|
|
/* Align button properly in child window */
|
|
ImVec2 pos = ImGui::GetWindowPos();
|
|
pos.x += innerPadding.x;
|
|
pos.y += innerPadding.y;
|
|
ImGui::SetWindowPos(pos);
|
|
result = ImGui::Button(str_id, innerSize);
|
|
ImGui::EndChildFrame();
|
|
ImGui::PopStyleColor(3);
|
|
return result;
|
|
}
|
|
|
|
static inline void TextCentered(const char* str_id, const ImVec2 &size)
|
|
{
|
|
ImGui::PushStyleColor(ImGuiCol_Button, ImGui::GetStyle().Colors[ImGuiCol_WindowBg]);
|
|
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImGui::GetStyle().Colors[ImGuiCol_WindowBg]);
|
|
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImGui::GetStyle().Colors[ImGuiCol_WindowBg]);
|
|
ImGui::Button(str_id, size);
|
|
ImGui::PopStyleColor(3);
|
|
}
|
|
|
|
void displayGraphicsTab()
|
|
{
|
|
if(ImGui::CollapsingHeader("Display Settings", 0, true, true))
|
|
{
|
|
/* current resolution and native rendering resolution */
|
|
int *res = &tempConfig.defScreenW;
|
|
int native[2] = {(rtData.config.rgssVersion == 1 ? 640 : 544),
|
|
(rtData.config.rgssVersion == 1 ? 480 : 416)};
|
|
|
|
if(ImGui::InputInt2("Window Size", res))
|
|
{
|
|
/* clamp to between 320x240 and 4K resolutions */
|
|
tempConfig.defScreenW = std::min(std::max(tempConfig.defScreenW, 320),4096);
|
|
tempConfig.defScreenH = std::min(std::max(tempConfig.defScreenH, 240),2160);
|
|
}
|
|
if(TextCheckbox("1X native", resolutionEqualsN(res, native, 1), resCheckbox[0], ImVec2(80, 24)))
|
|
{
|
|
res[0] = native[0];
|
|
res[1] = native[1];
|
|
}
|
|
ImGui::SameLine();
|
|
if(TextCheckbox("2X native", resolutionEqualsN(res, native, 2), resCheckbox[1], ImVec2(80, 24)))
|
|
{
|
|
res[0] = 2*native[0];
|
|
res[1] = 2*native[1];
|
|
}
|
|
ImGui::SameLine();
|
|
if(TextCheckbox("3X native", resolutionEqualsN(res, native, 3), resCheckbox[2], ImVec2(80, 24)))
|
|
{
|
|
res[0] = 3*native[0];
|
|
res[1] = 3*native[1];
|
|
}
|
|
ImGui::SameLine();
|
|
TextCentered("Recommended if no smooth upscaling.", ImVec2(0, 24));
|
|
ImGui::Checkbox("Start in fullscreen", &tempConfig.fullscreen);
|
|
ImGui::SameLine();
|
|
ImGui::Checkbox("Keep aspect ratio", &tempConfig.fixedAspectRatio);
|
|
}
|
|
|
|
ImGui::Dummy(ImVec2(0, 48));
|
|
|
|
if(ImGui::CollapsingHeader("Quality Settings", 0, true, true))
|
|
{
|
|
ImGui::Checkbox("Enable smooth upscaling", &tempConfig.smoothScaling);
|
|
ImGui::Checkbox("Enable vertical sync", &tempConfig.vsync);
|
|
ImGui::Checkbox("Skip frames when too slow", &tempConfig.frameSkip);
|
|
ImGui::Checkbox("Fast font rendering", &tempConfig.solidFonts);
|
|
}
|
|
|
|
ImGui::Spacing();
|
|
ImGui::Separator();
|
|
ImGui::Spacing();
|
|
|
|
/* Buttons */
|
|
ImVec2 btnDim = ImVec2(150, 24);
|
|
ImGui::Dummy(ImVec2(ImGui::GetWindowContentRegionMax().x - ImGui::GetStyle().WindowPadding.x - 2*btnDim.x - 1*ImGui::GetStyle().ItemSpacing.x, btnDim.y));
|
|
ImGui::SameLine();
|
|
if(ImGui::Button("Discard Changes", btnDim))
|
|
{
|
|
tempConfig = Configurables(rtData.config);
|
|
}
|
|
ImGui::SameLine();
|
|
if(ImGui::Button("Apply Changes", btnDim))
|
|
{
|
|
bool refreshWindow = false;
|
|
if(VALUE_CHANGED(defScreenW) || VALUE_CHANGED(defScreenH))
|
|
{
|
|
STORE_CONFIG(defScreenW);
|
|
STORE_CONFIG(defScreenH);
|
|
refreshWindow = true;
|
|
}
|
|
|
|
if(VALUE_CHANGED(fullscreen))
|
|
{
|
|
STORE_CONFIG(fullscreen);
|
|
}
|
|
|
|
if(VALUE_CHANGED(fixedAspectRatio))
|
|
{
|
|
STORE_CONFIG(fixedAspectRatio);
|
|
refreshWindow = true;
|
|
}
|
|
|
|
if(VALUE_CHANGED(smoothScaling))
|
|
{
|
|
STORE_CONFIG(smoothScaling);
|
|
}
|
|
|
|
if(VALUE_CHANGED(vsync))
|
|
{
|
|
STORE_CONFIG(vsync);
|
|
}
|
|
|
|
if(VALUE_CHANGED(frameSkip))
|
|
{
|
|
STORE_CONFIG(frameSkip);
|
|
}
|
|
|
|
if(VALUE_CHANGED(solidFonts))
|
|
{
|
|
STORE_CONFIG(solidFonts);
|
|
}
|
|
|
|
if(refreshWindow)
|
|
SDL_SetWindowSize(rtData.window, tempConfig.defScreenW, tempConfig.defScreenH);
|
|
}
|
|
}
|
|
|
|
bool onCaptureInputEvent(const SDL_Event &event)
|
|
{
|
|
assert(captureDesc);
|
|
SourceDesc &desc = *captureDesc;
|
|
|
|
switch (event.type)
|
|
{
|
|
case SDL_KEYDOWN:
|
|
desc.type = Key;
|
|
desc.d.scan = event.key.keysym.scancode;
|
|
|
|
/* Special case aliases */
|
|
if (desc.d.scan == SDL_SCANCODE_RSHIFT)
|
|
desc.d.scan = SDL_SCANCODE_LSHIFT;
|
|
|
|
if (desc.d.scan == SDL_SCANCODE_KP_ENTER)
|
|
desc.d.scan = SDL_SCANCODE_RETURN;
|
|
|
|
break;
|
|
|
|
case SDL_JOYBUTTONDOWN:
|
|
desc.type = JButton;
|
|
desc.d.jb = event.jbutton.button;
|
|
break;
|
|
|
|
case SDL_JOYHATMOTION:
|
|
{
|
|
int v = event.jhat.value;
|
|
|
|
/* Only register if single directional input */
|
|
if (v != SDL_HAT_LEFT && v != SDL_HAT_RIGHT &&
|
|
v != SDL_HAT_UP && v != SDL_HAT_DOWN)
|
|
return true;
|
|
|
|
desc.type = JHat;
|
|
desc.d.jh.hat = event.jhat.hat;
|
|
desc.d.jh.pos = v;
|
|
break;
|
|
}
|
|
|
|
case SDL_JOYAXISMOTION:
|
|
{
|
|
int v = event.jaxis.value;
|
|
|
|
/* Only register if pushed halfway through */
|
|
if (v > -JAXIS_THRESHOLD && v < JAXIS_THRESHOLD)
|
|
return true;
|
|
|
|
desc.type = JAxis;
|
|
desc.d.ja.axis = event.jaxis.axis;
|
|
desc.d.ja.dir = v < 0 ? Negative : Positive;
|
|
break;
|
|
}
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
captureDesc = 0;
|
|
state = Idle;
|
|
updateDuplicateStatus();
|
|
|
|
return true;
|
|
}
|
|
|
|
void onResetToDefault()
|
|
{
|
|
setupBindingData(genDefaultBindings(rtData.config, rtData.gamecontroller));
|
|
updateDuplicateStatus();
|
|
}
|
|
|
|
void onAccept()
|
|
{
|
|
BDescVec binds;
|
|
|
|
for (size_t i = 0; i < bWidgets.size(); ++i)
|
|
bWidgets[i].appendBindings(binds);
|
|
|
|
rtData.bindingUpdateMsg.post(binds);
|
|
|
|
/* Store the key bindings to disk as well to prevent config loss */
|
|
storeBindings(binds, rtData.config);
|
|
}
|
|
|
|
void onCancel()
|
|
{
|
|
destroyReq = true;
|
|
}
|
|
};
|
|
|
|
void BindingWidget::click(SourceDesc &desc)
|
|
{
|
|
/* Check for right click */
|
|
if(ImGui::IsMouseClicked(1))
|
|
{
|
|
desc.type = Invalid;
|
|
p->updateDuplicateStatus();
|
|
return;
|
|
}
|
|
|
|
p->captureDesc = &desc;
|
|
p->captureName = vb.str;
|
|
p->state = AwaitingInput;
|
|
}
|
|
|
|
void BindingWidget::displayWidget(uint32_t width, uint32_t height)
|
|
{
|
|
ImVec2 buttonSize = ImVec2((width-6)/3, height/2-ImGui::GetStyle().ItemSpacing.x);
|
|
ImGui::PushID(vb.code);
|
|
|
|
/* Label for Widget */
|
|
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImGui::GetStyle().Colors[ImGuiCol_WindowBg]);
|
|
ImGui::Button(vb.str, ImVec2(width/3, height-ImGui::GetStyle().ItemSpacing.x));
|
|
ImGui::PopStyleColor();
|
|
ImGui::SameLine();
|
|
|
|
/* Group of buttons */
|
|
ImGui::BeginGroup();
|
|
for(int i=0; i<4; i++)
|
|
{
|
|
if(dupFlag[i])
|
|
ImGui::PushStyleColor(ImGuiCol_Text, ImColor(255, 0, 0));
|
|
|
|
if(ImGui::Button(sourceDescString(src[i]).c_str(), buttonSize))
|
|
click(src[i]);
|
|
|
|
if(dupFlag[i])
|
|
ImGui::PopStyleColor();
|
|
|
|
if(i%2 == 0)
|
|
ImGui::SameLine();
|
|
}
|
|
ImGui::EndGroup();
|
|
|
|
ImGui::PopID();
|
|
}
|
|
|
|
void BindingWidget::appendBindings(BDescVec &d) const
|
|
{
|
|
for (size_t i = 0; i < 4; ++i)
|
|
{
|
|
if (src[i].type == Invalid)
|
|
continue;
|
|
|
|
BindingDesc desc;
|
|
desc.src = src[i];
|
|
desc.target = vb.code;
|
|
d.push_back(desc);
|
|
}
|
|
}
|
|
|
|
SettingsMenu::SettingsMenu(RGSSThreadData &rtData)
|
|
{
|
|
p = new SettingsMenuPrivate(rtData);
|
|
p->state = Idle;
|
|
|
|
p->hasFocus = false;
|
|
p->destroyReq = false;
|
|
p->dupWarn = false;
|
|
|
|
p->currentTab = SettingsMenuPrivate::CONTROLS;
|
|
|
|
p->window = SDL_CreateWindow("Settings Menu",
|
|
SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
|
|
winSize.x, winSize.y, SDL_WINDOW_OPENGL|SDL_WINDOW_INPUT_FOCUS);
|
|
p->winID = SDL_GetWindowID(p->window);
|
|
p->glContext = SDL_GL_CreateContext(p->window);
|
|
|
|
ImGui_ImplSdl_Init(p->window);
|
|
|
|
/* ImGUI wants to own the memory with the TTF data, so requires a copy. */
|
|
void * liberation_copy = malloc(BNDL_F_L(BUNDLED_FONT));
|
|
memcpy(liberation_copy, BNDL_F_D(BUNDLED_FONT), BNDL_F_L(BUNDLED_FONT));
|
|
ImGuiIO& io = ImGui::GetIO();
|
|
ImFont* im_font = io.Fonts->AddFontFromMemoryTTF(liberation_copy, BNDL_F_L(BUNDLED_FONT), 16.0f);
|
|
|
|
/* Generate Binding Widgets */
|
|
assert(numRows*numCols == vButtonsN);
|
|
|
|
for (int i = 0; i < vButtonsN; i++)
|
|
{
|
|
BindingWidget w(i, p);
|
|
p->bWidgets.push_back(w);
|
|
}
|
|
|
|
BDescVec binds;
|
|
rtData.bindingUpdateMsg.get(binds);
|
|
p->setupBindingData(binds);
|
|
|
|
p->captureDesc = 0;
|
|
p->captureName = 0;
|
|
|
|
p->updateDuplicateStatus();
|
|
|
|
p->redraw();
|
|
}
|
|
|
|
SettingsMenu::~SettingsMenu()
|
|
{
|
|
ImGui_ImplSdl_Shutdown();
|
|
SDL_GL_DeleteContext(p->glContext);
|
|
SDL_DestroyWindow(p->window);
|
|
|
|
delete p;
|
|
}
|
|
|
|
bool SettingsMenu::onEvent(const SDL_Event &event)
|
|
{
|
|
/* Check for a redraw event first */
|
|
if(event.type == EventThread::UPDATE_POPUP + EventThread::UsrIdStart)
|
|
{
|
|
if(p->hasFocus)
|
|
p->redraw();
|
|
}
|
|
|
|
/* Check whether this event is for us */
|
|
switch (event.type)
|
|
{
|
|
case SDL_WINDOWEVENT :
|
|
case SDL_MOUSEBUTTONDOWN :
|
|
case SDL_MOUSEBUTTONUP :
|
|
case SDL_MOUSEMOTION :
|
|
case SDL_KEYDOWN :
|
|
case SDL_KEYUP :
|
|
case SDL_TEXTINPUT :
|
|
/* We can do this because windowID has the same
|
|
* struct offset in all these event types */
|
|
if (event.window.windowID != p->winID)
|
|
return false;
|
|
break;
|
|
|
|
case SDL_JOYBUTTONDOWN :
|
|
case SDL_JOYBUTTONUP :
|
|
case SDL_JOYHATMOTION :
|
|
case SDL_JOYAXISMOTION :
|
|
if (!p->hasFocus)
|
|
return false;
|
|
break;
|
|
|
|
/* Don't try to handle something we don't understand */
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
/* Pass through event data to ImGUI */
|
|
ImGui_ImplSdl_ProcessEvent(event);
|
|
|
|
/* Now process it.. */
|
|
switch (event.type)
|
|
{
|
|
/* Ignore these event */
|
|
case SDL_MOUSEBUTTONUP :
|
|
case SDL_KEYUP :
|
|
return true;
|
|
|
|
case SDL_WINDOWEVENT :
|
|
switch (event.window.event)
|
|
{
|
|
case SDL_WINDOWEVENT_SHOWN : // SDL is bugged and doesn't give us a first FOCUS_GAINED event
|
|
case SDL_WINDOWEVENT_FOCUS_GAINED :
|
|
p->hasFocus = true;
|
|
break;
|
|
|
|
case SDL_WINDOWEVENT_FOCUS_LOST :
|
|
p->hasFocus = false;
|
|
break;
|
|
|
|
case SDL_WINDOWEVENT_EXPOSED :
|
|
p->redraw();
|
|
break;
|
|
|
|
case SDL_WINDOWEVENT_CLOSE:
|
|
p->onCancel();
|
|
}
|
|
|
|
return true;
|
|
|
|
case SDL_MOUSEMOTION:
|
|
//p->onMotion(event.motion);
|
|
return true;
|
|
|
|
case SDL_KEYDOWN:
|
|
if (p->state != AwaitingInput)
|
|
{
|
|
if (event.key.keysym.sym == SDLK_RETURN)
|
|
p->onAccept();
|
|
else if (event.key.keysym.sym == SDLK_ESCAPE)
|
|
p->onCancel();
|
|
|
|
return true;
|
|
}
|
|
|
|
/* Don't let the user bind keys that trigger
|
|
* mkxp functions */
|
|
switch(event.key.keysym.scancode)
|
|
{
|
|
case SDL_SCANCODE_F1:
|
|
case SDL_SCANCODE_F2:
|
|
case SDL_SCANCODE_F12:
|
|
return true;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
case SDL_JOYBUTTONDOWN:
|
|
case SDL_JOYHATMOTION:
|
|
case SDL_JOYAXISMOTION:
|
|
if (p->state != AwaitingInput)
|
|
return true;
|
|
break;
|
|
|
|
case SDL_MOUSEBUTTONDOWN:
|
|
return true;
|
|
|
|
default:
|
|
return true;
|
|
}
|
|
|
|
if (p->state == AwaitingInput)
|
|
return p->onCaptureInputEvent(event);
|
|
|
|
return true;
|
|
}
|
|
|
|
void SettingsMenu::raise()
|
|
{
|
|
SDL_RaiseWindow(p->window);
|
|
}
|
|
|
|
bool SettingsMenu::destroyReq() const
|
|
{
|
|
return p->destroyReq;
|
|
}
|