scummvm/backends/platform/android/touchcontrols.cpp
Le Philousophe 106f9caa89 ANDROID: Improve emulated gamepad buttons handling
When triggering buttons on the virtual gamepad, the down and up events
are sent on release. That's a leftover from the previous virtual gamepad
which didn't allow to select the buttons easily.
On the new virtual gamepad, the button choice is better so we can
trigger a button down as soon as the finger is on it.
This fixes a bug in some AGS games which missed the button press because
the release was sent too soon.
Fixes Trac#15444.
2025-02-09 14:00:19 +01:00

784 lines
20 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program 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 3 of the License, or
* (at your option) any later version.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
// Allow use of stuff in <time.h>
#define FORBIDDEN_SYMBOL_EXCEPTION_time_h
//
// Disable printf override in common/forbidden.h to avoid
// clashes with log.h from the Android SDK.
// That header file uses
// __attribute__ ((format(printf, 3, 4)))
// which gets messed up by our override mechanism; this could
// be avoided by either changing the Android SDK to use the equally
// legal and valid
// __attribute__ ((format(__printf__, 3, 4)))
// or by refining our printf override to use a varadic macro
// (which then wouldn't be portable, though).
// Anyway, for now we just disable the printf override globally
// for the Android port
#define FORBIDDEN_SYMBOL_EXCEPTION_printf
#include "backends/platform/android/android.h"
#include "backends/platform/android/jni-android.h"
#include "backends/platform/android/touchcontrols.h"
#include "common/file.h"
#include "graphics/svg.h"
#define SVG_WIDTH 384
#define SVG_HEIGHT 256
#define BG_WIDTH 128
#define BG_HEIGHT 128
#define ARROW_DEAD 21
#define ARROW_SUPER 42
#define ARROW_END 64
#define ARROW_WIDTH 64
#define ARROW_SEMI_HEIGHT 52
#define ARROW_HEIGHT 64
#define ARROW_HL_LEFT 0
#define ARROW_HL_TOP 128
#define BUTTON_DEAD 12
#define BUTTON_END 64
#define BUTTON_WIDTH 64
#define BUTTON_HEIGHT 64
#define BUTTON_HL_LEFT 128
#define BUTTON_HL_TOP 128
#define BUTTON2_HL_LEFT 256
#define BUTTON2_HL_TOP 128
#define ALPHA_OPAQUE 255
#define ZOMBIE_TIMEOUT 500 // ms
// The scale factor is stored as a fixed point 30.2 bits
// This avoids floating point operations
#define SCALE_FACTOR_FXP 4
// gamepad.svg was designed with a basis of 128x128 sized widget
// As it's too small on screen, we apply a factor of 1.5x
// It can be tuned here and in SVG viewBox without changing anything else
#define SVG_UNSCALED(x) ((x) * 3 / 2)
#define SVG_SQ_UNSCALED(x) ((x * x) * 9 / 4)
#define SVG_SCALED(x) (SVG_UNSCALED(x) * _scale)
#define SCALED_PIXELS(x) ((x) / SCALE_FACTOR_FXP)
#define SVG_PIXELS(x) SCALED_PIXELS(SVG_SCALED(x))
#define FUNC_SVG_SQ_SCALED(x) (SVG_SQ_UNSCALED(x) * parent->_scale2)
TouchControls::TouchControls() :
_drawer(nullptr),
_screen_width(0),
_screen_height(0),
_svg(nullptr),
_zombieCount(0),
_scale(0),
_scale2(0) {
_functions[kFunctionLeft] = new FunctionLeft(this);
_functions[kFunctionRight] = new FunctionRight(this);
_functions[kFunctionCenter] = new FunctionCenter(this);
}
void TouchControls::init(float scale) {
_scale = scale * SCALE_FACTOR_FXP;
// As scale is small, this should fit in int
_scale2 = _scale * _scale;
Common::File stream;
if (!stream.open("gamepad.svg")) {
error("Failed to fetch gamepad image");
}
delete _svg;
_svg = new Graphics::SVGBitmap(&stream, SVG_PIXELS(SVG_WIDTH), SVG_PIXELS(SVG_HEIGHT));
}
TouchControls::~TouchControls() {
delete _svg;
for(unsigned int i = 0; i < kFunctionCount; i++) {
delete _functions[i];
}
}
void TouchControls::beforeDraw() {
if (!_zombieCount) {
return;
}
// We have zombies, force redraw to render fading out
if (_drawer) {
_drawer->touchControlNotifyChanged();
}
// Check for zombie functions, clear them out if expired
unsigned int zombieCount = 0;
uint32 now = g_system->getMillis(true);
for (uint i = 0; i < kFunctionCount; i++) {
Function *func = _functions[i];
if (func->status != kFunctionZombie) {
continue;
}
if (func->lastActivable < now) {
func->status = kFunctionInactive;
continue;
}
zombieCount++;
}
_zombieCount = zombieCount;
}
TouchControls::FunctionId TouchControls::getFunctionId(int x, int y) {
if (_screen_width == 0) {
// Avoid divide by 0 error
return kFunctionNone;
}
// Exclude areas reserved for system
if ((x < JNI::gestures_insets[0] * SCALE_FACTOR_FXP) ||
(y < JNI::gestures_insets[1] * SCALE_FACTOR_FXP) ||
(x >= _screen_width - JNI::gestures_insets[2] * SCALE_FACTOR_FXP) ||
(y >= _screen_height - JNI::gestures_insets[3] * SCALE_FACTOR_FXP)) {
return kFunctionNone;
}
float xRatio = float(x) / _screen_width;
if (xRatio < 0.3) {
return kFunctionLeft;
} else if (xRatio < 0.7) {
return kFunctionCenter;
} else {
return kFunctionRight;
}
}
void TouchControls::setDrawer(TouchControlsDrawer *drawer, int width, int height) {
_drawer = drawer;
_screen_width = width * SCALE_FACTOR_FXP;
_screen_height = height * SCALE_FACTOR_FXP;
if (drawer) {
drawer->touchControlInitSurface(*_svg);
}
}
TouchControls::Function *TouchControls::getFunctionFromPointerId(int ptrId) {
for (uint i = 0; i < kFunctionCount; i++) {
Function *func = _functions[i];
if (func->status != kFunctionActive) {
continue;
}
if (func->pointerId == ptrId) {
return func;
}
}
return nullptr;
}
TouchControls::Function *TouchControls::getZombieFunctionFromPos(int x, int y) {
if (!_zombieCount) {
return nullptr;
}
for (uint i = 0; i < kFunctionCount; i++) {
Function *func = _functions[i];
if (func->status != kFunctionZombie) {
// Already assigned to a finger or dead
continue;
}
if (func->isInside(x, y)) {
return func;
}
}
return nullptr;
}
void TouchControls::draw() {
assert(_drawer != nullptr);
uint32 now = g_system->getMillis(true);
for (uint i = 0; i < kFunctionCount; i++) {
Function *func = _functions[i];
uint8 alpha;
switch (func->status) {
case kFunctionActive:
alpha = ALPHA_OPAQUE;
break;
case kFunctionZombie:
if (func->lastActivable < now) {
// This function is definitely dead
continue;
}
alpha = (func->lastActivable - now) * ALPHA_OPAQUE / ZOMBIE_TIMEOUT;
break;
case kFunctionInactive:
default:
continue;
}
func->draw(alpha);
}
}
void TouchControls::update(Action action, int ptrId, int x, int y) {
x *= SCALE_FACTOR_FXP;
y *= SCALE_FACTOR_FXP;
if (action == JACTION_DOWN) {
Function *func = getZombieFunctionFromPos(x, y);
if (!func) {
// Finger was not pressed on a zombie function
// Determine which function it could be
FunctionId funcId = getFunctionId(x, y);
if (funcId != kFunctionNone) {
func = _functions[funcId];
}
if (!func) {
// No function for this finger
return;
}
if (func->status == kFunctionActive) {
// Another finger is already on this function
return;
}
// When zombie, we reuse the old start coordinates
// but not when starting over
func->reset();
func->startX = x;
func->startY = y;
}
func->status = kFunctionActive;
func->pointerId = ptrId;
func->currentX = x;
func->currentY = y;
int dX = x - func->startX;
int dY = y - func->startY;
func->touch(dX, dY, action);
if (_drawer) {
_drawer->touchControlNotifyChanged();
}
} else if (action == JACTION_MOVE) {
Function *func = getFunctionFromPointerId(ptrId);
if (!func) {
return;
}
func->currentX = x;
func->currentY = y;
int dX = x - func->startX;
int dY = y - func->startY;
func->touch(dX, dY, action);
if (_drawer) {
_drawer->touchControlNotifyChanged();
}
} else if (action == JACTION_UP) {
Function *func = getFunctionFromPointerId(ptrId);
if (!func) {
return;
}
func->currentX = x;
func->currentY = y;
int dX = x - func->startX;
int dY = y - func->startY;
func->touch(dX, dY, action);
func->status = kFunctionZombie;
func->lastActivable = g_system->getMillis(true) + ZOMBIE_TIMEOUT;
if (_drawer) {
_drawer->touchControlNotifyChanged();
}
_zombieCount++;
} else if (action == JACTION_CANCEL) {
for (uint i = 0; i < kFunctionCount; i++) {
Function *func = _functions[i];
if (func->status == kFunctionActive) {
func->touch(0, 0, action);
}
func->reset();
}
if (_drawer) {
_drawer->touchControlNotifyChanged();
}
_zombieCount = 0;
}
}
void TouchControls::buttonDown(Common::JoystickButton jb) {
if (jb == Common::JOYSTICK_BUTTON_INVALID) {
return;
}
//LOGD("TouchControls::buttonDown: %d", jb);
Common::Event ev;
ev.type = Common::EVENT_JOYBUTTON_DOWN;
ev.joystick.button = jb;
dynamic_cast<OSystem_Android *>(g_system)->pushEvent(ev);
}
void TouchControls::buttonUp(Common::JoystickButton jb) {
if (jb == Common::JOYSTICK_BUTTON_INVALID) {
return;
}
//LOGD("TouchControls::buttonUp: %d", jb);
Common::Event ev;
ev.type = Common::EVENT_JOYBUTTON_UP;
ev.joystick.button = jb;
dynamic_cast<OSystem_Android *>(g_system)->pushEvent(ev);
}
void TouchControls::buttonPress(Common::JoystickButton jb) {
if (jb == Common::JOYSTICK_BUTTON_INVALID) {
return;
}
//LOGD("TouchControls::buttonPress: %d", jb);
Common::Event ev1, ev2;
ev1.type = Common::EVENT_JOYBUTTON_DOWN;
ev1.joystick.button = jb;
ev2.type = Common::EVENT_JOYBUTTON_UP;
ev2.joystick.button = jb;
dynamic_cast<OSystem_Android *>(g_system)->pushEvent(ev1, ev2);
}
void TouchControls::drawSurface(uint8 alpha, int x, int y, int offX, int offY, const Common::Rect &clip) const {
Common::Rect clip_(SVG_PIXELS(clip.left), SVG_PIXELS(clip.top),
SVG_PIXELS(clip.right), SVG_PIXELS(clip.bottom));
_drawer->touchControlDraw(alpha,
SCALED_PIXELS(x + SVG_SCALED(offX)), SCALED_PIXELS(y + SVG_SCALED(offY)),
clip_.width(), clip_.height(), clip_);
}
bool TouchControls::FunctionLeft::isInside(int x, int y) {
int dX = x - startX;
int dY = y - startY;
// norm 2 squared (to avoid square root)
unsigned int sqNorm = (unsigned int)(dX * dX) + (unsigned int)(dY * dY);
if (sqNorm <= FUNC_SVG_SQ_SCALED(ARROW_END)) {
return true;
}
// Also accept touches near the old last touch
dX = x - currentX;
dY = y - currentY;
sqNorm = (unsigned int)(dX * dX) + (unsigned int)(dY * dY);
if (sqNorm <= FUNC_SVG_SQ_SCALED(ARROW_DEAD)) {
return true;
}
return false;
}
void TouchControls::FunctionLeft::maskToLeftButtons(uint32 oldMask, uint32 newMask) {
static const Common::JoystickButton buttons[] = {
Common::JOYSTICK_BUTTON_DPAD_UP, Common::JOYSTICK_BUTTON_DPAD_RIGHT,
Common::JOYSTICK_BUTTON_DPAD_DOWN, Common::JOYSTICK_BUTTON_DPAD_LEFT
};
uint32 diff = newMask ^ oldMask;
for(int i = 0, m = 1; i < ARRAYSIZE(buttons); i++, m <<= 1) {
if (!(diff & m)) {
continue;
}
if (oldMask & m) {
TouchControls::buttonUp(buttons[i]);
}
}
if (diff & 16) {
if (oldMask & 16) {
TouchControls::buttonUp(Common::JOYSTICK_BUTTON_RIGHT_SHOULDER);
} else {
TouchControls::buttonDown(Common::JOYSTICK_BUTTON_RIGHT_SHOULDER);
}
}
for(int i = 0, m = 1; i < ARRAYSIZE(buttons); i++, m <<= 1) {
if (!(diff & m)) {
continue;
}
if (newMask & m) {
TouchControls::buttonDown(buttons[i]);
}
}
}
void TouchControls::FunctionLeft::touch(int dX, int dY, Action action) {
if (action == JACTION_CANCEL ||
action == JACTION_UP) {
maskToLeftButtons(mask, 0);
resetState();
return;
}
uint32 newMask = 0;
// norm 2 squared (to avoid square root)
unsigned int sqNorm = (unsigned int)(dX * dX) + (unsigned int)(dY * dY);
if (sqNorm >= FUNC_SVG_SQ_SCALED(ARROW_DEAD)) {
// We are far enough from the center
// For right we use angles -60,60 as a sensitive zone
// For left it's the same but mirrored in negative
// We must be between the two lines which are of tan(60),tan(-60) and this corrsponds to sqrt(3)
// For up down we use angles -30,30 as a sensitive zone
// We must be outside the two lines which are of tan(30),tan(-30) and this corrsponds to 1/sqrt(3)
/*
static const double SQRT3 = 1.73205080756887719318;
unsigned int sq3 = SQRT3 * abs(dX);
*/
// Optimize by using an approximation of sqrt(3)
unsigned int sq3 = abs(dX) * 51409 / 29681;
unsigned int isq3 = abs(dX) * 29681 / 51409;
unsigned int adY = abs(dY);
if (adY <= sq3) {
// Left or right
if (dX < 0) {
newMask |= 8;
} else {
newMask |= 2;
}
}
if (adY >= isq3) {
// Up or down
if (dY < 0) {
newMask |= 1;
} else {
newMask |= 4;
}
}
}
if (sqNorm > FUNC_SVG_SQ_SCALED(ARROW_SUPER)) {
newMask |= 16;
}
if (mask != newMask) {
maskToLeftButtons(mask, newMask);
}
mask = newMask;
}
void TouchControls::FunctionLeft::draw(uint8 alpha) {
// Draw background
{
Common::Rect clip(0, 0, BG_WIDTH, BG_HEIGHT);
parent->drawSurface(alpha, startX, startY, -clip.width() / 2, -clip.height() / 2, clip);
}
if (mask == 0) {
return;
}
// Width and height here are rotated for left/right
uint16 width = ARROW_WIDTH;
uint16 height;
if (mask & 16) {
height = ARROW_HEIGHT;
} else {
height = ARROW_SEMI_HEIGHT;
}
// We can draw multiple arrows
if (mask & 1) {
// Draw UP
Common::Rect clip(width, height);
clip.translate(0, ARROW_HL_TOP + ARROW_HEIGHT - height);
int16 offX = -1, offY = -2;
parent->drawSurface(ALPHA_OPAQUE, startX, startY, offX * clip.width() / 2, offY * clip.height() / 2, clip);
}
if (mask & 2) {
// Draw RIGHT
Common::Rect clip(height, width);
clip.translate(ARROW_WIDTH, ARROW_HL_TOP);
int16 offX = 0, offY = -1;
parent->drawSurface(ALPHA_OPAQUE, startX, startY, offX * clip.width() / 2, offY * clip.height() / 2, clip);
}
if (mask & 4) {
// Draw DOWN
Common::Rect clip(width, height);
clip.translate(0, ARROW_HL_TOP + ARROW_HEIGHT);
int16 offX = -1, offY = 0;
parent->drawSurface(ALPHA_OPAQUE, startX, startY, offX * clip.width() / 2, offY * clip.height() / 2, clip);
}
if (mask & 8) {
// Draw LEFT
Common::Rect clip(height, width);
clip.translate(ARROW_WIDTH + ARROW_WIDTH - height, ARROW_HL_TOP + ARROW_HEIGHT);
int16 offX = -2, offY = -1;
parent->drawSurface(ALPHA_OPAQUE, startX, startY, offX * clip.width() / 2, offY * clip.height() / 2, clip);
}
}
bool TouchControls::FunctionRight::isInside(int x, int y) {
int dX = x - startX;
int dY = y - startY;
// norm 2 squared (to avoid square root)
unsigned int sqNorm = (unsigned int)(dX * dX) + (unsigned int)(dY * dY);
return sqNorm <= FUNC_SVG_SQ_SCALED(BUTTON_END);
}
void TouchControls::FunctionRight::touch(int dX, int dY, Action action) {
static const Common::JoystickButton buttons[] = {
Common::JOYSTICK_BUTTON_Y, Common::JOYSTICK_BUTTON_B,
Common::JOYSTICK_BUTTON_A, Common::JOYSTICK_BUTTON_X
};
static const Common::JoystickButton modifiers[] = {
Common::JOYSTICK_BUTTON_INVALID, Common::JOYSTICK_BUTTON_INVALID,
Common::JOYSTICK_BUTTON_INVALID, Common::JOYSTICK_BUTTON_INVALID
};
if (action == JACTION_CANCEL ||
action == JACTION_UP) {
if (button) {
buttonUp(buttons[button - 1]);
buttonUp(modifiers[button - 1]);
}
resetState();
return;
}
uint32 newButton = 0;
// norm 2 squared (to avoid square root)
unsigned int sqNorm = (unsigned int)(dX * dX) + (unsigned int)(dY * dY);
if (sqNorm >= FUNC_SVG_SQ_SCALED(BUTTON_DEAD) && sqNorm <= FUNC_SVG_SQ_SCALED(BUTTON_END)) {
// We are far enough from the center
// For right we use angles -45,45 as a sensitive zone
// For left it's the same but mirrored in negative
// We must be between the two lines which are of tan(45),tan(-45) and this corrsponds to 1
// For up down we use angles -45,45 as a sensitive zone
// We must be outside the two lines which are of tan(45),tan(-45) and this corrsponds to 1
unsigned int adX = abs(dX);
unsigned int adY = abs(dY);
if (adY <= adX) {
// X or B
if (dX < 0) {
newButton = 4;
} else {
newButton = 2;
}
} else {
// Y or A
if (dY < 0) {
newButton = 1;
} else {
newButton = 3;
}
}
} else {
newButton = 0;
}
if (button != newButton) {
// Release the previously pressed button, if any
if (button) {
buttonUp(buttons[button - 1]);
buttonUp(modifiers[button - 1]);
}
button = newButton;
// Press the new button
if (button) {
buttonDown(modifiers[button - 1]);
buttonDown(buttons[button - 1]);
}
}
}
void TouchControls::FunctionRight::draw(uint8 alpha) {
// Draw background
{
Common::Rect clip(BG_WIDTH, 0, 2 * BG_WIDTH, BG_HEIGHT);
parent->drawSurface(alpha, startX, startY, -clip.width() / 2, -clip.height() / 2, clip);
}
if (button == 0) {
return;
}
Common::Rect clip(BUTTON_WIDTH, BUTTON_HEIGHT);
int16 offX, offY;
if (button == 1) {
// Draw Y
clip.translate(BUTTON_HL_LEFT, BUTTON_HL_TOP);
offX = -1;
offY = -2;
} else if (button == 2) {
// Draw B
clip.translate(BUTTON_HL_LEFT + BUTTON_WIDTH, BUTTON_HL_TOP);
offX = 0;
offY = -1;
} else if (button == 3) {
// Draw A
clip.translate(BUTTON_HL_LEFT, BUTTON_HL_TOP + BUTTON_HEIGHT);
offX = -1;
offY = 0;
} else if (button == 4) {
// Draw X
clip.translate(BUTTON_HL_LEFT + BUTTON_WIDTH, BUTTON_HL_TOP + BUTTON_HEIGHT);
offX = -2;
offY = -1;
} else {
return;
}
parent->drawSurface(ALPHA_OPAQUE, startX, startY, offX * clip.width() / 2, offY * clip.height() / 2, clip);
}
bool TouchControls::FunctionCenter::isInside(int x, int y) {
int dX = x - startX;
int dY = y - startY;
// norm 2 squared (to avoid square root)
unsigned int sqNorm = (unsigned int)(dX * dX) + (unsigned int)(dY * dY);
return sqNorm <= FUNC_SVG_SQ_SCALED(BUTTON_END);
}
void TouchControls::FunctionCenter::touch(int dX, int dY, Action action) {
static const Common::JoystickButton buttons[] = {
Common::JOYSTICK_BUTTON_GUIDE, Common::JOYSTICK_BUTTON_RIGHT_STICK,
Common::JOYSTICK_BUTTON_START, Common::JOYSTICK_BUTTON_LEFT_STICK
};
static const Common::JoystickButton modifiers[] = {
Common::JOYSTICK_BUTTON_INVALID, Common::JOYSTICK_BUTTON_INVALID,
Common::JOYSTICK_BUTTON_INVALID, Common::JOYSTICK_BUTTON_INVALID
};
if (action == JACTION_CANCEL ||
action == JACTION_UP) {
if (button) {
buttonUp(buttons[button - 1]);
buttonUp(modifiers[button - 1]);
}
resetState();
return;
}
uint32 newButton = 0;
// norm 2 squared (to avoid square root)
unsigned int sqNorm = (unsigned int)(dX * dX) + (unsigned int)(dY * dY);
if (sqNorm >= FUNC_SVG_SQ_SCALED(BUTTON_DEAD) && sqNorm <= FUNC_SVG_SQ_SCALED(BUTTON_END)) {
// We are far enough from the center
// For right we use angles -45,45 as a sensitive zone
// For left it's the same but mirrored in negative
// We must be between the two lines which are of tan(45),tan(-45) and this corrsponds to 1
// For up down we use angles -45,45 as a sensitive zone
// We must be outside the two lines which are of tan(45),tan(-45) and this corrsponds to 1
unsigned int adX = abs(dX);
unsigned int adY = abs(dY);
if (adY <= adX) {
// X or B
if (dX < 0) {
newButton = 4;
} else {
newButton = 2;
}
} else {
// Y or A
if (dY < 0) {
newButton = 1;
} else {
newButton = 3;
}
}
} else {
newButton = 0;
}
if (button != newButton) {
// Release the previously pressed button, if any
if (button) {
buttonUp(buttons[button - 1]);
buttonUp(modifiers[button - 1]);
}
button = newButton;
// Press the new button
if (button) {
buttonDown(modifiers[button - 1]);
buttonDown(buttons[button - 1]);
}
}
}
void TouchControls::FunctionCenter::draw(uint8 alpha) {
// Draw background
{
Common::Rect clip(BG_WIDTH * 2, 0, 3 * BG_WIDTH, BG_HEIGHT);
parent->drawSurface(alpha, startX, startY, -clip.width() / 2, -clip.height() / 2, clip);
}
if (button == 0) {
return;
}
Common::Rect clip(BUTTON_WIDTH, BUTTON_HEIGHT);
int16 offX, offY;
if (button == 1) {
// Draw Y
clip.translate(BUTTON2_HL_LEFT, BUTTON2_HL_TOP);
offX = -1;
offY = -2;
} else if (button == 2) {
// Draw B
clip.translate(BUTTON2_HL_LEFT + BUTTON_WIDTH, BUTTON2_HL_TOP);
offX = 0;
offY = -1;
} else if (button == 3) {
// Draw A
clip.translate(BUTTON2_HL_LEFT, BUTTON2_HL_TOP + BUTTON_HEIGHT);
offX = -1;
offY = 0;
} else if (button == 4) {
// Draw X
clip.translate(BUTTON2_HL_LEFT + BUTTON_WIDTH, BUTTON2_HL_TOP + BUTTON_HEIGHT);
offX = -2;
offY = -1;
} else {
return;
}
// Always draw overlay as opaque
parent->drawSurface(ALPHA_OPAQUE, startX, startY, offX * clip.width() / 2, offY * clip.height() / 2, clip);
}