Noah Petherbridge
ecaa8c6cef
* Add new pixel attributes: SemiSolid and Slippery (the latter is WIP) * SemiSolid pixels are only solid below the player character. You can walk on them and up and down SemiSolid slopes, but can freely pass through from the sides or jump through from below. * Update the Palette Editor UI to replace the Attributes buttons: instead of text labels they now have smaller icons (w/ tooltips) for the Solid, SemiSolid, Fire, Water and Slippery attributes. * Bugfix in Palette Editor: use cropped (24x24) images for the Tex buttons so that the large Bubbles texture stays within its designated space! * uix.Actor.SetGrounded() to also set the Y velocity to zero when an actor becomes grounded. This fixes a minor bug where the player's Y velocity (due to gravity) was not updated while they were grounded, which may eventually become useful to allow them to jump down thru a SemiSolid floor. Warp Doors needed a fix to work around the bug, to set the player's Grounded(false) or else they would hover a few pixels above the ground at their destination, since Grounded status paused gravity calculations.
496 lines
11 KiB
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,
|
|
})
|
|
}
|