mirror of
https://github.com/OpenDiablo2/OpenDiablo2
synced 2024-12-26 12:06:24 -05:00
0d691dbffa
* Feat(KeyBindingMenu): Adds dynamic box system with scrollbar * Feat(Hotkeys): WIP Adds a lot of things * Feat(KeyBindingMenu): WIP Adds logic to binding * Feat(KeyBindingMenu): Fixes assignment logic * Feat(KeyBindingMenu): Adds buttons logic * Feat(KeyBindingMenu): Fixes sprites positions+add padding to Box * Feat(KeyBindingMenu): Adds label blinking cap * Feat(KeyBindingMenu): Removes commented func * Feat(KeyBindingMenu): Fixes lint errors and refactors a bit * Feat(KeyBindingMenu): Corrects few minor things from Grave * Feat(KeyBindingMenu): removes forgotten key to string mapping
386 lines
11 KiB
Go
386 lines
11 KiB
Go
package d2gui
|
|
|
|
import (
|
|
"log"
|
|
"math"
|
|
|
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
|
|
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2ui"
|
|
)
|
|
|
|
// LayoutScrollbar is a scrollbar that can be used with any layout
|
|
// and attaches to a main layout. You need to use a wrapper for your content
|
|
// as main layout in order for the scrollbar to work properly
|
|
type LayoutScrollbar struct {
|
|
sliderSprites []*d2ui.Sprite
|
|
gutterSprites []*d2ui.Sprite
|
|
|
|
parentLayout *Layout
|
|
targetLayout *Layout
|
|
sliderLayout *Layout
|
|
|
|
arrowUpLayout *Layout
|
|
arrowDownLayout *Layout
|
|
|
|
arrowUpSprite *d2ui.Sprite
|
|
arrowDownSprite *d2ui.Sprite
|
|
|
|
maxY int
|
|
minY int
|
|
arrowClickSliderOffset int
|
|
viewportSize int
|
|
contentSize int
|
|
|
|
clickedAtY int
|
|
mouseYOnSlider int
|
|
lastY int
|
|
gutterHeight int
|
|
sliderHeight int
|
|
contentToViewRatio float32
|
|
|
|
// isVisible bool
|
|
arrowUpClicked bool
|
|
arrowDownClicked bool
|
|
sliderClicked bool
|
|
}
|
|
|
|
const (
|
|
textSliderPartWidth = 12
|
|
textSliderPartHeight = 13
|
|
|
|
arrrowClickContentOffsetPercentage = 0.02
|
|
oneHundredPercent = 1.0
|
|
)
|
|
|
|
const (
|
|
textSliderPartArrowDownHollow int = iota + 8
|
|
textSliderPartArrowUpHollow
|
|
textSliderPartArrowDownFilled int = 10
|
|
textSliderPartArrowUpFilled int = 11
|
|
// textSliderPartSquare
|
|
textSliderPartInnerGutter int = 13
|
|
textSliderPartFillingVariation1 int = 14
|
|
)
|
|
|
|
// NewLayoutScrollbar attaches a scrollbar to the parentLayout to control the targetLayout
|
|
func NewLayoutScrollbar(
|
|
parentLayout *Layout,
|
|
targetLayout *Layout,
|
|
) *LayoutScrollbar {
|
|
parentW, parentH := parentLayout.GetSize()
|
|
_, targetH := targetLayout.GetSize()
|
|
gutterHeight := parentH - (2 * textSliderPartHeight)
|
|
viewportPercentage := oneHundredPercent - (float32(targetH-parentH) / float32(targetH))
|
|
sliderHeight := int(float32(gutterHeight) * viewportPercentage)
|
|
x, y := parentW-textSliderPartWidth, 0
|
|
|
|
ret := &LayoutScrollbar{
|
|
sliderSprites: []*d2ui.Sprite{},
|
|
gutterSprites: []*d2ui.Sprite{},
|
|
}
|
|
|
|
ret.contentToViewRatio = viewportPercentage
|
|
ret.contentToViewRatio = float32(targetH) / float32(gutterHeight)
|
|
ret.gutterHeight = gutterHeight
|
|
ret.sliderHeight = sliderHeight
|
|
ret.minY = y + textSliderPartHeight
|
|
ret.maxY = (y + parentH) - (textSliderPartHeight + sliderHeight)
|
|
ret.contentSize = targetH
|
|
ret.arrowClickSliderOffset = int(float32(sliderHeight) * arrrowClickContentOffsetPercentage)
|
|
ret.viewportSize = parentH
|
|
|
|
arrowUpLayout := parentLayout.AddLayout(PositionTypeAbsolute)
|
|
arrowUpLayout.SetSize(textSliderPartWidth, textSliderPartHeight)
|
|
arrowUpLayout.SetPosition(x, 0)
|
|
ret.arrowUpLayout = arrowUpLayout
|
|
|
|
gutterLayout := parentLayout.AddLayout(PositionTypeAbsolute)
|
|
gutterLayout.SetSize(textSliderPartWidth, gutterHeight)
|
|
gutterLayout.SetPosition(x, textSliderPartHeight)
|
|
|
|
sliderLayout := parentLayout.AddLayout(PositionTypeAbsolute)
|
|
sliderLayout.SetPosition(x, textSliderPartHeight)
|
|
sliderLayout.SetSize(textSliderPartWidth, sliderHeight)
|
|
sliderLayout.SetMouseClickHandler(ret.OnSliderMouseClick)
|
|
|
|
arrowDownLayout := parentLayout.AddLayout(PositionTypeAbsolute)
|
|
arrowDownLayout.SetSize(textSliderPartWidth, textSliderPartHeight)
|
|
arrowDownLayout.SetPosition(x, textSliderPartHeight+gutterHeight)
|
|
ret.arrowDownLayout = arrowDownLayout
|
|
|
|
ret.sliderLayout = sliderLayout
|
|
ret.parentLayout = parentLayout
|
|
ret.targetLayout = targetLayout
|
|
|
|
ret.parentLayout.AdjustEntryPlacement()
|
|
ret.targetLayout.AdjustEntryPlacement()
|
|
ret.sliderLayout.AdjustEntryPlacement()
|
|
|
|
return ret
|
|
}
|
|
|
|
// Load sets the scrollbar layouts and loads the sprites
|
|
func (scrollbar *LayoutScrollbar) Load(ui *d2ui.UIManager) error {
|
|
arrowUpX, arrowUpY := scrollbar.arrowUpLayout.ScreenPos()
|
|
arrowUpSprite, _ := ui.NewSprite(d2resource.TextSlider, d2resource.PaletteSky)
|
|
|
|
if err := arrowUpSprite.SetCurrentFrame(textSliderPartArrowUpFilled); err != nil {
|
|
return err
|
|
}
|
|
|
|
arrowUpSprite.SetPosition(arrowUpX, arrowUpY+textSliderPartHeight)
|
|
scrollbar.arrowUpSprite = arrowUpSprite
|
|
|
|
arrowDownX, arrowDownY := scrollbar.arrowDownLayout.ScreenPos()
|
|
arrowDownSprite, _ := ui.NewSprite(d2resource.TextSlider, d2resource.PaletteSky)
|
|
|
|
if err := arrowDownSprite.SetCurrentFrame(textSliderPartArrowDownFilled); err != nil {
|
|
return err
|
|
}
|
|
|
|
arrowDownSprite.SetPosition(arrowDownX, arrowDownY+textSliderPartHeight)
|
|
scrollbar.arrowDownSprite = arrowDownSprite
|
|
|
|
gutterParts := int(math.Ceil(float64(scrollbar.gutterHeight+(2*textSliderPartHeight)) / float64(textSliderPartHeight)))
|
|
sliderParts := int(math.Ceil(float64(scrollbar.sliderHeight) / float64(textSliderPartHeight)))
|
|
gutterX, gutterY := arrowUpX, arrowUpY+(2*textSliderPartHeight)-1
|
|
i := 0
|
|
|
|
for {
|
|
if i >= gutterParts {
|
|
break
|
|
}
|
|
|
|
f, _ := ui.NewSprite(d2resource.TextSlider, d2resource.PaletteSky)
|
|
|
|
if err := f.SetCurrentFrame(textSliderPartInnerGutter); err != nil {
|
|
return err
|
|
}
|
|
|
|
newY := gutterY + (i * (textSliderPartHeight - 1))
|
|
f.SetPosition(gutterX, newY)
|
|
|
|
scrollbar.gutterSprites = append(scrollbar.gutterSprites, f)
|
|
|
|
i++
|
|
}
|
|
|
|
i = 0
|
|
|
|
for {
|
|
if i >= sliderParts {
|
|
break
|
|
}
|
|
|
|
f, _ := ui.NewSprite(d2resource.TextSlider, d2resource.PaletteSky)
|
|
|
|
if err := f.SetCurrentFrame(textSliderPartFillingVariation1); err != nil {
|
|
return err
|
|
}
|
|
|
|
scrollbar.sliderSprites = append(scrollbar.sliderSprites, f)
|
|
|
|
i++
|
|
}
|
|
|
|
scrollbar.updateSliderSpritesPosition()
|
|
|
|
return nil
|
|
}
|
|
|
|
func (scrollbar *LayoutScrollbar) updateSliderSpritesPosition() {
|
|
scrollbar.sliderLayout.AdjustEntryPlacement()
|
|
sliderLayoutX, sliderLayoutY := scrollbar.sliderLayout.ScreenPos()
|
|
|
|
for i, s := range scrollbar.sliderSprites {
|
|
newY := sliderLayoutY + (i * (textSliderPartHeight - 1)) + textSliderPartHeight
|
|
s.SetPosition(sliderLayoutX-1, newY)
|
|
}
|
|
}
|
|
|
|
// OnSliderMouseClick affects the state of the slider
|
|
func (scrollbar *LayoutScrollbar) OnSliderMouseClick(event d2interface.MouseEvent) {
|
|
scrollbar.clickedAtY = event.Y()
|
|
scrollbar.lastY = scrollbar.clickedAtY
|
|
scrollbar.mouseYOnSlider = event.Y() - scrollbar.sliderLayout.Sy
|
|
}
|
|
|
|
func (scrollbar *LayoutScrollbar) moveScaledContentBy(offset int) int {
|
|
_, y := scrollbar.sliderLayout.GetPosition()
|
|
newY := y + offset
|
|
|
|
outOfBoundsUp := false
|
|
outOfBoundsDown := false
|
|
|
|
if newY > scrollbar.maxY {
|
|
newY = scrollbar.maxY
|
|
outOfBoundsDown = true
|
|
}
|
|
|
|
if newY < scrollbar.minY {
|
|
newY = scrollbar.minY
|
|
outOfBoundsUp = true
|
|
}
|
|
|
|
if !outOfBoundsUp && !outOfBoundsDown {
|
|
scrollbar.clickedAtY += offset
|
|
|
|
if scrollbar.targetLayout != nil {
|
|
contentX, contentY := scrollbar.targetLayout.GetPosition()
|
|
scaledOffset := int(math.Round(float64(float32(offset) * scrollbar.contentToViewRatio)))
|
|
newContentY := contentY - scaledOffset
|
|
scrollbar.targetLayout.SetPosition(contentX, newContentY)
|
|
}
|
|
}
|
|
|
|
if outOfBoundsDown && scrollbar.targetLayout != nil {
|
|
newContentY := -scrollbar.contentSize + scrollbar.viewportSize
|
|
scrollbar.targetLayout.SetPosition(0, newContentY)
|
|
}
|
|
|
|
if outOfBoundsUp && scrollbar.targetLayout != nil {
|
|
scrollbar.targetLayout.SetPosition(0, 0)
|
|
}
|
|
|
|
return newY
|
|
}
|
|
|
|
// OnMouseMove will affect the slider and the content depending on the state fof it
|
|
func (scrollbar *LayoutScrollbar) OnMouseMove(event d2interface.MouseMoveEvent) {
|
|
if !scrollbar.sliderClicked {
|
|
return
|
|
}
|
|
|
|
sliderX, _ := scrollbar.sliderLayout.GetPosition()
|
|
newY := scrollbar.moveScaledContentBy(event.Y() - scrollbar.clickedAtY)
|
|
|
|
scrollbar.sliderLayout.SetPosition(sliderX, newY)
|
|
scrollbar.updateSliderSpritesPosition()
|
|
}
|
|
|
|
// OnArrowUpClick will move the slider and the content up
|
|
func (scrollbar *LayoutScrollbar) OnArrowUpClick() {
|
|
sliderX, _ := scrollbar.sliderLayout.GetPosition()
|
|
newY := scrollbar.moveScaledContentBy(-scrollbar.arrowClickSliderOffset)
|
|
|
|
scrollbar.sliderLayout.SetPosition(sliderX, newY)
|
|
scrollbar.updateSliderSpritesPosition()
|
|
}
|
|
|
|
// OnArrowDownClick will move the slider and the content down
|
|
func (scrollbar *LayoutScrollbar) OnArrowDownClick() {
|
|
sliderX, _ := scrollbar.sliderLayout.GetPosition()
|
|
newY := scrollbar.moveScaledContentBy(scrollbar.arrowClickSliderOffset)
|
|
|
|
scrollbar.sliderLayout.SetPosition(sliderX, newY)
|
|
scrollbar.updateSliderSpritesPosition()
|
|
}
|
|
|
|
// SetSliderClicked sets the value of sliderClicked
|
|
func (scrollbar *LayoutScrollbar) SetSliderClicked(value bool) {
|
|
scrollbar.sliderClicked = value
|
|
}
|
|
|
|
// SetArrowUpClicked sets the value of sliderClicked
|
|
func (scrollbar *LayoutScrollbar) SetArrowUpClicked(value bool) {
|
|
var arrowSpriteFrame int
|
|
|
|
scrollbar.arrowUpClicked = value
|
|
|
|
if scrollbar.arrowUpClicked {
|
|
arrowSpriteFrame = textSliderPartArrowUpHollow
|
|
} else {
|
|
arrowSpriteFrame = textSliderPartArrowUpFilled
|
|
}
|
|
|
|
if err := scrollbar.arrowUpSprite.SetCurrentFrame(arrowSpriteFrame); err != nil {
|
|
log.Printf("unable to set arrow up sprite frame: %v", err)
|
|
}
|
|
}
|
|
|
|
// SetArrowDownClicked sets the value of sliderClicked
|
|
func (scrollbar *LayoutScrollbar) SetArrowDownClicked(value bool) {
|
|
var arrowSpriteFrame int
|
|
|
|
scrollbar.arrowDownClicked = value
|
|
|
|
if scrollbar.arrowDownClicked {
|
|
arrowSpriteFrame = textSliderPartArrowDownHollow
|
|
} else {
|
|
arrowSpriteFrame = textSliderPartArrowDownFilled
|
|
}
|
|
|
|
if err := scrollbar.arrowDownSprite.SetCurrentFrame(arrowSpriteFrame); err != nil {
|
|
log.Printf("unable to set arrow down sprite frame: %v", err)
|
|
}
|
|
}
|
|
|
|
// Advance updates the layouts according to the state of the arrown
|
|
func (scrollbar *LayoutScrollbar) Advance(elapsed float64) error {
|
|
if scrollbar.arrowDownClicked {
|
|
scrollbar.OnArrowDownClick()
|
|
}
|
|
|
|
if scrollbar.arrowUpClicked {
|
|
scrollbar.OnArrowUpClick()
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Render draws the scrollbar sprites on the given surface
|
|
func (scrollbar *LayoutScrollbar) Render(target d2interface.Surface) {
|
|
for _, s := range scrollbar.gutterSprites {
|
|
s.Render(target)
|
|
}
|
|
|
|
for _, s := range scrollbar.sliderSprites {
|
|
s.Render(target)
|
|
}
|
|
|
|
scrollbar.arrowUpSprite.Render(target)
|
|
scrollbar.arrowDownSprite.Render(target)
|
|
}
|
|
|
|
func (scrollbar *LayoutScrollbar) isInLayoutRect(layout *Layout, px, py int) bool {
|
|
ww, hh := layout.GetSize()
|
|
x, y := layout.Sx, layout.Sy
|
|
|
|
if px >= x && px <= x+ww && py >= y && py <= y+hh {
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// IsSliderClicked returns the state of the slider
|
|
func (scrollbar *LayoutScrollbar) IsSliderClicked() bool {
|
|
return scrollbar.sliderClicked
|
|
}
|
|
|
|
// IsArrowUpClicked returns the state of arrow up clicked
|
|
func (scrollbar *LayoutScrollbar) IsArrowUpClicked() bool {
|
|
return scrollbar.arrowUpClicked
|
|
}
|
|
|
|
// IsArrowDownClicked returns the state of arrow down clicked
|
|
func (scrollbar *LayoutScrollbar) IsArrowDownClicked() bool {
|
|
return scrollbar.arrowDownClicked
|
|
}
|
|
|
|
// IsInArrowUpRect checks if the given point is within the overlay layout rectangle
|
|
func (scrollbar *LayoutScrollbar) IsInArrowUpRect(px, py int) bool {
|
|
return scrollbar.isInLayoutRect(scrollbar.arrowUpLayout, px, py)
|
|
}
|
|
|
|
// IsInArrowDownRect checks if the given point is within the overlay layout rectangle
|
|
func (scrollbar *LayoutScrollbar) IsInArrowDownRect(px, py int) bool {
|
|
return scrollbar.isInLayoutRect(scrollbar.arrowDownLayout, px, py)
|
|
}
|
|
|
|
// IsInSliderRect checks if the given point is within the overlay layout rectangle
|
|
func (scrollbar *LayoutScrollbar) IsInSliderRect(px, py int) bool {
|
|
return scrollbar.isInLayoutRect(scrollbar.sliderLayout, px, py)
|
|
}
|