File XMLUIGameLayer.c

File List > engine > src > gameframework > layers > UI > XMLUIGameLayer.c

Go to the documentation of this file

#include "XMLUIGameLayer.h"
#include "DrawContext.h"
#include "InputContext.h"
#include "GameFramework.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "IntTypes.h"
#include "DynArray.h"
#include "ObjectPool.h"
#include "Widget.h"
#include "Widget.h"
#include "StaticWidget.h"
#include "StackPanelWidget.h"
#include "Atlas.h"
#include "RootWidget.h"
#include "main.h"
#include "TextWidget.h"
#include "BackgroundBoxWidget.h"
#include "Scripting.h"
#include "Geometry.h"
#include "AssertLib.h"
#include "TextButtonWidget.h"
#include "RadioButtonWidget.h"
#include "RadioGroupWidget.h"
#include "SliderWidget.h"
#include "CanvasWidget.h"
#include "TextEntryWidget.h"
#include "DataNode.h"
#include "StringKeyHashMap.h"
#include "GameFrameworkEvent.h"
#include <libxml/parser.h>
#include <libxml/tree.h>
#include "Log.h"
#include "cwalk.h"
#include "main.h"

static struct HashMap gNodeNameMap;
static bool gInitialisedNodeNameMap = false;


static void InitializeNodeNameMap()
{
    HashmapInit(&gNodeNameMap, 20, sizeof(AddChildFn));

    AddChildFn fn = &StackPanelWidgetNew;
    HashmapInsert(&gNodeNameMap, "stackpanel", &fn);
    fn = &StaticWidgetNew;
    HashmapInsert(&gNodeNameMap, "static", &fn);
    fn = &TextWidgetNew;
    HashmapInsert(&gNodeNameMap, "text", &fn);
    fn = &BackgroundBoxWidgetNew;
    HashmapInsert(&gNodeNameMap, "backgroundbox", &fn);
    fn = &TextButtonWidgetNew;
    HashmapInsert(&gNodeNameMap, "textButton", &fn);
    fn = &RadioGroupWidgetNew;
    HashmapInsert(&gNodeNameMap, "radioGroup", &fn);
    fn = &RadioButtonWidgetNew;
    HashmapInsert(&gNodeNameMap, "radioButton", &fn);
    fn = &SliderWidgetNew;
    HashmapInsert(&gNodeNameMap, "slider", &fn);
    fn = &CanvasWidgetNew;
    HashmapInsert(&gNodeNameMap, "canvas", &fn);
    fn = &TextEntryWidgetNew;
    HashmapInsert(&gNodeNameMap, "textInput", &fn);
}


AddChildFn LookupWidgetCtor(const char* widgetName)
{
    return *((AddChildFn*)HashmapSearch(&gNodeNameMap, widgetName));
}

static void FreeWidgetTree_internal(HWidget root, bool bFreeRoot)
{
    struct UIWidget* pWidget = UI_GetWidget(root);
    struct UIWidget* pWidgetRoot = pWidget;
    if (!pWidget)
    {
        return;
    }

    HWidget h = pWidget->hFirstChild;
    while (h != NULL_HWIDGET)
    {
        HWidget oldH = h;
        pWidget = UI_GetWidget(h);

        h = pWidget->hNext;
        FreeWidgetTree_internal(oldH, true);
    }
    pWidgetRoot->hFirstChild = NULL_HWIDGET;
    if (bFreeRoot)
    {
        UI_DestroyWidget(root);
    }

}

static void FreeWidgetTree(HWidget root)
{
    FreeWidgetTree_internal(root, true);
}

static void FreeWidgetChildren(HWidget root)
{
    FreeWidgetTree_internal(root, false);
}

