Ui panel refactor part 2 (#921)

* d2ui/skilltree: Don't render availSPLabel

this is handled by the ui_manager now.

* d2ui/custom_widget: Allow them to be cached into static images

* d2player/hero_stats_panel: Remove render() function from game_controls

all ui elements are now grouped into a WidgetGroup, thus rendering is
done by the ui manager.

* d2player/hero_stats_panel: Remove unnecessary widgets from struct

we don't need to store them in the HeroStatsPanel struct anymore as they
are completly handled by the uiManager.

* d2ui/widget_group: Remove priority member

this is already defined by the BaseWidget.

* d2ui/widget: Move uiManager.contains() to the baseWidgets

this method makes more sense on a widget anyways.

* d2ui/widget: Add methods to handle widget hovering

* d2ui/custom_widget: Require define width/height

since the custom render() method can do whatever, we need the user to specify
the width/height such that GetSize() calls are meaningful.

* d2ui/widget: Allow widgets to return the uiManager

* d2player/HUD: Refactor health/mana globe into its own widget

* d2player/hud: Refactor load()

seperate each type of loading into its own method.

* d2player/HUD: Move stamina/exp bar into widgets

* d2player/HUD: Refactor left/right skills into widget

* d2ui/custom_widget: cached custom widgets should use widget.x/y

since we render to an image, we use widget.x/y to position the cached
image.

* d2player/HUD: User cached custom widget for all static images
This commit is contained in:
juander-ux 2020-11-13 21:08:43 +01:00 committed by GitHub
parent 0d691dbffa
commit 12821147ce
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 433 additions and 256 deletions

View File

@ -9,11 +9,29 @@ var _ Widget = &CustomWidget{}
type CustomWidget struct {
*BaseWidget
renderFunc func(target d2interface.Surface)
cached bool
cachedImg *d2interface.Surface
}
// NewCustomWidgetCached creates a new widget and caches anything rendered via the
// renderFunc into a static image to be displayed
func (ui *UIManager) NewCustomWidgetCached(renderFunc func(target d2interface.Surface), width, height int) *CustomWidget {
c := ui.NewCustomWidget(renderFunc, width, height)
c.cached = true
// render using the renderFunc to a cache
surface := ui.Renderer().NewSurface(width, height)
c.cachedImg = &surface
renderFunc(*c.cachedImg)
return c
}
// NewCustomWidget creates a new widget with custom render function
func (ui *UIManager) NewCustomWidget(renderFunc func(target d2interface.Surface)) *CustomWidget {
func (ui *UIManager) NewCustomWidget(renderFunc func(target d2interface.Surface), width, height int) *CustomWidget {
base := NewBaseWidget(ui)
base.width = width
base.height = height
return &CustomWidget{
BaseWidget: base,
@ -23,7 +41,13 @@ func (ui *UIManager) NewCustomWidget(renderFunc func(target d2interface.Surface)
// Render draws the custom widget
func (c *CustomWidget) Render(target d2interface.Surface) {
c.renderFunc(target)
if c.cached {
target.PushTranslation(c.GetPosition())
target.Render(*c.cachedImg)
target.Pop()
} else {
c.renderFunc(target)
}
}
// Advance is a no-op

View File

@ -54,8 +54,9 @@ func (ui *UIManager) Reset() {
// 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
return ui.widgetsGroups[i].renderPriority < ui.widgetsGroups[j].renderPriority
})
}
@ -83,7 +84,7 @@ 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 && w.Contains(ui.CursorX, ui.CursorY) && w.GetVisible() && w.GetEnabled() {
w.Activate()
}
@ -96,6 +97,17 @@ func (ui *UIManager) OnMouseButtonUp(event d2interface.MouseEvent) bool {
return false
}
// OnMouseMove is the mouse move event handler
func (ui *UIManager) OnMouseMove(event d2interface.MouseMoveEvent) bool {
for _, w := range ui.widgetsGroups {
if w.GetVisible() {
w.OnMouseMove(event.X(), event.Y())
}
}
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()
@ -103,7 +115,7 @@ func (ui *UIManager) OnMouseButtonDown(event d2interface.MouseEvent) bool {
// find and press a widget on screen
ui.pressedWidget = nil
for _, w := range ui.clickableWidgets {
if ui.contains(w, ui.CursorX, ui.CursorY) && w.GetVisible() && w.GetEnabled() {
if w.Contains(ui.CursorX, ui.CursorY) && w.GetVisible() && w.GetEnabled() {
w.SetPressed(true)
ui.pressedWidget = w
ui.clickSfx.Play()
@ -135,14 +147,6 @@ func (ui *UIManager) Render(target d2interface.Surface) {
}
}
// 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 {

View File

@ -11,6 +11,8 @@ const (
RenderPrioritySkilltree
// RenderPrioritySkilltreeIcon is the priority for the skilltree icons
RenderPrioritySkilltreeIcon
// RenderPriorityHeroStatsPanel is the priority for the hero_stats_panel
RenderPriorityHeroStatsPanel
// RenderPriorityForeground is the last element drawn
RenderPriorityForeground
)
@ -19,6 +21,13 @@ const (
type Widget interface {
Drawable
bindManager(ui *UIManager)
GetManager() (ui *UIManager)
OnHoverStart(callback func())
OnHoverEnd(callback func())
isHovered() bool
hoverStart()
hoverEnd()
Contains(x, y int) (contained bool)
}
// ClickableWidget defines an object that can be clicked
@ -41,6 +50,10 @@ type BaseWidget struct {
height int
renderPriority RenderPriority
visible bool
hovered bool
onHoverStartCb func()
onHoverEndCb func()
}
// NewBaseWidget creates a new BaseWidget with defaults
@ -100,3 +113,46 @@ func (b *BaseWidget) GetRenderPriority() (prio RenderPriority) {
func (b *BaseWidget) SetRenderPriority(prio RenderPriority) {
b.renderPriority = prio
}
// OnHoverStart sets a function that is called if the hovering of the widget starts
func (b *BaseWidget) OnHoverStart(callback func()) {
b.onHoverStartCb = callback
}
// HoverStart is called when the hovering of the widget starts
func (b *BaseWidget) hoverStart() {
b.hovered = true
if b.onHoverStartCb != nil {
b.onHoverStartCb()
}
}
// OnHoverEnd sets a function that is called if the hovering of the widget ends
func (b *BaseWidget) OnHoverEnd(callback func()) {
b.onHoverEndCb = callback
}
// hoverEnd is called when the widget hovering ends
func (b *BaseWidget) hoverEnd() {
b.hovered = false
if b.onHoverEndCb != nil {
b.onHoverEndCb()
}
}
func (b *BaseWidget) isHovered() bool {
return b.hovered
}
// Contains determines whether a given x,y coordinate lands within a Widget
func (b *BaseWidget) Contains(x, y int) bool {
wx, wy := b.GetPosition()
ww, wh := b.GetSize()
return x >= wx && x <= wx+ww && y >= wy && y <= wy+wh
}
// GetManager returns the uiManager
func (b *BaseWidget) GetManager() (ui *UIManager) {
return b.manager
}

View File

@ -13,8 +13,7 @@ var _ Widget = &WidgetGroup{}
// widgets at once.
type WidgetGroup struct {
*BaseWidget
entries []Widget
priority RenderPriority
entries []Widget
}
// NewWidgetGroup creates a new widget group
@ -85,3 +84,16 @@ func (wg *WidgetGroup) SetVisible(visible bool) {
entry.SetVisible(visible)
}
}
// OnMouseMove handles mouse move events
func (wg *WidgetGroup) OnMouseMove(x, y int) {
for _, entry := range wg.entries {
if entry.Contains(x, y) && entry.GetVisible() {
if !entry.isHovered() {
entry.hoverStart()
}
} else if entry.isHovered() {
entry.hoverEnd()
}
}
}

View File

@ -57,7 +57,7 @@ const (
leftSkillX,
leftSkillY,
leftSkillWidth,
leftSkillHeight = 115, 550, 50, 50
leftSkillHeight = 117, 550, 50, 50
newStatsX,
newStatsY,
@ -92,7 +92,7 @@ const (
rightSkillX,
rightSkillY,
rightSkillWidth,
rightSkillHeight = 634, 550, 50, 50
rightSkillHeight = 635, 550, 50, 50
hpGlobeX,
hpGlobeY,
@ -783,7 +783,6 @@ func (g *GameControls) Render(target d2interface.Surface) error {
}
func (g *GameControls) renderPanels(target d2interface.Surface) error {
g.heroStatsPanel.Render(target)
g.inventory.Render(target)
return nil

View File

@ -0,0 +1,145 @@
package d2player
import (
"image"
"log"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2ui"
)
type globeType = int
const (
typeHealthGlobe globeType = iota
typeManaGlobe
)
const (
globeHeight = 80
globeWidth = 80
globeSpriteOffsetX = 28
globeSpriteOffsetY = -5
healthStatusOffsetX = 30
healthStatusOffsetY = -13
manaStatusOffsetX = 7
manaStatusOffsetY = -12
manaGlobeScreenOffsetX = 117
)
// static check that globeWidget implements Widget
var _ d2ui.Widget = &globeWidget{}
type globeFrame struct {
sprite *d2ui.Sprite
offsetX int
offsetY int
idx int
}
func (gf *globeFrame) setFrameIndex() {
if err := gf.sprite.SetCurrentFrame(gf.idx); err != nil {
log.Print(err)
}
}
func (gf *globeFrame) setPosition(x, y int) {
gf.sprite.SetPosition(x+gf.offsetX, y+gf.offsetY)
}
func (gf *globeFrame) getSize() (x, y int) {
w, h := gf.sprite.GetSize()
return w + gf.offsetX, h + gf.offsetY
}
type globeWidget struct {
*d2ui.BaseWidget
value *int
valueMax *int
globe *globeFrame
overlap *globeFrame
}
func newGlobeWidget(ui *d2ui.UIManager, x, y int, gtype globeType, value, valueMax *int) *globeWidget {
var globe, overlap *globeFrame
base := d2ui.NewBaseWidget(ui)
base.SetPosition(x, y)
if gtype == typeHealthGlobe {
globe = &globeFrame{
offsetX: healthStatusOffsetX,
offsetY: healthStatusOffsetY,
idx: frameHealthStatus,
}
overlap = &globeFrame{
offsetX: globeSpriteOffsetX,
offsetY: globeSpriteOffsetY,
idx: frameHealthStatus,
}
} else if gtype == typeManaGlobe {
globe = &globeFrame{
offsetX: manaStatusOffsetX,
offsetY: manaStatusOffsetY,
idx: frameManaStatus,
}
overlap = &globeFrame{
offsetX: rightGlobeOffsetX,
offsetY: rightGlobeOffsetY,
idx: frameRightGlobe,
}
}
return &globeWidget{
BaseWidget: base,
value: value,
valueMax: valueMax,
globe: globe,
overlap: overlap,
}
}
func (g *globeWidget) load() {
var err error
g.globe.sprite, err = g.GetManager().NewSprite(d2resource.HealthManaIndicator, d2resource.PaletteSky)
if err != nil {
log.Print(err)
}
g.globe.setFrameIndex()
g.overlap.sprite, err = g.GetManager().NewSprite(d2resource.GameGlobeOverlap, d2resource.PaletteSky)
if err != nil {
log.Print(err)
}
g.overlap.setFrameIndex()
}
// Render draws the widget to the screen
func (g *globeWidget) Render(target d2interface.Surface) {
valuePercent := float64(*g.value) / float64(*g.valueMax)
barHeight := int(valuePercent * float64(globeHeight))
maskRect := image.Rect(0, globeHeight-barHeight, globeWidth, globeHeight)
g.globe.setPosition(g.GetPosition())
g.globe.sprite.RenderSection(target, maskRect)
g.overlap.setPosition(g.GetPosition())
g.overlap.sprite.Render(target)
}
func (g *globeWidget) GetSize() (x, y int) {
return g.overlap.getSize()
}
func (g *globeWidget) Advance(elapsed float64) error {
return nil
}

View File

@ -84,18 +84,15 @@ type StatsPanelLabels struct {
// HeroStatsPanel represents the hero status panel
type HeroStatsPanel struct {
asset *d2asset.AssetManager
uiManager *d2ui.UIManager
frame *d2ui.UIFrame
panel *d2ui.Sprite
heroState *d2hero.HeroStatsState
heroName string
heroClass d2enum.Hero
renderer d2interface.Renderer
staticMenuImageCache *d2interface.Surface
labels *StatsPanelLabels
closeButton *d2ui.Button
onCloseCb func()
asset *d2asset.AssetManager
uiManager *d2ui.UIManager
panel *d2ui.Sprite
heroState *d2hero.HeroStatsState
heroName string
heroClass d2enum.Hero
labels *StatsPanelLabels
onCloseCb func()
panelGroup *d2ui.WidgetGroup
originX int
originY int
@ -111,7 +108,6 @@ func NewHeroStatsPanel(asset *d2asset.AssetManager, ui *d2ui.UIManager, heroName
return &HeroStatsPanel{
asset: asset,
uiManager: ui,
renderer: ui.Renderer(),
originX: originX,
originY: originY,
heroState: heroState,
@ -125,19 +121,30 @@ func NewHeroStatsPanel(asset *d2asset.AssetManager, ui *d2ui.UIManager, heroName
func (s *HeroStatsPanel) Load() {
var err error
s.frame = d2ui.NewUIFrame(s.asset, s.uiManager, d2ui.FrameLeft)
s.panelGroup = s.uiManager.NewWidgetGroup(d2ui.RenderPriorityHeroStatsPanel)
s.closeButton = s.uiManager.NewButton(d2ui.ButtonTypeSquareClose, "")
s.closeButton.SetVisible(false)
s.closeButton.SetPosition(heroStatsCloseButtonX, heroStatsCloseButtonY)
s.closeButton.OnActivated(func() { s.Close() })
frame := d2ui.NewUIFrame(s.asset, s.uiManager, d2ui.FrameLeft)
s.panelGroup.AddWidget(frame)
s.panel, err = s.uiManager.NewSprite(d2resource.InventoryCharacterPanel, d2resource.PaletteSky)
if err != nil {
log.Print(err)
}
fw, fh := frame.GetFrameBounds()
fc := frame.GetFrameCount()
w, h := fw*fc, fh*fc
staticPanel := s.uiManager.NewCustomWidgetCached(s.renderStaticMenu, w, h)
s.panelGroup.AddWidget(staticPanel)
closeButton := s.uiManager.NewButton(d2ui.ButtonTypeSquareClose, "")
closeButton.SetVisible(false)
closeButton.SetPosition(heroStatsCloseButtonX, heroStatsCloseButtonY)
closeButton.OnActivated(func() { s.Close() })
s.panelGroup.AddWidget(closeButton)
s.initStatValueLabels()
s.panelGroup.SetVisible(false)
}
// IsOpen returns true if the hero status panel is open
@ -157,13 +164,13 @@ func (s *HeroStatsPanel) Toggle() {
// Open opens the hero status panel
func (s *HeroStatsPanel) Open() {
s.isOpen = true
s.closeButton.SetVisible(true)
s.panelGroup.SetVisible(true)
}
// Close closed the hero status panel
func (s *HeroStatsPanel) Close() {
s.isOpen = false
s.closeButton.SetVisible(false)
s.panelGroup.SetVisible(false)
s.onCloseCb()
}
@ -172,43 +179,21 @@ func (s *HeroStatsPanel) SetOnCloseCb(cb func()) {
s.onCloseCb = cb
}
// Render renders the hero status panel
func (s *HeroStatsPanel) Render(target d2interface.Surface) {
// Advance updates labels on the panel
func (s *HeroStatsPanel) Advance(elapsed float64) {
if !s.isOpen {
return
}
if s.staticMenuImageCache == nil {
frameWidth, frameHeight := s.frame.GetFrameBounds()
framesCount := s.frame.GetFrameCount()
surface := s.renderer.NewSurface(frameWidth*framesCount, frameHeight*framesCount)
s.staticMenuImageCache = &surface
err := s.renderStaticMenu(*s.staticMenuImageCache)
if err != nil {
log.Println(err)
}
}
target.Render(*s.staticMenuImageCache)
s.renderStatValues(target)
s.setStatValues()
}
func (s *HeroStatsPanel) renderStaticMenu(target d2interface.Surface) error {
if err := s.renderStaticPanelFrames(target); err != nil {
return err
}
func (s *HeroStatsPanel) renderStaticMenu(target d2interface.Surface) {
s.renderStaticPanelFrames(target)
s.renderStaticLabels(target)
return nil
}
func (s *HeroStatsPanel) renderStaticPanelFrames(target d2interface.Surface) error {
s.frame.Render(target)
func (s *HeroStatsPanel) renderStaticPanelFrames(target d2interface.Surface) {
frames := []int{
statsPanelTopLeft,
statsPanelTopRight,
@ -221,7 +206,7 @@ func (s *HeroStatsPanel) renderStaticPanelFrames(target d2interface.Surface) err
for _, frameIndex := range frames {
if err := s.panel.SetCurrentFrame(frameIndex); err != nil {
return err
log.Printf("%e", err)
}
w, h := s.panel.GetCurrentFrameSize()
@ -241,8 +226,6 @@ func (s *HeroStatsPanel) renderStaticPanelFrames(target d2interface.Surface) err
s.panel.Render(target)
}
return nil
}
func (s *HeroStatsPanel) renderStaticLabels(target d2interface.Surface) {
@ -322,30 +305,24 @@ func (s *HeroStatsPanel) initStatValueLabels() {
}
}
func (s *HeroStatsPanel) renderStatValues(target d2interface.Surface) {
s.renderStatValueNum(s.labels.Level, s.heroState.Level, target)
s.renderStatValueNum(s.labels.Experience, s.heroState.Experience, target)
s.renderStatValueNum(s.labels.NextLevelExp, s.heroState.NextLevelExp, target)
func (s *HeroStatsPanel) setStatValues() {
s.labels.Level.SetText(strconv.Itoa(s.heroState.Level))
s.labels.Experience.SetText(strconv.Itoa(s.heroState.Experience))
s.labels.NextLevelExp.SetText(strconv.Itoa(s.heroState.NextLevelExp))
s.renderStatValueNum(s.labels.Strength, s.heroState.Strength, target)
s.renderStatValueNum(s.labels.Dexterity, s.heroState.Dexterity, target)
s.renderStatValueNum(s.labels.Vitality, s.heroState.Vitality, target)
s.renderStatValueNum(s.labels.Energy, s.heroState.Energy, target)
s.labels.Strength.SetText(strconv.Itoa(s.heroState.Strength))
s.labels.Dexterity.SetText(strconv.Itoa(s.heroState.Dexterity))
s.labels.Vitality.SetText(strconv.Itoa(s.heroState.Vitality))
s.labels.Energy.SetText(strconv.Itoa(s.heroState.Energy))
s.renderStatValueNum(s.labels.MaxHealth, s.heroState.MaxHealth, target)
s.renderStatValueNum(s.labels.Health, s.heroState.Health, target)
s.labels.MaxHealth.SetText(strconv.Itoa(s.heroState.MaxHealth))
s.labels.Health.SetText(strconv.Itoa(s.heroState.Health))
s.renderStatValueNum(s.labels.MaxStamina, s.heroState.MaxStamina, target)
s.renderStatValueNum(s.labels.Stamina, int(s.heroState.Stamina), target)
s.labels.MaxStamina.SetText(strconv.Itoa(s.heroState.MaxStamina))
s.labels.Stamina.SetText(strconv.Itoa(int(s.heroState.Stamina)))
s.renderStatValueNum(s.labels.MaxMana, s.heroState.MaxMana, target)
s.renderStatValueNum(s.labels.Mana, s.heroState.Mana, target)
}
func (s *HeroStatsPanel) renderStatValueNum(label *d2ui.Label, value int,
target d2interface.Surface) {
label.SetText(strconv.Itoa(value))
label.Render(target)
s.labels.MaxMana.SetText(strconv.Itoa(s.heroState.MaxMana))
s.labels.Mana.SetText(strconv.Itoa(s.heroState.Mana))
}
func (s *HeroStatsPanel) createStatValueLabel(stat, x, y int) *d2ui.Label {
@ -361,6 +338,7 @@ func (s *HeroStatsPanel) createTextLabel(element PanelText) *d2ui.Label {
label.SetText(element.Text)
label.SetPosition(element.X, element.Y)
s.panelGroup.AddWidget(label)
return label
}

View File

@ -2,7 +2,6 @@ package d2player
import (
"fmt"
"image"
"log"
"math"
"strings"
@ -30,10 +29,9 @@ const (
const (
expBarWidth = 120.0
expBarHeight = 4
staminaBarWidth = 102.0
staminaBarHeight = 19.0
globeHeight = 80
globeWidth = 80
hoverLabelOuterPad = 5
percentStaminaBarLow = 0.25
)
@ -61,15 +59,6 @@ const (
)
const (
manaStatusOffsetX = 7
manaStatusOffsetY = -12
healthStatusOffsetX = 30
healthStatusOffsetY = -13
globeSpriteOffsetX = 28
globeSpriteOffsetY = -5
staminaBarOffsetX = 273
staminaBarOffsetY = 572
@ -120,6 +109,13 @@ type HUD struct {
manaTooltip *d2ui.Tooltip
miniPanelTooltip *d2ui.Tooltip
nameLabel *d2ui.Label
healthGlobe *globeWidget
manaGlobe *globeWidget
widgetStamina *d2ui.CustomWidget
widgetExperience *d2ui.CustomWidget
widgetLeftSkill *d2ui.CustomWidget
widgetRightSkill *d2ui.CustomWidget
panelBackground *d2ui.CustomWidget
}
// NewHUD creates a HUD object
@ -140,6 +136,9 @@ func NewHUD(
zoneLabel := ui.NewLabel(d2resource.Font30, d2resource.PaletteUnits)
zoneLabel.Alignment = d2ui.HorizontalAlignCenter
healthGlobe := newGlobeWidget(ui, 0, screenHeight, typeHealthGlobe, &hero.Stats.Health, &hero.Stats.MaxHealth)
manaGlobe := newGlobeWidget(ui, screenWidth-manaGlobeScreenOffsetX, screenHeight, typeManaGlobe, &hero.Stats.Mana, &hero.Stats.MaxMana)
return &HUD{
asset: asset,
uiManager: ui,
@ -152,11 +151,84 @@ func NewHUD(
nameLabel: nameLabel,
skillSelectMenu: NewSkillSelectMenu(asset, ui, hero),
zoneChangeText: zoneLabel,
healthGlobe: healthGlobe,
manaGlobe: manaGlobe,
}
}
// Load creates the ui elemets
func (h *HUD) Load() {
h.loadSprites()
h.healthGlobe.load()
h.manaGlobe.load()
h.loadSkillResources()
h.loadCustomWidgets()
h.loadUIButtons()
h.loadTooltips()
}
func (h *HUD) loadCustomWidgets() {
// static background
_, height, err := h.mainPanel.GetFrameSize(0) // health globe is the frame with max height
if err != nil {
log.Print(err)
return
}
h.panelBackground = h.uiManager.NewCustomWidgetCached(h.renderPanelStatic, screenWidth, height)
h.panelBackground.SetPosition(0, screenHeight-height)
// stamina bar
h.widgetStamina = h.uiManager.NewCustomWidget(h.renderStaminaBar, staminaBarWidth, staminaBarHeight)
h.widgetStamina.SetPosition(staminaBarOffsetX, staminaBarOffsetY)
// experience bar
h.widgetExperience = h.uiManager.NewCustomWidget(h.renderExperienceBar, expBarWidth, expBarHeight)
// Left skill widget
leftRenderFunc := func(target d2interface.Surface) {
x, y := h.widgetLeftSkill.GetPosition()
h.renderLeftSkill(x, y, target)
}
h.widgetLeftSkill = h.uiManager.NewCustomWidget(leftRenderFunc, skillIconWidth, skillIconHeight)
h.widgetLeftSkill.SetPosition(leftSkillX, screenHeight)
// Right skill widget
rightRenderFunc := func(target d2interface.Surface) {
x, y := h.widgetRightSkill.GetPosition()
h.renderRightSkill(x, y, target)
}
h.widgetRightSkill = h.uiManager.NewCustomWidget(rightRenderFunc, skillIconWidth, skillIconHeight)
h.widgetRightSkill.SetPosition(rightSkillX, screenHeight)
}
func (h *HUD) loadSkillResources() {
// https://github.com/OpenDiablo2/OpenDiablo2/issues/799
genericSkillsSprite, err := h.uiManager.NewSprite(d2resource.GenericSkills, d2resource.PaletteSky)
if err != nil {
log.Print(err)
}
attackIconID := 2
h.leftSkillResource = &SkillResource{
SkillIcon: genericSkillsSprite,
IconNumber: attackIconID,
SkillResourcePath: d2resource.GenericSkills,
}
h.rightSkillResource = &SkillResource{
SkillIcon: genericSkillsSprite,
IconNumber: attackIconID,
SkillResourcePath: d2resource.GenericSkills,
}
}
func (h *HUD) loadSprites() {
var err error
h.globeSprite, err = h.uiManager.NewSprite(d2resource.GameGlobeOverlap, d2resource.PaletteSky)
@ -183,29 +255,6 @@ func (h *HUD) Load() {
if err != nil {
log.Print(err)
}
// https://github.com/OpenDiablo2/OpenDiablo2/issues/799
genericSkillsSprite, err := h.uiManager.NewSprite(d2resource.GenericSkills, d2resource.PaletteSky)
if err != nil {
log.Print(err)
}
attackIconID := 2
h.leftSkillResource = &SkillResource{
SkillIcon: genericSkillsSprite,
IconNumber: attackIconID,
SkillResourcePath: d2resource.GenericSkills,
}
h.rightSkillResource = &SkillResource{
SkillIcon: genericSkillsSprite,
IconNumber: attackIconID,
SkillResourcePath: d2resource.GenericSkills,
}
h.loadUIButtons()
h.loadTooltips()
}
func (h *HUD) loadTooltips() {
@ -292,35 +341,23 @@ func (h *HUD) onToggleRunButton(noButton bool) {
// NOTE: the positioning of all of the panel elements is coupled to the rendering order :(
// don't change the order in which the render methods are called, as there is an x,y offset
// that is updated between render calls
func (h *HUD) renderGameControlPanelElements(target d2interface.Surface) error {
func (h *HUD) renderPanelStatic(target d2interface.Surface) {
_, height := target.GetSize()
offsetX, offsetY := 0, 0
offsetX, offsetY := 0, height
// Main panel background
offsetY = height
if err := h.renderPanel(offsetX, offsetY, target); err != nil {
return err
}
// Health globe
w, _ := h.mainPanel.GetCurrentFrameSize()
if err := h.renderHealthGlobe(offsetX, offsetY, target); err != nil {
return err
}
// Left Skill
offsetX += w
if err := h.renderLeftSkill(offsetX, offsetY, target); err != nil {
return err
log.Print(err)
return
}
// New Stats Button
w, _ = h.leftSkillResource.SkillIcon.GetCurrentFrameSize()
offsetX += w
w, _ := h.mainPanel.GetCurrentFrameSize()
offsetX += w + skillIconWidth
if err := h.renderNewStatsButton(offsetX, offsetY, target); err != nil {
return err
log.Print(err)
return
}
// Stamina
@ -328,30 +365,17 @@ func (h *HUD) renderGameControlPanelElements(target d2interface.Surface) error {
offsetX += w
if err := h.renderStamina(offsetX, offsetY, target); err != nil {
return err
}
// Stamina status bar
w, _ = h.mainPanel.GetCurrentFrameSize()
offsetX += w
if err := h.renderStaminaBar(target); err != nil {
return err
}
// Experience status bar
if err := h.renderExperienceBar(target); err != nil {
return err
}
// Mini Panel and button
if err := h.renderMiniPanel(target); err != nil {
return err
log.Print(err)
return
}
// Potions
w, _ = h.mainPanel.GetCurrentFrameSize()
offsetX += w
if err := h.renderPotions(offsetX, offsetY, target); err != nil {
return err
log.Print(err)
return
}
// New Skills Button
@ -359,87 +383,21 @@ func (h *HUD) renderGameControlPanelElements(target d2interface.Surface) error {
offsetX += w
if err := h.renderNewSkillsButton(offsetX, offsetY, target); err != nil {
return err
log.Print(err)
return
}
// Right skill
// Empty Mana Globe
w, _ = h.mainPanel.GetCurrentFrameSize()
offsetX += w
if err := h.renderRightSkill(offsetX, offsetY, target); err != nil {
return err
}
// Mana Globe
w, _ = h.rightSkillResource.SkillIcon.GetCurrentFrameSize()
offsetX += w
if err := h.renderManaGlobe(offsetX, offsetY, target); err != nil {
return err
}
return nil
}
func (h *HUD) renderManaGlobe(x, _ int, target d2interface.Surface) error {
_, height := target.GetSize()
offsetX += w + skillIconWidth
if err := h.mainPanel.SetCurrentFrame(frameRightGlobeHolder); err != nil {
return err
log.Print(err)
return
}
h.mainPanel.SetPosition(x, height)
h.mainPanel.SetPosition(offsetX, height)
h.mainPanel.Render(target)
// Mana status bar
manaPercent := float64(h.hero.Stats.Mana) / float64(h.hero.Stats.MaxMana)
manaBarHeight := int(manaPercent * float64(globeHeight))
if err := h.hpManaStatusSprite.SetCurrentFrame(frameManaStatus); err != nil {
return err
}
h.hpManaStatusSprite.SetPosition(x+manaStatusOffsetX, height+manaStatusOffsetY)
manaMaskRect := image.Rect(0, globeHeight-manaBarHeight, globeWidth, globeHeight)
h.hpManaStatusSprite.RenderSection(target, manaMaskRect)
// Right globe
if err := h.globeSprite.SetCurrentFrame(frameRightGlobe); err != nil {
return err
}
h.globeSprite.SetPosition(x+rightGlobeOffsetX, height+rightGlobeOffsetY)
h.globeSprite.Render(target)
h.globeSprite.Render(target)
return nil
}
func (h *HUD) renderHealthGlobe(x, y int, target d2interface.Surface) error {
healthPercent := float64(h.hero.Stats.Health) / float64(h.hero.Stats.MaxHealth)
hpBarHeight := int(healthPercent * float64(globeHeight))
if err := h.hpManaStatusSprite.SetCurrentFrame(0); err != nil {
return err
}
h.hpManaStatusSprite.SetPosition(x+healthStatusOffsetX, y+healthStatusOffsetY)
healthMaskRect := image.Rect(0, globeHeight-hpBarHeight, globeWidth, globeHeight)
h.hpManaStatusSprite.RenderSection(target, healthMaskRect)
// Left globe
if err := h.globeSprite.SetCurrentFrame(frameHealthStatus); err != nil {
return err
}
h.globeSprite.SetPosition(x+globeSpriteOffsetX, y+globeSpriteOffsetY)
h.globeSprite.Render(target)
return nil
}
func (h *HUD) renderPanel(x, y int, target d2interface.Surface) error {
@ -453,7 +411,7 @@ func (h *HUD) renderPanel(x, y int, target d2interface.Surface) error {
return nil
}
func (h *HUD) renderLeftSkill(x, y int, target d2interface.Surface) error {
func (h *HUD) renderLeftSkill(x, y int, target d2interface.Surface) {
newSkillResourcePath := h.getSkillResourceByClass(h.hero.LeftSkill.Charclass)
if newSkillResourcePath != h.leftSkillResource.SkillResourcePath {
h.leftSkillResource.SkillResourcePath = newSkillResourcePath
@ -461,16 +419,15 @@ func (h *HUD) renderLeftSkill(x, y int, target d2interface.Surface) error {
}
if err := h.leftSkillResource.SkillIcon.SetCurrentFrame(h.hero.LeftSkill.IconCel); err != nil {
return err
log.Print(err)
return
}
h.leftSkillResource.SkillIcon.SetPosition(x, y)
h.leftSkillResource.SkillIcon.Render(target)
return nil
}
func (h *HUD) renderRightSkill(x, _ int, target d2interface.Surface) error {
func (h *HUD) renderRightSkill(x, _ int, target d2interface.Surface) {
_, height := target.GetSize()
newSkillResourcePath := h.getSkillResourceByClass(h.hero.RightSkill.Charclass)
@ -480,13 +437,12 @@ func (h *HUD) renderRightSkill(x, _ int, target d2interface.Surface) error {
}
if err := h.rightSkillResource.SkillIcon.SetCurrentFrame(h.hero.RightSkill.IconCel); err != nil {
return err
log.Print(err)
return
}
h.rightSkillResource.SkillIcon.SetPosition(x, height)
h.rightSkillResource.SkillIcon.Render(target)
return nil
}
func (h *HUD) renderNewStatsButton(x, y int, target d2interface.Surface) error {
@ -511,7 +467,7 @@ func (h *HUD) renderStamina(x, y int, target d2interface.Surface) error {
return nil
}
func (h *HUD) renderStaminaBar(target d2interface.Surface) error {
func (h *HUD) renderStaminaBar(target d2interface.Surface) {
target.PushTranslation(staminaBarOffsetX, staminaBarOffsetY)
defer target.Pop()
@ -526,19 +482,15 @@ func (h *HUD) renderStaminaBar(target d2interface.Surface) error {
}
target.DrawRect(int(staminaPercent*staminaBarWidth), staminaBarHeight, staminaBarColor)
return nil
}
func (h *HUD) renderExperienceBar(target d2interface.Surface) error {
func (h *HUD) renderExperienceBar(target d2interface.Surface) {
target.PushTranslation(experienceBarOffsetX, experienceBarOffsetY)
defer target.Pop()
expPercent := float64(h.hero.Stats.Experience) / float64(h.hero.Stats.NextLevelExp)
target.DrawRect(int(expPercent*expBarWidth), 2, d2util.Color(whiteAlpha100))
return nil
}
func (h *HUD) renderMiniPanel(target d2interface.Surface) error {
@ -765,7 +717,17 @@ func (h *HUD) renderForSelectableEntitiesHovered(target d2interface.Surface) {
func (h *HUD) Render(target d2interface.Surface) error {
h.renderForSelectableEntitiesHovered(target)
if err := h.renderGameControlPanelElements(target); err != nil {
h.panelBackground.Render(target)
h.healthGlobe.Render(target)
h.widgetLeftSkill.Render(target)
h.widgetRightSkill.Render(target)
h.manaGlobe.Render(target)
h.widgetStamina.Render(target)
h.widgetExperience.Render(target)
// Mini Panel and button
if err := h.renderMiniPanel(target); err != nil {
return err
}

View File

@ -131,7 +131,7 @@ 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.panel = s.uiManager.NewCustomWidget(s.Render, 400, 600)
s.panelGroup.AddWidget(s.panel)
s.frame = d2ui.NewUIFrame(s.asset, s.uiManager, d2ui.FrameRight)
@ -424,9 +424,6 @@ func (s *skillTree) renderTabCommon(target d2interface.Surface) {
skillPanel.SetPosition(x+w, y)
s.renderPanelSegment(target, frameCommonTabBottomRight)
// available skill points label
s.availSPLabel.Render(target)
}
func (s *skillTree) renderTab(target d2interface.Surface, tab int) {