doodle/pkg/windows/palette_editor.go

496 lines
11 KiB
Go

package windows
import (
"fmt"
"math"
"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/modal"
"git.kirsle.net/SketchyMaze/doodle/pkg/pattern"
"git.kirsle.net/SketchyMaze/doodle/pkg/shmem"
"git.kirsle.net/SketchyMaze/doodle/pkg/sprites"
"git.kirsle.net/SketchyMaze/doodle/pkg/usercfg"
"git.kirsle.net/go/render"
"git.kirsle.net/go/ui"
"git.kirsle.net/go/ui/style"
)
// PaletteEditor lets you customize the level palette in Edit Mode.
type PaletteEditor struct {
Supervisor *ui.Supervisor
Engine render.Engine
IsDoodad bool // you're editing a doodad instead of a level?
// Pointer to the currently edited palette, be it
// from a level or a doodad.
EditPalette *level.Palette
// Callback functions.
OnChange func()
OnAddColor func()
OnCancel func()
}
// NewPaletteEditor initializes the window.
func NewPaletteEditor(config PaletteEditor) *ui.Window {
// Default options.
var (
title = "Level Palette"
buttonSize = balance.DoodadButtonSize
columns = balance.DoodadDropperCols
rows = []*ui.Frame{}
// size of the popup window
width = buttonSize * columns
height = (buttonSize * balance.DoodadDropperRows) + 64 // account for button borders :(
// Column sizes of the palette table.
col1 = 15 // ID no.
col2 = 24 // Color
col5 = 24 // Texture
col3 = 130 // Name
col4 = 140 // Attributes
// col5 = 150 // Delete
// pagination values
page = 1
perPage = 5
)
if config.IsDoodad {
title = "Doodad Palette"
}
window := ui.NewWindow(title)
window.SetButtons(ui.CloseButton)
window.Configure(ui.Config{
Width: width,
Height: height,
Background: render.Grey,
})
frame := ui.NewFrame("Window Body Frame")
window.Pack(frame, ui.Pack{
Side: ui.N,
Fill: true,
Expand: true,
})
// Draw the header row.
headers := []struct {
Name string
Size int
}{
{"ID", col1},
{"Col", col2},
{"Tex", col5},
{"Name", col3},
{"Attributes", col4},
// {"Delete", col5},
}
header := ui.NewFrame("Palette Header")
for _, col := range headers {
labelFrame := ui.NewFrame(col.Name)
labelFrame.Configure(ui.Config{
Width: col.Size,
Height: 24,
})
label := ui.NewLabel(ui.Label{
Text: col.Name,
Font: balance.MenuFontBold,
})
labelFrame.Pack(label, ui.Pack{
Side: ui.N,
})
header.Pack(labelFrame, ui.Pack{
Side: ui.W,
Padding: 2,
})
}
header.Compute(config.Engine)
frame.Pack(header, ui.Pack{
Side: ui.N,
})
// Draw the main table of Palette rows.
if pal := config.EditPalette; pal != nil {
for i, swatch := range pal.Swatches {
var (
i = i
swatch = swatch
)
var idStr = fmt.Sprintf("%d", i)
row := ui.NewFrame("Swatch " + idStr)
rows = append(rows, row)
// Off the end of the first page?
if i >= perPage {
row.Hide()
}
//////////////
// ID label.
idLabel := ui.NewLabel(ui.Label{
Text: idStr + ".",
Font: balance.MenuFont,
})
idLabel.Configure(ui.Config{
Width: col1,
Height: 24,
})
//////////////
// Name button (click to rename the swatch)
btnName := ui.NewButton("Name", ui.NewLabel(ui.Label{
TextVariable: &swatch.Name,
}))
btnName.Configure(ui.Config{
Width: col3,
Height: 24,
})
btnName.Handle(ui.Click, func(ed ui.EventData) error {
shmem.Prompt("New swatch name ["+swatch.Name+"]: ", func(answer string) {
log.Warn("Answer: %s", answer)
if answer != "" {
// Confirm it is unique.
for j, exist := range pal.Swatches {
if exist.Name == answer && i != j {
modal.Alert("That name is already used by another color.")
return
}
}
swatch.Name = answer
if config.OnChange != nil {
config.OnChange()
}
}
})
return nil
})
config.Supervisor.Add(btnName)
//////////////
// Color Choice button.
btnColor := ui.NewButton("Color", ui.NewFrame("Color Frame"))
setPaletteButtonColor(btnColor, swatch.Color)
btnColor.Resize(render.NewRect(col2, 24))
btnColor.Handle(ui.Click, func(ed ui.EventData) error {
// Open a ColorPicker widget.
picker, err := ui.NewColorPicker(ui.ColorPicker{
Title: "Select a color",
Supervisor: config.Supervisor,
Engine: config.Engine,
Color: swatch.Color,
OnManualInput: func(callback func(render.Color)) {
// Prompt the user to enter a hex color using the developer shell.
shmem.Prompt(fmt.Sprintf(
"New color in hex notation [%s]: ", swatch.Color.ToHex()), func(answer string) {
if answer != "" {
// XXX: pure white renders as invisible, fudge it a bit.
if answer == "FFFFFF" {
answer = "FFFFFE"
}
color, err := render.HexColor(answer)
if err != nil {
shmem.Flash("Error with that color code: %s", err)
return
}
callback(color)
}
})
},
})
if err != nil {
log.Error("Couldn't open ColorPicker: %s", err)
return err
}
picker.Then(func(color render.Color) {
swatch.Color = color
setPaletteButtonColor(btnColor, color)
if config.OnChange != nil {
config.OnChange()
}
})
picker.Center(shmem.CurrentRenderEngine.WindowSize())
picker.Show()
return nil
})
config.Supervisor.Add(btnColor)
//////////////
// Texture (pattern) option.
selTexture := ui.NewSelectBox("Texture", ui.Label{
Font: balance.MenuFont,
})
selTexture.Resize(render.NewRect(col5, 24))
for _, t := range pattern.Builtins {
if t.Hidden && !usercfg.Current.ShowHiddenDoodads {
continue
}
selTexture.AddItem(t.Name, t.Filename, func() {})
}
selTexture.SetValue(swatch.Pattern)
setImageOnSelect(selTexture, swatch.Pattern)
selTexture.Handle(ui.Change, func(ed ui.EventData) error {
if val, ok := selTexture.GetValue(); ok {
filename, _ := val.Value.(string)
setImageOnSelect(selTexture, filename)
swatch.Pattern = filename
if config.OnChange != nil {
config.OnChange()
}
}
return nil
})
selTexture.Supervise(config.Supervisor)
config.Supervisor.Add(selTexture)
//////////////
// Attribute flags.
attrFrame := ui.NewFrame("Attributes")
attrFrame.Configure(ui.Config{
Width: col4,
Height: 24,
})
attributes := []struct {
Label string
Sprite string
Var *bool
}{
{
Label: "Solid",
Sprite: balance.AttrSolid,
Var: &swatch.Solid,
},
{
Label: "Semi-Solid",
Sprite: balance.AttrSemiSolid,
Var: &swatch.SemiSolid,
},
{
Label: "Fire",
Sprite: balance.AttrFire,
Var: &swatch.Fire,
},
{
Label: "Water",
Sprite: balance.AttrWater,
Var: &swatch.Water,
},
{
Label: "Slippery",
Sprite: balance.AttrSlippery,
Var: &swatch.Slippery,
},
}
// Do not show in Doodad editing mode.
if !config.IsDoodad {
for _, attr := range attributes {
attr := attr
var child ui.Widget
icon, err := sprites.LoadImage(config.Engine, attr.Sprite)
if err != nil {
log.Error("Sprite loading error: %s", err)
child = ui.NewLabel(ui.Label{
Text: attr.Label,
Font: balance.MenuFont,
})
} else {
child = icon
}
btn := ui.NewCheckButton(attr.Label, attr.Var, child)
btn.Handle(ui.Click, func(ed ui.EventData) error {
if config.OnChange != nil {
config.OnChange()
}
return nil
})
config.Supervisor.Add(btn)
tt := ui.NewTooltip(btn, ui.Tooltip{
Text: attr.Label,
Edge: ui.Bottom,
})
tt.Supervise(config.Supervisor)
attrFrame.Pack(btn, ui.Pack{
Side: ui.W,
PadX: 1,
})
}
}
//////////////
// Pack all the widgets.
row.Pack(idLabel, ui.Pack{
Side: ui.W,
PadX: 2,
})
row.Pack(btnColor, ui.Pack{
Side: ui.W,
PadX: 2,
})
row.Pack(selTexture, ui.Pack{
Side: ui.W,
PadX: 2,
})
row.Pack(btnName, ui.Pack{
Side: ui.W,
PadX: 2,
})
row.Pack(attrFrame, ui.Pack{
Side: ui.W,
PadX: 2,
})
row.Compute(config.Engine)
frame.Pack(row, ui.Pack{
Side: ui.N,
PadY: 2,
})
}
}
{
/******************
* Confirm/cancel buttons.
******************/
bottomFrame := ui.NewFrame("Button Frame")
frame.Pack(bottomFrame, ui.Pack{
Side: ui.S,
FillX: true,
})
// Pager for the doodads.
pager := ui.NewPager(ui.Pager{
Name: "Palette Editor Pager",
Page: page,
Pages: int(math.Ceil(
float64(len(rows)) / float64(perPage),
)),
PerPage: perPage,
MaxPageButtons: 6,
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 this page.
var (
minRow = (page - 1) * perPage
visible = 0
)
for i, row := range rows {
if visible >= perPage {
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,
})
btnFrame := ui.NewFrame("Window Buttons")
var buttons = []struct {
Label string
F func(ui.EventData) error
}{
{"Add Color", func(ed ui.EventData) error {
if config.OnAddColor != nil {
config.OnAddColor()
}
if config.OnChange != nil {
config.OnChange()
}
return nil
}},
{"Close", func(ed ui.EventData) error {
if config.OnCancel != nil {
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)
btn.Compute(config.Engine)
config.Supervisor.Add(btn)
btnFrame.Pack(btn, ui.Pack{
Side: ui.W,
PadX: 4,
})
}
bottomFrame.Place(btnFrame, ui.Place{
Top: 20,
Right: 20,
})
}
window.Hide()
return window
}
// Helper function to get the Tex (pattern) select box to
// show the image by its filename... for both onChange and
// initial render needs.
func setImageOnSelect(sel *ui.SelectBox, filename string) {
if img, err := pattern.GetImageCropped(filename, render.NewRect(24, 24)); err == nil {
sel.SetImage(img)
} else {
sel.SetImage(nil)
}
}
// Helper function to assign a palette "color button" color.
func setPaletteButtonColor(btn *ui.Button, color render.Color) {
btn.SetStyle(&style.Button{
Background: color,
HoverBackground: color.Lighten(40),
OutlineColor: render.Black,
OutlineSize: 1,
BorderStyle: style.BorderRaised,
BorderSize: 2,
})
}