#include "ui_widgets.h" #include "game_state.h" #include "components.h" #include f32 uiGetScale() { // Assuming 16:9 aspect f32 scaleX = GetScreenWidth() / 1280.0f; f32 scaleY = GetScreenHeight() / 720.0f; return BZ_MIN(scaleX, scaleY); } Font getFont() { static const Game *game = NULL; if (!game) { game = ecs_singleton_get(ECS, Game); } return game->font; } BzUINode *uiPushDivParentPercentage(f32 xPercent, f32 yPercent) { return bzUIPushDiv(UI, (BzUISize) { .kind = BZ_UI_SIZE_REL_PARENT, .value = xPercent, }, (BzUISize) { .kind = BZ_UI_SIZE_REL_PARENT, .value = yPercent }); } void uiBaseLabel(const char *txt, Font font, f32 scl, Color color) { BzUINode *node = bzUINodeMake(UI, bzUIKeyFromString(txt), &(BzUINodeDesc) { .flags = BZ_UI_DRAW_TEXT | BZ_UI_DRAW_TEXT_SHADOW | BZ_UI_ALIGN_CENTER, .semanticSize[BZ_UI_AXIS_X] = { .kind = BZ_UI_SIZE_FIT, }, .semanticSize[BZ_UI_AXIS_Y] = { .kind = BZ_UI_SIZE_FIT }, .padding = {5, 5, 5, 5}, }); bzUISetTextStyle(UI, node, (BzUITextStyle) { .text = txt, .font = font, .fontSpacing = 2 * uiGetScale() * scl, .fontSize = 62 * uiGetScale() * scl, .normal = color, .hover = color, .active = color, }); bzUISetTextShadowStyle(UI, node, (BzUITextShadowStyle) { .offset[BZ_UI_AXIS_X] = 2 * uiGetScale() * scl, .offset[BZ_UI_AXIS_Y] = 2 * uiGetScale() * scl, .normal = BLACK, .hover = BLACK, .active = BLACK, }); } bool uiBaseTextButton(const char *txt, Font font, f32 scl, bool enabled) { BzUINode *node = bzUINodeMake(UI, bzUIKeyFromString(txt), &(BzUINodeDesc) { .flags = BZ_UI_DRAW_TEXT | BZ_UI_DRAW_TEXT_SHADOW | BZ_UI_ALIGN_CENTER, .semanticSize[BZ_UI_AXIS_X] = { .kind = BZ_UI_SIZE_FIT, }, .semanticSize[BZ_UI_AXIS_Y] = { .kind = BZ_UI_SIZE_FIT }, .padding = {5, 0, 5, 0}, .margin = {5, 5, 5, 5}, }); bzUISetTextStyle(UI, node, (BzUITextStyle) { .text = txt, .font = font, .fontSpacing = 2 * uiGetScale() * scl, .fontSize = 62 * uiGetScale() * scl, .normal = enabled ? WHITE : GRAY, .hover = enabled ? GRAY : GRAY, .active = enabled ? GRAY : LIGHTGRAY, }); bzUISetTextShadowStyle(UI, node, (BzUITextShadowStyle) { .offset[BZ_UI_AXIS_X] = 2 * uiGetScale() * scl, .offset[BZ_UI_AXIS_Y] = 2 * uiGetScale() * scl, .normal = BLACK, .hover = BLACK, .active = BLACK, }); return bzUIGetInteraction(UI, node).clicked; } void uiBaseCheckbox(const char *txt, Font font, f32 scl, bool *check) { BZ_ASSERT(check); bzUIPushDiv(UI, (BzUISize) {BZ_UI_SIZE_CHILD_SUM}, (BzUISize) {BZ_UI_SIZE_CHILD_MAX}); bzUISetParentLayout(UI, (BzUILayout) { BZ_UI_LAYOUT_FLEX_BOX, BZ_UI_FLEX_DIR_ROW | BZ_UI_FLEX_ALIGN_CENTER | BZ_UI_FLEX_JUSTIFY_START }); f32 size = 16 * uiGetScale() * scl; BzUINode *checkbox = bzUINodeMake(UI, bzUIGetUniqueKey(UI), &(BzUINodeDesc) { .flags = BZ_UI_CLICKABLE | BZ_UI_DRAW_BACKGROUND | BZ_UI_DRAW_BORDER, .semanticSize[BZ_UI_AXIS_X] = (BzUISize) { BZ_UI_SIZE_PIXELS, size}, .semanticSize[BZ_UI_AXIS_Y] = (BzUISize) { BZ_UI_SIZE_PIXELS, size}, .margin = {0, 0, 10 * uiGetScale() * scl, 0 } }); bzUISetBackgroundStyle(UI, checkbox, (BzUIBackgroundStyle) { .normal = *check ? WHITE : BLACK, .hover = GRAY, .active = YELLOW, }); bzUISetBorderStyle(UI, checkbox, (BzUIBorderStyle) { .thickness = 5.0f * uiGetScale() * scl, .normal = BLACK, .hover = BLACK, .active = BLACK, }); if (bzUIGetInteraction(UI, checkbox).clicked) { *check = !*check; } BzUINode *label = bzUINodeMake(UI, bzUIKeyFromString(txt), &(BzUINodeDesc) { .flags = BZ_UI_DRAW_TEXT | BZ_UI_DRAW_TEXT_SHADOW | BZ_UI_ALIGN_CENTER, .semanticSize[BZ_UI_AXIS_X] = { .kind = BZ_UI_SIZE_FIT, }, .semanticSize[BZ_UI_AXIS_Y] = { .kind = BZ_UI_SIZE_FIT }, }); bzUISetTextStyle(UI, label, (BzUITextStyle) { .text = txt, .font = font, .fontSpacing = 2 * uiGetScale() * scl, .fontSize = 32 * uiGetScale() * scl, .normal = WHITE, .hover = GRAY, .active = YELLOW }); bzUISetTextShadowStyle(UI, label, (BzUITextShadowStyle) { .offset[BZ_UI_AXIS_X] = 2 * uiGetScale() * scl, .offset[BZ_UI_AXIS_Y] = 2 * uiGetScale() * scl, .normal = BLACK, .hover = BLACK, .active = BLACK, }); if (bzUIGetInteraction(UI, label).clicked) { *check = !*check; } bzUIPopParent(UI); } void uiBaseSlider(const char *txt, Font font, f32 scl, f32 *value, f32 min, f32 max) { BZ_ASSERT(value); bzUIPushDiv(UI, (BzUISize) {BZ_UI_SIZE_CHILD_SUM}, (BzUISize) {BZ_UI_SIZE_CHILD_MAX}); bzUISetParentLayout(UI, (BzUILayout) { BZ_UI_LAYOUT_FLEX_BOX, BZ_UI_FLEX_DIR_ROW | BZ_UI_FLEX_ALIGN_CENTER | BZ_UI_FLEX_JUSTIFY_SPACE_BETWEEN }); uiBaseLabel(txt, font, 0.6 * scl, WHITE); bzUIPushDiv(UI, (BzUISize) {BZ_UI_SIZE_CHILD_SUM}, (BzUISize) { BZ_UI_SIZE_CHILD_MAX}); bzUISetParentLayout(UI, (BzUILayout) { BZ_UI_LAYOUT_FLEX_BOX, BZ_UI_FLEX_DIR_ROW | BZ_UI_FLEX_ALIGN_CENTER | BZ_UI_FLEX_JUSTIFY_END }); char buf[32]; snprintf(buf, sizeof(buf), "-##%s", txt); if (uiBaseTextButton(buf, font, 0.6 * scl, true)) (*value)--; *value = BZ_MAX(*value, min); snprintf(buf, sizeof(buf), "%2.0f##%s", *value, txt); uiBaseLabel(buf, font, 0.6 * scl, WHITE); snprintf(buf, sizeof(buf), "+##%s", txt); if (uiBaseTextButton(buf, font, 0.6 * scl, true)) (*value)++; *value = BZ_MIN(*value, max); bzUIPopParent(UI); bzUIPopParent(UI); } void uiMainMenuLabel(const char *txt) { uiBaseLabel(txt, getFont(), 1.8f, WHITE); } bool uiMainMenuButton(const char *txt, bool enabled) { return uiBaseTextButton(txt, getFont(), 0.8f, enabled); } void uiSettingsLabel(const char *txt) { uiBaseLabel(txt, getFont(), 0.8f, WHITE); } bool uiSettingsButton(const char *txt) { return uiBaseTextButton(txt, getFont(), 0.7f, true); } void uiSettingsCheckbox(const char *txt, bool *check) { uiBaseCheckbox(txt, getFont(), 1.0f, check); } void uiSettingsSlider(const char *txt, f32 *value) { f32 val = *value * 10.0f; uiBaseSlider(txt, getFont(), 1.0f, &val, 0, 10); *value = val / 10.0f; } void uiGameResCount(i32 amount, i32 capacity, Rectangle icon, Texture2D texture) { bzUIPushDiv(UI, (BzUISize) { BZ_UI_SIZE_CHILD_SUM }, (BzUISize) { BZ_UI_SIZE_CHILD_MAX }); bzUISetParentLayout(UI, (BzUILayout) { BZ_UI_LAYOUT_FLEX_BOX, BZ_UI_FLEX_DIR_ROW | BZ_UI_FLEX_ALIGN_CENTER }); char buf[64]; BzUIKey id = bzUIGetUniqueKey(UI); if (capacity == -1) snprintf(buf, sizeof(buf), "%d##%d", amount, id); else snprintf(buf, sizeof(buf), "%d/%d##%d", amount, capacity, id); const f32 scl = 0.4f; Color color = WHITE; if (capacity != -1 && amount >= capacity) color = RED; uiBaseLabel(buf, getFont(), scl, color); BzUINode *iconNode = bzUINodeMake(UI, id, &(BzUINodeDesc) { .flags = BZ_UI_DRAW_SPRITE | BZ_UI_ALIGN_CENTER, .semanticSize[BZ_UI_AXIS_X] = { BZ_UI_SIZE_PIXELS, 80 * scl * uiGetScale(), }, .semanticSize[BZ_UI_AXIS_Y] = { BZ_UI_SIZE_PIXELS, 80 * scl * uiGetScale(), }, .margin = {0, 0, 20 * uiGetScale(), 0} }); bzUISetSpriteStyle(UI, iconNode, (BzUISpriteStyle) { .rec = icon, .texture = texture, .tintNormal = WHITE, .tintHover = WHITE, .tintActive = WHITE, }); bzUIPopParent(UI); } void uiGameRecruit(const char *label, Rectangle rec, Texture2D tex, i32 numRecruiting, f32 progress, bool canAfford, bool *selected, bool *hovered) { f32 scl = uiGetScale(); BzUINode *btn = bzUINodeMake(UI, bzUIKeyFromString(label), &(BzUINodeDesc) { .flags = BZ_UI_CLICKABLE | BZ_UI_ALIGN_CENTER | BZ_UI_DRAW_BORDER, .margin[BZ_UI_AXIS_X] = 10.0f * scl, .margin[BZ_UI_AXIS_Y] = 10.0f * scl, .margin[BZ_UI_AXIS_Y * 2] = 10.0f * scl, .semanticSize[BZ_UI_AXIS_X] = {BZ_UI_SIZE_CHILD_MAX}, .semanticSize[BZ_UI_AXIS_Y] = {BZ_UI_SIZE_CHILD_SUM}, }); if (rec.height > 16) rec.y += 16; rec.height = rec.width; BzUIInteraction inter = bzUIGetInteraction(UI, btn); Color bgColor = canAfford ? DARKBROWN : MAROON; if (*selected || inter.hovering) bgColor = canAfford ? BROWN : RED; if (inter.clicked && canAfford) *selected = true; if (hovered) *hovered = inter.hovering; bzUISetBackgroundStyle(UI, btn, (BzUIBackgroundStyle) { .roundness = 0.2f, .active = bgColor, .normal = bgColor, .hover = bgColor, }); bzUISetBorderStyle(UI, btn, (BzUIBorderStyle) { .roundness = 0.2f, .thickness = 5.0f * scl, .normal = BLACK, .hover = BLACK, .active = BLACK, }); bzUIPushParent(UI, btn); bzUISetParentLayout(UI, (BzUILayout) { .type = BZ_UI_LAYOUT_FLEX_BOX, .flags = BZ_UI_FLEX_DIR_COLUMN | BZ_UI_FLEX_ALIGN_CENTER | BZ_UI_FLEX_JUSTIFY_START }); BzUINode *tileSprite = bzUINodeMake(UI, bzUIGetUniqueKey(UI), &(BzUINodeDesc) { .flags = BZ_UI_DRAW_SPRITE, .semanticSize[BZ_UI_AXIS_X] = {BZ_UI_SIZE_PIXELS, rec.width * 5 * scl}, .semanticSize[BZ_UI_AXIS_Y] = {BZ_UI_SIZE_PIXELS, rec.height * 5 * scl}, }); bzUISetSpriteStyle(UI, tileSprite, (BzUISpriteStyle) { .texture = tex, .rec = rec, .tintNormal = WHITE, .tintHover = WHITE, .tintActive = WHITE, }); char text[128]; if (numRecruiting > 0) { snprintf(text, sizeof(text), "%dx %s", numRecruiting, label); } else { snprintf(text, sizeof(text), "%s", label); } BzUINode *labelDisplay = bzUINodeMake(UI, bzUIGetUniqueKey(UI), &(BzUINodeDesc) { .flags = BZ_UI_DRAW_TEXT | BZ_UI_DRAW_TEXT_SHADOW, .semanticSize[BZ_UI_AXIS_X] = {BZ_UI_SIZE_FIT}, .semanticSize[BZ_UI_AXIS_Y] = {BZ_UI_SIZE_FIT}, }); bzUISetTextStyle(UI, labelDisplay, (BzUITextStyle) { .font = getFont(), .fontSize = 25.0f * scl, .fontSpacing = 1.0f * scl, .normal = WHITE, .hover = WHITE, .active = WHITE, .text = text }); bzUISetTextShadowStyle(UI, labelDisplay, (BzUITextShadowStyle) { .offset = {1.0f * scl, 1.0f * scl}, .normal = BLACK, .hover = BLACK, .active = BLACK }); if (progress > 0.0f) { BzUINode *progressBar = bzUINodeMake(UI, bzUIGetUniqueKey(UI), &(BzUINodeDesc) { .flags = BZ_UI_DRAW_BACKGROUND | BZ_UI_ALIGN_HORIZ_START, .semanticSize[BZ_UI_AXIS_X] = {BZ_UI_SIZE_REL_PARENT, progress * 0.8f}, .semanticSize[BZ_UI_AXIS_Y] = {BZ_UI_SIZE_PIXELS, scl * 4} }); bzUISetBackgroundStyle(UI, progressBar, (BzUIBackgroundStyle) { .active = GREEN, .hover = GREEN, .normal = GREEN, }); } bzUIPopParent(UI); } void uiGameBuild(const char *label, Rectangle rec, Texture2D tex, bool canAfford, bool *selected, bool *hovered) { f32 scl = uiGetScale(); BzUINode *btn = bzUINodeMake(UI, bzUIKeyFromString(label), &(BzUINodeDesc) { .flags = BZ_UI_CLICKABLE | BZ_UI_ALIGN_CENTER | BZ_UI_DRAW_BORDER, .margin[BZ_UI_AXIS_X] = 10.0f * scl, .margin[BZ_UI_AXIS_Y] = 10.0f * scl, .margin[BZ_UI_AXIS_Y * 2] = 10.0f * scl, .semanticSize[BZ_UI_AXIS_X] = {BZ_UI_SIZE_CHILD_MAX}, .semanticSize[BZ_UI_AXIS_Y] = {BZ_UI_SIZE_CHILD_SUM}, }); if (rec.height > 16) rec.y += 16; rec.height = rec.width; BzUIInteraction inter = bzUIGetInteraction(UI, btn); Color bgColor = canAfford ? DARKBROWN : MAROON; if (*selected || inter.hovering) bgColor = canAfford ? BROWN : RED; if (inter.clicked && canAfford) *selected = true; if (hovered) *hovered = inter.hovering; bzUISetBackgroundStyle(UI, btn, (BzUIBackgroundStyle) { .roundness = 0.2f, .active = bgColor, .normal = bgColor, .hover = bgColor, }); bzUISetBorderStyle(UI, btn, (BzUIBorderStyle) { .roundness = 0.2f, .thickness = 5.0f * scl, .normal = BLACK, .hover = BLACK, .active = BLACK, }); bzUIPushParent(UI, btn); bzUISetParentLayout(UI, (BzUILayout) { .type = BZ_UI_LAYOUT_FLEX_BOX, .flags = BZ_UI_FLEX_DIR_COLUMN | BZ_UI_FLEX_ALIGN_CENTER | BZ_UI_FLEX_JUSTIFY_START }); BzUINode *tileSprite = bzUINodeMake(UI, bzUIGetUniqueKey(UI), &(BzUINodeDesc) { .flags = BZ_UI_DRAW_SPRITE, .semanticSize[BZ_UI_AXIS_X] = {BZ_UI_SIZE_PIXELS, rec.width * 5 * scl}, .semanticSize[BZ_UI_AXIS_Y] = {BZ_UI_SIZE_PIXELS, rec.height * 5 * scl}, }); bzUISetSpriteStyle(UI, tileSprite, (BzUISpriteStyle) { .texture = tex, .rec = rec, .tintNormal = WHITE, .tintHover = WHITE, .tintActive = WHITE, }); BzUINode *labelDisplay = bzUINodeMake(UI, bzUIGetUniqueKey(UI), &(BzUINodeDesc) { .flags = BZ_UI_DRAW_TEXT | BZ_UI_DRAW_TEXT_SHADOW, .semanticSize[BZ_UI_AXIS_X] = {BZ_UI_SIZE_FIT}, .semanticSize[BZ_UI_AXIS_Y] = {BZ_UI_SIZE_FIT}, }); bzUISetTextStyle(UI, labelDisplay, (BzUITextStyle) { .font = getFont(), .fontSize = 25.0f * scl, .fontSpacing = 1.0f * scl, .normal = WHITE, .hover = WHITE, .active = WHITE, .text = label }); bzUISetTextShadowStyle(UI, labelDisplay, (BzUITextShadowStyle) { .offset = {1.0f * scl, 1.0f * scl}, .normal = BLACK, .hover = BLACK, .active = BLACK }); bzUIPopParent(UI); } bool uiGameTrade(const char *label, Rectangle rec, Texture2D tex, bool canAfford) { bool selected = false; uiGameBuild(label, rec, tex, canAfford, &selected, NULL); return selected; } bool uiGameUnit(const char *label, i32 count, Rectangle rec, Texture2D tex) { if (rec.height > 16) rec.y += 16; rec.height = rec.width; f32 scl = uiGetScale(); BzUINode *btn = bzUINodeMake(UI, bzUIKeyFromString(label), &(BzUINodeDesc) { .flags = BZ_UI_CLICKABLE | BZ_UI_ALIGN_CENTER | BZ_UI_DRAW_BORDER, .margin[BZ_UI_AXIS_X] = 10.0f * scl, .margin[BZ_UI_AXIS_Y] = 10.0f * scl, .margin[BZ_UI_AXIS_Y * 2] = 10.0f * scl, .semanticSize[BZ_UI_AXIS_X] = {BZ_UI_SIZE_CHILD_MAX}, .semanticSize[BZ_UI_AXIS_Y] = {BZ_UI_SIZE_CHILD_SUM}, }); BzUIInteraction inter = bzUIGetInteraction(UI, btn); Color bgColor = DARKBROWN; if (inter.hovering) bgColor = BROWN; bzUISetBackgroundStyle(UI, btn, (BzUIBackgroundStyle) { .roundness = 0.2f, .active = bgColor, .normal = bgColor, .hover = bgColor, }); bzUISetBorderStyle(UI, btn, (BzUIBorderStyle) { .roundness = 0.2f, .thickness = 5.0f * scl, .normal = BLACK, .hover = BLACK, .active = BLACK, }); bzUIPushParent(UI, btn); bzUISetParentLayout(UI, (BzUILayout) { .type = BZ_UI_LAYOUT_FLEX_BOX, .flags = BZ_UI_FLEX_DIR_COLUMN | BZ_UI_FLEX_ALIGN_CENTER | BZ_UI_FLEX_JUSTIFY_START }); BzUINode *tileSprite = bzUINodeMake(UI, bzUIGetUniqueKey(UI), &(BzUINodeDesc) { .flags = BZ_UI_DRAW_SPRITE, .semanticSize[BZ_UI_AXIS_X] = {BZ_UI_SIZE_PIXELS, rec.width * 5 * scl}, .semanticSize[BZ_UI_AXIS_Y] = {BZ_UI_SIZE_PIXELS, rec.height * 5 * scl}, }); bzUISetSpriteStyle(UI, tileSprite, (BzUISpriteStyle) { .texture = tex, .rec = rec, .tintNormal = WHITE, .tintHover = WHITE, .tintActive = WHITE, }); BzUINode *labelDisplay = bzUINodeMake(UI, bzUIGetUniqueKey(UI), &(BzUINodeDesc) { .flags = BZ_UI_DRAW_TEXT | BZ_UI_DRAW_TEXT_SHADOW, .semanticSize[BZ_UI_AXIS_X] = {BZ_UI_SIZE_FIT}, .semanticSize[BZ_UI_AXIS_Y] = {BZ_UI_SIZE_FIT}, }); char buf[128]; snprintf(buf, sizeof(buf), "%d", count); bzUISetTextStyle(UI, labelDisplay, (BzUITextStyle) { .font = getFont(), .fontSize = 25.0f * scl, .fontSpacing = 1.0f * scl, .normal = WHITE, .hover = WHITE, .active = WHITE, .text = buf }); bzUISetTextShadowStyle(UI, labelDisplay, (BzUITextShadowStyle) { .offset = {1.0f * scl, 1.0f * scl}, .normal = BLACK, .hover = BLACK, .active = BLACK }); bzUIPopParent(UI); return inter.clicked; } void uiGameTooltipCost(Vector2 pos, i32 costs[RES_COUNT], Texture2D tex) { char text[RES_COUNT][32]; for (i32 i = 0; i < RES_COUNT; i++) { snprintf(text[i], sizeof(text[i]), "%d", BZ_ABS(costs[i])); } Vector2 offset = pos; const f32 fontSize = 32 * uiGetScale(); const f32 margin = 10 * uiGetScale(); offset.x += margin * 0.5f; offset.y -= margin; for (i32 i = 0; i < RES_COUNT; i++) { EntityType entity = ENTITY_NONE; switch (i) { case RES_FOOD: entity = ENTITY_APPLE; break; case RES_WOOD: entity = ENTITY_WOOD; break; case RES_GOLD: entity = ENTITY_GOLD; break; } if (entity == ENTITY_NONE) continue; if (costs[i] == 0) continue; Vector2 size = MeasureTextEx(getFont(), text[i], fontSize, 1.0f); Rectangle rec = getTextureRect(getEntityTile(entity)); DrawText(text[i], offset.x, offset.y, fontSize, WHITE); const f32 texSize = size.y; offset.x += size.x + margin * 0.8f; DrawTexturePro(tex, rec, (Rectangle) { offset.x, offset.y, texSize, texSize }, (Vector2) { 0, 0 }, 0.0f, WHITE); offset.x += texSize + margin; } }