quickwin 1.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 +197 -0
- package/README.md +196 -0
- package/examples/PdfCanvas.js +67 -0
- package/examples/PdfCanvas.tsx +83 -0
- package/examples/pdf_preview2.js +431 -0
- package/examples/pdf_preview2.ts +375 -0
- package/examples/pdf_viewer.js +17623 -0
- package/examples/pdf_viewer.tsx +279 -0
- package/examples/test_gallery.js +17452 -0
- package/examples/test_gallery.tsx +285 -0
- package/examples/test_react_lazy.js +17336 -0
- package/examples/test_react_lazy.tsx +63 -0
- package/examples/test_react_listbox.js +17339 -0
- package/examples/test_react_listbox.tsx +71 -0
- package/examples/test_root_window.js +17313 -0
- package/examples/test_root_window.tsx +41 -0
- package/examples/test_tab.js +17339 -0
- package/examples/test_tab.tsx +75 -0
- package/examples/tray_demo.js +79 -0
- package/examples/tray_demo.tsx +81 -0
- package/lib/assert.js +8 -0
- package/lib/assert.ts +7 -0
- package/lib/cache-utils.js +43 -0
- package/lib/cache-utils.ts +40 -0
- package/lib/dumpRects.js +25 -0
- package/lib/dumpRects.ts +25 -0
- package/lib/fetch.js +490 -0
- package/lib/fetch.ts +502 -0
- package/lib/http-common.js +342 -0
- package/lib/http-common.ts +399 -0
- package/lib/http-server.js +416 -0
- package/lib/http-server.ts +424 -0
- package/lib/polyfill.js +156 -0
- package/lib/polyfill.ts +171 -0
- package/lib/react-qw/components/Button.js +8 -0
- package/lib/react-qw/components/CheckBox.js +31 -0
- package/lib/react-qw/components/ComboBox.js +42 -0
- package/lib/react-qw/components/DateTimePicker.js +96 -0
- package/lib/react-qw/components/Input.js +50 -0
- package/lib/react-qw/components/Link.js +43 -0
- package/lib/react-qw/components/ListBox.js +48 -0
- package/lib/react-qw/components/ListView.js +328 -0
- package/lib/react-qw/components/PathPicker.js +136 -0
- package/lib/react-qw/components/ProgressBar.js +22 -0
- package/lib/react-qw/components/RadioButton.js +31 -0
- package/lib/react-qw/components/ScrollView.js +139 -0
- package/lib/react-qw/components/Slider.js +45 -0
- package/lib/react-qw/components/Tab.js +61 -0
- package/lib/react-qw/components/Tooltip.js +55 -0
- package/lib/react-qw/components/TreeView.js +130 -0
- package/lib/react-qw/components/index.js +16 -0
- package/lib/react-qw/components/index.ts +17 -0
- package/lib/react-qw/index.js +73 -0
- package/lib/react-qw/index.ts +90 -0
- package/lib/react-qw/jsx.d.ts +34 -0
- package/lib/react-qw/layout.js +82 -0
- package/lib/react-qw/layout.ts +111 -0
- package/lib/react-qw/props.js +27 -0
- package/lib/react-qw/props.ts +36 -0
- package/lib/react-qw/reconciler.js +333 -0
- package/lib/react-qw/reconciler.ts +373 -0
- package/lib/stream.js +2 -0
- package/lib/stream.ts +22 -0
- package/lib/text-codec.js +197 -0
- package/lib/text-codec.ts +171 -0
- package/lib/text-measure.js +52 -0
- package/lib/text-measure.ts +54 -0
- package/lib/url.js +278 -0
- package/lib/url.ts +201 -0
- package/lib/vendor/web-streams/ponyfill.d.mts +829 -0
- package/lib/vendor/web-streams/ponyfill.mjs +8 -0
- package/lib/websocket.js +541 -0
- package/lib/websocket.ts +555 -0
- package/package.json +44 -0
- package/quickwin.d.ts +594 -0
- package/quickwin_const.d.ts +900 -0
- package/qwin-nowasm.exe +0 -0
- package/qwin.exe +0 -0
- package/test/add.wasm +0 -0
- package/test/complex.wasm +0 -0
- package/test/complex_imports.wasm +0 -0
- package/test/debug/http_server_e2e.js +31 -0
- package/test/debug/minwasm.js +52 -0
- package/test/debug/mupdf_debug.js +104 -0
- package/test/debug/wasm.js +122 -0
- package/test/debug/wasm_export_debug.js +64 -0
- package/test/debug/wasm_names.js +46 -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 +91 -0
- package/test/run.ts +96 -0
- package/test/sjlj.wasm +0 -0
- package/test/test_basic.js +7 -0
- package/test/test_basic.ts +9 -0
- package/test/test_basic_fetch.js +34 -0
- package/test/test_basic_fetch.ts +33 -0
- package/test/test_brotli.js +48 -0
- package/test/test_brotli.ts +52 -0
- package/test/test_fetch_cache.js +169 -0
- package/test/test_fetch_cache.ts +187 -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_http_server.js +142 -0
- package/test/test_http_server.ts +136 -0
- package/test/test_import_dir.js +4 -0
- package/test/test_import_dir.ts +5 -0
- package/test/test_ipv6.js +134 -0
- package/test/test_ipv6.ts +129 -0
- package/test/test_jsx.js +1314 -0
- package/test/test_meta_url.js +3 -0
- package/test/test_meta_url.ts +4 -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 +57 -0
- package/test/test_net_event.ts +55 -0
- package/test/test_net_fetch.js +259 -0
- package/test/test_net_fetch.ts +238 -0
- package/test/test_net_websocket.js +180 -0
- package/test/test_net_websocket.ts +164 -0
- package/test/test_polyfill.js +144 -0
- package/test/test_polyfill.ts +159 -0
- package/test/test_react_complex.js +17440 -0
- package/test/test_react_counter.js +17331 -0
- package/test/test_react_flex.js +17643 -0
- package/test/test_react_hidden.js +17336 -0
- package/test/test_react_listview_link.js +17371 -0
- package/test/test_react_render.js +17326 -0
- package/test/test_url.js +173 -0
- package/test/test_url.ts +183 -0
- package/test/test_wasm_basic.js +85 -0
- package/test/test_wasm_basic.ts +73 -0
- package/test/test_wasm_import_global.js +41 -0
- package/test/test_wasm_import_global.ts +45 -0
- package/test/test_wasm_sjlj.js +145 -0
- package/test/test_wasm_sjlj.ts +125 -0
- package/test/test_wasm_types.js +97 -0
- package/test/test_wasm_types.ts +124 -0
- package/test/test_worker.js +21 -0
- package/test/test_worker.ts +24 -0
- package/test/test_worker_fetch.js +26 -0
- package/test/test_worker_fetch.ts +27 -0
- package/test/test_worker_net.js +51 -0
- package/test/test_worker_net.ts +54 -0
- package/test/test_worker_wasm_concurrent.js +44 -0
- package/test/test_worker_wasm_concurrent.ts +45 -0
- package/test/types.wasm +0 -0
- package/test/worker_fetch.js +13 -0
- package/test/worker_fetch.ts +13 -0
- package/test/worker_helper.js +1 -0
- package/test/worker_helper.ts +1 -0
- package/test/worker_http.js +18 -0
- package/test/worker_http.ts +20 -0
- package/test/worker_import_lib.js +12 -0
- package/test/worker_import_lib.ts +13 -0
- package/test/worker_module.js +16 -0
- package/test/worker_module.ts +17 -0
- package/test/worker_wasm_concurrent.js +22 -0
- package/test/worker_wasm_concurrent.ts +22 -0
- package/tsconfig.json +27 -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
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { forwardRef, useRef, useEffect } from 'react';
|
|
3
|
+
import * as gui from 'gui';
|
|
4
|
+
export const Slider = forwardRef(({ value, onChange, min = 0, max = 100, vertical = false, disabled, style }, ref) => {
|
|
5
|
+
const wrapperRef = useRef(null);
|
|
6
|
+
const sliderRef = useRef(null);
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
const h = sliderRef.current;
|
|
9
|
+
if (!h)
|
|
10
|
+
return;
|
|
11
|
+
const lParam = (min & 0xFFFF) | ((max & 0xFFFF) << 16);
|
|
12
|
+
gui.SendMessage(h, 1030 /* gui.TbMsg.SETRANGE */, 0, lParam);
|
|
13
|
+
gui.SendMessage(h, 1029 /* gui.TbMsg.SETPOS */, 1, value);
|
|
14
|
+
}, [min, max]);
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
const h = sliderRef.current;
|
|
17
|
+
if (!h)
|
|
18
|
+
return;
|
|
19
|
+
gui.SendMessage(h, 1029 /* gui.TbMsg.SETPOS */, 1, value);
|
|
20
|
+
}, [value]);
|
|
21
|
+
const tbStyle = 268435456 /* gui.WindowStyle.VISIBLE */ | 65536 /* gui.WindowStyle.TABSTOP */
|
|
22
|
+
| 1 /* gui.TrackBarStyle.AUTOTICKS */
|
|
23
|
+
| (vertical ? 2 /* gui.TrackBarStyle.VERT */ : 0);
|
|
24
|
+
return (_jsx("w", { type: "STATIC", ws: 268435456 /* gui.WindowStyle.VISIBLE */ | 33554432 /* gui.WindowStyle.CLIPCHILDREN */, style: style, ref: (h) => {
|
|
25
|
+
wrapperRef.current = h;
|
|
26
|
+
if (typeof ref === 'function')
|
|
27
|
+
ref(h);
|
|
28
|
+
else if (ref)
|
|
29
|
+
ref.current = h;
|
|
30
|
+
}, onEvent: (e) => {
|
|
31
|
+
if (e.msg === 132 /* gui.WmMsg.NCHITTEST */ ||
|
|
32
|
+
e.msg === 161 /* gui.WmMsg.NCLBUTTONDOWN */) {
|
|
33
|
+
return gui.DefWindowProc(e.hwnd, e.msg, e.wParam, e.lParam);
|
|
34
|
+
}
|
|
35
|
+
if (e.msg === 276 /* gui.WmMsg.HSCROLL */ || e.msg === 277 /* gui.WmMsg.VSCROLL */) {
|
|
36
|
+
const h = sliderRef.current;
|
|
37
|
+
if (!h)
|
|
38
|
+
return 0;
|
|
39
|
+
const pos = gui.SendMessage(h, 1024 /* gui.TbMsg.GETPOS */, 0, 0);
|
|
40
|
+
onChange?.(pos);
|
|
41
|
+
return 0;
|
|
42
|
+
}
|
|
43
|
+
return;
|
|
44
|
+
}, children: _jsx("w", { type: "msctls_trackbar32", ws: tbStyle, disabled: disabled, ref: (h) => { sliderRef.current = h; } }) }));
|
|
45
|
+
});
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { forwardRef, useRef, useEffect, useState } from 'react';
|
|
3
|
+
import * as gui from 'gui';
|
|
4
|
+
import * as ffi from 'ffi';
|
|
5
|
+
function textToUtf16(s) {
|
|
6
|
+
const buf = new ArrayBuffer((s.length + 1) * 2);
|
|
7
|
+
const dv = new DataView(buf);
|
|
8
|
+
for (let i = 0; i < s.length; i++)
|
|
9
|
+
dv.setUint16(i * 2, s.charCodeAt(i), true);
|
|
10
|
+
return buf;
|
|
11
|
+
}
|
|
12
|
+
function readI32(ptr, offset) {
|
|
13
|
+
return ffi.readByte(ptr + offset) | (ffi.readByte(ptr + offset + 1) << 8) |
|
|
14
|
+
(ffi.readByte(ptr + offset + 2) << 16) | (ffi.readByte(ptr + offset + 3) << 24);
|
|
15
|
+
}
|
|
16
|
+
export const Tab = forwardRef(({ tabs, selectedIndex: controlledIndex, defaultSelectedIndex = 0, onChange, style }, ref) => {
|
|
17
|
+
const [internalIndex, setInternalIndex] = useState(defaultSelectedIndex);
|
|
18
|
+
const isControlled = controlledIndex !== undefined;
|
|
19
|
+
const sel = isControlled ? controlledIndex : internalIndex;
|
|
20
|
+
const tabHwnd = useRef(null);
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
const h = tabHwnd.current;
|
|
23
|
+
if (!h)
|
|
24
|
+
return;
|
|
25
|
+
gui.SendMessage(h, 4873 /* gui.TcMsg.DELETEALLITEMS */, 0, 0);
|
|
26
|
+
for (let i = 0; i < tabs.length; i++) {
|
|
27
|
+
const titleBuf = textToUtf16(tabs[i].title);
|
|
28
|
+
const tci = new ArrayBuffer(40);
|
|
29
|
+
const dv = new DataView(tci);
|
|
30
|
+
dv.setUint32(0, 1 /* gui.TcItemFlag.TEXT */, true);
|
|
31
|
+
dv.setBigUint64(16, BigInt(ffi.bufferPtr(titleBuf)), true);
|
|
32
|
+
dv.setInt32(24, tabs[i].title.length + 1, true);
|
|
33
|
+
const tciPtr = ffi.bufferPtr(tci);
|
|
34
|
+
gui.SendMessage(h, 4926 /* gui.TcMsg.INSERTITEMW */, i, tciPtr);
|
|
35
|
+
}
|
|
36
|
+
gui.SendMessage(h, 4876 /* gui.TcMsg.SETCURSEL */, sel, 0);
|
|
37
|
+
}, [tabs]);
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
const h = tabHwnd.current;
|
|
40
|
+
if (h)
|
|
41
|
+
gui.SendMessage(h, 4876 /* gui.TcMsg.SETCURSEL */, sel, 0);
|
|
42
|
+
}, [sel]);
|
|
43
|
+
return (_jsxs("w", { type: "STATIC", ws: 268435456 /* gui.WindowStyle.VISIBLE */ | 33554432 /* gui.WindowStyle.CLIPCHILDREN */, style: { ...style, flexDirection: 'column', alignItems: 'stretch' }, ref: ref, onEvent: (e) => {
|
|
44
|
+
if (e.msg === 78 /* gui.WmMsg.NOTIFY */) {
|
|
45
|
+
const h = tabHwnd.current;
|
|
46
|
+
if (!h)
|
|
47
|
+
return;
|
|
48
|
+
const code = readI32(e.lParam, 16);
|
|
49
|
+
// SysTabControl32 在 comctl32 v6 下不发标准的 TCN_SELCHANGE (-550),
|
|
50
|
+
// 收到 NM_CLICK (-2) 或 TCN_SELCHANGING (-551) 时读实际选中项
|
|
51
|
+
if (code === -552 /* gui.TcNotifyCode.SELCHANGING */ || code === -2 /* gui.SysLinkNotifyCode.CLICK */) {
|
|
52
|
+
const newSel = gui.SendMessage(h, 4875 /* gui.TcMsg.GETCURSEL */, 0, 0);
|
|
53
|
+
if (newSel !== sel) {
|
|
54
|
+
if (!isControlled)
|
|
55
|
+
setInternalIndex(newSel);
|
|
56
|
+
onChange?.(newSel);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}, children: [_jsx("w", { type: "SysTabControl32", ws: 268435456 /* gui.WindowStyle.VISIBLE */ | 32768 /* gui.TabStyle.FOCUSNEVER */, style: { height: 20 }, ref: tabHwnd }), _jsx("w", { type: "STATIC", ws: 268435456 /* gui.WindowStyle.VISIBLE */ | 33554432 /* gui.WindowStyle.CLIPCHILDREN */, style: { flexGrow: 1, flexDirection: 'column', alignItems: 'stretch' }, children: tabs[sel]?.content })] }));
|
|
61
|
+
});
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { useRef, useEffect, Children, cloneElement } from 'react';
|
|
2
|
+
import * as gui from 'gui';
|
|
3
|
+
import * as ffi from 'ffi';
|
|
4
|
+
const TTTOOLINFO_CBSIZE = 64;
|
|
5
|
+
function setQword(dv, offset, val) {
|
|
6
|
+
dv.setUint32(offset, val & 0xFFFFFFFF, true);
|
|
7
|
+
dv.setUint32(offset + 4, Math.floor(val / 0x100000000), true);
|
|
8
|
+
}
|
|
9
|
+
function buildToolInfo(hTarget, text) {
|
|
10
|
+
const textOff = TTTOOLINFO_CBSIZE;
|
|
11
|
+
const textLen = (text.length + 1) * 2;
|
|
12
|
+
const buf = new ArrayBuffer(TTTOOLINFO_CBSIZE + textLen);
|
|
13
|
+
const dv = new DataView(buf);
|
|
14
|
+
dv.setUint32(0, TTTOOLINFO_CBSIZE, true);
|
|
15
|
+
dv.setUint32(4, 16 /* gui.TtToolFlag.SUBCLASS */ | 1 /* gui.TtToolFlag.IDISHWND */, true);
|
|
16
|
+
setQword(dv, 8, hTarget);
|
|
17
|
+
setQword(dv, 16, hTarget);
|
|
18
|
+
for (let i = 0; i < text.length; i++)
|
|
19
|
+
dv.setUint16(textOff + i * 2, text.charCodeAt(i), true);
|
|
20
|
+
dv.setUint16(textOff + text.length * 2, 0, true);
|
|
21
|
+
const bufPtr = ffi.bufferPtr(buf);
|
|
22
|
+
setQword(dv, 48, bufPtr + textOff);
|
|
23
|
+
return buf;
|
|
24
|
+
}
|
|
25
|
+
function Tooltip({ text, children, balloon }) {
|
|
26
|
+
const childRef = useRef(null);
|
|
27
|
+
const hTTRef = useRef(null);
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
const hTarget = childRef.current;
|
|
30
|
+
if (!hTarget)
|
|
31
|
+
return;
|
|
32
|
+
const hTT = gui.CreateWindow('tooltips_class32', '', -2147483648 /* gui.WindowStyle.POPUP */ | 1 /* gui.TooltipStyle.ALWAYSTIP */ | 2 /* gui.TooltipStyle.NOPREFIX */
|
|
33
|
+
| (balloon ? 64 /* gui.TooltipStyle.BALLOON */ : 0), 0, 0, 0, 0, hTarget, null);
|
|
34
|
+
if (!hTT)
|
|
35
|
+
return;
|
|
36
|
+
hTTRef.current = hTT;
|
|
37
|
+
gui.SetWindowPos(hTT, -1 /* gui.SetWindowPosHwnd.TOPMOST */, 0, 0, 0, 0, 2 /* gui.SetWindowPosFlag.SWP_NOMOVE */ | 1 /* gui.SetWindowPosFlag.SWP_NOSIZE */ | 16 /* gui.SetWindowPosFlag.SWP_NOACTIVATE */);
|
|
38
|
+
const ti = buildToolInfo(hTarget, text);
|
|
39
|
+
const tiPtr = ffi.bufferPtr(ti);
|
|
40
|
+
gui.SendMessage(hTT, 1074 /* gui.TtMsg.ADDTOOLW */, 0, tiPtr);
|
|
41
|
+
gui.SendMessage(hTT, 1048 /* gui.TtMsg.SETMAXTIPWIDTH */, 0, 400);
|
|
42
|
+
gui.SendMessage(hTT, 1025 /* gui.TtMsg.ACTIVATE */, 1, 0);
|
|
43
|
+
return () => {
|
|
44
|
+
if (hTT) {
|
|
45
|
+
const ti2 = buildToolInfo(hTarget, text);
|
|
46
|
+
gui.SendMessage(hTT, 1075 /* gui.TtMsg.DELTOOLW */, 0, ffi.bufferPtr(ti2));
|
|
47
|
+
gui.DestroyWindow(hTT);
|
|
48
|
+
}
|
|
49
|
+
hTTRef.current = null;
|
|
50
|
+
};
|
|
51
|
+
}, [text, balloon]);
|
|
52
|
+
const child = Children.only(children);
|
|
53
|
+
return cloneElement(child, { ref: childRef });
|
|
54
|
+
}
|
|
55
|
+
export { Tooltip };
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { forwardRef, useRef, useEffect, useState } from 'react';
|
|
3
|
+
import * as gui from 'gui';
|
|
4
|
+
import * as ffi from 'ffi';
|
|
5
|
+
function textToUtf16(s) {
|
|
6
|
+
const buf = new ArrayBuffer((s.length + 1) * 2);
|
|
7
|
+
const dv = new DataView(buf);
|
|
8
|
+
for (let i = 0; i < s.length; i++)
|
|
9
|
+
dv.setUint16(i * 2, s.charCodeAt(i), true);
|
|
10
|
+
return buf;
|
|
11
|
+
}
|
|
12
|
+
function readI32(ptr, offset) {
|
|
13
|
+
return ffi.readByte(ptr + offset) | (ffi.readByte(ptr + offset + 1) << 8) |
|
|
14
|
+
(ffi.readByte(ptr + offset + 2) << 16) | (ffi.readByte(ptr + offset + 3) << 24);
|
|
15
|
+
}
|
|
16
|
+
function bufPtr(buf) {
|
|
17
|
+
return ffi.bufferPtr(buf);
|
|
18
|
+
}
|
|
19
|
+
function setPtr(dv, offset, ptr) {
|
|
20
|
+
dv.setUint32(offset, ptr & 0xFFFFFFFF, true);
|
|
21
|
+
dv.setUint32(offset + 4, Math.floor(ptr / 0x100000000), true);
|
|
22
|
+
}
|
|
23
|
+
function buildTvItem(textPtr, cChildren) {
|
|
24
|
+
let mask = 1 /* gui.TvIfFlag.TEXT */;
|
|
25
|
+
if (cChildren > 0)
|
|
26
|
+
mask |= 64 /* gui.TvIfFlag.CHILDREN */;
|
|
27
|
+
const buf = new ArrayBuffer(56);
|
|
28
|
+
const dv = new DataView(buf);
|
|
29
|
+
dv.setUint32(0, mask, true); // mask (offset 0)
|
|
30
|
+
setPtr(dv, 8, 0); // hItem
|
|
31
|
+
dv.setUint32(16, 0, true); // state
|
|
32
|
+
dv.setUint32(20, 0, true); // stateMask
|
|
33
|
+
setPtr(dv, 24, textPtr); // pszText
|
|
34
|
+
dv.setInt32(32, 260, true); // cchTextMax
|
|
35
|
+
dv.setInt32(36, 0, true); // iImage
|
|
36
|
+
dv.setInt32(40, 0, true); // iSelectedImage
|
|
37
|
+
dv.setInt32(44, cChildren, true); // cChildren
|
|
38
|
+
setPtr(dv, 48, 0); // lParam
|
|
39
|
+
return buf;
|
|
40
|
+
}
|
|
41
|
+
function buildTvInsertStruct(hParent, hInsertAfter, itemBuf) {
|
|
42
|
+
const buf = new ArrayBuffer(72);
|
|
43
|
+
const dv = new DataView(buf);
|
|
44
|
+
setPtr(dv, 0, hParent); // hParent (offset 0)
|
|
45
|
+
setPtr(dv, 8, hInsertAfter); // hInsertAfter (offset 8)
|
|
46
|
+
const src = new DataView(itemBuf);
|
|
47
|
+
for (let i = 0; i < 56; i += 4)
|
|
48
|
+
dv.setUint32(16 + i, src.getUint32(i, true), true);
|
|
49
|
+
return buf;
|
|
50
|
+
}
|
|
51
|
+
function insertItems(hTree, nodes, parentHandle, hItemMap, keyMap) {
|
|
52
|
+
for (const node of nodes) {
|
|
53
|
+
const cChildren = node.children && node.children.length > 0 ? 1 : 0;
|
|
54
|
+
const textBuf = textToUtf16(node.label);
|
|
55
|
+
const itemBuf = buildTvItem(bufPtr(textBuf), cChildren);
|
|
56
|
+
const tvins = buildTvInsertStruct(parentHandle, -65536 /* gui.TvInsertAfter.ROOT */, itemBuf);
|
|
57
|
+
const hItem = gui.SendMessage(hTree, 4402 /* gui.TvMsg.INSERTITEMW */, 0, bufPtr(tvins));
|
|
58
|
+
hItemMap.set(hItem, node);
|
|
59
|
+
if (node.key)
|
|
60
|
+
keyMap.set(node.key, hItem);
|
|
61
|
+
if (node.children && node.children.length > 0) {
|
|
62
|
+
insertItems(hTree, node.children, hItem, hItemMap, keyMap);
|
|
63
|
+
gui.SendMessage(hTree, 4354 /* gui.TvMsg.EXPAND */, 2 /* gui.TvExpandCmd.EXPAND */, hItem);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function deleteAllItems(hTree) {
|
|
68
|
+
const n = gui.SendMessage(hTree, 4357 /* gui.TvMsg.GETCOUNT */, 0, 0);
|
|
69
|
+
if (n > 0)
|
|
70
|
+
gui.SendMessage(hTree, 4353 /* gui.TvMsg.DELETEITEM */, 1, -65536 /* gui.TvInsertAfter.ROOT */);
|
|
71
|
+
}
|
|
72
|
+
const TreeView = forwardRef(function TreeViewInner({ data, onSelect, defaultSelectedKey, selectedKey: controlledKey, style }, ref) {
|
|
73
|
+
const [internalKey, setInternalKey] = useState(defaultSelectedKey ?? null);
|
|
74
|
+
const isControlled = controlledKey !== undefined;
|
|
75
|
+
const selKey = isControlled ? controlledKey : internalKey;
|
|
76
|
+
const tvRef = useRef(null);
|
|
77
|
+
const hItemMapRef = useRef(new Map());
|
|
78
|
+
const keyMapRef = useRef(new Map());
|
|
79
|
+
const onChangeRef = useRef(onSelect);
|
|
80
|
+
onChangeRef.current = onSelect;
|
|
81
|
+
const tvWs = 268435456 /* gui.WindowStyle.VISIBLE */ | 8388608 /* gui.WindowStyle.BORDER */ | 2097152 /* gui.WindowStyle.VSCROLL */
|
|
82
|
+
| 65536 /* gui.WindowStyle.TABSTOP */
|
|
83
|
+
| 2 /* gui.TreeViewStyle.HASLINES */
|
|
84
|
+
| 1 /* gui.TreeViewStyle.HASBUTTONS */
|
|
85
|
+
| 4 /* gui.TreeViewStyle.LINESATROOT */
|
|
86
|
+
| 32 /* gui.TreeViewStyle.SHOWSELALWAYS */;
|
|
87
|
+
useEffect(() => {
|
|
88
|
+
const h = tvRef.current;
|
|
89
|
+
if (!h)
|
|
90
|
+
return;
|
|
91
|
+
deleteAllItems(h);
|
|
92
|
+
hItemMapRef.current.clear();
|
|
93
|
+
keyMapRef.current.clear();
|
|
94
|
+
if (data && data.length > 0)
|
|
95
|
+
insertItems(h, data, -65536 /* gui.TvInsertAfter.ROOT */, hItemMapRef.current, keyMapRef.current);
|
|
96
|
+
}, [data]);
|
|
97
|
+
useEffect(() => {
|
|
98
|
+
const h = tvRef.current;
|
|
99
|
+
if (!h)
|
|
100
|
+
return;
|
|
101
|
+
gui.SendMessage(h, 4396 /* gui.TvMsg.SETEXTENDEDSTYLE */, 0, 4 /* gui.TvExStyle.DOUBLEBUFFER */);
|
|
102
|
+
}, []);
|
|
103
|
+
useEffect(() => {
|
|
104
|
+
if (selKey == null)
|
|
105
|
+
return;
|
|
106
|
+
const h = tvRef.current;
|
|
107
|
+
if (!h)
|
|
108
|
+
return;
|
|
109
|
+
const hItem = keyMapRef.current.get(selKey);
|
|
110
|
+
if (hItem !== undefined)
|
|
111
|
+
gui.SendMessage(h, 4363 /* gui.TvMsg.SELECTITEM */, 9 /* gui.TvGnRelative.CARET */, hItem);
|
|
112
|
+
}, [selKey]);
|
|
113
|
+
return (_jsx("w", { type: "STATIC", ws: 268435456 /* gui.WindowStyle.VISIBLE */ | 33554432 /* gui.WindowStyle.CLIPCHILDREN */, style: { ...style, flexDirection: 'column', alignItems: 'stretch' }, ref: ref, onEvent: (e) => {
|
|
114
|
+
if (e.msg === 78 /* gui.WmMsg.NOTIFY */) {
|
|
115
|
+
const code = readI32(e.lParam, 16);
|
|
116
|
+
if (code === -451 /* gui.TvNotifyCode.SELCHANGEDW */) {
|
|
117
|
+
const h = tvRef.current;
|
|
118
|
+
if (!h)
|
|
119
|
+
return;
|
|
120
|
+
const hCaret = gui.SendMessage(h, 4362 /* gui.TvMsg.GETNEXTITEM */, 9 /* gui.TvGnRelative.CARET */, 0);
|
|
121
|
+
const node = hItemMapRef.current.get(hCaret) ?? null;
|
|
122
|
+
const key = node?.key ?? null;
|
|
123
|
+
if (!isControlled)
|
|
124
|
+
setInternalKey(key);
|
|
125
|
+
onChangeRef.current?.(node);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}, children: _jsx("w", { type: "SysTreeView32", ws: tvWs, style: { flexGrow: 1 }, ref: (h) => { tvRef.current = h; } }) }));
|
|
129
|
+
});
|
|
130
|
+
export { TreeView };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export { Button } from './Button.js';
|
|
2
|
+
export { CheckBox } from './CheckBox.js';
|
|
3
|
+
export { ComboBox } from './ComboBox.js';
|
|
4
|
+
export { DateTimePicker } from './DateTimePicker.js';
|
|
5
|
+
export { Input } from './Input.js';
|
|
6
|
+
export { Link } from './Link.js';
|
|
7
|
+
export { ListBox } from './ListBox.js';
|
|
8
|
+
export { ListView, makeColorBlock } from './ListView.js';
|
|
9
|
+
export { ProgressBar } from './ProgressBar.js';
|
|
10
|
+
export { RadioButton } from './RadioButton.js';
|
|
11
|
+
export { ScrollView } from './ScrollView.js';
|
|
12
|
+
export { Slider } from './Slider.js';
|
|
13
|
+
export { Tab } from './Tab.js';
|
|
14
|
+
export { Tooltip } from './Tooltip.js';
|
|
15
|
+
export { TreeView } from './TreeView.js';
|
|
16
|
+
export { PathPicker } from './PathPicker.js';
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export { Button, type ButtonProps } from './Button.js'
|
|
2
|
+
export { CheckBox, type CheckBoxProps } from './CheckBox.js'
|
|
3
|
+
export { ComboBox, type ComboBoxProps } from './ComboBox.js'
|
|
4
|
+
export { DateTimePicker, type DateTimePickerProps } from './DateTimePicker.js'
|
|
5
|
+
export { Input, type InputProps } from './Input.js'
|
|
6
|
+
export { Link, type LinkProps } from './Link.js'
|
|
7
|
+
export { ListBox, type ListBoxProps } from './ListBox.js'
|
|
8
|
+
export { ListView, makeColorBlock, type ListViewProps, type Column, type CellStyle, type Align } from './ListView.js'
|
|
9
|
+
export { ProgressBar, type ProgressBarProps } from './ProgressBar.js'
|
|
10
|
+
export { RadioButton, type RadioButtonProps } from './RadioButton.js'
|
|
11
|
+
export { ScrollView, type ScrollViewProps } from './ScrollView.js'
|
|
12
|
+
export { Slider, type SliderProps } from './Slider.js'
|
|
13
|
+
export { Tab, type TabProps } from './Tab.js'
|
|
14
|
+
export { Tooltip, type TooltipProps } from './Tooltip.js'
|
|
15
|
+
export { TreeView, type TreeViewProps, type TreeNode } from './TreeView.js'
|
|
16
|
+
export { PathPicker, type PathPickerProps } from './PathPicker.js'
|
|
17
|
+
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import * as gui from 'gui';
|
|
2
|
+
import reconciler, { scaleFactor, instancesByHwnd, forceFlexLayout } from './reconciler.js';
|
|
3
|
+
const noop = () => { };
|
|
4
|
+
let defaultClassRegistered = false;
|
|
5
|
+
const DEFAULT_CLASS = '_QuickWinDefault';
|
|
6
|
+
const rootMap = new Map();
|
|
7
|
+
function ensureDefaultClass() {
|
|
8
|
+
if (defaultClassRegistered)
|
|
9
|
+
return DEFAULT_CLASS;
|
|
10
|
+
gui.RegisterClass(DEFAULT_CLASS, (hwnd, msg, wParam, lParam) => {
|
|
11
|
+
const entry = rootMap.get(hwnd);
|
|
12
|
+
if (msg === 2 /* gui.WmMsg.DESTROY */ && entry) {
|
|
13
|
+
rootMap.delete(hwnd);
|
|
14
|
+
reconciler.updateContainer(null, entry.root, null, () => {
|
|
15
|
+
instancesByHwnd.delete(hwnd);
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
const result = entry?.onEvent?.({ hwnd, msg, wParam, lParam });
|
|
19
|
+
if (typeof result === 'number')
|
|
20
|
+
return result;
|
|
21
|
+
if (msg === 5 /* gui.WmMsg.SIZE */)
|
|
22
|
+
forceFlexLayout(hwnd);
|
|
23
|
+
return gui.DefWindowProc(hwnd, msg, wParam, lParam);
|
|
24
|
+
});
|
|
25
|
+
defaultClassRegistered = true;
|
|
26
|
+
return DEFAULT_CLASS;
|
|
27
|
+
}
|
|
28
|
+
export function createRoot(container) {
|
|
29
|
+
let hwnd;
|
|
30
|
+
if (typeof container === 'object') {
|
|
31
|
+
const cfg = container;
|
|
32
|
+
const className = ensureDefaultClass();
|
|
33
|
+
const ws = (cfg.ws ?? 0) | 13565952 /* gui.WindowStyle.OVERLAPPEDWINDOW */;
|
|
34
|
+
const w = (cfg.width ?? 800) * scaleFactor;
|
|
35
|
+
const h = (cfg.height ?? 600) * scaleFactor;
|
|
36
|
+
const [sw, sh] = gui.GetScreenSize();
|
|
37
|
+
const x = cfg.x === undefined ? ((sw - w) / 2) | 0
|
|
38
|
+
: cfg.x === -2147483648 /* gui.CreatePos.USEDEFAULT */ ? -2147483648 /* gui.CreatePos.USEDEFAULT */ : cfg.x * scaleFactor;
|
|
39
|
+
const y = cfg.y === undefined ? ((sh - h) / 2) | 0
|
|
40
|
+
: cfg.y === -2147483648 /* gui.CreatePos.USEDEFAULT */ ? -2147483648 /* gui.CreatePos.USEDEFAULT */ : cfg.y * scaleFactor;
|
|
41
|
+
const hwin = gui.CreateWindow(className, cfg.text || '', ws, x, y, w, h, null, null);
|
|
42
|
+
if (!hwin)
|
|
43
|
+
throw new Error('CreateWindow failed');
|
|
44
|
+
hwnd = hwin;
|
|
45
|
+
if (!cfg.noShowWindow)
|
|
46
|
+
gui.ShowWindow(hwnd);
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
hwnd = container;
|
|
50
|
+
}
|
|
51
|
+
const root = reconciler.createContainer(hwnd, 0, null, false, null, '', noop, noop, noop, noop);
|
|
52
|
+
const rootInst = { hwnd, type: '_root', props: { type: '_root', style: { flexDirection: 'column', alignItems: 'stretch' } }, children: [] };
|
|
53
|
+
instancesByHwnd.set(hwnd, rootInst);
|
|
54
|
+
if (typeof container === 'object')
|
|
55
|
+
rootMap.set(hwnd, { root, onEvent: container.onEvent });
|
|
56
|
+
return {
|
|
57
|
+
hwnd,
|
|
58
|
+
render(element) {
|
|
59
|
+
reconciler.updateContainer(element, root, null, noop);
|
|
60
|
+
},
|
|
61
|
+
unmount() {
|
|
62
|
+
reconciler.updateContainer(null, root, null, noop);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
export * from './components/index.js';
|
|
67
|
+
export function render(element, containerOrConfig, _callback) {
|
|
68
|
+
if (containerOrConfig == null) {
|
|
69
|
+
throw new Error('render() requires a window handle or RootWindowConfig as second argument');
|
|
70
|
+
}
|
|
71
|
+
const root = createRoot(containerOrConfig);
|
|
72
|
+
root.render(element);
|
|
73
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import * as gui from 'gui'
|
|
2
|
+
import React from 'react'
|
|
3
|
+
import reconciler, { scaleFactor, instancesByHwnd, forceFlexLayout, type Instance } from './reconciler.js'
|
|
4
|
+
|
|
5
|
+
const noop = () => { }
|
|
6
|
+
|
|
7
|
+
export interface RootWindowConfig {
|
|
8
|
+
text?: string
|
|
9
|
+
ws?: number
|
|
10
|
+
x?: number
|
|
11
|
+
y?: number
|
|
12
|
+
width?: number
|
|
13
|
+
height?: number
|
|
14
|
+
noShowWindow?: boolean
|
|
15
|
+
onEvent?: (e: { hwnd: gui.HWND; msg: number; wParam: number; lParam: number }) => number | void
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
let defaultClassRegistered = false
|
|
19
|
+
const DEFAULT_CLASS = '_QuickWinDefault'
|
|
20
|
+
const rootMap = new Map<gui.HWND, { root: ReturnType<typeof reconciler.createContainer>, onEvent?: RootWindowConfig['onEvent'] }>()
|
|
21
|
+
|
|
22
|
+
function ensureDefaultClass() {
|
|
23
|
+
if (defaultClassRegistered) return DEFAULT_CLASS
|
|
24
|
+
gui.RegisterClass(DEFAULT_CLASS, (hwnd, msg, wParam, lParam) => {
|
|
25
|
+
const entry = rootMap.get(hwnd)
|
|
26
|
+
if (msg === gui.WmMsg.DESTROY && entry) {
|
|
27
|
+
rootMap.delete(hwnd)
|
|
28
|
+
reconciler.updateContainer(null, entry.root, null, () => {
|
|
29
|
+
instancesByHwnd.delete(hwnd)
|
|
30
|
+
})
|
|
31
|
+
}
|
|
32
|
+
const result = entry?.onEvent?.({ hwnd, msg, wParam, lParam })
|
|
33
|
+
if (typeof result === 'number') return result
|
|
34
|
+
if (msg === gui.WmMsg.SIZE) forceFlexLayout(hwnd)
|
|
35
|
+
return gui.DefWindowProc(hwnd, msg, wParam, lParam)
|
|
36
|
+
})
|
|
37
|
+
defaultClassRegistered = true
|
|
38
|
+
return DEFAULT_CLASS
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function createRoot(container: gui.HWND | RootWindowConfig) {
|
|
42
|
+
let hwnd: gui.HWND
|
|
43
|
+
if (typeof container === 'object') {
|
|
44
|
+
const cfg = container
|
|
45
|
+
const className = ensureDefaultClass()
|
|
46
|
+
const ws = (cfg.ws ?? 0) | gui.WindowStyle.OVERLAPPEDWINDOW
|
|
47
|
+
const w = (cfg.width ?? 800) * scaleFactor
|
|
48
|
+
const h = (cfg.height ?? 600) * scaleFactor
|
|
49
|
+
const [sw, sh] = gui.GetScreenSize()
|
|
50
|
+
const x = cfg.x === undefined ? ((sw - w) / 2) | 0
|
|
51
|
+
: cfg.x === gui.CreatePos.USEDEFAULT ? gui.CreatePos.USEDEFAULT
|
|
52
|
+
: cfg.x * scaleFactor
|
|
53
|
+
const y = cfg.y === undefined ? ((sh - h) / 2) | 0
|
|
54
|
+
: cfg.y === gui.CreatePos.USEDEFAULT ? gui.CreatePos.USEDEFAULT
|
|
55
|
+
: cfg.y * scaleFactor
|
|
56
|
+
const hwin = gui.CreateWindow(className, cfg.text || '', ws, x, y, w, h, null, null)
|
|
57
|
+
if (!hwin) throw new Error('CreateWindow failed')
|
|
58
|
+
hwnd = hwin
|
|
59
|
+
if (!cfg.noShowWindow) gui.ShowWindow(hwnd)
|
|
60
|
+
} else {
|
|
61
|
+
hwnd = container
|
|
62
|
+
}
|
|
63
|
+
const root = reconciler.createContainer(
|
|
64
|
+
hwnd, 0, null, false, null, '',
|
|
65
|
+
noop, noop, noop, noop,
|
|
66
|
+
)
|
|
67
|
+
const rootInst: Instance = { hwnd, type: '_root', props: { type: '_root', style: { flexDirection: 'column', alignItems: 'stretch' } }, children: [] }
|
|
68
|
+
instancesByHwnd.set(hwnd, rootInst)
|
|
69
|
+
if (typeof container === 'object')
|
|
70
|
+
rootMap.set(hwnd, { root, onEvent: container.onEvent })
|
|
71
|
+
return {
|
|
72
|
+
hwnd,
|
|
73
|
+
render(element: React.ReactNode) {
|
|
74
|
+
reconciler.updateContainer(element, root, null, noop)
|
|
75
|
+
},
|
|
76
|
+
unmount() {
|
|
77
|
+
reconciler.updateContainer(null, root, null, noop)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export * from './components/index.js'
|
|
83
|
+
|
|
84
|
+
export function render(element: React.ReactNode, containerOrConfig?: gui.HWND | RootWindowConfig | null, _callback?: () => void) {
|
|
85
|
+
if (containerOrConfig == null) {
|
|
86
|
+
throw new Error('render() requires a window handle or RootWindowConfig as second argument')
|
|
87
|
+
}
|
|
88
|
+
const root = createRoot(containerOrConfig)
|
|
89
|
+
root.render(element)
|
|
90
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { HWND } from 'gui'
|
|
2
|
+
|
|
3
|
+
interface WStyle {
|
|
4
|
+
width?: number | 'auto'; height?: number | 'auto'
|
|
5
|
+
flexDirection?: 'row' | 'column'
|
|
6
|
+
justifyContent?: 'flex-start' | 'flex-end' | 'center' | 'space-between' | 'space-around' | 'space-evenly'
|
|
7
|
+
alignItems?: 'flex-start' | 'flex-end' | 'center' | 'stretch'
|
|
8
|
+
alignSelf?: 'auto' | 'flex-start' | 'flex-end' | 'center' | 'stretch'
|
|
9
|
+
gap?: number
|
|
10
|
+
padding?: number
|
|
11
|
+
paddingTop?: number
|
|
12
|
+
paddingRight?: number
|
|
13
|
+
paddingBottom?: number
|
|
14
|
+
paddingLeft?: number
|
|
15
|
+
flexGrow?: number
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface WEvent {
|
|
19
|
+
hwnd: HWND; msg: number; wParam: number; lParam: number
|
|
20
|
+
}
|
|
21
|
+
interface WOnEventObj {
|
|
22
|
+
fn: (e: WEvent & {callOldWndProc: () => number}) => number
|
|
23
|
+
}
|
|
24
|
+
interface WIntrinsicProps {
|
|
25
|
+
key?: string | number; type: string; text?: string; ws?: number
|
|
26
|
+
disabled?: boolean; visible?: boolean; hidden?: boolean; style?: WStyle
|
|
27
|
+
onEvent?: ((e: WEvent) => number | void) | WOnEventObj
|
|
28
|
+
children?: React.ReactNode
|
|
29
|
+
ref?: React.Ref<HWND>
|
|
30
|
+
}
|
|
31
|
+
declare module 'react' {
|
|
32
|
+
namespace JSX { interface IntrinsicElements { w: WIntrinsicProps } }
|
|
33
|
+
}
|
|
34
|
+
export type { WEvent, WIntrinsicProps, WStyle }
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
const DEFAULT_W = 100;
|
|
2
|
+
const DEFAULT_H = 30;
|
|
3
|
+
export function calculateFlexLayout(flex, parentW, parentH, children) {
|
|
4
|
+
const dir = flex.flexDirection || 'column';
|
|
5
|
+
const gap = flex.gap ?? 0;
|
|
6
|
+
const justify = flex.justifyContent || 'flex-start';
|
|
7
|
+
const align = flex.alignItems || 'stretch';
|
|
8
|
+
const isRow = dir === 'row';
|
|
9
|
+
const n = children.length;
|
|
10
|
+
const mainSize = isRow ? 'w' : 'h';
|
|
11
|
+
const crossSize = isRow ? 'h' : 'w';
|
|
12
|
+
const mainPos = isRow ? 'x' : 'y';
|
|
13
|
+
const crossPos = isRow ? 'y' : 'x';
|
|
14
|
+
const mainDim = isRow ? 'width' : 'height';
|
|
15
|
+
const crossDim = isRow ? 'height' : 'width';
|
|
16
|
+
const pt = flex.paddingTop ?? flex.padding ?? 0;
|
|
17
|
+
const pr = flex.paddingRight ?? flex.padding ?? 0;
|
|
18
|
+
const pb = flex.paddingBottom ?? flex.padding ?? 0;
|
|
19
|
+
const pl = flex.paddingLeft ?? flex.padding ?? 0;
|
|
20
|
+
const mainPadding = isRow ? pl + pr : pt + pb;
|
|
21
|
+
const crossPadding = isRow ? pt + pb : pl + pr;
|
|
22
|
+
const parentMain = (isRow ? parentW : parentH) - mainPadding;
|
|
23
|
+
const parentCross = (isRow ? parentH : parentW) - crossPadding;
|
|
24
|
+
const sizes = children.map(c => ({
|
|
25
|
+
w: typeof c.style.width === 'number' ? c.style.width : DEFAULT_W,
|
|
26
|
+
h: typeof c.style.height === 'number' ? c.style.height : DEFAULT_H,
|
|
27
|
+
}));
|
|
28
|
+
const flexGrows = children.map(c => c.style.flexGrow ?? 0);
|
|
29
|
+
const totalGrow = flexGrows.reduce((s, g) => s + g, 0);
|
|
30
|
+
const baseMain = sizes.reduce((s, c) => s + c[mainSize], 0);
|
|
31
|
+
let freeGrow = parentMain - baseMain - Math.max(0, n - 1) * gap;
|
|
32
|
+
if (totalGrow > 0 && freeGrow > 0) {
|
|
33
|
+
for (let i = 0; i < n; i++) {
|
|
34
|
+
sizes[i][mainSize] += freeGrow * flexGrows[i] / totalGrow;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
const totalMain = sizes.reduce((s, c) => s + c[mainSize], 0);
|
|
38
|
+
const free = parentMain - totalMain - Math.max(0, n - 1) * gap;
|
|
39
|
+
let offset = 0, extraGap = 0;
|
|
40
|
+
if (justify === 'flex-end')
|
|
41
|
+
offset = free;
|
|
42
|
+
else if (justify === 'center')
|
|
43
|
+
offset = Math.max(0, free / 2);
|
|
44
|
+
else if (justify === 'space-between' && n > 1)
|
|
45
|
+
extraGap = free / (n - 1);
|
|
46
|
+
else if (justify === 'space-around') {
|
|
47
|
+
extraGap = free / n;
|
|
48
|
+
offset = extraGap / 2;
|
|
49
|
+
}
|
|
50
|
+
else if (justify === 'space-evenly') {
|
|
51
|
+
extraGap = free / (n + 1);
|
|
52
|
+
offset = extraGap;
|
|
53
|
+
}
|
|
54
|
+
let cursor = offset;
|
|
55
|
+
return sizes.map((sz, i) => {
|
|
56
|
+
const childCrossBase = sz[crossSize];
|
|
57
|
+
const childAlign = children[i].style.alignSelf ?? 'auto';
|
|
58
|
+
const effectiveAlign = childAlign === 'auto' ? align : childAlign;
|
|
59
|
+
let childMain = sz[mainSize];
|
|
60
|
+
let childCross = childCrossBase;
|
|
61
|
+
const res = { x: 0, y: 0, width: 0, height: 0 };
|
|
62
|
+
res[mainPos] = cursor;
|
|
63
|
+
res[crossPos] = 0;
|
|
64
|
+
res[mainDim] = childMain;
|
|
65
|
+
res[crossDim] = childCross;
|
|
66
|
+
if (effectiveAlign === 'stretch') {
|
|
67
|
+
const hasExplicitCross = typeof children[i].style[crossDim] === 'number';
|
|
68
|
+
if (!hasExplicitCross) {
|
|
69
|
+
res[crossDim] = parentCross;
|
|
70
|
+
res[crossPos] = 0;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
else if (effectiveAlign === 'flex-end') {
|
|
74
|
+
res[crossPos] = parentCross - childCross;
|
|
75
|
+
}
|
|
76
|
+
else if (effectiveAlign === 'center') {
|
|
77
|
+
res[crossPos] = Math.max(0, (parentCross - childCross) / 2);
|
|
78
|
+
}
|
|
79
|
+
cursor += childMain + gap + extraGap;
|
|
80
|
+
return res;
|
|
81
|
+
});
|
|
82
|
+
}
|