Add core popup overlay primitive

This commit is contained in:
2026-04-06 22:32:40 +08:00
parent 9568cf0a16
commit 620717f8b4
16 changed files with 1261 additions and 4 deletions

View File

@@ -0,0 +1,113 @@
#pragma once
#include <XCEngine/UI/Input/UIInputPath.h>
#include <XCEngine/UI/Types.h>
#include <cstddef>
#include <cstdint>
#include <string>
#include <string_view>
#include <vector>
namespace XCEngine {
namespace UI {
namespace Widgets {
enum class UIPopupPlacement : std::uint8_t {
BottomStart = 0,
BottomEnd,
TopStart,
TopEnd,
RightStart,
RightEnd,
LeftStart,
LeftEnd
};
enum class UIPopupDismissReason : std::uint8_t {
None = 0,
Programmatic,
EscapeKey,
PointerOutside,
FocusLoss
};
struct UIPopupPlacementResult {
UIRect rect = {};
UIPopupPlacement effectivePlacement = UIPopupPlacement::BottomStart;
bool clampedX = false;
bool clampedY = false;
};
struct UIPopupOverlayEntry {
std::string popupId = {};
std::string parentPopupId = {};
UIRect anchorRect = {};
UIInputPath anchorPath = {};
UIInputPath surfacePath = {};
UIPopupPlacement placement = UIPopupPlacement::BottomStart;
bool dismissOnPointerOutside = true;
bool dismissOnEscape = true;
bool dismissOnFocusLoss = true;
};
struct UIPopupOverlayMutationResult {
bool changed = false;
std::string openedPopupId = {};
std::vector<std::string> closedPopupIds = {};
UIPopupDismissReason dismissReason = UIPopupDismissReason::None;
};
UIPopupPlacementResult ResolvePopupPlacementRect(
const UIRect& anchorRect,
const UISize& popupSize,
const UIRect& viewportRect,
UIPopupPlacement placement);
class UIPopupOverlayModel {
public:
static constexpr std::size_t InvalidIndex = static_cast<std::size_t>(-1);
const std::vector<UIPopupOverlayEntry>& GetPopupChain() const {
return m_popupChain;
}
bool HasOpenPopups() const {
return !m_popupChain.empty();
}
std::size_t GetPopupCount() const {
return m_popupChain.size();
}
const UIPopupOverlayEntry* GetRootPopup() const;
const UIPopupOverlayEntry* GetTopmostPopup() const;
const UIPopupOverlayEntry* FindPopup(std::string_view popupId) const;
UIPopupOverlayMutationResult OpenPopup(UIPopupOverlayEntry entry);
UIPopupOverlayMutationResult ClosePopup(
std::string_view popupId,
UIPopupDismissReason dismissReason = UIPopupDismissReason::Programmatic);
UIPopupOverlayMutationResult CloseAll(
UIPopupDismissReason dismissReason = UIPopupDismissReason::Programmatic);
UIPopupOverlayMutationResult DismissFromEscape();
UIPopupOverlayMutationResult DismissFromPointerDown(const UIInputPath& hitPath);
UIPopupOverlayMutationResult DismissFromFocusLoss(const UIInputPath& focusedPath);
private:
std::size_t FindPopupIndex(std::string_view popupId) const;
std::size_t FindDeepestContainingPopupIndex(const UIInputPath& path) const;
UIPopupOverlayMutationResult CloseFromIndex(
std::size_t index,
UIPopupDismissReason dismissReason);
static bool PopupContainsPath(
const UIPopupOverlayEntry& entry,
const UIInputPath& path);
std::vector<UIPopupOverlayEntry> m_popupChain = {};
};
} // namespace Widgets
} // namespace UI
} // namespace XCEngine