OpenDiablo2/d2core/d2screen/d2screen.go

146 lines
4.0 KiB
Go

package d2screen
import (
"log"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2gui"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2ui"
)
// Screen is an exported interface
type Screen interface{}
// ScreenLoadHandler is an exported interface
type ScreenLoadHandler interface {
// OnLoad performs all necessary loading to prepare a screen to be shown such as loading assets, placing and binding
// of ui elements, etc. This loading is done asynchronously. The provided channel will allow implementations to
// provide progress via Error, Progress, or Done
OnLoad(loading LoadingState)
}
// ScreenUnloadHandler is an exported interface
type ScreenUnloadHandler interface {
OnUnload() error
}
// ScreenRenderHandler is an exported interface
type ScreenRenderHandler interface {
Render(target d2interface.Surface) error
}
// ScreenAdvanceHandler is an exported interface
type ScreenAdvanceHandler interface {
Advance(elapsed float64) error
}
var singleton struct {
nextScreen Screen
loadingScreen Screen
loadingState LoadingState
currentScreen Screen
}
// SetNextScreen is about to set a given screen as next
func SetNextScreen(screen Screen) {
singleton.nextScreen = screen
}
// Advance updates the UI on every frame
func Advance(elapsed float64) error {
switch {
case singleton.loadingScreen != nil:
// this call blocks execution and could lead to deadlock if a screen implements OnLoad incorreclty
load, ok := <-singleton.loadingState.updates
if !ok {
log.Println("loadingState chan should not be closed while in a loading screen")
}
if load.err != nil {
log.Printf("PROBLEM LOADING THE SCREEN: %v", load.err)
return load.err
}
d2gui.ShowLoadScreen(load.progress)
if load.done {
singleton.currentScreen = singleton.loadingScreen
singleton.loadingScreen = nil
d2gui.ShowCursor()
d2gui.HideLoadScreen()
}
case singleton.nextScreen != nil:
if handler, ok := singleton.currentScreen.(ScreenUnloadHandler); ok {
if err := handler.OnUnload(); err != nil {
return err
}
}
d2ui.Reset()
d2gui.SetLayout(nil)
if handler, ok := singleton.nextScreen.(ScreenLoadHandler); ok {
d2gui.ShowLoadScreen(0)
d2gui.HideCursor()
singleton.loadingState = LoadingState{updates: make(chan loadingUpdate)}
go func() {
handler.OnLoad(singleton.loadingState)
singleton.loadingState.Done()
}()
singleton.currentScreen = nil
singleton.loadingScreen = singleton.nextScreen
} else {
singleton.currentScreen = singleton.nextScreen
singleton.loadingScreen = nil
}
singleton.nextScreen = nil
case singleton.currentScreen != nil:
if handler, ok := singleton.currentScreen.(ScreenAdvanceHandler); ok {
if err := handler.Advance(elapsed); err != nil {
return err
}
}
}
return nil
}
// Render renders the UI by a given surface
func Render(surface d2interface.Surface) error {
if handler, ok := singleton.currentScreen.(ScreenRenderHandler); ok {
if err := handler.Render(surface); err != nil {
return err
}
}
return nil
}
// LoadingState represents the loading state
type LoadingState struct {
updates chan loadingUpdate
}
type loadingUpdate struct {
progress float64
err error
done bool
}
// Error provides a way for callers to report an error during loading.
// This is meant to be delivered via the progress channel in OnLoad implementations.
func (l *LoadingState) Error(err error) {
l.updates <- loadingUpdate{err: err}
}
// Progress provides a way for callers to report the ratio between `0` and `1` of the progress made loading a screen.
// This is meant to be delivered via the progress channel in OnLoad implementations.
func (l *LoadingState) Progress(ratio float64) {
l.updates <- loadingUpdate{progress: ratio}
}
// Done provides a way for callers to report that screen loading has been completed.
// This is meant to be delivered via the progress channel in OnLoad implementations.
func (l *LoadingState) Done() {
l.updates <- loadingUpdate{progress: 1.0}
l.updates <- loadingUpdate{done: true}
}