关键节点
This commit is contained in:
305
editor/src/Menu/UIEditorMenuPopup.cpp
Normal file
305
editor/src/Menu/UIEditorMenuPopup.cpp
Normal file
@@ -0,0 +1,305 @@
|
||||
#include <XCEditor/Menu/UIEditorMenuPopup.h>
|
||||
|
||||
#include <XCEditor/Foundation/UIEditorTextLayout.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace XCEngine::UI::Editor::Widgets {
|
||||
|
||||
namespace {
|
||||
|
||||
using ::XCEngine::UI::UIColor;
|
||||
using ::XCEngine::UI::UIDrawList;
|
||||
using ::XCEngine::UI::UIPoint;
|
||||
using ::XCEngine::UI::UIRect;
|
||||
using ::XCEngine::UI::Editor::UIEditorMenuItemKind;
|
||||
|
||||
float ClampNonNegative(float value) {
|
||||
return (std::max)(value, 0.0f);
|
||||
}
|
||||
|
||||
bool IsPointInsideRect(const UIRect& rect, const UIPoint& point) {
|
||||
return point.x >= rect.x &&
|
||||
point.x <= rect.x + rect.width &&
|
||||
point.y >= rect.y &&
|
||||
point.y <= rect.y + rect.height;
|
||||
}
|
||||
|
||||
bool IsInteractiveItem(const UIEditorMenuPopupItem& item) {
|
||||
return item.kind != UIEditorMenuItemKind::Separator;
|
||||
}
|
||||
|
||||
float ResolveRowTextTop(const UIRect& rect, const UIEditorMenuPopupMetrics& metrics) {
|
||||
return rect.y + (std::max)(0.0f, (rect.height - metrics.labelFontSize) * 0.5f) + metrics.labelInsetY;
|
||||
}
|
||||
|
||||
float ResolveGlyphTop(const UIRect& rect, const UIEditorMenuPopupMetrics& metrics) {
|
||||
return rect.y + (std::max)(0.0f, (rect.height - metrics.glyphFontSize) * 0.5f) - 0.5f;
|
||||
}
|
||||
|
||||
bool IsHighlighted(const UIEditorMenuPopupState& state, std::size_t index) {
|
||||
return state.hoveredIndex == index || state.submenuOpenIndex == index;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
float ResolveUIEditorMenuPopupMeasuredLabelWidth(
|
||||
const UIEditorMenuPopupItem& item,
|
||||
const UIEditorMenuPopupMetrics& metrics,
|
||||
const UIEditorTextMeasurer* textMeasurer) {
|
||||
return ResolveUIEditorMeasuredTextWidth(
|
||||
item.label,
|
||||
item.measuredLabelWidth,
|
||||
metrics.labelFontSize,
|
||||
metrics.estimatedGlyphWidth,
|
||||
textMeasurer);
|
||||
}
|
||||
|
||||
float ResolveUIEditorMenuPopupMeasuredShortcutWidth(
|
||||
const UIEditorMenuPopupItem& item,
|
||||
const UIEditorMenuPopupMetrics& metrics,
|
||||
const UIEditorTextMeasurer* textMeasurer) {
|
||||
return ResolveUIEditorMeasuredTextWidth(
|
||||
item.shortcutText,
|
||||
item.measuredShortcutWidth,
|
||||
metrics.labelFontSize,
|
||||
metrics.estimatedGlyphWidth,
|
||||
textMeasurer);
|
||||
}
|
||||
|
||||
float ResolveUIEditorMenuPopupDesiredWidth(
|
||||
const std::vector<UIEditorMenuPopupItem>& items,
|
||||
const UIEditorMenuPopupMetrics& metrics) {
|
||||
float widestRow = 0.0f;
|
||||
for (const UIEditorMenuPopupItem& item : items) {
|
||||
if (item.kind == UIEditorMenuItemKind::Separator) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const float labelWidth =
|
||||
ResolveUIEditorMenuPopupMeasuredLabelWidth(item, metrics);
|
||||
const float shortcutWidth =
|
||||
item.shortcutText.empty()
|
||||
? 0.0f
|
||||
: ResolveUIEditorMenuPopupMeasuredShortcutWidth(item, metrics);
|
||||
const float submenuWidth =
|
||||
item.hasSubmenu ? ClampNonNegative(metrics.submenuIndicatorWidth) : 0.0f;
|
||||
const float rowWidth =
|
||||
ClampNonNegative(metrics.labelInsetX) +
|
||||
ClampNonNegative(metrics.checkColumnWidth) +
|
||||
labelWidth +
|
||||
(shortcutWidth > 0.0f ? ClampNonNegative(metrics.shortcutGap) + shortcutWidth : 0.0f) +
|
||||
submenuWidth +
|
||||
ClampNonNegative(metrics.shortcutInsetRight);
|
||||
widestRow = (std::max)(widestRow, rowWidth);
|
||||
}
|
||||
|
||||
return widestRow + ClampNonNegative(metrics.contentPaddingX) * 2.0f;
|
||||
}
|
||||
|
||||
float MeasureUIEditorMenuPopupHeight(
|
||||
const std::vector<UIEditorMenuPopupItem>& items,
|
||||
const UIEditorMenuPopupMetrics& metrics) {
|
||||
float height = ClampNonNegative(metrics.contentPaddingY) * 2.0f;
|
||||
for (const UIEditorMenuPopupItem& item : items) {
|
||||
height += item.kind == UIEditorMenuItemKind::Separator
|
||||
? ClampNonNegative(metrics.separatorHeight)
|
||||
: ClampNonNegative(metrics.itemHeight);
|
||||
}
|
||||
return height;
|
||||
}
|
||||
|
||||
UIEditorMenuPopupLayout BuildUIEditorMenuPopupLayout(
|
||||
const UIRect& popupRect,
|
||||
const std::vector<UIEditorMenuPopupItem>& items,
|
||||
const UIEditorMenuPopupMetrics& metrics) {
|
||||
UIEditorMenuPopupLayout layout = {};
|
||||
layout.popupRect = UIRect(
|
||||
popupRect.x,
|
||||
popupRect.y,
|
||||
ClampNonNegative(popupRect.width),
|
||||
ClampNonNegative(popupRect.height));
|
||||
layout.contentRect = UIRect(
|
||||
layout.popupRect.x + ClampNonNegative(metrics.contentPaddingX),
|
||||
layout.popupRect.y + ClampNonNegative(metrics.contentPaddingY),
|
||||
(std::max)(
|
||||
layout.popupRect.width - ClampNonNegative(metrics.contentPaddingX) * 2.0f,
|
||||
0.0f),
|
||||
(std::max)(
|
||||
layout.popupRect.height - ClampNonNegative(metrics.contentPaddingY) * 2.0f,
|
||||
0.0f));
|
||||
|
||||
float cursorY = layout.contentRect.y;
|
||||
layout.itemRects.reserve(items.size());
|
||||
for (const UIEditorMenuPopupItem& item : items) {
|
||||
const float itemHeight = item.kind == UIEditorMenuItemKind::Separator
|
||||
? ClampNonNegative(metrics.separatorHeight)
|
||||
: ClampNonNegative(metrics.itemHeight);
|
||||
layout.itemRects.emplace_back(
|
||||
layout.contentRect.x,
|
||||
cursorY,
|
||||
layout.contentRect.width,
|
||||
itemHeight);
|
||||
cursorY += itemHeight;
|
||||
}
|
||||
|
||||
return layout;
|
||||
}
|
||||
|
||||
UIEditorMenuPopupHitTarget HitTestUIEditorMenuPopup(
|
||||
const UIEditorMenuPopupLayout& layout,
|
||||
const std::vector<UIEditorMenuPopupItem>& items,
|
||||
const UIPoint& point) {
|
||||
UIEditorMenuPopupHitTarget target = {};
|
||||
if (!IsPointInsideRect(layout.popupRect, point)) {
|
||||
return target;
|
||||
}
|
||||
|
||||
for (std::size_t index = 0; index < layout.itemRects.size() && index < items.size(); ++index) {
|
||||
if (IsInteractiveItem(items[index]) && IsPointInsideRect(layout.itemRects[index], point)) {
|
||||
target.kind = UIEditorMenuPopupHitTargetKind::Item;
|
||||
target.index = index;
|
||||
return target;
|
||||
}
|
||||
}
|
||||
|
||||
target.kind = UIEditorMenuPopupHitTargetKind::PopupSurface;
|
||||
return target;
|
||||
}
|
||||
|
||||
void AppendUIEditorMenuPopupBackground(
|
||||
UIDrawList& drawList,
|
||||
const UIEditorMenuPopupLayout& layout,
|
||||
const std::vector<UIEditorMenuPopupItem>& items,
|
||||
const UIEditorMenuPopupState& state,
|
||||
const UIEditorMenuPopupPalette& palette,
|
||||
const UIEditorMenuPopupMetrics& metrics) {
|
||||
drawList.AddFilledRect(layout.popupRect, palette.popupColor, metrics.popupCornerRounding);
|
||||
drawList.AddRectOutline(
|
||||
layout.popupRect,
|
||||
palette.borderColor,
|
||||
metrics.borderThickness,
|
||||
metrics.popupCornerRounding);
|
||||
|
||||
for (std::size_t index = 0; index < layout.itemRects.size() && index < items.size(); ++index) {
|
||||
const UIEditorMenuPopupItem& item = items[index];
|
||||
const UIRect& rect = layout.itemRects[index];
|
||||
if (item.kind == UIEditorMenuItemKind::Separator) {
|
||||
const float lineY = rect.y + rect.height * 0.5f;
|
||||
const float separatorInset =
|
||||
ClampNonNegative(metrics.contentPaddingX) + 3.0f;
|
||||
drawList.AddFilledRect(
|
||||
UIRect(
|
||||
rect.x + separatorInset,
|
||||
lineY,
|
||||
(std::max)(rect.width - separatorInset * 2.0f, 0.0f),
|
||||
metrics.separatorThickness),
|
||||
palette.separatorColor);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (IsHighlighted(state, index)) {
|
||||
drawList.AddFilledRect(
|
||||
rect,
|
||||
state.submenuOpenIndex == index ? palette.itemOpenColor : palette.itemHoverColor,
|
||||
metrics.rowCornerRounding);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AppendUIEditorMenuPopupForeground(
|
||||
UIDrawList& drawList,
|
||||
const UIEditorMenuPopupLayout& layout,
|
||||
const std::vector<UIEditorMenuPopupItem>& items,
|
||||
const UIEditorMenuPopupState&,
|
||||
const UIEditorMenuPopupPalette& palette,
|
||||
const UIEditorMenuPopupMetrics& metrics) {
|
||||
for (std::size_t index = 0; index < layout.itemRects.size() && index < items.size(); ++index) {
|
||||
const UIEditorMenuPopupItem& item = items[index];
|
||||
if (item.kind == UIEditorMenuItemKind::Separator) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const UIRect& rect = layout.itemRects[index];
|
||||
const UIColor mainColor = item.enabled ? palette.textPrimary : palette.textDisabled;
|
||||
const UIColor secondaryColor = item.enabled ? palette.textMuted : palette.textDisabled;
|
||||
|
||||
const float checkLeft = rect.x + 6.0f;
|
||||
if (item.checked) {
|
||||
drawList.AddText(
|
||||
UIPoint(checkLeft, ResolveGlyphTop(rect, metrics)),
|
||||
"*",
|
||||
palette.glyphColor,
|
||||
metrics.glyphFontSize);
|
||||
}
|
||||
|
||||
const float labelLeft =
|
||||
rect.x + ClampNonNegative(metrics.labelInsetX) + ClampNonNegative(metrics.checkColumnWidth);
|
||||
const float labelRight =
|
||||
rect.x + rect.width - ClampNonNegative(metrics.shortcutInsetRight);
|
||||
float labelClipWidth = (std::max)(labelRight - labelLeft, 0.0f);
|
||||
if (!item.shortcutText.empty()) {
|
||||
const float shortcutWidth =
|
||||
ResolveUIEditorMenuPopupMeasuredShortcutWidth(item, metrics);
|
||||
labelClipWidth = (std::max)(
|
||||
labelClipWidth - shortcutWidth - ClampNonNegative(metrics.shortcutGap),
|
||||
0.0f);
|
||||
}
|
||||
if (item.hasSubmenu) {
|
||||
labelClipWidth = (std::max)(
|
||||
labelClipWidth - ClampNonNegative(metrics.submenuIndicatorWidth),
|
||||
0.0f);
|
||||
}
|
||||
|
||||
drawList.PushClipRect(UIRect(labelLeft, rect.y, labelClipWidth, rect.height), true);
|
||||
drawList.AddText(
|
||||
UIPoint(labelLeft, ResolveRowTextTop(rect, metrics)),
|
||||
item.label,
|
||||
mainColor,
|
||||
metrics.labelFontSize);
|
||||
drawList.PopClipRect();
|
||||
|
||||
if (!item.shortcutText.empty()) {
|
||||
const float shortcutWidth =
|
||||
ResolveUIEditorMenuPopupMeasuredShortcutWidth(item, metrics);
|
||||
const float shortcutLeft = rect.x + rect.width -
|
||||
ClampNonNegative(metrics.shortcutInsetRight) -
|
||||
shortcutWidth -
|
||||
(item.hasSubmenu ? ClampNonNegative(metrics.submenuIndicatorWidth) : 0.0f);
|
||||
drawList.AddText(
|
||||
UIPoint(shortcutLeft, ResolveRowTextTop(rect, metrics)),
|
||||
item.shortcutText,
|
||||
secondaryColor,
|
||||
metrics.labelFontSize);
|
||||
}
|
||||
|
||||
if (item.hasSubmenu) {
|
||||
const float submenuLeft =
|
||||
rect.x + rect.width -
|
||||
ClampNonNegative(metrics.shortcutInsetRight) -
|
||||
ClampNonNegative(metrics.submenuIndicatorWidth);
|
||||
drawList.AddText(
|
||||
UIPoint(
|
||||
submenuLeft,
|
||||
ResolveGlyphTop(rect, metrics)),
|
||||
">",
|
||||
palette.glyphColor,
|
||||
metrics.glyphFontSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AppendUIEditorMenuPopup(
|
||||
UIDrawList& drawList,
|
||||
const UIRect& popupRect,
|
||||
const std::vector<UIEditorMenuPopupItem>& items,
|
||||
const UIEditorMenuPopupState& state,
|
||||
const UIEditorMenuPopupPalette& palette,
|
||||
const UIEditorMenuPopupMetrics& metrics) {
|
||||
const UIEditorMenuPopupLayout layout =
|
||||
BuildUIEditorMenuPopupLayout(popupRect, items, metrics);
|
||||
AppendUIEditorMenuPopupBackground(drawList, layout, items, state, palette, metrics);
|
||||
AppendUIEditorMenuPopupForeground(drawList, layout, items, state, palette, metrics);
|
||||
}
|
||||
|
||||
} // namespace XCEngine::UI::Editor::Widgets
|
||||
Reference in New Issue
Block a user