summary refs log tree commit diff
path: root/src/TouchUI.c
diff options
context:
space:
mode:
authorWlodekM <[email protected]>2024-06-16 10:35:45 +0300
committerWlodekM <[email protected]>2024-06-16 10:35:45 +0300
commitabef6da56913f1c55528103e60a50451a39628b1 (patch)
treeb3c8092471ecbb73e568cd0d336efa0e7871ee8d /src/TouchUI.c
initial commit
Diffstat (limited to 'src/TouchUI.c')
-rw-r--r--src/TouchUI.c798
1 files changed, 798 insertions, 0 deletions
diff --git a/src/TouchUI.c b/src/TouchUI.c
new file mode 100644
index 0000000..11ff717
--- /dev/null
+++ b/src/TouchUI.c
@@ -0,0 +1,798 @@
+#include "Screens.h"
+#ifdef CC_BUILD_TOUCH
+#include "Widgets.h"
+#include "Game.h"
+#include "Event.h"
+#include "Platform.h"
+#include "Inventory.h"
+#include "Drawer2D.h"
+#include "Graphics.h"
+#include "Funcs.h"
+#include "TexturePack.h"
+#include "Model.h"
+#include "Generator.h"
+#include "Server.h"
+#include "Chat.h"
+#include "ExtMath.h"
+#include "Window.h"
+#include "Camera.h"
+#include "Http.h"
+#include "Block.h"
+#include "Menus.h"
+#include "World.h"
+#include "Input.h"
+#include "Utils.h"
+#include "Options.h"
+
+
+/* Enumeration of on-screen buttons for touch GUI */
+#define ONSCREEN_BTN_CHAT      (1 << 0)
+#define ONSCREEN_BTN_LIST      (1 << 1)
+#define ONSCREEN_BTN_SPAWN     (1 << 2)
+#define ONSCREEN_BTN_SETSPAWN  (1 << 3)
+#define ONSCREEN_BTN_FLY       (1 << 4)
+#define ONSCREEN_BTN_NOCLIP    (1 << 5)
+#define ONSCREEN_BTN_SPEED     (1 << 6)
+#define ONSCREEN_BTN_HALFSPEED (1 << 7)
+#define ONSCREEN_BTN_CAMERA    (1 << 8)
+#define ONSCREEN_BTN_DELETE    (1 << 9)
+#define ONSCREEN_BTN_PICK      (1 << 10)
+#define ONSCREEN_BTN_PLACE     (1 << 11)
+#define ONSCREEN_BTN_SWITCH    (1 << 12)
+#define ONSCREEN_MAX_BTNS 13
+
+static int GetOnscreenButtons(void) {
+	#define DEFAULT_SP_ONSCREEN (ONSCREEN_BTN_FLY | ONSCREEN_BTN_SPEED)
+	#define DEFAULT_MP_ONSCREEN (ONSCREEN_BTN_FLY | ONSCREEN_BTN_SPEED | ONSCREEN_BTN_CHAT)
+	
+	return Options_GetInt(OPT_TOUCH_BUTTONS, 0, Int32_MaxValue,
+							Server.IsSinglePlayer ? DEFAULT_SP_ONSCREEN : DEFAULT_MP_ONSCREEN);
+}
+
+static int GetOnscreenHAligns(void) {
+	return Options_GetInt(OPT_TOUCH_HALIGN, 0, Int32_MaxValue, 0);
+}
+
+/*########################################################################################################################*
+*---------------------------------------------------TouchControlsScreen---------------------------------------------------*
+*#########################################################################################################################*/
+#define ONSCREEN_PAGE_BTNS 4
+#define ONSCREEN_NUM_PAGES 4
+static struct TouchOnscreenScreen {
+	Screen_Body
+	struct ButtonWidget back, left, right;
+	struct ButtonWidget btns[ONSCREEN_PAGE_BTNS];
+	struct FontDesc font;
+	int page;
+} TouchOnscreenScreen;
+
+static struct Widget* touchOnscreen_widgets[3 + ONSCREEN_PAGE_BTNS];
+
+static const char* const touchOnscreen[ONSCREEN_PAGE_BTNS * ONSCREEN_NUM_PAGES] = {
+	"Chat", "Tablist", "Spawn", "Set spawn",
+	"Fly",  "Noclip",  "Speed", "Half speed",
+	"Third person", "Delete", "Pick", "Place",
+	"Switch hotbar", "---", "---", "---",
+};
+
+static void TouchOnscreen_UpdateButton(struct TouchOnscreenScreen* s, struct ButtonWidget* btn) {
+	PackedCol grey = PackedCol_Make(0x7F, 0x7F, 0x7F, 0xFF);
+	int buttons = GetOnscreenButtons();
+	int haligns = GetOnscreenHAligns();
+	const char* label;
+	char buffer[64];
+	cc_string str;
+	int bit;
+
+	bit        = 1 << btn->meta.val;
+	btn->color = (buttons & bit) ? PACKEDCOL_WHITE : grey;
+	
+	String_InitArray(str, buffer);
+	label = touchOnscreen[btn->meta.val];
+	
+	if ((buttons & bit) && (haligns & bit)) {
+		String_Format1(&str, "%c: Left aligned",  label);
+	} else if (buttons & bit) {
+		String_Format1(&str, "%c: Right aligned", label);
+	} else {
+		String_AppendConst(&str, label);
+	}
+	ButtonWidget_Set(btn, &str, &s->font);
+}
+
+static void TouchOnscreen_UpdateAll(struct TouchOnscreenScreen* s) {
+	int i;
+	for (i = 0; i < ONSCREEN_PAGE_BTNS; i++) 
+	{
+		TouchOnscreen_UpdateButton(s, &s->btns[i]);
+	}
+}
+
+static void TouchOnscreen_Any(void* screen, void* w) {
+	struct TouchOnscreenScreen* s = (struct TouchOnscreenScreen*)screen;
+	struct ButtonWidget* btn      = (struct ButtonWidget*)w;
+	int buttons = GetOnscreenButtons();
+	int haligns = GetOnscreenHAligns();
+	int bit = 1 << btn->meta.val;
+	
+	if ((buttons & bit) & (haligns & bit)) {
+		buttons &= ~bit;
+		haligns &= ~bit;
+	} else if (buttons & bit) {
+		haligns |= bit;
+	} else {
+		buttons |= bit;
+	}
+
+	Options_SetInt(OPT_TOUCH_BUTTONS, buttons);
+	Options_SetInt(OPT_TOUCH_HALIGN,  haligns);
+	TouchOnscreen_UpdateButton(s, btn);
+	TouchScreen_Refresh();
+}
+static void TouchOnscreen_More(void* s, void* w) { TouchCtrlsScreen_Show(); }
+
+static void TouchOnscreen_Left(void* screen,  void* b);
+static void TouchOnscreen_Right(void* screen, void* b);
+
+static void TouchOnscreen_RemakeWidgets(struct TouchOnscreenScreen* s) {
+	int i;
+	int offset    = s->page * ONSCREEN_PAGE_BTNS;
+	s->widgets    = touchOnscreen_widgets;
+	s->numWidgets = 0;
+	s->maxWidgets = Array_Elems(touchOnscreen_widgets);
+
+	for (i = 0; i < ONSCREEN_PAGE_BTNS; i++) 
+	{
+		ButtonWidget_Add(s, &s->btns[i], 300, TouchOnscreen_Any);
+		s->btns[i].meta.val = i + offset;
+	}
+	ButtonWidget_Add(s, &s->back, 400, TouchOnscreen_More);
+	ButtonWidget_Add(s, &s->left,  40, TouchOnscreen_Left);
+	ButtonWidget_Add(s, &s->right, 40, TouchOnscreen_Right);
+
+	Widget_SetDisabled(&s->left,  s->page == 0);
+	Widget_SetDisabled(&s->right, s->page == ONSCREEN_NUM_PAGES - 1);
+}
+
+static void TouchOnscreen_Left(void* screen, void* b) {
+	struct TouchOnscreenScreen* s = (struct TouchOnscreenScreen*)screen;
+	s->page--;
+	TouchOnscreen_RemakeWidgets(s);
+	Gui_Refresh((struct Screen*)s);
+	TouchOnscreen_UpdateAll(s);
+}
+
+static void TouchOnscreen_Right(void* screen, void* b) {
+	struct TouchOnscreenScreen* s = (struct TouchOnscreenScreen*)screen;
+	s->page++;
+	TouchOnscreen_RemakeWidgets(s);
+	Gui_Refresh((struct Screen*)s);
+	TouchOnscreen_UpdateAll(s);
+}
+
+static void TouchOnscreenScreen_ContextRecreated(void* screen) {
+	struct TouchOnscreenScreen* s = (struct TouchOnscreenScreen*)screen;
+	Screen_UpdateVb(screen);
+	TouchOnscreen_UpdateAll(s);
+	ButtonWidget_SetConst(&s->back,  "Done", &s->font);
+	ButtonWidget_SetConst(&s->left,  "<",    &s->font);
+	ButtonWidget_SetConst(&s->right, ">",    &s->font);
+}
+
+static void TouchOnscreenScreen_Layout(void* screen) {
+	struct TouchOnscreenScreen* s = (struct TouchOnscreenScreen*)screen;
+	int i;
+	for (i = 0; i < ONSCREEN_PAGE_BTNS; i++) 
+	{
+		Widget_SetLocation(&s->btns[i], ANCHOR_CENTRE, ANCHOR_CENTRE, 0, -75 + i * 50);
+	}
+	
+	Menu_LayoutBack(&s->back);
+	Widget_SetLocation(&s->left,  ANCHOR_CENTRE, ANCHOR_CENTRE, -220, 0);
+	Widget_SetLocation(&s->right, ANCHOR_CENTRE, ANCHOR_CENTRE,  220, 0);
+}
+
+static void TouchOnscreenScreen_Init(void* screen) {
+	struct TouchOnscreenScreen* s = (struct TouchOnscreenScreen*)screen;
+	s->page = 0;
+	Gui_MakeTitleFont(&s->font);
+	TouchOnscreen_RemakeWidgets(s);
+	TouchOnscreen_UpdateAll(screen);
+
+	s->maxVertices = Screen_CalcDefaultMaxVertices(s);
+}
+
+static void TouchOnscreenScreen_Free(void* screen) {
+	struct TouchOnscreenScreen* s = (struct TouchOnscreenScreen*)screen;
+	Font_Free(&s->font);
+}
+
+static const struct ScreenVTABLE TouchOnscreenScreen_VTABLE = {
+	TouchOnscreenScreen_Init,   Screen_NullUpdate, TouchOnscreenScreen_Free,
+	MenuScreen_Render2,         Screen_BuildMesh,
+	Menu_InputDown,             Screen_InputUp,    Screen_TKeyPress, Screen_TText,
+	Menu_PointerDown,           Screen_PointerUp,  Menu_PointerMove, Screen_TMouseScroll,
+	TouchOnscreenScreen_Layout, Screen_ContextLost, TouchOnscreenScreen_ContextRecreated
+};
+void TouchOnscreenScreen_Show(void) {
+	struct TouchOnscreenScreen* s = &TouchOnscreenScreen;
+	s->grabsInput = true;
+	s->closable   = true;
+	s->VTABLE     = &TouchOnscreenScreen_VTABLE;
+
+	Gui_Add((struct Screen*)s, GUI_PRIORITY_TOUCHMORE);
+}
+
+
+/*########################################################################################################################*
+*---------------------------------------------------TouchControlsScreen---------------------------------------------------*
+*#########################################################################################################################*/
+#define TOUCHCTRLS_BTNS 5
+static struct TouchCtrlsScreen {
+	Screen_Body
+	struct ButtonWidget back;
+	struct ButtonWidget btns[TOUCHCTRLS_BTNS];
+	struct FontDesc font;
+} TouchCtrlsScreen;
+
+static struct Widget* touchCtrls_widgets[TOUCHCTRLS_BTNS + 1];
+
+static const char* GetTapDesc(int mode) {
+	if (mode == INPUT_MODE_PLACE)  return "Tap: Place";
+	if (mode == INPUT_MODE_DELETE) return "Tap: Delete";
+	return "Tap: None";
+}
+static void TouchCtrls_UpdateTapText(void* screen) {
+	struct TouchCtrlsScreen* s = (struct TouchCtrlsScreen*)screen;
+	ButtonWidget_SetConst(&s->btns[0], GetTapDesc(Input_TapMode),  &s->font);
+	s->dirty = true;
+}
+
+static const char* GetHoldDesc(int mode) {
+	if (mode == INPUT_MODE_PLACE)  return "Hold: Place";
+	if (mode == INPUT_MODE_DELETE) return "Hold: Delete";
+	return "Hold: None";
+}
+static void TouchCtrls_UpdateHoldText(void* screen) {
+	struct TouchCtrlsScreen* s = (struct TouchCtrlsScreen*)screen;
+	ButtonWidget_SetConst(&s->btns[1], GetHoldDesc(Input_HoldMode), &s->font);
+	s->dirty = true;
+}
+
+static void TouchCtrls_UpdateSensitivity(void* screen) {
+	cc_string value; char valueBuffer[STRING_SIZE];
+	struct TouchCtrlsScreen* s = (struct TouchCtrlsScreen*)screen;
+	String_InitArray(value, valueBuffer);
+
+	String_Format1(&value, "Sensitivity: %i", &Camera.Sensitivity);
+	ButtonWidget_Set(&s->btns[2], &value, &s->font);
+	s->dirty = true;
+}
+
+static void TouchCtrls_UpdateScale(void* screen) {
+	cc_string value; char valueBuffer[STRING_SIZE];
+	struct TouchCtrlsScreen* s = (struct TouchCtrlsScreen*)screen;
+	String_InitArray(value, valueBuffer);
+
+	String_AppendConst(&value, "Scale: ");
+	String_AppendFloat(&value, Gui.RawTouchScale, 1);
+	ButtonWidget_Set(&s->btns[3], &value, &s->font);
+	s->dirty = true;
+}
+
+static void TouchCtrls_More(void* s,     void* w) { TouchMoreScreen_Show(); }
+static void TouchCtrls_Onscreen(void* s, void* w) { TouchOnscreenScreen_Show(); }
+
+static void TouchCtrls_Tap(void* s, void* w) {
+	Input_TapMode  = (Input_TapMode  + 1) % INPUT_MODE_COUNT;
+	TouchCtrls_UpdateTapText(s);
+}
+static void TouchCtrls_Hold(void* s, void* w) {
+	Input_HoldMode = (Input_HoldMode + 1) % INPUT_MODE_COUNT;
+	TouchCtrls_UpdateHoldText(s);
+}
+
+static void TouchCtrls_SensitivityDone(const cc_string* value, cc_bool valid) {
+	int sensitivity;
+	if (!valid) return;
+	
+	Convert_ParseInt(value, &sensitivity);
+	Camera.Sensitivity = sensitivity;
+	Options_Set(OPT_SENSITIVITY, value);
+	TouchCtrls_UpdateSensitivity(&TouchCtrlsScreen);
+}
+
+static void TouchCtrls_Sensitivity(void* screen, void* w) {
+	struct TouchCtrlsScreen* s = (struct TouchCtrlsScreen*)screen;
+	static struct MenuInputDesc desc;
+	cc_string value; char valueBuffer[STRING_SIZE];
+	String_InitArray(value, valueBuffer);
+
+	MenuInput_Int(desc, 1, 200, 30);
+	String_AppendInt(&value, Camera.Sensitivity);
+	MenuInputOverlay_Show(&desc, &value, TouchCtrls_SensitivityDone, true);
+	/* Fix Sensitivity button getting stuck as 'active' */
+	/* (input overlay swallows subsequent pointer events) */
+	s->btns[2].active = 0;
+}
+
+static void TouchCtrls_ScaleDone(const cc_string* value, cc_bool valid) {
+	if (!valid) return;
+	Convert_ParseFloat(value, &Gui.RawTouchScale);
+	Options_Set(OPT_TOUCH_SCALE, value);
+	
+	TouchCtrls_UpdateScale(&TouchCtrlsScreen);
+	Gui_LayoutAll();
+}
+
+static void TouchCtrls_Scale(void* screen, void* w) {
+	struct TouchCtrlsScreen* s = (struct TouchCtrlsScreen*)screen;
+	static struct MenuInputDesc desc;
+	cc_string value; char valueBuffer[STRING_SIZE];
+	String_InitArray(value, valueBuffer);
+
+	MenuInput_Float(desc, 0.25f, 5.0f, 1.0f);
+	String_AppendFloat(&value, Gui.RawTouchScale, 1);
+	MenuInputOverlay_Show(&desc, &value, TouchCtrls_ScaleDone, true);
+	s->btns[3].active = 0;
+}
+
+static const struct SimpleButtonDesc touchCtrls_btns[5] = {
+	{ -102,  -50, "",     TouchCtrls_Tap  },
+	{  102,  -50, "",     TouchCtrls_Hold },
+	{ -102,    0, "",     TouchCtrls_Sensitivity },
+	{  102,    0, "",     TouchCtrls_Scale },
+	{    0,   50, "On-screen controls", TouchCtrls_Onscreen }
+};
+
+static void TouchCtrlsScreen_ContextLost(void* screen) {
+	struct TouchCtrlsScreen* s = (struct TouchCtrlsScreen*)screen;
+	Font_Free(&s->font);
+	Screen_ContextLost(screen);
+}
+
+static void TouchCtrlsScreen_ContextRecreated(void* screen) {
+	struct TouchCtrlsScreen* s = (struct TouchCtrlsScreen*)screen;
+	Gui_MakeTitleFont(&s->font);
+	Screen_UpdateVb(screen);
+	Menu_SetButtons(s->btns, &s->font, touchCtrls_btns, TOUCHCTRLS_BTNS);
+	ButtonWidget_SetConst(&s->back, "Done", &s->font);
+
+	TouchCtrls_UpdateTapText(s);
+	TouchCtrls_UpdateHoldText(s);
+	TouchCtrls_UpdateSensitivity(s);
+	TouchCtrls_UpdateScale(s);
+}
+
+static void TouchCtrlsScreen_Layout(void* screen) {
+	struct TouchCtrlsScreen* s = (struct TouchCtrlsScreen*)screen;
+	Menu_LayoutButtons(s->btns, touchCtrls_btns, TOUCHCTRLS_BTNS);
+	Menu_LayoutBack(&s->back);
+}
+
+static void TouchCtrlsScreen_Init(void* screen) {
+	struct TouchCtrlsScreen* s = (struct TouchCtrlsScreen*)screen;
+	s->widgets     = touchCtrls_widgets;
+	s->numWidgets  = 0;
+	s->maxWidgets  = Array_Elems(touchCtrls_widgets);
+
+	Menu_AddButtons(s,  s->btns,     195, touchCtrls_btns,     4);
+	Menu_AddButtons(s,  s->btns + 4, 400, touchCtrls_btns + 4, 1);
+	ButtonWidget_Add(s, &s->back,    400, TouchCtrls_More);
+
+	s->maxVertices = Screen_CalcDefaultMaxVertices(s);
+}
+
+static const struct ScreenVTABLE TouchCtrlsScreen_VTABLE = {
+	TouchCtrlsScreen_Init,   Screen_NullUpdate, Screen_NullFunc,
+	MenuScreen_Render2,      Screen_BuildMesh,
+	Menu_InputDown,          Screen_InputUp,    Screen_TKeyPress, Screen_TText,
+	Menu_PointerDown,        Screen_PointerUp,  Menu_PointerMove, Screen_TMouseScroll,
+	TouchCtrlsScreen_Layout, TouchCtrlsScreen_ContextLost, TouchCtrlsScreen_ContextRecreated
+};
+void TouchCtrlsScreen_Show(void) {
+	struct TouchCtrlsScreen* s = &TouchCtrlsScreen;
+	s->grabsInput = true;
+	s->closable   = true;
+	s->VTABLE     = &TouchCtrlsScreen_VTABLE;
+
+	Gui_Add((struct Screen*)s, GUI_PRIORITY_TOUCHMORE);
+}
+
+
+/*########################################################################################################################*
+*-----------------------------------------------------TouchMoreScreen-----------------------------------------------------*
+*#########################################################################################################################*/
+#define TOUCHMORE_BTNS 6
+static struct TouchMoreScreen {
+	Screen_Body
+	struct ButtonWidget back;
+	struct ButtonWidget btns[TOUCHMORE_BTNS];
+} TouchMoreScreen;
+
+static struct Widget* touchMore_widgets[TOUCHMORE_BTNS + 1];
+
+static void TouchMore_Take(void* s, void* w) {
+	Gui_Remove((struct Screen*)&TouchMoreScreen);
+	Game_ScreenshotRequested = true;
+}
+static void TouchMore_Screen(void* s, void* w) {
+	Gui_Remove((struct Screen*)&TouchMoreScreen);
+	Game_ToggleFullscreen();
+}
+static void TouchMore_Ctrls(void* s, void* w) { TouchCtrlsScreen_Show(); }
+static void TouchMore_Menu(void* s, void* w) {
+	Gui_Remove((struct Screen*)&TouchMoreScreen);
+	Gui_ShowPauseMenu();
+}
+static void TouchMore_Game(void* s, void* w) { 
+	Gui_Remove((struct Screen*)&TouchMoreScreen);
+}
+static void TouchMore_Chat(void* s, void* w) {
+	Gui_Remove((struct Screen*)&TouchMoreScreen);
+	ChatScreen_OpenInput(&String_Empty);
+}
+static void TouchMore_Fog(void* s, void* w) { Game_CycleViewDistance(); }
+
+static const struct SimpleButtonDesc touchMore_btns[TOUCHMORE_BTNS] = {
+	{ -102, -50, "Screenshot", TouchMore_Take   },
+	{ -102,   0, "Fullscreen", TouchMore_Screen },
+	{  102, -50, "Chat",       TouchMore_Chat   },
+	{  102,   0, "Fog",        TouchMore_Fog    },
+	{    0,  50, "Controls",   TouchMore_Ctrls  },
+	{    0, 100, "Main menu",  TouchMore_Menu   }
+};
+
+static void TouchMoreScreen_ContextRecreated(void* screen) {
+	struct TouchMoreScreen* s = (struct TouchMoreScreen*)screen;
+	struct FontDesc titleFont;
+	Gui_MakeTitleFont(&titleFont);
+	Screen_UpdateVb(screen);
+
+	Menu_SetButtons(s->btns, &titleFont, touchMore_btns, TOUCHMORE_BTNS);
+	ButtonWidget_SetConst(&s->back, "Back to game", &titleFont);
+	Font_Free(&titleFont);
+}
+
+static void TouchMoreScreen_Layout(void* screen) {
+	struct TouchMoreScreen* s = (struct TouchMoreScreen*)screen;
+	Menu_LayoutButtons(s->btns, touchMore_btns, TOUCHMORE_BTNS);
+	Menu_LayoutBack(&s->back);
+}
+
+static void TouchMoreScreen_Init(void* screen) {
+	struct TouchMoreScreen* s = (struct TouchMoreScreen*)screen;
+	s->widgets     = touchMore_widgets;
+	s->numWidgets  = 0;
+	s->maxWidgets  = Array_Elems(touchMore_widgets);
+
+	Menu_AddButtons(s,  s->btns,     195, touchMore_btns,     4);
+	Menu_AddButtons(s,  s->btns + 4, 400, touchMore_btns + 4, 2);
+	ButtonWidget_Add(s, &s->back,    400, TouchMore_Game);
+
+	s->maxVertices = Screen_CalcDefaultMaxVertices(s);
+}
+
+static const struct ScreenVTABLE TouchMoreScreen_VTABLE = {
+	TouchMoreScreen_Init,   Screen_NullUpdate, Screen_NullFunc,
+	MenuScreen_Render2,     Screen_BuildMesh,
+	Menu_InputDown,         Screen_InputUp,    Screen_TKeyPress, Screen_TText,
+	Menu_PointerDown,       Screen_PointerUp,  Menu_PointerMove, Screen_TMouseScroll,
+	TouchMoreScreen_Layout, Screen_ContextLost, TouchMoreScreen_ContextRecreated
+};
+void TouchMoreScreen_Show(void) {
+	struct TouchMoreScreen* s = &TouchMoreScreen;
+	s->grabsInput = true;
+	s->closable   = true;
+	s->VTABLE     = &TouchMoreScreen_VTABLE;
+
+	Gui_Add((struct Screen*)s, GUI_PRIORITY_TOUCHMORE);
+}
+
+
+/*########################################################################################################################*
+*--------------------------------------------------------TouchScreen------------------------------------------------------*
+*#########################################################################################################################*/
+#define TOUCH_EXTRA_BTNS 2
+#define TOUCH_MAX_BTNS (ONSCREEN_MAX_BTNS + TOUCH_EXTRA_BTNS + 1)
+struct TouchButtonDesc {
+	const char* text;
+	cc_uint8 bind, x, y;
+	Widget_LeftClick OnClick;
+	cc_bool* enabled;
+};
+
+static struct TouchScreen {
+	Screen_Body
+	const struct TouchButtonDesc* descs;
+	int numOnscreen, numBtns;
+	struct FontDesc font;
+	struct ThumbstickWidget thumbstick;
+	struct ButtonWidget onscreen[ONSCREEN_MAX_BTNS];
+	struct ButtonWidget btns[TOUCH_EXTRA_BTNS], more;
+} TouchScreen;
+
+static struct Widget* touch_widgets[ONSCREEN_MAX_BTNS + TOUCH_EXTRA_BTNS + 2] = {
+	NULL,NULL,NULL,NULL, NULL,NULL,NULL,NULL, NULL,NULL,NULL,NULL, NULL,
+	NULL,NULL, (struct Widget*)&TouchScreen.thumbstick, (struct Widget*)&TouchScreen.more
+};
+#define TOUCH_MAX_VERTICES (THUMBSTICKWIDGET_MAX + TOUCH_MAX_BTNS * BUTTONWIDGET_MAX)
+
+static void TouchScreen_ChatClick(void* s,     void* w) { ChatScreen_OpenInput(&String_Empty); }
+static void TouchScreen_RespawnClick(void* s,  void* w) { Bind_OnTriggered[BIND_RESPAWN](0); }
+static void TouchScreen_SetSpawnClick(void* s, void* w) { Bind_OnTriggered[BIND_SET_SPAWN](0); }
+static void TouchScreen_FlyClick(void* s,      void* w) { Bind_OnTriggered[BIND_FLY](0); }
+static void TouchScreen_NoclipClick(void* s,   void* w) { Bind_OnTriggered[BIND_NOCLIP](0); }
+static void TouchScreen_CameraClick(void* s,   void* w) { Bind_OnTriggered[BIND_THIRD_PERSON](0); }
+static void TouchScreen_MoreClick(void* s,     void* w) { TouchMoreScreen_Show(); }
+static void TouchScreen_SwitchClick(void* s,   void* w) { Inventory_SwitchHotbar(); }
+static void TouchScreen_DeleteClick(void* s,   void* w) { InputHandler_DeleteBlock(); } /* TODO: also Send CPEClick packet */
+static void TouchScreen_PlaceClick(void* s,    void* w) { InputHandler_PlaceBlock(); }
+static void TouchScreen_PickClick(void* s,     void* w) { InputHandler_PickBlock(); }
+
+static void TouchScreen_TabClick(void* s, void* w) {
+	struct Screen* tablist = Gui_GetScreen(GUI_PRIORITY_TABLIST);
+	if (tablist) {
+		Gui_Remove(tablist);
+	} else {
+		TabListOverlay_Show(true);
+	}
+}
+
+static void TouchScreen_SpeedClick(void* s, void* w) {
+	struct HacksComp* hacks = &Entities.CurPlayer->Hacks;
+	if (hacks->Enabled) hacks->Speeding = !hacks->Speeding;
+}
+static void TouchScreen_HalfClick(void* s, void* w) {
+	struct HacksComp* hacks = &Entities.CurPlayer->Hacks;
+	if (hacks->Enabled) hacks->HalfSpeeding = !hacks->HalfSpeeding;
+}
+
+static void TouchScreen_BindClick(void* screen, void* widget) {
+	struct TouchScreen* s    = (struct TouchScreen*)screen;
+	struct ButtonWidget* btn = (struct ButtonWidget*)widget;
+	
+	int i = btn->meta.val;
+	Input_Set(KeyBind_Mappings[s->descs[i].bind].button1, true);
+}
+
+static const struct TouchButtonDesc onscreenDescs[ONSCREEN_MAX_BTNS] = {
+	{ "Chat",      0,0,0, TouchScreen_ChatClick },
+	{ "Tablist",   0,0,0, TouchScreen_TabClick },
+	{ "Respawn",   0,0,0, TouchScreen_RespawnClick,  &LocalPlayer_Instances[0].Hacks.CanRespawn },
+	{ "Set spawn", 0,0,0, TouchScreen_SetSpawnClick, &LocalPlayer_Instances[0].Hacks.CanRespawn },
+	{ "Fly",       0,0,0, TouchScreen_FlyClick,      &LocalPlayer_Instances[0].Hacks.CanFly     },
+	{ "Noclip",    0,0,0, TouchScreen_NoclipClick,   &LocalPlayer_Instances[0].Hacks.CanNoclip  },
+	{ "Speed",     0,0,0, TouchScreen_SpeedClick,    &LocalPlayer_Instances[0].Hacks.CanSpeed   },
+	{ "\xabSpeed", 0,0,0, TouchScreen_HalfClick,     &LocalPlayer_Instances[0].Hacks.CanSpeed   },
+	{ "Camera",    0,0,0, TouchScreen_CameraClick,   &LocalPlayer_Instances[0].Hacks.CanUseThirdPerson },
+	{ "Delete",    0,0,0, TouchScreen_DeleteClick },
+	{ "Pick",      0,0,0, TouchScreen_PickClick },
+	{ "Place",     0,0,0, TouchScreen_PlaceClick },
+	{ "Hotbar",    0,0,0, TouchScreen_SwitchClick }
+};
+static const struct TouchButtonDesc normDescs[1] = {
+	{ "\x1E", BIND_JUMP,     50,  10, TouchScreen_BindClick }
+};
+static const struct TouchButtonDesc hackDescs[2] = {
+	{ "\x1E", BIND_FLY_UP,   50,  70, TouchScreen_BindClick },
+	{ "\x1F", BIND_FLY_DOWN, 50,  10, TouchScreen_BindClick }
+};
+
+#define TOUCHSCREEN_BTN_COLOR PackedCol_Make(255, 255, 255, 200)
+static void TouchScreen_InitButtons(struct TouchScreen* s) {
+	struct HacksComp* hacks = &Entities.CurPlayer->Hacks;
+	const struct TouchButtonDesc* desc;
+	int buttons = GetOnscreenButtons();
+	int i, j;
+	for (i = 0; i < ONSCREEN_MAX_BTNS + TOUCH_EXTRA_BTNS; i++) s->widgets[i] = NULL;
+
+	for (i = 0, j = 0; i < ONSCREEN_MAX_BTNS; i++) 
+	{
+		if (!(buttons & (1 << i))) continue;
+		desc = &onscreenDescs[i];
+
+		ButtonWidget_Init(&s->onscreen[j], 100, desc->OnClick);
+		if (desc->enabled) Widget_SetDisabled(&s->onscreen[j], !(*desc->enabled));
+
+		s->onscreen[j].meta.val = i;
+		s->onscreen[j].color    = TOUCHSCREEN_BTN_COLOR;
+		s->widgets[j]           = (struct Widget*)&s->onscreen[j];
+		j++;
+	}
+
+	s->numOnscreen = j;
+	if (hacks->Flying || hacks->Noclip) {
+		s->descs   = hackDescs;
+		s->numBtns = Array_Elems(hackDescs);
+	} else {
+		s->descs   = normDescs;
+		s->numBtns = Array_Elems(normDescs);
+	}
+
+	for (i = 0; i < s->numBtns; i++) 
+	{
+		s->widgets[i + ONSCREEN_MAX_BTNS] = (struct Widget*)&s->btns[i];
+		ButtonWidget_Init(&s->btns[i], 60, s->descs[i].OnClick);
+		s->btns[i].color = TOUCHSCREEN_BTN_COLOR;
+		s->btns[i].meta.val = i;
+	}
+}
+
+void TouchScreen_Refresh(void) {
+	struct TouchScreen* s = &TouchScreen;
+	/* InitButtons changes number of widgets, hence */
+	/* must destroy graphics resources BEFORE that */
+	Screen_ContextLost(s);
+	TouchScreen_InitButtons(s);
+	Gui_Refresh((struct Screen*)s);
+}
+static void TouchScreen_HacksChanged(void* s) { TouchScreen_Refresh(); }
+
+static void TouchScreen_ContextLost(void* screen) {
+	struct TouchScreen* s = (struct TouchScreen*)screen;
+	Font_Free(&s->font);
+	Screen_ContextLost(screen);
+}
+
+static void TouchScreen_ContextRecreated(void* screen) {
+	struct TouchScreen* s = (struct TouchScreen*)screen;
+	const struct TouchButtonDesc* desc;
+	int i;
+	Screen_UpdateVb(screen);
+	Gui_MakeTitleFont(&s->font);
+
+	for (i = 0; i < s->numOnscreen; i++) 
+	{
+		desc = &onscreenDescs[s->onscreen[i].meta.val];
+		ButtonWidget_SetConst(&s->onscreen[i], desc->text, &s->font);
+	}
+	for (i = 0; i < s->numBtns; i++) 
+	{
+		desc = &s->descs[i];
+		ButtonWidget_SetConst(&s->btns[i], desc->text, &s->font);
+	}
+	ButtonWidget_SetConst(&s->more, "...", &s->font);
+}
+
+static void TouchScreen_Render(void* screen, float delta) {
+	if (Gui.InputGrab) return;
+	Screen_Render2Widgets(screen, delta);
+}
+
+static int TouchScreen_PointerDown(void* screen, int id, int x, int y) {
+	struct TouchScreen* s = (struct TouchScreen*)screen;
+	struct Widget* w;
+	int i;
+	//Chat_Add1("POINTER DOWN: %i", &id);
+	if (Gui.InputGrab) return false;
+
+	i = Screen_DoPointerDown(screen, id, x, y);
+	if (i < ONSCREEN_MAX_BTNS) return i >= 0;
+
+	/* Clicking on other buttons then */
+	w = s->widgets[i];
+	w->active |= id;
+
+	/* Clicking on jump or fly buttons should still move camera */
+	for (i = 0; i < s->numBtns; i++) 
+	{
+		if (w == (struct Widget*)&s->btns[i]) return TOUCH_TYPE_GUI | TOUCH_TYPE_CAMERA;
+	}
+	return TOUCH_TYPE_GUI;
+}
+
+static void TouchScreen_PointerUp(void* screen, int id, int x, int y) {
+	struct TouchScreen* s = (struct TouchScreen*)screen;
+	int i;
+	//Chat_Add1("POINTER UP: %i", &id);
+	s->thumbstick.active &= ~id;
+	s->more.active       &= ~id;
+
+	for (i = 0; i < s->numBtns; i++) 
+	{
+		if (!(s->btns[i].active & id)) continue;
+
+		if (s->descs[i].bind < BIND_COUNT) {
+			Input_Set(KeyBind_Mappings[s->descs[i].bind].button1, false);
+		}
+		s->btns[i].active &= ~id;
+		return;
+	}
+}
+
+static void TouchScreen_LayoutOnscreen(struct TouchScreen* s, cc_uint8 alignment) {
+	int haligns = GetOnscreenHAligns();
+	cc_uint8 halign;
+	int i, index, x, y;
+
+	for (i = 0, x = 10, y = 10; i < s->numOnscreen; i++) 
+	{
+		index  = s->onscreen[i].meta.val;
+		halign = (haligns & (1 << index)) ? ANCHOR_MIN : ANCHOR_MAX;
+		if (halign != alignment) continue;
+		
+		Widget_SetLocation(&s->onscreen[i], halign, ANCHOR_MIN, x, y);
+		if (s->onscreen[i].y + s->onscreen[i].height <= s->btns[0].y){ y += 40; continue; }
+
+		// overflowed onto jump/fly buttons, move to next column
+		y = 10;
+		x += 110;
+		Widget_SetLocation(&s->onscreen[i], halign, ANCHOR_MIN, x, y);
+	}
+}
+
+static void TouchScreen_Layout(void* screen) {
+	struct TouchScreen* s = (struct TouchScreen*)screen;
+	const struct TouchButtonDesc* desc;
+	float scale = Gui.RawTouchScale;
+	int i, height;
+
+	/* Need to align these relative to the hotbar */
+	height = HUDScreen_LayoutHotbar();
+
+	for (i = 0; i < s->numBtns; i++) 
+	{
+		desc = &s->descs[i];
+		Widget_SetLocation(&s->btns[i], ANCHOR_MAX, ANCHOR_MAX, desc->x, desc->y);
+		s->btns[i].yOffset += height;
+
+		/* TODO: Maybe move scaling to be part of button instead */
+		s->btns[i].minWidth  = Display_ScaleX(60 * scale);
+		s->btns[i].minHeight = Display_ScaleY(60 * scale);
+		Widget_Layout(&s->btns[i]);
+	}
+
+	TouchScreen_LayoutOnscreen(s, ANCHOR_MIN);
+	TouchScreen_LayoutOnscreen(s, ANCHOR_MAX);
+	Widget_SetLocation(&s->more, ANCHOR_CENTRE, ANCHOR_MIN, 0, 10);
+
+	Widget_SetLocation(&s->thumbstick, ANCHOR_MIN, ANCHOR_MAX, 30, 5);
+	s->thumbstick.yOffset += height;
+	s->thumbstick.scale = scale;
+	Widget_Layout(&s->thumbstick);
+}
+
+static void TouchScreen_GetMovement(struct LocalPlayer* p, float* xMoving, float* zMoving) {
+	ThumbstickWidget_GetMovement(&TouchScreen.thumbstick, xMoving, zMoving);
+}
+static struct LocalPlayerInput touchInput = { TouchScreen_GetMovement };
+
+static void TouchScreen_Init(void* screen) {
+	struct TouchScreen* s = (struct TouchScreen*)screen;
+
+	s->widgets     = touch_widgets;
+	s->numWidgets  = Array_Elems(touch_widgets);
+	s->maxVertices = TOUCH_MAX_VERTICES;
+	Event_Register_(&UserEvents.HacksStateChanged, screen, TouchScreen_HacksChanged);
+	Event_Register_(&UserEvents.HackPermsChanged,  screen, TouchScreen_HacksChanged);
+
+	TouchScreen_InitButtons(s);
+	ButtonWidget_Init(&s->more, 40, TouchScreen_MoreClick);
+	s->more.color = TOUCHSCREEN_BTN_COLOR;
+	ThumbstickWidget_Init(&s->thumbstick);
+
+	LocalPlayerInput_Add(&touchInput);
+}
+
+static void TouchScreen_Free(void* s) {
+	Event_Unregister_(&UserEvents.HacksStateChanged, s, TouchScreen_HacksChanged);
+	Event_Unregister_(&UserEvents.HackPermsChanged,  s, TouchScreen_HacksChanged);
+	LocalPlayerInput_Remove(&touchInput);
+}
+
+static const struct ScreenVTABLE TouchScreen_VTABLE = {
+	TouchScreen_Init,        Screen_NullUpdate,     TouchScreen_Free,
+	TouchScreen_Render,      Screen_BuildMesh,
+	Screen_FInput,           Screen_InputUp,        Screen_FKeyPress, Screen_FText,
+	TouchScreen_PointerDown, TouchScreen_PointerUp, Screen_FPointer,  Screen_FMouseScroll,
+	TouchScreen_Layout,      TouchScreen_ContextLost, TouchScreen_ContextRecreated
+};
+void TouchScreen_Show(void) {
+	struct TouchScreen* s = &TouchScreen;
+	s->VTABLE = &TouchScreen_VTABLE;
+
+	if (!Gui.TouchUI) return;
+	Gui_Add((struct Screen*)s, GUI_PRIORITY_TOUCH);
+}
+#endif