Implement the options menu, part 1
This commit implements loading and saving options from/to config, and enough UI toolkit magic to allow changes to boolean options to be persisted. We now respect the "play movies" setting and take screen resolution from the config file.
This commit is contained in:
@@ -3,6 +3,7 @@ package ui
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
"log"
|
||||
"reflect" // For DeepEqual
|
||||
|
||||
"github.com/hajimehoshi/ebiten"
|
||||
@@ -30,7 +31,6 @@ type Interface struct {
|
||||
func NewInterface(menu *assetstore.Menu) (*Interface, error) {
|
||||
iface := &Interface{
|
||||
Name: menu.Name,
|
||||
|
||||
menu: menu,
|
||||
}
|
||||
|
||||
@@ -112,25 +112,19 @@ func (i *Interface) Draw(screen *ebiten.Image) error {
|
||||
}
|
||||
|
||||
func (i *Interface) addRecord(record *menus.Record) error {
|
||||
switch record.Type {
|
||||
case menus.TypeStatic: // These are static
|
||||
if sprite, err := i.menu.Sprite(record.SpriteId[0]); err != nil {
|
||||
return err
|
||||
} else {
|
||||
i.static = append(i.static, sprite)
|
||||
}
|
||||
case menus.TypeMenu: // These aren't drawable and can be ignored
|
||||
case menus.TypeOverlay, menus.TypeMainButton: // Widgets \o/
|
||||
if widget, err := i.widgetFromRecord(record); err != nil {
|
||||
return err
|
||||
} else {
|
||||
i.widgets = append(i.widgets, widget)
|
||||
}
|
||||
log.Printf("Adding record: %#+v", record)
|
||||
|
||||
default:
|
||||
handler, ok := setupHandlers[record.Type]
|
||||
if !ok {
|
||||
return fmt.Errorf("ui.interface: encountered unknown menu record: %#+v", record)
|
||||
}
|
||||
|
||||
if handler != nil {
|
||||
if err := handler(i, record); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Recursively add all children
|
||||
for _, record := range record.Children {
|
||||
if err := i.addRecord(record); err != nil {
|
||||
@@ -166,47 +160,3 @@ func (i *Interface) getMousePos(w, h int) image.Point {
|
||||
|
||||
return image.Pt(int(sX), int(sY))
|
||||
}
|
||||
|
||||
func (i *Interface) widgetFromRecord(record *menus.Record) (*Widget, error) {
|
||||
// FIXME: we assume that all widgets have a share sprite, but is that true?
|
||||
sprite, err := i.menu.Sprite(record.Share)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var path []int
|
||||
for r := record; r != nil; r = r.Parent {
|
||||
path = append([]int{r.Id}, path...)
|
||||
}
|
||||
|
||||
widget := &Widget{
|
||||
Bounds: sprite.Rect,
|
||||
Tooltip: record.Desc,
|
||||
path: path,
|
||||
record: record,
|
||||
sprite: sprite,
|
||||
}
|
||||
|
||||
switch record.Type {
|
||||
case menus.TypeMainButton:
|
||||
hovers, err := i.menu.Images(record.SpriteId[0], record.DrawType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
widget.hoverAnimation = hovers
|
||||
|
||||
pressed, err := i.menu.Sprite(record.Share + 1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
widget.mouseButtonDownImage = pressed.Image
|
||||
|
||||
disabled, err := i.menu.Sprite(record.Share + 2)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
widget.disabledImage = disabled.Image
|
||||
}
|
||||
|
||||
return widget, nil
|
||||
}
|
||||
|
185
internal/ui/setup_handlers.go
Normal file
185
internal/ui/setup_handlers.go
Normal file
@@ -0,0 +1,185 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
"github.com/hajimehoshi/ebiten"
|
||||
|
||||
"code.ur.gs/lupine/ordoor/internal/menus"
|
||||
)
|
||||
|
||||
// Setup handlers know how to handle each type of widget
|
||||
var setupHandlers = map[menus.MenuType]func(i *Interface, r *menus.Record) error {
|
||||
menus.TypeStatic: handleStatic,
|
||||
menus.TypeMenu: nil,
|
||||
menus.TypeButton: handleButton,
|
||||
menus.TypeInvokeButton: handleInvokeButton,
|
||||
menus.TypeOverlay: handleStatic, // FIXME: more?
|
||||
menus.TypeHypertext: nil, // FIXME: handle this
|
||||
menus.TypeCheckbox: handleCheckbox,
|
||||
menus.TypeAnimationSample: nil, // FIXME: handle this
|
||||
menus.TypeMainButton: handleMainButton,
|
||||
menus.TypeSlider: nil, // FIXME: handle this
|
||||
}
|
||||
|
||||
func handleStatic(i *Interface, record *menus.Record) error {
|
||||
spriteId := record.Share
|
||||
|
||||
// FIXME: SpriteID takes precedence over SHARE if present, but is that right?
|
||||
if len(record.SpriteId) > 0 && record.SpriteId[0] != -1 {
|
||||
spriteId = record.SpriteId[0]
|
||||
}
|
||||
|
||||
sprite, err := i.menu.Sprite(spriteId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
i.static = append(i.static, sprite)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// A checkbox has 3 sprites, and 3 states: unchecked, checked, disabled.
|
||||
func handleCheckbox(i *Interface, record *menus.Record) error {
|
||||
widget, err := i.widgetFromRecord(record, record.Share)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
unchecked := widget.sprite
|
||||
disabled, err := i.menu.Sprite(record.Share + 1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
checked, err := i.menu.Sprite(record.Share + 2)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
widget.Value = "0"
|
||||
|
||||
widget.OnMouseClick = func() {
|
||||
if widget.Value == "1" { // Click disables
|
||||
widget.Value = "0"
|
||||
} else { // Click enables
|
||||
widget.Value = "1"
|
||||
}
|
||||
}
|
||||
|
||||
widget.disabledImage = disabled.Image
|
||||
widget.valueToImage = func() *ebiten.Image {
|
||||
if widget.Value == "1" {
|
||||
return checked.Image
|
||||
}
|
||||
|
||||
return unchecked.Image
|
||||
}
|
||||
|
||||
i.widgets = append(i.widgets, widget)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleButton(i *Interface, record *menus.Record) error {
|
||||
spriteId := record.SpriteId[0]
|
||||
widget, err := i.widgetFromRecord(record, spriteId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pressed, err := i.menu.Sprite(spriteId + 1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
disabled, err := i.menu.Sprite(spriteId + 2)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
widget.mouseButtonDownImage = pressed.Image
|
||||
widget.disabledImage = disabled.Image
|
||||
|
||||
i.widgets = append(i.widgets, widget)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleInvokeButton(i *Interface, record *menus.Record) error {
|
||||
widget, err := i.widgetFromRecord(record, record.Share)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pressed, err := i.menu.Sprite(record.Share + 1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
disabled, err := i.menu.Sprite(record.Share + 2)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
widget.mouseButtonDownImage = pressed.Image
|
||||
widget.disabledImage = disabled.Image
|
||||
|
||||
i.widgets = append(i.widgets, widget)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// A main button is quite complex. It has 3 main sprites and a hover animation
|
||||
func handleMainButton(i *Interface, record *menus.Record) error {
|
||||
widget, err := i.widgetFromRecord(record, record.Share)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pressed, err := i.menu.Sprite(record.Share + 1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
disabled, err := i.menu.Sprite(record.Share + 2)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
hovers, err := i.menu.Images(record.SpriteId[0], record.DrawType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
widget.mouseButtonDownImage = pressed.Image
|
||||
widget.disabledImage = disabled.Image
|
||||
widget.hoverAnimation = hovers
|
||||
|
||||
i.widgets = append(i.widgets, widget)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
// Widgets need a bounding box determined by a sprite. Different widgets specify
|
||||
// their sprites in different attributes, so pass in the right sprite externally
|
||||
func (i *Interface) widgetFromRecord(record *menus.Record, spriteId int) (*Widget, error) {
|
||||
sprite, err := i.menu.Sprite(spriteId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var path []int
|
||||
for r := record; r != nil; r = r.Parent {
|
||||
path = append([]int{r.Id}, path...)
|
||||
}
|
||||
|
||||
widget := &Widget{
|
||||
Bounds: sprite.Rect,
|
||||
Tooltip: record.Desc,
|
||||
path: path,
|
||||
record: record,
|
||||
sprite: sprite,
|
||||
}
|
||||
|
||||
return widget, nil
|
||||
}
|
@@ -15,6 +15,7 @@ type Widget struct {
|
||||
// Position on the screen in original (i.e., unscaled) coordinates
|
||||
Bounds image.Rectangle
|
||||
Tooltip string
|
||||
Value string // #dealwithit for bools and ints and so on :p
|
||||
|
||||
OnHoverEnter func()
|
||||
OnHoverLeave func()
|
||||
@@ -40,6 +41,8 @@ type Widget struct {
|
||||
path []int
|
||||
record *menus.Record
|
||||
sprite *assetstore.Sprite
|
||||
|
||||
valueToImage func() *ebiten.Image
|
||||
}
|
||||
|
||||
func (w *Widget) Disable() {
|
||||
@@ -86,7 +89,7 @@ func (w *Widget) Image(aniStep int) (*ebiten.Image, error) {
|
||||
return w.disabledImage, nil
|
||||
}
|
||||
|
||||
if w.hoverState && w.mouseButtonState {
|
||||
if w.mouseButtonDownImage != nil && w.hoverState && w.mouseButtonState {
|
||||
return w.mouseButtonDownImage, nil
|
||||
}
|
||||
|
||||
@@ -94,5 +97,9 @@ func (w *Widget) Image(aniStep int) (*ebiten.Image, error) {
|
||||
return w.hoverAnimation[(aniStep)%len(w.hoverAnimation)], nil
|
||||
}
|
||||
|
||||
if w.valueToImage != nil {
|
||||
return w.valueToImage(), nil
|
||||
}
|
||||
|
||||
return w.sprite.Image, nil
|
||||
}
|
||||
|
Reference in New Issue
Block a user