blob: 68b6d992e4d4be536c0adbf51235275e109becd7 [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.
package x11driver
// TODO: implement a back buffer.
import (
"image"
"image/color"
"image/draw"
"sync"
"github.com/jezek/xgb"
"github.com/jezek/xgb/render"
"github.com/jezek/xgb/xproto"
"golang.org/x/exp/shiny/driver/internal/drawer"
"golang.org/x/exp/shiny/driver/internal/event"
"golang.org/x/exp/shiny/driver/internal/lifecycler"
"golang.org/x/exp/shiny/driver/internal/x11key"
"golang.org/x/exp/shiny/screen"
"golang.org/x/image/math/f64"
"golang.org/x/mobile/event/key"
"golang.org/x/mobile/event/mouse"
"golang.org/x/mobile/event/paint"
"golang.org/x/mobile/event/size"
"golang.org/x/mobile/geom"
)
type windowImpl struct {
s *screenImpl
xw xproto.Window
xg xproto.Gcontext
xp render.Picture
event.Deque
xevents chan xgb.Event
// This next group of variables are mutable, but are only modified in the
// screenImpl.run goroutine.
width, height int
lifecycler lifecycler.State
mu sync.Mutex
released bool
}
func (w *windowImpl) Release() {
w.mu.Lock()
released := w.released
w.released = true
w.mu.Unlock()
// TODO: call w.lifecycler.SetDead and w.lifecycler.SendEvent, a la
// handling atomWMDeleteWindow?
if released {
return
}
render.FreePicture(w.s.xc, w.xp)
xproto.FreeGC(w.s.xc, w.xg)
xproto.DestroyWindow(w.s.xc, w.xw)
}
func (w *windowImpl) Upload(dp image.Point, src screen.Buffer, sr image.Rectangle) {
src.(bufferUploader).upload(xproto.Drawable(w.xw), w.xg, w.s.xsi.RootDepth, dp, sr)
}
func (w *windowImpl) Fill(dr image.Rectangle, src color.Color, op draw.Op) {
fill(w.s.xc, w.xp, dr, src, op)
}
func (w *windowImpl) DrawUniform(src2dst f64.Aff3, src color.Color, sr image.Rectangle, op draw.Op, opts *screen.DrawOptions) {
w.s.drawUniform(w.xp, &src2dst, src, sr, op, opts)
}
func (w *windowImpl) Draw(src2dst f64.Aff3, src screen.Texture, sr image.Rectangle, op draw.Op, opts *screen.DrawOptions) {
src.(*textureImpl).draw(w.xp, &src2dst, sr, op, opts)
}
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: implement a back buffer, and copy or flip that here to the front
// buffer.
// This sync isn't needed to flush the outgoing X11 requests. Instead, it
// acts as a form of flow control. Outgoing requests can be quite small on
// the wire, e.g. draw this texture ID (an integer) to this rectangle (four
// more integers), but much more expensive on the server (blending a
// million source and destination pixels). Without this sync, the Go X11
// client could easily end up sending work at a faster rate than the X11
// server can serve.
w.s.xc.Sync()
return screen.PublishResult{}
}
func (w *windowImpl) handleConfigureNotify(ev xproto.ConfigureNotifyEvent) {
// TODO: does the order of these lifecycle and size events matter? Should
// they really be a single, atomic event?
w.lifecycler.SetVisible((int(ev.X)+int(ev.Width)) > 0 && (int(ev.Y)+int(ev.Height)) > 0)
w.lifecycler.SendEvent(w, nil)
newWidth, newHeight := int(ev.Width), int(ev.Height)
if w.width == newWidth && w.height == newHeight {
return
}
w.width, w.height = newWidth, newHeight
w.Send(size.Event{
WidthPx: newWidth,
HeightPx: newHeight,
WidthPt: geom.Pt(newWidth),
HeightPt: geom.Pt(newHeight),
PixelsPerPt: w.s.pixelsPerPt,
})
}
func (w *windowImpl) handleExpose() {
w.Send(paint.Event{})
}
func (w *windowImpl) handleKey(detail xproto.Keycode, state uint16, dir key.Direction) {
r, c := w.s.keysyms.Lookup(uint8(detail), state)
w.Send(key.Event{
Rune: r,
Code: c,
Modifiers: x11key.KeyModifiers(state),
Direction: dir,
})
}
func (w *windowImpl) handleMouse(x, y int16, b xproto.Button, state uint16, dir mouse.Direction) {
// TODO: should a mouse.Event have a separate MouseModifiers field, for
// which buttons are pressed during a mouse move?
btn := mouse.Button(b)
switch btn {
case 4:
btn = mouse.ButtonWheelUp
case 5:
btn = mouse.ButtonWheelDown
case 6:
btn = mouse.ButtonWheelLeft
case 7:
btn = mouse.ButtonWheelRight
}
if btn.IsWheel() {
if dir != mouse.DirPress {
return
}
dir = mouse.DirStep
}
w.Send(mouse.Event{
X: float32(x),
Y: float32(y),
Button: btn,
Modifiers: x11key.KeyModifiers(state),
Direction: dir,
})
}