mirror of https://github.com/liamg/aminal.git
efficiency, introduced resize bug though :/
This commit is contained in:
parent
bb38d22a60
commit
88528fc6b0
|
@ -20,7 +20,7 @@ Ensure you have your latest graphics card drivers installed before use.
|
||||||
|
|
||||||
## What isn't supported?
|
## What isn't supported?
|
||||||
|
|
||||||
- Suspend/Continue (^S, ^Q). This is archaic bullshit that annoys more people than it helps. Basically:
|
- Suspend/Continue (\^S, \^Q). This is archaic bullshit that annoys more people than it helps. Basically:q
|
||||||
|
|
||||||
<span style="display:block;text-align:center">
|
<span style="display:block;text-align:center">
|
||||||

|

|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
package buffer
|
package buffer
|
||||||
|
|
||||||
type Cell struct {
|
type Cell struct {
|
||||||
r rune
|
r rune
|
||||||
attr CellAttributes
|
attr CellAttributes
|
||||||
hasContent bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type CellAttributes struct {
|
type CellAttributes struct {
|
||||||
|
@ -43,5 +42,12 @@ func (cell *Cell) erase() {
|
||||||
|
|
||||||
func (cell *Cell) setRune(r rune) {
|
func (cell *Cell) setRune(r rune) {
|
||||||
cell.r = r
|
cell.r = r
|
||||||
cell.hasContent = r > 0
|
}
|
||||||
|
|
||||||
|
func NewBackgroundCell(colour [3]float32) Cell {
|
||||||
|
return Cell{
|
||||||
|
attr: CellAttributes{
|
||||||
|
BgColour: colour,
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,18 +0,0 @@
|
||||||
package buffer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestSetRune(t *testing.T) {
|
|
||||||
cell := newCell()
|
|
||||||
assert.False(t, cell.hasContent)
|
|
||||||
cell.setRune('X')
|
|
||||||
assert.True(t, cell.hasContent)
|
|
||||||
assert.Equal(t, 'X', cell.r)
|
|
||||||
cell.setRune('Y')
|
|
||||||
assert.True(t, cell.hasContent)
|
|
||||||
assert.Equal(t, 'Y', cell.r)
|
|
||||||
}
|
|
150
gui/gui.go
150
gui/gui.go
|
@ -9,34 +9,37 @@ import (
|
||||||
|
|
||||||
"github.com/go-gl/gl/all-core/gl"
|
"github.com/go-gl/gl/all-core/gl"
|
||||||
"github.com/go-gl/glfw/v3.2/glfw"
|
"github.com/go-gl/glfw/v3.2/glfw"
|
||||||
|
"gitlab.com/liamg/raft/buffer"
|
||||||
"gitlab.com/liamg/raft/config"
|
"gitlab.com/liamg/raft/config"
|
||||||
"gitlab.com/liamg/raft/terminal"
|
"gitlab.com/liamg/raft/terminal"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
type GUI struct {
|
type GUI struct {
|
||||||
window *glfw.Window
|
window *glfw.Window
|
||||||
logger *zap.SugaredLogger
|
logger *zap.SugaredLogger
|
||||||
config *config.Config
|
config *config.Config
|
||||||
terminal *terminal.Terminal
|
terminal *terminal.Terminal
|
||||||
width int //window width in pixels
|
width int //window width in pixels
|
||||||
height int //window height in pixels
|
height int //window height in pixels
|
||||||
font *glfont.Font
|
font *glfont.Font
|
||||||
fontScale int32
|
fontScale int32
|
||||||
renderer Renderer
|
renderer Renderer
|
||||||
colourAttr uint32
|
colourAttr uint32
|
||||||
|
renderState *RenderState
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(config *config.Config, terminal *terminal.Terminal, logger *zap.SugaredLogger) *GUI {
|
func New(config *config.Config, terminal *terminal.Terminal, logger *zap.SugaredLogger) *GUI {
|
||||||
|
|
||||||
//logger.
|
//logger.
|
||||||
return &GUI{
|
return &GUI{
|
||||||
config: config,
|
config: config,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
width: 600,
|
width: 600,
|
||||||
height: 300,
|
height: 300,
|
||||||
terminal: terminal,
|
terminal: terminal,
|
||||||
fontScale: 15.0,
|
fontScale: 15.0,
|
||||||
|
renderState: NewRenderState(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,25 +48,39 @@ func New(config *config.Config, terminal *terminal.Terminal, logger *zap.Sugared
|
||||||
// can only be called on OS thread
|
// can only be called on OS thread
|
||||||
func (gui *GUI) resize(w *glfw.Window, width int, height int) {
|
func (gui *GUI) resize(w *glfw.Window, width int, height int) {
|
||||||
|
|
||||||
gui.logger.Debugf("GUI resize to %dx%d", width, height)
|
if width == gui.width && height == gui.height {
|
||||||
|
//return
|
||||||
|
}
|
||||||
|
|
||||||
|
gui.logger.Debugf("Initiating GUI resize to %dx%d", width, height)
|
||||||
|
|
||||||
gui.width = width
|
gui.width = width
|
||||||
gui.height = height
|
gui.height = height
|
||||||
|
|
||||||
|
gui.logger.Debugf("Updating font resolution...")
|
||||||
if gui.font != nil {
|
if gui.font != nil {
|
||||||
gui.font.UpdateResolution((width), (height))
|
gui.font.UpdateResolution((width), (height))
|
||||||
}
|
}
|
||||||
|
|
||||||
gl.Viewport(0, 0, int32(gui.width), int32(gui.height))
|
gui.logger.Debugf("Setting renderer area...")
|
||||||
|
|
||||||
gui.renderer.SetArea(0, 0, gui.width, gui.height)
|
gui.renderer.SetArea(0, 0, gui.width, gui.height)
|
||||||
|
|
||||||
|
gui.logger.Debugf("Calculating size in cols/rows...")
|
||||||
cols, rows := gui.renderer.GetTermSize()
|
cols, rows := gui.renderer.GetTermSize()
|
||||||
|
|
||||||
|
gui.logger.Debugf("Resizing internal terminal...")
|
||||||
if err := gui.terminal.SetSize(cols, rows); err != nil {
|
if err := gui.terminal.SetSize(cols, rows); err != nil {
|
||||||
gui.logger.Errorf("Failed to resize terminal to %d cols, %d rows: %s", cols, rows, err)
|
gui.logger.Errorf("Failed to resize terminal to %d cols, %d rows: %s", cols, rows, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gui.logger.Debugf("Resetting render state...")
|
||||||
|
gui.renderState.Reset()
|
||||||
|
|
||||||
|
gui.logger.Debugf("Setting viewport size...")
|
||||||
|
gl.Viewport(0, 0, int32(gui.width), int32(gui.height))
|
||||||
|
|
||||||
|
gui.logger.Debugf("Resize complete!")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *GUI) glfwScrollCallback(w *glfw.Window, xoff float64, yoff float64) {
|
func (gui *GUI) glfwScrollCallback(w *glfw.Window, xoff float64, yoff float64) {
|
||||||
|
@ -93,7 +110,7 @@ func (gui *GUI) Render() error {
|
||||||
|
|
||||||
gui.logger.Debugf("Creating window...")
|
gui.logger.Debugf("Creating window...")
|
||||||
var err error
|
var err error
|
||||||
gui.window, err = gui.createWindow(gui.width, gui.height)
|
gui.window, err = gui.createWindow(500, 300)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Failed to create window: %s", err)
|
return fmt.Errorf("Failed to create window: %s", err)
|
||||||
}
|
}
|
||||||
|
@ -172,37 +189,79 @@ func (gui *GUI) Render() error {
|
||||||
gui.terminal.AttachTitleChangeHandler(titleChan)
|
gui.terminal.AttachTitleChangeHandler(titleChan)
|
||||||
gui.terminal.AttachDisplayChangeHandler(changeChan)
|
gui.terminal.AttachDisplayChangeHandler(changeChan)
|
||||||
|
|
||||||
frames := 0
|
gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
|
||||||
frameCount := 0
|
|
||||||
fps := 0
|
|
||||||
ticker := time.NewTicker(time.Second)
|
ticker := time.NewTicker(time.Second)
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
dirty := true
|
||||||
|
defaultCell := buffer.NewBackgroundCell(gui.config.ColourScheme.Background)
|
||||||
|
|
||||||
|
var lastCursorX uint
|
||||||
|
var lastCursorY uint
|
||||||
|
|
||||||
for !gui.window.ShouldClose() {
|
for !gui.window.ShouldClose() {
|
||||||
|
|
||||||
select {
|
select {
|
||||||
|
|
||||||
case <-changeChan:
|
case <-ticker.C:
|
||||||
frames = 2
|
|
||||||
gui.logger.Sync()
|
gui.logger.Sync()
|
||||||
|
case <-changeChan:
|
||||||
|
dirty = true
|
||||||
case <-titleChan:
|
case <-titleChan:
|
||||||
gui.window.SetTitle(gui.terminal.GetTitle())
|
gui.window.SetTitle(gui.terminal.GetTitle())
|
||||||
case <-ticker.C:
|
|
||||||
fps = frameCount
|
|
||||||
frameCount = 0
|
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
gl.UseProgram(program)
|
gl.UseProgram(program)
|
||||||
|
|
||||||
if gui.config.Rendering.AlwaysRepaint || frames > 0 {
|
if dirty {
|
||||||
|
gui.window.SwapBuffers()
|
||||||
|
dirty = false
|
||||||
|
}
|
||||||
|
|
||||||
gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
|
if gui.terminal.CheckDirty() {
|
||||||
|
|
||||||
|
if gui.terminal.Modes().ShowCursor {
|
||||||
|
cx := uint(gui.terminal.GetLogicalCursorX())
|
||||||
|
cy := uint(gui.terminal.GetLogicalCursorY())
|
||||||
|
cy = cy + uint(gui.terminal.GetScrollOffset())
|
||||||
|
|
||||||
|
if lastCursorX != cx || lastCursorY != cy {
|
||||||
|
gui.renderState.SetDirty(lastCursorX, lastCursorY)
|
||||||
|
dirty = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
gui.renderState.SetDirty(lastCursorX, lastCursorY)
|
||||||
|
dirty = true
|
||||||
|
}
|
||||||
|
|
||||||
lines := gui.terminal.GetVisibleLines()
|
lines := gui.terminal.GetVisibleLines()
|
||||||
for y := 0; y < len(lines); y++ {
|
lineCount := gui.terminal.ActiveBuffer().ViewHeight()
|
||||||
for x, cell := range lines[y].Cells() {
|
colCount := gui.terminal.ActiveBuffer().ViewWidth()
|
||||||
gui.renderer.DrawCell(cell, uint(x), uint(y))
|
for y := 0; y < int(lineCount); y++ {
|
||||||
|
|
||||||
|
for x := 0; x < int(colCount); x++ {
|
||||||
|
|
||||||
|
cell := defaultCell
|
||||||
|
empty := true
|
||||||
|
|
||||||
|
if y < len(lines) {
|
||||||
|
cells := lines[y].Cells()
|
||||||
|
if x < len(cells) {
|
||||||
|
cell = cells[x]
|
||||||
|
if cell.Rune() == 0 {
|
||||||
|
cell = defaultCell
|
||||||
|
}
|
||||||
|
empty = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if gui.renderState.RequiresRender(uint(x), uint(y), cell.Bg(), cell.Fg(), cell.Rune(), empty) {
|
||||||
|
gui.renderer.DrawCell(cell, uint(x), uint(y))
|
||||||
|
dirty = true
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -210,26 +269,19 @@ func (gui *GUI) Render() error {
|
||||||
cx := uint(gui.terminal.GetLogicalCursorX())
|
cx := uint(gui.terminal.GetLogicalCursorX())
|
||||||
cy := uint(gui.terminal.GetLogicalCursorY())
|
cy := uint(gui.terminal.GetLogicalCursorY())
|
||||||
cy = cy + uint(gui.terminal.GetScrollOffset())
|
cy = cy + uint(gui.terminal.GetScrollOffset())
|
||||||
gui.renderer.DrawCursor(cx, cy, gui.config.ColourScheme.Cursor)
|
|
||||||
|
if lastCursorX != cx || lastCursorY != cy {
|
||||||
|
gui.renderer.DrawCursor(cx, cy, gui.config.ColourScheme.Cursor)
|
||||||
|
gui.renderState.SetDirty(lastCursorX, lastCursorY)
|
||||||
|
lastCursorX = cx
|
||||||
|
lastCursorY = cy
|
||||||
|
dirty = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = fps
|
|
||||||
/*
|
|
||||||
gui.font.SetColor(1, 0.5, 0.5, 0.5)
|
|
||||||
fpsData := ""
|
|
||||||
if gui.config.Rendering.AlwaysRepaint {
|
|
||||||
fpsData = fmt.Sprintf("%d FPS | %d,%d", fps, gui.terminal.GetLogicalCursorX(), gui.terminal.GetLogicalCursorY())
|
|
||||||
}
|
|
||||||
gui.font.Print(10, float32(gui.height-20), 1.5, fmt.Sprintf("%s", fpsData))
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
||||||
if gui.config.Rendering.AlwaysRepaint || frames > 0 {
|
|
||||||
gui.window.SwapBuffers()
|
|
||||||
frameCount++
|
|
||||||
frames--
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//glfw.PollEvents()
|
||||||
glfw.WaitEventsTimeout(0.02) // up to 50fps on no input, otherwise higher
|
glfw.WaitEventsTimeout(0.02) // up to 50fps on no input, otherwise higher
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
103
gui/renderer.go
103
gui/renderer.go
|
@ -43,6 +43,7 @@ type rectangle struct {
|
||||||
colourAttr uint32
|
colourAttr uint32
|
||||||
colour [3]float32
|
colour [3]float32
|
||||||
points []float32
|
points []float32
|
||||||
|
prog uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *OpenGLRenderer) newRectangle(x float32, y float32, colourAttr uint32) *rectangle {
|
func (r *OpenGLRenderer) newRectangle(x float32, y float32, colourAttr uint32) *rectangle {
|
||||||
|
@ -53,7 +54,6 @@ func (r *OpenGLRenderer) newRectangle(x float32, y float32, colourAttr uint32) *
|
||||||
h := r.cellHeight / float32(r.areaHeight/2)
|
h := r.cellHeight / float32(r.areaHeight/2)
|
||||||
|
|
||||||
rect := &rectangle{
|
rect := &rectangle{
|
||||||
colour: [3]float32{0, 0, 1},
|
|
||||||
points: []float32{
|
points: []float32{
|
||||||
x, y, 0,
|
x, y, 0,
|
||||||
x, y + h, 0,
|
x, y + h, 0,
|
||||||
|
@ -64,24 +64,8 @@ func (r *OpenGLRenderer) newRectangle(x float32, y float32, colourAttr uint32) *
|
||||||
x + w, y + h, 0,
|
x + w, y + h, 0,
|
||||||
},
|
},
|
||||||
colourAttr: colourAttr,
|
colourAttr: colourAttr,
|
||||||
|
prog: r.program,
|
||||||
}
|
}
|
||||||
|
|
||||||
rect.gen()
|
|
||||||
|
|
||||||
return rect
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rect *rectangle) gen() {
|
|
||||||
|
|
||||||
colour := []float32{
|
|
||||||
rect.colour[0], rect.colour[1], rect.colour[2],
|
|
||||||
rect.colour[0], rect.colour[1], rect.colour[2],
|
|
||||||
rect.colour[0], rect.colour[1], rect.colour[2],
|
|
||||||
rect.colour[0], rect.colour[1], rect.colour[2],
|
|
||||||
rect.colour[0], rect.colour[1], rect.colour[2],
|
|
||||||
rect.colour[0], rect.colour[1], rect.colour[2],
|
|
||||||
}
|
|
||||||
|
|
||||||
// SHAPE
|
// SHAPE
|
||||||
gl.GenBuffers(1, &rect.vbo)
|
gl.GenBuffers(1, &rect.vbo)
|
||||||
gl.BindBuffer(gl.ARRAY_BUFFER, rect.vbo)
|
gl.BindBuffer(gl.ARRAY_BUFFER, rect.vbo)
|
||||||
|
@ -96,22 +80,37 @@ func (rect *rectangle) gen() {
|
||||||
|
|
||||||
// colour
|
// colour
|
||||||
gl.GenBuffers(1, &rect.cv)
|
gl.GenBuffers(1, &rect.cv)
|
||||||
gl.BindBuffer(gl.ARRAY_BUFFER, rect.cv)
|
|
||||||
gl.BufferData(gl.ARRAY_BUFFER, len(colour)*4, gl.Ptr(colour), gl.STATIC_DRAW)
|
rect.setColour([3]float32{0, 1, 0})
|
||||||
gl.EnableVertexAttribArray(rect.colourAttr)
|
|
||||||
gl.VertexAttribPointer(rect.colourAttr, 3, gl.FLOAT, false, 0, gl.PtrOffset(0))
|
return rect
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rect *rectangle) setColour(colour [3]float32) {
|
func (rect *rectangle) setColour(colour [3]float32) {
|
||||||
if rect.colour == colour {
|
if rect.colour == colour {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
rect.Free()
|
|
||||||
|
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
|
rect.colour = colour
|
||||||
rect.gen()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rect *rectangle) Free() {
|
func (rect *rectangle) Free() {
|
||||||
|
gl.UseProgram(rect.prog)
|
||||||
gl.DeleteVertexArrays(1, &rect.vao)
|
gl.DeleteVertexArrays(1, &rect.vao)
|
||||||
gl.DeleteBuffers(1, &rect.vbo)
|
gl.DeleteBuffers(1, &rect.vbo)
|
||||||
gl.DeleteBuffers(1, &rect.cv)
|
gl.DeleteBuffers(1, &rect.cv)
|
||||||
|
@ -159,7 +158,6 @@ func (r *OpenGLRenderer) SetFont(font *glfont.Font) { // @todo check for monospa
|
||||||
r.termCols = uint(math.Floor(float64(float32(r.areaWidth) / r.cellWidth)))
|
r.termCols = uint(math.Floor(float64(float32(r.areaWidth) / r.cellWidth)))
|
||||||
r.termRows = uint(math.Floor(float64(float32(r.areaHeight) / r.cellHeight)))
|
r.termRows = uint(math.Floor(float64(float32(r.areaHeight) / r.cellHeight)))
|
||||||
r.calculatePositions()
|
r.calculatePositions()
|
||||||
r.generateRectangles()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *OpenGLRenderer) calculatePositions() {
|
func (r *OpenGLRenderer) calculatePositions() {
|
||||||
|
@ -175,22 +173,27 @@ func (r *OpenGLRenderer) calculatePositions() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *OpenGLRenderer) generateRectangles() {
|
func (r *OpenGLRenderer) getRectangle(col uint, row uint) *rectangle {
|
||||||
gl.UseProgram(r.program)
|
if rect, ok := r.rectangles[[2]uint{col, row}]; ok {
|
||||||
for line := uint(0); line < r.termRows; line++ {
|
return rect
|
||||||
for col := uint(0); col < r.termCols; col++ {
|
|
||||||
|
|
||||||
rect, ok := r.rectangles[[2]uint{col, line}]
|
|
||||||
if ok {
|
|
||||||
rect.Free()
|
|
||||||
}
|
|
||||||
|
|
||||||
// rounding to whole pixels makes everything nice
|
|
||||||
x := float32(float64((float32(col) * r.cellWidth)))
|
|
||||||
y := float32(float64((float32(line) * r.cellHeight) + (r.cellHeight)))
|
|
||||||
r.rectangles[[2]uint{col, line}] = r.newRectangle(x, y, r.colourAttr)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return r.generateRectangle(col, row)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *OpenGLRenderer) generateRectangle(col uint, line uint) *rectangle {
|
||||||
|
|
||||||
|
gl.UseProgram(r.program)
|
||||||
|
|
||||||
|
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.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) {
|
func (r *OpenGLRenderer) DrawCursor(col uint, row uint, colour config.Colour) {
|
||||||
|
@ -220,10 +223,6 @@ func (r *OpenGLRenderer) DrawCursor(col uint, row uint, colour config.Colour) {
|
||||||
|
|
||||||
func (r *OpenGLRenderer) DrawCell(cell buffer.Cell, col uint, row uint) {
|
func (r *OpenGLRenderer) DrawCell(cell buffer.Cell, col uint, row uint) {
|
||||||
|
|
||||||
if cell.Attr().Hidden || (cell.Rune() == 0x00) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var fg [3]float32
|
var fg [3]float32
|
||||||
var bg [3]float32
|
var bg [3]float32
|
||||||
|
|
||||||
|
@ -241,18 +240,10 @@ func (r *OpenGLRenderer) DrawCell(cell buffer.Cell, col uint, row uint) {
|
||||||
}
|
}
|
||||||
|
|
||||||
gl.UseProgram(r.program)
|
gl.UseProgram(r.program)
|
||||||
|
rect := r.getRectangle(col, row)
|
||||||
// don't bother rendering rectangles that are the same colour as the background
|
rect.setColour(bg)
|
||||||
if bg != r.config.ColourScheme.Background {
|
gl.BindVertexArray(rect.vao)
|
||||||
|
gl.DrawArrays(gl.TRIANGLES, 0, 6)
|
||||||
rect, ok := r.rectangles[[2]uint{col, row}]
|
|
||||||
if !ok {
|
|
||||||
panic(fmt.Sprintf("Missing rectangle data for cell at %d,%d", col, row))
|
|
||||||
}
|
|
||||||
rect.setColour(bg)
|
|
||||||
gl.BindVertexArray(rect.vao)
|
|
||||||
gl.DrawArrays(gl.TRIANGLE_STRIP, 0, 6)
|
|
||||||
}
|
|
||||||
|
|
||||||
var alpha float32 = 1
|
var alpha float32 = 1
|
||||||
if cell.Attr().Dim {
|
if cell.Attr().Dim {
|
||||||
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
package gui
|
||||||
|
|
||||||
|
type RenderState struct {
|
||||||
|
cells map[[2]uint]RenderedCell
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRenderState() *RenderState {
|
||||||
|
return &RenderState{
|
||||||
|
cells: map[[2]uint]RenderedCell{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type RenderedCell struct {
|
||||||
|
bg [3]float32
|
||||||
|
fg [3]float32
|
||||||
|
contents rune
|
||||||
|
dirty bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rs *RenderState) Reset() {
|
||||||
|
rs.cells = map[[2]uint]RenderedCell{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rs *RenderState) SetDirty(x uint, y uint) {
|
||||||
|
rs.cells[[2]uint{x, y}] = RenderedCell{
|
||||||
|
bg: [3]float32{0, 0, 0},
|
||||||
|
fg: [3]float32{0, 0, 0},
|
||||||
|
contents: 0,
|
||||||
|
dirty: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rs *RenderState) RequiresRender(x uint, y uint, bg [3]float32, fg [3]float32, contents rune, empty bool) bool {
|
||||||
|
|
||||||
|
state, found := rs.cells[[2]uint{x, y}]
|
||||||
|
if !found {
|
||||||
|
if empty {
|
||||||
|
//return false
|
||||||
|
}
|
||||||
|
rs.cells[[2]uint{x, y}] = RenderedCell{
|
||||||
|
bg: bg,
|
||||||
|
fg: fg,
|
||||||
|
contents: contents,
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if state.bg != bg || state.fg != fg || state.contents != contents || state.dirty {
|
||||||
|
rs.cells[[2]uint{x, y}] = RenderedCell{
|
||||||
|
bg: bg,
|
||||||
|
fg: fg,
|
||||||
|
contents: contents,
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
102
terminal/ansi.go
102
terminal/ansi.go
|
@ -1,18 +1,36 @@
|
||||||
package terminal
|
package terminal
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
// https://www.xfree86.org/4.8.0/ctlseqs.html
|
// https://www.xfree86.org/4.8.0/ctlseqs.html
|
||||||
// https://vt100.net/docs/vt100-ug/chapter3.html
|
// https://vt100.net/docs/vt100-ug/chapter3.html
|
||||||
|
|
||||||
var ansiSequenceMap = map[rune]escapeSequenceHandler{
|
var ansiSequenceMap = map[rune]escapeSequenceHandler{
|
||||||
'[': csiHandler,
|
'[': csiHandler,
|
||||||
0x5d: oscHandler,
|
']': oscHandler,
|
||||||
'7': saveCursorHandler,
|
'7': saveCursorHandler,
|
||||||
'8': restoreCursorHandler,
|
'8': restoreCursorHandler,
|
||||||
'D': indexHandler,
|
'D': indexHandler,
|
||||||
'M': reverseIndexHandler,
|
'M': reverseIndexHandler,
|
||||||
|
'c': swallowHandler(0), //RIS
|
||||||
|
'(': swallowHandler(1), // character set bullshit
|
||||||
|
')': swallowHandler(1), // character set bullshit
|
||||||
|
'*': swallowHandler(1), // character set bullshit
|
||||||
|
'+': swallowHandler(1), // character set bullshit
|
||||||
|
'>': swallowHandler(0), // numeric char selection //@todo
|
||||||
|
'=': swallowHandler(0), // alt char selection //@todo
|
||||||
}
|
}
|
||||||
|
|
||||||
func indexHandler(buffer chan rune, terminal *Terminal) error {
|
func swallowHandler(n int) func(pty chan rune, terminal *Terminal) error {
|
||||||
|
return func(pty chan rune, terminal *Terminal) error {
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
<-pty
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func indexHandler(pty chan rune, terminal *Terminal) error {
|
||||||
// @todo is thus right?
|
// @todo is thus right?
|
||||||
// "This sequence causes the active position to move downward one line without changing the column position. If the active position is at the bottom margin, a scroll up is performed."
|
// "This sequence causes the active position to move downward one line without changing the column position. If the active position is at the bottom margin, a scroll up is performed."
|
||||||
if terminal.ActiveBuffer().CursorLine() == terminal.ActiveBuffer().ViewHeight()-1 {
|
if terminal.ActiveBuffer().CursorLine() == terminal.ActiveBuffer().ViewHeight()-1 {
|
||||||
|
@ -23,17 +41,17 @@ func indexHandler(buffer chan rune, terminal *Terminal) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func reverseIndexHandler(buffer chan rune, terminal *Terminal) error {
|
func reverseIndexHandler(pty chan rune, terminal *Terminal) error {
|
||||||
terminal.ActiveBuffer().MovePosition(0, -1)
|
terminal.ActiveBuffer().MovePosition(0, -1)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func saveCursorHandler(buffer chan rune, terminal *Terminal) error {
|
func saveCursorHandler(pty chan rune, terminal *Terminal) error {
|
||||||
terminal.ActiveBuffer().SaveCursor()
|
terminal.ActiveBuffer().SaveCursor()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func restoreCursorHandler(buffer chan rune, terminal *Terminal) error {
|
func restoreCursorHandler(pty chan rune, terminal *Terminal) error {
|
||||||
terminal.ActiveBuffer().RestoreCursor()
|
terminal.ActiveBuffer().RestoreCursor()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -47,67 +65,5 @@ func ansiHandler(pty chan rune, terminal *Terminal) error {
|
||||||
return handler(pty, terminal)
|
return handler(pty, terminal)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch b {
|
return fmt.Errorf("Unknown ANSI control sequence byte: 0x%02X [%v]", b, string(b))
|
||||||
|
|
||||||
case 'c':
|
|
||||||
terminal.logger.Errorf("RIS not yet supported")
|
|
||||||
case '(':
|
|
||||||
b = <-pty
|
|
||||||
switch b {
|
|
||||||
case 'A': //uk @todo handle these?
|
|
||||||
//terminal.charSet = C0
|
|
||||||
case 'B': //us
|
|
||||||
//terminal.charSet = C0
|
|
||||||
}
|
|
||||||
case ')':
|
|
||||||
b = <-pty
|
|
||||||
switch b {
|
|
||||||
case 'A': //uk @todo handle these?
|
|
||||||
//terminal.charSet = C1
|
|
||||||
case 'B': //us
|
|
||||||
//terminal.charSet = C1
|
|
||||||
}
|
|
||||||
case '*':
|
|
||||||
b = <-pty
|
|
||||||
switch b {
|
|
||||||
case 'A': //uk @todo handle these?
|
|
||||||
//terminal.charSet = C2
|
|
||||||
case 'B': //us
|
|
||||||
//terminal.charSet = C2
|
|
||||||
}
|
|
||||||
case '+':
|
|
||||||
b = <-pty
|
|
||||||
switch b {
|
|
||||||
case 'A': //uk @todo handle these?
|
|
||||||
//terminal.charSet = C3
|
|
||||||
case 'B': //us
|
|
||||||
//terminal.charSet = C3
|
|
||||||
}
|
|
||||||
case '>':
|
|
||||||
// numeric char selection @todo
|
|
||||||
case '=':
|
|
||||||
//alternate char selection @todo
|
|
||||||
case '?':
|
|
||||||
pm := ""
|
|
||||||
for {
|
|
||||||
b = <-pty
|
|
||||||
switch b {
|
|
||||||
case 'h':
|
|
||||||
switch pm {
|
|
||||||
default:
|
|
||||||
terminal.logger.Errorf("Unknown private code ESC?%sh", pm)
|
|
||||||
}
|
|
||||||
case 'l':
|
|
||||||
switch pm {
|
|
||||||
default:
|
|
||||||
terminal.logger.Errorf("Unknown private code ESC?%sl", pm)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
pm += string(b)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
terminal.logger.Errorf("Unknown control sequence: 0x%02X [%s]", b, string(b))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -98,7 +98,7 @@ func csiSetMode(modeStr string, enabled bool, terminal *Terminal) error {
|
||||||
} else {
|
} else {
|
||||||
terminal.UseMainBuffer()
|
terminal.UseMainBuffer()
|
||||||
}
|
}
|
||||||
case "?1000", "?10061000": // ?10061000 seen from htop
|
case "?1000", "?1006;1000", "?10061000": // ?10061000 seen from htop
|
||||||
// enable mouse tracking
|
// enable mouse tracking
|
||||||
if enabled {
|
if enabled {
|
||||||
terminal.SetMouseMode(MouseModeVT200)
|
terminal.SetMouseMode(MouseModeVT200)
|
||||||
|
@ -153,16 +153,16 @@ func csiWindowManipulation(params []string, intermediate string, terminal *Termi
|
||||||
}
|
}
|
||||||
|
|
||||||
func csiLinePositionAbsolute(params []string, intermediate string, terminal *Terminal) error {
|
func csiLinePositionAbsolute(params []string, intermediate string, terminal *Terminal) error {
|
||||||
col := 1
|
row := 1
|
||||||
if len(params) > 0 {
|
if len(params) > 0 {
|
||||||
var err error
|
var err error
|
||||||
col, err = strconv.Atoi(params[0])
|
row, err = strconv.Atoi(params[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
col = 1
|
row = 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
terminal.ActiveBuffer().SetPosition(uint16(col), terminal.ActiveBuffer().CursorLine())
|
terminal.ActiveBuffer().SetPosition(terminal.ActiveBuffer().CursorColumn(), uint16(row-1))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,9 +12,59 @@ type TerminalCharSet int
|
||||||
type escapeSequenceHandler func(pty chan rune, terminal *Terminal) error
|
type escapeSequenceHandler func(pty chan rune, terminal *Terminal) error
|
||||||
|
|
||||||
var escapeSequenceMap = map[rune]escapeSequenceHandler{
|
var escapeSequenceMap = map[rune]escapeSequenceHandler{
|
||||||
|
0x05: enqSequenceHandler,
|
||||||
|
0x07: bellSequenceHandler,
|
||||||
|
0x08: backspaceSequenceHandler,
|
||||||
|
0x09: tabSequenceHandler,
|
||||||
|
0x0a: newLineSequenceHandler,
|
||||||
|
0x0b: newLineSequenceHandler,
|
||||||
|
0x0c: newLineSequenceHandler,
|
||||||
|
0x0d: carriageReturnSequenceHandler,
|
||||||
|
0x0e: shiftOutSequenceHandler,
|
||||||
|
0x0f: shiftInSequenceHandler,
|
||||||
0x1b: ansiHandler,
|
0x1b: ansiHandler,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func newLineSequenceHandler(pty chan rune, terminal *Terminal) error {
|
||||||
|
terminal.ActiveBuffer().NewLine()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func carriageReturnSequenceHandler(pty chan rune, terminal *Terminal) error {
|
||||||
|
terminal.ActiveBuffer().CarriageReturn()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func backspaceSequenceHandler(pty chan rune, terminal *Terminal) error {
|
||||||
|
terminal.ActiveBuffer().Backspace()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func bellSequenceHandler(pty chan rune, terminal *Terminal) error {
|
||||||
|
// @todo ring bell - flash red or some shit?
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func enqSequenceHandler(pty chan rune, terminal *Terminal) error {
|
||||||
|
terminal.logger.Errorf("Received ENQ!")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func shiftOutSequenceHandler(pty chan rune, terminal *Terminal) error {
|
||||||
|
terminal.logger.Errorf("Received shift out")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func shiftInSequenceHandler(pty chan rune, terminal *Terminal) error {
|
||||||
|
terminal.logger.Errorf("Received shift in")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func tabSequenceHandler(pty chan rune, terminal *Terminal) error {
|
||||||
|
terminal.logger.Errorf("Received tab")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (terminal *Terminal) processInput(ctx context.Context, pty chan rune) {
|
func (terminal *Terminal) processInput(ctx context.Context, pty chan rune) {
|
||||||
|
|
||||||
// https://en.wikipedia.org/wiki/ANSI_escape_code
|
// https://en.wikipedia.org/wiki/ANSI_escape_code
|
||||||
|
@ -43,28 +93,8 @@ func (terminal *Terminal) processInput(ctx context.Context, pty chan rune) {
|
||||||
if err := handler(pty, terminal); err != nil {
|
if err := handler(pty, terminal); err != nil {
|
||||||
terminal.logger.Errorf("Error handling escape sequence: %s", err)
|
terminal.logger.Errorf("Error handling escape sequence: %s", err)
|
||||||
}
|
}
|
||||||
continue
|
} else {
|
||||||
}
|
terminal.logger.Debugf("Received character 0x%X: %q", b, string(b))
|
||||||
|
|
||||||
terminal.logger.Debugf("Received character 0x%X: %q", b, string(b))
|
|
||||||
|
|
||||||
switch b {
|
|
||||||
case 0x0a, 0x0c, 0x0b: // LF, FF, VT
|
|
||||||
terminal.ActiveBuffer().NewLine()
|
|
||||||
case 0x0d: // CR
|
|
||||||
terminal.ActiveBuffer().CarriageReturn()
|
|
||||||
case 0x08: // BS
|
|
||||||
// backspace
|
|
||||||
terminal.ActiveBuffer().Backspace()
|
|
||||||
case 0x07: // BEL
|
|
||||||
// @todo ring bell - flash red or some shit?
|
|
||||||
case 0x05: // ENQ
|
|
||||||
terminal.logger.Errorf("Received ENQ!")
|
|
||||||
case 0xe, 0xf:
|
|
||||||
terminal.logger.Errorf("Received SI/SO")
|
|
||||||
case 0x09:
|
|
||||||
terminal.logger.Errorf("Received TAB")
|
|
||||||
default:
|
|
||||||
if b >= 0x20 {
|
if b >= 0x20 {
|
||||||
terminal.ActiveBuffer().Write(b)
|
terminal.ActiveBuffer().Write(b)
|
||||||
} else {
|
} else {
|
||||||
|
@ -72,5 +102,6 @@ func (terminal *Terminal) processInput(ctx context.Context, pty chan rune) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
terminal.isDirty = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,6 +45,7 @@ type Terminal struct {
|
||||||
resumeChan chan bool
|
resumeChan chan bool
|
||||||
modes Modes
|
modes Modes
|
||||||
mouseMode MouseMode
|
mouseMode MouseMode
|
||||||
|
isDirty bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type Modes struct {
|
type Modes struct {
|
||||||
|
@ -90,6 +91,12 @@ func New(pty *os.File, logger *zap.SugaredLogger, config *config.Config) *Termin
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (terminal *Terminal) CheckDirty() bool {
|
||||||
|
d := terminal.isDirty
|
||||||
|
terminal.isDirty = false
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
|
||||||
func (terminal *Terminal) IsApplicationCursorKeysModeEnabled() bool {
|
func (terminal *Terminal) IsApplicationCursorKeysModeEnabled() bool {
|
||||||
return terminal.modes.ApplicationCursorKeys
|
return terminal.modes.ApplicationCursorKeys
|
||||||
}
|
}
|
||||||
|
|
|
@ -229,7 +229,7 @@ func (f *Font) Height(scale float32, text string) float32 {
|
||||||
ch := f.fontChar[runeIndex-lowChar]
|
ch := f.fontChar[runeIndex-lowChar]
|
||||||
|
|
||||||
// Now advance cursors for next glyph (note that advance is number of 1/64 pixels)
|
// Now advance cursors for next glyph (note that advance is number of 1/64 pixels)
|
||||||
if float32(ch.height) * scale > height {
|
if float32(ch.height)*scale > height {
|
||||||
height = float32(ch.height) * scale
|
height = float32(ch.height) * scale
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue