MooTool next-compose 分栏迁入实录:Protobuf 与加解密工具可拖拽分栏的实现与 Electron 配置迁移
MooTool next-compose 分栏迁入实录:Protobuf 与加解密工具可拖拽分栏的实现与 Electron 配置迁移
导读
本文围绕 DIFF-170 设计文档 展开,梳理 MooTool 下一代 Compose 桌面版(next-compose)如何将 Protobuf 工具与加解密工具的固定分栏布局升级为可拖拽分栏,并补齐 Electron 旧版用户的分栏宽度迁移能力。读完本文你将掌握:Electron 与 Compose 两套分栏模型各自的存储格式与换算公式、IoThreePaneRow / IoTwoPaneRow 可复用分栏组件的约束逻辑,以及如何通过 ElectronPaneSizeImport 将旧版比例一键转换为新版绝对宽度。
背景:两套分栏模型之间的鸿沟
MooTool 的 Electron 版(next/)与下一代 Compose 桌面版(next-compose/)在分栏布局上采用了两种截然不同的模型:
- Electron 版:使用
ResizableColumns组件,将分栏宽度保存为归一化比例(normalized ratios),并按storageKey写入设置中的layout.paneSizes。 - Compose 版:此前 Protobuf / 加解密等工具页面要么使用固定的
weight权重,要么硬编码 132dp 的中栏,导致用户无法按需调整三栏或两栏的宽度;同时从 Electron 迁移设置时,这些工具的分栏比例并未被覆盖,属于迁入盲区。
DIFF-170 的目标正是补齐这一缺口。文档原文给出的基线数据如下:
- Protobuf:
protobuf-json(两列,比例 0.34 : 0.66)、protobuf-wire/protobuf-convert(三列,比例 1 : 0.28 : 1)。 - 加解密:
crypto-symmetric、crypto-base(三列,比例 1 : 0.28 : 1)、crypto-key-pair(两列,比例 1 : 1)。
Electron 侧的分栏定义
在 Electron 版源码中,这些分栏的默认尺寸与最小宽度直接写在 JSX 里。以 ProtobufTool.tsx 为例:
protobuf-json:columns={2}、defaultSizes={[0.34, 0.66]}、minPaneWidths={[240, 420]}、minimumWidth={760}。protobuf-wire:columns={3}、defaultSizes={[1, 0.28, 1]}、minPaneWidths={[240, 110, 240]}。protobuf-convert:与protobuf-wire相同的三列配置,但storageKey独立为protobuf-convert。
加解密侧对应 CryptoTool.tsx:
- 对称加密面板使用
crypto-symmetric(三列);Base 面板使用crypto-base(columns={3}、defaultSizes={[1, 0.28, 1]}、minPaneWidths={[240, 110, 240]})。 - 非对称面板的公私钥区使用
crypto-key-pair(columns={2}、defaultSizes={[1, 1]}、minPaneWidths={[260, 260]})。
ResizableColumns 的存储机制定义在 ResizableColumns.tsx:初始化时以 settings.layout.paneSizes[storageKey] ?? defaultSizes 作为起点,经过 normalizeSizes 归一化后渲染 minmax(minWidth, ratio fr) 网格;每次拖拽结束调用 persist(),把最新比例写回 layout.paneSizes[storageKey]。因此 Electron 设置文件里保存的始终是比例数组,而非像素宽度。
Compose 侧的历史遗留
Compose 版此前对 Protobuf / 加解密页面使用固定的 weight 或 132dp 中栏,迁移逻辑(ElectronPaneSizeImport)也尚未注册这些工具的 storage key——这正是文档所述「compose 此前为固定 weight 或 132dp 中栏,迁入未覆盖」的背景。
行为拆解:F07 / F14 / A03
F07:Protobuf 三个 Tab 的分栏可拖拽
Protobuf 工具页包含 json / wire / convert 三个 Tab,对应分栏 key 分别为 protobuf-json、protobuf-wire、protobuf-convert。
在 Compose 实现 ProtobufScreen.kt 中:
JsonTab使用IoTwoPaneRow(paneKey = "protobuf-json", minLeft = 240f, minRight = 420f, defaultLeftFraction = 0.34f)(两列,左列 .proto 定义、右列 JSON / 二进制)。WireTab与ConvertTab均使用IoThreePaneRow(paneKey = "protobuf-wire" / "protobuf-convert", middleRatio = 0.28f, minRight = 240f)(三列,左输入、中操作按钮、右输出)。
F14:加解密分栏可拖拽
加解密工具页(CryptoScreen.kt)覆盖 Symmetric、Asymmetric、Digest、Base、Random 五个 Tab,其中:
SymmetricPanel使用IoThreePaneRow(paneKey = "crypto-symmetric", middleRatio = 0.28f, minRight = 240f):明文 | 加密/解密/复制 | 密文。BasePanel使用IoThreePaneRow(paneKey = "crypto-base", middleRatio = 0.28f, minRight = 240f):原文 | 编码/解码/复制 | 编码结果。AsymmetricPanel的公私钥区使用IoTwoPaneRow(paneKey = "crypto-key-pair", minLeft = 260f, minRight = 260f, defaultLeftFraction = 0.5f):公钥 | 私钥,两列默认 1 : 1。
A03:抽取可复用分栏组件
为避免配置页、Protobuf、加解密各自维护一套拖拽分栏逻辑,本次将三列与两列分栏抽象为两个通用组件,集中放在 IoPaneLayouts.kt:
IoThreePaneRow(container, settings, paneKey, middleRatio, minRight, modifier, left, middle, right)。IoTwoPaneRow(container, settings, paneKey, minLeft, minRight, defaultLeftFraction, modifier, left, right)。
组件内部通过 settings.layout.pane(paneKey, index, default, min, max) 读取已持久化的宽度,并通过 VerticalPaneHandle 的回调 container.setPaneSize(paneKey, index, value, slots) 写回设置,完成拖拽闭环。
核心换算:ElectronPaneSizeImport 的归一化迁移
跨版本迁入的关键实现在 ElectronPaneSizeImport.kt。该对象将 Electron 的比例数组换算为 Compose 的绝对 dp 宽度,统一以 1320dp 作为参考内容宽度:
private const val REFERENCE_CONTENT_WIDTH_DP = 1320f
换算思路:比例 / 总和 * 1320,再用 coerceIn(min, max) 约束到合理区间,避免窗口缩放或极端比例导致分栏越界。
三列分栏:mapIoThreePane
本次新增的 protobuf-wire、protobuf-convert、crypto-symmetric、crypto-base 全部走三列映射:
mapIoThreePane(electron["protobuf-wire"], "protobuf-wire", 240f, 700f, 110f, 280f, result)
mapIoThreePane(electron["protobuf-convert"], "protobuf-convert", 240f, 700f, 110f, 280f, result)
mapIoThreePane(electron["crypto-symmetric"], "crypto-symmetric", 240f, 700f, 110f, 280f, result)
mapIoThreePane(electron["crypto-base"], "crypto-base", 240f, 700f, 110f, 280f, result)
其实现为:取前三个比例,若总和大于 0 则计算左栏 ratios[0] / total * 1320 并限制在 [minLeft, maxLeft] = [240, 700],计算中栏 ratios[1] / total * 1320 并限制在 [minMiddle, maxMiddle] = [110, 280],最终写入 paneKey -> listOf(left, middle)(右栏在布局中由 Modifier.weight(1f) 自适应剩余宽度)。
两列分栏:mapTwoColumnWorkspace 与 existingSlot
protobuf-json(两列 0.34 : 0.66)与 crypto-key-pair(两列 1 : 1)走两列映射:
mapTwoColumnWorkspace(electron["protobuf-json"], "protobuf-json", 240f, 700f, 420f, result, current)
mapTwoColumnWorkspace(electron["crypto-key-pair"], "crypto-key-pair", 260f, 900f, 260f, result, current)
mapTwoColumnWorkspace 计算第一列 ratios[0] / total * 1320 并限制在 [minFirst, maxFirst];第二列则通过 existingSlot 优先复用本次合并结果或当前设置中已有的第二列宽度,实在没有才使用传入的 defaultSecond。这样保证了「只迁第一列比例,不覆盖用户已手动调整的第二列」。
换算的数值验证
以文档与测试中的基线数据验算:
protobuf-json(0.34 : 0.66):第一列 =0.34 / 1.0 * 1320 = 448.8dp(在[240, 700]内),第二列沿用默认420dp。protobuf-wire(1 : 0.28 : 1):总和 2.28,左栏 =1 / 2.28 * 1320 ≈ 578.947dp,中栏 =0.28 / 2.28 * 1320 ≈ 162.105dp。crypto-key-pair(1 : 1):第一列 =1 / 2.0 * 1320 = 660dp(在[260, 900]内)。
这些结果与 ElectronPaneSizeImportTest.kt 中 convertsProtobufAndCryptoPaneRatios 的断言(448.8f、578.947f、660f)完全一致。
已注册的 key 全景
从源码可以确认,本次补全后 ElectronPaneSizeImport.mergeElectronIntoCompose 已覆盖下列与 Protobuf / 加解密相关的 storage key:
| 功能 | storage key | 列数 | Electron 默认比例 | 换算后左/中栏约束 |
|---|---|---|---|---|
| Protobuf JSON 互转 | protobuf-json |
2 | [0.34, 0.66] |
[240, 700] + 默认 420 |
| Protobuf Wire 解码 | protobuf-wire |
3 | [1, 0.28, 1] |
左 [240, 700]、中 [110, 280] |
| Protobuf Hex/Base64 转换 | protobuf-convert |
3 | [1, 0.28, 1] |
左 [240, 700]、中 [110, 280] |
| 对称加解密 | crypto-symmetric |
3 | [1, 0.28, 1] |
左 [240, 700]、中 [110, 280] |
| Base 编码解码 | crypto-base |
3 | [1, 0.28, 1] |
左 [240, 700]、中 [110, 280] |
| 公私钥对 | crypto-key-pair |
2 | [1, 1] |
左 [260, 900] + 默认 260 |
Compose 侧如何消费迁移后的宽度
迁移后的宽度最终落在 LayoutSettings.paneSizes(见 Settings.kt),这是一个 Map<String, List<Float>>,key 为工具/分栏标识,value 为各列的 dp 宽度列表。
读写两端对称设计:
- 读取:
LayoutSettings.pane(toolId, index, default, min, max)——优先取paneSizes[toolId]?.getOrNull(index),值为空或非正数时回退默认值,并统一coerceIn(min, max)兜底。 - 写入:
LayoutSettings.withPane(toolId, index, value, slots)——自动扩充列表到所需槽位数,再写入对应下标。
IoThreePaneRow / IoTwoPaneRow 正是通过 settings.layout.pane(...) 在 BoxWithConstraints 内计算实际渲染宽度,并通过 VerticalPaneHandle 的 onDelta / onReset 回调触发 container.setPaneSize 持久化。其中三栏组件的关键约束参数为:分隔条 paneHandle = 10f、左栏最小 240dp、中栏最小 110dp、右栏最小由调用方传入(Protobuf / 加解密均为 240dp)、中栏默认占比 middleRatio = 0.28f。
迁移调用链与验证方式
完整链路
设置迁入并非孤立调用,而是经由 ElectronNextSettingsImport.mergeInto 统一入口(见 ElectronNextSettingsImport.kt):
ElectronNextSettingsImport.mergeInto(current, imported)
└─ layout.paneSizes = ElectronPaneSizeImport.mergeElectronIntoCompose(
patch.layout.paneSizes, current.layout.paneSizes)
mergeElectronIntoCompose 以「Electron 比例为唯一来源、Compose 当前值兜底」的策略合并:electron.isEmpty() 时直接返回当前值;存在比例但不足所需列数、或总和非法时跳过;existingSlot 保证第二列优先沿用既有用户配置。整体结果再交由 SettingsLayoutNormalize 做数值边界归一化(见 SettingsLayoutNormalize.kt)。
测试用例与运行命令
文档指定的验证方式有二:
-
单元测试
ElectronPaneSizeImportTest.convertsProtobufAndCryptoPaneRatios:以protobuf-json(0.34 : 0.66)、protobuf-wire(1 : 0.28 : 1)、crypto-symmetric(1 : 0.28 : 1)、crypto-key-pair(1 : 1)为输入,断言换算后的protobuf-json<a href="https://link.gitcode.com/i/01649556cbdbc24d361180412ee90517" target="_blank">0] == 448.8f、protobuf-wire[0] == 578.947f、crypto-key-pair[0] == 660f。测试文件位于 [ElectronPaneSizeImportTest.kt。 -
命令行:在
next-compose/目录下执行离线桌面测试:
./gradlew :composeApp:desktopTest --offline
--offline 保证在无网络环境下也能完整跑通测试,适合 CI 或离线开发环境复现验证。
小结
DIFF-170 的核心价值在于两点:一是让 Protobuf 与加解密工具从「固定 weight / 132dp」升级为与 Electron 体验一致的可拖拽分栏,并通过 IoThreePaneRow / IoTwoPaneRow 消除了跨页面的重复实现;二是补齐 ElectronPaneSizeImport 对这六个 storage key 的注册,使用户从 Electron 版迁入 Compose 版时,分栏比例能够以「归一化比例 → 绝对 dp 宽度」的方式无损继承。对需要继续深入本仓库的读者,推荐按 ElectronPaneSizeImport.kt → IoPaneLayouts.kt → ProtobufScreen.kt / CryptoScreen.kt 的顺序阅读,即可完整还原从「Electron 设置读取」到「Compose 分栏渲染」的整条链路。