/*
    add the TOS lua table as a child of hParent, and recurse throught children
*/
static void AddLuaTableSubTree(XMLUIData* pData, HWidget hParent)
{
    /*
        Add the lua node and its children here:
    */
    struct DataNode node;
    DN_InitForLuaTableOnTopOfStack(&node);
    Sc_TableGet("type");
    size_t len = Sc_StackTopStringLen();
    if (len)
    {
        char* pStr = malloc(len + 1);
        Sc_StackTopStrCopy(pStr);
        Sc_Pop();

        AddChildFn fn = LookupWidgetCtor(pStr);
        HWidget hNew = fn(hParent, &node, pData);
        free(pStr);

        struct UIWidget* pWiddget = UI_GetWidget(hNew);
        UI_WidgetCommonInit(&node, pWiddget);
        pWiddget->hNext = NULL_HANDLE;
        pWiddget->scriptCallbacks.viewmodelTable = pData->hViewModel;

        UI_AddChild(hParent, hNew);
        Sc_TableGet("children");
        for (int i = 1; i <= Sc_TableLen(); i++)
        {
            Sc_TableGetIndex(i);
            if (Sc_IsTable())
            {
                AddLuaTableSubTree(pData, hNew);
            }
            Sc_Pop();
        }
        Sc_Pop();
    }
    else
    {
        Log_Error("error: child node in children table is not a string or is empty");
        Sc_Pop();
    }
}

static void Update(struct GameFrameworkLayer* pLayer, float deltaT)
{
    XMLUIData* pData = pLayer->userData;
    TP_DoTimers(&pData->timerPool, deltaT);
    if (VectorSize(pData->pChildrenChangeRequests))
    {
        struct WidgetChildrenChangeRequest* pReq = &pData->pChildrenChangeRequests[0];
        FreeWidgetChildren(pReq->hWidget);
        Sc_CallFuncInRegTableEntryTable(pReq->regIndex, pReq->funcName, NULL, 0, 1);
        int t = Sc_Type();
        if (Sc_IsTable())
        {
            for (int i = 1; i <= Sc_TableLen(); i++)
            {
                Sc_TableGetIndex(i);

                if (Sc_IsTable())
                {
                    AddLuaTableSubTree(pData, pReq->hWidget);
                }

                Sc_Pop();
            }
            Sc_Pop();
            struct UIWidget* pWidget = UI_GetWidget(pReq->hWidget);
            if (pWidget)
            {
                if (pWidget->fnOnWidgetChildrenChangedFn)
                {
                    pWidget->fnOnWidgetChildrenChangedFn(pWidget);
                }
            }
            SetRootWidgetIsDirty(pData->rootWidget, true);
        }
        else
        {
            Log_Error("error: function %s did not return a table", pReq->funcName);
        }
    }
    for (int i = 0; i < VectorSize(pData->pChildrenChangeRequests); i++)
    {
        free(pData->pChildrenChangeRequests[i].funcName);
    }
    pData->pChildrenChangeRequests = VectorClear(pData->pChildrenChangeRequests);
    Sc_ResetStack();
}

static void UpdateRootWidget(XMLUIData* pData, DrawContext* dc)
{
    VectorClear(pData->pWidgetVertices);
    struct UIWidget* pRootWidget = UI_GetWidget(pData->rootWidget);
    pRootWidget->fnLayoutChildren(pRootWidget, NULL);
    pData->pWidgetVertices = pRootWidget->fnOutputVertices(pRootWidget, pData->pWidgetVertices);
    dc->UIVertexBufferData(pData->hVertexBuffer, pData->pWidgetVertices, VectorSize(pData->pWidgetVertices));
    SetRootWidgetIsDirty(pData->rootWidget, false);
}

static void Draw(struct GameFrameworkLayer* pLayer, DrawContext* dc)
{
    XMLUIData* pData = pLayer->userData;
    At_SetCurrent(pData->atlas, dc);
    struct UIWidget* pRootWidget = UI_GetWidget(pData->rootWidget);
    if (!pRootWidget)
    {
        Log_Error("something wrong");
        return;
    }

    if (GetRootWidgetIsDirty(pData->rootWidget))
    {
        UpdateRootWidget(pData, dc);
    }
    int size = VectorSize(pData->pWidgetVertices);

    dc->DrawUIVertexBuffer(pData->hVertexBuffer, size);
}


