github.com/AlpineAIO/wails/v2@v2.0.0-beta.32.0.20240505041856-1047a8fa5fef/internal/platform/menu/manager.go (about) 1 //go:build windows 2 3 package menu 4 5 import ( 6 "github.com/AlpineAIO/wails/v2/pkg/menu" 7 ) 8 9 // MenuManager manages the menus for the application 10 var MenuManager = NewManager() 11 12 type radioGroup []*menu.MenuItem 13 14 // Click updates the radio group state based on the item clicked 15 func (g *radioGroup) Click(item *menu.MenuItem) { 16 for _, radioGroupItem := range *g { 17 if radioGroupItem != item { 18 radioGroupItem.Checked = false 19 } 20 } 21 } 22 23 type processedMenu struct { 24 25 // the menu we processed 26 menu *menu.Menu 27 28 // updateMenuItemCallback is called when the menu item needs to be updated in the UI 29 updateMenuItemCallback func(*menu.MenuItem) 30 31 // items is a map of all menu items in this menu 32 items map[*menu.MenuItem]struct{} 33 34 // radioGroups tracks which radiogroup a menu item belongs to 35 radioGroups map[*menu.MenuItem][]*radioGroup 36 } 37 38 func newProcessedMenu(topLevelMenu *menu.Menu, updateMenuItemCallback func(*menu.MenuItem)) *processedMenu { 39 result := &processedMenu{ 40 updateMenuItemCallback: updateMenuItemCallback, 41 menu: topLevelMenu, 42 items: make(map[*menu.MenuItem]struct{}), 43 radioGroups: make(map[*menu.MenuItem][]*radioGroup), 44 } 45 result.process(topLevelMenu.Items) 46 return result 47 } 48 49 func (p *processedMenu) process(items []*menu.MenuItem) { 50 var currentRadioGroup radioGroup 51 for index, item := range items { 52 // Save the reference to the top level menu for this item 53 p.items[item] = struct{}{} 54 55 // If this is a radio item, add it to the radio group 56 if item.Type == menu.RadioType { 57 currentRadioGroup = append(currentRadioGroup, item) 58 } 59 60 // If this is not a radio item, or we are processing the last item in the menu, 61 // then we need to add the current radio group to the map if it has items 62 if item.Type != menu.RadioType || index == len(items)-1 { 63 if len(currentRadioGroup) > 0 { 64 p.addRadioGroup(currentRadioGroup) 65 currentRadioGroup = nil 66 } 67 } 68 69 // Process the submenu 70 if item.SubMenu != nil { 71 p.process(item.SubMenu.Items) 72 } 73 } 74 } 75 76 func (p *processedMenu) processClick(item *menu.MenuItem) { 77 // If this item is not in our menu, then we can't process it 78 if _, ok := p.items[item]; !ok { 79 return 80 } 81 82 // If this is a radio item, then we need to update the radio group 83 if item.Type == menu.RadioType { 84 // Get the radio groups for this item 85 radioGroups := p.radioGroups[item] 86 // Iterate each radio group this item belongs to and set the checked state 87 // of all items apart from the one that was clicked to false 88 for _, thisRadioGroup := range radioGroups { 89 thisRadioGroup.Click(item) 90 for _, thisRadioGroupItem := range *thisRadioGroup { 91 p.updateMenuItemCallback(thisRadioGroupItem) 92 } 93 } 94 } 95 96 if item.Type == menu.CheckboxType { 97 p.updateMenuItemCallback(item) 98 } 99 100 } 101 102 func (p *processedMenu) addRadioGroup(r radioGroup) { 103 for _, item := range r { 104 p.radioGroups[item] = append(p.radioGroups[item], &r) 105 } 106 } 107 108 type Manager struct { 109 menus map[*menu.Menu]*processedMenu 110 } 111 112 func NewManager() *Manager { 113 return &Manager{ 114 menus: make(map[*menu.Menu]*processedMenu), 115 } 116 } 117 118 func (m *Manager) AddMenu(menu *menu.Menu, updateMenuItemCallback func(*menu.MenuItem)) { 119 m.menus[menu] = newProcessedMenu(menu, updateMenuItemCallback) 120 } 121 122 func (m *Manager) ProcessClick(item *menu.MenuItem) { 123 124 // if menuitem is a checkbox, then we need to toggle the state 125 if item.Type == menu.CheckboxType { 126 item.Checked = !item.Checked 127 } 128 129 // Set the radio item to checked 130 if item.Type == menu.RadioType { 131 item.Checked = true 132 } 133 134 for _, thisMenu := range m.menus { 135 thisMenu.processClick(item) 136 } 137 138 if item.Click != nil { 139 item.Click(&menu.CallbackData{ 140 MenuItem: item, 141 }) 142 } 143 } 144 145 func (m *Manager) RemoveMenu(data *menu.Menu) { 146 delete(m.menus, data) 147 }