electrobun 1.14.4 → 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 +120 -0
- 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 +6 -4
- package/src/cli/index.ts +410 -2
package/README.md
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<a href="https://electrobun.dev"><img src="https://github.com/blackboardsh/electrobun/assets/75102186/8799b522-0507-45e9-86e3-c3cfded1aa7c" alt="Logo" height=170></a>
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<h1 align="center">Electrobun</h1>
|
|
6
|
+
|
|
7
|
+
<div align="center">
|
|
8
|
+
Get started with a template <br />
|
|
9
|
+
<code><strong>npx electrobun init</strong></code>
|
|
10
|
+
</div>
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
## What is Electrobun?
|
|
15
|
+
|
|
16
|
+
Electrobun aims to be a complete **solution-in-a-box** for building, updating, and shipping ultra fast, tiny, and cross-platform desktop applications written in Typescript.
|
|
17
|
+
Under the hood it uses <a href="https://bun.sh">bun</a> to execute the main process and to bundle webview typescript, and has native bindings written in <a href="https://ziglang.org/">zig</a>.
|
|
18
|
+
|
|
19
|
+
Visit <a href="https://blackboard.sh/electrobun/">https://blackboard.sh/electrobun/</a> to see api documentation, guides, and more.
|
|
20
|
+
|
|
21
|
+
**Project Goals**
|
|
22
|
+
|
|
23
|
+
- Write typescript for the main process and webviews without having to think about it.
|
|
24
|
+
- Isolation between main and webview processes with fast, typed, easy to implement RPC between them.
|
|
25
|
+
- Small self-extracting app bundles ~12MB (when using system webview, most of this is the bun runtime)
|
|
26
|
+
- Even smaller app updates as small as 14KB (using bsdiff it only downloads tiny patches between versions)
|
|
27
|
+
- Provide everything you need in one tightly integrated workflow to start writing code in 5 minutes and distribute in 10.
|
|
28
|
+
|
|
29
|
+
## Apps Built with Electrobun
|
|
30
|
+
- [Audio TTS](https://github.com/blackboardsh/audio-tts) - desktop text-to-speech app using Qwen3-TTS for voice design, cloning, and generation
|
|
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)
|
|
33
|
+
|
|
34
|
+
### Video Demos
|
|
35
|
+
|
|
36
|
+
[](https://www.youtube.com/watch?v=Z4dNK1d6l6E)
|
|
37
|
+
|
|
38
|
+
[](https://www.youtube.com/watch?v=WWTCqGmE86w)
|
|
39
|
+
|
|
40
|
+
[](https://x.com/YoavCodes/status/2028499038148903239?s=20)
|
|
41
|
+
|
|
42
|
+
## Star History
|
|
43
|
+
|
|
44
|
+
[](https://www.star-history.com/#blackboardsh/electrobun&type=date&legend=top-left)
|
|
45
|
+
|
|
46
|
+
## Contributing
|
|
47
|
+
Ways to get involved:
|
|
48
|
+
|
|
49
|
+
- Follow us on X for updates <a href="https://twitter.com/BlackboardTech">@BlackboardTech</a> or <a href="https://bsky.app/profile/yoav.codes">@yoav.codes</a>
|
|
50
|
+
- Join the conversation on <a href="https://discord.gg/ueKE4tjaCE">Discord</a>
|
|
51
|
+
- Create and participate in Github issues and discussions
|
|
52
|
+
- Let me know what you're building with Electrobun
|
|
53
|
+
|
|
54
|
+
## Development Setup
|
|
55
|
+
Building apps with Electrobun is as easy as updating your package.json dependencies with `npm add electrobun` or try one of our templates via `npx electrobun init`.
|
|
56
|
+
|
|
57
|
+
**This section is for building Electrobun from source locally in order to contribute fixes to it.**
|
|
58
|
+
|
|
59
|
+
### Prerequisites
|
|
60
|
+
|
|
61
|
+
**macOS:**
|
|
62
|
+
- Xcode command line tools
|
|
63
|
+
- cmake (install via homebrew: `brew install cmake`)
|
|
64
|
+
|
|
65
|
+
**Windows:**
|
|
66
|
+
- Visual Studio Build Tools or Visual Studio with C++ development tools
|
|
67
|
+
- cmake
|
|
68
|
+
|
|
69
|
+
**Linux:**
|
|
70
|
+
- build-essential package
|
|
71
|
+
- cmake
|
|
72
|
+
- webkit2gtk and GTK development packages
|
|
73
|
+
|
|
74
|
+
On Ubuntu/Debian based distros: `sudo apt install build-essential cmake pkg-config libgtk-3-dev libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev`
|
|
75
|
+
|
|
76
|
+
### First-time Setup
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
git clone --recurse-submodules https://github.com/blackboardsh/electrobun.git
|
|
80
|
+
cd electrobun/package
|
|
81
|
+
bun install
|
|
82
|
+
bun dev:clean
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Development Workflow
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
# All commands are run from the /package directory
|
|
89
|
+
cd electrobun/package
|
|
90
|
+
|
|
91
|
+
# After making changes to source code
|
|
92
|
+
bun dev
|
|
93
|
+
|
|
94
|
+
# If you only changed kitchen sink code (not electrobun source)
|
|
95
|
+
bun dev:rerun
|
|
96
|
+
|
|
97
|
+
# If you need a completely fresh start
|
|
98
|
+
bun dev:clean
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Additional Commands
|
|
102
|
+
|
|
103
|
+
All commands are run from the `/package` directory:
|
|
104
|
+
|
|
105
|
+
- `bun dev:canary` - Build and run kitchen sink in canary mode
|
|
106
|
+
- `bun build:dev` - Build electrobun in development mode
|
|
107
|
+
- `bun build:release` - Build electrobun in release mode
|
|
108
|
+
|
|
109
|
+
### Debugging
|
|
110
|
+
|
|
111
|
+
**macOS:** Use `lldb <path-to-bundle>/Contents/MacOS/launcher` and then `run` to debug release builds
|
|
112
|
+
|
|
113
|
+
## Platform Support
|
|
114
|
+
|
|
115
|
+
| OS | Status |
|
|
116
|
+
|---|---|
|
|
117
|
+
| macOS 14+ | Official |
|
|
118
|
+
| Windows 11+ | Official |
|
|
119
|
+
| Ubuntu 22.04+ | Official |
|
|
120
|
+
| Other Linux distros (gtk3, webkit2gtk-4.1) | Community |
|
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
|
+
}
|