OpenDiablo2/d2core/d2ui/label.go

206 lines
4.7 KiB
Go
Raw Normal View History

2019-11-10 13:51:02 +00:00
package d2ui
2019-10-24 13:31:59 +00:00
import (
"image/color"
"regexp"
"strings"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2font"
Decouple asset manager from renderer (#730) * improve AssetManager implementation Notable changes are: * removed the individual managers inside of d2asset, only one asset manager * AssetManager now has caches for the types of files it loads * created a type for TextDictionary (the txt file structs) * fixed a file path bug in d2loader Source * fixed a asset stream bug in d2loader Asset * d2loader.Loader now needs a d2config.Config on creation (for resolving locale files) * updated the mpq file in d2asset test data, added test case for "sub-directory" * added a Data method to d2asset.Asset. The data is cached on first full read. * renamed ArchiveDataStream to DataStream in d2interface * moved palette utility func out of d2asset and into d2util * bugfix for MacOS mpq loader issue * lint fixes, added data caching to filesystem asset * adding comment for mpq asset close * Decouple d2asset from d2render Notable changes in d2common: * d2dcc.Load now fully decodes the dcc and stores the directions/frames in the dcc struct * un-exported dcc.decodeDirection, it is only used in d2dcc * removed font interface from d2interface, we only have one font implementation * added `Renderer` method to d2interface.Surface, animations use this to bind to a renderer and create surfaces as they need * added `BindRenderer` method to animation interface Notable changes in d2common/d2asset: * **d2asset.NewAssetManager only needs to be passed a d2config.Config**, it is decoupled from d2render * exported Animation * Animation implementation binds to the renderer to create surfaces only on the first time it is rendered * font, dcc, dc6 initialization logic moved out of asset_manager.go * for dc6 and dcc animations, the process of decoding and creating render surfaces has been broken into different methods * the d2asset.Font struct now stores font table data for initialization purposes Notable changes in d2core/d2render: * Surfaces store a renderer reference, this allows animations to bind to the renderer and create a surface just-in-time **These last changes should have been a separate PR, sorry.** Notable changes in d2core/d2ui: * ui.NewSprite now handles creating an animation internally, only needs image and palette path as arguments Notable Changes in d2game: Because of the change in d2ui, all instances of this code pattern... ```golang animation, err := screen.asset.LoadAnimation(imgPath, palettePath) sprite, err := screen.ui.NewSprite(animation) ``` ... becomes this ... ```golang sprite, err := screen.ui.NewSprite(imgPath, palettePath) ```
2020-09-14 21:31:45 +00:00
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
2020-09-12 20:25:09 +00:00
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2util"
)
// static check if Label implemented Widget
var _ Widget = &Label{}
2019-10-25 23:37:04 +00:00
// Label represents a user interface label
type Label struct {
*BaseWidget
text string
Alignment HorizontalAlign
font *d2font.Font
Color map[int]color.Color
backgroundColor color.Color
*d2util.Logger
2019-10-24 13:31:59 +00:00
}
// NewLabel creates a new instance of a UI label
func (ui *UIManager) NewLabel(fontPath, palettePath string) *Label {
font, err := ui.asset.LoadFont(fontPath+".tbl", fontPath+".dc6", palettePath)
if err != nil {
ui.Error(err.Error())
return nil
}
Decouple asset manager from renderer (#730) * improve AssetManager implementation Notable changes are: * removed the individual managers inside of d2asset, only one asset manager * AssetManager now has caches for the types of files it loads * created a type for TextDictionary (the txt file structs) * fixed a file path bug in d2loader Source * fixed a asset stream bug in d2loader Asset * d2loader.Loader now needs a d2config.Config on creation (for resolving locale files) * updated the mpq file in d2asset test data, added test case for "sub-directory" * added a Data method to d2asset.Asset. The data is cached on first full read. * renamed ArchiveDataStream to DataStream in d2interface * moved palette utility func out of d2asset and into d2util * bugfix for MacOS mpq loader issue * lint fixes, added data caching to filesystem asset * adding comment for mpq asset close * Decouple d2asset from d2render Notable changes in d2common: * d2dcc.Load now fully decodes the dcc and stores the directions/frames in the dcc struct * un-exported dcc.decodeDirection, it is only used in d2dcc * removed font interface from d2interface, we only have one font implementation * added `Renderer` method to d2interface.Surface, animations use this to bind to a renderer and create surfaces as they need * added `BindRenderer` method to animation interface Notable changes in d2common/d2asset: * **d2asset.NewAssetManager only needs to be passed a d2config.Config**, it is decoupled from d2render * exported Animation * Animation implementation binds to the renderer to create surfaces only on the first time it is rendered * font, dcc, dc6 initialization logic moved out of asset_manager.go * for dc6 and dcc animations, the process of decoding and creating render surfaces has been broken into different methods * the d2asset.Font struct now stores font table data for initialization purposes Notable changes in d2core/d2render: * Surfaces store a renderer reference, this allows animations to bind to the renderer and create a surface just-in-time **These last changes should have been a separate PR, sorry.** Notable changes in d2core/d2ui: * ui.NewSprite now handles creating an animation internally, only needs image and palette path as arguments Notable Changes in d2game: Because of the change in d2ui, all instances of this code pattern... ```golang animation, err := screen.asset.LoadAnimation(imgPath, palettePath) sprite, err := screen.ui.NewSprite(animation) ``` ... becomes this ... ```golang sprite, err := screen.ui.NewSprite(imgPath, palettePath) ```
2020-09-14 21:31:45 +00:00
base := NewBaseWidget(ui)
result := &Label{
BaseWidget: base,
Alignment: HorizontalAlignLeft,
Color: map[int]color.Color{0: color.White},
font: font,
Logger: ui.Logger,
2019-10-24 13:31:59 +00:00
}
2020-06-25 21:28:48 +00:00
result.bindManager(ui)
result.SetVisible(false)
ui.addWidget(result)
2019-10-24 13:31:59 +00:00
return result
}
// Render draws the label on the screen, respliting the lines to allow for other alignments.
func (v *Label) Render(target d2interface.Surface) {
target.PushTranslation(v.GetPosition())
lines := strings.Split(v.text, "\n")
yOffset := 0
lastColor := v.Color[0]
v.font.SetColor(lastColor)
for _, line := range lines {
lw, lh := v.GetTextMetrics(line)
characters := []rune(line)
target.PushTranslation(v.getAlignOffset(lw), yOffset)
for idx := range characters {
character := string(characters[idx])
charWidth, charHeight := v.GetTextMetrics(character)
if v.Color[idx] != nil {
lastColor = v.Color[idx]
v.font.SetColor(lastColor)
}
if v.backgroundColor != nil {
target.DrawRect(charWidth, charHeight, v.backgroundColor)
}
err := v.font.RenderText(character, target)
if err != nil {
v.Error(err.Error())
}
target.PushTranslation(charWidth, 0)
}
target.PopN(len(characters))
yOffset += lh
target.Pop()
}
target.Pop()
2019-10-24 13:31:59 +00:00
}
// GetTextMetrics returns the width and height of the enclosing rectangle in Pixels.
func (v *Label) GetTextMetrics(text string) (width, height int) {
return v.font.GetTextMetrics(text)
2019-10-24 13:31:59 +00:00
}
2019-10-25 23:12:42 +00:00
// SetText sets the label's text
2019-10-25 23:37:04 +00:00
func (v *Label) SetText(newText string) {
v.text = v.processColorTokens(newText)
v.BaseWidget.width, v.BaseWidget.height = v.font.GetTextMetrics(v.text)
}
2020-12-14 17:29:54 +00:00
// GetText returns label text
func (v *Label) GetText() string {
return v.text
}
// SetBackgroundColor sets the background highlight color
func (v *Label) SetBackgroundColor(c color.Color) {
v.backgroundColor = c
}
func (v *Label) processColorTokens(str string) string {
tokenMatch := regexp.MustCompile(colorTokenMatch)
tokenStrMatch := regexp.MustCompile(colorStrMatch)
empty := []byte("")
tokenPosition := 0
withoutTokens := string(tokenMatch.ReplaceAll([]byte(str), empty)) // remove tokens from string
matches := tokenStrMatch.FindAll([]byte(str), -1)
if len(matches) == 0 {
v.Color[0] = getColor(ColorTokenWhite)
}
// we find the index of each token and update the color map.
// the key in the map is the starting index of each color token, the value is the color
for idx := range matches {
match := matches[idx]
matchToken := tokenMatch.Find(match)
matchStr := string(tokenMatch.ReplaceAll(match, empty))
token := ColorToken(matchToken)
2020-11-18 07:50:31 +00:00
theColor := getColor(token)
2020-11-18 07:50:31 +00:00
if theColor == nil {
continue
}
if v.Color == nil {
v.Color = make(map[int]color.Color)
}
v.Color[tokenPosition] = theColor
tokenPosition += len(matchStr)
}
return withoutTokens
2019-10-24 13:31:59 +00:00
}
2019-10-26 14:34:55 +00:00
func (v *Label) getAlignOffset(textWidth int) int {
switch v.Alignment {
case HorizontalAlignLeft:
return 0
case HorizontalAlignCenter:
2021-02-02 11:00:31 +00:00
// nolint:gomnd // center of label = 1/2 of it
return -textWidth / 2
case HorizontalAlignRight:
return -textWidth
default:
v.Fatal("Invalid Alignment")
return 0
}
2019-10-26 14:34:55 +00:00
}
// Advance is a no-op
func (v *Label) Advance(elapsed float64) error {
return nil
}
func getColor(token ColorToken) color.Color {
// https://github.com/OpenDiablo2/OpenDiablo2/issues/823
colors := map[ColorToken]color.Color{
ColorTokenGrey: d2util.Color(colorGrey100Alpha),
ColorTokenWhite: d2util.Color(colorWhite100Alpha),
ColorTokenBlue: d2util.Color(colorBlue100Alpha),
ColorTokenYellow: d2util.Color(colorYellow100Alpha),
ColorTokenGreen: d2util.Color(colorGreen100Alpha),
ColorTokenGold: d2util.Color(colorGold100Alpha),
ColorTokenOrange: d2util.Color(colorOrange100Alpha),
ColorTokenRed: d2util.Color(colorRed100Alpha),
ColorTokenBlack: d2util.Color(colorBlack100Alpha),
}
chosen := colors[token]
if chosen == nil {
2020-11-18 07:50:31 +00:00
return nil
}
return chosen
}