Editor: Doodad Properties Window
The Doodad Properties window brings many features that used to be available only in the `doodad` CLI tool into the Doodad Editor. * In the Doodad Editor there is a new menubar item: "Doodad" which corresponds to the "Level" menu when you're editing a level. * The "Doodad" menu has two items: - "Doodad Properties" (NEW) - "Layers" (moved here from the Tools menu) * The Doodad Properties window lets you edit the Title and Author values of the doodad, as well as modify its Tags and manage its Script. * Its script can be attached (browse for .js file on disk), its existing script saved back to disk (dev shell prompt) or deleted altogether from the doodad. * You can create, modify, and delete Tags on the doodad. Other changes: * In the Level Editor, the "Level->Page Settings" menu is renamed to "Level->Level Properties" to match with "Doodad->Doodad Properties" and the pop-up window is retitled accordingly. * The Exit Flag only exits if the Player touches it - not just any mobile doodad!
This commit is contained in:
parent
0cc1d17f4f
commit
0fa1bf8a76
|
@ -7,6 +7,11 @@ function main() {
|
|||
return;
|
||||
}
|
||||
|
||||
// Only care if it's the player.
|
||||
if (!e.Actor.IsPlayer()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.InHitbox) {
|
||||
EndLevel();
|
||||
}
|
||||
|
|
|
@ -105,6 +105,13 @@ var (
|
|||
Color: render.Black,
|
||||
}
|
||||
|
||||
LargeLabelFont = render.Text{
|
||||
Size: 18,
|
||||
FontFilename: "DejaVuSans-Bold.ttf",
|
||||
Padding: 4,
|
||||
Color: render.Black,
|
||||
}
|
||||
|
||||
// SmallMonoFont for cramped spaces like the +/- buttons on Toolbar.
|
||||
SmallMonoFont = render.Text{
|
||||
Size: 14,
|
||||
|
|
|
@ -44,6 +44,7 @@ type EditorUI struct {
|
|||
|
||||
// Popup windows.
|
||||
levelSettingsWindow *ui.Window
|
||||
doodadPropertiesWindow *ui.Window
|
||||
aboutWindow *ui.Window
|
||||
doodadWindow *ui.Window
|
||||
paletteEditor *ui.Window
|
||||
|
|
|
@ -117,7 +117,7 @@ func (u *EditorUI) SetupMenuBar(d *Doodle) *ui.MenuBar {
|
|||
// Level menu
|
||||
if u.Scene.DrawingType == enum.LevelDrawing {
|
||||
levelMenu := menu.AddMenu("Level")
|
||||
levelMenu.AddItem("Page settings", func() {
|
||||
levelMenu.AddItem("Level Properties", func() {
|
||||
log.Info("Opening the window")
|
||||
|
||||
// Open the New Level window in edit-settings mode.
|
||||
|
@ -135,6 +135,25 @@ func (u *EditorUI) SetupMenuBar(d *Doodle) *ui.MenuBar {
|
|||
})
|
||||
}
|
||||
|
||||
////////
|
||||
// Doodad Menu
|
||||
if u.Scene.DrawingType == enum.DoodadDrawing {
|
||||
levelMenu := menu.AddMenu("Doodad")
|
||||
levelMenu.AddItem("Doodad Properties", func() {
|
||||
log.Info("Opening the window")
|
||||
|
||||
// Open the New Level window in edit-settings mode.
|
||||
u.doodadPropertiesWindow.Hide()
|
||||
u.doodadPropertiesWindow = nil
|
||||
u.SetupPopups(u.d)
|
||||
u.doodadPropertiesWindow.Show()
|
||||
})
|
||||
|
||||
levelMenu.AddItem("Layers", func() {
|
||||
u.OpenLayersWindow()
|
||||
})
|
||||
}
|
||||
|
||||
////////
|
||||
// View menu
|
||||
if balance.Feature.Zoom {
|
||||
|
@ -169,11 +188,6 @@ func (u *EditorUI) SetupMenuBar(d *Doodle) *ui.MenuBar {
|
|||
toolMenu.AddItem("Edit Palette", func() {
|
||||
u.OpenPaletteWindow()
|
||||
})
|
||||
if u.Scene.DrawingType == enum.DoodadDrawing {
|
||||
toolMenu.AddItem("Layers", func() {
|
||||
u.OpenLayersWindow()
|
||||
})
|
||||
}
|
||||
|
||||
// Draw Tools
|
||||
toolMenu.AddItemAccel("Pencil Tool", "F", func() {
|
||||
|
|
|
@ -140,6 +140,28 @@ func (u *EditorUI) SetupPopups(d *Doodle) {
|
|||
configure(u.levelSettingsWindow)
|
||||
}
|
||||
|
||||
// Doodad Properties
|
||||
if u.doodadPropertiesWindow == nil {
|
||||
scene, _ := d.Scene.(*EditorScene)
|
||||
|
||||
cfg := &windows.DoodadProperties{
|
||||
Supervisor: u.Supervisor,
|
||||
Engine: d.Engine,
|
||||
EditDoodad: scene.Doodad,
|
||||
}
|
||||
|
||||
// Rebuild the window. TODO: hacky af.
|
||||
cfg.OnRefresh = func() {
|
||||
u.doodadPropertiesWindow.Hide()
|
||||
u.doodadPropertiesWindow = nil
|
||||
u.SetupPopups(u.d)
|
||||
u.doodadPropertiesWindow.Show()
|
||||
}
|
||||
|
||||
u.doodadPropertiesWindow = windows.NewDoodadPropertiesWindow(cfg)
|
||||
configure(u.doodadPropertiesWindow)
|
||||
}
|
||||
|
||||
// Publish Level (embed doodads)
|
||||
if u.publishWindow == nil {
|
||||
scene, _ := d.Scene.(*EditorScene)
|
||||
|
|
|
@ -50,7 +50,7 @@ func NewAddEditLevel(config AddEditLevel) *ui.Window {
|
|||
newPageType = config.EditLevel.PageType.String()
|
||||
newWallpaper = config.EditLevel.Wallpaper
|
||||
paletteName = textCurrentPalette
|
||||
title = "Page Settings"
|
||||
title = "Level Properties"
|
||||
}
|
||||
|
||||
window := ui.NewWindow(title)
|
||||
|
@ -272,7 +272,7 @@ func NewAddEditLevel(config AddEditLevel) *ui.Window {
|
|||
|
||||
if config.EditLevel != nil {
|
||||
palBtn.AddItem(paletteName, paletteName, func() {})
|
||||
palBtn.AddSeparator();
|
||||
palBtn.AddSeparator()
|
||||
}
|
||||
|
||||
for _, palName := range level.DefaultPaletteNames {
|
||||
|
@ -411,7 +411,7 @@ func NewAddEditLevel(config AddEditLevel) *ui.Window {
|
|||
"if the new palette is smaller, some pixels may be\n" +
|
||||
"lost from your level. OK to continue?",
|
||||
).WithTitle("Change Level Palette").Then(func() {
|
||||
config.OnCancel();
|
||||
config.OnCancel()
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
|
455
pkg/windows/doodad_properties.go
Normal file
455
pkg/windows/doodad_properties.go
Normal file
|
@ -0,0 +1,455 @@
|
|||
package windows
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
|
||||
"git.kirsle.net/apps/doodle/pkg/balance"
|
||||
"git.kirsle.net/apps/doodle/pkg/doodads"
|
||||
"git.kirsle.net/apps/doodle/pkg/log"
|
||||
"git.kirsle.net/apps/doodle/pkg/modal"
|
||||
"git.kirsle.net/apps/doodle/pkg/native"
|
||||
"git.kirsle.net/apps/doodle/pkg/shmem"
|
||||
"git.kirsle.net/go/render"
|
||||
"git.kirsle.net/go/ui"
|
||||
)
|
||||
|
||||
// DoodadProperties window.
|
||||
type DoodadProperties struct {
|
||||
// Settings passed in by doodle
|
||||
Supervisor *ui.Supervisor
|
||||
Engine render.Engine
|
||||
|
||||
// Configuration options.
|
||||
EditDoodad *doodads.Doodad
|
||||
ActiveTab string // specify the tab to open
|
||||
OnRefresh func() // caller should rebuild the window
|
||||
|
||||
// Widgets.
|
||||
TabFrame *ui.TabFrame
|
||||
}
|
||||
|
||||
// HACKY GLOBAL VARIABLE
|
||||
var showTagsOnRefreshDoodadPropertiesWindow bool
|
||||
|
||||
// NewSettingsWindow initializes the window.
|
||||
func NewDoodadPropertiesWindow(cfg *DoodadProperties) *ui.Window {
|
||||
var (
|
||||
Width = 400
|
||||
Height = 300
|
||||
)
|
||||
|
||||
window := ui.NewWindow("Doodad Properties")
|
||||
window.SetButtons(ui.CloseButton)
|
||||
window.Configure(ui.Config{
|
||||
Width: Width,
|
||||
Height: Height,
|
||||
Background: render.Grey,
|
||||
})
|
||||
|
||||
///////////
|
||||
// Tab Bar
|
||||
tabFrame := ui.NewTabFrame("Tab Frame")
|
||||
tabFrame.SetBackground(render.DarkGrey)
|
||||
window.Pack(tabFrame, ui.Pack{
|
||||
Side: ui.N,
|
||||
FillX: true,
|
||||
})
|
||||
cfg.TabFrame = tabFrame
|
||||
|
||||
// Make the tabs.
|
||||
cfg.makeMetaTab(tabFrame, Width, Height)
|
||||
cfg.makeTagsTab(tabFrame, Width, Height)
|
||||
|
||||
if showTagsOnRefreshDoodadPropertiesWindow {
|
||||
tabFrame.SetTab("Tags")
|
||||
showTagsOnRefreshDoodadPropertiesWindow = false
|
||||
}
|
||||
|
||||
tabFrame.Supervise(cfg.Supervisor)
|
||||
|
||||
return window
|
||||
}
|
||||
|
||||
// DoodadProperties Window "Metadata" Tab
|
||||
func (c DoodadProperties) makeMetaTab(tabFrame *ui.TabFrame, Width, Height int) *ui.Frame {
|
||||
tab := tabFrame.AddTab("Metadata", ui.NewLabel(ui.Label{
|
||||
Text: "Metadata",
|
||||
Font: balance.TabFont,
|
||||
}))
|
||||
tab.Resize(render.NewRect(Width-4, Height-tab.Size().H-46))
|
||||
|
||||
if c.EditDoodad == nil {
|
||||
return tab
|
||||
}
|
||||
|
||||
//////////////
|
||||
// Draw the editable metadata form.
|
||||
for _, data := range []struct {
|
||||
Label string
|
||||
Variable *string
|
||||
Update func(string)
|
||||
}{
|
||||
{
|
||||
Label: "Title:",
|
||||
Variable: &c.EditDoodad.Title,
|
||||
Update: func(v string) {
|
||||
c.EditDoodad.Title = v
|
||||
},
|
||||
},
|
||||
{
|
||||
Label: "Author:",
|
||||
Variable: &c.EditDoodad.Author,
|
||||
Update: func(v string) {
|
||||
c.EditDoodad.Author = v
|
||||
},
|
||||
},
|
||||
} {
|
||||
data := data
|
||||
frame := ui.NewFrame("Metadata " + data.Label + " Frame")
|
||||
tab.Pack(frame, ui.Pack{
|
||||
Side: ui.N,
|
||||
PadY: 4,
|
||||
FillX: true,
|
||||
})
|
||||
|
||||
// The label
|
||||
label := ui.NewLabel(ui.Label{
|
||||
Text: data.Label,
|
||||
Font: balance.MenuFont,
|
||||
})
|
||||
label.Configure(ui.Config{
|
||||
Width: 75,
|
||||
})
|
||||
frame.Pack(label, ui.Pack{
|
||||
Side: ui.W,
|
||||
})
|
||||
|
||||
// The button.
|
||||
btn := ui.NewButton(data.Label, ui.NewLabel(ui.Label{
|
||||
TextVariable: data.Variable,
|
||||
Font: balance.MenuFont,
|
||||
}))
|
||||
btn.Handle(ui.Click, func(ed ui.EventData) error {
|
||||
shmem.Prompt("Enter a new "+data.Label+" ", func(answer string) {
|
||||
if answer != "" {
|
||||
data.Update(answer)
|
||||
}
|
||||
})
|
||||
return nil
|
||||
})
|
||||
c.Supervisor.Add(btn)
|
||||
frame.Pack(btn, ui.Pack{
|
||||
Side: ui.W,
|
||||
Expand: true,
|
||||
PadX: 2,
|
||||
})
|
||||
}
|
||||
|
||||
//////////////////////////////////
|
||||
// Draw the JavaScript management
|
||||
|
||||
scriptHeader := ui.NewLabel(ui.Label{
|
||||
Text: "Doodad Script",
|
||||
Font: balance.LargeLabelFont,
|
||||
})
|
||||
tab.Pack(scriptHeader, ui.Pack{
|
||||
Side: ui.N,
|
||||
FillX: true,
|
||||
PadY: 8,
|
||||
})
|
||||
|
||||
// Frame for if a script does exist on the doodad.
|
||||
var (
|
||||
ifScript *ui.Frame
|
||||
elseScript *ui.Frame
|
||||
)
|
||||
|
||||
// "If Script" Frame
|
||||
{
|
||||
ifScript = ui.NewFrame("If Script")
|
||||
tab.Pack(ifScript, ui.Pack{
|
||||
Side: ui.N,
|
||||
FillX: true,
|
||||
})
|
||||
|
||||
label := ui.NewLabel(ui.Label{
|
||||
Text: "This Doodad has a script attached.",
|
||||
Font: balance.MenuFont,
|
||||
})
|
||||
ifScript.Pack(label, ui.Pack{
|
||||
Side: ui.W,
|
||||
})
|
||||
|
||||
// Delete Button
|
||||
deleteBtn := ui.NewButton("Save", ui.NewLabel(ui.Label{
|
||||
Text: "Delete",
|
||||
Font: balance.MenuFont,
|
||||
}))
|
||||
deleteBtn.SetStyle(&balance.ButtonDanger)
|
||||
deleteBtn.Handle(ui.Click, func(ed ui.EventData) error {
|
||||
modal.Confirm("Are you sure you want to delete this script?").Then(func() {
|
||||
c.EditDoodad.Script = ""
|
||||
ifScript.Hide()
|
||||
elseScript.Show()
|
||||
})
|
||||
return nil
|
||||
})
|
||||
c.Supervisor.Add(deleteBtn)
|
||||
ifScript.Pack(deleteBtn, ui.Pack{
|
||||
Side: ui.E,
|
||||
PadX: 2,
|
||||
})
|
||||
|
||||
// Save Button
|
||||
saveBtn := ui.NewButton("Save", ui.NewLabel(ui.Label{
|
||||
Text: "Save",
|
||||
Font: balance.MenuFont,
|
||||
}))
|
||||
saveBtn.SetStyle(&balance.ButtonPrimary)
|
||||
saveBtn.Handle(ui.Click, func(ed ui.EventData) error {
|
||||
shmem.Prompt("Save script as (*.js): ", func(answer string) {
|
||||
if answer != "" {
|
||||
cwd, _ := os.Getwd()
|
||||
err := ioutil.WriteFile(answer, []byte(c.EditDoodad.Script), 0644)
|
||||
if err != nil {
|
||||
shmem.Flash(err.Error())
|
||||
} else {
|
||||
shmem.Flash("Written to: %s (%d bytes)", filepath.Join(cwd, answer), len(c.EditDoodad.Script))
|
||||
}
|
||||
}
|
||||
})
|
||||
return nil
|
||||
})
|
||||
c.Supervisor.Add(saveBtn)
|
||||
ifScript.Pack(saveBtn, ui.Pack{
|
||||
Side: ui.E,
|
||||
PadX: 2,
|
||||
})
|
||||
}
|
||||
|
||||
// "Else Script" Frame
|
||||
{
|
||||
elseScript = ui.NewFrame("If Script")
|
||||
tab.Pack(elseScript, ui.Pack{
|
||||
Side: ui.N,
|
||||
FillX: true,
|
||||
})
|
||||
|
||||
label := ui.NewLabel(ui.Label{
|
||||
Text: "There is no script attached to this doodad.",
|
||||
Font: balance.MenuFont,
|
||||
})
|
||||
elseScript.Pack(label, ui.Pack{
|
||||
Side: ui.W,
|
||||
})
|
||||
}
|
||||
|
||||
// Browse Script button.
|
||||
btnBrowse := ui.NewButton("Browse Script", ui.NewLabel(ui.Label{
|
||||
Text: "Attach a script...",
|
||||
Font: balance.MenuFont,
|
||||
}))
|
||||
btnBrowse.SetStyle(&balance.ButtonPrimary)
|
||||
btnBrowse.Handle(ui.Click, func(ed ui.EventData) error {
|
||||
filename, err := native.OpenFile("Choose a .js file", "*.js")
|
||||
if err != nil {
|
||||
shmem.Flash("Couldn't show file dialog: %s", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
data, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
shmem.Flash("Couldn't read file: %s", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
c.EditDoodad.Script = string(data)
|
||||
shmem.Flash("Attached %d-byte script to this doodad.", len(c.EditDoodad.Script))
|
||||
|
||||
// Toggle the if/else frames.
|
||||
ifScript.Show()
|
||||
elseScript.Hide()
|
||||
|
||||
return nil
|
||||
})
|
||||
c.Supervisor.Add(btnBrowse)
|
||||
tab.Pack(btnBrowse, ui.Pack{
|
||||
Side: ui.N,
|
||||
Padding: 4,
|
||||
})
|
||||
|
||||
// Show/hide appropriate frames.
|
||||
if c.EditDoodad.Script == "" {
|
||||
ifScript.Hide()
|
||||
elseScript.Show()
|
||||
} else {
|
||||
ifScript.Show()
|
||||
elseScript.Hide()
|
||||
}
|
||||
|
||||
return tab
|
||||
}
|
||||
|
||||
// DoodadProperties Window "Tags" Tab
|
||||
func (c DoodadProperties) makeTagsTab(tabFrame *ui.TabFrame, Width, Height int) *ui.Frame {
|
||||
tab := tabFrame.AddTab("Tags", ui.NewLabel(ui.Label{
|
||||
Text: "Tags",
|
||||
Font: balance.TabFont,
|
||||
}))
|
||||
tab.Resize(render.NewRect(Width-4, Height-tab.Size().H-46))
|
||||
|
||||
if c.EditDoodad == nil {
|
||||
return tab
|
||||
}
|
||||
|
||||
// Draw a table view of the current tags on this doodad.
|
||||
var (
|
||||
headers = []string{"Name", "Value", "Del."}
|
||||
columns = []int{150, 150, 80} // TODO, Width=400
|
||||
height = 24
|
||||
row = ui.NewFrame("HeaderRow")
|
||||
)
|
||||
tab.Pack(row, ui.Pack{
|
||||
Side: ui.N,
|
||||
FillX: true,
|
||||
})
|
||||
for i, value := range headers {
|
||||
cell := ui.NewLabel(ui.Label{
|
||||
Text: value,
|
||||
Font: balance.MenuFontBold,
|
||||
})
|
||||
cell.Resize(render.NewRect(columns[i], height))
|
||||
row.Pack(cell, ui.Pack{
|
||||
Side: ui.W,
|
||||
})
|
||||
}
|
||||
|
||||
// No tags?
|
||||
if len(c.EditDoodad.Tags) == 0 {
|
||||
label := ui.NewLabel(ui.Label{
|
||||
Text: "There are no tags on this doodad.",
|
||||
Font: balance.MenuFont,
|
||||
})
|
||||
tab.Pack(label, ui.Pack{
|
||||
Side: ui.N,
|
||||
FillX: true,
|
||||
})
|
||||
} else {
|
||||
// Draw the rows for each tag.
|
||||
var sortedTags []string
|
||||
for name := range c.EditDoodad.Tags {
|
||||
sortedTags = append(sortedTags, name)
|
||||
}
|
||||
sort.Strings(sortedTags)
|
||||
|
||||
for _, tagName := range sortedTags {
|
||||
var (
|
||||
name = tagName
|
||||
value = c.EditDoodad.Tags[name]
|
||||
)
|
||||
|
||||
row = ui.NewFrame("Tag Row")
|
||||
tab.Pack(row, ui.Pack{
|
||||
Side: ui.N,
|
||||
FillX: true,
|
||||
PadY: 2,
|
||||
})
|
||||
|
||||
lblName := ui.NewLabel(ui.Label{
|
||||
Text: name,
|
||||
Font: balance.MenuFont,
|
||||
})
|
||||
lblName.Resize(render.NewRect(columns[0], height))
|
||||
|
||||
btnValue := ui.NewButton("Tag Button", ui.NewLabel(ui.Label{
|
||||
Text: value,
|
||||
Font: balance.MenuFont,
|
||||
}))
|
||||
btnValue.Resize(render.NewRect(columns[1], height))
|
||||
btnValue.Handle(ui.Click, func(ed ui.EventData) error {
|
||||
shmem.Prompt("Enter new value: ", func(answer string) {
|
||||
if answer == "" {
|
||||
return
|
||||
}
|
||||
c.EditDoodad.Tags[name] = answer
|
||||
btnValue.SetText(answer)
|
||||
})
|
||||
return nil
|
||||
})
|
||||
c.Supervisor.Add(btnValue)
|
||||
|
||||
btnDelete := ui.NewButton("Delete Button", ui.NewLabel(ui.Label{
|
||||
Text: "Delete",
|
||||
Font: balance.MenuFont,
|
||||
}))
|
||||
btnDelete.Resize(render.NewRect(columns[2], height))
|
||||
btnDelete.SetStyle(&balance.ButtonDanger)
|
||||
btnDelete.Handle(ui.Click, func(ed ui.EventData) error {
|
||||
modal.Confirm("Delete tag %s?", name).Then(func() {
|
||||
log.Info("Delete tag: %s", name)
|
||||
delete(c.EditDoodad.Tags, name)
|
||||
|
||||
// Trigger a refresh.
|
||||
if c.OnRefresh != nil {
|
||||
showTagsOnRefreshDoodadPropertiesWindow = true
|
||||
c.OnRefresh()
|
||||
}
|
||||
})
|
||||
return nil
|
||||
})
|
||||
c.Supervisor.Add(btnDelete)
|
||||
|
||||
// Pack the widgets.
|
||||
row.Pack(lblName, ui.Pack{
|
||||
Side: ui.W,
|
||||
})
|
||||
row.Pack(btnValue, ui.Pack{
|
||||
Side: ui.W,
|
||||
PadX: 4,
|
||||
})
|
||||
row.Pack(btnDelete, ui.Pack{
|
||||
Side: ui.W,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Add Tag button.
|
||||
row = ui.NewFrame("Button Frame")
|
||||
tab.Pack(row, ui.Pack{
|
||||
Side: ui.N,
|
||||
FillX: true,
|
||||
})
|
||||
btnAdd := ui.NewButton("New Tag", ui.NewLabel(ui.Label{
|
||||
Text: "Add Tag",
|
||||
Font: balance.MenuFont,
|
||||
}))
|
||||
btnAdd.SetStyle(&balance.ButtonPrimary)
|
||||
btnAdd.Handle(ui.Click, func(ed ui.EventData) error {
|
||||
shmem.Prompt("Enter name of the new tag: ", func(answer string) {
|
||||
if answer == "" {
|
||||
return
|
||||
}
|
||||
|
||||
log.Info("Adding doodad tag: %s", answer)
|
||||
c.EditDoodad.Tags[answer] = ""
|
||||
if c.OnRefresh != nil {
|
||||
showTagsOnRefreshDoodadPropertiesWindow = true
|
||||
c.OnRefresh()
|
||||
}
|
||||
})
|
||||
return nil
|
||||
})
|
||||
c.Supervisor.Add(btnAdd)
|
||||
row.Pack(btnAdd, ui.Pack{
|
||||
Side: ui.E,
|
||||
})
|
||||
|
||||
return tab
|
||||
}
|
||||
|
||||
func (c DoodadProperties) reloadTagFrame() {
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user