Nimbin[12]?SDL & Graphics / mapcreator / src/defaults/ui.cpp

mapcreator git · master

SDL3 2.5D game and engine using assets, with map editor

sdl3 c++ game engine map-editor cmake · first commit 2025-10-31 · last commit 2026-09-07 (1 month ago) · synced 3 days ago · upstream: git.ide3.de/hsnr/sdl-spieleentwicklung/map_creator

C++ 99.3%
git clone https://git.christianimmanuel.de/sdl-graphics/mapcreator.gitwget https://git.christianimmanuel.de/sdl-graphics/mapcreator/archive/mapcreator.tar.gz
src/defaults/ui.cpp 17.9 KB · 526 lines raw
#include "defaults/ui.hpp"

#include "inventory/inventory.hpp"

#include "game/game.hpp"
#include "character/character.hpp"

#include <iostream>

// Key description pairs
std::vector<std::pair<std::string, std::string>>
keyboard_layout = {
    {               "9", "Game mode: play"},
    {          "Scroll", "Zoom in or out"},
    {          "ctrl+s", "Map save to file"},
    {               "s", "Map scale"},
    {               "r", "Map clear"},
    {         "shift+k", "Map clear colliders"},
    {  "ctrl+LeftClick", "Map move"},
    {               "i", "Info toggle"},
    {               "h", "Tileset toggle hide"},
    {   "[ctrl,shift]m", "Tileset toggle"},
    {               "k", "Toggle set collission"},
    {               "o", "Toggle set object or floor"},
    {          "ctrl+o", "Set properties object"},
    {         "shift+o", "Set properites resource"},
    {          "ctrl+k", "Collider transfer to map"},
    {     "c+LeftClick", "Object clear"},
    { "shift+LeftClick", "Object select"},
    {   "[ctrl,shift]+", "Selector increase"},
    {  "[ctrl,shift]- ", "Selector decrease"},
    {          "ctrl+q", "Exit game"}
};

std::vector<std::pair<std::string, std::string>>
keyboard_layout_playing = {
    {             "f10", "Game mode: modify"},
    {          "Scroll", "Zoom in or out"},
    {               "0", "Zoom reset"},
    {       "[w,a,s,d]", "Move in direction"},
    {               "c", "Collect item | interact | plant"},
    {               "i", "Info toggle"},
    {               "e", "Inventory toggle"},
    {               "b", "Build menu toggle"},
    {               "k", "Building set deconstruct"},
    {           "[1-6]", "Equipement set or select"},
    {      "Left-Click", "Attack | Build | Deconstruct"},
    {          "ctrl+q", "Exit game"}
};

namespace Ui {
    bool draw_key_info = false;

    // Font
    TTF_Font* font_big   = nullptr;
    TTF_Font* font_small = nullptr;
    TTF_Font* font_tiny  = nullptr;
    TTF_Font* font_menu  = nullptr;
    TTF_Font* font_top_bar   = nullptr;
    TTF_Font* font_menu_bold = nullptr;
    TTF_Font* font_message   = nullptr;

    float top_bar_height = 40;

    bool
    init_Font() {
        if (!TTF_Init()) {
            std::cerr << "SDL_ttf initialization failed: " << SDL_GetError() << std::endl;
            return false;
        }

        font_big   = TTF_OpenFont("fonts/DejaVuSans-Bold.ttf", 28);
        font_small = TTF_OpenFont("fonts/DejaVuSansMono.ttf", 30);
        font_tiny  = TTF_OpenFont("fonts/DejaVuSansMono.ttf", 12);
        font_menu  = TTF_OpenFont("fonts/DejaVuSansMono.ttf", 18);
        font_menu_bold = TTF_OpenFont("fonts/DejaVuSansMono-Bold.ttf", 18);
        font_top_bar   = TTF_OpenFont("fonts/DejaVuSansMono-Bold.ttf", 28);
        font_message   = TTF_OpenFont("fonts/DejaVuSansMono.ttf", 22);

        if (!font_big || !font_small) {
            std::cerr << "Failed to load font: " << SDL_GetError() << std::endl;
            return false;
        }
        return true;
    }

