File SliderWidget.c

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

Go to the documentation of this file

#include "SliderWidget.h"
#include "Widget.h"
#include "DataNode.h"
#include "XMLUIGameLayer.h"
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include "Atlas.h"
#include "AssertLib.h"
#include "StaticWidget.h"
#include "Atlas.h"
#include "WidgetVertexOutputHelpers.h"
#include "RootWidget.h"
#include "Scripting.h"
#include "Log.h"


float SliderWidget_GetWidth(struct SliderData* pData, struct WidgetPadding* pPadding)
{
    switch (pData->orientation)
    {
    case SO_Horizontal:
        return pData->lengthPx + pPadding->paddingLeft + pPadding->paddingRight;
    case SO_Vertical:
    {
        AtlasSprite* pSprite = At_GetSprite(pData->sliderStaticData.sprite, pData->sliderStaticData.atlas);
        return pSprite->widthPx * pData->sliderStaticData.scale.scaleX + pPadding->paddingLeft + pPadding->paddingRight;
    }
    default:
        EASSERT(false);
        break;
    }
    return 0.0f;
}

float SliderWidget_GetHeight(struct SliderData* pData, struct WidgetPadding* pPadding)
{
    switch (pData->orientation)
    {
    case SO_Horizontal:
    {
        AtlasSprite* pSprite = At_GetSprite(pData->sliderStaticData.sprite, pData->sliderStaticData.atlas);
        return pSprite->heightPx * pData->sliderStaticData.scale.scaleY + pPadding->paddingTop + pPadding->paddingBottom;
    }
    case SO_Vertical:
    {
        return pData->lengthPx + pPadding->paddingTop + pPadding->paddingBottom;
    }
    default:
        EASSERT(false);
        break;
    }

    return 0.0f;
}

static float GetWidth(struct UIWidget* pWidget, struct UIWidget* pParent)
{
    struct SliderData* pData = pWidget->pImplementationData;
    return SliderWidget_GetWidth(pData, &pWidget->padding);
}

static float GetHeight(struct UIWidget* pWidget, struct UIWidget* pParent)
{
    struct SliderData* pData = pWidget->pImplementationData;
    return SliderWidget_GetHeight(pData, &pWidget->padding);

}

static void LayoutChildren(struct UIWidget* pWidget, struct UIWidget* pParent)
{
}

static void OnDestroy(struct UIWidget* pWidget)
{
    free(pWidget->pImplementationData);
}

static void Populate3PanelRailQuads(float left, float top, WidgetQuad* pOutQuads, struct SliderData* pData)
{
    vec2 a2 = {
        left,
        top
    };

    switch (pData->orientation)
    {
    case SO_Vertical:
        {
            AtlasSprite* pSprite = At_GetSprite(pData->railStaticData.sprite, pData->railStaticData.atlas);
            vec2 tl = { 0,0 };
            vec2 br = { pSprite->widthPx, pSprite->heightPx / 3.0f };
            vec2 addition = { 0, pSprite->heightPx / 3.0f };
            PopulateWidgetQuad(&pOutQuads[0], pSprite, tl, br);
            glm_vec2_add(tl, addition, tl);
            glm_vec2_add(br, addition, br);
            PopulateWidgetQuad(&pOutQuads[1], pSprite, tl, br);
            glm_vec2_add(tl, addition, tl);
            glm_vec2_add(br, addition, br);
            PopulateWidgetQuad(&pOutQuads[2], pSprite, tl, br);

            vec2 trans = {
                0,
                pData->lengthPx - pSprite->heightPx / 3
            };

            TranslateWidgetQuad(trans, &pOutQuads[2]);
            pOutQuads[1].v[VL_TL].y = pOutQuads[0].v[VL_BL].y;
            pOutQuads[1].v[VL_TR].y = pOutQuads[0].v[VL_BR].y;
            pOutQuads[1].v[VL_BL].y = pOutQuads[2].v[VL_TL].y;
            pOutQuads[1].v[VL_BR].y = pOutQuads[2].v[VL_TR].y;
            for (int i = 0; i < 3; i++)
            {
                TranslateWidgetQuad(a2, &pOutQuads[i]);
            }
            break;
        }
    case SO_Horizontal:
        {
            AtlasSprite* pSprite = At_GetSprite(pData->railStaticData.sprite, pData->railStaticData.atlas);
            vec2 tl = { 0,0 };
            vec2 br = { pSprite->widthPx/3, pSprite->heightPx };
            vec2 addition = { pSprite->widthPx / 3, 0 };
            PopulateWidgetQuad(&pOutQuads[0], pSprite, tl, br);
            glm_vec2_add(tl, addition, tl);
            glm_vec2_add(br, addition, br);
            PopulateWidgetQuad(&pOutQuads[1], pSprite, tl, br);
            glm_vec2_add(tl, addition, tl);
            glm_vec2_add(br, addition, br);
            PopulateWidgetQuad(&pOutQuads[2], pSprite, tl, br);
            vec2 trans = {
                pData->lengthPx - pSprite->widthPx / 3,
                0
            };

            TranslateWidgetQuad(trans, &pOutQuads[2]);
            pOutQuads[1].v[VL_TL].x = pOutQuads[0].v[VL_TR].x;
            pOutQuads[1].v[VL_BL].x = pOutQuads[0].v[VL_BR].x;
            pOutQuads[1].v[VL_TR].x = pOutQuads[2].v[VL_TL].x;
            pOutQuads[1].v[VL_BR].x = pOutQuads[2].v[VL_BL].x;
            for (int i = 0; i < 3; i++)
            {
                TranslateWidgetQuad(a2, &pOutQuads[i]);
            }
            break;
        }
    }
}

