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  }