Files
XCEngine/editor/src/UI/ScalarControls.h

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;
}
}
}
}