#include #include #include namespace XCEngine::UI::Editor { namespace { bool AreEquivalentPopupEntries( const ::XCEngine::UI::Widgets::UIPopupOverlayEntry& lhs, const ::XCEngine::UI::Widgets::UIPopupOverlayEntry& rhs) { return lhs.popupId == rhs.popupId && lhs.parentPopupId == rhs.parentPopupId && lhs.anchorRect.x == rhs.anchorRect.x && lhs.anchorRect.y == rhs.anchorRect.y && lhs.anchorRect.width == rhs.anchorRect.width && lhs.anchorRect.height == rhs.anchorRect.height && lhs.anchorPath == rhs.anchorPath && lhs.surfacePath == rhs.surfacePath && lhs.placement == rhs.placement && lhs.dismissOnPointerOutside == rhs.dismissOnPointerOutside && lhs.dismissOnEscape == rhs.dismissOnEscape && lhs.dismissOnFocusLoss == rhs.dismissOnFocusLoss; } } // namespace bool UIEditorMenuSession::IsPopupOpen(std::string_view popupId) const { return m_popupOverlayModel.FindPopup(popupId) != nullptr; } const UIEditorMenuPopupState* UIEditorMenuSession::FindPopupState( std::string_view popupId) const { for (const UIEditorMenuPopupState& state : m_popupStates) { if (state.popupId == popupId) { return &state; } } return nullptr; } void UIEditorMenuSession::Reset() { m_openRootMenuId.clear(); m_popupOverlayModel = {}; m_popupStates.clear(); m_openSubmenuItemIds.clear(); } UIEditorMenuSessionMutationResult UIEditorMenuSession::OpenMenuBarRoot( std::string_view menuId, ::XCEngine::UI::Widgets::UIPopupOverlayEntry entry) { return OpenRootMenu(menuId, std::move(entry)); } UIEditorMenuSessionMutationResult UIEditorMenuSession::OpenRootMenu( std::string_view menuId, ::XCEngine::UI::Widgets::UIPopupOverlayEntry entry) { if (menuId.empty() || entry.popupId.empty()) { return BuildResult({}); } const ::XCEngine::UI::Widgets::UIPopupOverlayEntry* rootPopup = m_popupOverlayModel.GetRootPopup(); if (rootPopup != nullptr && m_openRootMenuId == menuId && AreEquivalentPopupEntries(*rootPopup, entry)) { return BuildResult({}); } entry.parentPopupId.clear(); const ::XCEngine::UI::Widgets::UIPopupOverlayMutationResult mutation = m_popupOverlayModel.OpenPopup(std::move(entry)); if (mutation.changed) { m_popupStates.clear(); UIEditorMenuPopupState rootState = {}; rootState.popupId = mutation.openedPopupId; rootState.menuId = std::string(menuId); m_popupStates.push_back(std::move(rootState)); RebuildDerivedState(); } return BuildResult(mutation); } UIEditorMenuSessionMutationResult UIEditorMenuSession::HoverMenuBarRoot( std::string_view menuId, ::XCEngine::UI::Widgets::UIPopupOverlayEntry entry) { if (!HasOpenMenu() || IsMenuOpen(menuId)) { return BuildResult({}); } return OpenRootMenu(menuId, std::move(entry)); } UIEditorMenuSessionMutationResult UIEditorMenuSession::HoverSubmenu( std::string_view itemId, ::XCEngine::UI::Widgets::UIPopupOverlayEntry entry) { if (!HasOpenMenu() || itemId.empty() || entry.popupId.empty() || entry.parentPopupId.empty() || m_popupOverlayModel.FindPopup(entry.parentPopupId) == nullptr) { return BuildResult({}); } const ::XCEngine::UI::Widgets::UIPopupOverlayEntry* existingPopup = m_popupOverlayModel.FindPopup(entry.popupId); if (existingPopup != nullptr && existingPopup->parentPopupId == entry.parentPopupId) { return BuildResult({}); } const ::XCEngine::UI::Widgets::UIPopupOverlayMutationResult mutation = m_popupOverlayModel.OpenPopup(std::move(entry)); if (mutation.changed) { RemoveClosedPopupStates(mutation.closedPopupIds); UIEditorMenuPopupState popupState = {}; popupState.popupId = mutation.openedPopupId; popupState.menuId = m_openRootMenuId; popupState.itemId = std::string(itemId); m_popupStates.push_back(std::move(popupState)); RebuildDerivedState(); } return BuildResult(mutation); } UIEditorMenuSessionMutationResult UIEditorMenuSession::CloseAll( ::XCEngine::UI::Widgets::UIPopupDismissReason dismissReason) { const ::XCEngine::UI::Widgets::UIPopupOverlayMutationResult mutation = m_popupOverlayModel.CloseAll(dismissReason); if (mutation.changed) { RemoveClosedPopupStates(mutation.closedPopupIds); RebuildDerivedState(); } return BuildResult(mutation); } UIEditorMenuSessionMutationResult UIEditorMenuSession::DismissFromEscape() { const ::XCEngine::UI::Widgets::UIPopupOverlayMutationResult mutation = m_popupOverlayModel.DismissFromEscape(); if (mutation.changed) { RemoveClosedPopupStates(mutation.closedPopupIds); RebuildDerivedState(); } return BuildResult(mutation); } UIEditorMenuSessionMutationResult UIEditorMenuSession::DismissFromPointerDown( const UIInputPath& hitPath) { const ::XCEngine::UI::Widgets::UIPopupOverlayMutationResult mutation = m_popupOverlayModel.DismissFromPointerDown(hitPath); if (mutation.changed) { RemoveClosedPopupStates(mutation.closedPopupIds); RebuildDerivedState(); } return BuildResult(mutation); } UIEditorMenuSessionMutationResult UIEditorMenuSession::DismissFromFocusLoss( const UIInputPath& focusedPath) { const ::XCEngine::UI::Widgets::UIPopupOverlayMutationResult mutation = m_popupOverlayModel.DismissFromFocusLoss(focusedPath); if (mutation.changed) { RemoveClosedPopupStates(mutation.closedPopupIds); RebuildDerivedState(); } return BuildResult(mutation); } UIEditorMenuSessionMutationResult UIEditorMenuSession::BuildResult( const ::XCEngine::UI::Widgets::UIPopupOverlayMutationResult& mutation) const { UIEditorMenuSessionMutationResult result = {}; result.changed = mutation.changed; result.openRootMenuId = m_openRootMenuId; result.openedPopupId = mutation.openedPopupId; result.closedPopupIds = mutation.closedPopupIds; result.dismissReason = mutation.dismissReason; return result; } void UIEditorMenuSession::RemoveClosedPopupStates( const std::vector& closedPopupIds) { if (closedPopupIds.empty()) { return; } std::erase_if( m_popupStates, [&closedPopupIds](const UIEditorMenuPopupState& state) { return std::find( closedPopupIds.begin(), closedPopupIds.end(), state.popupId) != closedPopupIds.end(); }); } void UIEditorMenuSession::RebuildDerivedState() { m_openSubmenuItemIds.clear(); if (m_popupStates.empty() || !m_popupOverlayModel.HasOpenPopups()) { m_openRootMenuId.clear(); return; } m_openRootMenuId = m_popupStates.front().menuId; for (std::size_t index = 1u; index < m_popupStates.size(); ++index) { if (!m_popupStates[index].itemId.empty()) { m_openSubmenuItemIds.push_back(m_popupStates[index].itemId); } } } } // namespace XCEngine::UI::Editor