#pragma once #include "Core.h" #include "StyleTokens.h" #include #include namespace XCEngine { namespace Editor { namespace UI { struct ComponentSectionResult { bool open = false; }; struct HierarchyNodeResult { bool open = false; bool clicked = false; bool doubleClicked = false; }; 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); }; enum class AssetIconKind { Folder, File }; 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 inline bool DrawMenuScope(const char* label, DrawContentFn&& drawContent) { if (!ImGui::BeginMenu(label)) { return false; } drawContent(); ImGui::EndMenu(); return true; } template 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 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); } } template inline void DrawToolbarBreadcrumbs( const char* rootLabel, size_t segmentCount, GetNameFn&& getName, 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) { ImGui::TextUnformatted(rootLabel); ImGui::PopStyleColor(2); return; } for (size_t i = 0; i < segmentCount; ++i) { if (i > 0) { ImGui::SameLine(); ImGui::TextDisabled("/"); ImGui::SameLine(); } const std::string label = getName(i); if (i + 1 < segmentCount) { if (ImGui::Button(label.c_str())) { navigateToSegment(i); } } else { ImGui::Text("%s", label.c_str()); } } ImGui::PopStyleColor(2); } inline HierarchyNodeResult DrawHierarchyNode( const void* id, const char* label, bool selected, bool leaf) { ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_SpanAvailWidth | ImGuiTreeNodeFlags_FramePadding; if (leaf) { flags |= ImGuiTreeNodeFlags_Leaf; } if (selected) { flags |= ImGuiTreeNodeFlags_Selected; } ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, HierarchyNodeFramePadding()); const bool open = ImGui::TreeNodeEx(id, flags, "%s", label); ImGui::PopStyleVar(); return HierarchyNodeResult{ open, ImGui::IsItemClicked() && !ImGui::IsItemToggledOpen(), ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(0) }; } inline void EndHierarchyNode() { ImGui::TreePop(); } template inline AssetTileResult DrawAssetTile( const char* label, bool selected, bool dimmed, DrawIconFn&& drawIcon) { const ImVec2 tileSize = AssetTileSize(); 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(); if (hovered || selected) { drawList->AddRectFilled(min, max, ImGui::GetColorU32(selected ? AssetTileSelectedFillColor() : AssetTileHoverFillColor()), AssetTileRounding()); } if (selected) { drawList->AddRect(min, max, ImGui::GetColorU32(AssetTileSelectedBorderColor()), AssetTileRounding()); } if (dimmed) { drawList->AddRectFilled(min, max, ImGui::GetColorU32(AssetTileDraggedOverlayColor()), 0.0f); } const ImVec2 iconOffset = AssetTileIconOffset(); const ImVec2 iconSize = AssetTileIconSize(); const ImVec2 iconMin(min.x + iconOffset.x, min.y + iconOffset.y); const ImVec2 iconMax(iconMin.x + iconSize.x, iconMin.y + iconSize.y); drawIcon(drawList, iconMin, iconMax); const ImVec2 textSize = ImGui::CalcTextSize(label); 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); drawList->AddText(ImVec2(min.x + AssetTileTextPadding().x, textY), ImGui::GetColorU32(AssetTileTextColor(selected)), label); ImGui::PopClipRect(); 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 inline ComponentSectionResult BeginComponentSection( const void* id, const char* label, DrawMenuFn&& drawMenu, bool defaultOpen = true) { const ImGuiStyle& style = ImGui::GetStyle(); ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, InspectorSectionFramePadding()); ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(style.ItemSpacing.x, InspectorSectionItemSpacing().y)); ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_Framed | ImGuiTreeNodeFlags_SpanAvailWidth | ImGuiTreeNodeFlags_FramePadding | ImGuiTreeNodeFlags_AllowOverlap; if (defaultOpen) { flags |= ImGuiTreeNodeFlags_DefaultOpen; } const bool open = ImGui::TreeNodeEx(id, flags, "%s", label); ImGui::PopStyleVar(2); if (BeginPopupContextItem("##ComponentSettings")) { drawMenu(); EndPopup(); } return ComponentSectionResult{ open }; } inline ComponentSectionResult BeginComponentSection( const void* id, const char* label, bool defaultOpen = true) { return BeginComponentSection(id, label, []() {}, defaultOpen); } inline void EndComponentSection() { ImGui::TreePop(); } 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(); } } } }