1
1
mirror of https://github.com/OpenDiablo2/OpenDiablo2 synced 2024-07-01 11:25:26 +00:00

Ui hud polishing (#938)

* d2ui/tooltip: Make it invisible by default

* d2ui/button: Add GetToggled() method

* d2player/HUD: Add tooltip for minipanel button

* d2ui/button: Add disabled frame to minipanel buttons

* d2ui/widget_group: Add SetEnable method for clickable widgets

* d2player/mini_panel: move menu button here from HUD

* d2ui/button: toggled buttons take preference over disabled buttons

* d2player/help_overlay: Make panel only use widgets

* d2player/hud: Group most widgets into widget group

* d2ui/custom_widget: Allow tooltip to be attached

* d2player/hud: Attach staminaBar tooltip to staminaBar

* d2player/hud: Attach experienceBar tooltip to experienceBar widget

* d2ui/ui_manager: Always draw tooltips last

* d2player/help_overlay: It should be drawn over the HUD

* d2player/globeWidget: Move tooltip here from HUD

* d2core/tooltip: Automatically add tooltips to the uiManager

* d2core/ui_manager: Remove special handling of widgetGroups for rendering

* d2player/help_overlay: Add button to widget group

* d2player/hud: Attack runwalk tooltip to button

* d2player/mini_panel: Add panelButton to its own widget group

* d2core/widget_group: When a clickable is added, it's also added to uiManager

* d2player/globeWidget: make tooltip un/lock on click

* d2player/hud: Add runbutton to widget group

* d2player/mini_panel: Add group for tooltips

this allows us to move the tooltip with the panelbuttons. They can't be
in the general panelGroup as they would all become visible when the
panel is opened.

* d2core/button: Remove debug log when a button with tooltip is hovered
This commit is contained in:
juander-ux 2020-11-21 11:35:32 +01:00 committed by GitHub
parent e89450daf1
commit 1f49df62d1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 412 additions and 261 deletions

View File

@ -131,7 +131,6 @@ const (
buttonSkillTreeTabFixedHeight = 107 buttonSkillTreeTabFixedHeight = 107
buttonMinipanelOpenCloseBaseFrame = 0 buttonMinipanelOpenCloseBaseFrame = 0
buttonMinipanelDisabledFrame = 2
buttonMinipanelXSegments = 1 buttonMinipanelXSegments = 1
buttonMinipanelYSegments = 1 buttonMinipanelYSegments = 1
@ -278,7 +277,7 @@ func getButtonLayouts() map[ButtonType]ButtonLayout {
ButtonTypeMinipanelOpenClose: { ButtonTypeMinipanelOpenClose: {
XSegments: buttonMinipanelXSegments, XSegments: buttonMinipanelXSegments,
YSegments: buttonMinipanelYSegments, YSegments: buttonMinipanelYSegments,
DisabledFrame: buttonMinipanelDisabledFrame, DisabledFrame: buttonMinipanelOpenCloseBaseFrame,
DisabledColor: whiteAlpha100, DisabledColor: whiteAlpha100,
BaseFrame: buttonMinipanelOpenCloseBaseFrame, BaseFrame: buttonMinipanelOpenCloseBaseFrame,
ResourceName: d2resource.MenuButton, ResourceName: d2resource.MenuButton,
@ -294,6 +293,8 @@ func getButtonLayouts() map[ButtonType]ButtonLayout {
ButtonTypeMinipanelCharacter: { ButtonTypeMinipanelCharacter: {
XSegments: buttonMinipanelXSegments, XSegments: buttonMinipanelXSegments,
YSegments: buttonMinipanelYSegments, YSegments: buttonMinipanelYSegments,
DisabledFrame: buttonMinipanelCharacterBaseFrame,
DisabledColor: whiteAlpha100,
BaseFrame: buttonMinipanelCharacterBaseFrame, BaseFrame: buttonMinipanelCharacterBaseFrame,
ResourceName: d2resource.MinipanelButton, ResourceName: d2resource.MinipanelButton,
PaletteName: d2resource.PaletteSky, PaletteName: d2resource.PaletteSky,
@ -308,6 +309,8 @@ func getButtonLayouts() map[ButtonType]ButtonLayout {
ButtonTypeMinipanelInventory: { ButtonTypeMinipanelInventory: {
XSegments: buttonMinipanelXSegments, XSegments: buttonMinipanelXSegments,
YSegments: buttonMinipanelYSegments, YSegments: buttonMinipanelYSegments,
DisabledFrame: buttonMinipanelInventoryBaseFrame,
DisabledColor: whiteAlpha100,
BaseFrame: buttonMinipanelInventoryBaseFrame, BaseFrame: buttonMinipanelInventoryBaseFrame,
ResourceName: d2resource.MinipanelButton, ResourceName: d2resource.MinipanelButton,
PaletteName: d2resource.PaletteSky, PaletteName: d2resource.PaletteSky,
@ -322,6 +325,8 @@ func getButtonLayouts() map[ButtonType]ButtonLayout {
ButtonTypeMinipanelSkill: { ButtonTypeMinipanelSkill: {
XSegments: buttonMinipanelXSegments, XSegments: buttonMinipanelXSegments,
YSegments: buttonMinipanelYSegments, YSegments: buttonMinipanelYSegments,
DisabledFrame: buttonMinipanelSkilltreeBaseFrame,
DisabledColor: whiteAlpha100,
BaseFrame: buttonMinipanelSkilltreeBaseFrame, BaseFrame: buttonMinipanelSkilltreeBaseFrame,
ResourceName: d2resource.MinipanelButton, ResourceName: d2resource.MinipanelButton,
PaletteName: d2resource.PaletteSky, PaletteName: d2resource.PaletteSky,
@ -336,6 +341,8 @@ func getButtonLayouts() map[ButtonType]ButtonLayout {
ButtonTypeMinipanelParty: { ButtonTypeMinipanelParty: {
XSegments: buttonMinipanelXSegments, XSegments: buttonMinipanelXSegments,
YSegments: buttonMinipanelYSegments, YSegments: buttonMinipanelYSegments,
DisabledFrame: buttonMinipanelPartyBaseFrame,
DisabledColor: whiteAlpha100,
BaseFrame: buttonMinipanelPartyBaseFrame, BaseFrame: buttonMinipanelPartyBaseFrame,
ResourceName: d2resource.MinipanelButton, ResourceName: d2resource.MinipanelButton,
PaletteName: d2resource.PaletteSky, PaletteName: d2resource.PaletteSky,
@ -350,6 +357,8 @@ func getButtonLayouts() map[ButtonType]ButtonLayout {
ButtonTypeMinipanelAutomap: { ButtonTypeMinipanelAutomap: {
XSegments: buttonMinipanelXSegments, XSegments: buttonMinipanelXSegments,
YSegments: buttonMinipanelYSegments, YSegments: buttonMinipanelYSegments,
DisabledFrame: buttonMinipanelAutomapBaseFrame,
DisabledColor: whiteAlpha100,
BaseFrame: buttonMinipanelAutomapBaseFrame, BaseFrame: buttonMinipanelAutomapBaseFrame,
ResourceName: d2resource.MinipanelButton, ResourceName: d2resource.MinipanelButton,
PaletteName: d2resource.PaletteSky, PaletteName: d2resource.PaletteSky,
@ -364,6 +373,8 @@ func getButtonLayouts() map[ButtonType]ButtonLayout {
ButtonTypeMinipanelMessage: { ButtonTypeMinipanelMessage: {
XSegments: buttonMinipanelXSegments, XSegments: buttonMinipanelXSegments,
YSegments: buttonMinipanelYSegments, YSegments: buttonMinipanelYSegments,
DisabledFrame: buttonMinipanelMessageBaseFrame,
DisabledColor: whiteAlpha100,
BaseFrame: buttonMinipanelMessageBaseFrame, BaseFrame: buttonMinipanelMessageBaseFrame,
ResourceName: d2resource.MinipanelButton, ResourceName: d2resource.MinipanelButton,
PaletteName: d2resource.PaletteSky, PaletteName: d2resource.PaletteSky,
@ -378,6 +389,8 @@ func getButtonLayouts() map[ButtonType]ButtonLayout {
ButtonTypeMinipanelQuest: { ButtonTypeMinipanelQuest: {
XSegments: buttonMinipanelXSegments, XSegments: buttonMinipanelXSegments,
YSegments: buttonMinipanelYSegments, YSegments: buttonMinipanelYSegments,
DisabledFrame: buttonMinipanelQuestBaseFrame,
DisabledColor: whiteAlpha100,
BaseFrame: buttonMinipanelQuestBaseFrame, BaseFrame: buttonMinipanelQuestBaseFrame,
ResourceName: d2resource.MinipanelButton, ResourceName: d2resource.MinipanelButton,
PaletteName: d2resource.PaletteSky, PaletteName: d2resource.PaletteSky,
@ -392,6 +405,8 @@ func getButtonLayouts() map[ButtonType]ButtonLayout {
ButtonTypeMinipanelMen: { ButtonTypeMinipanelMen: {
XSegments: buttonMinipanelXSegments, XSegments: buttonMinipanelXSegments,
YSegments: buttonMinipanelYSegments, YSegments: buttonMinipanelYSegments,
DisabledFrame: buttonMinipanelMenBaseFrame,
DisabledColor: whiteAlpha100,
BaseFrame: buttonMinipanelMenBaseFrame, BaseFrame: buttonMinipanelMenBaseFrame,
ResourceName: d2resource.MinipanelButton, ResourceName: d2resource.MinipanelButton,
PaletteName: d2resource.PaletteSky, PaletteName: d2resource.PaletteSky,
@ -628,7 +643,12 @@ func (v *Button) Render(target d2interface.Surface) {
case !v.enabled: case !v.enabled:
target.PushColor(d2util.Color(v.buttonLayout.DisabledColor)) target.PushColor(d2util.Color(v.buttonLayout.DisabledColor))
defer target.Pop() defer target.Pop()
target.Render(v.disabledSurface)
if v.toggled {
target.Render(v.toggledSurface)
} else {
target.Render(v.disabledSurface)
}
case v.toggled && v.pressed: case v.toggled && v.pressed:
target.Render(v.pressedToggledSurface) target.Render(v.pressedToggledSurface)
case v.pressed: case v.pressed:
@ -649,6 +669,11 @@ func (v *Button) Toggle() {
v.toggled = !v.toggled v.toggled = !v.toggled
} }
// GetToggled returns the toggled state of the button
func (v *Button) GetToggled() bool {
return v.toggled
}
// Advance advances the button state // Advance advances the button state
func (v *Button) Advance(_ float64) error { func (v *Button) Advance(_ float64) error {
return nil return nil
@ -695,8 +720,7 @@ func (v *Button) SetPosition(x, y int) {
// SetTooltip adds a tooltip to the button // SetTooltip adds a tooltip to the button
func (v *Button) SetTooltip(t *Tooltip) { func (v *Button) SetTooltip(t *Tooltip) {
v.tooltip = t v.tooltip = t
v.manager.addWidget(t) v.OnHoverStart(func() { v.tooltip.SetVisible(true) })
v.OnHoverStart(func() { log.Print("HoverStart"); v.tooltip.SetVisible(true) })
v.OnHoverEnd(func() { v.tooltip.SetVisible(false) }) v.OnHoverEnd(func() { v.tooltip.SetVisible(false) })
} }

View File

@ -11,6 +11,7 @@ type CustomWidget struct {
renderFunc func(target d2interface.Surface) renderFunc func(target d2interface.Surface)
cached bool cached bool
cachedImg *d2interface.Surface cachedImg *d2interface.Surface
tooltip *Tooltip
} }
// NewCustomWidgetCached creates a new widget and caches anything rendered via the // NewCustomWidgetCached creates a new widget and caches anything rendered via the
@ -50,6 +51,13 @@ func (c *CustomWidget) Render(target d2interface.Surface) {
} }
} }
// SetTooltip gives this widget a Tooltip that is displayed if the widget is hovered
func (c *CustomWidget) SetTooltip(t *Tooltip) {
c.tooltip = t
c.OnHoverStart(func() { c.tooltip.SetVisible(true) })
c.OnHoverEnd(func() { c.tooltip.SetVisible(false) })
}
// Advance is a no-op // Advance is a no-op
func (c *CustomWidget) Advance(elapsed float64) error { func (c *CustomWidget) Advance(elapsed float64) error {
return nil return nil

View File

@ -58,6 +58,7 @@ func (ui *UIManager) NewTooltip(font,
label.Alignment = HorizontalAlignCenter label.Alignment = HorizontalAlignCenter
base := NewBaseWidget(ui) base := NewBaseWidget(ui)
base.SetVisible(false)
res := &Tooltip{ res := &Tooltip{
BaseWidget: base, BaseWidget: base,
@ -68,6 +69,7 @@ func (ui *UIManager) NewTooltip(font,
boxEnabled: true, boxEnabled: true,
} }
res.manager = ui res.manager = ui
ui.addTooltip(res)
return res return res
} }

View File

@ -18,6 +18,7 @@ type UIManager struct {
inputManager d2interface.InputManager inputManager d2interface.InputManager
audio d2interface.AudioProvider audio d2interface.AudioProvider
widgets []Widget widgets []Widget
tooltips []*Tooltip
widgetsGroups []*WidgetGroup widgetsGroups []*WidgetGroup
clickableWidgets []ClickableWidget clickableWidgets []ClickableWidget
cursorButtons CursorButton cursorButtons CursorButton
@ -53,13 +54,8 @@ func (ui *UIManager) Reset() {
ui.pressedWidget = nil ui.pressedWidget = nil
} }
// addWidgetGroup adds a widgetGroup to the UI manager and sorts by priority func (ui *UIManager) addClickable(widget ClickableWidget) {
func (ui *UIManager) addWidgetGroup(group *WidgetGroup) { ui.clickableWidgets = append(ui.clickableWidgets, widget)
ui.widgetsGroups = append(ui.widgetsGroups, group)
sort.SliceStable(ui.widgetsGroups, func(i, j int) bool {
return ui.widgetsGroups[i].renderPriority < ui.widgetsGroups[j].renderPriority
})
} }
// addWidget adds a widget to the UI manager // addWidget adds a widget to the UI manager
@ -71,13 +67,27 @@ func (ui *UIManager) addWidget(widget Widget) {
clickable, ok := widget.(ClickableWidget) clickable, ok := widget.(ClickableWidget)
if ok { if ok {
ui.clickableWidgets = append(ui.clickableWidgets, clickable) ui.addClickable(clickable)
}
if widgetGroup, ok := widget.(*WidgetGroup); ok {
ui.widgetsGroups = append(ui.widgetsGroups, widgetGroup)
} }
ui.widgets = append(ui.widgets, widget) ui.widgets = append(ui.widgets, widget)
sort.SliceStable(ui.widgets, func(i, j int) bool {
return ui.widgets[i].GetRenderPriority() < ui.widgets[j].GetRenderPriority()
})
widget.bindManager(ui) widget.bindManager(ui)
} }
// addTooltip adds a widget to the UI manager
func (ui *UIManager) addTooltip(t *Tooltip) {
ui.tooltips = append(ui.tooltips, t)
}
// OnMouseButtonUp is an event handler for input // OnMouseButtonUp is an event handler for input
func (ui *UIManager) OnMouseButtonUp(event d2interface.MouseEvent) bool { func (ui *UIManager) OnMouseButtonUp(event d2interface.MouseEvent) bool {
ui.CursorX, ui.CursorY = event.X(), event.Y() ui.CursorX, ui.CursorY = event.X(), event.Y()
@ -142,17 +152,17 @@ func (ui *UIManager) OnMouseButtonDown(event d2interface.MouseEvent) bool {
// Render renders all of the UI elements // Render renders all of the UI elements
func (ui *UIManager) Render(target d2interface.Surface) { func (ui *UIManager) Render(target d2interface.Surface) {
for _, widgetGroup := range ui.widgetsGroups {
if widgetGroup.GetVisible() {
widgetGroup.Render(target)
}
}
for _, widget := range ui.widgets { for _, widget := range ui.widgets {
if widget.GetVisible() { if widget.GetVisible() {
widget.Render(target) widget.Render(target)
} }
} }
for _, tooltip := range ui.tooltips {
if tooltip.GetVisible() {
tooltip.Render(target)
}
}
} }
// Advance updates all of the UI elements // Advance updates all of the UI elements

View File

@ -4,18 +4,16 @@ package d2ui
// The higher the number the later an element is drawn. // The higher the number the later an element is drawn.
type RenderPriority int type RenderPriority int
// Render priorities that determine the order in which widgets/widgetgroups are
// rendered. The higher the later it is rendered
const ( const (
// RenderPriorityBackground is the first element drawn
RenderPriorityBackground RenderPriority = iota RenderPriorityBackground RenderPriority = iota
// RenderPrioritySkilltree is the priority for the skilltree
RenderPrioritySkilltree RenderPrioritySkilltree
// RenderPrioritySkilltreeIcon is the priority for the skilltree icons
RenderPrioritySkilltreeIcon RenderPrioritySkilltreeIcon
// RenderPriorityMinipanel is the priority for the minipanel icons
RenderPriorityMinipanel
// RenderPriorityHeroStatsPanel is the priority for the hero_stats_panel
RenderPriorityHeroStatsPanel RenderPriorityHeroStatsPanel
// RenderPriorityForeground is the last element drawn RenderPriorityHUDPanel
RenderPriorityMinipanel
RenderPriorityHelpPanel
RenderPriorityForeground RenderPriorityForeground
) )

View File

@ -28,7 +28,7 @@ func (ui *UIManager) NewWidgetGroup(priority RenderPriority) *WidgetGroup {
BaseWidget: base, BaseWidget: base,
} }
ui.addWidgetGroup(group) ui.addWidget(group)
return group return group
} }
@ -40,6 +40,10 @@ func (wg *WidgetGroup) AddWidget(w Widget) {
sort.SliceStable(wg.entries, func(i, j int) bool { sort.SliceStable(wg.entries, func(i, j int) bool {
return wg.entries[i].GetRenderPriority() < wg.entries[j].GetRenderPriority() return wg.entries[i].GetRenderPriority() < wg.entries[j].GetRenderPriority()
}) })
if clickable, ok := w.(ClickableWidget); ok {
wg.manager.addClickable(clickable)
}
} }
// adjustSize recalculates the bounding box if a new widget is added // adjustSize recalculates the bounding box if a new widget is added
@ -127,3 +131,12 @@ func (wg *WidgetGroup) OnMouseMove(x, y int) {
} }
} }
} }
// SetEnabled sets enable on all clickable widgets of this group
func (wg *WidgetGroup) SetEnabled(enabled bool) {
for _, entry := range wg.entries {
if v, ok := entry.(ClickableWidget); ok {
v.SetEnabled(enabled)
}
}
}

View File

@ -219,8 +219,8 @@ func NewGameControls(
return nil, err return nil, err
} }
helpOverlay := NewHelpOverlay(asset, renderer, ui, guiManager, l, keyMap) helpOverlay := NewHelpOverlay(asset, ui, l, keyMap)
hud := NewHUD(asset, ui, hero, helpOverlay, miniPanel, actionableRegions, mapEngine, l, mapRenderer) hud := NewHUD(asset, ui, hero, miniPanel, actionableRegions, mapEngine, l, mapRenderer)
const blackAlpha50percent = 0x0000007f const blackAlpha50percent = 0x0000007f
@ -270,7 +270,8 @@ func NewGameControls(
gc.inventory.SetOnCloseCb(gc.onCloseInventory) gc.inventory.SetOnCloseCb(gc.onCloseInventory)
gc.skilltree.SetOnCloseCb(gc.onCloseSkilltree) gc.skilltree.SetOnCloseCb(gc.onCloseSkilltree)
gc.escapeMenu.SetOnCloseCb(gc.hud.restoreMinipanelFromTempClose) gc.escapeMenu.SetOnCloseCb(gc.hud.miniPanel.restoreDisabled)
gc.HelpOverlay.SetOnCloseCb(gc.hud.miniPanel.restoreDisabled)
err = gc.bindTerminalCommands(term) err = gc.bindTerminalCommands(term)
if err != nil { if err != nil {
@ -397,6 +398,8 @@ func (g *GameControls) OnKeyDown(event d2interface.KeyEvent) bool {
case d2enum.HoldRun: case d2enum.HoldRun:
g.hud.onToggleRunButton(true) g.hud.onToggleRunButton(true)
case d2enum.ToggleHelpScreen: case d2enum.ToggleHelpScreen:
g.hud.miniPanel.openDisabled()
g.HelpOverlay.Toggle() g.HelpOverlay.Toggle()
g.updateLayout() g.updateLayout()
default: default:
@ -646,7 +649,7 @@ func (g *GameControls) openEscMenu() {
g.inventory.Close() g.inventory.Close()
g.skilltree.Close() g.skilltree.Close()
g.heroStatsPanel.Close() g.heroStatsPanel.Close()
g.hud.closeMinipanelTemporary() g.hud.miniPanel.closeDisabled()
g.escapeMenu.open() g.escapeMenu.open()
g.updateLayout() g.updateLayout()
} }
@ -671,6 +674,7 @@ func (g *GameControls) Load() {
// Advance advances the state of the GameControls // Advance advances the state of the GameControls
func (g *GameControls) Advance(elapsed float64) error { func (g *GameControls) Advance(elapsed float64) error {
g.mapRenderer.Advance(elapsed) g.mapRenderer.Advance(elapsed)
g.hud.Advance(elapsed)
if err := g.escapeMenu.Advance(elapsed); err != nil { if err := g.escapeMenu.Advance(elapsed); err != nil {
return err return err

View File

@ -1,11 +1,13 @@
package d2player package d2player
import ( import (
"fmt"
"image" "image"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2util" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2util"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2ui" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2ui"
) )
@ -30,11 +32,20 @@ const (
manaStatusOffsetY = -12 manaStatusOffsetY = -12
manaGlobeScreenOffsetX = 117 manaGlobeScreenOffsetX = 117
hpLabelX = 15
hpLabelY = 487
manaLabelX = 785
manaLabelY = 487
) )
// static check that globeWidget implements Widget // static check that globeWidget implements Widget
var _ d2ui.Widget = &globeWidget{} var _ d2ui.Widget = &globeWidget{}
// static check that globeWidget implements ClickableWidget
var _ d2ui.ClickableWidget = &globeWidget{}
type globeFrame struct { type globeFrame struct {
sprite *d2ui.Sprite sprite *d2ui.Sprite
offsetX int offsetX int
@ -53,14 +64,18 @@ func (gf *globeFrame) setPosition(x, y int) {
gf.sprite.SetPosition(x+gf.offsetX, y+gf.offsetY) gf.sprite.SetPosition(x+gf.offsetX, y+gf.offsetY)
} }
func (gf *globeFrame) getSize() (x, y int) { func newGlobeWidget(ui *d2ui.UIManager,
w, h := gf.sprite.GetSize() asset *d2asset.AssetManager,
return w + gf.offsetX, h + gf.offsetY x, y int,
} gtype globeType,
value *int, valueMax *int,
func newGlobeWidget(ui *d2ui.UIManager, x, y int, gtype globeType, value *int, l d2util.LogLevel, valueMax *int) *globeWidget { l d2util.LogLevel) *globeWidget {
var globe, overlap *globeFrame var globe, overlap *globeFrame
var tooltipX, tooltipY int
var tooltipTrans string
base := d2ui.NewBaseWidget(ui) base := d2ui.NewBaseWidget(ui)
base.SetPosition(x, y) base.SetPosition(x, y)
@ -75,6 +90,8 @@ func newGlobeWidget(ui *d2ui.UIManager, x, y int, gtype globeType, value *int, l
offsetY: globeSpriteOffsetY, offsetY: globeSpriteOffsetY,
idx: frameHealthStatus, idx: frameHealthStatus,
} }
tooltipX, tooltipY = hpLabelX, hpLabelY
tooltipTrans = "panelhealth"
} else if gtype == typeManaGlobe { } else if gtype == typeManaGlobe {
globe = &globeFrame{ globe = &globeFrame{
offsetX: manaStatusOffsetX, offsetX: manaStatusOffsetX,
@ -86,16 +103,35 @@ func newGlobeWidget(ui *d2ui.UIManager, x, y int, gtype globeType, value *int, l
offsetY: rightGlobeOffsetY, offsetY: rightGlobeOffsetY,
idx: frameRightGlobe, idx: frameRightGlobe,
} }
tooltipX, tooltipY = manaLabelX, manaLabelY
tooltipTrans = "panelmana"
} }
gw := &globeWidget{ gw := &globeWidget{
BaseWidget: base, BaseWidget: base,
value: value, asset: asset,
valueMax: valueMax, value: value,
globe: globe, valueMax: valueMax,
overlap: overlap, globe: globe,
overlap: overlap,
isTooltipLocked: false,
tooltipX: tooltipX,
tooltipY: tooltipY,
tooltipTrans: tooltipTrans,
} }
gw.OnHoverStart(func() {
if !gw.isTooltipLocked {
gw.tooltip.SetVisible(true)
}
})
gw.OnHoverEnd(func() {
if !gw.isTooltipLocked {
gw.tooltip.SetVisible(false)
}
})
gw.logger = d2util.NewLogger() gw.logger = d2util.NewLogger()
gw.logger.SetLevel(l) gw.logger.SetLevel(l)
gw.logger.SetPrefix(logPrefix) gw.logger.SetPrefix(logPrefix)
@ -105,11 +141,19 @@ func newGlobeWidget(ui *d2ui.UIManager, x, y int, gtype globeType, value *int, l
type globeWidget struct { type globeWidget struct {
*d2ui.BaseWidget *d2ui.BaseWidget
asset *d2asset.AssetManager
value *int value *int
valueMax *int valueMax *int
globe *globeFrame globe *globeFrame
overlap *globeFrame overlap *globeFrame
logger *d2util.Logger logger *d2util.Logger
pressed bool
isTooltipLocked bool
tooltip *d2ui.Tooltip
tooltipX int
tooltipY int
tooltipTrans string
} }
func (g *globeWidget) load() { func (g *globeWidget) load() {
@ -128,6 +172,11 @@ func (g *globeWidget) load() {
} }
g.overlap.setFrameIndex() g.overlap.setFrameIndex()
// tooltip
g.tooltip = g.GetManager().NewTooltip(d2resource.Font16, d2resource.PaletteUnits, d2ui.TooltipXLeft, d2ui.TooltipYTop)
g.tooltip.SetPosition(g.tooltipX, g.tooltipY)
g.tooltip.SetBoxEnabled(false)
} }
// Render draws the widget to the screen // Render draws the widget to the screen
@ -144,10 +193,49 @@ func (g *globeWidget) Render(target d2interface.Surface) {
g.overlap.sprite.Render(target) g.overlap.sprite.Render(target)
} }
func (g *globeWidget) GetSize() (x, y int) { // Contains is special here as the point of origin is at the lower left corner
return g.overlap.getSize() // in contrast to any other element which is top left.
func (g *globeWidget) Contains(px, py int) bool {
wx, wy := g.globe.sprite.GetPosition()
width, height := g.globe.sprite.GetSize()
return px >= wx && px <= wx+width && py <= wy && py >= wy-height
}
func (g *globeWidget) updateTooltip() {
// Create and format string from string lookup table.
fmtStr := g.asset.TranslateString(g.tooltipTrans)
strPanel := fmt.Sprintf(fmtStr, *g.value, *g.valueMax)
g.tooltip.SetText(strPanel)
} }
func (g *globeWidget) Advance(elapsed float64) error { func (g *globeWidget) Advance(elapsed float64) error {
g.updateTooltip()
return nil return nil
} }
func (g *globeWidget) Activate() {
g.isTooltipLocked = !g.isTooltipLocked
g.tooltip.SetVisible(g.isTooltipLocked)
}
func (g *globeWidget) GetEnabled() bool {
return true
}
func (g *globeWidget) SetEnabled(enable bool) {
// No-op
}
func (g *globeWidget) GetPressed() bool {
return g.pressed
}
func (g *globeWidget) SetPressed(pressed bool) {
g.pressed = pressed
}
func (g *globeWidget) OnActivated(callback func()) {
// No-op
}

View File

@ -10,7 +10,6 @@ import (
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2util" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2util"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2gui"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2ui" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2ui"
) )
@ -159,18 +158,14 @@ const (
// NewHelpOverlay creates a new HelpOverlay instance // NewHelpOverlay creates a new HelpOverlay instance
func NewHelpOverlay( func NewHelpOverlay(
asset *d2asset.AssetManager, asset *d2asset.AssetManager,
renderer d2interface.Renderer,
ui *d2ui.UIManager, ui *d2ui.UIManager,
guiManager *d2gui.GuiManager,
l d2util.LogLevel, l d2util.LogLevel,
keyMap *KeyMap, keyMap *KeyMap,
) *HelpOverlay { ) *HelpOverlay {
h := &HelpOverlay{ h := &HelpOverlay{
asset: asset, asset: asset,
renderer: renderer, uiManager: ui,
uiManager: ui, keyMap: keyMap,
guiManager: guiManager,
keyMap: keyMap,
} }
h.logger = d2util.NewLogger() h.logger = d2util.NewLogger()
@ -182,17 +177,17 @@ func NewHelpOverlay(
// HelpOverlay represents the in-game overlay that toggles visibility when the h key is pressed // HelpOverlay represents the in-game overlay that toggles visibility when the h key is pressed
type HelpOverlay struct { type HelpOverlay struct {
asset *d2asset.AssetManager asset *d2asset.AssetManager
isOpen bool isOpen bool
renderer d2interface.Renderer frames []*d2ui.Sprite
frames []*d2ui.Sprite text []*d2ui.Label
text []*d2ui.Label lines []line
lines []line uiManager *d2ui.UIManager
uiManager *d2ui.UIManager closeButton *d2ui.Button
layout *d2gui.Layout keyMap *KeyMap
closeButton *d2ui.Button onCloseCb func()
guiManager *d2gui.GuiManager panelGroup *d2ui.WidgetGroup
keyMap *KeyMap backgroundWidget *d2ui.CustomWidget
logger *d2util.Logger logger *d2util.Logger
} }
@ -211,20 +206,18 @@ func (h *HelpOverlay) Toggle() {
// Close will hide the help overlay // Close will hide the help overlay
func (h *HelpOverlay) Close() { func (h *HelpOverlay) Close() {
h.isOpen = false h.isOpen = false
h.closeButton.SetVisible(false) h.panelGroup.SetVisible(false)
h.guiManager.SetLayout(nil) h.onCloseCb()
}
// SetOnCloseCb sets the callback run when Close() is called
func (h *HelpOverlay) SetOnCloseCb(cb func()) {
h.onCloseCb = cb
} }
func (h *HelpOverlay) open() { func (h *HelpOverlay) open() {
h.isOpen = true h.isOpen = true
if h.layout == nil { h.panelGroup.SetVisible(true)
h.layout = d2gui.CreateLayout(h.renderer, d2gui.PositionTypeHorizontal, h.asset)
}
h.closeButton.SetVisible(true)
h.closeButton.SetPressed(false)
h.guiManager.SetLayout(h.layout)
} }
// IsOpen returns whether or not the overlay is visible/open // IsOpen returns whether or not the overlay is visible/open
@ -234,22 +227,21 @@ func (h *HelpOverlay) IsOpen() bool {
// IsInRect checks if the given point is within the overlay layout rectangle // IsInRect checks if the given point is within the overlay layout rectangle
func (h *HelpOverlay) IsInRect(px, py int) bool { func (h *HelpOverlay) IsInRect(px, py int) bool {
ww, hh := h.layout.GetSize() return h.panelGroup.Contains(px, py)
x, y := h.layout.GetPosition()
if px >= x && px <= x+ww && py >= y && py <= y+hh {
return true
}
return false
} }
// Load the overlay graphical assets // Load the overlay graphical assets
func (h *HelpOverlay) Load() { func (h *HelpOverlay) Load() {
h.panelGroup = h.uiManager.NewWidgetGroup(d2ui.RenderPriorityHelpPanel)
h.setupOverlayFrame() h.setupOverlayFrame()
h.setupTitleAndButton() h.setupTitleAndButton()
h.setupBulletedList() h.setupBulletedList()
h.setupLabelsWithLines() h.setupLabelsWithLines()
h.backgroundWidget = h.uiManager.NewCustomWidgetCached(h.Render, screenWidth, screenHeight)
h.panelGroup.AddWidget(h.backgroundWidget)
h.panelGroup.SetVisible(false)
} }
func (h *HelpOverlay) setupOverlayFrame() { func (h *HelpOverlay) setupOverlayFrame() {
@ -330,6 +322,8 @@ func (h *HelpOverlay) setupTitleAndButton() {
h.closeButton.SetPosition(closeButtonX, closeButtonY) h.closeButton.SetPosition(closeButtonX, closeButtonY)
h.closeButton.SetVisible(false) h.closeButton.SetVisible(false)
h.closeButton.OnActivated(func() { h.Close() }) h.closeButton.OnActivated(func() { h.Close() })
h.closeButton.SetRenderPriority(d2ui.RenderPriorityForeground)
h.panelGroup.AddWidget(h.closeButton)
newLabel = h.uiManager.NewLabel(d2resource.Font16, d2resource.PaletteSky) newLabel = h.uiManager.NewLabel(d2resource.Font16, d2resource.PaletteSky)
newLabel.SetText(h.asset.TranslateString("strClose")) // "Close" newLabel.SetText(h.asset.TranslateString("strClose")) // "Close"
@ -621,11 +615,7 @@ func (h *HelpOverlay) createCallout(c callout) {
} }
// Render the overlay to the given surface // Render the overlay to the given surface
func (h *HelpOverlay) Render(target d2interface.Surface) error { func (h *HelpOverlay) Render(target d2interface.Surface) {
if !h.isOpen {
return nil
}
for _, f := range h.frames { for _, f := range h.frames {
f.Render(target) f.Render(target)
} }
@ -639,6 +629,4 @@ func (h *HelpOverlay) Render(target d2interface.Surface) error {
target.DrawLine(l.MoveX, l.MoveY, l.Color) target.DrawLine(l.MoveX, l.MoveY, l.Color)
target.Pop() target.Pop()
} }
return nil
} }

View File

@ -35,16 +35,6 @@ const (
percentStaminaBarLow = 0.25 percentStaminaBarLow = 0.25
) )
const (
hpLabelX = 15
hpLabelY = 487
manaLabelX = 785
manaLabelY = 487
staminaExperienceY = 535
)
const ( const (
frameHealthStatus = 0 frameHealthStatus = 0
frameManaStatus = 1 frameManaStatus = 1
@ -57,8 +47,9 @@ const (
) )
const ( const (
staminaBarOffsetX = 273 staminaBarOffsetX = 273
staminaBarOffsetY = 572 staminaBarOffsetY = 572
staminaExperienceY = 535
experienceBarOffsetX = 256 experienceBarOffsetX = 256
experienceBarOffsetY = 561 experienceBarOffsetY = 561
@ -68,6 +59,9 @@ const (
miniPanelButtonOffsetX = -8 miniPanelButtonOffsetX = -8
miniPanelButtonOffsetY = -38 miniPanelButtonOffsetY = -38
miniPanelTooltipOffsetX = 7
miniPanelTooltipOffsetY = -14
) )
const ( const (
@ -81,7 +75,6 @@ type HUD struct {
actionableRegions []actionableRegion actionableRegions []actionableRegion
asset *d2asset.AssetManager asset *d2asset.AssetManager
uiManager *d2ui.UIManager uiManager *d2ui.UIManager
help *HelpOverlay
mapEngine *d2mapengine.MapEngine mapEngine *d2mapengine.MapEngine
mapRenderer *d2maprenderer.MapRenderer mapRenderer *d2maprenderer.MapRenderer
lastMouseX int lastMouseX int
@ -89,14 +82,12 @@ type HUD struct {
hero *d2mapentity.Player hero *d2mapentity.Player
mainPanel *d2ui.Sprite mainPanel *d2ui.Sprite
globeSprite *d2ui.Sprite globeSprite *d2ui.Sprite
menuButton *d2ui.Button
hpManaStatusSprite *d2ui.Sprite hpManaStatusSprite *d2ui.Sprite
leftSkillResource *SkillResource leftSkillResource *SkillResource
rightSkillResource *SkillResource rightSkillResource *SkillResource
runButton *d2ui.Button runButton *d2ui.Button
zoneChangeText *d2ui.Label zoneChangeText *d2ui.Label
miniPanel *miniPanel miniPanel *miniPanel
isMiniPanelOpen bool
isZoneTextShown bool isZoneTextShown bool
hpStatsIsVisible bool hpStatsIsVisible bool
manaStatsIsVisible bool manaStatsIsVisible bool
@ -104,9 +95,6 @@ type HUD struct {
staminaTooltip *d2ui.Tooltip staminaTooltip *d2ui.Tooltip
runWalkTooltip *d2ui.Tooltip runWalkTooltip *d2ui.Tooltip
experienceTooltip *d2ui.Tooltip experienceTooltip *d2ui.Tooltip
healthTooltip *d2ui.Tooltip
manaTooltip *d2ui.Tooltip
miniPanelTooltip *d2ui.Tooltip
nameLabel *d2ui.Label nameLabel *d2ui.Label
healthGlobe *globeWidget healthGlobe *globeWidget
manaGlobe *globeWidget manaGlobe *globeWidget
@ -115,6 +103,7 @@ type HUD struct {
widgetLeftSkill *d2ui.CustomWidget widgetLeftSkill *d2ui.CustomWidget
widgetRightSkill *d2ui.CustomWidget widgetRightSkill *d2ui.CustomWidget
panelBackground *d2ui.CustomWidget panelBackground *d2ui.CustomWidget
panelGroup *d2ui.WidgetGroup
logger *d2util.Logger logger *d2util.Logger
} }
@ -123,7 +112,6 @@ func NewHUD(
asset *d2asset.AssetManager, asset *d2asset.AssetManager,
ui *d2ui.UIManager, ui *d2ui.UIManager,
hero *d2mapentity.Player, hero *d2mapentity.Player,
help *HelpOverlay,
miniPanel *miniPanel, miniPanel *miniPanel,
actionableRegions []actionableRegion, actionableRegions []actionableRegion,
mapEngine *d2mapengine.MapEngine, mapEngine *d2mapengine.MapEngine,
@ -137,14 +125,21 @@ func NewHUD(
zoneLabel := ui.NewLabel(d2resource.Font30, d2resource.PaletteUnits) zoneLabel := ui.NewLabel(d2resource.Font30, d2resource.PaletteUnits)
zoneLabel.Alignment = d2ui.HorizontalAlignCenter zoneLabel.Alignment = d2ui.HorizontalAlignCenter
healthGlobe := newGlobeWidget(ui, 0, screenHeight, typeHealthGlobe, &hero.Stats.Health, l, &hero.Stats.MaxHealth) healthGlobe := newGlobeWidget(ui, asset,
manaGlobe := newGlobeWidget(ui, screenWidth-manaGlobeScreenOffsetX, screenHeight, typeManaGlobe, &hero.Stats.Mana, l, &hero.Stats.MaxMana) 0, screenHeight,
typeHealthGlobe,
&hero.Stats.Health, &hero.Stats.MaxHealth,
l)
manaGlobe := newGlobeWidget(ui, asset,
screenWidth-manaGlobeScreenOffsetX, screenHeight,
typeManaGlobe,
&hero.Stats.Mana, &hero.Stats.MaxMana,
l)
hud := &HUD{ hud := &HUD{
asset: asset, asset: asset,
uiManager: ui, uiManager: ui,
hero: hero, hero: hero,
help: help,
mapEngine: mapEngine, mapEngine: mapEngine,
mapRenderer: mapRenderer, mapRenderer: mapRenderer,
miniPanel: miniPanel, miniPanel: miniPanel,
@ -165,15 +160,24 @@ func NewHUD(
// Load creates the ui elemets // Load creates the ui elemets
func (h *HUD) Load() { func (h *HUD) Load() {
h.panelGroup = h.uiManager.NewWidgetGroup(d2ui.RenderPriorityHUDPanel)
h.loadSprites() h.loadSprites()
h.healthGlobe.load() h.healthGlobe.load()
h.manaGlobe.load() h.healthGlobe.SetRenderPriority(d2ui.RenderPriorityForeground)
h.panelGroup.AddWidget(h.healthGlobe)
h.manaGlobe.load()
h.manaGlobe.SetRenderPriority(d2ui.RenderPriorityForeground)
h.panelGroup.AddWidget(h.manaGlobe)
h.loadTooltips()
h.loadSkillResources() h.loadSkillResources()
h.loadCustomWidgets() h.loadCustomWidgets()
h.loadUIButtons() h.loadUIButtons()
h.loadTooltips()
h.panelGroup.SetVisible(true)
} }
func (h *HUD) loadCustomWidgets() { func (h *HUD) loadCustomWidgets() {
@ -186,13 +190,19 @@ func (h *HUD) loadCustomWidgets() {
h.panelBackground = h.uiManager.NewCustomWidgetCached(h.renderPanelStatic, screenWidth, height) h.panelBackground = h.uiManager.NewCustomWidgetCached(h.renderPanelStatic, screenWidth, height)
h.panelBackground.SetPosition(0, screenHeight-height) h.panelBackground.SetPosition(0, screenHeight-height)
h.panelGroup.AddWidget(h.panelBackground)
// stamina bar // stamina bar
h.widgetStamina = h.uiManager.NewCustomWidget(h.renderStaminaBar, staminaBarWidth, staminaBarHeight) h.widgetStamina = h.uiManager.NewCustomWidget(h.renderStaminaBar, staminaBarWidth, staminaBarHeight)
h.widgetStamina.SetPosition(staminaBarOffsetX, staminaBarOffsetY) h.widgetStamina.SetPosition(staminaBarOffsetX, staminaBarOffsetY)
h.widgetStamina.SetTooltip(h.staminaTooltip)
h.panelGroup.AddWidget(h.widgetStamina)
// experience bar // experience bar
h.widgetExperience = h.uiManager.NewCustomWidget(h.renderExperienceBar, expBarWidth, expBarHeight) h.widgetExperience = h.uiManager.NewCustomWidget(h.renderExperienceBar, expBarWidth, expBarHeight)
h.widgetExperience.SetPosition(experienceBarOffsetX, experienceBarOffsetY)
h.widgetExperience.SetTooltip(h.experienceTooltip)
h.panelGroup.AddWidget(h.widgetExperience)
// Left skill widget // Left skill widget
leftRenderFunc := func(target d2interface.Surface) { leftRenderFunc := func(target d2interface.Surface) {
@ -202,6 +212,7 @@ func (h *HUD) loadCustomWidgets() {
h.widgetLeftSkill = h.uiManager.NewCustomWidget(leftRenderFunc, skillIconWidth, skillIconHeight) h.widgetLeftSkill = h.uiManager.NewCustomWidget(leftRenderFunc, skillIconWidth, skillIconHeight)
h.widgetLeftSkill.SetPosition(leftSkillX, screenHeight) h.widgetLeftSkill.SetPosition(leftSkillX, screenHeight)
h.panelGroup.AddWidget(h.widgetLeftSkill)
// Right skill widget // Right skill widget
rightRenderFunc := func(target d2interface.Surface) { rightRenderFunc := func(target d2interface.Surface) {
@ -211,6 +222,7 @@ func (h *HUD) loadCustomWidgets() {
h.widgetRightSkill = h.uiManager.NewCustomWidget(rightRenderFunc, skillIconWidth, skillIconHeight) h.widgetRightSkill = h.uiManager.NewCustomWidget(rightRenderFunc, skillIconWidth, skillIconHeight)
h.widgetRightSkill.SetPosition(rightSkillX, screenHeight) h.widgetRightSkill.SetPosition(rightSkillX, screenHeight)
h.panelGroup.AddWidget(h.widgetRightSkill)
} }
func (h *HUD) loadSkillResources() { func (h *HUD) loadSkillResources() {
@ -297,42 +309,31 @@ func (h *HUD) loadTooltips() {
labelX = centerX labelX = centerX
labelY = staminaExperienceY - halfLabelHeight labelY = staminaExperienceY - halfLabelHeight
h.experienceTooltip.SetPosition(labelX, labelY) h.experienceTooltip.SetPosition(labelX, labelY)
// Health tooltip
h.healthTooltip = h.uiManager.NewTooltip(d2resource.Font16, d2resource.PaletteUnits, d2ui.TooltipXLeft, d2ui.TooltipYTop)
h.healthTooltip.SetPosition(hpLabelX, hpLabelY)
h.healthTooltip.SetBoxEnabled(false)
// Health tooltip
h.manaTooltip = h.uiManager.NewTooltip(d2resource.Font16, d2resource.PaletteUnits, d2ui.TooltipXLeft, d2ui.TooltipYTop)
h.manaTooltip.SetPosition(manaLabelX, manaLabelY)
h.manaTooltip.SetBoxEnabled(false)
// minipanel tooltip
h.miniPanelTooltip = h.uiManager.NewTooltip(d2resource.Font16, d2resource.PaletteUnits, d2ui.TooltipXCenter, d2ui.TooltipYTop)
} }
func (h *HUD) loadUIButtons() { func (h *HUD) loadUIButtons() {
// Run button // Run button
h.runButton = h.uiManager.NewButton(d2ui.ButtonTypeRun, "") h.runButton = h.uiManager.NewButton(d2ui.ButtonTypeRun, "")
h.runButton.SetPosition(runButtonX, runButtonY) h.runButton.SetPosition(runButtonX, runButtonY)
h.runButton.OnActivated(func() { h.onToggleRunButton(false) }) h.runButton.OnActivated(func() { h.onToggleRunButton(false) })
h.runButton.SetTooltip(h.runWalkTooltip)
h.updateRunTooltipText()
h.panelGroup.AddWidget(h.runButton)
if h.hero.IsRunToggled() { if h.hero.IsRunToggled() {
h.runButton.Toggle() h.runButton.Toggle()
} }
}
// minipanel button func (h *HUD) updateRunTooltipText() {
h.menuButton = h.uiManager.NewButton(d2ui.ButtonTypeMinipanelOpenClose, "") var stringTableKey string
//nolint:golint,gomnd // 2 is not a magic number if h.hero.IsRunToggled() {
x := screenWidth/2 + miniPanelButtonOffsetX stringTableKey = "RunOff"
y := screenHeight + miniPanelButtonOffsetY } else {
h.menuButton.SetPosition(x, y) stringTableKey = "RunOn"
h.menuButton.OnActivated(func() { }
h.menuButton.Toggle()
h.miniPanel.Toggle() h.runWalkTooltip.SetText(h.asset.TranslateString(stringTableKey))
})
} }
func (h *HUD) onToggleRunButton(noButton bool) { func (h *HUD) onToggleRunButton(noButton bool) {
@ -341,6 +342,7 @@ func (h *HUD) onToggleRunButton(noButton bool) {
} }
h.hero.ToggleRunWalk() h.hero.ToggleRunWalk()
h.updateRunTooltipText()
// https://github.com/OpenDiablo2/OpenDiablo2/issues/800 // https://github.com/OpenDiablo2/OpenDiablo2/issues/800
h.hero.SetIsRunning(h.hero.IsRunToggled()) h.hero.SetIsRunning(h.hero.IsRunToggled())
@ -527,87 +529,16 @@ func (h *HUD) renderNewSkillsButton(x, _ int, target d2interface.Surface) error
return nil return nil
} }
//nolint:golint,dupl // we clean this up later func (h *HUD) setStaminaTooltipText() {
func (h *HUD) renderHealthTooltip(target d2interface.Surface) {
mx, my := h.lastMouseX, h.lastMouseY
// Create and format Health string from string lookup table.
fmtHealth := h.asset.TranslateString("panelhealth")
healthCurr, healthMax := h.hero.Stats.Health, h.hero.Stats.MaxHealth
strPanelHealth := fmt.Sprintf(fmtHealth, healthCurr, healthMax)
// Display current hp and mana stats hpGlobe or manaGlobe region is clicked
if !(h.actionableRegions[hpGlobe].rect.IsInRect(mx, my) || h.hpStatsIsVisible) {
return
}
h.healthTooltip.SetText(strPanelHealth)
h.healthTooltip.Render(target)
}
//nolint:golint,dupl // we clean this up later
func (h *HUD) renderManaTooltip(target d2interface.Surface) {
mx, my := h.lastMouseX, h.lastMouseY
// Create and format Mana string from string lookup table.
fmtMana := h.asset.TranslateString("panelmana")
manaCurr, manaMax := h.hero.Stats.Mana, h.hero.Stats.MaxMana
strPanelMana := fmt.Sprintf(fmtMana, manaCurr, manaMax)
if !(h.actionableRegions[manaGlobe].rect.IsInRect(mx, my) || h.manaStatsIsVisible) {
return
}
h.manaTooltip.SetText(strPanelMana)
h.manaTooltip.Render(target)
}
func (h *HUD) renderRunWalkTooltip(target d2interface.Surface) {
mx, my := h.lastMouseX, h.lastMouseY
// Display run/walk tooltip when hovered.
// Note that whether the player is walking or running, the tooltip is the same in Diablo 2.
if !h.actionableRegions[walkRun].rect.IsInRect(mx, my) {
return
}
var stringTableKey string
if h.hero.IsRunToggled() {
stringTableKey = "RunOff"
} else {
stringTableKey = "RunOn"
}
h.runWalkTooltip.SetText(h.asset.TranslateString(stringTableKey))
h.runWalkTooltip.Render(target)
}
func (h *HUD) renderStaminaTooltip(target d2interface.Surface) {
mx, my := h.lastMouseX, h.lastMouseY
// Display stamina tooltip when hovered.
if !h.actionableRegions[stamina].rect.IsInRect(mx, my) {
return
}
// Create and format Stamina string from string lookup table. // Create and format Stamina string from string lookup table.
fmtStamina := h.asset.TranslateString("panelstamina") fmtStamina := h.asset.TranslateString("panelstamina")
staminaCurr, staminaMax := int(h.hero.Stats.Stamina), h.hero.Stats.MaxStamina staminaCurr, staminaMax := int(h.hero.Stats.Stamina), h.hero.Stats.MaxStamina
strPanelStamina := fmt.Sprintf(fmtStamina, staminaCurr, staminaMax) strPanelStamina := fmt.Sprintf(fmtStamina, staminaCurr, staminaMax)
h.staminaTooltip.SetText(strPanelStamina) h.staminaTooltip.SetText(strPanelStamina)
h.staminaTooltip.Render(target)
} }
func (h *HUD) renderExperienceTooltip(target d2interface.Surface) { func (h *HUD) setExperienceTooltipText() {
mx, my := h.lastMouseX, h.lastMouseY
// Display experience tooltip when hovered.
if !h.actionableRegions[xp].rect.IsInRect(mx, my) {
return
}
// Create and format Experience string from string lookup table. // Create and format Experience string from string lookup table.
fmtExp := h.asset.TranslateString("panelexp") fmtExp := h.asset.TranslateString("panelexp")
@ -621,7 +552,6 @@ func (h *HUD) renderExperienceTooltip(target d2interface.Surface) {
strPanelExp := fmt.Sprintf(fmtExp, expCurr, expMax) strPanelExp := fmt.Sprintf(fmtExp, expCurr, expMax)
h.experienceTooltip.SetText(strPanelExp) h.experienceTooltip.SetText(strPanelExp)
h.experienceTooltip.Render(target)
} }
func (h *HUD) renderForSelectableEntitiesHovered(target d2interface.Surface) { func (h *HUD) renderForSelectableEntitiesHovered(target d2interface.Surface) {
@ -666,30 +596,11 @@ func (h *HUD) renderForSelectableEntitiesHovered(target d2interface.Surface) {
func (h *HUD) Render(target d2interface.Surface) error { func (h *HUD) Render(target d2interface.Surface) error {
h.renderForSelectableEntitiesHovered(target) h.renderForSelectableEntitiesHovered(target)
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)
if err := h.help.Render(target); err != nil {
return err
}
if h.isZoneTextShown { if h.isZoneTextShown {
h.zoneChangeText.SetPosition(zoneChangeTextX, zoneChangeTextY) h.zoneChangeText.SetPosition(zoneChangeTextX, zoneChangeTextY)
h.zoneChangeText.Render(target) h.zoneChangeText.Render(target)
} }
h.renderHealthTooltip(target)
h.renderManaTooltip(target)
h.renderRunWalkTooltip(target)
h.renderStaminaTooltip(target)
h.renderExperienceTooltip(target)
if h.skillSelectMenu.IsOpen() { if h.skillSelectMenu.IsOpen() {
h.skillSelectMenu.Render(target) h.skillSelectMenu.Render(target)
} }
@ -717,6 +628,21 @@ func (h *HUD) getSkillResourceByClass(class string) string {
return entry return entry
} }
// Advance updates syncs data on widgets that might have changed. I.e. the current stamina value
// in the stamina tooltip
func (h *HUD) Advance(elapsed float64) {
h.setStaminaTooltipText()
h.setExperienceTooltipText()
if err := h.healthGlobe.Advance(elapsed); err != nil {
h.logger.Error(err.Error())
}
if err := h.manaGlobe.Advance(elapsed); err != nil {
h.logger.Error(err.Error())
}
}
// OnMouseMove handles mouse move events // OnMouseMove handles mouse move events
func (h *HUD) OnMouseMove(event d2interface.MouseMoveEvent) bool { func (h *HUD) OnMouseMove(event d2interface.MouseMoveEvent) bool {
mx, my := event.X(), event.Y() mx, my := event.X(), event.Y()
@ -728,20 +654,3 @@ func (h *HUD) OnMouseMove(event d2interface.MouseMoveEvent) bool {
return false return false
} }
func (h *HUD) closeMinipanelTemporary() {
h.isMiniPanelOpen = h.miniPanel.IsOpen()
if h.isMiniPanelOpen {
h.menuButton.SetEnabled(false)
h.menuButton.Toggle()
h.miniPanel.Close()
}
}
func (h *HUD) restoreMinipanelFromTempClose() {
if h.isMiniPanelOpen {
h.menuButton.SetEnabled(true)
h.menuButton.Toggle()
h.miniPanel.Open()
}
}

View File

@ -59,16 +59,21 @@ func newMiniPanel(asset *d2asset.AssetManager,
} }
type miniPanel struct { type miniPanel struct {
ui *d2ui.UIManager ui *d2ui.UIManager
asset *d2asset.AssetManager asset *d2asset.AssetManager
container *d2ui.Sprite container *d2ui.Sprite
sprite *d2ui.Sprite sprite *d2ui.Sprite
isOpen bool menuButton *d2ui.Button
isSinglePlayer bool miniPanelTooltip *d2ui.Tooltip
movedLeft bool isOpen bool
movedRight bool tempIsOpen bool
panelGroup *d2ui.WidgetGroup disabled bool
tooltipGroup *d2ui.WidgetGroup isSinglePlayer bool
movedLeft bool
movedRight bool
panelGroup *d2ui.WidgetGroup
groupAlwaysVis *d2ui.WidgetGroup
tooltipGroup *d2ui.WidgetGroup
logger *d2util.Logger logger *d2util.Logger
} }
@ -91,8 +96,11 @@ func (m *miniPanel) createWidgets(actions *miniPanelActions) {
m.panelGroup = m.ui.NewWidgetGroup(d2ui.RenderPriorityMinipanel) m.panelGroup = m.ui.NewWidgetGroup(d2ui.RenderPriorityMinipanel)
m.panelGroup.SetPosition(miniPanelX, miniPanelY) m.panelGroup.SetPosition(miniPanelX, miniPanelY)
m.groupAlwaysVis = m.ui.NewWidgetGroup(d2ui.RenderPriorityMinipanel)
m.tooltipGroup = m.ui.NewWidgetGroup(d2ui.RenderPriorityForeground) m.tooltipGroup = m.ui.NewWidgetGroup(d2ui.RenderPriorityForeground)
// container sprite
miniPanelContainerPath := d2resource.Minipanel miniPanelContainerPath := d2resource.Minipanel
if m.isSinglePlayer { if m.isSinglePlayer {
miniPanelContainerPath = d2resource.MinipanelSmall miniPanelContainerPath = d2resource.MinipanelSmall
@ -114,6 +122,14 @@ func (m *miniPanel) createWidgets(actions *miniPanelActions) {
m.container.SetPosition(x, y) m.container.SetPosition(x, y)
m.panelGroup.AddWidget(m.container) m.panelGroup.AddWidget(m.container)
m.createButtons(actions)
m.panelGroup.SetVisible(false)
}
func (m *miniPanel) createButtons(actions *miniPanelActions) {
var x, y int
buttonWidth, buttonHeight, err := m.sprite.GetFrameSize(0) buttonWidth, buttonHeight, err := m.sprite.GetFrameSize(0)
if err != nil { if err != nil {
m.logger.Error(err.Error()) m.logger.Error(err.Error())
@ -181,7 +197,21 @@ func (m *miniPanel) createWidgets(actions *miniPanelActions) {
m.panelGroup.AddWidget(btn) m.panelGroup.AddWidget(btn)
} }
m.panelGroup.SetVisible(false) //nolint:gomnd // divide by 2 is not a magic number
x = screenWidth/2 + miniPanelButtonOffsetX
y = screenHeight + miniPanelButtonOffsetY
// minipanel open/close tooltip
m.miniPanelTooltip = m.ui.NewTooltip(d2resource.Font16, d2resource.PaletteUnits, d2ui.TooltipXCenter, d2ui.TooltipYTop)
m.miniPanelTooltip.SetPosition(x+miniPanelTooltipOffsetX, y+miniPanelTooltipOffsetY)
// minipanel button
m.menuButton = m.ui.NewButton(d2ui.ButtonTypeMinipanelOpenClose, "")
m.menuButton.SetPosition(x, y)
m.menuButton.OnActivated(m.onMenuButtonClicked)
m.menuButton.SetTooltip(m.miniPanelTooltip)
m.updateMinipanelTooltipText()
m.groupAlwaysVis.AddWidget(m.menuButton)
} }
func (m *miniPanel) createButton(content miniPanelContent, x, y, buttonHeight int) *d2ui.Button { func (m *miniPanel) createButton(content miniPanelContent, x, y, buttonHeight int) *d2ui.Button {
@ -197,10 +227,28 @@ func (m *miniPanel) createButton(content miniPanelContent, x, y, buttonHeight in
btn.SetPosition(x, y) btn.SetPosition(x, y)
btn.OnActivated(content.onActivate) btn.OnActivated(content.onActivate)
btn.SetTooltip(tt) btn.SetTooltip(tt)
btn.SetRenderPriority(d2ui.RenderPriorityForeground)
return btn return btn
} }
func (m *miniPanel) onMenuButtonClicked() {
m.menuButton.Toggle()
m.Toggle()
m.updateMinipanelTooltipText()
}
func (m *miniPanel) updateMinipanelTooltipText() {
var stringTableKey string
if m.menuButton.GetToggled() {
stringTableKey = "panelcmini"
} else {
stringTableKey = "panelmini"
}
m.miniPanelTooltip.SetText(m.asset.TranslateString(stringTableKey))
}
func (m *miniPanel) IsOpen() bool { func (m *miniPanel) IsOpen() bool {
return m.isOpen return m.isOpen
} }
@ -214,12 +262,24 @@ func (m *miniPanel) Toggle() {
} }
func (m *miniPanel) Open() { func (m *miniPanel) Open() {
m.panelGroup.SetVisible(true) if !m.movedLeft && !m.movedRight {
m.panelGroup.SetVisible(true)
}
if !m.menuButton.GetToggled() {
m.menuButton.Toggle()
}
m.isOpen = true m.isOpen = true
} }
func (m *miniPanel) Close() { func (m *miniPanel) Close() {
m.panelGroup.SetVisible(false) m.panelGroup.SetVisible(false)
if m.menuButton.GetToggled() {
m.menuButton.Toggle()
}
m.isOpen = false m.isOpen = false
} }
@ -234,7 +294,6 @@ func (m *miniPanel) moveRight() {
func (m *miniPanel) undoMoveRight() { func (m *miniPanel) undoMoveRight() {
m.panelGroup.OffsetPosition(-panelOffsetRight, 0) m.panelGroup.OffsetPosition(-panelOffsetRight, 0)
m.tooltipGroup.OffsetPosition(-panelOffsetRight, 0)
} }
func (m *miniPanel) moveLeft() { func (m *miniPanel) moveLeft() {
@ -258,7 +317,7 @@ func (m *miniPanel) SetMovedLeft(moveLeft bool) {
m.panelGroup.SetVisible(false) m.panelGroup.SetVisible(false)
} else { } else {
m.moveRight() m.moveRight()
m.panelGroup.SetVisible(true) m.panelGroup.SetVisible(m.isOpen)
} }
} else { } else {
if moveLeft { if moveLeft {
@ -282,7 +341,7 @@ func (m *miniPanel) SetMovedRight(moveRight bool) {
m.panelGroup.SetVisible(false) m.panelGroup.SetVisible(false)
} else { } else {
m.moveLeft() m.moveLeft()
m.panelGroup.SetVisible(true) m.panelGroup.SetVisible(m.isOpen)
} }
} else { } else {
if moveRight { if moveRight {
@ -294,3 +353,51 @@ func (m *miniPanel) SetMovedRight(moveRight bool) {
m.movedRight = moveRight m.movedRight = moveRight
} }
func (m *miniPanel) openDisabled() {
if m.disabled {
return
}
m.tempIsOpen = m.isOpen
if !m.isOpen {
m.Open()
}
m.menuButton.SetEnabled(false)
m.panelGroup.SetEnabled(false)
m.disabled = true
}
func (m *miniPanel) closeDisabled() {
if m.disabled {
return
}
m.tempIsOpen = m.isOpen
if m.isOpen {
m.Close()
}
m.menuButton.SetEnabled(false)
m.panelGroup.SetEnabled(false)
m.disabled = true
}
func (m *miniPanel) restoreDisabled() {
if !m.disabled {
return
}
m.disabled = false
m.menuButton.SetEnabled(true)
m.panelGroup.SetEnabled(true)
if m.tempIsOpen {
m.Open()
} else {
m.Close()
}
}