323 lines
10 KiB
Go
323 lines
10 KiB
Go
package gui
|
|
|
|
import (
|
|
"image/color"
|
|
"strings"
|
|
|
|
"github.com/hajimehoshi/ebiten/v2"
|
|
"github.com/hajimehoshi/ebiten/v2/ebitenutil"
|
|
"github.com/hajimehoshi/ebiten/v2/text"
|
|
"github.com/liamg/darktile/internal/app/darktile/termutil"
|
|
imagefont "golang.org/x/image/font"
|
|
)
|
|
|
|
// Draw renders the terminal GUI to the ebtien window. Required to implement the ebiten interface.
|
|
func (g *GUI) Draw(screen *ebiten.Image) {
|
|
|
|
tmp := ebiten.NewImage(g.size.X, g.size.Y)
|
|
|
|
cellSize := g.fontManager.CharSize()
|
|
dotDepth := g.fontManager.DotDepth()
|
|
|
|
buffer := g.terminal.GetActiveBuffer()
|
|
|
|
regularFace := g.fontManager.RegularFontFace()
|
|
boldFace := g.fontManager.BoldFontFace()
|
|
italicFace := g.fontManager.ItalicFontFace()
|
|
boldItalicFace := g.fontManager.BoldItalicFontFace()
|
|
|
|
var useFace imagefont.Face
|
|
|
|
defBg := g.terminal.Theme().DefaultBackground()
|
|
defFg := g.terminal.Theme().DefaultForeground()
|
|
|
|
var colour color.Color
|
|
|
|
endX := float64(cellSize.X * int(buffer.ViewWidth()))
|
|
endY := float64(cellSize.Y * int(buffer.ViewHeight()))
|
|
extraW := float64(g.size.X) - endX
|
|
extraH := float64(g.size.Y) - endY
|
|
if extraW > 0 {
|
|
ebitenutil.DrawRect(tmp, endX, 0, extraW, endY, defBg)
|
|
}
|
|
if extraH > 0 {
|
|
ebitenutil.DrawRect(tmp, 0, endY, float64(g.size.X), extraH, defBg)
|
|
}
|
|
|
|
var inHighlight bool
|
|
var highlightRendered bool
|
|
var highlightMin termutil.Position
|
|
highlightMin.Col = uint16(g.size.X)
|
|
highlightMin.Line = uint64(g.size.Y)
|
|
var highlightMax termutil.Position
|
|
|
|
for y := int(buffer.ViewHeight() - 1); y >= 0; y-- {
|
|
py := cellSize.Y * y
|
|
|
|
ebitenutil.DrawRect(tmp, 0, float64(py), float64(g.size.X), float64(cellSize.Y), defBg)
|
|
inHighlight = false
|
|
for x := uint16(0); x < buffer.ViewWidth(); x++ {
|
|
cell := buffer.GetCell(x, uint16(y))
|
|
px := cellSize.X * int(x)
|
|
if cell != nil {
|
|
colour = cell.Bg()
|
|
} else {
|
|
colour = defBg
|
|
}
|
|
isCursor := g.terminal.GetActiveBuffer().IsCursorVisible() && int(buffer.CursorLine()) == y && buffer.CursorColumn() == x
|
|
if isCursor {
|
|
colour = g.terminal.Theme().CursorBackground()
|
|
} else if buffer.InSelection(termutil.Position{
|
|
Line: uint64(y),
|
|
Col: x,
|
|
}) {
|
|
colour = g.terminal.Theme().SelectionBackground()
|
|
} else if colour == nil {
|
|
colour = defBg
|
|
}
|
|
|
|
ebitenutil.DrawRect(tmp, float64(px), float64(py), float64(cellSize.X), float64(cellSize.Y), colour)
|
|
|
|
if buffer.IsHighlighted(termutil.Position{
|
|
Line: uint64(y),
|
|
Col: x,
|
|
}) {
|
|
|
|
if !inHighlight {
|
|
highlightRendered = true
|
|
}
|
|
|
|
if uint64(y) < highlightMin.Line {
|
|
highlightMin.Col = uint16(g.size.X)
|
|
highlightMin.Line = uint64(y)
|
|
}
|
|
if uint64(y) > highlightMax.Line {
|
|
highlightMax.Line = uint64(y)
|
|
}
|
|
if uint64(y) == highlightMax.Line && x > highlightMax.Col {
|
|
highlightMax.Col = x
|
|
}
|
|
if uint64(y) == highlightMin.Line && x < highlightMin.Col {
|
|
highlightMin.Col = x
|
|
}
|
|
|
|
inHighlight = true
|
|
|
|
} else if inHighlight {
|
|
inHighlight = false
|
|
}
|
|
|
|
if isCursor && !ebiten.IsFocused() {
|
|
ebitenutil.DrawRect(tmp, float64(px)+1, float64(py)+1, float64(cellSize.X)-2, float64(cellSize.Y)-2, g.terminal.Theme().DefaultBackground())
|
|
}
|
|
}
|
|
for x := uint16(0); x < buffer.ViewWidth(); x++ {
|
|
cell := buffer.GetCell(x, uint16(y))
|
|
if cell == nil || cell.Rune().Rune == 0 {
|
|
continue
|
|
}
|
|
|
|
px := cellSize.X * int(x)
|
|
colour = cell.Fg()
|
|
if g.terminal.GetActiveBuffer().IsCursorVisible() && int(buffer.CursorLine()) == y && buffer.CursorColumn() == x {
|
|
colour = g.terminal.Theme().CursorForeground()
|
|
} else if buffer.InSelection(termutil.Position{
|
|
Line: uint64(y),
|
|
Col: x,
|
|
}) {
|
|
colour = g.terminal.Theme().SelectionForeground()
|
|
} else if colour == nil {
|
|
colour = defFg
|
|
}
|
|
|
|
useFace = regularFace
|
|
if cell.Bold() && cell.Italic() {
|
|
useFace = boldItalicFace
|
|
} else if cell.Bold() {
|
|
useFace = boldFace
|
|
} else if cell.Italic() {
|
|
useFace = italicFace
|
|
}
|
|
|
|
if cell.Underline() {
|
|
uly := float64(py + (dotDepth+cellSize.Y)/2)
|
|
ebitenutil.DrawLine(tmp, float64(px), uly, float64(px+cellSize.X), uly, colour)
|
|
}
|
|
|
|
text.Draw(tmp, string(cell.Rune().Rune), useFace, px, py+dotDepth, colour)
|
|
|
|
if cell.Strikethrough() {
|
|
ebitenutil.DrawLine(tmp, float64(px), float64(py+(cellSize.Y/2)), float64(px+cellSize.X), float64(py+(cellSize.Y/2)), colour)
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
for _, sixel := range buffer.GetVisibleSixels() {
|
|
sx := float64(int(sixel.Sixel.X) * cellSize.X)
|
|
sy := float64(sixel.ViewLineOffset * cellSize.Y)
|
|
|
|
op := &ebiten.DrawImageOptions{}
|
|
op.GeoM.Translate(sx, sy)
|
|
tmp.DrawImage(
|
|
ebiten.NewImageFromImage(sixel.Sixel.Image),
|
|
op,
|
|
)
|
|
}
|
|
|
|
// draw annotations and overlays
|
|
if highlightRendered {
|
|
if annotation := buffer.GetHighlightAnnotation(); annotation != nil {
|
|
|
|
if highlightMin.Col == uint16(g.size.X) {
|
|
highlightMin.Col = 0
|
|
}
|
|
if highlightMin.Line == uint64(g.size.Y) {
|
|
highlightMin.Line = 0
|
|
}
|
|
|
|
mx, _ := ebiten.CursorPosition()
|
|
padding := float64(cellSize.X) / 2
|
|
lineX := float64(mx)
|
|
var lineY float64
|
|
var lineHeight float64
|
|
annotationX := mx - cellSize.X*2
|
|
var annotationY float64
|
|
annotationWidth := float64(cellSize.X) * annotation.Width
|
|
var annotationHeight float64
|
|
|
|
if annotationX+int(annotationWidth)+int(padding*2) > g.size.X {
|
|
annotationX = g.size.X - (int(annotationWidth) + int(padding*2))
|
|
}
|
|
if annotationX < int(padding) {
|
|
annotationX = int(padding)
|
|
}
|
|
|
|
if (highlightMin.Line + (highlightMax.Line-highlightMin.Line)/2) < uint64(buffer.ViewHeight()/2) {
|
|
// annotate underneath max
|
|
|
|
pixelsUnderHighlight := float64(g.size.Y) - float64((highlightMax.Line+1)*uint64(cellSize.Y))
|
|
// we need to reserve at least one cell height for the label line
|
|
pixelsAvailableY := pixelsUnderHighlight - float64(cellSize.Y)
|
|
annotationHeight = annotation.Height * float64(cellSize.Y)
|
|
if annotationHeight > pixelsAvailableY {
|
|
annotationHeight = pixelsAvailableY
|
|
}
|
|
|
|
lineHeight = pixelsUnderHighlight - padding - annotationHeight
|
|
if lineHeight > annotationHeight {
|
|
if annotationHeight > float64(cellSize.Y)*3 {
|
|
lineHeight = annotationHeight
|
|
} else {
|
|
lineHeight = float64(cellSize.Y) * 3
|
|
}
|
|
}
|
|
annotationY = float64((highlightMax.Line+1)*uint64(cellSize.Y)) + lineHeight + float64(padding)
|
|
lineY = float64((highlightMax.Line + 1) * uint64(cellSize.Y))
|
|
|
|
} else {
|
|
//annotate above min
|
|
|
|
pixelsAboveHighlight := float64((highlightMin.Line) * uint64(cellSize.Y))
|
|
// we need to reserve at least one cell height for the label line
|
|
pixelsAvailableY := pixelsAboveHighlight - float64(cellSize.Y)
|
|
annotationHeight = annotation.Height * float64(cellSize.Y)
|
|
if annotationHeight > pixelsAvailableY {
|
|
annotationHeight = pixelsAvailableY
|
|
}
|
|
|
|
lineHeight = pixelsAboveHighlight - annotationHeight
|
|
if lineHeight > annotationHeight {
|
|
if annotationHeight > float64(cellSize.Y)*3 {
|
|
lineHeight = annotationHeight
|
|
} else {
|
|
lineHeight = float64(cellSize.Y) * 3
|
|
}
|
|
}
|
|
annotationY = float64((highlightMin.Line)*uint64(cellSize.Y)) - lineHeight - float64(padding*2) - annotationHeight
|
|
lineY = annotationY + annotationHeight + +padding
|
|
}
|
|
|
|
// draw opaque box below and above highlighted line(s)
|
|
ebitenutil.DrawRect(tmp, 0, float64(highlightMin.Line*uint64(cellSize.Y)), float64(cellSize.X*int(highlightMin.Col)), float64(cellSize.Y), color.RGBA{A: 0x80})
|
|
ebitenutil.DrawRect(tmp, float64((cellSize.X)*int(highlightMax.Col+1)), float64(highlightMax.Line*uint64(cellSize.Y)), float64(g.size.X), float64(cellSize.Y), color.RGBA{A: 0x80})
|
|
ebitenutil.DrawRect(tmp, 0, 0, float64(g.size.X), float64(highlightMin.Line*uint64(cellSize.Y)), color.RGBA{A: 0x80})
|
|
afterLineY := float64((1 + highlightMax.Line) * uint64(cellSize.Y))
|
|
ebitenutil.DrawRect(tmp, 0, afterLineY, float64(g.size.X), float64(g.size.Y)-afterLineY, color.RGBA{A: 0x80})
|
|
|
|
// annotation border
|
|
ebitenutil.DrawRect(tmp, float64(annotationX)-padding, annotationY-padding, float64(annotationWidth)+(padding*2), annotationHeight+(padding*2), g.terminal.Theme().SelectionBackground())
|
|
// annotation background
|
|
ebitenutil.DrawRect(tmp, 1+float64(annotationX)-padding, 1+annotationY-padding, float64(annotationWidth)+(padding*2)-2, annotationHeight+(padding*2)-2, g.terminal.Theme().DefaultBackground())
|
|
|
|
// vertical line
|
|
ebitenutil.DrawLine(tmp, lineX, float64(lineY), lineX, lineY+lineHeight, g.terminal.Theme().SelectionBackground())
|
|
|
|
var tY int
|
|
var tX int
|
|
|
|
if annotation.Image != nil {
|
|
tY += annotation.Image.Bounds().Dy() + cellSize.Y/2
|
|
|
|
op := &ebiten.DrawImageOptions{}
|
|
op.GeoM.Translate(float64(annotationX), annotationY)
|
|
tmp.DrawImage(
|
|
ebiten.NewImageFromImage(annotation.Image),
|
|
op,
|
|
)
|
|
}
|
|
|
|
for _, r := range annotation.Text {
|
|
if r == '\n' {
|
|
tY += cellSize.Y
|
|
tX = 0
|
|
continue
|
|
}
|
|
text.Draw(tmp, string(r), regularFace, annotationX+tX, int(annotationY)+dotDepth+tY, g.terminal.Theme().DefaultForeground())
|
|
tX += cellSize.X
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
if len(g.popupMessages) > 0 {
|
|
pad := cellSize.Y / 2 // horizontal and vertical padding
|
|
msgEndY := endY
|
|
for _, msg := range g.popupMessages {
|
|
|
|
lines := strings.Split(msg.Text, "\n")
|
|
|
|
msgX := pad
|
|
|
|
msgY := msgEndY - float64(pad*3) - float64(cellSize.Y*len(lines))
|
|
|
|
msgText := msg.Text
|
|
|
|
boxWidth := float64(pad*2) + float64(cellSize.X*len(msgText))
|
|
boxHeight := float64(pad*2) + float64(cellSize.Y*len(lines))
|
|
|
|
if boxWidth < endX/8 {
|
|
boxWidth = endX / 8
|
|
}
|
|
|
|
ebitenutil.DrawRect(tmp, float64(msgX-1), msgY-1, boxWidth+2, boxHeight+2, msg.Foreground)
|
|
ebitenutil.DrawRect(tmp, float64(msgX), msgY, boxWidth, boxHeight, msg.Background)
|
|
for y, line := range lines {
|
|
for x, r := range line {
|
|
text.Draw(tmp, string(r), regularFace, msgX+pad+(x*cellSize.X), pad+(y*cellSize.Y)+int(msgY)+dotDepth, msg.Foreground)
|
|
}
|
|
}
|
|
msgEndY = msgEndY - float64(pad*4) - float64(len(lines)*g.CellSize().Y)
|
|
}
|
|
}
|
|
|
|
if g.screenshotRequested {
|
|
g.takeScreenshot(tmp)
|
|
}
|
|
|
|
opt := &ebiten.DrawImageOptions{}
|
|
opt.ColorM.Scale(1, 1, 1, g.opacity)
|
|
screen.DrawImage(tmp, opt)
|
|
tmp.Dispose()
|
|
}
|