static void* BuildWidgetsHoverred(VECTOR(HWidget) outWidgets, HWidget hWidget, float mouseX, float mouseY)
{
    GeomRect hitbox;
    UI_GetHitBox(hitbox, hWidget);
    if (Ge_PointInAABB(mouseX, mouseY, hitbox))
    {
        outWidgets = VectorPush(outWidgets, &hWidget);

        struct UIWidget* pWidget = UI_GetWidget(hWidget);
        HWidget hChild = pWidget->hFirstChild;
        while (hChild != NULL_HWIDGET)
        {
            outWidgets = BuildWidgetsHoverred(outWidgets, hChild, mouseX, mouseY);
            struct UIWidget* pChild = UI_GetWidget(hChild);
            hChild = pChild->hNext;
        }
    }
    return outWidgets;

}

static void Unfocus(XMLUIData* pUIData)
{
    for (int i = 0; i < pUIData->nFocusedWidgets; i++)
    {
        struct UIWidget* pWidget = UI_GetWidget(pUIData->focusedWidgets[i]);
        EASSERT(pWidget->bAcceptsFocus);
        pWidget->bHasFocus = false;
        if (pWidget->cCallbacks.Callbacks[WC_OnLeaveFocus].callback.bActive)
        {
            pWidget->cCallbacks.Callbacks[WC_OnLeaveFocus].callback.focusChangeFn(pWidget);
        }
    }
    pUIData->nFocusedWidgets = 0;
}

static bool SendMouseDownAndFocus(struct WidgetMouseInfo* info, HWidget hWidget, XMLUIData* pUIData)
{
    Unfocus(pUIData);
    struct UIWidget* pWidget = UI_GetWidget(hWidget);
    bool rval = false;
    if (pWidget->bAcceptsFocus)
    {
        rval = true;
        pWidget->bHasFocus = true;
        pUIData->focusedWidgets[pUIData->nFocusedWidgets++] = hWidget;
        if (pWidget->cCallbacks.Callbacks[WC_OnGainFocus].callback.bActive)
        {
            pWidget->cCallbacks.Callbacks[WC_OnGainFocus].callback.focusChangeFn(pWidget);
        }
    }
    UI_SendWidgetMouseEvent(pWidget, WC_OnMouseDown, info);
    return rval;
}

