plusui-native 0.2.66 → 0.2.69
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/package.json +4 -4
- package/src/doctor/detectors/compiler.js +16 -20
- package/src/index.js +71 -56
- package/templates/react/frontend/src/App.tsx +7 -7
- package/templates/react/frontend/src/plusui.ts +535 -99
- package/templates/react/frontend/tsconfig.json +15 -8
- package/templates/react/frontend/vite.config.ts +3 -3
- package/templates/react/main.cpp.template +18 -3
- package/templates/solid/frontend/tsconfig.json +14 -7
- package/templates/solid/frontend/vite.config.ts +2 -3
- package/templates/solid/main.cpp.template +18 -3
- package/templates/solid/frontend/src/plusui.ts +0 -402
|
@@ -1,21 +1,3 @@
|
|
|
1
|
-
// ============================================================
|
|
2
|
-
// PlusUI — single import entrypoint
|
|
3
|
-
//
|
|
4
|
-
// import plusui from 'plusui';
|
|
5
|
-
//
|
|
6
|
-
// plusui.win.minimize();
|
|
7
|
-
// plusui.fileDrop.onFilesDropped(...);
|
|
8
|
-
// plusui.formatFileSize(bytes);
|
|
9
|
-
//
|
|
10
|
-
// For TypeScript types only:
|
|
11
|
-
// import type { FileInfo } from 'plusui';
|
|
12
|
-
//
|
|
13
|
-
// After running `plusui connect`, custom channel objects are
|
|
14
|
-
// exported from Connections/connections.gen.ts:
|
|
15
|
-
// import { myChannel } from '../Connections/connections.gen';
|
|
16
|
-
// ============================================================
|
|
17
|
-
|
|
18
|
-
// ─── Bridge bootstrap ────────────────────────────────────────────────────────
|
|
19
1
|
|
|
20
2
|
type InvokeFn = (method: string, args?: unknown[]) => Promise<unknown>;
|
|
21
3
|
type PendingMap = Record<string, { resolve: (value: unknown) => void; reject: (reason?: unknown) => void }>;
|
|
@@ -256,32 +238,32 @@ export const connect = {
|
|
|
256
238
|
feature: createFeatureConnect,
|
|
257
239
|
};
|
|
258
240
|
|
|
259
|
-
/** Advanced: raw connection client —
|
|
260
|
-
export
|
|
241
|
+
/** Advanced: raw connection client — used by generated code */
|
|
242
|
+
export { _client, _client as connection };
|
|
261
243
|
|
|
262
244
|
// ─── win — window management ──────────────────────────────────────────────────
|
|
263
245
|
const _winEvents = createFeatureConnect('window');
|
|
264
246
|
|
|
265
247
|
export const win = {
|
|
266
|
-
minimize:
|
|
267
|
-
maximize:
|
|
268
|
-
show:
|
|
269
|
-
hide:
|
|
270
|
-
close:
|
|
271
|
-
center:
|
|
272
|
-
setTitle:
|
|
273
|
-
setSize:
|
|
274
|
-
setMinSize:
|
|
275
|
-
setMaxSize:
|
|
276
|
-
setPosition:
|
|
248
|
+
minimize: async () => invoke('window.minimize', []),
|
|
249
|
+
maximize: async () => invoke('window.maximize', []),
|
|
250
|
+
show: async () => invoke('window.show', []),
|
|
251
|
+
hide: async () => invoke('window.hide', []),
|
|
252
|
+
close: async () => invoke('window.close', []),
|
|
253
|
+
center: async () => invoke('window.center', []),
|
|
254
|
+
setTitle: async (title: string) => invoke('window.setTitle', [title]),
|
|
255
|
+
setSize: async (w: number, h: number) => invoke('window.setSize', [w, h]),
|
|
256
|
+
setMinSize: async (w: number, h: number) => invoke('window.setMinSize', [w, h]),
|
|
257
|
+
setMaxSize: async (w: number, h: number) => invoke('window.setMaxSize', [w, h]),
|
|
258
|
+
setPosition: async (x: number, y: number) => invoke('window.setPosition', [x, y]),
|
|
277
259
|
setAlwaysOnTop: async (v: boolean) => invoke('window.setAlwaysOnTop', [v]),
|
|
278
|
-
setFullscreen:
|
|
279
|
-
setOpacity:
|
|
280
|
-
getSize:
|
|
281
|
-
getPosition:
|
|
282
|
-
isMaximized:
|
|
283
|
-
isVisible:
|
|
284
|
-
on:
|
|
260
|
+
setFullscreen: async (v: boolean) => invoke('window.setFullscreen', [v]),
|
|
261
|
+
setOpacity: async (v: number) => invoke('window.setOpacity', [v]),
|
|
262
|
+
getSize: async (): Promise<WindowSize> => invoke('window.getSize', []) as Promise<WindowSize>,
|
|
263
|
+
getPosition: async (): Promise<WindowPosition> => invoke('window.getPosition', []) as Promise<WindowPosition>,
|
|
264
|
+
isMaximized: async (): Promise<boolean> => invoke('window.isMaximized', []) as Promise<boolean>,
|
|
265
|
+
isVisible: async (): Promise<boolean> => invoke('window.isVisible', []) as Promise<boolean>,
|
|
266
|
+
on: _winEvents.on.bind(_winEvents),
|
|
285
267
|
emit: _winEvents.emit.bind(_winEvents),
|
|
286
268
|
};
|
|
287
269
|
|
|
@@ -289,20 +271,20 @@ export const win = {
|
|
|
289
271
|
const _browserEvents = createFeatureConnect('browser');
|
|
290
272
|
|
|
291
273
|
export const browser = {
|
|
292
|
-
getUrl:
|
|
293
|
-
navigate:
|
|
294
|
-
goBack:
|
|
295
|
-
goForward:
|
|
296
|
-
reload:
|
|
297
|
-
canGoBack:
|
|
274
|
+
getUrl: async (): Promise<string> => invoke('browser.getUrl', []) as Promise<string>,
|
|
275
|
+
navigate: async (url: string) => invoke('browser.navigate', [url]),
|
|
276
|
+
goBack: async () => invoke('browser.goBack', []),
|
|
277
|
+
goForward: async () => invoke('browser.goForward', []),
|
|
278
|
+
reload: async () => invoke('browser.reload', []),
|
|
279
|
+
canGoBack: async (): Promise<boolean> => invoke('browser.canGoBack', []) as Promise<boolean>,
|
|
298
280
|
canGoForward: async (): Promise<boolean> => invoke('browser.canGoForward', []) as Promise<boolean>,
|
|
299
281
|
onNavigate: (handler: (url: string) => void) => {
|
|
300
|
-
if (typeof window === 'undefined') return () => {};
|
|
282
|
+
if (typeof window === 'undefined') return () => { };
|
|
301
283
|
const h = (e: Event) => handler((e as CustomEvent<{ url?: string }>).detail?.url ?? '');
|
|
302
284
|
window.addEventListener('plusui:navigate', h);
|
|
303
285
|
return () => window.removeEventListener('plusui:navigate', h);
|
|
304
286
|
},
|
|
305
|
-
on:
|
|
287
|
+
on: _browserEvents.on.bind(_browserEvents),
|
|
306
288
|
emit: _browserEvents.emit.bind(_browserEvents),
|
|
307
289
|
};
|
|
308
290
|
|
|
@@ -316,8 +298,8 @@ export const router = {
|
|
|
316
298
|
const _appEvents = createFeatureConnect('app');
|
|
317
299
|
|
|
318
300
|
export const app = {
|
|
319
|
-
quit:
|
|
320
|
-
on:
|
|
301
|
+
quit: async () => invoke('app.quit', []),
|
|
302
|
+
on: _appEvents.on.bind(_appEvents),
|
|
321
303
|
emit: _appEvents.emit.bind(_appEvents),
|
|
322
304
|
};
|
|
323
305
|
|
|
@@ -325,11 +307,11 @@ export const app = {
|
|
|
325
307
|
const _clipboardEvents = createFeatureConnect('clipboard');
|
|
326
308
|
|
|
327
309
|
export const clipboard = {
|
|
328
|
-
getText:
|
|
329
|
-
setText:
|
|
330
|
-
clear:
|
|
331
|
-
hasText:
|
|
332
|
-
on:
|
|
310
|
+
getText: async (): Promise<string> => invoke('clipboard.getText', []) as Promise<string>,
|
|
311
|
+
setText: async (text: string) => invoke('clipboard.setText', [text]),
|
|
312
|
+
clear: async () => invoke('clipboard.clear', []),
|
|
313
|
+
hasText: async (): Promise<boolean> => invoke('clipboard.hasText', []) as Promise<boolean>,
|
|
314
|
+
on: _clipboardEvents.on.bind(_clipboardEvents),
|
|
333
315
|
emit: _clipboardEvents.emit.bind(_clipboardEvents),
|
|
334
316
|
};
|
|
335
317
|
|
|
@@ -338,64 +320,518 @@ export interface FileInfo { path: string; name: string; type: string; size: numb
|
|
|
338
320
|
|
|
339
321
|
export const fileDrop = {
|
|
340
322
|
setEnabled: async (enabled: boolean) => invoke('fileDrop.setEnabled', [enabled]),
|
|
341
|
-
isEnabled:
|
|
323
|
+
isEnabled: async (): Promise<boolean> => invoke('fileDrop.isEnabled', []) as Promise<boolean>,
|
|
342
324
|
onFilesDropped: (handler: (files: FileInfo[]) => void) => {
|
|
343
|
-
if (typeof window === 'undefined') return () => {};
|
|
325
|
+
if (typeof window === 'undefined') return () => { };
|
|
344
326
|
const h = (e: Event) => handler((e as CustomEvent<{ files?: FileInfo[] }>).detail?.files ?? []);
|
|
345
327
|
window.addEventListener('plusui:fileDrop.filesDropped', h);
|
|
346
328
|
return () => window.removeEventListener('plusui:fileDrop.filesDropped', h);
|
|
347
329
|
},
|
|
348
330
|
onDragEnter: (handler: () => void) => {
|
|
349
|
-
if (typeof window === 'undefined') return () => {};
|
|
331
|
+
if (typeof window === 'undefined') return () => { };
|
|
350
332
|
window.addEventListener('plusui:fileDrop.dragEnter', handler);
|
|
351
333
|
return () => window.removeEventListener('plusui:fileDrop.dragEnter', handler);
|
|
352
334
|
},
|
|
353
335
|
onDragLeave: (handler: () => void) => {
|
|
354
|
-
if (typeof window === 'undefined') return () => {};
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
336
|
+
if (typeof window === 'undefined') return () => { };
|
|
337
|
+
|
|
338
|
+
// ─── win — window management ──────────────────────────────────────────────────
|
|
339
|
+
const _winEvents = createFeatureConnect('window');
|
|
340
|
+
|
|
341
|
+
export const win = {
|
|
342
|
+
minimize: async () => invoke('window.minimize', []),
|
|
343
|
+
maximize: async () => invoke('window.maximize', []),
|
|
344
|
+
show: async () => invoke('window.show', []),
|
|
345
|
+
hide: async () => invoke('window.hide', []),
|
|
346
|
+
close: async () => invoke('window.close', []),
|
|
347
|
+
center: async () => invoke('window.center', []),
|
|
348
|
+
setTitle: async (title: string) => invoke('window.setTitle', [title]),
|
|
349
|
+
setSize: async (w: number, h: number) => invoke('window.setSize', [w, h]),
|
|
350
|
+
setMinSize: async (w: number, h: number) => invoke('window.setMinSize', [w, h]),
|
|
351
|
+
setMaxSize: async (w: number, h: number) => invoke('window.setMaxSize', [w, h]),
|
|
352
|
+
setPosition: async (x: number, y: number) => invoke('window.setPosition', [x, y]),
|
|
353
|
+
setAlwaysOnTop: async (v: boolean) => invoke('window.setAlwaysOnTop', [v]),
|
|
354
|
+
setFullscreen: async (v: boolean) => invoke('window.setFullscreen', [v]),
|
|
355
|
+
setOpacity: async (v: number) => invoke('window.setOpacity', [v]),
|
|
356
|
+
getSize: async (): Promise<WindowSize> => invoke('window.getSize', []) as Promise<WindowSize>,
|
|
357
|
+
getPosition: async (): Promise<WindowPosition> => invoke('window.getPosition', []) as Promise<WindowPosition>,
|
|
358
|
+
isMaximized: async (): Promise<boolean> => invoke('window.isMaximized', []) as Promise<boolean>,
|
|
359
|
+
isVisible: async (): Promise<boolean> => invoke('window.isVisible', []) as Promise<boolean>,
|
|
360
|
+
on: _winEvents.on.bind(_winEvents),
|
|
361
|
+
emit: _winEvents.emit.bind(_winEvents),
|
|
362
|
+
};
|
|
359
363
|
|
|
360
|
-
// ───
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
364
|
+
// ─── browser ──────────────────────────────────────────────────────────────────
|
|
365
|
+
const _browserEvents = createFeatureConnect('browser');
|
|
366
|
+
|
|
367
|
+
export const browser = {
|
|
368
|
+
getUrl: async (): Promise<string> => invoke('browser.getUrl', []) as Promise<string>,
|
|
369
|
+
navigate: async (url: string) => invoke('browser.navigate', [url]),
|
|
370
|
+
goBack: async () => invoke('browser.goBack', []),
|
|
371
|
+
goForward: async () => invoke('browser.goForward', []),
|
|
372
|
+
reload: async () => invoke('browser.reload', []),
|
|
373
|
+
canGoBack: async (): Promise<boolean> => invoke('browser.canGoBack', []) as Promise<boolean>,
|
|
374
|
+
canGoForward: async (): Promise<boolean> => invoke('browser.canGoForward', []) as Promise<boolean>,
|
|
375
|
+
onNavigate: (handler: (url: string) => void) => {
|
|
376
|
+
if (typeof window === 'undefined') return () => { };
|
|
377
|
+
const h = (e: Event) => handler((e as CustomEvent<{ url?: string }>).detail?.url ?? '');
|
|
378
|
+
window.addEventListener('plusui:navigate', h);
|
|
379
|
+
return () => window.removeEventListener('plusui:navigate', h);
|
|
380
|
+
},
|
|
381
|
+
on: _browserEvents.on.bind(_browserEvents),
|
|
382
|
+
emit: _browserEvents.emit.bind(_browserEvents),
|
|
383
|
+
};
|
|
368
384
|
|
|
369
|
-
|
|
385
|
+
// ─── router ───────────────────────────────────────────────────────────────────
|
|
386
|
+
export const router = {
|
|
387
|
+
setRoutes: (routes: RouteMap) => { _routes = routes; },
|
|
388
|
+
push: async (path: string) => invoke('browser.navigate', [_routes[path] ?? path]),
|
|
389
|
+
};
|
|
370
390
|
|
|
371
|
-
// ───
|
|
372
|
-
|
|
373
|
-
// import plusui from 'plusui';
|
|
374
|
-
//
|
|
375
|
-
// plusui.emit('myEvent', { value: 42 }); // TS → C++
|
|
376
|
-
// plusui.on('myEvent', (data) => { ... }); // C++ → TS
|
|
377
|
-
//
|
|
378
|
-
// plusui.win.minimize();
|
|
379
|
-
// plusui.clipboard.on('changed', (data) => { ... });
|
|
380
|
-
//
|
|
381
|
-
export const on = connect.on.bind(connect) as typeof connect.on;
|
|
382
|
-
export const emit = connect.emit.bind(connect) as typeof connect.emit;
|
|
391
|
+
// ─── app ──────────────────────────────────────────────────────────────────────
|
|
392
|
+
const _appEvents = createFeatureConnect('app');
|
|
383
393
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
394
|
+
export const app = {
|
|
395
|
+
quit: async () => invoke('app.quit', []),
|
|
396
|
+
on: _appEvents.on.bind(_appEvents),
|
|
397
|
+
emit: _appEvents.emit.bind(_appEvents),
|
|
398
|
+
};
|
|
399
|
+
|
|
400
|
+
// ─── clipboard ────────────────────────────────────────────────────────────────
|
|
401
|
+
const _clipboardEvents = createFeatureConnect('clipboard');
|
|
402
|
+
|
|
403
|
+
export const clipboard = {
|
|
404
|
+
getText: async (): Promise<string> => invoke('clipboard.getText', []) as Promise<string>,
|
|
405
|
+
setText: async (text: string) => invoke('clipboard.setText', [text]),
|
|
406
|
+
clear: async () => invoke('clipboard.clear', []),
|
|
407
|
+
hasText: async (): Promise<boolean> => invoke('clipboard.hasText', []) as Promise<boolean>,
|
|
408
|
+
on: _clipboardEvents.on.bind(_clipboardEvents),
|
|
409
|
+
emit: _clipboardEvents.emit.bind(_clipboardEvents),
|
|
410
|
+
};
|
|
411
|
+
|
|
412
|
+
// ─── fileDrop ─────────────────────────────────────────────────────────────────
|
|
413
|
+
export interface FileInfo { path: string; name: string; type: string; size: number; }
|
|
414
|
+
|
|
415
|
+
export const fileDrop = {
|
|
416
|
+
setEnabled: async (enabled: boolean) => invoke('fileDrop.setEnabled', [enabled]),
|
|
417
|
+
isEnabled: async (): Promise<boolean> => invoke('fileDrop.isEnabled', []) as Promise<boolean>,
|
|
418
|
+
onFilesDropped: (handler: (files: FileInfo[]) => void) => {
|
|
419
|
+
if (typeof window === 'undefined') return () => { };
|
|
420
|
+
const h = (e: Event) => handler((e as CustomEvent<{ files?: FileInfo[] }>).detail?.files ?? []);
|
|
421
|
+
window.addEventListener('plusui:fileDrop.filesDropped', h);
|
|
422
|
+
return () => window.removeEventListener('plusui:fileDrop.filesDropped', h);
|
|
423
|
+
},
|
|
424
|
+
onDragEnter: (handler: () => void) => {
|
|
425
|
+
if (typeof window === 'undefined') return () => { };
|
|
426
|
+
window.addEventListener('plusui:fileDrop.dragEnter', handler);
|
|
427
|
+
return () => window.removeEventListener('plusui:fileDrop.dragEnter', handler);
|
|
428
|
+
},
|
|
429
|
+
onDragLeave: (handler: () => void) => {
|
|
430
|
+
if (typeof window === 'undefined') return () => { };
|
|
431
|
+
window.addEventListener('plusui:fileDrop.dragLeave', handler);
|
|
432
|
+
return () => window.removeEventListener('plusui:fileDrop.dragLeave', handler);
|
|
433
|
+
},
|
|
434
|
+
};
|
|
435
|
+
|
|
436
|
+
// ─── keyboard ─────────────────────────────────────────────────────────────────
|
|
437
|
+
export enum KeyCode {
|
|
438
|
+
Unknown = 0, Space = 32, Escape = 256, Enter = 257, Tab = 258,
|
|
439
|
+
Backspace = 259, Delete = 261, Right = 262, Left = 263, Down = 264, Up = 265,
|
|
440
|
+
F1 = 290, F2 = 291, F3 = 292, F4 = 293, F5 = 294, F6 = 295,
|
|
441
|
+
F7 = 296, F8 = 297, F9 = 298, F10 = 299, F11 = 300, F12 = 301,
|
|
442
|
+
LeftShift = 340, LeftControl = 341, LeftAlt = 342,
|
|
443
|
+
}
|
|
444
|
+
export enum KeyMod { None = 0, Shift = 1, Control = 2, Alt = 4, Super = 8 }
|
|
445
|
+
export interface KeyEvent { key: KeyCode; scancode: number; mods: KeyMod; pressed: boolean; repeat: boolean; keyName: string; }
|
|
446
|
+
export interface Shortcut { key: KeyCode; mods: KeyMod; }
|
|
447
|
+
|
|
448
|
+
const _shortcutHandlers = new Map<string, () => void>();
|
|
449
|
+
|
|
450
|
+
export const keyboard = {
|
|
451
|
+
isKeyPressed: async (key: KeyCode): Promise<boolean> => invoke('keyboard.isKeyPressed', [key]) as Promise<boolean>,
|
|
452
|
+
setAutoRepeat: async (enabled: boolean): Promise<void> => { await invoke('keyboard.setAutoRepeat', [enabled]); },
|
|
453
|
+
getAutoRepeat: async (): Promise<boolean> => invoke('keyboard.getAutoRepeat') as Promise<boolean>,
|
|
454
|
+
async registerShortcut(id: string, shortcut: Shortcut, callback: () => void): Promise<boolean> {
|
|
455
|
+
_shortcutHandlers.set(id, callback);
|
|
456
|
+
return invoke<boolean>('keyboard.registerShortcut', [id, shortcut]);
|
|
457
|
+
},
|
|
458
|
+
async unregisterShortcut(id: string): Promise<boolean> {
|
|
459
|
+
_shortcutHandlers.delete(id);
|
|
460
|
+
return invoke<boolean>('keyboard.unregisterShortcut', [id]);
|
|
461
|
+
},
|
|
462
|
+
async clearShortcuts(): Promise<void> {
|
|
463
|
+
_shortcutHandlers.clear();
|
|
464
|
+
await invoke('keyboard.clearShortcuts');
|
|
465
|
+
},
|
|
466
|
+
onKeyDown(callback: (event: KeyEvent) => void): () => void {
|
|
467
|
+
if (typeof window === 'undefined') return () => { };
|
|
468
|
+
const h = (e: Event) => callback((e as CustomEvent<KeyEvent>).detail);
|
|
469
|
+
window.addEventListener('plusui:keyboard.keydown', h);
|
|
470
|
+
return () => window.removeEventListener('plusui:keyboard.keydown', h);
|
|
471
|
+
},
|
|
472
|
+
onKeyUp(callback: (event: KeyEvent) => void): () => void {
|
|
473
|
+
if (typeof window === 'undefined') return () => { };
|
|
474
|
+
const h = (e: Event) => callback((e as CustomEvent<KeyEvent>).detail);
|
|
475
|
+
window.addEventListener('plusui:keyboard.keyup', h);
|
|
476
|
+
return () => window.removeEventListener('plusui:keyboard.keyup', h);
|
|
477
|
+
},
|
|
478
|
+
onShortcut(callback: (id: string) => void): () => void {
|
|
479
|
+
if (typeof window === 'undefined') return () => { };
|
|
480
|
+
const h = (e: Event) => callback((e as CustomEvent<{ id: string }>).detail.id);
|
|
481
|
+
window.addEventListener('plusui:keyboard.shortcut', h);
|
|
482
|
+
return () => window.removeEventListener('plusui:keyboard.shortcut', h);
|
|
483
|
+
},
|
|
484
|
+
parseShortcut(str: string): Shortcut {
|
|
485
|
+
const parts = str.toLowerCase().split('+');
|
|
486
|
+
let mods = KeyMod.None;
|
|
487
|
+
let key = KeyCode.Unknown;
|
|
488
|
+
for (const part of parts) {
|
|
489
|
+
const t = part.trim();
|
|
490
|
+
if (t === 'ctrl' || t === 'control') mods |= KeyMod.Control;
|
|
491
|
+
else if (t === 'alt') mods |= KeyMod.Alt;
|
|
492
|
+
else if (t === 'shift') mods |= KeyMod.Shift;
|
|
493
|
+
else if (t === 'super' || t === 'win' || t === 'cmd') mods |= KeyMod.Super;
|
|
494
|
+
else key = this.keyNameToCode(t);
|
|
495
|
+
}
|
|
496
|
+
return { key, mods };
|
|
497
|
+
},
|
|
498
|
+
keyNameToCode(name: string): KeyCode {
|
|
499
|
+
const map: Record<string, KeyCode> = {
|
|
500
|
+
space: KeyCode.Space, escape: KeyCode.Escape, enter: KeyCode.Enter,
|
|
501
|
+
tab: KeyCode.Tab, backspace: KeyCode.Backspace, delete: KeyCode.Delete,
|
|
502
|
+
right: KeyCode.Right, left: KeyCode.Left, down: KeyCode.Down, up: KeyCode.Up,
|
|
503
|
+
f1: KeyCode.F1, f2: KeyCode.F2, f3: KeyCode.F3, f4: KeyCode.F4,
|
|
504
|
+
f5: KeyCode.F5, f6: KeyCode.F6, f7: KeyCode.F7, f8: KeyCode.F8,
|
|
505
|
+
f9: KeyCode.F9, f10: KeyCode.F10, f11: KeyCode.F11, f12: KeyCode.F12,
|
|
506
|
+
};
|
|
507
|
+
return map[name] ?? KeyCode.Unknown;
|
|
508
|
+
},
|
|
509
|
+
};
|
|
510
|
+
|
|
511
|
+
// ─── tray ─────────────────────────────────────────────────────────────────────
|
|
512
|
+
export interface TrayMenuItem { id: string; label: string; icon?: string; enabled?: boolean; checked?: boolean; separator?: boolean; submenu?: TrayMenuItem[]; }
|
|
513
|
+
export interface TrayIconData { id: number; tooltip: string; iconPath: string; isVisible: boolean; }
|
|
514
|
+
|
|
515
|
+
export const tray = {
|
|
516
|
+
setIcon: async (iconPath: string): Promise<void> => { await invoke('tray.setIcon', [iconPath]); },
|
|
517
|
+
setTooltip: async (tooltip: string): Promise<void> => { await invoke('tray.setTooltip', [tooltip]); },
|
|
518
|
+
setVisible: async (visible: boolean): Promise<void> => { await invoke('tray.setVisible', [visible]); },
|
|
519
|
+
setMenu: async (items: TrayMenuItem[]): Promise<void> => { await invoke('tray.setMenu', [items]); },
|
|
520
|
+
setContextMenu: async (items: TrayMenuItem[]): Promise<void> => { await invoke('tray.setContextMenu', [items]); },
|
|
521
|
+
onClick(callback: (x: number, y: number) => void): () => void {
|
|
522
|
+
if (typeof window === 'undefined') return () => { };
|
|
523
|
+
const h = (e: Event) => { const d = (e as CustomEvent<{ x: number; y: number }>).detail; callback(d.x, d.y); };
|
|
524
|
+
window.addEventListener('plusui:tray.click', h);
|
|
525
|
+
return () => window.removeEventListener('plusui:tray.click', h);
|
|
526
|
+
},
|
|
527
|
+
onDoubleClick(callback: () => void): () => void {
|
|
528
|
+
if (typeof window === 'undefined') return () => { };
|
|
529
|
+
window.addEventListener('plusui:tray.doubleClick', callback);
|
|
530
|
+
return () => window.removeEventListener('plusui:tray.doubleClick', callback);
|
|
531
|
+
},
|
|
532
|
+
onRightClick(callback: (x: number, y: number) => void): () => void {
|
|
533
|
+
if (typeof window === 'undefined') return () => { };
|
|
534
|
+
const h = (e: Event) => { const d = (e as CustomEvent<{ x: number; y: number }>).detail; callback(d.x, d.y); };
|
|
535
|
+
window.addEventListener('plusui:tray.rightClick', h);
|
|
536
|
+
return () => window.removeEventListener('plusui:tray.rightClick', h);
|
|
537
|
+
},
|
|
538
|
+
onMenuItemClick(callback: (id: string) => void): () => void {
|
|
539
|
+
if (typeof window === 'undefined') return () => { };
|
|
540
|
+
const h = (e: Event) => callback((e as CustomEvent<{ id: string }>).detail.id);
|
|
541
|
+
window.addEventListener('plusui:tray.menuItemClick', h);
|
|
542
|
+
return () => window.removeEventListener('plusui:tray.menuItemClick', h);
|
|
543
|
+
},
|
|
544
|
+
};
|
|
545
|
+
|
|
546
|
+
// ─── display ──────────────────────────────────────────────────────────────────
|
|
547
|
+
export interface DisplayMode { width: number; height: number; refreshRate: number; bitDepth: number; }
|
|
548
|
+
export interface DisplayBounds { x: number; y: number; width: number; height: number; }
|
|
549
|
+
export interface DisplayResolution { width: number; height: number; }
|
|
550
|
+
export interface Display {
|
|
551
|
+
id: number; name: string; isPrimary: boolean;
|
|
552
|
+
bounds: DisplayBounds; workArea: DisplayBounds; resolution: DisplayResolution;
|
|
553
|
+
currentMode: DisplayMode; scaleFactor: number; rotation: number;
|
|
554
|
+
isInternal: boolean; isConnected: boolean;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
export const display = {
|
|
558
|
+
getAllDisplays: async (): Promise<Display[]> => invoke<Display[]>('display.getAllDisplays'),
|
|
559
|
+
getPrimaryDisplay: async (): Promise<Display> => invoke<Display>('display.getPrimaryDisplay'),
|
|
560
|
+
getDisplayAt: async (x: number, y: number): Promise<Display> => invoke<Display>('display.getDisplayAt', [x, y]),
|
|
561
|
+
getDisplayAtCursor: async (): Promise<Display> => invoke<Display>('display.getDisplayAtCursor'),
|
|
562
|
+
getDisplayById: async (id: number): Promise<Display> => invoke<Display>('display.getDisplayById', [id]),
|
|
563
|
+
setDisplayMode: async (displayId: number, mode: DisplayMode): Promise<boolean> => invoke<boolean>('display.setDisplayMode', [displayId, mode]),
|
|
564
|
+
setPosition: async (displayId: number, x: number, y: number): Promise<boolean> => invoke<boolean>('display.setPosition', [displayId, x, y]),
|
|
565
|
+
turnOff: async (displayId: number): Promise<boolean> => invoke<boolean>('display.turnOff', [displayId]),
|
|
566
|
+
getScreenWidth: async (): Promise<number> => invoke<number>('screen.getWidth'),
|
|
567
|
+
getScreenHeight: async (): Promise<number> => invoke<number>('screen.getHeight'),
|
|
568
|
+
getScaleFactor: async (): Promise<number> => invoke<number>('screen.getScaleFactor'),
|
|
569
|
+
getRefreshRate: async (): Promise<number> => invoke<number>('screen.getRefreshRate'),
|
|
570
|
+
onConnected(callback: (d: Display) => void): () => void {
|
|
571
|
+
if (typeof window === 'undefined') return () => { };
|
|
572
|
+
const h = (e: Event) => callback((e as CustomEvent<Display>).detail);
|
|
573
|
+
window.addEventListener('plusui:display.connected', h);
|
|
574
|
+
return () => window.removeEventListener('plusui:display.connected', h);
|
|
575
|
+
},
|
|
576
|
+
onDisconnected(callback: (id: number) => void): () => void {
|
|
577
|
+
if (typeof window === 'undefined') return () => { };
|
|
578
|
+
const h = (e: Event) => callback((e as CustomEvent<{ id: number }>).detail.id);
|
|
579
|
+
window.addEventListener('plusui:display.disconnected', h);
|
|
580
|
+
return () => window.removeEventListener('plusui:display.disconnected', h);
|
|
581
|
+
},
|
|
582
|
+
onChanged(callback: (d: Display) => void): () => void {
|
|
583
|
+
if (typeof window === 'undefined') return () => { };
|
|
584
|
+
const h = (e: Event) => callback((e as CustomEvent<Display>).detail);
|
|
585
|
+
window.addEventListener('plusui:display.changed', h);
|
|
586
|
+
return () => window.removeEventListener('plusui:display.changed', h);
|
|
587
|
+
},
|
|
588
|
+
};
|
|
589
|
+
|
|
590
|
+
// ─── menu ─────────────────────────────────────────────────────────────────────
|
|
591
|
+
export type MenuItemType = 'normal' | 'separator' | 'submenu' | 'checkbox' | 'radio';
|
|
592
|
+
export interface MenuItem {
|
|
593
|
+
id: string; label: string; accelerator?: string; icon?: string;
|
|
594
|
+
type?: MenuItemType; enabled?: boolean; checked?: boolean;
|
|
595
|
+
submenu?: MenuItem[]; click?: (item: MenuItem) => void; data?: Record<string, unknown>;
|
|
596
|
+
}
|
|
597
|
+
export interface ContextMenuOptions { x?: number; y?: number; selector?: string; context?: Record<string, unknown>; }
|
|
598
|
+
export interface ContextInfo { x: number; y: number; clientX: number; clientY: number; selector: string; tagName: string; isEditable: boolean; hasSelection: boolean; selectedText?: string; }
|
|
599
|
+
|
|
600
|
+
const _menuClickHandlers = new Map<string, (item: MenuItem) => void>();
|
|
601
|
+
|
|
602
|
+
function _registerMenuClicks(items: MenuItem[]): void {
|
|
603
|
+
for (const item of items) {
|
|
604
|
+
if (item.click) _menuClickHandlers.set(item.id, item.click);
|
|
605
|
+
if (item.submenu) _registerMenuClicks(item.submenu);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
function _stripMenuFunctions(items: MenuItem[]): unknown[] {
|
|
610
|
+
return items.map(({ click: _c, submenu, ...rest }) => ({
|
|
611
|
+
...rest,
|
|
612
|
+
...(submenu ? { submenu: _stripMenuFunctions(submenu) } : {}),
|
|
613
|
+
}));
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
export const menu = {
|
|
617
|
+
async create(items: MenuItem[]): Promise<string> {
|
|
618
|
+
_registerMenuClicks(items);
|
|
619
|
+
return invoke<string>('menu.create', [_stripMenuFunctions(items)]);
|
|
620
|
+
},
|
|
621
|
+
popup: async (menuId: string, x?: number, y?: number): Promise<void> => { await invoke('menu.popup', [menuId, x ?? 0, y ?? 0]); },
|
|
622
|
+
popupAtCursor: async (menuId: string): Promise<void> => { await invoke('menu.popupAtCursor', [menuId]); },
|
|
623
|
+
close: async (menuId: string): Promise<void> => { await invoke('menu.close', [menuId]); },
|
|
624
|
+
destroy: async (menuId: string): Promise<void> => { await invoke('menu.destroy', [menuId]); },
|
|
625
|
+
async setApplicationMenu(items: MenuItem[]): Promise<void> {
|
|
626
|
+
_registerMenuClicks(items);
|
|
627
|
+
await invoke('menu.setApplicationMenu', [_stripMenuFunctions(items)]);
|
|
628
|
+
},
|
|
629
|
+
getApplicationMenu: async (): Promise<MenuItem[]> => invoke<MenuItem[]>('menu.getApplicationMenu'),
|
|
630
|
+
async appendToMenuBar(item: MenuItem): Promise<void> {
|
|
631
|
+
_registerMenuClicks([item]);
|
|
632
|
+
await invoke('menu.appendToMenuBar', [_stripMenuFunctions([item])[0]]);
|
|
633
|
+
},
|
|
634
|
+
async showContextMenu(items: MenuItem[], options: ContextMenuOptions = {}): Promise<void> {
|
|
635
|
+
const menuId = await menu.create(items);
|
|
636
|
+
await menu.popup(menuId, options.x, options.y);
|
|
637
|
+
},
|
|
638
|
+
onItemClick(callback: (id: string) => void): () => void {
|
|
639
|
+
if (typeof window === 'undefined') return () => { };
|
|
640
|
+
const h = (e: Event) => callback((e as CustomEvent<{ id: string }>).detail.id);
|
|
641
|
+
window.addEventListener('plusui:menu.itemClick', h);
|
|
642
|
+
return () => window.removeEventListener('plusui:menu.itemClick', h);
|
|
643
|
+
},
|
|
644
|
+
onContextOpen(callback: (info: ContextInfo) => void): () => void {
|
|
645
|
+
if (typeof window === 'undefined') return () => { };
|
|
646
|
+
const h = (e: Event) => callback((e as CustomEvent<ContextInfo>).detail);
|
|
647
|
+
window.addEventListener('plusui:menu.contextOpen', h);
|
|
648
|
+
return () => window.removeEventListener('plusui:menu.contextOpen', h);
|
|
649
|
+
},
|
|
650
|
+
createEditMenu(handlers?: Partial<{ undo: () => void; redo: () => void; cut: () => void; copy: () => void; paste: () => void; selectAll: () => void; }>): MenuItem {
|
|
651
|
+
return {
|
|
652
|
+
id: 'edit', label: '&Edit', submenu: [
|
|
653
|
+
{ id: 'undo', label: 'Undo', accelerator: 'Ctrl+Z', click: handlers?.undo },
|
|
654
|
+
{ id: 'redo', label: 'Redo', accelerator: 'Ctrl+Y', click: handlers?.redo },
|
|
655
|
+
{ id: 'sep1', label: '', type: 'separator' },
|
|
656
|
+
{ id: 'cut', label: 'Cut', accelerator: 'Ctrl+X', click: handlers?.cut },
|
|
657
|
+
{ id: 'copy', label: 'Copy', accelerator: 'Ctrl+C', click: handlers?.copy },
|
|
658
|
+
{ id: 'paste', label: 'Paste', accelerator: 'Ctrl+V', click: handlers?.paste },
|
|
659
|
+
{ id: 'sep2', label: '', type: 'separator' },
|
|
660
|
+
{ id: 'selectAll', label: 'Select All', accelerator: 'Ctrl+A', click: handlers?.selectAll },
|
|
661
|
+
]
|
|
662
|
+
};
|
|
663
|
+
},
|
|
664
|
+
createFileMenu(handlers?: Partial<{ new: () => void; open: () => void; save: () => void; saveAs: () => void; exit: () => void; }>): MenuItem {
|
|
665
|
+
return {
|
|
666
|
+
id: 'file', label: '&File', submenu: [
|
|
667
|
+
{ id: 'new', label: 'New', accelerator: 'Ctrl+N', click: handlers?.new },
|
|
668
|
+
{ id: 'open', label: 'Open...', accelerator: 'Ctrl+O', click: handlers?.open },
|
|
669
|
+
{ id: 'sep1', label: '', type: 'separator' },
|
|
670
|
+
{ id: 'save', label: 'Save', accelerator: 'Ctrl+S', click: handlers?.save },
|
|
671
|
+
{ id: 'saveAs', label: 'Save As...', accelerator: 'Ctrl+Shift+S', click: handlers?.saveAs },
|
|
672
|
+
{ id: 'sep2', label: '', type: 'separator' },
|
|
673
|
+
{ id: 'exit', label: 'Exit', accelerator: 'Alt+F4', click: handlers?.exit },
|
|
674
|
+
]
|
|
675
|
+
};
|
|
676
|
+
},
|
|
677
|
+
createViewMenu(handlers?: Partial<{ zoomIn: () => void; zoomOut: () => void; resetZoom: () => void; fullscreen: () => void; devtools: () => void; }>): MenuItem {
|
|
678
|
+
return {
|
|
679
|
+
id: 'view', label: '&View', submenu: [
|
|
680
|
+
{ id: 'zoomIn', label: 'Zoom In', accelerator: 'Ctrl++', click: handlers?.zoomIn },
|
|
681
|
+
{ id: 'zoomOut', label: 'Zoom Out', accelerator: 'Ctrl+-', click: handlers?.zoomOut },
|
|
682
|
+
{ id: 'resetZoom', label: 'Reset Zoom', accelerator: 'Ctrl+0', click: handlers?.resetZoom },
|
|
683
|
+
{ id: 'sep1', label: '', type: 'separator' },
|
|
684
|
+
{ id: 'fullscreen', label: 'Toggle Fullscreen', accelerator: 'F11', click: handlers?.fullscreen },
|
|
685
|
+
{ id: 'sep2', label: '', type: 'separator' },
|
|
686
|
+
{ id: 'devtools', label: 'Developer Tools', accelerator: 'F12', click: handlers?.devtools },
|
|
687
|
+
]
|
|
688
|
+
};
|
|
689
|
+
},
|
|
690
|
+
createTextContextMenu(): MenuItem[] { return [{ id: 'cut', label: 'Cut', accelerator: 'Ctrl+X' }, { id: 'copy', label: 'Copy', accelerator: 'Ctrl+C' }, { id: 'paste', label: 'Paste', accelerator: 'Ctrl+V' }, { id: 'sep1', label: '', type: 'separator' }, { id: 'selectAll', label: 'Select All', accelerator: 'Ctrl+A' }]; },
|
|
691
|
+
createImageContextMenu(): MenuItem[] { return [{ id: 'copyImage', label: 'Copy Image' }, { id: 'saveImage', label: 'Save Image As...' }, { id: 'sep1', label: '', type: 'separator' }, { id: 'openInNewTab', label: 'Open Image in New Tab' }]; },
|
|
692
|
+
createLinkContextMenu(): MenuItem[] { return [{ id: 'openLink', label: 'Open Link' }, { id: 'openInNewTab', label: 'Open in New Tab' }, { id: 'sep1', label: '', type: 'separator' }, { id: 'copyLink', label: 'Copy Link Address' }]; },
|
|
693
|
+
dispose() { _menuClickHandlers.clear(); },
|
|
694
|
+
};
|
|
695
|
+
|
|
696
|
+
// ─── gpu ──────────────────────────────────────────────────────────────────────
|
|
697
|
+
export interface GPUAdapter { requestDevice(descriptor?: GPUDeviceDescriptor): Promise<GPUDevice>; features: Set<string>; limits: Record<string, number>; info?: GPUAdapterInfo; }
|
|
698
|
+
export interface GPUAdapterInfo { vendor?: string; architecture?: string; device?: string; description?: string; }
|
|
699
|
+
export interface GPUDevice {
|
|
700
|
+
createBuffer(d: GPUBufferDescriptor): GPUBuffer;
|
|
701
|
+
createTexture(d: GPUTextureDescriptor): GPUTexture;
|
|
702
|
+
createSampler(d?: GPUSamplerDescriptor): GPUSampler;
|
|
703
|
+
createShaderModule(d: GPUShaderModuleDescriptor): GPUShaderModule;
|
|
704
|
+
createRenderPipeline(d: GPURenderPipelineDescriptor): GPURenderPipeline;
|
|
705
|
+
createComputePipeline(d: GPUComputePipelineDescriptor): GPUComputePipeline;
|
|
706
|
+
createBindGroupLayout(d: GPUBindGroupLayoutDescriptor): GPUBindGroupLayout;
|
|
707
|
+
createBindGroup(d: GPUBindGroupDescriptor): GPUBindGroup;
|
|
708
|
+
createCommandEncoder(d?: GPUCommandEncoderDescriptor): GPUCommandEncoder;
|
|
709
|
+
queue: GPUQueue; destroy(): void; lost?: Promise<GPUDeviceLostInfo>;
|
|
710
|
+
}
|
|
711
|
+
export interface GPUDeviceLostInfo { reason: 'unknown' | 'destroyed'; message?: string; }
|
|
712
|
+
export interface GPUBuffer { mapAsync(mode: number, offset?: number, size?: number): Promise<void>; getMappedRange(offset?: number, size?: number): ArrayBuffer; unmap(): void; destroy(): void; size: number; usage: number; mapState: 'unmapped' | 'pending' | 'mapped'; }
|
|
713
|
+
export interface GPUTexture { createView(d?: GPUTextureViewDescriptor): GPUTextureView; destroy(): void; width: number; height: number; depthOrArrayLayers: number; mipLevelCount: number; sampleCount: number; dimension: string; format: string; usage: number; }
|
|
714
|
+
export interface GPUTextureView { }
|
|
715
|
+
export interface GPUSampler { }
|
|
716
|
+
export interface GPUShaderModule { getCompilationInfo(): Promise<{ messages: Array<{ message: string; type: 'error' | 'warning' | 'info'; lineNum?: number; linePos?: number; }> }>; }
|
|
717
|
+
export interface GPURenderPipeline { getBindGroupLayout(index: number): GPUBindGroupLayout; }
|
|
718
|
+
export interface GPUComputePipeline { getBindGroupLayout(index: number): GPUBindGroupLayout; }
|
|
719
|
+
export interface GPUBindGroupLayout { }
|
|
720
|
+
export interface GPUBindGroup { }
|
|
721
|
+
export interface GPUQueue { submit(cbs: GPUCommandBuffer[]): void; writeBuffer(b: GPUBuffer, offset: number, data: ArrayBuffer | ArrayBufferView, dataOffset?: number, size?: number): void; onSubmittedWorkDone(): Promise<void>; }
|
|
722
|
+
export interface GPUCommandBuffer { }
|
|
723
|
+
export interface GPUCommandEncoder {
|
|
724
|
+
beginRenderPass(d: GPURenderPassDescriptor): GPURenderPassEncoder;
|
|
725
|
+
beginComputePass(d?: GPUComputePassDescriptor): GPUComputePassEncoder;
|
|
726
|
+
copyBufferToBuffer(src: GPUBuffer, srcOffset: number, dst: GPUBuffer, dstOffset: number, size: number): void;
|
|
727
|
+
finish(d?: { label?: string }): GPUCommandBuffer;
|
|
728
|
+
}
|
|
729
|
+
export interface GPURenderPassEncoder { setPipeline(p: GPURenderPipeline): void; setVertexBuffer(slot: number, b: GPUBuffer, offset?: number, size?: number): void; setIndexBuffer(b: GPUBuffer, fmt: string, offset?: number, size?: number): void; setBindGroup(index: number, bg: GPUBindGroup, offsets?: number[]): void; draw(vertexCount: number, instanceCount?: number, firstVertex?: number, firstInstance?: number): void; drawIndexed(indexCount: number, instanceCount?: number, firstIndex?: number, baseVertex?: number, firstInstance?: number): void; end(): void; }
|
|
730
|
+
export interface GPUComputePassEncoder { setPipeline(p: GPUComputePipeline): void; setBindGroup(index: number, bg: GPUBindGroup, offsets?: number[]): void; dispatchWorkgroups(x: number, y?: number, z?: number): void; end(): void; }
|
|
731
|
+
export interface GPUBufferDescriptor { size: number; usage: number; mappedAtCreation?: boolean; label?: string; }
|
|
732
|
+
export interface GPUTextureDescriptor { size: { width: number; height?: number; depthOrArrayLayers?: number }; mipLevelCount?: number; sampleCount?: number; dimension?: string; format: string; usage: number; label?: string; }
|
|
733
|
+
export interface GPUTextureViewDescriptor { format?: string; dimension?: string; baseMipLevel?: number; mipLevelCount?: number; baseArrayLayer?: number; arrayLayerCount?: number; label?: string; }
|
|
734
|
+
export interface GPUSamplerDescriptor { label?: string; addressModeU?: string; addressModeV?: string; magFilter?: string; minFilter?: string; }
|
|
735
|
+
export interface GPUShaderModuleDescriptor { code: string; label?: string; }
|
|
736
|
+
export interface GPURenderPipelineDescriptor { layout?: GPUPipelineLayout; vertex: { module: GPUShaderModule; entryPoint: string; buffers?: unknown[] }; primitive?: { topology?: string; cullMode?: string }; fragment?: { module: GPUShaderModule; entryPoint: string; targets: unknown[] }; label?: string; }
|
|
737
|
+
export interface GPUComputePipelineDescriptor { layout?: GPUPipelineLayout; compute: { module: GPUShaderModule; entryPoint: string }; label?: string; }
|
|
738
|
+
export interface GPUBindGroupLayoutDescriptor { entries: unknown[]; label?: string; }
|
|
739
|
+
export interface GPUBindGroupDescriptor { layout: GPUBindGroupLayout; entries: { binding: number; resource: unknown }[]; label?: string; }
|
|
740
|
+
export interface GPUCommandEncoderDescriptor { label?: string; }
|
|
741
|
+
export interface GPURenderPassDescriptor { colorAttachments: unknown[]; depthStencilAttachment?: unknown; label?: string; }
|
|
742
|
+
export interface GPUComputePassDescriptor { label?: string; }
|
|
743
|
+
export interface GPUPipelineLayout { }
|
|
744
|
+
export interface GPURequestAdapterOptions { powerPreference?: 'low-power' | 'high-performance'; forceFallbackAdapter?: boolean; }
|
|
745
|
+
export interface GPUDeviceDescriptor { requiredFeatures?: string[]; requiredLimits?: Record<string, number>; label?: string; }
|
|
746
|
+
export const GPUBufferUsage = { MAP_READ: 0x0001, MAP_WRITE: 0x0002, COPY_SRC: 0x0004, COPY_DST: 0x0008, INDEX: 0x0010, VERTEX: 0x0020, UNIFORM: 0x0040, STORAGE: 0x0080, INDIRECT: 0x0100, QUERY_RESOLVE: 0x0200 } as const;
|
|
747
|
+
export const GPUTextureUsage = { COPY_SRC: 0x0001, COPY_DST: 0x0002, TEXTURE_BINDING: 0x0004, STORAGE_BINDING: 0x0008, RENDER_ATTACHMENT: 0x0010 } as const;
|
|
748
|
+
export const GPUMapMode = { READ: 0x0001, WRITE: 0x0002 } as const;
|
|
749
|
+
export const GPUShaderStage = { VERTEX: 0x0001, FRAGMENT: 0x0002, COMPUTE: 0x0004 } as const;
|
|
750
|
+
export const GPUColorWrite = { RED: 0x1, GREEN: 0x2, BLUE: 0x4, ALPHA: 0x8, ALL: 0xF } as const;
|
|
751
|
+
|
|
752
|
+
export const gpu = {
|
|
753
|
+
async requestAdapter(options?: GPURequestAdapterOptions): Promise<GPUAdapter | null> {
|
|
754
|
+
const result = await invoke<any>('webgpu.requestAdapter', [options || {}]);
|
|
755
|
+
if (!result) return null;
|
|
756
|
+
return {
|
|
757
|
+
features: new Set<string>(result.features || []),
|
|
758
|
+
limits: result.limits || {},
|
|
759
|
+
info: result.info,
|
|
760
|
+
requestDevice: async (descriptor?: GPUDeviceDescriptor): Promise<GPUDevice> =>
|
|
761
|
+
invoke<any>('webgpu.requestDevice', [result.id, descriptor || {}]),
|
|
762
|
+
} as GPUAdapter;
|
|
763
|
+
},
|
|
764
|
+
getPreferredCanvasFormat(): string { return 'bgra8unorm'; },
|
|
765
|
+
onAdapterLost(callback: (info: GPUAdapterInfo) => void): () => void {
|
|
766
|
+
if (typeof window === 'undefined') return () => { };
|
|
767
|
+
const h = (e: Event) => callback((e as CustomEvent<GPUAdapterInfo>).detail);
|
|
768
|
+
window.addEventListener('plusui:webgpu.adapterLost', h);
|
|
769
|
+
return () => window.removeEventListener('plusui:webgpu.adapterLost', h);
|
|
770
|
+
},
|
|
771
|
+
onDeviceLost(callback: (info: GPUDeviceLostInfo) => void): () => void {
|
|
772
|
+
if (typeof window === 'undefined') return () => { };
|
|
773
|
+
const h = (e: Event) => callback((e as CustomEvent<GPUDeviceLostInfo>).detail);
|
|
774
|
+
window.addEventListener('plusui:webgpu.deviceLost', h);
|
|
775
|
+
return () => window.removeEventListener('plusui:webgpu.deviceLost', h);
|
|
776
|
+
},
|
|
777
|
+
onError(callback: (error: string) => void): () => void {
|
|
778
|
+
if (typeof window === 'undefined') return () => { };
|
|
779
|
+
const h = (e: Event) => callback((e as CustomEvent<{ error: string }>).detail.error);
|
|
780
|
+
window.addEventListener('plusui:webgpu.error', h);
|
|
781
|
+
return () => window.removeEventListener('plusui:webgpu.error', h);
|
|
782
|
+
},
|
|
783
|
+
};
|
|
784
|
+
|
|
785
|
+
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
786
|
+
export function formatFileSize(bytes: number): string {
|
|
787
|
+
if (bytes === 0) return '0 Bytes';
|
|
788
|
+
const k = 1024;
|
|
789
|
+
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
|
|
790
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
791
|
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
export function isImageFile(file: FileInfo): boolean { return file.type.startsWith('image/'); }
|
|
795
|
+
|
|
796
|
+
// ─── Top-level on / emit ─────────────────────────────────────────────────────
|
|
797
|
+
//
|
|
798
|
+
// import plusui from 'plusui';
|
|
799
|
+
//
|
|
800
|
+
// plusui.emit('myEvent', { value: 42 }); // TS → C++
|
|
801
|
+
// plusui.on('myEvent', (data) => { ... }); // C++ → TS
|
|
802
|
+
//
|
|
803
|
+
// plusui.win.minimize();
|
|
804
|
+
// plusui.clipboard.on('changed', (data) => { ... });
|
|
805
|
+
//
|
|
806
|
+
export const on = connect.on.bind(connect) as typeof connect.on;
|
|
807
|
+
export const emit = connect.emit.bind(connect) as typeof connect.emit;
|
|
808
|
+
|
|
809
|
+
// ─── Default export — everything under one roof ───────────────────────────────
|
|
810
|
+
const plusui = {
|
|
811
|
+
feature: createFeatureConnect,
|
|
812
|
+
connection: _client,
|
|
813
|
+
win,
|
|
814
|
+
browser,
|
|
815
|
+
router,
|
|
816
|
+
app,
|
|
817
|
+
clipboard,
|
|
818
|
+
fileDrop,
|
|
819
|
+
keyboard,
|
|
820
|
+
KeyCode,
|
|
821
|
+
KeyMod,
|
|
822
|
+
tray,
|
|
823
|
+
display,
|
|
824
|
+
menu,
|
|
825
|
+
gpu,
|
|
826
|
+
GPUBufferUsage,
|
|
827
|
+
GPUTextureUsage,
|
|
828
|
+
GPUMapMode,
|
|
829
|
+
GPUShaderStage,
|
|
830
|
+
GPUColorWrite,
|
|
831
|
+
formatFileSize,
|
|
832
|
+
isImageFile,
|
|
833
|
+
on,
|
|
834
|
+
emit,
|
|
835
|
+
};
|
|
400
836
|
|
|
401
|
-
export default plusui;
|
|
837
|
+
export default plusui;
|