243 lines
8.7 KiB
C++
243 lines
8.7 KiB
C++
#include "ui/Application.hpp"
|
|
|
|
#include <raylib.h>
|
|
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <cstddef>
|
|
#include <cstdio>
|
|
#include <string>
|
|
|
|
#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<const char*, 7> kMenus = {
|
|
"Archivo", "Editar", "Animación", "Sprite", "Audio", "Ver", "Lua"};
|
|
|
|
int px(float value) {
|
|
return static_cast<int>(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<float>(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<Rectangle, kToolCount> computeToolButtons(const Rectangle& bar) {
|
|
std::array<Rectangle, kToolCount> rects{};
|
|
float x = bar.x + 8.0f;
|
|
for (std::size_t i = 0; i < kToolCount; ++i) {
|
|
const float w = static_cast<float>(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<Rectangle, kMenus.size()> computeMenuRects(const Rectangle& bar) {
|
|
std::array<Rectangle, kMenus.size()> rects{};
|
|
float x = bar.x + 6.0f;
|
|
for (std::size_t i = 0; i < kMenus.size(); ++i) {
|
|
const float w = static_cast<float>(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<float>(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<float>(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<float>(project_.width());
|
|
const float projectH = static_cast<float>(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<double>(project_.baseFps()),
|
|
static_cast<double>(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<double>(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<const char*, 3> 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<float>(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
|