2020-02-17 22:11:52 -05:00
|
|
|
package d2gui
|
|
|
|
|
|
|
|
import (
|
|
|
|
"image/color"
|
|
|
|
|
2020-02-24 22:35:21 -05:00
|
|
|
"github.com/OpenDiablo2/OpenDiablo2/d2common"
|
2020-02-17 22:11:52 -05:00
|
|
|
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2input"
|
|
|
|
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2render"
|
|
|
|
)
|
|
|
|
|
|
|
|
type layoutEntry struct {
|
|
|
|
widget widget
|
|
|
|
|
2020-02-24 22:35:21 -05:00
|
|
|
x int
|
|
|
|
y int
|
|
|
|
width int
|
|
|
|
height int
|
|
|
|
|
2020-02-17 22:11:52 -05:00
|
|
|
mouseOver bool
|
|
|
|
mouseDown [3]bool
|
|
|
|
}
|
|
|
|
|
2020-02-24 22:35:21 -05:00
|
|
|
type VerticalAlign int
|
|
|
|
|
|
|
|
const (
|
|
|
|
VerticalAlignTop VerticalAlign = iota
|
|
|
|
VerticalAlignMiddle
|
|
|
|
VerticalAlignBottom
|
|
|
|
)
|
|
|
|
|
|
|
|
type HorizontalAlign int
|
|
|
|
|
|
|
|
const (
|
|
|
|
HorizontalAlignLeft HorizontalAlign = iota
|
|
|
|
HorizontalAlignCenter
|
|
|
|
HorizontalAlignRight
|
|
|
|
)
|
|
|
|
|
|
|
|
type PositionType int
|
|
|
|
|
|
|
|
const (
|
|
|
|
PositionTypeAbsolute PositionType = iota
|
|
|
|
PositionTypeVertical
|
|
|
|
PositionTypeHorizontal
|
|
|
|
)
|
|
|
|
|
2020-02-17 22:11:52 -05:00
|
|
|
type Layout struct {
|
|
|
|
widgetBase
|
2020-02-24 22:35:21 -05:00
|
|
|
|
|
|
|
width int
|
|
|
|
height int
|
|
|
|
verticalAlign VerticalAlign
|
|
|
|
horizontalAlign HorizontalAlign
|
|
|
|
positionType PositionType
|
|
|
|
entries []*layoutEntry
|
|
|
|
}
|
|
|
|
|
|
|
|
func createLayout(positionType PositionType) *Layout {
|
|
|
|
layout := &Layout{positionType: positionType}
|
|
|
|
layout.SetVisible(true)
|
|
|
|
|
|
|
|
return layout
|
|
|
|
}
|
|
|
|
|
|
|
|
func (l *Layout) SetSize(width, height int) {
|
|
|
|
l.width = width
|
|
|
|
l.height = height
|
|
|
|
}
|
|
|
|
|
|
|
|
func (l *Layout) SetVerticalAlign(verticalAlign VerticalAlign) {
|
|
|
|
l.verticalAlign = verticalAlign
|
|
|
|
}
|
|
|
|
|
|
|
|
func (l *Layout) SetHorizontalAlign(horizontalAlign HorizontalAlign) {
|
|
|
|
l.horizontalAlign = horizontalAlign
|
2020-02-17 22:11:52 -05:00
|
|
|
}
|
|
|
|
|
2020-02-24 22:35:21 -05:00
|
|
|
func (l *Layout) AddLayout(positionType PositionType) *Layout {
|
|
|
|
layout := createLayout(positionType)
|
|
|
|
l.entries = append(l.entries, &layoutEntry{widget: layout})
|
2020-02-17 22:11:52 -05:00
|
|
|
return layout
|
|
|
|
}
|
|
|
|
|
2020-02-24 22:35:21 -05:00
|
|
|
func (l *Layout) AddSpacerStatic(width, height int) *SpacerStatic {
|
|
|
|
spacer := createSpacerStatic(width, height)
|
|
|
|
l.entries = append(l.entries, &layoutEntry{widget: spacer})
|
|
|
|
return spacer
|
|
|
|
}
|
|
|
|
|
|
|
|
func (l *Layout) AddSpacerDynamic() *SpacerDynamic {
|
|
|
|
spacer := createSpacerDynamic()
|
|
|
|
l.entries = append(l.entries, &layoutEntry{widget: spacer})
|
|
|
|
return spacer
|
|
|
|
}
|
|
|
|
|
|
|
|
func (l *Layout) AddSprite(imagePath, palettePath string) (*Sprite, error) {
|
|
|
|
sprite, err := createSprite(imagePath, palettePath)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
l.entries = append(l.entries, &layoutEntry{widget: sprite})
|
|
|
|
return sprite, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (l *Layout) AddLabel(text string, fontStyle FontStyle) (*Label, error) {
|
|
|
|
label, err := createLabel(text, fontStyle)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
l.entries = append(l.entries, &layoutEntry{widget: label})
|
|
|
|
return label, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (l *Layout) AddButton(text string, buttonStyle ButtonStyle) (*Button, error) {
|
|
|
|
button, err := createButton(text, buttonStyle)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
l.entries = append(l.entries, &layoutEntry{widget: button})
|
|
|
|
return button, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (l *Layout) Clear() {
|
|
|
|
l.entries = nil
|
|
|
|
}
|
|
|
|
|
2020-02-17 22:11:52 -05:00
|
|
|
func (l *Layout) render(target d2render.Surface) error {
|
2020-02-24 22:35:21 -05:00
|
|
|
l.adjustEntryPlacement()
|
|
|
|
|
2020-02-17 22:11:52 -05:00
|
|
|
for _, entry := range l.entries {
|
2020-02-24 22:35:21 -05:00
|
|
|
if !entry.widget.isVisible() {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := l.renderEntry(entry, target); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := l.renderEntryDebug(entry, target); err != nil {
|
|
|
|
return err
|
2020-02-17 22:11:52 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (l *Layout) advance(elapsed float64) error {
|
|
|
|
for _, entry := range l.entries {
|
2020-02-24 22:35:21 -05:00
|
|
|
if err := entry.widget.advance(elapsed); err != nil {
|
|
|
|
return err
|
2020-02-17 22:11:52 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-02-24 22:35:21 -05:00
|
|
|
func (l *Layout) renderEntry(entry *layoutEntry, target d2render.Surface) error {
|
|
|
|
target.PushTranslation(entry.x, entry.y)
|
2020-02-17 22:11:52 -05:00
|
|
|
defer target.Pop()
|
|
|
|
|
2020-02-24 22:35:21 -05:00
|
|
|
return entry.widget.render(target)
|
2020-02-17 22:11:52 -05:00
|
|
|
}
|
|
|
|
|
2020-02-24 22:35:21 -05:00
|
|
|
func (l *Layout) renderEntryDebug(entry *layoutEntry, target d2render.Surface) error {
|
|
|
|
target.PushTranslation(entry.x, entry.y)
|
2020-02-17 22:11:52 -05:00
|
|
|
defer target.Pop()
|
|
|
|
|
2020-02-24 22:35:21 -05:00
|
|
|
drawColor := color.RGBA{R: 0xff, G: 0xff, B: 0xff, A: 0xff}
|
|
|
|
switch entry.widget.(type) {
|
|
|
|
case *Layout:
|
|
|
|
drawColor = color.RGBA{R: 0xff, G: 0x00, B: 0xff, A: 0xff}
|
|
|
|
case *SpacerStatic, *SpacerDynamic:
|
|
|
|
drawColor = color.RGBA{R: 0x80, G: 0x80, B: 0x80, A: 0xff}
|
|
|
|
case *Label:
|
|
|
|
drawColor = color.RGBA{R: 0x00, G: 0x00, B: 0xff, A: 0xff}
|
|
|
|
case *Button:
|
|
|
|
drawColor = color.RGBA{R: 0xff, G: 0xff, B: 0x00, A: 0xff}
|
|
|
|
}
|
2020-02-17 22:11:52 -05:00
|
|
|
|
2020-02-24 22:35:21 -05:00
|
|
|
target.DrawLine(entry.width, 0, drawColor)
|
|
|
|
target.DrawLine(0, entry.height, drawColor)
|
2020-02-17 22:11:52 -05:00
|
|
|
|
2020-02-24 22:35:21 -05:00
|
|
|
target.PushTranslation(entry.width, 0)
|
|
|
|
target.DrawLine(0, entry.height, drawColor)
|
2020-02-17 22:11:52 -05:00
|
|
|
target.Pop()
|
|
|
|
|
2020-02-24 22:35:21 -05:00
|
|
|
target.PushTranslation(0, entry.height)
|
|
|
|
target.DrawLine(entry.width, 0, drawColor)
|
2020-02-17 22:11:52 -05:00
|
|
|
target.Pop()
|
2020-02-24 22:35:21 -05:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (l *Layout) getContentSize() (int, int) {
|
|
|
|
var width, height int
|
|
|
|
|
|
|
|
for _, entry := range l.entries {
|
|
|
|
x, y := entry.widget.getPosition()
|
|
|
|
w, h := entry.widget.getSize()
|
|
|
|
|
|
|
|
switch l.positionType {
|
|
|
|
case PositionTypeVertical:
|
|
|
|
width = d2common.MaxInt(width, w)
|
|
|
|
height += h
|
|
|
|
break
|
|
|
|
case PositionTypeHorizontal:
|
|
|
|
width += w
|
|
|
|
height = d2common.MaxInt(height, h)
|
|
|
|
break
|
|
|
|
case PositionTypeAbsolute:
|
|
|
|
width = d2common.MaxInt(width, x+w)
|
|
|
|
height = d2common.MaxInt(height, y+h)
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return width, height
|
2020-02-17 22:11:52 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
func (l *Layout) getSize() (int, int) {
|
2020-02-24 22:35:21 -05:00
|
|
|
width, height := l.getContentSize()
|
|
|
|
return d2common.MaxInt(width, l.width), d2common.MaxInt(height, l.height)
|
2020-02-17 22:11:52 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
func (l *Layout) onMouseButtonDown(event d2input.MouseEvent) bool {
|
|
|
|
for _, entry := range l.entries {
|
|
|
|
eventLocal := event
|
|
|
|
|
2020-02-24 22:35:21 -05:00
|
|
|
if l.adjustEntryEvent(entry, &eventLocal.X, &eventLocal.Y) {
|
|
|
|
entry.widget.onMouseButtonDown(eventLocal)
|
2020-02-17 22:11:52 -05:00
|
|
|
entry.mouseDown[event.Button] = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
func (l *Layout) onMouseButtonUp(event d2input.MouseEvent) bool {
|
|
|
|
for _, entry := range l.entries {
|
|
|
|
eventLocal := event
|
|
|
|
|
2020-02-24 22:35:21 -05:00
|
|
|
if l.adjustEntryEvent(entry, &eventLocal.X, &eventLocal.Y) {
|
2020-02-17 22:11:52 -05:00
|
|
|
if entry.mouseDown[event.Button] {
|
2020-02-24 22:35:21 -05:00
|
|
|
entry.widget.onMouseButtonClick(eventLocal)
|
|
|
|
entry.widget.onMouseButtonUp(eventLocal)
|
2020-02-17 22:11:52 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
entry.mouseDown[event.Button] = false
|
|
|
|
}
|
|
|
|
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
func (l *Layout) onMouseMove(event d2input.MouseMoveEvent) bool {
|
|
|
|
for _, entry := range l.entries {
|
|
|
|
eventLocal := event
|
|
|
|
|
2020-02-24 22:35:21 -05:00
|
|
|
if l.adjustEntryEvent(entry, &eventLocal.X, &eventLocal.Y) {
|
|
|
|
entry.widget.onMouseMove(eventLocal)
|
2020-02-17 22:11:52 -05:00
|
|
|
if entry.mouseOver {
|
|
|
|
entry.widget.onMouseOver(eventLocal)
|
|
|
|
} else {
|
|
|
|
entry.widget.onMouseEnter(eventLocal)
|
|
|
|
}
|
|
|
|
entry.mouseOver = true
|
|
|
|
} else if entry.mouseOver {
|
|
|
|
entry.widget.onMouseLeave(eventLocal)
|
|
|
|
entry.mouseOver = false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2020-02-24 22:35:21 -05:00
|
|
|
func (l *Layout) adjustEntryEvent(entry *layoutEntry, eventX, eventY *int) bool {
|
|
|
|
*eventX -= entry.x
|
|
|
|
*eventY -= entry.y
|
2020-02-17 22:11:52 -05:00
|
|
|
|
2020-02-24 22:35:21 -05:00
|
|
|
if *eventX < 0 || *eventY < 0 || *eventX >= entry.width || *eventY >= entry.height {
|
2020-02-17 22:11:52 -05:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2020-02-24 22:35:21 -05:00
|
|
|
func (l *Layout) adjustEntryPlacement() {
|
|
|
|
width, height := l.getSize()
|
2020-02-17 22:11:52 -05:00
|
|
|
|
2020-02-24 22:35:21 -05:00
|
|
|
var expanderCount int
|
|
|
|
for _, entry := range l.entries {
|
|
|
|
if entry.widget.isVisible() && entry.widget.isExpanding() {
|
|
|
|
expanderCount++
|
|
|
|
}
|
|
|
|
}
|
2020-02-17 22:11:52 -05:00
|
|
|
|
2020-02-24 22:35:21 -05:00
|
|
|
var expanderWidth, expanderHeight int
|
|
|
|
if expanderCount > 0 {
|
|
|
|
contentWidth, contentHeight := l.getContentSize()
|
|
|
|
|
|
|
|
switch l.positionType {
|
|
|
|
case PositionTypeVertical:
|
|
|
|
expanderHeight = (height - contentHeight) / expanderCount
|
|
|
|
break
|
|
|
|
case PositionTypeHorizontal:
|
|
|
|
expanderWidth = (width - contentWidth) / expanderCount
|
|
|
|
break
|
|
|
|
}
|
2020-02-17 22:11:52 -05:00
|
|
|
|
2020-02-24 22:35:21 -05:00
|
|
|
expanderWidth = d2common.MaxInt(0, expanderWidth)
|
|
|
|
expanderHeight = d2common.MaxInt(0, expanderHeight)
|
|
|
|
}
|
|
|
|
|
|
|
|
var offsetX, offsetY int
|
|
|
|
for _, entry := range l.entries {
|
|
|
|
if !entry.widget.isVisible() {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if entry.widget.isExpanding() {
|
|
|
|
entry.width, entry.height = expanderWidth, expanderHeight
|
|
|
|
} else {
|
|
|
|
entry.width, entry.height = entry.widget.getSize()
|
|
|
|
}
|
|
|
|
|
|
|
|
switch l.positionType {
|
|
|
|
case PositionTypeVertical:
|
|
|
|
entry.y = offsetY
|
|
|
|
offsetY += entry.height
|
|
|
|
switch l.horizontalAlign {
|
|
|
|
case HorizontalAlignLeft:
|
|
|
|
entry.x = 0
|
|
|
|
break
|
|
|
|
case HorizontalAlignCenter:
|
|
|
|
entry.x = width/2 - entry.width/2
|
|
|
|
break
|
|
|
|
case HorizontalAlignRight:
|
|
|
|
entry.x = width - entry.width
|
|
|
|
break
|
|
|
|
}
|
|
|
|
break
|
|
|
|
case PositionTypeHorizontal:
|
|
|
|
entry.x = offsetX
|
|
|
|
offsetX += entry.width
|
|
|
|
switch l.verticalAlign {
|
|
|
|
case VerticalAlignTop:
|
|
|
|
entry.y = 0
|
|
|
|
break
|
|
|
|
case VerticalAlignMiddle:
|
|
|
|
entry.y = height/2 - entry.height/2
|
|
|
|
break
|
|
|
|
case VerticalAlignBottom:
|
|
|
|
entry.y = height - entry.height
|
|
|
|
break
|
|
|
|
}
|
|
|
|
break
|
|
|
|
case PositionTypeAbsolute:
|
|
|
|
entry.x, entry.y = entry.widget.getPosition()
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
2020-02-17 22:11:52 -05:00
|
|
|
}
|