1
1
mirror of https://github.com/OpenDiablo2/OpenDiablo2 synced 2024-11-17 18:06:03 -05:00

Tooltip refactor (#872)

* d2ui: Add tooltip class

we reimplemented tooltips in several places
(inventory/skill_select_panel). Let's make it its own ui element.

* d2player: Refactor skill_select_panel to use the tooltip class

* d2player: Refactor inventory to use the tooltip class

* Make linter happy

* Make golangci-lint 1.27.0 happy as well

* tooltip: Remove const and rather disable linter
This commit is contained in:
juander-ux 2020-10-28 18:54:55 +01:00 committed by GitHub
parent 4a62101b96
commit 79c147866e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 214 additions and 105 deletions

188
d2core/d2ui/tooltip.go Normal file
View File

@ -0,0 +1,188 @@
package d2ui
import (
"strings"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2util"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2gui"
)
const (
blackAlpha70 = 0x000000C8
screenWidth = 800
screenHeight = 600
)
// Tooltip contains a label containing text with a transparent, black background
type Tooltip struct {
manager *UIManager
lines []string
label *Label
backgroundColor int
x, y int
originX tooltipXOrigin
originY tooltipYOrigin
}
type tooltipXOrigin = int
type tooltipYOrigin = int
const (
// TooltipYTop sets the Y origin of the tooltip to the top
TooltipYTop tooltipYOrigin = iota
// TooltipYCenter sets the Y origin of the tooltip to the center
TooltipYCenter
// TooltipYBottom sets the Y origin of the tooltip to the bottom
TooltipYBottom
)
const (
// TooltipXLeft sets the X origin of the tooltip to the left
TooltipXLeft tooltipXOrigin = iota
// TooltipXCenter sets the X origin of the tooltip to the center
TooltipXCenter
// TooltipXRight sets the X origin of the tooltip to the right
TooltipXRight
)
// NewTooltip creates a tooltip instance. Note here, that we need to define the
// orign point of the tooltip rect using tooltipXOrigin and tooltinYOrigin
func (ui *UIManager) NewTooltip(font,
palette string,
originX tooltipXOrigin,
originY tooltipYOrigin) *Tooltip {
label := ui.NewLabel(font, palette)
label.Alignment = d2gui.HorizontalAlignCenter
res := &Tooltip{
backgroundColor: blackAlpha70,
label: label,
x: 0,
y: 0,
originX: originX,
originY: originY,
}
res.manager = ui
return res
}
// SetPosition sets the position of the origin point of the tooltip
func (t *Tooltip) SetPosition(x, y int) {
t.x = x
t.y = y
}
// SetTextLines sets the tooltip text in the form of an array of strings
func (t *Tooltip) SetTextLines(lines []string) {
t.lines = lines
}
// SetText sets the tooltip text and splits \n into lines
func (t *Tooltip) SetText(text string) {
t.lines = strings.Split(text, "\n")
}
func (t *Tooltip) adjustCoordinatesToScreen(maxW, maxH, halfW, halfH int) (rx, ry int) {
var xOffset, yOffset int
switch t.originX {
case TooltipXLeft:
xOffset = maxW
case TooltipXCenter:
xOffset = halfW
case TooltipXRight:
xOffset = 0
}
renderX := t.x
if (t.x + xOffset) > screenWidth {
renderX = screenWidth - xOffset
}
switch t.originY {
case TooltipYTop:
yOffset = 0
case TooltipYCenter:
yOffset = halfH
case TooltipYBottom:
yOffset = maxH
}
renderY := t.y
if (t.y + yOffset) > screenHeight {
renderY = screenHeight - yOffset
}
return renderX, renderY
}
func (t *Tooltip) getTextSize() (sx, sy int) {
maxW, maxH := 0, 0
for i := range t.lines {
w, h := t.label.GetTextMetrics(t.lines[i])
if maxW < w {
maxW = w
}
maxH += h
}
return maxW, maxH
}
// Render draws the tooltip
func (t *Tooltip) Render(target d2interface.Surface) {
maxW, maxH := t.getTextSize()
// nolint:gomnd // no magic numbers, their meaning is obvious
halfW, halfH := maxW/2, maxH/2
renderX, renderY := t.adjustCoordinatesToScreen(maxW, maxH, halfW, halfH)
target.PushTranslation(renderX, renderY)
defer target.Pop()
// adjust starting point of the background rect based on the origin point
// as we always draw a rect from top left
switch t.originX {
case TooltipXLeft:
target.PushTranslation(0, 0)
case TooltipXCenter:
target.PushTranslation(-halfW, 0)
case TooltipXRight:
target.PushTranslation(-maxW, 0)
}
defer target.Pop()
switch t.originY {
case TooltipYTop:
target.PushTranslation(0, 0)
case TooltipYCenter:
target.PushTranslation(0, -halfH)
case TooltipXRight:
target.PushTranslation(0, -maxH)
}
defer target.Pop()
// tooltip background
target.DrawRect(maxW, maxH, d2util.Color(blackAlpha70))
// text
target.PushTranslation(halfW, 0) // text is centered, our box is not
defer target.Pop()
for i := range t.lines {
t.label.SetText(t.lines[i])
_, h := t.label.GetTextMetrics(t.lines[i])
t.label.Render(target)
target.PushTranslation(0, h)
}
target.PopN(len(t.lines))
}

View File

@ -3,14 +3,12 @@ package d2player
import ( import (
"fmt" "fmt"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2util"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2records" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2records"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
"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/d2core/d2asset" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2gui"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2item/diablo2item" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2item/diablo2item"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2ui" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2ui"
) )
@ -22,10 +20,6 @@ const (
frameInventoryBottomRight = 7 frameInventoryBottomRight = 7
) )
const (
blackAlpha70 = 0x000000C8
)
const ( const (
invCloseButtonX, invCloseButtonY = 419, 449 invCloseButtonX, invCloseButtonY = 419, 449
) )
@ -38,7 +32,7 @@ type Inventory struct {
frame *d2ui.UIFrame frame *d2ui.UIFrame
panel *d2ui.Sprite panel *d2ui.Sprite
grid *ItemGrid grid *ItemGrid
hoverLabel *d2ui.Label itemTooltip *d2ui.Tooltip
closeButton *d2ui.Button closeButton *d2ui.Button
hoverX int hoverX int
hoverY int hoverY int
@ -54,19 +48,18 @@ type Inventory struct {
// NewInventory creates an inventory instance and returns a pointer to it // NewInventory creates an inventory instance and returns a pointer to it
func NewInventory(asset *d2asset.AssetManager, ui *d2ui.UIManager, func NewInventory(asset *d2asset.AssetManager, ui *d2ui.UIManager,
record *d2records.InventoryRecord) *Inventory { record *d2records.InventoryRecord) *Inventory {
hoverLabel := ui.NewLabel(d2resource.FontFormal11, d2resource.PaletteStatic) itemTooltip := ui.NewTooltip(d2resource.FontFormal11, d2resource.PaletteStatic, d2ui.TooltipXCenter, d2ui.TooltipYBottom)
hoverLabel.Alignment = d2gui.HorizontalAlignCenter
// https://github.com/OpenDiablo2/OpenDiablo2/issues/797 // https://github.com/OpenDiablo2/OpenDiablo2/issues/797
itemFactory, _ := diablo2item.NewItemFactory(asset) itemFactory, _ := diablo2item.NewItemFactory(asset)
return &Inventory{ return &Inventory{
asset: asset, asset: asset,
uiManager: ui, uiManager: ui,
item: itemFactory, item: itemFactory,
grid: NewItemGrid(asset, ui, record), grid: NewItemGrid(asset, ui, record),
originX: record.Panel.Left, originX: record.Panel.Left,
hoverLabel: hoverLabel, itemTooltip: itemTooltip,
// originY: record.Panel.Top, // originY: record.Panel.Top,
originY: 0, // expansion data has these all offset by +60 ... originY: 0, // expansion data has these all offset by +60 ...
} }
@ -250,48 +243,8 @@ func (g *Inventory) renderItemHover(target d2interface.Surface) {
func (g *Inventory) renderItemDescription(target d2interface.Surface, i InventoryItem) { func (g *Inventory) renderItemDescription(target d2interface.Surface, i InventoryItem) {
lines := i.GetItemDescription() lines := i.GetItemDescription()
g.itemTooltip.SetTextLines(lines)
maxW, maxH := 0, 0 _, y := g.grid.SlotToScreen(i.InventoryGridSlot())
_, iy := g.grid.SlotToScreen(i.InventoryGridSlot()) g.itemTooltip.SetPosition(g.hoverX, y)
g.itemTooltip.Render(target)
for idx := range lines {
w, h := g.hoverLabel.GetTextMetrics(lines[idx])
if maxW < w {
maxW = w
}
maxH += h
}
halfW, halfH := maxW>>1, maxH>>1
centerX, centerY := g.hoverX, iy-halfH
if (centerX + halfW) > screenWidth {
centerX = screenWidth - halfW
}
if (centerY + halfH) > screenHeight {
centerY = screenHeight - halfH
}
target.PushTranslation(centerX, centerY)
defer target.Pop()
target.PushTranslation(-halfW, -halfH)
defer target.Pop()
target.DrawRect(maxW, maxH, d2util.Color(blackAlpha70))
target.PushTranslation(halfW, 0)
defer target.Pop()
for idx := range lines {
g.hoverLabel.SetText(lines[idx])
_, h := g.hoverLabel.GetTextMetrics(lines[idx])
g.hoverLabel.Render(target)
target.PushTranslation(0, h)
}
target.PopN(len(lines))
} }

View File

@ -5,15 +5,12 @@ import (
"log" "log"
"sort" "sort"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2util"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2tbl" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2tbl"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2geom" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2geom"
"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/d2core/d2asset" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2gui"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2hero" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2hero"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapentity" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapentity"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2ui" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2ui"
@ -28,9 +25,6 @@ const (
leftPanelStartX = 90 leftPanelStartX = 90
skillPanelOffsetY = 465 skillPanelOffsetY = 465
skillListsLength = 5 // 0 to 4. 0 - General Skills, 1 to 3 - Class-specific skills(based on the 3 different skill trees), 4 - Other skills skillListsLength = 5 // 0 to 4. 0 - General Skills, 1 to 3 - Class-specific skills(based on the 3 different skill trees), 4 - Other skills
tooltipPadLeft = 3
tooltipPadRight = 3
tooltipPadBottom = 5
) )
// SkillPanel represents a skill select menu popup that is displayed when the player left clicks on his active left/right skill. // SkillPanel represents a skill select menu popup that is displayed when the player left clicks on his active left/right skill.
@ -42,8 +36,7 @@ type SkillPanel struct {
renderer d2interface.Renderer renderer d2interface.Renderer
ui *d2ui.UIManager ui *d2ui.UIManager
hoveredSkill *d2hero.HeroSkill hoveredSkill *d2hero.HeroSkill
hoverTooltipRect *d2geom.Rectangle hoverTooltip *d2ui.Tooltip
hoverTooltipText *d2ui.Label
isOpen bool isOpen bool
regenerateImageCache bool regenerateImageCache bool
isLeftPanel bool isLeftPanel bool
@ -58,19 +51,18 @@ func NewHeroSkillsPanel(asset *d2asset.AssetManager, ui *d2ui.UIManager, hero *d
activeSkill = hero.RightSkill activeSkill = hero.RightSkill
} }
hoverTooltipText := ui.NewLabel(d2resource.Font16, d2resource.PaletteStatic) hoverTooltip := ui.NewTooltip(d2resource.Font16, d2resource.PaletteStatic, d2ui.TooltipXLeft, d2ui.TooltipYTop)
hoverTooltipText.Alignment = d2gui.HorizontalAlignCenter
return &SkillPanel{ return &SkillPanel{
asset: asset, asset: asset,
activeSkill: activeSkill, activeSkill: activeSkill,
ui: ui, ui: ui,
isOpen: false, isOpen: false,
ListRows: make([]*SkillListRow, skillListsLength), ListRows: make([]*SkillListRow, skillListsLength),
renderer: ui.Renderer(), renderer: ui.Renderer(),
isLeftPanel: isLeftPanel, isLeftPanel: isLeftPanel,
hero: hero, hero: hero,
hoverTooltipText: hoverTooltipText, hoverTooltip: hoverTooltip,
} }
} }
@ -143,18 +135,7 @@ func (s *SkillPanel) Render(target d2interface.Surface) error {
} }
if s.hoveredSkill != nil { if s.hoveredSkill != nil {
target.PushTranslation(s.hoverTooltipRect.Left, s.hoverTooltipRect.Top) s.hoverTooltip.Render(target)
black70 := d2util.Color(blackAlpha70)
target.DrawRect(s.hoverTooltipRect.Width, s.hoverTooltipRect.Height, black70)
// the text should be centered horizontally in the tooltip rect
centerX := s.hoverTooltipRect.Width >> 1
target.PushTranslation(centerX, 0)
s.hoverTooltipText.Render(target)
target.Pop()
target.Pop()
} }
return nil return nil
@ -353,26 +334,13 @@ func (s *SkillPanel) HandleMouseMove(x, y int) bool {
if previousHovered != s.hoveredSkill && s.hoveredSkill != nil { if previousHovered != s.hoveredSkill && s.hoveredSkill != nil {
skillDescription := d2tbl.TranslateString(s.hoveredSkill.ShortKey) skillDescription := d2tbl.TranslateString(s.hoveredSkill.ShortKey)
s.hoverTooltipText.SetText(fmt.Sprintf("%s\n%s", s.hoveredSkill.Skill, skillDescription)) s.hoverTooltip.SetText(fmt.Sprintf("%s\n%s", s.hoveredSkill.Skill, skillDescription))
listRow := s.GetListRowByPos(x, y) listRow := s.GetListRowByPos(x, y)
textWidth, textHeight := s.hoverTooltipText.GetSize()
tooltipX := (s.getSkillIdxAtPos(x, y) * skillIconWidth) + s.getRowStartX(listRow) tooltipX := (s.getSkillIdxAtPos(x, y) * skillIconWidth) + s.getRowStartX(listRow)
tooltipWidth := textWidth + tooltipPadLeft + tooltipPadRight
if tooltipX+tooltipWidth >= screenWidth {
tooltipX = screenWidth - tooltipWidth
}
tooltipY := listRow.Rectangle.Top + listRow.Rectangle.Height tooltipY := listRow.Rectangle.Top + listRow.Rectangle.Height
tooltipHeight := textHeight + tooltipPadBottom s.hoverTooltip.SetPosition(tooltipX, tooltipY)
s.hoverTooltipRect = &d2geom.Rectangle{
Left: tooltipX,
Top: tooltipY,
Width: tooltipWidth,
Height: tooltipHeight}
} }
return true return true