2226 lines
83 KiB
C++
2226 lines
83 KiB
C++
#include "gui/GuiManager.h"
|
|
#include "raylib.h"
|
|
#include "raymath.h"
|
|
#include <string>
|
|
#include <iostream>
|
|
#include <vector>
|
|
#include <unordered_set>
|
|
#include <unordered_map>
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
#include <cstdio>
|
|
#include <sstream>
|
|
#include <entt.hpp>
|
|
#include "log.h"
|
|
#include "Application.h"
|
|
#include "ECSComponents.h"
|
|
#include "SceneGraph.h"
|
|
#include "SceneLoader.h"
|
|
#include "PhysFSManager.h"
|
|
#define IMVIEWGUIZMO_IMPLEMENTATION
|
|
#include "ImViewGuizmoRaymath.h"
|
|
|
|
GuiManager::GuiManager() {}
|
|
|
|
GuiManager::~GuiManager() {}
|
|
|
|
void GuiManager::Initialize(Application *application)
|
|
{
|
|
app = application;
|
|
rlImGuiSetup(true);
|
|
ImGuiIO &io = ImGui::GetIO();
|
|
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
|
|
|
|
SetTheme();
|
|
|
|
if (sceneLoadPath[0] == '\0') {
|
|
std::snprintf(sceneLoadPath, sizeof(sceneLoadPath), "scenes/demo.toml");
|
|
}
|
|
if (sceneSavePath[0] == '\0') {
|
|
std::snprintf(sceneSavePath, sizeof(sceneSavePath), "scenes/demo.toml");
|
|
}
|
|
|
|
logWindow.Initialize(); // Initialize the LogWindow
|
|
log_trace("GuiManager::Initialize - Started");
|
|
}
|
|
|
|
void GuiManager::Render(RenderTexture2D &renderTexture)
|
|
{
|
|
rlImGuiBegin();
|
|
ImViewGuizmo::BeginFrame();
|
|
SetupDockspace(renderTexture);
|
|
|
|
RenderErrorBanner();
|
|
RenderNotifications();
|
|
logWindow.Render(showLogWindow); // Render the LogWindow
|
|
RenderSceneWindow();
|
|
RenderAssetWindow();
|
|
RenderInspectorWindow();
|
|
|
|
|
|
rlImGuiEnd();
|
|
|
|
ImGui::UpdatePlatformWindows();
|
|
ImGui::RenderPlatformWindowsDefault();
|
|
}
|
|
|
|
void GuiManager::ApplyRenderTextureResize(RenderTexture2D& renderTexture)
|
|
{
|
|
if (desiredRenderSize.x < 1.0f || desiredRenderSize.y < 1.0f) {
|
|
return;
|
|
}
|
|
|
|
const int targetWidth = std::max(1, static_cast<int>(desiredRenderSize.x));
|
|
const int targetHeight = std::max(1, static_cast<int>(desiredRenderSize.y));
|
|
|
|
if (renderTexture.texture.id == 0 ||
|
|
renderTexture.texture.width != targetWidth ||
|
|
renderTexture.texture.height != targetHeight) {
|
|
if (renderTexture.texture.id != 0) {
|
|
UnloadRenderTexture(renderTexture);
|
|
}
|
|
renderTexture = LoadRenderTexture(targetWidth, targetHeight);
|
|
}
|
|
}
|
|
|
|
void GuiManager::Shutdown()
|
|
{
|
|
rlImGuiShutdown();
|
|
}
|
|
|
|
void GuiManager::SetupDockspace(RenderTexture2D &renderTexture)
|
|
{
|
|
static bool dockspaceOpen = true;
|
|
static bool opt_fullscreen = true;
|
|
static ImGuiDockNodeFlags dockspace_flags = ImGuiDockNodeFlags_None;
|
|
|
|
ImGuiWindowFlags window_flags = ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoDocking;
|
|
if (opt_fullscreen)
|
|
{
|
|
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);
|
|
window_flags |= ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove;
|
|
window_flags |= ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNavFocus;
|
|
}
|
|
|
|
ImGui::Begin("DockSpaceHost", &dockspaceOpen, window_flags);
|
|
if (opt_fullscreen)
|
|
ImGui::PopStyleVar(2);
|
|
if (ImGui::BeginMenuBar())
|
|
{
|
|
if (ImGui::BeginMenu("Game"))
|
|
{
|
|
if (ImGui::MenuItem("Quit", "Ctrl + Q"))
|
|
{
|
|
// Handle quit here
|
|
app->RequestShutdown();
|
|
}
|
|
ImGui::EndMenu();
|
|
}
|
|
if (ImGui::BeginMenu("Scene"))
|
|
{
|
|
if (ImGui::MenuItem("New", "Ctrl + N"))
|
|
{
|
|
requestSceneNew = true;
|
|
}
|
|
if (ImGui::MenuItem("Load...", "Ctrl + O"))
|
|
{
|
|
requestSceneLoad = true;
|
|
}
|
|
if (ImGui::MenuItem("Save As...", "Ctrl + S"))
|
|
{
|
|
requestSceneSave = true;
|
|
}
|
|
ImGui::EndMenu();
|
|
}
|
|
if (ImGui::BeginMenu("View"))
|
|
{
|
|
ImGui::MenuItem("Log Viewer", "Ctrl + L", &showLogWindow);
|
|
ImGui::MenuItem("Scene", nullptr, &showSceneWindow);
|
|
ImGui::MenuItem("Assets", nullptr, &showAssetWindow);
|
|
bool useEditorCamera = app->IsUsingEditorCamera();
|
|
if (ImGui::MenuItem("Use Editor Camera", nullptr, &useEditorCamera)) {
|
|
app->SetUseEditorCamera(useEditorCamera);
|
|
}
|
|
ImGui::MenuItem("Inspector", nullptr, &showInspectorWindow);
|
|
ImGui::EndMenu();
|
|
}
|
|
ImGui::EndMenuBar();
|
|
}
|
|
|
|
if (ImGui::GetIO().KeyCtrl && ImGui::IsKeyPressed(ImGuiKey_L))
|
|
{
|
|
showLogWindow = !showLogWindow;
|
|
}
|
|
if (ImGui::GetIO().KeyCtrl && ImGui::IsKeyPressed(ImGuiKey_Q))
|
|
{
|
|
app->Shutdown();
|
|
}
|
|
if (ImGui::GetIO().KeyCtrl && ImGui::IsKeyPressed(ImGuiKey_N))
|
|
{
|
|
requestSceneNew = true;
|
|
}
|
|
if (ImGui::GetIO().KeyCtrl && ImGui::IsKeyPressed(ImGuiKey_O))
|
|
{
|
|
requestSceneLoad = true;
|
|
}
|
|
if (ImGui::GetIO().KeyCtrl && ImGui::IsKeyPressed(ImGuiKey_S))
|
|
{
|
|
requestSceneSave = true;
|
|
}
|
|
|
|
if (requestSceneNew)
|
|
{
|
|
ImGui::OpenPopup("New Scene");
|
|
requestSceneNew = false;
|
|
}
|
|
if (requestSceneLoad)
|
|
{
|
|
ImGui::OpenPopup("Load Scene");
|
|
requestSceneLoad = false;
|
|
}
|
|
if (requestSceneSave)
|
|
{
|
|
ImGui::OpenPopup("Save Scene");
|
|
requestSceneSave = false;
|
|
}
|
|
|
|
if (ImGui::BeginPopupModal("New Scene", nullptr, ImGuiWindowFlags_AlwaysAutoResize))
|
|
{
|
|
ImGui::Text("Clear the current scene and start new?");
|
|
ImGui::Spacing();
|
|
|
|
if (ImGui::Button("Create New"))
|
|
{
|
|
ClearScene();
|
|
selectedEntity = entt::null;
|
|
tagBufferEntity = entt::null;
|
|
tagBuffer[0] = '\0';
|
|
ImGui::CloseCurrentPopup();
|
|
}
|
|
ImGui::SameLine();
|
|
if (ImGui::Button("Cancel"))
|
|
{
|
|
ImGui::CloseCurrentPopup();
|
|
}
|
|
ImGui::EndPopup();
|
|
}
|
|
|
|
if (ImGui::BeginPopupModal("Load Scene", nullptr, ImGuiWindowFlags_AlwaysAutoResize))
|
|
{
|
|
ImGui::InputText("Path", sceneLoadPath, sizeof(sceneLoadPath));
|
|
bool hasPath = sceneLoadPath[0] != '\0';
|
|
if (!hasPath)
|
|
{
|
|
ImGui::BeginDisabled();
|
|
}
|
|
if (ImGui::Button("Load"))
|
|
{
|
|
entt::entity root = LoadSceneFromFile(sceneLoadPath, true);
|
|
if (root != entt::null)
|
|
{
|
|
selectedEntity = entt::null;
|
|
tagBufferEntity = entt::null;
|
|
tagBuffer[0] = '\0';
|
|
ImGui::CloseCurrentPopup();
|
|
}
|
|
}
|
|
if (!hasPath)
|
|
{
|
|
ImGui::EndDisabled();
|
|
}
|
|
ImGui::SameLine();
|
|
if (ImGui::Button("Cancel"))
|
|
{
|
|
ImGui::CloseCurrentPopup();
|
|
}
|
|
ImGui::EndPopup();
|
|
}
|
|
|
|
if (ImGui::BeginPopupModal("Save Scene", nullptr, ImGuiWindowFlags_AlwaysAutoResize))
|
|
{
|
|
ImGui::InputText("Path", sceneSavePath, sizeof(sceneSavePath));
|
|
bool hasPath = sceneSavePath[0] != '\0';
|
|
if (!hasPath)
|
|
{
|
|
ImGui::BeginDisabled();
|
|
}
|
|
if (ImGui::Button("Save"))
|
|
{
|
|
if (SaveSceneToFile(sceneSavePath))
|
|
{
|
|
ImGui::CloseCurrentPopup();
|
|
}
|
|
}
|
|
if (!hasPath)
|
|
{
|
|
ImGui::EndDisabled();
|
|
}
|
|
ImGui::SameLine();
|
|
if (ImGui::Button("Cancel"))
|
|
{
|
|
ImGui::CloseCurrentPopup();
|
|
}
|
|
ImGui::EndPopup();
|
|
}
|
|
ImGuiID dockspace_id = ImGui::GetID("MyDockSpace");
|
|
mainDockspaceId = dockspace_id;
|
|
ImGui::DockSpace(dockspace_id, ImVec2(0.0f, 0.0f), dockspace_flags);
|
|
|
|
// Add a Game Window
|
|
ImGui::Begin("Game Window");
|
|
|
|
// Render Raylib content to the Game Window
|
|
ImVec2 avail = ImGui::GetContentRegionAvail();
|
|
ImVec2 windowPos = ImGui::GetCursorScreenPos();
|
|
const int drawWidth = std::max(1, static_cast<int>(avail.x));
|
|
const int drawHeight = std::max(1, static_cast<int>(avail.y));
|
|
desiredRenderSize = ImVec2(static_cast<float>(drawWidth), static_cast<float>(drawHeight));
|
|
|
|
if (renderTexture.texture.id != 0 && drawWidth > 0 && drawHeight > 0) {
|
|
rlImGuiImageRect(&renderTexture.texture, drawWidth, drawHeight,
|
|
Rectangle{0, 0, static_cast<float>(renderTexture.texture.width), -static_cast<float>(renderTexture.texture.height)});
|
|
} else {
|
|
ImGui::TextDisabled("No render target available");
|
|
}
|
|
|
|
ImGui::SetCursorScreenPos({windowPos.x + 12.0f, windowPos.y + 12.0f});
|
|
bool useEditorCamera = app->IsUsingEditorCamera();
|
|
const char* cameraLabel = useEditorCamera ? "Use Game Camera" : "Use Editor Camera";
|
|
if (ImGui::Button(cameraLabel)) {
|
|
app->SetUseEditorCamera(!useEditorCamera);
|
|
}
|
|
|
|
if (app->IsUsingEditorCamera()) {
|
|
Camera3D& editorCamera = app->GetEditorCamera();
|
|
Vector3 camPos = editorCamera.position;
|
|
Vector3 pivot = editorCameraPivot;
|
|
const bool viewportHovered = ImGui::IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem);
|
|
const bool flyActive = viewportHovered && IsMouseButtonDown(MOUSE_BUTTON_RIGHT);
|
|
|
|
if (flyActive) {
|
|
editorPivotEntity = entt::null;
|
|
editorPivotOffset = {0.0f, 0.0f, 0.0f};
|
|
editorCameraPivot = editorCamera.target;
|
|
editorPivotInitialized = true;
|
|
pivot = editorCamera.target;
|
|
} else if (selectedEntity != entt::null) {
|
|
auto& registry = app->GetRegistry();
|
|
Vector3 entityPos = {0.0f, 0.0f, 0.0f};
|
|
if (auto* worldTransform = registry.try_get<WorldTransform>(selectedEntity)) {
|
|
entityPos = worldTransform->position;
|
|
} else if (auto* localTransform = registry.try_get<ECSTransform>(selectedEntity)) {
|
|
entityPos = localTransform->position;
|
|
}
|
|
|
|
if (editorPivotEntity != selectedEntity) {
|
|
editorPivotEntity = selectedEntity;
|
|
editorPivotOffset = {0.0f, 0.0f, 0.0f};
|
|
}
|
|
|
|
pivot = Vector3Add(entityPos, editorPivotOffset);
|
|
editorCameraPivot = pivot;
|
|
editorPivotInitialized = true;
|
|
} else {
|
|
editorPivotEntity = entt::null;
|
|
if (!editorPivotInitialized) {
|
|
editorCameraPivot = editorCamera.target;
|
|
editorPivotInitialized = true;
|
|
}
|
|
pivot = editorCameraPivot;
|
|
}
|
|
|
|
Vector3 direction = Vector3Subtract(editorCamera.target, editorCamera.position);
|
|
if (Vector3Length(direction) <= 0.0001f) {
|
|
direction = {0.0f, 0.0f, 1.0f};
|
|
}
|
|
direction = Vector3Normalize(direction);
|
|
|
|
Quaternion camRot = ImViewGuizmo::GizmoMath::quatLookAt(direction, editorCamera.up);
|
|
|
|
if (flyActive) {
|
|
Vector2 mouseDelta = GetMouseDelta();
|
|
float yaw = std::atan2(direction.x, direction.z);
|
|
float pitch = std::asin(direction.y);
|
|
const float lookSensitivity = 0.003f;
|
|
|
|
yaw -= mouseDelta.x * lookSensitivity;
|
|
pitch -= mouseDelta.y * lookSensitivity;
|
|
|
|
const float maxPitch = 1.55f;
|
|
if (pitch > maxPitch) pitch = maxPitch;
|
|
if (pitch < -maxPitch) pitch = -maxPitch;
|
|
|
|
Vector3 forward = {
|
|
std::sin(yaw) * std::cos(pitch),
|
|
std::sin(pitch),
|
|
std::cos(yaw) * std::cos(pitch)
|
|
};
|
|
|
|
Vector3 right = Vector3Normalize(Vector3CrossProduct(forward, editorCamera.up));
|
|
Vector3 up = Vector3Normalize(Vector3CrossProduct(right, forward));
|
|
|
|
InputManager& input = app->GetInputManager();
|
|
Vector3 move = {0.0f, 0.0f, 0.0f};
|
|
if (input.IsActionDown("move_up")) {
|
|
move = Vector3Add(move, forward);
|
|
}
|
|
if (input.IsActionDown("move_down")) {
|
|
move = Vector3Subtract(move, forward);
|
|
}
|
|
if (input.IsActionDown("move_left")) {
|
|
move = Vector3Subtract(move, right);
|
|
}
|
|
if (input.IsActionDown("move_right")) {
|
|
move = Vector3Add(move, right);
|
|
}
|
|
|
|
const float baseSpeed = 6.0f;
|
|
float speed = baseSpeed;
|
|
if (IsKeyDown(KEY_LEFT_SHIFT) || IsKeyDown(KEY_RIGHT_SHIFT)) {
|
|
speed *= 3.0f;
|
|
}
|
|
|
|
if (Vector3Length(move) > 0.0001f) {
|
|
move = Vector3Scale(Vector3Normalize(move), speed * GetFrameTime());
|
|
editorCamera.position = Vector3Add(editorCamera.position, move);
|
|
}
|
|
|
|
editorCamera.target = Vector3Add(editorCamera.position, forward);
|
|
editorCamera.up = up;
|
|
editorCameraPivot = editorCamera.target;
|
|
}
|
|
|
|
const float baseGizmoSize = 256.0f;
|
|
const float marginX = 6.0f;
|
|
const float marginY = 75.0f;
|
|
ImViewGuizmo::Style& gizmoStyle = ImViewGuizmo::GetStyle();
|
|
|
|
const float maxScaleX = (avail.x - (marginX * 2.0f)) / baseGizmoSize;
|
|
const float maxScaleY = (avail.y - (marginY * 2.0f) - 12.0f) /
|
|
(baseGizmoSize + (gizmoStyle.toolButtonRadius * 2.0f));
|
|
float gizmoScale = std::min(1.0f, std::min(maxScaleX, maxScaleY));
|
|
if (gizmoScale < 0.0f) {
|
|
gizmoScale = 0.0f;
|
|
}
|
|
gizmoStyle.scale = gizmoScale;
|
|
|
|
const float gizmoSize = baseGizmoSize * gizmoScale;
|
|
if (gizmoSize <= 0.0f) {
|
|
ImGui::End();
|
|
ImGui::End();
|
|
return;
|
|
}
|
|
|
|
ImVec2 gizmoPos = {windowPos.x + avail.x - gizmoSize - marginX, windowPos.y + marginY};
|
|
ImVec2 dollyPos = {gizmoPos.x, gizmoPos.y + gizmoSize + 12.0f};
|
|
ImVec2 panPos = {gizmoPos.x + 60.0f, gizmoPos.y + gizmoSize + 12.0f};
|
|
|
|
if (!flyActive) {
|
|
bool cameraChanged = false;
|
|
cameraChanged |= ImViewGuizmo::Rotate(camPos, camRot, pivot, gizmoPos);
|
|
cameraChanged |= ImViewGuizmo::Dolly(camPos, camRot, dollyPos);
|
|
Vector3 panStartPos = camPos;
|
|
bool panChanged = ImViewGuizmo::Pan(camPos, camRot, panPos);
|
|
cameraChanged |= panChanged;
|
|
if (panChanged) {
|
|
Vector3 delta = Vector3Subtract(camPos, panStartPos);
|
|
pivot = Vector3Add(pivot, delta);
|
|
if (selectedEntity != entt::null) {
|
|
editorPivotOffset = Vector3Add(editorPivotOffset, delta);
|
|
} else {
|
|
editorCameraPivot = pivot;
|
|
}
|
|
}
|
|
|
|
if (cameraChanged) {
|
|
editorCamera.position = camPos;
|
|
Vector3 up = Vector3RotateByQuaternion({0.0f, 1.0f, 0.0f}, camRot);
|
|
editorCamera.target = pivot;
|
|
editorCamera.up = up;
|
|
}
|
|
}
|
|
}
|
|
|
|
ImGui::End();
|
|
ImGui::End();
|
|
}
|
|
|
|
static entt::id_type ToId(entt::entity entity) {
|
|
return entt::to_integral(entity);
|
|
}
|
|
|
|
bool GuiManager::IsEntityAncestor(entt::entity ancestor, entt::entity entity) const
|
|
{
|
|
if (!app) return false;
|
|
auto& registry = app->GetRegistry();
|
|
entt::entity current = entity;
|
|
while (auto* hierarchy = registry.try_get<Hierarchy>(current)) {
|
|
if (hierarchy->parent == ancestor) {
|
|
return true;
|
|
}
|
|
if (hierarchy->parent == entt::null) {
|
|
return false;
|
|
}
|
|
current = hierarchy->parent;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
const char* GuiManager::GetEntityLabel(entt::entity entity)
|
|
{
|
|
if (!app) return "<no app>";
|
|
auto& registry = app->GetRegistry();
|
|
if (auto* tag = registry.try_get<Tag>(entity)) {
|
|
if (!tag->name.empty()) {
|
|
return tag->name.c_str();
|
|
}
|
|
}
|
|
|
|
static char label[64];
|
|
std::snprintf(label, sizeof(label), "Entity %u", static_cast<unsigned int>(ToId(entity)));
|
|
return label;
|
|
}
|
|
|
|
void GuiManager::SyncTagBuffer(entt::entity entity)
|
|
{
|
|
if (!app) return;
|
|
if (entity == tagBufferEntity) return;
|
|
|
|
tagBufferEntity = entity;
|
|
tagBuffer[0] = '\0';
|
|
auto& registry = app->GetRegistry();
|
|
if (auto* tag = registry.try_get<Tag>(entity)) {
|
|
std::snprintf(tagBuffer, sizeof(tagBuffer), "%s", tag->name.c_str());
|
|
}
|
|
}
|
|
|
|
void GuiManager::SyncScriptPathBuffer(entt::entity entity)
|
|
{
|
|
if (!app) return;
|
|
if (entity == scriptPathEntity) return;
|
|
|
|
scriptPathEntity = entity;
|
|
scriptPathBuffer[0] = '\0';
|
|
auto& registry = app->GetRegistry();
|
|
if (auto* script = registry.try_get<ScriptComponent>(entity)) {
|
|
std::snprintf(scriptPathBuffer, sizeof(scriptPathBuffer), "%s", script->scriptPath.c_str());
|
|
}
|
|
}
|
|
|
|
void GuiManager::RenderEntityNode(entt::entity entity)
|
|
{
|
|
if (!app) return;
|
|
auto& registry = app->GetRegistry();
|
|
if (!registry.valid(entity)) return;
|
|
|
|
auto* hierarchy = registry.try_get<Hierarchy>(entity);
|
|
bool hasChildren = hierarchy && hierarchy->first != entt::null;
|
|
|
|
ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_SpanFullWidth;
|
|
if (entity == selectedEntity) {
|
|
flags |= ImGuiTreeNodeFlags_Selected;
|
|
}
|
|
if (!hasChildren) {
|
|
flags |= ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen;
|
|
}
|
|
|
|
bool opened = ImGui::TreeNodeEx(reinterpret_cast<void*>(static_cast<intptr_t>(ToId(entity))), flags, "%s", GetEntityLabel(entity));
|
|
if (ImGui::IsItemClicked()) {
|
|
selectedEntity = entity;
|
|
SyncTagBuffer(entity);
|
|
}
|
|
|
|
if (ImGui::BeginDragDropSource()) {
|
|
entt::entity payload = entity;
|
|
ImGui::SetDragDropPayload("SCENE_ENTITY", &payload, sizeof(payload));
|
|
ImGui::Text("%s", GetEntityLabel(entity));
|
|
ImGui::EndDragDropSource();
|
|
}
|
|
|
|
if (ImGui::BeginDragDropTarget()) {
|
|
if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("SCENE_ENTITY")) {
|
|
entt::entity dragged = *static_cast<const entt::entity*>(payload->Data);
|
|
if (registry.valid(dragged) && dragged != entity && !IsEntityAncestor(dragged, entity)) {
|
|
AttachChild(registry, dragged, entity);
|
|
}
|
|
}
|
|
ImGui::EndDragDropTarget();
|
|
}
|
|
|
|
if (opened && hasChildren) {
|
|
entt::entity child = hierarchy->first;
|
|
while (child != entt::null) {
|
|
if (!registry.valid(child)) {
|
|
break;
|
|
}
|
|
RenderEntityNode(child);
|
|
auto* childHierarchy = registry.try_get<Hierarchy>(child);
|
|
child = childHierarchy ? childHierarchy->next : entt::null;
|
|
}
|
|
ImGui::TreePop();
|
|
}
|
|
}
|
|
|
|
void GuiManager::RenderSceneWindow()
|
|
{
|
|
if (!showSceneWindow) return;
|
|
ImGui::Begin("Scene", &showSceneWindow);
|
|
|
|
if (!app) {
|
|
ImGui::Text("No application context.");
|
|
ImGui::End();
|
|
return;
|
|
}
|
|
|
|
auto& registry = app->GetRegistry();
|
|
|
|
const std::string& sceneScriptPath = GetSceneScriptPath();
|
|
if (sceneScriptPath != lastSceneScriptPath) {
|
|
std::snprintf(sceneScriptPathBuffer, sizeof(sceneScriptPathBuffer), "%s", sceneScriptPath.c_str());
|
|
lastSceneScriptPath = sceneScriptPath;
|
|
}
|
|
|
|
ImGui::Text("Scene Script");
|
|
ImGui::SameLine();
|
|
ImGui::SetNextItemWidth(240.0f);
|
|
ImGui::InputText("##SceneScriptPath", sceneScriptPathBuffer, sizeof(sceneScriptPathBuffer));
|
|
ImGui::SameLine();
|
|
if (ImGui::Button("Apply")) {
|
|
SetSceneScriptPath(sceneScriptPathBuffer);
|
|
lastSceneScriptPath = sceneScriptPathBuffer;
|
|
}
|
|
|
|
ImGui::Separator();
|
|
|
|
if (ImGui::Button("Create Entity")) {
|
|
entt::entity entity = registry.create();
|
|
std::string name = "Entity " + std::to_string(static_cast<unsigned int>(ToId(entity)));
|
|
registry.emplace<Tag>(entity, name);
|
|
registry.emplace<ECSTransform>(entity, Vector3{0.0f, 0.0f, 0.0f}, Vector3{1.0f, 1.0f, 1.0f}, 0.0f);
|
|
auto& sceneEntity = registry.emplace<SceneEntity>(entity);
|
|
sceneEntity.persistent = true;
|
|
sceneEntity.id = "entity_" + std::to_string(static_cast<unsigned int>(ToId(entity)));
|
|
selectedEntity = entity;
|
|
SyncTagBuffer(entity);
|
|
}
|
|
|
|
ImGui::SameLine();
|
|
bool canDelete = registry.valid(selectedEntity);
|
|
if (!canDelete) {
|
|
ImGui::BeginDisabled();
|
|
}
|
|
if (ImGui::Button("Delete Selected") && canDelete) {
|
|
DetachChildren(registry, selectedEntity);
|
|
DetachFromParent(registry, selectedEntity);
|
|
registry.destroy(selectedEntity);
|
|
selectedEntity = entt::null;
|
|
tagBufferEntity = entt::null;
|
|
tagBuffer[0] = '\0';
|
|
}
|
|
if (!canDelete) {
|
|
ImGui::EndDisabled();
|
|
}
|
|
|
|
ImGui::Separator();
|
|
|
|
std::vector<entt::entity> roots;
|
|
auto &storage = registry.storage<entt::entity>();
|
|
for (auto entity : storage) {
|
|
if (!registry.valid(entity)) {
|
|
continue;
|
|
}
|
|
auto* hierarchy = registry.try_get<Hierarchy>(entity);
|
|
if (!hierarchy || hierarchy->parent == entt::null || !registry.valid(hierarchy->parent)) {
|
|
roots.push_back(entity);
|
|
}
|
|
}
|
|
|
|
std::sort(roots.begin(), roots.end(), [](entt::entity a, entt::entity b) {
|
|
return ToId(a) < ToId(b);
|
|
});
|
|
|
|
for (auto entity : roots) {
|
|
RenderEntityNode(entity);
|
|
}
|
|
|
|
ImGui::Separator();
|
|
ImGui::TextDisabled("Drop here to clear parent");
|
|
if (ImGui::BeginDragDropTarget()) {
|
|
if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("SCENE_ENTITY")) {
|
|
entt::entity dragged = *static_cast<const entt::entity*>(payload->Data);
|
|
if (registry.valid(dragged)) {
|
|
DetachFromParent(registry, dragged);
|
|
}
|
|
}
|
|
ImGui::EndDragDropTarget();
|
|
}
|
|
|
|
ImGui::End();
|
|
}
|
|
|
|
void GuiManager::RenderAssetWindow()
|
|
{
|
|
if (!showAssetWindow) return;
|
|
if (mainDockspaceId != 0) {
|
|
ImGui::SetNextWindowDockID(mainDockspaceId, ImGuiCond_FirstUseEver);
|
|
}
|
|
ImGui::Begin("Assets", &showAssetWindow);
|
|
|
|
if (!app) {
|
|
ImGui::Text("No application context.");
|
|
ImGui::End();
|
|
return;
|
|
}
|
|
|
|
auto& assets = app->GetAssetManager();
|
|
|
|
auto openUnloadPopup = [&](AssetUnloadType type, const std::string& key) {
|
|
pendingAssetUnloadType = type;
|
|
pendingAssetUnloadKey = key;
|
|
ImGui::OpenPopup("Unload Asset");
|
|
};
|
|
|
|
auto assetTypeLabel = [](AssetUnloadType type) {
|
|
switch (type) {
|
|
case AssetUnloadType::Texture: return "texture";
|
|
case AssetUnloadType::Shader: return "shader";
|
|
case AssetUnloadType::Model: return "model";
|
|
case AssetUnloadType::Material: return "material";
|
|
default: return "asset";
|
|
}
|
|
};
|
|
|
|
if (ImGui::BeginTabBar("AssetsTabs")) {
|
|
if (ImGui::BeginTabItem("Textures")) {
|
|
ImGui::InputText("Key", textureKeyBuffer, sizeof(textureKeyBuffer));
|
|
ImGui::InputText("Path", texturePathBuffer, sizeof(texturePathBuffer));
|
|
|
|
bool canLoad = textureKeyBuffer[0] != '\0' && texturePathBuffer[0] != '\0';
|
|
if (!canLoad) ImGui::BeginDisabled();
|
|
if (ImGui::Button("Load Texture")) {
|
|
assets.LoadTexture(textureKeyBuffer, texturePathBuffer);
|
|
}
|
|
if (!canLoad) ImGui::EndDisabled();
|
|
|
|
auto keys = assets.ListTextureKeys();
|
|
std::sort(keys.begin(), keys.end());
|
|
|
|
if (ImGui::BeginTable("TextureList", 3, ImGuiTableFlags_RowBg | ImGuiTableFlags_Borders)) {
|
|
ImGui::TableSetupColumn("Key");
|
|
ImGui::TableSetupColumn("Source");
|
|
ImGui::TableSetupColumn("Actions", ImGuiTableColumnFlags_WidthFixed, 90.0f);
|
|
ImGui::TableHeadersRow();
|
|
for (const auto& key : keys) {
|
|
ImGui::TableNextRow();
|
|
ImGui::TableSetColumnIndex(0);
|
|
ImGui::TextUnformatted(key.c_str());
|
|
ImGui::TableSetColumnIndex(1);
|
|
std::string source = assets.GetTextureSource(key);
|
|
ImGui::TextUnformatted(source.c_str());
|
|
ImGui::TableSetColumnIndex(2);
|
|
ImGui::PushID(key.c_str());
|
|
if (ImGui::SmallButton("Unload")) {
|
|
openUnloadPopup(AssetUnloadType::Texture, key);
|
|
}
|
|
ImGui::PopID();
|
|
}
|
|
ImGui::EndTable();
|
|
}
|
|
|
|
if (keys.empty()) {
|
|
ImGui::TextDisabled("No textures loaded.");
|
|
}
|
|
|
|
ImGui::EndTabItem();
|
|
}
|
|
|
|
if (ImGui::BeginTabItem("Shaders")) {
|
|
ImGui::InputText("Key", shaderKeyBuffer, sizeof(shaderKeyBuffer));
|
|
ImGui::InputText("Vertex Path", shaderVsPathBuffer, sizeof(shaderVsPathBuffer));
|
|
ImGui::InputText("Fragment Path", shaderFsPathBuffer, sizeof(shaderFsPathBuffer));
|
|
|
|
bool canLoad = shaderKeyBuffer[0] != '\0' && shaderVsPathBuffer[0] != '\0' && shaderFsPathBuffer[0] != '\0';
|
|
if (!canLoad) ImGui::BeginDisabled();
|
|
if (ImGui::Button("Load Shader")) {
|
|
assets.LoadShader(shaderKeyBuffer, shaderVsPathBuffer, shaderFsPathBuffer);
|
|
}
|
|
if (!canLoad) ImGui::EndDisabled();
|
|
|
|
auto keys = assets.ListShaderKeys();
|
|
std::sort(keys.begin(), keys.end());
|
|
|
|
if (ImGui::BeginTable("ShaderList", 3, ImGuiTableFlags_RowBg | ImGuiTableFlags_Borders)) {
|
|
ImGui::TableSetupColumn("Key");
|
|
ImGui::TableSetupColumn("Source");
|
|
ImGui::TableSetupColumn("Actions", ImGuiTableColumnFlags_WidthFixed, 90.0f);
|
|
ImGui::TableHeadersRow();
|
|
for (const auto& key : keys) {
|
|
ImGui::TableNextRow();
|
|
ImGui::TableSetColumnIndex(0);
|
|
ImGui::TextUnformatted(key.c_str());
|
|
ImGui::TableSetColumnIndex(1);
|
|
std::string source = assets.GetShaderSource(key);
|
|
ImGui::TextUnformatted(source.c_str());
|
|
ImGui::TableSetColumnIndex(2);
|
|
ImGui::PushID(key.c_str());
|
|
if (ImGui::SmallButton("Unload")) {
|
|
openUnloadPopup(AssetUnloadType::Shader, key);
|
|
}
|
|
ImGui::PopID();
|
|
}
|
|
ImGui::EndTable();
|
|
}
|
|
|
|
if (keys.empty()) {
|
|
ImGui::TextDisabled("No shaders loaded.");
|
|
}
|
|
|
|
ImGui::EndTabItem();
|
|
}
|
|
|
|
if (ImGui::BeginTabItem("Models")) {
|
|
ImGui::InputText("Key", modelKeyBuffer, sizeof(modelKeyBuffer));
|
|
ImGui::InputText("Path", modelPathBuffer, sizeof(modelPathBuffer));
|
|
|
|
bool canLoad = modelKeyBuffer[0] != '\0' && modelPathBuffer[0] != '\0';
|
|
if (!canLoad) ImGui::BeginDisabled();
|
|
if (ImGui::Button("Load Model")) {
|
|
assets.LoadModel(modelKeyBuffer, modelPathBuffer);
|
|
}
|
|
if (!canLoad) ImGui::EndDisabled();
|
|
|
|
ImGui::Separator();
|
|
ImGui::Text("Primitive");
|
|
|
|
const char* primitiveLabels[] = {
|
|
"Cube", "Sphere", "HemiSphere", "Plane", "Cylinder", "Cone", "Torus", "Knot", "Poly", "Heightmap", "Cubicmap"
|
|
};
|
|
ImGui::Combo("Type", &modelPrimitiveIndex, primitiveLabels, IM_ARRAYSIZE(primitiveLabels));
|
|
|
|
switch (modelPrimitiveIndex) {
|
|
case 0:
|
|
ImGui::DragFloat("Width", &modelWidth, 0.05f, 0.01f, 100.0f);
|
|
ImGui::DragFloat("Height", &modelHeight, 0.05f, 0.01f, 100.0f);
|
|
ImGui::DragFloat("Length", &modelLength, 0.05f, 0.01f, 100.0f);
|
|
break;
|
|
case 1:
|
|
case 2:
|
|
ImGui::DragFloat("Radius", &modelRadius, 0.05f, 0.01f, 100.0f);
|
|
ImGui::InputInt("Rings", &modelRings);
|
|
ImGui::InputInt("Slices", &modelSlices);
|
|
break;
|
|
case 3:
|
|
ImGui::DragFloat("Width", &modelWidth, 0.05f, 0.01f, 100.0f);
|
|
ImGui::DragFloat("Length", &modelLength, 0.05f, 0.01f, 100.0f);
|
|
ImGui::InputInt("Res X", &modelResX);
|
|
ImGui::InputInt("Res Z", &modelResZ);
|
|
break;
|
|
case 4:
|
|
case 5:
|
|
ImGui::DragFloat("Radius", &modelRadius, 0.05f, 0.01f, 100.0f);
|
|
ImGui::DragFloat("Height", &modelHeight, 0.05f, 0.01f, 100.0f);
|
|
ImGui::InputInt("Slices", &modelSlices);
|
|
break;
|
|
case 6:
|
|
case 7:
|
|
ImGui::DragFloat("Radius", &modelRadius, 0.05f, 0.01f, 100.0f);
|
|
ImGui::DragFloat("Size", &modelLength, 0.05f, 0.01f, 100.0f);
|
|
ImGui::InputInt("Rad Seg", &modelRadSeg);
|
|
ImGui::InputInt("Sides", &modelSides);
|
|
break;
|
|
case 8:
|
|
ImGui::InputInt("Sides", &modelPolySides);
|
|
ImGui::DragFloat("Radius", &modelRadius, 0.05f, 0.01f, 100.0f);
|
|
break;
|
|
case 9:
|
|
ImGui::InputText("Heightmap Path", modelHeightmapPathBuffer, sizeof(modelHeightmapPathBuffer));
|
|
ImGui::DragFloat("Size X", &modelSizeX, 0.05f, 0.01f, 1000.0f);
|
|
ImGui::DragFloat("Size Y", &modelSizeY, 0.05f, 0.01f, 1000.0f);
|
|
ImGui::DragFloat("Size Z", &modelSizeZ, 0.05f, 0.01f, 1000.0f);
|
|
break;
|
|
case 10:
|
|
ImGui::InputText("Cubicmap Path", modelCubicmapPathBuffer, sizeof(modelCubicmapPathBuffer));
|
|
ImGui::DragFloat("Size X", &modelSizeX, 0.05f, 0.01f, 1000.0f);
|
|
ImGui::DragFloat("Size Y", &modelSizeY, 0.05f, 0.01f, 1000.0f);
|
|
ImGui::DragFloat("Size Z", &modelSizeZ, 0.05f, 0.01f, 1000.0f);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (ImGui::Button("Create Primitive")) {
|
|
std::string key = modelKeyBuffer;
|
|
if (key.empty()) {
|
|
std::snprintf(modelKeyBuffer, sizeof(modelKeyBuffer), "%s_%d",
|
|
primitiveLabels[modelPrimitiveIndex], ++modelAutoKeyCounter);
|
|
key = modelKeyBuffer;
|
|
}
|
|
|
|
switch (modelPrimitiveIndex) {
|
|
case 0:
|
|
assets.LoadCube(key, modelWidth, modelHeight, modelLength);
|
|
break;
|
|
case 1:
|
|
assets.LoadSphere(key, modelRadius, modelRings, modelSlices);
|
|
break;
|
|
case 2:
|
|
assets.LoadHemiSphere(key, modelRadius, modelRings, modelSlices);
|
|
break;
|
|
case 3:
|
|
assets.LoadPlane(key, modelWidth, modelLength, modelResX, modelResZ);
|
|
break;
|
|
case 4:
|
|
assets.LoadCylinder(key, modelRadius, modelHeight, modelSlices);
|
|
break;
|
|
case 5:
|
|
assets.LoadCone(key, modelRadius, modelHeight, modelSlices);
|
|
break;
|
|
case 6:
|
|
assets.LoadTorus(key, modelRadius, modelLength, modelRadSeg, modelSides);
|
|
break;
|
|
case 7:
|
|
assets.LoadKnot(key, modelRadius, modelLength, modelRadSeg, modelSides);
|
|
break;
|
|
case 8:
|
|
assets.LoadPoly(key, modelPolySides, modelRadius);
|
|
break;
|
|
case 9:
|
|
if (modelHeightmapPathBuffer[0] != '\0') {
|
|
assets.LoadHeightmap(key, modelHeightmapPathBuffer, modelSizeX, modelSizeY, modelSizeZ);
|
|
}
|
|
break;
|
|
case 10:
|
|
if (modelCubicmapPathBuffer[0] != '\0') {
|
|
assets.LoadCubicmap(key, modelCubicmapPathBuffer, modelSizeX, modelSizeY, modelSizeZ);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
ImGui::TextDisabled("Leave key empty to auto-generate.");
|
|
|
|
auto keys = assets.ListModelKeys();
|
|
std::sort(keys.begin(), keys.end());
|
|
|
|
if (ImGui::BeginTable("ModelList", 3, ImGuiTableFlags_RowBg | ImGuiTableFlags_Borders)) {
|
|
ImGui::TableSetupColumn("Key");
|
|
ImGui::TableSetupColumn("Source");
|
|
ImGui::TableSetupColumn("Actions", ImGuiTableColumnFlags_WidthFixed, 90.0f);
|
|
ImGui::TableHeadersRow();
|
|
for (const auto& key : keys) {
|
|
ImGui::TableNextRow();
|
|
ImGui::TableSetColumnIndex(0);
|
|
ImGui::TextUnformatted(key.c_str());
|
|
ImGui::TableSetColumnIndex(1);
|
|
std::string source = assets.GetModelSource(key);
|
|
ImGui::TextUnformatted(source.c_str());
|
|
ImGui::TableSetColumnIndex(2);
|
|
ImGui::PushID(key.c_str());
|
|
if (ImGui::SmallButton("Unload")) {
|
|
openUnloadPopup(AssetUnloadType::Model, key);
|
|
}
|
|
ImGui::PopID();
|
|
}
|
|
ImGui::EndTable();
|
|
}
|
|
|
|
if (keys.empty()) {
|
|
ImGui::TextDisabled("No models loaded.");
|
|
}
|
|
|
|
ImGui::EndTabItem();
|
|
}
|
|
|
|
if (ImGui::BeginTabItem("Materials")) {
|
|
ImGui::InputText("Key", materialKeyBuffer, sizeof(materialKeyBuffer));
|
|
|
|
auto shaderKeys = assets.ListShaderKeys();
|
|
std::sort(shaderKeys.begin(), shaderKeys.end());
|
|
if (shaderKeys.empty()) {
|
|
ImGui::InputInt("Shader Id", &materialShaderId);
|
|
} else {
|
|
if (materialShaderIndex < 0 || materialShaderIndex >= static_cast<int>(shaderKeys.size())) {
|
|
materialShaderIndex = 0;
|
|
}
|
|
std::vector<const char*> keyLabels;
|
|
keyLabels.reserve(shaderKeys.size());
|
|
for (const auto& key : shaderKeys) {
|
|
keyLabels.push_back(key.c_str());
|
|
}
|
|
ImGui::Combo("Shader Asset", &materialShaderIndex, keyLabels.data(), static_cast<int>(keyLabels.size()));
|
|
}
|
|
|
|
ImGui::ColorEdit4("Albedo", materialAlbedo);
|
|
|
|
bool canCreate = materialKeyBuffer[0] != '\0';
|
|
if (!canCreate) ImGui::BeginDisabled();
|
|
if (ImGui::Button("Create Material")) {
|
|
unsigned int shaderId = 0;
|
|
if (shaderKeys.empty()) {
|
|
shaderId = materialShaderId < 0 ? 0u : static_cast<unsigned int>(materialShaderId);
|
|
} else {
|
|
shaderId = assets.GetShaderId(shaderKeys[materialShaderIndex]);
|
|
}
|
|
Color albedo = {
|
|
static_cast<unsigned char>(materialAlbedo[0] * 255.0f),
|
|
static_cast<unsigned char>(materialAlbedo[1] * 255.0f),
|
|
static_cast<unsigned char>(materialAlbedo[2] * 255.0f),
|
|
static_cast<unsigned char>(materialAlbedo[3] * 255.0f)
|
|
};
|
|
assets.CreateMaterial(materialKeyBuffer, shaderId, albedo);
|
|
}
|
|
if (!canCreate) ImGui::EndDisabled();
|
|
|
|
auto keys = assets.ListMaterialKeys();
|
|
std::sort(keys.begin(), keys.end());
|
|
|
|
if (ImGui::BeginTable("MaterialList", 3, ImGuiTableFlags_RowBg | ImGuiTableFlags_Borders)) {
|
|
ImGui::TableSetupColumn("Key");
|
|
ImGui::TableSetupColumn("Source");
|
|
ImGui::TableSetupColumn("Actions", ImGuiTableColumnFlags_WidthFixed, 90.0f);
|
|
ImGui::TableHeadersRow();
|
|
for (const auto& key : keys) {
|
|
ImGui::TableNextRow();
|
|
ImGui::TableSetColumnIndex(0);
|
|
ImGui::TextUnformatted(key.c_str());
|
|
ImGui::TableSetColumnIndex(1);
|
|
std::string source = assets.GetMaterialSource(key);
|
|
ImGui::TextUnformatted(source.c_str());
|
|
ImGui::TableSetColumnIndex(2);
|
|
ImGui::PushID(key.c_str());
|
|
if (ImGui::SmallButton("Unload")) {
|
|
openUnloadPopup(AssetUnloadType::Material, key);
|
|
}
|
|
ImGui::PopID();
|
|
}
|
|
ImGui::EndTable();
|
|
}
|
|
|
|
if (keys.empty()) {
|
|
ImGui::TextDisabled("No materials loaded.");
|
|
}
|
|
|
|
ImGui::EndTabItem();
|
|
}
|
|
|
|
ImGui::EndTabBar();
|
|
}
|
|
|
|
if (ImGui::BeginPopupModal("Unload Asset", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
|
|
const char* typeLabel = assetTypeLabel(pendingAssetUnloadType);
|
|
ImGui::Text("Unload %s '%s'?", typeLabel, pendingAssetUnloadKey.c_str());
|
|
ImGui::Spacing();
|
|
|
|
if (ImGui::Button("Unload")) {
|
|
switch (pendingAssetUnloadType) {
|
|
case AssetUnloadType::Texture:
|
|
assets.UnloadTexture(pendingAssetUnloadKey);
|
|
break;
|
|
case AssetUnloadType::Shader:
|
|
assets.UnloadShader(pendingAssetUnloadKey);
|
|
break;
|
|
case AssetUnloadType::Model:
|
|
assets.UnloadModel(pendingAssetUnloadKey);
|
|
break;
|
|
case AssetUnloadType::Material:
|
|
assets.UnloadMaterial(pendingAssetUnloadKey);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
pendingAssetUnloadType = AssetUnloadType::None;
|
|
pendingAssetUnloadKey.clear();
|
|
ImGui::CloseCurrentPopup();
|
|
}
|
|
ImGui::SameLine();
|
|
if (ImGui::Button("Cancel")) {
|
|
pendingAssetUnloadType = AssetUnloadType::None;
|
|
pendingAssetUnloadKey.clear();
|
|
ImGui::CloseCurrentPopup();
|
|
}
|
|
|
|
ImGui::EndPopup();
|
|
}
|
|
|
|
ImGui::End();
|
|
}
|
|
|
|
void GuiManager::RenderInspectorWindow()
|
|
{
|
|
if (!showInspectorWindow) return;
|
|
ImGui::Begin("Inspector", &showInspectorWindow);
|
|
|
|
if (!app) {
|
|
ImGui::Text("No application context.");
|
|
ImGui::End();
|
|
return;
|
|
}
|
|
|
|
auto& registry = app->GetRegistry();
|
|
auto& assets = app->GetAssetManager();
|
|
if (!registry.valid(selectedEntity)) {
|
|
ImGui::Text("No entity selected.");
|
|
ImGui::End();
|
|
return;
|
|
}
|
|
|
|
ImGui::Text("%s", GetEntityLabel(selectedEntity));
|
|
ImGui::Separator();
|
|
|
|
struct ComponentOption {
|
|
const char* label;
|
|
int id;
|
|
};
|
|
|
|
const ComponentOption allOptions[] = {
|
|
{"Tag", 0},
|
|
{"Transform", 1},
|
|
{"Velocity", 2},
|
|
{"Model Renderer", 3},
|
|
{"Script", 4},
|
|
{"Camera", 5},
|
|
{"Light", 6},
|
|
{"Material", 7},
|
|
{"Render Pass", 8},
|
|
{"Texture", 9},
|
|
{"UV Transform", 10}
|
|
};
|
|
|
|
auto hasComponent = [&](int id) {
|
|
switch (id) {
|
|
case 0: return registry.any_of<Tag>(selectedEntity);
|
|
case 1: return registry.any_of<ECSTransform>(selectedEntity);
|
|
case 2: return registry.any_of<Velocity>(selectedEntity);
|
|
case 3: return registry.any_of<ModelRenderer>(selectedEntity);
|
|
case 4: return registry.any_of<ScriptComponent>(selectedEntity);
|
|
case 5: return registry.any_of<Camera3DComponent>(selectedEntity);
|
|
case 6: return registry.any_of<LightComponent>(selectedEntity);
|
|
case 7: return registry.any_of<MaterialComponent>(selectedEntity);
|
|
case 8: return registry.any_of<RenderPassComponent>(selectedEntity);
|
|
case 9: return registry.any_of<TextureComponent>(selectedEntity);
|
|
case 10: return registry.any_of<UVTransform>(selectedEntity);
|
|
default: return false;
|
|
}
|
|
};
|
|
|
|
std::vector<const char*> availableLabels;
|
|
std::vector<int> availableIds;
|
|
availableLabels.reserve(sizeof(allOptions) / sizeof(allOptions[0]));
|
|
availableIds.reserve(sizeof(allOptions) / sizeof(allOptions[0]));
|
|
|
|
for (const auto& option : allOptions) {
|
|
if (!hasComponent(option.id)) {
|
|
availableLabels.push_back(option.label);
|
|
availableIds.push_back(option.id);
|
|
}
|
|
}
|
|
|
|
ImGui::Text("Add Component");
|
|
ImGui::SameLine();
|
|
ImGui::SetNextItemWidth(180.0f);
|
|
|
|
if (availableLabels.empty()) {
|
|
ImGui::TextDisabled("All added");
|
|
} else {
|
|
if (addComponentIndex < 0 || addComponentIndex >= static_cast<int>(availableLabels.size())) {
|
|
addComponentIndex = 0;
|
|
}
|
|
ImGui::Combo("##AddComponentCombo", &addComponentIndex, availableLabels.data(), static_cast<int>(availableLabels.size()));
|
|
}
|
|
|
|
bool canAddComponent = !availableIds.empty();
|
|
|
|
if (!canAddComponent) {
|
|
ImGui::BeginDisabled();
|
|
}
|
|
if (ImGui::Button("Add")) {
|
|
int selectedId = canAddComponent ? availableIds[addComponentIndex] : -1;
|
|
switch (selectedId) {
|
|
case 0: {
|
|
registry.emplace<Tag>(selectedEntity, "Entity");
|
|
SyncTagBuffer(selectedEntity);
|
|
break;
|
|
}
|
|
case 1:
|
|
registry.emplace<ECSTransform>(selectedEntity, Vector3{0.0f, 0.0f, 0.0f}, Vector3{1.0f, 1.0f, 1.0f}, 0.0f);
|
|
break;
|
|
case 2:
|
|
registry.emplace<Velocity>(selectedEntity, Vector3{0.0f, 0.0f, 0.0f}, 0.0f);
|
|
break;
|
|
case 3:
|
|
registry.emplace<ModelRenderer>(selectedEntity, 0, WHITE, 0.0f, 0);
|
|
break;
|
|
case 4:
|
|
registry.emplace<ScriptComponent>(selectedEntity);
|
|
SyncScriptPathBuffer(selectedEntity);
|
|
break;
|
|
case 5:
|
|
registry.emplace<Camera3DComponent>(selectedEntity);
|
|
break;
|
|
case 6:
|
|
registry.emplace<LightComponent>(selectedEntity);
|
|
break;
|
|
case 7:
|
|
registry.emplace<MaterialComponent>(selectedEntity);
|
|
break;
|
|
case 8:
|
|
registry.emplace<RenderPassComponent>(selectedEntity);
|
|
break;
|
|
case 9:
|
|
registry.emplace<TextureComponent>(selectedEntity, 0);
|
|
break;
|
|
case 10:
|
|
registry.emplace<UVTransform>(selectedEntity);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
if (!canAddComponent) {
|
|
ImGui::EndDisabled();
|
|
}
|
|
ImGui::Separator();
|
|
|
|
auto normalizeVector = [](Vector3 value, Vector3 fallback = Vector3{0.0f, 1.0f, 0.0f}) {
|
|
float length = std::sqrt(value.x * value.x + value.y * value.y + value.z * value.z);
|
|
if (length <= 0.0001f) {
|
|
return fallback;
|
|
}
|
|
return Vector3{value.x / length, value.y / length, value.z / length};
|
|
};
|
|
|
|
// Tag
|
|
if (registry.any_of<Tag>(selectedEntity)) {
|
|
SyncTagBuffer(selectedEntity);
|
|
if (ImGui::InputText("Tag", tagBuffer, sizeof(tagBuffer))) {
|
|
auto& tag = registry.get<Tag>(selectedEntity);
|
|
tag.name = tagBuffer;
|
|
}
|
|
}
|
|
|
|
// Transform
|
|
if (registry.any_of<ECSTransform>(selectedEntity)) {
|
|
ImGui::PushID("Transform");
|
|
auto& transform = registry.get<ECSTransform>(selectedEntity);
|
|
float position[3] = {transform.position.x, transform.position.y, transform.position.z};
|
|
float scale[3] = {transform.scale.x, transform.scale.y, transform.scale.z};
|
|
float rotationDeg[3] = {
|
|
transform.rotation.x * RAD2DEG,
|
|
transform.rotation.y * RAD2DEG,
|
|
transform.rotation.z * RAD2DEG
|
|
};
|
|
|
|
if (ImGui::DragFloat3("Position", position, 0.1f)) {
|
|
transform.position = {position[0], position[1], position[2]};
|
|
}
|
|
if (ImGui::DragFloat3("Scale", scale, 0.05f)) {
|
|
transform.scale = {scale[0], scale[1], scale[2]};
|
|
}
|
|
if (ImGui::DragFloat3("Rotation (deg)", rotationDeg, 0.5f)) {
|
|
transform.rotation = {
|
|
rotationDeg[0] * DEG2RAD,
|
|
rotationDeg[1] * DEG2RAD,
|
|
rotationDeg[2] * DEG2RAD
|
|
};
|
|
}
|
|
ImGui::PopID();
|
|
}
|
|
|
|
// Velocity
|
|
if (registry.any_of<Velocity>(selectedEntity)) {
|
|
auto& velocity = registry.get<Velocity>(selectedEntity);
|
|
ImGui::Separator();
|
|
ImGui::Text("Velocity");
|
|
|
|
float linear[3] = {velocity.linear.x, velocity.linear.y, velocity.linear.z};
|
|
if (ImGui::DragFloat3("Linear", linear, 0.1f)) {
|
|
velocity.linear = {linear[0], linear[1], linear[2]};
|
|
}
|
|
|
|
float angular = velocity.angular;
|
|
if (ImGui::DragFloat("Angular (rad/s)", &angular, 0.05f)) {
|
|
velocity.angular = angular;
|
|
}
|
|
|
|
if (ImGui::Button("Remove Velocity")) {
|
|
registry.remove<Velocity>(selectedEntity);
|
|
}
|
|
}
|
|
|
|
// Hierarchy
|
|
if (auto* hierarchy = registry.try_get<Hierarchy>(selectedEntity)) {
|
|
ImGui::Separator();
|
|
ImGui::Text("Hierarchy");
|
|
if (hierarchy->parent != entt::null) {
|
|
ImGui::Text("Parent: %s", GetEntityLabel(hierarchy->parent));
|
|
if (ImGui::Button("Clear Parent")) {
|
|
DetachFromParent(registry, selectedEntity);
|
|
}
|
|
} else {
|
|
ImGui::Text("Parent: <none>");
|
|
}
|
|
}
|
|
|
|
// World Transform (computed)
|
|
if (auto* world = registry.try_get<WorldTransform>(selectedEntity)) {
|
|
ImGui::Separator();
|
|
ImGui::Text("World Transform (computed)");
|
|
ImGui::BeginDisabled();
|
|
|
|
float worldPos[3] = {world->position.x, world->position.y, world->position.z};
|
|
float worldScale[3] = {world->scale.x, world->scale.y, world->scale.z};
|
|
float worldRotDeg[3] = {
|
|
world->rotation.x * RAD2DEG,
|
|
world->rotation.y * RAD2DEG,
|
|
world->rotation.z * RAD2DEG
|
|
};
|
|
|
|
ImGui::DragFloat3("World Position", worldPos, 0.1f);
|
|
ImGui::DragFloat3("World Scale", worldScale, 0.05f);
|
|
ImGui::DragFloat3("World Rotation (deg)", worldRotDeg, 0.5f);
|
|
|
|
ImGui::EndDisabled();
|
|
}
|
|
|
|
// Model Renderer
|
|
if (registry.any_of<ModelRenderer>(selectedEntity)) {
|
|
auto& renderer = registry.get<ModelRenderer>(selectedEntity);
|
|
ImGui::Separator();
|
|
ImGui::Text("Model Renderer");
|
|
|
|
auto modelKeys = assets.ListModelKeys();
|
|
std::sort(modelKeys.begin(), modelKeys.end());
|
|
std::vector<std::string> modelLabels;
|
|
modelLabels.reserve(modelKeys.size() + 1);
|
|
modelLabels.push_back("<none>");
|
|
for (const auto& key : modelKeys) {
|
|
modelLabels.push_back(key);
|
|
}
|
|
std::vector<const char*> modelLabelPtrs;
|
|
modelLabelPtrs.reserve(modelLabels.size());
|
|
for (const auto& label : modelLabels) {
|
|
modelLabelPtrs.push_back(label.c_str());
|
|
}
|
|
|
|
int modelIndex = 0;
|
|
std::string currentModelKey = assets.GetModelKey(static_cast<unsigned int>(renderer.modelId));
|
|
if (!currentModelKey.empty()) {
|
|
auto it = std::find(modelKeys.begin(), modelKeys.end(), currentModelKey);
|
|
if (it != modelKeys.end()) {
|
|
modelIndex = static_cast<int>(std::distance(modelKeys.begin(), it)) + 1;
|
|
}
|
|
}
|
|
int selectedModelIndex = modelIndex;
|
|
if (ImGui::Combo("Model Asset", &selectedModelIndex, modelLabelPtrs.data(), static_cast<int>(modelLabelPtrs.size()))) {
|
|
if (selectedModelIndex == 0) {
|
|
renderer.modelId = 0;
|
|
} else {
|
|
renderer.modelId = assets.GetModelId(modelKeys[selectedModelIndex - 1]);
|
|
}
|
|
}
|
|
|
|
int modelId = static_cast<int>(renderer.modelId);
|
|
if (ImGui::InputInt("Model Id", &modelId)) {
|
|
renderer.modelId = modelId < 0 ? 0 : static_cast<size_t>(modelId);
|
|
}
|
|
|
|
auto shaderKeys = assets.ListShaderKeys();
|
|
std::sort(shaderKeys.begin(), shaderKeys.end());
|
|
std::vector<std::string> shaderLabels;
|
|
shaderLabels.reserve(shaderKeys.size() + 1);
|
|
shaderLabels.push_back("<none>");
|
|
for (const auto& key : shaderKeys) {
|
|
shaderLabels.push_back(key);
|
|
}
|
|
std::vector<const char*> shaderLabelPtrs;
|
|
shaderLabelPtrs.reserve(shaderLabels.size());
|
|
for (const auto& label : shaderLabels) {
|
|
shaderLabelPtrs.push_back(label.c_str());
|
|
}
|
|
|
|
int shaderIndex = 0;
|
|
std::string currentShaderKey = assets.GetShaderKey(renderer.shaderId);
|
|
if (!currentShaderKey.empty()) {
|
|
auto it = std::find(shaderKeys.begin(), shaderKeys.end(), currentShaderKey);
|
|
if (it != shaderKeys.end()) {
|
|
shaderIndex = static_cast<int>(std::distance(shaderKeys.begin(), it)) + 1;
|
|
}
|
|
}
|
|
int selectedShaderIndex = shaderIndex;
|
|
if (ImGui::Combo("Shader Asset", &selectedShaderIndex, shaderLabelPtrs.data(), static_cast<int>(shaderLabelPtrs.size()))) {
|
|
if (selectedShaderIndex == 0) {
|
|
renderer.shaderId = 0;
|
|
} else {
|
|
renderer.shaderId = assets.GetShaderId(shaderKeys[selectedShaderIndex - 1]);
|
|
}
|
|
}
|
|
|
|
int shaderId = static_cast<int>(renderer.shaderId);
|
|
if (ImGui::InputInt("Shader Id", &shaderId)) {
|
|
renderer.shaderId = shaderId < 0 ? 0 : static_cast<unsigned int>(shaderId);
|
|
}
|
|
|
|
float color[4] = {
|
|
renderer.color.r / 255.0f,
|
|
renderer.color.g / 255.0f,
|
|
renderer.color.b / 255.0f,
|
|
renderer.color.a / 255.0f
|
|
};
|
|
if (ImGui::ColorEdit4("Color", color)) {
|
|
renderer.color.r = static_cast<unsigned char>(color[0] * 255.0f);
|
|
renderer.color.g = static_cast<unsigned char>(color[1] * 255.0f);
|
|
renderer.color.b = static_cast<unsigned char>(color[2] * 255.0f);
|
|
renderer.color.a = static_cast<unsigned char>(color[3] * 255.0f);
|
|
}
|
|
|
|
ImGui::DragFloat("Outline", &renderer.outlineSize, 0.05f, 0.0f, 10.0f);
|
|
|
|
if (ImGui::Button("Remove Model Renderer")) {
|
|
registry.remove<ModelRenderer>(selectedEntity);
|
|
}
|
|
}
|
|
|
|
// Texture
|
|
if (registry.any_of<TextureComponent>(selectedEntity)) {
|
|
auto& texture = registry.get<TextureComponent>(selectedEntity);
|
|
ImGui::Separator();
|
|
ImGui::Text("Texture");
|
|
|
|
auto textureKeys = assets.ListTextureKeys();
|
|
std::sort(textureKeys.begin(), textureKeys.end());
|
|
std::vector<std::string> textureLabels;
|
|
textureLabels.reserve(textureKeys.size() + 1);
|
|
textureLabels.push_back("<none>");
|
|
for (const auto& key : textureKeys) {
|
|
textureLabels.push_back(key);
|
|
}
|
|
std::vector<const char*> textureLabelPtrs;
|
|
textureLabelPtrs.reserve(textureLabels.size());
|
|
for (const auto& label : textureLabels) {
|
|
textureLabelPtrs.push_back(label.c_str());
|
|
}
|
|
|
|
int textureIndex = 0;
|
|
std::string currentTextureKey = assets.GetTextureKey(texture.textureId);
|
|
if (!currentTextureKey.empty()) {
|
|
auto it = std::find(textureKeys.begin(), textureKeys.end(), currentTextureKey);
|
|
if (it != textureKeys.end()) {
|
|
textureIndex = static_cast<int>(std::distance(textureKeys.begin(), it)) + 1;
|
|
}
|
|
}
|
|
int selectedTextureIndex = textureIndex;
|
|
if (ImGui::Combo("Texture Asset", &selectedTextureIndex, textureLabelPtrs.data(), static_cast<int>(textureLabelPtrs.size()))) {
|
|
if (selectedTextureIndex == 0) {
|
|
texture.textureId = 0;
|
|
} else {
|
|
texture.textureId = assets.GetTextureId(textureKeys[selectedTextureIndex - 1]);
|
|
}
|
|
}
|
|
|
|
int textureId = static_cast<int>(texture.textureId);
|
|
if (ImGui::InputInt("Texture Id", &textureId)) {
|
|
texture.textureId = textureId < 0 ? 0u : static_cast<unsigned int>(textureId);
|
|
}
|
|
|
|
if (ImGui::Button("Remove Texture")) {
|
|
registry.remove<TextureComponent>(selectedEntity);
|
|
}
|
|
}
|
|
|
|
// UV Transform
|
|
if (registry.any_of<UVTransform>(selectedEntity)) {
|
|
auto& uv = registry.get<UVTransform>(selectedEntity);
|
|
ImGui::Separator();
|
|
ImGui::Text("UV Transform");
|
|
|
|
float scale[2] = {uv.scale.x, uv.scale.y};
|
|
if (ImGui::DragFloat2("Scale", scale, 0.01f)) {
|
|
uv.scale = {scale[0], scale[1]};
|
|
}
|
|
|
|
float offset[2] = {uv.offset.x, uv.offset.y};
|
|
if (ImGui::DragFloat2("Offset", offset, 0.01f)) {
|
|
uv.offset = {offset[0], offset[1]};
|
|
}
|
|
|
|
float rotation = uv.rotation;
|
|
if (ImGui::DragFloat("Rotation (rad)", &rotation, 0.01f)) {
|
|
uv.rotation = rotation;
|
|
}
|
|
|
|
if (ImGui::Button("Remove UV Transform")) {
|
|
registry.remove<UVTransform>(selectedEntity);
|
|
}
|
|
}
|
|
|
|
// Script
|
|
if (registry.any_of<ScriptComponent>(selectedEntity)) {
|
|
auto& script = registry.get<ScriptComponent>(selectedEntity);
|
|
ImGui::Separator();
|
|
ImGui::Text("Script");
|
|
ImGui::PushID("ScriptComponent");
|
|
|
|
SyncScriptPathBuffer(selectedEntity);
|
|
if (ImGui::InputText("Path", scriptPathBuffer, sizeof(scriptPathBuffer))) {
|
|
script.scriptPath = scriptPathBuffer;
|
|
script.started = false;
|
|
}
|
|
|
|
if (ImGui::Checkbox("Enabled", &script.enabled)) {
|
|
if (!script.enabled) {
|
|
script.started = false;
|
|
}
|
|
}
|
|
|
|
if (ImGui::Button("Remove Script")) {
|
|
registry.remove<ScriptComponent>(selectedEntity);
|
|
scriptPathEntity = entt::null;
|
|
scriptPathBuffer[0] = '\0';
|
|
}
|
|
|
|
ImGui::PopID();
|
|
|
|
RenderScriptEditableControls(selectedEntity);
|
|
}
|
|
|
|
// Camera
|
|
if (registry.any_of<Camera3DComponent>(selectedEntity)) {
|
|
ImGui::PushID("Camera");
|
|
auto& camera = registry.get<Camera3DComponent>(selectedEntity);
|
|
ImGui::Separator();
|
|
ImGui::Text("Camera");
|
|
|
|
float camPos[3] = {camera.position.x, camera.position.y, camera.position.z};
|
|
float camTarget[3] = {camera.target.x, camera.target.y, camera.target.z};
|
|
float camUp[3] = {camera.up.x, camera.up.y, camera.up.z};
|
|
|
|
if (ImGui::DragFloat3("Position", camPos, 0.1f)) {
|
|
camera.position = {camPos[0], camPos[1], camPos[2]};
|
|
}
|
|
if (ImGui::DragFloat3("Target", camTarget, 0.1f)) {
|
|
camera.target = {camTarget[0], camTarget[1], camTarget[2]};
|
|
}
|
|
if (ImGui::DragFloat3("Up", camUp, 0.05f)) {
|
|
camera.up = {camUp[0], camUp[1], camUp[2]};
|
|
}
|
|
|
|
float fovy = camera.fovy;
|
|
if (ImGui::SliderFloat("FOV", &fovy, 1.0f, 179.0f)) {
|
|
camera.fovy = fovy;
|
|
}
|
|
|
|
const char* projectionLabels[] = {"Perspective", "Orthographic"};
|
|
int projectionIndex = (camera.projection == CAMERA_ORTHOGRAPHIC) ? 1 : 0;
|
|
if (ImGui::Combo("Projection", &projectionIndex, projectionLabels, 2)) {
|
|
camera.projection = (projectionIndex == 1) ? CAMERA_ORTHOGRAPHIC : CAMERA_PERSPECTIVE;
|
|
}
|
|
|
|
if (ImGui::Button("Look At Origin")) {
|
|
camera.target = {0.0f, 0.0f, 0.0f};
|
|
}
|
|
ImGui::SameLine();
|
|
if (ImGui::Button("Normalize Up")) {
|
|
camera.up = normalizeVector(camera.up, Vector3{0.0f, 1.0f, 0.0f});
|
|
}
|
|
ImGui::SameLine();
|
|
if (ImGui::Button("Reset Camera")) {
|
|
camera = Camera3DComponent();
|
|
}
|
|
|
|
if (ImGui::Button("Remove Camera")) {
|
|
registry.remove<Camera3DComponent>(selectedEntity);
|
|
}
|
|
ImGui::PopID();
|
|
}
|
|
|
|
// Light
|
|
if (registry.any_of<LightComponent>(selectedEntity)) {
|
|
auto& light = registry.get<LightComponent>(selectedEntity);
|
|
ImGui::Separator();
|
|
ImGui::Text("Light");
|
|
|
|
float lightDir[3] = {light.direction.x, light.direction.y, light.direction.z};
|
|
if (ImGui::DragFloat3("Direction", lightDir, 0.05f)) {
|
|
light.direction = {lightDir[0], lightDir[1], lightDir[2]};
|
|
}
|
|
|
|
if (ImGui::Button("Normalize Dir")) {
|
|
light.direction = normalizeVector(light.direction, Vector3{0.5f, 0.7f, 0.3f});
|
|
}
|
|
ImGui::SameLine();
|
|
if (ImGui::Button("Top")) {
|
|
light.direction = {0.0f, 1.0f, 0.0f};
|
|
}
|
|
ImGui::SameLine();
|
|
if (ImGui::Button("Angled")) {
|
|
light.direction = {0.5f, 0.7f, 0.3f};
|
|
}
|
|
ImGui::SameLine();
|
|
if (ImGui::Button("Side")) {
|
|
light.direction = {1.0f, 0.0f, 0.0f};
|
|
}
|
|
|
|
float lightColor[4] = {
|
|
light.color.r / 255.0f,
|
|
light.color.g / 255.0f,
|
|
light.color.b / 255.0f,
|
|
light.color.a / 255.0f
|
|
};
|
|
if (ImGui::ColorEdit4("Light Color", lightColor)) {
|
|
light.color.r = static_cast<unsigned char>(lightColor[0] * 255.0f);
|
|
light.color.g = static_cast<unsigned char>(lightColor[1] * 255.0f);
|
|
light.color.b = static_cast<unsigned char>(lightColor[2] * 255.0f);
|
|
light.color.a = static_cast<unsigned char>(lightColor[3] * 255.0f);
|
|
}
|
|
|
|
float intensity = light.intensity;
|
|
if (ImGui::SliderFloat("Intensity", &intensity, 0.0f, 10.0f)) {
|
|
light.intensity = intensity;
|
|
}
|
|
|
|
if (ImGui::Button("Remove Light")) {
|
|
registry.remove<LightComponent>(selectedEntity);
|
|
}
|
|
}
|
|
|
|
// Material
|
|
if (registry.any_of<MaterialComponent>(selectedEntity)) {
|
|
auto& material = registry.get<MaterialComponent>(selectedEntity);
|
|
ImGui::Separator();
|
|
ImGui::Text("Material");
|
|
|
|
auto materialKeys = assets.ListMaterialKeys();
|
|
std::sort(materialKeys.begin(), materialKeys.end());
|
|
std::vector<std::string> materialLabels;
|
|
materialLabels.reserve(materialKeys.size() + 1);
|
|
materialLabels.push_back("<none>");
|
|
for (const auto& key : materialKeys) {
|
|
materialLabels.push_back(key);
|
|
}
|
|
std::vector<const char*> materialLabelPtrs;
|
|
materialLabelPtrs.reserve(materialLabels.size());
|
|
for (const auto& label : materialLabels) {
|
|
materialLabelPtrs.push_back(label.c_str());
|
|
}
|
|
|
|
int materialIndex = 0;
|
|
std::string currentMaterialKey = assets.GetMaterialKey(material.materialId);
|
|
if (!currentMaterialKey.empty()) {
|
|
auto it = std::find(materialKeys.begin(), materialKeys.end(), currentMaterialKey);
|
|
if (it != materialKeys.end()) {
|
|
materialIndex = static_cast<int>(std::distance(materialKeys.begin(), it)) + 1;
|
|
}
|
|
}
|
|
int selectedMaterialIndex = materialIndex;
|
|
if (ImGui::Combo("Material Asset", &selectedMaterialIndex, materialLabelPtrs.data(), static_cast<int>(materialLabelPtrs.size()))) {
|
|
if (selectedMaterialIndex == 0) {
|
|
material.materialId = 0;
|
|
} else {
|
|
material.materialId = assets.GetMaterialId(materialKeys[selectedMaterialIndex - 1]);
|
|
}
|
|
}
|
|
|
|
int materialId = static_cast<int>(material.materialId);
|
|
if (ImGui::InputInt("Material Id", &materialId)) {
|
|
material.materialId = materialId < 0 ? 0u : static_cast<unsigned int>(materialId);
|
|
}
|
|
|
|
if (ImGui::Button("Remove Material")) {
|
|
registry.remove<MaterialComponent>(selectedEntity);
|
|
}
|
|
}
|
|
|
|
// Render Pass
|
|
if (registry.any_of<RenderPassComponent>(selectedEntity)) {
|
|
auto& pass = registry.get<RenderPassComponent>(selectedEntity);
|
|
ImGui::Separator();
|
|
ImGui::Text("Render Pass");
|
|
|
|
int order = pass.order;
|
|
if (ImGui::InputInt("Order", &order)) {
|
|
pass.order = order;
|
|
}
|
|
ImGui::Checkbox("Depth Test", &pass.depthTest);
|
|
ImGui::Checkbox("Depth Write", &pass.depthWrite);
|
|
|
|
auto passShaderKeys = assets.ListShaderKeys();
|
|
std::sort(passShaderKeys.begin(), passShaderKeys.end());
|
|
std::vector<std::string> passShaderLabels;
|
|
passShaderLabels.reserve(passShaderKeys.size() + 1);
|
|
passShaderLabels.push_back("<none>");
|
|
for (const auto& key : passShaderKeys) {
|
|
passShaderLabels.push_back(key);
|
|
}
|
|
std::vector<const char*> passShaderLabelPtrs;
|
|
passShaderLabelPtrs.reserve(passShaderLabels.size());
|
|
for (const auto& label : passShaderLabels) {
|
|
passShaderLabelPtrs.push_back(label.c_str());
|
|
}
|
|
|
|
int passShaderIndex = 0;
|
|
std::string currentPassShaderKey = assets.GetShaderKey(pass.shaderId);
|
|
if (!currentPassShaderKey.empty()) {
|
|
auto it = std::find(passShaderKeys.begin(), passShaderKeys.end(), currentPassShaderKey);
|
|
if (it != passShaderKeys.end()) {
|
|
passShaderIndex = static_cast<int>(std::distance(passShaderKeys.begin(), it)) + 1;
|
|
}
|
|
}
|
|
int selectedPassShaderIndex = passShaderIndex;
|
|
if (ImGui::Combo("Pass Shader Asset", &selectedPassShaderIndex, passShaderLabelPtrs.data(), static_cast<int>(passShaderLabelPtrs.size()))) {
|
|
if (selectedPassShaderIndex == 0) {
|
|
pass.shaderId = 0;
|
|
} else {
|
|
pass.shaderId = assets.GetShaderId(passShaderKeys[selectedPassShaderIndex - 1]);
|
|
}
|
|
}
|
|
|
|
int shaderId = static_cast<int>(pass.shaderId);
|
|
if (ImGui::InputInt("Pass Shader Id", &shaderId)) {
|
|
pass.shaderId = shaderId < 0 ? 0u : static_cast<unsigned int>(shaderId);
|
|
}
|
|
|
|
if (ImGui::Button("Remove Render Pass")) {
|
|
registry.remove<RenderPassComponent>(selectedEntity);
|
|
}
|
|
}
|
|
|
|
ImGui::End();
|
|
}
|
|
|
|
static std::string TrimString(const std::string &value)
|
|
{
|
|
size_t start = value.find_first_not_of(" \t\r\n");
|
|
if (start == std::string::npos) return std::string();
|
|
size_t end = value.find_last_not_of(" \t\r\n");
|
|
return value.substr(start, end - start + 1);
|
|
}
|
|
|
|
static std::string UnquoteString(const std::string &value)
|
|
{
|
|
if (value.size() >= 2 && value.front() == '"' && value.back() == '"') {
|
|
return value.substr(1, value.size() - 2);
|
|
}
|
|
return value;
|
|
}
|
|
|
|
static bool ExtractTagContent(const std::string &line, const std::string &tag, std::string &out)
|
|
{
|
|
std::string needle = "[" + tag + "[";
|
|
size_t start = line.find(needle);
|
|
if (start == std::string::npos) return false;
|
|
start += needle.size();
|
|
size_t end = line.find("]]", start);
|
|
if (end == std::string::npos) return false;
|
|
out = line.substr(start, end - start);
|
|
return true;
|
|
}
|
|
|
|
static std::vector<std::string> SplitOptions(const std::string &value)
|
|
{
|
|
std::vector<std::string> parts;
|
|
std::stringstream ss(value);
|
|
std::string item;
|
|
while (std::getline(ss, item, ',')) {
|
|
parts.push_back(TrimString(item));
|
|
}
|
|
return parts;
|
|
}
|
|
|
|
static bool TryParseRange(const std::string &value, float &outMin, float &outMax)
|
|
{
|
|
size_t comma = value.find(',');
|
|
if (comma == std::string::npos) return false;
|
|
std::string left = TrimString(value.substr(0, comma));
|
|
std::string right = TrimString(value.substr(comma + 1));
|
|
try {
|
|
outMin = std::stof(left);
|
|
outMax = std::stof(right);
|
|
return true;
|
|
} catch (...) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
GuiManager::ScriptEditableCache &GuiManager::GetOrParseEditableCache(const std::string &path, const std::string &className)
|
|
{
|
|
std::string key = path + "::" + className;
|
|
auto &cache = scriptEditableCache[key];
|
|
cache.className = className;
|
|
|
|
bool needsParse = true;
|
|
std::int64_t writeTime = PhysFSManager::GetModTime(path);
|
|
if (writeTime != 0 && cache.hasWriteTime && cache.lastWriteTime == writeTime) {
|
|
needsParse = false;
|
|
} else {
|
|
cache.lastWriteTime = writeTime;
|
|
cache.hasWriteTime = (writeTime != 0);
|
|
}
|
|
|
|
if (!needsParse) return cache;
|
|
|
|
cache.fields.clear();
|
|
|
|
std::string contents;
|
|
if (!PhysFSManager::ReadTextFile(path, contents)) {
|
|
return cache;
|
|
}
|
|
|
|
std::istringstream file(contents);
|
|
std::string line;
|
|
bool inClass = false;
|
|
int braceDepth = 0;
|
|
|
|
while (std::getline(file, line)) {
|
|
std::string trimmed = TrimString(line);
|
|
if (!inClass) {
|
|
if (trimmed.find("class " + className) != std::string::npos) {
|
|
inClass = true;
|
|
for (char c : trimmed) {
|
|
if (c == '{') braceDepth++;
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
|
|
for (char c : line) {
|
|
if (c == '{') braceDepth++;
|
|
if (c == '}') braceDepth--;
|
|
}
|
|
|
|
if (braceDepth <= 0) {
|
|
inClass = false;
|
|
continue;
|
|
}
|
|
|
|
if (line.find("[editable]") == std::string::npos) continue;
|
|
|
|
ScriptEditableFieldMeta meta;
|
|
meta.hasRange = false;
|
|
meta.rangeMin = 0.0f;
|
|
meta.rangeMax = 1.0f;
|
|
meta.group.clear();
|
|
meta.description.clear();
|
|
|
|
std::string rangeContent;
|
|
if (ExtractTagContent(line, "range", rangeContent)) {
|
|
float minVal = 0.0f;
|
|
float maxVal = 1.0f;
|
|
if (TryParseRange(rangeContent, minVal, maxVal)) {
|
|
meta.hasRange = true;
|
|
meta.rangeMin = minVal;
|
|
meta.rangeMax = maxVal;
|
|
}
|
|
}
|
|
|
|
std::string groupContent;
|
|
if (ExtractTagContent(line, "group", groupContent)) {
|
|
meta.group = UnquoteString(TrimString(groupContent));
|
|
}
|
|
|
|
std::string descContent;
|
|
if (ExtractTagContent(line, "desc", descContent)) {
|
|
meta.description = UnquoteString(TrimString(descContent));
|
|
}
|
|
|
|
std::string optionsContent;
|
|
if (ExtractTagContent(line, "options", optionsContent)) {
|
|
meta.options = SplitOptions(optionsContent);
|
|
}
|
|
|
|
size_t lastTag = line.find_last_of(']');
|
|
if (lastTag == std::string::npos) continue;
|
|
std::string declaration = TrimString(line.substr(lastTag + 1));
|
|
if (declaration.empty()) continue;
|
|
|
|
std::stringstream declStream(declaration);
|
|
declStream >> meta.type;
|
|
std::string nameToken;
|
|
declStream >> nameToken;
|
|
if (meta.type.empty() || nameToken.empty()) continue;
|
|
|
|
size_t equalPos = nameToken.find('=');
|
|
if (equalPos != std::string::npos) {
|
|
nameToken = nameToken.substr(0, equalPos);
|
|
}
|
|
if (!nameToken.empty() && nameToken.back() == ';') {
|
|
nameToken.pop_back();
|
|
}
|
|
meta.name = TrimString(nameToken);
|
|
if (!meta.name.empty()) {
|
|
cache.fields.push_back(meta);
|
|
}
|
|
}
|
|
|
|
return cache;
|
|
}
|
|
|
|
void GuiManager::RenderScriptEditableControls(entt::entity entity)
|
|
{
|
|
auto ®istry = app->GetRegistry();
|
|
auto &script = registry.get<ScriptComponent>(entity);
|
|
if (!script.enabled || script.scriptPath.empty() || !script.scriptInstance) return;
|
|
|
|
auto *obj = reinterpret_cast<asIScriptObject *>(script.scriptInstance);
|
|
if (!obj) return;
|
|
|
|
// If the script instance changed (hot reload or reassign), drop staged values
|
|
auto instIt = scriptEditableInstancePtr.find(entity);
|
|
if (instIt == scriptEditableInstancePtr.end() || instIt->second != script.scriptInstance) {
|
|
scriptEditableStaged.erase(entity);
|
|
scriptEditableInstancePtr[entity] = script.scriptInstance;
|
|
}
|
|
|
|
asITypeInfo *objType = obj->GetObjectType();
|
|
if (!objType) return;
|
|
asIScriptEngine *engine = obj->GetEngine();
|
|
if (!engine) return;
|
|
|
|
const char *className = objType->GetName();
|
|
if (!className) return;
|
|
|
|
auto &cache = GetOrParseEditableCache(script.scriptPath, className);
|
|
if (cache.fields.empty()) return;
|
|
|
|
ImGui::Separator();
|
|
ImGui::Text("Editable");
|
|
ImGui::PushID("ScriptEditable");
|
|
|
|
int floatTypeId = engine->GetTypeIdByDecl("float");
|
|
int intTypeId = engine->GetTypeIdByDecl("int");
|
|
int boolTypeId = engine->GetTypeIdByDecl("bool");
|
|
int stringTypeId = engine->GetTypeIdByDecl("string");
|
|
|
|
auto &stagedMap = scriptEditableStaged[entity];
|
|
|
|
std::string currentGroup;
|
|
bool groupOpen = true;
|
|
for (const auto &field : cache.fields) {
|
|
if (field.group != currentGroup) {
|
|
currentGroup = field.group;
|
|
if (!currentGroup.empty()) {
|
|
groupOpen = ImGui::CollapsingHeader(currentGroup.c_str(), ImGuiTreeNodeFlags_DefaultOpen);
|
|
} else {
|
|
groupOpen = true;
|
|
}
|
|
}
|
|
if (!groupOpen) {
|
|
continue;
|
|
}
|
|
int propertyIndex = -1;
|
|
int propertyTypeId = 0;
|
|
const int propertyCount = static_cast<int>(obj->GetPropertyCount());
|
|
for (int i = 0; i < propertyCount; ++i) {
|
|
const char *propName = obj->GetPropertyName(i);
|
|
if (propName && field.name == propName) {
|
|
propertyIndex = i;
|
|
propertyTypeId = obj->GetPropertyTypeId(i);
|
|
break;
|
|
}
|
|
}
|
|
if (propertyIndex < 0) continue;
|
|
|
|
void *propAddr = obj->GetAddressOfProperty(propertyIndex);
|
|
if (!propAddr) continue;
|
|
|
|
ScriptEditableValue &staged = stagedMap[field.name];
|
|
if (!staged.hasValue) {
|
|
if (propertyTypeId == floatTypeId) {
|
|
staged.kind = ScriptValueKind::Float;
|
|
staged.f = *reinterpret_cast<float *>(propAddr);
|
|
staged.hasValue = true;
|
|
staged.baselineF = staged.f;
|
|
staged.hasBaseline = true;
|
|
} else if (propertyTypeId == intTypeId) {
|
|
staged.kind = ScriptValueKind::Int;
|
|
staged.i = *reinterpret_cast<int *>(propAddr);
|
|
staged.hasValue = true;
|
|
staged.baselineI = staged.i;
|
|
staged.hasBaseline = true;
|
|
} else if (propertyTypeId == boolTypeId) {
|
|
staged.kind = ScriptValueKind::Bool;
|
|
staged.b = *reinterpret_cast<bool *>(propAddr);
|
|
staged.hasValue = true;
|
|
staged.baselineB = staged.b;
|
|
staged.hasBaseline = true;
|
|
} else if (propertyTypeId == stringTypeId) {
|
|
staged.kind = ScriptValueKind::String;
|
|
staged.s = *reinterpret_cast<std::string *>(propAddr);
|
|
staged.hasValue = true;
|
|
staged.baselineS = staged.s;
|
|
staged.hasBaseline = true;
|
|
} else {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
ImGui::PushID(field.name.c_str());
|
|
bool changed = false;
|
|
if (ImGui::SmallButton(ICON_FA_ROTATE_LEFT)) {
|
|
if (staged.kind == ScriptValueKind::Float && staged.hasBaseline) {
|
|
staged.f = staged.baselineF;
|
|
} else if (staged.kind == ScriptValueKind::Int && staged.hasBaseline) {
|
|
staged.i = staged.baselineI;
|
|
} else if (staged.kind == ScriptValueKind::Bool && staged.hasBaseline) {
|
|
staged.b = staged.baselineB;
|
|
} else if (staged.kind == ScriptValueKind::String && staged.hasBaseline) {
|
|
staged.s = staged.baselineS;
|
|
}
|
|
changed = true;
|
|
}
|
|
ImGui::SameLine();
|
|
|
|
if (staged.kind == ScriptValueKind::Float) {
|
|
if (!field.options.empty()) {
|
|
int currentIndex = 0;
|
|
float currentValue = staged.f;
|
|
int maxIndex = static_cast<int>(field.options.size()) - 1;
|
|
if (maxIndex < 0) maxIndex = 0;
|
|
currentIndex = static_cast<int>(currentValue);
|
|
currentIndex = std::clamp(currentIndex, 0, maxIndex);
|
|
if (ImGui::SliderInt(field.name.c_str(), ¤tIndex, 0, maxIndex)) {
|
|
staged.f = static_cast<float>(currentIndex);
|
|
changed = true;
|
|
}
|
|
} else if (field.hasRange) {
|
|
if (ImGui::SliderFloat(field.name.c_str(), &staged.f, field.rangeMin, field.rangeMax)) {
|
|
changed = true;
|
|
}
|
|
} else {
|
|
if (ImGui::DragFloat(field.name.c_str(), &staged.f, 0.1f)) {
|
|
changed = true;
|
|
}
|
|
}
|
|
} else if (staged.kind == ScriptValueKind::Int) {
|
|
if (!field.options.empty()) {
|
|
int currentIndex = staged.i;
|
|
int maxIndex = static_cast<int>(field.options.size()) - 1;
|
|
if (maxIndex < 0) maxIndex = 0;
|
|
currentIndex = std::clamp(currentIndex, 0, maxIndex);
|
|
if (ImGui::Combo(field.name.c_str(), ¤tIndex, [](void *data, int idx, const char **outText) {
|
|
auto *opts = reinterpret_cast<std::vector<std::string> *>(data);
|
|
if (idx < 0 || idx >= static_cast<int>(opts->size())) return false;
|
|
*outText = (*opts)[idx].c_str();
|
|
return true;
|
|
}, (void *)&field.options, static_cast<int>(field.options.size()))) {
|
|
staged.i = currentIndex;
|
|
changed = true;
|
|
}
|
|
} else if (field.hasRange) {
|
|
if (ImGui::SliderInt(field.name.c_str(), &staged.i, static_cast<int>(field.rangeMin), static_cast<int>(field.rangeMax))) {
|
|
changed = true;
|
|
}
|
|
} else {
|
|
if (ImGui::DragInt(field.name.c_str(), &staged.i, 1.0f)) {
|
|
changed = true;
|
|
}
|
|
}
|
|
} else if (staged.kind == ScriptValueKind::Bool) {
|
|
if (ImGui::Checkbox(field.name.c_str(), &staged.b)) {
|
|
changed = true;
|
|
}
|
|
} else if (staged.kind == ScriptValueKind::String) {
|
|
if (!field.options.empty()) {
|
|
int currentIndex = 0;
|
|
for (size_t i = 0; i < field.options.size(); ++i) {
|
|
if (field.options[i] == staged.s) {
|
|
currentIndex = static_cast<int>(i);
|
|
break;
|
|
}
|
|
}
|
|
if (ImGui::Combo(field.name.c_str(), ¤tIndex, [](void *data, int idx, const char **outText) {
|
|
auto *opts = reinterpret_cast<std::vector<std::string> *>(data);
|
|
if (idx < 0 || idx >= static_cast<int>(opts->size())) return false;
|
|
*outText = (*opts)[idx].c_str();
|
|
return true;
|
|
}, (void *)&field.options, static_cast<int>(field.options.size()))) {
|
|
if (currentIndex >= 0 && currentIndex < static_cast<int>(field.options.size())) {
|
|
staged.s = field.options[currentIndex];
|
|
changed = true;
|
|
}
|
|
}
|
|
} else {
|
|
char buffer[256];
|
|
std::snprintf(buffer, sizeof(buffer), "%s", staged.s.c_str());
|
|
if (ImGui::InputText(field.name.c_str(), buffer, sizeof(buffer))) {
|
|
staged.s = buffer;
|
|
changed = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!field.description.empty() && ImGui::IsItemHovered()) {
|
|
ImGui::BeginTooltip();
|
|
ImGui::TextUnformatted(field.description.c_str());
|
|
ImGui::EndTooltip();
|
|
}
|
|
|
|
if (changed) {
|
|
if (propertyTypeId == floatTypeId && staged.kind == ScriptValueKind::Float) {
|
|
*reinterpret_cast<float *>(propAddr) = staged.f;
|
|
script.scriptOverrides[field.name] = std::to_string(staged.f);
|
|
} else if (propertyTypeId == intTypeId && staged.kind == ScriptValueKind::Int) {
|
|
*reinterpret_cast<int *>(propAddr) = staged.i;
|
|
script.scriptOverrides[field.name] = std::to_string(staged.i);
|
|
} else if (propertyTypeId == boolTypeId && staged.kind == ScriptValueKind::Bool) {
|
|
*reinterpret_cast<bool *>(propAddr) = staged.b;
|
|
script.scriptOverrides[field.name] = staged.b ? "true" : "false";
|
|
} else if (propertyTypeId == stringTypeId && staged.kind == ScriptValueKind::String) {
|
|
*reinterpret_cast<std::string *>(propAddr) = staged.s;
|
|
script.scriptOverrides[field.name] = staged.s;
|
|
}
|
|
|
|
// If reset was clicked and we returned to baseline, clear overrides
|
|
if (staged.hasBaseline) {
|
|
bool atBaseline = false;
|
|
if (staged.kind == ScriptValueKind::Float) atBaseline = (staged.f == staged.baselineF);
|
|
else if (staged.kind == ScriptValueKind::Int) atBaseline = (staged.i == staged.baselineI);
|
|
else if (staged.kind == ScriptValueKind::Bool) atBaseline = (staged.b == staged.baselineB);
|
|
else if (staged.kind == ScriptValueKind::String) atBaseline = (staged.s == staged.baselineS);
|
|
if (atBaseline) {
|
|
script.scriptOverrides.erase(field.name);
|
|
}
|
|
}
|
|
}
|
|
ImGui::PopID();
|
|
}
|
|
ImGui::PopID();
|
|
}
|
|
|
|
void GuiManager::RenderErrorBanner()
|
|
{
|
|
if (!app || !app->HasScriptCompilationError())
|
|
return;
|
|
|
|
ImGui::SetNextWindowPos(ImVec2(0.0f, 0.0f));
|
|
ImGui::SetNextWindowSize(ImVec2(ImGui::GetIO().DisplaySize.x, 0.0f));
|
|
ImGuiWindowFlags flags = ImGuiWindowFlags_NoDecoration |
|
|
ImGuiWindowFlags_NoMove |
|
|
ImGuiWindowFlags_NoSavedSettings;
|
|
ImGui::Begin("PersistentBanner", nullptr, flags);
|
|
ImGui::TextColored(ImVec4(1, 0.4f, 0.4f, 1), ICON_FA_TRIANGLE_EXCLAMATION " Script Compilation Error - running last working version");
|
|
ImGui::End();
|
|
}
|
|
|
|
void GuiManager::RenderNotifications()
|
|
{
|
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.f);
|
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.f);
|
|
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.10f, 0.10f, 0.10f, 1.00f));
|
|
|
|
ImGui::RenderNotifications();
|
|
|
|
ImGui::PopStyleVar(2);
|
|
ImGui::PopStyleColor(1);
|
|
}
|
|
void GuiManager::SetTheme()
|
|
{
|
|
|
|
ImGuiIO &io = ImGui::GetIO();
|
|
|
|
io.Fonts->Clear();
|
|
|
|
// Don't clear; just add new fonts and rebuild
|
|
io.Fonts->AddFontFromFileTTF("fonts/Inter-Regular.ttf", 16); // index 0 (replaces default)
|
|
|
|
// Re-add FontAwesome as merge into default (index 0)
|
|
float iconFontSize = 16.0f * 2.0f / 3.0f;
|
|
static constexpr ImWchar iconsRanges[] = {ICON_MIN_FA, ICON_MAX_16_FA, 0};
|
|
ImFontConfig iconsConfig;
|
|
iconsConfig.MergeMode = true;
|
|
iconsConfig.PixelSnapH = true;
|
|
iconsConfig.GlyphMinAdvanceX = iconFontSize;
|
|
io.Fonts->AddFontFromMemoryCompressedTTF(fa_solid_900_compressed_data,
|
|
fa_solid_900_compressed_size,
|
|
iconFontSize, &iconsConfig, iconsRanges);
|
|
io.Fonts->AddFontFromFileTTF("fonts/JetBrainsMono-Regular.ttf", 16);
|
|
io.Fonts->Build();
|
|
|
|
ImVec4 *colors = ImGui::GetStyle().Colors;
|
|
colors[ImGuiCol_Text] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f);
|
|
colors[ImGuiCol_TextDisabled] = ImVec4(0.50f, 0.50f, 0.50f, 1.00f);
|
|
colors[ImGuiCol_WindowBg] = ImVec4(0.10f, 0.10f, 0.10f, 1.00f);
|
|
colors[ImGuiCol_ChildBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
|
|
colors[ImGuiCol_PopupBg] = ImVec4(0.19f, 0.19f, 0.19f, 0.92f);
|
|
colors[ImGuiCol_Border] = ImVec4(0.19f, 0.19f, 0.19f, 0.29f);
|
|
colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.24f);
|
|
colors[ImGuiCol_FrameBg] = ImVec4(0.05f, 0.05f, 0.05f, 0.54f);
|
|
colors[ImGuiCol_FrameBgHovered] = ImVec4(0.19f, 0.19f, 0.19f, 0.54f);
|
|
colors[ImGuiCol_FrameBgActive] = ImVec4(0.20f, 0.22f, 0.23f, 1.00f);
|
|
colors[ImGuiCol_TitleBg] = ImVec4(0.00f, 0.00f, 0.00f, 1.00f);
|
|
colors[ImGuiCol_TitleBgActive] = ImVec4(0.06f, 0.06f, 0.06f, 1.00f);
|
|
colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.00f, 0.00f, 0.00f, 1.00f);
|
|
colors[ImGuiCol_MenuBarBg] = ImVec4(0.14f, 0.14f, 0.14f, 1.00f);
|
|
colors[ImGuiCol_ScrollbarBg] = ImVec4(0.05f, 0.05f, 0.05f, 0.54f);
|
|
colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.34f, 0.34f, 0.34f, 0.54f);
|
|
colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.40f, 0.40f, 0.40f, 0.54f);
|
|
colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.56f, 0.56f, 0.56f, 0.54f);
|
|
colors[ImGuiCol_CheckMark] = ImVec4(0.33f, 0.67f, 0.86f, 1.00f);
|
|
colors[ImGuiCol_SliderGrab] = ImVec4(0.34f, 0.34f, 0.34f, 0.54f);
|
|
colors[ImGuiCol_SliderGrabActive] = ImVec4(0.56f, 0.56f, 0.56f, 0.54f);
|
|
colors[ImGuiCol_Button] = ImVec4(0.10f, 0.58f, 0.30f, 0.90f);
|
|
colors[ImGuiCol_ButtonHovered] = ImVec4(0.12f, 0.70f, 0.36f, 1.00f);
|
|
colors[ImGuiCol_ButtonActive] = ImVec4(0.08f, 0.46f, 0.24f, 1.00f);
|
|
colors[ImGuiCol_Header] = ImVec4(0.00f, 0.00f, 0.00f, 0.52f);
|
|
colors[ImGuiCol_HeaderHovered] = ImVec4(0.00f, 0.00f, 0.00f, 0.36f);
|
|
colors[ImGuiCol_HeaderActive] = ImVec4(0.20f, 0.22f, 0.23f, 0.33f);
|
|
colors[ImGuiCol_Separator] = ImVec4(0.28f, 0.28f, 0.28f, 0.29f);
|
|
colors[ImGuiCol_SeparatorHovered] = ImVec4(0.44f, 0.44f, 0.44f, 0.29f);
|
|
colors[ImGuiCol_SeparatorActive] = ImVec4(0.40f, 0.44f, 0.47f, 1.00f);
|
|
colors[ImGuiCol_ResizeGrip] = ImVec4(0.28f, 0.28f, 0.28f, 0.29f);
|
|
colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.44f, 0.44f, 0.44f, 0.29f);
|
|
colors[ImGuiCol_ResizeGripActive] = ImVec4(0.40f, 0.44f, 0.47f, 1.00f);
|
|
colors[ImGuiCol_Tab] = ImVec4(0.00f, 0.00f, 0.00f, 0.52f);
|
|
colors[ImGuiCol_TabHovered] = ImVec4(0.14f, 0.14f, 0.14f, 1.00f);
|
|
colors[ImGuiCol_TabActive] = ImVec4(0.20f, 0.20f, 0.20f, 0.36f);
|
|
colors[ImGuiCol_TabUnfocused] = ImVec4(0.00f, 0.00f, 0.00f, 0.52f);
|
|
colors[ImGuiCol_TabUnfocusedActive] = ImVec4(0.14f, 0.14f, 0.14f, 1.00f);
|
|
colors[ImGuiCol_DockingPreview] = ImVec4(0.33f, 0.67f, 0.86f, 1.00f);
|
|
colors[ImGuiCol_DockingEmptyBg] = ImVec4(1.00f, 0.00f, 0.00f, 1.00f);
|
|
colors[ImGuiCol_PlotLines] = ImVec4(1.00f, 0.00f, 0.00f, 1.00f);
|
|
colors[ImGuiCol_PlotLinesHovered] = ImVec4(1.00f, 0.00f, 0.00f, 1.00f);
|
|
colors[ImGuiCol_PlotHistogram] = ImVec4(1.00f, 0.00f, 0.00f, 1.00f);
|
|
colors[ImGuiCol_PlotHistogramHovered] = ImVec4(1.00f, 0.00f, 0.00f, 1.00f);
|
|
colors[ImGuiCol_TableHeaderBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.52f);
|
|
colors[ImGuiCol_TableBorderStrong] = ImVec4(0.00f, 0.00f, 0.00f, 0.52f);
|
|
colors[ImGuiCol_TableBorderLight] = ImVec4(0.28f, 0.28f, 0.28f, 0.29f);
|
|
colors[ImGuiCol_TableRowBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
|
|
colors[ImGuiCol_TableRowBgAlt] = ImVec4(1.00f, 1.00f, 1.00f, 0.06f);
|
|
colors[ImGuiCol_TextSelectedBg] = ImVec4(0.20f, 0.22f, 0.23f, 1.00f);
|
|
colors[ImGuiCol_DragDropTarget] = ImVec4(0.33f, 0.67f, 0.86f, 1.00f);
|
|
colors[ImGuiCol_NavHighlight] = ImVec4(1.00f, 0.00f, 0.00f, 1.00f);
|
|
colors[ImGuiCol_NavWindowingHighlight] = ImVec4(1.00f, 0.00f, 0.00f, 0.70f);
|
|
colors[ImGuiCol_NavWindowingDimBg] = ImVec4(1.00f, 0.00f, 0.00f, 0.20f);
|
|
colors[ImGuiCol_ModalWindowDimBg] = ImVec4(1.00f, 0.00f, 0.00f, 0.35f);
|
|
colors[ImGuiCol_CheckMark] = ImVec4(0.33f, 0.67f, 0.86f, 1.00f);
|
|
|
|
ImGuiStyle &style = ImGui::GetStyle();
|
|
style.WindowPadding = ImVec2(8.00f, 8.00f);
|
|
style.FramePadding = ImVec2(5.00f, 2.00f);
|
|
style.CellPadding = ImVec2(6.00f, 6.00f);
|
|
style.ItemSpacing = ImVec2(6.00f, 6.00f);
|
|
style.ItemInnerSpacing = ImVec2(6.00f, 6.00f);
|
|
style.TouchExtraPadding = ImVec2(0.00f, 0.00f);
|
|
style.IndentSpacing = 25;
|
|
style.ScrollbarSize = 15;
|
|
style.GrabMinSize = 10;
|
|
style.WindowBorderSize = 1;
|
|
style.ChildBorderSize = 1;
|
|
style.PopupBorderSize = 1;
|
|
style.FrameBorderSize = 1;
|
|
style.TabBorderSize = 1;
|
|
style.WindowRounding = 7;
|
|
style.ChildRounding = 4;
|
|
style.FrameRounding = 3;
|
|
style.PopupRounding = 4;
|
|
style.ScrollbarRounding = 9;
|
|
style.GrabRounding = 3;
|
|
style.LogSliderDeadzone = 4;
|
|
style.TabRounding = 4;
|
|
}
|