Key binding menu (#918)

* Feat(KeyBindingMenu): Adds dynamic box system with scrollbar

* Feat(Hotkeys): WIP Adds a lot of things

* Feat(KeyBindingMenu): WIP Adds logic to binding

* Feat(KeyBindingMenu): Fixes assignment logic

* Feat(KeyBindingMenu): Adds buttons logic

* Feat(KeyBindingMenu): Fixes sprites positions+add padding to Box

* Feat(KeyBindingMenu): Adds label blinking cap

* Feat(KeyBindingMenu): Removes commented func

* Feat(KeyBindingMenu): Fixes lint errors and refactors a bit

* Feat(KeyBindingMenu): Corrects few minor things from Grave

* Feat(KeyBindingMenu): removes forgotten key to string mapping
This commit is contained in:
Julien Ganichot 2020-11-13 21:03:30 +01:00 committed by GitHub
parent 7a75dbc284
commit 0d691dbffa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 2391 additions and 138 deletions

View File

@ -26,6 +26,7 @@ const (
FadeAutomap // reduces the brightness of the map (not the players/npcs)
TogglePartyOnAutomap // toggles the display of the party members on the automap
ToggleNamesOnAutomap // toggles the display of party members names and npcs on the automap
ToggleMiniMap
// there can be 16 hotkeys, each hotkey can have a skill assigned
UseSkill1
@ -58,13 +59,25 @@ const (
UseBeltSlot4
SwapWeapons
ToggleChatBox
ToggleRunWalk
SayHelp
SayFollowMe
SayThisIsForYou
SayThanks
SaySorry
SayBye
SayNowYouDie
SayRetreat
// these events are fired while a player holds the corresponding key
HoldRun
HoldStandStill
HoldShowGroundItems
HoldShowPortraits
TakeScreenShot
ClearScreen // closes all active menus/panels
ClearMessages
)

View File

@ -3,28 +3,6 @@ package d2enum
// Key represents button on a traditional keyboard.
type Key int
// GetString returns a string representing the key
func (k Key) GetString() string {
switch k {
case -1:
return "None"
case KeyControl:
return "Ctrl"
case KeyShift:
return "Shift"
case KeySpace:
return "Space"
case KeyAlt:
return "Alt"
case KeyTab:
return "Tab"
case KeyH:
return "H"
default:
return "Unknown"
}
}
// Input keys
const (
Key0 Key = iota
@ -128,9 +106,14 @@ const (
KeyControl
KeyShift
KeyTilde
KeyMouse3
KeyMouse4
KeyMouse5
KeyMouseWheelUp
KeyMouseWheelDown
KeyMin = Key0
KeyMax = KeyShift
KeyMax = KeyMouseWheelDown
)
// KeyMod represents a "modified" key action. This could mean, for example, ctrl-S

View File

@ -13,6 +13,7 @@ type Animation interface {
Clone() Animation
SetSubLoop(startFrame, EndFrame int)
Advance(elapsed float64) error
GetCurrentFrameSurface() Surface
Render(target Surface)
RenderFromOrigin(target Surface, shadow bool)
RenderSection(sfc Surface, bound image.Rectangle)

View File

@ -111,6 +111,14 @@ const (
HelpYellowBullet = "/data/global/ui/MENU/helpyellowbullet.DC6"
HelpWhiteBullet = "/data/global/ui/MENU/helpwhitebullet.DC6"
// Box pieces, used in all in game boxes like npc interaction menu on click,
// the chat window and the key binding menu
BoxPieces = "/data/global/ui/MENU/boxpieces.DC6"
// TextSlider contains the pieces to build a scrollbar in the
// menus, such as the one in the configure keys menu
TextSlider = "/data/global/ui/MENU/textslid.DC6"
// Issue #685 - used in the mini-panel
GameSmallMenuButton = "/data/global/ui/PANEL/menubutton.DC6"
SkillIcon = "/data/global/ui/PANEL/Skillicon.DC6"
@ -152,6 +160,14 @@ const (
Checkbox = "/data/global/ui/FrontEnd/clickbox.dc6"
Scrollbar = "/data/global/ui/PANEL/scrollbar.dc6"
PopUpLarge = "/data/global/ui/FrontEnd/PopUpLarge.dc6"
PopUpLargest = "/data/global/ui/FrontEnd/PopUpLargest.dc6"
PopUpWide = "/data/global/ui/FrontEnd/PopUpWide.dc6"
PopUpOk = "/data/global/ui/FrontEnd/PopUpOk.dc6"
PopUpOk2 = "/data/global/ui/FrontEnd/PopUpOk.dc6"
PopUpOkCancel2 = "/data/global/ui/FrontEnd/PopUpOkCancel2.dc6"
PopUp340x224 = "/data/global/ui/FrontEnd/PopUp_340x224.dc6"
// --- GAME UI ---
PentSpin = "/data/global/ui/CURSOR/pentspin.DC6"

View File

@ -151,6 +151,12 @@ func (a *Animation) renderShadow(target d2interface.Surface) {
target.Render(frame.image)
}
// GetCurrentFrameSurface returns the surface for the current frame of the
// animation
func (a *Animation) GetCurrentFrameSurface() d2interface.Surface {
return a.directions[a.directionIndex].frames[a.frameIndex].image
}
// Render renders the animation to the given surface
func (a *Animation) Render(target d2interface.Surface) {
if a.renderer == nil {

513
d2core/d2gui/box.go Normal file
View File

@ -0,0 +1,513 @@
package d2gui
import (
"log"
"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
}
// NewBox return a new Box instance
func NewBox(
asset *d2asset.AssetManager,
renderer d2interface.Renderer,
ui *d2ui.UIManager,
contentLayout *Layout,
width, height int,
x, y int,
title string,
) *Box {
return &Box{
asset: asset,
renderer: renderer,
uiManager: ui,
width: width,
height: height,
contentLayout: contentLayout,
sfc: renderer.NewSurface(width, height),
title: title,
x: x,
y: y,
}
}
// 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 {
log.Print(err)
}
err = f.SetCurrentFrame(frameIndex)
if err != nil {
log.Print(err)
}
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 {
log.Print(err)
}
err = f.SetCurrentFrame(frameIndex)
if err != nil {
log.Print(err)
}
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 {
log.Print(err)
}
err = f.SetCurrentFrame(frameIndex)
if err != nil {
log.Print(err)
}
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 {
log.Print(err)
}
err = f.SetCurrentFrame(frameIndex)
if err != nil {
log.Print(err)
}
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 {
log.Print(err)
}
err = f.SetCurrentFrame(frameIndex)
if err != nil {
log.Print(err)
}
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)
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)
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
}

View File

@ -1,26 +1,47 @@
package d2gui
import (
"image/color"
"log"
"time"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset"
)
// Constants defining the main shades of basic colors
// found in the game
const (
ColorWhite = 0xffffffff
ColorRed = 0xdb3f3dff
ColorGreen = 0x00d000ff
ColorBlue = 0x5450d1ff
ColorBrown = 0xa1925dff
ColorGrey = 0x555555ff
)
// Label is renderable text
type Label struct {
widgetBase
renderer d2interface.Renderer
text string
font *d2asset.Font
surface d2interface.Surface
renderer d2interface.Renderer
text string
font *d2asset.Font
surface d2interface.Surface
color color.RGBA
hoverColor color.RGBA
isHovered bool
isBlinking bool
isDisplayed bool
blinkTimer time.Time
}
func createLabel(renderer d2interface.Renderer, text string, font *d2asset.Font) *Label {
func createLabel(renderer d2interface.Renderer, text string, font *d2asset.Font, col color.RGBA) *Label {
label := &Label{
font: font,
renderer: renderer,
font: font,
renderer: renderer,
color: col,
hoverColor: col,
}
err := label.setText(text)
@ -34,7 +55,33 @@ func createLabel(renderer d2interface.Renderer, text string, font *d2asset.Font)
return label
}
// SetHoverColor will set the value of hoverColor
func (l *Label) SetHoverColor(col color.RGBA) {
l.hoverColor = col
}
// SetIsBlinking will set the isBlinking value
func (l *Label) SetIsBlinking(isBlinking bool) {
l.isBlinking = isBlinking
}
// SetIsHovered will set the isHovered value
func (l *Label) SetIsHovered(isHovered bool) error {
l.isHovered = isHovered
return l.setText(l.text)
}
func (l *Label) render(target d2interface.Surface) {
if l.isBlinking && time.Since(l.blinkTimer) >= 200*time.Millisecond {
l.isDisplayed = !l.isDisplayed
l.blinkTimer = time.Now()
}
if l.isBlinking && !l.isDisplayed {
return
}
target.Render(l.surface)
}
@ -47,6 +94,12 @@ func (l *Label) GetText() string {
return l.text
}
// SetColor sets the label text
func (l *Label) SetColor(col color.RGBA) error {
l.color = col
return l.setText(l.text)
}
// SetText sets the label text
func (l *Label) SetText(text string) error {
if text == l.text {
@ -61,6 +114,13 @@ func (l *Label) setText(text string) error {
surface := l.renderer.NewSurface(width, height)
col := l.color
if l.isHovered {
col = l.hoverColor
}
l.font.SetColor(col)
if err := l.font.RenderText(text, surface); err != nil {
return err
}

View File

@ -0,0 +1,99 @@
package d2gui
import (
"image/color"
"log"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2util"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset"
)
// LabelButton is a label that can change when hovered and has
// a callback function that can be called when clicked
type LabelButton struct {
label string
callback func()
hoverColor color.RGBA
canHover bool
isHovered bool
layout *Layout
x, y int
}
// NewLabelButton generates a new instance of LabelButton
func NewLabelButton(x, y int, text string, col color.RGBA, callback func()) *LabelButton {
return &LabelButton{
x: x,
y: y,
hoverColor: col,
label: text,
callback: callback,
canHover: true,
}
}
// IsInRect checks if the given point is within the overlay layout rectangle
func (lb *LabelButton) IsInRect(px, py int) bool {
if lb.layout == nil {
return false
}
ww, hh := lb.layout.GetSize()
x, y := lb.layout.Sx, lb.layout.Sy
if px >= x && px <= x+ww && py >= y && py <= y+hh {
return true
}
return false
}
// Load sets the button handlers and sets the layouts
func (lb *LabelButton) Load(renderer d2interface.Renderer, asset *d2asset.AssetManager) {
mainLayout := CreateLayout(renderer, PositionTypeAbsolute, asset)
l, _ := mainLayout.AddLabelWithColor(lb.label, FontStyleFormal11Units, d2util.Color(ColorBrown))
if lb.canHover {
l.SetHoverColor(lb.hoverColor)
}
mainLayout.SetMouseEnterHandler(func(event d2interface.MouseMoveEvent) {
if err := l.SetIsHovered(true); err != nil {
log.Printf("could not change label to hover state: %v", err)
}
})
mainLayout.SetMouseLeaveHandler(func(event d2interface.MouseMoveEvent) {
if err := l.SetIsHovered(false); err != nil {
log.Printf("could not change label to hover state: %v", err)
}
})
lb.layout = mainLayout
}
// SetLabel sets the text of label label
func (lb *LabelButton) SetLabel(val string) {
lb.label = val
}
// SetHoverColor sets the hover color of the Label
func (lb *LabelButton) SetHoverColor(col color.RGBA) {
lb.hoverColor = col
}
// SetCanHover sets the value of canHover
func (lb *LabelButton) SetCanHover(val bool) {
lb.canHover = val
}
// IsHovered returns the value of isHovered
func (lb *LabelButton) IsHovered() bool {
return lb.isHovered
}
// GetLayout returns the laout of the label
func (lb *LabelButton) GetLayout() *Layout {
return lb.layout
}

View File

@ -2,9 +2,11 @@ package d2gui
import (
"errors"
"image/color"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2math"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2util"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset"
)
@ -105,6 +107,14 @@ func (l *Layout) AddLayout(positionType PositionType) *Layout {
return layout
}
// AddLayoutFromSource adds a nested layout to this layout, given a position type.
// Returns a pointer to the nested layout
func (l *Layout) AddLayoutFromSource(source *Layout) *Layout {
l.entries = append(l.entries, &layoutEntry{widget: source})
return source
}
// AddSpacerStatic adds a spacer with explicitly defined height and width
func (l *Layout) AddSpacerStatic(width, height int) *SpacerStatic {
spacer := createSpacerStatic(width, height)
@ -157,7 +167,21 @@ func (l *Layout) AddLabel(text string, fontStyle FontStyle) (*Label, error) {
return nil, err
}
label := createLabel(l.renderer, text, font)
label := createLabel(l.renderer, text, font, d2util.Color(ColorWhite))
l.entries = append(l.entries, &layoutEntry{widget: label})
return label, nil
}
// AddLabelWithColor given a string and a FontStyle and a Color, adds a text label as a layout entry
func (l *Layout) AddLabelWithColor(text string, fontStyle FontStyle, col color.RGBA) (*Label, error) {
font, err := l.loadFont(fontStyle)
if err != nil {
return nil, err
}
label := createLabel(l.renderer, text, font, col)
l.entries = append(l.entries, &layoutEntry{widget: label})
@ -276,6 +300,7 @@ func (l *Layout) getSize() (width, height int) {
func (l *Layout) onMouseButtonDown(event d2interface.MouseEvent) bool {
for _, entry := range l.entries {
if entry.IsIn(event) {
entry.widget.onMouseButtonClick(event)
entry.widget.onMouseButtonDown(event)
entry.mouseDown[event.Button()] = true
}

View File

@ -0,0 +1,385 @@
package d2gui
import (
"log"
"math"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2ui"
)
// LayoutScrollbar is a scrollbar that can be used with any layout
// and attaches to a main layout. You need to use a wrapper for your content
// as main layout in order for the scrollbar to work properly
type LayoutScrollbar struct {
sliderSprites []*d2ui.Sprite
gutterSprites []*d2ui.Sprite
parentLayout *Layout
targetLayout *Layout
sliderLayout *Layout
arrowUpLayout *Layout
arrowDownLayout *Layout
arrowUpSprite *d2ui.Sprite
arrowDownSprite *d2ui.Sprite
maxY int
minY int
arrowClickSliderOffset int
viewportSize int
contentSize int
clickedAtY int
mouseYOnSlider int
lastY int
gutterHeight int
sliderHeight int
contentToViewRatio float32
// isVisible bool
arrowUpClicked bool
arrowDownClicked bool
sliderClicked bool
}
const (
textSliderPartWidth = 12
textSliderPartHeight = 13
arrrowClickContentOffsetPercentage = 0.02
oneHundredPercent = 1.0
)
const (
textSliderPartArrowDownHollow int = iota + 8
textSliderPartArrowUpHollow
textSliderPartArrowDownFilled int = 10
textSliderPartArrowUpFilled int = 11
// textSliderPartSquare
textSliderPartInnerGutter int = 13
textSliderPartFillingVariation1 int = 14
)
// NewLayoutScrollbar attaches a scrollbar to the parentLayout to control the targetLayout
func NewLayoutScrollbar(
parentLayout *Layout,
targetLayout *Layout,
) *LayoutScrollbar {
parentW, parentH := parentLayout.GetSize()
_, targetH := targetLayout.GetSize()
gutterHeight := parentH - (2 * textSliderPartHeight)
viewportPercentage := oneHundredPercent - (float32(targetH-parentH) / float32(targetH))
sliderHeight := int(float32(gutterHeight) * viewportPercentage)
x, y := parentW-textSliderPartWidth, 0
ret := &LayoutScrollbar{
sliderSprites: []*d2ui.Sprite{},
gutterSprites: []*d2ui.Sprite{},
}
ret.contentToViewRatio = viewportPercentage
ret.contentToViewRatio = float32(targetH) / float32(gutterHeight)
ret.gutterHeight = gutterHeight
ret.sliderHeight = sliderHeight
ret.minY = y + textSliderPartHeight
ret.maxY = (y + parentH) - (textSliderPartHeight + sliderHeight)
ret.contentSize = targetH
ret.arrowClickSliderOffset = int(float32(sliderHeight) * arrrowClickContentOffsetPercentage)
ret.viewportSize = parentH
arrowUpLayout := parentLayout.AddLayout(PositionTypeAbsolute)
arrowUpLayout.SetSize(textSliderPartWidth, textSliderPartHeight)
arrowUpLayout.SetPosition(x, 0)
ret.arrowUpLayout = arrowUpLayout
gutterLayout := parentLayout.AddLayout(PositionTypeAbsolute)
gutterLayout.SetSize(textSliderPartWidth, gutterHeight)
gutterLayout.SetPosition(x, textSliderPartHeight)
sliderLayout := parentLayout.AddLayout(PositionTypeAbsolute)
sliderLayout.SetPosition(x, textSliderPartHeight)
sliderLayout.SetSize(textSliderPartWidth, sliderHeight)
sliderLayout.SetMouseClickHandler(ret.OnSliderMouseClick)
arrowDownLayout := parentLayout.AddLayout(PositionTypeAbsolute)
arrowDownLayout.SetSize(textSliderPartWidth, textSliderPartHeight)
arrowDownLayout.SetPosition(x, textSliderPartHeight+gutterHeight)
ret.arrowDownLayout = arrowDownLayout
ret.sliderLayout = sliderLayout
ret.parentLayout = parentLayout
ret.targetLayout = targetLayout
ret.parentLayout.AdjustEntryPlacement()
ret.targetLayout.AdjustEntryPlacement()
ret.sliderLayout.AdjustEntryPlacement()
return ret
}
// Load sets the scrollbar layouts and loads the sprites
func (scrollbar *LayoutScrollbar) Load(ui *d2ui.UIManager) error {
arrowUpX, arrowUpY := scrollbar.arrowUpLayout.ScreenPos()
arrowUpSprite, _ := ui.NewSprite(d2resource.TextSlider, d2resource.PaletteSky)
if err := arrowUpSprite.SetCurrentFrame(textSliderPartArrowUpFilled); err != nil {
return err
}
arrowUpSprite.SetPosition(arrowUpX, arrowUpY+textSliderPartHeight)
scrollbar.arrowUpSprite = arrowUpSprite
arrowDownX, arrowDownY := scrollbar.arrowDownLayout.ScreenPos()
arrowDownSprite, _ := ui.NewSprite(d2resource.TextSlider, d2resource.PaletteSky)
if err := arrowDownSprite.SetCurrentFrame(textSliderPartArrowDownFilled); err != nil {
return err
}
arrowDownSprite.SetPosition(arrowDownX, arrowDownY+textSliderPartHeight)
scrollbar.arrowDownSprite = arrowDownSprite
gutterParts := int(math.Ceil(float64(scrollbar.gutterHeight+(2*textSliderPartHeight)) / float64(textSliderPartHeight)))
sliderParts := int(math.Ceil(float64(scrollbar.sliderHeight) / float64(textSliderPartHeight)))
gutterX, gutterY := arrowUpX, arrowUpY+(2*textSliderPartHeight)-1
i := 0
for {
if i >= gutterParts {
break
}
f, _ := ui.NewSprite(d2resource.TextSlider, d2resource.PaletteSky)
if err := f.SetCurrentFrame(textSliderPartInnerGutter); err != nil {
return err
}
newY := gutterY + (i * (textSliderPartHeight - 1))
f.SetPosition(gutterX, newY)
scrollbar.gutterSprites = append(scrollbar.gutterSprites, f)
i++
}
i = 0
for {
if i >= sliderParts {
break
}
f, _ := ui.NewSprite(d2resource.TextSlider, d2resource.PaletteSky)
if err := f.SetCurrentFrame(textSliderPartFillingVariation1); err != nil {
return err
}
scrollbar.sliderSprites = append(scrollbar.sliderSprites, f)
i++
}
scrollbar.updateSliderSpritesPosition()
return nil
}
func (scrollbar *LayoutScrollbar) updateSliderSpritesPosition() {
scrollbar.sliderLayout.AdjustEntryPlacement()
sliderLayoutX, sliderLayoutY := scrollbar.sliderLayout.ScreenPos()
for i, s := range scrollbar.sliderSprites {
newY := sliderLayoutY + (i * (textSliderPartHeight - 1)) + textSliderPartHeight
s.SetPosition(sliderLayoutX-1, newY)
}
}
// OnSliderMouseClick affects the state of the slider
func (scrollbar *LayoutScrollbar) OnSliderMouseClick(event d2interface.MouseEvent) {
scrollbar.clickedAtY = event.Y()
scrollbar.lastY = scrollbar.clickedAtY
scrollbar.mouseYOnSlider = event.Y() - scrollbar.sliderLayout.Sy
}
func (scrollbar *LayoutScrollbar) moveScaledContentBy(offset int) int {
_, y := scrollbar.sliderLayout.GetPosition()
newY := y + offset
outOfBoundsUp := false
outOfBoundsDown := false
if newY > scrollbar.maxY {
newY = scrollbar.maxY
outOfBoundsDown = true
}
if newY < scrollbar.minY {
newY = scrollbar.minY
outOfBoundsUp = true
}
if !outOfBoundsUp && !outOfBoundsDown {
scrollbar.clickedAtY += offset
if scrollbar.targetLayout != nil {
contentX, contentY := scrollbar.targetLayout.GetPosition()
scaledOffset := int(math.Round(float64(float32(offset) * scrollbar.contentToViewRatio)))
newContentY := contentY - scaledOffset
scrollbar.targetLayout.SetPosition(contentX, newContentY)
}
}
if outOfBoundsDown && scrollbar.targetLayout != nil {
newContentY := -scrollbar.contentSize + scrollbar.viewportSize
scrollbar.targetLayout.SetPosition(0, newContentY)
}
if outOfBoundsUp && scrollbar.targetLayout != nil {
scrollbar.targetLayout.SetPosition(0, 0)
}
return newY
}
// OnMouseMove will affect the slider and the content depending on the state fof it
func (scrollbar *LayoutScrollbar) OnMouseMove(event d2interface.MouseMoveEvent) {
if !scrollbar.sliderClicked {
return
}
sliderX, _ := scrollbar.sliderLayout.GetPosition()
newY := scrollbar.moveScaledContentBy(event.Y() - scrollbar.clickedAtY)
scrollbar.sliderLayout.SetPosition(sliderX, newY)
scrollbar.updateSliderSpritesPosition()
}
// OnArrowUpClick will move the slider and the content up
func (scrollbar *LayoutScrollbar) OnArrowUpClick() {
sliderX, _ := scrollbar.sliderLayout.GetPosition()
newY := scrollbar.moveScaledContentBy(-scrollbar.arrowClickSliderOffset)
scrollbar.sliderLayout.SetPosition(sliderX, newY)
scrollbar.updateSliderSpritesPosition()
}
// OnArrowDownClick will move the slider and the content down
func (scrollbar *LayoutScrollbar) OnArrowDownClick() {
sliderX, _ := scrollbar.sliderLayout.GetPosition()
newY := scrollbar.moveScaledContentBy(scrollbar.arrowClickSliderOffset)
scrollbar.sliderLayout.SetPosition(sliderX, newY)
scrollbar.updateSliderSpritesPosition()
}
// SetSliderClicked sets the value of sliderClicked
func (scrollbar *LayoutScrollbar) SetSliderClicked(value bool) {
scrollbar.sliderClicked = value
}
// SetArrowUpClicked sets the value of sliderClicked
func (scrollbar *LayoutScrollbar) SetArrowUpClicked(value bool) {
var arrowSpriteFrame int
scrollbar.arrowUpClicked = value
if scrollbar.arrowUpClicked {
arrowSpriteFrame = textSliderPartArrowUpHollow
} else {
arrowSpriteFrame = textSliderPartArrowUpFilled
}
if err := scrollbar.arrowUpSprite.SetCurrentFrame(arrowSpriteFrame); err != nil {
log.Printf("unable to set arrow up sprite frame: %v", err)
}
}
// SetArrowDownClicked sets the value of sliderClicked
func (scrollbar *LayoutScrollbar) SetArrowDownClicked(value bool) {
var arrowSpriteFrame int
scrollbar.arrowDownClicked = value
if scrollbar.arrowDownClicked {
arrowSpriteFrame = textSliderPartArrowDownHollow
} else {
arrowSpriteFrame = textSliderPartArrowDownFilled
}
if err := scrollbar.arrowDownSprite.SetCurrentFrame(arrowSpriteFrame); err != nil {
log.Printf("unable to set arrow down sprite frame: %v", err)
}
}
// Advance updates the layouts according to the state of the arrown
func (scrollbar *LayoutScrollbar) Advance(elapsed float64) error {
if scrollbar.arrowDownClicked {
scrollbar.OnArrowDownClick()
}
if scrollbar.arrowUpClicked {
scrollbar.OnArrowUpClick()
}
return nil
}
// Render draws the scrollbar sprites on the given surface
func (scrollbar *LayoutScrollbar) Render(target d2interface.Surface) {
for _, s := range scrollbar.gutterSprites {
s.Render(target)
}
for _, s := range scrollbar.sliderSprites {
s.Render(target)
}
scrollbar.arrowUpSprite.Render(target)
scrollbar.arrowDownSprite.Render(target)
}
func (scrollbar *LayoutScrollbar) isInLayoutRect(layout *Layout, px, py int) bool {
ww, hh := layout.GetSize()
x, y := layout.Sx, layout.Sy
if px >= x && px <= x+ww && py >= y && py <= y+hh {
return true
}
return false
}
// IsSliderClicked returns the state of the slider
func (scrollbar *LayoutScrollbar) IsSliderClicked() bool {
return scrollbar.sliderClicked
}
// IsArrowUpClicked returns the state of arrow up clicked
func (scrollbar *LayoutScrollbar) IsArrowUpClicked() bool {
return scrollbar.arrowUpClicked
}
// IsArrowDownClicked returns the state of arrow down clicked
func (scrollbar *LayoutScrollbar) IsArrowDownClicked() bool {
return scrollbar.arrowDownClicked
}
// IsInArrowUpRect checks if the given point is within the overlay layout rectangle
func (scrollbar *LayoutScrollbar) IsInArrowUpRect(px, py int) bool {
return scrollbar.isInLayoutRect(scrollbar.arrowUpLayout, px, py)
}
// IsInArrowDownRect checks if the given point is within the overlay layout rectangle
func (scrollbar *LayoutScrollbar) IsInArrowDownRect(px, py int) bool {
return scrollbar.isInLayoutRect(scrollbar.arrowDownLayout, px, py)
}
// IsInSliderRect checks if the given point is within the overlay layout rectangle
func (scrollbar *LayoutScrollbar) IsInSliderRect(px, py int) bool {
return scrollbar.isInLayoutRect(scrollbar.sliderLayout, px, py)
}

View File

@ -11,6 +11,7 @@ import (
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2ui"
)
// static check that we implement our interface
@ -136,6 +137,20 @@ func (s *ebitenSurface) PopN(n int) {
}
}
func (s *ebitenSurface) RenderSprite(sprite *d2ui.Sprite) {
opts := s.createDrawImageOptions()
if s.stateCurrent.brightness != 1 || s.stateCurrent.saturation != 1 {
opts.ColorM.ChangeHSV(0, s.stateCurrent.saturation, s.stateCurrent.brightness)
}
s.handleStateEffect(opts)
opts.CompositeMode = ebiten.CompositeModeSourceOver
sprite.Render(s)
}
// Render renders the given surface
func (s *ebitenSurface) Render(sfc d2interface.Surface) {
opts := s.createDrawImageOptions()
@ -146,6 +161,8 @@ func (s *ebitenSurface) Render(sfc d2interface.Surface) {
s.handleStateEffect(opts)
opts.CompositeMode = ebiten.CompositeModeSourceOver
s.image.DrawImage(sfc.(*ebitenSurface).image, opts)
}

View File

@ -8,7 +8,6 @@ import (
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2util"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2gui"
)
// ButtonType defines the type of button
@ -271,7 +270,7 @@ func (ui *UIManager) NewButton(buttonType ButtonType, text string) *Button {
lbl.SetText(text)
lbl.Color[0] = d2util.Color(buttonLayout.LabelColor)
lbl.Alignment = d2gui.HorizontalAlignCenter
lbl.Alignment = HorizontalAlignCenter
buttonSprite, err := ui.NewSprite(buttonLayout.ResourceName, buttonLayout.PaletteName)
if err != nil {

View File

@ -15,6 +15,16 @@ const (
CursorButtonRight CursorButton = 2
)
// HorizontalAlign type, determines alignment along x-axis within a layout
type HorizontalAlign int
// Horizontal alignment types
const (
HorizontalAlignLeft HorizontalAlign = iota
HorizontalAlignCenter
HorizontalAlignRight
)
// NewUIManager creates a UIManager instance with the given input and audio provider
func NewUIManager(
asset *d2asset.AssetManager,

View File

@ -10,14 +10,13 @@ import (
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2util"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2gui"
)
// Label represents a user interface label
type Label struct {
*BaseWidget
text string
Alignment d2gui.HorizontalAlign
Alignment HorizontalAlign
font *d2asset.Font
Color map[int]color.Color
backgroundColor color.Color
@ -35,7 +34,7 @@ func (ui *UIManager) NewLabel(fontPath, palettePath string) *Label {
result := &Label{
BaseWidget: base,
Alignment: d2gui.HorizontalAlignLeft,
Alignment: HorizontalAlignLeft,
Color: map[int]color.Color{0: color.White},
font: font,
}
@ -150,11 +149,11 @@ func (v *Label) processColorTokens(str string) string {
func (v *Label) getAlignOffset(textWidth int) int {
switch v.Alignment {
case d2gui.HorizontalAlignLeft:
case HorizontalAlignLeft:
return 0
case d2gui.HorizontalAlignCenter:
case HorizontalAlignCenter:
return -textWidth / 2
case d2gui.HorizontalAlignRight:
case HorizontalAlignRight:
return -textWidth
default:
log.Fatal("Invalid Alignment")

View File

@ -47,6 +47,11 @@ func (s *Sprite) Render(target d2interface.Surface) {
s.animation.Render(target)
}
// GetSurface returns the surface of the sprite at the given frame
func (s *Sprite) GetSurface() d2interface.Surface {
return s.animation.GetCurrentFrameSurface()
}
// RenderSection renders the section of the sprite enclosed by bounds
func (s *Sprite) RenderSection(sfc d2interface.Surface, bound image.Rectangle) {
sfc.PushTranslation(s.x, s.y-bound.Dy())

View File

@ -5,7 +5,6 @@ import (
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2util"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2gui"
)
const (
@ -56,7 +55,7 @@ func (ui *UIManager) NewTooltip(font,
originX tooltipXOrigin,
originY tooltipYOrigin) *Tooltip {
label := ui.NewLabel(font, palette)
label.Alignment = d2gui.HorizontalAlignCenter
label.Alignment = HorizontalAlignCenter
base := NewBaseWidget(ui)

View File

@ -13,7 +13,6 @@ import (
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2gui"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapentity"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2screen"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2ui"
@ -216,14 +215,14 @@ func (v *CharacterSelect) loadHeroTitle() {
heroTitleX, heroTitleY := 320, 23
v.d2HeroTitle = v.uiManager.NewLabel(d2resource.Font42, d2resource.PaletteUnits)
v.d2HeroTitle.SetPosition(heroTitleX, heroTitleY)
v.d2HeroTitle.Alignment = d2gui.HorizontalAlignCenter
v.d2HeroTitle.Alignment = d2ui.HorizontalAlignCenter
}
func (v *CharacterSelect) loadDeleteCharConfirm() {
v.deleteCharConfirmLabel = v.uiManager.NewLabel(d2resource.Font16, d2resource.PaletteUnits)
lines := "Are you sure that you want\nto delete this character?\nTake note: this will delete all\nversions of this Character."
v.deleteCharConfirmLabel.SetText(lines)
v.deleteCharConfirmLabel.Alignment = d2gui.HorizontalAlignCenter
v.deleteCharConfirmLabel.Alignment = d2ui.HorizontalAlignCenter
deleteConfirmX, deleteConfirmY := 400, 185
v.deleteCharConfirmLabel.SetPosition(deleteConfirmX, deleteConfirmY)
}

View File

@ -7,7 +7,6 @@ import (
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2gui"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2screen"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2ui"
)
@ -90,7 +89,7 @@ func (v *Cinematics) OnLoad(_ d2screen.LoadingState) {
v.createButtons()
v.cinematicsLabel = v.uiManager.NewLabel(d2resource.Font30, d2resource.PaletteStatic)
v.cinematicsLabel.Alignment = d2gui.HorizontalAlignCenter
v.cinematicsLabel.Alignment = d2ui.HorizontalAlignCenter
v.cinematicsLabel.SetText("SELECT CINEMATIC")
v.cinematicsLabel.Color[0] = rgbaColor(lightBrown)
v.cinematicsLabel.SetPosition(cinematicsLabelX, cinematicsLabelY)

View File

@ -50,6 +50,7 @@ type Game struct {
soundEngine *d2audio.SoundEngine
soundEnv d2audio.SoundEnvironment
guiManager *d2gui.GuiManager
keyMap *d2player.KeyMap
renderer d2interface.Renderer
inputManager d2interface.InputManager
@ -83,6 +84,8 @@ func CreateGame(
break
}
keyMap := d2player.GetDefaultKeyMap(asset)
result := &Game{
asset: asset,
gameClient: gameClient,
@ -92,7 +95,7 @@ func CreateGame(
ticksSinceLevelCheck: 0,
mapRenderer: d2maprenderer.CreateMapRenderer(asset, renderer,
gameClient.MapEngine, term, startX, startY),
escapeMenu: d2player.NewEscapeMenu(navigator, renderer, audioProvider, guiManager, asset),
escapeMenu: d2player.NewEscapeMenu(navigator, renderer, audioProvider, ui, guiManager, asset, keyMap),
inputManager: inputManager,
audioProvider: audioProvider,
renderer: renderer,
@ -100,6 +103,7 @@ func CreateGame(
soundEngine: d2audio.NewSoundEngine(audioProvider, asset, term),
uiManager: ui,
guiManager: guiManager,
keyMap: keyMap,
}
result.soundEnv = d2audio.NewSoundEnvironment(result.soundEngine)
@ -290,7 +294,7 @@ func (v *Game) bindGameControls() error {
var err error
v.gameControls, err = d2player.NewGameControls(v.asset, v.renderer, player, v.gameClient.MapEngine,
v.escapeMenu, v.mapRenderer, v, v.terminal, v.uiManager, v.guiManager, v.gameClient.IsSinglePlayer())
v.escapeMenu, v.mapRenderer, v, v.terminal, v.uiManager, v.guiManager, v.keyMap, v.gameClient.IsSinglePlayer())
if err != nil {
return err

View File

@ -15,7 +15,6 @@ import (
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2gui"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2screen"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2ui"
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2client/d2clientconnectiontype"
@ -226,32 +225,32 @@ func (v *MainMenu) loadBackgroundSprites() {
func (v *MainMenu) createLabels(loading d2screen.LoadingState) {
v.versionLabel = v.uiManager.NewLabel(d2resource.FontFormal12, d2resource.PaletteStatic)
v.versionLabel.Alignment = d2gui.HorizontalAlignRight
v.versionLabel.Alignment = d2ui.HorizontalAlignRight
v.versionLabel.SetText("OpenDiablo2 - " + v.buildInfo.Branch)
v.versionLabel.Color[0] = rgbaColor(white)
v.versionLabel.SetPosition(versionLabelX, versionLabelY)
v.commitLabel = v.uiManager.NewLabel(d2resource.FontFormal10, d2resource.PaletteStatic)
v.commitLabel.Alignment = d2gui.HorizontalAlignLeft
v.commitLabel.Alignment = d2ui.HorizontalAlignLeft
v.commitLabel.SetText(v.buildInfo.Commit)
v.commitLabel.Color[0] = rgbaColor(white)
v.commitLabel.SetPosition(commitLabelX, commitLabelY)
v.copyrightLabel = v.uiManager.NewLabel(d2resource.FontFormal12, d2resource.PaletteStatic)
v.copyrightLabel.Alignment = d2gui.HorizontalAlignCenter
v.copyrightLabel.Alignment = d2ui.HorizontalAlignCenter
v.copyrightLabel.SetText("Diablo 2 is © Copyright 2000-2016 Blizzard Entertainment")
v.copyrightLabel.Color[0] = rgbaColor(lightBrown)
v.copyrightLabel.SetPosition(copyrightX, copyrightY)
loading.Progress(thirtyPercent)
v.copyrightLabel2 = v.uiManager.NewLabel(d2resource.FontFormal12, d2resource.PaletteStatic)
v.copyrightLabel2.Alignment = d2gui.HorizontalAlignCenter
v.copyrightLabel2.Alignment = d2ui.HorizontalAlignCenter
v.copyrightLabel2.SetText("All Rights Reserved.")
v.copyrightLabel2.Color[0] = rgbaColor(lightBrown)
v.copyrightLabel2.SetPosition(copyright2X, copyright2Y)
v.openDiabloLabel = v.uiManager.NewLabel(d2resource.FontFormal10, d2resource.PaletteStatic)
v.openDiabloLabel.Alignment = d2gui.HorizontalAlignCenter
v.openDiabloLabel.Alignment = d2ui.HorizontalAlignCenter
v.openDiabloLabel.SetText("OpenDiablo2 is neither developed by, nor endorsed by Blizzard or its parent company Activision")
v.openDiabloLabel.Color[0] = rgbaColor(lightYellow)
v.openDiabloLabel.SetPosition(od2LabelX, od2LabelY)
@ -259,24 +258,24 @@ func (v *MainMenu) createLabels(loading d2screen.LoadingState) {
v.tcpIPOptionsLabel = v.uiManager.NewLabel(d2resource.Font42, d2resource.PaletteUnits)
v.tcpIPOptionsLabel.SetPosition(tcpOptionsX, tcpOptionsY)
v.tcpIPOptionsLabel.Alignment = d2gui.HorizontalAlignCenter
v.tcpIPOptionsLabel.Alignment = d2ui.HorizontalAlignCenter
v.tcpIPOptionsLabel.SetText("TCP/IP Options")
v.tcpJoinGameLabel = v.uiManager.NewLabel(d2resource.Font16, d2resource.PaletteUnits)
v.tcpJoinGameLabel.Alignment = d2gui.HorizontalAlignCenter
v.tcpJoinGameLabel.Alignment = d2ui.HorizontalAlignCenter
v.tcpJoinGameLabel.SetText("Enter Host IP Address\nto Join Game")
v.tcpJoinGameLabel.Color[0] = rgbaColor(gold)
v.tcpJoinGameLabel.SetPosition(joinGameX, joinGameY)
v.machineIP = v.uiManager.NewLabel(d2resource.Font24, d2resource.PaletteUnits)
v.machineIP.Alignment = d2gui.HorizontalAlignCenter
v.machineIP.Alignment = d2ui.HorizontalAlignCenter
v.machineIP.SetText("Your IP address is:\n" + v.getLocalIP())
v.machineIP.Color[0] = rgbaColor(lightYellow)
v.machineIP.SetPosition(machineIPX, machineIPY)
if v.errorLabel != nil {
v.errorLabel.SetPosition(errorLabelX, errorLabelY)
v.errorLabel.Alignment = d2gui.HorizontalAlignCenter
v.errorLabel.Alignment = d2ui.HorizontalAlignCenter
v.errorLabel.Color[0] = rgbaColor(red)
}
}

View File

@ -14,7 +14,6 @@ import (
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2util"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2gui"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2screen"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2ui"
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2client/d2clientconnectiontype"
@ -412,36 +411,36 @@ func (v *SelectHeroClass) createLabels() {
v.headingLabel.SetPosition(headingX-halfFontWidth, headingY)
v.headingLabel.SetText("Select Hero Class")
v.headingLabel.Alignment = d2gui.HorizontalAlignCenter
v.headingLabel.Alignment = d2ui.HorizontalAlignCenter
v.heroClassLabel = v.uiManager.NewLabel(d2resource.Font30, d2resource.PaletteUnits)
v.heroClassLabel.Alignment = d2gui.HorizontalAlignCenter
v.heroClassLabel.Alignment = d2ui.HorizontalAlignCenter
v.heroClassLabel.SetPosition(heroClassLabelX, heroClassLabelY)
v.heroDesc1Label = v.uiManager.NewLabel(d2resource.Font16, d2resource.PaletteUnits)
v.heroDesc1Label.Alignment = d2gui.HorizontalAlignCenter
v.heroDesc1Label.Alignment = d2ui.HorizontalAlignCenter
v.heroDesc1Label.SetPosition(heroDescLine1X, heroDescLine1Y)
v.heroDesc2Label = v.uiManager.NewLabel(d2resource.Font16, d2resource.PaletteUnits)
v.heroDesc2Label.Alignment = d2gui.HorizontalAlignCenter
v.heroDesc2Label.Alignment = d2ui.HorizontalAlignCenter
v.heroDesc2Label.SetPosition(heroDescLine2X, heroDescLine2Y)
v.heroDesc3Label = v.uiManager.NewLabel(d2resource.Font16, d2resource.PaletteUnits)
v.heroDesc3Label.Alignment = d2gui.HorizontalAlignCenter
v.heroDesc3Label.Alignment = d2ui.HorizontalAlignCenter
v.heroDesc3Label.SetPosition(heroDescLine3X, heroDescLine3Y)
v.heroNameLabel = v.uiManager.NewLabel(d2resource.Font16, d2resource.PaletteUnits)
v.heroNameLabel.Alignment = d2gui.HorizontalAlignLeft
v.heroNameLabel.Alignment = d2ui.HorizontalAlignLeft
v.heroNameLabel.SetText(d2ui.ColorTokenize("Character Name", d2ui.ColorTokenGold))
v.heroNameLabel.SetPosition(heroNameLabelX, heroNameLabelY)
v.expansionCharLabel = v.uiManager.NewLabel(d2resource.Font16, d2resource.PaletteUnits)
v.expansionCharLabel.Alignment = d2gui.HorizontalAlignLeft
v.expansionCharLabel.Alignment = d2ui.HorizontalAlignLeft
v.expansionCharLabel.SetText(d2ui.ColorTokenize("EXPANSION CHARACTER", d2ui.ColorTokenGold))
v.expansionCharLabel.SetPosition(expansionLabelX, expansionLabelY)
v.hardcoreCharLabel = v.uiManager.NewLabel(d2resource.Font16, d2resource.PaletteUnits)
v.hardcoreCharLabel.Alignment = d2gui.HorizontalAlignLeft
v.hardcoreCharLabel.Alignment = d2ui.HorizontalAlignLeft
v.hardcoreCharLabel.SetText(d2ui.ColorTokenize("Hardcore", d2ui.ColorTokenGold))
v.hardcoreCharLabel.SetPosition(hardcoreLabelX, hardcoreLabelY)
}

View File

@ -0,0 +1,91 @@
package d2player
import (
"image/color"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2gui"
)
type bindingLayout struct {
wrapperLayout *d2gui.Layout
descLayout *d2gui.Layout
descLabel *d2gui.Label
primaryLayout *d2gui.Layout
primaryLabel *d2gui.Label
secondaryLayout *d2gui.Layout
secondaryLabel *d2gui.Label
binding *KeyBinding
gameEvent d2enum.GameEvent
}
func (l *bindingLayout) setTextAndColor(layout *d2gui.Label, text string, col color.RGBA) error {
if err := layout.SetText(text); err != nil {
return err
}
if err := layout.SetColor(col); err != nil {
return err
}
return nil
}
func (l *bindingLayout) SetPrimaryBindingTextAndColor(text string, col color.RGBA) error {
return l.setTextAndColor(l.primaryLabel, text, col)
}
func (l *bindingLayout) SetSecondaryBindingTextAndColor(text string, col color.RGBA) error {
return l.setTextAndColor(l.secondaryLabel, text, col)
}
func (l *bindingLayout) Reset() error {
if err := l.descLabel.SetIsHovered(false); err != nil {
return err
}
if err := l.primaryLabel.SetIsHovered(false); err != nil {
return err
}
if err := l.secondaryLabel.SetIsHovered(false); err != nil {
return err
}
l.primaryLabel.SetIsBlinking(false)
l.secondaryLabel.SetIsBlinking(false)
return nil
}
func (l *bindingLayout) isInLayoutRect(x, y int, targetLayout *d2gui.Layout) bool {
targetW, targetH := targetLayout.GetSize()
targetX, targetY := targetLayout.Sx, targetLayout.Sy
if x >= targetX && x <= targetX+targetW && y >= targetY && y <= targetY+targetH {
return true
}
return false
}
func (l *bindingLayout) GetPointedLayoutAndLabel(x, y int) (d2enum.GameEvent, KeyBindingType) {
if l.isInLayoutRect(x, y, l.descLayout) {
return l.gameEvent, KeyBindingTypePrimary
}
if l.primaryLayout != nil {
if l.isInLayoutRect(x, y, l.primaryLayout) {
return l.gameEvent, KeyBindingTypePrimary
}
}
if l.secondaryLayout != nil {
if l.isInLayoutRect(x, y, l.secondaryLayout) {
return l.gameEvent, KeyBindingTypeSecondary
}
}
return defaultGameEvent, KeyBindingTypeNone
}

View File

@ -10,6 +10,7 @@ import (
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2gui"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2ui"
)
type (
@ -70,22 +71,25 @@ type EscapeMenu struct {
// leftPent and rightPent are generated once and shared between the layouts
leftPent *d2gui.AnimatedSprite
rightPent *d2gui.AnimatedSprite
layouts []*layout
layouts map[layoutID]*layout
renderer d2interface.Renderer
audioProvider d2interface.AudioProvider
navigator d2interface.Navigator
guiManager *d2gui.GuiManager
assetManager *d2asset.AssetManager
renderer d2interface.Renderer
audioProvider d2interface.AudioProvider
navigator d2interface.Navigator
guiManager *d2gui.GuiManager
assetManager *d2asset.AssetManager
keyMap *KeyMap
keyBindingMenu *KeyBindingMenu
}
type layout struct {
*d2gui.Layout
leftPent *d2gui.AnimatedSprite
rightPent *d2gui.AnimatedSprite
actionableElements []actionableElement
currentEl int
rendered bool
actionableElements []actionableElement
isRaw bool
}
func (l *layout) Trigger() {
@ -134,8 +138,10 @@ type actionableElement interface {
func NewEscapeMenu(navigator d2interface.Navigator,
renderer d2interface.Renderer,
audioProvider d2interface.AudioProvider,
uiManager *d2ui.UIManager,
guiManager *d2gui.GuiManager,
assetManager *d2asset.AssetManager,
keyMap *KeyMap,
) *EscapeMenu {
m := &EscapeMenu{
audioProvider: audioProvider,
@ -143,16 +149,18 @@ func NewEscapeMenu(navigator d2interface.Navigator,
navigator: navigator,
guiManager: guiManager,
assetManager: assetManager,
keyMap: keyMap,
}
m.layouts = []*layout{
mainLayoutID: m.newMainLayout(),
optionsLayoutID: m.newOptionsLayout(),
soundOptionsLayoutID: m.newSoundOptionsLayout(),
videoOptionsLayoutID: m.newVideoOptionsLayout(),
automapOptionsLayoutID: m.newAutomapOptionsLayout(),
configureControlsLayoutID: m.newConfigureControlsLayout(),
}
keyBindingMenu := NewKeyBindingMenu(assetManager, renderer, uiManager, guiManager, keyMap, m)
m.keyBindingMenu = keyBindingMenu
m.layouts = make(map[layoutID]*layout)
m.layouts[mainLayoutID] = m.newMainLayout()
m.layouts[optionsLayoutID] = m.newOptionsLayout()
m.layouts[soundOptionsLayoutID] = m.newSoundOptionsLayout()
m.layouts[videoOptionsLayoutID] = m.newVideoOptionsLayout()
m.layouts[automapOptionsLayoutID] = m.newAutomapOptionsLayout()
return m
}
@ -213,11 +221,11 @@ func (m *EscapeMenu) newAutomapOptionsLayout() *layout {
})
}
func (m *EscapeMenu) newConfigureControlsLayout() *layout {
return m.wrapLayout(func(l *layout) {
m.addTitle(l, "CONFIGURE CONTROLS")
m.addPreviousMenuLabel(l)
})
func (m *EscapeMenu) newConfigureControlsLayout(keyBindingMenu *KeyBindingMenu) *layout {
return &layout{
Layout: keyBindingMenu.GetLayout(),
isRaw: true,
}
}
func (m *EscapeMenu) wrapLayout(fn func(*layout)) *layout {
@ -366,6 +374,13 @@ func (m *EscapeMenu) addEnumLabel(l *layout, optID optionID, text string, values
func (m *EscapeMenu) OnLoad() {
var err error
err = m.keyBindingMenu.Load()
if err != nil {
log.Printf("unable to load the configure controls window: %v", err)
}
m.layouts[configureControlsLayoutID] = m.newConfigureControlsLayout(m.keyBindingMenu)
m.selectSound, err = m.audioProvider.LoadSound(d2resource.SFXCursorSelect, false, false)
if err != nil {
log.Print(err)
@ -384,6 +399,11 @@ func (m *EscapeMenu) OnEscKey() {
automapOptionsLayoutID,
configureControlsLayoutID:
m.setLayout(optionsLayoutID)
if err := m.keyBindingMenu.Close(); err != nil {
log.Printf("unable to close the configure controls menu: %v", err)
}
return
}
@ -419,6 +439,12 @@ func (m *EscapeMenu) showLayout(id layoutID) {
}
m.setLayout(id)
if id == configureControlsLayoutID {
m.keyBindingMenu.Open()
} else if err := m.keyBindingMenu.Close(); err != nil {
fmt.Printf("unable to close the configure controls menu: %v", err)
}
}
func (m *EscapeMenu) onHoverElement(id int) {
@ -437,6 +463,16 @@ func (m *EscapeMenu) onUpdateValue(optID optionID, value string) {
}
func (m *EscapeMenu) setLayout(id layoutID) {
layout := m.layouts[id]
if layout.isRaw {
m.guiManager.SetLayout(layout.Layout)
m.layouts[id].rendered = true
m.currentLayout = id
return
}
m.leftPent = m.layouts[id].leftPent
m.rightPent = m.layouts[id].rightPent
m.currentLayout = id
@ -500,8 +536,79 @@ func (m *EscapeMenu) IsOpen() bool {
return m.isOpen
}
// Advance computes the state of the elements of the menu overtime
func (m *EscapeMenu) Advance(elapsed float64) error {
if m.keyBindingMenu != nil {
if err := m.keyBindingMenu.Advance(elapsed); err != nil {
return err
}
}
return nil
}
// Render will render the escape menu on the target surface
func (m *EscapeMenu) Render(target d2interface.Surface) error {
if m.isOpen {
if err := m.keyBindingMenu.Render(target); err != nil {
return err
}
}
return nil
}
// OnMouseButtonDown triggers whnever a mous button is pressed
func (m *EscapeMenu) OnMouseButtonDown(event d2interface.MouseEvent) bool {
if !m.isOpen {
return false
}
if m.currentLayout == configureControlsLayoutID {
if err := m.keyBindingMenu.onMouseButtonDown(event); err != nil {
log.Printf("unable to handle mouse down on configure controls menu: %v", err)
}
}
return false
}
// OnMouseButtonUp triggers whenever a mouse button is released
func (m *EscapeMenu) OnMouseButtonUp(event d2interface.MouseEvent) bool {
if !m.isOpen {
return false
}
if m.currentLayout == configureControlsLayoutID {
m.keyBindingMenu.onMouseButtonUp()
}
return false
}
// OnMouseMove triggers whenever the mouse moves within the renderer
func (m *EscapeMenu) OnMouseMove(event d2interface.MouseMoveEvent) bool {
if !m.isOpen {
return false
}
if m.currentLayout == configureControlsLayoutID {
m.keyBindingMenu.onMouseMove(event)
}
return false
}
// OnKeyDown defines the actions of the Escape Menu when a key is pressed
func (m *EscapeMenu) OnKeyDown(event d2interface.KeyEvent) bool {
if m.keyBindingMenu.IsOpen() {
if err := m.keyBindingMenu.OnKeyDown(event); err != nil {
log.Printf("unable to handle key down on configure controls menu: %v", err)
}
return false
}
switch event.Key() {
case d2enum.KeyUp:
m.onUpKey()

View File

@ -214,7 +214,7 @@ func NewGameControls(
term d2interface.Terminal,
ui *d2ui.UIManager,
guiManager *d2gui.GuiManager,
keyMap *KeyMap,
isSinglePlayer bool,
) (*GameControls, error) {
var inventoryRecordKey string
@ -350,7 +350,6 @@ func NewGameControls(
return nil, err
}
keyMap := getDefaultKeyMap()
helpOverlay := NewHelpOverlay(asset, renderer, ui, guiManager, keyMap)
hud := NewHUD(asset, ui, hero, helpOverlay, newMiniPanel(asset, ui, isSinglePlayer), actionableRegions, mapEngine, mapRenderer)
@ -360,7 +359,6 @@ func NewGameControls(
hoverLabel.SetBackgroundColor(d2util.Color(blackAlpha50percent))
gc := &GameControls{
keyMap: keyMap,
asset: asset,
ui: ui,
renderer: renderer,
@ -373,6 +371,7 @@ func NewGameControls(
skilltree: newSkillTree(hero.Skills, hero.Class, asset, ui),
heroStatsPanel: NewHeroStatsPanel(asset, ui, hero.Name(), hero.Class, hero.Stats),
HelpOverlay: helpOverlay,
keyMap: keyMap,
hud: hud,
bottomMenuRect: &d2geom.Rectangle{
Left: menuBottomRectX,
@ -639,6 +638,11 @@ func (g *GameControls) OnMouseMove(event d2interface.MouseMoveEvent) bool {
return false
}
// OnMouseButtonUp handles mouse button presses
func (g *GameControls) OnMouseButtonUp(event d2interface.MouseEvent) bool {
return false
}
// OnMouseButtonDown handles mouse button presses
func (g *GameControls) OnMouseButtonDown(event d2interface.MouseEvent) bool {
mx, my := event.X(), event.Y()
@ -698,6 +702,11 @@ func (g *GameControls) Load() {
// Advance advances the state of the GameControls
func (g *GameControls) Advance(elapsed float64) error {
g.mapRenderer.Advance(elapsed)
if err := g.escapeMenu.Advance(elapsed); err != nil {
return err
}
return nil
}
@ -766,6 +775,10 @@ func (g *GameControls) Render(target d2interface.Surface) error {
return err
}
if err := g.escapeMenu.Render(target); err != nil {
return err
}
return nil
}

View File

@ -339,25 +339,25 @@ func (h *HelpOverlay) setupBulletedList() {
// "Ctrl" should be hotkey // "Hold Down <%s> to Run"
{text: fmt.Sprintf(
h.asset.TranslateString("StrHelp2"),
h.keyMap.GetKeysForGameEvent(d2enum.HoldRun).Primary.GetString(),
h.keyMap.KeyToString(h.keyMap.GetKeysForGameEvent(d2enum.HoldRun).Primary),
)},
// "Alt" should be hotkey // "Hold down <%s> to highlight items on the ground"
{text: fmt.Sprintf(
h.asset.TranslateString("StrHelp3"),
h.keyMap.GetKeysForGameEvent(d2enum.HoldShowGroundItems).Primary.GetString(),
h.keyMap.KeyToString(h.keyMap.GetKeysForGameEvent(d2enum.HoldShowGroundItems).Primary),
)},
// "Shift" should be hotkey // "Hold down <%s> to attack while standing still"
{text: fmt.Sprintf(
h.asset.TranslateString("StrHelp4"),
h.keyMap.GetKeysForGameEvent(d2enum.HoldStandStill).Primary.GetString(),
h.keyMap.KeyToString(h.keyMap.GetKeysForGameEvent(d2enum.HoldStandStill).Primary),
)},
// "Tab" should be hotkey // "Hit <%s> to toggle the automap on and off"
{text: fmt.Sprintf(
h.asset.TranslateString("StrHelp5"),
h.keyMap.GetKeysForGameEvent(d2enum.ToggleAutomap).Primary.GetString(),
h.keyMap.KeyToString(h.keyMap.GetKeysForGameEvent(d2enum.ToggleAutomap).Primary),
)},
// "Hit <Esc> to bring up the Game Menu"
@ -372,7 +372,7 @@ func (h *HelpOverlay) setupBulletedList() {
// "H" should be hotkey,
{text: fmt.Sprintf(
h.asset.TranslateString("StrHelp8a"),
h.keyMap.GetKeysForGameEvent(d2enum.ToggleHelpScreen).Primary.GetString(),
h.keyMap.KeyToString(h.keyMap.GetKeysForGameEvent(d2enum.ToggleHelpScreen).Primary),
)},
}
@ -576,7 +576,7 @@ func (h *HelpOverlay) createLabel(c callout) {
newLabel.SetText(c.LabelText)
newLabel.SetPosition(c.LabelX, c.LabelY)
h.text = append(h.text, newLabel)
newLabel.Alignment = d2gui.HorizontalAlignCenter
newLabel.Alignment = d2ui.HorizontalAlignCenter
}
func (h *HelpOverlay) createCallout(c callout) {
@ -584,7 +584,7 @@ func (h *HelpOverlay) createCallout(c callout) {
newLabel.Color[0] = color.White
newLabel.SetText(c.LabelText)
newLabel.SetPosition(c.LabelX, c.LabelY)
newLabel.Alignment = d2gui.HorizontalAlignCenter
newLabel.Alignment = d2ui.HorizontalAlignCenter
ww, hh := newLabel.GetTextMetrics(c.LabelText)
h.text = append(h.text, newLabel)
_ = ww

View File

@ -8,7 +8,6 @@ import (
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2gui"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2hero"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2ui"
)
@ -357,7 +356,7 @@ func (s *HeroStatsPanel) createStatValueLabel(stat, x, y int) *d2ui.Label {
func (s *HeroStatsPanel) createTextLabel(element PanelText) *d2ui.Label {
label := s.uiManager.NewLabel(element.Font, d2resource.PaletteStatic)
if element.AlignCenter {
label.Alignment = d2gui.HorizontalAlignCenter
label.Alignment = d2ui.HorizontalAlignCenter
}
label.SetText(element.Text)

View File

@ -12,7 +12,6 @@ import (
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2util"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2gui"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapengine"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapentity"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2maprenderer"
@ -135,11 +134,11 @@ func NewHUD(
mapRenderer *d2maprenderer.MapRenderer,
) *HUD {
nameLabel := ui.NewLabel(d2resource.Font16, d2resource.PaletteStatic)
nameLabel.Alignment = d2gui.HorizontalAlignCenter
nameLabel.Alignment = d2ui.HorizontalAlignCenter
nameLabel.SetText(d2ui.ColorTokenize("", d2ui.ColorTokenServer))
zoneLabel := ui.NewLabel(d2resource.Font30, d2resource.PaletteUnits)
zoneLabel.Alignment = d2gui.HorizontalAlignCenter
zoneLabel.Alignment = d2ui.HorizontalAlignCenter
return &HUD{
asset: asset,

View File

@ -0,0 +1,733 @@
package d2player
import (
"image/color"
"log"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2util"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2gui"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2ui"
)
const (
selectionBackgroundColor = 0x000000d0
defaultGameEvent = -1
keyBindingMenuWidth = 620
keyBindingMenuHeight = 375
keyBindingMenuX = 90
keyBindingMenuY = 75
keyBindingMenuPaddingX = 17
keyBindingSettingPaddingY = 19
keyBindingMenuHeaderHeight = 24
keyBindingMenuHeaderSpacer1 = 131
keyBindingMenuHeaderSpacer2 = 86
keyBindingMenuBindingSpacerBetween = 25
keyBindingMenuBindingSpacerLeft = 17
keyBindingMenuBindingDescWidth = 190
keyBindingMenuBindingDescHeight = 0
keyBindingMenuBindingPrimaryWidth = 190
keyBindingMenuBindingPrimaryHeight = 0
keyBindingMenuBindingSecondaryWidth = 90
keyBindingMenuBindingSecondaryHeight = 0
)
type bindingChange struct {
target *KeyBinding
primary d2enum.Key
secondary d2enum.Key
}
// KeyBindingMenu represents the menu to view/edit the
// key bindings
type KeyBindingMenu struct {
*d2gui.Box
asset *d2asset.AssetManager
renderer d2interface.Renderer
ui *d2ui.UIManager
guiManager *d2gui.GuiManager
keyMap *KeyMap
escapeMenu *EscapeMenu
mainLayout *d2gui.Layout
contentLayout *d2gui.Layout
scrollbar *d2gui.LayoutScrollbar
bindingLayouts []*bindingLayout
changesToBeSaved map[d2enum.GameEvent]*bindingChange
isAwaitingKeyDown bool
currentBindingModifierType KeyBindingType
currentBindingModifier d2enum.GameEvent
currentBindingLayout *bindingLayout
lastBindingLayout *bindingLayout
}
// NewKeyBindingMenu generates a new instance of the "Configure Keys"
// menu found in the options
func NewKeyBindingMenu(
asset *d2asset.AssetManager,
renderer d2interface.Renderer,
ui *d2ui.UIManager,
guiManager *d2gui.GuiManager,
keyMap *KeyMap,
escapeMenu *EscapeMenu,
) *KeyBindingMenu {
mainLayout := d2gui.CreateLayout(renderer, d2gui.PositionTypeAbsolute, asset)
contentLayout := mainLayout.AddLayout(d2gui.PositionTypeAbsolute)
ret := &KeyBindingMenu{
keyMap: keyMap,
asset: asset,
ui: ui,
guiManager: guiManager,
renderer: renderer,
mainLayout: mainLayout,
contentLayout: contentLayout,
bindingLayouts: []*bindingLayout{},
changesToBeSaved: make(map[d2enum.GameEvent]*bindingChange),
escapeMenu: escapeMenu,
}
ret.Box = d2gui.NewBox(
asset, renderer, ui, ret.mainLayout,
keyBindingMenuWidth, keyBindingMenuHeight,
keyBindingMenuX, keyBindingMenuY, "",
)
ret.Box.SetPadding(keyBindingMenuPaddingX, keyBindingSettingPaddingY)
ret.Box.SetOptions([]*d2gui.LabelButton{
d2gui.NewLabelButton(0, 0, "Cancel", d2util.Color(d2gui.ColorRed), func() {
if err := ret.onCancelClicked(); err != nil {
log.Printf("error while clicking option Cancel: %v", err)
}
}),
d2gui.NewLabelButton(0, 0, "Default", d2util.Color(d2gui.ColorBlue), func() {
if err := ret.onDefaultClicked(); err != nil {
log.Printf("error while clicking option Default: %v", err)
}
}),
d2gui.NewLabelButton(0, 0, "Accept", d2util.Color(d2gui.ColorGreen), func() {
if err := ret.onAcceptClicked(); err != nil {
log.Printf("error while clicking option Accept: %v", err)
}
}),
})
return ret
}
// Close will disable the render of the menu and clear
// the current selection
func (menu *KeyBindingMenu) Close() error {
menu.Box.Close()
if err := menu.clearSelection(); err != nil {
return err
}
return nil
}
// Load will setup the layouts of the menu
func (menu *KeyBindingMenu) Load() error {
if err := menu.Box.Load(); err != nil {
return err
}
mainLayoutW, mainLayoutH := menu.mainLayout.GetSize()
headerLayout := menu.contentLayout.AddLayout(d2gui.PositionTypeHorizontal)
headerLayout.SetSize(mainLayoutW, keyBindingMenuHeaderHeight)
if _, err := headerLayout.AddLabelWithColor(
menu.asset.TranslateString("CfgFunction"),
d2gui.FontStyleFormal11Units,
d2util.Color(d2gui.ColorBrown),
); err != nil {
return err
}
headerLayout.AddSpacerStatic(keyBindingMenuHeaderSpacer1, keyBindingMenuHeaderHeight)
if _, err := headerLayout.AddLabelWithColor(
menu.asset.TranslateString("CfgPrimaryKey"),
d2gui.FontStyleFormal11Units,
d2util.Color(d2gui.ColorBrown),
); err != nil {
return err
}
headerLayout.AddSpacerStatic(keyBindingMenuHeaderSpacer2, 1)
if _, err := headerLayout.AddLabelWithColor(
menu.asset.TranslateString("CfgSecondaryKey"),
d2gui.FontStyleFormal11Units,
d2util.Color(d2gui.ColorBrown),
); err != nil {
return err
}
headerLayout.SetVerticalAlign(d2gui.VerticalAlignMiddle)
bindingWrapper := menu.contentLayout.AddLayout(d2gui.PositionTypeAbsolute)
bindingWrapper.SetPosition(0, keyBindingMenuHeaderHeight)
bindingWrapper.SetSize(mainLayoutW, mainLayoutH-keyBindingMenuHeaderHeight)
bindingLayout := menu.generateLayout()
menu.Box.GetLayout().AdjustEntryPlacement()
menu.mainLayout.AdjustEntryPlacement()
menu.contentLayout.AdjustEntryPlacement()
menu.scrollbar = d2gui.NewLayoutScrollbar(bindingWrapper, bindingLayout)
if err := menu.scrollbar.Load(menu.ui); err != nil {
return err
}
bindingWrapper.AddLayoutFromSource(bindingLayout)
bindingWrapper.AdjustEntryPlacement()
return nil
}
type keyBindingSetting struct {
label string
gameEvent d2enum.GameEvent
}
func (menu *KeyBindingMenu) getBindingGroups() [][]keyBindingSetting {
return [][]keyBindingSetting{
{
{menu.asset.TranslateString("CfgCharacter"), d2enum.ToggleCharacterPanel},
{menu.asset.TranslateString("CfgInventory"), d2enum.ToggleInventoryPanel},
{menu.asset.TranslateString("CfgParty"), d2enum.TogglePartyPanel},
{menu.asset.TranslateString("Cfghireling"), d2enum.ToggleHirelingPanel},
{menu.asset.TranslateString("CfgMessageLog"), d2enum.ToggleMessageLog},
{menu.asset.TranslateString("CfgQuestLog"), d2enum.ToggleQuestLog},
{menu.asset.TranslateString("CfgHelp"), d2enum.ToggleHelpScreen},
},
{
{menu.asset.TranslateString("CfgSkillTree"), d2enum.ToggleSkillTreePanel},
{menu.asset.TranslateString("CfgSkillPick"), d2enum.ToggleRightSkillSelector},
{menu.asset.TranslateString("CfgSkill1"), d2enum.UseSkill1},
{menu.asset.TranslateString("CfgSkill2"), d2enum.UseSkill2},
{menu.asset.TranslateString("CfgSkill3"), d2enum.UseSkill3},
{menu.asset.TranslateString("CfgSkill4"), d2enum.UseSkill4},
{menu.asset.TranslateString("CfgSkill5"), d2enum.UseSkill5},
{menu.asset.TranslateString("CfgSkill6"), d2enum.UseSkill6},
{menu.asset.TranslateString("CfgSkill7"), d2enum.UseSkill7},
{menu.asset.TranslateString("CfgSkill8"), d2enum.UseSkill8},
{menu.asset.TranslateString("CfgSkill9"), d2enum.UseSkill9},
{menu.asset.TranslateString("CfgSkill10"), d2enum.UseSkill10},
{menu.asset.TranslateString("CfgSkill11"), d2enum.UseSkill11},
{menu.asset.TranslateString("CfgSkill12"), d2enum.UseSkill12},
{menu.asset.TranslateString("CfgSkill13"), d2enum.UseSkill13},
{menu.asset.TranslateString("CfgSkill14"), d2enum.UseSkill14},
{menu.asset.TranslateString("CfgSkill15"), d2enum.UseSkill15},
{menu.asset.TranslateString("CfgSkill16"), d2enum.UseSkill16},
{menu.asset.TranslateString("Cfgskillup"), d2enum.SelectPreviousSkill},
{menu.asset.TranslateString("Cfgskilldown"), d2enum.SelectNextSkill},
},
{
{menu.asset.TranslateString("CfgBeltShow"), d2enum.ToggleBelts},
{menu.asset.TranslateString("CfgBelt1"), d2enum.UseBeltSlot1},
{menu.asset.TranslateString("CfgBelt2"), d2enum.UseBeltSlot2},
{menu.asset.TranslateString("CfgBelt3"), d2enum.UseBeltSlot3},
{menu.asset.TranslateString("CfgBelt4"), d2enum.UseBeltSlot4},
{menu.asset.TranslateString("Cfgswapweapons"), d2enum.SwapWeapons},
},
{
{menu.asset.TranslateString("CfgChat"), d2enum.ToggleChatBox},
{menu.asset.TranslateString("CfgRun"), d2enum.HoldRun},
{menu.asset.TranslateString("CfgRunLock"), d2enum.ToggleRunWalk},
{menu.asset.TranslateString("CfgStandStill"), d2enum.HoldStandStill},
{menu.asset.TranslateString("CfgShowItems"), d2enum.HoldShowGroundItems},
{menu.asset.TranslateString("CfgTogglePortraits"), d2enum.HoldShowPortraits},
},
{
{menu.asset.TranslateString("CfgAutoMap"), d2enum.ToggleAutomap},
{menu.asset.TranslateString("CfgAutoMapCenter"), d2enum.CenterAutomap},
{menu.asset.TranslateString("CfgAutoMapParty"), d2enum.TogglePartyOnAutomap},
{menu.asset.TranslateString("CfgAutoMapNames"), d2enum.ToggleNamesOnAutomap},
{menu.asset.TranslateString("CfgToggleminimap"), d2enum.ToggleMiniMap},
},
{
{menu.asset.TranslateString("CfgSay0"), d2enum.SayHelp},
{menu.asset.TranslateString("CfgSay1"), d2enum.SayFollowMe},
{menu.asset.TranslateString("CfgSay2"), d2enum.SayThisIsForYou},
{menu.asset.TranslateString("CfgSay3"), d2enum.SayThanks},
{menu.asset.TranslateString("CfgSay4"), d2enum.SaySorry},
{menu.asset.TranslateString("CfgSay5"), d2enum.SayBye},
{menu.asset.TranslateString("CfgSay6"), d2enum.SayNowYouDie},
{menu.asset.TranslateString("CfgSay7"), d2enum.SayNowYouDie},
},
{
{menu.asset.TranslateString("CfgSnapshot"), d2enum.TakeScreenShot},
{menu.asset.TranslateString("CfgClearScreen"), d2enum.ClearScreen},
{menu.asset.TranslateString("Cfgcleartextmsg"), d2enum.ClearMessages},
},
}
}
func (menu *KeyBindingMenu) generateLayout() *d2gui.Layout {
groups := menu.getBindingGroups()
wrapper := d2gui.CreateLayout(menu.renderer, d2gui.PositionTypeAbsolute, menu.asset)
layout := wrapper.AddLayout(d2gui.PositionTypeVertical)
for i, settingsGroup := range groups {
groupLayout := layout.AddLayout(d2gui.PositionTypeVertical)
for _, setting := range settingsGroup {
bl := bindingLayout{}
settingLayout := groupLayout.AddLayout(d2gui.PositionTypeHorizontal)
settingLayout.AddSpacerStatic(keyBindingMenuBindingSpacerLeft, 0)
descLabelWrapper := settingLayout.AddLayout(d2gui.PositionTypeAbsolute)
descLabelWrapper.SetSize(keyBindingMenuBindingDescWidth, keyBindingMenuBindingDescHeight)
descLabel, _ := descLabelWrapper.AddLabel(setting.label, d2gui.FontStyleFormal11Units)
descLabel.SetHoverColor(d2util.Color(d2gui.ColorBlue))
bl.wrapperLayout = settingLayout
bl.descLabel = descLabel
bl.descLayout = descLabelWrapper
if binding := menu.keyMap.GetKeysForGameEvent(setting.gameEvent); binding != nil {
primaryStr := menu.keyMap.KeyToString(binding.Primary)
secondaryStr := menu.keyMap.KeyToString(binding.Secondary)
primaryCol := menu.getKeyColor(binding.Primary)
secondaryCol := menu.getKeyColor(binding.Secondary)
if binding.IsEmpty() {
primaryCol = d2util.Color(d2gui.ColorRed)
secondaryCol = d2util.Color(d2gui.ColorRed)
}
primaryKeyLabelWrapper := settingLayout.AddLayout(d2gui.PositionTypeAbsolute)
primaryKeyLabelWrapper.SetSize(keyBindingMenuBindingPrimaryWidth, keyBindingMenuBindingPrimaryHeight)
primaryLabel, _ := primaryKeyLabelWrapper.AddLabelWithColor(primaryStr, d2gui.FontStyleFormal11Units, primaryCol)
primaryLabel.SetHoverColor(d2util.Color(d2gui.ColorBlue))
bl.primaryLabel = primaryLabel
bl.primaryLayout = primaryKeyLabelWrapper
bl.gameEvent = setting.gameEvent
secondaryKeyLabelWrapper := settingLayout.AddLayout(d2gui.PositionTypeAbsolute)
secondaryKeyLabelWrapper.SetSize(keyBindingMenuBindingSecondaryWidth, keyBindingMenuBindingSecondaryHeight)
secondaryLabel, _ := secondaryKeyLabelWrapper.AddLabelWithColor(secondaryStr, d2gui.FontStyleFormal11Units, secondaryCol)
secondaryLabel.SetHoverColor(d2util.Color(d2gui.ColorBlue))
bl.secondaryLabel = secondaryLabel
bl.secondaryLayout = secondaryKeyLabelWrapper
bl.binding = binding
}
menu.bindingLayouts = append(menu.bindingLayouts, &bl)
}
if i < len(groups)-1 {
layout.AddSpacerStatic(0, keyBindingMenuBindingSpacerBetween)
}
}
return wrapper
}
func (menu *KeyBindingMenu) getKeyColor(key d2enum.Key) color.RGBA {
switch key {
case -1:
return d2util.Color(d2gui.ColorGrey)
default:
return d2util.Color(d2gui.ColorBrown)
}
}
func (menu *KeyBindingMenu) setSelection(bl *bindingLayout, bindingType KeyBindingType, gameEvent d2enum.GameEvent) error {
if menu.currentBindingLayout != nil {
menu.lastBindingLayout = menu.currentBindingLayout
if err := menu.currentBindingLayout.Reset(); err != nil {
return err
}
}
menu.currentBindingModifier = gameEvent
menu.currentBindingLayout = bl
if bindingType == KeyBindingTypePrimary {
menu.currentBindingLayout.primaryLabel.SetIsBlinking(true)
} else if bindingType == KeyBindingTypeSecondary {
menu.currentBindingLayout.secondaryLabel.SetIsBlinking(true)
}
menu.currentBindingModifierType = bindingType
menu.isAwaitingKeyDown = true
if err := bl.descLabel.SetIsHovered(true); err != nil {
return err
}
if err := bl.primaryLabel.SetIsHovered(true); err != nil {
return err
}
if err := bl.secondaryLabel.SetIsHovered(true); err != nil {
return err
}
return nil
}
func (menu *KeyBindingMenu) onMouseButtonDown(event d2interface.MouseEvent) error {
if !menu.IsOpen() {
return nil
}
menu.Box.OnMouseButtonDown(event)
if menu.scrollbar != nil {
if menu.scrollbar.IsInSliderRect(event.X(), event.Y()) {
menu.scrollbar.SetSliderClicked(true)
menu.scrollbar.OnSliderMouseClick(event)
return nil
}
if menu.scrollbar.IsInArrowUpRect(event.X(), event.Y()) {
if !menu.scrollbar.IsArrowUpClicked() {
menu.scrollbar.SetArrowUpClicked(true)
}
menu.scrollbar.OnArrowUpClick()
return nil
}
if menu.scrollbar.IsInArrowDownRect(event.X(), event.Y()) {
if !menu.scrollbar.IsArrowDownClicked() {
menu.scrollbar.SetArrowDownClicked(true)
}
menu.scrollbar.OnArrowDownClick()
return nil
}
}
for _, bl := range menu.bindingLayouts {
gameEvent, typ := bl.GetPointedLayoutAndLabel(event.X(), event.Y())
if gameEvent != -1 {
if err := menu.setSelection(bl, typ, gameEvent); err != nil {
return err
}
break
} else if menu.currentBindingLayout != nil {
if err := menu.clearSelection(); err != nil {
return err
}
}
}
return nil
}
func (menu *KeyBindingMenu) onMouseMove(event d2interface.MouseMoveEvent) {
if !menu.IsOpen() {
return
}
if menu.scrollbar != nil && menu.scrollbar.IsSliderClicked() {
menu.scrollbar.OnMouseMove(event)
}
}
func (menu *KeyBindingMenu) onMouseButtonUp() {
if !menu.IsOpen() {
return
}
if menu.scrollbar != nil {
menu.scrollbar.SetSliderClicked(false)
menu.scrollbar.SetArrowDownClicked(false)
menu.scrollbar.SetArrowUpClicked(false)
}
}
func (menu *KeyBindingMenu) getPendingChangeByKey(key d2enum.Key) (*bindingChange, *KeyBinding, d2enum.GameEvent, KeyBindingType) {
var (
existingBinding *KeyBinding
gameEvent d2enum.GameEvent
bindingType KeyBindingType
)
for ge, existingChange := range menu.changesToBeSaved {
if existingChange.primary == key {
bindingType = KeyBindingTypePrimary
} else if existingChange.secondary == key {
bindingType = KeyBindingTypeSecondary
}
if bindingType != -1 {
existingBinding = existingChange.target
gameEvent = ge
return existingChange, existingBinding, gameEvent, bindingType
}
}
return nil, nil, -1, KeyBindingTypeNone
}
func (menu *KeyBindingMenu) saveKeyChange(key d2enum.Key) error {
changeExisting, existingBinding, gameEvent, bindingType := menu.getPendingChangeByKey(key)
if changeExisting == nil {
existingBinding, gameEvent, bindingType = menu.keyMap.GetBindingByKey(key)
}
if existingBinding != nil && changeExisting == nil {
changeExisting = &bindingChange{
target: existingBinding,
primary: existingBinding.Primary,
secondary: existingBinding.Secondary,
}
menu.changesToBeSaved[gameEvent] = changeExisting
}
changeCurrent := menu.changesToBeSaved[menu.currentBindingLayout.gameEvent]
if changeCurrent == nil {
changeCurrent = &bindingChange{
target: menu.currentBindingLayout.binding,
primary: menu.currentBindingLayout.binding.Primary,
secondary: menu.currentBindingLayout.binding.Secondary,
}
menu.changesToBeSaved[menu.currentBindingLayout.gameEvent] = changeCurrent
}
switch menu.currentBindingModifierType {
case KeyBindingTypePrimary:
changeCurrent.primary = key
case KeyBindingTypeSecondary:
changeCurrent.secondary = key
}
if changeExisting != nil {
if bindingType == KeyBindingTypePrimary {
changeExisting.primary = -1
}
if bindingType == KeyBindingTypeSecondary {
changeExisting.secondary = -1
}
}
if err := menu.setBindingLabels(
changeCurrent.primary,
changeCurrent.secondary,
menu.currentBindingLayout,
); err != nil {
return err
}
if changeExisting != nil {
for _, bindingLayout := range menu.bindingLayouts {
if bindingLayout.binding == changeExisting.target {
if err := menu.setBindingLabels(changeExisting.primary, changeExisting.secondary, bindingLayout); err != nil {
return err
}
}
}
}
return nil
}
func (menu *KeyBindingMenu) setBindingLabels(primary, secondary d2enum.Key, bl *bindingLayout) error {
noneStr := menu.keyMap.KeyToString(-1)
if primary != -1 {
if err := bl.SetPrimaryBindingTextAndColor(menu.keyMap.KeyToString(primary), d2util.Color(d2gui.ColorBrown)); err != nil {
return err
}
} else {
if err := bl.SetPrimaryBindingTextAndColor(noneStr, d2util.Color(d2gui.ColorGrey)); err != nil {
return err
}
}
if secondary != -1 {
if err := bl.SetSecondaryBindingTextAndColor(menu.keyMap.KeyToString(secondary), d2util.Color(d2gui.ColorBrown)); err != nil {
return err
}
} else {
if err := bl.SetSecondaryBindingTextAndColor(noneStr, d2util.Color(d2gui.ColorGrey)); err != nil {
return err
}
}
if primary == -1 && secondary == -1 {
if err := bl.primaryLabel.SetColor(d2util.Color(d2gui.ColorRed)); err != nil {
return err
}
if err := bl.secondaryLabel.SetColor(d2util.Color(d2gui.ColorRed)); err != nil {
return err
}
}
return nil
}
func (menu *KeyBindingMenu) onCancelClicked() error {
for gameEvent := range menu.changesToBeSaved {
for _, bindingLayout := range menu.bindingLayouts {
if bindingLayout.gameEvent == gameEvent {
if err := menu.setBindingLabels(bindingLayout.binding.Primary, bindingLayout.binding.Secondary, bindingLayout); err != nil {
return err
}
}
}
}
menu.changesToBeSaved = make(map[d2enum.GameEvent]*bindingChange)
if menu.currentBindingLayout != nil {
if err := menu.clearSelection(); err != nil {
return err
}
}
if err := menu.Close(); err != nil {
return err
}
menu.escapeMenu.showLayout(optionsLayoutID)
return nil
}
func (menu *KeyBindingMenu) reload() error {
for _, bl := range menu.bindingLayouts {
if bl.binding != nil {
if err := menu.setBindingLabels(bl.binding.Primary, bl.binding.Secondary, bl); err != nil {
return err
}
}
}
return nil
}
func (menu *KeyBindingMenu) clearSelection() error {
if menu.currentBindingLayout != nil {
if err := menu.currentBindingLayout.Reset(); err != nil {
return err
}
menu.lastBindingLayout = menu.currentBindingLayout
menu.currentBindingLayout = nil
menu.currentBindingModifier = -1
menu.currentBindingModifierType = -1
}
return nil
}
func (menu *KeyBindingMenu) onDefaultClicked() error {
menu.keyMap.ResetToDefault()
if err := menu.reload(); err != nil {
return err
}
menu.changesToBeSaved = make(map[d2enum.GameEvent]*bindingChange)
return menu.clearSelection()
}
func (menu *KeyBindingMenu) onAcceptClicked() error {
for gameEvent, change := range menu.changesToBeSaved {
menu.keyMap.SetPrimaryBinding(gameEvent, change.primary)
menu.keyMap.SetSecondaryBinding(gameEvent, change.primary)
}
menu.changesToBeSaved = make(map[d2enum.GameEvent]*bindingChange)
return menu.clearSelection()
}
// OnKeyDown will assign the new key to the selected binding if any
func (menu *KeyBindingMenu) OnKeyDown(event d2interface.KeyEvent) error {
if menu.isAwaitingKeyDown {
key := event.Key()
if key == d2enum.KeyEscape {
if menu.currentBindingLayout != nil {
menu.lastBindingLayout = menu.currentBindingLayout
if err := menu.currentBindingLayout.Reset(); err != nil {
return err
}
if err := menu.clearSelection(); err != nil {
return err
}
}
} else {
if err := menu.saveKeyChange(key); err != nil {
return err
}
}
menu.isAwaitingKeyDown = false
}
return nil
}
// Advance computes the state of the elements of the menu overtime
func (menu *KeyBindingMenu) Advance(elapsed float64) error {
if menu.scrollbar != nil {
if err := menu.scrollbar.Advance(elapsed); err != nil {
return err
}
}
return nil
}
// Render draws the different element of the menu on the target surface
func (menu *KeyBindingMenu) Render(target d2interface.Surface) error {
if menu.IsOpen() {
if err := menu.Box.Render(target); err != nil {
return err
}
if menu.scrollbar != nil {
menu.scrollbar.Render(target)
}
if menu.currentBindingLayout != nil {
x, y := menu.currentBindingLayout.wrapperLayout.Sx, menu.currentBindingLayout.wrapperLayout.Sy
w, h := menu.currentBindingLayout.wrapperLayout.GetSize()
target.PushTranslation(x, y)
target.DrawRect(w, h, d2util.Color(selectionBackgroundColor))
target.Pop()
}
}
return nil
}

View File

@ -4,28 +4,143 @@ import (
"sync"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset"
)
// KeyMap represents the key mappings of the game. Each game event
// can be associated to 2 different keys. A key of -1 means none
type KeyMap struct {
mutex sync.RWMutex
mapping map[d2enum.Key]d2enum.GameEvent
controls map[d2enum.GameEvent]*KeyBinding
mutex sync.RWMutex
mapping map[d2enum.Key]d2enum.GameEvent
controls map[d2enum.GameEvent]*KeyBinding
keyToStringMapping map[d2enum.Key]string
}
// KeyBindingType defines whether it's a primary or
// secondary binding
type KeyBindingType int
// Values defining the type of key binding
const (
KeyBindingTypeNone KeyBindingType = iota
KeyBindingTypePrimary
KeyBindingTypeSecondary
)
// NewKeyMap returns a new instance of a KeyMap
func NewKeyMap() *KeyMap {
func NewKeyMap(asset *d2asset.AssetManager) *KeyMap {
return &KeyMap{
mapping: make(map[d2enum.Key]d2enum.GameEvent),
controls: make(map[d2enum.GameEvent]*KeyBinding),
mapping: make(map[d2enum.Key]d2enum.GameEvent),
controls: make(map[d2enum.GameEvent]*KeyBinding),
keyToStringMapping: getKeyStringMapping(asset),
}
}
func getKeyStringMapping(assetManager *d2asset.AssetManager) map[d2enum.Key]string {
return map[d2enum.Key]string{
-1: assetManager.TranslateString("KeyNone"),
d2enum.KeyTilde: "~",
d2enum.KeyHome: assetManager.TranslateString("KeyHome"),
d2enum.KeyControl: assetManager.TranslateString("KeyControl"),
d2enum.KeyShift: assetManager.TranslateString("KeyShift"),
d2enum.KeySpace: assetManager.TranslateString("KeySpace"),
d2enum.KeyAlt: assetManager.TranslateString("KeyAlt"),
d2enum.KeyTab: assetManager.TranslateString("KeyTab"),
d2enum.Key0: "0",
d2enum.Key1: "1",
d2enum.Key2: "2",
d2enum.Key3: "3",
d2enum.Key4: "4",
d2enum.Key5: "5",
d2enum.Key6: "6",
d2enum.Key7: "7",
d2enum.Key8: "8",
d2enum.Key9: "9",
d2enum.KeyA: "A",
d2enum.KeyB: "B",
d2enum.KeyC: "C",
d2enum.KeyD: "D",
d2enum.KeyE: "E",
d2enum.KeyF: "F",
d2enum.KeyG: "G",
d2enum.KeyH: "H",
d2enum.KeyI: "I",
d2enum.KeyJ: "J",
d2enum.KeyK: "K",
d2enum.KeyL: "L",
d2enum.KeyM: "M",
d2enum.KeyN: "N",
d2enum.KeyO: "O",
d2enum.KeyP: "P",
d2enum.KeyQ: "Q",
d2enum.KeyR: "R",
d2enum.KeyS: "S",
d2enum.KeyT: "T",
d2enum.KeyU: "U",
d2enum.KeyV: "V",
d2enum.KeyW: "W",
d2enum.KeyX: "X",
d2enum.KeyY: "Y",
d2enum.KeyZ: "Z",
d2enum.KeyF1: "F1",
d2enum.KeyF2: "F2",
d2enum.KeyF3: "F3",
d2enum.KeyF4: "F4",
d2enum.KeyF5: "F5",
d2enum.KeyF6: "F6",
d2enum.KeyF7: "F7",
d2enum.KeyF8: "F8",
d2enum.KeyF9: "F9",
d2enum.KeyF10: "F10",
d2enum.KeyF11: "F11",
d2enum.KeyF12: "F12",
d2enum.KeyKP0: assetManager.TranslateString("KeyNumPad0"),
d2enum.KeyKP1: assetManager.TranslateString("KeyNumPad1"),
d2enum.KeyKP2: assetManager.TranslateString("KeyNumPad2"),
d2enum.KeyKP3: assetManager.TranslateString("KeyNumPad3"),
d2enum.KeyKP4: assetManager.TranslateString("KeyNumPad4"),
d2enum.KeyKP5: assetManager.TranslateString("KeyNumPad5"),
d2enum.KeyKP6: assetManager.TranslateString("KeyNumPad6"),
d2enum.KeyKP7: assetManager.TranslateString("KeyNumPad7"),
d2enum.KeyKP8: assetManager.TranslateString("KeyNumPad8"),
d2enum.KeyKP9: assetManager.TranslateString("KeyNumPad9"),
d2enum.KeyPrintScreen: assetManager.TranslateString("KeySnapshot"),
d2enum.KeyRightBracket: assetManager.TranslateString("KeyRBracket"),
d2enum.KeyLeftBracket: assetManager.TranslateString("KeyLBracket"),
d2enum.KeyMouse3: assetManager.TranslateString("KeyMButton"),
d2enum.KeyMouse4: assetManager.TranslateString("Key4Button"),
d2enum.KeyMouse5: assetManager.TranslateString("Key5Button"),
d2enum.KeyMouseWheelUp: assetManager.TranslateString("KeyWheelUp"),
d2enum.KeyMouseWheelDown: assetManager.TranslateString("KeyWheelDown"),
}
}
func (km *KeyMap) checkOverwrite(key d2enum.Key) (*KeyBinding, KeyBindingType) {
var (
overwrittenBinding *KeyBinding
overwrittenBindingType KeyBindingType
)
for _, binding := range km.controls {
if binding.Primary == key {
binding.Primary = -1
overwrittenBinding = binding
overwrittenBindingType = KeyBindingTypePrimary
}
if binding.Secondary == key {
binding.Secondary = -1
overwrittenBinding = binding
overwrittenBindingType = KeyBindingTypeSecondary
}
}
return overwrittenBinding, overwrittenBindingType
}
// SetPrimaryBinding binds the first key for gameEvent
func (km *KeyMap) SetPrimaryBinding(gameEvent d2enum.GameEvent, key d2enum.Key) {
func (km *KeyMap) SetPrimaryBinding(gameEvent d2enum.GameEvent, key d2enum.Key) (*KeyBinding, KeyBindingType) {
if key == d2enum.KeyEscape {
return
return nil, -1
}
km.mutex.Lock()
@ -35,17 +150,21 @@ func (km *KeyMap) SetPrimaryBinding(gameEvent d2enum.GameEvent, key d2enum.Key)
km.controls[gameEvent] = &KeyBinding{}
}
overwrittenBinding, overwrittenBindingType := km.checkOverwrite(key)
currentKey := km.controls[gameEvent].Primary
delete(km.mapping, currentKey)
km.mapping[key] = gameEvent
km.controls[gameEvent].Primary = key
return overwrittenBinding, overwrittenBindingType
}
// SetSecondaryBinding binds the second key for gameEvent
func (km *KeyMap) SetSecondaryBinding(gameEvent d2enum.GameEvent, key d2enum.Key) {
func (km *KeyMap) SetSecondaryBinding(gameEvent d2enum.GameEvent, key d2enum.Key) (*KeyBinding, KeyBindingType) {
if key == d2enum.KeyEscape {
return
return nil, -1
}
km.mutex.Lock()
@ -55,6 +174,8 @@ func (km *KeyMap) SetSecondaryBinding(gameEvent d2enum.GameEvent, key d2enum.Key
km.controls[gameEvent] = &KeyBinding{}
}
overwrittenBinding, overwrittenBindingType := km.checkOverwrite(key)
currentKey := km.controls[gameEvent].Secondary
delete(km.mapping, currentKey)
km.mapping[key] = gameEvent
@ -64,6 +185,8 @@ func (km *KeyMap) SetSecondaryBinding(gameEvent d2enum.GameEvent, key d2enum.Key
}
km.controls[gameEvent].Secondary = key
return overwrittenBinding, overwrittenBindingType
}
func (km *KeyMap) getGameEvent(key d2enum.Key) d2enum.GameEvent {
@ -81,25 +204,46 @@ func (km *KeyMap) GetKeysForGameEvent(gameEvent d2enum.GameEvent) *KeyBinding {
return km.controls[gameEvent]
}
// GetBindingByKey returns the bindings for a givent game event
func (km *KeyMap) GetBindingByKey(key d2enum.Key) (*KeyBinding, d2enum.GameEvent, KeyBindingType) {
km.mutex.RLock()
defer km.mutex.RUnlock()
for gameEvent, binding := range km.controls {
if binding.Primary == key {
return binding, gameEvent, KeyBindingTypePrimary
}
if binding.Secondary == key {
return binding, gameEvent, KeyBindingTypeSecondary
}
}
return nil, -1, -1
}
// KeyBinding holds the primary and secondary keys assigned to a GameEvent
type KeyBinding struct {
Primary d2enum.Key
Secondary d2enum.Key
}
func getDefaultKeyMap() *KeyMap {
keyMap := NewKeyMap()
// IsEmpty checks if no keys are associated to the binding
func (b KeyBinding) IsEmpty() bool {
return b.Primary == -1 && b.Secondary == -1
}
// ResetToDefault will reset the KeyMap to the default values
func (km *KeyMap) ResetToDefault() {
defaultControls := map[d2enum.GameEvent]KeyBinding{
d2enum.ToggleCharacterPanel: {d2enum.KeyA, d2enum.KeyC},
d2enum.ToggleInventoryPanel: {d2enum.KeyB, d2enum.KeyI},
d2enum.ToggleHelpScreen: {d2enum.KeyH, -1},
d2enum.TogglePartyPanel: {d2enum.KeyP, -1},
d2enum.ToggleMessageLog: {d2enum.KeyM, -1},
d2enum.ToggleQuestLog: {d2enum.KeyQ, -1},
d2enum.ToggleChatOverlay: {d2enum.KeyEnter, -1},
d2enum.ToggleAutomap: {d2enum.KeyTab, -1},
d2enum.CenterAutomap: {d2enum.KeyHome, -1},
d2enum.ToggleCharacterPanel: {d2enum.KeyA, d2enum.KeyC},
d2enum.ToggleInventoryPanel: {d2enum.KeyB, d2enum.KeyI},
d2enum.TogglePartyPanel: {d2enum.KeyP, -1},
d2enum.ToggleHirelingPanel: {d2enum.KeyO, -1},
d2enum.ToggleMessageLog: {d2enum.KeyM, -1},
d2enum.ToggleQuestLog: {d2enum.KeyQ, -1},
d2enum.ToggleHelpScreen: {d2enum.KeyH, -1},
d2enum.ToggleSkillTreePanel: {d2enum.KeyT, -1},
d2enum.ToggleRightSkillSelector: {d2enum.KeyS, -1},
d2enum.UseSkill1: {d2enum.KeyF1, -1},
@ -118,22 +262,59 @@ func getDefaultKeyMap() *KeyMap {
d2enum.UseSkill14: {-1, -1},
d2enum.UseSkill15: {-1, -1},
d2enum.UseSkill16: {-1, -1},
d2enum.ToggleBelts: {d2enum.KeyTilde, -1},
d2enum.UseBeltSlot1: {d2enum.Key1, -1},
d2enum.UseBeltSlot2: {d2enum.Key2, -1},
d2enum.UseBeltSlot3: {d2enum.Key3, -1},
d2enum.UseBeltSlot4: {d2enum.Key4, -1},
d2enum.ToggleRunWalk: {d2enum.KeyR, -1},
d2enum.HoldRun: {d2enum.KeyControl, -1},
d2enum.HoldShowGroundItems: {d2enum.KeyAlt, -1},
d2enum.HoldShowPortraits: {d2enum.KeyZ, -1},
d2enum.HoldStandStill: {d2enum.KeyShift, -1},
d2enum.ClearScreen: {d2enum.KeySpace, -1},
d2enum.SelectPreviousSkill: {d2enum.KeyMouseWheelUp, -1},
d2enum.SelectNextSkill: {d2enum.KeyMouseWheelDown, -1},
d2enum.ToggleBelts: {d2enum.KeyTilde, -1},
d2enum.UseBeltSlot1: {d2enum.Key1, -1},
d2enum.UseBeltSlot2: {d2enum.Key2, -1},
d2enum.UseBeltSlot3: {d2enum.Key3, -1},
d2enum.UseBeltSlot4: {d2enum.Key4, -1},
d2enum.SwapWeapons: {d2enum.KeyW, -1},
d2enum.ToggleChatBox: {d2enum.KeyEnter, -1},
d2enum.HoldRun: {d2enum.KeyControl, -1},
d2enum.ToggleRunWalk: {d2enum.KeyR, -1},
d2enum.HoldStandStill: {d2enum.KeyShift, -1},
d2enum.HoldShowGroundItems: {d2enum.KeyAlt, -1},
d2enum.HoldShowPortraits: {d2enum.KeyZ, -1},
d2enum.ToggleAutomap: {d2enum.KeyTab, -1},
d2enum.CenterAutomap: {d2enum.KeyHome, -1},
d2enum.TogglePartyOnAutomap: {d2enum.KeyF11, -1},
d2enum.ToggleNamesOnAutomap: {d2enum.KeyF12, -1},
d2enum.ToggleMiniMap: {d2enum.KeyV, -1},
d2enum.SayHelp: {d2enum.KeyKP0, -1},
d2enum.SayFollowMe: {d2enum.KeyKP1, -1},
d2enum.SayThisIsForYou: {d2enum.KeyKP2, -1},
d2enum.SayThanks: {d2enum.KeyKP3, -1},
d2enum.SaySorry: {d2enum.KeyKP4, -1},
d2enum.SayBye: {d2enum.KeyKP5, -1},
d2enum.SayNowYouDie: {d2enum.KeyKP6, -1},
d2enum.SayRetreat: {d2enum.KeyKP7, -1},
d2enum.TakeScreenShot: {d2enum.KeyPrintScreen, -1},
d2enum.ClearScreen: {d2enum.KeySpace, -1},
d2enum.ClearMessages: {d2enum.KeyN, -1},
}
for gameEvent, keys := range defaultControls {
keyMap.SetPrimaryBinding(gameEvent, keys.Primary)
keyMap.SetSecondaryBinding(gameEvent, keys.Secondary)
km.SetPrimaryBinding(gameEvent, keys.Primary)
km.SetSecondaryBinding(gameEvent, keys.Secondary)
}
}
// KeyToString returns a string representing the key
func (km *KeyMap) KeyToString(k d2enum.Key) string {
return km.keyToStringMapping[k]
}
// GetDefaultKeyMap generates a KeyMap instance with the
// default values
func GetDefaultKeyMap(asset *d2asset.AssetManager) *KeyMap {
keyMap := NewKeyMap(asset)
keyMap.ResetToDefault()
return keyMap
}

View File

@ -8,7 +8,6 @@ import (
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2gui"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2hero"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2ui"
)
@ -186,7 +185,7 @@ func (s *skillTree) loadForHeroType() {
s.availSPLabel = s.uiManager.NewLabel(d2resource.Font16, d2resource.PaletteSky)
s.availSPLabel.SetPosition(availSPLabelX, availSPLabelY)
s.availSPLabel.Alignment = d2gui.HorizontalAlignCenter
s.availSPLabel.Alignment = d2ui.HorizontalAlignCenter
s.availSPLabel.SetText(s.makeTabString("StrSklTree1", "StrSklTree2", "StrSklTree3"))
s.panelGroup.AddWidget(s.availSPLabel)
}

1
go.mod
View File

@ -6,6 +6,7 @@ require (
github.com/JoshVarga/blast v0.0.0-20180421040937-681c804fb9f0
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d // indirect
github.com/davecgh/go-spew v1.1.0
github.com/go-restruct/restruct v1.2.0-alpha
github.com/google/uuid v1.1.2
github.com/gravestench/akara v0.0.0-20201014060234-a64208a7fd3c