OpenDiablo2/d2core/d2gui/box.go

523 lines
12 KiB
Go

package d2gui
import (
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2util"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2ui"
)
const (
boxSpriteHeight = 15 - 5
boxSpriteWidth = 14 - 2
boxBorderSpriteLeftBorderOffset = 4
boxBorderSpriteRightBorderOffset = 7
boxBorderSpriteTopBorderSectionOffset = 5
minimumAllowedSectionSize = 14
sectionHeightPercentageOfBox = 0.12
boxBackgroundColor = 0x000000d0
)
const (
boxCornerTopLeft = iota
boxCornerTopRight
boxTopHorizontalEdge1
boxTopHorizontalEdge2
boxTopHorizontalEdge3
boxTopHorizontalEdge4
boxTopHorizontalEdge5
boxTopHorizontalEdge6
boxCornerBottomLeft
boxCornerBottomRight
boxSideEdge1
boxSideEdge2
boxSideEdge3
boxSideEdge4
boxSideEdge5
boxSideEdge6
boxBottomHorizontalEdge1
boxBottomHorizontalEdge2
boxBottomHorizontalEdge3
boxBottomHorizontalEdge4
boxBottomHorizontalEdge5
boxBottomHorizontalEdge6
)
// Box takes a content layout and wraps in
// a box
type Box struct {
renderer d2interface.Renderer
asset *d2asset.AssetManager
sprites []*d2ui.Sprite
uiManager *d2ui.UIManager
layout *Layout
contentLayout *Layout
Options []*LabelButton
sfc d2interface.Surface
x, y int
paddingX, paddingY int
width, height int
disableBorder bool
isOpen bool
title string
*d2util.Logger
}
// NewBox return a new Box instance
func NewBox(
asset *d2asset.AssetManager,
renderer d2interface.Renderer,
ui *d2ui.UIManager,
contentLayout *Layout,
width, height, x, y int,
l d2util.LogLevel,
title string,
) *Box {
box := &Box{
asset: asset,
renderer: renderer,
uiManager: ui,
width: width,
height: height,
contentLayout: contentLayout,
sfc: renderer.NewSurface(width, height),
title: title,
x: x,
y: y,
}
box.Logger = d2util.NewLogger()
box.Logger.SetLevel(l)
box.Logger.SetPrefix(logPrefix)
return box
}
// GetLayout returns the box layout
func (box *Box) GetLayout() *Layout {
return box.layout
}
// Toggle the visibility state of the menu
func (box *Box) Toggle() {
if box.isOpen {
box.Close()
} else {
box.Open()
}
}
// SetPadding sets the padding of the box content
func (box *Box) SetPadding(paddingX, paddingY int) {
box.paddingX = paddingX
box.paddingY = paddingY
}
// Open will set the isOpen value to true
func (box *Box) Open() {
box.isOpen = true
}
// Close will hide the help overlay
func (box *Box) Close() {
box.isOpen = false
}
// IsOpen returns whether or not the box is opened
func (box *Box) IsOpen() bool {
return box.isOpen
}
func (box *Box) setupTopBorder(offsetY int) {
topEdgePiece := []int{
boxTopHorizontalEdge1,
boxTopHorizontalEdge2,
boxTopHorizontalEdge3,
boxTopHorizontalEdge4,
boxTopHorizontalEdge5,
boxTopHorizontalEdge6,
}
i := 0
maxPieces := box.width / boxSpriteWidth
currentX, currentY := box.x, box.y+offsetY
for {
for _, frameIndex := range topEdgePiece {
f, err := box.uiManager.NewSprite(d2resource.BoxPieces, d2resource.PaletteSky)
if err != nil {
box.Error(err.Error())
}
err = f.SetCurrentFrame(frameIndex)
if err != nil {
box.Error(err.Error())
}
f.SetPosition(currentX, currentY)
currentX += boxSpriteWidth
box.sprites = append(box.sprites, f)
i++
if i >= maxPieces {
break
}
}
if i >= maxPieces {
break
}
}
}
func (box *Box) setupBottomBorder(offsetY int) {
bottomEdgePiece := []int{
boxBottomHorizontalEdge1,
boxBottomHorizontalEdge2,
boxBottomHorizontalEdge3,
boxBottomHorizontalEdge4,
boxBottomHorizontalEdge5,
boxBottomHorizontalEdge6,
}
i := 0
currentX, currentY := box.x, offsetY
maxPieces := box.width / boxSpriteWidth
for {
for _, frameIndex := range bottomEdgePiece {
f, err := box.uiManager.NewSprite(d2resource.BoxPieces, d2resource.PaletteSky)
if err != nil {
box.Error(err.Error())
}
err = f.SetCurrentFrame(frameIndex)
if err != nil {
box.Error(err.Error())
}
f.SetPosition(currentX, currentY)
currentX += boxSpriteWidth
box.sprites = append(box.sprites, f)
i++
if i >= maxPieces {
break
}
}
if i >= maxPieces {
break
}
}
}
func (box *Box) setupLeftBorder() {
leftBorderPiece := []int{
boxSideEdge1,
boxSideEdge2,
boxSideEdge3,
}
currentX, currentY := box.x-boxBorderSpriteLeftBorderOffset, box.y+boxSpriteHeight
maxPieces := box.height / boxSpriteHeight
i := 0
for {
for _, frameIndex := range leftBorderPiece {
f, err := box.uiManager.NewSprite(d2resource.BoxPieces, d2resource.PaletteSky)
if err != nil {
box.Error(err.Error())
}
err = f.SetCurrentFrame(frameIndex)
if err != nil {
box.Error(err.Error())
}
f.SetPosition(currentX, currentY)
currentY += boxSpriteHeight
box.sprites = append(box.sprites, f)
i++
if i >= maxPieces {
break
}
}
if i >= maxPieces {
break
}
}
}
func (box *Box) setupRightBorder() {
rightBorderPiece := []int{
boxSideEdge4,
boxSideEdge5,
boxSideEdge6,
}
i := 0
currentX, currentY := box.width+box.x-boxBorderSpriteRightBorderOffset, box.y+boxSpriteHeight
maxPieces := box.height / boxSpriteHeight
for {
for _, frameIndex := range rightBorderPiece {
f, err := box.uiManager.NewSprite(d2resource.BoxPieces, d2resource.PaletteSky)
if err != nil {
box.Error(err.Error())
}
err = f.SetCurrentFrame(frameIndex)
if err != nil {
box.Error(err.Error())
}
f.SetPosition(currentX, currentY)
currentY += boxSpriteHeight
box.sprites = append(box.sprites, f)
i++
if i >= maxPieces {
break
}
}
if i >= maxPieces {
break
}
}
}
func (box *Box) setupCorners() {
cornersFrames := []int{
boxCornerTopLeft,
boxCornerTopRight,
boxCornerBottomLeft,
boxCornerBottomRight,
}
for _, frameIndex := range cornersFrames {
f, err := box.uiManager.NewSprite(d2resource.BoxPieces, d2resource.PaletteSky)
if err != nil {
box.Error(err.Error())
}
err = f.SetCurrentFrame(frameIndex)
if err != nil {
box.Error(err.Error())
}
switch frameIndex {
case boxCornerTopLeft:
f.SetPosition(box.x, box.y+boxSpriteHeight)
case boxCornerTopRight:
f.SetPosition(box.x+box.width-boxSpriteWidth, box.y+boxSpriteHeight)
case boxCornerBottomLeft:
f.SetPosition(box.x, box.y+box.height)
case boxCornerBottomRight:
f.SetPosition(box.x+box.width-boxSpriteWidth, box.y+box.height)
}
box.sprites = append(box.sprites, f)
}
}
// SetOptions sets the box options that will show up at the bottom
func (box *Box) SetOptions(options []*LabelButton) {
box.Options = options
}
func (box *Box) setupTitle(sectionHeight int) error {
if !box.disableBorder {
cornerLeft, err := box.uiManager.NewSprite(d2resource.BoxPieces, d2resource.PaletteSky)
if err != nil {
return err
}
cornerRight, err := box.uiManager.NewSprite(d2resource.BoxPieces, d2resource.PaletteSky)
if err != nil {
return err
}
offsetY := box.y + sectionHeight
if err := cornerLeft.SetCurrentFrame(boxCornerBottomLeft); err != nil {
return err
}
cornerLeft.SetPosition(box.x, offsetY)
if err := cornerRight.SetCurrentFrame(boxCornerBottomRight); err != nil {
return err
}
cornerRight.SetPosition(box.x+box.width-boxSpriteWidth, offsetY)
box.sprites = append(box.sprites, cornerLeft, cornerRight)
box.setupBottomBorder(offsetY)
}
contentLayoutW, contentLayoutH := box.contentLayout.GetSize()
contentLayoutX, contentLayoutY := box.contentLayout.GetPosition()
box.contentLayout.SetSize(contentLayoutW, contentLayoutH-sectionHeight)
box.contentLayout.SetPosition(contentLayoutX, contentLayoutY+sectionHeight)
titleLayout := box.layout.AddLayout(PositionTypeHorizontal)
titleLayout.SetHorizontalAlign(HorizontalAlignCenter)
titleLayout.SetVerticalAlign(VerticalAlignMiddle)
titleLayout.SetPosition(box.x, box.y)
titleLayout.SetSize(contentLayoutW, sectionHeight)
titleLayout.AddSpacerDynamic()
if _, err := titleLayout.AddLabel(box.title, FontStyle30Units); err != nil {
return err
}
titleLayout.AddSpacerDynamic()
return nil
}
func (box *Box) setupOptions(sectionHeight int) error {
box.contentLayout.SetSize(box.width, box.height-sectionHeight)
if !box.disableBorder {
cornerLeft, err := box.uiManager.NewSprite(d2resource.BoxPieces, d2resource.PaletteSky)
if err != nil {
return err
}
cornerRight, err := box.uiManager.NewSprite(d2resource.BoxPieces, d2resource.PaletteSky)
if err != nil {
return err
}
offsetY := box.y + box.height - sectionHeight + boxSpriteHeight
if err := cornerLeft.SetCurrentFrame(boxCornerTopLeft); err != nil {
return err
}
cornerLeft.SetPosition(box.x, offsetY)
if err := cornerRight.SetCurrentFrame(boxCornerTopRight); err != nil {
return err
}
cornerRight.SetPosition(box.x+box.width-boxSpriteWidth, offsetY)
// nolint:gomnd // constant
box.setupTopBorder(box.height - (4 * boxSpriteHeight) + boxSpriteHeight - boxBorderSpriteTopBorderSectionOffset)
box.sprites = append(box.sprites, cornerLeft, cornerRight)
}
buttonsLayoutWrapper := box.layout.AddLayout(PositionTypeAbsolute)
buttonsLayoutWrapper.SetSize(box.width, sectionHeight)
buttonsLayoutWrapper.SetPosition(box.x, box.y+box.height-sectionHeight)
buttonsLayout := buttonsLayoutWrapper.AddLayout(PositionTypeHorizontal)
buttonsLayout.SetSize(buttonsLayoutWrapper.GetSize())
buttonsLayout.SetVerticalAlign(VerticalAlignMiddle)
buttonsLayout.AddSpacerDynamic()
for _, option := range box.Options {
option.Load(box.renderer, box.asset)
buttonsLayout.AddLayoutFromSource(option.GetLayout())
buttonsLayout.AddSpacerDynamic()
}
return nil
}
// Load will setup the layouts and sprites for the box deptending on the parameters
func (box *Box) Load() error {
box.layout = CreateLayout(box.renderer, PositionTypeAbsolute, box.asset)
box.layout.SetPosition(box.x, box.y)
box.layout.SetSize(box.width, box.height)
box.contentLayout.SetPosition(box.x, box.y)
if !box.disableBorder {
box.setupTopBorder(boxSpriteHeight)
box.setupBottomBorder(box.y + box.height + boxSpriteHeight)
box.setupLeftBorder()
box.setupRightBorder()
box.setupCorners()
}
sectionHeight := int(float32(box.height) * sectionHeightPercentageOfBox)
optionsEnabled := len(box.Options) > 0 && sectionHeight >= minimumAllowedSectionSize
if optionsEnabled {
if err := box.setupOptions(sectionHeight); err != nil {
return err
}
} else {
box.contentLayout.SetSize(box.width, box.height)
}
if box.title != "" {
if err := box.setupTitle(sectionHeight); err != nil {
return err
}
}
contentLayoutW, contentLayoutH := box.contentLayout.GetSize()
contentLayoutX, contentLayoutY := box.contentLayout.GetPosition()
box.contentLayout.SetPosition(contentLayoutX+box.paddingX, contentLayoutY+box.paddingY)
// nolint:gomnd // constant
box.contentLayout.SetSize(contentLayoutW-(2*box.paddingX), contentLayoutH-(2*box.paddingY))
box.layout.AddLayoutFromSource(box.contentLayout)
return nil
}
// OnMouseButtonDown will be called whenever a mouse button is triggered
func (box *Box) OnMouseButtonDown(event d2interface.MouseEvent) bool {
for _, option := range box.Options {
if option.IsInRect(event.X(), event.Y()) {
option.callback()
return true
}
}
return false
}
// Render the box to the given surface
func (box *Box) Render(target d2interface.Surface) error {
if !box.isOpen {
return nil
}
target.PushTranslation(box.x, box.y)
target.DrawRect(box.width, box.height, d2util.Color(boxBackgroundColor))
target.Pop()
for _, s := range box.sprites {
s.Render(target)
}
return nil
}
// IsInRect checks if the given point is within the box main layout rectangle
func (box *Box) IsInRect(px, py int) bool {
ww, hh := box.layout.GetSize()
x, y := box.layout.GetPosition()
if px >= x && px <= x+ww && py >= y && py <= y+hh {
return true
}
return false
}