plusui-native-core 0.1.4 → 0.1.5

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.
Files changed (54) hide show
  1. package/Core/CMakeLists.txt +190 -7
  2. package/Core/Features/App/app.cpp +129 -0
  3. package/Core/Features/App/app.ts +126 -0
  4. package/Core/Features/Browser/browser.cpp +181 -0
  5. package/Core/Features/Browser/browser.ts +182 -0
  6. package/Core/Features/Clipboard/clipboard.cpp +234 -0
  7. package/Core/Features/Clipboard/clipboard.ts +113 -0
  8. package/Core/Features/Display/display.cpp +209 -0
  9. package/Core/Features/Display/display.ts +104 -0
  10. package/Core/Features/Event/Events.ts +166 -0
  11. package/Core/Features/Event/events.cpp +200 -0
  12. package/Core/Features/Keyboard/keyboard.cpp +186 -0
  13. package/Core/Features/Keyboard/keyboard.ts +175 -0
  14. package/Core/Features/Menu/context-menu.css +293 -0
  15. package/Core/Features/Menu/menu.cpp +481 -0
  16. package/Core/Features/Menu/menu.ts +439 -0
  17. package/Core/Features/Tray/tray.cpp +310 -0
  18. package/Core/Features/Tray/tray.ts +68 -0
  19. package/Core/Features/WebGPU/webgpu.cpp +937 -0
  20. package/Core/Features/WebGPU/webgpu.ts +1013 -0
  21. package/Core/Features/WebView/webview.cpp +1052 -0
  22. package/Core/Features/WebView/webview.ts +510 -0
  23. package/Core/Features/Window/window.cpp +664 -0
  24. package/Core/Features/Window/window.ts +142 -0
  25. package/Core/Features/WindowManager/window_manager.cpp +341 -0
  26. package/Core/include/plusui/app.hpp +73 -0
  27. package/Core/include/plusui/browser.hpp +66 -0
  28. package/Core/include/plusui/clipboard.hpp +41 -0
  29. package/Core/include/plusui/events.hpp +58 -0
  30. package/Core/include/{keyboard.hpp → plusui/keyboard.hpp} +21 -44
  31. package/Core/include/plusui/menu.hpp +153 -0
  32. package/Core/include/plusui/tray.hpp +93 -0
  33. package/Core/include/plusui/webgpu.hpp +434 -0
  34. package/Core/include/plusui/webview.hpp +142 -0
  35. package/Core/include/plusui/window.hpp +111 -0
  36. package/Core/include/plusui/window_manager.hpp +57 -0
  37. package/Core/vendor/WebView2EnvironmentOptions.h +406 -0
  38. package/Core/vendor/stb_image.h +7988 -0
  39. package/Core/vendor/webview.h +618 -510
  40. package/Core/vendor/webview2.h +52079 -0
  41. package/README.md +19 -0
  42. package/package.json +12 -15
  43. package/Core/include/app.hpp +0 -121
  44. package/Core/include/menu.hpp +0 -79
  45. package/Core/include/tray.hpp +0 -81
  46. package/Core/include/window.hpp +0 -106
  47. package/Core/src/app.cpp +0 -311
  48. package/Core/src/display.cpp +0 -424
  49. package/Core/src/tray.cpp +0 -275
  50. package/Core/src/window.cpp +0 -528
  51. package/dist/index.d.ts +0 -205
  52. package/dist/index.js +0 -198
  53. package/src/index.ts +0 -574
  54. /package/Core/include/{display.hpp → plusui/display.hpp} +0 -0
