chore: sync workspace state

This commit is contained in:
2026-03-29 01:36:53 +08:00
parent eb5de3e3d4
commit e5cb79f3ce
4935 changed files with 35593 additions and 360696 deletions

View File

@@ -6,9 +6,13 @@
#include <algorithm>
#include <cstdint>
#include <cwctype>
#include <cstring>
#include <filesystem>
#include <fstream>
#include <string>
#include <unordered_map>
#include <vector>
#include <wrl/client.h>
#include <stb_image.h>
@@ -36,12 +40,26 @@ struct BuiltInTexture {
struct BuiltInIconState {
ImGuiBackendBridge* backend = nullptr;
ID3D12Device* device = nullptr;
ID3D12CommandQueue* commandQueue = nullptr;
BuiltInTexture folder;
BuiltInTexture gameObject;
BuiltInTexture scene;
struct CachedAssetPreview {
BuiltInTexture texture;
bool loadAttempted = false;
int lastUsedFrame = -1;
};
std::unordered_map<std::string, CachedAssetPreview> assetPreviews;
int lastPreviewBudgetFrame = -1;
int previewLoadsThisFrame = 0;
};
BuiltInIconState g_icons;
constexpr size_t kMaxCachedAssetPreviews = 40;
constexpr int kMaxPreviewLoadsPerFrame = 2;
std::filesystem::path ResolveFolderIconPath() {
const std::filesystem::path exeDir(Platform::GetExecutableDirectoryUtf8());
return (exeDir / ".." / ".." / "resources" / "Icons" / "folder_icon.png").lexically_normal();
@@ -52,6 +70,33 @@ std::filesystem::path ResolveGameObjectIconPath() {
return (exeDir / ".." / ".." / "resources" / "Icons" / "gameobject_icon.png").lexically_normal();
}
std::filesystem::path ResolveSceneIconPath() {
const std::filesystem::path exeDir(Platform::GetExecutableDirectoryUtf8());
return (exeDir / ".." / ".." / "resources" / "Icons" / "scene_icon.png").lexically_normal();
}
std::string MakeAssetPreviewKey(const std::string& filePath) {
std::wstring key = std::filesystem::path(Platform::Utf8ToWide(filePath)).lexically_normal().generic_wstring();
std::transform(key.begin(), key.end(), key.begin(), ::towlower);
return Platform::WideToUtf8(key);
}
bool ReadFileBytes(const std::filesystem::path& filePath, std::vector<stbi_uc>& bytes) {
std::ifstream stream(filePath, std::ios::binary | std::ios::ate);
if (!stream.is_open()) {
return false;
}
const std::streamsize size = stream.tellg();
if (size <= 0) {
return false;
}
bytes.resize(static_cast<size_t>(size));
stream.seekg(0, std::ios::beg);
return stream.read(reinterpret_cast<char*>(bytes.data()), size).good();
}
void ResetTexture(BuiltInTexture& texture) {
if (g_icons.backend && texture.cpuHandle.ptr != 0) {
g_icons.backend->FreeTextureDescriptor(texture.cpuHandle, texture.gpuHandle);
@@ -65,6 +110,13 @@ void ResetTexture(BuiltInTexture& texture) {
texture.height = 0;
}
void ResetAssetPreviewCache() {
for (auto& entry : g_icons.assetPreviews) {
ResetTexture(entry.second.texture);
}
g_icons.assetPreviews.clear();
}
bool WaitForQueueIdle(ID3D12Device* device, ID3D12CommandQueue* commandQueue) {
if (!device || !commandQueue) {
return false;
@@ -109,10 +161,21 @@ bool LoadTextureFromFile(
return false;
}
std::vector<stbi_uc> fileData;
if (!ReadFileBytes(filePath, fileData)) {
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);
stbi_uc* pixels = stbi_load_from_memory(
fileData.data(),
static_cast<int>(fileData.size()),
&width,
&height,
&channels,
STBI_rgb_alpha);
if (!pixels || width <= 0 || height <= 0) {
if (pixels) {
stbi_image_free(pixels);
@@ -328,6 +391,76 @@ void DrawBuiltInFileIcon(ImDrawList* drawList, const ImVec2& min, const ImVec2&
drawList->AddLine(foldA, foldB, lineColor);
}
BuiltInIconState::CachedAssetPreview* GetOrCreateAssetPreview(const std::string& filePath) {
if (!g_icons.backend || !g_icons.device || !g_icons.commandQueue || filePath.empty()) {
return nullptr;
}
const std::string key = MakeAssetPreviewKey(filePath);
auto [it, inserted] = g_icons.assetPreviews.try_emplace(key);
BuiltInIconState::CachedAssetPreview& preview = it->second;
preview.lastUsedFrame = ImGui::GetFrameCount();
if (!inserted && !std::filesystem::exists(std::filesystem::path(Platform::Utf8ToWide(filePath)))) {
ResetTexture(preview.texture);
preview.loadAttempted = true;
return &preview;
}
if (preview.texture.IsValid() || preview.loadAttempted) {
return &preview;
}
const int frame = ImGui::GetFrameCount();
if (g_icons.lastPreviewBudgetFrame != frame) {
g_icons.lastPreviewBudgetFrame = frame;
g_icons.previewLoadsThisFrame = 0;
}
if (g_icons.previewLoadsThisFrame >= kMaxPreviewLoadsPerFrame) {
return &preview;
}
preview.loadAttempted = true;
++g_icons.previewLoadsThisFrame;
LoadTextureFromFile(
*g_icons.backend,
g_icons.device,
g_icons.commandQueue,
std::filesystem::path(Platform::Utf8ToWide(filePath)),
preview.texture);
return &preview;
}
void PruneAssetPreviewCache() {
if (g_icons.assetPreviews.size() <= kMaxCachedAssetPreviews) {
return;
}
std::vector<std::pair<std::string, int>> candidates;
candidates.reserve(g_icons.assetPreviews.size());
for (const auto& entry : g_icons.assetPreviews) {
candidates.emplace_back(entry.first, entry.second.lastUsedFrame);
}
std::sort(
candidates.begin(),
candidates.end(),
[](const auto& lhs, const auto& rhs) {
return lhs.second < rhs.second;
});
size_t removeCount = g_icons.assetPreviews.size() - kMaxCachedAssetPreviews;
for (size_t i = 0; i < removeCount && i < candidates.size(); ++i) {
auto it = g_icons.assetPreviews.find(candidates[i].first);
if (it == g_icons.assetPreviews.end()) {
continue;
}
ResetTexture(it->second.texture);
g_icons.assetPreviews.erase(it);
}
}
} // namespace
void InitializeBuiltInIcons(
@@ -336,14 +469,23 @@ void InitializeBuiltInIcons(
ID3D12CommandQueue* commandQueue) {
ShutdownBuiltInIcons();
g_icons.backend = &backend;
g_icons.device = device;
g_icons.commandQueue = commandQueue;
LoadTextureFromFile(backend, device, commandQueue, ResolveFolderIconPath(), g_icons.folder);
LoadTextureFromFile(backend, device, commandQueue, ResolveGameObjectIconPath(), g_icons.gameObject);
LoadTextureFromFile(backend, device, commandQueue, ResolveSceneIconPath(), g_icons.scene);
}
void ShutdownBuiltInIcons() {
ResetAssetPreviewCache();
ResetTexture(g_icons.folder);
ResetTexture(g_icons.gameObject);
ResetTexture(g_icons.scene);
g_icons.backend = nullptr;
g_icons.device = nullptr;
g_icons.commandQueue = nullptr;
g_icons.lastPreviewBudgetFrame = -1;
g_icons.previewLoadsThisFrame = 0;
}
void DrawAssetIcon(ImDrawList* drawList, const ImVec2& min, const ImVec2& max, AssetIconKind kind) {
@@ -367,9 +509,34 @@ void DrawAssetIcon(ImDrawList* drawList, const ImVec2& min, const ImVec2& max, A
return;
}
if (kind == AssetIconKind::Scene) {
if (g_icons.scene.IsValid()) {
DrawTextureIcon(drawList, g_icons.scene, min, max);
return;
}
DrawBuiltInFileIcon(drawList, min, max);
return;
}
DrawBuiltInFileIcon(drawList, min, max);
}
bool DrawTextureAssetPreview(
ImDrawList* drawList,
const ImVec2& min,
const ImVec2& max,
const std::string& filePath) {
BuiltInIconState::CachedAssetPreview* preview = GetOrCreateAssetPreview(filePath);
if (!preview || !preview->texture.IsValid()) {
return false;
}
DrawTextureIcon(drawList, preview->texture, min, max);
PruneAssetPreviewCache();
return true;
}
} // namespace UI
} // namespace Editor
} // namespace XCEngine

View File

@@ -1,6 +1,7 @@
#pragma once
#include <imgui.h>
#include <string>
struct ID3D12Device;
struct ID3D12CommandQueue;
@@ -14,7 +15,8 @@ class ImGuiBackendBridge;
enum class AssetIconKind {
Folder,
File,
GameObject
GameObject,
Scene
};
void InitializeBuiltInIcons(
@@ -25,6 +27,11 @@ void InitializeBuiltInIcons(
void ShutdownBuiltInIcons();
void DrawAssetIcon(ImDrawList* drawList, const ImVec2& min, const ImVec2& max, AssetIconKind kind);
bool DrawTextureAssetPreview(
ImDrawList* drawList,
const ImVec2& min,
const ImVec2& max,
const std::string& filePath);
} // namespace UI
} // namespace Editor

View File

@@ -89,6 +89,18 @@ inline float ColorPickerInputWidth() {
return 62.0f;
}
inline float ColorPickerChannelLabelWidth() {
return 12.0f;
}
inline float ColorPickerHexLabelWidth() {
return 84.0f;
}
inline float ColorPickerFieldSpacing() {
return 8.0f;
}
inline float ColorPickerCloseButtonSize() {
return 18.0f;
}
@@ -137,6 +149,10 @@ inline ImVec4 ColorPickerBorderColor() {
return ImVec4(0.15f, 0.15f, 0.15f, 1.0f);
}
inline float ColorPickerPopupRounding() {
return 3.0f;
}
inline ImVec4 ColorPickerPreviewBorderColor() {
return ImVec4(0.12f, 0.12f, 0.12f, 1.0f);
}
@@ -550,14 +566,15 @@ inline bool DrawChannelGradientSlider(
}
inline bool DrawChannelRow(const char* label, int componentIndex, ColorPickerState& state, float rowWidth) {
constexpr float labelWidth = 14.0f;
constexpr float spacing = 8.0f;
const float labelWidth = ColorPickerChannelLabelWidth();
const float spacing = ColorPickerFieldSpacing();
const float inputWidth = ColorPickerInputWidth();
const float sliderWidth = ImMax(rowWidth - labelWidth - spacing - inputWidth - spacing, 60.0f);
const float rowStartX = ImGui::GetCursorPosX();
ImGui::AlignTextToFramePadding();
ImGui::TextUnformatted(label);
ImGui::SameLine(0.0f, spacing);
ImGui::SameLine(rowStartX + labelWidth + spacing, 0.0f);
ImGui::PushID(label);
float& value = state.color[componentIndex];
@@ -577,14 +594,15 @@ inline bool DrawChannelRow(const char* label, int componentIndex, ColorPickerSta
}
inline bool DrawAlphaRow(ColorPickerState& state, float rowWidth) {
constexpr float labelWidth = 14.0f;
constexpr float spacing = 8.0f;
const float labelWidth = ColorPickerChannelLabelWidth();
const float spacing = ColorPickerFieldSpacing();
const float inputWidth = ColorPickerInputWidth();
const float sliderWidth = ImMax(rowWidth - labelWidth - spacing - inputWidth - spacing, 60.0f);
const float rowStartX = ImGui::GetCursorPosX();
ImGui::AlignTextToFramePadding();
ImGui::TextUnformatted("A");
ImGui::SameLine(0.0f, spacing);
ImGui::SameLine(rowStartX + labelWidth + spacing, 0.0f);
ImGui::PushID("Alpha");
const ImVec4 startColor(state.color[0], state.color[1], state.color[2], 0.0f);
@@ -607,15 +625,19 @@ inline bool DrawAlphaRow(ColorPickerState& state, float rowWidth) {
}
inline bool DrawHexRow(ColorPickerState& state, float rowWidth) {
const float labelWidth = ColorPickerHexLabelWidth();
const float spacing = ColorPickerFieldSpacing();
const float rowStartX = ImGui::GetCursorPosX();
ImGui::AlignTextToFramePadding();
ImGui::TextUnformatted("Hexadecimal");
ImGui::SameLine(0.0f, 8.0f);
ImGui::SameLine(rowStartX + labelWidth + spacing, 0.0f);
if (!state.hexEditing) {
FormatHexBuffer(state);
}
ImGui::SetNextItemWidth(ImMax(rowWidth - ImGui::CalcTextSize("Hexadecimal").x - 8.0f, 40.0f));
ImGui::SetNextItemWidth(ImMax(rowWidth - labelWidth - spacing, 40.0f));
const bool edited = ImGui::InputText(
"##hex",
state.hexBuffer,
@@ -660,10 +682,11 @@ inline bool DrawCloseButton(const char* id, const ImVec2& size) {
inline bool DrawUnityColorPickerPopup(const char* popupId, ColorPickerState& state, bool includeAlpha) {
const float headerHeight = ColorPickerHeaderHeight();
const float bodyPadding = ColorPickerBodyPadding();
const float popupRounding = ColorPickerPopupRounding();
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
ImGui::PushStyleVar(ImGuiStyleVar_PopupRounding, 3.0f);
ImGui::PushStyleVar(ImGuiStyleVar_PopupBorderSize, 1.0f);
ImGui::PushStyleVar(ImGuiStyleVar_PopupRounding, popupRounding);
ImGui::PushStyleVar(ImGuiStyleVar_PopupBorderSize, PopupWindowBorderSize());
ImGui::PushStyleColor(ImGuiCol_PopupBg, ColorPickerBodyColor());
ImGui::PushStyleColor(ImGuiCol_Border, ColorPickerBorderColor());
@@ -685,15 +708,16 @@ inline bool DrawUnityColorPickerPopup(const char* popupId, ColorPickerState& sta
const ImVec2 windowPos = ImGui::GetWindowPos();
const ImVec2 windowSize = ImGui::GetWindowSize();
const ImVec2 windowMax(windowPos.x + windowSize.x, windowPos.y + windowSize.y);
drawList->AddRectFilled(windowPos, windowMax, ImGui::GetColorU32(ColorPickerBodyColor()), 3.0f);
drawList->AddRectFilled(windowPos, windowMax, ImGui::GetColorU32(ColorPickerBodyColor()), popupRounding);
drawList->AddRectFilled(
windowPos,
ImVec2(windowMax.x, windowPos.y + headerHeight),
ImGui::GetColorU32(ColorPickerHeaderColor()),
3.0f,
popupRounding,
ImDrawFlags_RoundCornersTop);
drawList->AddRect(windowPos, windowMax, ImGui::GetColorU32(ColorPickerBorderColor()), 3.0f);
drawList->AddText(ImVec2(windowPos.x + 10.0f, windowPos.y + 8.0f), IM_COL32(255, 255, 255, 255), "Color");
drawList->AddRect(windowPos, windowMax, ImGui::GetColorU32(ColorPickerBorderColor()), popupRounding);
const float titleY = windowPos.y + (headerHeight - ImGui::GetTextLineHeight()) * 0.5f - 1.0f;
drawList->AddText(ImVec2(windowPos.x + 10.0f, titleY), IM_COL32(255, 255, 255, 255), "Color");
const float closeButtonSize = ColorPickerCloseButtonSize();
ImGui::SetCursorPos(ImVec2(windowSize.x - closeButtonSize - 4.0f, 5.0f));

View File

@@ -108,12 +108,11 @@ inline void PopPopupWindowChrome() {
}
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);
ImGui::PushStyleColor(ImGuiCol_Border, PopupBorderColor());
}
inline void PopComboPopupWindowChrome() {

View File

@@ -15,6 +15,7 @@ struct PropertyLayoutSpec {
float controlColumnStart = InspectorPropertyControlColumnStart();
float labelControlGap = InspectorPropertyLabelControlGap();
float controlTrailingInset = InspectorPropertyControlTrailingInset();
float minimumRowHeight = 0.0f;
};
struct PropertyLayoutMetrics {
@@ -32,6 +33,15 @@ inline PropertyLayoutSpec MakePropertyLayout() {
return PropertyLayoutSpec{};
}
inline float CalcPropertyRowHeightForFramePadding(const ImVec2& framePadding) {
return ImGui::GetFontSize() + framePadding.y * 2.0f + ControlRowHeightOffset();
}
inline PropertyLayoutSpec WithMinimumRowHeight(PropertyLayoutSpec spec, float minimumRowHeight) {
spec.minimumRowHeight = std::max(spec.minimumRowHeight, minimumRowHeight);
return spec;
}
inline void PushPropertyLayoutStyles() {
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ControlFramePadding());
}
@@ -58,6 +68,15 @@ inline void AlignPropertyControlToRight(
}
}
inline void AlignPropertyControlVertically(
const PropertyLayoutMetrics& layout,
float height) {
const float offset = std::max((layout.rowHeight - height) * 0.5f, 0.0f);
if (offset > 0.0f) {
ImGui::SetCursorPosY(layout.cursorPos.y + offset);
}
}
template <typename DrawControlFn>
inline auto DrawPropertyRow(
const char* label,
@@ -72,7 +91,9 @@ inline auto DrawPropertyRow(
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 rowHeight = std::max(
ImGui::GetFrameHeight() + ControlRowHeightOffset(),
spec.minimumRowHeight);
const float labelInset = std::max(spec.labelInset, 0.0f);
const float controlColumnStart = std::clamp(
std::max(spec.controlColumnStart, 0.0f),

View File

@@ -118,6 +118,14 @@ inline ImVec2 ControlFramePadding() {
return ImVec2(3.0f, 0.0f);
}
inline ImVec2 ScalarControlFramePadding() {
return ImVec2(3.0f, 2.0f);
}
inline float CompactIndicatorSize() {
return 18.0f;
}
inline float ControlRowHeightOffset() {
return 1.0f;
}
@@ -138,12 +146,16 @@ inline float SliderValueFieldWidth() {
return 64.0f;
}
inline float ComboPreviewHeightOffset() {
return -2.0f;
}
inline float LinearSliderTrackThickness() {
return 2.0f;
return 3.0f;
}
inline float LinearSliderGrabRadius() {
return 5.0f;
return 6.0f;
}
inline float LinearSliderHorizontalPadding() {
@@ -381,19 +393,43 @@ inline ImVec2 InspectorActionButtonPadding() {
}
inline ImVec2 PopupWindowPadding() {
return ImVec2(12.0f, 10.0f);
return ImVec2(10.0f, 0.0f);
}
inline ImVec2 ComboPopupWindowPadding() {
return ImVec2(6.0f, 1.0f);
return ImVec2(5.0f, 0.0f);
}
inline ImVec2 ComboPopupItemSpacing() {
return ImVec2(0.0f, 0.0f);
}
inline ImVec4 MenuSurfaceColor() {
return ImVec4(0.98f, 0.98f, 0.98f, 1.0f);
}
inline ImVec4 MenuSurfaceHoverColor() {
return ImVec4(0.97f, 0.97f, 0.97f, 1.0f);
}
inline ImVec4 MenuSurfaceActiveColor() {
return ImVec4(0.955f, 0.955f, 0.955f, 1.0f);
}
inline ImVec4 MenuBorderColor() {
return ImVec4(0.84f, 0.84f, 0.84f, 1.0f);
}
inline ImVec4 MenuTextColor() {
return ImVec4(0.16f, 0.16f, 0.16f, 1.0f);
}
inline ImVec4 MenuTextDisabledColor() {
return ImVec4(0.47f, 0.47f, 0.47f, 1.0f);
}
inline float ComboPopupRounding() {
return 1.0f;
return 3.0f;
}
inline float ComboPopupBorderSize() {
@@ -401,15 +437,15 @@ inline float ComboPopupBorderSize() {
}
inline ImVec4 ComboPopupBackgroundColor() {
return ImVec4(1.0f, 1.0f, 1.0f, 1.0f);
return MenuSurfaceColor();
}
inline ImVec4 ComboPopupTextColor() {
return ImVec4(0.14f, 0.14f, 0.14f, 1.0f);
return MenuTextColor();
}
inline ImVec4 ComboPopupTextDisabledColor() {
return ImVec4(0.55f, 0.55f, 0.55f, 1.0f);
return MenuTextDisabledColor();
}
inline ImVec4 ComboPopupItemColor() {
@@ -417,27 +453,39 @@ inline ImVec4 ComboPopupItemColor() {
}
inline ImVec4 ComboPopupItemHoveredColor() {
return ImVec4(0.0f, 0.0f, 0.0f, 0.045f);
return MenuSurfaceHoverColor();
}
inline ImVec4 ComboPopupItemActiveColor() {
return ImVec4(0.0f, 0.0f, 0.0f, 0.08f);
return MenuSurfaceActiveColor();
}
inline ImVec4 ComboPopupCheckMarkColor() {
return ImVec4(0.20f, 0.20f, 0.20f, 1.0f);
return MenuTextColor();
}
inline float PopupWindowRounding() {
return 5.0f;
return 3.0f;
}
inline float PopupWindowBorderSize() {
return 0.0f;
return 1.0f;
}
inline float PopupFrameRounding() {
return 4.0f;
return 3.0f;
}
inline float PopupSubmenuArrowExtent() {
return 8.0f;
}
inline float PopupSubmenuArrowTrailingInset() {
return 8.0f;
}
inline float PopupSubmenuOpenOffsetX() {
return 0.0f;
}
inline float PopupFrameBorderSize() {
@@ -445,19 +493,19 @@ inline float PopupFrameBorderSize() {
}
inline ImVec4 PopupBackgroundColor() {
return ImVec4(1.0f, 1.0f, 1.0f, 1.0f);
return MenuSurfaceColor();
}
inline ImVec4 PopupBorderColor() {
return ImVec4(0.0f, 0.0f, 0.0f, 0.10f);
return MenuBorderColor();
}
inline ImVec4 PopupTextColor() {
return ImVec4(0.14f, 0.14f, 0.14f, 1.0f);
return MenuTextColor();
}
inline ImVec4 PopupTextDisabledColor() {
return ImVec4(0.55f, 0.55f, 0.55f, 1.0f);
return MenuTextDisabledColor();
}
inline ImVec4 PopupItemColor() {
@@ -465,39 +513,39 @@ inline ImVec4 PopupItemColor() {
}
inline ImVec4 PopupItemHoveredColor() {
return ImVec4(0.0f, 0.0f, 0.0f, 0.06f);
return MenuSurfaceHoverColor();
}
inline ImVec4 PopupItemActiveColor() {
return ImVec4(0.0f, 0.0f, 0.0f, 0.10f);
return MenuSurfaceActiveColor();
}
inline ImVec4 PopupFrameColor() {
return ImVec4(1.0f, 1.0f, 1.0f, 1.0f);
return MenuSurfaceColor();
}
inline ImVec4 PopupFrameHoveredColor() {
return ImVec4(0.965f, 0.965f, 0.965f, 1.0f);
return MenuSurfaceHoverColor();
}
inline ImVec4 PopupFrameActiveColor() {
return ImVec4(0.94f, 0.94f, 0.94f, 1.0f);
return MenuSurfaceActiveColor();
}
inline ImVec4 PopupButtonColor() {
return ImVec4(0.95f, 0.95f, 0.95f, 1.0f);
return MenuSurfaceColor();
}
inline ImVec4 PopupButtonHoveredColor() {
return ImVec4(0.90f, 0.90f, 0.90f, 1.0f);
return MenuSurfaceHoverColor();
}
inline ImVec4 PopupButtonActiveColor() {
return ImVec4(0.86f, 0.86f, 0.86f, 1.0f);
return MenuSurfaceActiveColor();
}
inline ImVec4 PopupCheckMarkColor() {
return ImVec4(0.20f, 0.20f, 0.20f, 1.0f);
return MenuTextColor();
}
inline ImVec2 AssetTileSize() {
@@ -556,14 +604,22 @@ inline ImVec2 AssetTileIconSize() {
return ImVec2(32.0f, 24.0f);
}
inline ImVec2 FolderAssetTileIconSize() {
inline ImVec2 ProjectAssetTileIconSize() {
return ImVec2(72.0f, 72.0f);
}
inline ImVec2 FolderAssetTileIconOffset() {
inline ImVec2 ProjectAssetTileIconOffset() {
return ImVec2(0.0f, 2.0f);
}
inline ImVec2 FolderAssetTileIconSize() {
return ProjectAssetTileIconSize();
}
inline ImVec2 FolderAssetTileIconOffset() {
return ProjectAssetTileIconOffset();
}
inline float AssetTileIconTextGap() {
return 4.0f;
}

View File

@@ -22,6 +22,8 @@ struct AssetTileResult {
bool hovered = false;
ImVec2 min = ImVec2(0.0f, 0.0f);
ImVec2 max = ImVec2(0.0f, 0.0f);
ImVec2 labelMin = ImVec2(0.0f, 0.0f);
ImVec2 labelMax = ImVec2(0.0f, 0.0f);
};
struct AssetTileOptions {
@@ -30,6 +32,7 @@ struct AssetTileOptions {
ImVec2 iconSize = AssetTileIconSize();
bool drawIdleFrame = true;
bool drawSelectionBorder = true;
bool drawLabel = true;
};
enum class DialogActionResult {
@@ -79,6 +82,77 @@ inline bool DrawMenuScope(const char* label, DrawContentFn&& drawContent) {
return true;
}
template <typename DrawContentFn>
inline bool DrawPopupSubmenuScope(const char* label, DrawContentFn&& drawContent) {
if (!label || label[0] == '\0') {
return false;
}
ImGui::PushID(label);
const char* popupId = "##PopupSubmenu";
const ImVec2 labelSize = ImGui::CalcTextSize(label);
const ImVec2 rowPos = ImGui::GetCursorScreenPos();
const float rowHeight = labelSize.y;
const float rowWidth = ImMax(ImGui::GetContentRegionAvail().x, 1.0f);
const bool popupOpen = ImGui::IsPopupOpen(popupId);
if (ImGui::Selectable(
"##PopupSubmenuRow",
popupOpen,
ImGuiSelectableFlags_NoAutoClosePopups,
ImVec2(rowWidth, rowHeight))) {
ImGui::OpenPopup(popupId);
}
const bool hovered = ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup);
if (hovered && !popupOpen) {
ImGui::OpenPopup(popupId);
}
const ImVec2 itemMin = ImGui::GetItemRectMin();
const ImVec2 itemMax = ImGui::GetItemRectMax();
const ImVec2 parentWindowPos = ImGui::GetWindowPos();
const ImVec2 parentWindowSize = ImGui::GetWindowSize();
const float parentWindowRight = parentWindowPos.x + parentWindowSize.x;
const float itemHeight = itemMax.y - itemMin.y;
ImDrawList* drawList = ImGui::GetWindowDrawList();
drawList->AddText(
ImVec2(itemMin.x + ImGui::GetStyle().FramePadding.x, itemMin.y + (itemHeight - labelSize.y) * 0.5f),
ImGui::GetColorU32(ImGuiCol_Text),
label);
const float arrowExtent = PopupSubmenuArrowExtent();
const float arrowCenterX = itemMax.x - PopupSubmenuArrowTrailingInset() - arrowExtent * 0.5f;
const float arrowCenterY = (itemMin.y + itemMax.y) * 0.5f;
drawList->AddTriangleFilled(
ImVec2(arrowCenterX - arrowExtent * 0.30f, arrowCenterY - arrowExtent * 0.50f),
ImVec2(arrowCenterX - arrowExtent * 0.30f, arrowCenterY + arrowExtent * 0.50f),
ImVec2(arrowCenterX + arrowExtent * 0.50f, arrowCenterY),
ImGui::GetColorU32(ImGuiCol_Text));
ImGui::SetNextWindowPos(
ImVec2(parentWindowRight + PopupSubmenuOpenOffsetX(), rowPos.y - PopupWindowPadding().y),
ImGuiCond_Always);
const bool open = BeginPopup(
popupId,
ImGuiWindowFlags_NoMove |
ImGuiWindowFlags_NoResize |
ImGuiWindowFlags_NoSavedSettings);
if (!open) {
ImGui::PopID();
return false;
}
drawContent();
const bool popupHovered = ImGui::IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup);
if (!hovered && !popupHovered && !ImGui::IsWindowAppearing()) {
ImGui::CloseCurrentPopup();
}
EndPopup();
ImGui::PopID();
return true;
}
template <typename ExecuteFn>
inline bool DrawMenuCommand(const MenuCommand& command, ExecuteFn&& execute) {
if (command.kind == MenuCommandKind::Separator) {
@@ -285,8 +359,8 @@ inline AssetTileResult DrawAssetTile(
drawList->AddRect(min, max, ImGui::GetColorU32(AssetTileIdleBorderColor()), AssetTileRounding());
}
if (hovered || selected) {
drawList->AddRectFilled(min, max, ImGui::GetColorU32(selected ? AssetTileSelectedFillColor() : AssetTileHoverFillColor()), AssetTileRounding());
if (selected) {
drawList->AddRectFilled(min, max, ImGui::GetColorU32(AssetTileSelectedFillColor()), AssetTileRounding());
}
if (selected && options.drawSelectionBorder) {
drawList->AddRect(min, max, ImGui::GetColorU32(AssetTileSelectedBorderColor()), AssetTileRounding());
@@ -302,14 +376,19 @@ inline AssetTileResult DrawAssetTile(
drawIcon(drawList, iconMin, iconMax);
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;
ImGui::PushClipRect(ImVec2(min.x + textPadding.x, min.y), ImVec2(max.x - textPadding.x, max.y), true);
drawList->AddText(ImVec2(centeredTextX, textY), ImGui::GetColorU32(AssetTileTextColor(selected)), label);
ImGui::PopClipRect();
const float labelHeight = ImGui::GetFrameHeight();
const ImVec2 labelMin(min.x + textPadding.x, max.y - labelHeight - textPadding.y * 0.5f);
const ImVec2 labelMax(max.x - textPadding.x, labelMin.y + labelHeight);
if (options.drawLabel) {
const float textAreaWidth = labelMax.x - labelMin.x;
const float centeredTextX = labelMin.x + std::max(0.0f, (textAreaWidth - textSize.x) * 0.5f);
const float textY = labelMin.y + (labelHeight - textSize.y) * 0.5f;
ImGui::PushClipRect(labelMin, labelMax, true);
drawList->AddText(ImVec2(centeredTextX, textY), ImGui::GetColorU32(AssetTileTextColor(selected)), label);
ImGui::PopClipRect();
}
return AssetTileResult{ clicked, contextRequested, openRequested, hovered, min, max };
return AssetTileResult{ clicked, contextRequested, openRequested, hovered, min, max, labelMin, labelMax };
}
template <typename DrawMenuFn>