github.com/secoba/wails/v2@v2.6.4/pkg/menu/menuitem.go (about) 1 package menu 2 3 import ( 4 "sync" 5 6 "github.com/secoba/wails/v2/pkg/menu/keys" 7 ) 8 9 // MenuItem represents a menuitem contained in a menu 10 type MenuItem struct { 11 // Label is what appears as the menu text 12 Label string 13 // Role is a predefined menu type 14 Role Role 15 // Accelerator holds a representation of a key binding 16 Accelerator *keys.Accelerator 17 // Type of MenuItem, EG: Checkbox, Text, Separator, Radio, Submenu 18 Type Type 19 // Disabled makes the item unselectable 20 Disabled bool 21 // Hidden ensures that the item is not shown in the menu 22 Hidden bool 23 // Checked indicates if the item is selected (used by Checkbox and Radio types only) 24 Checked bool 25 // Submenu contains a list of menu items that will be shown as a submenu 26 // SubMenu []*MenuItem `json:"SubMenu,omitempty"` 27 SubMenu *Menu 28 29 // Callback function when menu clicked 30 Click Callback 31 /* 32 // Text Colour 33 RGBA string 34 35 // Font 36 FontSize int 37 FontName string 38 39 // Image - base64 image data 40 Image string 41 42 // MacTemplateImage indicates that on a Mac, this image is a template image 43 MacTemplateImage bool 44 45 // MacAlternate indicates that this item is an alternative to the previous menu item 46 MacAlternate bool 47 48 // Tooltip 49 Tooltip string 50 */ 51 // This holds the menu item's parent. 52 parent *MenuItem 53 54 // Used for locking when removing elements 55 removeLock sync.Mutex 56 } 57 58 // Parent returns the parent of the menu item. 59 // If it is a top level menu then it returns nil. 60 func (m *MenuItem) Parent() *MenuItem { 61 return m.parent 62 } 63 64 // Append will attempt to append the given menu item to 65 // this item's submenu items. If this menu item is not a 66 // submenu, then this method will not add the item and 67 // simply return false. 68 func (m *MenuItem) Append(item *MenuItem) bool { 69 if !m.isSubMenu() { 70 return false 71 } 72 item.parent = m 73 m.SubMenu.Append(item) 74 return true 75 } 76 77 // Prepend will attempt to prepend the given menu item to 78 // this item's submenu items. If this menu item is not a 79 // submenu, then this method will not add the item and 80 // simply return false. 81 func (m *MenuItem) Prepend(item *MenuItem) bool { 82 if !m.isSubMenu() { 83 return false 84 } 85 item.parent = m 86 m.SubMenu.Prepend(item) 87 return true 88 } 89 90 func (m *MenuItem) Remove() { 91 // Iterate my parent's children 92 m.Parent().removeChild(m) 93 } 94 95 func (m *MenuItem) removeChild(item *MenuItem) { 96 m.removeLock.Lock() 97 for index, child := range m.SubMenu.Items { 98 if item == child { 99 m.SubMenu.Items = append(m.SubMenu.Items[:index], m.SubMenu.Items[index+1:]...) 100 } 101 } 102 m.removeLock.Unlock() 103 } 104 105 // InsertAfter attempts to add the given item after this item in the parent 106 // menu. If there is no parent menu (we are a top level menu) then false is 107 // returned 108 func (m *MenuItem) InsertAfter(item *MenuItem) bool { 109 // We need to find my parent 110 if m.parent == nil { 111 return false 112 } 113 114 // Get my parent to insert the item 115 return m.parent.insertNewItemAfterGivenItem(m, item) 116 } 117 118 // InsertBefore attempts to add the given item before this item in the parent 119 // menu. If there is no parent menu (we are a top level menu) then false is 120 // returned 121 func (m *MenuItem) InsertBefore(item *MenuItem) bool { 122 // We need to find my parent 123 if m.parent == nil { 124 return false 125 } 126 127 // Get my parent to insert the item 128 return m.parent.insertNewItemBeforeGivenItem(m, item) 129 } 130 131 // insertNewItemAfterGivenItem will insert the given item after the given target 132 // in this item's submenu. If we are not a submenu, 133 // then something bad has happened :/ 134 func (m *MenuItem) insertNewItemAfterGivenItem(target *MenuItem, 135 newItem *MenuItem, 136 ) bool { 137 if !m.isSubMenu() { 138 return false 139 } 140 141 // Find the index of the target 142 targetIndex := m.getItemIndex(target) 143 if targetIndex == -1 { 144 return false 145 } 146 147 // Insert element into slice 148 return m.insertItemAtIndex(targetIndex+1, newItem) 149 } 150 151 // insertNewItemBeforeGivenItem will insert the given item before the given 152 // target in this item's submenu. If we are not a submenu, then something bad 153 // has happened :/ 154 func (m *MenuItem) insertNewItemBeforeGivenItem(target *MenuItem, 155 newItem *MenuItem, 156 ) bool { 157 if !m.isSubMenu() { 158 return false 159 } 160 161 // Find the index of the target 162 targetIndex := m.getItemIndex(target) 163 if targetIndex == -1 { 164 return false 165 } 166 167 // Insert element into slice 168 return m.insertItemAtIndex(targetIndex, newItem) 169 } 170 171 func (m *MenuItem) isSubMenu() bool { 172 return m.Type == SubmenuType 173 } 174 175 // getItemIndex returns the index of the given target relative to this menu 176 func (m *MenuItem) getItemIndex(target *MenuItem) int { 177 // This should only be called on submenus 178 if !m.isSubMenu() { 179 return -1 180 } 181 182 // hunt down that bad boy 183 for index, item := range m.SubMenu.Items { 184 if item == target { 185 return index 186 } 187 } 188 189 return -1 190 } 191 192 // insertItemAtIndex attempts to insert the given item into the submenu at 193 // the given index 194 // Credit: https://stackoverflow.com/a/61822301 195 func (m *MenuItem) insertItemAtIndex(index int, target *MenuItem) bool { 196 // If index is OOB, return false 197 if index > len(m.SubMenu.Items) { 198 return false 199 } 200 201 // Save parent reference 202 target.parent = m 203 204 // If index is last item, then just regular append 205 if index == len(m.SubMenu.Items) { 206 m.SubMenu.Items = append(m.SubMenu.Items, target) 207 return true 208 } 209 210 m.SubMenu.Items = append(m.SubMenu.Items[:index+1], m.SubMenu.Items[index:]...) 211 m.SubMenu.Items[index] = target 212 return true 213 } 214 215 func (m *MenuItem) SetLabel(name string) { 216 if m.Label == name { 217 return 218 } 219 m.Label = name 220 } 221 222 func (m *MenuItem) IsSeparator() bool { 223 return m.Type == SeparatorType 224 } 225 226 func (m *MenuItem) IsCheckbox() bool { 227 return m.Type == CheckboxType 228 } 229 230 func (m *MenuItem) Disable() *MenuItem { 231 m.Disabled = true 232 return m 233 } 234 235 func (m *MenuItem) Enable() *MenuItem { 236 m.Disabled = false 237 return m 238 } 239 240 func (m *MenuItem) OnClick(click Callback) *MenuItem { 241 m.Click = click 242 return m 243 } 244 245 func (m *MenuItem) SetAccelerator(acc *keys.Accelerator) *MenuItem { 246 m.Accelerator = acc 247 return m 248 } 249 250 func (m *MenuItem) SetChecked(value bool) *MenuItem { 251 m.Checked = value 252 if m.Type != RadioType { 253 m.Type = CheckboxType 254 } 255 return m 256 } 257 258 func (m *MenuItem) Hide() *MenuItem { 259 m.Hidden = true 260 return m 261 } 262 263 func (m *MenuItem) Show() *MenuItem { 264 m.Hidden = false 265 return m 266 } 267 268 func (m *MenuItem) IsRadio() bool { 269 return m.Type == RadioType 270 } 271 272 func Label(label string) *MenuItem { 273 return &MenuItem{ 274 Type: TextType, 275 Label: label, 276 } 277 } 278 279 // Text is a helper to create basic Text menu items 280 func Text(label string, accelerator *keys.Accelerator, click Callback) *MenuItem { 281 return &MenuItem{ 282 Label: label, 283 Type: TextType, 284 Accelerator: accelerator, 285 Click: click, 286 } 287 } 288 289 // Separator provides a menu separator 290 func Separator() *MenuItem { 291 return &MenuItem{ 292 Type: SeparatorType, 293 } 294 } 295 296 // Radio is a helper to create basic Radio menu items with an accelerator 297 func Radio(label string, selected bool, accelerator *keys.Accelerator, click Callback) *MenuItem { 298 return &MenuItem{ 299 Label: label, 300 Type: RadioType, 301 Checked: selected, 302 Accelerator: accelerator, 303 Click: click, 304 } 305 } 306 307 // Checkbox is a helper to create basic Checkbox menu items 308 func Checkbox(label string, checked bool, accelerator *keys.Accelerator, click Callback) *MenuItem { 309 return &MenuItem{ 310 Label: label, 311 Type: CheckboxType, 312 Checked: checked, 313 Accelerator: accelerator, 314 Click: click, 315 } 316 } 317 318 // SubMenu is a helper to create Submenus 319 func SubMenu(label string, menu *Menu) *MenuItem { 320 result := &MenuItem{ 321 Label: label, 322 SubMenu: menu, 323 Type: SubmenuType, 324 } 325 326 menu.setParent(result) 327 328 return result 329 }