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.
- package/README.md +6 -0
- package/examples/pdf_preview.js +440 -0
- package/examples/pdf_preview.ts +470 -0
- package/examples/preact_demo.js +35 -0
- package/examples/preact_demo.tsx +49 -0
- package/examples/tray_demo.js +75 -0
- package/examples/tray_demo.tsx +79 -0
- package/lib/fetch.js +746 -0
- package/lib/fetch.ts +811 -0
- package/lib/polyfill.js +500 -0
- package/lib/polyfill.ts +454 -0
- package/lib/preact/hooks.js +287 -0
- package/lib/preact/hooks.ts +330 -0
- package/lib/preact/jsx-runtime.js +1 -0
- package/lib/preact/jsx-runtime.ts +2 -0
- package/lib/preact/jsx.d.ts +36 -0
- package/lib/preact/layout.js +153 -0
- package/lib/preact/layout.ts +183 -0
- package/lib/preact/preact.js +54 -0
- package/lib/preact/preact.ts +133 -0
- package/lib/preact/props.js +99 -0
- package/lib/preact/props.ts +119 -0
- package/lib/preact/render.js +320 -0
- package/lib/preact/render.ts +353 -0
- package/lib/websocket.js +540 -0
- package/lib/websocket.ts +574 -0
- package/package.json +32 -0
- package/quickwin.d.ts +657 -0
- package/test/add.wasm +0 -0
- package/test/complex.wasm +0 -0
- package/test/complex_imports.wasm +0 -0
- package/test/global_imports.wasm +0 -0
- package/test/import_func.wasm +0 -0
- package/test/imports.wasm +0 -0
- package/test/run.js +86 -0
- package/test/run.ts +90 -0
- package/test/sjlj.wasm +0 -0
- package/test/test_basic.js +7 -0
- package/test/test_basic.ts +9 -0
- package/test/test_brotli.js +48 -0
- package/test/test_brotli.ts +52 -0
- package/test/test_fetch_cache.js +131 -0
- package/test/test_fetch_cache.ts +141 -0
- package/test/test_ffi.js +157 -0
- package/test/test_ffi.ts +174 -0
- package/test/test_frame_encoding.js +128 -0
- package/test/test_frame_encoding.ts +132 -0
- package/test/test_helper.js +84 -0
- package/test/test_helper.ts +80 -0
- package/test/test_http_import.js +78 -0
- package/test/test_http_import.ts +74 -0
- package/test/test_mupdf_render.js +69 -0
- package/test/test_mupdf_render.ts +74 -0
- package/test/test_mupdf_twice.js +77 -0
- package/test/test_mupdf_twice.ts +81 -0
- package/test/test_mupdf_wasm.js +33 -0
- package/test/test_mupdf_wasm.ts +30 -0
- package/test/test_net_event.js +63 -0
- package/test/test_net_event.ts +59 -0
- package/test/test_net_fetch.js +153 -0
- package/test/test_net_fetch.ts +131 -0
- package/test/test_net_websocket.js +158 -0
- package/test/test_net_websocket.ts +144 -0
- package/test/test_polyfill.js +58 -0
- package/test/test_polyfill.ts +60 -0
- package/test/test_url.js +173 -0
- package/test/test_url.ts +183 -0
- package/test/test_wasm_basic.js +82 -0
- package/test/test_wasm_basic.ts +70 -0
- package/test/test_wasm_import_global.js +41 -0
- package/test/test_wasm_import_global.ts +39 -0
- package/test/test_wasm_sjlj.js +153 -0
- package/test/test_wasm_sjlj.ts +134 -0
- package/test/test_wasm_types.js +96 -0
- package/test/test_wasm_types.ts +108 -0
- package/test/types.wasm +0 -0
- package/tsconfig.json +18 -0
- package/vendor/mupdf-wasm/mupdf-wasm.d.ts +571 -0
- package/vendor/mupdf-wasm/mupdf-wasm.js +2749 -0
- package/vendor/mupdf-wasm/mupdf-wasm.wasm +0 -0
- package/vendor/mupdf-wasm/mupdf.d.ts +939 -0
- package/vendor/mupdf-wasm/mupdf.js +3317 -0
- 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
|
+
}
|