417 lines
13 KiB
Go
417 lines
13 KiB
Go
|
package gl
|
||
|
|
||
|
import (
|
||
|
"image/color"
|
||
|
"math"
|
||
|
|
||
|
"fyne.io/fyne/v2"
|
||
|
"fyne.io/fyne/v2/canvas"
|
||
|
paint "fyne.io/fyne/v2/internal/painter"
|
||
|
)
|
||
|
|
||
|
func (p *painter) createBuffer(points []float32) Buffer {
|
||
|
vbo := p.ctx.CreateBuffer()
|
||
|
p.logError()
|
||
|
p.ctx.BindBuffer(arrayBuffer, vbo)
|
||
|
p.logError()
|
||
|
p.ctx.BufferData(arrayBuffer, points, staticDraw)
|
||
|
p.logError()
|
||
|
return vbo
|
||
|
}
|
||
|
|
||
|
func (p *painter) defineVertexArray(prog Program, name string, size, stride, offset int) {
|
||
|
vertAttrib := p.ctx.GetAttribLocation(prog, name)
|
||
|
p.ctx.EnableVertexAttribArray(vertAttrib)
|
||
|
p.ctx.VertexAttribPointerWithOffset(vertAttrib, size, float, false, stride*floatSize, offset*floatSize)
|
||
|
p.logError()
|
||
|
}
|
||
|
|
||
|
func (p *painter) drawCircle(circle *canvas.Circle, pos fyne.Position, frame fyne.Size) {
|
||
|
p.drawTextureWithDetails(circle, p.newGlCircleTexture, pos, circle.Size(), frame, canvas.ImageFillStretch,
|
||
|
1.0, paint.VectorPad(circle))
|
||
|
}
|
||
|
|
||
|
func (p *painter) drawGradient(o fyne.CanvasObject, texCreator func(fyne.CanvasObject) Texture, pos fyne.Position, frame fyne.Size) {
|
||
|
p.drawTextureWithDetails(o, texCreator, pos, o.Size(), frame, canvas.ImageFillStretch, 1.0, 0)
|
||
|
}
|
||
|
|
||
|
func (p *painter) drawImage(img *canvas.Image, pos fyne.Position, frame fyne.Size) {
|
||
|
p.drawTextureWithDetails(img, p.newGlImageTexture, pos, img.Size(), frame, img.FillMode, float32(img.Alpha()), 0)
|
||
|
}
|
||
|
|
||
|
func (p *painter) drawLine(line *canvas.Line, pos fyne.Position, frame fyne.Size) {
|
||
|
if line.StrokeColor == color.Transparent || line.StrokeColor == nil || line.StrokeWidth == 0 {
|
||
|
return
|
||
|
}
|
||
|
points, halfWidth, feather := p.lineCoords(pos, line.Position1, line.Position2, line.StrokeWidth, 0.5, frame)
|
||
|
p.ctx.UseProgram(p.lineProgram)
|
||
|
vbo := p.createBuffer(points)
|
||
|
p.defineVertexArray(p.lineProgram, "vert", 2, 4, 0)
|
||
|
p.defineVertexArray(p.lineProgram, "normal", 2, 4, 2)
|
||
|
|
||
|
p.ctx.BlendFunc(srcAlpha, oneMinusSrcAlpha)
|
||
|
p.logError()
|
||
|
|
||
|
colorUniform := p.ctx.GetUniformLocation(p.lineProgram, "color")
|
||
|
r, g, b, a := getFragmentColor(line.StrokeColor)
|
||
|
p.ctx.Uniform4f(colorUniform, r, g, b, a)
|
||
|
|
||
|
lineWidthUniform := p.ctx.GetUniformLocation(p.lineProgram, "lineWidth")
|
||
|
p.ctx.Uniform1f(lineWidthUniform, halfWidth)
|
||
|
|
||
|
featherUniform := p.ctx.GetUniformLocation(p.lineProgram, "feather")
|
||
|
p.ctx.Uniform1f(featherUniform, feather)
|
||
|
|
||
|
p.ctx.DrawArrays(triangles, 0, 6)
|
||
|
p.logError()
|
||
|
p.freeBuffer(vbo)
|
||
|
}
|
||
|
|
||
|
func (p *painter) drawObject(o fyne.CanvasObject, pos fyne.Position, frame fyne.Size) {
|
||
|
switch obj := o.(type) {
|
||
|
case *canvas.Circle:
|
||
|
p.drawCircle(obj, pos, frame)
|
||
|
case *canvas.Line:
|
||
|
p.drawLine(obj, pos, frame)
|
||
|
case *canvas.Image:
|
||
|
p.drawImage(obj, pos, frame)
|
||
|
case *canvas.Raster:
|
||
|
p.drawRaster(obj, pos, frame)
|
||
|
case *canvas.Rectangle:
|
||
|
p.drawRectangle(obj, pos, frame)
|
||
|
case *canvas.Text:
|
||
|
p.drawText(obj, pos, frame)
|
||
|
case *canvas.LinearGradient:
|
||
|
p.drawGradient(obj, p.newGlLinearGradientTexture, pos, frame)
|
||
|
case *canvas.RadialGradient:
|
||
|
p.drawGradient(obj, p.newGlRadialGradientTexture, pos, frame)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (p *painter) drawRaster(img *canvas.Raster, pos fyne.Position, frame fyne.Size) {
|
||
|
p.drawTextureWithDetails(img, p.newGlRasterTexture, pos, img.Size(), frame, canvas.ImageFillStretch, float32(img.Alpha()), 0)
|
||
|
}
|
||
|
|
||
|
func (p *painter) drawRectangle(rect *canvas.Rectangle, pos fyne.Position, frame fyne.Size) {
|
||
|
if (rect.FillColor == color.Transparent || rect.FillColor == nil) && (rect.StrokeColor == color.Transparent || rect.StrokeColor == nil || rect.StrokeWidth == 0) {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
roundedCorners := rect.CornerRadius != 0
|
||
|
var program Program
|
||
|
if roundedCorners {
|
||
|
program = p.roundRectangleProgram
|
||
|
} else {
|
||
|
program = p.rectangleProgram
|
||
|
}
|
||
|
|
||
|
// Vertex: BEG
|
||
|
bounds, points := p.vecRectCoords(pos, rect, frame)
|
||
|
p.ctx.UseProgram(program)
|
||
|
vbo := p.createBuffer(points)
|
||
|
p.defineVertexArray(program, "vert", 2, 4, 0)
|
||
|
p.defineVertexArray(program, "normal", 2, 4, 2)
|
||
|
|
||
|
p.ctx.BlendFunc(srcAlpha, oneMinusSrcAlpha)
|
||
|
p.logError()
|
||
|
// Vertex: END
|
||
|
|
||
|
// Fragment: BEG
|
||
|
frameSizeUniform := p.ctx.GetUniformLocation(program, "frame_size")
|
||
|
frameWidthScaled, frameHeightScaled := p.scaleFrameSize(frame)
|
||
|
p.ctx.Uniform2f(frameSizeUniform, frameWidthScaled, frameHeightScaled)
|
||
|
|
||
|
rectCoordsUniform := p.ctx.GetUniformLocation(program, "rect_coords")
|
||
|
x1Scaled, x2Scaled, y1Scaled, y2Scaled := p.scaleRectCoords(bounds[0], bounds[2], bounds[1], bounds[3])
|
||
|
p.ctx.Uniform4f(rectCoordsUniform, x1Scaled, x2Scaled, y1Scaled, y2Scaled)
|
||
|
|
||
|
strokeWidthScaled := roundToPixel(rect.StrokeWidth*p.pixScale, 1.0)
|
||
|
if roundedCorners {
|
||
|
strokeUniform := p.ctx.GetUniformLocation(program, "stroke_width_half")
|
||
|
p.ctx.Uniform1f(strokeUniform, strokeWidthScaled*0.5)
|
||
|
|
||
|
rectSizeUniform := p.ctx.GetUniformLocation(program, "rect_size_half")
|
||
|
rectSizeWidthScaled := x2Scaled - x1Scaled - strokeWidthScaled
|
||
|
rectSizeHeightScaled := y2Scaled - y1Scaled - strokeWidthScaled
|
||
|
p.ctx.Uniform2f(rectSizeUniform, rectSizeWidthScaled*0.5, rectSizeHeightScaled*0.5)
|
||
|
|
||
|
radiusUniform := p.ctx.GetUniformLocation(program, "radius")
|
||
|
radiusScaled := roundToPixel(rect.CornerRadius*p.pixScale, 1.0)
|
||
|
p.ctx.Uniform1f(radiusUniform, radiusScaled)
|
||
|
} else {
|
||
|
strokeUniform := p.ctx.GetUniformLocation(program, "stroke_width")
|
||
|
p.ctx.Uniform1f(strokeUniform, strokeWidthScaled)
|
||
|
}
|
||
|
|
||
|
var r, g, b, a float32
|
||
|
fillColorUniform := p.ctx.GetUniformLocation(program, "fill_color")
|
||
|
r, g, b, a = getFragmentColor(rect.FillColor)
|
||
|
p.ctx.Uniform4f(fillColorUniform, r, g, b, a)
|
||
|
|
||
|
strokeColorUniform := p.ctx.GetUniformLocation(program, "stroke_color")
|
||
|
strokeColor := rect.StrokeColor
|
||
|
if strokeColor == nil {
|
||
|
strokeColor = color.Transparent
|
||
|
}
|
||
|
r, g, b, a = getFragmentColor(strokeColor)
|
||
|
p.ctx.Uniform4f(strokeColorUniform, r, g, b, a)
|
||
|
p.logError()
|
||
|
// Fragment: END
|
||
|
|
||
|
p.ctx.DrawArrays(triangleStrip, 0, 4)
|
||
|
p.logError()
|
||
|
p.freeBuffer(vbo)
|
||
|
}
|
||
|
|
||
|
func (p *painter) drawText(text *canvas.Text, pos fyne.Position, frame fyne.Size) {
|
||
|
if text.Text == "" || text.Text == " " {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
size := text.MinSize()
|
||
|
containerSize := text.Size()
|
||
|
switch text.Alignment {
|
||
|
case fyne.TextAlignTrailing:
|
||
|
pos = fyne.NewPos(pos.X+containerSize.Width-size.Width, pos.Y)
|
||
|
case fyne.TextAlignCenter:
|
||
|
pos = fyne.NewPos(pos.X+(containerSize.Width-size.Width)/2, pos.Y)
|
||
|
}
|
||
|
|
||
|
if containerSize.Height > size.Height {
|
||
|
pos = fyne.NewPos(pos.X, pos.Y+(containerSize.Height-size.Height)/2)
|
||
|
}
|
||
|
|
||
|
// text size is sensitive to position on screen
|
||
|
size, _ = roundToPixelCoords(size, text.Position(), p.pixScale)
|
||
|
size.Width += roundToPixel(paint.VectorPad(text), p.pixScale)
|
||
|
p.drawTextureWithDetails(text, p.newGlTextTexture, pos, size, frame, canvas.ImageFillStretch, 1.0, 0)
|
||
|
}
|
||
|
|
||
|
func (p *painter) drawTextureWithDetails(o fyne.CanvasObject, creator func(canvasObject fyne.CanvasObject) Texture,
|
||
|
pos fyne.Position, size, frame fyne.Size, fill canvas.ImageFill, alpha float32, pad float32) {
|
||
|
|
||
|
texture, err := p.getTexture(o, creator)
|
||
|
if err != nil {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
aspect := float32(0)
|
||
|
if img, ok := o.(*canvas.Image); ok {
|
||
|
aspect = img.Aspect()
|
||
|
if aspect == 0 {
|
||
|
aspect = 1 // fallback, should not occur - normally an image load error
|
||
|
}
|
||
|
}
|
||
|
points := p.rectCoords(size, pos, frame, fill, aspect, pad)
|
||
|
p.ctx.UseProgram(p.program)
|
||
|
vbo := p.createBuffer(points)
|
||
|
p.defineVertexArray(p.program, "vert", 3, 5, 0)
|
||
|
p.defineVertexArray(p.program, "vertTexCoord", 2, 5, 3)
|
||
|
|
||
|
// here we have to choose between blending the image alpha or fading it...
|
||
|
// TODO find a way to support both
|
||
|
if alpha != 1.0 {
|
||
|
p.ctx.BlendColor(0, 0, 0, alpha)
|
||
|
p.ctx.BlendFunc(constantAlpha, oneMinusConstantAlpha)
|
||
|
} else {
|
||
|
p.ctx.BlendFunc(one, oneMinusSrcAlpha)
|
||
|
}
|
||
|
p.logError()
|
||
|
|
||
|
p.ctx.ActiveTexture(texture0)
|
||
|
p.ctx.BindTexture(texture2D, texture)
|
||
|
p.logError()
|
||
|
|
||
|
p.ctx.DrawArrays(triangleStrip, 0, 4)
|
||
|
p.logError()
|
||
|
p.freeBuffer(vbo)
|
||
|
}
|
||
|
|
||
|
func (p *painter) freeBuffer(vbo Buffer) {
|
||
|
p.ctx.BindBuffer(arrayBuffer, noBuffer)
|
||
|
p.logError()
|
||
|
p.ctx.DeleteBuffer(vbo)
|
||
|
p.logError()
|
||
|
}
|
||
|
|
||
|
func (p *painter) lineCoords(pos, pos1, pos2 fyne.Position, lineWidth, feather float32, frame fyne.Size) ([]float32, float32, float32) {
|
||
|
// Shift line coordinates so that they match the target position.
|
||
|
xPosDiff := pos.X - fyne.Min(pos1.X, pos2.X)
|
||
|
yPosDiff := pos.Y - fyne.Min(pos1.Y, pos2.Y)
|
||
|
pos1.X = roundToPixel(pos1.X+xPosDiff, p.pixScale)
|
||
|
pos1.Y = roundToPixel(pos1.Y+yPosDiff, p.pixScale)
|
||
|
pos2.X = roundToPixel(pos2.X+xPosDiff, p.pixScale)
|
||
|
pos2.Y = roundToPixel(pos2.Y+yPosDiff, p.pixScale)
|
||
|
|
||
|
if lineWidth <= 1 {
|
||
|
offset := float32(0.5) // adjust location for lines < 1pt on regular display
|
||
|
if lineWidth <= 0.5 && p.pixScale > 1 { // and for 1px drawing on HiDPI (width 0.5)
|
||
|
offset = 0.25
|
||
|
}
|
||
|
if pos1.X == pos2.X {
|
||
|
pos1.X -= offset
|
||
|
pos2.X -= offset
|
||
|
}
|
||
|
if pos1.Y == pos2.Y {
|
||
|
pos1.Y -= offset
|
||
|
pos2.Y -= offset
|
||
|
}
|
||
|
}
|
||
|
|
||
|
x1Pos := pos1.X / frame.Width
|
||
|
x1 := -1 + x1Pos*2
|
||
|
y1Pos := pos1.Y / frame.Height
|
||
|
y1 := 1 - y1Pos*2
|
||
|
x2Pos := pos2.X / frame.Width
|
||
|
x2 := -1 + x2Pos*2
|
||
|
y2Pos := pos2.Y / frame.Height
|
||
|
y2 := 1 - y2Pos*2
|
||
|
|
||
|
normalX := (pos2.Y - pos1.Y) / frame.Width
|
||
|
normalY := (pos2.X - pos1.X) / frame.Height
|
||
|
dirLength := float32(math.Sqrt(float64(normalX*normalX + normalY*normalY)))
|
||
|
normalX /= dirLength
|
||
|
normalY /= dirLength
|
||
|
|
||
|
normalObjX := normalX * 0.5 * frame.Width
|
||
|
normalObjY := normalY * 0.5 * frame.Height
|
||
|
widthMultiplier := float32(math.Sqrt(float64(normalObjX*normalObjX + normalObjY*normalObjY)))
|
||
|
halfWidth := (roundToPixel(lineWidth+feather, p.pixScale) * 0.5) / widthMultiplier
|
||
|
featherWidth := feather / widthMultiplier
|
||
|
|
||
|
return []float32{
|
||
|
// coord x, y normal x, y
|
||
|
x1, y1, normalX, normalY,
|
||
|
x2, y2, normalX, normalY,
|
||
|
x2, y2, -normalX, -normalY,
|
||
|
x2, y2, -normalX, -normalY,
|
||
|
x1, y1, normalX, normalY,
|
||
|
x1, y1, -normalX, -normalY,
|
||
|
}, halfWidth, featherWidth
|
||
|
}
|
||
|
|
||
|
// rectCoords calculates the openGL coordinate space of a rectangle
|
||
|
func (p *painter) rectCoords(size fyne.Size, pos fyne.Position, frame fyne.Size,
|
||
|
fill canvas.ImageFill, aspect float32, pad float32) []float32 {
|
||
|
size, pos = rectInnerCoords(size, pos, fill, aspect)
|
||
|
size, pos = roundToPixelCoords(size, pos, p.pixScale)
|
||
|
|
||
|
xPos := (pos.X - pad) / frame.Width
|
||
|
x1 := -1 + xPos*2
|
||
|
x2Pos := (pos.X + size.Width + pad) / frame.Width
|
||
|
x2 := -1 + x2Pos*2
|
||
|
|
||
|
yPos := (pos.Y - pad) / frame.Height
|
||
|
y1 := 1 - yPos*2
|
||
|
y2Pos := (pos.Y + size.Height + pad) / frame.Height
|
||
|
y2 := 1 - y2Pos*2
|
||
|
|
||
|
return []float32{
|
||
|
// coord x, y, z texture x, y
|
||
|
x1, y2, 0, 0.0, 1.0, // top left
|
||
|
x1, y1, 0, 0.0, 0.0, // bottom left
|
||
|
x2, y2, 0, 1.0, 1.0, // top right
|
||
|
x2, y1, 0, 1.0, 0.0, // bottom right
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func rectInnerCoords(size fyne.Size, pos fyne.Position, fill canvas.ImageFill, aspect float32) (fyne.Size, fyne.Position) {
|
||
|
if fill == canvas.ImageFillContain || fill == canvas.ImageFillOriginal {
|
||
|
// change pos and size accordingly
|
||
|
|
||
|
viewAspect := size.Width / size.Height
|
||
|
|
||
|
newWidth, newHeight := size.Width, size.Height
|
||
|
widthPad, heightPad := float32(0), float32(0)
|
||
|
if viewAspect > aspect {
|
||
|
newWidth = size.Height * aspect
|
||
|
widthPad = (size.Width - newWidth) / 2
|
||
|
} else if viewAspect < aspect {
|
||
|
newHeight = size.Width / aspect
|
||
|
heightPad = (size.Height - newHeight) / 2
|
||
|
}
|
||
|
|
||
|
return fyne.NewSize(newWidth, newHeight), fyne.NewPos(pos.X+widthPad, pos.Y+heightPad)
|
||
|
}
|
||
|
|
||
|
return size, pos
|
||
|
}
|
||
|
|
||
|
func (p *painter) vecRectCoords(pos fyne.Position, rect *canvas.Rectangle, frame fyne.Size) ([4]float32, []float32) {
|
||
|
size := rect.Size()
|
||
|
pos1 := rect.Position()
|
||
|
|
||
|
xPosDiff := pos.X - pos1.X
|
||
|
yPosDiff := pos.Y - pos1.Y
|
||
|
pos1.X = roundToPixel(pos1.X+xPosDiff, p.pixScale)
|
||
|
pos1.Y = roundToPixel(pos1.Y+yPosDiff, p.pixScale)
|
||
|
size.Width = roundToPixel(size.Width, p.pixScale)
|
||
|
size.Height = roundToPixel(size.Height, p.pixScale)
|
||
|
|
||
|
x1Pos := pos1.X
|
||
|
x1Norm := -1 + x1Pos*2/frame.Width
|
||
|
x2Pos := pos1.X + size.Width
|
||
|
x2Norm := -1 + x2Pos*2/frame.Width
|
||
|
y1Pos := pos1.Y
|
||
|
y1Norm := 1 - y1Pos*2/frame.Height
|
||
|
y2Pos := pos1.Y + size.Height
|
||
|
y2Norm := 1 - y2Pos*2/frame.Height
|
||
|
|
||
|
// output a norm for the fill and the vert is unused, but we pass 0 to avoid optimisation issues
|
||
|
coords := []float32{
|
||
|
0, 0, x1Norm, y1Norm, // first triangle
|
||
|
0, 0, x2Norm, y1Norm, // second triangle
|
||
|
0, 0, x1Norm, y2Norm,
|
||
|
0, 0, x2Norm, y2Norm}
|
||
|
|
||
|
return [4]float32{x1Pos, y1Pos, x2Pos, y2Pos}, coords
|
||
|
}
|
||
|
|
||
|
func roundToPixel(v float32, pixScale float32) float32 {
|
||
|
if pixScale == 1.0 {
|
||
|
return float32(math.Round(float64(v)))
|
||
|
}
|
||
|
|
||
|
return float32(math.Round(float64(v*pixScale))) / pixScale
|
||
|
}
|
||
|
|
||
|
func roundToPixelCoords(size fyne.Size, pos fyne.Position, pixScale float32) (fyne.Size, fyne.Position) {
|
||
|
end := pos.Add(size)
|
||
|
end.X = roundToPixel(end.X, pixScale)
|
||
|
end.Y = roundToPixel(end.Y, pixScale)
|
||
|
pos.X = roundToPixel(pos.X, pixScale)
|
||
|
pos.Y = roundToPixel(pos.Y, pixScale)
|
||
|
size.Width = end.X - pos.X
|
||
|
size.Height = end.Y - pos.Y
|
||
|
|
||
|
return size, pos
|
||
|
}
|
||
|
|
||
|
// Returns FragmentColor(red,green,blue,alpha) from fyne.Color
|
||
|
func getFragmentColor(col color.Color) (float32, float32, float32, float32) {
|
||
|
if col == nil {
|
||
|
return 0, 0, 0, 0
|
||
|
}
|
||
|
r, g, b, a := col.RGBA()
|
||
|
if a == 0 {
|
||
|
return 0, 0, 0, 0
|
||
|
}
|
||
|
alpha := float32(a)
|
||
|
return float32(r) / alpha, float32(g) / alpha, float32(b) / alpha, alpha / 0xffff
|
||
|
}
|
||
|
|
||
|
func (p *painter) scaleFrameSize(frame fyne.Size) (float32, float32) {
|
||
|
frameWidthScaled := roundToPixel(frame.Width*p.pixScale, 1.0)
|
||
|
frameHeightScaled := roundToPixel(frame.Height*p.pixScale, 1.0)
|
||
|
return frameWidthScaled, frameHeightScaled
|
||
|
}
|
||
|
|
||
|
// Returns scaled RectCoords(x1,x2,y1,y2) in same order
|
||
|
func (p *painter) scaleRectCoords(x1, x2, y1, y2 float32) (float32, float32, float32, float32) {
|
||
|
x1Scaled := roundToPixel(x1*p.pixScale, 1.0)
|
||
|
x2Scaled := roundToPixel(x2*p.pixScale, 1.0)
|
||
|
y1Scaled := roundToPixel(y1*p.pixScale, 1.0)
|
||
|
y2Scaled := roundToPixel(y2*p.pixScale, 1.0)
|
||
|
return x1Scaled, x2Scaled, y1Scaled, y2Scaled
|
||
|
}
|