static void PopulateSliderQuad(float left, float top, WidgetQuad* pOutQuad, struct SliderData* pData)
{
    //EASSERT(pData->fVal >= pData->fMinVal && pData->fVal <= pData->fMaxVal);
    AtlasSprite* pSprite = At_GetSprite(pData->sliderStaticData.sprite, pData->sliderStaticData.atlas);
    float fraction = (pData->fVal - pData->fMinVal) / (pData->fMaxVal - pData->fMinVal);
    PopulateWidgetQuadWholeSprite(pOutQuad, pSprite);
    vec2 t0 = {
        left,
        top
    };
    TranslateWidgetQuad(t0, pOutQuad);
    switch (pData->orientation)
    {
    case SO_Vertical:
    {
        float maxLen = pData->lengthPx - pSprite->heightPx;
        vec2 t = {
            0,
            maxLen* fraction
        };
        TranslateWidgetQuad(t, pOutQuad);
        break;
    }
    case SO_Horizontal:
    {
        float maxLen = pData->lengthPx - pSprite->widthPx;
        vec2 t = {
            maxLen* fraction,
            0
        };
        TranslateWidgetQuad(t, pOutQuad);
        break;
    }
    default:
        EASSERT(false);
        break;
    }
}

void* SliderWidget_OnOutputVerts(VECTOR(WidgetVertex) pOutVerts, struct SliderData* pData, float top, float left, struct WidgetPadding* pPadding)
{
    WidgetQuad rail[4];
    top = top + pPadding->paddingTop;
    left = left + pPadding->paddingLeft;
    Populate3PanelRailQuads(left, top, rail, pData);
    PopulateSliderQuad(left, top, &rail[3], pData);
    return OutputWidgetQuads(pOutVerts, rail, 4);
}

static void* OnOutputVerts(struct UIWidget* pWidget, VECTOR(WidgetVertex) pOutVerts)
{
    struct SliderData* pData = pWidget->pImplementationData;
    return SliderWidget_OnOutputVerts(pOutVerts, pData, pWidget->top, pWidget->left, &pWidget->padding);
}

static void OnPropertyChanged(struct UIWidget* pThisWidget, struct WidgetPropertyBinding* pBinding)
{
    struct SliderData* pData = pThisWidget->pImplementationData;
    if (strcmp(pBinding->boundPropertyName, "val") == 0)
    {
        char* fnName = UI_MakeBindingGetterFunctionName(pBinding->name);
        Sc_CallFuncInRegTableEntryTable(pThisWidget->scriptCallbacks.viewmodelTable, fnName, NULL, 0, 1);
        free(fnName);
        pData->fVal = Sc_Float();
        Sc_ResetStack();
        SetRootWidgetIsDirty(pData->rootWidget, true);
    }
}

void SliderWudget_SetSliderPositionFromMouse(struct UIWidget* pWidget, struct SliderData* pData, float x, float y, float top, float left, struct WidgetPadding padding)
{
    switch (pData->orientation)
    {
    case SO_Vertical:
    {
        float zeroY = top + padding.paddingTop;
        float maxY = top + padding.paddingTop + pData->lengthPx;
        float fraction = (y - zeroY) / (maxY - zeroY);
        pData->fVal = pData->fMinVal + fraction * (pData->fMaxVal - pData->fMinVal);
        break;
    }
    case SO_Horizontal:
    {
        float zeroX = left + padding.paddingLeft;
        float maxX = left + padding.paddingLeft + pData->lengthPx;
        float fraction = (x - zeroX) / (maxX - zeroX);
        pData->fVal = pData->fMinVal + fraction * (pData->fMaxVal - pData->fMinVal);
        break;
    }
    default:
        break;
    }
    struct WidgetPropertyBinding* pBinding = UI_FindBinding(pWidget, "val");
    if (pBinding)
    {
        char* setterName = UI_MakeBindingSetterFunctionName(pBinding->name);
        struct ScriptCallArgument arg;
        arg.type = SCA_number;
        arg.val.number = pData->fVal;
        Sc_CallFuncInRegTableEntryTable(pWidget->scriptCallbacks.viewmodelTable, setterName, &arg, 1, 0);
        free(setterName);

    }
    SetRootWidgetIsDirty(pData->rootWidget, true);
}

