diff --git a/icon.go b/icon.go new file mode 100644 index 0000000..ecc377f --- /dev/null +++ b/icon.go @@ -0,0 +1,17 @@ +//go:build !windows +// +build !windows + +package webview + +import ( + "image" + "unsafe" +) + +type icons struct{} + +func (*icons) setIcon(window unsafe.Pointer, icon image.Image, kind IconKind) { +} + +func (*icons) free() { +} diff --git a/icon_windows.go b/icon_windows.go new file mode 100644 index 0000000..4e8af0e --- /dev/null +++ b/icon_windows.go @@ -0,0 +1,93 @@ +package webview + +/* +#cgo LDFLAGS: -lgdi32 + +#include + +LONG_PTR hIconToLongPtr(HICON hIcon) { + return (LONG_PTR)hIcon; +} +*/ +import "C" + +import ( + "image" + "sync" + "unsafe" +) + +type icons struct { + mutex sync.Mutex + icons []C.HICON +} + +func (c *icons) setIcon(window unsafe.Pointer, icon image.Image, kind IconKind) { + hIcon := prepareIcon(icon) + c.mutex.Lock() + defer c.mutex.Unlock() + if c.icons == nil { + c.icons = make([]C.HICON, maxIconKind) + } + previousHIcon := c.icons[kind] + c.icons[kind] = hIcon + C.SetClassLongPtr(C.HWND(window), prepareIconKind(kind), C.hIconToLongPtr(hIcon)) + if previousHIcon != nil { + C.DestroyIcon(previousHIcon) + } +} + +func (c *icons) free() { + c.mutex.Lock() + defer c.mutex.Unlock() + for i, hIcon := range c.icons { + if hIcon != nil { + C.DestroyIcon(hIcon) + c.icons[i] = nil + } + } +} + +func prepareIcon(icon image.Image) C.HICON { + width, height, mask, color := prepareIconData(icon) + iconInfo := C.ICONINFO{} + iconInfo.fIcon = C.TRUE + iconInfo.xHotspot = 0 + iconInfo.yHotspot = 0 + iconInfo.hbmMask = C.CreateBitmap(C.int(width), C.int(height), 1, 8, unsafe.Pointer(&mask[0])) + iconInfo.hbmColor = C.CreateBitmap(C.int(width), C.int(height), 1, 32, unsafe.Pointer(&color[0])) + hIcon := C.CreateIconIndirect(&iconInfo) + C.DeleteObject(C.HGDIOBJ(iconInfo.hbmMask)) + C.DeleteObject(C.HGDIOBJ(iconInfo.hbmColor)) + return hIcon +} + +func prepareIconData(img image.Image) (width, height int, mask, color []byte) { + bounds := img.Bounds() + width, height = bounds.Dx(), bounds.Dy() + mask = make([]byte, width*height) + color = make([]byte, 4*width*height) + for y, maskIndex, colorIndex := 0, 0, 0; y < height; y++ { + for x := 0; x < width; x++ { + r, g, b, a := img.At(x, y).RGBA() + color[colorIndex] = byte(b >> 8) + colorIndex++ + color[colorIndex] = byte(g >> 8) + colorIndex++ + color[colorIndex] = byte(r >> 8) + colorIndex++ + color[colorIndex] = byte(a >> 8) + colorIndex++ + mask[maskIndex] = byte(a >> 8) + maskIndex++ + } + } + return width, height, mask, color +} + +func prepareIconKind(kind IconKind) C.int { + if kind == IconKindSmaller { + return C.GCLP_HICONSM + } + return C.GCLP_HICON +} diff --git a/webview.go b/webview.go index 6acf90d..f5977b2 100644 --- a/webview.go +++ b/webview.go @@ -27,6 +27,7 @@ import "C" import ( "encoding/json" "errors" + "image" "reflect" "runtime" "sync" @@ -38,6 +39,15 @@ func init() { runtime.LockOSThread() } +// IconKind is used to specify usage of icon. +type IconKind int + +const ( + IconKindDefault = IconKind(iota) + IconKindSmaller + maxIconKind +) + // Hints are used to configure window sizing and resizing type Hint int @@ -78,6 +88,10 @@ type WebView interface { // NSWindow pointer, when using Win32 backend the pointer is HWND pointer. Window() unsafe.Pointer + // SetIcon updates the icon of the native window. Must be called from the UI + // thread. + SetIcon(icon image.Image, kind IconKind) + // SetTitle updates the title of the native window. Must be called from the UI // thread. SetTitle(title string) @@ -122,6 +136,7 @@ type WebView interface { type webview struct { w C.webview_t + i icons } var ( @@ -156,6 +171,7 @@ func NewWindow(debug bool, window unsafe.Pointer) WebView { func (w *webview) Destroy() { C.webview_destroy(w.w) + w.i.free() } func (w *webview) Run() { @@ -182,6 +198,10 @@ func (w *webview) SetHtml(html string) { C.webview_set_html(w.w, s) } +func (w *webview) SetIcon(icon image.Image, kind IconKind) { + w.i.setIcon(w.Window(), icon, kind) +} + func (w *webview) SetTitle(title string) { s := C.CString(title) defer C.free(unsafe.Pointer(s))