Finalize basic functionality for Level Packs
* The "Story Mode" button on the MainScene opens the levelpacks window. * Levelpacks from all places are shown (built-in and user files), basic level picker works. * When playing a level out of a levelpack: the PlayScene gets the file data from the zipfile and plays it OK. * When a levelpack level is solved, the "Next Level" button appears on the success modal and hitting Return will advance to the next level in the pack. The final level doesn't show this button. * The user can edit levelpack levels! Clicking the "Edit" button on the Play Mode moves the loaded level over to the EditScene and the user could save it to disk or edit/playtest it perfectly OK! The link to the levelpack is lost upon opening in the editor, so the "Next Level" victory button doesn't appear.
This commit is contained in:
parent
678326540b
commit
6d3ffcd98c
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -10,6 +10,7 @@ wasm/assets/
|
|||
*.wasm
|
||||
*.doodad
|
||||
*.level
|
||||
*.levelpack
|
||||
docker/ubuntu
|
||||
docker/debian
|
||||
docker/fedora
|
||||
|
|
|
@ -121,6 +121,8 @@ def copy_assets():
|
|||
shell("cp -rv deps/vendor/fonts assets/fonts")
|
||||
if not os.path.isdir("assets/levels"):
|
||||
shell("cp -rv deps/masters/levels assets/levels")
|
||||
if not os.path.isdir("assets/levelpacks"):
|
||||
shell("cp -rv deps/masters/levelpacks/levelpacks assets/levelpacks")
|
||||
if not os.path.isdir("rtp"):
|
||||
shell("mkdir -p rtp && cp -rv deps/rtp/* rtp/")
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
"git.kirsle.net/apps/doodle/pkg/branding"
|
||||
"git.kirsle.net/apps/doodle/pkg/enum"
|
||||
"git.kirsle.net/apps/doodle/pkg/keybind"
|
||||
"git.kirsle.net/apps/doodle/pkg/levelpack"
|
||||
"git.kirsle.net/apps/doodle/pkg/log"
|
||||
"git.kirsle.net/apps/doodle/pkg/modal"
|
||||
"git.kirsle.net/apps/doodle/pkg/modal/loadscreen"
|
||||
|
@ -330,3 +331,15 @@ func (d *Doodle) PlayLevel(filename string) error {
|
|||
d.Goto(scene)
|
||||
return nil
|
||||
}
|
||||
|
||||
// PlayFromLevelpack initializes the Play Scene from a level as part of
|
||||
// a levelpack.
|
||||
func (d *Doodle) PlayFromLevelpack(pack levelpack.LevelPack, which levelpack.Level) error {
|
||||
log.Info("Loading level %s from levelpack %s", which.Filename, pack.Title)
|
||||
scene := &PlayScene{
|
||||
Filename: which.Filename,
|
||||
LevelPack: &pack,
|
||||
}
|
||||
d.Goto(scene)
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -98,6 +98,8 @@ func FindFile(filename string) (string, error) {
|
|||
filetype = enum.LevelExt
|
||||
} else if strings.HasSuffix(filename, enum.DoodadExt) {
|
||||
filetype = enum.DoodadExt
|
||||
} else if strings.HasSuffix(filename, enum.LevelPackExt) {
|
||||
filetype = enum.LevelPackExt
|
||||
}
|
||||
|
||||
// Search level directories.
|
||||
|
@ -156,5 +158,33 @@ func FindFile(filename string) (string, error) {
|
|||
}
|
||||
}
|
||||
|
||||
// Search levelpack directories.
|
||||
if filetype == enum.LevelPackExt || filetype == "" {
|
||||
// system levelpacks path
|
||||
candidate := filepath.Join(SystemLevelPacksPath, filename)
|
||||
|
||||
// embedded in binary?
|
||||
if _, err := assets.Asset(candidate); err == nil {
|
||||
return candidate, nil
|
||||
}
|
||||
|
||||
// WASM: can't check the filesystem. Let the caller go ahead and try
|
||||
// loading via ajax request.
|
||||
if runtime.GOOS == "js" {
|
||||
return filename, nil
|
||||
}
|
||||
|
||||
// external system levelpack?
|
||||
if _, err := os.Stat(candidate); !os.IsNotExist(err) {
|
||||
return candidate, nil
|
||||
}
|
||||
|
||||
// user levelpacks
|
||||
candidate = userdir.LevelPackPath(filename)
|
||||
if _, err := os.Stat(candidate); !os.IsNotExist(err) {
|
||||
return candidate, nil
|
||||
}
|
||||
}
|
||||
|
||||
return filename, errors.New("file not found")
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"io/ioutil"
|
||||
"os"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
|
@ -69,6 +70,34 @@ func LoadFile(filename string) (LevelPack, error) {
|
|||
return lp, nil
|
||||
}
|
||||
|
||||
// LoadAllAvailable loads every levelpack visible to the game. Returns
|
||||
// the sorted list of filenames as from ListFiles, plus a deeply loaded
|
||||
// hash map associating the filenames with their data.
|
||||
func LoadAllAvailable() ([]string, map[string]LevelPack, error) {
|
||||
filenames, err := ListFiles()
|
||||
if err != nil {
|
||||
return filenames, nil, err
|
||||
}
|
||||
|
||||
var dictionary = map[string]LevelPack{}
|
||||
for _, filename := range filenames {
|
||||
// Resolve the filename to a definite path on disk.
|
||||
path, err := filesystem.FindFile(filename)
|
||||
if err != nil {
|
||||
return filenames, nil, err
|
||||
}
|
||||
|
||||
lp, err := LoadFile(path)
|
||||
if err != nil {
|
||||
return filenames, nil, err
|
||||
}
|
||||
|
||||
dictionary[filename] = lp
|
||||
}
|
||||
|
||||
return filenames, dictionary, nil
|
||||
}
|
||||
|
||||
// ListFiles lists all the discoverable levelpack files, starting from
|
||||
// the game's built-ins all the way to user levelpacks.
|
||||
func ListFiles() ([]string, error) {
|
||||
|
@ -102,7 +131,21 @@ func ListFiles() ([]string, error) {
|
|||
}
|
||||
}
|
||||
|
||||
return names, nil
|
||||
// Deduplicate strings. Can happen e.g. because assets/ is baked
|
||||
// in to bindata but files also exist there locally.
|
||||
var (
|
||||
dedupe []string
|
||||
seen = map[string]interface{}{}
|
||||
)
|
||||
for _, value := range names {
|
||||
if _, ok := seen[value]; !ok {
|
||||
seen[value] = nil
|
||||
dedupe = append(dedupe, value)
|
||||
}
|
||||
}
|
||||
|
||||
sort.Strings(dedupe)
|
||||
return dedupe, nil
|
||||
}
|
||||
|
||||
// WriteFile saves the metadata to a .json file on disk.
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"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"
|
||||
|
@ -175,6 +176,12 @@ func (s *MainScene) Setup(d *Doodle) error {
|
|||
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())
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
s.winLevelPacks.MoveTo(render.Point{
|
||||
|
@ -183,6 +190,7 @@ func (s *MainScene) Setup(d *Doodle) error {
|
|||
})
|
||||
s.winLevelPacks.Show()
|
||||
},
|
||||
Style: &balance.ButtonBabyBlue,
|
||||
},
|
||||
{
|
||||
Name: "Play a Level",
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"git.kirsle.net/apps/doodle/pkg/doodads"
|
||||
"git.kirsle.net/apps/doodle/pkg/keybind"
|
||||
"git.kirsle.net/apps/doodle/pkg/level"
|
||||
"git.kirsle.net/apps/doodle/pkg/levelpack"
|
||||
"git.kirsle.net/apps/doodle/pkg/log"
|
||||
"git.kirsle.net/apps/doodle/pkg/modal"
|
||||
"git.kirsle.net/apps/doodle/pkg/modal/loadscreen"
|
||||
|
@ -30,6 +31,10 @@ type PlayScene struct {
|
|||
RememberScrollPosition render.Point // for the Editor quality of life
|
||||
SpawnPoint render.Point // if not zero, overrides Start Flag
|
||||
|
||||
// If this level was part of a levelpack. The Play Scene will read it
|
||||
// from the levelpack ZIP file in priority over any other location.
|
||||
LevelPack *levelpack.LevelPack
|
||||
|
||||
// Private variables.
|
||||
d *Doodle
|
||||
drawing *uix.Canvas
|
||||
|
@ -374,6 +379,24 @@ func (s *PlayScene) ShowEndLevelModal(success bool, title, message string) {
|
|||
// Beaten the level?
|
||||
if success {
|
||||
config.OnRetryCheckpoint = nil
|
||||
|
||||
// Are we in a levelpack? Show the "Next Level" button if there is
|
||||
// a sequel to this level.
|
||||
if s.LevelPack != nil {
|
||||
for i, level := range s.LevelPack.Levels {
|
||||
i := i
|
||||
level := level
|
||||
|
||||
if level.Filename == s.Filename && i < len(s.LevelPack.Levels)-1 {
|
||||
// Show "Next" button!
|
||||
config.OnNextLevel = func() {
|
||||
nextLevel := s.LevelPack.Levels[i+1]
|
||||
log.Info("Advance to next level: %s", nextLevel.Filename)
|
||||
s.d.PlayFromLevelpack(*s.LevelPack, nextLevel)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Show the modal.
|
||||
|
@ -595,15 +618,45 @@ func (s *PlayScene) Drawing() *uix.Canvas {
|
|||
}
|
||||
|
||||
// LoadLevel loads a level from disk.
|
||||
//
|
||||
// If the PlayScene was called with a LevelPack, it will check there
|
||||
// first before the usual locations.
|
||||
//
|
||||
// The usual locations are: embedded bindata, ./assets folder on disk,
|
||||
// and user content finally.
|
||||
func (s *PlayScene) LoadLevel(filename string) error {
|
||||
s.Filename = filename
|
||||
|
||||
level, err := level.LoadFile(filename)
|
||||
var (
|
||||
lvl *level.Level
|
||||
err error
|
||||
)
|
||||
|
||||
// Are we playing out of a levelpack?
|
||||
if s.LevelPack != nil {
|
||||
levelbin, err := s.LevelPack.GetData("levels/" + filename)
|
||||
if err != nil {
|
||||
log.Error("Error reading levels/%s from zip: %s", filename, err)
|
||||
}
|
||||
|
||||
lvl, err = level.FromJSON(filename, levelbin)
|
||||
if err != nil {
|
||||
log.Error("PlayScene.LoadLevel(%s) from zipfile: %s", filename, err)
|
||||
}
|
||||
|
||||
log.Info("PlayScene.LoadLevel: found %s in LevelPack zip data", filename)
|
||||
}
|
||||
|
||||
// Try the usual suspects.
|
||||
if lvl == nil {
|
||||
log.Info("PlayScene.LoadLevel: trying the usual places")
|
||||
lvl, err = level.LoadFile(filename)
|
||||
if err != nil {
|
||||
return fmt.Errorf("PlayScene.LoadLevel(%s): %s", filename, err)
|
||||
}
|
||||
}
|
||||
|
||||
s.Level = level
|
||||
s.Level = lvl
|
||||
s.drawing.LoadLevel(s.Level)
|
||||
s.drawing.InstallActors(s.Level.Actors)
|
||||
|
||||
|
|
|
@ -63,8 +63,6 @@ func (s *StoryScene) Setup(d *Doodle) error {
|
|||
s.levelSelectFrame = windows.NewLevelPackWindow(windows.LevelPack{
|
||||
Supervisor: s.supervisor,
|
||||
Engine: d.Engine,
|
||||
|
||||
OnPlayLevel: func(levelpack, filename string) {},
|
||||
})
|
||||
s.levelSelectFrame.Show()
|
||||
|
||||
|
|
|
@ -30,6 +30,7 @@ var (
|
|||
const (
|
||||
extLevel = ".level"
|
||||
extDoodad = ".doodad"
|
||||
extLevelPack = ".levelpack"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
@ -69,6 +70,11 @@ func DoodadPath(filename string) string {
|
|||
return resolvePath(DoodadDirectory, filename, extDoodad)
|
||||
}
|
||||
|
||||
// LevelPackPath returns the user's levelpacks directory.
|
||||
func LevelPackPath(filename string) string {
|
||||
return resolvePath(LevelPackDirectory, filename, extLevelPack)
|
||||
}
|
||||
|
||||
// CacheFilename returns a path to a file in the cache folder. Send in path
|
||||
// components and not literal slashes, like
|
||||
// CacheFilename("images", "chunks", "id.bmp")
|
||||
|
|
|
@ -17,11 +17,11 @@ type LevelPack struct {
|
|||
Engine render.Engine
|
||||
|
||||
// Callback functions.
|
||||
OnPlayLevel func(levelpack, filename string)
|
||||
OnPlayLevel func(pack levelpack.LevelPack, level levelpack.Level)
|
||||
|
||||
// Internal variables
|
||||
window *ui.Window
|
||||
gotoIndex func() // return to index screen
|
||||
tabFrame *ui.TabFrame
|
||||
}
|
||||
|
||||
// NewLevelPackWindow initializes the window.
|
||||
|
@ -35,6 +35,15 @@ func NewLevelPackWindow(config LevelPack) *ui.Window {
|
|||
height = 300
|
||||
)
|
||||
|
||||
// Get the available .levelpack files.
|
||||
lpFiles, packmap, err := levelpack.LoadAllAvailable()
|
||||
if err != nil {
|
||||
log.Error("Couldn't list levelpack files: %s", err)
|
||||
}
|
||||
|
||||
log.Error("lpFiles: %+v", lpFiles)
|
||||
log.Error("packmap: %+v", packmap)
|
||||
|
||||
window := ui.NewWindow(title)
|
||||
window.SetButtons(ui.CloseButton)
|
||||
window.Configure(ui.Config{
|
||||
|
@ -51,32 +60,53 @@ func NewLevelPackWindow(config LevelPack) *ui.Window {
|
|||
Expand: true,
|
||||
})
|
||||
|
||||
// We'll divide this window into "Screens", where the default
|
||||
// screen shows the available level packs and then each level
|
||||
// pack gets its own screen showing its levels.
|
||||
var indexScreen *ui.Frame
|
||||
config.gotoIndex = func() {
|
||||
indexScreen.Show()
|
||||
}
|
||||
indexScreen = config.makeIndexScreen(width, height, func(screen *ui.Frame) {
|
||||
// Use a TabFrame to organize the "screens" of this window.
|
||||
// The default screen is a pager for LevelPacks,
|
||||
// And each LevelPack's screen is a pager for its Levels.
|
||||
tabFrame := ui.NewTabFrame("Screens Manager")
|
||||
tabFrame.SetTabsHidden(true)
|
||||
window.Pack(tabFrame, ui.Pack{
|
||||
Side: ui.N,
|
||||
FillX: true,
|
||||
})
|
||||
config.tabFrame = tabFrame
|
||||
|
||||
// Make the tabs.
|
||||
indexTab := tabFrame.AddTab("LevelPacks", ui.NewLabel(ui.Label{
|
||||
Text: "LevelPacks",
|
||||
Font: balance.TabFont,
|
||||
}))
|
||||
config.makeIndexScreen(indexTab, width, height, lpFiles, packmap, func(screen string) {
|
||||
// Callback for user choosing a level pack.
|
||||
// Hide the index screen and show the screen for this pack.
|
||||
indexScreen.Hide()
|
||||
screen.Show()
|
||||
})
|
||||
window.Pack(indexScreen, ui.Pack{
|
||||
Side: ui.N,
|
||||
Fill: true,
|
||||
Expand: true,
|
||||
log.Info("Called for tab: %s", screen)
|
||||
tabFrame.SetTab(screen)
|
||||
})
|
||||
for _, filename := range lpFiles {
|
||||
tab := tabFrame.AddTab(filename, ui.NewLabel(ui.Label{
|
||||
Text: filename,
|
||||
Font: balance.TabFont,
|
||||
}))
|
||||
config.makeDetailScreen(tab, width, height, packmap[filename])
|
||||
}
|
||||
|
||||
// indexTab.Resize(render.Rect{
|
||||
// W: width-4,
|
||||
// H: height-4,
|
||||
// })
|
||||
|
||||
tabFrame.Supervise(config.Supervisor)
|
||||
window.Supervise(config.Supervisor)
|
||||
window.Hide()
|
||||
return window
|
||||
}
|
||||
|
||||
// Index screen for the LevelPack window.
|
||||
func (config LevelPack) makeIndexScreen(width, height int, onChoose func(*ui.Frame)) *ui.Frame {
|
||||
/* Index screen for the LevelPack window.
|
||||
|
||||
frame: a TabFrame to populate
|
||||
*/
|
||||
func (config LevelPack) makeIndexScreen(frame *ui.Frame, width, height int,
|
||||
lpFiles []string, packmap map[string]levelpack.LevelPack, onChoose func(string)) {
|
||||
var (
|
||||
buttonHeight = 60 // height of each LevelPack button
|
||||
buttonWidth = width - 40
|
||||
|
@ -87,7 +117,6 @@ func (config LevelPack) makeIndexScreen(width, height int, onChoose func(*ui.Fra
|
|||
perPage = 3
|
||||
maxPageButtons = 10
|
||||
)
|
||||
frame := ui.NewFrame("Index Screen")
|
||||
|
||||
label := ui.NewLabel(ui.Label{
|
||||
Text: "Select from a Level Pack below:",
|
||||
|
@ -99,12 +128,6 @@ func (config LevelPack) makeIndexScreen(width, height int, onChoose func(*ui.Fra
|
|||
PadY: 8,
|
||||
})
|
||||
|
||||
// Get the available .levelpack files.
|
||||
lpFiles, err := levelpack.ListFiles()
|
||||
if err != nil {
|
||||
log.Error("Couldn't list levelpack files: %s", err)
|
||||
}
|
||||
|
||||
pages = int(
|
||||
math.Ceil(
|
||||
float64(len(lpFiles)) / float64(perPage),
|
||||
|
@ -113,12 +136,12 @@ func (config LevelPack) makeIndexScreen(width, height int, onChoose func(*ui.Fra
|
|||
|
||||
var buttons []*ui.Button
|
||||
for i, filename := range lpFiles {
|
||||
lp, err := levelpack.LoadFile(filename)
|
||||
if err != nil {
|
||||
log.Error("Couldn't read %s: %s", filename, err)
|
||||
filename := filename
|
||||
lp, ok := packmap[filename]
|
||||
if !ok {
|
||||
log.Error("Couldn't find %s in packmap!", filename)
|
||||
continue
|
||||
}
|
||||
_ = lp
|
||||
|
||||
// Make a frame to hold a complex button layout.
|
||||
btnFrame := ui.NewFrame("Frame")
|
||||
|
@ -157,19 +180,9 @@ func (config LevelPack) makeIndexScreen(width, height int, onChoose func(*ui.Fra
|
|||
Side: ui.N,
|
||||
})
|
||||
|
||||
// Generate the detail screen (Frame) for this level pack.
|
||||
// Should the user click our button, this screen is shown.
|
||||
screen := config.makeDetailScreen(width, height, lp)
|
||||
screen.Hide()
|
||||
config.window.Pack(screen, ui.Pack{
|
||||
Side: ui.N,
|
||||
Fill: true,
|
||||
Expand: true,
|
||||
})
|
||||
|
||||
button := ui.NewButton(filename, btnFrame)
|
||||
button.Handle(ui.Click, func(ed ui.EventData) error {
|
||||
onChoose(screen)
|
||||
onChoose(filename)
|
||||
return nil
|
||||
})
|
||||
|
||||
|
@ -179,7 +192,7 @@ func (config LevelPack) makeIndexScreen(width, height int, onChoose func(*ui.Fra
|
|||
})
|
||||
config.Supervisor.Add(button)
|
||||
|
||||
if i > perPage {
|
||||
if i > perPage-1 {
|
||||
button.Hide()
|
||||
}
|
||||
buttons = append(buttons, button)
|
||||
|
@ -189,6 +202,170 @@ func (config LevelPack) makeIndexScreen(width, height int, onChoose func(*ui.Fra
|
|||
Name: "LevelPack Pager",
|
||||
Page: page,
|
||||
Pages: pages,
|
||||
PerPage: perPage,
|
||||
MaxPageButtons: maxPageButtons,
|
||||
Font: balance.MenuFont,
|
||||
OnChange: func(newPage, perPage int) {
|
||||
page = newPage
|
||||
log.Info("Page: %d, %d", page, perPage)
|
||||
|
||||
// Re-evaluate which rows are shown/hidden for the page we're on.
|
||||
var (
|
||||
minRow = (page - 1) * perPage
|
||||
visible = 0
|
||||
)
|
||||
for i, row := range buttons {
|
||||
if visible >= perPage {
|
||||
row.Hide()
|
||||
continue
|
||||
}
|
||||
|
||||
if i < minRow {
|
||||
row.Hide()
|
||||
} else {
|
||||
row.Show()
|
||||
visible++
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
pager.Compute(config.Engine)
|
||||
pager.Supervise(config.Supervisor)
|
||||
frame.Pack(pager, ui.Pack{
|
||||
Side: ui.N,
|
||||
PadY: 2,
|
||||
})
|
||||
}
|
||||
|
||||
// Detail screen for a given levelpack.
|
||||
func (config LevelPack) makeDetailScreen(frame *ui.Frame, width, height int, lp levelpack.LevelPack) *ui.Frame {
|
||||
var (
|
||||
buttonHeight = 40
|
||||
buttonWidth = width - 40
|
||||
|
||||
page = 1
|
||||
perPage = 3
|
||||
pages = int(
|
||||
math.Ceil(
|
||||
float64(len(lp.Levels)) / float64(perPage),
|
||||
),
|
||||
)
|
||||
maxPageButtons = 10
|
||||
)
|
||||
|
||||
/** Back Button */
|
||||
backButton := ui.NewButton("Back", ui.NewLabel(ui.Label{
|
||||
Text: "< Back",
|
||||
Font: ui.MenuFont,
|
||||
}))
|
||||
backButton.SetStyle(&balance.ButtonBabyBlue)
|
||||
backButton.Handle(ui.Click, func(ed ui.EventData) error {
|
||||
config.tabFrame.SetTab("LevelPacks")
|
||||
return nil
|
||||
})
|
||||
config.Supervisor.Add(backButton)
|
||||
frame.Pack(backButton, ui.Pack{
|
||||
Side: ui.NE,
|
||||
PadY: 2,
|
||||
PadX: 6,
|
||||
})
|
||||
|
||||
// Spacer: the back button is position NW and the rest against N
|
||||
// so may overlap.
|
||||
spacer := ui.NewFrame("Spacer")
|
||||
spacer.Configure(ui.Config{
|
||||
Width: 64,
|
||||
Height: 30,
|
||||
})
|
||||
frame.Pack(spacer, ui.Pack{
|
||||
Side: ui.N,
|
||||
})
|
||||
|
||||
// LevelPack Title label
|
||||
label := ui.NewLabel(ui.Label{
|
||||
Text: lp.Title,
|
||||
Font: balance.LabelFont,
|
||||
})
|
||||
frame.Pack(label, ui.Pack{
|
||||
Side: ui.NW,
|
||||
PadX: 8,
|
||||
PadY: 2,
|
||||
})
|
||||
|
||||
// Description
|
||||
if lp.Description != "" {
|
||||
label := ui.NewLabel(ui.Label{
|
||||
Text: lp.Description,
|
||||
Font: balance.MenuFont,
|
||||
})
|
||||
frame.Pack(label, ui.Pack{
|
||||
Side: ui.N,
|
||||
PadX: 8,
|
||||
PadY: 2,
|
||||
})
|
||||
}
|
||||
|
||||
// Byline
|
||||
if lp.Author != "" {
|
||||
label := ui.NewLabel(ui.Label{
|
||||
Text: "by " + lp.Author,
|
||||
Font: balance.MenuFont,
|
||||
})
|
||||
frame.Pack(label, ui.Pack{
|
||||
Side: ui.N,
|
||||
PadX: 8,
|
||||
PadY: 2,
|
||||
})
|
||||
}
|
||||
|
||||
// Loop over all the levels in this pack.
|
||||
var buttons []*ui.Button
|
||||
for i, level := range lp.Levels {
|
||||
level := level
|
||||
|
||||
// Make a frame to hold a complex button layout.
|
||||
btnFrame := ui.NewFrame("Frame")
|
||||
btnFrame.Resize(render.Rect{
|
||||
W: buttonWidth,
|
||||
H: buttonHeight,
|
||||
})
|
||||
|
||||
title := ui.NewLabel(ui.Label{
|
||||
Text: level.Title,
|
||||
Font: balance.LabelFont,
|
||||
})
|
||||
btnFrame.Pack(title, ui.Pack{
|
||||
Side: ui.NW,
|
||||
})
|
||||
|
||||
btn := ui.NewButton(level.Filename, btnFrame)
|
||||
btn.Handle(ui.Click, func(ed ui.EventData) error {
|
||||
// Play Level
|
||||
if config.OnPlayLevel != nil {
|
||||
config.OnPlayLevel(lp, level)
|
||||
} else {
|
||||
log.Error("LevelPack Window: OnPlayLevel callback not ready")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
frame.Pack(btn, ui.Pack{
|
||||
Side: ui.N,
|
||||
PadY: 2,
|
||||
})
|
||||
config.Supervisor.Add(btn)
|
||||
|
||||
if i > perPage-1 {
|
||||
btn.Hide()
|
||||
}
|
||||
buttons = append(buttons, btn)
|
||||
}
|
||||
|
||||
pager := ui.NewPager(ui.Pager{
|
||||
Name: "Level Pager",
|
||||
Page: page,
|
||||
Pages: pages,
|
||||
PerPage: perPage,
|
||||
MaxPageButtons: maxPageButtons,
|
||||
Font: balance.MenuFont,
|
||||
OnChange: func(newPage, perPage int) {
|
||||
|
@ -224,35 +401,3 @@ func (config LevelPack) makeIndexScreen(width, height int, onChoose func(*ui.Fra
|
|||
|
||||
return frame
|
||||
}
|
||||
|
||||
// Detail screen for a given levelpack.
|
||||
func (config LevelPack) makeDetailScreen(width, height int, lp levelpack.LevelPack) *ui.Frame {
|
||||
frame := ui.NewFrame("Detail Screen")
|
||||
|
||||
label := ui.NewLabel(ui.Label{
|
||||
Text: "HELLO " + lp.Title,
|
||||
Font: balance.LabelFont,
|
||||
})
|
||||
frame.Pack(label, ui.Pack{
|
||||
Side: ui.N,
|
||||
PadX: 8,
|
||||
PadY: 8,
|
||||
})
|
||||
|
||||
backButton := ui.NewButton("Back", ui.NewLabel(ui.Label{
|
||||
Text: "< Back to Level Packs",
|
||||
Font: ui.MenuFont,
|
||||
}))
|
||||
backButton.Handle(ui.Click, func(ed ui.EventData) error {
|
||||
frame.Hide()
|
||||
config.gotoIndex()
|
||||
return nil
|
||||
})
|
||||
config.Supervisor.Add(backButton)
|
||||
frame.Pack(backButton, ui.Pack{
|
||||
Side: ui.N,
|
||||
PadY: 2,
|
||||
})
|
||||
|
||||
return frame
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user