blob: 9577f2c69916011307c21d005dd68b04717aa0cd [file] [log] [blame]
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build windows
// +build windows
package windriver
// TODO: implement a back buffer.
import (
"fmt"
"image"
"image/color"
"image/draw"
"math"
"syscall"
"unsafe"
"golang.org/x/exp/shiny/driver/internal/drawer"
"golang.org/x/exp/shiny/driver/internal/event"
"golang.org/x/exp/shiny/driver/internal/win32"
"golang.org/x/exp/shiny/screen"
"golang.org/x/image/math/f64"
"golang.org/x/mobile/event/key"
"golang.org/x/mobile/event/lifecycle"
"golang.org/x/mobile/event/mouse"
"golang.org/x/mobile/event/paint"
"golang.org/x/mobile/event/size"
)
type windowImpl struct {
hwnd syscall.Handle
event.Deque
sz size.Event
lifecycleStage lifecycle.Stage
}
func (w *windowImpl) Release() {
win32.Release(w.hwnd)
}
func (w *windowImpl) Upload(dp image.Point, src screen.Buffer, sr image.Rectangle) {
src.(*bufferImpl).preUpload()
defer src.(*bufferImpl).postUpload()
w.execCmd(&cmd{
id: cmdUpload,
dp: dp,
buffer: src.(*bufferImpl),
sr: sr,
})
}
func (w *windowImpl) Fill(dr image.Rectangle, src color.Color, op draw.Op) {
w.execCmd(&cmd{
id: cmdFill,
dr: dr,
color: src,
op: op,
})
}
func (w *windowImpl) Draw(src2dst f64.Aff3, src screen.Texture, sr image.Rectangle, op draw.Op, opts *screen.DrawOptions) {
if op != draw.Src && op != draw.Over {
// TODO:
return
}
w.execCmd(&cmd{
id: cmdDraw,
src2dst: src2dst,
texture: src.(*textureImpl).bitmap,
sr: sr,
op: op,
})
}
func (w *windowImpl) DrawUniform(src2dst f64.Aff3, src color.Color, sr image.Rectangle, op draw.Op, opts *screen.DrawOptions) {
if op != draw.Src && op != draw.Over {
// TODO:
return
}
w.execCmd(&cmd{
id: cmdDrawUniform,
src2dst: src2dst,
color: src,
sr: sr,
op: op,
})
}
func drawWindow(dc syscall.Handle, src2dst f64.Aff3, src interface{}, sr image.Rectangle, op draw.Op) (retErr error) {
var dr image.Rectangle
if src2dst[1] != 0 || src2dst[3] != 0 {
// general drawing
dr = sr.Sub(sr.Min)
prevmode, err := _SetGraphicsMode(dc, _GM_ADVANCED)
if err != nil {
return err
}
defer func() {
_, err := _SetGraphicsMode(dc, prevmode)
if retErr == nil {
retErr = err
}
}()
x := _XFORM{
eM11: +float32(src2dst[0]),
eM12: -float32(src2dst[1]),
eM21: -float32(src2dst[3]),
eM22: +float32(src2dst[4]),
eDx: +float32(src2dst[2]),
eDy: +float32(src2dst[5]),
}
err = _SetWorldTransform(dc, &x)
if err != nil {
return err
}
defer func() {
err := _ModifyWorldTransform(dc, nil, _MWT_IDENTITY)
if retErr == nil {
retErr = err
}
}()
} else if src2dst[0] == 1 && src2dst[4] == 1 {
// copy bitmap
dr = sr.Add(image.Point{int(src2dst[2]), int(src2dst[5])})
} else {
// scale bitmap
dstXMin := float64(sr.Min.X)*src2dst[0] + src2dst[2]
dstXMax := float64(sr.Max.X)*src2dst[0] + src2dst[2]
if dstXMin > dstXMax {
// TODO: check if this (and below) works when src2dst[0] < 0.
dstXMin, dstXMax = dstXMax, dstXMin
}
dstYMin := float64(sr.Min.Y)*src2dst[4] + src2dst[5]
dstYMax := float64(sr.Max.Y)*src2dst[4] + src2dst[5]
if dstYMin > dstYMax {
// TODO: check if this (and below) works when src2dst[4] < 0.
dstYMin, dstYMax = dstYMax, dstYMin
}
dr = image.Rectangle{
image.Point{int(math.Floor(dstXMin)), int(math.Floor(dstYMin))},
image.Point{int(math.Ceil(dstXMax)), int(math.Ceil(dstYMax))},
}
}
switch s := src.(type) {
case syscall.Handle:
return copyBitmapToDC(dc, dr, s, sr, op)
case color.Color:
return fill(dc, dr, s, op)
}
return fmt.Errorf("unsupported type %T", src)
}
func (w *windowImpl) Copy(dp image.Point, src screen.Texture, sr image.Rectangle, op draw.Op, opts *screen.DrawOptions) {
drawer.Copy(w, dp, src, sr, op, opts)
}
func (w *windowImpl) Scale(dr image.Rectangle, src screen.Texture, sr image.Rectangle, op draw.Op, opts *screen.DrawOptions) {
drawer.Scale(w, dr, src, sr, op, opts)
}
func (w *windowImpl) Publish() screen.PublishResult {
// TODO
return screen.PublishResult{}
}
func init() {
send := func(hwnd syscall.Handle, e interface{}) {
theScreen.mu.Lock()
w := theScreen.windows[hwnd]
theScreen.mu.Unlock()
w.Send(e)
}
win32.MouseEvent = func(hwnd syscall.Handle, e mouse.Event) { send(hwnd, e) }
win32.PaintEvent = func(hwnd syscall.Handle, e paint.Event) { send(hwnd, e) }
win32.KeyEvent = func(hwnd syscall.Handle, e key.Event) { send(hwnd, e) }
win32.LifecycleEvent = lifecycleEvent
win32.SizeEvent = sizeEvent
}
func lifecycleEvent(hwnd syscall.Handle, to lifecycle.Stage) {
theScreen.mu.Lock()
w := theScreen.windows[hwnd]
theScreen.mu.Unlock()
if w.lifecycleStage == to {
return
}
w.Send(lifecycle.Event{
From: w.lifecycleStage,
To: to,
})
w.lifecycleStage = to
}
func sizeEvent(hwnd syscall.Handle, e size.Event) {
theScreen.mu.Lock()
w := theScreen.windows[hwnd]
theScreen.mu.Unlock()
w.Send(e)
if e != w.sz {
w.sz = e
w.Send(paint.Event{})
}
}
// cmd is used to carry parameters between user code
// and Windows message pump thread.
type cmd struct {
id int
err error
src2dst f64.Aff3
sr image.Rectangle
dp image.Point
dr image.Rectangle
color color.Color
op draw.Op
texture syscall.Handle
buffer *bufferImpl
}
const (
cmdDraw = iota
cmdFill
cmdUpload
cmdDrawUniform
)
var msgCmd = win32.AddWindowMsg(handleCmd)
func (w *windowImpl) execCmd(c *cmd) {
win32.SendMessage(w.hwnd, msgCmd, 0, uintptr(unsafe.Pointer(c)))
if c.err != nil {
panic(fmt.Sprintf("execCmd faild for cmd.id=%d: %v", c.id, c.err)) // TODO handle errors
}
}
func handleCmd(hwnd syscall.Handle, uMsg uint32, wParam, lParam uintptr) {
c := (*cmd)(unsafe.Pointer(lParam))
dc, err := win32.GetDC(hwnd)
if err != nil {
c.err = err
return
}
defer win32.ReleaseDC(hwnd, dc)
switch c.id {
case cmdDraw:
c.err = drawWindow(dc, c.src2dst, c.texture, c.sr, c.op)
case cmdDrawUniform:
c.err = drawWindow(dc, c.src2dst, c.color, c.sr, c.op)
case cmdFill:
c.err = fill(dc, c.dr, c.color, c.op)
case cmdUpload:
// TODO: adjust if dp is outside dst bounds, or sr is outside buffer bounds.
dr := c.sr.Add(c.dp.Sub(c.sr.Min))
c.err = copyBitmapToDC(dc, dr, c.buffer.hbitmap, c.sr, draw.Src)
default:
c.err = fmt.Errorf("unknown command id=%d", c.id)
}
return
}