|
3 | 3 | // This must be included before many other Windows headers.
|
4 | 4 | #include <windows.h>
|
5 | 5 |
|
6 |
| -// For getPlatformVersion; remove unless needed for your plugin implementation. |
7 |
| -#include <VersionHelpers.h> |
8 |
| - |
9 | 6 | #include <flutter/method_channel.h>
|
10 | 7 | #include <flutter/plugin_registrar_windows.h>
|
11 | 8 | #include <flutter/standard_method_codec.h>
|
12 | 9 |
|
| 10 | +#include <codecvt> |
13 | 11 | #include <map>
|
14 | 12 | #include <memory>
|
15 | 13 | #include <sstream>
|
16 | 14 |
|
| 15 | +using flutter::EncodableList; |
| 16 | +using flutter::EncodableMap; |
| 17 | +using flutter::EncodableValue; |
| 18 | + |
17 | 19 | namespace {
|
18 | 20 |
|
| 21 | +const EncodableValue* ValueOrNull(const EncodableMap& map, const char* key) { |
| 22 | + auto it = map.find(EncodableValue(key)); |
| 23 | + if (it == map.end()) { |
| 24 | + return nullptr; |
| 25 | + } |
| 26 | + return &(it->second); |
| 27 | +} |
| 28 | +std::unique_ptr<flutter::MethodChannel<EncodableValue>, |
| 29 | + std::default_delete<flutter::MethodChannel<EncodableValue>>> |
| 30 | + channel = nullptr; |
19 | 31 | class ContextualMenuPlugin : public flutter::Plugin {
|
20 | 32 | public:
|
21 |
| - static void RegisterWithRegistrar(flutter::PluginRegistrarWindows *registrar); |
| 33 | + static void RegisterWithRegistrar(flutter::PluginRegistrarWindows* registrar); |
22 | 34 |
|
23 |
| - ContextualMenuPlugin(); |
| 35 | + ContextualMenuPlugin(flutter::PluginRegistrarWindows* registrar); |
24 | 36 |
|
25 | 37 | virtual ~ContextualMenuPlugin();
|
26 | 38 |
|
27 | 39 | private:
|
| 40 | + std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> g_converter; |
| 41 | + |
| 42 | + flutter::PluginRegistrarWindows* registrar; |
| 43 | + |
| 44 | + // The ID of the WindowProc delegate registration. |
| 45 | + int window_proc_id = -1; |
| 46 | + |
| 47 | + HMENU hMenu; |
| 48 | + |
| 49 | + void ContextualMenuPlugin::_CreateMenu(HMENU menu, EncodableMap args); |
| 50 | + |
| 51 | + // Called for top-level WindowProc delegation. |
| 52 | + std::optional<LRESULT> ContextualMenuPlugin::HandleWindowProc(HWND hwnd, |
| 53 | + UINT message, |
| 54 | + WPARAM wparam, |
| 55 | + LPARAM lparam); |
| 56 | + HWND ContextualMenuPlugin::GetMainWindow(); |
| 57 | + void ContextualMenuPlugin::PopUp( |
| 58 | + const flutter::MethodCall<EncodableValue>& method_call, |
| 59 | + std::unique_ptr<flutter::MethodResult<EncodableValue>> result); |
| 60 | + |
28 | 61 | // Called when a method is called on this plugin's channel from Dart.
|
29 | 62 | void HandleMethodCall(
|
30 |
| - const flutter::MethodCall<flutter::EncodableValue> &method_call, |
31 |
| - std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result); |
| 63 | + const flutter::MethodCall<EncodableValue>& method_call, |
| 64 | + std::unique_ptr<flutter::MethodResult<EncodableValue>> result); |
32 | 65 | };
|
33 | 66 |
|
34 | 67 | // static
|
35 | 68 | void ContextualMenuPlugin::RegisterWithRegistrar(
|
36 |
| - flutter::PluginRegistrarWindows *registrar) { |
37 |
| - auto channel = |
38 |
| - std::make_unique<flutter::MethodChannel<flutter::EncodableValue>>( |
39 |
| - registrar->messenger(), "contextual_menu", |
40 |
| - &flutter::StandardMethodCodec::GetInstance()); |
| 69 | + flutter::PluginRegistrarWindows* registrar) { |
| 70 | + channel = std::make_unique<flutter::MethodChannel<EncodableValue>>( |
| 71 | + registrar->messenger(), "contextual_menu", |
| 72 | + &flutter::StandardMethodCodec::GetInstance()); |
41 | 73 |
|
42 |
| - auto plugin = std::make_unique<ContextualMenuPlugin>(); |
| 74 | + auto plugin = std::make_unique<ContextualMenuPlugin>(registrar); |
43 | 75 |
|
44 | 76 | channel->SetMethodCallHandler(
|
45 |
| - [plugin_pointer = plugin.get()](const auto &call, auto result) { |
| 77 | + [plugin_pointer = plugin.get()](const auto& call, auto result) { |
46 | 78 | plugin_pointer->HandleMethodCall(call, std::move(result));
|
47 | 79 | });
|
48 | 80 |
|
49 | 81 | registrar->AddPlugin(std::move(plugin));
|
50 | 82 | }
|
51 | 83 |
|
52 |
| -ContextualMenuPlugin::ContextualMenuPlugin() {} |
| 84 | +ContextualMenuPlugin::ContextualMenuPlugin( |
| 85 | + flutter::PluginRegistrarWindows* registrar) |
| 86 | + : registrar(registrar) { |
| 87 | + window_proc_id = registrar->RegisterTopLevelWindowProcDelegate( |
| 88 | + [this](HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) { |
| 89 | + return HandleWindowProc(hwnd, message, wparam, lparam); |
| 90 | + }); |
| 91 | +} |
53 | 92 |
|
54 | 93 | ContextualMenuPlugin::~ContextualMenuPlugin() {}
|
55 | 94 |
|
56 |
| -void ContextualMenuPlugin::HandleMethodCall( |
57 |
| - const flutter::MethodCall<flutter::EncodableValue> &method_call, |
58 |
| - std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result) { |
59 |
| - if (method_call.method_name().compare("getPlatformVersion") == 0) { |
60 |
| - std::ostringstream version_stream; |
61 |
| - version_stream << "Windows "; |
62 |
| - if (IsWindows10OrGreater()) { |
63 |
| - version_stream << "10+"; |
64 |
| - } else if (IsWindows8OrGreater()) { |
65 |
| - version_stream << "8"; |
66 |
| - } else if (IsWindows7OrGreater()) { |
67 |
| - version_stream << "7"; |
| 95 | +void ContextualMenuPlugin::_CreateMenu(HMENU menu, EncodableMap args) { |
| 96 | + EncodableList items = |
| 97 | + std::get<EncodableList>(args.at(EncodableValue("items"))); |
| 98 | + |
| 99 | + int count = GetMenuItemCount(menu); |
| 100 | + for (int i = 0; i < count; i++) { |
| 101 | + // always remove at 0 because they shift every time |
| 102 | + RemoveMenu(menu, 0, MF_BYPOSITION); |
| 103 | + } |
| 104 | + |
| 105 | + for (EncodableValue item_value : items) { |
| 106 | + EncodableMap item_map = std::get<EncodableMap>(item_value); |
| 107 | + int id = std::get<int>(item_map.at(EncodableValue("id"))); |
| 108 | + std::string type = |
| 109 | + std::get<std::string>(item_map.at(EncodableValue("type"))); |
| 110 | + std::string label = |
| 111 | + std::get<std::string>(item_map.at(EncodableValue("label"))); |
| 112 | + auto* checked = std::get_if<bool>(ValueOrNull(item_map, "checked")); |
| 113 | + bool disabled = std::get<bool>(item_map.at(EncodableValue("disabled"))); |
| 114 | + |
| 115 | + UINT_PTR item_id = id; |
| 116 | + UINT uFlags = MF_STRING; |
| 117 | + |
| 118 | + if (disabled) { |
| 119 | + uFlags |= MF_GRAYED; |
| 120 | + } |
| 121 | + |
| 122 | + if (type.compare("separator") == 0) { |
| 123 | + AppendMenuW(menu, MF_SEPARATOR, item_id, NULL); |
| 124 | + } else { |
| 125 | + if (type.compare("checkbox") == 0) { |
| 126 | + if (checked == nullptr) { |
| 127 | + // skip |
| 128 | + } else { |
| 129 | + uFlags |= (*checked == true ? MF_CHECKED : MF_UNCHECKED); |
| 130 | + } |
| 131 | + } else if (type.compare("submenu") == 0) { |
| 132 | + uFlags |= MF_POPUP; |
| 133 | + HMENU sub_menu = ::CreatePopupMenu(); |
| 134 | + _CreateMenu(sub_menu, std::get<EncodableMap>( |
| 135 | + item_map.at(EncodableValue("submenu")))); |
| 136 | + item_id = reinterpret_cast<UINT_PTR>(sub_menu); |
| 137 | + } |
| 138 | + AppendMenuW(menu, uFlags, item_id, g_converter.from_bytes(label).c_str()); |
68 | 139 | }
|
69 |
| - result->Success(flutter::EncodableValue(version_stream.str())); |
| 140 | + } |
| 141 | +} |
| 142 | + |
| 143 | +std::optional<LRESULT> ContextualMenuPlugin::HandleWindowProc(HWND hWnd, |
| 144 | + UINT message, |
| 145 | + WPARAM wParam, |
| 146 | + LPARAM lParam) { |
| 147 | + std::optional<LRESULT> result; |
| 148 | + if (message == WM_COMMAND) { |
| 149 | + flutter::EncodableMap eventData = flutter::EncodableMap(); |
| 150 | + eventData[flutter::EncodableValue("id")] = |
| 151 | + flutter::EncodableValue((int)wParam); |
| 152 | + |
| 153 | + channel->InvokeMethod("onMenuItemClick", |
| 154 | + std::make_unique<flutter::EncodableValue>(eventData)); |
| 155 | + } |
| 156 | + return std::nullopt; |
| 157 | +} |
| 158 | + |
| 159 | +HWND ContextualMenuPlugin::GetMainWindow() { |
| 160 | + return ::GetAncestor(registrar->GetView()->GetNativeWindow(), GA_ROOT); |
| 161 | +} |
| 162 | + |
| 163 | +void ContextualMenuPlugin::PopUp( |
| 164 | + const flutter::MethodCall<EncodableValue>& method_call, |
| 165 | + std::unique_ptr<flutter::MethodResult<EncodableValue>> result) { |
| 166 | + HWND hWnd = GetMainWindow(); |
| 167 | + |
| 168 | + const EncodableMap& args = std::get<EncodableMap>(*method_call.arguments()); |
| 169 | + |
| 170 | + std::string placement = |
| 171 | + std::get<std::string>(args.at(EncodableValue("placement"))); |
| 172 | + double device_pixel_ratio = |
| 173 | + std::get<double>(args.at(flutter::EncodableValue("devicePixelRatio"))); |
| 174 | + |
| 175 | + hMenu = CreatePopupMenu(); |
| 176 | + _CreateMenu(hMenu, std::get<EncodableMap>(args.at(EncodableValue("menu")))); |
| 177 | + |
| 178 | + double x, y; |
| 179 | + |
| 180 | + UINT uFlags = TPM_TOPALIGN; |
| 181 | + |
| 182 | + POINT cursorPos; |
| 183 | + GetCursorPos(&cursorPos); |
| 184 | + x = cursorPos.x; |
| 185 | + y = cursorPos.y; |
| 186 | + |
| 187 | + if (args.find(EncodableValue("position")) != args.end()) { |
| 188 | + const EncodableMap& position = |
| 189 | + std::get<EncodableMap>(args.at(EncodableValue("position"))); |
| 190 | + |
| 191 | + double position_x = |
| 192 | + std::get<double>(position.at(flutter::EncodableValue("x"))); |
| 193 | + double position_y = |
| 194 | + std::get<double>(position.at(flutter::EncodableValue("y"))); |
| 195 | + |
| 196 | + RECT window_rect, client_rect; |
| 197 | + TITLEBARINFOEX title_bar_info; |
| 198 | + |
| 199 | + GetWindowRect(hWnd, &window_rect); |
| 200 | + GetClientRect(hWnd, &client_rect); |
| 201 | + title_bar_info.cbSize = sizeof(TITLEBARINFOEX); |
| 202 | + ::SendMessage(hWnd, WM_GETTITLEBARINFOEX, 0, (LPARAM)&title_bar_info); |
| 203 | + int32_t title_bar_height = |
| 204 | + title_bar_info.rcTitleBar.bottom == 0 |
| 205 | + ? 0 |
| 206 | + : title_bar_info.rcTitleBar.bottom - title_bar_info.rcTitleBar.top; |
| 207 | + |
| 208 | + int border_thickness = |
| 209 | + ((window_rect.right - window_rect.left) - client_rect.right) / 2; |
| 210 | + |
| 211 | + x = static_cast<double>((position_x * device_pixel_ratio) + |
| 212 | + (window_rect.left + border_thickness)); |
| 213 | + y = static_cast<double>((position_y * device_pixel_ratio) + |
| 214 | + (window_rect.top + title_bar_height)); |
| 215 | + } |
| 216 | + |
| 217 | + if (placement.compare("topLeft") == 0) { |
| 218 | + uFlags = TPM_BOTTOMALIGN | TPM_RIGHTALIGN; |
| 219 | + } else if (placement.compare("topRight") == 0) { |
| 220 | + uFlags = TPM_BOTTOMALIGN | TPM_LEFTALIGN; |
| 221 | + } else if (placement.compare("bottomLeft") == 0) { |
| 222 | + uFlags = TPM_TOPALIGN | TPM_RIGHTALIGN; |
| 223 | + } else if (placement.compare("bottomRight") == 0) { |
| 224 | + uFlags = TPM_TOPALIGN | TPM_LEFTALIGN; |
| 225 | + } |
| 226 | + |
| 227 | + TrackPopupMenu(hMenu, uFlags, static_cast<int>(x), static_cast<int>(y), 0, |
| 228 | + hWnd, NULL); |
| 229 | + |
| 230 | + result->Success(EncodableValue(true)); |
| 231 | +} |
| 232 | + |
| 233 | +void ContextualMenuPlugin::HandleMethodCall( |
| 234 | + const flutter::MethodCall<EncodableValue>& method_call, |
| 235 | + std::unique_ptr<flutter::MethodResult<EncodableValue>> result) { |
| 236 | + if (method_call.method_name().compare("popUp") == 0) { |
| 237 | + PopUp(method_call, std::move(result)); |
70 | 238 | } else {
|
71 | 239 | result->NotImplemented();
|
72 | 240 | }
|
|
0 commit comments