Extend app input planning to UI state
This commit is contained in:
@@ -843,7 +843,8 @@ Known local toolchain state:
|
||||
- `pp_app_core_app_input_tests` covers pointer coordinate normalization,
|
||||
invalid pointer/gesture inputs, designer-first mouse routing, mouse-cancel
|
||||
routing, gesture midpoint/distance/delta math, main-layout routing, key state
|
||||
mutation intent, and VR spacebar camera-sync intent.
|
||||
mutation intent, VR spacebar camera-sync intent, UI visibility toggle target
|
||||
selection, malformed UI-toggle layout rejection, and stylus touch-lock intent.
|
||||
- `pp_app_core_app_shutdown_tests` covers legacy shutdown cleanup staging for
|
||||
UI-state save, stroke-preview renderer shutdown, recording stop,
|
||||
texture/shader invalidation, layout unload, render-target/mesh destruction,
|
||||
|
||||
@@ -100,11 +100,14 @@ agent or engineer to remove them without reconstructing context from chat.
|
||||
render-target recreation, and OpenGL/UI drawing remain in the legacy app.
|
||||
- 2026-06-05: DEBT-0003 was narrowed. Pointer coordinate normalization,
|
||||
mouse designer-first routing, gesture midpoint/delta math, touch/key
|
||||
main-layout routing, and VR spacebar camera-sync intent are now tested
|
||||
`pp_app_core` input plans consumed by `App::mouse_*`, `App::gesture_*`,
|
||||
`App::touch_tap`, `App::key_*`, and `pano_cli plan-app-input`; retained
|
||||
`MouseEvent`/`GestureEvent`/`TouchEvent`/`KeyEvent` construction and legacy
|
||||
`Node` event dispatch remain in the app shell.
|
||||
main-layout routing, VR spacebar camera-sync intent, UI visibility toggling,
|
||||
and stylus touch-lock attachment are now tested `pp_app_core` input plans
|
||||
consumed by `App::mouse_*`, `App::gesture_*`, `App::touch_tap`,
|
||||
`App::key_*`, `App::toggle_ui`, `App::set_stylus`, and
|
||||
`pano_cli plan-app-input`; retained
|
||||
`MouseEvent`/`GestureEvent`/`TouchEvent`/`KeyEvent` construction,
|
||||
UI child-node mutation, and legacy `Node` event dispatch remain in the app
|
||||
shell.
|
||||
- 2026-06-05: DEBT-0003 was narrowed again. Shutdown cleanup staging for
|
||||
UI-state save, stroke-preview renderer shutdown, recording stop,
|
||||
texture/shader invalidation, layout unload, UI render-target and face-plane
|
||||
|
||||
@@ -206,10 +206,12 @@ VR UI drawing, main UI drawing, and redraw reset now live in `pp_app_core`;
|
||||
render-target recreation, and OpenGL/UI drawing stay in the legacy app.
|
||||
App input dispatch decisions for pointer coordinate normalization, mouse
|
||||
designer-first routing, gesture midpoint/delta math, touch/key main-layout
|
||||
routing, and VR spacebar camera-sync intent now live in `pp_app_core`;
|
||||
`App::mouse_*`, `App::gesture_*`, `App::touch_tap`, `App::key_*`, and
|
||||
`pano_cli plan-app-input` consume those plans while retained event objects and
|
||||
legacy `Node` dispatch stay in the app shell.
|
||||
routing, VR spacebar camera-sync intent, UI visibility toggling, and stylus
|
||||
touch-lock attachment now live in `pp_app_core`; `App::mouse_*`,
|
||||
`App::gesture_*`, `App::touch_tap`, `App::key_*`, `App::toggle_ui`,
|
||||
`App::set_stylus`, and `pano_cli plan-app-input` consume those plans while
|
||||
retained event objects, child-node mutation, and legacy `Node` dispatch stay
|
||||
in the app shell.
|
||||
Shutdown lifecycle staging for UI-state save, stroke-preview renderer shutdown,
|
||||
recording stop, texture/shader invalidation, layout unload, render-target
|
||||
destruction, panel-node release, and quick-mode cleanup now lives in
|
||||
@@ -1678,8 +1680,11 @@ Results:
|
||||
- Focused app-input CTest coverage passed for `pp_app_core_app_input_tests`,
|
||||
`pano_cli_plan_app_input_pointer_smoke`,
|
||||
`pano_cli_plan_app_input_gesture_smoke`,
|
||||
`pano_cli_plan_app_input_key_vr_smoke`, and
|
||||
`pano_cli_plan_app_input_rejects_bad_float`.
|
||||
`pano_cli_plan_app_input_key_vr_smoke`,
|
||||
`pano_cli_plan_app_input_ui_toggle_smoke`,
|
||||
`pano_cli_plan_app_input_stylus_smoke`,
|
||||
`pano_cli_plan_app_input_rejects_bad_float`, and
|
||||
`pano_cli_plan_app_input_rejects_missing_ui_panel`.
|
||||
- `PanoPainter`, `pp_app_core_app_shutdown_tests`, and `pano_cli` built after
|
||||
shutdown cleanup staging moved into `pp_app_core`.
|
||||
- Focused shutdown CTest coverage passed for `pp_app_core_app_shutdown_tests`,
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "foundation/result.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
|
||||
namespace pp::app {
|
||||
|
||||
@@ -37,6 +38,17 @@ struct AppKeyDispatchPlan {
|
||||
bool sync_vr_camera_rotation = false;
|
||||
};
|
||||
|
||||
struct AppUiVisibilityTogglePlan {
|
||||
bool next_ui_visible = true;
|
||||
std::size_t first_panel_child_index = 1;
|
||||
std::size_t panel_child_count = 0;
|
||||
};
|
||||
|
||||
struct AppStylusAttachPlan {
|
||||
bool set_has_stylus = true;
|
||||
bool enable_canvas_touch_lock = false;
|
||||
};
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status validate_input_zoom(float zoom)
|
||||
{
|
||||
if (!std::isfinite(zoom) || zoom <= 0.0F) {
|
||||
@@ -163,4 +175,35 @@ struct AppKeyDispatchPlan {
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Result<AppUiVisibilityTogglePlan> plan_app_ui_visibility_toggle(
|
||||
bool current_ui_visible,
|
||||
bool has_main_layout,
|
||||
std::size_t main_child_count,
|
||||
std::size_t panel_child_count)
|
||||
{
|
||||
if (!has_main_layout) {
|
||||
return pp::foundation::Result<AppUiVisibilityTogglePlan>::failure(
|
||||
pp::foundation::Status::invalid_argument("UI toggle requires a main layout"));
|
||||
}
|
||||
|
||||
if (main_child_count <= 1U) {
|
||||
return pp::foundation::Result<AppUiVisibilityTogglePlan>::failure(
|
||||
pp::foundation::Status::invalid_argument("UI toggle requires a panel container child"));
|
||||
}
|
||||
|
||||
return pp::foundation::Result<AppUiVisibilityTogglePlan>::success(AppUiVisibilityTogglePlan {
|
||||
.next_ui_visible = !current_ui_visible,
|
||||
.first_panel_child_index = 1U,
|
||||
.panel_child_count = panel_child_count,
|
||||
});
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr AppStylusAttachPlan plan_app_stylus_attach(bool has_canvas) noexcept
|
||||
{
|
||||
return AppStylusAttachPlan {
|
||||
.set_has_stylus = true,
|
||||
.enable_canvas_touch_lock = has_canvas,
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace pp::app
|
||||
|
||||
@@ -661,15 +661,34 @@ bool App::key_char(char key)
|
||||
|
||||
void App::toggle_ui()
|
||||
{
|
||||
auto m = layout[main_id]->m_children[1];
|
||||
ui_visible = !ui_visible;
|
||||
for (int i = 1; i < m->m_children.size(); i++)
|
||||
m->m_children[i]->m_display = ui_visible;
|
||||
auto* main = layout[main_id];
|
||||
const std::size_t main_child_count = main ? main->m_children.size() : 0U;
|
||||
auto* panel_container = main_child_count > 1U ? main->m_children[1].get() : nullptr;
|
||||
const auto plan = pp::app::plan_app_ui_visibility_toggle(
|
||||
ui_visible,
|
||||
main != nullptr,
|
||||
main_child_count,
|
||||
panel_container ? panel_container->m_children.size() : 0U);
|
||||
if (!plan) {
|
||||
LOG("UI toggle plan failed: %s", plan.status().message);
|
||||
return;
|
||||
}
|
||||
|
||||
ui_visible = plan.value().next_ui_visible;
|
||||
if (!panel_container)
|
||||
return;
|
||||
|
||||
for (std::size_t i = plan.value().first_panel_child_index;
|
||||
i < plan.value().panel_child_count;
|
||||
++i) {
|
||||
panel_container->m_children[i]->m_display = ui_visible;
|
||||
}
|
||||
}
|
||||
|
||||
void App::set_stylus()
|
||||
{
|
||||
has_stylus = true;
|
||||
if (canvas)
|
||||
const auto plan = pp::app::plan_app_stylus_attach(canvas != nullptr);
|
||||
has_stylus = plan.set_has_stylus;
|
||||
if (plan.enable_canvas_touch_lock && canvas)
|
||||
canvas->m_canvas->m_touch_lock = true;
|
||||
}
|
||||
|
||||
@@ -1030,6 +1030,18 @@ if(TARGET pano_cli)
|
||||
LABELS "app;integration;desktop-fast"
|
||||
PASS_REGULAR_EXPRESSION "\"command\":\"plan-app-input\".*\"kind\":\"key\".*\"dispatchMain\":true.*\"setKeyDown\":true.*\"syncVrCameraRotation\":true")
|
||||
|
||||
add_test(NAME pano_cli_plan_app_input_ui_toggle_smoke
|
||||
COMMAND pano_cli plan-app-input --kind ui-toggle --panel-child-count 4)
|
||||
set_tests_properties(pano_cli_plan_app_input_ui_toggle_smoke PROPERTIES
|
||||
LABELS "app;ui;integration;desktop-fast"
|
||||
PASS_REGULAR_EXPRESSION "\"command\":\"plan-app-input\".*\"kind\":\"ui-toggle\".*\"nextUiVisible\":false.*\"firstPanelChildIndex\":1.*\"panelChildCount\":4")
|
||||
|
||||
add_test(NAME pano_cli_plan_app_input_stylus_smoke
|
||||
COMMAND pano_cli plan-app-input --kind stylus --no-canvas)
|
||||
set_tests_properties(pano_cli_plan_app_input_stylus_smoke PROPERTIES
|
||||
LABELS "app;ui;integration;desktop-fast"
|
||||
PASS_REGULAR_EXPRESSION "\"command\":\"plan-app-input\".*\"kind\":\"stylus\".*\"setHasStylus\":true.*\"enableCanvasTouchLock\":false")
|
||||
|
||||
add_test(NAME pano_cli_plan_app_input_rejects_bad_float
|
||||
COMMAND pano_cli plan-app-input --kind pointer --bad-float)
|
||||
set_tests_properties(pano_cli_plan_app_input_rejects_bad_float PROPERTIES
|
||||
@@ -1037,6 +1049,13 @@ if(TARGET pano_cli)
|
||||
WILL_FAIL TRUE
|
||||
PASS_REGULAR_EXPRESSION "\"command\":\"plan-app-input\".*\"message\":\"input zoom must be finite and positive\"")
|
||||
|
||||
add_test(NAME pano_cli_plan_app_input_rejects_missing_ui_panel
|
||||
COMMAND pano_cli plan-app-input --kind ui-toggle --main-child-count 1)
|
||||
set_tests_properties(pano_cli_plan_app_input_rejects_missing_ui_panel PROPERTIES
|
||||
LABELS "app;ui;integration;desktop-fast;fuzz"
|
||||
WILL_FAIL TRUE
|
||||
PASS_REGULAR_EXPRESSION "\"command\":\"plan-app-input\".*\"message\":\"UI toggle requires a panel container child\"")
|
||||
|
||||
add_test(NAME pano_cli_plan_app_shutdown_smoke
|
||||
COMMAND pano_cli plan-app-shutdown)
|
||||
set_tests_properties(pano_cli_plan_app_shutdown_smoke PROPERTIES
|
||||
|
||||
@@ -102,6 +102,37 @@ void simple_input_plan_tracks_main_layout_availability(pp::tests::Harness& harne
|
||||
PP_EXPECT(harness, !missing.dispatch_main);
|
||||
}
|
||||
|
||||
void ui_visibility_toggle_plan_flips_state_and_targets_panel_children(pp::tests::Harness& harness)
|
||||
{
|
||||
const auto hidden = pp::app::plan_app_ui_visibility_toggle(true, true, 2U, 4U);
|
||||
const auto visible = pp::app::plan_app_ui_visibility_toggle(false, true, 3U, 1U);
|
||||
|
||||
PP_EXPECT(harness, hidden);
|
||||
PP_EXPECT(harness, !hidden.value().next_ui_visible);
|
||||
PP_EXPECT(harness, hidden.value().first_panel_child_index == 1U);
|
||||
PP_EXPECT(harness, hidden.value().panel_child_count == 4U);
|
||||
PP_EXPECT(harness, visible);
|
||||
PP_EXPECT(harness, visible.value().next_ui_visible);
|
||||
PP_EXPECT(harness, visible.value().panel_child_count == 1U);
|
||||
}
|
||||
|
||||
void ui_visibility_toggle_plan_rejects_missing_panel_container(pp::tests::Harness& harness)
|
||||
{
|
||||
PP_EXPECT(harness, !pp::app::plan_app_ui_visibility_toggle(true, false, 0U, 0U));
|
||||
PP_EXPECT(harness, !pp::app::plan_app_ui_visibility_toggle(true, true, 1U, 0U));
|
||||
}
|
||||
|
||||
void stylus_attach_plan_sets_touch_lock_only_when_canvas_exists(pp::tests::Harness& harness)
|
||||
{
|
||||
const auto with_canvas = pp::app::plan_app_stylus_attach(true);
|
||||
const auto without_canvas = pp::app::plan_app_stylus_attach(false);
|
||||
|
||||
PP_EXPECT(harness, with_canvas.set_has_stylus);
|
||||
PP_EXPECT(harness, with_canvas.enable_canvas_touch_lock);
|
||||
PP_EXPECT(harness, without_canvas.set_has_stylus);
|
||||
PP_EXPECT(harness, !without_canvas.enable_canvas_touch_lock);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main()
|
||||
@@ -114,5 +145,8 @@ int main()
|
||||
harness.run("gesture plan rejects invalid input", gesture_plan_rejects_invalid_input);
|
||||
harness.run("key plan tracks state and VR spacebar sync", key_plan_tracks_state_and_vr_spacebar_sync);
|
||||
harness.run("simple input plan tracks main layout availability", simple_input_plan_tracks_main_layout_availability);
|
||||
harness.run("UI visibility toggle plan flips state and targets panel children", ui_visibility_toggle_plan_flips_state_and_targets_panel_children);
|
||||
harness.run("UI visibility toggle plan rejects missing panel container", ui_visibility_toggle_plan_rejects_missing_panel_container);
|
||||
harness.run("stylus attach plan sets touch lock only when canvas exists", stylus_attach_plan_sets_touch_lock_only_when_canvas_exists);
|
||||
return harness.finish();
|
||||
}
|
||||
|
||||
@@ -280,6 +280,10 @@ struct PlanAppInputArgs {
|
||||
bool spacebar = false;
|
||||
bool vr_active = false;
|
||||
bool key_up = false;
|
||||
bool ui_visible = true;
|
||||
bool has_canvas = true;
|
||||
std::uint32_t main_child_count = 2;
|
||||
std::uint32_t panel_child_count = 4;
|
||||
bool bad_float = false;
|
||||
};
|
||||
|
||||
@@ -2071,7 +2075,7 @@ void print_help()
|
||||
<< " plan-app-startup [--run-counter N] [--auto-timelapse-disabled] [--vr-controllers-disabled] [--license-invalid]\n"
|
||||
<< " plan-app-startup-resources [--width N] [--height N] [--bad-size]\n"
|
||||
<< " plan-app-frame [--redraw] [--animate] [--no-designer-layout] [--no-main-layout] [--no-canvas] [--no-canvas-document] [--vr-active] [--ui-hidden] [--vr-only] [--resize-width N] [--resize-height N] [--bad-resize]\n"
|
||||
<< " plan-app-input --kind pointer|gesture|cancel|main|key [--x N] [--y N] [--x1 N] [--y1 N] [--prev-x N] [--prev-y N] [--prev-x1 N] [--prev-y1 N] [--zoom N] [--no-designer-layout] [--no-main-layout] [--spacebar] [--vr-active] [--key-up] [--bad-float]\n"
|
||||
<< " plan-app-input --kind pointer|gesture|cancel|main|key|ui-toggle|stylus [--x N] [--y N] [--x1 N] [--y1 N] [--prev-x N] [--prev-y N] [--prev-x1 N] [--prev-y1 N] [--zoom N] [--no-designer-layout] [--no-main-layout] [--spacebar] [--vr-active] [--key-up] [--ui-hidden] [--no-canvas] [--main-child-count N] [--panel-child-count N] [--bad-float]\n"
|
||||
<< " plan-app-shutdown\n"
|
||||
<< " plan-command-convert [--project FILE] [--output FILE] [--canvas-resolution N]\n"
|
||||
<< " plan-app-status [--doc-name NAME] [--unsaved] [--resolution N] [--resolution-index N] [--zoom N] [--history-bytes N] [--recording-running] [--encoder-available] [--encoded-frames N] [--framebuffer-fetch] [--float32] [--float32-linear] [--float16]\n"
|
||||
@@ -3960,6 +3964,28 @@ pp::foundation::Status parse_plan_app_input_args(
|
||||
args.vr_active = true;
|
||||
} else if (key == "--key-up") {
|
||||
args.key_up = true;
|
||||
} else if (key == "--ui-hidden") {
|
||||
args.ui_visible = false;
|
||||
} else if (key == "--no-canvas") {
|
||||
args.has_canvas = false;
|
||||
} else if (key == "--main-child-count") {
|
||||
if (i + 1 >= argc) {
|
||||
return pp::foundation::Status::invalid_argument("missing value for option");
|
||||
}
|
||||
const auto value = pp::foundation::parse_u32(argv[++i]);
|
||||
if (!value) {
|
||||
return value.status();
|
||||
}
|
||||
args.main_child_count = value.value();
|
||||
} else if (key == "--panel-child-count") {
|
||||
if (i + 1 >= argc) {
|
||||
return pp::foundation::Status::invalid_argument("missing value for option");
|
||||
}
|
||||
const auto value = pp::foundation::parse_u32(argv[++i]);
|
||||
if (!value) {
|
||||
return value.status();
|
||||
}
|
||||
args.panel_child_count = value.value();
|
||||
} else if (key == "--bad-float") {
|
||||
args.bad_float = true;
|
||||
} else {
|
||||
@@ -4070,6 +4096,33 @@ int plan_app_input(int argc, char** argv)
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (args.kind == "ui-toggle") {
|
||||
const auto plan = pp::app::plan_app_ui_visibility_toggle(
|
||||
args.ui_visible,
|
||||
args.has_main_layout,
|
||||
args.main_child_count,
|
||||
args.panel_child_count);
|
||||
if (!plan) {
|
||||
print_error("plan-app-input", plan.status().message);
|
||||
return 2;
|
||||
}
|
||||
std::cout << "{\"ok\":true,\"command\":\"plan-app-input\",\"kind\":\"ui-toggle\""
|
||||
<< ",\"nextUiVisible\":" << json_bool(plan.value().next_ui_visible)
|
||||
<< ",\"firstPanelChildIndex\":" << plan.value().first_panel_child_index
|
||||
<< ",\"panelChildCount\":" << plan.value().panel_child_count
|
||||
<< "}\n";
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (args.kind == "stylus") {
|
||||
const auto plan = pp::app::plan_app_stylus_attach(args.has_canvas);
|
||||
std::cout << "{\"ok\":true,\"command\":\"plan-app-input\",\"kind\":\"stylus\""
|
||||
<< ",\"setHasStylus\":" << json_bool(plan.set_has_stylus)
|
||||
<< ",\"enableCanvasTouchLock\":" << json_bool(plan.enable_canvas_touch_lock)
|
||||
<< "}\n";
|
||||
return 0;
|
||||
}
|
||||
|
||||
print_error("plan-app-input", "unknown input plan kind");
|
||||
return 2;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user