535 lines
13 KiB
Go
535 lines
13 KiB
Go
// Copyright 2010 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 (
|
|
"unsafe"
|
|
|
|
"github.com/lxn/win"
|
|
)
|
|
|
|
type HatchStyle int
|
|
|
|
const (
|
|
HatchHorizontal HatchStyle = win.HS_HORIZONTAL
|
|
HatchVertical HatchStyle = win.HS_VERTICAL
|
|
HatchForwardDiagonal HatchStyle = win.HS_FDIAGONAL
|
|
HatchBackwardDiagonal HatchStyle = win.HS_BDIAGONAL
|
|
HatchCross HatchStyle = win.HS_CROSS
|
|
HatchDiagonalCross HatchStyle = win.HS_DIAGCROSS
|
|
)
|
|
|
|
type SystemColor int
|
|
|
|
const (
|
|
SysColor3DDkShadow SystemColor = win.COLOR_3DDKSHADOW
|
|
SysColor3DFace SystemColor = win.COLOR_3DFACE
|
|
SysColor3DHighlight SystemColor = win.COLOR_3DHIGHLIGHT
|
|
SysColor3DLight SystemColor = win.COLOR_3DLIGHT
|
|
SysColor3DShadow SystemColor = win.COLOR_3DSHADOW
|
|
SysColorActiveBorder SystemColor = win.COLOR_ACTIVEBORDER
|
|
SysColorActiveCaption SystemColor = win.COLOR_ACTIVECAPTION
|
|
SysColorAppWorkspace SystemColor = win.COLOR_APPWORKSPACE
|
|
SysColorBackground SystemColor = win.COLOR_BACKGROUND
|
|
SysColorDesktop SystemColor = win.COLOR_DESKTOP
|
|
SysColorBtnFace SystemColor = win.COLOR_BTNFACE
|
|
SysColorBtnHighlight SystemColor = win.COLOR_BTNHIGHLIGHT
|
|
SysColorBtnShadow SystemColor = win.COLOR_BTNSHADOW
|
|
SysColorBtnText SystemColor = win.COLOR_BTNTEXT
|
|
SysColorCaptionText SystemColor = win.COLOR_CAPTIONTEXT
|
|
SysColorGrayText SystemColor = win.COLOR_GRAYTEXT
|
|
SysColorHighlight SystemColor = win.COLOR_HIGHLIGHT
|
|
SysColorHighlightText SystemColor = win.COLOR_HIGHLIGHTTEXT
|
|
SysColorInactiveBorder SystemColor = win.COLOR_INACTIVEBORDER
|
|
SysColorInactiveCaption SystemColor = win.COLOR_INACTIVECAPTION
|
|
SysColorInactiveCaptionText SystemColor = win.COLOR_INACTIVECAPTIONTEXT
|
|
SysColorInfoBk SystemColor = win.COLOR_INFOBK
|
|
SysColorInfoText SystemColor = win.COLOR_INFOTEXT
|
|
SysColorMenu SystemColor = win.COLOR_MENU
|
|
SysColorMenuText SystemColor = win.COLOR_MENUTEXT
|
|
SysColorScrollBar SystemColor = win.COLOR_SCROLLBAR
|
|
SysColorWindow SystemColor = win.COLOR_WINDOW
|
|
SysColorWindowFrame SystemColor = win.COLOR_WINDOWFRAME
|
|
SysColorWindowText SystemColor = win.COLOR_WINDOWTEXT
|
|
SysColorHotLight SystemColor = win.COLOR_HOTLIGHT
|
|
SysColorGradientActiveCaption SystemColor = win.COLOR_GRADIENTACTIVECAPTION
|
|
SysColorGradientInactiveCaption SystemColor = win.COLOR_GRADIENTINACTIVECAPTION
|
|
)
|
|
|
|
type Brush interface {
|
|
Dispose()
|
|
handle() win.HBRUSH
|
|
logbrush() *win.LOGBRUSH
|
|
attachWindow(wb *WindowBase)
|
|
detachWindow(wb *WindowBase)
|
|
simple() bool
|
|
}
|
|
|
|
type perWindowBrush interface {
|
|
Brush
|
|
delegateForWindow(wb *WindowBase) Brush
|
|
}
|
|
|
|
type windowBrushInfo struct {
|
|
SizeChangedHandle int
|
|
Delegate *BitmapBrush
|
|
}
|
|
|
|
type brushBase struct {
|
|
hBrush win.HBRUSH
|
|
wb2info map[*WindowBase]*windowBrushInfo
|
|
}
|
|
|
|
func (bb *brushBase) Dispose() {
|
|
if bb.hBrush != 0 {
|
|
win.DeleteObject(win.HGDIOBJ(bb.hBrush))
|
|
|
|
bb.hBrush = 0
|
|
}
|
|
}
|
|
|
|
func (bb *brushBase) handle() win.HBRUSH {
|
|
return bb.hBrush
|
|
}
|
|
|
|
func (bb *brushBase) attachWindow(wb *WindowBase) {
|
|
if wb == nil {
|
|
return
|
|
}
|
|
|
|
if bb.wb2info == nil {
|
|
bb.wb2info = make(map[*WindowBase]*windowBrushInfo)
|
|
}
|
|
|
|
bb.wb2info[wb] = nil
|
|
}
|
|
|
|
func (bb *brushBase) detachWindow(wb *WindowBase) {
|
|
if bb.wb2info == nil || wb == nil {
|
|
return
|
|
}
|
|
|
|
delete(bb.wb2info, wb)
|
|
|
|
if len(bb.wb2info) == 0 {
|
|
bb.Dispose()
|
|
}
|
|
}
|
|
|
|
type nullBrush struct {
|
|
brushBase
|
|
}
|
|
|
|
func newNullBrush() *nullBrush {
|
|
lb := &win.LOGBRUSH{LbStyle: win.BS_NULL}
|
|
|
|
hBrush := win.CreateBrushIndirect(lb)
|
|
if hBrush == 0 {
|
|
panic("failed to create null brush")
|
|
}
|
|
|
|
return &nullBrush{brushBase: brushBase{hBrush: hBrush}}
|
|
}
|
|
|
|
func (b *nullBrush) Dispose() {
|
|
if b == nullBrushSingleton {
|
|
return
|
|
}
|
|
|
|
b.brushBase.Dispose()
|
|
}
|
|
|
|
func (*nullBrush) logbrush() *win.LOGBRUSH {
|
|
return &win.LOGBRUSH{LbStyle: win.BS_NULL}
|
|
}
|
|
|
|
func (*nullBrush) simple() bool {
|
|
return true
|
|
}
|
|
|
|
var (
|
|
nullBrushSingleton Brush
|
|
sysColorBtnFaceBrush *SystemColorBrush
|
|
)
|
|
|
|
func NullBrush() Brush {
|
|
return nullBrushSingleton
|
|
}
|
|
|
|
type SystemColorBrush struct {
|
|
brushBase
|
|
sysColor SystemColor
|
|
}
|
|
|
|
func init() {
|
|
AppendToWalkInit(func() {
|
|
nullBrushSingleton = newNullBrush()
|
|
sysColorBtnFaceBrush, _ = NewSystemColorBrush(SysColorBtnFace)
|
|
})
|
|
}
|
|
|
|
func NewSystemColorBrush(sysColor SystemColor) (*SystemColorBrush, error) {
|
|
hBrush := win.GetSysColorBrush(int(sysColor))
|
|
if hBrush == 0 {
|
|
return nil, newError("GetSysColorBrush failed")
|
|
}
|
|
|
|
return &SystemColorBrush{brushBase: brushBase{hBrush: hBrush}, sysColor: sysColor}, nil
|
|
}
|
|
|
|
func (b *SystemColorBrush) Color() Color {
|
|
return Color(win.GetSysColor(int(b.sysColor)))
|
|
}
|
|
|
|
func (b *SystemColorBrush) SystemColor() SystemColor {
|
|
return b.sysColor
|
|
}
|
|
|
|
func (*SystemColorBrush) Dispose() {
|
|
// nop
|
|
}
|
|
|
|
func (b *SystemColorBrush) logbrush() *win.LOGBRUSH {
|
|
return &win.LOGBRUSH{
|
|
LbStyle: win.BS_SOLID,
|
|
LbColor: win.COLORREF(win.GetSysColor(int(b.sysColor))),
|
|
}
|
|
}
|
|
|
|
func (*SystemColorBrush) simple() bool {
|
|
return true
|
|
}
|
|
|
|
type SolidColorBrush struct {
|
|
brushBase
|
|
color Color
|
|
}
|
|
|
|
func NewSolidColorBrush(color Color) (*SolidColorBrush, error) {
|
|
lb := &win.LOGBRUSH{LbStyle: win.BS_SOLID, LbColor: win.COLORREF(color)}
|
|
|
|
hBrush := win.CreateBrushIndirect(lb)
|
|
if hBrush == 0 {
|
|
return nil, newError("CreateBrushIndirect failed")
|
|
}
|
|
|
|
return &SolidColorBrush{brushBase: brushBase{hBrush: hBrush}, color: color}, nil
|
|
}
|
|
|
|
func (b *SolidColorBrush) Color() Color {
|
|
return b.color
|
|
}
|
|
|
|
func (b *SolidColorBrush) logbrush() *win.LOGBRUSH {
|
|
return &win.LOGBRUSH{LbStyle: win.BS_SOLID, LbColor: win.COLORREF(b.color)}
|
|
}
|
|
|
|
func (*SolidColorBrush) simple() bool {
|
|
return true
|
|
}
|
|
|
|
type HatchBrush struct {
|
|
brushBase
|
|
color Color
|
|
style HatchStyle
|
|
}
|
|
|
|
func NewHatchBrush(color Color, style HatchStyle) (*HatchBrush, error) {
|
|
lb := &win.LOGBRUSH{LbStyle: win.BS_HATCHED, LbColor: win.COLORREF(color), LbHatch: uintptr(style)}
|
|
|
|
hBrush := win.CreateBrushIndirect(lb)
|
|
if hBrush == 0 {
|
|
return nil, newError("CreateBrushIndirect failed")
|
|
}
|
|
|
|
return &HatchBrush{brushBase: brushBase{hBrush: hBrush}, color: color, style: style}, nil
|
|
}
|
|
|
|
func (b *HatchBrush) Color() Color {
|
|
return b.color
|
|
}
|
|
|
|
func (b *HatchBrush) logbrush() *win.LOGBRUSH {
|
|
return &win.LOGBRUSH{LbStyle: win.BS_HATCHED, LbColor: win.COLORREF(b.color), LbHatch: uintptr(b.style)}
|
|
}
|
|
|
|
func (b *HatchBrush) Style() HatchStyle {
|
|
return b.style
|
|
}
|
|
|
|
func (b *HatchBrush) simple() bool {
|
|
return false
|
|
}
|
|
|
|
type BitmapBrush struct {
|
|
brushBase
|
|
bitmap *Bitmap
|
|
}
|
|
|
|
func NewBitmapBrush(bitmap *Bitmap) (*BitmapBrush, error) {
|
|
if bitmap == nil {
|
|
return nil, newError("bitmap cannot be nil")
|
|
}
|
|
|
|
hBrush := win.CreatePatternBrush(bitmap.hBmp)
|
|
if hBrush == 0 {
|
|
return nil, newError("CreatePatternBrush failed")
|
|
}
|
|
|
|
return &BitmapBrush{brushBase: brushBase{hBrush: hBrush}, bitmap: bitmap}, nil
|
|
}
|
|
|
|
func (b *BitmapBrush) logbrush() *win.LOGBRUSH {
|
|
return &win.LOGBRUSH{LbStyle: win.BS_DIBPATTERN, LbColor: win.DIB_RGB_COLORS, LbHatch: uintptr(b.bitmap.hPackedDIB)}
|
|
}
|
|
|
|
func (b *BitmapBrush) Bitmap() *Bitmap {
|
|
return b.bitmap
|
|
}
|
|
|
|
func (b *BitmapBrush) simple() bool {
|
|
return false
|
|
}
|
|
|
|
type GradientStop struct {
|
|
Offset float64
|
|
Color Color
|
|
}
|
|
|
|
type GradientVertex struct {
|
|
X float64
|
|
Y float64
|
|
Color Color
|
|
}
|
|
|
|
type GradientTriangle struct {
|
|
Vertex1 int
|
|
Vertex2 int
|
|
Vertex3 int
|
|
}
|
|
|
|
type GradientBrush struct {
|
|
brushBase
|
|
mainDelegate *BitmapBrush
|
|
vertexes []GradientVertex
|
|
triangles []GradientTriangle
|
|
orientation gradientOrientation
|
|
absolute bool
|
|
}
|
|
|
|
type gradientOrientation int
|
|
|
|
const (
|
|
gradientOrientationNone gradientOrientation = iota
|
|
gradientOrientationHorizontal
|
|
gradientOrientationVertical
|
|
)
|
|
|
|
func NewHorizontalGradientBrush(stops []GradientStop) (*GradientBrush, error) {
|
|
return newGradientBrushWithOrientation(stops, gradientOrientationHorizontal)
|
|
}
|
|
|
|
func NewVerticalGradientBrush(stops []GradientStop) (*GradientBrush, error) {
|
|
return newGradientBrushWithOrientation(stops, gradientOrientationVertical)
|
|
}
|
|
|
|
func newGradientBrushWithOrientation(stops []GradientStop, orientation gradientOrientation) (*GradientBrush, error) {
|
|
if len(stops) < 2 {
|
|
return nil, newError("at least 2 stops are required")
|
|
}
|
|
|
|
var vertexes []GradientVertex
|
|
var triangles []GradientTriangle
|
|
|
|
for i, stop := range stops {
|
|
var x0, y0, x1, y1 float64
|
|
if orientation == gradientOrientationHorizontal {
|
|
x0 = stop.Offset
|
|
x1 = stop.Offset
|
|
y1 = 1.0
|
|
} else {
|
|
y0 = stop.Offset
|
|
x1 = 1.0
|
|
y1 = stop.Offset
|
|
}
|
|
|
|
vertexes = append(vertexes, GradientVertex{X: x0, Y: y0, Color: stop.Color})
|
|
vertexes = append(vertexes, GradientVertex{X: x1, Y: y1, Color: stop.Color})
|
|
|
|
if i > 0 {
|
|
triangles = append(triangles, GradientTriangle{Vertex1: i*2 - 2, Vertex2: i*2 + 1, Vertex3: i*2 - 1})
|
|
triangles = append(triangles, GradientTriangle{Vertex1: i*2 - 2, Vertex2: i * 2, Vertex3: i*2 + 1})
|
|
}
|
|
}
|
|
|
|
return newGradientBrush(vertexes, triangles, orientation)
|
|
}
|
|
|
|
func NewGradientBrush(vertexes []GradientVertex, triangles []GradientTriangle) (*GradientBrush, error) {
|
|
if len(vertexes) < 3 {
|
|
return nil, newError("at least 3 vertexes are required")
|
|
}
|
|
|
|
if len(triangles) < 1 {
|
|
return nil, newError("at least 1 triangle is required")
|
|
}
|
|
|
|
return newGradientBrush(vertexes, triangles, gradientOrientationNone)
|
|
}
|
|
|
|
func newGradientBrush(vertexes []GradientVertex, triangles []GradientTriangle, orientation gradientOrientation) (*GradientBrush, error) {
|
|
var size Size
|
|
for _, v := range vertexes {
|
|
size = maxSize(size, Size{int(v.X), int(v.Y)})
|
|
}
|
|
|
|
gb := &GradientBrush{vertexes: vertexes, triangles: triangles, orientation: orientation, absolute: size.Width > 1 || size.Height > 1}
|
|
|
|
if gb.absolute {
|
|
bb, err := gb.create(size)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
gb.mainDelegate = bb
|
|
gb.hBrush = bb.hBrush
|
|
}
|
|
|
|
return gb, nil
|
|
}
|
|
|
|
func (b *GradientBrush) logbrush() *win.LOGBRUSH {
|
|
if b.mainDelegate == nil {
|
|
return nil
|
|
}
|
|
|
|
return b.mainDelegate.logbrush()
|
|
}
|
|
|
|
func (*GradientBrush) simple() bool {
|
|
return false
|
|
}
|
|
|
|
// create creates a gradient brush at given size in native pixels.
|
|
func (b *GradientBrush) create(size Size) (*BitmapBrush, error) {
|
|
var disposables Disposables
|
|
defer disposables.Treat()
|
|
|
|
switch b.orientation {
|
|
case gradientOrientationHorizontal:
|
|
size.Height = 1
|
|
|
|
case gradientOrientationVertical:
|
|
size.Width = 1
|
|
}
|
|
|
|
bitmap, err := NewBitmapForDPI(size, 96) // Size is in native pixels and bitmap is used for brush only => DPI is not used anywhere.
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
disposables.Add(bitmap)
|
|
|
|
canvas, err := NewCanvasFromImage(bitmap)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer canvas.Dispose()
|
|
|
|
var scaleX, scaleY float64
|
|
if b.absolute {
|
|
scaleX, scaleY = 1, 1
|
|
} else {
|
|
scaleX, scaleY = float64(size.Width), float64(size.Height)
|
|
}
|
|
|
|
vertexes := make([]win.TRIVERTEX, len(b.vertexes))
|
|
for i, src := range b.vertexes {
|
|
dst := &vertexes[i]
|
|
|
|
dst.X = int32(src.X * scaleX)
|
|
dst.Y = int32(src.Y * scaleY)
|
|
dst.Red = uint16(src.Color.R()) * 256
|
|
dst.Green = uint16(src.Color.G()) * 256
|
|
dst.Blue = uint16(src.Color.B()) * 256
|
|
}
|
|
|
|
triangles := make([]win.GRADIENT_TRIANGLE, len(b.triangles))
|
|
for i, src := range b.triangles {
|
|
dst := &triangles[i]
|
|
|
|
dst.Vertex1 = uint32(src.Vertex1)
|
|
dst.Vertex2 = uint32(src.Vertex2)
|
|
dst.Vertex3 = uint32(src.Vertex3)
|
|
}
|
|
|
|
if !win.GradientFill(canvas.hdc, &vertexes[0], uint32(len(vertexes)), unsafe.Pointer(&triangles[0]), uint32(len(triangles)), win.GRADIENT_FILL_TRIANGLE) {
|
|
return nil, newError("GradientFill failed")
|
|
}
|
|
|
|
disposables.Spare()
|
|
|
|
return NewBitmapBrush(bitmap)
|
|
}
|
|
|
|
func (b *GradientBrush) attachWindow(wb *WindowBase) {
|
|
b.brushBase.attachWindow(wb)
|
|
|
|
if b.absolute {
|
|
return
|
|
}
|
|
|
|
var info *windowBrushInfo
|
|
|
|
update := func() {
|
|
if bb, err := b.create(wb.window.ClientBoundsPixels().Size()); err == nil {
|
|
if info.Delegate != nil {
|
|
info.Delegate.bitmap.Dispose()
|
|
info.Delegate.Dispose()
|
|
}
|
|
|
|
info.Delegate = bb
|
|
|
|
wb.Invalidate()
|
|
}
|
|
}
|
|
|
|
info = &windowBrushInfo{
|
|
SizeChangedHandle: wb.SizeChanged().Attach(update),
|
|
}
|
|
|
|
update()
|
|
|
|
b.wb2info[wb] = info
|
|
}
|
|
|
|
func (b *GradientBrush) detachWindow(wb *WindowBase) {
|
|
if !b.absolute {
|
|
if info, ok := b.wb2info[wb]; ok {
|
|
if info.Delegate != nil {
|
|
info.Delegate.bitmap.Dispose()
|
|
info.Delegate.Dispose()
|
|
}
|
|
|
|
wb.SizeChanged().Detach(info.SizeChangedHandle)
|
|
}
|
|
}
|
|
|
|
b.brushBase.detachWindow(wb)
|
|
}
|
|
|
|
func (b *GradientBrush) delegateForWindow(wb *WindowBase) Brush {
|
|
if b.absolute {
|
|
return b.mainDelegate
|
|
}
|
|
|
|
if info, ok := b.wb2info[wb]; ok && info.Delegate != nil {
|
|
return info.Delegate
|
|
}
|
|
|
|
return nil
|
|
}
|