Noah Petherbridge
49876c4fdf
* Install the new ui.TabFrame widget into the Settings and Doodad Dropper windows to give them properly tabbed interfaces. * Doodad Dropper's new tabs divide the list of doodads into categories to make them easier to find. * The officially defined categories so far are: - Objects (Start/End Flags and Box) - Doors (All locked doors and keys, Warp Doors, and Electric Door) - Gizmos (All buttons, switches, state blocks/doors, Electric Door) - Creatures (Blue/Red Azulian, Bird, Boy) * The "All" tab of the Doodad Dropper will show every doodad regardless of its category or whether it fit one of the official categories. * How doodads are assigned categories is by a special "category" tag in their metadata, e.g. "category=doors,gizmos" - multiple supported.
293 lines
6.6 KiB
Go
293 lines
6.6 KiB
Go
package windows
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"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/apps/doodle/pkg/uix"
|
|
"git.kirsle.net/apps/doodle/pkg/usercfg"
|
|
"git.kirsle.net/go/render"
|
|
"git.kirsle.net/go/ui"
|
|
)
|
|
|
|
// DoodadDropper is the doodad palette pop-up window for Editor Mode.
|
|
type DoodadDropper struct {
|
|
Supervisor *ui.Supervisor
|
|
Engine render.Engine
|
|
|
|
// Editing settings for an existing level?
|
|
EditLevel *level.Level
|
|
|
|
// Callback functions.
|
|
OnStartDragActor func(doodad *doodads.Doodad, actor *level.Actor)
|
|
OnCancel func()
|
|
}
|
|
|
|
// NewDoodadDropper initializes the window.
|
|
func NewDoodadDropper(config DoodadDropper) *ui.Window {
|
|
// Default options.
|
|
var (
|
|
title = "Doodads"
|
|
|
|
buttonSize = balance.DoodadButtonSize
|
|
columns = balance.DoodadDropperCols
|
|
rows = balance.DoodadDropperRows
|
|
|
|
// size of the doodad window
|
|
width = buttonSize * columns
|
|
height = (buttonSize * rows) + 64 // account for button borders :(
|
|
)
|
|
|
|
// Get all the doodads.
|
|
doodadsAvailable, err := doodads.ListDoodads()
|
|
if err != nil {
|
|
log.Error("NewDoodadDropper: doodads.ListDoodads: %s", err)
|
|
}
|
|
|
|
// Load all the doodads, skip hidden ones.
|
|
var items []*doodads.Doodad
|
|
for _, filename := range doodadsAvailable {
|
|
doodad, err := doodads.LoadFile(filename)
|
|
if err != nil {
|
|
log.Error(err.Error())
|
|
doodad = doodads.New(balance.DoodadSize)
|
|
}
|
|
|
|
// Skip hidden doodads.
|
|
if doodad.Hidden && !usercfg.Current.ShowHiddenDoodads {
|
|
continue
|
|
}
|
|
|
|
doodad.Filename = filename
|
|
items = append(items, doodad)
|
|
}
|
|
|
|
window := ui.NewWindow(title)
|
|
window.SetButtons(ui.CloseButton)
|
|
window.Configure(ui.Config{
|
|
Width: width,
|
|
Height: height + 30,
|
|
Background: render.Grey,
|
|
})
|
|
|
|
tabFrame := ui.NewTabFrame("Category Tabs")
|
|
window.Pack(tabFrame, ui.Pack{
|
|
Side: ui.N,
|
|
Fill: true,
|
|
Expand: true,
|
|
})
|
|
|
|
// The Category Tabs.
|
|
categories := []struct {
|
|
ID string
|
|
Name string
|
|
}{
|
|
{"objects", "Objects"},
|
|
{"doors", "Doors"},
|
|
{"gizmos", "Gizmos"},
|
|
{"creatures", "Creatures"},
|
|
{"", "All"},
|
|
}
|
|
for _, category := range categories {
|
|
tab1 := tabFrame.AddTab(category.Name, ui.NewLabel(ui.Label{
|
|
Text: category.Name,
|
|
Font: balance.TabFont,
|
|
}))
|
|
makeDoodadTab(config, tab1, render.NewRect(width-4, height-60), category.ID, items)
|
|
}
|
|
|
|
tabFrame.Supervise(config.Supervisor)
|
|
|
|
window.Hide()
|
|
return window
|
|
}
|
|
|
|
// Function to generate the TabFrame frame of the Doodads window.
|
|
func makeDoodadTab(config DoodadDropper, frame *ui.Frame, size render.Rect, category string, available []*doodads.Doodad) {
|
|
var (
|
|
buttonSize = balance.DoodadButtonSize
|
|
columns = balance.DoodadDropperCols
|
|
rows = balance.DoodadDropperRows
|
|
|
|
// pagination values
|
|
page = 1
|
|
pages int
|
|
perPage = 20
|
|
maxPageButtons = 10
|
|
)
|
|
frame.Resize(size)
|
|
|
|
// Trim the available doodads to those fitting the category.
|
|
var items = []*doodads.Doodad{}
|
|
for _, candidate := range available {
|
|
if value, ok := candidate.Tags["category"]; ok {
|
|
if category != "" && !strings.Contains(value, category) {
|
|
continue
|
|
}
|
|
} else if category != "" {
|
|
continue
|
|
}
|
|
items = append(items, candidate)
|
|
}
|
|
|
|
doodads.SortByName(items)
|
|
|
|
// Compute the number of pages for the pager widget.
|
|
pages = int(
|
|
math.Ceil(
|
|
float64(len(items)) / float64(columns*rows),
|
|
),
|
|
)
|
|
|
|
// Draw the doodad buttons in rows.
|
|
var btnRows = []*ui.Frame{}
|
|
{
|
|
var (
|
|
row *ui.Frame
|
|
rowCount int // for labeling the ui.Frame for each row
|
|
|
|
// TODO: pre-size btnRows by calculating how many needed
|
|
)
|
|
|
|
for i, doodad := range items {
|
|
doodad := doodad
|
|
|
|
if row == nil || i%columns == 0 {
|
|
var hidden = rowCount >= rows
|
|
rowCount++
|
|
|
|
row = ui.NewFrame(fmt.Sprintf("Doodad Row %d", rowCount))
|
|
row.SetBackground(balance.DoodadButtonBackground)
|
|
btnRows = append(btnRows, row)
|
|
frame.Pack(row, ui.Pack{
|
|
Side: ui.N,
|
|
// Fill: true,
|
|
})
|
|
|
|
// Hide overflowing rows until we scroll to them.
|
|
if hidden {
|
|
row.Hide()
|
|
}
|
|
}
|
|
|
|
can := uix.NewCanvas(int(buttonSize), true)
|
|
can.Name = doodad.Title
|
|
can.SetBackground(balance.DoodadButtonBackground)
|
|
can.LoadDoodad(doodad)
|
|
|
|
btn := ui.NewButton(doodad.Title, can)
|
|
btn.Resize(render.NewRect(
|
|
buttonSize-2, // TODO: without the -2 the button border
|
|
buttonSize-2, // rests on top of the window border
|
|
))
|
|
row.Pack(btn, ui.Pack{
|
|
Side: ui.W,
|
|
})
|
|
|
|
// Tooltip hover to show the doodad's name.
|
|
ui.NewTooltip(btn, ui.Tooltip{
|
|
Text: doodad.Title,
|
|
Edge: ui.Top,
|
|
})
|
|
|
|
// Begin the drag event to grab this Doodad.
|
|
// NOTE: The drag target is the EditorUI.Canvas in
|
|
// editor_ui.go#SetupCanvas()
|
|
btn.Handle(ui.MouseDown, func(ed ui.EventData) error {
|
|
log.Warn("MouseDown on doodad %s (%s)", doodad.Filename, doodad.Title)
|
|
config.OnStartDragActor(doodad, nil)
|
|
return nil
|
|
})
|
|
config.Supervisor.Add(btn)
|
|
|
|
// Resize the canvas to fill the button interior.
|
|
btnSize := btn.Size()
|
|
can.Resize(render.NewRect(
|
|
btnSize.W-btn.BoxThickness(2),
|
|
btnSize.H-btn.BoxThickness(2),
|
|
))
|
|
|
|
btn.Compute(config.Engine)
|
|
}
|
|
}
|
|
|
|
{
|
|
/******************
|
|
* Confirm/cancel buttons.
|
|
******************/
|
|
|
|
bottomFrame := ui.NewFrame("Button Frame")
|
|
frame.Pack(bottomFrame, ui.Pack{
|
|
Side: ui.N,
|
|
FillX: true,
|
|
})
|
|
|
|
// Pager for the doodads.
|
|
pager := ui.NewPager(ui.Pager{
|
|
Name: "Doodad Dropper Pager",
|
|
Page: page,
|
|
Pages: pages,
|
|
PerPage: perPage,
|
|
MaxPageButtons: maxPageButtons,
|
|
Font: balance.MenuFont,
|
|
OnChange: 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) * rows
|
|
visible = 0
|
|
)
|
|
for i, row := range btnRows {
|
|
if visible >= rows {
|
|
row.Hide()
|
|
continue
|
|
}
|
|
|
|
if i < minRow {
|
|
row.Hide()
|
|
} else {
|
|
row.Show()
|
|
visible++
|
|
}
|
|
}
|
|
},
|
|
})
|
|
pager.Compute(config.Engine)
|
|
pager.Supervise(config.Supervisor)
|
|
bottomFrame.Place(pager, ui.Place{
|
|
Top: 20,
|
|
Left: 20,
|
|
})
|
|
|
|
var buttons = []struct {
|
|
Label string
|
|
F func(ui.EventData) error
|
|
}{
|
|
// OK button is for editing an existing level.
|
|
{"Close", 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.Place(btn, ui.Place{
|
|
Top: 20,
|
|
Right: 20,
|
|
})
|
|
}
|
|
}
|
|
}
|