Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,34 @@ if(SDL3_FOUND)
target_link_libraries(Caffeine PUBLIC SDL3::SDL3)
target_compile_definitions(Caffeine PUBLIC CF_HAS_SDL3=1)
message(STATUS "SDL3 found – RHI module enabled")

include(FetchContent)
FetchContent_Declare(
imgui
GIT_REPOSITORY https://github.com/ocornut/imgui.git
GIT_TAG v1.91.9
GIT_SHALLOW TRUE
)
set(FETCHCONTENT_QUIET OFF)
FetchContent_MakeAvailable(imgui)

add_library(ImGui STATIC
${imgui_SOURCE_DIR}/imgui.cpp
${imgui_SOURCE_DIR}/imgui_draw.cpp
${imgui_SOURCE_DIR}/imgui_widgets.cpp
${imgui_SOURCE_DIR}/imgui_tables.cpp
${imgui_SOURCE_DIR}/backends/imgui_impl_sdl3.cpp
${imgui_SOURCE_DIR}/backends/imgui_impl_sdlgpu3.cpp
)
target_include_directories(ImGui PUBLIC
${imgui_SOURCE_DIR}
${imgui_SOURCE_DIR}/backends
)
target_link_libraries(ImGui PUBLIC SDL3::SDL3)

target_link_libraries(Caffeine PUBLIC ImGui)
target_compile_definitions(Caffeine PUBLIC CF_HAS_IMGUI=1)
message(STATUS "ImGui fetched and enabled")
else()
message(STATUS "SDL3 not found – RHI module disabled")
endif()
Expand Down
98 changes: 62 additions & 36 deletions docs/fase6/embedded-ui.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

> **Fase:** 6 — O Olimpo
> **Namespace:** `Caffeine::Editor`
> **Status:** 📅 Planejado
> **Status:** ✅ Implementado
> **RFs:** RF6.1, RF6.2, RF6.6

---
Expand All @@ -17,83 +17,109 @@ Integração de **Dear ImGui** para a interface do Caffeine Studio IDE. ImGui é

---

## API Planejada
## API Implementada

