feat(input): 完成阶段3键盘路由与焦点重绘收口
This commit is contained in:
+77
-6
@@ -50,11 +50,23 @@ namespace
|
||||
}
|
||||
return false;
|
||||
}
|
||||
StellarX::KeyRouteResult handleKeyEvent(StellarX::RoutedKeyEvent& event) override
|
||||
{
|
||||
++routedKeyEventCount;
|
||||
if (event.key.action == StellarX::KeyAction::Down)
|
||||
++routedKeyDownCount;
|
||||
else
|
||||
++routedKeyUpCount;
|
||||
return {};
|
||||
}
|
||||
bool model() const override { return false; }
|
||||
|
||||
std::uint64_t keyEventCount = 0;
|
||||
std::uint64_t keyDownCount = 0;
|
||||
std::uint64_t keyUpCount = 0;
|
||||
std::uint64_t routedKeyEventCount = 0;
|
||||
std::uint64_t routedKeyDownCount = 0;
|
||||
std::uint64_t routedKeyUpCount = 0;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -64,8 +76,9 @@ int main(int argc, char** argv)
|
||||
const std::string scenario = argc > 1 ? argv[1] : "geometry";
|
||||
const std::filesystem::path resultDir = argc > 2 ? argv[2] : "TestResults";
|
||||
const bool consumeProbeScenario = scenario == "input-consume-probe";
|
||||
const int windowWidth = consumeProbeScenario ? 720 : 480;
|
||||
const int windowHeight = consumeProbeScenario ? 420 : 320;
|
||||
const bool expandedFocusScenario = scenario == "focus";
|
||||
const int windowWidth = consumeProbeScenario ? 720 : (expandedFocusScenario ? 800 : 480);
|
||||
const int windowHeight = consumeProbeScenario ? 420 : (expandedFocusScenario ? 520 : 320);
|
||||
Window window(windowWidth, windowHeight, 1, Background, "StellarX.UI." + scenario);
|
||||
|
||||
if (scenario == "ownership")
|
||||
@@ -164,11 +177,13 @@ int main(int argc, char** argv)
|
||||
}
|
||||
else if (scenario == "input-shadow")
|
||||
{
|
||||
// Stage 1 keeps legacy EX_KEY delivery while the normalized queue is only
|
||||
// observed and discarded at host safe points.
|
||||
// Stage 3 production routes the normalized queue while EasyX EX_KEY is
|
||||
// still drained but never delivered to Control::handleEvent().
|
||||
auto observer = std::make_unique<KeyboardObservationControl>();
|
||||
KeyboardObservationControl* observerRaw = observer.get();
|
||||
window.addControl(std::move(observer));
|
||||
observerRaw->setFocusPolicy(StellarX::FocusPolicy::Programmatic);
|
||||
const auto observerHandle = window.addControl(std::move(observer));
|
||||
window.getFocusManager().requestFocus({ observerHandle, 0 });
|
||||
|
||||
auto verify = MakeButton(20, 40, 160, 42, "verify input shadow");
|
||||
verify->setOnClickListener([&, observerRaw]() {
|
||||
@@ -177,6 +192,13 @@ int main(int argc, char** argv)
|
||||
std::string("{\"legacyKeyCount\":") + std::to_string(observerRaw->keyEventCount) +
|
||||
",\"legacyKeyDownCount\":" + std::to_string(observerRaw->keyDownCount) +
|
||||
",\"legacyKeyUpCount\":" + std::to_string(observerRaw->keyUpCount) +
|
||||
",\"routedKeyCount\":" + std::to_string(observerRaw->routedKeyEventCount) +
|
||||
",\"routedKeyDownCount\":" + std::to_string(observerRaw->routedKeyDownCount) +
|
||||
",\"routedKeyUpCount\":" + std::to_string(observerRaw->routedKeyUpCount) +
|
||||
",\"easyXKeyboardMessagesDrained\":" +
|
||||
std::to_string(snapshot.easyXKeyboardMessagesDrained) +
|
||||
",\"legacyEasyXKeyboardBusinessDeliveries\":" +
|
||||
std::to_string(snapshot.legacyEasyXKeyboardBusinessDeliveries) +
|
||||
",\"pushed\":" + std::to_string(snapshot.queue.pushed) +
|
||||
",\"popped\":" + std::to_string(snapshot.queue.popped) +
|
||||
",\"currentDepth\":" + std::to_string(snapshot.queue.currentDepth) +
|
||||
@@ -191,6 +213,31 @@ int main(int argc, char** argv)
|
||||
});
|
||||
window.addControl(std::move(verify));
|
||||
}
|
||||
else if (scenario == "keyboard-routing")
|
||||
{
|
||||
// End-to-end production cutover: WndProc -> NativeInputQueue ->
|
||||
// KeyboardRouter -> Button, with no EX_KEY business fallback.
|
||||
int firstClicks = 0;
|
||||
int secondClicks = 0;
|
||||
auto first = MakeButton(20, 40, 150, 42, "space target");
|
||||
Button* firstRaw = first.get();
|
||||
const auto firstHandle = window.addControl(std::move(first));
|
||||
auto second = MakeButton(210, 40, 150, 42, "enter target");
|
||||
Button* secondRaw = second.get();
|
||||
const auto secondHandle = window.addControl(std::move(second));
|
||||
|
||||
firstRaw->setOnClickListener([&]() { ++firstClicks; });
|
||||
secondRaw->setOnClickListener([&, secondHandle]() {
|
||||
++secondClicks;
|
||||
const bool secondFocused = window.getFocusManager().focusedTarget() ==
|
||||
StellarX::FocusTargetId{ secondHandle, 0 };
|
||||
WriteResult(resultDir, scenario,
|
||||
std::string("{\"firstClicks\":") + std::to_string(firstClicks) +
|
||||
",\"secondClicks\":" + std::to_string(secondClicks) +
|
||||
",\"secondFocused\":" + (secondFocused ? "true" : "false") + "}");
|
||||
});
|
||||
window.getFocusManager().requestFocus({ firstHandle, 0 });
|
||||
}
|
||||
else if (scenario == "native-close")
|
||||
{
|
||||
// 只创建顶层窗口,由 UI 驱动发送原生关闭命令并验证宿主循环自行退出。
|
||||
@@ -253,7 +300,8 @@ int main(int argc, char** argv)
|
||||
}
|
||||
else if (scenario == "focus")
|
||||
{
|
||||
// Stage 2 integration: real focus visuals, Table child eligibility, and active Tab page filtering.
|
||||
// Focus integration: real focus visuals, Table child eligibility, active Tab page
|
||||
// filtering, background pointer clearing, and same-parent overlay restoration.
|
||||
auto button = MakeButton(20, 20, 120, 40, "focus button");
|
||||
Button* buttonRaw = button.get();
|
||||
bool buttonFocusObserved = false;
|
||||
@@ -286,6 +334,29 @@ int main(int argc, char** argv)
|
||||
tabs->addTab(MakeButton(100, 0, 100, 30, "second"), std::move(secondPage));
|
||||
window.addControl(std::move(tabs));
|
||||
|
||||
// Canvas 空白点击必须清焦点。右侧 Canvas 与左侧控件不重叠,便于脚本
|
||||
// 对焦点框出现/消失做稳定的局部像素比较。
|
||||
auto focusCanvas = std::make_unique<Canvas>(500, 20, 260, 120);
|
||||
focusCanvas->setCanvasBkColor(RGB(60, 90, 120));
|
||||
auto canvasButton = MakeButton(20, 20, 110, 34, "canvas focus");
|
||||
focusCanvas->addControl(std::move(canvasButton));
|
||||
window.addControl(std::move(focusCanvas));
|
||||
|
||||
// 复现 KEY5 D 区:下层与上层 Canvas 同属一个父 Canvas,且两者相交。
|
||||
// 从下层 Button 切到上层 Button 时,旧焦点消失和新焦点出现会让两条
|
||||
// 子树在同一批次变脏;上层必须完整恢复,不能只画自己的脏 Button。
|
||||
auto overlayRoot = std::make_unique<Canvas>(500, 180, 260, 180);
|
||||
overlayRoot->setCanvasBkColor(RGB(235, 225, 205));
|
||||
auto lowerLayer = std::make_unique<Canvas>(10, 20, 170, 110);
|
||||
lowerLayer->setCanvasBkColor(RGB(90, 120, 150));
|
||||
lowerLayer->addControl(MakeButton(15, 15, 90, 30, "lower"));
|
||||
auto upperLayer = std::make_unique<Canvas>(100, 55, 145, 100);
|
||||
upperLayer->setCanvasBkColor(RGB(200, 160, 120));
|
||||
upperLayer->addControl(MakeButton(30, 45, 100, 30, "upper"));
|
||||
overlayRoot->addControl(std::move(lowerLayer));
|
||||
overlayRoot->addControl(std::move(upperLayer));
|
||||
window.addControl(std::move(overlayRoot));
|
||||
|
||||
auto verify = MakeButton(350, 260, 110, 36, "verify");
|
||||
verify->setOnClickListener([&, buttonRaw]() {
|
||||
const auto stops = window.getFocusManager().collectTabStops();
|
||||
|
||||
Reference in New Issue
Block a user