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

View File

@ -11,6 +11,7 @@ type CustomWidget struct {
renderFunc func(target d2interface.Surface)
cached bool
cachedImg *d2interface.Surface
tooltip *Tooltip
}
// 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
func (c *CustomWidget) Advance(elapsed float64) error {
return nil

View File

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

View File

@ -18,6 +18,7 @@ type UIManager struct {
inputManager d2interface.InputManager
audio d2interface.AudioProvider
widgets []Widget
tooltips []*Tooltip
widgetsGroups []*WidgetGroup
clickableWidgets []ClickableWidget
cursorButtons CursorButton
@ -53,13 +54,8 @@ func (ui *UIManager) Reset() {
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].renderPriority < ui.widgetsGroups[j].renderPriority
})
func (ui *UIManager) addClickable(widget ClickableWidget) {
ui.clickableWidgets = append(ui.clickableWidgets, widget)
}
// addWidget adds a widget to the UI manager
@ -71,13 +67,27 @@ func (ui *UIManager) addWidget(widget Widget) {
clickable, ok := widget.(ClickableWidget)
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)
sort.SliceStable(ui.widgets, func(i, j int) bool {
return ui.widgets[i].GetRenderPriority() < ui.widgets[j].GetRenderPriority()
})
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
func (ui *UIManager) OnMouseButtonUp(event d2interface.MouseEvent) bool {
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
func (ui *UIManager) Render(target d2interface.Surface) {
for _, widgetGroup := range ui.widgetsGroups {
if widgetGroup.GetVisible() {
widgetGroup.Render(target)
}
}
for _, widget := range ui.widgets {
if widget.GetVisible() {
widget.Render(target)
}
}
for _, tooltip := range ui.tooltips {
if tooltip.GetVisible() {
tooltip.Render(target)
}
}
}
// 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.
type RenderPriority int
// Render priorities that determine the order in which widgets/widgetgroups are
// rendered. The higher the later it is rendered
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
// RenderPriorityMinipanel is the priority for the minipanel icons
RenderPriorityMinipanel
// RenderPriorityHeroStatsPanel is the priority for the hero_stats_panel
RenderPriorityHeroStatsPanel
// RenderPriorityForeground is the last element drawn
RenderPriorityHUDPanel
RenderPriorityMinipanel
RenderPriorityHelpPanel
RenderPriorityForeground
)

View File

