#pragma once #include "Core.h" #include "StyleTokens.h" #include #include namespace XCEngine { namespace Editor { namespace UI { struct ComponentSectionResult { bool open = false; float contentIndent = 0.0f; }; 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); }; struct AssetTileOptions { ImVec2 size = AssetTileSize(); ImVec2 iconOffset = AssetTileIconOffset(); ImVec2 iconSize = AssetTileIconSize(); bool drawIdleFrame = true; bool drawSelectionBorder = true; }; 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) { PushPopupWindowChrome(); if (!ImGui::BeginMenu(label)) { PopPopupWindowChrome(); return false; } PopPopupWindowChrome(); PushPopupContentChrome(); drawContent(); PopPopupContentChrome(); 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); } } 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 inline void DrawToolbarBreadcrumbs( const char* rootLabel, size_t segmentCount, GetNameFn&& getName, NavigateFn&& navigateToSegment) { if (segmentCount == 0) { DrawBreadcrumbSegment(rootLabel, false, true); return; } for (size_t i = 0; i < segmentCount; ++i) { if (i > 0) { ImGui::SameLine(0.0f, BreadcrumbSegmentSpacing()); DrawBreadcrumbSeparator(); ImGui::SameLine(0.0f, BreadcrumbSegmentSpacing()); } 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(i)); if (DrawBreadcrumbSegment(label.c_str(), !current, current)) { navigateToSegment(i); } ImGui::PopID(); } } template inline AssetTileResult DrawAssetTile( const char* label, bool selected, bool dimmed, 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); 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 (options.drawIdleFrame) { drawList->AddRectFilled(min, max, ImGui::GetColorU32(AssetTileIdleFillColor()), AssetTileRounding()); drawList->AddRect(min, max, ImGui::GetColorU32(AssetTileIdleBorderColor()), AssetTileRounding()); } if (hovered || selected) { drawList->AddRectFilled(min, max, ImGui::GetColorU32(selected ? AssetTileSelectedFillColor() : AssetTileHoverFillColor()), AssetTileRounding()); } if (selected && options.drawSelectionBorder) { drawList->AddRect(min, max, ImGui::GetColorU32(AssetTileSelectedBorderColor()), AssetTileRounding()); } if (dimmed) { drawList->AddRectFilled(min, max, ImGui::GetColorU32(AssetTileDraggedOverlayColor()), 0.0f); } 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); 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(); return AssetTileResult{ clicked, contextRequested, openRequested, hovered, min, max }; } template inline ComponentSectionResult BeginComponentSection( const void* id, const char* label, DrawMenuFn&& drawMenu, bool defaultOpen = true) { const ImGuiStyle& style = ImGui::GetStyle(); 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); ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(style.ItemSpacing.x, InspectorSectionItemSpacing().y)); 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 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(); } if (BeginPopupContextItem("##ComponentSettings")) { drawMenu(); EndPopup(); } ImGui::PopID(); return ComponentSectionResult{ open, InspectorSectionContentIndent() }; } inline ComponentSectionResult BeginComponentSection( const void* id, const char* label, bool defaultOpen = true) { return BeginComponentSection(id, label, []() {}, defaultOpen); } inline void EndComponentSection(const ComponentSectionResult& section) { if (section.open && section.contentIndent > 0.0f) { ImGui::Unindent(section.contentIndent); } } 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(); } } } }