use crate::config::{CommonConfig, TruncateMode}; use crate::modules::menu::{MenuEntry, XdgSection}; use crate::modules::launcher::default_launch_command; use indexmap::IndexMap; use serde::Deserialize; /// An individual entry in the main menu section. #[derive(Debug, Deserialize, Clone)] #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] #[serde(tag = "type", rename_all = "snake_case")] pub enum MenuConfig { /// Contains all applications matching the configured `categories`. XdgEntry(XdgEntry), /// Contains all applications not covered by `xdg_entry` categories. XdgOther, /// Individual shell command entry. Custom(CustomEntry), } #[derive(Debug, Deserialize, Clone)] #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] pub struct XdgEntry { /// Text to display on the button. #[serde(default)] pub label: String, /// Name of the image icon to show next to the label. #[serde(default)] pub icon: Option, /// XDG categories the associated submenu should contain. #[serde(default)] pub categories: Vec, } /// Individual shell command entry. #[derive(Debug, Deserialize, Clone)] #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] pub struct CustomEntry { /// Text to display on the button. #[serde(default)] pub label: String, /// Name of the image icon to show next to the label. /// /// **Default**: `null` pub icon: Option, /// Shell command to execute when the button is clicked. /// This is run using `sh -c`. #[serde(default)] pub on_click: String, } #[derive(Debug, Deserialize, Clone)] #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] pub struct MenuModule { /// Items to add to the start of the main menu. /// /// **Default**: `[]` #[serde(default)] pub(super) start: Vec, /// Items to add to the start of the main menu. /// /// By default, this shows a number of XDG entries /// that should cover all common applications. /// /// **Default**: See `examples/menu/default` #[serde(default = "default_menu")] pub(super) center: Vec, /// Items to add to the end of the main menu. /// /// **Default**: `[]` #[serde(default)] pub(super) end: Vec, /// Fixed height of the menu. /// /// When set, if the number of (sub)menu entries exceeds this value, /// a scrollbar will be shown. /// /// Leave null to resize dynamically. /// /// **Default**: `null` #[serde(default)] pub(super) height: Option, /// Fixed width of the menu. /// /// Can be used with `truncate` options /// to customise how item labels are truncated. /// /// **Default**: `null` #[serde(default)] pub(super) width: Option, /// Label to show on the menu button on the bar. /// /// **Default**: `≡` #[serde(default = "default_menu_popup_label")] pub(super) label: Option, /// Icon to show on the menu button on the bar. /// /// **Default**: `null` #[serde(default)] pub(super) label_icon: Option, /// Size of the `label_icon` image. #[serde(default = "default_menu_popup_icon_size")] pub(super) label_icon_size: i32, // -- common -- /// Truncate options to apply to (sub)menu item labels. /// /// See [truncate options](module-level-options#truncate-mode). /// /// **Default**: `Auto (end)` #[serde(default)] pub(super) truncate: TruncateMode, /// See [common options](module-level-options#common-options). #[serde(flatten)] pub common: Option, /// Command used to launch applications. /// /// **Default**: `gtk-launch` #[serde(default = "default_launch_command")] pub launch_command: String, } impl Default for MenuModule { fn default() -> Self { MenuModule { start: vec![], center: default_menu(), end: vec![], height: None, width: None, truncate: TruncateMode::default(), // max_label_length: default_length(), label: default_menu_popup_label(), label_icon: None, label_icon_size: default_menu_popup_icon_size(), common: Some(CommonConfig::default()), launch_command: default_launch_command(), } } } fn default_menu() -> Vec { vec![ MenuConfig::XdgEntry(XdgEntry { label: "Accessories".to_string(), icon: Some("accessories".to_string()), categories: vec![ "Accessibility".to_string(), "Core".to_string(), "Legacy".to_string(), "Utility".to_string(), ], }), MenuConfig::XdgEntry(XdgEntry { label: "Development".to_string(), icon: Some("applications-development".to_string()), categories: vec!["Development".to_string()], }), MenuConfig::XdgEntry(XdgEntry { label: "Education".to_string(), icon: Some("applications-education".to_string()), categories: vec!["Education".to_string()], }), MenuConfig::XdgEntry(XdgEntry { label: "Games".to_string(), icon: Some("applications-games".to_string()), categories: vec!["Game".to_string()], }), MenuConfig::XdgEntry(XdgEntry { label: "Graphics".to_string(), icon: Some("applications-graphics".to_string()), categories: vec!["Graphics".to_string()], }), MenuConfig::XdgEntry(XdgEntry { label: "Multimedia".to_string(), icon: Some("applications-multimedia".to_string()), categories: vec![ "Audio".to_string(), "Video".to_string(), "AudioVideo".to_string(), ], }), MenuConfig::XdgEntry(XdgEntry { label: "Network".to_string(), icon: Some("applications-internet".to_string()), categories: vec!["Network".to_string()], }), MenuConfig::XdgEntry(XdgEntry { label: "Office".to_string(), icon: Some("applications-office".to_string()), categories: vec!["Office".to_string()], }), MenuConfig::XdgEntry(XdgEntry { label: "Science".to_string(), icon: Some("applications-science".to_string()), categories: vec!["Science".to_string()], }), MenuConfig::XdgEntry(XdgEntry { label: "System".to_string(), icon: Some("applications-system".to_string()), categories: vec!["Emulator".to_string(), "System".to_string()], }), MenuConfig::XdgOther, MenuConfig::XdgEntry(XdgEntry { label: "Settings".to_string(), icon: Some("preferences-system".to_string()), categories: vec!["Settings".to_string(), "Screensaver".to_string()], }), ] } fn default_menu_popup_label() -> Option { Some("≡".to_string()) } const fn default_menu_popup_icon_size() -> i32 { 16 } pub const OTHER_LABEL: &str = "Other"; pub fn parse_config( section_config: Vec, sections_by_cat: &mut IndexMap>, ) -> IndexMap { section_config .into_iter() .map(|entry_config| match entry_config { MenuConfig::XdgEntry(entry) => { entry.categories.into_iter().for_each(|cat| { let existing = sections_by_cat.get_mut(&cat); if let Some(existing) = existing { existing.push(entry.label.clone()); } else { sections_by_cat.insert(cat, vec![entry.label.clone()]); } }); ( entry.label.clone(), MenuEntry::Xdg(XdgSection { label: entry.label, icon: entry.icon, applications: IndexMap::new(), }), ) } MenuConfig::XdgOther => ( OTHER_LABEL.to_string(), MenuEntry::Xdg(XdgSection { label: OTHER_LABEL.to_string(), icon: Some("applications-other".to_string()), applications: IndexMap::new(), }), ), MenuConfig::Custom(entry) => ( entry.label.clone(), MenuEntry::Custom(CustomEntry { icon: entry.icon, label: entry.label, on_click: entry.on_click, }), ), }) .collect() }