plusui-native 0.2.90 → 0.2.92

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "plusui-native",
3
- "version": "0.2.90",
3
+ "version": "0.2.92",
4
4
  "description": "PlusUI CLI - Build C++ desktop apps modern UI ",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -27,11 +27,11 @@
27
27
  "semver": "^7.6.0",
28
28
  "which": "^4.0.0",
29
29
  "execa": "^8.0.1",
30
- "plusui-native-builder": "^0.1.88",
31
- "plusui-native-connect": "^0.1.88"
30
+ "plusui-native-builder": "^0.1.90",
31
+ "plusui-native-connect": "^0.1.90"
32
32
  },
33
33
  "peerDependencies": {
34
- "plusui-native-connect": "^0.1.88"
34
+ "plusui-native-connect": "^0.1.90"
35
35
  },
36
36
  "publishConfig": {
37
37
  "access": "public"
@@ -1,193 +1,120 @@
1
1
  import { useState, useEffect } from 'react';
2
2
  import plusui from 'plusui';
3
- // Built-in features are exposed directly on the plusui object
3
+
4
+ // That's the only import you need.
5
+ // All features are available directly:
6
+ // plusui.window — show, hide, minimize, maximize, setTitle, setSize ...
7
+ // plusui.app — quit
8
+ // plusui.router — push, setRoutes
9
+ // plusui.clipboard— getText, setText
10
+ // plusui.fileDrop — onFilesDropped, setEnabled
11
+ // plusui.tray — setIcon, setTooltip, onClick
4
12
 
5
13
  function App() {
6
14
  const [windowSize, setWindowSize] = useState({ width: 0, height: 0 });
7
- const [windowPos, setWindowPos] = useState({ x: 0, y: 0 });
8
- const [currentUrl, setCurrentUrl] = useState('');
9
- const [canGoBack, setCanGoBack] = useState(false);
10
- const [canGoForward, setCanGoForward] = useState(false);
11
-
12
- // Native FileDrop state
13
- const [isDragging, setIsDragging] = useState(false);
15
+ const [isMaximized, setIsMaximized] = useState(false);
14
16
  const [droppedFiles, setDroppedFiles] = useState<{ name: string; size: number; type: string }[]>([]);
15
- const [backendMsg, setBackendMsg] = useState<string | null>(null);
17
+ const [isDragging, setIsDragging] = useState(false);
16
18
 
17
19
  useEffect(() => {
18
- plusui.win.show().catch(() => {
19
- // no-op for browser preview mode
20
- });
21
-
22
- // Setup routes
23
- plusui.router.setRoutes({
24
- '/app': 'http://localhost:5173/app',
25
- '/about': 'http://localhost:5173/about',
26
- });
27
-
28
- // Listen for navigation changes
29
- const unsubNav = plusui.browser.onNavigate((url) => {
30
- setCurrentUrl(url);
31
- plusui.browser.canGoBack().then(setCanGoBack);
32
- plusui.browser.canGoForward().then(setCanGoForward);
33
- });
34
-
35
- // Get initial state
36
- plusui.browser.getUrl().then(setCurrentUrl);
37
- plusui.browser.canGoBack().then(setCanGoBack);
38
- plusui.browser.canGoForward().then(setCanGoForward);
39
-
40
- // Listen for native file drops
41
- const unsubFileDrop = plusui.fileDrop.onFilesDropped((files: any[]) => {
42
- setDroppedFiles(files);
43
- setBackendMsg(`Native FileDrop: Received ${files.length} file(s)`);
44
- });
45
-
46
- const unsubDragEnter = plusui.fileDrop.onDragEnter(() => setIsDragging(true));
47
- const unsubDragLeave = plusui.fileDrop.onDragLeave(() => setIsDragging(false));
20
+ // Show the window once the UI is mounted.
21
+ // C++ hides it on startup (showWhenFrontendReady = true) so there's no flash.
22
+ plusui.window.show();
23
+
24
+ // Window events — fire automatically, no wiring in main.cpp needed.
25
+ const unsubResize = plusui.window.onResize((size: { width: number; height: number }) => setWindowSize(size));
26
+ const unsubState = plusui.window.onStateChange((state: { isMaximized: boolean; isMinimized: boolean; isVisible: boolean; isFullscreen: boolean }) => setIsMaximized(state.isMaximized));
27
+
28
+ // File drop — built-in, works out of the box.
29
+ const unsubDrop = plusui.fileDrop.onFilesDropped((files: { name: string; size: number; type: string }[]) => setDroppedFiles(files));
30
+ const unsubEnter = plusui.fileDrop.onDragEnter(() => setIsDragging(true));
31
+ const unsubLeave = plusui.fileDrop.onDragLeave(() => setIsDragging(false));
32
+
33
+ // Seed initial size
34
+ plusui.window.getSize().then(setWindowSize);
48
35
 
49
36
  return () => {
50
- unsubNav();
51
- unsubFileDrop();
52
- unsubDragEnter();
53
- unsubDragLeave();
37
+ unsubResize();
38
+ unsubState();
39
+ unsubDrop();
40
+ unsubEnter();
41
+ unsubLeave();
54
42
  };
55
43
  }, []);
56
44
 
57
- const handleMinimize = async () => await plusui.win.minimize();
58
- const handleMaximize = async () => await plusui.win.maximize();
59
- const handleClose = async () => await plusui.win.close();
60
- const handleGetSize = async () => {
61
- const size = await plusui.win.getSize();
62
- setWindowSize(size);
63
- };
64
- const handleGetPosition = async () => {
65
- const pos = await plusui.win.getPosition();
66
- setWindowPos(pos);
67
- };
68
-
69
- // Browser navigation
70
- const handleGoBack = async () => await plusui.browser.goBack();
71
- const handleGoForward = async () => await plusui.browser.goForward();
72
- const handleReload = async () => await plusui.browser.reload();
73
-
74
- // App control
75
- const handleQuit = async () => await plusui.app.quit();
76
-
77
- // Native FileDrop handlers
78
- // The C++ native window automatically detects elements with class 'filedrop-zone'.
79
- // We do not need HTML5 drag events, the C++ backend emits events directly.
80
-
81
45
  return (
82
46
  <div className="app">
83
47
  <header className="app-header">
84
48
  <h1>{'{{PROJECT_NAME}}'}</h1>
85
- <p>Built with PlusUI Framework</p>
49
+ <p>Built with PlusUI</p>
86
50
  </header>
87
51
 
88
52
  <main className="app-content">
89
- <div className="card">
90
- <h2>Window Controls</h2>
91
- <div className="button-group">
92
- <button onClick={handleMinimize} className="button">Minimize</button>
93
- <button onClick={handleMaximize} className="button">Maximize</button>
94
- <button onClick={handleClose} className="button button-danger">Close</button>
95
- </div>
96
- </div>
97
53
 
54
+ {/* ── Window ─────────────────────────────────────────────── */}
98
55
  <div className="card">
99
- <h2>Window Size</h2>
100
- <button onClick={handleGetSize} className="button">Get Size</button>
101
- {windowSize.width > 0 && (
102
- <p className="info-text">Size: {windowSize.width} x {windowSize.height}</p>
103
- )}
104
- </div>
105
-
106
- <div className="card">
107
- <h2>Window Position</h2>
56
+ <h2>Window</h2>
108
57
  <div className="button-group">
109
- <button onClick={handleGetPosition} className="button">Get Position</button>
110
- <button onClick={() => plusui.win.setPosition(100, 100)} className="button">Move Left</button>
111
- <button onClick={() => plusui.win.setPosition(800, 100)} className="button">Move Right</button>
58
+ <button className="button" onClick={() => plusui.window.show()}>Show</button>
59
+ <button className="button" onClick={() => plusui.window.hide()}>Hide</button>
60
+ <button className="button" onClick={() => plusui.window.minimize()}>Minimize</button>
61
+ <button className="button" onClick={() => isMaximized ? plusui.window.restore() : plusui.window.maximize()}>
62
+ {isMaximized ? 'Restore' : 'Maximize'}
63
+ </button>
64
+ <button className="button" onClick={() => plusui.window.center()}>Center</button>
65
+ <button className="button button-danger" onClick={() => plusui.window.close()}>Close</button>
112
66
  </div>
113
- {windowPos.x !== 0 && (
114
- <p className="info-text">Position: {windowPos.x}, {windowPos.y}</p>
67
+ {windowSize.width > 0 && (
68
+ <p className="info-text">Size: {windowSize.width} × {windowSize.height}</p>
115
69
  )}
116
70
  </div>
117
71
 
72
+ {/* ── App ────────────────────────────────────────────────── */}
118
73
  <div className="card">
119
- <h2>Browser Navigation</h2>
120
- <p className="info-text">Current: {currentUrl}</p>
121
- <div className="button-group">
122
- <button onClick={handleGoBack} className="button" disabled={!canGoBack}>Back</button>
123
- <button onClick={handleGoForward} className="button" disabled={!canGoForward}>Forward</button>
124
- <button onClick={handleReload} className="button">Reload</button>
125
- </div>
126
- </div>
127
-
128
- <div className="card">
129
- <h2>App Control</h2>
130
- <button onClick={handleQuit} className="button button-danger">Quit App</button>
74
+ <h2>App</h2>
75
+ <button className="button button-danger" onClick={() => plusui.app.quit()}>Quit</button>
131
76
  </div>
132
77
 
78
+ {/* ── File Drop ──────────────────────────────────────────── */}
133
79
  <div className="card">
134
- <h2>Native FileDrop API</h2>
135
- <p style={{ fontSize: '0.85em', color: '#aaa', marginBottom: '1rem' }}>
136
- Drop files below. The <code>plusui.fileDrop</code> API is handled natively in C++
137
- which overrides standard browser drag-and-drop. It automatically detects elements
138
- with the <code>.filedrop-zone</code> class or <code>data-dropzone="true"</code>.
139
- </p>
140
-
80
+ <h2>File Drop</h2>
141
81
  <div
142
82
  className={`filedrop-zone ${isDragging ? 'filedrop-active' : ''}`}
143
83
  data-dropzone="true"
144
84
  >
145
85
  <div className="filedrop-content">
146
- <svg className="filedrop-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor">
147
- <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2}
148
- d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12" />
149
- </svg>
150
86
  <div className="filedrop-text">
151
- {isDragging ? 'Drop files here' : 'Drag & drop files to send to C++'}
87
+ {isDragging ? 'Drop files here' : 'Drag & drop files here'}
152
88
  </div>
153
89
  </div>
154
90
  </div>
155
91
 
156
92
  {droppedFiles.length > 0 && (
157
93
  <div className="filedrop-files" style={{ marginTop: '1rem' }}>
158
- <p style={{ fontSize: '0.8em', color: '#888', marginBottom: '0.5rem' }}>
159
- Sent to C++ via <code>customFileDrop.emit()</code>:
160
- </p>
161
- {droppedFiles.map((file, i) => (
94
+ {droppedFiles.map((file: { name: string; size: number; type: string }, i: number) => (
162
95
  <div key={i} className="filedrop-file-item">
163
96
  <div className="filedrop-file-icon">📄</div>
164
97
  <div className="filedrop-file-info">
165
98
  <div className="filedrop-file-name">{file.name}</div>
166
- <div className="filedrop-file-meta">{plusui.formatFileSize(file.size)} • {file.type}</div>
99
+ <div className="filedrop-file-meta">
100
+ {plusui.formatFileSize(file.size)} · {file.type || 'unknown'}
101
+ </div>
167
102
  </div>
168
103
  </div>
169
104
  ))}
170
105
  </div>
171
106
  )}
172
-
173
- {backendMsg !== null && (
174
- <div style={{ marginTop: '1rem', padding: '0.75rem 1rem', background: '#1a2e1a', border: '1px solid #2d5a2d', borderRadius: '6px' }}>
175
- <p style={{ fontSize: '0.8em', color: '#4caf50', margin: 0 }}>
176
- ✓ C++ replied via <code>ch.customFileDrop.emit()</code>:
177
- </p>
178
- <p style={{ fontSize: '0.9em', color: '#e0e0e0', margin: '0.4rem 0 0' }}>{backendMsg}</p>
179
- </div>
180
- )}
181
107
  </div>
182
108
 
183
109
  <div className="info">
184
- <p>Edit <code>frontend/src/App.tsx</code> to modify the UI.</p>
185
- <p>Edit <code>main.cpp</code> to add C++ functionality.</p>
110
+ <p>Edit <code>frontend/src/App.tsx</code> for the UI.</p>
111
+ <p>Edit <code>main.cpp</code> for native backend logic.</p>
112
+ <p>Run <code>plusui connect</code> to add custom C++ ↔ frontend channels.</p>
186
113
  </div>
114
+
187
115
  </main>
188
116
  </div>
189
117
  );
190
118
  }
191
119
 
192
120
  export default App;
193
-
@@ -1,12 +1,29 @@
1
1
  import { defineConfig } from 'vite';
2
2
  import react from '@vitejs/plugin-react';
3
+ import { resolve, dirname } from 'path';
4
+ import { fileURLToPath } from 'url';
5
+ import { existsSync } from 'fs';
6
+
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = dirname(__filename);
9
+
10
+ // Resolve plusui-native-core entry directly (bypasses package.json exports)
11
+ function findPlusuiEntry(): string {
12
+ const candidates = [
13
+ resolve(__dirname, '../node_modules/plusui-native-core/Core/Features/API/index.ts'),
14
+ resolve(__dirname, '../node_modules/plusui-native-core/Core/API/index.ts'),
15
+ ];
16
+ for (const p of candidates) {
17
+ if (existsSync(p)) return p;
18
+ }
19
+ return 'plusui-native-core';
20
+ }
3
21
 
4
22
  export default defineConfig({
5
23
  plugins: [react()],
6
24
  resolve: {
7
25
  alias: {
8
- // `import plusui from 'plusui'` resolves to the installed plusui-native-core package
9
- plusui: 'plusui-native-core',
26
+ plusui: findPlusuiEntry(),
10
27
  },
11
28
  },
12
29
  build: {
@@ -1,262 +1,105 @@
1
- #include <plusui/plusui.hpp> // All-in-one framework header
1
+ #include <plusui/plusui>
2
2
  #include <iostream>
3
3
  #include "generated/assets.h"
4
- // ── Generated channel bindings (run `plusui connect` to regenerate) ──────────
5
- #include "Connections/connections.gen.hpp"
6
4
 
7
5
  using namespace plusui;
6
+ using json = nlohmann::json;
8
7
 
9
8
  // ============================================================================
10
- // {{PROJECT_NAME}} - Configuration
9
+ // {{PROJECT_NAME}} Configuration
11
10
  // ============================================================================
12
- // Change these values to configure your app. All options are true/false toggles
13
- // or simple values. Easy to customize without digging through code.
11
+ // Edit these structs to configure your app.
12
+ // Built-in features (window, router, clipboard, etc.) work without any extra
13
+ // wiring — just call them from the frontend via `import plusui from 'plusui'`.
14
+ //
15
+ // Custom frontend ↔ backend channels:
16
+ // Run `plusui connect` to generate typed channels for your own events.
14
17
  // ============================================================================
15
18
 
16
- // ----------------------------------------------------------------------------
17
- // APP CONFIGURATION (Application-level settings)
18
- // ----------------------------------------------------------------------------
19
19
  struct AppConfig {
20
- // --- Identity ---
21
- std::string name = "{{PROJECT_NAME}}"; // Your app name (used for window title, etc.)
20
+ std::string name = "{{PROJECT_NAME}}";
22
21
  std::string version = "0.1.0";
23
-
24
- // --- App Type ---
25
- bool headless = false; // No window, background app only
26
- bool singleInstance = true; // Only allow one instance of the app
27
-
28
- // --- Startup ---
29
- bool autoStart = false; // Launch on system startup
30
- bool hiddenOnStartup = false; // Start hidden
31
- bool showWhenFrontendReady = true; // Hide initial blank webview until React is mounted
32
-
33
- // --- Security ---
34
- bool webSecurity = true; // Enable web security (CORS, etc.)
35
- bool allowFileAccess = false; // Allow file:// URLs
22
+ bool showWhenFrontendReady = true; // Hides blank webview until React mounts
36
23
  } appConfig;
37
24
 
38
- // ----------------------------------------------------------------------------
39
- // WINDOW CONFIGURATION (Window appearance and behavior)
40
- // ----------------------------------------------------------------------------
41
25
  struct WindowConfig {
42
- // --- Size & Position ---
43
- int width = 1200;
26
+ int width = 1200;
44
27
  int height = 800;
45
- int minWidth = 400; // Minimum window width
46
- int minHeight = 300; // Minimum window height
47
- int maxWidth = 0; // 0 = no limit
48
- int maxHeight = 0; // 0 = no limit
49
- bool centerOnStartup = true; // Center window on launch
50
-
51
- // --- Appearance ---
52
- bool showTitlebar = true; // Show native titlebar (set false for frameless)
53
- bool showBorder = true; // Show window border
54
- bool transparent = false; // Transparent window background
55
- double opacity = 1.0; // Window opacity 0.0 (invisible) to 1.0 (solid)
56
-
57
- // --- Behavior ---
58
- bool resizable = true; // Allow user to resize window
59
- bool minimizable = true; // Show minimize button
60
- bool maximizable = true; // Show maximize button
61
- bool closable = true; // Show close button
62
- bool alwaysOnTop = false; // Keep window above all others
63
- bool showInTaskbar = true; // Show in taskbar (false = hidden from taskbar)
64
- bool fullscreen = false; // Start in fullscreen mode
65
- bool enableFileDrop = true; // Enable native FileDrop API (disables webview drag-drop)
66
-
67
- // --- Dev Tools ---
68
- bool devTools = true; // Enable DevTools (F12, right-click inspect)
69
- bool contextMenu = true; // Enable right-click context menu
70
-
71
- // --- WebView ---
72
- bool scrollbars = false; // Show scrollbars in webview (false = hidden)
73
-
74
- // --- Routing ---
75
- // The frontend route this window opens on.
76
- // Use "/app" for main view, "/settings" for settings page, etc.
77
- // Works with any frontend router (React Router, TanStack, Solid Router).
78
- std::string route = "/app"; // Starting route (e.g. "/app", "/settings")
79
- int devServerPort = 5173; // Vite dev server port
28
+ bool resizable = true;
29
+ bool showTitlebar = true;
30
+ bool transparent = false;
31
+ double opacity = 1.0;
32
+ bool alwaysOnTop = false;
33
+ bool showInTaskbar = true;
34
+ bool devTools = true;
35
+ bool scrollbars = false;
36
+ bool enableFileDrop = true;
37
+ std::string route = "/"; // Starting route
38
+ int devServerPort = 5173;
80
39
  } windowConfig;
81
40
 
82
- // ----------------------------------------------------------------------------
83
- // SERVICES CONFIGURATION (Optional features)
84
- // ----------------------------------------------------------------------------
85
- struct ServicesConfig {
86
- bool enableClipboard = true; // Enable clipboard read/write from frontend
87
- bool enableDisplay = true; // Enable multi-display detection
88
- bool enableKeyboard = true; // Enable keyboard shortcuts/hotkeys
89
- bool enableMenu = true; // Enable custom menu bar
90
- } servicesConfig;
91
-
92
41
  // ============================================================================
93
- // WEBGPU CONFIGURATION (Optional - 3D Graphics & GPU Compute)
42
+ // CUSTOM CHANNELS (optional)
94
43
  // ============================================================================
95
- // WebGPU provides native GPU acceleration for rendering and compute shaders.
96
- // Requires: PLUSUI_ENABLE_WEBGPU build flag
44
+ // If you need your own frontend backend events beyond the built-in features,
45
+ // use `plusui connect`. Add a Connect subclass here and bind it:
97
46
  //
98
- // Usage in frontend:
99
- // const adapter = await navigator.gpu.requestAdapter();
100
- // const device = await adapter.requestDevice();
101
- // const canvas = document.querySelector('canvas');
102
- // const context = canvas.getContext('webgpu');
103
- // context.configure({ device, format: 'bgra8unorm' });
47
+ // #include "Connections/connections.gen.hpp"
48
+ // static Connect conn;
49
+ // bindConnect(mainWindow, conn);
50
+ // initChannels(conn);
104
51
  //
105
- // See: https://www.w3.org/TR/webgpu/ for full API documentation
52
+ // Then in C++: myChannel.emit({{"key", "value"}});
53
+ // And frontend: myChannel.on((data) => { ... });
106
54
  // ============================================================================
107
- struct WebGPUConfig {
108
- bool enabled = false; // Enable WebGPU support
109
- bool validateAPI = true; // Enable validation in debug builds
110
- bool verbose = false; // Enable verbose logging for debugging
111
- } webgpuConfig;
112
-
113
- // ============================================================================
114
- // MAIN - Application Entry Point
115
- // ============================================================================
116
- // ── Connect instance ─────────────────────────────────────────────────────────
117
- // conn is the bridge between C++ and the frontend.
118
- // Run `plusui connect` to generate Connections/ from your name.on / name.emit usage.
119
- // Call initChannels(conn) once, then use channels directly:
120
- // customFileDrop.on([](const json& p) { ... });
121
- // customFileDrop.emit({{"value", 42}});
122
- static Connect conn;
123
- using json = nlohmann::json;
124
55
 
125
56
  int main() {
126
- // Build the app with configuration
127
- auto appBuilder = createApp()
57
+ auto mainWindow = createApp()
128
58
  .title(appConfig.name)
129
59
  .width(windowConfig.width)
130
60
  .height(windowConfig.height)
131
- .centered(windowConfig.centerOnStartup)
132
61
  .resizable(windowConfig.resizable)
133
- .decorations(windowConfig.showTitlebar && windowConfig.showBorder)
62
+ .decorations(windowConfig.showTitlebar)
134
63
  .transparent(windowConfig.transparent)
135
64
  .devtools(windowConfig.devTools)
136
65
  .alwaysOnTop(windowConfig.alwaysOnTop)
137
66
  .skipTaskbar(!windowConfig.showInTaskbar)
138
67
  .scrollbars(windowConfig.scrollbars)
139
- .enableFileDrop(windowConfig.enableFileDrop);
68
+ .enableFileDrop(windowConfig.enableFileDrop)
69
+ .centered(true)
70
+ .build();
140
71
 
141
- auto mainWindow = appBuilder.build();
142
-
143
- // ========================================
144
- // ICON SETUP
145
- // ========================================
146
- // Set app icons from embedded assets
72
+ // Icon from embedded assets
147
73
  #ifdef ASSET_ICON_PNG
148
74
  mainWindow.setIconFromMemory(ASSET_ICON_PNG, ASSET_ICON_PNG_LEN);
149
- mainWindow.tray().setIconFromMemory(ASSET_ICON_PNG, ASSET_ICON_PNG_LEN);
150
75
  #endif
151
-
152
- // ========================================
153
- // SYSTEM TRAY (optional)
154
- // ========================================
155
- // mainWindow.tray().setTooltip(appConfig.name);
156
- // mainWindow.tray().setVisible(true);
157
-
158
- // Apply additional settings
159
- if (windowConfig.opacity < 1.0) {
160
- mainWindow.setOpacity(windowConfig.opacity);
161
- }
162
- if (windowConfig.fullscreen) {
163
- mainWindow.setFullscreen(true);
164
- }
165
-
166
- // ========================================
167
- // SYSTEM TRAY (if enabled)
168
- // ========================================
169
- // HEADLESS / HIDDEN STARTUP
170
- // ========================================
171
- if (appConfig.headless || appConfig.hiddenOnStartup || appConfig.showWhenFrontendReady) {
76
+
77
+ // Hide until frontend signals it's ready (no blank flash)
78
+ if (appConfig.showWhenFrontendReady) {
172
79
  mainWindow.hide();
173
80
  }
174
-
175
- // ========================================
176
- // NAVIGATION — Load Frontend Route
177
- // ========================================
178
- // The window loads the route from windowConfig.route.
179
- // In dev mode: http://localhost:{port}{route}
180
- // In production: file://frontend/dist/index.html#{route}
81
+
82
+ // Load frontend
181
83
  #ifdef PLUSUI_DEV_MODE
182
- std::string url = "http://localhost:" + std::to_string(windowConfig.devServerPort) + windowConfig.route;
183
- mainWindow.navigate(url);
84
+ mainWindow.navigate("http://localhost:" + std::to_string(windowConfig.devServerPort) + windowConfig.route);
184
85
  #else
185
- if (windowConfig.route == "/") {
186
- mainWindow.loadFile("frontend/dist/index.html");
187
- } else {
188
- mainWindow.loadFile("frontend/dist/index.html#" + windowConfig.route);
189
- }
86
+ mainWindow.loadFile("frontend/dist/index.html" + (windowConfig.route == "/" ? "" : "#" + windowConfig.route));
190
87
  #endif
191
-
192
- // ========================================
193
- // CONNECT bind frontend backend
194
- // ========================================
195
- // Wires the connect object to this window, then initialises
196
- // the auto-generated channels so you can use them directly:
197
- // customFileDrop.on([](const json& p) { ... });
198
- // customFileDrop.emit({{"value", 42}});
199
- bindConnect(mainWindow, conn);
200
- initChannels(conn);
201
-
202
- // ========================================
203
- // NATIVE FILE DROP
204
- // ========================================
205
- // The PlusUI native file drop feature is enabled in WindowConfig.
206
- // Frontend dropped files are automatically passed to C++ as JSON.
207
- // Frontend also receives the 'plusui:fileDrop.filesDropped' event automatically.
88
+
89
+ // File drop — C++ receives dropped files automatically.
90
+ // Frontend receives the event via plusui.fileDrop.onFilesDropped().
208
91
  mainWindow.onFileDrop([](const std::string& filesJson) {
209
92
  try {
210
93
  auto files = json::parse(filesJson);
211
- int count = static_cast<int>(files.size());
212
- std::cout << "Native FileDrop: received " << count << " file(s)" << std::endl;
94
+ std::cout << "[FileDrop] " << files.size() << " file(s) received\n";
213
95
  for (const auto& f : files) {
214
- std::cout << " - " << f.value("name", "?") << " (" << f.value("size", 0) << " bytes)" << std::endl;
96
+ std::cout << " " << f.value("name", "?")
97
+ << " (" << f.value("size", 0) << " bytes)\n";
215
98
  }
216
- } catch (const std::exception& e) {
217
- std::cout << "Native FileDrop Error: " << e.what() << std::endl;
218
- }
99
+ } catch (...) {}
219
100
  });
220
-
221
- // ========================================
222
- // RUN APPLICATION
223
- // ========================================
101
+
224
102
  App runtime;
225
103
  runtime.run();
226
-
227
104
  return 0;
228
105
  }
229
-
230
- // ============================================================================
231
- // FRONTEND API REFERENCE
232
- // ============================================================================
233
- // import plusui from 'plusui';
234
- // import { connect, win, clipboard, app, browser, router, fileDrop } from 'plusui';
235
- //
236
- // CONNECT (custom channels — same API on both sides):
237
- // Run `plusui connect` to generate Connections/ from your name.on / name.emit calls.
238
- // C++: customFileDrop.on([](const json& p) { ... }); // receive
239
- // customFileDrop.emit({{"value", 42}}); // send
240
- // TS: customFileDrop.on((data) => { ... }); // receive
241
- // customFileDrop.emit({ value: 42 }); // send
242
- //
243
- // WINDOW: win.minimize(), win.maximize(), win.close(), win.center(),
244
- // win.setSize(w, h), win.setPosition(x, y), win.setTitle(str),
245
- // win.setFullscreen(bool), win.setAlwaysOnTop(bool),
246
- // win.getSize(), win.getPosition(), win.isMaximized(), win.isVisible()
247
- //
248
- // BROWSER: browser.navigate(url), browser.goBack(), browser.goForward(),
249
- // browser.reload(), browser.getUrl(), browser.canGoBack()
250
- //
251
- // ROUTER: router.setRoutes({'/': 'url'}), router.push('/path')
252
- //
253
- // APP: app.quit(), app.invoke('customFn', args)
254
- //
255
- // DISPLAY: display.getAll(), display.getPrimary(), display.getCurrent()
256
- //
257
- // CLIPBOARD: clipboard.writeText(str), clipboard.readText(), clipboard.clear()
258
- //
259
- // FILEDROP: fileDrop.onFilesDropped(callback), fileDrop.setEnabled(bool),
260
- // fileDrop.onDragEnter(callback), fileDrop.onDragLeave(callback),
261
- // fileDrop.startDrag([paths])
262
-
@@ -1,191 +1,120 @@
1
- import { createSignal, onMount, onCleanup, Show, For } from 'solid-js';
1
+ import { createSignal, onMount, onCleanup } from 'solid-js';
2
2
  import plusui from 'plusui';
3
- // Built-in features are exposed directly on the plusui object
3
+
4
+ // That's the only import you need.
5
+ // All features are available directly:
6
+ // plusui.window — show, hide, minimize, maximize, setTitle, setSize ...
7
+ // plusui.app — quit
8
+ // plusui.router — push, setRoutes
9
+ // plusui.clipboard— getText, setText
10
+ // plusui.fileDrop — onFilesDropped, setEnabled
11
+ // plusui.tray — setIcon, setTooltip, onClick
4
12
 
5
13
  function App() {
6
14
  const [windowSize, setWindowSize] = createSignal({ width: 0, height: 0 });
7
- const [windowPos, setWindowPos] = createSignal({ x: 0, y: 0 });
8
- const [currentUrl, setCurrentUrl] = createSignal('');
9
- const [canGoBack, setCanGoBack] = createSignal(false);
10
- const [canGoForward, setCanGoForward] = createSignal(false);
11
-
12
- // Native FileDrop state
13
- const [isDragging, setIsDragging] = createSignal(false);
15
+ const [isMaximized, setIsMaximized] = createSignal(false);
14
16
  const [droppedFiles, setDroppedFiles] = createSignal<{ name: string; size: number; type: string }[]>([]);
15
- const [backendMsg, setBackendMsg] = createSignal<string | null>(null);
17
+ const [isDragging, setIsDragging] = createSignal(false);
16
18
 
17
19
  onMount(() => {
18
- // Setup routes
19
- plusui.router.setRoutes({
20
- '/app': 'http://localhost:5173/app',
21
- '/about': 'http://localhost:5173/about',
22
- });
20
+ // Show the window once the UI is mounted.
21
+ // C++ hides it on startup (showWhenFrontendReady = true) so there's no flash.
22
+ plusui.window.show();
23
23
 
24
- // Listen for navigation changes
25
- const unsubNav = plusui.browser.onNavigate((url) => {
26
- setCurrentUrl(url);
27
- plusui.browser.canGoBack().then(setCanGoBack);
28
- plusui.browser.canGoForward().then(setCanGoForward);
29
- });
24
+ // Window events fire automatically, no wiring in main.cpp needed.
25
+ const unsubResize = plusui.window.onResize((size: { width: number; height: number }) => setWindowSize(size));
26
+ const unsubState = plusui.window.onStateChange((state: { isMaximized: boolean; isMinimized: boolean; isVisible: boolean; isFullscreen: boolean }) => setIsMaximized(state.isMaximized));
30
27
 
31
- // Get initial state
32
- plusui.browser.getUrl().then(setCurrentUrl);
33
- plusui.browser.canGoBack().then(setCanGoBack);
34
- plusui.browser.canGoForward().then(setCanGoForward);
28
+ // File drop — built-in, works out of the box.
29
+ const unsubDrop = plusui.fileDrop.onFilesDropped((files: { name: string; size: number; type: string }[]) => setDroppedFiles(files));
30
+ const unsubEnter = plusui.fileDrop.onDragEnter(() => setIsDragging(true));
31
+ const unsubLeave = plusui.fileDrop.onDragLeave(() => setIsDragging(false));
35
32
 
36
- // Listen for native file drops
37
- const unsubFileDrop = plusui.fileDrop.onFilesDropped((files: any[]) => {
38
- setDroppedFiles(files);
39
- setBackendMsg(`Native FileDrop: Received ${files.length} file(s)`);
40
- });
41
-
42
- const unsubDragEnter = plusui.fileDrop.onDragEnter(() => setIsDragging(true));
43
- const unsubDragLeave = plusui.fileDrop.onDragLeave(() => setIsDragging(false));
33
+ // Seed initial size
34
+ plusui.window.getSize().then(setWindowSize);
44
35
 
45
36
  onCleanup(() => {
46
- unsubNav();
47
- unsubFileDrop();
48
- unsubDragEnter();
49
- unsubDragLeave();
37
+ unsubResize();
38
+ unsubState();
39
+ unsubDrop();
40
+ unsubEnter();
41
+ unsubLeave();
50
42
  });
51
43
  });
52
44
 
53
- const handleMinimize = async () => await plusui.win.minimize();
54
- const handleMaximize = async () => await plusui.win.maximize();
55
- const handleClose = async () => await plusui.win.close();
56
- const handleGetSize = async () => {
57
- const size = await plusui.win.getSize();
58
- setWindowSize(size);
59
- };
60
- const handleGetPosition = async () => {
61
- const pos = await plusui.win.getPosition();
62
- setWindowPos(pos);
63
- };
64
-
65
- // Browser navigation
66
- const handleGoBack = async () => await plusui.browser.goBack();
67
- const handleGoForward = async () => await plusui.browser.goForward();
68
- const handleReload = async () => await plusui.browser.reload();
69
-
70
- // App control
71
- const handleQuit = async () => await plusui.app.quit();
72
-
73
- // Native FileDrop handlers
74
- // The C++ native window automatically detects elements with class 'filedrop-zone'.
75
- // We do not need HTML5 drag events, the C++ backend emits events directly.
76
-
77
45
  return (
78
46
  <div class="app">
79
47
  <header class="app-header">
80
- <h1>{'{{PROJECT_NAME}}'} App</h1>
81
- <p>Built with PlusUI Framework</p>
48
+ <h1>{'{{PROJECT_NAME}}'}</h1>
49
+ <p>Built with PlusUI</p>
82
50
  </header>
83
51
 
84
52
  <main class="app-content">
85
- <div class="card">
86
- <h2>Window Controls</h2>
87
- <div class="button-group">
88
- <button onClick={handleMinimize} class="button">Minimize</button>
89
- <button onClick={handleMaximize} class="button">Maximize</button>
90
- <button onClick={handleClose} class="button button-danger">Close</button>
91
- </div>
92
- </div>
93
53
 
54
+ {/* ── Window ─────────────────────────────────────────────── */}
94
55
  <div class="card">
95
- <h2>Window Size</h2>
96
- <button onClick={handleGetSize} class="button">Get Size</button>
97
- <Show when={windowSize().width > 0}>
98
- <p class="info-text">Size: {windowSize().width} x {windowSize().height}</p>
99
- </Show>
100
- </div>
101
-
102
- <div class="card">
103
- <h2>Window Position</h2>
104
- <div class="button-group">
105
- <button onClick={handleGetPosition} class="button">Get Position</button>
106
- <button onClick={() => plusui.win.setPosition(100, 100)} class="button">Move Left</button>
107
- <button onClick={() => plusui.win.setPosition(800, 100)} class="button">Move Right</button>
108
- </div>
109
- <Show when={windowPos().x !== 0}>
110
- <p class="info-text">Position: {windowPos().x}, {windowPos().y}</p>
111
- </Show>
112
- </div>
113
-
114
- <div class="card">
115
- <h2>Browser Navigation</h2>
116
- <p class="info-text">Current: {currentUrl()}</p>
56
+ <h2>Window</h2>
117
57
  <div class="button-group">
118
- <button onClick={handleGoBack} class="button" disabled={!canGoBack()}>Back</button>
119
- <button onClick={handleGoForward} class="button" disabled={!canGoForward()}>Forward</button>
120
- <button onClick={handleReload} class="button">Reload</button>
58
+ <button class="button" onClick={() => plusui.window.show()}>Show</button>
59
+ <button class="button" onClick={() => plusui.window.hide()}>Hide</button>
60
+ <button class="button" onClick={() => plusui.window.minimize()}>Minimize</button>
61
+ <button class="button" onClick={() => isMaximized() ? plusui.window.restore() : plusui.window.maximize()}>
62
+ {isMaximized() ? 'Restore' : 'Maximize'}
63
+ </button>
64
+ <button class="button" onClick={() => plusui.window.center()}>Center</button>
65
+ <button class="button button-danger" onClick={() => plusui.window.close()}>Close</button>
121
66
  </div>
67
+ {windowSize().width > 0 && (
68
+ <p class="info-text">Size: {windowSize().width} × {windowSize().height}</p>
69
+ )}
122
70
  </div>
123
71
 
72
+ {/* ── App ────────────────────────────────────────────────── */}
124
73
  <div class="card">
125
- <h2>App Control</h2>
126
- <button onClick={handleQuit} class="button button-danger">Quit App</button>
74
+ <h2>App</h2>
75
+ <button class="button button-danger" onClick={() => plusui.app.quit()}>Quit</button>
127
76
  </div>
128
77
 
78
+ {/* ── File Drop ──────────────────────────────────────────── */}
129
79
  <div class="card">
130
- <h2>Native FileDrop API</h2>
131
- <p style={{ 'font-size': '0.85em', color: '#aaa', 'margin-bottom': '1rem' }}>
132
- Drop files below. The <code>plusui.fileDrop</code> API is handled natively in C++
133
- which overrides standard browser drag-and-drop. It automatically detects elements
134
- with the <code>.filedrop-zone</code> class or <code>data-dropzone="true"</code>.
135
- </p>
136
-
80
+ <h2>File Drop</h2>
137
81
  <div
138
82
  class={`filedrop-zone ${isDragging() ? 'filedrop-active' : ''}`}
139
83
  data-dropzone="true"
140
84
  >
141
85
  <div class="filedrop-content">
142
- <svg class="filedrop-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor">
143
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width={2}
144
- d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12" />
145
- </svg>
146
86
  <div class="filedrop-text">
147
- {isDragging() ? 'Drop files here' : 'Drag & drop files to send to C++'}
87
+ {isDragging() ? 'Drop files here' : 'Drag & drop files here'}
148
88
  </div>
149
89
  </div>
150
90
  </div>
151
91
 
152
- <Show when={droppedFiles().length > 0}>
92
+ {droppedFiles().length > 0 && (
153
93
  <div class="filedrop-files" style={{ 'margin-top': '1rem' }}>
154
- <p style={{ 'font-size': '0.8em', color: '#888', 'margin-bottom': '0.5rem' }}>
155
- Sent to C++ via <code>customFileDrop.emit()</code>:
156
- </p>
157
- <For each={droppedFiles()}>
158
- {(file) => (
159
- <div class="filedrop-file-item">
160
- <div class="filedrop-file-icon">📄</div>
161
- <div class="filedrop-file-info">
162
- <div class="filedrop-file-name">{file.name}</div>
163
- <div class="filedrop-file-meta">{plusui.formatFileSize(file.size)} • {file.type}</div>
94
+ {droppedFiles().map((file: { name: string; size: number; type: string }) => (
95
+ <div class="filedrop-file-item">
96
+ <div class="filedrop-file-icon">📄</div>
97
+ <div class="filedrop-file-info">
98
+ <div class="filedrop-file-name">{file.name}</div>
99
+ <div class="filedrop-file-meta">
100
+ {plusui.formatFileSize(file.size)} · {file.type || 'unknown'}
164
101
  </div>
165
102
  </div>
166
- )}
167
- </For>
103
+ </div>
104
+ ))}
168
105
  </div>
169
- </Show>
170
-
171
- <Show when={backendMsg() !== null}>
172
- <div style={{ 'margin-top': '1rem', padding: '0.75rem 1rem', background: '#1a2e1a', border: '1px solid #2d5a2d', 'border-radius': '6px' }}>
173
- <p style={{ 'font-size': '0.8em', color: '#4caf50', margin: '0' }}>
174
- ✓ C++ replied via <code>ch.customFileDrop.emit()</code>:
175
- </p>
176
- <p style={{ 'font-size': '0.9em', color: '#e0e0e0', margin: '0.4rem 0 0' }}>{backendMsg()}</p>
177
- </div>
178
- </Show>
106
+ )}
179
107
  </div>
180
108
 
181
109
  <div class="info">
182
- <p>Edit <code>frontend/src/App.tsx</code> to modify the UI.</p>
183
- <p>Edit <code>main.cpp</code> to add C++ functionality.</p>
110
+ <p>Edit <code>frontend/src/App.tsx</code> for the UI.</p>
111
+ <p>Edit <code>main.cpp</code> for native backend logic.</p>
112
+ <p>Run <code>plusui connect</code> to add custom C++ ↔ frontend channels.</p>
184
113
  </div>
114
+
185
115
  </main>
186
116
  </div>
187
117
  );
188
118
  }
189
119
 
190
120
  export default App;
191
-
@@ -1,12 +1,29 @@
1
1
  import { defineConfig } from 'vite';
2
2
  import solid from 'vite-plugin-solid';
3
+ import { resolve, dirname } from 'path';
4
+ import { fileURLToPath } from 'url';
5
+ import { existsSync } from 'fs';
6
+
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = dirname(__filename);
9
+
10
+ // Resolve plusui-native-core entry directly (bypasses package.json exports)
11
+ function findPlusuiEntry(): string {
12
+ const candidates = [
13
+ resolve(__dirname, '../node_modules/plusui-native-core/Core/Features/API/index.ts'),
14
+ resolve(__dirname, '../node_modules/plusui-native-core/Core/API/index.ts'),
15
+ ];
16
+ for (const p of candidates) {
17
+ if (existsSync(p)) return p;
18
+ }
19
+ return 'plusui-native-core';
20
+ }
3
21
 
4
22
  export default defineConfig({
5
23
  plugins: [solid()],
6
24
  resolve: {
7
25
  alias: {
8
- // `import plusui from 'plusui'` resolves to the installed plusui-native-core package
9
- plusui: 'plusui-native-core',
26
+ plusui: findPlusuiEntry(),
10
27
  },
11
28
  },
12
29
  build: {
@@ -1,256 +1,105 @@
1
- #include <plusui/plusui.hpp> // All-in-one framework header
1
+ #include <plusui/plusui>
2
2
  #include <iostream>
3
3
  #include "generated/assets.h"
4
- // ── Generated channel bindings (run `plusui connect` to regenerate) ──────────
5
- #include "Connections/connections.gen.hpp"
6
4
 
7
5
  using namespace plusui;
6
+ using json = nlohmann::json;
8
7
 
9
8
  // ============================================================================
10
- // {{PROJECT_NAME}} - Configuration
9
+ // {{PROJECT_NAME}} Configuration
11
10
  // ============================================================================
12
- // Change these values to configure your app. All options are true/false toggles
13
- // or simple values. Easy to customize without digging through code.
11
+ // Edit these structs to configure your app.
12
+ // Built-in features (window, router, clipboard, etc.) work without any extra
13
+ // wiring — just call them from the frontend via `import plusui from 'plusui'`.
14
+ //
15
+ // Custom frontend ↔ backend channels:
16
+ // Run `plusui connect` to generate typed channels for your own events.
14
17
  // ============================================================================
15
18
 
16
- // ----------------------------------------------------------------------------
17
- // APP CONFIGURATION (Application-level settings)
18
- // ----------------------------------------------------------------------------
19
19
  struct AppConfig {
20
- // --- Identity ---
21
- std::string name = "{{PROJECT_NAME}}"; // Your app name (used for window title, etc.)
20
+ std::string name = "{{PROJECT_NAME}}";
22
21
  std::string version = "0.1.0";
23
-
24
- // --- App Type ---
25
- bool headless = false; // No window, background app only
26
- bool singleInstance = true; // Only allow one instance of the app
27
-
28
- // --- Startup ---
29
- bool autoStart = false; // Launch on system startup
30
- bool hiddenOnStartup = false; // Start hidden
31
-
32
- // --- Security ---
33
- bool webSecurity = true; // Enable web security (CORS, etc.)
34
- bool allowFileAccess = false; // Allow file:// URLs
22
+ bool showWhenFrontendReady = true; // Hides blank webview until Solid mounts
35
23
  } appConfig;
36
24
 
37
- // ----------------------------------------------------------------------------
38
- // WINDOW CONFIGURATION (Window appearance and behavior)
39
- // ----------------------------------------------------------------------------
40
25
  struct WindowConfig {
41
- // --- Size & Position ---
42
- int width = 1200;
26
+ int width = 1200;
43
27
  int height = 800;
44
- int minWidth = 400; // Minimum window width
45
- int minHeight = 300; // Minimum window height
46
- int maxWidth = 0; // 0 = no limit
47
- int maxHeight = 0; // 0 = no limit
48
- bool centerOnStartup = true; // Center window on launch
49
-
50
- // --- Appearance ---
51
- bool showTitlebar = true; // Show native titlebar (set false for frameless)
52
- bool showBorder = true; // Show window border
53
- bool transparent = false; // Transparent window background
54
- double opacity = 1.0; // Window opacity 0.0 (invisible) to 1.0 (solid)
55
-
56
- // --- Behavior ---
57
- bool resizable = true; // Allow user to resize window
58
- bool minimizable = true; // Show minimize button
59
- bool maximizable = true; // Show maximize button
60
- bool closable = true; // Show close button
61
- bool alwaysOnTop = false; // Keep window above all others
62
- bool showInTaskbar = true; // Show in taskbar (false = hidden from taskbar)
63
- bool fullscreen = false; // Start in fullscreen mode
64
- bool enableFileDrop = true; // Enable native FileDrop API (disables webview drag-drop)
65
-
66
- // --- Dev Tools ---
67
- bool devTools = true; // Enable DevTools (F12, right-click inspect)
68
- bool contextMenu = true; // Enable right-click context menu
69
-
70
- // --- WebView ---
71
- bool scrollbars = false; // Show scrollbars in webview (false = hidden)
72
-
73
- // --- Routing ---
74
- // The frontend route this window opens on.
75
- // Use "/app" for main view, "/settings" for settings page, etc.
76
- // Works with any frontend router (Solid Router, TanStack, etc.).
77
- std::string route = "/app"; // Starting route (e.g. "/app", "/settings")
78
- int devServerPort = 5173; // Vite dev server port
28
+ bool resizable = true;
29
+ bool showTitlebar = true;
30
+ bool transparent = false;
31
+ double opacity = 1.0;
32
+ bool alwaysOnTop = false;
33
+ bool showInTaskbar = true;
34
+ bool devTools = true;
35
+ bool scrollbars = false;
36
+ bool enableFileDrop = true;
37
+ std::string route = "/"; // Starting route
38
+ int devServerPort = 5173;
79
39
  } windowConfig;
80
40
 
81
- // ----------------------------------------------------------------------------
82
- // SERVICES CONFIGURATION (Optional features)
83
- // ----------------------------------------------------------------------------
84
- struct ServicesConfig {
85
- bool enableClipboard = true; // Enable clipboard read/write from frontend
86
- bool enableDisplay = true; // Enable multi-display detection
87
- bool enableKeyboard = true; // Enable keyboard shortcuts/hotkeys
88
- bool enableMenu = true; // Enable custom menu bar
89
- } servicesConfig;
90
-
91
41
  // ============================================================================
92
- // WEBGPU CONFIGURATION (Optional - 3D Graphics & GPU Compute)
42
+ // CUSTOM CHANNELS (optional)
93
43
  // ============================================================================
94
- // WebGPU provides native GPU acceleration for rendering and compute shaders.
95
- // Requires: PLUSUI_ENABLE_WEBGPU build flag
44
+ // If you need your own frontend backend events beyond the built-in features,
45
+ // use `plusui connect`. Add a Connect subclass here and bind it:
96
46
  //
97
- // Usage in frontend:
98
- // const adapter = await navigator.gpu.requestAdapter();
99
- // const device = await adapter.requestDevice();
100
- // const canvas = document.querySelector('canvas');
101
- // const context = canvas.getContext('webgpu');
102
- // context.configure({ device, format: 'bgra8unorm' });
47
+ // #include "Connections/connections.gen.hpp"
48
+ // static Connect conn;
49
+ // bindConnect(mainWindow, conn);
50
+ // initChannels(conn);
103
51
  //
104
- // See: https://www.w3.org/TR/webgpu/ for full API documentation
52
+ // Then in C++: myChannel.emit({{"key", "value"}});
53
+ // And frontend: myChannel.on((data) => { ... });
105
54
  // ============================================================================
106
- struct WebGPUConfig {
107
- bool enabled = false; // Enable WebGPU support
108
- bool validateAPI = true; // Enable validation in debug builds
109
- bool verbose = false; // Enable verbose logging for debugging
110
- } webgpuConfig;
111
-
112
- // ============================================================================
113
- // MAIN - Application Entry Point
114
- // ============================================================================
115
- // ── Connect instance ─────────────────────────────────────────────────────────
116
- // conn is the bridge between C++ and the frontend.
117
- // Run `plusui connect` to generate Connections/ from your name.on / name.emit usage.
118
- // Call initChannels(conn) once, then use channels directly:
119
- // customFileDrop.on([](const json& p) { ... });
120
- // customFileDrop.emit({{"value", 42}});
121
- static Connect conn;
122
- using json = nlohmann::json;
123
55
 
124
56
  int main() {
125
- // Build the app with configuration
126
- auto appBuilder = createApp()
57
+ auto mainWindow = createApp()
127
58
  .title(appConfig.name)
128
59
  .width(windowConfig.width)
129
60
  .height(windowConfig.height)
130
- .centered(windowConfig.centerOnStartup)
131
61
  .resizable(windowConfig.resizable)
132
- .decorations(windowConfig.showTitlebar && windowConfig.showBorder)
62
+ .decorations(windowConfig.showTitlebar)
133
63
  .transparent(windowConfig.transparent)
134
64
  .devtools(windowConfig.devTools)
135
65
  .alwaysOnTop(windowConfig.alwaysOnTop)
136
66
  .skipTaskbar(!windowConfig.showInTaskbar)
137
67
  .scrollbars(windowConfig.scrollbars)
138
- .enableFileDrop(windowConfig.enableFileDrop);
68
+ .enableFileDrop(windowConfig.enableFileDrop)
69
+ .centered(true)
70
+ .build();
139
71
 
140
- auto mainWindow = appBuilder.build();
141
-
142
- // Set app icon from embedded assets
72
+ // Icon from embedded assets
143
73
  #ifdef ASSET_ICON_PNG
144
74
  mainWindow.setIconFromMemory(ASSET_ICON_PNG, ASSET_ICON_PNG_LEN);
145
- mainWindow.tray().setIconFromMemory(ASSET_ICON_PNG, ASSET_ICON_PNG_LEN);
146
75
  #endif
147
-
148
- // Set tray tooltip and visibility
149
- mainWindow.tray().setTooltip(appConfig.name);
150
- mainWindow.tray().setVisible(true);
151
-
152
- // Apply additional settings
153
- if (windowConfig.opacity < 1.0) {
154
- mainWindow.setOpacity(windowConfig.opacity);
155
- }
156
- if (windowConfig.fullscreen) {
157
- mainWindow.setFullscreen(true);
158
- }
159
-
160
- // ========================================
161
- // SYSTEM TRAY (if enabled)
162
- // ========================================
163
- // HEADLESS / HIDDEN STARTUP
164
- // ========================================
165
- if (appConfig.headless || appConfig.hiddenOnStartup) {
76
+
77
+ // Hide until frontend signals it's ready (no blank flash)
78
+ if (appConfig.showWhenFrontendReady) {
166
79
  mainWindow.hide();
167
80
  }
168
-
169
- // ========================================
170
- // NAVIGATION — Load Frontend Route
171
- // ========================================
172
- // The window loads the route from windowConfig.route.
173
- // In dev mode: http://localhost:{port}{route}
174
- // In production: file://frontend/dist/index.html#{route}
81
+
82
+ // Load frontend
175
83
  #ifdef PLUSUI_DEV_MODE
176
- std::string url = "http://localhost:" + std::to_string(windowConfig.devServerPort) + windowConfig.route;
177
- mainWindow.navigate(url);
84
+ mainWindow.navigate("http://localhost:" + std::to_string(windowConfig.devServerPort) + windowConfig.route);
178
85
  #else
179
- if (windowConfig.route == "/") {
180
- mainWindow.loadFile("frontend/dist/index.html");
181
- } else {
182
- mainWindow.loadFile("frontend/dist/index.html#" + windowConfig.route);
183
- }
86
+ mainWindow.loadFile("frontend/dist/index.html" + (windowConfig.route == "/" ? "" : "#" + windowConfig.route));
184
87
  #endif
185
-
186
- // ========================================
187
- // CONNECT bind frontend backend
188
- // ========================================
189
- // Wires the connect object to this window, then initialises
190
- // the auto-generated channels so you can use them directly:
191
- // customFileDrop.on([](const json& p) { ... });
192
- // customFileDrop.emit({{"value", 42}});
193
- bindConnect(mainWindow, conn);
194
- initChannels(conn);
195
-
196
- // ========================================
197
- // NATIVE FILE DROP
198
- // ========================================
199
- // The PlusUI native file drop feature is enabled in WindowConfig.
200
- // Frontend dropped files are automatically passed to C++ as JSON.
201
- // Frontend also receives the 'plusui:fileDrop.filesDropped' event automatically.
88
+
89
+ // File drop — C++ receives dropped files automatically.
90
+ // Frontend receives the event via plusui.fileDrop.onFilesDropped().
202
91
  mainWindow.onFileDrop([](const std::string& filesJson) {
203
92
  try {
204
93
  auto files = json::parse(filesJson);
205
- int count = static_cast<int>(files.size());
206
- std::cout << "Native FileDrop: received " << count << " file(s)" << std::endl;
94
+ std::cout << "[FileDrop] " << files.size() << " file(s) received\n";
207
95
  for (const auto& f : files) {
208
- std::cout << " - " << f.value("name", "?") << " (" << f.value("size", 0) << " bytes)" << std::endl;
96
+ std::cout << " " << f.value("name", "?")
97
+ << " (" << f.value("size", 0) << " bytes)\n";
209
98
  }
210
- } catch (const std::exception& e) {
211
- std::cout << "Native FileDrop Error: " << e.what() << std::endl;
212
- }
99
+ } catch (...) {}
213
100
  });
214
-
215
- // ========================================
216
- // RUN APPLICATION
217
- // ========================================
101
+
218
102
  App runtime;
219
103
  runtime.run();
220
-
221
104
  return 0;
222
105
  }
223
-
224
- // ============================================================================
225
- // FRONTEND API REFERENCE
226
- // ============================================================================
227
- // import plusui from 'plusui';
228
- // import { connect, win, clipboard, app, browser, router, fileDrop } from 'plusui';
229
- //
230
- // CONNECT (custom channels — same API on both sides):
231
- // Run `plusui connect` to generate Connections/ from your name.on / name.emit calls.
232
- // C++: customFileDrop.on([](const json& p) { ... }); // receive
233
- // customFileDrop.emit({{"value", 42}}); // send
234
- // TS: customFileDrop.on((data) => { ... }); // receive
235
- // customFileDrop.emit({ value: 42 }); // send
236
- //
237
- // WINDOW: win.minimize(), win.maximize(), win.close(), win.center(),
238
- // win.setSize(w, h), win.setPosition(x, y), win.setTitle(str),
239
- // win.setFullscreen(bool), win.setAlwaysOnTop(bool),
240
- // win.getSize(), win.getPosition(), win.isMaximized(), win.isVisible()
241
- //
242
- // BROWSER: browser.navigate(url), browser.goBack(), browser.goForward(),
243
- // browser.reload(), browser.getUrl(), browser.canGoBack()
244
- //
245
- // ROUTER: router.setRoutes({'/': 'url'}), router.push('/path')
246
- //
247
- // APP: app.quit(), app.invoke('customFn', args)
248
- //
249
- // DISPLAY: display.getAll(), display.getPrimary(), display.getCurrent()
250
- //
251
- // CLIPBOARD: clipboard.writeText(str), clipboard.readText(), clipboard.clear()
252
- //
253
- // FILEDROP: fileDrop.onFilesDropped(callback), fileDrop.setEnabled(bool),
254
- // fileDrop.onDragEnter(callback), fileDrop.onDragLeave(callback),
255
- // fileDrop.startDrag([paths])
256
-