2020-03-24 20:21:55 +00:00
|
|
|
package ui
|
|
|
|
|
|
|
|
import (
|
2020-03-24 22:33:26 +00:00
|
|
|
"image"
|
|
|
|
"math"
|
|
|
|
"strconv"
|
|
|
|
|
|
|
|
"code.ur.gs/lupine/ordoor/internal/assetstore"
|
2020-03-24 20:21:55 +00:00
|
|
|
"code.ur.gs/lupine/ordoor/internal/menus"
|
|
|
|
)
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
registerBuilder(menus.TypeCheckbox, registerCheckbox)
|
2020-03-24 22:33:26 +00:00
|
|
|
registerBuilder(menus.TypeSlider, registerSlider)
|
2020-03-26 23:35:34 +00:00
|
|
|
registerBuilder(menus.TypeInventorySelect, registerInventorySelect)
|
2020-03-24 20:21:55 +00:00
|
|
|
}
|
|
|
|
|
2020-03-24 22:33:26 +00:00
|
|
|
// A checkbox can be a fancy button
|
2020-03-24 20:21:55 +00:00
|
|
|
type checkbox struct {
|
|
|
|
button
|
|
|
|
|
|
|
|
valueImpl
|
|
|
|
}
|
|
|
|
|
2020-03-24 22:33:26 +00:00
|
|
|
// A slider is harder. Two separate elements to render
|
|
|
|
type slider struct {
|
|
|
|
path string
|
|
|
|
|
|
|
|
baseSpr *assetstore.Sprite
|
|
|
|
clickSpr *assetstore.Sprite
|
|
|
|
sliderSpr *assetstore.Sprite
|
|
|
|
|
|
|
|
hv bool // horizontal (false) or vertical (true) slider
|
|
|
|
steps map[int]int // A list of valid steps. value:offset
|
|
|
|
|
|
|
|
clickImpl
|
|
|
|
mouseImpl
|
|
|
|
valueImpl
|
|
|
|
}
|
|
|
|
|
2020-03-26 23:35:34 +00:00
|
|
|
// An inventory select is a sort of radio button. If 2 share the same menu,
|
|
|
|
// selecting one deselects the other. Otherwise, they act like checkboxes
|
|
|
|
type inventorySelect struct {
|
|
|
|
checkbox
|
|
|
|
|
|
|
|
parentPath string
|
|
|
|
others []*inventorySelect
|
|
|
|
}
|
|
|
|
|
2020-03-24 20:21:55 +00:00
|
|
|
// A checkbox has 3 sprites, and 3 states: unchecked, checked, disabled.
|
|
|
|
func registerCheckbox(d *Driver, r *menus.Record) error {
|
|
|
|
sprites, err := d.menu.Sprites(r.Share, 3) // unchecked, disabled, checked
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
checkbox := &checkbox{
|
|
|
|
button: button{
|
|
|
|
path: r.Path(),
|
|
|
|
baseSpr: sprites[0], // unchecked
|
|
|
|
clickSpr: sprites[2], // checked
|
|
|
|
frozenSpr: sprites[1],
|
2020-03-26 22:09:26 +00:00
|
|
|
hoverImpl: hoverImpl{text: r.Text},
|
2020-03-24 20:21:55 +00:00
|
|
|
},
|
|
|
|
valueImpl: valueImpl{str: "0"},
|
|
|
|
}
|
|
|
|
|
|
|
|
d.clickables = append(d.clickables, checkbox)
|
|
|
|
d.freezables = append(d.freezables, checkbox)
|
|
|
|
d.hoverables = append(d.hoverables, checkbox)
|
|
|
|
d.paintables = append(d.paintables, checkbox)
|
|
|
|
d.valueables = append(d.valueables, checkbox)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-03-24 22:33:26 +00:00
|
|
|
func registerSlider(d *Driver, r *menus.Record) error {
|
|
|
|
sprites, err := d.menu.Sprites(r.Share, 3) // base, clicked, slider element
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
slider := &slider{
|
|
|
|
path: r.Path(),
|
|
|
|
baseSpr: sprites[0],
|
|
|
|
clickSpr: sprites[1],
|
|
|
|
sliderSpr: sprites[2],
|
|
|
|
hv: sprites[0].Rect.Dy() > sprites[0].Rect.Dx(), // A best guess
|
|
|
|
}
|
|
|
|
|
|
|
|
d.clickables = append(d.clickables, slider)
|
|
|
|
d.mouseables = append(d.mouseables, slider)
|
|
|
|
d.paintables = append(d.paintables, slider)
|
|
|
|
d.valueables = append(d.valueables, slider)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-03-26 23:35:34 +00:00
|
|
|
func registerInventorySelect(d *Driver, r *menus.Record) error {
|
|
|
|
sprites, err := d.menu.Sprites(r.Share, 3) // unchecked, checked, disabled
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
element := &inventorySelect{
|
|
|
|
checkbox: checkbox{
|
|
|
|
button: button{
|
|
|
|
path: r.Path(),
|
|
|
|
baseSpr: sprites[0], // unchecked
|
|
|
|
clickSpr: sprites[1], // checked
|
|
|
|
frozenSpr: sprites[2], // disabled
|
|
|
|
hoverImpl: hoverImpl{text: r.Text},
|
|
|
|
},
|
|
|
|
|
|
|
|
valueImpl: valueImpl{str: "0"},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
d.clickables = append(d.clickables, element)
|
|
|
|
d.freezables = append(d.freezables, element)
|
|
|
|
d.hoverables = append(d.hoverables, element)
|
|
|
|
d.paintables = append(d.paintables, element)
|
|
|
|
d.valueables = append(d.valueables, element)
|
|
|
|
|
|
|
|
if r.Parent == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
element.parentPath = r.Parent.Path()
|
|
|
|
|
|
|
|
// Now update all inventory selects belonging to the same menu so they share
|
|
|
|
// a list of all inventory selects. This will be replaced several times as
|
|
|
|
// the menu is built.
|
|
|
|
var inventorySelects []*inventorySelect
|
|
|
|
for _, valueable := range d.valueables {
|
|
|
|
if is, ok := valueable.(*inventorySelect); ok && is.parentPath == element.parentPath {
|
|
|
|
inventorySelects = append(inventorySelects, is)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, is := range inventorySelects {
|
|
|
|
is.others = inventorySelects
|
|
|
|
}
|
|
|
|
|
|
|
|
// Select the first in the list
|
|
|
|
if len(inventorySelects) == 1 {
|
|
|
|
element.setValue("1")
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-03-24 20:21:55 +00:00
|
|
|
func (c *checkbox) registerMouseClick() {
|
|
|
|
if c.value() == "1" { // Click disables
|
|
|
|
c.setValue("0")
|
|
|
|
} else { // Click enables
|
|
|
|
c.setValue("1")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *checkbox) regions(tick int) []region {
|
|
|
|
if c.isFrozen() {
|
|
|
|
return oneRegion(c.bounds().Min, c.frozenSpr.Image)
|
|
|
|
}
|
|
|
|
|
|
|
|
if c.value() == "1" {
|
|
|
|
return oneRegion(c.bounds().Min, c.clickSpr.Image)
|
|
|
|
}
|
|
|
|
|
|
|
|
return oneRegion(c.bounds().Min, c.baseSpr.Image)
|
|
|
|
}
|
2020-03-24 22:33:26 +00:00
|
|
|
|
|
|
|
func (s *slider) id() string {
|
|
|
|
return s.path
|
|
|
|
}
|
|
|
|
|
|
|
|
// The bounds of the slider are the whole thing
|
|
|
|
func (s *slider) bounds() image.Rectangle {
|
|
|
|
return s.baseSpr.Rect
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *slider) registerMouseClick() {
|
|
|
|
var value int
|
|
|
|
if s.hv {
|
|
|
|
value = s.valueFromPix(s.bounds().Min.Y, s.sliderPos().Y)
|
|
|
|
} else {
|
|
|
|
value = s.valueFromPix(s.bounds().Min.X, s.sliderPos().X)
|
|
|
|
}
|
|
|
|
|
|
|
|
s.valueImpl.str = strconv.Itoa(value)
|
|
|
|
|
|
|
|
s.clickImpl.registerMouseClick()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *slider) regions(tick int) []region {
|
|
|
|
var out []region
|
|
|
|
|
|
|
|
if s.mouseDownState() {
|
|
|
|
out = append(out, oneRegion(s.bounds().Min, s.clickSpr.Image)...)
|
|
|
|
} else {
|
|
|
|
out = append(out, oneRegion(s.bounds().Min, s.baseSpr.Image)...)
|
|
|
|
}
|
|
|
|
|
|
|
|
out = append(out, oneRegion(s.sliderPos(), s.sliderSpr.Image)...)
|
|
|
|
|
|
|
|
return out
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *slider) sliderPos() image.Point {
|
|
|
|
if s.hv {
|
|
|
|
return s.sliderPosVertical()
|
|
|
|
}
|
|
|
|
|
|
|
|
return s.sliderPosHorizontal()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *slider) sliderPosHorizontal() image.Point {
|
|
|
|
pos := s.bounds().Min
|
|
|
|
|
|
|
|
if s.mouseDownState() {
|
|
|
|
pos.X = s.constrainPix(s.bounds().Min.X, s.mouseImpl.pos.X)
|
|
|
|
} else {
|
|
|
|
pos.X = s.bounds().Min.X + s.offsetFromValue(s.valueInt())
|
|
|
|
}
|
|
|
|
|
|
|
|
return pos
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *slider) sliderPosVertical() image.Point {
|
|
|
|
pos := s.bounds().Min
|
|
|
|
|
|
|
|
if s.mouseDownState() {
|
|
|
|
pos.Y = s.constrainPix(s.bounds().Min.Y, s.mouseImpl.pos.Y)
|
|
|
|
} else {
|
|
|
|
pos.Y = s.bounds().Min.Y + s.offsetFromValue(s.valueInt())
|
|
|
|
}
|
|
|
|
|
|
|
|
return pos
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *slider) valueFromPix(start, actual int) int {
|
|
|
|
if len(s.steps) == 0 {
|
|
|
|
return actual - start
|
|
|
|
}
|
|
|
|
|
|
|
|
minDistance := 9999
|
|
|
|
var out int
|
|
|
|
|
|
|
|
for value, offset := range s.steps {
|
|
|
|
pix := start + offset
|
|
|
|
distance := int(math.Abs(float64(actual - pix)))
|
|
|
|
|
|
|
|
if distance < minDistance {
|
|
|
|
minDistance = distance
|
|
|
|
out = value
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return out
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *slider) offsetFromValue(value int) int {
|
|
|
|
if len(s.steps) == 0 {
|
|
|
|
return value
|
|
|
|
}
|
|
|
|
|
|
|
|
value = s.constrainValue(value)
|
|
|
|
|
|
|
|
return s.steps[value]
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *slider) constrainPix(start, actual int) int {
|
|
|
|
if len(s.steps) == 0 {
|
|
|
|
return actual
|
|
|
|
}
|
|
|
|
|
|
|
|
minDistance := 9999
|
|
|
|
out := actual
|
|
|
|
|
|
|
|
for _, offset := range s.steps {
|
|
|
|
pix := start + offset
|
|
|
|
distance := int(math.Abs(float64(actual - pix)))
|
|
|
|
|
|
|
|
if distance < minDistance {
|
|
|
|
minDistance = distance
|
|
|
|
out = pix
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return out
|
|
|
|
}
|
|
|
|
func (s *slider) constrainValue(actual int) int {
|
|
|
|
if len(s.steps) == 0 {
|
|
|
|
return actual
|
|
|
|
}
|
|
|
|
|
|
|
|
minDistance := 9999
|
|
|
|
out := actual
|
|
|
|
|
|
|
|
for value, _ := range s.steps {
|
|
|
|
distance := int(math.Abs(float64(value - actual)))
|
|
|
|
|
|
|
|
if distance < minDistance {
|
|
|
|
minDistance = distance
|
|
|
|
out = value
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return out
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *slider) valueInt() int {
|
|
|
|
v, _ := strconv.Atoi(s.valueImpl.value())
|
|
|
|
|
|
|
|
return s.constrainValue(v)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *slider) value() string {
|
|
|
|
return strconv.Itoa(s.valueInt())
|
|
|
|
}
|
2020-03-26 23:35:34 +00:00
|
|
|
|
|
|
|
func (i *inventorySelect) registerMouseClick() {
|
|
|
|
// Do nothing if we're already selected
|
|
|
|
if i.value() == "1" {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Turn us on, turn everyone else off
|
|
|
|
for _, other := range i.others {
|
|
|
|
other.setValue("0")
|
|
|
|
}
|
|
|
|
|
|
|
|
i.setValue("1")
|
|
|
|
}
|