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 }