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.
236 lines
4.4 KiB
Go
236 lines
4.4 KiB
Go
package menus
|
|
|
|
import (
|
|
"fmt"
|
|
"io/ioutil"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"code.ur.gs/lupine/ordoor/internal/util/asciiscan"
|
|
)
|
|
|
|
type MenuType int
|
|
|
|
const (
|
|
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 {
|
|
Parent *Record
|
|
Children []*Record
|
|
|
|
Id int
|
|
Type MenuType
|
|
DrawType int
|
|
FontType int
|
|
Active bool
|
|
SpriteId []int
|
|
Share int
|
|
X int
|
|
Y int
|
|
Desc string
|
|
|
|
// FIXME: turn these into first-class data
|
|
properties map[string]string
|
|
}
|
|
|
|
type Menu struct {
|
|
Name string
|
|
// TODO: load these
|
|
ObjectFiles []string
|
|
FontNames []string
|
|
|
|
// FIXME: turn these into first-class data
|
|
Properties map[string]string
|
|
|
|
// The actual menu records. There are multiple top-level items. Submenus are
|
|
// only ever nested one deep.
|
|
Records []*Record
|
|
}
|
|
|
|
func LoadMenu(filename string) (*Menu, error) {
|
|
name := filepath.Base(filename)
|
|
|
|
// FIXME: this needs turning into a real parser sometime
|
|
scanner, err := asciiscan.New(filename)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
defer scanner.Close()
|
|
|
|
var str string
|
|
var record *Record
|
|
|
|
section := 0
|
|
isProp := false
|
|
out := &Menu{
|
|
Name: name,
|
|
Properties: map[string]string{},
|
|
}
|
|
|
|
for {
|
|
str, err = scanner.ConsumeString()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Whether the lines are properties or not alternate with each section,
|
|
// except the records use `*` as a separator
|
|
if section < 3 && isProp != asciiscan.IsProperty(str) {
|
|
section += 1
|
|
isProp = !isProp
|
|
}
|
|
|
|
if str == "~" {
|
|
break
|
|
}
|
|
|
|
switch section {
|
|
case 0: // List of object files
|
|
out.ObjectFiles = append(out.ObjectFiles, str)
|
|
case 1: // List of properties
|
|
k, v := asciiscan.ConsumeProperty(str)
|
|
out.Properties[k] = v
|
|
case 2: // list of fonts
|
|
// FIXME: do we need to do something cleverer here?
|
|
if str == "NULL" {
|
|
continue
|
|
}
|
|
out.FontNames = append(out.FontNames, str)
|
|
case 3: // Menu records
|
|
if str == "*" { // NEXT RECORD
|
|
out.Records = append(out.Records, record.Toplevel())
|
|
continue
|
|
}
|
|
|
|
k, v := asciiscan.ConsumeProperty(str)
|
|
switch k {
|
|
case "MENUID":
|
|
record = newRecord(nil)
|
|
case "SUBMENUID":
|
|
record = newRecord(record.Toplevel())
|
|
}
|
|
setProperty(record, k, v)
|
|
}
|
|
}
|
|
|
|
return out, nil
|
|
}
|
|
|
|
func LoadMenus(dir string) (map[string]*Menu, error) {
|
|
fis, err := ioutil.ReadDir(dir)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
out := make(map[string]*Menu, len(fis))
|
|
|
|
for _, fi := range fis {
|
|
relname := fi.Name()
|
|
basename := filepath.Base(relname)
|
|
extname := filepath.Ext(relname)
|
|
|
|
// Skip anything that isn't a .mnu file
|
|
if !strings.EqualFold(extname, ".mnu") {
|
|
continue
|
|
}
|
|
|
|
built, err := LoadMenu(filepath.Join(dir, relname))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("%s: %v", filepath.Join(dir, relname), err)
|
|
}
|
|
|
|
out[basename] = built
|
|
}
|
|
|
|
return out, nil
|
|
}
|
|
|
|
func newRecord(parent *Record) *Record {
|
|
out := &Record{
|
|
Parent: parent,
|
|
properties: map[string]string{},
|
|
}
|
|
|
|
if parent != nil {
|
|
parent.Children = append(parent.Children, out)
|
|
}
|
|
|
|
return out
|
|
}
|
|
|
|
func (r *Record) Toplevel() *Record {
|
|
if r.Parent != nil {
|
|
return r.Parent.Toplevel()
|
|
}
|
|
|
|
return r
|
|
}
|
|
|
|
func setProperty(r *Record, k, v string) {
|
|
vSplit := strings.Split(v, ",")
|
|
vInt, _ := strconv.Atoi(v)
|
|
vSplitInt := make([]int, len(vSplit))
|
|
|
|
for i, subV := range vSplit {
|
|
vSplitInt[i], _ = strconv.Atoi(subV)
|
|
}
|
|
|
|
switch k {
|
|
case "MENUID", "SUBMENUID":
|
|
r.Id = vInt
|
|
case "MENUTYPE", "SUBMENUTYPE":
|
|
r.Type = MenuType(vInt)
|
|
case "ACTIVE":
|
|
r.Active = (vInt != 0)
|
|
case "SPRITEID":
|
|
r.SpriteId = vSplitInt
|
|
case "X-CORD":
|
|
r.X = vInt
|
|
case "Y-CORD":
|
|
r.Y = vInt
|
|
case "DESC":
|
|
r.Desc = v
|
|
case "FONTTYPE":
|
|
r.FontType = vInt
|
|
case "DRAW TYPE":
|
|
r.DrawType = vInt
|
|
case "SHARE":
|
|
r.Share = vInt
|
|
default:
|
|
r.properties[k] = v
|
|
}
|
|
}
|
|
|
|
type Replacer interface {
|
|
Replace(int, *string)
|
|
}
|
|
|
|
func (r *Record) Internationalize(replacer Replacer) {
|
|
id, err := strconv.Atoi(r.Desc)
|
|
if err == nil {
|
|
replacer.Replace(id, &r.Desc)
|
|
}
|
|
|
|
for _, child := range r.Children {
|
|
child.Internationalize(replacer)
|
|
}
|
|
}
|
|
|
|
func (m *Menu) Internationalize(replacer Replacer) {
|
|
for _, record := range m.Records {
|
|
record.Internationalize(replacer)
|
|
}
|
|
}
|