aboutsummaryrefslogtreecommitdiffstats
path: root/gui/layout/scroller.go
blob: b7fc3f5fd053e997c6afaf8d6e09d059155ae370 (plain) (blame)
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
}