Skip to main content

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}