381 lines
14 KiB
C++
381 lines
14 KiB
C++
#pragma once
|
|
|
|
#include "ColorPicker.h"
|
|
#include "Core.h"
|
|
#include "PropertyLayout.h"
|
|
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
#include <imgui.h>
|
|
|
|
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<float>(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<float>(value - min) / static_cast<float>(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<int>(std::round(mouseRatio * static_cast<float>(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;
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|