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
|
package layout
import (
"image"
"image/color"
"image/draw"
"sync"
"volute/gui"
"volute/gui/win"
)
var _ Layout = &Scroller{}
type Scroller struct {
Background color.Color
Length int
ChildHeight int
Offset int
Gap int
Vertical bool
}
func (s Scroller) redraw(drw draw.Image, bounds image.Rectangle) {
col := s.Background
if col == nil {
col = image.Black
}
draw.Draw(drw, bounds, image.NewUniform(col), image.ZP, draw.Src)
}
func clamp(val, a, b int) int {
if a > b {
if val < b {
return b
}
if val > a {
return a
}
} else {
if val > b {
return b
}
if val < a {
return a
}
}
return val
}
func (s *Scroller) Intercept(env gui.Env) gui.Env {
evs := env.Events()
out, in := gui.MakeEventsChan()
drawChan := make(chan func(draw.Image) image.Rectangle)
ret := &muxEnv{out, drawChan}
var lastResize gui.Resize
var img draw.Image
img = image.NewRGBA(image.ZR)
var mu sync.Mutex
var over bool
go func() {
for dc := range drawChan {
mu.Lock()
// draw.Draw will not draw out of bounds, call should be inexpensive if element not visible
res := dc(img)
// Only send a draw call up if visibly changed
if res.Intersect(img.Bounds()) != image.ZR {
env.Draw() <- func(drw draw.Image) image.Rectangle {
draw.Draw(drw, lastResize.Rectangle, img, lastResize.Rectangle.Min, draw.Over)
return img.Bounds()
}
}
mu.Unlock()
}
}()
go func() {
for ev := range evs {
switch ev := ev.(type) {
case win.MoMove:
mu.Lock()
over = ev.Point.In(lastResize.Rectangle)
mu.Unlock()
case win.MoScroll:
if !over {
continue
}
mu.Lock()
oldoff := s.Offset
v := s.Length*s.ChildHeight + ((s.Length + 1) * s.Gap)
if s.Vertical {
h := lastResize.Dx()
s.Offset = clamp(s.Offset+ev.Point.X*16, h-v, 0)
} else {
h := lastResize.Dy()
s.Offset = clamp(s.Offset+ev.Point.Y*16, h-v, 0)
}
if oldoff != s.Offset {
s.redraw(img, img.Bounds())
in <- lastResize
}
mu.Unlock()
case gui.Resize:
mu.Lock()
lastResize = ev
img = image.NewRGBA(lastResize.Rectangle)
s.redraw(img, img.Bounds())
mu.Unlock()
in <- ev
default:
in <- ev
}
}
}()
return ret
}
func (s Scroller) Lay(bounds image.Rectangle) []image.Rectangle {
items := s.Length
ch := s.ChildHeight
gap := s.Gap
ret := make([]image.Rectangle, items)
Y := bounds.Min.Y + s.Offset + gap
for i := 0; i < items; i++ {
r := image.Rect(bounds.Min.X+gap, Y, bounds.Max.X-gap, Y+ch)
ret[i] = r
Y += ch + gap
}
return ret
}
|