Files
ordoor/internal/ui/window.go

182 lines
3.9 KiB
Go
Raw Normal View History

package ui
import (
"flag"
2019-12-29 20:34:05 +00:00
"fmt"
"log"
"os"
2020-03-25 02:12:17 +00:00
"runtime/debug"
"runtime/pprof"
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"
)
type Game interface {
Update(screenX, screenY int) error
Draw(*ebiten.Image) error
}
type CustomCursor interface {
// The cursor draw operation
Cursor() (*ebiten.Image, *ebiten.DrawImageOptions, error)
}
var (
2020-03-19 18:44:51 +00:00
screenScale = flag.Float64("screen-scale", 1.0, "Scale the window by this factor")
cpuprofile = flag.String("cpuprofile", "", "write cpu profile to `file`")
)
2020-04-10 19:55:16 +01:00
// 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 {
2019-12-29 17:30:21 +00:00
Title string
KeyUpHandlers map[ebiten.Key]func()
MouseWheelHandler func(float64, float64)
MouseClickHandler func()
2019-12-29 15:38:49 +00:00
// Allow the "game" to be switched out at any time
game Game
2019-12-29 20:34:05 +00:00
debug bool
2020-03-19 18:44:51 +00:00
firstRun bool
xRes int
yRes int
}
2019-12-29 15:38:49 +00:00
// 0,0 is the *top left* of the window
//
2019-12-29 15:38:49 +00:00
// ebiten assumes a single window, so only call this once...
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,
game: game,
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
}
func (w *Window) OnMouseClick(f func()) {
w.MouseClickHandler = f
}
func (w *Window) Layout(_, _ int) (int, int) {
return w.xRes, w.yRes
}
2020-03-19 18:44:51 +00:00
func (w *Window) drawCursor(screen *ebiten.Image) error {
cIface, ok := w.game.(CustomCursor)
if !ok {
return nil
}
cursor, op, err := cIface.Cursor()
if err != nil {
return err
}
2020-04-10 19:55:16 +01:00
// Hide the system cursor if we have a custom one
if cursor == nil {
ebiten.SetCursorMode(ebiten.CursorModeVisible)
return nil
2020-04-10 19:55:16 +01:00
}
ebiten.SetCursorMode(ebiten.CursorModeHidden)
return screen.DrawImage(cursor, op)
2020-04-10 19:55:16 +01: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)
}
}()
if err := w.game.Update(screen.Size()); err != nil {
2019-12-29 15:38:49 +00:00
return err
}
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
if w.MouseClickHandler != nil {
if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft) {
w.MouseClickHandler()
}
}
if ebiten.IsDrawingSkipped() {
return nil
}
2019-12-29 20:34:05 +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)
}
// Draw the cursor last
return w.drawCursor(screen)
}
// 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
func (w *Window) Run() error {
if *cpuprofile != "" {
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()
}
ebiten.SetWindowSize(int(float64(w.xRes)*(*screenScale)), int(float64(w.yRes)*(*screenScale)))
ebiten.SetWindowTitle(w.Title)
return ebiten.RunGame(w)
}