Noah Petherbridge
b17ca34de2
* Use `go-bindata` to embed built-in doodads and levels directly into the Doodle binary. `make bindata` produces the bindata source file. * Add `FromJSON()` method to Levels and Doodads to load objects from JSON strings in memory (for bindata built-ins or WASM ajax requests) * Update file loading functions to check the embedded bindata files. * pkg/config.go#EditFile: * Supports editing a level from bindata (TODO: remove this support) * If the "assets/levels/%(simple-name.level)" exists in bindata, edits that drawing. * No such support for editing built-in doodads. * WASM has no filesystem access to edit files except built-in levels (yet) * pkg/doodads#ListDoodads: * Prepends built-in doodads from bindata to the returned list. * WASM: no filesystem access so gets only the built-ins. * pkg/doodads#LoadFile: * Checks built-in bindata store first for doodad files. * WASM: tries an HTTP request if not found in bindata but can go no further if not found (no filesystem access) * pkg/filesystem#FindFile: * This function finds a level/doodad by checking all the places. * If the level or doodad exists in bindata built-in, always returns its system path like "assets/doodads/test.doodad" * WASM: always returns the built-in candidate path even if not found in bindata so that ajax GET can be attempted. * pkg/level#ListSystemLevels: * New function that lists the system level files, similar to the equivalent doodads function. * Prepends the bindata built-in level files. * WASM: only returns the built-ins (no filesystem support) * Desktop: also lists and returns the assets/levels/ directory. * pkg/level#LoadFile: * Like the doodads.LoadFile, tries from built-in bindata first, then ajax request (WASM) before accessing the filesystem (desktop) * Menu Scene: TODO, list the built-in levels in the Load Level menu. This feature will soon go away when WASM gets its own storage for user levels (localStorage instead of filesystem)
503 lines
11 KiB
Go
503 lines
11 KiB
Go
package doodle
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"git.kirsle.net/apps/doodle/lib/events"
|
|
"git.kirsle.net/apps/doodle/lib/render"
|
|
"git.kirsle.net/apps/doodle/lib/ui"
|
|
"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/log"
|
|
"git.kirsle.net/apps/doodle/pkg/uix"
|
|
"git.kirsle.net/apps/doodle/pkg/userdir"
|
|
)
|
|
|
|
/*
|
|
MenuScene holds the main dialog menu UIs for:
|
|
|
|
* New Level
|
|
* Open Level
|
|
* Settings
|
|
*/
|
|
type MenuScene struct {
|
|
// Configuration.
|
|
StartupMenu string
|
|
|
|
Supervisor *ui.Supervisor
|
|
|
|
// Private widgets.
|
|
window *ui.Window
|
|
|
|
// Background wallpaper canvas.
|
|
canvas *uix.Canvas
|
|
|
|
// Values for the New menu
|
|
newPageType string
|
|
newWallpaper string
|
|
}
|
|
|
|
// Name of the scene.
|
|
func (s *MenuScene) Name() string {
|
|
return "Menu"
|
|
}
|
|
|
|
// GotoNewMenu loads the MenuScene and shows the "New" window.
|
|
func (d *Doodle) GotoNewMenu() {
|
|
log.Info("Loading the MenuScene to the New window")
|
|
scene := &MenuScene{
|
|
StartupMenu: "new",
|
|
}
|
|
d.Goto(scene)
|
|
}
|
|
|
|
// GotoLoadMenu loads the MenuScene and shows the "Load" window.
|
|
func (d *Doodle) GotoLoadMenu() {
|
|
log.Info("Loading the MenuScene to the Load window")
|
|
scene := &MenuScene{
|
|
StartupMenu: "load",
|
|
}
|
|
d.Goto(scene)
|
|
}
|
|
|
|
// Setup the scene.
|
|
func (s *MenuScene) Setup(d *Doodle) error {
|
|
s.Supervisor = ui.NewSupervisor()
|
|
|
|
// Set up the background wallpaper canvas.
|
|
s.canvas = uix.NewCanvas(100, false)
|
|
s.canvas.Resize(render.Rect{
|
|
W: int32(d.width),
|
|
H: int32(d.height),
|
|
})
|
|
s.canvas.LoadLevel(d.Engine, &level.Level{
|
|
Chunker: level.NewChunker(100),
|
|
Palette: level.NewPalette(),
|
|
PageType: level.Bounded,
|
|
Wallpaper: "notebook.png",
|
|
})
|
|
|
|
switch s.StartupMenu {
|
|
case "new":
|
|
if err := s.setupNewWindow(d); err != nil {
|
|
return err
|
|
}
|
|
case "load":
|
|
if err := s.setupLoadWindow(d); err != nil {
|
|
return err
|
|
}
|
|
default:
|
|
d.Flash("No Valid StartupMenu Given to MenuScene")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// configureCanvas updates the settings of the background canvas, so a live
|
|
// preview of the wallpaper and wrapping type can be shown.
|
|
func (s *MenuScene) configureCanvas(e render.Engine, pageType level.PageType, wallpaper string) {
|
|
s.canvas.LoadLevel(e, &level.Level{
|
|
Chunker: level.NewChunker(100),
|
|
Palette: level.NewPalette(),
|
|
PageType: pageType,
|
|
Wallpaper: wallpaper,
|
|
})
|
|
}
|
|
|
|
// setupNewWindow sets up the UI for the "New" window.
|
|
func (s *MenuScene) setupNewWindow(d *Doodle) error {
|
|
// Default scene options.
|
|
s.newPageType = level.Bounded.String()
|
|
s.newWallpaper = "notebook.png"
|
|
|
|
window := ui.NewWindow("New Drawing")
|
|
window.Configure(ui.Config{
|
|
Width: int32(float64(d.width) * 0.75),
|
|
Height: int32(float64(d.height) * 0.75),
|
|
Background: render.Grey,
|
|
})
|
|
window.Compute(d.Engine)
|
|
|
|
{
|
|
frame := ui.NewFrame("New Level Frame")
|
|
window.Pack(frame, ui.Pack{
|
|
Anchor: ui.N,
|
|
Fill: true,
|
|
Expand: true,
|
|
})
|
|
|
|
/******************
|
|
* Frame for selecting Page Type
|
|
******************/
|
|
|
|
label1 := ui.NewLabel(ui.Label{
|
|
Text: "Page Type",
|
|
Font: balance.LabelFont,
|
|
})
|
|
frame.Pack(label1, ui.Pack{
|
|
Anchor: ui.N,
|
|
FillX: true,
|
|
})
|
|
|
|
typeFrame := ui.NewFrame("Page Type Options Frame")
|
|
frame.Pack(typeFrame, ui.Pack{
|
|
Anchor: ui.N,
|
|
FillX: true,
|
|
})
|
|
|
|
type typeObj struct {
|
|
Name string
|
|
Value level.PageType
|
|
}
|
|
var types = []typeObj{
|
|
{"Unbounded", level.Unbounded},
|
|
{"Bounded", level.Bounded},
|
|
{"No Negative Space", level.NoNegativeSpace},
|
|
// {"Bordered (TODO)", level.Bordered},
|
|
}
|
|
for _, t := range types {
|
|
// TODO: Hide some options for the free version of the game.
|
|
// - At launch only Bounded and Bordered will be available
|
|
// in the shareware version.
|
|
// - For now, only hide Bordered as it's not yet implemented.
|
|
// --------
|
|
// if balance.FreeVersion {
|
|
// if t.Value == level.Bordered {
|
|
// continue
|
|
// }
|
|
// }
|
|
|
|
func(t typeObj) {
|
|
radio := ui.NewRadioButton(t.Name,
|
|
&s.newPageType,
|
|
t.Value.String(),
|
|
ui.NewLabel(ui.Label{
|
|
Text: t.Name,
|
|
Font: balance.MenuFont,
|
|
}),
|
|
)
|
|
radio.Handle(ui.Click, func(p render.Point) {
|
|
s.configureCanvas(d.Engine, t.Value, s.newWallpaper)
|
|
})
|
|
s.Supervisor.Add(radio)
|
|
typeFrame.Pack(radio, ui.Pack{
|
|
Anchor: ui.W,
|
|
PadX: 4,
|
|
})
|
|
}(t)
|
|
}
|
|
|
|
/******************
|
|
* Frame for selecting Level Wallpaper
|
|
******************/
|
|
|
|
label2 := ui.NewLabel(ui.Label{
|
|
Text: "Wallpaper",
|
|
Font: balance.LabelFont,
|
|
})
|
|
frame.Pack(label2, ui.Pack{
|
|
Anchor: ui.N,
|
|
FillX: true,
|
|
})
|
|
|
|
wpFrame := ui.NewFrame("Wallpaper Frame")
|
|
frame.Pack(wpFrame, ui.Pack{
|
|
Anchor: ui.N,
|
|
FillX: true,
|
|
})
|
|
|
|
type wallpaperObj struct {
|
|
Name string
|
|
Value string
|
|
}
|
|
var wallpapers = []wallpaperObj{
|
|
{"Notebook", "notebook.png"},
|
|
{"Blueprint", "blueprint.png"},
|
|
{"Legal Pad", "legal.png"},
|
|
{"Pure White", "white.png"},
|
|
// {"Placemat", "placemat.png"},
|
|
}
|
|
for _, t := range wallpapers {
|
|
func(t wallpaperObj) {
|
|
radio := ui.NewRadioButton(t.Name, &s.newWallpaper, t.Value, ui.NewLabel(ui.Label{
|
|
Text: t.Name,
|
|
Font: balance.MenuFont,
|
|
}))
|
|
radio.Handle(ui.Click, func(p render.Point) {
|
|
log.Info("Set wallpaper to %s", t.Value)
|
|
if pageType, ok := level.PageTypeFromString(s.newPageType); ok {
|
|
s.configureCanvas(d.Engine, pageType, t.Value)
|
|
}
|
|
})
|
|
s.Supervisor.Add(radio)
|
|
wpFrame.Pack(radio, ui.Pack{
|
|
Anchor: ui.W,
|
|
PadX: 4,
|
|
})
|
|
}(t)
|
|
}
|
|
|
|
/******************
|
|
* Confirm/cancel buttons.
|
|
******************/
|
|
|
|
bottomFrame := ui.NewFrame("Button Frame")
|
|
// bottomFrame.Configure(ui.Config{
|
|
// BorderSize: 1,
|
|
// BorderStyle: ui.BorderSunken,
|
|
// BorderColor: render.Black,
|
|
// })
|
|
// bottomFrame.SetBackground(render.Grey)
|
|
frame.Pack(bottomFrame, ui.Pack{
|
|
Anchor: ui.N,
|
|
FillX: true,
|
|
PadY: 8,
|
|
})
|
|
|
|
var buttons = []struct {
|
|
Label string
|
|
F func(render.Point)
|
|
}{
|
|
{"Continue", func(p render.Point) {
|
|
d.Flash("Create new map with %s page type and %s wallpaper", s.newPageType, s.newWallpaper)
|
|
pageType, ok := level.PageTypeFromString(s.newPageType)
|
|
if !ok {
|
|
d.Flash("Invalid Page Type '%s'", s.newPageType)
|
|
return
|
|
}
|
|
|
|
lvl := level.New()
|
|
lvl.Palette = level.DefaultPalette()
|
|
lvl.Wallpaper = s.newWallpaper
|
|
lvl.PageType = pageType
|
|
|
|
// Blueprint theme palette for the dark wallpaper color.
|
|
if lvl.Wallpaper == "blueprint.png" {
|
|
lvl.Palette = level.NewBlueprintPalette()
|
|
}
|
|
|
|
d.Goto(&EditorScene{
|
|
DrawingType: enum.LevelDrawing,
|
|
Level: lvl,
|
|
})
|
|
}},
|
|
|
|
{"Cancel", func(p render.Point) {
|
|
d.Goto(&MainScene{})
|
|
}},
|
|
}
|
|
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)
|
|
s.Supervisor.Add(btn)
|
|
bottomFrame.Pack(btn, ui.Pack{
|
|
Anchor: ui.W,
|
|
PadX: 4,
|
|
PadY: 8,
|
|
})
|
|
}
|
|
}
|
|
|
|
s.window = window
|
|
return nil
|
|
}
|
|
|
|
// setupLoadWindow sets up the UI for the "New" window.
|
|
func (s *MenuScene) setupLoadWindow(d *Doodle) error {
|
|
window := ui.NewWindow("Open Drawing")
|
|
window.Configure(ui.Config{
|
|
Width: int32(float64(d.width) * 0.75),
|
|
Height: int32(float64(d.height) * 0.75),
|
|
Background: render.Grey,
|
|
})
|
|
window.Compute(d.Engine)
|
|
|
|
{
|
|
frame := ui.NewFrame("Open Drawing Frame")
|
|
window.Pack(frame, ui.Pack{
|
|
Anchor: ui.N,
|
|
Fill: true,
|
|
Expand: true,
|
|
})
|
|
|
|
/******************
|
|
* Frame for selecting User Levels
|
|
******************/
|
|
|
|
label1 := ui.NewLabel(ui.Label{
|
|
Text: "Levels",
|
|
Font: balance.LabelFont,
|
|
})
|
|
frame.Pack(label1, ui.Pack{
|
|
Anchor: ui.N,
|
|
FillX: true,
|
|
})
|
|
|
|
// Get the user's levels.
|
|
levels, _ := userdir.ListLevels()
|
|
|
|
// Embedded levels, TODO
|
|
sysLevels, _ := level.ListSystemLevels()
|
|
levels = append(levels, sysLevels...)
|
|
|
|
lvlRow := ui.NewFrame("Level Row 0")
|
|
frame.Pack(lvlRow, ui.Pack{
|
|
Anchor: ui.N,
|
|
FillX: true,
|
|
PadY: 1,
|
|
})
|
|
for i, lvl := range levels {
|
|
func(i int, lvl string) {
|
|
log.Info("Add file %s to row %s", lvl, lvlRow.Name)
|
|
btn := ui.NewButton("Level Btn", ui.NewLabel(ui.Label{
|
|
Text: lvl,
|
|
Font: balance.MenuFont,
|
|
}))
|
|
btn.Handle(ui.Click, func(p render.Point) {
|
|
d.EditFile(lvl)
|
|
})
|
|
s.Supervisor.Add(btn)
|
|
lvlRow.Pack(btn, ui.Pack{
|
|
Anchor: ui.W,
|
|
Expand: true,
|
|
Fill: true,
|
|
})
|
|
|
|
if i > 0 && (i+1)%4 == 0 {
|
|
log.Warn("i=%d wrapped at mod 4", i)
|
|
lvlRow = ui.NewFrame(fmt.Sprintf("Level Row %d", i))
|
|
frame.Pack(lvlRow, ui.Pack{
|
|
Anchor: ui.N,
|
|
FillX: true,
|
|
PadY: 1,
|
|
})
|
|
}
|
|
}(i, lvl)
|
|
}
|
|
|
|
/******************
|
|
* Frame for selecting User Doodads
|
|
******************/
|
|
|
|
if !balance.FreeVersion {
|
|
label2 := ui.NewLabel(ui.Label{
|
|
Text: "Doodads",
|
|
Font: balance.LabelFont,
|
|
})
|
|
frame.Pack(label2, ui.Pack{
|
|
Anchor: ui.N,
|
|
FillX: true,
|
|
})
|
|
|
|
files, _ := userdir.ListDoodads()
|
|
ddRow := ui.NewFrame("Doodad Row 0")
|
|
frame.Pack(ddRow, ui.Pack{
|
|
Anchor: ui.N,
|
|
FillX: true,
|
|
PadY: 1,
|
|
})
|
|
for i, dd := range files {
|
|
func(i int, dd string) {
|
|
btn := ui.NewButton("Doodad Btn", ui.NewLabel(ui.Label{
|
|
Text: dd,
|
|
Font: balance.MenuFont,
|
|
}))
|
|
btn.Handle(ui.Click, func(p render.Point) {
|
|
d.EditFile(dd)
|
|
})
|
|
s.Supervisor.Add(btn)
|
|
ddRow.Pack(btn, ui.Pack{
|
|
Anchor: ui.W,
|
|
Expand: true,
|
|
Fill: true,
|
|
})
|
|
|
|
if i > 0 && (i+1)%4 == 0 {
|
|
ddRow = ui.NewFrame(fmt.Sprintf("Doodad Row %d", i))
|
|
frame.Pack(ddRow, ui.Pack{
|
|
Anchor: ui.N,
|
|
FillX: true,
|
|
PadY: 1,
|
|
})
|
|
}
|
|
}(i, dd)
|
|
}
|
|
}
|
|
|
|
/******************
|
|
* Confirm/cancel buttons.
|
|
******************/
|
|
|
|
bottomFrame := ui.NewFrame("Button Frame")
|
|
// bottomFrame.Configure(ui.Config{
|
|
// BorderSize: 1,
|
|
// BorderStyle: ui.BorderSunken,
|
|
// BorderColor: render.Black,
|
|
// })
|
|
// bottomFrame.SetBackground(render.Grey)
|
|
frame.Pack(bottomFrame, ui.Pack{
|
|
Anchor: ui.N,
|
|
FillX: true,
|
|
PadY: 8,
|
|
})
|
|
|
|
var buttons = []struct {
|
|
Label string
|
|
F func(render.Point)
|
|
}{
|
|
{"Cancel", func(p render.Point) {
|
|
d.Goto(&MainScene{})
|
|
}},
|
|
}
|
|
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)
|
|
s.Supervisor.Add(btn)
|
|
bottomFrame.Pack(btn, ui.Pack{
|
|
Anchor: ui.W,
|
|
PadX: 4,
|
|
PadY: 8,
|
|
})
|
|
}
|
|
}
|
|
|
|
s.window = window
|
|
return nil
|
|
}
|
|
|
|
// Loop the editor scene.
|
|
func (s *MenuScene) Loop(d *Doodle, ev *events.State) error {
|
|
s.Supervisor.Loop(ev)
|
|
return nil
|
|
}
|
|
|
|
// Draw the pixels on this frame.
|
|
func (s *MenuScene) Draw(d *Doodle) error {
|
|
// Clear the canvas and fill it with white.
|
|
d.Engine.Clear(render.White)
|
|
|
|
// Draw the background canvas.
|
|
s.canvas.Present(d.Engine, render.Origin)
|
|
|
|
s.window.Compute(d.Engine)
|
|
s.window.MoveTo(render.Point{
|
|
X: (int32(d.width) / 2) - (s.window.Size().W / 2),
|
|
Y: 60,
|
|
})
|
|
s.window.Present(d.Engine, s.window.Point())
|
|
|
|
return nil
|
|
}
|
|
|
|
// Destroy the scene.
|
|
func (s *MenuScene) Destroy() error {
|
|
return nil
|
|
}
|