quickwin 2026.5.2-3.145209
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 +6 -0
- package/examples/pdf_preview.js +440 -0
- package/examples/pdf_preview.ts +470 -0
- package/examples/preact_demo.js +35 -0
- package/examples/preact_demo.tsx +49 -0
- package/examples/tray_demo.js +75 -0
- package/examples/tray_demo.tsx +79 -0
- package/lib/fetch.js +746 -0
- package/lib/fetch.ts +811 -0
- package/lib/polyfill.js +500 -0
- package/lib/polyfill.ts +454 -0
- package/lib/preact/hooks.js +287 -0
- package/lib/preact/hooks.ts +330 -0
- package/lib/preact/jsx-runtime.js +1 -0
- package/lib/preact/jsx-runtime.ts +2 -0
- package/lib/preact/jsx.d.ts +36 -0
- package/lib/preact/layout.js +153 -0
- package/lib/preact/layout.ts +183 -0
- package/lib/preact/preact.js +54 -0
- package/lib/preact/preact.ts +133 -0
- package/lib/preact/props.js +99 -0
- package/lib/preact/props.ts +119 -0
- package/lib/preact/render.js +320 -0
- package/lib/preact/render.ts +353 -0
- package/lib/websocket.js +540 -0
- package/lib/websocket.ts +574 -0
- package/package.json +32 -0
- package/quickwin.d.ts +657 -0
- package/test/add.wasm +0 -0
- package/test/complex.wasm +0 -0
- package/test/complex_imports.wasm +0 -0
- package/test/global_imports.wasm +0 -0
- package/test/import_func.wasm +0 -0
- package/test/imports.wasm +0 -0
- package/test/run.js +86 -0
- package/test/run.ts +90 -0
- package/test/sjlj.wasm +0 -0
- package/test/test_basic.js +7 -0
- package/test/test_basic.ts +9 -0
- package/test/test_brotli.js +48 -0
- package/test/test_brotli.ts +52 -0
- package/test/test_fetch_cache.js +131 -0
- package/test/test_fetch_cache.ts +141 -0
- package/test/test_ffi.js +157 -0
- package/test/test_ffi.ts +174 -0
- package/test/test_frame_encoding.js +128 -0
- package/test/test_frame_encoding.ts +132 -0
- package/test/test_helper.js +84 -0
- package/test/test_helper.ts +80 -0
- package/test/test_http_import.js +78 -0
- package/test/test_http_import.ts +74 -0
- package/test/test_mupdf_render.js +69 -0
- package/test/test_mupdf_render.ts +74 -0
- package/test/test_mupdf_twice.js +77 -0
- package/test/test_mupdf_twice.ts +81 -0
- package/test/test_mupdf_wasm.js +33 -0
- package/test/test_mupdf_wasm.ts +30 -0
- package/test/test_net_event.js +63 -0
- package/test/test_net_event.ts +59 -0
- package/test/test_net_fetch.js +153 -0
- package/test/test_net_fetch.ts +131 -0
- package/test/test_net_websocket.js +158 -0
- package/test/test_net_websocket.ts +144 -0
- package/test/test_polyfill.js +58 -0
- package/test/test_polyfill.ts +60 -0
- package/test/test_url.js +173 -0
- package/test/test_url.ts +183 -0
- package/test/test_wasm_basic.js +82 -0
- package/test/test_wasm_basic.ts +70 -0
- package/test/test_wasm_import_global.js +41 -0
- package/test/test_wasm_import_global.ts +39 -0
- package/test/test_wasm_sjlj.js +153 -0
- package/test/test_wasm_sjlj.ts +134 -0
- package/test/test_wasm_types.js +96 -0
- package/test/test_wasm_types.ts +108 -0
- package/test/types.wasm +0 -0
- package/tsconfig.json +18 -0
- package/vendor/mupdf-wasm/mupdf-wasm.d.ts +571 -0
- package/vendor/mupdf-wasm/mupdf-wasm.js +2749 -0
- package/vendor/mupdf-wasm/mupdf-wasm.wasm +0 -0
- package/vendor/mupdf-wasm/mupdf.d.ts +939 -0
- package/vendor/mupdf-wasm/mupdf.js +3317 -0
- package/win-mingw64.exe +0 -0
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import * as ffi from 'ffi';
|
|
2
|
+
import * as win from 'win';
|
|
3
|
+
import * as gui from 'gui';
|
|
4
|
+
import { HWND_PROP, STYLE_PROP, RENDERED_VNODE_PROP, isVNode, getChildren } from './render.js';
|
|
5
|
+
import { moveWindow } from './props.js';
|
|
6
|
+
const scaleFactor = gui.GetScaleFactor();
|
|
7
|
+
const FFI_PTR = ffi.FFI_TYPE_POINTER;
|
|
8
|
+
const FFI_S32 = ffi.FFI_TYPE_SINT32;
|
|
9
|
+
const FFI_U32 = ffi.FFI_TYPE_UINT32;
|
|
10
|
+
const FFI_U64 = ffi.FFI_TYPE_UINT64;
|
|
11
|
+
const _user32 = win.LoadLibrary('user32.dll');
|
|
12
|
+
const GetClientRect_proc = _user32 ? win.GetProcAddress(_user32, 'GetClientRect') : 0;
|
|
13
|
+
function getClientSize(hwnd) {
|
|
14
|
+
if (!GetClientRect_proc)
|
|
15
|
+
return { w: 0, h: 0 };
|
|
16
|
+
const rectBuf = new ArrayBuffer(16);
|
|
17
|
+
const ok = ffi.ffiCall(GetClientRect_proc, [FFI_U64, FFI_PTR], [hwnd, rectBuf], FFI_U32);
|
|
18
|
+
if (!ok)
|
|
19
|
+
return { w: 0, h: 0 };
|
|
20
|
+
const dv = new DataView(rectBuf);
|
|
21
|
+
return { w: dv.getInt32(8, true), h: dv.getInt32(12, true) };
|
|
22
|
+
}
|
|
23
|
+
function resolveSize(val, available) {
|
|
24
|
+
if (val === undefined)
|
|
25
|
+
return -1;
|
|
26
|
+
if (typeof val === 'number')
|
|
27
|
+
return Math.floor(val * scaleFactor);
|
|
28
|
+
if (typeof val === 'string' && val.endsWith('%')) {
|
|
29
|
+
const pct = parseFloat(val);
|
|
30
|
+
if (!isNaN(pct))
|
|
31
|
+
return Math.floor(available * pct / 100);
|
|
32
|
+
}
|
|
33
|
+
return -1;
|
|
34
|
+
}
|
|
35
|
+
const DEFAULT_SIZES = {
|
|
36
|
+
button: Math.floor(24 * scaleFactor), edit: Math.floor(24 * scaleFactor),
|
|
37
|
+
static: Math.floor(20 * scaleFactor), checkbox: Math.floor(24 * scaleFactor),
|
|
38
|
+
groupbox: Math.floor(48 * scaleFactor), combobox: Math.floor(200 * scaleFactor),
|
|
39
|
+
listbox: Math.floor(100 * scaleFactor), progressbar: Math.floor(24 * scaleFactor),
|
|
40
|
+
};
|
|
41
|
+
function getDefaultChildSize(vnode) {
|
|
42
|
+
const ctrlType = typeof vnode.props === 'object' ? vnode.props?.type : undefined;
|
|
43
|
+
return DEFAULT_SIZES[ctrlType] ?? Math.floor(30 * scaleFactor);
|
|
44
|
+
}
|
|
45
|
+
function getLayoutChildren(vnode) {
|
|
46
|
+
const result = [];
|
|
47
|
+
if (vnode.type !== 'w')
|
|
48
|
+
return result;
|
|
49
|
+
for (const child of getChildren(vnode)) {
|
|
50
|
+
if (!isVNode(child))
|
|
51
|
+
continue;
|
|
52
|
+
const childHwnd = child[HWND_PROP];
|
|
53
|
+
if (childHwnd) {
|
|
54
|
+
result.push({ hwnd: childHwnd, style: child[STYLE_PROP] || {}, vnode: child });
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return result;
|
|
58
|
+
}
|
|
59
|
+
function layoutNode(hwnd, vnode, availableRect) {
|
|
60
|
+
if (!isVNode(vnode) || vnode.type !== 'w')
|
|
61
|
+
return;
|
|
62
|
+
const style = vnode[STYLE_PROP] || {};
|
|
63
|
+
const dir = style.flexDirection || 'column';
|
|
64
|
+
const gap = Math.floor((style.gap || 0) * scaleFactor);
|
|
65
|
+
const padding = Math.floor((style.padding || 0) * scaleFactor);
|
|
66
|
+
const margin = Math.floor((style.margin || 0) * scaleFactor);
|
|
67
|
+
const fixedW = resolveSize(style.width, availableRect.w);
|
|
68
|
+
const fixedH = resolveSize(style.height, availableRect.h);
|
|
69
|
+
const nodeW = fixedW >= 0 ? fixedW : availableRect.w - margin * 2;
|
|
70
|
+
const nodeH = fixedH >= 0 ? fixedH : availableRect.h - margin * 2;
|
|
71
|
+
const nodeX = availableRect.x + margin;
|
|
72
|
+
const nodeY = availableRect.y + margin;
|
|
73
|
+
moveWindow(hwnd, nodeX, nodeY, nodeW, nodeH);
|
|
74
|
+
const children = getLayoutChildren(vnode);
|
|
75
|
+
if (children.length === 0)
|
|
76
|
+
return;
|
|
77
|
+
const isRow = dir === 'row';
|
|
78
|
+
const totalGap = gap * Math.max(0, children.length - 1);
|
|
79
|
+
const mainSize = (isRow ? nodeW : nodeH) - padding * 2 - totalGap;
|
|
80
|
+
const crossSize = (isRow ? nodeH : nodeW) - padding * 2;
|
|
81
|
+
let totalFlex = 0;
|
|
82
|
+
let fixedMainTotal = 0;
|
|
83
|
+
for (const child of children) {
|
|
84
|
+
const fg = child.style.flexGrow || 0;
|
|
85
|
+
const fixedMain = isRow
|
|
86
|
+
? resolveSize(child.style.width, mainSize)
|
|
87
|
+
: resolveSize(child.style.height, mainSize);
|
|
88
|
+
if (fg > 0) {
|
|
89
|
+
totalFlex += fg;
|
|
90
|
+
}
|
|
91
|
+
else if (fixedMain >= 0) {
|
|
92
|
+
fixedMainTotal += fixedMain;
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
fixedMainTotal += getDefaultChildSize(child.vnode);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
const flexUnit = totalFlex > 0 ? Math.max(0, mainSize - fixedMainTotal) / totalFlex : 0;
|
|
99
|
+
let offset = 0;
|
|
100
|
+
for (const child of children) {
|
|
101
|
+
const fg = child.style.flexGrow || 0;
|
|
102
|
+
let childMain;
|
|
103
|
+
if (fg > 0) {
|
|
104
|
+
childMain = Math.floor(fg * flexUnit);
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
const fixed = isRow
|
|
108
|
+
? resolveSize(child.style.width, mainSize)
|
|
109
|
+
: resolveSize(child.style.height, mainSize);
|
|
110
|
+
childMain = fixed >= 0 ? fixed : getDefaultChildSize(child.vnode);
|
|
111
|
+
}
|
|
112
|
+
const childCross = isRow
|
|
113
|
+
? resolveSize(child.style.height, crossSize)
|
|
114
|
+
: resolveSize(child.style.width, crossSize);
|
|
115
|
+
const actualCross = childCross >= 0 ? childCross : crossSize;
|
|
116
|
+
const childMargin = Math.floor((child.style.margin || 0) * scaleFactor);
|
|
117
|
+
const relX = isRow ? padding + offset : padding + childMargin;
|
|
118
|
+
const relY = isRow ? padding + childMargin : padding + offset;
|
|
119
|
+
const cw = isRow ? childMain : actualCross - childMargin * 2;
|
|
120
|
+
const ch = isRow ? actualCross - childMargin * 2 : childMain;
|
|
121
|
+
moveWindow(child.hwnd, relX, relY, Math.max(cw, 0), Math.max(ch, 0));
|
|
122
|
+
if (isVNode(child.vnode) && child.vnode.type === 'w') {
|
|
123
|
+
layoutNode(child.hwnd, child.vnode, { x: relX, y: relY, w: Math.max(cw, 0), h: Math.max(ch, 0) });
|
|
124
|
+
}
|
|
125
|
+
offset += childMain + gap;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
function resolveToElementVNode(vnode) {
|
|
129
|
+
if (!isVNode(vnode))
|
|
130
|
+
return null;
|
|
131
|
+
if (vnode.type === 'w')
|
|
132
|
+
return vnode;
|
|
133
|
+
if (typeof vnode.type === 'function') {
|
|
134
|
+
const rendered = vnode[RENDERED_VNODE_PROP];
|
|
135
|
+
if (rendered && isVNode(rendered)) {
|
|
136
|
+
return resolveToElementVNode(rendered);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
export function layout(rootHwnd, vnode) {
|
|
142
|
+
const clientSize = getClientSize(rootHwnd);
|
|
143
|
+
if (clientSize.w <= 0 || clientSize.h <= 0)
|
|
144
|
+
return;
|
|
145
|
+
const targetVNode = resolveToElementVNode(vnode);
|
|
146
|
+
if (!targetVNode)
|
|
147
|
+
return;
|
|
148
|
+
const targetHwnd = targetVNode[HWND_PROP];
|
|
149
|
+
if (!targetHwnd)
|
|
150
|
+
return;
|
|
151
|
+
moveWindow(targetHwnd, 0, 0, clientSize.w, clientSize.h);
|
|
152
|
+
layoutNode(targetHwnd, targetVNode, { x: 0, y: 0, w: clientSize.w, h: clientSize.h });
|
|
153
|
+
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import * as ffi from 'ffi'
|
|
2
|
+
import * as win from 'win'
|
|
3
|
+
import * as gui from 'gui'
|
|
4
|
+
import { HWND_PROP, STYLE_PROP, CHILDREN_HWNDS_PROP, RENDERED_VNODE_PROP, isVNode, getChildren, type VNode } from './render.js'
|
|
5
|
+
import { moveWindow } from './props.js'
|
|
6
|
+
|
|
7
|
+
const scaleFactor = gui.GetScaleFactor()
|
|
8
|
+
|
|
9
|
+
const FFI_PTR = ffi.FFI_TYPE_POINTER
|
|
10
|
+
const FFI_S32 = ffi.FFI_TYPE_SINT32
|
|
11
|
+
const FFI_U32 = ffi.FFI_TYPE_UINT32
|
|
12
|
+
const FFI_U64 = ffi.FFI_TYPE_UINT64
|
|
13
|
+
|
|
14
|
+
const _user32 = win.LoadLibrary('user32.dll')
|
|
15
|
+
const GetClientRect_proc = _user32 ? win.GetProcAddress(_user32, 'GetClientRect') : 0
|
|
16
|
+
|
|
17
|
+
export interface LayoutStyle {
|
|
18
|
+
flexDirection?: 'row' | 'column'
|
|
19
|
+
justifyContent?: string
|
|
20
|
+
alignItems?: string
|
|
21
|
+
flexGrow?: number
|
|
22
|
+
width?: number | string
|
|
23
|
+
height?: number | string
|
|
24
|
+
padding?: number
|
|
25
|
+
margin?: number
|
|
26
|
+
gap?: number
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface LayoutRect {
|
|
30
|
+
x: number
|
|
31
|
+
y: number
|
|
32
|
+
w: number
|
|
33
|
+
h: number
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function getClientSize(hwnd: number): { w: number; h: number } {
|
|
37
|
+
if (!GetClientRect_proc) return { w: 0, h: 0 }
|
|
38
|
+
const rectBuf = new ArrayBuffer(16)
|
|
39
|
+
const ok = ffi.ffiCall(GetClientRect_proc, [FFI_U64, FFI_PTR], [hwnd, rectBuf], FFI_U32) as number
|
|
40
|
+
if (!ok) return { w: 0, h: 0 }
|
|
41
|
+
const dv = new DataView(rectBuf)
|
|
42
|
+
return { w: dv.getInt32(8, true), h: dv.getInt32(12, true) }
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function resolveSize(val: number | string | undefined, available: number): number {
|
|
46
|
+
if (val === undefined) return -1
|
|
47
|
+
if (typeof val === 'number') return Math.floor(val * scaleFactor)
|
|
48
|
+
if (typeof val === 'string' && val.endsWith('%')) {
|
|
49
|
+
const pct = parseFloat(val)
|
|
50
|
+
if (!isNaN(pct)) return Math.floor(available * pct / 100)
|
|
51
|
+
}
|
|
52
|
+
return -1
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const DEFAULT_SIZES: Record<string, number> = {
|
|
56
|
+
button: Math.floor(24 * scaleFactor), edit: Math.floor(24 * scaleFactor),
|
|
57
|
+
static: Math.floor(20 * scaleFactor), checkbox: Math.floor(24 * scaleFactor),
|
|
58
|
+
groupbox: Math.floor(48 * scaleFactor), combobox: Math.floor(200 * scaleFactor),
|
|
59
|
+
listbox: Math.floor(100 * scaleFactor), progressbar: Math.floor(24 * scaleFactor),
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function getDefaultChildSize(vnode: VNode): number {
|
|
63
|
+
const ctrlType = typeof vnode.props === 'object' ? vnode.props?.type : undefined
|
|
64
|
+
return DEFAULT_SIZES[ctrlType as string] ?? Math.floor(30 * scaleFactor)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function getLayoutChildren(vnode: VNode): { hwnd: number; style: LayoutStyle; vnode: VNode }[] {
|
|
68
|
+
const result: { hwnd: number; style: LayoutStyle; vnode: VNode }[] = []
|
|
69
|
+
if (vnode.type !== 'w') return result
|
|
70
|
+
|
|
71
|
+
for (const child of getChildren(vnode)) {
|
|
72
|
+
if (!isVNode(child)) continue
|
|
73
|
+
const childHwnd = child[HWND_PROP] as number | undefined
|
|
74
|
+
if (childHwnd) {
|
|
75
|
+
result.push({ hwnd: childHwnd, style: (child[STYLE_PROP] as LayoutStyle) || {}, vnode: child })
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return result
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function layoutNode(hwnd: number, vnode: VNode, availableRect: LayoutRect): void {
|
|
82
|
+
if (!isVNode(vnode) || vnode.type !== 'w') return
|
|
83
|
+
|
|
84
|
+
const style = (vnode[STYLE_PROP] as LayoutStyle) || {}
|
|
85
|
+
const dir = style.flexDirection || 'column'
|
|
86
|
+
const gap = Math.floor((style.gap || 0) * scaleFactor)
|
|
87
|
+
const padding = Math.floor((style.padding || 0) * scaleFactor)
|
|
88
|
+
const margin = Math.floor((style.margin || 0) * scaleFactor)
|
|
89
|
+
|
|
90
|
+
const fixedW = resolveSize(style.width, availableRect.w)
|
|
91
|
+
const fixedH = resolveSize(style.height, availableRect.h)
|
|
92
|
+
const nodeW = fixedW >= 0 ? fixedW : availableRect.w - margin * 2
|
|
93
|
+
const nodeH = fixedH >= 0 ? fixedH : availableRect.h - margin * 2
|
|
94
|
+
|
|
95
|
+
const nodeX = availableRect.x + margin
|
|
96
|
+
const nodeY = availableRect.y + margin
|
|
97
|
+
moveWindow(hwnd, nodeX, nodeY, nodeW, nodeH)
|
|
98
|
+
|
|
99
|
+
const children = getLayoutChildren(vnode)
|
|
100
|
+
if (children.length === 0) return
|
|
101
|
+
|
|
102
|
+
const isRow = dir === 'row'
|
|
103
|
+
const totalGap = gap * Math.max(0, children.length - 1)
|
|
104
|
+
const mainSize = (isRow ? nodeW : nodeH) - padding * 2 - totalGap
|
|
105
|
+
const crossSize = (isRow ? nodeH : nodeW) - padding * 2
|
|
106
|
+
|
|
107
|
+
let totalFlex = 0
|
|
108
|
+
let fixedMainTotal = 0
|
|
109
|
+
for (const child of children) {
|
|
110
|
+
const fg = child.style.flexGrow || 0
|
|
111
|
+
const fixedMain = isRow
|
|
112
|
+
? resolveSize(child.style.width, mainSize)
|
|
113
|
+
: resolveSize(child.style.height, mainSize)
|
|
114
|
+
if (fg > 0) {
|
|
115
|
+
totalFlex += fg
|
|
116
|
+
} else if (fixedMain >= 0) {
|
|
117
|
+
fixedMainTotal += fixedMain
|
|
118
|
+
} else {
|
|
119
|
+
fixedMainTotal += getDefaultChildSize(child.vnode)
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const flexUnit = totalFlex > 0 ? Math.max(0, mainSize - fixedMainTotal) / totalFlex : 0
|
|
124
|
+
|
|
125
|
+
let offset = 0
|
|
126
|
+
for (const child of children) {
|
|
127
|
+
const fg = child.style.flexGrow || 0
|
|
128
|
+
let childMain: number
|
|
129
|
+
if (fg > 0) {
|
|
130
|
+
childMain = Math.floor(fg * flexUnit)
|
|
131
|
+
} else {
|
|
132
|
+
const fixed = isRow
|
|
133
|
+
? resolveSize(child.style.width, mainSize)
|
|
134
|
+
: resolveSize(child.style.height, mainSize)
|
|
135
|
+
childMain = fixed >= 0 ? fixed : getDefaultChildSize(child.vnode)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const childCross = isRow
|
|
139
|
+
? resolveSize(child.style.height, crossSize)
|
|
140
|
+
: resolveSize(child.style.width, crossSize)
|
|
141
|
+
const actualCross = childCross >= 0 ? childCross : crossSize
|
|
142
|
+
|
|
143
|
+
const childMargin = Math.floor((child.style.margin || 0) * scaleFactor)
|
|
144
|
+
const relX = isRow ? padding + offset : padding + childMargin
|
|
145
|
+
const relY = isRow ? padding + childMargin : padding + offset
|
|
146
|
+
const cw = isRow ? childMain : actualCross - childMargin * 2
|
|
147
|
+
const ch = isRow ? actualCross - childMargin * 2 : childMain
|
|
148
|
+
|
|
149
|
+
moveWindow(child.hwnd, relX, relY, Math.max(cw, 0), Math.max(ch, 0))
|
|
150
|
+
|
|
151
|
+
if (isVNode(child.vnode) && child.vnode.type === 'w') {
|
|
152
|
+
layoutNode(child.hwnd, child.vnode, { x: relX, y: relY, w: Math.max(cw, 0), h: Math.max(ch, 0) })
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
offset += childMain + gap
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function resolveToElementVNode(vnode: VNode): VNode | null {
|
|
160
|
+
if (!isVNode(vnode)) return null
|
|
161
|
+
if (vnode.type === 'w') return vnode
|
|
162
|
+
if (typeof vnode.type === 'function') {
|
|
163
|
+
const rendered = vnode[RENDERED_VNODE_PROP]
|
|
164
|
+
if (rendered && isVNode(rendered)) {
|
|
165
|
+
return resolveToElementVNode(rendered)
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return null
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export function layout(rootHwnd: number, vnode: any): void {
|
|
172
|
+
const clientSize = getClientSize(rootHwnd)
|
|
173
|
+
if (clientSize.w <= 0 || clientSize.h <= 0) return
|
|
174
|
+
|
|
175
|
+
const targetVNode = resolveToElementVNode(vnode)
|
|
176
|
+
if (!targetVNode) return
|
|
177
|
+
|
|
178
|
+
const targetHwnd = targetVNode[HWND_PROP] as number
|
|
179
|
+
if (!targetHwnd) return
|
|
180
|
+
|
|
181
|
+
moveWindow(targetHwnd, 0, 0, clientSize.w, clientSize.h)
|
|
182
|
+
layoutNode(targetHwnd, targetVNode, { x: 0, y: 0, w: clientSize.w, h: clientSize.h })
|
|
183
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// Minimal Preact - VNode creation + Options
|
|
2
|
+
// Based on https://github.com/preactjs/preact (src/create-element.js, src/options.js, src/constants.js)
|
|
3
|
+
// DOM dependencies removed - for custom renderers only
|
|
4
|
+
let vnodeId = 0;
|
|
5
|
+
export const options = {};
|
|
6
|
+
export function createElement(type, props, ...children) {
|
|
7
|
+
let normalizedProps = {};
|
|
8
|
+
let key = null;
|
|
9
|
+
let ref = null;
|
|
10
|
+
if (props != null) {
|
|
11
|
+
for (const i in props) {
|
|
12
|
+
if (i === 'key')
|
|
13
|
+
key = props[i] ?? null;
|
|
14
|
+
else if (i === 'ref' && typeof type !== 'function')
|
|
15
|
+
ref = props[i] ?? null;
|
|
16
|
+
else
|
|
17
|
+
normalizedProps[i] = props[i];
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
if (children.length > 0) {
|
|
21
|
+
normalizedProps.children = children.length === 1 ? children[0] : children;
|
|
22
|
+
}
|
|
23
|
+
return createVNode(type, normalizedProps, key, ref, null);
|
|
24
|
+
}
|
|
25
|
+
export function createVNode(type, props, key, ref, original) {
|
|
26
|
+
const vnode = {
|
|
27
|
+
type,
|
|
28
|
+
props: props,
|
|
29
|
+
key,
|
|
30
|
+
ref,
|
|
31
|
+
_children: [],
|
|
32
|
+
_parent: null,
|
|
33
|
+
_depth: 0,
|
|
34
|
+
_dom: null,
|
|
35
|
+
_component: null,
|
|
36
|
+
constructor: undefined,
|
|
37
|
+
_original: original == null ? ++vnodeId : original,
|
|
38
|
+
_index: -1,
|
|
39
|
+
_flags: 0,
|
|
40
|
+
};
|
|
41
|
+
if (original == null && options.vnode)
|
|
42
|
+
options.vnode(vnode);
|
|
43
|
+
return vnode;
|
|
44
|
+
}
|
|
45
|
+
export function createRef() {
|
|
46
|
+
return { current: null };
|
|
47
|
+
}
|
|
48
|
+
export function Fragment(props) {
|
|
49
|
+
return props.children;
|
|
50
|
+
}
|
|
51
|
+
export function isValidElement(vnode) {
|
|
52
|
+
return vnode != null && vnode.constructor === undefined;
|
|
53
|
+
}
|
|
54
|
+
export const h = createElement;
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
// Minimal Preact - VNode creation + Options
|
|
2
|
+
// Based on https://github.com/preactjs/preact (src/create-element.js, src/options.js, src/constants.js)
|
|
3
|
+
// DOM dependencies removed - for custom renderers only
|
|
4
|
+
|
|
5
|
+
let vnodeId = 0
|
|
6
|
+
|
|
7
|
+
export type Key = string | number | null
|
|
8
|
+
|
|
9
|
+
export interface Ref<T = any> {
|
|
10
|
+
current: T | null
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type ComponentChild = VNode<any> | string | number | boolean | null | undefined
|
|
14
|
+
export type ComponentChildren = ComponentChild[] | ComponentChild
|
|
15
|
+
|
|
16
|
+
export type FunctionComponent<P = {}> = {
|
|
17
|
+
(props: P, context?: any): VNode<any> | null
|
|
18
|
+
defaultProps?: Partial<P>
|
|
19
|
+
displayName?: string
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export type ComponentType<P = {}> = FunctionComponent<P>
|
|
23
|
+
|
|
24
|
+
export interface VNode<P = any> {
|
|
25
|
+
type: string | ComponentType<P>
|
|
26
|
+
props: P & { children?: ComponentChildren }
|
|
27
|
+
key: Key
|
|
28
|
+
ref: Ref<any>
|
|
29
|
+
_children: VNode[]
|
|
30
|
+
_parent: VNode | null
|
|
31
|
+
_depth: number
|
|
32
|
+
_dom: unknown
|
|
33
|
+
_component: Component<{}, {}> | null
|
|
34
|
+
constructor: undefined
|
|
35
|
+
_original: number
|
|
36
|
+
_index: number
|
|
37
|
+
_flags: number
|
|
38
|
+
_mask?: number[]
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface Component<P = {}, S = {}> {
|
|
42
|
+
props: P
|
|
43
|
+
context: Record<string, unknown>
|
|
44
|
+
_vnode: VNode | null
|
|
45
|
+
_renderCallbacks: Array<() => void>
|
|
46
|
+
_parentDom: unknown
|
|
47
|
+
__hooks: { _list: any[]; _pendingEffects: any[] } | null
|
|
48
|
+
_forceUpdate(): void
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface Options {
|
|
52
|
+
vnode?(vnode: VNode): void
|
|
53
|
+
_diff?(vnode: VNode): void
|
|
54
|
+
_render?(vnode: VNode): void
|
|
55
|
+
diffed?(vnode: VNode): void
|
|
56
|
+
_commit?(vnode: VNode, commitQueue: Component[]): void
|
|
57
|
+
unmount?(vnode: VNode): void
|
|
58
|
+
_root?(vnode: VNode, parentDom: unknown): void
|
|
59
|
+
_catchError?(error: unknown, vnode: VNode, oldVNode?: VNode): unknown
|
|
60
|
+
debounceRendering?(cb: () => void): void
|
|
61
|
+
requestAnimationFrame?: ((cb: () => void) => void) | null
|
|
62
|
+
useDebugValue?(value: unknown): void
|
|
63
|
+
_hook?(component: Component, index: number, type: number): void
|
|
64
|
+
_skipEffects?: boolean
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export const options: Options = {}
|
|
68
|
+
|
|
69
|
+
export function createElement<P extends Record<string, any> = {}>(
|
|
70
|
+
type: string | ComponentType<P>,
|
|
71
|
+
props: (P & { key?: Key; ref?: Ref }) | null,
|
|
72
|
+
...children: ComponentChildren[]
|
|
73
|
+
): VNode<P> {
|
|
74
|
+
let normalizedProps: Record<string, any> = {}
|
|
75
|
+
let key: Key = null
|
|
76
|
+
let ref: Ref | null = null
|
|
77
|
+
|
|
78
|
+
if (props != null) {
|
|
79
|
+
for (const i in props) {
|
|
80
|
+
if (i === 'key') key = props[i] ?? null
|
|
81
|
+
else if (i === 'ref' && typeof type !== 'function') ref = props[i] ?? null
|
|
82
|
+
else normalizedProps[i] = props[i]
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (children.length > 0) {
|
|
87
|
+
normalizedProps.children = children.length === 1 ? children[0] : children
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return createVNode(type, normalizedProps as P, key, ref!, null)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function createVNode<P = {}>(
|
|
94
|
+
type: string | ComponentType<P>,
|
|
95
|
+
props: P,
|
|
96
|
+
key: Key,
|
|
97
|
+
ref: Ref,
|
|
98
|
+
original: number | null
|
|
99
|
+
): VNode<P> {
|
|
100
|
+
const vnode: VNode<P> = {
|
|
101
|
+
type,
|
|
102
|
+
props: props as VNode<P>['props'],
|
|
103
|
+
key,
|
|
104
|
+
ref,
|
|
105
|
+
_children: [],
|
|
106
|
+
_parent: null,
|
|
107
|
+
_depth: 0,
|
|
108
|
+
_dom: null,
|
|
109
|
+
_component: null,
|
|
110
|
+
constructor: undefined,
|
|
111
|
+
_original: original == null ? ++vnodeId : original,
|
|
112
|
+
_index: -1,
|
|
113
|
+
_flags: 0,
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (original == null && options.vnode) options.vnode(vnode)
|
|
117
|
+
|
|
118
|
+
return vnode
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function createRef<T = null>(): Ref<T> {
|
|
122
|
+
return { current: null }
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function Fragment<P extends { children?: ComponentChildren }>(props: P): ComponentChildren {
|
|
126
|
+
return props.children
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function isValidElement(vnode: unknown): vnode is VNode {
|
|
130
|
+
return vnode != null && (vnode as any).constructor === undefined
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export const h = createElement
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import * as gui from 'gui';
|
|
2
|
+
import * as ffi from 'ffi';
|
|
3
|
+
import * as win from 'win';
|
|
4
|
+
const FFI_PTR = ffi.FFI_TYPE_POINTER;
|
|
5
|
+
const FFI_S32 = ffi.FFI_TYPE_SINT32;
|
|
6
|
+
const FFI_U32 = ffi.FFI_TYPE_UINT32;
|
|
7
|
+
const FFI_U64 = ffi.FFI_TYPE_UINT64;
|
|
8
|
+
const _user32 = win.LoadLibrary('user32.dll');
|
|
9
|
+
const MoveWindow_proc = _user32 ? win.GetProcAddress(_user32, 'MoveWindow') : 0;
|
|
10
|
+
const EnableWindow_proc = _user32 ? win.GetProcAddress(_user32, 'EnableWindow') : 0;
|
|
11
|
+
const ShowWindow_proc = _user32 ? win.GetProcAddress(_user32, 'ShowWindow') : 0;
|
|
12
|
+
const SendMessageW_proc = _user32 ? win.GetProcAddress(_user32, 'SendMessageW') : 0;
|
|
13
|
+
const DestroyWindow_proc = _user32 ? win.GetProcAddress(_user32, 'DestroyWindow') : 0;
|
|
14
|
+
const SW_HIDE = 0 /* gui.ShowWindowCmd.HIDE */;
|
|
15
|
+
const SW_SHOW = 5 /* gui.ShowWindowCmd.SHOW */;
|
|
16
|
+
const BM_GETCHECK = 240 /* gui.ButtonMsg.GETCHECK */;
|
|
17
|
+
const BM_SETCHECK = 241 /* gui.ButtonMsg.SETCHECK */;
|
|
18
|
+
const BST_CHECKED = 1 /* gui.ButtonCheckState.CHECKED */;
|
|
19
|
+
const BST_UNCHECKED = 0 /* gui.ButtonCheckState.UNCHECKED */;
|
|
20
|
+
const EM_SETCUEBANNER = 5377 /* gui.EditMsg.SETCUEBANNER */;
|
|
21
|
+
const EM_SETPASSWORDCHAR = 204 /* gui.EditMsg.SETPASSWORDCHAR */;
|
|
22
|
+
const CB_ADDSTRING = 323 /* gui.ComboBoxMsg.ADDSTRING */;
|
|
23
|
+
const LB_ADDSTRING = 384 /* gui.LbMsg.ADDSTRING */;
|
|
24
|
+
const PBM_SETRANGE32 = 1030 /* gui.ProgressMsg.SETRANGE32 */;
|
|
25
|
+
const PBM_SETPOS = 1026 /* gui.ProgressMsg.SETPOS */;
|
|
26
|
+
export function applyProps(hwnd, props, vnode) {
|
|
27
|
+
if (props.text !== undefined) {
|
|
28
|
+
gui.SetWindowText(hwnd, props.text);
|
|
29
|
+
}
|
|
30
|
+
if (props.value !== undefined) {
|
|
31
|
+
gui.SetWindowText(hwnd, props.value);
|
|
32
|
+
}
|
|
33
|
+
if (props.disabled !== undefined && EnableWindow_proc) {
|
|
34
|
+
ffi.ffiCall(EnableWindow_proc, [FFI_U64, FFI_U32], [hwnd, props.disabled ? 0 : 1], FFI_U32);
|
|
35
|
+
}
|
|
36
|
+
if (props.visible !== undefined && ShowWindow_proc) {
|
|
37
|
+
ffi.ffiCall(ShowWindow_proc, [FFI_U64, FFI_S32], [hwnd, props.visible ? SW_SHOW : SW_HIDE], FFI_U32);
|
|
38
|
+
}
|
|
39
|
+
if (props.onEvent !== undefined && vnode) {
|
|
40
|
+
const h = hwnd;
|
|
41
|
+
const oldProc = vnode._oldProc || gui.GetWindowLongPtr(h, -4 /* gui.Gwlp.WNDPROC */);
|
|
42
|
+
vnode._oldProc = oldProc;
|
|
43
|
+
gui.SetWindowProc(h, (hw, msg, wParam, lParam) => {
|
|
44
|
+
const cb = vnode.props?.onEvent;
|
|
45
|
+
if (cb)
|
|
46
|
+
cb({ hwnd: hw, msg, wParam, lParam });
|
|
47
|
+
return gui.CallWindowProc(oldProc, hw, msg, wParam, lParam);
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
if (props.placeholder !== undefined && SendMessageW_proc) {
|
|
51
|
+
ffi.ffiCall(SendMessageW_proc, [FFI_U64, FFI_S32, FFI_U32, FFI_PTR], [hwnd, EM_SETCUEBANNER, 0, strToWide(props.placeholder)], FFI_U64);
|
|
52
|
+
}
|
|
53
|
+
if (props.password !== undefined && SendMessageW_proc) {
|
|
54
|
+
ffi.ffiCall(SendMessageW_proc, [FFI_U64, FFI_U64, FFI_U64, FFI_U64], [hwnd, EM_SETPASSWORDCHAR, props.password ? 42 : 0, 0], FFI_U64);
|
|
55
|
+
}
|
|
56
|
+
if (props.checked !== undefined && SendMessageW_proc) {
|
|
57
|
+
ffi.ffiCall(SendMessageW_proc, [FFI_U64, FFI_U64, FFI_U64, FFI_U64], [hwnd, BM_SETCHECK, props.checked ? BST_CHECKED : BST_UNCHECKED, 0], FFI_U64);
|
|
58
|
+
}
|
|
59
|
+
if (props.items && SendMessageW_proc) {
|
|
60
|
+
const msg = props.type === 'combobox' ? CB_ADDSTRING : LB_ADDSTRING;
|
|
61
|
+
for (const item of props.items) {
|
|
62
|
+
ffi.ffiCall(SendMessageW_proc, [FFI_U64, FFI_S32, FFI_U32, FFI_PTR], [hwnd, msg, 0, strToWide(item)], FFI_U64);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (props.max !== undefined && SendMessageW_proc) {
|
|
66
|
+
ffi.ffiCall(SendMessageW_proc, [FFI_U64, FFI_U64, FFI_U64, FFI_U64], [hwnd, PBM_SETRANGE32, 0, props.max], FFI_U64);
|
|
67
|
+
}
|
|
68
|
+
if (props.value !== undefined && props.type === 'progressbar' && SendMessageW_proc) {
|
|
69
|
+
ffi.ffiCall(SendMessageW_proc, [FFI_U64, FFI_U64, FFI_U64, FFI_U64], [hwnd, PBM_SETPOS, Number(props.value), 0], FFI_U64);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
const wideCache = new Map();
|
|
73
|
+
const MAX_WIDE_CACHE = 50;
|
|
74
|
+
function strToWide(str) {
|
|
75
|
+
const cached = wideCache.get(str);
|
|
76
|
+
if (cached)
|
|
77
|
+
return cached;
|
|
78
|
+
const buf = new ArrayBuffer((str.length + 1) * 2);
|
|
79
|
+
const dv = new DataView(buf);
|
|
80
|
+
for (let i = 0; i < str.length; i++)
|
|
81
|
+
dv.setUint16(i * 2, str.charCodeAt(i), true);
|
|
82
|
+
if (wideCache.size >= MAX_WIDE_CACHE) {
|
|
83
|
+
const key = wideCache.keys().next().value;
|
|
84
|
+
if (key !== undefined)
|
|
85
|
+
wideCache.delete(key);
|
|
86
|
+
}
|
|
87
|
+
wideCache.set(str, buf);
|
|
88
|
+
return buf;
|
|
89
|
+
}
|
|
90
|
+
export function moveWindow(hwnd, x, y, w, h) {
|
|
91
|
+
if (!MoveWindow_proc)
|
|
92
|
+
return;
|
|
93
|
+
ffi.ffiCall(MoveWindow_proc, [FFI_U64, FFI_S32, FFI_S32, FFI_S32, FFI_S32, FFI_U32], [hwnd, x, y, w, h, 1], FFI_U32);
|
|
94
|
+
}
|
|
95
|
+
export function destroyWindow(hwnd) {
|
|
96
|
+
if (!DestroyWindow_proc)
|
|
97
|
+
return false;
|
|
98
|
+
return !!ffi.ffiCall(DestroyWindow_proc, [FFI_U64], [hwnd], FFI_U32);
|
|
99
|
+
}
|