cj-tauri 2.0.0 → 2.1.1
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 +206 -139
- package/bin/cj-tauri.js +287 -287
- package/cjpm.toml +63 -52
- package/cli/cjpm.toml +22 -22
- package/cli/src/doctor.cj +308 -306
- package/cli/src/main.cj +129 -129
- package/cli/src/manifest.cj +643 -558
- package/cli/src/manifest_test.cj +87 -0
- package/cli/src/project.cj +770 -770
- package/cli/src/resolve.cj +348 -348
- package/cli/src/scaffold.cj +369 -369
- package/cli/src/sync.cj +638 -526
- package/cli/src/util.cj +168 -168
- package/cli/templates/app/README.md +51 -48
- package/cli/templates/app/assets/capabilities/default.json +7 -7
- package/cli/templates/app/assets/ui/index.html +120 -118
- package/cli/templates/app/cj-tauri.toml +32 -22
- package/cli/templates/app/cjpm.toml +34 -34
- package/cli/templates/app/src/main.cj +101 -90
- package/cli/templates/app-react/README.md +56 -56
- package/cli/templates/app-react/assets/capabilities/default.json +7 -7
- package/cli/templates/app-react/assets/ui/index.html +25 -25
- package/cli/templates/app-react/assets/ui/package.json +19 -19
- package/cli/templates/app-react/assets/ui/src/App.css +67 -67
- package/cli/templates/app-react/assets/ui/src/App.jsx +124 -124
- package/cli/templates/app-react/assets/ui/src/main.jsx +6 -6
- package/cli/templates/app-react/assets/ui/vite.config.js +24 -24
- package/cli/templates/app-react/cj-tauri.toml +33 -23
- package/cli/templates/app-react/cjpm.toml +34 -34
- package/cli/templates/app-react/src/main.cj +91 -91
- package/cli/templates/app-vue/README.md +56 -56
- package/cli/templates/app-vue/assets/capabilities/default.json +7 -7
- package/cli/templates/app-vue/assets/ui/index.html +25 -25
- package/cli/templates/app-vue/assets/ui/package.json +18 -18
- package/cli/templates/app-vue/assets/ui/src/App.vue +176 -176
- package/cli/templates/app-vue/assets/ui/src/main.js +4 -4
- package/cli/templates/app-vue/assets/ui/vite.config.js +24 -24
- package/cli/templates/app-vue/cj-tauri.toml +33 -23
- package/cli/templates/app-vue/cjpm.toml +34 -34
- package/cli/templates/app-vue/src/main.cj +91 -91
- package/cli/templates/ohos-skeleton/AppScope/app.json5 +13 -13
- package/cli/templates/ohos-skeleton/AppScope/resources/base/element/string.json +8 -8
- package/cli/templates/ohos-skeleton/AppScope/resources/base/media/layered_image.json +6 -6
- package/cli/templates/ohos-skeleton/README.md +84 -79
- package/cli/templates/ohos-skeleton/build-hap.sh +15 -0
- package/cli/templates/ohos-skeleton/build-profile.json5 +46 -46
- package/cli/templates/ohos-skeleton/entry/build-profile.json5 +26 -26
- package/cli/templates/ohos-skeleton/entry/cjpm.toml +51 -43
- package/cli/templates/ohos-skeleton/entry/hvigorfile.ts +6 -6
- package/cli/templates/ohos-skeleton/entry/oh-package.json5 +9 -9
- package/cli/templates/ohos-skeleton/entry/src/main/cangjie/ability_stage.cj +17 -17
- package/cli/templates/ohos-skeleton/entry/src/main/cangjie/app_host.cj +94 -94
- package/cli/templates/ohos-skeleton/entry/src/main/cangjie/dialog_host.cj +217 -0
- package/cli/templates/ohos-skeleton/entry/src/main/cangjie/index.cj +527 -167
- package/cli/templates/ohos-skeleton/entry/src/main/cangjie/main_ability.cj +139 -139
- package/cli/templates/ohos-skeleton/entry/src/main/module.json5 +57 -49
- package/cli/templates/ohos-skeleton/entry/src/main/resources/base/element/color.json +8 -8
- package/cli/templates/ohos-skeleton/entry/src/main/resources/base/element/string.json +16 -16
- package/cli/templates/ohos-skeleton/entry/src/main/resources/base/media/layered_image.json +6 -6
- package/cli/templates/ohos-skeleton/entry/src/main/resources/base/profile/main_pages.json +2 -2
- package/cli/templates/ohos-skeleton/entry/src/main/resources/rawfile/capabilities.json +6 -6
- package/cli/templates/ohos-skeleton/entry/src/main/resources/rawfile/index.html +92 -92
- package/cli/templates/ohos-skeleton/hvigor/hvigor-config.json5 +5 -5
- package/cli/templates/ohos-skeleton/hvigorfile.ts +6 -6
- package/cli/templates/ohos-skeleton/oh-package.json5 +10 -10
- package/native/build_win.bat +62 -62
- package/native/core/bridge_core.c +1681 -1626
- package/native/core/bridge_core.h +675 -618
- package/native/core/bridge_js.h +108 -108
- package/native/platforms/harmonyos/README.md +115 -6
- package/native/platforms/linux/bridge_linux.c +2787 -2485
- package/native/platforms/windows/bridge_win.c +3745 -3248
- package/native/tests/gtk_threading_probe.c +143 -143
- package/native/tests/test_bridge_core.c +1339 -1228
- package/package.json +53 -53
- package/src/api_system.cj +67 -67
- package/src/app.cj +335 -969
- package/src/app_events.cj +166 -0
- package/src/app_host_ops.cj +287 -0
- package/src/app_run.cj +283 -0
- package/src/app_spec.cj +76 -76
- package/src/arkweb_menu.cj +216 -0
- package/src/arkweb_script.cj +142 -142
- package/src/capability.cj +409 -409
- package/src/clipboard_bridge.cj +178 -0
- package/src/dialog_spec.cj +79 -0
- package/src/dragdrop.cj +26 -26
- package/src/embedded_assets.cj +98 -98
- package/src/host.cj +752 -607
- package/src/host_arkweb.cj +924 -778
- package/src/host_webkit.cj +663 -570
- package/src/host_webview2.cj +663 -570
- package/src/hotkey.cj +66 -66
- package/src/ipc_hub.cj +401 -337
- package/src/ipc_message.cj +286 -286
- package/src/launch.cj +62 -62
- package/src/logging.cj +127 -127
- package/src/menu.cj +197 -197
- package/src/platform/platform_app_context.cj +77 -77
- package/src/platform/platform_assets.cj +33 -33
- package/src/platform/platform_discover.cj +176 -176
- package/src/platform/platform_env.cj +29 -29
- package/src/platform/platform_feature.cj +31 -31
- package/src/platform/platform_page_source.cj +87 -87
- package/src/platform/platform_target_info.cj +32 -32
- package/src/platform_host_provider.cj +87 -87
- package/src/plugin.cj +333 -333
- package/src/plugins/clipboard/plugin_clipboard.cj +152 -152
- package/src/plugins/dialog/plugin_dialog.cj +209 -209
- package/src/plugins/fs/plugin_fs.cj +105 -1
- package/src/plugins/http/http_backend.cj +131 -0
- package/src/plugins/http/http_desktop.cj +195 -0
- package/src/plugins/http/http_ohos.cj +204 -0
- package/src/plugins/http/plugin_http.cj +310 -459
- package/src/plugins/log/plugin_log.cj +105 -105
- package/src/plugins/menu/plugin_menu.cj +158 -158
- package/src/plugins/notification/plugin_notification.cj +154 -154
- package/src/plugins/serial/plugin_serial.cj +458 -454
- package/src/plugins/serial/serial_bridge.cj +194 -194
- package/src/plugins/serial/serial_manager_ohos.cj +92 -0
- package/src/plugins/serial/serial_ohos.cj +529 -0
- package/src/plugins/shell/plugin_shell.cj +258 -258
- package/src/plugins/sqlite/plugin_sqlite.cj +705 -710
- package/src/plugins/sqlite/sqlite_ffi.cj +206 -211
- package/src/plugins/ws/plugin_ws.cj +488 -470
- package/src/process_hidden.cj +174 -174
- package/src/scope.cj +391 -391
- package/src/shell_event.cj +69 -69
- package/src/tests/app_spec_test.cj +130 -127
- package/src/tests/capability_test.cj +243 -243
- package/src/tests/dialog_spec_test.cj +73 -0
- package/src/tests/fake_host.cj +201 -201
- package/src/tests/ipc_hub_test.cj +563 -487
- package/src/tests/logging_test.cj +62 -62
- package/src/tests/platform_contract_test.cj +264 -251
- package/src/tests/plugin_clipboard_test.cj +222 -222
- package/src/tests/plugin_dialog_test.cj +237 -237
- package/src/tests/plugin_fs_test.cj +207 -0
- package/src/tests/plugin_http_test.cj +124 -124
- package/src/tests/plugin_notification_test.cj +205 -205
- package/src/tests/plugin_serial_test.cj +48 -0
- package/src/tests/plugin_test.cj +605 -585
- package/src/tests/plugin_ws_test.cj +40 -40
- package/src/tests/process_hidden_test.cj +129 -129
- package/src/tests/scope_test.cj +350 -350
- package/src/tests/system_host_test.cj +99 -81
- package/src/tests/urlutil_test.cj +53 -53
- package/src/tests/version_test.cj +75 -75
- package/src/tests/window_registry_test.cj +123 -123
- package/src/tray.cj +90 -90
- package/src/urlutil.cj +159 -159
- package/src/version.cj +44 -44
- package/src/window.cj +51 -51
- package/src/window_registry.cj +116 -116
- package/prebuilt/win32-x64/cj-tauri.exe +0 -0
- package/src/plugins/serial/serial_ohos_stub.cj +0 -61
- package/src/plugins/sqlite/sqlite_ohos_stub.cj +0 -91
package/src/menu.cj
CHANGED
|
@@ -1,197 +1,197 @@
|
|
|
1
|
-
package cjTauri
|
|
2
|
-
|
|
3
|
-
import std.collection.*
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* 窗口菜单栏的**纯仓颉模型**(RFC-002 §5.1):只描述意图,不含任何平台概念。
|
|
7
|
-
*
|
|
8
|
-
* 分层:模型(本文件)→ `WebViewHost.setMenu()`(src/host.cj)→ C 桥 `cj_bridge_set_menu` →
|
|
9
|
-
* 平台翻译层(native/bridge_win.c 的 CreateMenu 族 / bridge_linux.c 的 GtkMenuBar)。
|
|
10
|
-
* 菜单文本来自**应用作者**(不是页面可控的不可信输入),所以这里不做转义——
|
|
11
|
-
* 非法字段让那一项出局并出声,而不是写一套转义/反转义。
|
|
12
|
-
*/
|
|
13
|
-
public enum MenuItemKind {
|
|
14
|
-
| Normal // 普通项:点击回调自己的 id
|
|
15
|
-
| Checkbox // 可勾选项:回调里带 checked
|
|
16
|
-
| Separator // 分隔线:无 id、不可点
|
|
17
|
-
| Submenu // 子菜单:自身不可点,只承载 children
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
public class MenuItem {
|
|
21
|
-
public let id: String
|
|
22
|
-
public let label: String
|
|
23
|
-
public let kind: MenuItemKind
|
|
24
|
-
/** 快捷键**提示**文本(如 "Ctrl+S")。v1 只显示,不抢页面按键 */
|
|
25
|
-
public let accelerator: String
|
|
26
|
-
public var enabled: Bool
|
|
27
|
-
public var checked: Bool
|
|
28
|
-
/** 仅 Submenu 用;其它 kind 恒为空表 */
|
|
29
|
-
public let children: ArrayList<MenuItem>
|
|
30
|
-
|
|
31
|
-
public init(id: String, label: String, kind: MenuItemKind, accelerator: String,
|
|
32
|
-
enabled: Bool, checked: Bool, children: ArrayList<MenuItem>) {
|
|
33
|
-
this.id = id
|
|
34
|
-
this.label = label
|
|
35
|
-
this.kind = kind
|
|
36
|
-
this.accelerator = accelerator
|
|
37
|
-
this.enabled = enabled
|
|
38
|
-
this.checked = checked
|
|
39
|
-
this.children = children
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/** 普通菜单项(最常用) */
|
|
43
|
-
public static func normal(id: String, label: String): MenuItem {
|
|
44
|
-
return MenuItem(id, label, MenuItemKind.Normal, "", true, false, ArrayList<MenuItem>())
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/** 带快捷键提示的普通项 */
|
|
48
|
-
public static func normalWithAccel(id: String, label: String, accelerator: String): MenuItem {
|
|
49
|
-
return MenuItem(id, label, MenuItemKind.Normal, accelerator, true, false, ArrayList<MenuItem>())
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/** 勾选项(初始 checked) */
|
|
53
|
-
public static func checkbox(id: String, label: String, checked: Bool): MenuItem {
|
|
54
|
-
return MenuItem(id, label, MenuItemKind.Checkbox, "", true, checked, ArrayList<MenuItem>())
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/** 子菜单:id 留空(自身不回调),动作交给 children */
|
|
58
|
-
public static func submenu(label: String, children: ArrayList<MenuItem>): MenuItem {
|
|
59
|
-
return MenuItem("", label, MenuItemKind.Submenu, "", true, false, children)
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/** 分隔线 */
|
|
63
|
-
public static func separator(): MenuItem {
|
|
64
|
-
return MenuItem("", "", MenuItemKind.Separator, "", true, false, ArrayList<MenuItem>())
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* 菜单模型:`items` 是菜单栏的顶层,子菜单靠 `MenuItem.children` 递归。
|
|
70
|
-
*
|
|
71
|
-
* **不可变**(items 只读):运行期改状态走 `WebViewHost.setMenuItemState(id, …)`,不在这里改——
|
|
72
|
-
* 状态的真身在平台对象上(Windows 的 HMENU、GTK 的 GtkCheckMenuItem),只改模型平台不会自己变。
|
|
73
|
-
*/
|
|
74
|
-
public class MenuModel {
|
|
75
|
-
public let items: ArrayList<MenuItem>
|
|
76
|
-
|
|
77
|
-
public init(items: ArrayList<MenuItem>) {
|
|
78
|
-
this.items = items
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* 按 id 找菜单项(本层优先,再下钻子菜单);找不到返回 None
|
|
83
|
-
*
|
|
84
|
-
* 为什么要它:运行期改状态按 id 定位(`setMenuItemState`),而**应用层的模型状态**必须
|
|
85
|
-
* 能反查出「这个 id 现在是什么状态」——平台侧一次写两位(enabled + checked,见
|
|
86
|
-
* `WebViewHost.setMenuItemState`),拿不到另一维就会把用户刚点出来的勾选抹掉。
|
|
87
|
-
*/
|
|
88
|
-
public func findById(id: String): Option<MenuItem> {
|
|
89
|
-
return MenuModel.lookup(this.items, id)
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
private static func lookup(items: ArrayList<MenuItem>, id: String): Option<MenuItem> {
|
|
93
|
-
for (item in items) {
|
|
94
|
-
if (item.id == id) {
|
|
95
|
-
return Some(item)
|
|
96
|
-
}
|
|
97
|
-
match (MenuModel.lookup(item.children, id)) {
|
|
98
|
-
case Some(found) => return Some(found)
|
|
99
|
-
case None => ()
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
return None
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* 线路格式(RFC-002 §5.4):一行一项,字段用制表符分隔,**行首制表符数 = 层级**。
|
|
107
|
-
*
|
|
108
|
-
* <tabs><kind>\t<id>\t<label>\t<flags>\t<accel>\n
|
|
109
|
-
*
|
|
110
|
-
* kind:`n` 普通 / `c` 勾选 / `s` 分隔线 / `m` 子菜单;
|
|
111
|
-
* flags:十进制,bit0 = 可用、bit1 = 勾选(`3` = 又可用又勾选);
|
|
112
|
-
* 分隔线与子菜单也走满 5 个字段(id / label / accel 为空),C 侧因此不必为它们开特例。
|
|
113
|
-
*
|
|
114
|
-
* 为什么 kind 放最前:C 侧先分派再取值,日志里一眼能看出这行是什么。
|
|
115
|
-
* 为什么不是 JSON:C 侧(bridge_core.c)是纯 C、不引第三方,行式文本解析几十行就够,
|
|
116
|
-
* 且坏了能直接 `fprintf` 到 stderr(诊断走 stderr 是项目铁律)。JSON 留在仓颉侧给 CLI / 清单用。
|
|
117
|
-
* 为什么不做转义:字段里出现制表符 / 换行会破坏「一行一项」的结构——v1 的口径是
|
|
118
|
-
* **丢弃该项 + stderr 逐项提示**(见 emit),而不是引入转义/反转义这一对必须同时正确的函数。
|
|
119
|
-
*/
|
|
120
|
-
public func toWire(): String {
|
|
121
|
-
return MenuModel.emit(this.items, 0)
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/** 包内共享(TrayModel 的托盘菜单复用同一遍发射器,别再抄一份格式) */
|
|
125
|
-
static func emit(items: ArrayList<MenuItem>, depth: Int64): String {
|
|
126
|
-
var out = ""
|
|
127
|
-
for (item in items) {
|
|
128
|
-
if (!MenuModel.wireSafe(item.id) || !MenuModel.wireSafe(item.label) ||
|
|
129
|
-
!MenuModel.wireSafe(item.accelerator)) {
|
|
130
|
-
eprintln("[cj-tauri] menu: item dropped from wire (id/label/accelerator contains tab or newline)")
|
|
131
|
-
continue
|
|
132
|
-
}
|
|
133
|
-
out += MenuModel.tabs(depth) + MenuModel.kindCode(item.kind) + "\t" + item.id + "\t" +
|
|
134
|
-
item.label + "\t" + MenuModel.flags(item).toString() + "\t" + item.accelerator + "\n"
|
|
135
|
-
if (MenuModel.isSubmenu(item.kind)) {
|
|
136
|
-
out += MenuModel.emit(item.children, depth + 1)
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
return out
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* 枚举值**不能用 `==` 比较**(实测:Cangjie 1.2.0 编译期报
|
|
144
|
-
* `invalid binary operator '==' on type 'Enum-MenuItemKind' and 'Enum-MenuItemKind'`),只有 match 能判。
|
|
145
|
-
*/
|
|
146
|
-
private static func isSubmenu(kind: MenuItemKind): Bool {
|
|
147
|
-
match (kind) {
|
|
148
|
-
case MenuItemKind.Submenu => true
|
|
149
|
-
case _ => false
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* 字段里不许有制表符 / 换行 / 回车:它们是线路格式的结构字符。
|
|
155
|
-
* 注意本 SDK 里 `for (cp in someString)` 迭代出的是 **UInt8 字节**(不是 UInt32 码点,AGENTS §4 那条已更正):
|
|
156
|
-
* 三个结构字符都在 ASCII 范围内,按字节比对既够用、也不受多字节字符影响。
|
|
157
|
-
*/
|
|
158
|
-
private static func wireSafe(s: String): Bool {
|
|
159
|
-
for (cp in s) {
|
|
160
|
-
if (cp == 9u8 || cp == 10u8 || cp == 13u8) {
|
|
161
|
-
return false
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
return true
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
private static func tabs(n: Int64): String {
|
|
168
|
-
var out = ""
|
|
169
|
-
var i = 0
|
|
170
|
-
while (i < n) {
|
|
171
|
-
out += "\t"
|
|
172
|
-
i++
|
|
173
|
-
}
|
|
174
|
-
return out
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
private static func kindCode(kind: MenuItemKind): String {
|
|
178
|
-
match (kind) {
|
|
179
|
-
case MenuItemKind.Normal => "n"
|
|
180
|
-
case MenuItemKind.Checkbox => "c"
|
|
181
|
-
case MenuItemKind.Separator => "s"
|
|
182
|
-
case MenuItemKind.Submenu => "m"
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
/** flags:bit0 = 可用、bit1 = 勾选(十进制传输,C 侧只要 atoi) */
|
|
187
|
-
private static func flags(item: MenuItem): Int64 {
|
|
188
|
-
var f = 0
|
|
189
|
-
if (item.enabled) {
|
|
190
|
-
f += 1
|
|
191
|
-
}
|
|
192
|
-
if (item.checked) {
|
|
193
|
-
f += 2
|
|
194
|
-
}
|
|
195
|
-
return f
|
|
196
|
-
}
|
|
197
|
-
}
|
|
1
|
+
package cjTauri
|
|
2
|
+
|
|
3
|
+
import std.collection.*
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 窗口菜单栏的**纯仓颉模型**(RFC-002 §5.1):只描述意图,不含任何平台概念。
|
|
7
|
+
*
|
|
8
|
+
* 分层:模型(本文件)→ `WebViewHost.setMenu()`(src/host.cj)→ C 桥 `cj_bridge_set_menu` →
|
|
9
|
+
* 平台翻译层(native/bridge_win.c 的 CreateMenu 族 / bridge_linux.c 的 GtkMenuBar)。
|
|
10
|
+
* 菜单文本来自**应用作者**(不是页面可控的不可信输入),所以这里不做转义——
|
|
11
|
+
* 非法字段让那一项出局并出声,而不是写一套转义/反转义。
|
|
12
|
+
*/
|
|
13
|
+
public enum MenuItemKind {
|
|
14
|
+
| Normal // 普通项:点击回调自己的 id
|
|
15
|
+
| Checkbox // 可勾选项:回调里带 checked
|
|
16
|
+
| Separator // 分隔线:无 id、不可点
|
|
17
|
+
| Submenu // 子菜单:自身不可点,只承载 children
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
public class MenuItem {
|
|
21
|
+
public let id: String
|
|
22
|
+
public let label: String
|
|
23
|
+
public let kind: MenuItemKind
|
|
24
|
+
/** 快捷键**提示**文本(如 "Ctrl+S")。v1 只显示,不抢页面按键 */
|
|
25
|
+
public let accelerator: String
|
|
26
|
+
public var enabled: Bool
|
|
27
|
+
public var checked: Bool
|
|
28
|
+
/** 仅 Submenu 用;其它 kind 恒为空表 */
|
|
29
|
+
public let children: ArrayList<MenuItem>
|
|
30
|
+
|
|
31
|
+
public init(id: String, label: String, kind: MenuItemKind, accelerator: String,
|
|
32
|
+
enabled: Bool, checked: Bool, children: ArrayList<MenuItem>) {
|
|
33
|
+
this.id = id
|
|
34
|
+
this.label = label
|
|
35
|
+
this.kind = kind
|
|
36
|
+
this.accelerator = accelerator
|
|
37
|
+
this.enabled = enabled
|
|
38
|
+
this.checked = checked
|
|
39
|
+
this.children = children
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** 普通菜单项(最常用) */
|
|
43
|
+
public static func normal(id: String, label: String): MenuItem {
|
|
44
|
+
return MenuItem(id, label, MenuItemKind.Normal, "", true, false, ArrayList<MenuItem>())
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** 带快捷键提示的普通项 */
|
|
48
|
+
public static func normalWithAccel(id: String, label: String, accelerator: String): MenuItem {
|
|
49
|
+
return MenuItem(id, label, MenuItemKind.Normal, accelerator, true, false, ArrayList<MenuItem>())
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** 勾选项(初始 checked) */
|
|
53
|
+
public static func checkbox(id: String, label: String, checked: Bool): MenuItem {
|
|
54
|
+
return MenuItem(id, label, MenuItemKind.Checkbox, "", true, checked, ArrayList<MenuItem>())
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** 子菜单:id 留空(自身不回调),动作交给 children */
|
|
58
|
+
public static func submenu(label: String, children: ArrayList<MenuItem>): MenuItem {
|
|
59
|
+
return MenuItem("", label, MenuItemKind.Submenu, "", true, false, children)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** 分隔线 */
|
|
63
|
+
public static func separator(): MenuItem {
|
|
64
|
+
return MenuItem("", "", MenuItemKind.Separator, "", true, false, ArrayList<MenuItem>())
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* 菜单模型:`items` 是菜单栏的顶层,子菜单靠 `MenuItem.children` 递归。
|
|
70
|
+
*
|
|
71
|
+
* **不可变**(items 只读):运行期改状态走 `WebViewHost.setMenuItemState(id, …)`,不在这里改——
|
|
72
|
+
* 状态的真身在平台对象上(Windows 的 HMENU、GTK 的 GtkCheckMenuItem),只改模型平台不会自己变。
|
|
73
|
+
*/
|
|
74
|
+
public class MenuModel {
|
|
75
|
+
public let items: ArrayList<MenuItem>
|
|
76
|
+
|
|
77
|
+
public init(items: ArrayList<MenuItem>) {
|
|
78
|
+
this.items = items
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* 按 id 找菜单项(本层优先,再下钻子菜单);找不到返回 None
|
|
83
|
+
*
|
|
84
|
+
* 为什么要它:运行期改状态按 id 定位(`setMenuItemState`),而**应用层的模型状态**必须
|
|
85
|
+
* 能反查出「这个 id 现在是什么状态」——平台侧一次写两位(enabled + checked,见
|
|
86
|
+
* `WebViewHost.setMenuItemState`),拿不到另一维就会把用户刚点出来的勾选抹掉。
|
|
87
|
+
*/
|
|
88
|
+
public func findById(id: String): Option<MenuItem> {
|
|
89
|
+
return MenuModel.lookup(this.items, id)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
private static func lookup(items: ArrayList<MenuItem>, id: String): Option<MenuItem> {
|
|
93
|
+
for (item in items) {
|
|
94
|
+
if (item.id == id) {
|
|
95
|
+
return Some(item)
|
|
96
|
+
}
|
|
97
|
+
match (MenuModel.lookup(item.children, id)) {
|
|
98
|
+
case Some(found) => return Some(found)
|
|
99
|
+
case None => ()
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return None
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* 线路格式(RFC-002 §5.4):一行一项,字段用制表符分隔,**行首制表符数 = 层级**。
|
|
107
|
+
*
|
|
108
|
+
* <tabs><kind>\t<id>\t<label>\t<flags>\t<accel>\n
|
|
109
|
+
*
|
|
110
|
+
* kind:`n` 普通 / `c` 勾选 / `s` 分隔线 / `m` 子菜单;
|
|
111
|
+
* flags:十进制,bit0 = 可用、bit1 = 勾选(`3` = 又可用又勾选);
|
|
112
|
+
* 分隔线与子菜单也走满 5 个字段(id / label / accel 为空),C 侧因此不必为它们开特例。
|
|
113
|
+
*
|
|
114
|
+
* 为什么 kind 放最前:C 侧先分派再取值,日志里一眼能看出这行是什么。
|
|
115
|
+
* 为什么不是 JSON:C 侧(bridge_core.c)是纯 C、不引第三方,行式文本解析几十行就够,
|
|
116
|
+
* 且坏了能直接 `fprintf` 到 stderr(诊断走 stderr 是项目铁律)。JSON 留在仓颉侧给 CLI / 清单用。
|
|
117
|
+
* 为什么不做转义:字段里出现制表符 / 换行会破坏「一行一项」的结构——v1 的口径是
|
|
118
|
+
* **丢弃该项 + stderr 逐项提示**(见 emit),而不是引入转义/反转义这一对必须同时正确的函数。
|
|
119
|
+
*/
|
|
120
|
+
public func toWire(): String {
|
|
121
|
+
return MenuModel.emit(this.items, 0)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** 包内共享(TrayModel 的托盘菜单复用同一遍发射器,别再抄一份格式) */
|
|
125
|
+
static func emit(items: ArrayList<MenuItem>, depth: Int64): String {
|
|
126
|
+
var out = ""
|
|
127
|
+
for (item in items) {
|
|
128
|
+
if (!MenuModel.wireSafe(item.id) || !MenuModel.wireSafe(item.label) ||
|
|
129
|
+
!MenuModel.wireSafe(item.accelerator)) {
|
|
130
|
+
eprintln("[cj-tauri] menu: item dropped from wire (id/label/accelerator contains tab or newline)")
|
|
131
|
+
continue
|
|
132
|
+
}
|
|
133
|
+
out += MenuModel.tabs(depth) + MenuModel.kindCode(item.kind) + "\t" + item.id + "\t" +
|
|
134
|
+
item.label + "\t" + MenuModel.flags(item).toString() + "\t" + item.accelerator + "\n"
|
|
135
|
+
if (MenuModel.isSubmenu(item.kind)) {
|
|
136
|
+
out += MenuModel.emit(item.children, depth + 1)
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return out
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* 枚举值**不能用 `==` 比较**(实测:Cangjie 1.2.0 编译期报
|
|
144
|
+
* `invalid binary operator '==' on type 'Enum-MenuItemKind' and 'Enum-MenuItemKind'`),只有 match 能判。
|
|
145
|
+
*/
|
|
146
|
+
private static func isSubmenu(kind: MenuItemKind): Bool {
|
|
147
|
+
match (kind) {
|
|
148
|
+
case MenuItemKind.Submenu => true
|
|
149
|
+
case _ => false
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* 字段里不许有制表符 / 换行 / 回车:它们是线路格式的结构字符。
|
|
155
|
+
* 注意本 SDK 里 `for (cp in someString)` 迭代出的是 **UInt8 字节**(不是 UInt32 码点,AGENTS §4 那条已更正):
|
|
156
|
+
* 三个结构字符都在 ASCII 范围内,按字节比对既够用、也不受多字节字符影响。
|
|
157
|
+
*/
|
|
158
|
+
private static func wireSafe(s: String): Bool {
|
|
159
|
+
for (cp in s) {
|
|
160
|
+
if (cp == 9u8 || cp == 10u8 || cp == 13u8) {
|
|
161
|
+
return false
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return true
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
private static func tabs(n: Int64): String {
|
|
168
|
+
var out = ""
|
|
169
|
+
var i = 0
|
|
170
|
+
while (i < n) {
|
|
171
|
+
out += "\t"
|
|
172
|
+
i++
|
|
173
|
+
}
|
|
174
|
+
return out
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
private static func kindCode(kind: MenuItemKind): String {
|
|
178
|
+
match (kind) {
|
|
179
|
+
case MenuItemKind.Normal => "n"
|
|
180
|
+
case MenuItemKind.Checkbox => "c"
|
|
181
|
+
case MenuItemKind.Separator => "s"
|
|
182
|
+
case MenuItemKind.Submenu => "m"
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** flags:bit0 = 可用、bit1 = 勾选(十进制传输,C 侧只要 atoi) */
|
|
187
|
+
private static func flags(item: MenuItem): Int64 {
|
|
188
|
+
var f = 0
|
|
189
|
+
if (item.enabled) {
|
|
190
|
+
f += 1
|
|
191
|
+
}
|
|
192
|
+
if (item.checked) {
|
|
193
|
+
f += 2
|
|
194
|
+
}
|
|
195
|
+
return f
|
|
196
|
+
}
|
|
197
|
+
}
|
|
@@ -1,77 +1,77 @@
|
|
|
1
|
-
package cjTauri.platform
|
|
2
|
-
|
|
3
|
-
import std.collection.ArrayList
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* 应用可写目录(RFC-002 §5.2)
|
|
7
|
-
*
|
|
8
|
-
* 默认跟应用 id 走平台规范目录(XDG / AppData / filesDir),不落可执行文件旁边;
|
|
9
|
-
* 便携版(数据跟 exe)是显式选项,由骨架构建期注入,见 RFC §5.2。
|
|
10
|
-
* Windows 下统一用 "/" 分隔符(Windows API 均接受),减少按端拼路径的分支。
|
|
11
|
-
*/
|
|
12
|
-
public class AppPaths {
|
|
13
|
-
public let dataDir: String // 收藏库、状态等持久数据
|
|
14
|
-
public let cacheDir: String // 可清理缓存
|
|
15
|
-
public let tempDir: String // 本次运行临时文件
|
|
16
|
-
|
|
17
|
-
public init(dataDir: String, cacheDir: String, tempDir: String) {
|
|
18
|
-
this.dataDir = dataDir
|
|
19
|
-
this.cacheDir = cacheDir
|
|
20
|
-
this.tempDir = tempDir
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* 运行时上下文(RFC-002 §4.5)——框架在 launch() 里构建,业务只读
|
|
26
|
-
*
|
|
27
|
-
* supports() 是业务唯一的平台判断入口;target 字段仅供日志与埋点,
|
|
28
|
-
* code review 里出现「按 target.os 分支」应被拒绝(RFC 硬约束 1)。
|
|
29
|
-
*/
|
|
30
|
-
public class AppContext {
|
|
31
|
-
public let paths: AppPaths
|
|
32
|
-
public let target: TargetInfo
|
|
33
|
-
/** 开发期:资产优先读盘、允许 DevServer */
|
|
34
|
-
public let dev: Bool
|
|
35
|
-
private let features: ArrayList<PlatformFeature>
|
|
36
|
-
|
|
37
|
-
public init(paths: AppPaths, target: TargetInfo, dev: Bool, features: ArrayList<PlatformFeature>) {
|
|
38
|
-
this.paths = paths
|
|
39
|
-
this.target = target
|
|
40
|
-
this.dev = dev
|
|
41
|
-
this.features = features
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/** 特性探测:批次 0 用线性扫(特性位数 ≤ 十几,不值得上哈希表) */
|
|
45
|
-
public func supports(f: PlatformFeature): Bool {
|
|
46
|
-
for (item in features) {
|
|
47
|
-
if (PlatformFeature.same(item, f)) {
|
|
48
|
-
return true
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
return false
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/** 供日志输出:本端支持哪些特性位(降级行为要可观测,见 RFC §8) */
|
|
55
|
-
public func describeFeatures(): String {
|
|
56
|
-
let sb = StringBuilder()
|
|
57
|
-
var first = true
|
|
58
|
-
for (item in features) {
|
|
59
|
-
if (!first) {
|
|
60
|
-
sb.append(", ")
|
|
61
|
-
}
|
|
62
|
-
first = false
|
|
63
|
-
sb.append(featureName(item))
|
|
64
|
-
}
|
|
65
|
-
return sb.toString()
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
private static func featureName(f: PlatformFeature): String {
|
|
69
|
-
match (f) {
|
|
70
|
-
case Tray => "Tray"
|
|
71
|
-
case MultiWindow => "MultiWindow"
|
|
72
|
-
case FilePicker => "FilePicker"
|
|
73
|
-
case ClipboardImage => "ClipboardImage"
|
|
74
|
-
case SoftKeyboardInset => "SoftKeyboardInset"
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
}
|
|
1
|
+
package cjTauri.platform
|
|
2
|
+
|
|
3
|
+
import std.collection.ArrayList
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 应用可写目录(RFC-002 §5.2)
|
|
7
|
+
*
|
|
8
|
+
* 默认跟应用 id 走平台规范目录(XDG / AppData / filesDir),不落可执行文件旁边;
|
|
9
|
+
* 便携版(数据跟 exe)是显式选项,由骨架构建期注入,见 RFC §5.2。
|
|
10
|
+
* Windows 下统一用 "/" 分隔符(Windows API 均接受),减少按端拼路径的分支。
|
|
11
|
+
*/
|
|
12
|
+
public class AppPaths {
|
|
13
|
+
public let dataDir: String // 收藏库、状态等持久数据
|
|
14
|
+
public let cacheDir: String // 可清理缓存
|
|
15
|
+
public let tempDir: String // 本次运行临时文件
|
|
16
|
+
|
|
17
|
+
public init(dataDir: String, cacheDir: String, tempDir: String) {
|
|
18
|
+
this.dataDir = dataDir
|
|
19
|
+
this.cacheDir = cacheDir
|
|
20
|
+
this.tempDir = tempDir
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* 运行时上下文(RFC-002 §4.5)——框架在 launch() 里构建,业务只读
|
|
26
|
+
*
|
|
27
|
+
* supports() 是业务唯一的平台判断入口;target 字段仅供日志与埋点,
|
|
28
|
+
* code review 里出现「按 target.os 分支」应被拒绝(RFC 硬约束 1)。
|
|
29
|
+
*/
|
|
30
|
+
public class AppContext {
|
|
31
|
+
public let paths: AppPaths
|
|
32
|
+
public let target: TargetInfo
|
|
33
|
+
/** 开发期:资产优先读盘、允许 DevServer */
|
|
34
|
+
public let dev: Bool
|
|
35
|
+
private let features: ArrayList<PlatformFeature>
|
|
36
|
+
|
|
37
|
+
public init(paths: AppPaths, target: TargetInfo, dev: Bool, features: ArrayList<PlatformFeature>) {
|
|
38
|
+
this.paths = paths
|
|
39
|
+
this.target = target
|
|
40
|
+
this.dev = dev
|
|
41
|
+
this.features = features
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** 特性探测:批次 0 用线性扫(特性位数 ≤ 十几,不值得上哈希表) */
|
|
45
|
+
public func supports(f: PlatformFeature): Bool {
|
|
46
|
+
for (item in features) {
|
|
47
|
+
if (PlatformFeature.same(item, f)) {
|
|
48
|
+
return true
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return false
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** 供日志输出:本端支持哪些特性位(降级行为要可观测,见 RFC §8) */
|
|
55
|
+
public func describeFeatures(): String {
|
|
56
|
+
let sb = StringBuilder()
|
|
57
|
+
var first = true
|
|
58
|
+
for (item in features) {
|
|
59
|
+
if (!first) {
|
|
60
|
+
sb.append(", ")
|
|
61
|
+
}
|
|
62
|
+
first = false
|
|
63
|
+
sb.append(featureName(item))
|
|
64
|
+
}
|
|
65
|
+
return sb.toString()
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
private static func featureName(f: PlatformFeature): String {
|
|
69
|
+
match (f) {
|
|
70
|
+
case Tray => "Tray"
|
|
71
|
+
case MultiWindow => "MultiWindow"
|
|
72
|
+
case FilePicker => "FilePicker"
|
|
73
|
+
case ClipboardImage => "ClipboardImage"
|
|
74
|
+
case SoftKeyboardInset => "SoftKeyboardInset"
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -1,33 +1,33 @@
|
|
|
1
|
-
package cjTauri.platform
|
|
2
|
-
|
|
3
|
-
import std.fs.*
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* 目录型资产解析器(RFC §6.2):桌面开发期 / 发布读盘路径的默认实现
|
|
7
|
-
*
|
|
8
|
-
* root 为资产根(清单 [assets] root),逻辑名按 <root>/<name> 相对寻址。
|
|
9
|
-
* 沙箱端实现(鸿蒙 rawfile / 内嵌常量)在各自适配层,批次 3 随清单派生接入。
|
|
10
|
-
*/
|
|
11
|
-
public class DirectoryAssetResolver <: AssetResolver {
|
|
12
|
-
private let root: String
|
|
13
|
-
|
|
14
|
-
public init(root: String) {
|
|
15
|
-
this.root = root
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
public func readText(name: String): Option<String> {
|
|
19
|
-
try {
|
|
20
|
-
return Some(String.fromUtf8(File.readFrom(root + "/" + name)))
|
|
21
|
-
} catch (e: Exception) {
|
|
22
|
-
return None
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/** 以「能否读到」为准(批次 2 只服务文本资产;图标等二进制走各端资源目录,不进常量表) */
|
|
27
|
-
public func exists(name: String): Bool {
|
|
28
|
-
match (readText(name)) {
|
|
29
|
-
case Some(_) => return true
|
|
30
|
-
case None => return false
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
}
|
|
1
|
+
package cjTauri.platform
|
|
2
|
+
|
|
3
|
+
import std.fs.*
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 目录型资产解析器(RFC §6.2):桌面开发期 / 发布读盘路径的默认实现
|
|
7
|
+
*
|
|
8
|
+
* root 为资产根(清单 [assets] root),逻辑名按 <root>/<name> 相对寻址。
|
|
9
|
+
* 沙箱端实现(鸿蒙 rawfile / 内嵌常量)在各自适配层,批次 3 随清单派生接入。
|
|
10
|
+
*/
|
|
11
|
+
public class DirectoryAssetResolver <: AssetResolver {
|
|
12
|
+
private let root: String
|
|
13
|
+
|
|
14
|
+
public init(root: String) {
|
|
15
|
+
this.root = root
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
public func readText(name: String): Option<String> {
|
|
19
|
+
try {
|
|
20
|
+
return Some(String.fromUtf8(File.readFrom(root + "/" + name)))
|
|
21
|
+
} catch (e: Exception) {
|
|
22
|
+
return None
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** 以「能否读到」为准(批次 2 只服务文本资产;图标等二进制走各端资源目录,不进常量表) */
|
|
27
|
+
public func exists(name: String): Bool {
|
|
28
|
+
match (readText(name)) {
|
|
29
|
+
case Some(_) => return true
|
|
30
|
+
case None => return false
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|