static void Input(struct GameFrameworkLayer* pLayer, InputContext* ctx)
{
    static VECTOR(HWidget) pWidgetsHovverred = NULL;
    static VECTOR(HWidget) pWidgetsHovverredLastFrame = NULL;
    static VECTOR(HWidget) pWidgetsEntered = NULL;
    static VECTOR(HWidget) pWidgetsLeft = NULL;
    static VECTOR(HWidget) pWidgetsRemained = NULL;

    static bool bLastLeftClick = false;
    static bool bThisLeftClick = false;

    if (!pWidgetsHovverred)
    {
        pWidgetsHovverred = NEW_VECTOR(HWidget);
    }
    if (!pWidgetsHovverredLastFrame)
    {
        pWidgetsHovverredLastFrame = NEW_VECTOR(HWidget);
    }
    if (!pWidgetsEntered)
    {
        pWidgetsEntered = NEW_VECTOR(HWidget);
    }
    if (!pWidgetsLeft)
    {
        pWidgetsLeft = NEW_VECTOR(HWidget);
    }
    if (!pWidgetsRemained)
    {
        pWidgetsRemained = NEW_VECTOR(HWidget);
    }

    pWidgetsHovverredLastFrame = VectorClear(pWidgetsHovverredLastFrame);
    for (int i = 0; i < VectorSize(pWidgetsHovverred); i++)
    {
        pWidgetsHovverredLastFrame = VectorPush(pWidgetsHovverredLastFrame, &pWidgetsHovverred[i]);
    }

    pWidgetsHovverred = VectorClear(pWidgetsHovverred);
    pWidgetsEntered = VectorClear(pWidgetsEntered);
    pWidgetsLeft = VectorClear(pWidgetsLeft);
    pWidgetsRemained = VectorClear(pWidgetsRemained);

    XMLUIData* pUIData = pLayer->userData;
    //pUIData->pChildrenChangeRequests = VectorClear(pUIData->pChildrenChangeRequests);
    float w, h;

    bThisLeftClick = In_GetButtonValue(ctx, pUIData->gMouseBtnLeft);

    vec2 mousePos = { In_GetAxisValue(ctx, pUIData->gMouseX), In_GetAxisValue(ctx, pUIData->gMouseY) };


    pWidgetsHovverred = BuildWidgetsHoverred(pWidgetsHovverred, pUIData->rootWidget, mousePos[0], mousePos[1]);

    // any widgets started to be hoverred?
    for (int i = 0; i < VectorSize(pWidgetsHovverred); i++)
    {
        HWidget hHovvered = pWidgetsHovverred[i];
        bool bWasHoverredLastFrame = false;
        for (int j = 0; j < VectorSize(pWidgetsHovverredLastFrame); j++)
        {
            if (hHovvered == pWidgetsHovverredLastFrame[j])
            {
                bWasHoverredLastFrame = true;
                break;
            }
        }
        if (!bWasHoverredLastFrame)
        {
            pWidgetsEntered = VectorPush(pWidgetsEntered, &hHovvered);
        }
        else
        {
            pWidgetsRemained = VectorPush(pWidgetsRemained, &hHovvered);
        }
    }

    // any widgets stopped being hovvered?
    for (int i = 0; i < VectorSize(pWidgetsHovverredLastFrame); i++)
    {
        HWidget hHovvered = pWidgetsHovverredLastFrame[i];
        bool bHoverredThisFrame = false;
        for (int j = 0; j < VectorSize(pWidgetsHovverred); j++)
        {
            if (hHovvered == pWidgetsHovverred[j])
            {
                bHoverredThisFrame = true;
                break;;
            }
        }
        if (!bHoverredThisFrame)
        {
            pWidgetsLeft = VectorPush(pWidgetsLeft, &hHovvered);
        }
    }

    struct WidgetMouseInfo info =
    {
        .x = mousePos[0],
        .y = mousePos[1],
        .button = 0
    };

    bool bSendLMouseDown = false;
    bool bSendLMouseUp = false;
    if (bThisLeftClick && !bLastLeftClick)
    {
        info.button = 0;
        bSendLMouseDown = true;
    }
    else if (!bThisLeftClick && bLastLeftClick)
    {
        info.button = 0;
        bSendLMouseUp = true;
    }

    bLastLeftClick = bThisLeftClick;


    for (int i = 0; i < VectorSize(pWidgetsEntered); i++)
    {
        HWidget hWidget = pWidgetsEntered[i];
        struct UIWidget* pWidget = UI_GetWidget(hWidget);
        UI_SendWidgetMouseEvent(pWidget, WC_OnMouseEnter, &info);
    }
    for (int i = 0; i < VectorSize(pWidgetsLeft); i++)
    {
        HWidget hWidget = pWidgetsLeft[i];
        struct UIWidget* pWidget = UI_GetWidget(hWidget);
        UI_SendWidgetMouseEvent(pWidget, WC_OnMouseLeave, &info);
    }


    bool bFocusChanged = false;
    bool bWidgetsClicked = VectorSize(pWidgetsRemained) > 0 && bSendLMouseDown;


    for (int i = 0; i < VectorSize(pWidgetsRemained); i++)
    {
        HWidget hWidget = pWidgetsRemained[i];
        struct UIWidget* pWidget = UI_GetWidget(hWidget);
        if (bSendLMouseDown)
        {
            EASSERT(!bSendLMouseUp);
            if (SendMouseDownAndFocus(&info, hWidget, pUIData))
            {
                bFocusChanged = true;
            }
        }
        if (bSendLMouseUp)
        {
            EASSERT(!bSendLMouseDown);
            UI_SendWidgetMouseEvent(pWidget, WC_OnMouseUp, &info);
        }
        UI_SendWidgetMouseEvent(pWidget, WC_OnMouseMove, &info);
    }

    for (int i = 0; i < pUIData->nFocusedWidgets; i++)
    {
        HWidget hWidget = pUIData->focusedWidgets[i];
        struct UIWidget* pWidget = UI_GetWidget(hWidget);
        for (int j = 0; j < ctx->textInput.nKeystrokesThisFrame; j++)
        {
            int keystroke = ctx->textInput.keystrokes[j];
            if (pWidget->fnRecieveKeystroke)
                pWidget->fnRecieveKeystroke(pWidget, keystroke);
        }
    }

    if (Sc_FunctionPresentInTable(pUIData->hViewModel, "OnInput"))
    {
        Sc_CallFuncInRegTableEntryTable(pUIData->hViewModel, "OnInput", NULL, 0, 0);
    }

}

