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:
@@ -1,6 +1,7 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
@@ -11,8 +12,32 @@ type Ordoor struct {
|
||||
VideoPlayer []string `toml:"video_player"`
|
||||
}
|
||||
|
||||
// Things set
|
||||
type Options struct {
|
||||
PlayMovies bool `toml:"play_movies"`
|
||||
Animations bool `toml:"animations"`
|
||||
PlayMusic bool `toml:"play_music"`
|
||||
CombatVoices bool `toml:"combat_voices"`
|
||||
ShowGrid bool `toml:"show_grid"`
|
||||
ShowPaths bool `toml:"show_paths"`
|
||||
PointSaving bool `toml:"point_saving"`
|
||||
AutoCutLevel bool `toml:"auto_cut_level"`
|
||||
|
||||
XRes int `toml:"x_resolution"`
|
||||
YRes int `toml:"y_resolution"`
|
||||
|
||||
MusicVolume int `toml:"music_volume"`
|
||||
SFXVolume int `toml:"sfx_volume"`
|
||||
|
||||
UnitSpeed int `toml:"unit_speed"`
|
||||
AnimSpeed int `toml:"animation_speed"`
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Ordoor `toml:"ordoor"`
|
||||
filename string `toml:"-"`
|
||||
|
||||
Ordoor `toml:"ordoor"`
|
||||
Options `toml:"options"`
|
||||
}
|
||||
|
||||
func Load(filename string) (*Config, error) {
|
||||
@@ -23,9 +48,21 @@ func Load(filename string) (*Config, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
out.filename = filename
|
||||
|
||||
return &out, err
|
||||
}
|
||||
|
||||
func (c *Config) Save() error {
|
||||
f, err := os.OpenFile(c.filename, os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
return toml.NewEncoder(f).Encode(c)
|
||||
}
|
||||
|
||||
// TODO: case-insensitive lookup
|
||||
func (c *Config) DataFile(path string) string {
|
||||
return filepath.Join(c.DataDir, path)
|
||||
|
@@ -10,11 +10,19 @@ import (
|
||||
"code.ur.gs/lupine/ordoor/internal/util/asciiscan"
|
||||
)
|
||||
|
||||
type MenuType int
|
||||
|
||||
const (
|
||||
TypeStatic = 0
|
||||
TypeMenu = 1
|
||||
TypeOverlay = 61
|
||||
TypeMainButton = 228
|
||||
TypeStatic MenuType = 0
|
||||
TypeMenu MenuType = 1
|
||||
TypeButton MenuType = 3
|
||||
TypeInvokeButton MenuType = 50
|
||||
TypeOverlay MenuType = 61
|
||||
TypeHypertext MenuType = 70
|
||||
TypeCheckbox MenuType = 91
|
||||
TypeAnimationSample MenuType = 220
|
||||
TypeMainButton MenuType = 228
|
||||
TypeSlider MenuType = 232
|
||||
)
|
||||
|
||||
type Record struct {
|
||||
@@ -22,7 +30,7 @@ type Record struct {
|
||||
Children []*Record
|
||||
|
||||
Id int
|
||||
Type int
|
||||
Type MenuType
|
||||
DrawType int
|
||||
FontType int
|
||||
Active bool
|
||||
@@ -183,7 +191,7 @@ func setProperty(r *Record, k, v string) {
|
||||
case "MENUID", "SUBMENUID":
|
||||
r.Id = vInt
|
||||
case "MENUTYPE", "SUBMENUTYPE":
|
||||
r.Type = vInt
|
||||
r.Type = MenuType(vInt)
|
||||
case "ACTIVE":
|
||||
r.Active = (vInt != 0)
|
||||
case "SPRITEID":
|
||||
|
@@ -2,15 +2,138 @@ package ordoor
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"code.ur.gs/lupine/ordoor/internal/ui"
|
||||
)
|
||||
|
||||
func try(result error, into *error) {
|
||||
if *into == nil {
|
||||
*into = result
|
||||
}
|
||||
}
|
||||
|
||||
// These are UI interfaces covering the game entrypoint
|
||||
|
||||
func (o *Ordoor) ifaceMain() (*ui.Interface, error) {
|
||||
// TODO: Start in the "main" menu
|
||||
menu, err := o.assets.Menu("Main")
|
||||
// Start in the "main" menu
|
||||
main, err := o.buildInterface("main")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
options, err := o.ifaceOptions(main)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO: clicking these buttons should load other interfaces
|
||||
try(wireupClick(main, func() {}, 2, 1), &err) // New game
|
||||
try(wireupClick(main, func() {}, 2, 2), &err) // Load game
|
||||
try(disableWidget(main, 2, 3), &err) // Multiplayer - disable for now
|
||||
try(wireupClick(main, func() { o.iface = options }, 2, 4), &err) // Options
|
||||
try(wireupClick(main, func() { o.nextState = StateExit }, 2, 5), &err) // Quit
|
||||
|
||||
return main, err
|
||||
}
|
||||
|
||||
// Options needs to know how to go back to main
|
||||
func (o *Ordoor) ifaceOptions(main *ui.Interface) (*ui.Interface, error) {
|
||||
options, err := o.buildInterface("options")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := o.configIntoOptions(options); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO: load current options state into UI
|
||||
try(wireupClick(options, func() {}, 2, 8), &err) // Keyboard settings button
|
||||
// Resolution slider is 2,9
|
||||
// Music volume slider is 2,10
|
||||
// Sound FX volume slider is 2,11
|
||||
|
||||
// Accept button
|
||||
try(wireupClick(
|
||||
options, func() {
|
||||
if err := o.optionsIntoConfig(options); err != nil {
|
||||
// FIXME: exiting is a bit OTT. Perhaps display "save failed"?
|
||||
log.Printf("Saving options to config failed: %v", err)
|
||||
o.nextState = StateExit
|
||||
} else {
|
||||
o.iface = main
|
||||
}
|
||||
},
|
||||
2, 12,
|
||||
), &err)
|
||||
|
||||
// 13...23 are "hypertext"
|
||||
|
||||
// Cancel button
|
||||
try(
|
||||
wireupClick(
|
||||
options,
|
||||
func() {
|
||||
// FIXME: again, exiting is OTT. We're just resetting the state of
|
||||
// the interface to the values in config.
|
||||
if err := o.configIntoOptions(options); err != nil {
|
||||
log.Printf("Saving options to config failed: %v", err)
|
||||
o.nextState = StateExit
|
||||
} else {
|
||||
o.iface = main
|
||||
}
|
||||
},
|
||||
2, 24,
|
||||
), &err)
|
||||
// Unit speed slider is 2,26
|
||||
// Looping effect speed slider is 2,27
|
||||
// Sample of unit speed animation is 2,28
|
||||
// Sample of effect speed animation is 2,29
|
||||
|
||||
// 30...35 are "hypertext"
|
||||
|
||||
return options, err
|
||||
}
|
||||
|
||||
func (o *Ordoor) configIntoOptions(options *ui.Interface) error {
|
||||
cfg := &o.config.Options
|
||||
var err error
|
||||
|
||||
try(setWidgetValueBool(options, cfg.PlayMovies, 2, 1), &err)
|
||||
try(setWidgetValueBool(options, cfg.Animations, 2, 2), &err)
|
||||
try(setWidgetValueBool(options, cfg.PlayMusic, 2, 3), &err)
|
||||
try(setWidgetValueBool(options, cfg.CombatVoices, 2, 4), &err)
|
||||
try(setWidgetValueBool(options, cfg.ShowGrid, 2, 5), &err)
|
||||
try(setWidgetValueBool(options, cfg.ShowPaths, 2, 6), &err)
|
||||
try(setWidgetValueBool(options, cfg.PointSaving, 2, 7), &err)
|
||||
try(setWidgetValueBool(options, cfg.AutoCutLevel, 2, 25), &err)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (o *Ordoor) optionsIntoConfig(options *ui.Interface) error {
|
||||
cfg := &o.config.Options
|
||||
var err error
|
||||
|
||||
try(getWidgetValueBool(options, &cfg.PlayMovies, 2, 1), &err)
|
||||
try(getWidgetValueBool(options, &cfg.Animations, 2, 2), &err)
|
||||
try(getWidgetValueBool(options, &cfg.PlayMusic, 2, 3), &err)
|
||||
try(getWidgetValueBool(options, &cfg.CombatVoices, 2, 4), &err)
|
||||
try(getWidgetValueBool(options, &cfg.ShowGrid, 2, 5), &err)
|
||||
try(getWidgetValueBool(options, &cfg.ShowPaths, 2, 6), &err)
|
||||
try(getWidgetValueBool(options, &cfg.PointSaving, 2, 7), &err)
|
||||
try(getWidgetValueBool(options, &cfg.AutoCutLevel, 2, 25), &err)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return o.config.Save()
|
||||
}
|
||||
|
||||
func (o *Ordoor) buildInterface(name string) (*ui.Interface, error) {
|
||||
menu, err := o.assets.Menu(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -20,33 +143,79 @@ func (o *Ordoor) ifaceMain() (*ui.Interface, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO: clicking these buttons should load other interfaces
|
||||
wireupClick(iface, func() {}, 2, 1) // New game
|
||||
wireupClick(iface, func() {}, 2, 2) // Load game
|
||||
disableWidget(iface, 2, 3) // Multiplayer. Disable for now.
|
||||
wireupClick(iface, func() {}, 2, 4) // Options
|
||||
wireupClick(iface, func() { o.nextState = StateExit }, 2, 5) // Quit
|
||||
|
||||
return iface, nil
|
||||
}
|
||||
|
||||
func findWidgetOrPanic(iface *ui.Interface, spec ...int) *ui.Widget {
|
||||
func findWidget(iface *ui.Interface, spec ...int) (*ui.Widget, error) {
|
||||
widget, err := iface.Widget(spec...)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Couldn't find widget %v:%+v", iface.Name, spec))
|
||||
return nil, fmt.Errorf("Couldn't find widget %v:%+v", iface.Name, spec)
|
||||
}
|
||||
|
||||
return widget
|
||||
return widget, nil
|
||||
}
|
||||
|
||||
func wireupClick(iface *ui.Interface, f func(), spec ...int) {
|
||||
findWidgetOrPanic(iface, spec...).OnMouseClick = f
|
||||
func getWidgetValue(iface *ui.Interface, spec ...int) (string, error) {
|
||||
widget, err := findWidget(iface, spec...)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return widget.Value, nil
|
||||
}
|
||||
|
||||
func disableWidget(iface *ui.Interface, spec ...int) {
|
||||
findWidgetOrPanic(iface, spec...).Disable()
|
||||
func setWidgetValue(iface *ui.Interface, value string, spec ...int) error {
|
||||
widget, err := findWidget(iface, spec...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
widget.Value = value
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *Ordoor) ifaceOptions() (*ui.Interface, error) {
|
||||
return nil, nil
|
||||
func getWidgetValueBool(iface *ui.Interface, into *bool, spec ...int) error {
|
||||
vStr, err := getWidgetValue(iface, spec...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*into = vStr == "1"
|
||||
return nil
|
||||
}
|
||||
|
||||
func setWidgetValueBool(iface *ui.Interface, value bool, spec ...int) error {
|
||||
vStr := "0"
|
||||
if value {
|
||||
vStr = "1"
|
||||
}
|
||||
|
||||
return setWidgetValue(iface, vStr, spec...)
|
||||
}
|
||||
|
||||
func wireupClick(iface *ui.Interface, f func(), spec ...int) error {
|
||||
widget, err := findWidget(iface, spec...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if widget.OnMouseClick != nil {
|
||||
return fmt.Errorf("Widget %#+v already has an OnMouseClick handler", widget)
|
||||
}
|
||||
|
||||
widget.OnMouseClick = f
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func disableWidget(iface *ui.Interface, spec ...int) error {
|
||||
widget, err := findWidget(iface, spec...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
widget.Disable()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@@ -64,7 +64,7 @@ func Run(configFile string) error {
|
||||
nextState: StateInterface,
|
||||
}
|
||||
|
||||
win, err := ui.NewWindow(ordoor, "Ordoor")
|
||||
win, err := ui.NewWindow(ordoor, "Ordoor", cfg.Options.XRes, cfg.Options.YRes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to create window: %v", err)
|
||||
}
|
||||
@@ -79,12 +79,14 @@ func Run(configFile string) error {
|
||||
}
|
||||
|
||||
func (o *Ordoor) Run() error {
|
||||
// On startup, play these two videos
|
||||
o.PlaySkippableVideo("LOGOS")
|
||||
o.PlaySkippableVideo("movie1")
|
||||
if o.config.Options.PlayMovies {
|
||||
o.PlayUnskippableVideo("LOGOS")
|
||||
o.PlaySkippableVideo("movie1")
|
||||
}
|
||||
|
||||
err := o.win.Run()
|
||||
if err == errExit {
|
||||
log.Printf("Exit requested")
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@@ -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