Skip to main content

win_context_menu/
menu_items.rs

1//! Data types representing context menu items and user selections.
2
3/// A single item from a context menu.
4///
5/// Obtained via [`ContextMenu::enumerate`](crate::ContextMenu::enumerate) or
6/// as part of a [`SelectedItem`].
7#[derive(Debug, Clone, PartialEq, Eq)]
8#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
9pub struct MenuItem {
10    /// The raw command ID assigned by the shell handler.
11    pub id: u32,
12    /// Display label with accelerator characters (`&`) stripped.
13    pub label: String,
14    /// The verb/command string if available (e.g. `"open"`, `"delete"`,
15    /// `"properties"`). Not all items expose a verb.
16    pub command_string: Option<String>,
17    /// `true` if this item is a visual separator line.
18    pub is_separator: bool,
19    /// `true` if this item is disabled / grayed out.
20    pub is_disabled: bool,
21    /// `true` if this item has a check mark.
22    pub is_checked: bool,
23    /// `true` if this is the default (bold) item.
24    pub is_default: bool,
25    /// Sub-menu items, if this item opens a submenu.
26    pub submenu: Option<Vec<MenuItem>>,
27}
28
29impl MenuItem {
30    pub(crate) fn separator() -> Self {
31        Self {
32            id: 0,
33            label: String::new(),
34            command_string: None,
35            is_separator: true,
36            is_disabled: false,
37            is_checked: false,
38            is_default: false,
39            submenu: None,
40        }
41    }
42}
43
44impl std::fmt::Display for MenuItem {
45    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
46        if self.is_separator {
47            write!(f, "---separator---")
48        } else {
49            write!(f, "{}", self.label)?;
50            if let Some(ref cmd) = self.command_string {
51                write!(f, " [{}]", cmd)?;
52            }
53            Ok(())
54        }
55    }
56}
57
58/// Boxed closure that executes a context menu command.
59pub(crate) type Invoker = Box<dyn FnOnce(Option<InvokeParams>) -> crate::error::Result<()>>;
60
61/// A menu item that was selected by the user via
62/// [`ContextMenu::show`](crate::ContextMenu::show) or
63/// [`ContextMenu::show_at`](crate::ContextMenu::show_at).
64///
65/// Call [`execute`](SelectedItem::execute) to run the associated shell command,
66/// or inspect [`menu_item`](SelectedItem::menu_item) to decide first.
67///
68/// The `SelectedItem` keeps the hidden helper window alive until it is dropped
69/// or consumed by [`execute`](SelectedItem::execute), ensuring the owner HWND
70/// remains valid for commands that display UI (e.g., Properties dialog).
71pub struct SelectedItem {
72    pub(crate) menu_item: MenuItem,
73    pub(crate) command_id: u32,
74    pub(crate) invoker: Option<Invoker>,
75    /// Prevent the hidden window from being destroyed before execute().
76    pub(crate) _hidden_window: Option<crate::hidden_window::HiddenWindow>,
77}
78
79impl SelectedItem {
80    /// Get the menu item metadata (label, verb, flags, etc.).
81    pub fn menu_item(&self) -> &MenuItem {
82        &self.menu_item
83    }
84
85    /// Get the raw command ID that was returned by `TrackPopupMenu`.
86    pub fn command_id(&self) -> u32 {
87        self.command_id
88    }
89
90    /// Execute the selected command with default parameters.
91    ///
92    /// This is equivalent to the user clicking the menu item in Explorer.
93    /// Consumes `self` because a command can only be invoked once.
94    pub fn execute(self) -> crate::error::Result<()> {
95        if let Some(invoker) = self.invoker {
96            invoker(None)
97        } else {
98            Ok(())
99        }
100    }
101
102    /// Execute the selected command with custom parameters.
103    ///
104    /// Use this to override the working directory or window show state.
105    pub fn execute_with(self, params: InvokeParams) -> crate::error::Result<()> {
106        if let Some(invoker) = self.invoker {
107            invoker(Some(params))
108        } else {
109            Ok(())
110        }
111    }
112}
113
114impl std::fmt::Debug for SelectedItem {
115    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
116        f.debug_struct("SelectedItem")
117            .field("menu_item", &self.menu_item)
118            .field("command_id", &self.command_id)
119            .finish_non_exhaustive()
120    }
121}
122
123/// Parameters for customizing command invocation.
124///
125/// Pass to [`SelectedItem::execute_with`] to override defaults.
126#[derive(Debug, Clone, Default)]
127pub struct InvokeParams {
128    /// Working directory for the command. Defaults to the item's parent folder.
129    pub directory: Option<String>,
130    /// Window show command (`SW_SHOW`, `SW_HIDE`, etc.). Defaults to
131    /// `SW_SHOWNORMAL`.
132    pub show_cmd: Option<i32>,
133}