package gui import ( "math" "github.com/go-gl/gl/all-core/gl" "gitlab.com/liamg/raft/buffer" "gitlab.com/liamg/raft/config" "gitlab.com/liamg/raft/glfont" ) type Renderer interface { SetArea(areaX int, areaY int, areaWidth int, areaHeight int) DrawCellBg(cell buffer.Cell, col uint, row uint) DrawCellText(cell buffer.Cell, col uint, row uint) DrawCursor(col uint, row uint, colour config.Colour) GetTermSize() (uint, uint) CellWidth() float32 CellHeight() float32 } type OpenGLRenderer struct { font *glfont.Font areaWidth int areaHeight int areaX int areaY int cellWidth float32 cellHeight float32 termCols uint termRows uint cellPositions map[[2]uint][2]float32 rectangles map[[2]uint]*rectangle config *config.Config colourAttr uint32 program uint32 } type rectangle struct { vao uint32 vbo uint32 cv uint32 colourAttr uint32 colour [3]float32 points []float32 prog uint32 } func (r *OpenGLRenderer) CellWidth() float32 { return r.cellWidth } func (r *OpenGLRenderer) CellHeight() float32 { return r.cellHeight } func (r *OpenGLRenderer) newRectangle(x float32, y float32, colourAttr uint32) *rectangle { halfAreaWidth := float32(r.areaWidth / 2) halfAreaHeight := float32(r.areaHeight / 2) x = (x - halfAreaWidth) / halfAreaWidth y = -(y - halfAreaHeight) / halfAreaHeight w := r.cellWidth / halfAreaWidth h := r.cellHeight / halfAreaHeight rect := &rectangle{ points: []float32{ x, y, 0, x, y + h, 0, x + w, y + h, 0, x + w, y, 0, x, y, 0, x + w, y + h, 0, }, colourAttr: colourAttr, prog: r.program, } gl.UseProgram(rect.prog) // SHAPE gl.GenBuffers(1, &rect.vbo) gl.BindBuffer(gl.ARRAY_BUFFER, rect.vbo) gl.BufferData(gl.ARRAY_BUFFER, 4*len(rect.points), gl.Ptr(rect.points), gl.STATIC_DRAW) gl.GenVertexArrays(1, &rect.vao) gl.BindVertexArray(rect.vao) gl.EnableVertexAttribArray(0) gl.BindBuffer(gl.ARRAY_BUFFER, rect.vbo) gl.VertexAttribPointer(0, 3, gl.FLOAT, false, 0, nil) // colour gl.GenBuffers(1, &rect.cv) rect.setColour([3]float32{0, 1, 0}) return rect } func (rect *rectangle) Draw() { gl.UseProgram(rect.prog) gl.BindVertexArray(rect.vao) gl.DrawArrays(gl.TRIANGLES, 0, 6) } func (rect *rectangle) setColour(colour [3]float32) { if rect.colour == colour { return } c := []float32{ colour[0], colour[1], colour[2], colour[0], colour[1], colour[2], colour[0], colour[1], colour[2], colour[0], colour[1], colour[2], colour[0], colour[1], colour[2], colour[0], colour[1], colour[2], } gl.UseProgram(rect.prog) gl.BindBuffer(gl.ARRAY_BUFFER, rect.cv) gl.BufferData(gl.ARRAY_BUFFER, len(c)*4, gl.Ptr(c), gl.STATIC_DRAW) gl.EnableVertexAttribArray(rect.colourAttr) gl.VertexAttribPointer(rect.colourAttr, 3, gl.FLOAT, false, 0, gl.PtrOffset(0)) rect.colour = colour } func (rect *rectangle) Free() { gl.UseProgram(rect.prog) gl.DeleteVertexArrays(1, &rect.vao) gl.DeleteBuffers(1, &rect.vbo) gl.DeleteBuffers(1, &rect.cv) } func NewOpenGLRenderer(config *config.Config, font *glfont.Font, areaX int, areaY int, areaWidth int, areaHeight int, colourAttr uint32, program uint32) *OpenGLRenderer { r := &OpenGLRenderer{ areaWidth: areaWidth, areaHeight: areaHeight, areaX: areaX, areaY: areaY, cellPositions: map[[2]uint][2]float32{}, rectangles: map[[2]uint]*rectangle{}, config: config, colourAttr: colourAttr, program: program, } r.SetFont(font) return r } func (r *OpenGLRenderer) GetTermSize() (uint, uint) { return r.termCols, r.termRows } func (r *OpenGLRenderer) SetArea(areaX int, areaY int, areaWidth int, areaHeight int) { r.areaWidth = areaWidth r.areaHeight = areaHeight r.areaX = areaX r.areaY = areaY r.SetFont(r.font) } func (r *OpenGLRenderer) SetFont(font *glfont.Font) { // @todo check for monospace and return error if not? r.font = font r.cellWidth, _ = font.Size("X") r.cellHeight = font.LineHeight() // vertical padding r.termCols = uint(math.Floor(float64(float32(r.areaWidth) / r.cellWidth))) r.termRows = uint(math.Floor(float64(float32(r.areaHeight) / r.cellHeight))) r.rectangles = map[[2]uint]*rectangle{} } func (r *OpenGLRenderer) getRectangle(col uint, row uint) *rectangle { if rect, ok := r.rectangles[[2]uint{col, row}]; ok { return rect } return r.generateRectangle(col, row) } func (r *OpenGLRenderer) generateRectangle(col uint, line uint) *rectangle { rect, ok := r.rectangles[[2]uint{col, line}] if ok { rect.Free() } // rounding to whole pixels makes everything nice x := float32(float32(col) * r.cellWidth) y := float32((float32(line) * r.cellHeight)) + r.cellHeight + (r.font.LinePadding() / 2) r.rectangles[[2]uint{col, line}] = r.newRectangle(x, y, r.colourAttr) return r.rectangles[[2]uint{col, line}] } func (r *OpenGLRenderer) DrawCursor(col uint, row uint, colour config.Colour) { rect := r.getRectangle(col, row) rect.setColour(colour) rect.Draw() } func (r *OpenGLRenderer) DrawCellBg(cell buffer.Cell, col uint, row uint) { var bg [3]float32 if cell.Attr().Reverse { bg = cell.Fg() } else { bg = cell.Bg() } if bg != r.config.ColourScheme.Background { rect := r.getRectangle(col, row) rect.setColour(bg) rect.Draw() } } func (r *OpenGLRenderer) DrawCellText(cell buffer.Cell, col uint, row uint) { var fg [3]float32 if cell.Attr().Reverse { fg = cell.Bg() } else { fg = cell.Fg() } var alpha float32 = 1 if cell.Attr().Dim { alpha = 0.5 } r.font.SetColor(fg[0], fg[1], fg[2], alpha) x := float32(col) * r.cellWidth y := (float32(row+1) * r.cellHeight) - (r.font.LinePadding() / 2) if cell.Attr().Bold { // bold means draw text again one pixel to right, so it's fatter r.font.Print(x+1, y, string(cell.Rune())) } r.font.Print(x, y, string(cell.Rune())) }