Window Icon, UI Polish

* SDL2 builds of the game now set their app window icon.
* Create/Edit Level window is updated to show a tabbed UI to create a
  new Level or a new Doodad. The dedicated main menu button to create a
  new doodad (which immediately prompted for its size) is replaced by
  this new tab's UI.
* Edit Drawing/Play Level window is more responsive to smaller screen
  sizes by drawing fewer columns of filenames.
* Bugfix: the Alert and Confirm modals always re-center themselves on
  screen, especially to adapt between Portrait or Landscape mode on a
  mobile device.
This commit is contained in:
Noah 2021-12-30 16:31:45 -08:00
parent 37377cdcc1
commit d16a8657aa
18 changed files with 692 additions and 511 deletions

BIN
assets/icons/1024.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

BIN
assets/icons/128.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

BIN
assets/icons/16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 785 B

BIN
assets/icons/256.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
assets/icons/32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
assets/icons/512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

BIN
assets/icons/64.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

BIN
assets/icons/96.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

View File

@ -19,6 +19,7 @@ import (
"git.kirsle.net/apps/doodle/pkg/log" "git.kirsle.net/apps/doodle/pkg/log"
"git.kirsle.net/apps/doodle/pkg/shmem" "git.kirsle.net/apps/doodle/pkg/shmem"
"git.kirsle.net/apps/doodle/pkg/sound" "git.kirsle.net/apps/doodle/pkg/sound"
"git.kirsle.net/apps/doodle/pkg/sprites"
"git.kirsle.net/apps/doodle/pkg/usercfg" "git.kirsle.net/apps/doodle/pkg/usercfg"
"git.kirsle.net/go/render" "git.kirsle.net/go/render"
"git.kirsle.net/go/render/sdl" "git.kirsle.net/go/render/sdl"
@ -166,6 +167,16 @@ func main() {
game := doodle.New(c.Bool("debug"), engine) game := doodle.New(c.Bool("debug"), engine)
game.SetupEngine() game.SetupEngine()
// Set the app window icon.
if engine, ok := game.Engine.(*sdl.Renderer); ok {
if icon, err := sprites.LoadImage(game.Engine, balance.WindowIcon); err == nil {
engine.SetWindowIcon(icon.Image)
} else {
log.Error("Couldn't load WindowIcon (%s): %s", balance.WindowIcon, err)
}
}
if c.Bool("guitest") { if c.Bool("guitest") {
game.Goto(&doodle.GUITestScene{}) game.Goto(&doodle.GUITestScene{})
} else if filename != "" { } else if filename != "" {

View File

@ -8,6 +8,8 @@ import (
// Theme and appearance variables. // Theme and appearance variables.
var ( var (
WindowIcon = "assets/icons/96.png"
// Title Screen Font // Title Screen Font
TitleScreenFont = render.Text{ TitleScreenFont = render.Text{
Size: 46, Size: 46,

View File

@ -78,7 +78,7 @@ func (u *EditorUI) SetupMenuBar(d *Doodle) *ui.MenuBar {
fileMenu.AddItemAccel("Open...", "Ctrl-O", u.Scene.MenuOpen) fileMenu.AddItemAccel("Open...", "Ctrl-O", u.Scene.MenuOpen)
fileMenu.AddSeparator() fileMenu.AddSeparator()
fileMenu.AddItem("Close "+drawingType, func() { fileMenu.AddItem("Quit to menu", func() {
u.Scene.ConfirmUnload(func() { u.Scene.ConfirmUnload(func() {
d.Goto(&MainScene{}) d.Goto(&MainScene{})
}) })

View File

@ -17,3 +17,11 @@ const (
DoodadExt = ".doodad" DoodadExt = ".doodad"
LevelPackExt = ".levelpack" LevelPackExt = ".levelpack"
) )
// Responsive breakpoints for mobile friendly UIs.
const (
ScreenWidthXSmall = 400
ScreenWidthSmall = 600
ScreenWidthMedium = 800
ScreenWidthLarge = 1000
)

View File

@ -36,7 +36,6 @@ type MainScene struct {
labelVersion *ui.Label labelVersion *ui.Label
labelHint *ui.Label labelHint *ui.Label
frame *ui.Frame // Main button frame frame *ui.Frame // Main button frame
btnRegister *ui.Button
winRegister *ui.Window winRegister *ui.Window
winSettings *ui.Window winSettings *ui.Window
winLevelPacks *ui.Window winLevelPacks *ui.Window
@ -125,13 +124,72 @@ func (s *MainScene) Setup(d *Doodle) error {
s.updateButton.Hide() s.updateButton.Hide()
s.Supervisor.Add(s.updateButton) s.Supervisor.Add(s.updateButton)
// Register button. // Main UI button frame.
s.btnRegister = ui.NewButton("Register", ui.NewLabel(ui.Label{ frame := ui.NewFrame("frame")
Text: "Register Game", s.frame = frame
Font: balance.LabelFont,
})) var buttons = []struct {
s.btnRegister.SetStyle(&balance.ButtonPrimary) Name string
s.btnRegister.Handle(ui.Click, func(ed ui.EventData) error { 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.Hide()
},
})
}
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 { if s.winRegister == nil {
cfg := windows.License{ cfg := windows.License{
Supervisor: s.Supervisor, Supervisor: s.Supervisor,
@ -151,78 +209,9 @@ func (s *MainScene) Setup(d *Doodle) error {
cfg.OnLicensed() cfg.OnLicensed()
} }
s.winRegister.Show() s.winRegister.Show()
return nil
})
s.btnRegister.Compute(d.Engine)
s.Supervisor.Add(s.btnRegister)
if license.IsRegistered() {
s.btnRegister.Hide()
}
// Main UI button frame.
frame := ui.NewFrame("frame")
s.frame = frame
var buttons = []struct {
Name string
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())
}
}, },
})
}
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: "Create a Level",
Func: d.GotoNewMenu,
Style: &balance.ButtonPink,
},
{
Name: "Create a Doodad",
Func: func() {
d.NewDoodad(0)
},
Style: &balance.ButtonPink,
},
{
Name: "Edit a Drawing",
Func: d.GotoLoadMenu,
Style: &balance.ButtonPrimary, Style: &balance.ButtonPrimary,
}, },
{
Name: "Settings",
Func: func() {
if s.winSettings == nil {
s.winSettings = d.MakeSettingsWindow(s.Supervisor)
}
s.winSettings.Show()
},
},
} }
for _, button := range buttons { for _, button := range buttons {
button := button button := button
@ -406,12 +395,6 @@ func (s *MainScene) positionMenuPortrait(d *Doodle) {
X: (d.width / 2) - (s.frame.Size().W / 2), X: (d.width / 2) - (s.frame.Size().W / 2),
Y: 260, Y: 260,
}) })
// Register button.
s.btnRegister.MoveTo(render.Point{
X: d.width - s.btnRegister.Size().W - 24,
Y: d.height - s.btnRegister.Size().H - 24,
})
} }
func (s *MainScene) positionMenuLandscape(d *Doodle) { func (s *MainScene) positionMenuLandscape(d *Doodle) {
@ -447,13 +430,6 @@ func (s *MainScene) positionMenuLandscape(d *Doodle) {
X: (col2.X+col2.W)/2 - (s.frame.Size().W / 2), X: (col2.X+col2.W)/2 - (s.frame.Size().W / 2),
Y: (d.height / 2) - (s.frame.Size().H / 2), Y: (d.height / 2) - (s.frame.Size().H / 2),
}) })
// Register button to the top left.
// TODO: not ideal, move into main button list?
s.btnRegister.MoveTo(render.Point{
X: 20,
Y: 20,
})
} }
// LoopLazyScroll gently scrolls the title screen demo level, called each Loop. // LoopLazyScroll gently scrolls the title screen demo level, called each Loop.
@ -560,9 +536,6 @@ func (s *MainScene) Draw(d *Doodle) error {
s.frame.Compute(d.Engine) s.frame.Compute(d.Engine)
s.frame.Present(d.Engine, s.frame.Point()) s.frame.Present(d.Engine, s.frame.Point())
// Register button.
s.btnRegister.Present(d.Engine, s.btnRegister.Point())
// Present supervised windows. // Present supervised windows.
s.Supervisor.Present(d.Engine) s.Supervisor.Present(d.Engine)

View File

@ -17,6 +17,9 @@ MenuScene holds the main dialog menu UIs for:
* New Level * New Level
* Open Level * Open Level
* Settings * Settings
DEPRECATED: migrate these prompts into popup windows to appear
on the MainScene or elsewhere as wanted.
*/ */
type MenuScene struct { type MenuScene struct {
// Configuration. // Configuration.
@ -159,6 +162,9 @@ func (s *MenuScene) setupNewWindow(d *Doodle) error {
Level: lvl, Level: lvl,
}) })
}, },
OnCreateNewDoodad: func(size int) {
d.NewDoodad(size)
},
OnCancel: func() { OnCancel: func() {
d.Goto(&MainScene{}) d.Goto(&MainScene{})
}, },

