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

628 lines
21 KiB
C
Raw Normal View History

2026-03-26 21:18:33 +08:00
#pragma once
2026-03-27 00:15:38 +08:00
#include "Core.h"
#include "MenuCommand.h"
2026-03-26 21:18:33 +08:00
#include "StyleTokens.h"
#include <XCEngine/Debug/Logger.h>
2026-03-26 21:18:33 +08:00
#include <imgui.h>
#include <string>
namespace XCEngine {
namespace Editor {
namespace UI {
inline void TracePopupSubmenuIfNeeded(const char* label, const std::string& message) {
if (!label || std::string(label) != "Create") {
return;
}
XCEngine::Debug::Logger::Get().Info(
XCEngine::Debug::LogCategory::General,
XCEngine::Containers::String(message.c_str()));
}
2026-03-26 21:18:33 +08:00
struct ComponentSectionResult {
bool open = false;
float contentIndent = 0.0f;
2026-03-26 21:18:33 +08:00
};
struct AssetTileResult {
bool clicked = false;
bool openRequested = false;
bool hovered = false;
ImVec2 min = ImVec2(0.0f, 0.0f);
ImVec2 max = ImVec2(0.0f, 0.0f);
2026-03-29 01:36:53 +08:00
ImVec2 labelMin = ImVec2(0.0f, 0.0f);
ImVec2 labelMax = ImVec2(0.0f, 0.0f);
2026-03-26 21:18:33 +08:00
};
struct AssetTileOptions {
ImVec2 size = AssetTileSize();
ImVec2 iconOffset = AssetTileIconOffset();
ImVec2 iconSize = AssetTileIconSize();
bool drawIdleFrame = true;
bool drawSelectionBorder = true;
2026-03-29 01:36:53 +08:00
bool drawLabel = true;
2026-03-26 21:18:33 +08:00
};
enum class DialogActionResult {
None,
Primary,
Secondary
};
struct InlineRenameFieldResult {
bool submitted = false;
bool cancelRequested = false;
bool deactivated = false;
bool active = false;
2026-03-26 21:18:33 +08:00
};
template <typename DrawContentFn>
inline bool DrawMenuScope(const char* label, DrawContentFn&& drawContent) {
PushPopupWindowChrome();
2026-03-26 21:18:33 +08:00
if (!ImGui::BeginMenu(label)) {
PopPopupWindowChrome();
2026-03-26 21:18:33 +08:00
return false;
}
PopPopupWindowChrome();
2026-03-26 21:18:33 +08:00
PushPopupContentChrome();
2026-03-26 21:18:33 +08:00
drawContent();
PopPopupContentChrome();
2026-03-26 21:18:33 +08:00
ImGui::EndMenu();
return true;
}
2026-03-29 01:36:53 +08:00
template <typename DrawContentFn>
inline bool DrawPopupSubmenuScope(const char* label, DrawContentFn&& drawContent) {
if (!label || label[0] == '\0') {
return false;
}
ImGui::PushID(label);
const char* popupId = "##PopupSubmenu";
const ImVec2 labelSize = ImGui::CalcTextSize(label);
const ImVec2 rowPos = ImGui::GetCursorScreenPos();
const float rowHeight = labelSize.y;
const float rowWidth = ImMax(ImGui::GetContentRegionAvail().x, 1.0f);
const bool popupOpen = ImGui::IsPopupOpen(popupId);
if (ImGui::Selectable(
"##PopupSubmenuRow",
popupOpen,
ImGuiSelectableFlags_NoAutoClosePopups,
ImVec2(rowWidth, rowHeight))) {
TracePopupSubmenuIfNeeded(label, "Hierarchy create submenu selectable clicked -> OpenPopup");
2026-03-29 01:36:53 +08:00
ImGui::OpenPopup(popupId);
}
const bool hovered = ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup);
if (hovered && !popupOpen) {
TracePopupSubmenuIfNeeded(label, "Hierarchy create submenu hovered -> OpenPopup");
2026-03-29 01:36:53 +08:00
ImGui::OpenPopup(popupId);
}
const ImVec2 itemMin = ImGui::GetItemRectMin();
const ImVec2 itemMax = ImGui::GetItemRectMax();
const ImVec2 parentWindowPos = ImGui::GetWindowPos();
const ImVec2 parentWindowSize = ImGui::GetWindowSize();
const float parentWindowRight = parentWindowPos.x + parentWindowSize.x;
const float itemHeight = itemMax.y - itemMin.y;
ImDrawList* drawList = ImGui::GetWindowDrawList();
drawList->AddText(
ImVec2(itemMin.x + ImGui::GetStyle().FramePadding.x, itemMin.y + (itemHeight - labelSize.y) * 0.5f),
ImGui::GetColorU32(ImGuiCol_Text),
label);
const float arrowExtent = PopupSubmenuArrowExtent();
const float arrowCenterX = itemMax.x - PopupSubmenuArrowTrailingInset() - arrowExtent * 0.5f;
const float arrowCenterY = (itemMin.y + itemMax.y) * 0.5f;
drawList->AddTriangleFilled(
ImVec2(arrowCenterX - arrowExtent * 0.30f, arrowCenterY - arrowExtent * 0.50f),
ImVec2(arrowCenterX - arrowExtent * 0.30f, arrowCenterY + arrowExtent * 0.50f),
ImVec2(arrowCenterX + arrowExtent * 0.50f, arrowCenterY),
ImGui::GetColorU32(ImGuiCol_Text));
ImGui::SetNextWindowPos(
ImVec2(parentWindowRight + PopupSubmenuOpenOffsetX(), rowPos.y - PopupWindowPadding().y),
ImGuiCond_Always);
const bool open = BeginPopup(
popupId,
ImGuiWindowFlags_NoMove |
ImGuiWindowFlags_NoResize |
ImGuiWindowFlags_NoSavedSettings);
if (std::string(label) == "Create") {
static bool s_lastCreateOpen = false;
if (open != s_lastCreateOpen) {
TracePopupSubmenuIfNeeded(
label,
std::string("Hierarchy create submenu popup ") + (open ? "opened" : "closed"));
s_lastCreateOpen = open;
}
}
2026-03-29 01:36:53 +08:00
if (!open) {
ImGui::PopID();
return false;
}
drawContent();
const bool popupHovered = ImGui::IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup);
if (!hovered && !popupHovered && !ImGui::IsWindowAppearing()) {
TracePopupSubmenuIfNeeded(
label,
std::string("Hierarchy create submenu auto-close: rowHovered=") +
(hovered ? "1" : "0") +
", popupHovered=" +
(popupHovered ? "1" : "0"));
2026-03-29 01:36:53 +08:00
ImGui::CloseCurrentPopup();
}
EndPopup();
ImGui::PopID();
return true;
}
2026-03-26 21:18:33 +08:00
template <typename ExecuteFn>
inline bool DrawMenuCommand(const MenuCommand& command, ExecuteFn&& execute) {
if (command.kind == MenuCommandKind::Separator) {
ImGui::Separator();
return false;
}
if (!ImGui::MenuItem(command.label, command.shortcut, command.selected, command.enabled)) {
return false;
}
execute();
return true;
}
template <size_t N, typename ExecuteFn>
inline void DrawMenuCommands(const MenuCommand (&commands)[N], ExecuteFn&& execute) {
for (size_t i = 0; i < N; ++i) {
DrawMenuCommand(commands[i], [&]() { execute(i); });
}
}
inline bool ToolbarSearchField(
const char* id,
const char* hint,
char* buffer,
size_t bufferSize,
float trailingWidth = 0.0f) {
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, SearchFieldFramePadding());
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, SearchFieldFrameRounding());
const float width = ImGui::GetContentRegionAvail().x - trailingWidth;
ImGui::SetNextItemWidth(width > 0.0f ? width : 0.0f);
const bool changed = ImGui::InputTextWithHint(id, hint, buffer, bufferSize);
ImGui::PopStyleVar(2);
return changed;
}
inline InlineRenameFieldResult DrawInlineRenameField(
const char* id,
char* buffer,
size_t bufferSize,
float width = -1.0f,
bool requestFocus = false,
ImGuiInputTextFlags flags = ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_AutoSelectAll) {
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, InlineRenameFieldFramePadding());
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, InlineRenameFieldRounding());
2026-03-30 00:49:10 +08:00
ImGui::PushStyleColor(ImGuiCol_FrameBg, InlineRenameFieldBackgroundColor());
ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, InlineRenameFieldHoveredColor());
ImGui::PushStyleColor(ImGuiCol_FrameBgActive, InlineRenameFieldActiveColor());
ImGui::SetNextItemWidth(width);
if (requestFocus) {
ImGui::SetKeyboardFocusHere();
}
const bool submitted = ImGui::InputText(id, buffer, bufferSize, flags);
const bool active = ImGui::IsItemActive();
const bool deactivated = ImGui::IsItemDeactivated();
const bool cancelRequested = active && ImGui::IsKeyPressed(ImGuiKey_Escape);
2026-03-30 00:49:10 +08:00
ImGui::PopStyleColor(3);
ImGui::PopStyleVar(2);
return InlineRenameFieldResult{ submitted, cancelRequested, deactivated, active };
}
inline InlineRenameFieldResult DrawInlineRenameFieldAt(
const char* id,
const ImVec2& screenPos,
char* buffer,
size_t bufferSize,
float width,
bool requestFocus = false,
ImGuiInputTextFlags flags = ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_AutoSelectAll) {
const ImVec2 restoreCursor = ImGui::GetCursorPos();
ImGui::SetCursorScreenPos(screenPos);
const InlineRenameFieldResult result = DrawInlineRenameField(
id,
buffer,
bufferSize,
width,
requestFocus,
flags);
ImGui::SetCursorPos(restoreCursor);
return result;
}
2026-03-26 21:18:33 +08:00
inline void DrawToolbarLabel(const char* text) {
ImGui::AlignTextToFramePadding();
ImGui::TextColored(HintTextColor(), "%s", text);
}
inline bool ToolbarToggleButton(const char* label, bool& active, ImVec2 size = ImVec2(0.0f, 0.0f)) {
if (!ToolbarButton(label, active, size)) {
return false;
}
active = !active;
return true;
}
inline void DrawToolbarRowGap() {
ImGui::Dummy(ImVec2(0.0f, ToolbarRowGap()));
}
inline void DrawHintText(const char* text) {
ImGui::TextColored(HintTextColor(), "%s", text);
}
inline void DrawEmptyState(
const char* title,
const char* subtitle = nullptr,
ImVec2 start = ImVec2(10.0f, 10.0f)) {
ImGui::SetCursorPos(start);
ImGui::TextUnformatted(title);
if (subtitle && subtitle[0] != '\0') {
ImGui::SetCursorPos(ImVec2(start.x, start.y + EmptyStateLineOffset()));
ImGui::TextColored(EmptyStateSubtitleColor(), "%s", subtitle);
}
}
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());
ImGui::InvisibleButton("##BreadcrumbItem", size);
const bool hovered = clickable && ImGui::IsItemHovered();
const bool pressed = clickable && ImGui::IsItemClicked(ImGuiMouseButton_Left);
const ImVec2 itemMin = ImGui::GetItemRectMin();
const ImVec2 itemMax = ImGui::GetItemRectMax();
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(BreadcrumbSegmentTextColor(current, hovered)),
label);
if (hovered) {
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);
}
2026-03-26 21:18:33 +08:00
template <typename GetNameFn, typename NavigateFn>
inline void DrawToolbarBreadcrumbs(
const char* rootLabel,
size_t segmentCount,
GetNameFn&& getName,
NavigateFn&& navigateToSegment) {
const float lineY = ImGui::GetCursorPosY();
2026-03-26 21:18:33 +08:00
if (segmentCount == 0) {
ImGui::SetCursorPosY(lineY);
DrawBreadcrumbSegment(rootLabel, false, true);
2026-03-26 21:18:33 +08:00
return;
}
for (size_t i = 0; i < segmentCount; ++i) {
if (i > 0) {
ImGui::SameLine(0.0f, BreadcrumbSegmentSpacing());
ImGui::SetCursorPosY(lineY);
DrawBreadcrumbSeparator();
ImGui::SameLine(0.0f, BreadcrumbSegmentSpacing());
ImGui::SetCursorPosY(lineY);
2026-03-26 21:18:33 +08:00
}
const std::string label = (i == 0 && rootLabel && rootLabel[0] != '\0')
? std::string(rootLabel)
: getName(i);
const bool current = (i + 1 == segmentCount);
ImGui::SetCursorPosY(lineY);
ImGui::PushID(static_cast<int>(i));
if (DrawBreadcrumbSegment(label.c_str(), !current, current)) {
navigateToSegment(i);
2026-03-26 21:18:33 +08:00
}
ImGui::PopID();
2026-03-26 21:18:33 +08:00
}
}
template <typename DrawIconFn>
inline AssetTileResult DrawAssetTile(
const char* label,
bool selected,
bool dimmed,
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);
2026-03-26 21:18:33 +08:00
ImGui::InvisibleButton("##AssetBtn", tileSize);
const bool clicked = ImGui::IsItemClicked(ImGuiMouseButton_Left);
const bool openRequested = ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(0);
const bool hovered = ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem);
const ImVec2 min = ImGui::GetItemRectMin();
const ImVec2 max = ImVec2(min.x + tileSize.x, min.y + tileSize.y);
ImDrawList* drawList = ImGui::GetWindowDrawList();
if (options.drawIdleFrame) {
drawList->AddRectFilled(min, max, ImGui::GetColorU32(AssetTileIdleFillColor()), AssetTileRounding());
drawList->AddRect(min, max, ImGui::GetColorU32(AssetTileIdleBorderColor()), AssetTileRounding());
}
2026-03-29 01:36:53 +08:00
if (selected) {
drawList->AddRectFilled(min, max, ImGui::GetColorU32(AssetTileSelectedFillColor()), AssetTileRounding());
2026-03-26 21:18:33 +08:00
}
if (selected && options.drawSelectionBorder) {
2026-03-26 21:18:33 +08:00
drawList->AddRect(min, max, ImGui::GetColorU32(AssetTileSelectedBorderColor()), AssetTileRounding());
}
if (dimmed) {
drawList->AddRectFilled(min, max, ImGui::GetColorU32(AssetTileDraggedOverlayColor()), 0.0f);
}
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);
2026-03-26 21:18:33 +08:00
drawIcon(drawList, iconMin, iconMax);
const ImVec2 textPadding = AssetTileTextPadding();
2026-03-29 01:36:53 +08:00
const float labelHeight = ImGui::GetFrameHeight();
const ImVec2 labelMin(min.x + textPadding.x, max.y - labelHeight - textPadding.y * 0.5f);
const ImVec2 labelMax(max.x - textPadding.x, labelMin.y + labelHeight);
if (options.drawLabel) {
const float textAreaWidth = labelMax.x - labelMin.x;
const float centeredTextX = labelMin.x + std::max(0.0f, (textAreaWidth - textSize.x) * 0.5f);
const float textY = labelMin.y + (labelHeight - textSize.y) * 0.5f;
ImGui::PushClipRect(labelMin, labelMax, true);
drawList->AddText(ImVec2(centeredTextX, textY), ImGui::GetColorU32(AssetTileTextColor(selected)), label);
ImGui::PopClipRect();
}
return AssetTileResult{ clicked, openRequested, hovered, min, max, labelMin, labelMax };
2026-03-26 21:18:33 +08:00
}
template <typename DrawMenuFn>
2026-03-26 21:18:33 +08:00
inline ComponentSectionResult BeginComponentSection(
const void* id,
const char* label,
DrawMenuFn&& drawMenu,
2026-03-26 21:18:33 +08:00
bool defaultOpen = true) {
(void)drawMenu;
2026-03-26 21:18:33 +08:00
const ImGuiStyle& style = ImGui::GetStyle();
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;
ImGui::PushID(id);
const ImGuiID openStateId = ImGui::GetID("##ComponentSectionOpen");
ImGuiStorage* storage = ImGui::GetStateStorage();
bool open = storage->GetBool(openStateId, defaultOpen);
2026-03-26 21:18:33 +08:00
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(style.ItemSpacing.x, InspectorSectionItemSpacing().y));
ImGui::InvisibleButton("##ComponentSectionHeader", ImVec2(availableWidth, rowHeight));
ImGui::PopStyleVar();
2026-03-26 21:18:33 +08:00
const bool hovered = ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem);
const bool held = ImGui::IsItemActive();
if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) {
open = !open;
storage->SetBool(openStateId, open);
2026-03-26 21:18:33 +08:00
}
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();
}
2026-03-26 21:18:33 +08:00
ImGui::PopID();
return ComponentSectionResult{ open, InspectorSectionContentIndent() };
}
inline ComponentSectionResult BeginComponentSection(
const void* id,
const char* label,
bool defaultOpen = true) {
return BeginComponentSection(id, label, []() {}, defaultOpen);
2026-03-26 21:18:33 +08:00
}
inline void EndComponentSection(const ComponentSectionResult& section) {
if (section.open && section.contentIndent > 0.0f) {
ImGui::Unindent(section.contentIndent);
}
2026-03-26 21:18:33 +08:00
}
inline bool InspectorActionButton(const char* label, ImVec2 size = ImVec2(-1.0f, 0.0f)) {
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, InspectorActionButtonPadding());
const bool pressed = ImGui::Button(label, size);
ImGui::PopStyleVar();
return pressed;
}
inline bool BeginTitledPopup(const char* id, const char* title) {
const bool open = BeginPopup(id);
if (!open) {
return false;
}
if (title && title[0] != '\0') {
ImGui::TextUnformatted(title);
ImGui::Separator();
}
return true;
}
inline void EndTitledPopup() {
EndPopup();
}
inline DialogActionResult DrawDialogActionRow(
const char* primaryLabel,
const char* secondaryLabel,
bool primaryEnabled = true,
bool secondaryEnabled = true) {
DialogActionResult result = DialogActionResult::None;
ImGui::BeginDisabled(!primaryEnabled);
if (ImGui::Button(primaryLabel, DialogActionButtonSize())) {
result = DialogActionResult::Primary;
}
ImGui::EndDisabled();
ImGui::SameLine();
ImGui::BeginDisabled(!secondaryEnabled);
if (ImGui::Button(secondaryLabel, DialogActionButtonSize())) {
result = DialogActionResult::Secondary;
}
ImGui::EndDisabled();
return result;
}
inline void DrawRightAlignedText(const char* text, const ImVec4& color, float rightPadding = MenuBarStatusRightPadding()) {
const ImVec2 textSize = ImGui::CalcTextSize(text);
const float targetX = ImGui::GetWindowWidth() - textSize.x - rightPadding;
if (targetX > ImGui::GetCursorPosX()) {
ImGui::SetCursorPosX(targetX);
}
ImGui::TextColored(color, "%s", text);
}
inline void BeginTitledTooltip(const char* title) {
ImGui::BeginTooltip();
if (title && title[0] != '\0') {
ImGui::TextUnformatted(title);
ImGui::Separator();
}
}
inline void EndTitledTooltip() {
ImGui::EndTooltip();
}
inline bool DrawConsoleLogRow(const char* text) {
ImGui::TextUnformatted(text);
if (ImGui::IsItemHovered()) {
ImDrawList* drawList = ImGui::GetWindowDrawList();
drawList->AddRectFilled(ImGui::GetItemRectMin(), ImGui::GetItemRectMax(), ImGui::GetColorU32(ConsoleRowHoverFillColor()));
}
return ImGui::IsItemClicked();
}
}
}
}