static void LoadUIData(XMLUIData* pUIData, DrawContext* pDC);


static void OnPush(struct GameFrameworkLayer* pLayer, DrawContext* drawContext, InputContext* inputContext)
{
    XMLUIData* pData = pLayer->userData;
    pData->timerPool = TP_InitTimerPool(32);

    if (!pData->bLoaded)
    {
        LoadUIData(pData, drawContext);
    }
    hTexture hAtlasTex = At_GetAtlasTexture(pData->atlas);
    drawContext->SetCurrentAtlas(hAtlasTex);
    if (Sc_FunctionPresentInTable(pData->hViewModel, "OnXMLUILayerPush"))
    {
        Sc_CallFuncInRegTableEntryTable(pData->hViewModel, "OnXMLUILayerPush", NULL, 0, 0);
    }

    struct ActiveInputBindingsMask mask;
    In_GetMask(&mask, inputContext);
    pData->gMouseBtnLeft = In_FindButtonMapping(inputContext, "select");
    pData->gMouseY = In_FindAxisMapping(inputContext, "CursorPosY");
    pData->gMouseX = In_FindAxisMapping(inputContext, "CursorPosX");
    In_ActivateButtonBinding(pData->gMouseBtnLeft, &mask);
    In_ActivateAxisBinding(pData->gMouseY, &mask);
    In_ActivateAxisBinding(pData->gMouseX, &mask); /* TODO: this is incomplete: these */
    In_SetMask(&mask, inputContext);
}


static void OnPop(struct GameFrameworkLayer* pLayer, DrawContext* drawContext, InputContext* inputContext)
{
    XMLUIData* pData = pLayer->userData;
    if (Sc_FunctionPresentInTable(pData->hViewModel, "OnXMLUILayerPop"))
    {
        Sc_CallFuncInRegTableEntryTable(pData->hViewModel, "OnXMLUILayerPop", NULL, 0, 0);
    }
    DestoryVector(pData->pWidgetVertices);
    drawContext->DestroyVertexBuffer(pData->hVertexBuffer);
    FreeWidgetTree(pData->rootWidget);
    if (pData->hViewModel)
    {
        Sc_DeleteTableInReg(pData->hViewModel);
    }
    TP_DestroyTimerPool(&pData->timerPool);

    struct ActiveInputBindingsMask mask;
    In_GetMask(&mask, inputContext);
    In_DeactivateButtonBinding(pData->gMouseBtnLeft, &mask);
    In_DeactivateAxisBinding(pData->gMouseX, &mask);
    In_DeactivateAxisBinding(pData->gMouseY, &mask);
    In_SetMask(&mask, inputContext);
}

void AddNodeChildren(HWidget widget, xmlNode* pNode, XMLUIData* pUIData)
{
    struct DataNode dataNode;
    for (xmlNode* pChild = pNode->children; pChild; pChild = pChild->next)
    {
        DN_InitForXMLNode(&dataNode, pChild);
        if (pChild->type != XML_ELEMENT_NODE)
        {
            continue;
        }
        AddChildFn pCtor = LookupWidgetCtor(pChild->name);
        if (!pCtor)
        {
            // log error
            Log_Error("error occured");
            return;
        }
        HWidget childWidget = pCtor(widget, &dataNode, pUIData);

        struct UIWidget* pWiddget = UI_GetWidget(childWidget);
        pWiddget->scriptCallbacks.viewmodelTable = pUIData->hViewModel;
        UI_WidgetCommonInit(&dataNode, pWiddget);

        UI_AddChild(widget, childWidget);

        AddNodeChildren(childWidget, pChild, pUIData);
    }
}



