# 提供按钮预设

好的体验往往藏在细节里。当用户在飞块中为一个物理按键添加功能时，如果能“一步到位”，开箱自带精美的图标、贴切的标题和合理的默认参数，这种省心感会让你的插件格外受欢迎。

**按钮预设（Button Preset）**正是为此而生。它就像一套开箱即用的模板：用户从菜单中拖出一个预设按键，即可自动绑定好动作与视觉外观；当然，在创建完成后，用户依然可以自由微调。

---

## 1. 绑定动作与默认外观

在 `manifest.json` 的 `contributes.buttonPresets` 数组中添加预设项，将 `actionId` 指向你在插件中实现的动作 ID，并在 `button` 中定制初始视觉：

```json
{
  "contributes": {
    "buttonPresets": [
      {
        "id": "hello-button",
        "title": "问候按钮",
        "actionId": "hello",
        "button": {
          "title": "打个招呼",
          "icon": {
            "kind": "emoji",
            "text": "👋"
          }
        }
      }
    ]
  }
}
```

> 💡 **清单组织提示**：上面的代码片段基于[创建第一个插件](./quickstart.md)中的 `hello` 动作。在实际项目中，只需将 `buttonPresets` 追加到已有清单中，原有的动作声明依然完整保留。

---

## 2. 动态生成按键名称（titleFrom）

在许多实用工具中，固定的标题显得过于呆板。例如，“启动应用程序”按键如果能自动显示为“微信”或“Photoshop”，体验就会流畅得多。

飞块支持通过 `button.titleFrom` 让宿主根据用户填写的特定参数动态推导按键名称：

- **从参数智能解析**：例如配置 `titleFrom: "application-name"` 时，飞块会自动解析用户选中的本地应用路径或可执行文件名，提取出精简美观的软件名称；
- **从文本模板填充**：结合用户输入的变量格式化按钮标题。

> 📚 **深入参考**：更多支持的动态转换模式与图标格式（如内置 Emoji、官方矢量图标库及自定义 SVG），请参阅 [ButtonAppearance 参考文档](../reference/presets.md#buttonappearance)。

---

## 3. 根据实时状态切换外观与动作（多状态按键）

对于开关类设备（如静音切换、麦克风静音、智能插座等），我们通常希望按键能够真实反映当前设备状态，并能在点击时触发相反的操作。

如果你的插件已发布了全局枚举状态（详见[发布状态](./states.md)），预设可以通过 `multiState.sourceId` 订阅该来源，为不同的状态值定义专属的外观与点击动作。官方的“状态开关”示例正是如此实现的：



```json
{
  "apiVersion": 1,
  "id": "example.state-button",
  "name": "状态按钮",
  "version": "1.0.0",
  "description": "状态按钮教学示例",
  "hostPlatforms": [
    "windows",
    "macos",
    "linux"
  ],
  "main": "index.js",
  "permissions": [],
  "contributes": {
    "actions": [
      {
        "id": "enable",
        "title": "开启",
        "argsSchema": {
          "type": "object",
          "properties": {},
          "required": [],
          "additionalProperties": false
        },
        "outputSchema": {
          "type": "object",
          "properties": {
            "status": {
              "type": "string"
            }
          },
          "required": [
            "status"
          ],
          "additionalProperties": false
        },
        "titleKey": "actions.enable.title"
      },
      {
        "id": "disable",
        "title": "关闭",
        "argsSchema": {
          "type": "object",
          "properties": {},
          "required": [],
          "additionalProperties": false
        },
        "outputSchema": {
          "type": "object",
          "properties": {
            "status": {
              "type": "string"
            }
          },
          "required": [
            "status"
          ],
          "additionalProperties": false
        },
        "titleKey": "actions.disable.title"
      }
    ],
    "stateSources": [
      {
        "id": "status",
        "title": "开关状态",
        "description": "教学开关，不修改实际系统静音",
        "scope": "global",
        "visibility": "global",
        "readAccess": {
          "workflows": true,
          "widgets": true
        },
        "writeAccess": "owner-plugin",
        "lifetime": "plugin",
        "schema": {
          "type": "string",
          "enum": [
            "on",
            "off"
          ],
          "enumLabels": [
            "已开启",
            "已关闭"
          ]
        },
        "titleKey": "stateSources.status.title",
        "descriptionKey": "stateSources.status.description"
      }
    ],
    "buttonPresets": [
      {
        "id": "switch",
        "title": "教学状态开关",
        "actionId": "enable",
        "button": {
          "title": "开关",
          "icon": {
            "kind": "emoji",
            "text": "💡"
          },
          "size": "1x1",
          "titleKey": "buttonPresets.switch.button.title"
        },
        "multiState": {
          "sourceId": "status",
          "states": [
            {
              "id": "on",
              "actionId": "disable",
              "button": {
                "title": "已开启",
                "icon": {
                  "kind": "emoji",
                  "text": "💡"
                },
                "titleKey": "buttonPresets.switch.multiState.states.on.button.title"
              }
            },
            {
              "id": "off",
              "actionId": "enable",
              "button": {
                "title": "已关闭",
                "icon": {
                  "kind": "emoji",
                  "text": "💤"
                },
                "titleKey": "buttonPresets.switch.multiState.states.off.button.title"
              }
            },
            {
              "id": "unknown",
              "button": {
                "title": "等待状态",
                "icon": {
                  "kind": "none"
                },
                "titleKey": "buttonPresets.switch.multiState.states.unknown.button.title"
              }
            }
          ]
        },
        "titleKey": "buttonPresets.switch.title"
      }
    ]
  },
  "i18n": {
    "defaultLocale": "zh-CN",
    "resources": {
      "zh-CN": "locales/zh-CN.json",
      "en-US": "locales/en-US.json"
    }
  },
  "nameKey": "plugin.name",
  "descriptionKey": "plugin.description"
}
```



**状态联动逻辑剖析：**
1. **状态为 `on` 时**：按键视觉高亮并显示“关闭”；按下该按键将触发 `disable` 动作。
2. **动作生效后**：后台逻辑发布状态为 `off`，按键界面立即自动响应并变更为“开启”；下次按下则触发 `enable` 动作。
3. **未就绪兜底（`unknown`）**：在插件刚启动或外部硬件连接尚未就绪时，状态处于未知阶段。预设通过 `unknown` 状态展示等待图标或灰度提示，让用户清晰分辨“当前正在加载中”与“设备已关闭”。

> 🔍 **本地轮换按键**：如果你的按键不需要读取外部硬件状态，仅仅是用户每按一下在本地循环切换（例如在 3 种预设模式间轮转），可使用无需后台的本地多状态模式。详细配置请参阅 [MultiState 清单参考](../reference/presets.md#multistate)。
