#ifdef ENABLE_EDITOR
#define IMGUI_DEFINE_MATH_OPERATORS
#include "editor.h"
#include <imgui.h>
#include <imgui_internal.h>
#include "backend/imgui_backend.h"
#include "core/logger.h"
#include "core/platform.h"
#include "renderer.h"
#include "scene/scene.h"
namespace editor {
struct DebugItem {
DebugViewMode mode;
const char *name;
};
const DebugItem DEBUG_ITEMS[] = {
{NONE, "NONE"},
{DEPTH_grayscale, "DEPTH_grayscale"},
{DEPTH_color, "DEPTH_color"},
{UV, "UV"},
{NORMAL_TEX, "NORMAL_TEX"},
{NORMAL_GEO, "NORMAL_GEO"},
{TANGENT_GEO, "TANGENT_GEO"},
{BITANGENT_GEO, "BITANGENT_GEO"},
{TANGENT_GEO_W, "TANGENT_GEO_W"},
{SHADING_NORMAL, "SHADING_NORMAL"},
{ALPHA, "ALPHA"},
{EMISSIVE, "EMISSIVE"},
{ALBEDO, "ALBEDO"},
{METALLIC, "METALLIC"},
{ROUGHNESS, "ROUGHNESS"},
{OCCLUSION, "OCCLUSION"},
{SHADOW_CASCADE, "SHADOW_CASCADE"},
{LIGHTING_ONLY, "LIGHTING_ONLY"},
{CLUSTER_HEATMAP, "CLUSTER_HEATMAP"},
{CLUSTER_BOUNDS, "CLUSTER_BOUNDS"},
{WIREFRAME, "WIREFRAME"},
{PROBES_RAY_SAMPLES, "PROBES_RAY_SAMPLES"},
{PROBES_IRRADIANCE, "PROBES_IRRADIANCE"},
{PROBES_DISTANCE, "PROBES_DISTANCE"},
{PROBES_CLASSIFICATION, "PROBES_CLASSIFICATION"}
};
void build_layout_0(Editor &e, ImGuiID dockspace_id, ImVec2 size) {
ImGui::DockBuilderRemoveNode(dockspace_id);
ImGui::DockBuilderAddNode(dockspace_id, ImGuiDockNodeFlags_DockSpace | ImGuiDockNodeFlags_PassthruCentralNode);
ImGui::DockBuilderSetNodeSize(dockspace_id, size);
ImGuiID root = dockspace_id;
// split 0: root → left (20%) + remaining (80%)
ImGuiID left;
ImGuiID remaining;
ImGui::DockBuilderSplitNode(root, ImGuiDir_Left, 0.20f, &left, &remaining);
// split 1: remaining → right (25%) + center (75%)
ImGuiID right;
ImGuiID center;
ImGui::DockBuilderSplitNode(remaining, ImGuiDir_Right, 0.25f, &right, ¢er);
// split 2: left → left_bottom (50%) + left (50%)
ImGuiID left_bottom;
ImGui::DockBuilderSplitNode(left, ImGuiDir_Down, 0.5f, &left_bottom, &left);
// split 3: center → center_bottom (35%) + center (65%)
ImGuiID center_bottom;
ImGui::DockBuilderSplitNode(center, ImGuiDir_Down, 0.35f, ¢er_bottom, ¢er);
// split 4: right → right_bottom (50%) + right (50%)
ImGuiID right_bottom;
ImGui::DockBuilderSplitNode(right, ImGuiDir_Down, 0.5f, &right_bottom, &right);
ImGui::DockBuilderDockWindow("Hierarchy", left);
ImGui::DockBuilderDockWindow("Viewport", center);
ImGui::DockBuilderDockWindow("Timings", center_bottom);
ImGui::DockBuilderDockWindow("Log", center_bottom);
ImGui::DockBuilderDockWindow("Inspector", right);
ImGui::DockBuilderDockWindow("Texture Viewer", right_bottom);
ImGui::DockBuilderFinish(dockspace_id);
e.dock_layout_built = true;
}
void draw_menu_bar(Editor &editor, RenderData &rd) {
if (ImGui::BeginMenuBar()) {
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(10.0f, 8.0f));
if (ImGui::BeginMenu("File")) {
if (ImGui::MenuItem("Exit")) {
platform_request_close(*editor.platform);
}
ImGui::EndMenu();
}
if (ImGui::BeginMenu("View")) {
ImGui::MenuItem("Viewport", nullptr, &editor.show_viewport);
ImGui::MenuItem("Hierarchy", nullptr, &editor.show_hierarchy);
ImGui::MenuItem("Inspector", nullptr, &editor.show_inspector);
ImGui::MenuItem("Texture Viewer", nullptr, &editor.show_texture_viewer);
ImGui::MenuItem("Timings", nullptr, &editor.show_timings);
ImGui::MenuItem("Log", nullptr, &editor.show_log);
if (ImGui::BeginMenu("Render Mode")) {
for (int i = 0; i < IM_ARRAYSIZE(DEBUG_ITEMS); i++) {
if (ImGui::MenuItem(DEBUG_ITEMS[i].name, nullptr, rd.debug == DEBUG_ITEMS[i].mode)) {
rd.debug = DEBUG_ITEMS[i].mode;
}
}
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Debug Draw")) {
ImGui::MenuItem("Lines enabled", nullptr, &editor.show_debug_lines);
ImGui::MenuItem("Force X-ray (ignore depth)", nullptr, &editor.force_xray);
ImGui::Separator();
ImGui::MenuItem("Category: default", nullptr, &editor.show_cat_default);
ImGui::MenuItem("Category: grid", nullptr, &editor.show_cat_grid);
ImGui::MenuItem("Category: proxy", nullptr, &editor.show_cat_proxy);
ImGui::MenuItem("Category: selection", nullptr, &editor.show_cat_selection);
ImGui::MenuItem("Category: light", nullptr, &editor.show_cat_light);
ImGui::MenuItem("Category: DDGI", nullptr, &editor.show_cat_ddgi);
ImGui::MenuItem("Category: gizmo", nullptr, &editor.show_cat_gizmo);
ImGui::EndMenu();
}
ImGui::EndMenu();
}
ImGui::PopStyleVar();
const char *button_text = "Reload Shaders";
f32 button_width = ImGui::CalcTextSize(button_text).x + ImGui::GetStyle().FramePadding.x * 2.0f;
f32 right_padding = ImGui::GetStyle().FramePadding.x;
f32 target_x = ImGui::GetCursorPosX() + ImGui::GetContentRegionAvail().x - button_width - right_padding;
ImGui::SetCursorPosX(target_x);
if (ImGui::Button(button_text)) {
request_reload(editor);
}
ImGui::EndMenuBar();
}
}
void draw_status_bar(Editor &editor, RenderData &rd) {
const ImGuiViewport *viewport = ImGui::GetMainViewport();
f32 frame_height = ImGui::GetFrameHeight();
ImGui::SetNextWindowPos(ImVec2(viewport->Pos.x, viewport->Pos.y + viewport->Size.y - frame_height));
ImGui::SetNextWindowSize(ImVec2(viewport->Size.x, frame_height));
ImGui::SetNextWindowViewport(viewport->ID);
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(6.0f, 2.0f));
ImGuiWindowFlags flags = ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoMove |
ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_NoSavedSettings |
ImGuiWindowFlags_NoDocking;
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImGui::GetStyleColorVec4(ImGuiCol_MenuBarBg));
if (ImGui::Begin("##StatusBarWindow", nullptr, flags)) {
const FrameStats &st = rd.frame_stats;
auto sep = []() {
ImGui::SameLine();
ImGui::TextUnformatted("|");
ImGui::SameLine();
};
ImGui::PushFont(editor.font_mono);
ImGui::Text("FPS: %.1f", st.fps());
sep();
ImGui::Text("Viewport: %ux%u", editor.viewport_width, editor.viewport_height);
ImGui::PopFont();
sep();
if (rd.debug >= 0 && rd.debug < IM_ARRAYSIZE(DEBUG_ITEMS)) {
ImGui::Text("Mode: %s", DEBUG_ITEMS[rd.debug].name);
} else {
ImGui::Text("Mode: %d", rd.debug);
}
ImGui::End();
}
ImGui::PopStyleColor();
ImGui::PopStyleVar(3);
}
void init(
Editor &e,
rhi::Device &device,
Platform &platform,
QuickSubmit &qs,
Arena &staging,
FrameGraph &fg,
AssetManager &assets
) {
e.device = &device;
e.platform = &platform;
e.assets = &assets;
e.qs = &qs;
VEL_INFO("Initializing ImGui...");
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGuiIO &io = ImGui::GetIO();
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
static const ImWchar ranges[] = {0x0020, 0x007E, 0};
ImGuiStyle &style = ImGui::GetStyle();
f32 scale_factor = platform_content_scale(platform);
style.ScaleAllSizes(scale_factor);
apply_style(scale_factor, style);
ImFontConfig cfg{};
e.font_ui = io.Fonts->AddFontFromFileTTF("assets/fonts/Inter-Regular.otf", 16.0f, &cfg, ranges);
e.font_mono = io.Fonts->AddFontFromFileTTF("assets/fonts/MonaspaceNeon-Regular.otf", 14.0f, &cfg, ranges);
io.ConfigDpiScaleFonts = true;
io.ConfigDpiScaleViewports = true;
io.ConfigViewportsNoDecoration = true;
io.ConfigViewportsNoDefaultParent = true;
io.ConfigNavCaptureKeyboard = true;
io.ConfigInputTextCursorBlink = true;
io.ConfigWindowsResizeFromEdges = true;
cfg.PixelSnapH = true;
cfg.PixelSnapV = true;
imgui_backend::init(device, platform_native_window(platform), qs, staging);
rhi::SamplerDesc vp_sampler_desc;
vp_sampler_desc.filter = rhi::SamplerFilter::LINEAR;
vp_sampler_desc.address = rhi::SamplerAddressMode::CLAMP;
vp_sampler_desc.name = "samp/viewport";
rhi::create_sampler(device, vp_sampler_desc, e.viewport_sampler);
init_log_viewer();
}
void shutdown(Editor &e, rhi::Device &device) {
VEL_INFO("Shutting down ImGui...");
shutdown_texture_inspectors(device);
imgui_backend::shutdown(device);
ImGui::DestroyContext();
rhi::destroy_sampler(device, e.viewport_sampler);
}
void begin_frame(Editor &e, RenderData &rd) {
imgui_backend::begin_frame();
ImGuiViewport *viewport = ImGui::GetMainViewport();
ImGui::SetNextWindowPos(viewport->WorkPos);
ImGui::SetNextWindowSize(viewport->WorkSize);
ImGui::SetNextWindowViewport(viewport->ID);
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
ImGui::Begin(
"##DockSpaceHost",
nullptr,
ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoTitleBar |
ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove |
ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNavFocus
);
draw_menu_bar(e, rd);
ImVec2 host_size = ImGui::GetContentRegionAvail();
ImGuiID dockspace_id = ImGui::GetID("MainDockSpace");
// status bar substract
f32 status_h = ImGui::GetFrameHeight();
ImVec2 subtracted_dock_size = ImVec2(host_size.x, ImMax(host_size.y - status_h, 0.0f));
ImGui::DockSpace(dockspace_id, subtracted_dock_size, ImGuiDockNodeFlags_None);
if (!e.dock_layout_built) {
build_layout_0(e, dockspace_id, subtracted_dock_size);
}
draw_status_bar(e, rd);
ImGui::End();
ImGui::PopStyleVar(3);
}
void end_frame(Editor &e, Scene &scene, RenderData &rd, const PassInspectorEntry *passes, u32 pass_count) {
// ImGui::ShowStyleEditor();
if (e.show_timings) {
draw_timings_window(scene, rd, passes, pass_count, e, e.font_mono);
}
// if (e.show_texture_viewer) {
// draw_texture_viewer_window(*e.device, e, rd);
// }
if (e.show_log) {
draw_log_window(e.font_mono);
}
if (e.show_hierarchy && e.assets) {
draw_scene_graph_window(scene, *e.assets, e.selected_node);
}
if (e.show_inspector && e.assets) {
draw_inspector_window(scene, *e.assets, e.font_mono, e);
}
if (e.show_viewport) {
draw_viewport_central(e, rd);
}
}
} // namespace editor
#endif // ENABLE_EDITOR