mirror of
https://github.com/OpenDiablo2/OpenDiablo2
synced 2024-11-16 17:35:57 -05:00
523 lines
12 KiB
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
|
|
}
|