/
scrollbox.go
179 lines (158 loc) · 5.75 KB
/
scrollbox.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
package render
import (
"errors"
"image/draw"
"strconv"
"time"
"github.com/oakmound/oak/physics"
)
// A ScrollBox is a renderable that draws other renderables to itself in a scrolling fashion,
// for animating ticker tape feeds or rotating background animations.
type ScrollBox struct {
*Sprite
pauseBool
Rs []Renderable
nextScrollX, nextScrollY time.Time
scrollRateX, scrollRateY time.Duration
View, reappear physics.Vector
dirX, dirY float64
}
// NewScrollBox returns a ScrollBox of the input renderables and the given dimensions.
// milliPerPixel represents the number of milliseconds it will take for the scroll box
// to move a horizontal or vertical pixel respectively. A negative value for milliPerPixel
// will move in a negative direction.
func NewScrollBox(rs []Renderable, milliPerPixelX, milliPerPixelY, width, height int) *ScrollBox {
s := new(ScrollBox)
s.pauseBool = pauseBool{playing: true}
s.Rs = rs
s.View = physics.NewVector(float64(width), float64(height))
s.SetScrollRate(milliPerPixelX, milliPerPixelY)
s.reappear = physics.NewVector(-1*s.dirX*s.View.X(), -1*s.dirY*s.View.Y())
s.nextScrollX = time.Now().Add(s.scrollRateX)
s.nextScrollY = time.Now().Add(s.scrollRateY)
s.Sprite = NewEmptySprite(0, 0, width, height)
s.drawRenderables()
return s
}
// DrawOffset draws this scroll box at +xOff, +yOff
func (s *ScrollBox) DrawOffset(buff draw.Image, xOff, yOff float64) {
s.update()
s.Sprite.DrawOffset(buff, xOff, yOff)
}
// Draw draws this scroll box to the input buffer
func (s *ScrollBox) Draw(buff draw.Image) {
s.DrawOffset(buff, 0, 0)
}
func (s *ScrollBox) update() {
if !s.playing {
return
}
// ScrollBoxes update in a discontinuous fashion with Animation and Sequence
// Both of the mentioned types will only ever advance one frame per update,
// whereas ScrollBox will move however many pixels it should have moved in
// the case of a long lag in draw calls.
if s.dirX != 0 && time.Now().After(s.nextScrollX) {
pixelsMovedX := int64(time.Since(s.nextScrollX))/int64(s.scrollRateX) + 1
s.nextScrollX = time.Now().Add(s.scrollRateX)
newS := NewEmptySprite(s.Sprite.X(), s.Sprite.Y(), int(s.View.X()), int(s.View.Y()))
newS.SetLayer(s.Sprite.GetLayer())
for _, m := range s.Rs {
m.ShiftX(-1 * s.dirX * float64(pixelsMovedX))
if s.shouldReappearX(m) {
m.ShiftX(-1 * s.reappear.X()) //Hope that delta is not higher than reappear...
}
}
*s.Sprite = *newS
s.drawRenderables()
}
if s.dirY != 0 && time.Now().After(s.nextScrollY) {
pixelsMovedY := int64(time.Since(s.nextScrollY))/int64(s.scrollRateY) + 1
s.nextScrollY = time.Now().Add(s.scrollRateY)
newS := NewEmptySprite(s.Sprite.X(), s.Sprite.Y(), int(s.View.X()), int(s.View.Y()))
newS.SetLayer(s.Sprite.GetLayer())
for _, m := range s.Rs {
m.ShiftY(-1 * s.dirY * float64(pixelsMovedY))
if s.shouldReappearY(m) {
m.ShiftY(-1 * s.reappear.Y()) //Hope that delta is not higher than reappear...
}
}
*s.Sprite = *newS
s.drawRenderables()
}
}
func (s *ScrollBox) shouldReappearY(m Renderable) bool {
return (s.dirY == 1 && m.GetY() <= s.reappear.Y()) || (s.dirY == -1 && m.GetY() >= s.reappear.Y())
}
func (s *ScrollBox) shouldReappearX(m Renderable) bool {
return (s.dirX == 1 && m.GetX() <= s.reappear.X()) || (s.dirX == -1 && m.GetX() >= s.reappear.X())
}
// Unpause resumes this scroll box's scrolling. Will delay the next scroll frame
// if already unpaused.
func (s *ScrollBox) Unpause() {
s.pauseBool.Unpause()
s.nextScrollX = time.Now().Add(s.scrollRateX)
s.nextScrollY = time.Now().Add(s.scrollRateY)
}
// SetReappearPos sets at what point renderables in this box should loop back on
// themselves to begin scrolling again
func (s *ScrollBox) SetReappearPos(x, y float64) error {
s.reappear = physics.NewVector(x, y)
if x*s.dirX > 0 {
return errors.New("ScrollBox will not loop with direction.X: " + strconv.Itoa(int(s.dirX)) + " and reappear.X: " + strconv.Itoa(int(x)))
}
if y*s.dirY > 0 {
return errors.New("ScrollBox will not loop with direction.Y: " + strconv.Itoa(int(s.dirY)) + " and reappear.X: " + strconv.Itoa(int(y)))
}
return nil
}
// SetScrollRate sets how fast this scroll box should rotate its x and y axes
// Maybe BUG, Consider: The next time that the box will scroll at is not updated
// immediately after this is called, only after the box is drawn.
func (s *ScrollBox) SetScrollRate(milliPerPixelX, milliPerPixelY int) {
s.dirX = 1
s.dirY = 1
if milliPerPixelX < 0 {
milliPerPixelX *= -1
s.dirX = -1
} else if milliPerPixelX == 0 {
s.dirX = 0
}
if milliPerPixelY < 0 {
milliPerPixelY *= -1
s.dirY = -1
} else if milliPerPixelY == 0 {
s.dirY = 0
}
s.scrollRateX = time.Duration(milliPerPixelX) * time.Millisecond
s.scrollRateY = time.Duration(milliPerPixelY) * time.Millisecond
}
// AddRenderable adds the inputs to this scrollbox.
func (s *ScrollBox) AddRenderable(rs ...Renderable) {
for _, r := range rs {
// We don't do this specific position swapping (which is to attempt
// to do what we think users actually want) at initialization,
// I suppose because we assume the inputs are at 0,0?
switch r.(type) {
case *Text:
r.SetPos(r.GetX()*-1, r.GetY()*-1)
}
s.Rs = append(s.Rs, r)
}
s.drawRenderables()
}
func (s *ScrollBox) drawRenderables() {
for _, r := range s.Rs {
// This might be the only place where we draw to a buffer that isn't
// oak's main buffer.
r.DrawOffset(s.GetRGBA(), -2*r.GetX(), -2*r.GetY())
if s.scrollRateY != 0 {
r.DrawOffset(s.GetRGBA(), -2*r.GetX(), -2*r.GetY()+s.reappear.Y())
}
if s.scrollRateX != 0 {
r.DrawOffset(s.GetRGBA(), -2*r.GetX()+s.reappear.X(), -2*r.GetY())
}
if s.scrollRateX != 0 && s.scrollRateY != 0 {
r.DrawOffset(s.GetRGBA(), -2*r.GetX()+s.reappear.X(), -2*r.GetY()+s.reappear.Y())
}
}
}