Files
ordoor/internal/ui/noninteractive.go
Nick Thomas 27fbccdc5f Get the briefing menu linked up
Yes, that means hypertext is now a clickable.
2020-03-27 02:16:54 +00:00

229 lines
5.1 KiB
Go

package ui
import (
"image"
"log"
"github.com/hajimehoshi/ebiten"
"github.com/hajimehoshi/ebiten/ebitenutil"
"code.ur.gs/lupine/ordoor/internal/menus"
)
func init() {
registerBuilder(menus.TypeStatic, registerStatic)
registerBuilder(menus.TypeHypertext, registerHypertext)
registerBuilder(menus.TypeOverlay, registerOverlay)
registerBuilder(menus.TypeAnimationSample, registerAnimation)
registerBuilder(menus.TypeAnimationHover, registerAnimationHover)
}
// A non-interactive element is not a widget; it merely displays some pixels and
// may optionally have a tooltip for display within bounds.
//
// For non-animated non-interactive elements, just give them a single frame.
type noninteractive struct {
path string
frames animation
rect image.Rectangle
// Some non-interactives, e.g., overlays, are an image + text to be shown
textImg *ebiten.Image
clickImpl // Alright, alright, it turns out the bridge mission briefing is clickable
hoverImpl
}
// This particular animation has entry and exit sequences, which are invoked
// when entering and leaving hover, respectively. Example: bridge doors
type animationHover struct {
noninteractive // Use the frames in here for the "enter hover" animation
exitFrames animation // and here the "exit hover" animation
atTick int // Tracks progress through the frames
opening bool
closing bool
}
func registerStatic(d *Driver, r *menus.Record) error {
// FIXME: SpriteID takes precedence over SHARE if present, but is that right?
spriteId := r.Share
if len(r.SpriteId) > 0 && r.SpriteId[0] != -1 {
spriteId = r.SpriteId[0]
}
sprite, err := d.menu.Sprite(spriteId)
if err != nil {
return err
}
ni := &noninteractive{
path: r.Path(),
frames: animation{sprite.Image},
hoverImpl: hoverImpl{text: r.Text},
rect: sprite.Rect,
}
d.hoverables = append(d.hoverables, ni)
d.paintables = append(d.paintables, ni)
return nil
}
func registerHypertext(d *Driver, r *menus.Record) error {
sprite, err := d.menu.Sprite(r.Share)
if err != nil {
return err
}
ni := &noninteractive{
path: r.Path(),
hoverImpl: hoverImpl{text: r.Text},
rect: sprite.Rect,
}
d.clickables = append(d.clickables, ni)
d.hoverables = append(d.hoverables, ni)
return nil
}
// An overlay is a static image + some text that needs to be rendered
func registerOverlay(d *Driver, r *menus.Record) error {
sprite, err := d.menu.Sprite(r.Share)
if err != nil {
return err
}
ni := &noninteractive{
path: r.Path(),
frames: animation{sprite.Image},
rect: sprite.Rect,
}
if r.Text != "" {
// FIXME: we should be rendering the text much more nicely than this
textImg, err := ebiten.NewImage(sprite.Rect.Dx(), sprite.Rect.Dy(), ebiten.FilterDefault)
if err != nil {
return err
}
ebitenutil.DebugPrint(textImg, r.Text)
ni.textImg = textImg
} else {
log.Printf("Overlay without text detected: %#+v", r)
}
d.paintables = append(d.paintables, ni)
return nil
}
// An animation is a non-interactive element that displays something in a loop
func registerAnimation(d *Driver, r *menus.Record) error {
sprite, err := d.menu.Sprite(r.SpriteId[0])
if err != nil {
return err
}
frames, err := d.menu.Images(r.SpriteId[0], r.DrawType)
if err != nil {
return err
}
ani := &noninteractive{
path: r.Path(),
frames: animation(frames),
hoverImpl: hoverImpl{text: r.Text},
rect: sprite.Rect,
}
d.hoverables = append(d.hoverables, ani)
d.paintables = append(d.paintables, ani)
return nil
}
func registerAnimationHover(d *Driver, r *menus.Record) error {
sprite, err := d.menu.Sprite(r.SpriteId[0])
if err != nil {
return err
}
enterFrames, err := d.menu.Images(r.SpriteId[0], r.DrawType)
if err != nil {
return err
}
exitFrames, err := d.menu.Images(r.SpriteId[0]+r.DrawType, r.DrawType)
if err != nil {
return err
}
ani := &animationHover{
noninteractive: noninteractive{
path: r.Path(),
frames: animation(enterFrames),
hoverImpl: hoverImpl{text: r.Text},
rect: sprite.Rect,
},
exitFrames: animation(exitFrames),
}
d.hoverables = append(d.hoverables, ani)
d.paintables = append(d.paintables, ani)
return nil
}
func (n *noninteractive) id() string {
return n.path
}
func (n *noninteractive) bounds() image.Rectangle {
return n.rect
}
func (n *noninteractive) regions(tick int) []region {
out := oneRegion(n.bounds().Min, n.frames.image(tick))
if n.textImg != nil {
out = append(out, oneRegion(n.bounds().Min, n.textImg)...)
}
return out
}
func (a *animationHover) regions(tick int) []region {
if a.opening || a.closing {
var anim animation
if a.opening {
anim = a.frames
} else {
anim = a.exitFrames
}
out := oneRegion(a.bounds().Min, anim[a.atTick])
if a.atTick < len(anim)-1 {
a.atTick += 1
} else if !a.hoverState() {
a.closing = false
}
return out
}
// Nothing doing, show a closed door
return oneRegion(a.bounds().Min, a.frames.image(0))
}
func (a *animationHover) setHoverState(value bool) {
a.atTick = 0
a.opening = value
a.closing = !value
a.hoverImpl.setHoverState(value)
}