Skip to content

Commit 0f84355

Browse files
committed
Merge branch 'pr-53' into pr53-merge
2 parents 7720107 + a0c04c1 commit 0f84355

2 files changed

Lines changed: 65 additions & 2 deletions

File tree

src/platform/windows/menu_windows.cpp

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -877,11 +877,46 @@ bool Menu::Open(const PositioningStrategy& strategy, Placement placement) {
877877
// - WM_INITMENUPOPUP is sent when the menu opens (triggers MenuOpenedEvent)
878878
// - WM_UNINITMENUPOPUP is sent when the menu closes (triggers
879879
// MenuClosedEvent)
880+
// TPM_RETURNCMD: return the chosen item's id instead of posting WM_COMMAND.
881+
// A posted WM_COMMAND is dispatched after Open() has returned, from the
882+
// host's message loop, where no caller is on the stack; bindings whose
883+
// listeners must run synchronously inside a call (Dart's
884+
// NativeCallable.isolateLocal) then abort with "Cannot invoke native
885+
// callback outside an isolate". Emitting the click here, before Open()
886+
// returns, keeps every menu event inside the Open() call.
880887
pimpl_->opening_ = true;
881-
const BOOL result = TrackPopupMenu(pimpl_->hmenu_, uFlags, pt.x, pt.y, 0, host_window, nullptr);
888+
SetLastError(ERROR_SUCCESS);
889+
const UINT cmd = static_cast<UINT>(TrackPopupMenu(
890+
pimpl_->hmenu_, uFlags | TPM_RETURNCMD, pt.x, pt.y, 0, host_window, nullptr));
891+
// With TPM_RETURNCMD, 0 means "dismissed" or "failed"; only the error code
892+
// tells them apart.
893+
const bool failed = cmd == 0 && GetLastError() != ERROR_SUCCESS;
882894
pimpl_->opening_ = false;
895+
if (failed) {
896+
return false;
897+
}
883898

884-
return result != FALSE;
899+
if (cmd != 0) {
900+
// Find the item (searching submenus) and fire its click.
901+
std::function<bool(const Menu&)> dispatch = [&](const Menu& menu) -> bool {
902+
for (const auto& item : menu.pimpl_->items_) {
903+
if (item->GetId() == cmd) {
904+
if (item->pimpl_->clicked_callback_) {
905+
item->pimpl_->clicked_callback_(item->pimpl_->id_);
906+
}
907+
return true;
908+
}
909+
if (auto submenu = item->GetSubmenu(); submenu && dispatch(*submenu)) {
910+
return true;
911+
}
912+
}
913+
return false;
914+
};
915+
dispatch(*this);
916+
}
917+
918+
// Shown (and possibly dismissed without a pick): success, as before.
919+
return true;
885920
}
886921

887922
bool Menu::Close() {

tests/menu_backend_test.cpp

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,34 @@ int main(int argc, char**) {
4545
Menu wrapped(CreatePopupMenu());
4646
if (!Check(!wrapped.SetBackend(MenuBackend::WinUI3), "Wrapped HMENU accepted modern backend")) return 1;
4747
if (argc <= 1) return 0;
48+
{
49+
// Native backend: the click must be emitted before Open() returns. A
50+
// posted WM_COMMAND would arrive later from the message loop, where
51+
// bindings with call-scoped callbacks (Dart NativeCallable.isolateLocal)
52+
// cannot receive it.
53+
Menu native_menu;
54+
if (!Check(native_menu.SetBackend(MenuBackend::Native), "Native backend rejected")) return 1;
55+
auto pick = std::make_shared<MenuItem>("Pick me");
56+
native_menu.AddItem(pick);
57+
bool clicked = false;
58+
pick->AddListener<MenuItemClickedEvent>([&](const auto&) { clicked = true; });
59+
auto pick_timer = SetTimer(nullptr, 0, 700, [](HWND, UINT, UINT_PTR t, DWORD) {
60+
KillTimer(nullptr, t);
61+
INPUT keys[4] = {};
62+
for (auto& k : keys) k.type = INPUT_KEYBOARD;
63+
keys[0].ki.wVk = VK_DOWN;
64+
keys[1].ki.wVk = VK_DOWN;
65+
keys[1].ki.dwFlags = KEYEVENTF_KEYUP;
66+
keys[2].ki.wVk = VK_RETURN;
67+
keys[3].ki.wVk = VK_RETURN;
68+
keys[3].ki.dwFlags = KEYEVENTF_KEYUP;
69+
SendInput(4, keys, sizeof(INPUT));
70+
});
71+
const bool native_opened =
72+
pick_timer && native_menu.Open(PositioningStrategy::Absolute({300, 300}));
73+
if (!Check(native_opened, "Native Open failed") ||
74+
!Check(clicked, "Native click was not emitted before Open() returned")) return 1;
75+
}
4876
if (!supported) return 1;
4977
HWND preview = nullptr;
5078
if (argc > 2) {

0 commit comments

Comments
 (0)