320 lines
7.4 KiB
Go
320 lines
7.4 KiB
Go
|
package windows
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"sort"
|
||
|
|
||
|
"git.kirsle.net/SketchyMaze/doodle/pkg/balance"
|
||
|
"git.kirsle.net/SketchyMaze/doodle/pkg/level"
|
||
|
"git.kirsle.net/SketchyMaze/doodle/pkg/log"
|
||
|
"git.kirsle.net/SketchyMaze/doodle/pkg/shmem"
|
||
|
"git.kirsle.net/SketchyMaze/doodle/pkg/uix"
|
||
|
magicform "git.kirsle.net/SketchyMaze/doodle/pkg/uix/magic-form"
|
||
|
"git.kirsle.net/go/render"
|
||
|
"git.kirsle.net/go/ui"
|
||
|
)
|
||
|
|
||
|
// DoodadConfig window is what pops up in Edit Mode when you mouse over
|
||
|
// an actor and click its properties button.
|
||
|
type DoodadConfig struct {
|
||
|
// Settings passed in by doodle
|
||
|
Supervisor *ui.Supervisor
|
||
|
Engine render.Engine
|
||
|
|
||
|
// Configuration options.
|
||
|
EditActor *uix.Actor
|
||
|
ActiveTab string // specify the tab to open
|
||
|
OnRefresh func() // caller should rebuild the window
|
||
|
|
||
|
// Widgets.
|
||
|
TabFrame *ui.TabFrame
|
||
|
}
|
||
|
|
||
|
// NewSettingsWindow initializes the window.
|
||
|
func NewDoodadConfigWindow(cfg *DoodadConfig) *ui.Window {
|
||
|
var (
|
||
|
Width = 400
|
||
|
Height = 300
|
||
|
)
|
||
|
|
||
|
window := ui.NewWindow(cfg.EditActor.Doodad().Title + " - Actor 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.makeOptionsTab(tabFrame, Width, Height)
|
||
|
|
||
|
tabFrame.Supervise(cfg.Supervisor)
|
||
|
|
||
|
return window
|
||
|
}
|
||
|
|
||
|
// DoodadConfig Window "Metadata" Tab
|
||
|
func (c *DoodadConfig) 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.EditActor == nil {
|
||
|
return tab
|
||
|
}
|
||
|
|
||
|
var (
|
||
|
doodad = c.EditActor.Doodad()
|
||
|
actorID = c.EditActor.ID()
|
||
|
// actorPos = c.EditActor.Position().String()
|
||
|
)
|
||
|
|
||
|
form := magicform.Form{
|
||
|
Supervisor: c.Supervisor,
|
||
|
Engine: c.Engine,
|
||
|
Vertical: true,
|
||
|
LabelWidth: 110,
|
||
|
PadY: 2,
|
||
|
}
|
||
|
fields := []magicform.Field{
|
||
|
{
|
||
|
Label: "Doodad",
|
||
|
Font: balance.LabelFont,
|
||
|
},
|
||
|
{
|
||
|
Label: "Title:",
|
||
|
Type: magicform.Value,
|
||
|
Font: balance.UIFont,
|
||
|
TextVariable: &doodad.Title,
|
||
|
},
|
||
|
{
|
||
|
Label: "Author:",
|
||
|
Type: magicform.Value,
|
||
|
Font: balance.UIFont,
|
||
|
TextVariable: &doodad.Author,
|
||
|
},
|
||
|
|
||
|
{
|
||
|
Label: "Actor (Doodad instance in level)",
|
||
|
Font: balance.LabelFont,
|
||
|
},
|
||
|
{
|
||
|
Label: "Actor ID:",
|
||
|
Type: magicform.Value,
|
||
|
Font: balance.UIFont,
|
||
|
TextVariable: &actorID,
|
||
|
},
|
||
|
/* TODO: doesn't update dynamically enough
|
||
|
{
|
||
|
Label: "World Position:",
|
||
|
Type: magicform.Value,
|
||
|
Font: balance.UIFont,
|
||
|
TextVariable: actorPos,
|
||
|
},
|
||
|
*/
|
||
|
}
|
||
|
|
||
|
form.Create(tab, fields)
|
||
|
|
||
|
return tab
|
||
|
}
|
||
|
|
||
|
// SetTextable is a Button or Checkbox widget having a SetText function,
|
||
|
// to support the reset button on the Doodad Options tab.
|
||
|
type SetTextable interface {
|
||
|
SetText(string) error
|
||
|
}
|
||
|
|
||
|
// DoodadConfig Window "Tags" Tab
|
||
|
func (c DoodadConfig) makeOptionsTab(tabFrame *ui.TabFrame, Width, Height int) *ui.Frame {
|
||
|
tab := tabFrame.AddTab("Options", ui.NewLabel(ui.Label{
|
||
|
Text: "Options",
|
||
|
Font: balance.TabFont,
|
||
|
}))
|
||
|
tab.Resize(render.NewRect(Width-4, Height-tab.Size().H-46))
|
||
|
|
||
|
if c.EditActor == nil {
|
||
|
return tab
|
||
|
}
|
||
|
|
||
|
// Draw a table view of the current tags on this doodad.
|
||
|
var (
|
||
|
doodad = c.EditActor.Doodad()
|
||
|
headers = []string{"Type", "Name", "Value", "Reset"}
|
||
|
columns = []int{40, 130, 130, 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(doodad.Options) == 0 {
|
||
|
label := ui.NewLabel(ui.Label{
|
||
|
Text: "There are no options on this doodad.",
|
||
|
Font: balance.MenuFont,
|
||
|
})
|
||
|
tab.Pack(label, ui.Pack{
|
||
|
Side: ui.N,
|
||
|
FillX: true,
|
||
|
})
|
||
|
} else {
|
||
|
// Initialize the Actor Options if nil
|
||
|
if c.EditActor.Actor.Options == nil {
|
||
|
c.EditActor.Actor.Options = map[string]*level.Option{}
|
||
|
}
|
||
|
|
||
|
// Draw the rows for each tag.
|
||
|
var sortedOpts []string
|
||
|
for name := range doodad.Options {
|
||
|
sortedOpts = append(sortedOpts, name)
|
||
|
}
|
||
|
sort.Strings(sortedOpts)
|
||
|
|
||
|
for _, optName := range sortedOpts {
|
||
|
var (
|
||
|
name = optName
|
||
|
value = c.EditActor.GetOption(name)
|
||
|
)
|
||
|
|
||
|
if value == nil {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
row = ui.NewFrame("Option Row")
|
||
|
tab.Pack(row, ui.Pack{
|
||
|
Side: ui.N,
|
||
|
FillX: true,
|
||
|
PadY: 2,
|
||
|
})
|
||
|
|
||
|
lblType := ui.NewLabel(ui.Label{
|
||
|
Text: value.Type,
|
||
|
Font: balance.MenuFont,
|
||
|
})
|
||
|
lblType.Resize(render.NewRect(columns[0], height))
|
||
|
|
||
|
lblName := ui.NewLabel(ui.Label{
|
||
|
Text: name,
|
||
|
Font: balance.MenuFont,
|
||
|
})
|
||
|
lblName.Resize(render.NewRect(columns[1], height))
|
||
|
|
||
|
// Value button: show a checkbox for booleans or a clickable
|
||
|
// button for other types (prompts user for value)
|
||
|
var btnValue ui.Widget
|
||
|
var cbValue bool
|
||
|
if value.Type == "bool" {
|
||
|
cbValue = value.Value.(bool)
|
||
|
checkbox := ui.NewCheckbox("Bool Box", &cbValue, ui.NewLabel(ui.Label{
|
||
|
Text: fmt.Sprintf("%v", cbValue),
|
||
|
Font: balance.MenuFont,
|
||
|
}))
|
||
|
checkbox.Resize(render.NewRect(columns[2], height))
|
||
|
checkbox.Handle(ui.Click, func(ed ui.EventData) error {
|
||
|
var label string
|
||
|
if cbValue {
|
||
|
label = "true"
|
||
|
} else {
|
||
|
label = "false"
|
||
|
}
|
||
|
c.EditActor.Actor.SetOption(name, value.Type, label)
|
||
|
checkbox.SetText(label)
|
||
|
return nil
|
||
|
})
|
||
|
checkbox.Supervise(c.Supervisor)
|
||
|
btnValue = checkbox
|
||
|
} else {
|
||
|
button := ui.NewButton("Tag Button", ui.NewLabel(ui.Label{
|
||
|
Text: fmt.Sprintf("%v", value.Value),
|
||
|
Font: balance.MenuFont,
|
||
|
}))
|
||
|
button.Resize(render.NewRect(columns[2], height))
|
||
|
button.Handle(ui.Click, func(ed ui.EventData) error {
|
||
|
shmem.Prompt("Enter new value: ", func(answer string) {
|
||
|
if answer == "" {
|
||
|
return
|
||
|
}
|
||
|
answer = c.EditActor.Actor.SetOption(name, value.Type, answer)
|
||
|
button.SetText(answer)
|
||
|
})
|
||
|
return nil
|
||
|
})
|
||
|
c.Supervisor.Add(button)
|
||
|
btnValue = button
|
||
|
}
|
||
|
|
||
|
// "Delete" / Reset Button: removes the Actor Option so it falls
|
||
|
// back onto the default Doodad Option.
|
||
|
btnDelete := ui.NewButton("Delete Button", ui.NewLabel(ui.Label{
|
||
|
Text: "Reset",
|
||
|
Font: balance.MenuFont,
|
||
|
}))
|
||
|
btnDelete.Resize(render.NewRect(columns[3], height))
|
||
|
btnDelete.SetStyle(&balance.ButtonDanger)
|
||
|
btnDelete.Handle(ui.Click, func(ed ui.EventData) error {
|
||
|
log.Info("Delete option: %s", name)
|
||
|
delete(c.EditActor.Actor.Options, name)
|
||
|
|
||
|
// Update the value button's text label.
|
||
|
if stt, ok := btnValue.(SetTextable); ok {
|
||
|
value := c.EditActor.GetOption(name)
|
||
|
if value != nil {
|
||
|
stt.SetText(fmt.Sprintf("%v", value.Value))
|
||
|
|
||
|
// Set the correct boolean checkbox state.
|
||
|
if value.Type == "bool" {
|
||
|
cbValue = value.Value.(bool)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
})
|
||
|
c.Supervisor.Add(btnDelete)
|
||
|
|
||
|
// Pack the widgets.
|
||
|
row.Pack(lblType, ui.Pack{
|
||
|
Side: ui.W,
|
||
|
})
|
||
|
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,
|
||
|
})
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return tab
|
||
|
}
|