Model UI overlay lifetime in ui core
This commit is contained in:
@@ -221,7 +221,8 @@ add_library(pp_ui_core STATIC
|
||||
src/ui_core/color.cpp
|
||||
src/ui_core/layout_value.cpp
|
||||
src/ui_core/layout_xml.cpp
|
||||
src/ui_core/node_lifetime.cpp)
|
||||
src/ui_core/node_lifetime.cpp
|
||||
src/ui_core/overlay_lifetime.cpp)
|
||||
target_include_directories(pp_ui_core
|
||||
PUBLIC
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src")
|
||||
|
||||
@@ -737,8 +737,11 @@ powershell -ExecutionPolicy Bypass -File scripts\automation\apple-remote-build.p
|
||||
node handles, hidden parent/child invariants, scoped callback connections,
|
||||
pointer/keyboard capture ownership, mutation-safe event dispatch, whole-tree
|
||||
reload invalidation, and focused destroy-during-callback/layout-reload
|
||||
coverage in `pp_ui_core_node_lifetime_tests`. Retained `Node` still needs to
|
||||
adopt those semantics under DEBT-0063.
|
||||
coverage in `pp_ui_core_node_lifetime_tests`. It also owns a pure
|
||||
`UiOverlayLifetime` coordinator with root/nested popup, modal/modeless
|
||||
dialog, capture-restore, branch-close, and layout-reload coverage in
|
||||
`pp_ui_core_overlay_lifetime_tests`. Retained `Node`/popup/dialog code still
|
||||
needs to adopt those semantics under DEBT-0063.
|
||||
- `scripts/automation/analyze.*` runs shader validation plus a
|
||||
renderer-boundary guard that reports JSON and fails if active non-backend
|
||||
source code reintroduces raw `GL_*`/`WGL_*` constants outside the allowed
|
||||
|
||||
@@ -58,7 +58,7 @@ and validation command.
|
||||
| XML layout parsing | `LayoutManager`, `Node` | `pp_ui_core` | Layout fixtures and malformed XML |
|
||||
| Yoga layout | `Node` | `pp_ui_core` | Deterministic geometry fixtures |
|
||||
| Generic controls | `NodeButton`, sliders, text, images | `pp_ui_core` | Event dispatch, layout, ownership-handle, callback-disconnect, and destroy-during-callback tests |
|
||||
| PanoPainter panels/dialogs | `NodePanel*`, `NodeDialog*` | `pp_panopainter_ui` | UI automation scripts, command-dispatch view models, popup/dialog lifetime tests |
|
||||
| PanoPainter panels/dialogs | `NodePanel*`, `NodeDialog*` | `pp_panopainter_ui`, `pp_ui_core` | UI automation scripts, command-dispatch view models, pure overlay lifetime tests, retained popup/dialog lifetime tests |
|
||||
| Canvas viewport UI | `NodeCanvas` | `pp_panopainter_ui`, `pp_paint_renderer` | Input-to-command automation |
|
||||
| Settings UI | `Settings`, `NodeSettings` | `pp_assets`, `pp_panopainter_ui` | Round-trip settings |
|
||||
|
||||
|
||||
@@ -452,6 +452,11 @@ agent or engineer to remove them without reconstructing context from chat.
|
||||
a captured node is destroyed, and whole-tree `clear()` invalidation for layout
|
||||
reloads. Retained `Node` adoption and app-specific popup/dialog lifetime
|
||||
tests remain open.
|
||||
- 2026-06-06: DEBT-0063 was narrowed again. `pp_ui_core::UiOverlayLifetime`
|
||||
now models root and nested popup ownership, modal/modeless dialog capture
|
||||
policy, capture restoration after child/modal close, parent popup branch
|
||||
teardown, untracked close rejection, and layout-reload invalidation. Retained
|
||||
`NodePopupMenu`/`NodeDialog*` adoption remains open.
|
||||
- 2026-06-05: DEBT-0011 was narrowed. The Windows app package smoke target now
|
||||
passes the configure-time CMake executable into `package-smoke.ps1`, so VS
|
||||
2026 generator validation does not depend on an older `cmake` on PATH, and
|
||||
@@ -645,7 +650,7 @@ agent or engineer to remove them without reconstructing context from chat.
|
||||
| DEBT-0060 | Open | Modernization | Retained Android package CMake generates a patched `nanort.h` overlay in the build tree for `native-lib` instead of modifying the `libs/nanort` submodule | Current SDK Manager NDK/Clang rejects `TriangleSAHPred::operator=` assigning to a `const size_t` member, but the retained grid/lightmap path still includes `nanort` before that dependency is replaced or updated | `powershell -ExecutionPolicy Bypass -File scripts\automation\android-legacy-package-build.ps1 -Packages standard`; Quest/Focus retained package configure checks; Windows app build | Update/replace `nanort`, move grid/lightmap baking behind a component that owns its dependency, or retire the retained Android package CMake path so no generated vendor overlay is required |
|
||||
| DEBT-0061 | Open | Modernization | Desktop XR runtime selection now lives in tested `pp_platform_api` policy and prefers OpenXR, but `WindowsPlatformServices` still reports OpenXR unavailable and reaches the retained OpenVR SDK bridge as a legacy fallback; Windows runtime deployment copies `openvr_api.dll` beside `PanoPainter.exe` until that fallback is removed | Preserve current desktop VR behavior while replacing OpenVR with OpenXR behind the platform/renderer boundary | `pp_platform_api_tests`; `ctest --preset desktop-fast --build-config Debug -R pp_platform_api_tests --output-on-failure`; `cmake --build --preset windows-msvc-default --config Debug --target PanoPainter` | Add an OpenXR SDK/package target, implement desktop OpenXR startup/shutdown/pose/controller submission behind `pp_platform_vr` or `PlatformServices`, validate parity with mocked/runtime smoke coverage, and remove `libs/openvr` plus the OpenVR link/include paths from root CMake |
|
||||
| DEBT-0062 | Open | Modernization | VS 2026 builds generate a patched fmt `format.h` overlay in the build tree for `pp_legacy_vendor`, disabling the old `_SECURE_SCL` checked-array iterator branch while leaving the fmt submodule clean | VS 2026's STL no longer exposes the legacy checked-array iterator used by this old fmt release, but replacing fmt is part of the dependency migration rather than this platform unblock | `cmake --build --preset windows-msvc-default --config Debug --target PanoPainter`; `cmake --build --preset windows-msvc-default --config Debug --target pp_platform_api_tests`; `ctest --preset desktop-fast --build-config Debug -R pp_platform_api_tests --output-on-failure` | Move fmt to a supported vcpkg/package version or update the vendored fmt release, then remove the generated fmt overlay from `pp_legacy_vendor` |
|
||||
| DEBT-0063 | Open | Modernization | The retained UI tree still exposes `Node* m_parent`, public `std::vector<std::shared_ptr<Node>> m_children`, raw `find<T>()` lookup results, `add_child<T>()` allocation through `new`, callbacks/observers that take or capture raw `Node*`, and manual `destroy()`/`m_destroyed` semantics. `pp_ui_core` now owns a tested `NodeLifetimeTree` target model with checked node handles, scoped callback connections, subtree destruction, pointer/keyboard capture release, whole-tree clear for layout reload, and mutation-safe dispatch, but retained `Node` has not adopted it yet | Preserve current UI behavior while making panel/dialog extraction safe instead of spreading lifetime hazards into the new architecture | `pp_ui_core_layout_xml_tests`; `pp_ui_core_node_lifetime_tests`; future `pp_panopainter_ui_dialog_lifetime_tests`; `ctest --preset desktop-fast --build-config Debug`; `cmake --build --preset windows-msvc-default --config Debug --target PanoPainter` | Retained `Node` and `pp_panopainter_ui` adopt checked node handles or equivalent non-owning references, scoped callback connection/disconnect semantics, mutation-safe event dispatch, parent/child invariants hidden behind APIs, and destroy-during-callback/capture-release/popup-close/layout-reload tests; retained `Node*` APIs are removed or isolated behind compatibility adapters |
|
||||
| DEBT-0063 | Open | Modernization | The retained UI tree still exposes `Node* m_parent`, public `std::vector<std::shared_ptr<Node>> m_children`, raw `find<T>()` lookup results, `add_child<T>()` allocation through `new`, callbacks/observers that take or capture raw `Node*`, and manual `destroy()`/`m_destroyed` semantics. `pp_ui_core` now owns a tested `NodeLifetimeTree` target model with checked node handles, scoped callback connections, subtree destruction, pointer/keyboard capture release, whole-tree clear for layout reload, and mutation-safe dispatch, plus a tested `UiOverlayLifetime` popup/dialog stack model, but retained `Node`/`NodePopupMenu`/`NodeDialog*` have not adopted them yet | Preserve current UI behavior while making panel/dialog extraction safe instead of spreading lifetime hazards into the new architecture | `pp_ui_core_layout_xml_tests`; `pp_ui_core_node_lifetime_tests`; `pp_ui_core_overlay_lifetime_tests`; future `pp_panopainter_ui_dialog_lifetime_tests`; `ctest --preset desktop-fast --build-config Debug`; `cmake --build --preset windows-msvc-default --config Debug --target PanoPainter` | Retained `Node` and `pp_panopainter_ui` adopt checked node handles or equivalent non-owning references, scoped callback connection/disconnect semantics, mutation-safe event dispatch, parent/child invariants hidden behind APIs, and destroy-during-callback/capture-release/popup-close/layout-reload tests; retained `Node*` APIs are removed or isolated behind compatibility adapters |
|
||||
| DEBT-0057 | Open | Modernization | Default canvas allocation size now dispatches through `PlatformServices::default_canvas_resolution`, removing the `CANVAS_RES` platform macro from `src/canvas.h`; WebGL's retained 512 default now lives in tested `pp_platform_api::platform_policy`, but the Web shell still reaches it through the legacy platform fallback until injected Web services own the policy | Preserve WebGL memory behavior while moving canvas creation policy out of shared canvas headers and into the platform boundary | `pp_platform_api_tests`; `ctest --preset desktop-fast --build-config Debug -R pp_platform_api_tests`; Windows app build; WebGL package smoke once root Web build exists | Default canvas resolution is owned by injected `pp_platform_*` services for every supported platform, with no WebGL branch in the legacy fallback |
|
||||
| DEBT-0058 | Open | Modernization | App-level progress/message/input dialog metadata, including message-dialog OK/cancel captions, now consumes pure `pp_app_core` through `App::show_progress`, `App::message_box`, `App::input_box`, `pano_cli plan-app-dialog`, and `pp_app_core_app_dialog_tests`; live execution is centralized in `src/legacy_app_dialog_services.*`, but the bridge still creates retained `NodeProgressBar`, `NodeMessageBox`, and `NodeInputBox` instances and inserts them into the legacy layout tree | Preserve current app-shell dialog behavior while moving shared dialog policy toward UI/app services | `pp_app_core_app_dialog_tests`; `pano_cli plan-app-dialog --kind progress --total -4`; `pano_cli plan-app-dialog --kind message --cancel`; `pano_cli plan-app-dialog --kind input --ok-caption Save`; `ctest --preset desktop-fast --build-config Debug`; Windows app build | Progress/message/input dialog creation, callback wiring, layout insertion, lifetime ownership, and headless automation are owned by injected app/UI services with `App` methods acting only as adapters |
|
||||
| DEBT-0059 | Open | Modernization | iOS root CMake headless builds assign generated bundle identifiers and disable code signing for executable test/tool targets | The current Apple gate is compile validation for shared component targets; signed iOS app/package validation is not migrated to root CMake yet | `powershell -ExecutionPolicy Bypass -File scripts\automation\apple-remote-build.ps1 -Presets macos,ios-simulator,ios-device`; `sh scripts/automation/platform-build.sh "ios-device"` on `panopainter-mac` | Root CMake owns the signed Apple app/package targets, package-smoke validates Apple bundles where signing material is available, and headless iOS test/tool targets are either excluded from signed package builds or use explicit test-runner signing policy |
|
||||
|
||||
@@ -471,8 +471,11 @@ migration accelerates. The first pure `pp_ui_core::NodeLifetimeTree` slice now
|
||||
models checked handles, parent/child invariants, scoped callback connections,
|
||||
and mutation-safe dispatch for destroy-during-callback and connection-addition
|
||||
cases. It now also models pointer/keyboard capture release and whole-tree
|
||||
`clear()` invalidation for layout reload; wiring those semantics into retained
|
||||
`Node` remains tracked by the same debt.
|
||||
`clear()` invalidation for layout reload. `pp_ui_core::UiOverlayLifetime` now
|
||||
layers popup/dialog stack ownership on top of that model, covering root and
|
||||
nested popups, modal versus modeless dialogs, capture restoration, parent-popup
|
||||
branch close, and layout-reload invalidation. Wiring those semantics into
|
||||
retained `Node`/`NodePopupMenu`/`NodeDialog*` remains tracked by the same debt.
|
||||
`pano_cli inspect-image` exposes PNG IHDR metadata as JSON,
|
||||
`pano_cli import-image` accepts a PNG path and imports decoded RGBA8 pixels
|
||||
into a new pure `pp_document` face payload,
|
||||
|
||||
173
src/ui_core/overlay_lifetime.cpp
Normal file
173
src/ui_core/overlay_lifetime.cpp
Normal 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;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
54
src/ui_core/overlay_lifetime.h
Normal file
54
src/ui_core/overlay_lifetime.h
Normal 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_;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -310,6 +310,16 @@ add_test(NAME pp_ui_core_node_lifetime_tests COMMAND pp_ui_core_node_lifetime_te
|
||||
set_tests_properties(pp_ui_core_node_lifetime_tests PROPERTIES
|
||||
LABELS "ui;desktop-fast")
|
||||
|
||||
add_executable(pp_ui_core_overlay_lifetime_tests
|
||||
ui_core/overlay_lifetime_tests.cpp)
|
||||
target_link_libraries(pp_ui_core_overlay_lifetime_tests PRIVATE
|
||||
pp_ui_core
|
||||
pp_test_harness)
|
||||
|
||||
add_test(NAME pp_ui_core_overlay_lifetime_tests COMMAND pp_ui_core_overlay_lifetime_tests)
|
||||
set_tests_properties(pp_ui_core_overlay_lifetime_tests PROPERTIES
|
||||
LABELS "ui;desktop-fast")
|
||||
|
||||
add_executable(pp_app_core_about_menu_tests
|
||||
app_core/about_menu_tests.cpp)
|
||||
target_link_libraries(pp_app_core_about_menu_tests PRIVATE
|
||||
|
||||
227
tests/ui_core/overlay_lifetime_tests.cpp
Normal file
227
tests/ui_core/overlay_lifetime_tests.cpp
Normal file
@@ -0,0 +1,227 @@
|
||||
#include "ui_core/overlay_lifetime.h"
|
||||
#include "test_harness.h"
|
||||
|
||||
using pp::foundation::StatusCode;
|
||||
using pp::ui::NodeLifetimeTree;
|
||||
using pp::ui::UiCaptureKind;
|
||||
using pp::ui::UiOverlayLifetime;
|
||||
|
||||
namespace {
|
||||
|
||||
void opens_root_and_child_popups_with_capture_restore(pp::tests::Harness& h)
|
||||
{
|
||||
NodeLifetimeTree tree;
|
||||
const auto root = tree.create_root();
|
||||
PP_EXPECT(h, root);
|
||||
if (!root) {
|
||||
return;
|
||||
}
|
||||
|
||||
UiOverlayLifetime overlays(tree, root.value());
|
||||
const auto popup = overlays.open_popup();
|
||||
PP_EXPECT(h, popup);
|
||||
if (!popup) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto child = overlays.open_child_popup(popup.value());
|
||||
PP_EXPECT(h, child);
|
||||
if (!child) {
|
||||
return;
|
||||
}
|
||||
|
||||
PP_EXPECT(h, overlays.overlay_count() == 2U);
|
||||
const auto captured_child = tree.captured_node(UiCaptureKind::pointer);
|
||||
PP_EXPECT(h, captured_child);
|
||||
if (captured_child) {
|
||||
PP_EXPECT(h, captured_child.value() == child.value());
|
||||
}
|
||||
|
||||
PP_EXPECT(h, overlays.close(child.value()).ok());
|
||||
PP_EXPECT(h, overlays.overlay_count() == 1U);
|
||||
PP_EXPECT(h, !tree.contains(child.value()));
|
||||
PP_EXPECT(h, tree.contains(popup.value()));
|
||||
|
||||
const auto captured_parent = tree.captured_node(UiCaptureKind::pointer);
|
||||
PP_EXPECT(h, captured_parent);
|
||||
if (captured_parent) {
|
||||
PP_EXPECT(h, captured_parent.value() == popup.value());
|
||||
}
|
||||
}
|
||||
|
||||
void closing_parent_popup_closes_child_branch(pp::tests::Harness& h)
|
||||
{
|
||||
NodeLifetimeTree tree;
|
||||
const auto root = tree.create_root();
|
||||
PP_EXPECT(h, root);
|
||||
if (!root) {
|
||||
return;
|
||||
}
|
||||
|
||||
UiOverlayLifetime overlays(tree, root.value());
|
||||
const auto popup = overlays.open_popup();
|
||||
PP_EXPECT(h, popup);
|
||||
if (!popup) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto child = overlays.open_child_popup(popup.value());
|
||||
PP_EXPECT(h, child);
|
||||
if (!child) {
|
||||
return;
|
||||
}
|
||||
|
||||
PP_EXPECT(h, overlays.close(popup.value()).ok());
|
||||
PP_EXPECT(h, overlays.overlay_count() == 0U);
|
||||
PP_EXPECT(h, !tree.contains(popup.value()));
|
||||
PP_EXPECT(h, !tree.contains(child.value()));
|
||||
PP_EXPECT(h, !tree.captured_node(UiCaptureKind::pointer).ok());
|
||||
}
|
||||
|
||||
void modal_dialog_captures_pointer_and_keyboard(pp::tests::Harness& h)
|
||||
{
|
||||
NodeLifetimeTree tree;
|
||||
const auto root = tree.create_root();
|
||||
PP_EXPECT(h, root);
|
||||
if (!root) {
|
||||
return;
|
||||
}
|
||||
|
||||
UiOverlayLifetime overlays(tree, root.value());
|
||||
const auto popup = overlays.open_popup();
|
||||
PP_EXPECT(h, popup);
|
||||
if (!popup) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto dialog = overlays.open_dialog(true);
|
||||
PP_EXPECT(h, dialog);
|
||||
if (!dialog) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto pointer = tree.captured_node(UiCaptureKind::pointer);
|
||||
const auto keyboard = tree.captured_node(UiCaptureKind::keyboard);
|
||||
PP_EXPECT(h, pointer);
|
||||
PP_EXPECT(h, keyboard);
|
||||
if (pointer) {
|
||||
PP_EXPECT(h, pointer.value() == dialog.value());
|
||||
}
|
||||
if (keyboard) {
|
||||
PP_EXPECT(h, keyboard.value() == dialog.value());
|
||||
}
|
||||
|
||||
PP_EXPECT(h, overlays.close(dialog.value()).ok());
|
||||
const auto restored_pointer = tree.captured_node(UiCaptureKind::pointer);
|
||||
PP_EXPECT(h, restored_pointer);
|
||||
if (restored_pointer) {
|
||||
PP_EXPECT(h, restored_pointer.value() == popup.value());
|
||||
}
|
||||
PP_EXPECT(h, !tree.captured_node(UiCaptureKind::keyboard).ok());
|
||||
}
|
||||
|
||||
void modeless_dialog_does_not_steal_capture(pp::tests::Harness& h)
|
||||
{
|
||||
NodeLifetimeTree tree;
|
||||
const auto root = tree.create_root();
|
||||
PP_EXPECT(h, root);
|
||||
if (!root) {
|
||||
return;
|
||||
}
|
||||
|
||||
UiOverlayLifetime overlays(tree, root.value());
|
||||
const auto popup = overlays.open_popup();
|
||||
PP_EXPECT(h, popup);
|
||||
if (!popup) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto dialog = overlays.open_dialog(false);
|
||||
PP_EXPECT(h, dialog);
|
||||
if (!dialog) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto pointer = tree.captured_node(UiCaptureKind::pointer);
|
||||
PP_EXPECT(h, pointer);
|
||||
if (pointer) {
|
||||
PP_EXPECT(h, pointer.value() == popup.value());
|
||||
}
|
||||
PP_EXPECT(h, !tree.captured_node(UiCaptureKind::keyboard).ok());
|
||||
PP_EXPECT(h, overlays.top_overlay());
|
||||
if (const auto top = overlays.top_overlay()) {
|
||||
PP_EXPECT(h, top.value() == dialog.value());
|
||||
}
|
||||
}
|
||||
|
||||
void rejects_untracked_or_dead_overlay_closes(pp::tests::Harness& h)
|
||||
{
|
||||
NodeLifetimeTree tree;
|
||||
const auto root = tree.create_root();
|
||||
PP_EXPECT(h, root);
|
||||
if (!root) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto raw_child = tree.create_child(root.value());
|
||||
PP_EXPECT(h, raw_child);
|
||||
if (!raw_child) {
|
||||
return;
|
||||
}
|
||||
|
||||
UiOverlayLifetime overlays(tree, root.value());
|
||||
const auto close_raw = overlays.close(raw_child.value());
|
||||
const auto child_popup = overlays.open_child_popup(raw_child.value());
|
||||
PP_EXPECT(h, !close_raw.ok());
|
||||
PP_EXPECT(h, close_raw.code == StatusCode::invalid_argument);
|
||||
PP_EXPECT(h, !child_popup.ok());
|
||||
PP_EXPECT(h, child_popup.status().code == StatusCode::invalid_argument);
|
||||
}
|
||||
|
||||
void clear_for_layout_reload_invalidates_overlays(pp::tests::Harness& h)
|
||||
{
|
||||
NodeLifetimeTree tree;
|
||||
const auto root = tree.create_root();
|
||||
PP_EXPECT(h, root);
|
||||
if (!root) {
|
||||
return;
|
||||
}
|
||||
|
||||
UiOverlayLifetime overlays(tree, root.value());
|
||||
const auto popup = overlays.open_popup();
|
||||
PP_EXPECT(h, popup);
|
||||
if (!popup) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto dialog = overlays.open_dialog(true);
|
||||
PP_EXPECT(h, dialog);
|
||||
if (!dialog) {
|
||||
return;
|
||||
}
|
||||
|
||||
overlays.clear_for_layout_reload();
|
||||
PP_EXPECT(h, overlays.overlay_count() == 0U);
|
||||
PP_EXPECT(h, !overlays.top_overlay().ok());
|
||||
PP_EXPECT(h, !tree.contains(root.value()));
|
||||
PP_EXPECT(h, !tree.contains(popup.value()));
|
||||
PP_EXPECT(h, !tree.contains(dialog.value()));
|
||||
PP_EXPECT(h, !tree.captured_node(UiCaptureKind::pointer).ok());
|
||||
PP_EXPECT(h, !tree.captured_node(UiCaptureKind::keyboard).ok());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
pp::tests::Harness harness;
|
||||
harness.run(
|
||||
"opens_root_and_child_popups_with_capture_restore",
|
||||
opens_root_and_child_popups_with_capture_restore);
|
||||
harness.run("closing_parent_popup_closes_child_branch", closing_parent_popup_closes_child_branch);
|
||||
harness.run("modal_dialog_captures_pointer_and_keyboard", modal_dialog_captures_pointer_and_keyboard);
|
||||
harness.run("modeless_dialog_does_not_steal_capture", modeless_dialog_does_not_steal_capture);
|
||||
harness.run("rejects_untracked_or_dead_overlay_closes", rejects_untracked_or_dead_overlay_closes);
|
||||
harness.run("clear_for_layout_reload_invalidates_overlays", clear_for_layout_reload_invalidates_overlays);
|
||||
return harness.finish();
|
||||
}
|
||||
Reference in New Issue
Block a user