static void MouseButtonDownCallback(struct UIWidget* pWidget, float x, float y, int btn)
{
    struct SliderData* pData = pWidget->pImplementationData;
    pData->bMouseDown = true;
    SliderWudget_SetSliderPositionFromMouse(pWidget, pData, x, y, pWidget->top, pWidget->left, pWidget->padding);
}

static void MouseButtonUpCallback(struct UIWidget* pWidget, float x, float y, int btn)
{
    struct SliderData* pData = pWidget->pImplementationData;
    pData->bMouseDown = false;
}

static void MouseLeaveCallback(struct UIWidget* pWidget, float x, float y)
{
    struct SliderData* pData = pWidget->pImplementationData;
    pData->bMouseDown = false;
}

static void MouseMoveCallback(struct UIWidget* pWidget, float x, float y)
{
    struct SliderData* pData = pWidget->pImplementationData;
    if (pData->bMouseDown)
    {
        SliderWudget_SetSliderPositionFromMouse(pWidget, pData, x, y, pWidget->top, pWidget->left, pWidget->padding);
    }
}

static void PopulateStaticInternal(
    struct DataNode* pDataNode,
    struct StaticWidgetData* pWidgetData,
    struct XMLUIData* pUILayerData,
    const char* spriteAttribName,
    const char* scaleXAttribName,
    const char* scaleYAttribName)
{
    if(pDataNode->fnGetPropType(pDataNode, spriteAttribName) == DN_String)
    {
        size_t len = pDataNode->fnGetStrlen(pDataNode, spriteAttribName);
        if (pWidgetData->imageName)
        {
            free(pWidgetData->imageName);
        }
        pWidgetData->imageName = malloc(len + 1);
        pDataNode->fnGetStrcpy(pDataNode, spriteAttribName, pWidgetData->imageName);
        pWidgetData->sprite = At_FindSprite(pWidgetData->imageName, pUILayerData->atlas);
        pWidgetData->atlas = pUILayerData->atlas;
    }
    if(pDataNode->fnGetPropType(pDataNode, scaleXAttribName) == DN_Float)
    {
        pWidgetData->scale.scaleX = pDataNode->fnGetFloat(pDataNode, scaleXAttribName);
    }
    if(pDataNode->fnGetPropType(pDataNode, scaleYAttribName) == DN_Float)
    {
        pWidgetData->scale.scaleY = pDataNode->fnGetFloat(pDataNode, scaleYAttribName);
    }
}

static void ParseBindingEspressionAttribute(char* attribName, char* attribContent, struct UIWidget* pWidget, struct SliderData* pData, struct XMLUIData* pUILayerData)
{
    if (strcmp(attribName, "val") == 0)
    {
        UI_AddFloatPropertyBinding(pWidget, attribName, attribContent, &pData->fVal, pUILayerData->hViewModel);
    }
    else
    {
        Log_Error("invalid property binding: %s", attribContent);
    }

}

static void MakeSliderFromXML(struct UIWidget* pWidget, struct SliderData* pData, struct DataNode* pXMLNode, struct XMLUIData* pUILayerData)
{
    PopulateStaticInternal(pXMLNode, &pData->railStaticData, pUILayerData,
        "railSprite",
        "railScaleX",
        "railScaleY");
    PopulateStaticInternal(pXMLNode, &pData->sliderStaticData, pUILayerData,
        "sliderSprite",
        "sliderScaleX",
        "sliderScaleY");
    if(pXMLNode->fnGetPropType(pXMLNode, "orientation") == DN_String)
    {
        if(pXMLNode->fnContentStrCmp(pXMLNode, "horizontal"))
        {
            pData->orientation = SO_Horizontal;
        }
        else if (pXMLNode->fnContentStrCmp(pXMLNode, "vertical"))
        {
            pData->orientation = SO_Vertical;
        }
        else
        {
            Log_Warning("invalid slider orientation");
        }
    }
    if(pXMLNode->fnGetPropType(pXMLNode, "val") == DN_String)
    {
        size_t len = pXMLNode->fnGetStrlen(pXMLNode, "val");
        char* attribute = malloc(len + 1);
        pXMLNode->fnGetStrcpy(pXMLNode, "val", attribute);
        if(UI_IsAttributeStringABindingExpression(attribute))
        {
            ParseBindingEspressionAttribute("val", attribute, pWidget, pData, pUILayerData);
        }
        free(attribute);
    }
    if(pXMLNode->fnGetPropType(pXMLNode, "maxVal") == DN_Float)
    {
        pData->fMaxVal = pXMLNode->fnGetFloat(pXMLNode, "maxVal");
    }
    if(pXMLNode->fnGetPropType(pXMLNode, "minVal") == DN_Float)
    {
        pData->fMinVal = pXMLNode->fnGetFloat(pXMLNode, "minVal");
        pData->fVal = pData->fMinVal;
    }
}

