1160 lines
23 KiB
C++
1160 lines
23 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_video.h>
|
|
#include <SDL_ttf.h>
|
|
#include <SDL_surface.h>
|
|
#include <SDL_keyboard.h>
|
|
#include <SDL_rect.h>
|
|
|
|
#include "keybindings.h"
|
|
#include "eventthread.h"
|
|
#include "font.h"
|
|
#include "input.h"
|
|
#include "etc-internal.h"
|
|
#include "util.h"
|
|
|
|
#include <algorithm>
|
|
#include <assert.h>
|
|
|
|
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];
|
|
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 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),
|
|
visible(true)
|
|
{
|
|
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<BindingWidget> bWidgets;
|
|
std::vector<Button> buttons;
|
|
Label infoLabel;
|
|
Label dupWarnLabel;
|
|
std::vector<Widget*> widgets;
|
|
Widget *hovered;
|
|
|
|
SourceDesc *captureDesc;
|
|
const char *captureName;
|
|
|
|
SettingsMenuPrivate(RGSSThreadData &rtData)
|
|
: rtData(rtData)
|
|
{}
|
|
|
|
SDL_Surface *createSurface(int w, int h)
|
|
{
|
|
int bpp;
|
|
Uint32 rMask, gMask, bMask, aMask;
|
|
SDL_PixelFormatEnumToMasks(SDL_PIXELFORMAT_ABGR8888,
|
|
&bpp, &rMask, &gMask, &bMask, &aMask);
|
|
|
|
return SDL_CreateRGBSurface(0, w, h, bpp, rMask, gMask, bMask, 0);
|
|
}
|
|
|
|
void fillSurface(SDL_Surface *surf, uint8_t grey)
|
|
{
|
|
SDL_FillRect(surf, 0, SDL_MapRGBA(rgb, grey, grey, grey, 255));
|
|
}
|
|
|
|
void fillRect(SDL_Surface *surf,
|
|
int x, int y, int w, int h,
|
|
uint8_t r, uint8_t g, uint8_t b)
|
|
{
|
|
SDL_Rect rect = { drawOff.x+x, drawOff.y+y, w, h };
|
|
SDL_FillRect(surf, &rect, SDL_MapRGB(rgb, r, g, b));
|
|
}
|
|
|
|
void fillRect(SDL_Surface *surf, uint8_t grey,
|
|
int x, int y, int w, int h)
|
|
{
|
|
fillRect(surf, x, y, w, h, grey, grey, grey);
|
|
}
|
|
|
|
void strokeLineH(SDL_Surface *surf, uint8_t grey,
|
|
int x, int y, int length, int width)
|
|
{
|
|
fillRect(surf, grey, x, y-width/2, length, width);
|
|
}
|
|
|
|
void strokeLineV(SDL_Surface *surf, uint8_t grey,
|
|
int x, int y, int length, int width)
|
|
{
|
|
fillRect(surf, grey, x-width/2, y, width, length);
|
|
}
|
|
|
|
void strokeLineH(SDL_Surface *surf, uint8_t r, uint8_t g, uint8_t b,
|
|
int x, int y, int length, int width)
|
|
{
|
|
fillRect(surf, r, g, b, x, y-width/2, length, width);
|
|
}
|
|
|
|
void strokeLineV(SDL_Surface *surf, uint8_t r, uint8_t g, uint8_t b,
|
|
int x, int y, int length, int width)
|
|
{
|
|
fillRect(surf, r, g, b, x-width/2, y, width, length);
|
|
}
|
|
|
|
void strokeRect(SDL_Surface *surf, uint8_t grey,
|
|
int x, int y, int w, int h,
|
|
int lineW)
|
|
{
|
|
strokeLineH(surf, grey, x, y, w, lineW);
|
|
strokeLineH(surf, grey, x, y+h, w, lineW);
|
|
|
|
strokeLineV(surf, grey, x, y, h, lineW);
|
|
strokeLineV(surf, grey, x+w, y, h, lineW);
|
|
}
|
|
|
|
void strokeRectInner(SDL_Surface *surf,
|
|
int x, int y, int w, int h, int lineW,
|
|
uint8_t r, uint8_t g, uint8_t b)
|
|
{
|
|
fillRect(surf, x, y, w, lineW, r, g, b);
|
|
fillRect(surf, x, y+h-lineW, w, lineW, r, g, b);
|
|
fillRect(surf, x, y, lineW, h, r, g, b);
|
|
fillRect(surf, x+w-lineW, y, lineW, h, r, g ,b);
|
|
}
|
|
|
|
void strokeRectInner(SDL_Surface *surf, uint8_t grey,
|
|
int x, int y, int w, int h,
|
|
int lineW)
|
|
{
|
|
strokeRectInner(surf, x, y, w, h, lineW, grey, grey, grey);
|
|
}
|
|
|
|
void applyFontStyle(bool bold)
|
|
{
|
|
if (bold)
|
|
TTF_SetFontStyle(font, TTF_STYLE_BOLD);
|
|
else
|
|
TTF_SetFontStyle(font, TTF_STYLE_NORMAL);
|
|
}
|
|
|
|
SDL_Surface *createTextSurface(const char *str, bool bold)
|
|
{
|
|
SDL_Color c = { cText, cText, cText, 255 };
|
|
applyFontStyle(bold);
|
|
|
|
return TTF_RenderUTF8_Blended(font, str, c);
|
|
}
|
|
|
|
SDL_Surface *createTextSurface(const char *str, SDL_Color c,
|
|
bool bold)
|
|
{
|
|
applyFontStyle(bold);
|
|
return TTF_RenderUTF8_Blended(font, str, c);
|
|
}
|
|
|
|
/* Horizontally centered */
|
|
void blitTextSurf(SDL_Surface *surf, int x, int y,
|
|
int alignW, SDL_Surface *txtSurf,
|
|
Justification just)
|
|
{
|
|
SDL_Rect dstRect;
|
|
dstRect.x = drawOff.x;
|
|
dstRect.y = drawOff.y + y - txtSurf->h / 2;
|
|
dstRect.w = txtSurf->w;
|
|
dstRect.h = txtSurf->h;
|
|
|
|
switch (just)
|
|
{
|
|
case Left:
|
|
dstRect.x += x;
|
|
break;
|
|
case Center:
|
|
dstRect.x += x - (txtSurf->w - alignW) / 2;
|
|
break;
|
|
}
|
|
|
|
if (txtSurf->w <= alignW)
|
|
{
|
|
SDL_BlitSurface(txtSurf, 0, surf, &dstRect);
|
|
}
|
|
else
|
|
{
|
|
dstRect.w = alignW;
|
|
dstRect.x = x;
|
|
SDL_BlitScaled(txtSurf, 0, surf, &dstRect);
|
|
}
|
|
}
|
|
|
|
void drawText(SDL_Surface *surf, const char *str,
|
|
int x, int y, int alignW,
|
|
Justification just, SDL_Color c, bool bold = false)
|
|
{
|
|
SDL_Surface *txt = createTextSurface(str, c, bold);
|
|
blitTextSurf(surf, x, y, alignW, txt, just);
|
|
SDL_FreeSurface(txt);
|
|
}
|
|
|
|
void drawText(SDL_Surface *surf, const char *str,
|
|
int x, int y, int alignW,
|
|
Justification just, bool bold = false)
|
|
{
|
|
SDL_Color c = { cText, cText, cText, 255 };
|
|
drawText(surf, str, x, y, alignW, just, c, bold);
|
|
}
|
|
|
|
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;
|
|
|
|
bool haveDup = 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;
|
|
haveDup = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
dupWarnLabel.setVisible(haveDup);
|
|
}
|
|
|
|
void redraw()
|
|
{
|
|
fillSurface(winSurf, cBgNorm);
|
|
|
|
for (size_t i = 0; i < widgets.size(); ++i)
|
|
widgets[i]->draw(winSurf);
|
|
|
|
if (state == AwaitingInput)
|
|
{
|
|
char buf[64];
|
|
snprintf(buf, sizeof(buf), "Press key or joystick button for \"%s\"", captureName);
|
|
|
|
drawOff = Vec2i();
|
|
|
|
SDL_Surface *dark = createSurface(winSize.x, winSize.y);
|
|
fillSurface(dark, 0);
|
|
SDL_SetSurfaceAlphaMod(dark, 128);
|
|
SDL_SetSurfaceBlendMode(dark, SDL_BLENDMODE_BLEND);
|
|
SDL_Surface *txt = createTextSurface(buf, false);
|
|
|
|
SDL_BlitSurface(dark, 0, winSurf, 0);
|
|
|
|
SDL_Rect fill;
|
|
fill.x = (winSize.x - txt->w - 20) / 2;
|
|
fill.y = (winSize.y - txt->h - 20) / 2;
|
|
fill.w = txt->w + 20;
|
|
fill.h = txt->h + 20;
|
|
|
|
fillRect(winSurf, cBgNorm, fill.x, fill.y, fill.w, fill.h);
|
|
strokeRectInner(winSurf, cLine, fill.x, fill.y, fill.w, fill.h, 2);
|
|
|
|
fill.x += 10;
|
|
fill.y += 10;
|
|
fill.w = txt->w;
|
|
fill.h = txt->h;
|
|
|
|
SDL_BlitSurface(txt, 0, winSurf, &fill);
|
|
|
|
SDL_FreeSurface(txt);
|
|
SDL_FreeSurface(dark);
|
|
}
|
|
|
|
SDL_UpdateWindowSurface(window);
|
|
}
|
|
|
|
Widget *findWidget(int x, int y)
|
|
{
|
|
Widget *w = 0;
|
|
|
|
for (size_t i = 0; i < widgets.size(); ++i)
|
|
if (widgets[i]->hit(x, y))
|
|
{
|
|
w = widgets[i];
|
|
break;
|
|
}
|
|
|
|
return w;
|
|
}
|
|
|
|
void onClick(const SDL_MouseButtonEvent &e)
|
|
{
|
|
if (e.button != SDL_BUTTON_LEFT && e.button != SDL_BUTTON_RIGHT)
|
|
return;
|
|
|
|
if (state == AwaitingInput)
|
|
{
|
|
state = Idle;
|
|
redraw();
|
|
return;
|
|
}
|
|
|
|
Widget *w = findWidget(e.x, e.y);
|
|
|
|
if (w)
|
|
w->click(e.x, e.y, e.button);
|
|
}
|
|
|
|
void onMotion(const SDL_MouseMotionEvent &e)
|
|
{
|
|
if (state == AwaitingInput)
|
|
return;
|
|
|
|
Widget *w = findWidget(e.x, e.y);
|
|
|
|
if (w != hovered)
|
|
{
|
|
if (hovered)
|
|
hovered->leave();
|
|
hovered = w;
|
|
}
|
|
|
|
if (hovered)
|
|
hovered->motion(e.x, e.y);
|
|
}
|
|
|
|
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();
|
|
redraw();
|
|
|
|
return true;
|
|
}
|
|
|
|
void onBWidgetCellClicked(SourceDesc &desc, const char *str, uint8_t button)
|
|
{
|
|
if (state != Idle)
|
|
return;
|
|
|
|
if (button == SDL_BUTTON_LEFT)
|
|
{
|
|
captureDesc = &desc;
|
|
captureName = str;
|
|
state = AwaitingInput;
|
|
}
|
|
else /* e.button == SDL_BUTTON_RIGHT */
|
|
{
|
|
/* Clear binding */
|
|
desc.type = Invalid;
|
|
}
|
|
|
|
updateDuplicateStatus();
|
|
redraw();
|
|
}
|
|
|
|
void onResetToDefault()
|
|
{
|
|
setupBindingData(genDefaultBindings(rtData.config));
|
|
updateDuplicateStatus();
|
|
redraw();
|
|
}
|
|
|
|
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);
|
|
|
|
destroyReq = true;
|
|
}
|
|
|
|
void onCancel()
|
|
{
|
|
destroyReq = true;
|
|
}
|
|
};
|
|
|
|
Widget::Widget(SMP *p, const IntRect &rect)
|
|
: p(p), rect(rect)
|
|
{}
|
|
|
|
bool Widget::hit(int x, int y)
|
|
{
|
|
return pointInRect(rect, x, y);
|
|
}
|
|
|
|
void Widget::draw(SDL_Surface *surf)
|
|
{
|
|
Vec2i prev = p->drawOff;
|
|
p->drawOff = rect.pos();
|
|
drawHandler(surf);
|
|
p->drawOff = prev;
|
|
}
|
|
|
|
void Widget::motion(int x, int y)
|
|
{
|
|
motionHandler(x - rect.x, y - rect.y);
|
|
}
|
|
|
|
void Widget::leave()
|
|
{
|
|
leaveHandler();
|
|
}
|
|
|
|
void Widget::click(int x, int y, uint8_t button)
|
|
{
|
|
clickHandler(x - rect.x, y - rect.y, button);
|
|
}
|
|
|
|
/* Ratio of cell area to total widget width */
|
|
#define BW_CELL_R 0.75f
|
|
|
|
void BindingWidget::drawHandler(SDL_Surface *surf)
|
|
{
|
|
const int cellW = (rect.w*BW_CELL_R) / 2;
|
|
const int cellH = rect.h / 2;
|
|
const int cellOffX = (1.0f-BW_CELL_R) * rect.w;
|
|
|
|
const int cellOff[] =
|
|
{
|
|
cellOffX, 1,
|
|
cellOffX+cellW, 1,
|
|
cellOffX, cellH,
|
|
cellOffX+cellW, cellH
|
|
};
|
|
|
|
const int lbOff[] =
|
|
{
|
|
0, cellH / 2,
|
|
cellW, cellH / 2,
|
|
0, cellH + cellH / 2,
|
|
cellW, cellH + cellH / 2
|
|
};
|
|
|
|
/* Hovered cell background */
|
|
if (hoveredCell != -1)
|
|
p->fillRect(surf, cBgDark,
|
|
cellOff[hoveredCell*2], cellOff[hoveredCell*2+1],
|
|
cellW, cellH);
|
|
|
|
/* Frame */
|
|
p->strokeRectInner(surf, cLine, 0, 0, rect.w, rect.h, 2);
|
|
|
|
/* Virtual button name */
|
|
p->drawText(surf, vb.str, 1, rect.h/2, cellOffX, Center, true);
|
|
|
|
/* Cell frames */
|
|
p->strokeLineV(surf, cLine, cellOffX, 0, rect.h, 2);
|
|
p->strokeLineV(surf, cLine, cellOffX+cellW, 0, rect.h, 2);
|
|
p->strokeLineH(surf, cLine, cellOffX, cellH, cellW*2, 2);
|
|
|
|
/* Draw binding labels */
|
|
for (size_t i = 0; i < 4; ++i)
|
|
{
|
|
std::string lb = sourceDescString(src[i]);
|
|
if (lb.empty())
|
|
continue;
|
|
|
|
const int x = lbOff[i*2+0];
|
|
const int y = lbOff[i*2+1];
|
|
p->drawText(surf, lb.c_str(), cellOffX+x+1, y, cellW-2, Center);
|
|
}
|
|
|
|
for (size_t i = 0; i < 4; ++i)
|
|
{
|
|
if (!dupFlag[i])
|
|
continue;
|
|
|
|
p->strokeRectInner(surf, cellOff[i*2]+1, cellOff[i*2+1]+1,
|
|
cellW-2, cellH-3, 1, 255, 0, 0);
|
|
}
|
|
}
|
|
|
|
void BindingWidget::setHoveredCell(int cell)
|
|
{
|
|
if (hoveredCell == cell)
|
|
return;
|
|
|
|
hoveredCell = cell;
|
|
p->redraw();
|
|
}
|
|
|
|
void BindingWidget::motionHandler(int x, int y)
|
|
{
|
|
setHoveredCell(cellIndex(x, y));
|
|
}
|
|
|
|
void BindingWidget::leaveHandler()
|
|
{
|
|
setHoveredCell(-1);
|
|
}
|
|
|
|
void BindingWidget::clickHandler(int x, int y, uint8_t button)
|
|
{
|
|
int cell = cellIndex(x, y);
|
|
|
|
if (cell == -1)
|
|
return;
|
|
|
|
p->onBWidgetCellClicked(src[cell], vb.str, button);
|
|
}
|
|
|
|
int BindingWidget::cellIndex(int x, int y) const
|
|
{
|
|
const int cellW = (rect.w*BW_CELL_R) / 2;
|
|
const int cellH = rect.h / 2;
|
|
const int cellOff = (1.0f-BW_CELL_R) * rect.w;
|
|
|
|
if (x < cellOff)
|
|
return -1;
|
|
|
|
x -= cellOff;
|
|
|
|
if (y < cellH)
|
|
if (x < cellW)
|
|
return 0;
|
|
else
|
|
return 1;
|
|
else
|
|
if (x < cellW)
|
|
return 2;
|
|
else
|
|
return 3;
|
|
|
|
return -1;
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
void Button::setHovered(bool val)
|
|
{
|
|
if (hovered == val)
|
|
return;
|
|
|
|
hovered = val;
|
|
p->redraw();
|
|
}
|
|
|
|
void Button::drawHandler(SDL_Surface *surf)
|
|
{
|
|
if (hovered)
|
|
p->fillRect(surf, cBgDark, 0, 0, rect.w, rect.h);
|
|
|
|
p->strokeRectInner(surf, cLine, 0, 0, rect.w, rect.h, 2);
|
|
p->drawText(surf, str, 0, rect.h/2, rect.w, Center);
|
|
}
|
|
|
|
void Button::motionHandler(int, int)
|
|
{
|
|
setHovered(true);
|
|
}
|
|
|
|
void Button::leaveHandler()
|
|
{
|
|
setHovered(false);
|
|
}
|
|
|
|
void Button::clickHandler(int, int, uint8_t button)
|
|
{
|
|
if (button != SDL_BUTTON_LEFT)
|
|
return;
|
|
|
|
(p->*cb)();
|
|
}
|
|
|
|
void Label::setVisible(bool val)
|
|
{
|
|
if (visible == val)
|
|
return;
|
|
|
|
visible = val;
|
|
p->redraw();
|
|
}
|
|
|
|
void Label::drawHandler(SDL_Surface *surf)
|
|
{
|
|
if (visible)
|
|
p->drawText(surf, str, 0, rect.h/2, rect.w, Left, c);
|
|
}
|
|
|
|
SettingsMenu::SettingsMenu(RGSSThreadData &rtData)
|
|
{
|
|
p = new SettingsMenuPrivate(rtData);
|
|
p->state = Idle;
|
|
|
|
p->hasFocus = false;
|
|
p->destroyReq = false;
|
|
|
|
p->window = SDL_CreateWindow("Key bindings",
|
|
SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
|
|
winSize.x, winSize.y, SDL_WINDOW_INPUT_FOCUS);
|
|
p->winSurf = SDL_GetWindowSurface(p->window);
|
|
p->winID = SDL_GetWindowID(p->window);
|
|
|
|
p->font = SharedFontState::openBundled(fontSize);
|
|
|
|
p->rgb = p->winSurf->format;
|
|
|
|
const size_t layoutW = 4;
|
|
const size_t layoutH = 3;
|
|
assert(layoutW*layoutH == vButtonsN);
|
|
|
|
const int bWidgetW = winSize.x / layoutH;
|
|
const int bWidgetH = 64;
|
|
const int bWidgetY = winSize.y - layoutW*bWidgetH - 48;
|
|
|
|
for (int y = 0; y < 4; ++y)
|
|
for (int x = 0; x < 3; ++x)
|
|
{
|
|
int i = y*3+x;
|
|
BindingWidget w(i, p, IntRect(x*bWidgetW, bWidgetY+y*bWidgetH,
|
|
bWidgetW, bWidgetH));
|
|
p->bWidgets.push_back(w);
|
|
}
|
|
|
|
for (size_t i = 0; i< p->bWidgets.size(); ++i)
|
|
p->widgets.push_back(&p->bWidgets[i]);
|
|
|
|
BDescVec binds;
|
|
rtData.bindingUpdateMsg.get(binds);
|
|
p->setupBindingData(binds);
|
|
|
|
/* Buttons */
|
|
const int buttonH = 32;
|
|
const int buttonY = winSize.y - buttonH - 8;
|
|
|
|
IntRect btRects[] =
|
|
{
|
|
IntRect(16, buttonY, 112, buttonH),
|
|
IntRect(winSize.x-16-64*2-8, buttonY, 64, buttonH),
|
|
IntRect(winSize.x-16-64, buttonY, 64, buttonH)
|
|
};
|
|
|
|
p->buttons.push_back(Button(p, btRects[0], "Reset defaults", &SMP::onResetToDefault));
|
|
p->buttons.push_back(Button(p, btRects[1], "Cancel", &SMP::onCancel));
|
|
p->buttons.push_back(Button(p, btRects[2], "Accept", &SMP::onAccept));
|
|
|
|
for (size_t i = 0; i< p->buttons.size(); ++i)
|
|
p->widgets.push_back(&p->buttons[i]);
|
|
|
|
/* Labels */
|
|
const char *info = "Use left click to bind a slot, right click to clear its binding";
|
|
p->infoLabel = Label(p, IntRect(16, 6, winSize.x, 16), info, cText, cText, cText);
|
|
|
|
const char *warn = "Warning: Same physical key bound to multiple slots";
|
|
p->dupWarnLabel = Label(p, IntRect(16, 26, winSize.x, 16), warn, 255, 0, 0);
|
|
|
|
p->widgets.push_back(&p->infoLabel);
|
|
p->widgets.push_back(&p->dupWarnLabel);
|
|
|
|
p->hovered = 0;
|
|
p->captureDesc = 0;
|
|
p->captureName = 0;
|
|
|
|
p->updateDuplicateStatus();
|
|
|
|
p->redraw();
|
|
}
|
|
|
|
SettingsMenu::~SettingsMenu()
|
|
{
|
|
TTF_CloseFont(p->font);
|
|
SDL_DestroyWindow(p->window);
|
|
|
|
delete p;
|
|
}
|
|
|
|
bool SettingsMenu::onEvent(const SDL_Event &event)
|
|
{
|
|
/* First, 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 :
|
|
/* 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;
|
|
}
|
|
|
|
/* 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 :
|
|
SDL_UpdateWindowSurface(p->window);
|
|
break;
|
|
|
|
case SDL_WINDOWEVENT_LEAVE:
|
|
if (p->hovered)
|
|
{
|
|
p->hovered->leave();
|
|
p->hovered = 0;
|
|
}
|
|
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:
|
|
p->onClick(event.button);
|
|
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;
|
|
}
|