void LoadAtlas(XMLUIData* pUIData, xmlNode* child0, DrawContext* pDC)
{
    pUIData->atlas = At_LoadAtlas(child0, pDC);
}

static bool TryLoadViewModel(XMLUIData* pUIData, xmlNode* pScreenNode)
{
    bool rVal = false;
    bool bVMFileSet = false;
    bool bVMFunctionSet = false;
    char* pFilePath = NULL;
    char* pFnName = NULL;
    xmlChar* attribute = NULL;
    if (attribute = xmlGetProp(pScreenNode, "viewmodelFile"))
    {
        pFilePath = attribute;
        bVMFileSet = true;
    }
    if (attribute = xmlGetProp(pScreenNode, "viewmodelFunction"))
    {
        pFnName = attribute;
        bVMFunctionSet = true;
    }

    if (bVMFileSet && bVMFunctionSet)
    {
        char buf[256];
        cwk_path_join(gCmdArgs.assetsDir, pFilePath, buf, 256);
        Log_Verbose("opening viewmodel file %s", buf);
        // instantiate viewmodel lua object and store in registry
        Sc_OpenFile(buf);
        Log_Verbose("done");
        pUIData->hViewModel = Sc_CallGlobalFuncReturningTableAndStoreResultInReg(pFnName, NULL, 0);

        // tag the viewmodel table with a ptr to the XMLUIDataPtr so it can set the widget tree flag to dirty
        Sc_AddLightUserDataValueToTable(pUIData->hViewModel, "XMLUIDataPtr", pUIData);
    }
    else
    {
        Log_Error("TryLoadViewModel, either file or function (or both) not set. file: %i function name: %i", bVMFileSet, bVMFunctionSet);
    }

    if (pFnName)
    {
        xmlFree(pFnName);
    }
    if (pFilePath)
    {
        xmlFree(pFilePath);
    }

    return rVal;
}

static void InitializeWidgets(HWidget root)
{
    struct UIWidget* pWidget = UI_GetWidget(root);
    if (pWidget->fnOnWidgetInit)
    {
        pWidget->fnOnWidgetInit(pWidget);
    }
    HWidget child = pWidget->hFirstChild;
    while (child != NULL_HWIDGET)
    {
        InitializeWidgets(child);
        struct UIWidget* pWidget = UI_GetWidget(child);
        child = pWidget->hNext;
    }
}

xmlNode* GetNthChild(xmlNode* node, unsigned int index)
{
    int onChild = 0;
    assert(xmlChildElementCount(node) > index);
    xmlNode* pChild = xmlFirstElementChild(node);
    while (pChild)
    {
        if (pChild->type != XML_ELEMENT_NODE)
        {
            pChild = pChild->next;
            continue;
        }
        if (onChild++ == index)
        {
            return pChild;
        }
        pChild = pChild->next;
    }
    return NULL;
}

