1
1
mirror of https://github.com/OpenDiablo2/OpenDiablo2 synced 2024-06-05 15:30:42 +00:00

Merge pull request #911 from juander-ux/ui_refactor

d2ui refactor
I *almost* squashed it!
This commit is contained in:
Tim Sarbin 2020-11-10 00:04:08 -05:00 committed by GitHub
commit 5e5d8415ba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 638 additions and 424 deletions

View File

@ -242,32 +242,28 @@ var _ Widget = &Button{} // static check to ensure button implements widget
// Button defines a standard wide UI button
type Button struct {
manager *UIManager
*BaseWidget
buttonLayout ButtonLayout
normalSurface d2interface.Surface
pressedSurface d2interface.Surface
toggledSurface d2interface.Surface
pressedToggledSurface d2interface.Surface
disabledSurface d2interface.Surface
x int
y int
width int
height int
onClick func()
enabled bool
visible bool
pressed bool
toggled bool
}
// NewButton creates an instance of Button
func (ui *UIManager) NewButton(buttonType ButtonType, text string) *Button {
base := NewBaseWidget(ui)
base.SetVisible(true)
btn := &Button{
width: 0,
height: 0,
visible: true,
enabled: true,
pressed: false,
BaseWidget: base,
enabled: true,
pressed: false,
}
buttonLayout := getButtonLayouts()[buttonType]
@ -348,7 +344,7 @@ func (v *Button) prerenderStates(btnSprite *Sprite, btnLayout *ButtonLayout, lab
xOffset := half(v.width)
label.SetPosition(xOffset, textY)
label.Render(v.normalSurface)
label.RenderNoError(v.normalSurface)
if !btnLayout.HasImage || !btnLayout.AllowFrameChange {
return
@ -422,15 +418,10 @@ func (v *Button) prerenderStates(btnSprite *Sprite, btnLayout *ButtonLayout, lab
}
label.SetPosition(state.offsetX, state.offsetY)
label.Render(*state.prerenderdestination)
label.RenderNoError(*state.prerenderdestination)
}
}
// 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
@ -495,32 +486,6 @@ func (v *Button) SetEnabled(enabled bool) {
v.enabled = enabled
}
// GetSize returns the size of the button
func (v *Button) GetSize() (width, height int) {
return v.width, v.height
}
// SetPosition moves the button
func (v *Button) SetPosition(x, y int) {
v.x = x
v.y = y
}
// GetPosition returns the location of the button
func (v *Button) GetPosition() (x, y int) {
return v.x, v.y
}
// GetVisible returns the visibility of the button
func (v *Button) GetVisible() bool {
return v.visible
}
// SetVisible sets the visibility of the button
func (v *Button) SetVisible(visible bool) {
v.visible = visible
}
// GetPressed returns the pressed state of the button
func (v *Button) GetPressed() bool {
return v.pressed

View File

@ -8,18 +8,16 @@ import (
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
)
// static check that Checkbox implements Widget
var _ Widget = &Checkbox{}
// Checkbox represents a checkbox UI element
type Checkbox struct {
manager *UIManager
*BaseWidget
Image d2interface.Surface
checkedImage d2interface.Surface
x int
y int
width int
height int
onClick func()
checkState bool
visible bool
enabled bool
}
@ -27,11 +25,11 @@ type Checkbox struct {
func (ui *UIManager) NewCheckbox(checkState bool) *Checkbox {
var err error
base := NewBaseWidget(ui)
result := &Checkbox{
BaseWidget: base,
checkState: checkState,
visible: true,
width: 0,
height: 0,
enabled: true,
}
@ -70,11 +68,6 @@ func (ui *UIManager) NewCheckbox(checkState bool) *Checkbox {
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)
@ -140,29 +133,3 @@ func (v *Checkbox) Activate() {
v.onClick()
}
// GetPosition returns the position of the checkbox
func (v *Checkbox) GetPosition() (x, y int) {
return v.x, v.y
}
// GetSize returns the size of the checkbox
func (v *Checkbox) GetSize() (width, height int) {
return v.width, v.height
}
// GetVisible returns the visibility state of the checkbox
func (v *Checkbox) GetVisible() bool {
return v.visible
}
// SetPosition sets the position of the checkbox
func (v *Checkbox) SetPosition(x, y int) {
v.x = x
v.y = y
}
// SetVisible sets the visibility of the checkbox
func (v *Checkbox) SetVisible(visible bool) {
v.visible = visible
}

View File

@ -0,0 +1,32 @@
package d2ui
import "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
// static check that CustomWidget implements widget
var _ Widget = &CustomWidget{}
// CustomWidget is a widget with a fully custom render function
type CustomWidget struct {
*BaseWidget
renderFunc func(target d2interface.Surface) error
}
// NewCustomWidget creates a new widget with custom render function
func (ui *UIManager) NewCustomWidget(renderFunc func(target d2interface.Surface) error) *CustomWidget {
base := NewBaseWidget(ui)
return &CustomWidget{
BaseWidget: base,
renderFunc: renderFunc,
}
}
// Render draws the custom widget
func (c *CustomWidget) Render(target d2interface.Surface) error {
return c.renderFunc(target)
}
// Advance is a no-op
func (c *CustomWidget) Advance(elapsed float64) error {
return nil
}

View File

@ -11,6 +11,9 @@ type Drawable interface {
GetSize() (width, height int)
SetPosition(x, y int)
GetPosition() (x, y int)
OffsetPosition(xo, yo int)
GetVisible() bool
SetVisible(visible bool)
SetRenderPriority(priority RenderPriority)
GetRenderPriority() (priority RenderPriority)
}

View File

@ -8,6 +8,9 @@ import (
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset"
)
// static check that UIFrame implements Widget
var _ Widget = &UIFrame{}
type frameOrientation = int
// Frame orientations
@ -19,11 +22,9 @@ const (
// UIFrame is a representation of a ui panel that occupies the left or right half of the screen
// when it is visible.
type UIFrame struct {
*BaseWidget
asset *d2asset.AssetManager
uiManager *UIManager
frame *Sprite
originX int
originY int
frameOrientation frameOrientation
}
@ -58,12 +59,13 @@ func NewUIFrame(
originY = 0
}
base := NewBaseWidget(uiManager)
base.SetPosition(originX, originY)
frame := &UIFrame{
BaseWidget: base,
asset: asset,
uiManager: uiManager,
frameOrientation: frameOrientation,
originX: originX,
originY: originY,
}
frame.Load()
@ -72,7 +74,7 @@ func NewUIFrame(
// Load the necessary frame resources
func (u *UIFrame) Load() {
sprite, err := u.uiManager.NewSprite(d2resource.Frame, d2resource.PaletteSky)
sprite, err := u.manager.NewSprite(d2resource.Frame, d2resource.PaletteSky)
if err != nil {
log.Print(err)
}
@ -105,7 +107,7 @@ func (u *UIFrame) renderLeft(target d2interface.Surface) error {
// the frame coordinates
coord := make(map[int]*struct{ x, y int })
startX, startY := u.originX, u.originY
startX, startY := u.GetPosition()
currentX, currentY := startX, startY
// first determine the coordinates for each frame
@ -162,7 +164,7 @@ func (u *UIFrame) renderRight(target d2interface.Surface) error {
// the frame coordinates
coord := make(map[int]*struct{ x, y int })
startX, startY := u.originX, u.originY
startX, startY := u.GetPosition()
currentX, currentY := startX, startY
// first determine the coordinates for each frame
@ -225,7 +227,12 @@ func (u *UIFrame) renderFramePiece(sfc d2interface.Surface, x, y, idx int) error
u.frame.SetPosition(x, y)
u.frame.Render(sfc)
u.frame.RenderNoError(sfc)
return nil
}
// Advance is a no-op
func (u *UIFrame) Advance(elapsed float64) error {
return nil
}

View File

@ -13,12 +13,13 @@ import (
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2gui"
)
// static check that UIFrame implements Widget
var _ Widget = &Label{}
// Label represents a user interface label
type Label struct {
manager *UIManager
*BaseWidget
text string
X int
Y int
Alignment d2gui.HorizontalAlign
font *d2asset.Font
Color map[int]color.Color
@ -33,10 +34,13 @@ func (ui *UIManager) NewLabel(fontPath, palettePath string) *Label {
return nil
}
base := NewBaseWidget(ui)
result := &Label{
Alignment: d2gui.HorizontalAlignLeft,
Color: map[int]color.Color{0: color.White},
font: font,
BaseWidget: base,
Alignment: d2gui.HorizontalAlignLeft,
Color: map[int]color.Color{0: color.White},
font: font,
}
result.bindManager(ui)
@ -44,9 +48,15 @@ func (ui *UIManager) NewLabel(fontPath, palettePath string) *Label {
return result
}
// Render draws the label on the screen, respliting the lines to allow for other alignments.
func (v *Label) Render(target d2interface.Surface) {
target.PushTranslation(v.X, v.Y)
// Render draws the label on the screen
func (v *Label) Render(target d2interface.Surface) error {
v.RenderNoError(target)
return nil
}
// RenderNoError draws the label on the screen, respliting the lines to allow for other alignments.
func (v *Label) RenderNoError(target d2interface.Surface) {
target.PushTranslation(v.GetPosition())
lines := strings.Split(v.text, "\n")
yOffset := 0
@ -91,17 +101,6 @@ 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
v.Y = y
}
// GetSize returns the size of the label
func (v *Label) GetSize() (width, height int) {
return v.font.GetTextMetrics(v.text)
@ -172,6 +171,11 @@ func (v *Label) getAlignOffset(textWidth int) int {
}
}
// Advance is a no-op
func (v *Label) Advance(elapsed float64) error {
return nil
}
func getColor(token ColorToken) color.Color {
// https://github.com/OpenDiablo2/OpenDiablo2/issues/823
colors := map[ColorToken]color.Color{

View File

@ -15,11 +15,12 @@ const (
scrollbarWidth = 10
)
// static check that Scrollbar implements widget
var _ Widget = &Scrollbar{}
// Scrollbar is a vertical slider ui element
type Scrollbar struct {
manager *UIManager
x, y, height int
visible bool
*BaseWidget
enabled bool
currentOffset int
maxOffset int
@ -36,12 +37,13 @@ func (ui *UIManager) NewScrollbar(x, y, height int) *Scrollbar {
return nil
}
base := NewBaseWidget(ui)
base.SetPosition(x, y)
base.height = height
result := &Scrollbar{
visible: true,
BaseWidget: base,
enabled: true,
x: x,
y: y,
height: height,
scrollbarSprite: scrollbarSprite,
}
@ -146,11 +148,6 @@ func (v *Scrollbar) Render(target d2interface.Surface) error {
return nil
}
// 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)
@ -161,27 +158,6 @@ func (v *Scrollbar) GetSize() (width, height int) {
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

View File

@ -11,10 +11,12 @@ import (
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2math"
)
// static check that Sprite implements Widget
var _ Widget = &Sprite{}
// Sprite is a positioned visual object.
type Sprite struct {
x int
y int
*BaseWidget
animation d2interface.Animation
}
@ -31,11 +33,21 @@ func (ui *UIManager) NewSprite(animationPath, palettePath string) (*Sprite, erro
animation.BindRenderer(ui.renderer)
return &Sprite{animation: animation}, nil
base := NewBaseWidget(ui)
return &Sprite{
BaseWidget: base,
animation: animation}, nil
}
// Render renders the sprite on the given surface
func (s *Sprite) Render(target d2interface.Surface) {
func (s *Sprite) Render(target d2interface.Surface) error {
s.RenderNoError(target)
return nil
}
// RenderNoError renders the sprite on the given surface
func (s *Sprite) RenderNoError(target d2interface.Surface) {
_, frameHeight := s.animation.GetCurrentFrameSize()
target.PushTranslation(s.x, s.y-frameHeight)
@ -80,15 +92,9 @@ func (s *Sprite) RenderSegmented(target d2interface.Surface, segmentsX, segments
return nil
}
// SetPosition places the sprite in 2D
func (s *Sprite) SetPosition(x, y int) {
s.x = x
s.y = y
}
// GetPosition retrieves the 2D position of the sprite
func (s *Sprite) GetPosition() (x, y int) {
return s.x, s.y
// GetSize returns the size of the current frame
func (s *Sprite) GetSize() (width, height int) {
return s.GetCurrentFrameSize()
}
// GetFrameSize gets the Size(width, height) of a indexed frame.

View File

@ -10,17 +10,17 @@ import (
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
)
// static check that TextBox implements widget
var _ Widget = &TextBox{}
// TextBox represents a text input box
type TextBox struct {
manager *UIManager
*BaseWidget
textLabel *Label
lineBar *Label
text string
filter string
x int
y int
bgSprite *Sprite
visible bool
enabled bool
isFocused bool
}
@ -33,13 +33,15 @@ func (ui *UIManager) NewTextbox() *TextBox {
return nil
}
base := NewBaseWidget(ui)
tb := &TextBox{
filter: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
bgSprite: bgSprite,
textLabel: ui.NewLabel(d2resource.FontFormal11, d2resource.PaletteUnits),
lineBar: ui.NewLabel(d2resource.FontFormal11, d2resource.PaletteUnits),
enabled: true,
visible: true,
BaseWidget: base,
filter: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
bgSprite: bgSprite,
textLabel: ui.NewLabel(d2resource.FontFormal11, d2resource.PaletteUnits),
lineBar: ui.NewLabel(d2resource.FontFormal11, d2resource.PaletteUnits),
enabled: true,
}
tb.lineBar.SetText("_")
@ -59,21 +61,16 @@ func (v *TextBox) Render(target d2interface.Surface) error {
return nil
}
v.bgSprite.Render(target)
v.textLabel.Render(target)
v.bgSprite.RenderNoError(target)
v.textLabel.RenderNoError(target)
if (time.Now().UnixNano()/1e6)&(1<<8) > 0 {
v.lineBar.Render(target)
v.lineBar.RenderNoError(target)
}
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 {
@ -189,21 +186,6 @@ func (v *TextBox) SetPosition(x, y int) {
v.bgSprite.SetPosition(v.x, v.y+26)
}
// GetPosition returns the position of the text box
func (v *TextBox) GetPosition() (x, y int) {
return v.x, v.y
}
// GetVisible returns the visibility of the text box
func (v *TextBox) GetVisible() bool {
return v.visible
}
// SetVisible sets the visibility of the text box
func (v *TextBox) SetVisible(visible bool) {
v.visible = visible
}
// GetEnabled returns the enabled state of the text box
func (v *TextBox) GetEnabled() bool {
return v.enabled

View File

@ -14,13 +14,15 @@ const (
screenHeight = 600
)
// static check that Tooltip implements widget
var _ Widget = &Tooltip{}
// Tooltip contains a label containing text with a transparent, black background
type Tooltip struct {
manager *UIManager
*BaseWidget
lines []string
label *Label
backgroundColor int
x, y int
originX tooltipXOrigin
originY tooltipYOrigin
boxEnabled bool
@ -56,11 +58,12 @@ func (ui *UIManager) NewTooltip(font,
label := ui.NewLabel(font, palette)
label.Alignment = d2gui.HorizontalAlignCenter
base := NewBaseWidget(ui)
res := &Tooltip{
BaseWidget: base,
backgroundColor: blackAlpha70,
label: label,
x: 0,
y: 0,
originX: originX,
originY: originY,
boxEnabled: true,
@ -70,12 +73,6 @@ func (ui *UIManager) NewTooltip(font,
return res
}
// SetPosition sets the position of the origin point of the tooltip
func (t *Tooltip) SetPosition(x, y int) {
t.x = x
t.y = y
}
// SetTextLines sets the tooltip text in the form of an array of strings
func (t *Tooltip) SetTextLines(lines []string) {
t.lines = lines
@ -143,7 +140,7 @@ func (t *Tooltip) GetSize() (sx, sy int) {
}
// Render draws the tooltip
func (t *Tooltip) Render(target d2interface.Surface) {
func (t *Tooltip) Render(target d2interface.Surface) error {
maxW, maxH := t.GetSize()
// nolint:gomnd // no magic numbers, their meaning is obvious
@ -190,9 +187,16 @@ func (t *Tooltip) Render(target d2interface.Surface) {
for i := range t.lines {
t.label.SetText(t.lines[i])
_, h := t.label.GetTextMetrics(t.lines[i])
t.label.Render(target)
t.label.RenderNoError(target)
target.PushTranslation(0, h)
}
target.PopN(len(t.lines))
return nil
}
// Advance is a no-op
func (t *Tooltip) Advance(elapsed float64) error {
return nil
}

View File

@ -2,6 +2,7 @@ package d2ui
import (
"log"
"sort"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset"
@ -12,16 +13,18 @@ import (
// UIManager manages a collection of UI elements (buttons, textboxes, labels)
type UIManager struct {
asset *d2asset.AssetManager
renderer d2interface.Renderer
inputManager d2interface.InputManager
audio d2interface.AudioProvider
widgets []Widget
cursorButtons CursorButton
CursorX int
CursorY int
pressedWidget Widget
clickSfx d2interface.SoundEffect
asset *d2asset.AssetManager
renderer d2interface.Renderer
inputManager d2interface.InputManager
audio d2interface.AudioProvider
widgets []Widget
widgetsGroups []*WidgetGroup
clickableWidgets []ClickableWidget
cursorButtons CursorButton
CursorX int
CursorY int
pressedWidget ClickableWidget
clickSfx d2interface.SoundEffect
}
// Note: methods for creating buttons and stuff are in their respective files
@ -44,9 +47,18 @@ func (ui *UIManager) Initialize() {
// Reset resets the state of the UI manager. Typically called for new screens
func (ui *UIManager) Reset() {
ui.widgets = nil
ui.clickableWidgets = nil
ui.pressedWidget = nil
}
// addWidgetGroup adds a widgetGroup to the UI manager and sorts by priority
func (ui *UIManager) addWidgetGroup(group *WidgetGroup) {
ui.widgetsGroups = append(ui.widgetsGroups, group)
sort.SliceStable(ui.widgetsGroups, func(i, j int) bool {
return ui.widgetsGroups[i].priority < ui.widgetsGroups[j].priority
})
}
// addWidget adds a widget to the UI manager
func (ui *UIManager) addWidget(widget Widget) {
err := ui.inputManager.BindHandler(widget)
@ -54,8 +66,12 @@ func (ui *UIManager) addWidget(widget Widget) {
log.Print(err)
}
ui.widgets = append(ui.widgets, widget)
clickable, ok := widget.(ClickableWidget)
if ok {
ui.clickableWidgets = append(ui.clickableWidgets, clickable)
}
ui.widgets = append(ui.widgets, widget)
widget.bindManager(ui)
}
@ -67,13 +83,12 @@ func (ui *UIManager) OnMouseButtonUp(event d2interface.MouseEvent) bool {
// 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() {
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 {
for _, w := range ui.clickableWidgets {
w.SetPressed(false)
}
}
@ -87,7 +102,7 @@ func (ui *UIManager) OnMouseButtonDown(event d2interface.MouseEvent) bool {
if event.Button() == d2enum.MouseButtonLeft {
// find and press a widget on screen
ui.pressedWidget = nil
for _, w := range ui.widgets {
for _, w := range ui.clickableWidgets {
if ui.contains(w, ui.CursorX, ui.CursorY) && w.GetVisible() && w.GetEnabled() {
w.SetPressed(true)
ui.pressedWidget = w
@ -115,6 +130,15 @@ func (ui *UIManager) Render(target d2interface.Surface) {
}
}
}
for _, widgetGroup := range ui.widgetsGroups {
if widgetGroup.GetVisible() {
err := widgetGroup.Render(target)
if err != nil {
log.Print(err)
}
}
}
}
// contains determines whether a given x,y coordinate lands within a Widget

View File

@ -1,13 +1,102 @@
package d2ui
// RenderPriority determines in which order ui elements are drawn.
// The higher the number the later an element is drawn.
type RenderPriority int
const (
// RenderPriorityBackground is the first element drawn
RenderPriorityBackground RenderPriority = iota
// RenderPrioritySkilltree is the priority for the skilltree
RenderPrioritySkilltree
// RenderPrioritySkilltreeIcon is the priority for the skilltree icons
RenderPrioritySkilltreeIcon
// RenderPriorityForeground is the last element drawn
RenderPriorityForeground
)
// Widget defines an object that is a UI widget
type Widget interface {
Drawable
bindManager(ui *UIManager)
GetEnabled() bool
}
// ClickableWidget defines an object that can be clicked
type ClickableWidget interface {
Widget
SetEnabled(enabled bool)
SetPressed(pressed bool)
GetEnabled() bool
GetPressed() bool
OnActivated(callback func())
Activate()
}
// BaseWidget contains default functionality that all widgets share
type BaseWidget struct {
manager *UIManager
x int
y int
width int
height int
renderPriority RenderPriority
visible bool
}
// NewBaseWidget creates a new BaseWidget with defaults
func NewBaseWidget(manager *UIManager) *BaseWidget {
return &BaseWidget{
manager: manager,
x: 0,
y: 0,
width: 0,
height: 0,
visible: true,
renderPriority: RenderPriorityBackground,
}
}
func (b *BaseWidget) bindManager(manager *UIManager) {
b.manager = manager
}
// GetSize returns the size of the widget
func (b *BaseWidget) GetSize() (width, height int) {
return b.width, b.height
}
// SetPosition sets the position of the widget
func (b *BaseWidget) SetPosition(x, y int) {
b.x, b.y = x, y
}
// OffsetPosition moves the widget by x and y
func (b *BaseWidget) OffsetPosition(x, y int) {
b.x += x
b.y += y
}
// GetPosition returns the position of the widget
func (b *BaseWidget) GetPosition() (x, y int) {
return b.x, b.y
}
// GetVisible returns whether the widget is visible
func (b *BaseWidget) GetVisible() (visible bool) {
return b.visible
}
// SetVisible make the widget visible, not visible
func (b *BaseWidget) SetVisible(visible bool) {
b.visible = visible
}
// GetRenderPriority returns the order in which this widget is rendered
func (b *BaseWidget) GetRenderPriority() (prio RenderPriority) {
return b.renderPriority
}
// SetRenderPriority sets the order in which this widget is rendered
func (b *BaseWidget) SetRenderPriority(prio RenderPriority) {
b.renderPriority = prio
}

View File

@ -0,0 +1,92 @@
package d2ui
import (
"sort"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
)
// static check that WidgetGroup implements widget
var _ Widget = &WidgetGroup{}
// WidgetGroup allows the grouping of widgets to apply actions to all
// widgets at once.
type WidgetGroup struct {
*BaseWidget
entries []Widget
priority RenderPriority
}
// NewWidgetGroup creates a new widget group
func (ui *UIManager) NewWidgetGroup(priority RenderPriority) *WidgetGroup {
base := NewBaseWidget(ui)
base.SetRenderPriority(priority)
group := &WidgetGroup{
BaseWidget: base,
}
ui.addWidgetGroup(group)
return group
}
// AddWidget adds a widget to the group
func (wg *WidgetGroup) AddWidget(w Widget) {
wg.adjustSize(w)
wg.entries = append(wg.entries, w)
sort.SliceStable(wg.entries, func(i, j int) bool {
return wg.entries[i].GetRenderPriority() < wg.entries[j].GetRenderPriority()
})
}
// adjustSize recalculates the bounding box if a new widget is added
func (wg *WidgetGroup) adjustSize(w Widget) {
x, y := w.GetPosition()
width, height := w.GetSize()
if x+width > wg.width {
wg.width = x + width
}
if wg.x > x {
wg.width += wg.x - x
wg.x = x
}
if y+height > wg.height {
wg.height = x + height
}
if wg.y > y {
wg.height += wg.y - y
wg.y = y
}
}
// Advance is a no-op here
func (wg *WidgetGroup) Advance(elapsed float64) error {
// No-op
return nil
}
// Render draw the widgets to the screen
func (wg *WidgetGroup) Render(target d2interface.Surface) error {
for _, entry := range wg.entries {
if entry.GetVisible() {
err := entry.Render(target)
if err != nil {
return err
}
}
}
return nil
}
// SetVisible sets the visibility of all widgets in the group
func (wg *WidgetGroup) SetVisible(visible bool) {
for _, entry := range wg.entries {
entry.SetVisible(visible)
}
}

View File

@ -373,7 +373,7 @@ func (v *CharacterSelect) Render(screen d2interface.Surface) {
return
}
v.d2HeroTitle.Render(screen)
v.d2HeroTitle.RenderNoError(screen)
actualSelectionIndex := v.selectedCharacter - (v.charScrollbar.GetCurrentOffset() * 2)
if v.selectedCharacter > -1 && actualSelectionIndex >= 0 && actualSelectionIndex < 8 {
@ -388,12 +388,13 @@ func (v *CharacterSelect) Render(screen d2interface.Surface) {
continue
}
v.characterNameLabel[i].Render(screen)
v.characterStatsLabel[i].Render(screen)
v.characterExpLabel[i].Render(screen)
v.characterNameLabel[i].RenderNoError(screen)
v.characterStatsLabel[i].RenderNoError(screen)
v.characterExpLabel[i].RenderNoError(screen)
charImgX := v.characterNameLabel[i].X - selectionBoxImageOffsetX
charImgY := v.characterNameLabel[i].Y + selectionBoxImageOffsetY
x, y := v.characterNameLabel[i].GetPosition()
charImgX := x - selectionBoxImageOffsetX
charImgY := y + selectionBoxImageOffsetY
screen.PushTranslation(charImgX, charImgY)
v.characterImage[i].Render(screen)
screen.Pop()
@ -406,7 +407,7 @@ func (v *CharacterSelect) Render(screen d2interface.Surface) {
return
}
v.deleteCharConfirmLabel.Render(screen)
v.deleteCharConfirmLabel.RenderNoError(screen)
}
}

View File

@ -139,7 +139,7 @@ func (v *Credits) Render(screen d2interface.Surface) {
continue
}
label.Label.Render(screen)
label.Label.RenderNoError(screen)
}
}
@ -159,11 +159,14 @@ func (v *Credits) Advance(tickTime float64) error {
continue
}
if label.Label.Y-1 < -15 {
_, y := label.Label.GetPosition()
if y-1 < -15 {
label.Available = true
continue
}
label.Label.Y--
label.Label.OffsetPosition(0, -1)
}
}

View File

@ -452,31 +452,31 @@ func (v *MainMenu) renderBackgrounds(screen d2interface.Surface) {
func (v *MainMenu) renderLogos(screen d2interface.Surface) {
switch v.screenMode {
case ScreenModeTrademark, ScreenModeMainMenu, ScreenModeMultiplayer:
v.diabloLogoLeftBack.Render(screen)
v.diabloLogoRightBack.Render(screen)
v.diabloLogoLeft.Render(screen)
v.diabloLogoRight.Render(screen)
v.diabloLogoLeftBack.RenderNoError(screen)
v.diabloLogoRightBack.RenderNoError(screen)
v.diabloLogoLeft.RenderNoError(screen)
v.diabloLogoRight.RenderNoError(screen)
}
}
func (v *MainMenu) renderLabels(screen d2interface.Surface) {
switch v.screenMode {
case ScreenModeServerIP:
v.tcpIPOptionsLabel.Render(screen)
v.tcpJoinGameLabel.Render(screen)
v.tcpIPOptionsLabel.RenderNoError(screen)
v.tcpJoinGameLabel.RenderNoError(screen)
case ScreenModeTCPIP:
v.tcpIPOptionsLabel.Render(screen)
v.tcpIPOptionsLabel.RenderNoError(screen)
case ScreenModeTrademark:
v.copyrightLabel.Render(screen)
v.copyrightLabel2.Render(screen)
v.copyrightLabel.RenderNoError(screen)
v.copyrightLabel2.RenderNoError(screen)
if v.errorLabel != nil {
v.errorLabel.Render(screen)
v.errorLabel.RenderNoError(screen)
}
case ScreenModeMainMenu:
v.openDiabloLabel.Render(screen)
v.versionLabel.Render(screen)
v.commitLabel.Render(screen)
v.openDiabloLabel.RenderNoError(screen)
v.versionLabel.RenderNoError(screen)
v.commitLabel.RenderNoError(screen)
}
}

View File

@ -515,13 +515,13 @@ func (v *SelectHeroClass) Render(screen d2interface.Surface) {
return
}
v.headingLabel.Render(screen)
v.headingLabel.RenderNoError(screen)
if v.selectedHero != d2enum.HeroNone {
v.heroClassLabel.Render(screen)
v.heroDesc1Label.Render(screen)
v.heroDesc2Label.Render(screen)
v.heroDesc3Label.Render(screen)
v.heroClassLabel.RenderNoError(screen)
v.heroDesc1Label.RenderNoError(screen)
v.heroDesc2Label.RenderNoError(screen)
v.heroDesc3Label.RenderNoError(screen)
}
for heroClass, heroInfo := range v.heroRenderInfo {
@ -536,12 +536,12 @@ func (v *SelectHeroClass) Render(screen d2interface.Surface) {
}
}
v.campfire.Render(screen)
v.campfire.RenderNoError(screen)
if v.heroNameTextbox.GetVisible() {
v.heroNameLabel.Render(screen)
v.expansionCharLabel.Render(screen)
v.hardcoreCharLabel.Render(screen)
v.heroNameLabel.RenderNoError(screen)
v.expansionCharLabel.RenderNoError(screen)
v.hardcoreCharLabel.RenderNoError(screen)
}
}
@ -739,7 +739,7 @@ func setSpriteToFirstFrame(sprite *d2ui.Sprite) {
func drawSprite(sprite *d2ui.Sprite, target d2interface.Surface) {
if sprite != nil {
sprite.Render(target)
sprite.RenderNoError(target)
}
}

View File

@ -370,7 +370,7 @@ func NewGameControls(
inputListener: inputListener,
mapRenderer: mapRenderer,
inventory: NewInventory(asset, ui, inventoryRecord),
skilltree: newSkillTree(hero.Skills, hero.Class, asset, renderer, ui, guiManager),
skilltree: newSkillTree(hero.Skills, hero.Class, asset, ui),
heroStatsPanel: NewHeroStatsPanel(asset, ui, hero.Name(), hero.Class, hero.Stats),
HelpOverlay: helpOverlay,
hud: hud,
@ -773,11 +773,6 @@ func (g *GameControls) renderPanels(target d2interface.Surface) error {
g.heroStatsPanel.Render(target)
g.inventory.Render(target)
err := g.skilltree.Render(target)
if err != nil {
log.Println(err)
}
return nil
}
@ -1014,18 +1009,23 @@ func (g *GameControls) bindLearnSkillsCommand(term d2interface.Terminal) error {
continue
}
skill, skillErr := g.heroState.CreateHeroSkill(1, skillDetailRecord.Skill)
if skill == nil {
continue
}
if skill, ok := g.hero.Skills[skillDetailRecord.ID]; ok {
skill.SkillPoints++
learnedSkillsCount++
} else {
skill, skillErr := g.heroState.CreateHeroSkill(1, skillDetailRecord.Skill)
if skill == nil {
continue
}
learnedSkillsCount++
learnedSkillsCount++
g.hero.Skills[skill.ID] = skill
g.hero.Skills[skill.ID] = skill
if skillErr != nil {
err = skillErr
break
if skillErr != nil {
err = skillErr
break
}
}
}

View File

@ -620,11 +620,11 @@ func (h *HelpOverlay) Render(target d2interface.Surface) error {
}
for _, f := range h.frames {
f.Render(target)
f.RenderNoError(target)
}
for _, t := range h.text {
t.Render(target)
t.RenderNoError(target)
}
for _, l := range h.lines {

View File

@ -242,7 +242,7 @@ func (s *HeroStatsPanel) renderStaticPanelFrames(target d2interface.Surface) err
s.panel.SetPosition(currentX-w, currentY+h)
}
s.panel.Render(target)
s.panel.RenderNoError(target)
}
return nil
@ -295,7 +295,7 @@ func (s *HeroStatsPanel) renderStaticLabels(target d2interface.Surface) {
cfg.centerAlign,
})
label.Render(target)
label.RenderNoError(target)
}
}
@ -348,7 +348,7 @@ func (s *HeroStatsPanel) renderStatValues(target d2interface.Surface) {
func (s *HeroStatsPanel) renderStatValueNum(label *d2ui.Label, value int,
target d2interface.Surface) {
label.SetText(strconv.Itoa(value))
label.Render(target)
label.RenderNoError(target)
}
func (s *HeroStatsPanel) createStatValueLabel(stat, x, y int) *d2ui.Label {

View File

@ -391,7 +391,7 @@ func (h *HUD) renderManaGlobe(x, _ int, target d2interface.Surface) error {
h.mainPanel.SetPosition(x, height)
h.mainPanel.Render(target)
h.mainPanel.RenderNoError(target)
// Mana status bar
manaPercent := float64(h.hero.Stats.Mana) / float64(h.hero.Stats.MaxMana)
@ -413,8 +413,8 @@ func (h *HUD) renderManaGlobe(x, _ int, target d2interface.Surface) error {
h.globeSprite.SetPosition(x+rightGlobeOffsetX, height+rightGlobeOffsetY)
h.globeSprite.Render(target)
h.globeSprite.Render(target)
h.globeSprite.RenderNoError(target)
h.globeSprite.RenderNoError(target)
return nil
}
@ -438,7 +438,7 @@ func (h *HUD) renderHealthGlobe(x, y int, target d2interface.Surface) error {
}
h.globeSprite.SetPosition(x+globeSpriteOffsetX, y+globeSpriteOffsetY)
h.globeSprite.Render(target)
h.globeSprite.RenderNoError(target)
return nil
}
@ -449,7 +449,7 @@ func (h *HUD) renderPanel(x, y int, target d2interface.Surface) error {
}
h.mainPanel.SetPosition(x, y)
h.mainPanel.Render(target)
h.mainPanel.RenderNoError(target)
return nil
}
@ -466,7 +466,7 @@ func (h *HUD) renderLeftSkill(x, y int, target d2interface.Surface) error {
}
h.leftSkillResource.SkillIcon.SetPosition(x, y)
h.leftSkillResource.SkillIcon.Render(target)
h.leftSkillResource.SkillIcon.RenderNoError(target)
return nil
}
@ -485,7 +485,7 @@ func (h *HUD) renderRightSkill(x, _ int, target d2interface.Surface) error {
}
h.rightSkillResource.SkillIcon.SetPosition(x, height)
h.rightSkillResource.SkillIcon.Render(target)
h.rightSkillResource.SkillIcon.RenderNoError(target)
return nil
}
@ -496,7 +496,7 @@ func (h *HUD) renderNewStatsButton(x, y int, target d2interface.Surface) error {
}
h.mainPanel.SetPosition(x, y)
h.mainPanel.Render(target)
h.mainPanel.RenderNoError(target)
return nil
}
@ -507,7 +507,7 @@ func (h *HUD) renderStamina(x, y int, target d2interface.Surface) error {
}
h.mainPanel.SetPosition(x, y)
h.mainPanel.Render(target)
h.mainPanel.RenderNoError(target)
return nil
}
@ -558,7 +558,7 @@ func (h *HUD) renderMiniPanel(target d2interface.Surface) error {
buttonX, buttonY := (width>>1)+miniPanelButtonOffsetX, height+miniPanelButtonOffsetY
h.menuButton.SetPosition(buttonX, buttonY)
h.menuButton.Render(target)
h.menuButton.RenderNoError(target)
h.miniPanel.Render(target)
miniPanelButtons := map[actionableType]string{
@ -595,7 +595,10 @@ func (h *HUD) renderMiniPanel(target d2interface.Surface) error {
labelY := centerY - halfButtonHeight - labelHeight
h.miniPanelTooltip.SetPosition(labelX, labelY)
h.miniPanelTooltip.Render(target)
if err := h.miniPanelTooltip.Render(target); err != nil {
return err
}
}
return nil
@ -609,7 +612,7 @@ func (h *HUD) renderPotions(x, _ int, target d2interface.Surface) error {
}
h.mainPanel.SetPosition(x, height)
h.mainPanel.Render(target)
h.mainPanel.RenderNoError(target)
return nil
}
@ -622,11 +625,12 @@ func (h *HUD) renderNewSkillsButton(x, _ int, target d2interface.Surface) error
}
h.mainPanel.SetPosition(x, height)
h.mainPanel.Render(target)
h.mainPanel.RenderNoError(target)
return nil
}
//nolint:golint,dupl // we clean this up later
func (h *HUD) renderHealthTooltip(target d2interface.Surface) {
mx, my := h.lastMouseX, h.lastMouseY
@ -641,9 +645,13 @@ func (h *HUD) renderHealthTooltip(target d2interface.Surface) {
}
h.healthTooltip.SetText(strPanelHealth)
h.healthTooltip.Render(target)
if err := h.healthTooltip.Render(target); err != nil {
log.Printf("Cannot render tooltip, %e", err)
}
}
//nolint:golint,dupl // we clean this up later
func (h *HUD) renderManaTooltip(target d2interface.Surface) {
mx, my := h.lastMouseX, h.lastMouseY
@ -657,7 +665,10 @@ func (h *HUD) renderManaTooltip(target d2interface.Surface) {
}
h.manaTooltip.SetText(strPanelMana)
h.manaTooltip.Render(target)
if err := h.manaTooltip.Render(target); err != nil {
log.Printf("Cannot render tooltip, %e", err)
}
}
func (h *HUD) renderRunWalkTooltip(target d2interface.Surface) {
@ -679,7 +690,9 @@ func (h *HUD) renderRunWalkTooltip(target d2interface.Surface) {
h.runWalkTooltip.SetText(h.asset.TranslateString(stringTableKey))
h.runWalkTooltip.Render(target)
if err := h.runWalkTooltip.Render(target); err != nil {
log.Printf("Cannot render tooltip, %e", err)
}
}
func (h *HUD) renderStaminaTooltip(target d2interface.Surface) {
@ -696,7 +709,10 @@ func (h *HUD) renderStaminaTooltip(target d2interface.Surface) {
strPanelStamina := fmt.Sprintf(fmtStamina, staminaCurr, staminaMax)
h.staminaTooltip.SetText(strPanelStamina)
h.staminaTooltip.Render(target)
if err := h.staminaTooltip.Render(target); err != nil {
log.Printf("Cannot render tooltip, %e", err)
}
}
func (h *HUD) renderExperienceTooltip(target d2interface.Surface) {
@ -720,7 +736,10 @@ func (h *HUD) renderExperienceTooltip(target d2interface.Surface) {
strPanelExp := fmt.Sprintf(fmtExp, expCurr, expMax)
h.experienceTooltip.SetText(strPanelExp)
h.experienceTooltip.Render(target)
if err := h.experienceTooltip.Render(target); err != nil {
log.Printf("Cannot render tooltip, %e", err)
}
}
func (h *HUD) renderForSelectableEntitiesHovered(target d2interface.Surface) {
@ -753,7 +772,7 @@ func (h *HUD) renderForSelectableEntitiesHovered(target d2interface.Surface) {
xLabel, yLabel := entScreenX-xOff, entScreenY-yOff-entityHeight-hoverLabelOuterPad
h.nameLabel.SetPosition(xLabel, yLabel)
h.nameLabel.Render(target)
h.nameLabel.RenderNoError(target)
entity.Highlight()
break
@ -775,7 +794,7 @@ func (h *HUD) Render(target d2interface.Surface) error {
if h.isZoneTextShown {
h.zoneChangeText.SetPosition(zoneChangeTextX, zoneChangeTextY)
h.zoneChangeText.Render(target)
h.zoneChangeText.RenderNoError(target)
}
h.renderHealthTooltip(target)

View File

@ -196,7 +196,7 @@ func (g *Inventory) renderFrame(target d2interface.Surface) error {
w, h := g.panel.GetCurrentFrameSize()
g.panel.SetPosition(x, y+h)
g.panel.Render(target)
g.panel.RenderNoError(target)
switch frame {
case frameInventoryTopLeft:
@ -242,5 +242,8 @@ func (g *Inventory) renderItemDescription(target d2interface.Surface, i Inventor
g.itemTooltip.SetTextLines(lines)
_, y := g.grid.SlotToScreen(i.InventoryGridSlot())
g.itemTooltip.SetPosition(g.hoverX, y)
g.itemTooltip.Render(target)
if err := g.itemTooltip.Render(target); err != nil {
log.Printf("Cannot render tooltip, %e", err)
}
}

View File

@ -234,7 +234,7 @@ func (g *ItemGrid) renderItem(item InventoryItem, target d2interface.Surface, x,
if itemSprite != nil {
itemSprite.SetPosition(x, y)
itemSprite.GetCurrentFrameSize()
itemSprite.Render(target)
itemSprite.RenderNoError(target)
}
}

View File

@ -104,7 +104,7 @@ func (m *miniPanel) Render(target d2interface.Surface) {
m.container.SetPosition(x, y)
m.container.Render(target)
m.container.RenderNoError(target)
buttonWidth, _ := m.button.GetCurrentFrameSize()
buttonWidth++
@ -122,7 +122,7 @@ func (m *miniPanel) Render(target d2interface.Surface) {
x, y := halfW+offsetX, height+buttonOffsetY
m.button.SetPosition(x, y)
m.button.Render(target)
m.button.RenderNoError(target)
j += 2
}

View File

@ -129,7 +129,9 @@ func (s *SkillPanel) Render(target d2interface.Surface) error {
}
if s.hoveredSkill != nil {
s.hoverTooltip.Render(target)
if err := s.hoverTooltip.Render(target); err != nil {
log.Printf("Cannot render tooltip, %e", err)
}
}
return nil
@ -240,7 +242,7 @@ func (s *SkillPanel) createSkillListImage(skillsListRow *SkillListRow) (d2interf
}
surface.PushTranslation(idx*skillIconWidth, 50)
skillSprite.Render(surface)
skillSprite.RenderNoError(surface)
surface.Pop()
}

View File

@ -0,0 +1,99 @@
package d2player
import (
"strconv"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2hero"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2ui"
)
const (
skillLabelXOffset = 49
skillLabelYOffset = -4
skillIconXOff = 346
skillIconYOff = 59
skillIconDistX = 69
skillIconDistY = 68
)
type skillIcon struct {
*d2ui.BaseWidget
lvlLabel *d2ui.Label
sprite *d2ui.Sprite
skill *d2hero.HeroSkill
}
func newSkillIcon(ui *d2ui.UIManager, baseSprite *d2ui.Sprite, skill *d2hero.HeroSkill) *skillIcon {
base := d2ui.NewBaseWidget(ui)
label := ui.NewLabel(d2resource.Font16, d2resource.PaletteSky)
x := skillIconXOff + skill.SkillColumn*skillIconDistX
y := skillIconYOff + skill.SkillRow*skillIconDistY
res := &skillIcon{
BaseWidget: base,
sprite: baseSprite,
skill: skill,
lvlLabel: label,
}
res.SetPosition(x, y)
return res
}
func (si *skillIcon) SetVisible(visible bool) {
si.BaseWidget.SetVisible(visible)
si.lvlLabel.SetVisible(visible)
}
func (si *skillIcon) renderSprite(target d2interface.Surface) error {
x, y := si.GetPosition()
if err := si.sprite.SetCurrentFrame(si.skill.IconCel); err != nil {
return err
}
if si.skill.SkillPoints == 0 {
target.PushSaturation(skillIconGreySat)
defer target.Pop()
target.PushBrightness(skillIconGreyBright)
defer target.Pop()
}
si.sprite.SetPosition(x, y)
if err := si.sprite.Render(target); err != nil {
return err
}
return nil
}
func (si *skillIcon) renderSpriteLabel(target d2interface.Surface) error {
if si.skill.SkillPoints == 0 {
return nil
}
x, y := si.GetPosition()
si.lvlLabel.SetText(strconv.Itoa(si.skill.SkillPoints))
si.lvlLabel.SetPosition(x+skillLabelXOffset, y+skillLabelYOffset)
return si.lvlLabel.Render(target)
}
func (si *skillIcon) Render(target d2interface.Surface) error {
if err := si.renderSprite(target); err != nil {
return err
}
return si.renderSpriteLabel(target)
}
func (si *skillIcon) Advance(elapsed float64) error {
return nil
}

View File

@ -3,7 +3,6 @@ package d2player
import (
"fmt"
"log"
"strconv"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
@ -23,14 +22,6 @@ const (
availSPLabelX = 677
availSPLabelY = 72
skillIconXOff = 346
skillIconYOff = 59
skillIconDistX = 69
skillIconDistY = 68
skillLabelXOffset = 49
skillLabelYOffset = -4
skillCloseButtonXLeft = 416
skillCloseButtonXMiddle = 501
skillCloseButtonXRight = 572
@ -83,55 +74,50 @@ type skillTreeTab struct {
func (st *skillTreeTab) createButton(uiManager *d2ui.UIManager, x, y int) {
st.button = uiManager.NewButton(d2ui.ButtonTypeSkillTreeTab, st.buttonText)
st.button.SetVisible(false)
st.button.SetPosition(x, y)
}
type skillTreeHeroTypeResources struct {
skillIcon *d2ui.Sprite
skillSprite *d2ui.Sprite
skillIconPath string
skillPanel *d2ui.Sprite
skillPanelPath string
}
type skillTree struct {
resources *skillTreeHeroTypeResources
asset *d2asset.AssetManager
renderer d2interface.Renderer
guiManager *d2gui.GuiManager
uiManager *d2ui.UIManager
layout *d2gui.Layout
skills map[int]*d2hero.HeroSkill
heroClass d2enum.Hero
frame *d2ui.UIFrame
availSPLabel *d2ui.Label
skillLvlLabel *d2ui.Label
closeButton *d2ui.Button
tab [numTabs]*skillTreeTab
isOpen bool
originX int
originY int
selectedTab int
onCloseCb func()
resources *skillTreeHeroTypeResources
asset *d2asset.AssetManager
uiManager *d2ui.UIManager
skills map[int]*d2hero.HeroSkill
skillIcons []*skillIcon
heroClass d2enum.Hero
frame *d2ui.UIFrame
availSPLabel *d2ui.Label
closeButton *d2ui.Button
tab [numTabs]*skillTreeTab
isOpen bool
originX int
originY int
selectedTab int
onCloseCb func()
panelGroup *d2ui.WidgetGroup
iconGroup *d2ui.WidgetGroup
panel *d2ui.CustomWidget
}
func newSkillTree(
skills map[int]*d2hero.HeroSkill,
heroClass d2enum.Hero,
asset *d2asset.AssetManager,
renderer d2interface.Renderer,
ui *d2ui.UIManager,
guiManager *d2gui.GuiManager,
) *skillTree {
st := &skillTree{
skills: skills,
heroClass: heroClass,
asset: asset,
renderer: renderer,
uiManager: ui,
guiManager: guiManager,
originX: skillTreePanelX,
originY: skillTreePanelY,
skills: skills,
heroClass: heroClass,
asset: asset,
uiManager: ui,
originX: skillTreePanelX,
originY: skillTreePanelY,
tab: [numTabs]*skillTreeTab{
{},
{},
@ -143,16 +129,32 @@ func newSkillTree(
}
func (s *skillTree) load() {
s.panelGroup = s.uiManager.NewWidgetGroup(d2ui.RenderPrioritySkilltree)
s.iconGroup = s.uiManager.NewWidgetGroup(d2ui.RenderPrioritySkilltreeIcon)
s.panel = s.uiManager.NewCustomWidget(s.Render)
s.panelGroup.AddWidget(s.panel)
s.frame = d2ui.NewUIFrame(s.asset, s.uiManager, d2ui.FrameRight)
s.panelGroup.AddWidget(s.frame)
s.closeButton = s.uiManager.NewButton(d2ui.ButtonTypeSquareClose, "")
s.closeButton.SetVisible(false)
s.closeButton.OnActivated(func() { s.Close() })
s.skillLvlLabel = s.uiManager.NewLabel(d2resource.Font16, d2resource.PaletteSky)
s.panelGroup.AddWidget(s.closeButton)
s.setHeroTypeResourcePath()
s.loadForHeroType()
for _, skill := range s.skills {
si := newSkillIcon(s.uiManager, s.resources.skillSprite, skill)
s.skillIcons = append(s.skillIcons, si)
s.iconGroup.AddWidget(si)
}
s.panelGroup.SetVisible(false)
s.setTab(0)
s.iconGroup.SetVisible(false)
}
func (s *skillTree) loadForHeroType() {
@ -168,21 +170,25 @@ func (s *skillTree) loadForHeroType() {
log.Print(err)
}
s.resources.skillIcon = si
s.resources.skillSprite = si
s.tab[firstTab].createButton(s.uiManager, tabButtonX, tabButton0Y)
s.tab[firstTab].button.OnActivated(func() { s.setTab(firstTab) })
s.panelGroup.AddWidget(s.tab[firstTab].button)
s.tab[secondTab].createButton(s.uiManager, tabButtonX, tabButton1Y)
s.tab[secondTab].button.OnActivated(func() { s.setTab(secondTab) })
s.panelGroup.AddWidget(s.tab[secondTab].button)
s.tab[thirdTab].createButton(s.uiManager, tabButtonX, tabButton2Y)
s.tab[thirdTab].button.OnActivated(func() { s.setTab(thirdTab) })
s.panelGroup.AddWidget(s.tab[thirdTab].button)
s.availSPLabel = s.uiManager.NewLabel(d2resource.Font16, d2resource.PaletteSky)
s.availSPLabel.SetPosition(availSPLabelX, availSPLabelY)
s.availSPLabel.Alignment = d2gui.HorizontalAlignCenter
s.availSPLabel.SetText(s.makeTabString("StrSklTree1", "StrSklTree2", "StrSklTree3"))
s.panelGroup.AddWidget(s.availSPLabel)
}
type heroTabData struct {
@ -340,12 +346,9 @@ func (s *skillTree) Toggle() {
// Close the skill tree
func (s *skillTree) Close() {
s.isOpen = false
s.guiManager.SetLayout(nil)
s.closeButton.SetVisible(false)
for i := 0; i < numTabs; i++ {
s.tab[i].button.SetVisible(false)
}
s.panelGroup.SetVisible(false)
s.iconGroup.SetVisible(false)
s.onCloseCb()
}
@ -353,17 +356,11 @@ func (s *skillTree) Close() {
// Open the skill tree
func (s *skillTree) Open() {
s.isOpen = true
if s.layout == nil {
s.layout = d2gui.CreateLayout(s.renderer, d2gui.PositionTypeHorizontal, s.asset)
}
s.closeButton.SetVisible(true)
s.panelGroup.SetVisible(true)
for i := 0; i < numTabs; i++ {
s.tab[i].button.SetVisible(true)
}
s.guiManager.SetLayout(s.layout)
// we only want to enable the icons of our current tab again
s.setTab(s.selectedTab)
}
func (s *skillTree) IsOpen() bool {
@ -378,6 +375,10 @@ func (s *skillTree) SetOnCloseCb(cb func()) {
func (s *skillTree) setTab(tab int) {
s.selectedTab = tab
s.closeButton.SetPosition(s.tab[tab].closeButtonPosX, skillCloseButtonY)
for _, si := range s.skillIcons {
si.SetVisible(si.skill.SkillPage == tab+1)
}
}
func (s *skillTree) renderPanelSegment(
@ -387,7 +388,7 @@ func (s *skillTree) renderPanelSegment(
return err
}
s.resources.skillPanel.Render(target)
s.resources.skillPanel.RenderNoError(target)
return nil
}
@ -441,7 +442,7 @@ func (s *skillTree) renderTabCommon(target d2interface.Surface) error {
}
// available skill points label
s.availSPLabel.Render(target)
s.availSPLabel.RenderNoError(target)
return nil
}
@ -512,69 +513,8 @@ func (s *skillTree) renderTab(target d2interface.Surface, tab int) error {
return nil
}
func (s *skillTree) renderSkillIcon(target d2interface.Surface, skill *d2hero.HeroSkill) error {
skillIcon := s.resources.skillIcon
if err := skillIcon.SetCurrentFrame(skill.IconCel); err != nil {
return err
}
x := skillIconXOff + skill.SkillColumn*skillIconDistX
y := skillIconYOff + skill.SkillRow*skillIconDistY
skillIcon.SetPosition(x, y)
if skill.SkillPoints == 0 {
target.PushSaturation(skillIconGreySat)
defer target.Pop()
target.PushBrightness(skillIconGreyBright)
defer target.Pop()
}
skillIcon.Render(target)
return nil
}
func (s *skillTree) renderSkillIconLabel(target d2interface.Surface, skill *d2hero.HeroSkill) {
if skill.SkillPoints == 0 {
return
}
s.skillLvlLabel.SetText(strconv.Itoa(skill.SkillPoints))
x := skillIconXOff + skill.SkillColumn*skillIconDistX + skillLabelXOffset
y := skillIconYOff + skill.SkillRow*skillIconDistY + skillLabelYOffset
s.skillLvlLabel.SetPosition(x, y)
s.skillLvlLabel.Render(target)
}
func (s *skillTree) renderSkillIcons(target d2interface.Surface, tab int) error {
for idx := range s.skills {
skill := s.skills[idx]
if skill.SkillPage != tab+1 {
continue
}
if err := s.renderSkillIcon(target, skill); err != nil {
return err
}
s.renderSkillIconLabel(target, skill)
}
return nil
}
// Render the skill tree panel
func (s *skillTree) Render(target d2interface.Surface) error {
if !s.isOpen {
return nil
}
if err := s.frame.Render(target); err != nil {
return err
}
if err := s.renderTabCommon(target); err != nil {
return err
}
@ -583,9 +523,5 @@ func (s *skillTree) Render(target d2interface.Surface) error {
return err
}
if err := s.renderSkillIcons(target, s.selectedTab); err != nil {
return err
}
return nil
}