erm/internal/app/darktile/gui/gui.go

161 lines
3.4 KiB
Go

package gui
import (
"fmt"
"image"
"image/color"
"math/rand"
"os"
"strings"
"time"
"github.com/liamg/darktile/internal/app/darktile/font"
"github.com/liamg/darktile/internal/app/darktile/hinters"
"github.com/liamg/darktile/internal/app/darktile/termutil"
"github.com/hajimehoshi/ebiten/v2"
)
func init() {
rand.Seed(time.Now().UnixNano())
}
type GUI struct {
mouseStateLeft MouseState
mouseStateRight MouseState
mouseStateMiddle MouseState
mouseDrag bool
size image.Point // pixels
terminal *termutil.Terminal
updateChan chan struct{}
lastClick time.Time
clickCount int
fontManager *font.Manager
mousePos termutil.Position
hinters []hinters.Hinter
activeHinter int
popupMessages []PopupMessage
screenshotRequested bool
screenshotFilename string
startupFuncs []func(g *GUI)
keyState *keyState
opacity float64
}
type PopupMessage struct {
Text string
Expiry time.Time
Foreground color.Color
Background color.Color
}
type MouseState uint8
const (
MouseStateNone MouseState = iota
MouseStatePressed
)
func New(terminal *termutil.Terminal, options ...Option) (*GUI, error) {
g := &GUI{
terminal: terminal,
size: image.Point{80, 30},
updateChan: make(chan struct{}),
fontManager: font.NewManager(),
activeHinter: -1,
keyState: newKeyState(),
}
for _, option := range options {
if err := option(g); err != nil {
return nil, err
}
}
terminal.SetWindowManipulator(NewManipulator(g))
return g, nil
}
func (g *GUI) Run() error {
go func() {
if err := g.terminal.Run(g.updateChan, uint16(g.size.X), uint16(g.size.Y)); err != nil {
fmt.Fprintf(os.Stderr, "Fatal error: %s", err)
os.Exit(1)
}
os.Exit(0)
}()
ebiten.SetScreenTransparent(true)
ebiten.SetScreenClearedEveryFrame(true)
ebiten.SetWindowResizable(true)
ebiten.SetRunnableOnUnfocused(true)
ebiten.SetFPSMode(ebiten.FPSModeVsyncOffMinimum)
for _, f := range g.startupFuncs {
go f(g)
}
go g.watchForUpdate()
return ebiten.RunGame(g)
}
func (g *GUI) watchForUpdate() {
for range g.updateChan {
ebiten.ScheduleFrame()
go func() {
for g.keyState.AnythingPressed() {
time.Sleep(time.Millisecond * 10)
ebiten.ScheduleFrame()
}
}()
}
}
func (g *GUI) CellSize() image.Point {
return g.fontManager.CharSize()
}
func (g *GUI) Highlight(start termutil.Position, end termutil.Position, label string, img image.Image) {
if label == "" && img == nil {
g.terminal.GetActiveBuffer().Highlight(start, end, nil)
return
}
annotation := &termutil.Annotation{
Text: label,
Image: img,
}
if label != "" {
lines := strings.Split(label, "\n")
annotation.Height = float64(len(lines))
for _, line := range lines {
if float64(len(line)) > annotation.Width {
annotation.Width = float64(len(line))
}
}
}
if img != nil {
annotation.Height += float64(img.Bounds().Dy() / g.fontManager.CharSize().Y)
if label != "" {
annotation.Height += 0.5 // half line spacing between image + text
}
imgCellWidth := img.Bounds().Dx() / g.fontManager.CharSize().X
if float64(imgCellWidth) > annotation.Width {
annotation.Width = float64(imgCellWidth)
}
}
g.terminal.GetActiveBuffer().Highlight(start, end, annotation)
}
func (g *GUI) ClearHighlight() {
g.terminal.GetActiveBuffer().ClearHighlight()
}