fresh_core/menu.rs
1use schemars::JsonSchema;
2use serde::{Deserialize, Serialize};
3use std::collections::HashMap;
4use ts_rs::TS;
5
6/// Menu state context — provides named boolean states for menu item conditions.
7///
8/// Both `when` conditions (controlling enabled/disabled state) and `checkbox`
9/// states (controlling checkmark display) look up values here. The editor
10/// computes these values each frame from its internal state and exposes the
11/// context to the GUI layer via the `GuiApplication` trait so that
12/// platform-native menus can reflect the same state as the TUI menu bar.
13#[derive(Debug, Clone, Default, PartialEq)]
14pub struct MenuContext {
15 states: HashMap<String, bool>,
16}
17
18impl MenuContext {
19 pub fn new() -> Self {
20 Self {
21 states: HashMap::new(),
22 }
23 }
24
25 /// Set a named boolean state.
26 pub fn set(&mut self, name: impl Into<String>, value: bool) -> &mut Self {
27 self.states.insert(name.into(), value);
28 self
29 }
30
31 /// Get a named boolean state (defaults to `false` if not set).
32 pub fn get(&self, name: &str) -> bool {
33 self.states.get(name).copied().unwrap_or(false)
34 }
35
36 /// Builder-style setter.
37 pub fn with(mut self, name: impl Into<String>, value: bool) -> Self {
38 self.set(name, value);
39 self
40 }
41}
42
43/// A menu item (action, separator, or submenu)
44#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq, TS)]
45#[ts(export)]
46#[serde(untagged)]
47pub enum MenuItem {
48 /// A separator line
49 Separator { separator: bool },
50 /// An action item
51 Action {
52 label: String,
53 action: String,
54 #[serde(default)]
55 #[ts(type = "Record<string, any>")]
56 args: HashMap<String, serde_json::Value>,
57 #[serde(default)]
58 when: Option<String>,
59 /// Checkbox state condition (e.g., "line_numbers", "line_wrap")
60 #[serde(default)]
61 checkbox: Option<String>,
62 },
63 /// A submenu (for future extensibility)
64 Submenu { label: String, items: Vec<Self> },
65 /// A dynamic submenu whose items are generated at runtime
66 /// The `source` field specifies what to generate (e.g., "themes")
67 DynamicSubmenu { label: String, source: String },
68 /// A disabled info label (no action)
69 Label { info: String },
70}
71
72/// A top-level menu in the menu bar
73#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq, TS)]
74#[ts(export)]
75pub struct Menu {
76 /// Internal identifier for the menu (used for keybinding matching).
77 /// This should NOT be translated - use English names like "File", "Edit".
78 /// If not set, the label is used for matching (for backward compatibility).
79 #[serde(default, skip_serializing_if = "Option::is_none")]
80 pub id: Option<String>,
81 /// Display label for the menu (can be translated)
82 pub label: String,
83 /// Menu items (actions, separators, or submenus)
84 pub items: Vec<MenuItem>,
85 /// Context condition for menu visibility (e.g., "file_explorer_focused")
86 /// If set, the menu is only shown when this condition evaluates to true
87 #[serde(default, skip_serializing_if = "Option::is_none")]
88 pub when: Option<String>,
89}