Move widgets disabled field to be a flag

3DS: Fix trying to connect to server using Direct Connect menu always showing 'invalid IP'
This commit is contained in:
UnknownShadow200 2023-08-02 20:41:50 +10:00
parent e070201c94
commit 2d58ce3893
6 changed files with 120 additions and 62 deletions

View File

@ -73,7 +73,8 @@ int Gui_Contains(int recX, int recY, int width, int height, int x, int y) {
int Gui_ContainsPointers(int x, int y, int width, int height) {
int i, px, py;
for (i = 0; i < Pointers_Count; i++) {
for (i = 0; i < Pointers_Count; i++)
{
px = Pointers[i].x; py = Pointers[i].y;
if (px >= x && py >= y && px < (x + width) && py < (y + height)) return true;
@ -112,7 +113,8 @@ static void LoseAllScreens(void) {
struct Screen* s;
int i;
for (i = 0; i < Gui.ScreensCount; i++) {
for (i = 0; i < Gui.ScreensCount; i++)
{
s = Gui_Screens[i];
s->VTABLE->ContextLost(s);
}
@ -122,7 +124,8 @@ static void OnContextRecreated(void* obj) {
struct Screen* s;
int i;
for (i = 0; i < Gui.ScreensCount; i++) {
for (i = 0; i < Gui.ScreensCount; i++)
{
s = Gui_Screens[i];
s->VTABLE->ContextRecreated(s);
s->dirty = true;
@ -134,7 +137,8 @@ void Gui_LayoutAll(void) {
struct Screen* s;
int i;
for (i = 0; i < Gui.ScreensCount; i++) {
for (i = 0; i < Gui.ScreensCount; i++)
{
s = Gui_Screens[i];
s->VTABLE->Layout(s);
s->dirty = true;
@ -158,11 +162,13 @@ static void Gui_AddCore(struct Screen* s, int priority) {
int i, j;
if (Gui.ScreensCount >= GUI_MAX_SCREENS) Logger_Abort("Hit max screens");
for (i = 0; i < Gui.ScreensCount; i++) {
for (i = 0; i < Gui.ScreensCount; i++)
{
if (priority <= priorities[i]) continue;
/* Shift lower priority screens right */
for (j = Gui.ScreensCount; j > i; j--) {
for (j = Gui.ScreensCount; j > i; j--)
{
Gui_Screens[j] = Gui_Screens[j - 1];
priorities[j] = priorities[j - 1];
}
@ -179,7 +185,8 @@ static void Gui_AddCore(struct Screen* s, int priority) {
s->VTABLE->Layout(s);
/* for selecting active button etc */
for (i = 0; i < Pointers_Count; i++) {
for (i = 0; i < Pointers_Count; i++)
{
s->VTABLE->HandlesPointerMove(s, i, Pointers[i].x, Pointers[i].y);
}
}
@ -187,7 +194,8 @@ static void Gui_AddCore(struct Screen* s, int priority) {
/* Returns index of the given screen in the screens list, -1 if not */
static int IndexOfScreen(struct Screen* s) {
int i;
for (i = 0; i < Gui.ScreensCount; i++) {
for (i = 0; i < Gui.ScreensCount; i++)
{
if (Gui_Screens[i] == s) return i;
}
return -1;
@ -197,7 +205,8 @@ void Gui_RemoveCore(struct Screen* s) {
int i = IndexOfScreen(s);
if (i == -1) return;
for (; i < Gui.ScreensCount - 1; i++) {
for (; i < Gui.ScreensCount - 1; i++)
{
Gui_Screens[i] = Gui_Screens[i + 1];
priorities[i] = priorities[i + 1];
}
@ -221,7 +230,8 @@ void Gui_Add(struct Screen* s, int priority) {
int i;
Gui_RemoveCore(s);
/* Backwards loop since removing changes count and gui_screens */
for (i = Gui.ScreensCount - 1; i >= 0; i--) {
for (i = Gui.ScreensCount - 1; i >= 0; i--)
{
if (priorities[i] == priority) Gui_RemoveCore(Gui_Screens[i]);
}
@ -231,7 +241,8 @@ void Gui_Add(struct Screen* s, int priority) {
struct Screen* Gui_GetInputGrab(void) {
int i;
for (i = 0; i < Gui.ScreensCount; i++) {
for (i = 0; i < Gui.ScreensCount; i++)
{
if (Gui_Screens[i]->grabsInput) return Gui_Screens[i];
}
return NULL;
@ -239,7 +250,8 @@ struct Screen* Gui_GetInputGrab(void) {
struct Screen* Gui_GetBlocksWorld(void) {
int i;
for (i = 0; i < Gui.ScreensCount; i++) {
for (i = 0; i < Gui.ScreensCount; i++)
{
if (Gui_Screens[i]->blocksWorld) return Gui_Screens[i];
}
return NULL;
@ -247,7 +259,8 @@ struct Screen* Gui_GetBlocksWorld(void) {
struct Screen* Gui_GetClosable(void) {
int i;
for (i = 0; i < Gui.ScreensCount; i++) {
for (i = 0; i < Gui.ScreensCount; i++)
{
if (Gui_Screens[i]->closable) return Gui_Screens[i];
}
return NULL;
@ -271,7 +284,8 @@ void Gui_RenderGui(double delta) {
int i;
/* Draw back to front so highest priority screen is on top */
for (i = Gui.ScreensCount - 1; i >= 0; i--) {
for (i = Gui.ScreensCount - 1; i >= 0; i--)
{
s = Gui_Screens[i];
s->VTABLE->Update(s, delta);
@ -295,7 +309,8 @@ void TextAtlas_Make(struct TextAtlas* atlas, const cc_string* chars, struct Font
width = Drawer2D_TextWidth(&args);
atlas->offset = width;
for (i = 0; i < chars->length; i++) {
for (i = 0; i < chars->length; i++)
{
args.text = String_UNSAFE_Substring(chars, i, 1);
charWidth = Drawer2D_TextWidth(&args);
@ -311,7 +326,8 @@ void TextAtlas_Make(struct TextAtlas* atlas, const cc_string* chars, struct Font
args.text = *prefix;
Context2D_DrawText(&ctx, &args, 0, 0);
for (i = 0; i < chars->length; i++) {
for (i = 0; i < chars->length; i++)
{
args.text = String_UNSAFE_Substring(chars, i, 1);
Context2D_DrawText(&ctx, &args, atlas->offsets[i], 0);
}
@ -347,7 +363,8 @@ void TextAtlas_AddInt(struct TextAtlas* atlas, int value, struct VertexTextured*
}
count = String_MakeUInt32((cc_uint32)value, digits);
for (i = count - 1; i >= 0; i--) {
for (i = count - 1; i >= 0; i--)
{
TextAtlas_Add(atlas, digits[i] - '0' , vertices);
}
}
@ -373,7 +390,7 @@ void Widget_CalcPosition(void* widget) {
void Widget_Reset(void* widget) {
struct Widget* w = (struct Widget*)widget;
w->active = false;
w->disabled = false;
w->flags = 0;
w->x = 0; w->y = 0;
w->width = 0; w->height = 0;
w->horAnchor = ANCHOR_MIN;
@ -387,6 +404,16 @@ int Widget_Contains(void* widget, int x, int y) {
return Gui_Contains(w->x, w->y, w->width, w->height, x, y);
}
void Widget_SetDisabled(void* widget, int disabled) {
struct Widget* w = (struct Widget*)widget;
if (disabled) {
w->flags |= WIDGET_FLAG_DISABLED;
} else {
w->flags &= ~WIDGET_FLAG_DISABLED;
}
}
/*########################################################################################################################*
*-------------------------------------------------------Screen base-------------------------------------------------------*
@ -399,7 +426,8 @@ void Screen_Render2Widgets(void* screen, double delta) {
Gfx_SetVertexFormat(VERTEX_FORMAT_TEXTURED);
Gfx_BindDynamicVb(s->vb);
for (i = 0; i < s->numWidgets; i++) {
for (i = 0; i < s->numWidgets; i++)
{
if (!widgets[i]) continue;
offset = Widget_Render2(widgets[i], offset);
}
@ -422,10 +450,11 @@ int Screen_DoPointerDown(void* screen, int id, int x, int y) {
int i, count = s->numWidgets;
/* iterate backwards (because last elements rendered are shown over others) */
for (i = count - 1; i >= 0; i--) {
for (i = count - 1; i >= 0; i--)
{
struct Widget* w = widgets[i];
if (!w || !Widget_Contains(w, x, y)) continue;
if (w->disabled) return i;
if (w->flags & WIDGET_FLAG_DISABLED) return i;
if (w->MenuClick) {
w->MenuClick(s, w);
@ -443,7 +472,8 @@ int Screen_Index(void* screen, void* widget) {
int i;
struct Widget* w = (struct Widget*)widget;
for (i = 0; i < s->numWidgets; i++) {
for (i = 0; i < s->numWidgets; i++)
{
if (widgets[i] == w) return i;
}
return -1;
@ -459,7 +489,8 @@ void Screen_BuildMesh(void* screen) {
data = Screen_LockVb(s);
ptr = &data;
for (i = 0; i < s->numWidgets; i++) {
for (i = 0; i < s->numWidgets; i++)
{
if (!widgets[i]) continue;
Widget_BuildMesh(widgets[i], ptr);
}
@ -471,7 +502,8 @@ void Screen_Layout(void* screen) {
struct Widget** widgets = s->widgets;
int i;
for (i = 0; i < s->numWidgets; i++) {
for (i = 0; i < s->numWidgets; i++)
{
if (!widgets[i]) continue;
Widget_Layout(widgets[i]);
}
@ -483,7 +515,8 @@ void Screen_ContextLost(void* screen) {
int i;
Gfx_DeleteDynamicVb(&s->vb);
for (i = 0; i < s->numWidgets; i++) {
for (i = 0; i < s->numWidgets; i++)
{
if (!widgets[i]) continue;
Elem_Free(widgets[i]);
}
@ -526,7 +559,8 @@ static void OnKeyPress(void* obj, int cp) {
char c;
if (!Convert_TryCodepointToCP437(cp, &c)) return;
for (i = 0; i < Gui.ScreensCount; i++) {
for (i = 0; i < Gui.ScreensCount; i++)
{
s = Gui_Screens[i];
if (s->VTABLE->HandlesKeyPress(s, c)) return;
}
@ -537,7 +571,8 @@ static void OnTextChanged(void* obj, const cc_string* str) {
struct Screen* s;
int i;
for (i = 0; i < Gui.ScreensCount; i++) {
for (i = 0; i < Gui.ScreensCount; i++)
{
s = Gui_Screens[i];
if (s->VTABLE->HandlesTextChanged(s, str)) return;
}

View File

@ -158,11 +158,16 @@ struct WidgetVTABLE {
#define Widget_Body const struct WidgetVTABLE* VTABLE; \
int x, y, width, height; /* Top left corner, and dimensions, of this widget */ \
cc_bool active; /* Whether this widget is currently being moused over */ \
cc_bool disabled; /* Whether widget is prevented from being interacted with */ \
cc_uint8 flags; /* Flags controlling the widget's interactability */ \
cc_uint8 horAnchor, verAnchor; /* The reference point for when this widget is resized */ \
int xOffset, yOffset; /* Offset from the reference point */ \
Widget_LeftClick MenuClick;
/* Whether a widget is prevented from being interacted with */
#define WIDGET_FLAG_DISABLED 0x01
/* Whether a widget can be selected via up/down */
#define WIDGET_FLAG_SELECTABLE 0x02
/* Represents an individual 2D gui component. */
struct Widget { Widget_Body };
void Widget_SetLocation(void* widget, cc_uint8 horAnchor, cc_uint8 verAnchor, int xOffset, int yOffset);
@ -173,6 +178,8 @@ void Widget_CalcPosition(void* widget);
void Widget_Reset(void* widget);
/* Returns non-zero if the given point is located within the bounds of the widget. */
int Widget_Contains(void* widget, int x, int y);
/* Sets whether the widget is prevented from being interacted with */
void Widget_SetDisabled(void* widget, int disabled);
/* Higher priority handles input first and draws on top */

View File

@ -246,8 +246,8 @@ static void ListScreen_UpdateTitle(struct ListScreen* s) {
static void ListScreen_UpdatePage(struct ListScreen* s) {
int end = s->entries.count - LIST_SCREEN_ITEMS;
s->left.disabled = s->currentIndex <= 0;
s->right.disabled = s->currentIndex >= end;
Widget_SetDisabled(&s->left, s->currentIndex <= 0);
Widget_SetDisabled(&s->right, s->currentIndex >= end);
ListScreen_UpdateTitle(s);
}
@ -261,7 +261,7 @@ static void ListScreen_RedrawEntries(struct ListScreen* s) {
for (i = 0; i < LIST_SCREEN_ITEMS; i++) {
str = ListScreen_UNSAFE_Get(s, s->currentIndex + i);
s->btns[i].disabled = String_CaselessEqualsConst(&str, LISTSCREEN_EMPTY);
Widget_SetDisabled(&s->btns[i], String_CaselessEqualsConst(&str, LISTSCREEN_EMPTY));
s->UpdateEntry(s, &s->btns[i], &str);
}
}
@ -467,7 +467,9 @@ static struct Widget* pause_widgets[] = {
static void PauseScreen_CheckHacksAllowed(void* screen) {
struct PauseScreen* s = (struct PauseScreen*)screen;
if (Gui.ClassicMenu) return;
s->btns[4].disabled = !LocalPlayer_Instance.Hacks.CanAnyHacks; /* select texture pack */
Widget_SetDisabled(&s->btns[4],
!LocalPlayer_Instance.Hacks.CanAnyHacks); /* select texture pack */
s->dirty = true;
}
@ -509,8 +511,8 @@ static void PauseScreen_Init(void* screen) {
PauseScreenBase_Init(s, 300);
if (Server.IsSinglePlayer) return;
s->btns[1].disabled = true;
s->btns[2].disabled = true;
s->btns[1].flags = WIDGET_FLAG_DISABLED;
s->btns[2].flags = WIDGET_FLAG_DISABLED;
}
static void PauseScreen_Free(void* screen) {
@ -576,8 +578,8 @@ static void ClassicPauseScreen_Init(void* screen) {
PauseScreenBase_Init(s, 400);
if (Server.IsSinglePlayer) return;
s->btns[1].disabled = true;
s->btns[3].disabled = true;
s->btns[1].flags = WIDGET_FLAG_DISABLED;
s->btns[3].flags = WIDGET_FLAG_DISABLED;
}
static const struct ScreenVTABLE ClassicPauseScreen_VTABLE = {
@ -640,7 +642,8 @@ static const struct SimpleButtonDesc optsGroup_btns[8] = {
static void OptionsGroupScreen_CheckHacksAllowed(void* screen) {
struct OptionsGroupScreen* s = (struct OptionsGroupScreen*)screen;
s->btns[6].disabled = !LocalPlayer_Instance.Hacks.CanAnyHacks; /* env settings */
Widget_SetDisabled(&s->btns[6],
!LocalPlayer_Instance.Hacks.CanAnyHacks); /* env settings */
s->dirty = true;
}
@ -1890,8 +1893,8 @@ static void KeyBindsScreen_Init(void* screen) {
ButtonWidget_Init(&s->left, 40, s->leftPage);
ButtonWidget_Init(&s->right, 40, s->rightPage);
s->left.disabled = !s->leftPage;
s->right.disabled = !s->rightPage;
Widget_SetDisabled(&s->left, !s->leftPage);
Widget_SetDisabled(&s->right, !s->rightPage);
if (!s->leftPage && !s->rightPage) return;
s->numWidgets += 2;
@ -2297,7 +2300,9 @@ static void MenuOptionsScreen_SelectExtHelp(struct MenuOptionsScreen* s, int idx
if (s->activeI >= 0) return;
desc = s->descriptions[idx];
if (!desc) return;
if (!s->widgets[idx] || s->widgets[idx]->disabled) return;
if (!s->widgets[idx]) return;
if (s->widgets[idx]->flags & WIDGET_FLAG_DISABLED) return;
descRaw = String_FromReadonly(desc);
s->extHelp.lines = String_UNSAFE_Split(&descRaw, '\n', descLines, Array_Elems(descLines));
@ -2386,10 +2391,11 @@ static void MenuOptionsScreen_Enum(void* screen, void* widget) {
static void MenuInputOverlay_CheckStillValid(struct MenuOptionsScreen* s) {
if (s->activeI == -1) return;
if (!s->widgets[s->activeI]->disabled) return;
/* source button is disabled now, so close open input overlay */
MenuInputOverlay_Close(&MenuInputOverlay, false);
if (s->widgets[s->activeI]->flags & WIDGET_FLAG_DISABLED) {
/* source button is disabled now, so close open input overlay */
MenuInputOverlay_Close(&MenuInputOverlay, false);
}
}
static void MenuOptionsScreen_Input(void* screen, void* widget) {
@ -2983,10 +2989,10 @@ static void HacksSettingsScreen_CheckHacksAllowed(struct MenuOptionsScreen* s) {
struct LocalPlayer* p = &LocalPlayer_Instance;
cc_bool disabled = !p->Hacks.Enabled;
widgets[3]->disabled = disabled || !p->Hacks.CanSpeed;
widgets[4]->disabled = disabled || !p->Hacks.CanSpeed;
widgets[5]->disabled = disabled || !p->Hacks.CanSpeed;
widgets[7]->disabled = disabled || !p->Hacks.CanPushbackBlocks;
Widget_SetDisabled(widgets[3], disabled || !p->Hacks.CanSpeed);
Widget_SetDisabled(widgets[4], disabled || !p->Hacks.CanSpeed);
Widget_SetDisabled(widgets[5], disabled || !p->Hacks.CanSpeed);
Widget_SetDisabled(widgets[7], disabled || !p->Hacks.CanPushbackBlocks);
MenuInputOverlay_CheckStillValid(s);
}
@ -3248,7 +3254,8 @@ void NostalgiaAppearanceScreen_Show(void) {
*----------------------------------------------NostalgiaFunctionalityScreen-----------------------------------------------*
*#########################################################################################################################*/
static void NostalgiaScreen_UpdateVersionDisabled(void) {
MenuOptionsScreen_Instance.buttons[3].disabled = Game_Version.HasCPE;
struct ButtonWidget* gameVerBtn = &MenuOptionsScreen_Instance.buttons[3];
Widget_SetDisabled(gameVerBtn, Game_Version.HasCPE);
}
static void NostalgiaScreen_GetTexs(cc_string* v) { Menu_GetBool(v, Game_AllowServerTextures); }
@ -3872,8 +3879,8 @@ static void TouchOnscreen_SetPage(struct TouchOnscreenScreen* s, cc_bool page1)
s->btnDescs = page1 ? touchOnscreen_page1 : touchOnscreen_page2;
Menu_InitButtons(s->btns, 200, s->btnDescs, ONSCREEN_PAGE_BTNS);
s->left.disabled = page1;
s->right.disabled = !page1;
Widget_SetDisabled(&s->left, page1);
Widget_SetDisabled(&s->right, !page1);
}
static void TouchOnscreen_Left(void* screen, void* b) {

View File

@ -1425,7 +1425,7 @@ void ChatScreen_OpenInput(const cc_string* text) {
args.placeholder = "Enter chat";
args.multiline = true;
Window_OpenKeyboard(&args);
s->input.base.disabled = args.opaque;
Widget_SetDisabled(&s->input.base, args.opaque);
String_Copy(&s->input.base.text, text);
InputWidget_UpdateText(&s->input.base);
@ -1972,7 +1972,7 @@ static void DisconnectScreen_UpdateReconnect(struct DisconnectScreen* s) {
if (secsLeft > 0) {
String_Format1(&msg, "Reconnect in %i", &secsLeft);
}
s->reconnect.disabled = secsLeft > 0;
Widget_SetDisabled(&s->reconnect, secsLeft > 0);
}
if (!msg.length) String_AppendConst(&msg, "Reconnect");
@ -2015,7 +2015,7 @@ static void DisconnectScreen_Init(void* screen) {
ButtonWidget_Init(&s->reconnect, 300, DisconnectScreen_OnReconnect);
ButtonWidget_Init(&s->quit, 300, DisconnectScreen_OnQuit);
s->reconnect.disabled = !s->canReconnect;
if (!s->canReconnect) s->reconnect.flags = WIDGET_FLAG_DISABLED;
/* NOTE: changing VSync can't be done within frame, causes crash on some GPUs */
Gfx_SetFpsLimit(Game_FpsLimit == FPS_LIMIT_VSYNC, 1000 / 5.0f);
@ -2192,7 +2192,8 @@ static void TouchScreen_InitButtons(struct TouchScreen* s) {
desc = &onscreenDescs[i];
ButtonWidget_Init(&s->onscreen[j], 100, desc->OnClick);
if (desc->enabled) s->onscreen[j].disabled = !(*desc->enabled);
if (desc->enabled) Widget_SetDisabled(&s->onscreen[j], !(*desc->enabled));
s->onscreenDescs[j] = desc;
s->widgets[j] = (struct Widget*)&s->onscreen[j];
j++;

View File

@ -127,7 +127,7 @@ static void ButtonWidget_Render(void* widget, double delta) {
float scale;
back = w->active ? btnSelectedTex : btnShadowTex;
if (w->disabled) back = btnDisabledTex;
if (w->flags & WIDGET_FLAG_DISABLED) back = btnDisabledTex;
back.ID = Gui.ClassicTexture ? Gui.GuiClassicTex : Gui.GuiTex;
back.X = w->x; back.Width = w->width;
@ -152,7 +152,8 @@ static void ButtonWidget_Render(void* widget, double delta) {
}
if (!w->tex.ID) return;
color = w->disabled ? disabledColor : (w->active ? activeColor : normColor);
color = (w->flags & WIDGET_FLAG_DISABLED) ? disabledColor
: (w->active ? activeColor : normColor);
Texture_RenderShaded(&w->tex, color);
}
@ -167,7 +168,8 @@ static void ButtonWidget_BuildMesh(void* widget, struct VertexTextured** vertice
float scale;
back = w->active ? btnSelectedTex : btnShadowTex;
if (w->disabled) back = btnDisabledTex;
if (w->flags & WIDGET_FLAG_DISABLED) back = btnDisabledTex;
back.X = w->x; back.Width = w->width;
back.Y = w->y; back.Height = w->height;
@ -189,7 +191,8 @@ static void ButtonWidget_BuildMesh(void* widget, struct VertexTextured** vertice
Gfx_Make2DQuad(&back, w->col, vertices);
}
color = w->disabled ? disabledColor : (w->active ? activeColor : normColor);
color = (w->flags & WIDGET_FLAG_DISABLED) ? disabledColor
: (w->active ? activeColor : normColor);
Gfx_Make2DQuad(&w->tex, color, vertices);
}
@ -222,6 +225,7 @@ void ButtonWidget_Init(struct ButtonWidget* w, int minWidth, Widget_LeftClick on
w->VTABLE = &ButtonWidget_VTABLE;
w->col = PACKEDCOL_WHITE;
w->optName = NULL;
w->flags = WIDGET_FLAG_SELECTABLE;
w->minWidth = Display_ScaleX(minWidth);
w->minHeight = Display_ScaleY(40);
w->MenuClick = onClick;
@ -1640,6 +1644,7 @@ void TextInputWidget_Create(struct TextInputWidget* w, int width, const cc_strin
w->base.convertPercents = false;
w->base.padding = 3;
w->base.showCaret = !Input_TouchMode;
w->base.flags = WIDGET_FLAG_SELECTABLE;
w->base.GetMaxLines = TextInputWidget_GetMaxLines;
w->base.RemakeTexture = TextInputWidget_RemakeTexture;
@ -1713,7 +1718,7 @@ static void ChatInputWidget_RemakeTexture(void* widget) {
if (!width) width = w->prefixWidth;
if (!height) height = w->lineHeight;
if (w->disabled) {
if (w->flags & WIDGET_FLAG_DISABLED) {
Gfx_DeleteTexture(&w->inputTex.ID);
} else {
ChatInputWidget_MakeTexture(w, width, height);
@ -1733,7 +1738,7 @@ static void ChatInputWidget_Render(void* widget, double delta) {
int x = w->x, y = w->y;
cc_bool caretAtEnd;
int i, width;
if (w->disabled) return;
if (w->flags & WIDGET_FLAG_DISABLED) return;
for (i = 0; i < INPUTWIDGET_MAX_LINES; i++) {
if (i > 0 && !w->lines[i].length) break;

View File

@ -301,7 +301,10 @@ CC_NOINLINE void SpecialInputWidget_UpdateCols(struct SpecialInputWidget* w);
CC_NOINLINE void SpecialInputWidget_SetActive(struct SpecialInputWidget* w, cc_bool active);
#ifdef CC_BUILD_TOUCH
struct ThumbstickWidget { Widget_Body; float scale; };
struct ThumbstickWidget {
Widget_Body
float scale;
};
#define THUMBSTICKWIDGET_PER (4 * 4)
#define THUMBSTICKWIDGET_MAX (THUMBSTICKWIDGET_PER * 2)