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

View File

@ -3,28 +3,6 @@ package d2enum
// Key represents button on a traditional keyboard. // Key represents button on a traditional keyboard.
type Key int 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 // Input keys
const ( const (
Key0 Key = iota Key0 Key = iota
@ -128,9 +106,14 @@ const (
KeyControl KeyControl
KeyShift KeyShift
KeyTilde KeyTilde
KeyMouse3
KeyMouse4
KeyMouse5
KeyMouseWheelUp
KeyMouseWheelDown
KeyMin = Key0 KeyMin = Key0
KeyMax = KeyShift KeyMax = KeyMouseWheelDown
) )
// KeyMod represents a "modified" key action. This could mean, for example, ctrl-S // KeyMod represents a "modified" key action. This could mean, for example, ctrl-S

View File

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

View File

@ -111,6 +111,14 @@ const (
HelpYellowBullet = "/data/global/ui/MENU/helpyellowbullet.DC6" HelpYellowBullet = "/data/global/ui/MENU/helpyellowbullet.DC6"
HelpWhiteBullet = "/data/global/ui/MENU/helpwhitebullet.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 // Issue #685 - used in the mini-panel
GameSmallMenuButton = "/data/global/ui/PANEL/menubutton.DC6" GameSmallMenuButton = "/data/global/ui/PANEL/menubutton.DC6"
SkillIcon = "/data/global/ui/PANEL/Skillicon.DC6" SkillIcon = "/data/global/ui/PANEL/Skillicon.DC6"
@ -152,6 +160,14 @@ const (
Checkbox = "/data/global/ui/FrontEnd/clickbox.dc6" Checkbox = "/data/global/ui/FrontEnd/clickbox.dc6"
Scrollbar = "/data/global/ui/PANEL/scrollbar.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 --- // --- GAME UI ---
PentSpin = "/data/global/ui/CURSOR/pentspin.DC6" PentSpin = "/data/global/ui/CURSOR/pentspin.DC6"

View File

@ -151,6 +151,12 @@ func (a *Animation) renderShadow(target d2interface.Surface) {
target.Render(frame.image) 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 // Render renders the animation to the given surface
func (a *Animation) Render(target d2interface.Surface) { func (a *Animation) Render(target d2interface.Surface) {
if a.renderer == nil { 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 package d2gui
import ( import (
"image/color"
"log" "log"
"time"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset" "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 // Label is renderable text
type Label struct { type Label struct {
widgetBase widgetBase
renderer d2interface.Renderer renderer d2interface.Renderer
text string text string
font *d2asset.Font font *d2asset.Font
surface d2interface.Surface 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{ label := &Label{
font: font, font: font,
renderer: renderer, renderer: renderer,
color: col,
hoverColor: col,
} }
err := label.setText(text) err := label.setText(text)
@ -34,7 +55,33 @@ func createLabel(renderer d2interface.Renderer, text string, font *d2asset.Font)
return label 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) { 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) target.Render(l.surface)
} }
@ -47,6 +94,12 @@ func (l *Label) GetText() string {
return l.text 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 // SetText sets the label text
func (l *Label) SetText(text string) error { func (l *Label) SetText(text string) error {
if text == l.text { if text == l.text {
@ -61,6 +114,13 @@ func (l *Label) setText(text string) error {
surface := l.renderer.NewSurface(width, height) 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 { if err := l.font.RenderText(text, surface); err != nil {
return err 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 ( import (
"errors" "errors"
"image/color"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2math" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2math"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2util"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset"
) )
@ -105,6 +107,14 @@ func (l *Layout) AddLayout(positionType PositionType) *Layout {
return 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 // AddSpacerStatic adds a spacer with explicitly defined height and width
func (l *Layout) AddSpacerStatic(width, height int) *SpacerStatic { func (l *Layout) AddSpacerStatic(width, height int) *SpacerStatic {
spacer := createSpacerStatic(width, height) spacer := createSpacerStatic(width, height)
@ -157,7 +167,21 @@ func (l *Layout) AddLabel(text string, fontStyle FontStyle) (*Label, error) {
return nil, err 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}) 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 { func (l *Layout) onMouseButtonDown(event d2interface.MouseEvent) bool {
for _, entry := range l.entries { for _, entry := range l.entries {
if entry.IsIn(event) { if entry.IsIn(event) {
entry.widget.onMouseButtonClick(event)
entry.widget.onMouseButtonDown(event) entry.widget.onMouseButtonDown(event)
entry.mouseDown[event.Button()] = true 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/d2enum"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2ui"
) )
// static check that we implement our interface // 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 // Render renders the given surface
func (s *ebitenSurface) Render(sfc d2interface.Surface) { func (s *ebitenSurface) Render(sfc d2interface.Surface) {
opts := s.createDrawImageOptions() opts := s.createDrawImageOptions()
@ -146,6 +161,8 @@ func (s *ebitenSurface) Render(sfc d2interface.Surface) {
s.handleStateEffect(opts) s.handleStateEffect(opts)
opts.CompositeMode = ebiten.CompositeModeSourceOver
s.image.DrawImage(sfc.(*ebitenSurface).image, opts) 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/d2interface"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2util" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2util"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2gui"
) )
// ButtonType defines the type of button // ButtonType defines the type of button
@ -271,7 +270,7 @@ func (ui *UIManager) NewButton(buttonType ButtonType, text string) *Button {
lbl.SetText(text) lbl.SetText(text)
lbl.Color[0] = d2util.Color(buttonLayout.LabelColor) lbl.Color[0] = d2util.Color(buttonLayout.LabelColor)
lbl.Alignment = d2gui.HorizontalAlignCenter lbl.Alignment = HorizontalAlignCenter
buttonSprite, err := ui.NewSprite(buttonLayout.ResourceName, buttonLayout.PaletteName) buttonSprite, err := ui.NewSprite(buttonLayout.ResourceName, buttonLayout.PaletteName)
if err != nil { if err != nil {

View File

@ -15,6 +15,16 @@ const (
CursorButtonRight CursorButton = 2 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 // NewUIManager creates a UIManager instance with the given input and audio provider
func NewUIManager( func NewUIManager(
asset *d2asset.AssetManager, asset *d2asset.AssetManager,

View File

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

View File

@ -47,6 +47,11 @@ func (s *Sprite) Render(target d2interface.Surface) {
s.animation.Render(target) 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 // RenderSection renders the section of the sprite enclosed by bounds
func (s *Sprite) RenderSection(sfc d2interface.Surface, bound image.Rectangle) { func (s *Sprite) RenderSection(sfc d2interface.Surface, bound image.Rectangle) {
sfc.PushTranslation(s.x, s.y-bound.Dy()) 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/d2interface"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2util" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2util"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2gui"
) )
const ( const (
@ -56,7 +55,7 @@ func (ui *UIManager) NewTooltip(font,
originX tooltipXOrigin, originX tooltipXOrigin,
originY tooltipYOrigin) *Tooltip { originY tooltipYOrigin) *Tooltip {
label := ui.NewLabel(font, palette) label := ui.NewLabel(font, palette)
label.Alignment = d2gui.HorizontalAlignCenter label.Alignment = HorizontalAlignCenter
base := NewBaseWidget(ui) base := NewBaseWidget(ui)

View File

@ -13,7 +13,6 @@ import (
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2gui"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapentity" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapentity"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2screen" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2screen"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2ui" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2ui"
@ -216,14 +215,14 @@ func (v *CharacterSelect) loadHeroTitle() {
heroTitleX, heroTitleY := 320, 23 heroTitleX, heroTitleY := 320, 23
v.d2HeroTitle = v.uiManager.NewLabel(d2resource.Font42, d2resource.PaletteUnits) v.d2HeroTitle = v.uiManager.NewLabel(d2resource.Font42, d2resource.PaletteUnits)
v.d2HeroTitle.SetPosition(heroTitleX, heroTitleY) v.d2HeroTitle.SetPosition(heroTitleX, heroTitleY)
v.d2HeroTitle.Alignment = d2gui.HorizontalAlignCenter v.d2HeroTitle.Alignment = d2ui.HorizontalAlignCenter
} }
func (v *CharacterSelect) loadDeleteCharConfirm() { func (v *CharacterSelect) loadDeleteCharConfirm() {
v.deleteCharConfirmLabel = v.uiManager.NewLabel(d2resource.Font16, d2resource.PaletteUnits) 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." 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.SetText(lines)
v.deleteCharConfirmLabel.Alignment = d2gui.HorizontalAlignCenter v.deleteCharConfirmLabel.Alignment = d2ui.HorizontalAlignCenter
deleteConfirmX, deleteConfirmY := 400, 185 deleteConfirmX, deleteConfirmY := 400, 185
v.deleteCharConfirmLabel.SetPosition(deleteConfirmX, deleteConfirmY) v.deleteCharConfirmLabel.SetPosition(deleteConfirmX, deleteConfirmY)
} }

View File

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

View File

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

View File

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

View File

@ -14,7 +14,6 @@ import (
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2util" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2util"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2gui"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2screen" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2screen"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2ui" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2ui"
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2client/d2clientconnectiontype" "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2client/d2clientconnectiontype"
@ -412,36 +411,36 @@ func (v *SelectHeroClass) createLabels() {
v.headingLabel.SetPosition(headingX-halfFontWidth, headingY) v.headingLabel.SetPosition(headingX-halfFontWidth, headingY)
v.headingLabel.SetText("Select Hero Class") 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 = v.uiManager.NewLabel(d2resource.Font30, d2resource.PaletteUnits)
v.heroClassLabel.Alignment = d2gui.HorizontalAlignCenter v.heroClassLabel.Alignment = d2ui.HorizontalAlignCenter
v.heroClassLabel.SetPosition(heroClassLabelX, heroClassLabelY) v.heroClassLabel.SetPosition(heroClassLabelX, heroClassLabelY)
v.heroDesc1Label = v.uiManager.NewLabel(d2resource.Font16, d2resource.PaletteUnits) 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.heroDesc1Label.SetPosition(heroDescLine1X, heroDescLine1Y)
v.heroDesc2Label = v.uiManager.NewLabel(d2resource.Font16, d2resource.PaletteUnits) 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.heroDesc2Label.SetPosition(heroDescLine2X, heroDescLine2Y)
v.heroDesc3Label = v.uiManager.NewLabel(d2resource.Font16, d2resource.PaletteUnits) 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.heroDesc3Label.SetPosition(heroDescLine3X, heroDescLine3Y)
v.heroNameLabel = v.uiManager.NewLabel(d2resource.Font16, d2resource.PaletteUnits) 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.SetText(d2ui.ColorTokenize("Character Name", d2ui.ColorTokenGold))
v.heroNameLabel.SetPosition(heroNameLabelX, heroNameLabelY) v.heroNameLabel.SetPosition(heroNameLabelX, heroNameLabelY)
v.expansionCharLabel = v.uiManager.NewLabel(d2resource.Font16, d2resource.PaletteUnits) 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.SetText(d2ui.ColorTokenize("EXPANSION CHARACTER", d2ui.ColorTokenGold))
v.expansionCharLabel.SetPosition(expansionLabelX, expansionLabelY) v.expansionCharLabel.SetPosition(expansionLabelX, expansionLabelY)
v.hardcoreCharLabel = v.uiManager.NewLabel(d2resource.Font16, d2resource.PaletteUnits) 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.SetText(d2ui.ColorTokenize("Hardcore", d2ui.ColorTokenGold))
v.hardcoreCharLabel.SetPosition(hardcoreLabelX, hardcoreLabelY) 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/d2common/d2resource"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2gui" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2gui"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2ui"
) )
type ( type (
@ -70,22 +71,25 @@ type EscapeMenu struct {
// leftPent and rightPent are generated once and shared between the layouts // leftPent and rightPent are generated once and shared between the layouts
leftPent *d2gui.AnimatedSprite leftPent *d2gui.AnimatedSprite
rightPent *d2gui.AnimatedSprite rightPent *d2gui.AnimatedSprite
layouts []*layout layouts map[layoutID]*layout
renderer d2interface.Renderer renderer d2interface.Renderer
audioProvider d2interface.AudioProvider audioProvider d2interface.AudioProvider
navigator d2interface.Navigator navigator d2interface.Navigator
guiManager *d2gui.GuiManager guiManager *d2gui.GuiManager
assetManager *d2asset.AssetManager assetManager *d2asset.AssetManager
keyMap *KeyMap
keyBindingMenu *KeyBindingMenu
} }
type layout struct { type layout struct {
*d2gui.Layout *d2gui.Layout
leftPent *d2gui.AnimatedSprite leftPent *d2gui.AnimatedSprite
rightPent *d2gui.AnimatedSprite rightPent *d2gui.AnimatedSprite
actionableElements []actionableElement
currentEl int currentEl int
rendered bool rendered bool
actionableElements []actionableElement isRaw bool
} }
func (l *layout) Trigger() { func (l *layout) Trigger() {
@ -134,8 +138,10 @@ type actionableElement interface {
func NewEscapeMenu(navigator d2interface.Navigator, func NewEscapeMenu(navigator d2interface.Navigator,
renderer d2interface.Renderer, renderer d2interface.Renderer,
audioProvider d2interface.AudioProvider, audioProvider d2interface.AudioProvider,
uiManager *d2ui.UIManager,
guiManager *d2gui.GuiManager, guiManager *d2gui.GuiManager,
assetManager *d2asset.AssetManager, assetManager *d2asset.AssetManager,
keyMap *KeyMap,
) *EscapeMenu { ) *EscapeMenu {
m := &EscapeMenu{ m := &EscapeMenu{
audioProvider: audioProvider, audioProvider: audioProvider,
@ -143,16 +149,18 @@ func NewEscapeMenu(navigator d2interface.Navigator,
navigator: navigator, navigator: navigator,
guiManager: guiManager, guiManager: guiManager,
assetManager: assetManager, assetManager: assetManager,
keyMap: keyMap,
} }
m.layouts = []*layout{ keyBindingMenu := NewKeyBindingMenu(assetManager, renderer, uiManager, guiManager, keyMap, m)
mainLayoutID: m.newMainLayout(), m.keyBindingMenu = keyBindingMenu
optionsLayoutID: m.newOptionsLayout(),
soundOptionsLayoutID: m.newSoundOptionsLayout(), m.layouts = make(map[layoutID]*layout)
videoOptionsLayoutID: m.newVideoOptionsLayout(), m.layouts[mainLayoutID] = m.newMainLayout()
automapOptionsLayoutID: m.newAutomapOptionsLayout(), m.layouts[optionsLayoutID] = m.newOptionsLayout()
configureControlsLayoutID: m.newConfigureControlsLayout(), m.layouts[soundOptionsLayoutID] = m.newSoundOptionsLayout()
} m.layouts[videoOptionsLayoutID] = m.newVideoOptionsLayout()
m.layouts[automapOptionsLayoutID] = m.newAutomapOptionsLayout()
return m return m
} }
@ -213,11 +221,11 @@ func (m *EscapeMenu) newAutomapOptionsLayout() *layout {
}) })
} }
func (m *EscapeMenu) newConfigureControlsLayout() *layout { func (m *EscapeMenu) newConfigureControlsLayout(keyBindingMenu *KeyBindingMenu) *layout {
return m.wrapLayout(func(l *layout) { return &layout{
m.addTitle(l, "CONFIGURE CONTROLS") Layout: keyBindingMenu.GetLayout(),
m.addPreviousMenuLabel(l) isRaw: true,
}) }
} }
func (m *EscapeMenu) wrapLayout(fn func(*layout)) *layout { 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() { func (m *EscapeMenu) OnLoad() {
var err error 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) m.selectSound, err = m.audioProvider.LoadSound(d2resource.SFXCursorSelect, false, false)
if err != nil { if err != nil {
log.Print(err) log.Print(err)
@ -384,6 +399,11 @@ func (m *EscapeMenu) OnEscKey() {
automapOptionsLayoutID, automapOptionsLayoutID,
configureControlsLayoutID: configureControlsLayoutID:
m.setLayout(optionsLayoutID) m.setLayout(optionsLayoutID)
if err := m.keyBindingMenu.Close(); err != nil {
log.Printf("unable to close the configure controls menu: %v", err)
}
return return
} }
@ -419,6 +439,12 @@ func (m *EscapeMenu) showLayout(id layoutID) {
} }
m.setLayout(id) 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) { func (m *EscapeMenu) onHoverElement(id int) {
@ -437,6 +463,16 @@ func (m *EscapeMenu) onUpdateValue(optID optionID, value string) {
} }
func (m *EscapeMenu) setLayout(id layoutID) { 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.leftPent = m.layouts[id].leftPent
m.rightPent = m.layouts[id].rightPent m.rightPent = m.layouts[id].rightPent
m.currentLayout = id m.currentLayout = id
@ -500,8 +536,79 @@ func (m *EscapeMenu) IsOpen() bool {
return m.isOpen 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 // OnKeyDown defines the actions of the Escape Menu when a key is pressed
func (m *EscapeMenu) OnKeyDown(event d2interface.KeyEvent) bool { 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() { switch event.Key() {
case d2enum.KeyUp: case d2enum.KeyUp:
m.onUpKey() m.onUpKey()

View File

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

View File

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

View File

@ -8,7 +8,6 @@ import (
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2gui"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2hero" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2hero"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2ui" "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 { func (s *HeroStatsPanel) createTextLabel(element PanelText) *d2ui.Label {
label := s.uiManager.NewLabel(element.Font, d2resource.PaletteStatic) label := s.uiManager.NewLabel(element.Font, d2resource.PaletteStatic)
if element.AlignCenter { if element.AlignCenter {
label.Alignment = d2gui.HorizontalAlignCenter label.Alignment = d2ui.HorizontalAlignCenter
} }
label.SetText(element.Text) label.SetText(element.Text)

View File

@ -12,7 +12,6 @@ import (
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2util" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2util"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset" "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/d2mapengine"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapentity" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapentity"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2maprenderer" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2maprenderer"
@ -135,11 +134,11 @@ func NewHUD(
mapRenderer *d2maprenderer.MapRenderer, mapRenderer *d2maprenderer.MapRenderer,
) *HUD { ) *HUD {
nameLabel := ui.NewLabel(d2resource.Font16, d2resource.PaletteStatic) nameLabel := ui.NewLabel(d2resource.Font16, d2resource.PaletteStatic)
nameLabel.Alignment = d2gui.HorizontalAlignCenter nameLabel.Alignment = d2ui.HorizontalAlignCenter
nameLabel.SetText(d2ui.ColorTokenize("", d2ui.ColorTokenServer)) nameLabel.SetText(d2ui.ColorTokenize("", d2ui.ColorTokenServer))
zoneLabel := ui.NewLabel(d2resource.Font30, d2resource.PaletteUnits) zoneLabel := ui.NewLabel(d2resource.Font30, d2resource.PaletteUnits)
zoneLabel.Alignment = d2gui.HorizontalAlignCenter zoneLabel.Alignment = d2ui.HorizontalAlignCenter
return &HUD{ return &HUD{
asset: asset, 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" "sync"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset"
) )
// KeyMap represents the key mappings of the game. Each game event // KeyMap represents the key mappings of the game. Each game event
// can be associated to 2 different keys. A key of -1 means none // can be associated to 2 different keys. A key of -1 means none
type KeyMap struct { type KeyMap struct {
mutex sync.RWMutex mutex sync.RWMutex
mapping map[d2enum.Key]d2enum.GameEvent mapping map[d2enum.Key]d2enum.GameEvent
controls map[d2enum.GameEvent]*KeyBinding 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 // NewKeyMap returns a new instance of a KeyMap
func NewKeyMap() *KeyMap { func NewKeyMap(asset *d2asset.AssetManager) *KeyMap {
return &KeyMap{ return &KeyMap{
mapping: make(map[d2enum.Key]d2enum.GameEvent), mapping: make(map[d2enum.Key]d2enum.GameEvent),
controls: make(map[d2enum.GameEvent]*KeyBinding), 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 // 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 { if key == d2enum.KeyEscape {
return return nil, -1
} }
km.mutex.Lock() km.mutex.Lock()
@ -35,17 +150,21 @@ func (km *KeyMap) SetPrimaryBinding(gameEvent d2enum.GameEvent, key d2enum.Key)
km.controls[gameEvent] = &KeyBinding{} km.controls[gameEvent] = &KeyBinding{}
} }
overwrittenBinding, overwrittenBindingType := km.checkOverwrite(key)
currentKey := km.controls[gameEvent].Primary currentKey := km.controls[gameEvent].Primary
delete(km.mapping, currentKey) delete(km.mapping, currentKey)
km.mapping[key] = gameEvent km.mapping[key] = gameEvent
km.controls[gameEvent].Primary = key km.controls[gameEvent].Primary = key
return overwrittenBinding, overwrittenBindingType
} }
// SetSecondaryBinding binds the second key for gameEvent // 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 { if key == d2enum.KeyEscape {
return return nil, -1
} }
km.mutex.Lock() km.mutex.Lock()
@ -55,6 +174,8 @@ func (km *KeyMap) SetSecondaryBinding(gameEvent d2enum.GameEvent, key d2enum.Key
km.controls[gameEvent] = &KeyBinding{} km.controls[gameEvent] = &KeyBinding{}
} }
overwrittenBinding, overwrittenBindingType := km.checkOverwrite(key)
currentKey := km.controls[gameEvent].Secondary currentKey := km.controls[gameEvent].Secondary
delete(km.mapping, currentKey) delete(km.mapping, currentKey)
km.mapping[key] = gameEvent km.mapping[key] = gameEvent
@ -64,6 +185,8 @@ func (km *KeyMap) SetSecondaryBinding(gameEvent d2enum.GameEvent, key d2enum.Key
} }
km.controls[gameEvent].Secondary = key km.controls[gameEvent].Secondary = key
return overwrittenBinding, overwrittenBindingType
} }
func (km *KeyMap) getGameEvent(key d2enum.Key) d2enum.GameEvent { 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] 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 // KeyBinding holds the primary and secondary keys assigned to a GameEvent
type KeyBinding struct { type KeyBinding struct {
Primary d2enum.Key Primary d2enum.Key
Secondary d2enum.Key Secondary d2enum.Key
} }
func getDefaultKeyMap() *KeyMap { // IsEmpty checks if no keys are associated to the binding
keyMap := NewKeyMap() 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{ defaultControls := map[d2enum.GameEvent]KeyBinding{
d2enum.ToggleCharacterPanel: {d2enum.KeyA, d2enum.KeyC}, d2enum.ToggleCharacterPanel: {d2enum.KeyA, d2enum.KeyC},
d2enum.ToggleInventoryPanel: {d2enum.KeyB, d2enum.KeyI}, d2enum.ToggleInventoryPanel: {d2enum.KeyB, d2enum.KeyI},
d2enum.ToggleHelpScreen: {d2enum.KeyH, -1}, d2enum.TogglePartyPanel: {d2enum.KeyP, -1},
d2enum.TogglePartyPanel: {d2enum.KeyP, -1}, d2enum.ToggleHirelingPanel: {d2enum.KeyO, -1},
d2enum.ToggleMessageLog: {d2enum.KeyM, -1}, d2enum.ToggleMessageLog: {d2enum.KeyM, -1},
d2enum.ToggleQuestLog: {d2enum.KeyQ, -1}, d2enum.ToggleQuestLog: {d2enum.KeyQ, -1},
d2enum.ToggleChatOverlay: {d2enum.KeyEnter, -1}, d2enum.ToggleHelpScreen: {d2enum.KeyH, -1},
d2enum.ToggleAutomap: {d2enum.KeyTab, -1},
d2enum.CenterAutomap: {d2enum.KeyHome, -1},
d2enum.ToggleSkillTreePanel: {d2enum.KeyT, -1}, d2enum.ToggleSkillTreePanel: {d2enum.KeyT, -1},
d2enum.ToggleRightSkillSelector: {d2enum.KeyS, -1}, d2enum.ToggleRightSkillSelector: {d2enum.KeyS, -1},
d2enum.UseSkill1: {d2enum.KeyF1, -1}, d2enum.UseSkill1: {d2enum.KeyF1, -1},
@ -118,22 +262,59 @@ func getDefaultKeyMap() *KeyMap {
d2enum.UseSkill14: {-1, -1}, d2enum.UseSkill14: {-1, -1},
d2enum.UseSkill15: {-1, -1}, d2enum.UseSkill15: {-1, -1},
d2enum.UseSkill16: {-1, -1}, d2enum.UseSkill16: {-1, -1},
d2enum.ToggleBelts: {d2enum.KeyTilde, -1}, d2enum.SelectPreviousSkill: {d2enum.KeyMouseWheelUp, -1},
d2enum.UseBeltSlot1: {d2enum.Key1, -1}, d2enum.SelectNextSkill: {d2enum.KeyMouseWheelDown, -1},
d2enum.UseBeltSlot2: {d2enum.Key2, -1},
d2enum.UseBeltSlot3: {d2enum.Key3, -1}, d2enum.ToggleBelts: {d2enum.KeyTilde, -1},
d2enum.UseBeltSlot4: {d2enum.Key4, -1}, d2enum.UseBeltSlot1: {d2enum.Key1, -1},
d2enum.ToggleRunWalk: {d2enum.KeyR, -1}, d2enum.UseBeltSlot2: {d2enum.Key2, -1},
d2enum.HoldRun: {d2enum.KeyControl, -1}, d2enum.UseBeltSlot3: {d2enum.Key3, -1},
d2enum.HoldShowGroundItems: {d2enum.KeyAlt, -1}, d2enum.UseBeltSlot4: {d2enum.Key4, -1},
d2enum.HoldShowPortraits: {d2enum.KeyZ, -1}, d2enum.SwapWeapons: {d2enum.KeyW, -1},
d2enum.HoldStandStill: {d2enum.KeyShift, -1},
d2enum.ClearScreen: {d2enum.KeySpace, -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 { for gameEvent, keys := range defaultControls {
keyMap.SetPrimaryBinding(gameEvent, keys.Primary) km.SetPrimaryBinding(gameEvent, keys.Primary)
keyMap.SetSecondaryBinding(gameEvent, keys.Secondary) 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 return keyMap
} }

View File

@ -8,7 +8,6 @@ import (
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2gui"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2hero" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2hero"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2ui" "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 = s.uiManager.NewLabel(d2resource.Font16, d2resource.PaletteSky)
s.availSPLabel.SetPosition(availSPLabelX, availSPLabelY) s.availSPLabel.SetPosition(availSPLabelX, availSPLabelY)
s.availSPLabel.Alignment = d2gui.HorizontalAlignCenter s.availSPLabel.Alignment = d2ui.HorizontalAlignCenter
s.availSPLabel.SetText(s.makeTabString("StrSklTree1", "StrSklTree2", "StrSklTree3")) s.availSPLabel.SetText(s.makeTabString("StrSklTree1", "StrSklTree2", "StrSklTree3"))
s.panelGroup.AddWidget(s.availSPLabel) 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/JoshVarga/blast v0.0.0-20180421040937-681c804fb9f0
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d // 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/go-restruct/restruct v1.2.0-alpha
github.com/google/uuid v1.1.2 github.com/google/uuid v1.1.2
github.com/gravestench/akara v0.0.0-20201014060234-a64208a7fd3c github.com/gravestench/akara v0.0.0-20201014060234-a64208a7fd3c