2019-11-10 08:51:02 -05:00
|
|
|
package d2ui
|
2019-10-24 09:31:59 -04:00
|
|
|
|
|
|
|
import (
|
2019-10-24 19:13:30 -04:00
|
|
|
"image/color"
|
2020-08-02 21:26:07 -04:00
|
|
|
"regexp"
|
2020-07-07 20:16:22 -04:00
|
|
|
"strings"
|
2020-08-02 21:26:07 -04:00
|
|
|
|
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 17:31:45 -04:00
|
|
|
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset"
|
|
|
|
|
2020-08-06 10:30:23 -04:00
|
|
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
2020-09-12 16:25:09 -04:00
|
|
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2util"
|
2020-08-02 21:26:07 -04:00
|
|
|
)
|
|
|
|
|
2020-12-19 15:28:07 -05:00
|
|
|
// static check if Label implemented Widget
|
|
|
|
var _ Widget = &Label{}
|
|
|
|
|
2019-10-25 19:37:04 -04:00
|
|
|
// Label represents a user interface label
|
|
|
|
type Label struct {
|
2020-11-06 06:38:20 -05:00
|
|
|
*BaseWidget
|
2020-08-03 00:48:17 -04:00
|
|
|
text string
|
2020-11-13 15:03:30 -05:00
|
|
|
Alignment HorizontalAlign
|
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 17:31:45 -04:00
|
|
|
font *d2asset.Font
|
2020-08-03 00:48:17 -04:00
|
|
|
Color map[int]color.Color
|
|
|
|
backgroundColor color.Color
|
2020-11-21 05:33:22 -05:00
|
|
|
|
|
|
|
*d2util.Logger
|
2019-10-24 09:31:59 -04:00
|
|
|
}
|
|
|
|
|
2020-08-06 10:30:23 -04:00
|
|
|
// NewLabel creates a new instance of a UI label
|
|
|
|
func (ui *UIManager) NewLabel(fontPath, palettePath string) *Label {
|
2020-09-23 13:30:54 -04:00
|
|
|
font, err := ui.asset.LoadFont(fontPath+".tbl", fontPath+".dc6", palettePath)
|
|
|
|
if err != nil {
|
2020-11-21 05:33:22 -05:00
|
|
|
ui.Error(err.Error())
|
2020-09-23 13:30:54 -04:00
|
|
|
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 17:31:45 -04:00
|
|
|
|
2020-11-06 06:38:20 -05:00
|
|
|
base := NewBaseWidget(ui)
|
|
|
|
|
2020-08-06 10:30:23 -04:00
|
|
|
result := &Label{
|
2020-11-06 06:38:20 -05:00
|
|
|
BaseWidget: base,
|
2020-11-13 15:03:30 -05:00
|
|
|
Alignment: HorizontalAlignLeft,
|
2020-11-06 06:38:20 -05:00
|
|
|
Color: map[int]color.Color{0: color.White},
|
|
|
|
font: font,
|
2020-11-21 05:33:22 -05:00
|
|
|
Logger: ui.Logger,
|
2019-10-24 09:31:59 -04:00
|
|
|
}
|
2020-06-25 17:28:48 -04:00
|
|
|
|
2020-08-06 10:30:23 -04:00
|
|
|
result.bindManager(ui)
|
|
|
|
|
2019-10-24 09:31:59 -04:00
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
2020-11-11 09:01:36 -05:00
|
|
|
// Render draws the label on the screen, respliting the lines to allow for other alignments.
|
2020-11-11 08:55:59 -05:00
|
|
|
func (v *Label) Render(target d2interface.Surface) {
|
2020-11-06 06:38:20 -05:00
|
|
|
target.PushTranslation(v.GetPosition())
|
2019-10-24 19:13:30 -04:00
|
|
|
|
2020-07-07 20:16:22 -04:00
|
|
|
lines := strings.Split(v.text, "\n")
|
|
|
|
yOffset := 0
|
|
|
|
|
2020-08-02 21:26:07 -04:00
|
|
|
lastColor := v.Color[0]
|
|
|
|
v.font.SetColor(lastColor)
|
|
|
|
|
2020-07-07 20:16:22 -04:00
|
|
|
for _, line := range lines {
|
|
|
|
lw, lh := v.GetTextMetrics(line)
|
2020-08-02 21:26:07 -04:00
|
|
|
characters := []rune(line)
|
|
|
|
|
2020-07-07 20:16:22 -04:00
|
|
|
target.PushTranslation(v.getAlignOffset(lw), yOffset)
|
|
|
|
|
2020-08-02 21:26:07 -04:00
|
|
|
for idx := range characters {
|
|
|
|
character := string(characters[idx])
|
2020-08-03 00:48:17 -04:00
|
|
|
charWidth, charHeight := v.GetTextMetrics(character)
|
2020-08-02 21:26:07 -04:00
|
|
|
|
|
|
|
if v.Color[idx] != nil {
|
|
|
|
lastColor = v.Color[idx]
|
|
|
|
v.font.SetColor(lastColor)
|
|
|
|
}
|
|
|
|
|
2020-08-03 00:48:17 -04:00
|
|
|
if v.backgroundColor != nil {
|
|
|
|
target.DrawRect(charWidth, charHeight, v.backgroundColor)
|
|
|
|
}
|
|
|
|
|
2020-09-23 13:30:54 -04:00
|
|
|
err := v.font.RenderText(character, target)
|
|
|
|
if err != nil {
|
2020-11-21 05:33:22 -05:00
|
|
|
v.Error(err.Error())
|
2020-09-23 13:30:54 -04:00
|
|
|
}
|
2020-08-02 21:26:07 -04:00
|
|
|
|
|
|
|
target.PushTranslation(charWidth, 0)
|
|
|
|
}
|
|
|
|
|
|
|
|
target.PopN(len(characters))
|
2020-07-07 20:16:22 -04:00
|
|
|
|
|
|
|
yOffset += lh
|
2019-12-28 16:46:08 -05:00
|
|
|
|
2020-07-07 20:16:22 -04:00
|
|
|
target.Pop()
|
|
|
|
}
|
2019-12-28 16:46:08 -05:00
|
|
|
|
2020-07-07 20:16:22 -04:00
|
|
|
target.Pop()
|
2019-10-24 09:31:59 -04:00
|
|
|
}
|
|
|
|
|
2020-07-07 20:16:22 -04:00
|
|
|
// GetSize returns the size of the label
|
|
|
|
func (v *Label) GetSize() (width, height int) {
|
|
|
|
return v.font.GetTextMetrics(v.text)
|
2019-11-10 12:28:41 -05:00
|
|
|
}
|
|
|
|
|
2020-07-07 20:16:22 -04: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 09:31:59 -04:00
|
|
|
}
|
|
|
|
|
2019-10-25 19:12:42 -04:00
|
|
|
// SetText sets the label's text
|
2019-10-25 19:37:04 -04:00
|
|
|
func (v *Label) SetText(newText string) {
|
2020-08-02 21:26:07 -04:00
|
|
|
v.text = v.processColorTokens(newText)
|
|
|
|
}
|
|
|
|
|
2020-12-14 12:29:54 -05:00
|
|
|
// GetText returns label text
|
|
|
|
func (v *Label) GetText() string {
|
|
|
|
return v.text
|
|
|
|
}
|
|
|
|
|
2020-08-03 00:48:17 -04:00
|
|
|
// SetBackgroundColor sets the background highlight color
|
2020-08-06 10:30:23 -04:00
|
|
|
func (v *Label) SetBackgroundColor(c color.Color) {
|
2020-08-03 00:48:17 -04:00
|
|
|
v.backgroundColor = c
|
|
|
|
}
|
|
|
|
|
2020-08-02 21:26:07 -04:00
|
|
|
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 02:50:31 -05:00
|
|
|
|
2020-08-02 21:26:07 -04:00
|
|
|
theColor := getColor(token)
|
2020-11-18 02:50:31 -05:00
|
|
|
if theColor == nil {
|
|
|
|
continue
|
|
|
|
}
|
2020-08-02 21:26:07 -04:00
|
|
|
|
|
|
|
if v.Color == nil {
|
|
|
|
v.Color = make(map[int]color.Color)
|
|
|
|
}
|
|
|
|
|
|
|
|
v.Color[tokenPosition] = theColor
|
|
|
|
|
|
|
|
tokenPosition += len(matchStr)
|
|
|
|
}
|
|
|
|
|
|
|
|
return withoutTokens
|
2019-10-24 09:31:59 -04:00
|
|
|
}
|
2019-10-26 10:34:55 -04:00
|
|
|
|
2020-07-07 20:16:22 -04:00
|
|
|
func (v *Label) getAlignOffset(textWidth int) int {
|
|
|
|
switch v.Alignment {
|
2020-11-13 15:03:30 -05:00
|
|
|
case HorizontalAlignLeft:
|
2020-07-07 20:16:22 -04:00
|
|
|
return 0
|
2020-11-13 15:03:30 -05:00
|
|
|
case HorizontalAlignCenter:
|
2020-07-07 20:16:22 -04:00
|
|
|
return -textWidth / 2
|
2020-11-13 15:03:30 -05:00
|
|
|
case HorizontalAlignRight:
|
2020-07-07 20:16:22 -04:00
|
|
|
return -textWidth
|
|
|
|
default:
|
2020-11-21 05:33:22 -05:00
|
|
|
v.Fatal("Invalid Alignment")
|
2020-07-07 20:16:22 -04:00
|
|
|
return 0
|
|
|
|
}
|
2019-10-26 10:34:55 -04:00
|
|
|
}
|
2020-08-02 21:26:07 -04:00
|
|
|
|
2020-11-09 09:46:08 -05:00
|
|
|
// Advance is a no-op
|
|
|
|
func (v *Label) Advance(elapsed float64) error {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-08-02 21:26:07 -04:00
|
|
|
func getColor(token ColorToken) color.Color {
|
2020-10-25 18:36:12 -04:00
|
|
|
// https://github.com/OpenDiablo2/OpenDiablo2/issues/823
|
2020-08-02 21:26:07 -04:00
|
|
|
colors := map[ColorToken]color.Color{
|
2020-09-08 15:58:35 -04:00
|
|
|
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),
|
2020-08-02 21:26:07 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
chosen := colors[token]
|
|
|
|
|
|
|
|
if chosen == nil {
|
2020-11-18 02:50:31 -05:00
|
|
|
return nil
|
2020-08-02 21:26:07 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
return chosen
|
|
|
|
}
|