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