Build XCEditor menu and status shell widgets
This commit is contained in:
@@ -6,6 +6,8 @@
|
||||
#include <XCEditor/Core/UIEditorMenuModel.h>
|
||||
#include <XCEditor/Core/UIEditorMenuSession.h>
|
||||
#include <XCEditor/Core/UIEditorShortcutManager.h>
|
||||
#include <XCEditor/Widgets/UIEditorMenuBar.h>
|
||||
#include <XCEditor/Widgets/UIEditorMenuPopup.h>
|
||||
#include "Host/AutoScreenshot.h"
|
||||
#include "Host/NativeRenderer.h"
|
||||
|
||||
@@ -77,6 +79,20 @@ using XCEngine::UI::UIShortcutScope;
|
||||
using XCEngine::UI::Widgets::ResolvePopupPlacementRect;
|
||||
using XCEngine::UI::Widgets::UIPopupOverlayEntry;
|
||||
using XCEngine::UI::Widgets::UIPopupPlacement;
|
||||
using XCEngine::UI::Editor::Widgets::AppendUIEditorMenuBarBackground;
|
||||
using XCEngine::UI::Editor::Widgets::AppendUIEditorMenuBarForeground;
|
||||
using XCEngine::UI::Editor::Widgets::AppendUIEditorMenuPopupBackground;
|
||||
using XCEngine::UI::Editor::Widgets::AppendUIEditorMenuPopupForeground;
|
||||
using XCEngine::UI::Editor::Widgets::BuildUIEditorMenuBarLayout;
|
||||
using XCEngine::UI::Editor::Widgets::BuildUIEditorMenuPopupLayout;
|
||||
using XCEngine::UI::Editor::Widgets::MeasureUIEditorMenuPopupHeight;
|
||||
using XCEngine::UI::Editor::Widgets::ResolveUIEditorMenuPopupDesiredWidth;
|
||||
using XCEngine::UI::Editor::Widgets::UIEditorMenuBarItem;
|
||||
using XCEngine::UI::Editor::Widgets::UIEditorMenuBarState;
|
||||
using UIEditorMenuPopupWidgetItem = XCEngine::UI::Editor::Widgets::UIEditorMenuPopupItem;
|
||||
using UIEditorMenuPopupWidgetState = XCEngine::UI::Editor::Widgets::UIEditorMenuPopupState;
|
||||
using XCEngine::UI::Editor::Widgets::UIEditorMenuBarInvalidIndex;
|
||||
using XCEngine::UI::Editor::Widgets::UIEditorMenuPopupInvalidIndex;
|
||||
using XCEngine::UI::Editor::Host::AutoScreenshotController;
|
||||
using XCEngine::UI::Editor::Host::NativeRenderer;
|
||||
|
||||
@@ -159,8 +175,11 @@ std::string JoinClosedPopupIds(const UIEditorMenuSessionMutationResult& result);
|
||||
const UIEditorResolvedMenuDescriptor* FindResolvedMenu(const UIEditorResolvedMenuModel& model, std::string_view menuId);
|
||||
const UIEditorResolvedMenuItem* FindResolvedMenuItemRecursive(const std::vector<UIEditorResolvedMenuItem>& items, std::string_view itemId);
|
||||
const std::vector<UIEditorResolvedMenuItem>* ResolvePopupItems(const UIEditorResolvedMenuModel& model, const UIEditorMenuPopupState& popupState);
|
||||
std::vector<UIEditorMenuBarItem> BuildMenuBarWidgetItems(const UIEditorResolvedMenuModel& model);
|
||||
std::vector<UIEditorMenuPopupWidgetItem> BuildMenuPopupWidgetItems(const std::vector<UIEditorResolvedMenuItem>& items);
|
||||
std::size_t FindMenuBarWidgetIndex(const std::vector<UIEditorMenuBarItem>& items, std::string_view menuId);
|
||||
std::size_t FindMenuPopupWidgetIndex(const std::vector<UIEditorMenuPopupWidgetItem>& items, std::string_view itemId);
|
||||
std::uint64_t HashText(std::string_view text);
|
||||
float MeasureMenuPopupHeight(const std::vector<UIEditorResolvedMenuItem>& items);
|
||||
|
||||
class ScenarioApp {
|
||||
public:
|
||||
@@ -602,13 +621,61 @@ const std::vector<UIEditorResolvedMenuItem>* ResolvePopupItems(
|
||||
return &item->children;
|
||||
}
|
||||
|
||||
float MeasureMenuPopupHeight(const std::vector<UIEditorResolvedMenuItem>& items) {
|
||||
float contentHeight = 10.0f;
|
||||
std::vector<UIEditorMenuBarItem> BuildMenuBarWidgetItems(
|
||||
const UIEditorResolvedMenuModel& model) {
|
||||
std::vector<UIEditorMenuBarItem> items = {};
|
||||
items.reserve(model.menus.size());
|
||||
for (const UIEditorResolvedMenuDescriptor& menu : model.menus) {
|
||||
UIEditorMenuBarItem item = {};
|
||||
item.menuId = menu.menuId;
|
||||
item.label = menu.label;
|
||||
item.enabled = true;
|
||||
items.push_back(std::move(item));
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
std::vector<UIEditorMenuPopupWidgetItem> BuildMenuPopupWidgetItems(
|
||||
const std::vector<UIEditorResolvedMenuItem>& items) {
|
||||
std::vector<UIEditorMenuPopupWidgetItem> popupItems = {};
|
||||
popupItems.reserve(items.size());
|
||||
for (const UIEditorResolvedMenuItem& item : items) {
|
||||
contentHeight += item.kind == UIEditorMenuItemKind::Separator ? 12.0f : 34.0f;
|
||||
UIEditorMenuPopupWidgetItem popupItem = {};
|
||||
popupItem.itemId = item.itemId;
|
||||
popupItem.kind = item.kind;
|
||||
popupItem.label = item.label;
|
||||
popupItem.shortcutText = item.shortcutText;
|
||||
popupItem.enabled = item.enabled;
|
||||
popupItem.checked = item.checked;
|
||||
popupItem.hasSubmenu =
|
||||
item.kind == UIEditorMenuItemKind::Submenu && !item.children.empty();
|
||||
popupItems.push_back(std::move(popupItem));
|
||||
}
|
||||
return popupItems;
|
||||
}
|
||||
|
||||
std::size_t FindMenuBarWidgetIndex(
|
||||
const std::vector<UIEditorMenuBarItem>& items,
|
||||
std::string_view menuId) {
|
||||
for (std::size_t index = 0; index < items.size(); ++index) {
|
||||
if (items[index].menuId == menuId) {
|
||||
return index;
|
||||
}
|
||||
}
|
||||
|
||||
return contentHeight + 8.0f;
|
||||
return UIEditorMenuBarInvalidIndex;
|
||||
}
|
||||
|
||||
std::size_t FindMenuPopupWidgetIndex(
|
||||
const std::vector<UIEditorMenuPopupWidgetItem>& items,
|
||||
std::string_view itemId) {
|
||||
for (std::size_t index = 0; index < items.size(); ++index) {
|
||||
if (items[index].itemId == itemId) {
|
||||
return index;
|
||||
}
|
||||
}
|
||||
|
||||
return UIEditorMenuPopupInvalidIndex;
|
||||
}
|
||||
|
||||
int ScenarioApp::Run(HINSTANCE hInstance, int nCmdShow) {
|
||||
@@ -867,7 +934,8 @@ const MenuPopupLayout* ScenarioApp::HitTestMenuPopup(float x, float y) const {
|
||||
|
||||
const MenuItemLayout* ScenarioApp::HitTestMenuItem(float x, float y) const {
|
||||
for (auto it = m_menuItems.rbegin(); it != m_menuItems.rend(); ++it) {
|
||||
if (ContainsPoint(it->rect, x, y)) {
|
||||
if (it->kind != UIEditorMenuItemKind::Separator &&
|
||||
ContainsPoint(it->rect, x, y)) {
|
||||
return &(*it);
|
||||
}
|
||||
}
|
||||
@@ -1234,31 +1302,25 @@ void ScenarioApp::DrawMenuBar(
|
||||
UIDrawList& drawList,
|
||||
const UIRect& rect,
|
||||
const UIEditorResolvedMenuModel& resolvedModel) {
|
||||
drawList.AddFilledRect(rect, kMenuBarBg, 8.0f);
|
||||
drawList.AddRectOutline(rect, kCardBorder, 1.0f, 8.0f);
|
||||
const auto barItems = BuildMenuBarWidgetItems(resolvedModel);
|
||||
UIEditorMenuBarState barState = {};
|
||||
barState.openIndex = FindMenuBarWidgetIndex(barItems, m_menuSession.GetOpenRootMenuId());
|
||||
barState.hoveredIndex = FindMenuBarWidgetIndex(barItems, m_hoveredMenuId);
|
||||
barState.focused = m_menuSession.HasOpenMenu();
|
||||
|
||||
float buttonX = rect.x + 12.0f;
|
||||
for (const UIEditorResolvedMenuDescriptor& menu : resolvedModel.menus) {
|
||||
const bool open = m_menuSession.IsMenuOpen(menu.menuId);
|
||||
const bool hovered = m_hoveredMenuId == menu.menuId;
|
||||
const float buttonWidth = 104.0f;
|
||||
const UIRect buttonRect(buttonX, rect.y + 6.0f, buttonWidth, rect.height - 12.0f);
|
||||
|
||||
drawList.AddFilledRect(
|
||||
buttonRect,
|
||||
open ? kMenuButtonOpen : (hovered ? kMenuButtonHover : kMenuButtonBg),
|
||||
6.0f);
|
||||
drawList.AddRectOutline(buttonRect, kCardBorder, 1.0f, 6.0f);
|
||||
drawList.AddText(
|
||||
UIPoint(buttonRect.x + 14.0f, buttonRect.y + 10.0f),
|
||||
menu.label,
|
||||
kTextPrimary,
|
||||
14.0f);
|
||||
const auto barLayout = BuildUIEditorMenuBarLayout(rect, barItems);
|
||||
AppendUIEditorMenuBarBackground(drawList, barLayout, barItems, barState);
|
||||
AppendUIEditorMenuBarForeground(drawList, barLayout, barItems, barState);
|
||||
|
||||
for (std::size_t index = 0; index < barItems.size() && index < barLayout.buttonRects.size(); ++index) {
|
||||
m_menuButtons.push_back(
|
||||
{ menu.menuId, menu.label, BuildRootPopupId(menu.menuId), buttonRect, BuildMenuButtonPath(menu.menuId) });
|
||||
|
||||
buttonX += buttonWidth + 10.0f;
|
||||
{
|
||||
barItems[index].menuId,
|
||||
barItems[index].label,
|
||||
BuildRootPopupId(barItems[index].menuId),
|
||||
barLayout.buttonRects[index],
|
||||
BuildMenuButtonPath(barItems[index].menuId)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1269,75 +1331,45 @@ void ScenarioApp::DrawPopup(
|
||||
const std::vector<UIEditorResolvedMenuItem>& items,
|
||||
const UIPopupOverlayEntry& popupEntry,
|
||||
const UIRect& viewportRect) {
|
||||
const auto popupItems = BuildMenuPopupWidgetItems(items);
|
||||
const float popupWidth = (std::max)(
|
||||
kMenuPopupWidth,
|
||||
ResolveUIEditorMenuPopupDesiredWidth(popupItems));
|
||||
const auto placementResult =
|
||||
ResolvePopupPlacementRect(
|
||||
popupEntry.anchorRect,
|
||||
XCEngine::UI::UISize(kMenuPopupWidth, MeasureMenuPopupHeight(items)),
|
||||
XCEngine::UI::UISize(popupWidth, MeasureUIEditorMenuPopupHeight(popupItems)),
|
||||
viewportRect,
|
||||
popupEntry.placement);
|
||||
const UIRect popupRect = placementResult.rect;
|
||||
|
||||
drawList.AddFilledRect(popupRect, kMenuDropBg, 8.0f);
|
||||
drawList.AddRectOutline(popupRect, kCardBorder, 1.0f, 8.0f);
|
||||
UIEditorMenuPopupWidgetState popupState = {};
|
||||
popupState.hoveredIndex = FindMenuPopupWidgetIndex(popupItems, m_hoveredItemId);
|
||||
popupState.focused = true;
|
||||
for (std::size_t index = 0; index < popupItems.size(); ++index) {
|
||||
if (!popupItems[index].hasSubmenu) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (m_menuSession.IsPopupOpen(BuildSubmenuPopupId(popupItems[index].itemId))) {
|
||||
popupState.submenuOpenIndex = index;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const auto popupLayout = BuildUIEditorMenuPopupLayout(popupRect, popupItems);
|
||||
AppendUIEditorMenuPopupBackground(drawList, popupLayout, popupItems, popupState);
|
||||
AppendUIEditorMenuPopupForeground(drawList, popupLayout, popupItems, popupState);
|
||||
|
||||
m_menuPopups.push_back(
|
||||
{ std::string(popupId), std::string(menuId), popupEntry.parentPopupId, popupRect, popupEntry.surfacePath });
|
||||
|
||||
float itemY = popupRect.y + 8.0f;
|
||||
for (const UIEditorResolvedMenuItem& item : items) {
|
||||
if (item.kind == UIEditorMenuItemKind::Separator) {
|
||||
drawList.AddFilledRect(
|
||||
UIRect(popupRect.x + 12.0f, itemY + 4.0f, popupRect.width - 24.0f, 1.0f),
|
||||
kMenuDivider);
|
||||
itemY += 12.0f;
|
||||
continue;
|
||||
}
|
||||
|
||||
for (std::size_t index = 0; index < items.size() && index < popupItems.size() && index < popupLayout.itemRects.size(); ++index) {
|
||||
const UIEditorResolvedMenuItem& item = items[index];
|
||||
const bool hasSubmenu =
|
||||
item.kind == UIEditorMenuItemKind::Submenu && !item.children.empty();
|
||||
const std::string childPopupId =
|
||||
hasSubmenu ? BuildSubmenuPopupId(item.itemId) : std::string();
|
||||
const bool submenuOpen =
|
||||
hasSubmenu && m_menuSession.IsPopupOpen(childPopupId);
|
||||
const bool hovered = m_hoveredItemId == item.itemId;
|
||||
|
||||
const UIRect itemRect(popupRect.x + 8.0f, itemY, popupRect.width - 16.0f, 30.0f);
|
||||
if (hovered || submenuOpen) {
|
||||
drawList.AddFilledRect(itemRect, kMenuItemHover, 6.0f);
|
||||
}
|
||||
|
||||
drawList.AddRectOutline(
|
||||
UIRect(itemRect.x + 10.0f, itemRect.y + 8.0f, 10.0f, 10.0f),
|
||||
item.checked ? kAccent : kMenuDivider,
|
||||
item.checked ? 2.0f : 1.0f,
|
||||
3.0f);
|
||||
if (item.checked) {
|
||||
drawList.AddFilledRect(
|
||||
UIRect(itemRect.x + 12.0f, itemRect.y + 10.0f, 6.0f, 6.0f),
|
||||
kAccent,
|
||||
2.0f);
|
||||
}
|
||||
|
||||
drawList.AddText(
|
||||
UIPoint(itemRect.x + 30.0f, itemRect.y + 7.0f),
|
||||
item.label,
|
||||
item.enabled ? kTextPrimary : kTextDisabled,
|
||||
13.0f);
|
||||
if (!item.shortcutText.empty()) {
|
||||
drawList.AddText(
|
||||
UIPoint(itemRect.x + itemRect.width - 92.0f, itemRect.y + 7.0f),
|
||||
item.shortcutText,
|
||||
item.enabled ? kTextMuted : kTextDisabled,
|
||||
12.0f);
|
||||
}
|
||||
if (hasSubmenu) {
|
||||
drawList.AddText(
|
||||
UIPoint(itemRect.x + itemRect.width - 24.0f, itemRect.y + 7.0f),
|
||||
">",
|
||||
kTextMuted,
|
||||
13.0f);
|
||||
}
|
||||
|
||||
m_menuItems.push_back(
|
||||
{
|
||||
std::string(popupId),
|
||||
@@ -1348,13 +1380,12 @@ void ScenarioApp::DrawPopup(
|
||||
item.commandId,
|
||||
item.shortcutText,
|
||||
childPopupId,
|
||||
itemRect,
|
||||
popupLayout.itemRects[index],
|
||||
BuildMenuItemPath(popupId, item.itemId),
|
||||
item.enabled,
|
||||
item.checked,
|
||||
hasSubmenu
|
||||
});
|
||||
itemY += 34.0f;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1410,7 +1441,7 @@ void ScenarioApp::BuildDrawData(UIDrawData& drawData, float width, float height)
|
||||
shellRect.y,
|
||||
width - shellRect.width - margin * 2.0f - 16.0f,
|
||||
height - 312.0f);
|
||||
const UIRect footerRect(margin, height - 100.0f, width - margin * 2.0f, 80.0f);
|
||||
const UIRect footerRect(margin, height - 124.0f, width - margin * 2.0f, 104.0f);
|
||||
|
||||
DrawCard(
|
||||
drawList,
|
||||
@@ -1461,8 +1492,8 @@ void ScenarioApp::BuildDrawData(UIDrawData& drawData, float width, float height)
|
||||
drawList.AddText(UIPoint(stateRect.x + 18.0f, stateRect.y + 482.0f), "menu model validation", kTextMuted, 12.0f);
|
||||
drawList.AddText(UIPoint(stateRect.x + 18.0f, stateRect.y + 502.0f), menuValidation.IsValid() ? "OK" : menuValidation.message, menuValidation.IsValid() ? kSuccess : kDanger, 12.0f);
|
||||
|
||||
drawList.AddText(UIPoint(footerRect.x + 18.0f, footerRect.y + 28.0f), "Last interaction: " + m_lastActionName + " | Result: " + m_lastStatusLabel, m_lastStatusColor, 13.0f);
|
||||
drawList.AddText(UIPoint(footerRect.x + 18.0f, footerRect.y + 48.0f), m_lastMessage, kTextPrimary, 12.0f);
|
||||
drawList.AddText(UIPoint(footerRect.x + 18.0f, footerRect.y + 56.0f), "Last interaction: " + m_lastActionName + " | Result: " + m_lastStatusLabel, m_lastStatusColor, 13.0f);
|
||||
drawList.AddText(UIPoint(footerRect.x + 18.0f, footerRect.y + 74.0f), m_lastMessage, kTextPrimary, 12.0f);
|
||||
|
||||
const std::string captureSummary =
|
||||
m_autoScreenshot.HasPendingCapture()
|
||||
@@ -1470,7 +1501,7 @@ void ScenarioApp::BuildDrawData(UIDrawData& drawData, float width, float height)
|
||||
: (m_autoScreenshot.GetLastCaptureSummary().empty()
|
||||
? std::string("F12 -> tests/UI/Editor/integration/shell/menu_bar_basic/captures/")
|
||||
: m_autoScreenshot.GetLastCaptureSummary());
|
||||
drawList.AddText(UIPoint(footerRect.x + 18.0f, footerRect.y + 66.0f), captureSummary, kTextMuted, 12.0f);
|
||||
drawList.AddText(UIPoint(footerRect.x + 18.0f, footerRect.y + 92.0f), captureSummary, kTextMuted, 12.0f);
|
||||
|
||||
DrawOpenPopups(drawList, resolvedModel, viewportRect);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user