feat: refine editor color picker UI
This commit is contained in:
843
editor/src/UI/ColorPicker.h
Normal file
843
editor/src/UI/ColorPicker.h
Normal file
@@ -0,0 +1,843 @@
|
||||
#pragma once
|
||||
|
||||
#include "Core.h"
|
||||
#include "PropertyLayout.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Editor {
|
||||
namespace UI {
|
||||
|
||||
namespace detail {
|
||||
|
||||
constexpr float kColorPickerPi = 3.14159265358979323846f;
|
||||
|
||||
struct ColorPickerState {
|
||||
float color[4] = { 1.0f, 1.0f, 1.0f, 1.0f };
|
||||
float hue = 0.0f;
|
||||
float saturation = 0.0f;
|
||||
float value = 1.0f;
|
||||
char hexBuffer[16] = "FFFFFF";
|
||||
bool hexEditing = false;
|
||||
bool initialized = false;
|
||||
bool hueDragging = false;
|
||||
};
|
||||
|
||||
inline std::unordered_map<ImGuiID, ColorPickerState>& ColorPickerStates() {
|
||||
static std::unordered_map<ImGuiID, ColorPickerState> states;
|
||||
return states;
|
||||
}
|
||||
|
||||
inline float ColorPickerHeaderHeight() {
|
||||
return 30.0f;
|
||||
}
|
||||
|
||||
inline float ColorPickerBodyPadding() {
|
||||
return 10.0f;
|
||||
}
|
||||
|
||||
inline float ColorPickerTopRowHeight() {
|
||||
return 34.0f;
|
||||
}
|
||||
|
||||
inline float ColorPickerPreviewWidth() {
|
||||
return 96.0f;
|
||||
}
|
||||
|
||||
inline float ColorPickerPreviewHeight() {
|
||||
return 28.0f;
|
||||
}
|
||||
|
||||
inline float ColorPickerContentSpacing() {
|
||||
return 6.0f;
|
||||
}
|
||||
|
||||
inline float ColorPickerFieldInset() {
|
||||
return 6.0f;
|
||||
}
|
||||
|
||||
inline float ColorPickerHueOuterRadius() {
|
||||
return 110.0f;
|
||||
}
|
||||
|
||||
inline float ColorPickerHueRingThickness() {
|
||||
return 24.0f;
|
||||
}
|
||||
|
||||
inline float ColorPickerValueSquareSize() {
|
||||
return 114.0f;
|
||||
}
|
||||
|
||||
inline float ColorPickerWheelRegionHeight() {
|
||||
return 220.0f;
|
||||
}
|
||||
|
||||
inline float ColorPickerChannelRowHeight() {
|
||||
return 20.0f;
|
||||
}
|
||||
|
||||
inline float ColorPickerInputWidth() {
|
||||
return 62.0f;
|
||||
}
|
||||
|
||||
inline float ColorPickerCloseButtonSize() {
|
||||
return 18.0f;
|
||||
}
|
||||
|
||||
inline float ColorPickerControlRowLayoutHeight() {
|
||||
return (std::max)(ColorPickerChannelRowHeight(), ImGui::GetFrameHeight());
|
||||
}
|
||||
|
||||
inline float ColorPickerHexRowLayoutHeight() {
|
||||
return ImGui::GetFrameHeight();
|
||||
}
|
||||
|
||||
inline ImVec2 ColorPickerPopupSize(bool includeAlpha) {
|
||||
const float spacing = ColorPickerContentSpacing();
|
||||
const float channelRows = includeAlpha ? 4.0f : 3.0f;
|
||||
const float contentHeight =
|
||||
ColorPickerTopRowHeight() +
|
||||
spacing +
|
||||
ColorPickerWheelRegionHeight() +
|
||||
spacing +
|
||||
channelRows * ColorPickerControlRowLayoutHeight() +
|
||||
channelRows * spacing +
|
||||
ColorPickerHexRowLayoutHeight();
|
||||
return ImVec2(
|
||||
292.0f,
|
||||
ColorPickerHeaderHeight() + ColorPickerBodyPadding() * 2.0f + contentHeight);
|
||||
}
|
||||
|
||||
inline ImVec4 ColorPickerHeaderColor() {
|
||||
return ImVec4(0.43f, 0.24f, 0.05f, 1.0f);
|
||||
}
|
||||
|
||||
inline ImVec4 ColorPickerCloseButtonColor() {
|
||||
return ImVec4(0.76f, 0.35f, 0.34f, 1.0f);
|
||||
}
|
||||
|
||||
inline ImVec4 ColorPickerCloseButtonHoveredColor() {
|
||||
return ImVec4(0.82f, 0.40f, 0.39f, 1.0f);
|
||||
}
|
||||
|
||||
inline ImVec4 ColorPickerBodyColor() {
|
||||
return ImVec4(0.24f, 0.24f, 0.24f, 1.0f);
|
||||
}
|
||||
|
||||
inline ImVec4 ColorPickerBorderColor() {
|
||||
return ImVec4(0.15f, 0.15f, 0.15f, 1.0f);
|
||||
}
|
||||
|
||||
inline ImVec4 ColorPickerPreviewBorderColor() {
|
||||
return ImVec4(0.12f, 0.12f, 0.12f, 1.0f);
|
||||
}
|
||||
|
||||
inline ImVec4 ColorPickerPreviewBaseColor() {
|
||||
return ImVec4(0.19f, 0.19f, 0.19f, 1.0f);
|
||||
}
|
||||
|
||||
inline ImVec4 CheckerDarkColor() {
|
||||
return ImVec4(0.55f, 0.55f, 0.55f, 1.0f);
|
||||
}
|
||||
|
||||
inline ImVec4 CheckerLightColor() {
|
||||
return ImVec4(0.84f, 0.84f, 0.84f, 1.0f);
|
||||
}
|
||||
|
||||
inline ImU32 HueColorU32(float hue, float alpha = 1.0f) {
|
||||
float red = 1.0f;
|
||||
float green = 0.0f;
|
||||
float blue = 0.0f;
|
||||
ImGui::ColorConvertHSVtoRGB(hue, 1.0f, 1.0f, red, green, blue);
|
||||
return ImGui::ColorConvertFloat4ToU32(ImVec4(red, green, blue, alpha));
|
||||
}
|
||||
|
||||
inline ImVec4 ToImVec4(const float color[4]) {
|
||||
return ImVec4(color[0], color[1], color[2], color[3]);
|
||||
}
|
||||
|
||||
inline ImVec2 ClampPopupPositionToViewport(const ImVec2& desiredPosition, const ImVec2& popupSize) {
|
||||
const ImGuiViewport* viewport = ImGui::GetMainViewport();
|
||||
if (!viewport) {
|
||||
return desiredPosition;
|
||||
}
|
||||
|
||||
const ImVec2 min = viewport->WorkPos;
|
||||
const ImVec2 max(viewport->WorkPos.x + viewport->WorkSize.x, viewport->WorkPos.y + viewport->WorkSize.y);
|
||||
const float maxX = (std::max)(min.x, max.x - popupSize.x);
|
||||
const float maxY = (std::max)(min.y, max.y - popupSize.y);
|
||||
return ImVec2(
|
||||
std::clamp(desiredPosition.x, min.x, maxX),
|
||||
std::clamp(desiredPosition.y, min.y, maxY));
|
||||
}
|
||||
|
||||
inline bool NearlyEqual(float lhs, float rhs, float epsilon = 0.0005f) {
|
||||
return std::fabs(lhs - rhs) <= epsilon;
|
||||
}
|
||||
|
||||
inline bool ColorsEqual(const float lhs[4], const float rhs[4], bool includeAlpha) {
|
||||
const int componentCount = includeAlpha ? 4 : 3;
|
||||
for (int i = 0; i < componentCount; ++i) {
|
||||
if (!NearlyEqual(lhs[i], rhs[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
inline void FormatHexBuffer(ColorPickerState& state) {
|
||||
const int red = static_cast<int>(std::round(std::clamp(state.color[0], 0.0f, 1.0f) * 255.0f));
|
||||
const int green = static_cast<int>(std::round(std::clamp(state.color[1], 0.0f, 1.0f) * 255.0f));
|
||||
const int blue = static_cast<int>(std::round(std::clamp(state.color[2], 0.0f, 1.0f) * 255.0f));
|
||||
std::snprintf(state.hexBuffer, sizeof(state.hexBuffer), "%02X%02X%02X", red, green, blue);
|
||||
}
|
||||
|
||||
inline void SyncStateFromColor(ColorPickerState& state, const float color[4], bool includeAlpha) {
|
||||
state.color[0] = color[0];
|
||||
state.color[1] = color[1];
|
||||
state.color[2] = color[2];
|
||||
state.color[3] = includeAlpha ? color[3] : 1.0f;
|
||||
|
||||
float hue = state.hue;
|
||||
float saturation = state.saturation;
|
||||
float value = state.value;
|
||||
ImGui::ColorConvertRGBtoHSV(state.color[0], state.color[1], state.color[2], hue, saturation, value);
|
||||
|
||||
if (saturation > 0.0001f) {
|
||||
state.hue = hue;
|
||||
}
|
||||
state.saturation = saturation;
|
||||
state.value = value;
|
||||
|
||||
if (!state.hexEditing) {
|
||||
FormatHexBuffer(state);
|
||||
}
|
||||
state.initialized = true;
|
||||
}
|
||||
|
||||
inline void SyncStateFromColorIfNeeded(ColorPickerState& state, const float color[4], bool includeAlpha) {
|
||||
if (!state.initialized || !ColorsEqual(state.color, color, includeAlpha)) {
|
||||
SyncStateFromColor(state, color, includeAlpha);
|
||||
}
|
||||
}
|
||||
|
||||
inline void RebuildRgbFromHsv(ColorPickerState& state) {
|
||||
ImGui::ColorConvertHSVtoRGB(
|
||||
std::clamp(state.hue, 0.0f, 1.0f),
|
||||
std::clamp(state.saturation, 0.0f, 1.0f),
|
||||
std::clamp(state.value, 0.0f, 1.0f),
|
||||
state.color[0],
|
||||
state.color[1],
|
||||
state.color[2]);
|
||||
}
|
||||
|
||||
inline void RebuildHsvFromRgb(ColorPickerState& state) {
|
||||
float hue = state.hue;
|
||||
float saturation = state.saturation;
|
||||
float value = state.value;
|
||||
ImGui::ColorConvertRGBtoHSV(state.color[0], state.color[1], state.color[2], hue, saturation, value);
|
||||
if (saturation > 0.0001f) {
|
||||
state.hue = hue;
|
||||
}
|
||||
state.saturation = saturation;
|
||||
state.value = value;
|
||||
}
|
||||
|
||||
inline bool TryParseHexColor(ColorPickerState& state) {
|
||||
std::string text(state.hexBuffer);
|
||||
text.erase(std::remove_if(text.begin(), text.end(), [](unsigned char ch) {
|
||||
return ch == '#' || std::isspace(ch) != 0;
|
||||
}), text.end());
|
||||
|
||||
if (text.size() != 6 && text.size() != 8) {
|
||||
return false;
|
||||
}
|
||||
|
||||
unsigned int value = 0;
|
||||
if (std::sscanf(text.c_str(), "%x", &value) != 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (text.size() == 6) {
|
||||
state.color[0] = static_cast<float>((value >> 16) & 0xFF) / 255.0f;
|
||||
state.color[1] = static_cast<float>((value >> 8) & 0xFF) / 255.0f;
|
||||
state.color[2] = static_cast<float>(value & 0xFF) / 255.0f;
|
||||
} else {
|
||||
state.color[0] = static_cast<float>((value >> 24) & 0xFF) / 255.0f;
|
||||
state.color[1] = static_cast<float>((value >> 16) & 0xFF) / 255.0f;
|
||||
state.color[2] = static_cast<float>((value >> 8) & 0xFF) / 255.0f;
|
||||
state.color[3] = static_cast<float>(value & 0xFF) / 255.0f;
|
||||
}
|
||||
|
||||
RebuildHsvFromRgb(state);
|
||||
FormatHexBuffer(state);
|
||||
return true;
|
||||
}
|
||||
|
||||
inline void DrawCheckerboard(ImDrawList* drawList, const ImVec2& min, const ImVec2& max, float cellSize = 6.0f) {
|
||||
if (!drawList) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ImU32 darkColor = ImGui::GetColorU32(CheckerDarkColor());
|
||||
const ImU32 lightColor = ImGui::GetColorU32(CheckerLightColor());
|
||||
const int columns = static_cast<int>(std::ceil((max.x - min.x) / cellSize));
|
||||
const int rows = static_cast<int>(std::ceil((max.y - min.y) / cellSize));
|
||||
|
||||
for (int row = 0; row < rows; ++row) {
|
||||
for (int column = 0; column < columns; ++column) {
|
||||
const bool light = ((row + column) & 1) == 0;
|
||||
const ImVec2 cellMin(min.x + cellSize * static_cast<float>(column), min.y + cellSize * static_cast<float>(row));
|
||||
const ImVec2 cellMax(
|
||||
std::min(cellMin.x + cellSize, max.x),
|
||||
std::min(cellMin.y + cellSize, max.y));
|
||||
drawList->AddRectFilled(cellMin, cellMax, light ? lightColor : darkColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline bool ShouldDrawAlphaCheckerboard(const ImVec4& color) {
|
||||
return color.w < 0.999f;
|
||||
}
|
||||
|
||||
inline void DrawPreviewSwatch(
|
||||
ImDrawList* drawList,
|
||||
const ImRect& rect,
|
||||
const ImVec4& color,
|
||||
float rounding = 2.0f,
|
||||
bool showAlphaBackground = false) {
|
||||
if (!drawList) {
|
||||
return;
|
||||
}
|
||||
|
||||
drawList->AddRectFilled(rect.Min, rect.Max, ImGui::GetColorU32(ColorPickerPreviewBaseColor()), rounding);
|
||||
if (showAlphaBackground) {
|
||||
DrawCheckerboard(drawList, rect.Min, rect.Max);
|
||||
}
|
||||
drawList->AddRectFilled(rect.Min, rect.Max, ImGui::ColorConvertFloat4ToU32(color), rounding);
|
||||
drawList->AddRect(rect.Min, rect.Max, ImGui::GetColorU32(ColorPickerPreviewBorderColor()), rounding);
|
||||
}
|
||||
|
||||
inline void SetColorComponent(ImVec4& color, int componentIndex, float value) {
|
||||
switch (componentIndex) {
|
||||
case 0: color.x = value; break;
|
||||
case 1: color.y = value; break;
|
||||
case 2: color.z = value; break;
|
||||
default: color.w = value; break;
|
||||
}
|
||||
}
|
||||
|
||||
inline bool DrawColorPreviewButton(const char* id, const float color[4], float size, bool popupOpen, ImRect& outRect) {
|
||||
const float previewSize = (std::max)(size, 1.0f);
|
||||
ImGui::InvisibleButton(id, ImVec2(previewSize, previewSize));
|
||||
|
||||
const bool hovered = ImGui::IsItemHovered();
|
||||
const bool pressed = ImGui::IsItemClicked(ImGuiMouseButton_Left);
|
||||
const ImVec2 min = ImGui::GetItemRectMin();
|
||||
const ImVec2 max = ImGui::GetItemRectMax();
|
||||
outRect = ImRect(min, max);
|
||||
|
||||
const float rounding = ImGui::GetStyle().FrameRounding;
|
||||
const ImU32 borderColor = ImGui::GetColorU32(ImGuiCol_Border);
|
||||
|
||||
ImDrawList* drawList = ImGui::GetWindowDrawList();
|
||||
const ImVec4 previewColor = ToImVec4(color);
|
||||
DrawPreviewSwatch(drawList, ImRect(min, max), previewColor, rounding, false);
|
||||
if (popupOpen || hovered) {
|
||||
const ImU32 overlayColor = popupOpen ? IM_COL32(255, 255, 255, 24) : IM_COL32(255, 255, 255, 12);
|
||||
drawList->AddRectFilled(min, max, overlayColor, rounding);
|
||||
}
|
||||
drawList->AddRect(min, max, borderColor, rounding);
|
||||
return pressed;
|
||||
}
|
||||
|
||||
inline bool DrawHueWheel(const char* id, ColorPickerState& state, const ImVec2& center) {
|
||||
const float outerRadius = ColorPickerHueOuterRadius();
|
||||
const float innerRadius = outerRadius - ColorPickerHueRingThickness();
|
||||
const float feather = 1.5f;
|
||||
const float outerFadeRadius = outerRadius + feather;
|
||||
const float innerFadeRadius = (std::max)(innerRadius - feather, 1.0f);
|
||||
const ImVec2 wheelMin(center.x - outerRadius, center.y - outerRadius);
|
||||
const ImVec2 wheelSize(outerRadius * 2.0f, outerRadius * 2.0f);
|
||||
const ImVec2 mouse = ImGui::GetIO().MousePos;
|
||||
const bool windowHovered = ImGui::IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem);
|
||||
const bool insideBounds =
|
||||
mouse.x >= wheelMin.x &&
|
||||
mouse.x <= wheelMin.x + wheelSize.x &&
|
||||
mouse.y >= wheelMin.y &&
|
||||
mouse.y <= wheelMin.y + wheelSize.y;
|
||||
const float dx = mouse.x - center.x;
|
||||
const float dy = mouse.y - center.y;
|
||||
const float distance = std::sqrt(dx * dx + dy * dy);
|
||||
const bool hoveringRing = windowHovered &&
|
||||
insideBounds &&
|
||||
distance >= innerRadius - 5.0f &&
|
||||
distance <= outerRadius + 5.0f;
|
||||
|
||||
if (!ImGui::IsMouseDown(ImGuiMouseButton_Left)) {
|
||||
state.hueDragging = false;
|
||||
} else if (!state.hueDragging && ImGui::IsMouseClicked(ImGuiMouseButton_Left) && hoveringRing) {
|
||||
state.hueDragging = true;
|
||||
}
|
||||
|
||||
bool changed = false;
|
||||
if (state.hueDragging) {
|
||||
float angle = std::atan2(dy, dx);
|
||||
if (angle < 0.0f) {
|
||||
angle += 2.0f * kColorPickerPi;
|
||||
}
|
||||
state.hue = std::clamp(angle / (2.0f * kColorPickerPi), 0.0f, 1.0f);
|
||||
changed = true;
|
||||
}
|
||||
|
||||
ImDrawList* drawList = ImGui::GetWindowDrawList();
|
||||
const ImVec2 uvWhite = ImGui::GetFontTexUvWhitePixel();
|
||||
const int segmentCount = (std::max)(192, static_cast<int>(outerRadius * 2.4f));
|
||||
for (int i = 0; i < segmentCount; ++i) {
|
||||
const float hue0 = static_cast<float>(i) / static_cast<float>(segmentCount);
|
||||
const float hue1 = static_cast<float>(i + 1) / static_cast<float>(segmentCount);
|
||||
const float angle0 = hue0 * 2.0f * kColorPickerPi;
|
||||
const float angle1 = hue1 * 2.0f * kColorPickerPi;
|
||||
|
||||
ImVec2 outerFade0(center.x + std::cos(angle0) * outerFadeRadius, center.y + std::sin(angle0) * outerFadeRadius);
|
||||
ImVec2 outerFade1(center.x + std::cos(angle1) * outerFadeRadius, center.y + std::sin(angle1) * outerFadeRadius);
|
||||
ImVec2 outer0(center.x + std::cos(angle0) * outerRadius, center.y + std::sin(angle0) * outerRadius);
|
||||
ImVec2 outer1(center.x + std::cos(angle1) * outerRadius, center.y + std::sin(angle1) * outerRadius);
|
||||
ImVec2 inner0(center.x + std::cos(angle0) * innerRadius, center.y + std::sin(angle0) * innerRadius);
|
||||
ImVec2 inner1(center.x + std::cos(angle1) * innerRadius, center.y + std::sin(angle1) * innerRadius);
|
||||
ImVec2 innerFade0(center.x + std::cos(angle0) * innerFadeRadius, center.y + std::sin(angle0) * innerFadeRadius);
|
||||
ImVec2 innerFade1(center.x + std::cos(angle1) * innerFadeRadius, center.y + std::sin(angle1) * innerFadeRadius);
|
||||
|
||||
const ImU32 col0 = HueColorU32(hue0, 1.0f);
|
||||
const ImU32 col1 = HueColorU32(hue1, 1.0f);
|
||||
const ImU32 col0Transparent = HueColorU32(hue0, 0.0f);
|
||||
const ImU32 col1Transparent = HueColorU32(hue1, 0.0f);
|
||||
|
||||
drawList->PrimReserve(18, 12);
|
||||
|
||||
drawList->PrimWriteIdx(static_cast<ImDrawIdx>(drawList->_VtxCurrentIdx + 0));
|
||||
drawList->PrimWriteIdx(static_cast<ImDrawIdx>(drawList->_VtxCurrentIdx + 1));
|
||||
drawList->PrimWriteIdx(static_cast<ImDrawIdx>(drawList->_VtxCurrentIdx + 2));
|
||||
drawList->PrimWriteIdx(static_cast<ImDrawIdx>(drawList->_VtxCurrentIdx + 0));
|
||||
drawList->PrimWriteIdx(static_cast<ImDrawIdx>(drawList->_VtxCurrentIdx + 2));
|
||||
drawList->PrimWriteIdx(static_cast<ImDrawIdx>(drawList->_VtxCurrentIdx + 3));
|
||||
drawList->PrimWriteVtx(outerFade0, uvWhite, col0Transparent);
|
||||
drawList->PrimWriteVtx(outerFade1, uvWhite, col1Transparent);
|
||||
drawList->PrimWriteVtx(outer1, uvWhite, col1);
|
||||
drawList->PrimWriteVtx(outer0, uvWhite, col0);
|
||||
|
||||
drawList->PrimWriteIdx(static_cast<ImDrawIdx>(drawList->_VtxCurrentIdx + 0));
|
||||
drawList->PrimWriteIdx(static_cast<ImDrawIdx>(drawList->_VtxCurrentIdx + 1));
|
||||
drawList->PrimWriteIdx(static_cast<ImDrawIdx>(drawList->_VtxCurrentIdx + 2));
|
||||
drawList->PrimWriteIdx(static_cast<ImDrawIdx>(drawList->_VtxCurrentIdx + 0));
|
||||
drawList->PrimWriteIdx(static_cast<ImDrawIdx>(drawList->_VtxCurrentIdx + 2));
|
||||
drawList->PrimWriteIdx(static_cast<ImDrawIdx>(drawList->_VtxCurrentIdx + 3));
|
||||
drawList->PrimWriteVtx(outer0, uvWhite, col0);
|
||||
drawList->PrimWriteVtx(outer1, uvWhite, col1);
|
||||
drawList->PrimWriteVtx(inner1, uvWhite, col1);
|
||||
drawList->PrimWriteVtx(inner0, uvWhite, col0);
|
||||
|
||||
drawList->PrimWriteIdx(static_cast<ImDrawIdx>(drawList->_VtxCurrentIdx + 0));
|
||||
drawList->PrimWriteIdx(static_cast<ImDrawIdx>(drawList->_VtxCurrentIdx + 1));
|
||||
drawList->PrimWriteIdx(static_cast<ImDrawIdx>(drawList->_VtxCurrentIdx + 2));
|
||||
drawList->PrimWriteIdx(static_cast<ImDrawIdx>(drawList->_VtxCurrentIdx + 0));
|
||||
drawList->PrimWriteIdx(static_cast<ImDrawIdx>(drawList->_VtxCurrentIdx + 2));
|
||||
drawList->PrimWriteIdx(static_cast<ImDrawIdx>(drawList->_VtxCurrentIdx + 3));
|
||||
drawList->PrimWriteVtx(inner0, uvWhite, col0);
|
||||
drawList->PrimWriteVtx(inner1, uvWhite, col1);
|
||||
drawList->PrimWriteVtx(innerFade1, uvWhite, col1Transparent);
|
||||
drawList->PrimWriteVtx(innerFade0, uvWhite, col0Transparent);
|
||||
}
|
||||
|
||||
const float selectionAngle = state.hue * 2.0f * kColorPickerPi;
|
||||
const float selectionRadius = (outerRadius + innerRadius) * 0.5f;
|
||||
const ImVec2 selectionCenter(
|
||||
center.x + std::cos(selectionAngle) * selectionRadius,
|
||||
center.y + std::sin(selectionAngle) * selectionRadius);
|
||||
drawList->AddCircle(selectionCenter, 13.0f, IM_COL32(255, 255, 255, 255), 28, 2.2f);
|
||||
drawList->AddCircle(selectionCenter, 12.0f, IM_COL32(0, 0, 0, 92), 28, 1.0f);
|
||||
return changed;
|
||||
}
|
||||
|
||||
inline bool DrawValueSquare(const char* id, ColorPickerState& state, const ImVec2& min) {
|
||||
const float size = ColorPickerValueSquareSize();
|
||||
const ImVec2 max(min.x + size, min.y + size);
|
||||
const ImVec2 restoreCursorPos = ImGui::GetCursorPos();
|
||||
ImGui::SetCursorScreenPos(min);
|
||||
ImGui::InvisibleButton(id, ImVec2(size, size));
|
||||
|
||||
const bool active = ImGui::IsItemActive();
|
||||
bool changed = false;
|
||||
if (active) {
|
||||
const ImVec2 mouse = ImGui::GetIO().MousePos;
|
||||
state.saturation = std::clamp((mouse.x - min.x) / size, 0.0f, 1.0f);
|
||||
state.value = std::clamp(1.0f - (mouse.y - min.y) / size, 0.0f, 1.0f);
|
||||
changed = true;
|
||||
}
|
||||
|
||||
float red = 1.0f, green = 0.0f, blue = 0.0f;
|
||||
ImGui::ColorConvertHSVtoRGB(state.hue, 1.0f, 1.0f, red, green, blue);
|
||||
ImDrawList* drawList = ImGui::GetWindowDrawList();
|
||||
drawList->AddRectFilledMultiColor(
|
||||
min,
|
||||
max,
|
||||
IM_COL32(255, 255, 255, 255),
|
||||
ImGui::ColorConvertFloat4ToU32(ImVec4(red, green, blue, 1.0f)),
|
||||
ImGui::ColorConvertFloat4ToU32(ImVec4(red, green, blue, 1.0f)),
|
||||
IM_COL32(255, 255, 255, 255));
|
||||
drawList->AddRectFilledMultiColor(
|
||||
min,
|
||||
max,
|
||||
IM_COL32(0, 0, 0, 0),
|
||||
IM_COL32(0, 0, 0, 0),
|
||||
IM_COL32(0, 0, 0, 255),
|
||||
IM_COL32(0, 0, 0, 255));
|
||||
drawList->AddRect(min, max, ImGui::GetColorU32(ColorPickerPreviewBorderColor()), 0.0f);
|
||||
|
||||
const ImVec2 selection(
|
||||
min.x + state.saturation * size,
|
||||
min.y + (1.0f - state.value) * size);
|
||||
drawList->AddCircle(selection, 8.0f, IM_COL32(255, 255, 255, 255), 24, 2.0f);
|
||||
drawList->AddCircle(selection, 7.0f, IM_COL32(0, 0, 0, 96), 24, 1.0f);
|
||||
ImGui::SetCursorPos(restoreCursorPos);
|
||||
return changed;
|
||||
}
|
||||
|
||||
inline bool DrawChannelGradientSlider(
|
||||
const char* id,
|
||||
float& value,
|
||||
const ImVec2& size,
|
||||
const ImVec4& colorA,
|
||||
const ImVec4& colorB,
|
||||
bool drawAlphaBackground = false) {
|
||||
ImGui::InvisibleButton(id, size);
|
||||
bool changed = false;
|
||||
if (ImGui::IsItemActive()) {
|
||||
const float t = std::clamp((ImGui::GetIO().MousePos.x - ImGui::GetItemRectMin().x) / size.x, 0.0f, 1.0f);
|
||||
value = t;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
const ImVec2 min = ImGui::GetItemRectMin();
|
||||
const ImVec2 max = ImGui::GetItemRectMax();
|
||||
ImDrawList* drawList = ImGui::GetWindowDrawList();
|
||||
if (drawAlphaBackground) {
|
||||
DrawCheckerboard(drawList, min, max, 5.0f);
|
||||
}
|
||||
|
||||
drawList->AddRectFilledMultiColor(
|
||||
min,
|
||||
max,
|
||||
ImGui::ColorConvertFloat4ToU32(colorA),
|
||||
ImGui::ColorConvertFloat4ToU32(colorB),
|
||||
ImGui::ColorConvertFloat4ToU32(colorB),
|
||||
ImGui::ColorConvertFloat4ToU32(colorA));
|
||||
drawList->AddRect(min, max, ImGui::GetColorU32(ColorPickerPreviewBorderColor()), 0.0f);
|
||||
|
||||
const float x = min.x + std::clamp(value, 0.0f, 1.0f) * size.x;
|
||||
drawList->AddLine(ImVec2(x, min.y), ImVec2(x, max.y), IM_COL32(255, 255, 255, 255), 2.0f);
|
||||
drawList->AddLine(ImVec2(x + 1.5f, min.y), ImVec2(x + 1.5f, max.y), IM_COL32(0, 0, 0, 100), 1.0f);
|
||||
return changed;
|
||||
}
|
||||
|
||||
inline bool DrawChannelRow(const char* label, int componentIndex, ColorPickerState& state, float rowWidth) {
|
||||
constexpr float labelWidth = 14.0f;
|
||||
constexpr float spacing = 8.0f;
|
||||
const float inputWidth = ColorPickerInputWidth();
|
||||
const float sliderWidth = ImMax(rowWidth - labelWidth - spacing - inputWidth - spacing, 60.0f);
|
||||
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextUnformatted(label);
|
||||
ImGui::SameLine(0.0f, spacing);
|
||||
|
||||
ImGui::PushID(label);
|
||||
float& value = state.color[componentIndex];
|
||||
ImVec4 startColor(state.color[0], state.color[1], state.color[2], 1.0f);
|
||||
ImVec4 endColor(startColor);
|
||||
SetColorComponent(startColor, componentIndex, 0.0f);
|
||||
SetColorComponent(endColor, componentIndex, 1.0f);
|
||||
const bool gradientChanged = DrawChannelGradientSlider("##slider", value, ImVec2(sliderWidth, ColorPickerChannelRowHeight()), startColor, endColor);
|
||||
|
||||
ImGui::SameLine(0.0f, spacing);
|
||||
float inputValue = value;
|
||||
ImGui::SetNextItemWidth(inputWidth);
|
||||
const bool inputChanged = ImGui::InputFloat("##input", &inputValue, 0.0f, 0.0f, "%.4f");
|
||||
value = std::clamp(inputValue, 0.0f, 1.0f);
|
||||
ImGui::PopID();
|
||||
return gradientChanged || inputChanged;
|
||||
}
|
||||
|
||||
inline bool DrawAlphaRow(ColorPickerState& state, float rowWidth) {
|
||||
constexpr float labelWidth = 14.0f;
|
||||
constexpr float spacing = 8.0f;
|
||||
const float inputWidth = ColorPickerInputWidth();
|
||||
const float sliderWidth = ImMax(rowWidth - labelWidth - spacing - inputWidth - spacing, 60.0f);
|
||||
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextUnformatted("A");
|
||||
ImGui::SameLine(0.0f, spacing);
|
||||
|
||||
ImGui::PushID("Alpha");
|
||||
const ImVec4 startColor(state.color[0], state.color[1], state.color[2], 0.0f);
|
||||
const ImVec4 endColor(state.color[0], state.color[1], state.color[2], 1.0f);
|
||||
bool changed = DrawChannelGradientSlider(
|
||||
"##slider",
|
||||
state.color[3],
|
||||
ImVec2(sliderWidth, ColorPickerChannelRowHeight()),
|
||||
startColor,
|
||||
endColor,
|
||||
true);
|
||||
|
||||
ImGui::SameLine(0.0f, spacing);
|
||||
float inputValue = state.color[3];
|
||||
ImGui::SetNextItemWidth(inputWidth);
|
||||
changed = ImGui::InputFloat("##input", &inputValue, 0.0f, 0.0f, "%.4f") || changed;
|
||||
state.color[3] = std::clamp(inputValue, 0.0f, 1.0f);
|
||||
ImGui::PopID();
|
||||
return changed;
|
||||
}
|
||||
|
||||
inline bool DrawHexRow(ColorPickerState& state, float rowWidth) {
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextUnformatted("Hexadecimal");
|
||||
ImGui::SameLine(0.0f, 8.0f);
|
||||
|
||||
if (!state.hexEditing) {
|
||||
FormatHexBuffer(state);
|
||||
}
|
||||
|
||||
ImGui::SetNextItemWidth(ImMax(rowWidth - ImGui::CalcTextSize("Hexadecimal").x - 8.0f, 40.0f));
|
||||
const bool edited = ImGui::InputText(
|
||||
"##hex",
|
||||
state.hexBuffer,
|
||||
IM_ARRAYSIZE(state.hexBuffer),
|
||||
ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase | ImGuiInputTextFlags_EnterReturnsTrue);
|
||||
|
||||
bool changed = false;
|
||||
if (ImGui::IsItemActive()) {
|
||||
state.hexEditing = true;
|
||||
} else if (state.hexEditing) {
|
||||
changed = TryParseHexColor(state) || edited;
|
||||
state.hexEditing = false;
|
||||
} else if (edited) {
|
||||
changed = TryParseHexColor(state);
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
inline bool DrawCloseButton(const char* id, const ImVec2& size) {
|
||||
ImGui::InvisibleButton(id, size);
|
||||
const bool hovered = ImGui::IsItemHovered();
|
||||
const bool pressed = ImGui::IsItemClicked(ImGuiMouseButton_Left);
|
||||
const ImRect rect(ImGui::GetItemRectMin(), ImGui::GetItemRectMax());
|
||||
ImDrawList* drawList = ImGui::GetWindowDrawList();
|
||||
const ImU32 fillColor = ImGui::GetColorU32(hovered ? ColorPickerCloseButtonHoveredColor() : ColorPickerCloseButtonColor());
|
||||
drawList->AddRectFilled(rect.Min, rect.Max, fillColor, 0.0f);
|
||||
const float pad = 5.0f;
|
||||
drawList->AddLine(
|
||||
ImVec2(rect.Min.x + pad, rect.Min.y + pad),
|
||||
ImVec2(rect.Max.x - pad, rect.Max.y - pad),
|
||||
IM_COL32(255, 255, 255, 255),
|
||||
1.2f);
|
||||
drawList->AddLine(
|
||||
ImVec2(rect.Max.x - pad, rect.Min.y + pad),
|
||||
ImVec2(rect.Min.x + pad, rect.Max.y - pad),
|
||||
IM_COL32(255, 255, 255, 255),
|
||||
1.2f);
|
||||
return pressed;
|
||||
}
|
||||
|
||||
inline bool DrawUnityColorPickerPopup(const char* popupId, ColorPickerState& state, bool includeAlpha) {
|
||||
const float headerHeight = ColorPickerHeaderHeight();
|
||||
const float bodyPadding = ColorPickerBodyPadding();
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_PopupRounding, 3.0f);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_PopupBorderSize, 1.0f);
|
||||
ImGui::PushStyleColor(ImGuiCol_PopupBg, ColorPickerBodyColor());
|
||||
ImGui::PushStyleColor(ImGuiCol_Border, ColorPickerBorderColor());
|
||||
|
||||
bool popupOpen = ImGui::BeginPopup(
|
||||
popupId,
|
||||
ImGuiWindowFlags_NoMove |
|
||||
ImGuiWindowFlags_NoResize |
|
||||
ImGuiWindowFlags_NoSavedSettings |
|
||||
ImGuiWindowFlags_NoScrollbar |
|
||||
ImGuiWindowFlags_NoScrollWithMouse);
|
||||
if (!popupOpen) {
|
||||
ImGui::PopStyleColor(2);
|
||||
ImGui::PopStyleVar(3);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool changed = false;
|
||||
ImDrawList* drawList = ImGui::GetWindowDrawList();
|
||||
const ImVec2 windowPos = ImGui::GetWindowPos();
|
||||
const ImVec2 windowSize = ImGui::GetWindowSize();
|
||||
const ImVec2 windowMax(windowPos.x + windowSize.x, windowPos.y + windowSize.y);
|
||||
drawList->AddRectFilled(windowPos, windowMax, ImGui::GetColorU32(ColorPickerBodyColor()), 3.0f);
|
||||
drawList->AddRectFilled(
|
||||
windowPos,
|
||||
ImVec2(windowMax.x, windowPos.y + headerHeight),
|
||||
ImGui::GetColorU32(ColorPickerHeaderColor()),
|
||||
3.0f,
|
||||
ImDrawFlags_RoundCornersTop);
|
||||
drawList->AddRect(windowPos, windowMax, ImGui::GetColorU32(ColorPickerBorderColor()), 3.0f);
|
||||
drawList->AddText(ImVec2(windowPos.x + 10.0f, windowPos.y + 8.0f), IM_COL32(255, 255, 255, 255), "Color");
|
||||
|
||||
const float closeButtonSize = ColorPickerCloseButtonSize();
|
||||
ImGui::SetCursorPos(ImVec2(windowSize.x - closeButtonSize - 4.0f, 5.0f));
|
||||
if (DrawCloseButton("##close", ImVec2(closeButtonSize, closeButtonSize))) {
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
|
||||
const float contentWidth = windowSize.x - bodyPadding * 2.0f;
|
||||
const float fieldInset = ColorPickerFieldInset();
|
||||
const float fieldWidth = contentWidth - fieldInset * 2.0f;
|
||||
ImGui::SetCursorPos(ImVec2(bodyPadding, headerHeight + bodyPadding));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(8.0f, ColorPickerContentSpacing()));
|
||||
|
||||
const ImVec2 previewRowMin = ImGui::GetCursorScreenPos();
|
||||
ImGui::Dummy(ImVec2(contentWidth, ColorPickerTopRowHeight()));
|
||||
const float previewOffsetY = (ColorPickerTopRowHeight() - ColorPickerPreviewHeight()) * 0.5f;
|
||||
const ImVec4 previewColor = ToImVec4(state.color);
|
||||
DrawPreviewSwatch(
|
||||
drawList,
|
||||
ImRect(
|
||||
ImVec2(previewRowMin.x + contentWidth - ColorPickerPreviewWidth(), previewRowMin.y + previewOffsetY),
|
||||
ImVec2(previewRowMin.x + contentWidth, previewRowMin.y + previewOffsetY + ColorPickerPreviewHeight())),
|
||||
previewColor,
|
||||
2.0f,
|
||||
includeAlpha && ShouldDrawAlphaCheckerboard(previewColor));
|
||||
|
||||
const ImVec2 wheelRegionMin = ImGui::GetCursorScreenPos();
|
||||
ImGui::Dummy(ImVec2(contentWidth, ColorPickerWheelRegionHeight()));
|
||||
const ImVec2 wheelRegionCursor = ImGui::GetCursorPos();
|
||||
|
||||
const ImVec2 ringCenter(
|
||||
wheelRegionMin.x + contentWidth * 0.5f,
|
||||
wheelRegionMin.y + ColorPickerHueOuterRadius());
|
||||
const bool hueChanged = DrawHueWheel("##HueWheel", state, ringCenter);
|
||||
|
||||
const float squareSize = ColorPickerValueSquareSize();
|
||||
const ImVec2 squareMin(ringCenter.x - squareSize * 0.5f, ringCenter.y - squareSize * 0.5f);
|
||||
const bool squareChanged = DrawValueSquare("##ValueSquare", state, squareMin);
|
||||
ImGui::SetCursorPos(wheelRegionCursor);
|
||||
|
||||
if (hueChanged || squareChanged) {
|
||||
RebuildRgbFromHsv(state);
|
||||
changed = true;
|
||||
}
|
||||
|
||||
ImGui::SetCursorPosX(bodyPadding + fieldInset);
|
||||
if (DrawChannelRow("R", 0, state, fieldWidth)) {
|
||||
RebuildHsvFromRgb(state);
|
||||
changed = true;
|
||||
}
|
||||
ImGui::SetCursorPosX(bodyPadding + fieldInset);
|
||||
if (DrawChannelRow("G", 1, state, fieldWidth)) {
|
||||
RebuildHsvFromRgb(state);
|
||||
changed = true;
|
||||
}
|
||||
ImGui::SetCursorPosX(bodyPadding + fieldInset);
|
||||
if (DrawChannelRow("B", 2, state, fieldWidth)) {
|
||||
RebuildHsvFromRgb(state);
|
||||
changed = true;
|
||||
}
|
||||
ImGui::SetCursorPosX(bodyPadding + fieldInset);
|
||||
if (includeAlpha && DrawAlphaRow(state, fieldWidth)) {
|
||||
changed = true;
|
||||
}
|
||||
|
||||
ImGui::SetCursorPosX(bodyPadding + fieldInset);
|
||||
if (DrawHexRow(state, fieldWidth)) {
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (changed && !state.hexEditing) {
|
||||
FormatHexBuffer(state);
|
||||
}
|
||||
|
||||
ImGui::PopStyleVar();
|
||||
ImGui::EndPopup();
|
||||
ImGui::PopStyleColor(2);
|
||||
ImGui::PopStyleVar(3);
|
||||
return changed;
|
||||
}
|
||||
|
||||
inline bool DrawUnityColorControl(
|
||||
const char* label,
|
||||
float color[4],
|
||||
bool includeAlpha,
|
||||
const PropertyLayoutSpec& layoutSpec = MakePropertyLayout()) {
|
||||
bool changed = false;
|
||||
DrawPropertyRow(label, layoutSpec, [&](const PropertyLayoutMetrics& layout) {
|
||||
const float controlSize = CompactIndicatorSize();
|
||||
AlignPropertyControlVertically(layout, controlSize);
|
||||
ImRect previewRect;
|
||||
const bool popupOpen = ImGui::IsPopupOpen("##picker_popup");
|
||||
if (DrawColorPreviewButton("##value", color, controlSize, popupOpen, previewRect)) {
|
||||
auto& state = ColorPickerStates()[ImGui::GetID("##picker_state")];
|
||||
SyncStateFromColor(state, color, includeAlpha);
|
||||
ImGui::OpenPopup("##picker_popup");
|
||||
}
|
||||
|
||||
auto& state = ColorPickerStates()[ImGui::GetID("##picker_state")];
|
||||
SyncStateFromColorIfNeeded(state, color, includeAlpha);
|
||||
const ImVec2 popupSize = ColorPickerPopupSize(includeAlpha);
|
||||
const ImVec2 popupPos = ClampPopupPositionToViewport(
|
||||
ImVec2(previewRect.Min.x, previewRect.Max.y + 2.0f),
|
||||
popupSize);
|
||||
ImGui::SetNextWindowPos(popupPos, ImGuiCond_Always);
|
||||
ImGui::SetNextWindowSize(popupSize, ImGuiCond_Always);
|
||||
if (DrawUnityColorPickerPopup("##picker_popup", state, includeAlpha)) {
|
||||
color[0] = state.color[0];
|
||||
color[1] = state.color[1];
|
||||
color[2] = state.color[2];
|
||||
if (includeAlpha) {
|
||||
color[3] = state.color[3];
|
||||
}
|
||||
changed = true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
return changed;
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
inline bool DrawUnityColor3(
|
||||
const char* label,
|
||||
float color[3],
|
||||
const PropertyLayoutSpec& layoutSpec = MakePropertyLayout()) {
|
||||
float rgba[4] = { color[0], color[1], color[2], 1.0f };
|
||||
const bool changed = detail::DrawUnityColorControl(label, rgba, false, layoutSpec);
|
||||
if (changed) {
|
||||
color[0] = rgba[0];
|
||||
color[1] = rgba[1];
|
||||
color[2] = rgba[2];
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
inline bool DrawUnityColor4(
|
||||
const char* label,
|
||||
float color[4],
|
||||
const PropertyLayoutSpec& layoutSpec = MakePropertyLayout()) {
|
||||
return detail::DrawUnityColorControl(label, color, true, layoutSpec);
|
||||
}
|
||||
|
||||
} // namespace UI
|
||||
} // namespace Editor
|
||||
} // namespace XCEngine
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "ColorPicker.h"
|
||||
#include "Core.h"
|
||||
#include "PropertyLayout.h"
|
||||
|
||||
@@ -26,8 +27,9 @@ inline void DrawComboPreviewFrame(
|
||||
const char* id,
|
||||
const char* previewValue,
|
||||
float width,
|
||||
float height,
|
||||
bool popupOpen) {
|
||||
const float frameHeight = ImGui::GetFrameHeight();
|
||||
const float frameHeight = (std::max)(height, 1.0f);
|
||||
ImGui::InvisibleButton(id, ImVec2(width, frameHeight));
|
||||
|
||||
const bool hovered = ImGui::IsItemHovered();
|
||||
@@ -86,28 +88,42 @@ inline void DrawComboPreviewFrame(
|
||||
textColor);
|
||||
}
|
||||
|
||||
inline void DrawControlFrameChrome(
|
||||
ImDrawList* drawList,
|
||||
const ImVec2& min,
|
||||
const ImVec2& max,
|
||||
bool hovered,
|
||||
bool active = false) {
|
||||
if (!drawList) {
|
||||
return;
|
||||
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 ImGuiStyle& style = ImGui::GetStyle();
|
||||
const ImU32 frameColor = ImGui::GetColorU32(
|
||||
active ? ImGuiCol_FrameBgActive : (hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg));
|
||||
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);
|
||||
drawList->AddRectFilled(min, max, frameColor, style.FrameRounding);
|
||||
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);
|
||||
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(
|
||||
@@ -119,9 +135,16 @@ inline bool DrawFloat(
|
||||
float max = 0.0f,
|
||||
const char* format = "%.2f"
|
||||
) {
|
||||
return DrawPropertyRow(label, layoutSpec, [&](const PropertyLayoutMetrics& layout) {
|
||||
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);
|
||||
return ImGui::DragFloat("##value", &value, dragSpeed, min, max, format);
|
||||
const bool changed = ImGui::DragFloat("##value", &value, dragSpeed, min, max, format);
|
||||
ImGui::PopStyleVar();
|
||||
return changed;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -137,8 +160,7 @@ inline bool DrawLinearSlider(
|
||||
const bool active = ImGui::IsItemActive();
|
||||
const ImVec2 min = ImGui::GetItemRectMin();
|
||||
const ImVec2 max = ImGui::GetItemRectMax();
|
||||
const ImGuiStyle& style = ImGui::GetStyle();
|
||||
const float trackPadding = (std::max)(LinearSliderHorizontalPadding(), style.FramePadding.x);
|
||||
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;
|
||||
@@ -146,7 +168,6 @@ inline bool DrawLinearSlider(
|
||||
const float trackHalfThickness = LinearSliderTrackThickness() * 0.5f;
|
||||
|
||||
ImDrawList* drawList = ImGui::GetWindowDrawList();
|
||||
DrawControlFrameChrome(drawList, min, max, hovered, active);
|
||||
drawList->AddRectFilled(
|
||||
ImVec2(trackMinX, centerY - trackHalfThickness),
|
||||
ImVec2(trackMaxX, centerY + trackHalfThickness),
|
||||
@@ -184,9 +205,16 @@ inline bool DrawInt(
|
||||
int min = 0,
|
||||
int max = 0
|
||||
) {
|
||||
return DrawPropertyRow(label, layoutSpec, [&](const PropertyLayoutMetrics& layout) {
|
||||
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);
|
||||
return ImGui::DragInt("##value", &value, static_cast<float>(step), min, max);
|
||||
const bool changed = ImGui::DragInt("##value", &value, static_cast<float>(step), min, max);
|
||||
ImGui::PopStyleVar();
|
||||
return changed;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -195,8 +223,9 @@ inline bool DrawBool(
|
||||
bool& value,
|
||||
const PropertyLayoutSpec& layoutSpec = MakePropertyLayout()
|
||||
) {
|
||||
return DrawPropertyRow(label, layoutSpec, [&](const PropertyLayoutMetrics&) {
|
||||
return ImGui::Checkbox("##value", &value);
|
||||
return DrawPropertyRow(label, layoutSpec, [&](const PropertyLayoutMetrics& layout) {
|
||||
AlignPropertyControlVertically(layout, CompactIndicatorSize());
|
||||
return DrawCompactCheckboxControl("##value", value);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -205,9 +234,7 @@ inline bool DrawColor3(
|
||||
float color[3],
|
||||
const PropertyLayoutSpec& layoutSpec = MakePropertyLayout()
|
||||
) {
|
||||
return DrawPropertyRow(label, layoutSpec, [&](const PropertyLayoutMetrics&) {
|
||||
return ImGui::ColorEdit3("##value", color, ImGuiColorEditFlags_NoInputs);
|
||||
});
|
||||
return DrawUnityColor3(label, color, layoutSpec);
|
||||
}
|
||||
|
||||
inline bool DrawColor4(
|
||||
@@ -215,9 +242,7 @@ inline bool DrawColor4(
|
||||
float color[4],
|
||||
const PropertyLayoutSpec& layoutSpec = MakePropertyLayout()
|
||||
) {
|
||||
return DrawPropertyRow(label, layoutSpec, [&](const PropertyLayoutMetrics&) {
|
||||
return ImGui::ColorEdit4("##value", color, ImGuiColorEditFlags_NoInputs);
|
||||
});
|
||||
return DrawUnityColor4(label, color, layoutSpec);
|
||||
}
|
||||
|
||||
inline bool DrawSliderFloat(
|
||||
@@ -228,13 +253,18 @@ inline bool DrawSliderFloat(
|
||||
const PropertyLayoutSpec& layoutSpec = MakePropertyLayout(),
|
||||
const char* format = "%.2f"
|
||||
) {
|
||||
return DrawPropertyRow(label, layoutSpec, [&](const PropertyLayoutMetrics& layout) {
|
||||
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);
|
||||
@@ -251,6 +281,7 @@ inline bool DrawSliderFloat(
|
||||
changed = ImGui::InputFloat("##value", &value, 0.0f, 0.0f, format) || changed;
|
||||
value = std::clamp(value, min, max);
|
||||
ImGui::PopStyleVar();
|
||||
ImGui::PopStyleVar();
|
||||
return changed;
|
||||
});
|
||||
}
|
||||
@@ -262,13 +293,18 @@ inline bool DrawSliderInt(
|
||||
int max,
|
||||
const PropertyLayoutSpec& layoutSpec = MakePropertyLayout()
|
||||
) {
|
||||
return DrawPropertyRow(label, layoutSpec, [&](const PropertyLayoutMetrics& layout) {
|
||||
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);
|
||||
@@ -285,6 +321,7 @@ inline bool DrawSliderInt(
|
||||
changed = ImGui::InputInt("##value", &value, 0, 0) || changed;
|
||||
value = std::clamp(value, min, max);
|
||||
ImGui::PopStyleVar();
|
||||
ImGui::PopStyleVar();
|
||||
return changed;
|
||||
});
|
||||
}
|
||||
@@ -306,7 +343,9 @@ inline int DrawCombo(
|
||||
: "";
|
||||
const float comboWidth = layout.controlWidth;
|
||||
const float popupWidth = comboWidth;
|
||||
DrawComboPreviewFrame("##value", previewValue, comboWidth, ImGui::IsPopupOpen(popupId));
|
||||
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();
|
||||
|
||||
Reference in New Issue
Block a user