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 fn all_menus(&self) -> Vec<Menu> {
14 self.menus
15 .menus
16 .iter()
17 .chain(self.menu_state.plugin_menus.iter())
18 .cloned()
19 .map(|mut menu| {
20 menu.expand_dynamic_items(&self.menu_state.themes_dir);
21 menu
22 })
23 .collect()
24 }
25
26 pub fn handle_menu_activate(&mut self) {
29 if !self.menu_bar_visible {
31 self.menu_bar_visible = true;
32 self.menu_bar_auto_shown = true;
33 }
34 self.on_editor_focus_lost();
35 self.menu_state.open_menu(0);
36 }
37
38 pub fn close_menu_with_auto_hide(&mut self) {
41 self.menu_state.close_menu();
42 if self.menu_bar_auto_shown {
43 self.menu_bar_visible = false;
44 self.menu_bar_auto_shown = false;
45 }
46 }
47
48 pub fn handle_menu_close(&mut self) {
51 self.close_menu_with_auto_hide();
52 }
53
54 pub fn handle_menu_left(&mut self) {
56 if !self.menu_state.close_submenu() {
57 let all_menus = self.all_menus();
58 self.menu_state.prev_menu(&all_menus);
59 }
60 }
61
62 pub fn handle_menu_right(&mut self) {
64 let all_menus = self.all_menus();
65 if !self.menu_state.open_submenu(&all_menus) {
66 self.menu_state.next_menu(&all_menus);
67 }
68 }
69
70 pub fn handle_menu_up(&mut self) {
72 if let Some(active_idx) = self.menu_state.active_menu {
73 let all_menus = self.all_menus();
74 if let Some(menu) = all_menus.get(active_idx) {
75 self.menu_state.prev_item(menu);
76 }
77 }
78 }
79
80 pub fn handle_menu_down(&mut self) {
82 if let Some(active_idx) = self.menu_state.active_menu {
83 let all_menus = self.all_menus();
84 if let Some(menu) = all_menus.get(active_idx) {
85 self.menu_state.next_item(menu);
86 }
87 }
88 }
89
90 pub fn handle_menu_execute(&mut self) -> Option<Action> {
94 let all_menus = self.all_menus();
95
96 if self.menu_state.is_highlighted_submenu(&all_menus) {
98 self.menu_state.open_submenu(&all_menus);
99 return None;
100 }
101
102 use crate::view::ui::context_keys;
104 self.menu_state
105 .context
106 .set(context_keys::HAS_SELECTION, self.has_active_selection())
107 .set(
108 context_keys::FILE_EXPLORER_FOCUSED,
109 self.key_context == crate::input::keybindings::KeyContext::FileExplorer,
110 );
111
112 if let Some((action_name, args)) = self.menu_state.get_highlighted_action(&all_menus) {
113 self.close_menu_with_auto_hide();
115
116 if let Some(action) = Action::from_str(&action_name, &args) {
118 Some(action)
119 } else {
120 Some(Action::PluginAction(action_name))
122 }
123 } else {
124 None
125 }
126 }
127
128 pub fn handle_menu_open(&mut self, menu_name: &str) {
131 if !self.menu_bar_visible {
133 self.menu_bar_visible = true;
134 self.menu_bar_auto_shown = true;
135 }
136 self.on_editor_focus_lost();
137
138 let all_menus = self.all_menus();
139 for (idx, menu) in all_menus.iter().enumerate() {
140 if menu.match_id().eq_ignore_ascii_case(menu_name) {
142 self.menu_state.open_menu(idx);
143 break;
144 }
145 }
146 }
147
148 pub(crate) fn compute_menu_dropdown_hover(
151 &self,
152 col: u16,
153 row: u16,
154 menu_index: usize,
155 ) -> Option<HoverTarget> {
156 let menu_layout = self.cached_layout.menu_layout.as_ref()?;
157
158 if let Some((depth, item_idx)) = menu_layout.submenu_item_at(col, row) {
160 return Some(HoverTarget::SubmenuItem(depth, item_idx));
161 }
162
163 if let Some(item_idx) = menu_layout.item_at(col, row) {
165 return Some(HoverTarget::MenuDropdownItem(menu_index, item_idx));
166 }
167
168 None
169 }
170
171 pub(crate) fn handle_menu_dropdown_click(
175 &mut self,
176 col: u16,
177 row: u16,
178 menu: &Menu,
179 ) -> AnyhowResult<Option<AnyhowResult<()>>> {
180 use crate::view::ui::menu::MenuHit;
181
182 let menu_layout = match &self.cached_layout.menu_layout {
183 Some(layout) => layout.clone(),
184 None => return Ok(None),
185 };
186
187 let hit = match menu_layout.hit_test(col, row) {
189 Some(MenuHit::DropdownItem(item_idx)) => (0, item_idx),
190 Some(MenuHit::SubmenuItem { depth, index }) => (depth, index),
191 _ => return Ok(None), };
193
194 let (depth, item_idx) = hit;
195
196 let items = if depth == 0 {
198 menu.items.clone()
200 } else {
201 let mut current_items = menu.items.clone();
203 for d in 0..depth {
204 if d < self.menu_state.submenu_path.len() {
205 let submenu_idx = self.menu_state.submenu_path[d];
206 match current_items.get(submenu_idx) {
207 Some(MenuItem::Submenu { items, .. }) => {
208 current_items = items.clone();
209 }
210 Some(MenuItem::DynamicSubmenu { source, .. }) => {
211 current_items =
212 generate_dynamic_items(source, &self.menu_state.themes_dir);
213 }
214 _ => return Ok(Some(Ok(()))),
215 }
216 } else {
217 return Ok(Some(Ok(())));
218 }
219 }
220 current_items
221 };
222
223 let Some(item) = items.get(item_idx) else {
224 return Ok(Some(Ok(())));
225 };
226
227 match item {
229 MenuItem::Separator { .. } | MenuItem::Label { .. } => {
230 Ok(Some(Ok(())))
232 }
233 MenuItem::Submenu {
234 items: submenu_items,
235 ..
236 } => {
237 self.menu_state.submenu_path.truncate(depth);
239 if !submenu_items.is_empty() {
240 self.menu_state.submenu_path.push(item_idx);
241 self.menu_state.highlighted_item = Some(0);
242 }
243 Ok(Some(Ok(())))
244 }
245 MenuItem::DynamicSubmenu { source, .. } => {
246 self.menu_state.submenu_path.truncate(depth);
248 let generated = generate_dynamic_items(source, &self.menu_state.themes_dir);
249 if !generated.is_empty() {
250 self.menu_state.submenu_path.push(item_idx);
251 self.menu_state.highlighted_item = Some(0);
252 }
253 Ok(Some(Ok(())))
254 }
255 MenuItem::Action { action, args, .. } => {
256 let action_name = action.clone();
258 let action_args = args.clone();
259
260 self.close_menu_with_auto_hide();
261
262 if let Some(action) = Action::from_str(&action_name, &action_args) {
263 return Ok(Some(self.handle_action(action)));
264 }
265 Ok(Some(Ok(())))
266 }
267 }
268 }
269}