File BackgroundBoxWidget.c

File List > engine > src > gameframework > layers > UI > widgets > BackgroundBoxWidget.c

Go to the documentation of this file

#include "BackgroundBoxWidget.h"
#include "XMLUIGameLayer.h"
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include "WidgetVertexOutputHelpers.h"
#include "AssertLib.h"
#include "Scripting.h"
#include "DataNode.h"
#include "Log.h"

static float GetWidth(struct UIWidget* pWidget, struct UIWidget* pParent)
{
    EASSERT(UI_CountWidgetChildrenPtr(pWidget) == 1);
    if (pWidget->hFirstChild != NULL_HWIDGET)
    {
        struct UIWidget* pFirstChild = UI_GetWidget(pWidget->hFirstChild);
        return pFirstChild->fnGetWidth(pFirstChild, pWidget) + pWidget->padding.paddingLeft + pWidget->padding.paddingRight;
    }
    return 0.0f;
}

static float GetHeight(struct UIWidget* pWidget, struct UIWidget* pParent)
{
    EASSERT(UI_CountWidgetChildrenPtr(pWidget) == 1);
    if (pWidget->hFirstChild != NULL_HWIDGET)
    {
        struct UIWidget* pFirstChild = UI_GetWidget(pWidget->hFirstChild);
        return pFirstChild->fnGetHeight(pFirstChild, pWidget) + pWidget->padding.paddingTop + pWidget->padding.paddingBottom;
    }
    return 0.0f;
}

static void LayoutChildren(struct UIWidget* pWidget, struct UIWidget* pParent)
{
    struct UIWidget* pChild = UI_GetWidget(pWidget->hFirstChild);
    if (pChild)
    {
        pChild->top = pWidget->top + pWidget->padding.paddingTop;
        pChild->left = pWidget->left + pWidget->padding.paddingLeft;
        pChild->fnLayoutChildren(pChild, pWidget);
    }
}

void BackgroundBoxWidget_Destroy(struct BackgroundBoxWidgetData* pBBoxData)
{
    if (pBBoxData->imageName)
    {
        free(pBBoxData->imageName);
    }
}


static void OnDestroy(struct UIWidget* pWidget)
{
    struct BackgroundBoxWidgetData* pBBoxData = pWidget->pImplementationData;

    free(pWidget->pImplementationData);
}

void* OnOutputVerts_3StripsHorizontal(
    struct BackgroundBoxWidgetData* pBBoxData,
    VECTOR(WidgetVertex) pOutVerts,
    float totalWidth,
    float totalHeight,
    const struct WidgetPadding* padding,
    float left, float top
)
{
    AtlasSprite* pAtlasSprite = At_GetSprite(pBBoxData->sprite, pBBoxData->atlas);
    const struct WidgetScale* pScale = &pBBoxData->scale;
    WidgetQuad quads[3]; // in row first order, ie topleft, topmcentre, topright, middleleft, middlecentre, ect...
    float height = (float)pAtlasSprite->heightPx;
    float widthOver3 = (float)pAtlasSprite->widthPx / 3.0f;
    float widgetWidth = totalWidth - padding->paddingLeft - padding->paddingRight;//GetWidth(pWidget, NULL) - pWidget->padding.paddingLeft - pWidget->padding.paddingRight;
    float widgetHeight = totalHeight - padding->paddingTop - padding->paddingBottom;

    vec2 tl, br;
    tl[0] = 0.0f;
    tl[1] = 0.0f;
    br[0] = widthOver3;
    br[1] = height;
    for (int row = 0; row < 3; row++)
    {
        PopulateWidgetQuad(&quads[row], pAtlasSprite, tl, br);
        tl[0] += widthOver3;
        br[0] += widthOver3;
    }

    vec2 translation = { left + padding->paddingLeft, top + padding->paddingTop };
    for (int i = 0; i < 3; i++)
    {
        TranslateWidgetQuad(translation, &quads[i]);
    }

    vec2 size = {
        widthOver3 * pScale->scaleX,
        height * pScale->scaleY,

    };
    SizeWidgetQuad(size, &quads[0]);
    SizeWidgetQuad(size, &quads[1]);
    SizeWidgetQuad(size, &quads[2]);

    quads[1].v[VL_TR].x += widgetWidth - size[0];
    quads[1].v[VL_BR].x += widgetWidth - size[0];
    quads[1].v[VL_TL].x += size[0];
    quads[1].v[VL_BL].x += size[0];

    translation[1] = 0.0f;
    translation[0] = widgetWidth - size[0];
    TranslateWidgetQuad(translation, &quads[2]);
    for (int i = 0; i < 3; i++)
    {
        pOutVerts = OutputWidgetQuad(pOutVerts, &quads[i]);
    }
    return pOutVerts;
}