@@ -0,0 +1,439 @@
1
+ /**
2
+ * PlusUI Menu API
3
+ *
4
+ * Cross-platform native menu system including:
5
+ * - Application menu bar (File, Edit, View, etc.)
6
+ * - Context menus (right-click)
7
+ * - Popup menus
8
+ * - System tray menus
9
+ */
10
+
11
+ // ============================================================================
12
+ // Types
13
+ // ============================================================================
14
+
15
+ export type MenuItemType = 'normal' | 'separator' | 'submenu' | 'checkbox' | 'radio';
16
+
17
+ export interface MenuItem {
18
+ /** Unique identifier for this menu item */
19
+ id: string;
20
+ /** Display text (use & before a letter for keyboard shortcut, e.g., "&File") */
21
+ label: string;
22
+ /** Keyboard shortcut (e.g., "Ctrl+S", "Cmd+Shift+N") */
23
+ accelerator?: string;
24
+ /** Icon path or data URL */
25
+ icon?: string;
26
+ /** Type of menu item */
27
+ type?: MenuItemType;
28
+ /** Whether the item is clickable */
29
+ enabled?: boolean;
30
+ /** For checkbox/radio types, whether it's checked */
31
+ checked?: boolean;
32
+ /** Nested menu items for submenus */
33
+ submenu?: MenuItem[];
34
+ /** Click handler (frontend only, not sent to backend) */
35
+ click?: (menuItem: MenuItem) => void;
36
+ /** Custom data attached to this item */
37
+ data?: Record<string, unknown>;
38
+ }
39
+
40
+ export interface MenuBarData {
41
+ items: MenuItem[];
42
+ }
43
+
44
+ export interface ContextMenuOptions {
45
+ /** Position to show the menu (defaults to cursor position) */
46
+ x?: number;
47
+ y?: number;
48
+ /** Only show for elements matching this selector */
49
+ selector?: string;
50
+ /** Context info passed to click handlers */
51
+ context?: Record<string, unknown>;
52
+ }
53
+
54
+ export interface ContextInfo {
55
+ x: number;
56
+ y: number;
57
+ clientX: number;
58
+ clientY: number;
59
+ selector: string;
60
+ tagName: string;
61
+ isEditable: boolean;
62
+ hasSelection: boolean;
63
+ selectedText?: string;
64
+ }
65
+
66
+ // ============================================================================
67
+ // Menu API Class
68
+ // ============================================================================
69
+
70
+ export class MenuAPI {
71
+ private clickHandlers: Map<string, (item: MenuItem) => void> = new Map();
72
+ private contextMenuItems: MenuItem[] = [];
73
+ private contextMenuEnabled = false;
74
+ private selectorMenus: Map<string, MenuItem[]> = new Map();
75
+ private unsubscribeClick?: () => void;
76
+
77
+ constructor(
78
+ private invokeFn: (name: string, args?: unknown[]) => Promise<unknown>,
79
+ private eventFn: (event: string, callback: (...args: unknown[]) => void) => () => void
80
+ ) {
81
+ // Listen for menu item clicks from backend
82
+ this.unsubscribeClick = this.eventFn('menu:itemClick', (...args: unknown[]) => {
83
+ const id = args[0] as string;
84
+ this.handleItemClick(id);
85
+ });
86
+ }
87
+
88
+ // ========================================================================
89
+ // Popup Menus
90
+ // ========================================================================
91
+
92
+ /**
93
+ * Create a new popup menu and return its ID
94
+ */
95
+ async create(items: MenuItem[]): Promise<string> {
96
+ this.registerClickHandlers(items);
97
+ const cleanItems = this.stripFunctions(items);
98
+ return await this.invokeFn('menu.create', [cleanItems]) as string;
99
+ }
100
+
101
+ /**
102
+ * Show a popup menu at specified coordinates
103
+ */
104
+ async popup(menuId: string, x?: number, y?: number): Promise<void> {
105
+ await this.invokeFn('menu.popup', [menuId, x ?? 0, y ?? 0]);
106
+ }
107
+
108
+ /**
109
+ * Show a popup menu at the current cursor position
110
+ */
111
+ async popupAtCursor(menuId: string): Promise<void> {
112
+ await this.invokeFn('menu.popupAtCursor', [menuId]);
113
+ }
114
+
115
+ /**
116
+ * Close an open popup menu
117
+ */
118
+ async close(menuId: string): Promise<void> {
119
+ await this.invokeFn('menu.close', [menuId]);
120
+ }
121
+
122
+ /**
123
+ * Destroy a menu and free its resources
124
+ */
125
+ async destroy(menuId: string): Promise<void> {
126
+ await this.invokeFn('menu.destroy', [menuId]);
127
+ }
128
+
129
+ // ========================================================================
130
+ // Application Menu Bar
131
+ // ========================================================================
132
+
133
+ /**
134
+ * Set the application menu bar (Window menu on Windows/Linux, global on macOS)
135
+ */
136
+ async setApplicationMenu(items: MenuItem[]): Promise<void> {
137
+ this.registerClickHandlers(items);
138
+ const cleanItems = this.stripFunctions(items);
139
+ await this.invokeFn('menu.setApplicationMenu', [cleanItems]);
140
+ }
141
+
142
+ /**
143
+ * Get the current application menu structure
144
+ */
145
+ async getApplicationMenu(): Promise<MenuItem[]> {
146
+ return await this.invokeFn('menu.getApplicationMenu', []) as MenuItem[];
147
+ }
148
+
149
+ /**
150
+ * Append an item to the application menu bar
151
+ */
152
+ async appendToMenuBar(item: MenuItem): Promise<void> {
153
+ this.registerClickHandlers([item]);
154
+ const cleanItem = this.stripFunctions([item])[0];
155
+ await this.invokeFn('menu.appendToMenuBar', [cleanItem]);
156
+ }
157
+
158
+ // ========================================================================
159
+ // Context Menus (Right-Click)
160
+ // ========================================================================
161
+
162
+ /**
163
+ * Enable custom context menu, replacing the default browser/webview menu
164
+ */
165
+ enableContextMenu(items: MenuItem[]): void {
166
+ this.contextMenuItems = items;
167
+ this.contextMenuEnabled = true;
168
+ this.registerClickHandlers(items);
169
+ this.setupContextMenuHandler();
170
+ }
171
+
172
+ /**
173
+ * Disable custom context menu, restoring default behavior
174
+ */
175
+ disableContextMenu(): void {
176
+ this.contextMenuEnabled = false;
177
+ }
178
+
179
+ /**
180
+ * Set context menu items for a specific CSS selector
181
+ * Allows different menus for different elements
182
+ */
183
+ setContextMenuForSelector(selector: string, items: MenuItem[]): void {
184
+ this.selectorMenus.set(selector, items);
185
+ this.registerClickHandlers(items);
186
+ }
187
+
188
+ /**
189
+ * Show context menu programmatically
190
+ */
191
+ async showContextMenu(items: MenuItem[], options: ContextMenuOptions = {}): Promise<void> {
192
+ const menuId = await this.create(items);
193
+ await this.popup(menuId, options.x, options.y);
194
+ }
195
+
196
+ // ========================================================================
197
+ // Event Handling
198
+ // ========================================================================
199
+
200
+ /**
201
+ * Listen for any menu item click
202
+ */
203
+ onMenuItemClick(callback: (id: string, item?: MenuItem) => void): () => void {
204
+ return this.eventFn('menu:itemClick', (...args: unknown[]) => {
205
+ const id = args[0] as string;
206
+ callback(id);
207
+ });
208
+ }
209
+
210
+ /**
211
+ * Listen for context menu open
212
+ */
213
+ onContextMenuOpen(callback: (info: ContextInfo) => void): () => void {
214
+ return this.eventFn('menu:contextOpen', callback as (...args: unknown[]) => void);
215
+ }
216
+
217
+ // ========================================================================
218
+ // Predefined Menu Templates
219
+ // ========================================================================
220
+
221
+ /**
222
+ * Create a standard edit menu (Cut, Copy, Paste, etc.)
223
+ */
224
+ static createEditMenu(handlers?: Partial<{
225
+ undo: () => void;
226
+ redo: () => void;
227
+ cut: () => void;
228
+ copy: () => void;
229
+ paste: () => void;
230
+ selectAll: () => void;
231
+ }>): MenuItem {
232
+ return {
233
+ id: 'edit',
234
+ label: '&Edit',
235
+ submenu: [
236
+ { id: 'undo', label: 'Undo', accelerator: 'Ctrl+Z', click: handlers?.undo },
237
+ { id: 'redo', label: 'Redo', accelerator: 'Ctrl+Y', click: handlers?.redo },
238
+ { id: 'sep1', label: '', type: 'separator' },
239
+ { id: 'cut', label: 'Cut', accelerator: 'Ctrl+X', click: handlers?.cut },
240
+ { id: 'copy', label: 'Copy', accelerator: 'Ctrl+C', click: handlers?.copy },
241
+ { id: 'paste', label: 'Paste', accelerator: 'Ctrl+V', click: handlers?.paste },
242
+ { id: 'sep2', label: '', type: 'separator' },
243
+ { id: 'selectAll', label: 'Select All', accelerator: 'Ctrl+A', click: handlers?.selectAll },
244
+ ]
245
+ };
246
+ }
247
+
248
+ /**
249
+ * Create a standard file menu
250
+ */
251
+ static createFileMenu(handlers?: Partial<{
252
+ new: () => void;
253
+ open: () => void;
254
+ save: () => void;
255
+ saveAs: () => void;
256
+ exit: () => void;
257
+ }>): MenuItem {
258
+ return {
259
+ id: 'file',
260
+ label: '&File',
261
+ submenu: [
262
+ { id: 'new', label: 'New', accelerator: 'Ctrl+N', click: handlers?.new },
263
+ { id: 'open', label: 'Open...', accelerator: 'Ctrl+O', click: handlers?.open },
264
+ { id: 'sep1', label: '', type: 'separator' },
265
+ { id: 'save', label: 'Save', accelerator: 'Ctrl+S', click: handlers?.save },
266
+ { id: 'saveAs', label: 'Save As...', accelerator: 'Ctrl+Shift+S', click: handlers?.saveAs },
267
+ { id: 'sep2', label: '', type: 'separator' },
268
+ { id: 'exit', label: 'Exit', accelerator: 'Alt+F4', click: handlers?.exit },
269
+ ]
270
+ };
271
+ }
272
+
273
+ /**
274
+ * Create a standard view menu
275
+ */
276
+ static createViewMenu(handlers?: Partial<{
277
+ zoomIn: () => void;
278
+ zoomOut: () => void;
279
+ resetZoom: () => void;
280
+ fullscreen: () => void;
281
+ devtools: () => void;
282
+ }>): MenuItem {
283
+ return {
284
+ id: 'view',
285
+ label: '&View',
286
+ submenu: [
287
+ { id: 'zoomIn', label: 'Zoom In', accelerator: 'Ctrl++', click: handlers?.zoomIn },
288
+ { id: 'zoomOut', label: 'Zoom Out', accelerator: 'Ctrl+-', click: handlers?.zoomOut },
289
+ { id: 'resetZoom', label: 'Reset Zoom', accelerator: 'Ctrl+0', click: handlers?.resetZoom },
290
+ { id: 'sep1', label: '', type: 'separator' },
291
+ { id: 'fullscreen', label: 'Toggle Fullscreen', accelerator: 'F11', click: handlers?.fullscreen },
292
+ { id: 'sep2', label: '', type: 'separator' },
293
+ { id: 'devtools', label: 'Developer Tools', accelerator: 'F12', click: handlers?.devtools },
294
+ ]
295
+ };
296
+ }
297
+
298
+ /**
299
+ * Create a default context menu for text areas
300
+ */
301
+ static createTextContextMenu(): MenuItem[] {
302
+ return [
303
+ { id: 'cut', label: 'Cut', accelerator: 'Ctrl+X' },
304
+ { id: 'copy', label: 'Copy', accelerator: 'Ctrl+C' },
305
+ { id: 'paste', label: 'Paste', accelerator: 'Ctrl+V' },
306
+ { id: 'sep1', label: '', type: 'separator' },
307
+ { id: 'selectAll', label: 'Select All', accelerator: 'Ctrl+A' },
308
+ ];
309
+ }
310
+
311
+ /**
312
+ * Create a default context menu for images
313
+ */
314
+ static createImageContextMenu(): MenuItem[] {
315
+ return [
316
+ { id: 'copyImage', label: 'Copy Image' },
317
+ { id: 'saveImage', label: 'Save Image As...' },
318
+ { id: 'sep1', label: '', type: 'separator' },
319
+ { id: 'openInNewTab', label: 'Open Image in New Tab' },
320
+ ];
321
+ }
322
+
323
+ /**
324
+ * Create a default context menu for links
325
+ */
326
+ static createLinkContextMenu(): MenuItem[] {
327
+ return [
328
+ { id: 'openLink', label: 'Open Link' },
329
+ { id: 'openInNewTab', label: 'Open in New Tab' },
330
+ { id: 'sep1', label: '', type: 'separator' },
331
+ { id: 'copyLink', label: 'Copy Link Address' },
332
+ ];
333
+ }
334
+
335
+ // ========================================================================
336
+ // Private Helpers
337
+ // ========================================================================
338
+
339
+ private contextMenuHandlerSetup = false;
340
+
341
+ private setupContextMenuHandler(): void {
342
+ if (this.contextMenuHandlerSetup) return;
343
+ this.contextMenuHandlerSetup = true;
344
+
345
+ document.addEventListener('contextmenu', async (e) => {
346
+ if (!this.contextMenuEnabled) return;
347
+
348
+ e.preventDefault();
349
+
350
+ const target = e.target as HTMLElement;
351
+ const info: ContextInfo = {
352
+ x: e.screenX,
353
+ y: e.screenY,
354
+ clientX: e.clientX,
355
+ clientY: e.clientY,
356
+ selector: this.getElementSelector(target),
357
+ tagName: target.tagName,
358
+ isEditable: target.isContentEditable ||
359
+ target.tagName === 'INPUT' ||
360
+ target.tagName === 'TEXTAREA',
361
+ hasSelection: (window.getSelection()?.toString().length ?? 0) > 0,
362
+ selectedText: window.getSelection()?.toString()
363
+ };
364
+
365
+ // Find appropriate menu for this element
366
+ let items = this.contextMenuItems;
367
+
368
+ for (const [selector, menuItems] of this.selectorMenus) {
369
+ if (target.matches(selector)) {
370
+ items = menuItems;
371
+ break;
372
+ }
373
+ }
374
+
375
+ // Show the menu
376
+ await this.showContextMenu(items, { x: info.x, y: info.y, context: info as unknown as Record<string, unknown> });
377
+ });
378
+ }
379
+
380
+ private getElementSelector(el: HTMLElement): string {
381
+ let selector = el.tagName.toLowerCase();
382
+ if (el.id) selector += '#' + el.id;
383
+ if (el.className && typeof el.className === 'string') {
384
+ selector += '.' + el.className.split(' ').filter(Boolean).join('.');
385
+ }
386
+ return selector;
387
+ }
388
+
389
+ private registerClickHandlers(items: MenuItem[]): void {
390
+ for (const item of items) {
391
+ if (item.click) {
392
+ this.clickHandlers.set(item.id, item.click);
393
+ }
394
+ if (item.submenu) {
395
+ this.registerClickHandlers(item.submenu);
396
+ }
397
+ }
398
+ }
399
+
400
+ private handleItemClick(id: string): void {
401
+ const handler = this.clickHandlers.get(id);
402
+ if (handler) {
403
+ handler({ id, label: '' });
404
+ }
405
+ }
406
+
407
+ private stripFunctions(items: MenuItem[]): unknown[] {
408
+ return items.map(item => {
409
+ const { click, submenu, ...rest } = item;
410
+ const cleanItem: Record<string, unknown> = { ...rest };
411
+ if (submenu) {
412
+ cleanItem.submenu = this.stripFunctions(submenu);
413
+ }
414
+ return cleanItem;
415
+ });
416
+ }
417
+
418
+ /**
419
+ * Cleanup resources
420
+ */
421
+ dispose(): void {
422
+ this.unsubscribeClick?.();
423
+ this.clickHandlers.clear();
424
+ this.selectorMenus.clear();
425
+ }
426
+ }
427
+
428
+ // ============================================================================
429
+ // Factory Function
430
+ // ============================================================================
431
+
432
+ export function createMenuAPI(
433
+ invokeFn: (name: string, args?: unknown[]) => Promise<unknown>,
434
+ eventFn: (event: string, callback: (...args: unknown[]) => void) => () => void
435
+ ): MenuAPI {
436
+ return new MenuAPI(invokeFn, eventFn);
437
+ }
438
+
439
+ export default MenuAPI;