Make include directives work in .mnu files
This commit is contained in:
@@ -134,6 +134,16 @@ $GenLoad.mni
|
|||||||
It looks like we just interpolate the named file into the text when we come
|
It looks like we just interpolate the named file into the text when we come
|
||||||
across one of these lines.
|
across one of these lines.
|
||||||
|
|
||||||
|
The `MENUID` in `GenDialog` and `GenLoad` is a 2-element list, like `1000,1`
|
||||||
|
or `2000,2`. The second number corresponds to the offset in the list of object
|
||||||
|
files.
|
||||||
|
|
||||||
|
## `MENUTYPE`
|
||||||
|
|
||||||
|
Here's the full list of
|
||||||
|
|
||||||
|
## `SUBMENUTYPE`
|
||||||
|
|
||||||
## (Sub)menu types
|
## (Sub)menu types
|
||||||
|
|
||||||
The types seem to refer to different types of UI widget. Here's a list of unique
|
The types seem to refer to different types of UI widget. Here's a list of unique
|
||||||
|
@@ -7,10 +7,10 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Menu struct {
|
type Menu struct {
|
||||||
assets *AssetStore
|
assets *AssetStore
|
||||||
fonts []*Font // TODO: place the fonts directly into the relevant records
|
fonts []*Font // TODO: place the fonts directly into the relevant records
|
||||||
obj *Object // TODO: handle multiple objects in the menu
|
objects []*Object // TODO: place the objects directly into the relevant records
|
||||||
raw *menus.Menu // TODO: remove raw
|
raw *menus.Menu // TODO: remove raw
|
||||||
|
|
||||||
Name string
|
Name string
|
||||||
}
|
}
|
||||||
@@ -25,10 +25,10 @@ func (m *Menu) Font(idx int) *Font {
|
|||||||
return m.fonts[idx]
|
return m.fonts[idx]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Menu) Images(start, count int) ([]*ebiten.Image, error) {
|
func (m *Menu) Images(objIdx, start, count int) ([]*ebiten.Image, error) {
|
||||||
out := make([]*ebiten.Image, count)
|
out := make([]*ebiten.Image, count)
|
||||||
|
|
||||||
sprites, err := m.Sprites(start, count)
|
sprites, err := m.Sprites(objIdx, start, count)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -40,11 +40,11 @@ func (m *Menu) Images(start, count int) ([]*ebiten.Image, error) {
|
|||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Menu) Sprites(start, count int) ([]*Sprite, error) {
|
func (m *Menu) Sprites(objIdx, start, count int) ([]*Sprite, error) {
|
||||||
out := make([]*Sprite, count)
|
out := make([]*Sprite, count)
|
||||||
|
|
||||||
for i := start; i < start+count; i++ {
|
for i := start; i < start+count; i++ {
|
||||||
sprite, err := m.Sprite(i)
|
sprite, err := m.Sprite(objIdx, i)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -55,8 +55,8 @@ func (m *Menu) Sprites(start, count int) ([]*Sprite, error) {
|
|||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Menu) Sprite(idx int) (*Sprite, error) {
|
func (m *Menu) Sprite(objIdx, idx int) (*Sprite, error) {
|
||||||
return m.obj.Sprite(idx)
|
return m.objects[objIdx].Sprite(idx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AssetStore) Menu(name string) (*Menu, error) {
|
func (a *AssetStore) Menu(name string) (*Menu, error) {
|
||||||
@@ -93,29 +93,40 @@ func (a *AssetStore) Menu(name string) (*Menu, error) {
|
|||||||
|
|
||||||
raw.Internationalize(i18n)
|
raw.Internationalize(i18n)
|
||||||
|
|
||||||
obj, err := a.loadMenuObject(raw) // TODO: multiple objects
|
// FIXME: we should parse the menu into a list of elements like "ListBox",
|
||||||
|
// "Dialogue", etc, and present those with objects already selected
|
||||||
|
objects, err := a.loadMenuObjects(raw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
menu := &Menu{
|
menu := &Menu{
|
||||||
assets: a,
|
assets: a,
|
||||||
fonts: fonts,
|
fonts: fonts,
|
||||||
obj: obj,
|
objects: objects,
|
||||||
raw: raw,
|
raw: raw,
|
||||||
Name: name,
|
Name: name,
|
||||||
}
|
}
|
||||||
|
|
||||||
a.menus[name] = menu
|
a.menus[name] = menu
|
||||||
return menu, nil
|
return menu, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AssetStore) loadMenuObject(menu *menus.Menu) (*Object, error) {
|
func (a *AssetStore) loadMenuObjects(menu *menus.Menu) ([]*Object, error) {
|
||||||
filename := menu.ObjectFiles[0]
|
out := make([]*Object, len(menu.ObjectFiles))
|
||||||
filename, err := a.lookup(filename, "", "Menu") // Extension already present
|
for i, name := range menu.ObjectFiles {
|
||||||
if err != nil {
|
filename, err := a.lookup(name, "", "Menu") // Extension already present
|
||||||
return nil, err
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
obj, err := a.ObjectByPath(filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
out[i] = obj
|
||||||
}
|
}
|
||||||
|
|
||||||
return a.ObjectByPath(filename)
|
return out, nil
|
||||||
}
|
}
|
||||||
|
@@ -16,9 +16,10 @@ import (
|
|||||||
type MenuType int
|
type MenuType int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
TypeStatic MenuType = 0
|
TypeStatic MenuType = 0
|
||||||
TypeMenu MenuType = 1
|
TypeMenu MenuType = 1
|
||||||
TypeDragMenu MenuType = 2 // Only seen in Configure_Vehicle_{Chaos,Ultra}
|
TypeDragMenu MenuType = 2 // Only seen in Configure_Vehicle_{Chaos,Ultra}
|
||||||
|
|
||||||
TypeSimpleButton MenuType = 3
|
TypeSimpleButton MenuType = 3
|
||||||
TypeDoorHotspot MenuType = 30 // Like a button I guess? "FONTTYPE is animation speed"
|
TypeDoorHotspot MenuType = 30 // Like a button I guess? "FONTTYPE is animation speed"
|
||||||
TypeDoorHotspot2 MenuType = 31 // Seems like a duplicate of the above? What's different?
|
TypeDoorHotspot2 MenuType = 31 // Seems like a duplicate of the above? What's different?
|
||||||
@@ -85,7 +86,9 @@ type Record struct {
|
|||||||
Parent *Record
|
Parent *Record
|
||||||
Children []*Record
|
Children []*Record
|
||||||
|
|
||||||
Id int
|
Id int
|
||||||
|
ObjectIdx int // Can be specified in MENUID, defaults to 0
|
||||||
|
|
||||||
Type MenuType
|
Type MenuType
|
||||||
DrawType int
|
DrawType int
|
||||||
FontType int
|
FontType int
|
||||||
@@ -109,11 +112,11 @@ type Menu struct {
|
|||||||
ObjectFiles []string
|
ObjectFiles []string
|
||||||
FontNames []string
|
FontNames []string
|
||||||
|
|
||||||
|
// These are properties set in the menu header. We don't know what they're
|
||||||
|
// all for.
|
||||||
BackgroundColor color.Color
|
BackgroundColor color.Color
|
||||||
HypertextColor color.Color
|
HypertextColor color.Color
|
||||||
|
FontType int
|
||||||
// FIXME: turn these into first-class data
|
|
||||||
Properties map[string]string
|
|
||||||
|
|
||||||
// The actual menu records. There are multiple top-level items. Submenus are
|
// The actual menu records. There are multiple top-level items. Submenus are
|
||||||
// only ever nested one deep.
|
// only ever nested one deep.
|
||||||
@@ -122,10 +125,9 @@ type Menu struct {
|
|||||||
|
|
||||||
func LoadMenu(filename string) (*Menu, error) {
|
func LoadMenu(filename string) (*Menu, error) {
|
||||||
name := filepath.Base(filename)
|
name := filepath.Base(filename)
|
||||||
name = strings.Replace(name, filepath.Ext(name), "", -1)
|
name = strings.TrimSuffix(name, filepath.Ext(name))
|
||||||
name = strings.ToLower(name)
|
name = strings.ToLower(name)
|
||||||
|
|
||||||
// FIXME: this needs turning into a real parser sometime
|
|
||||||
scanner, err := asciiscan.New(filename)
|
scanner, err := asciiscan.New(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -133,81 +135,135 @@ func LoadMenu(filename string) (*Menu, error) {
|
|||||||
|
|
||||||
defer scanner.Close()
|
defer scanner.Close()
|
||||||
|
|
||||||
var str string
|
|
||||||
var record *Record
|
|
||||||
|
|
||||||
section := 0
|
|
||||||
isProp := false
|
|
||||||
out := &Menu{
|
out := &Menu{
|
||||||
Name: name,
|
Name: name,
|
||||||
Properties: map[string]string{},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := loadObjects(out, scanner); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := loadProperties(out, scanner); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := loadFonts(out, scanner); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := loadRecords(filepath.Dir(filename), out, scanner); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadObjects(menu *Menu, scanner *asciiscan.Scanner) error {
|
||||||
|
strs, err := scanner.ConsumeStringList()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
menu.ObjectFiles = strs
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadProperties(menu *Menu, scanner *asciiscan.Scanner) error {
|
||||||
for {
|
for {
|
||||||
str, err = scanner.ConsumeString()
|
ok, err := scanner.PeekProperty()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
|
if !ok {
|
||||||
// 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
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
switch section {
|
k, v, err := scanner.ConsumeProperty()
|
||||||
case 0: // List of object files
|
if err != nil {
|
||||||
out.ObjectFiles = append(out.ObjectFiles, str)
|
return err
|
||||||
case 1: // List of properties
|
}
|
||||||
k, v := asciiscan.ConsumeProperty(str)
|
|
||||||
vInt, err := strconv.Atoi(v) // FIXME:
|
|
||||||
switch k {
|
|
||||||
case "BACKGROUND COLOR 0..255..-1 trans":
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
out.BackgroundColor = data.ColorPalette[vInt]
|
vInt, err := strconv.Atoi(v) // All properties have been int
|
||||||
case "HYPERTEXT COLOR 0..255":
|
if err != nil {
|
||||||
if err != nil {
|
return err
|
||||||
return nil, err
|
}
|
||||||
}
|
|
||||||
|
|
||||||
out.HypertextColor = data.ColorPalette[vInt]
|
switch strings.ToUpper(k) {
|
||||||
default:
|
case "BACKGROUND COLOR 0..255..-1 TRANS":
|
||||||
out.Properties[k] = v
|
menu.BackgroundColor = data.ColorPalette[vInt]
|
||||||
}
|
case "HYPERTEXT COLOR 0..255":
|
||||||
case 2: // list of fonts
|
menu.HypertextColor = data.ColorPalette[vInt]
|
||||||
// FIXME: do we need to do something cleverer here?
|
case "FONT TYPE 0..5":
|
||||||
if str == "NULL" {
|
menu.FontType = vInt
|
||||||
continue
|
default:
|
||||||
}
|
return fmt.Errorf("Unhandled menu property in %v: %q=%q", menu.Name, k, v)
|
||||||
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(out, nil)
|
|
||||||
case "SUBMENUID":
|
|
||||||
record = newRecord(out, record.Toplevel())
|
|
||||||
}
|
|
||||||
setProperty(record, k, v)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("Menu properties: %#+v", out.Properties)
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
return out, nil
|
func loadFonts(menu *Menu, scanner *asciiscan.Scanner) error {
|
||||||
|
// FIXME: Can we just ignore NULL, or does the index matter?
|
||||||
|
strs, err := scanner.ConsumeStringList("NULL")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
menu.FontNames = strs
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadRecords(baseDir string, menu *Menu, scanner *asciiscan.Scanner) error {
|
||||||
|
var record *Record // We build records here and add them when complete
|
||||||
|
|
||||||
|
for {
|
||||||
|
str, err := scanner.ConsumeString()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(str, "$") {
|
||||||
|
subScanner, err := asciiscan.New(filepath.Join(baseDir, str[1:]))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = loadRecords(baseDir, menu, subScanner)
|
||||||
|
subScanner.Close() // Don't keep this around for all of loadRecords
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Processing child %q: %v", str, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
switch str {
|
||||||
|
case "*":
|
||||||
|
if record != nil {
|
||||||
|
menu.Records = append(menu.Records, record.Toplevel())
|
||||||
|
}
|
||||||
|
|
||||||
|
continue // NEXT RECORD
|
||||||
|
case "~":
|
||||||
|
return nil // THE END
|
||||||
|
}
|
||||||
|
|
||||||
|
k, v := asciiscan.ConsumeProperty(str)
|
||||||
|
switch k {
|
||||||
|
case "MENUID":
|
||||||
|
record = newRecord(menu, nil)
|
||||||
|
case "SUBMENUID":
|
||||||
|
record = newRecord(menu, record.Toplevel())
|
||||||
|
}
|
||||||
|
setProperty(record, k, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func LoadMenus(dir string) (map[string]*Menu, error) {
|
func LoadMenus(dir string) (map[string]*Menu, error) {
|
||||||
@@ -271,8 +327,25 @@ func setProperty(r *Record, k, v string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch k {
|
switch k {
|
||||||
case "MENUID", "SUBMENUID":
|
case "MENUID":
|
||||||
r.Id = vInt
|
// ObjectIdx can be specified in the MENUID. Only seen for .mni files
|
||||||
|
if strings.Contains(v, ",") && len(vSplitInt) >= 2 {
|
||||||
|
r.Id = vSplitInt[0]
|
||||||
|
r.ObjectIdx = vSplitInt[1]
|
||||||
|
} else {
|
||||||
|
r.Id = vInt
|
||||||
|
}
|
||||||
|
case "SUBMENUID":
|
||||||
|
if strings.Contains(v, ",") {
|
||||||
|
log.Printf("%v has an object index in SUBMENUID - surprising", r.Locator())
|
||||||
|
r.Id = vSplitInt[0]
|
||||||
|
r.ObjectIdx = vSplitInt[1]
|
||||||
|
} else {
|
||||||
|
r.Id = vInt
|
||||||
|
if r.Parent != nil { // Children seem to inherit from parents?
|
||||||
|
r.ObjectIdx = r.Parent.ObjectIdx
|
||||||
|
}
|
||||||
|
}
|
||||||
case "MENUTYPE", "SUBMENUTYPE":
|
case "MENUTYPE", "SUBMENUTYPE":
|
||||||
if strings.Contains(v, ",") {
|
if strings.Contains(v, ",") {
|
||||||
r.Type = MenuType(vSplitInt[0]) // FIXME: what are the other values in this case?
|
r.Type = MenuType(vSplitInt[0]) // FIXME: what are the other values in this case?
|
||||||
|
@@ -51,12 +51,12 @@ func registerInvokeButton(d *Driver, r *menus.Record) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func registerMainButton(d *Driver, r *menus.Record) error {
|
func registerMainButton(d *Driver, r *menus.Record) error {
|
||||||
sprites, err := d.menu.Sprites(r.Share, 3) // base, pressed, disabled
|
sprites, err := d.menu.Sprites(r.ObjectIdx, r.Share, 3) // base, pressed, disabled
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
hovers, err := d.menu.Images(r.SpriteId[0], r.DrawType)
|
hovers, err := d.menu.Images(r.ObjectIdx, r.SpriteId[0], r.DrawType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -81,7 +81,7 @@ func registerMainButton(d *Driver, r *menus.Record) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func registerDoorHotspot(d *Driver, r *menus.Record) error {
|
func registerDoorHotspot(d *Driver, r *menus.Record) error {
|
||||||
sprites, err := d.menu.Sprites(r.Share, 2) // base, pressed
|
sprites, err := d.menu.Sprites(r.ObjectIdx, r.Share, 2) // base, pressed
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -104,7 +104,7 @@ func registerDoorHotspot(d *Driver, r *menus.Record) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func registerButton(d *Driver, r *menus.Record, spriteId int) (*button, error) {
|
func registerButton(d *Driver, r *menus.Record, spriteId int) (*button, error) {
|
||||||
sprites, err := d.menu.Sprites(spriteId, 3) // base, pressed, disabled
|
sprites, err := d.menu.Sprites(r.ObjectIdx, spriteId, 3) // base, pressed, disabled
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
20
internal/ui/dialogue.go
Normal file
20
internal/ui/dialogue.go
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"code.ur.gs/lupine/ordoor/internal/menus"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// Needed for Keyboard.mnu (main -> options -> keyboard).
|
||||||
|
// Dialogues can be active(?) or not. If they're not, then they are hidden.
|
||||||
|
// Dialogues seem to be modal in all cases?
|
||||||
|
registerBuilder(menus.TypeDialogue, registerDebug("WIP Dialogue", registerDialogue))
|
||||||
|
}
|
||||||
|
|
||||||
|
func registerDialogue(d *Driver, r *menus.Record) ([]*menus.Record, error) {
|
||||||
|
// The dialogue itself has a sprite
|
||||||
|
_, err := registerNoninteractive(d, r)
|
||||||
|
|
||||||
|
// TODO: we need to group these. Z levels seem overkill?
|
||||||
|
return r.Children, err
|
||||||
|
}
|
@@ -17,11 +17,6 @@ import (
|
|||||||
func init() {
|
func init() {
|
||||||
// FIXME: these need implementing
|
// FIXME: these need implementing
|
||||||
|
|
||||||
// Needed for Keyboard.mnu (main -> options -> keyboard)
|
|
||||||
registerBuilder(menus.TypeDialogue, registerDebug("Unimplemented Dialogue", nil))
|
|
||||||
|
|
||||||
// Needed for ChaEquip.mnu
|
|
||||||
|
|
||||||
// Needed for MainGameChaos.mnu
|
// Needed for MainGameChaos.mnu
|
||||||
registerBuilder(menus.TypeStatusBar, registerDebug("Unimplemented StatusBar", nil))
|
registerBuilder(menus.TypeStatusBar, registerDebug("Unimplemented StatusBar", nil))
|
||||||
|
|
||||||
@@ -51,7 +46,7 @@ type builderFunc func(d *Driver, r *menus.Record) (children []*menus.Record, err
|
|||||||
|
|
||||||
func registerDebug(reason string, onward builderFunc) builderFunc {
|
func registerDebug(reason string, onward builderFunc) builderFunc {
|
||||||
return func(d *Driver, r *menus.Record) ([]*menus.Record, error) {
|
return func(d *Driver, r *menus.Record) ([]*menus.Record, error) {
|
||||||
log.Printf("%v: %#+v", reason, r)
|
log.Printf("%v: %v: %#+v", reason, r.Locator(), r)
|
||||||
if onward == nil {
|
if onward == nil {
|
||||||
return r.Children, nil
|
return r.Children, nil
|
||||||
}
|
}
|
||||||
@@ -327,7 +322,7 @@ func (d *Driver) Cursor() (*ebiten.Image, *ebiten.DrawImageOptions, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *Driver) addRecord(record *menus.Record) error {
|
func (d *Driver) addRecord(record *menus.Record) error {
|
||||||
log.Printf("Adding record %v: %#+v", record.Locator(), record)
|
//log.Printf("Adding record %v: %#+v", record.Locator(), record)
|
||||||
children := record.Children
|
children := record.Children
|
||||||
|
|
||||||
handler, ok := widgetBuilders[record.Type]
|
handler, ok := widgetBuilders[record.Type]
|
||||||
|
@@ -21,7 +21,7 @@ type inventorySelect struct {
|
|||||||
|
|
||||||
// Called from the menu, which fills "others" for us
|
// Called from the menu, which fills "others" for us
|
||||||
func registerInventorySelect(d *Driver, r *menus.Record) (*inventorySelect, error) {
|
func registerInventorySelect(d *Driver, r *menus.Record) (*inventorySelect, error) {
|
||||||
sprites, err := d.menu.Sprites(r.Share, 3) // unchecked, checked, disabled
|
sprites, err := d.menu.Sprites(r.ObjectIdx, r.Share, 3) // unchecked, checked, disabled
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@@ -101,7 +101,7 @@ func registerListBox(d *Driver, menu *menus.Record) ([]*menus.Record, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
thumbBaseSpr, err := d.menu.Sprite(thumb.Share)
|
thumbBaseSpr, err := d.menu.Sprite(menu.ObjectIdx, thumb.Share)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -111,7 +111,7 @@ func registerListBox(d *Driver, menu *menus.Record) ([]*menus.Record, error) {
|
|||||||
thumbSprId = thumb.Share
|
thumbSprId = thumb.Share
|
||||||
}
|
}
|
||||||
|
|
||||||
thumbImgSpr, err := d.menu.Sprite(thumbSprId)
|
thumbImgSpr, err := d.menu.Sprite(menu.ObjectIdx, thumbSprId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@@ -70,7 +70,7 @@ func registerNoninteractive(d *Driver, r *menus.Record) (*noninteractive, error)
|
|||||||
spriteId = r.SpriteId[0]
|
spriteId = r.SpriteId[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
sprite, err := d.menu.Sprite(spriteId)
|
sprite, err := d.menu.Sprite(r.ObjectIdx, spriteId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -89,7 +89,7 @@ func registerNoninteractive(d *Driver, r *menus.Record) (*noninteractive, error)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func registerHypertext(d *Driver, r *menus.Record) error {
|
func registerHypertext(d *Driver, r *menus.Record) error {
|
||||||
sprite, err := d.menu.Sprite(r.Share)
|
sprite, err := d.menu.Sprite(r.ObjectIdx, r.Share)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -108,7 +108,7 @@ func registerHypertext(d *Driver, r *menus.Record) error {
|
|||||||
|
|
||||||
// An overlay is a static image + some text that needs to be rendered
|
// An overlay is a static image + some text that needs to be rendered
|
||||||
func registerOverlay(d *Driver, r *menus.Record) error {
|
func registerOverlay(d *Driver, r *menus.Record) error {
|
||||||
sprite, err := d.menu.Sprite(r.Share)
|
sprite, err := d.menu.Sprite(r.ObjectIdx, r.Share)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -139,12 +139,12 @@ func registerOverlay(d *Driver, r *menus.Record) error {
|
|||||||
|
|
||||||
// An animation is a non-interactive element that displays something in a loop
|
// An animation is a non-interactive element that displays something in a loop
|
||||||
func registerAnimation(d *Driver, r *menus.Record) error {
|
func registerAnimation(d *Driver, r *menus.Record) error {
|
||||||
sprite, err := d.menu.Sprite(r.SpriteId[0])
|
sprite, err := d.menu.Sprite(r.ObjectIdx, r.SpriteId[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
frames, err := d.menu.Images(r.SpriteId[0], r.DrawType)
|
frames, err := d.menu.Images(r.ObjectIdx, r.SpriteId[0], r.DrawType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -163,17 +163,17 @@ func registerAnimation(d *Driver, r *menus.Record) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func registerAnimationHover(d *Driver, r *menus.Record) error {
|
func registerAnimationHover(d *Driver, r *menus.Record) error {
|
||||||
sprite, err := d.menu.Sprite(r.SpriteId[0])
|
sprite, err := d.menu.Sprite(r.ObjectIdx, r.SpriteId[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
enterFrames, err := d.menu.Images(r.SpriteId[0], r.DrawType)
|
enterFrames, err := d.menu.Images(r.ObjectIdx, r.SpriteId[0], r.DrawType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
exitFrames, err := d.menu.Images(r.SpriteId[0]+r.DrawType, r.DrawType)
|
exitFrames, err := d.menu.Images(r.ObjectIdx, r.SpriteId[0]+r.DrawType, r.DrawType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@@ -39,7 +39,7 @@ type slider struct {
|
|||||||
|
|
||||||
// A checkbox has 3 sprites, and 3 states: unchecked, checked, disabled.
|
// A checkbox has 3 sprites, and 3 states: unchecked, checked, disabled.
|
||||||
func registerCheckbox(d *Driver, r *menus.Record) error {
|
func registerCheckbox(d *Driver, r *menus.Record) error {
|
||||||
sprites, err := d.menu.Sprites(r.Share, 3) // unchecked, disabled, checked
|
sprites, err := d.menu.Sprites(r.ObjectIdx, r.Share, 3) // unchecked, disabled, checked
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -65,7 +65,7 @@ func registerCheckbox(d *Driver, r *menus.Record) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func registerSlider(d *Driver, r *menus.Record) error {
|
func registerSlider(d *Driver, r *menus.Record) error {
|
||||||
sprites, err := d.menu.Sprites(r.Share, 3) // base, clicked, slider element
|
sprites, err := d.menu.Sprites(r.ObjectIdx, r.Share, 3) // base, clicked, slider element
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@@ -4,6 +4,7 @@ package asciiscan
|
|||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -15,6 +16,9 @@ var hashComment = []byte("#")
|
|||||||
type Scanner struct {
|
type Scanner struct {
|
||||||
bufio *bufio.Scanner
|
bufio *bufio.Scanner
|
||||||
closer io.Closer
|
closer io.Closer
|
||||||
|
|
||||||
|
// If we've peeked, there will be items here
|
||||||
|
buffered []string
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(filename string) (*Scanner, error) {
|
func New(filename string) (*Scanner, error) {
|
||||||
@@ -38,6 +42,13 @@ func (s *Scanner) Close() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Scanner) ConsumeString() (string, error) {
|
func (s *Scanner) ConsumeString() (string, error) {
|
||||||
|
if len(s.buffered) > 0 {
|
||||||
|
out, buffered := s.buffered[0], s.buffered[1:]
|
||||||
|
s.buffered = buffered
|
||||||
|
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
for s.bufio.Scan() {
|
for s.bufio.Scan() {
|
||||||
line := s.bufio.Bytes()
|
line := s.bufio.Bytes()
|
||||||
|
|
||||||
@@ -68,15 +79,41 @@ func ConsumeProperty(s string) (string, string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
parts := strings.SplitN(s, ":", 2)
|
parts := strings.SplitN(s, ":", 2)
|
||||||
|
|
||||||
return strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1])
|
return strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1])
|
||||||
}
|
}
|
||||||
|
|
||||||
// Peek ahead in the input stream to see if the next line might be a property
|
// Check to see if the line looks like a property (contains a colon character).
|
||||||
// (contain a colon character).
|
|
||||||
func IsProperty(s string) bool {
|
func IsProperty(s string) bool {
|
||||||
return strings.Contains(s, ":")
|
return strings.Contains(s, ":")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Checks if the next line might be a property, without reading it
|
||||||
|
func (s *Scanner) PeekProperty() (bool, error) {
|
||||||
|
str, err := s.ConsumeString()
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
s.buffered = append(s.buffered, str)
|
||||||
|
|
||||||
|
return IsProperty(str), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Scanner) ConsumeProperty() (string, string, error) {
|
||||||
|
str, err := s.ConsumeString()
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !IsProperty(str) {
|
||||||
|
return "", "", fmt.Errorf("Not a property: %q", str)
|
||||||
|
}
|
||||||
|
|
||||||
|
k, v := ConsumeProperty(str)
|
||||||
|
return k, v, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Scanner) ConsumeInt() (int, error) {
|
func (s *Scanner) ConsumeInt() (int, error) {
|
||||||
str, err := s.ConsumeString()
|
str, err := s.ConsumeString()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -86,6 +123,39 @@ func (s *Scanner) ConsumeInt() (int, error) {
|
|||||||
return strconv.Atoi(str)
|
return strconv.Atoi(str)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reads a list of non-property lines, skipping any that match the given strings
|
||||||
|
func (s *Scanner) ConsumeStringList(skip ...string) ([]string, error) {
|
||||||
|
skipper := make(map[string]bool, len(skip))
|
||||||
|
for _, str := range skip {
|
||||||
|
skipper[str] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
var out []string
|
||||||
|
|
||||||
|
for {
|
||||||
|
isProp, err := s.PeekProperty()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// The object list is terminated by the first property
|
||||||
|
if isProp {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
str, err := s.ConsumeString()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !skipper[str] {
|
||||||
|
out = append(out, str)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Scanner) ConsumeIntPtr(to *int) error {
|
func (s *Scanner) ConsumeIntPtr(to *int) error {
|
||||||
val, err := s.ConsumeInt()
|
val, err := s.ConsumeInt()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
Reference in New Issue
Block a user