void* OnOutputVerts_3StripsVertical(
    struct BackgroundBoxWidgetData* pBBoxData,
    VECTOR(WidgetVertex) pOutVerts,
    float totalWidth,
    float totalHeight,
    const struct WidgetPadding* padding,
    float left, float top
)
{
    return pOutVerts;
}

void* BackgroundBoxWidget_OutputVerts(
    struct BackgroundBoxWidgetData* pBBoxData,
    VECTOR(WidgetVertex) pOutVerts,
    float totalWidth,
    float totalHeight,
    const struct WidgetPadding* padding,
    float left, float top
)
{
    AtlasSprite* pAtlasSprite = At_GetSprite(pBBoxData->sprite, pBBoxData->atlas);
    const struct WidgetScale* pScale = &pBBoxData->scale;
    float widgetWidth = totalWidth - padding->paddingLeft - padding->paddingRight;//GetWidth(pWidget, NULL) - pWidget->padding.paddingLeft - pWidget->padding.paddingRight;
    float widgetHeight = totalHeight - padding->paddingTop - padding->paddingBottom;

    if (widgetWidth <= pAtlasSprite->widthPx * pBBoxData->scale.scaleX && widgetHeight > pAtlasSprite->heightPx * pBBoxData->scale.scaleY)
    {
        return OnOutputVerts_3StripsVertical(pBBoxData, pOutVerts, totalWidth, totalHeight, padding, left, top);
    }
    else if (widgetWidth > pAtlasSprite->widthPx * pBBoxData->scale.scaleX && widgetHeight <= pAtlasSprite->heightPx * pBBoxData->scale.scaleY)
    {
        return OnOutputVerts_3StripsHorizontal(pBBoxData, pOutVerts, totalWidth, totalHeight, padding, left, top);
    }

    // widget is bigger in both dimensions than the sprite - do 9 panel scaling.
    /*
     https://en.wikipedia.org/wiki/9-slice_scaling#:~:text=9%2Dslice%20scaling%20(also%20known,a%20grid%20of%20nine%20parts.
                            ________
         _ _ _             |_|____|_|
        |_|_|_|   ->       | |    | |
        |_|_|_|            | |    | |
        |_|_|_|            |_|____|_|
                           |_|____|_|


    */
    WidgetQuad quads[9]; // in row first order, ie topleft, topmcentre, topright, middleleft, middlecentre, ect...
    float widthOver3 = (float)pAtlasSprite->widthPx / 3.0f;
    float heightOver3 = (float)pAtlasSprite->heightPx / 3.0f;
    vec2 tl, br;

    tl[0] = 0;
    tl[1] = 0;
    br[0] = widthOver3;
    br[1] = heightOver3;
    int i = 0;
    for (int row = 0; row < 3; row++)
    {
        for (int col = 0; col < 3; col++)
        {
            PopulateWidgetQuad(&quads[i++], pAtlasSprite, tl, br);

            tl[0] += widthOver3;
            br[0] += widthOver3;
        }
        tl[0] = 0;
        br[0] = widthOver3;
        tl[1] += heightOver3;
        br[1] += heightOver3;
    }

    vec2 translation = { left + padding->paddingLeft, top + padding->paddingTop };
    for (int i = 0; i < 9; i++)
    {
        TranslateWidgetQuad(translation, &quads[i]);
    }

    vec2 size = {
        widthOver3 * pScale->scaleX,
        heightOver3 * pScale->scaleY,

    };
    SizeWidgetQuad(size, &quads[0]);

    // top row
    translation[0] = size[0];
    translation[1] = 0.0f;
    TranslateWidgetQuad(translation, &quads[1]);
    float borderPieceNewWidth = widgetWidth - size[0] * 2;
    vec2 horizontalBorderSize = { borderPieceNewWidth, size[1] };
    SizeWidgetQuad(horizontalBorderSize, &quads[1]);

    translation[0] = size[0] + borderPieceNewWidth;
    translation[1] = 0.0f;
    TranslateWidgetQuad(translation, &quads[2]);
    SizeWidgetQuad(size, &quads[2]);

    // middle row
    translation[0] = 0.0f;
    translation[1] = size[1];
    TranslateWidgetQuad(translation, &quads[3]);
    float borderPieceNewHeight = widgetHeight - size[1] * 2;
    vec2 verticalBorderSize = { size[0], borderPieceNewHeight };
    SizeWidgetQuad(verticalBorderSize, &quads[3]);

    translation[0] = size[0];
    translation[1] = size[1];
    TranslateWidgetQuad(translation, &quads[4]);
    vec2 middlePieceSize = { borderPieceNewWidth, borderPieceNewHeight };
    SizeWidgetQuad(middlePieceSize, &quads[4]);

    translation[0] = size[0] + borderPieceNewWidth;
    translation[1] = size[1];
    TranslateWidgetQuad(translation, &quads[5]);
    SizeWidgetQuad(verticalBorderSize, &quads[5]);

    // bottom row
    translation[0] = 0.0f;
    translation[1] = size[1] + borderPieceNewHeight;
    TranslateWidgetQuad(translation, &quads[6]);
    SizeWidgetQuad(size, &quads[6]);

    translation[0] = size[0];
    translation[1] = size[1] + borderPieceNewHeight;
    TranslateWidgetQuad(translation, &quads[7]);
    SizeWidgetQuad(horizontalBorderSize, &quads[7]);

    translation[0] = size[0] + borderPieceNewWidth;
    translation[1] = size[1] + borderPieceNewHeight;
    TranslateWidgetQuad(translation, &quads[8]);
    SizeWidgetQuad(size, &quads[8]);

    for (int i = 0; i < 9; i++)
    {
        pOutVerts = OutputWidgetQuad(pOutVerts, &quads[i]);
    }

    return pOutVerts;
}

