#include "ui/Application.hpp" #include #include #include #include #include #include #include "core/Constants.hpp" #include "editor/Tool.hpp" #include "ui/Theme.hpp" namespace tahgibu { namespace { constexpr int kInitialWidth = 1280; constexpr int kInitialHeight = 800; constexpr int kMinWidth = 1000; constexpr int kMinHeight = 640; constexpr int kUiTargetFps = 60; // FPS de la interfaz; no tiene relación con los FPS de la animación constexpr int kFontSize = 16; constexpr int kSmallFontSize = 14; constexpr int kButtonPadding = 10; constexpr float kButtonGap = 4.0f; constexpr float kPanelHeaderHeight = 24.0f; constexpr std::array kMenus = { "Archivo", "Editar", "Animación", "Sprite", "Audio", "Ver", "Lua"}; int px(float value) { return static_cast(value); } void drawTextVCentered(const char* text, float x, const Rectangle& area, int size, Color color) { DrawText(text, px(x), px(area.y + (area.height - static_cast(size)) / 2.0f), size, color); } void drawPanelHeader(const Rectangle& panel, const char* title) { DrawRectangle(px(panel.x), px(panel.y), px(panel.width), px(kPanelHeaderHeight), theme::kPanelHeader); DrawText(title, px(panel.x + 12.0f), px(panel.y + 4.0f), kFontSize, theme::kText); } std::array computeToolButtons(const Rectangle& bar) { std::array rects{}; float x = bar.x + 8.0f; for (std::size_t i = 0; i < kToolCount; ++i) { const float w = static_cast(MeasureText(kToolInfos[i].label, kFontSize) + 2 * kButtonPadding); rects[i] = {x, bar.y + 6.0f, w, bar.height - 12.0f}; x += w + kButtonGap; } return rects; } std::array computeMenuRects(const Rectangle& bar) { std::array rects{}; float x = bar.x + 6.0f; for (std::size_t i = 0; i < kMenus.size(); ++i) { const float w = static_cast(MeasureText(kMenus[i], kFontSize) + 2 * kButtonPadding); rects[i] = {x, bar.y + 2.0f, w, bar.height - 4.0f}; x += w + 2.0f; } return rects; } } // namespace Application::Application() { SetConfigFlags(FLAG_WINDOW_RESIZABLE); const std::string title = std::string(kAppName) + " " + kAppVersion; InitWindow(kInitialWidth, kInitialHeight, title.c_str()); if (!IsWindowReady()) { return; } SetWindowMinSize(kMinWidth, kMinHeight); SetExitKey(KEY_NULL); // Esc no debe cerrar el editor SetTargetFPS(kUiTargetFps); layout_ = Layout::compute(GetScreenWidth(), GetScreenHeight()); } Application::~Application() { if (IsWindowReady()) { CloseWindow(); } } int Application::run() { if (!IsWindowReady()) { std::fprintf(stderr, "No se pudo crear la ventana (¿hay entorno gráfico y OpenGL disponible?).\n"); return 1; } while (!WindowShouldClose()) { update(); draw(); } return 0; } void Application::update() { layout_ = Layout::compute(GetScreenWidth(), GetScreenHeight()); if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) { const Vector2 mouse = GetMousePosition(); const auto rects = computeToolButtons(layout_.toolBar); for (std::size_t i = 0; i < kToolCount; ++i) { if (CheckCollisionPointRec(mouse, rects[i])) { state_.activeTool = kToolInfos[i].id; break; } } } } void Application::draw() const { BeginDrawing(); ClearBackground(theme::kWindowBg); drawCanvas(); drawProperties(); drawTimeline(); drawToolBar(); drawMenuBar(); EndDrawing(); } void Application::drawMenuBar() const { const Rectangle& bar = layout_.menuBar; DrawRectangleRec(bar, theme::kPanelHeader); const Vector2 mouse = GetMousePosition(); const auto rects = computeMenuRects(bar); for (std::size_t i = 0; i < kMenus.size(); ++i) { if (CheckCollisionPointRec(mouse, rects[i])) { DrawRectangleRec(rects[i], theme::kHover); } drawTextVCentered(kMenus[i], rects[i].x + static_cast(kButtonPadding), rects[i], kFontSize, theme::kText); } } void Application::drawToolBar() const { const Rectangle& bar = layout_.toolBar; DrawRectangleRec(bar, theme::kPanelBg); DrawRectangle(px(bar.x), px(bar.y + bar.height - 1.0f), px(bar.width), 1, theme::kBorder); const Vector2 mouse = GetMousePosition(); const auto rects = computeToolButtons(bar); for (std::size_t i = 0; i < kToolCount; ++i) { const bool active = state_.activeTool == kToolInfos[i].id; const bool hover = CheckCollisionPointRec(mouse, rects[i]); const Color background = active ? theme::kAccent : (hover ? theme::kHover : theme::kPanelHeader); DrawRectangleRec(rects[i], background); drawTextVCentered(kToolInfos[i].label, rects[i].x + static_cast(kButtonPadding), rects[i], kFontSize, active ? theme::kWindowBg : theme::kText); } } void Application::drawCanvas() const { const Rectangle& area = layout_.canvas; DrawRectangleRec(area, theme::kCanvasBg); // Escenario del tamaño del proyecto, ajustado al área disponible. constexpr float kMargin = 32.0f; const float projectW = static_cast(project_.width()); const float projectH = static_cast(project_.height()); const float availableW = std::max(1.0f, area.width - 2.0f * kMargin); const float availableH = std::max(1.0f, area.height - 2.0f * kMargin); const float scale = std::min(availableW / projectW, availableH / projectH); const float w = projectW * scale; const float h = projectH * scale; const Rectangle stage{area.x + (area.width - w) / 2.0f, area.y + (area.height - h) / 2.0f, w, h}; DrawRectangleRec(stage, theme::kStage); DrawRectangleLinesEx(stage, 1.0f, theme::kBorder); char caption[96]; std::snprintf(caption, sizeof caption, "Escenario %d x %d", project_.width(), project_.height()); DrawText(caption, px(area.x + 10.0f), px(area.y + 8.0f), kSmallFontSize, theme::kTextDim); } void Application::drawProperties() const { const Rectangle& panel = layout_.properties; DrawRectangleRec(panel, theme::kPanelBg); drawPanelHeader(panel, "Propiedades"); DrawRectangle(px(panel.x), px(panel.y), 1, px(panel.height), theme::kBorder); const std::size_t index = std::min(state_.currentFrame, project_.frameCount() - 1); const Frame& frame = project_.frame(index); float y = panel.y + kPanelHeaderHeight + 12.0f; const float x = panel.x + 12.0f; char line[128]; auto put = [&](Color color) { DrawText(line, px(x), px(y), kFontSize, color); y += 24.0f; }; std::snprintf(line, sizeof line, "Proyecto: %s", project_.name().c_str()); put(theme::kText); std::snprintf(line, sizeof line, "Resolución: %d x %d", project_.width(), project_.height()); put(theme::kText); std::snprintf(line, sizeof line, "FPS base: %.1f (máx. %.0f)", static_cast(project_.baseFps()), static_cast(kMaxFps)); put(theme::kText); std::snprintf(line, sizeof line, "Frame: %zu / %zu", index + 1, project_.frameCount()); put(theme::kText); std::snprintf(line, sizeof line, "FPS del frame: %.1f %s", static_cast(frame.effectiveFps(project_.baseFps())), frame.hasFpsOverride() ? "(propio)" : "(base)"); put(theme::kText); std::snprintf(line, sizeof line, "Herramienta: %s", toolLabel(state_.activeTool)); put(theme::kText); y += 8.0f; std::snprintf(line, sizeof line, "Sprites y vértices: etapas 4 y 8"); put(theme::kTextDim); } void Application::drawTimeline() const { const Rectangle& panel = layout_.timeline; DrawRectangleRec(panel, theme::kPanelBg); drawPanelHeader(panel, "Timeline"); DrawRectangle(px(panel.x), px(panel.y), px(panel.width), 1, theme::kBorder); constexpr std::array kTracks = {"Fondo", "Sprite", "Audio"}; constexpr float kLaneHeight = 32.0f; constexpr float kLabelWidth = 90.0f; float y = panel.y + kPanelHeaderHeight + 12.0f; for (const char* name : kTracks) { DrawText(name, px(panel.x + 12.0f), px(y + (kLaneHeight - static_cast(kFontSize)) / 2.0f), kFontSize, theme::kText); const Rectangle lane{panel.x + kLabelWidth, y, panel.width - kLabelWidth - 12.0f, kLaneHeight - 4.0f}; DrawRectangleRec(lane, theme::kPanelHeader); y += kLaneHeight; } DrawText("La timeline se implementa en la etapa 3", px(panel.x + 12.0f), px(y + 8.0f), kSmallFontSize, theme::kTextDim); } } // namespace tahgibu