quickwin 1.3.0 → 2.1.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 +18 -24
- package/README.md +17 -23
- package/examples/PdfCanvas.js +8 -37
- package/examples/PdfCanvas.tsx +11 -41
- package/examples/exec_server.js +20155 -1420
- 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 +1424 -1180
- package/examples/pdf_viewer.tsx +16 -67
- package/examples/setres.js +22 -47
- package/examples/setres.ts +23 -57
- package/examples/test_gallery.js +1418 -1075
- package/examples/test_react_lazy.js +1391 -1059
- package/examples/test_react_listbox.js +1390 -1058
- package/examples/test_root_window.js +1384 -1052
- package/examples/test_tab.js +1391 -1059
- package/lib/abort.js +70 -0
- package/lib/abort.ts +69 -0
- package/lib/certs.js +219 -0
- package/lib/certs.ts +229 -0
- package/lib/exec-log.js +15 -0
- package/lib/exec-log.ts +30 -0
- package/lib/fetch.js +88 -7
- package/lib/fetch.ts +73 -7
- package/lib/ffi/bind.js +441 -0
- package/lib/ffi/bind.ts +539 -0
- package/lib/ffi/ctype.js +194 -0
- package/lib/ffi/ctype.ts +369 -0
- package/lib/ffi/index.js +6 -0
- package/lib/ffi/index.ts +6 -0
- package/lib/ffi/struct.js +490 -0
- package/lib/ffi/struct.ts +753 -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 +50 -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 +5 -3
- package/lib/react-qw/index.ts +8 -3
- 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 +20 -23
- package/lib/text-measure.ts +22 -26
- package/lib/websocket.js +44 -4
- package/lib/websocket.ts +40 -4
- package/lib/windows/advapi32.js +53 -0
- package/lib/windows/advapi32.ts +64 -0
- package/lib/windows/comctl32.js +18 -0
- package/lib/windows/comctl32.ts +19 -0
- package/lib/windows/comdlg32.js +8 -0
- package/lib/windows/comdlg32.ts +10 -0
- package/lib/windows/gdi32.js +392 -0
- package/lib/windows/gdi32.ts +420 -0
- package/lib/windows/kernel32.js +262 -0
- package/lib/windows/kernel32.ts +291 -0
- package/lib/windows/msimg32.js +38 -0
- package/lib/windows/msimg32.ts +45 -0
- package/lib/windows/ntdll.js +9 -0
- package/lib/windows/ntdll.ts +11 -0
- package/lib/windows/ole32.js +7 -0
- package/lib/windows/ole32.ts +9 -0
- package/lib/windows/psapi.js +32 -0
- package/lib/windows/psapi.ts +37 -0
- package/lib/windows/shell32.js +13 -0
- package/lib/windows/shell32.ts +16 -0
- package/lib/windows/structs.js +519 -0
- package/lib/windows/structs.ts +566 -0
- package/lib/windows/user32.js +572 -0
- package/lib/windows/user32.ts +607 -0
- package/lib/windows/version.js +29 -0
- package/lib/windows/version.ts +35 -0
- package/lib/windows/winspool.js +71 -0
- package/lib/windows/winspool.ts +80 -0
- package/package.json +1 -1
- package/quickwin.d.ts +31 -40
- package/quickwin_const.d.ts +137 -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 +19 -4
- package/test/run.ts +20 -5
- package/test/test_basic_fetch.js +29 -0
- package/test/test_basic_fetch.ts +26 -0
- package/test/test_ffi.js +27 -45
- package/test/test_ffi.ts +35 -54
- package/test/test_ffi_abi.js +290 -0
- package/test/test_ffi_abi.ts +274 -0
- package/test/test_ffi_array.js +116 -0
- package/test/test_ffi_array.ts +141 -0
- package/test/test_ffi_bind.js +620 -8
- package/test/test_ffi_bind.ts +630 -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_files.js +102 -0
- package/test/test_files.ts +121 -0
- package/test/test_gdi_draw.js +681 -0
- package/test/test_gdi_draw.ts +725 -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 +74 -11
- package/test/test_net_fetch.ts +73 -10
- package/test/test_net_websocket.js +1 -1
- package/test/test_net_websocket.ts +1 -1
- package/test/test_printer.js +137 -0
- package/test/test_printer.ts +148 -0
- package/test/test_proc.js +141 -0
- package/test/test_proc.ts +163 -0
- package/test/test_react_complex.js +1401 -1069
- package/test/test_react_counter.js +1391 -1059
- package/test/test_react_flex.js +1411 -1072
- package/test/test_react_hidden.js +1394 -1062
- package/test/test_react_listview_link.js +1391 -1059
- package/test/test_react_notify.js +18638 -0
- package/test/test_react_render.js +1391 -1059
- package/test/test_registry.js +74 -0
- package/test/test_registry.ts +95 -0
- package/test/test_sysinfo.js +122 -0
- package/test/test_sysinfo.ts +141 -0
- package/test/test_wolfssl_read.js +45 -43
- package/test/test_wolfssl_read.ts +42 -40
- 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,30 +1,20 @@
|
|
|
1
1
|
import { useRef, useEffect, Children, cloneElement } from 'react';
|
|
2
2
|
import * as gui from 'gui';
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
cbSize: 'u32',
|
|
7
|
-
uFlags: 'u32',
|
|
8
|
-
hwnd: 'ptr',
|
|
9
|
-
uId: 'ptr',
|
|
10
|
-
rect: 'i32[4]',
|
|
11
|
-
hinst: 'ptr',
|
|
12
|
-
lpszText: 'ptr',
|
|
13
|
-
lParam: 'ptr',
|
|
14
|
-
lpReserved: 'ptr', // WinXP+ 追加字段(系统 sizeof 含之)
|
|
15
|
-
});
|
|
3
|
+
import { NULL, PtrArrayBuffer } from '../../ffi/ctype.js';
|
|
4
|
+
import { TTTOOLINFOW } from '../../windows/structs.js';
|
|
5
|
+
import { SetWindowPos } from '../../windows/user32.js';
|
|
16
6
|
function buildToolInfo(hTarget, text) {
|
|
17
7
|
const size = TTTOOLINFOW.size;
|
|
18
8
|
const textLen = (text.length + 1) * 2;
|
|
19
|
-
const buf = new
|
|
20
|
-
TTTOOLINFOW.
|
|
9
|
+
const buf = new PtrArrayBuffer(size + textLen);
|
|
10
|
+
TTTOOLINFOW.encode({
|
|
21
11
|
cbSize: size,
|
|
22
12
|
uFlags: 16 /* gui.TtToolFlag.SUBCLASS */ | 1 /* gui.TtToolFlag.IDISHWND */,
|
|
23
13
|
hwnd: hTarget,
|
|
24
14
|
uId: hTarget,
|
|
25
15
|
rect: [0, 0, 0, 0],
|
|
26
|
-
hinst:
|
|
27
|
-
lpszText:
|
|
16
|
+
hinst: NULL,
|
|
17
|
+
lpszText: buf.ptr + size,
|
|
28
18
|
lParam: 0,
|
|
29
19
|
lpReserved: 0,
|
|
30
20
|
}, buf);
|
|
@@ -46,16 +36,15 @@ function Tooltip({ text, children, balloon }) {
|
|
|
46
36
|
if (!hTT)
|
|
47
37
|
return;
|
|
48
38
|
hTTRef.current = hTT;
|
|
49
|
-
|
|
39
|
+
SetWindowPos(hTT, -1 /* gui.SetWindowPosHwnd.TOPMOST */, 0, 0, 0, 0, 2 /* gui.SetWindowPosFlag.SWP_NOMOVE */ | 1 /* gui.SetWindowPosFlag.SWP_NOSIZE */ | 16 /* gui.SetWindowPosFlag.SWP_NOACTIVATE */);
|
|
50
40
|
const ti = buildToolInfo(hTarget, text);
|
|
51
|
-
|
|
52
|
-
gui.SendMessage(hTT, 1074 /* gui.TtMsg.ADDTOOLW */, 0, tiPtr);
|
|
41
|
+
gui.SendMessage(hTT, 1074 /* gui.TtMsg.ADDTOOLW */, 0, ti.ptr);
|
|
53
42
|
gui.SendMessage(hTT, 1048 /* gui.TtMsg.SETMAXTIPWIDTH */, 0, 400);
|
|
54
43
|
gui.SendMessage(hTT, 1025 /* gui.TtMsg.ACTIVATE */, 1, 0);
|
|
55
44
|
return () => {
|
|
56
45
|
if (hTT) {
|
|
57
46
|
const ti2 = buildToolInfo(hTarget, text);
|
|
58
|
-
gui.SendMessage(hTT, 1075 /* gui.TtMsg.DELTOOLW */, 0,
|
|
47
|
+
gui.SendMessage(hTT, 1075 /* gui.TtMsg.DELTOOLW */, 0, ti2.ptr);
|
|
59
48
|
gui.DestroyWindow(hTT);
|
|
60
49
|
}
|
|
61
50
|
hTTRef.current = null;
|
|
@@ -1,37 +1,14 @@
|
|
|
1
1
|
import { jsx as _jsx } 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 { TVITEM, TVINSERTSTRUCT } from '../../windows/structs.js';
|
|
6
6
|
import { nmCode } from '../nmhdr.js';
|
|
7
|
-
const TVITEM = struct({
|
|
8
|
-
mask: 'u32',
|
|
9
|
-
hItem: 'ptr',
|
|
10
|
-
state: 'u32',
|
|
11
|
-
stateMask: 'u32',
|
|
12
|
-
pszText: 'ptr',
|
|
13
|
-
cchTextMax: 'i32',
|
|
14
|
-
iImage: 'i32',
|
|
15
|
-
iSelectedImage: 'i32',
|
|
16
|
-
cChildren: 'i32',
|
|
17
|
-
lParam: 'ptr',
|
|
18
|
-
});
|
|
19
|
-
const TVINSERTSTRUCT = struct({ hParent: 'ptr', hInsertAfter: 'ptr', item: TVITEM });
|
|
20
|
-
function textToUtf16(s) {
|
|
21
|
-
const buf = new ArrayBuffer((s.length + 1) * 2);
|
|
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 bufPtr(buf) {
|
|
28
|
-
return ffi.bufferPtr(buf);
|
|
29
|
-
}
|
|
30
7
|
function buildTvItem(textPtr, cChildren) {
|
|
31
8
|
let mask = 1 /* gui.TvIfFlag.TEXT */;
|
|
32
9
|
if (cChildren > 0)
|
|
33
10
|
mask |= 64 /* gui.TvIfFlag.CHILDREN */;
|
|
34
|
-
return TVITEM.
|
|
11
|
+
return TVITEM.encode({
|
|
35
12
|
mask, hItem: 0, state: 0, stateMask: 0,
|
|
36
13
|
pszText: textPtr, cchTextMax: 260,
|
|
37
14
|
iImage: 0, iSelectedImage: 0, cChildren, lParam: 0,
|
|
@@ -40,11 +17,11 @@ function buildTvItem(textPtr, cChildren) {
|
|
|
40
17
|
function insertItems(hTree, nodes, parentHandle, hItemMap, keyMap) {
|
|
41
18
|
for (const node of nodes) {
|
|
42
19
|
const cChildren = node.children && node.children.length > 0 ? 1 : 0;
|
|
43
|
-
const textBuf =
|
|
44
|
-
const itemBuf = buildTvItem(
|
|
45
|
-
const item = TVITEM.
|
|
46
|
-
const tvins = TVINSERTSTRUCT.
|
|
47
|
-
const hItem = gui.SendMessage(hTree, 4402 /* gui.TvMsg.INSERTITEMW */, 0,
|
|
20
|
+
const textBuf = WCHAR.encode(node.label);
|
|
21
|
+
const itemBuf = buildTvItem(textBuf.ptr, cChildren);
|
|
22
|
+
const item = TVITEM.decode(itemBuf);
|
|
23
|
+
const tvins = TVINSERTSTRUCT.encode({ hParent: parentHandle, hInsertAfter: -65536 /* gui.TvInsertAfter.ROOT */, item });
|
|
24
|
+
const hItem = gui.SendMessage(hTree, 4402 /* gui.TvMsg.INSERTITEMW */, 0, tvins.ptr);
|
|
48
25
|
hItemMap.set(hItem, node);
|
|
49
26
|
if (node.key)
|
|
50
27
|
keyMap.set(node.key, hItem);
|
package/lib/react-qw/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as gui from 'gui';
|
|
2
|
+
import { DefWindowProc, GetSystemMetrics } from '../windows/user32.js';
|
|
2
3
|
import reconciler, { scaleFactor, instancesByHwnd, forceFlexLayout } from './reconciler.js';
|
|
3
4
|
const noop = () => { };
|
|
4
5
|
let defaultClassRegistered = false;
|
|
@@ -20,7 +21,7 @@ function ensureDefaultClass() {
|
|
|
20
21
|
return result;
|
|
21
22
|
if (msg === 5 /* gui.WmMsg.SIZE */)
|
|
22
23
|
forceFlexLayout(hwnd);
|
|
23
|
-
return
|
|
24
|
+
return DefWindowProc(hwnd, msg, wParam, lParam);
|
|
24
25
|
});
|
|
25
26
|
defaultClassRegistered = true;
|
|
26
27
|
return DEFAULT_CLASS;
|
|
@@ -33,7 +34,8 @@ export function createRoot(container) {
|
|
|
33
34
|
const ws = (cfg.ws ?? 0) | 13565952 /* gui.WindowStyle.OVERLAPPEDWINDOW */;
|
|
34
35
|
const w = (cfg.width ?? 800) * scaleFactor;
|
|
35
36
|
const h = (cfg.height ?? 600) * scaleFactor;
|
|
36
|
-
|
|
37
|
+
// GetScreenSize 等价内联:SM_CXSCREEN=0 / SM_CYSCREEN=1
|
|
38
|
+
const [sw, sh] = [GetSystemMetrics(0), GetSystemMetrics(1)];
|
|
37
39
|
const x = cfg.x === undefined ? ((sw - w) / 2) | 0
|
|
38
40
|
: cfg.x === -2147483648 /* gui.CreatePos.USEDEFAULT */ ? -2147483648 /* gui.CreatePos.USEDEFAULT */ : cfg.x * scaleFactor;
|
|
39
41
|
const y = cfg.y === undefined ? ((sh - h) / 2) | 0
|
|
@@ -48,7 +50,7 @@ export function createRoot(container) {
|
|
|
48
50
|
else {
|
|
49
51
|
hwnd = container;
|
|
50
52
|
}
|
|
51
|
-
const root = reconciler.createContainer(hwnd, 0, null, false, null, '',
|
|
53
|
+
const root = reconciler.createContainer(hwnd, 0, null, false, null, '', (error, info) => console.log('[React uncaught]', error, error.stack, info.componentStack), (error, info) => console.log('[React caught]', error, error.stack, info.componentStack), (error, info) => console.log('[React recoverable]', error, error.stack, info.componentStack), noop);
|
|
52
54
|
const rootInst = { hwnd, type: '_root', props: { type: '_root', style: { flexDirection: 'column', alignItems: 'stretch' } }, children: [] };
|
|
53
55
|
instancesByHwnd.set(hwnd, rootInst);
|
|
54
56
|
if (typeof container === 'object')
|
package/lib/react-qw/index.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as gui from 'gui'
|
|
2
2
|
import React from 'react'
|
|
3
|
+
import { DefWindowProc, GetSystemMetrics } from '../windows/user32.js'
|
|
3
4
|
import reconciler, { scaleFactor, instancesByHwnd, forceFlexLayout, type Instance } from './reconciler.js'
|
|
4
5
|
|
|
5
6
|
const noop = () => { }
|
|
@@ -32,7 +33,7 @@ function ensureDefaultClass() {
|
|
|
32
33
|
const result = entry?.onEvent?.({ hwnd, msg, wParam, lParam })
|
|
33
34
|
if (typeof result === 'number') return result
|
|
34
35
|
if (msg === gui.WmMsg.SIZE) forceFlexLayout(hwnd)
|
|
35
|
-
return
|
|
36
|
+
return DefWindowProc(hwnd, msg, wParam, lParam)
|
|
36
37
|
})
|
|
37
38
|
defaultClassRegistered = true
|
|
38
39
|
return DEFAULT_CLASS
|
|
@@ -46,7 +47,8 @@ export function createRoot(container: gui.HWND | RootWindowConfig) {
|
|
|
46
47
|
const ws = (cfg.ws ?? 0) | gui.WindowStyle.OVERLAPPEDWINDOW
|
|
47
48
|
const w = (cfg.width ?? 800) * scaleFactor
|
|
48
49
|
const h = (cfg.height ?? 600) * scaleFactor
|
|
49
|
-
|
|
50
|
+
// GetScreenSize 等价内联:SM_CXSCREEN=0 / SM_CYSCREEN=1
|
|
51
|
+
const [sw, sh] = [GetSystemMetrics(0), GetSystemMetrics(1)]
|
|
50
52
|
const x = cfg.x === undefined ? ((sw - w) / 2) | 0
|
|
51
53
|
: cfg.x === gui.CreatePos.USEDEFAULT ? gui.CreatePos.USEDEFAULT
|
|
52
54
|
: cfg.x * scaleFactor
|
|
@@ -62,7 +64,10 @@ export function createRoot(container: gui.HWND | RootWindowConfig) {
|
|
|
62
64
|
}
|
|
63
65
|
const root = reconciler.createContainer(
|
|
64
66
|
hwnd, 0, null, false, null, '',
|
|
65
|
-
|
|
67
|
+
(error, info) => console.log('[React uncaught]', error, error.stack, info.componentStack),
|
|
68
|
+
(error, info) => console.log('[React caught]', error, error.stack, info.componentStack),
|
|
69
|
+
(error, info) => console.log('[React recoverable]', error, error.stack, info.componentStack),
|
|
70
|
+
noop,
|
|
66
71
|
)
|
|
67
72
|
const rootInst: Instance = { hwnd, type: '_root', props: { type: '_root', style: { flexDirection: 'column', alignItems: 'stretch' } }, children: [] }
|
|
68
73
|
instancesByHwnd.set(hwnd, rootInst)
|
package/lib/react-qw/nmhdr.js
CHANGED
|
@@ -1,26 +1,5 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
import { struct } from '../ffi-struct.js';
|
|
4
|
-
// 架构相关常量:32 位(ia32)/64 位(x64) Windows ABI
|
|
5
|
-
export const IS64 = os.arch === 'x64';
|
|
6
|
-
export const PTR_SIZE = IS64 ? 8 : 4;
|
|
7
|
-
// NMHDR = hwndFrom(ptr) + idFrom(ptr) + code(i32)。
|
|
8
|
-
// 用 ffi-struct 定义以复现 MSVC/嵌套结构的“尾 padding 传染”(内嵌 NMHDR 占满 24/12 字节)。
|
|
9
|
-
export const NMHDR = struct({ hwndFrom: 'ptr', idFrom: 'ptr', code: 'i32' });
|
|
10
|
-
export const NMHDR_SIZE = NMHDR.size; // 24 (x64) / 12 (ia32)
|
|
11
|
-
export const NMHDR_CODE = NMHDR.offsetOf('code'); // 16 / 8
|
|
12
|
-
export function readI32At(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
|
-
}
|
|
16
|
-
export function readU32At(ptr, offset) {
|
|
17
|
-
return ffi.readByte(ptr + offset) | (ffi.readByte(ptr + offset + 1) << 8) |
|
|
18
|
-
(ffi.readByte(ptr + offset + 2) << 16) | (ffi.readByte(ptr + offset + 3) << 24) >>> 0;
|
|
19
|
-
}
|
|
20
|
-
export function readU16At(ptr, offset) {
|
|
21
|
-
return ffi.readByte(ptr + offset) | (ffi.readByte(ptr + offset + 1) << 8);
|
|
22
|
-
}
|
|
23
|
-
// 读 NMHDR.code(WM_NOTIFY 通知码)
|
|
1
|
+
import { NMHDR } from '../windows/structs.js';
|
|
2
|
+
// 读 NMHDR.code(WM_NOTIFY 通知码):lParam 是通知结构的原生指针,decode 直接从该地址读
|
|
24
3
|
export function nmCode(lParam) {
|
|
25
|
-
return
|
|
4
|
+
return NMHDR.decode(lParam).code;
|
|
26
5
|
}
|
package/lib/react-qw/nmhdr.ts
CHANGED
|
@@ -1,32 +1,7 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import { struct } from '../ffi-struct.js'
|
|
1
|
+
import type { Ptr } from '../ffi/ctype.js'
|
|
2
|
+
import { NMHDR } from '../windows/structs.js'
|
|
4
3
|
|
|
5
|
-
//
|
|
6
|
-
export const IS64 = os.arch === 'x64'
|
|
7
|
-
export const PTR_SIZE = IS64 ? 8 : 4
|
|
8
|
-
|
|
9
|
-
// NMHDR = hwndFrom(ptr) + idFrom(ptr) + code(i32)。
|
|
10
|
-
// 用 ffi-struct 定义以复现 MSVC/嵌套结构的“尾 padding 传染”(内嵌 NMHDR 占满 24/12 字节)。
|
|
11
|
-
export const NMHDR = struct({ hwndFrom: 'ptr', idFrom: 'ptr', code: 'i32' })
|
|
12
|
-
export const NMHDR_SIZE = NMHDR.size // 24 (x64) / 12 (ia32)
|
|
13
|
-
export const NMHDR_CODE = NMHDR.offsetOf('code') // 16 / 8
|
|
14
|
-
|
|
15
|
-
export function readI32At(ptr: number, offset: number): number {
|
|
16
|
-
return ffi.readByte(ptr + offset) | (ffi.readByte(ptr + offset + 1) << 8) |
|
|
17
|
-
(ffi.readByte(ptr + offset + 2) << 16) | (ffi.readByte(ptr + offset + 3) << 24)
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export function readU32At(ptr: number, offset: number): number {
|
|
21
|
-
return ffi.readByte(ptr + offset) | (ffi.readByte(ptr + offset + 1) << 8) |
|
|
22
|
-
(ffi.readByte(ptr + offset + 2) << 16) | (ffi.readByte(ptr + offset + 3) << 24) >>> 0
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export function readU16At(ptr: number, offset: number): number {
|
|
26
|
-
return ffi.readByte(ptr + offset) | (ffi.readByte(ptr + offset + 1) << 8)
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// 读 NMHDR.code(WM_NOTIFY 通知码)
|
|
4
|
+
// 读 NMHDR.code(WM_NOTIFY 通知码):lParam 是通知结构的原生指针,decode 直接从该地址读
|
|
30
5
|
export function nmCode(lParam: number): number {
|
|
31
|
-
return
|
|
32
|
-
}
|
|
6
|
+
return NMHDR.decode(lParam as Ptr<'NMHDR'>).code
|
|
7
|
+
}
|
package/lib/react-qw/props.js
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import * as gui from 'gui';
|
|
2
|
+
import { SetWindowText, EnableWindow } from '../windows/user32.js';
|
|
2
3
|
export function applyProps(instance, newProps, _oldProps) {
|
|
3
4
|
const hwnd = instance.hwnd;
|
|
4
5
|
instance.props = newProps;
|
|
6
|
+
// 延迟控件(DELAYED_CONTROLS)建窗前 hwnd 为 null:窗口操作此刻全是 no-op
|
|
7
|
+
// (text/ws/style 在 ensureChildWindow 建窗时应用),FFI 位不收 JS null——早退。
|
|
8
|
+
if (hwnd === null)
|
|
9
|
+
return;
|
|
5
10
|
const textVal = 'text' in newProps ? newProps.text : (typeof newProps.children === 'string' ? newProps.children :
|
|
6
11
|
typeof newProps.children === 'number' ? String(newProps.children) : undefined);
|
|
7
12
|
if (textVal !== undefined) {
|
|
@@ -10,13 +15,13 @@ export function applyProps(instance, newProps, _oldProps) {
|
|
|
10
15
|
const sel = gui.SendMessage(hwnd, 176 /* gui.EditMsg.GETSEL */, 0, 0);
|
|
11
16
|
cursor = sel >>> 16;
|
|
12
17
|
}
|
|
13
|
-
|
|
18
|
+
SetWindowText(hwnd, textVal);
|
|
14
19
|
if (instance.type === 'EDIT' && cursor >= 0) {
|
|
15
20
|
gui.SendMessage(hwnd, 177 /* gui.EditMsg.SETSEL */, cursor, cursor);
|
|
16
21
|
}
|
|
17
22
|
}
|
|
18
23
|
if ('disabled' in newProps) {
|
|
19
|
-
|
|
24
|
+
EnableWindow(hwnd, !newProps.disabled ? 1 : 0);
|
|
20
25
|
}
|
|
21
26
|
if ('hidden' in newProps) {
|
|
22
27
|
gui.ShowWindow(hwnd, newProps.hidden ? 0 /* gui.ShowWindowCmd.HIDE */ : 5 /* gui.ShowWindowCmd.SHOW */);
|
package/lib/react-qw/props.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as gui from 'gui'
|
|
2
|
+
import { SetWindowText, EnableWindow } from '../windows/user32.js'
|
|
2
3
|
import type { Instance, Props } from './reconciler.js'
|
|
3
4
|
|
|
4
5
|
export function applyProps(
|
|
@@ -6,8 +7,11 @@ export function applyProps(
|
|
|
6
7
|
newProps: Props,
|
|
7
8
|
_oldProps: Props,
|
|
8
9
|
) {
|
|
9
|
-
const hwnd = instance.hwnd
|
|
10
|
+
const hwnd = instance.hwnd
|
|
10
11
|
instance.props = newProps
|
|
12
|
+
// 延迟控件(DELAYED_CONTROLS)建窗前 hwnd 为 null:窗口操作此刻全是 no-op
|
|
13
|
+
// (text/ws/style 在 ensureChildWindow 建窗时应用),FFI 位不收 JS null——早退。
|
|
14
|
+
if (hwnd === null) return
|
|
11
15
|
|
|
12
16
|
const textVal = 'text' in newProps ? newProps.text : (
|
|
13
17
|
typeof newProps.children === 'string' ? newProps.children :
|
|
@@ -19,13 +23,13 @@ export function applyProps(
|
|
|
19
23
|
const sel = gui.SendMessage(hwnd, gui.EditMsg.GETSEL, 0, 0)
|
|
20
24
|
cursor = sel >>> 16
|
|
21
25
|
}
|
|
22
|
-
|
|
26
|
+
SetWindowText(hwnd, textVal)
|
|
23
27
|
if (instance.type === 'EDIT' && cursor >= 0) {
|
|
24
28
|
gui.SendMessage(hwnd, gui.EditMsg.SETSEL, cursor, cursor)
|
|
25
29
|
}
|
|
26
30
|
}
|
|
27
31
|
if ('disabled' in newProps) {
|
|
28
|
-
|
|
32
|
+
EnableWindow(hwnd, !newProps.disabled ? 1 : 0)
|
|
29
33
|
}
|
|
30
34
|
if ('hidden' in newProps) {
|
|
31
35
|
gui.ShowWindow(hwnd, newProps.hidden ? gui.ShowWindowCmd.HIDE : gui.ShowWindowCmd.SHOW)
|
|
@@ -3,27 +3,37 @@ import { createContext } from 'react';
|
|
|
3
3
|
import { DefaultEventPriority, NoEventPriority } from 'react-reconciler/constants';
|
|
4
4
|
import * as gui from 'gui';
|
|
5
5
|
import * as os from 'os';
|
|
6
|
+
import { GetDC, ReleaseDC, GetWindowLongPtr, CallWindowProc, SetWindowPos, InvalidateRect, SetParent, SetWindowText } from '../windows/user32.js';
|
|
7
|
+
import { GetDeviceCaps } from '../windows/gdi32.js';
|
|
8
|
+
import { NULL } from '../ffi/ctype.js';
|
|
6
9
|
import { applyProps } from './props.js';
|
|
7
10
|
import { calculateFlexLayout } from './layout.js';
|
|
8
11
|
import { getButtonIdealSize, measureTextForHwnd } from '../text-measure.js';
|
|
9
12
|
const dpiFont = gui.CreateSystemDpiFont();
|
|
10
|
-
|
|
13
|
+
// GetScaleFactor 等价内联:GetDC → GetDeviceCaps(LOGPIXELSX=88) → ReleaseDC 配对
|
|
14
|
+
export const scaleFactor = (() => {
|
|
15
|
+
const hdc = GetDC(NULL);
|
|
16
|
+
const dpi = hdc ? GetDeviceCaps(hdc, 88) : 96;
|
|
17
|
+
if (hdc)
|
|
18
|
+
ReleaseDC(NULL, hdc);
|
|
19
|
+
return dpi / 96;
|
|
20
|
+
})();
|
|
11
21
|
const DELAYED_CONTROLS = new Set(['SysListView32', 'SysTreeView32', 'LISTBOX', 'COMBOBOX', 'msctls_trackbar32', 'SysDateTimePick32', 'SysLink']);
|
|
12
22
|
function isDelayedControl(winClass) {
|
|
13
23
|
return DELAYED_CONTROLS.has(winClass);
|
|
14
24
|
}
|
|
15
25
|
function setupWindowProc(instance, hwnd) {
|
|
16
26
|
instance.hwnd = hwnd;
|
|
17
|
-
const oldProc =
|
|
27
|
+
const oldProc = GetWindowLongPtr(hwnd, -4 /* gui.Gwlp.WNDPROC */);
|
|
18
28
|
instancesByHwnd.set(hwnd, instance);
|
|
19
29
|
gui.SetWindowProc(hwnd, (hwnd, msg, wParam, lParam) => {
|
|
20
30
|
const ev = instance.props.onEvent;
|
|
21
31
|
if (!ev)
|
|
22
|
-
return
|
|
32
|
+
return CallWindowProc(oldProc, hwnd, msg, wParam, lParam);
|
|
23
33
|
if (typeof ev === 'object') {
|
|
24
|
-
return ev.fn({ hwnd, msg, wParam, lParam, callOldWndProc: () =>
|
|
34
|
+
return ev.fn({ hwnd, msg, wParam, lParam, callOldWndProc: () => CallWindowProc(oldProc, hwnd, msg, wParam, lParam) });
|
|
25
35
|
}
|
|
26
|
-
const result =
|
|
36
|
+
const result = CallWindowProc(oldProc, hwnd, msg, wParam, lParam);
|
|
27
37
|
const override = ev({ hwnd, msg, wParam, lParam });
|
|
28
38
|
return typeof override === 'number' && Number.isInteger(override) ? override : result;
|
|
29
39
|
});
|
|
@@ -94,8 +104,8 @@ function runFlexLayout(inst) {
|
|
|
94
104
|
if (lr && lr.x === r.x && lr.y === r.y && lr.w === r.width && lr.h === r.height)
|
|
95
105
|
continue;
|
|
96
106
|
console.log('flex: set', child.type, child.hwnd, 'to', r.x, r.y, r.width, r.height);
|
|
97
|
-
|
|
98
|
-
|
|
107
|
+
SetWindowPos(child.hwnd, 0 /* gui.SetWindowPosHwnd.TOP */, (r.x + pl) * scaleFactor, (r.y + pt) * scaleFactor, r.width * scaleFactor, r.height * scaleFactor, 64 /* gui.SetWindowPosFlag.SWP_SHOWWINDOW */);
|
|
108
|
+
InvalidateRect(child.hwnd, NULL, 1);
|
|
99
109
|
child.lastRect = { x: r.x, y: r.y, w: r.width, h: r.height };
|
|
100
110
|
}
|
|
101
111
|
for (const c of children)
|
|
@@ -143,7 +153,7 @@ const hostConfig = {
|
|
|
143
153
|
ensureChildWindow(child, parent.hwnd);
|
|
144
154
|
}
|
|
145
155
|
else {
|
|
146
|
-
|
|
156
|
+
SetParent(child.hwnd, parent.hwnd ?? NULL);
|
|
147
157
|
}
|
|
148
158
|
if (DEBUG)
|
|
149
159
|
console.log('[reconciler] appendInitialChild parent:', parent.hwnd, 'child:', child.hwnd);
|
|
@@ -154,7 +164,7 @@ const hostConfig = {
|
|
|
154
164
|
ensureChildWindow(child, parent.hwnd);
|
|
155
165
|
}
|
|
156
166
|
else {
|
|
157
|
-
|
|
167
|
+
SetParent(child.hwnd, parent.hwnd ?? NULL);
|
|
158
168
|
}
|
|
159
169
|
if (DEBUG)
|
|
160
170
|
console.log('[reconciler] appendChild parent:', parent.hwnd, 'child:', child.hwnd);
|
|
@@ -165,7 +175,7 @@ const hostConfig = {
|
|
|
165
175
|
ensureChildWindow(child, container);
|
|
166
176
|
}
|
|
167
177
|
else {
|
|
168
|
-
|
|
178
|
+
SetParent(child.hwnd, container);
|
|
169
179
|
}
|
|
170
180
|
const rootInst = instancesByHwnd.get(container);
|
|
171
181
|
if (rootInst)
|
|
@@ -178,7 +188,7 @@ const hostConfig = {
|
|
|
178
188
|
ensureChildWindow(child, parent.hwnd);
|
|
179
189
|
}
|
|
180
190
|
else {
|
|
181
|
-
|
|
191
|
+
SetParent(child.hwnd, parent.hwnd ?? NULL);
|
|
182
192
|
}
|
|
183
193
|
if (DEBUG)
|
|
184
194
|
console.log('[reconciler] insertBefore parent:', parent.hwnd, 'child:', child.hwnd, 'before:', beforeChild.hwnd);
|
|
@@ -191,7 +201,7 @@ const hostConfig = {
|
|
|
191
201
|
ensureChildWindow(child, container);
|
|
192
202
|
}
|
|
193
203
|
else {
|
|
194
|
-
|
|
204
|
+
SetParent(child.hwnd, container);
|
|
195
205
|
}
|
|
196
206
|
const rootInst = instancesByHwnd.get(container);
|
|
197
207
|
if (!rootInst)
|
|
@@ -246,7 +256,7 @@ const hostConfig = {
|
|
|
246
256
|
},
|
|
247
257
|
resetTextContent(_instance) { },
|
|
248
258
|
commitTextUpdate(instance, _oldText, newText) {
|
|
249
|
-
|
|
259
|
+
SetWindowText(instance.hwnd, newText);
|
|
250
260
|
},
|
|
251
261
|
hideInstance(instance) {
|
|
252
262
|
if (instance.hwnd !== null)
|
|
@@ -3,6 +3,9 @@ import { createContext } from 'react'
|
|
|
3
3
|
import { DefaultEventPriority, NoEventPriority } from 'react-reconciler/constants'
|
|
4
4
|
import * as gui from 'gui'
|
|
5
5
|
import * as os from 'os'
|
|
6
|
+
import { GetDC, ReleaseDC, GetWindowLongPtr, CallWindowProc, SetWindowPos, InvalidateRect, SetParent, SetWindowText } from '../windows/user32.js'
|
|
7
|
+
import { GetDeviceCaps } from '../windows/gdi32.js'
|
|
8
|
+
import { NULL } from '../ffi/ctype.js'
|
|
6
9
|
import { applyProps } from './props.js'
|
|
7
10
|
import { calculateFlexLayout, type FlexStyle } from './layout.js'
|
|
8
11
|
import { getButtonIdealSize, measureTextForHwnd } from '../text-measure.js'
|
|
@@ -12,7 +15,13 @@ import type { WIntrinsicProps } from './jsx.js'
|
|
|
12
15
|
declare const DEBUG: boolean
|
|
13
16
|
|
|
14
17
|
const dpiFont = gui.CreateSystemDpiFont()
|
|
15
|
-
|
|
18
|
+
// GetScaleFactor 等价内联:GetDC → GetDeviceCaps(LOGPIXELSX=88) → ReleaseDC 配对
|
|
19
|
+
export const scaleFactor = (() => {
|
|
20
|
+
const hdc = GetDC(NULL)
|
|
21
|
+
const dpi = hdc ? GetDeviceCaps(hdc, 88) : 96
|
|
22
|
+
if (hdc) ReleaseDC(NULL, hdc)
|
|
23
|
+
return dpi / 96
|
|
24
|
+
})()
|
|
16
25
|
|
|
17
26
|
type Container = gui.HWND
|
|
18
27
|
export type Props = WIntrinsicProps
|
|
@@ -33,15 +42,15 @@ function isDelayedControl(winClass: string): boolean {
|
|
|
33
42
|
|
|
34
43
|
function setupWindowProc(instance: Instance, hwnd: gui.HWND) {
|
|
35
44
|
instance.hwnd = hwnd
|
|
36
|
-
const oldProc =
|
|
45
|
+
const oldProc = GetWindowLongPtr(hwnd, gui.Gwlp.WNDPROC)
|
|
37
46
|
instancesByHwnd.set(hwnd, instance)
|
|
38
47
|
gui.SetWindowProc(hwnd, (hwnd: gui.HWND, msg: number, wParam: number, lParam: number) => {
|
|
39
48
|
const ev = instance.props.onEvent
|
|
40
|
-
if (!ev) return
|
|
49
|
+
if (!ev) return CallWindowProc(oldProc, hwnd, msg, wParam, lParam)
|
|
41
50
|
if (typeof ev === 'object') {
|
|
42
|
-
return ev.fn({ hwnd, msg, wParam, lParam, callOldWndProc: () =>
|
|
51
|
+
return ev.fn({ hwnd, msg, wParam, lParam, callOldWndProc: () => CallWindowProc(oldProc, hwnd, msg, wParam, lParam) })
|
|
43
52
|
}
|
|
44
|
-
const result =
|
|
53
|
+
const result = CallWindowProc(oldProc, hwnd, msg, wParam, lParam)
|
|
45
54
|
const override = ev({ hwnd, msg, wParam, lParam })
|
|
46
55
|
return typeof override === 'number' && Number.isInteger(override) ? override : result
|
|
47
56
|
})
|
|
@@ -109,8 +118,8 @@ function runFlexLayout(inst: Instance) {
|
|
|
109
118
|
const lr = child.lastRect
|
|
110
119
|
if (lr && lr.x === r.x && lr.y === r.y && lr.w === r.width && lr.h === r.height) continue
|
|
111
120
|
console.log('flex: set', child.type, child.hwnd, 'to', r.x, r.y, r.width, r.height)
|
|
112
|
-
|
|
113
|
-
|
|
121
|
+
SetWindowPos(child.hwnd!, gui.SetWindowPosHwnd.TOP, (r.x + pl) * scaleFactor, (r.y + pt) * scaleFactor, r.width * scaleFactor, r.height * scaleFactor, gui.SetWindowPosFlag.SWP_SHOWWINDOW)
|
|
122
|
+
InvalidateRect(child.hwnd!, NULL, 1)
|
|
114
123
|
child.lastRect = { x: r.x, y: r.y, w: r.width, h: r.height }
|
|
115
124
|
}
|
|
116
125
|
for (const c of children) runFlexLayout(c)
|
|
@@ -178,7 +187,7 @@ const hostConfig: QuickWinHostConfig = {
|
|
|
178
187
|
if (child.hwnd === null) {
|
|
179
188
|
ensureChildWindow(child, parent.hwnd!)
|
|
180
189
|
} else {
|
|
181
|
-
|
|
190
|
+
SetParent(child.hwnd, parent.hwnd ?? NULL)
|
|
182
191
|
}
|
|
183
192
|
if (DEBUG) console.log('[reconciler] appendInitialChild parent:', parent.hwnd, 'child:', child.hwnd)
|
|
184
193
|
parent.children.push(child)
|
|
@@ -188,7 +197,7 @@ const hostConfig: QuickWinHostConfig = {
|
|
|
188
197
|
if (child.hwnd === null) {
|
|
189
198
|
ensureChildWindow(child, parent.hwnd!)
|
|
190
199
|
} else {
|
|
191
|
-
|
|
200
|
+
SetParent(child.hwnd, parent.hwnd ?? NULL)
|
|
192
201
|
}
|
|
193
202
|
if (DEBUG) console.log('[reconciler] appendChild parent:', parent.hwnd, 'child:', child.hwnd)
|
|
194
203
|
parent.children.push(child)
|
|
@@ -198,7 +207,7 @@ const hostConfig: QuickWinHostConfig = {
|
|
|
198
207
|
if (child.hwnd === null) {
|
|
199
208
|
ensureChildWindow(child, container)
|
|
200
209
|
} else {
|
|
201
|
-
|
|
210
|
+
SetParent(child.hwnd, container)
|
|
202
211
|
}
|
|
203
212
|
const rootInst = instancesByHwnd.get(container)
|
|
204
213
|
if (rootInst) rootInst.children.push(child)
|
|
@@ -209,7 +218,7 @@ const hostConfig: QuickWinHostConfig = {
|
|
|
209
218
|
if (child.hwnd === null) {
|
|
210
219
|
ensureChildWindow(child, parent.hwnd!)
|
|
211
220
|
} else {
|
|
212
|
-
|
|
221
|
+
SetParent(child.hwnd, parent.hwnd ?? NULL)
|
|
213
222
|
}
|
|
214
223
|
if (DEBUG) console.log('[reconciler] insertBefore parent:', parent.hwnd, 'child:', child.hwnd, 'before:', beforeChild.hwnd)
|
|
215
224
|
const idx = parent.children.indexOf(beforeChild)
|
|
@@ -220,7 +229,7 @@ const hostConfig: QuickWinHostConfig = {
|
|
|
220
229
|
if (child.hwnd === null) {
|
|
221
230
|
ensureChildWindow(child, container)
|
|
222
231
|
} else {
|
|
223
|
-
|
|
232
|
+
SetParent(child.hwnd, container)
|
|
224
233
|
}
|
|
225
234
|
const rootInst = instancesByHwnd.get(container)
|
|
226
235
|
if (!rootInst) throw new Error('insertInContainerBefore: no root instance for container')
|
|
@@ -270,7 +279,7 @@ const hostConfig: QuickWinHostConfig = {
|
|
|
270
279
|
resetTextContent(_instance: Instance) { },
|
|
271
280
|
|
|
272
281
|
commitTextUpdate(instance: Instance, _oldText: string, newText: string) {
|
|
273
|
-
|
|
282
|
+
SetWindowText(instance.hwnd!, newText)
|
|
274
283
|
},
|
|
275
284
|
|
|
276
285
|
hideInstance(instance: Instance) {
|
package/lib/text-measure.js
CHANGED
|
@@ -1,37 +1,34 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { NULL } from './ffi/ctype.js';
|
|
2
2
|
import * as gui from 'gui';
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
GetDC: 'ptr -> ptr',
|
|
7
|
-
ReleaseDC: 'ptr ptr -> i32',
|
|
8
|
-
});
|
|
9
|
-
const gdi32 = bindLib('gdi32.dll', {
|
|
10
|
-
SelectObject: 'ptr ptr -> ptr',
|
|
11
|
-
});
|
|
3
|
+
import { DrawText, GetDC, ReleaseDC } from './windows/user32.js';
|
|
4
|
+
import { SelectObject } from './windows/gdi32.js';
|
|
5
|
+
import { RECT, SIZE } from './windows/structs.js';
|
|
12
6
|
export function measureText(hdc, text, maxWidth) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
7
|
+
// rect 双向位不注册 encoder(CALCRECT 就地写回):encode(初值) 新建 + DeepPartial 初值一步到位
|
|
8
|
+
//(left/top/bottom 缺省跳过保持 0)→ 传 .ptr → decode 读回
|
|
9
|
+
const r = RECT.encode({ right: maxWidth });
|
|
10
|
+
DrawText(hdc, text, -1, r.ptr, 1024 /* gui.DrawTextFlag.CALCRECT */);
|
|
11
|
+
const { right, bottom } = RECT.decode(r);
|
|
12
|
+
return { width: right, height: bottom };
|
|
18
13
|
}
|
|
19
14
|
export function getButtonIdealSize(hwnd) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
15
|
+
// SIZE 纯出参位(GETIDEALSIZE 就地写回):alloc → 传 .ptr → decode 读回
|
|
16
|
+
const size = SIZE.alloc();
|
|
17
|
+
gui.SendMessage(hwnd, 5633 /* gui.ButtonExtMsg.GETIDEALSIZE */, 0, size.ptr);
|
|
18
|
+
const { cx, cy } = SIZE.decode(size);
|
|
19
|
+
return { width: cx, height: cy };
|
|
24
20
|
}
|
|
25
21
|
export function measureTextForHwnd(hwnd, text) {
|
|
26
|
-
const hdc =
|
|
22
|
+
const hdc = GetDC(hwnd);
|
|
27
23
|
if (!hdc)
|
|
28
24
|
return { width: 0, height: 0 };
|
|
25
|
+
// SendMessage 返回 LRESULT 裸数;GETFONT 语义为 HFONT——断言进 GDI 对象品牌链
|
|
29
26
|
const hFont = gui.SendMessage(hwnd, 49 /* gui.WmMsg.GETFONT */, 0, 0);
|
|
30
|
-
const oldFont = hFont ?
|
|
27
|
+
const oldFont = hFont ? SelectObject(hdc, hFont) : NULL;
|
|
31
28
|
const result = measureText(hdc, text, 0);
|
|
32
29
|
if (hFont)
|
|
33
|
-
|
|
34
|
-
|
|
30
|
+
SelectObject(hdc, oldFont);
|
|
31
|
+
ReleaseDC(hwnd, hdc);
|
|
35
32
|
const wr = gui.GetWindowRect(hwnd);
|
|
36
33
|
const cr = gui.GetClientRect(hwnd);
|
|
37
34
|
if (wr && cr) {
|