File TextEntryWidget.c
File List > engine > src > gameframework > layers > UI > widgets > TextEntryWidget.c
Go to the documentation of this file
#include "TextEntryWidget.h"
#include "Widget.h"
#include "DataNode.h"
#include "XMLUIGameLayer.h"
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include "TextWidget.h"
#include "Atlas.h"
#include "AssertLib.h"
#include "CanvasWidget.h"
#include "InputContext.h"
#include "RootWidget.h"
#include "TimerPool.h"
#include "Scripting.h"
#include "Log.h"
struct TextEntryWidgetData
{
struct TextWidgetData textWidget;
struct CanvasData canvasWidget;
// uses the TextWidgetData content pointer directly to store
// the string as it's being manipulated
int maxStringLen;
int currentStringLen;
int cursorIndex;
XMLUIData* pLayerData;
char caretChar;
HTimer caretTimer;
bool bCaretBlinkState;
char onEnterPressLuaCallbackName[TEXT_WIDGET_DATA_LUA_CALLBACK_NAME_BUFFER_SIZE];
bool bEnterPressCallbackSet;
int viewmodelRegIndex;
};
static float GetWidth(struct UIWidget* pWidget, struct UIWidget* pParent)
{
return UI_ResolveWidthDimPxls(pWidget, &pWidget->width) + pWidget->padding.paddingLeft + pWidget->padding.paddingRight;
}
static float GetHeight(struct UIWidget* pWidget, struct UIWidget* pParent)
{
struct TextEntryWidgetData* pData = (struct TextEntryWidgetData*)pWidget->pImplementationData;
return pData->textWidget.fSizePts * 1.33333333 + pWidget->padding.paddingTop + pWidget->padding.paddingBottom;
}
static void LayoutChildren(struct UIWidget* pWidget, struct UIWidget* pParent)
{
}
static void OnDestroy(struct UIWidget* pWidget)
{
TextWidget_Destroy(&((struct TextEntryWidgetData*)&pWidget->pImplementationData)->textWidget);
free(pWidget->pImplementationData);
}
static void* FakeChildOutputVerts(struct UIWidget* pWidget, VECTOR(WidgetVertex) pOutVerts)
{
return TextWidget_OutputVerts(pWidget->left, pWidget->top, &pWidget->padding, pWidget->pImplementationData, pOutVerts);
}
static void* OnOutputVerts(struct UIWidget* pWidget, VECTOR(WidgetVertex) pOutVerts)
{
struct TextEntryWidgetData* pData = pWidget->pImplementationData;
pData->canvasWidget.bHSliderActive = false;
pData->canvasWidget.bVSliderActive = false;
HWidget hFake = UI_GetScratchWiget();
struct UIWidget* fakeChild = UI_GetWidget(hFake);
memcpy(fakeChild, pWidget, sizeof(struct UIWidget));
fakeChild->hNext = NULL_HANDLE;
fakeChild->fnOutputVertices = &FakeChildOutputVerts;
fakeChild->top += pWidget->padding.paddingTop;
fakeChild->left += pWidget->padding.paddingLeft;
fakeChild->dockPoint = WDP_BottomLeft;
memset(&fakeChild->padding, 0, sizeof(struct WidgetPadding));
fakeChild->pImplementationData = &pData->textWidget;
pWidget->hFirstChild = hFake;
pOutVerts = CanvasWidget_OnOutputVerts(pWidget, pOutVerts);
pWidget->hFirstChild = NULL_HANDLE;
if(pData->bCaretBlinkState)
pOutVerts = TextWidget_OutputAtLetter(pWidget->left, pWidget->top, &pWidget->padding, &pData->textWidget, pData->caretChar, pData->cursorIndex, pOutVerts );
return pOutVerts;
}
static void OnPropertyChanged(struct UIWidget* pThisWidget, struct WidgetPropertyBinding* pBinding)
{
}
static void DoBackspace(struct TextEntryWidgetData* pData, struct UIWidget* pWidget)
{
if(pData->currentStringLen == 0)
{
return;
}
// shift along starting with the char at cursorIndex
for(int i=pData->cursorIndex; i < pData->currentStringLen; i++)
{
pData->textWidget.content[i - 1] = pData->textWidget.content[i];
}
pData->cursorIndex--;
pData->textWidget.content[--pData->currentStringLen] = '\0';
struct WidgetPropertyBinding* pBinding = UI_FindBinding(pWidget, "content");
if (pBinding)
{
char* setterName = UI_MakeBindingSetterFunctionName(pBinding->name);
struct ScriptCallArgument arg;
arg.type = SCA_string;
arg.val.string = pData->textWidget.content;
Sc_CallFuncInRegTableEntryTable(pWidget->scriptCallbacks.viewmodelTable, setterName, &arg, 1, 0);
free(setterName);
}
}
static void DoEnterChar(struct TextEntryWidgetData* pData, char c, struct UIWidget* pWidget)
{
if(pData->currentStringLen >= pData->maxStringLen)
{
return;
}
// shift along starting with the char at cursorIndex
pData->textWidget.content[pData->currentStringLen + 1] = '\0';
for(int i=pData->currentStringLen - 1; i >= pData->cursorIndex; i--)
{
pData->textWidget.content[i + 1] = pData->textWidget.content[i];
}
pData->textWidget.content[pData->cursorIndex++] = c;
pData->currentStringLen++;
struct WidgetPropertyBinding* pBinding = UI_FindBinding(pWidget, "content");
if (pBinding)
{
char* setterName = UI_MakeBindingSetterFunctionName(pBinding->name);
struct ScriptCallArgument arg;
arg.type = SCA_string;
arg.val.string = pData->textWidget.content;
Sc_CallFuncInRegTableEntryTable(pWidget->scriptCallbacks.viewmodelTable, setterName, &arg, 1, 0);
free(setterName);
}
}
static void RecieveKeystrokeCallback(struct UIWidget* pWidget, int keystroke)
{
struct TextEntryWidgetData* pData = pWidget->pImplementationData;
switch(keystroke)
{
case KEYSTROKE_LEFT:
{
if(pData->cursorIndex != 0)
{
pData->cursorIndex--;
}
}
break;
case KEYSTROKE_RIGHT:
{
if(pData->cursorIndex != pData->currentStringLen)
{
pData->cursorIndex++;
}
}
break;
case KEYSTROKE_BACKSPACE:
{
if(pData->cursorIndex != 0)
{
DoBackspace(pData, pWidget);
}
}
break;
case KEYSTROKE_ENTER:
{
if (pData->bEnterPressCallbackSet)
{
Sc_CallFuncInRegTableEntryTable(pData->viewmodelRegIndex, pData->onEnterPressLuaCallbackName, NULL, 0, 0);
}
}
return;
default:
DoEnterChar(pData, (char)keystroke, pWidget);
break;
}
SetRootWidgetIsDirty(pData->pLayerData->rootWidget, true);
}
/*
Initialise the TextEntryWidget and create a bigger allocation than is required it's TextWidget content string,
or truncate it to fit withing maxStringLength.
*/
static void AllocateStringContents(int maxStringLength, struct TextEntryWidgetData* pData)
{
EASSERT(pData->textWidget.content);
char* newAlloc = malloc(maxStringLength + 1);
int len = strlen(pData->textWidget.content);
if(len >= maxStringLength)
{
memcpy(newAlloc, pData->textWidget.content, maxStringLength);
newAlloc[maxStringLength] = '\0';
}
else
{
strcpy(newAlloc, pData->textWidget.content);
}
free(pData->textWidget.content);
pData->textWidget.content = newAlloc;
pData->cursorIndex = len;
pData->currentStringLen = len;
}
static void OnFocus(struct UIWidget* pWidget)
{
struct TextEntryWidgetData* pData = pWidget->pImplementationData;
HTimer hCaretTimer = pData->caretTimer;
pData->pLayerData->timerPool.pPool[hCaretTimer].bActive = true;
pData->bCaretBlinkState = true;
SetRootWidgetIsDirty(pData->pLayerData->rootWidget, true);
}
static void OnUnFocus(struct UIWidget* pWidget)
{
struct TextEntryWidgetData* pData = pWidget->pImplementationData;
HTimer hCaretTimer = pData->caretTimer;
pData->pLayerData->timerPool.pPool[hCaretTimer].bActive = false;
pData->bCaretBlinkState = false;
SetRootWidgetIsDirty(pData->pLayerData->rootWidget, true);
}
static bool OnCaretBlinkTimerElapsed(struct SDTimer* pTimer)
{
struct UIWidget* pWidget = UI_GetWidget((HWidget)(u64)pTimer->pUserData);
struct TextEntryWidgetData* pData = pWidget->pImplementationData;
pData->bCaretBlinkState = ! pData->bCaretBlinkState;
SetRootWidgetIsDirty(pData->pLayerData->rootWidget, true);
static int r = 0;
return false;
}
static void SetupCaretBlinkTimer(struct TextEntryWidgetData* pData, struct XMLUIData* pUILayerData, HWidget hWidget)
{
struct SDTimer timer =
{
.bActive = true,
.bRepeat = true,
.bAutoReset = true,
.total = 0.5,
.fnCallback = &OnCaretBlinkTimerElapsed,
.pUserData = (void*)(u64)hWidget
};
pData->caretTimer = TP_GetTimer(&pUILayerData->timerPool, &timer);
}
static void MakeWidgetIntoTextEntryWidget(HWidget hWidget, struct DataNode* pDataNode, struct XMLUIData* pUILayerData)
{
struct UIWidget* pWidget = UI_GetWidget(hWidget);
pWidget->hNext = -1;
pWidget->hPrev = -1;
pWidget->hParent = -1;
pWidget->hFirstChild = -1;
pWidget->fnGetHeight = &GetHeight;
pWidget->fnGetWidth = &GetWidth;
pWidget->fnLayoutChildren = &LayoutChildren;
pWidget->fnOnDestroy = &OnDestroy;
pWidget->fnOutputVertices = &OnOutputVerts;
pWidget->fnOnBoundPropertyChanged = &OnPropertyChanged;
pWidget->fnRecieveKeystroke = &RecieveKeystrokeCallback;
pWidget->bAcceptsFocus = true;
pWidget->pImplementationData = malloc(sizeof(struct TextEntryWidgetData));
pWidget->cCallbacks.Callbacks[WC_OnGainFocus].callback.focusChangeFn = &OnFocus;
pWidget->cCallbacks.Callbacks[WC_OnLeaveFocus].callback.focusChangeFn = &OnUnFocus;
memset(pWidget->pImplementationData, 0, sizeof(struct TextEntryWidgetData));
struct TextEntryWidgetData* pData = pWidget->pImplementationData;
pData->pLayerData = pUILayerData;
TextWidget_FromXML(pWidget, &pData->textWidget, pDataNode, pUILayerData);
pData->canvasWidget.bUseHSlider = false;
pData->canvasWidget.bUseVSlider = false;
xmlChar* attribute = NULL;
if(pDataNode->fnGetPropType(pDataNode, "maxStringLength") == DN_Int)
{
pData->maxStringLen = pDataNode->fnGetInt(pDataNode, "maxStringLength");
}
else
{
pData->maxStringLen = 16;
}
if (pDataNode->fnGetPropType(pDataNode, "onEnter") == DN_String)
{
int nameLen = pDataNode->fnGetStrlen(pDataNode, "onEnter");
if (nameLen <= TEXT_WIDGET_DATA_LUA_CALLBACK_NAME_BUFFER_SIZE)
{
pDataNode->fnGetStrcpy(pDataNode, "onEnter", pData->onEnterPressLuaCallbackName);
pData->bEnterPressCallbackSet = true;
}
else
{
char* errorMsgName = malloc(nameLen + 1);
pDataNode->fnGetStrcpy(pDataNode, "onEnter", errorMsgName);
Log_Error("TextWidget: onEnter callback name '%s' too long. 31 chars max, name was %i", errorMsgName, nameLen);
free(errorMsgName);
}
}
AllocateStringContents(pData->maxStringLen, pData);
pData->caretChar = 'I'; // todo: move to xml
SetupCaretBlinkTimer(pData, pUILayerData, hWidget);
HTimer hCaretTimer = pData->caretTimer;
pData->pLayerData->timerPool.pPool[hCaretTimer].bActive = false;
pData->bCaretBlinkState = false;
pData->viewmodelRegIndex = pUILayerData->hViewModel;
}
HWidget TextEntryWidgetNew(HWidget hParent, struct DataNode* pDataNode, struct XMLUIData* pUILayerData)
{
HWidget hWidget = UI_NewBlankWidget();
MakeWidgetIntoTextEntryWidget(hWidget, pDataNode, pUILayerData);
return hWidget;
}