```cpp
namespace Caffeine::Editor {

// ============================================================================
// @brief FrameStats — metrics de frame para StatsOverlay.
// ============================================================================
struct FrameStats {
f64 deltaTime = 0.0;
f64 fps = 0.0;
u64 frameCount = 0;
f64 elapsedTime = 0.0;
};

// ============================================================================
// @brief Integração ImGui com SDL3 + RHI da Caffeine.
//
// Inicializa ImGui com:
// - Backend SDL3 (eventos de mouse/teclado)
// - Backend SDL_GPU (renderização)
//
// Lifecycle:
// 1. init(window, device)
// 2. Per frame: beginFrame() → ImGui widgets → endFrame(cmd)
// 3. shutdown()
// Disponível apenas quando CF_HAS_SDL3 e CF_HAS_IMGUI estão definidos.
// ============================================================================
class ImGuiIntegration {
public:
bool init(SDL_Window* window, RHI::RenderDevice* device);
void shutdown();

void beginFrame(); // chama ImGui::NewFrame()
void endFrame(RHI::CommandBuffer* cmd); // chama ImGui::Render() + draw data
void beginFrame();
void endFrame(RHI::CommandBuffer* cmd);

// Passa evento SDL para ImGui (chame antes de processar input do jogo)
bool processEvent(const SDL_Event& event);

// Verifica se ImGui está capturando input (não repassar ao jogo)
bool wantsKeyboard() const;
bool wantsMouse() const;
};

// ============================================================================
// @brief Janela de profiler — visualiza dados do Profiler da Fase 2.
// Renderização ImGui disponível via CF_HAS_IMGUI.
// ============================================================================
class ProfilerWindow {
public:
void pushFrameTime(f32 ms);
void pause();
void resume();
bool isPaused() const;
bool isOpen() const;
void close();
void open();

f32 lastFrameTime() const;
const std::array<f32, 120>& frameTimes() const;
u32 frameIndex() const;

#ifdef CF_HAS_IMGUI
void render(const Debug::Profiler& profiler);

private:
bool m_open = true;
bool m_paused = false;
// histograma de 120 frames
std::array<f32, 120> m_frameTimes;
u32 m_frameIdx = 0;
#endif
};

// ============================================================================
// @brief Console window — exibe logs e aceita comandos.
// Renderização ImGui disponível via CF_HAS_IMGUI.
// ============================================================================
class ConsoleWindow {
public:
void render();
void addLog(Debug::LogLevel level, const char* category,
const char* message);
void clear();

usize entryCount() const;
const LogEntry& entry(usize i) const;

bool isOpen() const;
bool autoScroll() const;
void setAutoScroll(bool v);
void close();
void open();

Debug::LogLevel filterLevel() const;
void setFilterLevel(Debug::LogLevel lvl);

#ifdef CF_HAS_IMGUI
void render();
#endif

private:
struct LogEntry {
Debug::LogLevel level;
FixedString<32> category;
FixedString<256> message;
};
std::vector<LogEntry> m_entries;
char m_inputBuf[256] = {};
bool m_autoScroll = true;
bool m_open = true;
Debug::LogLevel m_filterLevel = Debug::LogLevel::Trace;
};

// ============================================================================
// @brief Stats overlay — frame time, FPS, memory.
// @brief Stats overlay — frame time, FPS, cache stats.
// Renderização ImGui disponível via CF_HAS_IMGUI.
// ============================================================================
class StatsOverlay {
public:
void render(const GameLoop::FrameStats& stats,
const AssetManager::CacheStats& cache);
bool isOpen() const;
void close();
void open();

#ifdef CF_HAS_IMGUI
void render(const FrameStats& stats,
const Assets::CacheStats& cache);
#endif
};

} // namespace Caffeine::Editor
Expand Down Expand Up @@ -186,11 +212,11 @@ Arquivo modificado em disco

## Critério de Aceitação

- [ ] Dear ImGui integrado com SDL3 sem conflitos de input
- [ ] ProfilerWindow mostra dados do Profiler real-time
- [ ] ConsoleWindow filtra por nível e categoria
- [x] Dear ImGui integrado com SDL3 sem conflitos de input
- [x] ProfilerWindow mostra dados do Profiler real-time
- [x] ConsoleWindow filtra por nível e categoria
- [ ] Hot-reload: textura atualizada sem restart do jogo
- [ ] ImGui não interfere com input do jogo quando não em foco
- [x] ImGui não interfere com input do jogo quando não em foco

---

Expand Down
13 changes: 12 additions & 1 deletion src/Caffeine.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -80,4 +80,15 @@
// Mesh (3D)
#include "ecs/Components3D.hpp"
#include "assets/MeshTypes.hpp"
#include "assets/MeshLoader.hpp"
#include "assets/MeshLoader.hpp"

// Editor (Dear ImGui)
#include "editor/EditorTypes.hpp"
#include "editor/ConsoleWindow.hpp"
#include "editor/ProfilerWindow.hpp"
#include "editor/StatsOverlay.hpp"
#ifdef CF_HAS_SDL3
#ifdef CF_HAS_IMGUI
#include "editor/ImGuiIntegration.hpp"
#endif
#endif
101 changes: 101 additions & 0 deletions src/editor/ConsoleWindow.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
#pragma once
#include "core/Types.hpp"
#include "debug/LogSystem.hpp"
#include "containers/FixedString.hpp"
#include <vector>
#include <cstring>

#ifdef CF_HAS_IMGUI
#include <imgui.h>
#endif

namespace Caffeine::Editor {
using namespace Caffeine;

class ConsoleWindow {
public:
ConsoleWindow() = default;

struct LogEntry {
Debug::LogLevel level;
FixedString<32> category;
FixedString<256> message;
};

void addLog(Debug::LogLevel level, const char* category, const char* message) {
LogEntry e;
e.level = level;
e.category = FixedString<32>(category);
e.message = FixedString<256>(message);
m_entries.push_back(e);
}

void clear() {
m_entries.clear();
}

usize entryCount() const { return m_entries.size(); }
const LogEntry& entry(usize i) const { return m_entries[i]; }

bool isOpen() const { return m_open; }
bool autoScroll() const { return m_autoScroll; }
void setAutoScroll(bool v) { m_autoScroll = v; }
void close() { m_open = false; }
void open() { m_open = true; }

Debug::LogLevel filterLevel() const { return m_filterLevel; }
void setFilterLevel(Debug::LogLevel lvl) { m_filterLevel = lvl; }

#ifdef CF_HAS_IMGUI
void render() {
if (!m_open) return;
if (ImGui::Begin("Console", &m_open)) {
const char* levels[] = { "Trace", "Info", "Warn", "Error", "Fatal" };
int lvl = static_cast<int>(m_filterLevel);
if (ImGui::Combo("Filter", &lvl, levels, 5)) {
m_filterLevel = static_cast<Debug::LogLevel>(lvl);
}
ImGui::SameLine();
ImGui::Checkbox("Auto-scroll", &m_autoScroll);
ImGui::SameLine();
if (ImGui::Button("Clear")) clear();

ImGui::Separator();
ImGui::BeginChild("scroll", ImVec2(0, -ImGui::GetFrameHeightWithSpacing()));
for (const auto& e : m_entries) {
if (e.level < m_filterLevel) continue;
ImVec4 col = ImVec4(1, 1, 1, 1);
if (e.level == Debug::LogLevel::Warn) col = ImVec4(1, 1, 0, 1);
if (e.level == Debug::LogLevel::Error) col = ImVec4(1, 0.4f, 0.4f, 1);
if (e.level == Debug::LogLevel::Fatal) col = ImVec4(1, 0, 0, 1);
ImGui::PushStyleColor(ImGuiCol_Text, col);
ImGui::Text("[%s] %s %s",
Debug::LogSystem::levelToString(e.level),
e.category.cStr(),
e.message.cStr());
ImGui::PopStyleColor();
}
if (m_autoScroll && ImGui::GetScrollY() >= ImGui::GetScrollMaxY()) {
ImGui::SetScrollHereY(1.0f);
}
ImGui::EndChild();

ImGui::Separator();
if (ImGui::InputText("##input", m_inputBuf, sizeof(m_inputBuf),
ImGuiInputTextFlags_EnterReturnsTrue)) {
m_inputBuf[0] = '\0';
}
}
ImGui::End();
}
#endif

private:
std::vector<LogEntry> m_entries;
char m_inputBuf[256] = {};
bool m_autoScroll = true;
bool m_open = true;
Debug::LogLevel m_filterLevel = Debug::LogLevel::Trace;
};

} // namespace Caffeine::Editor
14 changes: 14 additions & 0 deletions src/editor/EditorTypes.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#pragma once
#include "core/Types.hpp"

namespace Caffeine::Editor {
using namespace Caffeine;

struct FrameStats {
f64 deltaTime = 0.0;
f64 fps = 0.0;
u64 frameCount = 0;
f64 elapsedTime = 0.0;
};

} // namespace Caffeine::Editor
Loading
Loading