plusui-native-core 0.1.52 → 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.
Files changed (28) hide show
  1. package/Core/CMakeLists.txt +2 -3
  2. package/Core/Features/App/app.cpp +58 -1
  3. package/Core/Features/Connection/ARCHITECTURE.md +369 -0
  4. package/Core/Features/Connection/README.md +165 -0
  5. package/Core/Features/Connection/connection.cpp +57 -0
  6. package/Core/Features/Connection/connection.ts +186 -0
  7. package/Core/Features/Connection/examples/simple_tags_example.hpp +130 -0
  8. package/Core/Features/Connection/examples/simple_tags_example.ts +247 -0
  9. package/Core/Features/Window/window.cpp +187 -151
  10. package/Core/generated/bridge.hpp +302 -0
  11. package/Core/include/plusui/connection.hpp +145 -0
  12. package/Core/include/plusui/plusui.hpp +1 -3
  13. package/Core/include/plusui/window.hpp +17 -19
  14. package/package.json +1 -1
  15. package/Core/Features/Bindings/ARCHITECTURE.md +0 -328
  16. package/Core/Features/Bindings/CustomBindings/custom_bindings.cpp +0 -55
  17. package/Core/Features/Bindings/CustomBindings/custom_bindings.ts +0 -35
  18. package/Core/Features/Bindings/EXAMPLE_USAGE.hpp +0 -143
  19. package/Core/Features/Bindings/EXAMPLE_USAGE.tsx +0 -210
  20. package/Core/Features/Bindings/IPC_GUIDE.md +0 -325
  21. package/Core/Features/Bindings/NativeBindings/native_bindings.cpp +0 -30
  22. package/Core/Features/Bindings/NativeBindings/native_bindings.ts +0 -29
  23. package/Core/Features/Bindings/UNIFIED_SYSTEM.md +0 -351
  24. package/Core/Features/Event/Events.ts +0 -166
  25. package/Core/Features/Event/events.cpp +0 -200
  26. package/Core/include/plusui/bindings.hpp +0 -65
  27. package/Core/include/plusui/custom_bindings.hpp +0 -17
  28. package/Core/include/plusui/events.hpp +0 -58