@ -28,7 +28,7 @@ func (ui *UIManager) NewWidgetGroup(priority RenderPriority) *WidgetGroup {
BaseWidget: base,
}
ui.addWidgetGroup(group)
ui.addWidget(group)
return group
}
@ -40,6 +40,10 @@ func (wg *WidgetGroup) AddWidget(w Widget) {
sort.SliceStable(wg.entries, func(i, j int) bool {
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
@ -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
}
helpOverlay := NewHelpOverlay(asset, renderer, ui, guiManager, l, keyMap)
hud := NewHUD(asset, ui, hero, helpOverlay, miniPanel, actionableRegions, mapEngine, l, mapRenderer)
helpOverlay := NewHelpOverlay(asset, ui, l, keyMap)
hud := NewHUD(asset, ui, hero, miniPanel, actionableRegions, mapEngine, l, mapRenderer)
const blackAlpha50percent = 0x0000007f
@ -270,7 +270,8 @@ func NewGameControls(
gc.inventory.SetOnCloseCb(gc.onCloseInventory)
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)
if err != nil {
@ -397,6 +398,8 @@ func (g *GameControls) OnKeyDown(event d2interface.KeyEvent) bool {
case d2enum.HoldRun:
g.hud.onToggleRunButton(true)
case d2enum.ToggleHelpScreen:
g.hud.miniPanel.openDisabled()
g.HelpOverlay.Toggle()
g.updateLayout()
default:
@ -646,7 +649,7 @@ func (g *GameControls) openEscMenu() {
g.inventory.Close()
g.skilltree.Close()
g.heroStatsPanel.Close()
g.hud.closeMinipanelTemporary()
g.hud.miniPanel.closeDisabled()
g.escapeMenu.open()
g.updateLayout()
}
@ -671,6 +674,7 @@ func (g *GameControls) Load() {
// Advance advances the state of the GameControls
func (g *GameControls) Advance(elapsed float64) error {
g.mapRenderer.Advance(elapsed)
g.hud.Advance(elapsed)
if err := g.escapeMenu.Advance(elapsed); err != nil {
return err

View File

@ -1,11 +1,13 @@
package d2player
import (
"fmt"
"image"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2util"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2ui"
)
@ -30,11 +32,20 @@ const (
manaStatusOffsetY = -12
manaGlobeScreenOffsetX = 117
hpLabelX = 15
hpLabelY = 487
manaLabelX = 785
manaLabelY = 487
)
// static check that globeWidget implements Widget
var _ d2ui.Widget = &globeWidget{}
// static check that globeWidget implements ClickableWidget
var _ d2ui.ClickableWidget = &globeWidget{}
type globeFrame struct {
sprite *d2ui.Sprite
offsetX int
@ -53,14 +64,18 @@ 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
}
func newGlobeWidget(ui *d2ui.UIManager, x, y int, gtype globeType, value *int, l d2util.LogLevel, valueMax *int) *globeWidget {
func newGlobeWidget(ui *d2ui.UIManager,
asset *d2asset.AssetManager,
x, y int,
gtype globeType,
value *int, valueMax *int,
l d2util.LogLevel) *globeWidget {
var globe, overlap *globeFrame
var tooltipX, tooltipY int
var tooltipTrans string
base := d2ui.NewBaseWidget(ui)
base.SetPosition(x, y)
@ -75,6 +90,8 @@ func newGlobeWidget(ui *d2ui.UIManager, x, y int, gtype globeType, value *int, l
offsetY: globeSpriteOffsetY,
idx: frameHealthStatus,
}
tooltipX, tooltipY = hpLabelX, hpLabelY
tooltipTrans = "panelhealth"
} else if gtype == typeManaGlobe {
globe = &globeFrame{
offsetX: manaStatusOffsetX,
@ -86,16 +103,35 @@ func newGlobeWidget(ui *d2ui.UIManager, x, y int, gtype globeType, value *int, l
offsetY: rightGlobeOffsetY,
idx: frameRightGlobe,
}
tooltipX, tooltipY = manaLabelX, manaLabelY
tooltipTrans = "panelmana"
}
gw := &globeWidget{
BaseWidget: base,
value: value,
valueMax: valueMax,
globe: globe,
overlap: overlap,
BaseWidget: base,
asset: asset,
value: value,
valueMax: valueMax,
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.SetLevel(l)
gw.logger.SetPrefix(logPrefix)
@ -105,11 +141,19 @@ func newGlobeWidget(ui *d2ui.UIManager, x, y int, gtype globeType, value *int, l
type globeWidget struct {
*d2ui.BaseWidget
asset *d2asset.AssetManager
value *int
valueMax *int
globe *globeFrame
overlap *globeFrame
logger *d2util.Logger
pressed bool
isTooltipLocked bool
tooltip *d2ui.Tooltip
tooltipX int
tooltipY int
tooltipTrans string
}
func (g *globeWidget) load() {
@ -128,6 +172,11 @@ func (g *globeWidget) load() {
}
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
@ -144,10 +193,49 @@ func (g *globeWidget) Render(target d2interface.Surface) {
g.overlap.sprite.Render(target)
}
func (g *globeWidget) GetSize() (x, y int) {
return g.overlap.getSize()
// Contains is special here as the point of origin is at the lower left corner
// 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 {
g.updateTooltip()
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/d2util"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2gui"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2ui"
)
@ -159,18 +158,14 @@ const (
// NewHelpOverlay creates a new HelpOverlay instance
func NewHelpOverlay(
asset *d2asset.AssetManager,
renderer d2interface.Renderer,
ui *d2ui.UIManager,
guiManager *d2gui.GuiManager,
l d2util.LogLevel,
keyMap *KeyMap,
) *HelpOverlay {
h := &HelpOverlay{
asset: asset,
renderer: renderer,
uiManager: ui,
guiManager: guiManager,
keyMap: keyMap,
asset: asset,
uiManager: ui,
keyMap: keyMap,
}
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
type HelpOverlay struct {
asset *d2asset.AssetManager
isOpen bool
renderer d2interface.Renderer
frames []*d2ui.Sprite
text []*d2ui.Label
lines []line
uiManager *d2ui.UIManager
layout *d2gui.Layout
closeButton *d2ui.Button
guiManager *d2gui.GuiManager
keyMap *KeyMap
asset *d2asset.AssetManager
isOpen bool
frames []*d2ui.Sprite
text []*d2ui.Label
lines []line
uiManager *d2ui.UIManager
closeButton *d2ui.Button
keyMap *KeyMap
onCloseCb func()
panelGroup *d2ui.WidgetGroup
backgroundWidget *d2ui.CustomWidget
logger *d2util.Logger
}
@ -211,20 +206,18 @@ func (h *HelpOverlay) Toggle() {
// Close will hide the help overlay
func (h *HelpOverlay) Close() {
h.isOpen = false
h.closeButton.SetVisible(false)
h.guiManager.SetLayout(nil)
h.panelGroup.SetVisible(false)
h.onCloseCb()
}
// SetOnCloseCb sets the callback run when Close() is called
func (h *HelpOverlay) SetOnCloseCb(cb func()) {
h.onCloseCb = cb
}
func (h *HelpOverlay) open() {
h.isOpen = true
if h.layout == nil {
h.layout = d2gui.CreateLayout(h.renderer, d2gui.PositionTypeHorizontal, h.asset)
}
h.closeButton.SetVisible(true)
h.closeButton.SetPressed(false)
h.guiManager.SetLayout(h.layout)
h.panelGroup.SetVisible(true)
}
// 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
func (h *HelpOverlay) IsInRect(px, py int) bool {
ww, hh := h.layout.GetSize()
x, y := h.layout.GetPosition()
if px >= x && px <= x+ww && py >= y && py <= y+hh {
return true
}
return false
return h.panelGroup.Contains(px, py)
}
// Load the overlay graphical assets
func (h *HelpOverlay) Load() {
h.panelGroup = h.uiManager.NewWidgetGroup(d2ui.RenderPriorityHelpPanel)
h.setupOverlayFrame()
h.setupTitleAndButton()
h.setupBulletedList()
h.setupLabelsWithLines()
h.backgroundWidget = h.uiManager.NewCustomWidgetCached(h.Render, screenWidth, screenHeight)
h.panelGroup.AddWidget(h.backgroundWidget)
h.panelGroup.SetVisible(false)
}
func (h *HelpOverlay) setupOverlayFrame() {
@ -330,6 +322,8 @@ func (h *HelpOverlay) setupTitleAndButton() {
h.closeButton.SetPosition(closeButtonX, closeButtonY)
h.closeButton.SetVisible(false)
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.SetText(h.asset.TranslateString("strClose")) // "Close"
@ -621,11 +615,7 @@ func (h *HelpOverlay) createCallout(c callout) {
}
// Render the overlay to the given surface
func (h *HelpOverlay) Render(target d2interface.Surface) error {
if !h.isOpen {
return nil
}
func (h *HelpOverlay) Render(target d2interface.Surface) {
for _, f := range h.frames {
f.Render(target)
}
@ -639,6 +629,4 @@ func (h *HelpOverlay) Render(target d2interface.Surface) error {
target.DrawLine(l.MoveX, l.MoveY, l.Color)
target.Pop()
}
return nil
}

View File

@ -35,16 +35,6 @@ const (
percentStaminaBarLow = 0.25
)
const (
hpLabelX = 15
hpLabelY = 487
manaLabelX = 785
manaLabelY = 487
staminaExperienceY = 535
)
const (
frameHealthStatus = 0
frameManaStatus = 1
@ -57,8 +47,9 @@ const (
)
const (
staminaBarOffsetX = 273
staminaBarOffsetY = 572
staminaBarOffsetX = 273
staminaBarOffsetY = 572
staminaExperienceY = 535
experienceBarOffsetX = 256
experienceBarOffsetY = 561
@ -68,6 +59,9 @@ const (
miniPanelButtonOffsetX = -8
miniPanelButtonOffsetY = -38
miniPanelTooltipOffsetX = 7
miniPanelTooltipOffsetY = -14
)
const (
@ -81,7 +75,6 @@ type HUD struct {
actionableRegions []actionableRegion
asset *d2asset.AssetManager
uiManager *d2ui.UIManager
help *HelpOverlay
mapEngine *d2mapengine.MapEngine
mapRenderer *d2maprenderer.MapRenderer
lastMouseX int
@ -89,14 +82,12 @@ type HUD struct {
hero *d2mapentity.Player
mainPanel *d2ui.Sprite
globeSprite *d2ui.Sprite
menuButton *d2ui.Button
hpManaStatusSprite *d2ui.Sprite
leftSkillResource *SkillResource
rightSkillResource *SkillResource
runButton *d2ui.Button
zoneChangeText *d2ui.Label
miniPanel *miniPanel
isMiniPanelOpen bool
isZoneTextShown bool
hpStatsIsVisible bool
manaStatsIsVisible bool
@ -104,9 +95,6 @@ type HUD struct {
staminaTooltip *d2ui.Tooltip
runWalkTooltip *d2ui.Tooltip
experienceTooltip *d2ui.Tooltip
healthTooltip *d2ui.Tooltip
manaTooltip *d2ui.Tooltip
miniPanelTooltip *d2ui.Tooltip
nameLabel *d2ui.Label
healthGlobe *globeWidget
manaGlobe *globeWidget
@ -115,6 +103,7 @@ type HUD struct {
widgetLeftSkill *d2ui.CustomWidget
widgetRightSkill *d2ui.CustomWidget
panelBackground *d2ui.CustomWidget
panelGroup *d2ui.WidgetGroup
logger *d2util.Logger
}
@ -123,7 +112,6 @@ func NewHUD(
asset *d2asset.AssetManager,
ui *d2ui.UIManager,
hero *d2mapentity.Player,
help *HelpOverlay,
miniPanel *miniPanel,
actionableRegions []actionableRegion,
mapEngine *d2mapengine.MapEngine,
@ -137,14 +125,21 @@ func NewHUD(
zoneLabel := ui.NewLabel(d2resource.Font30, d2resource.PaletteUnits)
zoneLabel.Alignment = d2ui.HorizontalAlignCenter
healthGlobe := newGlobeWidget(ui, 0, screenHeight, typeHealthGlobe, &hero.Stats.Health, l, &hero.Stats.MaxHealth)
manaGlobe := newGlobeWidget(ui, screenWidth-manaGlobeScreenOffsetX, screenHeight, typeManaGlobe, &hero.Stats.Mana, l, &hero.Stats.MaxMana)
healthGlobe := newGlobeWidget(ui, asset,
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{
asset: asset,
uiManager: ui,
hero: hero,
help: help,
mapEngine: mapEngine,
mapRenderer: mapRenderer,
miniPanel: miniPanel,
@ -165,15 +160,24 @@ func NewHUD(
// Load creates the ui elemets
func (h *HUD) Load() {
h.panelGroup = h.uiManager.NewWidgetGroup(d2ui.RenderPriorityHUDPanel)
h.loadSprites()
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.loadCustomWidgets()
h.loadUIButtons()
h.loadTooltips()
h.panelGroup.SetVisible(true)
}
func (h *HUD) loadCustomWidgets() {
@ -186,13 +190,19 @@ func (h *HUD) loadCustomWidgets() {
h.panelBackground = h.uiManager.NewCustomWidgetCached(h.renderPanelStatic, screenWidth, height)
h.panelBackground.SetPosition(0, screenHeight-height)
h.panelGroup.AddWidget(h.panelBackground)
// stamina bar
h.widgetStamina = h.uiManager.NewCustomWidget(h.renderStaminaBar, staminaBarWidth, staminaBarHeight)
h.widgetStamina.SetPosition(staminaBarOffsetX, staminaBarOffsetY)
h.widgetStamina.SetTooltip(h.staminaTooltip)
h.panelGroup.AddWidget(h.widgetStamina)
// experience bar
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
leftRenderFunc := func(target d2interface.Surface) {
@ -202,6 +212,7 @@ func (h *HUD) loadCustomWidgets() {
h.widgetLeftSkill = h.uiManager.NewCustomWidget(leftRenderFunc, skillIconWidth, skillIconHeight)
h.widgetLeftSkill.SetPosition(leftSkillX, screenHeight)
h.panelGroup.AddWidget(h.widgetLeftSkill)
// Right skill widget
rightRenderFunc := func(target d2interface.Surface) {
@ -211,6 +222,7 @@ func (h *HUD) loadCustomWidgets() {
h.widgetRightSkill = h.uiManager.NewCustomWidget(rightRenderFunc, skillIconWidth, skillIconHeight)
h.widgetRightSkill.SetPosition(rightSkillX, screenHeight)
h.panelGroup.AddWidget(h.widgetRightSkill)
}
func (h *HUD) loadSkillResources() {
@ -297,42 +309,31 @@ func (h *HUD) loadTooltips() {
labelX = centerX
labelY = staminaExperienceY - halfLabelHeight
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() {
// Run button
h.runButton = h.uiManager.NewButton(d2ui.ButtonTypeRun, "")
h.runButton.SetPosition(runButtonX, runButtonY)
h.runButton.OnActivated(func() { h.onToggleRunButton(false) })
h.runButton.SetTooltip(h.runWalkTooltip)
h.updateRunTooltipText()
h.panelGroup.AddWidget(h.runButton)
if h.hero.IsRunToggled() {
h.runButton.Toggle()
}
}
// minipanel button
h.menuButton = h.uiManager.NewButton(d2ui.ButtonTypeMinipanelOpenClose, "")
//nolint:golint,gomnd // 2 is not a magic number
x := screenWidth/2 + miniPanelButtonOffsetX
y := screenHeight + miniPanelButtonOffsetY
h.menuButton.SetPosition(x, y)
h.menuButton.OnActivated(func() {
h.menuButton.Toggle()
h.miniPanel.Toggle()
})
func (h *HUD) updateRunTooltipText() {
var stringTableKey string
if h.hero.IsRunToggled() {
stringTableKey = "RunOff"
} else {
stringTableKey = "RunOn"
}
h.runWalkTooltip.SetText(h.asset.TranslateString(stringTableKey))
}
func (h *HUD) onToggleRunButton(noButton bool) {
@ -341,6 +342,7 @@ func (h *HUD) onToggleRunButton(noButton bool) {
}
h.hero.ToggleRunWalk()
h.updateRunTooltipText()
// https://github.com/OpenDiablo2/OpenDiablo2/issues/800
h.hero.SetIsRunning(h.hero.IsRunToggled())
@ -527,87 +529,16 @@ func (h *HUD) renderNewSkillsButton(x, _ int, target d2interface.Surface) error
return nil
}
//nolint:golint,dupl // we clean this up later
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
}
func (h *HUD) setStaminaTooltipText() {
// Create and format Stamina string from string lookup table.
fmtStamina := h.asset.TranslateString("panelstamina")
staminaCurr, staminaMax := int(h.hero.Stats.Stamina), h.hero.Stats.MaxStamina
strPanelStamina := fmt.Sprintf(fmtStamina, staminaCurr, staminaMax)
h.staminaTooltip.SetText(strPanelStamina)
h.staminaTooltip.Render(target)
}
func (h *HUD) renderExperienceTooltip(target d2interface.Surface) {
mx, my := h.lastMouseX, h.lastMouseY
// Display experience tooltip when hovered.
if !h.actionableRegions[xp].rect.IsInRect(mx, my) {
return
}
func (h *HUD) setExperienceTooltipText() {
// Create and format Experience string from string lookup table.
fmtExp := h.asset.TranslateString("panelexp")
@ -621,7 +552,6 @@ func (h *HUD) renderExperienceTooltip(target d2interface.Surface) {
strPanelExp := fmt.Sprintf(fmtExp, expCurr, expMax)
h.experienceTooltip.SetText(strPanelExp)
h.experienceTooltip.Render(target)
}
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 {
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 {
h.zoneChangeText.SetPosition(zoneChangeTextX, zoneChangeTextY)
h.zoneChangeText.Render(target)
}
h.renderHealthTooltip(target)
h.renderManaTooltip(target)
h.renderRunWalkTooltip(target)
h.renderStaminaTooltip(target)
h.renderExperienceTooltip(target)
if h.skillSelectMenu.IsOpen() {
h.skillSelectMenu.Render(target)
}
@ -717,6 +628,21 @@ func (h *HUD) getSkillResourceByClass(class string) string {
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
func (h *HUD) OnMouseMove(event d2interface.MouseMoveEvent) bool {
mx, my := event.X(), event.Y()
@ -728,20 +654,3 @@ func (h *HUD) OnMouseMove(event d2interface.MouseMoveEvent) bool {
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 {
ui *d2ui.UIManager
asset *d2asset.AssetManager
container *d2ui.Sprite
sprite *d2ui.Sprite
isOpen bool
isSinglePlayer bool
movedLeft bool
movedRight bool
panelGroup *d2ui.WidgetGroup
tooltipGroup *d2ui.WidgetGroup
ui *d2ui.UIManager
asset *d2asset.AssetManager
container *d2ui.Sprite
sprite *d2ui.Sprite
menuButton *d2ui.Button
miniPanelTooltip *d2ui.Tooltip
isOpen bool
tempIsOpen bool
disabled bool
isSinglePlayer bool
movedLeft bool
movedRight bool
panelGroup *d2ui.WidgetGroup
groupAlwaysVis *d2ui.WidgetGroup
tooltipGroup *d2ui.WidgetGroup
logger *d2util.Logger
}
@ -91,8 +96,11 @@ func (m *miniPanel) createWidgets(actions *miniPanelActions) {
m.panelGroup = m.ui.NewWidgetGroup(d2ui.RenderPriorityMinipanel)
m.panelGroup.SetPosition(miniPanelX, miniPanelY)
m.groupAlwaysVis = m.ui.NewWidgetGroup(d2ui.RenderPriorityMinipanel)
m.tooltipGroup = m.ui.NewWidgetGroup(d2ui.RenderPriorityForeground)
// container sprite
miniPanelContainerPath := d2resource.Minipanel
if m.isSinglePlayer {
miniPanelContainerPath = d2resource.MinipanelSmall
@ -114,6 +122,14 @@ func (m *miniPanel) createWidgets(actions *miniPanelActions) {
m.container.SetPosition(x, y)
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)
if err != nil {
m.logger.Error(err.Error())
@ -181,7 +197,21 @@ func (m *miniPanel) createWidgets(actions *miniPanelActions) {
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 {
@ -197,10 +227,28 @@ func (m *miniPanel) createButton(content miniPanelContent, x, y, buttonHeight in
btn.SetPosition(x, y)
btn.OnActivated(content.onActivate)
btn.SetTooltip(tt)
btn.SetRenderPriority(d2ui.RenderPriorityForeground)
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 {
return m.isOpen
}
@ -214,12 +262,24 @@ func (m *miniPanel) Toggle() {
}
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
}
func (m *miniPanel) Close() {
m.panelGroup.SetVisible(false)
if m.menuButton.GetToggled() {
m.menuButton.Toggle()
}
m.isOpen = false
}
@ -234,7 +294,6 @@ func (m *miniPanel) moveRight() {
func (m *miniPanel) undoMoveRight() {
m.panelGroup.OffsetPosition(-panelOffsetRight, 0)
m.tooltipGroup.OffsetPosition(-panelOffsetRight, 0)
}
func (m *miniPanel) moveLeft() {
@ -258,7 +317,7 @@ func (m *miniPanel) SetMovedLeft(moveLeft bool) {
m.panelGroup.SetVisible(false)
} else {
m.moveRight()
m.panelGroup.SetVisible(true)
m.panelGroup.SetVisible(m.isOpen)
}
} else {
if moveLeft {
@ -282,7 +341,7 @@ func (m *miniPanel) SetMovedRight(moveRight bool) {
m.panelGroup.SetVisible(false)
} else {
m.moveLeft()
m.panelGroup.SetVisible(true)
m.panelGroup.SetVisible(m.isOpen)
}
} else {
if moveRight {
@ -294,3 +353,51 @@ func (m *miniPanel) SetMovedRight(moveRight bool) {
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()
}
}