plusui-native-core 0.1.50 → 0.1.53
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 +2 -3
- package/Core/Features/App/app.cpp +58 -1
- package/Core/Features/Connection/ARCHITECTURE.md +369 -0
- package/Core/Features/Connection/README.md +165 -0
- package/Core/Features/Connection/connection.cpp +57 -0
- package/Core/Features/Connection/connection.ts +186 -0
- package/Core/Features/Connection/examples/simple_tags_example.hpp +130 -0
- package/Core/Features/Connection/examples/simple_tags_example.ts +247 -0
- package/Core/Features/Window/window.cpp +187 -151
- package/Core/generated/bridge.hpp +302 -0
- package/Core/include/plusui/connection.hpp +145 -0
- package/Core/include/plusui/plusui.hpp +1 -3
- package/Core/include/plusui/window.hpp +17 -19
- package/package.json +1 -1
- package/Core/Features/Bindings/ARCHITECTURE.md +0 -328
- package/Core/Features/Bindings/CustomBindings/custom_bindings.cpp +0 -55
- package/Core/Features/Bindings/CustomBindings/custom_bindings.ts +0 -35
- package/Core/Features/Bindings/EXAMPLE_USAGE.hpp +0 -143
- package/Core/Features/Bindings/EXAMPLE_USAGE.tsx +0 -210
- package/Core/Features/Bindings/IPC_GUIDE.md +0 -325
- package/Core/Features/Bindings/NativeBindings/native_bindings.cpp +0 -30
- package/Core/Features/Bindings/NativeBindings/native_bindings.ts +0 -29
- package/Core/Features/Bindings/UNIFIED_SYSTEM.md +0 -351
- package/Core/Features/Event/Events.ts +0 -166
- package/Core/Features/Event/events.cpp +0 -200
- package/Core/include/plusui/bindings.hpp +0 -65
- package/Core/include/plusui/custom_bindings.hpp +0 -17
- package/Core/include/plusui/events.hpp +0 -58
|
@@ -1,351 +0,0 @@
|
|
|
1
|
-
# PlusUI Unified Binding System
|
|
2
|
-
|
|
3
|
-
## One Macro to Rule Them All: `PLUSUI_BIND`
|
|
4
|
-
|
|
5
|
-
The PlusUI framework now uses a **single, unified macro** for all frontend ↔ backend communication.
|
|
6
|
-
|
|
7
|
-
### Simple. Type-Safe. Bidirectional.
|
|
8
|
-
|
|
9
|
-
```cpp
|
|
10
|
-
PLUSUI_BIND(name, returnType, ...paramTypes)
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
## How It Works
|
|
14
|
-
|
|
15
|
-
### 1. Define Bindings in C++
|
|
16
|
-
|
|
17
|
-
```cpp
|
|
18
|
-
// src/features/myapp.hpp
|
|
19
|
-
#pragma once
|
|
20
|
-
#include <plusui/bindings.hpp>
|
|
21
|
-
|
|
22
|
-
//====================================
|
|
23
|
-
// Frontend → Backend (Methods)
|
|
24
|
-
//====================================
|
|
25
|
-
PLUSUI_BIND(getUserName, string, void)
|
|
26
|
-
PLUSUI_BIND(setTheme, void, string)
|
|
27
|
-
PLUSUI_BIND(calculate, int, int, int)
|
|
28
|
-
PLUSUI_BIND(queryDatabase, json, string)
|
|
29
|
-
|
|
30
|
-
//====================================
|
|
31
|
-
// Backend → Frontend (Events)
|
|
32
|
-
// Names starting with "on" = events
|
|
33
|
-
//====================================
|
|
34
|
-
PLUSUI_BIND(onDataUpdated, void, json)
|
|
35
|
-
PLUSUI_BIND(onProgressUpdate, void, int)
|
|
36
|
-
PLUSUI_BIND(onError, void, string)
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
### 2. Run Code Generation
|
|
40
|
-
|
|
41
|
-
```bash
|
|
42
|
-
# Generate type-safe TypeScript bindings
|
|
43
|
-
plusui generate
|
|
44
|
-
|
|
45
|
-
# Or directly:
|
|
46
|
-
npx plusui-bindgen . ./src/Bindings
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
### 3. Use in TypeScript
|
|
50
|
-
|
|
51
|
-
```typescript
|
|
52
|
-
// Auto-generated: ./src/Bindings/bindings.gen.ts
|
|
53
|
-
import { plusui } from './bindings.gen';
|
|
54
|
-
|
|
55
|
-
//====================================
|
|
56
|
-
// Call Backend Methods
|
|
57
|
-
//====================================
|
|
58
|
-
const name = await plusui.myapp.getUserName();
|
|
59
|
-
await plusui.myapp.setTheme('dark');
|
|
60
|
-
const result = await plusui.myapp.calculate(10, 20);
|
|
61
|
-
const data = await plusui.myapp.queryDatabase('SELECT * FROM users');
|
|
62
|
-
|
|
63
|
-
//====================================
|
|
64
|
-
// Listen to Backend Events
|
|
65
|
-
//====================================
|
|
66
|
-
plusui.myapp.onDataUpdated((data) => {
|
|
67
|
-
console.log('Data changed:', data);
|
|
68
|
-
updateUI(data);
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
const unsubscribe = plusui.myapp.onProgressUpdate((percent) => {
|
|
72
|
-
setProgress(percent);
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
plusui.myapp.onError((error) => {
|
|
76
|
-
toast.error(error);
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
// Clean up when component unmounts
|
|
80
|
-
unsubscribe();
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
### 4. Implement in C++
|
|
84
|
-
|
|
85
|
-
```cpp
|
|
86
|
-
// src/features/myapp.cpp
|
|
87
|
-
#include "myapp.hpp"
|
|
88
|
-
#include <plusui/bindings.hpp>
|
|
89
|
-
|
|
90
|
-
using json = nlohmann::json;
|
|
91
|
-
|
|
92
|
-
//====================================
|
|
93
|
-
// Implement Methods
|
|
94
|
-
//====================================
|
|
95
|
-
std::string getUserName() {
|
|
96
|
-
return "PlusUI Developer";
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
void setTheme(const std::string& theme) {
|
|
100
|
-
applyTheme(theme);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
int calculate(int x, int y) {
|
|
104
|
-
return x + y;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
json queryDatabase(const std::string& sql) {
|
|
108
|
-
return db.execute(sql);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
//====================================
|
|
112
|
-
// Emit Events from C++
|
|
113
|
-
//====================================
|
|
114
|
-
void onDataChanged() {
|
|
115
|
-
json update = {
|
|
116
|
-
{"timestamp", getCurrentTime()},
|
|
117
|
-
{"records", getRecordCount()}
|
|
118
|
-
};
|
|
119
|
-
plusui::bindings::emitToFrontend("onDataUpdated", update);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
void processFile(const std::string& path) {
|
|
123
|
-
for (int i = 0; i < 100; i++) {
|
|
124
|
-
// Do work...
|
|
125
|
-
|
|
126
|
-
// Emit progress
|
|
127
|
-
plusui::bindings::emitToFrontend("onProgressUpdate", i);
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
## Two Categories of Bindings
|
|
133
|
-
|
|
134
|
-
### NativeBindings (Core Framework)
|
|
135
|
-
|
|
136
|
-
Auto-detected based on your TypeScript imports:
|
|
137
|
-
|
|
138
|
-
```typescript
|
|
139
|
-
import { window, app, display } from 'plusui-native-core';
|
|
140
|
-
|
|
141
|
-
// These automatically generate native bindings:
|
|
142
|
-
await window.minimize();
|
|
143
|
-
await app.quit();
|
|
144
|
-
const displays = await display.getAll();
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
**Features:**
|
|
148
|
-
- Window (minimize, maximize, show, hide, etc.)
|
|
149
|
-
- App (quit, ready lifecycle)
|
|
150
|
-
- Display (get displays)
|
|
151
|
-
- Clipboard (read/write)
|
|
152
|
-
- Keyboard (shortcuts)
|
|
153
|
-
- Menu (native menus)
|
|
154
|
-
- Tray (system tray)
|
|
155
|
-
- Browser (open URLs)
|
|
156
|
-
|
|
157
|
-
### CustomBindings (Your App Logic)
|
|
158
|
-
|
|
159
|
-
Defined by **you** using `PLUSUI_BIND`:
|
|
160
|
-
|
|
161
|
-
```cpp
|
|
162
|
-
// Your custom features
|
|
163
|
-
PLUSUI_BIND(authenticateUser, bool, string, string)
|
|
164
|
-
PLUSUI_BIND(loadFile, string, string)
|
|
165
|
-
PLUSUI_BIND(saveFile, void, string, string)
|
|
166
|
-
PLUSUI_BIND(onFileChanged, void, json)
|
|
167
|
-
```
|
|
168
|
-
|
|
169
|
-
## Generated File Structure
|
|
170
|
-
|
|
171
|
-
After running `plusui generate`:
|
|
172
|
-
|
|
173
|
-
```
|
|
174
|
-
src/Bindings/
|
|
175
|
-
├── bindings.gen.ts # Main unified TypeScript export
|
|
176
|
-
├── bindings.gen.hpp # C++ binding metadata
|
|
177
|
-
├── bindings.report.json # Generation report
|
|
178
|
-
│
|
|
179
|
-
├── NativeBindings/
|
|
180
|
-
│ ├── CPP_IO/
|
|
181
|
-
│ │ ├── core.bindings.gen.hpp
|
|
182
|
-
│ │ └── core.bindings.gen.cpp
|
|
183
|
-
│ └── WEB_IO/
|
|
184
|
-
│ └── core.bindings.gen.ts
|
|
185
|
-
│
|
|
186
|
-
└── CustomBindings/
|
|
187
|
-
├── CPP_IO/
|
|
188
|
-
│ ├── custom.bindings.gen.hpp
|
|
189
|
-
│ └── custom.bindings.gen.cpp
|
|
190
|
-
└── WEB_IO/
|
|
191
|
-
└── custom.bindings.gen.ts
|
|
192
|
-
|
|
193
|
-
include/Bindings/ # Mirrored C++ headers for imports
|
|
194
|
-
├── bindings.gen.hpp
|
|
195
|
-
├── NativeBindings/
|
|
196
|
-
│ └── CPP_IO/
|
|
197
|
-
│ └── core.bindings.gen.hpp
|
|
198
|
-
└── CustomBindings/
|
|
199
|
-
└── CPP_IO/
|
|
200
|
-
└── custom.bindings.gen.hpp
|
|
201
|
-
```
|
|
202
|
-
|
|
203
|
-
## Type Mapping
|
|
204
|
-
|
|
205
|
-
| C++ Type | TypeScript Type | Example |
|
|
206
|
-
|-----------------------|-----------------|-----------------|
|
|
207
|
-
| `void` | `void` | No return |
|
|
208
|
-
| `bool` | `boolean` | `true`, `false` |
|
|
209
|
-
| `int`, `float`, `double` | `number` | `42`, `3.14` |
|
|
210
|
-
| `string`, `std::string` | `string` | `"hello"` |
|
|
211
|
-
| `json`, `nlohmann::json` | `any` | Objects, arrays |
|
|
212
|
-
|
|
213
|
-
## Naming Conventions
|
|
214
|
-
|
|
215
|
-
### Methods (Frontend → Backend)
|
|
216
|
-
- Any descriptive name
|
|
217
|
-
- Examples: `getUserData`, `calculate`, `saveFile`, `queryDatabase`
|
|
218
|
-
|
|
219
|
-
### Events (Backend → Frontend)
|
|
220
|
-
- **Must start with "on" prefix**
|
|
221
|
-
- Examples: `onDataChanged`, `onError`, `onProgress`, `onFileUpdated`
|
|
222
|
-
|
|
223
|
-
This makes the direction clear at a glance!
|
|
224
|
-
|
|
225
|
-
## Import Styles
|
|
226
|
-
|
|
227
|
-
### Recommended: Unified Import
|
|
228
|
-
|
|
229
|
-
```typescript
|
|
230
|
-
import { plusui } from './bindings.gen';
|
|
231
|
-
|
|
232
|
-
// Access everything through plusui
|
|
233
|
-
await plusui.myFeature.myMethod();
|
|
234
|
-
plusui.myFeature.onEvent((data) => { });
|
|
235
|
-
```
|
|
236
|
-
|
|
237
|
-
### Alternative: Individual Imports
|
|
238
|
-
|
|
239
|
-
```typescript
|
|
240
|
-
import { myFeature, otherFeature } from './bindings.gen';
|
|
241
|
-
|
|
242
|
-
// Direct access
|
|
243
|
-
await myFeature.myMethod();
|
|
244
|
-
myFeature.onEvent((data) => { });
|
|
245
|
-
```
|
|
246
|
-
|
|
247
|
-
## Best Practices
|
|
248
|
-
|
|
249
|
-
✅ **Use PLUSUI_BIND for Everything**
|
|
250
|
-
- Don't mix with manual bindings
|
|
251
|
-
- One system = simple codebase
|
|
252
|
-
|
|
253
|
-
✅ **Event Names Start with "on"**
|
|
254
|
-
- Makes direction obvious
|
|
255
|
-
- `onProgress`, `onError`, `onUpdate`
|
|
256
|
-
|
|
257
|
-
✅ **Group Related Bindings**
|
|
258
|
-
- `database.hpp` for DB bindings
|
|
259
|
-
- `auth.hpp` for auth bindings
|
|
260
|
-
- etc.
|
|
261
|
-
|
|
262
|
-
✅ **Run Generate After Changes**
|
|
263
|
-
```bash
|
|
264
|
-
plusui generate
|
|
265
|
-
```
|
|
266
|
-
|
|
267
|
-
✅ **Use `json` for Complex Data**
|
|
268
|
-
```cpp
|
|
269
|
-
PLUSUI_BIND(getUser, json, int) // Not: (string, string, int, bool, ...)
|
|
270
|
-
```
|
|
271
|
-
|
|
272
|
-
✅ **Clean Up Event Listeners**
|
|
273
|
-
```typescript
|
|
274
|
-
const unsubscribe = plusui.feature.onEvent(() => {});
|
|
275
|
-
// Later:
|
|
276
|
-
unsubscribe();
|
|
277
|
-
```
|
|
278
|
-
|
|
279
|
-
## Migration from Old System
|
|
280
|
-
|
|
281
|
-
### Before (Multiple Macros)
|
|
282
|
-
```cpp
|
|
283
|
-
PLUSUI_METHOD(getUserName, string, void)
|
|
284
|
-
PLUSUI_EVENT(onDataChanged, json)
|
|
285
|
-
PLUSUI_SERVICE(dataService, json, string)
|
|
286
|
-
PLUSUI_STREAM(logStream, string)
|
|
287
|
-
```
|
|
288
|
-
|
|
289
|
-
### After (Unified)
|
|
290
|
-
```cpp
|
|
291
|
-
PLUSUI_BIND(getUserName, string, void)
|
|
292
|
-
PLUSUI_BIND(onDataChanged, void, json)
|
|
293
|
-
PLUSUI_BIND(dataService, json, string)
|
|
294
|
-
PLUSUI_BIND(logStream, void, string) // Events use void + "on" prefix
|
|
295
|
-
```
|
|
296
|
-
|
|
297
|
-
## Runtime/Dynamic Bindings (Advanced)
|
|
298
|
-
|
|
299
|
-
For advanced scenarios (plugins, hot-reload):
|
|
300
|
-
|
|
301
|
-
```typescript
|
|
302
|
-
import { registerMethod, registerEvent } from './bindings/custom_bindings';
|
|
303
|
-
|
|
304
|
-
// Register at runtime
|
|
305
|
-
registerMethod('dynamicMethod', async (params) => {
|
|
306
|
-
return processParams(params);
|
|
307
|
-
});
|
|
308
|
-
|
|
309
|
-
registerEvent('dynamicEvent', (data) => {
|
|
310
|
-
console.log('Dynamic event:', data);
|
|
311
|
-
});
|
|
312
|
-
```
|
|
313
|
-
|
|
314
|
-
## Under the Hood
|
|
315
|
-
|
|
316
|
-
### Compile Time
|
|
317
|
-
1. `plusui-bindgen` scans all C++ files for `PLUSUI_BIND`
|
|
318
|
-
2. Parses type signatures
|
|
319
|
-
3. Auto-detects direction (method vs event)
|
|
320
|
-
4. Generates TypeScript classes with proper types
|
|
321
|
-
5. Generates C++ registration metadata
|
|
322
|
-
|
|
323
|
-
### Runtime
|
|
324
|
-
1. Frontend calls `plusui.feature.method(args)`
|
|
325
|
-
2. TypeScript bridge serializes as JSON
|
|
326
|
-
3. WebView IPC sends to C++ via `__invoke__`
|
|
327
|
-
4. C++ dispatcher routes to registered handler
|
|
328
|
-
5. Result serialized back to frontend
|
|
329
|
-
6. Promise resolves with typed result
|
|
330
|
-
|
|
331
|
-
### Events
|
|
332
|
-
1. C++ calls `plusui::bindings::emitToFrontend(name, data)`
|
|
333
|
-
2. Serializes JSON data
|
|
334
|
-
3. WebView dispatches as CustomEvent
|
|
335
|
-
4. Frontend listeners receive typed data
|
|
336
|
-
|
|
337
|
-
## Summary
|
|
338
|
-
|
|
339
|
-
**Old Way:**
|
|
340
|
-
- Multiple macros (PLUSUI_METHOD, PLUSUI_EVENT, PLUSUI_SERVICE, PLUSUI_STREAM)
|
|
341
|
-
- Confusing overlap between Events and Bindings
|
|
342
|
-
- Manual type mapping
|
|
343
|
-
|
|
344
|
-
**New Way:**
|
|
345
|
-
- **One macro**: `PLUSUI_BIND`
|
|
346
|
-
- **Simple naming**: methods vs events (starting with "on")
|
|
347
|
-
- **Auto-generated**: Run `plusui generate`
|
|
348
|
-
- **Type-safe**: Full TypeScript autocomplete
|
|
349
|
-
- **Bidirectional**: Methods and events in one system
|
|
350
|
-
|
|
351
|
-
**Result:** Clean, simple, powerful! 🚀
|
|
@@ -1,166 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* PlusUI Event System - scalable bidirectional events
|
|
3
|
-
*
|
|
4
|
-
* Backend:
|
|
5
|
-
* event::on("myEvent", callback) // Listen for events from frontend
|
|
6
|
-
* event::once("myEvent", callback) // Listen once
|
|
7
|
-
* event::emit("myEvent", data) // Send events to frontend
|
|
8
|
-
*
|
|
9
|
-
* Frontend:
|
|
10
|
-
* const stop = event.on("myEvent", callback) // Listen + unsubscribe
|
|
11
|
-
* event.once("myEvent", callback) // Listen once
|
|
12
|
-
* event.emit("myEvent", data) // Send events to backend
|
|
13
|
-
*
|
|
14
|
-
* Users create custom event names anywhere.
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
type EventCallback<T = unknown> = (data: T) => void;
|
|
18
|
-
type AnyEventCallback = (eventName: string, data: unknown) => void;
|
|
19
|
-
export type Unsubscribe = () => void;
|
|
20
|
-
|
|
21
|
-
const listeners = new Map<string, Set<EventCallback>>();
|
|
22
|
-
const anyListeners = new Set<AnyEventCallback>();
|
|
23
|
-
const attachedDomBridges = new Set<string>();
|
|
24
|
-
|
|
25
|
-
let _invoke: ((method: string, args?: unknown[]) => Promise<unknown>) | null = null;
|
|
26
|
-
|
|
27
|
-
export function setInvokeFn(fn: (method: string, args?: unknown[]) => Promise<unknown>) {
|
|
28
|
-
_invoke = fn;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
async function invoke(method: string, args?: unknown[]): Promise<unknown> {
|
|
32
|
-
if (!_invoke) {
|
|
33
|
-
if (typeof window !== 'undefined' && (window as any).__invoke__) {
|
|
34
|
-
_invoke = (window as any).__invoke__;
|
|
35
|
-
} else {
|
|
36
|
-
throw new Error('Event API not initialized');
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
return _invoke!(method, args);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function validateEventName(name: string): void {
|
|
43
|
-
if (!name || !name.trim()) {
|
|
44
|
-
throw new Error('Event name must be a non-empty string');
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function emitToLocalListeners(name: string, data: unknown): void {
|
|
49
|
-
const eventListeners = listeners.get(name);
|
|
50
|
-
if (eventListeners && eventListeners.size > 0) {
|
|
51
|
-
for (const callback of Array.from(eventListeners)) {
|
|
52
|
-
callback(data);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
for (const callback of Array.from(anyListeners)) {
|
|
57
|
-
callback(name, data);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function ensureDomBridge(name: string): void {
|
|
62
|
-
if (typeof window === 'undefined' || attachedDomBridges.has(name)) {
|
|
63
|
-
return;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const domEvent = `plusui:event:${name}`;
|
|
67
|
-
const handler = (event: Event) => {
|
|
68
|
-
emitToLocalListeners(name, (event as CustomEvent).detail);
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
window.addEventListener(domEvent, handler as EventListener);
|
|
72
|
-
attachedDomBridges.add(name);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
export const event = {
|
|
76
|
-
/**
|
|
77
|
-
* Listen for events from backend
|
|
78
|
-
* Returns an unsubscribe function.
|
|
79
|
-
*/
|
|
80
|
-
on: <T = unknown>(name: string, callback: EventCallback<T>): Unsubscribe => {
|
|
81
|
-
validateEventName(name);
|
|
82
|
-
|
|
83
|
-
if (!listeners.has(name)) {
|
|
84
|
-
listeners.set(name, new Set());
|
|
85
|
-
}
|
|
86
|
-
listeners.get(name)!.add(callback as EventCallback);
|
|
87
|
-
|
|
88
|
-
ensureDomBridge(name);
|
|
89
|
-
|
|
90
|
-
return () => {
|
|
91
|
-
event.off(name, callback as EventCallback);
|
|
92
|
-
};
|
|
93
|
-
},
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Listen once for an event from backend.
|
|
97
|
-
*/
|
|
98
|
-
once: <T = unknown>(name: string, callback: EventCallback<T>): Unsubscribe => {
|
|
99
|
-
validateEventName(name);
|
|
100
|
-
|
|
101
|
-
let unsubscribe: Unsubscribe = () => {};
|
|
102
|
-
const wrapped = (data: unknown) => {
|
|
103
|
-
unsubscribe();
|
|
104
|
-
callback(data as T);
|
|
105
|
-
};
|
|
106
|
-
|
|
107
|
-
unsubscribe = event.on(name, wrapped);
|
|
108
|
-
return unsubscribe;
|
|
109
|
-
},
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Listen to every incoming backend event that has a registered bridge.
|
|
113
|
-
*/
|
|
114
|
-
onAny: (callback: AnyEventCallback): Unsubscribe => {
|
|
115
|
-
if (typeof callback !== 'function') {
|
|
116
|
-
throw new Error('onAny callback must be a function');
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
anyListeners.add(callback);
|
|
120
|
-
|
|
121
|
-
return () => {
|
|
122
|
-
anyListeners.delete(callback);
|
|
123
|
-
};
|
|
124
|
-
},
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* Remove one callback from a named event.
|
|
128
|
-
*/
|
|
129
|
-
off: (name: string, callback: EventCallback): boolean => {
|
|
130
|
-
validateEventName(name);
|
|
131
|
-
|
|
132
|
-
const set = listeners.get(name);
|
|
133
|
-
if (!set) {
|
|
134
|
-
return false;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
const removed = set.delete(callback);
|
|
138
|
-
if (set.size === 0) {
|
|
139
|
-
listeners.delete(name);
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
return removed;
|
|
143
|
-
},
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* Remove all listeners. If name is set, clear only that event.
|
|
147
|
-
*/
|
|
148
|
-
clear: (name?: string): void => {
|
|
149
|
-
if (name === undefined) {
|
|
150
|
-
listeners.clear();
|
|
151
|
-
anyListeners.clear();
|
|
152
|
-
return;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
validateEventName(name);
|
|
156
|
-
listeners.delete(name);
|
|
157
|
-
},
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* Emit events to backend.
|
|
161
|
-
*/
|
|
162
|
-
emit: async (name: string, data: unknown): Promise<void> => {
|
|
163
|
-
validateEventName(name);
|
|
164
|
-
await invoke('event.emit', [name, data]);
|
|
165
|
-
},
|
|
166
|
-
};
|
|
@@ -1,200 +0,0 @@
|
|
|
1
|
-
// Event API implementation
|
|
2
|
-
#include <plusui/events.hpp>
|
|
3
|
-
#include <functional>
|
|
4
|
-
#include <unordered_map>
|
|
5
|
-
#include <vector>
|
|
6
|
-
#include <mutex>
|
|
7
|
-
#include <atomic>
|
|
8
|
-
#include <algorithm>
|
|
9
|
-
#include <utility>
|
|
10
|
-
#include <iostream>
|
|
11
|
-
|
|
12
|
-
namespace plusui {
|
|
13
|
-
namespace event {
|
|
14
|
-
|
|
15
|
-
struct ListenerEntry {
|
|
16
|
-
ListenerId id;
|
|
17
|
-
EventCallback callback;
|
|
18
|
-
bool once;
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
struct AnyListenerEntry {
|
|
22
|
-
ListenerId id;
|
|
23
|
-
AnyEventCallback callback;
|
|
24
|
-
bool once;
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
// Event listeners registry
|
|
28
|
-
static std::unordered_map<std::string, std::vector<ListenerEntry>> listeners;
|
|
29
|
-
static std::vector<AnyListenerEntry> any_listeners;
|
|
30
|
-
static std::mutex mutex;
|
|
31
|
-
static std::atomic<ListenerId> next_listener_id{1};
|
|
32
|
-
|
|
33
|
-
// Bridge to frontend (set by webview initialization)
|
|
34
|
-
std::function<void(const std::string&, const EventData&)> __bridge_to_frontend__;
|
|
35
|
-
|
|
36
|
-
// Listen for events from frontend
|
|
37
|
-
ListenerId on(const std::string& name, EventCallback callback) {
|
|
38
|
-
if (name.empty() || !callback) {
|
|
39
|
-
return 0;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const ListenerId id = next_listener_id.fetch_add(1);
|
|
43
|
-
std::lock_guard<std::mutex> lock(mutex);
|
|
44
|
-
listeners[name].push_back({id, std::move(callback), false});
|
|
45
|
-
return id;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
ListenerId once(const std::string& name, EventCallback callback) {
|
|
49
|
-
if (name.empty() || !callback) {
|
|
50
|
-
return 0;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const ListenerId id = next_listener_id.fetch_add(1);
|
|
54
|
-
std::lock_guard<std::mutex> lock(mutex);
|
|
55
|
-
listeners[name].push_back({id, std::move(callback), true});
|
|
56
|
-
return id;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
ListenerId on_any(AnyEventCallback callback) {
|
|
60
|
-
if (!callback) {
|
|
61
|
-
return 0;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
const ListenerId id = next_listener_id.fetch_add(1);
|
|
65
|
-
std::lock_guard<std::mutex> lock(mutex);
|
|
66
|
-
any_listeners.push_back({id, std::move(callback), false});
|
|
67
|
-
return id;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
bool off(const std::string& name, ListenerId id) {
|
|
71
|
-
if (name.empty() || id == 0) {
|
|
72
|
-
return false;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
std::lock_guard<std::mutex> lock(mutex);
|
|
76
|
-
auto it = listeners.find(name);
|
|
77
|
-
if (it == listeners.end()) {
|
|
78
|
-
return false;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
auto& bucket = it->second;
|
|
82
|
-
const auto old_size = bucket.size();
|
|
83
|
-
bucket.erase(
|
|
84
|
-
std::remove_if(bucket.begin(), bucket.end(), [id](const ListenerEntry& entry) {
|
|
85
|
-
return entry.id == id;
|
|
86
|
-
}),
|
|
87
|
-
bucket.end()
|
|
88
|
-
);
|
|
89
|
-
const auto new_size = bucket.size();
|
|
90
|
-
|
|
91
|
-
if (bucket.empty()) {
|
|
92
|
-
listeners.erase(it);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
return new_size != old_size;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
bool off_any(ListenerId id) {
|
|
99
|
-
if (id == 0) {
|
|
100
|
-
return false;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
std::lock_guard<std::mutex> lock(mutex);
|
|
104
|
-
const auto old_size = any_listeners.size();
|
|
105
|
-
any_listeners.erase(
|
|
106
|
-
std::remove_if(any_listeners.begin(), any_listeners.end(), [id](const AnyListenerEntry& entry) {
|
|
107
|
-
return entry.id == id;
|
|
108
|
-
}),
|
|
109
|
-
any_listeners.end()
|
|
110
|
-
);
|
|
111
|
-
|
|
112
|
-
return any_listeners.size() != old_size;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
std::size_t off_all(const std::string& name) {
|
|
116
|
-
if (name.empty()) {
|
|
117
|
-
return 0;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
std::lock_guard<std::mutex> lock(mutex);
|
|
121
|
-
auto it = listeners.find(name);
|
|
122
|
-
if (it == listeners.end()) {
|
|
123
|
-
return 0;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
const auto removed = it->second.size();
|
|
127
|
-
listeners.erase(it);
|
|
128
|
-
return removed;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
void clear() {
|
|
132
|
-
std::lock_guard<std::mutex> lock(mutex);
|
|
133
|
-
listeners.clear();
|
|
134
|
-
any_listeners.clear();
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// Emit events to frontend
|
|
138
|
-
void emit(const std::string& name, const EventData& data) {
|
|
139
|
-
if (__bridge_to_frontend__) {
|
|
140
|
-
__bridge_to_frontend__(name, data);
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// Internal: Receive event from frontend (called by binding system)
|
|
145
|
-
// This is registered as "event.emit" in the IPC handlers
|
|
146
|
-
void __dispatch_from_frontend__(const std::string& name, const EventData& data) {
|
|
147
|
-
std::vector<ListenerEntry> named_callbacks;
|
|
148
|
-
std::vector<AnyListenerEntry> global_callbacks;
|
|
149
|
-
|
|
150
|
-
{
|
|
151
|
-
std::lock_guard<std::mutex> lock(mutex);
|
|
152
|
-
auto it = listeners.find(name);
|
|
153
|
-
if (it != listeners.end()) {
|
|
154
|
-
named_callbacks = it->second;
|
|
155
|
-
it->second.erase(
|
|
156
|
-
std::remove_if(it->second.begin(), it->second.end(), [](const ListenerEntry& entry) {
|
|
157
|
-
return entry.once;
|
|
158
|
-
}),
|
|
159
|
-
it->second.end()
|
|
160
|
-
);
|
|
161
|
-
|
|
162
|
-
if (it->second.empty()) {
|
|
163
|
-
listeners.erase(it);
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
global_callbacks = any_listeners;
|
|
168
|
-
any_listeners.erase(
|
|
169
|
-
std::remove_if(any_listeners.begin(), any_listeners.end(), [](const AnyListenerEntry& entry) {
|
|
170
|
-
return entry.once;
|
|
171
|
-
}),
|
|
172
|
-
any_listeners.end()
|
|
173
|
-
);
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
for (const auto& entry : named_callbacks) {
|
|
177
|
-
try {
|
|
178
|
-
entry.callback(data);
|
|
179
|
-
} catch (const std::exception& ex) {
|
|
180
|
-
std::cerr << "[PlusUI:event] listener error for '" << name << "': " << ex.what() << std::endl;
|
|
181
|
-
} catch (...) {
|
|
182
|
-
std::cerr << "[PlusUI:event] listener error for '" << name << "': unknown exception" << std::endl;
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
for (const auto& entry : global_callbacks) {
|
|
187
|
-
try {
|
|
188
|
-
entry.callback(name, data);
|
|
189
|
-
} catch (const std::exception& ex) {
|
|
190
|
-
std::cerr << "[PlusUI:event] on_any listener error for '" << name << "': " << ex.what() << std::endl;
|
|
191
|
-
} catch (...) {
|
|
192
|
-
std::cerr << "[PlusUI:event] on_any listener error for '" << name << "': unknown exception" << std::endl;
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
} // namespace event
|
|
198
|
-
} // namespace plusui
|
|
199
|
-
|
|
200
|
-
|