240 lines
5.6 KiB
Go
240 lines
5.6 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"
|
||
|
)
|
||
|
|
||
|
const customWidgetWindowClass = `\o/ Walk_CustomWidget_Class \o/`
|
||
|
|
||
|
func init() {
|
||
|
AppendToWalkInit(func() {
|
||
|
MustRegisterWindowClass(customWidgetWindowClass)
|
||
|
})
|
||
|
}
|
||
|
|
||
|
// PaintFunc paints custom widget content. updateBounds is specified in 1/96" or native pixels.
|
||
|
type PaintFunc func(canvas *Canvas, updateBounds Rectangle) error
|
||
|
|
||
|
type PaintMode int
|
||
|
|
||
|
const (
|
||
|
PaintNormal PaintMode = iota // erase background before PaintFunc
|
||
|
PaintNoErase // PaintFunc clears background, single buffered
|
||
|
PaintBuffered // PaintFunc clears background, double buffered
|
||
|
)
|
||
|
|
||
|
type CustomWidget struct {
|
||
|
WidgetBase
|
||
|
paint PaintFunc // in 1/96" units
|
||
|
paintPixels PaintFunc // in native pixels
|
||
|
invalidatesOnResize bool
|
||
|
paintMode PaintMode
|
||
|
}
|
||
|
|
||
|
// NewCustomWidget creates and initializes a new custom draw widget.
|
||
|
//
|
||
|
// Deprecated: PaintFunc is taking updateBounds parameter at 96dpi for backward compatibility with
|
||
|
// clients. On high-DPI displays this is too sparse and may incur a thin unpainted edge around
|
||
|
// control due to rounding errors. Newer applications should use NewCustomWidgetPixels.
|
||
|
func NewCustomWidget(parent Container, style uint, paint PaintFunc) (*CustomWidget, error) {
|
||
|
cw := &CustomWidget{paint: paint}
|
||
|
err := cw.init(parent, style)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
return cw, nil
|
||
|
}
|
||
|
|
||
|
// NewCustomWidgetPixels creates and initializes a new custom draw widget.
|
||
|
func NewCustomWidgetPixels(parent Container, style uint, paintPixels PaintFunc) (*CustomWidget, error) {
|
||
|
cw := &CustomWidget{paintPixels: paintPixels}
|
||
|
err := cw.init(parent, style)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
return cw, nil
|
||
|
}
|
||
|
|
||
|
func (cw *CustomWidget) init(parent Container, style uint) error {
|
||
|
if err := InitWidget(
|
||
|
cw,
|
||
|
parent,
|
||
|
customWidgetWindowClass,
|
||
|
win.WS_VISIBLE|uint32(style),
|
||
|
0); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// deprecated, use PaintMode
|
||
|
func (cw *CustomWidget) ClearsBackground() bool {
|
||
|
return cw.paintMode != PaintNormal
|
||
|
}
|
||
|
|
||
|
// deprecated, use SetPaintMode
|
||
|
func (cw *CustomWidget) SetClearsBackground(value bool) {
|
||
|
if value != cw.ClearsBackground() {
|
||
|
if value {
|
||
|
cw.paintMode = PaintNormal
|
||
|
} else {
|
||
|
cw.paintMode = PaintNoErase
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (cw *CustomWidget) InvalidatesOnResize() bool {
|
||
|
return cw.invalidatesOnResize
|
||
|
}
|
||
|
|
||
|
func (cw *CustomWidget) SetInvalidatesOnResize(value bool) {
|
||
|
cw.invalidatesOnResize = value
|
||
|
}
|
||
|
|
||
|
func (cw *CustomWidget) PaintMode() PaintMode {
|
||
|
return cw.paintMode
|
||
|
}
|
||
|
|
||
|
func (cw *CustomWidget) SetPaintMode(value PaintMode) {
|
||
|
cw.paintMode = value
|
||
|
}
|
||
|
|
||
|
func (cw *CustomWidget) WndProc(hwnd win.HWND, msg uint32, wParam, lParam uintptr) uintptr {
|
||
|
switch msg {
|
||
|
case win.WM_PAINT:
|
||
|
if cw.paint == nil && cw.paintPixels == nil {
|
||
|
newError("paint(Pixels) func is nil")
|
||
|
break
|
||
|
}
|
||
|
|
||
|
var ps win.PAINTSTRUCT
|
||
|
|
||
|
var hdc win.HDC
|
||
|
if wParam == 0 {
|
||
|
hdc = win.BeginPaint(cw.hWnd, &ps)
|
||
|
} else {
|
||
|
hdc = win.HDC(wParam)
|
||
|
}
|
||
|
if hdc == 0 {
|
||
|
newError("BeginPaint failed")
|
||
|
break
|
||
|
}
|
||
|
defer func() {
|
||
|
if wParam == 0 {
|
||
|
win.EndPaint(cw.hWnd, &ps)
|
||
|
}
|
||
|
}()
|
||
|
|
||
|
canvas, err := newCanvasFromHDC(hdc)
|
||
|
if err != nil {
|
||
|
newError("newCanvasFromHDC failed")
|
||
|
break
|
||
|
}
|
||
|
defer canvas.Dispose()
|
||
|
|
||
|
bounds := rectangleFromRECT(ps.RcPaint)
|
||
|
if cw.paintMode == PaintBuffered {
|
||
|
err = cw.bufferedPaint(canvas, bounds)
|
||
|
} else if cw.paintPixels != nil {
|
||
|
err = cw.paintPixels(canvas, bounds)
|
||
|
} else {
|
||
|
err = cw.paint(canvas, RectangleTo96DPI(bounds, cw.DPI()))
|
||
|
}
|
||
|
|
||
|
if err != nil {
|
||
|
newError("paint failed")
|
||
|
break
|
||
|
}
|
||
|
|
||
|
return 0
|
||
|
|
||
|
case win.WM_ERASEBKGND:
|
||
|
if cw.paintMode != PaintNormal {
|
||
|
return 1
|
||
|
}
|
||
|
|
||
|
case win.WM_PRINTCLIENT:
|
||
|
win.SendMessage(hwnd, win.WM_PAINT, wParam, lParam)
|
||
|
|
||
|
case win.WM_WINDOWPOSCHANGED:
|
||
|
wp := (*win.WINDOWPOS)(unsafe.Pointer(lParam))
|
||
|
|
||
|
if wp.Flags&win.SWP_NOSIZE != 0 {
|
||
|
break
|
||
|
}
|
||
|
|
||
|
if cw.invalidatesOnResize {
|
||
|
cw.Invalidate()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return cw.WidgetBase.WndProc(hwnd, msg, wParam, lParam)
|
||
|
}
|
||
|
|
||
|
// bufferedPaint draws widget on a memory buffer. updateBounds are in native pixels.
|
||
|
func (cw *CustomWidget) bufferedPaint(canvas *Canvas, updateBounds Rectangle) error {
|
||
|
hdc := win.CreateCompatibleDC(canvas.hdc)
|
||
|
if hdc == 0 {
|
||
|
return newError("CreateCompatibleDC failed")
|
||
|
}
|
||
|
defer win.DeleteDC(hdc)
|
||
|
|
||
|
buffered := Canvas{hdc: hdc, doNotDispose: true}
|
||
|
if _, err := buffered.init(); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
w, h := int32(updateBounds.Width), int32(updateBounds.Height)
|
||
|
if w < 1 {
|
||
|
w = 1
|
||
|
}
|
||
|
if h < 1 {
|
||
|
h = 1
|
||
|
}
|
||
|
hbmp := win.CreateCompatibleBitmap(canvas.hdc, w, h)
|
||
|
if hbmp == 0 {
|
||
|
return lastError("CreateCompatibleBitmap failed")
|
||
|
}
|
||
|
defer win.DeleteObject(win.HGDIOBJ(hbmp))
|
||
|
|
||
|
oldbmp := win.SelectObject(buffered.hdc, win.HGDIOBJ(hbmp))
|
||
|
if oldbmp == 0 {
|
||
|
return newError("SelectObject failed")
|
||
|
}
|
||
|
defer win.SelectObject(buffered.hdc, oldbmp)
|
||
|
|
||
|
win.SetViewportOrgEx(buffered.hdc, -int32(updateBounds.X), -int32(updateBounds.Y), nil)
|
||
|
win.SetBrushOrgEx(buffered.hdc, -int32(updateBounds.X), -int32(updateBounds.Y), nil)
|
||
|
|
||
|
var err error
|
||
|
if cw.paintPixels != nil {
|
||
|
err = cw.paintPixels(&buffered, updateBounds)
|
||
|
} else {
|
||
|
err = cw.paint(&buffered, RectangleTo96DPI(updateBounds, cw.DPI()))
|
||
|
}
|
||
|
|
||
|
if !win.BitBlt(canvas.hdc,
|
||
|
int32(updateBounds.X), int32(updateBounds.Y), w, h,
|
||
|
buffered.hdc,
|
||
|
int32(updateBounds.X), int32(updateBounds.Y), win.SRCCOPY) {
|
||
|
return lastError("buffered BitBlt failed")
|
||
|
}
|
||
|
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
func (*CustomWidget) CreateLayoutItem(ctx *LayoutContext) LayoutItem {
|
||
|
return NewGreedyLayoutItem()
|
||
|
}
|