417 lines
11 KiB
Go
417 lines
11 KiB
Go
// Copyright 2011 The Walk Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
// +build windows
|
|
|
|
package walk
|
|
|
|
import (
|
|
"image"
|
|
"path/filepath"
|
|
"syscall"
|
|
"unsafe"
|
|
|
|
"golang.org/x/sys/windows"
|
|
|
|
"github.com/lxn/win"
|
|
)
|
|
|
|
// Icon is a bitmap that supports transparency and combining multiple
|
|
// variants of an image in different resolutions.
|
|
type Icon struct {
|
|
filePath string
|
|
index int
|
|
res *uint16
|
|
dpi2hIcon map[int]win.HICON
|
|
size96dpi Size
|
|
isStock bool
|
|
hasIndex bool
|
|
}
|
|
|
|
type ExtractableIcon interface {
|
|
FilePath_() string
|
|
Index_() int
|
|
Size_() int
|
|
}
|
|
|
|
func IconFrom(src interface{}, dpi int) (*Icon, error) {
|
|
if src == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
img, err := ImageFrom(src)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return iconCache.Icon(img, dpi)
|
|
}
|
|
|
|
func IconApplication() *Icon {
|
|
return stockIcon(win.IDI_APPLICATION)
|
|
}
|
|
|
|
func IconError() *Icon {
|
|
return stockIcon(win.IDI_ERROR)
|
|
}
|
|
|
|
func IconQuestion() *Icon {
|
|
return stockIcon(win.IDI_QUESTION)
|
|
}
|
|
|
|
func IconWarning() *Icon {
|
|
return stockIcon(win.IDI_WARNING)
|
|
}
|
|
|
|
func IconInformation() *Icon {
|
|
return stockIcon(win.IDI_INFORMATION)
|
|
}
|
|
|
|
func IconWinLogo() *Icon {
|
|
return stockIcon(win.IDI_WINLOGO)
|
|
}
|
|
|
|
func IconShield() *Icon {
|
|
return stockIcon(win.IDI_SHIELD)
|
|
}
|
|
|
|
func stockIcon(id uintptr) *Icon {
|
|
return &Icon{res: win.MAKEINTRESOURCE(id), size96dpi: defaultIconSize(), isStock: true}
|
|
}
|
|
|
|
// NewIconFromFile returns a new Icon, using the specified icon image file and default size.
|
|
func NewIconFromFile(filePath string) (*Icon, error) {
|
|
return NewIconFromFileWithSize(filePath, Size{})
|
|
}
|
|
|
|
// NewIconFromFileWithSize returns a new Icon, using the specified icon image file and size.
|
|
func NewIconFromFileWithSize(filePath string, size Size) (*Icon, error) {
|
|
if size.Width == 0 || size.Height == 0 {
|
|
size = defaultIconSize()
|
|
}
|
|
|
|
return checkNewIcon(&Icon{filePath: filePath, size96dpi: size})
|
|
}
|
|
|
|
// NewIconFromResource returns a new Icon of default size, using the specified icon resource.
|
|
func NewIconFromResource(name string) (*Icon, error) {
|
|
return NewIconFromResourceWithSize(name, Size{})
|
|
}
|
|
|
|
// NewIconFromResourceWithSize returns a new Icon of size size, using the specified icon resource.
|
|
func NewIconFromResourceWithSize(name string, size Size) (*Icon, error) {
|
|
return newIconFromResource(syscall.StringToUTF16Ptr(name), size)
|
|
}
|
|
|
|
// NewIconFromResourceId returns a new Icon of default size, using the specified icon resource.
|
|
func NewIconFromResourceId(id int) (*Icon, error) {
|
|
return NewIconFromResourceIdWithSize(id, Size{})
|
|
}
|
|
|
|
// NewIconFromResourceIdWithSize returns a new Icon of size size, using the specified icon resource.
|
|
func NewIconFromResourceIdWithSize(id int, size Size) (*Icon, error) {
|
|
return newIconFromResource(win.MAKEINTRESOURCE(uintptr(id)), size)
|
|
}
|
|
|
|
func newIconFromResource(res *uint16, size Size) (*Icon, error) {
|
|
if size.Width == 0 || size.Height == 0 {
|
|
size = defaultIconSize()
|
|
}
|
|
|
|
return checkNewIcon(&Icon{res: res, size96dpi: size})
|
|
}
|
|
|
|
// NewIconFromSysDLL returns a new Icon, as identified by index of
|
|
// size 16x16 from the system DLL identified by dllBaseName.
|
|
func NewIconFromSysDLL(dllBaseName string, index int) (*Icon, error) {
|
|
return NewIconFromSysDLLWithSize(dllBaseName, index, 16)
|
|
}
|
|
|
|
// NewIconFromSysDLLWithSize returns a new Icon, as identified by
|
|
// index of the desired size from the system DLL identified by dllBaseName.
|
|
func NewIconFromSysDLLWithSize(dllBaseName string, index, size int) (*Icon, error) {
|
|
system32, err := windows.GetSystemDirectory()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return checkNewIcon(&Icon{filePath: filepath.Join(system32, dllBaseName+".dll"), index: index, hasIndex: true, size96dpi: Size{size, size}})
|
|
}
|
|
|
|
// NewIconExtractedFromFile returns a new Icon, as identified by index of size 16x16 from filePath.
|
|
func NewIconExtractedFromFile(filePath string, index, _ int) (*Icon, error) {
|
|
return checkNewIcon(&Icon{filePath: filePath, index: index, hasIndex: true, size96dpi: Size{16, 16}})
|
|
}
|
|
|
|
// NewIconExtractedFromFileWithSize returns a new Icon, as identified by index of the desired size from filePath.
|
|
func NewIconExtractedFromFileWithSize(filePath string, index, size int) (*Icon, error) {
|
|
return checkNewIcon(&Icon{filePath: filePath, index: index, hasIndex: true, size96dpi: Size{size, size}})
|
|
}
|
|
|
|
// NewIconFromImage returns a new Icon at 96dpi, using the specified image.Image as source.
|
|
//
|
|
// Deprecated: Newer applications should use NewIconFromImageForDPI.
|
|
func NewIconFromImage(im image.Image) (ic *Icon, err error) {
|
|
return NewIconFromImageForDPI(im, 96)
|
|
}
|
|
|
|
// NewIconFromImageForDPI returns a new Icon at given DPI, using the specified image.Image as source.
|
|
func NewIconFromImageForDPI(im image.Image, dpi int) (ic *Icon, err error) {
|
|
hIcon, err := createAlphaCursorOrIconFromImage(im, image.Pt(0, 0), true)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
b := im.Bounds()
|
|
return newIconFromHICONAndSize(hIcon, SizeTo96DPI(Size{b.Dx(), b.Dy()}, dpi), dpi), nil
|
|
}
|
|
|
|
// NewIconFromImageWithSize returns a new Icon of the given size in native pixels, using the
|
|
// specified Image as source.
|
|
func NewIconFromImageWithSize(image Image, size Size) (*Icon, error) {
|
|
bmp, err := NewBitmapFromImageWithSize(image, size)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return NewIconFromBitmap(bmp)
|
|
}
|
|
|
|
func newIconFromImageForDPI(image Image, dpi int) (*Icon, error) {
|
|
size96dpi := image.Size()
|
|
size := SizeFrom96DPI(size96dpi, dpi)
|
|
|
|
bmp, err := NewBitmapFromImageWithSize(image, size)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
hIcon, err := createAlphaCursorOrIconFromBitmap(bmp, Point{}, true)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &Icon{dpi2hIcon: map[int]win.HICON{dpi: hIcon}, size96dpi: size96dpi}, nil
|
|
}
|
|
|
|
// NewIconFromBitmap returns a new Icon, using the specified Bitmap as source.
|
|
func NewIconFromBitmap(bmp *Bitmap) (ic *Icon, err error) {
|
|
hIcon, err := createAlphaCursorOrIconFromBitmap(bmp, Point{}, true)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return newIconFromHICONAndSize(hIcon, bmp.Size(), bmp.dpi), nil
|
|
}
|
|
|
|
// NewIconFromHICON returns a new Icon at 96dpi, using the specified win.HICON as source.
|
|
//
|
|
// Deprecated: Newer applications should use NewIconFromHICONForDPI.
|
|
func NewIconFromHICON(hIcon win.HICON) (ic *Icon, err error) {
|
|
return NewIconFromHICONForDPI(hIcon, 96)
|
|
}
|
|
|
|
// NewIconFromHICONForDPI returns a new Icon at given DPI, using the specified win.HICON as source.
|
|
func NewIconFromHICONForDPI(hIcon win.HICON, dpi int) (ic *Icon, err error) {
|
|
s, err := sizeFromHICON(hIcon)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return newIconFromHICONAndSize(hIcon, SizeTo96DPI(s, dpi), dpi), nil
|
|
}
|
|
|
|
func newIconFromHICONAndSize(hIcon win.HICON, size Size, dpi int) *Icon {
|
|
return &Icon{dpi2hIcon: map[int]win.HICON{dpi: hIcon}, size96dpi: size}
|
|
}
|
|
|
|
func checkNewIcon(icon *Icon) (*Icon, error) {
|
|
if _, err := icon.handleForDPIWithError(96); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return icon, nil
|
|
}
|
|
|
|
func (i *Icon) handleForDPI(dpi int) win.HICON {
|
|
hIcon, _ := i.handleForDPIWithError(dpi)
|
|
return hIcon
|
|
}
|
|
|
|
func (i *Icon) handleForDPIWithError(dpi int) (win.HICON, error) {
|
|
if i.dpi2hIcon == nil {
|
|
i.dpi2hIcon = make(map[int]win.HICON)
|
|
} else if handle, ok := i.dpi2hIcon[dpi]; ok {
|
|
return handle, nil
|
|
}
|
|
|
|
var hInst win.HINSTANCE
|
|
var name *uint16
|
|
if i.filePath != "" {
|
|
absFilePath, err := filepath.Abs(i.filePath)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
name = syscall.StringToUTF16Ptr(absFilePath)
|
|
} else {
|
|
if !i.isStock {
|
|
if hInst = win.GetModuleHandle(nil); hInst == 0 {
|
|
return 0, lastError("GetModuleHandle")
|
|
}
|
|
}
|
|
|
|
name = i.res
|
|
}
|
|
|
|
var size Size
|
|
if i.size96dpi.Width == 0 || i.size96dpi.Height == 0 {
|
|
size = SizeFrom96DPI(defaultIconSize(), dpi)
|
|
} else {
|
|
size = SizeFrom96DPI(i.size96dpi, dpi)
|
|
}
|
|
|
|
var hIcon win.HICON
|
|
|
|
if i.hasIndex {
|
|
win.SHDefExtractIcon(
|
|
name,
|
|
int32(i.index),
|
|
0,
|
|
nil,
|
|
&hIcon,
|
|
win.MAKELONG(0, uint16(size.Width)))
|
|
if hIcon == 0 {
|
|
return 0, newError("SHDefExtractIcon")
|
|
}
|
|
} else {
|
|
hr := win.HICON(win.LoadIconWithScaleDown(
|
|
hInst,
|
|
name,
|
|
int32(size.Width),
|
|
int32(size.Height),
|
|
&hIcon))
|
|
if hr < 0 || hIcon == 0 {
|
|
return 0, lastError("LoadIconWithScaleDown")
|
|
}
|
|
}
|
|
|
|
i.dpi2hIcon[dpi] = hIcon
|
|
|
|
return hIcon, nil
|
|
}
|
|
|
|
// Dispose releases the operating system resources associated with the Icon.
|
|
func (i *Icon) Dispose() {
|
|
if i.isStock || len(i.dpi2hIcon) == 0 {
|
|
return
|
|
}
|
|
|
|
for dpi, hIcon := range i.dpi2hIcon {
|
|
win.DestroyIcon(hIcon)
|
|
delete(i.dpi2hIcon, dpi)
|
|
}
|
|
}
|
|
|
|
func (i *Icon) draw(hdc win.HDC, location Point) error {
|
|
dpi := dpiForHDC(hdc)
|
|
size := SizeFrom96DPI(i.size96dpi, dpi)
|
|
|
|
return i.drawStretched(hdc, Rectangle{location.X, location.Y, size.Width, size.Height})
|
|
}
|
|
|
|
func (i *Icon) drawStretched(hdc win.HDC, bounds Rectangle) error {
|
|
dpi := int(float64(bounds.Width) / float64(i.size96dpi.Width) * 96.0)
|
|
|
|
hIcon := i.handleForDPI(dpi)
|
|
if hIcon == 0 {
|
|
var dpiAvailMax int
|
|
for dpiAvail, handle := range i.dpi2hIcon {
|
|
if dpiAvail > dpiAvailMax {
|
|
hIcon = handle
|
|
dpiAvailMax = dpiAvail
|
|
}
|
|
if dpiAvail > dpi {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
if !win.DrawIconEx(hdc, int32(bounds.X), int32(bounds.Y), hIcon, int32(bounds.Width), int32(bounds.Height), 0, 0, win.DI_NORMAL) {
|
|
return lastError("DrawIconEx")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Size returns icon size in 1/96" units.
|
|
func (i *Icon) Size() Size {
|
|
return i.size96dpi
|
|
}
|
|
|
|
// create an Alpha Icon or Cursor from an Image
|
|
// http://support.microsoft.com/kb/318876
|
|
func createAlphaCursorOrIconFromImage(im image.Image, hotspot image.Point, fIcon bool) (win.HICON, error) {
|
|
bmp, err := NewBitmapFromImage(im)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
defer bmp.Dispose()
|
|
|
|
return createAlphaCursorOrIconFromBitmap(bmp, Point{hotspot.X, hotspot.Y}, fIcon)
|
|
}
|
|
|
|
// createAlphaCursorOrIconFromBitmap creates a cursor/icon from a bitmap. hotspot coordinates are in native pixels.
|
|
func createAlphaCursorOrIconFromBitmap(bmp *Bitmap, hotspot Point, fIcon bool) (win.HICON, error) {
|
|
// Create an empty mask bitmap.
|
|
hMonoBitmap := win.CreateBitmap(int32(bmp.size.Width), int32(bmp.size.Height), 1, 1, nil)
|
|
if hMonoBitmap == 0 {
|
|
return 0, newError("CreateBitmap failed")
|
|
}
|
|
defer win.DeleteObject(win.HGDIOBJ(hMonoBitmap))
|
|
|
|
var ii win.ICONINFO
|
|
if fIcon {
|
|
ii.FIcon = win.TRUE
|
|
}
|
|
ii.XHotspot = uint32(hotspot.X)
|
|
ii.YHotspot = uint32(hotspot.Y)
|
|
ii.HbmMask = hMonoBitmap
|
|
ii.HbmColor = bmp.hBmp
|
|
|
|
// Create the alpha cursor with the alpha DIB section.
|
|
hIconOrCursor := win.CreateIconIndirect(&ii)
|
|
|
|
return hIconOrCursor, nil
|
|
}
|
|
|
|
// sizeFromHICON returns icon size in native pixels.
|
|
func sizeFromHICON(hIcon win.HICON) (Size, error) {
|
|
var ii win.ICONINFO
|
|
var bi win.BITMAPINFO
|
|
|
|
if !win.GetIconInfo(hIcon, &ii) {
|
|
return Size{}, lastError("GetIconInfo")
|
|
}
|
|
defer win.DeleteObject(win.HGDIOBJ(ii.HbmMask))
|
|
|
|
var hBmp win.HBITMAP
|
|
if ii.HbmColor != 0 {
|
|
hBmp = ii.HbmColor
|
|
|
|
defer win.DeleteObject(win.HGDIOBJ(ii.HbmColor))
|
|
} else {
|
|
hBmp = ii.HbmMask
|
|
}
|
|
|
|
if 0 == win.GetObject(win.HGDIOBJ(hBmp), unsafe.Sizeof(bi), unsafe.Pointer(&bi)) {
|
|
return Size{}, newError("GetObject")
|
|
}
|
|
|
|
return Size{int(bi.BmiHeader.BiWidth), int(bi.BmiHeader.BiHeight)}, nil
|
|
}
|
|
|
|
// defaultIconSize returns default small icon size in 1/92" units.
|
|
func defaultIconSize() Size {
|
|
return Size{int(win.GetSystemMetricsForDpi(win.SM_CXSMICON, 96)), int(win.GetSystemMetricsForDpi(win.SM_CYSMICON, 96))}
|
|
}
|