Level Thumbnails on Story Mode Select
* Rework the Story Mode UI to display level thumbnails. * Responsive UI: defaults to wide screen mode and shows 3 levels horizontally but on narrow/mobile display, shows 2 levels per page in portrait. * Add "Tiny" screenshot size (224x126) to fit the Story Mode UI. * Make the pager buttons bigger and more touchable. * Maximize the game window on startup unless the -w option with a specific window resolution is provided.
This commit is contained in:
parent
9cce93f431
commit
1a9706c09f
|
@ -3,7 +3,6 @@ package main
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
@ -47,9 +46,6 @@ func init() {
|
||||||
// Use all the CPU cores for collision detection and other load balanced
|
// Use all the CPU cores for collision detection and other load balanced
|
||||||
// goroutine work in the app.
|
// goroutine work in the app.
|
||||||
runtime.GOMAXPROCS(runtime.NumCPU())
|
runtime.GOMAXPROCS(runtime.NumCPU())
|
||||||
|
|
||||||
// Seed the random number generator.
|
|
||||||
rand.Seed(time.Now().UnixNano())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
@ -154,10 +150,12 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setting a custom resolution?
|
// Setting a custom resolution?
|
||||||
|
var maximize = true
|
||||||
if c.String("window") != "" {
|
if c.String("window") != "" {
|
||||||
if err := setResolution(c.String("window")); err != nil {
|
if err := setResolution(c.String("window")); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
maximize = false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enable feature flags?
|
// Enable feature flags?
|
||||||
|
@ -200,6 +198,12 @@ func main() {
|
||||||
game := doodle.New(c.Bool("debug"), engine)
|
game := doodle.New(c.Bool("debug"), engine)
|
||||||
game.SetupEngine()
|
game.SetupEngine()
|
||||||
|
|
||||||
|
// Start with maximized window unless -w was given.
|
||||||
|
if maximize {
|
||||||
|
log.Info("Maximize window")
|
||||||
|
engine.Maximize()
|
||||||
|
}
|
||||||
|
|
||||||
// Reload usercfg - if their settings.json doesn't exist, we try and pick a
|
// Reload usercfg - if their settings.json doesn't exist, we try and pick a
|
||||||
// default "hide touch hints" based on touch device presence - which is only
|
// default "hide touch hints" based on touch device presence - which is only
|
||||||
// known after SetupEngine.
|
// known after SetupEngine.
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
//go:build shareware
|
||||||
// +build shareware
|
// +build shareware
|
||||||
|
|
||||||
package balance
|
package balance
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
//go:build !shareware
|
||||||
// +build !shareware
|
// +build !shareware
|
||||||
|
|
||||||
package balance
|
package balance
|
||||||
|
|
|
@ -21,10 +21,6 @@ var (
|
||||||
Width = 1024
|
Width = 1024
|
||||||
Height = 768
|
Height = 768
|
||||||
|
|
||||||
// Title screen height needed for the main menu. Phones in landscape
|
|
||||||
// mode will switch to the horizontal layout if less than this height.
|
|
||||||
TitleScreenResponsiveHeight = 600
|
|
||||||
|
|
||||||
// Speed to scroll a canvas with arrow keys in Edit Mode.
|
// Speed to scroll a canvas with arrow keys in Edit Mode.
|
||||||
CanvasScrollSpeed = 8
|
CanvasScrollSpeed = 8
|
||||||
FollowActorMaxScrollSpeed = 64
|
FollowActorMaxScrollSpeed = 64
|
||||||
|
@ -158,9 +154,11 @@ var (
|
||||||
LevelScreenshotLargeFilename = "large.png"
|
LevelScreenshotLargeFilename = "large.png"
|
||||||
LevelScreenshotMediumFilename = "medium.png"
|
LevelScreenshotMediumFilename = "medium.png"
|
||||||
LevelScreenshotSmallFilename = "small.png"
|
LevelScreenshotSmallFilename = "small.png"
|
||||||
|
LevelScreenshotTinyFilename = "tiny.png"
|
||||||
LevelScreenshotLargeSize = render.NewRect(1280, 720)
|
LevelScreenshotLargeSize = render.NewRect(1280, 720)
|
||||||
LevelScreenshotMediumSize = render.NewRect(640, 360)
|
LevelScreenshotMediumSize = render.NewRect(640, 360)
|
||||||
LevelScreenshotSmallSize = render.NewRect(320, 180)
|
LevelScreenshotSmallSize = render.NewRect(320, 180) // Level Properties thumbnail size
|
||||||
|
LevelScreenshotTinySize = render.NewRect(224, 126) // Story Mode thumbnail size
|
||||||
)
|
)
|
||||||
|
|
||||||
// Edit Mode Values
|
// Edit Mode Values
|
||||||
|
|
36
pkg/balance/responsive.go
Normal file
36
pkg/balance/responsive.go
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
package balance
|
||||||
|
|
||||||
|
/*
|
||||||
|
Responsive breakpoints and dimensions for Sketchy Maze.
|
||||||
|
|
||||||
|
Ideas for breakpoints (copying web CSS frameworks):
|
||||||
|
- Mobile up to 768px
|
||||||
|
- Tablet from 769px
|
||||||
|
- Desktop from 1024px
|
||||||
|
- Widescreen from 1216px
|
||||||
|
- FullHD from 1408px
|
||||||
|
*/
|
||||||
|
const (
|
||||||
|
// Title screen height needed for the main menu. Phones in landscape
|
||||||
|
// mode will switch to the horizontal layout if less than this height.
|
||||||
|
TitleScreenResponsiveHeight = 600
|
||||||
|
|
||||||
|
BreakpointMobile = 0 // 0-768
|
||||||
|
BreakpointTablet = 769 // from 769
|
||||||
|
BreakpointDesktop = 1024 // from 1024
|
||||||
|
BreakpointWidescreen = 1216
|
||||||
|
BreakpointFullHD = 1408
|
||||||
|
)
|
||||||
|
|
||||||
|
// IsShortWide is a custom responsive breakpoint to mimic the mobile app in landscape mode like on a Pinephone.
|
||||||
|
//
|
||||||
|
// Parameters are the width and height of the application window (usually the screen if maximized).
|
||||||
|
//
|
||||||
|
// It is used on the MainScene to decide whether the main menu is drawn tall or wide.
|
||||||
|
func IsShortWide(width, height int) bool {
|
||||||
|
return height < TitleScreenResponsiveHeight
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsBreakpointTablet(width, height int) bool {
|
||||||
|
return width >= BreakpointTablet
|
||||||
|
}
|
|
@ -110,6 +110,7 @@ var (
|
||||||
MenuFont = render.Text{
|
MenuFont = render.Text{
|
||||||
Size: 12,
|
Size: 12,
|
||||||
PadX: 4,
|
PadX: 4,
|
||||||
|
PadY: 2,
|
||||||
}
|
}
|
||||||
MenuFontBold = render.Text{
|
MenuFontBold = render.Text{
|
||||||
FontFilename: SansBoldFont,
|
FontFilename: SansBoldFont,
|
||||||
|
@ -124,6 +125,14 @@ var (
|
||||||
PadY: 4,
|
PadY: 4,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pager styles.
|
||||||
|
PagerLargeFont = render.Text{
|
||||||
|
FontFilename: SansBoldFont,
|
||||||
|
Size: 14,
|
||||||
|
PadX: 6,
|
||||||
|
PadY: 4,
|
||||||
|
}
|
||||||
|
|
||||||
// Modal backdrop color.
|
// Modal backdrop color.
|
||||||
ModalBackdrop = render.RGBA(1, 1, 1, 42)
|
ModalBackdrop = render.RGBA(1, 1, 1, 42)
|
||||||
|
|
||||||
|
|
|
@ -7,10 +7,10 @@ Stroke holds temporary pixel data with a shape and color.
|
||||||
|
|
||||||
It is used for myriad purposes:
|
It is used for myriad purposes:
|
||||||
|
|
||||||
- As a staging area for drawing new pixels to the drawing without committing
|
- As a staging area for drawing new pixels to the drawing without committing
|
||||||
them until completed.
|
them until completed.
|
||||||
- As a unit of work for the Undo/Redo History when editing a drawing.
|
- As a unit of work for the Undo/Redo History when editing a drawing.
|
||||||
- As imaginary visual lines superimposed on top of a drawing, for example to
|
- As imaginary visual lines superimposed on top of a drawing, for example to
|
||||||
visualize the link between two doodads or to draw collision hitboxes and other
|
visualize the link between two doodads or to draw collision hitboxes and other
|
||||||
debug lines to the drawing.
|
debug lines to the drawing.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -133,7 +133,7 @@ func SaveCroppedScreenshot(level *level.Level, viewport render.Rect) (string, er
|
||||||
// UpdateLevelScreenshots will generate and embed the screenshot PNGs into the level data.
|
// UpdateLevelScreenshots will generate and embed the screenshot PNGs into the level data.
|
||||||
func UpdateLevelScreenshots(lvl *level.Level, scroll render.Point) error {
|
func UpdateLevelScreenshots(lvl *level.Level, scroll render.Point) error {
|
||||||
// Take screenshots.
|
// Take screenshots.
|
||||||
large, medium, small, err := CreateLevelScreenshots(lvl, scroll)
|
large, medium, small, tiny, err := CreateLevelScreenshots(lvl, scroll)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -143,6 +143,7 @@ func UpdateLevelScreenshots(lvl *level.Level, scroll render.Point) error {
|
||||||
balance.LevelScreenshotLargeFilename: large,
|
balance.LevelScreenshotLargeFilename: large,
|
||||||
balance.LevelScreenshotMediumFilename: medium,
|
balance.LevelScreenshotMediumFilename: medium,
|
||||||
balance.LevelScreenshotSmallFilename: small,
|
balance.LevelScreenshotSmallFilename: small,
|
||||||
|
balance.LevelScreenshotTinyFilename: tiny,
|
||||||
} {
|
} {
|
||||||
var fh = bytes.NewBuffer([]byte{})
|
var fh = bytes.NewBuffer([]byte{})
|
||||||
if err := png.Encode(fh, img); err != nil {
|
if err := png.Encode(fh, img); err != nil {
|
||||||
|
@ -165,7 +166,7 @@ func UpdateLevelScreenshots(lvl *level.Level, scroll render.Point) error {
|
||||||
// will be embedded within the level data itself.
|
// will be embedded within the level data itself.
|
||||||
//
|
//
|
||||||
// Returns the large, medium and small images.
|
// Returns the large, medium and small images.
|
||||||
func CreateLevelScreenshots(lvl *level.Level, scroll render.Point) (large, medium, small image.Image, err error) {
|
func CreateLevelScreenshots(lvl *level.Level, scroll render.Point) (large, medium, small, tiny image.Image, err error) {
|
||||||
// Viewport to screenshot.
|
// Viewport to screenshot.
|
||||||
viewport := render.Rect{
|
viewport := render.Rect{
|
||||||
X: scroll.X,
|
X: scroll.X,
|
||||||
|
@ -183,7 +184,8 @@ func CreateLevelScreenshots(lvl *level.Level, scroll render.Point) (large, mediu
|
||||||
// Scale the medium and small versions.
|
// Scale the medium and small versions.
|
||||||
medium = Scale(large, image.Rect(0, 0, balance.LevelScreenshotMediumSize.W, balance.LevelScreenshotMediumSize.H), draw.ApproxBiLinear)
|
medium = Scale(large, image.Rect(0, 0, balance.LevelScreenshotMediumSize.W, balance.LevelScreenshotMediumSize.H), draw.ApproxBiLinear)
|
||||||
small = Scale(large, image.Rect(0, 0, balance.LevelScreenshotSmallSize.W, balance.LevelScreenshotSmallSize.H), draw.ApproxBiLinear)
|
small = Scale(large, image.Rect(0, 0, balance.LevelScreenshotSmallSize.W, balance.LevelScreenshotSmallSize.H), draw.ApproxBiLinear)
|
||||||
return large, medium, small, nil
|
tiny = Scale(large, image.Rect(0, 0, balance.LevelScreenshotTinySize.W, balance.LevelScreenshotTinySize.H), draw.ApproxBiLinear)
|
||||||
|
return large, medium, small, tiny, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scale down an image. Example:
|
// Scale down an image. Example:
|
||||||
|
|
|
@ -9,10 +9,10 @@ by its index number.
|
||||||
|
|
||||||
This function calls the following:
|
This function calls the following:
|
||||||
|
|
||||||
* Chunker.Inflate(Palette) to update references to the level's pixels to point
|
- Chunker.Inflate(Palette) to update references to the level's pixels to point
|
||||||
to the Swatch entry.
|
to the Swatch entry.
|
||||||
* Actors.Inflate()
|
- Actors.Inflate()
|
||||||
* Palette.Inflate() to load private instance values for the palette subsystem.
|
- Palette.Inflate() to load private instance values for the palette subsystem.
|
||||||
*/
|
*/
|
||||||
func (l *Level) Inflate() {
|
func (l *Level) Inflate() {
|
||||||
// Inflate the chunk metadata to map the pixels to their palette indexes.
|
// Inflate the chunk metadata to map the pixels to their palette indexes.
|
||||||
|
|
|
@ -497,12 +497,11 @@ func (s *MainScene) Resized(width, height int) {
|
||||||
log.Info("Resized to %dx%d", width, height)
|
log.Info("Resized to %dx%d", width, height)
|
||||||
|
|
||||||
// If the height is not tall enough for the menu, switch to the horizontal layout.
|
// If the height is not tall enough for the menu, switch to the horizontal layout.
|
||||||
if height < balance.TitleScreenResponsiveHeight {
|
isLandscape := balance.IsShortWide(width, height)
|
||||||
log.Error("Switch to landscape mode")
|
if isLandscape != s.landscapeMode {
|
||||||
s.landscapeMode = true
|
log.Info("Toggled LandscapeMode to: %+v", isLandscape)
|
||||||
} else {
|
|
||||||
s.landscapeMode = false
|
|
||||||
}
|
}
|
||||||
|
s.landscapeMode = isLandscape
|
||||||
|
|
||||||
s.canvas.Resize(render.Rect{
|
s.canvas.Resize(render.Rect{
|
||||||
W: width,
|
W: width,
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
//go:build !js
|
||||||
// +build !js
|
// +build !js
|
||||||
|
|
||||||
package native
|
package native
|
||||||
|
@ -14,8 +15,8 @@ import (
|
||||||
// OpenURL opens a web browser to the given URL.
|
// OpenURL opens a web browser to the given URL.
|
||||||
//
|
//
|
||||||
// On Linux this will look for xdg-open or try a few common browser names.
|
// On Linux this will look for xdg-open or try a few common browser names.
|
||||||
// On Windows this uses the ``start`` command.
|
// On Windows this uses the “start“ command.
|
||||||
// On MacOS this uses the ``open`` command.
|
// On MacOS this uses the “open“ command.
|
||||||
func OpenURL(url string) {
|
func OpenURL(url string) {
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
go windowsOpenURL(url)
|
go windowsOpenURL(url)
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
//go:build js && wasm
|
||||||
// +build js,wasm
|
// +build js,wasm
|
||||||
|
|
||||||
package native
|
package native
|
||||||
|
|
|
@ -109,3 +109,10 @@ func TextToImage(e render.Engine, text render.Text) (image.Image, error) {
|
||||||
|
|
||||||
return img, nil
|
return img, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set the window to maximized.
|
||||||
|
func MaximizeWindow(e render.Engine) {
|
||||||
|
if sdl, ok := e.(*sdl.Renderer); ok {
|
||||||
|
sdl.Maximize()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -25,3 +25,5 @@ func CopyToClipboard(text string) error {
|
||||||
func CountTextures(e render.Engine) string {
|
func CountTextures(e render.Engine) string {
|
||||||
return "n/a"
|
return "n/a"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func MaximizeWindow(e render.Engine) {}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
//go:build js && wasm
|
||||||
// +build js,wasm
|
// +build js,wasm
|
||||||
|
|
||||||
package native
|
package native
|
||||||
|
@ -17,4 +18,3 @@ func OpenFile(title string, filter string) (string, error) {
|
||||||
})
|
})
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
//go:build !js
|
||||||
// +build !js
|
// +build !js
|
||||||
|
|
||||||
package native
|
package native
|
||||||
|
|
|
@ -7,8 +7,10 @@ RegisterEventHooks attaches the supervisor level event hooks into a JS VM.
|
||||||
|
|
||||||
Names registered:
|
Names registered:
|
||||||
|
|
||||||
- EndLevel(): for a doodad to exit the level. Panics if the OnLevelExit
|
- EndLevel(): for a doodad to exit the level. Panics if the OnLevelExit
|
||||||
handler isn't defined.
|
handler isn't defined.
|
||||||
|
- FailLevel(): for a doodad to cause a level failure.
|
||||||
|
- SetCheckpoint(): update the player's respawn location.
|
||||||
*/
|
*/
|
||||||
func RegisterEventHooks(s *Supervisor, vm *VM) {
|
func RegisterEventHooks(s *Supervisor, vm *VM) {
|
||||||
vm.Set("EndLevel", func() {
|
vm.Set("EndLevel", func() {
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
//+build js,wasm
|
//go:build js && wasm
|
||||||
|
// +build js,wasm
|
||||||
|
|
||||||
package sound
|
package sound
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
package wallpaper
|
package wallpaper
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
|
||||||
"io/ioutil"
|
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
//go:build !js
|
||||||
// +build !js
|
// +build !js
|
||||||
|
|
||||||
package wasm
|
package wasm
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
//go:build js && wasm
|
||||||
// +build js,wasm
|
// +build js,wasm
|
||||||
|
|
||||||
package wasm
|
package wasm
|
||||||
|
|
|
@ -24,11 +24,16 @@ type LevelPack struct {
|
||||||
OnCloseWindow func()
|
OnCloseWindow func()
|
||||||
|
|
||||||
// Internal variables
|
// Internal variables
|
||||||
|
isLandscape bool // wide window rather than tall
|
||||||
window *ui.Window
|
window *ui.Window
|
||||||
tabFrame *ui.TabFrame
|
tabFrame *ui.TabFrame
|
||||||
savegame *savegame.SaveGame
|
savegame *savegame.SaveGame
|
||||||
goldSprite *ui.Image
|
goldSprite *ui.Image
|
||||||
silverSprite *ui.Image
|
silverSprite *ui.Image
|
||||||
|
|
||||||
|
// Button frames for the footer: one with Back+Close, other with Close only.
|
||||||
|
footerWithBackButton *ui.Frame
|
||||||
|
footerWithCloseButton *ui.Frame
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewLevelPackWindow initializes the window.
|
// NewLevelPackWindow initializes the window.
|
||||||
|
@ -37,11 +42,18 @@ func NewLevelPackWindow(config LevelPack) *ui.Window {
|
||||||
var (
|
var (
|
||||||
title = "Select a Level"
|
title = "Select a Level"
|
||||||
|
|
||||||
// size of the popup window
|
// size of the popup window (vertical)
|
||||||
width = 320
|
width = 320
|
||||||
height = 360
|
height = 540
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Are we horizontal?
|
||||||
|
if balance.IsBreakpointTablet(config.Engine.WindowSize()) {
|
||||||
|
width = 720
|
||||||
|
height = 360
|
||||||
|
config.isLandscape = true
|
||||||
|
}
|
||||||
|
|
||||||
// Get the available .levelpack files.
|
// Get the available .levelpack files.
|
||||||
lpFiles, packmap, err := levelpack.LoadAllAvailable()
|
lpFiles, packmap, err := levelpack.LoadAllAvailable()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -70,6 +82,15 @@ func NewLevelPackWindow(config LevelPack) *ui.Window {
|
||||||
Height: height,
|
Height: height,
|
||||||
Background: render.Grey,
|
Background: render.Grey,
|
||||||
})
|
})
|
||||||
|
window.Handle(ui.CloseWindow, func(ed ui.EventData) error {
|
||||||
|
if config.OnCloseWindow != nil {
|
||||||
|
// fn := config.OnCloseWindow
|
||||||
|
// config.OnCloseWindow = nil
|
||||||
|
// fn()
|
||||||
|
config.OnCloseWindow()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
config.window = window
|
config.window = window
|
||||||
|
|
||||||
frame := ui.NewFrame("Window Body Frame")
|
frame := ui.NewFrame("Window Body Frame")
|
||||||
|
@ -99,6 +120,8 @@ func NewLevelPackWindow(config LevelPack) *ui.Window {
|
||||||
config.makeIndexScreen(indexTab, width, height, lpFiles, packmap, func(screen string) {
|
config.makeIndexScreen(indexTab, width, height, lpFiles, packmap, func(screen string) {
|
||||||
// Callback for user choosing a level pack.
|
// Callback for user choosing a level pack.
|
||||||
// Hide the index screen and show the screen for this pack.
|
// Hide the index screen and show the screen for this pack.
|
||||||
|
config.footerWithBackButton.Show()
|
||||||
|
config.footerWithCloseButton.Hide()
|
||||||
tabFrame.SetTab(screen)
|
tabFrame.SetTab(screen)
|
||||||
})
|
})
|
||||||
for _, filename := range lpFiles {
|
for _, filename := range lpFiles {
|
||||||
|
@ -109,8 +132,43 @@ func NewLevelPackWindow(config LevelPack) *ui.Window {
|
||||||
config.makeDetailScreen(tab, width, height, packmap[filename])
|
config.makeDetailScreen(tab, width, height, packmap[filename])
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close button.
|
// Button toolbar at the bottom (Back, Close)
|
||||||
|
config.footerWithBackButton = ui.NewFrame("Button Bar w/ Back Button")
|
||||||
|
config.footerWithCloseButton = ui.NewFrame("Button Bar w/ Close Button Only")
|
||||||
|
window.Place(config.footerWithBackButton, ui.Place{
|
||||||
|
Bottom: 15,
|
||||||
|
Center: true,
|
||||||
|
})
|
||||||
|
window.Place(config.footerWithCloseButton, ui.Place{
|
||||||
|
Bottom: 15,
|
||||||
|
Center: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Back button hidden by default.
|
||||||
|
config.footerWithBackButton.Hide()
|
||||||
|
|
||||||
|
// Back button (conditionally visible)
|
||||||
|
backButton := ui.NewButton("Back", ui.NewLabel(ui.Label{
|
||||||
|
Text: "« Back",
|
||||||
|
Font: balance.MenuFont,
|
||||||
|
}))
|
||||||
|
backButton.SetStyle(&balance.ButtonBabyBlue)
|
||||||
|
backButton.Handle(ui.Click, func(ed ui.EventData) error {
|
||||||
|
tabFrame.SetTab("LevelPacks")
|
||||||
|
config.footerWithBackButton.Hide()
|
||||||
|
config.footerWithCloseButton.Show()
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
config.Supervisor.Add(backButton)
|
||||||
|
config.footerWithBackButton.Pack(backButton, ui.Pack{
|
||||||
|
Side: ui.W,
|
||||||
|
PadX: 4,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Close button (on both versions of the footer frame).
|
||||||
if config.OnCloseWindow != nil {
|
if config.OnCloseWindow != nil {
|
||||||
|
// Create two copies of the button, so we can parent one to each footer frame.
|
||||||
|
makeCloseButton := func() *ui.Button {
|
||||||
closeBtn := ui.NewButton("Close Window", ui.NewLabel(ui.Label{
|
closeBtn := ui.NewButton("Close Window", ui.NewLabel(ui.Label{
|
||||||
Text: "Close",
|
Text: "Close",
|
||||||
Font: balance.MenuFont,
|
Font: balance.MenuFont,
|
||||||
|
@ -120,9 +178,19 @@ func NewLevelPackWindow(config LevelPack) *ui.Window {
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
config.Supervisor.Add(closeBtn)
|
config.Supervisor.Add(closeBtn)
|
||||||
window.Place(closeBtn, ui.Place{
|
return closeBtn
|
||||||
Bottom: 15,
|
}
|
||||||
Center: true,
|
var (
|
||||||
|
button1 = makeCloseButton()
|
||||||
|
button2 = makeCloseButton()
|
||||||
|
)
|
||||||
|
|
||||||
|
// Add it to both frames.
|
||||||
|
config.footerWithBackButton.Pack(button1, ui.Pack{
|
||||||
|
Side: ui.W,
|
||||||
|
})
|
||||||
|
config.footerWithCloseButton.Pack(button2, ui.Pack{
|
||||||
|
Side: ui.W,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -235,7 +303,7 @@ func (config LevelPack) makeIndexScreen(frame *ui.Frame, width, height int,
|
||||||
Pages: pages,
|
Pages: pages,
|
||||||
PerPage: perPage,
|
PerPage: perPage,
|
||||||
MaxPageButtons: maxPageButtons,
|
MaxPageButtons: maxPageButtons,
|
||||||
Font: balance.MenuFont,
|
Font: balance.PagerLargeFont,
|
||||||
OnChange: func(newPage, perPage int) {
|
OnChange: func(newPage, perPage int) {
|
||||||
page = newPage
|
page = newPage
|
||||||
log.Info("Page: %d, %d", page, perPage)
|
log.Info("Page: %d, %d", page, perPage)
|
||||||
|
@ -271,18 +339,32 @@ func (config LevelPack) makeIndexScreen(frame *ui.Frame, width, height int,
|
||||||
// Detail screen for a given levelpack.
|
// Detail screen for a given levelpack.
|
||||||
func (config LevelPack) makeDetailScreen(frame *ui.Frame, width, height int, lp *levelpack.LevelPack) *ui.Frame {
|
func (config LevelPack) makeDetailScreen(frame *ui.Frame, width, height int, lp *levelpack.LevelPack) *ui.Frame {
|
||||||
var (
|
var (
|
||||||
buttonHeight = 40
|
|
||||||
buttonWidth = width - 40
|
|
||||||
|
|
||||||
page = 1
|
page = 1
|
||||||
perPage = 4
|
perPage = 2 // 2 for tall mobile, 3 for landscape
|
||||||
pages = int(
|
pages = int(
|
||||||
math.Ceil(
|
math.Ceil(
|
||||||
float64(len(lp.Levels)) / float64(perPage),
|
float64(len(lp.Levels)) / float64(perPage),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
maxPageButtons = 10
|
maxPageButtons = 10
|
||||||
|
|
||||||
|
buttonHeight = 172
|
||||||
|
buttonWidth = 230
|
||||||
|
thumbnailName = balance.LevelScreenshotTinyFilename
|
||||||
|
thumbnailPadY = 46
|
||||||
)
|
)
|
||||||
|
if config.isLandscape {
|
||||||
|
perPage = 3
|
||||||
|
pages = int(
|
||||||
|
math.Ceil(
|
||||||
|
float64(len(lp.Levels)) / float64(perPage),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
buttonHeight = 172
|
||||||
|
thumbnailName = balance.LevelScreenshotTinyFilename
|
||||||
|
thumbnailPadY = 46
|
||||||
|
buttonWidth = (width / perPage) - 16 // pixel-pushing
|
||||||
|
}
|
||||||
|
|
||||||
// Load the padlock icon for locked levels.
|
// Load the padlock icon for locked levels.
|
||||||
// If not loadable, won't be used in UI.
|
// If not loadable, won't be used in UI.
|
||||||
|
@ -294,42 +376,13 @@ func (config LevelPack) makeDetailScreen(frame *ui.Frame, width, height int, lp
|
||||||
numUnlocked = lp.FreeLevels + numCompleted
|
numUnlocked = lp.FreeLevels + numCompleted
|
||||||
)
|
)
|
||||||
|
|
||||||
/** 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
|
// LevelPack Title label
|
||||||
label := ui.NewLabel(ui.Label{
|
label := ui.NewLabel(ui.Label{
|
||||||
Text: lp.Title,
|
Text: lp.Title,
|
||||||
Font: balance.LabelFont,
|
Font: balance.LabelFont,
|
||||||
})
|
})
|
||||||
frame.Pack(label, ui.Pack{
|
frame.Pack(label, ui.Pack{
|
||||||
Side: ui.NW,
|
Side: ui.N,
|
||||||
PadX: 8,
|
|
||||||
PadY: 2,
|
PadY: 2,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -359,12 +412,36 @@ func (config LevelPack) makeDetailScreen(frame *ui.Frame, width, height int, lp
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Arranging the buttons into groups of 3, vertical or horizontal.
|
||||||
|
var packDir = ui.Pack{
|
||||||
|
Side: ui.N,
|
||||||
|
PadY: 2,
|
||||||
|
}
|
||||||
|
if config.isLandscape {
|
||||||
|
packDir = ui.Pack{
|
||||||
|
Side: ui.W,
|
||||||
|
PadX: 2,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buttonRow := ui.NewFrame("Level Buttons")
|
||||||
|
frame.Pack(buttonRow, ui.Pack{
|
||||||
|
Side: ui.N,
|
||||||
|
PadY: 4,
|
||||||
|
})
|
||||||
|
|
||||||
// Loop over all the levels in this pack.
|
// Loop over all the levels in this pack.
|
||||||
var buttons []*ui.Button
|
var buttons []*ui.Button
|
||||||
for i, level := range lp.Levels {
|
for i, level := range lp.Levels {
|
||||||
level := level
|
level := level
|
||||||
score := config.savegame.GetLevelScore(lp.Filename, level.Filename, level.UUID)
|
score := config.savegame.GetLevelScore(lp.Filename, level.Filename, level.UUID)
|
||||||
|
|
||||||
|
// Load the level zip for its thumbnail image.
|
||||||
|
lvl, err := lp.GetLevel(level.Filename)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Couldn't GetLevel(%s) from LevelPack %s: %s", level.Filename, lp.Filename, err)
|
||||||
|
lvl = nil
|
||||||
|
}
|
||||||
|
|
||||||
// Make a frame to hold a complex button layout.
|
// Make a frame to hold a complex button layout.
|
||||||
btnFrame := ui.NewFrame("Frame")
|
btnFrame := ui.NewFrame("Frame")
|
||||||
btnFrame.Resize(render.Rect{
|
btnFrame.Resize(render.Rect{
|
||||||
|
@ -464,6 +541,16 @@ func (config LevelPack) makeDetailScreen(frame *ui.Frame, width, height int, lp
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Level screenshot.
|
||||||
|
if lvl != nil {
|
||||||
|
if img, err := lvl.GetScreenshotImageAsUIImage(thumbnailName); err == nil {
|
||||||
|
btnFrame.Pack(img, ui.Pack{
|
||||||
|
Side: ui.N,
|
||||||
|
PadY: thumbnailPadY, // TODO: otherwise it overlaps the other labels :(
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
btn := ui.NewButton(level.Filename, btnFrame)
|
btn := ui.NewButton(level.Filename, btnFrame)
|
||||||
btn.Handle(ui.Click, func(ed ui.EventData) error {
|
btn.Handle(ui.Click, func(ed ui.EventData) error {
|
||||||
// Is this level locked?
|
// Is this level locked?
|
||||||
|
@ -484,10 +571,7 @@ func (config LevelPack) makeDetailScreen(frame *ui.Frame, width, height int, lp
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
frame.Pack(btn, ui.Pack{
|
buttonRow.Pack(btn, packDir)
|
||||||
Side: ui.N,
|
|
||||||
PadY: 2,
|
|
||||||
})
|
|
||||||
config.Supervisor.Add(btn)
|
config.Supervisor.Add(btn)
|
||||||
|
|
||||||
if i > perPage-1 {
|
if i > perPage-1 {
|
||||||
|
@ -502,7 +586,7 @@ func (config LevelPack) makeDetailScreen(frame *ui.Frame, width, height int, lp
|
||||||
Pages: pages,
|
Pages: pages,
|
||||||
PerPage: perPage,
|
PerPage: perPage,
|
||||||
MaxPageButtons: maxPageButtons,
|
MaxPageButtons: maxPageButtons,
|
||||||
Font: balance.MenuFont,
|
Font: balance.PagerLargeFont,
|
||||||
OnChange: func(newPage, perPage int) {
|
OnChange: func(newPage, perPage int) {
|
||||||
page = newPage
|
page = newPage
|
||||||
log.Info("Page: %d, %d", page, perPage)
|
log.Info("Page: %d, %d", page, perPage)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user