@@ -0,0 +1,186 @@
1
+ export type ConnectionKind =
2
+ | "call"
3
+ | "fire"
4
+ | "result"
5
+ | "event"
6
+ | "stream"
7
+ | "sub"
8
+ | "unsub"
9
+ | "publish"
10
+ | "error";
11
+
12
+ export type ConnectionEnvelope = {
13
+ kind: ConnectionKind;
14
+ id?: string;
15
+ name: string;
16
+ payload?: unknown;
17
+ error?: string;
18
+ };
19
+
20
+ type MessageCallback = (payload: any) => void;
21
+
22
+ class ConnectionClient {
23
+ private pending = new Map<string, { resolve: (v: any) => void; reject: (e: Error) => void }>();
24
+ private listeners = new Map<string, Set<MessageCallback>>();
25
+
26
+ constructor() {
27
+ const host = globalThis as any;
28
+
29
+ host.__plusuiConnectionMessage = (message: unknown) => {
30
+ this.handleIncoming(message);
31
+ };
32
+
33
+ if (typeof window !== "undefined") {
34
+ window.addEventListener("plusui:connection:message", (ev: Event) => {
35
+ const custom = ev as CustomEvent<unknown>;
36
+ this.handleIncoming(custom.detail);
37
+ });
38
+ }
39
+ }
40
+
41
+ private nextId(): string {
42
+ return `${Date.now()}-${Math.random().toString(16).slice(2)}`;
43
+ }
44
+
45
+ private async send(env: ConnectionEnvelope): Promise<any> {
46
+ const host = globalThis as any;
47
+
48
+ if (typeof host.__invoke__ === "function") {
49
+ return host.__invoke__("connection.dispatch", env);
50
+ }
51
+
52
+ if (host.ipc?.postMessage) {
53
+ host.ipc.postMessage(JSON.stringify(env));
54
+ return null;
55
+ }
56
+
57
+ return null;
58
+ }
59
+
60
+ private decode(message: unknown): ConnectionEnvelope | null {
61
+ if (!message) {
62
+ return null;
63
+ }
64
+ if (typeof message === "string") {
65
+ try {
66
+ return JSON.parse(message) as ConnectionEnvelope;
67
+ } catch {
68
+ return null;
69
+ }
70
+ }
71
+ if (typeof message === "object") {
72
+ return message as ConnectionEnvelope;
73
+ }
74
+ return null;
75
+ }
76
+
77
+ private handleIncoming(message: unknown): void {
78
+ const env = this.decode(message);
79
+ if (!env) {
80
+ return;
81
+ }
82
+
83
+ if ((env.kind === "result" || env.kind === "error") && env.id) {
84
+ const entry = this.pending.get(env.id);
85
+ if (!entry) {
86
+ return;
87
+ }
88
+ this.pending.delete(env.id);
89
+ if (env.kind === "error") {
90
+ entry.reject(new Error(env.error || "Connection call failed"));
91
+ } else {
92
+ entry.resolve(env.payload);
93
+ }
94
+ return;
95
+ }
96
+
97
+ if (env.kind === "event" || env.kind === "stream" || env.kind === "publish") {
98
+ const handlers = this.listeners.get(env.name);
99
+ if (!handlers) {
100
+ return;
101
+ }
102
+ for (const handler of handlers) {
103
+ handler(env.payload);
104
+ }
105
+ }
106
+ }
107
+
108
+ async call<TOut = unknown, TIn = Record<string, unknown>>(name: string, payload: TIn): Promise<TOut> {
109
+ const id = this.nextId();
110
+ const promise = new Promise<TOut>((resolve, reject) => {
111
+ this.pending.set(id, { resolve, reject });
112
+ });
113
+
114
+ await this.send({ kind: "call", id, name, payload });
115
+ return promise;
116
+ }
117
+
118
+ fire<TIn = Record<string, unknown>>(name: string, payload: TIn): void {
119
+ void this.send({ kind: "fire", name, payload });
120
+ }
121
+
122
+ on<TData = unknown>(name: string, callback: (payload: TData) => void): () => void {
123
+ const existing = this.listeners.get(name) ?? new Set<MessageCallback>();
124
+ existing.add(callback as MessageCallback);
125
+ this.listeners.set(name, existing);
126
+
127
+ return () => {
128
+ const current = this.listeners.get(name);
129
+ if (!current) {
130
+ return;
131
+ }
132
+ current.delete(callback as MessageCallback);
133
+ if (current.size === 0) {
134
+ this.listeners.delete(name);
135
+ }
136
+ };
137
+ }
138
+
139
+ stream<TData = unknown>(name: string) {
140
+ return {
141
+ subscribe: (callback: (payload: TData) => void): (() => void) => {
142
+ void this.send({ kind: "sub", name });
143
+ const off = this.on<TData>(name, callback);
144
+ return () => {
145
+ off();
146
+ void this.send({ kind: "unsub", name });
147
+ };
148
+ },
149
+ };
150
+ }
151
+
152
+ channel<TData = unknown>(name: string) {
153
+ return {
154
+ subscribe: (callback: (payload: TData) => void): (() => void) => {
155
+ void this.send({ kind: "sub", name });
156
+ const off = this.on<TData>(name, callback);
157
+ return () => {
158
+ off();
159
+ void this.send({ kind: "unsub", name });
160
+ };
161
+ },
162
+ publish: (payload: TData): void => {
163
+ void this.send({ kind: "publish", name, payload });
164
+ },
165
+ };
166
+ }
167
+ }
168
+
169
+ // Create the connection client instance
170
+ const connectionClient = new ConnectionClient();
171
+
172
+ // Simple connection API - just emit() and on()
173
+ export const connect = {
174
+ // Send a message to backend
175
+ emit: <TIn = Record<string, unknown>>(name: string, payload: TIn): void => {
176
+ connectionClient.fire(name, payload);
177
+ },
178
+
179
+ // Listen for messages from backend
180
+ on: <TData = unknown>(name: string, callback: (payload: TData) => void): (() => void) => {
181
+ return connectionClient.on<TData>(name, callback);
182
+ },
183
+ };
184
+
185
+ // Export the raw client for advanced use cases (call, fire, stream, channel)
186
+ export const connection = connectionClient;
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Example: Simple Tag-Based Connection (C++ Backend)
3
+ *
4
+ * No schema file needed! Just tag your methods and bindgen does the rest.
5
+ */
6
+
7
+ #pragma once
8
+ #include <plusui/connection.hpp>
9
+ #include <string>
10
+
11
+ namespace example {
12
+
13
+ class SimpleApp : public plusui::Connection {
14
+ public:
15
+
16
+ // ============================================
17
+ // CONNECT_CALL: Request/Response
18
+ // Frontend can await the result
19
+ // ============================================
20
+
21
+ // Simple call with one parameter
22
+ CONNECT_CALL(greet, std::string, std::string name)
23
+ std::string greet(const std::string& name) {
24
+ return "Hello, " + name + "!";
25
+ }
26
+
27
+ // Call with multiple parameters → returns object
28
+ CONNECT_CALL(calculate, double, double a, double b)
29
+ double calculate(double a, double b) {
30
+ return a + b;
31
+ }
32
+
33
+ // Call with complex return (auto-generates struct)
34
+ CONNECT_CALL(getUser, {int id, std::string name, std::string email}, int userId)
35
+ auto getUser(int userId) {
36
+ struct Result { int id; std::string name; std::string email; };
37
+ return Result{ userId, "John Doe", "john@example.com" };
38
+ }
39
+
40
+ // ============================================
41
+ // CONNECT_FIRE: One-way to Backend
42
+ // No response expected
43
+ // ============================================
44
+
45
+ CONNECT_FIRE(minimize, void)
46
+ void minimize() {
47
+ // Minimize window
48
+ std::cout << "Minimizing..." << std::endl;
49
+ }
50
+
51
+ CONNECT_FIRE(log, void, std::string level, std::string message)
52
+ void log(const std::string& level, const std::string& message) {
53
+ std::cout << "[" << level << "] " << message << std::endl;
54
+ }
55
+
56
+ // ============================================
57
+ // CONNECT_EVENT: Backend → Frontend
58
+ // Backend emits, frontend listens
59
+ // ============================================
60
+
61
+ CONNECT_EVENT(onResize, int width, int height)
62
+ void emitResize(int width, int height) {
63
+ // Auto-generated emit method
64
+ emit_onResize(width, height);
65
+ }
66
+
67
+ CONNECT_EVENT(onFileDrop, std::vector<std::string> files)
68
+ void emitFileDrop(const std::vector<std::string>& files) {
69
+ emit_onFileDrop(files);
70
+ }
71
+
72
+ // ============================================
73
+ // CONNECT_STREAM: Continuous Data
74
+ // Backend streams, frontend subscribes
75
+ // ============================================
76
+
77
+ CONNECT_STREAM(cpuUsage, double percent)
78
+ void startMonitoring() {
79
+ // Stream CPU usage continuously
80
+ std::thread([this]() {
81
+ while (running) {
82
+ double cpu = getCpuUsage();
83
+ emit_cpuUsage(cpu);
84
+ std::this_thread::sleep_for(std::chrono::seconds(1));
85
+ }
86
+ }).detach();
87
+ }
88
+
89
+ // ============================================
90
+ // CONNECT_CHANNEL: Bidirectional
91
+ // Both sides can send and receive
92
+ // ============================================
93
+
94
+ CONNECT_CHANNEL(logBus, std::string level, std::string line)
95
+
96
+ // Receive from frontend
97
+ void onLogFromFrontend(const std::string& level, const std::string& line) {
98
+ std::cout << "[Frontend] " << level << ": " << line << std::endl;
99
+ }
100
+
101
+ // Send to frontend
102
+ void sendLogToFrontend(const std::string& level, const std::string& line) {
103
+ emit_logBus(level, line);
104
+ }
105
+
106
+ private:
107
+ bool running = true;
108
+
109
+ double getCpuUsage() {
110
+ return 50.0 + (rand() % 30);
111
+ }
112
+ };
113
+
114
+ } // namespace example
115
+
116
+ /**
117
+ * This example shows how simple it is:
118
+ *
119
+ * 1. Tag your methods with CONNECT_* macros
120
+ * 2. Run: npm run bindgen
121
+ * 3. Use in TypeScript:
122
+ *
123
+ * const result = await connect.greet({ name: 'World' });
124
+ * connect.minimize();
125
+ * connect.onResize((w, h) => updateUI(w, h));
126
+ * connect.cpuUsage.subscribe((cpu) => showCpu(cpu));
127
+ * connect.logBus.publish({ level: 'info', line: 'msg' });
128
+ *
129
+ * That's it! No separate schema file, no manual typing.
130
+ */
@@ -0,0 +1,247 @@
1
+ /**
2
+ * Example: Simple Tag-Based Connection (TypeScript Frontend)
3
+ *
4
+ * After running bindgen, use the auto-generated connect API
5
+ */
6
+
7
+ import { connect } from '../generated/bindings';
8
+
9
+ /**
10
+ * All these methods are auto-generated from C++ CONNECT_* tags!
11
+ */
12
+
13
+ // ============================================
14
+ // CALL - Request/Response
15
+ // ============================================
16
+
17
+ async function callExamples() {
18
+ // Simple call
19
+ const greeting = await connect.greet({ name: 'PlusUI' });
20
+ console.log(greeting); // "Hello, PlusUI!"
21
+
22
+ // Call with multiple params
23
+ const sum = await connect.calculate({ a: 10, b: 20 });
24
+ console.log(sum); // 30
25
+
26
+ // Complex return type
27
+ const user = await connect.getUser({ userId: 123 });
28
+ console.log(user.name, user.email);
29
+ }
30
+
31
+ // ============================================
32
+ // FIRE - One-way to Backend
33
+ // ============================================
34
+
35
+ function fireExamples() {
36
+ // Fire with no params
37
+ connect.minimize();
38
+
39
+ // Fire with params
40
+ connect.log({
41
+ level: 'info',
42
+ message: 'User clicked button'
43
+ });
44
+
45
+ // Fire and forget - doesn't wait
46
+ connect.log({ level: 'debug', message: 'Debug info' });
47
+ doSomethingElse(); // Runs immediately
48
+ }
49
+
50
+ // ============================================
51
+ // EVENT - Listen to Backend
52
+ // ============================================
53
+
54
+ function eventExamples() {
55
+ // Listen for resize events
56
+ const unsubResize = connect.onResize((data) => {
57
+ console.log(`Window: ${data.width}x${data.height}`);
58
+ updateLayout(data.width, data.height);
59
+ });
60
+
61
+ // Listen for file drops
62
+ connect.onFileDrop((data) => {
63
+ console.log('Files dropped:', data.files);
64
+ data.files.forEach(file => processFile(file));
65
+ });
66
+
67
+ // Clean up when done
68
+ // unsubResize();
69
+ }
70
+
71
+ // ============================================
72
+ // STREAM - Subscribe to Continuous Data
73
+ // ============================================
74
+
75
+ function streamExamples() {
76
+ // Subscribe to CPU usage stream
77
+ const unsub = connect.cpuUsage.subscribe((data) => {
78
+ updateCpuChart(data.percent);
79
+
80
+ if (data.percent > 90) {
81
+ showWarning('High CPU!');
82
+ }
83
+ });
84
+
85
+ // Unsubscribe when component unmounts
86
+ return unsub;
87
+ }
88
+
89
+ // ============================================
90
+ // CHANNEL - Bidirectional Communication
91
+ // ============================================
92
+
93
+ function channelExamples() {
94
+ // Subscribe to receive from backend
95
+ connect.logBus.subscribe((msg) => {
96
+ const color = msg.level === 'error' ? 'red' : 'gray';
97
+ console.log(`%c[${msg.level}] ${msg.line}`, `color: ${color}`);
98
+ addToLogView(msg);
99
+ });
100
+
101
+ // Publish to backend
102
+ connect.logBus.publish({
103
+ level: 'info',
104
+ line: 'Frontend initialized'
105
+ });
106
+
107
+ // Both sides can communicate freely
108
+ document.onclick = () => {
109
+ connect.logBus.publish({
110
+ level: 'debug',
111
+ line: 'User clicked somewhere'
112
+ });
113
+ };
114
+ }
115
+
116
+ // ============================================
117
+ // React Component Example
118
+ // ============================================
119
+
120
+ function ReactExample() {
121
+ const [greeting, setGreeting] = React.useState('');
122
+ const [cpu, setCpu] = React.useState(0);
123
+
124
+ React.useEffect(() => {
125
+ // Call on mount
126
+ connect.greet({ name: 'React' })
127
+ .then(result => setGreeting(result));
128
+
129
+ // Subscribe to events
130
+ const unsubResize = connect.onResize((data) => {
131
+ console.log('Resize:', data);
132
+ });
133
+
134
+ // Subscribe to stream
135
+ const unsubCpu = connect.cpuUsage.subscribe((data) => {
136
+ setCpu(data.percent);
137
+ });
138
+
139
+ // Subscribe to channel
140
+ const unsubLog = connect.logBus.subscribe((msg) => {
141
+ console.log(msg);
142
+ });
143
+
144
+ // Cleanup all subscriptions
145
+ return () => {
146
+ unsubResize();
147
+ unsubCpu();
148
+ unsubLog();
149
+ };
150
+ }, []);
151
+
152
+ const handleMinimize = () => {
153
+ connect.minimize();
154
+ };
155
+
156
+ const handleLog = () => {
157
+ connect.log({
158
+ level: 'info',
159
+ message: 'Button clicked!'
160
+ });
161
+ };
162
+
163
+ return (
164
+ <div>
165
+ <h1>{greeting}</h1>
166
+ <div>CPU: {cpu.toFixed(1)}%</div>
167
+ <button onClick={handleMinimize}>Minimize</button>
168
+ <button onClick={handleLog}>Log Message</button>
169
+ </div>
170
+ );
171
+ }
172
+
173
+ // ============================================
174
+ // Vue Composition API Example
175
+ // ============================================
176
+
177
+ function vueExample() {
178
+ const greeting = ref('');
179
+ const cpu = ref(0);
180
+
181
+ onMounted(async () => {
182
+ // Call backend
183
+ const result = await connect.greet({ name: 'Vue' });
184
+ greeting.value = result;
185
+
186
+ // Subscribe to stream
187
+ const unsub = connect.cpuUsage.subscribe((data) => {
188
+ cpu.value = data.percent;
189
+ });
190
+
191
+ onUnmounted(() => {
192
+ unsub();
193
+ });
194
+ });
195
+
196
+ return {
197
+ greeting,
198
+ cpu,
199
+ minimize: () => connect.minimize(),
200
+ log: (msg: string) => connect.log({ level: 'info', message: msg }),
201
+ };
202
+ }
203
+
204
+ // ============================================
205
+ // Helper Functions
206
+ // ============================================
207
+
208
+ function updateLayout(width: number, height: number) {
209
+ // Update UI layout
210
+ }
211
+
212
+ function processFile(file: string) {
213
+ // Process dropped file
214
+ }
215
+
216
+ function updateCpuChart(percent: number) {
217
+ // Update chart
218
+ }
219
+
220
+ function showWarning(msg: string) {
221
+ // Show warning toast
222
+ }
223
+
224
+ function addToLogView(msg: any) {
225
+ // Add to log view
226
+ }
227
+
228
+ function doSomethingElse() {
229
+ // Continue execution
230
+ }
231
+
232
+ /**
233
+ * THAT'S IT!
234
+ *
235
+ * No schema file needed.
236
+ * No manual type definitions.
237
+ * Just tag your C++ methods and use them in TypeScript.
238
+ *
239
+ * Bindgen automatically:
240
+ * - Finds all CONNECT_* tags
241
+ * - Generates TypeScript types
242
+ * - Creates the connect.* API
243
+ * - Handles all serialization
244
+ * - Manages subscriptions
245
+ *
246
+ * Perfect developer experience! 🚀
247
+ */