electrobun 1.14.5-beta.0 → 1.15.0-beta.0
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/README.md +4 -1
- package/bun.lock +6 -0
- package/dist/api/browser/index.ts +3 -0
- package/dist/api/browser/wgputag.ts +48 -0
- package/dist/api/bun/ElectrobunConfig.ts +36 -0
- package/dist/api/bun/core/BrowserWindow.ts +27 -4
- package/dist/api/bun/core/GpuWindow.ts +248 -0
- package/dist/api/bun/core/WGPUView.ts +137 -0
- package/dist/api/bun/core/windowIds.ts +5 -0
- package/dist/api/bun/events/windowEvents.ts +3 -0
- package/dist/api/bun/index.ts +23 -1
- package/dist/api/bun/preload/.generated/compiled.ts +2 -2
- package/dist/api/bun/preload/index.ts +2 -0
- package/dist/api/bun/preload/overlaySync.ts +107 -0
- package/dist/api/bun/preload/webviewTag.ts +57 -82
- package/dist/api/bun/preload/wgpuTag.ts +246 -0
- package/dist/api/bun/proc/native.ts +532 -20
- package/dist/api/bun/webGPU.ts +371 -0
- package/dist/api/bun/webgpuAdapter.ts +3007 -0
- package/package.json +4 -2
- package/src/cli/index.ts +385 -2
package/README.md
CHANGED
|
@@ -29,6 +29,7 @@ Visit <a href="https://blackboard.sh/electrobun/">https://blackboard.sh/electrob
|
|
|
29
29
|
## Apps Built with Electrobun
|
|
30
30
|
- [Audio TTS](https://github.com/blackboardsh/audio-tts) - desktop text-to-speech app using Qwen3-TTS for voice design, cloning, and generation
|
|
31
31
|
- [Co(lab)](https://blackboard.sh/colab/) - a hybrid web browser + code editor for deep work
|
|
32
|
+
- [DOOM](https://github.com/blackboardsh/electrobun-doom) - DOOM implemented in 2 ways: bun -> (c doom -> bundled wgpu) and (full ts port bun -> bundled wgpu)
|
|
32
33
|
|
|
33
34
|
### Video Demos
|
|
34
35
|
|
|
@@ -36,9 +37,11 @@ Visit <a href="https://blackboard.sh/electrobun/">https://blackboard.sh/electrob
|
|
|
36
37
|
|
|
37
38
|
[](https://www.youtube.com/watch?v=WWTCqGmE86w)
|
|
38
39
|
|
|
40
|
+
[](https://x.com/YoavCodes/status/2028499038148903239?s=20)
|
|
41
|
+
|
|
39
42
|
## Star History
|
|
40
43
|
|
|
41
|
-
[](https://www.star-history.com/#blackboardsh/electrobun&type=date&legend=top-left)
|
|
42
45
|
|
|
43
46
|
## Contributing
|
|
44
47
|
Ways to get involved:
|
package/bun.lock
CHANGED
|
@@ -5,10 +5,12 @@
|
|
|
5
5
|
"": {
|
|
6
6
|
"name": "electrobun",
|
|
7
7
|
"dependencies": {
|
|
8
|
+
"@babylonjs/core": "^7.45.0",
|
|
8
9
|
"@types/bun": "^1.3.8",
|
|
9
10
|
"archiver": "^7.0.1",
|
|
10
11
|
"png-to-ico": "^2.1.8",
|
|
11
12
|
"rcedit": "^4.0.1",
|
|
13
|
+
"three": "^0.165.0",
|
|
12
14
|
},
|
|
13
15
|
"devDependencies": {
|
|
14
16
|
"@types/archiver": "^6.0.3",
|
|
@@ -17,6 +19,8 @@
|
|
|
17
19
|
},
|
|
18
20
|
},
|
|
19
21
|
"packages": {
|
|
22
|
+
"@babylonjs/core": ["@babylonjs/core@7.54.3", "", {}, "sha512-P5ncXVd8GEUJLhwloP9V0oVwQYIrvZztguVeLlvd5Rx+9aQnenKjpV8auJ6SRsUlAmNZU4pFTKzwF6o2EUfhAw=="],
|
|
23
|
+
|
|
20
24
|
"@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="],
|
|
21
25
|
|
|
22
26
|
"@malept/cross-spawn-promise": ["@malept/cross-spawn-promise@1.1.1", "", { "dependencies": { "cross-spawn": "^7.0.1" } }, "sha512-RTBGWL5FWQcg9orDOCcp4LvItNzUPcyEU9bwaeJX0rJ1IQxzucC48Y0/sQLp/g6t99IQgAlGIaesJS+gTn7tVQ=="],
|
|
@@ -169,6 +173,8 @@
|
|
|
169
173
|
|
|
170
174
|
"text-decoder": ["text-decoder@1.2.3", "", { "dependencies": { "b4a": "^1.6.4" } }, "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA=="],
|
|
171
175
|
|
|
176
|
+
"three": ["three@0.165.0", "", {}, "sha512-cc96IlVYGydeceu0e5xq70H8/yoVT/tXBxV/W8A/U6uOq7DXc4/s1Mkmnu6SqoYGhSRWWYFOhVwvq6V0VtbplA=="],
|
|
177
|
+
|
|
172
178
|
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
|
173
179
|
|
|
174
180
|
"undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
type WebviewTagElement,
|
|
12
12
|
type WebviewEventTypes,
|
|
13
13
|
} from "./webviewtag";
|
|
14
|
+
import { type WgpuTagElement, type WgpuEventTypes } from "./wgputag";
|
|
14
15
|
import "./global.d.ts";
|
|
15
16
|
|
|
16
17
|
const WEBVIEW_ID = window.__electrobunWebviewId;
|
|
@@ -175,6 +176,8 @@ export {
|
|
|
175
176
|
Electroview,
|
|
176
177
|
type WebviewTagElement,
|
|
177
178
|
type WebviewEventTypes,
|
|
179
|
+
type WgpuTagElement,
|
|
180
|
+
type WgpuEventTypes,
|
|
178
181
|
};
|
|
179
182
|
|
|
180
183
|
const Electrobun = {
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import "./global.d.ts";
|
|
2
|
+
|
|
3
|
+
type WgpuEventTypes = "ready";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Interface representing an <electrobun-wgpu> custom element.
|
|
7
|
+
* Use this to properly type wgpu elements obtained via querySelector.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* const wgpu = document.querySelector('electrobun-wgpu') as WgpuTagElement;
|
|
11
|
+
* wgpu.toggleTransparent(true);
|
|
12
|
+
*/
|
|
13
|
+
interface WgpuTagElement extends HTMLElement {
|
|
14
|
+
// Properties
|
|
15
|
+
wgpuViewId?: number;
|
|
16
|
+
transparent: boolean;
|
|
17
|
+
passthroughEnabled: boolean;
|
|
18
|
+
hidden: boolean;
|
|
19
|
+
|
|
20
|
+
// Visibility and interaction
|
|
21
|
+
toggleTransparent(transparent?: boolean): void;
|
|
22
|
+
togglePassthrough(enablePassthrough?: boolean): void;
|
|
23
|
+
toggleHidden(hidden?: boolean): void;
|
|
24
|
+
|
|
25
|
+
// Dimension sync
|
|
26
|
+
syncDimensions(force?: boolean): void;
|
|
27
|
+
|
|
28
|
+
// Debug helper
|
|
29
|
+
runTest(): void;
|
|
30
|
+
|
|
31
|
+
// Mask management
|
|
32
|
+
addMaskSelector(selector: string): void;
|
|
33
|
+
removeMaskSelector(selector: string): void;
|
|
34
|
+
|
|
35
|
+
// Events - listener receives a CustomEvent with detail property
|
|
36
|
+
on(event: WgpuEventTypes, listener: (event: CustomEvent) => void): void;
|
|
37
|
+
off(event: WgpuEventTypes, listener: (event: CustomEvent) => void): void;
|
|
38
|
+
emit(event: WgpuEventTypes, detail: unknown): void;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Augment global types so querySelector('electrobun-wgpu') returns WgpuTagElement
|
|
42
|
+
declare global {
|
|
43
|
+
interface HTMLElementTagNameMap {
|
|
44
|
+
"electrobun-wgpu": WgpuTagElement;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export { type WgpuTagElement, type WgpuEventTypes };
|
|
@@ -139,6 +139,16 @@ export interface ElectrobunConfig {
|
|
|
139
139
|
*/
|
|
140
140
|
cefVersion?: string;
|
|
141
141
|
|
|
142
|
+
/**
|
|
143
|
+
* Override the Dawn (WebGPU) version.
|
|
144
|
+
* Format: semver string (e.g., "0.2.3") or tag (e.g., "v0.2.3-beta.0")
|
|
145
|
+
*
|
|
146
|
+
* This downloads the specified electrobun-dawn release and uses it
|
|
147
|
+
* instead of the latest release.
|
|
148
|
+
* @default Uses the latest electrobun-dawn release
|
|
149
|
+
*/
|
|
150
|
+
wgpuVersion?: string;
|
|
151
|
+
|
|
142
152
|
/**
|
|
143
153
|
* Override the Bun runtime version.
|
|
144
154
|
* Format: semver string (e.g., "1.4.2")
|
|
@@ -149,6 +159,14 @@ export interface ElectrobunConfig {
|
|
|
149
159
|
*/
|
|
150
160
|
bunVersion?: string;
|
|
151
161
|
|
|
162
|
+
/**
|
|
163
|
+
* Locales to include in the ICU data file (Linux/Windows only).
|
|
164
|
+
* Set to '*' to include all locales, or specify a subset like ['en', 'de']
|
|
165
|
+
* to reduce app size. Has no effect on macOS (uses system ICU).
|
|
166
|
+
* @default '*'
|
|
167
|
+
*/
|
|
168
|
+
locales?: string[] | "*";
|
|
169
|
+
|
|
152
170
|
/**
|
|
153
171
|
* macOS-specific build configuration
|
|
154
172
|
*/
|
|
@@ -171,6 +189,12 @@ export interface ElectrobunConfig {
|
|
|
171
189
|
*/
|
|
172
190
|
bundleCEF?: boolean;
|
|
173
191
|
|
|
192
|
+
/**
|
|
193
|
+
* Bundle Dawn (WebGPU) for GPU-native rendering
|
|
194
|
+
* @default false
|
|
195
|
+
*/
|
|
196
|
+
bundleWGPU?: boolean;
|
|
197
|
+
|
|
174
198
|
/**
|
|
175
199
|
* Default renderer for webviews when not explicitly specified
|
|
176
200
|
* @default 'native'
|
|
@@ -214,6 +238,12 @@ export interface ElectrobunConfig {
|
|
|
214
238
|
*/
|
|
215
239
|
bundleCEF?: boolean;
|
|
216
240
|
|
|
241
|
+
/**
|
|
242
|
+
* Bundle Dawn (WebGPU) for GPU-native rendering
|
|
243
|
+
* @default false
|
|
244
|
+
*/
|
|
245
|
+
bundleWGPU?: boolean;
|
|
246
|
+
|
|
217
247
|
/**
|
|
218
248
|
* Default renderer for webviews when not explicitly specified
|
|
219
249
|
* @default 'native'
|
|
@@ -255,6 +285,12 @@ export interface ElectrobunConfig {
|
|
|
255
285
|
*/
|
|
256
286
|
bundleCEF?: boolean;
|
|
257
287
|
|
|
288
|
+
/**
|
|
289
|
+
* Bundle Dawn (WebGPU) for GPU-native rendering
|
|
290
|
+
* @default false
|
|
291
|
+
*/
|
|
292
|
+
bundleWGPU?: boolean;
|
|
293
|
+
|
|
258
294
|
/**
|
|
259
295
|
* Default renderer for webviews when not explicitly specified
|
|
260
296
|
* @default 'native'
|
|
@@ -5,11 +5,12 @@ import { type Pointer } from "bun:ffi";
|
|
|
5
5
|
import { BuildConfig } from "./BuildConfig";
|
|
6
6
|
import { quit } from "./Utils";
|
|
7
7
|
import { type RPCWithTransport } from "../../shared/rpc.js";
|
|
8
|
+
import { getNextWindowId } from "./windowIds";
|
|
9
|
+
import { GpuWindowMap } from "./GpuWindow";
|
|
10
|
+
import { WGPUView } from "./WGPUView";
|
|
8
11
|
|
|
9
12
|
const buildConfig = await BuildConfig.get();
|
|
10
13
|
|
|
11
|
-
let nextWindowId = 1;
|
|
12
|
-
|
|
13
14
|
export type WindowOptionsType<T = undefined> = {
|
|
14
15
|
title: string;
|
|
15
16
|
frame: {
|
|
@@ -72,16 +73,38 @@ electrobunEventEmitter.on("close", (event: { data: { id: number } }) => {
|
|
|
72
73
|
}
|
|
73
74
|
}
|
|
74
75
|
|
|
76
|
+
// Clean up all WGPU views associated with this window
|
|
77
|
+
const wgpuViews = WGPUView.getAll().filter(v => v.windowId === windowId);
|
|
78
|
+
for (const view of wgpuViews) {
|
|
79
|
+
try {
|
|
80
|
+
// If ptr is null, the view was already cleaned up by the renderer or native cleanup
|
|
81
|
+
if (view.ptr === null) {
|
|
82
|
+
// Already cleaned up, skip
|
|
83
|
+
} else {
|
|
84
|
+
// Programmatic close path - remove the view
|
|
85
|
+
view.remove();
|
|
86
|
+
}
|
|
87
|
+
} catch (e) {
|
|
88
|
+
console.error(`Error cleaning up WGPU view ${view.id}:`, e);
|
|
89
|
+
// If remove() failed, at least mark it as cleaned up
|
|
90
|
+
view.ptr = null as any;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
75
94
|
const exitOnLastWindowClosed =
|
|
76
95
|
buildConfig.runtime?.exitOnLastWindowClosed ?? true;
|
|
77
96
|
|
|
78
|
-
if (
|
|
97
|
+
if (
|
|
98
|
+
exitOnLastWindowClosed &&
|
|
99
|
+
Object.keys(BrowserWindowMap).length === 0 &&
|
|
100
|
+
Object.keys(GpuWindowMap).length === 0
|
|
101
|
+
) {
|
|
79
102
|
quit();
|
|
80
103
|
}
|
|
81
104
|
});
|
|
82
105
|
|
|
83
106
|
export class BrowserWindow<T extends RPCWithTransport = RPCWithTransport> {
|
|
84
|
-
id: number =
|
|
107
|
+
id: number = getNextWindowId();
|
|
85
108
|
ptr!: Pointer;
|
|
86
109
|
title: string = "Electrobun";
|
|
87
110
|
state: "creating" | "created" = "creating";
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import { ffi } from "../proc/native";
|
|
2
|
+
import electrobunEventEmitter from "../events/eventEmitter";
|
|
3
|
+
import { type Pointer } from "bun:ffi";
|
|
4
|
+
import { WGPUView } from "./WGPUView";
|
|
5
|
+
import { getNextWindowId } from "./windowIds";
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
export type GpuWindowOptionsType = {
|
|
9
|
+
title: string;
|
|
10
|
+
frame: {
|
|
11
|
+
x: number;
|
|
12
|
+
y: number;
|
|
13
|
+
width: number;
|
|
14
|
+
height: number;
|
|
15
|
+
};
|
|
16
|
+
styleMask?: {};
|
|
17
|
+
titleBarStyle: "hidden" | "hiddenInset" | "default";
|
|
18
|
+
transparent: boolean;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const defaultOptions: GpuWindowOptionsType = {
|
|
22
|
+
title: "Electrobun",
|
|
23
|
+
frame: {
|
|
24
|
+
x: 0,
|
|
25
|
+
y: 0,
|
|
26
|
+
width: 800,
|
|
27
|
+
height: 600,
|
|
28
|
+
},
|
|
29
|
+
titleBarStyle: "default",
|
|
30
|
+
transparent: false,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export const GpuWindowMap: {
|
|
34
|
+
[id: number]: GpuWindow;
|
|
35
|
+
} = {};
|
|
36
|
+
|
|
37
|
+
// Clean up the window map when a window closes and optionally quit the app
|
|
38
|
+
electrobunEventEmitter.on("close", (event: { data: { id: number } }) => {
|
|
39
|
+
const windowId = event.data.id;
|
|
40
|
+
delete GpuWindowMap[windowId];
|
|
41
|
+
|
|
42
|
+
// Clean up all WGPU views associated with this window
|
|
43
|
+
for (const view of WGPUView.getAll()) {
|
|
44
|
+
if (view.windowId === windowId) {
|
|
45
|
+
view.remove();
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
export class GpuWindow {
|
|
52
|
+
id: number = getNextWindowId();
|
|
53
|
+
ptr!: Pointer;
|
|
54
|
+
title: string = "Electrobun";
|
|
55
|
+
state: "creating" | "created" = "creating";
|
|
56
|
+
transparent: boolean = false;
|
|
57
|
+
frame: {
|
|
58
|
+
x: number;
|
|
59
|
+
y: number;
|
|
60
|
+
width: number;
|
|
61
|
+
height: number;
|
|
62
|
+
} = {
|
|
63
|
+
x: 0,
|
|
64
|
+
y: 0,
|
|
65
|
+
width: 800,
|
|
66
|
+
height: 600,
|
|
67
|
+
};
|
|
68
|
+
wgpuViewId!: number;
|
|
69
|
+
|
|
70
|
+
constructor(options: Partial<GpuWindowOptionsType> = defaultOptions) {
|
|
71
|
+
this.title = options.title || "New Window";
|
|
72
|
+
this.frame = options.frame
|
|
73
|
+
? { ...defaultOptions.frame, ...options.frame }
|
|
74
|
+
: { ...defaultOptions.frame };
|
|
75
|
+
this.transparent = options.transparent ?? false;
|
|
76
|
+
|
|
77
|
+
this.init(options);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
init({
|
|
81
|
+
styleMask,
|
|
82
|
+
titleBarStyle,
|
|
83
|
+
transparent,
|
|
84
|
+
}: Partial<GpuWindowOptionsType>) {
|
|
85
|
+
this.ptr = ffi.request.createWindow({
|
|
86
|
+
id: this.id,
|
|
87
|
+
title: this.title,
|
|
88
|
+
url: "",
|
|
89
|
+
frame: {
|
|
90
|
+
width: this.frame.width,
|
|
91
|
+
height: this.frame.height,
|
|
92
|
+
x: this.frame.x,
|
|
93
|
+
y: this.frame.y,
|
|
94
|
+
},
|
|
95
|
+
styleMask: {
|
|
96
|
+
Borderless: false,
|
|
97
|
+
Titled: true,
|
|
98
|
+
Closable: true,
|
|
99
|
+
Miniaturizable: true,
|
|
100
|
+
Resizable: true,
|
|
101
|
+
UnifiedTitleAndToolbar: false,
|
|
102
|
+
FullScreen: false,
|
|
103
|
+
FullSizeContentView: false,
|
|
104
|
+
UtilityWindow: false,
|
|
105
|
+
DocModalWindow: false,
|
|
106
|
+
NonactivatingPanel: false,
|
|
107
|
+
HUDWindow: false,
|
|
108
|
+
...(styleMask || {}),
|
|
109
|
+
// hiddenInset: transparent titlebar with inset native controls
|
|
110
|
+
...(titleBarStyle === "hiddenInset"
|
|
111
|
+
? {
|
|
112
|
+
Titled: true,
|
|
113
|
+
FullSizeContentView: true,
|
|
114
|
+
}
|
|
115
|
+
: {}),
|
|
116
|
+
// hidden: no titlebar, no native controls (for fully custom chrome)
|
|
117
|
+
...(titleBarStyle === "hidden"
|
|
118
|
+
? {
|
|
119
|
+
Titled: false,
|
|
120
|
+
FullSizeContentView: true,
|
|
121
|
+
}
|
|
122
|
+
: {}),
|
|
123
|
+
},
|
|
124
|
+
titleBarStyle: titleBarStyle || "default",
|
|
125
|
+
transparent: transparent ?? false,
|
|
126
|
+
}) as Pointer;
|
|
127
|
+
|
|
128
|
+
GpuWindowMap[this.id] = this;
|
|
129
|
+
|
|
130
|
+
const wgpuView = new WGPUView({
|
|
131
|
+
frame: {
|
|
132
|
+
x: 0,
|
|
133
|
+
y: 0,
|
|
134
|
+
width: this.frame.width,
|
|
135
|
+
height: this.frame.height,
|
|
136
|
+
},
|
|
137
|
+
windowId: this.id,
|
|
138
|
+
autoResize: true,
|
|
139
|
+
startTransparent: false,
|
|
140
|
+
startPassthrough: false,
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
this.wgpuViewId = wgpuView.id;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
get wgpuView() {
|
|
147
|
+
return WGPUView.getById(this.wgpuViewId) as WGPUView;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
static getById(id: number) {
|
|
151
|
+
return GpuWindowMap[id];
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
setTitle(title: string) {
|
|
155
|
+
this.title = title;
|
|
156
|
+
return ffi.request.setTitle({ winId: this.id, title });
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
close() {
|
|
160
|
+
return ffi.request.closeWindow({ winId: this.id });
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
focus() {
|
|
164
|
+
return ffi.request.focusWindow({ winId: this.id });
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
show() {
|
|
168
|
+
return ffi.request.focusWindow({ winId: this.id });
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
minimize() {
|
|
172
|
+
return ffi.request.minimizeWindow({ winId: this.id });
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
unminimize() {
|
|
176
|
+
return ffi.request.restoreWindow({ winId: this.id });
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
isMinimized(): boolean {
|
|
180
|
+
return ffi.request.isWindowMinimized({ winId: this.id });
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
maximize() {
|
|
184
|
+
return ffi.request.maximizeWindow({ winId: this.id });
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
unmaximize() {
|
|
188
|
+
return ffi.request.unmaximizeWindow({ winId: this.id });
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
isMaximized(): boolean {
|
|
192
|
+
return ffi.request.isWindowMaximized({ winId: this.id });
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
setFullScreen(fullScreen: boolean) {
|
|
196
|
+
return ffi.request.setWindowFullScreen({ winId: this.id, fullScreen });
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
isFullScreen(): boolean {
|
|
200
|
+
return ffi.request.isWindowFullScreen({ winId: this.id });
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
setAlwaysOnTop(alwaysOnTop: boolean) {
|
|
204
|
+
return ffi.request.setWindowAlwaysOnTop({ winId: this.id, alwaysOnTop });
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
isAlwaysOnTop(): boolean {
|
|
208
|
+
return ffi.request.isWindowAlwaysOnTop({ winId: this.id });
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
setPosition(x: number, y: number) {
|
|
212
|
+
this.frame.x = x;
|
|
213
|
+
this.frame.y = y;
|
|
214
|
+
return ffi.request.setWindowPosition({ winId: this.id, x, y });
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
setSize(width: number, height: number) {
|
|
218
|
+
this.frame.width = width;
|
|
219
|
+
this.frame.height = height;
|
|
220
|
+
return ffi.request.setWindowSize({ winId: this.id, width, height });
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
setFrame(x: number, y: number, width: number, height: number) {
|
|
224
|
+
this.frame = { x, y, width, height };
|
|
225
|
+
return ffi.request.setWindowFrame({ winId: this.id, x, y, width, height });
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
getFrame(): { x: number; y: number; width: number; height: number } {
|
|
229
|
+
const frame = ffi.request.getWindowFrame({ winId: this.id });
|
|
230
|
+
this.frame = frame;
|
|
231
|
+
return frame;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
getPosition(): { x: number; y: number } {
|
|
235
|
+
const frame = this.getFrame();
|
|
236
|
+
return { x: frame.x, y: frame.y };
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
getSize(): { width: number; height: number } {
|
|
240
|
+
const frame = this.getFrame();
|
|
241
|
+
return { width: frame.width, height: frame.height };
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
on(name: string, handler: (event: unknown) => void) {
|
|
245
|
+
const specificName = `${name}-${this.id}`;
|
|
246
|
+
electrobunEventEmitter.on(specificName, handler);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { ffi } from "../proc/native";
|
|
2
|
+
import electrobunEventEmitter from "../events/eventEmitter";
|
|
3
|
+
import { type Pointer } from "bun:ffi";
|
|
4
|
+
|
|
5
|
+
const WGPUViewMap: {
|
|
6
|
+
[id: number]: WGPUView;
|
|
7
|
+
} = {};
|
|
8
|
+
|
|
9
|
+
let nextWGPUViewId = 1;
|
|
10
|
+
|
|
11
|
+
export type WGPUViewOptions = {
|
|
12
|
+
frame: {
|
|
13
|
+
x: number;
|
|
14
|
+
y: number;
|
|
15
|
+
width: number;
|
|
16
|
+
height: number;
|
|
17
|
+
};
|
|
18
|
+
autoResize: boolean;
|
|
19
|
+
windowId: number;
|
|
20
|
+
startTransparent: boolean;
|
|
21
|
+
startPassthrough: boolean;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const defaultOptions: Partial<WGPUViewOptions> = {
|
|
25
|
+
frame: {
|
|
26
|
+
x: 0,
|
|
27
|
+
y: 0,
|
|
28
|
+
width: 800,
|
|
29
|
+
height: 600,
|
|
30
|
+
},
|
|
31
|
+
autoResize: true,
|
|
32
|
+
startTransparent: false,
|
|
33
|
+
startPassthrough: false,
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export class WGPUView {
|
|
37
|
+
id: number = nextWGPUViewId++;
|
|
38
|
+
ptr!: Pointer;
|
|
39
|
+
windowId!: number;
|
|
40
|
+
autoResize: boolean = true;
|
|
41
|
+
frame: {
|
|
42
|
+
x: number;
|
|
43
|
+
y: number;
|
|
44
|
+
width: number;
|
|
45
|
+
height: number;
|
|
46
|
+
} = {
|
|
47
|
+
x: 0,
|
|
48
|
+
y: 0,
|
|
49
|
+
width: 800,
|
|
50
|
+
height: 600,
|
|
51
|
+
};
|
|
52
|
+
startTransparent: boolean = false;
|
|
53
|
+
startPassthrough: boolean = false;
|
|
54
|
+
|
|
55
|
+
constructor(options: Partial<WGPUViewOptions> = defaultOptions) {
|
|
56
|
+
this.frame = {
|
|
57
|
+
x: options.frame?.x ?? defaultOptions.frame!.x,
|
|
58
|
+
y: options.frame?.y ?? defaultOptions.frame!.y,
|
|
59
|
+
width: options.frame?.width ?? defaultOptions.frame!.width,
|
|
60
|
+
height: options.frame?.height ?? defaultOptions.frame!.height,
|
|
61
|
+
};
|
|
62
|
+
this.windowId = options.windowId ?? 0;
|
|
63
|
+
this.autoResize = options.autoResize === false ? false : true;
|
|
64
|
+
this.startTransparent = options.startTransparent ?? false;
|
|
65
|
+
this.startPassthrough = options.startPassthrough ?? false;
|
|
66
|
+
|
|
67
|
+
WGPUViewMap[this.id] = this;
|
|
68
|
+
this.ptr = this.init() as Pointer;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
init() {
|
|
72
|
+
return ffi.request.createWGPUView({
|
|
73
|
+
id: this.id,
|
|
74
|
+
windowId: this.windowId,
|
|
75
|
+
frame: {
|
|
76
|
+
width: this.frame.width,
|
|
77
|
+
height: this.frame.height,
|
|
78
|
+
x: this.frame.x,
|
|
79
|
+
y: this.frame.y,
|
|
80
|
+
},
|
|
81
|
+
autoResize: this.autoResize,
|
|
82
|
+
startTransparent: this.startTransparent,
|
|
83
|
+
startPassthrough: this.startPassthrough,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
setFrame(x: number, y: number, width: number, height: number) {
|
|
88
|
+
this.frame = { x, y, width, height };
|
|
89
|
+
ffi.request.wgpuViewSetFrame({ id: this.id, x, y, width, height });
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
setTransparent(transparent: boolean) {
|
|
93
|
+
ffi.request.wgpuViewSetTransparent({ id: this.id, transparent });
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
setPassthrough(passthrough: boolean) {
|
|
97
|
+
ffi.request.wgpuViewSetPassthrough({ id: this.id, passthrough });
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
setHidden(hidden: boolean) {
|
|
101
|
+
ffi.request.wgpuViewSetHidden({ id: this.id, hidden });
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
on(name: "frame-updated", handler: (event: unknown) => void) {
|
|
105
|
+
const specificName = `${name}-${this.id}`;
|
|
106
|
+
electrobunEventEmitter.on(specificName, handler);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
remove() {
|
|
110
|
+
// Check if already removed
|
|
111
|
+
if (this.ptr === null) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
try {
|
|
116
|
+
ffi.request.wgpuViewRemove({ id: this.id });
|
|
117
|
+
} catch (e) {
|
|
118
|
+
console.error(`Error removing WGPU view ${this.id}:`, e);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
delete WGPUViewMap[this.id];
|
|
122
|
+
// Clear the pointer to prevent any accidental access
|
|
123
|
+
this.ptr = null as any;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
getNativeHandle() {
|
|
127
|
+
return ffi.request.wgpuViewGetNativeHandle({ id: this.id });
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
static getById(id: number) {
|
|
131
|
+
return WGPUViewMap[id];
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
static getAll() {
|
|
135
|
+
return Object.values(WGPUViewMap);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
@@ -9,6 +9,7 @@ type ResizeData = {
|
|
|
9
9
|
height: number;
|
|
10
10
|
};
|
|
11
11
|
type MoveData = { id: number; x: number; y: number };
|
|
12
|
+
type KeyData = { id: number; keyCode: number; modifiers: number; isRepeat: boolean };
|
|
12
13
|
|
|
13
14
|
export default {
|
|
14
15
|
close: (data: IdData) => new ElectrobunEvent<IdData, {}>("close", data),
|
|
@@ -16,4 +17,6 @@ export default {
|
|
|
16
17
|
new ElectrobunEvent<ResizeData, {}>("resize", data),
|
|
17
18
|
move: (data: MoveData) => new ElectrobunEvent<MoveData, {}>("move", data),
|
|
18
19
|
focus: (data: IdData) => new ElectrobunEvent<IdData, {}>("focus", data),
|
|
20
|
+
keyDown: (data: KeyData) => new ElectrobunEvent<KeyData, {}>("keyDown", data),
|
|
21
|
+
keyUp: (data: KeyData) => new ElectrobunEvent<KeyData, {}>("keyUp", data),
|
|
19
22
|
};
|