static void* OnOutputVerts(struct UIWidget* pWidget, VECTOR(WidgetVertex) pOutVerts)
{
    float totalW = GetWidth(pWidget, NULL);
    float totalH = GetHeight(pWidget, NULL);
    pOutVerts = BackgroundBoxWidget_OutputVerts(
        pWidget->pImplementationData,
        pOutVerts,
        totalW,
        totalH,
        &pWidget->padding,
        pWidget->left,
        pWidget->top
    );
    pOutVerts = UI_Helper_OnOutputVerts(pWidget, pOutVerts);
}


static void OnPropertyChanged(struct UIWidget* pThisWidget, struct WidgetPropertyBinding* pBinding)
{
    struct BackgroundBoxWidgetData* pData = pThisWidget->pImplementationData;
    if (strcmp(pBinding->boundPropertyName, "sprite") == 0)
    {
        char* fnName = UI_MakeBindingGetterFunctionName(pBinding->name);
        Sc_CallFuncInRegTableEntryTable(pThisWidget->scriptCallbacks.viewmodelTable, fnName, NULL, 0, 1);
        free(fnName);
        EASSERT(pData->imageName);
        free(pData->imageName);
        size_t len = Sc_StackTopStringLen();
        pData->imageName = malloc(len + 1);
        Sc_StackTopStrCopy(pData->imageName);
        pData->sprite = At_FindSprite(pData->imageName, pData->atlas);
    }
}

