fresh_core/menu.rs
1use schemars::JsonSchema;
2use serde::{Deserialize, Serialize};
3use std::collections::HashMap;
4use ts_rs::TS;
5
6/// A menu item (action, separator, or submenu)
7#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq, TS)]
8#[ts(export)]
9#[serde(untagged)]
10pub enum MenuItem {
11 /// A separator line
12 Separator { separator: bool },
13 /// An action item
14 Action {
15 label: String,
16 action: String,
17 #[serde(default)]
18 #[ts(type = "Record<string, any>")]
19 args: HashMap<String, serde_json::Value>,
20 #[serde(default)]
21 when: Option<String>,
22 /// Checkbox state condition (e.g., "line_numbers", "line_wrap")
23 #[serde(default)]
24 checkbox: Option<String>,
25 },
26 /// A submenu (for future extensibility)
27 Submenu { label: String, items: Vec<Self> },
28 /// A dynamic submenu whose items are generated at runtime
29 /// The `source` field specifies what to generate (e.g., "themes")
30 DynamicSubmenu { label: String, source: String },
31 /// A disabled info label (no action)
32 Label { info: String },
33}
34
35/// A top-level menu in the menu bar
36#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq, TS)]
37#[ts(export)]
38pub struct Menu {
39 /// Internal identifier for the menu (used for keybinding matching).
40 /// This should NOT be translated - use English names like "File", "Edit".
41 /// If not set, the label is used for matching (for backward compatibility).
42 #[serde(default, skip_serializing_if = "Option::is_none")]
43 pub id: Option<String>,
44 /// Display label for the menu (can be translated)
45 pub label: String,
46 /// Menu items (actions, separators, or submenus)
47 pub items: Vec<MenuItem>,
48 /// Context condition for menu visibility (e.g., "file_explorer_focused")
49 /// If set, the menu is only shown when this condition evaluates to true
50 #[serde(default, skip_serializing_if = "Option::is_none")]
51 pub when: Option<String>,
52}