Allow dialogues to be hidden or shown
To do this, MENU and SUBMENU are split into two types (at last), and a Widget type is introduced. This should allow lots of code to be removed at some point.
This commit is contained in:
@@ -3,98 +3,40 @@ package ui
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
"log"
|
||||
"runtime/debug"
|
||||
"strconv"
|
||||
|
||||
"github.com/hajimehoshi/ebiten"
|
||||
"github.com/hajimehoshi/ebiten/ebitenutil"
|
||||
|
||||
"code.ur.gs/lupine/ordoor/internal/assetstore"
|
||||
"code.ur.gs/lupine/ordoor/internal/menus"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// FIXME: these need implementing
|
||||
|
||||
// Needed for MainGameChaos.mnu
|
||||
registerBuilder(menus.TypeStatusBar, registerDebug("Unimplemented StatusBar", nil))
|
||||
|
||||
// Needed for Multiplayer_Choose.mnu
|
||||
registerBuilder(menus.TypeComboBoxItem, registerDebug("Unimplemented ComboBoxItem", nil))
|
||||
registerBuilder(menus.TypeDropdownButton, registerDebug("Unimplemented DropdownButton", nil))
|
||||
|
||||
// Needed for Multiplayer_Configure.mnu
|
||||
registerBuilder(menus.TypeEditBox, registerDebug("Unimplemented EditBox", nil))
|
||||
|
||||
// Needed for Multiplayer_Connect.mnu
|
||||
registerBuilder(menus.TypeRadioButton, registerDebug("Unimplemented RadioButton", nil))
|
||||
}
|
||||
|
||||
const (
|
||||
OriginalX = 640.0
|
||||
OriginalY = 480.0
|
||||
)
|
||||
|
||||
var (
|
||||
// Widgets register their builder here
|
||||
widgetBuilders = map[menus.MenuType]builderFunc{}
|
||||
)
|
||||
|
||||
// Used to add widgets to a driver
|
||||
type builderFunc func(d *Driver, r *menus.Record) (children []*menus.Record, err error)
|
||||
|
||||
func registerDebug(reason string, onward builderFunc) builderFunc {
|
||||
return func(d *Driver, r *menus.Record) ([]*menus.Record, error) {
|
||||
log.Printf("%v: %v: %#+v", reason, r.Locator(), r)
|
||||
if onward == nil {
|
||||
return r.Children, nil
|
||||
}
|
||||
|
||||
return onward(d, r)
|
||||
}
|
||||
}
|
||||
|
||||
func noChildren(f func(d *Driver, r *menus.Record) error) builderFunc {
|
||||
return func(d *Driver, r *menus.Record) ([]*menus.Record, error) {
|
||||
if len(r.Children) > 0 {
|
||||
return nil, fmt.Errorf("Children in record %v:%v (%#+v)", r.Menu.Name, r.Path(), r)
|
||||
}
|
||||
|
||||
return nil, f(d, r)
|
||||
}
|
||||
}
|
||||
|
||||
func ownedByMenu(d *Driver, r *menus.Record) ([]*menus.Record, error) {
|
||||
return nil, fmt.Errorf("This record should be handled by a menu: %v:%v (%#+v)", r.Menu.Name, r.Path(), r)
|
||||
}
|
||||
|
||||
func registerBuilder(t menus.MenuType, f builderFunc) {
|
||||
if _, ok := widgetBuilders[t]; ok {
|
||||
panic(fmt.Sprintf("A builder for menu type %v already exists", t))
|
||||
}
|
||||
|
||||
widgetBuilders[t] = f
|
||||
}
|
||||
|
||||
// Driver acts as an interface between the main loop and the widgets specified
|
||||
// in a menu.
|
||||
//
|
||||
// Menu assets assume a 640x480 screen; Driver is responsible for scaling to the
|
||||
// actual screen size when drawing.
|
||||
//
|
||||
// TODO: move scaling responsibilities to Window?
|
||||
type Driver struct {
|
||||
Name string
|
||||
|
||||
assets *assetstore.AssetStore
|
||||
menu *assetstore.Menu
|
||||
|
||||
// UI elements we need to drive
|
||||
clickables []clickable
|
||||
freezables []freezable
|
||||
hoverables []hoverable
|
||||
mouseables []mouseable
|
||||
paintables []paintable
|
||||
valueables []valueable
|
||||
// UI elements we need to drive. Note that widgets are hierarchical - these
|
||||
// are just the toplevel. Dialogues are separated out. We only want to show
|
||||
// one dialogue at a time, and if a dialogue is active, the main widgets are
|
||||
// unusable (i.e., dialogues are modal)
|
||||
dialogues []*Widget
|
||||
widgets []*Widget
|
||||
|
||||
activeDialogue *Widget
|
||||
|
||||
cursor assetstore.CursorName
|
||||
|
||||
@@ -118,8 +60,8 @@ func NewDriver(assets *assetstore.AssetStore, menu *assetstore.Menu) (*Driver, e
|
||||
menu: menu,
|
||||
}
|
||||
|
||||
for _, record := range menu.Records() {
|
||||
if err := driver.addRecord(record); err != nil {
|
||||
for _, group := range menu.Groups() {
|
||||
if err := driver.registerGroup(group); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
@@ -127,104 +69,6 @@ func NewDriver(assets *assetstore.AssetStore, menu *assetstore.Menu) (*Driver, e
|
||||
return driver, nil
|
||||
}
|
||||
|
||||
func (d *Driver) Value(id string, into *string) error {
|
||||
for _, valueable := range d.valueables {
|
||||
if valueable.id() == id {
|
||||
*into = valueable.value()
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("Couldn't find valueable widget %v:%v", d.menu.Name, id)
|
||||
}
|
||||
|
||||
func (d *Driver) SetValue(id, value string) error {
|
||||
for _, valueable := range d.valueables {
|
||||
if valueable.id() == id {
|
||||
valueable.setValue(value)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("Couldn't find valueable widget %v:%v", d.menu.Name, id)
|
||||
}
|
||||
|
||||
func (d *Driver) ValueBool(id string, into *bool) error {
|
||||
var vStr string
|
||||
if err := d.Value(id, &vStr); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*into = vStr == "1"
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Driver) SetValueBool(id string, value bool) error {
|
||||
vStr := "0"
|
||||
if value {
|
||||
vStr = "1"
|
||||
}
|
||||
|
||||
return d.SetValue(id, vStr)
|
||||
}
|
||||
|
||||
func (d *Driver) SetFreeze(id string, value bool) error {
|
||||
for _, freezable := range d.freezables {
|
||||
if freezable.id() == id {
|
||||
freezable.setFreezeState(value)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("Couldn't find clickable widget %v:%v", d.menu.Name, id)
|
||||
}
|
||||
|
||||
func (d *Driver) OnClick(id string, f func()) error {
|
||||
for _, clickable := range d.clickables {
|
||||
if clickable.id() == id {
|
||||
clickable.onClick(f)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("Couldn't find clickable widget %v:%v", d.menu.Name, id)
|
||||
}
|
||||
|
||||
// FIXME: HURK. Surely I'm missing something? steps is value:offset
|
||||
func (d *Driver) ConfigureSlider(id string, steps map[int]int) error {
|
||||
for _, clickable := range d.clickables {
|
||||
if slider, ok := clickable.(*slider); ok && slider.id() == id {
|
||||
slider.steps = steps
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("Couldn't find slider %v:%v", d.menu.Name, id)
|
||||
}
|
||||
|
||||
func (d *Driver) ValueInt(id string, into *int) error {
|
||||
var vStr string
|
||||
if err := d.Value(id, &vStr); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
value, err := strconv.Atoi(vStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*into = value
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Driver) SetValueInt(id string, value int) error {
|
||||
vStr := strconv.Itoa(value)
|
||||
|
||||
return d.SetValue(id, vStr)
|
||||
}
|
||||
|
||||
func (d *Driver) Update(screenX, screenY int) error {
|
||||
if d == nil {
|
||||
debug.PrintStack()
|
||||
@@ -250,7 +94,7 @@ func (d *Driver) Update(screenX, screenY int) error {
|
||||
d.cursorOrig = image.Pt(int(mnX), int(mnY))
|
||||
|
||||
// Dispatch notifications to our widgets
|
||||
for _, hoverable := range d.hoverables {
|
||||
for _, hoverable := range d.hoverables() {
|
||||
inBounds := d.cursorOrig.In(hoverable.bounds())
|
||||
|
||||
d.hoverStartEvent(hoverable, inBounds)
|
||||
@@ -262,7 +106,7 @@ func (d *Driver) Update(screenX, screenY int) error {
|
||||
}
|
||||
|
||||
mouseIsDown := ebiten.IsMouseButtonPressed(ebiten.MouseButtonLeft)
|
||||
for _, clickable := range d.clickables {
|
||||
for _, clickable := range d.clickables() {
|
||||
inBounds := d.cursorOrig.In(clickable.bounds())
|
||||
mouseWasDown := clickable.mouseDownState()
|
||||
|
||||
@@ -271,7 +115,7 @@ func (d *Driver) Update(screenX, screenY int) error {
|
||||
d.mouseUpEvent(clickable, inBounds, mouseWasDown, mouseIsDown)
|
||||
}
|
||||
|
||||
for _, mouseable := range d.mouseables {
|
||||
for _, mouseable := range d.mouseables() {
|
||||
mouseable.registerMousePosition(d.cursorOrig)
|
||||
}
|
||||
|
||||
@@ -286,7 +130,7 @@ func (d *Driver) Draw(screen *ebiten.Image) error {
|
||||
|
||||
var do ebiten.DrawImageOptions
|
||||
|
||||
for _, paint := range d.paintables {
|
||||
for _, paint := range d.paintables() {
|
||||
for _, region := range paint.regions(d.ticks) {
|
||||
x, y := d.orig2native.Apply(float64(region.offset.X), float64(region.offset.Y))
|
||||
|
||||
@@ -321,71 +165,66 @@ func (d *Driver) Cursor() (*ebiten.Image, *ebiten.DrawImageOptions, error) {
|
||||
return cursor.Image, op, nil
|
||||
}
|
||||
|
||||
func (d *Driver) addRecord(record *menus.Record) error {
|
||||
//log.Printf("Adding record %v: %#+v", record.Locator(), record)
|
||||
children := record.Children
|
||||
func (d *Driver) clickables() []clickable {
|
||||
var out []clickable
|
||||
|
||||
handler, ok := widgetBuilders[record.Type]
|
||||
if !ok {
|
||||
return fmt.Errorf("UI driver encountered unknown menu record: %#+v", record)
|
||||
for _, widget := range d.widgets {
|
||||
out = append(out, widget.clickables()...)
|
||||
}
|
||||
|
||||
if handler != nil {
|
||||
var err error
|
||||
children, err = handler(d, record)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Recursively add all remaining children of this record
|
||||
for _, record := range children {
|
||||
if err := d.addRecord(record); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
return out
|
||||
}
|
||||
|
||||
func (d *Driver) hoverStartEvent(h hoverable, inBounds bool) {
|
||||
if inBounds && !h.hoverState() {
|
||||
//log.Printf("hoverable false -> true")
|
||||
h.setHoverState(true)
|
||||
func (d *Driver) freezables() []freezable {
|
||||
var out []freezable
|
||||
|
||||
for _, widget := range d.widgets {
|
||||
out = append(out, widget.freezables()...)
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func (d *Driver) hoverEndEvent(h hoverable, inBounds bool) {
|
||||
if !inBounds && h.hoverState() {
|
||||
//log.Printf("hoverable true -> false")
|
||||
h.setHoverState(false)
|
||||
func (d *Driver) hoverables() []hoverable {
|
||||
var out []hoverable
|
||||
|
||||
for _, widget := range d.widgets {
|
||||
out = append(out, widget.hoverables()...)
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func (d *Driver) mouseDownEvent(c clickable, inBounds, wasDown, isDown bool) {
|
||||
if inBounds && !wasDown && isDown {
|
||||
//log.Printf("mouse down false -> true")
|
||||
c.setMouseDownState(true)
|
||||
func (d *Driver) mouseables() []mouseable {
|
||||
var out []mouseable
|
||||
|
||||
for _, widget := range d.widgets {
|
||||
out = append(out, widget.mouseables()...)
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func (d *Driver) mouseClickEvent(c clickable, inBounds, wasDown, isDown bool) {
|
||||
if inBounds && wasDown && !isDown {
|
||||
//log.Printf("mouse click")
|
||||
c.registerMouseClick()
|
||||
func (d *Driver) paintables() []paintable {
|
||||
var out []paintable
|
||||
|
||||
for _, widget := range d.widgets {
|
||||
out = append(out, widget.paintables()...)
|
||||
}
|
||||
|
||||
if d.activeDialogue != nil {
|
||||
out = append(out, d.activeDialogue.paintables()...)
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func (d *Driver) mouseUpEvent(c clickable, inBounds, wasDown, isDown bool) {
|
||||
if inBounds {
|
||||
if wasDown && !isDown {
|
||||
//log.Printf("mouse down true -> false")
|
||||
c.setMouseDownState(false)
|
||||
}
|
||||
} else {
|
||||
if wasDown {
|
||||
//log.Printf("mouse down true -> false")
|
||||
c.setMouseDownState(false)
|
||||
}
|
||||
func (d *Driver) valueables() []valueable {
|
||||
var out []valueable
|
||||
|
||||
for _, widget := range d.widgets {
|
||||
out = append(out, widget.valueables()...)
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
Reference in New Issue
Block a user