    void
    delete_Font() {
        TTF_CloseFont(font_big);
        TTF_CloseFont(font_small);
        TTF_CloseFont(font_top_bar);
    }

    void
    draw_KeyInfo(Game* game) {
        if (!game->renderer || !font_small || !font_big) return;

        SDL_FRect rect_background_window = {0, 0, static_cast<float>(game->win_size.w), static_cast<float>(game->win_size.h)};
        SDL_SetRenderDrawBlendMode(game->renderer, SDL_BLENDMODE_BLEND);
        SDL_SetRenderDrawColor(game->renderer, 0, 0, 0, 200);
        SDL_RenderFillRect(game->renderer, &rect_background_window);



        SDL_Color text_color = {255, 255, 255, 255};
        float line_spacing = 8.0f;

        float total_height = 0.0f;
        float max_key_width = 0.0f;
        float max_desc_width = 0.0f;

        std::vector<std::pair<std::string, std::string>>& layout =
                  (dynamic_cast<StatePlaying*>(game->state_current) != nullptr)
                ? keyboard_layout_playing
                : keyboard_layout;

        for (const auto& [key, desc] : layout) {
            SDL_Surface* surface_key  = TTF_RenderText_Blended(font_big, key.c_str(), key.length(), text_color);
            SDL_Surface* surface_desc = TTF_RenderText_Blended(font_small, desc.c_str(), desc.length(), text_color);
            if (!surface_key || !surface_desc) {
                if (surface_key)  SDL_DestroySurface(surface_key);
                if (surface_desc) SDL_DestroySurface(surface_desc);
                continue;
            }

            max_key_width = std::max(max_key_width, static_cast<float>(surface_key->w));
            max_desc_width = std::max(max_desc_width, static_cast<float>(surface_desc->w));
            total_height += std::max(surface_key->h, surface_desc->h) + line_spacing;

            SDL_DestroySurface(surface_key);
            SDL_DestroySurface(surface_desc);
        }

        total_height -= line_spacing;

        // Center coordinates
        float col_spacing = 50.0f;
        float x_key = (game->win_size.w - (max_key_width + max_desc_width + col_spacing)) / 2.0f;
        float x_desc = x_key + max_key_width + col_spacing;
        float y = (game->win_size.h - total_height) / 2.0f;

        // background
        SDL_FPoint padding = {120.0f, 30.0f};
        SDL_FRect rect_background = {x_key - padding.x, y - padding.y,
            (max_key_width + max_desc_width + 20.0f) + 2*padding.x,
            total_height + 2*padding.y};
        SDL_SetRenderDrawBlendMode(game->renderer, SDL_BLENDMODE_BLEND);
        SDL_SetRenderDrawColor(game->renderer, 0, 0, 0, 182);
        SDL_RenderFillRect(game->renderer, &rect_background);

        // Text
        for (const auto& [key, desc] : layout) {
            SDL_Surface* surface_key = TTF_RenderText_Blended(font_big, key.c_str(), key.length(), text_color);
            if (!surface_key) continue;
            SDL_Texture* texture_key = SDL_CreateTextureFromSurface(game->renderer, surface_key);
            if (!texture_key) {
                SDL_DestroySurface(surface_key);
                continue;
            }
            float h = static_cast<float>(surface_key->h);

            // Right-align the key
            SDL_FRect dst_key = {x_key + max_key_width - static_cast<float>(surface_key->w), y,
                static_cast<float>(surface_key->w), h};
            SDL_RenderTexture(game->renderer, texture_key, nullptr, &dst_key);
            SDL_DestroySurface(surface_key);
            SDL_DestroyTexture(texture_key);

            SDL_Surface* surface_desc = TTF_RenderText_Blended(font_small, desc.c_str(), desc.length(), text_color);
            if (!surface_desc) continue;
            SDL_Texture* texture_desc = SDL_CreateTextureFromSurface(game->renderer, surface_desc);
            if (!texture_desc) {
                SDL_DestroySurface(surface_desc);
                continue;
            }

            SDL_FRect dst_desc = {x_desc, y, static_cast<float>(surface_desc->w), static_cast<float>(surface_desc->h)};
            SDL_RenderTexture(game->renderer, texture_desc, nullptr, &dst_desc);
            SDL_DestroySurface(surface_desc);
            SDL_DestroyTexture(texture_desc);

            y += h + line_spacing;
        }
    }


