doodle/pkg/modal/loadscreen/loadscreen.go

250 lines
5.8 KiB
Go
Raw Normal View History

// Package loadscreen implements a modal "Loading" screen for the game, which
// can be shown or hidden by gameplay scenes as needed.
package loadscreen
import (
"strings"
"git.kirsle.net/apps/doodle/pkg/balance"
"git.kirsle.net/apps/doodle/pkg/level"
"git.kirsle.net/apps/doodle/pkg/log"
"git.kirsle.net/apps/doodle/pkg/shmem"
"git.kirsle.net/apps/doodle/pkg/uix"
"git.kirsle.net/go/render"
"git.kirsle.net/go/ui"
)
// Configuration values.
const (
ProgressWidth = 300
ProgressHeight = 16
)
// State variables for the loading screen.
var (
visible bool
withProgress bool
subtitle string // custom subtitle text, SetSubtitle().
// Animated title bar
titleBase = "Loading"
animState = 0
animation = []string{
". ",
".. ",
"...",
" ..",
" .",
" ",
}
animSpeed uint64 = 32
titleVar string
// UI widgets.
window *ui.Frame
canvas *uix.Canvas
secondary *ui.Label // subtitle text
progressTrough *ui.Frame
progressBar *ui.Frame
progressText *ui.Label
)
// Show the basic loading screen without a progress bar.
func Show() {
setup()
visible = true
withProgress = false
subtitle = ""
}
// ShowWithProgress initializes the loading screen with a progress bar starting at zero.
func ShowWithProgress() {
setup()
visible = true
withProgress = true
subtitle = ""
SetProgress(0)
}
// SetSubtitle specifies secondary text beneath the Loading banner.
// The subtitle is blanked on Show() and ShowWithProgress() and must
// be specified by the caller if desired. Pass multiple values for
// multiple lines of text.
func SetSubtitle(value ...string) {
subtitle = strings.Join(value, "\n")
}
// IsActive returns whether the loading screen is currently visible.
func IsActive() bool {
return visible
}
// Resized the window.
func Resized() {
if visible {
size := render.NewRect(shmem.CurrentRenderEngine.WindowSize())
window.Resize(size)
}
}
// Hide the loading screen.
func Hide() {
Optimize memory by freeing up SDL2 textures * Added to the F3 Debug Overlay is a "Texture:" label that counts the number of textures currently loaded by the (SDL2) render engine. * Added Teardown() functions to Level, Doodad and the Chunker they both use to free up SDL2 textures for all their cached graphics. * The Canvas.Destroy() function now cleans up all textures that the Canvas is responsible for: calling the Teardown() of the Level or Doodad, calling Destroy() on all level actors, and cleaning up Wallpaper textures. * The Destroy() method of the game's various Scenes will properly Destroy() their canvases to clean up when transitioning to another scene. The MainScene, MenuScene, EditorScene and PlayScene. * Fix the sprites package to actually cache the ui.Image widgets. The game has very few sprites so no need to free them just yet. Some tricky places that were leaking textures have been cleaned up: * Canvas.InstallActors() destroys the canvases of existing actors before it reinitializes the list and installs the replacements. * The DraggableActor when the user is dragging an actor around their level cleans up the blueprint masked drag/drop actor before nulling it out. Misc changes: * The player character cheats during Play Mode will immediately swap out the player character on the current level. * Properly call the Close() function instead of Hide() to dismiss popup windows. The Close() function itself calls Hide() but also triggers WindowClose event handlers. The Doodad Dropper subscribes to its close event to free textures for all its doodad canvases.
2022-04-09 21:41:24 +00:00
canvas.Destroy() // cleanup wallpaper textures
visible = false
}
// SetProgress sets the current progress value for loading screens having a progress bar.
func SetProgress(v float64) {
// Resize the progress bar in the trough.
if progressTrough != nil {
var (
troughSize = progressTrough.Size()
height = progressBar.Size().H
)
progressBar.Resize(render.Rect{
W: int(float64(troughSize.W-4) * v),
H: height,
})
}
}
// Common function to initialize the loading screen.
func setup() {
if window != nil {
return
}
titleVar = titleBase + animation[animState]
// Create the parent container that will stretch full screen.
window = ui.NewFrame("Loadscreen Window")
window.SetBackground(render.RGBA(0, 0, 1, 40)) // makes the wallpaper darker? :/
// "Loading" text.
label := ui.NewLabel(ui.Label{
TextVariable: &titleVar,
Font: balance.LoadScreenFont,
})
label.Compute(shmem.CurrentRenderEngine)
window.Place(label, ui.Place{
Top: 128,
Center: true,
})
// Subtitle text.
secondary = ui.NewLabel(ui.Label{
TextVariable: &subtitle,
Font: balance.LoadScreenSecondaryFont,
})
window.Place(secondary, ui.Place{
Top: 128 + label.Size().H + 64,
Center: true,
})
// Progress bar.
progressTrough = ui.NewFrame("Progress Trough")
progressTrough.Configure(ui.Config{
Width: ProgressWidth,
Height: ProgressHeight,
BorderSize: 2,
BorderStyle: ui.BorderSunken,
Background: render.DarkGrey,
})
window.Place(progressTrough, ui.Place{
// Nestle it between the Title and Subtitle.
Center: true,
Top: 128 + label.Size().H + 16,
})
progressBar = ui.NewFrame("Progress Bar")
progressBar.Configure(ui.Config{
Width: 0,
Height: ProgressHeight - 4,
Background: render.Green,
})
progressTrough.Pack(progressBar, ui.Pack{
Side: ui.W,
})
}
// Loop is called on every game loop. If the loadscreen is not active, nothing happens.
// Otherwise the loading screen UI is drawn to screen.
func Loop(windowSize render.Rect, e render.Engine) {
if !visible {
return
}
if window != nil {
// Initialize the wallpaper canvas?
if canvas == nil {
canvas = uix.NewCanvas(128, false)
canvas.LoadLevel(&level.Level{
Chunker: level.NewChunker(100),
Palette: level.NewPalette(),
PageType: level.Bounded,
Wallpaper: "blueprint.png",
})
}
canvas.Resize(windowSize)
canvas.Compute(e)
canvas.Present(e, render.Origin)
window.Resize(windowSize)
window.Compute(e)
window.Present(e, render.Origin)
// Show/hide the progress bar.
progressTrough.Compute(e)
if withProgress && progressTrough.Hidden() {
progressTrough.Show()
} else if !withProgress && !progressTrough.Hidden() {
progressTrough.Hide()
}
// Show/hide the subtitle text.
if len(subtitle) > 0 && secondary.Hidden() {
secondary.Show()
} else if subtitle == "" && !secondary.Hidden() {
secondary.Hide()
}
// Animate the ellipses.
if shmem.Tick%animSpeed == 0 {
titleVar = titleBase + animation[animState]
animState++
if animState >= len(animation) {
animState = 0
}
}
}
}
// PreloadAllChunkBitmaps is a helper function to eager cache all bitmap
// images of the chunks in a level drawing. It is designed to work with the
// loading screen and will set the Progress percent based on the total number
// of chunks vs. chunks remaining to pre-cache bitmaps from.
func PreloadAllChunkBitmaps(chunker *level.Chunker) {
loadChunksTarget := len(chunker.Chunks)
// Skipping the eager rendering of chunks?
if !balance.EagerRenderLevelChunks {
log.Info("PreloadAllChunkBitmaps: skipping eager render")
return
}
for {
remaining := chunker.PrerenderN(10)
// Set the load screen progress % based on number of chunks to render.
if loadChunksTarget > 0 {
percent := float64(loadChunksTarget-remaining) / float64(loadChunksTarget)
SetProgress(percent)
}
if remaining == 0 {
break
}
}
}