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  }