blob: 77412bdb1d01133644061df7df4915ce43b9b254 [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/BurntSushi/xgb"
"github.com/BurntSushi/xgb/render"
"github.com/BurntSushi/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/x11key"
"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"
"golang.org/x/mobile/geom"
)
type windowImpl struct {
s *screenImpl
xw xproto.Window
xg xproto.Gcontext
xp render.Picture
event.Queue
xevents chan xgb.Event
// This next group of variables are mutable, but are only modified in the
// screenImpl.run goroutine.
width, height int
mu sync.Mutex
stage lifecycle.Stage
dead bool
visible bool
focused bool
released bool
}
func (w *windowImpl) sendLifecycle() {
w.mu.Lock()
from, to := w.stage, lifecycle.StageAlive
// The order of these if's is important. For example, once a window becomes
// StageDead, it should never change stage again.
//
// Similarly, focused trumps visible. It's hard to imagine a situation
// where an X11 window is focused and not visible on screen, but in that
// unlikely case, StageFocused seems the most appropriate stage.
if w.dead {
to = lifecycle.StageDead
} else if w.focused {
to = lifecycle.StageFocused
} else if w.visible {
to = lifecycle.StageVisible
}
w.stage = to
w.mu.Unlock()
if from != to {
w.Send(lifecycle.Event{
From: from,
To: to,
})
}
}
func (w *windowImpl) Release() {
w.mu.Lock()
released := w.released
w.released = true
w.mu.Unlock()
// TODO: set w.dead and call w.sendLifecycle, 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.(*bufferImpl).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) Draw(src2dst f64.Aff3, src screen.Texture, sr image.Rectangle, op draw.Op, opts *screen.DrawOptions) {
t := src.(*textureImpl)
if t.degenerate() {
return
}
t.draw(w.xp, &src2dst, sr, op, w.width, w.height, 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.
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.mu.Lock()
w.visible = (int(ev.X)+int(ev.Width)) >= 0 && (int(ev.Y)+int(ev.Height)) >= 0
w.mu.Unlock()
w.sendLifecycle()
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) {
// The key event's rune depends on whether the shift key is down.
unshifted := rune(w.s.keysyms[detail][0])
r := unshifted
if state&x11key.ShiftMask != 0 {
r = rune(w.s.keysyms[detail][1])
// In X11, a zero xproto.Keysym when shift is down means to use what
// the xproto.Keysym is when shift is up.
if r == 0 {
r = unshifted
}
}
// The key event's code is independent of whether the shift key is down.
var c key.Code
if 0 <= unshifted && unshifted < 0x80 {
// TODO: distinguish the regular '2' key and number-pad '2' key (with
// Num-Lock).
c = x11key.ASCIIKeycodes[unshifted]
} else {
r, c = -1, x11key.NonUnicodeKeycodes[unshifted]
}
// TODO: Unicode-but-not-ASCII keysyms like the Swiss keyboard's 'รถ'.
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 b {
case 4:
btn = mouse.ButtonWheelUp
case 5:
btn = mouse.ButtonWheelDown
}
w.Send(mouse.Event{
X: float32(x),
Y: float32(y),
Button: btn,
Modifiers: x11key.KeyModifiers(state),
Direction: dir,
})
}