mirror of
https://github.com/wailsapp/wails.git
synced 2025-05-02 12:12:39 +08:00
WIP
This commit is contained in:
parent
8abcd81f30
commit
b879742571
@ -43,6 +43,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Add doc comments for Service API by [@fbbdev](https://github.com/fbbdev) in [#4024](https://github.com/wailsapp/wails/pull/4024)
|
||||
- Add function `application.NewServiceWithOptions` to initialise services with additional configuration by [@leaanthony](https://github.com/leaanthony) in [#4024](https://github.com/wailsapp/wails/pull/4024)
|
||||
- Improved menu control by [@FalcoG](https://github.com/FalcoG) and [@leaanthony](https://github.com/leaanthony) in [#4031](https://github.com/wailsapp/wails/pull/4031)
|
||||
- Added index-based menu operations (`ItemAt`, `InsertAt`, etc.) and consistent visibility control for all menu item types by [@leaanthony](https://github.com/leaanthony)
|
||||
- More documentation by [@leaanthony](https://github.com/leaanthony)
|
||||
- Support cancellation of events in standard event listeners by [@leaanthony](https://github.com/leaanthony)
|
||||
- Systray `Hide`, `Show` and `Destroy` support by [@leaanthony](https://github.com/leaanthony)
|
||||
|
@ -91,6 +91,21 @@ checkbox.SetChecked(true)
|
||||
isChecked := checkbox.Checked()
|
||||
```
|
||||
|
||||
### Visibility
|
||||
|
||||
Control whether a menu item is visible using the `SetHidden` method, which works for all menu item types including submenus:
|
||||
|
||||
```go
|
||||
menuItem := menu.Add("Regular Item")
|
||||
menuItem.SetHidden(true) // Hide the item
|
||||
menuItem.SetHidden(false) // Show the item
|
||||
|
||||
// For submenus, use the same method
|
||||
submenuItem := menu.AddSubmenu("Advanced Options")
|
||||
submenuItem.SetHidden(true) // Hide the submenu
|
||||
submenuItem.SetHidden(false) // Show the submenu
|
||||
```
|
||||
|
||||
### Accelerators
|
||||
|
||||
Add keyboard shortcuts to menu items:
|
||||
@ -106,6 +121,71 @@ Common accelerator modifiers:
|
||||
- `Alt`: Option on macOS
|
||||
- `Ctrl`: Control key on all platforms
|
||||
|
||||
## Menu Operations
|
||||
|
||||
### Adding Items
|
||||
|
||||
Standard methods for adding items to the end of a menu:
|
||||
|
||||
```go
|
||||
menu.Add("Regular Item")
|
||||
menu.AddCheckbox("Checkbox Item", false)
|
||||
menu.AddRadio("Radio Item", false)
|
||||
menu.AddSubmenu("Submenu")
|
||||
menu.AddSeparator()
|
||||
```
|
||||
|
||||
### Accessing Items by Index
|
||||
|
||||
Retrieve menu items using their index position:
|
||||
|
||||
```go
|
||||
firstItem := menu.ItemAt(0)
|
||||
secondItem := menu.ItemAt(1)
|
||||
|
||||
// Check if an item exists at the given index
|
||||
if item := menu.ItemAt(5); item != nil {
|
||||
// Item exists, use it
|
||||
}
|
||||
```
|
||||
|
||||
### Inserting Items by Index
|
||||
|
||||
Insert items at specific positions in the menu. If the index is out of bounds, the item will be inserted at the beginning (for negative indices) or appended to the end (for indices greater than the menu length):
|
||||
|
||||
```go
|
||||
// Insert a regular item at index 0 (beginning of the menu)
|
||||
menu.InsertAt(0, "First Item")
|
||||
|
||||
// Insert a separator at index 2
|
||||
menu.InsertSeparatorAt(2)
|
||||
|
||||
// Insert a checkbox at index 3
|
||||
menu.InsertCheckboxAt(3, "Enable Feature", false)
|
||||
|
||||
// Insert a radio button at index 4
|
||||
menu.InsertRadioAt(4, "Option", true)
|
||||
|
||||
// Insert a submenu at index 5
|
||||
submenu := menu.InsertSubmenuAt(5, "More Options")
|
||||
|
||||
// Insert an existing menu item at index 6
|
||||
existingItem := NewMenuItem("Existing Item")
|
||||
menu.InsertItemAt(6, existingItem)
|
||||
|
||||
// Handling out-of-bounds indices
|
||||
menu.InsertAt(-1, "Will be inserted at the beginning")
|
||||
menu.InsertAt(999, "Will be appended to the end")
|
||||
```
|
||||
|
||||
### Menu Size
|
||||
|
||||
Get the number of items in a menu:
|
||||
|
||||
```go
|
||||
count := menu.Count()
|
||||
```
|
||||
|
||||
## Event Handling
|
||||
|
||||
### Click Events
|
||||
@ -151,6 +231,7 @@ menuItem := menu.Add("Initial State")
|
||||
menuItem.SetLabel("New Label")
|
||||
menuItem.SetEnabled(false)
|
||||
menuItem.SetChecked(true)
|
||||
menuItem.SetHidden(true) // Hide the item
|
||||
|
||||
// Apply changes
|
||||
menu.Update()
|
||||
@ -169,6 +250,8 @@ After modifying menu items, call `Update()` on the parent menu to apply the chan
|
||||
5. Keep radio groups focused on a single choice
|
||||
6. Update menu items to reflect application state
|
||||
7. Handle all possible states in click handlers
|
||||
8. Use index-based operations for dynamic menu restructuring
|
||||
9. Use `SetHidden` consistently for all menu item types, including submenus
|
||||
|
||||
:::tip[Pro Tip]
|
||||
When sharing event handlers, use the `ctx.ClickedMenuItem()` method to determine which item triggered the event and handle it accordingly.
|
||||
@ -186,3 +269,37 @@ Menu appearance and behaviour varies by platform:
|
||||
:::danger[Warning]
|
||||
Always test menu functionality across all supported platforms, as behaviour and appearance may vary significantly.
|
||||
:::
|
||||
|
||||
## Example: Dynamic Menu Management
|
||||
|
||||
This example demonstrates how to dynamically manage menu items using the index-based operations:
|
||||
|
||||
```go
|
||||
// Create a menu with some initial items
|
||||
menu := application.NewMenu()
|
||||
menu.Add("Item 1")
|
||||
menu.Add("Item 3") // Intentionally skipping Item 2
|
||||
|
||||
// Later, insert the missing item at the correct position
|
||||
menu.InsertAt(1, "Item 2")
|
||||
|
||||
// Get the count of items
|
||||
fmt.Printf("Menu has %d items\n", menu.Count())
|
||||
|
||||
// Access items by index
|
||||
for i := 0; i < menu.Count(); i++ {
|
||||
item := menu.ItemAt(i)
|
||||
fmt.Printf("Item at index %d: %s\n", i, item.Label())
|
||||
}
|
||||
|
||||
// Show/hide a submenu based on application state
|
||||
submenu := menu.AddSubmenu("Advanced Options")
|
||||
if userIsAdvanced {
|
||||
submenu.SetHidden(false) // Show the submenu
|
||||
} else {
|
||||
submenu.SetHidden(true) // Hide the submenu
|
||||
}
|
||||
|
||||
// Apply all changes
|
||||
menu.Update()
|
||||
```
|
||||
|
@ -113,6 +113,120 @@ func main() {
|
||||
ctx.ClickedMenuItem().SetLabel("Unhide the beatles!")
|
||||
}
|
||||
})
|
||||
|
||||
// ---- New index-based menu operations demo ----
|
||||
indexMenu := menu.AddSubmenu("Index Operations")
|
||||
|
||||
// Add some initial items
|
||||
indexMenu.Add("Item 0")
|
||||
indexMenu.Add("Item 2")
|
||||
indexMenu.Add("Item 4")
|
||||
|
||||
// Demonstrate inserting items at specific indices
|
||||
indexMenu.InsertAt(1, "Item 1").OnClick(func(*application.Context) {
|
||||
println("Item 1 clicked")
|
||||
})
|
||||
|
||||
indexMenu.InsertAt(3, "Item 3").OnClick(func(*application.Context) {
|
||||
println("Item 3 clicked")
|
||||
})
|
||||
|
||||
// Demonstrate inserting different types of items at specific indices
|
||||
indexMenu.AddSeparator()
|
||||
indexMenu.InsertCheckboxAt(6, "Checkbox at index 6", true).OnClick(func(ctx *application.Context) {
|
||||
println("Checkbox at index 6 clicked, checked:", ctx.ClickedMenuItem().Checked())
|
||||
})
|
||||
|
||||
indexMenu.InsertRadioAt(7, "Radio at index 7", true).OnClick(func(ctx *application.Context) {
|
||||
println("Radio at index 7 clicked")
|
||||
})
|
||||
|
||||
indexMenu.InsertSeparatorAt(8)
|
||||
|
||||
// Create a submenu and insert it at a specific index
|
||||
submenuAtIndex := indexMenu.InsertSubmenuAt(9, "Inserted Submenu")
|
||||
submenuAtIndex.Add("Submenu Item 1")
|
||||
submenuAtIndex.Add("Submenu Item 2")
|
||||
|
||||
// Demonstrate ItemAt to access items by index
|
||||
indexMenu.AddSeparator()
|
||||
indexMenu.Add("Get Item at Index").OnClick(func(*application.Context) {
|
||||
// Get the item at index 2 and change its label
|
||||
if item := indexMenu.ItemAt(2); item != nil {
|
||||
println("Item at index 2:", item.Label())
|
||||
item.SetLabel("Item 2 (Modified)")
|
||||
}
|
||||
})
|
||||
|
||||
// Demonstrate Count method
|
||||
indexMenu.Add("Count Items").OnClick(func(*application.Context) {
|
||||
println("Menu has", indexMenu.Count(), "items")
|
||||
})
|
||||
|
||||
// Demonstrate visibility control for different item types
|
||||
visibilityMenu := menu.AddSubmenu("Visibility Control")
|
||||
|
||||
// Regular menu item
|
||||
regularItem := visibilityMenu.Add("Regular Item")
|
||||
|
||||
// Checkbox menu item
|
||||
checkboxItem := visibilityMenu.AddCheckbox("Checkbox Item", true)
|
||||
|
||||
// Radio menu item
|
||||
radioItem := visibilityMenu.AddRadio("Radio Item", true)
|
||||
|
||||
// Separator
|
||||
visibilityMenu.AddSeparator()
|
||||
separatorIndex := visibilityMenu.Count() - 1
|
||||
separatorItem := visibilityMenu.ItemAt(separatorIndex)
|
||||
|
||||
// Submenu - get the MenuItem for the submenu to control visibility
|
||||
submenuMenuItem := application.NewSubMenuItem("Submenu")
|
||||
visibilityMenu.InsertItemAt(visibilityMenu.Count(), submenuMenuItem)
|
||||
submenuContent := submenuMenuItem.GetSubmenu()
|
||||
submenuContent.Add("Submenu Content")
|
||||
|
||||
// Controls for toggling visibility
|
||||
visibilityMenu.AddSeparator()
|
||||
visibilityMenu.Add("Toggle Regular Item").OnClick(func(*application.Context) {
|
||||
regularItem.SetHidden(!regularItem.Hidden())
|
||||
println("Regular item hidden:", regularItem.Hidden())
|
||||
})
|
||||
|
||||
visibilityMenu.Add("Toggle Checkbox Item").OnClick(func(*application.Context) {
|
||||
checkboxItem.SetHidden(!checkboxItem.Hidden())
|
||||
println("Checkbox item hidden:", checkboxItem.Hidden())
|
||||
})
|
||||
|
||||
visibilityMenu.Add("Toggle Radio Item").OnClick(func(*application.Context) {
|
||||
radioItem.SetHidden(!radioItem.Hidden())
|
||||
println("Radio item hidden:", radioItem.Hidden())
|
||||
})
|
||||
|
||||
visibilityMenu.Add("Toggle Separator").OnClick(func(*application.Context) {
|
||||
separatorItem.SetHidden(!separatorItem.Hidden())
|
||||
println("Separator hidden:", separatorItem.Hidden())
|
||||
})
|
||||
|
||||
// For submenu visibility, we need to toggle the visibility of the MenuItem that contains the submenu
|
||||
visibilityMenu.Add("Toggle Submenu").OnClick(func(ctx *application.Context) {
|
||||
// Log the current state before toggling
|
||||
println("Submenu hidden before toggle:", submenuMenuItem.Hidden())
|
||||
|
||||
// Toggle the visibility
|
||||
submenuMenuItem.SetHidden(!submenuMenuItem.Hidden())
|
||||
|
||||
// Log the new state after toggling
|
||||
println("Submenu hidden after toggle:", submenuMenuItem.Hidden())
|
||||
|
||||
// Update the menu item label to reflect the current state
|
||||
if submenuMenuItem.Hidden() {
|
||||
ctx.ClickedMenuItem().SetLabel("Show Submenu")
|
||||
} else {
|
||||
ctx.ClickedMenuItem().SetLabel("Hide Submenu")
|
||||
}
|
||||
})
|
||||
|
||||
app.SetMenu(menu)
|
||||
|
||||
window := app.NewWebviewWindow().SetBackgroundColour(application.NewRGB(33, 37, 41))
|
||||
|
@ -190,6 +190,78 @@ func (m *Menu) ItemAt(index int) *MenuItem {
|
||||
return m.items[index]
|
||||
}
|
||||
|
||||
// InsertAt inserts a menu item at the specified index.
|
||||
// If index is out of bounds, the item will be appended to the end of the menu.
|
||||
// Returns the newly created menu item for further customisation.
|
||||
func (m *Menu) InsertAt(index int, label string) *MenuItem {
|
||||
result := NewMenuItem(label)
|
||||
m.InsertItemAt(index, result)
|
||||
return result
|
||||
}
|
||||
|
||||
// InsertItemAt inserts an existing menu item at the specified index.
|
||||
// If index is negative, the item will be inserted at the beginning of the menu.
|
||||
// If index is greater than the current length, the item will be appended to the end of the menu.
|
||||
// Returns the menu for method chaining.
|
||||
func (m *Menu) InsertItemAt(index int, item *MenuItem) *Menu {
|
||||
if index < 0 {
|
||||
index = 0
|
||||
}
|
||||
if index > len(m.items) {
|
||||
index = len(m.items)
|
||||
}
|
||||
|
||||
if index == 0 {
|
||||
m.items = append([]*MenuItem{item}, m.items...)
|
||||
} else if index == len(m.items) {
|
||||
m.items = append(m.items, item)
|
||||
} else {
|
||||
m.items = append(m.items[:index], append([]*MenuItem{item}, m.items[index:]...)...)
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// InsertSeparatorAt inserts a separator at the specified index.
|
||||
// If index is out of bounds, the separator will be appended to the end of the menu.
|
||||
// Returns the menu for method chaining.
|
||||
func (m *Menu) InsertSeparatorAt(index int) *Menu {
|
||||
result := NewMenuItemSeparator()
|
||||
m.InsertItemAt(index, result)
|
||||
return m
|
||||
}
|
||||
|
||||
// InsertCheckboxAt inserts a checkbox menu item at the specified index.
|
||||
// If index is out of bounds, the item will be appended to the end of the menu.
|
||||
// Returns the newly created menu item for further customisation.
|
||||
func (m *Menu) InsertCheckboxAt(index int, label string, enabled bool) *MenuItem {
|
||||
result := NewMenuItemCheckbox(label, enabled)
|
||||
m.InsertItemAt(index, result)
|
||||
return result
|
||||
}
|
||||
|
||||
// InsertRadioAt inserts a radio menu item at the specified index.
|
||||
// If index is out of bounds, the item will be appended to the end of the menu.
|
||||
// Returns the newly created menu item for further customisation.
|
||||
func (m *Menu) InsertRadioAt(index int, label string, enabled bool) *MenuItem {
|
||||
result := NewMenuItemRadio(label, enabled)
|
||||
m.InsertItemAt(index, result)
|
||||
return result
|
||||
}
|
||||
|
||||
// InsertSubmenuAt inserts a submenu at the specified index.
|
||||
// If index is out of bounds, the submenu will be appended to the end of the menu.
|
||||
// Returns the newly created submenu for further customisation.
|
||||
func (m *Menu) InsertSubmenuAt(index int, label string) *Menu {
|
||||
result := NewSubMenuItem(label)
|
||||
m.InsertItemAt(index, result)
|
||||
return result.submenu
|
||||
}
|
||||
|
||||
// Count returns the number of items in the menu.
|
||||
func (m *Menu) Count() int {
|
||||
return len(m.items)
|
||||
}
|
||||
|
||||
// Clone recursively clones the menu and all its submenus.
|
||||
func (m *Menu) Clone() *Menu {
|
||||
result := &Menu{
|
||||
|
@ -62,20 +62,31 @@ func TestMenu_FindByLabel(t *testing.T) {
|
||||
|
||||
func TestMenu_ItemAt(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
menu *application.Menu
|
||||
index int
|
||||
shouldError bool
|
||||
name string
|
||||
menu *application.Menu
|
||||
index int
|
||||
expectedLabel string
|
||||
shouldBeNil bool
|
||||
}{
|
||||
{
|
||||
name: "Valid index",
|
||||
name: "Get first item",
|
||||
menu: application.NewMenuFromItems(
|
||||
application.NewMenuItem("Item 1"),
|
||||
application.NewMenuItem("Item 2"),
|
||||
application.NewMenuItem("Target"),
|
||||
),
|
||||
index: 2,
|
||||
shouldError: false,
|
||||
index: 0,
|
||||
expectedLabel: "Item 1",
|
||||
shouldBeNil: false,
|
||||
},
|
||||
{
|
||||
name: "Get last item",
|
||||
menu: application.NewMenuFromItems(
|
||||
application.NewMenuItem("Item 1"),
|
||||
application.NewMenuItem("Item 2"),
|
||||
),
|
||||
index: 1,
|
||||
expectedLabel: "Item 2",
|
||||
shouldBeNil: false,
|
||||
},
|
||||
{
|
||||
name: "Index out of bounds (negative)",
|
||||
@ -84,7 +95,7 @@ func TestMenu_ItemAt(t *testing.T) {
|
||||
application.NewMenuItem("Item 2"),
|
||||
),
|
||||
index: -1,
|
||||
shouldError: true,
|
||||
shouldBeNil: true,
|
||||
},
|
||||
{
|
||||
name: "Index out of bounds (too large)",
|
||||
@ -93,18 +104,379 @@ func TestMenu_ItemAt(t *testing.T) {
|
||||
application.NewMenuItem("Item 2"),
|
||||
),
|
||||
index: 2,
|
||||
shouldError: true,
|
||||
shouldBeNil: true,
|
||||
},
|
||||
{
|
||||
name: "Empty menu",
|
||||
menu: application.NewMenu(),
|
||||
index: 0,
|
||||
shouldBeNil: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
item := test.menu.ItemAt(test.index)
|
||||
if test.shouldError && item != nil {
|
||||
t.Errorf("Expected error, but found %v", item)
|
||||
|
||||
if test.shouldBeNil {
|
||||
if item != nil {
|
||||
t.Errorf("Expected nil item, but got item with label: %s", item.Label())
|
||||
}
|
||||
} else {
|
||||
if item == nil {
|
||||
t.Errorf("Expected item with label %s, but got nil", test.expectedLabel)
|
||||
} else if item.Label() != test.expectedLabel {
|
||||
t.Errorf("Expected item with label %s, but got %s", test.expectedLabel, item.Label())
|
||||
}
|
||||
}
|
||||
if !test.shouldError && item == nil {
|
||||
t.Errorf("Expected item, but found none")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMenu_InsertAt(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
setupMenu func() *application.Menu
|
||||
index int
|
||||
label string
|
||||
expectedLabels []string
|
||||
}{
|
||||
{
|
||||
name: "Insert at beginning",
|
||||
setupMenu: func() *application.Menu {
|
||||
return application.NewMenuFromItems(
|
||||
application.NewMenuItem("Item 1"),
|
||||
application.NewMenuItem("Item 2"),
|
||||
)
|
||||
},
|
||||
index: 0,
|
||||
label: "New Item",
|
||||
expectedLabels: []string{"New Item", "Item 1", "Item 2"},
|
||||
},
|
||||
{
|
||||
name: "Insert in middle",
|
||||
setupMenu: func() *application.Menu {
|
||||
return application.NewMenuFromItems(
|
||||
application.NewMenuItem("Item 1"),
|
||||
application.NewMenuItem("Item 2"),
|
||||
)
|
||||
},
|
||||
index: 1,
|
||||
label: "New Item",
|
||||
expectedLabels: []string{"Item 1", "New Item", "Item 2"},
|
||||
},
|
||||
{
|
||||
name: "Insert at end",
|
||||
setupMenu: func() *application.Menu {
|
||||
return application.NewMenuFromItems(
|
||||
application.NewMenuItem("Item 1"),
|
||||
application.NewMenuItem("Item 2"),
|
||||
)
|
||||
},
|
||||
index: 2,
|
||||
label: "New Item",
|
||||
expectedLabels: []string{"Item 1", "Item 2", "New Item"},
|
||||
},
|
||||
{
|
||||
name: "Insert with negative index (should insert at beginning)",
|
||||
setupMenu: func() *application.Menu {
|
||||
return application.NewMenuFromItems(
|
||||
application.NewMenuItem("Item 1"),
|
||||
application.NewMenuItem("Item 2"),
|
||||
)
|
||||
},
|
||||
index: -1,
|
||||
label: "New Item",
|
||||
expectedLabels: []string{"New Item", "Item 1", "Item 2"},
|
||||
},
|
||||
{
|
||||
name: "Insert with index too large (should append)",
|
||||
setupMenu: func() *application.Menu {
|
||||
return application.NewMenuFromItems(
|
||||
application.NewMenuItem("Item 1"),
|
||||
application.NewMenuItem("Item 2"),
|
||||
)
|
||||
},
|
||||
index: 10,
|
||||
label: "New Item",
|
||||
expectedLabels: []string{"Item 1", "Item 2", "New Item"},
|
||||
},
|
||||
{
|
||||
name: "Insert into empty menu",
|
||||
setupMenu: func() *application.Menu {
|
||||
return application.NewMenu()
|
||||
},
|
||||
index: 0,
|
||||
label: "New Item",
|
||||
expectedLabels: []string{"New Item"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
menu := test.setupMenu()
|
||||
menu.InsertAt(test.index, test.label)
|
||||
|
||||
// Verify the menu has the correct number of items
|
||||
if menu.Count() != len(test.expectedLabels) {
|
||||
t.Errorf("Expected menu to have %d items, but got %d", len(test.expectedLabels), menu.Count())
|
||||
}
|
||||
|
||||
// Verify each item has the expected label
|
||||
for i, expectedLabel := range test.expectedLabels {
|
||||
item := menu.ItemAt(i)
|
||||
if item == nil {
|
||||
t.Errorf("Expected item at index %d, but got nil", i)
|
||||
} else if item.Label() != expectedLabel {
|
||||
t.Errorf("Expected item at index %d to have label %s, but got %s", i, expectedLabel, item.Label())
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMenu_InsertItemAt(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
setupMenu func() *application.Menu
|
||||
index int
|
||||
itemLabel string
|
||||
expectedLabels []string
|
||||
}{
|
||||
{
|
||||
name: "Insert existing item at beginning",
|
||||
setupMenu: func() *application.Menu {
|
||||
return application.NewMenuFromItems(
|
||||
application.NewMenuItem("Item 1"),
|
||||
application.NewMenuItem("Item 2"),
|
||||
)
|
||||
},
|
||||
index: 0,
|
||||
itemLabel: "Existing Item",
|
||||
expectedLabels: []string{"Existing Item", "Item 1", "Item 2"},
|
||||
},
|
||||
{
|
||||
name: "Insert existing item in middle",
|
||||
setupMenu: func() *application.Menu {
|
||||
return application.NewMenuFromItems(
|
||||
application.NewMenuItem("Item 1"),
|
||||
application.NewMenuItem("Item 2"),
|
||||
)
|
||||
},
|
||||
index: 1,
|
||||
itemLabel: "Existing Item",
|
||||
expectedLabels: []string{"Item 1", "Existing Item", "Item 2"},
|
||||
},
|
||||
{
|
||||
name: "Insert existing item at end",
|
||||
setupMenu: func() *application.Menu {
|
||||
return application.NewMenuFromItems(
|
||||
application.NewMenuItem("Item 1"),
|
||||
application.NewMenuItem("Item 2"),
|
||||
)
|
||||
},
|
||||
index: 2,
|
||||
itemLabel: "Existing Item",
|
||||
expectedLabels: []string{"Item 1", "Item 2", "Existing Item"},
|
||||
},
|
||||
{
|
||||
name: "Insert with negative index (should insert at beginning)",
|
||||
setupMenu: func() *application.Menu {
|
||||
return application.NewMenuFromItems(
|
||||
application.NewMenuItem("Item 1"),
|
||||
application.NewMenuItem("Item 2"),
|
||||
)
|
||||
},
|
||||
index: -1,
|
||||
itemLabel: "Existing Item",
|
||||
expectedLabels: []string{"Existing Item", "Item 1", "Item 2"},
|
||||
},
|
||||
{
|
||||
name: "Insert with index too large (should append)",
|
||||
setupMenu: func() *application.Menu {
|
||||
return application.NewMenuFromItems(
|
||||
application.NewMenuItem("Item 1"),
|
||||
application.NewMenuItem("Item 2"),
|
||||
)
|
||||
},
|
||||
index: 10,
|
||||
itemLabel: "Existing Item",
|
||||
expectedLabels: []string{"Item 1", "Item 2", "Existing Item"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
menu := test.setupMenu()
|
||||
existingItem := application.NewMenuItem(test.itemLabel)
|
||||
menu.InsertItemAt(test.index, existingItem)
|
||||
|
||||
// Verify the menu has the correct number of items
|
||||
if menu.Count() != len(test.expectedLabels) {
|
||||
t.Errorf("Expected menu to have %d items, but got %d", len(test.expectedLabels), menu.Count())
|
||||
}
|
||||
|
||||
// Verify each item has the expected label
|
||||
for i, expectedLabel := range test.expectedLabels {
|
||||
item := menu.ItemAt(i)
|
||||
if item == nil {
|
||||
t.Errorf("Expected item at index %d, but got nil", i)
|
||||
} else if item.Label() != expectedLabel {
|
||||
t.Errorf("Expected item at index %d to have label %s, but got %s", i, expectedLabel, item.Label())
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMenu_SpecializedInsertFunctions(t *testing.T) {
|
||||
t.Run("InsertSeparatorAt", func(t *testing.T) {
|
||||
menu := application.NewMenuFromItems(
|
||||
application.NewMenuItem("Item 1"),
|
||||
application.NewMenuItem("Item 2"),
|
||||
)
|
||||
|
||||
menu.InsertSeparatorAt(1)
|
||||
|
||||
// Verify the separator was inserted
|
||||
if menu.Count() != 3 {
|
||||
t.Errorf("Expected menu to have 3 items, but got %d", menu.Count())
|
||||
}
|
||||
|
||||
separator := menu.ItemAt(1)
|
||||
if separator == nil {
|
||||
t.Errorf("Expected separator at index 1, but got nil")
|
||||
} else if !separator.IsSeparator() {
|
||||
t.Errorf("Expected item at index 1 to be a separator, but it wasn't")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("InsertCheckboxAt", func(t *testing.T) {
|
||||
menu := application.NewMenuFromItems(
|
||||
application.NewMenuItem("Item 1"),
|
||||
application.NewMenuItem("Item 2"),
|
||||
)
|
||||
|
||||
menu.InsertCheckboxAt(1, "Checkbox", true)
|
||||
|
||||
// Verify the checkbox was inserted
|
||||
if menu.Count() != 3 {
|
||||
t.Errorf("Expected menu to have 3 items, but got %d", menu.Count())
|
||||
}
|
||||
|
||||
checkbox := menu.ItemAt(1)
|
||||
if checkbox == nil {
|
||||
t.Errorf("Expected checkbox at index 1, but got nil")
|
||||
} else if !checkbox.IsCheckbox() {
|
||||
t.Errorf("Expected item at index 1 to be a checkbox, but it wasn't")
|
||||
} else if checkbox.Label() != "Checkbox" {
|
||||
t.Errorf("Expected checkbox to have label 'Checkbox', but got '%s'", checkbox.Label())
|
||||
} else if !checkbox.Checked() {
|
||||
t.Errorf("Expected checkbox to be checked, but it wasn't")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("InsertRadioAt", func(t *testing.T) {
|
||||
menu := application.NewMenuFromItems(
|
||||
application.NewMenuItem("Item 1"),
|
||||
application.NewMenuItem("Item 2"),
|
||||
)
|
||||
|
||||
menu.InsertRadioAt(1, "Radio", true)
|
||||
|
||||
// Verify the radio button was inserted
|
||||
if menu.Count() != 3 {
|
||||
t.Errorf("Expected menu to have 3 items, but got %d", menu.Count())
|
||||
}
|
||||
|
||||
radio := menu.ItemAt(1)
|
||||
if radio == nil {
|
||||
t.Errorf("Expected radio button at index 1, but got nil")
|
||||
} else if !radio.IsRadio() {
|
||||
t.Errorf("Expected item at index 1 to be a radio button, but it wasn't")
|
||||
} else if radio.Label() != "Radio" {
|
||||
t.Errorf("Expected radio button to have label 'Radio', but got '%s'", radio.Label())
|
||||
} else if !radio.Checked() {
|
||||
t.Errorf("Expected radio button to be checked, but it wasn't")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("InsertSubmenuAt", func(t *testing.T) {
|
||||
menu := application.NewMenuFromItems(
|
||||
application.NewMenuItem("Item 1"),
|
||||
application.NewMenuItem("Item 2"),
|
||||
)
|
||||
|
||||
submenu := menu.InsertSubmenuAt(1, "Submenu")
|
||||
submenu.Add("Submenu Item")
|
||||
|
||||
// Verify the submenu was inserted
|
||||
if menu.Count() != 3 {
|
||||
t.Errorf("Expected menu to have 3 items, but got %d", menu.Count())
|
||||
}
|
||||
|
||||
submenuItem := menu.ItemAt(1)
|
||||
if submenuItem == nil {
|
||||
t.Errorf("Expected submenu at index 1, but got nil")
|
||||
} else if !submenuItem.IsSubmenu() {
|
||||
t.Errorf("Expected item at index 1 to be a submenu, but it wasn't")
|
||||
} else if submenuItem.Label() != "Submenu" {
|
||||
t.Errorf("Expected submenu to have label 'Submenu', but got '%s'", submenuItem.Label())
|
||||
}
|
||||
|
||||
// Verify the submenu has the expected item
|
||||
submenuFromItem := submenuItem.GetSubmenu()
|
||||
if submenuFromItem == nil {
|
||||
t.Errorf("Expected to get submenu from item, but got nil")
|
||||
} else if submenuFromItem.Count() != 1 {
|
||||
t.Errorf("Expected submenu to have 1 item, but got %d", submenuFromItem.Count())
|
||||
} else {
|
||||
submenuItemFromSubmenu := submenuFromItem.ItemAt(0)
|
||||
if submenuItemFromSubmenu == nil {
|
||||
t.Errorf("Expected item in submenu, but got nil")
|
||||
} else if submenuItemFromSubmenu.Label() != "Submenu Item" {
|
||||
t.Errorf("Expected submenu item to have label 'Submenu Item', but got '%s'", submenuItemFromSubmenu.Label())
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestMenu_Count(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
menu *application.Menu
|
||||
expectedCount int
|
||||
}{
|
||||
{
|
||||
name: "Menu with items",
|
||||
menu: application.NewMenuFromItems(
|
||||
application.NewMenuItem("Item 1"),
|
||||
application.NewMenuItem("Item 2"),
|
||||
application.NewMenuItem("Item 3"),
|
||||
),
|
||||
expectedCount: 3,
|
||||
},
|
||||
{
|
||||
name: "Empty menu",
|
||||
menu: application.NewMenu(),
|
||||
expectedCount: 0,
|
||||
},
|
||||
{
|
||||
name: "Menu with separator",
|
||||
menu: application.NewMenuFromItems(
|
||||
application.NewMenuItem("Item 1"),
|
||||
application.NewMenuItemSeparator(),
|
||||
application.NewMenuItem("Item 2"),
|
||||
),
|
||||
expectedCount: 3,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
count := test.menu.Count()
|
||||
if count != test.expectedCount {
|
||||
t.Errorf("Expected count to be %d, but got %d", test.expectedCount, count)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -52,6 +52,7 @@ func (w *windowsMenu) processMenu(parentMenu w32.HMENU, inputMenu *Menu) {
|
||||
globalApplication.removeKeyBinding(item.accelerator.String())
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
flags := uint32(w32.MF_STRING)
|
||||
@ -88,11 +89,6 @@ func (w *windowsMenu) processMenu(parentMenu w32.HMENU, inputMenu *Menu) {
|
||||
}
|
||||
var menuText = w32.MustStringToUTF16Ptr(thisText)
|
||||
|
||||
// If the item is hidden, don't append
|
||||
if item.Hidden() {
|
||||
continue
|
||||
}
|
||||
|
||||
w32.AppendMenu(parentMenu, flags, uintptr(itemID), menuText)
|
||||
if item.bitmap != nil {
|
||||
w32.SetMenuIcons(parentMenu, itemID, item.bitmap, nil)
|
||||
|
@ -341,44 +341,22 @@ func (m *MenuItem) SetChecked(checked bool) *MenuItem {
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *MenuItem) SetHidden(hidden bool) *MenuItem {
|
||||
m.hidden = hidden
|
||||
if m.impl != nil {
|
||||
m.impl.setHidden(m.hidden)
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// GetSubmenu returns the submenu of the MenuItem.
|
||||
// If the MenuItem is not a submenu, it returns nil.
|
||||
func (m *MenuItem) GetSubmenu() *Menu {
|
||||
return m.submenu
|
||||
}
|
||||
|
||||
func (m *MenuItem) Checked() bool {
|
||||
return m.checked
|
||||
}
|
||||
|
||||
func (m *MenuItem) IsSeparator() bool {
|
||||
return m.itemType == separator
|
||||
}
|
||||
|
||||
func (m *MenuItem) IsSubmenu() bool {
|
||||
return m.itemType == submenu
|
||||
}
|
||||
|
||||
func (m *MenuItem) IsCheckbox() bool {
|
||||
return m.itemType == checkbox
|
||||
}
|
||||
|
||||
func (m *MenuItem) IsRadio() bool {
|
||||
return m.itemType == radio
|
||||
}
|
||||
|
||||
func (m *MenuItem) Hidden() bool {
|
||||
return m.hidden
|
||||
}
|
||||
|
||||
// SetHidden sets whether the menu item is hidden.
|
||||
// This works for all menu item types, including submenus.
|
||||
// Returns the menu item for method chaining.
|
||||
func (m *MenuItem) SetHidden(hidden bool) *MenuItem {
|
||||
m.hidden = hidden
|
||||
if m.impl != nil {
|
||||
m.impl.setHidden(hidden)
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// OnClick sets the callback function for when the menu item is clicked.
|
||||
func (m *MenuItem) OnClick(f func(*Context)) *MenuItem {
|
||||
m.callback = f
|
||||
return m
|
||||
@ -454,3 +432,27 @@ func (m *MenuItem) Destroy() {
|
||||
m.radioGroupMembers = nil
|
||||
|
||||
}
|
||||
|
||||
func (m *MenuItem) GetSubmenu() *Menu {
|
||||
return m.submenu
|
||||
}
|
||||
|
||||
func (m *MenuItem) IsSeparator() bool {
|
||||
return m.itemType == separator
|
||||
}
|
||||
|
||||
func (m *MenuItem) IsSubmenu() bool {
|
||||
return m.itemType == submenu
|
||||
}
|
||||
|
||||
func (m *MenuItem) IsCheckbox() bool {
|
||||
return m.itemType == checkbox
|
||||
}
|
||||
|
||||
func (m *MenuItem) IsRadio() bool {
|
||||
return m.itemType == radio
|
||||
}
|
||||
|
||||
func (m *MenuItem) Checked() bool {
|
||||
return m.checked
|
||||
}
|
||||
|
@ -59,3 +59,121 @@ func TestMenuItem_RemoveAccelerator(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMenuItem_SetHidden(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
setupMenuItem func() *application.MenuItem
|
||||
setHidden bool
|
||||
expectedState bool
|
||||
}{
|
||||
{
|
||||
name: "Hide regular menu item",
|
||||
setupMenuItem: func() *application.MenuItem {
|
||||
return application.NewMenuItem("Regular Item")
|
||||
},
|
||||
setHidden: true,
|
||||
expectedState: true,
|
||||
},
|
||||
{
|
||||
name: "Show regular menu item",
|
||||
setupMenuItem: func() *application.MenuItem {
|
||||
return application.NewMenuItem("Regular Item").SetHidden(true)
|
||||
},
|
||||
setHidden: false,
|
||||
expectedState: false,
|
||||
},
|
||||
{
|
||||
name: "Hide checkbox menu item",
|
||||
setupMenuItem: func() *application.MenuItem {
|
||||
return application.NewMenuItemCheckbox("Checkbox Item", true)
|
||||
},
|
||||
setHidden: true,
|
||||
expectedState: true,
|
||||
},
|
||||
{
|
||||
name: "Hide radio menu item",
|
||||
setupMenuItem: func() *application.MenuItem {
|
||||
return application.NewMenuItemRadio("Radio Item", true)
|
||||
},
|
||||
setHidden: true,
|
||||
expectedState: true,
|
||||
},
|
||||
{
|
||||
name: "Hide separator",
|
||||
setupMenuItem: func() *application.MenuItem {
|
||||
return application.NewMenuItemSeparator()
|
||||
},
|
||||
setHidden: true,
|
||||
expectedState: true,
|
||||
},
|
||||
{
|
||||
name: "Hide submenu",
|
||||
setupMenuItem: func() *application.MenuItem {
|
||||
submenu := application.NewSubmenu("Submenu", application.NewMenu())
|
||||
return submenu
|
||||
},
|
||||
setHidden: true,
|
||||
expectedState: true,
|
||||
},
|
||||
{
|
||||
name: "Show submenu",
|
||||
setupMenuItem: func() *application.MenuItem {
|
||||
submenu := application.NewSubmenu("Submenu", application.NewMenu())
|
||||
submenu.SetHidden(true)
|
||||
return submenu
|
||||
},
|
||||
setHidden: false,
|
||||
expectedState: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
menuItem := test.setupMenuItem()
|
||||
|
||||
// Set the hidden state
|
||||
menuItem.SetHidden(test.setHidden)
|
||||
|
||||
// Verify the hidden state
|
||||
if menuItem.Hidden() != test.expectedState {
|
||||
t.Errorf("Expected hidden state to be %v, but got %v", test.expectedState, menuItem.Hidden())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMenuItem_SubmenuVisibility(t *testing.T) {
|
||||
t.Run("Submenu visibility through SetHidden", func(t *testing.T) {
|
||||
// Create a menu with a submenu
|
||||
menu := application.NewMenu()
|
||||
|
||||
// Create a submenu using NewSubMenuItem directly to get the MenuItem
|
||||
submenuItem := application.NewSubMenuItem("Submenu")
|
||||
menu.InsertItemAt(0, submenuItem)
|
||||
|
||||
// Get the submenu from the menu item
|
||||
submenu := submenuItem.GetSubmenu()
|
||||
|
||||
// Add some items to the submenu
|
||||
submenu.Add("Submenu Item 1")
|
||||
submenu.Add("Submenu Item 2")
|
||||
|
||||
// Initially, the submenu should be visible
|
||||
if submenuItem.Hidden() {
|
||||
t.Errorf("Expected submenu to be visible initially, but it was hidden")
|
||||
}
|
||||
|
||||
// Hide the submenu
|
||||
submenuItem.SetHidden(true)
|
||||
if !submenuItem.Hidden() {
|
||||
t.Errorf("Expected submenu to be hidden after SetHidden(true), but it was visible")
|
||||
}
|
||||
|
||||
// Show the submenu again
|
||||
submenuItem.SetHidden(false)
|
||||
if submenuItem.Hidden() {
|
||||
t.Errorf("Expected submenu to be visible after SetHidden(false), but it was hidden")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -24,35 +24,63 @@ type windowsMenuItem struct {
|
||||
}
|
||||
|
||||
func (m *windowsMenuItem) setHidden(hidden bool) {
|
||||
if hidden && !m.hidden {
|
||||
m.hidden = true
|
||||
// iterate the parent items and find the menu item after us
|
||||
// Only process if the visibility state is changing
|
||||
if hidden == m.hidden {
|
||||
return
|
||||
}
|
||||
|
||||
m.hidden = hidden
|
||||
|
||||
// For submenus, we need special handling
|
||||
if m.menuItem.submenu != nil {
|
||||
// Find our position in the parent menu
|
||||
var position int = -1
|
||||
for i, item := range m.parent.items {
|
||||
if item == m.menuItem {
|
||||
if i < len(m.parent.items)-1 {
|
||||
m.itemAfter = m.parent.items[i+1]
|
||||
} else {
|
||||
m.itemAfter = nil
|
||||
}
|
||||
position = i
|
||||
break
|
||||
}
|
||||
}
|
||||
// Remove from parent menu
|
||||
w32.RemoveMenu(m.hMenu, m.id, w32.MF_BYCOMMAND)
|
||||
} else if !hidden && m.hidden {
|
||||
m.hidden = false
|
||||
// Add to parent menu before the "itemAfter"
|
||||
var pos int
|
||||
if m.itemAfter != nil {
|
||||
|
||||
if position == -1 {
|
||||
// Can't find our position, can't proceed
|
||||
return
|
||||
}
|
||||
|
||||
if hidden {
|
||||
// When hiding, we need to remove the menu item by position
|
||||
w32.RemoveMenu(m.hMenu, position, w32.MF_BYPOSITION)
|
||||
} else {
|
||||
// When showing, we need to insert the menu item at the correct position
|
||||
// Create a new menu info for this item
|
||||
menuInfo := m.getMenuInfo()
|
||||
w32.InsertMenuItem(m.hMenu, uint32(position), true, menuInfo)
|
||||
}
|
||||
} else {
|
||||
// For regular menu items, we can use the command ID
|
||||
if hidden {
|
||||
w32.RemoveMenu(m.hMenu, int(m.id), w32.MF_BYCOMMAND)
|
||||
} else {
|
||||
// Find the position to insert at
|
||||
var position int = 0
|
||||
for i, item := range m.parent.items {
|
||||
if item == m.itemAfter {
|
||||
pos = i - 1
|
||||
if item == m.menuItem {
|
||||
position = i
|
||||
break
|
||||
}
|
||||
}
|
||||
m.itemAfter = nil
|
||||
menuInfo := m.getMenuInfo()
|
||||
w32.InsertMenuItem(m.hMenu, uint32(position), true, menuInfo)
|
||||
}
|
||||
}
|
||||
|
||||
// If we have a parent window, redraw the menu
|
||||
if m.parent.impl != nil {
|
||||
if windowsImpl, ok := m.parent.impl.(*windowsMenu); ok {
|
||||
if windowsImpl.parentWindow != nil {
|
||||
w32.DrawMenuBar(windowsImpl.parentWindow.hwnd)
|
||||
}
|
||||
}
|
||||
w32.InsertMenuItem(m.hMenu, uint32(pos), true, m.getMenuInfo())
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user