#pragma once #include "ColorPicker.h" #include "Core.h" #include "PropertyLayout.h" #include #include #include namespace XCEngine { namespace Editor { 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, float height, bool popupOpen) { const float frameHeight = (std::max)(height, 1.0f); 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 DrawCompactCheckboxControl(const char* id, bool& value) { const float size = CompactIndicatorSize(); ImGui::InvisibleButton(id, ImVec2(size, size)); const bool hovered = ImGui::IsItemHovered(); const bool held = ImGui::IsItemActive(); const bool pressed = ImGui::IsItemClicked(ImGuiMouseButton_Left); if (pressed) { value = !value; } const ImVec2 min = ImGui::GetItemRectMin(); const ImVec2 max = ImGui::GetItemRectMax(); const float rounding = ImGui::GetStyle().FrameRounding; const ImU32 fillColor = ImGui::GetColorU32( (held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg); const ImU32 borderColor = ImGui::GetColorU32(ImGuiCol_Border); const ImU32 checkColor = ImGui::GetColorU32(ImGuiCol_CheckMark); ImDrawList* drawList = ImGui::GetWindowDrawList(); drawList->AddRectFilled(min, max, fillColor, rounding); drawList->AddRect(min, max, borderColor, rounding); if (value) { const float pad = (std::max)(size * 0.22f, 2.0f); const ImVec2 points[3] = { ImVec2(min.x + pad, min.y + size * 0.56f), ImVec2(min.x + size * 0.44f, max.y - pad), ImVec2(max.x - pad, min.y + pad) }; drawList->AddPolyline(points, 3, checkColor, ImDrawFlags_None, 2.0f); } return pressed; } inline bool DrawFloat( const char* label, float& value, const PropertyLayoutSpec& layoutSpec = MakePropertyLayout(), float dragSpeed = 0.1f, float min = 0.0f, float max = 0.0f, const char* format = "%.2f" ) { const ImVec2 framePadding = ScalarControlFramePadding(); const PropertyLayoutSpec adjustedLayout = WithMinimumRowHeight(layoutSpec, CalcPropertyRowHeightForFramePadding(framePadding)); return DrawPropertyRow(label, adjustedLayout, [&](const PropertyLayoutMetrics& layout) { ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, framePadding); AlignPropertyControlVertically(layout, ImGui::GetFrameHeight()); SetNextPropertyControlWidth(layout); const bool changed = ImGui::DragFloat("##value", &value, dragSpeed, min, max, format); ImGui::PopStyleVar(); return changed; }); } 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( const char* label, int& value, const PropertyLayoutSpec& layoutSpec = MakePropertyLayout(), int step = 1, int min = 0, int max = 0 ) { const ImVec2 framePadding = ScalarControlFramePadding(); const PropertyLayoutSpec adjustedLayout = WithMinimumRowHeight(layoutSpec, CalcPropertyRowHeightForFramePadding(framePadding)); return DrawPropertyRow(label, adjustedLayout, [&](const PropertyLayoutMetrics& layout) { ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, framePadding); AlignPropertyControlVertically(layout, ImGui::GetFrameHeight()); SetNextPropertyControlWidth(layout); const bool changed = ImGui::DragInt("##value", &value, static_cast(step), min, max); ImGui::PopStyleVar(); return changed; }); } inline bool DrawBool( const char* label, bool& value, const PropertyLayoutSpec& layoutSpec = MakePropertyLayout() ) { return DrawPropertyRow(label, layoutSpec, [&](const PropertyLayoutMetrics& layout) { AlignPropertyControlVertically(layout, CompactIndicatorSize()); return DrawCompactCheckboxControl("##value", value); }); } inline bool DrawColor3( const char* label, float color[3], const PropertyLayoutSpec& layoutSpec = MakePropertyLayout() ) { return DrawUnityColor3(label, color, layoutSpec); } inline bool DrawColor4( const char* label, float color[4], const PropertyLayoutSpec& layoutSpec = MakePropertyLayout() ) { return DrawUnityColor4(label, color, layoutSpec); } inline bool DrawSliderFloat( const char* label, float& value, float min, float max, const PropertyLayoutSpec& layoutSpec = MakePropertyLayout(), const char* format = "%.2f" ) { const ImVec2 framePadding = ScalarControlFramePadding(); const PropertyLayoutSpec adjustedLayout = WithMinimumRowHeight(layoutSpec, CalcPropertyRowHeightForFramePadding(framePadding)); return DrawPropertyRow(label, adjustedLayout, [&](const PropertyLayoutMetrics& layout) { 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_FramePadding, framePadding); AlignPropertyControlVertically(layout, ImGui::GetFrameHeight()); 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(); ImGui::PopStyleVar(); return changed; }); } inline bool DrawSliderInt( const char* label, int& value, int min, int max, const PropertyLayoutSpec& layoutSpec = MakePropertyLayout() ) { const ImVec2 framePadding = ScalarControlFramePadding(); const PropertyLayoutSpec adjustedLayout = WithMinimumRowHeight(layoutSpec, CalcPropertyRowHeightForFramePadding(framePadding)); return DrawPropertyRow(label, adjustedLayout, [&](const PropertyLayoutMetrics& layout) { 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_FramePadding, framePadding); AlignPropertyControlVertically(layout, ImGui::GetFrameHeight()); ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(spacing, 0.0f)); const float normalizedValue = range > 0 ? static_cast(value - min) / static_cast(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(std::round(mouseRatio * static_cast(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(); ImGui::PopStyleVar(); return changed; }); } inline int DrawCombo( const char* label, int currentItem, const char* const items[], int itemCount, const PropertyLayoutSpec& layoutSpec = MakePropertyLayout(), int heightInItems = -1 ) { int changedItem = currentItem; DrawPropertyRow(label, layoutSpec, [&](const PropertyLayoutMetrics& layout) { const char* popupId = "##value_popup"; const char* previewValue = (currentItem >= 0 && currentItem < itemCount && items[currentItem] != nullptr) ? items[currentItem] : ""; const float comboWidth = layout.controlWidth; const float popupWidth = comboWidth; const float comboHeight = (std::max)(ImGui::GetFrameHeight() + ComboPreviewHeightOffset(), 1.0f); AlignPropertyControlVertically(layout, comboHeight); DrawComboPreviewFrame("##value", previewValue, comboWidth, comboHeight, 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 changedItem; } } } }