plusui-native-core 0.1.4 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Core/CMakeLists.txt +190 -7
- package/Core/Features/App/app.cpp +129 -0
- package/Core/Features/App/app.ts +126 -0
- package/Core/Features/Browser/browser.cpp +181 -0
- package/Core/Features/Browser/browser.ts +182 -0
- package/Core/Features/Clipboard/clipboard.cpp +234 -0
- package/Core/Features/Clipboard/clipboard.ts +113 -0
- package/Core/Features/Display/display.cpp +209 -0
- package/Core/Features/Display/display.ts +104 -0
- package/Core/Features/Event/Events.ts +166 -0
- package/Core/Features/Event/events.cpp +200 -0
- package/Core/Features/Keyboard/keyboard.cpp +186 -0
- package/Core/Features/Keyboard/keyboard.ts +175 -0
- package/Core/Features/Menu/context-menu.css +293 -0
- package/Core/Features/Menu/menu.cpp +481 -0
- package/Core/Features/Menu/menu.ts +439 -0
- package/Core/Features/Tray/tray.cpp +310 -0
- package/Core/Features/Tray/tray.ts +68 -0
- package/Core/Features/WebGPU/webgpu.cpp +939 -0
- package/Core/Features/WebGPU/webgpu.ts +1013 -0
- package/Core/Features/WebView/webview.cpp +1052 -0
- package/Core/Features/WebView/webview.ts +510 -0
- package/Core/Features/Window/window.cpp +664 -0
- package/Core/Features/Window/window.ts +142 -0
- package/Core/Features/WindowManager/window_manager.cpp +341 -0
- package/Core/include/plusui/app.hpp +73 -0
- package/Core/include/plusui/browser.hpp +66 -0
- package/Core/include/plusui/clipboard.hpp +41 -0
- package/Core/include/plusui/events.hpp +58 -0
- package/Core/include/{keyboard.hpp → plusui/keyboard.hpp} +21 -44
- package/Core/include/plusui/menu.hpp +153 -0
- package/Core/include/plusui/tray.hpp +93 -0
- package/Core/include/plusui/webgpu.hpp +434 -0
- package/Core/include/plusui/webview.hpp +142 -0
- package/Core/include/plusui/window.hpp +111 -0
- package/Core/include/plusui/window_manager.hpp +57 -0
- package/Core/vendor/WebView2EnvironmentOptions.h +406 -0
- package/Core/vendor/stb_image.h +7988 -0
- package/Core/vendor/webview.h +618 -510
- package/Core/vendor/webview2.h +52079 -0
- package/README.md +19 -0
- package/package.json +12 -15
- package/Core/include/app.hpp +0 -121
- package/Core/include/menu.hpp +0 -79
- package/Core/include/tray.hpp +0 -81
- package/Core/include/window.hpp +0 -106
- package/Core/src/app.cpp +0 -311
- package/Core/src/display.cpp +0 -424
- package/Core/src/tray.cpp +0 -275
- package/Core/src/window.cpp +0 -528
- package/dist/index.d.ts +0 -205
- package/dist/index.js +0 -198
- package/src/index.ts +0 -574
- /package/Core/include/{display.hpp → plusui/display.hpp} +0 -0
|
@@ -0,0 +1,481 @@
|
|
|
1
|
+
#include <iostream>
|
|
2
|
+
#include <plusui/menu.hpp>
|
|
3
|
+
#include <plusui/webview.hpp>
|
|
4
|
+
#include <sstream>
|
|
5
|
+
|
|
6
|
+
#ifdef _WIN32
|
|
7
|
+
#include <windows.h>
|
|
8
|
+
#endif
|
|
9
|
+
|
|
10
|
+
namespace plusui {
|
|
11
|
+
namespace bindings {
|
|
12
|
+
|
|
13
|
+
// JSON parsing helpers (simple implementation)
|
|
14
|
+
static std::string trim(const std::string &str) {
|
|
15
|
+
size_t first = str.find_first_not_of(" \t\n\r");
|
|
16
|
+
if (first == std::string::npos)
|
|
17
|
+
return "";
|
|
18
|
+
size_t last = str.find_last_not_of(" \t\n\r");
|
|
19
|
+
return str.substr(first, last - first + 1);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
static std::string extractJsonString(const std::string &json,
|
|
23
|
+
const std::string &key) {
|
|
24
|
+
std::string searchKey = "\"" + key + "\"";
|
|
25
|
+
size_t pos = json.find(searchKey);
|
|
26
|
+
if (pos == std::string::npos)
|
|
27
|
+
return "";
|
|
28
|
+
|
|
29
|
+
pos = json.find(":", pos);
|
|
30
|
+
if (pos == std::string::npos)
|
|
31
|
+
return "";
|
|
32
|
+
|
|
33
|
+
pos = json.find("\"", pos);
|
|
34
|
+
if (pos == std::string::npos)
|
|
35
|
+
return "";
|
|
36
|
+
|
|
37
|
+
size_t start = pos + 1;
|
|
38
|
+
size_t end = json.find("\"", start);
|
|
39
|
+
if (end == std::string::npos)
|
|
40
|
+
return "";
|
|
41
|
+
|
|
42
|
+
return json.substr(start, end - start);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
static bool extractJsonBool(const std::string &json, const std::string &key,
|
|
46
|
+
bool defaultVal = false) {
|
|
47
|
+
std::string searchKey = "\"" + key + "\"";
|
|
48
|
+
size_t pos = json.find(searchKey);
|
|
49
|
+
if (pos == std::string::npos)
|
|
50
|
+
return defaultVal;
|
|
51
|
+
|
|
52
|
+
pos = json.find(":", pos);
|
|
53
|
+
if (pos == std::string::npos)
|
|
54
|
+
return defaultVal;
|
|
55
|
+
|
|
56
|
+
std::string rest = json.substr(pos + 1);
|
|
57
|
+
rest = trim(rest);
|
|
58
|
+
|
|
59
|
+
if (rest.find("true") == 0)
|
|
60
|
+
return true;
|
|
61
|
+
if (rest.find("false") == 0)
|
|
62
|
+
return false;
|
|
63
|
+
return defaultVal;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
void MenuBindings::registerBindings(WebView &webview) {
|
|
67
|
+
// Binding: menu.create
|
|
68
|
+
webview.bind("plusui_menu_create",
|
|
69
|
+
[this](const std::string &args) -> std::string {
|
|
70
|
+
auto items = parseMenuItems(args);
|
|
71
|
+
std::string menuId = MenuManager::instance().createMenu(items);
|
|
72
|
+
return "\"" + menuId + "\"";
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// Binding: menu.popup
|
|
76
|
+
webview.bind("plusui_menu_popup", [](const std::string &args) -> std::string {
|
|
77
|
+
// Parse: [menuId, x, y]
|
|
78
|
+
std::string menuId = extractJsonString(args, "menuId");
|
|
79
|
+
|
|
80
|
+
// Get x, y from args (simplified parsing)
|
|
81
|
+
int x = 0, y = 0;
|
|
82
|
+
size_t xPos = args.find("\"x\"");
|
|
83
|
+
size_t yPos = args.find("\"y\"");
|
|
84
|
+
|
|
85
|
+
if (xPos != std::string::npos) {
|
|
86
|
+
size_t colonPos = args.find(":", xPos);
|
|
87
|
+
if (colonPos != std::string::npos) {
|
|
88
|
+
x = std::stoi(args.substr(colonPos + 1));
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
if (yPos != std::string::npos) {
|
|
92
|
+
size_t colonPos = args.find(":", yPos);
|
|
93
|
+
if (colonPos != std::string::npos) {
|
|
94
|
+
y = std::stoi(args.substr(colonPos + 1));
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
Menu *menu = MenuManager::instance().getMenu(menuId);
|
|
99
|
+
if (menu) {
|
|
100
|
+
menu->popup(x, y);
|
|
101
|
+
}
|
|
102
|
+
return "null";
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// Binding: menu.setApplicationMenu
|
|
106
|
+
webview.bind("plusui_menu_setAppMenu",
|
|
107
|
+
[this](const std::string &args) -> std::string {
|
|
108
|
+
auto items = parseMenuItems(args);
|
|
109
|
+
MenuBarData data;
|
|
110
|
+
data.items = items;
|
|
111
|
+
MenuBar::instance().setMenu(data);
|
|
112
|
+
return "null";
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// Binding: menu.destroy
|
|
116
|
+
webview.bind("plusui_menu_destroy",
|
|
117
|
+
[](const std::string &args) -> std::string {
|
|
118
|
+
std::string menuId = extractJsonString(args, "menuId");
|
|
119
|
+
MenuManager::instance().destroyMenu(menuId);
|
|
120
|
+
return "null";
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// Binding: menu.close
|
|
124
|
+
webview.bind("plusui_menu_close", [](const std::string &args) -> std::string {
|
|
125
|
+
std::string menuId = extractJsonString(args, "menuId");
|
|
126
|
+
Menu *menu = MenuManager::instance().getMenu(menuId);
|
|
127
|
+
if (menu) {
|
|
128
|
+
menu->close();
|
|
129
|
+
}
|
|
130
|
+
return "null";
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// Inject JavaScript bridge for context menu handling
|
|
134
|
+
webview.eval(R"(
|
|
135
|
+
// PlusUI Context Menu Bridge
|
|
136
|
+
window.__plusui_contextMenu = {
|
|
137
|
+
enabled: false,
|
|
138
|
+
items: [],
|
|
139
|
+
|
|
140
|
+
enable: function(items) {
|
|
141
|
+
this.enabled = true;
|
|
142
|
+
this.items = items || [];
|
|
143
|
+
},
|
|
144
|
+
|
|
145
|
+
disable: function() {
|
|
146
|
+
this.enabled = false;
|
|
147
|
+
},
|
|
148
|
+
|
|
149
|
+
setItems: function(items) {
|
|
150
|
+
this.items = items;
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
// Override right-click if custom context menu is enabled
|
|
155
|
+
document.addEventListener('contextmenu', function(e) {
|
|
156
|
+
if (window.__plusui_contextMenu.enabled) {
|
|
157
|
+
e.preventDefault();
|
|
158
|
+
|
|
159
|
+
// Get element info for context-aware menus
|
|
160
|
+
const target = e.target;
|
|
161
|
+
const selector = target.tagName.toLowerCase() +
|
|
162
|
+
(target.id ? '#' + target.id : '') +
|
|
163
|
+
(target.className ? '.' + target.className.split(' ').join('.') : '');
|
|
164
|
+
|
|
165
|
+
// Notify C++ to show native context menu
|
|
166
|
+
if (typeof plusui_menu_showContext === 'function') {
|
|
167
|
+
plusui_menu_showContext(JSON.stringify({
|
|
168
|
+
x: e.screenX,
|
|
169
|
+
y: e.screenY,
|
|
170
|
+
clientX: e.clientX,
|
|
171
|
+
clientY: e.clientY,
|
|
172
|
+
selector: selector,
|
|
173
|
+
tagName: target.tagName,
|
|
174
|
+
isEditable: target.isContentEditable ||
|
|
175
|
+
target.tagName === 'INPUT' ||
|
|
176
|
+
target.tagName === 'TEXTAREA',
|
|
177
|
+
hasSelection: window.getSelection().toString().length > 0
|
|
178
|
+
}));
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
)");
|
|
185
|
+
|
|
186
|
+
// Binding: Show context menu
|
|
187
|
+
webview.bind("plusui_menu_showContext",
|
|
188
|
+
[this](const std::string &args) -> std::string {
|
|
189
|
+
if (!contextMenuEnabled_ || contextMenuItems_.empty()) {
|
|
190
|
+
return "null";
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
int x = 0, y = 0;
|
|
194
|
+
size_t xPos = args.find("\"x\"");
|
|
195
|
+
size_t yPos = args.find("\"y\"");
|
|
196
|
+
|
|
197
|
+
if (xPos != std::string::npos) {
|
|
198
|
+
size_t colonPos = args.find(":", xPos);
|
|
199
|
+
if (colonPos != std::string::npos) {
|
|
200
|
+
x = std::stoi(args.substr(colonPos + 1));
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
if (yPos != std::string::npos) {
|
|
204
|
+
size_t colonPos = args.find(":", yPos);
|
|
205
|
+
if (colonPos != std::string::npos) {
|
|
206
|
+
y = std::stoi(args.substr(colonPos + 1));
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Create and show context menu
|
|
211
|
+
if (currentContextMenuId_.empty()) {
|
|
212
|
+
currentContextMenuId_ =
|
|
213
|
+
MenuManager::instance().createMenu(contextMenuItems_);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
Menu *menu =
|
|
217
|
+
MenuManager::instance().getMenu(currentContextMenuId_);
|
|
218
|
+
if (menu) {
|
|
219
|
+
menu->popup(x, y);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return "null";
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
// Binding: Enable custom context menu
|
|
226
|
+
webview.bind("plusui_menu_enableContext",
|
|
227
|
+
[this, &webview](const std::string &args) -> std::string {
|
|
228
|
+
contextMenuEnabled_ = true;
|
|
229
|
+
webview.eval("window.__plusui_contextMenu.enabled = true;");
|
|
230
|
+
return "null";
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
// Binding: Disable custom context menu
|
|
234
|
+
webview.bind("plusui_menu_disableContext",
|
|
235
|
+
[this, &webview](const std::string &args) -> std::string {
|
|
236
|
+
contextMenuEnabled_ = false;
|
|
237
|
+
webview.eval("window.__plusui_contextMenu.enabled = false;");
|
|
238
|
+
return "null";
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
void MenuBindings::setItemClickCallback(
|
|
243
|
+
std::function<void(const std::string &, const std::string &)> callback) {
|
|
244
|
+
itemClickCallback_ = callback;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
void MenuBindings::setContextMenu(const std::vector<MenuItem> &items) {
|
|
248
|
+
contextMenuItems_ = items;
|
|
249
|
+
|
|
250
|
+
// Destroy old context menu if exists
|
|
251
|
+
if (!currentContextMenuId_.empty()) {
|
|
252
|
+
MenuManager::instance().destroyMenu(currentContextMenuId_);
|
|
253
|
+
currentContextMenuId_.clear();
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
void MenuBindings::enableContextMenu(bool enable) {
|
|
258
|
+
contextMenuEnabled_ = enable;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
void MenuBindings::showContextMenuAtCursor() {
|
|
262
|
+
#ifdef _WIN32
|
|
263
|
+
POINT pt;
|
|
264
|
+
GetCursorPos(&pt);
|
|
265
|
+
|
|
266
|
+
if (!currentContextMenuId_.empty()) {
|
|
267
|
+
Menu *menu = MenuManager::instance().getMenu(currentContextMenuId_);
|
|
268
|
+
if (menu) {
|
|
269
|
+
menu->popup(pt.x, pt.y);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
#endif
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
std::vector<MenuItem> MenuBindings::parseMenuItems(const std::string &json) {
|
|
276
|
+
std::vector<MenuItem> items;
|
|
277
|
+
|
|
278
|
+
// Simple JSON array parsing
|
|
279
|
+
// Format: [{"id":"...", "label":"...", ...}, ...]
|
|
280
|
+
size_t pos = json.find('[');
|
|
281
|
+
if (pos == std::string::npos)
|
|
282
|
+
return items;
|
|
283
|
+
|
|
284
|
+
size_t end = json.rfind(']');
|
|
285
|
+
if (end == std::string::npos)
|
|
286
|
+
return items;
|
|
287
|
+
|
|
288
|
+
std::string content = json.substr(pos + 1, end - pos - 1);
|
|
289
|
+
|
|
290
|
+
// Find each object
|
|
291
|
+
int braceCount = 0;
|
|
292
|
+
size_t objStart = std::string::npos;
|
|
293
|
+
|
|
294
|
+
for (size_t i = 0; i < content.size(); i++) {
|
|
295
|
+
char c = content[i];
|
|
296
|
+
if (c == '{') {
|
|
297
|
+
if (braceCount == 0) {
|
|
298
|
+
objStart = i;
|
|
299
|
+
}
|
|
300
|
+
braceCount++;
|
|
301
|
+
} else if (c == '}') {
|
|
302
|
+
braceCount--;
|
|
303
|
+
if (braceCount == 0 && objStart != std::string::npos) {
|
|
304
|
+
std::string objStr = content.substr(objStart, i - objStart + 1);
|
|
305
|
+
|
|
306
|
+
MenuItem item;
|
|
307
|
+
item.id = extractJsonString(objStr, "id");
|
|
308
|
+
item.label = extractJsonString(objStr, "label");
|
|
309
|
+
item.accelerator = extractJsonString(objStr, "accelerator");
|
|
310
|
+
item.icon = extractJsonString(objStr, "icon");
|
|
311
|
+
item.enabled = extractJsonBool(objStr, "enabled", true);
|
|
312
|
+
item.checked = extractJsonBool(objStr, "checked", false);
|
|
313
|
+
|
|
314
|
+
std::string typeStr = extractJsonString(objStr, "type");
|
|
315
|
+
if (typeStr == "separator") {
|
|
316
|
+
item.type = MenuItemType::Separator;
|
|
317
|
+
} else if (typeStr == "checkbox") {
|
|
318
|
+
item.type = MenuItemType::Checkbox;
|
|
319
|
+
} else if (typeStr == "radio") {
|
|
320
|
+
item.type = MenuItemType::Radio;
|
|
321
|
+
} else if (typeStr == "submenu") {
|
|
322
|
+
item.type = MenuItemType::Submenu;
|
|
323
|
+
} else {
|
|
324
|
+
item.type = MenuItemType::Normal;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Parse submenu recursively
|
|
328
|
+
size_t subPos = objStr.find("\"submenu\"");
|
|
329
|
+
if (subPos != std::string::npos) {
|
|
330
|
+
size_t subStart = objStr.find('[', subPos);
|
|
331
|
+
if (subStart != std::string::npos) {
|
|
332
|
+
int bracketCount = 1;
|
|
333
|
+
size_t subEnd = subStart + 1;
|
|
334
|
+
while (subEnd < objStr.size() && bracketCount > 0) {
|
|
335
|
+
if (objStr[subEnd] == '[')
|
|
336
|
+
bracketCount++;
|
|
337
|
+
else if (objStr[subEnd] == ']')
|
|
338
|
+
bracketCount--;
|
|
339
|
+
subEnd++;
|
|
340
|
+
}
|
|
341
|
+
std::string subJson = objStr.substr(subStart, subEnd - subStart);
|
|
342
|
+
item.submenu = parseMenuItems(subJson);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
items.push_back(item);
|
|
347
|
+
objStart = std::string::npos;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
return items;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
std::string MenuBindings::menuItemToJson(const MenuItem &item) {
|
|
356
|
+
std::ostringstream ss;
|
|
357
|
+
ss << "{";
|
|
358
|
+
ss << "\"id\":\"" << item.id << "\",";
|
|
359
|
+
ss << "\"label\":\"" << item.label << "\",";
|
|
360
|
+
ss << "\"enabled\":" << (item.enabled ? "true" : "false") << ",";
|
|
361
|
+
ss << "\"checked\":" << (item.checked ? "true" : "false");
|
|
362
|
+
|
|
363
|
+
if (!item.accelerator.empty()) {
|
|
364
|
+
ss << ",\"accelerator\":\"" << item.accelerator << "\"";
|
|
365
|
+
}
|
|
366
|
+
if (!item.icon.empty()) {
|
|
367
|
+
ss << ",\"icon\":\"" << item.icon << "\"";
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
std::string typeStr;
|
|
371
|
+
switch (item.type) {
|
|
372
|
+
case MenuItemType::Separator:
|
|
373
|
+
typeStr = "separator";
|
|
374
|
+
break;
|
|
375
|
+
case MenuItemType::Checkbox:
|
|
376
|
+
typeStr = "checkbox";
|
|
377
|
+
break;
|
|
378
|
+
case MenuItemType::Radio:
|
|
379
|
+
typeStr = "radio";
|
|
380
|
+
break;
|
|
381
|
+
case MenuItemType::Submenu:
|
|
382
|
+
typeStr = "submenu";
|
|
383
|
+
break;
|
|
384
|
+
default:
|
|
385
|
+
typeStr = "normal";
|
|
386
|
+
break;
|
|
387
|
+
}
|
|
388
|
+
ss << ",\"type\":\"" << typeStr << "\"";
|
|
389
|
+
|
|
390
|
+
if (!item.submenu.empty()) {
|
|
391
|
+
ss << ",\"submenu\":[";
|
|
392
|
+
for (size_t i = 0; i < item.submenu.size(); i++) {
|
|
393
|
+
if (i > 0)
|
|
394
|
+
ss << ",";
|
|
395
|
+
ss << menuItemToJson(item.submenu[i]);
|
|
396
|
+
}
|
|
397
|
+
ss << "]";
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
ss << "}";
|
|
401
|
+
return ss.str();
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// ContextMenuManager implementation
|
|
405
|
+
|
|
406
|
+
ContextMenuManager &ContextMenuManager::instance() {
|
|
407
|
+
static ContextMenuManager inst;
|
|
408
|
+
return inst;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
void ContextMenuManager::setDefaultMenu(const std::vector<MenuItem> &items) {
|
|
412
|
+
defaultMenu_ = items;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
void ContextMenuManager::setMenuForSelector(
|
|
416
|
+
const std::string &selector, const std::vector<MenuItem> &items) {
|
|
417
|
+
selectorMenus_[selector] = items;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
void ContextMenuManager::attachToWebView(WebView &webview) {
|
|
421
|
+
// Inject context menu override script
|
|
422
|
+
webview.eval(R"(
|
|
423
|
+
(function() {
|
|
424
|
+
document.addEventListener('contextmenu', function(e) {
|
|
425
|
+
// Check if we should use custom context menu
|
|
426
|
+
if (window.__plusui_useCustomContext) {
|
|
427
|
+
e.preventDefault();
|
|
428
|
+
|
|
429
|
+
const info = {
|
|
430
|
+
x: e.screenX,
|
|
431
|
+
y: e.screenY,
|
|
432
|
+
target: e.target.tagName,
|
|
433
|
+
id: e.target.id,
|
|
434
|
+
className: e.target.className,
|
|
435
|
+
isInput: e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA',
|
|
436
|
+
isEditable: e.target.isContentEditable,
|
|
437
|
+
selectedText: window.getSelection().toString()
|
|
438
|
+
};
|
|
439
|
+
|
|
440
|
+
if (window.plusui && window.plusui.menu) {
|
|
441
|
+
window.plusui.menu._showContextMenu(info);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
});
|
|
445
|
+
})();
|
|
446
|
+
)");
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
void ContextMenuManager::handleRightClick(int x, int y,
|
|
450
|
+
const std::string &targetSelector) {
|
|
451
|
+
// Find the appropriate menu for this selector
|
|
452
|
+
std::vector<MenuItem> *menuItems = &defaultMenu_;
|
|
453
|
+
|
|
454
|
+
for (const auto &pair : selectorMenus_) {
|
|
455
|
+
if (targetSelector.find(pair.first) != std::string::npos) {
|
|
456
|
+
menuItems = const_cast<std::vector<MenuItem> *>(&pair.second);
|
|
457
|
+
break;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
if (menuItems->empty())
|
|
462
|
+
return;
|
|
463
|
+
|
|
464
|
+
// Create and show menu
|
|
465
|
+
std::string menuId = MenuManager::instance().createMenu(*menuItems);
|
|
466
|
+
Menu *menu = MenuManager::instance().getMenu(menuId);
|
|
467
|
+
if (menu) {
|
|
468
|
+
menu->popup(x, y);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// Cleanup after menu closes (delayed)
|
|
472
|
+
// In production, this should be handled by menu close event
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
void ContextMenuManager::onItemClick(
|
|
476
|
+
std::function<void(const std::string &, const std::string &)> callback) {
|
|
477
|
+
clickCallback_ = callback;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
} // namespace bindings
|
|
481
|
+
} // namespace plusui
|