quickwin 1.3.0 → 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 +1 -2
- package/README.md +19 -23
- 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 +25 -82
- package/examples/pdf_preview2.ts +27 -84
- package/examples/pdf_viewer.js +1318 -1178
- package/examples/pdf_viewer.tsx +16 -67
- package/examples/setres.js +22 -47
- package/examples/setres.ts +23 -57
- package/examples/test_gallery.js +1313 -1074
- package/examples/test_react_lazy.js +1284 -1056
- package/examples/test_react_listbox.js +1283 -1055
- package/examples/test_root_window.js +1279 -1051
- package/examples/test_tab.js +1284 -1056
- 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 +18 -37
- package/lib/react-qw/components/Input.js +3 -10
- package/lib/react-qw/components/Link.js +6 -19
- package/lib/react-qw/components/ListView.js +49 -154
- package/lib/react-qw/components/PathPicker.js +28 -88
- 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 +6 -22
- package/lib/react-qw/components/Tooltip.js +10 -21
- package/lib/react-qw/components/TreeView.js +8 -31
- package/lib/react-qw/index.js +4 -2
- package/lib/react-qw/index.ts +4 -2
- package/lib/react-qw/nmhdr.js +3 -24
- package/lib/react-qw/nmhdr.ts +5 -30
- 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 -23
- package/lib/text-measure.ts +20 -25
- 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 -37
- 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 +12 -4
- package/test/run.ts +13 -5
- package/test/test_basic_fetch.js +8 -0
- package/test/test_basic_fetch.ts +9 -0
- package/test/test_ffi.js +25 -43
- package/test/test_ffi.ts +33 -52
- package/test/test_ffi_abi.js +235 -0
- package/test/test_ffi_abi.ts +218 -0
- package/test/test_ffi_bind.js +538 -8
- package/test/test_ffi_bind.ts +559 -8
- package/test/test_ffi_struct.js +632 -88
- package/test/test_ffi_struct.ts +666 -91
- 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 +1294 -1066
- package/test/test_react_counter.js +1284 -1056
- package/test/test_react_flex.js +1304 -1069
- package/test/test_react_hidden.js +1287 -1059
- package/test/test_react_listview_link.js +1284 -1056
- package/test/test_react_notify.js +18534 -0
- package/test/test_react_render.js +1284 -1056
- 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
- package/lib/ffi-bind.js +0 -85
- package/lib/ffi-bind.ts +0 -170
- package/lib/ffi-struct.js +0 -224
- package/lib/ffi-struct.ts +0 -239
- package/lib/win-common-structs.js +0 -39
- package/lib/win-common-structs.ts +0 -41
|
@@ -1,10 +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 {
|
|
7
|
-
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';
|
|
8
11
|
export function makeColorBlock(size, bgra) {
|
|
9
12
|
const n = size * size;
|
|
10
13
|
const buf = new ArrayBuffer(n * 4);
|
|
@@ -17,106 +20,19 @@ export function makeColorBlock(size, bgra) {
|
|
|
17
20
|
}
|
|
18
21
|
return buf;
|
|
19
22
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const dv = new DataView(buf);
|
|
23
|
-
for (let i = 0; i < s.length; i++)
|
|
24
|
-
dv.setUint16(i * 2, s.charCodeAt(i), true);
|
|
25
|
-
return buf;
|
|
26
|
-
}
|
|
27
|
-
function readI32(ptr, offset) {
|
|
28
|
-
return ffi.readByte(ptr + offset) | (ffi.readByte(ptr + offset + 1) << 8) |
|
|
29
|
-
(ffi.readByte(ptr + offset + 2) << 16) | (ffi.readByte(ptr + offset + 3) << 24);
|
|
30
|
-
}
|
|
31
|
-
function readU32(ptr, offset) {
|
|
32
|
-
return ffi.readByte(ptr + offset) | (ffi.readByte(ptr + offset + 1) << 8) |
|
|
33
|
-
(ffi.readByte(ptr + offset + 2) << 16) | (ffi.readByte(ptr + offset + 3) << 24) >>> 0;
|
|
34
|
-
}
|
|
35
|
-
function readU64(ptr, offset) {
|
|
36
|
-
const lo = readU32(ptr, offset);
|
|
37
|
-
const hi = readU32(ptr, offset + 4);
|
|
38
|
-
return lo + hi * 0x100000000;
|
|
39
|
-
}
|
|
23
|
+
// 自绘时要把颜色写回通知结构指向的原生内存;struct 的 decode 能读原生指针、encode 只写 ArrayBuffer,
|
|
24
|
+
// 所以这两处写入保留逐字节写(偏移由 struct 的 offsetOf 给出)。
|
|
40
25
|
function writeU32(ptr, offset, v) {
|
|
41
26
|
ffi.writeByte(ptr + offset, v & 0xFF);
|
|
42
27
|
ffi.writeByte(ptr + offset + 1, (v >> 8) & 0xFF);
|
|
43
28
|
ffi.writeByte(ptr + offset + 2, (v >> 16) & 0xFF);
|
|
44
29
|
ffi.writeByte(ptr + offset + 3, (v >> 24) & 0xFF);
|
|
45
30
|
}
|
|
46
|
-
function bufPtr(buf) {
|
|
47
|
-
return ffi.bufferPtr(buf);
|
|
48
|
-
}
|
|
49
31
|
const LV_WS = 268435456 /* gui.WindowStyle.VISIBLE */ | 8388608 /* gui.WindowStyle.BORDER */ | 2097152 /* gui.WindowStyle.VSCROLL */ | 1048576 /* gui.WindowStyle.HSCROLL */
|
|
50
32
|
| 1 /* gui.ListViewStyle.REPORT */ | 4 /* gui.ListViewStyle.SINGLESEL */;
|
|
51
|
-
// ListView 通知结构(嵌套 NMHDR 以复现 MSVC 尾 padding 传染,offsetOf 保证 ia32/x64 均正确)。
|
|
52
|
-
const NMCUSTOMDRAW = struct({
|
|
53
|
-
hdr: NMHDR,
|
|
54
|
-
dwDrawStage: 'u32', hdc: 'ptr', rc: 'i32[4]',
|
|
55
|
-
dwItemSpec: 'ptr', uItemState: 'u32', lItemlParam: 'ptr',
|
|
56
|
-
});
|
|
57
|
-
const NMLVCUSTOMDRAW = struct({
|
|
58
|
-
hdr: NMHDR,
|
|
59
|
-
dwDrawStage: 'u32', hdc: 'ptr', rc: 'i32[4]',
|
|
60
|
-
dwItemSpec: 'ptr', uItemState: 'u32', lItemlParam: 'ptr',
|
|
61
|
-
clrText: 'u32', clrTextBk: 'u32', iSubItem: 'i32', dwItemType: 'u32',
|
|
62
|
-
});
|
|
63
|
-
// NMLISTVIEW / NMITEMACTIVATE 前缀字段布局一致(iItem..uChanged)
|
|
64
|
-
const NMLISTVIEW = struct({
|
|
65
|
-
hdr: NMHDR,
|
|
66
|
-
iItem: 'i32', iSubItem: 'i32', uNewState: 'u32', uOldState: 'u32', uChanged: 'u32',
|
|
67
|
-
ptAction: 'i32[2]', lParam: 'ptr',
|
|
68
|
-
});
|
|
69
|
-
const CD_STAGE = NMCUSTOMDRAW.offsetOf('dwDrawStage');
|
|
70
|
-
const CD_HDC = NMCUSTOMDRAW.offsetOf('hdc');
|
|
71
|
-
const CD_ITEM = NMCUSTOMDRAW.offsetOf('dwItemSpec');
|
|
72
33
|
const CD_CLRTEXT = NMLVCUSTOMDRAW.offsetOf('clrText');
|
|
73
34
|
const CD_CLRTEXTBK = NMLVCUSTOMDRAW.offsetOf('clrTextBk');
|
|
74
|
-
const CD_SUBITEM = NMLVCUSTOMDRAW.offsetOf('iSubItem');
|
|
75
|
-
const NMIA_ITEM = NMLISTVIEW.offsetOf('iItem');
|
|
76
|
-
const NMIA_SUBITEM = NMLISTVIEW.offsetOf('iSubItem');
|
|
77
|
-
const NMLV_UNEW = NMLISTVIEW.offsetOf('uNewState');
|
|
78
|
-
const NMLV_UOLD = NMLISTVIEW.offsetOf('uOldState');
|
|
79
|
-
const LVITEMW = struct({
|
|
80
|
-
mask: 'u32', iItem: 'i32', iSubItem: 'i32',
|
|
81
|
-
state: 'u32', stateMask: 'u32',
|
|
82
|
-
pszText: 'ptr', cchTextMax: 'i32', iImage: 'i32', lParam: 'ptr',
|
|
83
|
-
iIndent: 'i32', iGroupId: 'i32', cColumns: 'u32',
|
|
84
|
-
puColumns: 'ptr', piColFmt: 'ptr', iGroup: 'i32',
|
|
85
|
-
});
|
|
86
|
-
const LVCOLUMNW = struct({
|
|
87
|
-
mask: 'u32', fmt: 'i32', cx: 'i32',
|
|
88
|
-
pszText: 'ptr', cchTextMax: 'i32', iSubItem: 'i32', iImage: 'i32', iOrder: 'i32',
|
|
89
|
-
cxMin: 'i32', cxDefault: 'i32', cxIdeal: 'i32',
|
|
90
|
-
});
|
|
91
35
|
const fontCache = new Map();
|
|
92
|
-
let gdi32 = null;
|
|
93
|
-
let createFontIndirectW = null;
|
|
94
|
-
let selectObjectFn = null;
|
|
95
|
-
let getObjectW = null;
|
|
96
|
-
function ensureGdi() {
|
|
97
|
-
if (gdi32 !== null)
|
|
98
|
-
return;
|
|
99
|
-
gdi32 = win.LoadLibrary('gdi32.dll');
|
|
100
|
-
if (!gdi32)
|
|
101
|
-
return;
|
|
102
|
-
createFontIndirectW = win.GetProcAddress(gdi32, 'CreateFontIndirectW');
|
|
103
|
-
selectObjectFn = win.GetProcAddress(gdi32, 'SelectObject');
|
|
104
|
-
getObjectW = win.GetProcAddress(gdi32, 'GetObjectW');
|
|
105
|
-
}
|
|
106
|
-
let user32 = null;
|
|
107
|
-
let loadCursorW = null;
|
|
108
|
-
let setCursorFn = null;
|
|
109
|
-
let screenToClient = null;
|
|
110
|
-
function ensureUser32() {
|
|
111
|
-
if (user32 !== null)
|
|
112
|
-
return;
|
|
113
|
-
user32 = win.LoadLibrary('user32.dll');
|
|
114
|
-
if (!user32)
|
|
115
|
-
return;
|
|
116
|
-
loadCursorW = win.GetProcAddress(user32, 'LoadCursorW');
|
|
117
|
-
setCursorFn = win.GetProcAddress(user32, 'SetCursor');
|
|
118
|
-
screenToClient = win.GetProcAddress(user32, 'ScreenToClient');
|
|
119
|
-
}
|
|
120
36
|
function getCellFont(hwnd, style) {
|
|
121
37
|
const key = (style.bold ? 'b' : '') + (style.italic ? 'i' : '') + (style.underline ? 'u' : '');
|
|
122
38
|
if (key === '')
|
|
@@ -124,40 +40,36 @@ function getCellFont(hwnd, style) {
|
|
|
124
40
|
const cached = fontCache.get(key);
|
|
125
41
|
if (cached !== undefined)
|
|
126
42
|
return cached === 0 ? null : cached;
|
|
127
|
-
|
|
128
|
-
if (!createFontIndirectW || !getObjectW || !hwnd)
|
|
43
|
+
if (!hwnd)
|
|
129
44
|
return null;
|
|
130
|
-
|
|
131
|
-
|
|
45
|
+
// 以控件当前字体为底(取不到则用 -13 高度的默认字体),再叠加粗 / 斜 / 下划线
|
|
46
|
+
let base = { lfHeight: -13 };
|
|
132
47
|
const cur = gui.SendMessage(hwnd, 49 /* gui.WmMsg.GETFONT */, 0, 0);
|
|
133
48
|
if (cur) {
|
|
134
|
-
const
|
|
135
|
-
if (!
|
|
49
|
+
const lf = LOGFONTW.alloc();
|
|
50
|
+
if (!GetObject(cur, LOGFONTW.size, lf))
|
|
136
51
|
return null;
|
|
52
|
+
base = LOGFONTW.decode(lf);
|
|
137
53
|
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
dv.setUint8(20, 1);
|
|
145
|
-
if (style.underline)
|
|
146
|
-
dv.setUint8(21, 1);
|
|
147
|
-
const h = ffi.ffiCall(createFontIndirectW, [ffi.FFI_TYPE_POINTER], [lf], ffi.FFI_TYPE_POINTER);
|
|
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
|
+
});
|
|
148
60
|
fontCache.set(key, h ? h : 0);
|
|
149
61
|
return h ? h : null;
|
|
150
62
|
}
|
|
151
63
|
function handleCustomDraw(lParam, columns, data, hwnd) {
|
|
152
|
-
const stage =
|
|
64
|
+
const { dwDrawStage: stage } = NMCUSTOMDRAW.decode(lParam);
|
|
153
65
|
if (stage === 1 /* gui.CustomDrawStage.PREPAINT */)
|
|
154
66
|
return 32 /* gui.CustomDrawFlag.NOTIFYITEMDRAW */;
|
|
155
67
|
if (stage === 65537 /* gui.CustomDrawStage.ITEMPREPAINT */)
|
|
156
68
|
return 32 /* gui.CustomDrawFlag.NOTIFYSUBITEMDRAW */;
|
|
157
69
|
if (stage === 196609 /* gui.CustomDrawStage.SUBITEMPREPAINT */) {
|
|
158
|
-
|
|
159
|
-
const
|
|
160
|
-
const style = resolveCellStyle(columns, data,
|
|
70
|
+
// 只有子项阶段才按 NMLVCUSTOMDRAW(更大的结构)读,前面的阶段只读了 NMCUSTOMDRAW 的前缀
|
|
71
|
+
const cd = NMLVCUSTOMDRAW.decode(lParam);
|
|
72
|
+
const style = resolveCellStyle(columns, data, cd.dwItemSpec, cd.iSubItem);
|
|
161
73
|
if (!style)
|
|
162
74
|
return 0 /* gui.CustomDrawFlag.DODEFAULT */;
|
|
163
75
|
if (style.color !== undefined)
|
|
@@ -165,12 +77,9 @@ function handleCustomDraw(lParam, columns, data, hwnd) {
|
|
|
165
77
|
if (style.background !== undefined)
|
|
166
78
|
writeU32(lParam, CD_CLRTEXTBK, style.background);
|
|
167
79
|
const hfont = getCellFont(hwnd, style);
|
|
168
|
-
if (hfont &&
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
ffi.ffiCall(selectObjectFn, [ffi.FFI_TYPE_POINTER, ffi.FFI_TYPE_POINTER], [hdc, hfont], ffi.FFI_TYPE_POINTER);
|
|
172
|
-
return 2 /* gui.CustomDrawFlag.NEWFONT */;
|
|
173
|
-
}
|
|
80
|
+
if (hfont && cd.hdc) {
|
|
81
|
+
SelectObject(cd.hdc, hfont);
|
|
82
|
+
return 2 /* gui.CustomDrawFlag.NEWFONT */;
|
|
174
83
|
}
|
|
175
84
|
}
|
|
176
85
|
return 0 /* gui.CustomDrawFlag.DODEFAULT */;
|
|
@@ -201,14 +110,14 @@ function resolveCellStyle(columns, data, row, colIndex) {
|
|
|
201
110
|
return style || undefined;
|
|
202
111
|
}
|
|
203
112
|
function makeLVItem(i, sub, text, image) {
|
|
204
|
-
const textBuf =
|
|
205
|
-
const b = LVITEMW.
|
|
113
|
+
const textBuf = WCHAR.encode(text);
|
|
114
|
+
const b = LVITEMW.encode({
|
|
206
115
|
mask: 1 /* LvItemFlag.TEXT */ | (image !== undefined ? 2 /* LvItemFlag.IMAGE */ : 0),
|
|
207
116
|
iItem: i,
|
|
208
117
|
iSubItem: sub,
|
|
209
118
|
state: 0,
|
|
210
119
|
stateMask: 0,
|
|
211
|
-
pszText:
|
|
120
|
+
pszText: textBuf.ptr,
|
|
212
121
|
cchTextMax: 0,
|
|
213
122
|
iImage: image ?? 0,
|
|
214
123
|
lParam: 0,
|
|
@@ -245,7 +154,7 @@ const ListView = forwardRef(function ListViewInner({ columns, data, style, icons
|
|
|
245
154
|
const hbm = gui.CreateBitmapFromPixels(size, size, p);
|
|
246
155
|
if (hbm) {
|
|
247
156
|
gui.ImageListAdd(img, hbm);
|
|
248
|
-
|
|
157
|
+
DeleteObject(hbm);
|
|
249
158
|
}
|
|
250
159
|
}
|
|
251
160
|
gui.SendMessage(h, 4099 /* gui.LvMsg.SETIMAGELIST */, 1 /* gui.LvImageList.SMALL */, img);
|
|
@@ -265,12 +174,12 @@ const ListView = forwardRef(function ListViewInner({ columns, data, style, icons
|
|
|
265
174
|
gui.SendMessage(h, 4150 /* gui.LvMsg.SETEXTENDEDLISTVIEWSTYLE */, 0, 32 /* gui.LvExStyle.FULLROWSELECT */ | 1 /* gui.LvExStyle.GRIDLINES */ | 65536 /* gui.LvExStyle.DOUBLEBUFFER */);
|
|
266
175
|
const n = columns.length;
|
|
267
176
|
for (let j = 0; j < n; j++) {
|
|
268
|
-
const titleBuf =
|
|
269
|
-
const lvc = LVCOLUMNW.
|
|
177
|
+
const titleBuf = WCHAR.encode(columns[j].name);
|
|
178
|
+
const lvc = LVCOLUMNW.encode({
|
|
270
179
|
mask: 4 /* LvColumnMask.TEXT */ | 2 /* LvColumnMask.WIDTH */ | 1 /* LvColumnMask.FORMAT */,
|
|
271
180
|
fmt: alignToFmt(columns[j].align),
|
|
272
181
|
cx: columns[j].width ?? 100,
|
|
273
|
-
pszText:
|
|
182
|
+
pszText: titleBuf.ptr,
|
|
274
183
|
cchTextMax: 0,
|
|
275
184
|
iSubItem: j,
|
|
276
185
|
iImage: 0,
|
|
@@ -280,7 +189,7 @@ const ListView = forwardRef(function ListViewInner({ columns, data, style, icons
|
|
|
280
189
|
cxIdeal: 0,
|
|
281
190
|
});
|
|
282
191
|
lvc.__titleBuf = titleBuf;
|
|
283
|
-
gui.SendMessage(h, 4193 /* gui.LvMsg.INSERTCOLUMNW */, j,
|
|
192
|
+
gui.SendMessage(h, 4193 /* gui.LvMsg.INSERTCOLUMNW */, j, lvc.ptr);
|
|
284
193
|
}
|
|
285
194
|
}, [columns]);
|
|
286
195
|
useEffect(() => {
|
|
@@ -293,10 +202,10 @@ const ListView = forwardRef(function ListViewInner({ columns, data, style, icons
|
|
|
293
202
|
const record = data[i];
|
|
294
203
|
const img = getIcon ? getIcon(record, i) : undefined;
|
|
295
204
|
const itemBuf = makeLVItem(i, 0, cellText(record, columns[0], i), img);
|
|
296
|
-
gui.SendMessage(h, 4173 /* gui.LvMsg.INSERTITEMW */, 0,
|
|
205
|
+
gui.SendMessage(h, 4173 /* gui.LvMsg.INSERTITEMW */, 0, itemBuf.ptr);
|
|
297
206
|
for (let j = 1; j < nCols; j++) {
|
|
298
207
|
const subBuf = makeLVItem(i, j, cellText(record, columns[j], i));
|
|
299
|
-
gui.SendMessage(h, 4172 /* gui.LvMsg.SETITEMW */, 0,
|
|
208
|
+
gui.SendMessage(h, 4172 /* gui.LvMsg.SETITEMW */, 0, subBuf.ptr);
|
|
300
209
|
}
|
|
301
210
|
}
|
|
302
211
|
for (let j = 0; j < columns.length; j++) {
|
|
@@ -312,14 +221,12 @@ const ListView = forwardRef(function ListViewInner({ columns, data, style, icons
|
|
|
312
221
|
return handleCustomDraw(e.lParam, columns, data, lvRef.current);
|
|
313
222
|
}
|
|
314
223
|
if (code === -100 /* gui.LvNotifyCode.ITEMCHANGING */) {
|
|
315
|
-
const uNewState =
|
|
316
|
-
const uOldState = readU32(e.lParam, NMLV_UOLD);
|
|
224
|
+
const { uNewState, uOldState } = NMLISTVIEW.decode(e.lParam);
|
|
317
225
|
if ((uNewState & 2 /* LvItemState.SELECTED */) !== (uOldState & 2 /* LvItemState.SELECTED */))
|
|
318
226
|
return 1;
|
|
319
227
|
}
|
|
320
228
|
if (code === -2 /* gui.LvNotifyCode.CLICK */) {
|
|
321
|
-
const iItem =
|
|
322
|
-
const iSubItem = readI32(e.lParam, NMIA_SUBITEM);
|
|
229
|
+
const { iItem, iSubItem } = NMLISTVIEW.decode(e.lParam);
|
|
323
230
|
const col = columns[iSubItem];
|
|
324
231
|
const record = data[iItem];
|
|
325
232
|
if (col?.onCellClick && record !== undefined)
|
|
@@ -337,33 +244,21 @@ const ListView = forwardRef(function ListViewInner({ columns, data, style, icons
|
|
|
337
244
|
const h = lvRef.current;
|
|
338
245
|
if (!h)
|
|
339
246
|
return;
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
const sp = gui.GetCursorPos();
|
|
344
|
-
if (!sp)
|
|
247
|
+
// GetCursorPos 出参写入初值 → ScreenToClient 就地更新 → decode 读回
|
|
248
|
+
const pt = POINT.alloc();
|
|
249
|
+
if (!GetCursorPos(pt.ptr))
|
|
345
250
|
return;
|
|
346
|
-
|
|
347
|
-
const
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
const lvhi = new ArrayBuffer(24);
|
|
352
|
-
const lvd = new DataView(lvhi);
|
|
353
|
-
lvd.setInt32(0, sdv.getInt32(0, true), true);
|
|
354
|
-
lvd.setInt32(4, sdv.getInt32(4, true), true);
|
|
355
|
-
lvd.setInt32(12, -1, true);
|
|
356
|
-
lvd.setInt32(16, -1, true);
|
|
357
|
-
const lvhiPtr = bufPtr(lvhi);
|
|
358
|
-
gui.SendMessage(h, 4153 /* gui.LvMsg.SUBITEMHITTEST */, 0, lvhiPtr);
|
|
359
|
-
const iItem = readI32(lvhiPtr, 12);
|
|
360
|
-
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);
|
|
361
256
|
const style = resolveCellStyle(columns, data, iItem, iSubItem);
|
|
362
257
|
if (!style || style.cursor === undefined)
|
|
363
258
|
return;
|
|
364
|
-
const hc =
|
|
259
|
+
const hc = LoadCursor(NULL, style.cursor);
|
|
365
260
|
if (hc) {
|
|
366
|
-
|
|
261
|
+
SetCursor(hc);
|
|
367
262
|
return 1;
|
|
368
263
|
}
|
|
369
264
|
return;
|
|
@@ -1,114 +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
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
let _GetOpenFileNameW = 0;
|
|
9
|
-
let _SHBrowseForFolderW = 0;
|
|
10
|
-
let _SHGetPathFromIDListW = 0;
|
|
11
|
-
let _CoTaskMemFree = 0;
|
|
12
|
-
function ensureDlls() {
|
|
13
|
-
if (_GetOpenFileNameW)
|
|
14
|
-
return true;
|
|
15
|
-
const comdlg32 = win.LoadLibrary('comdlg32.dll');
|
|
16
|
-
const shell32 = win.LoadLibrary('shell32.dll');
|
|
17
|
-
const ole32 = win.LoadLibrary('ole32.dll');
|
|
18
|
-
if (!comdlg32 || !shell32 || !ole32)
|
|
19
|
-
return false;
|
|
20
|
-
const load = (lib, name) => win.GetProcAddress(lib, name) || 0;
|
|
21
|
-
_GetOpenFileNameW = load(comdlg32, 'GetOpenFileNameW');
|
|
22
|
-
_SHBrowseForFolderW = load(shell32, 'SHBrowseForFolderW');
|
|
23
|
-
_SHGetPathFromIDListW = load(shell32, 'SHGetPathFromIDListW');
|
|
24
|
-
_CoTaskMemFree = load(ole32, 'CoTaskMemFree');
|
|
25
|
-
return !!_GetOpenFileNameW && !!_SHBrowseForFolderW && !!_SHGetPathFromIDListW && !!_CoTaskMemFree;
|
|
26
|
-
}
|
|
27
|
-
function strToWide(s) {
|
|
28
|
-
return new TextEncoder('utf-16le').encode(s + '\0').buffer;
|
|
29
|
-
}
|
|
30
|
-
const _decoder = new TextDecoder('utf-16le');
|
|
31
|
-
function wideToStr(buf, offset = 0) {
|
|
32
|
-
const str = _decoder.decode(new Uint8Array(buf, offset));
|
|
33
|
-
const nullIdx = str.indexOf('\0');
|
|
34
|
-
return nullIdx >= 0 ? str.substring(0, nullIdx) : str;
|
|
35
|
-
}
|
|
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';
|
|
36
8
|
function openFileDialog(owner, filter, title, multiple) {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const
|
|
40
|
-
const filterWide = strToWide(filter);
|
|
41
|
-
const titleWide = title ? strToWide(title) : null;
|
|
9
|
+
const fileBuf = WCHAR.alloc(260);
|
|
10
|
+
const filterWide = WCHAR.encode(filter);
|
|
11
|
+
const titleWide = title ? WCHAR.encode(title) : null;
|
|
42
12
|
let flags = 0x1000 | 0x0800 | 0x0008;
|
|
43
13
|
if (multiple)
|
|
44
14
|
flags |= 0x0200;
|
|
45
15
|
flags |= 0x80000;
|
|
46
|
-
|
|
16
|
+
// 缺省字段跳过 = fresh buffer 上留 0
|
|
17
|
+
const ofn = OPENFILENAMEW.encode({
|
|
47
18
|
lStructSize: OPENFILENAMEW.size,
|
|
48
19
|
hwndOwner: owner,
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
lpstrCustomFilter: 0,
|
|
52
|
-
nMaxCustFilter: 0,
|
|
53
|
-
nFilterIndex: 0,
|
|
54
|
-
lpstrFile: ffi.bufferPtr(fileBuf),
|
|
20
|
+
lpstrFilter: filterWide.ptr,
|
|
21
|
+
lpstrFile: fileBuf.ptr,
|
|
55
22
|
nMaxFile: 260,
|
|
56
|
-
|
|
57
|
-
nMaxFileTitle: 0,
|
|
58
|
-
lpstrInitialDir: 0,
|
|
59
|
-
lpstrTitle: titleWide ? ffi.bufferPtr(titleWide) : 0,
|
|
23
|
+
lpstrTitle: titleWide?.ptr ?? 0,
|
|
60
24
|
Flags: flags,
|
|
61
|
-
nFileOffset: 0,
|
|
62
|
-
nFileExtension: 0,
|
|
63
|
-
lpstrDefExt: 0,
|
|
64
|
-
lCustData: 0,
|
|
65
|
-
lpfnHook: 0,
|
|
66
|
-
lpTemplateName: 0,
|
|
67
|
-
pvReserved: 0,
|
|
68
|
-
dwReserved: 0,
|
|
69
|
-
FlagsEx: 0,
|
|
70
25
|
});
|
|
71
|
-
const ret =
|
|
26
|
+
const ret = GetOpenFileName(ofn);
|
|
72
27
|
if (!ret)
|
|
73
28
|
return null;
|
|
74
29
|
if (!multiple) {
|
|
75
|
-
return
|
|
76
|
-
}
|
|
77
|
-
const dir = wideToStr(fileBuf);
|
|
78
|
-
let pos = (dir.length + 1) * 2;
|
|
79
|
-
const files = [];
|
|
80
|
-
while (pos < fileBuf.byteLength) {
|
|
81
|
-
const f = wideToStr(fileBuf, pos);
|
|
82
|
-
if (f.length === 0)
|
|
83
|
-
break;
|
|
84
|
-
files.push(dir + '\\' + f);
|
|
85
|
-
pos += (f.length + 1) * 2;
|
|
30
|
+
return WCHAR.decode(fileBuf);
|
|
86
31
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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];
|
|
90
37
|
}
|
|
91
38
|
function openFolderDialog(owner, title) {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
const titleWide = title ? strToWide(title) : null;
|
|
95
|
-
const bi = BROWSEINFOW.write({
|
|
39
|
+
const titleWide = title ? WCHAR.encode(title) : null;
|
|
40
|
+
const bi = BROWSEINFOW.encode({
|
|
96
41
|
hwndOwner: owner,
|
|
97
|
-
|
|
98
|
-
pszDisplayName: 0,
|
|
99
|
-
lpszTitle: titleWide ? ffi.bufferPtr(titleWide) : 0,
|
|
42
|
+
lpszTitle: titleWide?.ptr ?? 0,
|
|
100
43
|
ulFlags: 0x00000041,
|
|
101
|
-
lpfn: 0,
|
|
102
|
-
lParam: 0,
|
|
103
|
-
iImage: 0,
|
|
104
44
|
});
|
|
105
|
-
const pidl =
|
|
45
|
+
const pidl = SHBrowseForFolder(bi);
|
|
106
46
|
if (!pidl)
|
|
107
47
|
return null;
|
|
108
|
-
const pathBuf =
|
|
109
|
-
const ok =
|
|
110
|
-
|
|
111
|
-
return ok ?
|
|
48
|
+
const pathBuf = WCHAR.alloc(260);
|
|
49
|
+
const ok = SHGetPathFromIDList(pidl, pathBuf);
|
|
50
|
+
CoTaskMemFree(pidl);
|
|
51
|
+
return ok ? WCHAR.decode(pathBuf) : null;
|
|
112
52
|
}
|
|
113
53
|
export const PathPicker = forwardRef((props, ref) => {
|
|
114
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,25 +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
|
-
import {
|
|
4
|
+
import { WCHAR } from '../../ffi/bind.js';
|
|
5
|
+
import { TCITEMW } from '../../windows/structs.js';
|
|
6
6
|
import { nmCode } from '../nmhdr.js';
|
|
7
|
-
const TCITEMW = struct({
|
|
8
|
-
mask: 'u32',
|
|
9
|
-
dwState: 'u32',
|
|
10
|
-
dwStateMask: 'u32',
|
|
11
|
-
pszText: 'ptr',
|
|
12
|
-
cchTextMax: 'i32',
|
|
13
|
-
iImage: 'i32',
|
|
14
|
-
lParam: 'ptr',
|
|
15
|
-
});
|
|
16
|
-
function textToUtf16(s) {
|
|
17
|
-
const buf = new ArrayBuffer((s.length + 1) * 2);
|
|
18
|
-
const dv = new DataView(buf);
|
|
19
|
-
for (let i = 0; i < s.length; i++)
|
|
20
|
-
dv.setUint16(i * 2, s.charCodeAt(i), true);
|
|
21
|
-
return buf;
|
|
22
|
-
}
|
|
23
7
|
export const Tab = forwardRef(({ tabs, selectedIndex: controlledIndex, defaultSelectedIndex = 0, onChange, style }, ref) => {
|
|
24
8
|
const [internalIndex, setInternalIndex] = useState(defaultSelectedIndex);
|
|
25
9
|
const isControlled = controlledIndex !== undefined;
|
|
@@ -31,17 +15,17 @@ export const Tab = forwardRef(({ tabs, selectedIndex: controlledIndex, defaultSe
|
|
|
31
15
|
return;
|
|
32
16
|
gui.SendMessage(h, 4873 /* gui.TcMsg.DELETEALLITEMS */, 0, 0);
|
|
33
17
|
for (let i = 0; i < tabs.length; i++) {
|
|
34
|
-
const titleBuf =
|
|
35
|
-
const tci = TCITEMW.
|
|
18
|
+
const titleBuf = WCHAR.encode(tabs[i].title);
|
|
19
|
+
const tci = TCITEMW.encode({
|
|
36
20
|
mask: 1 /* gui.TcItemFlag.TEXT */,
|
|
37
21
|
dwState: 0,
|
|
38
22
|
dwStateMask: 0,
|
|
39
|
-
pszText:
|
|
23
|
+
pszText: titleBuf.ptr,
|
|
40
24
|
cchTextMax: tabs[i].title.length + 1,
|
|
41
25
|
iImage: 0,
|
|
42
26
|
lParam: 0,
|
|
43
27
|
});
|
|
44
|
-
const tciPtr =
|
|
28
|
+
const tciPtr = tci.ptr;
|
|
45
29
|
gui.SendMessage(h, 4926 /* gui.TcMsg.INSERTITEMW */, i, tciPtr);
|
|
46
30
|
}
|
|
47
31
|
gui.SendMessage(h, 4876 /* gui.TcMsg.SETCURSEL */, sel, 0);
|