2026-03-26 21:18:33 +08:00
|
|
|
#pragma once
|
|
|
|
|
|
2026-03-27 00:15:38 +08:00
|
|
|
#include "Core.h"
|
2026-03-26 21:18:33 +08:00
|
|
|
#include "StyleTokens.h"
|
|
|
|
|
|
|
|
|
|
#include <imgui.h>
|
|
|
|
|
#include <string>
|
|
|
|
|
|
|
|
|
|
namespace XCEngine {
|
|
|
|
|
namespace Editor {
|
|
|
|
|
namespace UI {
|
|
|
|
|
|
|
|
|
|
struct ComponentSectionResult {
|
|
|
|
|
bool open = false;
|
2026-03-28 15:07:19 +08:00
|
|
|
float contentIndent = 0.0f;
|
2026-03-26 21:18:33 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct AssetTileResult {
|
|
|
|
|
bool clicked = false;
|
|
|
|
|
bool contextRequested = false;
|
|
|
|
|
bool openRequested = false;
|
|
|
|
|
bool hovered = false;
|
|
|
|
|
ImVec2 min = ImVec2(0.0f, 0.0f);
|
|
|
|
|
ImVec2 max = ImVec2(0.0f, 0.0f);
|
|
|
|
|
};
|
|
|
|
|
|
2026-03-28 15:07:19 +08:00
|
|
|
struct AssetTileOptions {
|
|
|
|
|
ImVec2 size = AssetTileSize();
|
|
|
|
|
ImVec2 iconOffset = AssetTileIconOffset();
|
|
|
|
|
ImVec2 iconSize = AssetTileIconSize();
|
|
|
|
|
bool drawIdleFrame = true;
|
|
|
|
|
bool drawSelectionBorder = true;
|
2026-03-26 21:18:33 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
enum class DialogActionResult {
|
|
|
|
|
None,
|
|
|
|
|
Primary,
|
|
|
|
|
Secondary
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
enum class MenuCommandKind {
|
|
|
|
|
Action,
|
|
|
|
|
Separator
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct MenuCommand {
|
|
|
|
|
MenuCommandKind kind = MenuCommandKind::Action;
|
|
|
|
|
const char* label = nullptr;
|
|
|
|
|
const char* shortcut = nullptr;
|
|
|
|
|
bool selected = false;
|
|
|
|
|
bool enabled = true;
|
|
|
|
|
|
|
|
|
|
static MenuCommand Action(
|
|
|
|
|
const char* label,
|
|
|
|
|
const char* shortcut = nullptr,
|
|
|
|
|
bool selected = false,
|
|
|
|
|
bool enabled = true) {
|
|
|
|
|
return MenuCommand{ MenuCommandKind::Action, label, shortcut, selected, enabled };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static MenuCommand Separator() {
|
|
|
|
|
return MenuCommand{ MenuCommandKind::Separator, nullptr, nullptr, false, true };
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
template <typename DrawContentFn>
|
|
|
|
|
inline bool DrawMenuScope(const char* label, DrawContentFn&& drawContent) {
|
2026-03-28 15:07:19 +08:00
|
|
|
PushPopupWindowChrome();
|
2026-03-26 21:18:33 +08:00
|
|
|
if (!ImGui::BeginMenu(label)) {
|
2026-03-28 15:07:19 +08:00
|
|
|
PopPopupWindowChrome();
|
2026-03-26 21:18:33 +08:00
|
|
|
return false;
|
|
|
|
|
}
|
2026-03-28 15:07:19 +08:00
|
|
|
PopPopupWindowChrome();
|
2026-03-26 21:18:33 +08:00
|
|
|
|
2026-03-28 15:07:19 +08:00
|
|
|
PushPopupContentChrome();
|
2026-03-26 21:18:33 +08:00
|
|
|
drawContent();
|
2026-03-28 15:07:19 +08:00
|
|
|
PopPopupContentChrome();
|
2026-03-26 21:18:33 +08:00
|
|
|
ImGui::EndMenu();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template <typename ExecuteFn>
|
|
|
|
|
inline bool DrawMenuCommand(const MenuCommand& command, ExecuteFn&& execute) {
|
|
|
|
|
if (command.kind == MenuCommandKind::Separator) {
|
|
|
|
|
ImGui::Separator();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!ImGui::MenuItem(command.label, command.shortcut, command.selected, command.enabled)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
execute();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template <size_t N, typename ExecuteFn>
|
|
|
|
|
inline void DrawMenuCommands(const MenuCommand (&commands)[N], ExecuteFn&& execute) {
|
|
|
|
|
for (size_t i = 0; i < N; ++i) {
|
|
|
|
|
DrawMenuCommand(commands[i], [&]() { execute(i); });
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline bool ToolbarSearchField(
|
|
|
|
|
const char* id,
|
|
|
|
|
const char* hint,
|
|
|
|
|
char* buffer,
|
|
|
|
|
size_t bufferSize,
|
|
|
|
|
float trailingWidth = 0.0f) {
|
|
|
|
|
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, SearchFieldFramePadding());
|
|
|
|
|
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, SearchFieldFrameRounding());
|
|
|
|
|
const float width = ImGui::GetContentRegionAvail().x - trailingWidth;
|
|
|
|
|
ImGui::SetNextItemWidth(width > 0.0f ? width : 0.0f);
|
|
|
|
|
const bool changed = ImGui::InputTextWithHint(id, hint, buffer, bufferSize);
|
|
|
|
|
ImGui::PopStyleVar(2);
|
|
|
|
|
return changed;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline void DrawToolbarLabel(const char* text) {
|
|
|
|
|
ImGui::AlignTextToFramePadding();
|
|
|
|
|
ImGui::TextColored(HintTextColor(), "%s", text);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline bool ToolbarToggleButton(const char* label, bool& active, ImVec2 size = ImVec2(0.0f, 0.0f)) {
|
|
|
|
|
if (!ToolbarButton(label, active, size)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
active = !active;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline void DrawToolbarRowGap() {
|
|
|
|
|
ImGui::Dummy(ImVec2(0.0f, ToolbarRowGap()));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline void DrawHintText(const char* text) {
|
|
|
|
|
ImGui::TextColored(HintTextColor(), "%s", text);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline void DrawEmptyState(
|
|
|
|
|
const char* title,
|
|
|
|
|
const char* subtitle = nullptr,
|
|
|
|
|
ImVec2 start = ImVec2(10.0f, 10.0f)) {
|
|
|
|
|
ImGui::SetCursorPos(start);
|
|
|
|
|
ImGui::TextUnformatted(title);
|
|
|
|
|
|
|
|
|
|
if (subtitle && subtitle[0] != '\0') {
|
|
|
|
|
ImGui::SetCursorPos(ImVec2(start.x, start.y + EmptyStateLineOffset()));
|
|
|
|
|
ImGui::TextColored(EmptyStateSubtitleColor(), "%s", subtitle);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-28 15:07:19 +08:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-26 21:18:33 +08:00
|
|
|
template <typename GetNameFn, typename NavigateFn>
|
|
|
|
|
inline void DrawToolbarBreadcrumbs(
|
|
|
|
|
const char* rootLabel,
|
|
|
|
|
size_t segmentCount,
|
|
|
|
|
GetNameFn&& getName,
|
|
|
|
|
NavigateFn&& navigateToSegment) {
|
|
|
|
|
if (segmentCount == 0) {
|
2026-03-28 15:07:19 +08:00
|
|
|
DrawBreadcrumbSegment(rootLabel, false, true);
|
2026-03-26 21:18:33 +08:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (size_t i = 0; i < segmentCount; ++i) {
|
|
|
|
|
if (i > 0) {
|
2026-03-28 15:07:19 +08:00
|
|
|
ImGui::SameLine(0.0f, BreadcrumbSegmentSpacing());
|
|
|
|
|
DrawBreadcrumbSeparator();
|
|
|
|
|
ImGui::SameLine(0.0f, BreadcrumbSegmentSpacing());
|
2026-03-26 21:18:33 +08:00
|
|
|
}
|
|
|
|
|
|
2026-03-28 15:07:19 +08:00
|
|
|
const std::string label = (i == 0 && rootLabel && rootLabel[0] != '\0')
|
|
|
|
|
? std::string(rootLabel)
|
|
|
|
|
: getName(i);
|
|
|
|
|
const bool current = (i + 1 == segmentCount);
|
|
|
|
|
|
|
|
|
|
ImGui::PushID(static_cast<int>(i));
|
|
|
|
|
if (DrawBreadcrumbSegment(label.c_str(), !current, current)) {
|
|
|
|
|
navigateToSegment(i);
|
2026-03-26 21:18:33 +08:00
|
|
|
}
|
2026-03-28 15:07:19 +08:00
|
|
|
ImGui::PopID();
|
2026-03-26 21:18:33 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template <typename DrawIconFn>
|
|
|
|
|
inline AssetTileResult DrawAssetTile(
|
|
|
|
|
const char* label,
|
|
|
|
|
bool selected,
|
|
|
|
|
bool dimmed,
|
2026-03-28 15:07:19 +08:00
|
|
|
DrawIconFn&& drawIcon,
|
|
|
|
|
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);
|
2026-03-26 21:18:33 +08:00
|
|
|
ImGui::InvisibleButton("##AssetBtn", tileSize);
|
|
|
|
|
|
|
|
|
|
const bool clicked = ImGui::IsItemClicked(ImGuiMouseButton_Left);
|
|
|
|
|
const bool contextRequested = ImGui::IsItemClicked(ImGuiMouseButton_Right);
|
|
|
|
|
const bool openRequested = ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(0);
|
|
|
|
|
const bool hovered = ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem);
|
|
|
|
|
|
|
|
|
|
const ImVec2 min = ImGui::GetItemRectMin();
|
|
|
|
|
const ImVec2 max = ImVec2(min.x + tileSize.x, min.y + tileSize.y);
|
|
|
|
|
ImDrawList* drawList = ImGui::GetWindowDrawList();
|
|
|
|
|
|
2026-03-28 15:07:19 +08:00
|
|
|
if (options.drawIdleFrame) {
|
|
|
|
|
drawList->AddRectFilled(min, max, ImGui::GetColorU32(AssetTileIdleFillColor()), AssetTileRounding());
|
|
|
|
|
drawList->AddRect(min, max, ImGui::GetColorU32(AssetTileIdleBorderColor()), AssetTileRounding());
|
|
|
|
|
}
|
2026-03-27 21:55:14 +08:00
|
|
|
|
2026-03-26 21:18:33 +08:00
|
|
|
if (hovered || selected) {
|
|
|
|
|
drawList->AddRectFilled(min, max, ImGui::GetColorU32(selected ? AssetTileSelectedFillColor() : AssetTileHoverFillColor()), AssetTileRounding());
|
|
|
|
|
}
|
2026-03-28 15:07:19 +08:00
|
|
|
if (selected && options.drawSelectionBorder) {
|
2026-03-26 21:18:33 +08:00
|
|
|
drawList->AddRect(min, max, ImGui::GetColorU32(AssetTileSelectedBorderColor()), AssetTileRounding());
|
|
|
|
|
}
|
|
|
|
|
if (dimmed) {
|
|
|
|
|
drawList->AddRectFilled(min, max, ImGui::GetColorU32(AssetTileDraggedOverlayColor()), 0.0f);
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-28 15:07:19 +08:00
|
|
|
const ImVec2 iconMin(
|
|
|
|
|
min.x + (tileSize.x - options.iconSize.x) * 0.5f,
|
|
|
|
|
min.y + options.iconOffset.y);
|
|
|
|
|
const ImVec2 iconMax(iconMin.x + options.iconSize.x, iconMin.y + options.iconSize.y);
|
2026-03-26 21:18:33 +08:00
|
|
|
drawIcon(drawList, iconMin, iconMax);
|
|
|
|
|
|
2026-03-28 15:07:19 +08:00
|
|
|
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);
|
2026-03-26 21:18:33 +08:00
|
|
|
const float textY = max.y - textSize.y - AssetTileTextPadding().y;
|
2026-03-28 15:07:19 +08:00
|
|
|
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);
|
2026-03-26 21:18:33 +08:00
|
|
|
ImGui::PopClipRect();
|
|
|
|
|
|
|
|
|
|
return AssetTileResult{ clicked, contextRequested, openRequested, hovered, min, max };
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-27 00:08:46 +08:00
|
|
|
template <typename DrawMenuFn>
|
2026-03-26 21:18:33 +08:00
|
|
|
inline ComponentSectionResult BeginComponentSection(
|
|
|
|
|
const void* id,
|
|
|
|
|
const char* label,
|
2026-03-27 00:08:46 +08:00
|
|
|
DrawMenuFn&& drawMenu,
|
2026-03-26 21:18:33 +08:00
|
|
|
bool defaultOpen = true) {
|
|
|
|
|
const ImGuiStyle& style = ImGui::GetStyle();
|
2026-03-28 15:07:19 +08:00
|
|
|
const ImVec2 framePadding = InspectorSectionFramePadding();
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
|
|
ImGui::PushID(id);
|
|
|
|
|
const ImGuiID openStateId = ImGui::GetID("##ComponentSectionOpen");
|
|
|
|
|
ImGuiStorage* storage = ImGui::GetStateStorage();
|
|
|
|
|
bool open = storage->GetBool(openStateId, defaultOpen);
|
|
|
|
|
|
2026-03-26 21:18:33 +08:00
|
|
|
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(style.ItemSpacing.x, InspectorSectionItemSpacing().y));
|
2026-03-28 15:07:19 +08:00
|
|
|
ImGui::InvisibleButton("##ComponentSectionHeader", ImVec2(availableWidth, rowHeight));
|
|
|
|
|
ImGui::PopStyleVar();
|
2026-03-26 21:18:33 +08:00
|
|
|
|
2026-03-28 15:07:19 +08:00
|
|
|
const bool hovered = ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem);
|
|
|
|
|
const bool held = ImGui::IsItemActive();
|
|
|
|
|
if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) {
|
|
|
|
|
open = !open;
|
|
|
|
|
storage->SetBool(openStateId, open);
|
2026-03-26 21:18:33 +08:00
|
|
|
}
|
|
|
|
|
|
2026-03-28 15:07:19 +08:00
|
|
|
const ImVec2 itemMin = ImGui::GetItemRectMin();
|
|
|
|
|
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();
|
|
|
|
|
}
|
2026-03-26 21:18:33 +08:00
|
|
|
|
|
|
|
|
if (BeginPopupContextItem("##ComponentSettings")) {
|
2026-03-27 00:08:46 +08:00
|
|
|
drawMenu();
|
2026-03-26 21:18:33 +08:00
|
|
|
EndPopup();
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-28 15:07:19 +08:00
|
|
|
ImGui::PopID();
|
|
|
|
|
return ComponentSectionResult{ open, InspectorSectionContentIndent() };
|
2026-03-27 00:08:46 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline ComponentSectionResult BeginComponentSection(
|
|
|
|
|
const void* id,
|
|
|
|
|
const char* label,
|
|
|
|
|
bool defaultOpen = true) {
|
|
|
|
|
return BeginComponentSection(id, label, []() {}, defaultOpen);
|
2026-03-26 21:18:33 +08:00
|
|
|
}
|
|
|
|
|
|
2026-03-28 15:07:19 +08:00
|
|
|
inline void EndComponentSection(const ComponentSectionResult& section) {
|
|
|
|
|
if (section.open && section.contentIndent > 0.0f) {
|
|
|
|
|
ImGui::Unindent(section.contentIndent);
|
|
|
|
|
}
|
2026-03-26 21:18:33 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline bool InspectorActionButton(const char* label, ImVec2 size = ImVec2(-1.0f, 0.0f)) {
|
|
|
|
|
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, InspectorActionButtonPadding());
|
|
|
|
|
const bool pressed = ImGui::Button(label, size);
|
|
|
|
|
ImGui::PopStyleVar();
|
|
|
|
|
return pressed;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline bool BeginTitledPopup(const char* id, const char* title) {
|
|
|
|
|
const bool open = BeginPopup(id);
|
|
|
|
|
if (!open) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (title && title[0] != '\0') {
|
|
|
|
|
ImGui::TextUnformatted(title);
|
|
|
|
|
ImGui::Separator();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline void EndTitledPopup() {
|
|
|
|
|
EndPopup();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline DialogActionResult DrawDialogActionRow(
|
|
|
|
|
const char* primaryLabel,
|
|
|
|
|
const char* secondaryLabel,
|
|
|
|
|
bool primaryEnabled = true,
|
|
|
|
|
bool secondaryEnabled = true) {
|
|
|
|
|
DialogActionResult result = DialogActionResult::None;
|
|
|
|
|
|
|
|
|
|
ImGui::BeginDisabled(!primaryEnabled);
|
|
|
|
|
if (ImGui::Button(primaryLabel, DialogActionButtonSize())) {
|
|
|
|
|
result = DialogActionResult::Primary;
|
|
|
|
|
}
|
|
|
|
|
ImGui::EndDisabled();
|
|
|
|
|
|
|
|
|
|
ImGui::SameLine();
|
|
|
|
|
|
|
|
|
|
ImGui::BeginDisabled(!secondaryEnabled);
|
|
|
|
|
if (ImGui::Button(secondaryLabel, DialogActionButtonSize())) {
|
|
|
|
|
result = DialogActionResult::Secondary;
|
|
|
|
|
}
|
|
|
|
|
ImGui::EndDisabled();
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline void DrawRightAlignedText(const char* text, const ImVec4& color, float rightPadding = MenuBarStatusRightPadding()) {
|
|
|
|
|
const ImVec2 textSize = ImGui::CalcTextSize(text);
|
|
|
|
|
const float targetX = ImGui::GetWindowWidth() - textSize.x - rightPadding;
|
|
|
|
|
if (targetX > ImGui::GetCursorPosX()) {
|
|
|
|
|
ImGui::SetCursorPosX(targetX);
|
|
|
|
|
}
|
|
|
|
|
ImGui::TextColored(color, "%s", text);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline void BeginTitledTooltip(const char* title) {
|
|
|
|
|
ImGui::BeginTooltip();
|
|
|
|
|
if (title && title[0] != '\0') {
|
|
|
|
|
ImGui::TextUnformatted(title);
|
|
|
|
|
ImGui::Separator();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline void EndTitledTooltip() {
|
|
|
|
|
ImGui::EndTooltip();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline bool DrawConsoleLogRow(const char* text) {
|
|
|
|
|
ImGui::TextUnformatted(text);
|
|
|
|
|
|
|
|
|
|
if (ImGui::IsItemHovered()) {
|
|
|
|
|
ImDrawList* drawList = ImGui::GetWindowDrawList();
|
|
|
|
|
drawList->AddRectFilled(ImGui::GetItemRectMin(), ImGui::GetItemRectMax(), ImGui::GetColorU32(ConsoleRowHoverFillColor()));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return ImGui::IsItemClicked();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|