1
1
mirror of https://github.com/OpenDiablo2/OpenDiablo2 synced 2024-12-24 11:06:51 -05:00

d2ui refactor (#699)

* fixed lint errors in button.go

* fixed lint errors in checkbox.go

* Removed d2ui singleton, fixed nearly all lint errors

- Changed `UI` struct to `UIManager`, removed singleton
- UI element provider functions are now methods of the UI Manager
- Screens now use the UI manager to create UI elements
- game panels in d2player now use the UI Manager to create UI elements
- Only the UI manager knows about "widgets"; calls to `d2ui.AddWidget` in Screen instances have been removed

* changed ui element provider methods from `Create` to `New`
This commit is contained in:
lord 2020-08-06 07:30:23 -07:00 committed by GitHub
parent c1ed5a2381
commit acc4c7a13e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 782 additions and 539 deletions

View File

@ -64,6 +64,7 @@ type App struct {
audio d2interface.AudioProvider
renderer d2interface.Renderer
screen *d2screen.ScreenManager
ui *d2ui.UIManager
tAllocSamples *ring.Ring
}
@ -85,7 +86,11 @@ func Create(gitBranch, gitCommit string,
terminal d2interface.Terminal,
scriptEngine *d2script.ScriptEngine,
audio d2interface.AudioProvider,
renderer d2interface.Renderer) *App {
renderer d2interface.Renderer,
) *App {
uiManager := d2ui.NewUIManager(renderer, inputManager, audio)
screenManager := d2screen.NewScreenManager(uiManager)
result := &App{
gitBranch: gitBranch,
gitCommit: gitCommit,
@ -94,7 +99,8 @@ func Create(gitBranch, gitCommit string,
scriptEngine: scriptEngine,
audio: audio,
renderer: renderer,
screen: d2screen.NewScreenManager(),
ui: uiManager,
screen: screenManager,
tAllocSamples: createZeroedRing(nSamplesTAlloc),
}
@ -187,7 +193,7 @@ func (a *App) initialize() error {
d2inventory.LoadHeroObjects()
d2ui.Initialize(a.inputManager, a.audio)
a.ui.Initialize()
return nil
}
@ -368,7 +374,7 @@ func (a *App) render(target d2interface.Surface) error {
return err
}
d2ui.Render(target)
a.ui.Render(target)
if err := d2gui.Render(target); err != nil {
return err
@ -398,7 +404,7 @@ func (a *App) advance(elapsed, elapsedUnscaled, current float64) error {
return err
}
d2ui.Advance(elapsed)
a.ui.Advance(elapsed)
if err := a.inputManager.Advance(elapsed, current); err != nil {
return err
@ -668,14 +674,14 @@ func updateInitError(target d2interface.Surface) error {
// ToMainMenu forces the game to transition to the Main Menu
func (a *App) ToMainMenu() {
buildInfo := d2gamescreen.BuildInfo{Branch: a.gitBranch, Commit: a.gitCommit}
mainMenu := d2gamescreen.CreateMainMenu(a, a.renderer, a.inputManager, a.audio, buildInfo)
mainMenu.SetScreenMode(d2gamescreen.ScreenModeMainMenu)
mainMenu := d2gamescreen.CreateMainMenu(a, a.renderer, a.inputManager, a.audio, a.ui, buildInfo)
// mainMenu.SetScreenMode(d2gamescreen.ScreenModeMainMenu)
a.screen.SetNextScreen(mainMenu)
}
// ToSelectHero forces the game to transition to the Select Hero (create character) screen
func (a *App) ToSelectHero(connType d2clientconnectiontype.ClientConnectionType, host string) {
selectHero := d2gamescreen.CreateSelectHeroClass(a, a.renderer, a.audio, connType, host)
selectHero := d2gamescreen.CreateSelectHeroClass(a, a.renderer, a.audio, a.ui, connType, host)
a.screen.SetNextScreen(selectHero)
}
@ -693,7 +699,8 @@ func (a *App) ToCreateGame(filePath string, connType d2clientconnectiontype.Clie
// ToCharacterSelect forces the game to transition to the Character Select (load character) screen
func (a *App) ToCharacterSelect(connType d2clientconnectiontype.ClientConnectionType, connHost string) {
characterSelect := d2gamescreen.CreateCharacterSelect(a, a.renderer, a.inputManager, a.audio, connType, connHost)
characterSelect := d2gamescreen.CreateCharacterSelect(a, a.renderer, a.inputManager, a.audio,
a.ui, connType, connHost)
a.screen.SetNextScreen(characterSelect)
}
@ -706,5 +713,5 @@ func (a *App) ToMapEngineTest(region, level int) {
// ToCredits forces the game to transition to the credits screen
func (a *App) ToCredits() {
a.screen.SetNextScreen(d2gamescreen.CreateCredits(a, a.renderer))
a.screen.SetNextScreen(d2gamescreen.CreateCredits(a, a.renderer, a.ui))
}

View File

@ -55,7 +55,7 @@ func NewPlayer(id, name string, x, y, direction int, heroType d2enum.Hero,
Stats: stats,
name: name,
Class: heroType,
//nameLabel: d2ui.CreateLabel(d2resource.FontFormal11, d2resource.PaletteStatic),
//nameLabel: d2ui.NewLabel(d2resource.FontFormal11, d2resource.PaletteStatic),
isRunToggled: true,
isInTown: true,
isRunning: true,

View File

@ -2,11 +2,16 @@ package d2screen
import (
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2ui"
)
// NewScreenManager creates a screen manager
func NewScreenManager() *ScreenManager {
return &ScreenManager{}
func NewScreenManager(ui *d2ui.UIManager) *ScreenManager {
sm := &ScreenManager{
uiManager: ui,
}
return sm
}
// Screen is an exported interface

View File

@ -10,6 +10,7 @@ import (
// ScreenManager manages game screens (main menu, credits, character select, game, etc)
type ScreenManager struct {
uiManager *d2ui.UIManager
nextScreen Screen
loadingScreen Screen
loadingState LoadingState
@ -52,7 +53,7 @@ func (sm *ScreenManager) Advance(elapsed float64) error {
}
}
d2ui.Reset()
sm.uiManager.Reset()
d2gui.SetLayout(nil)
if handler, ok := sm.nextScreen.(ScreenLoadHandler); ok {

View File

@ -3,8 +3,8 @@ package d2ui
import (
"fmt"
"image"
"image/color"
"github.com/OpenDiablo2/OpenDiablo2/d2common"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2gui"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
@ -43,6 +43,11 @@ const (
ButtonTypeMinipanelMen ButtonType = 19
)
const (
greyAlpha100 = 0x646464_ff
lightGreyAlpha75 = 0x808080_c3
)
// ButtonLayout defines the type of buttons
type ButtonLayout struct {
ResourceName string
@ -58,31 +63,101 @@ type ButtonLayout struct {
AllowFrameChange bool
}
const (
buttonWideSegmentsX = 2
buttonWideSegmentsY = 1
buttonWideDisabledFrame = -1
buttonWideTextOffset = 1
buttonShortSegmentsX = 1
buttonShortSegmentsY = 1
buttonShortDisabledFrame = -1
buttonShortTextOffset = -1
buttonMediumSegmentsX = 1
buttonMediumSegmentsY = 1
buttonTallSegmentsX = 1
buttonTallSegmentsY = 1
buttonTallTextOffset = 5
buttonOkCancelSegmentsX = 1
buttonOkCancelSegmentsY = 1
buttonOkCancelDisabledFrame = -1
buttonRunSegmentsX = 1
buttonRunSegmentsY = 1
buttonRunDisabledFrame = -1
pressedButtonOffset = 2
)
func getButtonLayouts() map[ButtonType]ButtonLayout {
return map[ButtonType]ButtonLayout{
ButtonTypeWide: {
XSegments: 2, YSegments: 1, ResourceName: d2resource.WideButtonBlank, PaletteName: d2resource.PaletteUnits,
DisabledFrame: -1, FontPath: d2resource.FontExocet10, AllowFrameChange: true, TextOffset: 1},
XSegments: buttonWideSegmentsX,
YSegments: buttonWideSegmentsY,
DisabledFrame: buttonWideDisabledFrame,
TextOffset: buttonWideTextOffset,
ResourceName: d2resource.WideButtonBlank,
PaletteName: d2resource.PaletteUnits,
FontPath: d2resource.FontExocet10,
AllowFrameChange: true,
},
ButtonTypeShort: {
XSegments: 1, YSegments: 1, ResourceName: d2resource.ShortButtonBlank, PaletteName: d2resource.PaletteUnits,
DisabledFrame: -1, FontPath: d2resource.FontRediculous, AllowFrameChange: true, TextOffset: -1},
XSegments: buttonShortSegmentsX,
YSegments: buttonShortSegmentsY,
DisabledFrame: buttonShortDisabledFrame,
TextOffset: buttonShortTextOffset,
ResourceName: d2resource.ShortButtonBlank,
PaletteName: d2resource.PaletteUnits,
FontPath: d2resource.FontRediculous,
AllowFrameChange: true,
},
ButtonTypeMedium: {
XSegments: 1, YSegments: 1, ResourceName: d2resource.MediumButtonBlank, PaletteName: d2resource.PaletteUnits,
FontPath: d2resource.FontExocet10, AllowFrameChange: true},
XSegments: buttonMediumSegmentsX,
YSegments: buttonMediumSegmentsY,
ResourceName: d2resource.MediumButtonBlank,
PaletteName: d2resource.PaletteUnits,
FontPath: d2resource.FontExocet10,
AllowFrameChange: true,
},
ButtonTypeTall: {
XSegments: 1, YSegments: 1, ResourceName: d2resource.TallButtonBlank, PaletteName: d2resource.PaletteUnits,
FontPath: d2resource.FontExocet10, AllowFrameChange: true, TextOffset: 5},
XSegments: buttonTallSegmentsX,
YSegments: buttonTallSegmentsY,
TextOffset: buttonTallTextOffset,
ResourceName: d2resource.TallButtonBlank,
PaletteName: d2resource.PaletteUnits,
FontPath: d2resource.FontExocet10,
AllowFrameChange: true,
},
ButtonTypeOkCancel: {
XSegments: 1, YSegments: 1, ResourceName: d2resource.CancelButton, PaletteName: d2resource.PaletteUnits,
DisabledFrame: -1, FontPath: d2resource.FontRediculous, AllowFrameChange: true},
XSegments: buttonOkCancelSegmentsX,
YSegments: buttonOkCancelSegmentsY,
DisabledFrame: buttonOkCancelDisabledFrame,
ResourceName: d2resource.CancelButton,
PaletteName: d2resource.PaletteUnits,
FontPath: d2resource.FontRediculous,
AllowFrameChange: true,
},
ButtonTypeRun: {
XSegments: 1, YSegments: 1, ResourceName: d2resource.RunButton, PaletteName: d2resource.PaletteSky,
Toggleable: true, DisabledFrame: -1, FontPath: d2resource.FontRediculous, AllowFrameChange: true},
XSegments: buttonRunSegmentsX,
YSegments: buttonRunSegmentsY,
DisabledFrame: buttonRunDisabledFrame,
ResourceName: d2resource.RunButton,
PaletteName: d2resource.PaletteSky,
Toggleable: true,
FontPath: d2resource.FontRediculous,
AllowFrameChange: true,
},
}
}
var _ Widget = &Button{} // static check to ensure button implements widget
// Button defines a standard wide UI button
type Button struct {
manager *UIManager
buttonLayout ButtonLayout
normalSurface d2interface.Surface
pressedSurface d2interface.Surface
@ -100,80 +175,93 @@ type Button struct {
toggled bool
}
// CreateButton creates an instance of Button
func CreateButton(renderer d2interface.Renderer, buttonType ButtonType, text string) Button {
result := Button{
// NewButton creates an instance of Button
func (ui *UIManager) NewButton(buttonType ButtonType, text string) *Button {
btn := &Button{
width: 0,
height: 0,
visible: true,
enabled: true,
pressed: false,
}
buttonLayout := getButtonLayouts()[buttonType]
result.buttonLayout = buttonLayout
lbl := CreateLabel(buttonLayout.FontPath, d2resource.PaletteUnits)
buttonLayout := getButtonLayouts()[buttonType]
btn.buttonLayout = buttonLayout
lbl := ui.NewLabel(buttonLayout.FontPath, d2resource.PaletteUnits)
lbl.SetText(text)
lbl.Color[0] = color.RGBA{R: 100, G: 100, B: 100, A: 255}
lbl.Color[0] = d2common.Color(greyAlpha100)
lbl.Alignment = d2gui.HorizontalAlignCenter
animation, _ := d2asset.LoadAnimation(buttonLayout.ResourceName, buttonLayout.PaletteName)
buttonSprite, _ := LoadSprite(animation)
buttonSprite, _ := ui.NewSprite(animation)
for i := 0; i < buttonLayout.XSegments; i++ {
w, _, _ := buttonSprite.GetFrameSize(i)
result.width += w
btn.width += w
}
for i := 0; i < buttonLayout.YSegments; i++ {
_, h, _ := buttonSprite.GetFrameSize(i * buttonLayout.YSegments)
result.height += h
btn.height += h
}
result.normalSurface, _ = renderer.NewSurface(result.width, result.height, d2enum.FilterNearest)
btn.normalSurface, _ = ui.renderer.NewSurface(btn.width, btn.height, d2enum.FilterNearest)
buttonSprite.SetPosition(0, 0)
buttonSprite.SetEffect(d2enum.DrawEffectModulate)
result.renderFrames(renderer, buttonSprite, &buttonLayout, &lbl)
ui.addWidget(btn) // important that this comes before renderFrames!
return result
btn.renderFrames(buttonSprite, &buttonLayout, lbl)
return btn
}
func (v *Button) renderFrames(renderer d2interface.Renderer, buttonSprite *Sprite, buttonLayout *ButtonLayout, label *Label) {
totalButtonTypes := buttonSprite.GetFrameCount() / (buttonLayout.XSegments * buttonLayout.YSegments)
func (v *Button) renderFrames(btnSprite *Sprite, btnLayout *ButtonLayout, label *Label) {
totalButtonTypes := btnSprite.GetFrameCount() / (btnLayout.XSegments * btnLayout.YSegments)
var err error
err = buttonSprite.RenderSegmented(v.normalSurface, buttonLayout.XSegments, buttonLayout.YSegments, buttonLayout.BaseFrame)
err = btnSprite.RenderSegmented(v.normalSurface, btnLayout.XSegments, btnLayout.YSegments, btnLayout.BaseFrame)
if err != nil {
fmt.Printf("failed to render button normalSurface, err: %v\n", err)
}
_, labelHeight := label.GetSize()
textY := v.height/2 - labelHeight/2
xOffset := v.width / 2
textY := half(v.height - labelHeight)
xOffset := half(v.width)
label.SetPosition(xOffset, textY)
label.Render(v.normalSurface)
if buttonLayout.AllowFrameChange {
if totalButtonTypes > 1 {
v.pressedSurface, _ = renderer.NewSurface(v.width, v.height, d2enum.FilterNearest)
err = buttonSprite.RenderSegmented(v.pressedSurface, buttonLayout.XSegments, buttonLayout.YSegments, buttonLayout.BaseFrame+1)
if btnLayout.AllowFrameChange {
frameOffset := 0
xSeg, ySeg, baseFrame := btnLayout.XSegments, btnLayout.YSegments, btnLayout.BaseFrame
totalButtonTypes--
if totalButtonTypes > 0 { // button has more than one type
frameOffset++
v.pressedSurface, _ = v.manager.renderer.NewSurface(v.width, v.height,
d2enum.FilterNearest)
err = btnSprite.RenderSegmented(v.pressedSurface, xSeg, ySeg, baseFrame+frameOffset)
if err != nil {
fmt.Printf("failed to render button pressedSurface, err: %v\n", err)
}
label.SetPosition(xOffset-2, textY+2)
label.SetPosition(xOffset-pressedButtonOffset, textY+pressedButtonOffset)
label.Render(v.pressedSurface)
}
if totalButtonTypes > 2 {
v.toggledSurface, _ = renderer.NewSurface(v.width, v.height, d2enum.FilterNearest)
err = buttonSprite.RenderSegmented(v.toggledSurface, buttonLayout.XSegments, buttonLayout.YSegments, buttonLayout.BaseFrame+2)
totalButtonTypes--
if totalButtonTypes > 0 { // button has more than two types
frameOffset++
v.toggledSurface, _ = v.manager.renderer.NewSurface(v.width, v.height,
d2enum.FilterNearest)
err = btnSprite.RenderSegmented(v.pressedSurface, xSeg, ySeg, baseFrame+frameOffset)
if err != nil {
fmt.Printf("failed to render button toggledSurface, err: %v\n", err)
@ -183,9 +271,13 @@ func (v *Button) renderFrames(renderer d2interface.Renderer, buttonSprite *Sprit
label.Render(v.toggledSurface)
}
if totalButtonTypes > 3 {
v.pressedToggledSurface, _ = renderer.NewSurface(v.width, v.height, d2enum.FilterNearest)
err = buttonSprite.RenderSegmented(v.pressedToggledSurface, buttonLayout.XSegments, buttonLayout.YSegments, buttonLayout.BaseFrame+3)
totalButtonTypes--
if totalButtonTypes > 0 { // button has more than three types
frameOffset++
v.pressedToggledSurface, _ = v.manager.renderer.NewSurface(v.width, v.height,
d2enum.FilterNearest)
err = btnSprite.RenderSegmented(v.pressedSurface, xSeg, ySeg, baseFrame+frameOffset)
if err != nil {
fmt.Printf("failed to render button pressedToggledSurface, err: %v\n", err)
@ -195,9 +287,10 @@ func (v *Button) renderFrames(renderer d2interface.Renderer, buttonSprite *Sprit
label.Render(v.pressedToggledSurface)
}
if buttonLayout.DisabledFrame != -1 {
v.disabledSurface, _ = renderer.NewSurface(v.width, v.height, d2enum.FilterNearest)
err = buttonSprite.RenderSegmented(v.disabledSurface, buttonLayout.XSegments, buttonLayout.YSegments, buttonLayout.DisabledFrame)
if btnLayout.DisabledFrame != -1 {
v.disabledSurface, _ = v.manager.renderer.NewSurface(v.width, v.height,
d2enum.FilterNearest)
err = btnSprite.RenderSegmented(v.disabledSurface, xSeg, ySeg, btnLayout.DisabledFrame)
if err != nil {
fmt.Printf("failed to render button disabledSurface, err: %v\n", err)
@ -209,6 +302,11 @@ func (v *Button) renderFrames(renderer d2interface.Renderer, buttonSprite *Sprit
}
}
// bindManager binds the button to the UI manager
func (v *Button) bindManager(manager *UIManager) {
v.manager = manager
}
// OnActivated defines the callback handler for the activate event
func (v *Button) OnActivated(callback func()) {
v.onClick = callback
@ -226,15 +324,16 @@ func (v *Button) Activate() {
// Render renders the button
func (v *Button) Render(target d2interface.Surface) error {
target.PushFilter(d2enum.FilterNearest)
target.PushTranslation(v.x, v.y)
defer target.Pop()
defer target.PopN(2)
target.PushTranslation(v.x, v.y)
defer target.Pop()
var err error
switch {
case !v.enabled:
target.PushColor(color.RGBA{R: 128, G: 128, B: 128, A: 195})
target.PushColor(d2common.Color(lightGreyAlpha75))
defer target.Pop()
err = target.Render(v.disabledSurface)
case v.toggled && v.pressed:
@ -260,8 +359,8 @@ func (v *Button) Toggle() {
}
// Advance advances the button state
func (v *Button) Advance(elapsed float64) {
func (v *Button) Advance(_ float64) error {
return nil
}
// GetEnabled returns the enabled state
@ -309,3 +408,7 @@ func (v *Button) GetPressed() bool {
func (v *Button) SetPressed(pressed bool) {
v.pressed = pressed
}
func half(n int) int {
return n / 2
}

View File

@ -9,6 +9,7 @@ import (
// Checkbox represents a checkbox UI element
type Checkbox struct {
manager *UIManager
Image d2interface.Surface
checkedImage d2interface.Surface
x int
@ -21,9 +22,9 @@ type Checkbox struct {
enabled bool
}
// CreateCheckbox creates a new instance of a checkbox
func CreateCheckbox(renderer d2interface.Renderer, checkState bool) Checkbox {
result := Checkbox{
// NewCheckbox creates a new instance of a checkbox
func (ui *UIManager) NewCheckbox(checkState bool) *Checkbox {
result := &Checkbox{
checkState: checkState,
visible: true,
width: 0,
@ -32,27 +33,35 @@ func CreateCheckbox(renderer d2interface.Renderer, checkState bool) Checkbox {
}
animation, _ := d2asset.LoadAnimation(d2resource.Checkbox, d2resource.PaletteFechar)
checkboxSprite, _ := LoadSprite(animation)
checkboxSprite, _ := ui.NewSprite(animation)
result.width, result.height, _ = checkboxSprite.GetFrameSize(0)
checkboxSprite.SetPosition(0, 0)
result.Image, _ = renderer.NewSurface(result.width, result.height, d2enum.FilterNearest)
result.Image, _ = ui.renderer.NewSurface(result.width, result.height, d2enum.FilterNearest)
_ = checkboxSprite.RenderSegmented(result.Image, 1, 1, 0)
result.checkedImage, _ = renderer.NewSurface(result.width, result.height, d2enum.FilterNearest)
result.checkedImage, _ = ui.renderer.NewSurface(result.width, result.height, d2enum.FilterNearest)
_ = checkboxSprite.RenderSegmented(result.checkedImage, 1, 1, 1)
ui.addWidget(result)
return result
}
// bindManager binds the checkbox to the UI manager
func (v *Checkbox) bindManager(manager *UIManager) {
v.manager = manager
}
// Render renders the checkbox
func (v *Checkbox) Render(target d2interface.Surface) error {
target.PushTranslation(v.x, v.y)
target.PushFilter(d2enum.FilterNearest)
defer target.Pop()
defer target.PopN(2)
target.PushFilter(d2enum.FilterNearest)
defer target.Pop()
if v.checkState {
_ = target.Render(v.checkedImage)
@ -64,8 +73,8 @@ func (v *Checkbox) Render(target d2interface.Surface) error {
}
// Advance does nothing for checkboxes
func (v *Checkbox) Advance(elapsed float64) {
func (v *Checkbox) Advance(_ float64) error {
return nil
}
// GetEnabled returns the enabled state of the checkbox
@ -97,7 +106,7 @@ func (v *Checkbox) GetPressed() bool {
return v.checkState
}
// OnACtivated sets the callback function of the click event for the checkbox
// OnActivated sets the callback function of the click event for the checkbox
func (v *Checkbox) OnActivated(callback func()) {
v.onClick = callback
}
@ -108,16 +117,17 @@ func (v *Checkbox) Activate() {
if v.onClick == nil {
return
}
v.onClick()
}
// GetPosition returns the position of the checkbox
func (v *Checkbox) GetPosition() (int, int) {
func (v *Checkbox) GetPosition() (x, y int) {
return v.x, v.y
}
// GetSize returns the size of the checkbox
func (v *Checkbox) GetSize() (int, int) {
func (v *Checkbox) GetSize() (width, height int) {
return v.width, v.height
}
@ -127,7 +137,7 @@ func (v *Checkbox) GetVisible() bool {
}
// SetPosition sets the position of the checkbox
func (v *Checkbox) SetPosition(x int, y int) {
func (v *Checkbox) SetPosition(x, y int) {
v.x = x
v.y = y
}

View File

@ -0,0 +1,58 @@
package d2ui
import "fmt"
// ColorToken is a string which is used inside of label strings to set font color.
type ColorToken string
const (
colorTokenFmt = `%s%s`
colorTokenMatch = `\[[^\]]+\]` // nolint:gosec // has nothing to to with credentials
colorStrMatch = colorTokenMatch + `[^\[]+`
)
// Color tokens for colored labels
const (
ColorTokenGrey ColorToken = "[grey]"
ColorTokenRed ColorToken = "[red]"
ColorTokenWhite ColorToken = "[white]"
ColorTokenBlue ColorToken = "[blue]"
ColorTokenYellow ColorToken = "[yellow]"
ColorTokenGreen ColorToken = "[green]"
ColorTokenGold ColorToken = "[gold]"
ColorTokenOrange ColorToken = "[orange]"
ColorTokenBlack ColorToken = "[black]"
)
// Color tokens for specific use-cases
const (
ColorTokenSocketedItem = ColorTokenGrey
ColorTokenNormalItem = ColorTokenWhite
ColorTokenMagicItem = ColorTokenBlue
ColorTokenRareItem = ColorTokenYellow
ColorTokenSetItem = ColorTokenGreen
ColorTokenUniqueItem = ColorTokenGold
ColorTokenCraftedItem = ColorTokenOrange
ColorTokenServer = ColorTokenRed
ColorTokenButton = ColorTokenBlack
ColorTokenCharacterName = ColorTokenGold
ColorTokenCharacterDesc = ColorTokenWhite
ColorTokenCharacterType = ColorTokenGreen
)
const (
colorGrey100Alpha = 0x69_69_69_ff
colorWhite100Alpha = 0xff_ff_ff_ff
colorBlue100Alpha = 0x69_69_ff_ff
colorYellow100Alpha = 0xff_ff_64_ff
colorGreen100Alpha = 0x00_ff_00_ff
colorGold100Alpha = 0xc7_b3_77_ff
colorOrange100Alpha = 0xff_a8_00_ff
colorRed100Alpha = 0xff_77_77_ff
colorBlack100Alpha = 0x00_00_00_ff
)
// ColorTokenize formats the string with the given color token
func ColorTokenize(s string, t ColorToken) string {
return fmt.Sprintf(colorTokenFmt, t, s)
}

View File

@ -1,13 +1,7 @@
package d2ui
import (
"log"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
)
// CursorButton represents a mouse button
@ -20,110 +14,17 @@ const (
CursorButtonRight CursorButton = 2
)
type UI struct {
inputManager d2interface.InputManager
widgets []Widget
cursorButtons CursorButton // TODO (carrelld) convert dependent code and remove
CursorX int // TODO (carrelld) convert dependent code and remove
CursorY int // TODO (carrelld) convert dependent code and remove
pressedWidget Widget
clickSfx d2interface.SoundEffect
}
var singleton UI
func Initialize(inputManager d2interface.InputManager, audioProvider d2interface.AudioProvider) {
sfx, err := audioProvider.LoadSound(d2resource.SFXButtonClick, false, false)
if err != nil {
log.Fatalf("failed to initialize ui: %v", err)
// NewUIManager creates a UIManager instance with the given input and audio provider
func NewUIManager(
renderer d2interface.Renderer,
input d2interface.InputManager,
audio d2interface.AudioProvider,
) *UIManager {
ui := &UIManager{
renderer: renderer,
inputManager: input,
audio: audio,
}
singleton.clickSfx = sfx
singleton.inputManager = inputManager
if err := singleton.inputManager.BindHandler(&singleton); err != nil {
log.Fatalf("failed to initialize ui: %v", err)
}
}
// Reset resets the state of the UI manager. Typically called for new screens
func Reset() {
singleton.widgets = nil
singleton.pressedWidget = nil
}
// AddWidget adds a widget to the UI manager
func AddWidget(widget Widget) {
singleton.inputManager.BindHandler(widget)
singleton.widgets = append(singleton.widgets, widget)
}
func (u *UI) OnMouseButtonUp(event d2interface.MouseEvent) bool {
singleton.CursorX, singleton.CursorY = event.X(), event.Y()
if event.Button() == d2enum.MouseButtonLeft {
singleton.cursorButtons |= CursorButtonLeft
// activate previously pressed widget if cursor is still hovering
w := singleton.pressedWidget
if w != nil && contains(w, singleton.CursorX, singleton.CursorY) && w.GetVisible() && w.GetEnabled() {
w.Activate()
}
// unpress all widgets that are pressed
for _, w := range singleton.widgets {
w.SetPressed(false)
}
}
return false
}
func (u *UI) OnMouseButtonDown(event d2interface.MouseEvent) bool {
singleton.CursorX, singleton.CursorY = event.X(), event.Y()
if event.Button() == d2enum.MouseButtonLeft {
// find and press a widget on screen
singleton.pressedWidget = nil
for _, w := range singleton.widgets {
if contains(w, singleton.CursorX, singleton.CursorY) && w.GetVisible() && w.GetEnabled() {
w.SetPressed(true)
singleton.pressedWidget = w
u.clickSfx.Play()
break
}
}
}
if event.Button() == d2enum.MouseButtonRight {
singleton.cursorButtons |= CursorButtonRight
}
return false
}
// Render renders all of the UI elements
func Render(target d2interface.Surface) {
for _, widget := range singleton.widgets {
if widget.GetVisible() {
widget.Render(target)
}
}
}
// contains determines whether a given x,y coordinate lands within a Widget
func contains(w Widget, x, y int) bool {
wx, wy := w.GetPosition()
ww, wh := w.GetSize()
return x >= wx && x <= wx+ww && y >= wy && y <= wy+wh
}
// Update updates all of the UI elements
func Advance(elapsed float64) {
for _, widget := range singleton.widgets {
if widget.GetVisible() {
widget.Advance(elapsed)
}
}
}
// CursorButtonPressed determines if the specified button has been pressed
func CursorButtonPressed(button CursorButton) bool {
return singleton.cursorButtons&button > 0
}
func CursorPosition() (x, y int) {
return singleton.CursorX, singleton.CursorY
return ui
}

View File

@ -7,7 +7,7 @@ import (
// Drawable represents an instance that can be drawn
type Drawable interface {
Render(target d2interface.Surface) error
Advance(elapsed float64)
Advance(elapsed float64) error
GetSize() (width, height int)
SetPosition(x, y int)
GetPosition() (x, y int)

View File

@ -1,66 +1,20 @@
package d2ui
import (
"fmt"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2gui"
"image/color"
"log"
"regexp"
"strings"
"github.com/OpenDiablo2/OpenDiablo2/d2common"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2gui"
)
// ColorToken is a string which is used inside of label strings to set font color.
type ColorToken string
const (
colorTokenFmt = `%s%s`
colorTokenMatch = `\[[^\]]+\]`
colorStrMatch = colorTokenMatch + `[^\[]+`
)
const (
ColorTokenGrey ColorToken = "[grey]"
ColorTokenRed ColorToken = "[red]"
ColorTokenWhite ColorToken = "[white]"
ColorTokenBlue ColorToken = "[blue]"
ColorTokenYellow ColorToken = "[yellow]"
ColorTokenGreen ColorToken = "[green]"
ColorTokenGold ColorToken = "[gold]"
ColorTokenOrange ColorToken = "[orange]"
ColorTokenBlack ColorToken = "[black]"
)
// Color tokens for item labels
const (
ColorTokenSocketedItem = ColorTokenGrey
ColorTokenNormalItem = ColorTokenWhite
ColorTokenMagicItem = ColorTokenBlue
ColorTokenRareItem = ColorTokenYellow
ColorTokenSetItem = ColorTokenGreen
ColorTokenUniqueItem = ColorTokenGold
ColorTokenCraftedItem = ColorTokenOrange
)
const (
ColorTokenServer = ColorTokenRed
ColorTokenButton = ColorTokenBlack
)
const (
ColorTokenCharacterName = ColorTokenGold
ColorTokenCharacterDesc = ColorTokenWhite
ColorTokenCharacterType = ColorTokenGreen
)
// ColorTokenize formats the string with the given color token
func ColorTokenize(s string, t ColorToken) string {
return fmt.Sprintf(colorTokenFmt, t, s)
}
// Label represents a user interface label
type Label struct {
manager *UIManager
text string
X int
Y int
@ -70,15 +24,17 @@ type Label struct {
backgroundColor color.Color
}
// CreateLabel creates a new instance of a UI label
func CreateLabel(fontPath, palettePath string) Label {
// NewLabel creates a new instance of a UI label
func (ui *UIManager) NewLabel(fontPath, palettePath string) *Label {
font, _ := d2asset.LoadFont(fontPath+".tbl", fontPath+".dc6", palettePath)
result := Label{
result := &Label{
Alignment: d2gui.HorizontalAlignLeft,
Color: map[int]color.Color{0: color.White},
font: font,
}
result.bindManager(ui)
return result
}
@ -126,6 +82,11 @@ func (v *Label) Render(target d2interface.Surface) {
target.Pop()
}
// bindManager binds the label to the UI manager
func (v *Label) bindManager(manager *UIManager) {
v.manager = manager
}
// SetPosition moves the label to the specified location
func (v *Label) SetPosition(x, y int) {
v.X = x
@ -148,7 +109,7 @@ func (v *Label) SetText(newText string) {
}
// SetBackgroundColor sets the background highlight color
func (v *Label) SetBackgroundColor(c color.RGBA) {
func (v *Label) SetBackgroundColor(c color.Color) {
v.backgroundColor = c
}
@ -203,19 +164,17 @@ func (v *Label) getAlignOffset(textWidth int) int {
}
func getColor(token ColorToken) color.Color {
alpha := uint8(255)
// todo this should really come from the PL2 files
colors := map[ColorToken]color.Color{
ColorTokenGrey: color.RGBA{105, 105, 105, alpha},
ColorTokenWhite: color.RGBA{255, 255, 255, alpha},
ColorTokenBlue: color.RGBA{105, 105, 255, alpha},
ColorTokenYellow: color.RGBA{255, 255, 100, alpha},
ColorTokenGreen: color.RGBA{0, 255, 0, alpha},
ColorTokenGold: color.RGBA{199, 179, 119, alpha},
ColorTokenOrange: color.RGBA{255, 168, 0, alpha},
ColorTokenRed: color.RGBA{255, 77, 77, alpha},
ColorTokenBlack: color.RGBA{0, 0, 0, alpha},
ColorTokenGrey: d2common.Color(colorGrey100Alpha),
ColorTokenWhite: d2common.Color(colorWhite100Alpha),
ColorTokenBlue: d2common.Color(colorBlue100Alpha),
ColorTokenYellow: d2common.Color(colorYellow100Alpha),
ColorTokenGreen: d2common.Color(colorGreen100Alpha),
ColorTokenGold: d2common.Color(colorGold100Alpha),
ColorTokenOrange: d2common.Color(colorOrange100Alpha),
ColorTokenRed: d2common.Color(colorRed100Alpha),
ColorTokenBlack: d2common.Color(colorBlack100Alpha),
}
chosen := colors[token]

View File

@ -6,7 +6,17 @@ import (
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset"
)
const (
scrollbarOffsetY = 30
halfScrollbarOffsetY = scrollbarOffsetY / 2
scrollbarSpriteOffsetY = 10
scrollbarFrameOffset = 4
scrollbarWidth = 10
)
// Scrollbar is a vertical slider ui element
type Scrollbar struct {
manager *UIManager
x, y, height int
visible bool
enabled bool
@ -17,10 +27,11 @@ type Scrollbar struct {
scrollbarSprite *Sprite
}
func CreateScrollbar(x, y, height int) Scrollbar {
// NewScrollbar creates a scrollbar instance
func (ui *UIManager) NewScrollbar(x, y, height int) *Scrollbar {
animation, _ := d2asset.LoadAnimation(d2resource.Scrollbar, d2resource.PaletteSky)
scrollbarSprite, _ := LoadSprite(animation)
result := Scrollbar{
scrollbarSprite, _ := ui.NewSprite(animation)
result := &Scrollbar{
visible: true,
enabled: true,
x: x,
@ -28,32 +39,44 @@ func CreateScrollbar(x, y, height int) Scrollbar {
height: height,
scrollbarSprite: scrollbarSprite,
}
ui.addWidget(result)
return result
}
func (v Scrollbar) GetEnabled() bool {
// GetEnabled returns whether or not the scrollbar is enabled
func (v *Scrollbar) GetEnabled() bool {
return v.enabled
}
// SetEnabled sets the enabled state
func (v *Scrollbar) SetEnabled(enabled bool) {
v.enabled = enabled
}
func (v *Scrollbar) SetPressed(pressed bool) {}
func (v *Scrollbar) GetPressed() bool { return false }
// SetPressed is not used by the scrollbar, but is present to satisfy the ui widget interface
func (v *Scrollbar) SetPressed(_ bool) {}
// GetPressed is not used by the scrollbar, but is present to satisfy the ui widget interface
func (v *Scrollbar) GetPressed() bool { return false }
// OnActivated sets the onActivate callback function for the scrollbar
func (v *Scrollbar) OnActivated(callback func()) {
v.onActivate = callback
}
func (v Scrollbar) getBarPosition() int {
return int((float32(v.currentOffset) / float32(v.maxOffset)) * float32(v.height-30))
func (v *Scrollbar) getBarPosition() int {
maxOffset := float32(v.maxOffset) * float32(v.height-scrollbarOffsetY)
return int(float32(v.currentOffset) / maxOffset)
}
// Activate will call the onActivate callback (if set)
func (v *Scrollbar) Activate() {
_, my := CursorPosition()
_, my := v.manager.CursorPosition()
barPosition := v.getBarPosition()
if my <= v.y+barPosition+15 {
if my <= v.y+barPosition+halfScrollbarOffsetY {
if v.currentOffset > 0 {
v.currentOffset--
v.lastDirChange = -1
@ -70,10 +93,12 @@ func (v *Scrollbar) Activate() {
}
}
// GetLastDirChange get the last direction change
func (v *Scrollbar) GetLastDirChange() int {
return v.lastDirChange
}
// Render renders the scrollbar to the given surface
func (v *Scrollbar) Render(target d2interface.Surface) error {
if !v.visible || v.maxOffset == 0 {
return nil
@ -91,7 +116,7 @@ func (v *Scrollbar) Render(target d2interface.Surface) error {
return err
}
v.scrollbarSprite.SetPosition(v.x, v.y+v.height-10)
v.scrollbarSprite.SetPosition(v.x, v.y+v.height-scrollbarSpriteOffsetY) // what is the magic?
if err := v.scrollbarSprite.RenderSegmented(target, 1, 1, 1+offset); err != nil {
return err
@ -103,65 +128,82 @@ func (v *Scrollbar) Render(target d2interface.Surface) error {
v.scrollbarSprite.SetPosition(v.x, v.y+10+v.getBarPosition())
offset = 0
offset = scrollbarFrameOffset
if !v.enabled {
offset = 1
offset++
}
if err := v.scrollbarSprite.RenderSegmented(target, 1, 1, 4+offset); err != nil {
if err := v.scrollbarSprite.RenderSegmented(target, 1, 1, offset); err != nil {
return err
}
return nil
}
func (v *Scrollbar) Advance(elapsed float64) {
v.scrollbarSprite.Advance(elapsed)
// bindManager binds the scrollbar to the UI manager
func (v *Scrollbar) bindManager(manager *UIManager) {
v.manager = manager
}
// Advance advances the scrollbar sprite
func (v *Scrollbar) Advance(elapsed float64) error {
return v.scrollbarSprite.Advance(elapsed)
}
// GetSize returns the scrollbar width and height
func (v *Scrollbar) GetSize() (width, height int) {
return 10, v.height
return scrollbarWidth, v.height
}
// SetPosition sets the scrollbar x,y position
func (v *Scrollbar) SetPosition(x, y int) {
v.x = x
v.y = y
}
// GetPosition returns the scrollbar x,y position
func (v *Scrollbar) GetPosition() (x, y int) {
return v.x, v.y
}
// GetVisible returns whether or not the scrollbar is visible
func (v *Scrollbar) GetVisible() bool {
return v.visible
}
// SetVisible sets the scrollbar visibility state
func (v *Scrollbar) SetVisible(visible bool) {
v.visible = visible
}
// SetMaxOffset sets the maximum offset of the scrollbar
func (v *Scrollbar) SetMaxOffset(maxOffset int) {
v.maxOffset = maxOffset
if v.maxOffset < 0 {
v.maxOffset = 0
}
if v.currentOffset > v.maxOffset {
v.currentOffset = v.maxOffset
}
if v.maxOffset == 0 {
v.currentOffset = 0
}
}
// SetCurrentOffset sets the scrollbar's current offset
func (v *Scrollbar) SetCurrentOffset(currentOffset int) {
v.currentOffset = currentOffset
}
// GetMaxOffset returns the max offset
func (v *Scrollbar) GetMaxOffset() int {
return v.maxOffset
}
// GetCurrentOffset gets the current max offset of the scrollbar
func (v *Scrollbar) GetCurrentOffset() int {
return v.currentOffset
}

View File

@ -1,13 +1,13 @@
package d2ui
import (
"errors"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2math"
"fmt"
"image"
"image/color"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2math"
)
// Sprite is a positioned visual object.
@ -17,17 +17,20 @@ type Sprite struct {
animation d2interface.Animation
}
var (
ErrNoAnimation = errors.New("no animation was specified")
const (
errNoAnimation = "no animation was specified"
)
func LoadSprite(animation d2interface.Animation) (*Sprite, error) {
// NewSprite creates a new Sprite
func (ui *UIManager) NewSprite(animation d2interface.Animation) (*Sprite, error) {
if animation == nil {
return nil, ErrNoAnimation
return nil, fmt.Errorf(errNoAnimation)
}
return &Sprite{animation: animation}, nil
}
// Render renders the sprite on the given surface
func (s *Sprite) Render(target d2interface.Surface) error {
_, frameHeight := s.animation.GetCurrentFrameSize()
@ -45,12 +48,12 @@ func (s *Sprite) RenderSection(sfc d2interface.Surface, bound image.Rectangle) e
return s.animation.RenderSection(sfc, bound)
}
// RenderSegmented renders a sprite that is internally segmented as frames
func (s *Sprite) RenderSegmented(target d2interface.Surface, segmentsX, segmentsY, frameOffset int) error {
var currentY int
for y := 0; y < segmentsY; y++ {
var currentX int
var maxFrameHeight int
var currentX, maxFrameHeight int
for x := 0; x < segmentsX; x++ {
if err := s.animation.SetCurrentFrame(x + y*segmentsX + frameOffset*segmentsX*segmentsY); err != nil {
@ -83,22 +86,22 @@ func (s *Sprite) SetPosition(x, y int) {
}
// GetPosition retrieves the 2D position of the sprite
func (s *Sprite) GetPosition() (int, int) {
func (s *Sprite) GetPosition() (x, y int) {
return s.x, s.y
}
// GetFrameSize gets the Size(width, height) of a indexed frame.
func (s *Sprite) GetFrameSize(frameIndex int) (int, int, error) {
func (s *Sprite) GetFrameSize(frameIndex int) (x, y int, err error) {
return s.animation.GetFrameSize(frameIndex)
}
// GetCurrentFrameSize gets the Size(width, height) of the current frame.
func (s *Sprite) GetCurrentFrameSize() (int, int) {
func (s *Sprite) GetCurrentFrameSize() (width, height int) {
return s.animation.GetCurrentFrameSize()
}
// GetFrameBounds gets maximum Size(width, height) of all frame.
func (s *Sprite) GetFrameBounds() (int, int) {
func (s *Sprite) GetFrameBounds() (width, height int) {
return s.animation.GetFrameBounds()
}
@ -144,7 +147,7 @@ func (s *Sprite) SetCurrentFrame(frameIndex int) error {
// Rewind sprite to beginning
func (s *Sprite) Rewind() {
s.animation.SetCurrentFrame(0)
_ = s.animation.SetCurrentFrame(0)
}
// PlayForward plays sprite forward
@ -167,22 +170,27 @@ func (s *Sprite) SetPlayLoop(loop bool) {
s.animation.SetPlayLoop(loop)
}
// SetPlayLength sets the play length of the sprite animation
func (s *Sprite) SetPlayLength(playLength float64) {
s.animation.SetPlayLength(playLength)
}
// SetPlayLengthMs sets the play length of the sprite animation in milliseconds
func (s *Sprite) SetPlayLengthMs(playLengthMs int) {
s.animation.SetPlayLengthMs(playLengthMs)
}
func (s *Sprite) SetColorMod(color color.Color) {
s.animation.SetColorMod(color)
// SetColorMod sets the color modifier
func (s *Sprite) SetColorMod(c color.Color) {
s.animation.SetColorMod(c)
}
// Advance advances the animation
func (s *Sprite) Advance(elapsed float64) error {
return s.animation.Advance(elapsed)
}
// SetEffect sets the draw effect type
func (s *Sprite) SetEffect(e d2enum.DrawEffect) {
s.animation.SetEffect(e)
}

View File

@ -12,8 +12,9 @@ import (
// TextBox represents a text input box
type TextBox struct {
textLabel Label
lineBar Label
manager *UIManager
textLabel *Label
lineBar *Label
text string
filter string
x int
@ -24,20 +25,22 @@ type TextBox struct {
isFocused bool
}
// CreateTextbox creates a new instance of a text box
func CreateTextbox() TextBox {
// NewTextbox creates a new instance of a text box
func (ui *UIManager) NewTextbox() *TextBox {
animation, _ := d2asset.LoadAnimation(d2resource.TextBox2, d2resource.PaletteUnits)
bgSprite, _ := LoadSprite(animation)
tb := TextBox{
bgSprite, _ := ui.NewSprite(animation)
tb := &TextBox{
filter: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
bgSprite: bgSprite,
textLabel: CreateLabel(d2resource.FontFormal11, d2resource.PaletteUnits),
lineBar: CreateLabel(d2resource.FontFormal11, d2resource.PaletteUnits),
textLabel: ui.NewLabel(d2resource.FontFormal11, d2resource.PaletteUnits),
lineBar: ui.NewLabel(d2resource.FontFormal11, d2resource.PaletteUnits),
enabled: true,
visible: true,
}
tb.lineBar.SetText("_")
ui.addWidget(tb)
return tb
}
@ -65,6 +68,11 @@ func (v *TextBox) Render(target d2interface.Surface) error {
return nil
}
// bindManager binds the textbox to the UI manager
func (v *TextBox) bindManager(manager *UIManager) {
v.manager = manager
}
// OnKeyChars handles key character events
func (v *TextBox) OnKeyChars(event d2interface.KeyCharsEvent) bool {
if !v.isFocused || !v.visible || !v.enabled {
@ -115,13 +123,11 @@ func debounceEvents(numFrames int) bool {
}
// Advance updates the text box
func (v *TextBox) Advance(_ float64) {
if !v.visible || !v.enabled {
return
}
func (v *TextBox) Advance(_ float64) error {
return nil
}
// Update updates the textbox
// Update updates the textbox (not currently implemented)
func (v *TextBox) Update() {
}

140
d2core/d2ui/ui_manager.go Normal file
View File

@ -0,0 +1,140 @@
package d2ui
import (
"log"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
)
// UIManager manages a collection of UI elements (buttons, textboxes, labels)
type UIManager struct {
renderer d2interface.Renderer
inputManager d2interface.InputManager
audio d2interface.AudioProvider
widgets []Widget
cursorButtons CursorButton // TODO (carrelld) convert dependent code and remove
CursorX int // TODO (carrelld) convert dependent code and remove
CursorY int // TODO (carrelld) convert dependent code and remove
pressedWidget Widget
clickSfx d2interface.SoundEffect
}
// Note: methods for creating buttons and stuff are in their respective files
// Initialize is meant to be called after the game loads all of the necessary files
// for sprites and audio
func (ui *UIManager) Initialize() {
sfx, err := ui.audio.LoadSound(d2resource.SFXButtonClick, false, false)
if err != nil {
log.Fatalf("failed to initialize ui: %v", err)
}
ui.clickSfx = sfx
if err := ui.inputManager.BindHandler(ui); err != nil {
log.Fatalf("failed to initialize ui: %v", err)
}
}
// Reset resets the state of the UI manager. Typically called for new screens
func (ui *UIManager) Reset() {
ui.widgets = nil
ui.pressedWidget = nil
}
// addWidget adds a widget to the UI manager
func (ui *UIManager) addWidget(widget Widget) {
_ = ui.inputManager.BindHandler(widget)
ui.widgets = append(ui.widgets, widget)
widget.bindManager(ui)
}
// OnMouseButtonUp is an event handler for input
func (ui *UIManager) OnMouseButtonUp(event d2interface.MouseEvent) bool {
ui.CursorX, ui.CursorY = event.X(), event.Y()
if event.Button() == d2enum.MouseButtonLeft {
ui.cursorButtons |= CursorButtonLeft
// activate previously pressed widget if cursor is still hovering
w := ui.pressedWidget
if w != nil && ui.contains(w, ui.CursorX, ui.CursorY) && w.GetVisible() && w.
GetEnabled() {
w.Activate()
}
// unpress all widgets that are pressed
for _, w := range ui.widgets {
w.SetPressed(false)
}
}
return false
}
// OnMouseButtonDown is the mouse button down event handler
func (ui *UIManager) OnMouseButtonDown(event d2interface.MouseEvent) bool {
ui.CursorX, ui.CursorY = event.X(), event.Y()
if event.Button() == d2enum.MouseButtonLeft {
// find and press a widget on screen
ui.pressedWidget = nil
for _, w := range ui.widgets {
if ui.contains(w, ui.CursorX, ui.CursorY) && w.GetVisible() && w.GetEnabled() {
w.SetPressed(true)
ui.pressedWidget = w
ui.clickSfx.Play()
break
}
}
}
if event.Button() == d2enum.MouseButtonRight {
ui.cursorButtons |= CursorButtonRight
}
return false
}
// Render renders all of the UI elements
func (ui *UIManager) Render(target d2interface.Surface) {
for _, widget := range ui.widgets {
if widget.GetVisible() {
_ = widget.Render(target)
}
}
}
// contains determines whether a given x,y coordinate lands within a Widget
func (ui *UIManager) contains(w Widget, x, y int) bool {
wx, wy := w.GetPosition()
ww, wh := w.GetSize()
return x >= wx && x <= wx+ww && y >= wy && y <= wy+wh
}
// Advance updates all of the UI elements
func (ui *UIManager) Advance(elapsed float64) {
for _, widget := range ui.widgets {
if widget.GetVisible() {
_ = widget.Advance(elapsed)
}
}
}
// CursorButtonPressed determines if the specified button has been pressed
func (ui *UIManager) CursorButtonPressed(button CursorButton) bool {
return ui.cursorButtons&button > 0
}
// CursorPosition returns the current cursor position
func (ui *UIManager) CursorPosition() (x, y int) {
return ui.CursorX, ui.CursorY
}
// Renderer returns the renderer for this ui manager
func (ui *UIManager) Renderer() d2interface.Renderer {
return ui.renderer
}

View File

@ -3,6 +3,7 @@ package d2ui
// Widget defines an object that is a UI widget
type Widget interface {
Drawable
bindManager(ui *UIManager)
GetEnabled() bool
SetEnabled(enabled bool)
SetPressed(pressed bool)

View File

@ -22,21 +22,21 @@ import (
// CharacterSelect represents the character select screen
type CharacterSelect struct {
background *d2ui.Sprite
newCharButton d2ui.Button
convertCharButton d2ui.Button
deleteCharButton d2ui.Button
exitButton d2ui.Button
okButton d2ui.Button
deleteCharCancelButton d2ui.Button
deleteCharOkButton d2ui.Button
newCharButton *d2ui.Button
convertCharButton *d2ui.Button
deleteCharButton *d2ui.Button
exitButton *d2ui.Button
okButton *d2ui.Button
deleteCharCancelButton *d2ui.Button
deleteCharOkButton *d2ui.Button
selectionBox *d2ui.Sprite
okCancelBox *d2ui.Sprite
d2HeroTitle d2ui.Label
deleteCharConfirmLabel d2ui.Label
charScrollbar d2ui.Scrollbar
characterNameLabel [8]d2ui.Label
characterStatsLabel [8]d2ui.Label
characterExpLabel [8]d2ui.Label
d2HeroTitle *d2ui.Label
deleteCharConfirmLabel *d2ui.Label
charScrollbar *d2ui.Scrollbar
characterNameLabel [8]*d2ui.Label
characterStatsLabel [8]*d2ui.Label
characterExpLabel [8]*d2ui.Label
characterImage [8]*d2mapentity.Player
gameStates []*d2player.PlayerState
selectedCharacter int
@ -44,6 +44,7 @@ type CharacterSelect struct {
connectionType d2clientconnectiontype.ClientConnectionType
connectionHost string
uiManager *d2ui.UIManager
inputManager d2interface.InputManager
audioProvider d2interface.AudioProvider
renderer d2interface.Renderer
@ -56,6 +57,7 @@ func CreateCharacterSelect(
renderer d2interface.Renderer,
inputManager d2interface.InputManager,
audioProvider d2interface.AudioProvider,
ui *d2ui.UIManager,
connectionType d2clientconnectiontype.ClientConnectionType,
connectionHost string,
) *CharacterSelect {
@ -67,6 +69,7 @@ func CreateCharacterSelect(
inputManager: inputManager,
audioProvider: audioProvider,
navigator: navigator,
uiManager: ui,
}
}
@ -132,19 +135,19 @@ func (v *CharacterSelect) OnLoad(loading d2screen.LoadingState) {
animation, _ := d2asset.LoadAnimation(d2resource.CharacterSelectionBackground, d2resource.PaletteSky)
bgX, bgY := 0, 0
v.background, _ = d2ui.LoadSprite(animation)
v.background, _ = v.uiManager.NewSprite(animation)
v.background.SetPosition(bgX, bgY)
v.createButtons(loading)
heroTitleX, heroTitleY := 320, 23
v.d2HeroTitle = d2ui.CreateLabel(d2resource.Font42, d2resource.PaletteUnits)
v.d2HeroTitle = v.uiManager.NewLabel(d2resource.Font42, d2resource.PaletteUnits)
v.d2HeroTitle.SetPosition(heroTitleX, heroTitleY)
v.d2HeroTitle.Alignment = d2gui.HorizontalAlignCenter
loading.Progress(thirtyPercent)
v.deleteCharConfirmLabel = d2ui.CreateLabel(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."
v.deleteCharConfirmLabel.SetText(lines)
v.deleteCharConfirmLabel.Alignment = d2gui.HorizontalAlignCenter
@ -152,19 +155,18 @@ func (v *CharacterSelect) OnLoad(loading d2screen.LoadingState) {
v.deleteCharConfirmLabel.SetPosition(deleteConfirmX, deleteConfirmY)
animation, _ = d2asset.LoadAnimation(d2resource.CharacterSelectionSelectBox, d2resource.PaletteSky)
v.selectionBox, _ = d2ui.LoadSprite(animation)
v.selectionBox, _ = v.uiManager.NewSprite(animation)
selBoxX, selBoxY := 37, 86
v.selectionBox.SetPosition(selBoxX, selBoxY)
animation, _ = d2asset.LoadAnimation(d2resource.PopUpOkCancel, d2resource.PaletteFechar)
v.okCancelBox, _ = d2ui.LoadSprite(animation)
v.okCancelBox, _ = v.uiManager.NewSprite(animation)
okCancelX, okCancelY := 270, 175
v.okCancelBox.SetPosition(okCancelX, okCancelY)
scrollBarX, scrollBarY, scrollBarHeight := 586, 87, 369
v.charScrollbar = d2ui.CreateScrollbar(scrollBarX, scrollBarY, scrollBarHeight)
v.charScrollbar = v.uiManager.NewScrollbar(scrollBarX, scrollBarY, scrollBarHeight)
v.charScrollbar.OnActivated(func() { v.onScrollUpdate() })
d2ui.AddWidget(&v.charScrollbar)
loading.Progress(fiftyPercent)
@ -174,16 +176,16 @@ func (v *CharacterSelect) OnLoad(loading d2screen.LoadingState) {
offsetX = 385
}
v.characterNameLabel[i] = d2ui.CreateLabel(d2resource.Font16, d2resource.PaletteUnits)
v.characterNameLabel[i] = v.uiManager.NewLabel(d2resource.Font16, d2resource.PaletteUnits)
v.characterNameLabel[i].SetPosition(offsetX, offsetY)
v.characterNameLabel[i].Color[0] = rgbaColor(lightBrown)
offsetY += labelHeight
v.characterStatsLabel[i] = d2ui.CreateLabel(d2resource.Font16, d2resource.PaletteUnits)
v.characterStatsLabel[i] = v.uiManager.NewLabel(d2resource.Font16, d2resource.PaletteUnits)
v.characterStatsLabel[i].SetPosition(offsetX, offsetY)
offsetY += labelHeight
v.characterExpLabel[i] = d2ui.CreateLabel(d2resource.Font16, d2resource.PaletteStatic)
v.characterExpLabel[i] = v.uiManager.NewLabel(d2resource.Font16, d2resource.PaletteStatic)
v.characterExpLabel[i].SetPosition(offsetX, offsetY)
v.characterExpLabel[i].Color[0] = rgbaColor(lightGreen)
}
@ -216,58 +218,39 @@ func rgbaColor(rgba uint32) color.RGBA {
}
func (v *CharacterSelect) createButtons(loading d2screen.LoadingState) {
v.newCharButton = d2ui.CreateButton(
v.renderer,
d2ui.ButtonTypeTall,
"CREATE NEW\nCHARACTER",
)
v.newCharButton = v.uiManager.NewButton(d2ui.ButtonTypeTall, "CREATE NEW\nCHARACTER")
v.newCharButton.SetPosition(newCharBtnX, newCharBtnY)
v.newCharButton.OnActivated(func() { v.onNewCharButtonClicked() })
d2ui.AddWidget(&v.newCharButton)
v.convertCharButton = d2ui.CreateButton(
v.renderer,
d2ui.ButtonTypeTall,
"CONVERT TO\nEXPANSION",
)
v.convertCharButton = v.uiManager.NewButton(d2ui.ButtonTypeTall, "CONVERT TO\nEXPANSION")
v.convertCharButton.SetPosition(convertCharBtnX, convertCharBtnY)
v.convertCharButton.SetEnabled(false)
d2ui.AddWidget(&v.convertCharButton)
v.deleteCharButton = d2ui.CreateButton(
v.renderer,
d2ui.ButtonTypeTall,
"DELETE\nCHARACTER",
)
v.deleteCharButton = v.uiManager.NewButton(d2ui.ButtonTypeTall, "DELETE\nCHARACTER")
v.deleteCharButton.OnActivated(func() { v.onDeleteCharButtonClicked() })
v.deleteCharButton.SetPosition(deleteCharBtnX, deleteCharBtnY)
d2ui.AddWidget(&v.deleteCharButton)
v.exitButton = d2ui.CreateButton(v.renderer, d2ui.ButtonTypeMedium, "EXIT")
v.exitButton = v.uiManager.NewButton(d2ui.ButtonTypeMedium, "EXIT")
v.exitButton.SetPosition(exitBtnX, exitBtnY)
v.exitButton.OnActivated(func() { v.onExitButtonClicked() })
d2ui.AddWidget(&v.exitButton)
loading.Progress(twentyPercent)
v.deleteCharCancelButton = d2ui.CreateButton(v.renderer, d2ui.ButtonTypeOkCancel, "NO")
v.deleteCharCancelButton = v.uiManager.NewButton(d2ui.ButtonTypeOkCancel, "NO")
v.deleteCharCancelButton.SetPosition(deleteCancelX, deleteCancelY)
v.deleteCharCancelButton.SetVisible(false)
v.deleteCharCancelButton.OnActivated(func() { v.onDeleteCharacterCancelClicked() })
d2ui.AddWidget(&v.deleteCharCancelButton)
v.deleteCharOkButton = d2ui.CreateButton(v.renderer, d2ui.ButtonTypeOkCancel, "YES")
v.deleteCharOkButton = v.uiManager.NewButton(d2ui.ButtonTypeOkCancel, "YES")
v.deleteCharOkButton.SetPosition(deleteOkX, deleteOkY)
v.deleteCharOkButton.SetVisible(false)
v.deleteCharOkButton.OnActivated(func() { v.onDeleteCharacterConfirmClicked() })
d2ui.AddWidget(&v.deleteCharOkButton)
v.okButton = d2ui.CreateButton(v.renderer, d2ui.ButtonTypeMedium, "OK")
v.okButton = v.uiManager.NewButton(d2ui.ButtonTypeMedium, "OK")
v.okButton.SetPosition(okBtnX, okBtnY)
v.okButton.OnActivated(func() { v.onOkButtonClicked() })
d2ui.AddWidget(&v.okButton)
}
func (v *CharacterSelect) onScrollUpdate() {

View File

@ -17,14 +17,14 @@ import (
)
const (
creditsX, creditsY = 0, 0
creditsX, creditsY = 0, 0
charSelExitBtnX, charSelExitBtnY = 33, 543
)
const secondsPerCycle float64 = 0.02
type labelItem struct {
Label d2ui.Label
Label *d2ui.Label
IsHeading bool
Available bool
}
@ -32,7 +32,7 @@ type labelItem struct {
// Credits represents the credits screen
type Credits struct {
creditsBackground *d2ui.Sprite
exitButton d2ui.Button
exitButton *d2ui.Button
creditsText []string
labels []*labelItem
cycleTime float64
@ -41,10 +41,11 @@ type Credits struct {
renderer d2interface.Renderer
navigator Navigator
uiManager *d2ui.UIManager
}
// CreateCredits creates an instance of the credits screen
func CreateCredits(navigator Navigator, renderer d2interface.Renderer) *Credits {
func CreateCredits(navigator Navigator, renderer d2interface.Renderer, ui *d2ui.UIManager) *Credits {
result := &Credits{
labels: make([]*labelItem, 0),
cycleTime: 0,
@ -52,6 +53,7 @@ func CreateCredits(navigator Navigator, renderer d2interface.Renderer) *Credits
cyclesTillNextLine: 0,
renderer: renderer,
navigator: navigator,
uiManager: ui,
}
return result
@ -85,14 +87,13 @@ func (v *Credits) LoadContributors() []string {
// OnLoad is called to load the resources for the credits screen
func (v *Credits) OnLoad(loading d2screen.LoadingState) {
animation, _ := d2asset.LoadAnimation(d2resource.CreditsBackground, d2resource.PaletteSky)
v.creditsBackground, _ = d2ui.LoadSprite(animation)
v.creditsBackground, _ = v.uiManager.NewSprite(animation)
v.creditsBackground.SetPosition(creditsX, creditsY)
loading.Progress(twentyPercent)
v.exitButton = d2ui.CreateButton(v.renderer, d2ui.ButtonTypeMedium, "EXIT")
v.exitButton = v.uiManager.NewButton(d2ui.ButtonTypeMedium, "EXIT")
v.exitButton.SetPosition(charSelExitBtnX, charSelExitBtnY)
v.exitButton.OnActivated(func() { v.onExitButtonClicked() })
d2ui.AddWidget(&v.exitButton)
loading.Progress(fourtyPercent)
fileData, err := d2asset.LoadFile(d2resource.CreditsText)
@ -214,17 +215,17 @@ func (v *Credits) addNextItem() {
}
}
const(
itemLabelY = 605
itemLabelX = 400
itemLabel2offsetX = 10
halfItemLabel2offsetX = itemLabel2offsetX/2
const (
itemLabelY = 605
itemLabelX = 400
itemLabel2offsetX = 10
halfItemLabel2offsetX = itemLabel2offsetX / 2
)
func (v *Credits) setItemLabelPosition(label *d2ui.Label, isHeading, isNextHeading, isNextSpace bool) (isDoubled, nextHeading bool) {
width, _ := label.GetSize()
half := 2
halfWidth := width/half
halfWidth := width / half
if !isHeading && !isNextHeading && !isNextSpace {
isDoubled = true
@ -250,7 +251,7 @@ func (v *Credits) setItemLabelPosition(label *d2ui.Label, isHeading, isNextHeadi
const (
lightRed = 0xff5852ff
beige = 0xc6b296ff
beige = 0xc6b296ff
)
func (v *Credits) getNewFontLabel(isHeading bool) *d2ui.Label {
@ -263,14 +264,14 @@ func (v *Credits) getNewFontLabel(isHeading bool) *d2ui.Label {
label.Label.Color[0] = rgbaColor(beige)
}
return &label.Label
return label.Label
}
}
newLabelItem := &labelItem{
Available: false,
IsHeading: isHeading,
Label: d2ui.CreateLabel(d2resource.FontFormal10, d2resource.PaletteSky),
Label: v.uiManager.NewLabel(d2resource.FontFormal10, d2resource.PaletteSky),
}
if isHeading {
@ -281,5 +282,5 @@ func (v *Credits) getNewFontLabel(isHeading bool) *d2ui.Label {
v.labels = append(v.labels, newLabelItem)
return &newLabelItem.Label
return newLabelItem.Label
}

View File

@ -3,6 +3,7 @@ package d2gamescreen
import (
"fmt"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2math/d2vector"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2ui"
"image/color"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict"
@ -30,6 +31,7 @@ const (
type Game struct {
gameClient *d2client.GameClient
mapRenderer *d2maprenderer.MapRenderer
uiManager *d2ui.UIManager
gameControls *d2player.GameControls // TODO: Hack
localPlayer *d2mapentity.Player
lastRegionType d2enum.RegionIdType
@ -77,6 +79,7 @@ func CreateGame(
renderer: renderer,
terminal: term,
soundEngine: d2audio.NewSoundEngine(audioProvider, term),
uiManager: d2ui.NewUIManager(renderer, inputManager, audioProvider),
}
result.soundEnv = d2audio.NewSoundEnvironment(result.soundEngine)
@ -244,7 +247,8 @@ func (v *Game) bindGameControls() error {
v.localPlayer = player
var err error
v.gameControls, err = d2player.NewGameControls(v.renderer, player, v.gameClient.MapEngine, v.mapRenderer, v, v.terminal)
v.gameControls, err = d2player.NewGameControls(v.renderer, player,
v.gameClient.MapEngine, v.mapRenderer, v, v.terminal, v.uiManager)
if err != nil {
return err

View File

@ -86,28 +86,28 @@ type MainMenu struct {
diabloLogoLeftBack *d2ui.Sprite
diabloLogoRightBack *d2ui.Sprite
serverIPBackground *d2ui.Sprite
singlePlayerButton d2ui.Button
multiplayerButton d2ui.Button
githubButton d2ui.Button
exitDiabloButton d2ui.Button
creditsButton d2ui.Button
cinematicsButton d2ui.Button
mapTestButton d2ui.Button
networkTCPIPButton d2ui.Button
networkCancelButton d2ui.Button
btnTCPIPCancel d2ui.Button
btnTCPIPHostGame d2ui.Button
btnTCPIPJoinGame d2ui.Button
btnServerIPCancel d2ui.Button
btnServerIPOk d2ui.Button
copyrightLabel d2ui.Label
copyrightLabel2 d2ui.Label
openDiabloLabel d2ui.Label
versionLabel d2ui.Label
commitLabel d2ui.Label
tcpIPOptionsLabel d2ui.Label
tcpJoinGameLabel d2ui.Label
tcpJoinGameEntry d2ui.TextBox
singlePlayerButton *d2ui.Button
multiplayerButton *d2ui.Button
githubButton *d2ui.Button
exitDiabloButton *d2ui.Button
creditsButton *d2ui.Button
cinematicsButton *d2ui.Button
mapTestButton *d2ui.Button
networkTCPIPButton *d2ui.Button
networkCancelButton *d2ui.Button
btnTCPIPCancel *d2ui.Button
btnTCPIPHostGame *d2ui.Button
btnTCPIPJoinGame *d2ui.Button
btnServerIPCancel *d2ui.Button
btnServerIPOk *d2ui.Button
copyrightLabel *d2ui.Label
copyrightLabel2 *d2ui.Label
openDiabloLabel *d2ui.Label
versionLabel *d2ui.Label
commitLabel *d2ui.Label
tcpIPOptionsLabel *d2ui.Label
tcpJoinGameLabel *d2ui.Label
tcpJoinGameEntry *d2ui.TextBox
screenMode mainMenuScreenMode
leftButtonHeld bool
@ -116,6 +116,7 @@ type MainMenu struct {
audioProvider d2interface.AudioProvider
scriptEngine *d2script.ScriptEngine
navigator Navigator
uiManager *d2ui.UIManager
buildInfo BuildInfo
}
@ -126,6 +127,7 @@ func CreateMainMenu(
renderer d2interface.Renderer,
inputManager d2interface.InputManager,
audioProvider d2interface.AudioProvider,
ui *d2ui.UIManager,
buildInfo BuildInfo,
) *MainMenu {
return &MainMenu{
@ -135,7 +137,8 @@ func CreateMainMenu(
inputManager: inputManager,
audioProvider: audioProvider,
navigator: navigator,
buildInfo: buildInfo,
buildInfo: buildInfo,
uiManager: ui,
}
}
@ -149,10 +152,9 @@ func (v *MainMenu) OnLoad(loading d2screen.LoadingState) {
v.createLogos(loading)
v.createButtons(loading)
v.tcpJoinGameEntry = d2ui.CreateTextbox()
v.tcpJoinGameEntry = v.uiManager.NewTextbox()
v.tcpJoinGameEntry.SetPosition(joinGameDialogX, joinGameDialogY)
v.tcpJoinGameEntry.SetFilter(joinGameCharacterFilter)
d2ui.AddWidget(&v.tcpJoinGameEntry)
loading.Progress(ninetyPercent)
if v.screenMode == ScreenModeUnknown {
@ -168,61 +170,61 @@ func (v *MainMenu) OnLoad(loading d2screen.LoadingState) {
func (v *MainMenu) loadBackgroundSprites() {
animation, _ := d2asset.LoadAnimation(d2resource.GameSelectScreen, d2resource.PaletteSky)
v.background, _ = d2ui.LoadSprite(animation)
v.background, _ = v.uiManager.NewSprite(animation)
v.background.SetPosition(backgroundX, backgroundY)
animation, _ = d2asset.LoadAnimation(d2resource.TrademarkScreen, d2resource.PaletteSky)
v.trademarkBackground, _ = d2ui.LoadSprite(animation)
v.trademarkBackground, _ = v.uiManager.NewSprite(animation)
v.trademarkBackground.SetPosition(backgroundX, backgroundY)
animation, _ = d2asset.LoadAnimation(d2resource.TCPIPBackground, d2resource.PaletteSky)
v.tcpIPBackground, _ = d2ui.LoadSprite(animation)
v.tcpIPBackground, _ = v.uiManager.NewSprite(animation)
v.tcpIPBackground.SetPosition(backgroundX, backgroundY)
animation, _ = d2asset.LoadAnimation(d2resource.PopUpOkCancel, d2resource.PaletteFechar)
v.serverIPBackground, _ = d2ui.LoadSprite(animation)
v.serverIPBackground, _ = v.uiManager.NewSprite(animation)
v.serverIPBackground.SetPosition(serverIPbackgroundX, serverIPbackgroundY)
}
func (v *MainMenu) createLabels(loading d2screen.LoadingState) {
v.versionLabel = d2ui.CreateLabel(d2resource.FontFormal12, d2resource.PaletteStatic)
v.versionLabel = v.uiManager.NewLabel(d2resource.FontFormal12, d2resource.PaletteStatic)
v.versionLabel.Alignment = d2gui.HorizontalAlignRight
v.versionLabel.SetText("OpenDiablo2 - " + v.buildInfo.Branch)
v.versionLabel.Color[0] = rgbaColor(white)
v.versionLabel.SetPosition(versionLabelX, versionLabelY)
v.commitLabel = d2ui.CreateLabel(d2resource.FontFormal10, d2resource.PaletteStatic)
v.commitLabel = v.uiManager.NewLabel(d2resource.FontFormal10, d2resource.PaletteStatic)
v.commitLabel.Alignment = d2gui.HorizontalAlignLeft
v.commitLabel.SetText(v.buildInfo.Commit)
v.commitLabel.Color[0] = rgbaColor(white)
v.commitLabel.SetPosition(commitLabelX, commitLabelY)
v.copyrightLabel = d2ui.CreateLabel(d2resource.FontFormal12, d2resource.PaletteStatic)
v.copyrightLabel = v.uiManager.NewLabel(d2resource.FontFormal12, d2resource.PaletteStatic)
v.copyrightLabel.Alignment = d2gui.HorizontalAlignCenter
v.copyrightLabel.SetText("Diablo 2 is © Copyright 2000-2016 Blizzard Entertainment")
v.copyrightLabel.Color[0] = rgbaColor(lightBrown)
v.copyrightLabel.SetPosition(copyrightX, copyrightY)
loading.Progress(thirtyPercent)
v.copyrightLabel2 = d2ui.CreateLabel(d2resource.FontFormal12, d2resource.PaletteStatic)
v.copyrightLabel2 = v.uiManager.NewLabel(d2resource.FontFormal12, d2resource.PaletteStatic)
v.copyrightLabel2.Alignment = d2gui.HorizontalAlignCenter
v.copyrightLabel2.SetText("All Rights Reserved.")
v.copyrightLabel2.Color[0] = rgbaColor(lightBrown)
v.copyrightLabel2.SetPosition(copyright2X, copyright2Y)
v.openDiabloLabel = d2ui.CreateLabel(d2resource.FontFormal10, d2resource.PaletteStatic)
v.openDiabloLabel = v.uiManager.NewLabel(d2resource.FontFormal10, d2resource.PaletteStatic)
v.openDiabloLabel.Alignment = d2gui.HorizontalAlignCenter
v.openDiabloLabel.SetText("OpenDiablo2 is neither developed by, nor endorsed by Blizzard or its parent company Activision")
v.openDiabloLabel.Color[0] = rgbaColor(lightYellow)
v.openDiabloLabel.SetPosition(od2LabelX, od2LabelY)
loading.Progress(fiftyPercent)
v.tcpIPOptionsLabel = d2ui.CreateLabel(d2resource.Font42, d2resource.PaletteUnits)
v.tcpIPOptionsLabel = v.uiManager.NewLabel(d2resource.Font42, d2resource.PaletteUnits)
v.tcpIPOptionsLabel.SetPosition(tcpOptionsX, tcpOptionsY)
v.tcpIPOptionsLabel.Alignment = d2gui.HorizontalAlignCenter
v.tcpIPOptionsLabel.SetText("TCP/IP Options")
v.tcpJoinGameLabel = d2ui.CreateLabel(d2resource.Font16, d2resource.PaletteUnits)
v.tcpJoinGameLabel = v.uiManager.NewLabel(d2resource.Font16, d2resource.PaletteUnits)
v.tcpJoinGameLabel.Alignment = d2gui.HorizontalAlignCenter
v.tcpJoinGameLabel.SetText("Enter Host IP Address\nto Join Game")
@ -232,102 +234,90 @@ func (v *MainMenu) createLabels(loading d2screen.LoadingState) {
func (v *MainMenu) createLogos(loading d2screen.LoadingState) {
animation, _ := d2asset.LoadAnimation(d2resource.Diablo2LogoFireLeft, d2resource.PaletteUnits)
v.diabloLogoLeft, _ = d2ui.LoadSprite(animation)
v.diabloLogoLeft, _ = v.uiManager.NewSprite(animation)
v.diabloLogoLeft.SetEffect(d2enum.DrawEffectModulate)
v.diabloLogoLeft.PlayForward()
v.diabloLogoLeft.SetPosition(diabloLogoX, diabloLogoY)
loading.Progress(sixtyPercent)
animation, _ = d2asset.LoadAnimation(d2resource.Diablo2LogoFireRight, d2resource.PaletteUnits)
v.diabloLogoRight, _ = d2ui.LoadSprite(animation)
v.diabloLogoRight, _ = v.uiManager.NewSprite(animation)
v.diabloLogoRight.SetEffect(d2enum.DrawEffectModulate)
v.diabloLogoRight.PlayForward()
v.diabloLogoRight.SetPosition(diabloLogoX, diabloLogoY)
animation, _ = d2asset.LoadAnimation(d2resource.Diablo2LogoBlackLeft, d2resource.PaletteUnits)
v.diabloLogoLeftBack, _ = d2ui.LoadSprite(animation)
v.diabloLogoLeftBack, _ = v.uiManager.NewSprite(animation)
v.diabloLogoLeftBack.SetPosition(diabloLogoX, diabloLogoY)
animation, _ = d2asset.LoadAnimation(d2resource.Diablo2LogoBlackRight, d2resource.PaletteUnits)
v.diabloLogoRightBack, _ = d2ui.LoadSprite(animation)
v.diabloLogoRightBack, _ = v.uiManager.NewSprite(animation)
v.diabloLogoRightBack.SetPosition(diabloLogoX, diabloLogoY)
}
func (v *MainMenu) createButtons(loading d2screen.LoadingState) {
v.exitDiabloButton = d2ui.CreateButton(v.renderer, d2ui.ButtonTypeWide, "EXIT DIABLO II")
v.exitDiabloButton = v.uiManager.NewButton(d2ui.ButtonTypeWide, "EXIT DIABLO II")
v.exitDiabloButton.SetPosition(exitDiabloBtnX, exitDiabloBtnY)
v.exitDiabloButton.OnActivated(func() { v.onExitButtonClicked() })
d2ui.AddWidget(&v.exitDiabloButton)
v.creditsButton = d2ui.CreateButton(v.renderer, d2ui.ButtonTypeShort, "CREDITS")
v.creditsButton = v.uiManager.NewButton(d2ui.ButtonTypeShort, "CREDITS")
v.creditsButton.SetPosition(creditBtnX, creditBtnY)
v.creditsButton.OnActivated(func() { v.onCreditsButtonClicked() })
d2ui.AddWidget(&v.creditsButton)
v.cinematicsButton = d2ui.CreateButton(v.renderer, d2ui.ButtonTypeShort, "CINEMATICS")
v.cinematicsButton = v.uiManager.NewButton(d2ui.ButtonTypeShort, "CINEMATICS")
v.cinematicsButton.SetPosition(cineBtnX, cineBtnY)
d2ui.AddWidget(&v.cinematicsButton)
loading.Progress(seventyPercent)
v.singlePlayerButton = d2ui.CreateButton(v.renderer, d2ui.ButtonTypeWide, "SINGLE PLAYER")
v.singlePlayerButton = v.uiManager.NewButton(d2ui.ButtonTypeWide, "SINGLE PLAYER")
v.singlePlayerButton.SetPosition(singlePlayerBtnX, singlePlayerBtnY)
v.singlePlayerButton.OnActivated(func() { v.onSinglePlayerClicked() })
d2ui.AddWidget(&v.singlePlayerButton)
v.githubButton = d2ui.CreateButton(v.renderer, d2ui.ButtonTypeWide, "PROJECT WEBSITE")
v.githubButton = v.uiManager.NewButton(d2ui.ButtonTypeWide, "PROJECT WEBSITE")
v.githubButton.SetPosition(githubBtnX, githubBtnY)
v.githubButton.OnActivated(func() { v.onGithubButtonClicked() })
d2ui.AddWidget(&v.githubButton)
v.mapTestButton = d2ui.CreateButton(v.renderer, d2ui.ButtonTypeWide, "MAP ENGINE TEST")
v.mapTestButton = v.uiManager.NewButton(d2ui.ButtonTypeWide, "MAP ENGINE TEST")
v.mapTestButton.SetPosition(mapTestBtnX, mapTestBtnY)
v.mapTestButton.OnActivated(func() { v.onMapTestClicked() })
d2ui.AddWidget(&v.mapTestButton)
v.btnTCPIPCancel = d2ui.CreateButton(v.renderer, d2ui.ButtonTypeMedium, d2common.TranslateString("cancel"))
v.btnTCPIPCancel = v.uiManager.NewButton(d2ui.ButtonTypeMedium,
d2common.TranslateString("cancel"))
v.btnTCPIPCancel.SetPosition(tcpBtnX, tcpBtnY)
v.btnTCPIPCancel.OnActivated(func() { v.onTCPIPCancelClicked() })
d2ui.AddWidget(&v.btnTCPIPCancel)
v.btnServerIPCancel = d2ui.CreateButton(v.renderer, d2ui.ButtonTypeOkCancel, "CANCEL")
v.btnServerIPCancel = v.uiManager.NewButton(d2ui.ButtonTypeOkCancel, "CANCEL")
v.btnServerIPCancel.SetPosition(srvCancelBtnX, srvCancelBtnY)
v.btnServerIPCancel.OnActivated(func() { v.onBtnTCPIPCancelClicked() })
d2ui.AddWidget(&v.btnServerIPCancel)
v.btnServerIPOk = d2ui.CreateButton(v.renderer, d2ui.ButtonTypeOkCancel, "OK")
v.btnServerIPOk = v.uiManager.NewButton(d2ui.ButtonTypeOkCancel, "OK")
v.btnServerIPOk.SetPosition(srvOkBtnX, srvOkBtnY)
v.btnServerIPOk.OnActivated(func() { v.onBtnTCPIPOkClicked() })
d2ui.AddWidget(&v.btnServerIPOk)
v.createMultiplayerMenuButtons()
loading.Progress(eightyPercent)
}
func (v *MainMenu) createMultiplayerMenuButtons() {
v.multiplayerButton = d2ui.CreateButton(v.renderer, d2ui.ButtonTypeWide, "MULTIPLAYER")
v.multiplayerButton = v.uiManager.NewButton(d2ui.ButtonTypeWide, "MULTIPLAYER")
v.multiplayerButton.SetPosition(multiplayerBtnX, multiplayerBtnY)
v.multiplayerButton.OnActivated(func() { v.onMultiplayerClicked() })
d2ui.AddWidget(&v.multiplayerButton)
v.networkTCPIPButton = d2ui.CreateButton(v.renderer, d2ui.ButtonTypeWide, "TCP/IP GAME")
v.networkTCPIPButton = v.uiManager.NewButton(d2ui.ButtonTypeWide, "TCP/IP GAME")
v.networkTCPIPButton.SetPosition(tcpNetBtnX, tcpNetBtnY)
v.networkTCPIPButton.OnActivated(func() { v.onNetworkTCPIPClicked() })
d2ui.AddWidget(&v.networkTCPIPButton)
v.networkCancelButton = d2ui.CreateButton(v.renderer, d2ui.ButtonTypeWide, d2common.TranslateString("cancel"))
v.networkCancelButton = v.uiManager.NewButton(d2ui.ButtonTypeWide,
d2common.TranslateString("cancel"))
v.networkCancelButton.SetPosition(networkCancelBtnX, networkCancelBtnY)
v.networkCancelButton.OnActivated(func() { v.onNetworkCancelClicked() })
d2ui.AddWidget(&v.networkCancelButton)
v.btnTCPIPHostGame = d2ui.CreateButton(v.renderer, d2ui.ButtonTypeWide, "HOST GAME")
v.btnTCPIPHostGame = v.uiManager.NewButton(d2ui.ButtonTypeWide, "HOST GAME")
v.btnTCPIPHostGame.SetPosition(tcpHostBtnX, tcpHostBtnY)
v.btnTCPIPHostGame.OnActivated(func() { v.onTCPIPHostGameClicked() })
d2ui.AddWidget(&v.btnTCPIPHostGame)
v.btnTCPIPJoinGame = d2ui.CreateButton(v.renderer, d2ui.ButtonTypeWide, "JOIN GAME")
v.btnTCPIPJoinGame = v.uiManager.NewButton(d2ui.ButtonTypeWide, "JOIN GAME")
v.btnTCPIPJoinGame.SetPosition(tcpJoinBtnX, tcpJoinBtnY)
v.btnTCPIPJoinGame.OnActivated(func() { v.onTCPIPJoinGameClicked() })
d2ui.AddWidget(&v.btnTCPIPJoinGame)
}
func (v *MainMenu) onMapTestClicked() {

View File

@ -267,23 +267,24 @@ func (hri *HeroRenderInfo) advance(elapsed float64) {
// SelectHeroClass represents the Select Hero Class screen
type SelectHeroClass struct {
uiManager *d2ui.UIManager
bgImage *d2ui.Sprite
campfire *d2ui.Sprite
headingLabel d2ui.Label
heroClassLabel d2ui.Label
heroDesc1Label d2ui.Label
heroDesc2Label d2ui.Label
heroDesc3Label d2ui.Label
heroNameTextbox d2ui.TextBox
heroNameLabel d2ui.Label
headingLabel *d2ui.Label
heroClassLabel *d2ui.Label
heroDesc1Label *d2ui.Label
heroDesc2Label *d2ui.Label
heroDesc3Label *d2ui.Label
heroNameTextbox *d2ui.TextBox
heroNameLabel *d2ui.Label
heroRenderInfo map[d2enum.Hero]*HeroRenderInfo
selectedHero d2enum.Hero
exitButton d2ui.Button
okButton d2ui.Button
expansionCheckbox d2ui.Checkbox
expansionCharLabel d2ui.Label
hardcoreCheckbox d2ui.Checkbox
hardcoreCharLabel d2ui.Label
exitButton *d2ui.Button
okButton *d2ui.Button
expansionCheckbox *d2ui.Checkbox
expansionCharLabel *d2ui.Label
hardcoreCheckbox *d2ui.Checkbox
hardcoreCharLabel *d2ui.Label
connectionType d2clientconnectiontype.ClientConnectionType
connectionHost string
@ -297,6 +298,7 @@ func CreateSelectHeroClass(
navigator Navigator,
renderer d2interface.Renderer,
audioProvider d2interface.AudioProvider,
ui *d2ui.UIManager,
connectionType d2clientconnectiontype.ClientConnectionType,
connectionHost string,
) *SelectHeroClass {
@ -308,6 +310,7 @@ func CreateSelectHeroClass(
audioProvider: audioProvider,
renderer: renderer,
navigator: navigator,
uiManager: ui,
}
return result
@ -318,7 +321,7 @@ func (v *SelectHeroClass) OnLoad(loading d2screen.LoadingState) {
v.audioProvider.PlayBGM(d2resource.BGMTitle)
loading.Progress(tenPercent)
v.bgImage = loadSprite(
v.bgImage = v.loadSprite(
d2resource.CharacterSelectBackground,
point(0, 0),
0,
@ -332,7 +335,7 @@ func (v *SelectHeroClass) OnLoad(loading d2screen.LoadingState) {
loading.Progress(fourtyPercent)
v.createButtons()
v.campfire = loadSprite(
v.campfire = v.loadSprite(
d2resource.CharacterSelectCampfire,
point(campfirePosX, campfirePosY),
0,
@ -340,12 +343,12 @@ func (v *SelectHeroClass) OnLoad(loading d2screen.LoadingState) {
true,
)
v.createCheckboxes(v.renderer)
v.createCheckboxes()
loading.Progress(fiftyPercent)
for hero, config := range getHeroRenderConfiguration() {
position := config.position
forwardWalkOverlaySprite := loadSprite(
forwardWalkOverlaySprite := v.loadSprite(
config.forwardWalkOverlayAnimationPath,
position,
config.forwardWalkPlayLengthMs,
@ -353,24 +356,32 @@ func (v *SelectHeroClass) OnLoad(loading d2screen.LoadingState) {
config.forwardWalkOverlayBlend,
)
v.heroRenderInfo[hero] = &HeroRenderInfo{
Stance: d2enum.HeroStanceIdle,
IdleSprite: loadSprite(config.idleAnimationPath, position, config.idlePlayLengthMs, true, false),
IdleSelectedSprite: loadSprite(config.idleSelectedAnimationPath, position, config.idlePlayLengthMs, true, false),
ForwardWalkSprite: loadSprite(config.forwardWalkAnimationPath, position, config.forwardWalkPlayLengthMs, false, false),
Stance: d2enum.HeroStanceIdle,
IdleSprite: v.loadSprite(config.idleAnimationPath, position,
config.idlePlayLengthMs, true, false),
IdleSelectedSprite: v.loadSprite(config.idleSelectedAnimationPath,
position,
config.idlePlayLengthMs, true, false),
ForwardWalkSprite: v.loadSprite(config.forwardWalkAnimationPath, position,
config.forwardWalkPlayLengthMs, false, false),
ForwardWalkSpriteOverlay: forwardWalkOverlaySprite,
SelectedSprite: loadSprite(config.selectedAnimationPath, position, config.idlePlayLengthMs, true, false),
SelectedSpriteOverlay: loadSprite(config.selectedOverlayAnimationPath, position, config.idlePlayLengthMs, true, true),
BackWalkSprite: loadSprite(config.backWalkAnimationPath, position, config.backWalkPlayLengthMs, false, false),
BackWalkSpriteOverlay: loadSprite(config.backWalkOverlayAnimationPath, position, config.backWalkPlayLengthMs, false, true),
SelectionBounds: config.selectionBounds,
SelectSfx: v.loadSoundEffect(config.selectSfx),
DeselectSfx: v.loadSoundEffect(config.deselectSfx),
SelectedSprite: v.loadSprite(config.selectedAnimationPath, position,
config.idlePlayLengthMs, true, false),
SelectedSpriteOverlay: v.loadSprite(config.selectedOverlayAnimationPath, position,
config.idlePlayLengthMs, true, true),
BackWalkSprite: v.loadSprite(config.backWalkAnimationPath, position,
config.backWalkPlayLengthMs, false, false),
BackWalkSpriteOverlay: v.loadSprite(config.backWalkOverlayAnimationPath, position,
config.backWalkPlayLengthMs, false, true),
SelectionBounds: config.selectionBounds,
SelectSfx: v.loadSoundEffect(config.selectSfx),
DeselectSfx: v.loadSoundEffect(config.deselectSfx),
}
}
}
func (v *SelectHeroClass) createLabels() {
v.headingLabel = d2ui.CreateLabel(d2resource.Font30, d2resource.PaletteUnits)
v.headingLabel = v.uiManager.NewLabel(d2resource.Font30, d2resource.PaletteUnits)
fontWidth, _ := v.headingLabel.GetSize()
half := 2
halfFontWidth := fontWidth / half
@ -379,67 +390,62 @@ func (v *SelectHeroClass) createLabels() {
v.headingLabel.SetText("Select Hero Class")
v.headingLabel.Alignment = d2gui.HorizontalAlignCenter
v.heroClassLabel = d2ui.CreateLabel(d2resource.Font30, d2resource.PaletteUnits)
v.heroClassLabel = v.uiManager.NewLabel(d2resource.Font30, d2resource.PaletteUnits)
v.heroClassLabel.Alignment = d2gui.HorizontalAlignCenter
v.heroClassLabel.SetPosition(heroClassLabelX, heroClassLabelY)
v.heroDesc1Label = d2ui.CreateLabel(d2resource.Font16, d2resource.PaletteUnits)
v.heroDesc1Label = v.uiManager.NewLabel(d2resource.Font16, d2resource.PaletteUnits)
v.heroDesc1Label.Alignment = d2gui.HorizontalAlignCenter
v.heroDesc1Label.SetPosition(heroDescLine1X, heroDescLine1Y)
v.heroDesc2Label = d2ui.CreateLabel(d2resource.Font16, d2resource.PaletteUnits)
v.heroDesc2Label = v.uiManager.NewLabel(d2resource.Font16, d2resource.PaletteUnits)
v.heroDesc2Label.Alignment = d2gui.HorizontalAlignCenter
v.heroDesc2Label.SetPosition(heroDescLine2X, heroDescLine2Y)
v.heroDesc3Label = d2ui.CreateLabel(d2resource.Font16, d2resource.PaletteUnits)
v.heroDesc3Label = v.uiManager.NewLabel(d2resource.Font16, d2resource.PaletteUnits)
v.heroDesc3Label.Alignment = d2gui.HorizontalAlignCenter
v.heroDesc3Label.SetPosition(heroDescLine3X, heroDescLine3Y)
v.heroNameLabel = d2ui.CreateLabel(d2resource.Font16, d2resource.PaletteUnits)
v.heroNameLabel = v.uiManager.NewLabel(d2resource.Font16, d2resource.PaletteUnits)
v.heroNameLabel.Alignment = d2gui.HorizontalAlignLeft
v.heroNameLabel.SetText(d2ui.ColorTokenize("Character Name", d2ui.ColorTokenGold))
v.heroNameLabel.SetPosition(heroNameLabelX, heroNameLabelY)
v.expansionCharLabel = d2ui.CreateLabel(d2resource.Font16, d2resource.PaletteUnits)
v.expansionCharLabel = v.uiManager.NewLabel(d2resource.Font16, d2resource.PaletteUnits)
v.expansionCharLabel.Alignment = d2gui.HorizontalAlignLeft
v.expansionCharLabel.SetText(d2ui.ColorTokenize("EXPANSION CHARACTER", d2ui.ColorTokenGold))
v.expansionCharLabel.SetPosition(expansionLabelX, expansionLabelY)
v.hardcoreCharLabel = d2ui.CreateLabel(d2resource.Font16, d2resource.PaletteUnits)
v.hardcoreCharLabel = v.uiManager.NewLabel(d2resource.Font16, d2resource.PaletteUnits)
v.hardcoreCharLabel.Alignment = d2gui.HorizontalAlignLeft
v.hardcoreCharLabel.SetText(d2ui.ColorTokenize("Hardcore", d2ui.ColorTokenGold))
v.hardcoreCharLabel.SetPosition(hardcoreLabelX, hardcoreLabelY)
}
func (v *SelectHeroClass) createButtons() {
v.exitButton = d2ui.CreateButton(v.renderer, d2ui.ButtonTypeMedium, "EXIT")
v.exitButton = v.uiManager.NewButton(d2ui.ButtonTypeMedium, "EXIT")
v.exitButton.SetPosition(selHeroExitBtnX, selHeroExitBtnY)
v.exitButton.OnActivated(func() { v.onExitButtonClicked() })
d2ui.AddWidget(&v.exitButton)
v.okButton = d2ui.CreateButton(v.renderer, d2ui.ButtonTypeMedium, "OK")
v.okButton = v.uiManager.NewButton(d2ui.ButtonTypeMedium, "OK")
v.okButton.SetPosition(selHeroOkBtnX, selHeroOkBtnY)
v.okButton.OnActivated(func() { v.onOkButtonClicked() })
v.okButton.SetVisible(false)
v.okButton.SetEnabled(false)
d2ui.AddWidget(&v.okButton)
}
func (v *SelectHeroClass) createCheckboxes(renderer d2interface.Renderer) {
v.heroNameTextbox = d2ui.CreateTextbox()
func (v *SelectHeroClass) createCheckboxes() {
v.heroNameTextbox = v.uiManager.NewTextbox()
v.heroNameTextbox.SetPosition(heroNameTextBoxX, heoNameTextBoxY)
v.heroNameTextbox.SetVisible(false)
d2ui.AddWidget(&v.heroNameTextbox)
v.expansionCheckbox = d2ui.CreateCheckbox(v.renderer, true)
v.expansionCheckbox = v.uiManager.NewCheckbox(true)
v.expansionCheckbox.SetPosition(expandsionCheckboxX, expansionCheckboxY)
v.expansionCheckbox.SetVisible(false)
d2ui.AddWidget(&v.expansionCheckbox)
v.hardcoreCheckbox = d2ui.CreateCheckbox(renderer, false)
v.hardcoreCheckbox = v.uiManager.NewCheckbox(false)
v.hardcoreCheckbox.SetPosition(hardcoreCheckoxX, hardcoreCheckboxY)
v.hardcoreCheckbox.SetVisible(false)
d2ui.AddWidget(&v.hardcoreCheckbox)
}
// OnUnload releases the resources of the Select Hero Class screen
@ -559,11 +565,11 @@ func (v *SelectHeroClass) updateHeroSelectionHover(hero d2enum.Hero, canSelect b
return
}
mouseX, mouseY := d2ui.CursorPosition()
mouseX, mouseY := v.uiManager.CursorPosition()
b := renderInfo.SelectionBounds
mouseHover := (mouseX >= b.Min.X) && (mouseX <= b.Min.X+b.Max.X) && (mouseY >= b.Min.Y) && (mouseY <= b.Min.Y+b.Max.Y)
if mouseHover && d2ui.CursorButtonPressed(d2ui.CursorButtonLeft) {
if mouseHover && v.uiManager.CursorButtonPressed(d2ui.CursorButtonLeft) {
v.handleCursorButtonPress(hero, renderInfo)
return
}
@ -717,7 +723,9 @@ func advanceSprite(sprite *d2ui.Sprite, elapsed float64) {
}
}
func loadSprite(animationPath string, position image.Point, playLength int, playLoop,
func (v *SelectHeroClass) loadSprite(animationPath string, position image.Point,
playLength int,
playLoop,
blend bool) *d2ui.Sprite {
if animationPath == "" {
return nil
@ -740,7 +748,7 @@ func loadSprite(animationPath string, position image.Point, playLength int, play
animation.SetPlayLengthMs(playLength)
}
sprite, err := d2ui.LoadSprite(animation)
sprite, err := v.uiManager.NewSprite(animation)
if err != nil {
fmt.Printf("could not load sprite for the animation: %s\n", animationPath)
return nil

View File

@ -46,6 +46,7 @@ type GameControls struct {
hero *d2mapentity.Player
mapEngine *d2mapengine.MapEngine
mapRenderer *d2maprenderer.MapRenderer
uiManager *d2ui.UIManager
inventory *Inventory
heroStatsPanel *HeroStatsPanel
inputListener InputCallbackListener
@ -62,7 +63,7 @@ type GameControls struct {
skillIcon *d2ui.Sprite
zoneChangeText *d2ui.Label
nameLabel *d2ui.Label
runButton d2ui.Button
runButton *d2ui.Button
isZoneTextShown bool
actionableRegions []ActionableRegion
}
@ -86,17 +87,24 @@ const (
rightSkill = ActionableType(iota)
)
func NewGameControls(renderer d2interface.Renderer, hero *d2mapentity.Player, mapEngine *d2mapengine.MapEngine,
mapRenderer *d2maprenderer.MapRenderer, inputListener InputCallbackListener, term d2interface.Terminal) (*GameControls, error) {
func NewGameControls(
renderer d2interface.Renderer,
hero *d2mapentity.Player,
mapEngine *d2mapengine.MapEngine,
mapRenderer *d2maprenderer.MapRenderer,
inputListener InputCallbackListener,
term d2interface.Terminal,
ui *d2ui.UIManager,
) (*GameControls, error) {
missileID := initialMissileID
term.BindAction("setmissile", "set missile id to summon on right click", func(id int) {
missileID = id
})
zoneLabel := d2ui.CreateLabel(d2resource.Font30, d2resource.PaletteUnits)
zoneLabel := ui.NewLabel(d2resource.Font30, d2resource.PaletteUnits)
zoneLabel.Alignment = d2gui.HorizontalAlignCenter
nameLabel := d2ui.CreateLabel(d2resource.FontFormal11, d2resource.PaletteStatic)
nameLabel := ui.NewLabel(d2resource.FontFormal11, d2resource.PaletteStatic)
nameLabel.Alignment = d2gui.HorizontalAlignCenter
nameLabel.SetText(d2ui.ColorTokenize("", d2ui.ColorTokenServer))
@ -124,20 +132,21 @@ func NewGameControls(renderer d2interface.Renderer, hero *d2mapentity.Player, ma
inventoryRecord := d2datadict.Inventory[inventoryRecordKey]
hoverLabel := &nameLabel
hoverLabel.SetBackgroundColor(color.RGBA{0,0,0, uint8(128)})
hoverLabel := nameLabel
hoverLabel.SetBackgroundColor(color.RGBA{0, 0, 0, uint8(128)})
gc := &GameControls{
uiManager: ui,
renderer: renderer,
hero: hero,
mapEngine: mapEngine,
inputListener: inputListener,
mapRenderer: mapRenderer,
inventory: NewInventory(inventoryRecord),
heroStatsPanel: NewHeroStatsPanel(renderer, hero.Name(), hero.Class, hero.Stats),
inventory: NewInventory(ui, inventoryRecord),
heroStatsPanel: NewHeroStatsPanel(ui, hero.Name(), hero.Class, hero.Stats),
missileID: missileID,
nameLabel: hoverLabel,
zoneChangeText: &zoneLabel,
zoneChangeText: zoneLabel,
actionableRegions: []ActionableRegion{
{leftSkill, d2common.Rectangle{Left: 115, Top: 550, Width: 50, Height: 50}},
{leftSelec, d2common.Rectangle{Left: 206, Top: 563, Width: 30, Height: 30}},
@ -328,19 +337,19 @@ func (g *GameControls) OnMouseButtonDown(event d2interface.MouseEvent) bool {
func (g *GameControls) Load() {
animation, _ := d2asset.LoadAnimation(d2resource.GameGlobeOverlap, d2resource.PaletteSky)
g.globeSprite, _ = d2ui.LoadSprite(animation)
g.globeSprite, _ = g.uiManager.NewSprite(animation)
animation, _ = d2asset.LoadAnimation(d2resource.HealthManaIndicator, d2resource.PaletteSky)
g.hpManaStatusSprite, _ = d2ui.LoadSprite(animation)
g.hpManaStatusSprite, _ = g.uiManager.NewSprite(animation)
animation, _ = d2asset.LoadAnimation(d2resource.GamePanels, d2resource.PaletteSky)
g.mainPanel, _ = d2ui.LoadSprite(animation)
g.mainPanel, _ = g.uiManager.NewSprite(animation)
animation, _ = d2asset.LoadAnimation(d2resource.MenuButton, d2resource.PaletteSky)
g.menuButton, _ = d2ui.LoadSprite(animation)
g.menuButton, _ = g.uiManager.NewSprite(animation)
animation, _ = d2asset.LoadAnimation(d2resource.GenericSkills, d2resource.PaletteSky)
g.skillIcon, _ = d2ui.LoadSprite(animation)
g.skillIcon, _ = g.uiManager.NewSprite(animation)
g.loadUIButtons()
@ -350,7 +359,7 @@ func (g *GameControls) Load() {
func (g *GameControls) loadUIButtons() {
// Run button
g.runButton = d2ui.CreateButton(g.renderer, d2ui.ButtonTypeRun, "")
g.runButton = g.uiManager.NewButton(d2ui.ButtonTypeRun, "")
g.runButton.SetPosition(255, 570)
g.runButton.OnActivated(func() { g.onToggleRunButton() })
@ -358,8 +367,6 @@ func (g *GameControls) loadUIButtons() {
if g.hero.IsRunToggled() {
g.runButton.Toggle()
}
d2ui.AddWidget(&g.runButton)
}
func (g *GameControls) onToggleRunButton() {

View File

@ -24,19 +24,19 @@ type PanelText struct {
// StatsPanelLabels represents the labels in the status panel
type StatsPanelLabels struct {
Level d2ui.Label
Experience d2ui.Label
NextLevelExp d2ui.Label
Strength d2ui.Label
Dexterity d2ui.Label
Vitality d2ui.Label
Energy d2ui.Label
Health d2ui.Label
MaxHealth d2ui.Label
Mana d2ui.Label
MaxMana d2ui.Label
MaxStamina d2ui.Label
Stamina d2ui.Label
Level *d2ui.Label
Experience *d2ui.Label
NextLevelExp *d2ui.Label
Strength *d2ui.Label
Dexterity *d2ui.Label
Vitality *d2ui.Label
Energy *d2ui.Label
Health *d2ui.Label
MaxHealth *d2ui.Label
Mana *d2ui.Label
MaxMana *d2ui.Label
MaxStamina *d2ui.Label
Stamina *d2ui.Label
}
// stores all the labels that can change during gameplay(e.g. current level, current hp, mana, etc.)
@ -44,6 +44,7 @@ var StatValueLabels = make([]d2ui.Label, 13)
// HeroStatsPanel represents the hero status panel
type HeroStatsPanel struct {
uiManager *d2ui.UIManager
frame *d2ui.Sprite
panel *d2ui.Sprite
heroState *d2hero.HeroStatsState
@ -59,13 +60,14 @@ type HeroStatsPanel struct {
}
// NewHeroStatsPanel creates a new hero status panel
func NewHeroStatsPanel(renderer d2interface.Renderer, heroName string, heroClass d2enum.Hero,
func NewHeroStatsPanel(ui *d2ui.UIManager, heroName string, heroClass d2enum.Hero,
heroState *d2hero.HeroStatsState) *HeroStatsPanel {
originX := 0
originY := 0
return &HeroStatsPanel{
renderer: renderer,
uiManager: ui,
renderer: ui.Renderer(),
originX: originX,
originY: originY,
heroState: heroState,
@ -78,9 +80,9 @@ func NewHeroStatsPanel(renderer d2interface.Renderer, heroName string, heroClass
// Load loads the data for the hero status panel
func (s *HeroStatsPanel) Load() {
animation, _ := d2asset.LoadAnimation(d2resource.Frame, d2resource.PaletteSky)
s.frame, _ = d2ui.LoadSprite(animation)
s.frame, _ = s.uiManager.NewSprite(animation)
animation, _ = d2asset.LoadAnimation(d2resource.InventoryCharacterPanel, d2resource.PaletteSky)
s.panel, _ = d2ui.LoadSprite(animation)
s.panel, _ = s.uiManager.NewSprite(animation)
s.initStatValueLabels()
}
@ -273,7 +275,7 @@ func (s *HeroStatsPanel) renderStaticMenu(target d2interface.Surface) error {
return err
}
var label d2ui.Label
var label *d2ui.Label
// all static labels are not stored since we use them only once to generate the image cache
@ -360,23 +362,25 @@ func (s *HeroStatsPanel) renderStatValues(target d2interface.Surface) {
s.renderStatValueNum(s.labels.Mana, s.heroState.Mana, target)
}
func (s *HeroStatsPanel) renderStatValueNum(label d2ui.Label, value int, target d2interface.Surface) {
func (s *HeroStatsPanel) renderStatValueNum(label *d2ui.Label, value int,
target d2interface.Surface) {
label.SetText(strconv.Itoa(value))
label.Render(target)
}
func (s *HeroStatsPanel) createStatValueLabel(stat int, x int, y int) d2ui.Label {
func (s *HeroStatsPanel) createStatValueLabel(stat int, x int, y int) *d2ui.Label {
text := strconv.Itoa(stat)
return s.createTextLabel(PanelText{X: x, Y: y, Text: text, Font: d2resource.Font16, AlignCenter: true})
}
func (s *HeroStatsPanel) createTextLabel(element PanelText) d2ui.Label {
label := d2ui.CreateLabel(element.Font, d2resource.PaletteStatic)
func (s *HeroStatsPanel) createTextLabel(element PanelText) *d2ui.Label {
label := s.uiManager.NewLabel(element.Font, d2resource.PaletteStatic)
if element.AlignCenter {
label.Alignment = d2gui.HorizontalAlignCenter
}
label.SetText(element.Text)
label.SetPosition(element.X, element.Y)
return label
}

View File

@ -14,9 +14,10 @@ import (
)
type Inventory struct {
frame *d2ui.Sprite
panel *d2ui.Sprite
grid *ItemGrid
uiManager *d2ui.UIManager
frame *d2ui.Sprite
panel *d2ui.Sprite
grid *ItemGrid
hoverLabel *d2ui.Label
hoverX, hoverY int
@ -28,15 +29,16 @@ type Inventory struct {
isOpen bool
}
func NewInventory(record *d2datadict.InventoryRecord) *Inventory {
func NewInventory(ui *d2ui.UIManager, record *d2datadict.InventoryRecord) *Inventory {
hoverLabel := d2ui.CreateLabel(d2resource.FontFormal11, d2resource.PaletteStatic)
hoverLabel := ui.NewLabel(d2resource.FontFormal11, d2resource.PaletteStatic)
hoverLabel.Alignment = d2gui.HorizontalAlignCenter
return &Inventory{
grid: NewItemGrid(record),
uiManager: ui,
grid: NewItemGrid(ui, record),
originX: record.Panel.Left,
hoverLabel: &hoverLabel,
hoverLabel: hoverLabel,
// originY: record.Panel.Top,
originY: 0, // expansion data has these all offset by +60 ...
}
@ -60,10 +62,10 @@ func (g *Inventory) Close() {
func (g *Inventory) Load() {
animation, _ := d2asset.LoadAnimation(d2resource.Frame, d2resource.PaletteSky)
g.frame, _ = d2ui.LoadSprite(animation)
g.frame, _ = g.uiManager.NewSprite(animation)
animation, _ = d2asset.LoadAnimation(d2resource.InventoryCharacterPanel, d2resource.PaletteSky)
g.panel, _ = d2ui.LoadSprite(animation)
g.panel, _ = g.uiManager.NewSprite(animation)
items := []InventoryItem{
diablo2item.NewItem("kit", "Crimson", "of the Bat", "of Frost").Identify(),
diablo2item.NewItem("rin", "Steel", "of Shock").Identify(),
@ -246,6 +248,7 @@ func (g *Inventory) Render(target d2interface.Surface) error {
}
g.renderItemDescription(target, item)
break
}
}
@ -272,7 +275,7 @@ func (g *Inventory) renderItemDescription(target d2interface.Surface, i Inventor
}
halfW, halfH := maxW/2, maxH/2
centerX, centerY := g.hoverX, iy - halfH
centerX, centerY := g.hoverX, iy-halfH
if (centerX + halfW) > 800 {
centerX = 800 - halfW

View File

@ -33,6 +33,7 @@ var ErrorInventoryFull = errors.New("inventory full")
// Reusable grid for use with player and merchant inventory.
// Handles layout and rendering item icons based on code.
type ItemGrid struct {
uiManager *d2ui.UIManager
items []InventoryItem
equipmentSlots map[d2enum.EquippedSlot]EquipmentSlot
width int
@ -43,10 +44,11 @@ type ItemGrid struct {
slotSize int
}
func NewItemGrid(record *d2datadict.InventoryRecord) *ItemGrid {
func NewItemGrid(ui *d2ui.UIManager, record *d2datadict.InventoryRecord) *ItemGrid {
grid := record.Grid
return &ItemGrid{
uiManager: ui,
width: grid.Box.Width,
height: grid.Box.Height,
originX: grid.Box.Left,
@ -126,7 +128,7 @@ func (g *ItemGrid) loadItem(item InventoryItem) {
return
}
itemSprite, err = d2ui.LoadSprite(animation)
itemSprite, err = g.uiManager.NewSprite(animation)
if err != nil {
log.Printf("Failed to load sprite, error: " + err.Error())
}