2018-03-18 20:41:17 +00:00
|
|
|
package ui
|
|
|
|
|
|
|
|
import (
|
|
|
|
"flag"
|
2019-12-29 20:34:05 +00:00
|
|
|
"fmt"
|
2020-03-19 18:36:20 +00:00
|
|
|
"log"
|
|
|
|
"os"
|
2020-03-25 02:12:17 +00:00
|
|
|
"runtime/debug"
|
2020-03-19 18:36:20 +00:00
|
|
|
"runtime/pprof"
|
2018-03-18 20:41:17 +00:00
|
|
|
|
2019-12-29 15:38:49 +00:00
|
|
|
"github.com/hajimehoshi/ebiten"
|
2019-12-29 20:34:05 +00:00
|
|
|
"github.com/hajimehoshi/ebiten/ebitenutil"
|
2019-12-29 17:30:21 +00:00
|
|
|
"github.com/hajimehoshi/ebiten/inpututil"
|
2018-03-18 20:41:17 +00:00
|
|
|
)
|
|
|
|
|
2020-03-22 02:58:52 +00:00
|
|
|
type Game interface {
|
|
|
|
Update(screenX, screenY int) error
|
|
|
|
Draw(*ebiten.Image) error
|
|
|
|
}
|
|
|
|
|
2018-03-18 20:41:17 +00:00
|
|
|
var (
|
2020-03-19 18:44:51 +00:00
|
|
|
screenScale = flag.Float64("screen-scale", 1.0, "Scale the window by this factor")
|
2020-03-22 22:12:20 +00:00
|
|
|
cpuprofile = flag.String("cpuprofile", "", "write cpu profile to `file`")
|
2018-03-18 20:41:17 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
type Window struct {
|
2019-12-29 17:30:21 +00:00
|
|
|
Title string
|
|
|
|
KeyUpHandlers map[ebiten.Key]func()
|
|
|
|
MouseWheelHandler func(float64, float64)
|
2019-12-29 15:38:49 +00:00
|
|
|
|
2020-03-22 02:58:52 +00:00
|
|
|
// Allow the "game" to be switched out at any time
|
|
|
|
game Game
|
2019-12-29 20:34:05 +00:00
|
|
|
|
2020-03-21 00:56:35 +00:00
|
|
|
debug bool
|
2020-03-19 18:44:51 +00:00
|
|
|
firstRun bool
|
2020-03-22 22:12:20 +00:00
|
|
|
|
|
|
|
xRes int
|
|
|
|
yRes int
|
2018-03-18 20:41:17 +00:00
|
|
|
}
|
|
|
|
|
2019-12-29 15:38:49 +00:00
|
|
|
// 0,0 is the *top left* of the window
|
2018-03-18 20:41:17 +00:00
|
|
|
//
|
2019-12-29 15:38:49 +00:00
|
|
|
// ebiten assumes a single window, so only call this once...
|
2020-03-22 22:12:20 +00:00
|
|
|
func NewWindow(game Game, title string, xRes int, yRes int) (*Window, error) {
|
2019-12-29 19:41:20 +00:00
|
|
|
ebiten.SetRunnableInBackground(true)
|
|
|
|
|
2019-12-29 15:38:49 +00:00
|
|
|
return &Window{
|
2019-12-29 17:30:21 +00:00
|
|
|
Title: title,
|
|
|
|
KeyUpHandlers: make(map[ebiten.Key]func()),
|
2019-12-29 20:34:05 +00:00
|
|
|
debug: true,
|
2020-03-19 18:44:51 +00:00
|
|
|
firstRun: true,
|
2020-03-22 02:58:52 +00:00
|
|
|
game: game,
|
2020-03-22 22:12:20 +00:00
|
|
|
xRes: xRes,
|
|
|
|
yRes: yRes,
|
2019-12-29 15:38:49 +00:00
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2019-12-29 17:30:21 +00:00
|
|
|
// TODO: multiple handlers for the same key?
|
2019-12-29 15:38:49 +00:00
|
|
|
func (w *Window) OnKeyUp(key ebiten.Key, f func()) {
|
2019-12-29 17:30:21 +00:00
|
|
|
w.KeyUpHandlers[key] = f
|
|
|
|
}
|
2019-12-29 15:38:49 +00:00
|
|
|
|
2019-12-29 17:30:21 +00:00
|
|
|
func (w *Window) OnMouseWheel(f func(x, y float64)) {
|
|
|
|
w.MouseWheelHandler = f
|
2019-12-29 15:38:49 +00:00
|
|
|
}
|
|
|
|
|
2020-03-22 02:58:52 +00:00
|
|
|
func (w *Window) Layout(_, _ int) (int, int) {
|
2020-03-22 22:12:20 +00:00
|
|
|
return w.xRes, w.yRes
|
2020-03-22 02:58:52 +00:00
|
|
|
}
|
2020-03-19 18:44:51 +00:00
|
|
|
|
2020-03-25 02:12:17 +00:00
|
|
|
func (w *Window) Update(screen *ebiten.Image) (outErr error) {
|
|
|
|
// Ebiten does not like it if we panic inside its main loop
|
|
|
|
defer func() {
|
|
|
|
if panicErr := recover(); panicErr != nil {
|
|
|
|
if w.debug {
|
|
|
|
debug.PrintStack()
|
|
|
|
}
|
|
|
|
|
|
|
|
outErr = fmt.Errorf("Panic: %v", panicErr)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
2020-03-22 02:58:52 +00:00
|
|
|
if err := w.game.Update(screen.Size()); err != nil {
|
2019-12-29 15:38:49 +00:00
|
|
|
return err
|
2018-03-18 20:41:17 +00:00
|
|
|
}
|
|
|
|
|
2019-12-29 15:38:49 +00:00
|
|
|
// Process keys
|
2019-12-29 17:30:21 +00:00
|
|
|
// TODO: efficient set operations
|
|
|
|
|
|
|
|
for key, cb := range w.KeyUpHandlers {
|
|
|
|
if inpututil.IsKeyJustReleased(key) {
|
|
|
|
cb()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if w.MouseWheelHandler != nil {
|
|
|
|
x, y := ebiten.Wheel()
|
|
|
|
if x != 0 || y != 0 {
|
|
|
|
w.MouseWheelHandler(x, y)
|
|
|
|
}
|
|
|
|
}
|
2019-12-29 15:38:49 +00:00
|
|
|
|
2020-03-22 02:58:52 +00:00
|
|
|
if ebiten.IsDrawingSkipped() {
|
|
|
|
return nil
|
|
|
|
}
|
2019-12-29 20:34:05 +00:00
|
|
|
|
2020-03-22 02:58:52 +00:00
|
|
|
if err := w.game.Draw(screen); 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())
|
|
|
|
ebitenutil.DebugPrint(screen, msg)
|
2018-03-18 20:41:17 +00:00
|
|
|
}
|
|
|
|
|
2019-12-29 15:38:49 +00:00
|
|
|
return nil
|
2018-03-18 20:41:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: a stop or other cancellation mechanism
|
2019-12-29 15:38:49 +00:00
|
|
|
//
|
|
|
|
// Note that this must be called on the main OS thread
|
2020-03-22 02:58:52 +00:00
|
|
|
func (w *Window) Run() error {
|
2020-03-19 18:36:20 +00:00
|
|
|
if *cpuprofile != "" {
|
2020-03-21 00:56:35 +00:00
|
|
|
f, err := os.Create(*cpuprofile)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal("could not create CPU profile: ", err)
|
|
|
|
}
|
|
|
|
defer f.Close() // error handling omitted for example
|
|
|
|
if err := pprof.StartCPUProfile(f); err != nil {
|
|
|
|
log.Fatal("could not start CPU profile: ", err)
|
|
|
|
}
|
|
|
|
defer pprof.StopCPUProfile()
|
|
|
|
}
|
|
|
|
|
2020-03-22 22:12:20 +00:00
|
|
|
ebiten.SetWindowSize(int(float64(w.xRes)*(*screenScale)), int(float64(w.yRes)*(*screenScale)))
|
2020-03-22 02:58:52 +00:00
|
|
|
ebiten.SetWindowTitle(w.Title)
|
2020-03-22 22:12:20 +00:00
|
|
|
return ebiten.RunGame(w)
|
2018-03-18 20:41:17 +00:00
|
|
|
}
|