app_lib/
native_menu.rs

1//! Native application menu bar — labels from SQLite `app_i18n` (see `appFmt` keys `menu.*`).
2
3use std::collections::HashMap;
4use tauri::menu::*;
5use tauri::{AppHandle, Runtime};
6
7/// Rebuilds the full menu bar using merged strings for the active UI locale.
8pub fn build_native_menu_bar<R: Runtime>(
9    handle: &AppHandle<R>,
10    strings: &HashMap<String, String>,
11) -> Result<Menu<R>, tauri::Error> {
12    let t = |key: &str, fallback: &str| -> String {
13        strings
14            .get(key)
15            .map(|s| s.as_str())
16            .filter(|s| !s.is_empty())
17            .unwrap_or(fallback)
18            .to_string()
19    };
20
21    // App menu (macOS convention — first menu shows app name)
22    let about_title = t("menu.about", "About AUDIO_HAXOR");
23    let app_about = PredefinedMenuItem::about(handle, Some(about_title.as_str()), None)?;
24    let app_sep1 = PredefinedMenuItem::separator(handle)?;
25    let app_prefs = MenuItem::with_id(
26        handle,
27        "open_prefs_app",
28        t("menu.preferences", "Preferences..."),
29        true,
30        Some("CmdOrCtrl+,"),
31    )?;
32    let app_sep2 = PredefinedMenuItem::separator(handle)?;
33    let app_services = PredefinedMenuItem::services(handle, None)?;
34    let app_sep3 = PredefinedMenuItem::separator(handle)?;
35    let app_hide = PredefinedMenuItem::hide(handle, None)?;
36    let app_hide_others = PredefinedMenuItem::hide_others(handle, None)?;
37    let app_show_all = PredefinedMenuItem::show_all(handle, None)?;
38    let app_sep4 = PredefinedMenuItem::separator(handle)?;
39    let app_quit = PredefinedMenuItem::quit(handle, None)?;
40
41    let app_menu = Submenu::with_id_and_items(
42        handle,
43        "app",
44        t("menu.app", "AUDIO_HAXOR"),
45        true,
46        &[
47            &app_about,
48            &app_sep1,
49            &app_prefs,
50            &app_sep2,
51            &app_services,
52            &app_sep3,
53            &app_hide,
54            &app_hide_others,
55            &app_show_all,
56            &app_sep4,
57            &app_quit,
58        ],
59    )?;
60
61    // File menu
62    let scan_all = MenuItem::with_id(
63        handle,
64        "scan_all",
65        t("menu.scan_all", "Scan All"),
66        true,
67        Some("CmdOrCtrl+Shift+S"),
68    )?;
69    let stop_all = MenuItem::with_id(
70        handle,
71        "stop_all",
72        t("menu.stop_all", "Stop All"),
73        true,
74        Some("CmdOrCtrl+."),
75    )?;
76    let sep1 = PredefinedMenuItem::separator(handle)?;
77    let export_plugins = MenuItem::with_id(
78        handle,
79        "export_plugins",
80        t("menu.export_plugins", "Export Plugins..."),
81        true,
82        Some("CmdOrCtrl+E"),
83    )?;
84    let import_plugins = MenuItem::with_id(
85        handle,
86        "import_plugins",
87        t("menu.import_plugins", "Import Plugins..."),
88        true,
89        Some("CmdOrCtrl+I"),
90    )?;
91    let sep2 = PredefinedMenuItem::separator(handle)?;
92    let export_audio = MenuItem::with_id(
93        handle,
94        "export_audio",
95        t("menu.export_samples", "Export Samples..."),
96        true,
97        Some("CmdOrCtrl+Shift+E"),
98    )?;
99    let import_audio = MenuItem::with_id(
100        handle,
101        "import_audio",
102        t("menu.import_samples", "Import Samples..."),
103        true,
104        Some("CmdOrCtrl+Shift+I"),
105    )?;
106    let sep3 = PredefinedMenuItem::separator(handle)?;
107    let export_daw = MenuItem::with_id(
108        handle,
109        "export_daw",
110        t("menu.export_daw", "Export DAW Projects..."),
111        true,
112        Some("CmdOrCtrl+Shift+O"),
113    )?;
114    let import_daw = MenuItem::with_id(
115        handle,
116        "import_daw",
117        t("menu.import_daw", "Import DAW Projects..."),
118        true,
119        Some("CmdOrCtrl+Shift+J"),
120    )?;
121    let sep4 = PredefinedMenuItem::separator(handle)?;
122    let export_presets = MenuItem::with_id(
123        handle,
124        "export_presets",
125        t("menu.export_presets", "Export Presets..."),
126        true,
127        Some("CmdOrCtrl+Shift+Y"),
128    )?;
129    let import_presets = MenuItem::with_id(
130        handle,
131        "import_presets",
132        t("menu.import_presets", "Import Presets..."),
133        true,
134        Some("CmdOrCtrl+Shift+Z"),
135    )?;
136    let file_menu = Submenu::with_id_and_items(
137        handle,
138        "file",
139        t("menu.file", "File"),
140        true,
141        &[
142            &scan_all,
143            &stop_all,
144            &sep1,
145            &export_plugins,
146            &import_plugins,
147            &sep2,
148            &export_audio,
149            &import_audio,
150            &sep3,
151            &export_daw,
152            &import_daw,
153            &sep4,
154            &export_presets,
155            &import_presets,
156        ],
157    )?;
158
159    // Edit menu
160    let edit_undo = PredefinedMenuItem::undo(handle, None)?;
161    let edit_redo = PredefinedMenuItem::redo(handle, None)?;
162    let edit_sep1 = PredefinedMenuItem::separator(handle)?;
163    let edit_cut = PredefinedMenuItem::cut(handle, None)?;
164    let edit_copy = PredefinedMenuItem::copy(handle, None)?;
165    let edit_paste = PredefinedMenuItem::paste(handle, None)?;
166    let edit_select_all = PredefinedMenuItem::select_all(handle, None)?;
167    let edit_sep2 = PredefinedMenuItem::separator(handle)?;
168    let find = MenuItem::with_id(
169        handle,
170        "find",
171        t("menu.find", "Find..."),
172        true,
173        Some("CmdOrCtrl+F"),
174    )?;
175
176    let edit_menu = Submenu::with_id_and_items(
177        handle,
178        "edit",
179        t("menu.edit", "Edit"),
180        true,
181        &[
182            &edit_undo,
183            &edit_redo,
184            &edit_sep1,
185            &edit_cut,
186            &edit_copy,
187            &edit_paste,
188            &edit_select_all,
189            &edit_sep2,
190            &find,
191        ],
192    )?;
193
194    // Scan menu
195    let scan_plugins = MenuItem::with_id(
196        handle,
197        "scan_plugins",
198        t("menu.scan_plugins", "Scan Plugins"),
199        true,
200        Some("CmdOrCtrl+Shift+P"),
201    )?;
202    let scan_audio = MenuItem::with_id(
203        handle,
204        "scan_audio",
205        t("menu.scan_samples", "Scan Samples"),
206        true,
207        Some("CmdOrCtrl+Shift+A"),
208    )?;
209    let scan_daw = MenuItem::with_id(
210        handle,
211        "scan_daw",
212        t("menu.scan_daw", "Scan DAW Projects"),
213        true,
214        Some("CmdOrCtrl+Shift+D"),
215    )?;
216    let scan_presets = MenuItem::with_id(
217        handle,
218        "scan_presets",
219        t("menu.scan_presets", "Scan Presets"),
220        true,
221        Some("CmdOrCtrl+Shift+R"),
222    )?;
223    let scan_sep = PredefinedMenuItem::separator(handle)?;
224    let check_updates = MenuItem::with_id(
225        handle,
226        "check_updates",
227        t("menu.check_updates", "Check Updates"),
228        true,
229        Some("CmdOrCtrl+U"),
230    )?;
231
232    let scan_menu = Submenu::with_id_and_items(
233        handle,
234        "scan",
235        t("menu.scan", "Scan"),
236        true,
237        &[
238            &scan_plugins,
239            &scan_audio,
240            &scan_daw,
241            &scan_presets,
242            &scan_sep,
243            &check_updates,
244        ],
245    )?;
246
247    // View menu
248    let tab_plugins = MenuItem::with_id(
249        handle,
250        "tab_plugins",
251        t("menu.tab_plugins", "Plugins"),
252        true,
253        Some("CmdOrCtrl+1"),
254    )?;
255    let tab_samples = MenuItem::with_id(
256        handle,
257        "tab_samples",
258        t("menu.tab_samples", "Samples"),
259        true,
260        Some("CmdOrCtrl+2"),
261    )?;
262    let tab_daw = MenuItem::with_id(
263        handle,
264        "tab_daw",
265        t("menu.tab_daw", "DAW Projects"),
266        true,
267        Some("CmdOrCtrl+3"),
268    )?;
269    let tab_presets = MenuItem::with_id(
270        handle,
271        "tab_presets",
272        t("menu.tab_presets", "Presets"),
273        true,
274        Some("CmdOrCtrl+4"),
275    )?;
276    let tab_favorites = MenuItem::with_id(
277        handle,
278        "tab_favorites",
279        t("menu.tab_favorites", "Favorites"),
280        true,
281        Some("CmdOrCtrl+5"),
282    )?;
283    let tab_notes = MenuItem::with_id(
284        handle,
285        "tab_notes",
286        t("menu.tab_notes", "Notes"),
287        true,
288        Some("CmdOrCtrl+6"),
289    )?;
290    let tab_history = MenuItem::with_id(
291        handle,
292        "tab_history",
293        t("menu.tab_history", "History"),
294        true,
295        Some("CmdOrCtrl+7"),
296    )?;
297    let tab_settings = MenuItem::with_id(
298        handle,
299        "tab_settings",
300        t("menu.tab_settings", "Settings"),
301        true,
302        Some("CmdOrCtrl+8"),
303    )?;
304    let tab_files = MenuItem::with_id(
305        handle,
306        "tab_files",
307        t("menu.tab_files", "Files"),
308        true,
309        Some("CmdOrCtrl+9"),
310    )?;
311    let tab_audio_engine = MenuItem::with_id(
312        handle,
313        "tab_audio_engine",
314        t("menu.tab_audio_engine", "Audio Engine"),
315        true,
316        Some("CmdOrCtrl+F6"),
317    )?;
318    let view_sep = PredefinedMenuItem::separator(handle)?;
319    let toggle_theme = MenuItem::with_id(
320        handle,
321        "toggle_theme",
322        t("menu.toggle_theme", "Toggle Light/Dark"),
323        true,
324        Some("CmdOrCtrl+T"),
325    )?;
326    let toggle_crt = MenuItem::with_id(
327        handle,
328        "toggle_crt",
329        t("menu.toggle_crt", "Toggle CRT Effects"),
330        true,
331        Some("F1"),
332    )?;
333    let view_sep2 = PredefinedMenuItem::separator(handle)?;
334    let reset_columns = MenuItem::with_id(
335        handle,
336        "reset_columns",
337        t("menu.reset_columns", "Reset Column Widths"),
338        true,
339        Some("CmdOrCtrl+Shift+W"),
340    )?;
341    let reset_tabs = MenuItem::with_id(
342        handle,
343        "reset_tabs",
344        t("menu.reset_tabs", "Reset Tab Order"),
345        true,
346        Some("CmdOrCtrl+Shift+T"),
347    )?;
348
349    let view_menu = Submenu::with_id_and_items(
350        handle,
351        "view",
352        t("menu.view", "View"),
353        true,
354        &[
355            &tab_plugins,
356            &tab_samples,
357            &tab_daw,
358            &tab_presets,
359            &tab_favorites,
360            &tab_notes,
361            &tab_history,
362            &tab_settings,
363            &tab_files,
364            &tab_audio_engine,
365            &view_sep,
366            &toggle_theme,
367            &toggle_crt,
368            &view_sep2,
369            &reset_columns,
370            &reset_tabs,
371        ],
372    )?;
373
374    // Playback menu
375    let play_pause = MenuItem::with_id(
376        handle,
377        "play_pause",
378        t("menu.play_pause", "Play / Pause"),
379        true,
380        Some("Space"),
381    )?;
382    let toggle_loop = MenuItem::with_id(
383        handle,
384        "toggle_loop",
385        t("menu.toggle_loop", "Toggle Loop"),
386        true,
387        Some("CmdOrCtrl+L"),
388    )?;
389    let stop_playback = MenuItem::with_id(
390        handle,
391        "stop_playback",
392        t("menu.stop_playback", "Stop Playback"),
393        true,
394        Some("CmdOrCtrl+Shift+."),
395    )?;
396    let expand_player = MenuItem::with_id(
397        handle,
398        "expand_player",
399        t("menu.expand_player", "Expand / Collapse Player"),
400        true,
401        Some("CmdOrCtrl+Shift+M"),
402    )?;
403
404    let next_track = MenuItem::with_id(
405        handle,
406        "next_track",
407        t("menu.next_track", "Next Track"),
408        true,
409        Some("CmdOrCtrl+Right"),
410    )?;
411    let prev_track = MenuItem::with_id(
412        handle,
413        "prev_track",
414        t("menu.prev_track", "Previous Track"),
415        true,
416        Some("CmdOrCtrl+Left"),
417    )?;
418    let toggle_shuffle = MenuItem::with_id(
419        handle,
420        "toggle_shuffle",
421        t("menu.toggle_shuffle", "Toggle Shuffle"),
422        true,
423        Some("S"),
424    )?;
425    let toggle_mute = MenuItem::with_id(
426        handle,
427        "toggle_mute",
428        t("menu.toggle_mute", "Mute / Unmute"),
429        true,
430        Some("M"),
431    )?;
432    let playback_sep = PredefinedMenuItem::separator(handle)?;
433
434    let playback_menu = Submenu::with_id_and_items(
435        handle,
436        "playback",
437        t("menu.playback", "Playback"),
438        true,
439        &[
440            &play_pause,
441            &stop_playback,
442            &playback_sep,
443            &next_track,
444            &prev_track,
445            &toggle_loop,
446            &toggle_shuffle,
447            &toggle_mute,
448            &playback_sep,
449            &expand_player,
450        ],
451    )?;
452
453    // Data menu
454    let clear_history = MenuItem::with_id(
455        handle,
456        "clear_history",
457        t("menu.clear_history", "Clear All History..."),
458        true,
459        Some("CmdOrCtrl+Shift+Delete"),
460    )?;
461    let clear_all_databases = MenuItem::with_id(
462        handle,
463        "clear_all_databases",
464        t("menu.clear_all_databases", "Clear All Databases"),
465        true,
466        Some("CmdOrCtrl+Shift+Alt+D"),
467    )?;
468    let clear_kvr = MenuItem::with_id(
469        handle,
470        "clear_kvr",
471        t("menu.clear_kvr", "Clear KVR Cache..."),
472        true,
473        Some("CmdOrCtrl+Shift+Alt+K"),
474    )?;
475    let clear_favorites = MenuItem::with_id(
476        handle,
477        "clear_favorites",
478        t("menu.clear_favorites", "Clear Favorites..."),
479        true,
480        Some("CmdOrCtrl+Shift+Alt+F"),
481    )?;
482
483    let reset_all = MenuItem::with_id(
484        handle,
485        "reset_all",
486        t("menu.reset_all_scans", "Reset All Scans..."),
487        true,
488        Some("CmdOrCtrl+Shift+Backspace"),
489    )?;
490    let data_sep = PredefinedMenuItem::separator(handle)?;
491    let find_duplicates = MenuItem::with_id(
492        handle,
493        "find_duplicates",
494        t("menu.find_duplicates", "Find Duplicates"),
495        true,
496        Some("CmdOrCtrl+D"),
497    )?;
498    let dep_graph = MenuItem::with_id(
499        handle,
500        "dep_graph",
501        t("menu.dep_graph", "Dependency Graph"),
502        true,
503        Some("CmdOrCtrl+G"),
504    )?;
505    let cmd_palette = MenuItem::with_id(
506        handle,
507        "cmd_palette",
508        t("menu.cmd_palette", "Command Palette"),
509        true,
510        Some("CmdOrCtrl+K"),
511    )?;
512    let help_overlay = MenuItem::with_id(
513        handle,
514        "help_overlay",
515        t("menu.help_overlay", "Keyboard Shortcuts"),
516        true,
517        Some("CmdOrCtrl+Shift+/"),
518    )?;
519
520    let data_menu = Submenu::with_id_and_items(
521        handle,
522        "data",
523        t("menu.data", "Data"),
524        true,
525        &[
526            &clear_history,
527            &clear_all_databases,
528            &clear_kvr,
529            &clear_favorites,
530            &data_sep,
531            &reset_all,
532        ],
533    )?;
534
535    let tools_menu = Submenu::with_id_and_items(
536        handle,
537        "tools",
538        t("menu.tools", "Tools"),
539        true,
540        &[
541            &find_duplicates,
542            &dep_graph,
543            &data_sep,
544            &cmd_palette,
545            &help_overlay,
546        ],
547    )?;
548
549    // Window menu
550    let minimize = PredefinedMenuItem::minimize(handle, None)?;
551    let zoom = PredefinedMenuItem::maximize(handle, None)?;
552    let win_sep = PredefinedMenuItem::separator(handle)?;
553    let close_win = PredefinedMenuItem::close_window(handle, None)?;
554
555    let window_menu = Submenu::with_id_and_items(
556        handle,
557        "window",
558        t("menu.window", "Window"),
559        true,
560        &[&minimize, &zoom, &win_sep, &close_win],
561    )?;
562
563    // Help menu
564    let github = MenuItem::with_id(
565        handle,
566        "github",
567        t("menu.github", "GitHub Repository"),
568        true,
569        Some("CmdOrCtrl+Shift+G"),
570    )?;
571    let docs = MenuItem::with_id(
572        handle,
573        "docs",
574        t("menu.docs", "Documentation"),
575        true,
576        Some("CmdOrCtrl+Shift+Alt+P"),
577    )?;
578
579    let help_menu = Submenu::with_id_and_items(
580        handle,
581        "help",
582        t("menu.help", "Help"),
583        true,
584        &[&github, &docs],
585    )?;
586
587    Menu::with_items(
588        handle,
589        &[
590            &app_menu,
591            &file_menu,
592            &edit_menu,
593            &scan_menu,
594            &view_menu,
595            &playback_menu,
596            &data_menu,
597            &tools_menu,
598            &window_menu,
599            &help_menu,
600        ],
601    )
602}