161 lines
3.4 KiB
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()
|
|
}
|