    void draw_Frames(SDL_Renderer* renderer, SDL_Rect win_size) {
        SDL_Color text_color = {255, 255, 255, 255};

        // --- RIGHT SIDE: FRAMES ---

        std::string res_text = "FPS: " + std::to_string(Default::fps);

        SDL_Surface* surf = TTF_RenderText_Blended(font_top_bar, res_text.c_str(), res_text.length(), text_color);
        if (!surf) return;

        SDL_Texture* tex = SDL_CreateTextureFromSurface(renderer, surf);
        if (!tex) {
            SDL_DestroySurface(surf);
            return;
        }

        float padding = 20.0f;
        float x = static_cast<float>(win_size.w) - static_cast<float>(surf->w) - padding;

        SDL_FRect rect = {
            x,
            6.0f,
            static_cast<float>(surf->w),
            static_cast<float>(surf->h)
        };

        SDL_RenderTexture(renderer, tex, nullptr, &rect);

        SDL_DestroyTexture(tex);
        SDL_DestroySurface(surf);
    }



    void draw_TopBarTileInfo(SDL_Renderer* renderer, SDL_Rect win_size) {
        SDL_Color text_color = {255, 255, 255, 255};

        std::string text;
        text = std::string("Mode: ") + Default::get_ModifyStateName();

        // background
        SDL_FRect top_bar_rect = {
            0.0f,
            0.0f,
            static_cast<float>(win_size.w),
            static_cast<float>(top_bar_height)
        };

        SDL_SetRenderDrawColor(renderer, 0, 0, 0, 120);
        SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
        SDL_RenderFillRect(renderer, &top_bar_rect);

        // --- LEFT TEXT ---

        SDL_Surface* surface_text = TTF_RenderText_Blended(font_top_bar, text.c_str(), text.length(), text_color);
        if (!surface_text) return;

        SDL_Texture* texture_text = SDL_CreateTextureFromSurface(renderer, surface_text);
        if (!texture_text) {
            SDL_DestroySurface(surface_text);
            return;
        }

        SDL_FRect dst_text = {20.0f, 6.0f, static_cast<float>(surface_text->w), static_cast<float>(surface_text->h)};
        SDL_RenderTexture(renderer, texture_text, nullptr, &dst_text);

        SDL_DestroyTexture(texture_text);
        SDL_DestroySurface(surface_text);
    }

