package ui import ( "flag" "fmt" "log" "os" "runtime/pprof" "github.com/hajimehoshi/ebiten" "github.com/hajimehoshi/ebiten/ebitenutil" "github.com/hajimehoshi/ebiten/inpututil" ) var ( screenScale = flag.Float64("screen-scale", 1.0, "Scale the window by this factor") cpuprofile = flag.String("cpuprofile", "", "write cpu profile to `file`") ) type Window struct { Title string KeyUpHandlers map[ebiten.Key]func() MouseWheelHandler func(float64, float64) // User-provided update actions updateFn func() error drawFn func(*ebiten.Image) error debug bool firstRun bool } // 0,0 is the *top left* of the window // // ebiten assumes a single window, so only call this once... func NewWindow(title string) (*Window, error) { ebiten.SetRunnableInBackground(true) return &Window{ Title: title, KeyUpHandlers: make(map[ebiten.Key]func()), debug: true, firstRun: true, }, nil } // TODO: multiple handlers for the same key? func (w *Window) OnKeyUp(key ebiten.Key, f func()) { w.KeyUpHandlers[key] = f } func (w *Window) OnMouseWheel(f func(x, y float64)) { w.MouseWheelHandler = f } func (w *Window) run(screen *ebiten.Image) error { if w.firstRun { ebiten.SetScreenScale(*screenScale) w.firstRun = false } if err := w.updateFn(); err != nil { return err } // Process keys // 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) } } if !ebiten.IsDrawingSkipped() { if err := w.drawFn(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) } } return nil } // TODO: a stop or other cancellation mechanism // // Note that this must be called on the main OS thread func (w *Window) Run(updateFn func() error, drawFn func(*ebiten.Image) error) error { w.updateFn = updateFn w.drawFn = drawFn 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() } return ebiten.Run(w.run, 640, 480, 1, w.Title) // Native game resolution: 640x480 }