static void ParseBindingEspressionAttribute(char* pAttributeName, char* pAttributeContent, struct UIWidget* pWidget, struct BackgroundBoxWidgetData* pWidgetData, struct XMLUIData* pUILayerData)
{

    if (strcmp(pAttributeName, "sprite") == 0)
    {
        if (pWidgetData->imageName)
        {
            free(pWidgetData->imageName);
            pWidgetData->imageName = NULL;
        }
        EASSERT(pWidgetData->imageName == NULL);
        UI_AddStringPropertyBinding(pWidget, pAttributeName, pAttributeContent, &pWidgetData->imageName, pUILayerData->hViewModel);
        pWidgetData->sprite = At_FindSprite(pWidgetData->imageName, pWidgetData->atlas);
    }
    else
    {
        Log_Warning("invalid property binding: %s", pAttributeContent);
    }
}

static void ParseLiteralBackgroundBoxData(struct BackgroundBoxWidgetData* pWidgetData, const char* attributeContentBuffer)
{
    if (pWidgetData->imageName)
    {
        free(pWidgetData->imageName);
        pWidgetData->imageName = NULL;
    }
    EASSERT(pWidgetData->imageName == NULL);
    pWidgetData->imageName = malloc(strlen(attributeContentBuffer) + 1);
    strcpy(pWidgetData->imageName, attributeContentBuffer);
    pWidgetData->sprite = At_FindSprite(pWidgetData->imageName, pWidgetData->atlas);
}

void BackgroundBoxWidget_fromXML(struct UIWidget* pWidget, struct BackgroundBoxWidgetData* pWidgetData, struct DataNode* pDataNode, struct XMLUIData* pUILayerData)
{
    pWidgetData->scale.scaleX = 1.0f;
    pWidgetData->scale.scaleY = 1.0f;

    pWidgetData->atlas = pUILayerData->atlas;

    if (pDataNode->fnGetPropType(pDataNode, "sprite") == DN_String)
    {
        size_t len = pDataNode->fnGetStrlen(pDataNode, "sprite");
        if(!len)
        {
            Log_Warning("empty string");
            return;
        }
        char* val = malloc(len + 1);
        pDataNode->fnGetStrcpy(pDataNode, "sprite", val); 
        if (UI_IsAttributeStringABindingExpression(val))
        {
            ParseBindingEspressionAttribute("sprite", val, pWidget, pWidgetData, pUILayerData);
        }
        else
        {
            ParseLiteralBackgroundBoxData(pWidgetData, val);
        }
        free(val);
    }
    if( pDataNode->fnGetPropType(pDataNode, "scaleX") == DN_Float)
    {
        pWidgetData->scale.scaleX = pDataNode->fnGetFloat(pDataNode, "scaleX");
    }
    if( pDataNode->fnGetPropType(pDataNode, "scaleY") == DN_Float)
    {
        pWidgetData->scale.scaleY = pDataNode->fnGetFloat(pDataNode, "scaleY");
    }
    if (pWidgetData->imageName)
    {
        pWidgetData->sprite = At_FindSprite(pWidgetData->imageName, pUILayerData->atlas);
        pWidgetData->atlas = pUILayerData->atlas;
    }
}

static void MakeWidgetIntoBackgroundBoxWidget(HWidget hWidget, struct DataNode* pXMLNode, 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->pImplementationData = malloc(sizeof(struct BackgroundBoxWidgetData));
    memset(pWidget->pImplementationData, 0, sizeof(struct BackgroundBoxWidgetData));

    struct BackgroundBoxWidgetData* pWidgetData = pWidget->pImplementationData;

    BackgroundBoxWidget_fromXML(pWidget, pWidgetData, pXMLNode, pUILayerData);
}

HWidget BackgroundBoxWidgetNew(HWidget hParent, struct DataNode* pXMLNode, struct XMLUIData* pUILayerData)
{
    HWidget hWidget = UI_NewBlankWidget();
    MakeWidgetIntoBackgroundBoxWidget(hWidget, pXMLNode, pUILayerData);
    return hWidget;
}