    void
    draw_BottomLeftBars(SDL_Renderer* renderer, SDL_Rect win_size, Character& player) {
        const float health_w = 20.0f;
        const float energy_w = health_w * 0.5f;
        const float thirst_w = health_w * 0.4f;
        const float bar_h = 120.0f;
        const float spacing = 4.0f;

        float start_x = 20.0f;
        float start_y = static_cast<float>(win_size.h) - bar_h - 20.0f;

        auto clamp01 = [](float v) {
            return v < 0.0f ? 0.0f : (v > 1.0f ? 1.0f : v);
        };

        float thirst_max = player.stats.thirst_max;
        if (thirst_max <= 0.0f) thirst_max = 1.0f;

        float thirst_ratio = clamp01(player.stats.thirst / thirst_max);

        // ---------------- HEALTH ----------------

        float health_max   = player.health_Max();
        float health_bonus = player.health_MaxBonus();
        float health_base_max = health_max - health_bonus;
        if (health_base_max < 0.0f) health_base_max = 0.0f;

        float health_capacity_ratio = clamp01(health_max / thirst_max);
        float health_bar_max_h = bar_h * health_capacity_ratio;

        float health_base_capacity_ratio =
            health_max > 0.0f ? clamp01(health_base_max / health_max) : 0.0f;

        float health_base_cap_h  = health_bar_max_h * health_base_capacity_ratio;
        float health_bonus_cap_h = health_bar_max_h - health_base_cap_h;

        float health_val = player.health_Get();

        float health_base_ratio =
            health_base_max > 0.0f ? clamp01(health_val / health_base_max) : 0.0f;

        float health_bonus_ratio =
            health_bonus > 0.0f
                ? clamp01((health_val - health_base_max) / health_bonus)
                : 0.0f;

        float health_bottom_y = start_y + (bar_h - health_bar_max_h);

        // ---------------- STAMINA ----------------

        float stamina_max   = player.stamina_Max();
        float stamina_bonus = player.stamina_MaxBonus();
        float stamina_base_max = stamina_max - stamina_bonus;
        if (stamina_base_max < 0.0f) stamina_base_max = 0.0f;

        float stamina_capacity_ratio = clamp01(stamina_max / thirst_max);
        float stamina_bar_max_h = bar_h * stamina_capacity_ratio;

        float stamina_base_capacity_ratio =
            stamina_max > 0.0f ? clamp01(stamina_base_max / stamina_max) : 0.0f;

        float stamina_base_cap_h  = stamina_bar_max_h * stamina_base_capacity_ratio;
        float stamina_bonus_cap_h = stamina_bar_max_h - stamina_base_cap_h;

        float stamina_val = player.stamina_Get();

        float stamina_base_ratio =
            stamina_base_max > 0.0f ? clamp01(stamina_val / stamina_base_max) : 0.0f;

        float stamina_bonus_ratio =
            stamina_bonus > 0.0f
                ? clamp01((stamina_val - stamina_base_max) / stamina_bonus)
                : 0.0f;

        float stamina_bottom_y = start_y + (bar_h - stamina_bar_max_h);

        // ---------------- RECTS ----------------

        SDL_FRect thirst_bg  = { start_x, start_y, thirst_w, bar_h };
        SDL_FRect energy_bg  = {
            thirst_bg.x + thirst_w + spacing,
            stamina_bottom_y,
            energy_w,
            stamina_bar_max_h
        };
        SDL_FRect health_bg  = {
            energy_bg.x + energy_w + spacing,
            health_bottom_y,
            health_w,
            health_bar_max_h
        };

        // ---------------- BACKGROUND ----------------

        // thirst background
        SDL_SetRenderDrawColor(renderer, 30, 30, 30, 255);
        SDL_RenderFillRect(renderer, &thirst_bg);

        // stamina background base
        SDL_FRect stamina_bg_base = {
            energy_bg.x,
            stamina_bottom_y + stamina_bonus_cap_h,
            energy_w,
            stamina_base_cap_h
        };
        SDL_SetRenderDrawColor(renderer, 30, 30, 30, 255);
        SDL_RenderFillRect(renderer, &stamina_bg_base);

        // stamina background bonus (slightly lighter)
        SDL_FRect stamina_bg_bonus = {
            energy_bg.x,
            stamina_bottom_y,
            energy_w,
            stamina_bonus_cap_h
        };
        SDL_SetRenderDrawColor(renderer, 45, 45, 45, 255);
        SDL_RenderFillRect(renderer, &stamina_bg_bonus);

        // health background base
        SDL_FRect health_bg_base = {
            health_bg.x,
            health_bottom_y + health_bonus_cap_h,
            health_w,
            health_base_cap_h
        };
        SDL_SetRenderDrawColor(renderer, 30, 30, 30, 255);
        SDL_RenderFillRect(renderer, &health_bg_base);

        // health background bonus (slightly lighter)
        SDL_FRect health_bg_bonus = {
            health_bg.x,
            health_bottom_y,
            health_w,
            health_bonus_cap_h
        };
        SDL_SetRenderDrawColor(renderer, 45, 45, 45, 255);
        SDL_RenderFillRect(renderer, &health_bg_bonus);

        // ---------------- FOREGROUND ----------------

        // health base fill
        SDL_FRect health_fill_base = {
            health_bg.x,
            health_bottom_y + health_bonus_cap_h
                + health_base_cap_h * (1.0f - health_base_ratio),
            health_w,
            health_base_cap_h * health_base_ratio
        };
        SDL_SetRenderDrawColor(renderer, 200, 40, 40, 255);
        SDL_RenderFillRect(renderer, &health_fill_base);

        // health bonus fill (lighter red)
        SDL_FRect health_fill_bonus = {
            health_bg.x,
            health_bottom_y
                + health_bonus_cap_h * (1.0f - health_bonus_ratio),
            health_w,
            health_bonus_cap_h * health_bonus_ratio
        };
        SDL_SetRenderDrawColor(renderer, 255, 100, 100, 255);
        SDL_RenderFillRect(renderer, &health_fill_bonus);

        // stamina base fill
        SDL_FRect stamina_fill_base = {
            energy_bg.x,
            stamina_bottom_y + stamina_bonus_cap_h
                + stamina_base_cap_h * (1.0f - stamina_base_ratio),
            energy_w,
            stamina_base_cap_h * stamina_base_ratio
        };
        SDL_SetRenderDrawColor(renderer, 200, 200, 40, 255);
        SDL_RenderFillRect(renderer, &stamina_fill_base);

        // stamina bonus fill (lighter yellow)
        SDL_FRect stamina_fill_bonus = {
            energy_bg.x,
            stamina_bottom_y
                + stamina_bonus_cap_h * (1.0f - stamina_bonus_ratio),
            energy_w,
            stamina_bonus_cap_h * stamina_bonus_ratio
        };
        SDL_SetRenderDrawColor(renderer, 255, 255, 120, 255);
        SDL_RenderFillRect(renderer, &stamina_fill_bonus);

        // thirst fill
        SDL_FRect thirst_fill = {
            thirst_bg.x,
            start_y + bar_h * (1.0f - thirst_ratio),
            thirst_w,
            bar_h * thirst_ratio
        };
        SDL_SetRenderDrawColor(renderer, 40, 120, 200, 255);
        SDL_RenderFillRect(renderer, &thirst_fill);

        // ---------------- BORDERS ----------------

        SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
        SDL_RenderRect(renderer, &thirst_bg);
        SDL_RenderRect(renderer, &energy_bg);
        SDL_RenderRect(renderer, &health_bg);












        const float icon_size = 32.0f;
        const float icon_spacing = 6.0f;
        const float icons_y = start_y - icon_size - 8.0f;

        const float max_duration = 300.0f;

        for (size_t n = 0; n < player.stats_bonus.size(); ++n) {

            const CharacterStatsBonus& bonus = player.stats_bonus[n];

            const VegetableData::CropDefinition* data =
                VegetableData::get_DataByType(
                        static_cast<VegetableData::Type>(bonus.type)
                        );

            if (!data)
                continue;

            SDL_FRect dst = {
                thirst_bg.x + n * (icon_size + icon_spacing),
                icons_y,
                icon_size,
                icon_size
            };

            SDL_FRect src = {
                static_cast<float>(data->icon_tile_x * 16),
                static_cast<float>(data->icon_tile_y * 16),
                16.0f,
                16.0f
            };

            SDL_RenderTexture(renderer, Icon::icons_texture, &src, &dst);

            // -------- duration overlay --------

            float ratio = bonus.duration / max_duration;
            if (ratio < 0.0f) ratio = 0.0f;
            if (ratio > 1.0f) ratio = 1.0f;

            float overlay_h = dst.h * ratio;

            SDL_FRect overlay = {
                dst.x,
                dst.y + (dst.h - overlay_h),
                dst.w,
                overlay_h
            };

            SDL_SetRenderDrawColor(renderer, 0, 0, 0, 140);
            SDL_RenderFillRect(renderer, &overlay);

            // border
            SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
            SDL_RenderRect(renderer, &dst);
        }
    }
}