Initial menu item bitmap support

This commit is contained in:
Lea Anthony
2023-10-22 09:32:04 +11:00
parent fb17ec8064
commit 36b4b3695b
13 changed files with 140 additions and 11 deletions
+5 -1
View File
@@ -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 not shown.

After

Width:  |  Height:  |  Size: 323 B

+4 -1
View File
@@ -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{
@@ -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!")
+7
View File
@@ -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(
+3
View File
@@ -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)))
}
}
}
+3
View File
@@ -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)
}
}
}
+10
View File
@@ -40,6 +40,7 @@ type menuItemImpl interface {
setChecked(checked bool)
setAccelerator(accelerator *accelerator)
setHidden(hidden bool)
setBitmap(bitmap []byte)
}
type MenuItem struct {
@@ -49,6 +50,7 @@ type MenuItem struct {
disabled bool
checked bool
hidden bool
bitmap []byte
submenu *Menu
callback func(*Context)
itemType menuItemType
@@ -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 {
+18 -9
View File
@@ -332,6 +332,11 @@ 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 (
@@ -339,33 +344,37 @@ import (
"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
@@ -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,
}
+7
View File
@@ -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 {
+14
View File
@@ -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,
+6
View File
@@ -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
}
+46
View File
@@ -3,7 +3,11 @@
package w32
import (
"bytes"
"fmt"
"image"
"image/draw"
"image/png"
"unsafe"
)
@@ -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
View File
@@ -9,6 +9,7 @@ package w32
import (
"fmt"
"golang.org/x/sys/windows"
"runtime"
"syscall"
"unsafe"
@@ -169,6 +170,8 @@ var (
procFlashWindowEx = moduser32.NewProc("FlashWindowEx")
procSetMenuItemBitmaps = moduser32.NewProc("SetMenuItemBitmaps")
mainThread HANDLE
)
@@ -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