This repository has been archived on 2023-08-08. You can view files and clone it, but cannot push or open issues or pull requests.
tomo-old/backends/x/window.go

339 lines
8.3 KiB
Go
Raw Normal View History

2023-01-08 23:03:19 -07:00
package x
import "image"
import "github.com/jezek/xgb/xproto"
import "github.com/jezek/xgbutil/ewmh"
import "github.com/jezek/xgbutil/icccm"
import "github.com/jezek/xgbutil/xevent"
import "github.com/jezek/xgbutil/xwindow"
import "github.com/jezek/xgbutil/xgraphics"
2023-02-01 23:47:55 -07:00
import "git.tebibyte.media/sashakoshka/tomo/input"
2023-02-02 23:35:59 -07:00
import "git.tebibyte.media/sashakoshka/tomo/theme"
import "git.tebibyte.media/sashakoshka/tomo/config"
2023-02-01 23:47:55 -07:00
import "git.tebibyte.media/sashakoshka/tomo/canvas"
import "git.tebibyte.media/sashakoshka/tomo/elements"
// import "runtime/debug"
2023-01-08 23:03:19 -07:00
type window struct {
backend *Backend
xWindow *xwindow.Window
xCanvas *xgraphics.Image
2023-02-01 23:47:55 -07:00
canvas canvas.BasicCanvas
child elements.Element
onClose func ()
2023-01-08 23:03:19 -07:00
skipChildDrawCallback bool
2023-02-02 23:35:59 -07:00
theme theme.Theme
config config.Config
2023-01-08 23:03:19 -07:00
metrics struct {
width int
height int
}
}
func (backend *Backend) NewWindow (
width, height int,
) (
2023-02-01 23:47:55 -07:00
output elements.Window,
2023-01-08 23:03:19 -07:00
err error,
) {
if backend == nil { panic("nil backend") }
window := &window { backend: backend }
2023-01-08 23:03:19 -07:00
window.xWindow, err = xwindow.Generate(backend.connection)
if err != nil { return }
window.xWindow.Create (
backend.connection.RootWin(),
0, 0, width, height, 0)
err = window.xWindow.Listen (
2023-01-26 00:08:07 -07:00
xproto.EventMaskExposure,
2023-01-08 23:03:19 -07:00
xproto.EventMaskStructureNotify,
xproto.EventMaskPointerMotion,
xproto.EventMaskKeyPress,
xproto.EventMaskKeyRelease,
xproto.EventMaskButtonPress,
xproto.EventMaskButtonRelease)
if err != nil { return }
window.xWindow.WMGracefulClose (func (xWindow *xwindow.Window) {
window.Close()
})
2023-01-26 00:08:07 -07:00
xevent.ExposeFun(window.handleExpose).
Connect(backend.connection, window.xWindow.Id)
2023-01-08 23:03:19 -07:00
xevent.ConfigureNotifyFun(window.handleConfigureNotify).
Connect(backend.connection, window.xWindow.Id)
xevent.KeyPressFun(window.handleKeyPress).
Connect(backend.connection, window.xWindow.Id)
xevent.KeyReleaseFun(window.handleKeyRelease).
Connect(backend.connection, window.xWindow.Id)
xevent.ButtonPressFun(window.handleButtonPress).
Connect(backend.connection, window.xWindow.Id)
xevent.ButtonReleaseFun(window.handleButtonRelease).
Connect(backend.connection, window.xWindow.Id)
xevent.MotionNotifyFun(window.handleMotionNotify).
Connect(backend.connection, window.xWindow.Id)
2023-02-02 23:35:59 -07:00
window.SetTheme(backend.theme)
window.SetConfig(backend.config)
2023-01-08 23:03:19 -07:00
window.metrics.width = width
window.metrics.height = height
window.childMinimumSizeChangeCallback(8, 8)
window.reallocateCanvas()
backend.windows[window.xWindow.Id] = window
output = window
return
}
func (window *window) NotifyMinimumSizeChange (child elements.Element) {
window.childMinimumSizeChangeCallback(child.MinimumSize())
}
func (window *window) Adopt (child elements.Element) {
2023-01-19 13:02:56 -07:00
// disown previous child
2023-01-08 23:03:19 -07:00
if window.child != nil {
window.child.SetParent(nil)
window.child.DrawTo(nil, image.Rectangle { }, nil)
2023-01-08 23:03:19 -07:00
}
2023-01-19 13:02:56 -07:00
// adopt new child
2023-01-08 23:03:19 -07:00
window.child = child
2023-02-02 23:35:59 -07:00
if newChild, ok := child.(elements.Themeable); ok {
newChild.SetTheme(window.theme)
}
if newChild, ok := child.(elements.Configurable); ok {
newChild.SetConfig(window.config)
}
2023-01-08 23:03:19 -07:00
if child != nil {
if !window.childMinimumSizeChangeCallback(child.MinimumSize()) {
window.resizeChildToFit()
window.redrawChildEntirely()
}
2023-01-08 23:03:19 -07:00
}
}
func (window *window) Child () (child elements.Element) {
2023-01-08 23:03:19 -07:00
child = window.child
return
}
func (window *window) SetTitle (title string) {
2023-01-08 23:03:19 -07:00
ewmh.WmNameSet (
window.backend.connection,
window.xWindow.Id,
title)
}
func (window *window) SetIcon (sizes []image.Image) {
2023-01-08 23:03:19 -07:00
wmIcons := []ewmh.WmIcon { }
for _, icon := range sizes {
width := icon.Bounds().Max.X
height := icon.Bounds().Max.Y
wmIcon := ewmh.WmIcon {
Width: uint(width),
Height: uint(height),
Data: make ([]uint, width * height),
}
// manually convert image data beacuse of course we have to do
// this
index := 0
for y := 0; y < height; y ++ {
for x := 0; x < width; x ++ {
r, g, b, a := icon.At(x, y).RGBA()
r >>= 8
g >>= 8
b >>= 8
a >>= 8
wmIcon.Data[index] =
(uint(a) << 24) |
(uint(r) << 16) |
(uint(g) << 8) |
(uint(b) << 0)
index ++
}}
wmIcons = append(wmIcons, wmIcon)
}
ewmh.WmIconSet (
window.backend.connection,
window.xWindow.Id,
wmIcons)
}
func (window *window) Show () {
2023-01-08 23:03:19 -07:00
if window.child == nil {
window.xCanvas.For (func (x, y int) xgraphics.BGRA {
return xgraphics.BGRA { }
})
window.pushRegion(window.xCanvas.Bounds())
}
window.xWindow.Map()
}
func (window *window) Hide () {
2023-01-08 23:03:19 -07:00
window.xWindow.Unmap()
}
func (window *window) Close () {
2023-01-08 23:03:19 -07:00
if window.onClose != nil { window.onClose() }
delete(window.backend.windows, window.xWindow.Id)
2023-01-08 23:03:19 -07:00
window.xWindow.Destroy()
}
func (window *window) OnClose (callback func ()) {
2023-01-08 23:03:19 -07:00
window.onClose = callback
}
func (window *window) SetTheme (theme theme.Theme) {
2023-02-02 23:35:59 -07:00
window.theme = theme
if child, ok := window.child.(elements.Themeable); ok {
child.SetTheme(theme)
}
}
func (window *window) SetConfig (config config.Config) {
2023-02-02 23:35:59 -07:00
window.config = config
if child, ok := window.child.(elements.Configurable); ok {
child.SetConfig(config)
}
}
func (window *window) reallocateCanvas () {
window.canvas.Reallocate(window.metrics.width, window.metrics.height)
previousWidth, previousHeight := 0, 0
2023-01-08 23:03:19 -07:00
if window.xCanvas != nil {
previousWidth = window.xCanvas.Bounds().Dx()
previousHeight = window.xCanvas.Bounds().Dy()
2023-01-08 23:03:19 -07:00
}
newWidth := window.metrics.width
newHeight := window.metrics.height
larger := newWidth > previousWidth || newHeight > previousHeight
smaller := newWidth < previousWidth / 2 || newHeight < previousHeight / 2
2023-03-07 17:13:08 -07:00
allocStep := 128
if larger || smaller {
if window.xCanvas != nil {
window.xCanvas.Destroy()
}
window.xCanvas = xgraphics.New (
window.backend.connection,
image.Rect (
0, 0,
2023-03-07 17:13:08 -07:00
(newWidth / allocStep + 1) * allocStep,
(newHeight / allocStep + 1) * allocStep))
window.xCanvas.CreatePixmap()
}
2023-01-08 23:03:19 -07:00
}
func (window *window) redrawChildEntirely () {
window.paste(window.canvas.Bounds())
window.pushRegion(window.canvas.Bounds())
2023-01-08 23:03:19 -07:00
}
func (window *window) resizeChildToFit () {
2023-01-08 23:03:19 -07:00
window.skipChildDrawCallback = true
window.child.DrawTo (
window.canvas,
window.canvas.Bounds(),
window.childDrawCallback)
2023-01-08 23:03:19 -07:00
window.skipChildDrawCallback = false
}
func (window *window) childDrawCallback (region image.Rectangle) {
2023-01-08 23:03:19 -07:00
if window.skipChildDrawCallback { return }
window.paste(region)
window.pushRegion(region)
}
2023-01-08 23:03:19 -07:00
func (window *window) paste (region image.Rectangle) {
canvas := canvas.Cut(window.canvas, region)
data, stride := canvas.Buffer()
bounds := canvas.Bounds().Intersect(window.xCanvas.Bounds())
2023-03-07 17:13:08 -07:00
dstStride := window.xCanvas.Stride
dstData := window.xCanvas.Pix
// debug.PrintStack()
2023-01-08 23:03:19 -07:00
for y := bounds.Min.Y; y < bounds.Max.Y; y ++ {
2023-03-07 17:13:08 -07:00
srcYComponent := y * stride
dstYComponent := y * dstStride
for x := bounds.Min.X; x < bounds.Max.X; x ++ {
rgba := data[srcYComponent + x]
index := dstYComponent + x * 4
dstData[index + 0] = rgba.B
dstData[index + 1] = rgba.G
dstData[index + 2] = rgba.R
dstData[index + 3] = rgba.A
}
}
2023-01-08 23:03:19 -07:00
}
func (window *window) childMinimumSizeChangeCallback (width, height int) (resized bool) {
2023-01-08 23:03:19 -07:00
icccm.WmNormalHintsSet (
window.backend.connection,
window.xWindow.Id,
&icccm.NormalHints {
Flags: icccm.SizeHintPMinSize,
MinWidth: uint(width),
MinHeight: uint(height),
})
newWidth := window.metrics.width
newHeight := window.metrics.height
if newWidth < width { newWidth = width }
if newHeight < height { newHeight = height }
if newWidth != window.metrics.width ||
newHeight != window.metrics.height {
window.xWindow.Resize(newWidth, newHeight)
return true
2023-01-08 23:03:19 -07:00
}
return false
2023-01-08 23:03:19 -07:00
}
func (window *window) childSelectionRequestCallback () (granted bool) {
if _, ok := window.child.(elements.Focusable); ok {
return true
2023-01-15 10:23:13 -07:00
}
return false
2023-01-09 13:14:36 -07:00
}
func (window *window) childSelectionMotionRequestCallback (
2023-02-01 23:47:55 -07:00
direction input.KeynavDirection,
) (
granted bool,
) {
2023-02-01 23:47:55 -07:00
if child, ok := window.child.(elements.Focusable); ok {
2023-01-30 15:01:47 -07:00
if !child.HandleFocus(direction) {
child.HandleUnfocus()
}
return true
}
return true
}
func (window *window) pushRegion (region image.Rectangle) {
2023-01-08 23:03:19 -07:00
if window.xCanvas == nil { panic("whoopsie!!!!!!!!!!!!!!") }
image, ok := window.xCanvas.SubImage(region).(*xgraphics.Image)
if ok {
image.XDraw()
2023-01-26 00:08:07 -07:00
image.XExpPaint (
window.xWindow.Id,
image.Bounds().Min.X,
image.Bounds().Min.Y)
2023-01-08 23:03:19 -07:00
}
}