#include "defaults/ui.hpp" #include "inventory/inventory.hpp" #include "game/game.hpp" #include "character/character.hpp" #include // Key description pairs std::vector> 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> 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(game->win_size.w), static_cast(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>& layout = (dynamic_cast(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(surface_key->w)); max_desc_width = std::max(max_desc_width, static_cast(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(surface_key->h); // Right-align the key SDL_FRect dst_key = {x_key + max_key_width - static_cast(surface_key->w), y, static_cast(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(surface_desc->w), static_cast(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(win_size.w) - static_cast(surf->w) - padding; SDL_FRect rect = { x, 6.0f, static_cast(surf->w), static_cast(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(win_size.w), static_cast(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(surface_text->w), static_cast(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(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(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(data->icon_tile_x * 16), static_cast(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); } } }