Files
XCEngine/editor/src/Menu/UIEditorMenuSession.cpp
2026-04-25 16:46:01 +08:00

223 lines
7.1 KiB
C++

#include <XCEditor/Menu/UIEditorMenuSession.h>
#include <algorithm>
#include <utility>
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<std::string>& 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