stone/backends/x/draw.go

328 lines
7.2 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()
style := cell.Style()
if
forceRedraw &&
content < 32 &&
style & (
stone.StyleHighlight |
stone.StyleUnderline) == 0 {
continue
}
areas = append(areas, backend.boundsOfCell(x, y))
backend.drawRune (
x, y,
content,
cell.Color(),
cell.Style(),
!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,
runeStyle stone.Style,
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.
face := backend.font.normal
highlight := runeStyle & stone.StyleHighlight > 0
bold := runeStyle & stone.StyleBold > 0
italic := runeStyle & stone.StyleItalic > 0
boldTransform := false
italicTransform := false
switch {
case bold && italic:
if backend.font.boldItalic == nil {
switch {
case
backend.font.bold == nil && backend.font.italic != nil,
backend.font.bold != nil && backend.font.italic != nil:
boldTransform = true
face = backend.font.italic
case backend.font.italic == nil && backend.font.bold != nil:
italicTransform = true
face = backend.font.bold
default:
boldTransform = true
italicTransform = true
}
} else {
face = backend.font.boldItalic
}
case bold:
if backend.font.bold == nil {
boldTransform = true
} else {
face = backend.font.bold
}
case italic:
if backend.font.italic == nil {
italicTransform = true
} else {
face = backend.font.italic
}
}
var background xgraphics.BGRA
var foreground xgraphics.BGRA
if highlight {
background = backend.colors[runeColor]
foreground = backend.colors[stone.ColorBackground]
} else {
background = backend.colors[stone.ColorBackground]
foreground = backend.colors[runeColor]
}
if drawBackground || highlight {
fillRectangle (
&image.Uniform { C: background },
backend.canvas,
backend.boundsOfCell(x, y))
}
origin := backend.originOfCell(x, y + 1)
if character >= 32 {
destinationRectangle, mask, maskPoint, _, ok := face.Glyph (
fixed.Point26_6 {
X: fixed.I(origin.X),
Y: fixed.I(origin.Y),
},
character)
if !ok {
strokeRectangle (
&image.Uniform { C: foreground },
backend.canvas,
backend.boundsOfCell(x, y))
return
}
if backend.drawCellBounds {
strokeRectangle (
&image.Uniform { C: foreground },
backend.canvas,
backend.boundsOfCell(x, y))
}
// alphaMask, isAlpha := mask.(*image.Alpha)
// if isAlpha {
// backend.sprayRuneMaskAlpha (
// alphaMask, destinationRectangle,
// maskPoint, foreground, background)
// } else {
backend.sprayRuneMask (
mask, destinationRectangle,
maskPoint, foreground, background,
italicTransform, boldTransform)
// }
}
// underline
if runeStyle & stone.StyleUnderline > 0 {
maxX := origin.X + backend.metrics.cellWidth
y :=
origin.Y -
backend.metrics.descent
for x := origin.X; x < maxX; x ++ {
backend.canvas.SetBGRA(x, y, foreground)
}
}
}
func (backend *Backend) sprayRuneMask (
mask image.Image,
bounds image.Rectangle,
maskPoint image.Point,
fill xgraphics.BGRA,
background xgraphics.BGRA,
italic bool,
bold bool,
) {
maxX := bounds.Max.X - bounds.Min.X
maxY := bounds.Max.Y - bounds.Min.Y
for y := 0; y < maxY; y ++ {
var previousAlpha uint32
offset := 0
if italic {
offset = (maxY - y) / 4
}
for x := 0; x < maxX; x ++ {
_, _, _,
alpha := mask.At(x + maskPoint.X, y + maskPoint.Y).RGBA()
currentAlpha := alpha
if bold && previousAlpha > alpha {
alpha = previousAlpha
}
backend.canvas.SetBGRA (
x + bounds.Min.X + offset,
y + bounds.Min.Y - backend.metrics.descent,
xgraphics.BlendBGRA (
background,
xgraphics.BGRA {
R: fill.R,
G: fill.G,
B: fill.B,
A: uint8(alpha >> 8),
}))
previousAlpha = currentAlpha
}
if bold {
backend.canvas.SetBGRA (
bounds.Max.X + offset,
y + bounds.Min.Y - backend.metrics.descent,
xgraphics.BlendBGRA (
background,
xgraphics.BGRA {
R: fill.R,
G: fill.G,
B: fill.B,
A: uint8(previousAlpha >> 8),
}))
}
}
}
// func (backend *Backend) sprayRuneMaskAlpha (
// mask *image.Alpha,
// bounds image.Rectangle,
// maskPoint image.Point,
// fill xgraphics.BGRA,
// background 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 (
// background,
// 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))
}
}