feat: update editor ui framework and assets
This commit is contained in:
@@ -12,6 +12,7 @@ namespace UI {
|
||||
|
||||
struct ComponentSectionResult {
|
||||
bool open = false;
|
||||
float contentIndent = 0.0f;
|
||||
};
|
||||
|
||||
struct AssetTileResult {
|
||||
@@ -23,9 +24,12 @@ struct AssetTileResult {
|
||||
ImVec2 max = ImVec2(0.0f, 0.0f);
|
||||
};
|
||||
|
||||
enum class AssetIconKind {
|
||||
Folder,
|
||||
File
|
||||
struct AssetTileOptions {
|
||||
ImVec2 size = AssetTileSize();
|
||||
ImVec2 iconOffset = AssetTileIconOffset();
|
||||
ImVec2 iconSize = AssetTileIconSize();
|
||||
bool drawIdleFrame = true;
|
||||
bool drawSelectionBorder = true;
|
||||
};
|
||||
|
||||
enum class DialogActionResult {
|
||||
@@ -61,11 +65,16 @@ struct MenuCommand {
|
||||
|
||||
template <typename DrawContentFn>
|
||||
inline bool DrawMenuScope(const char* label, DrawContentFn&& drawContent) {
|
||||
PushPopupWindowChrome();
|
||||
if (!ImGui::BeginMenu(label)) {
|
||||
PopPopupWindowChrome();
|
||||
return false;
|
||||
}
|
||||
PopPopupWindowChrome();
|
||||
|
||||
PushPopupContentChrome();
|
||||
drawContent();
|
||||
PopPopupContentChrome();
|
||||
ImGui::EndMenu();
|
||||
return true;
|
||||
}
|
||||
@@ -142,39 +151,105 @@ inline void DrawEmptyState(
|
||||
}
|
||||
}
|
||||
|
||||
inline float BreadcrumbItemHeight() {
|
||||
return ImGui::GetTextLineHeight() + BreadcrumbSegmentPadding().y * 2.0f;
|
||||
}
|
||||
|
||||
inline void DrawBreadcrumbTextItem(
|
||||
const char* label,
|
||||
const ImVec2& size,
|
||||
const ImVec4& color,
|
||||
bool clickable,
|
||||
bool* pressed = nullptr,
|
||||
bool* hovered = nullptr) {
|
||||
bool localPressed = false;
|
||||
if (clickable) {
|
||||
localPressed = ImGui::InvisibleButton("##BreadcrumbItem", size);
|
||||
} else {
|
||||
ImGui::Dummy(size);
|
||||
}
|
||||
const bool localHovered = clickable && ImGui::IsItemHovered();
|
||||
|
||||
const ImVec2 itemMin = ImGui::GetItemRectMin();
|
||||
const ImVec2 itemMax = ImGui::GetItemRectMax();
|
||||
const ImVec2 textSize = ImGui::CalcTextSize(label);
|
||||
const float textX = itemMin.x + (size.x - textSize.x) * 0.5f;
|
||||
const float textY = itemMin.y + (itemMax.y - itemMin.y - textSize.y) * 0.5f;
|
||||
|
||||
ImGui::GetWindowDrawList()->AddText(
|
||||
ImVec2(textX, textY),
|
||||
ImGui::GetColorU32(color),
|
||||
label);
|
||||
|
||||
if (pressed) {
|
||||
*pressed = localPressed;
|
||||
}
|
||||
if (hovered) {
|
||||
*hovered = localHovered;
|
||||
}
|
||||
}
|
||||
|
||||
inline bool DrawBreadcrumbSegment(const char* label, bool clickable, bool current = false) {
|
||||
if (!label || label[0] == '\0') {
|
||||
return false;
|
||||
}
|
||||
|
||||
const ImVec2 padding = BreadcrumbSegmentPadding();
|
||||
const ImVec2 textSize = ImGui::CalcTextSize(label);
|
||||
const ImVec2 size(textSize.x + padding.x * 2.0f, BreadcrumbItemHeight());
|
||||
bool pressed = false;
|
||||
bool hovered = false;
|
||||
DrawBreadcrumbTextItem(label, size, BreadcrumbSegmentTextColor(current, hovered), clickable, &pressed, &hovered);
|
||||
|
||||
if (hovered) {
|
||||
const ImVec2 itemMin = ImGui::GetItemRectMin();
|
||||
const ImVec2 itemMax = ImGui::GetItemRectMax();
|
||||
const ImVec2 textOnlySize = ImGui::CalcTextSize(label);
|
||||
const float textX = itemMin.x + (size.x - textOnlySize.x) * 0.5f;
|
||||
const float textY = itemMin.y + (itemMax.y - itemMin.y - textOnlySize.y) * 0.5f;
|
||||
ImGui::GetWindowDrawList()->AddText(
|
||||
ImVec2(textX, textY),
|
||||
ImGui::GetColorU32(BreadcrumbSegmentTextColor(current, true)),
|
||||
label);
|
||||
}
|
||||
|
||||
return pressed;
|
||||
}
|
||||
|
||||
inline void DrawBreadcrumbSeparator(const char* label = ">") {
|
||||
const ImVec2 textSize = ImGui::CalcTextSize(label);
|
||||
DrawBreadcrumbTextItem(label, ImVec2(textSize.x, BreadcrumbItemHeight()), BreadcrumbSeparatorColor(), false);
|
||||
}
|
||||
|
||||
template <typename GetNameFn, typename NavigateFn>
|
||||
inline void DrawToolbarBreadcrumbs(
|
||||
const char* rootLabel,
|
||||
size_t segmentCount,
|
||||
GetNameFn&& getName,
|
||||
NavigateFn&& navigateToSegment) {
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.0f, 0.0f, 0.0f, 0.0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.0f, 0.0f, 0.0f, 0.0f));
|
||||
|
||||
if (segmentCount == 0) {
|
||||
ImGui::TextUnformatted(rootLabel);
|
||||
ImGui::PopStyleColor(2);
|
||||
DrawBreadcrumbSegment(rootLabel, false, true);
|
||||
return;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < segmentCount; ++i) {
|
||||
if (i > 0) {
|
||||
ImGui::SameLine();
|
||||
ImGui::TextDisabled("/");
|
||||
ImGui::SameLine();
|
||||
ImGui::SameLine(0.0f, BreadcrumbSegmentSpacing());
|
||||
DrawBreadcrumbSeparator();
|
||||
ImGui::SameLine(0.0f, BreadcrumbSegmentSpacing());
|
||||
}
|
||||
|
||||
const std::string label = getName(i);
|
||||
if (i + 1 < segmentCount) {
|
||||
if (ImGui::Button(label.c_str())) {
|
||||
navigateToSegment(i);
|
||||
}
|
||||
} else {
|
||||
ImGui::Text("%s", label.c_str());
|
||||
const std::string label = (i == 0 && rootLabel && rootLabel[0] != '\0')
|
||||
? std::string(rootLabel)
|
||||
: getName(i);
|
||||
const bool current = (i + 1 == segmentCount);
|
||||
|
||||
ImGui::PushID(static_cast<int>(i));
|
||||
if (DrawBreadcrumbSegment(label.c_str(), !current, current)) {
|
||||
navigateToSegment(i);
|
||||
}
|
||||
ImGui::PopID();
|
||||
}
|
||||
|
||||
ImGui::PopStyleColor(2);
|
||||
}
|
||||
|
||||
template <typename DrawIconFn>
|
||||
@@ -182,8 +257,18 @@ inline AssetTileResult DrawAssetTile(
|
||||
const char* label,
|
||||
bool selected,
|
||||
bool dimmed,
|
||||
DrawIconFn&& drawIcon) {
|
||||
const ImVec2 tileSize = AssetTileSize();
|
||||
DrawIconFn&& drawIcon,
|
||||
const AssetTileOptions& options = AssetTileOptions()) {
|
||||
const ImVec2 textSize = ImGui::CalcTextSize(label);
|
||||
ImVec2 tileSize = options.size;
|
||||
tileSize.x = std::max(tileSize.x, options.iconSize.x + AssetTileTextPadding().x * 2.0f);
|
||||
tileSize.y = std::max(
|
||||
tileSize.y,
|
||||
options.iconOffset.y +
|
||||
options.iconSize.y +
|
||||
AssetTileIconTextGap() +
|
||||
textSize.y +
|
||||
AssetTileTextPadding().y);
|
||||
ImGui::InvisibleButton("##AssetBtn", tileSize);
|
||||
|
||||
const bool clicked = ImGui::IsItemClicked(ImGuiMouseButton_Left);
|
||||
@@ -195,58 +280,38 @@ inline AssetTileResult DrawAssetTile(
|
||||
const ImVec2 max = ImVec2(min.x + tileSize.x, min.y + tileSize.y);
|
||||
ImDrawList* drawList = ImGui::GetWindowDrawList();
|
||||
|
||||
drawList->AddRectFilled(min, max, ImGui::GetColorU32(AssetTileIdleFillColor()), AssetTileRounding());
|
||||
drawList->AddRect(min, max, ImGui::GetColorU32(AssetTileIdleBorderColor()), AssetTileRounding());
|
||||
if (options.drawIdleFrame) {
|
||||
drawList->AddRectFilled(min, max, ImGui::GetColorU32(AssetTileIdleFillColor()), AssetTileRounding());
|
||||
drawList->AddRect(min, max, ImGui::GetColorU32(AssetTileIdleBorderColor()), AssetTileRounding());
|
||||
}
|
||||
|
||||
if (hovered || selected) {
|
||||
drawList->AddRectFilled(min, max, ImGui::GetColorU32(selected ? AssetTileSelectedFillColor() : AssetTileHoverFillColor()), AssetTileRounding());
|
||||
}
|
||||
if (selected) {
|
||||
if (selected && options.drawSelectionBorder) {
|
||||
drawList->AddRect(min, max, ImGui::GetColorU32(AssetTileSelectedBorderColor()), AssetTileRounding());
|
||||
}
|
||||
if (dimmed) {
|
||||
drawList->AddRectFilled(min, max, ImGui::GetColorU32(AssetTileDraggedOverlayColor()), 0.0f);
|
||||
}
|
||||
|
||||
const ImVec2 iconOffset = AssetTileIconOffset();
|
||||
const ImVec2 iconSize = AssetTileIconSize();
|
||||
const ImVec2 iconMin(min.x + iconOffset.x, min.y + iconOffset.y);
|
||||
const ImVec2 iconMax(iconMin.x + iconSize.x, iconMin.y + iconSize.y);
|
||||
const ImVec2 iconMin(
|
||||
min.x + (tileSize.x - options.iconSize.x) * 0.5f,
|
||||
min.y + options.iconOffset.y);
|
||||
const ImVec2 iconMax(iconMin.x + options.iconSize.x, iconMin.y + options.iconSize.y);
|
||||
drawIcon(drawList, iconMin, iconMax);
|
||||
|
||||
const ImVec2 textSize = ImGui::CalcTextSize(label);
|
||||
const ImVec2 textPadding = AssetTileTextPadding();
|
||||
const float textAreaWidth = tileSize.x - textPadding.x * 2.0f;
|
||||
const float centeredTextX = min.x + textPadding.x + std::max(0.0f, (textAreaWidth - textSize.x) * 0.5f);
|
||||
const float textY = max.y - textSize.y - AssetTileTextPadding().y;
|
||||
ImGui::PushClipRect(ImVec2(min.x + AssetTileTextPadding().x, min.y), ImVec2(max.x - AssetTileTextPadding().x, max.y), true);
|
||||
drawList->AddText(ImVec2(min.x + AssetTileTextPadding().x, textY), ImGui::GetColorU32(AssetTileTextColor(selected)), label);
|
||||
ImGui::PushClipRect(ImVec2(min.x + textPadding.x, min.y), ImVec2(max.x - textPadding.x, max.y), true);
|
||||
drawList->AddText(ImVec2(centeredTextX, textY), ImGui::GetColorU32(AssetTileTextColor(selected)), label);
|
||||
ImGui::PopClipRect();
|
||||
|
||||
return AssetTileResult{ clicked, contextRequested, openRequested, hovered, min, max };
|
||||
}
|
||||
|
||||
inline void DrawAssetIcon(ImDrawList* drawList, const ImVec2& min, const ImVec2& max, AssetIconKind kind) {
|
||||
if (kind == AssetIconKind::Folder) {
|
||||
const ImU32 fillColor = ImGui::GetColorU32(AssetFolderIconFillColor());
|
||||
const ImU32 lineColor = ImGui::GetColorU32(AssetFolderIconLineColor());
|
||||
const float width = max.x - min.x;
|
||||
const float height = max.y - min.y;
|
||||
const ImVec2 tabMax(min.x + width * 0.45f, min.y + height * 0.35f);
|
||||
drawList->AddRectFilled(ImVec2(min.x, min.y + height * 0.14f), tabMax, fillColor, 2.0f);
|
||||
drawList->AddRectFilled(ImVec2(min.x, min.y + height * 0.28f), max, fillColor, 2.0f);
|
||||
drawList->AddRect(ImVec2(min.x, min.y + height * 0.14f), tabMax, lineColor, 2.0f);
|
||||
drawList->AddRect(ImVec2(min.x, min.y + height * 0.28f), max, lineColor, 2.0f);
|
||||
return;
|
||||
}
|
||||
|
||||
const ImU32 fillColor = ImGui::GetColorU32(AssetFileIconFillColor());
|
||||
const ImU32 lineColor = ImGui::GetColorU32(AssetFileIconLineColor());
|
||||
const ImVec2 foldA(max.x - 8.0f, min.y);
|
||||
const ImVec2 foldB(max.x, min.y + 8.0f);
|
||||
drawList->AddRectFilled(min, max, fillColor, 2.0f);
|
||||
drawList->AddRect(min, max, lineColor, 2.0f);
|
||||
drawList->AddTriangleFilled(foldA, ImVec2(max.x, min.y), foldB, ImGui::GetColorU32(AssetFileFoldColor()));
|
||||
drawList->AddLine(foldA, foldB, lineColor);
|
||||
}
|
||||
|
||||
template <typename DrawMenuFn>
|
||||
inline ComponentSectionResult BeginComponentSection(
|
||||
const void* id,
|
||||
@@ -254,27 +319,68 @@ inline ComponentSectionResult BeginComponentSection(
|
||||
DrawMenuFn&& drawMenu,
|
||||
bool defaultOpen = true) {
|
||||
const ImGuiStyle& style = ImGui::GetStyle();
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, InspectorSectionFramePadding());
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(style.ItemSpacing.x, InspectorSectionItemSpacing().y));
|
||||
const ImVec2 framePadding = InspectorSectionFramePadding();
|
||||
const float availableWidth = ImMax(ImGui::GetContentRegionAvail().x, 1.0f);
|
||||
const float arrowSlotWidth = ImGui::GetTreeNodeToLabelSpacing();
|
||||
const ImVec2 labelSize = ImGui::CalcTextSize(label ? label : "", nullptr, false);
|
||||
const float rowHeight = ImMax(labelSize.y, ImGui::GetFontSize()) + framePadding.y * 2.0f;
|
||||
|
||||
ImGuiTreeNodeFlags flags =
|
||||
ImGuiTreeNodeFlags_Framed |
|
||||
ImGuiTreeNodeFlags_SpanAvailWidth |
|
||||
ImGuiTreeNodeFlags_FramePadding |
|
||||
ImGuiTreeNodeFlags_AllowOverlap;
|
||||
if (defaultOpen) {
|
||||
flags |= ImGuiTreeNodeFlags_DefaultOpen;
|
||||
ImGui::PushID(id);
|
||||
const ImGuiID openStateId = ImGui::GetID("##ComponentSectionOpen");
|
||||
ImGuiStorage* storage = ImGui::GetStateStorage();
|
||||
bool open = storage->GetBool(openStateId, defaultOpen);
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(style.ItemSpacing.x, InspectorSectionItemSpacing().y));
|
||||
ImGui::InvisibleButton("##ComponentSectionHeader", ImVec2(availableWidth, rowHeight));
|
||||
ImGui::PopStyleVar();
|
||||
|
||||
const bool hovered = ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem);
|
||||
const bool held = ImGui::IsItemActive();
|
||||
if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) {
|
||||
open = !open;
|
||||
storage->SetBool(openStateId, open);
|
||||
}
|
||||
|
||||
const bool open = ImGui::TreeNodeEx(id, flags, "%s", label);
|
||||
ImGui::PopStyleVar(2);
|
||||
const ImVec2 itemMin = ImGui::GetItemRectMin();
|
||||
const ImVec2 itemMax = ImGui::GetItemRectMax();
|
||||
const ImRect frameRect(itemMin, itemMax);
|
||||
const ImRect arrowRect(
|
||||
ImVec2(itemMin.x + framePadding.x, itemMin.y),
|
||||
ImVec2(itemMin.x + arrowSlotWidth, itemMax.y));
|
||||
|
||||
ImDrawList* drawList = ImGui::GetWindowDrawList();
|
||||
const ImU32 bgColor = ImGui::GetColorU32(
|
||||
(held && hovered) ? ImGuiCol_HeaderActive :
|
||||
hovered ? ImGuiCol_HeaderHovered :
|
||||
ImGuiCol_Header);
|
||||
drawList->AddRectFilled(frameRect.Min, frameRect.Max, bgColor, style.FrameRounding);
|
||||
if (style.FrameBorderSize > 0.0f) {
|
||||
drawList->AddRect(
|
||||
frameRect.Min,
|
||||
frameRect.Max,
|
||||
ImGui::GetColorU32(ImGuiCol_Border),
|
||||
style.FrameRounding,
|
||||
0,
|
||||
style.FrameBorderSize);
|
||||
}
|
||||
|
||||
DrawDisclosureArrow(drawList, arrowRect.Min, arrowRect.Max, open, ImGui::GetColorU32(ImGuiCol_Text));
|
||||
|
||||
if (label && label[0] != '\0') {
|
||||
const float textX = itemMin.x + arrowSlotWidth;
|
||||
const float textY = itemMin.y + ((itemMax.y - itemMin.y) - labelSize.y) * 0.5f;
|
||||
drawList->PushClipRect(ImVec2(textX, itemMin.y), itemMax, true);
|
||||
drawList->AddText(ImVec2(textX, textY), ImGui::GetColorU32(ImGuiCol_Text), label);
|
||||
drawList->PopClipRect();
|
||||
}
|
||||
|
||||
if (BeginPopupContextItem("##ComponentSettings")) {
|
||||
drawMenu();
|
||||
EndPopup();
|
||||
}
|
||||
|
||||
return ComponentSectionResult{ open };
|
||||
ImGui::PopID();
|
||||
return ComponentSectionResult{ open, InspectorSectionContentIndent() };
|
||||
}
|
||||
|
||||
inline ComponentSectionResult BeginComponentSection(
|
||||
@@ -284,8 +390,10 @@ inline ComponentSectionResult BeginComponentSection(
|
||||
return BeginComponentSection(id, label, []() {}, defaultOpen);
|
||||
}
|
||||
|
||||
inline void EndComponentSection() {
|
||||
ImGui::TreePop();
|
||||
inline void EndComponentSection(const ComponentSectionResult& section) {
|
||||
if (section.open && section.contentIndent > 0.0f) {
|
||||
ImGui::Unindent(section.contentIndent);
|
||||
}
|
||||
}
|
||||
|
||||
inline bool InspectorActionButton(const char* label, ImVec2 size = ImVec2(-1.0f, 0.0f)) {
|
||||
|
||||
Reference in New Issue
Block a user