175 lines
4.4 KiB
Go
175 lines
4.4 KiB
Go
package gui
|
|
|
|
import (
|
|
"fmt"
|
|
"image"
|
|
"log/slog"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/hajimehoshi/ebiten/v2"
|
|
"go.uber.org/fx"
|
|
"tuxpa.in/t/erm/app/darktile/config"
|
|
"tuxpa.in/t/erm/app/darktile/font"
|
|
"tuxpa.in/t/erm/app/darktile/gui/popup"
|
|
"tuxpa.in/t/erm/app/darktile/hinters"
|
|
"tuxpa.in/t/erm/app/darktile/termutil"
|
|
termutil2 "tuxpa.in/t/erm/app/darktile/termutil"
|
|
)
|
|
|
|
type GUI struct {
|
|
mouseStateLeft MouseState
|
|
mouseStateRight MouseState
|
|
mouseStateMiddle MouseState
|
|
mouseDrag bool
|
|
size image.Point // pixels
|
|
terminal *termutil2.Terminal
|
|
updateChan chan struct{}
|
|
lastClick time.Time
|
|
clickCount int
|
|
fontManager *font.Manager
|
|
mousePos termutil2.Position
|
|
hinters []hinters.Hinter
|
|
activeHinter int
|
|
popupMessages []popup.Message
|
|
screenshotRequested bool
|
|
screenshotFilename string
|
|
startupFuncs []func(g *GUI)
|
|
keyState *keyState
|
|
opacity float64
|
|
enableLigatures bool
|
|
cursorImage *ebiten.Image
|
|
|
|
log *slog.Logger
|
|
theme *termutil.Theme
|
|
c *config.Lark
|
|
}
|
|
|
|
type MouseState uint8
|
|
|
|
const (
|
|
MouseStateNone MouseState = iota
|
|
MouseStatePressed
|
|
)
|
|
|
|
func New(terminal *termutil2.Terminal, c *config.Lark, log *slog.Logger) (*GUI, error) {
|
|
|
|
g := &GUI{
|
|
terminal: terminal,
|
|
updateChan: make(chan struct{}),
|
|
fontManager: font.NewManager(),
|
|
activeHinter: -1,
|
|
keyState: newKeyState(),
|
|
enableLigatures: true,
|
|
c: c,
|
|
log: log,
|
|
theme: termutil.ThemeFromLark(c),
|
|
}
|
|
terminal.SetWindowManipulator(NewManipulator(g))
|
|
font, err := g.c.Font("font")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
g.log.Info("running gui", "font", font)
|
|
g.fontManager.SetFontByFamilyName(font.Family)
|
|
if font.Size > 0 {
|
|
g.fontManager.SetSize(font.Size)
|
|
}
|
|
if len(font.Style) > 0 {
|
|
g.fontManager.SetFontStyle(font.Style)
|
|
}
|
|
g.fontManager.SetDPI(96)
|
|
g.enableLigatures = font.Ligatures
|
|
title := config.Default("erm")(g.c.Str("title"))
|
|
ebiten.SetWindowTitle(title)
|
|
ebiten.SetScreenClearedEveryFrame(true)
|
|
ebiten.SetWindowResizingMode(ebiten.WindowResizingModeEnabled)
|
|
ebiten.SetRunnableOnUnfocused(true)
|
|
ebiten.SetTPS(144)
|
|
ebiten.SetScreenClearedEveryFrame(false)
|
|
if g.c.Truthy("vsync") {
|
|
ebiten.SetVsyncEnabled(true)
|
|
} else {
|
|
ebiten.SetVsyncEnabled(false)
|
|
}
|
|
|
|
rows := config.Default(128)(g.c.Int("initial_rows"))
|
|
cols := config.Default(128)(g.c.Int("initial_cols"))
|
|
|
|
scale := ebiten.Monitor().DeviceScaleFactor()
|
|
w := int(cols * g.fontManager.CharSize().X)
|
|
h := int(rows * g.fontManager.CharSize().Y)
|
|
initialSize := image.Point{
|
|
int(float64(w) * scale), int(float64(h) * scale),
|
|
}
|
|
g.size = initialSize
|
|
ebiten.SetWindowSize(w, h)
|
|
return g, nil
|
|
}
|
|
|
|
func (g *GUI) Run(s fx.Shutdowner) error {
|
|
rows := config.Default(128)(g.c.Int("initial_rows"))
|
|
cols := config.Default(128)(g.c.Int("initial_cols"))
|
|
go func() {
|
|
if err := g.terminal.Run(g.updateChan, uint16(rows), uint16(cols)); err != nil {
|
|
fmt.Fprintf(os.Stderr, "Fatal error: %s", err)
|
|
s.Shutdown(fx.ExitCode(1))
|
|
}
|
|
s.Shutdown(fx.ExitCode(0))
|
|
}()
|
|
|
|
title := config.Default("erm")(g.c.Str("title"))
|
|
for _, f := range g.startupFuncs {
|
|
go f(g)
|
|
}
|
|
g.log.Info("initial size", "size", g.size)
|
|
return ebiten.RunGameWithOptions(g, &ebiten.RunGameOptions{
|
|
X11ClassName: title,
|
|
})
|
|
}
|
|
|
|
func (g *GUI) CellSize() image.Point {
|
|
return g.fontManager.CharSize()
|
|
}
|
|
|
|
func (g *GUI) Highlight(start termutil2.Position, end termutil2.Position, label string, img image.Image) {
|
|
|
|
if label == "" && img == nil {
|
|
g.terminal.GetActiveBuffer().Highlight(start, end, nil)
|
|
return
|
|
}
|
|
|
|
annotation := &termutil2.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()
|
|
}
|