资讯动态

MooTool 应用内遮罩弹层(DIFF-057)实现解析:命令盘与分组管理器的 Compose Overlay 实践

发布时间:2026/10/16 18:25:49 来源:尧图企业网站定制
桌面应用开发工具【免费下载链接】MooToolHandy tool set for developers项目地址https://gitcode.com/gh_mirrors/mo/MooTool点击查看免费下载MooTool 的桌面版基于 Compose Multiplatform 重写next-compose其中命令盘/分组弹层改为应用内遮罩DIFF-057记录了搜索与分组管理弹层从系统半透明窗口迁移到主窗口内固定遮罩的关键决策。本文以该设计文档为骨架结合 MooOverlay.kt、Workbench.kt、ThemeContrastTest.kt 等源码与测试完整还原其设计背景、遮罩配色体系、布局规则、交互行为与配套验证并给出可直接检索与复用的实现要点。一、变更背景Electron 原行为与本产品目标1.1 Electron 的固定遮罩在 Electron 版 MooTool 中命令搜索command palette与分组管理均以应用壳内 fixed 遮罩呈现遮罩色为rgba(0,0,0,.2)深色主题或 hero 风格下更深面板使用surface-elevated的不透明表面点击遮罩即可关闭弹层这些弹层不是独立透明系统窗始终位于应用主窗内。对应 CSS 选择器为 Electron 侧的command-palette-backdrop与.dialog。1.2 本产品Compose 重写版行为next-compose遵循同一设计目标但落地为全新的 Compose 实现弹层画在 Workbench 内由MooOverlay承载遮罩来自overlayScrim()浅色 0.20 / 深色 0.46点击遮罩关闭命令盘靠上距顶约 96dp分组管理器居中面板一律用workspace实底删除确认仍是第二层遮罩叠加在分组管理器之上不关闭管理器本身彻底弃用 ComposeDialog弹出独立半透明窗的做法此前主窗截图会出现命令盘/分组与首页叠在一起的问题。该变更影响两个核心区域A02 命令搜索与 A01 分组管理器。二、为什么不用 Compose Dialog技术决策依据设计文档给出的理由非常直接Electron 弹层属于壳不离开主窗口。系统Dialog在 macOS 上半透明截到的命令盘/分组与首页叠在一起也不符合点遮罩关闭。也就是说选择应用内遮罩而非系统Dialog有三个现实原因截图穿透macOS 上 ComposeDialog是独立半透明窗口主窗内容会透出下层导致命令盘/分组与首页视觉叠加交互不符系统窗口与点遮罩关闭的应用内行为不一致层级控制应用内遮罩能精确控制与侧栏、工具区、Swing 编辑器的 z-order 关系详见后文ModalOverlayState机制。2.1 同类演进工具内 Dialog 同样被替换这条设计路线并非只针对命令盘与分组管理器。受影响的后续变更包括DIFF-059JSON / 随手记 / HTTP / 图片 / 调色板 / Host / 环境变量 / 翻译 / 运行台 / PDF / 正则 / Cron / 时钟 的 ComposeDialog全部改为MooOverlayDIFF-058历史 / Vault Git / 冲突弹层也改为 overlay并解决与 Swing 编辑器的互斥DIFF-060主窗退出确认同样收敛为MooOverlay。由此可见MooOverlay是整套重写版桌面 UI 的统一弹层基元而非一次性特例。三、MooOverlay 实现剖析MooOverlay.kt 是整个方案的实现核心。它对外暴露两个部分ModalOverlayState全局弹层计数与MooOverlay组合函数。3.1 ModalOverlayState全局遮罩计数object ModalOverlayState { var count by mutableStateOf(0) private set fun enter() { count } fun leave() { count (count - 1).coerceAtLeast(0) } }这个单例维护当前打开的应用内遮罩数量。MooOverlay在组合进入时调用enter()在onDispose时调用leave()形成生命周期对称。该计数被 EditorHost.kt 消费if (ModalOverlayState.count 0) { Box(modifier.fillMaxSize().background(colors.workspace)) } else { SwingPanel(factory { buffer.scrollPane... }, ...) }即任何 overlay 打开时EditorHost暂时卸下SwingPanelAWT 编辑器换成一个纯 Compose 的workspace色占位关闭后恢复同一个EditorBuffer。原因是 Compose Desktop 的SwingPanel是重型 AWT 组件会盖在同窗口的 Compose 遮罩之上抢走点击——这正是 DIFF-058 记录的分组弹层盖在 JSON/HTTP 上时取消/保存点不到问题的根治方案。3.2 MooOverlay 组合函数Composable fun MooOverlay( onDismiss: () - Unit, alignment: Alignment Alignment.Center, contentPadding: PaddingValues PaddingValues(24.dp), fillMaxSize: Boolean false, content: Composable () - Unit )关键行为参数/行为说明onDismiss点遮罩或按 Esc 时回调由调用方决定关闭哪个弹层alignment面板对齐方式命令盘用Alignment.TopCenter分组管理器用默认居中contentPadding面板外边距命令盘传PaddingValues(top 96.dp, ...)fillMaxSize是否让面板占满整窗工具内弹层常用遮罩Modifier.fillMaxSize().background(scrim).clickable(onClick onDismiss)EsconPreviewKeyEvent中拦截Key.Escape并配合blockedByIme()避免 IME 输入法编辑时误关面板实底、圆角、consumeClicks消费点击防止点面板内部冒泡到遮罩关闭源码结构上MooOverlay是两层Box外层全屏Box负责 Esc 拦截内层第一个Box是遮罩fillMaxSize().background(scrim).clickable(...)第二个Box按alignment或fillMaxSize布局面板并用consumeClicks拦截面板自身的点击避免误触关闭。3.3 面板表面mooDialogSurface面板样式由 MooOverlay.kt 中的mooDialogSurface提供fun Modifier.mooDialogSurface(radius: Dp? null): Modifier { val shape RoundedCornerShape(radius ?: MooTheme.dimens.dialogRadius) val border when (colors.styleId) { smartisan, miui-v5 - colors.borderControl else - colors.borderSoft } return this .shadow(24.dp, shape, ambientColor colors.shadow, spotColor colors.shadow) .clip(shape) .background(colors.workspace) .border(1.dp, border, shape) }要点实底背景用colors.workspace与 DIFF-060 对齐的 chrome token 体系24dp 阴影 1dp 边框视觉上区别于页面内容边框 token 按风格差异化smartisan / miui-v5 用borderControl其余用borderSoft圆角默认取MooTheme.dimens.dialogRadius各风格不同如 modern 13dp、hero 16dp、miui-v5 8dp见 ThemeContrastTest.kt 的chromeRadiiMatchElectronStyleBlocks。四、遮罩配色体系overlayScrim4.1 六套风格的 alpha 设计MooTheme.kt 的overlayScrim()是遮罩色的唯一来源fun overlayScrim(): Color { val dark workspace.luminance() 0.5f return when (styleId) { hero - Color(0xFF09090B).copy(alpha 0.46f) smartisan - Color(0xFF231F1B).copy(alpha 0.42f) miui-v5 - Color.Black.copy(alpha 0.38f) claude - Color(0xFF2D2520).copy(alpha 0.34f) else - Color(0xFF121214).copy(alpha if (dark) 0.46f else 0.24f) } }对应文档所述浅色 0.20 / 深色 0.46的精确实现默认风格modern / quiet 等浅色遮罩 alpha 为 0.24深色为 0.46hero 在浅色下也达到 0.46与 Electron 侧深色/hero 更深对应。各风格遮罩 alpha 汇总由源码与 ThemeContrastTest.kt 交叉确认风格遮罩色alpha测试断言modern浅色0xFF1212140.240.24f ± 0.02modern深色0xFF1212140.46全部风格 0.2f深色 0.34fhero0xFF09090B0.46 0.4fnavSelectedTokensFollowElectronStyleBlockssmartisan0xFF231F1B0.42—miui-v5Color.Black0.38—claude0xFF2D25200.34—4.2 对比度下限的自动化守护ThemeContrastTest.kt 的bodyTextMeetsContrastFloorAcrossStyles对全部 6 套风格 × 明暗两态做校验val scrim colors.overlayScrim() assertTrue(scrim.alpha 0.2f, ...) if (dark) assertTrue(scrim.alpha 0.34f, ...)同时保证正文对比度textPrimary vs workspace 4.5、侧栏 4.5、焦点环 3。也就是说遮罩 alpha 的下限是被测试锁定的防止后续主题改动把遮罩调得过浅影响文字可读性。这正是设计文档中ThemeContrastTest校验各风格 scrim alpha、desktopTest 240/240的证据所指。五、命令盘靠上居中的搜索浮层命令盘是搜索弹层CommandSearch实现在 Workbench.kt。5.1 调用方式MooOverlay( onDismiss { container.setSearchOpen(false) }, alignment Alignment.TopCenter, contentPadding PaddingValues(top 96.dp, start 24.dp, end 24.dp, bottom 24.dp) ) { Column( Modifier .width(620.dp) .mooDialogSurface(MooTheme.dimens.commandRadius) .onPreviewKeyEvent { onPaletteKey(it) } ) { // 搜索输入框 结果列表 关闭按钮 } }面板宽620dp顶部距窗顶96dp正是文档所说约 96dp 靠上圆角取commandRadiusmodern 8dp、hero 16dp 等见 ThemeContrastTest.kt搜索结果实时根据 query 与当前语言重算remember(query, container.settings.value.general.language) { commandSearchEntries(query) { container.t(it) } }选中项索引在结果变化时保持LaunchedEffect(results) { selected nextCommandIndex(selected, results.size, stay true) }。5.2 快捷键与焦点管理命令盘实现了一整套键盘导航onPaletteKeyKey.DirectionDown / DirectionUp移动选中项Key.Enter执行选中命令打开工具或设置分类并关闭Key.Escape关闭命令盘。焦点管理通过CommandPaletteFocusTargetSearch / Close / Result 三区与 Tab 迁移函数commandPaletteTabFocusTransition实现支持 Tab / ShiftTab 在搜索框、结果列表、关闭按钮间循环函数commandPaletteTabForwardWalkthrough/commandPaletteTabBackwardWalkthrough还提供了测试友好的步进遍历。5.3 打开与 Esc 短路Workbench 顶层BoxWithConstraints挂onPreviewKeyEvent命中settings.shortcuts.search快捷键Electron 侧对应 Cmd/CtrlK时setSearchOpen(true)命中Key.Escape时按优先级短路searchOpen→groupManagerOpen→showSettings逐层关闭。由于快捷键挂在 Workbench 根布局而非弹层内部遮罩打开时这些快捷键依然生效这一点与 DIFF-058 的记录一致。六、分组管理器居中的实底面板 二级删除遮罩分组管理器CustomGroupManager实现在 CustomGroupManager.kt结构是一个居中 overlay 一个条件渲染的二级 overlay。6.1 主面板MooOverlay(onDismiss { container.setGroupManagerOpen(false) }) { Column( Modifier.width(720.dp).heightIn(max 560.dp).mooDialogSurface().padding(16.dp), ... ) { // 左侧分组列表新建/选中/数量 // 右侧名称编辑 工具勾选ToolRegistry 分组树 // 底部删除 / 取消 / 保存prominent校验通过才可用 } }面板宽720dp、高上限560dpalignment走默认Alignment.Center文档分组管理器居中左侧分组列表 200dp右侧工具勾选按ToolRegistry.groups的既有分组树渲染每行一个MooSwitch保存校验CustomGroupDraft.invalid(groups)返回非空表示非法名称空白或未勾选任何工具此时保存按钮enabled false输入区下方同步显示danger色提示保存时写回设置container.updateSettings { current - current.copy(layout ...customGroups CustomGroupDraft.persistable(groups)) }。6.2 二级删除确认不关闭管理器删除确认是第二层MooOverlay由pendingDelete状态控制if (pendingDelete selected ! null) { MooOverlay(onDismiss { pendingDelete false }) { Column( Modifier.width(420.dp).mooDialogSurface().padding(16.dp), ... ) { Text(container.t(app.group.manage.deleteConfirm, mapOf(name to selected.name))) Row { MooButton(container.t(app.group.manage.delete), danger true, onClick { val id selected.id groups groups.filterNot { it.id id } selectedId next.getOrNull(...)?.id pendingDelete false }) MooButton(container.t(common.cancel), onClick { pendingDelete false }) } } } }行为要点与文档完全对应删除确认浮在管理器之上形成第二层遮罩点遮罩或取消只把pendingDelete置回 false分组管理器保持打开确认删除后移除对应分组并把选中项迁移到相邻分组随后关闭确认层。由于ModalOverlayState.count为 2两层同时打开Swing 编辑器在此时也被一并卸下两层 Compose 遮罩可正常叠放渲染。七、设计红线与受影响范围7.1 仍然不是六套 CSS 的逐选择器皮肤文档明确仍非六套 CSS 逐选择器皮肤。即本方案通过统一 tokenoverlayScrim()/mooDialogSurface/MooTheme.dimens覆盖多风格外观而不是逐选择器复刻 Electron 的command-palette-backdrop/.dialog样式。从 ThemeContrastTest.kt 的modernChromeTokensMatchElectronCss等测试可以看出token 取值与 Electronglobal.css对齐但组织方式是 token 化而非选择器移植。7.2 工具内 Dialog 已由 DIFF-059 统一命令盘/分组之后所有工具内弹层统一走 DIFF-059最终命令盘、分组管理器、工具弹层、历史、Vault Git、退出确认全部收敛到同一套MooOverlay。唯一保持原生的例外是系统FileDialog/JFileChooser/JColorChooser见 DIFF-059。7.3 遗留未测项设计文档记录的未测范围在阅读源码时可视为验证边界产品主窗 Tab / IME 手工交互托盘 TCC透明控制场景三平台安装包。相关场景已在 Electron 侧或后续变更中覆盖如 DIFF-060 记录了关闭 overlay 产品窗截图docs/evidence/2026-09-15-inspector-screencapture/windows/。八、可直接复用的实现清单所有应用内弹层一律走MooOverlay不再使用 ComposeDialog避免 macOS 半透明系统窗导致的截图穿透与点击抢层遮罩色从overlayScrim()取不要硬编码 alpha改动主题时保证 alpha 下限与对比度断言ThemeContrastTest.kt不回归面板用mooDialogSurfaceworkspace实底 24dp 阴影 1dp 边框圆角取dialogRadius/commandRadius依赖ModalOverlayState计数overlay 打开时EditorHost自动卸下SwingPanel关闭后恢复同一EditorBuffer避免 AWT 编辑器遮挡 Compose 遮罩二级确认如删除用嵌套MooOverlay通过独立状态如pendingDelete控制点外层遮罩只关闭二级层不关父弹层快捷键挂在 Workbench 根层onPreviewKeyEvent里对 Esc 做searchOpen → groupManagerOpen → showSettings短路保证遮罩打开时快捷键仍然有效Esc 与 IME 协同所有 Esc 处理都先过blockedByIme()输入法组合编辑时不误关闭弹层。九、验证与证据单元/集成测试desktopTest在 DIFF-057 落地时为240/240通过后续 DIFF-059/060 推进到 241/243见各自设计文档遮罩与 chrome token 断言见 ThemeContrastTest.kt主窗重拍截图72-command-palette.png命令盘、73-group-manager.png分组管理器尺寸 2880×1628可直接核对弹层在真实主窗内的呈现遮罩、靠上/居中布局、面板实底。延伸阅读DIFF-058overlay 与 Swing 编辑器互斥、DIFF-059工具内 Dialog 统一、DIFF-060退出确认与 chrome token、architecture.mdSwingPanel 互操作说明。赞分享桌面应用开发工具【免费下载链接】MooToolHandy tool set for developers项目地址https://gitcode.com/gh_mirrors/mo/MooTool点击查看免费下载相关推荐MooTool next-compose 工具内 Dialog 改造为 MooOverlay 应用内遮罩的完整实现剖析MooTool next compose 工具内 Dialog 改造为 MooOverlay 应用内遮罩的完整实现剖析 导读 本文基于 MooTool 新一代桌桌面应用开发工具MooTool Next Compose DIFF-060退出确认改为应用内遮罩chrome token 对齐 ElectronMooTool Next Compose DIFF 060退出确认改为应用内遮罩chrome token 对齐 Electron 本文基于 MooTool桌面应用开发工具Vue DevUI Overlay 遮罩层组件实战指南固定遮罩与弹性跟随定位全解析Vue DevUI Overlay 遮罩层组件实战指南固定遮罩与弹性跟随定位全解析 导读 Overlay遮罩层是 vue devui https://li前端UI组件设计系统创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