mirror of
https://github.com/Zedfrigg/ironbar.git
synced 2025-09-16 11:46:58 +02:00
feat: add menu
module
Adds a new Menu module which allows users to create XDG or custom menus that open after clicking on a button. Resolves #534 Co-authored-by: Jake Stanger <mail@jstanger.dev>
This commit is contained in:
parent
e99a04923d
commit
96e10fe139
14 changed files with 1419 additions and 6 deletions
271
src/modules/menu/config.rs
Normal file
271
src/modules/menu/config.rs
Normal file
|
@ -0,0 +1,271 @@
|
|||
use crate::config::{CommonConfig, TruncateMode};
|
||||
use crate::modules::menu::{MenuEntry, XdgSection};
|
||||
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<String>,
|
||||
|
||||
/// XDG categories the associated submenu should contain.
|
||||
#[serde(default)]
|
||||
pub categories: Vec<String>,
|
||||
}
|
||||
|
||||
/// 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<String>,
|
||||
|
||||
/// 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<MenuConfig>,
|
||||
|
||||
/// 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<MenuConfig>,
|
||||
|
||||
/// Items to add to the end of the main menu.
|
||||
///
|
||||
/// **Default**: `[]`
|
||||
#[serde(default)]
|
||||
pub(super) end: Vec<MenuConfig>,
|
||||
|
||||
/// 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<i32>,
|
||||
|
||||
/// 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<i32>,
|
||||
|
||||
/// Label to show on the menu button on the bar.
|
||||
///
|
||||
/// **Default**: `≡`
|
||||
#[serde(default = "default_menu_popup_label")]
|
||||
pub(super) label: Option<String>,
|
||||
|
||||
/// Icon to show on the menu button on the bar.
|
||||
///
|
||||
/// **Default**: `null`
|
||||
#[serde(default)]
|
||||
pub(super) label_icon: Option<String>,
|
||||
|
||||
/// 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<CommonConfig>,
|
||||
}
|
||||
|
||||
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()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn default_menu() -> Vec<MenuConfig> {
|
||||
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<String> {
|
||||
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<MenuConfig>,
|
||||
sections_by_cat: &mut IndexMap<String, Vec<String>>,
|
||||
) -> IndexMap<String, MenuEntry> {
|
||||
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()
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue