fresh/app/
menu_actions.rs1use super::Editor;
6use crate::app::types::HoverTarget;
7use crate::config::{generate_dynamic_items, Menu, MenuExt, MenuItem};
8use crate::input::keybindings::Action;
9use anyhow::Result as AnyhowResult;
10
11impl Editor {
12 pub fn set_menus(&mut self, menus: crate::config::MenuConfig) {
17 self.menus = menus;
18 self.expanded_menus_cache.invalidate();
19 }
20
21 pub fn with_menu_by_label<F, R>(&mut self, label: &str, f: F) -> Option<R>
25 where
26 F: FnOnce(&mut Menu) -> R,
27 {
28 if let Some(idx) = self.menus.menus.iter().position(|m| m.label == label) {
29 let r = f(&mut self.menus.menus[idx]);
30 self.expanded_menus_cache.invalidate();
31 return Some(r);
32 }
33 if let Some(idx) = self
34 .menu_state
35 .plugin_menus
36 .iter()
37 .position(|m| m.label == label)
38 {
39 let r = f(&mut self.menu_state.plugin_menus[idx]);
40 self.expanded_menus_cache.invalidate();
41 return Some(r);
42 }
43 None
44 }
45
46 fn all_menus(&self) -> Vec<Menu> {
48 self.menus
49 .menus
50 .iter()
51 .chain(self.menu_state.plugin_menus.iter())
52 .cloned()
53 .map(|mut menu| {
54 menu.expand_dynamic_items(&self.menu_state.themes_dir);
55 menu
56 })
57 .collect()
58 }
59
60 pub fn handle_menu_activate(&mut self) {
63 if !self.menu_bar_visible {
65 self.menu_bar_visible = true;
66 self.menu_bar_auto_shown = true;
67 }
68 self.on_editor_focus_lost();
69 self.menu_state.open_menu(0);
70 }
71
72 pub fn close_menu_with_auto_hide(&mut self) {
75 self.menu_state.close_menu();
76 if self.menu_bar_auto_shown {
77 self.menu_bar_visible = false;
78 self.menu_bar_auto_shown = false;
79 }
80 }
81
82 pub fn handle_menu_close(&mut self) {
85 self.close_menu_with_auto_hide();
86 }
87
88 pub fn handle_menu_left(&mut self) {
90 if !self.menu_state.close_submenu() {
91 let all_menus = self.all_menus();
92 self.menu_state.prev_menu(&all_menus);
93 }
94 }
95
96 pub fn handle_menu_right(&mut self) {
98 let all_menus = self.all_menus();
99 if !self.menu_state.open_submenu(&all_menus) {
100 self.menu_state.next_menu(&all_menus);
101 }
102 }
103
104 pub fn handle_menu_up(&mut self) {
106 if let Some(active_idx) = self.menu_state.active_menu {
107 let all_menus = self.all_menus();
108 if let Some(menu) = all_menus.get(active_idx) {
109 self.menu_state.prev_item(menu);
110 }
111 }
112 }
113
114 pub fn handle_menu_down(&mut self) {
116 if let Some(active_idx) = self.menu_state.active_menu {
117 let all_menus = self.all_menus();
118 if let Some(menu) = all_menus.get(active_idx) {
119 self.menu_state.next_item(menu);
120 }
121 }
122 }
123
124 pub fn handle_menu_execute(&mut self) -> Option<Action> {
128 let all_menus = self.all_menus();
129
130 if self.menu_state.is_highlighted_submenu(&all_menus) {
132 self.menu_state.open_submenu(&all_menus);
133 return None;
134 }
135
136 use crate::view::ui::context_keys;
138 self.menu_state
139 .context
140 .set(context_keys::HAS_SELECTION, self.has_active_selection())
141 .set(
142 context_keys::FILE_EXPLORER_FOCUSED,
143 self.key_context == crate::input::keybindings::KeyContext::FileExplorer,
144 );
145
146 if let Some((action_name, args)) = self.menu_state.get_highlighted_action(&all_menus) {
147 self.close_menu_with_auto_hide();
149
150 if let Some(action) = Action::from_str(&action_name, &args) {
152 Some(action)
153 } else {
154 Some(Action::PluginAction(action_name))
156 }
157 } else {
158 None
159 }
160 }
161
162 pub fn handle_menu_open(&mut self, menu_name: &str) {
165 if !self.menu_bar_visible {
167 self.menu_bar_visible = true;
168 self.menu_bar_auto_shown = true;
169 }
170 self.on_editor_focus_lost();
171
172 let all_menus = self.all_menus();
173 for (idx, menu) in all_menus.iter().enumerate() {
174 if menu.match_id().eq_ignore_ascii_case(menu_name) {
176 self.menu_state.open_menu(idx);
177 break;
178 }
179 }
180 }
181
182 pub(crate) fn compute_menu_dropdown_hover(
185 &self,
186 col: u16,
187 row: u16,
188 menu_index: usize,
189 ) -> Option<HoverTarget> {
190 let menu_layout = self.cached_layout.menu_layout.as_ref()?;
191
192 if let Some((depth, item_idx)) = menu_layout.submenu_item_at(col, row) {
194 return Some(HoverTarget::SubmenuItem(depth, item_idx));
195 }
196
197 if let Some(item_idx) = menu_layout.item_at(col, row) {
199 return Some(HoverTarget::MenuDropdownItem(menu_index, item_idx));
200 }
201
202 None
203 }
204
205 pub(crate) fn handle_menu_dropdown_click(
209 &mut self,
210 col: u16,
211 row: u16,
212 menu: &Menu,
213 ) -> AnyhowResult<Option<AnyhowResult<()>>> {
214 use crate::view::ui::menu::MenuHit;
215
216 let menu_layout = match &self.cached_layout.menu_layout {
217 Some(layout) => layout.clone(),
218 None => return Ok(None),
219 };
220
221 let hit = match menu_layout.hit_test(col, row) {
223 Some(MenuHit::DropdownItem(item_idx)) => (0, item_idx),
224 Some(MenuHit::SubmenuItem { depth, index }) => (depth, index),
225 _ => return Ok(None), };
227
228 let (depth, item_idx) = hit;
229
230 let items = if depth == 0 {
232 menu.items.clone()
234 } else {
235 let mut current_items = menu.items.clone();
237 for d in 0..depth {
238 if d < self.menu_state.submenu_path.len() {
239 let submenu_idx = self.menu_state.submenu_path[d];
240 match current_items.get(submenu_idx) {
241 Some(MenuItem::Submenu { items, .. }) => {
242 current_items = items.clone();
243 }
244 Some(MenuItem::DynamicSubmenu { source, .. }) => {
245 current_items =
246 generate_dynamic_items(source, &self.menu_state.themes_dir);
247 }
248 _ => return Ok(Some(Ok(()))),
249 }
250 } else {
251 return Ok(Some(Ok(())));
252 }
253 }
254 current_items
255 };
256
257 let Some(item) = items.get(item_idx) else {
258 return Ok(Some(Ok(())));
259 };
260
261 match item {
263 MenuItem::Separator { .. } | MenuItem::Label { .. } => {
264 Ok(Some(Ok(())))
266 }
267 MenuItem::Submenu {
268 items: submenu_items,
269 ..
270 } => {
271 self.menu_state.submenu_path.truncate(depth);
273 if !submenu_items.is_empty() {
274 self.menu_state.submenu_path.push(item_idx);
275 self.menu_state.highlighted_item = Some(0);
276 }
277 Ok(Some(Ok(())))
278 }
279 MenuItem::DynamicSubmenu { source, .. } => {
280 self.menu_state.submenu_path.truncate(depth);
282 let generated = generate_dynamic_items(source, &self.menu_state.themes_dir);
283 if !generated.is_empty() {
284 self.menu_state.submenu_path.push(item_idx);
285 self.menu_state.highlighted_item = Some(0);
286 }
287 Ok(Some(Ok(())))
288 }
289 MenuItem::Action { action, args, .. } => {
290 let action_name = action.clone();
292 let action_args = args.clone();
293
294 self.close_menu_with_auto_hide();
295
296 if let Some(action) = Action::from_str(&action_name, &action_args) {
297 return Ok(Some(self.handle_action(action)));
298 }
299 Ok(Some(Ok(())))
300 }
301 }
302 }
303}