void SliderWidget_MakeDefaultSliderWidget(struct SliderData* pData, struct XMLUIData* pUILayerData, enum SliderOrientation orientation)
{
    pData->orientation = orientation;
    pData->rootWidget = pUILayerData->rootWidget;

    // slider and rail sprites
    switch (pData->orientation)
    {
    case SO_Horizontal:
        {
            pData->sliderStaticData.imageName = malloc(strlen("defaultSliderHorizontal") + 1);
            strcpy(pData->sliderStaticData.imageName, "defaultSliderHorizontal");
            pData->railStaticData.imageName = malloc(strlen("defaultRailHorizontal") + 1);
            strcpy(pData->railStaticData.imageName, "defaultRailHorizontal");
            break;
        }
    case SO_Vertical:
    {
        pData->sliderStaticData.imageName = malloc(strlen("defaultSliderVertical") + 1);
        strcpy(pData->sliderStaticData.imageName, "defaultSliderVertical");
        pData->railStaticData.imageName = malloc(strlen("defaultRailVertical") + 1);
        strcpy(pData->railStaticData.imageName, "defaultRailVertical");
        break;
    }
    default:
        break;
    }


    pData->sliderStaticData.scale.scaleX = 1.0f;
    pData->sliderStaticData.scale.scaleY = 1.0f;
    pData->railStaticData.scale.scaleX = 1.0f;
    pData->railStaticData.scale.scaleY = 1.0f;

    pData->sliderStaticData.atlas = pUILayerData->atlas;
    pData->railStaticData.atlas = pUILayerData->atlas;

    pData->sliderStaticData.sprite = At_FindSprite(pData->sliderStaticData.imageName, pUILayerData->atlas);
    pData->railStaticData.sprite = At_FindSprite(pData->railStaticData.imageName, pUILayerData->atlas);

    // slider data
    pData->lengthPx = 200.0f;
    pData->fMaxVal = 200.0f;
    pData->fMinVal = 0.0f;
    pData->fVal = 0.0f;

}


static void MakeWidgetIntoSliderWidget(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->pImplementationData = malloc(sizeof(struct SliderData));

    pWidget->cCallbacks.Callbacks[WC_OnMouseDown].type = WC_OnMouseDown;
    pWidget->cCallbacks.Callbacks[WC_OnMouseDown].callback.mouseBtnFn = &MouseButtonDownCallback;

    pWidget->cCallbacks.Callbacks[WC_OnMouseUp].type = WC_OnMouseUp;
    pWidget->cCallbacks.Callbacks[WC_OnMouseUp].callback.mouseBtnFn = &MouseButtonUpCallback;

    pWidget->cCallbacks.Callbacks[WC_OnMouseLeave].type = WC_OnMouseLeave;
    pWidget->cCallbacks.Callbacks[WC_OnMouseLeave].callback.mousePosFn = &MouseLeaveCallback;

    pWidget->cCallbacks.Callbacks[WC_OnMouseMove].type = WC_OnMouseMove;
    pWidget->cCallbacks.Callbacks[WC_OnMouseMove].callback.mousePosFn = &MouseMoveCallback;

    memset(pWidget->pImplementationData, 0, sizeof(struct SliderData));
    SliderWidget_MakeDefaultSliderWidget(pWidget->pImplementationData, pUILayerData, SO_Horizontal);
    struct SliderData* pData = pWidget->pImplementationData;
    MakeSliderFromXML(pWidget, pData, pDataNode, pUILayerData);
}

HWidget SliderWidgetNew(HWidget hParent, struct DataNode* pDataNode, struct XMLUIData* pUILayerData)
{
    HWidget hWidget = UI_NewBlankWidget();
    MakeWidgetIntoSliderWidget(hWidget, pDataNode, pUILayerData);
    return hWidget;
}