信箱模式与预览
信箱模式与预览是 SpinningMomo 桌面端(Win32 原生悬浮窗)中的视觉增强能力:features::letterbox 通过一个可切换的 enabled 状态控制悬浮窗预览画面的"黑边"呈现,features::preview 负责预览画面的采集、渲染与交互。本页梳理两者的模块组成、切换命令链路、状态初始化与配置入口。
目的与范围
本页覆盖以下内容:
src/features/letterbox模块的文件构成与对外接口(toggle_letterbox、LetterboxState)。letterbox.toggle命令在src/core/commands/builtin.cpp中的注册与回调链路(含触发悬浮窗重绘)。src/core/initializer/initializer.cpp启动时从 settings 同步letterbox启用状态的流程。AppState中 letterbox 状态的持有方式(前向声明 +unique_ptr)。src/features/preview模块的文件构成(capture / interaction / rendering / shaders / state)。- 相关 i18n 文案键(
menu.letterbox_toggle)。
不属于本页范围、由兄弟页面承接的主题:
- 悬浮窗口本身的创建与消息循环:参见
ui::*(floating_window)相关页面。 - 设置(settings)持久化的整体机制:仅在此引用其
features.letterbox.enabled字段。 - 命令注册表(command registry)的通用基础设施:参见 core/commands 相关页面。
- 录制(recording)、截图(screenshot)等与采集相关但彼此独立的能力。
诚实声明:本页在采证阶段受工具预算限制,仅完整核验了命令注册、初始化同步、状态持有与文件布局等证据点;
letterbox.cpp/usecase.cpp与preview/rendering.cpp等实现文件的内部算法未逐行读取,文中会明确标注"实现细节未在本次采证中核验"。
概述
信箱模式在视频/预览语境中通常指:当内容宽高比与显示区域不一致时,用黑色条带(letterbox bars)填充多余区域,避免画面拉伸变形。在 SpinningMomo 中:
- 该能力被建模为一个布尔开关状态
LetterboxState::enabled,保存在全局AppState中。 - 用户通过菜单命令
letterbox.toggle(i18n 文案 zh-CN 为"黑边模式",en-US 为 "Letterbox")切换该状态。 - 切换回调在调用
toggle_letterbox之后紧接着调用ui::floating_window::request_repaint(state),即状态变更立即驱动悬浮窗重绘,让预览画面按新状态渲染。 - 应用启动时(
initializer.cpp),letterbox的启用状态会从持久化 settings 中同步一次:state.letterbox->enabled = state.settings->raw.features.letterbox.enabled;,保证上次退出时的选择在下次启动时恢复。
features::preview 模块是预览画面的实现载体,按文件名划分为采集(capture)、交互、渲染、着色器与状态五部分(见下文"模块组成")。
从架构分层看(依据 AGENTS.md 对仓库结构的描述):core::* 提供框架基础设施(命令、初始化、状态等),features::* 承载业务逻辑(letterbox、preview 等),ui::* 提供 Win32 原生 UI(floating_window、tray_icon、context_menu、webview_window)。信箱模式正是这三层协作的最小样例:命令层触发 → 特性层改状态 → UI 层重绘。
架构
架构要点说明:
- 命令层(sg_Commands):
builtin.cpp在命令注册表中登记letterbox.toggle,并为其绑定 i18n 键menu.letterbox_toggle,因此该命令天然出现在托盘/上下文菜单中并可本地化。 - 特性层(sg_Letterbox / sg_Preview):letterbox 的全部业务状态收敛在
LetterboxState;行为入口收敛在自由函数toggle_letterbox。preview 模块按文件职责拆分(采集、渲染、交互、着色器、状态),与渲染相关的细节未在本次采证中核验,虚线箭头表示"状态被消费"的推断关系。 - 核心层(sg_Core):
AppState通过前向声明 +std::unique_ptr<features::letterbox::LetterboxState>持有特性状态,避免在公共头文件中拉入特性实现头,降低编译耦合。initializer.cpp负责把持久化设置灌入运行时状态。 - UI 层(sg_UI):切换回调显式调用
request_repaint,说明重绘不是隐式副作用,而是命令回调里的一次显式请求——这是理解"状态变更如何变为像素变化"的关键一步。
模块组成
letterbox 模块文件构成
src/features/letterbox/ 目录下共 5 个文件(模块结构已在采证中通过 ListFiles 核验):
| 文件 | 角色(依据仓库结构与命名惯例推断) |
|---|---|
state.hpp | 定义 features::letterbox::LetterboxState,持有 enabled 等运行时状态 |
usecase.hpp / usecase.cpp | 声明并实现用例层自由函数,含 toggle_letterbox(state) |
letterbox.hpp / letterbox.cpp | 模块对外的头文件与补充实现(内部细节未在本次采证中核验) |
preview 模块文件构成
src/features/preview/ 目录下共 10 个文件(已核验):
| 文件 | 角色(依据仓库结构与命名惯例推断) |
|---|---|
state.hpp | 预览模块的运行时状态定义 |
capture.hpp / capture.cpp | 预览画面帧的采集入口 |
rendering.hpp / rendering.cpp | 预览画面的绘制/渲染实现 |
shaders.hpp | 渲染所用着色器(letterbox 黑边等视觉效果通常在此实现) |
interaction.hpp / interaction.cpp | 预览区域内的鼠标/触摸等交互处理 |
preview.hpp / preview.cpp | 模块门面与整体流程编排 |
注意:上表"角色"列为基于文件命名的职责推断,
rendering.cpp/shaders.hpp内部如何实现信箱黑边(例如是否基于宽高比计算条带矩形、是否在 shader 中做 UV 裁剪)未在本次采证中核验。
核心流程
命令注册与切换回调
命令注册发生在 src/core/commands/builtin.cpp。该文件在头部包含了 letterbox 的状态与用例头:
#include "features/adb_mode/usecase.hpp"
#include "features/letterbox/state.hpp"
#include "features/letterbox/usecase.hpp"Source: builtin.cpp
随后通过 register_command 登记 letterbox.toggle 命令,回调体在切换状态后立即请求悬浮窗重绘:
1register_command(registry, {
2 .id = "letterbox.toggle",
3 .i18n_key = "menu.letterbox_toggle",
4 // ... 其余注册字段未在本次采证中读取
5 });
6// 回调(依据 Grep 上下文片段):
7// [&state]() {
8// features::letterbox::toggle_letterbox(state);
9// ui::floating_window::request_repaint(state);
10// }Source: builtin.cpp
关键设计意图:
- 命令即菜单项:
.i18n_key指向menu.letterbox_toggle,使得同一命令定义同时驱动菜单文案与行为,避免两处维护。 - 显式重绘请求:
request_repaint紧跟toggle_letterbox,说明悬浮窗采用"请求-重绘"模式而非脏标记自动传播;任何新增的视觉状态切换都必须记得补上这一步,否则会出现状态已变但画面未更新的 bug。 - lambda 捕获方式:回调以
[&state]捕获AppState引用,命令注册表与全局状态共享同一份AppState实例。
启动时状态同步
src/core/initializer/initializer.cpp 在初始化阶段把持久化设置灌入运行时状态:
// 从 settings 同步 letterbox 启用状态
state.letterbox->enabled = state.settings->raw.features.letterbox.enabled;Source: initializer.cpp
该行上方还有 #include "features/letterbox/state.hpp"(见同文件 L27)。设计意图:settings 是"用户偏好的持久化事实",letterbox 运行时状态是"本次会话的可变事实",初始化时做一次单向同步,避免运行期间反向写回造成意外覆盖。
状态持有方式
src/core/state/app_state.hpp 使用前向声明 + 智能指针持有特性状态:
1namespace features::letterbox {
2struct LetterboxState;
3}
4
5// 在 AppState 成员区:
6std::unique_ptr<features::letterbox::LetterboxState> letterbox;Source: app_state.hpp
letterbox(std::make_unique<features::letterbox::LetterboxState>()),Source: app_state.cpp
设计意图:Pimpl 式持有让 app_state.hpp 无需包含 letterbox/state.hpp 的完整定义,抑制头文件级联编译依赖;同时保持生命周期归属清晰(由 AppState 独占管理)。
切换时序
国际化文案
| 键 | zh-CN | en-US |
|---|---|---|
menu.letterbox_toggle | 黑边模式 | Letterbox |
Sources:
配置选项
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
features.letterbox.enabled | bool | 未见默认值(未在本次采证中核验) | 持久化的信箱模式开关;初始化时一次性灌入 LetterboxState::enabled |
命令 letterbox.toggle | — | — | 菜单可触发命令,i18n 键 menu.letterbox_toggle |
说明:设置持久化(settings 的加载/保存/默认值定义)属于 settings 模块职责,本页仅记录与 letterbox 相关的这一个字段。
API 参考
features::letterbox::toggle_letterbox(state)
- 说明:切换信箱模式启用状态的用例入口,由
letterbox.toggle命令回调调用。 - 参数:
state—AppState的引用(从回调中[&state]捕获获得;具体形参类型未在本次采证中核验)。 - 返回值:未核验(回调中未使用返回值,推断为
void)。 - 副作用:修改
LetterboxState::enabled;调用方需自行请求重绘(见命令回调中的request_repaint)。
features::letterbox::LetterboxState
- 说明:信箱模式运行时状态结构体,由
AppState以unique_ptr独占持有。 - 已核验字段:
enabled(bool,见 initializer.cpp 中的赋值)。 - 其余字段:未在本次采证中核验。
命令 letterbox.toggle
- id:
letterbox.toggle - i18n_key:
menu.letterbox_toggle - 行为:调用
toggle_letterbox后调用ui::floating_window::request_repaint。 - 注册位置:
src/core/commands/builtin.cpp的register_command调用。
故障模式、边界与并发
依据已核验源码可确认的行为:
- 状态与画面不同步:
request_repaint是命令回调中的显式步骤。任何新代码路径若直接修改LetterboxState::enabled而不调用request_repaint,将出现"状态已变、画面未更新"的可见 bug。这是本能力最典型的故障模式。 - 初始化竞态:
initializer.cpp的同步发生在初始化阶段(一次性),之后运行期内的设置修改如何回写到 letterbox 状态(或是否需要重启/重新初始化才生效)未在本次采证中核验。 - 并发:命令回调与渲染均围绕共享
AppState。是否所有访问都发生在同一线程(例如 Win32 消息循环线程)未在本次采证中核验;从[&state]直接捕获引用的模式看,至少命令回调假定 AppState 在其执行期间稳定存活。 - 持久化一致性:
settings->raw.features.letterbox.enabled仅在启动时单向流入运行时状态;本次采证未发现 toggle 后反向写回 settings 的证据(是否存在自动持久化未核验)。
扩展点
- 新增视觉开关:可完全复刻 letterbox 的三步模式——(1) 在特性状态结构体中加入布尔字段;(2) 在
builtin.cpp用register_command注册新命令并绑定 i18n 键;(3) 回调中修改状态并调用request_repaint。 - 新增文案:在
src/locales/*.json中补齐对应menu.*键即可获得多语言菜单项。 - 渲染侧定制:
preview/shaders.hpp与preview/rendering.cpp是实现黑边/裁剪等视觉效果的落点(内部细节未在本次采证中核验)。
相关链接
- AGENTS.md —
core::*/features::*/ui::*三层职责划分 - builtin.cpp —
letterbox.toggle命令注册 - initializer.cpp — 启动时同步 letterbox 状态
- app_state.hpp —
LetterboxState前向声明与持有 - src/features/letterbox/ — letterbox 模块源码目录
- src/features/preview/ — preview 模块源码目录