feat(editor): full Unreal/Unity/Godot studio overhaul with scene tree, component inspector and docked IDE

This commit is contained in:
Artvasil09 2026-10-23 09:48:40 +03:00
parent 341966debe
commit c14ae884aa
6 changed files with 548 additions and 180 deletions

Binary file not shown.

11
editor/project.eblanproj Normal file
View file

@ -0,0 +1,11 @@
[Project]
Title=EBLAN_GAME
Author=Developer
Version=1.0.0
StartupMap=assets/map.dat
WindowTitle=EBLAN ENGINE 3D - Standalone Retro Game
DefaultWidth=1280
DefaultHeight=960
GameMode=1
ShowWeapons=1
ShowCrosshair=1

View file

@ -1104,7 +1104,13 @@ void EditorApp::RenderAssetManagerModal() {
ImGui::SetNextWindowSize(ImVec2(820, 580), ImGuiCond_FirstUseEver);
if (ImGui::Begin("Менеджер ассетов (Texture & Audio Manager)", &showAssetManagerModal)) {
if (ImGui::BeginTabBar("AssetManagerTabs")) {
RenderAssetManagerContents();
}
ImGui::End();
}
void EditorApp::RenderAssetManagerContents() {
if (ImGui::BeginTabBar("AssetManagerTabs")) {
if (ImGui::BeginTabItem("Текстурный пакет (16 слотов)")) {
ImGui::Text("Файл пакета: %s", GetTexturePackPath().c_str());
ImGui::SameLine();
@ -1225,8 +1231,6 @@ void EditorApp::RenderAssetManagerModal() {
ImGui::EndTabBar();
}
}
ImGui::End();
}
// -----------------------------------------------------------------------------
@ -1334,6 +1338,51 @@ void EditorApp::FloodFill(int startX, int startY, uint8_t targetType, uint8_t fi
for (const auto& [x, y] : cells) SetCell(x, y, fillType);
}
void EditorApp::DuplicateSelectedEntity() {
if (selectedSpriteIdx >= 0 && selectedSpriteIdx < static_cast<int>(sprites.size())) {
if (sprites.size() >= MAX_LEVEL_SPRITES) {
statusMessage = "Лимит спрайтов на карте достигнут (макс. 32).";
return;
}
PushUndo();
SpriteEntry copy = sprites[selectedSpriteIdx];
copy.x = (std::min)(copy.x + 64, (MAP_GRID_W - 2) * 64);
copy.y = (std::min)(copy.y + 64, (MAP_GRID_H - 2) * 64);
sprites.push_back(copy);
selectedSpriteIdx = static_cast<int>(sprites.size()) - 1;
statusMessage = "Сущность дублирована (Ctrl+D).";
}
}
void EditorApp::DeleteSelectedEntity() {
if (selectedSpriteIdx >= 0 && selectedSpriteIdx < static_cast<int>(sprites.size())) {
PushUndo();
sprites.erase(sprites.begin() + selectedSpriteIdx);
selectedSpriteIdx = -1;
statusMessage = "Сущность удалена (Del).";
} else if (selectedTransitionIdx >= 0 && selectedTransitionIdx < static_cast<int>(transitions.size())) {
PushUndo();
RemoveCutsceneTransition(selectedTransitionIdx);
transitions.erase(transitions.begin() + selectedTransitionIdx);
selectedTransitionIdx = -1;
statusMessage = "Портал перехода удален (Del).";
}
}
void EditorApp::FocusSelectedEntity() {
if (selectedSpriteIdx >= 0 && selectedSpriteIdx < static_cast<int>(sprites.size())) {
int gx = sprites[selectedSpriteIdx].x / 64;
int gy = sprites[selectedSpriteIdx].y / 64;
canvasPanX = 150.0f - (gx * canvasZoom);
canvasPanY = 150.0f - (gy * canvasZoom);
statusMessage = "Камера сфокусирована на объекте (F).";
} else {
canvasPanX = 150.0f - (playerStartX * canvasZoom);
canvasPanY = 150.0f - (playerStartY * canvasZoom);
statusMessage = "Камера сфокусирована на игроке (F).";
}
}
// -----------------------------------------------------------------------------
// Hotkeys
// -----------------------------------------------------------------------------
@ -1363,14 +1412,17 @@ void EditorApp::HandleHotkeys() {
SaveLevel(currentFilePath);
}
}
if (ImGui::IsKeyPressed(ImGuiKey_D, false)) DuplicateSelectedEntity();
if (ImGui::IsKeyPressed(ImGuiKey_Z, false)) Undo();
if (ImGui::IsKeyPressed(ImGuiKey_Y, false)) Redo();
return; // no single-letter tool hotkeys while Ctrl held
}
if (ImGui::IsKeyPressed(ImGuiKey_F4, false)) {
showAssetManagerModal = !showAssetManagerModal;
}
if (ImGui::IsKeyPressed(ImGuiKey_Delete, false)) DeleteSelectedEntity();
if (ImGui::IsKeyPressed(ImGuiKey_F, false)) FocusSelectedEntity();
if (ImGui::IsKeyPressed(ImGuiKey_1, false)) viewportMode = 2; // 2D Map
if (ImGui::IsKeyPressed(ImGuiKey_2, false)) viewportMode = 1; // 3D View
if (ImGui::IsKeyPressed(ImGuiKey_3, false)) viewportMode = 0; // Split
if (ImGui::IsKeyPressed(ImGuiKey_B, false)) currentTool = TOOL_BRUSH;
if (ImGui::IsKeyPressed(ImGuiKey_E, false)) currentTool = TOOL_ERASER;
@ -1410,54 +1462,111 @@ void EditorApp::RenderUI() {
ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize |
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoBringToFrontOnFocus);
// Columns: Left = Palette & Hierarchy, Middle = Grid Canvas, Right = Inspector
float leftWidth = 220.0f;
float rightWidth = 280.0f;
// Columns: Left = Hierarchy & Tile Palette, Middle = Workspace Viewport/IDE, Right = Inspector
float leftWidth = 260.0f;
float rightWidth = 320.0f;
float canvasWidth = ImGui::GetContentRegionAvail().x - leftWidth - rightWidth - 20.0f;
// Left Panel
// Left Panel (Unity / Unreal Docked Tabs)
ImGui::BeginChild("LeftPanel", ImVec2(leftWidth, 0), true);
RenderTilePalette();
ImGui::Separator();
RenderHierarchy();
if (ImGui::BeginTabBar("LeftPanelTabs", ImGuiTabBarFlags_None)) {
if (ImGui::BeginTabItem("🗂 Сцена")) {
RenderHierarchy();
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem("🎨 Палитра")) {
RenderTilePalette();
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem("📁 Файлы")) {
ImGui::TextColored(ImVec4(0.2f, 0.8f, 1.0f, 1.0f), "Браузер ассетов");
ImGui::Separator();
RenderAssetManagerContents();
ImGui::EndTabItem();
}
ImGui::EndTabBar();
}
ImGui::EndChild();
ImGui::SameLine();
// Center Panel (Grid Canvas)
// Center Panel (Grid Canvas / 3D Scene Viewport / MASM32 IDE / Asset Browser)
ImGui::BeginChild("CenterPanel", ImVec2(canvasWidth, 0), false);
float timelineHeight = showCutsceneTimeline ? (std::min)(340.0f, ImGui::GetContentRegionAvail().y * 0.48f) : 0.0f;
ImGui::BeginChild("CanvasPanel", ImVec2(0, showCutsceneTimeline ? -timelineHeight - 6 : 0), true);
if (ImGui::RadioButton("Рядом", viewportMode == 0)) viewportMode = 0;
ImGui::SameLine(); if (ImGui::RadioButton("Игровой вид", viewportMode == 1)) viewportMode = 1;
ImGui::SameLine(); if (ImGui::RadioButton("Карта", viewportMode == 2)) viewportMode = 2;
ImGui::SameLine(); ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical); ImGui::SameLine();
if (ImGui::SmallButton(" - ")) { canvasZoom = (std::max)(8.0f, canvasZoom - 4.0f); }
ImGui::SameLine(); if (ImGui::SmallButton(" 100% ")) { canvasZoom = 20.0f; canvasPanX = 20.0f; canvasPanY = 20.0f; }
ImGui::SameLine(); if (ImGui::SmallButton(" + ")) { canvasZoom = (std::min)(48.0f, canvasZoom + 4.0f); }
float sceneWidth = viewportMode == 0 ? ImGui::GetContentRegionAvail().x * 0.5f : 0;
if (viewportMode != 2) {
ImGui::BeginChild("SceneViewport", ImVec2(sceneWidth,0), true);
RenderSceneViewport();
ImGui::EndChild();
if (viewportMode == 0) ImGui::SameLine();
// Workspace Mode Tabs (Godot 4 / Unreal Engine 5 style)
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 4.0f);
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4.0f, 4.0f));
auto ModeTabButton = [&](const char* label, int modeId) {
bool active = (viewportMode == modeId);
if (active) {
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.06f, 0.48f, 0.88f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.12f, 0.58f, 0.98f, 1.0f));
} else {
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.16f, 0.18f, 0.22f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.24f, 0.27f, 0.33f, 1.0f));
}
if (ImGui::Button(label)) {
viewportMode = modeId;
}
ImGui::PopStyleColor(2);
};
ModeTabButton("🗺 2D Карта", 2);
ImGui::SameLine();
ModeTabButton("🎮 3D Сцена", 1);
ImGui::SameLine();
ModeTabButton("🪟 2D + 3D", 0);
ImGui::SameLine();
ModeTabButton("💻 Скрипты (IDE)", 3);
ImGui::SameLine();
ModeTabButton("📦 Ассеты", 4);
if (viewportMode == 0 || viewportMode == 2) {
ImGui::SameLine();
ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical);
ImGui::SameLine();
if (ImGui::SmallButton(" - ")) { canvasZoom = (std::max)(8.0f, canvasZoom - 4.0f); }
ImGui::SameLine(); if (ImGui::SmallButton(" 100% ")) { canvasZoom = 20.0f; canvasPanX = 20.0f; canvasPanY = 20.0f; }
ImGui::SameLine(); if (ImGui::SmallButton(" + ")) { canvasZoom = (std::min)(48.0f, canvasZoom + 4.0f); }
}
if (viewportMode != 1) {
ImGui::BeginChild("MapViewport", ImVec2(0,0), false);
RenderGridCanvas();
ImGui::EndChild();
}
ImGui::EndChild();
if (showCutsceneTimeline) {
ImGui::BeginChild("CutsceneTimeline", ImVec2(0, 0), true);
RenderCutsceneTimeline();
ImGui::PopStyleVar(2);
if (viewportMode == 3) {
// Integrated MASM32 IDE docked directly in center workspace!
std::string root = fs::path(currentFilePath).parent_path().parent_path().string();
if (root.empty()) root = ".";
RenderCodeEditorContents(root);
} else if (viewportMode == 4) {
// Texture & Audio Asset Browser docked in center workspace!
RenderAssetManagerContents();
} else {
float timelineHeight = showCutsceneTimeline ? (std::min)(340.0f, ImGui::GetContentRegionAvail().y * 0.48f) : 0.0f;
ImGui::BeginChild("CanvasPanel", ImVec2(0, showCutsceneTimeline ? -timelineHeight - 6 : 0), true);
float sceneWidth = viewportMode == 0 ? ImGui::GetContentRegionAvail().x * 0.5f : 0;
if (viewportMode != 2) {
ImGui::BeginChild("SceneViewport", ImVec2(sceneWidth,0), true);
RenderSceneViewport();
ImGui::EndChild();
if (viewportMode == 0) ImGui::SameLine();
}
if (viewportMode != 1) {
ImGui::BeginChild("MapViewport", ImVec2(0,0), false);
RenderGridCanvas();
ImGui::EndChild();
}
ImGui::EndChild();
if (showCutsceneTimeline) {
ImGui::BeginChild("CutsceneTimeline", ImVec2(0, 0), true);
RenderCutsceneTimeline();
ImGui::EndChild();
}
}
ImGui::EndChild();
ImGui::SameLine();
// Right Panel (Inspector)
// Right Panel (Unity / Unreal Component Inspector)
ImGui::BeginChild("RightPanel", ImVec2(rightWidth, 0), true);
RenderInspector();
ImGui::EndChild();
@ -2058,46 +2167,197 @@ void EditorApp::RenderTilePalette() {
}
void EditorApp::RenderHierarchy() {
ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.2f, 1.0f), "СУЩНОСТИ (ИЕРАРХИЯ)");
ImGui::TextColored(ImVec4(0.2f, 0.8f, 1.0f, 1.0f), "ИЕРАРХИЯ СЦЕНЫ (SCENE TREE)");
ImGui::Separator();
ImGui::Text("Игрок: Spawn (%d, %d)", playerStartX, playerStartY);
// Search filter + Quick Add button (Unity / Unreal style)
ImGui::InputTextWithHint("##HierarchySearch", "🔍 Поиск...", searchHierarchyBuf, sizeof(searchHierarchyBuf));
ImGui::SameLine();
if (ImGui::Button("+ Добавить")) {
ImGui::OpenPopup("QuickEntitySpawnPopup");
}
if (ImGui::BeginPopup("QuickEntitySpawnPopup")) {
auto AddEntityItem = [&](const char* label, uint8_t archetype) {
if (ImGui::MenuItem(label)) {
if (sprites.size() < MAX_LEVEL_SPRITES) {
PushUndo();
SpriteEntry spr{};
spr.x = (playerStartX + 1) * 64 + 32;
spr.y = (playerStartY + 1) * 64 + 32;
spr.scale = 65536;
uint32_t defaultTex = 11;
if (archetype == ENTITY_ARCHETYPE_GUARD) defaultTex = 14;
else if (archetype == ENTITY_ARCHETYPE_MEDKIT) defaultTex = 12;
else if (archetype == ENTITY_ARCHETYPE_AMMO) defaultTex = 13;
else if (archetype == ENTITY_ARCHETYPE_KEY) defaultTex = 15;
else if (archetype == ENTITY_ARCHETYPE_SWITCH) defaultTex = 10;
else if (archetype == ENTITY_ARCHETYPE_BARREL) defaultTex = 11;
spr.textureId = defaultTex;
SetSpriteEntityData(spr, archetype, (archetype == ENTITY_ARCHETYPE_GUARD ? 60 : 100), (archetype == ENTITY_ARCHETYPE_GUARD ? 2 : 0), (archetype == ENTITY_ARCHETYPE_GUARD ? 6 : 0));
sprites.push_back(spr);
selectedSpriteIdx = static_cast<int>(sprites.size()) - 1;
selectedTransitionIdx = -1;
statusMessage = "Сущность создана на сцене.";
} else {
statusMessage = "Лимит спрайтов достигнут (макс. 32).";
}
}
};
AddEntityItem("⚔ Враг (Guard AI)", ENTITY_ARCHETYPE_GUARD);
AddEntityItem("🩹 Аптечка (+25 HP)", ENTITY_ARCHETYPE_MEDKIT);
AddEntityItem("📦 Патроны (+20)", ENTITY_ARCHETYPE_AMMO);
AddEntityItem("🔑 Ключ от двери", ENTITY_ARCHETYPE_KEY);
AddEntityItem("🕹 Рычаг / Переключатель", ENTITY_ARCHETYPE_SWITCH);
AddEntityItem("💥 Взрывная бочка", ENTITY_ARCHETYPE_BARREL);
AddEntityItem("🏺 Декорация / Проп", ENTITY_ARCHETYPE_PROP);
ImGui::EndPopup();
}
ImGui::Separator();
ImGui::Text("Спрайты на карте (%zu / %d):", sprites.size(), MAX_LEVEL_SPRITES);
auto MatchesSearch = [&](const char* text) {
if (!searchHierarchyBuf[0]) return true;
std::string query = searchHierarchyBuf;
std::string target = text;
for (auto& c : query) c = (char)tolower(c);
for (auto& c : target) c = (char)tolower(c);
return target.find(query) != std::string::npos;
};
// 1. Player Spawn (Always at top of hierarchy)
char playerLabel[96];
snprintf(playerLabel, sizeof(playerLabel), "👤 Игрок [Spawn: (%d, %d)]", playerStartX, playerStartY);
if (MatchesSearch(playerLabel) || MatchesSearch("player") || MatchesSearch("игрок")) {
bool isPlayerSelected = (selectedSpriteIdx == -1 && selectedTransitionIdx == -1);
if (ImGui::Selectable(playerLabel, isPlayerSelected)) {
selectedSpriteIdx = -1;
selectedTransitionIdx = -1;
}
if (ImGui::BeginPopupContextItem("PlayerContextMenu")) {
if (ImGui::MenuItem("🎯 Сфокусировать камеру", "F")) {
FocusSelectedEntity();
}
ImGui::EndPopup();
}
}
// 2. Sprite Entities grouped by categories
size_t guardCount = 0, pickupCount = 0, objectCount = 0;
for (size_t i = 0; i < sprites.size(); ++i) {
uint8_t a = GetSpriteEntityType(sprites[i]);
if (a == ENTITY_ARCHETYPE_GUARD) guardCount++;
else if (a == ENTITY_ARCHETYPE_MEDKIT || a == ENTITY_ARCHETYPE_AMMO || a == ENTITY_ARCHETYPE_KEY) pickupCount++;
else objectCount++;
}
auto RenderSpriteItem = [&](size_t i) {
int gx = sprites[i].x / 64;
int gy = sprites[i].y / 64;
uint8_t aType = GetSpriteEntityType(sprites[i]);
const char* typeTag = "Проп";
if (aType == ENTITY_ARCHETYPE_GUARD) typeTag = "Враг";
else if (aType == ENTITY_ARCHETYPE_MEDKIT) typeTag = "+HP";
else if (aType == ENTITY_ARCHETYPE_AMMO) typeTag = "+Ammo";
else if (aType == ENTITY_ARCHETYPE_MEDKIT) typeTag = "Аптечка";
else if (aType == ENTITY_ARCHETYPE_AMMO) typeTag = "Патроны";
else if (aType == ENTITY_ARCHETYPE_KEY) typeTag = "Ключ";
else if (aType == ENTITY_ARCHETYPE_SWITCH) typeTag = "Рычаг";
else if (aType == ENTITY_ARCHETYPE_BARREL) typeTag = "Бочка";
char label[64];
snprintf(label, sizeof(label), "[%s] #%zu (%d, %d)", typeTag, i + 1, gx, gy);
char label[96];
snprintf(label, sizeof(label), "[%s] #%zu (%d, %d)##spr_%zu", typeTag, i + 1, gx, gy, i);
if (ImGui::Selectable(label, selectedSpriteIdx == static_cast<int>(i))) {
if (!MatchesSearch(label)) return;
bool isSel = (selectedSpriteIdx == static_cast<int>(i));
if (ImGui::Selectable(label, isSel)) {
selectedSpriteIdx = static_cast<int>(i);
selectedTransitionIdx = -1;
}
if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
selectedSpriteIdx = static_cast<int>(i);
FocusSelectedEntity();
}
if (ImGui::BeginPopupContextItem()) {
if (ImGui::MenuItem("📋 Дублировать", "Ctrl+D")) {
selectedSpriteIdx = static_cast<int>(i);
DuplicateSelectedEntity();
}
if (ImGui::MenuItem("🎯 Сфокусировать", "F")) {
selectedSpriteIdx = static_cast<int>(i);
FocusSelectedEntity();
}
ImGui::Separator();
if (ImGui::MenuItem("🗑 Удалить", "Del")) {
selectedSpriteIdx = static_cast<int>(i);
DeleteSelectedEntity();
}
ImGui::EndPopup();
}
};
// Enemies Category
char enemyHdr[64];
snprintf(enemyHdr, sizeof(enemyHdr), "⚔ Враги и Guard AI (%zu)", guardCount);
if (ImGui::TreeNodeEx(enemyHdr, ImGuiTreeNodeFlags_DefaultOpen)) {
for (size_t i = 0; i < sprites.size(); ++i) {
if (GetSpriteEntityType(sprites[i]) == ENTITY_ARCHETYPE_GUARD) RenderSpriteItem(i);
}
ImGui::TreePop();
}
ImGui::Separator();
ImGui::Text("Порталы перехода (%zu / %d):", transitions.size(), MAX_LEVEL_TRANSITIONS);
for (size_t i = 0; i < transitions.size(); ++i) {
char label[96];
snprintf(label, sizeof(label), "Портал #%zu (%d, %d) -> %s",
i + 1, transitions[i].triggerCellX, transitions[i].triggerCellY,
transitions[i].targetMap[0] ? transitions[i].targetMap : "<?>");
if (ImGui::Selectable(label, selectedTransitionIdx == static_cast<int>(i))) {
selectedTransitionIdx = static_cast<int>(i);
selectedSpriteIdx = -1;
// Pickups Category
char pickupHdr[64];
snprintf(pickupHdr, sizeof(pickupHdr), "📦 Лут и предметы (%zu)", pickupCount);
if (ImGui::TreeNodeEx(pickupHdr, ImGuiTreeNodeFlags_DefaultOpen)) {
for (size_t i = 0; i < sprites.size(); ++i) {
uint8_t a = GetSpriteEntityType(sprites[i]);
if (a == ENTITY_ARCHETYPE_MEDKIT || a == ENTITY_ARCHETYPE_AMMO || a == ENTITY_ARCHETYPE_KEY) RenderSpriteItem(i);
}
ImGui::TreePop();
}
// Interactive & Props Category
char objHdr[64];
snprintf(objHdr, sizeof(objHdr), "⚙ Объекты и окружение (%zu)", objectCount);
if (ImGui::TreeNodeEx(objHdr, ImGuiTreeNodeFlags_DefaultOpen)) {
for (size_t i = 0; i < sprites.size(); ++i) {
uint8_t a = GetSpriteEntityType(sprites[i]);
if (a != ENTITY_ARCHETYPE_GUARD && a != ENTITY_ARCHETYPE_MEDKIT && a != ENTITY_ARCHETYPE_AMMO && a != ENTITY_ARCHETYPE_KEY) RenderSpriteItem(i);
}
ImGui::TreePop();
}
// 3. Sublevel Transitions Category
char transHdr[64];
snprintf(transHdr, sizeof(transHdr), "🌀 Порталы перехода (%zu)", transitions.size());
if (ImGui::TreeNodeEx(transHdr, ImGuiTreeNodeFlags_DefaultOpen)) {
for (size_t i = 0; i < transitions.size(); ++i) {
char label[96];
snprintf(label, sizeof(label), "Портал #%zu (%d, %d) -> %s##trans_%zu",
i + 1, transitions[i].triggerCellX, transitions[i].triggerCellY,
transitions[i].targetMap[0] ? transitions[i].targetMap : "<?>", i);
if (!MatchesSearch(label)) continue;
bool isSel = (selectedTransitionIdx == static_cast<int>(i));
if (ImGui::Selectable(label, isSel)) {
selectedTransitionIdx = static_cast<int>(i);
selectedSpriteIdx = -1;
}
if (ImGui::BeginPopupContextItem()) {
if (ImGui::MenuItem("🎯 Сфокусировать", "F")) {
selectedTransitionIdx = static_cast<int>(i);
canvasPanX = 150.0f - (transitions[i].triggerCellX * canvasZoom);
canvasPanY = 150.0f - (transitions[i].triggerCellY * canvasZoom);
}
ImGui::Separator();
if (ImGui::MenuItem("🗑 Удалить портал", "Del")) {
selectedTransitionIdx = static_cast<int>(i);
DeleteSelectedEntity();
}
ImGui::EndPopup();
}
}
ImGui::TreePop();
}
}
@ -2106,96 +2366,68 @@ void EditorApp::RenderInspector() {
RenderCutsceneInspector();
ImGui::Separator();
}
ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.2f, 1.0f), "ИНСПЕКТОР СВОЙСТВ");
ImGui::TextColored(ImVec4(0.2f, 0.8f, 1.0f, 1.0f), "ИНСПЕКТОР СВОЙСТВ (INSPECTOR)");
ImGui::Separator();
if (ImGui::CollapsingHeader("Спавн игрока", ImGuiTreeNodeFlags_DefaultOpen)) {
ImGui::SliderInt("Spawn X", &playerStartX, 0, MAP_GRID_W - 1);
ImGui::SliderInt("Spawn Y", &playerStartY, 0, MAP_GRID_H - 1);
float degrees = (playerStartAngle * 360.0f) / 512.0f;
if (ImGui::SliderFloat("Угол обзора (°)", &degrees, 0.0f, 360.0f, "%.0f°")) {
playerStartAngle = static_cast<int>((degrees * 512.0f) / 360.0f) & 511;
}
const char* dirName = "Восток (East)";
if (playerStartAngle >= 64 && playerStartAngle < 192) dirName = "Юг (South)";
else if (playerStartAngle >= 192 && playerStartAngle < 320) dirName = "Запад (West)";
else if (playerStartAngle >= 320 && playerStartAngle < 448) dirName = "Север (North)";
ImGui::Text("Направление: %s", dirName);
}
if (ImGui::CollapsingHeader("Окружение сцены", ImGuiTreeNodeFlags_DefaultOpen)) {
// Floor & Ceiling Color Pickers
float ceilCol[3] = {
((ceilingColor >> 16) & 0xFF) / 255.0f,
((ceilingColor >> 8) & 0xFF) / 255.0f,
(ceilingColor & 0xFF) / 255.0f
};
if (ImGui::ColorEdit3("Цвет потолка", ceilCol)) {
ceilingColor = (static_cast<uint32_t>(ceilCol[0] * 255.0f) << 16) |
(static_cast<uint32_t>(ceilCol[1] * 255.0f) << 8) |
static_cast<uint32_t>(ceilCol[2] * 255.0f);
}
float floorCol[3] = {
((floorColor >> 16) & 0xFF) / 255.0f,
((floorColor >> 8) & 0xFF) / 255.0f,
(floorColor & 0xFF) / 255.0f
};
if (ImGui::ColorEdit3("Цвет пола", floorCol)) {
floorColor = (static_cast<uint32_t>(floorCol[0] * 255.0f) << 16) |
(static_cast<uint32_t>(floorCol[1] * 255.0f) << 8) |
static_cast<uint32_t>(floorCol[2] * 255.0f);
}
}
// Case 1: Sprite Entity Selected
if (selectedSpriteIdx >= 0 && selectedSpriteIdx < static_cast<int>(sprites.size())) {
if (ImGui::CollapsingHeader("Выбранный спрайт", ImGuiTreeNodeFlags_DefaultOpen)) {
auto& spr = sprites[selectedSpriteIdx];
auto& spr = sprites[selectedSpriteIdx];
uint8_t aType = GetSpriteEntityType(spr);
const char* typeName = "Декорация (Проп)";
if (aType == ENTITY_ARCHETYPE_GUARD) typeName = "Враг (Guard AI)";
else if (aType == ENTITY_ARCHETYPE_MEDKIT) typeName = "Аптечка (+25 HP)";
else if (aType == ENTITY_ARCHETYPE_AMMO) typeName = "Патроны (+20)";
else if (aType == ENTITY_ARCHETYPE_KEY) typeName = "Ключ от двери";
else if (aType == ENTITY_ARCHETYPE_SWITCH) typeName = "Рычаг [E]";
else if (aType == ENTITY_ARCHETYPE_BARREL) typeName = "Взрывная бочка";
// Position (raw world units -> cell coords for UI)
// Entity Header Card (Unity / Unreal style)
ImGui::TextColored(ImVec4(0.4f, 0.9f, 1.0f, 1.0f), "Сущность #%d: %s", selectedSpriteIdx + 1, typeName);
if (ImGui::SmallButton("📋 Дублировать (Ctrl+D)")) { DuplicateSelectedEntity(); }
ImGui::SameLine();
if (ImGui::SmallButton("🎯 Фокус (F)")) { FocusSelectedEntity(); }
ImGui::SameLine();
if (ImGui::SmallButton("🗑 Удалить (Del)")) { DeleteSelectedEntity(); return; }
ImGui::Separator();
// Component 1: Transform
if (ImGui::CollapsingHeader("📍 Transform (Координаты и масштаб)", ImGuiTreeNodeFlags_DefaultOpen)) {
float sx = spr.x / 64.0f;
float sy = spr.y / 64.0f;
if (ImGui::SliderFloat("Sprite X", &sx, 0.0f, MAP_GRID_W - 0.01f)) {
if (ImGui::SliderFloat("Позиция X", &sx, 0.0f, MAP_GRID_W - 0.01f, "%.2f ячеек")) {
spr.x = static_cast<int32_t>(sx * 64.0f);
}
if (ImGui::SliderFloat("Sprite Y", &sy, 0.0f, MAP_GRID_H - 0.01f)) {
if (ImGui::SliderFloat("Позиция Y", &sy, 0.0f, MAP_GRID_H - 0.01f, "%.2f ячеек")) {
spr.y = static_cast<int32_t>(sy * 64.0f);
}
ImGui::TextDisabled("Мировые единицы (x86 ASM): X=%d, Y=%d", spr.x, spr.y);
// Texture slot
float scaleF = spr.scale / 65536.0f;
if (ImGui::SliderFloat("Масштаб", &scaleF, 0.25f, 4.0f, "%.2fx")) {
spr.scale = static_cast<int32_t>(scaleF * 65536.0f);
}
}
// Component 2: Sprite Renderer
if (ImGui::CollapsingHeader("🖼 Sprite Renderer (Текстура)", ImGuiTreeNodeFlags_DefaultOpen)) {
int tid = static_cast<int>(spr.textureId & 15);
if (ImGui::Combo("Текстура", &tid, g_TexSlotNames, 16)) {
if (ImGui::Combo("Слот текстуры", &tid, g_TexSlotNames, 16)) {
spr.textureId = static_cast<uint32_t>(tid);
}
if (tid >= 0 && tid < 16 && textureSRVs[tid]) {
ImGui::Text("Превью спрайта:");
ImGui::Image(reinterpret_cast<ImTextureID>(textureSRVs[tid]), ImVec2(64, 64));
ImGui::SameLine();
ImGui::BeginGroup();
if (ImGui::Button("Заменить PNG...")) {
ImportTextureImage(tid, true);
}
if (ImGui::Button("Экспорт PNG")) {
ExportTextureImage(tid);
}
if (ImGui::Button("Сброс")) {
ResetTextureSlotToDefault(tid);
}
if (ImGui::Button("Заменить PNG...")) { ImportTextureImage(tid, true); }
if (ImGui::Button("Экспорт PNG")) { ExportTextureImage(tid); }
if (ImGui::Button("Сброс слота")) { ResetTextureSlotToDefault(tid); }
ImGui::EndGroup();
}
}
// Scale (16.16 fixed -> float for UI)
float scaleF = spr.scale / 65536.0f;
if (ImGui::SliderFloat("Масштаб", &scaleF, 0.25f, 4.0f, "%.2f")) {
spr.scale = static_cast<int32_t>(scaleF * 65536.0f);
}
// Entity Archetype & Logic
ImGui::Separator();
ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.2f, 1.0f), "Геймплей / Архетип сущности:");
// Component 3: Gameplay & AI Controller
if (ImGui::CollapsingHeader("🧠 Gameplay & AI Controller", ImGuiTreeNodeFlags_DefaultOpen)) {
static const char* s_ArchetypeNames[7] = {
"0: Декорация / Проп (Static)",
"1: Враг (Enemy Guard AI)",
@ -2210,7 +2442,7 @@ void EditorApp::RenderInspector() {
int spd = static_cast<int>(GetSpriteSpeed(spr));
int agg = static_cast<int>(GetSpriteAggro(spr));
if (ImGui::Combo("Тип сущности", &archType, s_ArchetypeNames, 7)) {
if (ImGui::Combo("Архетип сущности", &archType, s_ArchetypeNames, 7)) {
PushUndo();
SetSpriteEntityData(spr, static_cast<uint8_t>(archType), static_cast<uint8_t>(hp), static_cast<uint8_t>(spd), static_cast<uint8_t>(agg));
}
@ -2219,39 +2451,85 @@ void EditorApp::RenderInspector() {
if (ImGui::SliderInt("Здоровье (HP)", &hp, 10, 500)) {
SetSpriteEntityData(spr, static_cast<uint8_t>(archType), static_cast<uint8_t>(hp), static_cast<uint8_t>(spd), static_cast<uint8_t>(agg));
}
if (ImGui::SliderInt("Скорость", &spd, 1, 8)) {
if (ImGui::SliderInt("Скорость бега", &spd, 1, 8)) {
SetSpriteEntityData(spr, static_cast<uint8_t>(archType), static_cast<uint8_t>(hp), static_cast<uint8_t>(spd), static_cast<uint8_t>(agg));
}
if (ImGui::SliderInt("Радиус агрессии (ячейки)", &agg, 1, 15)) {
if (ImGui::SliderInt("Радиус агро (ячейки)", &agg, 1, 15)) {
SetSpriteEntityData(spr, static_cast<uint8_t>(archType), static_cast<uint8_t>(hp), static_cast<uint8_t>(spd), static_cast<uint8_t>(agg));
}
ImGui::TextColored(ImVec4(1.0f, 0.4f, 0.4f, 1.0f), "● Отображается красным кругом на 2D карте");
} else if (archType == ENTITY_ARCHETYPE_MEDKIT) {
ImGui::TextColored(ImVec4(0.2f, 1.0f, 0.4f, 1.0f), "Восстанавливает +25 HP игроку при подборе");
ImGui::TextColored(ImVec4(0.2f, 1.0f, 0.4f, 1.0f), "● Восстанавливает +25 HP игроку при подборе");
} else if (archType == ENTITY_ARCHETYPE_AMMO) {
ImGui::TextColored(ImVec4(1.0f, 0.9f, 0.2f, 1.0f), "Пополняет +20 патронов игроку при подборе");
ImGui::TextColored(ImVec4(1.0f, 0.9f, 0.2f, 1.0f), "● Пополняет +20 патронов игроку при подборе");
} else if (archType == ENTITY_ARCHETYPE_KEY) {
ImGui::TextColored(ImVec4(0.4f, 0.8f, 1.0f, 1.0f), "Ключ для открытия запертых дверей");
ImGui::TextColored(ImVec4(0.4f, 0.8f, 1.0f, 1.0f), "● Ключ для открытия запертых дверей");
} else if (archType == ENTITY_ARCHETYPE_SWITCH) {
ImGui::TextColored(ImVec4(1.0f, 0.5f, 0.2f, 1.0f), "Активируется нажатием клавиши [E] рядом с объектом");
ImGui::TextColored(ImVec4(1.0f, 0.5f, 0.2f, 1.0f), "● Активируется нажатием [E] рядом с объектом");
} else if (archType == ENTITY_ARCHETYPE_BARREL) {
ImGui::TextColored(ImVec4(1.0f, 0.45f, 0.15f, 1.0f), "Взрывается от выстрелов, нанося 60 AOE урона всем вокруг!");
ImGui::TextColored(ImVec4(1.0f, 0.45f, 0.15f, 1.0f), "● Взрывается от пуль, нанося 60 AOE урона вокруг!");
}
if (ImGui::Button("Редактировать логику в Редакторе кода (F7)")) {
showCodeEditorModal = true;
}
ImGui::Separator();
if (ImGui::Button("Удалить спрайт")) {
PushUndo();
sprites.erase(sprites.begin() + selectedSpriteIdx);
selectedSpriteIdx = -1;
if (ImGui::Button("💻 Открыть логику в MASM32 IDE (F7)", ImVec2(-1, 28))) {
viewportMode = 3;
}
}
// Component 4: Interaction & Lore Notes
if (ImGui::CollapsingHeader("💬 Интерактивный диалог / Записка", ImGuiTreeNodeFlags_DefaultOpen)) {
ImGui::TextDisabled("При взаимодействии [E] вызывается ShowGameDialog:");
static char szSpeaker[32] = "ПЕРСОНАЖ";
static char szDialog[128] = "ПРИВЕТСТВУЮ В EBLAN ENGINE 3D!";
ImGui::InputText("Говорящий", szSpeaker, sizeof(szSpeaker));
ImGui::InputTextMultiline("Текст", szDialog, sizeof(szDialog), ImVec2(-1, 45));
if (ImGui::Button("Вставить хук диалога в код (F7)")) {
viewportMode = 3;
}
}
// Unity style [+ Сменить компонент...]
ImGui::Spacing();
ImGui::Separator();
if (ImGui::Button("+ Добавить / Сменить компонент...", ImVec2(-1, 28))) {
ImGui::OpenPopup("AddComponentPopup");
}
if (ImGui::BeginPopup("AddComponentPopup")) {
if (ImGui::MenuItem("🧠 Guard AI Controller")) {
PushUndo();
SetSpriteEntityData(spr, ENTITY_ARCHETYPE_GUARD, 60, 2, 6);
}
if (ImGui::MenuItem("🩹 Medkit Heal Component")) {
PushUndo();
SetSpriteEntityData(spr, ENTITY_ARCHETYPE_MEDKIT, 100, 0, 0);
}
if (ImGui::MenuItem("📦 Ammo Pickup Component")) {
PushUndo();
SetSpriteEntityData(spr, ENTITY_ARCHETYPE_AMMO, 100, 0, 0);
}
if (ImGui::MenuItem("🔑 Keycard Door Component")) {
PushUndo();
SetSpriteEntityData(spr, ENTITY_ARCHETYPE_KEY, 100, 0, 0);
}
if (ImGui::MenuItem("🕹 Switch Trigger Component")) {
PushUndo();
SetSpriteEntityData(spr, ENTITY_ARCHETYPE_SWITCH, 100, 0, 0);
}
if (ImGui::MenuItem("💥 Explosive Barrel Component")) {
PushUndo();
SetSpriteEntityData(spr, ENTITY_ARCHETYPE_BARREL, 100, 0, 0);
}
if (ImGui::MenuItem("🏺 Static Prop Decorator")) {
PushUndo();
SetSpriteEntityData(spr, ENTITY_ARCHETYPE_PROP, 100, 0, 0);
}
ImGui::EndPopup();
}
return;
}
// Case 2: Portal Selected
if (selectedTransitionIdx >= 0 && selectedTransitionIdx < static_cast<int>(transitions.size())) {
if (ImGui::CollapsingHeader("Свойства портала перехода", ImGuiTreeNodeFlags_DefaultOpen)) {
if (ImGui::CollapsingHeader("🌀 Портал перехода (Sublevel Portal)", ImGuiTreeNodeFlags_DefaultOpen)) {
auto& trans = transitions[selectedTransitionIdx];
ImGui::TextColored(ImVec4(0.7f, 0.4f, 1.0f, 1.0f), "Триггер-ячейка: (%d, %d)", trans.triggerCellX, trans.triggerCellY);
@ -2314,6 +2592,71 @@ void EditorApp::RenderInspector() {
selectedTransitionIdx = -1;
}
}
return;
}
// Case 3: Level / Player Spawn Selected (Default)
if (ImGui::CollapsingHeader("👤 Спавн игрока (Player Transform)", ImGuiTreeNodeFlags_DefaultOpen)) {
ImGui::SliderInt("Spawn X", &playerStartX, 0, MAP_GRID_W - 1);
ImGui::SliderInt("Spawn Y", &playerStartY, 0, MAP_GRID_H - 1);
float degrees = (playerStartAngle * 360.0f) / 512.0f;
if (ImGui::SliderFloat("Угол обзора (°)", &degrees, 0.0f, 360.0f, "%.0f°")) {
playerStartAngle = static_cast<int>((degrees * 512.0f) / 360.0f) & 511;
}
const char* dirName = "Восток (East)";
if (playerStartAngle >= 64 && playerStartAngle < 192) dirName = "Юг (South)";
else if (playerStartAngle >= 192 && playerStartAngle < 320) dirName = "Запад (West)";
else if (playerStartAngle >= 320 && playerStartAngle < 448) dirName = "Север (North)";
ImGui::Text("Направление: %s", dirName);
if (ImGui::SmallButton("🎯 Сфокусировать камеру на игроке (F)")) {
FocusSelectedEntity();
}
}
if (ImGui::CollapsingHeader("🌐 Окружение сцены (Environment)", ImGuiTreeNodeFlags_DefaultOpen)) {
// Floor & Ceiling Color Pickers
float ceilCol[3] = {
((ceilingColor >> 16) & 0xFF) / 255.0f,
((ceilingColor >> 8) & 0xFF) / 255.0f,
(ceilingColor & 0xFF) / 255.0f
};
if (ImGui::ColorEdit3("Цвет потолка", ceilCol)) {
ceilingColor = (static_cast<uint32_t>(ceilCol[0] * 255.0f) << 16) |
(static_cast<uint32_t>(ceilCol[1] * 255.0f) << 8) |
static_cast<uint32_t>(ceilCol[2] * 255.0f);
}
float floorCol[3] = {
((floorColor >> 16) & 0xFF) / 255.0f,
((floorColor >> 8) & 0xFF) / 255.0f,
(floorColor & 0xFF) / 255.0f
};
if (ImGui::ColorEdit3("Цвет пола", floorCol)) {
floorColor = (static_cast<uint32_t>(floorCol[0] * 255.0f) << 16) |
(static_cast<uint32_t>(floorCol[1] * 255.0f) << 8) |
static_cast<uint32_t>(floorCol[2] * 255.0f);
}
ImGui::Spacing();
ImGui::TextColored(ImVec4(0.8f, 0.8f, 0.2f, 1.0f), "Быстрые пресеты освещения:");
if (ImGui::SmallButton("Подземелье")) { ceilingColor = 0x00222222; floorColor = 0x00151515; }
ImGui::SameLine();
if (ImGui::SmallButton("Бункер")) { ceilingColor = 0x00383838; floorColor = 0x00242424; }
ImGui::SameLine();
if (ImGui::SmallButton("Неон")) { ceilingColor = 0x000F172A; floorColor = 0x00022C22; }
ImGui::SameLine();
if (ImGui::SmallButton("Хоррор")) { ceilingColor = 0x003B0710; floorColor = 0x001C1917; }
}
if (ImGui::CollapsingHeader("⚙ Настройки проекта & Сборка", ImGuiTreeNodeFlags_DefaultOpen)) {
if (ImGui::Button("Настройки проекта (Жанр, Оружие)...", ImVec2(-1, 26))) {
showProjectSettingsModal = true;
}
if (ImGui::Button("Экспорт автономной игры (.exe)...", ImVec2(-1, 26))) {
showExportGameModal = true;
}
}
}

View file

@ -67,10 +67,18 @@ public:
void StopAudioSound();
void RenderAssetManagerModal();
void RenderAssetManagerContents();
void RenderCodeEditorWindow();
void RenderCodeEditorContents(const std::string& root);
void RenderProjectSettingsModal();
void RenderExportGameModal();
void DuplicateSelectedEntity();
void DeleteSelectedEntity();
void FocusSelectedEntity();
char searchHierarchyBuf[64] = "";
int leftPanelTab = 0; // 0 = Hierarchy, 1 = Tile Palette, 2 = Project Assets
bool showAssetManagerModal = false;
bool showCutsceneTimeline = false;
bool showCodeEditorModal = false;

View file

@ -449,6 +449,13 @@ void EditorApp::RenderCodeEditorWindow() {
ImGui::EndMenuBar();
}
RenderCodeEditorContents(root);
ImGui::End();
}
void EditorApp::RenderCodeEditorContents(const std::string& root) {
g_CodeEditor.PollBuild();
// Top Toolbar
ImGui::BeginGroup();
if (ImGui::Button("Сохранить (Ctrl+S)")) {
@ -661,5 +668,4 @@ void EditorApp::RenderCodeEditorWindow() {
ImGui::EndTabBar();
ImGui::Columns(1);
ImGui::End();
}

View file

@ -30,51 +30,51 @@ void SetupGodotStyle() {
ImGuiStyle& style = ImGui::GetStyle();
ImVec4* colors = style.Colors;
style.WindowRounding = 6.0f;
style.ChildRounding = 5.0f;
style.FrameRounding = 4.0f;
style.PopupRounding = 6.0f;
style.ScrollbarRounding = 6.0f;
style.GrabRounding = 4.0f;
style.TabRounding = 5.0f;
style.WindowRounding = 8.0f;
style.ChildRounding = 6.0f;
style.FrameRounding = 5.0f;
style.PopupRounding = 8.0f;
style.ScrollbarRounding = 7.0f;
style.GrabRounding = 5.0f;
style.TabRounding = 6.0f;
style.FrameBorderSize = 1.0f;
style.WindowBorderSize = 1.0f;
style.WindowPadding = ImVec2(10.0f, 10.0f);
style.FramePadding = ImVec2(7.0f, 4.0f);
style.FramePadding = ImVec2(8.0f, 5.0f);
style.ItemSpacing = ImVec2(8.0f, 6.0f);
style.ItemInnerSpacing = ImVec2(6.0f, 4.0f);
// Modern Game Studio Dark Slate Palette with Electric Cyan & Neon Amber accents
colors[ImGuiCol_Text] = ImVec4(0.95f, 0.96f, 0.98f, 1.00f);
colors[ImGuiCol_TextDisabled] = ImVec4(0.52f, 0.55f, 0.60f, 1.00f);
// Modern Game Studio Dark Palette (Unreal / Unity / Godot Pro)
colors[ImGuiCol_Text] = ImVec4(0.96f, 0.97f, 0.99f, 1.00f);
colors[ImGuiCol_TextDisabled] = ImVec4(0.50f, 0.54f, 0.60f, 1.00f);
colors[ImGuiCol_WindowBg] = ImVec4(0.11f, 0.12f, 0.14f, 1.00f);
colors[ImGuiCol_ChildBg] = ImVec4(0.13f, 0.14f, 0.17f, 1.00f);
colors[ImGuiCol_PopupBg] = ImVec4(0.14f, 0.15f, 0.18f, 0.98f);
colors[ImGuiCol_Border] = ImVec4(0.22f, 0.24f, 0.28f, 1.00f);
colors[ImGuiCol_ChildBg] = ImVec4(0.14f, 0.15f, 0.18f, 1.00f);
colors[ImGuiCol_PopupBg] = ImVec4(0.14f, 0.16f, 0.20f, 0.98f);
colors[ImGuiCol_Border] = ImVec4(0.24f, 0.26f, 0.31f, 1.00f);
colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
colors[ImGuiCol_FrameBg] = ImVec4(0.18f, 0.20f, 0.24f, 1.00f);
colors[ImGuiCol_FrameBgHovered] = ImVec4(0.24f, 0.28f, 0.34f, 1.00f);
colors[ImGuiCol_FrameBgActive] = ImVec4(0.28f, 0.33f, 0.40f, 1.00f);
colors[ImGuiCol_FrameBg] = ImVec4(0.18f, 0.20f, 0.25f, 1.00f);
colors[ImGuiCol_FrameBgHovered] = ImVec4(0.25f, 0.29f, 0.36f, 1.00f);
colors[ImGuiCol_FrameBgActive] = ImVec4(0.30f, 0.36f, 0.45f, 1.00f);
colors[ImGuiCol_TitleBg] = ImVec4(0.09f, 0.10f, 0.12f, 1.00f);
colors[ImGuiCol_TitleBgActive] = ImVec4(0.14f, 0.16f, 0.20f, 1.00f);
colors[ImGuiCol_TitleBgActive] = ImVec4(0.13f, 0.15f, 0.19f, 1.00f);
colors[ImGuiCol_MenuBarBg] = ImVec4(0.10f, 0.11f, 0.13f, 1.00f);
colors[ImGuiCol_ScrollbarBg] = ImVec4(0.09f, 0.10f, 0.12f, 0.80f);
colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.24f, 0.27f, 0.33f, 1.00f);
colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.32f, 0.37f, 0.45f, 1.00f);
colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.40f, 0.46f, 0.56f, 1.00f);
colors[ImGuiCol_CheckMark] = ImVec4(0.20f, 0.75f, 1.00f, 1.00f);
colors[ImGuiCol_SliderGrab] = ImVec4(0.20f, 0.70f, 0.98f, 1.00f);
colors[ImGuiCol_SliderGrabActive] = ImVec4(0.35f, 0.82f, 1.00f, 1.00f);
colors[ImGuiCol_Button] = ImVec4(0.20f, 0.23f, 0.28f, 1.00f);
colors[ImGuiCol_ButtonHovered] = ImVec4(0.28f, 0.34f, 0.42f, 1.00f);
colors[ImGuiCol_ButtonActive] = ImVec4(0.34f, 0.42f, 0.52f, 1.00f);
colors[ImGuiCol_Header] = ImVec4(0.20f, 0.24f, 0.30f, 1.00f);
colors[ImGuiCol_HeaderHovered] = ImVec4(0.28f, 0.34f, 0.42f, 1.00f);
colors[ImGuiCol_HeaderActive] = ImVec4(0.34f, 0.42f, 0.52f, 1.00f);
colors[ImGuiCol_Separator] = ImVec4(0.20f, 0.22f, 0.26f, 1.00f);
colors[ImGuiCol_Tab] = ImVec4(0.14f, 0.16f, 0.19f, 1.00f);
colors[ImGuiCol_TabHovered] = ImVec4(0.28f, 0.33f, 0.40f, 1.00f);
colors[ImGuiCol_TabActive] = ImVec4(0.22f, 0.26f, 0.33f, 1.00f);
colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.25f, 0.28f, 0.35f, 1.00f);
colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.34f, 0.39f, 0.48f, 1.00f);
colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.42f, 0.48f, 0.60f, 1.00f);
colors[ImGuiCol_CheckMark] = ImVec4(0.05f, 0.65f, 0.95f, 1.00f);
colors[ImGuiCol_SliderGrab] = ImVec4(0.05f, 0.60f, 0.92f, 1.00f);
colors[ImGuiCol_SliderGrabActive] = ImVec4(0.20f, 0.75f, 1.00f, 1.00f);
colors[ImGuiCol_Button] = ImVec4(0.20f, 0.23f, 0.29f, 1.00f);
colors[ImGuiCol_ButtonHovered] = ImVec4(0.28f, 0.34f, 0.43f, 1.00f);
colors[ImGuiCol_ButtonActive] = ImVec4(0.35f, 0.43f, 0.55f, 1.00f);
colors[ImGuiCol_Header] = ImVec4(0.20f, 0.25f, 0.32f, 1.00f);
colors[ImGuiCol_HeaderHovered] = ImVec4(0.28f, 0.35f, 0.45f, 1.00f);
colors[ImGuiCol_HeaderActive] = ImVec4(0.35f, 0.44f, 0.56f, 1.00f);
colors[ImGuiCol_Separator] = ImVec4(0.22f, 0.24f, 0.29f, 1.00f);
colors[ImGuiCol_Tab] = ImVec4(0.14f, 0.16f, 0.20f, 1.00f);
colors[ImGuiCol_TabHovered] = ImVec4(0.28f, 0.34f, 0.42f, 1.00f);
colors[ImGuiCol_TabActive] = ImVec4(0.22f, 0.27f, 0.35f, 1.00f);
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {