Model UI overlay lifetime in ui core

This commit is contained in:
2026-06-06 09:48:00 +02:00
parent 4071919124
commit 3101e65dd3
9 changed files with 483 additions and 7 deletions

View File

@@ -0,0 +1,173 @@
#include "ui_core/overlay_lifetime.h"
#include <algorithm>
namespace pp::ui {
UiOverlayLifetime::UiOverlayLifetime(NodeLifetimeTree& tree, NodeHandle root) noexcept
: tree_(tree)
, root_(root)
{
}
pp::foundation::Result<NodeHandle> UiOverlayLifetime::open_popup()
{
return open_overlay(root_, UiOverlayKind::popup, true, false);
}
pp::foundation::Result<NodeHandle> UiOverlayLifetime::open_child_popup(NodeHandle parent_popup)
{
if (!tracked_alive(parent_popup)) {
return pp::foundation::Result<NodeHandle>::failure(
pp::foundation::Status::invalid_argument("child popup requires a live tracked parent popup"));
}
return open_overlay(parent_popup, UiOverlayKind::popup, true, false);
}
pp::foundation::Result<NodeHandle> UiOverlayLifetime::open_dialog(bool modal)
{
return open_overlay(root_, UiOverlayKind::dialog, modal, modal);
}
pp::foundation::Status UiOverlayLifetime::close(NodeHandle overlay) noexcept
{
if (!tracked_alive(overlay)) {
return pp::foundation::Status::invalid_argument("overlay close requires a live tracked overlay");
}
const auto status = tree_.destroy_subtree(overlay);
if (!status.ok()) {
return status;
}
prune_dead_entries();
restore_capture(UiCaptureKind::pointer);
restore_capture(UiCaptureKind::keyboard);
return pp::foundation::Status::success();
}
void UiOverlayLifetime::clear_for_layout_reload() noexcept
{
tree_.clear();
entries_.clear();
}
bool UiOverlayLifetime::tracks(NodeHandle overlay) const noexcept
{
return tracked_alive(overlay);
}
std::size_t UiOverlayLifetime::overlay_count() const noexcept
{
std::size_t count = 0;
for (const auto& entry : entries_) {
if (tree_.contains(entry.node)) {
++count;
}
}
return count;
}
pp::foundation::Result<NodeHandle> UiOverlayLifetime::top_overlay() const noexcept
{
for (auto index = entries_.size(); index > 0U; --index) {
const auto& entry = entries_[index - 1U];
if (tree_.contains(entry.node)) {
return pp::foundation::Result<NodeHandle>::success(entry.node);
}
}
return pp::foundation::Result<NodeHandle>::failure(
pp::foundation::Status::invalid_argument("overlay stack is empty"));
}
pp::foundation::Result<NodeHandle> UiOverlayLifetime::open_overlay(
NodeHandle parent,
UiOverlayKind kind,
bool captures_pointer,
bool captures_keyboard)
{
if (!tree_.contains(root_)) {
return pp::foundation::Result<NodeHandle>::failure(
pp::foundation::Status::invalid_argument("overlay root is not live"));
}
if (!tree_.contains(parent)) {
return pp::foundation::Result<NodeHandle>::failure(
pp::foundation::Status::invalid_argument("overlay parent is not live"));
}
auto node = tree_.create_child(parent);
if (!node) {
return node;
}
if (captures_pointer) {
const auto status = tree_.capture(UiCaptureKind::pointer, node.value());
if (!status.ok()) {
(void)tree_.destroy_subtree(node.value());
return pp::foundation::Result<NodeHandle>::failure(status);
}
}
if (captures_keyboard) {
const auto status = tree_.capture(UiCaptureKind::keyboard, node.value());
if (!status.ok()) {
(void)tree_.destroy_subtree(node.value());
return pp::foundation::Result<NodeHandle>::failure(status);
}
}
entries_.push_back(UiOverlayEntry {
.node = node.value(),
.parent = parent,
.kind = kind,
.captures_pointer = captures_pointer,
.captures_keyboard = captures_keyboard,
});
return node;
}
void UiOverlayLifetime::prune_dead_entries() noexcept
{
entries_.erase(
std::remove_if(
entries_.begin(),
entries_.end(),
[this](const UiOverlayEntry& entry) {
return !tree_.contains(entry.node);
}),
entries_.end());
}
void UiOverlayLifetime::restore_capture(UiCaptureKind kind) noexcept
{
for (auto index = entries_.size(); index > 0U; --index) {
const auto& entry = entries_[index - 1U];
const auto captures = kind == UiCaptureKind::pointer
? entry.captures_pointer
: entry.captures_keyboard;
if (captures && tree_.contains(entry.node)) {
(void)tree_.capture(kind, entry.node);
return;
}
}
}
bool UiOverlayLifetime::tracked_alive(NodeHandle overlay) const noexcept
{
if (!tree_.contains(overlay)) {
return false;
}
return std::any_of(
entries_.begin(),
entries_.end(),
[overlay](const UiOverlayEntry& entry) {
return entry.node == overlay;
});
}
}

View File

@@ -0,0 +1,54 @@
#pragma once
#include "foundation/result.h"
#include "ui_core/node_lifetime.h"
#include <cstdint>
#include <cstddef>
#include <vector>
namespace pp::ui {
enum class UiOverlayKind : std::uint8_t {
popup,
dialog,
};
struct UiOverlayEntry {
NodeHandle node {};
NodeHandle parent {};
UiOverlayKind kind = UiOverlayKind::popup;
bool captures_pointer = false;
bool captures_keyboard = false;
};
class UiOverlayLifetime {
public:
UiOverlayLifetime(NodeLifetimeTree& tree, NodeHandle root) noexcept;
[[nodiscard]] pp::foundation::Result<NodeHandle> open_popup();
[[nodiscard]] pp::foundation::Result<NodeHandle> open_child_popup(NodeHandle parent_popup);
[[nodiscard]] pp::foundation::Result<NodeHandle> open_dialog(bool modal);
[[nodiscard]] pp::foundation::Status close(NodeHandle overlay) noexcept;
void clear_for_layout_reload() noexcept;
[[nodiscard]] bool tracks(NodeHandle overlay) const noexcept;
[[nodiscard]] std::size_t overlay_count() const noexcept;
[[nodiscard]] pp::foundation::Result<NodeHandle> top_overlay() const noexcept;
private:
[[nodiscard]] pp::foundation::Result<NodeHandle> open_overlay(
NodeHandle parent,
UiOverlayKind kind,
bool captures_pointer,
bool captures_keyboard);
void prune_dead_entries() noexcept;
void restore_capture(UiCaptureKind kind) noexcept;
[[nodiscard]] bool tracked_alive(NodeHandle overlay) const noexcept;
NodeLifetimeTree& tree_;
NodeHandle root_ {};
std::vector<UiOverlayEntry> entries_;
};
}