static void LoadUIData(XMLUIData* pUIData, DrawContext* pDC)
{
    assert(!pUIData->bLoaded);
    pUIData->bLoaded = true;
    xmlDoc* pXMLDoc = xmlReadFile(pUIData->xmlFilePath, NULL, 0);

    if (!gInitialisedNodeNameMap)
    {
        gInitialisedNodeNameMap = true;
        InitializeNodeNameMap();
    }

    char nodeNameArr[128];

    if (pXMLDoc)
    {
        Log_Verbose("pXMLDoc is valid");
        xmlNode* root = xmlDocGetRootElement(pXMLDoc);
        unsigned long numchildren = xmlChildElementCount(root);
        if (numchildren != 2)
        {
            Log_Error("%s root should have 2 kids", __FUNCTION__);
            xmlFreeDoc(pXMLDoc);
            return;
        }
        xmlNode* child0 = GetNthChild(root, 0);
        xmlNode* child1 = GetNthChild(root, 1);

        bool bDoneAtlas = false;
        bool bDoneScreen = false;

        if (strcmp(child0->name, "atlas") == 0)
        {
            bDoneAtlas = true;
            LoadAtlas(pUIData, child0, pDC);
        }
        else if (strcmp(child0->name, "screen") == 0)
        {
            bDoneScreen = true;
            TryLoadViewModel(pUIData, child0);
            AddNodeChildren(pUIData->rootWidget, child0, pUIData);
        }

        if (strcmp(child1->name, "atlas") == 0 && !bDoneAtlas)
        {
            bDoneAtlas = true;
            LoadAtlas(pUIData, child1, pDC);
        }
        else if (strcmp(child1->name, "screen") == 0 && !bDoneScreen)
        {
            bDoneScreen = true;
            TryLoadViewModel(pUIData, child1);
            AddNodeChildren(pUIData->rootWidget, child1, pUIData);
        }
        if (!bDoneAtlas || !bDoneScreen)
        {
            Log_Error("%s ui xml file doesn't have both screen and atlas components", __FUNCTION__);
        }

        xmlFreeDoc(pXMLDoc);

        struct UIWidget* pWidget = UI_GetWidget(pUIData->rootWidget);
        pWidget->scriptCallbacks.viewmodelTable = pUIData->hViewModel;

        InitializeWidgets(pUIData->rootWidget);

        pUIData->hVertexBuffer = pDC->NewUIVertexBuffer(2048);
    }

}

static void OnWindowSizeChanged(struct GameFrameworkLayer* pLayer, int newW, int newH)
{
    XMLUIData* pData = pLayer->userData;
    RootWidget_OnWindowSizeChanged(pData->rootWidget, newW, newH);

    struct UIWidget* pWidget = UI_GetWidget(pData->rootWidget);
    SetRootWidgetIsDirty(pData->rootWidget, true);
}


void XMLUIGameLayer_Get(struct GameFrameworkLayer* pLayer, struct XMLUIGameLayerOptions* pOptions)
{
    pLayer->userData = malloc(sizeof(XMLUIData));
    if (!pLayer->userData) { Log_Error("XMLUIGameLayer_Get: no memory"); return; }
    XMLUIData* pUIData = (XMLUIData*)pLayer->userData;

    memset(pLayer->userData, 0, sizeof(XMLUIData));

    strcpy(pUIData->xmlFilePath, pOptions->xmlPath);
    pUIData->rootWidget = NewRootWidget();
    RootWidget_OnWindowSizeChanged(pUIData->rootWidget, Mn_GetScreenWidth(), Mn_GetScreenHeight());
    pLayer->draw = &Draw;
    pLayer->update = &Update;
    pLayer->input = &Input;
    pLayer->onPop = &OnPop;
    pLayer->onPush = &OnPush;
    pLayer->onWindowDimsChanged = &OnWindowSizeChanged;
    pLayer->flags = 0;
    pLayer->flags |= EnableDrawFn | EnableInputFn | EnableUpdateFn | EnableOnPop | EnableOnPush;
    pUIData->pWidgetVertices = NEW_VECTOR(WidgetVertex);
    pUIData->pChildrenChangeRequests = NEW_VECTOR(struct WidgetChildrenChangeRequest);
    if (pOptions->bLoadImmediately)
    {
        LoadUIData(pUIData, pOptions->pDc);
    }

}


void XMLUI_PushGameFrameworkLayer(const char* xmlPath)
{
    struct GameFrameworkLayer testLayer;
    memset(&testLayer, 0, sizeof(struct GameFrameworkLayer));
    struct XMLUIGameLayerOptions options;
    options.bLoadImmediately = false;
    options.xmlPath = xmlPath;
    options.pDc = NULL;
    testLayer.flags |= (EnableOnPush | EnableOnPop);
    Log_Verbose("making xml ui layer");
    XMLUIGameLayer_Get(&testLayer, &options);
    Log_Verbose("done");
    Log_Verbose("pushing framework layer");
    GF_PushGameFrameworkLayer(&testLayer);
}