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}