Files
ordoor/internal/ui/driver.go

270 lines
6.0 KiB
Go
Raw Normal View History

package ui
import (
"fmt"
"image"
"runtime/debug"
2020-11-21 19:27:09 +00:00
"github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/ebitenutil"
"code.ur.gs/lupine/ordoor/internal/assetstore"
)
const (
OriginalX = 640.0
OriginalY = 480.0
)
// 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. 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
// The cursor in two different coordinate spaces: original, and screen-scaled
cursorOrig image.Point
cursorScaled image.Point
// These two matrices are used for scaling between the two
orig2native ebiten.GeoM
native2orig ebiten.GeoM
ticks int // Used in animation effects
tooltip string
}
func NewDriver(assets *assetstore.AssetStore, menu *assetstore.Menu) (*Driver, error) {
driver := &Driver{
Name: menu.Name,
assets: assets,
menu: menu,
}
for _, group := range menu.Groups() {
if err := driver.registerGroup(group); err != nil {
return nil, err
}
}
return driver, nil
}
func (d *Driver) Update(screenX, screenY int) error {
if d == nil {
debug.PrintStack()
return fmt.Errorf("Tried to update a nil ui.Driver")
}
// This will be updated while processing hovers
d.tooltip = ""
d.ticks += 1
// Update translation matrices
d.orig2native.Reset()
d.orig2native.Scale(float64(screenX)/OriginalX, float64(screenY)/OriginalY)
d.native2orig = d.orig2native
d.native2orig.Invert()
// Update original and scaled mouse coordinates
mouseX, mouseY := ebiten.CursorPosition()
d.cursorScaled = image.Pt(mouseX, mouseY)
mnX, mnY := d.native2orig.Apply(float64(mouseX), float64(mouseY))
d.cursorOrig = image.Pt(int(mnX), int(mnY))
// Dispatch notifications to our widgets
for _, hoverable := range d.activeHoverables() {
inBounds := d.cursorOrig.In(hoverable.bounds())
d.hoverStartEvent(hoverable, inBounds)
d.hoverEndEvent(hoverable, inBounds)
if hoverable.hoverState() && hoverable.tooltip() != "" {
d.tooltip = hoverable.tooltip()
}
}
mouseIsDown := ebiten.IsMouseButtonPressed(ebiten.MouseButtonLeft)
for _, clickable := range d.activeClickables() {
inBounds := d.cursorOrig.In(clickable.bounds())
mouseWasDown := clickable.mouseDownState()
d.mouseDownEvent(clickable, inBounds, mouseWasDown, mouseIsDown)
d.mouseClickEvent(clickable, inBounds, mouseWasDown, mouseIsDown)
d.mouseUpEvent(clickable, inBounds, mouseWasDown, mouseIsDown)
}
for _, mouseable := range d.activeMouseables() {
mouseable.registerMousePosition(d.cursorOrig)
}
return nil
}
func (d *Driver) Draw(screen *ebiten.Image) error {
if d == nil {
debug.PrintStack()
return fmt.Errorf("Tried to draw a nil ui.Driver")
}
var do ebiten.DrawImageOptions
for _, paint := range d.activePaintables() {
for _, region := range paint.regions(d.ticks) {
x, y := d.orig2native.Apply(float64(region.offset.X), float64(region.offset.Y))
do.GeoM = d.orig2native
do.GeoM.Translate(x, y)
2020-11-21 19:27:09 +00:00
screen.DrawImage(region.image, &do)
}
}
if d.tooltip != "" {
x, y := d.cursorScaled.X+16, d.cursorScaled.Y-16
ebitenutil.DebugPrintAt(screen, d.tooltip, x, y)
}
return nil
}
func (d *Driver) Cursor() (*ebiten.Image, *ebiten.DrawImageOptions, error) {
cursor, err := d.assets.Cursor(d.cursor)
if err != nil {
return nil, nil, err
}
op := &ebiten.DrawImageOptions{}
op.GeoM.Translate(float64(d.cursorOrig.X), float64(d.cursorOrig.Y))
op.GeoM.Concat(d.orig2native)
op.GeoM.Translate(float64(-cursor.Hotspot.X), float64(-cursor.Hotspot.Y))
return cursor.Image, op, nil
}
func (d *Driver) allClickables() []clickable {
var out []clickable
for _, widget := range d.widgets {
2020-04-19 18:21:08 +01:00
out = append(out, widget.allClickables()...)
}
for _, widget := range d.dialogues {
2020-04-19 18:21:08 +01:00
out = append(out, widget.allClickables()...)
}
return out
}
func (d *Driver) allFreezables() []freezable {
var out []freezable
for _, widget := range d.widgets {
2020-04-19 18:21:08 +01:00
out = append(out, widget.allFreezables()...)
}
for _, widget := range d.dialogues {
2020-04-19 18:21:08 +01:00
out = append(out, widget.allFreezables()...)
}
return out
}
func (d *Driver) allValueables() []valueable {
var out []valueable
for _, widget := range d.widgets {
2020-04-19 18:21:08 +01:00
out = append(out, widget.allValueables()...)
}
for _, widget := range d.dialogues {
2020-04-19 18:21:08 +01:00
out = append(out, widget.allValueables()...)
}
return out
}
func (d *Driver) activeClickables() []clickable {
if d.activeDialogue != nil {
2020-04-19 18:21:08 +01:00
return d.activeDialogue.activeClickables()
}
var out []clickable
for _, widget := range d.widgets {
2020-04-19 18:21:08 +01:00
out = append(out, widget.activeClickables()...)
}
return out
}
func (d *Driver) activeHoverables() []hoverable {
if d.activeDialogue != nil {
2020-04-19 18:21:08 +01:00
return d.activeDialogue.activeHoverables()
}
var out []hoverable
for _, widget := range d.widgets {
2020-04-19 18:21:08 +01:00
out = append(out, widget.activeHoverables()...)
}
return out
}
func (d *Driver) activeMouseables() []mouseable {
if d.activeDialogue != nil {
2020-04-19 18:21:08 +01:00
return d.activeDialogue.activeMouseables()
}
var out []mouseable
for _, widget := range d.widgets {
2020-04-19 18:21:08 +01:00
out = append(out, widget.activeMouseables()...)
}
return out
}
func (d *Driver) activePaintables() []paintable {
var out []paintable
for _, widget := range d.widgets {
2020-04-19 18:21:08 +01:00
out = append(out, widget.activePaintables()...)
}
if d.activeDialogue != nil {
2020-04-19 18:21:08 +01:00
out = append(out, d.activeDialogue.activePaintables()...)
}
return out
}
2020-04-19 18:21:08 +01:00
func (d *Driver) findWidget(locator string) *Widget {
toplevels := append(d.widgets, d.dialogues...)
for _, widget := range toplevels {
if w := widget.findWidget(locator); w != nil {
return w
}
}
return nil
}