doodle/pkg/windows/open_level_editor.go
Noah Petherbridge 7093b102e3 Embeddable Doodads In Levels
* The Publisher is all hooked up. No native Save File dialogs yet, so
  uses the dev shell Prompt() to ask for output filename.
* Custom-only or builtin doodads too can be stored in the level's file
  data, at "assets/doodads/*.doodad"
* When loading the embedded level in the Editor: it gets its custom
  doodads out of its file, and you can drag and drop them elsehwere,
  link them, Play Mode can use them, etc. but they won't appear in the
  Doodad Dropper if they are not installed in your local doodads
  directory.
* Fleshed out serialization API for the Doodad files:
  - LoadFromEmbeddable() looks to load a doodad from embeddable file
    data in addition to the usual places.
  - Serialize() returns the doodad in bytes, for easy access to embed
    into level data.
  - Deserialize() to parse and return from bytes.
* When loading a level that references doodads not found in its embedded
  data or the filesystem: an Alert modal appears listing the missing
  doodads. The rest of the level loads fine, but the actors referenced
  by these doodads don't load.
2021-06-13 14:59:03 -07:00

271 lines
6.0 KiB
Go

package windows
import (
"fmt"
"git.kirsle.net/apps/doodle/pkg/balance"
"git.kirsle.net/apps/doodle/pkg/level"
"git.kirsle.net/apps/doodle/pkg/log"
"git.kirsle.net/apps/doodle/pkg/native"
"git.kirsle.net/apps/doodle/pkg/userdir"
"git.kirsle.net/go/render"
"git.kirsle.net/go/ui"
)
// OpenLevelEditor is the "Open a Level to Edit It" window
type OpenLevelEditor struct {
Supervisor *ui.Supervisor
Engine render.Engine
// Load it for playing instead of editing?
LoadForPlay bool
// Callback functions.
OnPlayLevel func(filename string)
OnEditLevel func(filename string)
OnCancel func()
}
// NewOpenLevelEditor initializes the window.
func NewOpenLevelEditor(config OpenLevelEditor) *ui.Window {
var (
width, height = config.Engine.WindowSize()
)
window := ui.NewWindow("Open Drawing")
window.Configure(ui.Config{
Width: int(float64(width) * 0.75),
Height: int(float64(height) * 0.75),
Background: render.Grey,
})
{
frame := ui.NewFrame("Open Drawing Frame")
window.Pack(frame, ui.Pack{
Side: 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{
Side: 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{
Side: ui.N,
FillX: true,
PadY: 1,
})
for i, lvl := range levels {
func(i int, lvl string) {
btn := ui.NewButton("Level Btn", ui.NewLabel(ui.Label{
Text: lvl,
Font: balance.MenuFont,
}))
btn.Handle(ui.Click, func(ed ui.EventData) error {
if config.LoadForPlay {
config.OnPlayLevel(lvl)
} else {
config.OnEditLevel(lvl)
}
return nil
})
config.Supervisor.Add(btn)
lvlRow.Pack(btn, ui.Pack{
Side: ui.W,
Expand: true,
Fill: true,
})
if i > 0 && (i+1)%4 == 0 {
lvlRow = ui.NewFrame(fmt.Sprintf("Level Row %d", i))
frame.Pack(lvlRow, ui.Pack{
Side: ui.N,
FillX: true,
PadY: 1,
})
}
}(i, lvl)
}
// Browse button for local filesystem.
browseLevelFrame := ui.NewFrame("Browse Level Frame")
frame.Pack(browseLevelFrame, ui.Pack{
Side: ui.N,
Expand: true,
FillX: true,
PadY: 1,
})
browseLevelButton := ui.NewButton("Browse Level", ui.NewLabel(ui.Label{
Text: "Browse...",
Font: balance.MenuFont,
}))
browseLevelButton.SetStyle(&balance.ButtonPrimary)
browseLevelFrame.Pack(browseLevelButton, ui.Pack{
Side: ui.W,
})
browseLevelButton.Handle(ui.Click, func(ed ui.EventData) error {
filename, err := native.OpenFile("Choose a .level file", "*.level")
if err != nil {
log.Error("Couldn't show file dialog: %s", err)
return nil
}
if config.LoadForPlay {
config.OnPlayLevel(filename)
} else {
config.OnEditLevel(filename)
}
return nil
})
config.Supervisor.Add(browseLevelButton)
/******************
* Frame for selecting User Doodads
******************/
// Doodads not shown if we're loading a map to play, nor are they
// available to the free version.
if !config.LoadForPlay && !balance.FreeVersion {
label2 := ui.NewLabel(ui.Label{
Text: "Doodads",
Font: balance.LabelFont,
})
frame.Pack(label2, ui.Pack{
Side: ui.N,
FillX: true,
})
files, _ := userdir.ListDoodads()
ddRow := ui.NewFrame("Doodad Row 0")
frame.Pack(ddRow, ui.Pack{
Side: 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(ed ui.EventData) error {
config.OnEditLevel(dd)
return nil
})
config.Supervisor.Add(btn)
ddRow.Pack(btn, ui.Pack{
Side: 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{
Side: ui.N,
FillX: true,
PadY: 1,
})
}
}(i, dd)
}
// Browse button for local filesystem.
browseDoodadFrame := ui.NewFrame("Browse Doodad Frame")
frame.Pack(browseDoodadFrame, ui.Pack{
Side: ui.N,
Expand: true,
FillX: true,
PadY: 1,
})
browseDoodadButton := ui.NewButton("Browse Doodad", ui.NewLabel(ui.Label{
Text: "Browse...",
Font: balance.MenuFont,
}))
browseDoodadButton.SetStyle(&balance.ButtonPrimary)
browseDoodadFrame.Pack(browseDoodadButton, ui.Pack{
Side: ui.W,
})
browseDoodadButton.Handle(ui.Click, func(ed ui.EventData) error {
filename, err := native.OpenFile("Choose a .doodad file", "*.doodad")
if err != nil {
log.Error("Couldn't show file dialog: %s", err)
return nil
}
if config.LoadForPlay {
config.OnPlayLevel(filename)
} else {
config.OnEditLevel(filename)
}
return nil
})
config.Supervisor.Add(browseDoodadButton)
}
/******************
* 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{
Side: ui.N,
FillX: true,
PadY: 8,
})
var buttons = []struct {
Label string
F func(ui.EventData) error
}{
{"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,
})
}
}
return window
}