quickwin 1.2.1 → 2.0.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.en.md +3 -3
- package/README.md +21 -24
- package/examples/PdfCanvas.js +8 -37
- package/examples/PdfCanvas.tsx +11 -41
- package/examples/exec_server.js +20054 -1423
- package/examples/exec_server.tsx +489 -0
- package/examples/exec_server_worker.js +383 -33
- package/examples/exec_server_worker.ts +408 -30
- package/examples/pdf_preview2.js +31 -73
- package/examples/pdf_preview2.ts +33 -76
- package/examples/pdf_viewer.js +1496 -616
- package/examples/pdf_viewer.tsx +23 -64
- package/examples/setres.js +22 -47
- package/examples/setres.ts +23 -57
- package/examples/test_gallery.js +1491 -525
- package/examples/test_react_lazy.js +1455 -500
- package/examples/test_react_listbox.js +1454 -499
- package/examples/test_root_window.js +1450 -495
- package/examples/test_tab.js +1455 -500
- package/lib/abort.js +70 -0
- package/lib/abort.ts +69 -0
- package/lib/certs.js +214 -0
- package/lib/certs.ts +224 -0
- package/lib/exec-log.js +15 -0
- package/lib/exec-log.ts +30 -0
- package/lib/fetch.js +37 -4
- package/lib/fetch.ts +35 -4
- package/lib/ffi/bind.js +352 -0
- package/lib/ffi/bind.ts +442 -0
- package/lib/ffi/ctype.js +168 -0
- package/lib/ffi/ctype.ts +331 -0
- package/lib/ffi/index.js +6 -0
- package/lib/ffi/index.ts +6 -0
- package/lib/ffi/struct.js +412 -0
- package/lib/ffi/struct.ts +664 -0
- package/lib/http-common.js +43 -5
- package/lib/http-common.ts +48 -6
- package/lib/http-server.js +187 -29
- package/lib/http-server.ts +177 -26
- package/lib/react-qw/components/DateTimePicker.js +20 -33
- package/lib/react-qw/components/Input.js +3 -10
- package/lib/react-qw/components/Link.js +7 -22
- package/lib/react-qw/components/ListView.js +80 -141
- package/lib/react-qw/components/PathPicker.js +36 -80
- package/lib/react-qw/components/ScrollView.js +15 -8
- package/lib/react-qw/components/Slider.js +2 -1
- package/lib/react-qw/components/Tab.js +15 -20
- package/lib/react-qw/components/Tooltip.js +21 -20
- package/lib/react-qw/components/TreeView.js +14 -47
- package/lib/react-qw/index.js +4 -2
- package/lib/react-qw/index.ts +4 -2
- package/lib/react-qw/nmhdr.js +5 -0
- package/lib/react-qw/nmhdr.ts +7 -0
- package/lib/react-qw/props.js +7 -2
- package/lib/react-qw/props.ts +7 -3
- package/lib/react-qw/reconciler.js +23 -13
- package/lib/react-qw/reconciler.ts +22 -13
- package/lib/text-measure.js +18 -35
- package/lib/text-measure.ts +20 -37
- package/lib/websocket.js +12 -2
- package/lib/websocket.ts +15 -2
- package/lib/windows/comctl32.js +18 -0
- package/lib/windows/comctl32.ts +20 -0
- package/lib/windows/comdlg32.js +7 -0
- package/lib/windows/comdlg32.ts +9 -0
- package/lib/windows/gdi32.js +90 -0
- package/lib/windows/gdi32.ts +97 -0
- package/lib/windows/kernel32.js +66 -0
- package/lib/windows/kernel32.ts +76 -0
- package/lib/windows/ntdll.js +8 -0
- package/lib/windows/ntdll.ts +10 -0
- package/lib/windows/ole32.js +7 -0
- package/lib/windows/ole32.ts +9 -0
- package/lib/windows/shell32.js +11 -0
- package/lib/windows/shell32.ts +14 -0
- package/lib/windows/structs.js +384 -0
- package/lib/windows/structs.ts +422 -0
- package/lib/windows/user32.js +469 -0
- package/lib/windows/user32.ts +502 -0
- package/package.json +1 -1
- package/quickwin.d.ts +22 -33
- package/quickwin_const.d.ts +1 -0
- package/qwin-nowasm-x86.exe +0 -0
- package/qwin-nowasm.exe +0 -0
- package/qwin-x86.exe +0 -0
- package/qwin.exe +0 -0
- package/test/bench_ffi_callraw.js +236 -0
- package/test/bench_ffi_callraw.ts +236 -0
- package/test/bench_ffi_overhead.js +139 -0
- package/test/bench_ffi_overhead.ts +141 -0
- package/test/bench_sendmessage.js +104 -0
- package/test/bench_sendmessage.ts +100 -0
- package/test/bench_sendmessage_nonnull.js +187 -0
- package/test/bench_sendmessage_nonnull.ts +188 -0
- package/test/run.js +14 -4
- package/test/run.ts +15 -5
- package/test/test_basic_fetch.js +8 -0
- package/test/test_basic_fetch.ts +9 -0
- package/test/test_ffi.js +33 -47
- package/test/test_ffi.ts +41 -60
- package/test/test_ffi_abi.js +235 -0
- package/test/test_ffi_abi.ts +218 -0
- package/test/test_ffi_bind.js +572 -0
- package/test/test_ffi_bind.ts +597 -0
- package/test/test_ffi_struct.js +745 -0
- package/test/test_ffi_struct.ts +798 -0
- package/test/test_ffi_struct_layout.js +495 -0
- package/test/test_ffi_struct_layout.ts +543 -0
- package/test/test_ffi_user32.js +103 -0
- package/test/test_ffi_user32.ts +113 -0
- package/test/test_ffi_windows.js +101 -0
- package/test/test_ffi_windows.ts +113 -0
- package/test/test_helper.js +8 -1
- package/test/test_helper.ts +9 -1
- package/test/test_http_import.js +18 -0
- package/test/test_http_import.ts +19 -0
- package/test/test_http_server.js +73 -1
- package/test/test_http_server.ts +75 -1
- package/test/test_ipv6.js +2 -2
- package/test/test_ipv6.ts +2 -2
- package/test/test_net_fetch.js +70 -8
- package/test/test_net_fetch.ts +69 -7
- package/test/test_net_websocket.js +1 -1
- package/test/test_net_websocket.ts +1 -1
- package/test/test_react_complex.js +1465 -510
- package/test/test_react_counter.js +1455 -500
- package/test/test_react_flex.js +1475 -513
- package/test/test_react_hidden.js +1458 -503
- package/test/test_react_listview_link.js +1455 -500
- package/test/test_react_notify.js +18534 -0
- package/test/test_react_render.js +1455 -500
- package/test/test_wolfssl_read.js +3 -2
- package/test/test_wolfssl_read.ts +3 -1
- package/test/test_worker_net.js +35 -17
- package/test/test_worker_net.ts +27 -15
- package/tsconfig.json +1 -1
- package/examples/exec_server.ts +0 -80
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { NULL } from '../../ffi/ctype.js';
|
|
2
3
|
import { forwardRef, useRef, useEffect } from 'react';
|
|
3
4
|
import * as gui from 'gui';
|
|
4
5
|
import * as ffi from 'ffi';
|
|
5
|
-
import
|
|
6
|
+
import { WCHAR } from '../../ffi/bind.js';
|
|
7
|
+
import { nmCode } from '../nmhdr.js';
|
|
8
|
+
import { LoadCursor, SetCursor, ScreenToClient, GetCursorPos } from '../../windows/user32.js';
|
|
9
|
+
import { CreateFontIndirect, DeleteObject, GetObject, SelectObject } from '../../windows/gdi32.js';
|
|
10
|
+
import { LOGFONTW, POINT, NMCUSTOMDRAW, NMLVCUSTOMDRAW, NMLISTVIEW, LVHITTESTINFO, LVITEMW, LVCOLUMNW, } from '../../windows/structs.js';
|
|
6
11
|
export function makeColorBlock(size, bgra) {
|
|
7
12
|
const n = size * size;
|
|
8
13
|
const buf = new ArrayBuffer(n * 4);
|
|
@@ -15,76 +20,19 @@ export function makeColorBlock(size, bgra) {
|
|
|
15
20
|
}
|
|
16
21
|
return buf;
|
|
17
22
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const dv = new DataView(buf);
|
|
21
|
-
for (let i = 0; i < s.length; i++)
|
|
22
|
-
dv.setUint16(i * 2, s.charCodeAt(i), true);
|
|
23
|
-
return buf;
|
|
24
|
-
}
|
|
25
|
-
function readI32(ptr, offset) {
|
|
26
|
-
return ffi.readByte(ptr + offset) | (ffi.readByte(ptr + offset + 1) << 8) |
|
|
27
|
-
(ffi.readByte(ptr + offset + 2) << 16) | (ffi.readByte(ptr + offset + 3) << 24);
|
|
28
|
-
}
|
|
29
|
-
function readU32(ptr, offset) {
|
|
30
|
-
return ffi.readByte(ptr + offset) | (ffi.readByte(ptr + offset + 1) << 8) |
|
|
31
|
-
(ffi.readByte(ptr + offset + 2) << 16) | (ffi.readByte(ptr + offset + 3) << 24) >>> 0;
|
|
32
|
-
}
|
|
33
|
-
function readU64(ptr, offset) {
|
|
34
|
-
const lo = readU32(ptr, offset);
|
|
35
|
-
const hi = readU32(ptr, offset + 4);
|
|
36
|
-
return lo + hi * 0x100000000;
|
|
37
|
-
}
|
|
23
|
+
// 自绘时要把颜色写回通知结构指向的原生内存;struct 的 decode 能读原生指针、encode 只写 ArrayBuffer,
|
|
24
|
+
// 所以这两处写入保留逐字节写(偏移由 struct 的 offsetOf 给出)。
|
|
38
25
|
function writeU32(ptr, offset, v) {
|
|
39
26
|
ffi.writeByte(ptr + offset, v & 0xFF);
|
|
40
27
|
ffi.writeByte(ptr + offset + 1, (v >> 8) & 0xFF);
|
|
41
28
|
ffi.writeByte(ptr + offset + 2, (v >> 16) & 0xFF);
|
|
42
29
|
ffi.writeByte(ptr + offset + 3, (v >> 24) & 0xFF);
|
|
43
30
|
}
|
|
44
|
-
function bufPtr(buf) {
|
|
45
|
-
return ffi.bufferPtr(buf);
|
|
46
|
-
}
|
|
47
31
|
const LV_WS = 268435456 /* gui.WindowStyle.VISIBLE */ | 8388608 /* gui.WindowStyle.BORDER */ | 2097152 /* gui.WindowStyle.VSCROLL */ | 1048576 /* gui.WindowStyle.HSCROLL */
|
|
48
32
|
| 1 /* gui.ListViewStyle.REPORT */ | 4 /* gui.ListViewStyle.SINGLESEL */;
|
|
49
|
-
|
|
50
|
-
const
|
|
51
|
-
const CD_HDC = 32; // NMCUSTOMDRAW.hdc
|
|
52
|
-
const CD_ITEM = 56; // NMCUSTOMDRAW.dwItemSpec(行索引)
|
|
53
|
-
const CD_CLRTEXT = 80; // NMLVCUSTOMDRAW.clrText
|
|
54
|
-
const CD_CLRTEXTBK = 84; // NMLVCUSTOMDRAW.clrTextBk
|
|
55
|
-
const CD_SUBITEM = 88; // NMLVCUSTOMDRAW.iSubItem
|
|
56
|
-
const NMIA_ITEM = 24; // NMITEMACTIVATE.iItem
|
|
57
|
-
const NMIA_SUBITEM = 28; // NMITEMACTIVATE.iSubItem
|
|
58
|
-
const NM_CODE = 16; // NMHDR.code
|
|
33
|
+
const CD_CLRTEXT = NMLVCUSTOMDRAW.offsetOf('clrText');
|
|
34
|
+
const CD_CLRTEXTBK = NMLVCUSTOMDRAW.offsetOf('clrTextBk');
|
|
59
35
|
const fontCache = new Map();
|
|
60
|
-
let gdi32 = null;
|
|
61
|
-
let createFontIndirectW = null;
|
|
62
|
-
let selectObjectFn = null;
|
|
63
|
-
let getObjectW = null;
|
|
64
|
-
function ensureGdi() {
|
|
65
|
-
if (gdi32 !== null)
|
|
66
|
-
return;
|
|
67
|
-
gdi32 = win.LoadLibrary('gdi32.dll');
|
|
68
|
-
if (!gdi32)
|
|
69
|
-
return;
|
|
70
|
-
createFontIndirectW = win.GetProcAddress(gdi32, 'CreateFontIndirectW');
|
|
71
|
-
selectObjectFn = win.GetProcAddress(gdi32, 'SelectObject');
|
|
72
|
-
getObjectW = win.GetProcAddress(gdi32, 'GetObjectW');
|
|
73
|
-
}
|
|
74
|
-
let user32 = null;
|
|
75
|
-
let loadCursorW = null;
|
|
76
|
-
let setCursorFn = null;
|
|
77
|
-
let screenToClient = null;
|
|
78
|
-
function ensureUser32() {
|
|
79
|
-
if (user32 !== null)
|
|
80
|
-
return;
|
|
81
|
-
user32 = win.LoadLibrary('user32.dll');
|
|
82
|
-
if (!user32)
|
|
83
|
-
return;
|
|
84
|
-
loadCursorW = win.GetProcAddress(user32, 'LoadCursorW');
|
|
85
|
-
setCursorFn = win.GetProcAddress(user32, 'SetCursor');
|
|
86
|
-
screenToClient = win.GetProcAddress(user32, 'ScreenToClient');
|
|
87
|
-
}
|
|
88
36
|
function getCellFont(hwnd, style) {
|
|
89
37
|
const key = (style.bold ? 'b' : '') + (style.italic ? 'i' : '') + (style.underline ? 'u' : '');
|
|
90
38
|
if (key === '')
|
|
@@ -92,40 +40,36 @@ function getCellFont(hwnd, style) {
|
|
|
92
40
|
const cached = fontCache.get(key);
|
|
93
41
|
if (cached !== undefined)
|
|
94
42
|
return cached === 0 ? null : cached;
|
|
95
|
-
|
|
96
|
-
if (!createFontIndirectW || !getObjectW || !hwnd)
|
|
43
|
+
if (!hwnd)
|
|
97
44
|
return null;
|
|
98
|
-
|
|
99
|
-
|
|
45
|
+
// 以控件当前字体为底(取不到则用 -13 高度的默认字体),再叠加粗 / 斜 / 下划线
|
|
46
|
+
let base = { lfHeight: -13 };
|
|
100
47
|
const cur = gui.SendMessage(hwnd, 49 /* gui.WmMsg.GETFONT */, 0, 0);
|
|
101
48
|
if (cur) {
|
|
102
|
-
const
|
|
103
|
-
if (!
|
|
49
|
+
const lf = LOGFONTW.alloc();
|
|
50
|
+
if (!GetObject(cur, LOGFONTW.size, lf))
|
|
104
51
|
return null;
|
|
52
|
+
base = LOGFONTW.decode(lf);
|
|
105
53
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
dv.setUint8(21, 1);
|
|
115
|
-
const h = ffi.ffiCall(createFontIndirectW, [ffi.FFI_TYPE_POINTER], [lf], ffi.FFI_TYPE_UINT64);
|
|
116
|
-
fontCache.set(key, h === 0 ? 0 : h);
|
|
117
|
-
return h === 0 ? null : h;
|
|
54
|
+
const h = CreateFontIndirect({
|
|
55
|
+
...base,
|
|
56
|
+
...(style.bold ? { lfWeight: 700 /* gui.FontWeight.BOLD */ } : {}),
|
|
57
|
+
...(style.italic ? { lfItalic: 1 } : {}),
|
|
58
|
+
...(style.underline ? { lfUnderline: 1 } : {}),
|
|
59
|
+
});
|
|
60
|
+
fontCache.set(key, h ? h : 0);
|
|
61
|
+
return h ? h : null;
|
|
118
62
|
}
|
|
119
63
|
function handleCustomDraw(lParam, columns, data, hwnd) {
|
|
120
|
-
const stage =
|
|
64
|
+
const { dwDrawStage: stage } = NMCUSTOMDRAW.decode(lParam);
|
|
121
65
|
if (stage === 1 /* gui.CustomDrawStage.PREPAINT */)
|
|
122
66
|
return 32 /* gui.CustomDrawFlag.NOTIFYITEMDRAW */;
|
|
123
67
|
if (stage === 65537 /* gui.CustomDrawStage.ITEMPREPAINT */)
|
|
124
68
|
return 32 /* gui.CustomDrawFlag.NOTIFYSUBITEMDRAW */;
|
|
125
69
|
if (stage === 196609 /* gui.CustomDrawStage.SUBITEMPREPAINT */) {
|
|
126
|
-
|
|
127
|
-
const
|
|
128
|
-
const style = resolveCellStyle(columns, data,
|
|
70
|
+
// 只有子项阶段才按 NMLVCUSTOMDRAW(更大的结构)读,前面的阶段只读了 NMCUSTOMDRAW 的前缀
|
|
71
|
+
const cd = NMLVCUSTOMDRAW.decode(lParam);
|
|
72
|
+
const style = resolveCellStyle(columns, data, cd.dwItemSpec, cd.iSubItem);
|
|
129
73
|
if (!style)
|
|
130
74
|
return 0 /* gui.CustomDrawFlag.DODEFAULT */;
|
|
131
75
|
if (style.color !== undefined)
|
|
@@ -133,12 +77,9 @@ function handleCustomDraw(lParam, columns, data, hwnd) {
|
|
|
133
77
|
if (style.background !== undefined)
|
|
134
78
|
writeU32(lParam, CD_CLRTEXTBK, style.background);
|
|
135
79
|
const hfont = getCellFont(hwnd, style);
|
|
136
|
-
if (hfont &&
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
ffi.ffiCall(selectObjectFn, [ffi.FFI_TYPE_UINT64, ffi.FFI_TYPE_UINT64], [hdc, hfont], ffi.FFI_TYPE_UINT64);
|
|
140
|
-
return 2 /* gui.CustomDrawFlag.NEWFONT */;
|
|
141
|
-
}
|
|
80
|
+
if (hfont && cd.hdc) {
|
|
81
|
+
SelectObject(cd.hdc, hfont);
|
|
82
|
+
return 2 /* gui.CustomDrawFlag.NEWFONT */;
|
|
142
83
|
}
|
|
143
84
|
}
|
|
144
85
|
return 0 /* gui.CustomDrawFlag.DODEFAULT */;
|
|
@@ -169,18 +110,24 @@ function resolveCellStyle(columns, data, row, colIndex) {
|
|
|
169
110
|
return style || undefined;
|
|
170
111
|
}
|
|
171
112
|
function makeLVItem(i, sub, text, image) {
|
|
172
|
-
const
|
|
173
|
-
const
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
113
|
+
const textBuf = WCHAR.encode(text);
|
|
114
|
+
const b = LVITEMW.encode({
|
|
115
|
+
mask: 1 /* LvItemFlag.TEXT */ | (image !== undefined ? 2 /* LvItemFlag.IMAGE */ : 0),
|
|
116
|
+
iItem: i,
|
|
117
|
+
iSubItem: sub,
|
|
118
|
+
state: 0,
|
|
119
|
+
stateMask: 0,
|
|
120
|
+
pszText: textBuf.ptr,
|
|
121
|
+
cchTextMax: 0,
|
|
122
|
+
iImage: image ?? 0,
|
|
123
|
+
lParam: 0,
|
|
124
|
+
iIndent: 0,
|
|
125
|
+
iGroupId: 0,
|
|
126
|
+
cColumns: 0,
|
|
127
|
+
puColumns: 0,
|
|
128
|
+
piColFmt: 0,
|
|
129
|
+
iGroup: 0,
|
|
130
|
+
});
|
|
184
131
|
b.__textBuf = textBuf;
|
|
185
132
|
return b;
|
|
186
133
|
}
|
|
@@ -207,7 +154,7 @@ const ListView = forwardRef(function ListViewInner({ columns, data, style, icons
|
|
|
207
154
|
const hbm = gui.CreateBitmapFromPixels(size, size, p);
|
|
208
155
|
if (hbm) {
|
|
209
156
|
gui.ImageListAdd(img, hbm);
|
|
210
|
-
|
|
157
|
+
DeleteObject(hbm);
|
|
211
158
|
}
|
|
212
159
|
}
|
|
213
160
|
gui.SendMessage(h, 4099 /* gui.LvMsg.SETIMAGELIST */, 1 /* gui.LvImageList.SMALL */, img);
|
|
@@ -227,16 +174,22 @@ const ListView = forwardRef(function ListViewInner({ columns, data, style, icons
|
|
|
227
174
|
gui.SendMessage(h, 4150 /* gui.LvMsg.SETEXTENDEDLISTVIEWSTYLE */, 0, 32 /* gui.LvExStyle.FULLROWSELECT */ | 1 /* gui.LvExStyle.GRIDLINES */ | 65536 /* gui.LvExStyle.DOUBLEBUFFER */);
|
|
228
175
|
const n = columns.length;
|
|
229
176
|
for (let j = 0; j < n; j++) {
|
|
230
|
-
const titleBuf =
|
|
231
|
-
const lvc =
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
177
|
+
const titleBuf = WCHAR.encode(columns[j].name);
|
|
178
|
+
const lvc = LVCOLUMNW.encode({
|
|
179
|
+
mask: 4 /* LvColumnMask.TEXT */ | 2 /* LvColumnMask.WIDTH */ | 1 /* LvColumnMask.FORMAT */,
|
|
180
|
+
fmt: alignToFmt(columns[j].align),
|
|
181
|
+
cx: columns[j].width ?? 100,
|
|
182
|
+
pszText: titleBuf.ptr,
|
|
183
|
+
cchTextMax: 0,
|
|
184
|
+
iSubItem: j,
|
|
185
|
+
iImage: 0,
|
|
186
|
+
iOrder: 0,
|
|
187
|
+
cxMin: 0,
|
|
188
|
+
cxDefault: 0,
|
|
189
|
+
cxIdeal: 0,
|
|
190
|
+
});
|
|
238
191
|
lvc.__titleBuf = titleBuf;
|
|
239
|
-
gui.SendMessage(h, 4193 /* gui.LvMsg.INSERTCOLUMNW */, j,
|
|
192
|
+
gui.SendMessage(h, 4193 /* gui.LvMsg.INSERTCOLUMNW */, j, lvc.ptr);
|
|
240
193
|
}
|
|
241
194
|
}, [columns]);
|
|
242
195
|
useEffect(() => {
|
|
@@ -249,10 +202,10 @@ const ListView = forwardRef(function ListViewInner({ columns, data, style, icons
|
|
|
249
202
|
const record = data[i];
|
|
250
203
|
const img = getIcon ? getIcon(record, i) : undefined;
|
|
251
204
|
const itemBuf = makeLVItem(i, 0, cellText(record, columns[0], i), img);
|
|
252
|
-
gui.SendMessage(h, 4173 /* gui.LvMsg.INSERTITEMW */, 0,
|
|
205
|
+
gui.SendMessage(h, 4173 /* gui.LvMsg.INSERTITEMW */, 0, itemBuf.ptr);
|
|
253
206
|
for (let j = 1; j < nCols; j++) {
|
|
254
207
|
const subBuf = makeLVItem(i, j, cellText(record, columns[j], i));
|
|
255
|
-
gui.SendMessage(h, 4172 /* gui.LvMsg.SETITEMW */, 0,
|
|
208
|
+
gui.SendMessage(h, 4172 /* gui.LvMsg.SETITEMW */, 0, subBuf.ptr);
|
|
256
209
|
}
|
|
257
210
|
}
|
|
258
211
|
for (let j = 0; j < columns.length; j++) {
|
|
@@ -263,19 +216,17 @@ const ListView = forwardRef(function ListViewInner({ columns, data, style, icons
|
|
|
263
216
|
}, [data, columns, getIcon]);
|
|
264
217
|
return (_jsx("w", { type: "STATIC", ws: 268435456 /* gui.WindowStyle.VISIBLE */ | 33554432 /* gui.WindowStyle.CLIPCHILDREN */, style: { ...style, flexDirection: 'column', alignItems: 'stretch' }, ref: ref, onEvent: (e) => {
|
|
265
218
|
if (e.msg === 78 /* gui.WmMsg.NOTIFY */) {
|
|
266
|
-
const code =
|
|
219
|
+
const code = nmCode(e.lParam);
|
|
267
220
|
if (code === -12 /* gui.LvNotifyCode.CUSTOMDRAW */) {
|
|
268
221
|
return handleCustomDraw(e.lParam, columns, data, lvRef.current);
|
|
269
222
|
}
|
|
270
223
|
if (code === -100 /* gui.LvNotifyCode.ITEMCHANGING */) {
|
|
271
|
-
const uNewState =
|
|
272
|
-
const uOldState = readU32(e.lParam, 36);
|
|
224
|
+
const { uNewState, uOldState } = NMLISTVIEW.decode(e.lParam);
|
|
273
225
|
if ((uNewState & 2 /* LvItemState.SELECTED */) !== (uOldState & 2 /* LvItemState.SELECTED */))
|
|
274
226
|
return 1;
|
|
275
227
|
}
|
|
276
228
|
if (code === -2 /* gui.LvNotifyCode.CLICK */) {
|
|
277
|
-
const iItem =
|
|
278
|
-
const iSubItem = readI32(e.lParam, NMIA_SUBITEM);
|
|
229
|
+
const { iItem, iSubItem } = NMLISTVIEW.decode(e.lParam);
|
|
279
230
|
const col = columns[iSubItem];
|
|
280
231
|
const record = data[iItem];
|
|
281
232
|
if (col?.onCellClick && record !== undefined)
|
|
@@ -293,33 +244,21 @@ const ListView = forwardRef(function ListViewInner({ columns, data, style, icons
|
|
|
293
244
|
const h = lvRef.current;
|
|
294
245
|
if (!h)
|
|
295
246
|
return;
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
const sp = gui.GetCursorPos();
|
|
300
|
-
if (!sp)
|
|
247
|
+
// GetCursorPos 出参写入初值 → ScreenToClient 就地更新 → decode 读回
|
|
248
|
+
const pt = POINT.alloc();
|
|
249
|
+
if (!GetCursorPos(pt.ptr))
|
|
301
250
|
return;
|
|
302
|
-
|
|
303
|
-
const
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
const lvhi = new ArrayBuffer(24);
|
|
308
|
-
const lvd = new DataView(lvhi);
|
|
309
|
-
lvd.setInt32(0, sdv.getInt32(0, true), true);
|
|
310
|
-
lvd.setInt32(4, sdv.getInt32(4, true), true);
|
|
311
|
-
lvd.setInt32(12, -1, true);
|
|
312
|
-
lvd.setInt32(16, -1, true);
|
|
313
|
-
const lvhiPtr = bufPtr(lvhi);
|
|
314
|
-
gui.SendMessage(h, 4153 /* gui.LvMsg.SUBITEMHITTEST */, 0, lvhiPtr);
|
|
315
|
-
const iItem = readI32(lvhiPtr, 12);
|
|
316
|
-
const iSubItem = readI32(lvhiPtr, 16);
|
|
251
|
+
ScreenToClient(h, pt.ptr);
|
|
252
|
+
const { x: sx, y: sy } = POINT.decode(pt);
|
|
253
|
+
const hit = LVHITTESTINFO.encode({ pt: [sx, sy], iItem: -1, iSubItem: -1 });
|
|
254
|
+
gui.SendMessage(h, 4153 /* gui.LvMsg.SUBITEMHITTEST */, 0, hit.ptr);
|
|
255
|
+
const { iItem, iSubItem } = LVHITTESTINFO.decode(hit);
|
|
317
256
|
const style = resolveCellStyle(columns, data, iItem, iSubItem);
|
|
318
257
|
if (!style || style.cursor === undefined)
|
|
319
258
|
return;
|
|
320
|
-
const hc =
|
|
259
|
+
const hc = LoadCursor(NULL, style.cursor);
|
|
321
260
|
if (hc) {
|
|
322
|
-
|
|
261
|
+
SetCursor(hc);
|
|
323
262
|
return 1;
|
|
324
263
|
}
|
|
325
264
|
return;
|
|
@@ -1,98 +1,54 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { forwardRef, useState, useRef } from 'react';
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
let _SHBrowseForFolderW = 0;
|
|
9
|
-
let _SHGetPathFromIDListW = 0;
|
|
10
|
-
let _CoTaskMemFree = 0;
|
|
11
|
-
function ensureDlls() {
|
|
12
|
-
if (_GetOpenFileNameW)
|
|
13
|
-
return true;
|
|
14
|
-
const comdlg32 = win.LoadLibrary('comdlg32.dll');
|
|
15
|
-
const shell32 = win.LoadLibrary('shell32.dll');
|
|
16
|
-
const ole32 = win.LoadLibrary('ole32.dll');
|
|
17
|
-
if (!comdlg32 || !shell32 || !ole32)
|
|
18
|
-
return false;
|
|
19
|
-
const load = (lib, name) => win.GetProcAddress(lib, name) || 0;
|
|
20
|
-
_GetOpenFileNameW = load(comdlg32, 'GetOpenFileNameW');
|
|
21
|
-
_SHBrowseForFolderW = load(shell32, 'SHBrowseForFolderW');
|
|
22
|
-
_SHGetPathFromIDListW = load(shell32, 'SHGetPathFromIDListW');
|
|
23
|
-
_CoTaskMemFree = load(ole32, 'CoTaskMemFree');
|
|
24
|
-
return !!_GetOpenFileNameW && !!_SHBrowseForFolderW && !!_SHGetPathFromIDListW && !!_CoTaskMemFree;
|
|
25
|
-
}
|
|
26
|
-
function strToWide(s) {
|
|
27
|
-
return new TextEncoder('utf-16le').encode(s + '\0').buffer;
|
|
28
|
-
}
|
|
29
|
-
const _decoder = new TextDecoder('utf-16le');
|
|
30
|
-
function wideToStr(buf, offset = 0) {
|
|
31
|
-
const str = _decoder.decode(new Uint8Array(buf, offset));
|
|
32
|
-
const nullIdx = str.indexOf('\0');
|
|
33
|
-
return nullIdx >= 0 ? str.substring(0, nullIdx) : str;
|
|
34
|
-
}
|
|
35
|
-
function setPtr(dv, off, ptr) {
|
|
36
|
-
dv.setUint32(off, ptr & 0xFFFFFFFF, true);
|
|
37
|
-
dv.setUint32(off + 4, Math.floor(ptr / 0x100000000), true);
|
|
38
|
-
}
|
|
3
|
+
import { WCHAR } from '../../ffi/bind.js';
|
|
4
|
+
import { OPENFILENAMEW, BROWSEINFOW } from '../../windows/structs.js';
|
|
5
|
+
import { GetOpenFileName } from '../../windows/comdlg32.js';
|
|
6
|
+
import { SHBrowseForFolder, SHGetPathFromIDList } from '../../windows/shell32.js';
|
|
7
|
+
import { CoTaskMemFree } from '../../windows/ole32.js';
|
|
39
8
|
function openFileDialog(owner, filter, title, multiple) {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
const
|
|
43
|
-
const sv = new DataView(structBuf);
|
|
44
|
-
const fileBuf = new ArrayBuffer(260 * 2);
|
|
45
|
-
const filterWide = strToWide(filter);
|
|
46
|
-
const titleWide = title ? strToWide(title) : null;
|
|
47
|
-
sv.setUint32(0, 152, true);
|
|
48
|
-
setPtr(sv, 8, owner);
|
|
49
|
-
setPtr(sv, 24, ffi.bufferPtr(filterWide));
|
|
50
|
-
setPtr(sv, 48, ffi.bufferPtr(fileBuf));
|
|
51
|
-
sv.setUint32(56, 260, true);
|
|
9
|
+
const fileBuf = WCHAR.alloc(260);
|
|
10
|
+
const filterWide = WCHAR.encode(filter);
|
|
11
|
+
const titleWide = title ? WCHAR.encode(title) : null;
|
|
52
12
|
let flags = 0x1000 | 0x0800 | 0x0008;
|
|
53
13
|
if (multiple)
|
|
54
14
|
flags |= 0x0200;
|
|
55
15
|
flags |= 0x80000;
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
16
|
+
// 缺省字段跳过 = fresh buffer 上留 0
|
|
17
|
+
const ofn = OPENFILENAMEW.encode({
|
|
18
|
+
lStructSize: OPENFILENAMEW.size,
|
|
19
|
+
hwndOwner: owner,
|
|
20
|
+
lpstrFilter: filterWide.ptr,
|
|
21
|
+
lpstrFile: fileBuf.ptr,
|
|
22
|
+
nMaxFile: 260,
|
|
23
|
+
lpstrTitle: titleWide?.ptr ?? 0,
|
|
24
|
+
Flags: flags,
|
|
25
|
+
});
|
|
26
|
+
const ret = GetOpenFileName(ofn);
|
|
60
27
|
if (!ret)
|
|
61
28
|
return null;
|
|
62
29
|
if (!multiple) {
|
|
63
|
-
return
|
|
64
|
-
}
|
|
65
|
-
const dir = wideToStr(fileBuf);
|
|
66
|
-
let pos = (dir.length + 1) * 2;
|
|
67
|
-
const files = [];
|
|
68
|
-
while (pos < fileBuf.byteLength) {
|
|
69
|
-
const f = wideToStr(fileBuf, pos);
|
|
70
|
-
if (f.length === 0)
|
|
71
|
-
break;
|
|
72
|
-
files.push(dir + '\\' + f);
|
|
73
|
-
pos += (f.length + 1) * 2;
|
|
30
|
+
return WCHAR.decode(fileBuf);
|
|
74
31
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
32
|
+
// 多选缓冲:dir\0 file1\0 file2\0 \0 —— 整缓冲一次解码按 NUL 拆分
|
|
33
|
+
const strs = new TextDecoder('utf-16le').decode(new Uint8Array(fileBuf)).split('\0');
|
|
34
|
+
const dir = strs[0] ?? '';
|
|
35
|
+
const files = strs.slice(1).filter((f) => f.length > 0).map((f) => dir + '\\' + f);
|
|
36
|
+
return files.length > 0 ? files : [dir];
|
|
78
37
|
}
|
|
79
38
|
function openFolderDialog(owner, title) {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
setPtr(sv, 24, ffi.bufferPtr(titleWide));
|
|
88
|
-
sv.setUint32(32, 0x00000041, true);
|
|
89
|
-
const pidl = ffi.ffiCall(_SHBrowseForFolderW, [FFI_PTR], [structBuf], FFI_PTR);
|
|
39
|
+
const titleWide = title ? WCHAR.encode(title) : null;
|
|
40
|
+
const bi = BROWSEINFOW.encode({
|
|
41
|
+
hwndOwner: owner,
|
|
42
|
+
lpszTitle: titleWide?.ptr ?? 0,
|
|
43
|
+
ulFlags: 0x00000041,
|
|
44
|
+
});
|
|
45
|
+
const pidl = SHBrowseForFolder(bi);
|
|
90
46
|
if (!pidl)
|
|
91
47
|
return null;
|
|
92
|
-
const pathBuf =
|
|
93
|
-
const ok =
|
|
94
|
-
|
|
95
|
-
return ok ?
|
|
48
|
+
const pathBuf = WCHAR.alloc(260);
|
|
49
|
+
const ok = SHGetPathFromIDList(pidl, pathBuf);
|
|
50
|
+
CoTaskMemFree(pidl);
|
|
51
|
+
return ok ? WCHAR.decode(pathBuf) : null;
|
|
96
52
|
}
|
|
97
53
|
export const PathPicker = forwardRef((props, ref) => {
|
|
98
54
|
const { type = 'file', filter = 'All Files\0*.*\0\0', title, disabled, style } = props;
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { forwardRef, useLayoutEffect, useRef } from 'react';
|
|
3
3
|
import * as gui from 'gui';
|
|
4
|
+
import { DefWindowProc, SetWindowPos, ShowScrollBar, GetDC, ReleaseDC } from '../../windows/user32.js';
|
|
5
|
+
import { NULL } from '../../ffi/ctype.js';
|
|
6
|
+
import { GetDeviceCaps } from '../../windows/gdi32.js';
|
|
4
7
|
import { forceFlexLayout } from '../reconciler.js';
|
|
5
8
|
export const ScrollView = forwardRef(({ children, style, contentWidth, contentHeight }, ref) => {
|
|
6
9
|
const svRef = useRef(null);
|
|
@@ -18,28 +21,32 @@ export const ScrollView = forwardRef(({ children, style, contentWidth, contentHe
|
|
|
18
21
|
return;
|
|
19
22
|
const svW = svRect.right - svRect.left;
|
|
20
23
|
const svH = svRect.bottom - svRect.top;
|
|
21
|
-
|
|
24
|
+
// GetScaleFactor 等价内联:GetDC → GetDeviceCaps(LOGPIXELSX=88) → ReleaseDC 配对
|
|
25
|
+
const hdc = GetDC(NULL);
|
|
26
|
+
const scale = hdc ? GetDeviceCaps(hdc, 88) / 96 : 1;
|
|
27
|
+
if (hdc)
|
|
28
|
+
ReleaseDC(NULL, hdc);
|
|
22
29
|
const natW = Math.round((contentWidth ?? (svW / scale)) * scale);
|
|
23
30
|
const natH = Math.round((contentHeight ?? (svH / scale)) * scale);
|
|
24
31
|
scrollXRef.current = Math.max(0, Math.min(scrollXRef.current, natW - svW));
|
|
25
32
|
scrollYRef.current = Math.max(0, Math.min(scrollYRef.current, natH - svH));
|
|
26
|
-
|
|
33
|
+
SetWindowPos(content, 0 /* gui.SetWindowPosHwnd.TOP */, -scrollXRef.current, -scrollYRef.current, natW, natH, 4 /* gui.SetWindowPosFlag.SWP_NOZORDER */);
|
|
27
34
|
forceFlexLayout(content);
|
|
28
35
|
if (natW > svW) {
|
|
29
36
|
gui.SetScrollInfo(sv, 0 /* gui.ScrollBar.HORZ */, { min: 0, max: natW - 1, page: svW, pos: scrollXRef.current }, true);
|
|
30
|
-
|
|
37
|
+
ShowScrollBar(sv, 0 /* gui.ScrollBar.HORZ */, 1);
|
|
31
38
|
}
|
|
32
39
|
else {
|
|
33
40
|
scrollXRef.current = 0;
|
|
34
|
-
|
|
41
|
+
ShowScrollBar(sv, 0 /* gui.ScrollBar.HORZ */, 0);
|
|
35
42
|
}
|
|
36
43
|
if (natH > svH) {
|
|
37
44
|
gui.SetScrollInfo(sv, 1 /* gui.ScrollBar.VERT */, { min: 0, max: natH - 1, page: svH, pos: scrollYRef.current }, true);
|
|
38
|
-
|
|
45
|
+
ShowScrollBar(sv, 1 /* gui.ScrollBar.VERT */, 1);
|
|
39
46
|
}
|
|
40
47
|
else {
|
|
41
48
|
scrollYRef.current = 0;
|
|
42
|
-
|
|
49
|
+
ShowScrollBar(sv, 1 /* gui.ScrollBar.VERT */, 0);
|
|
43
50
|
}
|
|
44
51
|
}
|
|
45
52
|
useLayoutEffect(() => {
|
|
@@ -56,7 +63,7 @@ export const ScrollView = forwardRef(({ children, style, contentWidth, contentHe
|
|
|
56
63
|
else {
|
|
57
64
|
scrollXRef.current = newPos;
|
|
58
65
|
}
|
|
59
|
-
|
|
66
|
+
SetWindowPos(content, 0 /* gui.SetWindowPosHwnd.TOP */, -scrollXRef.current, -scrollYRef.current, 0, 0, 1 /* gui.SetWindowPosFlag.SWP_NOSIZE */ | 4 /* gui.SetWindowPosFlag.SWP_NOZORDER */);
|
|
60
67
|
gui.SetScrollInfo(sv, bar, { pos: newPos }, true);
|
|
61
68
|
}
|
|
62
69
|
function handleScroll(e, bar) {
|
|
@@ -119,7 +126,7 @@ export const ScrollView = forwardRef(({ children, style, contentWidth, contentHe
|
|
|
119
126
|
fn: (e) => {
|
|
120
127
|
if (e.msg === 132 /* gui.WmMsg.NCHITTEST */ ||
|
|
121
128
|
e.msg === 161 /* gui.WmMsg.NCLBUTTONDOWN */) {
|
|
122
|
-
return
|
|
129
|
+
return DefWindowProc(e.hwnd, e.msg, e.wParam, e.lParam);
|
|
123
130
|
}
|
|
124
131
|
if (e.msg === 277 /* gui.WmMsg.VSCROLL */) {
|
|
125
132
|
handleScroll(e, 1 /* gui.ScrollBar.VERT */);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { forwardRef, useRef, useEffect } from 'react';
|
|
3
3
|
import * as gui from 'gui';
|
|
4
|
+
import { DefWindowProc } from '../../windows/user32.js';
|
|
4
5
|
export const Slider = forwardRef(({ value, onChange, min = 0, max = 100, vertical = false, disabled, style }, ref) => {
|
|
5
6
|
const wrapperRef = useRef(null);
|
|
6
7
|
const sliderRef = useRef(null);
|
|
@@ -30,7 +31,7 @@ export const Slider = forwardRef(({ value, onChange, min = 0, max = 100, vertica
|
|
|
30
31
|
}, onEvent: (e) => {
|
|
31
32
|
if (e.msg === 132 /* gui.WmMsg.NCHITTEST */ ||
|
|
32
33
|
e.msg === 161 /* gui.WmMsg.NCLBUTTONDOWN */) {
|
|
33
|
-
return
|
|
34
|
+
return DefWindowProc(e.hwnd, e.msg, e.wParam, e.lParam);
|
|
34
35
|
}
|
|
35
36
|
if (e.msg === 276 /* gui.WmMsg.HSCROLL */ || e.msg === 277 /* gui.WmMsg.VSCROLL */) {
|
|
36
37
|
const h = sliderRef.current;
|
|
@@ -1,18 +1,9 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { forwardRef, useRef, useEffect, useState } from 'react';
|
|
3
3
|
import * as gui from 'gui';
|
|
4
|
-
import
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const dv = new DataView(buf);
|
|
8
|
-
for (let i = 0; i < s.length; i++)
|
|
9
|
-
dv.setUint16(i * 2, s.charCodeAt(i), true);
|
|
10
|
-
return buf;
|
|
11
|
-
}
|
|
12
|
-
function readI32(ptr, offset) {
|
|
13
|
-
return ffi.readByte(ptr + offset) | (ffi.readByte(ptr + offset + 1) << 8) |
|
|
14
|
-
(ffi.readByte(ptr + offset + 2) << 16) | (ffi.readByte(ptr + offset + 3) << 24);
|
|
15
|
-
}
|
|
4
|
+
import { WCHAR } from '../../ffi/bind.js';
|
|
5
|
+
import { TCITEMW } from '../../windows/structs.js';
|
|
6
|
+
import { nmCode } from '../nmhdr.js';
|
|
16
7
|
export const Tab = forwardRef(({ tabs, selectedIndex: controlledIndex, defaultSelectedIndex = 0, onChange, style }, ref) => {
|
|
17
8
|
const [internalIndex, setInternalIndex] = useState(defaultSelectedIndex);
|
|
18
9
|
const isControlled = controlledIndex !== undefined;
|
|
@@ -24,13 +15,17 @@ export const Tab = forwardRef(({ tabs, selectedIndex: controlledIndex, defaultSe
|
|
|
24
15
|
return;
|
|
25
16
|
gui.SendMessage(h, 4873 /* gui.TcMsg.DELETEALLITEMS */, 0, 0);
|
|
26
17
|
for (let i = 0; i < tabs.length; i++) {
|
|
27
|
-
const titleBuf =
|
|
28
|
-
const tci =
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
18
|
+
const titleBuf = WCHAR.encode(tabs[i].title);
|
|
19
|
+
const tci = TCITEMW.encode({
|
|
20
|
+
mask: 1 /* gui.TcItemFlag.TEXT */,
|
|
21
|
+
dwState: 0,
|
|
22
|
+
dwStateMask: 0,
|
|
23
|
+
pszText: titleBuf.ptr,
|
|
24
|
+
cchTextMax: tabs[i].title.length + 1,
|
|
25
|
+
iImage: 0,
|
|
26
|
+
lParam: 0,
|
|
27
|
+
});
|
|
28
|
+
const tciPtr = tci.ptr;
|
|
34
29
|
gui.SendMessage(h, 4926 /* gui.TcMsg.INSERTITEMW */, i, tciPtr);
|
|
35
30
|
}
|
|
36
31
|
gui.SendMessage(h, 4876 /* gui.TcMsg.SETCURSEL */, sel, 0);
|
|
@@ -45,7 +40,7 @@ export const Tab = forwardRef(({ tabs, selectedIndex: controlledIndex, defaultSe
|
|
|
45
40
|
const h = tabHwnd.current;
|
|
46
41
|
if (!h)
|
|
47
42
|
return;
|
|
48
|
-
const code =
|
|
43
|
+
const code = nmCode(e.lParam);
|
|
49
44
|
// SysTabControl32 在 comctl32 v6 下不发标准的 TCN_SELCHANGE (-550),
|
|
50
45
|
// 收到 NM_CLICK (-2) 或 TCN_SELCHANGING (-551) 时读实际选中项
|
|
51
46
|
if (code === -552 /* gui.TcNotifyCode.SELCHANGING */ || code === -2 /* gui.SysLinkNotifyCode.CLICK */) {
|