gpui-react 0.1.7
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 +116 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1 -0
- package/dist/reconciler/ctx.d.ts +7 -0
- package/dist/reconciler/ctx.d.ts.map +1 -0
- package/dist/reconciler/ctx.js +7 -0
- package/dist/reconciler/element-store.d.ts +22 -0
- package/dist/reconciler/element-store.d.ts.map +1 -0
- package/dist/reconciler/element-store.js +61 -0
- package/dist/reconciler/events.d.ts +13 -0
- package/dist/reconciler/events.d.ts.map +1 -0
- package/dist/reconciler/events.js +4 -0
- package/dist/reconciler/gpui-binding.d.ts +7 -0
- package/dist/reconciler/gpui-binding.d.ts.map +1 -0
- package/dist/reconciler/gpui-binding.js +179 -0
- package/dist/reconciler/host-config.d.ts +33 -0
- package/dist/reconciler/host-config.d.ts.map +1 -0
- package/dist/reconciler/host-config.js +270 -0
- package/dist/reconciler/logging.d.ts +10 -0
- package/dist/reconciler/logging.d.ts.map +1 -0
- package/dist/reconciler/logging.js +68 -0
- package/dist/reconciler/reconciler.d.ts +6 -0
- package/dist/reconciler/reconciler.d.ts.map +1 -0
- package/dist/reconciler/reconciler.js +9 -0
- package/dist/reconciler/renderer.d.ts +12 -0
- package/dist/reconciler/renderer.d.ts.map +1 -0
- package/dist/reconciler/renderer.js +21 -0
- package/dist/reconciler/styles.d.ts +66 -0
- package/dist/reconciler/styles.d.ts.map +1 -0
- package/dist/reconciler/styles.js +214 -0
- package/package.json +44 -0
- package/src/native/linux-x64/libgpui_renderer.so +0 -0
package/README.md
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# React-GPUI Renderer
|
|
2
|
+
|
|
3
|
+
A React renderer for GPUI (Zed's GPU-accelerated UI framework) using Bun native FFI.
|
|
4
|
+
|
|
5
|
+
## Architecture
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
React Component (JSX)
|
|
9
|
+
↓
|
|
10
|
+
React Reconciler (src/reconciler/host-config.ts)
|
|
11
|
+
↓
|
|
12
|
+
Element Store (src/reconciler/element-store.ts)
|
|
13
|
+
↓
|
|
14
|
+
Bun FFI (src/reconciler/gpui-binding.ts)
|
|
15
|
+
↓
|
|
16
|
+
Rust FFI (rust/src/lib.rs)
|
|
17
|
+
↓
|
|
18
|
+
GPUI Runtime (rust/src/renderer.rs)
|
|
19
|
+
↓
|
|
20
|
+
GPU Rendering (GPUI)
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Project Structure
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
gpui-react/
|
|
27
|
+
├── rust/ # Rust FFI library (cdylib)
|
|
28
|
+
│ └── src/ # FFI exports, GPUI rendering, command bus
|
|
29
|
+
├── src/reconciler/ # React reconciler, FFI bindings, element store
|
|
30
|
+
└── demo/ # Demo applications (5 entry points)
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Quick Start
|
|
34
|
+
|
|
35
|
+
### 1. Build Rust Library
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
cd rust && cargo build --release
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### 2. Run Demos
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
bun run demo # Basic elements
|
|
45
|
+
bun run event-demo # Event handling
|
|
46
|
+
bun run styled-demo # Styled components
|
|
47
|
+
bun run flex-demo # Flexbox layout
|
|
48
|
+
bun run elements-demo # Span, image elements
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## API Reference
|
|
52
|
+
|
|
53
|
+
### createRoot(container)
|
|
54
|
+
|
|
55
|
+
Create a GPUI rendering root for a container element.
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
import { createRoot } from 'gpui-react';
|
|
59
|
+
|
|
60
|
+
const container = document.getElementById('root');
|
|
61
|
+
const root = createRoot(container);
|
|
62
|
+
root.render(<App />);
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Supported Elements
|
|
66
|
+
|
|
67
|
+
| Element | GPUI Mapping | Notes |
|
|
68
|
+
|---------|--------------|-------|
|
|
69
|
+
| `div` | `div()` | Block container |
|
|
70
|
+
| `span` | `span()` | Inline container, contains text children |
|
|
71
|
+
| `text` | Text nodes | Always child of span |
|
|
72
|
+
|
|
73
|
+
### Event Handlers
|
|
74
|
+
|
|
75
|
+
```jsx
|
|
76
|
+
<div onClick={handleClick} onHover={handleHover}>
|
|
77
|
+
<span>Click me</span>
|
|
78
|
+
</div>
|
|
79
|
+
```
|
|
80
|
+
的恩托斯
|
|
81
|
+
**Supported:** `onClick`, `onHover`, `onMouseEnter`, `onMouseLeave`
|
|
82
|
+
|
|
83
|
+
### Styles
|
|
84
|
+
|
|
85
|
+
```jsx
|
|
86
|
+
<div style={{
|
|
87
|
+
color: '#fff',
|
|
88
|
+
backgroundColor: '#333',
|
|
89
|
+
fontSize: 16,
|
|
90
|
+
width: 200,
|
|
91
|
+
height: 100,
|
|
92
|
+
margin: 10,
|
|
93
|
+
padding: 20,
|
|
94
|
+
borderRadius: 8,
|
|
95
|
+
display: 'flex',
|
|
96
|
+
flexDirection: 'row',
|
|
97
|
+
justifyContent: 'center',
|
|
98
|
+
alignItems: 'center',
|
|
99
|
+
gap: 8
|
|
100
|
+
}}>
|
|
101
|
+
Content
|
|
102
|
+
</div>
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Documentation
|
|
106
|
+
|
|
107
|
+
- **[AGENTS.md](./AGENTS.md)** - Root knowledge base
|
|
108
|
+
- **[src/reconciler/AGENTS.md](./src/reconciler/AGENTS.md)** - React reconciler layer
|
|
109
|
+
- **[rust/src/AGENTS.md](./rust/src/AGENTS.md)** - Rust FFI layer
|
|
110
|
+
- **[ROADMAP.md](./ROADMAP.md)** - Development roadmap
|
|
111
|
+
|
|
112
|
+
## Requirements
|
|
113
|
+
|
|
114
|
+
- [Bun](https://bun.sh) ≥ 1.0
|
|
115
|
+
- [Rust](https://rust-lang.org) ≥ 1.70
|
|
116
|
+
- GPUI dependencies (downloaded automatically on first cargo build)
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,uBAAuB,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./reconciler/renderer";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ctx.d.ts","sourceRoot":"","sources":["../../src/reconciler/ctx.ts"],"names":[],"mappings":"AAEA,UAAU,UAAU;IAChB,QAAQ,EAAE,MAAM,CAAA;CACnB;AAED,eAAO,MAAM,UAAU,qCAErB,CAAA;AAEF,eAAO,MAAM,aAAa,kBAEzB,CAAA"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export interface ElementData {
|
|
2
|
+
globalId: number;
|
|
3
|
+
type: string;
|
|
4
|
+
text?: string;
|
|
5
|
+
children: number[];
|
|
6
|
+
style?: Record<string, any>;
|
|
7
|
+
eventHandlers?: Record<string, number>;
|
|
8
|
+
}
|
|
9
|
+
export declare class ElementStore {
|
|
10
|
+
private store;
|
|
11
|
+
private nextId;
|
|
12
|
+
private rootId;
|
|
13
|
+
reset(): void;
|
|
14
|
+
createElement(type: string, text?: string, style?: Record<string, any>): number;
|
|
15
|
+
getStore(element: ElementData): ElementStore | undefined;
|
|
16
|
+
appendChild(parentId: number, childId: number): void;
|
|
17
|
+
removeChild(parentId: number, childId: number): void;
|
|
18
|
+
setContainerChild(childId: number): void;
|
|
19
|
+
getElement(globalId: number): ElementData | undefined;
|
|
20
|
+
getRoot(): ElementData;
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=element-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"element-store.d.ts","sourceRoot":"","sources":["../../src/reconciler/element-store.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC5B,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACxC;AAID,qBAAa,YAAY;IACvB,OAAO,CAAC,KAAK,CAAkC;IAC/C,OAAO,CAAC,MAAM,CAAK;IACnB,OAAO,CAAC,MAAM,CAAuB;IAErC,KAAK,IAAI,IAAI;IAMb,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM;IAe/E,QAAQ,CAAC,OAAO,EAAE,WAAW,GAAG,YAAY,GAAG,SAAS;IAIxD,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI;IAOpD,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI;IAUpD,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAOxC,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS;IAIrD,OAAO,IAAI,WAAW;CAMvB"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { trace } from "./logging";
|
|
2
|
+
const STORE_SYMBOL = Symbol('store');
|
|
3
|
+
export class ElementStore {
|
|
4
|
+
store = new Map();
|
|
5
|
+
nextId = 2;
|
|
6
|
+
rootId = null;
|
|
7
|
+
reset() {
|
|
8
|
+
this.store.clear();
|
|
9
|
+
this.nextId = 2;
|
|
10
|
+
this.rootId = null;
|
|
11
|
+
}
|
|
12
|
+
createElement(type, text, style) {
|
|
13
|
+
const globalId = this.nextId++;
|
|
14
|
+
const element = {
|
|
15
|
+
globalId,
|
|
16
|
+
type,
|
|
17
|
+
text,
|
|
18
|
+
style,
|
|
19
|
+
children: [],
|
|
20
|
+
};
|
|
21
|
+
Object.defineProperty(element, STORE_SYMBOL, { value: this, writable: false, enumerable: false });
|
|
22
|
+
this.store.set(globalId, element);
|
|
23
|
+
trace("createElement", { type, globalId, style });
|
|
24
|
+
return globalId;
|
|
25
|
+
}
|
|
26
|
+
getStore(element) {
|
|
27
|
+
return element[STORE_SYMBOL];
|
|
28
|
+
}
|
|
29
|
+
appendChild(parentId, childId) {
|
|
30
|
+
const parent = this.store.get(parentId);
|
|
31
|
+
if (!parent)
|
|
32
|
+
throw new Error(`Parent element ${parentId} not found`);
|
|
33
|
+
parent.children.push(childId);
|
|
34
|
+
trace("appendChild", { parentId, childId });
|
|
35
|
+
}
|
|
36
|
+
removeChild(parentId, childId) {
|
|
37
|
+
const parent = this.store.get(parentId);
|
|
38
|
+
if (!parent)
|
|
39
|
+
throw new Error(`Parent element ${parentId} not found`);
|
|
40
|
+
const index = parent.children.indexOf(childId);
|
|
41
|
+
if (index !== -1) {
|
|
42
|
+
parent.children.splice(index, 1);
|
|
43
|
+
trace("removeChild", { parentId, childId });
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
setContainerChild(childId) {
|
|
47
|
+
if (this.rootId === null) {
|
|
48
|
+
this.rootId = childId;
|
|
49
|
+
trace("setContainerChild - rootId", this.rootId);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
getElement(globalId) {
|
|
53
|
+
return this.store.get(globalId);
|
|
54
|
+
}
|
|
55
|
+
getRoot() {
|
|
56
|
+
if (this.rootId === null) {
|
|
57
|
+
throw new Error("No root element created yet");
|
|
58
|
+
}
|
|
59
|
+
return this.store.get(this.rootId);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Event Types for React-GPUI Renderer
|
|
3
|
+
*/
|
|
4
|
+
export type EventHandler = (event: Event) => void;
|
|
5
|
+
export type MouseEvent = {
|
|
6
|
+
type: 'click' | 'hover' | 'mouseenter' | 'mouseleave';
|
|
7
|
+
target: number;
|
|
8
|
+
button?: number;
|
|
9
|
+
clientX?: number;
|
|
10
|
+
clientY?: number;
|
|
11
|
+
};
|
|
12
|
+
export type Event = MouseEvent;
|
|
13
|
+
//# sourceMappingURL=events.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../src/reconciler/events.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,MAAM,YAAY,GAAG,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;AAElD,MAAM,MAAM,UAAU,GAAG;IACvB,IAAI,EAAE,OAAO,GAAG,OAAO,GAAG,YAAY,GAAG,YAAY,CAAC;IACtD,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,KAAK,GAAG,UAAU,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare let currentWindowId: number;
|
|
2
|
+
export declare function init(): void;
|
|
3
|
+
export declare function createWindow(width: number, height: number, title?: string): number;
|
|
4
|
+
export declare function renderFrame(windowId: number, element: any): void;
|
|
5
|
+
export declare function triggerRender(windowId: number): void;
|
|
6
|
+
export declare function batchElementUpdates(windowId: number, elements: any[]): void;
|
|
7
|
+
//# sourceMappingURL=gpui-binding.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gpui-binding.d.ts","sourceRoot":"","sources":["../../src/reconciler/gpui-binding.ts"],"names":[],"mappings":"AAiDA,eAAO,IAAI,eAAe,EAAE,MAAU,CAAC;AA8BvC,wBAAgB,IAAI,IAAI,IAAI,CAM3B;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAalF;AAED,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,GAAG,IAAI,CAsEhE;AAED,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAQpD;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,EAAE,GAAG,IAAI,CAiC3E"}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { dlopen, FFIType, suffix, ptr } from "bun:ffi";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import { sleep } from "bun";
|
|
4
|
+
import { info, debug, trace } from "./logging";
|
|
5
|
+
const libName = `libgpui_renderer.${suffix}`;
|
|
6
|
+
const libPath = join(import.meta.dir, "../native/linux-x64", libName);
|
|
7
|
+
info(`Loading GPUI library from: ${libPath}`);
|
|
8
|
+
const liveBuffers = [];
|
|
9
|
+
const lib = dlopen(libPath, {
|
|
10
|
+
gpui_init: {
|
|
11
|
+
args: [FFIType.ptr],
|
|
12
|
+
returns: FFIType.void,
|
|
13
|
+
},
|
|
14
|
+
gpui_create_window: {
|
|
15
|
+
args: [FFIType.f32, FFIType.f32, FFIType.ptr, FFIType.ptr],
|
|
16
|
+
returns: FFIType.void,
|
|
17
|
+
},
|
|
18
|
+
gpui_is_ready: {
|
|
19
|
+
args: [],
|
|
20
|
+
returns: FFIType.bool,
|
|
21
|
+
},
|
|
22
|
+
gpui_render_frame: {
|
|
23
|
+
args: [FFIType.ptr, FFIType.ptr, FFIType.ptr, FFIType.ptr, FFIType.ptr, FFIType.ptr, FFIType.ptr],
|
|
24
|
+
returns: FFIType.void,
|
|
25
|
+
},
|
|
26
|
+
gpui_trigger_render: {
|
|
27
|
+
args: [FFIType.ptr, FFIType.ptr],
|
|
28
|
+
returns: FFIType.void,
|
|
29
|
+
},
|
|
30
|
+
gpui_free_result: {
|
|
31
|
+
args: [FFIType.ptr],
|
|
32
|
+
returns: FFIType.void,
|
|
33
|
+
},
|
|
34
|
+
gpui_batch_update_elements: {
|
|
35
|
+
args: [FFIType.ptr, FFIType.ptr, FFIType.ptr, FFIType.ptr],
|
|
36
|
+
returns: FFIType.void,
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
if (!lib.symbols) {
|
|
40
|
+
throw new Error("Failed to load GPUI library");
|
|
41
|
+
}
|
|
42
|
+
const FFI_RESULT_SIZE = 16;
|
|
43
|
+
export let currentWindowId = 0;
|
|
44
|
+
function checkResult(resultBuffer) {
|
|
45
|
+
const status = new DataView(resultBuffer.buffer).getInt32(0, true);
|
|
46
|
+
if (status !== 0) {
|
|
47
|
+
const errorPtr = new DataView(resultBuffer.buffer).getInt32(8, true);
|
|
48
|
+
lib.symbols.gpui_free_result(resultBuffer);
|
|
49
|
+
throw new Error(`GPUI operation failed: error ptr=${errorPtr}`);
|
|
50
|
+
}
|
|
51
|
+
const errorCheck = new DataView(resultBuffer.buffer).getInt32(8, true);
|
|
52
|
+
if (errorCheck !== 0) {
|
|
53
|
+
lib.symbols.gpui_free_result(resultBuffer);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function checkWindowCreateResult(resultBuffer) {
|
|
57
|
+
const status = new DataView(resultBuffer.buffer).getInt32(0, true);
|
|
58
|
+
if (status !== 0) {
|
|
59
|
+
const errorPtr = new DataView(resultBuffer.buffer).getInt32(8, true);
|
|
60
|
+
lib.symbols.gpui_free_result(resultBuffer);
|
|
61
|
+
throw new Error(`GPUI window creation failed: error ptr=${errorPtr}`);
|
|
62
|
+
}
|
|
63
|
+
const windowId = new DataView(resultBuffer.buffer).getBigUint64(8, true);
|
|
64
|
+
return Number(windowId);
|
|
65
|
+
}
|
|
66
|
+
export function init() {
|
|
67
|
+
const resultBuffer = new Uint8Array(FFI_RESULT_SIZE);
|
|
68
|
+
lib.symbols.gpui_init(resultBuffer);
|
|
69
|
+
checkResult(resultBuffer);
|
|
70
|
+
waitGpuiReady();
|
|
71
|
+
}
|
|
72
|
+
export function createWindow(width, height, title) {
|
|
73
|
+
const resultBuffer = new Uint8Array(FFI_RESULT_SIZE);
|
|
74
|
+
// 编码 title 字符串,添加 null 终止符
|
|
75
|
+
const titleStr = title || "React-GPUI";
|
|
76
|
+
const titleBuffer = new TextEncoder().encode(titleStr + "\0");
|
|
77
|
+
liveBuffers.push(titleBuffer.buffer); // 保持引用防止 GC
|
|
78
|
+
const titlePtr = ptr(titleBuffer);
|
|
79
|
+
lib.symbols.gpui_create_window(width, height, titlePtr, resultBuffer);
|
|
80
|
+
currentWindowId = checkWindowCreateResult(resultBuffer);
|
|
81
|
+
info(`Created window with id: ${currentWindowId}`);
|
|
82
|
+
return currentWindowId;
|
|
83
|
+
}
|
|
84
|
+
export function renderFrame(windowId, element) {
|
|
85
|
+
trace("renderFrame called");
|
|
86
|
+
debug("Element", element);
|
|
87
|
+
liveBuffers.length = 0;
|
|
88
|
+
const windowIdBuffer = new ArrayBuffer(8);
|
|
89
|
+
new DataView(windowIdBuffer).setBigInt64(0, BigInt(windowId), true);
|
|
90
|
+
liveBuffers.push(windowIdBuffer);
|
|
91
|
+
const windowIdPtr = ptr(windowIdBuffer);
|
|
92
|
+
const globalIdBuffer = new ArrayBuffer(8);
|
|
93
|
+
new DataView(globalIdBuffer).setBigInt64(0, BigInt(element.globalId), true);
|
|
94
|
+
liveBuffers.push(globalIdBuffer);
|
|
95
|
+
const globalIdPtr = ptr(globalIdBuffer);
|
|
96
|
+
const typeBuffer = new TextEncoder().encode(element.type + "\0");
|
|
97
|
+
liveBuffers.push(typeBuffer.buffer);
|
|
98
|
+
const typePtr = ptr(typeBuffer);
|
|
99
|
+
const textContent = element.text || " ";
|
|
100
|
+
const textBuffer = new TextEncoder().encode(textContent + "\0");
|
|
101
|
+
liveBuffers.push(textBuffer.buffer);
|
|
102
|
+
const textPtr = ptr(textBuffer);
|
|
103
|
+
const childrenArray = element.children || [];
|
|
104
|
+
const childCountBuffer = new ArrayBuffer(8);
|
|
105
|
+
new DataView(childCountBuffer).setBigInt64(0, BigInt(childrenArray.length), true);
|
|
106
|
+
liveBuffers.push(childCountBuffer);
|
|
107
|
+
const childCountPtr = ptr(childCountBuffer);
|
|
108
|
+
const childrenByteLength = Math.max(childrenArray.length * 8, 8);
|
|
109
|
+
const childrenBuffer = new ArrayBuffer(childrenByteLength);
|
|
110
|
+
if (childrenArray.length > 0) {
|
|
111
|
+
const childrenView = new BigInt64Array(childrenBuffer);
|
|
112
|
+
for (let i = 0; i < childrenArray.length; i++) {
|
|
113
|
+
childrenView[i] = BigInt(childrenArray[i]);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
liveBuffers.push(childrenBuffer);
|
|
117
|
+
const childrenPtr = ptr(childrenBuffer);
|
|
118
|
+
debug("FFI params", {
|
|
119
|
+
windowId,
|
|
120
|
+
globalId: element.globalId,
|
|
121
|
+
type: element.type,
|
|
122
|
+
text: textContent,
|
|
123
|
+
childCount: childrenArray.length,
|
|
124
|
+
children: childrenArray
|
|
125
|
+
});
|
|
126
|
+
const resultBuffer = new Uint8Array(8);
|
|
127
|
+
lib.symbols.gpui_render_frame(windowIdPtr, globalIdPtr, typePtr, textPtr, childCountPtr, childrenPtr, resultBuffer);
|
|
128
|
+
const status = new DataView(resultBuffer.buffer).getInt32(0, true);
|
|
129
|
+
if (status !== 0) {
|
|
130
|
+
throw new Error(`GPUI render failed with status: ${status}`);
|
|
131
|
+
}
|
|
132
|
+
trace("renderFrame completed");
|
|
133
|
+
}
|
|
134
|
+
export function triggerRender(windowId) {
|
|
135
|
+
const windowIdBuffer = new ArrayBuffer(8);
|
|
136
|
+
new DataView(windowIdBuffer).setBigInt64(0, BigInt(windowId), true);
|
|
137
|
+
liveBuffers.push(windowIdBuffer);
|
|
138
|
+
const windowIdPtr = ptr(windowIdBuffer);
|
|
139
|
+
const triggerBuffer = new Uint8Array(8);
|
|
140
|
+
lib.symbols.gpui_trigger_render(windowIdPtr, triggerBuffer);
|
|
141
|
+
}
|
|
142
|
+
export function batchElementUpdates(windowId, elements) {
|
|
143
|
+
if (elements.length === 0) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
info(`Batching ${elements.length} element updates for window ${windowId}`);
|
|
147
|
+
liveBuffers.length = 0;
|
|
148
|
+
const windowIdBuffer = new ArrayBuffer(8);
|
|
149
|
+
new DataView(windowIdBuffer).setBigInt64(0, BigInt(windowId), true);
|
|
150
|
+
liveBuffers.push(windowIdBuffer);
|
|
151
|
+
const windowIdPtr = ptr(windowIdBuffer);
|
|
152
|
+
const countBuffer = new ArrayBuffer(8);
|
|
153
|
+
new DataView(countBuffer).setBigInt64(0, BigInt(elements.length), true);
|
|
154
|
+
liveBuffers.push(countBuffer);
|
|
155
|
+
const countPtr = ptr(countBuffer);
|
|
156
|
+
const elementsJsonString = JSON.stringify(elements);
|
|
157
|
+
debug("batchElementUpdates - elements JSON", elementsJsonString.substring(0, 500));
|
|
158
|
+
const elementsBuffer = new TextEncoder().encode(elementsJsonString + "\0");
|
|
159
|
+
liveBuffers.push(elementsBuffer.buffer);
|
|
160
|
+
const elementsPtr = ptr(elementsBuffer);
|
|
161
|
+
const resultBuffer = new Uint8Array(8);
|
|
162
|
+
lib.symbols.gpui_batch_update_elements(windowIdPtr, countPtr, elementsPtr, resultBuffer);
|
|
163
|
+
const triggerBuffer = new Uint8Array(8);
|
|
164
|
+
lib.symbols.gpui_trigger_render(windowIdPtr, triggerBuffer);
|
|
165
|
+
info(`Batch update completed for ${elements.length} elements`);
|
|
166
|
+
}
|
|
167
|
+
function waitGpuiReady() {
|
|
168
|
+
let delay = 1;
|
|
169
|
+
const maxDelay = 100;
|
|
170
|
+
const maxTotalWait = 5000;
|
|
171
|
+
const startTime = Date.now();
|
|
172
|
+
while (Date.now() - startTime < maxTotalWait) {
|
|
173
|
+
if (lib.symbols.gpui_is_ready()) {
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
sleep(Math.min(delay, maxDelay));
|
|
177
|
+
delay *= 2;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { ElementStore } from "./element-store";
|
|
2
|
+
import { HostConfig } from "react-reconciler";
|
|
3
|
+
type Type = string;
|
|
4
|
+
type Props = any;
|
|
5
|
+
type Container = ElementStore;
|
|
6
|
+
type SuspenseInstance = never;
|
|
7
|
+
type HydratableInstance = never;
|
|
8
|
+
type FormInstance = never;
|
|
9
|
+
type PublicInstance = Element;
|
|
10
|
+
type HostContext = object;
|
|
11
|
+
type ChildSet = never;
|
|
12
|
+
type TimeoutHandle = number;
|
|
13
|
+
type NoTimeout = -1;
|
|
14
|
+
type TransitionStatus = any;
|
|
15
|
+
export interface Instance {
|
|
16
|
+
id: number;
|
|
17
|
+
type: string;
|
|
18
|
+
text?: string;
|
|
19
|
+
children: Instance[];
|
|
20
|
+
style?: Record<string, any>;
|
|
21
|
+
eventHandlers?: Record<string, number>;
|
|
22
|
+
store: ElementStore;
|
|
23
|
+
}
|
|
24
|
+
export interface TextInstance {
|
|
25
|
+
id: number;
|
|
26
|
+
type: "text";
|
|
27
|
+
text: string;
|
|
28
|
+
children: [];
|
|
29
|
+
store: ElementStore;
|
|
30
|
+
}
|
|
31
|
+
export declare const hostConfig: HostConfig<Type, Props, Container, Instance, TextInstance, SuspenseInstance, HydratableInstance, FormInstance, PublicInstance, HostContext, ChildSet, TimeoutHandle, NoTimeout, TransitionStatus>;
|
|
32
|
+
export {};
|
|
33
|
+
//# sourceMappingURL=host-config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"host-config.d.ts","sourceRoot":"","sources":["../../src/reconciler/host-config.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,YAAY,EAAc,MAAM,iBAAiB,CAAC;AAI1D,OAAO,EAAC,UAAU,EAAe,MAAM,kBAAkB,CAAC;AAM1D,KAAK,IAAI,GAAG,MAAM,CAAC;AACnB,KAAK,KAAK,GAAG,GAAG,CAAC;AACjB,KAAK,SAAS,GAAG,YAAY,CAAC;AAC9B,KAAK,gBAAgB,GAAG,KAAK,CAAC;AAC9B,KAAK,kBAAkB,GAAG,KAAK,CAAC;AAChC,KAAK,YAAY,GAAG,KAAK,CAAC;AAC1B,KAAK,cAAc,GAAG,OAAO,CAAC;AAC9B,KAAK,WAAW,GAAG,MAAM,CAAC;AAC1B,KAAK,QAAQ,GAAG,KAAK,CAAC;AACtB,KAAK,aAAa,GAAG,MAAM,CAAC;AAC5B,KAAK,SAAS,GAAG,CAAC,CAAC,CAAC;AACpB,KAAK,gBAAgB,GAAG,GAAG,CAAC;AAG5B,MAAM,WAAW,QAAQ;IACrB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,QAAQ,EAAE,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC5B,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACvC,KAAK,EAAE,YAAY,CAAC;CACvB;AAED,MAAM,WAAW,YAAY;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,EAAE,CAAC;IACb,KAAK,EAAE,YAAY,CAAC;CACvB;AA6ED,eAAO,MAAM,UAAU,EAAE,UAAU,CAC/B,IAAI,EACJ,KAAK,EACL,SAAS,EACT,QAAQ,EACR,YAAY,EACZ,gBAAgB,EAChB,kBAAkB,EAClB,YAAY,EACZ,cAAc,EACd,WAAW,EACX,QAAQ,EACR,aAAa,EACb,SAAS,EACT,gBAAgB,CA0SnB,CAAA"}
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import { renderFrame, batchElementUpdates, currentWindowId } from "./gpui-binding";
|
|
2
|
+
import { mapStyleToProps } from "./styles";
|
|
3
|
+
import { DefaultEventPriority, NoEventPriority } from "react-reconciler/constants";
|
|
4
|
+
import { trace, info, warn } from "./logging";
|
|
5
|
+
let nextEventId = 0;
|
|
6
|
+
const eventHandlers = new Map();
|
|
7
|
+
const pendingUpdates = [];
|
|
8
|
+
function registerEventHandler(handler) {
|
|
9
|
+
const id = nextEventId++;
|
|
10
|
+
eventHandlers.set(id, handler);
|
|
11
|
+
return id;
|
|
12
|
+
}
|
|
13
|
+
function extractStyleProps(props) {
|
|
14
|
+
const styleProps = {};
|
|
15
|
+
if (props.style) {
|
|
16
|
+
Object.assign(styleProps, props.style);
|
|
17
|
+
}
|
|
18
|
+
if (props.className) {
|
|
19
|
+
warn("className not yet supported, use style prop instead");
|
|
20
|
+
}
|
|
21
|
+
if (props.onClick) {
|
|
22
|
+
styleProps.onClick = props.onClick;
|
|
23
|
+
}
|
|
24
|
+
if (props.onHover) {
|
|
25
|
+
styleProps.onHover = props.onHover;
|
|
26
|
+
}
|
|
27
|
+
if (props.onMouseEnter) {
|
|
28
|
+
styleProps.onMouseEnter = props.onMouseEnter;
|
|
29
|
+
}
|
|
30
|
+
if (props.onMouseLeave) {
|
|
31
|
+
styleProps.onMouseLeave = props.onMouseLeave;
|
|
32
|
+
}
|
|
33
|
+
return styleProps;
|
|
34
|
+
}
|
|
35
|
+
function extractEventHandlers(props) {
|
|
36
|
+
const handlers = {};
|
|
37
|
+
if (props.onClick) {
|
|
38
|
+
handlers['onClick'] = registerEventHandler(props.onClick);
|
|
39
|
+
}
|
|
40
|
+
if (props.onHover) {
|
|
41
|
+
handlers['onHover'] = registerEventHandler(props.onHover);
|
|
42
|
+
}
|
|
43
|
+
if (props.onMouseEnter) {
|
|
44
|
+
handlers['onMouseEnter'] = registerEventHandler(props.onMouseEnter);
|
|
45
|
+
}
|
|
46
|
+
if (props.onMouseLeave) {
|
|
47
|
+
handlers['onMouseLeave'] = registerEventHandler(props.onMouseLeave);
|
|
48
|
+
}
|
|
49
|
+
return handlers;
|
|
50
|
+
}
|
|
51
|
+
function queueElementUpdate(element) {
|
|
52
|
+
if (!element)
|
|
53
|
+
return;
|
|
54
|
+
const existingIndex = pendingUpdates.findIndex((e) => e.globalId === element.globalId);
|
|
55
|
+
if (existingIndex !== -1) {
|
|
56
|
+
pendingUpdates[existingIndex] = element;
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
pendingUpdates.push(element);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
let currentUpdatePriority = 0;
|
|
63
|
+
export const hostConfig = {
|
|
64
|
+
supportsMutation: true,
|
|
65
|
+
supportsPersistence: false,
|
|
66
|
+
supportsHydration: false,
|
|
67
|
+
isPrimaryRenderer: true,
|
|
68
|
+
getPublicInstance(_instance) {
|
|
69
|
+
return document.createElement("div");
|
|
70
|
+
},
|
|
71
|
+
getRootHostContext(_rootContainer) {
|
|
72
|
+
return {};
|
|
73
|
+
},
|
|
74
|
+
getChildHostContext(_parentHostContext, _type, _rootContainer) {
|
|
75
|
+
return {};
|
|
76
|
+
},
|
|
77
|
+
prepareForCommit(_containerInfo) {
|
|
78
|
+
return {};
|
|
79
|
+
},
|
|
80
|
+
resetAfterCommit(containerInfo) {
|
|
81
|
+
if (pendingUpdates.length > 0) {
|
|
82
|
+
info(`Processing ${pendingUpdates.length} batched updates`);
|
|
83
|
+
batchElementUpdates(currentWindowId, pendingUpdates);
|
|
84
|
+
pendingUpdates.length = 0;
|
|
85
|
+
}
|
|
86
|
+
const root = containerInfo.getRoot();
|
|
87
|
+
trace("resetAfterCommit - root element", root);
|
|
88
|
+
renderFrame(currentWindowId, root);
|
|
89
|
+
setImmediate(() => {
|
|
90
|
+
if (typeof window !== 'undefined' && window.__gpuiTrigger) {
|
|
91
|
+
window.__gpuiTrigger();
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
},
|
|
95
|
+
shouldSetTextContent(_type, _props) {
|
|
96
|
+
return false;
|
|
97
|
+
},
|
|
98
|
+
resetTextContent(_instance) {
|
|
99
|
+
},
|
|
100
|
+
createTextInstance(text, rootContainer, _hostContext, _internalHandle) {
|
|
101
|
+
const styleProps = extractStyleProps({ style: {} });
|
|
102
|
+
const styles = mapStyleToProps(styleProps);
|
|
103
|
+
const id = rootContainer.createElement("text", String(text), styles);
|
|
104
|
+
trace("createTextInstance", { text, id, styles });
|
|
105
|
+
const instance = {
|
|
106
|
+
id,
|
|
107
|
+
type: "text",
|
|
108
|
+
text: String(text),
|
|
109
|
+
children: [],
|
|
110
|
+
store: rootContainer,
|
|
111
|
+
};
|
|
112
|
+
queueElementUpdate(rootContainer.getElement(id));
|
|
113
|
+
return instance;
|
|
114
|
+
},
|
|
115
|
+
commitTextUpdate(textInstance, oldText, newText) {
|
|
116
|
+
textInstance.text = String(newText);
|
|
117
|
+
trace("commitTextUpdate", { oldText, newText });
|
|
118
|
+
const element = textInstance.store.getElement(textInstance.id);
|
|
119
|
+
if (element) {
|
|
120
|
+
element.text = String(newText);
|
|
121
|
+
queueElementUpdate(element);
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
createInstance(type, props, rootContainer, _hostContext, _internalHandle) {
|
|
125
|
+
const styleProps = extractStyleProps(props);
|
|
126
|
+
const styles = mapStyleToProps(styleProps);
|
|
127
|
+
const eventHandlers = extractEventHandlers(props);
|
|
128
|
+
const id = rootContainer.createElement(type, undefined, { ...styles, eventHandlers });
|
|
129
|
+
trace("createInstance", { type, id, styles, eventHandlers });
|
|
130
|
+
const instance = {
|
|
131
|
+
id,
|
|
132
|
+
type,
|
|
133
|
+
children: [],
|
|
134
|
+
style: styles,
|
|
135
|
+
eventHandlers,
|
|
136
|
+
store: rootContainer,
|
|
137
|
+
};
|
|
138
|
+
queueElementUpdate(rootContainer.getElement(id));
|
|
139
|
+
return instance;
|
|
140
|
+
},
|
|
141
|
+
appendInitialChild(parentInstance, child) {
|
|
142
|
+
trace("appendInitialChild", { parent: parentInstance, child });
|
|
143
|
+
parentInstance.children.push(child);
|
|
144
|
+
parentInstance.store.appendChild(parentInstance.id, child.id);
|
|
145
|
+
queueElementUpdate(parentInstance.store.getElement(parentInstance.id));
|
|
146
|
+
},
|
|
147
|
+
appendChild(parentInstance, child) {
|
|
148
|
+
trace("appendChild", { parent: parentInstance, child });
|
|
149
|
+
parentInstance.children.push(child);
|
|
150
|
+
parentInstance.store.appendChild(parentInstance.id, child.id);
|
|
151
|
+
queueElementUpdate(parentInstance.store.getElement(parentInstance.id));
|
|
152
|
+
},
|
|
153
|
+
appendChildToContainer(container, child) {
|
|
154
|
+
trace("appendChildToContainer", { container, child });
|
|
155
|
+
const childInstance = child;
|
|
156
|
+
container.setContainerChild(childInstance.id);
|
|
157
|
+
queueElementUpdate(container.getElement(childInstance.id));
|
|
158
|
+
},
|
|
159
|
+
insertBefore(parentInstance, child, beforeChild) {
|
|
160
|
+
const childInstance = child;
|
|
161
|
+
const beforeChildInstance = beforeChild;
|
|
162
|
+
const beforeIndex = parentInstance.children.indexOf(beforeChildInstance);
|
|
163
|
+
if (beforeIndex !== -1) {
|
|
164
|
+
parentInstance.children.splice(beforeIndex, 0, childInstance);
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
parentInstance.children.push(childInstance);
|
|
168
|
+
}
|
|
169
|
+
queueElementUpdate(parentInstance.store.getElement(parentInstance.id));
|
|
170
|
+
},
|
|
171
|
+
insertInContainerBefore(_container, child, beforeChild) {
|
|
172
|
+
trace("insertInContainerBefore", { child, beforeChild });
|
|
173
|
+
},
|
|
174
|
+
removeChild(parentInstance, child) {
|
|
175
|
+
const childInstance = child;
|
|
176
|
+
const index = parentInstance.children.indexOf(childInstance);
|
|
177
|
+
if (index !== -1) {
|
|
178
|
+
parentInstance.children.splice(index, 1);
|
|
179
|
+
}
|
|
180
|
+
parentInstance.store.removeChild(parentInstance.id, childInstance.id);
|
|
181
|
+
queueElementUpdate(parentInstance.store.getElement(parentInstance.id));
|
|
182
|
+
},
|
|
183
|
+
removeChildFromContainer(_container, child) {
|
|
184
|
+
trace("removeChildFromContainer", { container: _container, child });
|
|
185
|
+
const childInstance = child;
|
|
186
|
+
_container.removeChild(_container.getRoot().globalId, childInstance.id);
|
|
187
|
+
},
|
|
188
|
+
commitUpdate(instance, _type, _prevProps, nextProps, _internalHandle) {
|
|
189
|
+
if (nextProps && typeof nextProps.children === "string") {
|
|
190
|
+
instance.text = String(nextProps.children);
|
|
191
|
+
const element = instance.store.getElement(instance.id);
|
|
192
|
+
if (element) {
|
|
193
|
+
queueElementUpdate(element);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
},
|
|
197
|
+
finalizeInitialChildren(_instance, _type, _props, _rootContainer, _hostContext) {
|
|
198
|
+
return false;
|
|
199
|
+
},
|
|
200
|
+
clearContainer(_container) {
|
|
201
|
+
},
|
|
202
|
+
hideInstance(_instance) {
|
|
203
|
+
},
|
|
204
|
+
unhideInstance(_instance, _props) {
|
|
205
|
+
},
|
|
206
|
+
detachDeletedInstance(instance) {
|
|
207
|
+
trace("detachDeletedInstance", { instance });
|
|
208
|
+
},
|
|
209
|
+
prepareScopeUpdate(_scopeInstance, _instance) {
|
|
210
|
+
},
|
|
211
|
+
getInstanceFromScope(_scopeInstance) {
|
|
212
|
+
return null;
|
|
213
|
+
},
|
|
214
|
+
scheduleTimeout(fn, delay) {
|
|
215
|
+
return setTimeout(fn, delay);
|
|
216
|
+
},
|
|
217
|
+
cancelTimeout(id) {
|
|
218
|
+
clearTimeout(id);
|
|
219
|
+
},
|
|
220
|
+
noTimeout: -1,
|
|
221
|
+
preparePortalMount(_containerInfo) {
|
|
222
|
+
},
|
|
223
|
+
getInstanceFromNode(_node) {
|
|
224
|
+
return null;
|
|
225
|
+
},
|
|
226
|
+
beforeActiveInstanceBlur() {
|
|
227
|
+
},
|
|
228
|
+
afterActiveInstanceBlur() {
|
|
229
|
+
},
|
|
230
|
+
NotPendingTransition: null,
|
|
231
|
+
HostTransitionContext: null,
|
|
232
|
+
resetFormInstance(_form) {
|
|
233
|
+
},
|
|
234
|
+
requestPostPaintCallback(_callback) {
|
|
235
|
+
},
|
|
236
|
+
shouldAttemptEagerTransition() {
|
|
237
|
+
return false;
|
|
238
|
+
},
|
|
239
|
+
trackSchedulerEvent() {
|
|
240
|
+
},
|
|
241
|
+
resolveEventType() {
|
|
242
|
+
return null;
|
|
243
|
+
},
|
|
244
|
+
resolveEventTimeStamp() {
|
|
245
|
+
return Date.now();
|
|
246
|
+
},
|
|
247
|
+
maySuspendCommit(_type, _props) {
|
|
248
|
+
return false;
|
|
249
|
+
},
|
|
250
|
+
preloadInstance(_type, _props) {
|
|
251
|
+
return false;
|
|
252
|
+
},
|
|
253
|
+
startSuspendingCommit() {
|
|
254
|
+
},
|
|
255
|
+
suspendInstance(_type, _props) {
|
|
256
|
+
},
|
|
257
|
+
waitForCommitToBeReady() {
|
|
258
|
+
return null;
|
|
259
|
+
},
|
|
260
|
+
setCurrentUpdatePriority(newPriority) {
|
|
261
|
+
currentUpdatePriority = newPriority;
|
|
262
|
+
},
|
|
263
|
+
getCurrentUpdatePriority: () => currentUpdatePriority,
|
|
264
|
+
resolveUpdatePriority() {
|
|
265
|
+
if (currentUpdatePriority !== NoEventPriority) {
|
|
266
|
+
return currentUpdatePriority;
|
|
267
|
+
}
|
|
268
|
+
return DefaultEventPriority;
|
|
269
|
+
},
|
|
270
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
type LogLevel = 'error' | 'warn' | 'info' | 'debug' | 'trace';
|
|
2
|
+
export declare function initLogging(): void;
|
|
3
|
+
export declare function log(level: LogLevel, message: string, ...args: unknown[]): void;
|
|
4
|
+
export declare function trace(message: string, ...args: unknown[]): void;
|
|
5
|
+
export declare function debug(message: string, ...args: unknown[]): void;
|
|
6
|
+
export declare function info(message: string, ...args: unknown[]): void;
|
|
7
|
+
export declare function warn(message: string, ...args: unknown[]): void;
|
|
8
|
+
export declare function error(message: string, ...args: unknown[]): void;
|
|
9
|
+
export {};
|
|
10
|
+
//# sourceMappingURL=logging.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logging.d.ts","sourceRoot":"","sources":["../../src/reconciler/logging.ts"],"names":[],"mappings":"AAAA,KAAK,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,CAAC;AAkC9D,wBAAgB,WAAW,IAAI,IAAI,CAGlC;AAED,wBAAgB,GAAG,CAAC,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAoB9E;AAED,wBAAgB,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAE/D;AAED,wBAAgB,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAE/D;AAED,wBAAgB,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAE9D;AAED,wBAAgB,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAE9D;AAED,wBAAgB,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAE/D"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
const LOG_LEVELS = ['error', 'warn', 'info', 'debug', 'trace'];
|
|
2
|
+
function getLogLevel() {
|
|
3
|
+
const envLevel = process.env.RUST_LOG?.toLowerCase();
|
|
4
|
+
if (envLevel && LOG_LEVELS.includes(envLevel)) {
|
|
5
|
+
return envLevel;
|
|
6
|
+
}
|
|
7
|
+
return 'info';
|
|
8
|
+
}
|
|
9
|
+
function getLocalTime() {
|
|
10
|
+
const now = new Date();
|
|
11
|
+
const year = now.getFullYear();
|
|
12
|
+
const month = String(now.getMonth() + 1).padStart(2, '0');
|
|
13
|
+
const day = String(now.getDate()).padStart(2, '0');
|
|
14
|
+
const hours = String(now.getHours()).padStart(2, '0');
|
|
15
|
+
const minutes = String(now.getMinutes()).padStart(2, '0');
|
|
16
|
+
const seconds = String(now.getSeconds()).padStart(2, '0');
|
|
17
|
+
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
|
18
|
+
}
|
|
19
|
+
function shouldLog(level) {
|
|
20
|
+
const currentLevel = getLogLevel();
|
|
21
|
+
return LOG_LEVELS.indexOf(level) <= LOG_LEVELS.indexOf(currentLevel);
|
|
22
|
+
}
|
|
23
|
+
function formatMessage(level, message, args) {
|
|
24
|
+
const timestamp = getLocalTime();
|
|
25
|
+
const argsStr = args && args.length > 0 ? '\n' + args.map(a => JSON.stringify(a, null, 2)).join('\n') : '';
|
|
26
|
+
return `${timestamp} ${level.toUpperCase()} ${message}${argsStr}`;
|
|
27
|
+
}
|
|
28
|
+
export function initLogging() {
|
|
29
|
+
const level = getLogLevel();
|
|
30
|
+
console.log(`${getLocalTime()} INFO init: Logging system initialized (level: ${level})`);
|
|
31
|
+
}
|
|
32
|
+
export function log(level, message, ...args) {
|
|
33
|
+
if (!shouldLog(level))
|
|
34
|
+
return;
|
|
35
|
+
const formatted = formatMessage(level, message, args);
|
|
36
|
+
switch (level) {
|
|
37
|
+
case 'error':
|
|
38
|
+
console.error(formatted);
|
|
39
|
+
break;
|
|
40
|
+
case 'warn':
|
|
41
|
+
console.warn(formatted);
|
|
42
|
+
break;
|
|
43
|
+
case 'info':
|
|
44
|
+
console.log(formatted);
|
|
45
|
+
break;
|
|
46
|
+
case 'debug':
|
|
47
|
+
console.debug(formatted);
|
|
48
|
+
break;
|
|
49
|
+
case 'trace':
|
|
50
|
+
console.trace(formatted);
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
export function trace(message, ...args) {
|
|
55
|
+
log('trace', message, ...args);
|
|
56
|
+
}
|
|
57
|
+
export function debug(message, ...args) {
|
|
58
|
+
log('debug', message, ...args);
|
|
59
|
+
}
|
|
60
|
+
export function info(message, ...args) {
|
|
61
|
+
log('info', message, ...args);
|
|
62
|
+
}
|
|
63
|
+
export function warn(message, ...args) {
|
|
64
|
+
log('warn', message, ...args);
|
|
65
|
+
}
|
|
66
|
+
export function error(message, ...args) {
|
|
67
|
+
log('error', message, ...args);
|
|
68
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import ReactReconciler from "react-reconciler";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { ElementStore } from "./element-store";
|
|
4
|
+
export declare const reconciler: ReactReconciler.Reconciler<ElementStore, import("./host-config").Instance, import("./host-config").TextInstance, never, never, Element>;
|
|
5
|
+
export declare function _render(element: React.ReactNode, root: ElementStore): any;
|
|
6
|
+
//# sourceMappingURL=reconciler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reconciler.d.ts","sourceRoot":"","sources":["../../src/reconciler/reconciler.ts"],"names":[],"mappings":"AAAA,OAAO,eAAe,MAAM,kBAAkB,CAAC;AAE/C,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,EAAc,YAAY,EAAC,MAAM,iBAAiB,CAAC;AAG1D,eAAO,MAAM,UAAU,yIAA8B,CAAA;AAGrD,wBAAgB,OAAO,CAAC,OAAO,EAAE,KAAK,CAAC,SAAS,EAAE,IAAI,EAAE,YAAY,OAkBnE"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import ReactReconciler from "react-reconciler";
|
|
2
|
+
import { hostConfig } from "./host-config";
|
|
3
|
+
import { ConcurrentRoot } from "react-reconciler/constants";
|
|
4
|
+
export const reconciler = ReactReconciler(hostConfig);
|
|
5
|
+
export function _render(element, root) {
|
|
6
|
+
const container = reconciler.createContainer(root, ConcurrentRoot, null, false, null, "", console.error, console.error, console.error, console.error, null);
|
|
7
|
+
reconciler.updateContainer(element, container, null, () => { });
|
|
8
|
+
return container;
|
|
9
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
export type Root = {
|
|
3
|
+
render: (children: React.ReactNode) => void;
|
|
4
|
+
unmount: () => void;
|
|
5
|
+
};
|
|
6
|
+
export type RootProps = {
|
|
7
|
+
width: number;
|
|
8
|
+
height: number;
|
|
9
|
+
title?: string;
|
|
10
|
+
};
|
|
11
|
+
export declare function createRoot({ width, height, title }: RootProps): Root;
|
|
12
|
+
//# sourceMappingURL=renderer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"renderer.d.ts","sourceRoot":"","sources":["../../src/reconciler/renderer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAM/B,MAAM,MAAM,IAAI,GAAG;IACf,MAAM,EAAE,CAAC,QAAQ,EAAE,KAAK,CAAC,SAAS,KAAK,IAAI,CAAC;IAC5C,OAAO,EAAE,MAAM,IAAI,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAClB,CAAA;AAED,wBAAgB,UAAU,CAAC,EACI,KAAK,EAAE,MAAM,EAAE,KAAK,EACvB,EAAE,SAAS,GAAG,IAAI,CAuB7C"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { _render, reconciler } from "./reconciler";
|
|
3
|
+
import { init, createWindow } from "./gpui-binding";
|
|
4
|
+
import { ElementStore } from "./element-store";
|
|
5
|
+
import { AppContext } from "./ctx";
|
|
6
|
+
export function createRoot({ width, height, title }) {
|
|
7
|
+
let container = null;
|
|
8
|
+
init();
|
|
9
|
+
const windowId = createWindow(width, height, title);
|
|
10
|
+
console.log("Created window with id:", windowId);
|
|
11
|
+
const elementStore = new ElementStore();
|
|
12
|
+
return {
|
|
13
|
+
render(node) {
|
|
14
|
+
container = _render(React.createElement(AppContext.Provider, { value: { windowId } }, node), elementStore);
|
|
15
|
+
},
|
|
16
|
+
unmount() {
|
|
17
|
+
reconciler.updateContainer(null, container, null, () => {
|
|
18
|
+
});
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Style Utilities for React-GPUI Renderer
|
|
3
|
+
*
|
|
4
|
+
* Handles conversion between React style props and GPUI style values
|
|
5
|
+
*/
|
|
6
|
+
export interface StyleProps {
|
|
7
|
+
color?: string;
|
|
8
|
+
backgroundColor?: string;
|
|
9
|
+
borderColor?: string;
|
|
10
|
+
fontSize?: number | string;
|
|
11
|
+
fontWeight?: string | number;
|
|
12
|
+
width?: number | string;
|
|
13
|
+
height?: number | string;
|
|
14
|
+
margin?: number | string;
|
|
15
|
+
marginTop?: number | string;
|
|
16
|
+
marginRight?: number | string;
|
|
17
|
+
marginBottom?: number | string;
|
|
18
|
+
marginLeft?: number | string;
|
|
19
|
+
padding?: number | string;
|
|
20
|
+
paddingTop?: number | string;
|
|
21
|
+
paddingRight?: number | string;
|
|
22
|
+
paddingBottom?: number | string;
|
|
23
|
+
paddingLeft?: number | string;
|
|
24
|
+
display?: 'flex' | 'block' | 'inline' | 'inline-block';
|
|
25
|
+
flexDirection?: 'row' | 'column';
|
|
26
|
+
justifyContent?: 'flex-start' | 'center' | 'flex-end' | 'space-between' | 'space-around' | 'space-evenly';
|
|
27
|
+
alignItems?: 'flex-start' | 'center' | 'flex-end' | 'stretch';
|
|
28
|
+
gap?: number | string;
|
|
29
|
+
borderRadius?: number | string;
|
|
30
|
+
opacity?: number;
|
|
31
|
+
src?: string;
|
|
32
|
+
alt?: string;
|
|
33
|
+
onClick?: (event: MouseEvent) => void;
|
|
34
|
+
onHover?: (event: MouseEvent) => void;
|
|
35
|
+
onMouseEnter?: (event: MouseEvent) => void;
|
|
36
|
+
onMouseLeave?: (event: MouseEvent) => void;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Parse CSS color string to GPUI RGB value
|
|
40
|
+
* Supports: hex (#RRGGBB, #RGB), rgb(r, g, b), rgba(r, g, b, a), named colors
|
|
41
|
+
*/
|
|
42
|
+
export declare function parseColor(color: string): number;
|
|
43
|
+
/**
|
|
44
|
+
* Parse size value to pixels
|
|
45
|
+
* Supports: px, em, rem, %, number (assumed px)
|
|
46
|
+
*/
|
|
47
|
+
export declare function parseSize(size: number | string): number;
|
|
48
|
+
/**
|
|
49
|
+
* Parse font weight
|
|
50
|
+
*/
|
|
51
|
+
export declare function parseFontWeight(weight: string | number): string;
|
|
52
|
+
/**
|
|
53
|
+
* Parse margin/padding shorthand
|
|
54
|
+
* Supports: 1-4 values (CSS-like syntax)
|
|
55
|
+
* Returns: [top, right, bottom, left]
|
|
56
|
+
*/
|
|
57
|
+
export declare function parseSpacing(value: number | string): [number, number, number, number];
|
|
58
|
+
/**
|
|
59
|
+
* Parse margin/padding individual values
|
|
60
|
+
*/
|
|
61
|
+
export declare function parseSpacingIndividual(top?: number | string, right?: number | string, bottom?: number | string, left?: number | string): [number, number, number, number];
|
|
62
|
+
/**
|
|
63
|
+
* Map React style props to GPUI style object
|
|
64
|
+
*/
|
|
65
|
+
export declare function mapStyleToProps(props: StyleProps): Record<string, any>;
|
|
66
|
+
//# sourceMappingURL=styles.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"styles.d.ts","sourceRoot":"","sources":["../../src/reconciler/styles.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,WAAW,UAAU;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC3B,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC7B,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACzB,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC5B,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC9B,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC/B,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC7B,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC1B,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC7B,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC/B,aAAa,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAChC,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC9B,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,QAAQ,GAAG,cAAc,CAAC;IACvD,aAAa,CAAC,EAAE,KAAK,GAAG,QAAQ,CAAC;IACjC,cAAc,CAAC,EAAE,YAAY,GAAG,QAAQ,GAAG,UAAU,GAAG,eAAe,GAAG,cAAc,GAAG,cAAc,CAAC;IAC1G,UAAU,CAAC,EAAE,YAAY,GAAG,QAAQ,GAAG,UAAU,GAAG,SAAS,CAAC;IAC9D,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC/B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;IACtC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;IACtC,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;IAC3C,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;CAC5C;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CA2ChD;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAgCvD;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAc/D;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAGrF;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CACpC,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,EACrB,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,EACvB,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,EACxB,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,GACrB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAOlC;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAsFtE"}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Style Utilities for React-GPUI Renderer
|
|
3
|
+
*
|
|
4
|
+
* Handles conversion between React style props and GPUI style values
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Parse CSS color string to GPUI RGB value
|
|
8
|
+
* Supports: hex (#RRGGBB, #RGB), rgb(r, g, b), rgba(r, g, b, a), named colors
|
|
9
|
+
*/
|
|
10
|
+
export function parseColor(color) {
|
|
11
|
+
if (!color) {
|
|
12
|
+
return 0x000000;
|
|
13
|
+
}
|
|
14
|
+
if (color.startsWith('#')) {
|
|
15
|
+
const hex = color.slice(1);
|
|
16
|
+
if (hex.length === 3) {
|
|
17
|
+
const r = parseInt(hex[0] + hex[0], 16);
|
|
18
|
+
const g = parseInt(hex[1] + hex[1], 16);
|
|
19
|
+
const b = parseInt(hex[2] + hex[2], 16);
|
|
20
|
+
return (r << 16) | (g << 8) | b;
|
|
21
|
+
}
|
|
22
|
+
else if (hex.length === 6) {
|
|
23
|
+
const r = parseInt(hex.slice(0, 2), 16);
|
|
24
|
+
const g = parseInt(hex.slice(2, 4), 16);
|
|
25
|
+
const b = parseInt(hex.slice(4, 6), 16);
|
|
26
|
+
return (r << 16) | (g << 8) | b;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
const rgbMatch = color.match(/rgb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)/);
|
|
30
|
+
if (rgbMatch) {
|
|
31
|
+
const r = parseInt(rgbMatch[1], 10);
|
|
32
|
+
const g = parseInt(rgbMatch[2], 10);
|
|
33
|
+
const b = parseInt(rgbMatch[3], 10);
|
|
34
|
+
return (r << 16) | (g << 8) | b;
|
|
35
|
+
}
|
|
36
|
+
const rgbaMatch = color.match(/rgba\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*[\d.]+\s*\)/);
|
|
37
|
+
if (rgbaMatch) {
|
|
38
|
+
const r = parseInt(rgbaMatch[1], 10);
|
|
39
|
+
const g = parseInt(rgbaMatch[2], 10);
|
|
40
|
+
const b = parseInt(rgbaMatch[3], 10);
|
|
41
|
+
return (r << 16) | (g << 8) | b;
|
|
42
|
+
}
|
|
43
|
+
const namedColor = NAMED_COLORS[color.toLowerCase()];
|
|
44
|
+
if (namedColor !== undefined) {
|
|
45
|
+
return namedColor;
|
|
46
|
+
}
|
|
47
|
+
console.warn(`Unknown color format: ${color}, using black`);
|
|
48
|
+
return 0x000000;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Parse size value to pixels
|
|
52
|
+
* Supports: px, em, rem, %, number (assumed px)
|
|
53
|
+
*/
|
|
54
|
+
export function parseSize(size) {
|
|
55
|
+
if (typeof size === 'number') {
|
|
56
|
+
return size;
|
|
57
|
+
}
|
|
58
|
+
if (typeof size === 'string') {
|
|
59
|
+
const s = size.trim();
|
|
60
|
+
if (s.endsWith('px')) {
|
|
61
|
+
return parseFloat(s.slice(0, -2));
|
|
62
|
+
}
|
|
63
|
+
if (s.endsWith('em')) {
|
|
64
|
+
return parseFloat(s.slice(0, -2)) * 16;
|
|
65
|
+
}
|
|
66
|
+
if (s.endsWith('rem')) {
|
|
67
|
+
return parseFloat(s.slice(0, -3)) * 16;
|
|
68
|
+
}
|
|
69
|
+
if (s.endsWith('%')) {
|
|
70
|
+
return parseFloat(s.slice(0, -1));
|
|
71
|
+
}
|
|
72
|
+
const parsed = parseFloat(s);
|
|
73
|
+
if (!isNaN(parsed)) {
|
|
74
|
+
return parsed;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
console.warn(`Unknown size format: ${size}, using 0`);
|
|
78
|
+
return 0;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Parse font weight
|
|
82
|
+
*/
|
|
83
|
+
export function parseFontWeight(weight) {
|
|
84
|
+
if (typeof weight === 'number') {
|
|
85
|
+
return weight.toString();
|
|
86
|
+
}
|
|
87
|
+
const normalized = weight.toLowerCase();
|
|
88
|
+
const weightMap = {
|
|
89
|
+
'normal': '400',
|
|
90
|
+
'bold': '700',
|
|
91
|
+
'lighter': '300',
|
|
92
|
+
'bolder': '900',
|
|
93
|
+
};
|
|
94
|
+
return weightMap[normalized] || normalized;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Parse margin/padding shorthand
|
|
98
|
+
* Supports: 1-4 values (CSS-like syntax)
|
|
99
|
+
* Returns: [top, right, bottom, left]
|
|
100
|
+
*/
|
|
101
|
+
export function parseSpacing(value) {
|
|
102
|
+
const parsed = parseSize(value);
|
|
103
|
+
return [parsed, parsed, parsed, parsed];
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Parse margin/padding individual values
|
|
107
|
+
*/
|
|
108
|
+
export function parseSpacingIndividual(top, right, bottom, left) {
|
|
109
|
+
return [
|
|
110
|
+
top !== undefined ? parseSize(top) : 0,
|
|
111
|
+
right !== undefined ? parseSize(right) : 0,
|
|
112
|
+
bottom !== undefined ? parseSize(bottom) : 0,
|
|
113
|
+
left !== undefined ? parseSize(left) : 0,
|
|
114
|
+
];
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Map React style props to GPUI style object
|
|
118
|
+
*/
|
|
119
|
+
export function mapStyleToProps(props) {
|
|
120
|
+
const result = {};
|
|
121
|
+
if (props.color) {
|
|
122
|
+
result.textColor = parseColor(props.color);
|
|
123
|
+
}
|
|
124
|
+
if (props.backgroundColor) {
|
|
125
|
+
result.bgColor = parseColor(props.backgroundColor);
|
|
126
|
+
}
|
|
127
|
+
if (props.borderColor) {
|
|
128
|
+
result.borderColor = parseColor(props.borderColor);
|
|
129
|
+
}
|
|
130
|
+
if (props.fontSize) {
|
|
131
|
+
result.textSize = parseSize(props.fontSize);
|
|
132
|
+
}
|
|
133
|
+
if (props.fontWeight) {
|
|
134
|
+
result.fontWeight = parseFontWeight(props.fontWeight);
|
|
135
|
+
}
|
|
136
|
+
if (props.width) {
|
|
137
|
+
result.width = parseSize(props.width);
|
|
138
|
+
}
|
|
139
|
+
if (props.height) {
|
|
140
|
+
result.height = parseSize(props.height);
|
|
141
|
+
}
|
|
142
|
+
if (props.margin !== undefined) {
|
|
143
|
+
const [mt, mr, mb, ml] = parseSpacing(props.margin);
|
|
144
|
+
result.marginTop = mt;
|
|
145
|
+
result.marginRight = mr;
|
|
146
|
+
result.marginBottom = mb;
|
|
147
|
+
result.marginLeft = ml;
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
if (props.marginTop !== undefined)
|
|
151
|
+
result.marginTop = parseSize(props.marginTop);
|
|
152
|
+
if (props.marginRight !== undefined)
|
|
153
|
+
result.marginRight = parseSize(props.marginRight);
|
|
154
|
+
if (props.marginBottom !== undefined)
|
|
155
|
+
result.marginBottom = parseSize(props.marginBottom);
|
|
156
|
+
if (props.marginLeft !== undefined)
|
|
157
|
+
result.marginLeft = parseSize(props.marginLeft);
|
|
158
|
+
}
|
|
159
|
+
if (props.padding !== undefined) {
|
|
160
|
+
const [pt, pr, pb, pl] = parseSpacing(props.padding);
|
|
161
|
+
result.paddingTop = pt;
|
|
162
|
+
result.paddingRight = pr;
|
|
163
|
+
result.paddingBottom = pb;
|
|
164
|
+
result.paddingLeft = pl;
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
if (props.paddingTop !== undefined)
|
|
168
|
+
result.paddingTop = parseSize(props.paddingTop);
|
|
169
|
+
if (props.paddingRight !== undefined)
|
|
170
|
+
result.paddingRight = parseSize(props.paddingRight);
|
|
171
|
+
if (props.paddingBottom !== undefined)
|
|
172
|
+
result.paddingBottom = parseSize(props.paddingBottom);
|
|
173
|
+
if (props.paddingLeft !== undefined)
|
|
174
|
+
result.paddingLeft = parseSize(props.paddingLeft);
|
|
175
|
+
}
|
|
176
|
+
if (props.display) {
|
|
177
|
+
result.display = props.display;
|
|
178
|
+
}
|
|
179
|
+
if (props.flexDirection) {
|
|
180
|
+
result.flexDirection = props.flexDirection;
|
|
181
|
+
}
|
|
182
|
+
if (props.justifyContent) {
|
|
183
|
+
result.justifyContent = props.justifyContent;
|
|
184
|
+
}
|
|
185
|
+
if (props.alignItems) {
|
|
186
|
+
result.alignItems = props.alignItems;
|
|
187
|
+
}
|
|
188
|
+
if (props.gap !== undefined) {
|
|
189
|
+
result.gap = parseSize(props.gap);
|
|
190
|
+
}
|
|
191
|
+
if (props.borderRadius) {
|
|
192
|
+
result.borderRadius = parseSize(props.borderRadius);
|
|
193
|
+
}
|
|
194
|
+
if (props.opacity !== undefined) {
|
|
195
|
+
result.opacity = Math.max(0, Math.min(1, props.opacity));
|
|
196
|
+
}
|
|
197
|
+
return result;
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Named color map (common colors only for MVP)
|
|
201
|
+
*/
|
|
202
|
+
const NAMED_COLORS = {
|
|
203
|
+
'black': 0x000000,
|
|
204
|
+
'white': 0xffffff,
|
|
205
|
+
'red': 0xff0000,
|
|
206
|
+
'green': 0x00ff00,
|
|
207
|
+
'blue': 0x0000ff,
|
|
208
|
+
'yellow': 0xffff00,
|
|
209
|
+
'cyan': 0x00ffff,
|
|
210
|
+
'magenta': 0xff00ff,
|
|
211
|
+
'gray': 0x808080,
|
|
212
|
+
'grey': 0x808080,
|
|
213
|
+
'transparent': 0x000000,
|
|
214
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "gpui-react",
|
|
3
|
+
"version": "0.1.7",
|
|
4
|
+
"module": "src/index.ts",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist/",
|
|
9
|
+
"src/native/"
|
|
10
|
+
],
|
|
11
|
+
"devDependencies": {
|
|
12
|
+
"@types/react": "^19.0.0",
|
|
13
|
+
"@types/react-reconciler": "^0.32.0",
|
|
14
|
+
"bun-types": "latest",
|
|
15
|
+
"typescript": "^5.9.3",
|
|
16
|
+
"react": ">=19.0.0"
|
|
17
|
+
},
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "bun run build:rust && bun run build:ts && bun run copy:native",
|
|
20
|
+
"build:rust": "cd rust && cargo build --release",
|
|
21
|
+
"build:ts": "tsc",
|
|
22
|
+
"copy:native": "node scripts/copy-native.js",
|
|
23
|
+
"build:all": "bun run build && echo 'Build complete for distribution'",
|
|
24
|
+
"release": "node scripts/release.js",
|
|
25
|
+
"release:dry-run": "node scripts/release.js patch --dry-run",
|
|
26
|
+
"release:status": "node scripts/release.js --status",
|
|
27
|
+
"demo": "bun run demo/index.ts",
|
|
28
|
+
"styled-demo": "bun run demo/styled-index.ts",
|
|
29
|
+
"flex-demo": "bun run demo/flex-index.ts",
|
|
30
|
+
"elements-demo": "bun run demo/elements-index.ts",
|
|
31
|
+
"event-demo": "bun run demo/event-index.ts",
|
|
32
|
+
"test": "bun run src/reconciler/__tests__/element-store.test.ts",
|
|
33
|
+
"dev": "bun run build:rust && bun run demo",
|
|
34
|
+
"postinstall": "node scripts/postinstall.js"
|
|
35
|
+
},
|
|
36
|
+
"type": "module",
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"react-reconciler": "^0.32.0"
|
|
39
|
+
},
|
|
40
|
+
"engines": {
|
|
41
|
+
"bun": ">=1.2.0",
|
|
42
|
+
"node": ">=18.0.0"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
Binary file
|