View File

@ -5,6 +5,7 @@ import (
"git.kirsle.net/apps/doodle/pkg/balance" "git.kirsle.net/apps/doodle/pkg/balance"
"git.kirsle.net/apps/doodle/pkg/keybind" "git.kirsle.net/apps/doodle/pkg/keybind"
"git.kirsle.net/apps/doodle/pkg/modal/loadscreen" "git.kirsle.net/apps/doodle/pkg/modal/loadscreen"
"git.kirsle.net/apps/doodle/pkg/shmem"
"git.kirsle.net/go/render" "git.kirsle.net/go/render"
"git.kirsle.net/go/render/event" "git.kirsle.net/go/render/event"
"git.kirsle.net/go/ui" "git.kirsle.net/go/ui"
@ -87,10 +88,13 @@ func Draw() {
// Center the window on screen. // Center the window on screen.
func center(win *ui.Window) { func center(win *ui.Window) {
var modSize = win.Size() var (
modSize = win.Size()
width, height = shmem.CurrentRenderEngine.WindowSize()
)
var moveTo = render.Point{ var moveTo = render.Point{
X: (window.W / 2) - (modSize.W / 2), X: (width / 2) - (modSize.W / 2),
Y: (window.H / 4) - (modSize.H / 2), Y: (height / 4) - (modSize.H / 2),
} }
win.MoveTo(moveTo) win.MoveTo(moveTo)

View File

@ -24,19 +24,60 @@ type AddEditLevel struct {
// Callback functions. // Callback functions.
OnChangePageTypeAndWallpaper func(pageType level.PageType, wallpaper string) OnChangePageTypeAndWallpaper func(pageType level.PageType, wallpaper string)
OnCreateNewLevel func(*level.Level) OnCreateNewLevel func(*level.Level)
OnCreateNewDoodad func(size int)
OnReload func() OnReload func()
OnCancel func() OnCancel func()
} }
// NewAddEditLevel initializes the window. // NewAddEditLevel initializes the window.
func NewAddEditLevel(config AddEditLevel) *ui.Window { func NewAddEditLevel(config AddEditLevel) *ui.Window {
// Default options.
var (
title = "New Drawing"
)
// Given a level to edit?
if config.EditLevel != nil {
title = "Level Properties"
}
window := ui.NewWindow(title)
window.SetButtons(ui.CloseButton)
window.Configure(ui.Config{
Width: 400,
Height: 280,
Background: render.Grey,
})
// Tabbed UI for New Level or New Doodad.
tabframe := ui.NewTabFrame("Level Tabs")
if config.EditLevel != nil {
tabframe.SetTabsHidden(true)
}
window.Pack(tabframe, ui.Pack{
Side: ui.N,
Fill: true,
Expand: true,
})
// Add the tabs.
config.setupLevelFrame(tabframe)
config.setupDoodadFrame(tabframe)
tabframe.Supervise(config.Supervisor)
window.Hide()
return window
}
// Creates the Create/Edit Level tab ("index").
func (config AddEditLevel) setupLevelFrame(tf *ui.TabFrame) {
// Default options. // Default options.
var ( var (
newPageType = level.Bounded.String() newPageType = level.Bounded.String()
newWallpaper = "notebook.png" newWallpaper = "notebook.png"
paletteName = level.DefaultPaletteNames[0] paletteName = level.DefaultPaletteNames[0]
isNewLevel = config.EditLevel == nil isNewLevel = config.EditLevel == nil
title = "New Drawing"
// Default text for the Palette drop-down for already-existing levels. // Default text for the Palette drop-down for already-existing levels.
// (needs --experimental feature flag to enable the UI). // (needs --experimental feature flag to enable the UI).
@ -53,24 +94,12 @@ func NewAddEditLevel(config AddEditLevel) *ui.Window {
newPageType = config.EditLevel.PageType.String() newPageType = config.EditLevel.PageType.String()
newWallpaper = config.EditLevel.Wallpaper newWallpaper = config.EditLevel.Wallpaper
paletteName = textCurrentPalette paletteName = textCurrentPalette
title = "Level Properties"
} }
window := ui.NewWindow(title) frame := tf.AddTab("index", ui.NewLabel(ui.Label{
window.SetButtons(ui.CloseButton) Text: "New Level",
window.Configure(ui.Config{ Font: balance.TabFont,
Width: 400, }))
Height: 280,
Background: render.Grey,
})
{
frame := ui.NewFrame("New Level Frame")
window.Pack(frame, ui.Pack{
Side: ui.N,
Fill: true,
Expand: true,
})
/****************** /******************
* Frame for selecting Page Type * Frame for selecting Page Type
@ -468,7 +497,11 @@ func NewAddEditLevel(config AddEditLevel) *ui.Window {
lvl.SetFile(balance.CustomWallpaperEmbedPath, []byte(newWallpaperB64)) lvl.SetFile(balance.CustomWallpaperEmbedPath, []byte(newWallpaperB64))
} }
if config.OnCreateNewLevel != nil {
config.OnCreateNewLevel(lvl) config.OnCreateNewLevel(lvl)
} else {
shmem.FlashError("OnCreateNewLevel not attached")
}
return nil return nil
}}, }},
@ -517,8 +550,126 @@ func NewAddEditLevel(config AddEditLevel) *ui.Window {
PadY: 8, PadY: 8,
}) })
} }
}
// Creates the "New Doodad" frame.
func (config AddEditLevel) setupDoodadFrame(tf *ui.TabFrame) {
// Default options.
var (
doodadSize = 64
)
frame := tf.AddTab("doodad", ui.NewLabel(ui.Label{
Text: "New Doodad",
Font: balance.TabFont,
}))
/******************
* Frame for selecting Page Type
******************/
typeFrame := ui.NewFrame("Doodad Options Frame")
frame.Pack(typeFrame, ui.Pack{
Side: ui.N,
FillX: true,
})
label1 := ui.NewLabel(ui.Label{
Text: "Doodad sprite size (square):",
Font: balance.LabelFont,
})
typeFrame.Pack(label1, ui.Pack{
Side: ui.W,
})
// A selectbox to suggest some sizes or let the user enter a custom.
sizeBtn := ui.NewSelectBox("Size Select", ui.Label{
Font: ui.MenuFont,
})
typeFrame.Pack(sizeBtn, ui.Pack{
Side: ui.W,
Expand: true,
})
for _, row := range []struct {
Name string
Value int
}{
{"32", 32},
{"64", 64},
{"96", 96},
{"128", 128},
{"200", 200},
{"256", 256},
{"Custom...", 0},
} {
row := row
sizeBtn.AddItem(row.Name, row.Value, func() {})
} }
window.Hide() sizeBtn.SetValue(doodadSize)
return window sizeBtn.Handle(ui.Change, func(ed ui.EventData) error {
if selection, ok := sizeBtn.GetValue(); ok {
if size, ok := selection.Value.(int); ok {
if size == 0 {
shmem.Prompt("Enter a custom size for the doodad width and height: ", func(answer string) {
if a, err := strconv.Atoi(answer); err == nil && a > 0 {
doodadSize = a
} else {
shmem.FlashError("Doodad size should be a number greater than zero.")
}
})
} else {
doodadSize = size
}
}
}
return nil
})
sizeBtn.Supervise(config.Supervisor)
config.Supervisor.Add(sizeBtn)
/******************
* Confirm/cancel buttons.
******************/
bottomFrame := ui.NewFrame("Button Frame")
frame.Pack(bottomFrame, ui.Pack{
Side: ui.N,
FillX: true,
PadY: 8,
})
var buttons = []struct {
Label string
F func(ui.EventData) error
}{
{"Continue", func(ed ui.EventData) error {
if config.OnCreateNewDoodad != nil {
config.OnCreateNewDoodad(doodadSize)
} else {
shmem.FlashError("OnCreateNewDoodad not attached")
}
return nil
}},
{"Cancel", func(ed ui.EventData) error {
config.OnCancel()
return nil
}},
}
for _, t := range buttons {
btn := ui.NewButton(t.Label, ui.NewLabel(ui.Label{
Text: t.Label,
Font: balance.MenuFont,
}))
btn.Handle(ui.Click, t.F)
config.Supervisor.Add(btn)
bottomFrame.Pack(btn, ui.Pack{
Side: ui.W,
PadX: 4,
PadY: 8,
})
}
} }

View File

@ -18,6 +18,7 @@ type LevelPack struct {
// Callback functions. // Callback functions.
OnPlayLevel func(pack levelpack.LevelPack, level levelpack.Level) OnPlayLevel func(pack levelpack.LevelPack, level levelpack.Level)
OnCloseWindow func()
// Internal variables // Internal variables
window *ui.Window window *ui.Window
@ -65,6 +66,7 @@ func NewLevelPackWindow(config LevelPack) *ui.Window {
// And each LevelPack's screen is a pager for its Levels. // And each LevelPack's screen is a pager for its Levels.
tabFrame := ui.NewTabFrame("Screens Manager") tabFrame := ui.NewTabFrame("Screens Manager")
tabFrame.SetTabsHidden(true) tabFrame.SetTabsHidden(true)
tabFrame.Supervise(config.Supervisor)
window.Pack(tabFrame, ui.Pack{ window.Pack(tabFrame, ui.Pack{
Side: ui.N, Side: ui.N,
FillX: true, FillX: true,
@ -90,12 +92,23 @@ func NewLevelPackWindow(config LevelPack) *ui.Window {
config.makeDetailScreen(tab, width, height, packmap[filename]) config.makeDetailScreen(tab, width, height, packmap[filename])
} }
// indexTab.Resize(render.Rect{ // Close button.
// W: width-4, if config.OnCloseWindow != nil {
// H: height-4, 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)
window.Place(closeBtn, ui.Place{
Bottom: 15,
Center: true,
})
}
tabFrame.Supervise(config.Supervisor)
window.Supervise(config.Supervisor) window.Supervise(config.Supervisor)
window.Hide() window.Hide()
return window return window

View File

@ -6,6 +6,7 @@ import (
"strings" "strings"
"git.kirsle.net/apps/doodle/pkg/balance" "git.kirsle.net/apps/doodle/pkg/balance"
"git.kirsle.net/apps/doodle/pkg/enum"
"git.kirsle.net/apps/doodle/pkg/level" "git.kirsle.net/apps/doodle/pkg/level"
"git.kirsle.net/apps/doodle/pkg/log" "git.kirsle.net/apps/doodle/pkg/log"
"git.kirsle.net/apps/doodle/pkg/native" "git.kirsle.net/apps/doodle/pkg/native"
@ -32,8 +33,18 @@ type OpenLevelEditor struct {
func NewOpenLevelEditor(config OpenLevelEditor) *ui.Window { func NewOpenLevelEditor(config OpenLevelEditor) *ui.Window {
var ( var (
width, height = config.Engine.WindowSize() width, height = config.Engine.WindowSize()
columns = 4
) )
// Show fewer columns on smaller devices.
if width <= enum.ScreenWidthXSmall {
columns = 1
} else if width <= enum.ScreenWidthSmall {
columns = 2
} else if width <= enum.ScreenWidthMedium {
columns = 3
}
window := ui.NewWindow("Open Drawing") window := ui.NewWindow("Open Drawing")
window.Configure(ui.Config{ window.Configure(ui.Config{
Width: int(float64(width) * 0.75), Width: int(float64(width) * 0.75),
@ -86,7 +97,9 @@ func NewOpenLevelEditor(config OpenLevelEditor) *ui.Window {
func(i int, lvl string) { func(i int, lvl string) {
btn := ui.NewButton("Level Btn", ui.NewLabel(ui.Label{ btn := ui.NewButton("Level Btn", ui.NewLabel(ui.Label{
Text: lvl, Text: lvl,
Font: balance.MenuFont, Font: balance.MenuFont.Update(render.Text{
PadY: 2,
}),
})) }))
btn.Handle(ui.Click, func(ed ui.EventData) error { btn.Handle(ui.Click, func(ed ui.EventData) error {
if config.LoadForPlay { if config.LoadForPlay {
@ -103,7 +116,7 @@ func NewOpenLevelEditor(config OpenLevelEditor) *ui.Window {
Fill: true, Fill: true,
}) })
if i > 0 && (i+1)%4 == 0 { if columns == 1 || i > 0 && (i+1)%columns == 0 {
lvlRow = ui.NewFrame(fmt.Sprintf("Level Row %d", i)) lvlRow = ui.NewFrame(fmt.Sprintf("Level Row %d", i))
frame.Pack(lvlRow, ui.Pack{ frame.Pack(lvlRow, ui.Pack{
Side: ui.N, Side: ui.N,