plusui-native 0.2.89 → 0.2.91
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.
|
|
3
|
+
"version": "0.2.91",
|
|
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.
|
|
31
|
-
"plusui-native-connect": "^0.1.
|
|
30
|
+
"plusui-native-builder": "^0.1.89",
|
|
31
|
+
"plusui-native-connect": "^0.1.89"
|
|
32
32
|
},
|
|
33
33
|
"peerDependencies": {
|
|
34
|
-
"plusui-native-connect": "^0.1.
|
|
34
|
+
"plusui-native-connect": "^0.1.89"
|
|
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
|
-
|
|
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 [
|
|
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 [
|
|
17
|
+
const [isDragging, setIsDragging] = useState(false);
|
|
16
18
|
|
|
17
19
|
useEffect(() => {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
//
|
|
23
|
-
plusui.
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
|
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
|
|
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
|
|
110
|
-
<button onClick={() => plusui.
|
|
111
|
-
<button onClick={() => plusui.
|
|
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
|
-
{
|
|
114
|
-
<p className="info-text">
|
|
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>
|
|
120
|
-
<
|
|
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>
|
|
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
|
|
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
|
-
|
|
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">
|
|
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>
|
|
185
|
-
<p>Edit <code>main.cpp</code>
|
|
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,262 +1,105 @@
|
|
|
1
|
-
#include <plusui/plusui
|
|
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}}
|
|
9
|
+
// {{PROJECT_NAME}} — Configuration
|
|
11
10
|
// ============================================================================
|
|
12
|
-
//
|
|
13
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
43
|
-
int width = 1200;
|
|
26
|
+
int width = 1200;
|
|
44
27
|
int height = 800;
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
bool
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
bool
|
|
53
|
-
bool
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
//
|
|
42
|
+
// CUSTOM CHANNELS (optional)
|
|
94
43
|
// ============================================================================
|
|
95
|
-
//
|
|
96
|
-
//
|
|
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
|
-
//
|
|
99
|
-
//
|
|
100
|
-
//
|
|
101
|
-
//
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
183
|
-
mainWindow.navigate(url);
|
|
84
|
+
mainWindow.navigate("http://localhost:" + std::to_string(windowConfig.devServerPort) + windowConfig.route);
|
|
184
85
|
#else
|
|
185
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
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 << "
|
|
96
|
+
std::cout << " " << f.value("name", "?")
|
|
97
|
+
<< " (" << f.value("size", 0) << " bytes)\n";
|
|
215
98
|
}
|
|
216
|
-
} catch (
|
|
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
|
|
1
|
+
import { createSignal, onMount, onCleanup } from 'solid-js';
|
|
2
2
|
import plusui from 'plusui';
|
|
3
|
-
|
|
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 [
|
|
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 [
|
|
17
|
+
const [isDragging, setIsDragging] = createSignal(false);
|
|
16
18
|
|
|
17
19
|
onMount(() => {
|
|
18
|
-
//
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
//
|
|
25
|
-
const
|
|
26
|
-
|
|
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
|
-
//
|
|
32
|
-
plusui.
|
|
33
|
-
plusui.
|
|
34
|
-
plusui.
|
|
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
|
-
//
|
|
37
|
-
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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}}'}
|
|
81
|
-
<p>Built with PlusUI
|
|
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
|
|
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
|
|
119
|
-
<button
|
|
120
|
-
<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
|
|
126
|
-
<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>
|
|
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
|
|
87
|
+
{isDragging() ? 'Drop files here' : 'Drag & drop files here'}
|
|
148
88
|
</div>
|
|
149
89
|
</div>
|
|
150
90
|
</div>
|
|
151
91
|
|
|
152
|
-
|
|
92
|
+
{droppedFiles().length > 0 && (
|
|
153
93
|
<div class="filedrop-files" style={{ 'margin-top': '1rem' }}>
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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
|
-
|
|
103
|
+
</div>
|
|
104
|
+
))}
|
|
168
105
|
</div>
|
|
169
|
-
|
|
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>
|
|
183
|
-
<p>Edit <code>main.cpp</code>
|
|
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,256 +1,105 @@
|
|
|
1
|
-
#include <plusui/plusui
|
|
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}}
|
|
9
|
+
// {{PROJECT_NAME}} — Configuration
|
|
11
10
|
// ============================================================================
|
|
12
|
-
//
|
|
13
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
42
|
-
int width = 1200;
|
|
26
|
+
int width = 1200;
|
|
43
27
|
int height = 800;
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
bool
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
bool
|
|
52
|
-
bool
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
//
|
|
42
|
+
// CUSTOM CHANNELS (optional)
|
|
93
43
|
// ============================================================================
|
|
94
|
-
//
|
|
95
|
-
//
|
|
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
|
-
//
|
|
98
|
-
//
|
|
99
|
-
//
|
|
100
|
-
//
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
//
|
|
149
|
-
|
|
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
|
-
|
|
177
|
-
mainWindow.navigate(url);
|
|
84
|
+
mainWindow.navigate("http://localhost:" + std::to_string(windowConfig.devServerPort) + windowConfig.route);
|
|
178
85
|
#else
|
|
179
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
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 << "
|
|
96
|
+
std::cout << " " << f.value("name", "?")
|
|
97
|
+
<< " (" << f.value("size", 0) << " bytes)\n";
|
|
209
98
|
}
|
|
210
|
-
} catch (
|
|
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
|
-
|