Make a start on font rendering
I was hopeful I could use ebiten/text, but font.Face doesn't seem set up for fixed-colour fonts.
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -2,6 +2,7 @@
|
||||
/loader
|
||||
/orig
|
||||
/palette-idx
|
||||
/view-font
|
||||
/view-obj
|
||||
/view-map
|
||||
/view-minimap
|
||||
|
7
Makefile
7
Makefile
@@ -2,7 +2,7 @@ srcfiles = Makefile go.mod $(shell find . -iname *.go)
|
||||
|
||||
GOBUILD ?= go build -tags ebitengl
|
||||
|
||||
all: loader ordoor palette-idx view-obj view-map view-menu view-minimap view-set
|
||||
all: loader ordoor palette-idx view-font view-obj view-map view-menu view-minimap view-set
|
||||
|
||||
loader: $(srcfiles)
|
||||
$(GOBUILD) -o loader ./cmd/loader
|
||||
@@ -10,6 +10,9 @@ loader: $(srcfiles)
|
||||
palette-idx: $(srcfiles)
|
||||
$(GOBUILD) -o palette-idx ./cmd/palette-idx
|
||||
|
||||
view-font: $(srcfiles)
|
||||
$(GOBUILD) -o view-font ./cmd/view-font
|
||||
|
||||
view-obj: $(srcfiles)
|
||||
$(GOBUILD) -o view-obj ./cmd/view-obj
|
||||
|
||||
@@ -29,6 +32,6 @@ ordoor: $(srcfiles)
|
||||
$(GOBUILD) -o ordoor ./cmd/ordoor
|
||||
|
||||
clean:
|
||||
rm -f loader ordoor view-obj view-map view-minimap view-set palette-idx
|
||||
rm -f loader ordoor view-obj view-map view-minimap view-set palette-idx view-font
|
||||
|
||||
.PHONY: all clean
|
||||
|
101
cmd/view-font/main.go
Normal file
101
cmd/view-font/main.go
Normal file
@@ -0,0 +1,101 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"image"
|
||||
"log"
|
||||
"math"
|
||||
"os"
|
||||
|
||||
"github.com/hajimehoshi/ebiten"
|
||||
|
||||
"code.ur.gs/lupine/ordoor/internal/assetstore"
|
||||
"code.ur.gs/lupine/ordoor/internal/ui"
|
||||
)
|
||||
|
||||
var (
|
||||
gamePath = flag.String("game-path", "./orig", "Path to a WH40K: Chaos Gate installation")
|
||||
fontName = flag.String("font", "", "Name of a font, e.g., basfont12")
|
||||
txt = flag.String("text", "Test string", "Text to render")
|
||||
|
||||
winX = flag.Int("win-x", 1280, "Pre-scaled window X dimension")
|
||||
winY = flag.Int("win-y", 1024, "Pre-scaled window Y dimension")
|
||||
)
|
||||
|
||||
type env struct {
|
||||
font *assetstore.Font
|
||||
step int
|
||||
state state
|
||||
lastState state
|
||||
}
|
||||
|
||||
type state struct {
|
||||
zoom float64
|
||||
origin image.Point
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
if *gamePath == "" || *fontName == "" {
|
||||
flag.Usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
assets, err := assetstore.New(*gamePath)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
font, err := assets.Font(*fontName)
|
||||
if err != nil {
|
||||
log.Fatalf("Couldn't load font %s: %v", *fontName, err)
|
||||
}
|
||||
|
||||
state := state{zoom: 8.0}
|
||||
env := &env{
|
||||
font: font,
|
||||
state: state,
|
||||
lastState: state,
|
||||
}
|
||||
|
||||
win, err := ui.NewWindow(env, "View Font: "+*fontName, *winX, *winY)
|
||||
if err != nil {
|
||||
log.Fatal("Couldn't create window: %v", err)
|
||||
}
|
||||
|
||||
win.OnMouseWheel(env.changeZoom)
|
||||
|
||||
// Main thread now belongs to ebiten
|
||||
if err := win.Run(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *env) Update(screenX, screenY int) error {
|
||||
if e.step == 0 || e.lastState != e.state {
|
||||
log.Printf("new state: zoom=%.2f", e.state.zoom)
|
||||
}
|
||||
|
||||
e.step += 1
|
||||
e.lastState = e.state
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *env) Draw(screen *ebiten.Image) error {
|
||||
op := &ebiten.DrawImageOptions{}
|
||||
op.GeoM.Scale(e.state.zoom, e.state.zoom) // apply current zoom factor
|
||||
|
||||
img, err := e.font.DrawLine(*txt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return screen.DrawImage(img, op)
|
||||
}
|
||||
|
||||
func (e *env) changeZoom(_, y float64) {
|
||||
// Zoom in and out with the mouse wheel
|
||||
e.state.zoom *= math.Pow(1.2, y)
|
||||
}
|
11
go.sum
11
go.sum
@@ -2,8 +2,6 @@ dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1 h1:QbL/5oDUmRBzO9/Z7Seo6zf912W/a6Sr4Eu0G/3Jho0=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72 h1:b+9H1GAsx5RsjvDFLoS5zkNBzIQMuVKUYQDmxU3N5XE=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4 h1:WtGNWLvXpe6ZudgnXrq0barxBImvnnJoMEhXAzcbM0I=
|
||||
@@ -12,9 +10,6 @@ github.com/gofrs/flock v0.7.1 h1:DP+LD/t0njgoPBvT5MJLeliUIVQR03hiKR6vezdwHlc=
|
||||
github.com/gofrs/flock v0.7.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
||||
github.com/hajimehoshi/bitmapfont v1.2.0/go.mod h1:h9QrPk6Ktb2neObTlAbma6Ini1xgMjbJ3w7ysmD7IOU=
|
||||
github.com/hajimehoshi/ebiten v1.10.2 h1:PiJBY4Q4udip675T+Zqvb3NKMp1eyLWBelp660ZMrkQ=
|
||||
github.com/hajimehoshi/ebiten v1.10.2/go.mod h1:i9dIEUf5/MuPtbK1/wHR0PB7ZtqhjOxxg+U1xfxapcY=
|
||||
github.com/hajimehoshi/ebiten v1.10.5 h1:hVb3GJP4IDqOETifRmPg4xmURRgbIJoB9gQk+Jqe8Uk=
|
||||
github.com/hajimehoshi/ebiten v1.11.0-alpha.2.0.20200101150127-38815ba801a5 h1:hke9UdXY1YPfqjXG1bCSZnoVnfVBw9SzvmlrRn3dL3w=
|
||||
github.com/hajimehoshi/ebiten v1.11.0-alpha.2.0.20200101150127-38815ba801a5/go.mod h1:0SLvfr8iI2NxzpNB/olBM+dLN9Ur5a9szG13wOgQ0nQ=
|
||||
github.com/hajimehoshi/go-mp3 v0.2.1 h1:DH4ns3cPv39n3cs8MPcAlWqPeAwLCK8iNgqvg0QBWI8=
|
||||
@@ -39,10 +34,6 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56 h1:estk1glOnSVeJ9tdEZZc5mAMDZk5lNJNyJ6DvrBkTEU=
|
||||
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4=
|
||||
golang.org/x/exp v0.0.0-20200228211341-fcea875c7e85 h1:jqhIzSw5SQNkbu5hOGpgMHhkfXxrbsLJdkIRcX19gCY=
|
||||
golang.org/x/exp v0.0.0-20200228211341-fcea875c7e85/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw=
|
||||
golang.org/x/exp v0.0.0-20200319221330-857350248e3d h1:1kJNg12kVM6Xid7xoFkhq/YJVU4NMTv5b3hJCfQnwjc=
|
||||
golang.org/x/exp v0.0.0-20200319221330-857350248e3d/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw=
|
||||
golang.org/x/exp v0.0.0-20200320212757-167ffe94c325 h1:iPGJw87eUJvke9YLYKX0jIwLHiIrY/kXcFSgOpjav28=
|
||||
golang.org/x/exp v0.0.0-20200320212757-167ffe94c325/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
@@ -74,8 +65,6 @@ golang.org/x/sys v0.0.0-20190429190828-d89cdac9e872/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200317113312-5766fd39f98d h1:62ap6LNOjDU6uGmKXHJbSfciMoV+FeI1sRXx/pLDL44=
|
||||
golang.org/x/sys v0.0.0-20200317113312-5766fd39f98d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200321134203-328b4cd54aae h1:3tcmuaB7wwSZtelmiv479UjUB+vviwABz7a133ZwOKQ=
|
||||
golang.org/x/sys v0.0.0-20200321134203-328b4cd54aae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
|
@@ -34,6 +34,7 @@ type AssetStore struct {
|
||||
entries entryMap
|
||||
|
||||
// These members are used to store things we've already loaded
|
||||
fonts map[string]*Font
|
||||
maps map[string]*Map
|
||||
menus map[string]*Menu
|
||||
objs map[string]*Object
|
||||
@@ -84,6 +85,7 @@ func (a *AssetStore) Refresh() error {
|
||||
|
||||
// Refresh
|
||||
a.entries = newEntryMap
|
||||
a.fonts = make(map[string]*Font)
|
||||
a.maps = make(map[string]*Map)
|
||||
a.menus = make(map[string]*Menu)
|
||||
a.objs = make(map[string]*Object)
|
||||
|
116
internal/assetstore/font.go
Normal file
116
internal/assetstore/font.go
Normal file
@@ -0,0 +1,116 @@
|
||||
package assetstore
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/hajimehoshi/ebiten"
|
||||
|
||||
"code.ur.gs/lupine/ordoor/internal/fonts"
|
||||
)
|
||||
|
||||
type Font struct {
|
||||
Name string
|
||||
mapping map[rune]*Sprite
|
||||
}
|
||||
|
||||
func (a *AssetStore) Font(name string) (*Font, error) {
|
||||
name = canonical(name)
|
||||
|
||||
// FIXME: these fonts don't exist. For now, point at one that does.
|
||||
switch name {
|
||||
case "imfnt13", "imfnt14":
|
||||
name = "wh40k_12"
|
||||
}
|
||||
|
||||
if font, ok := a.fonts[name]; ok {
|
||||
return font, nil
|
||||
}
|
||||
log.Printf("Loading font %v", name)
|
||||
|
||||
filename, err := a.lookup(name, "fnt", "Fonts")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
raw, err := fonts.LoadFont(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
objFile, err := a.lookup(raw.ObjectFile, "", "Fonts")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
obj, err := a.ObjectByPath(objFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
out := &Font{
|
||||
Name: name,
|
||||
mapping: make(map[rune]*Sprite, len(raw.Mapping)),
|
||||
}
|
||||
|
||||
for r, offset := range raw.Mapping {
|
||||
spr, err := obj.Sprite(offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
out.mapping[r] = spr
|
||||
}
|
||||
|
||||
a.fonts[name] = out
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// FIXME: this violates the ebiten rules for fast drawing. We may need to do the
|
||||
// draw ourselves, with image.Paletted for each glyph to a single ebiten.Image
|
||||
//
|
||||
// FIXME: it'd be great if we didn't have to implement this all by ourselves;
|
||||
// golang.org/x/image/font and github.com/hajimehoshi/ebiten/text are *almost*
|
||||
// sufficient, but don't seem to like it when the glyphs are literal colours
|
||||
// instead of a mask.
|
||||
//
|
||||
// TODO: draw text in a bounding box, multiple lines, etc
|
||||
func (f *Font) DrawLine(text string) (*ebiten.Image, error) {
|
||||
sprites := make([]*Sprite, 0, len(text))
|
||||
width := 0
|
||||
height := 0
|
||||
|
||||
for _, r := range text {
|
||||
spr, ok := f.mapping[r]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("Font %v does not specify rune %v", f.Name, r)
|
||||
}
|
||||
|
||||
width += spr.Rect.Dx()
|
||||
if y := spr.Rect.Dy(); y > height {
|
||||
height = y
|
||||
}
|
||||
|
||||
sprites = append(sprites, spr)
|
||||
}
|
||||
|
||||
img, err := ebiten.NewImage(width, height, ebiten.FilterDefault)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
xOff := 0
|
||||
for _, spr := range sprites {
|
||||
op := &ebiten.DrawImageOptions{}
|
||||
op.GeoM.Translate(float64(xOff), 0)
|
||||
|
||||
xOff += spr.Rect.Dx()
|
||||
|
||||
if err := img.DrawImage(spr.Image, op); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return img, nil
|
||||
}
|
@@ -8,8 +8,9 @@ import (
|
||||
|
||||
type Menu struct {
|
||||
assets *AssetStore
|
||||
fonts []*Font // TODO: place the fonts directly into the relevant records
|
||||
obj *Object // TODO: handle multiple objects in the menu
|
||||
raw *menus.Menu
|
||||
raw *menus.Menu // TODO: remove raw
|
||||
|
||||
Name string
|
||||
}
|
||||
@@ -19,6 +20,11 @@ func (m *Menu) Records() []*menus.Record {
|
||||
return m.raw.Records
|
||||
}
|
||||
|
||||
// FIXME: don't expose this
|
||||
func (m *Menu) Font(idx int) *Font {
|
||||
return m.fonts[idx]
|
||||
}
|
||||
|
||||
func (m *Menu) Images(start, count int) ([]*ebiten.Image, error) {
|
||||
out := make([]*ebiten.Image, count)
|
||||
|
||||
@@ -70,6 +76,16 @@ func (a *AssetStore) Menu(name string) (*Menu, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var fonts []*Font
|
||||
for _, fontName := range raw.FontNames {
|
||||
fnt, err := a.Font(fontName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fonts = append(fonts, fnt)
|
||||
}
|
||||
|
||||
i18n, err := a.i18n()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -84,6 +100,7 @@ func (a *AssetStore) Menu(name string) (*Menu, error) {
|
||||
|
||||
menu := &Menu{
|
||||
assets: a,
|
||||
fonts: fonts,
|
||||
obj: obj,
|
||||
raw: raw,
|
||||
Name: name,
|
||||
|
@@ -10,17 +10,28 @@ import (
|
||||
"code.ur.gs/lupine/ordoor/internal/util/asciiscan"
|
||||
)
|
||||
|
||||
type Range struct {
|
||||
Min Point
|
||||
Max Point
|
||||
}
|
||||
|
||||
type Point struct {
|
||||
Rune rune
|
||||
Sprite int
|
||||
}
|
||||
|
||||
type Font struct {
|
||||
Name string
|
||||
// Contains the sprite data for the font. FIXME: load this?
|
||||
// Contains the sprite data for the font
|
||||
ObjectFile string
|
||||
|
||||
// Maps ASCII bytes to a sprite offset in the ObjectFile
|
||||
mapping map[int]int
|
||||
Ranges []Range
|
||||
Mapping map[rune]int
|
||||
}
|
||||
|
||||
func (f *Font) Entries() int {
|
||||
return len(f.mapping)
|
||||
return len(f.Mapping)
|
||||
}
|
||||
|
||||
// Returns the offsets required to display a given string, returning an error if
|
||||
@@ -30,7 +41,7 @@ func (f *Font) Indices(s string) ([]int, error) {
|
||||
|
||||
for i, b := range []byte(s) {
|
||||
|
||||
offset, ok := f.mapping[int(b)]
|
||||
offset, ok := f.Mapping[rune(b)]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("Unknown codepoint %v at offset %v in string %s", b, i, s)
|
||||
}
|
||||
@@ -58,7 +69,7 @@ func LoadFont(filename string) (*Font, error) {
|
||||
out := &Font{
|
||||
Name: filepath.Base(filename),
|
||||
ObjectFile: objFile,
|
||||
mapping: make(map[int]int),
|
||||
Mapping: make(map[rune]int),
|
||||
}
|
||||
|
||||
for {
|
||||
@@ -82,15 +93,32 @@ func LoadFont(filename string) (*Font, error) {
|
||||
cpEnd, _ := strconv.Atoi(fields[2])
|
||||
idxStart, _ := strconv.Atoi(fields[3])
|
||||
idxEnd, _ := strconv.Atoi(fields[4])
|
||||
size := idxEnd - idxStart
|
||||
cpSize := cpEnd - cpStart
|
||||
idxSize := idxEnd - idxStart
|
||||
|
||||
// FIXME: I'd love this to be an error but several .fnt files do it
|
||||
if cpEnd-cpStart != size {
|
||||
fmt.Printf("WARNING: %v has mismatched codepoints and indices: %q\n", filename, str)
|
||||
// Take the smallest range
|
||||
if cpSize != idxSize {
|
||||
fmt.Printf("WARNING: %v has mismatched codepoints (sz=%v) and indices (sz=%v): %q\n", filename, cpSize, idxSize, str)
|
||||
if cpSize < idxSize {
|
||||
idxEnd = idxStart + cpSize
|
||||
idxSize = cpSize
|
||||
} else {
|
||||
cpEnd = cpStart + idxSize
|
||||
cpSize = idxSize
|
||||
}
|
||||
|
||||
for offset := 0; offset < size; offset++ {
|
||||
out.mapping[cpStart+offset] = idxStart + offset
|
||||
}
|
||||
|
||||
r := Range{
|
||||
Min: Point{Rune: rune(cpStart), Sprite: idxStart},
|
||||
Max: Point{Rune: rune(cpEnd), Sprite: idxEnd},
|
||||
}
|
||||
|
||||
out.Ranges = append(out.Ranges, r)
|
||||
|
||||
for offset := 0; offset <= cpSize; offset++ {
|
||||
out.Mapping[rune(cpStart+offset)] = idxStart + offset
|
||||
}
|
||||
case "v": // A single codepoint, 4 fields
|
||||
if len(fields) < 3 {
|
||||
@@ -99,8 +127,11 @@ func LoadFont(filename string) (*Font, error) {
|
||||
|
||||
cp, _ := strconv.Atoi(fields[1])
|
||||
idx, _ := strconv.Atoi(fields[2])
|
||||
pt := Point{Rune: rune(cp), Sprite: idx}
|
||||
|
||||
out.mapping[cp] = idx
|
||||
out.Ranges = append(out.Ranges, Range{Min: pt, Max: pt})
|
||||
|
||||
out.Mapping[rune(cp)] = idx
|
||||
default:
|
||||
return nil, parseErr
|
||||
}
|
||||
|
@@ -2,11 +2,14 @@ package menus
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image/color"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"code.ur.gs/lupine/ordoor/internal/data"
|
||||
"code.ur.gs/lupine/ordoor/internal/util/asciiscan"
|
||||
)
|
||||
|
||||
@@ -93,6 +96,9 @@ type Menu struct {
|
||||
ObjectFiles []string
|
||||
FontNames []string
|
||||
|
||||
BackgroundColor color.Color
|
||||
HypertextColor color.Color
|
||||
|
||||
// FIXME: turn these into first-class data
|
||||
Properties map[string]string
|
||||
|
||||
@@ -146,7 +152,23 @@ func LoadMenu(filename string) (*Menu, error) {
|
||||
out.ObjectFiles = append(out.ObjectFiles, str)
|
||||
case 1: // List of properties
|
||||
k, v := asciiscan.ConsumeProperty(str)
|
||||
vInt, err := strconv.Atoi(v) // FIXME:
|
||||
switch k {
|
||||
case "BACKGROUND COLOR 0..255..-1 trans":
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
out.BackgroundColor = data.ColorPalette[vInt]
|
||||
case "HYPERTEXT COLOR 0..255":
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
out.HypertextColor = data.ColorPalette[vInt]
|
||||
default:
|
||||
out.Properties[k] = v
|
||||
}
|
||||
case 2: // list of fonts
|
||||
// FIXME: do we need to do something cleverer here?
|
||||
if str == "NULL" {
|
||||
@@ -170,6 +192,8 @@ func LoadMenu(filename string) (*Menu, error) {
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("Menu properties: %#+v", out.Properties)
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
|
@@ -5,7 +5,6 @@ import (
|
||||
"log"
|
||||
|
||||
"github.com/hajimehoshi/ebiten"
|
||||
"github.com/hajimehoshi/ebiten/ebitenutil"
|
||||
|
||||
"code.ur.gs/lupine/ordoor/internal/menus"
|
||||
)
|
||||
@@ -29,6 +28,7 @@ type noninteractive struct {
|
||||
|
||||
// Some non-interactives, e.g., overlays, are an image + text to be shown
|
||||
textImg *ebiten.Image
|
||||
textOffset image.Point
|
||||
|
||||
clickImpl // Alright, alright, it turns out the bridge mission briefing is clickable
|
||||
hoverImpl
|
||||
@@ -102,14 +102,27 @@ func registerOverlay(d *Driver, r *menus.Record) error {
|
||||
}
|
||||
|
||||
if r.Text != "" {
|
||||
// FIXME: we should be rendering the text much more nicely than this
|
||||
textImg, err := ebiten.NewImage(sprite.Rect.Dx(), sprite.Rect.Dy(), ebiten.FilterDefault)
|
||||
// FIXME: is this always right? Seems to make sense for Main.mnu
|
||||
fnt := d.menu.Font(r.FontType/10 - 1)
|
||||
|
||||
textImg, err := fnt.DrawLine(r.Text)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ebitenutil.DebugPrint(textImg, r.Text)
|
||||
ni.textImg = textImg
|
||||
|
||||
// Centre the image
|
||||
xSlack := ni.rect.Dx() - textImg.Bounds().Dx()
|
||||
if xSlack > 0 {
|
||||
ni.textOffset.X = xSlack / 2
|
||||
}
|
||||
|
||||
ySlack := ni.rect.Dy() - textImg.Bounds().Dy()
|
||||
if ySlack > 0 {
|
||||
ni.textOffset.Y = ySlack / 2
|
||||
}
|
||||
|
||||
} else {
|
||||
log.Printf("Overlay without text detected: %#+v", r)
|
||||
}
|
||||
@@ -189,12 +202,16 @@ func (n *noninteractive) regions(tick int) []region {
|
||||
out := oneRegion(n.bounds().Min, n.frames.image(tick))
|
||||
|
||||
if n.textImg != nil {
|
||||
out = append(out, oneRegion(n.bounds().Min, n.textImg)...)
|
||||
out = append(out, oneRegion(n.textPos(), n.textImg)...)
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func (n *noninteractive) textPos() image.Point {
|
||||
return image.Pt(n.rect.Min.X+n.textOffset.X, n.rect.Min.Y+n.textOffset.Y)
|
||||
}
|
||||
|
||||
func (a *animationHover) regions(tick int) []region {
|
||||
if a.opening || a.closing {
|
||||
var anim animation
|
||||
|
Reference in New Issue
Block a user