quickwin 2026.5.2-3.145209

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/README.md +6 -0
  2. package/examples/pdf_preview.js +440 -0
  3. package/examples/pdf_preview.ts +470 -0
  4. package/examples/preact_demo.js +35 -0
  5. package/examples/preact_demo.tsx +49 -0
  6. package/examples/tray_demo.js +75 -0
  7. package/examples/tray_demo.tsx +79 -0
  8. package/lib/fetch.js +746 -0
  9. package/lib/fetch.ts +811 -0
  10. package/lib/polyfill.js +500 -0
  11. package/lib/polyfill.ts +454 -0
  12. package/lib/preact/hooks.js +287 -0
  13. package/lib/preact/hooks.ts +330 -0
  14. package/lib/preact/jsx-runtime.js +1 -0
  15. package/lib/preact/jsx-runtime.ts +2 -0
  16. package/lib/preact/jsx.d.ts +36 -0
  17. package/lib/preact/layout.js +153 -0
  18. package/lib/preact/layout.ts +183 -0
  19. package/lib/preact/preact.js +54 -0
  20. package/lib/preact/preact.ts +133 -0
  21. package/lib/preact/props.js +99 -0
  22. package/lib/preact/props.ts +119 -0
  23. package/lib/preact/render.js +320 -0
  24. package/lib/preact/render.ts +353 -0
  25. package/lib/websocket.js +540 -0
  26. package/lib/websocket.ts +574 -0
  27. package/package.json +32 -0
  28. package/quickwin.d.ts +657 -0
  29. package/test/add.wasm +0 -0
  30. package/test/complex.wasm +0 -0
  31. package/test/complex_imports.wasm +0 -0
  32. package/test/global_imports.wasm +0 -0
  33. package/test/import_func.wasm +0 -0
  34. package/test/imports.wasm +0 -0
  35. package/test/run.js +86 -0
  36. package/test/run.ts +90 -0
  37. package/test/sjlj.wasm +0 -0
  38. package/test/test_basic.js +7 -0
  39. package/test/test_basic.ts +9 -0
  40. package/test/test_brotli.js +48 -0
  41. package/test/test_brotli.ts +52 -0
  42. package/test/test_fetch_cache.js +131 -0
  43. package/test/test_fetch_cache.ts +141 -0
  44. package/test/test_ffi.js +157 -0
  45. package/test/test_ffi.ts +174 -0
  46. package/test/test_frame_encoding.js +128 -0
  47. package/test/test_frame_encoding.ts +132 -0
  48. package/test/test_helper.js +84 -0
  49. package/test/test_helper.ts +80 -0
  50. package/test/test_http_import.js +78 -0
  51. package/test/test_http_import.ts +74 -0
  52. package/test/test_mupdf_render.js +69 -0
  53. package/test/test_mupdf_render.ts +74 -0
  54. package/test/test_mupdf_twice.js +77 -0
  55. package/test/test_mupdf_twice.ts +81 -0
  56. package/test/test_mupdf_wasm.js +33 -0
  57. package/test/test_mupdf_wasm.ts +30 -0
  58. package/test/test_net_event.js +63 -0
  59. package/test/test_net_event.ts +59 -0
  60. package/test/test_net_fetch.js +153 -0
  61. package/test/test_net_fetch.ts +131 -0
  62. package/test/test_net_websocket.js +158 -0
  63. package/test/test_net_websocket.ts +144 -0
  64. package/test/test_polyfill.js +58 -0
  65. package/test/test_polyfill.ts +60 -0
  66. package/test/test_url.js +173 -0
  67. package/test/test_url.ts +183 -0
  68. package/test/test_wasm_basic.js +82 -0
  69. package/test/test_wasm_basic.ts +70 -0
  70. package/test/test_wasm_import_global.js +41 -0
  71. package/test/test_wasm_import_global.ts +39 -0
  72. package/test/test_wasm_sjlj.js +153 -0
  73. package/test/test_wasm_sjlj.ts +134 -0
  74. package/test/test_wasm_types.js +96 -0
  75. package/test/test_wasm_types.ts +108 -0
  76. package/test/types.wasm +0 -0
  77. package/tsconfig.json +18 -0
  78. package/vendor/mupdf-wasm/mupdf-wasm.d.ts +571 -0
  79. package/vendor/mupdf-wasm/mupdf-wasm.js +2749 -0
  80. package/vendor/mupdf-wasm/mupdf-wasm.wasm +0 -0
  81. package/vendor/mupdf-wasm/mupdf.d.ts +939 -0
  82. package/vendor/mupdf-wasm/mupdf.js +3317 -0
  83. package/win-mingw64.exe +0 -0
