Skip to content

Commit

Permalink
Initial menu item bitmap support
Browse files Browse the repository at this point in the history
  • Loading branch information
leaanthony committed Oct 21, 2023
1 parent fb17ec8 commit 36b4b36
Show file tree
Hide file tree
Showing 13 changed files with 140 additions and 11 deletions.
6 changes: 5 additions & 1 deletion v3/examples/menu/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,8 @@ go run .

# Known Issues

- [Resize cursor still visible when not resizable](https://github.com/orgs/wailsapp/projects/6/views/1?pane=issue&itemId=40962163)
- [Resize cursor still visible when not resizable](https://github.com/orgs/wailsapp/projects/6/views/1?pane=issue&itemId=40962163)

---

Icon attribution: [Click icons created by kusumapotter - Flaticon](https://www.flaticon.com/free-icons/click)
Binary file added v3/examples/menu/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 4 additions & 1 deletion v3/examples/menu/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ import (
"github.com/wailsapp/wails/v3/pkg/application"
)

//go:embed icon.png
var clickBitmap []byte

func main() {

app := application.New(application.Options{
Expand All @@ -32,7 +35,7 @@ func main() {
myMenu.Add("Not Enabled").SetEnabled(false)

// Click callbacks
myMenu.Add("Click Me!").OnClick(func(ctx *application.Context) {
myMenu.Add("Click Me!").SetBitmap(clickBitmap).OnClick(func(ctx *application.Context) {
switch ctx.ClickedMenuItem().Label() {
case "Click Me!":
ctx.ClickedMenuItem().SetLabel("Thanks mate!")
Expand Down
7 changes: 7 additions & 0 deletions v3/pkg/application/linux_cgo.go
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,13 @@ func menuItemSetLabel(widget pointer, label string) {
C.free(unsafe.Pointer(value))
}

func menuItemSetBitmap(widget pointer, data []byte) {
image := C.gdk_pixbuf_new_from_inline(C.int(len(data)), (*C.guchar)(unsafe.Pointer(&data[0])), C.FALSE, nil)
imageWidget := C.gtk_image_new_from_pixbuf(image)
C.gtk_image_set_pixel_size((*C.GtkImage)(imageWidget), 16)
C.gtk_menu_item_set_image((*C.GtkMenuItem)(widget), imageWidget)
}

func menuItemSetToolTip(widget pointer, tooltip string) {
value := C.CString(tooltip)
C.gtk_widget_set_tooltip_text(
Expand Down
3 changes: 3 additions & 0 deletions v3/pkg/application/menu_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,9 @@ func (m *macosMenu) processMenu(parent unsafe.Pointer, menu *Menu) {
case separator:
C.addMenuSeparator(parent)
}
if item.bitmap != nil {
C.setMenuItemBitmap(item.impl, (*C.uchar)(&item.bitmap[0]), C.int(len(item.bitmap)))
}

}
}
Expand Down
3 changes: 3 additions & 0 deletions v3/pkg/application/menu_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ func (w *windowsMenu) processMenu(parentMenu w32.HMENU, inputMenu *Menu) {
var menuText = w32.MustStringToUTF16Ptr(item.Label())

w32.AppendMenu(parentMenu, flags, uintptr(itemID), menuText)
if item.bitmap != nil {
w32.SetMenuIcons(parentMenu, itemID, item.bitmap, nil)
}
}
}

Expand Down
10 changes: 10 additions & 0 deletions v3/pkg/application/menuitem.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ type menuItemImpl interface {
setChecked(checked bool)
setAccelerator(accelerator *accelerator)
setHidden(hidden bool)
setBitmap(bitmap []byte)
}

type MenuItem struct {
Expand All @@ -49,6 +50,7 @@ type MenuItem struct {
disabled bool
checked bool
hidden bool
bitmap []byte
submenu *Menu
callback func(*Context)
itemType menuItemType
Expand Down Expand Up @@ -259,6 +261,14 @@ func (m *MenuItem) SetEnabled(enabled bool) *MenuItem {
return m
}

func (m *MenuItem) SetBitmap(bitmap []byte) *MenuItem {
m.bitmap = bitmap
if m.impl != nil {
m.impl.setBitmap(bitmap)
}
return m
}

func (m *MenuItem) SetChecked(checked bool) *MenuItem {
m.checked = checked
if m.impl != nil {
Expand Down
27 changes: 18 additions & 9 deletions v3/pkg/application/menuitem_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -332,40 +332,49 @@ static void showAll(void) {
[[NSApplication sharedApplication] unhideAllApplications:nil];
}
static void setMenuItemBitmap(void* nsMenuItem, unsigned char *bitmap, int length) {
MenuItem *menuItem = (MenuItem *)nsMenuItem;
NSImage *image = [[NSImage alloc] initWithData:[NSData dataWithBytes:bitmap length:length]];
[menuItem setImage:image];
}
*/
import "C"
import (
"runtime"
"unsafe"
)

type windowsMenuItem struct {
type macosMenuItem struct {
menuItem *MenuItem

nsMenuItem unsafe.Pointer
}

func (m windowsMenuItem) setTooltip(tooltip string) {
func (m macosMenuItem) setTooltip(tooltip string) {
C.setMenuItemTooltip(m.nsMenuItem, C.CString(tooltip))
}

func (m windowsMenuItem) setLabel(s string) {
func (m macosMenuItem) setLabel(s string) {
C.setMenuItemLabel(m.nsMenuItem, C.CString(s))
}

func (m windowsMenuItem) setDisabled(disabled bool) {
func (m macosMenuItem) setDisabled(disabled bool) {
C.setMenuItemDisabled(m.nsMenuItem, C.bool(disabled))
}

func (m windowsMenuItem) setChecked(checked bool) {
func (m macosMenuItem) setChecked(checked bool) {
C.setMenuItemChecked(m.nsMenuItem, C.bool(checked))
}

func (m windowsMenuItem) setHidden(hidden bool) {
func (m macosMenuItem) setHidden(hidden bool) {
C.setMenuItemHidden(m.nsMenuItem, C.bool(hidden))
}

func (m windowsMenuItem) setAccelerator(accelerator *accelerator) {
func (m macosMenuItem) setBitmap(bitmap []byte) {
C.setMenuItemBitmap(m.nsMenuItem, (*C.uchar)(&bitmap[0]), C.int(len(bitmap)))
}

func (m macosMenuItem) setAccelerator(accelerator *accelerator) {
// Set the keyboard shortcut of the menu item
var modifier C.int
var key *C.char
Expand All @@ -378,8 +387,8 @@ func (m windowsMenuItem) setAccelerator(accelerator *accelerator) {
C.setMenuItemKeyEquivalent(m.nsMenuItem, key, modifier)
}

func newMenuItemImpl(item *MenuItem) *windowsMenuItem {
result := &windowsMenuItem{
func newMenuItemImpl(item *MenuItem) *macosMenuItem {
result := &macosMenuItem{
menuItem: item,
}

Expand Down
7 changes: 7 additions & 0 deletions v3/pkg/application/menuitem_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@ func (l linuxMenuItem) blockSignal() {
menuItemSignalBlock(l.native, l.handlerId, true)
}
}
func (l linuxMenuItem) setBitmap(data []byte) {
InvokeSync(func() {
l.blockSignal()
defer l.unBlockSignal()
menuItemSetBitmap(l.native, data)
})
}

func (l linuxMenuItem) unBlockSignal() {
if l.handlerId != 0 {
Expand Down
14 changes: 14 additions & 0 deletions v3/pkg/application/menuitem_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,20 @@ func (m *windowsMenuItem) setAccelerator(accelerator *accelerator) {
//C.setMenuItemKeyEquivalent(m.nsMenuItem, key, modifier)
}

func (m *windowsMenuItem) setBitmap(bitmap []byte) {
if m.menuItem.bitmap == nil {
return
}

// Set the icon
err := w32.SetMenuIcons(m.hMenu, m.id, bitmap, nil)
if err != nil {
globalApplication.error("Unable to set bitmap on menu item", "error", err.Error())
return
}
m.update()
}

func newMenuItemImpl(item *MenuItem, parentMenu w32.HMENU, ID int) *windowsMenuItem {
result := &windowsMenuItem{
menuItem: item,
Expand Down
6 changes: 6 additions & 0 deletions v3/pkg/application/popupmenu_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,12 @@ func (p *Win32Menu) buildMenu(parentMenu w32.HMENU, inputMenu *Menu) {
if !ok {
w32.Fatal(fmt.Sprintf("Error adding menu item: %s", menuText))
}
if item.bitmap != nil {
err := w32.SetMenuIcons(parentMenu, itemID, item.bitmap, nil)
if err != nil {
w32.Fatal(fmt.Sprintf("Error setting menu icons: %s", err.Error()))
}
}

item.impl = menuItemImpl
}
Expand Down
46 changes: 46 additions & 0 deletions v3/pkg/w32/icon.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@
package w32

import (
"bytes"
"fmt"
"image"
"image/draw"
"image/png"
"unsafe"
)

Expand Down Expand Up @@ -89,3 +93,45 @@ func CreateLargeHIconFromImage(fileData []byte) (HICON, error) {
func SetWindowIcon(hwnd HWND, icon HICON) {
SendMessage(hwnd, WM_SETICON, ICON_SMALL, uintptr(icon))
}

func pngToImage(data []byte) (*image.RGBA, error) {
img, err := png.Decode(bytes.NewReader(data))
if err != nil {
return nil, err
}

bounds := img.Bounds()
rgba := image.NewRGBA(bounds)
draw.Draw(rgba, bounds, img, bounds.Min, draw.Src)
return rgba, nil
}

func SetMenuIcons(parentMenu HMENU, itemID int, unchecked []byte, checked []byte) error {
if unchecked == nil {
return fmt.Errorf("invalid unchecked bitmap")
}
var err error
var uncheckedIcon, checkedIcon HBITMAP
var uncheckedImage, checkedImage *image.RGBA
uncheckedImage, err = pngToImage(unchecked)
if err != nil {
return err
}
uncheckedIcon, err = CreateHBITMAPFromImage(uncheckedImage)
if err != nil {
return err
}
if checked != nil {
checkedImage, err = pngToImage(checked)
if err != nil {
return err
}
checkedIcon, err = CreateHBITMAPFromImage(checkedImage)
if err != nil {
return err
}
} else {
checkedIcon = uncheckedIcon
}
return SetMenuItemBitmaps(parentMenu, uint32(itemID), MF_BYCOMMAND, checkedIcon, uncheckedIcon)
}
17 changes: 17 additions & 0 deletions v3/pkg/w32/user32.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ package w32

import (
"fmt"
"golang.org/x/sys/windows"
"runtime"
"syscall"
"unsafe"
Expand Down Expand Up @@ -169,6 +170,8 @@ var (

procFlashWindowEx = moduser32.NewProc("FlashWindowEx")

procSetMenuItemBitmaps = moduser32.NewProc("SetMenuItemBitmaps")

mainThread HANDLE
)

Expand All @@ -190,6 +193,20 @@ func RegisterClassEx(wndClassEx *WNDCLASSEX) ATOM {
return ATOM(ret)
}

func SetMenuItemBitmaps(hMenu HMENU, uPosition, uFlags uint32, hBitmapUnchecked HBITMAP, hBitmapChecked HBITMAP) error {
ret, _, _ := procSetMenuItemBitmaps.Call(
hMenu,
uintptr(uPosition),
uintptr(uFlags),
hBitmapUnchecked,
hBitmapChecked)

if ret == 0 {
return windows.GetLastError()
}
return nil
}

func GetDesktopWindow() HWND {
ret, _, _ := procGetDesktopWindow.Call()
return ret
Expand Down

0 comments on commit 36b4b36

Please sign in to comment.