Start work on menu interactivity.
With this commit, we get a ui.Interface and ui.Widget type. The interface monitors hover and mouse click state and tells the widgets about them; the widgets execute code specified by the application when events occur. Next step: have wh40k load the main menu and play sound, etc.
This commit is contained in:
@@ -69,7 +69,7 @@ func main() {
|
||||
lastState: state,
|
||||
}
|
||||
|
||||
win, err := ui.NewWindow("View Map " + *gameMap)
|
||||
win, err := ui.NewWindow(env, "View Map "+*gameMap)
|
||||
if err != nil {
|
||||
log.Fatal("Couldn't create window: %v", err)
|
||||
}
|
||||
@@ -86,12 +86,12 @@ func main() {
|
||||
win.OnKeyUp(ebiten.Key1+ebiten.Key(i), env.setZIdx(i+1))
|
||||
}
|
||||
|
||||
if err := win.Run(env.Update, env.Draw); err != nil {
|
||||
if err := win.Run(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *env) Update() error {
|
||||
func (e *env) Update(screenX, screenY int) error {
|
||||
if e.step == 0 || e.lastState != e.state {
|
||||
log.Printf("zoom=%.2f zIdx=%v camPos=%#v", e.state.zoom, e.state.zIdx, e.state.origin)
|
||||
}
|
||||
|
@@ -2,14 +2,10 @@ package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"image"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"code.ur.gs/lupine/ordoor/internal/assetstore"
|
||||
"code.ur.gs/lupine/ordoor/internal/data"
|
||||
"code.ur.gs/lupine/ordoor/internal/menus"
|
||||
"code.ur.gs/lupine/ordoor/internal/ui"
|
||||
"github.com/hajimehoshi/ebiten"
|
||||
"github.com/hajimehoshi/ebiten/audio"
|
||||
@@ -17,12 +13,11 @@ import (
|
||||
|
||||
var (
|
||||
gamePath = flag.String("game-path", "./orig", "Path to a WH40K: Chaos Gate installation")
|
||||
menuFile = flag.String("menu", "", "Name of a menu, e.g. Main")
|
||||
menuName = flag.String("menu", "", "Name of a menu, e.g. Main")
|
||||
)
|
||||
|
||||
type env struct {
|
||||
menu *menus.Menu
|
||||
objects []*assetstore.Object
|
||||
ui *ui.Interface
|
||||
|
||||
// fonts []*assetstore.Font
|
||||
// fontObjs []*assetstore.Object
|
||||
@@ -32,15 +27,12 @@ type env struct {
|
||||
lastState state
|
||||
}
|
||||
|
||||
type state struct {
|
||||
// Redraw the window if these change
|
||||
winBounds image.Rectangle
|
||||
}
|
||||
type state struct{}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
if *gamePath == "" || *menuFile == "" {
|
||||
if *gamePath == "" || *menuName == "" {
|
||||
flag.Usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
@@ -50,11 +42,12 @@ func main() {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
menu, err := menus.LoadMenu(*menuFile)
|
||||
menu, err := assets.Menu(*menuName)
|
||||
if err != nil {
|
||||
log.Fatalf("Couldn't load menu file %s: %v", *menuFile, err)
|
||||
log.Fatalf("Couldn't load menu %s: %v", *menuName, err)
|
||||
}
|
||||
|
||||
/* TODO: move i18n, fonts into assetstore
|
||||
if i18n, err := data.LoadI18n(filepath.Join(*gamePath, "Data", data.I18nFile)); err != nil {
|
||||
log.Printf("Failed to load i18n data, skipping internationalization: %v", err)
|
||||
} else {
|
||||
@@ -65,15 +58,22 @@ func main() {
|
||||
// if err != nil {
|
||||
// log.Fatalf("Failed to load font: %v", err)
|
||||
// }
|
||||
*/
|
||||
|
||||
var menuObjs []*assetstore.Object
|
||||
for _, filename := range menu.ObjectFiles {
|
||||
obj, err := assets.ObjectByPath(filepath.Join(*gamePath, "Menu", filename))
|
||||
iface, err := ui.NewInterface(menu)
|
||||
if err != nil {
|
||||
log.Fatalf("Couldn't initialize interface: %v", err)
|
||||
}
|
||||
|
||||
if menu.Name == "main" {
|
||||
log.Printf("Installing a click handler!")
|
||||
widget, err := iface.Widget(2, 5) // Menu 2, submenu 5
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to load %v: %v", filename, err)
|
||||
log.Fatalf("Couldn't find widget 2,5: %v", err)
|
||||
}
|
||||
widget.OnMouseClick = func() {
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
menuObjs = append(menuObjs, obj)
|
||||
}
|
||||
|
||||
// Yay sound
|
||||
@@ -92,132 +92,30 @@ func main() {
|
||||
|
||||
state := state{}
|
||||
env := &env{
|
||||
menu: menu,
|
||||
objects: menuObjs,
|
||||
ui: iface,
|
||||
//objects: menuObjs,
|
||||
// fonts: loadedFonts,
|
||||
state: state,
|
||||
lastState: state,
|
||||
}
|
||||
|
||||
win, err := ui.NewWindow("View Menu: " + *menuFile)
|
||||
win, err := ui.NewWindow(env, "View Menu: "+*menuName)
|
||||
if err != nil {
|
||||
log.Fatal("Couldn't create window: %v", err)
|
||||
}
|
||||
|
||||
if err := win.Run(env.Update, env.Draw); err != nil {
|
||||
if err := win.Run(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *env) Update() error {
|
||||
// No behaviour yet
|
||||
|
||||
func (e *env) Update(screenX, screenY int) error {
|
||||
e.step += 1
|
||||
e.lastState = e.state
|
||||
return nil
|
||||
}
|
||||
|
||||
const (
|
||||
origX = 640.0
|
||||
origY = 480.0
|
||||
)
|
||||
return e.ui.Update(screenX, screenY)
|
||||
}
|
||||
|
||||
func (e *env) Draw(screen *ebiten.Image) error {
|
||||
// The menus expect to be drawn to a 640x480 screen. We need to scale and
|
||||
// project that so it fills the window appropriately. This is a combination
|
||||
// of translate + zoom
|
||||
winSize := screen.Bounds().Max
|
||||
scaleX := float64(winSize.X) / float64(origX)
|
||||
scaleY := float64(winSize.Y) / float64(origY)
|
||||
|
||||
cam := ebiten.GeoM{}
|
||||
cam.Scale(scaleX, scaleY)
|
||||
|
||||
for _, record := range e.menu.Records {
|
||||
if err := e.drawRecordRecursive(record, screen, cam); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *env) drawRecordRecursive(record *menus.Record, screen *ebiten.Image, geo ebiten.GeoM) error {
|
||||
if err := e.drawRecord(record, screen, geo); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Draw all children of this record
|
||||
for _, child := range record.Children {
|
||||
if err := e.drawRecordRecursive(child, screen, geo); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// If the record has a "share" type, we can work out whether it's
|
||||
func (e *env) isFocused(record *menus.Record, geo ebiten.GeoM) bool {
|
||||
if record.Share < 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
sprite, err := e.objects[0].Sprite(record.Share) // FIXME: need to handle multiple objects
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
invGeo := geo
|
||||
invGeo.Invert()
|
||||
|
||||
cX, cY := ebiten.CursorPosition()
|
||||
cursorX, cursorY := invGeo.Apply(float64(cX), float64(cY)) // Undo screen scaling
|
||||
cursorPoint := image.Pt(int(cursorX), int(cursorY))
|
||||
|
||||
return cursorPoint.In(sprite.Rect)
|
||||
}
|
||||
|
||||
func (e *env) drawRecord(record *menus.Record, screen *ebiten.Image, geo ebiten.GeoM) error {
|
||||
// Draw this record if it's valid to do so. FIXME: lots to learn
|
||||
|
||||
spriteId := record.SelectSprite(
|
||||
e.step/2,
|
||||
ebiten.IsMouseButtonPressed(ebiten.MouseButtonLeft),
|
||||
e.isFocused(record, geo),
|
||||
)
|
||||
|
||||
if spriteId < 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// X-CORD and Y-CORD are universally either 0 or -1, so ignore here.
|
||||
// TODO: maybe 0 overrides in-sprite offset (set below)?
|
||||
|
||||
// FIXME: Need to handle multiple objects
|
||||
obj := e.objects[0]
|
||||
sprite, err := obj.Sprite(spriteId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Account for scaling, draw sprite at its specified offset
|
||||
x, y := geo.Apply(float64(sprite.XOffset), float64(sprite.YOffset))
|
||||
|
||||
// log.Printf(
|
||||
// "Drawing id=%v type=%v spriteid=%v x=%v(+%v) y=%v(%+v) desc=%q parent=%p",
|
||||
// record.Id, record.Type, spriteId, record.X, record.Y, sprite.XOffset, sprite.YOffset, record.Desc, record.Parent,
|
||||
// )
|
||||
|
||||
geo.Translate(x, y)
|
||||
|
||||
screen.DrawImage(sprite.Image, &ebiten.DrawImageOptions{GeoM: geo})
|
||||
|
||||
// FIXME: we probably shouldn't draw everything?
|
||||
// FIXME: handle multiple fonts
|
||||
// if len(e.fonts) > 0 && record.Desc != "" {
|
||||
// e.fonts[0].Output(screen, origOffset, record.Desc)
|
||||
// }
|
||||
|
||||
return nil
|
||||
return e.ui.Draw(screen)
|
||||
}
|
||||
|
@@ -71,7 +71,7 @@ func main() {
|
||||
}
|
||||
env := &env{gameMap: gameMap, set: mapSet, state: state, lastState: state}
|
||||
|
||||
win, err := ui.NewWindow("View Map " + *mapFile)
|
||||
win, err := ui.NewWindow(env, "View Map "+*mapFile)
|
||||
if err != nil {
|
||||
log.Fatal("Couldn't create window: %v", err)
|
||||
}
|
||||
@@ -92,7 +92,7 @@ func main() {
|
||||
|
||||
win.OnMouseWheel(env.changeZoom)
|
||||
|
||||
if err := win.Run(env.Update, env.Draw); err != nil {
|
||||
if err := win.Run(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
@@ -137,7 +137,7 @@ func (e *env) changeZoom(_, y float64) {
|
||||
e.state.zoom *= math.Pow(1.2, y)
|
||||
}
|
||||
|
||||
func (e *env) Update() error {
|
||||
func (e *env) Update(screenX, screenY int) error {
|
||||
// TODO: show details of clicked-on cell in terminal
|
||||
|
||||
// Automatically cycle every 500ms when auto-update is on
|
||||
|
@@ -68,7 +68,7 @@ func main() {
|
||||
lastState: state,
|
||||
}
|
||||
|
||||
win, err := ui.NewWindow("View Object: " + *objName)
|
||||
win, err := ui.NewWindow(env, "View Object: "+*objName)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
@@ -82,12 +82,12 @@ func main() {
|
||||
win.OnMouseWheel(env.changeZoom)
|
||||
|
||||
// The main thread now belongs to ebiten
|
||||
if err := win.Run(env.Update, env.Draw); err != nil {
|
||||
if err := win.Run(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *env) Update() error {
|
||||
func (e *env) Update(screenX, screenY int) error {
|
||||
if e.step == 0 || e.lastState != e.state {
|
||||
log.Printf(
|
||||
"new state: sprite=%d/%d zoom=%.2f, origin=%+v",
|
||||
|
@@ -51,11 +51,6 @@ func main() {
|
||||
log.Fatalf("Couldn't load set %s: %v", *setName, err)
|
||||
}
|
||||
|
||||
win, err := ui.NewWindow("View Set: " + *setName)
|
||||
if err != nil {
|
||||
log.Fatal("Couldn't create window: %v", err)
|
||||
}
|
||||
|
||||
state := state{zoom: 8.0}
|
||||
env := &env{
|
||||
set: set,
|
||||
@@ -63,6 +58,11 @@ func main() {
|
||||
lastState: state,
|
||||
}
|
||||
|
||||
win, err := ui.NewWindow(env, "View Set: "+*setName)
|
||||
if err != nil {
|
||||
log.Fatal("Couldn't create window: %v", err)
|
||||
}
|
||||
|
||||
win.OnKeyUp(ebiten.KeyLeft, env.changeObjIdx(-1))
|
||||
win.OnKeyUp(ebiten.KeyRight, env.changeObjIdx(+1))
|
||||
|
||||
@@ -72,12 +72,12 @@ func main() {
|
||||
win.OnMouseWheel(env.changeZoom)
|
||||
|
||||
// Main thread now belongs to ebiten
|
||||
if err := win.Run(env.Update, env.Draw); err != nil {
|
||||
if err := win.Run(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *env) Update() error {
|
||||
func (e *env) Update(screenX, screenY int) error {
|
||||
curObj, err := e.curObject()
|
||||
if err != nil {
|
||||
return err
|
||||
|
Reference in New Issue
Block a user