WIP Publish Dialog + UI Improvements
* File->Publish Level in the Level Editor opens the Publish window, where you can embed custom doodads into your level and export a portable .level file you can share with others. * Currently does not actually export a level file yet. * The dialog lists all unique doodad names in use in your level, and designates which are built-ins and which are custom (paginated). * A checkbox would let the user embed built-in doodads into their level, as well, locking it in to those versions and not using updated versions from future game releases. UI Improvements: * Added styling for a "Primary" UI button, rendered in deep blue. * Pop-up modals (Alert, Confirm) color their Ok button as Primary. * The Enter key pressed during an Alert or Confirm modal will invoke its default button and close the modal, corresponding to its Primary button. * The developer console is now opened with the tilde/grave key ` instead of the Enter key, so that the Enter key is free to click through modals. * In the "Open/Edit Drawing" window, a "Browse..." button is added to the level and doodad sections, spawning a native File Open dialog to pick a .level or .doodad outside the config root.
This commit is contained in:
parent
eb24858830
commit
d9bca2152a
|
@ -2,9 +2,12 @@ package balance
|
||||||
|
|
||||||
// Feature Flags to turn on/off experimental content.
|
// Feature Flags to turn on/off experimental content.
|
||||||
var Feature = feature{
|
var Feature = feature{
|
||||||
Zoom: false,
|
Zoom: false, // enable the zoom in/out feature (very buggy rn)
|
||||||
CustomWallpaper: true,
|
CustomWallpaper: true, // attach custom wallpaper img to levels
|
||||||
ChangePalette: false,
|
ChangePalette: false, // reset your palette after level creation to a diff preset
|
||||||
|
|
||||||
|
// Allow embedded doodads in levels.
|
||||||
|
EmbeddableDoodads: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
// FeaturesOn turns on all feature flags, from CLI --experimental option.
|
// FeaturesOn turns on all feature flags, from CLI --experimental option.
|
||||||
|
@ -18,4 +21,5 @@ type feature struct {
|
||||||
Zoom bool
|
Zoom bool
|
||||||
CustomWallpaper bool
|
CustomWallpaper bool
|
||||||
ChangePalette bool
|
ChangePalette bool
|
||||||
|
EmbeddableDoodads bool
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package balance
|
||||||
import (
|
import (
|
||||||
"git.kirsle.net/go/render"
|
"git.kirsle.net/go/render"
|
||||||
"git.kirsle.net/go/ui"
|
"git.kirsle.net/go/ui"
|
||||||
|
"git.kirsle.net/go/ui/style"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Theme and appearance variables.
|
// Theme and appearance variables.
|
||||||
|
@ -97,4 +98,15 @@ var (
|
||||||
DoodadButtonSize = 64
|
DoodadButtonSize = 64
|
||||||
DoodadDropperCols = 6 // rows/columns of buttons
|
DoodadDropperCols = 6 // rows/columns of buttons
|
||||||
DoodadDropperRows = 3
|
DoodadDropperRows = 3
|
||||||
|
|
||||||
|
// Button styles, customized in init().
|
||||||
|
ButtonPrimary = style.DefaultButton
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// Customize button styles.
|
||||||
|
ButtonPrimary.Background = render.RGBA(0, 60, 153, 255)
|
||||||
|
ButtonPrimary.Foreground = render.RGBA(255, 255, 254, 255)
|
||||||
|
ButtonPrimary.HoverBackground = render.RGBA(0, 153, 255, 255)
|
||||||
|
ButtonPrimary.HoverForeground = ButtonPrimary.Foreground
|
||||||
|
}
|
||||||
|
|
|
@ -41,6 +41,11 @@ func (c Command) Run(d *Doodle) error {
|
||||||
case "alert":
|
case "alert":
|
||||||
modal.Alert(c.ArgsLiteral)
|
modal.Alert(c.ArgsLiteral)
|
||||||
return nil
|
return nil
|
||||||
|
case "confirm":
|
||||||
|
modal.Confirm(c.ArgsLiteral).Then(func() {
|
||||||
|
d.Flash("Confirmed.")
|
||||||
|
})
|
||||||
|
return nil
|
||||||
case "new":
|
case "new":
|
||||||
return c.New(d)
|
return c.New(d)
|
||||||
case "save":
|
case "save":
|
||||||
|
|
|
@ -63,6 +63,49 @@ func ListDoodads() ([]string, error) {
|
||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ListBuiltin returns a listing of all built-in doodads.
|
||||||
|
// Exactly like ListDoodads() but doesn't return user home folder doodads.
|
||||||
|
func ListBuiltin() ([]string, error) {
|
||||||
|
var names []string
|
||||||
|
|
||||||
|
// List doodads embedded into the binary.
|
||||||
|
if files, err := bindata.AssetDir("assets/doodads"); err == nil {
|
||||||
|
names = append(names, files...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WASM
|
||||||
|
if runtime.GOOS == "js" {
|
||||||
|
// Return the array of doodads embedded in the bindata.
|
||||||
|
// TODO: append user doodads to the list.
|
||||||
|
return names, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read system-level doodads first. Ignore errors, if the system path is
|
||||||
|
// empty we still go on to read the user directory.
|
||||||
|
files, _ := ioutil.ReadDir(filesystem.SystemDoodadsPath)
|
||||||
|
|
||||||
|
for _, file := range files {
|
||||||
|
name := file.Name()
|
||||||
|
if strings.HasSuffix(strings.ToLower(name), enum.DoodadExt) {
|
||||||
|
names = append(names, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deduplicate names.
|
||||||
|
var uniq = map[string]interface{}{}
|
||||||
|
var result []string
|
||||||
|
for _, name := range names {
|
||||||
|
if _, ok := uniq[name]; !ok {
|
||||||
|
uniq[name] = nil
|
||||||
|
result = append(result, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Strings(result)
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
// LoadFile reads a doodad file from disk, checking a few locations.
|
// LoadFile reads a doodad file from disk, checking a few locations.
|
||||||
func LoadFile(filename string) (*Doodad, error) {
|
func LoadFile(filename string) (*Doodad, error) {
|
||||||
if !strings.HasSuffix(filename, enum.DoodadExt) {
|
if !strings.HasSuffix(filename, enum.DoodadExt) {
|
||||||
|
|
|
@ -134,10 +134,9 @@ func (d *Doodle) Run() error {
|
||||||
|
|
||||||
// Command line shell.
|
// Command line shell.
|
||||||
if d.shell.Open {
|
if d.shell.Open {
|
||||||
} else if ev.Enter {
|
} else if keybind.ShellKey(ev) {
|
||||||
log.Debug("Shell: opening shell")
|
log.Debug("Shell: opening shell")
|
||||||
d.shell.Open = true
|
d.shell.Open = true
|
||||||
ev.Enter = false
|
|
||||||
} else {
|
} else {
|
||||||
// Global event handlers.
|
// Global event handlers.
|
||||||
if keybind.Shutdown(ev) {
|
if keybind.Shutdown(ev) {
|
||||||
|
|
|
@ -49,6 +49,7 @@ type EditorUI struct {
|
||||||
doodadWindow *ui.Window
|
doodadWindow *ui.Window
|
||||||
paletteEditor *ui.Window
|
paletteEditor *ui.Window
|
||||||
layersWindow *ui.Window
|
layersWindow *ui.Window
|
||||||
|
publishWindow *ui.Window
|
||||||
|
|
||||||
// Palette window.
|
// Palette window.
|
||||||
Palette *ui.Window
|
Palette *ui.Window
|
||||||
|
@ -522,6 +523,13 @@ func (u *EditorUI) SetupMenuBar(d *Doodle) *ui.MenuBar {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if balance.Feature.EmbeddableDoodads && drawingType == "level" {
|
||||||
|
fileMenu.AddItem("Publish level", func() {
|
||||||
|
u.OpenPublishWindow()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fileMenu.AddItemAccel("Open...", "Ctrl-O*", func() {
|
fileMenu.AddItemAccel("Open...", "Ctrl-O*", func() {
|
||||||
u.Scene.ConfirmUnload(func() {
|
u.Scene.ConfirmUnload(func() {
|
||||||
d.GotoLoadMenu()
|
d.GotoLoadMenu()
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"git.kirsle.net/apps/doodle/pkg/doodads"
|
"git.kirsle.net/apps/doodle/pkg/doodads"
|
||||||
"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/modal"
|
||||||
"git.kirsle.net/apps/doodle/pkg/windows"
|
"git.kirsle.net/apps/doodle/pkg/windows"
|
||||||
"git.kirsle.net/go/render"
|
"git.kirsle.net/go/render"
|
||||||
"git.kirsle.net/go/ui"
|
"git.kirsle.net/go/ui"
|
||||||
|
@ -43,6 +44,14 @@ func (u *EditorUI) OpenDoodadDropper() {
|
||||||
u.doodadWindow.Show()
|
u.doodadWindow.Show()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OpenPublishWindow opens the Publisher window.
|
||||||
|
func (u *EditorUI) OpenPublishWindow() {
|
||||||
|
u.publishWindow.Hide()
|
||||||
|
u.publishWindow = nil
|
||||||
|
u.SetupPopups(u.d)
|
||||||
|
u.publishWindow.Show()
|
||||||
|
}
|
||||||
|
|
||||||
// SetupPopups preloads popup windows like the DoodadDropper.
|
// SetupPopups preloads popup windows like the DoodadDropper.
|
||||||
func (u *EditorUI) SetupPopups(d *Doodle) {
|
func (u *EditorUI) SetupPopups(d *Doodle) {
|
||||||
// Common window configure function.
|
// Common window configure function.
|
||||||
|
@ -56,6 +65,8 @@ func (u *EditorUI) SetupPopups(d *Doodle) {
|
||||||
X: (d.width / 2) - (size.W / 2),
|
X: (d.width / 2) - (size.W / 2),
|
||||||
Y: (d.height / 2) - (size.H / 2),
|
Y: (d.height / 2) - (size.H / 2),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
window.Hide()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Doodad Dropper.
|
// Doodad Dropper.
|
||||||
|
@ -94,6 +105,30 @@ func (u *EditorUI) SetupPopups(d *Doodle) {
|
||||||
configure(u.levelSettingsWindow)
|
configure(u.levelSettingsWindow)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Publish Level (embed doodads)
|
||||||
|
if u.publishWindow == nil {
|
||||||
|
scene, _ := d.Scene.(*EditorScene)
|
||||||
|
|
||||||
|
u.publishWindow = windows.NewPublishWindow(windows.Publish{
|
||||||
|
Supervisor: u.Supervisor,
|
||||||
|
Engine: d.Engine,
|
||||||
|
Level: scene.Level,
|
||||||
|
|
||||||
|
OnPublish: func() {
|
||||||
|
modal.Alert("Not Yet Implemented")
|
||||||
|
// filename, err := native.SaveFile("Save your level", "*.level")
|
||||||
|
// if err != nil {
|
||||||
|
// modal.Alert(err.Error())
|
||||||
|
// }
|
||||||
|
// log.Info("Write to: %s", filename)
|
||||||
|
},
|
||||||
|
OnCancel: func() {
|
||||||
|
u.publishWindow.Hide()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
configure(u.publishWindow)
|
||||||
|
}
|
||||||
|
|
||||||
// Palette Editor.
|
// Palette Editor.
|
||||||
if u.paletteEditor == nil {
|
if u.paletteEditor == nil {
|
||||||
scene, _ := d.Scene.(*EditorScene)
|
scene, _ := d.Scene.(*EditorScene)
|
||||||
|
|
|
@ -107,6 +107,20 @@ func DoodadDropper(ev *event.State) bool {
|
||||||
return ev.KeyDown("d")
|
return ev.KeyDown("d")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ShellKey (`) opens the developer console.
|
||||||
|
func ShellKey(ev *event.State) bool {
|
||||||
|
v := ev.KeyDown("`")
|
||||||
|
ev.SetKeyDown("`", false)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enter key.
|
||||||
|
func Enter(ev *event.State) bool {
|
||||||
|
v := ev.Enter
|
||||||
|
ev.Enter = false
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
// Shift key.
|
// Shift key.
|
||||||
func Shift(ev *event.State) bool {
|
func Shift(ev *event.State) bool {
|
||||||
return ev.Shift
|
return ev.Shift
|
||||||
|
|
|
@ -54,6 +54,7 @@ func makeAlert(m *Modal) *ui.Window {
|
||||||
Text: "Ok",
|
Text: "Ok",
|
||||||
Font: balance.MenuFont,
|
Font: balance.MenuFont,
|
||||||
}))
|
}))
|
||||||
|
button.SetStyle(&balance.ButtonPrimary)
|
||||||
button.Handle(ui.Click, func(ev ui.EventData) error {
|
button.Handle(ui.Click, func(ev ui.EventData) error {
|
||||||
log.Info("clicked!")
|
log.Info("clicked!")
|
||||||
m.Dismiss(true)
|
m.Dismiss(true)
|
||||||
|
|
|
@ -78,6 +78,11 @@ func makeConfirm(m *Modal) *ui.Window {
|
||||||
button.Compute(engine)
|
button.Compute(engine)
|
||||||
supervisor.Add(button)
|
supervisor.Add(button)
|
||||||
|
|
||||||
|
// OK Button is primary.
|
||||||
|
if btn.Label == "Ok" {
|
||||||
|
button.SetStyle(&balance.ButtonPrimary)
|
||||||
|
}
|
||||||
|
|
||||||
btnBar.Pack(button, ui.Pack{
|
btnBar.Pack(button, ui.Pack{
|
||||||
Side: ui.W,
|
Side: ui.W,
|
||||||
PadX: 2,
|
PadX: 2,
|
||||||
|
|
|
@ -3,6 +3,7 @@ package modal
|
||||||
|
|
||||||
import (
|
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/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"
|
||||||
|
@ -48,6 +49,12 @@ func Handled(ev *event.State) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Enter key submits the default button.
|
||||||
|
if keybind.Enter(ev) {
|
||||||
|
current.Dismiss(true)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
supervisor.Loop(ev)
|
supervisor.Loop(ev)
|
||||||
|
|
||||||
// Has the window changed size?
|
// Has the window changed size?
|
||||||
|
|
|
@ -3,8 +3,9 @@
|
||||||
package native
|
package native
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/gen2brain/dlgs"
|
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
|
"github.com/gen2brain/dlgs"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -28,3 +29,21 @@ func OpenFile(title string, filter string) (string, error) {
|
||||||
}
|
}
|
||||||
return "", errors.New("canceled")
|
return "", errors.New("canceled")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SaveFile invokes a native File Chooser dialog with the title
|
||||||
|
// and a set of file filters. The filters are a sequence of label
|
||||||
|
// and comma-separated file extensions.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
// SaveFile("Pick a file", "Images", "png,gif,jpg", "Audio", "mp3")
|
||||||
|
func SaveFile(title string, filter string) (string, error) {
|
||||||
|
filename, ok, err := dlgs.File(title, filter, false)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if ok {
|
||||||
|
return filename, nil
|
||||||
|
}
|
||||||
|
return "", errors.New("canceled")
|
||||||
|
}
|
||||||
|
|
|
@ -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/keybind"
|
||||||
"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/go/render"
|
"git.kirsle.net/go/render"
|
||||||
|
@ -215,7 +216,7 @@ func (s *Shell) Draw(d *Doodle, ev *event.State) error {
|
||||||
if ev.Escape {
|
if ev.Escape {
|
||||||
s.Close()
|
s.Close()
|
||||||
return nil
|
return nil
|
||||||
} else if ev.Enter {
|
} else if keybind.Enter(ev) {
|
||||||
s.Execute(s.Text)
|
s.Execute(s.Text)
|
||||||
|
|
||||||
// Auto-close the console unless in REPL mode.
|
// Auto-close the console unless in REPL mode.
|
||||||
|
@ -223,7 +224,6 @@ func (s *Shell) Draw(d *Doodle, ev *event.State) error {
|
||||||
s.Close()
|
s.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
ev.Enter = false
|
|
||||||
return nil
|
return nil
|
||||||
} else if (ev.Up || ev.Down) && len(s.History) > 0 {
|
} else if (ev.Up || ev.Down) && len(s.History) > 0 {
|
||||||
// Paging through history.
|
// Paging through history.
|
||||||
|
|
|
@ -5,6 +5,8 @@ import (
|
||||||
|
|
||||||
"git.kirsle.net/apps/doodle/pkg/balance"
|
"git.kirsle.net/apps/doodle/pkg/balance"
|
||||||
"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/native"
|
||||||
"git.kirsle.net/apps/doodle/pkg/userdir"
|
"git.kirsle.net/apps/doodle/pkg/userdir"
|
||||||
"git.kirsle.net/go/render"
|
"git.kirsle.net/go/render"
|
||||||
"git.kirsle.net/go/ui"
|
"git.kirsle.net/go/ui"
|
||||||
|
@ -103,6 +105,40 @@ func NewOpenLevelEditor(config OpenLevelEditor) *ui.Window {
|
||||||
}(i, lvl)
|
}(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
|
* Frame for selecting User Doodads
|
||||||
******************/
|
******************/
|
||||||
|
@ -155,6 +191,40 @@ func NewOpenLevelEditor(config OpenLevelEditor) *ui.Window {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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.
|
* Confirm/cancel buttons.
|
||||||
******************/
|
******************/
|
||||||
|
|
328
pkg/windows/publish_level.go
Normal file
328
pkg/windows/publish_level.go
Normal file
|
@ -0,0 +1,328 @@
|
||||||
|
package windows
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.kirsle.net/apps/doodle/pkg/balance"
|
||||||
|
"git.kirsle.net/apps/doodle/pkg/doodads"
|
||||||
|
"git.kirsle.net/apps/doodle/pkg/level"
|
||||||
|
"git.kirsle.net/apps/doodle/pkg/log"
|
||||||
|
"git.kirsle.net/go/render"
|
||||||
|
"git.kirsle.net/go/ui"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Publish window.
|
||||||
|
type Publish struct {
|
||||||
|
// Settings passed in by doodle
|
||||||
|
Supervisor *ui.Supervisor
|
||||||
|
Engine render.Engine
|
||||||
|
Level *level.Level
|
||||||
|
|
||||||
|
OnPublish func()
|
||||||
|
OnCancel func()
|
||||||
|
|
||||||
|
// Private vars.
|
||||||
|
includeBuiltins bool // show built-in doodads in checkbox-list.
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPublishWindow initializes the window.
|
||||||
|
func NewPublishWindow(cfg Publish) *ui.Window {
|
||||||
|
var (
|
||||||
|
windowWidth = 400
|
||||||
|
windowHeight = 300
|
||||||
|
page = 1
|
||||||
|
perPage = 4
|
||||||
|
pages = 1
|
||||||
|
maxPageButtons = 8
|
||||||
|
|
||||||
|
// columns and sizes to draw the doodad list
|
||||||
|
columns = 3
|
||||||
|
btnWidth = 120
|
||||||
|
btnHeight = 14
|
||||||
|
)
|
||||||
|
|
||||||
|
window := ui.NewWindow("Publish Level")
|
||||||
|
window.SetButtons(ui.CloseButton)
|
||||||
|
window.Configure(ui.Config{
|
||||||
|
Width: windowWidth,
|
||||||
|
Height: windowHeight,
|
||||||
|
Background: render.RGBA(200, 200, 255, 255),
|
||||||
|
})
|
||||||
|
|
||||||
|
/////////////
|
||||||
|
// Intro text
|
||||||
|
|
||||||
|
introFrame := ui.NewFrame("Intro Frame")
|
||||||
|
window.Pack(introFrame, ui.Pack{
|
||||||
|
Side: ui.N,
|
||||||
|
FillX: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
lines := []struct {
|
||||||
|
Text string
|
||||||
|
Font render.Text
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
Text: "About",
|
||||||
|
Font: balance.LabelFont,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Text: "Share your level easily! If you are using custom doodads in\n" +
|
||||||
|
"your level, you may attach them directly to your\n" +
|
||||||
|
"level file -- so it can easily run on another computer!",
|
||||||
|
Font: balance.UIFont,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Text: "List of Doodads in Your Level",
|
||||||
|
Font: balance.LabelFont,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for n, row := range lines {
|
||||||
|
frame := ui.NewFrame(fmt.Sprintf("Intro Line %d", n))
|
||||||
|
introFrame.Pack(frame, ui.Pack{
|
||||||
|
Side: ui.N,
|
||||||
|
FillX: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
label := ui.NewLabel(ui.Label{
|
||||||
|
Text: row.Text,
|
||||||
|
Font: row.Font,
|
||||||
|
})
|
||||||
|
frame.Pack(label, ui.Pack{
|
||||||
|
Side: ui.W,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////
|
||||||
|
// Custom Doodads checkbox-list.
|
||||||
|
doodadFrame := ui.NewFrame("Doodads Frame")
|
||||||
|
doodadFrame.Resize(render.Rect{
|
||||||
|
W: windowWidth,
|
||||||
|
H: btnHeight*perPage + 100,
|
||||||
|
})
|
||||||
|
window.Pack(doodadFrame, ui.Pack{
|
||||||
|
Side: ui.N,
|
||||||
|
FillX: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
// First, the checkbox to show built-in doodads or not.
|
||||||
|
builtinRow := ui.NewFrame("Show Builtins Frame")
|
||||||
|
doodadFrame.Pack(builtinRow, ui.Pack{
|
||||||
|
Side: ui.N,
|
||||||
|
FillX: true,
|
||||||
|
})
|
||||||
|
builtinCB := ui.NewCheckbox("Show Builtins", &cfg.includeBuiltins, ui.NewLabel(ui.Label{
|
||||||
|
Text: "Attach built-in* doodads too",
|
||||||
|
Font: balance.UIFont,
|
||||||
|
}))
|
||||||
|
builtinCB.Supervise(cfg.Supervisor)
|
||||||
|
builtinRow.Pack(builtinCB, ui.Pack{
|
||||||
|
Side: ui.W,
|
||||||
|
PadX: 2,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Collect all the doodad names in use in this level.
|
||||||
|
unique := map[string]interface{}{}
|
||||||
|
names := []string{}
|
||||||
|
if cfg.Level != nil {
|
||||||
|
for _, actor := range cfg.Level.Actors {
|
||||||
|
if _, ok := unique[actor.Filename]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
unique[actor.Filename] = nil
|
||||||
|
names = append(names, actor.Filename)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Strings(names)
|
||||||
|
|
||||||
|
// Identify which of the doodads are built-ins.
|
||||||
|
usedBuiltins := []string{}
|
||||||
|
builtinMap := map[string]interface{}{}
|
||||||
|
usedCustom := []string{}
|
||||||
|
if builtins, err := doodads.ListBuiltin(); err == nil {
|
||||||
|
for _, filename := range builtins {
|
||||||
|
if _, ok := unique[filename]; ok {
|
||||||
|
usedBuiltins = append(usedBuiltins, filename)
|
||||||
|
builtinMap[filename] = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, name := range names {
|
||||||
|
if _, ok := builtinMap[name]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
usedCustom = append(usedCustom, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to draw the button rows for a set of doodads.
|
||||||
|
mkDoodadRows := func(filenames []string, builtin bool) []*ui.Frame {
|
||||||
|
var (
|
||||||
|
curRow *ui.Frame // = ui.NewFrame("mkDoodadRows 0")
|
||||||
|
frames = []*ui.Frame{}
|
||||||
|
)
|
||||||
|
|
||||||
|
for i, name := range filenames {
|
||||||
|
if i%columns == 0 {
|
||||||
|
curRow = ui.NewFrame(fmt.Sprintf("mkDoodadRows %d", i))
|
||||||
|
frames = append(frames, curRow)
|
||||||
|
}
|
||||||
|
|
||||||
|
font := balance.UIFont
|
||||||
|
if builtin {
|
||||||
|
font.Color = render.Blue
|
||||||
|
name += "*"
|
||||||
|
}
|
||||||
|
|
||||||
|
btn := ui.NewLabel(ui.Label{
|
||||||
|
Text: strings.Replace(name, ".doodad", "", 1),
|
||||||
|
Font: font,
|
||||||
|
})
|
||||||
|
btn.Configure(ui.Config{
|
||||||
|
Width: btnWidth,
|
||||||
|
Height: btnHeight,
|
||||||
|
})
|
||||||
|
curRow.Pack(btn, ui.Pack{
|
||||||
|
Side: ui.W,
|
||||||
|
PadX: 2,
|
||||||
|
PadY: 2,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return frames
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. Draw the built-in doodads in use.
|
||||||
|
var (
|
||||||
|
btnRows = []*ui.Frame{}
|
||||||
|
builtinRows = []*ui.Frame{}
|
||||||
|
customRows = []*ui.Frame{}
|
||||||
|
)
|
||||||
|
if len(names) > 0 {
|
||||||
|
customRows = mkDoodadRows(usedCustom, false)
|
||||||
|
btnRows = append(btnRows, customRows...)
|
||||||
|
}
|
||||||
|
if len(usedBuiltins) > 0 {
|
||||||
|
builtinRows = mkDoodadRows(usedBuiltins, true)
|
||||||
|
btnRows = append(btnRows, builtinRows...)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, row := range btnRows {
|
||||||
|
doodadFrame.Pack(row, ui.Pack{
|
||||||
|
Side: ui.N,
|
||||||
|
FillX: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Hide if too long for 1st page.
|
||||||
|
if i >= perPage {
|
||||||
|
row.Hide()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////
|
||||||
|
// Buttons at bottom of window
|
||||||
|
|
||||||
|
bottomFrame := ui.NewFrame("Button Frame")
|
||||||
|
window.Pack(bottomFrame, ui.Pack{
|
||||||
|
Side: ui.S,
|
||||||
|
FillX: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Pager for the doodads.
|
||||||
|
pages = int(
|
||||||
|
math.Ceil(
|
||||||
|
float64(len(btnRows)) / float64(perPage),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
pagerOnChange := func(newPage, perPage int) {
|
||||||
|
page = newPage
|
||||||
|
log.Info("Page: %d, %d", page, perPage)
|
||||||
|
|
||||||
|
// Re-evaluate which rows are shown/hidden for the page we're on.
|
||||||
|
var (
|
||||||
|
minRow = (page - 1) * perPage
|
||||||
|
visible = 0
|
||||||
|
)
|
||||||
|
for i, row := range btnRows {
|
||||||
|
if visible >= perPage {
|
||||||
|
row.Hide()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if i < minRow {
|
||||||
|
row.Hide()
|
||||||
|
} else {
|
||||||
|
row.Show()
|
||||||
|
visible++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pager := ui.NewPager(ui.Pager{
|
||||||
|
Name: "Doodads List Pager",
|
||||||
|
Page: page,
|
||||||
|
Pages: pages,
|
||||||
|
PerPage: perPage,
|
||||||
|
MaxPageButtons: maxPageButtons,
|
||||||
|
Font: balance.MenuFont,
|
||||||
|
OnChange: pagerOnChange,
|
||||||
|
})
|
||||||
|
pager.Compute(cfg.Engine)
|
||||||
|
pager.Supervise(cfg.Supervisor)
|
||||||
|
bottomFrame.Place(pager, ui.Place{
|
||||||
|
Top: 20,
|
||||||
|
Left: 20,
|
||||||
|
})
|
||||||
|
|
||||||
|
frame := ui.NewFrame("Button frame")
|
||||||
|
buttons := []struct {
|
||||||
|
label string
|
||||||
|
primary bool
|
||||||
|
f func()
|
||||||
|
}{
|
||||||
|
{"Export Level", true, func() {
|
||||||
|
if cfg.OnPublish != nil {
|
||||||
|
cfg.OnPublish()
|
||||||
|
}
|
||||||
|
}},
|
||||||
|
{"Close", false, func() {
|
||||||
|
if cfg.OnCancel != nil {
|
||||||
|
cfg.OnCancel()
|
||||||
|
}
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
for _, button := range buttons {
|
||||||
|
button := button
|
||||||
|
|
||||||
|
btn := ui.NewButton(button.label, ui.NewLabel(ui.Label{
|
||||||
|
Text: button.label,
|
||||||
|
Font: balance.MenuFont,
|
||||||
|
}))
|
||||||
|
if button.primary {
|
||||||
|
btn.SetStyle(&balance.ButtonPrimary)
|
||||||
|
}
|
||||||
|
|
||||||
|
btn.Handle(ui.Click, func(ed ui.EventData) error {
|
||||||
|
button.f()
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
btn.Compute(cfg.Engine)
|
||||||
|
cfg.Supervisor.Add(btn)
|
||||||
|
|
||||||
|
frame.Pack(btn, ui.Pack{
|
||||||
|
Side: ui.W,
|
||||||
|
PadX: 4,
|
||||||
|
Expand: true,
|
||||||
|
Fill: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
bottomFrame.Pack(frame, ui.Pack{
|
||||||
|
Side: ui.E,
|
||||||
|
Padding: 8,
|
||||||
|
})
|
||||||
|
|
||||||
|
return window
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user