diff --git a/cmd/doodle/main.go b/cmd/doodle/main.go index 8a8ed99..180f820 100644 --- a/cmd/doodle/main.go +++ b/cmd/doodle/main.go @@ -3,7 +3,6 @@ package main import ( "errors" "fmt" - "math/rand" "os" "regexp" "runtime" @@ -47,9 +46,6 @@ func init() { // Use all the CPU cores for collision detection and other load balanced // goroutine work in the app. runtime.GOMAXPROCS(runtime.NumCPU()) - - // Seed the random number generator. - rand.Seed(time.Now().UnixNano()) } func main() { @@ -154,10 +150,12 @@ func main() { } // Setting a custom resolution? + var maximize = true if c.String("window") != "" { if err := setResolution(c.String("window")); err != nil { panic(err) } + maximize = false } // Enable feature flags? @@ -200,6 +198,12 @@ func main() { game := doodle.New(c.Bool("debug"), engine) 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 // default "hide touch hints" based on touch device presence - which is only // known after SetupEngine. diff --git a/pkg/balance/flag_free.go b/pkg/balance/flag_free.go index af5c4f4..725f5d3 100644 --- a/pkg/balance/flag_free.go +++ b/pkg/balance/flag_free.go @@ -1,3 +1,4 @@ +//go:build shareware // +build shareware package balance diff --git a/pkg/balance/flag_paid.go b/pkg/balance/flag_paid.go index 52a40aa..25482ae 100644 --- a/pkg/balance/flag_paid.go +++ b/pkg/balance/flag_paid.go @@ -1,3 +1,4 @@ +//go:build !shareware // +build !shareware package balance diff --git a/pkg/balance/numbers.go b/pkg/balance/numbers.go index 1644a4b..7e682b8 100644 --- a/pkg/balance/numbers.go +++ b/pkg/balance/numbers.go @@ -21,10 +21,6 @@ var ( Width = 1024 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. CanvasScrollSpeed = 8 FollowActorMaxScrollSpeed = 64 @@ -158,9 +154,11 @@ var ( LevelScreenshotLargeFilename = "large.png" LevelScreenshotMediumFilename = "medium.png" LevelScreenshotSmallFilename = "small.png" + LevelScreenshotTinyFilename = "tiny.png" LevelScreenshotLargeSize = render.NewRect(1280, 720) 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 diff --git a/pkg/balance/responsive.go b/pkg/balance/responsive.go new file mode 100644 index 0000000..e919dc1 --- /dev/null +++ b/pkg/balance/responsive.go @@ -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 +} diff --git a/pkg/balance/theme.go b/pkg/balance/theme.go index 78bce9f..9ca64e7 100644 --- a/pkg/balance/theme.go +++ b/pkg/balance/theme.go @@ -110,6 +110,7 @@ var ( MenuFont = render.Text{ Size: 12, PadX: 4, + PadY: 2, } MenuFontBold = render.Text{ FontFilename: SansBoldFont, @@ -124,6 +125,14 @@ var ( PadY: 4, } + // Pager styles. + PagerLargeFont = render.Text{ + FontFilename: SansBoldFont, + Size: 14, + PadX: 6, + PadY: 4, + } + // Modal backdrop color. ModalBackdrop = render.RGBA(1, 1, 1, 42) diff --git a/pkg/config.go b/pkg/config.go index 5a11c63..aa25d8a 100644 --- a/pkg/config.go +++ b/pkg/config.go @@ -21,9 +21,9 @@ EditFile opens a drawing file (Level or Doodad) in the EditorScene. The filename can be one of the following: - - A simple filename with no path separators in it and/or no file extension. - - An absolute path beginning with "/" - - A relative path beginning with "./" + - A simple filename with no path separators in it and/or no file extension. + - An absolute path beginning with "/" + - A relative path beginning with "./" If the filename has an extension (`.level` or `.doodad`), that will disambiguate how to find the file and which mode to start the EditorMode in. Otherwise, the diff --git a/pkg/drawtool/stroke.go b/pkg/drawtool/stroke.go index d16d433..9408e80 100644 --- a/pkg/drawtool/stroke.go +++ b/pkg/drawtool/stroke.go @@ -7,12 +7,12 @@ Stroke holds temporary pixel data with a shape and color. It is used for myriad purposes: -- As a staging area for drawing new pixels to the drawing without committing - them until completed. -- 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 - visualize the link between two doodads or to draw collision hitboxes and other - debug lines to the drawing. + - As a staging area for drawing new pixels to the drawing without committing + them until completed. + - 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 + visualize the link between two doodads or to draw collision hitboxes and other + debug lines to the drawing. */ type Stroke struct { ID int // Unique ID per each stroke diff --git a/pkg/level/giant_screenshot/regular_screenshot.go b/pkg/level/giant_screenshot/regular_screenshot.go index 796ead9..b6e73c8 100644 --- a/pkg/level/giant_screenshot/regular_screenshot.go +++ b/pkg/level/giant_screenshot/regular_screenshot.go @@ -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. func UpdateLevelScreenshots(lvl *level.Level, scroll render.Point) error { // Take screenshots. - large, medium, small, err := CreateLevelScreenshots(lvl, scroll) + large, medium, small, tiny, err := CreateLevelScreenshots(lvl, scroll) if err != nil { return err } @@ -143,6 +143,7 @@ func UpdateLevelScreenshots(lvl *level.Level, scroll render.Point) error { balance.LevelScreenshotLargeFilename: large, balance.LevelScreenshotMediumFilename: medium, balance.LevelScreenshotSmallFilename: small, + balance.LevelScreenshotTinyFilename: tiny, } { var fh = bytes.NewBuffer([]byte{}) 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. // // 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 := render.Rect{ X: scroll.X, @@ -183,7 +184,8 @@ func CreateLevelScreenshots(lvl *level.Level, scroll render.Point) (large, mediu // Scale the medium and small versions. 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) - 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: diff --git a/pkg/level/inflate.go b/pkg/level/inflate.go index 7f60aed..2d46538 100644 --- a/pkg/level/inflate.go +++ b/pkg/level/inflate.go @@ -9,10 +9,10 @@ by its index number. This function calls the following: -* Chunker.Inflate(Palette) to update references to the level's pixels to point - to the Swatch entry. -* Actors.Inflate() -* Palette.Inflate() to load private instance values for the palette subsystem. + - Chunker.Inflate(Palette) to update references to the level's pixels to point + to the Swatch entry. + - Actors.Inflate() + - Palette.Inflate() to load private instance values for the palette subsystem. */ func (l *Level) Inflate() { // Inflate the chunk metadata to map the pixels to their palette indexes. diff --git a/pkg/main_scene.go b/pkg/main_scene.go index 1edb7d2..ccc94d4 100644 --- a/pkg/main_scene.go +++ b/pkg/main_scene.go @@ -497,12 +497,11 @@ 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 + isLandscape := balance.IsShortWide(width, height) + if isLandscape != s.landscapeMode { + log.Info("Toggled LandscapeMode to: %+v", isLandscape) } + s.landscapeMode = isLandscape s.canvas.Resize(render.Rect{ W: width, diff --git a/pkg/native/browser.go b/pkg/native/browser.go index 52521df..5aa8d35 100644 --- a/pkg/native/browser.go +++ b/pkg/native/browser.go @@ -1,3 +1,4 @@ +//go:build !js // +build !js package native @@ -14,8 +15,8 @@ import ( // 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 Windows this uses the ``start`` command. -// On MacOS this uses the ``open`` command. +// On Windows this uses the “start“ command. +// On MacOS this uses the “open“ command. func OpenURL(url string) { if runtime.GOOS == "windows" { go windowsOpenURL(url) diff --git a/pkg/native/browser_wasm.go b/pkg/native/browser_wasm.go index d26e9e2..4b617a6 100644 --- a/pkg/native/browser_wasm.go +++ b/pkg/native/browser_wasm.go @@ -1,3 +1,4 @@ +//go:build js && wasm // +build js,wasm package native diff --git a/pkg/native/engine_sdl.go b/pkg/native/engine_sdl.go index 404592c..ee810ab 100644 --- a/pkg/native/engine_sdl.go +++ b/pkg/native/engine_sdl.go @@ -109,3 +109,10 @@ func TextToImage(e render.Engine, text render.Text) (image.Image, error) { return img, nil } + +// Set the window to maximized. +func MaximizeWindow(e render.Engine) { + if sdl, ok := e.(*sdl.Renderer); ok { + sdl.Maximize() + } +} diff --git a/pkg/native/engine_wasm.go b/pkg/native/engine_wasm.go index ea196c6..f93c8f9 100644 --- a/pkg/native/engine_wasm.go +++ b/pkg/native/engine_wasm.go @@ -25,3 +25,5 @@ func CopyToClipboard(text string) error { func CountTextures(e render.Engine) string { return "n/a" } + +func MaximizeWindow(e render.Engine) {} diff --git a/pkg/native/file_dialog_fallback.go b/pkg/native/file_dialog_fallback.go index 09872c5..82a1464 100644 --- a/pkg/native/file_dialog_fallback.go +++ b/pkg/native/file_dialog_fallback.go @@ -1,3 +1,4 @@ +//go:build js && wasm // +build js,wasm package native @@ -17,4 +18,3 @@ func OpenFile(title string, filter string) (string, error) { }) return "", nil } - diff --git a/pkg/native/file_dialog_native.go b/pkg/native/file_dialog_native.go index 0a7e920..5ba9576 100644 --- a/pkg/native/file_dialog_native.go +++ b/pkg/native/file_dialog_native.go @@ -1,3 +1,4 @@ +//go:build !js // +build !js package native diff --git a/pkg/native/file_dialogs.go b/pkg/native/file_dialogs.go index 5176b9c..7b39851 100644 --- a/pkg/native/file_dialogs.go +++ b/pkg/native/file_dialogs.go @@ -10,4 +10,4 @@ var ( // If false, a fallback uses the developer shell Prompt() // to ask for a file name. FileDialogsReady bool -) \ No newline at end of file +) diff --git a/pkg/scripting/supervisor_events.go b/pkg/scripting/supervisor_events.go index cf40dcf..c612272 100644 --- a/pkg/scripting/supervisor_events.go +++ b/pkg/scripting/supervisor_events.go @@ -7,8 +7,10 @@ RegisterEventHooks attaches the supervisor level event hooks into a JS VM. Names registered: -- EndLevel(): for a doodad to exit the level. Panics if the OnLevelExit - handler isn't defined. + - EndLevel(): for a doodad to exit the level. Panics if the OnLevelExit + 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) { vm.Set("EndLevel", func() { diff --git a/pkg/sound/sound_wasm.go b/pkg/sound/sound_wasm.go index 1cd457d..4874bd3 100644 --- a/pkg/sound/sound_wasm.go +++ b/pkg/sound/sound_wasm.go @@ -1,4 +1,5 @@ -//+build js,wasm +//go:build js && wasm +// +build js,wasm package sound diff --git a/pkg/wallpaper/ingest.go b/pkg/wallpaper/ingest.go index 66f2c88..b97bad9 100644 --- a/pkg/wallpaper/ingest.go +++ b/pkg/wallpaper/ingest.go @@ -1,9 +1,9 @@ package wallpaper import ( - "os" - "io/ioutil" "encoding/base64" + "io/ioutil" + "os" ) /* @@ -30,4 +30,4 @@ func FileToB64(filename string) (string, error) { } return b64, nil -} \ No newline at end of file +} diff --git a/pkg/wasm/localstorage.go b/pkg/wasm/localstorage.go index 96df402..df36c75 100644 --- a/pkg/wasm/localstorage.go +++ b/pkg/wasm/localstorage.go @@ -1,3 +1,4 @@ +//go:build !js // +build !js package wasm diff --git a/pkg/wasm/localstorage_js.go b/pkg/wasm/localstorage_js.go index 5783d1f..fb2d0f9 100644 --- a/pkg/wasm/localstorage_js.go +++ b/pkg/wasm/localstorage_js.go @@ -1,3 +1,4 @@ +//go:build js && wasm // +build js,wasm package wasm diff --git a/pkg/windows/levelpack_open.go b/pkg/windows/levelpack_open.go index 37c72e2..2a2cad2 100644 --- a/pkg/windows/levelpack_open.go +++ b/pkg/windows/levelpack_open.go @@ -24,11 +24,16 @@ type LevelPack struct { OnCloseWindow func() // Internal variables + isLandscape bool // wide window rather than tall window *ui.Window tabFrame *ui.TabFrame savegame *savegame.SaveGame goldSprite *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. @@ -37,11 +42,18 @@ func NewLevelPackWindow(config LevelPack) *ui.Window { var ( title = "Select a Level" - // size of the popup window + // size of the popup window (vertical) 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. lpFiles, packmap, err := levelpack.LoadAllAvailable() if err != nil { @@ -70,6 +82,15 @@ func NewLevelPackWindow(config LevelPack) *ui.Window { Height: height, 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 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) { // Callback for user choosing a level pack. // Hide the index screen and show the screen for this pack. + config.footerWithBackButton.Show() + config.footerWithCloseButton.Hide() tabFrame.SetTab(screen) }) for _, filename := range lpFiles { @@ -109,20 +132,65 @@ func NewLevelPackWindow(config LevelPack) *ui.Window { 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 { - closeBtn := ui.NewButton("Close Window", ui.NewLabel(ui.Label{ - Text: "Close", - Font: balance.MenuFont, - })) - closeBtn.Handle(ui.Click, func(ed ui.EventData) error { - config.OnCloseWindow() - return 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{ + Text: "Close", + Font: balance.MenuFont, + })) + closeBtn.Handle(ui.Click, func(ed ui.EventData) error { + config.OnCloseWindow() + return nil + }) + config.Supervisor.Add(closeBtn) + return closeBtn + } + var ( + button1 = makeCloseButton() + button2 = makeCloseButton() + ) + + // Add it to both frames. + config.footerWithBackButton.Pack(button1, ui.Pack{ + Side: ui.W, }) - config.Supervisor.Add(closeBtn) - window.Place(closeBtn, ui.Place{ - Bottom: 15, - Center: true, + 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, PerPage: perPage, MaxPageButtons: maxPageButtons, - Font: balance.MenuFont, + Font: balance.PagerLargeFont, OnChange: func(newPage, perPage int) { page = newPage 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. func (config LevelPack) makeDetailScreen(frame *ui.Frame, width, height int, lp *levelpack.LevelPack) *ui.Frame { var ( - buttonHeight = 40 - buttonWidth = width - 40 - page = 1 - perPage = 4 + perPage = 2 // 2 for tall mobile, 3 for landscape pages = int( math.Ceil( float64(len(lp.Levels)) / float64(perPage), ), ) 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. // 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 ) - /** 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, + Side: ui.N, 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. var buttons []*ui.Button for i, level := range lp.Levels { level := level 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. btnFrame := ui.NewFrame("Frame") 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.Handle(ui.Click, func(ed ui.EventData) error { // Is this level locked? @@ -484,10 +571,7 @@ func (config LevelPack) makeDetailScreen(frame *ui.Frame, width, height int, lp return nil }) - frame.Pack(btn, ui.Pack{ - Side: ui.N, - PadY: 2, - }) + buttonRow.Pack(btn, packDir) config.Supervisor.Add(btn) if i > perPage-1 { @@ -502,7 +586,7 @@ func (config LevelPack) makeDetailScreen(frame *ui.Frame, width, height int, lp Pages: pages, PerPage: perPage, MaxPageButtons: maxPageButtons, - Font: balance.MenuFont, + Font: balance.PagerLargeFont, OnChange: func(newPage, perPage int) { page = newPage log.Info("Page: %d, %d", page, perPage)