Add editor shell interaction contract
This commit is contained in:
@@ -19,6 +19,7 @@ add_library(XCUIEditorLib STATIC
|
||||
src/Core/UIEditorMenuSession.cpp
|
||||
src/Core/UIEditorPanelRegistry.cpp
|
||||
src/Core/UIEditorShellCompose.cpp
|
||||
src/Core/UIEditorShellInteraction.cpp
|
||||
src/Core/UIEditorShortcutManager.cpp
|
||||
src/Core/UIEditorViewportInputBridge.cpp
|
||||
src/Core/UIEditorViewportShell.cpp
|
||||
|
||||
139
new_editor/include/XCEditor/Core/UIEditorShellInteraction.h
Normal file
139
new_editor/include/XCEditor/Core/UIEditorShellInteraction.h
Normal file
@@ -0,0 +1,139 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEditor/Core/UIEditorMenuModel.h>
|
||||
#include <XCEditor/Core/UIEditorMenuSession.h>
|
||||
#include <XCEditor/Core/UIEditorShellCompose.h>
|
||||
#include <XCEditor/Core/UIEditorWorkspaceCompose.h>
|
||||
#include <XCEditor/Widgets/UIEditorMenuPopup.h>
|
||||
|
||||
#include <XCEngine/UI/DrawData.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace XCEngine::UI::Editor {
|
||||
|
||||
struct UIEditorShellInteractionModel {
|
||||
UIEditorResolvedMenuModel resolvedMenuModel = {};
|
||||
std::vector<Widgets::UIEditorStatusBarSegment> statusSegments = {};
|
||||
std::vector<UIEditorWorkspacePanelPresentationModel> workspacePresentations = {};
|
||||
};
|
||||
|
||||
struct UIEditorShellInteractionState {
|
||||
UIEditorShellComposeState composeState = {};
|
||||
UIEditorMenuSession menuSession = {};
|
||||
::XCEngine::UI::UIPoint pointerPosition = {};
|
||||
bool focused = false;
|
||||
bool hasPointerPosition = false;
|
||||
};
|
||||
|
||||
struct UIEditorShellInteractionMetrics {
|
||||
UIEditorShellComposeMetrics shellMetrics = {};
|
||||
Widgets::UIEditorMenuPopupMetrics popupMetrics = {};
|
||||
};
|
||||
|
||||
struct UIEditorShellInteractionPalette {
|
||||
UIEditorShellComposePalette shellPalette = {};
|
||||
Widgets::UIEditorMenuPopupPalette popupPalette = {};
|
||||
};
|
||||
|
||||
struct UIEditorShellInteractionMenuButtonRequest {
|
||||
std::string menuId = {};
|
||||
std::string label = {};
|
||||
std::string popupId = {};
|
||||
::XCEngine::UI::UIRect rect = {};
|
||||
::XCEngine::UI::UIInputPath path = {};
|
||||
bool enabled = true;
|
||||
};
|
||||
|
||||
struct UIEditorShellInteractionPopupItemRequest {
|
||||
std::string popupId = {};
|
||||
std::string menuId = {};
|
||||
std::string itemId = {};
|
||||
std::string label = {};
|
||||
std::string commandId = {};
|
||||
std::string childPopupId = {};
|
||||
::XCEngine::UI::UIRect rect = {};
|
||||
::XCEngine::UI::UIInputPath path = {};
|
||||
UIEditorMenuItemKind kind = UIEditorMenuItemKind::Command;
|
||||
bool enabled = false;
|
||||
bool checked = false;
|
||||
bool hasSubmenu = false;
|
||||
};
|
||||
|
||||
struct UIEditorShellInteractionPopupRequest {
|
||||
std::string popupId = {};
|
||||
std::string menuId = {};
|
||||
std::string sourceItemId = {};
|
||||
::XCEngine::UI::Widgets::UIPopupOverlayEntry overlayEntry = {};
|
||||
::XCEngine::UI::Widgets::UIPopupPlacementResult placement = {};
|
||||
Widgets::UIEditorMenuPopupLayout layout = {};
|
||||
std::vector<UIEditorResolvedMenuItem> resolvedItems = {};
|
||||
std::vector<Widgets::UIEditorMenuPopupItem> widgetItems = {};
|
||||
std::vector<UIEditorShellInteractionPopupItemRequest> itemRequests = {};
|
||||
};
|
||||
|
||||
struct UIEditorShellInteractionRequest {
|
||||
UIEditorShellComposeRequest shellRequest = {};
|
||||
std::vector<Widgets::UIEditorMenuBarItem> menuBarItems = {};
|
||||
std::vector<UIEditorShellInteractionMenuButtonRequest> menuButtons = {};
|
||||
std::vector<UIEditorShellInteractionPopupRequest> popupRequests = {};
|
||||
};
|
||||
|
||||
struct UIEditorShellInteractionResult {
|
||||
bool consumed = false;
|
||||
bool commandTriggered = false;
|
||||
std::string menuId = {};
|
||||
std::string popupId = {};
|
||||
std::string itemId = {};
|
||||
std::string commandId = {};
|
||||
UIEditorMenuSessionMutationResult menuMutation = {};
|
||||
};
|
||||
|
||||
struct UIEditorShellInteractionPopupFrame {
|
||||
std::string popupId = {};
|
||||
Widgets::UIEditorMenuPopupState popupState = {};
|
||||
};
|
||||
|
||||
struct UIEditorShellInteractionFrame {
|
||||
UIEditorShellInteractionRequest request = {};
|
||||
UIEditorShellComposeFrame shellFrame = {};
|
||||
std::vector<UIEditorShellInteractionPopupFrame> popupFrames = {};
|
||||
UIEditorShellInteractionResult result = {};
|
||||
std::string openRootMenuId = {};
|
||||
std::string hoveredMenuId = {};
|
||||
std::string hoveredPopupId = {};
|
||||
std::string hoveredItemId = {};
|
||||
bool focused = false;
|
||||
};
|
||||
|
||||
UIEditorShellInteractionRequest ResolveUIEditorShellInteractionRequest(
|
||||
const ::XCEngine::UI::UIRect& bounds,
|
||||
const UIEditorPanelRegistry& panelRegistry,
|
||||
const UIEditorWorkspaceModel& workspace,
|
||||
const UIEditorWorkspaceSession& session,
|
||||
const UIEditorShellInteractionModel& model,
|
||||
const Widgets::UIEditorDockHostState& dockHostState = {},
|
||||
const UIEditorShellInteractionState& state = {},
|
||||
const UIEditorShellInteractionMetrics& metrics = {});
|
||||
|
||||
UIEditorShellInteractionFrame UpdateUIEditorShellInteraction(
|
||||
UIEditorShellInteractionState& state,
|
||||
const ::XCEngine::UI::UIRect& bounds,
|
||||
const UIEditorPanelRegistry& panelRegistry,
|
||||
const UIEditorWorkspaceModel& workspace,
|
||||
const UIEditorWorkspaceSession& session,
|
||||
const UIEditorShellInteractionModel& model,
|
||||
const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents,
|
||||
const Widgets::UIEditorDockHostState& dockHostState = {},
|
||||
const UIEditorShellInteractionMetrics& metrics = {});
|
||||
|
||||
void AppendUIEditorShellInteraction(
|
||||
::XCEngine::UI::UIDrawList& drawList,
|
||||
const UIEditorShellInteractionFrame& frame,
|
||||
const UIEditorShellInteractionModel& model,
|
||||
const UIEditorShellInteractionState& state,
|
||||
const UIEditorShellInteractionPalette& palette = {},
|
||||
const UIEditorShellInteractionMetrics& metrics = {});
|
||||
|
||||
} // namespace XCEngine::UI::Editor
|
||||
756
new_editor/src/Core/UIEditorShellInteraction.cpp
Normal file
756
new_editor/src/Core/UIEditorShellInteraction.cpp
Normal file
@@ -0,0 +1,756 @@
|
||||
#include <XCEditor/Core/UIEditorShellInteraction.h>
|
||||
|
||||
#include <XCEngine/Input/InputTypes.h>
|
||||
#include <XCEngine/UI/Widgets/UIPopupOverlayModel.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
|
||||
namespace XCEngine::UI::Editor {
|
||||
|
||||
namespace {
|
||||
|
||||
using XCEngine::Input::KeyCode;
|
||||
using ::XCEngine::UI::UIElementId;
|
||||
using ::XCEngine::UI::UIInputEvent;
|
||||
using ::XCEngine::UI::UIInputEventType;
|
||||
using ::XCEngine::UI::UIInputPath;
|
||||
using ::XCEngine::UI::UIPoint;
|
||||
using ::XCEngine::UI::UIRect;
|
||||
using ::XCEngine::UI::UISize;
|
||||
using ::XCEngine::UI::Widgets::ResolvePopupPlacementRect;
|
||||
using ::XCEngine::UI::Widgets::UIPopupOverlayEntry;
|
||||
using ::XCEngine::UI::Widgets::UIPopupPlacement;
|
||||
using Widgets::AppendUIEditorMenuPopupBackground;
|
||||
using Widgets::AppendUIEditorMenuPopupForeground;
|
||||
using Widgets::BuildUIEditorMenuBarLayout;
|
||||
using Widgets::BuildUIEditorMenuPopupLayout;
|
||||
using Widgets::HitTestUIEditorMenuBar;
|
||||
using Widgets::HitTestUIEditorMenuPopup;
|
||||
using Widgets::MeasureUIEditorMenuPopupHeight;
|
||||
using Widgets::ResolveUIEditorMenuPopupDesiredWidth;
|
||||
using Widgets::UIEditorMenuBarHitTargetKind;
|
||||
using Widgets::UIEditorMenuBarInvalidIndex;
|
||||
using Widgets::UIEditorMenuPopupHitTargetKind;
|
||||
using Widgets::UIEditorMenuPopupInvalidIndex;
|
||||
|
||||
constexpr UIElementId kShellPathRoot = 0x1E1001ull;
|
||||
constexpr UIElementId kMenuBarPathRoot = 0x1E1002ull;
|
||||
constexpr UIElementId kPopupPathRoot = 0x1E1003ull;
|
||||
constexpr UIElementId kMenuItemPathRoot = 0x1E1004ull;
|
||||
constexpr UIElementId kOutsidePointerPath = 0x1E10FFull;
|
||||
|
||||
struct RequestHit {
|
||||
const UIEditorShellInteractionMenuButtonRequest* menuButton = nullptr;
|
||||
const UIEditorShellInteractionPopupRequest* popupRequest = nullptr;
|
||||
const UIEditorShellInteractionPopupItemRequest* popupItem = nullptr;
|
||||
};
|
||||
|
||||
struct BuildRequestOutput {
|
||||
UIEditorShellInteractionRequest request = {};
|
||||
bool hadInvalidPopupState = false;
|
||||
};
|
||||
|
||||
UIElementId HashText(std::string_view text) {
|
||||
std::uint64_t hash = 1469598103934665603ull;
|
||||
for (const unsigned char value : text) {
|
||||
hash ^= value;
|
||||
hash *= 1099511628211ull;
|
||||
}
|
||||
|
||||
hash &= 0x7FFFFFFFFFFFFFFFull;
|
||||
return hash == 0u ? 1u : hash;
|
||||
}
|
||||
|
||||
std::string BuildRootPopupId(std::string_view menuId) {
|
||||
return "editor.menu.root." + std::string(menuId);
|
||||
}
|
||||
|
||||
std::string BuildSubmenuPopupId(std::string_view popupId, std::string_view itemId) {
|
||||
return std::string(popupId) + ".child." + std::string(itemId);
|
||||
}
|
||||
|
||||
UIInputPath BuildMenuButtonPath(std::string_view menuId) {
|
||||
return UIInputPath { kShellPathRoot, kMenuBarPathRoot, HashText(menuId) };
|
||||
}
|
||||
|
||||
UIInputPath BuildPopupSurfacePath(std::string_view popupId) {
|
||||
return UIInputPath { kShellPathRoot, kPopupPathRoot, HashText(popupId) };
|
||||
}
|
||||
|
||||
UIInputPath BuildMenuItemPath(std::string_view popupId, std::string_view itemId) {
|
||||
return UIInputPath {
|
||||
kShellPathRoot,
|
||||
kPopupPathRoot,
|
||||
HashText(popupId),
|
||||
kMenuItemPathRoot,
|
||||
HashText(itemId)
|
||||
};
|
||||
}
|
||||
|
||||
const UIEditorResolvedMenuDescriptor* FindResolvedMenu(
|
||||
const UIEditorResolvedMenuModel& model,
|
||||
std::string_view menuId) {
|
||||
for (const UIEditorResolvedMenuDescriptor& menu : model.menus) {
|
||||
if (menu.menuId == menuId) {
|
||||
return &menu;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const UIEditorResolvedMenuItem* FindResolvedMenuItemRecursive(
|
||||
const std::vector<UIEditorResolvedMenuItem>& items,
|
||||
std::string_view itemId) {
|
||||
for (const UIEditorResolvedMenuItem& item : items) {
|
||||
if (item.itemId == itemId) {
|
||||
return &item;
|
||||
}
|
||||
|
||||
if (!item.children.empty()) {
|
||||
if (const UIEditorResolvedMenuItem* child =
|
||||
FindResolvedMenuItemRecursive(item.children, itemId);
|
||||
child != nullptr) {
|
||||
return child;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const std::vector<UIEditorResolvedMenuItem>* ResolvePopupItems(
|
||||
const UIEditorResolvedMenuModel& model,
|
||||
const UIEditorMenuPopupState& popupState) {
|
||||
const UIEditorResolvedMenuDescriptor* menu =
|
||||
FindResolvedMenu(model, popupState.menuId);
|
||||
if (menu == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (popupState.itemId.empty()) {
|
||||
return &menu->items;
|
||||
}
|
||||
|
||||
const UIEditorResolvedMenuItem* item =
|
||||
FindResolvedMenuItemRecursive(menu->items, popupState.itemId);
|
||||
if (item == nullptr || item->kind != UIEditorMenuItemKind::Submenu) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return &item->children;
|
||||
}
|
||||
|
||||
std::vector<Widgets::UIEditorMenuBarItem> BuildMenuBarItems(
|
||||
const UIEditorResolvedMenuModel& model) {
|
||||
std::vector<Widgets::UIEditorMenuBarItem> items = {};
|
||||
items.reserve(model.menus.size());
|
||||
|
||||
for (const UIEditorResolvedMenuDescriptor& menu : model.menus) {
|
||||
Widgets::UIEditorMenuBarItem item = {};
|
||||
item.menuId = menu.menuId;
|
||||
item.label = menu.label;
|
||||
item.enabled = !menu.items.empty();
|
||||
items.push_back(std::move(item));
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
UIEditorShellComposeModel BuildShellComposeModel(
|
||||
const UIEditorShellInteractionModel& model,
|
||||
const std::vector<Widgets::UIEditorMenuBarItem>& menuBarItems) {
|
||||
UIEditorShellComposeModel shellModel = {};
|
||||
shellModel.menuBarItems = menuBarItems;
|
||||
shellModel.statusSegments = model.statusSegments;
|
||||
shellModel.workspacePresentations = model.workspacePresentations;
|
||||
return shellModel;
|
||||
}
|
||||
|
||||
std::vector<Widgets::UIEditorMenuPopupItem> BuildPopupWidgetItems(
|
||||
const std::vector<UIEditorResolvedMenuItem>& items) {
|
||||
std::vector<Widgets::UIEditorMenuPopupItem> widgetItems = {};
|
||||
widgetItems.reserve(items.size());
|
||||
|
||||
for (const UIEditorResolvedMenuItem& item : items) {
|
||||
Widgets::UIEditorMenuPopupItem widgetItem = {};
|
||||
widgetItem.itemId = item.itemId;
|
||||
widgetItem.kind = item.kind;
|
||||
widgetItem.label = item.label;
|
||||
widgetItem.shortcutText = item.shortcutText;
|
||||
widgetItem.enabled = item.enabled;
|
||||
widgetItem.checked = item.checked;
|
||||
widgetItem.hasSubmenu = item.kind == UIEditorMenuItemKind::Submenu && !item.children.empty();
|
||||
widgetItems.push_back(std::move(widgetItem));
|
||||
}
|
||||
|
||||
return widgetItems;
|
||||
}
|
||||
|
||||
std::size_t FindMenuBarIndex(
|
||||
const std::vector<Widgets::UIEditorMenuBarItem>& items,
|
||||
std::string_view menuId) {
|
||||
for (std::size_t index = 0; index < items.size(); ++index) {
|
||||
if (items[index].menuId == menuId) {
|
||||
return index;
|
||||
}
|
||||
}
|
||||
|
||||
return UIEditorMenuBarInvalidIndex;
|
||||
}
|
||||
|
||||
std::size_t FindPopupItemIndex(
|
||||
const std::vector<UIEditorShellInteractionPopupItemRequest>& items,
|
||||
std::string_view itemId) {
|
||||
for (std::size_t index = 0; index < items.size(); ++index) {
|
||||
if (items[index].itemId == itemId) {
|
||||
return index;
|
||||
}
|
||||
}
|
||||
|
||||
return UIEditorMenuPopupInvalidIndex;
|
||||
}
|
||||
|
||||
bool HasMeaningfulInteractionResult(
|
||||
const UIEditorShellInteractionResult& result) {
|
||||
return result.consumed ||
|
||||
result.commandTriggered ||
|
||||
result.menuMutation.changed ||
|
||||
!result.menuId.empty() ||
|
||||
!result.popupId.empty() ||
|
||||
!result.itemId.empty() ||
|
||||
!result.commandId.empty();
|
||||
}
|
||||
|
||||
BuildRequestOutput BuildRequest(
|
||||
const UIRect& bounds,
|
||||
const UIEditorPanelRegistry& panelRegistry,
|
||||
const UIEditorWorkspaceModel& workspace,
|
||||
const UIEditorWorkspaceSession& session,
|
||||
const UIEditorShellInteractionModel& model,
|
||||
const Widgets::UIEditorDockHostState& dockHostState,
|
||||
const UIEditorShellInteractionState& state,
|
||||
const UIEditorShellInteractionMetrics& metrics) {
|
||||
BuildRequestOutput output = {};
|
||||
UIEditorShellInteractionRequest& request = output.request;
|
||||
request.menuBarItems = BuildMenuBarItems(model.resolvedMenuModel);
|
||||
|
||||
const UIEditorShellComposeModel shellModel =
|
||||
BuildShellComposeModel(model, request.menuBarItems);
|
||||
request.shellRequest = ResolveUIEditorShellComposeRequest(
|
||||
bounds,
|
||||
panelRegistry,
|
||||
workspace,
|
||||
session,
|
||||
shellModel,
|
||||
dockHostState,
|
||||
state.composeState,
|
||||
metrics.shellMetrics);
|
||||
|
||||
request.menuButtons.reserve(request.menuBarItems.size());
|
||||
for (std::size_t index = 0; index < request.menuBarItems.size(); ++index) {
|
||||
UIEditorShellInteractionMenuButtonRequest button = {};
|
||||
button.menuId = request.menuBarItems[index].menuId;
|
||||
button.label = request.menuBarItems[index].label;
|
||||
button.popupId = BuildRootPopupId(button.menuId);
|
||||
button.enabled = request.menuBarItems[index].enabled;
|
||||
if (index < request.shellRequest.layout.menuBarLayout.buttonRects.size()) {
|
||||
button.rect = request.shellRequest.layout.menuBarLayout.buttonRects[index];
|
||||
}
|
||||
button.path = BuildMenuButtonPath(button.menuId);
|
||||
request.menuButtons.push_back(std::move(button));
|
||||
}
|
||||
|
||||
const auto& popupStates = state.menuSession.GetPopupStates();
|
||||
request.popupRequests.reserve(popupStates.size());
|
||||
for (const UIEditorMenuPopupState& popupState : popupStates) {
|
||||
const UIPopupOverlayEntry* overlayEntry =
|
||||
state.menuSession.GetPopupOverlayModel().FindPopup(popupState.popupId);
|
||||
const std::vector<UIEditorResolvedMenuItem>* resolvedItems =
|
||||
ResolvePopupItems(model.resolvedMenuModel, popupState);
|
||||
if (overlayEntry == nullptr || resolvedItems == nullptr) {
|
||||
output.hadInvalidPopupState = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
UIEditorShellInteractionPopupRequest popupRequest = {};
|
||||
popupRequest.popupId = popupState.popupId;
|
||||
popupRequest.menuId = popupState.menuId;
|
||||
popupRequest.sourceItemId = popupState.itemId;
|
||||
popupRequest.overlayEntry = *overlayEntry;
|
||||
popupRequest.resolvedItems = *resolvedItems;
|
||||
popupRequest.widgetItems = BuildPopupWidgetItems(popupRequest.resolvedItems);
|
||||
|
||||
const float popupWidth =
|
||||
ResolveUIEditorMenuPopupDesiredWidth(popupRequest.widgetItems, metrics.popupMetrics);
|
||||
const float popupHeight =
|
||||
MeasureUIEditorMenuPopupHeight(popupRequest.widgetItems, metrics.popupMetrics);
|
||||
popupRequest.placement = ResolvePopupPlacementRect(
|
||||
overlayEntry->anchorRect,
|
||||
UISize(popupWidth, popupHeight),
|
||||
request.shellRequest.layout.bounds,
|
||||
overlayEntry->placement);
|
||||
popupRequest.layout =
|
||||
BuildUIEditorMenuPopupLayout(popupRequest.placement.rect, popupRequest.widgetItems, metrics.popupMetrics);
|
||||
|
||||
popupRequest.itemRequests.reserve(popupRequest.resolvedItems.size());
|
||||
for (std::size_t index = 0; index < popupRequest.resolvedItems.size(); ++index) {
|
||||
const UIEditorResolvedMenuItem& resolvedItem = popupRequest.resolvedItems[index];
|
||||
UIEditorShellInteractionPopupItemRequest itemRequest = {};
|
||||
itemRequest.popupId = popupRequest.popupId;
|
||||
itemRequest.menuId = popupRequest.menuId;
|
||||
itemRequest.itemId = resolvedItem.itemId;
|
||||
itemRequest.label = resolvedItem.label;
|
||||
itemRequest.commandId = resolvedItem.commandId;
|
||||
itemRequest.kind = resolvedItem.kind;
|
||||
itemRequest.enabled = resolvedItem.enabled;
|
||||
itemRequest.checked = resolvedItem.checked;
|
||||
itemRequest.hasSubmenu =
|
||||
resolvedItem.kind == UIEditorMenuItemKind::Submenu &&
|
||||
!resolvedItem.children.empty();
|
||||
if (itemRequest.hasSubmenu) {
|
||||
itemRequest.childPopupId =
|
||||
BuildSubmenuPopupId(popupRequest.popupId, itemRequest.itemId);
|
||||
}
|
||||
if (index < popupRequest.layout.itemRects.size()) {
|
||||
itemRequest.rect = popupRequest.layout.itemRects[index];
|
||||
}
|
||||
itemRequest.path = BuildMenuItemPath(popupRequest.popupId, itemRequest.itemId);
|
||||
popupRequest.itemRequests.push_back(std::move(itemRequest));
|
||||
}
|
||||
|
||||
request.popupRequests.push_back(std::move(popupRequest));
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
RequestHit HitTestRequest(
|
||||
const UIEditorShellInteractionRequest& request,
|
||||
const UIPoint& point,
|
||||
bool hasPointerPosition) {
|
||||
RequestHit hit = {};
|
||||
if (!hasPointerPosition) {
|
||||
return hit;
|
||||
}
|
||||
|
||||
for (std::size_t index = request.popupRequests.size(); index > 0u; --index) {
|
||||
const UIEditorShellInteractionPopupRequest& popupRequest =
|
||||
request.popupRequests[index - 1u];
|
||||
const auto popupHit =
|
||||
HitTestUIEditorMenuPopup(popupRequest.layout, popupRequest.widgetItems, point);
|
||||
if (popupHit.kind == UIEditorMenuPopupHitTargetKind::Item &&
|
||||
popupHit.index < popupRequest.itemRequests.size()) {
|
||||
hit.popupRequest = &popupRequest;
|
||||
hit.popupItem = &popupRequest.itemRequests[popupHit.index];
|
||||
return hit;
|
||||
}
|
||||
if (popupHit.kind == UIEditorMenuPopupHitTargetKind::PopupSurface) {
|
||||
hit.popupRequest = &popupRequest;
|
||||
return hit;
|
||||
}
|
||||
}
|
||||
|
||||
const auto menuHit =
|
||||
HitTestUIEditorMenuBar(request.shellRequest.layout.menuBarLayout, point);
|
||||
if (menuHit.kind == UIEditorMenuBarHitTargetKind::Button &&
|
||||
menuHit.index < request.menuButtons.size()) {
|
||||
hit.menuButton = &request.menuButtons[menuHit.index];
|
||||
}
|
||||
|
||||
return hit;
|
||||
}
|
||||
|
||||
UIPopupOverlayEntry BuildRootPopupEntry(
|
||||
const UIEditorShellInteractionMenuButtonRequest& button) {
|
||||
UIPopupOverlayEntry entry = {};
|
||||
entry.popupId = button.popupId;
|
||||
entry.anchorRect = button.rect;
|
||||
entry.anchorPath = button.path;
|
||||
entry.surfacePath = BuildPopupSurfacePath(button.popupId);
|
||||
entry.placement = UIPopupPlacement::BottomStart;
|
||||
return entry;
|
||||
}
|
||||
|
||||
UIPopupOverlayEntry BuildSubmenuPopupEntry(
|
||||
const UIEditorShellInteractionPopupItemRequest& item) {
|
||||
UIPopupOverlayEntry entry = {};
|
||||
entry.popupId = item.childPopupId;
|
||||
entry.parentPopupId = item.popupId;
|
||||
entry.anchorRect = item.rect;
|
||||
entry.anchorPath = item.path;
|
||||
entry.surfacePath = BuildPopupSurfacePath(item.childPopupId);
|
||||
entry.placement = UIPopupPlacement::RightStart;
|
||||
return entry;
|
||||
}
|
||||
|
||||
void UpdateMenuBarVisualState(
|
||||
UIEditorShellInteractionState& state,
|
||||
const UIEditorShellInteractionRequest& request,
|
||||
const RequestHit& hit) {
|
||||
state.composeState.menuBarState.openIndex = FindMenuBarIndex(
|
||||
request.menuBarItems,
|
||||
state.menuSession.GetOpenRootMenuId());
|
||||
state.composeState.menuBarState.hoveredIndex =
|
||||
hit.menuButton != nullptr
|
||||
? FindMenuBarIndex(request.menuBarItems, hit.menuButton->menuId)
|
||||
: UIEditorMenuBarInvalidIndex;
|
||||
state.composeState.menuBarState.focused =
|
||||
state.focused || state.menuSession.HasOpenMenu();
|
||||
}
|
||||
|
||||
std::vector<UIEditorShellInteractionPopupFrame> BuildPopupFrames(
|
||||
const UIEditorShellInteractionRequest& request,
|
||||
const UIEditorShellInteractionState& state,
|
||||
std::string_view hoveredPopupId,
|
||||
std::string_view hoveredItemId) {
|
||||
std::vector<UIEditorShellInteractionPopupFrame> popupFrames = {};
|
||||
popupFrames.reserve(request.popupRequests.size());
|
||||
|
||||
for (std::size_t index = 0; index < request.popupRequests.size(); ++index) {
|
||||
const UIEditorShellInteractionPopupRequest& popupRequest =
|
||||
request.popupRequests[index];
|
||||
UIEditorShellInteractionPopupFrame popupFrame = {};
|
||||
popupFrame.popupId = popupRequest.popupId;
|
||||
popupFrame.popupState.focused = state.focused || state.menuSession.HasOpenMenu();
|
||||
popupFrame.popupState.hoveredIndex =
|
||||
popupRequest.popupId == hoveredPopupId
|
||||
? FindPopupItemIndex(popupRequest.itemRequests, hoveredItemId)
|
||||
: UIEditorMenuPopupInvalidIndex;
|
||||
if (index + 1u < request.popupRequests.size()) {
|
||||
popupFrame.popupState.submenuOpenIndex = FindPopupItemIndex(
|
||||
popupRequest.itemRequests,
|
||||
request.popupRequests[index + 1u].sourceItemId);
|
||||
}
|
||||
popupFrames.push_back(std::move(popupFrame));
|
||||
}
|
||||
|
||||
return popupFrames;
|
||||
}
|
||||
|
||||
bool ShouldUsePointerPosition(const UIInputEvent& event) {
|
||||
switch (event.type) {
|
||||
case UIInputEventType::PointerMove:
|
||||
case UIInputEventType::PointerEnter:
|
||||
case UIInputEventType::PointerButtonDown:
|
||||
case UIInputEventType::PointerButtonUp:
|
||||
case UIInputEventType::PointerWheel:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<UIInputEvent> FilterComposeInputEvents(
|
||||
const std::vector<UIInputEvent>& inputEvents,
|
||||
bool menuModalDuringFrame) {
|
||||
if (!menuModalDuringFrame) {
|
||||
return inputEvents;
|
||||
}
|
||||
|
||||
std::vector<UIInputEvent> filtered = {};
|
||||
for (const UIInputEvent& event : inputEvents) {
|
||||
if (event.type == UIInputEventType::FocusGained ||
|
||||
event.type == UIInputEventType::FocusLost) {
|
||||
filtered.push_back(event);
|
||||
}
|
||||
}
|
||||
|
||||
return filtered;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
UIEditorShellInteractionRequest ResolveUIEditorShellInteractionRequest(
|
||||
const UIRect& bounds,
|
||||
const UIEditorPanelRegistry& panelRegistry,
|
||||
const UIEditorWorkspaceModel& workspace,
|
||||
const UIEditorWorkspaceSession& session,
|
||||
const UIEditorShellInteractionModel& model,
|
||||
const Widgets::UIEditorDockHostState& dockHostState,
|
||||
const UIEditorShellInteractionState& state,
|
||||
const UIEditorShellInteractionMetrics& metrics) {
|
||||
return BuildRequest(
|
||||
bounds,
|
||||
panelRegistry,
|
||||
workspace,
|
||||
session,
|
||||
model,
|
||||
dockHostState,
|
||||
state,
|
||||
metrics).request;
|
||||
}
|
||||
|
||||
UIEditorShellInteractionFrame UpdateUIEditorShellInteraction(
|
||||
UIEditorShellInteractionState& state,
|
||||
const UIRect& bounds,
|
||||
const UIEditorPanelRegistry& panelRegistry,
|
||||
const UIEditorWorkspaceModel& workspace,
|
||||
const UIEditorWorkspaceSession& session,
|
||||
const UIEditorShellInteractionModel& model,
|
||||
const std::vector<UIInputEvent>& inputEvents,
|
||||
const Widgets::UIEditorDockHostState& dockHostState,
|
||||
const UIEditorShellInteractionMetrics& metrics) {
|
||||
UIEditorShellInteractionResult interactionResult = {};
|
||||
bool menuModalDuringFrame = state.menuSession.HasOpenMenu();
|
||||
|
||||
BuildRequestOutput requestBuild = BuildRequest(
|
||||
bounds,
|
||||
panelRegistry,
|
||||
workspace,
|
||||
session,
|
||||
model,
|
||||
dockHostState,
|
||||
state,
|
||||
metrics);
|
||||
UIEditorShellInteractionRequest request = std::move(requestBuild.request);
|
||||
|
||||
if (requestBuild.hadInvalidPopupState && state.menuSession.HasOpenMenu()) {
|
||||
interactionResult.menuMutation = state.menuSession.CloseAll();
|
||||
interactionResult.consumed = interactionResult.menuMutation.changed;
|
||||
menuModalDuringFrame =
|
||||
interactionResult.menuMutation.changed || state.menuSession.HasOpenMenu();
|
||||
|
||||
requestBuild = BuildRequest(
|
||||
bounds,
|
||||
panelRegistry,
|
||||
workspace,
|
||||
session,
|
||||
model,
|
||||
dockHostState,
|
||||
state,
|
||||
metrics);
|
||||
request = std::move(requestBuild.request);
|
||||
}
|
||||
|
||||
for (const UIInputEvent& event : inputEvents) {
|
||||
UIEditorShellInteractionResult eventResult = {};
|
||||
|
||||
if (ShouldUsePointerPosition(event)) {
|
||||
state.pointerPosition = event.position;
|
||||
state.hasPointerPosition = true;
|
||||
} else if (event.type == UIInputEventType::PointerLeave) {
|
||||
state.hasPointerPosition = false;
|
||||
}
|
||||
|
||||
const RequestHit hit =
|
||||
HitTestRequest(request, state.pointerPosition, state.hasPointerPosition);
|
||||
|
||||
switch (event.type) {
|
||||
case UIInputEventType::FocusGained:
|
||||
state.focused = true;
|
||||
break;
|
||||
|
||||
case UIInputEventType::FocusLost:
|
||||
state.focused = false;
|
||||
if (state.menuSession.HasOpenMenu()) {
|
||||
eventResult.menuMutation =
|
||||
state.menuSession.DismissFromFocusLoss({});
|
||||
eventResult.consumed = eventResult.menuMutation.changed;
|
||||
}
|
||||
break;
|
||||
|
||||
case UIInputEventType::PointerMove:
|
||||
case UIInputEventType::PointerEnter:
|
||||
if (state.menuSession.HasOpenMenu()) {
|
||||
if (hit.menuButton != nullptr && hit.menuButton->enabled) {
|
||||
eventResult.menuId = hit.menuButton->menuId;
|
||||
if (!state.menuSession.IsMenuOpen(hit.menuButton->menuId)) {
|
||||
eventResult.menuMutation =
|
||||
state.menuSession.HoverMenuBarRoot(
|
||||
hit.menuButton->menuId,
|
||||
BuildRootPopupEntry(*hit.menuButton));
|
||||
} else {
|
||||
eventResult.menuMutation =
|
||||
state.menuSession.DismissFromFocusLoss(hit.menuButton->path);
|
||||
}
|
||||
} else if (hit.popupItem != nullptr) {
|
||||
eventResult.menuId = hit.popupItem->menuId;
|
||||
eventResult.popupId = hit.popupItem->popupId;
|
||||
eventResult.itemId = hit.popupItem->itemId;
|
||||
if (hit.popupItem->hasSubmenu && hit.popupItem->enabled) {
|
||||
eventResult.menuMutation =
|
||||
state.menuSession.HoverSubmenu(
|
||||
hit.popupItem->itemId,
|
||||
BuildSubmenuPopupEntry(*hit.popupItem));
|
||||
} else {
|
||||
eventResult.menuMutation =
|
||||
state.menuSession.DismissFromFocusLoss(hit.popupItem->path);
|
||||
}
|
||||
} else if (hit.popupRequest != nullptr) {
|
||||
eventResult.menuId = hit.popupRequest->menuId;
|
||||
eventResult.popupId = hit.popupRequest->popupId;
|
||||
eventResult.menuMutation =
|
||||
state.menuSession.DismissFromFocusLoss(
|
||||
hit.popupRequest->overlayEntry.surfacePath);
|
||||
}
|
||||
|
||||
if (eventResult.menuMutation.changed) {
|
||||
eventResult.consumed = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case UIInputEventType::PointerButtonDown:
|
||||
if (event.pointerButton != ::XCEngine::UI::UIPointerButton::Left) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (hit.menuButton != nullptr && hit.menuButton->enabled) {
|
||||
state.focused = true;
|
||||
eventResult.consumed = true;
|
||||
eventResult.menuId = hit.menuButton->menuId;
|
||||
if (state.menuSession.IsMenuOpen(hit.menuButton->menuId)) {
|
||||
eventResult.menuMutation = state.menuSession.CloseAll();
|
||||
} else {
|
||||
eventResult.menuMutation =
|
||||
state.menuSession.OpenMenuBarRoot(
|
||||
hit.menuButton->menuId,
|
||||
BuildRootPopupEntry(*hit.menuButton));
|
||||
}
|
||||
} else if (hit.popupItem != nullptr) {
|
||||
state.focused = true;
|
||||
eventResult.consumed = true;
|
||||
eventResult.menuId = hit.popupItem->menuId;
|
||||
eventResult.popupId = hit.popupItem->popupId;
|
||||
eventResult.itemId = hit.popupItem->itemId;
|
||||
if (hit.popupItem->hasSubmenu && hit.popupItem->enabled) {
|
||||
eventResult.menuMutation =
|
||||
state.menuSession.HoverSubmenu(
|
||||
hit.popupItem->itemId,
|
||||
BuildSubmenuPopupEntry(*hit.popupItem));
|
||||
} else if (hit.popupItem->enabled) {
|
||||
eventResult.commandTriggered = true;
|
||||
eventResult.commandId = hit.popupItem->commandId;
|
||||
eventResult.menuMutation = state.menuSession.CloseAll();
|
||||
} else {
|
||||
eventResult.menuMutation =
|
||||
state.menuSession.DismissFromPointerDown(hit.popupItem->path);
|
||||
}
|
||||
} else if (hit.popupRequest != nullptr) {
|
||||
eventResult.consumed = true;
|
||||
eventResult.menuId = hit.popupRequest->menuId;
|
||||
eventResult.popupId = hit.popupRequest->popupId;
|
||||
eventResult.menuMutation =
|
||||
state.menuSession.DismissFromPointerDown(
|
||||
hit.popupRequest->overlayEntry.surfacePath);
|
||||
} else if (state.menuSession.HasOpenMenu()) {
|
||||
eventResult.consumed = true;
|
||||
eventResult.menuMutation =
|
||||
state.menuSession.DismissFromPointerDown(UIInputPath { kOutsidePointerPath });
|
||||
}
|
||||
break;
|
||||
|
||||
case UIInputEventType::KeyDown:
|
||||
if (event.keyCode == static_cast<std::int32_t>(KeyCode::Escape) &&
|
||||
state.menuSession.HasOpenMenu()) {
|
||||
eventResult.consumed = true;
|
||||
eventResult.menuMutation = state.menuSession.DismissFromEscape();
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (HasMeaningfulInteractionResult(eventResult)) {
|
||||
interactionResult = std::move(eventResult);
|
||||
}
|
||||
|
||||
if (interactionResult.menuMutation.changed || state.menuSession.HasOpenMenu()) {
|
||||
menuModalDuringFrame = true;
|
||||
request = BuildRequest(
|
||||
bounds,
|
||||
panelRegistry,
|
||||
workspace,
|
||||
session,
|
||||
model,
|
||||
dockHostState,
|
||||
state,
|
||||
metrics).request;
|
||||
}
|
||||
}
|
||||
|
||||
const RequestHit finalHit =
|
||||
HitTestRequest(request, state.pointerPosition, state.hasPointerPosition);
|
||||
UpdateMenuBarVisualState(state, request, finalHit);
|
||||
|
||||
const UIEditorShellComposeModel shellModel =
|
||||
BuildShellComposeModel(model, request.menuBarItems);
|
||||
const std::vector<UIInputEvent> composeInputEvents =
|
||||
FilterComposeInputEvents(inputEvents, menuModalDuringFrame);
|
||||
|
||||
UIEditorShellInteractionFrame frame = {};
|
||||
frame.request = request;
|
||||
frame.shellFrame = UpdateUIEditorShellCompose(
|
||||
state.composeState,
|
||||
bounds,
|
||||
panelRegistry,
|
||||
workspace,
|
||||
session,
|
||||
shellModel,
|
||||
composeInputEvents,
|
||||
dockHostState,
|
||||
metrics.shellMetrics);
|
||||
frame.popupFrames = BuildPopupFrames(
|
||||
frame.request,
|
||||
state,
|
||||
finalHit.popupRequest != nullptr ? finalHit.popupRequest->popupId : std::string_view(),
|
||||
finalHit.popupItem != nullptr ? finalHit.popupItem->itemId : std::string_view());
|
||||
frame.result = interactionResult;
|
||||
frame.openRootMenuId = std::string(state.menuSession.GetOpenRootMenuId());
|
||||
frame.hoveredMenuId =
|
||||
finalHit.menuButton != nullptr ? finalHit.menuButton->menuId : std::string();
|
||||
frame.hoveredPopupId =
|
||||
finalHit.popupRequest != nullptr ? finalHit.popupRequest->popupId : std::string();
|
||||
frame.hoveredItemId =
|
||||
finalHit.popupItem != nullptr ? finalHit.popupItem->itemId : std::string();
|
||||
frame.focused = state.focused || state.menuSession.HasOpenMenu();
|
||||
return frame;
|
||||
}
|
||||
|
||||
void AppendUIEditorShellInteraction(
|
||||
::XCEngine::UI::UIDrawList& drawList,
|
||||
const UIEditorShellInteractionFrame& frame,
|
||||
const UIEditorShellInteractionModel& model,
|
||||
const UIEditorShellInteractionState& state,
|
||||
const UIEditorShellInteractionPalette& palette,
|
||||
const UIEditorShellInteractionMetrics& metrics) {
|
||||
const UIEditorShellComposeModel shellModel =
|
||||
BuildShellComposeModel(model, frame.request.menuBarItems);
|
||||
AppendUIEditorShellCompose(
|
||||
drawList,
|
||||
frame.shellFrame,
|
||||
shellModel,
|
||||
state.composeState,
|
||||
palette.shellPalette,
|
||||
metrics.shellMetrics);
|
||||
|
||||
const std::size_t popupCount =
|
||||
(std::min)(frame.request.popupRequests.size(), frame.popupFrames.size());
|
||||
for (std::size_t index = 0; index < popupCount; ++index) {
|
||||
const UIEditorShellInteractionPopupRequest& popupRequest =
|
||||
frame.request.popupRequests[index];
|
||||
const UIEditorShellInteractionPopupFrame& popupFrame =
|
||||
frame.popupFrames[index];
|
||||
AppendUIEditorMenuPopupBackground(
|
||||
drawList,
|
||||
popupRequest.layout,
|
||||
popupRequest.widgetItems,
|
||||
popupFrame.popupState,
|
||||
palette.popupPalette,
|
||||
metrics.popupMetrics);
|
||||
AppendUIEditorMenuPopupForeground(
|
||||
drawList,
|
||||
popupRequest.layout,
|
||||
popupRequest.widgetItems,
|
||||
popupFrame.popupState,
|
||||
palette.popupPalette,
|
||||
metrics.popupMetrics);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace XCEngine::UI::Editor
|
||||
Reference in New Issue
Block a user