First pass at custom cursor display

This commit is contained in:
2020-04-10 19:55:16 +01:00
parent d99a5b9ec3
commit bb3ddc4896
5 changed files with 146 additions and 0 deletions

View File

@@ -34,6 +34,9 @@ type AssetStore struct {
entries entryMap
// These members are used to store things we've already loaded
cursorObj *Object
cursors map[CursorName]*Cursor
fonts map[string]*Font
generic *data.Generic
maps map[string]*Map
@@ -85,6 +88,8 @@ func (a *AssetStore) Refresh() error {
}
// Refresh
a.cursorObj = nil
a.cursors = make(map[CursorName]*Cursor)
a.entries = newEntryMap
a.fonts = make(map[string]*Font)
a.maps = make(map[string]*Map)

View File

@@ -0,0 +1,100 @@
package assetstore
import (
"image"
"github.com/hajimehoshi/ebiten"
)
// These are just offsets into the Cursors.cur file
type CursorName int
type Cursor struct {
Hotspot image.Point
Image *ebiten.Image
}
const (
UltPointer CursorName = 0
ChaosPointer CursorName = 1
UltWaiter CursorName = 2
ChaosWaiter CursorName = 3
// I think these cursors are used in drag + drop
ChaosMarine1 CursorName = 4
ChaosMarine2 CursorName = 5
ChaosMarine3 CursorName = 6
UltMarine1 CursorName = 7
UltMarine2 CursorName = 8
UltMarine3 CursorName = 9
UltMarine4 CursorName = 10
UltMarine5 CursorName = 11
ChaosHeavy1 CursorName = 12
ChaosHeavy2 CursorName = 13
UltHeavy1 CursorName = 14
UltHeavy2 CursorName = 15
UltHeavy3 CursorName = 16
UltHeavy4 CursorName = 17
UltHeavy5 CursorName = 18
UltHeavy6 CursorName = 19
ChaosTerminator1 CursorName = 20
ChaosTerminator2 CursorName = 21
UltTerminator1 CursorName = 22
UltTerminator2 CursorName = 23
UltTerminator3 CursorName = 24
UltTerminator4 CursorName = 25
UltTerminator5 CursorName = 26
Deny CursorName = 27 // Red X
UltLogo CursorName = 28
UltSquadMarine CursorName = 29
UltSquadHeavy CursorName = 30
UltSquadAssault CursorName = 31
UltCaptain CursorName = 32
UltChaplain CursorName = 33 // (maybe?)
UltApothecary CursorName = 34
UltTechmarine CursorName = 35
UltLibrarian CursorName = 36
DenyAgain CursorName = 37 // Identical to Deny as far as I can see *shrug*
)
func (a *AssetStore) Cursor(name CursorName) (*Cursor, error) {
if cur, ok := a.cursors[name]; ok {
return cur, nil
}
if a.cursorObj == nil {
filename, err := a.lookup("Cursors.cur", "", "Cursor")
if err != nil {
return nil, err
}
obj, err := a.ObjectByPath(filename)
if err != nil {
return nil, err
}
a.cursorObj = obj
}
spr, err := a.cursorObj.Sprite(int(name))
if err != nil {
return nil, err
}
// TODO: hotspot info. We're using Cursor.cur because it's object format,
// but we do also have .ani files that might contain hotspots.
cur := &Cursor{Image: spr.Image}
a.cursors[name] = cur
return cur, nil
}

View File

@@ -93,6 +93,13 @@ func Run(configFile string, overrideX, overrideY int) error {
return fmt.Errorf("Failed to create window: %v", err)
}
// TODO: move this into driver. It will need to be able to change cursor.
cursor, err := assets.Cursor(assetstore.UltPointer)
if err != nil {
log.Fatalf("Couldn't load cursor: %v", err)
}
win.SetCursor(cursor.Image)
ordoor.win = win
if err := ordoor.setupFlow(); err != nil {

View File

@@ -3,6 +3,7 @@ package ui
import (
"flag"
"fmt"
"image"
"log"
"os"
"runtime/debug"
@@ -23,11 +24,16 @@ var (
cpuprofile = flag.String("cpuprofile", "", "write cpu profile to `file`")
)
// TODO: move all scaling into Window, so drivers only need to cope with one
// coordinate space. This will allow us to draw custom mouse cursors in the
// window, rather than in the driver.
type Window struct {
Title string
KeyUpHandlers map[ebiten.Key]func()
MouseWheelHandler func(float64, float64)
cursor *ebiten.Image
// Allow the "game" to be switched out at any time
game Game
@@ -68,6 +74,17 @@ func (w *Window) Layout(_, _ int) (int, int) {
return w.xRes, w.yRes
}
func (w *Window) SetCursor(cursor *ebiten.Image) {
w.cursor = cursor
// Hide the system cursor if we have a custom one
if cursor == nil {
ebiten.SetCursorMode(ebiten.CursorModeVisible)
} else {
ebiten.SetCursorMode(ebiten.CursorModeHidden)
}
}
func (w *Window) Update(screen *ebiten.Image) (outErr error) {
// Ebiten does not like it if we panic inside its main loop
defer func() {
@@ -108,6 +125,16 @@ func (w *Window) Update(screen *ebiten.Image) (outErr error) {
return err
}
if w.cursor != nil {
// TODO: account for scaling, including the hotspot.
pos := image.Pt(ebiten.CursorPosition())
op := ebiten.DrawImageOptions{}
op.GeoM.Translate(float64(pos.X), float64(pos.Y))
if err := screen.DrawImage(w.cursor, &op); err != nil {
return err
}
}
if w.debug {
// Draw FPS, etc, to the screen
msg := fmt.Sprintf("tps=%0.2f fps=%0.2f", ebiten.CurrentTPS(), ebiten.CurrentFPS())