Noah Petherbridge
db5760ee83
* 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.
655 lines
16 KiB
Go
655 lines
16 KiB
Go
package doodle
|
|
|
|
import (
|
|
"fmt"
|
|
"math/rand"
|
|
|
|
"git.kirsle.net/apps/doodle/pkg/balance"
|
|
"git.kirsle.net/apps/doodle/pkg/branding"
|
|
"git.kirsle.net/apps/doodle/pkg/level"
|
|
"git.kirsle.net/apps/doodle/pkg/levelpack"
|
|
"git.kirsle.net/apps/doodle/pkg/license"
|
|
"git.kirsle.net/apps/doodle/pkg/log"
|
|
"git.kirsle.net/apps/doodle/pkg/modal/loadscreen"
|
|
"git.kirsle.net/apps/doodle/pkg/native"
|
|
"git.kirsle.net/apps/doodle/pkg/scripting"
|
|
"git.kirsle.net/apps/doodle/pkg/shmem"
|
|
"git.kirsle.net/apps/doodle/pkg/uix"
|
|
"git.kirsle.net/apps/doodle/pkg/updater"
|
|
"git.kirsle.net/apps/doodle/pkg/windows"
|
|
"git.kirsle.net/go/render"
|
|
"git.kirsle.net/go/render/event"
|
|
"git.kirsle.net/go/ui"
|
|
"git.kirsle.net/go/ui/style"
|
|
)
|
|
|
|
// MainScene implements the main menu of Doodle.
|
|
type MainScene struct {
|
|
Supervisor *ui.Supervisor
|
|
LevelFilename string // custom level filename to load in background
|
|
|
|
// Background wallpaper canvas.
|
|
scripting *scripting.Supervisor
|
|
canvas *uix.Canvas
|
|
|
|
// UI components.
|
|
labelTitle *ui.Label
|
|
labelSubtitle *ui.Label
|
|
labelVersion *ui.Label
|
|
labelHint *ui.Label
|
|
frame *ui.Frame // Main button frame
|
|
winRegister *ui.Window
|
|
winSettings *ui.Window
|
|
winLevelPacks *ui.Window
|
|
|
|
// Update check variables.
|
|
updateButton *ui.Button
|
|
updateInfo updater.VersionInfo
|
|
|
|
// Lazy scroll variables. See LoopLazyScroll().
|
|
PauseLazyScroll bool // exported for dev console
|
|
lazyScrollBounce bool
|
|
lazyScrollTrajectory render.Point
|
|
lazyScrollLastValue render.Point
|
|
|
|
// Landscape mode: if the screen isn't tall enough to see the main
|
|
// menu we redo the layout to be landscape friendly. NOTE: this only
|
|
// happens one time, and does not re-adapt when the window is made
|
|
// tall enough again.
|
|
landscapeMode bool
|
|
}
|
|
|
|
/*
|
|
MakePhotogenic tweaks some variables to make a screenshotable title screen.
|
|
|
|
This function is designed to be called from the developer shell:
|
|
|
|
$ d.Scene.MakePhotogenic(true)
|
|
|
|
It automates the pausing of lazy scroll and hiding of UI elements except
|
|
for just the title and version number.
|
|
*/
|
|
func (s *MainScene) MakePhotogenic(v bool) {
|
|
if v {
|
|
s.PauseLazyScroll = true
|
|
s.ButtonFrame().Hide()
|
|
s.LabelHint().Hide()
|
|
} else {
|
|
s.PauseLazyScroll = false
|
|
s.ButtonFrame().Show()
|
|
s.LabelHint().Show()
|
|
}
|
|
}
|
|
|
|
// Name of the scene.
|
|
func (s *MainScene) Name() string {
|
|
return "Main"
|
|
}
|
|
|
|
// Setup the scene.
|
|
func (s *MainScene) Setup(d *Doodle) error {
|
|
s.Supervisor = ui.NewSupervisor()
|
|
|
|
if err := s.SetupDemoLevel(d); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Main title label
|
|
s.labelTitle = ui.NewLabel(ui.Label{
|
|
Text: branding.AppName,
|
|
Font: balance.TitleScreenFont,
|
|
})
|
|
s.labelTitle.Compute(d.Engine)
|
|
|
|
// Subtitle/byline.
|
|
s.labelSubtitle = ui.NewLabel(ui.Label{
|
|
Text: branding.Byline,
|
|
Font: balance.TitleScreenSubtitleFont,
|
|
})
|
|
s.labelSubtitle.Compute(d.Engine)
|
|
|
|
// Version label.
|
|
var shareware string
|
|
if !license.IsRegistered() {
|
|
shareware = " (shareware)"
|
|
}
|
|
ver := ui.NewLabel(ui.Label{
|
|
Text: fmt.Sprintf("v%s%s", branding.Version, shareware),
|
|
Font: balance.TitleScreenVersionFont,
|
|
})
|
|
ver.Compute(d.Engine)
|
|
s.labelVersion = ver
|
|
|
|
// Arrow Keys hint label (scroll the demo level).
|
|
s.labelHint = ui.NewLabel(ui.Label{
|
|
Text: "Hint: press the Arrow keys",
|
|
Font: render.Text{
|
|
Size: 16,
|
|
Color: render.Grey,
|
|
Shadow: render.Purple,
|
|
},
|
|
})
|
|
s.labelHint.Compute(d.Engine)
|
|
|
|
// "Update Available" button.
|
|
s.updateButton = ui.NewButton("Update Button", ui.NewLabel(ui.Label{
|
|
Text: "An update is available!",
|
|
Font: render.Text{
|
|
FontFilename: balance.SansBoldFont,
|
|
Size: 16,
|
|
Color: render.Blue,
|
|
Padding: 4,
|
|
},
|
|
}))
|
|
s.updateButton.Handle(ui.Click, func(ed ui.EventData) error {
|
|
native.OpenURL(s.updateInfo.DownloadURL)
|
|
return nil
|
|
})
|
|
s.updateButton.Compute(d.Engine)
|
|
s.updateButton.Hide()
|
|
s.Supervisor.Add(s.updateButton)
|
|
|
|
// Main UI button frame.
|
|
frame := ui.NewFrame("frame")
|
|
s.frame = frame
|
|
|
|
var buttons = []struct {
|
|
Name string
|
|
If func() bool
|
|
Func func()
|
|
Style *style.Button
|
|
}{
|
|
{
|
|
Name: "Story Mode",
|
|
Func: func() {
|
|
if s.winLevelPacks == nil {
|
|
s.winLevelPacks = windows.NewLevelPackWindow(windows.LevelPack{
|
|
Supervisor: s.Supervisor,
|
|
Engine: d.Engine,
|
|
|
|
OnPlayLevel: func(lp levelpack.LevelPack, which levelpack.Level) {
|
|
if err := d.PlayFromLevelpack(lp, which); err != nil {
|
|
shmem.FlashError(err.Error())
|
|
}
|
|
},
|
|
OnCloseWindow: func() {
|
|
s.winLevelPacks.Destroy()
|
|
s.winLevelPacks = nil
|
|
},
|
|
})
|
|
}
|
|
s.winLevelPacks.MoveTo(render.Point{
|
|
X: (d.width / 2) - (s.winLevelPacks.Size().W / 2),
|
|
Y: (d.height / 2) - (s.winLevelPacks.Size().H / 2),
|
|
})
|
|
s.winLevelPacks.Show()
|
|
},
|
|
Style: &balance.ButtonBabyBlue,
|
|
},
|
|
{
|
|
Name: "Play a Level",
|
|
Func: d.GotoPlayMenu,
|
|
Style: &balance.ButtonBabyBlue,
|
|
},
|
|
{
|
|
Name: "New Drawing",
|
|
Func: d.GotoNewMenu,
|
|
Style: &balance.ButtonPink,
|
|
},
|
|
{
|
|
Name: "Edit Drawing",
|
|
Func: d.GotoLoadMenu,
|
|
Style: &balance.ButtonPink,
|
|
},
|
|
{
|
|
Name: "Settings",
|
|
Func: func() {
|
|
if s.winSettings == nil {
|
|
s.winSettings = d.MakeSettingsWindow(s.Supervisor)
|
|
}
|
|
s.winSettings.Show()
|
|
},
|
|
},
|
|
{
|
|
Name: "Register",
|
|
If: func() bool {
|
|
return !license.IsRegistered()
|
|
},
|
|
Func: func() {
|
|
if s.winRegister == nil {
|
|
cfg := windows.License{
|
|
Supervisor: s.Supervisor,
|
|
Engine: d.Engine,
|
|
OnCancel: func() {
|
|
s.winRegister.Hide()
|
|
},
|
|
}
|
|
cfg.OnLicensed = func() {
|
|
// License status has changed, reload the window!
|
|
if s.winRegister != nil {
|
|
s.winRegister.Hide()
|
|
}
|
|
s.winRegister = windows.MakeLicenseWindow(d.width, d.height, cfg)
|
|
}
|
|
|
|
cfg.OnLicensed()
|
|
}
|
|
s.winRegister.Show()
|
|
},
|
|
Style: &balance.ButtonPrimary,
|
|
},
|
|
}
|
|
for _, button := range buttons {
|
|
if check := button.If; check != nil && !check() {
|
|
continue
|
|
}
|
|
|
|
button := button
|
|
btn := ui.NewButton(button.Name, ui.NewLabel(ui.Label{
|
|
Text: button.Name,
|
|
Font: balance.StatusFont,
|
|
}))
|
|
btn.Handle(ui.Click, func(ed ui.EventData) error {
|
|
button.Func()
|
|
return nil
|
|
})
|
|
if button.Style != nil {
|
|
btn.SetStyle(button.Style)
|
|
}
|
|
s.Supervisor.Add(btn)
|
|
frame.Pack(btn, ui.Pack{
|
|
Side: ui.N,
|
|
PadY: 8,
|
|
// Fill: true,
|
|
FillX: true,
|
|
})
|
|
}
|
|
|
|
// Check for update in the background.
|
|
go s.checkUpdate()
|
|
|
|
// Eager load the level in background, no time for load screen.
|
|
go func() {
|
|
if err := s.setupAsync(d); err != nil {
|
|
log.Error("MainScene.setupAsync: %s", err)
|
|
}
|
|
}()
|
|
|
|
// Trigger our "Window Resized" function so we can check if the
|
|
// layout needs to be switched to landscape mode for mobile.
|
|
s.Resized(d.width, d.height)
|
|
|
|
return nil
|
|
}
|
|
|
|
// setupAsync runs background tasks from setup, e.g. eager load
|
|
// chunks of the level for cache.
|
|
func (s *MainScene) setupAsync(d *Doodle) error {
|
|
loadscreen.PreloadAllChunkBitmaps(s.canvas.Chunker())
|
|
return nil
|
|
}
|
|
|
|
// checkUpdate checks for a version update and shows the button.
|
|
func (s *MainScene) checkUpdate() {
|
|
if shmem.OfflineMode {
|
|
log.Info("OfflineMode: skip updates check")
|
|
return
|
|
}
|
|
|
|
info, err := updater.Check()
|
|
if err != nil {
|
|
log.Error(err.Error())
|
|
return
|
|
}
|
|
|
|
if info.IsNewerVersionThan(branding.Version) {
|
|
s.updateInfo = info
|
|
s.updateButton.Show()
|
|
}
|
|
}
|
|
|
|
// SetupDemoLevel configures the wallpaper behind the New screen,
|
|
// which demos a title screen demo level.
|
|
func (s *MainScene) SetupDemoLevel(d *Doodle) error {
|
|
// Set up the background wallpaper canvas.
|
|
s.canvas = uix.NewCanvas(100, false)
|
|
s.canvas.Scrollable = true
|
|
s.canvas.Resize(render.Rect{
|
|
W: d.width,
|
|
H: d.height,
|
|
})
|
|
|
|
s.scripting = scripting.NewSupervisor()
|
|
s.canvas.SetScriptSupervisor(s.scripting)
|
|
|
|
// Title screen level to load. Pick a random level.
|
|
var (
|
|
levelName = balance.DemoLevelName[0]
|
|
fromLevelPack = true
|
|
lvl *level.Level
|
|
)
|
|
if s.LevelFilename != "" {
|
|
// User provided a custom level name, nix the demo levelpack.
|
|
levelName = s.LevelFilename
|
|
fromLevelPack = false
|
|
} else if len(balance.DemoLevelName) > 1 {
|
|
randIndex := rand.Intn(len(balance.DemoLevelName))
|
|
levelName = balance.DemoLevelName[randIndex]
|
|
}
|
|
|
|
// Get the level from the DemoLevelPack?
|
|
if fromLevelPack {
|
|
log.Debug("Initializing titlescreen from DemoLevelPack: %s", balance.DemoLevelPack)
|
|
lp, err := levelpack.LoadFile(balance.DemoLevelPack)
|
|
if err != nil {
|
|
log.Error("Error loading DemoLevelPack(%s): %s", balance.DemoLevelPack, err)
|
|
} else {
|
|
log.Debug("Loading selected level from pack: %s", levelName)
|
|
levelbin, err := lp.GetData("levels/" + levelName)
|
|
if err != nil {
|
|
log.Error("Error getting level from DemoLevelpack(%s#%s): %s",
|
|
balance.DemoLevelPack,
|
|
levelName,
|
|
err,
|
|
)
|
|
} else {
|
|
log.Debug("Parsing loaded level data (%d bytes)", len(levelbin))
|
|
lvl, err = level.FromJSON(levelName, levelbin)
|
|
if err != nil {
|
|
log.Error("DemoLevelPack FromJSON(%s): %s", levelName, err)
|
|
lvl = nil
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// May be a user-provided level.
|
|
if lvl == nil {
|
|
if trylvl, err := level.LoadFile(levelName); err == nil {
|
|
lvl = trylvl
|
|
} else {
|
|
log.Error("Error loading demo level %s: %s", balance.DemoLevelName, err)
|
|
}
|
|
}
|
|
|
|
// If still no level, initialize a basic notebook background.
|
|
if lvl != nil {
|
|
s.canvas.LoadLevel(lvl)
|
|
s.canvas.InstallActors(lvl.Actors)
|
|
|
|
// Load all actor scripts.
|
|
if err := s.scripting.InstallScripts(lvl); err != nil {
|
|
log.Error("Error with title screen level scripts: %s", err)
|
|
}
|
|
|
|
// Run all actors scripts main function to start them off.
|
|
if err := s.canvas.InstallScripts(); err != nil {
|
|
log.Error("Error running actor main() functions: %s", err)
|
|
}
|
|
} else {
|
|
// Create a basic notebook level.
|
|
s.canvas.LoadLevel(&level.Level{
|
|
Chunker: level.NewChunker(100),
|
|
Palette: level.NewPalette(),
|
|
PageType: level.Bounded,
|
|
MaxWidth: 42,
|
|
MaxHeight: 42,
|
|
Wallpaper: "notebook.png",
|
|
})
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Loop the editor scene.
|
|
func (s *MainScene) Loop(d *Doodle, ev *event.State) error {
|
|
s.Supervisor.Loop(ev)
|
|
|
|
if err := s.scripting.Loop(); err != nil {
|
|
log.Error("MainScene.Loop: scripting.Loop: %s", err)
|
|
}
|
|
|
|
// Lazily scroll the canvas around, slowly.
|
|
s.LoopLazyScroll()
|
|
|
|
s.canvas.Loop(ev)
|
|
|
|
if ev.WindowResized {
|
|
s.Resized(d.width, d.height)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Resized the app window.
|
|
func (s *MainScene) Resized(width, height int) {
|
|
log.Info("Resized to %dx%d", width, height)
|
|
|
|
// If the height is not tall enough for the menu, switch to the horizontal layout.
|
|
if height < balance.TitleScreenResponsiveHeight {
|
|
log.Error("Switch to landscape mode")
|
|
s.landscapeMode = true
|
|
} else {
|
|
s.landscapeMode = false
|
|
}
|
|
|
|
s.canvas.Resize(render.Rect{
|
|
W: width,
|
|
H: height,
|
|
})
|
|
}
|
|
|
|
// ButtonFrame returns the main button frame.
|
|
func (s *MainScene) ButtonFrame() *ui.Frame {
|
|
return s.frame
|
|
}
|
|
|
|
// LabelVersion returns the version widget.
|
|
func (s *MainScene) LabelVersion() *ui.Label {
|
|
return s.labelVersion
|
|
}
|
|
|
|
// LabelHint returns the hint widget.
|
|
func (s *MainScene) LabelHint() *ui.Label {
|
|
return s.labelHint
|
|
}
|
|
|
|
// Move things into position for the main menu. This function arranges
|
|
// the Title, Subtitle, Buttons, etc. into screen relative positions every
|
|
// tick. This function sets their 'default' values, but if the window is
|
|
// not tall enough and needs the landscape orientation, positionMenuLandscape()
|
|
// will override these defaults.
|
|
func (s *MainScene) positionMenuPortrait(d *Doodle) {
|
|
// App title label.
|
|
s.labelTitle.MoveTo(render.Point{
|
|
X: (d.width / 2) - (s.labelTitle.Size().W / 2),
|
|
Y: 120,
|
|
})
|
|
|
|
// App subtitle label (byline).
|
|
s.labelSubtitle.MoveTo(render.Point{
|
|
X: (d.width / 2) - (s.labelSubtitle.Size().W / 2),
|
|
Y: s.labelTitle.Point().Y + s.labelTitle.Size().H + 8,
|
|
})
|
|
|
|
// Version label
|
|
s.labelVersion.MoveTo(render.Point{
|
|
X: (d.width) - (s.labelVersion.Size().W) - 20,
|
|
Y: 20,
|
|
})
|
|
|
|
// Hint label.
|
|
s.labelHint.MoveTo(render.Point{
|
|
X: (d.width / 2) - (s.labelHint.Size().W / 2),
|
|
Y: d.height - s.labelHint.Size().H - 32,
|
|
})
|
|
|
|
// Update button.
|
|
s.updateButton.MoveTo(render.Point{
|
|
X: 24,
|
|
Y: d.height - s.updateButton.Size().H - 24,
|
|
})
|
|
|
|
// Button frame.
|
|
s.frame.MoveTo(render.Point{
|
|
X: (d.width / 2) - (s.frame.Size().W / 2),
|
|
Y: 260,
|
|
})
|
|
}
|
|
|
|
func (s *MainScene) positionMenuLandscape(d *Doodle) {
|
|
s.positionMenuPortrait(d)
|
|
|
|
var (
|
|
col1 = render.Rect{
|
|
X: 0,
|
|
Y: 0,
|
|
W: d.width / 2,
|
|
H: d.height,
|
|
}
|
|
col2 = render.Rect{
|
|
X: d.width,
|
|
Y: 0,
|
|
W: d.width - col1.W,
|
|
H: d.height,
|
|
}
|
|
)
|
|
|
|
// Title and subtitle move to the left.
|
|
s.labelTitle.MoveTo(render.Point{
|
|
X: (col1.W / 2) - (s.labelTitle.Size().W / 2),
|
|
Y: s.labelTitle.Point().Y,
|
|
})
|
|
s.labelSubtitle.MoveTo(render.Point{
|
|
X: (col1.W / 2) - (s.labelSubtitle.Size().W / 2),
|
|
Y: s.labelTitle.Point().Y + s.labelTitle.Size().H + 8,
|
|
})
|
|
|
|
// Button frame to the right.
|
|
s.frame.MoveTo(render.Point{
|
|
X: (col2.X+col2.W)/2 - (s.frame.Size().W / 2),
|
|
Y: (d.height / 2) - (s.frame.Size().H / 2),
|
|
})
|
|
}
|
|
|
|
// LoopLazyScroll gently scrolls the title screen demo level, called each Loop.
|
|
func (s *MainScene) LoopLazyScroll() {
|
|
if s.PauseLazyScroll {
|
|
return
|
|
}
|
|
|
|
// The v1 basic sauce algorithm:
|
|
// 1. We scroll diagonally downwards and rightwards.
|
|
// 2. When we scroll downwards far enough, we change direction.
|
|
// Make a zigzag pattern.
|
|
// 3. When we reach the right bound of the level
|
|
// OR some max number of px into an unbounded level:
|
|
// enter a simple ball bouncing mode like a screensaver.
|
|
var (
|
|
zigzagMaxHeight = 512
|
|
maxScrollX = zigzagMaxHeight * 2
|
|
lastScrollValue = s.lazyScrollLastValue
|
|
currentScroll = s.canvas.Scroll
|
|
)
|
|
|
|
// So we have two states:
|
|
// - Zigzag state (default)
|
|
// - Bounce state (when we hit a wall)
|
|
if !s.lazyScrollBounce {
|
|
// Zigzag state.
|
|
s.lazyScrollTrajectory = render.Point{
|
|
X: -1, // down and right
|
|
Y: -1,
|
|
}
|
|
|
|
// When we've gone far enough X, it's also far enough Y.
|
|
if currentScroll.X < -zigzagMaxHeight {
|
|
s.lazyScrollTrajectory.Y = 1 // go back up
|
|
}
|
|
|
|
// Have we gotten stuck in a corner? (ending the zigzag phase, for bounded levels)
|
|
if currentScroll.X < 0 && (currentScroll == lastScrollValue) || currentScroll.X < -maxScrollX {
|
|
log.Debug("LoopLazyScroll: Ending zigzag phase, enter bounce phase")
|
|
s.lazyScrollBounce = true
|
|
s.lazyScrollTrajectory = render.Point{
|
|
X: -1,
|
|
Y: -1,
|
|
}
|
|
}
|
|
} else {
|
|
// Lazy bounce algorithm.
|
|
if currentScroll.Y == lastScrollValue.Y {
|
|
log.Debug("LoopLazyScroll: Hit a floor/ceiling")
|
|
s.lazyScrollTrajectory.Y = -s.lazyScrollTrajectory.Y
|
|
}
|
|
if currentScroll.X == lastScrollValue.X {
|
|
log.Debug("LoopLazyScroll: Hit the side of the map!")
|
|
s.lazyScrollTrajectory.X = -s.lazyScrollTrajectory.X
|
|
}
|
|
}
|
|
|
|
// Check the scroll.
|
|
s.lazyScrollLastValue = currentScroll
|
|
s.canvas.ScrollBy(s.lazyScrollTrajectory)
|
|
}
|
|
|
|
// Draw the pixels on this frame.
|
|
func (s *MainScene) Draw(d *Doodle) error {
|
|
// Clear the canvas and fill it with white.
|
|
d.Engine.Clear(render.White)
|
|
|
|
s.canvas.Present(d.Engine, render.Origin)
|
|
|
|
// Draw a sheen over the level for clarity.
|
|
d.Engine.DrawBox(render.RGBA(255, 255, 254, 96), render.Rect{
|
|
X: 0,
|
|
Y: 0,
|
|
W: d.width,
|
|
H: d.height,
|
|
})
|
|
|
|
// Draw out bounding boxes.
|
|
if DebugCollision {
|
|
for _, actor := range s.canvas.Actors() {
|
|
d.DrawCollisionBox(s.canvas, actor)
|
|
}
|
|
}
|
|
|
|
// Arrange the main widgets by Portrait or Landscape mode.
|
|
if s.landscapeMode {
|
|
s.positionMenuLandscape(d)
|
|
} else {
|
|
s.positionMenuPortrait(d)
|
|
}
|
|
|
|
// App title label.
|
|
s.labelTitle.Present(d.Engine, s.labelTitle.Point())
|
|
|
|
// App subtitle label (byline).
|
|
s.labelSubtitle.Present(d.Engine, s.labelSubtitle.Point())
|
|
|
|
// Version label
|
|
s.labelVersion.Present(d.Engine, s.labelVersion.Point())
|
|
|
|
// Hint label.
|
|
s.labelHint.Present(d.Engine, s.labelHint.Point())
|
|
|
|
// Update button.
|
|
s.updateButton.Present(d.Engine, s.updateButton.Point())
|
|
|
|
s.frame.Compute(d.Engine)
|
|
s.frame.Present(d.Engine, s.frame.Point())
|
|
|
|
// Present supervised windows.
|
|
s.Supervisor.Present(d.Engine)
|
|
|
|
return nil
|
|
}
|
|
|
|
// Destroy the scene.
|
|
func (s *MainScene) Destroy() error {
|
|
log.Debug("MainScene.Destroy(): clean up the demo level canvas")
|
|
s.canvas.Destroy()
|
|
return nil
|
|
}
|