quickwin 2.3.0 → 3.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 +1 -2
- package/constants.ts +22 -0
- package/enum_map.d.ts +4 -0
- package/examples/PdfCanvas.js +1 -1
- package/examples/PdfCanvas.tsx +2 -2
- package/examples/execd/main.js +491 -217
- package/examples/pdf_preview2.js +76 -67
- package/examples/pdf_preview2.ts +84 -64
- package/examples/pdf_viewer.js +4932 -1029
- package/examples/pdf_viewer.tsx +11 -22
- package/examples/test_gallery.js +308 -143
- package/examples/test_gallery.tsx +12 -7
- package/examples/test_react_lazy.js +295 -133
- package/examples/test_react_lazy.tsx +4 -3
- package/examples/test_react_listbox.js +293 -132
- package/examples/test_react_listbox.tsx +3 -2
- package/examples/test_root_window.js +283 -126
- package/examples/test_tab.js +295 -133
- package/examples/test_tab.tsx +4 -3
- package/examples/tray_demo.js +37 -30
- package/examples/tray_demo.tsx +39 -32
- package/lib/bitmap.js +40 -0
- package/lib/bitmap.ts +39 -0
- package/lib/certs.js +31 -3
- package/lib/certs.ts +32 -3
- package/lib/dpi.js +30 -0
- package/lib/dpi.ts +31 -0
- package/lib/dumpRects.js +14 -7
- package/lib/dumpRects.ts +14 -6
- package/lib/ffi/bind.js +5 -4
- package/lib/ffi/bind.ts +9 -8
- package/lib/http-server.js +50 -25
- package/lib/http-server.ts +51 -23
- package/lib/react-qw/components/DateTimePicker.js +6 -7
- package/lib/react-qw/components/Input.js +5 -1
- package/lib/react-qw/components/ListView.js +8 -6
- package/lib/react-qw/components/ScrollView.js +28 -22
- package/lib/react-qw/index.js +7 -5
- package/lib/react-qw/index.ts +5 -5
- package/lib/react-qw/props.js +3 -3
- package/lib/react-qw/props.ts +3 -3
- package/lib/react-qw/reconciler.js +12 -17
- package/lib/react-qw/reconciler.ts +12 -16
- package/lib/text-measure.js +9 -6
- package/lib/text-measure.ts +9 -6
- package/lib/windows/comctl32.js +3 -2
- package/lib/windows/comctl32.ts +3 -2
- package/lib/windows/gdi32.js +28 -6
- package/lib/windows/gdi32.ts +30 -6
- package/lib/windows/kernel32.js +9 -0
- package/lib/windows/kernel32.ts +9 -0
- package/lib/windows/shell32.js +11 -2
- package/lib/windows/shell32.ts +13 -2
- package/lib/windows/structs.js +51 -0
- package/lib/windows/structs.ts +55 -0
- package/lib/windows/user32.js +6 -5
- package/lib/windows/user32.ts +6 -5
- package/main.js +13 -5
- package/package.json +2 -2
- package/quickwin.d.ts +13 -115
- 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 +3 -4
- package/test/bench_ffi_callraw.ts +3 -4
- package/test/debug/mupdf_debug.js +4 -22
- package/test/debug/wasm_export_debug.js +2 -12
- package/test/debug/wasm_names.js +3 -13
- package/test/example.pdf +0 -0
- package/test/mupdf-cdn.js +40 -0
- package/test/mupdf-cdn.ts +43 -0
- package/test/test_ffi.js +8 -9
- package/test/test_ffi.ts +8 -9
- package/test/test_ffi_abi.js +6 -6
- package/test/test_ffi_abi.ts +8 -8
- package/test/test_ffi_windows.js +82 -11
- package/test/test_ffi_windows.ts +85 -11
- package/test/test_gdi_draw.js +3 -3
- package/test/test_gdi_draw.ts +4 -4
- package/test/test_mupdf_render.js +4 -22
- package/test/test_mupdf_render.ts +4 -23
- package/test/test_mupdf_twice.js +4 -19
- package/test/test_mupdf_twice.ts +4 -20
- package/test/test_mupdf_wasm.js +10 -4
- package/test/test_mupdf_wasm.ts +6 -4
- package/test/test_react_complex.js +316 -140
- package/test/test_react_counter.js +295 -133
- package/test/test_react_flex.js +297 -138
- package/test/test_react_hidden.js +296 -134
- package/test/test_react_listview_link.js +288 -131
- package/test/test_react_notify.js +290 -133
- package/test/test_react_render.js +295 -133
- package/vendor/mupdf-wasm/mupdf-wasm.d.ts +0 -571
- package/vendor/mupdf-wasm/mupdf-wasm.js +0 -2749
- package/vendor/mupdf-wasm/mupdf-wasm.wasm +0 -0
- package/vendor/mupdf-wasm/mupdf.d.ts +0 -939
- package/vendor/mupdf-wasm/mupdf.js +0 -3317
package/lib/ffi/bind.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import * as ffi from 'ffi'
|
|
2
2
|
import * as os from 'os'
|
|
3
3
|
import * as std from 'std'
|
|
4
|
-
import * as win from 'win'
|
|
5
4
|
import '../text-codec.js'
|
|
6
5
|
import { type C_BasicType, C_BasicType_No_Void, C_BasicType_Token, C_BasicType_Token_No_Void, cTokenToType, isCPtrToken, isCValToken, valName, ArgJsTypeOfToken, RetJsTypeOfToken, normToken, PTR_SIZE, ptrName, type Ptr, readScalar, writeSlot, PtrArrayBuffer } from './ctype.js'
|
|
7
6
|
|
|
@@ -47,12 +46,12 @@ function valCodec(name: string, encoders: CodecMap): StructValCodec | undefined
|
|
|
47
46
|
return encoders[name] as unknown as StructValCodec | undefined
|
|
48
47
|
}
|
|
49
48
|
|
|
50
|
-
const _dllCache: Map<string,
|
|
49
|
+
const _dllCache: Map<string, ffi.HMODULE> = new Map()
|
|
51
50
|
|
|
52
|
-
function loadDll(dll: string):
|
|
51
|
+
function loadDll(dll: string): ffi.HMODULE {
|
|
53
52
|
const cached = _dllCache.get(dll)
|
|
54
53
|
if (cached !== undefined) return cached
|
|
55
|
-
const loaded =
|
|
54
|
+
const loaded = ffi.LoadLibrary(dll)
|
|
56
55
|
if (!loaded) throw new Error(`ffi-bind: LoadLibrary("${dll}") failed`)
|
|
57
56
|
_dllCache.set(dll, loaded)
|
|
58
57
|
return loaded
|
|
@@ -105,7 +104,9 @@ function parseSig(sig: string): ParsedSig {
|
|
|
105
104
|
}
|
|
106
105
|
}
|
|
107
106
|
|
|
108
|
-
|
|
107
|
+
/** proc 是 GetProcAddress 拿到的函数地址(<FARPROC>ptr):调用方已校验非空,故收窄后的
|
|
108
|
+
* Ptr 而非 MaybePtr;品牌只在绑定层内部走,落到 ffiCall 的 uintptr 槽(number)时脱落。 */
|
|
109
|
+
function makeFn(proc: Ptr<'FARPROC'>, sig: string, encoders: CodecMap, decoders: CodecMap): (...a: unknown[]) => unknown {
|
|
109
110
|
const ps = parseSig(sig)
|
|
110
111
|
// 按值位的能力校验前移到绑定期(模块加载即炸,不留到首个调用):布局缺位 / 非结构
|
|
111
112
|
// 布局(无 size)/ 退化 size 都是签名层面的错误,报错指向具体位与可用布局清单。
|
|
@@ -134,7 +135,7 @@ const IA32_ARG_SIZE: Record<C_BasicType_No_Void, number> = {
|
|
|
134
135
|
// x64 桩无条件双读 slots[0..3],故 argFrame 至少 4 个槽(32 字节)
|
|
135
136
|
const X64_MIN_SLOTS = 4
|
|
136
137
|
|
|
137
|
-
function callPacked(proc:
|
|
138
|
+
function callPacked(proc: Ptr<'FARPROC'>, { argTokens, retToken, sig }: ParsedSig, args: any[], encoders: CodecMap, decoders: CodecMap): unknown {
|
|
138
139
|
const is64 = os.arch === 'x64'
|
|
139
140
|
|
|
140
141
|
const getWidth = (t: C_BasicType_Token_No_Void, is64: boolean): number => {
|
|
@@ -381,7 +382,7 @@ export function bind<const S extends string,
|
|
|
381
382
|
const LE extends CodecMap = {},
|
|
382
383
|
const LD extends CodecMap = LE>(
|
|
383
384
|
dll: string, name: string, sig: S, encoders?: LE, decoders?: LD) {
|
|
384
|
-
const proc =
|
|
385
|
+
const proc = ffi.GetProcAddress(loadDll(dll), name)
|
|
385
386
|
if (!proc) throw new Error(`ffi-bind: proc "${name}" not found in ${dll}`)
|
|
386
387
|
const effDec = decoders ?? encoders
|
|
387
388
|
return makeFn(proc, sig,
|
|
@@ -401,7 +402,7 @@ export function bindLib<const M extends Record<string, string>,
|
|
|
401
402
|
const encTable = { ...builtinCodecs, ...encoders }
|
|
402
403
|
const decTable = { ...builtinCodecs, ...(effDec ?? {}) }
|
|
403
404
|
for (const [name, sig] of Object.entries(map)) {
|
|
404
|
-
const proc =
|
|
405
|
+
const proc = ffi.GetProcAddress(h, name)
|
|
405
406
|
if (!proc) throw new Error(`ffi-bind: proc "${name}" not found in ${dll}`)
|
|
406
407
|
out[name] = makeFn(proc, sig, encTable, decTable)
|
|
407
408
|
}
|
package/lib/http-server.js
CHANGED
|
@@ -24,6 +24,11 @@ function concatU8(parts) {
|
|
|
24
24
|
return out;
|
|
25
25
|
}
|
|
26
26
|
function toArrayBuffer(u8) {
|
|
27
|
+
// 已经是完整 buffer(byteOffset=0、无尾部余量)时零复制返回——发送大
|
|
28
|
+
// body 的主路径,避免每片 send 前白拷一份。视图场景仍 slice 出独立副本
|
|
29
|
+
//(FFI 持有的 buffer 生命周期不受 view 约束)。
|
|
30
|
+
if (u8.byteOffset === 0 && u8.byteLength === u8.buffer.byteLength)
|
|
31
|
+
return u8.buffer;
|
|
27
32
|
return u8.buffer.slice(u8.byteOffset, u8.byteOffset + u8.byteLength);
|
|
28
33
|
}
|
|
29
34
|
function findCrLfCrLf(buf) {
|
|
@@ -190,6 +195,7 @@ export class HttpServer {
|
|
|
190
195
|
const c = {
|
|
191
196
|
sock: ac.handle,
|
|
192
197
|
parts: [],
|
|
198
|
+
partsLen: 0,
|
|
193
199
|
pending: null,
|
|
194
200
|
current: null,
|
|
195
201
|
outQueue: null,
|
|
@@ -229,7 +235,9 @@ function onRead(c) {
|
|
|
229
235
|
const d = sock.recv(c.sock, 8192);
|
|
230
236
|
if (!d)
|
|
231
237
|
break;
|
|
232
|
-
|
|
238
|
+
const u8 = new Uint8Array(d);
|
|
239
|
+
c.parts.push(u8);
|
|
240
|
+
c.partsLen += u8.length;
|
|
233
241
|
}
|
|
234
242
|
if (!c.closed)
|
|
235
243
|
tryProcess(c);
|
|
@@ -334,9 +342,19 @@ async function writeStreamingBody(c, body, chunked, trailer) {
|
|
|
334
342
|
if (done)
|
|
335
343
|
break;
|
|
336
344
|
if (value && value.byteLength > 0) {
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
345
|
+
// 切成 ≤64KB 再入 queueSend:单发 200MB 级大 chunk 时
|
|
346
|
+
// toArrayBuffer 会全量 slice 复制,partial drain 又每次复制
|
|
347
|
+
// 剩余(O(n²)),XP 32 位 1GB 上直接把进程水位顶穿导致断连;
|
|
348
|
+
// 小片让每次复制/发送恒为常数大小,backpressure 语义不变。
|
|
349
|
+
const MAX_SEND = 65536;
|
|
350
|
+
for (let off = 0; off < value.byteLength; off += MAX_SEND) {
|
|
351
|
+
const piece = value.byteLength <= MAX_SEND ? value : value.subarray(off, Math.min(off + MAX_SEND, value.byteLength));
|
|
352
|
+
const frame = chunked ? encodeChunkFrame(piece, enc) : piece;
|
|
353
|
+
if (!queueSend(c, frame)) {
|
|
354
|
+
await new Promise((resolve) => { c.resumeRead = resolve; });
|
|
355
|
+
}
|
|
356
|
+
if (c.closed)
|
|
357
|
+
break;
|
|
340
358
|
}
|
|
341
359
|
}
|
|
342
360
|
}
|
|
@@ -383,38 +401,43 @@ function whole(c) {
|
|
|
383
401
|
function trimParts(c, buf, consumed) {
|
|
384
402
|
if (consumed >= buf.length) {
|
|
385
403
|
c.parts = [];
|
|
404
|
+
c.partsLen = 0;
|
|
386
405
|
}
|
|
387
406
|
else {
|
|
388
407
|
c.parts = [buf.subarray(consumed)];
|
|
408
|
+
c.partsLen = buf.length - consumed;
|
|
389
409
|
}
|
|
390
410
|
}
|
|
391
411
|
function tryProcess(c) {
|
|
392
412
|
if (c.closed || c.current)
|
|
393
413
|
return;
|
|
394
|
-
const buf = whole(c);
|
|
395
414
|
if (c.pending) {
|
|
396
415
|
const bodyStart = c.pending.bodyStart;
|
|
397
416
|
const parsed = c.pending.parsed;
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
const
|
|
402
|
-
if (
|
|
403
|
-
return;
|
|
404
|
-
body = dec.data;
|
|
405
|
-
end = dec.end;
|
|
406
|
-
}
|
|
407
|
-
else {
|
|
408
|
-
const needed = parsed.contentLength;
|
|
409
|
-
if (bodyStart + needed > buf.length)
|
|
417
|
+
if (!parsed.chunked) {
|
|
418
|
+
// O(1) length check while the body is still arriving — only
|
|
419
|
+
// concatenate once, when the last byte is in.
|
|
420
|
+
const end = bodyStart + parsed.contentLength;
|
|
421
|
+
if (end > c.partsLen)
|
|
410
422
|
return;
|
|
411
|
-
|
|
412
|
-
|
|
423
|
+
const buf = whole(c);
|
|
424
|
+
c.pending = null;
|
|
425
|
+
dispatchRequest(c, parsed, buf.subarray(bodyStart, end), end, buf);
|
|
426
|
+
return;
|
|
413
427
|
}
|
|
428
|
+
// chunked: no total length up front, so a decode attempt still needs
|
|
429
|
+
// the concatenated view (re-tried per read event as before).
|
|
430
|
+
const buf = whole(c);
|
|
431
|
+
const dec = decodeChunked(buf, bodyStart);
|
|
432
|
+
if (!dec)
|
|
433
|
+
return;
|
|
414
434
|
c.pending = null;
|
|
415
|
-
dispatchRequest(c, parsed,
|
|
435
|
+
dispatchRequest(c, parsed, dec.data, dec.end, buf);
|
|
416
436
|
return;
|
|
417
437
|
}
|
|
438
|
+
// Header phase: bodies are checked against `partsLen` above, so a full
|
|
439
|
+
// concatenation is only needed here — once per request, not per read.
|
|
440
|
+
const buf = whole(c);
|
|
418
441
|
const headerEnd = findCrLfCrLf(buf);
|
|
419
442
|
if (headerEnd < 0)
|
|
420
443
|
return;
|
|
@@ -432,20 +455,20 @@ function tryProcess(c) {
|
|
|
432
455
|
c.pending = { parsed, bodyStart };
|
|
433
456
|
return;
|
|
434
457
|
}
|
|
435
|
-
dispatchRequest(c, parsed, dec.data, dec.end);
|
|
458
|
+
dispatchRequest(c, parsed, dec.data, dec.end, buf);
|
|
436
459
|
}
|
|
437
460
|
else if (parsed.contentLength > 0) {
|
|
438
461
|
if (bodyStart + parsed.contentLength > buf.length) {
|
|
439
462
|
c.pending = { parsed, bodyStart };
|
|
440
463
|
return;
|
|
441
464
|
}
|
|
442
|
-
dispatchRequest(c, parsed, buf.subarray(bodyStart, bodyStart + parsed.contentLength), bodyStart + parsed.contentLength);
|
|
465
|
+
dispatchRequest(c, parsed, buf.subarray(bodyStart, bodyStart + parsed.contentLength), bodyStart + parsed.contentLength, buf);
|
|
443
466
|
}
|
|
444
467
|
else {
|
|
445
|
-
dispatchRequest(c, parsed, new Uint8Array(0), bodyStart);
|
|
468
|
+
dispatchRequest(c, parsed, new Uint8Array(0), bodyStart, buf);
|
|
446
469
|
}
|
|
447
470
|
}
|
|
448
|
-
function dispatchRequest(c, parsed, body, end) {
|
|
471
|
+
function dispatchRequest(c, parsed, body, end, wholeBuf) {
|
|
449
472
|
const connHeader = (parsed.headers['connection'] || '').toLowerCase();
|
|
450
473
|
if (parsed.version === 'HTTP/1.1')
|
|
451
474
|
c.keepAlive = connHeader.indexOf('close') < 0;
|
|
@@ -468,7 +491,9 @@ function dispatchRequest(c, parsed, body, end) {
|
|
|
468
491
|
});
|
|
469
492
|
c.ac = ac;
|
|
470
493
|
c.current = { req };
|
|
471
|
-
|
|
494
|
+
// `wholeBuf` is the concatenation the caller already produced — reuse it
|
|
495
|
+
// instead of concatenating the very same parts a second time.
|
|
496
|
+
trimParts(c, wholeBuf ?? whole(c), end);
|
|
472
497
|
void handleRequest(c);
|
|
473
498
|
}
|
|
474
499
|
async function handleRequest(c) {
|
package/lib/http-server.ts
CHANGED
|
@@ -35,6 +35,10 @@ interface ActiveReq {
|
|
|
35
35
|
interface Conn {
|
|
36
36
|
sock: SockHandle
|
|
37
37
|
parts: Uint8Array[]
|
|
38
|
+
/** Cached total length of `parts` — lets the pending-body wait check
|
|
39
|
+
* "have we received enough yet" in O(1) instead of re-concatenating
|
|
40
|
+
* every received byte on each read event (O(n²) for large bodies). */
|
|
41
|
+
partsLen: number
|
|
38
42
|
pending: PendingReq | null
|
|
39
43
|
current: ActiveReq | null
|
|
40
44
|
outQueue: Uint8Array | null
|
|
@@ -69,6 +73,10 @@ function concatU8(parts: Uint8Array[]): Uint8Array {
|
|
|
69
73
|
}
|
|
70
74
|
|
|
71
75
|
function toArrayBuffer(u8: Uint8Array): ArrayBuffer {
|
|
76
|
+
// 已经是完整 buffer(byteOffset=0、无尾部余量)时零复制返回——发送大
|
|
77
|
+
// body 的主路径,避免每片 send 前白拷一份。视图场景仍 slice 出独立副本
|
|
78
|
+
//(FFI 持有的 buffer 生命周期不受 view 约束)。
|
|
79
|
+
if (u8.byteOffset === 0 && u8.byteLength === u8.buffer.byteLength) return u8.buffer as ArrayBuffer
|
|
72
80
|
return u8.buffer.slice(u8.byteOffset, u8.byteOffset + u8.byteLength) as ArrayBuffer
|
|
73
81
|
}
|
|
74
82
|
|
|
@@ -213,6 +221,7 @@ export class HttpServer {
|
|
|
213
221
|
const c: Conn = {
|
|
214
222
|
sock: ac.handle,
|
|
215
223
|
parts: [],
|
|
224
|
+
partsLen: 0,
|
|
216
225
|
pending: null,
|
|
217
226
|
current: null,
|
|
218
227
|
outQueue: null,
|
|
@@ -251,7 +260,9 @@ function onRead(c: Conn): void {
|
|
|
251
260
|
while (true) {
|
|
252
261
|
const d = sock.recv(c.sock, 8192)
|
|
253
262
|
if (!d) break
|
|
254
|
-
|
|
263
|
+
const u8 = new Uint8Array(d)
|
|
264
|
+
c.parts.push(u8)
|
|
265
|
+
c.partsLen += u8.length
|
|
255
266
|
}
|
|
256
267
|
if (!c.closed) tryProcess(c)
|
|
257
268
|
}
|
|
@@ -350,9 +361,18 @@ async function writeStreamingBody(c: Conn, body: ReadableStream<Uint8Array> | nu
|
|
|
350
361
|
const { done, value } = await reader.read()
|
|
351
362
|
if (done) break
|
|
352
363
|
if (value && value.byteLength > 0) {
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
364
|
+
// 切成 ≤64KB 再入 queueSend:单发 200MB 级大 chunk 时
|
|
365
|
+
// toArrayBuffer 会全量 slice 复制,partial drain 又每次复制
|
|
366
|
+
// 剩余(O(n²)),XP 32 位 1GB 上直接把进程水位顶穿导致断连;
|
|
367
|
+
// 小片让每次复制/发送恒为常数大小,backpressure 语义不变。
|
|
368
|
+
const MAX_SEND = 65536
|
|
369
|
+
for (let off = 0; off < value.byteLength; off += MAX_SEND) {
|
|
370
|
+
const piece = value.byteLength <= MAX_SEND ? value : value.subarray(off, Math.min(off + MAX_SEND, value.byteLength))
|
|
371
|
+
const frame = chunked ? encodeChunkFrame(piece, enc) : piece
|
|
372
|
+
if (!queueSend(c, frame)) {
|
|
373
|
+
await new Promise<void>((resolve) => { c.resumeRead = resolve })
|
|
374
|
+
}
|
|
375
|
+
if (c.closed) break
|
|
356
376
|
}
|
|
357
377
|
}
|
|
358
378
|
}
|
|
@@ -396,36 +416,42 @@ function whole(c: Conn): Uint8Array {
|
|
|
396
416
|
function trimParts(c: Conn, buf: Uint8Array, consumed: number): void {
|
|
397
417
|
if (consumed >= buf.length) {
|
|
398
418
|
c.parts = []
|
|
419
|
+
c.partsLen = 0
|
|
399
420
|
} else {
|
|
400
421
|
c.parts = [buf.subarray(consumed)]
|
|
422
|
+
c.partsLen = buf.length - consumed
|
|
401
423
|
}
|
|
402
424
|
}
|
|
403
425
|
|
|
404
426
|
function tryProcess(c: Conn): void {
|
|
405
427
|
if (c.closed || c.current) return
|
|
406
|
-
const buf = whole(c)
|
|
407
428
|
|
|
408
429
|
if (c.pending) {
|
|
409
430
|
const bodyStart = c.pending.bodyStart
|
|
410
431
|
const parsed = c.pending.parsed
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
const
|
|
415
|
-
if (
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
if (bodyStart + needed > buf.length) return
|
|
421
|
-
body = buf.subarray(bodyStart, bodyStart + needed)
|
|
422
|
-
end = bodyStart + needed
|
|
432
|
+
if (!parsed.chunked) {
|
|
433
|
+
// O(1) length check while the body is still arriving — only
|
|
434
|
+
// concatenate once, when the last byte is in.
|
|
435
|
+
const end = bodyStart + parsed.contentLength
|
|
436
|
+
if (end > c.partsLen) return
|
|
437
|
+
const buf = whole(c)
|
|
438
|
+
c.pending = null
|
|
439
|
+
dispatchRequest(c, parsed, buf.subarray(bodyStart, end), end, buf)
|
|
440
|
+
return
|
|
423
441
|
}
|
|
442
|
+
// chunked: no total length up front, so a decode attempt still needs
|
|
443
|
+
// the concatenated view (re-tried per read event as before).
|
|
444
|
+
const buf = whole(c)
|
|
445
|
+
const dec = decodeChunked(buf, bodyStart)
|
|
446
|
+
if (!dec) return
|
|
424
447
|
c.pending = null
|
|
425
|
-
dispatchRequest(c, parsed,
|
|
448
|
+
dispatchRequest(c, parsed, dec.data, dec.end, buf)
|
|
426
449
|
return
|
|
427
450
|
}
|
|
428
451
|
|
|
452
|
+
// Header phase: bodies are checked against `partsLen` above, so a full
|
|
453
|
+
// concatenation is only needed here — once per request, not per read.
|
|
454
|
+
const buf = whole(c)
|
|
429
455
|
const headerEnd = findCrLfCrLf(buf)
|
|
430
456
|
if (headerEnd < 0) return
|
|
431
457
|
const absEnd = headerEnd + 4
|
|
@@ -440,19 +466,19 @@ function tryProcess(c: Conn): void {
|
|
|
440
466
|
c.pending = { parsed, bodyStart }
|
|
441
467
|
return
|
|
442
468
|
}
|
|
443
|
-
dispatchRequest(c, parsed, dec.data, dec.end)
|
|
469
|
+
dispatchRequest(c, parsed, dec.data, dec.end, buf)
|
|
444
470
|
} else if (parsed.contentLength > 0) {
|
|
445
471
|
if (bodyStart + parsed.contentLength > buf.length) {
|
|
446
472
|
c.pending = { parsed, bodyStart }
|
|
447
473
|
return
|
|
448
474
|
}
|
|
449
|
-
dispatchRequest(c, parsed, buf.subarray(bodyStart, bodyStart + parsed.contentLength), bodyStart + parsed.contentLength)
|
|
475
|
+
dispatchRequest(c, parsed, buf.subarray(bodyStart, bodyStart + parsed.contentLength), bodyStart + parsed.contentLength, buf)
|
|
450
476
|
} else {
|
|
451
|
-
dispatchRequest(c, parsed, new Uint8Array(0), bodyStart)
|
|
477
|
+
dispatchRequest(c, parsed, new Uint8Array(0), bodyStart, buf)
|
|
452
478
|
}
|
|
453
479
|
}
|
|
454
480
|
|
|
455
|
-
function dispatchRequest(c: Conn, parsed: ParsedHeader, body: Uint8Array, end: number): void {
|
|
481
|
+
function dispatchRequest(c: Conn, parsed: ParsedHeader, body: Uint8Array, end: number, wholeBuf?: Uint8Array): void {
|
|
456
482
|
const connHeader = (parsed.headers['connection'] || '').toLowerCase()
|
|
457
483
|
if (parsed.version === 'HTTP/1.1') c.keepAlive = connHeader.indexOf('close') < 0
|
|
458
484
|
else c.keepAlive = connHeader.indexOf('keep-alive') >= 0
|
|
@@ -477,7 +503,9 @@ function dispatchRequest(c: Conn, parsed: ParsedHeader, body: Uint8Array, end: n
|
|
|
477
503
|
|
|
478
504
|
c.ac = ac
|
|
479
505
|
c.current = { req }
|
|
480
|
-
|
|
506
|
+
// `wholeBuf` is the concatenation the caller already produced — reuse it
|
|
507
|
+
// instead of concatenating the very same parts a second time.
|
|
508
|
+
trimParts(c, wholeBuf ?? whole(c), end)
|
|
481
509
|
void handleRequest(c)
|
|
482
510
|
}
|
|
483
511
|
|
|
@@ -1,9 +1,9 @@
|
|
|
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 { InvalidateRect } from '../../windows/user32.js';
|
|
4
|
+
import { GetClientRect, InvalidateRect } from '../../windows/user32.js';
|
|
5
5
|
import { NULL } from '../../ffi/ctype.js';
|
|
6
|
-
import { SYSTEMTIME, NMDATETIMECHANGE } from '../../windows/structs.js';
|
|
6
|
+
import { RECT, SYSTEMTIME, NMDATETIMECHANGE } from '../../windows/structs.js';
|
|
7
7
|
import { nmCode } from '../nmhdr.js';
|
|
8
8
|
function dateToSysTimeBuf(d) {
|
|
9
9
|
// wDayOfWeek 设置时被忽略,缺省跳过 = 0
|
|
@@ -52,11 +52,10 @@ const DateTimePicker = forwardRef(function DateTimePicker({ value, onChange, def
|
|
|
52
52
|
return;
|
|
53
53
|
InvalidateRect(h, NULL, 1);
|
|
54
54
|
// 发送 WM_SIZE 让控件(尤其是 DTS_UPDOWN)重新布局内部子窗口
|
|
55
|
-
const cr =
|
|
56
|
-
if (cr) {
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
gui.SendMessage(h, 5 /* WmMsg.SIZE */, 0, (hh << 16) | w);
|
|
55
|
+
const cr = RECT.alloc();
|
|
56
|
+
if (GetClientRect(h, cr.ptr)) {
|
|
57
|
+
const { left, top, right, bottom } = RECT.decode(cr);
|
|
58
|
+
gui.SendMessage(h, 5 /* WmMsg.SIZE */, 0, ((bottom - top) << 16) | (right - left));
|
|
60
59
|
}
|
|
61
60
|
}, []);
|
|
62
61
|
return (_jsx("w", { type: "STATIC", ws: 268435456 /* WindowStyle.VISIBLE */ | 33554432 /* WindowStyle.CLIPCHILDREN */, style: { ...style, flexDirection: 'column', alignItems: 'stretch' }, ref: ref, onEvent: (e) => {
|
|
@@ -2,6 +2,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
2
2
|
import { forwardRef, useState, useRef, useEffect } from 'react';
|
|
3
3
|
import * as gui from 'gui';
|
|
4
4
|
import { WCHAR } from '../../ffi/bind.js';
|
|
5
|
+
import { GetWindowText, GetWindowTextLength } from '../../windows/user32.js';
|
|
5
6
|
export const Input = forwardRef(({ value: controlledValue, defaultValue = '', onChange, placeholder, password, multiline, readonly, number, disabled, style }, ref) => {
|
|
6
7
|
const [internalValue, setInternalValue] = useState(defaultValue);
|
|
7
8
|
const isControlled = controlledValue !== undefined;
|
|
@@ -34,7 +35,10 @@ export const Input = forwardRef(({ value: controlledValue, defaultValue = '', on
|
|
|
34
35
|
const h = inputRef.current;
|
|
35
36
|
if (!h)
|
|
36
37
|
return;
|
|
37
|
-
const
|
|
38
|
+
const cap = GetWindowTextLength(h) + 1;
|
|
39
|
+
const buf = WCHAR.alloc(cap);
|
|
40
|
+
GetWindowText(h, buf, cap);
|
|
41
|
+
const newText = WCHAR.decode(buf);
|
|
38
42
|
if (!isControlled)
|
|
39
43
|
setInternalValue(newText);
|
|
40
44
|
onChange?.(newText);
|
|
@@ -7,6 +7,8 @@ import { WCHAR } from '../../ffi/bind.js';
|
|
|
7
7
|
import { nmCode } from '../nmhdr.js';
|
|
8
8
|
import { LoadCursor, SetCursor, ScreenToClient, GetCursorPos } from '../../windows/user32.js';
|
|
9
9
|
import { CreateFontIndirect, DeleteObject, GetObject, SelectObject } from '../../windows/gdi32.js';
|
|
10
|
+
import { createBitmapFromPixels } from '../../bitmap.js';
|
|
11
|
+
import { ImageListCreate, ImageListAdd, ImageListDestroy } from '../../windows/comctl32.js';
|
|
10
12
|
import { LOGFONTW, POINT, NMCUSTOMDRAW, NMLVCUSTOMDRAW, NMLISTVIEW, LVHITTESTINFO, LVITEMW, LVCOLUMNW, } from '../../windows/structs.js';
|
|
11
13
|
export function makeColorBlock(size, bgra) {
|
|
12
14
|
const n = size * size;
|
|
@@ -134,27 +136,27 @@ function makeLVItem(i, sub, text, image) {
|
|
|
134
136
|
}
|
|
135
137
|
const ListView = forwardRef(function ListViewInner({ columns, data, style, icons, iconSize, getIcon }, ref) {
|
|
136
138
|
const lvRef = useRef(null);
|
|
137
|
-
const iconListRef = useRef(
|
|
139
|
+
const iconListRef = useRef(NULL);
|
|
138
140
|
useEffect(() => {
|
|
139
141
|
const h = lvRef.current;
|
|
140
142
|
if (!h)
|
|
141
143
|
return;
|
|
142
144
|
const il = iconListRef.current;
|
|
143
|
-
iconListRef.current =
|
|
145
|
+
iconListRef.current = NULL;
|
|
144
146
|
if (il) {
|
|
145
147
|
gui.SendMessage(h, 4099 /* LvMsg.SETIMAGELIST */, 1 /* LvImageList.SMALL */, 0);
|
|
146
|
-
|
|
148
|
+
ImageListDestroy(il);
|
|
147
149
|
}
|
|
148
150
|
if (!icons || icons.length === 0)
|
|
149
151
|
return;
|
|
150
152
|
const size = iconSize ?? 32;
|
|
151
|
-
const img =
|
|
153
|
+
const img = ImageListCreate(size, size, 32 /* ImageListFlag.COLOR32 */, icons.length, 1);
|
|
152
154
|
if (!img)
|
|
153
155
|
return;
|
|
154
156
|
for (const p of icons) {
|
|
155
|
-
const hbm =
|
|
157
|
+
const hbm = createBitmapFromPixels(size, size, p);
|
|
156
158
|
if (hbm) {
|
|
157
|
-
|
|
159
|
+
ImageListAdd(img, hbm, NULL);
|
|
158
160
|
DeleteObject(hbm);
|
|
159
161
|
}
|
|
160
162
|
}
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { forwardRef, useLayoutEffect, useRef } from 'react';
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
import { NULL } from '../../ffi/ctype.js';
|
|
6
|
-
import { GetDeviceCaps } from '../../windows/gdi32.js';
|
|
3
|
+
import { DefWindowProc, GetClientRect, GetScrollInfo, SetScrollInfo, SetWindowPos, ShowScrollBar } from '../../windows/user32.js';
|
|
4
|
+
import { RECT, SCROLLINFO } from '../../windows/structs.js';
|
|
7
5
|
import { forceFlexLayout } from '../reconciler.js';
|
|
6
|
+
import { getScaleFactor } from '../../dpi.js';
|
|
8
7
|
export const ScrollView = forwardRef(({ children, style, contentWidth, contentHeight }, ref) => {
|
|
9
8
|
const svRef = useRef(null);
|
|
10
9
|
const contentRef = useRef(null);
|
|
@@ -16,16 +15,13 @@ export const ScrollView = forwardRef(({ children, style, contentWidth, contentHe
|
|
|
16
15
|
// page = viewportSize(可见行数)
|
|
17
16
|
// nPos = 0 … max - page + 1(即 0 … contentSize - viewportSize)
|
|
18
17
|
function updateScroll(sv, content) {
|
|
19
|
-
const
|
|
20
|
-
if (!
|
|
18
|
+
const svBuf = RECT.alloc();
|
|
19
|
+
if (!GetClientRect(sv, svBuf.ptr))
|
|
21
20
|
return;
|
|
22
|
-
const
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
const
|
|
26
|
-
const scale = hdc ? GetDeviceCaps(hdc, 88) / 96 : 1;
|
|
27
|
-
if (hdc)
|
|
28
|
-
ReleaseDC(NULL, hdc);
|
|
21
|
+
const { left, top, right, bottom } = RECT.decode(svBuf);
|
|
22
|
+
const svW = right - left;
|
|
23
|
+
const svH = bottom - top;
|
|
24
|
+
const scale = getScaleFactor();
|
|
29
25
|
const natW = Math.round((contentWidth ?? (svW / scale)) * scale);
|
|
30
26
|
const natH = Math.round((contentHeight ?? (svH / scale)) * scale);
|
|
31
27
|
scrollXRef.current = Math.max(0, Math.min(scrollXRef.current, natW - svW));
|
|
@@ -33,7 +29,10 @@ export const ScrollView = forwardRef(({ children, style, contentWidth, contentHe
|
|
|
33
29
|
SetWindowPos(content, 0 /* SetWindowPosHwnd.TOP */, -scrollXRef.current, -scrollYRef.current, natW, natH, 4 /* SetWindowPosFlag.SWP_NOZORDER */);
|
|
34
30
|
forceFlexLayout(content);
|
|
35
31
|
if (natW > svW) {
|
|
36
|
-
|
|
32
|
+
SetScrollInfo(sv, 0 /* ScrollBar.HORZ */, {
|
|
33
|
+
cbSize: SCROLLINFO.size, fMask: 1 /* ScrollInfoFlag.RANGE */ | 2 /* ScrollInfoFlag.PAGE */ | 4 /* ScrollInfoFlag.POS */,
|
|
34
|
+
nMin: 0, nMax: natW - 1, nPage: svW, nPos: scrollXRef.current,
|
|
35
|
+
}, 1);
|
|
37
36
|
ShowScrollBar(sv, 0 /* ScrollBar.HORZ */, 1);
|
|
38
37
|
}
|
|
39
38
|
else {
|
|
@@ -41,7 +40,10 @@ export const ScrollView = forwardRef(({ children, style, contentWidth, contentHe
|
|
|
41
40
|
ShowScrollBar(sv, 0 /* ScrollBar.HORZ */, 0);
|
|
42
41
|
}
|
|
43
42
|
if (natH > svH) {
|
|
44
|
-
|
|
43
|
+
SetScrollInfo(sv, 1 /* ScrollBar.VERT */, {
|
|
44
|
+
cbSize: SCROLLINFO.size, fMask: 1 /* ScrollInfoFlag.RANGE */ | 2 /* ScrollInfoFlag.PAGE */ | 4 /* ScrollInfoFlag.POS */,
|
|
45
|
+
nMin: 0, nMax: natH - 1, nPage: svH, nPos: scrollYRef.current,
|
|
46
|
+
}, 1);
|
|
45
47
|
ShowScrollBar(sv, 1 /* ScrollBar.VERT */, 1);
|
|
46
48
|
}
|
|
47
49
|
else {
|
|
@@ -64,15 +66,17 @@ export const ScrollView = forwardRef(({ children, style, contentWidth, contentHe
|
|
|
64
66
|
scrollXRef.current = newPos;
|
|
65
67
|
}
|
|
66
68
|
SetWindowPos(content, 0 /* SetWindowPosHwnd.TOP */, -scrollXRef.current, -scrollYRef.current, 0, 0, 1 /* SetWindowPosFlag.SWP_NOSIZE */ | 4 /* SetWindowPosFlag.SWP_NOZORDER */);
|
|
67
|
-
|
|
69
|
+
SetScrollInfo(sv, bar, { cbSize: SCROLLINFO.size, fMask: 4 /* ScrollInfoFlag.POS */, nPos: newPos }, 1);
|
|
68
70
|
}
|
|
69
71
|
function handleScroll(e, bar) {
|
|
70
72
|
const sv = svRef.current;
|
|
71
73
|
const content = contentRef.current;
|
|
72
74
|
if (!sv || !content)
|
|
73
75
|
return;
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
+
const si = SCROLLINFO.encode({ cbSize: SCROLLINFO.size, fMask: 23 /* ScrollInfoFlag.ALL */ });
|
|
77
|
+
GetScrollInfo(sv, bar, si.ptr);
|
|
78
|
+
const info = SCROLLINFO.decode(si);
|
|
79
|
+
const maxPos = info.nMax - info.nPage + 1;
|
|
76
80
|
const code = e.wParam & 0xFFFF;
|
|
77
81
|
const ref_ = bar === 1 /* ScrollBar.VERT */ ? scrollYRef : scrollXRef;
|
|
78
82
|
let newPos = ref_.current;
|
|
@@ -84,9 +88,9 @@ export const ScrollView = forwardRef(({ children, style, contentWidth, contentHe
|
|
|
84
88
|
else if (code === 1 /* ScrollCmd.LINEDOWN */)
|
|
85
89
|
newPos += 20;
|
|
86
90
|
else if (code === 2 /* ScrollCmd.PAGEUP */)
|
|
87
|
-
newPos -= info.
|
|
91
|
+
newPos -= info.nPage;
|
|
88
92
|
else if (code === 3 /* ScrollCmd.PAGEDOWN */)
|
|
89
|
-
newPos += info.
|
|
93
|
+
newPos += info.nPage;
|
|
90
94
|
else
|
|
91
95
|
return;
|
|
92
96
|
newPos = Math.max(0, Math.min(maxPos, newPos));
|
|
@@ -106,8 +110,10 @@ export const ScrollView = forwardRef(({ children, style, contentWidth, contentHe
|
|
|
106
110
|
if (!isHorz && contentHeight === undefined)
|
|
107
111
|
return;
|
|
108
112
|
const bar = isHorz ? 0 /* ScrollBar.HORZ */ : 1 /* ScrollBar.VERT */;
|
|
109
|
-
const
|
|
110
|
-
|
|
113
|
+
const si = SCROLLINFO.encode({ cbSize: SCROLLINFO.size, fMask: 23 /* ScrollInfoFlag.ALL */ });
|
|
114
|
+
GetScrollInfo(sv, bar, si.ptr);
|
|
115
|
+
const info = SCROLLINFO.decode(si);
|
|
116
|
+
const maxPos = info.nMax - info.nPage + 1;
|
|
111
117
|
const ref_ = isHorz ? scrollXRef : scrollYRef;
|
|
112
118
|
let newPos = ref_.current + dy;
|
|
113
119
|
newPos = Math.max(0, Math.min(maxPos, newPos));
|
package/lib/react-qw/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as gui from 'gui';
|
|
2
|
-
import { DefWindowProc, GetSystemMetrics } from '../windows/user32.js';
|
|
2
|
+
import { DefWindowProc, GetSystemMetrics, ShowWindow, UpdateWindow } from '../windows/user32.js';
|
|
3
3
|
import reconciler, { scaleFactor, instancesByHwnd, forceFlexLayout } from './reconciler.js';
|
|
4
4
|
const noop = () => { };
|
|
5
5
|
let defaultClassRegistered = false;
|
|
@@ -34,8 +34,7 @@ export function createRoot(container) {
|
|
|
34
34
|
const ws = (cfg.ws ?? 0) | 13565952 /* WindowStyle.OVERLAPPEDWINDOW */;
|
|
35
35
|
const w = (cfg.width ?? 800) * scaleFactor;
|
|
36
36
|
const h = (cfg.height ?? 600) * scaleFactor;
|
|
37
|
-
|
|
38
|
-
const [sw, sh] = [GetSystemMetrics(0), GetSystemMetrics(1)];
|
|
37
|
+
const [sw, sh] = [GetSystemMetrics(0 /* SysMetrics.CXSCREEN */), GetSystemMetrics(1 /* SysMetrics.CYSCREEN */)];
|
|
39
38
|
const x = cfg.x === undefined ? ((sw - w) / 2) | 0
|
|
40
39
|
: cfg.x === -2147483648 /* CreatePos.USEDEFAULT */ ? -2147483648 /* CreatePos.USEDEFAULT */ : cfg.x * scaleFactor;
|
|
41
40
|
const y = cfg.y === undefined ? ((sh - h) / 2) | 0
|
|
@@ -44,8 +43,11 @@ export function createRoot(container) {
|
|
|
44
43
|
if (!hwin)
|
|
45
44
|
throw new Error('CreateWindow failed');
|
|
46
45
|
hwnd = hwin;
|
|
47
|
-
|
|
48
|
-
|
|
46
|
+
// 启动首帧:ShowWindow 后立即 WM_PAINT,不留白(原 gui.ShowWindow 复合语义)
|
|
47
|
+
if (!cfg.noShowWindow) {
|
|
48
|
+
ShowWindow(hwnd, 1 /* ShowWindowCmd.SHOWNORMAL */);
|
|
49
|
+
UpdateWindow(hwnd);
|
|
50
|
+
}
|
|
49
51
|
}
|
|
50
52
|
else {
|
|
51
53
|
hwnd = container;
|
package/lib/react-qw/index.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import * as gui from 'gui'
|
|
2
2
|
import React from 'react'
|
|
3
|
-
import { DefWindowProc, GetSystemMetrics } from '../windows/user32.js'
|
|
3
|
+
import { DefWindowProc, GetSystemMetrics, ShowWindow, UpdateWindow } from '../windows/user32.js'
|
|
4
4
|
import reconciler, { scaleFactor, instancesByHwnd, forceFlexLayout, type Instance } from './reconciler.js'
|
|
5
|
-
import { CreatePos, WindowStyle, WmMsg } from '../../constants'
|
|
5
|
+
import { CreatePos, ShowWindowCmd, SysMetrics, WindowStyle, WmMsg } from '../../constants'
|
|
6
6
|
|
|
7
7
|
const noop = () => { }
|
|
8
8
|
|
|
@@ -48,8 +48,7 @@ export function createRoot(container: gui.HWND | RootWindowConfig) {
|
|
|
48
48
|
const ws = (cfg.ws ?? 0) | WindowStyle.OVERLAPPEDWINDOW
|
|
49
49
|
const w = (cfg.width ?? 800) * scaleFactor
|
|
50
50
|
const h = (cfg.height ?? 600) * scaleFactor
|
|
51
|
-
|
|
52
|
-
const [sw, sh] = [GetSystemMetrics(0), GetSystemMetrics(1)]
|
|
51
|
+
const [sw, sh] = [GetSystemMetrics(SysMetrics.CXSCREEN), GetSystemMetrics(SysMetrics.CYSCREEN)]
|
|
53
52
|
const x = cfg.x === undefined ? ((sw - w) / 2) | 0
|
|
54
53
|
: cfg.x === CreatePos.USEDEFAULT ? CreatePos.USEDEFAULT
|
|
55
54
|
: cfg.x * scaleFactor
|
|
@@ -59,7 +58,8 @@ export function createRoot(container: gui.HWND | RootWindowConfig) {
|
|
|
59
58
|
const hwin = gui.CreateWindow(className, cfg.text || '', ws, x, y, w, h, null, null)
|
|
60
59
|
if (!hwin) throw new Error('CreateWindow failed')
|
|
61
60
|
hwnd = hwin
|
|
62
|
-
|
|
61
|
+
// 启动首帧:ShowWindow 后立即 WM_PAINT,不留白(原 gui.ShowWindow 复合语义)
|
|
62
|
+
if (!cfg.noShowWindow) { ShowWindow(hwnd, ShowWindowCmd.SHOWNORMAL); UpdateWindow(hwnd) }
|
|
63
63
|
} else {
|
|
64
64
|
hwnd = container
|
|
65
65
|
}
|
package/lib/react-qw/props.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as gui from 'gui';
|
|
2
|
-
import { SetWindowText, EnableWindow } from '../windows/user32.js';
|
|
2
|
+
import { SetWindowText, EnableWindow, ShowWindow } from '../windows/user32.js';
|
|
3
3
|
export function applyProps(instance, newProps, _oldProps) {
|
|
4
4
|
const hwnd = instance.hwnd;
|
|
5
5
|
instance.props = newProps;
|
|
@@ -24,9 +24,9 @@ export function applyProps(instance, newProps, _oldProps) {
|
|
|
24
24
|
EnableWindow(hwnd, !newProps.disabled ? 1 : 0);
|
|
25
25
|
}
|
|
26
26
|
if ('hidden' in newProps) {
|
|
27
|
-
|
|
27
|
+
ShowWindow(hwnd, newProps.hidden ? 0 /* ShowWindowCmd.HIDE */ : 5 /* ShowWindowCmd.SHOW */);
|
|
28
28
|
}
|
|
29
29
|
if ('visible' in newProps) {
|
|
30
|
-
|
|
30
|
+
ShowWindow(hwnd, newProps.visible ? 5 /* ShowWindowCmd.SHOW */ : 0 /* ShowWindowCmd.HIDE */);
|
|
31
31
|
}
|
|
32
32
|
}
|