MooTool next-compose 分栏迁入实录:Protobuf 与加解密工具可拖拽分栏的实现与 Electron 配置迁移

原创2026-10-14 12:20:391,760 阅读
文章标签:开发工具桌面应用MCP 服务

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)。

测试用例与运行命令

文档指定的验证方式有二:

  1. 单元测试 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。

  2. 命令行:在 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 分栏渲染」的整条链路。

登录后查看全文
MooTool