224 lines
5.1 KiB
Go
224 lines
5.1 KiB
Go
package x
|
|
|
|
import "image"
|
|
import "image/draw"
|
|
import "golang.org/x/image/math/fixed"
|
|
import "github.com/jezek/xgbutil/xgraphics"
|
|
|
|
import "git.tebibyte.media/sashakoshka/stone"
|
|
|
|
func (backend *Backend) Draw () {
|
|
backend.lock.Lock()
|
|
defer backend.lock.Unlock()
|
|
|
|
if backend.windowBoundsClean {
|
|
backend.canvas.XPaintRects (
|
|
backend.window.Id,
|
|
backend.drawCells(false)...)
|
|
} else {
|
|
backend.reallocateCanvas()
|
|
backend.drawCells(true)
|
|
backend.canvas.XDraw()
|
|
backend.canvas.XPaint(backend.window.Id)
|
|
backend.windowBoundsClean = true
|
|
}
|
|
}
|
|
|
|
func (backend *Backend) reallocateCanvas () {
|
|
if backend.canvas != nil {
|
|
backend.canvas.Destroy()
|
|
}
|
|
backend.canvas = xgraphics.New (
|
|
backend.connection,
|
|
image.Rect (
|
|
0, 0,
|
|
backend.metrics.windowWidth,
|
|
backend.metrics.windowHeight))
|
|
backend.canvas.For (func (x, y int) xgraphics.BGRA {
|
|
return backend.colors[stone.ColorBackground]
|
|
})
|
|
|
|
backend.canvas.XSurfaceSet(backend.window.Id)
|
|
}
|
|
|
|
func (backend *Backend) drawCells (forceRedraw bool) (areas []image.Rectangle) {
|
|
width, height := backend.application.Size()
|
|
for y := 0; y < height; y ++ {
|
|
for x := 0; x < width; x ++ {
|
|
if !forceRedraw && backend.application.Clean(x, y) { continue }
|
|
|
|
cell := backend.application.GetForRendering(x, y)
|
|
content := cell.Rune()
|
|
|
|
if forceRedraw && content < 32 { continue }
|
|
|
|
areas = append(areas, backend.boundsOfCell(x, y))
|
|
backend.drawRune(x, y, content, cell.Color(), !forceRedraw)
|
|
}}
|
|
|
|
if backend.drawBufferBounds && forceRedraw {
|
|
strokeRectangle (
|
|
&image.Uniform {
|
|
C: backend.config.Color(stone.ColorForeground),
|
|
},
|
|
backend.canvas,
|
|
image.Rectangle {
|
|
Min: backend.originOfCell(0, 0),
|
|
Max: backend.originOfCell(width, height),
|
|
})
|
|
}
|
|
return
|
|
}
|
|
|
|
func (backend *Backend) drawRune (
|
|
x, y int,
|
|
character rune,
|
|
runeColor stone.Color,
|
|
drawBackground bool,
|
|
) {
|
|
// TODO: cache these draws as non-transparent buffers with the
|
|
// application background color as the background. that way, we won't
|
|
// need to redraw the characters *or* composite them.
|
|
|
|
if drawBackground {
|
|
fillRectangle (
|
|
&image.Uniform {
|
|
C: backend.config.Color(stone.ColorBackground),
|
|
},
|
|
backend.canvas,
|
|
backend.boundsOfCell(x, y))
|
|
}
|
|
|
|
if character < 32 { return }
|
|
|
|
origin := backend.originOfCell(x, y + 1)
|
|
destinationRectangle, mask, maskPoint, _, ok := backend.font.face.Glyph (
|
|
fixed.Point26_6 {
|
|
X: fixed.I(origin.X),
|
|
Y: fixed.I(origin.Y),
|
|
},
|
|
character)
|
|
|
|
if !ok {
|
|
strokeRectangle (
|
|
&image.Uniform {
|
|
C: backend.config.Color(stone.ColorForeground),
|
|
},
|
|
backend.canvas,
|
|
backend.boundsOfCell(x, y))
|
|
return
|
|
}
|
|
|
|
if backend.drawCellBounds {
|
|
strokeRectangle (
|
|
&image.Uniform {
|
|
C: backend.config.Color(stone.ColorForeground),
|
|
},
|
|
backend.canvas,
|
|
backend.boundsOfCell(x, y))
|
|
}
|
|
|
|
// cue a series of pointless optimizations
|
|
alphaMask, isAlpha := mask.(*image.Alpha)
|
|
if isAlpha {
|
|
backend.sprayRuneMaskAlpha (
|
|
alphaMask, destinationRectangle,
|
|
maskPoint, backend.colors[runeColor])
|
|
} else {
|
|
backend.sprayRuneMask (
|
|
mask, destinationRectangle,
|
|
maskPoint, backend.colors[runeColor])
|
|
}
|
|
}
|
|
|
|
func (backend *Backend) sprayRuneMask (
|
|
mask image.Image,
|
|
bounds image.Rectangle,
|
|
maskPoint image.Point,
|
|
fill xgraphics.BGRA,
|
|
) {
|
|
maxX := bounds.Max.X - bounds.Min.X
|
|
maxY := bounds.Max.Y - bounds.Min.Y
|
|
|
|
for y := 0; y < maxY; y ++ {
|
|
for x := 0; x < maxX; x ++ {
|
|
_, _, _,
|
|
alpha := mask.At(x + maskPoint.X, y + maskPoint.Y).RGBA()
|
|
backend.canvas.SetBGRA (
|
|
x + bounds.Min.X,
|
|
y + bounds.Min.Y - backend.metrics.descent,
|
|
xgraphics.BlendBGRA (
|
|
backend.colors[stone.ColorBackground],
|
|
xgraphics.BGRA {
|
|
R: fill.R,
|
|
G: fill.G,
|
|
B: fill.B,
|
|
A: uint8(alpha >> 8),
|
|
}))
|
|
}}
|
|
}
|
|
|
|
func (backend *Backend) sprayRuneMaskAlpha (
|
|
mask *image.Alpha,
|
|
bounds image.Rectangle,
|
|
maskPoint image.Point,
|
|
fill xgraphics.BGRA,
|
|
) {
|
|
maxX := bounds.Max.X - bounds.Min.X
|
|
maxY := bounds.Max.Y - bounds.Min.Y
|
|
|
|
for y := 0; y < maxY; y ++ {
|
|
for x := 0; x < maxX; x ++ {
|
|
alpha := mask.AlphaAt(x + maskPoint.X, y + maskPoint.Y).A
|
|
backend.canvas.SetBGRA (
|
|
x + bounds.Min.X,
|
|
y + bounds.Min.Y - backend.metrics.descent,
|
|
xgraphics.BlendBGRA (
|
|
backend.colors[stone.ColorBackground],
|
|
xgraphics.BGRA {
|
|
R: fill.R,
|
|
G: fill.G,
|
|
B: fill.B,
|
|
A: alpha,
|
|
}))
|
|
}}
|
|
}
|
|
|
|
func fillRectangle (
|
|
source image.Image,
|
|
destination draw.Image,
|
|
bounds image.Rectangle,
|
|
) {
|
|
for y := bounds.Min.Y; y < bounds.Max.Y; y ++ {
|
|
for x := bounds.Min.X; x < bounds.Max.X; x ++ {
|
|
destination.Set(x, y, source.At(x, y))
|
|
}}
|
|
}
|
|
|
|
func strokeRectangle (
|
|
source image.Image,
|
|
destination draw.Image,
|
|
bounds image.Rectangle,
|
|
) {
|
|
x := 0
|
|
y := bounds.Min.Y
|
|
for x = bounds.Min.X; x < bounds.Max.X; x ++ {
|
|
destination.Set(x, y, source.At(x, y))
|
|
}
|
|
|
|
y = bounds.Max.Y - 1
|
|
for x = bounds.Min.X; x < bounds.Max.X; x ++ {
|
|
destination.Set(x, y, source.At(x, y))
|
|
}
|
|
|
|
x = bounds.Min.X
|
|
for y = bounds.Min.Y; y < bounds.Max.Y; y ++ {
|
|
destination.Set(x, y, source.At(x, y))
|
|
}
|
|
|
|
x = bounds.Max.X - 1
|
|
for y = bounds.Min.Y; y < bounds.Max.Y; y ++ {
|
|
destination.Set(x, y, source.At(x, y))
|
|
}
|
|
}
|