feat: update editor ui framework and assets
@@ -29,6 +29,7 @@ set(IMGUI_SOURCES
|
|||||||
)
|
)
|
||||||
|
|
||||||
add_executable(${PROJECT_NAME} WIN32
|
add_executable(${PROJECT_NAME} WIN32
|
||||||
|
src/EditorApp.rc
|
||||||
src/main.cpp
|
src/main.cpp
|
||||||
src/Application.cpp
|
src/Application.cpp
|
||||||
src/Theme.cpp
|
src/Theme.cpp
|
||||||
@@ -45,6 +46,7 @@ add_executable(${PROJECT_NAME} WIN32
|
|||||||
src/panels/InspectorPanel.cpp
|
src/panels/InspectorPanel.cpp
|
||||||
src/panels/ConsolePanel.cpp
|
src/panels/ConsolePanel.cpp
|
||||||
src/panels/ProjectPanel.cpp
|
src/panels/ProjectPanel.cpp
|
||||||
|
src/UI/BuiltInIcons.cpp
|
||||||
src/Layers/EditorLayer.cpp
|
src/Layers/EditorLayer.cpp
|
||||||
${IMGUI_SOURCES}
|
${IMGUI_SOURCES}
|
||||||
)
|
)
|
||||||
@@ -54,6 +56,7 @@ target_include_directories(${PROJECT_NAME} PRIVATE
|
|||||||
${imgui_SOURCE_DIR}
|
${imgui_SOURCE_DIR}
|
||||||
${imgui_SOURCE_DIR}/backends
|
${imgui_SOURCE_DIR}/backends
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/../engine/include
|
${CMAKE_CURRENT_SOURCE_DIR}/../engine/include
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/../engine/third_party/stb
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/../tests/OpenGL/package/glm
|
${CMAKE_CURRENT_SOURCE_DIR}/../tests/OpenGL/package/glm
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
BIN
editor/project.png
Normal file
|
After Width: | Height: | Size: 29 KiB |
28
editor/rename_color.py
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import os
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
|
||||||
|
def get_dominant_color(image_path):
|
||||||
|
img = Image.open(image_path).convert("RGB")
|
||||||
|
img = img.resize((1, 1), Image.Resampling.LANCZOS)
|
||||||
|
r, g, b = img.getpixel((0, 0))
|
||||||
|
return r, g, b
|
||||||
|
|
||||||
|
|
||||||
|
def rename_with_color(base_path):
|
||||||
|
files = ["color.png", "color2.png"]
|
||||||
|
for f in files:
|
||||||
|
old_path = os.path.join(base_path, f)
|
||||||
|
if os.path.exists(old_path):
|
||||||
|
r, g, b = get_dominant_color(old_path)
|
||||||
|
new_name = f"color-({r},{g},{b}).png"
|
||||||
|
new_path = os.path.join(base_path, new_name)
|
||||||
|
os.rename(old_path, new_path)
|
||||||
|
print(f"Renamed: {f} -> {new_name}")
|
||||||
|
else:
|
||||||
|
print(f"File not found: {old_path}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
base = r"D:\Xuanchi\Main\XCEngine\editor"
|
||||||
|
rename_with_color(base)
|
||||||
BIN
editor/resources/Icons/app.ico
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
editor/resources/Icons/folder_empty_icon.png
Normal file
|
After Width: | Height: | Size: 353 B |
BIN
editor/resources/Icons/folder_icon.png
Normal file
|
After Width: | Height: | Size: 270 B |
BIN
editor/resources/Icons/gameobject_icon.png
Normal file
|
After Width: | Height: | Size: 741 B |
BIN
editor/resources/Icons/logo_icon.png
Normal file
|
After Width: | Height: | Size: 926 B |
25
editor/resources/Icons/resize_png.py
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
from PIL import Image
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
def resize_png(input_path, output_path, size=(30, 30)):
|
||||||
|
img = Image.open(input_path)
|
||||||
|
img = img.resize(size, Image.Resampling.LANCZOS)
|
||||||
|
img.save(output_path, "PNG")
|
||||||
|
print(f"Resized {input_path} to {output_path}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
if len(sys.argv) < 2:
|
||||||
|
print("Usage: python resize_png.py <input.png> [output.png]")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
input_file = sys.argv[1]
|
||||||
|
output_file = (
|
||||||
|
sys.argv[2]
|
||||||
|
if len(sys.argv) > 2
|
||||||
|
else f"{os.path.splitext(input_file)[0]}_30x30.png"
|
||||||
|
)
|
||||||
|
|
||||||
|
resize_png(input_file, output_file)
|
||||||
|
Before Width: | Height: | Size: 6.5 KiB |
@@ -77,12 +77,11 @@ inline bool ExecuteOpenSelection(IEditorContext& context, const EditActionTarget
|
|||||||
inline bool ExecuteDeleteSelection(IEditorContext& context, const EditActionTarget& target) {
|
inline bool ExecuteDeleteSelection(IEditorContext& context, const EditActionTarget& target) {
|
||||||
if (target.route == EditorActionRoute::Project) {
|
if (target.route == EditorActionRoute::Project) {
|
||||||
auto& projectManager = context.GetProjectManager();
|
auto& projectManager = context.GetProjectManager();
|
||||||
if (!target.selectedAssetItem || projectManager.GetSelectedIndex() < 0) {
|
if (!target.selectedAssetItem) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Commands::DeleteAsset(projectManager, projectManager.GetSelectedIndex());
|
return Commands::DeleteAsset(projectManager, target.selectedAssetItem);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (target.route != EditorActionRoute::Hierarchy || !target.selectedGameObject) {
|
if (target.route != EditorActionRoute::Hierarchy || !target.selectedGameObject) {
|
||||||
|
|||||||
@@ -186,14 +186,7 @@ inline ::XCEngine::Components::GameObject* GetSelectedGameObject(IEditorContext&
|
|||||||
}
|
}
|
||||||
|
|
||||||
inline AssetItemPtr GetSelectedAssetItem(IEditorContext& context) {
|
inline AssetItemPtr GetSelectedAssetItem(IEditorContext& context) {
|
||||||
auto& projectManager = context.GetProjectManager();
|
return context.GetProjectManager().GetSelectedItem();
|
||||||
const int selectedIndex = projectManager.GetSelectedIndex();
|
|
||||||
auto& items = projectManager.GetCurrentItems();
|
|
||||||
if (selectedIndex < 0 || selectedIndex >= static_cast<int>(items.size())) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
return items[selectedIndex];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Actions
|
} // namespace Actions
|
||||||
|
|||||||
@@ -97,7 +97,6 @@ inline void DrawMainMenuBar(IEditorContext& context, UI::DeferredPopupState& abo
|
|||||||
UI::DrawMenuScope("Help", [&]() {
|
UI::DrawMenuScope("Help", [&]() {
|
||||||
DrawHelpMenuActions(aboutPopup);
|
DrawHelpMenuActions(aboutPopup);
|
||||||
});
|
});
|
||||||
UI::DrawSceneStatusWidget(context);
|
|
||||||
ImGui::EndMainMenuBar();
|
ImGui::EndMainMenuBar();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#include "Commands/ProjectCommands.h"
|
#include "Commands/ProjectCommands.h"
|
||||||
#include "Core/IEditorContext.h"
|
#include "Core/IEditorContext.h"
|
||||||
#include "Core/IProjectManager.h"
|
#include "Core/IProjectManager.h"
|
||||||
|
#include "UI/BuiltInIcons.h"
|
||||||
#include "UI/PopupState.h"
|
#include "UI/PopupState.h"
|
||||||
|
|
||||||
namespace XCEngine {
|
namespace XCEngine {
|
||||||
@@ -16,25 +17,6 @@ inline constexpr const char* ProjectAssetPayloadType() {
|
|||||||
|
|
||||||
inline void DrawProjectAssetContextActions(IEditorContext& context, const AssetItemPtr& item);
|
inline void DrawProjectAssetContextActions(IEditorContext& context, const AssetItemPtr& item);
|
||||||
|
|
||||||
inline int FindProjectItemIndex(IProjectManager& projectManager, const AssetItemPtr& item) {
|
|
||||||
if (!item) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto& items = projectManager.GetCurrentItems();
|
|
||||||
for (size_t i = 0; i < items.size(); ++i) {
|
|
||||||
if (items[i] == item) {
|
|
||||||
return static_cast<int>(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (items[i] && items[i]->fullPath == item->fullPath) {
|
|
||||||
return static_cast<int>(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline const char* GetDraggedProjectAssetPath() {
|
inline const char* GetDraggedProjectAssetPath() {
|
||||||
const ImGuiPayload* payload = ImGui::GetDragDropPayload();
|
const ImGuiPayload* payload = ImGui::GetDragDropPayload();
|
||||||
if (!payload || !payload->IsDataType(ProjectAssetPayloadType())) {
|
if (!payload || !payload->IsDataType(ProjectAssetPayloadType())) {
|
||||||
@@ -49,18 +31,20 @@ inline bool IsProjectAssetBeingDragged(const AssetItemPtr& item) {
|
|||||||
return item != nullptr && draggedPath != nullptr && item->fullPath == draggedPath;
|
return item != nullptr && draggedPath != nullptr && item->fullPath == draggedPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool AcceptProjectAssetDrop(IProjectManager& projectManager, const AssetItemPtr& targetFolder) {
|
inline std::string AcceptProjectAssetDropPayload(const AssetItemPtr& targetFolder) {
|
||||||
if (!targetFolder || !targetFolder->isFolder || !ImGui::BeginDragDropTarget()) {
|
if (!targetFolder || !targetFolder->isFolder || !ImGui::BeginDragDropTarget()) {
|
||||||
return false;
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
bool accepted = false;
|
std::string draggedPath;
|
||||||
if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload(ProjectAssetPayloadType())) {
|
if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload(ProjectAssetPayloadType())) {
|
||||||
const char* draggedPath = static_cast<const char*>(payload->Data);
|
const char* payloadPath = static_cast<const char*>(payload->Data);
|
||||||
accepted = Commands::MoveAssetToFolder(projectManager, draggedPath, targetFolder);
|
if (payloadPath) {
|
||||||
|
draggedPath = payloadPath;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ImGui::EndDragDropTarget();
|
ImGui::EndDragDropTarget();
|
||||||
return accepted;
|
return draggedPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool BeginProjectAssetDrag(const AssetItemPtr& item, UI::AssetIconKind iconKind) {
|
inline bool BeginProjectAssetDrag(const AssetItemPtr& item, UI::AssetIconKind iconKind) {
|
||||||
@@ -100,7 +84,7 @@ inline bool DrawProjectNavigateBackAction(IProjectManager& projectManager) {
|
|||||||
|
|
||||||
inline void HandleProjectBackgroundPrimaryClick(IProjectManager& projectManager) {
|
inline void HandleProjectBackgroundPrimaryClick(IProjectManager& projectManager) {
|
||||||
if (ImGui::IsWindowHovered() && ImGui::IsMouseClicked(0) && !ImGui::IsAnyItemHovered()) {
|
if (ImGui::IsWindowHovered() && ImGui::IsMouseClicked(0) && !ImGui::IsAnyItemHovered()) {
|
||||||
projectManager.SetSelectedIndex(-1);
|
projectManager.ClearSelection();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,16 +94,15 @@ inline void RequestProjectEmptyContextPopup(UI::DeferredPopupState& emptyContext
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void HandleProjectItemSelection(IProjectManager& projectManager, int index) {
|
inline void HandleProjectItemSelection(IProjectManager& projectManager, const AssetItemPtr& item) {
|
||||||
projectManager.SetSelectedIndex(index);
|
projectManager.SetSelectedItem(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void HandleProjectItemContextRequest(
|
inline void HandleProjectItemContextRequest(
|
||||||
IProjectManager& projectManager,
|
IProjectManager& projectManager,
|
||||||
int index,
|
|
||||||
const AssetItemPtr& item,
|
const AssetItemPtr& item,
|
||||||
UI::TargetedPopupState<AssetItemPtr>& itemContextMenu) {
|
UI::TargetedPopupState<AssetItemPtr>& itemContextMenu) {
|
||||||
projectManager.SetSelectedIndex(index);
|
projectManager.SetSelectedItem(item);
|
||||||
itemContextMenu.RequestOpen(item);
|
itemContextMenu.RequestOpen(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,14 +122,14 @@ inline void DrawProjectItemContextPopup(IEditorContext& context, UI::TargetedPop
|
|||||||
|
|
||||||
inline void DrawProjectAssetContextActions(IEditorContext& context, const AssetItemPtr& item) {
|
inline void DrawProjectAssetContextActions(IEditorContext& context, const AssetItemPtr& item) {
|
||||||
auto& projectManager = context.GetProjectManager();
|
auto& projectManager = context.GetProjectManager();
|
||||||
const int itemIndex = FindProjectItemIndex(projectManager, item);
|
const bool hasTarget = item != nullptr && !item->fullPath.empty();
|
||||||
|
|
||||||
DrawMenuAction(MakeOpenAssetAction(Commands::CanOpenAsset(item)), [&]() {
|
DrawMenuAction(MakeOpenAssetAction(Commands::CanOpenAsset(item)), [&]() {
|
||||||
Commands::OpenAsset(context, item);
|
Commands::OpenAsset(context, item);
|
||||||
});
|
});
|
||||||
DrawMenuSeparator();
|
DrawMenuSeparator();
|
||||||
DrawMenuAction(MakeDeleteAssetAction(itemIndex >= 0), [&]() {
|
DrawMenuAction(MakeDeleteAssetAction(hasTarget), [&]() {
|
||||||
Commands::DeleteAsset(projectManager, itemIndex);
|
Commands::DeleteAsset(projectManager, item);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#include "Core/EditorContext.h"
|
#include "Core/EditorContext.h"
|
||||||
#include "Core/EditorEvents.h"
|
#include "Core/EditorEvents.h"
|
||||||
#include "Core/EventBus.h"
|
#include "Core/EventBus.h"
|
||||||
|
#include "UI/BuiltInIcons.h"
|
||||||
#include "Platform/Win32Utf8.h"
|
#include "Platform/Win32Utf8.h"
|
||||||
#include "Platform/WindowsProcessDiagnostics.h"
|
#include "Platform/WindowsProcessDiagnostics.h"
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
@@ -18,7 +19,20 @@ Application& Application::Get() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool Application::InitializeWindowRenderer(HWND hwnd) {
|
bool Application::InitializeWindowRenderer(HWND hwnd) {
|
||||||
if (m_windowRenderer.Initialize(hwnd, 1280, 720)) {
|
RECT clientRect = {};
|
||||||
|
if (!GetClientRect(hwnd, &clientRect)) {
|
||||||
|
MessageBoxW(hwnd, L"Failed to query editor client area", L"Error", MB_OK | MB_ICONERROR);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int clientWidth = clientRect.right - clientRect.left;
|
||||||
|
const int clientHeight = clientRect.bottom - clientRect.top;
|
||||||
|
if (clientWidth <= 0 || clientHeight <= 0) {
|
||||||
|
MessageBoxW(hwnd, L"Editor client area is invalid", L"Error", MB_OK | MB_ICONERROR);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_windowRenderer.Initialize(hwnd, clientWidth, clientHeight)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,8 +52,20 @@ void Application::InitializeEditorContext(const std::string& projectPath) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Application::InitializeImGui(HWND hwnd) {
|
void Application::InitializeImGui(HWND hwnd) {
|
||||||
m_imguiSession.Initialize(m_editorContext->GetProjectPath());
|
m_imguiSession.Initialize(
|
||||||
m_imguiBackend.Initialize(hwnd, m_windowRenderer.GetDevice(), m_windowRenderer.GetSrvHeap());
|
m_editorContext->GetProjectPath(),
|
||||||
|
UI::ImGuiBackendBridge::GetDpiScaleForHwnd(hwnd));
|
||||||
|
m_imguiBackend.Initialize(
|
||||||
|
hwnd,
|
||||||
|
m_windowRenderer.GetDevice(),
|
||||||
|
m_windowRenderer.GetCommandQueue(),
|
||||||
|
m_windowRenderer.GetSrvHeap(),
|
||||||
|
m_windowRenderer.GetSrvDescriptorSize(),
|
||||||
|
m_windowRenderer.GetSrvDescriptorCount());
|
||||||
|
UI::InitializeBuiltInIcons(
|
||||||
|
m_imguiBackend,
|
||||||
|
m_windowRenderer.GetDevice(),
|
||||||
|
m_windowRenderer.GetCommandQueue());
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::AttachEditorLayer() {
|
void Application::AttachEditorLayer() {
|
||||||
@@ -65,7 +91,6 @@ void Application::ShutdownEditorContext() {
|
|||||||
|
|
||||||
void Application::RenderEditorFrame() {
|
void Application::RenderEditorFrame() {
|
||||||
static constexpr float kClearColor[4] = { 0.22f, 0.22f, 0.22f, 1.0f };
|
static constexpr float kClearColor[4] = { 0.22f, 0.22f, 0.22f, 1.0f };
|
||||||
|
|
||||||
m_imguiBackend.BeginFrame();
|
m_imguiBackend.BeginFrame();
|
||||||
m_layerStack.onImGuiRender();
|
m_layerStack.onImGuiRender();
|
||||||
UpdateWindowTitle();
|
UpdateWindowTitle();
|
||||||
@@ -89,11 +114,14 @@ bool Application::Initialize(HWND hwnd) {
|
|||||||
InitializeEditorContext(exeDir);
|
InitializeEditorContext(exeDir);
|
||||||
InitializeImGui(hwnd);
|
InitializeImGui(hwnd);
|
||||||
AttachEditorLayer();
|
AttachEditorLayer();
|
||||||
|
m_renderReady = true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::Shutdown() {
|
void Application::Shutdown() {
|
||||||
|
m_renderReady = false;
|
||||||
DetachEditorLayer();
|
DetachEditorLayer();
|
||||||
|
UI::ShutdownBuiltInIcons();
|
||||||
m_imguiBackend.Shutdown();
|
m_imguiBackend.Shutdown();
|
||||||
m_imguiSession.Shutdown();
|
m_imguiSession.Shutdown();
|
||||||
ShutdownEditorContext();
|
ShutdownEditorContext();
|
||||||
@@ -101,6 +129,9 @@ void Application::Shutdown() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Application::Render() {
|
void Application::Render() {
|
||||||
|
if (!m_renderReady) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
RenderEditorFrame();
|
RenderEditorFrame();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ public:
|
|||||||
void Shutdown();
|
void Shutdown();
|
||||||
void Render();
|
void Render();
|
||||||
void OnResize(int width, int height);
|
void OnResize(int width, int height);
|
||||||
|
bool IsRenderReady() const { return m_renderReady; }
|
||||||
HWND GetWindowHandle() const { return m_hwnd; }
|
HWND GetWindowHandle() const { return m_hwnd; }
|
||||||
|
|
||||||
IEditorContext& GetEditorContext() const { return *m_editorContext; }
|
IEditorContext& GetEditorContext() const { return *m_editorContext; }
|
||||||
@@ -50,6 +51,7 @@ private:
|
|||||||
UI::ImGuiSession m_imguiSession;
|
UI::ImGuiSession m_imguiSession;
|
||||||
uint64_t m_exitRequestedHandlerId = 0;
|
uint64_t m_exitRequestedHandlerId = 0;
|
||||||
std::wstring m_lastWindowTitle;
|
std::wstring m_lastWindowTitle;
|
||||||
|
bool m_renderReady = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,13 +37,20 @@ inline bool CreateFolder(IProjectManager& projectManager, const std::string& nam
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool DeleteAsset(IProjectManager& projectManager, int index) {
|
inline bool DeleteAsset(IProjectManager& projectManager, const std::string& fullPath) {
|
||||||
if (index < 0) {
|
if (fullPath.empty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
projectManager.DeleteItem(index);
|
return projectManager.DeleteItem(fullPath);
|
||||||
return true;
|
}
|
||||||
|
|
||||||
|
inline bool DeleteAsset(IProjectManager& projectManager, const AssetItemPtr& item) {
|
||||||
|
if (!item) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return DeleteAsset(projectManager, item->fullPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool MoveAssetToFolder(
|
inline bool MoveAssetToFolder(
|
||||||
|
|||||||
@@ -12,9 +12,16 @@ class IProjectManager {
|
|||||||
public:
|
public:
|
||||||
virtual ~IProjectManager() = default;
|
virtual ~IProjectManager() = default;
|
||||||
|
|
||||||
virtual std::vector<AssetItemPtr>& GetCurrentItems() = 0;
|
virtual const std::vector<AssetItemPtr>& GetCurrentItems() const = 0;
|
||||||
|
virtual AssetItemPtr GetRootFolder() const = 0;
|
||||||
|
virtual AssetItemPtr GetCurrentFolder() const = 0;
|
||||||
|
virtual AssetItemPtr GetSelectedItem() const = 0;
|
||||||
|
virtual const std::string& GetSelectedItemPath() const = 0;
|
||||||
virtual int GetSelectedIndex() const = 0;
|
virtual int GetSelectedIndex() const = 0;
|
||||||
virtual void SetSelectedIndex(int index) = 0;
|
virtual void SetSelectedIndex(int index) = 0;
|
||||||
|
virtual void SetSelectedItem(const AssetItemPtr& item) = 0;
|
||||||
|
virtual void ClearSelection() = 0;
|
||||||
|
virtual int FindCurrentItemIndex(const std::string& fullPath) const = 0;
|
||||||
|
|
||||||
virtual void NavigateToFolder(const AssetItemPtr& folder) = 0;
|
virtual void NavigateToFolder(const AssetItemPtr& folder) = 0;
|
||||||
virtual void NavigateBack() = 0;
|
virtual void NavigateBack() = 0;
|
||||||
@@ -29,11 +36,11 @@ public:
|
|||||||
virtual void RefreshCurrentFolder() = 0;
|
virtual void RefreshCurrentFolder() = 0;
|
||||||
|
|
||||||
virtual void CreateFolder(const std::string& name) = 0;
|
virtual void CreateFolder(const std::string& name) = 0;
|
||||||
virtual void DeleteItem(int index) = 0;
|
virtual bool DeleteItem(const std::string& fullPath) = 0;
|
||||||
virtual bool MoveItem(const std::string& sourceFullPath, const std::string& destFolderFullPath) = 0;
|
virtual bool MoveItem(const std::string& sourceFullPath, const std::string& destFolderFullPath) = 0;
|
||||||
|
|
||||||
virtual const std::string& GetProjectPath() const = 0;
|
virtual const std::string& GetProjectPath() const = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Editor
|
} // namespace Editor
|
||||||
} // namespace XCEngine
|
} // namespace XCEngine
|
||||||
|
|||||||
3
editor/src/EditorApp.rc
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
#include "EditorResources.h"
|
||||||
|
|
||||||
|
IDI_APP_ICON ICON "../resources/Icons/app.ico"
|
||||||
3
editor/src/EditorResources.h
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#define IDI_APP_ICON 101
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
#include "Core/EventBus.h"
|
#include "Core/EventBus.h"
|
||||||
#include "Core/IEditorContext.h"
|
#include "Core/IEditorContext.h"
|
||||||
#include "UI/DockHostStyle.h"
|
#include "UI/DockHostStyle.h"
|
||||||
|
#include "UI/DockTabBarChrome.h"
|
||||||
|
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
#include <imgui_internal.h>
|
#include <imgui_internal.h>
|
||||||
@@ -57,6 +58,7 @@ public:
|
|||||||
ImGui::PopStyleVar(2);
|
ImGui::PopStyleVar(2);
|
||||||
|
|
||||||
const ImGuiID dockspaceId = ImGui::GetID("MainDockspace.Root");
|
const ImGuiID dockspaceId = ImGui::GetID("MainDockspace.Root");
|
||||||
|
UI::ConfigureDockTabBarChrome(dockspaceId);
|
||||||
{
|
{
|
||||||
UI::DockHostStyleScope dockHostStyle;
|
UI::DockHostStyleScope dockHostStyle;
|
||||||
ImGui::DockSpace(dockspaceId, ImVec2(0.0f, 0.0f), m_dockspaceFlags);
|
ImGui::DockSpace(dockspaceId, ImVec2(0.0f, 0.0f), m_dockspaceFlags);
|
||||||
@@ -66,6 +68,7 @@ public:
|
|||||||
BuildDefaultLayout(dockspaceId, viewport->Size);
|
BuildDefaultLayout(dockspaceId, viewport->Size);
|
||||||
m_layoutDirty = false;
|
m_layoutDirty = false;
|
||||||
}
|
}
|
||||||
|
UI::ConfigureDockTabBarChrome(dockspaceId);
|
||||||
|
|
||||||
ImGui::End();
|
ImGui::End();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#include "ProjectManager.h"
|
#include "ProjectManager.h"
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <cwctype>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
|
|
||||||
@@ -9,23 +10,104 @@ namespace fs = std::filesystem;
|
|||||||
namespace XCEngine {
|
namespace XCEngine {
|
||||||
namespace Editor {
|
namespace Editor {
|
||||||
|
|
||||||
std::vector<AssetItemPtr>& ProjectManager::GetCurrentItems() {
|
namespace {
|
||||||
|
|
||||||
|
std::wstring MakePathKey(const fs::path& path) {
|
||||||
|
std::wstring key = path.lexically_normal().generic_wstring();
|
||||||
|
std::transform(key.begin(), key.end(), key.begin(), ::towlower);
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsSameOrDescendantPath(const fs::path& path, const fs::path& ancestor) {
|
||||||
|
const std::wstring pathKey = MakePathKey(path);
|
||||||
|
std::wstring ancestorKey = MakePathKey(ancestor);
|
||||||
|
if (pathKey.empty() || ancestorKey.empty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (pathKey == ancestorKey) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ancestorKey.back() != L'/') {
|
||||||
|
ancestorKey += L'/';
|
||||||
|
}
|
||||||
|
return pathKey.rfind(ancestorKey, 0) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
const std::vector<AssetItemPtr>& ProjectManager::GetCurrentItems() const {
|
||||||
if (m_path.empty()) {
|
if (m_path.empty()) {
|
||||||
static std::vector<AssetItemPtr> empty;
|
static const std::vector<AssetItemPtr> empty;
|
||||||
return empty;
|
return empty;
|
||||||
}
|
}
|
||||||
return m_path.back()->children;
|
return m_path.back()->children;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AssetItemPtr ProjectManager::GetSelectedItem() const {
|
||||||
|
return FindCurrentItemByPath(m_selectedItemPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
int ProjectManager::GetSelectedIndex() const {
|
||||||
|
return FindCurrentItemIndex(m_selectedItemPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProjectManager::SetSelectedIndex(int index) {
|
||||||
|
const auto& items = GetCurrentItems();
|
||||||
|
if (index < 0 || index >= static_cast<int>(items.size())) {
|
||||||
|
ClearSelection();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SetSelectedItem(items[index]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProjectManager::SetSelectedItem(const AssetItemPtr& item) {
|
||||||
|
if (!item) {
|
||||||
|
ClearSelection();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_selectedItemPath = item->fullPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProjectManager::ClearSelection() {
|
||||||
|
m_selectedItemPath.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
int ProjectManager::FindCurrentItemIndex(const std::string& fullPath) const {
|
||||||
|
if (fullPath.empty()) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& items = GetCurrentItems();
|
||||||
|
for (int i = 0; i < static_cast<int>(items.size()); ++i) {
|
||||||
|
if (items[i] && items[i]->fullPath == fullPath) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
void ProjectManager::NavigateToFolder(const AssetItemPtr& folder) {
|
void ProjectManager::NavigateToFolder(const AssetItemPtr& folder) {
|
||||||
m_path.push_back(folder);
|
if (!folder || !folder->isFolder || !m_rootFolder) {
|
||||||
m_selectedIndex = -1;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<AssetItemPtr> resolvedPath;
|
||||||
|
if (!BuildPathToFolder(m_rootFolder, folder->fullPath, resolvedPath)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_path = std::move(resolvedPath);
|
||||||
|
ClearSelection();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ProjectManager::NavigateBack() {
|
void ProjectManager::NavigateBack() {
|
||||||
if (m_path.size() > 1) {
|
if (m_path.size() > 1) {
|
||||||
m_path.pop_back();
|
m_path.pop_back();
|
||||||
m_selectedIndex = -1;
|
ClearSelection();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,7 +116,7 @@ void ProjectManager::NavigateToIndex(size_t index) {
|
|||||||
while (m_path.size() > index + 1) {
|
while (m_path.size() > index + 1) {
|
||||||
m_path.pop_back();
|
m_path.pop_back();
|
||||||
}
|
}
|
||||||
m_selectedIndex = -1;
|
ClearSelection();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string ProjectManager::GetCurrentPath() const {
|
std::string ProjectManager::GetCurrentPath() const {
|
||||||
@@ -79,16 +161,7 @@ void ProjectManager::Initialize(const std::string& projectPath) {
|
|||||||
try {
|
try {
|
||||||
if (!fs::exists(assetsPath)) {
|
if (!fs::exists(assetsPath)) {
|
||||||
fs::create_directories(assetsPath);
|
fs::create_directories(assetsPath);
|
||||||
fs::create_directories(assetsPath / L"Textures");
|
|
||||||
fs::create_directories(assetsPath / L"Models");
|
|
||||||
fs::create_directories(assetsPath / L"Scripts");
|
|
||||||
fs::create_directories(assetsPath / L"Materials");
|
|
||||||
fs::create_directories(assetsPath / L"Scenes");
|
fs::create_directories(assetsPath / L"Scenes");
|
||||||
|
|
||||||
std::ofstream((assetsPath / L"Textures" / L"Grass.png").wstring());
|
|
||||||
std::ofstream((assetsPath / L"Textures" / L"Stone.png").wstring());
|
|
||||||
std::ofstream((assetsPath / L"Models" / L"Character.fbx").wstring());
|
|
||||||
std::ofstream((assetsPath / L"Scripts" / L"PlayerController.cs").wstring());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
m_rootFolder = ScanDirectory(assetsPath.wstring());
|
m_rootFolder = ScanDirectory(assetsPath.wstring());
|
||||||
@@ -97,32 +170,32 @@ void ProjectManager::Initialize(const std::string& projectPath) {
|
|||||||
|
|
||||||
m_path.clear();
|
m_path.clear();
|
||||||
m_path.push_back(m_rootFolder);
|
m_path.push_back(m_rootFolder);
|
||||||
m_selectedIndex = -1;
|
ClearSelection();
|
||||||
} catch (const std::exception& e) {
|
} catch (...) {
|
||||||
m_rootFolder = std::make_shared<AssetItem>();
|
m_rootFolder = std::make_shared<AssetItem>();
|
||||||
m_rootFolder->name = "Assets";
|
m_rootFolder->name = "Assets";
|
||||||
m_rootFolder->isFolder = true;
|
m_rootFolder->isFolder = true;
|
||||||
m_rootFolder->type = "Folder";
|
m_rootFolder->type = "Folder";
|
||||||
|
m_rootFolder->fullPath = WstringToUtf8(assetsPath.wstring());
|
||||||
|
m_path.clear();
|
||||||
m_path.push_back(m_rootFolder);
|
m_path.push_back(m_rootFolder);
|
||||||
|
ClearSelection();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::wstring ProjectManager::GetCurrentFullPathW() const {
|
std::wstring ProjectManager::GetCurrentFullPathW() const {
|
||||||
if (m_path.empty()) return Utf8ToWstring(m_projectPath);
|
if (AssetItemPtr currentFolder = GetCurrentFolder()) {
|
||||||
|
return Utf8ToWstring(currentFolder->fullPath);
|
||||||
std::wstring fullPath = Utf8ToWstring(m_projectPath);
|
|
||||||
for (size_t i = 0; i < m_path.size(); i++) {
|
|
||||||
fullPath += L"/" + Utf8ToWstring(m_path[i]->name);
|
|
||||||
}
|
}
|
||||||
return fullPath;
|
|
||||||
|
return Utf8ToWstring(m_projectPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ProjectManager::RefreshCurrentFolder() {
|
void ProjectManager::RefreshCurrentFolder() {
|
||||||
if (m_path.empty()) return;
|
if (m_path.empty()) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
auto newFolder = ScanDirectory(GetCurrentFullPathW());
|
RebuildTreePreservingPath();
|
||||||
m_path.back()->children = newFolder->children;
|
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -137,34 +210,60 @@ void ProjectManager::CreateFolder(const std::string& name) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ProjectManager::DeleteItem(int index) {
|
bool ProjectManager::DeleteItem(const std::string& fullPath) {
|
||||||
if (m_path.empty()) return;
|
if (fullPath.empty() || !m_rootFolder) {
|
||||||
auto& items = m_path.back()->children;
|
return false;
|
||||||
if (index < 0 || index >= (int)items.size()) return;
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
std::wstring fullPath = GetCurrentFullPathW();
|
const fs::path itemPath = Utf8ToWstring(fullPath);
|
||||||
fs::path itemPath = fs::path(fullPath) / Utf8ToWstring(items[index]->name);
|
const fs::path rootPath = Utf8ToWstring(m_rootFolder->fullPath);
|
||||||
|
if (!fs::exists(itemPath) || !IsSameOrDescendantPath(itemPath, rootPath)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (MakePathKey(itemPath) == MakePathKey(rootPath)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
fs::remove_all(itemPath);
|
fs::remove_all(itemPath);
|
||||||
m_selectedIndex = -1;
|
if (m_selectedItemPath == fullPath) {
|
||||||
|
ClearSelection();
|
||||||
|
}
|
||||||
RefreshCurrentFolder();
|
RefreshCurrentFolder();
|
||||||
|
return true;
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ProjectManager::MoveItem(const std::string& sourceFullPath, const std::string& destFolderFullPath) {
|
bool ProjectManager::MoveItem(const std::string& sourceFullPath, const std::string& destFolderFullPath) {
|
||||||
|
if (sourceFullPath.empty() || destFolderFullPath.empty() || !m_rootFolder) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
fs::path sourcePath = Utf8ToWstring(sourceFullPath);
|
const fs::path sourcePath = Utf8ToWstring(sourceFullPath);
|
||||||
fs::path destPath = fs::path(Utf8ToWstring(destFolderFullPath)) / sourcePath.filename();
|
const fs::path destFolderPath = Utf8ToWstring(destFolderFullPath);
|
||||||
|
const fs::path rootPath = Utf8ToWstring(m_rootFolder->fullPath);
|
||||||
if (!fs::exists(sourcePath)) {
|
|
||||||
|
if (!fs::exists(sourcePath) || !fs::exists(destFolderPath) || !fs::is_directory(destFolderPath)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (!IsSameOrDescendantPath(sourcePath, rootPath) || !IsSameOrDescendantPath(destFolderPath, rootPath)) {
|
||||||
if (fs::exists(destPath)) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (MakePathKey(sourcePath) == MakePathKey(rootPath)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (fs::is_directory(sourcePath) && IsSameOrDescendantPath(destFolderPath, sourcePath)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fs::path destPath = destFolderPath / sourcePath.filename();
|
||||||
|
if (MakePathKey(destPath) == MakePathKey(sourcePath) || fs::exists(destPath)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
fs::rename(sourcePath, destPath);
|
fs::rename(sourcePath, destPath);
|
||||||
RefreshCurrentFolder();
|
RefreshCurrentFolder();
|
||||||
return true;
|
return true;
|
||||||
@@ -173,11 +272,27 @@ bool ProjectManager::MoveItem(const std::string& sourceFullPath, const std::stri
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AssetItemPtr ProjectManager::FindCurrentItemByPath(const std::string& fullPath) const {
|
||||||
|
const int index = FindCurrentItemIndex(fullPath);
|
||||||
|
if (index < 0) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return GetCurrentItems()[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProjectManager::SyncSelection() {
|
||||||
|
if (!m_selectedItemPath.empty() && !FindCurrentItemByPath(m_selectedItemPath)) {
|
||||||
|
ClearSelection();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
AssetItemPtr ProjectManager::ScanDirectory(const std::wstring& path) {
|
AssetItemPtr ProjectManager::ScanDirectory(const std::wstring& path) {
|
||||||
auto folder = std::make_shared<AssetItem>();
|
auto folder = std::make_shared<AssetItem>();
|
||||||
folder->name = WstringToUtf8(fs::path(path).filename().wstring());
|
folder->name = WstringToUtf8(fs::path(path).filename().wstring());
|
||||||
folder->isFolder = true;
|
folder->isFolder = true;
|
||||||
folder->type = "Folder";
|
folder->type = "Folder";
|
||||||
|
folder->fullPath = WstringToUtf8(path);
|
||||||
|
|
||||||
if (!fs::exists(path)) return folder;
|
if (!fs::exists(path)) return folder;
|
||||||
|
|
||||||
@@ -201,6 +316,61 @@ AssetItemPtr ProjectManager::ScanDirectory(const std::wstring& path) {
|
|||||||
return folder;
|
return folder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ProjectManager::BuildPathToFolder(
|
||||||
|
const AssetItemPtr& current,
|
||||||
|
const std::string& fullPath,
|
||||||
|
std::vector<AssetItemPtr>& outPath) const {
|
||||||
|
if (!current || !current->isFolder) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
outPath.push_back(current);
|
||||||
|
if (current->fullPath == fullPath) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& child : current->children) {
|
||||||
|
if (!child || !child->isFolder) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BuildPathToFolder(child, fullPath, outPath)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
outPath.pop_back();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProjectManager::RebuildTreePreservingPath() {
|
||||||
|
std::vector<std::string> preservedPaths;
|
||||||
|
preservedPaths.reserve(m_path.size());
|
||||||
|
for (const auto& folder : m_path) {
|
||||||
|
if (folder && folder->isFolder) {
|
||||||
|
preservedPaths.push_back(folder->fullPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const fs::path assetsPath = fs::path(Utf8ToWstring(m_projectPath)) / L"Assets";
|
||||||
|
m_rootFolder = ScanDirectory(assetsPath.wstring());
|
||||||
|
m_rootFolder->name = "Assets";
|
||||||
|
m_rootFolder->fullPath = WstringToUtf8(assetsPath.wstring());
|
||||||
|
|
||||||
|
m_path.clear();
|
||||||
|
m_path.push_back(m_rootFolder);
|
||||||
|
|
||||||
|
for (size_t i = 1; i < preservedPaths.size(); ++i) {
|
||||||
|
std::vector<AssetItemPtr> resolvedPath;
|
||||||
|
if (!BuildPathToFolder(m_rootFolder, preservedPaths[i], resolvedPath)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
m_path = std::move(resolvedPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
SyncSelection();
|
||||||
|
}
|
||||||
|
|
||||||
AssetItemPtr ProjectManager::CreateAssetItem(const std::wstring& path, const std::wstring& nameW, bool isFolder) {
|
AssetItemPtr ProjectManager::CreateAssetItem(const std::wstring& path, const std::wstring& nameW, bool isFolder) {
|
||||||
auto item = std::make_shared<AssetItem>();
|
auto item = std::make_shared<AssetItem>();
|
||||||
item->name = WstringToUtf8(nameW);
|
item->name = WstringToUtf8(nameW);
|
||||||
|
|||||||
@@ -11,9 +11,16 @@ namespace Editor {
|
|||||||
|
|
||||||
class ProjectManager : public IProjectManager {
|
class ProjectManager : public IProjectManager {
|
||||||
public:
|
public:
|
||||||
std::vector<AssetItemPtr>& GetCurrentItems() override;
|
const std::vector<AssetItemPtr>& GetCurrentItems() const override;
|
||||||
int GetSelectedIndex() const override { return m_selectedIndex; }
|
AssetItemPtr GetRootFolder() const override { return m_rootFolder; }
|
||||||
void SetSelectedIndex(int index) override { m_selectedIndex = index; }
|
AssetItemPtr GetCurrentFolder() const override { return m_path.empty() ? nullptr : m_path.back(); }
|
||||||
|
AssetItemPtr GetSelectedItem() const override;
|
||||||
|
const std::string& GetSelectedItemPath() const override { return m_selectedItemPath; }
|
||||||
|
int GetSelectedIndex() const override;
|
||||||
|
void SetSelectedIndex(int index) override;
|
||||||
|
void SetSelectedItem(const AssetItemPtr& item) override;
|
||||||
|
void ClearSelection() override;
|
||||||
|
int FindCurrentItemIndex(const std::string& fullPath) const override;
|
||||||
|
|
||||||
void NavigateToFolder(const AssetItemPtr& folder) override;
|
void NavigateToFolder(const AssetItemPtr& folder) override;
|
||||||
void NavigateBack() override;
|
void NavigateBack() override;
|
||||||
@@ -28,21 +35,25 @@ public:
|
|||||||
void RefreshCurrentFolder() override;
|
void RefreshCurrentFolder() override;
|
||||||
|
|
||||||
void CreateFolder(const std::string& name) override;
|
void CreateFolder(const std::string& name) override;
|
||||||
void DeleteItem(int index) override;
|
bool DeleteItem(const std::string& fullPath) override;
|
||||||
bool MoveItem(const std::string& sourceFullPath, const std::string& destFolderFullPath) override;
|
bool MoveItem(const std::string& sourceFullPath, const std::string& destFolderFullPath) override;
|
||||||
|
|
||||||
const std::string& GetProjectPath() const override { return m_projectPath; }
|
const std::string& GetProjectPath() const override { return m_projectPath; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
bool BuildPathToFolder(const AssetItemPtr& current, const std::string& fullPath, std::vector<AssetItemPtr>& outPath) const;
|
||||||
|
void RebuildTreePreservingPath();
|
||||||
|
AssetItemPtr FindCurrentItemByPath(const std::string& fullPath) const;
|
||||||
|
void SyncSelection();
|
||||||
AssetItemPtr ScanDirectory(const std::wstring& path);
|
AssetItemPtr ScanDirectory(const std::wstring& path);
|
||||||
AssetItemPtr CreateAssetItem(const std::wstring& path, const std::wstring& nameW, bool isFolder);
|
AssetItemPtr CreateAssetItem(const std::wstring& path, const std::wstring& nameW, bool isFolder);
|
||||||
std::wstring GetCurrentFullPathW() const;
|
std::wstring GetCurrentFullPathW() const;
|
||||||
|
|
||||||
AssetItemPtr m_rootFolder;
|
AssetItemPtr m_rootFolder;
|
||||||
std::vector<AssetItemPtr> m_path;
|
std::vector<AssetItemPtr> m_path;
|
||||||
int m_selectedIndex = -1;
|
std::string m_selectedItemPath;
|
||||||
std::string m_projectPath;
|
std::string m_projectPath;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
#include <d3d12.h>
|
#include <d3d12.h>
|
||||||
#include <dxgi1_6.h>
|
#include <dxgi1_6.h>
|
||||||
|
#include <array>
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
|
|
||||||
namespace XCEngine {
|
namespace XCEngine {
|
||||||
@@ -12,6 +13,8 @@ namespace Platform {
|
|||||||
|
|
||||||
class D3D12WindowRenderer {
|
class D3D12WindowRenderer {
|
||||||
public:
|
public:
|
||||||
|
static constexpr UINT kSrvDescriptorCount = 64;
|
||||||
|
|
||||||
bool Initialize(HWND hwnd, int width, int height) {
|
bool Initialize(HWND hwnd, int width, int height) {
|
||||||
m_hwnd = hwnd;
|
m_hwnd = hwnd;
|
||||||
m_width = width;
|
m_width = width;
|
||||||
@@ -36,7 +39,9 @@ public:
|
|||||||
m_height = 720;
|
m_height = 720;
|
||||||
m_fenceValue = 0;
|
m_fenceValue = 0;
|
||||||
m_rtvDescriptorSize = 0;
|
m_rtvDescriptorSize = 0;
|
||||||
|
m_srvDescriptorSize = 0;
|
||||||
m_frameIndex = 0;
|
m_frameIndex = 0;
|
||||||
|
m_srvDescriptorUsage.fill(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Resize(int width, int height) {
|
void Resize(int width, int height) {
|
||||||
@@ -107,6 +112,18 @@ public:
|
|||||||
return m_srvHeap;
|
return m_srvHeap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ID3D12CommandQueue* GetCommandQueue() const {
|
||||||
|
return m_commandQueue;
|
||||||
|
}
|
||||||
|
|
||||||
|
UINT GetSrvDescriptorSize() const {
|
||||||
|
return m_srvDescriptorSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
UINT GetSrvDescriptorCount() const {
|
||||||
|
return kSrvDescriptorCount;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool CreateDevice() {
|
bool CreateDevice() {
|
||||||
HRESULT hr = D3D12CreateDevice(nullptr, D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&m_device));
|
HRESULT hr = D3D12CreateDevice(nullptr, D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&m_device));
|
||||||
@@ -164,10 +181,12 @@ private:
|
|||||||
|
|
||||||
D3D12_DESCRIPTOR_HEAP_DESC srvDesc = {};
|
D3D12_DESCRIPTOR_HEAP_DESC srvDesc = {};
|
||||||
srvDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
|
srvDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
|
||||||
srvDesc.NumDescriptors = 1;
|
srvDesc.NumDescriptors = kSrvDescriptorCount;
|
||||||
srvDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
|
srvDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
|
||||||
hr = m_device->CreateDescriptorHeap(&srvDesc, IID_PPV_ARGS(&m_srvHeap));
|
hr = m_device->CreateDescriptorHeap(&srvDesc, IID_PPV_ARGS(&m_srvHeap));
|
||||||
if (FAILED(hr)) return false;
|
if (FAILED(hr)) return false;
|
||||||
|
m_srvDescriptorSize = m_device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
|
||||||
|
m_srvDescriptorUsage.fill(false);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -210,7 +229,9 @@ private:
|
|||||||
ID3D12Fence* m_fence = nullptr;
|
ID3D12Fence* m_fence = nullptr;
|
||||||
UINT64 m_fenceValue = 0;
|
UINT64 m_fenceValue = 0;
|
||||||
UINT m_rtvDescriptorSize = 0;
|
UINT m_rtvDescriptorSize = 0;
|
||||||
|
UINT m_srvDescriptorSize = 0;
|
||||||
UINT m_frameIndex = 0;
|
UINT m_frameIndex = 0;
|
||||||
|
std::array<bool, kSrvDescriptorCount> m_srvDescriptorUsage = {};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Platform
|
} // namespace Platform
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "Application.h"
|
#include "Application.h"
|
||||||
|
#include "EditorResources.h"
|
||||||
#include "UI/ImGuiBackendBridge.h"
|
#include "UI/ImGuiBackendBridge.h"
|
||||||
|
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
@@ -18,8 +19,22 @@ inline LRESULT WINAPI EditorWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM l
|
|||||||
case WM_SIZE:
|
case WM_SIZE:
|
||||||
if (wParam != SIZE_MINIMIZED) {
|
if (wParam != SIZE_MINIMIZED) {
|
||||||
Application::Get().OnResize(static_cast<int>(LOWORD(lParam)), static_cast<int>(HIWORD(lParam)));
|
Application::Get().OnResize(static_cast<int>(LOWORD(lParam)), static_cast<int>(HIWORD(lParam)));
|
||||||
|
if (Application::Get().IsRenderReady()) {
|
||||||
|
Application::Get().Render();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
|
case WM_PAINT:
|
||||||
|
if (Application::Get().IsRenderReady()) {
|
||||||
|
PAINTSTRUCT ps = {};
|
||||||
|
BeginPaint(hWnd, &ps);
|
||||||
|
Application::Get().Render();
|
||||||
|
EndPaint(hWnd, &ps);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case WM_ERASEBKGND:
|
||||||
|
return 1;
|
||||||
case WM_SYSCOMMAND:
|
case WM_SYSCOMMAND:
|
||||||
if ((wParam & 0xfff0) == SC_KEYMENU) {
|
if ((wParam & 0xfff0) == SC_KEYMENU) {
|
||||||
return 0;
|
return 0;
|
||||||
@@ -34,11 +49,21 @@ inline LRESULT WINAPI EditorWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM l
|
|||||||
}
|
}
|
||||||
|
|
||||||
inline int RunEditor(HINSTANCE hInstance, int nCmdShow) {
|
inline int RunEditor(HINSTANCE hInstance, int nCmdShow) {
|
||||||
|
UI::ImGuiBackendBridge::EnableDpiAwareness();
|
||||||
|
|
||||||
WNDCLASSEXW wc = {};
|
WNDCLASSEXW wc = {};
|
||||||
wc.cbSize = sizeof(wc);
|
wc.cbSize = sizeof(wc);
|
||||||
wc.style = CS_CLASSDC;
|
wc.style = CS_CLASSDC;
|
||||||
wc.lpfnWndProc = EditorWndProc;
|
wc.lpfnWndProc = EditorWndProc;
|
||||||
wc.hInstance = hInstance;
|
wc.hInstance = hInstance;
|
||||||
|
wc.hIcon = static_cast<HICON>(LoadImageW(hInstance, MAKEINTRESOURCEW(IDI_APP_ICON), IMAGE_ICON, 0, 0, LR_DEFAULTSIZE));
|
||||||
|
wc.hIconSm = static_cast<HICON>(LoadImageW(
|
||||||
|
hInstance,
|
||||||
|
MAKEINTRESOURCEW(IDI_APP_ICON),
|
||||||
|
IMAGE_ICON,
|
||||||
|
GetSystemMetrics(SM_CXSMICON),
|
||||||
|
GetSystemMetrics(SM_CYSMICON),
|
||||||
|
LR_DEFAULTCOLOR));
|
||||||
wc.lpszClassName = L"XCVolumeRendererUI2";
|
wc.lpszClassName = L"XCVolumeRendererUI2";
|
||||||
|
|
||||||
if (!RegisterClassExW(&wc)) {
|
if (!RegisterClassExW(&wc)) {
|
||||||
@@ -64,6 +89,13 @@ inline int RunEditor(HINSTANCE hInstance, int nCmdShow) {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (wc.hIcon) {
|
||||||
|
SendMessageW(hwnd, WM_SETICON, ICON_BIG, reinterpret_cast<LPARAM>(wc.hIcon));
|
||||||
|
}
|
||||||
|
if (wc.hIconSm) {
|
||||||
|
SendMessageW(hwnd, WM_SETICON, ICON_SMALL, reinterpret_cast<LPARAM>(wc.hIconSm));
|
||||||
|
}
|
||||||
|
|
||||||
ShowWindow(hwnd, nCmdShow);
|
ShowWindow(hwnd, nCmdShow);
|
||||||
UpdateWindow(hwnd);
|
UpdateWindow(hwnd);
|
||||||
|
|
||||||
|
|||||||
375
editor/src/UI/BuiltInIcons.cpp
Normal file
@@ -0,0 +1,375 @@
|
|||||||
|
#include "BuiltInIcons.h"
|
||||||
|
|
||||||
|
#include "ImGuiBackendBridge.h"
|
||||||
|
#include "Platform/Win32Utf8.h"
|
||||||
|
#include "StyleTokens.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstring>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <string>
|
||||||
|
#include <wrl/client.h>
|
||||||
|
|
||||||
|
#include <stb_image.h>
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace Editor {
|
||||||
|
namespace UI {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
using Microsoft::WRL::ComPtr;
|
||||||
|
|
||||||
|
struct BuiltInTexture {
|
||||||
|
ImTextureID textureId = {};
|
||||||
|
D3D12_CPU_DESCRIPTOR_HANDLE cpuHandle = {};
|
||||||
|
D3D12_GPU_DESCRIPTOR_HANDLE gpuHandle = {};
|
||||||
|
ComPtr<ID3D12Resource> texture;
|
||||||
|
int width = 0;
|
||||||
|
int height = 0;
|
||||||
|
|
||||||
|
bool IsValid() const {
|
||||||
|
return textureId != ImTextureID{} && texture != nullptr && width > 0 && height > 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct BuiltInIconState {
|
||||||
|
ImGuiBackendBridge* backend = nullptr;
|
||||||
|
BuiltInTexture folder;
|
||||||
|
BuiltInTexture gameObject;
|
||||||
|
};
|
||||||
|
|
||||||
|
BuiltInIconState g_icons;
|
||||||
|
|
||||||
|
std::filesystem::path ResolveFolderIconPath() {
|
||||||
|
const std::filesystem::path exeDir(Platform::GetExecutableDirectoryUtf8());
|
||||||
|
return (exeDir / ".." / ".." / "resources" / "Icons" / "folder_icon.png").lexically_normal();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::filesystem::path ResolveGameObjectIconPath() {
|
||||||
|
const std::filesystem::path exeDir(Platform::GetExecutableDirectoryUtf8());
|
||||||
|
return (exeDir / ".." / ".." / "resources" / "Icons" / "gameobject_icon.png").lexically_normal();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResetTexture(BuiltInTexture& texture) {
|
||||||
|
if (g_icons.backend && texture.cpuHandle.ptr != 0) {
|
||||||
|
g_icons.backend->FreeTextureDescriptor(texture.cpuHandle, texture.gpuHandle);
|
||||||
|
}
|
||||||
|
|
||||||
|
texture.texture.Reset();
|
||||||
|
texture.textureId = {};
|
||||||
|
texture.cpuHandle = {};
|
||||||
|
texture.gpuHandle = {};
|
||||||
|
texture.width = 0;
|
||||||
|
texture.height = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WaitForQueueIdle(ID3D12Device* device, ID3D12CommandQueue* commandQueue) {
|
||||||
|
if (!device || !commandQueue) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ComPtr<ID3D12Fence> fence;
|
||||||
|
if (FAILED(device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&fence)))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
HANDLE eventHandle = CreateEventW(nullptr, FALSE, FALSE, nullptr);
|
||||||
|
if (!eventHandle) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr UINT64 kFenceValue = 1;
|
||||||
|
const HRESULT signalHr = commandQueue->Signal(fence.Get(), kFenceValue);
|
||||||
|
if (FAILED(signalHr)) {
|
||||||
|
CloseHandle(eventHandle);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fence->GetCompletedValue() < kFenceValue) {
|
||||||
|
if (FAILED(fence->SetEventOnCompletion(kFenceValue, eventHandle))) {
|
||||||
|
CloseHandle(eventHandle);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
WaitForSingleObject(eventHandle, INFINITE);
|
||||||
|
}
|
||||||
|
|
||||||
|
CloseHandle(eventHandle);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LoadTextureFromFile(
|
||||||
|
ImGuiBackendBridge& backend,
|
||||||
|
ID3D12Device* device,
|
||||||
|
ID3D12CommandQueue* commandQueue,
|
||||||
|
const std::filesystem::path& filePath,
|
||||||
|
BuiltInTexture& outTexture) {
|
||||||
|
if (!device || !commandQueue || !std::filesystem::exists(filePath)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int width = 0;
|
||||||
|
int height = 0;
|
||||||
|
int channels = 0;
|
||||||
|
stbi_uc* pixels = stbi_load(filePath.string().c_str(), &width, &height, &channels, STBI_rgb_alpha);
|
||||||
|
if (!pixels || width <= 0 || height <= 0) {
|
||||||
|
if (pixels) {
|
||||||
|
stbi_image_free(pixels);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const UINT srcRowPitch = static_cast<UINT>(width * 4);
|
||||||
|
|
||||||
|
D3D12_RESOURCE_DESC textureDesc = {};
|
||||||
|
textureDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
|
||||||
|
textureDesc.Alignment = 0;
|
||||||
|
textureDesc.Width = static_cast<UINT64>(width);
|
||||||
|
textureDesc.Height = static_cast<UINT>(height);
|
||||||
|
textureDesc.DepthOrArraySize = 1;
|
||||||
|
textureDesc.MipLevels = 1;
|
||||||
|
textureDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
|
||||||
|
textureDesc.SampleDesc.Count = 1;
|
||||||
|
textureDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN;
|
||||||
|
|
||||||
|
D3D12_HEAP_PROPERTIES defaultHeap = {};
|
||||||
|
defaultHeap.Type = D3D12_HEAP_TYPE_DEFAULT;
|
||||||
|
|
||||||
|
ComPtr<ID3D12Resource> textureResource;
|
||||||
|
if (FAILED(device->CreateCommittedResource(
|
||||||
|
&defaultHeap,
|
||||||
|
D3D12_HEAP_FLAG_NONE,
|
||||||
|
&textureDesc,
|
||||||
|
D3D12_RESOURCE_STATE_COPY_DEST,
|
||||||
|
nullptr,
|
||||||
|
IID_PPV_ARGS(&textureResource)))) {
|
||||||
|
stbi_image_free(pixels);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
D3D12_PLACED_SUBRESOURCE_FOOTPRINT footprint = {};
|
||||||
|
UINT numRows = 0;
|
||||||
|
UINT64 rowSizeInBytes = 0;
|
||||||
|
UINT64 uploadBufferSize = 0;
|
||||||
|
device->GetCopyableFootprints(&textureDesc, 0, 1, 0, &footprint, &numRows, &rowSizeInBytes, &uploadBufferSize);
|
||||||
|
|
||||||
|
D3D12_RESOURCE_DESC uploadDesc = {};
|
||||||
|
uploadDesc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
|
||||||
|
uploadDesc.Width = uploadBufferSize;
|
||||||
|
uploadDesc.Height = 1;
|
||||||
|
uploadDesc.DepthOrArraySize = 1;
|
||||||
|
uploadDesc.MipLevels = 1;
|
||||||
|
uploadDesc.SampleDesc.Count = 1;
|
||||||
|
uploadDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
|
||||||
|
|
||||||
|
D3D12_HEAP_PROPERTIES uploadHeap = {};
|
||||||
|
uploadHeap.Type = D3D12_HEAP_TYPE_UPLOAD;
|
||||||
|
|
||||||
|
ComPtr<ID3D12Resource> uploadResource;
|
||||||
|
if (FAILED(device->CreateCommittedResource(
|
||||||
|
&uploadHeap,
|
||||||
|
D3D12_HEAP_FLAG_NONE,
|
||||||
|
&uploadDesc,
|
||||||
|
D3D12_RESOURCE_STATE_GENERIC_READ,
|
||||||
|
nullptr,
|
||||||
|
IID_PPV_ARGS(&uploadResource)))) {
|
||||||
|
stbi_image_free(pixels);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::uint8_t* mappedData = nullptr;
|
||||||
|
if (FAILED(uploadResource->Map(0, nullptr, reinterpret_cast<void**>(&mappedData)))) {
|
||||||
|
stbi_image_free(pixels);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (UINT row = 0; row < numRows; ++row) {
|
||||||
|
std::memcpy(
|
||||||
|
mappedData + footprint.Offset + static_cast<SIZE_T>(row) * footprint.Footprint.RowPitch,
|
||||||
|
pixels + static_cast<size_t>(row) * srcRowPitch,
|
||||||
|
srcRowPitch);
|
||||||
|
}
|
||||||
|
uploadResource->Unmap(0, nullptr);
|
||||||
|
stbi_image_free(pixels);
|
||||||
|
|
||||||
|
ComPtr<ID3D12CommandAllocator> commandAllocator;
|
||||||
|
ComPtr<ID3D12GraphicsCommandList> commandList;
|
||||||
|
if (FAILED(device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&commandAllocator)))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (FAILED(device->CreateCommandList(
|
||||||
|
0,
|
||||||
|
D3D12_COMMAND_LIST_TYPE_DIRECT,
|
||||||
|
commandAllocator.Get(),
|
||||||
|
nullptr,
|
||||||
|
IID_PPV_ARGS(&commandList)))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
D3D12_TEXTURE_COPY_LOCATION dst = {};
|
||||||
|
dst.pResource = textureResource.Get();
|
||||||
|
dst.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;
|
||||||
|
dst.SubresourceIndex = 0;
|
||||||
|
|
||||||
|
D3D12_TEXTURE_COPY_LOCATION src = {};
|
||||||
|
src.pResource = uploadResource.Get();
|
||||||
|
src.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT;
|
||||||
|
src.PlacedFootprint = footprint;
|
||||||
|
|
||||||
|
commandList->CopyTextureRegion(&dst, 0, 0, 0, &src, nullptr);
|
||||||
|
|
||||||
|
D3D12_RESOURCE_BARRIER barrier = {};
|
||||||
|
barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
|
||||||
|
barrier.Transition.pResource = textureResource.Get();
|
||||||
|
barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_COPY_DEST;
|
||||||
|
barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE;
|
||||||
|
barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
|
||||||
|
commandList->ResourceBarrier(1, &barrier);
|
||||||
|
|
||||||
|
if (FAILED(commandList->Close())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ID3D12CommandList* commandLists[] = { commandList.Get() };
|
||||||
|
commandQueue->ExecuteCommandLists(1, commandLists);
|
||||||
|
|
||||||
|
if (!WaitForQueueIdle(device, commandQueue)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
backend.AllocateTextureDescriptor(&outTexture.cpuHandle, &outTexture.gpuHandle);
|
||||||
|
|
||||||
|
D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = {};
|
||||||
|
srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
|
||||||
|
srvDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
|
||||||
|
srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
|
||||||
|
srvDesc.Texture2D.MipLevels = 1;
|
||||||
|
device->CreateShaderResourceView(textureResource.Get(), &srvDesc, outTexture.cpuHandle);
|
||||||
|
|
||||||
|
outTexture.texture = textureResource;
|
||||||
|
outTexture.textureId = (ImTextureID)(static_cast<intptr_t>(outTexture.gpuHandle.ptr));
|
||||||
|
outTexture.width = width;
|
||||||
|
outTexture.height = height;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImVec2 ComputeFittedIconSize(const BuiltInTexture& texture, const ImVec2& min, const ImVec2& max) {
|
||||||
|
const float availableWidth = max.x - min.x;
|
||||||
|
const float availableHeight = max.y - min.y;
|
||||||
|
if (availableWidth <= 0.0f || availableHeight <= 0.0f || texture.width <= 0 || texture.height <= 0) {
|
||||||
|
return ImVec2(0.0f, 0.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
const float scale = (std::min)(
|
||||||
|
availableWidth / static_cast<float>(texture.width),
|
||||||
|
availableHeight / static_cast<float>(texture.height));
|
||||||
|
return ImVec2(
|
||||||
|
static_cast<float>(texture.width) * scale,
|
||||||
|
static_cast<float>(texture.height) * scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DrawTextureIcon(ImDrawList* drawList, const BuiltInTexture& texture, const ImVec2& min, const ImVec2& max) {
|
||||||
|
if (!drawList || !texture.IsValid()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ImVec2 size = ComputeFittedIconSize(texture, min, max);
|
||||||
|
const float x = min.x + ((max.x - min.x) - size.x) * 0.5f;
|
||||||
|
const float y = min.y + ((max.y - min.y) - size.y) * 0.5f;
|
||||||
|
drawList->AddImage(texture.textureId, ImVec2(x, y), ImVec2(x + size.x, y + size.y));
|
||||||
|
}
|
||||||
|
|
||||||
|
void DrawBuiltInFolderFallback(ImDrawList* drawList, const ImVec2& min, const ImVec2& max) {
|
||||||
|
if (!drawList) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const float width = max.x - min.x;
|
||||||
|
const float height = max.y - min.y;
|
||||||
|
if (width <= 0.0f || height <= 0.0f) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const float rounding = (std::max)(1.0f, (std::min)(width, height) * 0.18f);
|
||||||
|
const ImU32 tabColor = ImGui::GetColorU32(BuiltInFolderIconTabColor());
|
||||||
|
const ImU32 topColor = ImGui::GetColorU32(BuiltInFolderIconTopColor());
|
||||||
|
const ImU32 bodyColor = ImGui::GetColorU32(BuiltInFolderIconBodyColor());
|
||||||
|
|
||||||
|
const ImVec2 tabMin(min.x + width * 0.08f, min.y + height * 0.14f);
|
||||||
|
const ImVec2 tabMax(min.x + width * 0.48f, min.y + height * 0.38f);
|
||||||
|
const ImVec2 topMin(min.x + width * 0.24f, min.y + height * 0.22f);
|
||||||
|
const ImVec2 topMax(min.x + width * 0.90f, min.y + height * 0.42f);
|
||||||
|
const ImVec2 bodyMin(min.x + width * 0.06f, min.y + height * 0.32f);
|
||||||
|
const ImVec2 bodyMax(min.x + width * 0.94f, min.y + height * 0.88f);
|
||||||
|
|
||||||
|
drawList->AddRectFilled(tabMin, tabMax, tabColor, rounding);
|
||||||
|
drawList->AddRectFilled(
|
||||||
|
topMin,
|
||||||
|
topMax,
|
||||||
|
topColor,
|
||||||
|
rounding,
|
||||||
|
ImDrawFlags_RoundCornersTopLeft | ImDrawFlags_RoundCornersTopRight);
|
||||||
|
drawList->AddRectFilled(bodyMin, bodyMax, bodyColor, rounding);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DrawBuiltInFileIcon(ImDrawList* drawList, const ImVec2& min, const ImVec2& max) {
|
||||||
|
if (!drawList) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ImU32 fillColor = ImGui::GetColorU32(AssetFileIconFillColor());
|
||||||
|
const ImU32 lineColor = ImGui::GetColorU32(AssetFileIconLineColor());
|
||||||
|
const ImVec2 foldA(max.x - 8.0f, min.y);
|
||||||
|
const ImVec2 foldB(max.x, min.y + 8.0f);
|
||||||
|
drawList->AddRectFilled(min, max, fillColor, 2.0f);
|
||||||
|
drawList->AddRect(min, max, lineColor, 2.0f);
|
||||||
|
drawList->AddTriangleFilled(foldA, ImVec2(max.x, min.y), foldB, ImGui::GetColorU32(AssetFileFoldColor()));
|
||||||
|
drawList->AddLine(foldA, foldB, lineColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void InitializeBuiltInIcons(
|
||||||
|
ImGuiBackendBridge& backend,
|
||||||
|
ID3D12Device* device,
|
||||||
|
ID3D12CommandQueue* commandQueue) {
|
||||||
|
ShutdownBuiltInIcons();
|
||||||
|
g_icons.backend = &backend;
|
||||||
|
LoadTextureFromFile(backend, device, commandQueue, ResolveFolderIconPath(), g_icons.folder);
|
||||||
|
LoadTextureFromFile(backend, device, commandQueue, ResolveGameObjectIconPath(), g_icons.gameObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShutdownBuiltInIcons() {
|
||||||
|
ResetTexture(g_icons.folder);
|
||||||
|
ResetTexture(g_icons.gameObject);
|
||||||
|
g_icons.backend = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DrawAssetIcon(ImDrawList* drawList, const ImVec2& min, const ImVec2& max, AssetIconKind kind) {
|
||||||
|
if (kind == AssetIconKind::Folder) {
|
||||||
|
if (g_icons.folder.IsValid()) {
|
||||||
|
DrawTextureIcon(drawList, g_icons.folder, min, max);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DrawBuiltInFolderFallback(drawList, min, max);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (kind == AssetIconKind::GameObject) {
|
||||||
|
if (g_icons.gameObject.IsValid()) {
|
||||||
|
DrawTextureIcon(drawList, g_icons.gameObject, min, max);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DrawBuiltInFileIcon(drawList, min, max);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DrawBuiltInFileIcon(drawList, min, max);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace UI
|
||||||
|
} // namespace Editor
|
||||||
|
} // namespace XCEngine
|
||||||
31
editor/src/UI/BuiltInIcons.h
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <imgui.h>
|
||||||
|
|
||||||
|
struct ID3D12Device;
|
||||||
|
struct ID3D12CommandQueue;
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace Editor {
|
||||||
|
namespace UI {
|
||||||
|
|
||||||
|
class ImGuiBackendBridge;
|
||||||
|
|
||||||
|
enum class AssetIconKind {
|
||||||
|
Folder,
|
||||||
|
File,
|
||||||
|
GameObject
|
||||||
|
};
|
||||||
|
|
||||||
|
void InitializeBuiltInIcons(
|
||||||
|
ImGuiBackendBridge& backend,
|
||||||
|
ID3D12Device* device,
|
||||||
|
ID3D12CommandQueue* commandQueue);
|
||||||
|
|
||||||
|
void ShutdownBuiltInIcons();
|
||||||
|
|
||||||
|
void DrawAssetIcon(ImDrawList* drawList, const ImVec2& min, const ImVec2& max, AssetIconKind kind);
|
||||||
|
|
||||||
|
} // namespace UI
|
||||||
|
} // namespace Editor
|
||||||
|
} // namespace XCEngine
|
||||||
@@ -9,54 +9,6 @@ namespace XCEngine {
|
|||||||
namespace Editor {
|
namespace Editor {
|
||||||
namespace UI {
|
namespace UI {
|
||||||
|
|
||||||
inline float DefaultControlLabelWidth() {
|
|
||||||
return InspectorPropertyLabelWidth();
|
|
||||||
}
|
|
||||||
|
|
||||||
inline ImVec2 DefaultControlCellPadding() {
|
|
||||||
return ControlCellPadding();
|
|
||||||
}
|
|
||||||
|
|
||||||
inline ImVec2 DefaultControlFramePadding() {
|
|
||||||
return ControlFramePadding();
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void PushControlRowStyles() {
|
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, DefaultControlCellPadding());
|
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, DefaultControlFramePadding());
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename DrawControlFn>
|
|
||||||
inline auto DrawControlRow(
|
|
||||||
const char* label,
|
|
||||||
float columnWidth,
|
|
||||||
DrawControlFn&& drawControl) -> decltype(drawControl()) {
|
|
||||||
using Result = decltype(drawControl());
|
|
||||||
|
|
||||||
Result result{};
|
|
||||||
ImGui::PushID(label);
|
|
||||||
PushControlRowStyles();
|
|
||||||
|
|
||||||
if (ImGui::BeginTable("##ControlRow", 2, ImGuiTableFlags_NoSavedSettings)) {
|
|
||||||
ImGui::TableSetupColumn("##label", ImGuiTableColumnFlags_WidthFixed, columnWidth);
|
|
||||||
ImGui::TableSetupColumn("##control", ImGuiTableColumnFlags_WidthStretch);
|
|
||||||
ImGui::TableNextRow(ImGuiTableRowFlags_None, ImGui::GetFontSize() + ControlRowHeightOffset());
|
|
||||||
|
|
||||||
ImGui::TableNextColumn();
|
|
||||||
ImGui::AlignTextToFramePadding();
|
|
||||||
ImGui::TextUnformatted(label);
|
|
||||||
|
|
||||||
ImGui::TableNextColumn();
|
|
||||||
result = drawControl();
|
|
||||||
|
|
||||||
ImGui::EndTable();
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::PopStyleVar(2);
|
|
||||||
ImGui::PopID();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void StyleVarPush(ImGuiStyleVar idx, float val) {
|
inline void StyleVarPush(ImGuiStyleVar idx, float val) {
|
||||||
ImGui::PushStyleVar(idx, val);
|
ImGui::PushStyleVar(idx, val);
|
||||||
}
|
}
|
||||||
@@ -81,33 +33,158 @@ inline void PopStyleColor(int count = 1) {
|
|||||||
ImGui::PopStyleColor(count);
|
ImGui::PopStyleColor(count);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void PushPopupWindowStyle() {
|
inline void DrawDisclosureArrow(ImDrawList* drawList, const ImVec2& min, const ImVec2& max, bool open, ImU32 color) {
|
||||||
|
if (!drawList) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ImVec2 center((min.x + max.x) * 0.5f, (min.y + max.y) * 0.5f);
|
||||||
|
const float width = max.x - min.x;
|
||||||
|
const float height = max.y - min.y;
|
||||||
|
const float size = (width < height ? width : height) * DisclosureArrowScale();
|
||||||
|
if (size <= 0.0f) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (open) {
|
||||||
|
drawList->AddTriangleFilled(
|
||||||
|
ImVec2(center.x - size, center.y - size * 0.45f),
|
||||||
|
ImVec2(center.x + size, center.y - size * 0.45f),
|
||||||
|
ImVec2(center.x, center.y + size),
|
||||||
|
color);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
drawList->AddTriangleFilled(
|
||||||
|
ImVec2(center.x - size * 0.45f, center.y - size),
|
||||||
|
ImVec2(center.x - size * 0.45f, center.y + size),
|
||||||
|
ImVec2(center.x + size, center.y),
|
||||||
|
color);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline constexpr int PopupWindowChromeVarCount() {
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline constexpr int PopupWindowChromeColorCount() {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline constexpr int PopupContentChromeVarCount() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline constexpr int PopupContentChromeColorCount() {
|
||||||
|
return 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline constexpr int ComboPopupWindowChromeVarCount() {
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline constexpr int ComboPopupWindowChromeColorCount() {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline constexpr int ComboPopupContentChromeVarCount() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline constexpr int ComboPopupContentChromeColorCount() {
|
||||||
|
return 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void PushPopupWindowChrome() {
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, PopupWindowPadding());
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, PopupWindowPadding());
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_PopupRounding, PopupWindowRounding());
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_PopupBorderSize, PopupWindowBorderSize());
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_PopupBg, PopupBackgroundColor());
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_Border, PopupBorderColor());
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void PopPopupWindowChrome() {
|
||||||
|
ImGui::PopStyleColor(PopupWindowChromeColorCount());
|
||||||
|
ImGui::PopStyleVar(PopupWindowChromeVarCount());
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void PushComboPopupWindowChrome() {
|
||||||
|
const ImVec4 borderColor = ImGui::GetStyleColorVec4(ImGuiCol_Border);
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ComboPopupWindowPadding());
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_PopupRounding, ComboPopupRounding());
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_PopupBorderSize, ComboPopupBorderSize());
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_PopupBg, ComboPopupBackgroundColor());
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_Border, borderColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void PopComboPopupWindowChrome() {
|
||||||
|
ImGui::PopStyleColor(ComboPopupWindowChromeColorCount());
|
||||||
|
ImGui::PopStyleVar(ComboPopupWindowChromeVarCount());
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void PushPopupContentChrome() {
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_Text, PopupTextColor());
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_TextDisabled, PopupTextDisabledColor());
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_Header, PopupItemColor());
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_HeaderHovered, PopupItemHoveredColor());
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_HeaderActive, PopupItemActiveColor());
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_Separator, PopupBorderColor());
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_CheckMark, PopupCheckMarkColor());
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void PopPopupContentChrome() {
|
||||||
|
ImGui::PopStyleColor(PopupContentChromeColorCount());
|
||||||
|
if (PopupContentChromeVarCount() > 0) {
|
||||||
|
ImGui::PopStyleVar(PopupContentChromeVarCount());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void PushComboPopupContentChrome() {
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ComboPopupItemSpacing());
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_Text, ComboPopupTextColor());
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_TextDisabled, ComboPopupTextDisabledColor());
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_Header, ComboPopupItemColor());
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ComboPopupItemHoveredColor());
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_HeaderActive, ComboPopupItemActiveColor());
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void PopComboPopupContentChrome() {
|
||||||
|
ImGui::PopStyleColor(ComboPopupContentChromeColorCount());
|
||||||
|
ImGui::PopStyleVar(ComboPopupContentChromeVarCount());
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void PushPopupChromeStyle() {
|
||||||
|
PushPopupWindowChrome();
|
||||||
|
PushPopupContentChrome();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void PopPopupChromeStyle() {
|
||||||
|
PopPopupContentChrome();
|
||||||
|
PopPopupWindowChrome();
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool BeginPopup(const char* str_id, ImGuiWindowFlags flags = 0) {
|
inline bool BeginPopup(const char* str_id, ImGuiWindowFlags flags = 0) {
|
||||||
PushPopupWindowStyle();
|
PushPopupChromeStyle();
|
||||||
bool is_open = ImGui::BeginPopup(str_id, flags);
|
bool is_open = ImGui::BeginPopup(str_id, flags);
|
||||||
if (!is_open) {
|
if (!is_open) {
|
||||||
ImGui::PopStyleVar();
|
PopPopupChromeStyle();
|
||||||
}
|
}
|
||||||
return is_open;
|
return is_open;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool BeginPopupContextItem(const char* str_id = nullptr, ImGuiPopupFlags popup_flags = ImGuiPopupFlags_MouseButtonRight) {
|
inline bool BeginPopupContextItem(const char* str_id = nullptr, ImGuiPopupFlags popup_flags = ImGuiPopupFlags_MouseButtonRight) {
|
||||||
PushPopupWindowStyle();
|
PushPopupChromeStyle();
|
||||||
bool is_open = ImGui::BeginPopupContextItem(str_id, popup_flags);
|
bool is_open = ImGui::BeginPopupContextItem(str_id, popup_flags);
|
||||||
if (!is_open) {
|
if (!is_open) {
|
||||||
ImGui::PopStyleVar();
|
PopPopupChromeStyle();
|
||||||
}
|
}
|
||||||
return is_open;
|
return is_open;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool BeginPopupContextWindow(const char* str_id = nullptr, ImGuiPopupFlags popup_flags = ImGuiPopupFlags_MouseButtonRight) {
|
inline bool BeginPopupContextWindow(const char* str_id = nullptr, ImGuiPopupFlags popup_flags = ImGuiPopupFlags_MouseButtonRight) {
|
||||||
PushPopupWindowStyle();
|
PushPopupChromeStyle();
|
||||||
bool is_open = ImGui::BeginPopupContextWindow(str_id, popup_flags);
|
bool is_open = ImGui::BeginPopupContextWindow(str_id, popup_flags);
|
||||||
if (!is_open) {
|
if (!is_open) {
|
||||||
ImGui::PopStyleVar();
|
PopPopupChromeStyle();
|
||||||
}
|
}
|
||||||
return is_open;
|
return is_open;
|
||||||
}
|
}
|
||||||
@@ -116,17 +193,60 @@ inline bool BeginModalPopup(
|
|||||||
const char* name,
|
const char* name,
|
||||||
bool* p_open = nullptr,
|
bool* p_open = nullptr,
|
||||||
ImGuiWindowFlags flags = ImGuiWindowFlags_AlwaysAutoResize) {
|
ImGuiWindowFlags flags = ImGuiWindowFlags_AlwaysAutoResize) {
|
||||||
PushPopupWindowStyle();
|
PushPopupChromeStyle();
|
||||||
bool is_open = ImGui::BeginPopupModal(name, p_open, flags);
|
bool is_open = ImGui::BeginPopupModal(name, p_open, flags);
|
||||||
if (!is_open) {
|
if (!is_open) {
|
||||||
ImGui::PopStyleVar();
|
PopPopupChromeStyle();
|
||||||
}
|
}
|
||||||
return is_open;
|
return is_open;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void EndPopup() {
|
inline void EndPopup() {
|
||||||
ImGui::EndPopup();
|
ImGui::EndPopup();
|
||||||
ImGui::PopStyleVar();
|
PopPopupChromeStyle();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool BeginStyledCombo(
|
||||||
|
const char* label,
|
||||||
|
const char* preview_value,
|
||||||
|
ImGuiComboFlags flags = ImGuiComboFlags_None) {
|
||||||
|
const ImVec4 previewBorderColor = ImGui::GetStyleColorVec4(ImGuiCol_Border);
|
||||||
|
PushComboPopupWindowChrome();
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_Border, previewBorderColor);
|
||||||
|
const bool is_open = ImGui::BeginCombo(label, preview_value, flags);
|
||||||
|
if (!is_open) {
|
||||||
|
ImGui::PopStyleColor();
|
||||||
|
PopComboPopupWindowChrome();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::PopStyleColor();
|
||||||
|
PopComboPopupWindowChrome();
|
||||||
|
PushComboPopupContentChrome();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void EndStyledCombo() {
|
||||||
|
PopComboPopupContentChrome();
|
||||||
|
ImGui::EndCombo();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool BeginStyledComboPopup(const char* str_id, ImGuiWindowFlags flags = ImGuiWindowFlags_None) {
|
||||||
|
PushComboPopupWindowChrome();
|
||||||
|
const bool is_open = ImGui::BeginPopup(str_id, flags);
|
||||||
|
if (!is_open) {
|
||||||
|
PopComboPopupWindowChrome();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
PopComboPopupWindowChrome();
|
||||||
|
PushComboPopupContentChrome();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void EndStyledComboPopup() {
|
||||||
|
PopComboPopupContentChrome();
|
||||||
|
ImGui::EndPopup();
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void BeginDisabled(bool disabled = true) {
|
inline void BeginDisabled(bool disabled = true) {
|
||||||
|
|||||||
@@ -219,16 +219,169 @@ inline ImU32 ResolveCustomDockOverlineColor(const ImGuiDockNode& node, const ImG
|
|||||||
node.IsFocused ? ImGuiWindowDockStyleCol_TabSelectedOverline : ImGuiWindowDockStyleCol_TabDimmedSelectedOverline];
|
node.IsFocused ? ImGuiWindowDockStyleCol_TabSelectedOverline : ImGuiWindowDockStyleCol_TabDimmedSelectedOverline];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline ImGuiID ResolveCustomDockTabItemId(const ImGuiWindow& window) {
|
||||||
|
return window.TabId != 0 ? window.TabId : window.MoveId;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void UpdateDockWindowDisplayOrder(ImGuiDockNode& node) {
|
||||||
|
auto& order = DockTabOrderCache()[node.ID];
|
||||||
|
int dockOrder = 0;
|
||||||
|
for (ImGuiID tabId : order) {
|
||||||
|
if (ImGuiWindow* window = FindDockWindowByTabId(node, tabId)) {
|
||||||
|
window->DockOrder = dockOrder++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (ImGuiWindow* window : node.Windows) {
|
||||||
|
if (!window) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (std::find(order.begin(), order.end(), window->TabId) == order.end()) {
|
||||||
|
window->DockOrder = dockOrder++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void ReorderDockTab(ImGuiDockNode& node, ImGuiID tabId, int destinationIndex) {
|
||||||
|
SyncDockTabOrderCache(node);
|
||||||
|
|
||||||
|
auto& order = DockTabOrderCache()[node.ID];
|
||||||
|
const auto it = std::find(order.begin(), order.end(), tabId);
|
||||||
|
if (it == order.end()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int sourceIndex = static_cast<int>(std::distance(order.begin(), it));
|
||||||
|
destinationIndex = std::clamp(destinationIndex, 0, static_cast<int>(order.size()) - 1);
|
||||||
|
if (destinationIndex == sourceIndex) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
order.erase(it);
|
||||||
|
order.insert(order.begin() + destinationIndex, tabId);
|
||||||
|
UpdateDockWindowDisplayOrder(node);
|
||||||
|
ImGui::MarkIniSettingsDirty();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void UpdateDraggedDockTabOrder(
|
||||||
|
ImGuiDockNode& node,
|
||||||
|
const std::vector<ImRect>& tabRects,
|
||||||
|
ImGuiID draggedTabId,
|
||||||
|
int sourceIndex) {
|
||||||
|
if (sourceIndex < 0 || sourceIndex >= static_cast<int>(tabRects.size())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ImRect& sourceRect = tabRects[sourceIndex];
|
||||||
|
const float mouseX = ImGui::GetIO().MousePos.x;
|
||||||
|
if (mouseX >= sourceRect.Min.x && mouseX <= sourceRect.Max.x) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int destinationIndex = static_cast<int>(tabRects.size()) - 1;
|
||||||
|
for (int i = 0; i < static_cast<int>(tabRects.size()); ++i) {
|
||||||
|
const float centerX = (tabRects[i].Min.x + tabRects[i].Max.x) * 0.5f;
|
||||||
|
if (mouseX < centerX) {
|
||||||
|
destinationIndex = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (destinationIndex > sourceIndex) {
|
||||||
|
--destinationIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
ReorderDockTab(node, draggedTabId, destinationIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool ShouldUndockDraggedDockTab(
|
||||||
|
const ImRect& tabRect,
|
||||||
|
int sourceIndex,
|
||||||
|
int tabCount) {
|
||||||
|
const ImGuiIO& io = ImGui::GetIO();
|
||||||
|
const float thresholdBase = ImGui::GetFontSize();
|
||||||
|
const float thresholdX = thresholdBase * 2.2f;
|
||||||
|
const float thresholdY =
|
||||||
|
(thresholdBase * 1.5f) +
|
||||||
|
ImClamp((ImFabs(io.MouseDragMaxDistanceAbs[0].x) - thresholdBase * 2.0f) * 0.20f, 0.0f, thresholdBase * 4.0f);
|
||||||
|
|
||||||
|
const float distanceFromEdgeY = ImMax(tabRect.Min.y - io.MousePos.y, io.MousePos.y - tabRect.Max.y);
|
||||||
|
if (distanceFromEdgeY >= thresholdY) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool draggingLeft = io.MousePos.x < tabRect.Min.x;
|
||||||
|
const bool draggingRight = io.MousePos.x > tabRect.Max.x;
|
||||||
|
if (draggingLeft && sourceIndex == 0 && (tabRect.Min.x - io.MousePos.x) > thresholdX) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (draggingRight && sourceIndex == tabCount - 1 && (io.MousePos.x - tabRect.Max.x) > thresholdX) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void BeginCustomDockTabUndock(ImGuiWindow& targetWindow, const ImRect& tabRect) {
|
||||||
|
ImGuiContext& g = *GImGui;
|
||||||
|
ImGui::DockContextQueueUndockWindow(&g, &targetWindow);
|
||||||
|
g.MovingWindow = &targetWindow;
|
||||||
|
ImGui::SetActiveID(targetWindow.MoveId, &targetWindow);
|
||||||
|
g.ActiveIdClickOffset.x -= (targetWindow.Pos.x - tabRect.Min.x);
|
||||||
|
g.ActiveIdClickOffset.y -= (targetWindow.Pos.y - tabRect.Min.y);
|
||||||
|
g.ActiveIdNoClearOnFocusLoss = true;
|
||||||
|
ImGui::SetActiveIdUsingAllKeyboardKeys();
|
||||||
|
}
|
||||||
|
|
||||||
inline void DrawCustomDockTab(
|
inline void DrawCustomDockTab(
|
||||||
ImGuiDockNode& node,
|
ImGuiDockNode& node,
|
||||||
ImGuiWindow& targetWindow,
|
ImGuiWindow& targetWindow,
|
||||||
const ImRect& tabRect,
|
const ImRect& tabRect,
|
||||||
const char* idSuffix) {
|
const std::vector<ImRect>& tabRects,
|
||||||
|
int tabIndex) {
|
||||||
|
ImGuiContext& g = *GImGui;
|
||||||
|
const ImGuiID itemId = ResolveCustomDockTabItemId(targetWindow);
|
||||||
ImGui::SetCursorScreenPos(tabRect.Min);
|
ImGui::SetCursorScreenPos(tabRect.Min);
|
||||||
ImGui::InvisibleButton(idSuffix, tabRect.GetSize());
|
ImGui::ItemSize(tabRect.GetSize(), 0.0f);
|
||||||
|
const bool submitted = ImGui::ItemAdd(tabRect, itemId);
|
||||||
|
bool hovered = false;
|
||||||
|
bool held = false;
|
||||||
|
bool clicked = false;
|
||||||
|
if (submitted) {
|
||||||
|
clicked = ImGui::ButtonBehavior(
|
||||||
|
tabRect,
|
||||||
|
itemId,
|
||||||
|
&hovered,
|
||||||
|
&held,
|
||||||
|
ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_AllowOverlap);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (held && g.ActiveId == itemId && g.ActiveIdIsJustActivated) {
|
||||||
|
g.ActiveIdWindow = &targetWindow;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ImGuiDockNode* dockNode = targetWindow.DockNode;
|
||||||
|
const bool singleFloatingWindowNode = dockNode && dockNode->IsFloatingNode() && dockNode->Windows.Size == 1;
|
||||||
|
if (held && singleFloatingWindowNode && ImGui::IsMouseDragging(ImGuiMouseButton_Left, 0.0f)) {
|
||||||
|
ImGui::StartMouseMovingWindow(&targetWindow);
|
||||||
|
} else if (held && ImGui::IsMouseDragging(ImGuiMouseButton_Left)) {
|
||||||
|
const bool canUndock =
|
||||||
|
dockNode &&
|
||||||
|
(targetWindow.Flags & ImGuiWindowFlags_NoMove) == 0 &&
|
||||||
|
(dockNode->MergedFlags & ImGuiDockNodeFlags_NoUndocking) == 0;
|
||||||
|
if (canUndock && ShouldUndockDraggedDockTab(tabRect, tabIndex, static_cast<int>(tabRects.size()))) {
|
||||||
|
BeginCustomDockTabUndock(targetWindow, tabRect);
|
||||||
|
} else if (!g.DragDropActive) {
|
||||||
|
const ImGuiID draggedTabId = targetWindow.TabId;
|
||||||
|
auto& order = DockTabOrderCache()[node.ID];
|
||||||
|
const auto sourceIt = std::find(order.begin(), order.end(), draggedTabId);
|
||||||
|
if (sourceIt != order.end()) {
|
||||||
|
const int sourceIndex = static_cast<int>(std::distance(order.begin(), sourceIt));
|
||||||
|
UpdateDraggedDockTabOrder(node, tabRects, draggedTabId, sourceIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const bool hovered = ImGui::IsItemHovered();
|
|
||||||
const bool clicked = ImGui::IsItemClicked(ImGuiMouseButton_Left);
|
|
||||||
ImDrawList* drawList = ImGui::GetWindowDrawList();
|
ImDrawList* drawList = ImGui::GetWindowDrawList();
|
||||||
ImRect fillRect = tabRect;
|
ImRect fillRect = tabRect;
|
||||||
if (IsDockWindowSelected(node, targetWindow)) {
|
if (IsDockWindowSelected(node, targetWindow)) {
|
||||||
@@ -300,11 +453,10 @@ inline void DrawDockedWindowTabStrip() {
|
|||||||
const ImVec2 stripMax(stripMin.x + stripSize.x, stripMin.y + stripSize.y);
|
const ImVec2 stripMax(stripMin.x + stripSize.x, stripMin.y + stripSize.y);
|
||||||
drawList->AddRectFilled(stripMin, stripMax, ImGui::GetColorU32(ToolbarBackgroundColor()));
|
drawList->AddRectFilled(stripMin, stripMax, ImGui::GetColorU32(ToolbarBackgroundColor()));
|
||||||
|
|
||||||
float cursorX = stripMin.x;
|
|
||||||
const std::vector<ImGuiWindow*> orderedWindows = GetOrderedDockWindows(node);
|
const std::vector<ImGuiWindow*> orderedWindows = GetOrderedDockWindows(node);
|
||||||
float selectedTabMinX = stripMin.x;
|
std::vector<ImRect> tabRects;
|
||||||
float selectedTabMaxX = stripMin.x;
|
tabRects.reserve(orderedWindows.size());
|
||||||
bool hasSelectedTab = false;
|
float cursorX = stripMin.x;
|
||||||
for (ImGuiWindow* dockedWindow : orderedWindows) {
|
for (ImGuiWindow* dockedWindow : orderedWindows) {
|
||||||
if (!dockedWindow) {
|
if (!dockedWindow) {
|
||||||
continue;
|
continue;
|
||||||
@@ -313,19 +465,27 @@ inline void DrawDockedWindowTabStrip() {
|
|||||||
const char* labelEnd = GetDockWindowLabelEnd(*dockedWindow);
|
const char* labelEnd = GetDockWindowLabelEnd(*dockedWindow);
|
||||||
const ImVec2 textSize = ImGui::CalcTextSize(dockedWindow->Name, labelEnd, true);
|
const ImVec2 textSize = ImGui::CalcTextSize(dockedWindow->Name, labelEnd, true);
|
||||||
const float tabWidth = textSize.x + DockedTabHorizontalPadding() * 2.0f;
|
const float tabWidth = textSize.x + DockedTabHorizontalPadding() * 2.0f;
|
||||||
const ImRect tabRect(cursorX, stripMin.y, cursorX + tabWidth, stripMin.y + stripHeight);
|
tabRects.emplace_back(cursorX, stripMin.y, cursorX + tabWidth, stripMin.y + stripHeight);
|
||||||
const ImGuiID pushId = dockedWindow->TabId ? dockedWindow->TabId : ImGui::GetID(dockedWindow->Name);
|
cursorX += tabWidth;
|
||||||
ImGui::PushID(pushId);
|
}
|
||||||
DrawCustomDockTab(node, *dockedWindow, tabRect, "##DockTab");
|
|
||||||
ImGui::PopID();
|
float selectedTabMinX = stripMin.x;
|
||||||
|
float selectedTabMaxX = stripMin.x;
|
||||||
|
bool hasSelectedTab = false;
|
||||||
|
for (int i = 0; i < static_cast<int>(orderedWindows.size()) && i < static_cast<int>(tabRects.size()); ++i) {
|
||||||
|
ImGuiWindow* dockedWindow = orderedWindows[i];
|
||||||
|
if (!dockedWindow) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ImRect& tabRect = tabRects[i];
|
||||||
|
DrawCustomDockTab(node, *dockedWindow, tabRect, tabRects, i);
|
||||||
|
|
||||||
if (IsDockWindowSelected(node, *dockedWindow)) {
|
if (IsDockWindowSelected(node, *dockedWindow)) {
|
||||||
selectedTabMinX = tabRect.Min.x;
|
selectedTabMinX = tabRect.Min.x;
|
||||||
selectedTabMaxX = tabRect.Max.x;
|
selectedTabMaxX = tabRect.Max.x;
|
||||||
hasSelectedTab = true;
|
hasSelectedTab = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
cursorX += tabWidth;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const float dividerY = stripMax.y - 0.5f;
|
const float dividerY = stripMax.y - 0.5f;
|
||||||
|
|||||||
@@ -1,12 +1,25 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef NOMINMAX
|
||||||
|
#define NOMINMAX
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <d3d12.h>
|
#include <d3d12.h>
|
||||||
#include <dxgi1_6.h>
|
#include <dxgi1_6.h>
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
#include <imgui_impl_dx12.h>
|
#include <imgui_impl_dx12.h>
|
||||||
#include <imgui_impl_win32.h>
|
#include <imgui_impl_win32.h>
|
||||||
|
#include <vector>
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
|
|
||||||
|
#ifdef min
|
||||||
|
#undef min
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef max
|
||||||
|
#undef max
|
||||||
|
#endif
|
||||||
|
|
||||||
extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
|
extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
|
||||||
|
|
||||||
namespace XCEngine {
|
namespace XCEngine {
|
||||||
@@ -15,20 +28,42 @@ namespace UI {
|
|||||||
|
|
||||||
class ImGuiBackendBridge {
|
class ImGuiBackendBridge {
|
||||||
public:
|
public:
|
||||||
|
static void EnableDpiAwareness() {
|
||||||
|
ImGui_ImplWin32_EnableDpiAwareness();
|
||||||
|
}
|
||||||
|
|
||||||
|
static float GetDpiScaleForHwnd(HWND hwnd) {
|
||||||
|
return hwnd ? ImGui_ImplWin32_GetDpiScaleForHwnd(hwnd) : 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
void Initialize(
|
void Initialize(
|
||||||
HWND hwnd,
|
HWND hwnd,
|
||||||
ID3D12Device* device,
|
ID3D12Device* device,
|
||||||
|
ID3D12CommandQueue* commandQueue,
|
||||||
ID3D12DescriptorHeap* srvHeap,
|
ID3D12DescriptorHeap* srvHeap,
|
||||||
|
UINT srvDescriptorSize,
|
||||||
|
UINT srvDescriptorCount,
|
||||||
int frameCount = 3,
|
int frameCount = 3,
|
||||||
DXGI_FORMAT backBufferFormat = DXGI_FORMAT_R8G8B8A8_UNORM) {
|
DXGI_FORMAT backBufferFormat = DXGI_FORMAT_R8G8B8A8_UNORM) {
|
||||||
|
m_srvHeap = srvHeap;
|
||||||
|
m_srvDescriptorSize = srvDescriptorSize;
|
||||||
|
m_srvCpuStart = srvHeap->GetCPUDescriptorHandleForHeapStart();
|
||||||
|
m_srvGpuStart = srvHeap->GetGPUDescriptorHandleForHeapStart();
|
||||||
|
m_srvUsage.assign(srvDescriptorCount, false);
|
||||||
|
|
||||||
ImGui_ImplWin32_Init(hwnd);
|
ImGui_ImplWin32_Init(hwnd);
|
||||||
ImGui_ImplDX12_Init(
|
|
||||||
device,
|
ImGui_ImplDX12_InitInfo initInfo = {};
|
||||||
frameCount,
|
initInfo.Device = device;
|
||||||
backBufferFormat,
|
initInfo.CommandQueue = commandQueue;
|
||||||
srvHeap,
|
initInfo.NumFramesInFlight = frameCount;
|
||||||
srvHeap->GetCPUDescriptorHandleForHeapStart(),
|
initInfo.RTVFormat = backBufferFormat;
|
||||||
srvHeap->GetGPUDescriptorHandleForHeapStart());
|
initInfo.DSVFormat = DXGI_FORMAT_UNKNOWN;
|
||||||
|
initInfo.UserData = this;
|
||||||
|
initInfo.SrvDescriptorHeap = srvHeap;
|
||||||
|
initInfo.SrvDescriptorAllocFn = &ImGuiBackendBridge::AllocateSrvDescriptor;
|
||||||
|
initInfo.SrvDescriptorFreeFn = &ImGuiBackendBridge::FreeSrvDescriptor;
|
||||||
|
ImGui_ImplDX12_Init(&initInfo);
|
||||||
m_initialized = true;
|
m_initialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,6 +74,11 @@ public:
|
|||||||
|
|
||||||
ImGui_ImplDX12_Shutdown();
|
ImGui_ImplDX12_Shutdown();
|
||||||
ImGui_ImplWin32_Shutdown();
|
ImGui_ImplWin32_Shutdown();
|
||||||
|
m_srvUsage.clear();
|
||||||
|
m_srvHeap = nullptr;
|
||||||
|
m_srvDescriptorSize = 0;
|
||||||
|
m_srvCpuStart.ptr = 0;
|
||||||
|
m_srvGpuStart.ptr = 0;
|
||||||
m_initialized = false;
|
m_initialized = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,12 +92,82 @@ public:
|
|||||||
ImGui_ImplDX12_RenderDrawData(ImGui::GetDrawData(), commandList);
|
ImGui_ImplDX12_RenderDrawData(ImGui::GetDrawData(), commandList);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AllocateTextureDescriptor(
|
||||||
|
D3D12_CPU_DESCRIPTOR_HANDLE* outCpuHandle,
|
||||||
|
D3D12_GPU_DESCRIPTOR_HANDLE* outGpuHandle) {
|
||||||
|
AllocateSrvDescriptorInternal(outCpuHandle, outGpuHandle);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FreeTextureDescriptor(
|
||||||
|
D3D12_CPU_DESCRIPTOR_HANDLE cpuHandle,
|
||||||
|
D3D12_GPU_DESCRIPTOR_HANDLE gpuHandle) {
|
||||||
|
FreeSrvDescriptorInternal(cpuHandle, gpuHandle);
|
||||||
|
}
|
||||||
|
|
||||||
static bool HandleWindowMessage(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
|
static bool HandleWindowMessage(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
|
||||||
return ImGui_ImplWin32_WndProcHandler(hwnd, msg, wParam, lParam) != 0;
|
return ImGui_ImplWin32_WndProcHandler(hwnd, msg, wParam, lParam) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
static void AllocateSrvDescriptor(
|
||||||
|
ImGui_ImplDX12_InitInfo* info,
|
||||||
|
D3D12_CPU_DESCRIPTOR_HANDLE* outCpuHandle,
|
||||||
|
D3D12_GPU_DESCRIPTOR_HANDLE* outGpuHandle) {
|
||||||
|
ImGuiBackendBridge* bridge = static_cast<ImGuiBackendBridge*>(info->UserData);
|
||||||
|
IM_ASSERT(bridge != nullptr);
|
||||||
|
bridge->AllocateSrvDescriptorInternal(outCpuHandle, outGpuHandle);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void FreeSrvDescriptor(
|
||||||
|
ImGui_ImplDX12_InitInfo* info,
|
||||||
|
D3D12_CPU_DESCRIPTOR_HANDLE cpuHandle,
|
||||||
|
D3D12_GPU_DESCRIPTOR_HANDLE) {
|
||||||
|
ImGuiBackendBridge* bridge = static_cast<ImGuiBackendBridge*>(info->UserData);
|
||||||
|
IM_ASSERT(bridge != nullptr);
|
||||||
|
bridge->FreeSrvDescriptorInternal(cpuHandle, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
void AllocateSrvDescriptorInternal(
|
||||||
|
D3D12_CPU_DESCRIPTOR_HANDLE* outCpuHandle,
|
||||||
|
D3D12_GPU_DESCRIPTOR_HANDLE* outGpuHandle) {
|
||||||
|
IM_ASSERT(outCpuHandle != nullptr && outGpuHandle != nullptr);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < m_srvUsage.size(); ++i) {
|
||||||
|
if (m_srvUsage[i]) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_srvUsage[i] = true;
|
||||||
|
outCpuHandle->ptr = m_srvCpuStart.ptr + static_cast<SIZE_T>(i) * m_srvDescriptorSize;
|
||||||
|
outGpuHandle->ptr = m_srvGpuStart.ptr + static_cast<UINT64>(i) * m_srvDescriptorSize;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
IM_ASSERT(false && "ImGui SRV descriptor heap is exhausted.");
|
||||||
|
*outCpuHandle = m_srvCpuStart;
|
||||||
|
*outGpuHandle = m_srvGpuStart;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FreeSrvDescriptorInternal(
|
||||||
|
D3D12_CPU_DESCRIPTOR_HANDLE cpuHandle,
|
||||||
|
D3D12_GPU_DESCRIPTOR_HANDLE) {
|
||||||
|
if (m_srvDescriptorSize == 0 || cpuHandle.ptr < m_srvCpuStart.ptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SIZE_T offset = cpuHandle.ptr - m_srvCpuStart.ptr;
|
||||||
|
const size_t index = static_cast<size_t>(offset / m_srvDescriptorSize);
|
||||||
|
if (index < m_srvUsage.size()) {
|
||||||
|
m_srvUsage[index] = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool m_initialized = false;
|
bool m_initialized = false;
|
||||||
|
ID3D12DescriptorHeap* m_srvHeap = nullptr;
|
||||||
|
UINT m_srvDescriptorSize = 0;
|
||||||
|
D3D12_CPU_DESCRIPTOR_HANDLE m_srvCpuStart = {};
|
||||||
|
D3D12_GPU_DESCRIPTOR_HANDLE m_srvGpuStart = {};
|
||||||
|
std::vector<bool> m_srvUsage;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace UI
|
} // namespace UI
|
||||||
|
|||||||
@@ -13,16 +13,18 @@ namespace UI {
|
|||||||
|
|
||||||
class ImGuiSession {
|
class ImGuiSession {
|
||||||
public:
|
public:
|
||||||
void Initialize(const std::string& projectPath) {
|
void Initialize(const std::string& projectPath, float mainDpiScale = 1.0f) {
|
||||||
IMGUI_CHECKVERSION();
|
IMGUI_CHECKVERSION();
|
||||||
ImGui::CreateContext();
|
ImGui::CreateContext();
|
||||||
|
|
||||||
ImGuiIO& io = ImGui::GetIO();
|
ImGuiIO& io = ImGui::GetIO();
|
||||||
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
|
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
|
||||||
|
io.ConfigDpiScaleFonts = true;
|
||||||
|
io.ConfigDpiScaleViewports = true;
|
||||||
|
|
||||||
ConfigureIniFile(projectPath, io);
|
ConfigureIniFile(projectPath, io);
|
||||||
|
ConfigureStyle(ImGui::GetStyle(), mainDpiScale);
|
||||||
ConfigureFonts(io);
|
ConfigureFonts(io);
|
||||||
ApplyBaseTheme(ImGui::GetStyle());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Shutdown() {
|
void Shutdown() {
|
||||||
@@ -47,6 +49,10 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
static constexpr float kUiFontSize = 18.0f;
|
||||||
|
static constexpr const char* kPrimaryUiFontPath = "C:/Windows/Fonts/segoeui.ttf";
|
||||||
|
static constexpr const char* kChineseFallbackFontPath = "C:/Windows/Fonts/msyh.ttc";
|
||||||
|
|
||||||
void ConfigureIniFile(const std::string& projectPath, ImGuiIO& io) {
|
void ConfigureIniFile(const std::string& projectPath, ImGuiIO& io) {
|
||||||
const std::filesystem::path configDir = std::filesystem::path(projectPath) / ".xceditor";
|
const std::filesystem::path configDir = std::filesystem::path(projectPath) / ".xceditor";
|
||||||
std::error_code ec;
|
std::error_code ec;
|
||||||
@@ -56,17 +62,54 @@ private:
|
|||||||
io.IniFilename = m_iniPath.c_str();
|
io.IniFilename = m_iniPath.c_str();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ConfigureStyle(ImGuiStyle& style, float mainDpiScale) const {
|
||||||
|
ApplyBaseTheme(style);
|
||||||
|
|
||||||
|
const float dpiScale = mainDpiScale < 1.0f ? 1.0f : (mainDpiScale > 4.0f ? 4.0f : mainDpiScale);
|
||||||
|
style.ScaleAllSizes(dpiScale);
|
||||||
|
style.FontScaleDpi = dpiScale;
|
||||||
|
}
|
||||||
|
|
||||||
void ConfigureFonts(ImGuiIO& io) const {
|
void ConfigureFonts(ImGuiIO& io) const {
|
||||||
if (ImFont* uiFont = io.Fonts->AddFontFromFileTTF("C:/Windows/Fonts/msyh.ttc", 15.0f)) {
|
ImFontAtlas* atlas = io.Fonts;
|
||||||
io.FontDefault = uiFont;
|
atlas->Clear();
|
||||||
|
|
||||||
|
ImFontConfig baseConfig;
|
||||||
|
baseConfig.OversampleH = 2;
|
||||||
|
baseConfig.OversampleV = 1;
|
||||||
|
baseConfig.PixelSnapH = true;
|
||||||
|
|
||||||
|
ImFont* uiFont = atlas->AddFontFromFileTTF(
|
||||||
|
kPrimaryUiFontPath,
|
||||||
|
kUiFontSize,
|
||||||
|
&baseConfig,
|
||||||
|
atlas->GetGlyphRangesDefault());
|
||||||
|
|
||||||
|
if (uiFont) {
|
||||||
|
ImFontConfig mergeConfig = baseConfig;
|
||||||
|
mergeConfig.MergeMode = true;
|
||||||
|
mergeConfig.PixelSnapH = true;
|
||||||
|
atlas->AddFontFromFileTTF(
|
||||||
|
kChineseFallbackFontPath,
|
||||||
|
kUiFontSize,
|
||||||
|
&mergeConfig,
|
||||||
|
atlas->GetGlyphRangesChineseSimplifiedCommon());
|
||||||
} else {
|
} else {
|
||||||
io.FontDefault = io.Fonts->AddFontDefault();
|
uiFont = atlas->AddFontFromFileTTF(
|
||||||
|
kChineseFallbackFontPath,
|
||||||
|
kUiFontSize,
|
||||||
|
&baseConfig,
|
||||||
|
atlas->GetGlyphRangesChineseSimplifiedCommon());
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned char* pixels = nullptr;
|
if (!uiFont) {
|
||||||
int width = 0;
|
ImFontConfig fallbackConfig = baseConfig;
|
||||||
int height = 0;
|
fallbackConfig.SizePixels = kUiFontSize;
|
||||||
io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height);
|
uiFont = atlas->AddFontDefault(&fallbackConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
io.FontDefault = uiFont;
|
||||||
|
atlas->Build();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string m_iniPath;
|
std::string m_iniPath;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "Core.h"
|
#include "Core.h"
|
||||||
|
#include "DockTabBarChrome.h"
|
||||||
#include "StyleTokens.h"
|
#include "StyleTokens.h"
|
||||||
|
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
@@ -9,12 +10,30 @@ namespace XCEngine {
|
|||||||
namespace Editor {
|
namespace Editor {
|
||||||
namespace UI {
|
namespace UI {
|
||||||
|
|
||||||
|
inline void CollapsePanelSectionSpacing() {
|
||||||
|
const float spacingY = ImGui::GetStyle().ItemSpacing.y;
|
||||||
|
if (spacingY <= 0.0f) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const float cursorY = ImGui::GetCursorPosY();
|
||||||
|
const float startY = ImGui::GetCursorStartPos().y;
|
||||||
|
if (cursorY <= startY) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::SetCursorPosY(cursorY - spacingY);
|
||||||
|
}
|
||||||
|
|
||||||
class PanelWindowScope {
|
class PanelWindowScope {
|
||||||
public:
|
public:
|
||||||
explicit PanelWindowScope(const char* name, ImGuiWindowFlags flags = ImGuiWindowFlags_None) {
|
explicit PanelWindowScope(const char* name, ImGuiWindowFlags flags = ImGuiWindowFlags_None) {
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, PanelWindowPadding());
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, PanelWindowPadding());
|
||||||
m_open = ImGui::Begin(name, nullptr, flags);
|
m_open = ImGui::Begin(name, nullptr, flags);
|
||||||
ImGui::PopStyleVar();
|
ImGui::PopStyleVar();
|
||||||
|
if (m_open) {
|
||||||
|
DrawDockedWindowTabStrip();
|
||||||
|
}
|
||||||
m_began = true;
|
m_began = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,9 +63,11 @@ public:
|
|||||||
ImGuiWindowFlags flags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse,
|
ImGuiWindowFlags flags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse,
|
||||||
bool drawBottomBorder = true,
|
bool drawBottomBorder = true,
|
||||||
ImVec2 padding = ToolbarPadding(),
|
ImVec2 padding = ToolbarPadding(),
|
||||||
ImVec2 itemSpacing = ToolbarItemSpacing())
|
ImVec2 itemSpacing = ToolbarItemSpacing(),
|
||||||
|
ImVec4 backgroundColor = ToolbarBackgroundColor())
|
||||||
: m_drawBottomBorder(drawBottomBorder) {
|
: m_drawBottomBorder(drawBottomBorder) {
|
||||||
ImGui::PushStyleColor(ImGuiCol_ChildBg, ToolbarBackgroundColor());
|
CollapsePanelSectionSpacing();
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_ChildBg, backgroundColor);
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, padding);
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, padding);
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, itemSpacing);
|
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, itemSpacing);
|
||||||
m_open = ImGui::BeginChild(id, ImVec2(0.0f, height), false, flags);
|
m_open = ImGui::BeginChild(id, ImVec2(0.0f, height), false, flags);
|
||||||
@@ -82,16 +103,18 @@ public:
|
|||||||
explicit PanelContentScope(
|
explicit PanelContentScope(
|
||||||
const char* id,
|
const char* id,
|
||||||
ImVec2 padding = DefaultPanelContentPadding(),
|
ImVec2 padding = DefaultPanelContentPadding(),
|
||||||
|
ImGuiChildFlags childFlags = ImGuiChildFlags_None,
|
||||||
ImGuiWindowFlags flags = ImGuiWindowFlags_None,
|
ImGuiWindowFlags flags = ImGuiWindowFlags_None,
|
||||||
bool pushItemSpacing = false,
|
bool pushItemSpacing = false,
|
||||||
ImVec2 itemSpacing = ImVec2(0.0f, 0.0f)) {
|
ImVec2 itemSpacing = ImVec2(0.0f, 0.0f)) {
|
||||||
|
CollapsePanelSectionSpacing();
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, padding);
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, padding);
|
||||||
m_styleVarCount = 1;
|
m_styleVarCount = 1;
|
||||||
if (pushItemSpacing) {
|
if (pushItemSpacing) {
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, itemSpacing);
|
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, itemSpacing);
|
||||||
++m_styleVarCount;
|
++m_styleVarCount;
|
||||||
}
|
}
|
||||||
m_open = ImGui::BeginChild(id, ImVec2(0.0f, 0.0f), false, flags);
|
m_open = ImGui::BeginChild(id, ImVec2(0.0f, 0.0f), childFlags, flags);
|
||||||
m_began = true;
|
m_began = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,10 @@ namespace XCEngine {
|
|||||||
namespace Editor {
|
namespace Editor {
|
||||||
namespace UI {
|
namespace UI {
|
||||||
|
|
||||||
|
inline PropertyLayoutSpec InspectorPropertyLayout() {
|
||||||
|
return MakePropertyLayout();
|
||||||
|
}
|
||||||
|
|
||||||
template <typename ApplyFn>
|
template <typename ApplyFn>
|
||||||
inline bool ApplyPropertyChange(
|
inline bool ApplyPropertyChange(
|
||||||
bool changedByWidget,
|
bool changedByWidget,
|
||||||
@@ -35,7 +39,7 @@ inline bool DrawPropertyFloat(
|
|||||||
float max = 0.0f,
|
float max = 0.0f,
|
||||||
const char* format = "%.2f"
|
const char* format = "%.2f"
|
||||||
) {
|
) {
|
||||||
return DrawFloat(label, value, InspectorPropertyLabelWidth(), dragSpeed, min, max, format);
|
return DrawFloat(label, value, InspectorPropertyLayout(), dragSpeed, min, max, format);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool DrawPropertyInt(
|
inline bool DrawPropertyInt(
|
||||||
@@ -45,28 +49,28 @@ inline bool DrawPropertyInt(
|
|||||||
int min = 0,
|
int min = 0,
|
||||||
int max = 0
|
int max = 0
|
||||||
) {
|
) {
|
||||||
return DrawInt(label, value, InspectorPropertyLabelWidth(), step, min, max);
|
return DrawInt(label, value, InspectorPropertyLayout(), step, min, max);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool DrawPropertyBool(
|
inline bool DrawPropertyBool(
|
||||||
const char* label,
|
const char* label,
|
||||||
bool& value
|
bool& value
|
||||||
) {
|
) {
|
||||||
return DrawBool(label, value, InspectorPropertyLabelWidth());
|
return DrawBool(label, value, InspectorPropertyLayout());
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool DrawPropertyColor3(
|
inline bool DrawPropertyColor3(
|
||||||
const char* label,
|
const char* label,
|
||||||
float color[3]
|
float color[3]
|
||||||
) {
|
) {
|
||||||
return DrawColor3(label, color, InspectorPropertyLabelWidth());
|
return DrawColor3(label, color, InspectorPropertyLayout());
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool DrawPropertyColor4(
|
inline bool DrawPropertyColor4(
|
||||||
const char* label,
|
const char* label,
|
||||||
float color[4]
|
float color[4]
|
||||||
) {
|
) {
|
||||||
return DrawColor4(label, color, InspectorPropertyLabelWidth());
|
return DrawColor4(label, color, InspectorPropertyLayout());
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool DrawPropertySliderFloat(
|
inline bool DrawPropertySliderFloat(
|
||||||
@@ -76,7 +80,7 @@ inline bool DrawPropertySliderFloat(
|
|||||||
float max,
|
float max,
|
||||||
const char* format = "%.2f"
|
const char* format = "%.2f"
|
||||||
) {
|
) {
|
||||||
return DrawSliderFloat(label, value, min, max, InspectorPropertyLabelWidth(), format);
|
return DrawSliderFloat(label, value, min, max, InspectorPropertyLayout(), format);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool DrawPropertySliderInt(
|
inline bool DrawPropertySliderInt(
|
||||||
@@ -85,7 +89,7 @@ inline bool DrawPropertySliderInt(
|
|||||||
int min,
|
int min,
|
||||||
int max
|
int max
|
||||||
) {
|
) {
|
||||||
return DrawSliderInt(label, value, min, max, InspectorPropertyLabelWidth());
|
return DrawSliderInt(label, value, min, max, InspectorPropertyLayout());
|
||||||
}
|
}
|
||||||
|
|
||||||
inline int DrawPropertyCombo(
|
inline int DrawPropertyCombo(
|
||||||
@@ -95,7 +99,7 @@ inline int DrawPropertyCombo(
|
|||||||
int itemCount,
|
int itemCount,
|
||||||
int heightInItems = -1
|
int heightInItems = -1
|
||||||
) {
|
) {
|
||||||
return DrawCombo(label, currentItem, items, itemCount, InspectorPropertyLabelWidth(), heightInItems);
|
return DrawCombo(label, currentItem, items, itemCount, InspectorPropertyLayout(), heightInItems);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool DrawPropertyVec2(
|
inline bool DrawPropertyVec2(
|
||||||
@@ -104,7 +108,7 @@ inline bool DrawPropertyVec2(
|
|||||||
float resetValue = 0.0f,
|
float resetValue = 0.0f,
|
||||||
float dragSpeed = 0.1f
|
float dragSpeed = 0.1f
|
||||||
) {
|
) {
|
||||||
return DrawVec2(label, values, resetValue, InspectorPropertyLabelWidth(), dragSpeed);
|
return DrawVec2(label, values, resetValue, InspectorPropertyLayout(), dragSpeed);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool DrawPropertyVec3(
|
inline bool DrawPropertyVec3(
|
||||||
@@ -114,7 +118,7 @@ inline bool DrawPropertyVec3(
|
|||||||
float dragSpeed = 0.1f,
|
float dragSpeed = 0.1f,
|
||||||
bool* isActive = nullptr
|
bool* isActive = nullptr
|
||||||
) {
|
) {
|
||||||
return DrawVec3(label, values, resetValue, InspectorPropertyLabelWidth(), dragSpeed, isActive);
|
return DrawVec3(label, values, resetValue, InspectorPropertyLayout(), dragSpeed, isActive);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool DrawPropertyVec3Input(
|
inline bool DrawPropertyVec3Input(
|
||||||
@@ -123,7 +127,7 @@ inline bool DrawPropertyVec3Input(
|
|||||||
float dragSpeed = 0.1f,
|
float dragSpeed = 0.1f,
|
||||||
bool* isActive = nullptr
|
bool* isActive = nullptr
|
||||||
) {
|
) {
|
||||||
return DrawVec3Input(label, values, InspectorPropertyLabelWidth(), dragSpeed, isActive);
|
return DrawVec3Input(label, values, InspectorPropertyLayout(), dragSpeed, isActive);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
128
editor/src/UI/PropertyLayout.h
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "StyleTokens.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <imgui.h>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace Editor {
|
||||||
|
namespace UI {
|
||||||
|
|
||||||
|
struct PropertyLayoutSpec {
|
||||||
|
float labelInset = InspectorPropertyLabelInset();
|
||||||
|
float controlColumnStart = InspectorPropertyControlColumnStart();
|
||||||
|
float labelControlGap = InspectorPropertyLabelControlGap();
|
||||||
|
float controlTrailingInset = InspectorPropertyControlTrailingInset();
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PropertyLayoutMetrics {
|
||||||
|
ImVec2 cursorPos = ImVec2(0.0f, 0.0f);
|
||||||
|
ImVec2 screenPos = ImVec2(0.0f, 0.0f);
|
||||||
|
float rowWidth = 0.0f;
|
||||||
|
float rowHeight = 0.0f;
|
||||||
|
float labelX = 0.0f;
|
||||||
|
float labelWidth = 0.0f;
|
||||||
|
float controlX = 0.0f;
|
||||||
|
float controlWidth = 0.0f;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline PropertyLayoutSpec MakePropertyLayout() {
|
||||||
|
return PropertyLayoutSpec{};
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void PushPropertyLayoutStyles() {
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ControlFramePadding());
|
||||||
|
}
|
||||||
|
|
||||||
|
inline float GetPropertyControlWidth(
|
||||||
|
const PropertyLayoutMetrics& layout,
|
||||||
|
float trailingInset = 0.0f) {
|
||||||
|
return std::max(layout.controlWidth - trailingInset, 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void SetNextPropertyControlWidth(
|
||||||
|
const PropertyLayoutMetrics& layout,
|
||||||
|
float trailingInset = 0.0f) {
|
||||||
|
ImGui::SetNextItemWidth(GetPropertyControlWidth(layout, trailingInset));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void AlignPropertyControlToRight(
|
||||||
|
const PropertyLayoutMetrics& layout,
|
||||||
|
float width,
|
||||||
|
float trailingInset = 0.0f) {
|
||||||
|
const float offset = layout.controlWidth - width - trailingInset;
|
||||||
|
if (offset > 0.0f) {
|
||||||
|
ImGui::SetCursorPosX(layout.controlX + offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename DrawControlFn>
|
||||||
|
inline auto DrawPropertyRow(
|
||||||
|
const char* label,
|
||||||
|
const PropertyLayoutSpec& spec,
|
||||||
|
DrawControlFn&& drawControl) -> decltype(drawControl(std::declval<const PropertyLayoutMetrics&>())) {
|
||||||
|
using Result = decltype(drawControl(std::declval<const PropertyLayoutMetrics&>()));
|
||||||
|
|
||||||
|
Result result{};
|
||||||
|
ImGui::PushID(label);
|
||||||
|
PushPropertyLayoutStyles();
|
||||||
|
|
||||||
|
const ImVec2 rowCursorPos = ImGui::GetCursorPos();
|
||||||
|
const ImVec2 rowScreenPos = ImGui::GetCursorScreenPos();
|
||||||
|
const float rowWidth = std::max(ImGui::GetContentRegionAvail().x, 1.0f);
|
||||||
|
const float rowHeight = ImGui::GetFrameHeight() + ControlRowHeightOffset();
|
||||||
|
const float labelInset = std::max(spec.labelInset, 0.0f);
|
||||||
|
const float controlColumnStart = std::clamp(
|
||||||
|
std::max(spec.controlColumnStart, 0.0f),
|
||||||
|
labelInset,
|
||||||
|
rowWidth);
|
||||||
|
const float labelWidth = std::max(
|
||||||
|
controlColumnStart - labelInset - std::max(spec.labelControlGap, 0.0f),
|
||||||
|
0.0f);
|
||||||
|
const float controlX = rowCursorPos.x + controlColumnStart;
|
||||||
|
const float controlRight = rowCursorPos.x + rowWidth - std::max(spec.controlTrailingInset, 0.0f);
|
||||||
|
const float controlWidth = std::max(controlRight - controlX, 1.0f);
|
||||||
|
const float labelX = rowCursorPos.x + labelInset;
|
||||||
|
|
||||||
|
const PropertyLayoutMetrics metrics{
|
||||||
|
rowCursorPos,
|
||||||
|
rowScreenPos,
|
||||||
|
rowWidth,
|
||||||
|
rowHeight,
|
||||||
|
labelX,
|
||||||
|
labelWidth,
|
||||||
|
controlX,
|
||||||
|
controlWidth
|
||||||
|
};
|
||||||
|
|
||||||
|
if (label && label[0] != '\0') {
|
||||||
|
ImDrawList* drawList = ImGui::GetWindowDrawList();
|
||||||
|
const float textY = rowScreenPos.y + std::max(0.0f, (rowHeight - ImGui::GetTextLineHeight()) * 0.5f);
|
||||||
|
drawList->PushClipRect(
|
||||||
|
ImVec2(rowScreenPos.x + labelInset, rowScreenPos.y),
|
||||||
|
ImVec2(rowScreenPos.x + labelInset + labelWidth, rowScreenPos.y + rowHeight),
|
||||||
|
true);
|
||||||
|
drawList->AddText(
|
||||||
|
ImVec2(rowScreenPos.x + labelInset, textY),
|
||||||
|
ImGui::GetColorU32(ImGuiCol_Text),
|
||||||
|
label);
|
||||||
|
drawList->PopClipRect();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::SetCursorPos(ImVec2(controlX, rowCursorPos.y));
|
||||||
|
result = drawControl(metrics);
|
||||||
|
|
||||||
|
const float consumedHeight = std::max(rowHeight, ImGui::GetCursorPosY() - rowCursorPos.y);
|
||||||
|
ImGui::SetCursorPos(rowCursorPos);
|
||||||
|
ImGui::Dummy(ImVec2(rowWidth, consumedHeight));
|
||||||
|
|
||||||
|
ImGui::PopStyleVar();
|
||||||
|
ImGui::PopID();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace UI
|
||||||
|
} // namespace Editor
|
||||||
|
} // namespace XCEngine
|
||||||
@@ -1,35 +1,165 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "Core.h"
|
#include "Core.h"
|
||||||
|
#include "PropertyLayout.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
|
|
||||||
namespace XCEngine {
|
namespace XCEngine {
|
||||||
namespace Editor {
|
namespace Editor {
|
||||||
namespace UI {
|
namespace UI {
|
||||||
|
|
||||||
|
inline float CalcComboPopupMaxHeightFromItemCount(int itemCount) {
|
||||||
|
if (itemCount <= 0) {
|
||||||
|
return FLT_MAX;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ImGuiStyle& style = ImGui::GetStyle();
|
||||||
|
return (ImGui::GetFontSize() + style.ItemSpacing.y) * itemCount -
|
||||||
|
style.ItemSpacing.y +
|
||||||
|
(ComboPopupWindowPadding().y * 2.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void DrawComboPreviewFrame(
|
||||||
|
const char* id,
|
||||||
|
const char* previewValue,
|
||||||
|
float width,
|
||||||
|
bool popupOpen) {
|
||||||
|
const float frameHeight = ImGui::GetFrameHeight();
|
||||||
|
ImGui::InvisibleButton(id, ImVec2(width, frameHeight));
|
||||||
|
|
||||||
|
const bool hovered = ImGui::IsItemHovered();
|
||||||
|
const ImVec2 min = ImGui::GetItemRectMin();
|
||||||
|
const ImVec2 max = ImGui::GetItemRectMax();
|
||||||
|
ImDrawList* drawList = ImGui::GetWindowDrawList();
|
||||||
|
const ImGuiStyle& style = ImGui::GetStyle();
|
||||||
|
|
||||||
|
const float arrowWidth = frameHeight;
|
||||||
|
const float valueMaxX = ImMax(min.x, max.x - arrowWidth);
|
||||||
|
const ImU32 frameColor = ImGui::GetColorU32(hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);
|
||||||
|
const ImU32 arrowColor = ImGui::GetColorU32((popupOpen || hovered) ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
|
||||||
|
const ImU32 borderColor = ImGui::GetColorU32(ImGuiCol_Border);
|
||||||
|
const ImU32 textColor = ImGui::GetColorU32(ImGuiCol_Text);
|
||||||
|
|
||||||
|
drawList->AddRectFilled(
|
||||||
|
min,
|
||||||
|
ImVec2(valueMaxX, max.y),
|
||||||
|
frameColor,
|
||||||
|
style.FrameRounding,
|
||||||
|
ImDrawFlags_RoundCornersLeft);
|
||||||
|
drawList->AddRectFilled(
|
||||||
|
ImVec2(valueMaxX, min.y),
|
||||||
|
max,
|
||||||
|
arrowColor,
|
||||||
|
style.FrameRounding,
|
||||||
|
ImDrawFlags_RoundCornersRight);
|
||||||
|
drawList->AddRect(
|
||||||
|
ImVec2(min.x + 0.5f, min.y + 0.5f),
|
||||||
|
ImVec2(max.x - 0.5f, max.y - 0.5f),
|
||||||
|
borderColor,
|
||||||
|
style.FrameRounding,
|
||||||
|
0,
|
||||||
|
style.FrameBorderSize);
|
||||||
|
|
||||||
|
if (previewValue && previewValue[0] != '\0') {
|
||||||
|
const ImVec2 textSize = ImGui::CalcTextSize(previewValue);
|
||||||
|
const ImVec2 textPos(
|
||||||
|
min.x + style.FramePadding.x,
|
||||||
|
min.y + ImMax(0.0f, (frameHeight - textSize.y) * 0.5f));
|
||||||
|
ImGui::PushClipRect(
|
||||||
|
ImVec2(min.x + style.FramePadding.x, min.y),
|
||||||
|
ImVec2(valueMaxX - style.FramePadding.x, max.y),
|
||||||
|
true);
|
||||||
|
drawList->AddText(textPos, textColor, previewValue);
|
||||||
|
ImGui::PopClipRect();
|
||||||
|
}
|
||||||
|
|
||||||
|
const float arrowHalfWidth = 4.0f;
|
||||||
|
const float arrowHalfHeight = 2.5f;
|
||||||
|
const ImVec2 arrowCenter((valueMaxX + max.x) * 0.5f, (min.y + max.y) * 0.5f + 0.5f);
|
||||||
|
drawList->AddTriangleFilled(
|
||||||
|
ImVec2(arrowCenter.x - arrowHalfWidth, arrowCenter.y - arrowHalfHeight),
|
||||||
|
ImVec2(arrowCenter.x + arrowHalfWidth, arrowCenter.y - arrowHalfHeight),
|
||||||
|
ImVec2(arrowCenter.x, arrowCenter.y + arrowHalfHeight),
|
||||||
|
textColor);
|
||||||
|
}
|
||||||
|
|
||||||
inline bool DrawFloat(
|
inline bool DrawFloat(
|
||||||
const char* label,
|
const char* label,
|
||||||
float& value,
|
float& value,
|
||||||
float columnWidth = DefaultControlLabelWidth(),
|
const PropertyLayoutSpec& layoutSpec = MakePropertyLayout(),
|
||||||
float dragSpeed = 0.1f,
|
float dragSpeed = 0.1f,
|
||||||
float min = 0.0f,
|
float min = 0.0f,
|
||||||
float max = 0.0f,
|
float max = 0.0f,
|
||||||
const char* format = "%.2f"
|
const char* format = "%.2f"
|
||||||
) {
|
) {
|
||||||
return DrawControlRow(label, columnWidth, [&]() {
|
return DrawPropertyRow(label, layoutSpec, [&](const PropertyLayoutMetrics& layout) {
|
||||||
|
SetNextPropertyControlWidth(layout);
|
||||||
return ImGui::DragFloat("##value", &value, dragSpeed, min, max, format);
|
return ImGui::DragFloat("##value", &value, dragSpeed, min, max, format);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline bool DrawLinearSlider(
|
||||||
|
const char* id,
|
||||||
|
float width,
|
||||||
|
float normalizedValue) {
|
||||||
|
const float clampedValue = std::clamp(normalizedValue, 0.0f, 1.0f);
|
||||||
|
const float frameHeight = ImGui::GetFrameHeight();
|
||||||
|
ImGui::InvisibleButton(id, ImVec2(width, frameHeight));
|
||||||
|
|
||||||
|
const bool hovered = ImGui::IsItemHovered();
|
||||||
|
const bool active = ImGui::IsItemActive();
|
||||||
|
const ImVec2 min = ImGui::GetItemRectMin();
|
||||||
|
const ImVec2 max = ImGui::GetItemRectMax();
|
||||||
|
const float trackPadding = LinearSliderHorizontalPadding();
|
||||||
|
const float trackMinX = min.x + trackPadding;
|
||||||
|
const float trackMaxX = max.x - trackPadding;
|
||||||
|
const float centerY = (min.y + max.y) * 0.5f;
|
||||||
|
const float knobX = trackMinX + (trackMaxX - trackMinX) * clampedValue;
|
||||||
|
const float trackHalfThickness = LinearSliderTrackThickness() * 0.5f;
|
||||||
|
|
||||||
|
ImDrawList* drawList = ImGui::GetWindowDrawList();
|
||||||
|
drawList->AddRectFilled(
|
||||||
|
ImVec2(trackMinX, centerY - trackHalfThickness),
|
||||||
|
ImVec2(trackMaxX, centerY + trackHalfThickness),
|
||||||
|
ImGui::GetColorU32(LinearSliderTrackColor()),
|
||||||
|
trackHalfThickness);
|
||||||
|
drawList->AddRectFilled(
|
||||||
|
ImVec2(trackMinX, centerY - trackHalfThickness),
|
||||||
|
ImVec2(knobX, centerY + trackHalfThickness),
|
||||||
|
ImGui::GetColorU32(LinearSliderFillColor()),
|
||||||
|
trackHalfThickness);
|
||||||
|
|
||||||
|
const ImVec4 grabColor = active
|
||||||
|
? LinearSliderGrabActiveColor()
|
||||||
|
: (hovered ? LinearSliderGrabHoveredColor() : LinearSliderGrabColor());
|
||||||
|
drawList->AddCircleFilled(
|
||||||
|
ImVec2(knobX, centerY),
|
||||||
|
LinearSliderGrabRadius(),
|
||||||
|
ImGui::GetColorU32(grabColor),
|
||||||
|
20);
|
||||||
|
|
||||||
|
if (!active) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const float trackWidth = std::max(trackMaxX - trackMinX, 1.0f);
|
||||||
|
const float mouseRatio = std::clamp((ImGui::GetIO().MousePos.x - trackMinX) / trackWidth, 0.0f, 1.0f);
|
||||||
|
return std::fabs(mouseRatio - clampedValue) > 0.0001f;
|
||||||
|
}
|
||||||
|
|
||||||
inline bool DrawInt(
|
inline bool DrawInt(
|
||||||
const char* label,
|
const char* label,
|
||||||
int& value,
|
int& value,
|
||||||
float columnWidth = DefaultControlLabelWidth(),
|
const PropertyLayoutSpec& layoutSpec = MakePropertyLayout(),
|
||||||
int step = 1,
|
int step = 1,
|
||||||
int min = 0,
|
int min = 0,
|
||||||
int max = 0
|
int max = 0
|
||||||
) {
|
) {
|
||||||
return DrawControlRow(label, columnWidth, [&]() {
|
return DrawPropertyRow(label, layoutSpec, [&](const PropertyLayoutMetrics& layout) {
|
||||||
|
SetNextPropertyControlWidth(layout);
|
||||||
return ImGui::DragInt("##value", &value, static_cast<float>(step), min, max);
|
return ImGui::DragInt("##value", &value, static_cast<float>(step), min, max);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -37,9 +167,9 @@ inline bool DrawInt(
|
|||||||
inline bool DrawBool(
|
inline bool DrawBool(
|
||||||
const char* label,
|
const char* label,
|
||||||
bool& value,
|
bool& value,
|
||||||
float columnWidth = DefaultControlLabelWidth()
|
const PropertyLayoutSpec& layoutSpec = MakePropertyLayout()
|
||||||
) {
|
) {
|
||||||
return DrawControlRow(label, columnWidth, [&]() {
|
return DrawPropertyRow(label, layoutSpec, [&](const PropertyLayoutMetrics&) {
|
||||||
return ImGui::Checkbox("##value", &value);
|
return ImGui::Checkbox("##value", &value);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -47,9 +177,9 @@ inline bool DrawBool(
|
|||||||
inline bool DrawColor3(
|
inline bool DrawColor3(
|
||||||
const char* label,
|
const char* label,
|
||||||
float color[3],
|
float color[3],
|
||||||
float columnWidth = DefaultControlLabelWidth()
|
const PropertyLayoutSpec& layoutSpec = MakePropertyLayout()
|
||||||
) {
|
) {
|
||||||
return DrawControlRow(label, columnWidth, [&]() {
|
return DrawPropertyRow(label, layoutSpec, [&](const PropertyLayoutMetrics&) {
|
||||||
return ImGui::ColorEdit3("##value", color, ImGuiColorEditFlags_NoInputs);
|
return ImGui::ColorEdit3("##value", color, ImGuiColorEditFlags_NoInputs);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -57,9 +187,9 @@ inline bool DrawColor3(
|
|||||||
inline bool DrawColor4(
|
inline bool DrawColor4(
|
||||||
const char* label,
|
const char* label,
|
||||||
float color[4],
|
float color[4],
|
||||||
float columnWidth = DefaultControlLabelWidth()
|
const PropertyLayoutSpec& layoutSpec = MakePropertyLayout()
|
||||||
) {
|
) {
|
||||||
return DrawControlRow(label, columnWidth, [&]() {
|
return DrawPropertyRow(label, layoutSpec, [&](const PropertyLayoutMetrics&) {
|
||||||
return ImGui::ColorEdit4("##value", color, ImGuiColorEditFlags_NoInputs);
|
return ImGui::ColorEdit4("##value", color, ImGuiColorEditFlags_NoInputs);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -69,11 +199,33 @@ inline bool DrawSliderFloat(
|
|||||||
float& value,
|
float& value,
|
||||||
float min,
|
float min,
|
||||||
float max,
|
float max,
|
||||||
float columnWidth = DefaultControlLabelWidth(),
|
const PropertyLayoutSpec& layoutSpec = MakePropertyLayout(),
|
||||||
const char* format = "%.2f"
|
const char* format = "%.2f"
|
||||||
) {
|
) {
|
||||||
return DrawControlRow(label, columnWidth, [&]() {
|
return DrawPropertyRow(label, layoutSpec, [&](const PropertyLayoutMetrics& layout) {
|
||||||
return ImGui::SliderFloat("##value", &value, min, max, format);
|
const float totalWidth = layout.controlWidth;
|
||||||
|
const float inputWidth = SliderValueFieldWidth();
|
||||||
|
const float spacing = CompoundControlSpacing();
|
||||||
|
const float sliderWidth = ImMax(totalWidth - inputWidth - spacing, 1.0f);
|
||||||
|
const float range = max - min;
|
||||||
|
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(spacing, 0.0f));
|
||||||
|
float normalizedValue = range > 0.0f ? (value - min) / range : 0.0f;
|
||||||
|
bool changed = DrawLinearSlider("##slider", sliderWidth, normalizedValue);
|
||||||
|
if (ImGui::IsItemActive()) {
|
||||||
|
const float trackWidth = std::max(sliderWidth - LinearSliderHorizontalPadding() * 2.0f, 1.0f);
|
||||||
|
const float mouseRatio = std::clamp(
|
||||||
|
(ImGui::GetIO().MousePos.x - (ImGui::GetItemRectMin().x + LinearSliderHorizontalPadding())) / trackWidth,
|
||||||
|
0.0f,
|
||||||
|
1.0f);
|
||||||
|
value = min + range * mouseRatio;
|
||||||
|
}
|
||||||
|
ImGui::SameLine(0.0f, spacing);
|
||||||
|
ImGui::SetNextItemWidth(inputWidth);
|
||||||
|
changed = ImGui::InputFloat("##value", &value, 0.0f, 0.0f, format) || changed;
|
||||||
|
value = std::clamp(value, min, max);
|
||||||
|
ImGui::PopStyleVar();
|
||||||
|
return changed;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,10 +234,32 @@ inline bool DrawSliderInt(
|
|||||||
int& value,
|
int& value,
|
||||||
int min,
|
int min,
|
||||||
int max,
|
int max,
|
||||||
float columnWidth = DefaultControlLabelWidth()
|
const PropertyLayoutSpec& layoutSpec = MakePropertyLayout()
|
||||||
) {
|
) {
|
||||||
return DrawControlRow(label, columnWidth, [&]() {
|
return DrawPropertyRow(label, layoutSpec, [&](const PropertyLayoutMetrics& layout) {
|
||||||
return ImGui::SliderInt("##value", &value, min, max);
|
const float totalWidth = layout.controlWidth;
|
||||||
|
const float inputWidth = SliderValueFieldWidth();
|
||||||
|
const float spacing = CompoundControlSpacing();
|
||||||
|
const float sliderWidth = ImMax(totalWidth - inputWidth - spacing, 1.0f);
|
||||||
|
const int range = max - min;
|
||||||
|
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(spacing, 0.0f));
|
||||||
|
const float normalizedValue = range > 0 ? static_cast<float>(value - min) / static_cast<float>(range) : 0.0f;
|
||||||
|
bool changed = DrawLinearSlider("##slider", sliderWidth, normalizedValue);
|
||||||
|
if (ImGui::IsItemActive()) {
|
||||||
|
const float trackWidth = std::max(sliderWidth - LinearSliderHorizontalPadding() * 2.0f, 1.0f);
|
||||||
|
const float mouseRatio = std::clamp(
|
||||||
|
(ImGui::GetIO().MousePos.x - (ImGui::GetItemRectMin().x + LinearSliderHorizontalPadding())) / trackWidth,
|
||||||
|
0.0f,
|
||||||
|
1.0f);
|
||||||
|
value = min + static_cast<int>(std::round(mouseRatio * static_cast<float>(range)));
|
||||||
|
}
|
||||||
|
ImGui::SameLine(0.0f, spacing);
|
||||||
|
ImGui::SetNextItemWidth(inputWidth);
|
||||||
|
changed = ImGui::InputInt("##value", &value, 0, 0) || changed;
|
||||||
|
value = std::clamp(value, min, max);
|
||||||
|
ImGui::PopStyleVar();
|
||||||
|
return changed;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,14 +268,42 @@ inline int DrawCombo(
|
|||||||
int currentItem,
|
int currentItem,
|
||||||
const char* const items[],
|
const char* const items[],
|
||||||
int itemCount,
|
int itemCount,
|
||||||
float columnWidth = DefaultControlLabelWidth(),
|
const PropertyLayoutSpec& layoutSpec = MakePropertyLayout(),
|
||||||
int heightInItems = -1
|
int heightInItems = -1
|
||||||
) {
|
) {
|
||||||
int changedItem = currentItem;
|
int changedItem = currentItem;
|
||||||
DrawControlRow(label, columnWidth, [&]() {
|
DrawPropertyRow(label, layoutSpec, [&](const PropertyLayoutMetrics& layout) {
|
||||||
ImGui::SetNextItemWidth(-1);
|
const char* popupId = "##value_popup";
|
||||||
if (ImGui::Combo("##value", ¤tItem, items, itemCount, heightInItems)) {
|
const char* previewValue =
|
||||||
changedItem = currentItem;
|
(currentItem >= 0 && currentItem < itemCount && items[currentItem] != nullptr)
|
||||||
|
? items[currentItem]
|
||||||
|
: "";
|
||||||
|
const float comboWidth = layout.controlWidth;
|
||||||
|
const float popupWidth = comboWidth;
|
||||||
|
DrawComboPreviewFrame("##value", previewValue, comboWidth, ImGui::IsPopupOpen(popupId));
|
||||||
|
|
||||||
|
const ImVec2 comboMin = ImGui::GetItemRectMin();
|
||||||
|
const ImVec2 comboMax = ImGui::GetItemRectMax();
|
||||||
|
if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) {
|
||||||
|
ImGui::OpenPopup(popupId);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::SetNextWindowPos(ImVec2(comboMin.x, comboMax.y), ImGuiCond_Always);
|
||||||
|
ImGui::SetNextWindowSizeConstraints(
|
||||||
|
ImVec2(popupWidth, 0.0f),
|
||||||
|
ImVec2(popupWidth, CalcComboPopupMaxHeightFromItemCount(heightInItems)));
|
||||||
|
if (BeginStyledComboPopup(popupId, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings)) {
|
||||||
|
for (int itemIndex = 0; itemIndex < itemCount; ++itemIndex) {
|
||||||
|
const bool selected = itemIndex == currentItem;
|
||||||
|
if (ImGui::Selectable(items[itemIndex], selected)) {
|
||||||
|
changedItem = itemIndex;
|
||||||
|
ImGui::CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
if (selected) {
|
||||||
|
ImGui::SetItemDefaultFocus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EndStyledComboPopup();
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -94,22 +94,82 @@ inline ImVec2 DefaultPanelContentPadding() {
|
|||||||
return ImVec2(8.0f, 6.0f);
|
return ImVec2(8.0f, 6.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline float InspectorPropertyLabelWidth() {
|
inline ImVec2 HierarchyPanelContentPadding() {
|
||||||
return 104.0f;
|
return ImVec2(10.0f, 6.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline ImVec2 ControlCellPadding() {
|
inline float InspectorSectionContentIndent() {
|
||||||
return ImVec2(0.0f, 2.0f);
|
return 12.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline float InspectorPropertyLabelInset() {
|
||||||
|
return 12.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline float InspectorPropertyControlColumnStart() {
|
||||||
|
return 236.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline float InspectorPropertyLabelControlGap() {
|
||||||
|
return 20.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline ImVec2 ControlFramePadding() {
|
inline ImVec2 ControlFramePadding() {
|
||||||
return ImVec2(6.0f, 3.0f);
|
return ImVec2(3.0f, 0.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline float ControlRowHeightOffset() {
|
inline float ControlRowHeightOffset() {
|
||||||
|
return 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline ImVec2 InspectorComponentControlSpacing() {
|
||||||
|
return ImVec2(6.0f, 3.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline float InspectorPropertyControlTrailingInset() {
|
||||||
|
return 8.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline float CompoundControlSpacing() {
|
||||||
|
return 4.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline float SliderValueFieldWidth() {
|
||||||
|
return 64.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline float LinearSliderTrackThickness() {
|
||||||
return 2.0f;
|
return 2.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline float LinearSliderGrabRadius() {
|
||||||
|
return 5.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline float LinearSliderHorizontalPadding() {
|
||||||
|
return 5.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline ImVec4 LinearSliderTrackColor() {
|
||||||
|
return ImVec4(0.30f, 0.30f, 0.30f, 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline ImVec4 LinearSliderFillColor() {
|
||||||
|
return ImVec4(0.66f, 0.66f, 0.66f, 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline ImVec4 LinearSliderGrabColor() {
|
||||||
|
return ImVec4(0.78f, 0.78f, 0.78f, 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline ImVec4 LinearSliderGrabHoveredColor() {
|
||||||
|
return ImVec4(0.88f, 0.88f, 0.88f, 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline ImVec4 LinearSliderGrabActiveColor() {
|
||||||
|
return ImVec4(0.95f, 0.95f, 0.95f, 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
inline ImVec2 InspectorPanelContentPadding() {
|
inline ImVec2 InspectorPanelContentPadding() {
|
||||||
return ImVec2(10.0f, 0.0f);
|
return ImVec2(10.0f, 0.0f);
|
||||||
}
|
}
|
||||||
@@ -147,15 +207,15 @@ inline ImVec4 PanelSplitterIdleColor() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
inline ImVec4 PanelSplitterHoveredColor() {
|
inline ImVec4 PanelSplitterHoveredColor() {
|
||||||
return ImVec4(0.30f, 0.30f, 0.30f, 1.0f);
|
return PanelSplitterIdleColor();
|
||||||
}
|
}
|
||||||
|
|
||||||
inline ImVec4 PanelSplitterActiveColor() {
|
inline ImVec4 PanelSplitterActiveColor() {
|
||||||
return ImVec4(0.34f, 0.34f, 0.34f, 1.0f);
|
return PanelSplitterIdleColor();
|
||||||
}
|
}
|
||||||
|
|
||||||
inline ImVec2 ProjectNavigationPanePadding() {
|
inline ImVec2 ProjectNavigationPanePadding() {
|
||||||
return ImVec2(8.0f, 6.0f);
|
return ImVec2(10.0f, 6.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline ImVec2 ProjectBrowserPanePadding() {
|
inline ImVec2 ProjectBrowserPanePadding() {
|
||||||
@@ -163,21 +223,37 @@ inline ImVec2 ProjectBrowserPanePadding() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
inline ImVec2 NavigationTreeNodeFramePadding() {
|
inline ImVec2 NavigationTreeNodeFramePadding() {
|
||||||
return ImVec2(4.0f, 3.0f);
|
return ImVec2(4.0f, 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline float CompactNavigationTreeItemSpacingY() {
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline float CompactNavigationTreeIndentSpacing() {
|
||||||
|
return 14.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline float NavigationTreeIconSize() {
|
||||||
|
return 17.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline float NavigationTreePrefixWidth() {
|
inline float NavigationTreePrefixWidth() {
|
||||||
return 16.0f;
|
return 18.0f;
|
||||||
}
|
|
||||||
|
|
||||||
inline float NavigationTreePrefixLabelGap() {
|
|
||||||
return 6.0f;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inline float NavigationTreePrefixStartOffset() {
|
inline float NavigationTreePrefixStartOffset() {
|
||||||
|
return -5.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline float NavigationTreePrefixLabelGap() {
|
||||||
return 2.0f;
|
return 2.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline float DisclosureArrowScale() {
|
||||||
|
return 0.14f;
|
||||||
|
}
|
||||||
|
|
||||||
inline ImVec4 NavigationTreePrefixColor(bool selected = false, bool hovered = false) {
|
inline ImVec4 NavigationTreePrefixColor(bool selected = false, bool hovered = false) {
|
||||||
if (selected) {
|
if (selected) {
|
||||||
return ImVec4(0.86f, 0.86f, 0.86f, 1.0f);
|
return ImVec4(0.86f, 0.86f, 0.86f, 1.0f);
|
||||||
@@ -188,16 +264,54 @@ inline ImVec4 NavigationTreePrefixColor(bool selected = false, bool hovered = fa
|
|||||||
return ImVec4(0.62f, 0.62f, 0.62f, 1.0f);
|
return ImVec4(0.62f, 0.62f, 0.62f, 1.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct TreeViewStyle {
|
||||||
|
ImVec2 framePadding = NavigationTreeNodeFramePadding();
|
||||||
|
float itemSpacingY = -1.0f;
|
||||||
|
float indentSpacing = -1.0f;
|
||||||
|
float prefixStartOffset = NavigationTreePrefixStartOffset();
|
||||||
|
float prefixLabelGap = NavigationTreePrefixLabelGap();
|
||||||
|
};
|
||||||
|
|
||||||
|
inline TreeViewStyle NavigationTreeStyle() {
|
||||||
|
TreeViewStyle style;
|
||||||
|
style.itemSpacingY = CompactNavigationTreeItemSpacingY();
|
||||||
|
style.indentSpacing = CompactNavigationTreeIndentSpacing();
|
||||||
|
style.framePadding = NavigationTreeNodeFramePadding();
|
||||||
|
style.prefixStartOffset = NavigationTreePrefixStartOffset();
|
||||||
|
style.prefixLabelGap = NavigationTreePrefixLabelGap();
|
||||||
|
return style;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline TreeViewStyle HierarchyTreeStyle() {
|
||||||
|
return NavigationTreeStyle();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline TreeViewStyle ProjectFolderTreeStyle() {
|
||||||
|
return NavigationTreeStyle();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline ImVec4 ProjectPanelBackgroundColor() {
|
||||||
|
return DockTabSelectedColor();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline ImVec4 ProjectPanelToolbarBackgroundColor() {
|
||||||
|
return ProjectPanelBackgroundColor();
|
||||||
|
}
|
||||||
|
|
||||||
inline ImVec4 ProjectNavigationPaneBackgroundColor() {
|
inline ImVec4 ProjectNavigationPaneBackgroundColor() {
|
||||||
|
return ProjectPanelBackgroundColor();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline ImVec4 ProjectBrowserSurfaceColor() {
|
||||||
return ImVec4(0.20f, 0.20f, 0.20f, 1.0f);
|
return ImVec4(0.20f, 0.20f, 0.20f, 1.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline ImVec4 ProjectBrowserHeaderBackgroundColor() {
|
inline ImVec4 ProjectBrowserHeaderBackgroundColor() {
|
||||||
return ImVec4(0.18f, 0.18f, 0.18f, 1.0f);
|
return ProjectPanelBackgroundColor();
|
||||||
}
|
}
|
||||||
|
|
||||||
inline ImVec4 ProjectBrowserPaneBackgroundColor() {
|
inline ImVec4 ProjectBrowserPaneBackgroundColor() {
|
||||||
return ImVec4(0.22f, 0.22f, 0.22f, 1.0f);
|
return ProjectBrowserSurfaceColor();
|
||||||
}
|
}
|
||||||
|
|
||||||
inline ImVec4 ToolbarBackgroundColor() {
|
inline ImVec4 ToolbarBackgroundColor() {
|
||||||
@@ -228,6 +342,28 @@ inline ImVec4 HintTextColor() {
|
|||||||
return ImVec4(0.53f, 0.53f, 0.53f, 1.0f);
|
return ImVec4(0.53f, 0.53f, 0.53f, 1.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline ImVec2 BreadcrumbSegmentPadding() {
|
||||||
|
return ImVec2(4.0f, 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline float BreadcrumbSegmentSpacing() {
|
||||||
|
return 3.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline ImVec4 BreadcrumbSegmentTextColor(bool current = false, bool hovered = false) {
|
||||||
|
if (hovered) {
|
||||||
|
return ImVec4(0.90f, 0.90f, 0.90f, 1.0f);
|
||||||
|
}
|
||||||
|
if (current) {
|
||||||
|
return ImVec4(0.84f, 0.84f, 0.84f, 1.0f);
|
||||||
|
}
|
||||||
|
return ImVec4(0.72f, 0.72f, 0.72f, 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline ImVec4 BreadcrumbSeparatorColor() {
|
||||||
|
return HintTextColor();
|
||||||
|
}
|
||||||
|
|
||||||
inline float EmptyStateLineOffset() {
|
inline float EmptyStateLineOffset() {
|
||||||
return 20.0f;
|
return 20.0f;
|
||||||
}
|
}
|
||||||
@@ -248,6 +384,122 @@ inline ImVec2 PopupWindowPadding() {
|
|||||||
return ImVec2(12.0f, 10.0f);
|
return ImVec2(12.0f, 10.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline ImVec2 ComboPopupWindowPadding() {
|
||||||
|
return ImVec2(6.0f, 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline ImVec2 ComboPopupItemSpacing() {
|
||||||
|
return ImVec2(0.0f, 0.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline float ComboPopupRounding() {
|
||||||
|
return 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline float ComboPopupBorderSize() {
|
||||||
|
return 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline ImVec4 ComboPopupBackgroundColor() {
|
||||||
|
return ImVec4(1.0f, 1.0f, 1.0f, 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline ImVec4 ComboPopupTextColor() {
|
||||||
|
return ImVec4(0.14f, 0.14f, 0.14f, 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline ImVec4 ComboPopupTextDisabledColor() {
|
||||||
|
return ImVec4(0.55f, 0.55f, 0.55f, 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline ImVec4 ComboPopupItemColor() {
|
||||||
|
return ImVec4(0.0f, 0.0f, 0.0f, 0.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline ImVec4 ComboPopupItemHoveredColor() {
|
||||||
|
return ImVec4(0.0f, 0.0f, 0.0f, 0.045f);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline ImVec4 ComboPopupItemActiveColor() {
|
||||||
|
return ImVec4(0.0f, 0.0f, 0.0f, 0.08f);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline ImVec4 ComboPopupCheckMarkColor() {
|
||||||
|
return ImVec4(0.20f, 0.20f, 0.20f, 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline float PopupWindowRounding() {
|
||||||
|
return 5.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline float PopupWindowBorderSize() {
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline float PopupFrameRounding() {
|
||||||
|
return 4.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline float PopupFrameBorderSize() {
|
||||||
|
return 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline ImVec4 PopupBackgroundColor() {
|
||||||
|
return ImVec4(1.0f, 1.0f, 1.0f, 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline ImVec4 PopupBorderColor() {
|
||||||
|
return ImVec4(0.0f, 0.0f, 0.0f, 0.10f);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline ImVec4 PopupTextColor() {
|
||||||
|
return ImVec4(0.14f, 0.14f, 0.14f, 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline ImVec4 PopupTextDisabledColor() {
|
||||||
|
return ImVec4(0.55f, 0.55f, 0.55f, 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline ImVec4 PopupItemColor() {
|
||||||
|
return ImVec4(0.0f, 0.0f, 0.0f, 0.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline ImVec4 PopupItemHoveredColor() {
|
||||||
|
return ImVec4(0.0f, 0.0f, 0.0f, 0.06f);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline ImVec4 PopupItemActiveColor() {
|
||||||
|
return ImVec4(0.0f, 0.0f, 0.0f, 0.10f);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline ImVec4 PopupFrameColor() {
|
||||||
|
return ImVec4(1.0f, 1.0f, 1.0f, 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline ImVec4 PopupFrameHoveredColor() {
|
||||||
|
return ImVec4(0.965f, 0.965f, 0.965f, 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline ImVec4 PopupFrameActiveColor() {
|
||||||
|
return ImVec4(0.94f, 0.94f, 0.94f, 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline ImVec4 PopupButtonColor() {
|
||||||
|
return ImVec4(0.95f, 0.95f, 0.95f, 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline ImVec4 PopupButtonHoveredColor() {
|
||||||
|
return ImVec4(0.90f, 0.90f, 0.90f, 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline ImVec4 PopupButtonActiveColor() {
|
||||||
|
return ImVec4(0.86f, 0.86f, 0.86f, 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline ImVec4 PopupCheckMarkColor() {
|
||||||
|
return ImVec4(0.20f, 0.20f, 0.20f, 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
inline ImVec2 AssetTileSize() {
|
inline ImVec2 AssetTileSize() {
|
||||||
return ImVec2(104.0f, 82.0f);
|
return ImVec2(104.0f, 82.0f);
|
||||||
}
|
}
|
||||||
@@ -304,6 +556,18 @@ inline ImVec2 AssetTileIconSize() {
|
|||||||
return ImVec2(32.0f, 24.0f);
|
return ImVec2(32.0f, 24.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline ImVec2 FolderAssetTileIconSize() {
|
||||||
|
return ImVec2(72.0f, 72.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline ImVec2 FolderAssetTileIconOffset() {
|
||||||
|
return ImVec2(0.0f, 2.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline float AssetTileIconTextGap() {
|
||||||
|
return 4.0f;
|
||||||
|
}
|
||||||
|
|
||||||
inline ImVec2 AssetTileTextPadding() {
|
inline ImVec2 AssetTileTextPadding() {
|
||||||
return ImVec2(6.0f, 10.0f);
|
return ImVec2(6.0f, 10.0f);
|
||||||
}
|
}
|
||||||
@@ -312,12 +576,16 @@ inline ImVec4 AssetTileTextColor(bool selected = false) {
|
|||||||
return selected ? ImVec4(0.93f, 0.93f, 0.93f, 1.0f) : ImVec4(0.76f, 0.76f, 0.76f, 1.0f);
|
return selected ? ImVec4(0.93f, 0.93f, 0.93f, 1.0f) : ImVec4(0.76f, 0.76f, 0.76f, 1.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline ImVec4 AssetFolderIconFillColor() {
|
inline ImVec4 BuiltInFolderIconTabColor() {
|
||||||
return ImVec4(0.50f, 0.50f, 0.50f, 1.0f);
|
return ImVec4(0.77f, 0.77f, 0.77f, 1.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline ImVec4 AssetFolderIconLineColor() {
|
inline ImVec4 BuiltInFolderIconTopColor() {
|
||||||
return ImVec4(0.80f, 0.80f, 0.80f, 0.90f);
|
return ImVec4(0.82f, 0.82f, 0.82f, 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline ImVec4 BuiltInFolderIconBodyColor() {
|
||||||
|
return ImVec4(0.72f, 0.72f, 0.72f, 1.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline ImVec4 AssetFileIconFillColor() {
|
inline ImVec4 AssetFileIconFillColor() {
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "StyleTokens.h"
|
#include "Core.h"
|
||||||
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace XCEngine {
|
namespace XCEngine {
|
||||||
namespace Editor {
|
namespace Editor {
|
||||||
@@ -90,91 +91,135 @@ struct TreeNodeCallbacks {
|
|||||||
struct TreeNodeDefinition {
|
struct TreeNodeDefinition {
|
||||||
TreeNodeOptions options;
|
TreeNodeOptions options;
|
||||||
std::string_view persistenceKey;
|
std::string_view persistenceKey;
|
||||||
|
TreeViewStyle style;
|
||||||
TreeNodePrefixSlot prefix;
|
TreeNodePrefixSlot prefix;
|
||||||
TreeNodeCallbacks callbacks;
|
TreeNodeCallbacks callbacks;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
inline std::vector<float>& TreeIndentStack() {
|
||||||
|
static std::vector<float> stack;
|
||||||
|
return stack;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void ResetTreeLayout() {
|
||||||
|
TreeIndentStack().clear();
|
||||||
|
}
|
||||||
|
|
||||||
inline TreeNodeResult DrawTreeNode(
|
inline TreeNodeResult DrawTreeNode(
|
||||||
TreeViewState* state,
|
TreeViewState* state,
|
||||||
const void* id,
|
const void* id,
|
||||||
const char* label,
|
const char* label,
|
||||||
const TreeNodeDefinition& definition = {}) {
|
const TreeNodeDefinition& definition = {}) {
|
||||||
|
const char* nodeLabel = label ? label : "";
|
||||||
TreeNodeOptions options = definition.options;
|
TreeNodeOptions options = definition.options;
|
||||||
if (state && !definition.persistenceKey.empty()) {
|
|
||||||
ImGui::SetNextItemOpen(state->ResolveExpanded(definition.persistenceKey, options.defaultOpen), ImGuiCond_Always);
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGuiTreeNodeFlags flags = 0;
|
const TreeViewStyle& visualStyle = definition.style;
|
||||||
if (options.openOnArrow) {
|
const ImVec2 nodeFramePadding = options.framePadding ? visualStyle.framePadding : ImVec2(0.0f, 0.0f);
|
||||||
flags |= ImGuiTreeNodeFlags_OpenOnArrow;
|
const ImVec2 currentItemSpacing = ImGui::GetStyle().ItemSpacing;
|
||||||
}
|
const float itemSpacingY = visualStyle.itemSpacingY >= 0.0f ? visualStyle.itemSpacingY : currentItemSpacing.y;
|
||||||
if (options.openOnDoubleClick) {
|
const float indentSpacing = visualStyle.indentSpacing >= 0.0f ? visualStyle.indentSpacing : ImGui::GetStyle().IndentSpacing;
|
||||||
flags |= ImGuiTreeNodeFlags_OpenOnDoubleClick;
|
const float depth = static_cast<float>(TreeIndentStack().size());
|
||||||
}
|
const float treeRootX = ImGui::GetCursorStartPos().x;
|
||||||
if (options.spanAvailWidth) {
|
const float arrowSlotWidth = ImGui::GetTreeNodeToLabelSpacing();
|
||||||
flags |= ImGuiTreeNodeFlags_SpanAvailWidth;
|
const float prefixWidth = definition.prefix.IsVisible() ? definition.prefix.width : 0.0f;
|
||||||
}
|
const float prefixGap = definition.prefix.IsVisible() ? visualStyle.prefixLabelGap : 0.0f;
|
||||||
if (options.framePadding) {
|
const ImVec2 labelSize = ImGui::CalcTextSize(nodeLabel, nullptr, false);
|
||||||
flags |= ImGuiTreeNodeFlags_FramePadding;
|
const float rowHeight = ImMax(labelSize.y, ImGui::GetFontSize()) + nodeFramePadding.y * 2.0f;
|
||||||
}
|
ImGui::SetCursorPosX(treeRootX + depth * indentSpacing);
|
||||||
if (options.leaf) {
|
const float availableWidth = options.spanAvailWidth
|
||||||
flags |= ImGuiTreeNodeFlags_Leaf;
|
? ImMax(ImGui::GetContentRegionAvail().x, 1.0f)
|
||||||
}
|
: arrowSlotWidth + visualStyle.prefixStartOffset + prefixWidth + prefixGap + labelSize.x + nodeFramePadding.x;
|
||||||
if (options.selected) {
|
|
||||||
flags |= ImGuiTreeNodeFlags_Selected;
|
|
||||||
}
|
|
||||||
if (options.defaultOpen) {
|
|
||||||
flags |= ImGuiTreeNodeFlags_DefaultOpen;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string displayLabel = label ? label : "";
|
ImGui::PushID(id);
|
||||||
if (definition.prefix.IsVisible()) {
|
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(currentItemSpacing.x, itemSpacingY));
|
||||||
const float reserveWidth =
|
ImGui::InvisibleButton("##TreeNode", ImVec2(availableWidth, rowHeight));
|
||||||
NavigationTreePrefixStartOffset() +
|
|
||||||
definition.prefix.width +
|
|
||||||
NavigationTreePrefixLabelGap();
|
|
||||||
const float spaceWidth = ImGui::CalcTextSize(" ").x;
|
|
||||||
int spaceCount = static_cast<int>(reserveWidth / (spaceWidth > 0.0f ? spaceWidth : 1.0f)) + 2;
|
|
||||||
if (spaceCount < 1) {
|
|
||||||
spaceCount = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
displayLabel.insert(0, static_cast<size_t>(spaceCount), ' ');
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, NavigationTreeNodeFramePadding());
|
|
||||||
const bool open = ImGui::TreeNodeEx(id, flags, "%s", displayLabel.c_str());
|
|
||||||
ImGui::PopStyleVar();
|
ImGui::PopStyleVar();
|
||||||
|
|
||||||
TreeNodeResult result{
|
|
||||||
open,
|
|
||||||
ImGui::IsItemClicked() && !ImGui::IsItemToggledOpen(),
|
|
||||||
ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(0),
|
|
||||||
ImGui::IsItemClicked(ImGuiMouseButton_Right),
|
|
||||||
ImGui::IsItemHovered(),
|
|
||||||
ImGui::IsItemToggledOpen()
|
|
||||||
};
|
|
||||||
|
|
||||||
if (state && !definition.persistenceKey.empty()) {
|
|
||||||
state->SetExpanded(definition.persistenceKey, result.open);
|
|
||||||
}
|
|
||||||
|
|
||||||
const ImVec2 itemMin = ImGui::GetItemRectMin();
|
const ImVec2 itemMin = ImGui::GetItemRectMin();
|
||||||
const ImVec2 itemMax = ImGui::GetItemRectMax();
|
const ImVec2 itemMax = ImGui::GetItemRectMax();
|
||||||
const float labelStartX = itemMin.x + ImGui::GetTreeNodeToLabelSpacing();
|
const ImRect rowRect(itemMin, itemMax);
|
||||||
|
const ImRect arrowRect(
|
||||||
|
ImVec2(itemMin.x + nodeFramePadding.x, itemMin.y),
|
||||||
|
ImVec2(itemMin.x + arrowSlotWidth, itemMax.y));
|
||||||
|
|
||||||
|
bool open = !options.leaf && options.defaultOpen;
|
||||||
|
if (state && !definition.persistenceKey.empty()) {
|
||||||
|
open = !options.leaf && state->ResolveExpanded(definition.persistenceKey, options.defaultOpen);
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool hovered = ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem);
|
||||||
|
const bool leftClicked = ImGui::IsItemClicked(ImGuiMouseButton_Left);
|
||||||
|
const bool rightClicked = ImGui::IsItemClicked(ImGuiMouseButton_Right);
|
||||||
|
const bool doubleClicked = hovered && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left);
|
||||||
|
const bool arrowHovered = !options.leaf && arrowRect.Contains(ImGui::GetIO().MousePos);
|
||||||
|
|
||||||
|
bool toggledOpen = false;
|
||||||
|
if (!options.leaf) {
|
||||||
|
if (options.openOnArrow && leftClicked && arrowHovered) {
|
||||||
|
toggledOpen = true;
|
||||||
|
} else if (!options.openOnArrow && leftClicked) {
|
||||||
|
toggledOpen = true;
|
||||||
|
} else if (options.openOnDoubleClick && doubleClicked && !arrowHovered) {
|
||||||
|
toggledOpen = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (toggledOpen) {
|
||||||
|
open = !open;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state && !definition.persistenceKey.empty()) {
|
||||||
|
state->SetExpanded(definition.persistenceKey, open);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImDrawList* drawList = ImGui::GetWindowDrawList();
|
||||||
|
if (options.selected || hovered) {
|
||||||
|
const ImU32 rowColor = ImGui::GetColorU32(
|
||||||
|
(options.selected && hovered) ? ImGuiCol_HeaderActive :
|
||||||
|
options.selected ? ImGuiCol_Header :
|
||||||
|
ImGuiCol_HeaderHovered);
|
||||||
|
drawList->AddRectFilled(rowRect.Min, rowRect.Max, rowColor, 0.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!options.leaf) {
|
||||||
|
DrawDisclosureArrow(drawList, arrowRect.Min, arrowRect.Max, open, ImGui::GetColorU32(ImGuiCol_Text));
|
||||||
|
}
|
||||||
|
|
||||||
|
const float baseTextX = itemMin.x + arrowSlotWidth;
|
||||||
|
float labelX = baseTextX;
|
||||||
|
|
||||||
if (definition.prefix.IsVisible()) {
|
if (definition.prefix.IsVisible()) {
|
||||||
const float prefixMinX = labelStartX + NavigationTreePrefixStartOffset();
|
const float prefixMinX = baseTextX + visualStyle.prefixStartOffset;
|
||||||
const float prefixMaxX = prefixMinX + definition.prefix.width;
|
const float prefixMaxX = prefixMinX + definition.prefix.width;
|
||||||
definition.prefix.draw(TreeNodePrefixContext{
|
definition.prefix.draw(TreeNodePrefixContext{
|
||||||
ImGui::GetWindowDrawList(),
|
drawList,
|
||||||
ImVec2(prefixMinX, itemMin.y),
|
ImVec2(prefixMinX, itemMin.y),
|
||||||
ImVec2(prefixMaxX, itemMax.y),
|
ImVec2(prefixMaxX, itemMax.y),
|
||||||
options.selected,
|
options.selected,
|
||||||
result.hovered
|
hovered
|
||||||
});
|
});
|
||||||
|
labelX = prefixMaxX + visualStyle.prefixLabelGap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (nodeLabel[0] != '\0') {
|
||||||
|
const float textY = itemMin.y + ((itemMax.y - itemMin.y) - labelSize.y) * 0.5f;
|
||||||
|
drawList->PushClipRect(ImVec2(labelX, itemMin.y), itemMax, true);
|
||||||
|
drawList->AddText(
|
||||||
|
ImVec2(labelX, textY),
|
||||||
|
ImGui::GetColorU32(ImGuiCol_Text),
|
||||||
|
nodeLabel);
|
||||||
|
drawList->PopClipRect();
|
||||||
|
}
|
||||||
|
|
||||||
|
TreeNodeResult result{
|
||||||
|
open,
|
||||||
|
leftClicked && !toggledOpen,
|
||||||
|
doubleClicked,
|
||||||
|
rightClicked,
|
||||||
|
hovered,
|
||||||
|
toggledOpen
|
||||||
|
};
|
||||||
|
|
||||||
if (definition.callbacks.onInteraction) {
|
if (definition.callbacks.onInteraction) {
|
||||||
definition.callbacks.onInteraction(result);
|
definition.callbacks.onInteraction(result);
|
||||||
}
|
}
|
||||||
@@ -182,6 +227,12 @@ inline TreeNodeResult DrawTreeNode(
|
|||||||
definition.callbacks.onRenderExtras();
|
definition.callbacks.onRenderExtras();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (open) {
|
||||||
|
TreeIndentStack().push_back(indentSpacing);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::PopID();
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -192,7 +243,11 @@ inline TreeNodeResult DrawTreeNode(const void* id, const char* label, const Tree
|
|||||||
}
|
}
|
||||||
|
|
||||||
inline void EndTreeNode() {
|
inline void EndTreeNode() {
|
||||||
ImGui::TreePop();
|
auto& stack = TreeIndentStack();
|
||||||
|
if (stack.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
stack.pop_back();
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace UI
|
} // namespace UI
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include "AboutEditorDialog.h"
|
#include "AboutEditorDialog.h"
|
||||||
#include "BaseTheme.h"
|
#include "BaseTheme.h"
|
||||||
|
#include "BuiltInIcons.h"
|
||||||
#include "ConsoleFilterState.h"
|
#include "ConsoleFilterState.h"
|
||||||
#include "ConsoleLogFormatter.h"
|
#include "ConsoleLogFormatter.h"
|
||||||
#include "Core.h"
|
#include "Core.h"
|
||||||
@@ -10,6 +11,7 @@
|
|||||||
#include "DividerChrome.h"
|
#include "DividerChrome.h"
|
||||||
#include "PanelChrome.h"
|
#include "PanelChrome.h"
|
||||||
#include "PopupState.h"
|
#include "PopupState.h"
|
||||||
|
#include "PropertyLayout.h"
|
||||||
#include "PropertyGrid.h"
|
#include "PropertyGrid.h"
|
||||||
#include "SplitterChrome.h"
|
#include "SplitterChrome.h"
|
||||||
#include "ScalarControls.h"
|
#include "ScalarControls.h"
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "Core.h"
|
#include "Core.h"
|
||||||
|
#include "PropertyLayout.h"
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
#include <XCEngine/Core/Math/Vector3.h>
|
#include <XCEngine/Core/Math/Vector3.h>
|
||||||
#include <XCEngine/Core/Math/Vector2.h>
|
#include <XCEngine/Core/Math/Vector2.h>
|
||||||
@@ -16,6 +17,7 @@ struct AxisFloatControlSpec {
|
|||||||
};
|
};
|
||||||
|
|
||||||
inline bool DrawAxisFloatControls(
|
inline bool DrawAxisFloatControls(
|
||||||
|
const PropertyLayoutMetrics& layout,
|
||||||
const AxisFloatControlSpec* axes,
|
const AxisFloatControlSpec* axes,
|
||||||
int axisCount,
|
int axisCount,
|
||||||
float dragSpeed,
|
float dragSpeed,
|
||||||
@@ -30,7 +32,7 @@ inline bool DrawAxisFloatControls(
|
|||||||
ImGuiStyleVar_ItemSpacing,
|
ImGuiStyleVar_ItemSpacing,
|
||||||
useResetButtons ? VectorAxisControlSpacing() : VectorAxisInputSpacing());
|
useResetButtons ? VectorAxisControlSpacing() : VectorAxisInputSpacing());
|
||||||
|
|
||||||
const float availableWidth = ImGui::GetContentRegionAvail().x;
|
const float availableWidth = layout.controlWidth;
|
||||||
const float spacing = ImGui::GetStyle().ItemSpacing.x;
|
const float spacing = ImGui::GetStyle().ItemSpacing.x;
|
||||||
|
|
||||||
if (useResetButtons) {
|
if (useResetButtons) {
|
||||||
@@ -101,7 +103,7 @@ inline bool DrawAxisFloatControls(
|
|||||||
inline bool DrawVec3Input(
|
inline bool DrawVec3Input(
|
||||||
const char* label,
|
const char* label,
|
||||||
::XCEngine::Math::Vector3& values,
|
::XCEngine::Math::Vector3& values,
|
||||||
float columnWidth = DefaultControlLabelWidth(),
|
const PropertyLayoutSpec& layoutSpec = MakePropertyLayout(),
|
||||||
float dragSpeed = 0.1f,
|
float dragSpeed = 0.1f,
|
||||||
bool* isActive = nullptr
|
bool* isActive = nullptr
|
||||||
) {
|
) {
|
||||||
@@ -111,8 +113,8 @@ inline bool DrawVec3Input(
|
|||||||
{ "Z", &values.z }
|
{ "Z", &values.z }
|
||||||
};
|
};
|
||||||
|
|
||||||
return DrawControlRow(label, columnWidth, [&]() {
|
return DrawPropertyRow(label, layoutSpec, [&](const PropertyLayoutMetrics& layout) {
|
||||||
return DrawAxisFloatControls(axes, 3, dragSpeed, false, 0.0f, isActive);
|
return DrawAxisFloatControls(layout, axes, 3, dragSpeed, false, 0.0f, isActive);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,7 +122,7 @@ inline bool DrawVec3(
|
|||||||
const char* label,
|
const char* label,
|
||||||
::XCEngine::Math::Vector3& values,
|
::XCEngine::Math::Vector3& values,
|
||||||
float resetValue = 0.0f,
|
float resetValue = 0.0f,
|
||||||
float columnWidth = DefaultControlLabelWidth(),
|
const PropertyLayoutSpec& layoutSpec = MakePropertyLayout(),
|
||||||
float dragSpeed = 0.1f,
|
float dragSpeed = 0.1f,
|
||||||
bool* isActive = nullptr
|
bool* isActive = nullptr
|
||||||
) {
|
) {
|
||||||
@@ -130,8 +132,8 @@ inline bool DrawVec3(
|
|||||||
{ "Z", &values.z }
|
{ "Z", &values.z }
|
||||||
};
|
};
|
||||||
|
|
||||||
return DrawControlRow(label, columnWidth, [&]() {
|
return DrawPropertyRow(label, layoutSpec, [&](const PropertyLayoutMetrics& layout) {
|
||||||
return DrawAxisFloatControls(axes, 3, dragSpeed, true, resetValue, isActive);
|
return DrawAxisFloatControls(layout, axes, 3, dragSpeed, true, resetValue, isActive);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,7 +141,7 @@ inline bool DrawVec2(
|
|||||||
const char* label,
|
const char* label,
|
||||||
::XCEngine::Math::Vector2& values,
|
::XCEngine::Math::Vector2& values,
|
||||||
float resetValue = 0.0f,
|
float resetValue = 0.0f,
|
||||||
float columnWidth = DefaultControlLabelWidth(),
|
const PropertyLayoutSpec& layoutSpec = MakePropertyLayout(),
|
||||||
float dragSpeed = 0.1f
|
float dragSpeed = 0.1f
|
||||||
) {
|
) {
|
||||||
const AxisFloatControlSpec axes[] = {
|
const AxisFloatControlSpec axes[] = {
|
||||||
@@ -147,8 +149,8 @@ inline bool DrawVec2(
|
|||||||
{ "Y", &values.y }
|
{ "Y", &values.y }
|
||||||
};
|
};
|
||||||
|
|
||||||
return DrawControlRow(label, columnWidth, [&]() {
|
return DrawPropertyRow(label, layoutSpec, [&](const PropertyLayoutMetrics& layout) {
|
||||||
return DrawAxisFloatControls(axes, 2, dragSpeed, true, resetValue);
|
return DrawAxisFloatControls(layout, axes, 2, dragSpeed, true, resetValue);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ namespace UI {
|
|||||||
|
|
||||||
struct ComponentSectionResult {
|
struct ComponentSectionResult {
|
||||||
bool open = false;
|
bool open = false;
|
||||||
|
float contentIndent = 0.0f;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct AssetTileResult {
|
struct AssetTileResult {
|
||||||
@@ -23,9 +24,12 @@ struct AssetTileResult {
|
|||||||
ImVec2 max = ImVec2(0.0f, 0.0f);
|
ImVec2 max = ImVec2(0.0f, 0.0f);
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class AssetIconKind {
|
struct AssetTileOptions {
|
||||||
Folder,
|
ImVec2 size = AssetTileSize();
|
||||||
File
|
ImVec2 iconOffset = AssetTileIconOffset();
|
||||||
|
ImVec2 iconSize = AssetTileIconSize();
|
||||||
|
bool drawIdleFrame = true;
|
||||||
|
bool drawSelectionBorder = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class DialogActionResult {
|
enum class DialogActionResult {
|
||||||
@@ -61,11 +65,16 @@ struct MenuCommand {
|
|||||||
|
|
||||||
template <typename DrawContentFn>
|
template <typename DrawContentFn>
|
||||||
inline bool DrawMenuScope(const char* label, DrawContentFn&& drawContent) {
|
inline bool DrawMenuScope(const char* label, DrawContentFn&& drawContent) {
|
||||||
|
PushPopupWindowChrome();
|
||||||
if (!ImGui::BeginMenu(label)) {
|
if (!ImGui::BeginMenu(label)) {
|
||||||
|
PopPopupWindowChrome();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
PopPopupWindowChrome();
|
||||||
|
|
||||||
|
PushPopupContentChrome();
|
||||||
drawContent();
|
drawContent();
|
||||||
|
PopPopupContentChrome();
|
||||||
ImGui::EndMenu();
|
ImGui::EndMenu();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -142,39 +151,105 @@ inline void DrawEmptyState(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline float BreadcrumbItemHeight() {
|
||||||
|
return ImGui::GetTextLineHeight() + BreadcrumbSegmentPadding().y * 2.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void DrawBreadcrumbTextItem(
|
||||||
|
const char* label,
|
||||||
|
const ImVec2& size,
|
||||||
|
const ImVec4& color,
|
||||||
|
bool clickable,
|
||||||
|
bool* pressed = nullptr,
|
||||||
|
bool* hovered = nullptr) {
|
||||||
|
bool localPressed = false;
|
||||||
|
if (clickable) {
|
||||||
|
localPressed = ImGui::InvisibleButton("##BreadcrumbItem", size);
|
||||||
|
} else {
|
||||||
|
ImGui::Dummy(size);
|
||||||
|
}
|
||||||
|
const bool localHovered = clickable && ImGui::IsItemHovered();
|
||||||
|
|
||||||
|
const ImVec2 itemMin = ImGui::GetItemRectMin();
|
||||||
|
const ImVec2 itemMax = ImGui::GetItemRectMax();
|
||||||
|
const ImVec2 textSize = ImGui::CalcTextSize(label);
|
||||||
|
const float textX = itemMin.x + (size.x - textSize.x) * 0.5f;
|
||||||
|
const float textY = itemMin.y + (itemMax.y - itemMin.y - textSize.y) * 0.5f;
|
||||||
|
|
||||||
|
ImGui::GetWindowDrawList()->AddText(
|
||||||
|
ImVec2(textX, textY),
|
||||||
|
ImGui::GetColorU32(color),
|
||||||
|
label);
|
||||||
|
|
||||||
|
if (pressed) {
|
||||||
|
*pressed = localPressed;
|
||||||
|
}
|
||||||
|
if (hovered) {
|
||||||
|
*hovered = localHovered;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool DrawBreadcrumbSegment(const char* label, bool clickable, bool current = false) {
|
||||||
|
if (!label || label[0] == '\0') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ImVec2 padding = BreadcrumbSegmentPadding();
|
||||||
|
const ImVec2 textSize = ImGui::CalcTextSize(label);
|
||||||
|
const ImVec2 size(textSize.x + padding.x * 2.0f, BreadcrumbItemHeight());
|
||||||
|
bool pressed = false;
|
||||||
|
bool hovered = false;
|
||||||
|
DrawBreadcrumbTextItem(label, size, BreadcrumbSegmentTextColor(current, hovered), clickable, &pressed, &hovered);
|
||||||
|
|
||||||
|
if (hovered) {
|
||||||
|
const ImVec2 itemMin = ImGui::GetItemRectMin();
|
||||||
|
const ImVec2 itemMax = ImGui::GetItemRectMax();
|
||||||
|
const ImVec2 textOnlySize = ImGui::CalcTextSize(label);
|
||||||
|
const float textX = itemMin.x + (size.x - textOnlySize.x) * 0.5f;
|
||||||
|
const float textY = itemMin.y + (itemMax.y - itemMin.y - textOnlySize.y) * 0.5f;
|
||||||
|
ImGui::GetWindowDrawList()->AddText(
|
||||||
|
ImVec2(textX, textY),
|
||||||
|
ImGui::GetColorU32(BreadcrumbSegmentTextColor(current, true)),
|
||||||
|
label);
|
||||||
|
}
|
||||||
|
|
||||||
|
return pressed;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void DrawBreadcrumbSeparator(const char* label = ">") {
|
||||||
|
const ImVec2 textSize = ImGui::CalcTextSize(label);
|
||||||
|
DrawBreadcrumbTextItem(label, ImVec2(textSize.x, BreadcrumbItemHeight()), BreadcrumbSeparatorColor(), false);
|
||||||
|
}
|
||||||
|
|
||||||
template <typename GetNameFn, typename NavigateFn>
|
template <typename GetNameFn, typename NavigateFn>
|
||||||
inline void DrawToolbarBreadcrumbs(
|
inline void DrawToolbarBreadcrumbs(
|
||||||
const char* rootLabel,
|
const char* rootLabel,
|
||||||
size_t segmentCount,
|
size_t segmentCount,
|
||||||
GetNameFn&& getName,
|
GetNameFn&& getName,
|
||||||
NavigateFn&& navigateToSegment) {
|
NavigateFn&& navigateToSegment) {
|
||||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.0f, 0.0f, 0.0f, 0.0f));
|
|
||||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.0f, 0.0f, 0.0f, 0.0f));
|
|
||||||
|
|
||||||
if (segmentCount == 0) {
|
if (segmentCount == 0) {
|
||||||
ImGui::TextUnformatted(rootLabel);
|
DrawBreadcrumbSegment(rootLabel, false, true);
|
||||||
ImGui::PopStyleColor(2);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (size_t i = 0; i < segmentCount; ++i) {
|
for (size_t i = 0; i < segmentCount; ++i) {
|
||||||
if (i > 0) {
|
if (i > 0) {
|
||||||
ImGui::SameLine();
|
ImGui::SameLine(0.0f, BreadcrumbSegmentSpacing());
|
||||||
ImGui::TextDisabled("/");
|
DrawBreadcrumbSeparator();
|
||||||
ImGui::SameLine();
|
ImGui::SameLine(0.0f, BreadcrumbSegmentSpacing());
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::string label = getName(i);
|
const std::string label = (i == 0 && rootLabel && rootLabel[0] != '\0')
|
||||||
if (i + 1 < segmentCount) {
|
? std::string(rootLabel)
|
||||||
if (ImGui::Button(label.c_str())) {
|
: getName(i);
|
||||||
navigateToSegment(i);
|
const bool current = (i + 1 == segmentCount);
|
||||||
}
|
|
||||||
} else {
|
ImGui::PushID(static_cast<int>(i));
|
||||||
ImGui::Text("%s", label.c_str());
|
if (DrawBreadcrumbSegment(label.c_str(), !current, current)) {
|
||||||
|
navigateToSegment(i);
|
||||||
}
|
}
|
||||||
|
ImGui::PopID();
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::PopStyleColor(2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename DrawIconFn>
|
template <typename DrawIconFn>
|
||||||
@@ -182,8 +257,18 @@ inline AssetTileResult DrawAssetTile(
|
|||||||
const char* label,
|
const char* label,
|
||||||
bool selected,
|
bool selected,
|
||||||
bool dimmed,
|
bool dimmed,
|
||||||
DrawIconFn&& drawIcon) {
|
DrawIconFn&& drawIcon,
|
||||||
const ImVec2 tileSize = AssetTileSize();
|
const AssetTileOptions& options = AssetTileOptions()) {
|
||||||
|
const ImVec2 textSize = ImGui::CalcTextSize(label);
|
||||||
|
ImVec2 tileSize = options.size;
|
||||||
|
tileSize.x = std::max(tileSize.x, options.iconSize.x + AssetTileTextPadding().x * 2.0f);
|
||||||
|
tileSize.y = std::max(
|
||||||
|
tileSize.y,
|
||||||
|
options.iconOffset.y +
|
||||||
|
options.iconSize.y +
|
||||||
|
AssetTileIconTextGap() +
|
||||||
|
textSize.y +
|
||||||
|
AssetTileTextPadding().y);
|
||||||
ImGui::InvisibleButton("##AssetBtn", tileSize);
|
ImGui::InvisibleButton("##AssetBtn", tileSize);
|
||||||
|
|
||||||
const bool clicked = ImGui::IsItemClicked(ImGuiMouseButton_Left);
|
const bool clicked = ImGui::IsItemClicked(ImGuiMouseButton_Left);
|
||||||
@@ -195,58 +280,38 @@ inline AssetTileResult DrawAssetTile(
|
|||||||
const ImVec2 max = ImVec2(min.x + tileSize.x, min.y + tileSize.y);
|
const ImVec2 max = ImVec2(min.x + tileSize.x, min.y + tileSize.y);
|
||||||
ImDrawList* drawList = ImGui::GetWindowDrawList();
|
ImDrawList* drawList = ImGui::GetWindowDrawList();
|
||||||
|
|
||||||
drawList->AddRectFilled(min, max, ImGui::GetColorU32(AssetTileIdleFillColor()), AssetTileRounding());
|
if (options.drawIdleFrame) {
|
||||||
drawList->AddRect(min, max, ImGui::GetColorU32(AssetTileIdleBorderColor()), AssetTileRounding());
|
drawList->AddRectFilled(min, max, ImGui::GetColorU32(AssetTileIdleFillColor()), AssetTileRounding());
|
||||||
|
drawList->AddRect(min, max, ImGui::GetColorU32(AssetTileIdleBorderColor()), AssetTileRounding());
|
||||||
|
}
|
||||||
|
|
||||||
if (hovered || selected) {
|
if (hovered || selected) {
|
||||||
drawList->AddRectFilled(min, max, ImGui::GetColorU32(selected ? AssetTileSelectedFillColor() : AssetTileHoverFillColor()), AssetTileRounding());
|
drawList->AddRectFilled(min, max, ImGui::GetColorU32(selected ? AssetTileSelectedFillColor() : AssetTileHoverFillColor()), AssetTileRounding());
|
||||||
}
|
}
|
||||||
if (selected) {
|
if (selected && options.drawSelectionBorder) {
|
||||||
drawList->AddRect(min, max, ImGui::GetColorU32(AssetTileSelectedBorderColor()), AssetTileRounding());
|
drawList->AddRect(min, max, ImGui::GetColorU32(AssetTileSelectedBorderColor()), AssetTileRounding());
|
||||||
}
|
}
|
||||||
if (dimmed) {
|
if (dimmed) {
|
||||||
drawList->AddRectFilled(min, max, ImGui::GetColorU32(AssetTileDraggedOverlayColor()), 0.0f);
|
drawList->AddRectFilled(min, max, ImGui::GetColorU32(AssetTileDraggedOverlayColor()), 0.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
const ImVec2 iconOffset = AssetTileIconOffset();
|
const ImVec2 iconMin(
|
||||||
const ImVec2 iconSize = AssetTileIconSize();
|
min.x + (tileSize.x - options.iconSize.x) * 0.5f,
|
||||||
const ImVec2 iconMin(min.x + iconOffset.x, min.y + iconOffset.y);
|
min.y + options.iconOffset.y);
|
||||||
const ImVec2 iconMax(iconMin.x + iconSize.x, iconMin.y + iconSize.y);
|
const ImVec2 iconMax(iconMin.x + options.iconSize.x, iconMin.y + options.iconSize.y);
|
||||||
drawIcon(drawList, iconMin, iconMax);
|
drawIcon(drawList, iconMin, iconMax);
|
||||||
|
|
||||||
const ImVec2 textSize = ImGui::CalcTextSize(label);
|
const ImVec2 textPadding = AssetTileTextPadding();
|
||||||
|
const float textAreaWidth = tileSize.x - textPadding.x * 2.0f;
|
||||||
|
const float centeredTextX = min.x + textPadding.x + std::max(0.0f, (textAreaWidth - textSize.x) * 0.5f);
|
||||||
const float textY = max.y - textSize.y - AssetTileTextPadding().y;
|
const float textY = max.y - textSize.y - AssetTileTextPadding().y;
|
||||||
ImGui::PushClipRect(ImVec2(min.x + AssetTileTextPadding().x, min.y), ImVec2(max.x - AssetTileTextPadding().x, max.y), true);
|
ImGui::PushClipRect(ImVec2(min.x + textPadding.x, min.y), ImVec2(max.x - textPadding.x, max.y), true);
|
||||||
drawList->AddText(ImVec2(min.x + AssetTileTextPadding().x, textY), ImGui::GetColorU32(AssetTileTextColor(selected)), label);
|
drawList->AddText(ImVec2(centeredTextX, textY), ImGui::GetColorU32(AssetTileTextColor(selected)), label);
|
||||||
ImGui::PopClipRect();
|
ImGui::PopClipRect();
|
||||||
|
|
||||||
return AssetTileResult{ clicked, contextRequested, openRequested, hovered, min, max };
|
return AssetTileResult{ clicked, contextRequested, openRequested, hovered, min, max };
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void DrawAssetIcon(ImDrawList* drawList, const ImVec2& min, const ImVec2& max, AssetIconKind kind) {
|
|
||||||
if (kind == AssetIconKind::Folder) {
|
|
||||||
const ImU32 fillColor = ImGui::GetColorU32(AssetFolderIconFillColor());
|
|
||||||
const ImU32 lineColor = ImGui::GetColorU32(AssetFolderIconLineColor());
|
|
||||||
const float width = max.x - min.x;
|
|
||||||
const float height = max.y - min.y;
|
|
||||||
const ImVec2 tabMax(min.x + width * 0.45f, min.y + height * 0.35f);
|
|
||||||
drawList->AddRectFilled(ImVec2(min.x, min.y + height * 0.14f), tabMax, fillColor, 2.0f);
|
|
||||||
drawList->AddRectFilled(ImVec2(min.x, min.y + height * 0.28f), max, fillColor, 2.0f);
|
|
||||||
drawList->AddRect(ImVec2(min.x, min.y + height * 0.14f), tabMax, lineColor, 2.0f);
|
|
||||||
drawList->AddRect(ImVec2(min.x, min.y + height * 0.28f), max, lineColor, 2.0f);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ImU32 fillColor = ImGui::GetColorU32(AssetFileIconFillColor());
|
|
||||||
const ImU32 lineColor = ImGui::GetColorU32(AssetFileIconLineColor());
|
|
||||||
const ImVec2 foldA(max.x - 8.0f, min.y);
|
|
||||||
const ImVec2 foldB(max.x, min.y + 8.0f);
|
|
||||||
drawList->AddRectFilled(min, max, fillColor, 2.0f);
|
|
||||||
drawList->AddRect(min, max, lineColor, 2.0f);
|
|
||||||
drawList->AddTriangleFilled(foldA, ImVec2(max.x, min.y), foldB, ImGui::GetColorU32(AssetFileFoldColor()));
|
|
||||||
drawList->AddLine(foldA, foldB, lineColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename DrawMenuFn>
|
template <typename DrawMenuFn>
|
||||||
inline ComponentSectionResult BeginComponentSection(
|
inline ComponentSectionResult BeginComponentSection(
|
||||||
const void* id,
|
const void* id,
|
||||||
@@ -254,27 +319,68 @@ inline ComponentSectionResult BeginComponentSection(
|
|||||||
DrawMenuFn&& drawMenu,
|
DrawMenuFn&& drawMenu,
|
||||||
bool defaultOpen = true) {
|
bool defaultOpen = true) {
|
||||||
const ImGuiStyle& style = ImGui::GetStyle();
|
const ImGuiStyle& style = ImGui::GetStyle();
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, InspectorSectionFramePadding());
|
const ImVec2 framePadding = InspectorSectionFramePadding();
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(style.ItemSpacing.x, InspectorSectionItemSpacing().y));
|
const float availableWidth = ImMax(ImGui::GetContentRegionAvail().x, 1.0f);
|
||||||
|
const float arrowSlotWidth = ImGui::GetTreeNodeToLabelSpacing();
|
||||||
|
const ImVec2 labelSize = ImGui::CalcTextSize(label ? label : "", nullptr, false);
|
||||||
|
const float rowHeight = ImMax(labelSize.y, ImGui::GetFontSize()) + framePadding.y * 2.0f;
|
||||||
|
|
||||||
ImGuiTreeNodeFlags flags =
|
ImGui::PushID(id);
|
||||||
ImGuiTreeNodeFlags_Framed |
|
const ImGuiID openStateId = ImGui::GetID("##ComponentSectionOpen");
|
||||||
ImGuiTreeNodeFlags_SpanAvailWidth |
|
ImGuiStorage* storage = ImGui::GetStateStorage();
|
||||||
ImGuiTreeNodeFlags_FramePadding |
|
bool open = storage->GetBool(openStateId, defaultOpen);
|
||||||
ImGuiTreeNodeFlags_AllowOverlap;
|
|
||||||
if (defaultOpen) {
|
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(style.ItemSpacing.x, InspectorSectionItemSpacing().y));
|
||||||
flags |= ImGuiTreeNodeFlags_DefaultOpen;
|
ImGui::InvisibleButton("##ComponentSectionHeader", ImVec2(availableWidth, rowHeight));
|
||||||
|
ImGui::PopStyleVar();
|
||||||
|
|
||||||
|
const bool hovered = ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem);
|
||||||
|
const bool held = ImGui::IsItemActive();
|
||||||
|
if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) {
|
||||||
|
open = !open;
|
||||||
|
storage->SetBool(openStateId, open);
|
||||||
}
|
}
|
||||||
|
|
||||||
const bool open = ImGui::TreeNodeEx(id, flags, "%s", label);
|
const ImVec2 itemMin = ImGui::GetItemRectMin();
|
||||||
ImGui::PopStyleVar(2);
|
const ImVec2 itemMax = ImGui::GetItemRectMax();
|
||||||
|
const ImRect frameRect(itemMin, itemMax);
|
||||||
|
const ImRect arrowRect(
|
||||||
|
ImVec2(itemMin.x + framePadding.x, itemMin.y),
|
||||||
|
ImVec2(itemMin.x + arrowSlotWidth, itemMax.y));
|
||||||
|
|
||||||
|
ImDrawList* drawList = ImGui::GetWindowDrawList();
|
||||||
|
const ImU32 bgColor = ImGui::GetColorU32(
|
||||||
|
(held && hovered) ? ImGuiCol_HeaderActive :
|
||||||
|
hovered ? ImGuiCol_HeaderHovered :
|
||||||
|
ImGuiCol_Header);
|
||||||
|
drawList->AddRectFilled(frameRect.Min, frameRect.Max, bgColor, style.FrameRounding);
|
||||||
|
if (style.FrameBorderSize > 0.0f) {
|
||||||
|
drawList->AddRect(
|
||||||
|
frameRect.Min,
|
||||||
|
frameRect.Max,
|
||||||
|
ImGui::GetColorU32(ImGuiCol_Border),
|
||||||
|
style.FrameRounding,
|
||||||
|
0,
|
||||||
|
style.FrameBorderSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
DrawDisclosureArrow(drawList, arrowRect.Min, arrowRect.Max, open, ImGui::GetColorU32(ImGuiCol_Text));
|
||||||
|
|
||||||
|
if (label && label[0] != '\0') {
|
||||||
|
const float textX = itemMin.x + arrowSlotWidth;
|
||||||
|
const float textY = itemMin.y + ((itemMax.y - itemMin.y) - labelSize.y) * 0.5f;
|
||||||
|
drawList->PushClipRect(ImVec2(textX, itemMin.y), itemMax, true);
|
||||||
|
drawList->AddText(ImVec2(textX, textY), ImGui::GetColorU32(ImGuiCol_Text), label);
|
||||||
|
drawList->PopClipRect();
|
||||||
|
}
|
||||||
|
|
||||||
if (BeginPopupContextItem("##ComponentSettings")) {
|
if (BeginPopupContextItem("##ComponentSettings")) {
|
||||||
drawMenu();
|
drawMenu();
|
||||||
EndPopup();
|
EndPopup();
|
||||||
}
|
}
|
||||||
|
|
||||||
return ComponentSectionResult{ open };
|
ImGui::PopID();
|
||||||
|
return ComponentSectionResult{ open, InspectorSectionContentIndent() };
|
||||||
}
|
}
|
||||||
|
|
||||||
inline ComponentSectionResult BeginComponentSection(
|
inline ComponentSectionResult BeginComponentSection(
|
||||||
@@ -284,8 +390,10 @@ inline ComponentSectionResult BeginComponentSection(
|
|||||||
return BeginComponentSection(id, label, []() {}, defaultOpen);
|
return BeginComponentSection(id, label, []() {}, defaultOpen);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void EndComponentSection() {
|
inline void EndComponentSection(const ComponentSectionResult& section) {
|
||||||
ImGui::TreePop();
|
if (section.open && section.contentIndent > 0.0f) {
|
||||||
|
ImGui::Unindent(section.contentIndent);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool InspectorActionButton(const char* label, ImVec2 size = ImVec2(-1.0f, 0.0f)) {
|
inline bool InspectorActionButton(const char* label, ImVec2 size = ImVec2(-1.0f, 0.0f)) {
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
#include "Actions/ConsoleActionRouter.h"
|
|
||||||
#include "Actions/ActionRouting.h"
|
#include "Actions/ActionRouting.h"
|
||||||
#include "ConsolePanel.h"
|
#include "ConsolePanel.h"
|
||||||
#include "Core/EditorConsoleSink.h"
|
|
||||||
#include "UI/UI.h"
|
#include "UI/UI.h"
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
|
|
||||||
@@ -18,22 +16,6 @@ void ConsolePanel::Render() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Actions::ObserveInactiveActionRoute(*m_context);
|
Actions::ObserveInactiveActionRoute(*m_context);
|
||||||
|
|
||||||
auto* sink = Debug::EditorConsoleSink::GetInstance();
|
|
||||||
|
|
||||||
{
|
|
||||||
UI::PanelToolbarScope toolbar("ConsoleToolbar", UI::StandardPanelToolbarHeight());
|
|
||||||
if (toolbar.IsOpen()) {
|
|
||||||
Actions::DrawConsoleToolbarActions(*sink, m_filterState);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
UI::PanelContentScope content("LogScroll", UI::DefaultPanelContentPadding(), ImGuiWindowFlags_HorizontalScrollbar);
|
|
||||||
if (!content.IsOpen()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Actions::DrawConsoleLogRows(*sink, m_filterState);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "Panel.h"
|
#include "Panel.h"
|
||||||
#include "UI/ConsoleFilterState.h"
|
|
||||||
|
|
||||||
namespace XCEngine {
|
namespace XCEngine {
|
||||||
namespace Editor {
|
namespace Editor {
|
||||||
@@ -10,9 +9,6 @@ class ConsolePanel : public Panel {
|
|||||||
public:
|
public:
|
||||||
ConsolePanel();
|
ConsolePanel();
|
||||||
void Render() override;
|
void Render() override;
|
||||||
|
|
||||||
private:
|
|
||||||
UI::ConsoleFilterState m_filterState;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,14 +17,16 @@ void DrawHierarchyTreePrefix(const XCEngine::Editor::UI::TreeNodePrefixContext&
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ImVec4 color = XCEngine::Editor::UI::NavigationTreePrefixColor(context.selected, context.hovered);
|
const float width = context.max.x - context.min.x;
|
||||||
const float size = 9.0f;
|
const float height = context.max.y - context.min.y;
|
||||||
const float centerX = context.min.x + (context.max.x - context.min.x) * 0.5f;
|
const float iconExtent = XCEngine::Editor::UI::NavigationTreeIconSize();
|
||||||
const float centerY = context.min.y + (context.max.y - context.min.y) * 0.5f;
|
const float minX = context.min.x + (width - iconExtent) * 0.5f;
|
||||||
const ImVec2 min(centerX - size * 0.5f, centerY - size * 0.5f);
|
const float minY = context.min.y + (height - iconExtent) * 0.5f;
|
||||||
const ImVec2 max(centerX + size * 0.5f, centerY + size * 0.5f);
|
XCEngine::Editor::UI::DrawAssetIcon(
|
||||||
context.drawList->AddRect(min, max, ImGui::GetColorU32(color), 1.5f);
|
context.drawList,
|
||||||
context.drawList->AddLine(ImVec2(min.x, centerY), ImVec2(max.x, centerY), ImGui::GetColorU32(color), 1.0f);
|
ImVec2(minX, minY),
|
||||||
|
ImVec2(minX + iconExtent, minY + iconExtent),
|
||||||
|
XCEngine::Editor::UI::AssetIconKind::GameObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
@@ -95,7 +97,7 @@ void HierarchyPanel::Render() {
|
|||||||
|
|
||||||
Actions::ObserveFocusedActionRoute(*m_context, EditorActionRoute::Hierarchy);
|
Actions::ObserveFocusedActionRoute(*m_context, EditorActionRoute::Hierarchy);
|
||||||
|
|
||||||
UI::PanelContentScope content("EntityList");
|
UI::PanelContentScope content("EntityList", UI::HierarchyPanelContentPadding());
|
||||||
if (!content.IsOpen()) {
|
if (!content.IsOpen()) {
|
||||||
ImGui::PopStyleColor(2);
|
ImGui::PopStyleColor(2);
|
||||||
return;
|
return;
|
||||||
@@ -103,6 +105,7 @@ void HierarchyPanel::Render() {
|
|||||||
|
|
||||||
auto& sceneManager = m_context->GetSceneManager();
|
auto& sceneManager = m_context->GetSceneManager();
|
||||||
auto rootEntities = sceneManager.GetRootEntities();
|
auto rootEntities = sceneManager.GetRootEntities();
|
||||||
|
UI::ResetTreeLayout();
|
||||||
|
|
||||||
for (auto* gameObject : rootEntities) {
|
for (auto* gameObject : rootEntities) {
|
||||||
RenderEntity(gameObject);
|
RenderEntity(gameObject);
|
||||||
@@ -147,6 +150,7 @@ void HierarchyPanel::RenderEntity(::XCEngine::Components::GameObject* gameObject
|
|||||||
nodeDefinition.options.leaf = gameObject->GetChildCount() == 0;
|
nodeDefinition.options.leaf = gameObject->GetChildCount() == 0;
|
||||||
const std::string persistenceKey = std::to_string(gameObject->GetUUID());
|
const std::string persistenceKey = std::to_string(gameObject->GetUUID());
|
||||||
nodeDefinition.persistenceKey = persistenceKey;
|
nodeDefinition.persistenceKey = persistenceKey;
|
||||||
|
nodeDefinition.style = UI::HierarchyTreeStyle();
|
||||||
nodeDefinition.prefix.width = UI::NavigationTreePrefixWidth();
|
nodeDefinition.prefix.width = UI::NavigationTreePrefixWidth();
|
||||||
nodeDefinition.prefix.draw = DrawHierarchyTreePrefix;
|
nodeDefinition.prefix.draw = DrawHierarchyTreePrefix;
|
||||||
nodeDefinition.callbacks.onInteraction = [this, gameObject](const UI::TreeNodeResult& node) {
|
nodeDefinition.callbacks.onInteraction = [this, gameObject](const UI::TreeNodeResult& node) {
|
||||||
|
|||||||
@@ -48,25 +48,33 @@ void InspectorPanel::OnSelectionChanged(const SelectionChangedEvent& event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void InspectorPanel::Render() {
|
void InspectorPanel::Render() {
|
||||||
UI::PanelWindowScope panel(m_name.c_str());
|
ImGui::PushStyleColor(ImGuiCol_WindowBg, UI::HierarchyInspectorPanelBackgroundColor());
|
||||||
if (!panel.IsOpen()) {
|
ImGui::PushStyleColor(ImGuiCol_ChildBg, UI::HierarchyInspectorPanelBackgroundColor());
|
||||||
return;
|
{
|
||||||
}
|
UI::PanelWindowScope panel(m_name.c_str());
|
||||||
|
if (!panel.IsOpen()) {
|
||||||
Actions::ObserveInactiveActionRoute(*m_context);
|
ImGui::PopStyleColor(2);
|
||||||
|
return;
|
||||||
if (m_selectedEntityId) {
|
}
|
||||||
auto& sceneManager = m_context->GetSceneManager();
|
|
||||||
auto* gameObject = sceneManager.GetEntity(m_selectedEntityId);
|
Actions::ObserveInactiveActionRoute(*m_context);
|
||||||
if (gameObject) {
|
|
||||||
RenderGameObject(gameObject);
|
if (m_selectedEntityId) {
|
||||||
} else {
|
auto& sceneManager = m_context->GetSceneManager();
|
||||||
RenderEmptyState("Object not found");
|
auto* gameObject = sceneManager.GetEntity(m_selectedEntityId);
|
||||||
|
if (gameObject) {
|
||||||
|
RenderGameObject(gameObject);
|
||||||
|
} else {
|
||||||
|
RenderEmptyState("Object not found");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
UI::PanelContentScope content(
|
||||||
|
"InspectorEmpty",
|
||||||
|
UI::InspectorPanelContentPadding(),
|
||||||
|
ImGuiChildFlags_None);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
RenderEmptyState("No Selection", "Select an object in Hierarchy");
|
|
||||||
}
|
}
|
||||||
|
ImGui::PopStyleColor(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
void InspectorPanel::RenderGameObject(::XCEngine::Components::GameObject* gameObject) {
|
void InspectorPanel::RenderGameObject(::XCEngine::Components::GameObject* gameObject) {
|
||||||
@@ -74,6 +82,7 @@ void InspectorPanel::RenderGameObject(::XCEngine::Components::GameObject* gameOb
|
|||||||
UI::PanelContentScope content(
|
UI::PanelContentScope content(
|
||||||
"InspectorContent",
|
"InspectorContent",
|
||||||
UI::InspectorPanelContentPadding(),
|
UI::InspectorPanelContentPadding(),
|
||||||
|
ImGuiChildFlags_None,
|
||||||
ImGuiWindowFlags_None,
|
ImGuiWindowFlags_None,
|
||||||
true,
|
true,
|
||||||
ImVec2(style.ItemSpacing.x, 0.0f));
|
ImVec2(style.ItemSpacing.x, 0.0f));
|
||||||
@@ -93,7 +102,10 @@ void InspectorPanel::RenderGameObject(::XCEngine::Components::GameObject* gameOb
|
|||||||
}
|
}
|
||||||
|
|
||||||
void InspectorPanel::RenderEmptyState(const char* title, const char* subtitle) {
|
void InspectorPanel::RenderEmptyState(const char* title, const char* subtitle) {
|
||||||
UI::PanelContentScope content("InspectorEmptyState", UI::InspectorPanelContentPadding());
|
UI::PanelContentScope content(
|
||||||
|
"InspectorEmptyState",
|
||||||
|
UI::InspectorPanelContentPadding(),
|
||||||
|
ImGuiChildFlags_None);
|
||||||
if (!content.IsOpen()) {
|
if (!content.IsOpen()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -120,6 +132,8 @@ void InspectorPanel::RenderComponent(::XCEngine::Components::Component* componen
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (section.open) {
|
if (section.open) {
|
||||||
|
ImGui::Indent(section.contentIndent);
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, UI::InspectorComponentControlSpacing());
|
||||||
if (editor) {
|
if (editor) {
|
||||||
if (editor->Render(component, &m_context->GetUndoManager())) {
|
if (editor->Render(component, &m_context->GetUndoManager())) {
|
||||||
m_context->GetSceneManager().MarkSceneDirty();
|
m_context->GetSceneManager().MarkSceneDirty();
|
||||||
@@ -127,8 +141,9 @@ void InspectorPanel::RenderComponent(::XCEngine::Components::Component* componen
|
|||||||
} else {
|
} else {
|
||||||
UI::DrawHintText("No registered editor for this component");
|
UI::DrawHintText("No registered editor for this component");
|
||||||
}
|
}
|
||||||
|
ImGui::PopStyleVar();
|
||||||
|
|
||||||
UI::EndComponentSection();
|
UI::EndComponentSection(section);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,14 +21,15 @@ void DrawProjectFolderTreePrefix(const UI::TreeNodePrefixContext& context) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const float iconWidth = 14.0f;
|
const float width = context.max.x - context.min.x;
|
||||||
const float iconHeight = 11.0f;
|
const float height = context.max.y - context.min.y;
|
||||||
const float minX = context.min.x + ((context.max.x - context.min.x) - iconWidth) * 0.5f;
|
const float iconExtent = UI::NavigationTreeIconSize();
|
||||||
const float minY = context.min.y + ((context.max.y - context.min.y) - iconHeight) * 0.5f;
|
const float minX = context.min.x + (width - iconExtent) * 0.5f;
|
||||||
|
const float minY = context.min.y + (height - iconExtent) * 0.5f;
|
||||||
UI::DrawAssetIcon(
|
UI::DrawAssetIcon(
|
||||||
context.drawList,
|
context.drawList,
|
||||||
ImVec2(minX, minY),
|
ImVec2(minX, minY),
|
||||||
ImVec2(minX + iconWidth, minY + iconHeight),
|
ImVec2(minX + iconExtent, minY + iconExtent),
|
||||||
UI::AssetIconKind::Folder);
|
UI::AssetIconKind::Folder);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,8 +53,10 @@ void ProjectPanel::Render() {
|
|||||||
auto& manager = m_context->GetProjectManager();
|
auto& manager = m_context->GetProjectManager();
|
||||||
RenderToolbar();
|
RenderToolbar();
|
||||||
|
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_ChildBg, UI::ProjectBrowserSurfaceColor());
|
||||||
UI::PanelContentScope content("ProjectContent", ImVec2(0.0f, 0.0f));
|
UI::PanelContentScope content("ProjectContent", ImVec2(0.0f, 0.0f));
|
||||||
if (!content.IsOpen()) {
|
if (!content.IsOpen()) {
|
||||||
|
ImGui::PopStyleColor();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,7 +66,7 @@ void ProjectPanel::Render() {
|
|||||||
const float clampedNavigationWidth = std::clamp(
|
const float clampedNavigationWidth = std::clamp(
|
||||||
m_navigationWidth,
|
m_navigationWidth,
|
||||||
UI::ProjectNavigationMinWidth(),
|
UI::ProjectNavigationMinWidth(),
|
||||||
std::max(UI::ProjectNavigationMinWidth(), availableWidth - UI::ProjectBrowserMinWidth() - splitterWidth));
|
(std::max)(UI::ProjectNavigationMinWidth(), availableWidth - UI::ProjectBrowserMinWidth() - splitterWidth));
|
||||||
m_navigationWidth = clampedNavigationWidth;
|
m_navigationWidth = clampedNavigationWidth;
|
||||||
|
|
||||||
RenderFolderTreePane(manager);
|
RenderFolderTreePane(manager);
|
||||||
@@ -76,10 +79,18 @@ void ProjectPanel::Render() {
|
|||||||
RenderBrowserPane(manager);
|
RenderBrowserPane(manager);
|
||||||
|
|
||||||
Actions::DrawProjectCreateFolderDialog(*m_context, m_createFolderDialog);
|
Actions::DrawProjectCreateFolderDialog(*m_context, m_createFolderDialog);
|
||||||
|
ImGui::PopStyleColor();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ProjectPanel::RenderToolbar() {
|
void ProjectPanel::RenderToolbar() {
|
||||||
UI::PanelToolbarScope toolbar("ProjectToolbar", UI::ProjectPanelToolbarHeight());
|
UI::PanelToolbarScope toolbar(
|
||||||
|
"ProjectToolbar",
|
||||||
|
UI::ProjectPanelToolbarHeight(),
|
||||||
|
ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse,
|
||||||
|
true,
|
||||||
|
UI::ToolbarPadding(),
|
||||||
|
UI::ToolbarItemSpacing(),
|
||||||
|
UI::ProjectPanelToolbarBackgroundColor());
|
||||||
if (!toolbar.IsOpen()) {
|
if (!toolbar.IsOpen()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -113,6 +124,7 @@ void ProjectPanel::RenderFolderTreePane(IProjectManager& manager) {
|
|||||||
const AssetItemPtr rootFolder = manager.GetRootFolder();
|
const AssetItemPtr rootFolder = manager.GetRootFolder();
|
||||||
const AssetItemPtr currentFolder = manager.GetCurrentFolder();
|
const AssetItemPtr currentFolder = manager.GetCurrentFolder();
|
||||||
const std::string currentFolderPath = currentFolder ? currentFolder->fullPath : std::string();
|
const std::string currentFolderPath = currentFolder ? currentFolder->fullPath : std::string();
|
||||||
|
UI::ResetTreeLayout();
|
||||||
if (rootFolder) {
|
if (rootFolder) {
|
||||||
RenderFolderTreeNode(manager, rootFolder, currentFolderPath);
|
RenderFolderTreeNode(manager, rootFolder, currentFolderPath);
|
||||||
} else {
|
} else {
|
||||||
@@ -143,6 +155,7 @@ void ProjectPanel::RenderFolderTreeNode(
|
|||||||
nodeDefinition.options.leaf = !hasChildFolders;
|
nodeDefinition.options.leaf = !hasChildFolders;
|
||||||
nodeDefinition.options.defaultOpen = IsCurrentTreeBranch(currentFolderPath, folder->fullPath);
|
nodeDefinition.options.defaultOpen = IsCurrentTreeBranch(currentFolderPath, folder->fullPath);
|
||||||
nodeDefinition.persistenceKey = folder->fullPath;
|
nodeDefinition.persistenceKey = folder->fullPath;
|
||||||
|
nodeDefinition.style = UI::ProjectFolderTreeStyle();
|
||||||
nodeDefinition.prefix.width = UI::NavigationTreePrefixWidth();
|
nodeDefinition.prefix.width = UI::NavigationTreePrefixWidth();
|
||||||
nodeDefinition.prefix.draw = DrawProjectFolderTreePrefix;
|
nodeDefinition.prefix.draw = DrawProjectFolderTreePrefix;
|
||||||
nodeDefinition.callbacks.onInteraction = [this, &manager, folder](const UI::TreeNodeResult& node) {
|
nodeDefinition.callbacks.onInteraction = [this, &manager, folder](const UI::TreeNodeResult& node) {
|
||||||
@@ -195,10 +208,12 @@ void ProjectPanel::RenderBrowserPane(IProjectManager& manager) {
|
|||||||
|
|
||||||
RenderBrowserHeader(manager);
|
RenderBrowserHeader(manager);
|
||||||
|
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_ChildBg, UI::ProjectBrowserPaneBackgroundColor());
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, UI::ProjectBrowserPanePadding());
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, UI::ProjectBrowserPanePadding());
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, UI::AssetGridSpacing());
|
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, UI::AssetGridSpacing());
|
||||||
const bool bodyOpen = ImGui::BeginChild("ProjectBrowserBody", ImVec2(0.0f, 0.0f), false);
|
const bool bodyOpen = ImGui::BeginChild("ProjectBrowserBody", ImVec2(0.0f, 0.0f), false);
|
||||||
ImGui::PopStyleVar(2);
|
ImGui::PopStyleVar(2);
|
||||||
|
ImGui::PopStyleColor();
|
||||||
if (!bodyOpen) {
|
if (!bodyOpen) {
|
||||||
ImGui::EndChild();
|
ImGui::EndChild();
|
||||||
ImGui::EndChild();
|
ImGui::EndChild();
|
||||||
@@ -244,10 +259,10 @@ void ProjectPanel::RenderBrowserPane(IProjectManager& manager) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (visibleItems.empty()) {
|
if (visibleItems.empty() && !search.empty()) {
|
||||||
UI::DrawEmptyState(
|
UI::DrawEmptyState(
|
||||||
search.empty() ? "No Assets" : "No Search Results",
|
"No Search Results",
|
||||||
search.empty() ? "Current folder is empty" : "No assets match the current search");
|
"No assets match the current search");
|
||||||
}
|
}
|
||||||
|
|
||||||
Actions::HandleProjectBackgroundPrimaryClick(manager);
|
Actions::HandleProjectBackgroundPrimaryClick(manager);
|
||||||
@@ -286,21 +301,23 @@ void ProjectPanel::RenderBrowserHeader(IProjectManager& manager) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ImGui::BeginTable("##ProjectBrowserHeaderLayout", 1, ImGuiTableFlags_NoSavedSettings | ImGuiTableFlags_SizingStretchProp | ImGuiTableFlags_NoPadOuterX)) {
|
const float rowHeight = UI::BreadcrumbItemHeight();
|
||||||
ImGui::TableNextRow();
|
const float startY = ImGui::GetCursorPosY();
|
||||||
ImGui::TableNextColumn();
|
const float availableHeight = ImGui::GetContentRegionAvail().y;
|
||||||
UI::DrawToolbarBreadcrumbs(
|
if (availableHeight > rowHeight) {
|
||||||
"Assets",
|
ImGui::SetCursorPosY(startY + (availableHeight - rowHeight) * 0.5f);
|
||||||
manager.GetPathDepth(),
|
|
||||||
[&](size_t index) { return manager.GetPathName(index); },
|
|
||||||
[&](size_t index) { manager.NavigateToIndex(index); });
|
|
||||||
ImGui::EndTable();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UI::DrawToolbarBreadcrumbs(
|
||||||
|
"Assets",
|
||||||
|
manager.GetPathDepth(),
|
||||||
|
[&](size_t index) { return manager.GetPathName(index); },
|
||||||
|
[&](size_t index) { manager.NavigateToIndex(index); });
|
||||||
|
|
||||||
ImDrawList* drawList = ImGui::GetWindowDrawList();
|
ImDrawList* drawList = ImGui::GetWindowDrawList();
|
||||||
const ImVec2 min = ImGui::GetWindowPos();
|
const ImVec2 windowMin = ImGui::GetWindowPos();
|
||||||
const ImVec2 max(min.x + ImGui::GetWindowSize().x, min.y + ImGui::GetWindowSize().y);
|
const ImVec2 windowMax(windowMin.x + ImGui::GetWindowSize().x, windowMin.y + ImGui::GetWindowSize().y);
|
||||||
UI::DrawHorizontalDivider(drawList, min.x, max.x, max.y - 0.5f);
|
UI::DrawHorizontalDivider(drawList, windowMin.x, windowMax.x, windowMax.y - 0.5f);
|
||||||
|
|
||||||
ImGui::EndChild();
|
ImGui::EndChild();
|
||||||
}
|
}
|
||||||
@@ -311,6 +328,13 @@ ProjectPanel::AssetItemInteraction ProjectPanel::RenderAssetItem(const AssetItem
|
|||||||
ImGui::PushID(item ? item->fullPath.c_str() : "ProjectItem");
|
ImGui::PushID(item ? item->fullPath.c_str() : "ProjectItem");
|
||||||
const bool isDraggingThisItem = Actions::IsProjectAssetBeingDragged(item);
|
const bool isDraggingThisItem = Actions::IsProjectAssetBeingDragged(item);
|
||||||
const UI::AssetIconKind iconKind = item->isFolder ? UI::AssetIconKind::Folder : UI::AssetIconKind::File;
|
const UI::AssetIconKind iconKind = item->isFolder ? UI::AssetIconKind::Folder : UI::AssetIconKind::File;
|
||||||
|
UI::AssetTileOptions tileOptions;
|
||||||
|
tileOptions.drawIdleFrame = false;
|
||||||
|
tileOptions.drawSelectionBorder = false;
|
||||||
|
if (item->isFolder) {
|
||||||
|
tileOptions.iconOffset = UI::FolderAssetTileIconOffset();
|
||||||
|
tileOptions.iconSize = UI::FolderAssetTileIconSize();
|
||||||
|
}
|
||||||
|
|
||||||
const UI::AssetTileResult tile = UI::DrawAssetTile(
|
const UI::AssetTileResult tile = UI::DrawAssetTile(
|
||||||
item->name.c_str(),
|
item->name.c_str(),
|
||||||
@@ -318,7 +342,8 @@ ProjectPanel::AssetItemInteraction ProjectPanel::RenderAssetItem(const AssetItem
|
|||||||
isDraggingThisItem,
|
isDraggingThisItem,
|
||||||
[&](ImDrawList* drawList, const ImVec2& iconMin, const ImVec2& iconMax) {
|
[&](ImDrawList* drawList, const ImVec2& iconMin, const ImVec2& iconMax) {
|
||||||
UI::DrawAssetIcon(drawList, iconMin, iconMax, iconKind);
|
UI::DrawAssetIcon(drawList, iconMin, iconMax, iconKind);
|
||||||
});
|
},
|
||||||
|
tileOptions);
|
||||||
|
|
||||||
if (tile.clicked) {
|
if (tile.clicked) {
|
||||||
interaction.clicked = true;
|
interaction.clicked = true;
|
||||||
|
|||||||