@@ -0,0 +1,470 @@
1
+ import '../lib/polyfill.js'
2
+ import * as std from 'std'
3
+ import * as gui from 'gui'
4
+ import * as win from 'win'
5
+ import * as ffi from 'ffi'
6
+ import type { Document, Page, Pixmap } from '../vendor/mupdf-wasm/mupdf.js'
7
+
8
+ const FFI_PTR = ffi.FFI_TYPE_POINTER
9
+ const FFI_U32 = ffi.FFI_TYPE_UINT32
10
+ const FFI_S32 = ffi.FFI_TYPE_SINT32
11
+
12
+ const _user32 = win.LoadLibrary('user32.dll')
13
+ const _gdi32 = win.LoadLibrary('gdi32.dll')
14
+ const _comdlg32 = win.LoadLibrary('comdlg32.dll')
15
+
16
+ type MuPdf = typeof import('../vendor/mupdf-wasm/mupdf.js')
17
+
18
+ if (!(_user32 && _gdi32 && _comdlg32)) {
19
+ std.exit(0)
20
+ }
21
+
22
+ function loadProc(lib: win.HMODULE, name: string): number {
23
+ const ptr = win.GetProcAddress(lib, name)
24
+ if (!ptr) {
25
+ std.printf('Error: cannot load %s\n', name)
26
+ std.exit(1)
27
+ }
28
+ return ptr
29
+ }
30
+
31
+ const GetDC = loadProc(_user32, 'GetDC')
32
+ const ReleaseDC = loadProc(_user32, 'ReleaseDC')
33
+ const GetOpenFileNameW = loadProc(_comdlg32, 'GetOpenFileNameW')
34
+ const SetDIBitsToDevice = loadProc(_gdi32, 'SetDIBitsToDevice')
35
+ const PatBlt = loadProc(_gdi32, 'PatBlt')
36
+ const InvalidateRect = loadProc(_user32, 'InvalidateRect')
37
+ const GetSystemMetrics = loadProc(_user32, 'GetSystemMetrics')
38
+ const SetScrollInfo = loadProc(_user32, 'SetScrollInfo')
39
+ const GetClientRect = loadProc(_user32, 'GetClientRect')
40
+
41
+ const TOP_OFFSET = 50
42
+
43
+ // FFI 类型签名快捷常量
44
+ const T_U64 = [ffi.FFI_TYPE_UINT64] as [typeof ffi.FFI_TYPE_UINT64]
45
+ const T_U64_PTR = [ffi.FFI_TYPE_UINT64, FFI_PTR] as [typeof ffi.FFI_TYPE_UINT64, typeof ffi.FFI_TYPE_POINTER]
46
+ const T_U64_S32_PTR_U32 = [ffi.FFI_TYPE_UINT64, FFI_S32, FFI_PTR, FFI_U32] as [typeof ffi.FFI_TYPE_UINT64, typeof ffi.FFI_TYPE_SINT32, typeof ffi.FFI_TYPE_POINTER, typeof ffi.FFI_TYPE_UINT32]
47
+ const T_U64_U64_U32 = [ffi.FFI_TYPE_UINT64, ffi.FFI_TYPE_UINT64, FFI_U32] as [typeof ffi.FFI_TYPE_UINT64, typeof ffi.FFI_TYPE_UINT64, typeof ffi.FFI_TYPE_UINT32]
48
+
49
+ function clamp(v: number, max: number): number {
50
+ return Math.max(0, Math.min(v, max))
51
+ }
52
+
53
+ function makeBitmapInfo(w: number, h: number): ArrayBuffer {
54
+ const bmi = new ArrayBuffer(40)
55
+ const bv = new DataView(bmi)
56
+ bv.setUint32(0, 40, true)
57
+ bv.setInt32(4, w, true)
58
+ bv.setInt32(8, -h, true)
59
+ bv.setUint16(12, 1, true)
60
+ bv.setUint16(14, 24, true)
61
+ return bmi
62
+ }
63
+
64
+ function strToWide(str: string): ArrayBuffer {
65
+ const buf = new ArrayBuffer((str.length + 1) * 2)
66
+ const dv = new DataView(buf)
67
+ for (let i = 0; i < str.length; i++) dv.setUint16(i * 2, str.charCodeAt(i), true)
68
+ return buf
69
+ }
70
+
71
+ function wideToStr(buf: ArrayBuffer): string {
72
+ const dv = new DataView(buf)
73
+ const chars: number[] = []
74
+ for (let i = 0; i < buf.byteLength; i += 2) {
75
+ const c = dv.getUint16(i, true)
76
+ if (c === 0) break
77
+ chars.push(c)
78
+ }
79
+ return String.fromCharCode(...chars)
80
+ }
81
+
82
+ function setPtr(dv: DataView, off: number, ptr: number): void {
83
+ dv.setUint32(off, ptr & 0xFFFFFFFF, true)
84
+ dv.setUint32(off + 4, Math.floor(ptr / 0x100000000), true)
85
+ }
86
+
87
+ interface PixmapInfo {
88
+ data: ArrayBuffer
89
+ w: number
90
+ h: number
91
+ }
92
+
93
+ let hwndMain: gui.HWND | null = null
94
+ let hwndEdit: gui.HWND | null = null
95
+ let hwndBtnOpen: gui.HWND | null = null
96
+ let hwndBtnPrev: gui.HWND | null = null
97
+ let hwndBtnNext: gui.HWND | null = null
98
+ let currentPixmap: PixmapInfo | null = null
99
+ let currentPage = 0
100
+ let totalPages = 0
101
+ let scrollX = 0
102
+ let scrollY = 0
103
+
104
+ // PDF 文档缓存 — 翻页时不重复解析
105
+ let cachedPath = ''
106
+ let cachedDoc: Document | null = null
107
+ let cachedTotalPages = 0
108
+
109
+ function clearCachedDoc(): void {
110
+ if (cachedDoc) { try { cachedDoc.destroy() } catch {} }
111
+ cachedDoc = null
112
+ cachedPath = ''
113
+ cachedTotalPages = 0
114
+ }
115
+
116
+ async function loadMupdf(): Promise<MuPdf | null> {
117
+ const wasmPath = './vendor/mupdf-wasm/mupdf-wasm.wasm'
118
+ const fp = std.open(wasmPath, 'rb')
119
+ if (!fp) { std.printf('Error: cannot open %s\n', wasmPath); return null }
120
+ fp.seek(0, 2)
121
+ const size = fp.tell()
122
+ fp.seek(0, 0)
123
+ const buf = new ArrayBuffer(size)
124
+ fp.read(buf, 0, size)
125
+ fp.close()
126
+ console.log(buf.byteLength)
127
+
128
+ ; (globalThis).$libmupdf_wasm_Module = {
129
+ wasmBinary: buf,
130
+ locateFile: (p: string) => p
131
+ }
132
+ try {
133
+ return await import('../vendor/mupdf-wasm/mupdf.js')
134
+ } catch (e) {
135
+ std.printf('Error: mupdf load failed: %s\n', String(e))
136
+ return null
137
+ }
138
+ }
139
+
140
+ function openPdfFileDialog(): string | null {
141
+ const structBuf = new ArrayBuffer(152)
142
+ const sv = new DataView(structBuf)
143
+ const fileBuf = new ArrayBuffer(260 * 2)
144
+ const filterWide = strToWide('PDF Files\0*.pdf\0All Files\0*.*\0\0')
145
+
146
+ sv.setUint32(0, 152, true)
147
+ const ownerPtr = hwndMain
148
+ if (!ownerPtr) return null
149
+ sv.setUint32(8, ownerPtr & 0xFFFFFFFF, true)
150
+ sv.setUint32(12, Math.floor(ownerPtr / 0x100000000), true)
151
+
152
+ setPtr(sv, 24, ffi.bufferPtr(filterWide))
153
+ setPtr(sv, 48, ffi.bufferPtr(fileBuf))
154
+ sv.setUint32(56, 260, true)
155
+
156
+ sv.setUint32(96, 0x1000 | 0x0800 | 0x0004, true)
157
+
158
+ const ret = ffi.ffiCall(GetOpenFileNameW, [FFI_PTR], [structBuf], FFI_U32)
159
+ if (!ret) return null
160
+
161
+ const path = wideToStr(fileBuf)
162
+ return path.length > 0 ? path : null
163
+ }
164
+
165
+ function renderPdfPage(mupdf: MuPdf, filePath: string, pageIndex: number): PixmapInfo & { totalPages: number } | null {
166
+ if (filePath !== cachedPath) {
167
+ clearCachedDoc()
168
+ const fp = std.open(filePath, 'rb')
169
+ if (!fp) { std.printf('Error: cannot open %s\n', filePath); return null }
170
+ let buf: ArrayBuffer | null = null
171
+ try {
172
+ fp.seek(0, 2)
173
+ const size = fp.tell()
174
+ fp.seek(0, 0)
175
+ buf = new ArrayBuffer(size)
176
+ fp.read(buf, 0, size)
177
+ } finally {
178
+ fp.close()
179
+ }
180
+ if (!buf) return null
181
+ try {
182
+ cachedDoc = mupdf.Document.openDocument(new Uint8Array(buf), 'application/pdf')
183
+ cachedTotalPages = cachedDoc.countPages()
184
+ std.printf('Pages: %d\n', cachedTotalPages)
185
+ cachedPath = filePath
186
+ } catch (e) {
187
+ std.printf('Error opening document: %s\n', String(e))
188
+ return null
189
+ }
190
+ }
191
+
192
+ if (!cachedDoc) return null
193
+ if (pageIndex >= cachedTotalPages) return null
194
+
195
+ let page: Page | null = null
196
+ let pixmap: Pixmap | null = null
197
+ try {
198
+ page = cachedDoc.loadPage(pageIndex)
199
+
200
+ const scale = 1.5
201
+ pixmap = page.toPixmap(
202
+ mupdf.Matrix.scale(scale, scale),
203
+ mupdf.ColorSpace.DeviceRGB,
204
+ false
205
+ )
206
+ if (!pixmap) return null
207
+
208
+ const srcPixels = pixmap.getPixels()
209
+ const srcStride = pixmap.getStride()
210
+ const w = pixmap.getWidth()
211
+ const h = pixmap.getHeight()
212
+ const dibStride = Math.floor((w * 3 + 3) / 4) * 4
213
+
214
+ const dibSize = h * dibStride
215
+ const dibBuffer = new ArrayBuffer(dibSize)
216
+ const dib = new Uint8Array(dibBuffer)
217
+
218
+ for (let y = 0; y < h; y++) {
219
+ const srcOff = y * srcStride
220
+ const dstOff = y * dibStride
221
+ for (let x = 0; x < w; x++) {
222
+ const sx = srcOff + x * 3
223
+ const dx = dstOff + x * 3
224
+ dib[dx] = srcPixels[sx + 2]
225
+ dib[dx + 1] = srcPixels[sx + 1]
226
+ dib[dx + 2] = srcPixels[sx]
227
+ }
228
+ }
229
+
230
+ return { data: dibBuffer, w, h, totalPages: cachedTotalPages }
231
+ } catch (e) {
232
+ std.printf('Error rendering: %s\n', String(e))
233
+ clearCachedDoc()
234
+ return null
235
+ } finally {
236
+ if (pixmap) { try { pixmap.destroy() } catch {} }
237
+ if (page) { try { page.destroy() } catch {} }
238
+ }
239
+ }
240
+
241
+
242
+ function getClientSize(h: number): { w: number, h: number } {
243
+ const rect = new ArrayBuffer(16)
244
+ ffi.ffiCall(GetClientRect, T_U64_PTR, [h, rect], FFI_U32) as number
245
+ const dv = new DataView(rect)
246
+ return { w: dv.getInt32(8, true), h: dv.getInt32(12, true) }
247
+ }
248
+
249
+ function setScrollPos(h: number, bar: number, pos: number): void {
250
+ const si = new ArrayBuffer(28)
251
+ const dv = new DataView(si)
252
+ dv.setUint32(0, 28, true)
253
+ dv.setUint32(4, gui.ScrollInfoFlag.POS, true)
254
+ dv.setInt32(20, pos, true)
255
+ ffi.ffiCall(SetScrollInfo, T_U64_S32_PTR_U32, [h, bar, si, 1], FFI_U32)
256
+ }
257
+
258
+ function updateScrollRange(h: number): void {
259
+ if (!currentPixmap) return
260
+ const client = getClientSize(h)
261
+ const viewH = client.h - TOP_OFFSET
262
+ const maxX = Math.max(0, currentPixmap.w - client.w)
263
+ const maxY = Math.max(0, currentPixmap.h - viewH)
264
+ scrollX = Math.min(scrollX, maxX)
265
+ scrollY = Math.min(scrollY, maxY)
266
+
267
+ const si = new ArrayBuffer(28)
268
+ const dv = new DataView(si)
269
+ dv.setUint32(0, 28, true)
270
+
271
+ dv.setUint32(4, gui.ScrollInfoFlag.RANGE | gui.ScrollInfoFlag.PAGE, true)
272
+ dv.setInt32(8, 0, true)
273
+ dv.setInt32(12, currentPixmap.w - 1, true)
274
+ dv.setUint32(16, client.w, true)
275
+ ffi.ffiCall(SetScrollInfo, T_U64_S32_PTR_U32, [h, gui.ScrollBar.HORZ, si, 1], FFI_U32)
276
+
277
+ dv.setInt32(8, 0, true)
278
+ dv.setInt32(12, currentPixmap.h - 1, true)
279
+ dv.setUint32(16, viewH, true)
280
+ ffi.ffiCall(SetScrollInfo, T_U64_S32_PTR_U32, [h, gui.ScrollBar.VERT, si, 1], FFI_U32)
281
+
282
+ setScrollPos(h, gui.ScrollBar.HORZ, scrollX)
283
+ setScrollPos(h, gui.ScrollBar.VERT, scrollY)
284
+ }
285
+
286
+ function doScroll(h: number, dx: number, dy: number): void {
287
+ const client = getClientSize(h)
288
+ scrollX = clamp(scrollX + dx, currentPixmap ? Math.max(0, currentPixmap.w - client.w) : 0)
289
+ scrollY = clamp(scrollY + dy, currentPixmap ? Math.max(0, currentPixmap.h - (client.h - TOP_OFFSET)) : 0)
290
+ setScrollPos(h, gui.ScrollBar.HORZ, scrollX)
291
+ setScrollPos(h, gui.ScrollBar.VERT, scrollY)
292
+ ffi.ffiCall(InvalidateRect, T_U64_U64_U32, [h, 0, 1], FFI_U32)
293
+ }
294
+
295
+ function renderAndDisplay(mupdf: MuPdf, path: string, pageIndex: number): void {
296
+ const edit = hwndEdit
297
+ const hMain = hwndMain
298
+ if (!edit || !hMain) return
299
+ gui.SetWindowText(edit, path)
300
+ const pix = renderPdfPage(mupdf, path, pageIndex)
301
+ if (pix) {
302
+ currentPixmap = pix
303
+ currentPage = pageIndex
304
+ totalPages = pix.totalPages
305
+ gui.SetWindowText(hMain, 'PDF 预览 - 第 ' + (pageIndex + 1) + '/' + totalPages + ' 页')
306
+ scrollX = 0
307
+ scrollY = 0
308
+ updateScrollRange(hMain)
309
+ ffi.ffiCall(InvalidateRect, T_U64_U64_U32, [hMain, 0, 1], FFI_U32)
310
+ } else {
311
+ gui.MessageBox('渲染 PDF 失败')
312
+ }
313
+ }
314
+
315
+ async function main(): Promise<void> {
316
+ const mupdf = await loadMupdf()
317
+ if (!mupdf) {
318
+ gui.MessageBox('加载 mupdf WASM 失败。\n请确保 vendor/mupdf-wasm/ 在构建目录中。')
319
+ return
320
+ }
321
+ gui.RegisterClass('PdfPreview', (hwnd, msg, wParam, lParam) => {
322
+ if (!hwnd) return gui.DefWindowProc(hwnd, msg, wParam, lParam)
323
+ const h = hwnd as number
324
+ switch (msg) {
325
+ case gui.WmMsg.DESTROY:
326
+ gui.PostQuitMessage(0)
327
+ return 0
328
+
329
+ case gui.WmMsg.COMMAND: {
330
+ const hCtrl = lParam
331
+ const btnOpen = hwndBtnOpen as number | null
332
+ const btnPrev = hwndBtnPrev as number | null
333
+ const btnNext = hwndBtnNext as number | null
334
+ if (hCtrl === btnOpen) {
335
+ const path = openPdfFileDialog()
336
+ if (path) renderAndDisplay(mupdf, path, 0)
337
+ } else if (hCtrl === btnPrev && currentPage > 0) {
338
+ const path = gui.GetWindowText(hwndEdit!)
339
+ if (path) renderAndDisplay(mupdf, path, currentPage - 1)
340
+ } else if (hCtrl === btnNext && currentPage < totalPages - 1) {
341
+ const path = gui.GetWindowText(hwndEdit!)
342
+ if (path) renderAndDisplay(mupdf, path, currentPage + 1)
343
+ }
344
+ return 0
345
+ }
346
+
347
+ case gui.WmMsg.PAINT: {
348
+ const hdc = ffi.ffiCall(GetDC, [ffi.FFI_TYPE_UINT64], [h], ffi.FFI_TYPE_UINT64)
349
+ if (hdc) {
350
+ ffi.ffiCall(PatBlt, [ffi.FFI_TYPE_UINT64, FFI_S32, FFI_S32, FFI_S32, FFI_S32, FFI_U32], [hdc, 0, 0, 32767, TOP_OFFSET, 0x00FF0062], FFI_U32)
351
+ if (currentPixmap) {
352
+ const bmi = makeBitmapInfo(currentPixmap.w, currentPixmap.h)
353
+ ffi.ffiCall(SetDIBitsToDevice, [
354
+ ffi.FFI_TYPE_UINT64, FFI_S32, FFI_S32, FFI_U32, FFI_U32,
355
+ FFI_S32, FFI_S32, FFI_U32, FFI_U32,
356
+ FFI_PTR, FFI_PTR, FFI_U32
357
+ ], [
358
+ hdc, -scrollX, TOP_OFFSET - scrollY,
359
+ currentPixmap.w, currentPixmap.h,
360
+ 0, 0, 0, currentPixmap.h,
361
+ currentPixmap.data, bmi, 0
362
+ ], FFI_S32)
363
+ }
364
+ ffi.ffiCall(ReleaseDC, [ffi.FFI_TYPE_UINT64, ffi.FFI_TYPE_UINT64], [h, hdc], FFI_S32)
365
+ }
366
+ return gui.DefWindowProc(hwnd, msg, wParam, lParam)
367
+ }
368
+
369
+ case gui.WmMsg.HSCROLL: {
370
+ if (wParam === 0) return 0
371
+ const code = wParam & 0xFFFF
372
+ const thumb = (wParam >> 16) & 0xFFFF
373
+ let dx = 0
374
+ if (code === gui.ScrollCmd.LINEUP) dx = -20
375
+ else if (code === gui.ScrollCmd.LINEDOWN) dx = 20
376
+ else if (code === gui.ScrollCmd.PAGEUP) dx = -60
377
+ else if (code === gui.ScrollCmd.PAGEDOWN) dx = 60
378
+ else if (code === gui.ScrollCmd.THUMBTRACK) dx = thumb - scrollX
379
+ if (dx) doScroll(h, dx, 0)
380
+ return 0
381
+ }
382
+
383
+ case gui.WmMsg.VSCROLL:
384
+ case gui.WmMsg.MOUSEWHEEL: {
385
+ let dy = 0
386
+ if (msg === gui.WmMsg.VSCROLL) {
387
+ if (wParam === 0) return 0
388
+ const code = wParam & 0xFFFF
389
+ const thumb = (wParam >> 16) & 0xFFFF
390
+ if (code === gui.ScrollCmd.LINEUP) dy = -20
391
+ else if (code === gui.ScrollCmd.LINEDOWN) dy = 20
392
+ else if (code === gui.ScrollCmd.PAGEUP) dy = -60
393
+ else if (code === gui.ScrollCmd.PAGEDOWN) dy = 60
394
+ else if (code === gui.ScrollCmd.THUMBTRACK) dy = thumb - scrollY
395
+ } else {
396
+ const raw = (wParam >>> 16) & 0xFFFF
397
+ const wheel = raw >= 0x8000 ? raw - 0x10000 : raw
398
+ dy = -Math.round(wheel * 40 / 120)
399
+ }
400
+ if (dy) doScroll(h, 0, dy)
401
+ return 0
402
+ }
403
+
404
+ case gui.WmMsg.SIZE: {
405
+ if (currentPixmap) updateScrollRange(h)
406
+ ffi.ffiCall(InvalidateRect, T_U64_U64_U32, [h, 0, 1], FFI_U32)
407
+ return 0
408
+ }
409
+
410
+ default:
411
+ return gui.DefWindowProc(hwnd, msg, wParam, lParam)
412
+ }
413
+ })
414
+
415
+ const winW = 960, winH = 720
416
+ const screenW = ffi.ffiCall(GetSystemMetrics, [FFI_S32], [gui.SysMetrics.CXSCREEN], FFI_S32) as number
417
+ const screenH = ffi.ffiCall(GetSystemMetrics, [FFI_S32], [gui.SysMetrics.CYSCREEN], FFI_S32) as number
418
+ const winX = Math.max(0, (screenW - winW) >> 1)
419
+ const winY = Math.max(0, (screenH - winH) >> 1)
420
+
421
+ const ctrlY = 12
422
+ const ctrlH = 26
423
+ const gap = 4
424
+ const btnOpenW = 80
425
+
426
+ hwndMain = gui.CreateWindow(
427
+ 'PdfPreview', 'PDF 预览',
428
+ gui.WindowStyle.OVERLAPPEDWINDOW | gui.WindowStyle.HSCROLL | gui.WindowStyle.VSCROLL | gui.WindowStyle.CLIPCHILDREN,
429
+ winX, winY, winW, winH,
430
+ null, null
431
+ )
432
+ if (!hwndMain) {
433
+ gui.MessageBox('创建主窗口失败')
434
+ return
435
+ }
436
+
437
+ hwndBtnOpen = gui.CreateWindow(
438
+ 'BUTTON', '打开 PDF',
439
+ gui.WindowStyle.CHILD | gui.WindowStyle.VISIBLE | gui.WmMsg.COMMAND,
440
+ ctrlY, ctrlY, btnOpenW, ctrlH, hwndMain, null
441
+ )
442
+ const editW = 480
443
+ const btnPageW = 72
444
+ const editX = ctrlY + btnOpenW + gap
445
+ hwndEdit = gui.CreateWindow(
446
+ 'EDIT', '',
447
+ gui.WindowStyle.CHILD | gui.WindowStyle.VISIBLE | gui.WindowStyle.BORDER,
448
+ editX, ctrlY, editW, ctrlH, hwndMain, null
449
+ )
450
+ hwndBtnPrev = gui.CreateWindow(
451
+ 'BUTTON', '上一页',
452
+ gui.WindowStyle.CHILD | gui.WindowStyle.VISIBLE | gui.WmMsg.COMMAND,
453
+ editX + editW + gap, ctrlY, btnPageW, ctrlH, hwndMain, null
454
+ )
455
+ hwndBtnNext = gui.CreateWindow(
456
+ 'BUTTON', '下一页',
457
+ gui.WindowStyle.CHILD | gui.WindowStyle.VISIBLE | gui.WmMsg.COMMAND,
458
+ editX + editW + gap + btnPageW + gap, ctrlY, btnPageW, ctrlH, hwndMain, null
459
+ )
460
+
461
+ gui.ShowWindow(hwndMain)
462
+
463
+ const test = std.open('example.pdf', 'rb')
464
+ if (test) {
465
+ test.close()
466
+ renderAndDisplay(mupdf, 'example.pdf', 0)
467
+ }
468
+ }
469
+
470
+ main()
@@ -0,0 +1,35 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "../lib/preact/jsx-runtime.js";
2
+ import '../lib/polyfill.js';
3
+ import * as gui from 'gui';
4
+ import * as ffi from 'ffi';
5
+ import * as win from 'win';
6
+ import { useState } from '../lib/preact/hooks.js';
7
+ import { render, notifyResize, scaleFactor } from '../lib/preact/render.js';
8
+ const _user32 = win.LoadLibrary('user32.dll');
9
+ const GetSystemMetrics = _user32 ? win.GetProcAddress(_user32, 'GetSystemMetrics') : 0;
10
+ const winW = 600 * scaleFactor;
11
+ const winH = 400 * scaleFactor;
12
+ const cxScreen = GetSystemMetrics ? ffi.ffiCall(GetSystemMetrics, [ffi.FFI_TYPE_SINT32], [0 /* gui.SysMetrics.CXSCREEN */], ffi.FFI_TYPE_SINT32) : 1920;
13
+ const cyScreen = GetSystemMetrics ? ffi.ffiCall(GetSystemMetrics, [ffi.FFI_TYPE_SINT32], [1 /* gui.SysMetrics.CYSCREEN */], ffi.FFI_TYPE_SINT32) : 1080;
14
+ const winX = Math.max(0, (cxScreen - winW) / 2);
15
+ const winY = Math.max(0, (cyScreen - winH) / 2);
16
+ gui.RegisterClass('DemoApp', (hwnd, msg, wParam, lParam) => {
17
+ switch (msg) {
18
+ case 2 /* gui.WmMsg.DESTROY */:
19
+ gui.PostQuitMessage(0);
20
+ return 0;
21
+ case 3 /* gui.WmMsg.SIZE */:
22
+ notifyResize(hwnd);
23
+ return 0;
24
+ }
25
+ return gui.DefWindowProc(hwnd, msg, wParam, lParam);
26
+ });
27
+ const mainWnd = gui.CreateWindow('DemoApp', 'Preact Demo', 13565952 /* gui.WindowStyle.OVERLAPPEDWINDOW */, winX, winY, winW, winH, null, null);
28
+ function App() {
29
+ const [count, setCount] = useState(0);
30
+ return (_jsxs("w", { style: { flexDirection: 'column', padding: 10, gap: 8 }, children: [_jsx("w", { type: "static", text: `Counter: ${count}`, style: { height: 24 } }), _jsxs("w", { style: { flexDirection: 'row', gap: 8 }, children: [_jsx("w", { type: "button", text: "+1", style: { width: 80, height: 28 }, onEvent: (e) => { if (e.msg === 513 /* gui.WmMsg.LBUTTONDOWN */)
31
+ setCount(count + 1); } }), _jsx("w", { type: "button", text: "-1", style: { width: 80, height: 28 }, onEvent: (e) => { if (e.msg === 513 /* gui.WmMsg.LBUTTONDOWN */)
32
+ setCount(count - 1); } })] }), _jsx("w", { type: "edit", value: "test input", style: { height: 26 } })] }));
33
+ }
34
+ gui.ShowWindow(mainWnd);
35
+ render(_jsx(App, {}), mainWnd);
@@ -0,0 +1,49 @@
1
+ import '../lib/polyfill.js'
2
+ import * as gui from 'gui'
3
+ import * as ffi from 'ffi'
4
+ import * as win from 'win'
5
+ import { useState } from '../lib/preact/hooks.js'
6
+ import { render, notifyResize, scaleFactor } from '../lib/preact/render.js'
7
+
8
+ const _user32 = win.LoadLibrary('user32.dll')
9
+ const GetSystemMetrics = _user32 ? win.GetProcAddress(_user32, 'GetSystemMetrics') : 0
10
+ const winW = 600 * scaleFactor
11
+ const winH = 400 * scaleFactor
12
+ const cxScreen = GetSystemMetrics ? (ffi.ffiCall(GetSystemMetrics, [ffi.FFI_TYPE_SINT32], [gui.SysMetrics.CXSCREEN], ffi.FFI_TYPE_SINT32) as number) : 1920
13
+ const cyScreen = GetSystemMetrics ? (ffi.ffiCall(GetSystemMetrics, [ffi.FFI_TYPE_SINT32], [gui.SysMetrics.CYSCREEN], ffi.FFI_TYPE_SINT32) as number) : 1080
14
+ const winX = Math.max(0, (cxScreen - winW) / 2)
15
+ const winY = Math.max(0, (cyScreen - winH) / 2)
16
+
17
+ gui.RegisterClass('DemoApp', (hwnd, msg, wParam, lParam) => {
18
+ switch (msg) {
19
+ case gui.WmMsg.DESTROY:
20
+ gui.PostQuitMessage(0)
21
+ return 0
22
+ case gui.WmMsg.SIZE:
23
+ notifyResize(hwnd)
24
+ return 0
25
+ }
26
+ return gui.DefWindowProc(hwnd, msg, wParam, lParam)
27
+ })
28
+
29
+ const mainWnd = gui.CreateWindow('DemoApp', 'Preact Demo',
30
+ gui.WindowStyle.OVERLAPPEDWINDOW,
31
+ winX, winY, winW, winH, null, null)
32
+
33
+ function App() {
34
+ const [count, setCount] = useState(0)
35
+
36
+ return (
37
+ <w style={{ flexDirection: 'column', padding: 10, gap: 8 }}>
38
+ <w type="static" text={`Counter: ${count}`} style={{ height: 24 }} />
39
+ <w style={{ flexDirection: 'row', gap: 8 }}>
40
+ <w type="button" text="+1" style={{ width: 80, height: 28 }} onEvent={(e) => { if (e.msg === gui.WmMsg.LBUTTONDOWN) setCount(count + 1) }} />
41
+ <w type="button" text="-1" style={{ width: 80, height: 28 }} onEvent={(e) => { if (e.msg === gui.WmMsg.LBUTTONDOWN) setCount(count - 1) }} />
42
+ </w>
43
+ <w type="edit" value="test input" style={{ height: 26 }} />
44
+ </w>
45
+ )
46
+ }
47
+
48
+ gui.ShowWindow(mainWnd)
49
+ render(<App />, mainWnd)
@@ -0,0 +1,75 @@
1
+ import * as gui from 'gui';
2
+ import * as std from 'std';
3
+ const WM_TRAY = 0x8001;
4
+ gui.RegisterClass('TrayApp', (hwnd, msg, wParam, lParam) => {
5
+ if (msg === WM_TRAY) {
6
+ const evt = lParam;
7
+ if (evt === 0x0201) { // WM_LBUTTONDOWN
8
+ gui.ShowWindow(hwnd, 5); // SW_SHOW
9
+ gui.SetForegroundWindow(hwnd);
10
+ }
11
+ else if (evt === 0x0204 || evt === 0x007B) { // WM_RBUTTONDOWN or WM_CONTEXTMENU
12
+ gui.SetForegroundWindow(hwnd);
13
+ const pos = gui.GetCursorPos();
14
+ const x = pos ? pos[0] : 0;
15
+ const y = pos ? pos[1] : 0;
16
+ const hMenu = gui.CreatePopupMenu();
17
+ if (hMenu) {
18
+ gui.AppendMenu(hMenu, 0, 1, '显示窗口');
19
+ gui.AppendMenu(hMenu, 0x0800, 0, '');
20
+ gui.AppendMenu(hMenu, 0, 2, '退出');
21
+ const cmd = gui.TrackPopupMenu(hMenu, x, y, undefined, hwnd);
22
+ gui.DestroyMenu(hMenu);
23
+ if (cmd === 1) {
24
+ gui.ShowWindow(hwnd, 5); // SW_SHOW
25
+ }
26
+ else if (cmd === 2) {
27
+ gui.ShellNotifyIcon(2 /* gui.NotifyIconCmd.DELETE */, { hwnd, uID: 1 });
28
+ gui.PostQuitMessage(0);
29
+ }
30
+ }
31
+ }
32
+ return 0;
33
+ }
34
+ if (msg === 16 /* gui.WmMsg.CLOSE */) {
35
+ gui.ShowWindow(hwnd, 0); // SW_HIDE
36
+ return 0;
37
+ }
38
+ return gui.DefWindowProc(hwnd, msg, wParam, lParam);
39
+ });
40
+ // 创建窗口
41
+ const W = 500, H = 400;
42
+ const scr = gui.GetScreenSize();
43
+ const x = (scr[0] - W) >> 1;
44
+ const y = (scr[1] - H) >> 1;
45
+ const hwnd = gui.CreateWindow('TrayApp', '系统托盘示例', 13565952 /* gui.WindowStyle.OVERLAPPEDWINDOW */, x, y, W, H, null, null);
46
+ gui.ShowWindow(hwnd);
47
+ // 添加说明文字
48
+ const ES_MULTILINE = 0x0004;
49
+ const ES_READONLY = 0x0800;
50
+ const style = 1073741824 /* gui.WindowStyle.CHILD */ | 268435456 /* gui.WindowStyle.VISIBLE */ | ES_MULTILINE | ES_READONLY;
51
+ const text = gui.CreateWindow('EDIT', 'QuickWin 系统托盘示例\r\n'
52
+ + '\r\n'
53
+ + '右键点击托盘图标打开上下文菜单。\r\n'
54
+ + '关闭此窗口将隐藏到托盘。\r\n'
55
+ + '从菜单中选择"显示窗口"恢复窗口,"退出"结束程序。', style, 15, 15, W - 30, H - 70, hwnd, null);
56
+ if (text) {
57
+ const font = gui.CreateSystemDpiFont();
58
+ if (font)
59
+ gui.SendMessage(text, 48 /* gui.WmMsg.SETFONT */, font, 1);
60
+ }
61
+ const hIcon = gui.LoadIcon('APPLICATION');
62
+ if (hIcon) {
63
+ const ok = gui.ShellNotifyIcon(0 /* gui.NotifyIconCmd.ADD */, {
64
+ hwnd, uID: 1,
65
+ flags: 1 /* gui.NotifyIconFlag.MESSAGE */ | 2 /* gui.NotifyIconFlag.ICON */,
66
+ callbackMessage: WM_TRAY,
67
+ hIcon,
68
+ });
69
+ std.out.printf('tray icon %s\n', ok ? 'added' : 'FAILED');
70
+ std.out.flush();
71
+ }
72
+ else {
73
+ std.out.printf('failed to load icon\n');
74
+ std.out.flush();
75
+ }