Files
XCEngine/editor/src/UI/PropertyLayout.h
2026-03-29 01:36:53 +08:00

150 lines
4.7 KiB
C++

#pragma once
#include "StyleTokens.h"
#include <algorithm>
#include <imgui.h>
#include <utility>
namespace XCEngine {
namespace Editor {
namespace UI {
struct PropertyLayoutSpec {
float labelInset = InspectorPropertyLabelInset();
float controlColumnStart = InspectorPropertyControlColumnStart();
float labelControlGap = InspectorPropertyLabelControlGap();
float controlTrailingInset = InspectorPropertyControlTrailingInset();
float minimumRowHeight = 0.0f;
};
struct PropertyLayoutMetrics {
ImVec2 cursorPos = ImVec2(0.0f, 0.0f);
ImVec2 screenPos = ImVec2(0.0f, 0.0f);
float rowWidth = 0.0f;
float rowHeight = 0.0f;
float labelX = 0.0f;
float labelWidth = 0.0f;
float controlX = 0.0f;
float controlWidth = 0.0f;
};
inline PropertyLayoutSpec MakePropertyLayout() {
return PropertyLayoutSpec{};
}
inline float CalcPropertyRowHeightForFramePadding(const ImVec2& framePadding) {
return ImGui::GetFontSize() + framePadding.y * 2.0f + ControlRowHeightOffset();
}
inline PropertyLayoutSpec WithMinimumRowHeight(PropertyLayoutSpec spec, float minimumRowHeight) {
spec.minimumRowHeight = std::max(spec.minimumRowHeight, minimumRowHeight);
return spec;
}
inline void PushPropertyLayoutStyles() {
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ControlFramePadding());
}
inline float GetPropertyControlWidth(
const PropertyLayoutMetrics& layout,
float trailingInset = 0.0f) {
return std::max(layout.controlWidth - trailingInset, 1.0f);
}
inline void SetNextPropertyControlWidth(
const PropertyLayoutMetrics& layout,
float trailingInset = 0.0f) {
ImGui::SetNextItemWidth(GetPropertyControlWidth(layout, trailingInset));
}
inline void AlignPropertyControlToRight(
const PropertyLayoutMetrics& layout,
float width,
float trailingInset = 0.0f) {
const float offset = layout.controlWidth - width - trailingInset;
if (offset > 0.0f) {
ImGui::SetCursorPosX(layout.controlX + offset);
}
}
inline void AlignPropertyControlVertically(
const PropertyLayoutMetrics& layout,
float height) {
const float offset = std::max((layout.rowHeight - height) * 0.5f, 0.0f);
if (offset > 0.0f) {
ImGui::SetCursorPosY(layout.cursorPos.y + offset);
}
}
template <typename DrawControlFn>
inline auto DrawPropertyRow(
const char* label,
const PropertyLayoutSpec& spec,
DrawControlFn&& drawControl) -> decltype(drawControl(std::declval<const PropertyLayoutMetrics&>())) {
using Result = decltype(drawControl(std::declval<const PropertyLayoutMetrics&>()));
Result result{};
ImGui::PushID(label);
PushPropertyLayoutStyles();
const ImVec2 rowCursorPos = ImGui::GetCursorPos();
const ImVec2 rowScreenPos = ImGui::GetCursorScreenPos();
const float rowWidth = std::max(ImGui::GetContentRegionAvail().x, 1.0f);
const float rowHeight = std::max(
ImGui::GetFrameHeight() + ControlRowHeightOffset(),
spec.minimumRowHeight);
const float labelInset = std::max(spec.labelInset, 0.0f);
const float controlColumnStart = std::clamp(
std::max(spec.controlColumnStart, 0.0f),
labelInset,
rowWidth);
const float labelWidth = std::max(
controlColumnStart - labelInset - std::max(spec.labelControlGap, 0.0f),
0.0f);
const float controlX = rowCursorPos.x + controlColumnStart;
const float controlRight = rowCursorPos.x + rowWidth - std::max(spec.controlTrailingInset, 0.0f);
const float controlWidth = std::max(controlRight - controlX, 1.0f);
const float labelX = rowCursorPos.x + labelInset;
const PropertyLayoutMetrics metrics{
rowCursorPos,
rowScreenPos,
rowWidth,
rowHeight,
labelX,
labelWidth,
controlX,
controlWidth
};
if (label && label[0] != '\0') {
ImDrawList* drawList = ImGui::GetWindowDrawList();
const float textY = rowScreenPos.y + std::max(0.0f, (rowHeight - ImGui::GetTextLineHeight()) * 0.5f);
drawList->PushClipRect(
ImVec2(rowScreenPos.x + labelInset, rowScreenPos.y),
ImVec2(rowScreenPos.x + labelInset + labelWidth, rowScreenPos.y + rowHeight),
true);
drawList->AddText(
ImVec2(rowScreenPos.x + labelInset, textY),
ImGui::GetColorU32(ImGuiCol_Text),
label);
drawList->PopClipRect();
}
ImGui::SetCursorPos(ImVec2(controlX, rowCursorPos.y));
result = drawControl(metrics);
const float consumedHeight = std::max(rowHeight, ImGui::GetCursorPosY() - rowCursorPos.y);
ImGui::SetCursorPos(rowCursorPos);
ImGui::Dummy(ImVec2(rowWidth, consumedHeight));
ImGui::PopStyleVar();
ImGui::PopID();
return result;
}
} // namespace UI
} // namespace Editor
} // namespace XCEngine